diff --git a/.gitignore b/.gitignore index 789ef81..76a0d79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -.vscode +.vscode/ patcher-installer diff --git a/.vscode/settings.json b/.vscode/settings.json index 33035a9..4f71092 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,10 @@ "decrypt.h": "c", "stdint.h": "c", "decryptutilities.h": "c" - } -} \ No newline at end of file + }, + "cSpell.words": [ + "init", + "miniz", + "tinfl" + ] +} diff --git a/src/fileReader.c b/src/fileReader.c index 9daa167..0872082 100644 --- a/src/fileReader.c +++ b/src/fileReader.c @@ -32,13 +32,8 @@ void initFileReader(char path[], unsigned long offset) { //Reads the given amount of bytes from the file and returns them. Automatically opens next file if EOF is reached. -char* getBytes(unsigned long numBytes) { - char* output = malloc(numBytes); - if (output == NULL) { - fprintf(stderr, "Could not allocate %lu bytes.\n", numBytes); - exit(1); - } - char* bufferPosition = output; +void getBytes(char* buffer, unsigned long numBytes) { + char* bufferPosition = buffer; unsigned long remainingBytes = numBytes; //As long as we still need to read bytes @@ -54,6 +49,4 @@ char* getBytes(unsigned long numBytes) { openNextFile(); } } - - return output; } diff --git a/src/fileReader.h b/src/fileReader.h index 5db6687..fb76723 100644 --- a/src/fileReader.h +++ b/src/fileReader.h @@ -2,4 +2,4 @@ void initFileReader(char path[], unsigned long offset); -char* getBytes(unsigned long numBytes); +void getBytes(char* buffer, unsigned long numBytes); diff --git a/src/inflate.c b/src/inflate.c new file mode 100644 index 0000000..49f7011 --- /dev/null +++ b/src/inflate.c @@ -0,0 +1,98 @@ +//Based on https://github.com/richgel999/miniz/blob/master/examples/example5.c (Public domain, April 11 2012, Rich Geldreich, richgel99@gmail.com) + +#include +#include + +//Include miniz, disabling what we don't need +#define MINIZ_NO_STDIO +#define MINIZ_NO_ARCHIVE_APIS +#define MINIZ_NO_TIME +#define MINIZ_NO_ZLIB_APIS +#define MINIZ_NO_MALLOC +#include "../lib/miniz/miniz.h" + +#include "inflate.h" + +//The compressed and uncompressed buffer arrays +char* comprBuffer; +char* uncomprBuffer; +//Position of the inflater in the buffer arrays +char* comprBufferNext; +char* uncomprBufferNext; +//Buffer lengths +unsigned long uncomprBufferSize; +unsigned long remainingInput; +unsigned long spaceInOutput; +//Internal variables used by miniz +tinfl_decompressor inflator; +tdefl_status status; +size_t in_bytes, out_bytes; + +void inflateInit(char* comprBufferIn, char* uncomprBufferIn, unsigned long uncomprBufferSizeIn) { + comprBuffer = comprBufferIn; + uncomprBuffer = uncomprBufferIn; + uncomprBufferSize = uncomprBufferSizeIn; + tinfl_init(&inflator); +} + +struct InflateOutput inflateInflate(unsigned long numInputBytes, bool hasMoreBytes) { + if (numInputBytes == 0) { + //continue in input buffer where we were before + } else { + //go back to beginning of input buffer + remainingInput = numInputBytes; + comprBufferNext = comprBuffer; + } + //output buffer has been emptied + spaceInOutput = uncomprBufferSize; + uncomprBufferNext = uncomprBuffer; + + struct InflateOutput out; + + while (remainingInput > 0 && spaceInOutput > 0) { + in_bytes = remainingInput; + out_bytes = spaceInOutput; + + //Call decompression + status = tinfl_decompress( + &inflator, + (const mz_uint8 *)comprBufferNext, + &in_bytes, + uncomprBuffer, + (mz_uint8 *)uncomprBufferNext, + &out_bytes, + (hasMoreBytes ? TINFL_FLAG_HAS_MORE_INPUT : 0) + ); + + //Adjust buffer positions by the amount of bytes processed + comprBufferNext += in_bytes; + remainingInput -= in_bytes; + uncomprBufferNext += out_bytes; + spaceInOutput -= out_bytes; + + //Check for errors + if (status <= TINFL_STATUS_DONE) { + if (status == TINFL_STATUS_DONE) { + // Decompression completed successfully. + break; + } else { + // Decompression failed. + fprintf(stderr, "tinfl_decompress() failed with status %i!\n", status); + exit(1); + } + } + + //If output buffer is filled + if (spaceInOutput == 0) { + break; + } + } + + //Return output + if (remainingInput == 0) { + out.needMoreInput = hasMoreBytes; + } + out.numBytesWrittenToOutput = uncomprBufferSize - spaceInOutput; + + return out; +} diff --git a/src/inflate.h b/src/inflate.h new file mode 100644 index 0000000..395a4ec --- /dev/null +++ b/src/inflate.h @@ -0,0 +1,10 @@ +#pragma once + +void inflateInit(char* comprBuffer, char* uncomprBuffer, unsigned long uncomprBufferSize); + +struct InflateOutput { + bool needMoreInput; + unsigned long numBytesWrittenToOutput; +}; + +struct InflateOutput inflateInflate(unsigned long numInputBytes, bool hasMoreBytes); diff --git a/src/main.c b/src/main.c index 32130d8..55fd357 100644 --- a/src/main.c +++ b/src/main.c @@ -3,13 +3,15 @@ #include #include #include +#include //Import our code #include "decrypt.h" #include "fileReader.h" +#include "inflate.h" #include "utils/min.h" -#define BUFFER_SIZE 0xffffUL +#define BUFFER_SIZE 512UL * 1024UL //512 KiB #define ENCRYPTION_HEADER_LENGTH 12UL #define LOCAL_FILE_HEADER_MAGIC (uint32_t)0x04034b50 @@ -45,55 +47,78 @@ int main(int argc, unsigned char *argv[]) { //------------------------------------------------- - //Initialize file reader - initFileReader(archiveName, archiveOffset); - - //Skip local file header (30 bytes + additional length) - char* fileHeader = getBytes(30UL); - //Check that header is correct - const uint32_t magic = getUint32(fileHeader); - if (magic != LOCAL_FILE_HEADER_MAGIC) { - fprintf(stderr, "Wrong magic in local file header, expected %#010x but found %#010x.", LOCAL_FILE_HEADER_MAGIC, magic); + char* compressedChunk = malloc(BUFFER_SIZE); + if (compressedChunk == NULL) { + fprintf(stderr, "Could not allocate %lu bytes for compressed buffer.\n", BUFFER_SIZE); exit(1); } - free(fileHeader); - //Read additional length - const unsigned long additionalLength = getUint16(fileHeader + 26) + getUint16(fileHeader + 28); - char* additionalBytes = getBytes(additionalLength); - free(additionalBytes); - //If file is encrypted, skip 12-byte encryption header - if (isEncrypted) { - char* encrHeader = getBytes(ENCRYPTION_HEADER_LENGTH); - decrypt(encrHeader, ENCRYPTION_HEADER_LENGTH); - free(encrHeader); + char* uncompressedChunk = malloc(BUFFER_SIZE); + if (uncompressedChunk == NULL) { + fprintf(stderr, "Could not allocate %lu bytes for uncompressed buffer.\n", BUFFER_SIZE); + exit(1); } //------------------------------------------------- + //Initialize file reader + initFileReader(archiveName, archiveOffset); + + //Skip local file header (30 bytes + additional length) + getBytes(compressedChunk, 30UL); + //Check that header is correct + const uint32_t magic = getUint32(compressedChunk); + if (magic != LOCAL_FILE_HEADER_MAGIC) { + fprintf(stderr, "Wrong magic in local file header, expected %#010x but found %#010x.", LOCAL_FILE_HEADER_MAGIC, magic); + exit(1); + } + //Read additional length + const unsigned long additionalLength = getUint16(compressedChunk + 26) + getUint16(compressedChunk + 28); + getBytes(compressedChunk, additionalLength); + + //If file is encrypted, skip 12-byte encryption header + if (isEncrypted) { + getBytes(compressedChunk, ENCRYPTION_HEADER_LENGTH); + decrypt(compressedChunk, ENCRYPTION_HEADER_LENGTH); + } + + //------------------------------------------------- + + struct InflateOutput inflateResult; + inflateInit(compressedChunk, uncompressedChunk, BUFFER_SIZE); + //Read actual file unsigned long remainingBytes = fileLength; + bool needToRead = true; + unsigned long chunkSize; while (remainingBytes > 0) { - const unsigned long chunkSize = min(BUFFER_SIZE, remainingBytes); - char* chunk = getBytes(chunkSize); - remainingBytes -= chunkSize; + if (needToRead) { + chunkSize = min(BUFFER_SIZE, remainingBytes); + getBytes(compressedChunk, chunkSize); + remainingBytes -= chunkSize; - //Decrypt file if it is encrypted - if (isEncrypted) { - //TODO: For highest performance, we need to move if condition outside of while loop - decrypt(chunk, chunkSize); + //Decrypt file if it is encrypted + if (isEncrypted) { + //TODO: For highest performance, we need to move if condition outside of while loop + decrypt(compressedChunk, chunkSize); + } } //Decompress file - //TODO + //bytes are contained in uncompressedChunk from [0, inflateResult.numBytesWrittenToOutput - 1] + inflateResult = inflateInflate(needToRead ? chunkSize : 0, remainingBytes > 0); + needToRead = inflateResult.needMoreInput; + + write(1, uncompressedChunk, inflateResult.numBytesWrittenToOutput); //Optionally perform xdelta3 //TODO - //release memory - //TODO: need to malloc once outside of while loop, and then reuse it instead of a new malloc() each loop - free(chunk); } + //release memory + free(compressedChunk); + free(uncompressedChunk); + return 0; }