From 7b3273696bf86dab3f1041d0a460780eecd7a575 Mon Sep 17 00:00:00 2001 From: brliron Date: Thu, 24 Aug 2017 17:29:26 +0200 Subject: [PATCH] Tasofro: add support for th135 encryption and memory objects. --- thcrap_tasofro/src/crypt.cpp | 154 ++++++++++++++++++++++++++ thcrap_tasofro/src/crypt.h | 50 +++++++++ thcrap_tasofro/src/crypt_block.cpp | 54 --------- thcrap_tasofro/src/files_list.cpp | 37 ++----- thcrap_tasofro/src/thcrap_tasofro.cpp | 47 ++++++-- thcrap_tasofro/src/thcrap_tasofro.h | 2 - thcrap_tasofro/thcrap_tasofro.vcxproj | 3 +- 7 files changed, 253 insertions(+), 94 deletions(-) create mode 100644 thcrap_tasofro/src/crypt.cpp create mode 100644 thcrap_tasofro/src/crypt.h delete mode 100644 thcrap_tasofro/src/crypt_block.cpp diff --git a/thcrap_tasofro/src/crypt.cpp b/thcrap_tasofro/src/crypt.cpp new file mode 100644 index 00000000..ecefb723 --- /dev/null +++ b/thcrap_tasofro/src/crypt.cpp @@ -0,0 +1,154 @@ +/** + * Touhou Community Reliant Automatic Patcher + * Tasogare Frontier support plugin + * + * ---- + * + * XOR-based encryption. + */ + +#include +#include "thcrap_tasofro.h" +#include "crypt.h" + +ICrypt* ICrypt::instance = nullptr; + +DWORD CryptTh135::cryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key) +{ + for (DWORD j = 0; j < FileSize / 4; j++) + { + *((DWORD*)Data + j) ^= Key[j % 4]; + } + + DWORD remain = FileSize % 4; + if (remain) + { + DWORD tk = Key[FileSize / 4 % 4]; + for (DWORD j = 0; j < remain; j++) + { + Data[FileSize - remain + j] ^= tk & 0xFF; + tk >>= 8; + } + } + return 0; +} + +void CryptTh135::uncryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key) +{ + this->cryptBlock(Data, FileSize, Key); +} + +// Normalized Hash +DWORD CryptTh135::SpecialFNVHash(const char *begin, const char *end, DWORD initHash) +{ + DWORD hash; // eax@1 + DWORD ch; // esi@2 + + int inMBCS = 0; + for (hash = initHash; begin != end; hash = ch ^ 0x1000193 * hash) + { + ch = *begin++; + if (!inMBCS && ((unsigned char)ch >= 0x81u && (unsigned char)ch <= 0x9Fu || (unsigned char)ch + 32 <= 0x1Fu)) inMBCS = 2; + if (!inMBCS) + { + ch = tolower(ch); // bad ass style but WORKS PERFECTLY! + if (ch == '/') ch = '\\'; + } + else inMBCS--; + } + return hash; +} + +void CryptTh135::convertKey(DWORD*) +{} + + + +CryptTh145::CryptTh145() + : tempCopy(nullptr), tempCopySize(0) +{} + +CryptTh145::~CryptTh145() +{ + if (this->tempCopy) { + delete[] this->tempCopy; + } +} + +DWORD CryptTh145::cryptBlockInternal(BYTE* Data, DWORD FileSize, DWORD* Key, DWORD Aux) +{ + BYTE *key = (BYTE*)Key; + BYTE *aux = (BYTE*)&Aux; + int i; + + for (i = FileSize - 1; i >= 0; i--) { + BYTE unencByte = Data[i]; + BYTE encByte = aux[i % 4]; + Data[i] = aux[i % 4]; + aux[i % 4] = unencByte ^ encByte ^ key[i % 16]; + } + return Aux; +} + +DWORD CryptTh145::cryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key) +{ + if (FileSize > this->tempCopySize) { + if (this->tempCopy) { + delete[] this->tempCopy; + } + this->tempCopy = new BYTE[FileSize]; + this->tempCopySize = FileSize; + } + + DWORD Aux; + memcpy(this->tempCopy, Data, FileSize); + Aux = this->cryptBlockInternal(tempCopy, FileSize, Key, Key[0]); // This call seems to give the correct Aux value. + + return this->cryptBlockInternal(Data, FileSize, Key, Aux); +} + +void CryptTh145::uncryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key) +{ + BYTE *key = (BYTE*)Key; + BYTE aux[4]; + DWORD i; + + for (i = 0; i < 4; i++) { + aux[i] = key[i]; + } + + for (i = 0; i < FileSize; i++) { + BYTE tmp = Data[i]; + Data[i] = Data[i] ^ key[i % 16] ^ aux[i % 4]; + aux[i % 4] = tmp; + } +} + +// Normalized Hash +DWORD CryptTh145::SpecialFNVHash(const char *begin, const char *end, DWORD initHash) +{ + DWORD hash; // eax@1 + DWORD ch; // esi@2 + + int inMBCS = 0; + for (hash = initHash; begin != end; hash = (hash ^ ch) * 0x1000193) + { + ch = *begin++; + if (!inMBCS && ((unsigned char)ch >= 0x81u && (unsigned char)ch <= 0x9Fu || (unsigned char)ch + 32 <= 0x1Fu)) inMBCS = 2; + if (!inMBCS) + { + ch = tolower(ch); // bad ass style but WORKS PERFECTLY! + if (ch == '/') ch = '\\'; + } + else inMBCS--; + } + return hash * -1; +} + +void CryptTh145::convertKey(DWORD* key) +{ + key[0] = key[0] *-1; + key[1] = key[1] *-1; + key[2] = key[2] *-1; + key[3] = key[3] *-1; +} diff --git a/thcrap_tasofro/src/crypt.h b/thcrap_tasofro/src/crypt.h new file mode 100644 index 00000000..187ab203 --- /dev/null +++ b/thcrap_tasofro/src/crypt.h @@ -0,0 +1,50 @@ +/** + * Touhou Community Reliant Automatic Patcher + * Tasogare Frontier support plugin + * + * ---- + * + * Encryption and hashing. + */ + +#pragma once + +#include +#include "thcrap_tasofro.h" + +class ICrypt +{ +public: + virtual ~ICrypt() {} + virtual DWORD cryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key) = 0; + virtual void uncryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key) = 0; + virtual DWORD SpecialFNVHash(const char *begin, const char *end, DWORD initHash = 0x811C9DC5u) = 0; + virtual void convertKey(DWORD *key) = 0; + + static ICrypt *instance; +}; + +class CryptTh135 : public ICrypt +{ +public: + virtual DWORD cryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key); + virtual void uncryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key); + virtual DWORD SpecialFNVHash(const char *begin, const char *end, DWORD initHash = 0x811C9DC5u); + virtual void convertKey(DWORD *key); +}; + +class CryptTh145 : public ICrypt +{ +private: + BYTE *tempCopy; + DWORD tempCopySize; + DWORD cryptBlockInternal(BYTE* Data, DWORD FileSize, DWORD* Key, DWORD Aux); + +public: + CryptTh145(); + ~CryptTh145(); + virtual DWORD cryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key); + virtual void uncryptBlock(BYTE* Data, DWORD FileSize, DWORD* Key); + virtual DWORD SpecialFNVHash(const char *begin, const char *end, DWORD initHash = 0x811C9DC5u); + virtual void convertKey(DWORD *key); +}; diff --git a/thcrap_tasofro/src/crypt_block.cpp b/thcrap_tasofro/src/crypt_block.cpp deleted file mode 100644 index 3743486a..00000000 --- a/thcrap_tasofro/src/crypt_block.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Touhou Community Reliant Automatic Patcher - * Tasogare Frontier support plugin - * - * ---- - * - * XOR-based encryption. - */ - -#include -#include "thcrap_tasofro.h" - -void uncrypt_block(BYTE* Data, DWORD FileSize, DWORD* Key) -{ - BYTE *key = (BYTE*)Key; - BYTE aux[4]; - DWORD i; - - for (i = 0; i < 4; i++) { - aux[i] = key[i]; - } - - for (i = 0; i < FileSize; i++) { - BYTE tmp = Data[i]; - Data[i] = Data[i] ^ key[i % 16] ^ aux[i % 4]; - aux[i % 4] = tmp; - } -} - -DWORD crypt_block_internal(BYTE* Data, DWORD FileSize, DWORD* Key, DWORD Aux) -{ - BYTE *key = (BYTE*)Key; - BYTE *aux = (BYTE*)&Aux; - int i; - - for (i = FileSize - 1; i >= 0; i--) { - BYTE unencByte = Data[i]; - BYTE encByte = aux[i % 4]; - Data[i] = aux[i % 4]; - aux[i % 4] = unencByte ^ encByte ^ key[i % 16]; - } - return Aux; -} - -DWORD crypt_block(BYTE* Data, DWORD FileSize, DWORD* Key) -{ - DWORD Aux; - VLA(BYTE, tempCopy, FileSize); - memcpy(tempCopy, Data, FileSize); - Aux = crypt_block_internal(tempCopy, FileSize, Key, Key[0]); // This call seems to give the correct Aux value. - VLA_FREE(tempCopy); - - return crypt_block_internal(Data, FileSize, Key, Aux); -} diff --git a/thcrap_tasofro/src/files_list.cpp b/thcrap_tasofro/src/files_list.cpp index efaee1c3..348498c5 100644 --- a/thcrap_tasofro/src/files_list.cpp +++ b/thcrap_tasofro/src/files_list.cpp @@ -10,27 +10,7 @@ #include #include #include "thcrap_tasofro.h" - -// Normalized Hash -DWORD SpecialFNVHash(const char *begin, const char *end, DWORD initHash = 0x811C9DC5u) -{ - DWORD hash; // eax@1 - DWORD ch; // esi@2 - - int inMBCS = 0; - for (hash = initHash; begin != end; hash = (hash ^ ch) * 0x1000193) - { - ch = *begin++; - if (!inMBCS && ((unsigned char)ch >= 0x81u && (unsigned char)ch <= 0x9Fu || (unsigned char)ch + 32 <= 0x1Fu)) inMBCS = 2; - if (!inMBCS) - { - ch = tolower(ch); // bad ass style but WORKS PERFECTLY! - if (ch == '/') ch = '\\'; - } - else inMBCS--; - } - return hash * -1; -} +#include "crypt.h" std::unordered_map fileHashToName; int LoadFileNameList(const char* FileName) @@ -42,7 +22,7 @@ int LoadFileNameList(const char* FileName) { int tlen = strlen(FilePath); while (tlen && FilePath[tlen - 1] == '\n') FilePath[--tlen] = 0; - DWORD thash = SpecialFNVHash(FilePath, FilePath + tlen); + DWORD thash = ICrypt::instance->SpecialFNVHash(FilePath, FilePath + tlen); strcpy(fileHashToName[thash].path, FilePath); } fclose(fp); @@ -57,7 +37,7 @@ int LoadFileNameListFromMemory(char* list, size_t size) while (len < size && list[len] != '\r' && list[len] != '\n') { len++; } - DWORD thash = SpecialFNVHash(list, list + len); + DWORD thash = ICrypt::instance->SpecialFNVHash(list, list + len); strncpy(fileHashToName[thash].path, list, len); list += len; size -= len; @@ -71,7 +51,7 @@ int LoadFileNameListFromMemory(char* list, size_t size) DWORD filename_to_hash(const char* filename) { - return SpecialFNVHash(filename, filename + strlen(filename)); + return ICrypt::instance->SpecialFNVHash(filename, filename + strlen(filename)); } struct FileHeaderFull* register_file_header(FileHeader* header, DWORD *key) @@ -83,10 +63,11 @@ struct FileHeaderFull* register_file_header(FileHeader* header, DWORD *key) full_header.offset = header->offset; full_header.size = header->size; - full_header.key[0] = key[0] * -1; - full_header.key[1] = key[1] * -1; - full_header.key[2] = key[2] * -1; - full_header.key[3] = key[3] * -1; + full_header.key[0] = key[0]; + full_header.key[1] = key[1]; + full_header.key[2] = key[2]; + full_header.key[3] = key[3]; + ICrypt::instance->convertKey(full_header.key); full_header.effective_offset = -1; full_header.orig_size = header->size; diff --git a/thcrap_tasofro/src/thcrap_tasofro.cpp b/thcrap_tasofro/src/thcrap_tasofro.cpp index b7ef5ad8..101ba543 100644 --- a/thcrap_tasofro/src/thcrap_tasofro.cpp +++ b/thcrap_tasofro/src/thcrap_tasofro.cpp @@ -15,6 +15,7 @@ #include "act-nut.h" #include "spellcards_generator.h" #include "plugin.h" +#include "crypt.h" // TODO: read the file names list in JSON format int __stdcall thcrap_plugin_init() @@ -39,6 +40,19 @@ int __stdcall thcrap_plugin_init() return 1; } } + + const char *crypt = json_object_get_string(runconfig_get(), "crypt"); + if (strcmp(crypt, "th135") == 0) { + ICrypt::instance = new CryptTh135(); + } + else if (strcmp(crypt, "th145") == 0) { + ICrypt::instance = new CryptTh145(); + } + else { + log_printf("thcrap_tasofro: unknown value for crypt in runconfig: \"%s\" (known values: th135, th145).\n" + "Defaulting to th145.\n", crypt); + ICrypt::instance = new CryptTh145(); + } patchhook_register("*/stage*.pl", patch_pl); patchhook_register("*/ed_*.pl", patch_pl); @@ -63,13 +77,25 @@ int BP_file_header(x86_reg_t *regs, json_t *bp_info) // Parameters // ---------- BYTE **pHeader = (BYTE**)json_object_get_register(bp_info, regs, "struct"); - DWORD **key = (DWORD**)json_object_get_register(bp_info, regs, "key"); + DWORD **pKey = (DWORD**)json_object_get_register(bp_info, regs, "key"); + DWORD key_offset = (DWORD)json_integer_value(json_object_get(bp_info, "key_offset")); // ---------- - if (!pHeader || !*pHeader || !key || !*key) + + DWORD *key; + if (!pHeader || !*pHeader) return 1; + if (pKey && *pKey) { + key = *pKey; + } + else if (key_offset) { + key = (DWORD*)(*pHeader + key_offset); + } + else { + return 1; + } struct FileHeader *header = (struct FileHeader*)(*pHeader + json_integer_value(json_object_get(bp_info, "struct_offset"))); - struct FileHeaderFull *full_header = register_file_header(header, *key); + struct FileHeaderFull *full_header = register_file_header(header, key); file_rep_t *fr = &full_header->fr; if (full_header->path[0]) { @@ -95,7 +121,7 @@ int BP_file_header(x86_reg_t *regs, json_t *bp_info) fr->hooks, fr->game_buffer, fr->pre_json_size + fr->patch_size, fr->pre_json_size, fr->patch ); - crypt_block((BYTE*)fr->game_buffer, full_header->size, full_header->key); + ICrypt::instance->cryptBlock((BYTE*)fr->game_buffer, full_header->size, full_header->key); } if (fr->rep_buffer == NULL && fr->patch != NULL) { @@ -143,6 +169,9 @@ int BP_replace_file(x86_reg_t *regs, json_t *bp_info) BYTE **pBuffer = (BYTE**)reg(regs, json_string_value(jBuffer)); DWORD *pSize = (DWORD*)reg(regs, json_string_value(jSize)); DWORD **ppNumberOfBytesRead = (DWORD**)json_object_get_register(bp_info, regs, "pNumberOfBytesRead"); + DWORD hFile_offset = (DWORD)json_integer_value(json_object_get(bp_info, "hFile_offset")); + DWORD hash_offset = (DWORD)json_integer_value(json_object_get(bp_info, "hash_offset")); + DWORD buffer_offset = (DWORD)json_integer_value(json_object_get(bp_info, "buffer_offset")); // ---------- static DWORD size = 0; @@ -169,16 +198,16 @@ int BP_replace_file(x86_reg_t *regs, json_t *bp_info) return 1; } - struct FileHeaderFull *header = hash_to_file_header(*(DWORD*)(*file_struct + 0x1001c)); + struct FileHeaderFull *header = hash_to_file_header(*(DWORD*)(*file_struct + hash_offset)); if (!header || !header->path[0] || (!header->fr.game_buffer && !header->fr.patch)) { // Nothing to patch. return 1; } - HANDLE hFile = *(HANDLE*)(*file_struct + 4); + HANDLE hFile = *(HANDLE*)(*file_struct + hFile_offset); DWORD numberOfBytesRead; if (buffer == NULL) { - buffer = *file_struct + 8; + buffer = *file_struct + buffer_offset; } if (size == 0) { size = 65536; @@ -201,11 +230,11 @@ int BP_replace_file(x86_reg_t *regs, json_t *bp_info) header->fr.game_buffer = malloc(header->size); ReadFile(hFile, header->fr.game_buffer, header->orig_size, &nbOfBytesRead, NULL); - uncrypt_block((BYTE*)header->fr.game_buffer, header->orig_size, header->key); + ICrypt::instance->uncryptBlock((BYTE*)header->fr.game_buffer, header->orig_size, header->key); patchhooks_run( header->fr.hooks, header->fr.game_buffer, header->size, header->orig_size, header->fr.patch ); - crypt_block((BYTE*)header->fr.game_buffer, header->size, header->key); + ICrypt::instance->cryptBlock((BYTE*)header->fr.game_buffer, header->size, header->key); SetFilePointer(hFile, header->effective_offset + numberOfBytesRead, NULL, FILE_BEGIN); file_rep_clear(&header->fr); diff --git a/thcrap_tasofro/src/thcrap_tasofro.h b/thcrap_tasofro/src/thcrap_tasofro.h index 2fae906f..67618ee3 100644 --- a/thcrap_tasofro/src/thcrap_tasofro.h +++ b/thcrap_tasofro/src/thcrap_tasofro.h @@ -54,8 +54,6 @@ int LoadFileNameListFromMemory(char* list, size_t size); DWORD filename_to_hash(const char* filename); struct FileHeaderFull* register_file_header(struct FileHeader* header, DWORD *key); struct FileHeaderFull* hash_to_file_header(DWORD hash); -void uncrypt_block(BYTE* Data, DWORD FileSize, DWORD* Key); -DWORD crypt_block(BYTE* Data, DWORD FileSize, DWORD* Key); int patch_plaintext(void *file_inout, size_t size_out, size_t size_in, json_t *patch); diff --git a/thcrap_tasofro/thcrap_tasofro.vcxproj b/thcrap_tasofro/thcrap_tasofro.vcxproj index 694c3a05..4d613535 100644 --- a/thcrap_tasofro/thcrap_tasofro.vcxproj +++ b/thcrap_tasofro/thcrap_tasofro.vcxproj @@ -34,7 +34,7 @@ - + @@ -46,6 +46,7 @@ +