diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f71092..54092be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "filereader.h": "c", "decrypt.h": "c", "stdint.h": "c", - "decryptutilities.h": "c" + "decryptutilities.h": "c", + "fileutilities.h": "c" }, "cSpell.words": [ "init", diff --git a/README.md b/README.md index 8fc4965..8cdbd41 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ This tool installs a patch, assuming the patch files are already downloaded, and if not patching from -1, that the previous patch is already installed. +# Usage + +To compile: + +``` +./build.sh +``` + +To use: + +``` +./patcher-installer --disk /tmp/patcher/cdn-patch.swtor.com/patch/assets_swtor_main/assets_swtor_main_-1to0/assets_swtor_main_-1to0.z08 --offset 267071107 --size 165738618 --keys 3393608425 2820264972 3930508185 > ../swtor_main_gfx_1.tor +``` + # Dependencies ## Miniz <[https://github.com/richgel999/miniz](https://github.com/richgel999/miniz)> diff --git a/src/fileReader.c b/src/fileReader.c index 0f25a2e..3491db5 100644 --- a/src/fileReader.c +++ b/src/fileReader.c @@ -31,7 +31,7 @@ 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. +//Reads the given amount of bytes from the file and writes them into the given buffer. Automatically opens next file if EOF is reached. void getBytes(uint8_t* buffer, unsigned long numBytes) { char* bufferPosition = buffer; unsigned long remainingBytes = numBytes; @@ -40,8 +40,14 @@ void getBytes(uint8_t* buffer, unsigned long numBytes) { while (remainingBytes > 0) { //Read as many bytes as we can from the current file const unsigned long availableBytes = min(remainingBytes, file.size - file.offset); - readBytesIntoBuffer(bufferPosition, availableBytes); - bufferPosition += availableBytes; + + //Read bytes into buffer if we have a valid buffer, otherwise skip bytes + if (buffer) { + readBytesIntoBuffer(bufferPosition, availableBytes); + bufferPosition += availableBytes; + } else { + skipBytes(availableBytes); + } remainingBytes -= availableBytes; //If we've reached end of file, close file and open next file diff --git a/src/main.c b/src/main.c index bbddada..a2d6e29 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -26,24 +27,98 @@ uint32_t getUint32(uint8_t* buffer) { (uint32_t)buffer[3] << 24; } -int main(int argc, unsigned char *argv[]) { - if (argc != 4 && argc != 7) { - fprintf(stderr, "Wrong number of arguments. Usage: patcher-installer [ ]"); +struct arguments { + /** Path to the disk file that contains the start of the file we want to extract. + * The file may stretch across multiple disks though. + */ + char* diskName; + /** Offset into the disk where the file starts. */ + unsigned long diskOffset; + /** Size of the file stored in the disk. */ + unsigned long fileSize; + //Decryption keys + bool isEncrypted; + uint32_t key0; + uint32_t key1; + uint32_t key2; + //TODO: For xdelta3, the location of the old file + char* prevFile; +}; + +static struct option long_options[] = { + {"disk", required_argument, 0, 'd'}, + {"offset", required_argument, 0, 'o'}, + {"size", required_argument, 0, 's'}, + {"keys", required_argument, 0, 'k'}, + {"prev", required_argument, 0, 'p'}, +}; + +//Stores current state from command line arguments, initialized to zero +struct arguments state = {}; + +int main(int argc, char *argv[]) { + //Parse command line arguments + + int requiredOptions = 0; + + while (1) { + //in this variable, getopt_long stores the current position in the command line args array + int option_index = 0; + + int curOption = getopt_long(argc, argv, "d:o:s:k:", long_options, &option_index); + + //end of command line arguments reached + if (curOption == -1) { + break; + } + + switch (curOption) { + case 'd': //disk name + state.diskName = optarg; + requiredOptions |= 1; + break; + case 'o': //offset + state.diskOffset = atol(optarg); + requiredOptions |= 2; + break; + case 's': //size + state.fileSize = atol(optarg); + requiredOptions |= 4; + break; + case 'k': { //decryption keys + //TODO: parse from optarg + uint32_t key0 = atoi(argv[8]); + uint32_t key1 = atoi(argv[9]); + uint32_t key2 = atoi(argv[10]); + //Initialize decryption (pass decryption keys) + initDecryptor(key0, key1, key2); + state.isEncrypted = true; + break; + } + default: + fprintf(stderr, "Unknown option '%c'.", (char)curOption); + exit(1); + } + } + + if (requiredOptions != 7) { + fprintf(stderr, "Missing arguments, received %i.", requiredOptions); exit(1); } - //TODO: verify argv and assign it to variables - char* archiveName = argv[1]; - const unsigned long archiveOffset = atol(argv[2]); - const unsigned long fileLength = atol(argv[3]); - const bool isEncrypted = argc == 7; - if (isEncrypted) { + //TODO: verify argv and assign it to variables + //char* archiveName = argv[1]; + //const unsigned long archiveOffset = atol(argv[2]); + //const unsigned long fileLength = atol(argv[3]); + + //const bool isEncrypted = argc == 7; + /*if (isEncrypted) { uint32_t key0 = atoi(argv[4]); uint32_t key1 = atoi(argv[5]); uint32_t key2 = atoi(argv[6]); //Initialize decryption (pass decryption keys) initDecryptor(key0, key1, key2); - } + }*/ //------------------------------------------------- @@ -63,7 +138,7 @@ int main(int argc, unsigned char *argv[]) { //------------------------------------------------- //Initialize file reader - initFileReader(archiveName, archiveOffset); + initFileReader(state.diskName, state.diskOffset); //Skip local file header (30 bytes + additional length) getBytes(compressedChunk, 30UL); @@ -75,10 +150,12 @@ int main(int argc, unsigned char *argv[]) { } //Read additional length const unsigned long additionalLength = getUint16(compressedChunk + 26) + getUint16(compressedChunk + 28); - getBytes(compressedChunk, additionalLength); + if (additionalLength > 0UL) { + getBytes(NULL, additionalLength); + } //If file is encrypted, skip 12-byte encryption header - if (isEncrypted) { + if (state.isEncrypted) { getBytes(compressedChunk, ENCRYPTION_HEADER_LENGTH); decrypt(compressedChunk, ENCRYPTION_HEADER_LENGTH); } @@ -89,7 +166,7 @@ int main(int argc, unsigned char *argv[]) { inflateInit(compressedChunk, uncompressedChunk, BUFFER_SIZE); //Read actual file - unsigned long remainingBytes = fileLength; + unsigned long remainingBytes = state.fileSize; bool needToRead = true; bool hasReachedEnd = false; unsigned long chunkSize; @@ -101,7 +178,7 @@ int main(int argc, unsigned char *argv[]) { remainingBytes -= chunkSize; //Decrypt file if it is encrypted - if (isEncrypted) { + if (state.isEncrypted) { //TODO: For highest performance, we need to move if condition outside of while loop decrypt(compressedChunk, chunkSize); } @@ -115,6 +192,7 @@ int main(int argc, unsigned char *argv[]) { //important: we must not modify uncompressedChunk since miniz may use it as dictionary and read from it during the next invocation of inflateInflate() + //Write to stdout write(1, uncompressedChunk + uncompressedPosition, inflateResult.numBytesWrittenToOutput); uncompressedPosition += inflateResult.numBytesWrittenToOutput; while (uncompressedPosition >= BUFFER_SIZE) { @@ -122,8 +200,9 @@ int main(int argc, unsigned char *argv[]) { } //Optionally perform xdelta3 + if (state.prevFile) { //TODO - + } } //release memory diff --git a/src/utils/fileUtilities.c b/src/utils/fileUtilities.c index 53786c0..77d6882 100644 --- a/src/utils/fileUtilities.c +++ b/src/utils/fileUtilities.c @@ -19,6 +19,19 @@ void readBytesIntoBuffer(uint8_t* buffer, long numBytes) { } +//Skips the given amount of bytes in the file +void skipBytes(long numBytes) { + const int result = fseek(file.filePointer, numBytes, SEEK_CUR); + + if (result != 0) { + fprintf(stderr, "Could not skip %lu bytes from file: %s\n", numBytes, strerror(errno)); + exit(1); + } else { + file.offset += numBytes; + } +} + + //Closes the currently opened file and opens the next file at its beginning. void openNextFile() { const int closeResult = fclose(file.filePointer); diff --git a/src/utils/fileUtilities.h b/src/utils/fileUtilities.h index ffd3fd9..2f8508d 100644 --- a/src/utils/fileUtilities.h +++ b/src/utils/fileUtilities.h @@ -17,5 +17,9 @@ struct FILE_INFO file; void readBytesIntoBuffer(uint8_t* buffer, long numBytes); +//Skips the given amount of bytes in the file +void skipBytes(long numBytes); + + //Closes the currently opened file and opens the next file at its beginning. void openNextFile();