diff --git a/game/Rayman3.exe b/game/Rayman3.exe index b675d71d..a5b0455b 100644 Binary files a/game/Rayman3.exe and b/game/Rayman3.exe differ diff --git a/patcher/patcher.cpp b/patcher/patcher.cpp index 8f1726e0..c916ca42 100644 --- a/patcher/patcher.cpp +++ b/patcher/patcher.cpp @@ -3,6 +3,380 @@ #include #include +struct Patcher { + COFFI::coffi objReader; + COFFI::coffi peReader; + std::string inputFile; + std::string outputFile; + COFFI::symbol *mainSymbol = nullptr; + COFFI::section *mainSection = nullptr; + COFFI::section *textSection = nullptr; + COFFI::section *rdataSection = nullptr; + uint64_t imageBase = 0; + uint32_t mainOffset = 0; + uint32_t mainSize = 0; + + bool loadFiles() { + std::string objPath = SRC_OBJECT; + SPDLOG_INFO("Loading object file: {}", objPath); + if (!objReader.load(objPath)) { + spdlog::error("Failed to load object file: {}", objPath); + return false; + } + + SPDLOG_INFO("Loading PE file: {}", inputFile); + if (!peReader.load(inputFile)) { + spdlog::error("Failed to load PE file: {}", inputFile); + return false; + } + + // Get image base + auto winHeader = peReader.get_win_header(); + if (winHeader) { + imageBase = winHeader->get_image_base(); + } + spdlog::info("PE Image base: 0x{:x}", imageBase); + + return true; + } + + bool findMainSymbol() { + auto &symbols = *objReader.get_symbols(); + for (auto &sym : symbols) { + SPDLOG_INFO("Symbol: {}", sym.get_name()); + if (sym.get_name() == "_ref") { + mainSymbol = &sym; + break; + } + } + + if (!mainSymbol) { + spdlog::error("Could not find 'main' symbol in object file"); + return false; + } + + // Get the section containing the main function + auto §ions = objReader.get_sections(); + mainSection = sections[mainSymbol->get_section_number() - 1]; + mainOffset = mainSymbol->get_value(); + + return true; + } + + uint32_t calculateMainSize() { + auto &symbols = *objReader.get_symbols(); + uint32_t nextSymbolOffset = UINT32_MAX; + + // Find the next symbol in the same section to calculate size + for (auto &sym : symbols) { + if (sym.get_section_number() == mainSymbol->get_section_number() && + sym.get_value() > mainOffset && sym.get_value() < nextSymbolOffset) { + nextSymbolOffset = sym.get_value(); + } + } + + if (nextSymbolOffset != UINT32_MAX) { + mainSize = nextSymbolOffset - mainOffset; + spdlog::info( + "Calculated main function size: {} bytes (next symbol at offset {})", + mainSize, nextSymbolOffset); + } else { + // If no next symbol found, use remaining section size + mainSize = mainSection->get_data_size() - mainOffset; + spdlog::info( + "No next symbol found, using remaining section size: {} bytes", + mainSize); + } + + return mainSize; + } + + COFFI::section *findSection(const std::string &name, bool isPE = true) { + auto §ions = isPE ? peReader.get_sections() : objReader.get_sections(); + for (auto §ion : sections) { + if (section->get_name() == name) { + return section; + } + } + return nullptr; + } + + uint32_t findAvailableSpaceAtEnd(COFFI::section *section) { + auto sectionData = section->get_data(); + uint32_t sectionSize = section->get_data_size(); + const char *sectionEnd = sectionData + sectionSize; + uint32_t availableSpace = 0; + + // Search backwards from the end to find contiguous null bytes + const char *ptr = sectionEnd-1; + while (ptr > sectionData) { + if (*ptr != 0x00) { + break; + } + availableSpace++; + ptr--; + } + // } + // for (int32_t i = sectionSize - 1; i >= 0; i--) { + // if (reinterpret_cast(sectionData)[i] == 0x00) { + // availableSpace++; + // } else { + // break; // Found non-null byte, stop counting + // } + // } + + return availableSpace; + } + + void logMainFunctionCode() { + auto mainCodeData = mainSection->get_data(); + spdlog::info("Found main function at offset {} with size {}", mainOffset, + mainSize); + spdlog::info("Main function code:"); + + std::string s; + for (uint32_t i = 0; i < mainSize; i++) { + if (i > 0 && i % 16 == 0) { + spdlog::info("{}", s); + s.clear(); + } + if (s.size() > 0) + s += " "; + s += fmt::format("{:02X}", mainCodeData[i]); + } + if (s.size() > 0) + spdlog::info("{}", s); + } + + void logRelocations() { + auto §ionRelocations = mainSection->get_relocations(); + spdlog::info("Relocations for section {} (containing main function):", + mainSymbol->get_section_number()); + for (auto &reloc : sectionRelocations) { + spdlog::info(" Relocation at offset 0x{:x}, type: {}, symbol index: {}", + reloc.get_virtual_address(), reloc.get_type(), + reloc.get_symbol_table_index()); + spdlog::info(" -> Symbol: {}", reloc.get_symbol()); + } + } + + void logSectionData(COFFI::section *section) { + uint32_t sectionRVA = section->get_virtual_address(); + uint32_t sectionSize = section->get_virtual_size(); + uint64_t sectionVA = imageBase + sectionRVA; + uint64_t sectionEndVA = sectionVA + sectionSize; + + spdlog::info("Section {} RVA: 0x{:x}, size: 0x{:x}", section->get_name(), + sectionRVA, sectionSize); + spdlog::info("Section {} VA: 0x{:x} - 0x{:x}", section->get_name(), + sectionVA, sectionEndVA); + } + + bool validateSections() { + textSection = findSection(".text"); + if (!textSection) { + spdlog::error("Could not find .text section in PE file"); + return false; + } + logSectionData(textSection); + + rdataSection = findSection(".rdata"); + if (!rdataSection) { + spdlog::error("Could not find .rdata section in PE file"); + return false; + } + logSectionData(rdataSection); + return true; + } + + bool processRelocations(std::vector &newTextData, + std::vector &newRdataData, + uint32_t injectionOffset, + uint32_t &rdataCurrentOffset) { + auto &symbols = *objReader.get_symbols(); + auto §ions = objReader.get_sections(); + auto §ionRelocations = mainSection->get_relocations(); + uint32_t rdataSize = rdataSection->get_data_size(); + uint32_t rdataRVA = rdataSection->get_virtual_address(); + + spdlog::info("Processing relocations..."); + + for (auto &reloc : sectionRelocations) { + if (reloc.get_type() != 6) + continue; // Only handle type 6 (IMAGE_REL_I386_DIR32) + + uint32_t relocOffset = reloc.get_virtual_address(); + std::string symbolName = reloc.get_symbol(); + uint32_t resolvedAddress = 0; + + spdlog::info("Processing relocation at offset 0x{:x} for symbol: {}", + relocOffset, symbolName); + + if (symbolName.starts_with("__imp_")) { + // Import symbol handling + std::string functionName = symbolName.substr(6); + size_t atPos = functionName.find('@'); + if (atPos != std::string::npos) { + functionName = functionName.substr(0, atPos); + } + + spdlog::info("Looking for import function: {}", functionName); + spdlog::warn("Import resolution not fully implemented yet for: {}", + functionName); + resolvedAddress = 0x12345678; // Placeholder + + } else if (symbolName.starts_with("??_C@")) { + // String constant handling + COFFI::symbol *constSymbol = nullptr; + for (auto &sym : symbols) { + if (sym.get_name() == symbolName) { + constSymbol = &sym; + break; + } + } + + if (constSymbol) { + auto constSection = sections[constSymbol->get_section_number() - 1]; + auto constData = constSection->get_data(); + uint32_t constOffset = constSymbol->get_value(); + + // Find string length + uint32_t stringLen = 0; + while (constData[constOffset + stringLen] != 0) { + stringLen++; + } + stringLen++; // Include null terminator + + if (rdataCurrentOffset + stringLen > rdataSize) { + spdlog::error("Not enough space in .rdata for string constant"); + return false; + } + + // Copy string to .rdata + std::memcpy(newRdataData.data() + rdataCurrentOffset, + constData + constOffset, stringLen); + + resolvedAddress = imageBase + rdataRVA + rdataCurrentOffset; + + spdlog::info("Copied string constant '{}' to .rdata at RVA 0x{:x} " + "(VA: 0x{:x})", + std::string(constData + constOffset), + rdataRVA + rdataCurrentOffset, resolvedAddress); + + rdataCurrentOffset += stringLen; + } + } + + // Apply the relocation + if (resolvedAddress != 0) { + uint32_t patchOffset = injectionOffset + relocOffset; + *reinterpret_cast(newTextData.data() + patchOffset) = + resolvedAddress; + spdlog::info( + "Applied relocation: patched offset 0x{:x} with address 0x{:x}", + patchOffset, resolvedAddress); + } + } + + return true; + } + + bool patchAndSave() { + // Check available space in both sections + uint32_t textAvailableSpace = findAvailableSpaceAtEnd(textSection); + uint32_t rdataAvailableSpace = findAvailableSpaceAtEnd(rdataSection); + + spdlog::info("Found {} bytes of available space (null bytes) at end of " + ".text section", + textAvailableSpace); + spdlog::info("Found {} bytes of available space in .rdata section", + rdataAvailableSpace); + + if (textAvailableSpace < mainSize) { + spdlog::error( + "Not enough space in .text section! Need {} bytes, found {} bytes", + mainSize, textAvailableSpace); + return false; + } + + // Calculate injection points + uint32_t textSize = textSection->get_data_size(); + uint32_t rdataSize = rdataSection->get_data_size(); + uint32_t injectionOffset = textSize - textAvailableSpace; + uint32_t rdataInjectionOffset = rdataSize - rdataAvailableSpace; + uint32_t rdataCurrentOffset = rdataInjectionOffset; + + uint64_t textSectionVA = imageBase + textSection->get_virtual_address(); + uint64_t injectionVA = textSectionVA + injectionOffset; + + spdlog::info( + "Injecting {} bytes at .text section offset 0x{:x} (VA: 0x{:x})", + mainSize, injectionOffset, injectionVA); + + // Create copies of section data + auto textSectionData = textSection->get_data(); + auto rdataData = rdataSection->get_data(); + + std::vector newTextData( + reinterpret_cast(textSectionData), + reinterpret_cast(textSectionData) + textSize); + std::vector newRdataData( + reinterpret_cast(rdataData), + reinterpret_cast(rdataData) + rdataSize); + + // Copy main function code + const uint8_t *mainCode = + reinterpret_cast(mainSection->get_data()) + mainOffset; + std::memcpy(newTextData.data() + injectionOffset, mainCode, mainSize); + + // Process relocations + if (!processRelocations(newTextData, newRdataData, injectionOffset, + rdataCurrentOffset)) { + return false; + } + + // Update sections + rdataSection->set_data(reinterpret_cast(newRdataData.data()), + newRdataData.size()); + textSection->set_data(reinterpret_cast(newTextData.data()), + newTextData.size()); + + spdlog::info("Injected code into existing .text section space (size " + "unchanged: 0x{:x})", + textSize); + + // Save the modified PE file + spdlog::info("Saving patched PE file to: {}", outputFile); + if (!peReader.save(outputFile)) { + spdlog::error("Failed to save patched PE file to: {}", outputFile); + return false; + } + + spdlog::info( + "Successfully patched PE file! Main function injected at VA: 0x{:x}", + injectionVA); + return true; + } + + bool run() { + if (!loadFiles()) + return false; + if (!findMainSymbol()) + return false; + + calculateMainSize(); + logMainFunctionCode(); + logRelocations(); + + if (!validateSections()) + return false; + if (!patchAndSave()) + return false; + + return true; + } +}; + int main(int argc, char *argv[]) { CLI::App app("Patcher"); std::string inputFile; @@ -14,311 +388,9 @@ int main(int argc, char *argv[]) { CLI11_PARSE(app, argc, argv); - // Load the object file containing the main function - COFFI::coffi objReader; - std::string objPath = SRC_OBJECT; - SPDLOG_INFO("Loading object file: {}", objPath); - if (!objReader.load(objPath)) { - spdlog::error("Failed to load object file: {}", objPath); - return 1; - } + Patcher patcher; + patcher.inputFile = inputFile; + patcher.outputFile = outputFile; - // Load the source PE file - COFFI::coffi peReader; - SPDLOG_INFO("Loading PE file: {}", inputFile); - if (!peReader.load(inputFile)) { - spdlog::error("Failed to load PE file: {}", inputFile); - return 1; - } - - // Find the 'main' function in the object file - auto &symbols = *objReader.get_symbols(); - COFFI::symbol *mainSymbol = nullptr; - for (auto &sym : symbols) { - SPDLOG_INFO("Symbol: {}", sym.get_name()); - if (sym.get_name() == "_ref") { - mainSymbol = &sym; - break; - } - } - - if (!mainSymbol) { - spdlog::error("Could not find 'main' symbol in object file"); - return 1; - } - - // Get the section containing the main function - auto §ions = objReader.get_sections(); - auto mainSection = sections[mainSymbol->get_section_number() - 1]; - - // Calculate main function size using next symbol method - uint32_t mainOffset = mainSymbol->get_value(); - uint32_t mainSize = 0; - - // Find the next symbol in the same section to calculate size - uint32_t nextSymbolOffset = UINT32_MAX; - for (auto &sym : symbols) { - if (sym.get_section_number() == mainSymbol->get_section_number() && - sym.get_value() > mainOffset && sym.get_value() < nextSymbolOffset) { - nextSymbolOffset = sym.get_value(); - } - } - - if (nextSymbolOffset != UINT32_MAX) { - mainSize = nextSymbolOffset - mainOffset; - spdlog::info( - "Calculated main function size: {} bytes (next symbol at offset {})", - mainSize, nextSymbolOffset); - } else { - // If no next symbol found, use remaining section size - mainSize = mainSection->get_data_size() - mainOffset; - spdlog::info("No next symbol found, using remaining section size: {} bytes", - mainSize); - } - - auto mainCodeData = mainSection->get_data(); - - spdlog::info("Found main function at offset {} with size {}", mainOffset, - mainSize); - - // List relocations for the main function section - auto& sectionRelocations = mainSection->get_relocations(); - spdlog::info("Relocations for section {} (containing main function):", mainSymbol->get_section_number()); - for (auto& reloc : sectionRelocations) { - spdlog::info(" Relocation at offset 0x{:x}, type: {}, symbol index: {}", - reloc.get_virtual_address(), - reloc.get_type(), - reloc.get_symbol_table_index()); - - // Get symbol name for this relocation - spdlog::info(" -> Symbol: {}", reloc.get_symbol()); - } - - spdlog::info("Main function code:"); - std::string s; - for (uint32_t i = 0; i < mainSize; i++) { - if (i > 0 && i % 16 == 0) { - spdlog::info("{}", s); - s.clear(); - } - if (s.size() > 0) - s += " "; - s += fmt::format("{:02X}", mainCodeData[i]); - } - if (s.size() > 0) - spdlog::info("{}", s); - - // Find .text section in PE file - auto &peSections = peReader.get_sections(); - COFFI::section *textSection = nullptr; - for (auto §ion : peSections) { - if (section->get_name() == ".text") { - textSection = section; - break; - } - } - - if (!textSection) { - spdlog::error("Could not find .text section in PE file"); - return 1; - } - - // Get image base and calculate actual VA - uint64_t imageBase = 0; - auto winHeader = peReader.get_win_header(); - if (winHeader) { - imageBase = winHeader->get_image_base(); - } - - uint32_t textSectionRVA = textSection->get_virtual_address(); - uint32_t textSectionSize = textSection->get_virtual_size(); - uint64_t textSectionVA = imageBase + textSectionRVA; - uint64_t textSectionEndVA = textSectionVA + textSectionSize; - - spdlog::info("PE Image base: 0x{:x}", imageBase); - spdlog::info(".text section RVA: 0x{:x}, size: 0x{:x}", textSectionRVA, textSectionSize); - spdlog::info(".text section VA: 0x{:x} - 0x{:x}", textSectionVA, textSectionEndVA); - - // Find available space at the end of .text section (look for null bytes) - auto textSectionData = textSection->get_data(); - uint32_t originalTextSize = textSection->get_data_size(); - - // Search backwards from the end to find contiguous null bytes - uint32_t availableSpace = 0; - for (int32_t i = originalTextSize - 1; i >= 0; i--) { - if (reinterpret_cast(textSectionData)[i] == 0x00) { - availableSpace++; - } else { - break; // Found non-null byte, stop counting - } - } - - spdlog::info("Found {} bytes of available space (null bytes) at end of .text section", availableSpace); - - if (availableSpace < mainSize) { - spdlog::error("Not enough space in .text section! Need {} bytes, found {} bytes", - mainSize, availableSpace); - return 1; - } - - // Calculate injection offset (place code at start of null space) - uint32_t injectionOffset = originalTextSize - availableSpace; - uint64_t injectionVA = textSectionVA + injectionOffset; - - spdlog::info("Injecting {} bytes at .text section offset 0x{:x} (VA: 0x{:x})", - mainSize, injectionOffset, injectionVA); - - // Copy the main function code into the available space - const uint8_t* mainCode = reinterpret_cast(mainCodeData) + mainOffset; - - // Create a copy of the section data to modify - std::vector newTextData(reinterpret_cast(textSectionData), - reinterpret_cast(textSectionData) + originalTextSize); - - // Copy our code into the null space - std::memcpy(newTextData.data() + injectionOffset, mainCode, mainSize); - - // Now handle relocations - resolve imports and copy constants to .rdata - spdlog::info("Processing relocations..."); - - // Find .rdata section for constants - COFFI::section* rdataSection = nullptr; - for (auto& section : peSections) { - if (section->get_name() == ".rdata") { - rdataSection = section; - break; - } - } - - if (!rdataSection) { - spdlog::error("Could not find .rdata section in PE file"); - return 1; - } - - // Find available space in .rdata section (similar to .text) - auto rdataData = rdataSection->get_data(); - uint32_t rdataSize = rdataSection->get_data_size(); - uint32_t rdataAvailableSpace = 0; - - for (int32_t i = rdataSize - 1; i >= 0; i--) { - if (reinterpret_cast(rdataData)[i] == 0x00) { - rdataAvailableSpace++; - } else { - break; - } - } - - spdlog::info("Found {} bytes of available space in .rdata section", rdataAvailableSpace); - - // Create rdata copy for modifications - std::vector newRdataData(reinterpret_cast(rdataData), - reinterpret_cast(rdataData) + rdataSize); - uint32_t rdataInjectionOffset = rdataSize - rdataAvailableSpace; - uint32_t rdataCurrentOffset = rdataInjectionOffset; - - // Get import table information for resolving __imp_ symbols - auto& directories = peReader.get_directories(); - uint32_t importRVA = 0; - if (directories.size() > 1) { - importRVA = directories[1]->get_virtual_address(); // Import table is directory entry 1 - } - - // Process each relocation - for (auto& reloc : sectionRelocations) { - if (reloc.get_type() != 6) continue; // Only handle type 6 (IMAGE_REL_I386_DIR32) - - uint32_t relocOffset = reloc.get_virtual_address(); - std::string symbolName = reloc.get_symbol(); - uint32_t resolvedAddress = 0; - - spdlog::info("Processing relocation at offset 0x{:x} for symbol: {}", relocOffset, symbolName); - - if (symbolName.starts_with("__imp_")) { - // This is an import symbol - find it in the PE's import table - std::string functionName = symbolName.substr(6); // Remove "__imp_" prefix - - // Remove @N suffix for stdcall functions - size_t atPos = functionName.find('@'); - if (atPos != std::string::npos) { - functionName = functionName.substr(0, atPos); - } - - spdlog::info("Looking for import function: {}", functionName); - - // For now, we'll need to implement import table parsing - // This is a placeholder - you'd need to parse the import table to find the actual IAT address - spdlog::warn("Import resolution not fully implemented yet for: {}", functionName); - resolvedAddress = 0x12345678; // Placeholder - - } else if (symbolName.starts_with("??_C@")) { - // This is a string constant - find it in the object file and copy to .rdata - COFFI::symbol* constSymbol = nullptr; - for (auto& sym : symbols) { - if (sym.get_name() == symbolName) { - constSymbol = &sym; - break; - } - } - - if (constSymbol) { - // Get the string data from the object file - auto constSection = sections[constSymbol->get_section_number() - 1]; - auto constData = constSection->get_data(); - uint32_t constOffset = constSymbol->get_value(); - - // Find the string length (null-terminated) - uint32_t stringLen = 0; - while (constData[constOffset + stringLen] != 0) { - stringLen++; - } - stringLen++; // Include null terminator - - if (rdataCurrentOffset + stringLen > rdataSize) { - spdlog::error("Not enough space in .rdata for string constant"); - return 1; - } - - // Copy string to .rdata - std::memcpy(newRdataData.data() + rdataCurrentOffset, - constData + constOffset, stringLen); - - // Calculate the new address - uint32_t rdataRVA = rdataSection->get_virtual_address(); - resolvedAddress = imageBase + rdataRVA + rdataCurrentOffset; - - spdlog::info("Copied string constant '{}' to .rdata at RVA 0x{:x} (VA: 0x{:x})", - std::string(constData + constOffset), - rdataRVA + rdataCurrentOffset, resolvedAddress); - - rdataCurrentOffset += stringLen; - } - } - - // Apply the relocation to the injected code - if (resolvedAddress != 0) { - uint32_t patchOffset = injectionOffset + relocOffset; - *reinterpret_cast(newTextData.data() + patchOffset) = resolvedAddress; - spdlog::info("Applied relocation: patched offset 0x{:x} with address 0x{:x}", - patchOffset, resolvedAddress); - } - } - - // Update both sections - rdataSection->set_data(reinterpret_cast(newRdataData.data()), newRdataData.size()); - - // Update the .text section with modified data (same size) - textSection->set_data(reinterpret_cast(newTextData.data()), newTextData.size()); - - spdlog::info("Injected code into existing .text section space (size unchanged: 0x{:x})", originalTextSize); - - // Save the modified PE file to output path - spdlog::info("Saving patched PE file to: {}", outputFile); - if (!peReader.save(outputFile)) { - spdlog::error("Failed to save patched PE file to: {}", outputFile); - return 1; - } - - spdlog::info("Successfully patched PE file! Main function injected at VA: 0x{:x}", injectionVA); - - return 0; + return patcher.run() ? 0 : 1; } \ No newline at end of file