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