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