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.  * - Where's Waldo? Exploring Geography
  5.  * - Eraser Turnabout by Imagination Pilots
  6.  * - Virtual K'Nex by Imagination Pilots
  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 "ipe32_bmpexport.h"
  17. #include "ipe32_artfile.h"
  18. #include "ipe32_lzw_decoder.h"
  19.  
  20. #include "utils.h"
  21.  
  22. #define MAX_FILE 256
  23.  
  24. typedef struct tagIpe32ReadPictureResult {
  25.         uint32_t   writtenBytes;
  26.         uint32_t   numCompressedChunks;
  27.         uint32_t   numRawChunks;
  28. } Ipe32ReadPictureResult;
  29.  
  30. Ipe32ReadPictureResult ipe32_read_picture(FILE* hFile, unsigned char* outbuf, const int outputBufLength, bool bVerbose) {
  31.         unsigned char* lzwbuf = (unsigned char*)malloc(0x8000);
  32.         int availableOutputBytes = outputBufLength;
  33.  
  34.         Ipe32ReadPictureResult res;
  35.         res.numCompressedChunks = 0;
  36.         res.numRawChunks = 0;
  37.         res.writtenBytes = 0;
  38.  
  39.         Ipe32LZWDecoder *decoder = new_ipe32lzw_decoder();
  40.         ipe32lzw_init_decoder(decoder);
  41.         if (outputBufLength != 0) {
  42.                 int chunkNo = 0;
  43.                 do {
  44.                         uint16_t len;
  45.                         fread(&len, 1, 2, hFile);
  46.  
  47.                         uint16_t writtenBytes;
  48.                         if (len < 0x8000) {
  49.                                 fread(lzwbuf, 1, len, hFile);
  50.                                 res.numCompressedChunks++;
  51.                                 if (bVerbose) fprintf(stdout, "Chunk %d (compressed, length: %d) ...\n", chunkNo, len);
  52.  
  53.                                 // Requirement 1: Each chunk (except the last one) MUST have 0x3FFE of uncompressed data
  54.                                 uint16_t expectedOutputSize = availableOutputBytes > 0x3FFE ? 0x3FFE : availableOutputBytes;
  55.  
  56.                                 // Requirement 2: The size of the uncompressed data must not exceed the size of the compressed data
  57.                                 size_t maxReadBytes = expectedOutputSize;
  58.  
  59.                                 writtenBytes = ipe32lzw_decode(decoder, outbuf, outputBufLength, lzwbuf, maxReadBytes); // returns bytes written, or -1
  60.  
  61.                                 if (writtenBytes == -1) {
  62.                                         fprintf(stderr, "ERROR: Fatal error during decompression of chunk %d!\n", chunkNo);
  63.                                         break;
  64.                                 }
  65.  
  66.                                 if (writtenBytes != expectedOutputSize) {
  67.                                         fprintf(stderr, "ERROR: Chunk %d decompressed %d bytes, but %d bytes are expected!\n", chunkNo, writtenBytes, expectedOutputSize);
  68.                                         break;
  69.                                 }
  70.                         } else {
  71.                                 len &= 0x7FFF;
  72.                                 res.numRawChunks++;
  73.                                 if (bVerbose) fprintf(stdout, "Chunk %d (raw, length: %d) ...\n", chunkNo, len);
  74.                                 fread(outbuf, 1, len, hFile);
  75.                                 writtenBytes = len;
  76.                         }
  77.                         outbuf += writtenBytes;
  78.                         availableOutputBytes -= writtenBytes;
  79.                         chunkNo++;
  80.                 } while (availableOutputBytes != 0);
  81.         }
  82.         ipe32lzw_free_decoder(decoder);
  83.  
  84.         free(lzwbuf);
  85.         res.writtenBytes = outputBufLength-availableOutputBytes;
  86.         return res;
  87. }
  88.  
  89. bool ipe32_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
  90.         bool bEverythingOK = true;
  91.  
  92.         fseek(fibArt, 0, SEEK_SET);
  93.  
  94.         Ipe32FileHeader efh;
  95.         if (fread(&efh, sizeof(efh), 1, fibArt) != 1) {
  96.                 fprintf(stderr, "FATAL: Cannot read Ipe32FileHeader. It is probably not an art file.\n");
  97.                 return false;
  98.         }
  99.  
  100.         // Check if the super header is correct
  101.         if ((memcmp(efh.magic, IPE32_MAGIC_ART, 8) != 0) || (efh.reserved != 0)) {
  102.                 fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
  103.                 return false;
  104.         }
  105.  
  106.         FILE* fotIndex = NULL;
  107.         if (strlen(szDestFolder) > 0) {
  108.                 char szIndexFilename[MAX_FILE];
  109.                 sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
  110.                 fotIndex = fopen(szIndexFilename, "wt");
  111.                 if (!fotIndex) {
  112.                         fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
  113.                         return false;
  114.                 }
  115.         }
  116.  
  117.         const int numPictures = efh.totalHeaderSize/sizeof(efh) - 1;
  118.         char knownNames[numPictures][IPE32_NAME_SIZE];
  119.         memset(knownNames, 0, numPictures*IPE32_NAME_SIZE);
  120.         int iPicNo;
  121.         for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
  122.                 Ipe32PictureEntryHeader peh;
  123.                 if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
  124.                         fprintf(stderr, "FATAL: Cannot read Ipe32PictureEntryHeader.\n");
  125.                         return false;
  126.                 }
  127.  
  128.                 char szName[IPE32_NAME_SIZE+1]={0};
  129.                 memcpy(szName, peh.name, IPE32_NAME_SIZE);
  130.  
  131.                 // Begin duplicate check
  132.                 // In ERASER, there are a few pictures which have the same identifier, in the same ART file!
  133.                 memcpy(&knownNames[iPicNo][0], peh.name, IPE32_NAME_SIZE);
  134.                 int iCopyNumber = 0;
  135.                 int j;
  136.                 for (j=0; j<=iPicNo; ++j) {
  137.                         if (memcmp(&knownNames[j][0], peh.name, IPE32_NAME_SIZE) == 0) ++iCopyNumber;
  138.                 }
  139.                 assert(iCopyNumber > 0);
  140.                 // End duplicate check
  141.  
  142.                 if (verbosity >= 2) fprintf(stdout, "Extracting %s (expected file size: %d bytes) ...\n", szName, peh.uncompressedSize);
  143.  
  144.                 size_t headersPos = ftell(fibArt);
  145.                 #define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
  146.  
  147.                 if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
  148.                         fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", szName);
  149.                         FAIL_CONTINUE;
  150.                 }
  151.  
  152.                 int outputBufLen = peh.uncompressedSize;
  153.                 unsigned char* outputBuf = (unsigned char*)malloc(outputBufLen);
  154.  
  155.                 char szBitmapFilename[MAX_FILE];
  156.                 if (iCopyNumber == 1) {
  157.                         sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(szName));
  158.                 } else {
  159.                         sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(szName), iCopyNumber);
  160.                 }
  161.  
  162.                 Ipe32ReadPictureResult res = ipe32_read_picture(fibArt, outputBuf, outputBufLen, verbosity >= 2);
  163.                 if (res.writtenBytes != outputBufLen) {
  164.                         fprintf(stderr, "FATAL: Error reading picture %s (compression failure?)\n", szName);
  165.                         FAIL_CONTINUE;
  166.                 }
  167.  
  168.                 if (strlen(szDestFolder) > 0) {
  169.                         char szAbsoluteBitmapFilename[MAX_FILE+1];
  170.                         sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
  171.                         FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
  172.                         if (!fobBitmap) {
  173.                                 fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
  174.                                 FAIL_CONTINUE;
  175.                         }
  176.                         ipe32_write_bmp(fobBitmap, outputBuf, outputBufLen);
  177.                         fclose(fobBitmap);
  178.                 }
  179.  
  180.                 free(outputBuf);
  181.  
  182.                 if (fotIndex) {
  183.                         // We require this index file so that our packer tool can know what to pack
  184.                         // The index file won't be written in simulation mode (when no output directory is defined)
  185.                         fprintf(fotIndex, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
  186.                 }
  187.                 if (verbosity >= 1) {
  188.                         fprintf(stdout, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
  189.                 }
  190.  
  191.                 fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
  192.         }
  193.  
  194.         if (strlen(szDestFolder) > 0) fclose(fotIndex);
  195.  
  196.         return bEverythingOK;
  197. }
  198.