From 04e716178b397d11b7eb52be4cd27b0e99cd559d Mon Sep 17 00:00:00 2001 From: "Skyth (Asilkan)" <19259897+blueskythlikesclouds@users.noreply.github.com> Date: Thu, 27 Feb 2025 00:11:11 +0300 Subject: [PATCH] Repository cleanup & README.md. (#5) --- CMakeLists.txt | 3 +- LICENSE.md | 21 +++ README.md | 258 ++++++++++++++++++++++++++ XenonAnalyse/main.cpp | 179 ++---------------- XenonRecomp/main.cpp | 10 +- XenonRecomp/recompiler.cpp | 6 +- XenonRecomp/test_recompiler.cpp | 14 +- XenonSample/.gitignore | 1 - XenonSample/CMakeLists.txt | 14 -- XenonSample/main.cpp | 0 XenonTests/CMakeLists.txt | 25 ++- tests/XenonAnalyse/add-cond.cpp | 10 - tests/XenonAnalyse/add-cond.elf | Bin 1472 -> 0 bytes tests/XenonAnalyse/add.cpp | 9 - tests/XenonAnalyse/add.elf | Bin 788 -> 0 bytes tests/XenonAnalyse/cond-fall.cpp | 15 -- tests/XenonAnalyse/cond-fall.elf | Bin 820 -> 0 bytes tests/XenonAnalyse/cond.cpp | 18 -- tests/XenonAnalyse/cond.elf | Bin 844 -> 0 bytes tests/XenonAnalyse/loop.cpp | 15 -- tests/XenonAnalyse/loop.elf | Bin 1496 -> 0 bytes tests/XenonAnalyse/private/.gitignore | 2 - tests/compile-all.bat | 5 - tests/compile.bat | 3 - 24 files changed, 340 insertions(+), 268 deletions(-) create mode 100644 LICENSE.md create mode 100644 README.md delete mode 100644 XenonSample/.gitignore delete mode 100644 XenonSample/CMakeLists.txt delete mode 100644 XenonSample/main.cpp delete mode 100644 tests/XenonAnalyse/add-cond.cpp delete mode 100644 tests/XenonAnalyse/add-cond.elf delete mode 100644 tests/XenonAnalyse/add.cpp delete mode 100644 tests/XenonAnalyse/add.elf delete mode 100644 tests/XenonAnalyse/cond-fall.cpp delete mode 100644 tests/XenonAnalyse/cond-fall.elf delete mode 100644 tests/XenonAnalyse/cond.cpp delete mode 100644 tests/XenonAnalyse/cond.elf delete mode 100644 tests/XenonAnalyse/loop.cpp delete mode 100644 tests/XenonAnalyse/loop.elf delete mode 100644 tests/XenonAnalyse/private/.gitignore delete mode 100644 tests/compile-all.bat delete mode 100644 tests/compile.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index e77cba7..b89407f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,7 @@ add_subdirectory(${XENONANALYSE_ROOT}) add_subdirectory(${XENONRECOMP_ROOT}) add_subdirectory(${XENONUTILS_ROOT}) -# Only build sample and tests if this is the top level project +# Only tests if this is the top level project if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) - add_subdirectory(XenonSample) add_subdirectory(XenonTests) endif() diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..589ce8e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2025 hedge-dev and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +- The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1621434 --- /dev/null +++ b/README.md @@ -0,0 +1,258 @@ +# XenonRecomp + +XenonRecomp is a tool that converts Xbox 360 executables into C++ code, which can then be recompiled for any platform. Currently, it only supports x86 platforms due to the use of x86 intrinsics. + +This project was heavily inspired by [N64: Recompiled](https://github.com/N64Recomp/N64Recomp), a similar tool for N64 executables. + +## Implementation Details + +### Instructions + +The instructions are directly converted without any effort to make them resemble decompiled code, meaning the output is not very human-readable. The CPU state is passed as an argument to every PPC function, which includes definitions for every PPC register and their current values at the time of execution. The second argument is the base address pointer, as the Xbox 360 CPU uses 32-bit pointers. + +A good amount of PPC instructions are implemented, with missing ones primarily being variants of already implemented instructions. Some instructions, like the D3D unpack/pack instructions, do not support all operand types. When a missing case is encountered, a warning is generated, or a debug break is inserted into the converted C++ code. + +The instruction implementations operate on little-endian values. However, since the Xbox 360 is a big-endian machine, the memory load instructions swap endianness when reading values, and memory store instructions reverse it to big-endian before writing. All the memory loads and stores are marked volatile to prevent Clang from doing unsafe code reordering. + +Vector registers' endianness handling is more complicated. Instead of swapping individual 32-bit elements, the recompiler chooses to reverse the entire 16-byte vector. Instructions must account for this reversed order, such as using the WZY components instead of XYZ in dot products or requiring reversed arguments for vector pack instructions. + +The FPU expects denormalized numbers to remain unmodified, while VMX instructions always flush them. This is managed by storing the current floating-point state in the CPU state struct and enabling or disabling denormal flushing as necessary before executing each instruction. + +Most VMX instructions are implemented using x86 intrinsics. Luckily, the number of AVX intrinsics used is relatively low, so adding support for other architectures using libraries like [SIMD Everywhere](https://github.com/simd-everywhere/simde) might be possible. + +### MMIO + +MMIO, which is typically used for hardware operations such as XMA decoding, is currently unimplemented. There is an unfinished attempt to implement MMIO, but supporting it may be non-trivial and could require advanced analysis of instructions. + +### Indirect Functions + +Virtual function calls are resolved by creating a "perfect hash table" at runtime, where dereferencing a 64-bit pointer (using the original instruction address multiplied by 2) gives the address of the recompiled function. This was previously implemented by creating an 8 GB virtual allocation, but it had too much memory pressure. Now it relies on function addresses being placed after the valid XEX memory region in the base memory pointer. These regions are exported as macros in the output `ppc_config.h` file. + +### Jump Tables + +Jump tables, at least in older Xbox 360 binaries, often have predictable assembly patterns, making them easy to detect statically without needing a virtual machine. XenonAnalyse has logic for detecting jump tables in Sonic Unleashed, though variations in other games (likely due to updates in the Xbox 360 compiler) may require modifications to the detection logic. Currently, there is no fully generic solution for handling jump tables, so updates to the detection logic may be needed for other games. + +The typical way to find jump tables is by searching for the `mtctr r0` instruction. It will almost always be followed with a `bctr`, with the previous instructions computing the jump address. + +XenonAnalyse generates a TOML file containing detected jump tables, which can be referenced in the main TOML config file. This allows the recompiler to generate real switch cases for these jump tables. + +### Function Boundary Analysis + +XenonAnalyse includes a function boundary analyzer that works well in most cases. Functions with stack space have their boundaries defined in the `.pdata` segment of the XEX. For functions not found in this segment, the analyzer detects the start of functions by searching for branch link instructions, and determines their length via static analysis. + +However, the analyzer struggles with functions containing jump tables, since they look like tail calls without enough information. While there is currently no solution for this, it might be relatively simple to extend the function analyzer to account for jump tables defined in the TOML file. As a workaround, the recompiler TOML file allows users to manually define function boundaries. + +### Exceptions + +The recompiler currently does not support exceptions. This is challenging due to the use of the link register and the fact that exception handlers can jump to arbitrary code locations. + +### setjmp + +`setjmp` and `longjmp` are implemented by redirecting them to native implementations. Thanks to the Xbox 360's large number of vector registers, the guest CPU state struct is large enough to hold the x86 CPU state and potentially states from other architectures. + +### Optimizations + +Since Xbox 360 binaries typically follow a stable ABI, we can make certain assumptions about code structure, allowing the Clang compiler to generate better code. Several optimization options are available in the recompiler, but it's recommended to test them only after having a successfully functioning recompilation. + +The link register can be skipped assuming the game does not utilize exceptions, as the whole process of recompilation already takes care of function return behavior. + +The following registers, assuming the game doesn't violate the ABI, can be safely converted into local variables, as they never leave the function scope: +* Count register +* XER +* Reserved register +* Condition registers +* Non argument registers +* Non volatile registers + +The local variable optimization particularly introduces the most improvements, as the calls to the register restore/save functions can be completely removed, and the redundant stores to the PPC context struct can be eliminated. In [Unleashed Recompiled](https://github.com/hedge-dev/UnleashedRecomp), the executable size decreases by around 20 MB with these optimizations, and frame times are reduced by several milliseconds. + +### Patch Mechanisms + +XenonRecomp defines PPC functions in a way that makes them easy to hook, using techniques in the Clang compiler. By aliasing a PPC function to an "implementation function" and marking the original function as weakly linked, users can override it with a custom implementation while retaining access to the original function: + +```cpp +PPC_FUNC_IMPL(__imp__sub_XXXXXXXX); +PPC_FUNC(sub_XXXXXXXX) +{ + __imp__sub_XXXXXXXX(ctx, base); +} +``` + +Additionally, mid-asm hooks can be inserted directly into the translated C++ code at specific instruction addresses. The recompiler inserts these function calls, and users are responsible for implementing them in their recompilation project. The linker resolves them during compilation. + +## Usage + +### XenonAnalyse + +XenonAnalyse, when used as a command-line application, allows an XEX file to be passed as an input argument to output a TOML file containing all the detected jump tables in the executable: + +``` +XenonAnalyse [input XEX file path] [output jump table TOML file path] +``` + +However, as explained in the earlier sections, due to variations between games, additional support may be needed to handle different patterns. + +[An example jump table TOML file can be viewed in the Unleashed Recompiled repository.](https://github.com/hedge-dev/UnleashedRecomp/blob/main/UnleashedRecompLib/config/SWA_switch_tables.toml) + +### XenonRecomp + +XenonRecomp accepts a TOML file with recompiler configurations and the path to the `ppc_context.h` file located in the XenonUtils directory: + +``` +XenonRecomp [input TOML file path] [input PPC context header file path] +``` + +[An example recompiler TOML file can be viewed in the Unleashed Recompiled repository.](https://github.com/hedge-dev/UnleashedRecomp/blob/main/UnleashedRecompLib/config/SWA.toml) + +#### Main + +```toml +[main] +file_path = "../private/default.xex" +patch_file_path = "../private/default.xexp" +patched_file_path = "../private/default_patched.xex" +out_directory_path = "../ppc" +switch_table_file_path = "SWA_switch_tables.toml" +``` + +All the paths are relative to the directory where the TOML file is stored. + +Property|Description +-|- +file_path|Path to the XEX file. +patch_file_path|Path to the XEXP file. This is not required if the game has no title updates. +patched_file_path|Path to the patched XEX file. XenonRecomp will create this file automatically if it is missing and reuse it in subsequent recompilations. It does nothing if no XEXP file is specified. You can pass this output file to XenonAnalyse. +out_directory_path|Path to the directory that will contain the output C++ code. This directory must exist before running the recompiler. +switch_table_file_path|Path to the TOML file containing the jump table definitions. The recompiler uses this file to convert jump tables to real switch cases. + +#### Optimizations + +```toml +skip_lr = false +skip_msr = false +ctr_as_local = false +xer_as_local = false +reserved_as_local = false +cr_as_local = false +non_argument_as_local = false +non_volatile_as_local = false +``` + +Enables or disables various optimizations explained earlier in the documentation. It is recommended not to enable these optimizations until you have a successfully running recompilation. + +#### Register Restore & Save Functions + +```toml +restgprlr_14_address = 0x831B0B40 +savegprlr_14_address = 0x831B0AF0 +restfpr_14_address = 0x831B144C +savefpr_14_address = 0x831B1400 +restvmx_14_address = 0x831B36E8 +savevmx_14_address = 0x831B3450 +restvmx_64_address = 0x831B377C +savevmx_64_address = 0x831B34E4 +``` + +Xbox 360 binaries feature specialized register restore & save functions that act similarly to switch case fallthroughs. Every function that utilizes non-volatile registers either has an inlined version of these functions or explicitly calls them. The recompiler requires the starting address of each restore/save function in the TOML file to recompile them correctly. These functions could likely be auto-detected, but there is currently no mechanism for it. + +Property|Description +-|- +restgprlr_14_address|Start address of the `__restgprlr_14` function. It starts with `ld r14, -0x98(r1)`, repeating the same operation for the rest of the non-volatile registers and restoring the link register at the end. +savegprlr_14_address|Start address of the `__savegprlr_14` function. It starts with `std r14, -0x98(r1)`, repeating the same operation for the rest of the non-volatile registers and saving the link register at the end. +restfpr_14_address|Start address of the `__restfpr_14` function. It starts with `lfd f14, -0x90(r12)`, repeating the same operation for the rest of the non-volatile FPU registers. +savefpr_14_address|Start address of the `__savefpr_14` function. It starts with `stfd r14, -0x90(r12)`, repeating the same operation for the rest of the non-volatile FPU registers. +restvmx_14_address|Start address of the `__restvmx_14` function. It starts with `li r11, -0x120` and `lvx v14, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers until `v31`. +savevmx_14_address|Start address of the `__savevmx_14` function. It starts with `li r11, -0x120` and `stvx v14, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers until `v31`. +restvmx_64_address|Start address of the `__restvmx_64` function. It starts with `li r11, -0x400` and `lvx128 v64, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers. +savevmx_64_address|Start address of the `__savevmx_64` function. It starts with `li r11, -0x400` and `stvx128 v64, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers. + +#### longjmp & setjmp + +```toml +longjmp_address = 0x831B6790 +setjmp_address = 0x831B6AB0 +``` + +These are addresses for the `longjmp` and `setjmp` functions in the executable. The recompiler directly redirects these functions to native versions. The implementation of these functions might vary between games. In some cases, you might find `longjmp` by looking for calls to `RtlUnwind`, and `setjmp` typically appears just after it. + +If the game does not use these functions, you can remove the properties from the TOML file. + +#### Explicit Function Boundaries + +```toml +functions = [ + { address = 0x824E7EF0, size = 0x98 }, + { address = 0x824E7F28, size = 0x60 }, +] +``` + +You can define function boundaries explicitly using the `functions` property if XenonAnalyse fails to analyze them correctly, for example, with functions containing jump tables. + +#### Invalid Instruction Skips + +```toml +invalid_instructions = [ + { data = 0x00000000, size = 4 }, # Padding + { data = 0x831B1C90, size = 8 }, # C++ Frame Handler + { data = 0x8324B3BC, size = 8 }, # C Specific Frame Handler + { data = 0x831C8B50, size = 8 }, + { data = 0x00485645, size = 44 } # End of .text +] +``` + +In the `invalid_instructions` property, you can define 32-bit integer values that instruct the recompiler to skip over certain bytes when it encounters them. For example, in Unleashed Recompiled, these are used to skip over exception handling data, which is placed between functions but is not valid code. + +#### Mid-asm Hooks + +```toml +[[midasm_hook]] +name = "IndexBufferLengthMidAsmHook" +address = 0x82E26244 +registers = ["r3"] +``` + +```cpp +void IndexBufferLengthMidAsmHook(PPCRegister& r3) +{ + // ... +} +``` + +You can define multiple mid-asm hooks in the TOML file, allowing the recompiler to insert function calls at specified addresses. When implementing them in your recompilation project, the linker will resolve the calls automatically. + +Property|Description +-|- +name|Function name of the mid-asm hook. You can reuse function names to place the same implementation at multiple addresses. Otherwise, unique implementations must have unique names. +address|Address of the instruction where the function call will be placed. This does not overwrite the instruction at the specified address. +registers|Registers to pass as arguments to the mid-asm hook. This is a list of registers because the local variable optimization does not keep optimized registers within the PPC context struct. +return|Set to `true` to indicate that the function where the hook was inserted should immediately return after calling the mid-asm hook. +return_on_true|Set to `true` to indicate that the function should return if the mid-asm hook call returns `true`. +return_on_false|Set to `true` to indicate that the function should return if the mid-asm hook call returns `false`. +jump_address|The address to jump to immediately after calling the mid-asm hook. The address must be within the same function where the hook was placed. +jump_address_on_true|The address to jump to if the mid-asm hook returns `true`. The address must be within the same function where the hook was placed. +jump_address_on_false|The address to jump to if the mid-asm hook returns `false`. The address must be within the same function where the hook was placed. +after_instruction|Set to `true` to place the mid-asm hook immediately after the instruction, instead of before. + +Certain properties are mutually exclusive. For example, you cannot use both `return` and `jump_address`, and direct or conditional returns/jumps cannot be mixed. The recompiler is going to show warnings if this is not followed. + +### Tests + +XenonRecomp can recompile Xenia's PPC tests and execute them through the XenonTests project in the repository. After building the tests using Xenia's build system, XenonRecomp can process the `src/xenia/cpu/ppc/testing/bin` directory as input, generating C++ files in the specified output directory: + +``` +XenonRecomp [input testing directory path] [input PPC context header file path] [output directory path] +``` + +Once the files are generated, refresh XenonTests' CMake cache to make them appear in the project. The tests can then be executed to compare the results of instructions against the expected values. + +## Building + +The project requires CMake 3.20 or later and Clang 18 or later to build. Since the repository includes submodules, ensure you clone it recursively. + +Compilers other than Clang have not been tested and are not recommended, including for recompilation output. The project relies on compiler-specific intrinsics and techniques that may not function correctly on other compilers, and many optimization methods depend on Clang's code generation. + +On Windows, you can use the clang-cl toolset and open the project in Visual Studio's CMake integration. + +## Special Thanks + +This project could not have been possible without the [Xenia](https://github.com/xenia-project/xenia) emulator, as many parts of the CPU code conversion process has been implemented by heavily referencing its PPC code translator. The project also uses code from [Xenia Canary](https://github.com/xenia-canary/xenia-canary) to patch XEX binaries. \ No newline at end of file diff --git a/XenonAnalyse/main.cpp b/XenonAnalyse/main.cpp index 74f04b4..d08371e 100644 --- a/XenonAnalyse/main.cpp +++ b/XenonAnalyse/main.cpp @@ -172,32 +172,25 @@ void* SearchMask(const void* source, const uint32_t* compare, size_t compareCoun return nullptr; } -int main() +static std::string out; + +template +static void println(fmt::format_string fmt, Args&&... args) { - const auto file = LoadFile("private/default.xex"); - auto image = Image::ParseImage(file.data(), file.size()); + fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...)); + out += '\n'; +}; - std::string out; - auto println = [&](fmt::format_string fmt, Args&&... args) +int main(int argc, char** argv) +{ + if (argc < 3) { - fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...)); - out += '\n'; - }; - //for (const auto& section : image.sections) - //{ - // image.symbols.emplace(section.name, section.base, section.size, Symbol_Section); - //} + printf("Usage: XenonAnalyse [input XEX file path] [output jump table TOML file path]"); + return EXIT_SUCCESS; + } - // MakeMask((uint32_t*)image.Find(0x82C40D84), 6); - - //auto data = "\x4D\x99\x00\x20"; - //auto data2 = ByteSwap((2129)); - //ppc_insn insn; - //ppc_insn insn2; - //ppc::Disassemble(data, 0, insn); - //ppc::Disassemble(&data2, 0, insn2); - //auto op = PPC_OP(insn.instruction); - //auto xop = PPC_XOP(insn.instruction); + const auto file = LoadFile(argv[1]); + auto image = Image::ParseImage(file.data(), file.size()); auto printTable = [&](const SwitchTable& table) { @@ -217,27 +210,7 @@ int main() std::vector switches{}; - auto insertTable = [&](size_t base, size_t defaultLabel, size_t r, size_t nLabels, uint32_t type) - { - auto& sw = switches.emplace_back(); - sw.base = base; - sw.defaultLabel = defaultLabel; - sw.r = r; - sw.labels.resize(nLabels); - sw.type = type; - }; - - println("# Generated by PowerAnalyse"); - insertTable(0x830ADAD8, 0x830ADB28, 11, 0x1B, SWITCH_COMPUTED); - insertTable(0x830AE1B0, 0x830AE21C, 11, 0x1B, SWITCH_BYTEOFFSET); - insertTable(0x82CFE120, 0x82CFDE68, 11, 0x10, SWITCH_SHORTOFFSET); - - println("# ---- MANUAL JUMPTABLE ----"); - for (auto& table : switches) - { - ReadTable(image, table); - printTable(table); - } + println("# Generated by XenonAnalyse"); auto scanPattern = [&](uint32_t* pattern, size_t count, size_t type) { @@ -332,122 +305,8 @@ int main() scanPattern(offsetSwitch, std::size(offsetSwitch), SWITCH_BYTEOFFSET); scanPattern(wordOffsetSwitch, std::size(wordOffsetSwitch), SWITCH_SHORTOFFSET); - FILE* f = fopen("out/switches.toml", "w"); - fwrite(out.data(), 1, out.size(), f); - fclose(f); + std::ofstream f(argv[2]); + f.write(out.data(), out.size()); - uint32_t cxxFrameHandler = ByteSwap(0x831B1C90); - uint32_t cSpecificFrameHandler = ByteSwap(0x8324B3BC); - image.symbols.emplace("__CxxFrameHandler", 0x831B1C90, 0x38, Symbol_Function); - image.symbols.emplace("__C_specific_handler", 0x8324B3BC, 0x38, Symbol_Function); - image.symbols.emplace("memcpy", 0x831B0ED0, 0x488, Symbol_Function); - image.symbols.emplace("memset", 0x831B0BA0, 0xA0, Symbol_Function); - image.symbols.emplace("blkmov", 0x831B1358, 0xA8, Symbol_Function); - - image.symbols.emplace(fmt::format("sub_{:X}", 0x82EF5D78), 0x82EF5D78, 0x3F8, Symbol_Function); - - // auto fnd = Function::Analyze(image.Find(0x82C40D58), image.size, 0x82C40D58); - - std::vector functions; - auto& pdata = *image.Find(".pdata"); - size_t count = pdata.size / sizeof(IMAGE_CE_RUNTIME_FUNCTION); - auto* pf = (IMAGE_CE_RUNTIME_FUNCTION*)pdata.data; - for (size_t i = 0; i < count; i++) - { - auto fn = pf[i]; - fn.BeginAddress = ByteSwap(fn.BeginAddress); - fn.Data = ByteSwap(fn.Data); - - auto& f = functions.emplace_back(); - f.base = fn.BeginAddress; - f.size = fn.FunctionLength * 4; - - if (f.base == 0x82BD7420) - { - __builtin_debugtrap(); - } - - image.symbols.emplace(fmt::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function); - } - - auto sym = image.symbols.find(0x82BD7420); - - std::vector missingFunctions; - for (const auto& section : image.sections) - { - if (!(section.flags & SectionFlags_Code)) - { - continue; - } - size_t base = section.base; - uint8_t* data = section.data; - uint8_t* dataEnd = section.data + section.size; - const Symbol* prevSymbol = nullptr; - while (data < dataEnd) - { - if (*(uint32_t*)data == 0) - { - data += 4; - base += 4; - continue; - } - - if (*(uint32_t*)data == cxxFrameHandler || *(uint32_t*)data == cSpecificFrameHandler) - { - data += 8; - base += 8; - continue; - } - - auto fnSymbol = image.symbols.find(base); - if (fnSymbol != image.symbols.end() && fnSymbol->type == Symbol_Function) - { - assert(fnSymbol->address == base); - - prevSymbol = &*fnSymbol; - base += fnSymbol->size; - data += fnSymbol->size; - } - else - { - auto& missingFn = missingFunctions.emplace_back(Function::Analyze(data, dataEnd - data, base)); - base += missingFn.size; - data += missingFn.size; - - fmt::println("sub_{:X}", missingFn.base); - } - } - } - - //ppc_insn insn; - //uint8_t c[4] = { 0x10, 0x00, 0x59, 0xC3 }; - //ppc::Disassemble(c, 0x831D6C64, insn); - //fmt::println("{:20}{}", insn.opcode->name, insn.op_str); - - - const auto entrySymbol = image.symbols.find(image.entry_point); - assert(entrySymbol != image.symbols.end()); - - const auto entrySize = entrySymbol->size; - image.symbols.erase(entrySymbol); - - image.symbols.emplace("_start", image.entry_point, entrySize, Symbol_Function); - - fmt::println("FUNCTIONS"); - for (const auto& fn : functions) - { - fmt::println("\tsub_{:X}", fn.base); - } - fmt::println(""); - - - fmt::println("SECTIONS"); - for (const auto& section : image.sections) - { - printf("Section %.8s\n", section.name.c_str()); - printf("\t%X-%X\n", section.base, section.base + section.size); - } - - fmt::println(""); - return 0; + return EXIT_SUCCESS; } diff --git a/XenonRecomp/main.cpp b/XenonRecomp/main.cpp index 602d3db..12c6d8c 100644 --- a/XenonRecomp/main.cpp +++ b/XenonRecomp/main.cpp @@ -3,6 +3,14 @@ int main(int argc, char* argv[]) { +#ifndef XENON_RECOMP_CONFIG_FILE_PATH + if (argc < 3) + { + printf("Usage: XenonRecomp [input TOML file path] [PPC context header file path]"); + return EXIT_SUCCESS; + } +#endif + const char* path = #ifdef XENON_RECOMP_CONFIG_FILE_PATH XENON_RECOMP_CONFIG_FILE_PATH @@ -40,5 +48,5 @@ int main(int argc, char* argv[]) TestRecompiler::RecompileTests(path, argv[2]); } - return 0; + return EXIT_SUCCESS; } diff --git a/XenonRecomp/recompiler.cpp b/XenonRecomp/recompiler.cpp index a854fe7..e412551 100644 --- a/XenonRecomp/recompiler.cpp +++ b/XenonRecomp/recompiler.cpp @@ -2578,7 +2578,11 @@ void Recompiler::SaveCurrentOutData(const std::string_view& name) bool shouldWrite = true; // Check if an identical file already exists first to not trigger recompilation - std::string filePath = fmt::format("{}/{}/{}", config.directoryPath, config.outDirectoryPath, name.empty() ? cppName : name); + std::string directoryPath = config.directoryPath; + if (!directoryPath.empty()) + directoryPath += "/"; + + std::string filePath = fmt::format("{}{}/{}", directoryPath, config.outDirectoryPath, name.empty() ? cppName : name); FILE* f = fopen(filePath.c_str(), "rb"); if (f) { diff --git a/XenonRecomp/test_recompiler.cpp b/XenonRecomp/test_recompiler.cpp index c179907..f4fcdfe 100644 --- a/XenonRecomp/test_recompiler.cpp +++ b/XenonRecomp/test_recompiler.cpp @@ -51,7 +51,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds recompiler.println("#define PPC_CONFIG_H_INCLUDED"); recompiler.println("#include \n"); - recompiler.println("#define __debugbreak()\n"); + recompiler.println("#define __builtin_debugtrap()\n"); for (auto& fn : recompiler.functions) { @@ -102,8 +102,12 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds fmt::println(file, "#define PPC_CONFIG_H_INCLUDED"); fmt::println(file, "#include "); + fmt::println(file, "#ifdef _WIN32"); fmt::println(file, "#include "); - fmt::println(file, "#include \n"); + fmt::println(file, "#else"); + fmt::println(file, "#include "); + fmt::println(file, "#endif"); + fmt::println(file, "#include \n"); fmt::println(file, "#define PPC_CHECK_VALUE_U(f, lhs, rhs) if (lhs != rhs) fmt::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{:X}}\", lhs)\n"); fmt::println(file, "#define PPC_CHECK_VALUE_F(f, lhs, rhs) if (lhs != rhs) fmt::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{}}\", lhs)\n"); @@ -274,7 +278,11 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds } fmt::println(file, "int main() {{"); - fmt::println(file, "\tuint8_t* base = reinterpret_cast(VirtualAlloc(nullptr, 0x100000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));"); + fmt::println(file, "#ifdef _WIN32"); + fmt::println(file, "\tuint8_t* base = reinterpret_cast(VirtualAlloc(nullptr, 0x100000000ull, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));"); + fmt::println(file, "#else"); + fmt::println(file, "\tuint8_t* base = reinterpret_cast(mmap(NULL, 0x100000000ull, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0));"); + fmt::println(file, "#endif"); fwrite(main.data(), 1, main.size(), file); fmt::println(file, "\treturn 0;"); fmt::println(file, "}}"); diff --git a/XenonSample/.gitignore b/XenonSample/.gitignore deleted file mode 100644 index 1d6f231..0000000 --- a/XenonSample/.gitignore +++ /dev/null @@ -1 +0,0 @@ -ppc_* \ No newline at end of file diff --git a/XenonSample/CMakeLists.txt b/XenonSample/CMakeLists.txt deleted file mode 100644 index fd5fbbd..0000000 --- a/XenonSample/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -project("XenonSample") - -add_compile_options( - "/D_HAS_EXCEPTIONS=0" - "/fp:strict" - "/GS-" - "/EHa-" - "-march=sandybridge" - "-fno-strict-aliasing") - -file(GLOB RecompiledFiles *.cpp) -add_library(XenonSample ${RecompiledFiles}) - -target_precompile_headers(XenonSample PUBLIC "ppc_recomp_shared.h") diff --git a/XenonSample/main.cpp b/XenonSample/main.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/XenonTests/CMakeLists.txt b/XenonTests/CMakeLists.txt index a79fe93..8beb23c 100644 --- a/XenonTests/CMakeLists.txt +++ b/XenonTests/CMakeLists.txt @@ -1,13 +1,20 @@ project("XenonTests") -add_compile_options( - "-march=x86-64-v3" - "-Wno-unused-label" - "-Wno-unused-variable") +file(GLOB TEST_FILES *.cpp) -file(GLOB TestFiles *.cpp) - -if(TestFiles) - add_executable(XenonTests ${TestFiles}) - target_link_libraries(XenonTests PUBLIC XenonUtils) +if(TEST_FILES) + add_executable(XenonTests + ${TEST_FILES} + ) + target_link_libraries(XenonTests + PUBLIC + XenonUtils + fmt::fmt + ) + target_compile_options(XenonTests + PRIVATE + "-march=sandybridge" + "-Wno-unused-label" + "-Wno-unused-variable" + ) endif() diff --git a/tests/XenonAnalyse/add-cond.cpp b/tests/XenonAnalyse/add-cond.cpp deleted file mode 100644 index 4502193..0000000 --- a/tests/XenonAnalyse/add-cond.cpp +++ /dev/null @@ -1,10 +0,0 @@ -int add(int a, int b) -{ - int c = a + b; - return c == 0 ? 50000 : c; -} - -extern "C" int _start() -{ - return add(1, 2); -} \ No newline at end of file diff --git a/tests/XenonAnalyse/add-cond.elf b/tests/XenonAnalyse/add-cond.elf deleted file mode 100644 index b889772b2d0839b31c80ba898c48d69ba34a1b67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1472 zcma)6T}u>E7=CAFwY5|%l0+jG5;Cw+Qz)tnm$sWHH8?a&AJO0Bo%F33wj71FivMfLJfqq_kJHriRsK(l&t@ z?jP4cp=}nnS#X&K%B^=Id;aA3#=(cx>T4;lypqawUqtbSIOMq3`;jM%t0dJ6)*FmZ z0jxWK8N3Id1gyCC^BZWt3#BN{wij(xo(D-9#!+SAnmg|h)URR?O;; z)xxNir1u4MLG=O+wp!1q>K&~2jK~RC&*lAGR=OKTS&($a^>?FmwfpwaRRnz&i8lc^}L6E=|;XII5-#+^nqiV_iuCl|1YLT(Cg-|sXJyWm^~VTfoH=woRc~$o>K{6 z44L|Aob7ioo<|&g!hqzvwsPi}{%K4mJAy$2e1ucYryJ`=}wy z(2bZl_C4cp$NTTavS7UGUF=uTV4IdCh(f`Df1sedPMux- zAN~Lp-E|k@Ij@O0IC$W^bMCwMedXqAx4NecB|k8v0IzaN<)9m|UpluTretJE#xROx znL~jl{C&(cluUt7$ZNt+$7GI&=>vHH3B1Hik1~3E%PG-Bt`;-HOe=UlMEnOj5d)e}dZA;>aRb}yi51uvfmqFMr(?S?{$(eqH=w6};Emi! zrQT{;&0bHAkJe!wM~?lV?giNS|KJ@-gFS_k>}i7J4HKB4&&F@yJ8ED8_MeymSyy5J zyMtj_+tI)rIScX|#85q$ZIFHDJon=-WP<$XVyFk0wIqUaFJcA!UH6K3j)@q$?i))O BVOanG diff --git a/tests/XenonAnalyse/cond-fall.cpp b/tests/XenonAnalyse/cond-fall.cpp deleted file mode 100644 index f2b439f..0000000 --- a/tests/XenonAnalyse/cond-fall.cpp +++ /dev/null @@ -1,15 +0,0 @@ -int cond(int a) -{ - int v = 2; - if (a == 1) - { - v += 5; - } - - return v; -} - -extern "C" int _start() -{ - return cond(0); -} diff --git a/tests/XenonAnalyse/cond-fall.elf b/tests/XenonAnalyse/cond-fall.elf deleted file mode 100644 index 637d89f531077d12061ae1a554e35273241ef2d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 820 zcmb7C!D`z;6nrb&X+l$CiVr>*BH!GQD01<^^pYIXV5lkNS_-x!E3K$38%YEQ3^D1Y z$3Xf4?Zw}c56B19klx#CF6zvBReH#!1G8`5&ePjn?Owj_9xF@9UszIsR%NAj5nCcw zO?xe|r6dh`f>C6RUfB@(n_&@=EP!|LHKCsw(&J)!LHI%fOT*&I(c^tti6*?MVTsN# z{dt1?F>qh99|ouU;m}FOPLui9diLXHDYe~561ky{=L7bxh|A2cX zE#5gI$ve-HpJf9R)NAG}JP+?S2j2&=1+va%0{I@p3oTazJ+cb&Ul^z_5QiY&0q5C| ePs|4SCJodr#LGNGWG`X|{I~7_`8Ev$UH1oxm}b}j diff --git a/tests/XenonAnalyse/cond.cpp b/tests/XenonAnalyse/cond.cpp deleted file mode 100644 index 3e98fbc..0000000 --- a/tests/XenonAnalyse/cond.cpp +++ /dev/null @@ -1,18 +0,0 @@ -int cond(int a) -{ - if (a == 1) - { - return 5; - } - else if (a == 4) - { - return 9; - } - - return 0; -} - -extern "C" int _start() -{ - return cond(0); -} diff --git a/tests/XenonAnalyse/cond.elf b/tests/XenonAnalyse/cond.elf deleted file mode 100644 index 3e8274f9fd0c9b9a6fbf1c80ffeb2be71ecbe716..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 844 zcma)4Jxjw-6umD^{T7RMDB=(|b%?2S5EmU(1cTt}qO}bgY}1kiQ7BmT4+uIqI*C8X zAE2P%sK20|^ZFtV4jwq~p8LUlU-x3e-&B^8A6Qa^Rt2Se*oue+YUM~?=4247$N(b+ zL+A%X8+j~g<_&Gd%I3kkd$+Wm}P-wH}Jk-Im{GgD9Bcj5m9o=X-R;o)MV z?j|j_O#g1S_j3CxGo97ZvzrSkcEP!vsI@C8ZsEMk2a-p;1nrre{VO+Ya#^hy-$rfe z8hw{%#o6hK(;2cJZPuRG$#rP8rA5?ISt|@Y(-g+clZPP4H>tk_XRV;_kum1#HYbdnLqTj?}kwt#BFiY;4Brl z)@n9`2qHOerqx5(<0Osys&A#%it28y-Io2m3Uw&^Nm`9lwEj1EM$+Oez>=JW4A~uq z5uhG0ci=tv=NULR#01DXj}Bx9%e7qSv61}5KEZ{ R%U;9+_^ZyEhEHbDb>I3BX;uIL diff --git a/tests/XenonAnalyse/loop.cpp b/tests/XenonAnalyse/loop.cpp deleted file mode 100644 index cc9f4a3..0000000 --- a/tests/XenonAnalyse/loop.cpp +++ /dev/null @@ -1,15 +0,0 @@ -int loop(int a) -{ - int result = 0; - for (int i = 0; i < a; i++) - { - result = result * 31 + i; - result *= result; - } - return result; -} - -extern "C" int _start() -{ - return loop(30); -} diff --git a/tests/XenonAnalyse/loop.elf b/tests/XenonAnalyse/loop.elf deleted file mode 100644 index 3f7c6e4baaecc6d8a13c0056d87b7977da035f39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1496 zcma)6&1%$882yq=tF2S1TA@w_2Xs-Pq*E_+{jVHz?pOD5nOVwhF% zyomK0Os^~>GA&1?h-g@4RGk{5nnDW56BgoRA2NmjQ8F{1Utfj00jkU6DesW=s{;Vlp=g z%{egkN*QxA$gvIAUD*tFt@>Q1d%pE*VOwNj4U=}|Os`p*SlAJx=brj>P5NJ!wx!G( z`RZEj-I`ncKtJsFe;igl$;p&>PK+J+M*xmFg!m136fna6&u^gJE`q$dZ70|$KlY;7 z4}7a)($5@#LUijyeyQkHfYc5S!YmB#&f`o1*c#EBAVEUqrq?%ux6fB%2eX9ssc z-$8ZvbPsxcMv&mXP=`HI`@}se0Mw8fAH%+W2UEA3B0GOb!l+_^zj46HVi3(@E>>>( TVPCXOfbBA~Uclj+YE!yjjEIa| diff --git a/tests/XenonAnalyse/private/.gitignore b/tests/XenonAnalyse/private/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/tests/XenonAnalyse/private/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/compile-all.bat b/tests/compile-all.bat deleted file mode 100644 index 1a20468..0000000 --- a/tests/compile-all.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off - -pushd XenonAnalyse -for %%f in (*.cpp) do call ..\compile.bat %%f -popd diff --git a/tests/compile.bat b/tests/compile.bat deleted file mode 100644 index 545d1c9..0000000 --- a/tests/compile.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -clang -target powerpc-unknown-linux-gnu -fuse-ld=lld -nostdlib -m32 -o %~n1.elf %1 \ No newline at end of file