Subversion Repositories ipe_artfile_utils

Compare Revisions

No changes between revisions

Regard whitespace Rev 1 → Rev 2

/trunk/ipe_artfile_unpacker_ipe32.c
0,0 → 1,197
/**
* ART file unpacker by Daniel Marschall, ViaThinkSoft (C) 2014-2018
* Supports:
* - Where's Waldo? Exploring Geography
* - Eraser Turnabout by Imagination Pilots
* - Virtual K'Nex by Imagination Pilots
* Revision: 2018-02-15
**/
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <getopt.h>
 
#include "ipe32_bmpexport.h"
#include "ipe32_artfile.h"
#include "ipe32_lzw_decoder.h"
 
#include "utils.h"
 
#define MAX_FILE 256
 
typedef struct tagIpe32ReadPictureResult {
uint32_t writtenBytes;
uint32_t numCompressedChunks;
uint32_t numRawChunks;
} Ipe32ReadPictureResult;
 
Ipe32ReadPictureResult ipe32_read_picture(FILE* hFile, unsigned char* outbuf, const int outputBufLength, bool bVerbose) {
unsigned char* lzwbuf = (unsigned char*)malloc(0x8000);
int availableOutputBytes = outputBufLength;
 
Ipe32ReadPictureResult res;
res.numCompressedChunks = 0;
res.numRawChunks = 0;
res.writtenBytes = 0;
 
Ipe32LZWDecoder *decoder = new_ipe32lzw_decoder();
ipe32lzw_init_decoder(decoder);
if (outputBufLength != 0) {
int chunkNo = 0;
do {
uint16_t len;
fread(&len, 1, 2, hFile);
 
uint16_t writtenBytes;
if (len < 0x8000) {
fread(lzwbuf, 1, len, hFile);
res.numCompressedChunks++;
if (bVerbose) fprintf(stdout, "Chunk %d (compressed, length: %d) ...\n", chunkNo, len);
 
// Requirement 1: Each chunk (except the last one) MUST have 0x3FFE of uncompressed data
uint16_t expectedOutputSize = availableOutputBytes > 0x3FFE ? 0x3FFE : availableOutputBytes;
 
// Requirement 2: The size of the uncompressed data must not exceed the size of the compressed data
size_t maxReadBytes = expectedOutputSize;
 
writtenBytes = ipe32lzw_decode(decoder, outbuf, outputBufLength, lzwbuf, maxReadBytes); // returns bytes written, or -1
 
if (writtenBytes == -1) {
fprintf(stderr, "ERROR: Fatal error during decompression of chunk %d!\n", chunkNo);
break;
}
 
if (writtenBytes != expectedOutputSize) {
fprintf(stderr, "ERROR: Chunk %d decompressed %d bytes, but %d bytes are expected!\n", chunkNo, writtenBytes, expectedOutputSize);
break;
}
} else {
len &= 0x7FFF;
res.numRawChunks++;
if (bVerbose) fprintf(stdout, "Chunk %d (raw, length: %d) ...\n", chunkNo, len);
fread(outbuf, 1, len, hFile);
writtenBytes = len;
}
outbuf += writtenBytes;
availableOutputBytes -= writtenBytes;
chunkNo++;
} while (availableOutputBytes != 0);
}
ipe32lzw_free_decoder(decoder);
 
free(lzwbuf);
res.writtenBytes = outputBufLength-availableOutputBytes;
return res;
}
 
bool ipe32_extract_art_to_folder(FILE* fibArt, const char* szDestFolder, const int verbosity) {
bool bEverythingOK = true;
 
fseek(fibArt, 0, SEEK_SET);
 
Ipe32FileHeader efh;
if (fread(&efh, sizeof(efh), 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read Ipe32FileHeader. It is probably not an art file.\n");
return false;
}
 
// Check if the super header is correct
if ((memcmp(efh.magic, IPE32_MAGIC_ART, 8) != 0) || (efh.reserved != 0)) {
fprintf(stderr, "FATAL: Something does not seem to be correct with this art file's header. It is probably not an art file.\n");
return false;
}
 
FILE* fotIndex = NULL;
if (strlen(szDestFolder) > 0) {
char szIndexFilename[MAX_FILE];
sprintf(szIndexFilename, "%s/index.txt", szDestFolder);
fotIndex = fopen(szIndexFilename, "wt");
if (!fotIndex) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szIndexFilename);
return false;
}
}
 
const int numPictures = efh.totalHeaderSize/sizeof(efh) - 1;
char knownNames[numPictures][IPE32_NAME_SIZE];
memset(knownNames, 0, numPictures*IPE32_NAME_SIZE);
int iPicNo;
for (iPicNo=0; iPicNo<numPictures; ++iPicNo) {
Ipe32PictureEntryHeader peh;
if (fread(&peh, sizeof(peh), 1, fibArt) != 1) {
fprintf(stderr, "FATAL: Cannot read Ipe32PictureEntryHeader.\n");
return false;
}
 
char szName[IPE32_NAME_SIZE+1]={0};
memcpy(szName, peh.name, IPE32_NAME_SIZE);
 
// Begin duplicate check
// In ERASER, there are a few pictures which have the same identifier, in the same ART file!
memcpy(&knownNames[iPicNo][0], peh.name, IPE32_NAME_SIZE);
int iCopyNumber = 0;
int j;
for (j=0; j<=iPicNo; ++j) {
if (memcmp(&knownNames[j][0], peh.name, IPE32_NAME_SIZE) == 0) ++iCopyNumber;
}
assert(iCopyNumber > 0);
// End duplicate check
 
if (verbosity >= 2) fprintf(stdout, "Extracting %s (expected file size: %d bytes) ...\n", szName, peh.uncompressedSize);
 
size_t headersPos = ftell(fibArt);
#define FAIL_CONTINUE { bEverythingOK = false; fseek(fibArt, headersPos, SEEK_SET); continue; }
 
if (fseek(fibArt, peh.offset, SEEK_SET) != 0) {
fprintf(stderr, "ERROR: Error jumping to offset defined for %s\n", szName);
FAIL_CONTINUE;
}
 
int outputBufLen = peh.uncompressedSize;
unsigned char* outputBuf = (unsigned char*)malloc(outputBufLen);
 
char szBitmapFilename[MAX_FILE];
if (iCopyNumber == 1) {
sprintf(szBitmapFilename, "%s.bmp", sanitize_filename(szName));
} else {
sprintf(szBitmapFilename, "%s__%d.bmp", sanitize_filename(szName), iCopyNumber);
}
 
Ipe32ReadPictureResult res = ipe32_read_picture(fibArt, outputBuf, outputBufLen, verbosity >= 2);
if (res.writtenBytes != outputBufLen) {
fprintf(stderr, "FATAL: Error reading picture %s (compression failure?)\n", szName);
FAIL_CONTINUE;
}
 
if (strlen(szDestFolder) > 0) {
char szAbsoluteBitmapFilename[MAX_FILE];
sprintf(szAbsoluteBitmapFilename, "%s/%s", szDestFolder, szBitmapFilename);
FILE* fobBitmap = fopen(szAbsoluteBitmapFilename, "wb");
if (!fobBitmap) {
fprintf(stderr, "FATAL: Cannot open %s for writing\n", szAbsoluteBitmapFilename);
FAIL_CONTINUE;
}
ipe32_write_bmp(fobBitmap, outputBuf, outputBufLen);
fclose(fobBitmap);
}
 
free(outputBuf);
 
if (fotIndex) {
// We require this index file so that our packer tool can know what to pack
// The index file won't be written in simulation mode (when no output directory is defined)
fprintf(fotIndex, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
}
if (verbosity >= 1) {
fprintf(stdout, "%s %d(C) %d(R) %s\n", szName, res.numCompressedChunks, res.numRawChunks, szBitmapFilename);
}
 
fseek(fibArt, headersPos, SEEK_SET); // we were already there, so we don't need to check for errors
}
 
if (strlen(szDestFolder) > 0) fclose(fotIndex);
 
return bEverythingOK;
}
Property changes:
Added: svn:mime-type
+text/x-csrc
\ No newline at end of property