/ source / tools / src / arttool.cpp
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  }