1 /**
2  ** data_utils.h: Generic data template functions.
3  **/
4 
5 /*
6 Copyright (C) 2009 Exult Team
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21 */
22 
23 #ifndef INCL_DATA_UTILS
24 #define INCL_DATA_UTILS 1
25 
26 #include <algorithm>
27 #include <map>
28 #include <sstream>
29 #include <string>
30 #include <vector>
31 #include "exult_constants.h"
32 #include "utils.h"
33 #include "U7obj.h"
34 #include "databuf.h"
35 #include "msgfile.h"
36 #include "fnames.h"
37 #include "ignore_unused_variable_warning.h"
38 
39 /*
40  *  Generic vector data handler routines.
41  *  They all assume that the template class has the following
42  *  operators defined:
43  *      (1) operator< (which must be a strict weak order)
44  *      (2) operator==
45  *      (3) operator!=
46  *      (4) operator= that sets modified flag if needed.
47  *  They also assume that the vector is totally ordered
48  *  with the operator< -- using these functions will ensure
49  *  that this is the case.
50  */
51 template <typename T>
add_vector_info(const T & inf,std::vector<T> & vec)52 void add_vector_info(const T &inf, std::vector<T> &vec) {
53 	// Find using operator<.
54 	auto it = std::lower_bound(vec.begin(), vec.end(), inf);
55 	if (it == vec.end() || *it != inf)  // Not found.
56 		vec.insert(it, inf);    // Add new.
57 	else {  // Already exists.
58 		bool st = it->have_static();
59 		bool inv = it->is_invalid();
60 		it->set(inf);   // Replace information.
61 		if (st && inv && !inf.is_invalid() &&
62 		        !inf.from_patch() && !inf.was_modified())
63 			it->set_modified(false);
64 	}
65 }
66 template <typename T>
copy_vector_info(const std::vector<T> & from,std::vector<T> & to)67 void copy_vector_info(const std::vector<T> &from, std::vector<T> &to) {
68 	if (from.size()) {
69 		to.resize(from.size());
70 		std::copy(from.begin(), from.end(), to.begin());
71 	} else
72 		to.clear();
73 }
74 template <typename T>
set_vector_info(bool tf,std::vector<T> & vec)75 std::vector<T> &set_vector_info(bool tf, std::vector<T> &vec) {
76 	invalidate_vector(vec);
77 	if (!tf)
78 		clean_vector(vec);
79 	return vec;
80 }
81 template <typename T>
invalidate_vector(std::vector<T> & vec)82 void invalidate_vector(std::vector<T> &vec) {
83 	for (auto& elem : vec)
84 		elem.set_invalid(true);
85 }
86 
87 template <typename T>
clean_vector(std::vector<T> & vec)88 void clean_vector(std::vector<T> &vec) {
89 	vec.erase(std::remove_if(vec.begin(), vec.end(),
90 		[](T& elem) {
91 			return elem.is_invalid() && !elem.have_static();
92 		}
93 		), vec.end());
94 }
95 
96 template <class T, typename U>
Search_vector_data_single_wildcard(const std::vector<T> & vec,int src,U T::* dat)97 static const T *Search_vector_data_single_wildcard(
98     const std::vector<T> &vec,
99     int src,
100     U T::*dat
101 ) {
102 	if (vec.empty())    // Not found.
103 		return nullptr;
104 	T inf;
105 	inf.*dat = src;
106 	// Try finding exact match first.
107 	auto it = std::lower_bound(vec.begin(), vec.end(), inf);
108 	if (it == vec.end())    // Nowhere to be found.
109 		return nullptr;
110 	else if (*it == inf && !it->is_invalid())   // Have it already.
111 		return &*it;
112 	// Try wildcard shape.
113 	inf.*dat = -1;
114 	it = std::lower_bound(it, vec.end(), inf);
115 	if (it == vec.end() || *it != inf   // It just isn't there...
116 	        || it->is_invalid())   // ... or it is invalid.
117 		return nullptr;
118 	else    // At last!
119 		return &*it;
120 }
121 
122 template <class T>
Search_vector_data_double_wildcards(const std::vector<T> & vec,int frame,int quality,short T::* fr,short T::* qual)123 static const T *Search_vector_data_double_wildcards(
124     const std::vector<T> &vec,
125     int frame, int quality,
126     short T::*fr, short T::*qual
127 ) {
128 	if (vec.empty())
129 		return nullptr;   // No name.
130 	T inf;
131 	inf.*fr = frame;
132 	inf.*qual = quality;
133 	// Try finding exact match first.
134 	auto it = std::lower_bound(vec.begin(), vec.end(), inf);
135 	if (it == vec.end())    // Nowhere to be found.
136 		return nullptr;
137 	else if (*it == inf && !it->is_invalid())   // Have it already.
138 		return &*it;
139 	// We only have to search forward for a match.
140 	if (quality != -1) {
141 		if ((*it).*fr == frame) {
142 			// Maybe quality is to blame. Try wildcard quality.
143 			inf.*qual = -1;
144 			it = std::lower_bound(it, vec.end(), inf);
145 			if (it == vec.end())    // Nowhere to be found.
146 				return nullptr;
147 			else if (*it == inf && !it->is_invalid())   // We got it!
148 				return &*it;
149 		}
150 		// Maybe frame is to blame? Try search for specific
151 		// quality with wildcard frame.
152 		inf.*qual = quality;
153 		inf.*fr = -1;
154 		it = std::lower_bound(it, vec.end(), inf);
155 		if (it == vec.end())    // Nowhere to be found.
156 			return nullptr;
157 		else if (*it == inf && !it->is_invalid())   // We got it!
158 			return &*it;
159 		inf.*qual = -1;
160 	}
161 	// *Still* haven't found it. Last try: wildcard frame *and* quality.
162 	inf.*fr = -1;
163 	it = std::lower_bound(it, vec.end(), inf);
164 	if (it == vec.end() || *it != inf || *it != inf // It just isn't there...
165 	        || it->is_invalid())   // ... or it is invalid.
166 		return nullptr;
167 	else    // At last!
168 		return &*it;
169 }
170 
171 /*
172  *  Generic data handler routine.
173  */
174 template <typename T>
set_info(bool tf,T * & pt)175 T *set_info(bool tf, T *&pt) {
176 	if (!tf) {
177 		delete pt;
178 		pt = nullptr;
179 	} else if (!pt)
180 		pt = new T();
181 	return pt;
182 }
183 
184 /*
185  *  Get # entries of binary data file (with Exult extension).
186  */
Read_count(std::istream & in)187 inline int Read_count(std::istream &in) {
188 	int cnt = Read1(in);    // How the originals did it.
189 	if (cnt == 255)
190 		cnt = Read2(in);    // Exult extension.
191 	return cnt;
192 }
193 
194 /*
195  *  Write # entries of binary data file (with Exult extension).
196  */
Write_count(std::ofstream & out,int cnt)197 inline void Write_count(
198     std::ofstream &out,
199     int cnt
200 ) {
201 	if (cnt >= 255) {
202 		// Exult extension.
203 		out.put(static_cast<char>(255));
204 		Write2(out, cnt);
205 	} else
206 		out.put(static_cast<char>(cnt));
207 }
208 
209 /*
210  *  Generic base data-agnostic reader class.
211  */
212 class Base_reader {
213 protected:
214 	bool haveversion;
read_data(std::istream & in,size_t index,int version,bool patch,Exult_Game game,bool binary)215 	virtual void read_data(std::istream &in, size_t index, int version,
216 	                       bool patch, Exult_Game game, bool binary) {
217 		ignore_unused_variable_warning(in, index, version, patch, game, binary);
218 	}
219 	// Binary data file.
read_binary_internal(std::istream & in,bool patch,Exult_Game game)220 	void read_binary_internal(std::istream &in, bool patch, Exult_Game game) {
221 		int vers = 0;
222 		if (haveversion)
223 			vers = Read1(in);
224 		size_t cnt = Read_count(in);
225 		for (size_t j = 0; j < cnt; j++)
226 			read_data(in, j, vers, patch, game, true);
227 	}
228 public:
Base_reader(bool h)229 	Base_reader(bool h)
230 		:   haveversion(h)
231 	{  }
232 	virtual ~Base_reader() = default;
233 	// Text data file.
parse(std::vector<std::string> & strings,int version,bool patch,Exult_Game game)234 	void parse(std::vector<std::string> &strings, int version, bool patch, Exult_Game game) {
235 		for (size_t j = 0; j < strings.size(); j++) {
236 			if (!strings[j].empty()) {
237 				std::istringstream strin(strings[j], std::ios::in);
238 				read_data(strin, j, version, patch, game, false);
239 			}
240 		}
241 		strings.clear();
242 	}
243 	// Binary data file.
read(const char * fname,bool patch,Exult_Game game)244 	void read(const char *fname, bool patch, Exult_Game game) {
245 		if (!U7exists(fname))
246 			return;
247 		std::ifstream fin;
248 		U7open(fin, fname);
249 		read_binary_internal(fin, patch, game);
250 		fin.close();
251 	}
252 	// Binary resource file.
read(Exult_Game game,int resource)253 	void read(Exult_Game game, int resource) {
254 		// Only for BG and SI.
255 		if (game != BLACK_GATE && game != SERPENT_ISLE)
256 			return;
257 		/*  ++++ Not because of ES.
258 		snprintf(buf, 50, "config/%s", fname);
259 		str_int_pair resource = game->get_resource(buf);
260 		U7object txtobj(resource.str, resource.num);
261 		*/
262 		bool bg = game == BLACK_GATE;
263 		const char *flexfile =
264 		    bg ? BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX)
265 		    : BUNDLE_CHECK(BUNDLE_EXULT_SI_FLX, EXULT_SI_FLX);
266 		U7object txtobj(flexfile, resource);
267 		std::size_t len;
268 		auto txt = txtobj.retrieve(len);
269 		std::string databuf(reinterpret_cast<char*>(txt.get()), len);
270 		std::istringstream strin(databuf, std::ios::in | std::ios::binary);
271 		read_binary_internal(strin, false, game);
272 	}
273 };
274 
275 class ID_reader_functor {
276 public:
operator()277 	int operator()(std::istream &in, int index, int version, bool binary) {
278 		ignore_unused_variable_warning(index, version);
279 		return binary ? Read2(in) : ReadInt(in);
280 	}
281 };
282 
283 /*
284  *  Post-read data transformation functors.
285  */
286 template <class Info>
287 class Null_functor {
288 public:
operator()289 	void operator()(std::istream &in, int version, bool patch,
290 	                Exult_Game game, Info &info) {
291 		ignore_unused_variable_warning(in, info, version, patch, game);
292 	}
293 };
294 
295 template <int flag, class Info>
296 class Patch_flags_functor {
297 public:
operator()298 	void operator()(std::istream &in, int version, bool patch,
299 	                Exult_Game game, Info &info) {
300 		ignore_unused_variable_warning(in, version, game);
301 		if (patch)
302 			info.frompatch_flags |= flag;
303 	}
304 };
305 
306 /*
307  *  Generic functor-based reader class for maps.
308  */
309 template < class Info, class Functor, class Transform = Null_functor<Info>,
310          class ReadID = ID_reader_functor >
311 class Functor_multidata_reader : public Base_reader {
312 protected:
313 	std::map<int, Info> &info;
314 	Functor reader;
315 	Transform postread;
316 	ReadID idread;
read_data(std::istream & in,size_t index,int version,bool patch,Exult_Game game,bool binary)317 	void read_data(std::istream &in, size_t index, int version,
318 	               bool patch, Exult_Game game, bool binary) override {
319 		int id = idread(in, index, version, binary);
320 		if (id >= 0) {
321 			Info &inf = info[id];
322 			reader(in, version, patch, game, inf);
323 			postread(in, version, patch, game, inf);
324 		}
325 	}
326 public:
327 	Functor_multidata_reader(std::map<int, Info> &nfo, bool h = false)
Base_reader(h)328 		:   Base_reader(h), info(nfo)
329 	{  }
330 };
331 
332 /*
333  *  Generic functor-based reader class.
334  */
335 template <class Info, class Functor, class Transform = Null_functor<Info> >
336 class Functor_data_reader : public Base_reader {
337 protected:
338 	Info &info;
339 	Functor reader;
340 	Transform postread;
read_data(std::istream & in,size_t index,int version,bool patch,Exult_Game game,bool binary)341 	void read_data(std::istream &in, size_t index, int version,
342 	               bool patch, Exult_Game game, bool binary) override {
343 		ignore_unused_variable_warning(index, binary);
344 		reader(in, version, patch, game, info);
345 		postread(in, version, patch, game, info);
346 	}
347 public:
348 	Functor_data_reader(Info &nfo, bool h = false)
Base_reader(h)349 		:   Base_reader(h), info(nfo)
350 	{  }
351 };
352 
353 /*
354  *  Data reader functors.
355  */
356 template <typename T, class Info, T Info::*data>
357 class Text_reader_functor {
358 public:
operator()359 	bool operator()(std::istream &in, int version, bool patch,
360 	                Exult_Game game, Info &info) {
361 		ignore_unused_variable_warning(version, patch, game);
362 		info.*data = ReadInt(in);
363 		return true;
364 	}
365 };
366 
367 template <typename T, class Info, T Info::*data1, T Info::*data2>
368 class Text_pair_reader_functor {
369 public:
operator()370 	bool operator()(std::istream &in, int version, bool patch,
371 	                Exult_Game game, Info &info) {
372 		ignore_unused_variable_warning(version, patch, game);
373 		info.*data1 = ReadInt(in, -1);
374 		info.*data2 = ReadInt(in, -1);
375 		return true;
376 	}
377 };
378 
379 template <typename T, class Info, T Info::*data, int bit>
380 class Bit_text_reader_functor {
381 public:
operator()382 	bool operator()(std::istream &in, int version, bool patch,
383 	                Exult_Game game, Info &info) {
384 		ignore_unused_variable_warning(version, patch, game);
385 		// For backwards compatibility.
386 		bool biton = ReadInt(in, 1) != 0;
387 		if (biton)
388 			info.*data |= (static_cast<T>(1) << bit);
389 		else
390 			info.*data &= ~(static_cast<T>(1) << bit);
391 		return true;
392 	}
393 };
394 
395 template <typename T, class Info, T Info::*data>
396 class Bit_field_text_reader_functor {
397 public:
operator()398 	bool operator()(std::istream &in, int version, bool patch,
399 	                Exult_Game game, Info &info) {
400 		ignore_unused_variable_warning(version, patch, game);
401 		int size = 8 * sizeof(T); // Bit count.
402 		int bit = 0;
403 		T flags = 0;
404 		while (in.good() && bit < size) {
405 			if (ReadInt(in) != 0)
406 				flags |= (static_cast<T>(1) << bit);
407 			else
408 				flags &= ~(static_cast<T>(1) << bit);
409 			bit++;
410 		}
411 		info.*data = flags;
412 		return true;
413 	}
414 };
415 
416 template <typename T, class Info, T Info::*data, unsigned pad>
417 class Binary_reader_functor {
418 public:
operator()419 	bool operator()(std::istream &in, int version, bool patch,
420 	                Exult_Game game, Info &info) {
421 		ignore_unused_variable_warning(version, patch, game);
422 		in.read(reinterpret_cast<char *>(&(info.*data)), sizeof(T));
423 		if (pad)    // Skip some bytes.
424 			in.ignore(pad);
425 		return true;
426 	}
427 };
428 
429 template < typename T1, typename T2, class Info,
430          T1 Info::*data1, T2 Info::*data2, unsigned pad >
431 class Binary_pair_reader_functor {
432 public:
operator()433 	bool operator()(std::istream &in, int version, bool patch,
434 	                Exult_Game game, Info &info) {
435 		ignore_unused_variable_warning(version, patch, game);
436 		in.read(reinterpret_cast<char *>(&(info.*data1)), sizeof(T1));
437 		in.read(reinterpret_cast<char *>(&(info.*data2)), sizeof(T2));
438 		if (pad)    // Skip some bytes.
439 			in.ignore(pad);
440 		return true;
441 	}
442 };
443 
444 template <typename T, class Info, T *Info::*data>
445 class Class_reader_functor {
446 public:
operator()447 	bool operator()(std::istream &in, int version, bool patch,
448 	                Exult_Game game, Info &info) {
449 		T *cls = new T();
450 		cls->set_patch(patch);  // Set patch flag.
451 		if (!cls->read(in, version, game)) {
452 			delete cls;
453 			return false;
454 		}
455 		T *&pt = info.*data;
456 		if (cls->is_invalid() && pt) {
457 			// 'Delete old' flag.
458 			delete pt;
459 			pt = nullptr;
460 			delete cls;
461 			return false;
462 		}
463 		if (!patch)     // This is a static data file.
464 			info.have_static_flags |= T::get_info_flag();
465 		// Delete old.
466 		delete pt;
467 		pt = cls;
468 		return true;
469 	}
470 };
471 
472 template <typename T, class Info, std::vector<T> Info::*data>
473 class Vector_reader_functor {
474 public:
operator()475 	bool operator()(std::istream &in, int version, bool patch,
476 	                Exult_Game game, Info &info) {
477 		T cls;
478 		if (!patch)
479 			cls.set_static(true);
480 		cls.set_patch(patch);
481 		cls.read(in, version, game);
482 		std::vector<T> &vec = info.*data;
483 		add_vector_info(cls, vec);
484 		return true;
485 	}
486 };
487 
488 /*
489  *  Reads text data file and parses it according to passed
490  *  parser functions.
491  */
492 template <class Parser>
Read_text_data_file(const char * fname,Parser * parsers[],const char * sections[],int numsections,bool editing,Exult_Game game,int resource)493 static void Read_text_data_file(
494     const char *fname,          // Name of file to read, sans extension
495     Parser *parsers[],          // What to use to parse data.
496     const char *sections[],     // The names of the sections
497     int numsections,            // Number of sections
498     bool editing,
499     Exult_Game game,
500     int resource
501 ) {
502 	int static_version = 1;
503 	int patch_version = 1;
504 	std::vector<std::vector<std::string> > static_strings;
505 	std::vector<std::vector<std::string> > patch_strings;
506 	char buf[50];
507 	if (game == BLACK_GATE || game == SERPENT_ISLE) {
508 		/*  ++++ Not because of ES.
509 		snprintf(buf, 50, "config/%s", fname);
510 		str_int_pair resource = game->get_resource(buf);
511 		U7object txtobj(resource.str, resource.num);
512 		*/
513 		bool bg = game == BLACK_GATE;
514 		const char *flexfile =
515 		    bg ? BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX)
516 		    : BUNDLE_CHECK(BUNDLE_EXULT_SI_FLX, EXULT_SI_FLX);
517 		IExultDataSource ds(flexfile, resource);
518 		static_version = Read_text_msg_file_sections(&ds,
519 		                 static_strings, sections, numsections);
520 	} else {
521 		try {
522 			snprintf(buf, 50, "<STATIC>/%s.txt", fname);
523 			std::ifstream in;
524 			U7open(in, buf, false);
525 			static_version = Read_text_msg_file_sections(in,
526 			                 static_strings, sections, numsections);
527 			in.close();
528 		} catch (std::exception &e) {
529 			if (!editing) {
530 				for (int i = 0; i < numsections; i++)
531 					delete parsers[i];
532 				throw;
533 			}
534 			static_strings.resize(numsections);
535 		}
536 	}
537 	patch_strings.resize(numsections);
538 	snprintf(buf, 50, "<PATCH>/%s.txt", fname);
539 	if (U7exists(buf)) {
540 		std::ifstream in;
541 		U7open(in, buf, false);
542 		patch_version = Read_text_msg_file_sections(in, patch_strings,
543 		                sections, numsections);
544 		in.close();
545 	}
546 	for (int i = 0; i < numsections; i++) {
547 		parsers[i]->parse(static_strings[i], static_version, false, game);
548 		parsers[i]->parse(patch_strings[i], patch_version, true, game);
549 	}
550 	static_strings.clear();
551 	patch_strings.clear();
552 }
553 
554 /*
555  *  Generic base data-agnostic writer class.
556  */
557 class Base_writer {
558 protected:
559 	const char *name;
560 	const int version;
561 	int cnt;
check_write()562 	virtual int check_write() {
563 		return 0;
564 	}
write_data(std::ostream & out,Exult_Game game)565 	virtual void write_data(std::ostream &out, Exult_Game game) {
566 		ignore_unused_variable_warning(out, game);
567 	}
568 public:
569 	Base_writer(const char *s, int v = -1)
name(s)570 		:   name(s), version(v), cnt(-1)
571 	{  }
572 	virtual ~Base_writer() = default;
check()573 	int check() {
574 		// Return cached value, if any.
575 		return cnt > -1 ? cnt : cnt = check_write();
576 	}
write_text(std::ostream & out,Exult_Game game)577 	void write_text
578 	(
579 	    std::ostream &out,
580 	    Exult_Game game
581 	) {
582 		if (cnt <= 0)   // Nothing to do.
583 			return;
584 		// Section is not empty.
585 		out << "%%section " << name << std::endl;
586 		write_data(out, game);
587 		out << "%%endsection" << std::endl;
588 	}
write_binary(Exult_Game game)589 	void write_binary
590 	(
591 	    Exult_Game game
592 	) {
593 		if (cnt <= 0)   // Nothing to do.
594 			return;
595 		std::ofstream fout; // Open file.
596 		U7open(fout, name);
597 		if (version >= 0)   // container.dat has version #.
598 			fout.put(version);
599 		Write_count(fout, cnt); // Object count, with Exult extension.
600 		write_data(fout, game);
601 		fout.close();
602 	}
603 };
604 
605 /*
606  *  Generic functor-based writer class for maps.
607  */
608 template <class Info, class Functor>
609 class Functor_multidata_writer : public Base_writer {
610 protected:
611 	std::map<int, Info> &info;
612 	const int numshapes;
613 	Functor writer;
check_write()614 	int check_write() override {
615 		int num = 0;
616 		for (auto& kvpair : info)
617 			if (writer(kvpair.second))
618 				num++;
619 		return num;
620 	}
write_data(std::ostream & out,Exult_Game game)621 	void write_data(std::ostream &out, Exult_Game game) override {
622 		for (auto& kvpair : info)
623 			if (writer(kvpair.second))
624 				writer(out, kvpair.first, game, kvpair.second);
625 	}
626 public:
627 	Functor_multidata_writer(const char *s, std::map<int, Info> &nfo,
628 	                         int n, int v = -1)
Base_writer(s,v)629 		:   Base_writer(s, v), info(nfo), numshapes(n) {
630 		check();
631 	}
632 };
633 
634 /*
635  *  Generic functor-based writer class.
636  */
637 template <class Info, class Functor>
638 class Functor_data_writer : public Base_writer {
639 protected:
640 	Info &info;
641 	Functor writer;
check_write()642 	int check_write() override {
643 		return writer(info) ? 1 : 0;
644 	}
write_data(std::ostream & out,Exult_Game game)645 	void write_data(std::ostream &out, Exult_Game game) override {
646 		if (writer(info))
647 			writer(out, -1, game, info);
648 	}
649 public:
650 	Functor_data_writer(const char *s, Info &nfo, int v = -1)
Base_writer(s,v)651 		:   Base_writer(s, v), info(nfo) {
652 		check();
653 	}
654 };
655 
656 
657 /*
658  *  Base flag checker functor.
659  */
660 template <int flag, class Info>
661 class Flag_check_functor {
662 public:
operator()663 	bool operator()(Info &info) {
664 		return (info.modified_flags | info.frompatch_flags) & flag;
665 	}
666 };
667 
WriteIndex(std::ostream & out,int index)668 inline void WriteIndex(std::ostream &out, int index) {
669 	if (index >= 0)
670 		WriteInt(out, index);
671 }
672 
673 /*
674  *  Data checker and writer functors.
675  */
676 template <int flag, typename T, class Info, T Info::*data>
677 class Text_writer_functor {
678 	Flag_check_functor<flag, Info> check;
679 public:
operator()680 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
681 		ignore_unused_variable_warning(game);
682 		out << ":";
683 		WriteIndex(out, index);
684 		WriteInt(out, info.*data, true);
685 	}
operator()686 	bool operator()(Info &info) {
687 		return check(info);
688 	}
689 };
690 
691 template <int flag, typename T, class Info, T Info::*data1, T Info::*data2>
692 class Text_pair_writer_functor {
693 	Flag_check_functor<flag, Info> check;
694 public:
operator()695 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
696 		ignore_unused_variable_warning(game);
697 		out << ":";
698 		WriteIndex(out, index);
699 		WriteInt(out, info.*data1);
700 		WriteInt(out, info.*data2, true);
701 	}
operator()702 	bool operator()(Info &info) {
703 		return check(info);
704 	}
705 };
706 
707 template <int flag, typename T, class Info, T Info::*data, int bit>
708 class Bit_text_writer_functor {
709 	Flag_check_functor<flag, Info> check;
710 public:
operator()711 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
712 		ignore_unused_variable_warning(game);
713 		bool val = ((info.*data) & (static_cast<T>(1) << bit));
714 		out << ":";
715 		WriteIndex(out, index);
716 		WriteInt(out, val, true);
717 	}
operator()718 	bool operator()(Info &info) {
719 		return check(info);
720 	}
721 };
722 
723 template <int flag, typename T, class Info, T Info::*data>
724 class Bit_field_text_writer_functor {
725 	Flag_check_functor<flag, Info> check;
726 public:
operator()727 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
728 		ignore_unused_variable_warning(game);
729 		out << ":";
730 		WriteIndex(out, index);
731 		int size = 8 * sizeof(T) - 1; // Bit count.
732 		int bit = 0;
733 		T flags = info.*data;
734 		while (bit < size) {
735 			out << static_cast<bool>((flags & (static_cast<T>(1) << bit)) != 0) << '/';
736 			bit++;
737 		}
738 		out << static_cast<bool>((flags & (static_cast<T>(1) << size)) != 0) << std::endl;
739 	}
operator()740 	bool operator()(Info &info) {
741 		return check(info);
742 	}
743 };
744 
745 template <int flag, typename T, class Info, T Info::*data, int pad>
746 class Binary_writer_functor {
747 	Flag_check_functor<flag, Info> check;
748 public:
operator()749 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
750 		ignore_unused_variable_warning(game);
751 		Write2(out, index);
752 		out.write(reinterpret_cast<char *>(&(info.*data)), sizeof(T));
753 		for (int i = 0; i < pad; i++)
754 			out.put(0);
755 	}
operator()756 	bool operator()(Info &info) {
757 		return check(info);
758 	}
759 };
760 
761 template < int flag, typename T1, typename T2, class Info,
762          T1 Info::*data1, T2 Info::*data2, int pad >
763 class Binary_pair_writer_functor {
764 	Flag_check_functor<flag, Info> check;
765 public:
operator()766 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
767 		ignore_unused_variable_warning(game);
768 		Write2(out, index);
769 		out.write(reinterpret_cast<char *>(&(info.*data1)), sizeof(T1));
770 		out.write(reinterpret_cast<char *>(&(info.*data2)), sizeof(T2));
771 		for (int i = 0; i < pad; i++)
772 			out.put(0);
773 	}
operator()774 	bool operator()(Info &info) {
775 		return check(info);
776 	}
777 };
778 
779 template <typename T, class Info, T *Info::*data>
780 class Class_writer_functor {
781 public:
operator()782 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
783 		T *cls = info.*data;
784 		if (!cls) { // Write 'remove' block.
785 			if (!T::is_binary)  // Text entry.
786 				out << ':' << index << "/-255" << std::endl;
787 			else if (T::entry_size >= 3) {
788 				// T::entry_size should be >= 3!
789 				// For stupid compilers...
790 				const size_t nelems = T::entry_size >= 3 ? T::entry_size : 1;
791 				auto *buf = new unsigned char[nelems];
792 				unsigned char *ptr = buf;
793 				Write2(ptr, index);
794 				if (T::entry_size >= 4)
795 					memset(ptr, 0, T::entry_size - 3);
796 				buf[T::entry_size - 1] = 0xff;
797 				out.write(reinterpret_cast<const char *>(buf), T::entry_size);
798 				delete [] buf;
799 			}
800 		} else
801 			cls->write(out, index, game);
802 	}
operator()803 	bool operator()(Info &info) {
804 		T *cls = info.*data;
805 		if (!cls)
806 			return (info.have_static_flags & T::get_info_flag()) != 0;
807 		return (cls->need_write());
808 	}
809 };
810 
811 template <typename T, class Info, std::vector<T> Info::*data>
812 class Vector_writer_functor {
need_write(T & inf)813 	bool need_write(T &inf) const {
814 		return inf.need_write() || (inf.is_invalid() && inf.have_static());
815 	}
816 public:
operator()817 	void operator()(std::ostream &out, int index, Exult_Game game, Info &info) {
818 		std::vector<T> &vec = info.*data;
819 		for (auto& elem : vec)
820 			if (need_write(elem))
821 				elem.write(out, index, game);
822 	}
operator()823 	bool operator()(Info &info) {
824 		std::vector<T> &vec = info.*data;
825 		if (vec.empty())    // Nothing to do.
826 			return false;
827 		for (auto& elem : vec)
828 			if (need_write(elem))
829 				return true;
830 		return false;
831 	}
832 };
833 
834 /*
835  *  Writes text data file according to passed writer functions.
836  */
837 template <class Writer>
Write_text_data_file(const char * fname,Writer * writers[],int numsections,int version,Exult_Game game)838 static void Write_text_data_file(
839     const char *fname,      // Name of file to read, sans extension
840     Writer *writers[],      // What to use to write data.
841     int numsections,        // Number of sections
842     int version,
843     Exult_Game game
844 ) {
845 	int cnt = 0;
846 	for (int i = 0; i < numsections; i++)
847 		cnt += writers[i]->check();
848 	if (!cnt) {
849 		// Nothing to do but delete the writers.
850 		for (int i = 0; i < numsections; i++)
851 			delete writers[i];
852 		return;
853 	}
854 	std::ofstream out;
855 	char buf[50];
856 	snprintf(buf, 50, "<PATCH>/%s.txt", fname);
857 	U7open(out, buf, true); // (It's a text file.)
858 	out << "#\tExult " << VERSION << " text message file."
859 	    << "\tWritten by ExultStudio." << std::endl;
860 	out << "%%section version" << std::endl
861 	    << ":" << version << std::endl
862 	    << "%%endsection" << std::endl;
863 	for (int i = 0; i < numsections; i++) {
864 		writers[i]->write_text(out, game);
865 		delete writers[i];
866 	}
867 	out.close();
868 }
869 
870 #endif
871