1 /**
2  * BUILD ART file editing tool
3  * @author Jonathon Fowler
4  * @license Artistic License 2.0 (http://www.perlfoundation.org/artistic_license_2_0)
5  */
6 
7 #include <iostream>
8 #include <fstream>
9 #include <string>
10 #include <cstdlib>
11 #include <cstring>
12 
13 using namespace std;
14 
usage()15 void usage()
16 {
17 	cout << "BUILD ART file editing tool" << endl;
18 	cout << "Copyright (C) 2008 Jonathon Fowler <jf@jonof.id.au>" << endl;
19 	cout << "Released under the Artistic License 2.0" << endl;
20 	cout << endl;
21 	cout << "  arttool info [tilenum]" << endl;
22 	cout << "    Display information about a specific tile, or all if none is specified" << endl;
23 	cout << endl;
24 	cout << "  arttool create [options]" << endl;
25 	cout << "    -f <filenum>   Selects which numbered ART file to create (default 0)" << endl;
26 	cout << "    -o <offset>    Specifies the first tile in the file (default 0)" << endl;
27 	cout << "    -n <ntiles>    The number of tiles for the art file (default 256)" << endl;
28 	cout << "    Creates an empty ART file named 'tilesXXX.art'" << endl;
29 	cout << endl;
30 	cout << "  arttool addtile [options] <tilenum> <filename>" << endl;
31 	cout << "    -x <pixels>    X-centre" << endl;
32 	cout << "    -y <pixels>    Y-centre" << endl;
33 	cout << "    -ann <frames>  Animation frame span" << endl;
34 	cout << "    -ant <type>    Animation type (0=none, 1=oscillate, 2=forward, 3=reverse)" << endl;
35 	cout << "    -ans <speed>   Animation speed" << endl;
36 	cout << "    Adds a tile to the 'tilesXXX.art' set from a TGA or PCX source" << endl;
37 	cout << endl;
38 	cout << "  arttool rmtile <tilenum>" << endl;
39 	cout << "    Removes a tile from the 'tilesXXX.art' set" << endl;
40 	cout << endl;
41 	cout << "  arttool exporttile <tilenum>" << endl;
42 	cout << "    Exports a tile from the 'tilesXXX.art' set to a PCX file" << endl;
43 	cout << endl;
44 	cout << "  arttool tileprop [options] <tilenum>" << endl;
45 	cout << "    -x <pixels>    X-centre" << endl;
46 	cout << "    -y <pixels>    Y-centre" << endl;
47 	cout << "    -ann <frames>  Animation frame span, may be -ve" << endl;
48 	cout << "    -ant <type>    Animation type (0=none, 1=oscillate, 2=forward, 3=reverse)" << endl;
49 	cout << "    -ans <speed>   Animation speed" << endl;
50 	cout << "    Changes tile properties" << endl;
51 	cout << endl;
52 }
53 
54 class ARTFile {
55 private:
56 	string filename_;
57 	int localtilestart_;
58 	int localtileend_;
59 	short * tilesizx_;
60 	short * tilesizy_;
61 	int * picanm_;
62 	int datastartoffset_;
63 
64 	// for removing or replacing tile data
65 	int markprelength_, markskiplength_, markpostlength_;
66 	char * insert_;
67 	int insertlen_;
68 
writeShort(ofstream & ofs,short s)69 	void writeShort(ofstream &ofs, short s)
70 	{
71 		char d[2] = { (char)(s&255), (char)((s>>8)&255) };
72 		ofs.write(d, 2);
73 	}
74 
writeLong(ofstream & ofs,int l)75 	void writeLong(ofstream &ofs, int l)
76 	{
77 		char d[4] = { (char)(l&255), (char)((l>>8)&255), (char)((l>>16)&255), (char)((l>>24)&255) };
78 		ofs.write(d, 4);
79 	}
80 
readShort(ifstream & ifs)81 	short readShort(ifstream &ifs)
82 	{
83 		unsigned char d[2];
84 		unsigned short s;
85 		ifs.read((char *) d, 2);
86 		s = (unsigned short)d[0];
87 		s |= (unsigned short)d[1] << 8;
88 		return (short)s;
89 	}
90 
readLong(ifstream & ifs)91 	int readLong(ifstream &ifs)
92 	{
93 		unsigned char d[4];
94 		unsigned int l;
95 		ifs.read((char *) d, 4);
96 		l = (unsigned int)d[0];
97 		l |= (unsigned int)d[1] << 8;
98 		l |= (unsigned int)d[2] << 16;
99 		l |= (unsigned int)d[3] << 24;
100 		return (int)l;
101 	}
102 
dispose()103 	void dispose()
104 	{
105 		if (tilesizx_) delete [] tilesizx_;
106 		if (tilesizy_) delete [] tilesizy_;
107 		if (picanm_) delete [] picanm_;
108 		if (insert_) delete [] insert_;
109 
110 		insert_ = 0;
111 		insertlen_ = 0;
112 	}
113 
load()114 	void load()
115 	{
116 		ifstream infile(filename_.c_str(), ios::in | ios::binary);
117 		int i, ntiles;
118 
119 		if (infile.is_open()) {
120 			do {
121 				if (readLong(infile) != 1) {
122 					break;
123 				}
124 				readLong(infile);	// skip the numtiles
125 				dispose();
126 
127 				localtilestart_ = readLong(infile);
128 				localtileend_   = readLong(infile);
129 				ntiles = localtileend_ - localtilestart_ + 1;
130 
131 				tilesizx_ = new short[ntiles];
132 				tilesizy_ = new short[ntiles];
133 				picanm_   = new int[ntiles];
134 
135 				for (i = 0; i < ntiles; i++) {
136 					tilesizx_[i] = readShort(infile);
137 				}
138 				for (i = 0; i < ntiles; i++) {
139 					tilesizy_[i] = readShort(infile);
140 				}
141 				for (i = 0; i < ntiles; i++) {
142 					picanm_[i] = readLong(infile);
143 				}
144 
145 				datastartoffset_ = infile.tellg();
146 
147 			} while (0);
148 
149 			infile.close();
150 		}
151 	}
152 
153 public:
ARTFile(string filename)154 	ARTFile(string filename)
155 	 : filename_(filename), localtilestart_(0), localtileend_(-1),
156 	   tilesizx_(0), tilesizy_(0), picanm_(0),
157 	   markprelength_(0), markskiplength_(0), markpostlength_(0),
158 	   insert_(0), insertlen_(0)
159 	{
160 		load();
161 	}
162 
~ARTFile()163 	~ARTFile()
164 	{
165 		dispose();
166 	}
167 
168 	/**
169 	 * Sets up for an empty file
170 	 * @param start the starting tile
171 	 * @param ntiles the number of tiles total
172 	 */
init(int start,int ntiles)173 	void init(int start, int ntiles)
174 	{
175 		dispose();
176 
177 		localtilestart_ = start;
178 		localtileend_ = start + ntiles - 1;
179 		tilesizx_ = new short[ntiles];
180 		tilesizy_ = new short[ntiles];
181 		picanm_   = new int[ntiles];
182 
183 		memset(tilesizx_, 0, sizeof(short)*ntiles);
184 		memset(tilesizy_, 0, sizeof(short)*ntiles);
185 		memset(picanm_, 0, sizeof(int)*ntiles);
186 
187 		markprelength_ = 0;
188 		markskiplength_ = 0;
189 		markpostlength_ = 0;
190 		insert_ = 0;
191 		insertlen_ = 0;
192 	}
193 
194 	/**
195 	 * Returns the number of tiles in the loaded file
196 	 * @return 0 means no file loaded
197 	 */
getNumTiles()198 	int getNumTiles()
199 	{
200 		return (localtileend_ - localtilestart_ + 1);
201 	}
202 
getFirstTile()203 	int getFirstTile()
204 	{
205 		return localtilestart_;
206 	}
207 
getLastTile()208 	int getLastTile()
209 	{
210 		return localtileend_;
211 	}
212 
removeTile(int tile)213 	void removeTile(int tile)
214 	{
215 		int i, end;
216 
217 		if (tile < localtilestart_ || tile > localtileend_) {
218 			return;
219 		}
220 
221 		end   = localtileend_ - tile;
222 		tile -= localtilestart_;
223 
224 		markprelength_ = markpostlength_ = 0;
225 
226 		for (i = 0; i < tile; i++) {
227 			markprelength_ += tilesizx_[i] * tilesizy_[i];
228 		}
229 		markskiplength_ = tilesizx_[tile] * tilesizy_[tile];
230 		for (i = tile + 1; i <= end; i++) {
231 			markpostlength_ += tilesizx_[i] * tilesizy_[i];
232 		}
233 
234 		tilesizx_[tile] = tilesizy_[tile] = 0;
235 	}
236 
replaceTile(int tile,char * replace,int replacelen)237 	void replaceTile(int tile, char * replace, int replacelen)
238 	{
239 		if (tile < localtilestart_ || tile > localtileend_) {
240 			return;
241 		}
242 
243 		removeTile(tile);
244 
245 		insert_ = replace;
246 		insertlen_ = replacelen;
247 	}
248 
getTileSize(int tile,int & x,int & y)249 	void getTileSize(int tile, int& x, int &y)
250 	{
251 		if (tile < localtilestart_ || tile > localtileend_) {
252 			x = y = -1;
253 			return;
254 		}
255 
256 		tile -= localtilestart_;
257 		x = tilesizx_[tile];
258 		y = tilesizy_[tile];
259 	}
260 
setTileSize(int tile,int x,int y)261 	void setTileSize(int tile, int x, int y)
262 	{
263 		if (tile < localtilestart_ || tile > localtileend_) {
264 			return;
265 		}
266 
267 		tile -= localtilestart_;
268 		tilesizx_[tile] = x;
269 		tilesizy_[tile] = y;
270 	}
271 
setXOfs(int tile,int x)272 	void setXOfs(int tile, int x)
273 	{
274 		if (tile < localtilestart_ || tile > localtileend_) {
275 			return;
276 		}
277 
278 		tile -= localtilestart_;
279 		picanm_[tile] &= ~(255<<8);
280 		picanm_[tile] |= ((int)((unsigned char)x) << 8);
281 	}
282 
setYOfs(int tile,int y)283 	void setYOfs(int tile, int y)
284 	{
285 		if (tile < localtilestart_ || tile > localtileend_) {
286 			return;
287 		}
288 
289 		tile -= localtilestart_;
290 		picanm_[tile] &= ~(255<<16);
291 		picanm_[tile] |= ((int)((unsigned char)y) << 16);
292 	}
293 
getXOfs(int tile)294 	int getXOfs(int tile)
295 	{
296 		if (tile < localtilestart_ || tile > localtileend_) {
297 			return 0;
298 		}
299 
300 		tile -= localtilestart_;
301 		return (picanm_[tile] >> 8) & 255;
302 	}
303 
getYOfs(int tile)304 	int getYOfs(int tile)
305 	{
306 		if (tile < localtilestart_ || tile > localtileend_) {
307 			return 0;
308 		}
309 
310 		tile -= localtilestart_;
311 		return (picanm_[tile] >> 16) & 255;
312 	}
313 
setAnimType(int tile,int type)314 	void setAnimType(int tile, int type)
315 	{
316 		if (tile < localtilestart_ || tile > localtileend_) {
317 			return;
318 		}
319 
320 		tile -= localtilestart_;
321 		picanm_[tile] &= ~(3<<6);
322 		picanm_[tile] |= ((int)(type&3) << 6);
323 	}
324 
getAnimType(int tile)325 	int getAnimType(int tile)
326 	{
327 		if (tile < localtilestart_ || tile > localtileend_) {
328 			return 0;
329 		}
330 
331 		tile -= localtilestart_;
332 		return (picanm_[tile] >> 6) & 3;
333 	}
334 
setAnimFrames(int tile,int frames)335 	void setAnimFrames(int tile, int frames)
336 	{
337 		if (tile < localtilestart_ || tile > localtileend_) {
338 			return;
339 		}
340 
341 		tile -= localtilestart_;
342 		picanm_[tile] &= ~(63);
343 		picanm_[tile] |= ((int)(frames&63));
344 	}
345 
getAnimFrames(int tile)346 	int getAnimFrames(int tile)
347 	{
348 		if (tile < localtilestart_ || tile > localtileend_) {
349 			return 0;
350 		}
351 
352 		tile -= localtilestart_;
353 		return picanm_[tile] & 63;
354 	}
355 
setAnimSpeed(int tile,int speed)356 	void setAnimSpeed(int tile, int speed)
357 	{
358 		if (tile < localtilestart_ || tile > localtileend_) {
359 			return;
360 		}
361 
362 		tile -= localtilestart_;
363 		picanm_[tile] &= ~(15<<24);
364 		picanm_[tile] |= ((int)(speed&15) << 24);
365 	}
366 
getAnimSpeed(int tile)367 	int getAnimSpeed(int tile)
368 	{
369 		if (tile < localtilestart_ || tile > localtileend_) {
370 			return 0;
371 		}
372 
373 		tile -= localtilestart_;
374 		return (picanm_[tile] >> 24) & 15;
375 	}
376 
write()377 	int write()
378 	{
379 		string tmpfilename(filename_ + ".arttooltmp");
380 		ofstream outfile(tmpfilename.c_str(), ios::out | ios::trunc | ios::binary);
381 		ifstream infile(filename_.c_str(), ios::in | ios::binary);
382 		int i, left;
383 		char blk[4096];
384 
385 		if (!infile.is_open() && (markprelength_ > 0 || markskiplength_ > 0 || markpostlength_ > 0)) {
386 			return -1;	// couldn't open the original file for copying
387 		} else if (infile.is_open()) {
388 			// skip to the start of the existing ART data
389 			int ofs = 4+4+4+4+(2+2+4)*(localtileend_-localtilestart_+1);
390 			infile.seekg(ofs, ios::cur);
391 		}
392 
393 		// write a header to the temporary file
394 		writeLong(outfile, 1);	// version
395 		writeLong(outfile, 0);	// numtiles
396 		writeLong(outfile, localtilestart_);
397 		writeLong(outfile, localtileend_);
398 		for (int i = 0; i < localtileend_ - localtilestart_ + 1; i++) {
399 			writeShort(outfile, tilesizx_[i]);
400 		}
401 		for (int i = 0; i < localtileend_ - localtilestart_ + 1; i++) {
402 			writeShort(outfile, tilesizy_[i]);
403 		}
404 		for (int i = 0; i < localtileend_ - localtilestart_ + 1; i++) {
405 			writeLong(outfile, picanm_[i]);
406 		}
407 
408 		// copy the existing leading tile data to be kept
409 		left = markprelength_;
410 		while (left > 0) {
411 			i = left;
412 			if (i > sizeof(blk)) {
413 				i = sizeof(blk);
414 			}
415 			infile.read(blk, i);
416 			outfile.write(blk, i);
417 			left -= i;
418 		}
419 
420 		// insert the replacement data
421 		if (insertlen_ > 0) {
422 			outfile.write(insert_, insertlen_);
423 		}
424 
425 		if (markskiplength_ > 0) {
426 			infile.seekg(markskiplength_, ios::cur);
427 		}
428 
429 		// copy the existing trailing tile data to be kept
430 		left = markpostlength_;
431 		while (left > 0) {
432 			i = left;
433 			if (i > sizeof(blk)) {
434 				i = sizeof(blk);
435 			}
436 			infile.read(blk, i);
437 			outfile.write(blk, i);
438 			left -= i;
439 		}
440 
441 		// close our files
442 		if (infile.is_open()) {
443 			infile.close();
444 		}
445 		outfile.close();
446 
447 		// replace it with the new one
448 		remove(filename_.c_str());
449 		rename(tmpfilename.c_str(), filename_.c_str());
450 
451 		return 0;
452 	}
453 
readTile(int tile,int & bytes)454 	char * readTile(int tile, int& bytes)
455 	{
456 		bytes = -1;
457 
458 		if (tile < localtilestart_ || tile > localtileend_) {
459 			return 0;
460 		}
461 		tile -= localtilestart_;
462 
463 		if (tilesizx_[tile] == 0 || tilesizy_[tile] == 0) {
464 			bytes = 0;
465 			return 0;
466 		}
467 
468 		ifstream infile(filename_.c_str(), ios::in | ios::binary);
469 		if (!infile.is_open()) {
470 			return 0;
471 		} else {
472 			// skip to the start of the existing ART data
473 			infile.seekg(datastartoffset_);
474 		}
475 
476 		bytes = tilesizx_[tile] * tilesizy_[tile];
477 		char * data = new char[bytes];
478 
479 		for (int i = 0; i < tile; i++) {
480 			infile.seekg(tilesizx_[i] * tilesizy_[i], ios::cur);
481 		}
482 		infile.read(data, bytes);
483 		if (infile.gcount() != bytes) {
484 			delete [] data;
485 			data = 0;
486 		}
487 
488 		return data;
489 	}
490 };
491 
492 
493 class PCX {
494 private:
writebyte(unsigned char colour,unsigned char count,ofstream & ofs)495 	static int writebyte(unsigned char colour, unsigned char count, ofstream& ofs)
496 	{
497 		if (!count) return 0;
498 		if (count == 1 && (colour & 0xc0) != 0xc0) {
499 			ofs.put(colour);
500 			return 1;
501 		} else {
502 			ofs.put(0xc0 | count);
503 			ofs.put(colour);
504 			return 2;
505 		}
506 	}
507 
writeline(unsigned char * buf,int bytes,int step,ofstream & ofs)508 	static void writeline(unsigned char *buf, int bytes, int step, ofstream& ofs)
509 	{
510 		unsigned char ths, last;
511 		int srcIndex, i;
512 		unsigned char runCount;
513 
514 		runCount = 1;
515 		last = *buf;
516 
517 		for (srcIndex=1; srcIndex<bytes; srcIndex++) {
518 			buf += step;
519 			ths = *buf;
520 			if (ths == last) {
521 				runCount++;
522 				if (runCount == 63) {
523 					writebyte(last, runCount, ofs);
524 					runCount = 0;
525 				}
526 			} else {
527 				if (runCount)
528 					writebyte(last, runCount, ofs);
529 
530 				last = ths;
531 				runCount = 1;
532 			}
533 		}
534 
535 		if (runCount) writebyte(last, runCount, ofs);
536 		if (bytes&1) writebyte(0, 1, ofs);
537 	}
538 
539 public:
540 	/**
541 	 * Decodes a PCX file to BUILD's column-major pixel order
542 	 * @param data the raw file data
543 	 * @param datalen the length of the raw file data
544 	 * @param imgdata receives a pointer to the decoded image data
545 	 * @param imgdataw receives the decoded image width
546 	 * @param imgdatah receives the decoded image height
547 	 * @return 0 on success, 1 if the format is invalid
548 	 */
decode(unsigned char * data,int datalen,char ** imgdata,int & imgdataw,int & imgdatah)549 	static int decode(unsigned char * data, int datalen, char ** imgdata, int& imgdataw, int& imgdatah)
550 	{
551 		if (data[0] != 10 ||
552 			data[1] != 5 ||
553 			data[2] != 1 ||
554 			data[3] != 8 ||
555 			data[64] != 0 ||
556 			data[65] != 1) {
557 			return 1;
558 		}
559 
560 		int bpl = data[66] + ((int)data[67] << 8);
561 		int x, y, repeat, colour;
562 		unsigned char *wptr, *rptr;
563 
564 		imgdataw = (data[8] + ((int)data[9] << 8)) - (data[4] + ((int)data[5] << 8)) + 1;
565 		imgdatah = (data[10] + ((int)data[11] << 8)) - (data[6] + ((int)data[7] << 8)) + 1;
566 
567 		*imgdata = new char [imgdataw * imgdatah];
568 
569 		rptr = data + 128;
570 		for (y = 0; y < imgdatah; y++) {
571 			wptr = (unsigned char *) (*imgdata + y);
572 			x = 0;
573 			do {
574 				repeat = *(rptr++);
575 				if ((repeat & 192) == 192) {
576 					colour = *(rptr++);
577 					repeat = repeat & 63;
578 				} else {
579 					colour = repeat;
580 					repeat = 1;
581 				}
582 
583 				for (; repeat > 0; repeat--, x++) {
584 					if (x < imgdataw) {
585 						*wptr = (unsigned char) colour;
586 						wptr += imgdatah;	// next column
587 					}
588 				}
589 			} while (x < bpl);
590 		}
591 
592 		return 0;
593 	}
594 
595 	/**
596 	 * Writes a PCX file from data in BUILD's column-major pixel order
597 	 * @param ofs the output file stream
598 	 * @param imgdata a pointer to the image data
599 	 * @param imgdataw the image width
600 	 * @param imgdatah the image height
601 	 * @param palette the image palette, 256*3 bytes
602 	 * @return 0 on success
603 	 */
write(ofstream & ofs,unsigned char * imgdata,int imgdataw,int imgdatah,unsigned char * palette)604 	static int write(ofstream& ofs, unsigned char * imgdata, int imgdataw, int imgdatah, unsigned char * palette)
605 	{
606 		unsigned char head[128];
607 		int bpl = imgdataw + (imgdataw&1);
608 
609 		memset(head,0,128);
610 		head[0] = 10;
611 		head[1] = 5;
612 		head[2] = 1;
613 		head[3] = 8;
614 		head[8] = (imgdataw-1) & 0xff;
615 		head[9] = ((imgdataw-1) >> 8) & 0xff;
616 		head[10] = (imgdatah-1) & 0xff;
617 		head[11] = ((imgdatah-1) >> 8) & 0xff;
618 		head[12] = 72; head[13] = 0;
619 		head[14] = 72; head[15] = 0;
620 		head[65] = 1;	// 8-bit
621 		head[66] = bpl & 0xff;
622 		head[67] = (bpl >> 8) & 0xff;
623 		head[68] = 1;
624 
625 		ofs.write((char *)head, sizeof(head));
626 		for (int i = 0; i < imgdatah; i++) {
627 			writeline(imgdata + i, imgdataw, imgdatah, ofs);
628 		}
629 
630 		return 0;
631 	}
632 };
633 
634 /**
635  * Loads a tile from a picture file into memory
636  * @param filename the filename
637  * @param imgdata receives a pointer to the decoded image data
638  * @param imgdataw receives the decoded image width
639  * @param imgdatah receives the decoded image height
640  * @return 0 on success
641  */
loadimage(string filename,char ** imgdata,int & imgdataw,int & imgdatah)642 int loadimage(string filename, char ** imgdata, int& imgdataw, int& imgdatah)
643 {
644 	ifstream infile(filename.c_str(), ios::in | ios::binary);
645 	unsigned char * data = 0;
646 	int datalen = 0, err = 0;
647 
648 	if (!infile.is_open()) {
649 		return 1;
650 	}
651 
652 	infile.seekg(0, ios::end);
653 	datalen = infile.tellg();
654 	infile.seekg(0, ios::beg);
655 
656 	data = new unsigned char [datalen];
657 	infile.read((char *) data, datalen);
658 	infile.close();
659 
660 	err = PCX::decode(data, datalen, imgdata, imgdataw, imgdatah);
661 
662 	delete [] data;
663 
664 	return err;
665 }
666 
667 
668 /**
669  * Saves a tile from memory to disk, taking the palette from palette.dat
670  * @param filename the filename
671  * @param imgdata a pointer to the image data
672  * @param imgdataw the image width
673  * @param imgdatah the image height
674  * @return 0 on success
675  */
saveimage(string filename,char * imgdata,int imgdataw,int imgdatah)676 int saveimage(string filename, char * imgdata, int imgdataw, int imgdatah)
677 {
678 	ofstream outfile(filename.c_str(), ios::out | ios::trunc | ios::binary);
679 	ifstream palfile("palette.dat", ios::in | ios::binary);
680 	unsigned char * data = 0;
681 	unsigned char palette[768];
682 
683 	if (palfile.is_open()) {
684 		palfile.read((char *)palette, 768);
685 		for (int i=0; i<256*3; i++) {
686 			palette[i] <<= 2;
687 		}
688 	} else {
689 		cerr << "warning: palette.dat could not be loaded\n" << endl;
690 		for (int i=0; i<256; i++) {
691 			palette[i*3+0] = i;
692 			palette[i*3+1] = i;
693 			palette[i*3+2] = i;
694 		}
695 	}
696 
697 	if (!outfile.is_open()) {
698 		return 1;
699 	}
700 
701 	PCX::write(outfile, (unsigned char *)imgdata, imgdataw, imgdatah, palette);
702 
703 	outfile.put(12);
704 	outfile.write((char *)palette, 768);
705 
706 	return 0;
707 }
708 
709 class Operation {
710 protected:
makefilename(int n)711 	string makefilename(int n)
712 	{
713 		string filename("tilesXXX.art");
714 		filename[5] = '0' + (n / 100) % 10;
715 		filename[6] = '0' + (n / 10) % 10;
716 		filename[7] = '0' + (n / 1) % 10;
717 		return filename;
718 	}
719 
720 public:
721 	typedef enum {
722 		NO_ERROR = 0,
723 		ERR_BAD_OPTION = 1,
724 		ERR_BAD_VALUE = 2,
725 		ERR_TOO_MANY_PARAMS = 3,
726 		ERR_NO_ART_FILE = 4,
727 		ERR_INVALID_IMAGE = 5,
728 	} Result;
729 
translateResult(Result r)730 	static char const * translateResult(Result r)
731 	{
732 		switch (r) {
733 			case NO_ERROR: return "no error";
734 			case ERR_BAD_OPTION: return "bad option";
735 			case ERR_BAD_VALUE: return "bad value";
736 			case ERR_TOO_MANY_PARAMS: return "too many parameters given";
737 			case ERR_NO_ART_FILE: return "no ART file was found";
738 			case ERR_INVALID_IMAGE: return "a corrupt or unrecognised image was given";
739 			default: return "unknown error";
740 		}
741 	}
742 
~Operation()743 	virtual ~Operation()
744 	{
745 	}
746 
747 	/**
748 	 * Sets an option
749 	 * @param opt the option name
750 	 * @param value the option value
751 	 * @return a value from the Result enum
752 	 */
753 	virtual Result setOption(string opt, string value) = 0;
754 
755 	/**
756 	 * Sets a parameter from the unnamed sequence
757 	 * @param number the parameter number
758 	 * @param value the parameter value
759 	 * @return a value from the Result enum
760 	 */
761 	virtual Result setParameter(int number, string value) = 0;
762 
763 	/**
764 	 * Do the operation
765 	 * @return a value from the Result enum
766 	 */
767 	virtual Result perform() = 0;
768 };
769 
770 class InfoOp : public Operation {
771 private:
772 	int tilenum_;
773 
outputInfo(ARTFile & art,int tile)774 	void outputInfo(ARTFile& art, int tile)
775 	{
776 		cout << "  Tile " << tile << ": ";
777 
778 		int w, h;
779 		art.getTileSize(tile, w, h);
780 		cout << w << "x" << h << " ";
781 
782 		cout << "Xofs: " << art.getXOfs(tile) << ", ";
783 		cout << "Yofs: " << art.getYOfs(tile) << ", ";
784 		cout << "AnimType: " << art.getAnimType(tile) << ", ";
785 		cout << "AnimFrames: " << art.getAnimFrames(tile) << ", ";
786 		cout << "AnimSpeed: " << art.getAnimSpeed(tile) << endl;
787 	}
788 
789 public:
InfoOp()790 	InfoOp() : tilenum_(-1) { }
791 
setOption(string opt,string value)792 	virtual Result setOption(string opt, string value)
793 	{
794 		return ERR_BAD_OPTION;
795 	}
796 
setParameter(int number,string value)797 	virtual Result setParameter(int number, string value)
798 	{
799 		switch (number) {
800 			case 0:
801 				tilenum_ = atoi(value.c_str());
802 				return NO_ERROR;
803 			default:
804 				return ERR_TOO_MANY_PARAMS;
805 		}
806 	}
807 
perform()808 	virtual Result perform()
809 	{
810 		int filenum = 0, tile;
811 
812 		for (filenum = 0; filenum < 1000; filenum++) {
813 			string filename = makefilename(filenum);
814 			ARTFile art(filename);
815 
816 			if (art.getNumTiles() == 0) {
817 				// no file exists, so give up
818 				if (tilenum_ < 0) {
819 					return NO_ERROR;
820 				}
821 				break;
822 			}
823 
824 			if (tilenum_ >= 0) {
825 				if (tilenum_ > art.getLastTile()) {
826 					// Not in this file.
827 					continue;
828 				} else {
829 					cout << "File " << filename << endl;
830 					outputInfo(art, tilenum_);
831 				}
832 				return NO_ERROR;
833 			} else {
834 				cout << "File " << filename << endl;
835 				for (tile = art.getFirstTile(); tile <= art.getLastTile(); tile++) {
836 					outputInfo(art, tile);
837 				}
838 			}
839 		}
840 
841 		return ERR_NO_ART_FILE;
842 	}
843 };
844 
845 class CreateOp : public Operation {
846 private:
847 	int filen_, offset_, ntiles_;
848 public:
CreateOp()849 	CreateOp() : filen_(0), offset_(0), ntiles_(256) { }
850 
setOption(string opt,string value)851 	virtual Result setOption(string opt, string value)
852 	{
853 		if (opt == "f") {
854 			filen_ = atoi(value.c_str());
855 			if (filen_ < 0 || filen_ > 999) {
856 				return ERR_BAD_VALUE;
857 			}
858 		} else if (opt == "o") {
859 			offset_ = atoi(value.c_str());
860 			if (offset_ < 0) {
861 				return ERR_BAD_VALUE;
862 			}
863 		} else if (opt == "n") {
864 			ntiles_ = atoi(value.c_str());
865 			if (ntiles_ < 1) {
866 				return ERR_BAD_VALUE;
867 			}
868 		} else {
869 			return ERR_BAD_OPTION;
870 		}
871 		return NO_ERROR;
872 	}
873 
setParameter(int number,string value)874 	virtual Result setParameter(int number, string value)
875 	{
876 		return ERR_TOO_MANY_PARAMS;
877 	}
878 
perform()879 	virtual Result perform()
880 	{
881 		ARTFile art(makefilename(filen_));
882 
883 		art.init(offset_, ntiles_);
884 		art.write();
885 
886 		return NO_ERROR;
887 	}
888 };
889 
890 class AddTileOp : public Operation {
891 private:
892 	int xofs_, yofs_;
893 	int animframes_, animtype_, animspeed_;
894 	int tilenum_;
895 	string filename_;
896 public:
AddTileOp()897 	AddTileOp()
898 	 : xofs_(0), yofs_(0),
899 	   animframes_(0), animtype_(0), animspeed_(0),
900 	   tilenum_(-1), filename_("")
901 	{ }
902 
setOption(string opt,string value)903 	virtual Result setOption(string opt, string value)
904 	{
905 		if (opt == "x") {
906 			xofs_ = atoi(value.c_str());
907 		} else if (opt == "y") {
908 			yofs_ = atoi(value.c_str());
909 		} else if (opt == "ann") {
910 			animframes_ = atoi(value.c_str());
911 			if (animframes_ < 0 || animframes_ > 63) {
912 				return ERR_BAD_VALUE;
913 			}
914 		} else if (opt == "ant") {
915 			animtype_ = atoi(value.c_str());
916 			if (animtype_ < 0 || animtype_ > 3) {
917 				return ERR_BAD_VALUE;
918 			}
919 		} else if (opt == "ans") {
920 			animspeed_ = atoi(value.c_str());
921 			if (animspeed_ < 0 || animspeed_ > 15) {
922 				return ERR_BAD_VALUE;
923 			}
924 		} else {
925 			return ERR_BAD_OPTION;
926 		}
927 		return NO_ERROR;
928 	}
929 
setParameter(int number,string value)930 	virtual Result setParameter(int number, string value)
931 	{
932 		switch (number) {
933 			case 0:
934 				tilenum_ = atoi(value.c_str());
935 				return NO_ERROR;
936 			case 1:
937 				filename_ = value;
938 				return NO_ERROR;
939 			default:
940 				return ERR_TOO_MANY_PARAMS;
941 		}
942 	}
943 
perform()944 	virtual Result perform()
945 	{
946 		int tilesperfile = 0, nextstart = 0;
947 		int filenum = 0;
948 		char * imgdata = 0;
949 		int imgdatalen = 0, imgdataw = 0, imgdatah = 0;
950 
951 		// open the first art file to get the file size used by default
952 		{
953 			ARTFile art(makefilename(0));
954 			tilesperfile = art.getNumTiles();
955 			if (tilesperfile == 0) {
956 				return ERR_NO_ART_FILE;
957 			}
958 		}
959 
960 		// load the tile image into memory
961 		switch (loadimage(filename_, &imgdata, imgdataw, imgdatah)) {
962 			case 0: break;	// win
963 			default: return ERR_INVALID_IMAGE;
964 		}
965 
966 		// open art files until we find one that encompasses the range we need
967 		// and when we find it, make the change
968 		for (filenum = 0; filenum < 1000; filenum++) {
969 			ARTFile art(makefilename(filenum));
970 			bool dirty = false, done = false;
971 
972 			if (art.getNumTiles() == 0) {
973 				// no file exists, so we treat it as though it does
974 				art.init(nextstart, tilesperfile);
975 				dirty = true;
976 			}
977 
978 			if (tilenum_ >= art.getFirstTile() && tilenum_ <= art.getLastTile()) {
979 				art.replaceTile(tilenum_, imgdata, imgdataw * imgdatah);
980 				art.setTileSize(tilenum_, imgdataw, imgdatah);
981 				art.setXOfs(tilenum_, xofs_);
982 				art.setYOfs(tilenum_, yofs_);
983 				art.setAnimFrames(tilenum_, animframes_);
984 				art.setAnimSpeed(tilenum_, animspeed_);
985 				art.setAnimType(tilenum_, animtype_);
986 				done = true;
987 				dirty = true;
988 
989 				imgdata = 0;	// ARTFile.replaceTile took ownership of the pointer
990 			}
991 
992 			nextstart += art.getNumTiles();
993 
994 			if (dirty) {
995 				art.write();
996 			}
997 			if (done) {
998 				return NO_ERROR;
999 			}
1000 		}
1001 
1002 		if (imgdata) {
1003 			delete [] imgdata;
1004 		}
1005 
1006 		return ERR_NO_ART_FILE;
1007 	}
1008 };
1009 
1010 class RmTileOp : public Operation {
1011 private:
1012 	int tilenum_;
1013 public:
RmTileOp()1014 	RmTileOp() : tilenum_(-1) { }
1015 
setOption(string opt,string value)1016 	virtual Result setOption(string opt, string value)
1017 	{
1018 		return ERR_BAD_OPTION;
1019 	}
1020 
setParameter(int number,string value)1021 	virtual Result setParameter(int number, string value)
1022 	{
1023 		switch (number) {
1024 			case 0:
1025 				tilenum_ = atoi(value.c_str());
1026 				return NO_ERROR;
1027 			default:
1028 				return ERR_TOO_MANY_PARAMS;
1029 		}
1030 	}
1031 
perform()1032 	virtual Result perform()
1033 	{
1034 		int filenum = 0;
1035 
1036 		// open art files until we find one that encompasses the range we need
1037 		// and when we find it, remove the tile
1038 		for (filenum = 0; filenum < 1000; filenum++) {
1039 			ARTFile art(makefilename(filenum));
1040 
1041 			if (art.getNumTiles() == 0) {
1042 				// no file exists, so give up
1043 				break;
1044 			}
1045 
1046 			if (tilenum_ >= art.getFirstTile() && tilenum_ <= art.getLastTile()) {
1047 				art.removeTile(tilenum_);
1048 				art.write();
1049 				return NO_ERROR;
1050 			}
1051 		}
1052 
1053 		return ERR_NO_ART_FILE;
1054 	}
1055 };
1056 
1057 class ExportTileOp : public Operation {
1058 private:
1059 	int tilenum_;
1060 public:
ExportTileOp()1061 	ExportTileOp() : tilenum_(-1) { }
1062 
setOption(string opt,string value)1063 	virtual Result setOption(string opt, string value)
1064 	{
1065 		return ERR_BAD_OPTION;
1066 	}
1067 
setParameter(int number,string value)1068 	virtual Result setParameter(int number, string value)
1069 	{
1070 		switch (number) {
1071 			case 0:
1072 				tilenum_ = atoi(value.c_str());
1073 				return NO_ERROR;
1074 			default:
1075 				return ERR_TOO_MANY_PARAMS;
1076 		}
1077 	}
1078 
perform()1079 	virtual Result perform()
1080 	{
1081 		int filenum = 0;
1082 
1083 		string filename("tile0000.pcx");
1084 		filename[4] = '0' + (tilenum_ / 1000) % 10;
1085 		filename[5] = '0' + (tilenum_ / 100) % 10;
1086 		filename[6] = '0' + (tilenum_ / 10) % 10;
1087 		filename[7] = '0' + (tilenum_) % 10;
1088 
1089 		// open art files until we find the one that encompasses the range we need
1090 		// and when we find it, export it
1091 		for (filenum = 0; filenum < 1000; filenum++) {
1092 			ARTFile art(makefilename(filenum));
1093 
1094 			if (art.getNumTiles() == 0) {
1095 				// no file exists, so give up
1096 				break;
1097 			}
1098 
1099 			if (tilenum_ >= art.getFirstTile() && tilenum_ <= art.getLastTile()) {
1100 				int bytes, w, h;
1101 				char * data = art.readTile(tilenum_, bytes);
1102 				art.getTileSize(tilenum_, w, h);
1103 
1104 				if (bytes == 0) {
1105 					return NO_ERROR;
1106 				}
1107 
1108 				switch (saveimage(filename, data, w, h)) {
1109 					case 0: break;	// win
1110 					default: return ERR_INVALID_IMAGE;
1111 				}
1112 
1113 				delete [] data;
1114 
1115 				return NO_ERROR;
1116 			}
1117 		}
1118 
1119 		return ERR_NO_ART_FILE;
1120 	}
1121 };
1122 
1123 class TilePropOp : public Operation {
1124 private:
1125 	int xofs_, yofs_;
1126 	int animframes_, animtype_, animspeed_;
1127 	int tilenum_;
1128 
1129 	int settings_;
1130 
1131 	enum {
1132 		SET_XOFS = 1,
1133 		SET_YOFS = 2,
1134 		SET_ANIMFRAMES = 4,
1135 		SET_ANIMTYPE = 8,
1136 		SET_ANIMSPEED = 16,
1137 	};
1138 public:
TilePropOp()1139 	TilePropOp()
1140 	: xofs_(0), yofs_(0),
1141 	  animframes_(0), animtype_(0), animspeed_(0),
1142 	  tilenum_(-1), settings_(0)
1143 	{ }
1144 
setOption(string opt,string value)1145 	virtual Result setOption(string opt, string value)
1146 	{
1147 		if (opt == "x") {
1148 			xofs_ = atoi(value.c_str());
1149 			settings_ |= SET_XOFS;
1150 		} else if (opt == "y") {
1151 			yofs_ = atoi(value.c_str());
1152 			settings_ |= SET_YOFS;
1153 		} else if (opt == "ann") {
1154 			animframes_ = atoi(value.c_str());
1155 			settings_ |= SET_ANIMFRAMES;
1156 			if (animframes_ < 0 || animframes_ > 63) {
1157 				return ERR_BAD_VALUE;
1158 			}
1159 		} else if (opt == "ant") {
1160 			animtype_ = atoi(value.c_str());
1161 			settings_ |= SET_ANIMTYPE;
1162 			if (animtype_ < 0 || animtype_ > 3) {
1163 				return ERR_BAD_VALUE;
1164 			}
1165 		} else if (opt == "ans") {
1166 			animspeed_ = atoi(value.c_str());
1167 			settings_ |= SET_ANIMSPEED;
1168 			if (animspeed_ < 0 || animspeed_ > 15) {
1169 				return ERR_BAD_VALUE;
1170 			}
1171 		} else {
1172 			return ERR_BAD_OPTION;
1173 		}
1174 		return NO_ERROR;
1175 	}
1176 
setParameter(int number,string value)1177 	virtual Result setParameter(int number, string value)
1178 	{
1179 		switch (number) {
1180 			case 0:
1181 				tilenum_ = atoi(value.c_str());
1182 				return NO_ERROR;
1183 			default:
1184 				return ERR_TOO_MANY_PARAMS;
1185 		}
1186 	}
1187 
perform()1188 	virtual Result perform()
1189 	{
1190 		int filenum = 0;
1191 
1192 		if (settings_ == 0) {
1193 			return NO_ERROR;
1194 		}
1195 
1196 		// open art files until we find one that encompasses the range we need
1197 		// and when we find it, make the change
1198 		for (filenum = 0; filenum < 1000; filenum++) {
1199 			ARTFile art(makefilename(filenum));
1200 
1201 			if (art.getNumTiles() == 0) {
1202 				// no file exists, so give up
1203 				break;
1204 			}
1205 
1206 			if (tilenum_ >= art.getFirstTile() && tilenum_ <= art.getLastTile()) {
1207 				if (settings_ & SET_XOFS) {
1208 					art.setXOfs(tilenum_, xofs_);
1209 				}
1210 				if (settings_ & SET_YOFS) {
1211 					art.setYOfs(tilenum_, yofs_);
1212 				}
1213 				if (settings_ & SET_ANIMFRAMES) {
1214 					art.setAnimFrames(tilenum_, animframes_);
1215 				}
1216 				if (settings_ & SET_ANIMSPEED) {
1217 					art.setAnimSpeed(tilenum_, animspeed_);
1218 				}
1219 				if (settings_ & SET_ANIMTYPE) {
1220 					art.setAnimType(tilenum_, animtype_);
1221 				}
1222 				art.write();
1223 				return NO_ERROR;
1224 			}
1225 		}
1226 
1227 		return ERR_NO_ART_FILE;
1228 	}
1229 };
1230 
main(int argc,char ** argv)1231 int main(int argc, char ** argv)
1232 {
1233 	int showusage = 0;
1234 	Operation * oper = 0;
1235 	Operation::Result err;
1236 
1237 	if (argc < 2) {
1238 		showusage = 1;
1239 	} else {
1240 		string opt(argv[1]);
1241 		string value;
1242 
1243 		// create the option handler object according to the first param
1244 		if (opt == "info") {
1245 			oper = new InfoOp;
1246 		} else if (opt == "create") {
1247 			oper = new CreateOp;
1248 		} else if (opt == "addtile") {
1249 			oper = new AddTileOp;
1250 		} else if (opt == "rmtile") {
1251 			oper = new RmTileOp;
1252 		} else if (opt == "exporttile") {
1253 			oper = new ExportTileOp;
1254 		} else if (opt == "tileprop") {
1255 			oper = new TilePropOp;
1256 		} else {
1257 			showusage = 2;
1258 		}
1259 
1260 		// apply the command line options given
1261 		if (oper) {
1262 			int unnamedParm = 0;
1263 			for (int i = 2; i < argc && !showusage; i++) {
1264 				if (argv[i][0] == '-') {
1265 					opt = string(argv[i]).substr(1);
1266 					if (i+1 >= argc) {
1267 						showusage = 2;
1268 						break;
1269 					}
1270 					value = string(argv[i+1]);
1271 					i++;
1272 
1273 					switch (err = oper->setOption(opt, value)) {
1274 						case Operation::NO_ERROR: break;
1275 						default:
1276 							cerr << "error: " << Operation::translateResult(err) << endl;
1277 							showusage = 2;
1278 							break;
1279 					}
1280 				} else {
1281 					value = string(argv[i]);
1282 					switch (oper->setParameter(unnamedParm, value)) {
1283 						case Operation::NO_ERROR: break;
1284 						default:
1285 							cerr << "error: " << Operation::translateResult(err) << endl;
1286 							showusage = 2;
1287 							break;
1288 					}
1289 					unnamedParm++;
1290 				}
1291 			}
1292 		}
1293 	}
1294 
1295 	if (showusage) {
1296 		usage();
1297 		if (oper) delete oper;
1298 		return (showusage - 1);
1299 	} else if (oper) {
1300 		err = oper->perform();
1301 		delete oper;
1302 
1303 		switch (err) {
1304 			case Operation::NO_ERROR: return 0;
1305 			default:
1306 				cerr << "error: " << Operation::translateResult(err) << endl;
1307 				return 1;
1308 		}
1309 	}
1310 
1311 	return 0;
1312 }
1313