Subversion Repositories ipe_artfile_utils

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. /**
  2.  * ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
  3.  * Supports:
  4.  * - Blown Away - The Interactive Game by Imagination Pilots
  5.  * - Panic in the Park - The Interactive Game by Imagination Pilots
  6.  * - Where's Waldo? At the Circus (Waldo1)
  7.  * Revision: 2018-02-15
  8.  **/
  9.  
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <assert.h>
  13. #include <string.h>
  14. #include <getopt.h>
  15.  
  16. #include "ipe16_bmpexport.h"
  17. #include "ipe16_artfile.h"
  18. #include "ipe16_lzw_decoder.h"
  19.  
  20. #include "utils.h"
  21.  
  22. #define MAX_FILE 256
  23.  
  24. void ipe16_generate_gray_table(Ipe16ColorTable *ct) {
  25.         int i;
  26.         for (i=0; i<=0xFF; ++i) {
  27.                 ct->colors[i].r = i;
  28.                 ct->colors[i].g = i;
  29.                 ct->colors[i].b = i;
  30.         }
  31. }
  32.  
  33. bool ipe16_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
  34.         bool bEverythingOK = true;
  35.  
  36.         fseek(fibArt, 0, SEEK_SET);
  37.  
  38.         Ipe16FileHeader bfh;
  39.         if (fread(&bfh, sizeof(bfh), 1, fibArt) != 1) {
  40.                 fprintf(stderr, "FATAL: Cannot read Ipe16FileHeader. It is probably not an art file.\n");
  41.                 return false;
  42.         }
  43.  
  44.         // The "super header" has some different meanings of the fields
  45.         // Name and Type are hardcoded
  46.         // startOffset is the number of header entries (including the super header)
  47.         // length is the complete file size
  48.         const size_t fileSize = file_size(fibArt);
  49.         if ((strcmp(bfh.magic, IPE16_MAGIC_ART) != 0) || // better memcpy over all 23 bytes?
  50.                 (bfh.dummy != IPE16_MAGIC_DUMMY) ||
  51.                 (bfh.totalFileSize != fileSize)) {
  52.                 fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
  53.                 return false;
  54.         }
  55.  
  56.         Ipe16LZWDecoder* lzwDecoder = NULL;
  57.  
  58.         FILE* fotIndex = NULL;
  59.         if (strlen(szDestFolder) > 0) {
  60.                 char szIndexFilename[MAX_FILE];
  61.                 sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
  62.                 fotIndex = fopen(szIndexFilename, "wt");
  63.                 if (!fotIndex) {
  64.                         fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
  65.                         return false;
  66.                 }
  67.         }
  68.  
  69.         const int numPictures = bfh.numHeaderEntries - 1;
  70.         char knownNames[numPictures][IPE16_NAME_SIZE];
  71.         memset(&knownNames[0][0], 0, numPictures*IPE16_NAME_SIZE);
  72.         int iPicNo;
  73.         for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
  74.                 Ipe16PictureEntryHeader peh;
  75.                 if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
  76.                         fprintf(stderr, "FATAL: Cannot read Ipe16PictureEntryHeader.\n");
  77.                         return false;
  78.                 }
  79.  
  80.                 // Begin duplicate check
  81.                 memcpy(&knownNames[iPicNo][0], peh.name, IPE16_NAME_SIZE);
  82.                 int iCopyNumber = 0;
  83.                 int j;
  84.                 for (j=0; j<=iPicNo; ++j) {
  85.                         // TODO: should we rather use strcmp() in IPE16?
  86.                         if (memcmp(&knownNames[j][0], peh.name, IPE16_NAME_SIZE) == 0) ++iCopyNumber;
  87.                 }
  88.                 assert(iCopyNumber > 0);
  89.                 // End duplicate check
  90.  
  91.                 // in the English version of Blown Away (not the Special Edition), there is a header entry
  92.                 // with the fields seh.name='', peh.paletteType=0x00, seh.offset[end of file], seh.size=0
  93.                 // ignore it
  94.                 if (strlen(peh.name) == 0) continue;
  95.                 if (peh.size == 0) continue;
  96.  
  97.                 if (strlen(peh.name) > IPE16_NAME_SIZE-1) {
  98.                         fprintf(stderr, "FATAL: szName at picture %d is breaking the boundaries. The file is probably corrupt.\n", iPicNo);
  99.                         return false;
  100.                 }
  101.  
  102.                 size_t headersPos = ftell(fibArt);
  103.                 #define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
  104.  
  105.                 if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
  106.                         fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", peh.name);
  107.                         FAIL_CONTINUE;
  108.                 }
  109.  
  110.                 if (peh.offset+peh.size > fileSize) {
  111.                         fprintf(stderr, "ERROR: Defined size of %s exceeds file size\n", peh.name);
  112.                         FAIL_CONTINUE;
  113.                 }
  114.  
  115.                 unsigned char compressionType;
  116.                 int bakPos = ftell(fibArt);
  117.                 fread(&compressionType, sizeof(compressionType), 1, fibArt);
  118.                 fseek(fibArt, bakPos, SEEK_SET);
  119.  
  120.                 if ((compressionType == BA_COMPRESSIONTYPE_LZW) || (compressionType == BA_COMPRESSIONTYPE_NONE)) {
  121.                         BAPictureHeader ph;
  122.                         if (fread(&ph, sizeof(ph), 1, fibArt) != 1) {
  123.                                 fprintf(stderr, "ERROR: Cannot read BAPictureHeader of %s\n", peh.name);
  124.                                 FAIL_CONTINUE;
  125.                         }
  126.  
  127.                         size_t imagedata_len = ph.width * ph.height;
  128.                         unsigned char* imagedata = (unsigned char*)malloc(imagedata_len);
  129.  
  130.                         Ipe16ColorTable ct;
  131.                         if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) {
  132.                                 if ((fseek(fibArt, peh.offset+peh.size-sizeof(ct), SEEK_SET) != 0) ||
  133.                                     (fread(&ct, sizeof(ct), 1, fibArt) != 1) ||
  134.                                     (fseek(fibArt, peh.offset+sizeof(ph), SEEK_SET) != 0)) {
  135.                                         fprintf(stderr, "ERROR: Cannot read palette of %s\n", peh.name);
  136.                                         FAIL_CONTINUE;
  137.                                 }
  138.                         } else if (peh.paletteType == IPE16_PALETTETYPE_PARENT) {
  139.                                 ipe16_generate_gray_table(&ct);
  140.                         } else {
  141.                                 fprintf(stderr, "ERROR: Unknown palette type 0x%x at %s\n", peh.paletteType, peh.name);
  142.                                 FAIL_CONTINUE;
  143.                         }
  144.  
  145.                         unsigned int bytes_written, expected_uncompressed_len;
  146.                         switch (ph.compressionType) {
  147.                                 case BA_COMPRESSIONTYPE_LZW:
  148.                                         if (!lzwDecoder) lzwDecoder = new_ipe16lzw_decoder();
  149.                                         bytes_written = ipe16lzw_decode(fibArt, lzwDecoder, imagedata, imagedata_len);
  150.                                         if (bytes_written < 0) {
  151.                                                 fprintf(stderr, "ERROR: LZW decompression error at %s\n", peh.name);
  152.                                                 FAIL_CONTINUE;
  153.                                         }
  154.                                         if (bytes_written != imagedata_len) {
  155.                                                 fprintf(stderr, "ERROR: Image dimensions and decompressed data size does not match for %s\n", peh.name);
  156.                                                 FAIL_CONTINUE;
  157.                                         }
  158.  
  159.                                         break;
  160.                                 case BA_COMPRESSIONTYPE_NONE:
  161.                                         expected_uncompressed_len = imagedata_len;
  162.                                         if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) expected_uncompressed_len += sizeof(ct);
  163.                                         expected_uncompressed_len += sizeof(ph.compressionType)+sizeof(ph.width)+sizeof(ph.height);
  164.                                         if (expected_uncompressed_len != peh.size) {
  165.                                                 fprintf(stderr, "ERROR: Image dimensions/palette (%d) and defined memory size (%d) does not match for %s\n", expected_uncompressed_len, peh.size, peh.name);
  166.                                                 FAIL_CONTINUE;
  167.                                         }
  168.                                         fread(imagedata, imagedata_len, 1, fibArt); // no error checking, because filesize was already checked
  169.                                         break;
  170.                         }
  171.  
  172.                         char szBitmapFilename[MAX_FILE];
  173.                         if (iCopyNumber == 1) {
  174.                                 sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(peh.name));
  175.                         } else {
  176.                                 sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(peh.name), iCopyNumber);
  177.                         }
  178.  
  179.                         if (strlen(szDestFolder) > 0) {
  180.                                 char szAbsoluteBitmapFilename[MAX_FILE+1];
  181.                                 sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
  182.                                 FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
  183.                                 if (!fobBitmap) {
  184.                                         fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
  185.                                         FAIL_CONTINUE;
  186.                                 }
  187.                                 ipe16_write_bmp(fobBitmap, ph.width, ph.height, imagedata, imagedata_len, ct);
  188.                                 fclose(fobBitmap);
  189.                         }
  190.  
  191.                         if (fotIndex) {
  192.                                 // We require this index file for 2 reasons
  193.                                 // 1. Our packer tool can then know what to pack
  194.                                 // 2. The packer tool can know which picture would have a palette appended and which one does not
  195.                                 // The index file won't be written in simulation mode (when no output directory is defined)
  196.                                 fprintf(fotIndex, "%c %c %s %s\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename);
  197.                         }
  198.                         if (verbosity >= 1) {
  199.                                 fprintf(stdout, "%c %c %s %s\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename);
  200.                         }
  201.  
  202.                         free(imagedata);
  203.                 } else if ((compressionType == PIP_COMPRESSIONTYPE_LZW) || (compressionType == PIP_COMPRESSIONTYPE_NONE)) {
  204.                         PipPictureHeader ph;
  205.                         if (fread(&ph, sizeof(ph), 1, fibArt) != 1) {
  206.                                 fprintf(stderr, "ERROR: Cannot read PipPictureHeader of %s\n", peh.name);
  207.                                 FAIL_CONTINUE;
  208.                         }
  209.  
  210.                         size_t imagedata_len = ph.width * ph.height;
  211.                         unsigned char* imagedata = (unsigned char*)malloc(imagedata_len);
  212.  
  213.                         Ipe16ColorTable ct;
  214.                         if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) {
  215.                                 if ((fseek(fibArt, peh.offset+peh.size-sizeof(ct), SEEK_SET) != 0) ||
  216.                                     (fread(&ct, sizeof(ct), 1, fibArt) != 1) ||
  217.                                     (fseek(fibArt, peh.offset+sizeof(ph), SEEK_SET) != 0)) {
  218.                                         fprintf(stderr, "ERROR: Cannot read palette of %s\n", peh.name);
  219.                                         FAIL_CONTINUE;
  220.                                 }
  221.                         } else if (peh.paletteType == IPE16_PALETTETYPE_PARENT) {
  222.                                 ipe16_generate_gray_table(&ct);
  223.                         } else {
  224.                                 fprintf(stderr, "ERROR: Unknown palette type 0x%x at %s\n", peh.paletteType, peh.name);
  225.                                 FAIL_CONTINUE;
  226.                         }
  227.  
  228.                         unsigned int bytes_written, expected_uncompressed_len;
  229.                         switch (ph.compressionType) {
  230.                                 case PIP_COMPRESSIONTYPE_LZW:
  231.                                         if (!lzwDecoder) lzwDecoder = new_ipe16lzw_decoder();
  232.                                         bytes_written = ipe16lzw_decode(fibArt, lzwDecoder, imagedata, imagedata_len);
  233.                                         if (bytes_written < 0) {
  234.                                                 fprintf(stderr, "ERROR: LZW decompression error at %s\n", peh.name);
  235.                                                 FAIL_CONTINUE;
  236.                                         }
  237.                                         if (bytes_written != imagedata_len) {
  238.                                                 fprintf(stderr, "ERROR: Image dimensions and decompressed data size does not match for %s\n", peh.name);
  239.                                                 FAIL_CONTINUE;
  240.                                         }
  241.  
  242.                                         break;
  243.                                 case PIP_COMPRESSIONTYPE_NONE:
  244.                                         expected_uncompressed_len = imagedata_len;
  245.                                         if (peh.paletteType == IPE16_PALETTETYPE_ATTACHED) expected_uncompressed_len += sizeof(ct);
  246.                                         expected_uncompressed_len += sizeof(ph.compressionType)+sizeof(ph.offsetX)+sizeof(ph.offsetY)+sizeof(ph.width)+sizeof(ph.height);
  247.                                         if (expected_uncompressed_len != peh.size) {
  248.                                                 fprintf(stderr, "ERROR: Image dimensions/palette (%d) and defined memory size (%d) does not match for %s\n", expected_uncompressed_len, peh.size, peh.name);
  249.                                                 FAIL_CONTINUE;
  250.                                         }
  251.                                         fread(imagedata, imagedata_len, 1, fibArt); // no error checking, because filesize was already checked
  252.                                         break;
  253.                         }
  254.  
  255.                         char szBitmapFilename[MAX_FILE];
  256.                         if (iCopyNumber == 1) {
  257.                                 sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(peh.name));
  258.                         } else {
  259.                                 sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(peh.name), iCopyNumber);
  260.                         }
  261.  
  262.                         if (strlen(szDestFolder) > 0) {
  263.                                 char szAbsoluteBitmapFilename[MAX_FILE+1];
  264.                                 sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
  265.                                 FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
  266.                                 if (!fobBitmap) {
  267.                                         fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
  268.                                         FAIL_CONTINUE;
  269.                                 }
  270.                                 ipe16_write_bmp(fobBitmap, ph.width, ph.height, imagedata, imagedata_len, ct);
  271.                                 fclose(fobBitmap);
  272.                         }
  273.  
  274.                         if (fotIndex) {
  275.                                 // We require this index file for 2 reasons
  276.                                 // 1. Our packer tool can then know what to pack
  277.                                 // 2. The packer tool can know which picture would have a palette appended and which one does not
  278.                                 // The index file won't be written in simulation mode (when no output directory is defined)
  279.                                 fprintf(fotIndex, "%c %c %s %s %d %d\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename, ph.offsetX, ph.offsetY);
  280.                         }
  281.                         if (verbosity >= 1) {
  282.                                 fprintf(stdout, "%c %c %s %s %d %d\n", peh.paletteType, ph.compressionType, peh.name, szBitmapFilename, ph.offsetX, ph.offsetY);
  283.                         }
  284.  
  285.                         free(imagedata);
  286.                 } else {
  287.                         fprintf(stderr, "ERROR: Unknown compression type 0x%x at %s\n", compressionType, peh.name);
  288.                         FAIL_CONTINUE;
  289.                 }
  290.  
  291.                 fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
  292.         }
  293.  
  294.         if (lzwDecoder) del_ipe16lzw_decoder(lzwDecoder);
  295.  
  296.         if (strlen(szDestFolder) > 0) fclose(fotIndex);
  297.  
  298.         return bEverythingOK;
  299. }
  300.