Rebrand to XenonRecomp.

This commit is contained in:
Skyth
2025-01-19 22:39:12 +03:00
parent 7fb8af1bad
commit 87e350906b
54 changed files with 69 additions and 64 deletions

View File

@@ -0,0 +1,24 @@
cmake_minimum_required (VERSION 3.8)
project("XenonRecomp")
add_executable(XenonRecomp
"main.cpp"
"recompiler.cpp"
"test_recompiler.cpp"
"recompiler_config.cpp")
target_precompile_headers(XenonRecomp PUBLIC "pch.h")
target_link_libraries(XenonRecomp PRIVATE
LibXenonAnalyse
XenonUtils
fmt::fmt
tomlplusplus::tomlplusplus
xxHash::xxhash)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(XenonRecomp PRIVATE -Wno-switch -Wno-unused-variable -Wno-null-arithmetic)
endif()
target_compile_definitions(XenonRecomp PRIVATE _CRT_SECURE_NO_WARNINGS)

42
XenonRecomp/main.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "pch.h"
#include "test_recompiler.h"
int main(int argc, char* argv[])
{
const char* path =
#ifdef XENON_RECOMP_CONFIG_FILE_PATH
XENON_RECOMP_CONFIG_FILE_PATH
#else
argv[1]
#endif
;
if (std::filesystem::is_regular_file(path))
{
Recompiler recompiler;
recompiler.LoadConfig(path);
recompiler.Analyse();
auto entry = recompiler.image.symbols.find(recompiler.image.entry_point);
if (entry != recompiler.image.symbols.end())
{
entry->name = "_xstart";
}
const char* headerFilePath =
#ifdef XENON_RECOMP_HEADER_FILE_PATH
XENON_RECOMP_HEADER_FILE_PATH
#else
argv[2]
#endif
;
recompiler.Recompile(headerFilePath);
}
else
{
TestRecompiler::RecompileTests(path, argv[2]);
}
return 0;
}

19
XenonRecomp/pch.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <charconv>
#include <disasm.h>
#include <file.h>
#include <filesystem>
#include <fstream>
#include <function.h>
#include <image.h>
#include <toml++/toml.hpp>
#include <unordered_map>
#include <unordered_set>
#include <xbox.h>
#include <xxhash.h>
#include <fmt/core.h>
#include <xmmintrin.h>

2526
XenonRecomp/recompiler.cpp Normal file

File diff suppressed because it is too large Load Diff

70
XenonRecomp/recompiler.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include "pch.h"
#include "recompiler_config.h"
struct RecompilerLocalVariables
{
bool ctr{};
bool xer{};
bool reserved{};
bool cr[8]{};
bool r[32]{};
bool f[32]{};
bool v[128]{};
bool env{};
bool temp{};
bool vTemp{};
bool ea{};
};
enum class CSRState
{
Unknown,
FPU,
VMX
};
struct Recompiler
{
// Enforce In-order Execution of I/O constant for quick comparison
static constexpr uint32_t c_eieio = 0xAC06007C;
Image image;
std::vector<Function> functions;
std::string out;
size_t cppFileIndex = 0;
RecompilerConfig config;
void LoadConfig(const std::string_view& configFilePath);
template<class... Args>
void print(fmt::format_string<Args...> fmt, Args&&... args)
{
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
}
template<class... Args>
void println(fmt::format_string<Args...> fmt, Args&&... args)
{
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
out += '\n';
}
void Analyse();
// TODO: make a RecompileArgs struct instead this is getting messy
bool Recompile(
const Function& fn,
uint32_t base,
const ppc_insn& insn,
const uint32_t* data,
std::unordered_map<uint32_t, RecompilerSwitchTable>::iterator& switchTable,
RecompilerLocalVariables& localVariables,
CSRState& csrState);
bool Recompile(const Function& fn);
void Recompile(const std::filesystem::path& headerFilePath);
void SaveCurrentOutData(const std::string_view& name = std::string_view());
};

View File

@@ -0,0 +1,124 @@
#include "recompiler_config.h"
void RecompilerConfig::Load(const std::string_view& configFilePath)
{
directoryPath = configFilePath.substr(0, configFilePath.find_last_of("\\/") + 1);
toml::table toml = toml::parse_file(configFilePath)
#if !TOML_EXCEPTIONS
.table()
#endif
;
if (auto mainPtr = toml["main"].as_table())
{
const auto& main = *mainPtr;
filePath = main["file_path"].value_or<std::string>("");
outDirectoryPath = main["out_directory_path"].value_or<std::string>("");
switchTableFilePath = main["switch_table_file_path"].value_or<std::string>("");
skipLr = main["skip_lr"].value_or(false);
skipMsr = main["skip_msr"].value_or(false);
ctrAsLocalVariable = main["ctr_as_local"].value_or(false);
xerAsLocalVariable = main["xer_as_local"].value_or(false);
reservedRegisterAsLocalVariable = main["reserved_as_local"].value_or(false);
crRegistersAsLocalVariables = main["cr_as_local"].value_or(false);
nonArgumentRegistersAsLocalVariables = main["non_argument_as_local"].value_or(false);
nonVolatileRegistersAsLocalVariables = main["non_volatile_as_local"].value_or(false);
restGpr14Address = main["restgprlr_14_address"].value_or(0u);
saveGpr14Address = main["savegprlr_14_address"].value_or(0u);
restFpr14Address = main["restfpr_14_address"].value_or(0u);
saveFpr14Address = main["savefpr_14_address"].value_or(0u);
restVmx14Address = main["restvmx_14_address"].value_or(0u);
saveVmx14Address = main["savevmx_14_address"].value_or(0u);
restVmx64Address = main["restvmx_64_address"].value_or(0u);
saveVmx64Address = main["savevmx_64_address"].value_or(0u);
longJmpAddress = main["longjmp_address"].value_or(0u);
setJmpAddress = main["setjmp_address"].value_or(0u);
if (auto functionsArray = main["functions"].as_array())
{
for (auto& func : *functionsArray)
{
auto& funcTable = *func.as_table();
uint32_t address = *funcTable["address"].value<uint32_t>();
uint32_t size = *funcTable["size"].value<uint32_t>();
functions.emplace(address, size);
}
}
if (auto invalidArray = main["invalid_instructions"].as_array())
{
for (auto& instr : *invalidArray)
{
auto& instrTable = *instr.as_table();
uint32_t data = *instrTable["data"].value<uint32_t>();
uint32_t size = *instrTable["size"].value<uint32_t>();
invalidInstructions.emplace(data, size);
}
}
if (!switchTableFilePath.empty())
{
toml::table switchToml = toml::parse_file(directoryPath + switchTableFilePath)
#if !TOML_EXCEPTIONS
.table()
#endif
;
if (auto switchArray = switchToml["switch"].as_array())
{
for (auto& entry : *switchArray)
{
auto& table = *entry.as_table();
RecompilerSwitchTable switchTable;
switchTable.r = *table["r"].value<uint32_t>();
for (auto& label : *table["labels"].as_array())
{
switchTable.labels.push_back(*label.value<uint32_t>());
}
switchTables.emplace(*table["base"].value<uint32_t>(), std::move(switchTable));
}
}
}
}
if (auto midAsmHookArray = toml["midasm_hook"].as_array())
{
for (auto& entry : *midAsmHookArray)
{
auto& table = *entry.as_table();
RecompilerMidAsmHook midAsmHook;
midAsmHook.name = *table["name"].value<std::string>();
if (auto registerArray = table["registers"].as_array())
{
for (auto& reg : *registerArray)
midAsmHook.registers.push_back(*reg.value<std::string>());
}
midAsmHook.ret = table["return"].value_or(false);
midAsmHook.returnOnTrue = table["return_on_true"].value_or(false);
midAsmHook.returnOnFalse = table["return_on_false"].value_or(false);
midAsmHook.jumpAddress = table["jump_address"].value_or(0u);
midAsmHook.jumpAddressOnTrue = table["jump_address_on_true"].value_or(0u);
midAsmHook.jumpAddressOnFalse = table["jump_address_on_false"].value_or(0u);
if ((midAsmHook.ret && midAsmHook.jumpAddress != NULL) ||
(midAsmHook.returnOnTrue && midAsmHook.jumpAddressOnTrue != NULL) ||
(midAsmHook.returnOnFalse && midAsmHook.jumpAddressOnFalse != NULL))
{
fmt::println("{}: can't return and jump at the same time", midAsmHook.name);
}
if ((midAsmHook.ret || midAsmHook.jumpAddress != NULL) &&
(midAsmHook.returnOnFalse != NULL || midAsmHook.returnOnTrue != NULL ||
midAsmHook.jumpAddressOnFalse != NULL || midAsmHook.jumpAddressOnTrue != NULL))
{
fmt::println("{}: can't mix direct and conditional return/jump", midAsmHook.name);
}
midAsmHooks.emplace(*table["address"].value<uint32_t>(), std::move(midAsmHook));
}
}
}

View File

@@ -0,0 +1,53 @@
#pragma once
struct RecompilerSwitchTable
{
uint32_t r;
std::vector<uint32_t> labels;
};
struct RecompilerMidAsmHook
{
std::string name;
std::vector<std::string> registers;
bool ret = false;
bool returnOnTrue = false;
bool returnOnFalse = false;
uint32_t jumpAddress = 0;
uint32_t jumpAddressOnTrue = 0;
uint32_t jumpAddressOnFalse = 0;
};
struct RecompilerConfig
{
std::string directoryPath;
std::string filePath;
std::string outDirectoryPath;
std::string switchTableFilePath;
std::unordered_map<uint32_t, RecompilerSwitchTable> switchTables;
bool skipLr = false;
bool ctrAsLocalVariable = false;
bool xerAsLocalVariable = false;
bool reservedRegisterAsLocalVariable = false;
bool skipMsr = false;
bool crRegistersAsLocalVariables = false;
bool nonArgumentRegistersAsLocalVariables = false;
bool nonVolatileRegistersAsLocalVariables = false;
uint32_t restGpr14Address = 0;
uint32_t saveGpr14Address = 0;
uint32_t restFpr14Address = 0;
uint32_t saveFpr14Address = 0;
uint32_t restVmx14Address = 0;
uint32_t saveVmx14Address = 0;
uint32_t restVmx64Address = 0;
uint32_t saveVmx64Address = 0;
uint32_t longJmpAddress = 0;
uint32_t setJmpAddress = 0;
std::unordered_map<uint32_t, uint32_t> functions;
std::unordered_map<uint32_t, uint32_t> invalidInstructions;
std::unordered_map<uint32_t, RecompilerMidAsmHook> midAsmHooks;
void Load(const std::string_view& configFilePath);
};

View File

@@ -0,0 +1,283 @@
#include "test_recompiler.h"
void TestRecompiler::Analyse(const std::string_view& testName)
{
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;
while (data < dataEnd)
{
if (*(uint32_t*)data == 0)
{
data += 4;
base += 4;
continue;
}
auto& fn = functions.emplace_back(Function::Analyze(data, dataEnd - data, base));
image.symbols.emplace(fmt::format("{}_{:X}", testName, fn.base), fn.base, fn.size, Symbol_Function);
base += fn.size;
data += fn.size;
}
}
std::sort(functions.begin(), functions.end(), [](auto& lhs, auto& rhs) { return lhs.base < rhs.base; });
}
void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* dstDirectoryPath)
{
std::map<std::string, std::unordered_set<size_t>> functions;
for (auto& file : std::filesystem::directory_iterator(srcDirectoryPath))
{
if (file.path().extension() == ".o")
{
const auto exeFile = LoadFile(file.path().string().c_str());
TestRecompiler recompiler;
recompiler.config.outDirectoryPath = dstDirectoryPath;
recompiler.image = Image::ParseImage(exeFile.data(), exeFile.size());
auto stem = file.path().stem().string();
recompiler.Analyse(stem);
recompiler.println("#define PPC_CONFIG_H_INCLUDED");
recompiler.println("#include <ppc_context.h>\n");
recompiler.println("#define __debugbreak()\n");
for (auto& fn : recompiler.functions)
{
if (recompiler.Recompile(fn))
{
functions[stem].emplace(fn.base);
}
else
{
fmt::println("Function {:X} in {} has unimplemented instructions", fn.base, stem);
}
}
stem += ".cpp";
recompiler.SaveCurrentOutData(stem);
}
}
std::unordered_map<std::string, std::string> symbols;
for (auto& [fn, addr] : functions)
{
std::ifstream in(fmt::format("{}/{}.dis", srcDirectoryPath, fn));
if (in.is_open())
{
std::string line;
while (std::getline(in, line))
{
int spaceIndex = line.find(' ');
int bracketIndex = line.find('>');
if (spaceIndex != std::string::npos && bracketIndex != std::string::npos)
{
size_t address = ~0;
std::from_chars(&line[0], &line[spaceIndex], address, 16);
address &= 0xFFFFF;
if (addr.find(address) != addr.end())
symbols.emplace(line.substr(spaceIndex + 2, bracketIndex - spaceIndex - 2), fmt::format("{}_{:X}", fn, address));
}
}
}
else
{
fmt::println("Unable to locate disassembly file for {}", fn);
}
}
FILE* file = fopen(fmt::format("{}/main.cpp", dstDirectoryPath).c_str(), "w");
std::string main;
fmt::println(file, "#define PPC_CONFIG_H_INCLUDED");
fmt::println(file, "#include <ppc_context.h>");
fmt::println(file, "#include <Windows.h>");
fmt::println(file, "#include <print>\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");
for (auto& [fn, addr] : functions)
{
std::ifstream in(fmt::format("{}/../{}.s", srcDirectoryPath, fn));
if (in.is_open())
{
std::string str;
auto getline = [&]()
{
if (std::getline(in, str))
{
str.erase(str.find_last_not_of(' ') + 1);
str.erase(0, str.find_first_not_of(' '));
return true;
}
return false;
};
while (getline())
{
if (!str.empty() && str[0] != '#')
{
int colonIndex = str.find(':');
if (colonIndex != std::string::npos)
{
auto name = str.substr(0, colonIndex);
auto symbol = symbols.find(name);
if (symbol != symbols.end())
{
fmt::println(file, "PPC_FUNC({});\n", symbol->second);
fmt::println(file, "void {}(uint8_t* base) {{", name);
fmt::println(file, "\tPPCContext ctx{{}};");
fmt::println(file, "\tctx.fpscr.loadFromHost();");
while (getline() && !str.empty() && str[0] == '#')
{
if (str.size() > 1 && str[1] == '_')
{
int registerInIndex = str.find("REGISTER_IN");
if (registerInIndex != std::string::npos)
{
int spaceIndex = str.find(' ', registerInIndex);
int secondSpaceIndex = str.find(' ', spaceIndex + 1);
auto reg = str.substr(spaceIndex + 1, secondSpaceIndex - spaceIndex - 1);
if (reg[0] == 'v')
{
int openingBracketIndex = str.find('[', secondSpaceIndex + 1);
int commaIndex0 = str.find(',', openingBracketIndex + 1);
int commaIndex1 = str.find(',', commaIndex0 + 1);
int commaIndex2 = str.find(',', commaIndex1 + 1);
int closingBracketIndex = str.find(']', commaIndex2 + 1);
fmt::println(file, "\tctx.{}.u32[3] = 0x{};", reg, str.substr(openingBracketIndex + 1, commaIndex0 - openingBracketIndex - 1));
fmt::println(file, "\tctx.{}.u32[2] = 0x{};", reg, str.substr(commaIndex0 + 2, commaIndex1 - commaIndex0 - 2));
fmt::println(file, "\tctx.{}.u32[1] = 0x{};", reg, str.substr(commaIndex1 + 2, commaIndex2 - commaIndex1 - 2));
fmt::println(file, "\tctx.{}.u32[0] = 0x{};", reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
}
else
{
fmt::println(file, "\tctx.{}.{}64 = {};",
reg,
str.find('.', secondSpaceIndex) != std::string::npos ? 'f' : 'u',
str.substr(secondSpaceIndex + 1));
}
}
else
{
int memoryInIndex = str.find("MEMORY_IN");
if (memoryInIndex != std::string::npos)
{
int spaceIndex = str.find(' ', memoryInIndex);
int secondSpaceIndex = str.find(' ', spaceIndex + 1);
auto address = str.substr(spaceIndex + 1, secondSpaceIndex - spaceIndex - 1);
for (size_t i = secondSpaceIndex + 1, j = 0; i < str.size(); i++)
{
if (str[i] != ' ')
{
fmt::println(file, "\tbase[0x{} + 0x{:X}] = 0x{}{};", address, j, str[i], str[i + 1]);
++i; // the loop adds another
++j;
}
}
}
}
}
}
while (getline() && (str.empty() || str[0] != '#'))
;
fmt::println(file, "\t{}(ctx, base);", symbol->second);
do
{
if (str.size() > 1 && str[1] == '_')
{
int registerOutIndex = str.find("REGISTER_OUT");
if (registerOutIndex != std::string::npos)
{
int spaceIndex = str.find(' ', registerOutIndex);
int secondSpaceIndex = str.find(' ', spaceIndex + 1);
auto reg = str.substr(spaceIndex + 1, secondSpaceIndex - spaceIndex - 1);
if (reg[0] == 'c')
continue; // TODO
if (reg[0] == 'v')
{
int openingBracketIndex = str.find('[', secondSpaceIndex + 1);
int commaIndex0 = str.find(',', openingBracketIndex + 1);
int commaIndex1 = str.find(',', commaIndex0 + 1);
int commaIndex2 = str.find(',', commaIndex1 + 1);
int closingBracketIndex = str.find(']', commaIndex2 + 1);
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[3], 0x{});", name, reg, str.substr(openingBracketIndex + 1, commaIndex0 - openingBracketIndex - 1));
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[2], 0x{});", name, reg, str.substr(commaIndex0 + 2, commaIndex1 - commaIndex0 - 2));
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[1], 0x{});", name, reg, str.substr(commaIndex1 + 2, commaIndex2 - commaIndex1 - 2));
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[0], 0x{});", name, reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
}
else
{
fmt::println(file, "\tPPC_CHECK_VALUE_{}({}, ctx.{}.{}64, {});",
str.find('.', secondSpaceIndex) != std::string::npos ? 'F' : 'U',
name,
reg,
str.find('.', secondSpaceIndex) != std::string::npos ? 'f' : 'u',
str.substr(secondSpaceIndex + 1));
}
}
else
{
int memoryOutIndex = str.find("MEMORY_OUT");
if (memoryOutIndex != std::string::npos)
{
int spaceIndex = str.find(' ', memoryOutIndex);
int secondSpaceIndex = str.find(' ', spaceIndex + 1);
auto address = str.substr(spaceIndex + 1, secondSpaceIndex - spaceIndex - 1);
for (size_t i = secondSpaceIndex + 1, j = 0; i < str.size(); i++)
{
if (str[i] != ' ')
{
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, base[0x{} + 0x{:X}], 0x{}{});", name, address, j, str[i], str[i + 1]);
++i; // the loop adds another
++j;
}
}
}
}
}
} while (getline() && !str.empty() && str[0] == '#');
fmt::println(file, "}}\n");
fmt::format_to(std::back_inserter(main), "\t{}(base);\n", name);
}
else
{
fmt::println("Found no symbol for {}", name);
}
}
}
}
}
else
{
fmt::println("Unable to locate source file for {}", fn);
}
}
fmt::println(file, "int main() {{");
fmt::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, 0x100000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));");
fwrite(main.data(), 1, main.size(), file);
fmt::println(file, "\treturn 0;");
fmt::println(file, "}}");
fclose(file);
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "recompiler.h"
struct TestRecompiler : Recompiler
{
void Analyse(const std::string_view& testName);
void Reset();
static void RecompileTests(const char* srcDirectoryPath, const char* dstDirectoryPath);
};