Compare commits

..

8 Commits

Author SHA1 Message Date
DeaTh-G
943fcafd37 make store instructions check for mmio 2024-11-24 11:26:14 +01:00
DeaTh-G
3dcf708c4d fix vaddsws implementation 2024-11-24 11:26:14 +01:00
DeaTh-G
7a3db6837f add remaining altivec instructions 2024-11-24 11:26:14 +01:00
DeaTh-G
a382cd5653 add vpkuhus implementation 2024-11-24 11:26:14 +01:00
DeaTh-G
c8ecd79c99 Fix instruction implementations based on unit tests 2024-11-24 11:26:14 +01:00
DeaTh-G
e33e0af159 add more basic instructions 2024-11-24 11:26:14 +01:00
DeaTh-G
db477c131d Fix indexing on certain instructions 2024-11-24 11:26:13 +01:00
DeaTh-G
cdfa0907fd Add more instructions regarding Bakugan Battle Brawlers 2024-11-24 11:26:13 +01:00
80 changed files with 1272 additions and 3281 deletions

10
.gitignore vendored
View File

@@ -397,13 +397,3 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
# IntelliJ IDEs
.idea/
# macOS metadata
*.DS_Store
# CMake Files
**/cmake-build-debug
**/CMakeCache.txt

15
.gitmodules vendored
View File

@@ -1,15 +0,0 @@
[submodule "thirdparty/xxHash"]
path = thirdparty/xxHash
url = https://github.com/Cyan4973/xxHash.git
[submodule "thirdparty/fmt"]
path = thirdparty/fmt
url = https://github.com/fmtlib/fmt.git
[submodule "thirdparty/tomlplusplus"]
path = thirdparty/tomlplusplus
url = https://github.com/marzer/tomlplusplus.git
[submodule "thirdparty/libmspack"]
path = thirdparty/libmspack
url = https://github.com/kyz/libmspack
[submodule "thirdparty/tiny-AES-c"]
path = thirdparty/tiny-AES-c
url = https://github.com/kokke/tiny-AES-c.git

View File

@@ -1,8 +1,6 @@
cmake_minimum_required (VERSION 3.20) cmake_minimum_required (VERSION 3.20)
set(THIRDPARTY_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty) set(THIRDPARTY_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD 17)
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF)
# Enable Hot Reload for MSVC compilers if supported. # Enable Hot Reload for MSVC compilers if supported.
@@ -13,18 +11,35 @@ endif()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_subdirectory(${THIRDPARTY_ROOT}) include("cmake/bin2h.cmake")
set(XENONANALYSE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/XenonAnalyse) include(FetchContent)
set(XENONUTILS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/XenonUtils) FetchContent_Declare(
set(XENONRECOMP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/XenonRecomp) tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
GIT_TAG v3.4.0
)
FetchContent_Declare(
xxHash
GIT_REPOSITORY https://github.com/Cyan4973/xxHash.git
GIT_TAG v0.8.2
SOURCE_SUBDIR "cmake_unofficial"
)
FetchContent_MakeAvailable(tomlplusplus)
FetchContent_MakeAvailable(xxHash)
project ("XenonRecomp-ALL") add_subdirectory(${THIRDPARTY_ROOT}/disasm)
set(POWERANALYSE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/PowerAnalyse)
set(POWERRECOMP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/PowerRecomp)
set(POWERUTILS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/PowerUtils)
add_subdirectory(${XENONANALYSE_ROOT}) project ("PowerRecomp-ALL")
add_subdirectory(${XENONRECOMP_ROOT})
add_subdirectory(${XENONUTILS_ROOT})
# Only tests if this is the top level project add_subdirectory(${POWERANALYSE_ROOT})
add_subdirectory(${POWERRECOMP_ROOT})
add_subdirectory(${POWERUTILS_ROOT})
# Only build sample and tests if this is the top level project
if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
add_subdirectory(XenonTests) add_subdirectory(PowerSample)
add_subdirectory(PowerTests)
endif() endif()

View File

@@ -1,21 +0,0 @@
# 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.

View File

@@ -0,0 +1,11 @@
# cmake_minimum_required (VERSION 3.16)
project("PowerAnalyse")
add_executable(PowerAnalyse "main.cpp" "function.h" "function.cpp")
add_library(LibPowerAnalyse "function.h" "function.cpp")
target_include_directories(LibPowerAnalyse PUBLIC .)
target_link_libraries(LibPowerAnalyse PUBLIC PowerUtils)
target_link_libraries(PowerAnalyse PRIVATE PowerUtils)

View File

@@ -4,7 +4,6 @@
#include <bit> #include <bit>
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <byteswap.h>
size_t Function::SearchBlock(size_t address) const size_t Function::SearchBlock(size_t address) const
{ {
@@ -64,7 +63,7 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
// TODO: Branch fallthrough // TODO: Branch fallthrough
for (; data <= dataEnd ; ++data) for (; data <= dataEnd ; ++data)
{ {
const size_t addr = base + ((data - dataStart) * sizeof(*data)); const auto addr = base + ((data - dataStart) * sizeof(*data));
if (blockStack.empty()) if (blockStack.empty())
{ {
break; // it's hideover break; // it's hideover
@@ -72,11 +71,11 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
auto& curBlock = blocks[blockStack.back()]; auto& curBlock = blocks[blockStack.back()];
DEBUG(const auto blockBase = curBlock.base); DEBUG(const auto blockBase = curBlock.base);
const uint32_t instruction = ByteSwap(*data); const auto instruction = std::byteswap(*data);
const uint32_t op = PPC_OP(instruction); const auto op = PPC_OP(instruction);
const uint32_t xop = PPC_XOP(instruction); const auto xop = PPC_XOP(instruction);
const uint32_t isLink = PPC_BL(instruction); // call const auto isLink = PPC_BL(instruction); // call
ppc_insn insn; ppc_insn insn;
ppc::Disassemble(data, addr, insn); ppc::Disassemble(data, addr, insn);
@@ -104,13 +103,13 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
// TODO: Handle absolute branches? // TODO: Handle absolute branches?
assert(!PPC_BA(instruction)); assert(!PPC_BA(instruction));
const size_t branchDest = addr + PPC_BD(instruction); const auto branchDest = addr + PPC_BD(instruction);
// true/false paths // true/false paths
// left block: false case // left block: false case
// right block: true case // right block: true case
const size_t lBase = (addr - base) + 4; const auto lBase = (addr - base) + 4;
const size_t rBase = (addr + PPC_BD(instruction)) - base; const auto rBase = (addr + PPC_BD(instruction)) - base;
// these will be -1 if it's our first time seeing these blocks // these will be -1 if it's our first time seeing these blocks
auto lBlock = fn.SearchBlock(base + lBase); auto lBlock = fn.SearchBlock(base + lBase);
@@ -125,7 +124,7 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
blockStack.emplace_back(lBlock); blockStack.emplace_back(lBlock);
} }
size_t rBlock = fn.SearchBlock(base + rBase); auto rBlock = fn.SearchBlock(base + rBase);
if (rBlock == -1) if (rBlock == -1)
{ {
blocks.emplace_back(branchDest - base, 0); blocks.emplace_back(branchDest - base, 0);
@@ -146,10 +145,10 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
if (op == PPC_OP_B) if (op == PPC_OP_B)
{ {
assert(!PPC_BA(instruction)); assert(!PPC_BA(instruction));
const size_t branchDest = addr + PPC_BI(instruction); const auto branchDest = addr + PPC_BI(instruction);
const size_t branchBase = branchDest - base; const auto branchBase = branchDest - base;
const size_t branchBlock = fn.SearchBlock(branchDest); const auto branchBlock = fn.SearchBlock(branchDest);
if (branchDest < base) if (branchDest < base)
{ {
@@ -159,8 +158,8 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
} }
// carry over our projection if blocks are next to each other // carry over our projection if blocks are next to each other
const bool isContinuous = branchBase == curBlock.base + curBlock.size; const auto isContinuous = branchBase == curBlock.base + curBlock.size;
size_t sizeProjection = (size_t)-1; auto sizeProjection = (size_t)-1;
if (curBlock.projectedSize != -1 && isContinuous) if (curBlock.projectedSize != -1 && isContinuous)
{ {
@@ -181,12 +180,12 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
else if (op == PPC_OP_CTR) else if (op == PPC_OP_CTR)
{ {
// 5th bit of BO tells cpu to ignore the counter, which is a blr/bctr otherwise it's conditional // 5th bit of BO tells cpu to ignore the counter, which is a blr/bctr otherwise it's conditional
const bool conditional = !(PPC_BO(instruction) & 0x10); const auto conditional = !(PPC_BO(instruction) & 0x10);
if (conditional) if (conditional)
{ {
// right block's just going to return // right block's just going to return
const size_t lBase = (addr - base) + 4; const auto lBase = (addr - base) + 4;
size_t lBlock = fn.SearchBlock(lBase); auto lBlock = fn.SearchBlock(lBase);
if (lBlock == -1) if (lBlock == -1)
{ {
blocks.emplace_back(lBase, 0); blocks.emplace_back(lBase, 0);
@@ -213,7 +212,7 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
// Sort and invalidate discontinuous blocks // Sort and invalidate discontinuous blocks
if (blocks.size() > 1) if (blocks.size() > 1)
{ {
std::sort(blocks.begin(), blocks.end(), [](const Block& a, const Block& b) std::ranges::sort(blocks, [](const Block& a, const Block& b)
{ {
return a.base < b.base; return a.base < b.base;
}); });

28
PowerAnalyse/function.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <vector>
#ifdef _DEBUG(X)
#define DEBUG(X) X
#else
#define DEBUG(X)
#endif
struct Function
{
struct Block
{
size_t base{};
size_t size{};
DEBUG(size_t parent{});
// scratch
size_t projectedSize{ static_cast<size_t>(-1) };
};
size_t base{};
size_t size{};
std::vector<Block> blocks{};
size_t SearchBlock(size_t address) const;
static Function Analyze(const void* code, size_t size, size_t base);
};

View File

@@ -1,11 +1,10 @@
#include <cassert> #include <cassert>
#include <iterator>
#include <file.h> #include <file.h>
#include <disasm.h> #include <disasm.h>
#include <image.h> #include <image.h>
#include <xbox.h>
#include <fmt/core.h>
#include "function.h" #include "function.h"
#include <print>
#include <xbox.h>
#define SWITCH_ABSOLUTE 0 #define SWITCH_ABSOLUTE 0
#define SWITCH_COMPUTED 1 #define SWITCH_COMPUTED 1
@@ -140,7 +139,7 @@ void MakeMask(const uint32_t* instructions, size_t count)
for (size_t i = 0; i < count; i++) for (size_t i = 0; i < count; i++)
{ {
ppc::Disassemble(&instructions[i], 0, insn); ppc::Disassemble(&instructions[i], 0, insn);
fmt::println("0x{:X}, // {}", ByteSwap(insn.opcode->opcode | (insn.instruction & insn.opcode->mask)), insn.opcode->name); std::println("0x{:X}, // {}", std::byteswap(insn.opcode->opcode | (insn.instruction & insn.opcode->mask)), insn.opcode->name);
} }
} }
@@ -172,25 +171,32 @@ void* SearchMask(const void* source, const uint32_t* compare, size_t compareCoun
return nullptr; return nullptr;
} }
static std::string out; int main()
template<class... Args>
static void println(fmt::format_string<Args...> fmt, Args&&... args)
{ {
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...)); const auto file = LoadFile("private/default.xex").value();
out += '\n'; auto image = Image::ParseImage(file.data(), file.size()).value();
};
int main(int argc, char** argv) std::string out;
{ auto println = [&]<class... Args>(std::format_string<Args...> fmt, Args&&... args)
if (argc < 3)
{ {
printf("Usage: XenonAnalyse [input XEX file path] [output jump table TOML file path]"); std::vformat_to(std::back_inserter(out), fmt.get(), std::make_format_args(args...));
return EXIT_SUCCESS; out += '\n';
} };
//for (const auto& section : image.sections)
//{
// image.symbols.emplace(section.name, section.base, section.size, Symbol_Section);
//}
const auto file = LoadFile(argv[1]); // MakeMask((uint32_t*)image.Find(0x82C40D84), 6);
auto image = Image::ParseImage(file.data(), file.size());
//auto data = "\x4D\x99\x00\x20";
//auto data2 = std::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);
auto printTable = [&](const SwitchTable& table) auto printTable = [&](const SwitchTable& table)
{ {
@@ -210,7 +216,27 @@ int main(int argc, char** argv)
std::vector<SwitchTable> switches{}; std::vector<SwitchTable> switches{};
println("# Generated by XenonAnalyse"); 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);
}
auto scanPattern = [&](uint32_t* pattern, size_t count, size_t type) auto scanPattern = [&](uint32_t* pattern, size_t count, size_t type)
{ {
@@ -235,7 +261,7 @@ int main(int argc, char** argv)
table.type = type; table.type = type;
ScanTable((uint32_t*)data, base + (data - dataStart), table); ScanTable((uint32_t*)data, base + (data - dataStart), table);
// fmt::println("{:X} ; jmptable - {}", base + (data - dataStart), table.labels.size()); // std::println("{:X} ; jmptable - {}", base + (data - dataStart), table.labels.size());
if (table.base != 0) if (table.base != 0)
{ {
ReadTable(image, table); ReadTable(image, table);
@@ -305,8 +331,122 @@ int main(int argc, char** argv)
scanPattern(offsetSwitch, std::size(offsetSwitch), SWITCH_BYTEOFFSET); scanPattern(offsetSwitch, std::size(offsetSwitch), SWITCH_BYTEOFFSET);
scanPattern(wordOffsetSwitch, std::size(wordOffsetSwitch), SWITCH_SHORTOFFSET); scanPattern(wordOffsetSwitch, std::size(wordOffsetSwitch), SWITCH_SHORTOFFSET);
std::ofstream f(argv[2]); FILE* f = fopen("out/switches.toml", "w");
f.write(out.data(), out.size()); fwrite(out.data(), 1, out.size(), f);
fclose(f);
return EXIT_SUCCESS; uint32_t cxxFrameHandler = std::byteswap(0x831B1C90);
uint32_t cSpecificFrameHandler = std::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(std::format("sub_{:X}", 0x82EF5D78), 0x82EF5D78, 0x3F8, Symbol_Function);
// auto fnd = Function::Analyze(image.Find(0x82C40D58), image.size, 0x82C40D58);
std::vector<Function> 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 = std::byteswap(fn.BeginAddress);
fn.Data = std::byteswap(fn.Data);
auto& f = functions.emplace_back();
f.base = fn.BeginAddress;
f.size = fn.FunctionLength * 4;
if (f.base == 0x82BD7420)
{
__debugbreak();
}
image.symbols.emplace(std::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function);
}
auto sym = image.symbols.find(0x82BD7420);
std::vector<Function> 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;
std::println("sub_{:X}", missingFn.base);
}
}
}
//ppc_insn insn;
//uint8_t c[4] = { 0x10, 0x00, 0x59, 0xC3 };
//ppc::Disassemble(c, 0x831D6C64, insn);
//std::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);
std::println("FUNCTIONS");
for (const auto& fn : functions)
{
std::println("\tsub_{:X}", fn.base);
}
std::println("");
std::println("SECTIONS");
for (const auto& section : image.sections)
{
std::printf("Section %.8s\n", section.name.c_str());
std::printf("\t%X-%X\n", section.base, section.base + section.size);
}
std::println("");
return 0;
} }

View File

@@ -0,0 +1,15 @@
cmake_minimum_required (VERSION 3.8)
project("PowerRecomp")
BIN2H(SOURCE_FILE ${POWERUTILS_ROOT}/ppc_context.h HEADER_FILE "generated/ppc_context.gen.h" ARRAY_TYPE "char" VARIABLE_NAME "g_PPCContextText")
add_executable(PowerRecomp "main.cpp" "pch.h" "recompiler.cpp" "recompiler.h" "test_recompiler.cpp" "test_recompiler.h" "recompiler_config.h" "recompiler_config.cpp")
target_precompile_headers(PowerRecomp PUBLIC "pch.h")
target_link_libraries(PowerRecomp PRIVATE LibPowerAnalyse tomlplusplus::tomlplusplus xxHash::xxhash)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_options(PowerRecomp PRIVATE -Wno-switch -Wno-unused-variable)
endif()
target_compile_definitions(PowerRecomp PRIVATE _CRT_SECURE_NO_WARNINGS)

3
PowerRecomp/generated/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*
!.gitignore

34
PowerRecomp/main.cpp Normal file
View File

@@ -0,0 +1,34 @@
#include "pch.h"
#include "test_recompiler.h"
int main(int argc, char* argv[])
{
const char* path =
#ifdef CONFIG_FILE_PATH
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";
}
recompiler.Recompile();
}
else
{
TestRecompiler::RecompileTests(path, argv[2]);
}
return 0;
}

View File

@@ -1,19 +1,22 @@
#pragma once #pragma once
#include <algorithm> // Workaround for the intellisense for some reason not seeing C++23 features
#ifdef __INTELLISENSE__
#undef __cplusplus
#define __cplusplus 202302L
#endif
#include <cassert> #include <cassert>
#include <cstddef>
#include <charconv>
#include <disasm.h> #include <disasm.h>
#include <file.h> #include <file.h>
#include <filesystem> #include <filesystem>
#include <fstream> #include <format>
#include <function.h> #include <function.h>
#include <image.h> #include <image.h>
#include <print>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <xbox.h> #include <xbox.h>
#include <xxhash.h> #include <xxhash.h>
#include <fmt/core.h> #include "generated/ppc_context.gen.h"
#include <xmmintrin.h>

View File

@@ -1,6 +1,5 @@
#include "pch.h" #include "pch.h"
#include "recompiler.h" #include "recompiler.h"
#include <xex_patcher.h>
static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop) static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop)
{ {
@@ -10,87 +9,12 @@ static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop)
return mstart <= mstop ? value : ~value; return mstart <= mstop ? value : ~value;
} }
bool Recompiler::LoadConfig(const std::string_view& configFilePath) void Recompiler::LoadConfig(const std::string_view& configFilePath)
{ {
config.Load(configFilePath); config.Load(configFilePath);
std::vector<uint8_t> file; const auto file = LoadFile((config.directoryPath + config.filePath).c_str()).value();
if (!config.patchedFilePath.empty()) image = Image::ParseImage(file.data(), file.size()).value();
file = LoadFile((config.directoryPath + config.patchedFilePath).c_str());
if (file.empty())
{
file = LoadFile((config.directoryPath + config.filePath).c_str());
if (!config.patchFilePath.empty())
{
const auto patchFile = LoadFile((config.directoryPath + config.patchFilePath).c_str());
if (!patchFile.empty())
{
std::vector<uint8_t> outBytes;
auto result = XexPatcher::apply(file.data(), file.size(), patchFile.data(), patchFile.size(), outBytes, false);
if (result == XexPatcher::Result::Success)
{
std::exchange(file, outBytes);
if (!config.patchedFilePath.empty())
{
std::ofstream stream(config.directoryPath + config.patchedFilePath, std::ios::binary);
if (stream.good())
{
stream.write(reinterpret_cast<const char*>(file.data()), file.size());
stream.close();
}
}
}
else
{
fmt::print("ERROR: Unable to apply the patch file, ");
switch (result)
{
case XexPatcher::Result::XexFileUnsupported:
fmt::println("XEX file unsupported");
break;
case XexPatcher::Result::XexFileInvalid:
fmt::println("XEX file invalid");
break;
case XexPatcher::Result::PatchFileInvalid:
fmt::println("patch file invalid");
break;
case XexPatcher::Result::PatchIncompatible:
fmt::println("patch file incompatible");
break;
case XexPatcher::Result::PatchFailed:
fmt::println("patch failed");
break;
case XexPatcher::Result::PatchUnsupported:
fmt::println("patch unsupported");
break;
default:
fmt::println("reason unknown");
break;
}
return false;
}
}
else
{
fmt::println("ERROR: Unable to load the patch file");
return false;
}
}
}
image = Image::ParseImage(file.data(), file.size());
return true;
} }
void Recompiler::Analyse() void Recompiler::Analyse()
@@ -99,79 +23,55 @@ void Recompiler::Analyse()
{ {
if (i < 32) if (i < 32)
{ {
if (config.restGpr14Address != 0) auto& restgpr = functions.emplace_back();
{ restgpr.base = config.restGpr14Address + (i - 14) * 4;
auto& restgpr = functions.emplace_back(); restgpr.size = (32 - i) * 4 + 12;
restgpr.base = config.restGpr14Address + (i - 14) * 4; image.symbols.emplace(std::format("__restgprlr_{}", i), restgpr.base, restgpr.size, Symbol_Function);
restgpr.size = (32 - i) * 4 + 12;
image.symbols.emplace(Symbol{ fmt::format("__restgprlr_{}", i), restgpr.base, restgpr.size, Symbol_Function });
}
if (config.saveGpr14Address != 0) auto& savegpr = functions.emplace_back();
{ savegpr.base = config.saveGpr14Address + (i - 14) * 4;
auto& savegpr = functions.emplace_back(); savegpr.size = (32 - i) * 4 + 8;
savegpr.base = config.saveGpr14Address + (i - 14) * 4; image.symbols.emplace(std::format("__savegprlr_{}", i), savegpr.base, savegpr.size, Symbol_Function);
savegpr.size = (32 - i) * 4 + 8;
image.symbols.emplace(fmt::format("__savegprlr_{}", i), savegpr.base, savegpr.size, Symbol_Function);
}
if (config.restFpr14Address != 0) auto& restfpr = functions.emplace_back();
{ restfpr.base = config.restFpr14Address + (i - 14) * 4;
auto& restfpr = functions.emplace_back(); restfpr.size = (32 - i) * 4 + 4;
restfpr.base = config.restFpr14Address + (i - 14) * 4; image.symbols.emplace(std::format("__restfpr_{}", i), restfpr.base, restfpr.size, Symbol_Function);
restfpr.size = (32 - i) * 4 + 4;
image.symbols.emplace(fmt::format("__restfpr_{}", i), restfpr.base, restfpr.size, Symbol_Function);
}
if (config.saveFpr14Address != 0) auto& savefpr = functions.emplace_back();
{ savefpr.base = config.saveFpr14Address + (i - 14) * 4;
auto& savefpr = functions.emplace_back(); savefpr.size = (32 - i) * 4 + 4;
savefpr.base = config.saveFpr14Address + (i - 14) * 4; image.symbols.emplace(std::format("__savefpr_{}", i), savefpr.base, savefpr.size, Symbol_Function);
savefpr.size = (32 - i) * 4 + 4;
image.symbols.emplace(fmt::format("__savefpr_{}", i), savefpr.base, savefpr.size, Symbol_Function);
}
if (config.restVmx14Address != 0) auto& restvmx = functions.emplace_back();
{ restvmx.base = config.restVmx14Address + (i - 14) * 8;
auto& restvmx = functions.emplace_back(); restvmx.size = (32 - i) * 8 + 4;
restvmx.base = config.restVmx14Address + (i - 14) * 8; image.symbols.emplace(std::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
restvmx.size = (32 - i) * 8 + 4;
image.symbols.emplace(fmt::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
}
if (config.saveVmx14Address != 0) auto& savevmx = functions.emplace_back();
{ savevmx.base = config.saveVmx14Address + (i - 14) * 8;
auto& savevmx = functions.emplace_back(); savevmx.size = (32 - i) * 8 + 4;
savevmx.base = config.saveVmx14Address + (i - 14) * 8; image.symbols.emplace(std::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
savevmx.size = (32 - i) * 8 + 4;
image.symbols.emplace(fmt::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
}
} }
if (i >= 64) if (i >= 64)
{ {
if (config.restVmx64Address != 0) auto& restvmx = functions.emplace_back();
{ restvmx.base = config.restVmx64Address + (i - 64) * 8;
auto& restvmx = functions.emplace_back(); restvmx.size = (128 - i) * 8 + 4;
restvmx.base = config.restVmx64Address + (i - 64) * 8; image.symbols.emplace(std::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
restvmx.size = (128 - i) * 8 + 4;
image.symbols.emplace(fmt::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
}
if (config.saveVmx64Address != 0) auto& savevmx = functions.emplace_back();
{ savevmx.base = config.saveVmx64Address + (i - 64) * 8;
auto& savevmx = functions.emplace_back(); savevmx.size = (128 - i) * 8 + 4;
savevmx.base = config.saveVmx64Address + (i - 64) * 8; image.symbols.emplace(std::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
savevmx.size = (128 - i) * 8 + 4;
image.symbols.emplace(fmt::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
}
} }
} }
for (auto& [address, size] : config.functions) for (auto& [address, size] : config.functions)
{ {
functions.emplace_back(address, size); functions.emplace_back(address, size);
image.symbols.emplace(fmt::format("sub_{:X}", address), address, size, Symbol_Function); image.symbols.emplace(std::format("sub_{:X}", address), address, size, Symbol_Function);
} }
auto& pdata = *image.Find(".pdata"); auto& pdata = *image.Find(".pdata");
@@ -180,8 +80,8 @@ void Recompiler::Analyse()
for (size_t i = 0; i < count; i++) for (size_t i = 0; i < count; i++)
{ {
auto fn = pf[i]; auto fn = pf[i];
fn.BeginAddress = ByteSwap(fn.BeginAddress); fn.BeginAddress = std::byteswap(fn.BeginAddress);
fn.Data = ByteSwap(fn.Data); fn.Data = std::byteswap(fn.Data);
if (image.symbols.find(fn.BeginAddress) == image.symbols.end()) if (image.symbols.find(fn.BeginAddress) == image.symbols.end())
{ {
@@ -189,7 +89,7 @@ void Recompiler::Analyse()
f.base = fn.BeginAddress; f.base = fn.BeginAddress;
f.size = fn.FunctionLength * 4; f.size = fn.FunctionLength * 4;
image.symbols.emplace(fmt::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function); image.symbols.emplace(std::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function);
} }
} }
@@ -205,7 +105,7 @@ void Recompiler::Analyse()
while (data < dataEnd) while (data < dataEnd)
{ {
uint32_t insn = ByteSwap(*(uint32_t*)data); uint32_t insn = std::byteswap(*(uint32_t*)data);
if (PPC_OP(insn) == PPC_OP_B && PPC_BL(insn)) if (PPC_OP(insn) == PPC_OP_B && PPC_BL(insn))
{ {
size_t address = base + (data - section.data) + PPC_BI(insn); size_t address = base + (data - section.data) + PPC_BI(insn);
@@ -214,7 +114,7 @@ void Recompiler::Analyse()
{ {
auto data = section.data + address - section.base; auto data = section.data + address - section.base;
auto& fn = functions.emplace_back(Function::Analyze(data, section.base + section.size - address, address)); auto& fn = functions.emplace_back(Function::Analyze(data, section.base + section.size - address, address));
image.symbols.emplace(fmt::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function); image.symbols.emplace(std::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function);
} }
} }
data += 4; data += 4;
@@ -224,7 +124,7 @@ void Recompiler::Analyse()
while (data < dataEnd) while (data < dataEnd)
{ {
auto invalidInstr = config.invalidInstructions.find(ByteSwap(*(uint32_t*)data)); auto invalidInstr = config.invalidInstructions.find(std::byteswap(*(uint32_t*)data));
if (invalidInstr != config.invalidInstructions.end()) if (invalidInstr != config.invalidInstructions.end())
{ {
base += invalidInstr->second; base += invalidInstr->second;
@@ -243,7 +143,7 @@ void Recompiler::Analyse()
else else
{ {
auto& fn = functions.emplace_back(Function::Analyze(data, dataEnd - data, base)); auto& fn = functions.emplace_back(Function::Analyze(data, dataEnd - data, base));
image.symbols.emplace(fmt::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function); image.symbols.emplace(std::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function);
base += fn.size; base += fn.size;
data += fn.size; data += fn.size;
@@ -272,9 +172,9 @@ bool Recompiler::Recompile(
(config.nonVolatileRegistersAsLocalVariables && index >= 14)) (config.nonVolatileRegistersAsLocalVariables && index >= 14))
{ {
localVariables.r[index] = true; localVariables.r[index] = true;
return fmt::format("r{}", index); return std::format("r{}", index);
} }
return fmt::format("ctx.r{}", index); return std::format("ctx.r{}", index);
}; };
auto f = [&](size_t index) auto f = [&](size_t index)
@@ -283,9 +183,9 @@ bool Recompiler::Recompile(
(config.nonVolatileRegistersAsLocalVariables && index >= 14)) (config.nonVolatileRegistersAsLocalVariables && index >= 14))
{ {
localVariables.f[index] = true; localVariables.f[index] = true;
return fmt::format("f{}", index); return std::format("f{}", index);
} }
return fmt::format("ctx.f{}", index); return std::format("ctx.f{}", index);
}; };
auto v = [&](size_t index) auto v = [&](size_t index)
@@ -294,9 +194,9 @@ bool Recompiler::Recompile(
(config.nonVolatileRegistersAsLocalVariables && ((index >= 14 && index <= 31) || (index >= 64 && index <= 127)))) (config.nonVolatileRegistersAsLocalVariables && ((index >= 14 && index <= 31) || (index >= 64 && index <= 127))))
{ {
localVariables.v[index] = true; localVariables.v[index] = true;
return fmt::format("v{}", index); return std::format("v{}", index);
} }
return fmt::format("ctx.v{}", index); return std::format("ctx.v{}", index);
}; };
auto cr = [&](size_t index) auto cr = [&](size_t index)
@@ -304,9 +204,9 @@ bool Recompiler::Recompile(
if (config.crRegistersAsLocalVariables) if (config.crRegistersAsLocalVariables)
{ {
localVariables.cr[index] = true; localVariables.cr[index] = true;
return fmt::format("cr{}", index); return std::format("cr{}", index);
} }
return fmt::format("ctx.cr{}", index); return std::format("ctx.cr{}", index);
}; };
auto ctr = [&]() auto ctr = [&]()
@@ -378,9 +278,8 @@ bool Recompiler::Recompile(
else if (address == config.setJmpAddress) else if (address == config.setJmpAddress)
{ {
println("\t{} = ctx;", env()); println("\t{} = ctx;", env());
println("\t{}.s64 = setjmp(*reinterpret_cast<jmp_buf*>(base + {}.u32));", temp(), r(3)); println("\t{}.s64 = setjmp(*reinterpret_cast<jmp_buf*>(base + {}.u32));", r(3), r(3));
println("\tif ({}.s64 != 0) ctx = {};", temp(), env()); println("\tif ({}.s64 != 0) ctx = {};", r(3), env());
println("\t{} = {};", r(3), temp());
} }
else else
{ {
@@ -388,7 +287,7 @@ bool Recompiler::Recompile(
if (targetSymbol != image.symbols.end() && targetSymbol->address == address && targetSymbol->type == Symbol_Function) if (targetSymbol != image.symbols.end() && targetSymbol->address == address && targetSymbol->type == Symbol_Function)
{ {
if (config.nonVolatileRegistersAsLocalVariables && (targetSymbol->name.find("__rest") == 0 || targetSymbol->name.find("__save") == 0)) if (config.nonVolatileRegistersAsLocalVariables && (targetSymbol->name.starts_with("__rest") || targetSymbol->name.starts_with("__save")))
{ {
// print nothing // print nothing
} }
@@ -434,88 +333,84 @@ bool Recompiler::Recompile(
}; };
auto midAsmHook = config.midAsmHooks.find(base); auto midAsmHook = config.midAsmHooks.find(base);
if (midAsmHook != config.midAsmHooks.end())
{
bool returnsBool = midAsmHook->second.returnOnFalse || midAsmHook->second.returnOnTrue ||
midAsmHook->second.jumpAddressOnFalse != NULL || midAsmHook->second.jumpAddressOnTrue != NULL;
auto printMidAsmHook = [&]() print("\t");
if (returnsBool)
print("if (");
print("{}(", midAsmHook->second.name);
for (auto& reg : midAsmHook->second.registers)
{ {
bool returnsBool = midAsmHook->second.returnOnFalse || midAsmHook->second.returnOnTrue || if (out.back() != '(')
midAsmHook->second.jumpAddressOnFalse != NULL || midAsmHook->second.jumpAddressOnTrue != NULL; out += ", ";
print("\t"); switch (reg[0])
if (returnsBool)
print("if (");
print("{}(", midAsmHook->second.name);
for (auto& reg : midAsmHook->second.registers)
{ {
if (out.back() != '(') case 'c':
out += ", "; if (reg == "ctr")
out += ctr();
else
out += cr(std::atoi(reg.c_str() + 2));
break;
switch (reg[0]) case 'x':
{ out += xer();
case 'c': break;
if (reg == "ctr")
out += ctr();
else
out += cr(std::atoi(reg.c_str() + 2));
break;
case 'x': case 'r':
out += xer(); if (reg == "reserved")
break; out += reserved();
else
out += r(std::atoi(reg.c_str() + 1));
break;
case 'r': case 'f':
if (reg == "reserved") if (reg == "fpscr")
out += reserved(); out += "ctx.fpscr";
else else
out += r(std::atoi(reg.c_str() + 1)); out += f(std::atoi(reg.c_str() + 1));
break; break;
case 'f': case 'v':
if (reg == "fpscr") out += v(std::atoi(reg.c_str() + 1));
out += "ctx.fpscr"; break;
else
out += f(std::atoi(reg.c_str() + 1));
break;
case 'v':
out += v(std::atoi(reg.c_str() + 1));
break;
}
} }
}
if (returnsBool) if (returnsBool)
{ {
println(")) {{"); println(")) {{");
if (midAsmHook->second.returnOnTrue) if (midAsmHook->second.returnOnTrue)
println("\t\treturn;"); println("\t\treturn;");
else if (midAsmHook->second.jumpAddressOnTrue != NULL) else if (midAsmHook->second.jumpAddressOnTrue != NULL)
println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnTrue); println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnTrue);
println("\t}}"); println("\t}}");
println("\telse {{"); println("\telse {{");
if (midAsmHook->second.returnOnFalse) if (midAsmHook->second.returnOnFalse)
println("\t\treturn;"); println("\t\treturn;");
else if (midAsmHook->second.jumpAddressOnFalse != NULL) else if (midAsmHook->second.jumpAddressOnFalse != NULL)
println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnFalse); println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnFalse);
println("\t}}"); println("\t}}");
} }
else else
{ {
println(");"); println(");");
if (midAsmHook->second.ret) if (midAsmHook->second.ret)
println("\treturn;"); println("\treturn;");
else if (midAsmHook->second.jumpAddress != NULL) else if (midAsmHook->second.jumpAddress != NULL)
println("\tgoto loc_{:X};", midAsmHook->second.jumpAddress); println("\tgoto loc_{:X};", midAsmHook->second.jumpAddress);
} }
}; }
if (midAsmHook != config.midAsmHooks.end() && !midAsmHook->second.afterInstruction)
printMidAsmHook();
int id = insn.opcode->id; int id = insn.opcode->id;
@@ -633,7 +528,7 @@ bool Recompiler::Recompile(
if (label < fn.base || label >= fn.base + fn.size) if (label < fn.base || label >= fn.base + fn.size)
{ {
println("\t\t// ERROR: 0x{:X}", label); println("\t\t// ERROR: 0x{:X}", label);
fmt::println("ERROR: Switch case at {:X} is trying to jump outside function: {:X}", base, label); std::println("ERROR: Switch case at {:X} is trying to jump outside function: {:X}", base, label);
println("\t\treturn;"); println("\t\treturn;");
} }
else else
@@ -745,7 +640,7 @@ bool Recompiler::Recompile(
break; break;
case PPC_INST_BLRL: case PPC_INST_BLRL:
println("__builtin_debugtrap();"); println("__debugbreak();");
break; break;
case PPC_INST_BLT: case PPC_INST_BLT:
@@ -822,11 +717,11 @@ bool Recompiler::Recompile(
break; break;
case PPC_INST_CNTLZD: case PPC_INST_CNTLZD:
println("\t{0}.u64 = {1}.u64 == 0 ? 64 : __builtin_clzll({1}.u64);", r(insn.operands[0]), r(insn.operands[1])); println("\t{}.u64 = __lzcnt64({}.u64);", r(insn.operands[0]), r(insn.operands[1]));
break; break;
case PPC_INST_CNTLZW: case PPC_INST_CNTLZW:
println("\t{0}.u64 = {1}.u32 == 0 ? 32 : __builtin_clz({1}.u32);", r(insn.operands[0]), r(insn.operands[1])); println("\t{}.u64 = __lzcnt({}.u32);", r(insn.operands[0]), r(insn.operands[1]));
break; break;
case PPC_INST_CROR: case PPC_INST_CROR:
@@ -1492,43 +1387,43 @@ bool Recompiler::Recompile(
break; break;
case PPC_INST_RLDICL: case PPC_INST_RLDICL:
println("\t{}.u64 = __builtin_rotateleft64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(insn.operands[3], 63)); println("\t{}.u64 = _rotl64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(insn.operands[3], 63));
break; break;
case PPC_INST_RLDICR: case PPC_INST_RLDICR:
println("\t{}.u64 = __builtin_rotateleft64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(0, insn.operands[3])); println("\t{}.u64 = _rotl64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(0, insn.operands[3]));
break; break;
case PPC_INST_RLDIMI: case PPC_INST_RLDIMI:
{ {
const uint64_t mask = ComputeMask(insn.operands[3], ~insn.operands[2]); const uint64_t mask = ComputeMask(insn.operands[3], ~insn.operands[2]);
println("\t{}.u64 = (__builtin_rotateleft64({}.u64, {}) & 0x{:X}) | ({}.u64 & 0x{:X});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], mask, r(insn.operands[0]), ~mask); println("\t{}.u64 = (_rotl64({}.u64, {}) & 0x{:X}) | ({}.u64 & 0x{:X});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], mask, r(insn.operands[0]), ~mask);
break; break;
} }
case PPC_INST_RLWIMI: case PPC_INST_RLWIMI:
{ {
const uint64_t mask = ComputeMask(insn.operands[3] + 32, insn.operands[4] + 32); const uint64_t mask = ComputeMask(insn.operands[3] + 32, insn.operands[4] + 32);
println("\t{}.u64 = (__builtin_rotateleft32({}.u32, {}) & 0x{:X}) | ({}.u64 & 0x{:X});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], mask, r(insn.operands[0]), ~mask); println("\t{}.u64 = (_rotl({}.u32, {}) & 0x{:X}) | ({}.u64 & 0x{:X});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], mask, r(insn.operands[0]), ~mask);
break; break;
} }
case PPC_INST_RLWINM: case PPC_INST_RLWINM:
println("\t{}.u64 = __builtin_rotateleft64({}.u32 | ({}.u64 << 32), {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), r(insn.operands[1]), insn.operands[2], ComputeMask(insn.operands[3] + 32, insn.operands[4] + 32)); println("\t{}.u64 = _rotl64({}.u32 | ({}.u64 << 32), {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), r(insn.operands[1]), insn.operands[2], ComputeMask(insn.operands[3] + 32, insn.operands[4] + 32));
if (strchr(insn.opcode->name, '.')) if (strchr(insn.opcode->name, '.'))
println("\t{}.compare<int32_t>({}.s32, 0, {});", cr(0), r(insn.operands[0]), xer()); println("\t{}.compare<int32_t>({}.s32, 0, {});", cr(0), r(insn.operands[0]), xer());
break; break;
case PPC_INST_ROTLDI: case PPC_INST_ROTLDI:
println("\t{}.u64 = __builtin_rotateleft64({}.u64, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]); println("\t{}.u64 = _rotl64({}.u64, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]);
break; break;
case PPC_INST_ROTLW: case PPC_INST_ROTLW:
println("\t{}.u64 = __builtin_rotateleft32({}.u32, {}.u8 & 0x1F);", r(insn.operands[0]), r(insn.operands[1]), r(insn.operands[2])); println("\t{}.u64 = _rotl({}.u32, {}.u8 & 0x1F);", r(insn.operands[0]), r(insn.operands[1]), r(insn.operands[2]));
break; break;
case PPC_INST_ROTLWI: case PPC_INST_ROTLWI:
println("\t{}.u64 = __builtin_rotateleft32({}.u32, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]); println("\t{}.u64 = _rotl({}.u32, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]);
if (strchr(insn.opcode->name, '.')) if (strchr(insn.opcode->name, '.'))
println("\t{}.compare<int32_t>({}.s32, 0, {});", cr(0), r(insn.operands[0]), xer()); println("\t{}.compare<int32_t>({}.s32, 0, {});", cr(0), r(insn.operands[0]), xer());
break; break;
@@ -1633,10 +1528,10 @@ bool Recompiler::Recompile(
case PPC_INST_STDCX: case PPC_INST_STDCX:
println("\t{}.lt = 0;", cr(0)); println("\t{}.lt = 0;", cr(0));
println("\t{}.gt = 0;", cr(0)); println("\t{}.gt = 0;", cr(0));
print("\t{}.eq = __sync_bool_compare_and_swap(reinterpret_cast<uint64_t*>(base + ", cr(0)); print("\t{}.eq = _InterlockedCompareExchange64(reinterpret_cast<__int64*>(base + ", cr(0));
if (insn.operands[1] != 0) if (insn.operands[1] != 0)
print("{}.u32 + ", r(insn.operands[1])); print("{}.u32 + ", r(insn.operands[1]));
println("{}.u32), {}.s64, __builtin_bswap64({}.s64));", r(insn.operands[2]), reserved(), r(insn.operands[0])); println("{}.u32), __builtin_bswap64({}.s64), {}.s64) == {}.s64;", r(insn.operands[2]), r(insn.operands[0]), reserved(), reserved());
println("\t{}.so = {}.so;", cr(0), xer()); println("\t{}.so = {}.so;", cr(0), xer());
break; break;
@@ -1829,10 +1724,10 @@ bool Recompiler::Recompile(
case PPC_INST_STWCX: case PPC_INST_STWCX:
println("\t{}.lt = 0;", cr(0)); println("\t{}.lt = 0;", cr(0));
println("\t{}.gt = 0;", cr(0)); println("\t{}.gt = 0;", cr(0));
print("\t{}.eq = __sync_bool_compare_and_swap(reinterpret_cast<uint32_t*>(base + ", cr(0)); print("\t{}.eq = _InterlockedCompareExchange(reinterpret_cast<long*>(base + ", cr(0));
if (insn.operands[1] != 0) if (insn.operands[1] != 0)
print("{}.u32 + ", r(insn.operands[1])); print("{}.u32 + ", r(insn.operands[1]));
println("{}.u32), {}.s32, __builtin_bswap32({}.s32));", r(insn.operands[2]), reserved(), r(insn.operands[0])); println("{}.u32), __builtin_bswap32({}.s32), {}.s32) == {}.s32;", r(insn.operands[2]), r(insn.operands[0]), reserved(), reserved());
println("\t{}.so = {}.so;", cr(0), xer()); println("\t{}.so = {}.so;", cr(0), xer());
break; break;
@@ -2037,7 +1932,7 @@ bool Recompiler::Recompile(
case PPC_INST_VCMPBFP: case PPC_INST_VCMPBFP:
case PPC_INST_VCMPBFP128: case PPC_INST_VCMPBFP128:
println("\t__builtin_debugtrap();"); println("\t__debugbreak();");
break; break;
case PPC_INST_VCMPEQFP: case PPC_INST_VCMPEQFP:
@@ -2237,8 +2132,8 @@ bool Recompiler::Recompile(
switch (insn.operands[2]) switch (insn.operands[2])
{ {
case 0: // D3D color case 0: // D3D color
if (insn.operands[3] != 1) if (insn.operands[3] != 1 || insn.operands[4] != 3)
fmt::println("Unexpected D3D color pack instruction at {:X}", base); std::println("Unexpected D3D color pack instruction at {:X}", base);
for (size_t i = 0; i < 4; i++) for (size_t i = 0; i < 4; i++)
{ {
@@ -2247,33 +2142,11 @@ bool Recompiler::Recompile(
println("\t{}.f32[{}] = {}.f32[{}] < 3.0f ? 3.0f : ({}.f32[{}] > {}.f32[{}] ? {}.f32[{}] : {}.f32[{}]);", vTemp(), i, v(insn.operands[1]), i, v(insn.operands[1]), i, vTemp(), i, vTemp(), i, v(insn.operands[1]), i); println("\t{}.f32[{}] = {}.f32[{}] < 3.0f ? 3.0f : ({}.f32[{}] > {}.f32[{}] ? {}.f32[{}] : {}.f32[{}]);", vTemp(), i, v(insn.operands[1]), i, v(insn.operands[1]), i, vTemp(), i, vTemp(), i, v(insn.operands[1]), i);
println("\t{}.u32 {}= uint32_t({}.u8[{}]) << {};", temp(), i == 0 ? "" : "|", vTemp(), i * 4, indices[i] * 8); println("\t{}.u32 {}= uint32_t({}.u8[{}]) << {};", temp(), i == 0 ? "" : "|", vTemp(), i * 4, indices[i] * 8);
} }
println("\t{}.u32[{}] = {}.u32;", v(insn.operands[0]), insn.operands[4], temp()); println("\t{}.u32[3] = {}.u32;", v(insn.operands[0]), temp());
break;
case 5: // float16_4
if (insn.operands[3] != 2 || insn.operands[4] > 2)
fmt::println("Unexpected float16_4 pack instruction at {:X}", base);
for (size_t i = 0; i < 4; i++)
{
// Strip sign from source
println("\t{}.u32 = ({}.u32[{}]&0x7FFFFFFF);", temp(), v(insn.operands[1]), i);
// If |source| is > 65504, clamp output to 0x7FFF, else save 8 exponent bits
println("\t{0}.u8[0] = ({1}.f32 != {1}.f32) || ({1}.f32 > 65504.0f) ? 0xFF : (({2}.u32[{3}]&0x7f800000)>>23);", vTemp(), temp(), v(insn.operands[1]), i);
// If 8 exponent bits were saved, it can only be 0x8E at most
// If saved, save first 10 bits of mantissa
println("\t{}.u16 = {}.u8[0] != 0xFF ? (({}.u32[{}]&0x7FE000)>>13) : 0x0;", temp(), vTemp(), v(insn.operands[1]), i);
// If saved and > 127-15, exponent is converted from 8 to 5-bit by subtracting 0x70
// If saved but not > 127-15, clamp exponent at 0, add 0x400 to mantissa and shift right by (0x71-exponent)
// If right shift is greater than 31 bits, manually clamp mantissa to 0 or else the output of the shift will be wrong
println("\t{0}.u16[{1}] = {2}.u8[0] != 0xFF ? ({2}.u8[0] > 0x70 ? ((({2}.u8[0]-0x70)<<10)+{3}.u16) : (0x71-{2}.u8[0] > 31 ? 0x0 : ((0x400+{3}.u16)>>(0x71-{2}.u8[0])))) : 0x7FFF;", v(insn.operands[0]), i+(2*insn.operands[4]), vTemp(), temp());
// Add back original sign
println("\t{}.u16[{}] |= (({}.u32[{}]&0x80000000)>>16);", v(insn.operands[0]), i+(2*insn.operands[4]), v(insn.operands[1]), i);
}
break; break;
default: default:
println("\t__builtin_debugtrap();"); println("\t__debugbreak();");
break; break;
} }
break; break;
@@ -2524,7 +2397,7 @@ bool Recompiler::Recompile(
break; break;
default: default:
println("\t__builtin_debugtrap();"); println("\t__debugbreak();");
break; break;
} }
break; break;
@@ -2583,12 +2456,9 @@ bool Recompiler::Recompile(
{ {
int lastLine = out.find_last_of('\n', out.size() - 2); int lastLine = out.find_last_of('\n', out.size() - 2);
if (out.find("cr0", lastLine + 1) == std::string::npos && out.find("cr6", lastLine + 1) == std::string::npos) if (out.find("cr0", lastLine + 1) == std::string::npos && out.find("cr6", lastLine + 1) == std::string::npos)
fmt::println("{} at {:X} has RC bit enabled but no comparison was generated", insn.opcode->name, base); std::println("{} at {:X} has RC bit enabled but no comparison was generated", insn.opcode->name, base);
} }
#endif #endif
if (midAsmHook != config.midAsmHooks.end() && midAsmHook->second.afterInstruction)
printMidAsmHook();
return true; return true;
} }
@@ -2604,7 +2474,7 @@ bool Recompiler::Recompile(const Function& fn)
for (size_t addr = base; addr < end; addr += 4) for (size_t addr = base; addr < end; addr += 4)
{ {
const uint32_t instruction = ByteSwap(*(uint32_t*)((char*)data + addr - base)); const uint32_t instruction = std::byteswap(*(uint32_t*)((char*)data + addr - base));
if (!PPC_BL(instruction)) if (!PPC_BL(instruction))
{ {
const size_t op = PPC_OP(instruction); const size_t op = PPC_OP(instruction);
@@ -2689,13 +2559,10 @@ bool Recompiler::Recompile(const Function& fn)
} }
else else
{ {
name = fmt::format("sub_{}", fn.base); name = std::format("sub_{}", fn.base);
} }
#ifdef XENON_RECOMP_USE_ALIAS
println("__attribute__((alias(\"__imp__{}\"))) PPC_WEAK_FUNC({});", name, name); println("__attribute__((alias(\"__imp__{}\"))) PPC_WEAK_FUNC({});", name, name);
#endif
println("PPC_FUNC_IMPL(__imp__{}) {{", name); println("PPC_FUNC_IMPL(__imp__{}) {{", name);
println("\tPPC_FUNC_PROLOGUE();"); println("\tPPC_FUNC_PROLOGUE();");
@@ -2712,7 +2579,7 @@ bool Recompiler::Recompile(const Function& fn)
ppc_insn insn; ppc_insn insn;
while (base < end) while (base < end)
{ {
if (labels.find(base) != labels.end()) if (labels.contains(base))
{ {
println("loc_{:X}:", base); println("loc_{:X}:", base);
@@ -2730,17 +2597,17 @@ bool Recompiler::Recompile(const Function& fn)
println("\t// {}", insn.op_str); println("\t// {}", insn.op_str);
#if 1 #if 1
if (*data != 0) if (*data != 0)
fmt::println("Unable to decode instruction {:X} at {:X}", *data, base); std::println("Unable to decode instruction {:X} at {:X}", *data, base);
#endif #endif
} }
else else
{ {
if (insn.opcode->id == PPC_INST_BCTR && (*(data - 1) == 0x07008038 || *(data - 1) == 0x00000060) && switchTable == config.switchTables.end()) if (insn.opcode->id == PPC_INST_BCTR && (*(data - 1) == 0x07008038 || *(data - 1) == 0x00000060) && switchTable == config.switchTables.end())
fmt::println("Found a switch jump table at {:X} with no switch table entry present", base); std::println("Found a switch jump table at {:X} with no switch table entry present", base);
if (!Recompile(fn, base, insn, data, switchTable, localVariables, csrState)) if (!Recompile(fn, base, insn, data, switchTable, localVariables, csrState))
{ {
fmt::println("Unrecognized instruction at 0x{:X}: {}", base, insn.opcode->name); std::println("Unrecognized instruction at 0x{:X}: {}", base, insn.opcode->name);
allRecompiled = false; allRecompiled = false;
} }
} }
@@ -2751,17 +2618,11 @@ bool Recompiler::Recompile(const Function& fn)
#if 0 #if 0
if (insn.opcode == nullptr || (insn.opcode->id != PPC_INST_B && insn.opcode->id != PPC_INST_BCTR && insn.opcode->id != PPC_INST_BLR)) if (insn.opcode == nullptr || (insn.opcode->id != PPC_INST_B && insn.opcode->id != PPC_INST_BCTR && insn.opcode->id != PPC_INST_BLR))
fmt::println("Function at {:X} ends prematurely with instruction {} at {:X}", fn.base, insn.opcode != nullptr ? insn.opcode->name : "INVALID", base - 4); std::println("Function at {:X} ends prematurely with instruction {} at {:X}", fn.base, insn.opcode != nullptr ? insn.opcode->name : "INVALID", base - 4);
#endif #endif
println("}}\n"); println("}}\n");
#ifndef XENON_RECOMP_USE_ALIAS
println("PPC_WEAK_FUNC({}) {{", name);
println("\t__imp__{}(ctx, base);", name);
println("}}\n");
#endif
std::swap(out, tempString); std::swap(out, tempString);
if (localVariables.ctr) if (localVariables.ctr)
println("\tPPCRegister ctr{{}};"); println("\tPPCRegister ctr{{}};");
@@ -2811,7 +2672,7 @@ bool Recompiler::Recompile(const Function& fn)
return allRecompiled; return allRecompiled;
} }
void Recompiler::Recompile(const std::filesystem::path& headerFilePath) void Recompiler::Recompile()
{ {
out.reserve(10 * 1024 * 1024); out.reserve(10 * 1024 * 1024);
@@ -2840,30 +2701,6 @@ void Recompiler::Recompile(const std::filesystem::path& headerFilePath)
println(""); println("");
println("#define PPC_IMAGE_BASE 0x{:X}ull", image.base);
println("#define PPC_IMAGE_SIZE 0x{:X}ull", image.size);
// Extract the address of the minimum code segment to store the function table at.
size_t codeMin = ~0;
size_t codeMax = 0;
for (auto& section : image.sections)
{
if ((section.flags & SectionFlags_Code) != 0)
{
if (section.base < codeMin)
codeMin = section.base;
if ((section.base + section.size) > codeMax)
codeMax = (section.base + section.size);
}
}
println("#define PPC_CODE_BASE 0x{:X}ull", codeMin);
println("#define PPC_CODE_SIZE 0x{:X}ull", codeMax - codeMin);
println("");
println("#ifdef PPC_INCLUDE_DETAIL"); println("#ifdef PPC_INCLUDE_DETAIL");
println("#include \"ppc_detail.h\""); println("#include \"ppc_detail.h\"");
println("#endif"); println("#endif");
@@ -2877,14 +2714,7 @@ void Recompiler::Recompile(const std::filesystem::path& headerFilePath)
println("#pragma once"); println("#pragma once");
println("#include \"ppc_config.h\"\n"); println("#include \"ppc_config.h\"\n");
println("{}", std::string_view{g_PPCContextText, g_PPCContextText_size});
std::ifstream stream(headerFilePath);
if (stream.good())
{
std::stringstream ss;
ss << stream.rdbuf();
out += ss.str();
}
SaveCurrentOutData("ppc_context.h"); SaveCurrentOutData("ppc_context.h");
} }
@@ -2922,7 +2752,7 @@ void Recompiler::Recompile(const std::filesystem::path& headerFilePath)
} }
if ((i % 2048) == 0 || (i == (functions.size() - 1))) if ((i % 2048) == 0 || (i == (functions.size() - 1)))
fmt::println("Recompiling functions... {}%", static_cast<float>(i + 1) / functions.size() * 100.0f); std::println("Recompiling functions... {}%", static_cast<float>(i + 1) / functions.size() * 100.0f);
Recompile(functions[i]); Recompile(functions[i]);
} }
@@ -2938,18 +2768,14 @@ void Recompiler::SaveCurrentOutData(const std::string_view& name)
if (name.empty()) if (name.empty())
{ {
cppName = fmt::format("ppc_recomp.{}.cpp", cppFileIndex); cppName = std::format("ppc_recomp.{}.cpp", cppFileIndex);
++cppFileIndex; ++cppFileIndex;
} }
bool shouldWrite = true; bool shouldWrite = true;
// Check if an identical file already exists first to not trigger recompilation // Check if an identical file already exists first to not trigger recompilation
std::string directoryPath = config.directoryPath; std::string filePath = std::format("{}/{}/{}", config.directoryPath, config.outDirectoryPath, name.empty() ? cppName : name);
if (!directoryPath.empty())
directoryPath += "/";
std::string filePath = fmt::format("{}{}/{}", directoryPath, config.outDirectoryPath, name.empty() ? cppName : name);
FILE* f = fopen(filePath.c_str(), "rb"); FILE* f = fopen(filePath.c_str(), "rb");
if (f) if (f)
{ {

View File

@@ -35,18 +35,18 @@ struct Recompiler
size_t cppFileIndex = 0; size_t cppFileIndex = 0;
RecompilerConfig config; RecompilerConfig config;
bool LoadConfig(const std::string_view& configFilePath); void LoadConfig(const std::string_view& configFilePath);
template<class... Args> template<class... Args>
void print(fmt::format_string<Args...> fmt, Args&&... args) void print(std::format_string<Args...> fmt, Args&&... args)
{ {
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...)); std::vformat_to(std::back_inserter(out), fmt.get(), std::make_format_args(args...));
} }
template<class... Args> template<class... Args>
void println(fmt::format_string<Args...> fmt, Args&&... args) void println(std::format_string<Args...> fmt, Args&&... args)
{ {
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...)); std::vformat_to(std::back_inserter(out), fmt.get(), std::make_format_args(args...));
out += '\n'; out += '\n';
} }
@@ -64,7 +64,7 @@ struct Recompiler
bool Recompile(const Function& fn); bool Recompile(const Function& fn);
void Recompile(const std::filesystem::path& headerFilePath); void Recompile();
void SaveCurrentOutData(const std::string_view& name = std::string_view()); void SaveCurrentOutData(const std::string_view& name = std::string_view());
}; };

View File

@@ -3,18 +3,12 @@
void RecompilerConfig::Load(const std::string_view& configFilePath) void RecompilerConfig::Load(const std::string_view& configFilePath)
{ {
directoryPath = configFilePath.substr(0, configFilePath.find_last_of("\\/") + 1); directoryPath = configFilePath.substr(0, configFilePath.find_last_of("\\/") + 1);
toml::table toml = toml::parse_file(configFilePath) toml::table toml = toml::parse_file(configFilePath);
#if !TOML_EXCEPTIONS
.table()
#endif
;
if (auto mainPtr = toml["main"].as_table()) if (auto mainPtr = toml["main"].as_table())
{ {
const auto& main = *mainPtr; const auto& main = *mainPtr;
filePath = main["file_path"].value_or<std::string>(""); filePath = main["file_path"].value_or<std::string>("");
patchFilePath = main["patch_file_path"].value_or<std::string>("");
patchedFilePath = main["patched_file_path"].value_or<std::string>("");
outDirectoryPath = main["out_directory_path"].value_or<std::string>(""); outDirectoryPath = main["out_directory_path"].value_or<std::string>("");
switchTableFilePath = main["switch_table_file_path"].value_or<std::string>(""); switchTableFilePath = main["switch_table_file_path"].value_or<std::string>("");
@@ -38,15 +32,6 @@ void RecompilerConfig::Load(const std::string_view& configFilePath)
longJmpAddress = main["longjmp_address"].value_or(0u); longJmpAddress = main["longjmp_address"].value_or(0u);
setJmpAddress = main["setjmp_address"].value_or(0u); setJmpAddress = main["setjmp_address"].value_or(0u);
if (restGpr14Address == 0) fmt::println("ERROR: __restgprlr_14 address is unspecified");
if (saveGpr14Address == 0) fmt::println("ERROR: __savegprlr_14 address is unspecified");
if (restFpr14Address == 0) fmt::println("ERROR: __restfpr_14 address is unspecified");
if (saveFpr14Address == 0) fmt::println("ERROR: __savefpr_14 address is unspecified");
if (restVmx14Address == 0) fmt::println("ERROR: __restvmx_14 address is unspecified");
if (saveVmx14Address == 0) fmt::println("ERROR: __savevmx_14 address is unspecified");
if (restVmx64Address == 0) fmt::println("ERROR: __restvmx_64 address is unspecified");
if (saveVmx64Address == 0) fmt::println("ERROR: __savevmx_64 address is unspecified");
if (auto functionsArray = main["functions"].as_array()) if (auto functionsArray = main["functions"].as_array())
{ {
for (auto& func : *functionsArray) for (auto& func : *functionsArray)
@@ -71,11 +56,7 @@ void RecompilerConfig::Load(const std::string_view& configFilePath)
if (!switchTableFilePath.empty()) if (!switchTableFilePath.empty())
{ {
toml::table switchToml = toml::parse_file(directoryPath + switchTableFilePath) toml::table switchToml = toml::parse_file(directoryPath + switchTableFilePath);
#if !TOML_EXCEPTIONS
.table()
#endif
;
if (auto switchArray = switchToml["switch"].as_array()) if (auto switchArray = switchToml["switch"].as_array())
{ {
for (auto& entry : *switchArray) for (auto& entry : *switchArray)
@@ -119,18 +100,16 @@ void RecompilerConfig::Load(const std::string_view& configFilePath)
(midAsmHook.returnOnTrue && midAsmHook.jumpAddressOnTrue != NULL) || (midAsmHook.returnOnTrue && midAsmHook.jumpAddressOnTrue != NULL) ||
(midAsmHook.returnOnFalse && midAsmHook.jumpAddressOnFalse != NULL)) (midAsmHook.returnOnFalse && midAsmHook.jumpAddressOnFalse != NULL))
{ {
fmt::println("{}: can't return and jump at the same time", midAsmHook.name); std::println("{}: can't return and jump at the same time", midAsmHook.name);
} }
if ((midAsmHook.ret || midAsmHook.jumpAddress != NULL) && if ((midAsmHook.ret || midAsmHook.jumpAddress != NULL) &&
(midAsmHook.returnOnFalse != NULL || midAsmHook.returnOnTrue != NULL || (midAsmHook.returnOnFalse != NULL || midAsmHook.returnOnTrue != NULL ||
midAsmHook.jumpAddressOnFalse != NULL || midAsmHook.jumpAddressOnTrue != NULL)) midAsmHook.jumpAddressOnFalse != NULL || midAsmHook.jumpAddressOnTrue != NULL))
{ {
fmt::println("{}: can't mix direct and conditional return/jump", midAsmHook.name); std::println("{}: can't mix direct and conditional return/jump", midAsmHook.name);
} }
midAsmHook.afterInstruction = table["after_instruction"].value_or(false);
midAsmHooks.emplace(*table["address"].value<uint32_t>(), std::move(midAsmHook)); midAsmHooks.emplace(*table["address"].value<uint32_t>(), std::move(midAsmHook));
} }
} }

View File

@@ -18,16 +18,12 @@ struct RecompilerMidAsmHook
uint32_t jumpAddress = 0; uint32_t jumpAddress = 0;
uint32_t jumpAddressOnTrue = 0; uint32_t jumpAddressOnTrue = 0;
uint32_t jumpAddressOnFalse = 0; uint32_t jumpAddressOnFalse = 0;
bool afterInstruction = false;
}; };
struct RecompilerConfig struct RecompilerConfig
{ {
std::string directoryPath; std::string directoryPath;
std::string filePath; std::string filePath;
std::string patchFilePath;
std::string patchedFilePath;
std::string outDirectoryPath; std::string outDirectoryPath;
std::string switchTableFilePath; std::string switchTableFilePath;
std::unordered_map<uint32_t, RecompilerSwitchTable> switchTables; std::unordered_map<uint32_t, RecompilerSwitchTable> switchTables;

View File

@@ -22,7 +22,7 @@ void TestRecompiler::Analyse(const std::string_view& testName)
} }
auto& fn = functions.emplace_back(Function::Analyze(data, dataEnd - data, base)); 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); image.symbols.emplace(std::format("{}_{:X}", testName, fn.base), fn.base, fn.size, Symbol_Function);
base += fn.size; base += fn.size;
data += fn.size; data += fn.size;
@@ -40,18 +40,18 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
{ {
if (file.path().extension() == ".o") if (file.path().extension() == ".o")
{ {
const auto exeFile = LoadFile(file.path().string().c_str()); const auto exeFile = LoadFile(file.path().string().c_str()).value();
TestRecompiler recompiler; TestRecompiler recompiler;
recompiler.config.outDirectoryPath = dstDirectoryPath; recompiler.config.outDirectoryPath = dstDirectoryPath;
recompiler.image = Image::ParseImage(exeFile.data(), exeFile.size()); recompiler.image = Image::ParseImage(exeFile.data(), exeFile.size()).value();
auto stem = file.path().stem().string(); auto stem = file.path().stem().string();
recompiler.Analyse(stem); recompiler.Analyse(stem);
recompiler.println("#define PPC_CONFIG_H_INCLUDED"); recompiler.println("#define PPC_CONFIG_H_INCLUDED");
recompiler.println("#include <ppc_context.h>\n"); recompiler.println("#include <ppc_context.h>\n");
recompiler.println("#define __builtin_debugtrap()\n"); recompiler.println("#define __debugbreak()\n");
for (auto& fn : recompiler.functions) for (auto& fn : recompiler.functions)
{ {
@@ -61,7 +61,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
} }
else else
{ {
fmt::println("Function {:X} in {} has unimplemented instructions", fn.base, stem); std::println("Function {:X} in {} has unimplemented instructions", fn.base, stem);
} }
} }
stem += ".cpp"; stem += ".cpp";
@@ -73,7 +73,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
for (auto& [fn, addr] : functions) for (auto& [fn, addr] : functions)
{ {
std::ifstream in(fmt::format("{}/{}.dis", srcDirectoryPath, fn)); std::ifstream in(std::format("{}/{}.dis", srcDirectoryPath, fn));
if (in.is_open()) if (in.is_open())
{ {
std::string line; std::string line;
@@ -86,34 +86,30 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
size_t address = ~0; size_t address = ~0;
std::from_chars(&line[0], &line[spaceIndex], address, 16); std::from_chars(&line[0], &line[spaceIndex], address, 16);
address &= 0xFFFFF; address &= 0xFFFFF;
if (addr.find(address) != addr.end()) if (addr.contains(address))
symbols.emplace(line.substr(spaceIndex + 2, bracketIndex - spaceIndex - 2), fmt::format("{}_{:X}", fn, address)); symbols.emplace(line.substr(spaceIndex + 2, bracketIndex - spaceIndex - 2), std::format("{}_{:X}", fn, address));
} }
} }
} }
else else
{ {
fmt::println("Unable to locate disassembly file for {}", fn); std::println("Unable to locate disassembly file for {}", fn);
} }
} }
FILE* file = fopen(fmt::format("{}/main.cpp", dstDirectoryPath).c_str(), "w"); FILE* file = fopen(std::format("{}/main.cpp", dstDirectoryPath).c_str(), "w");
std::string main; std::string main;
fmt::println(file, "#define PPC_CONFIG_H_INCLUDED"); std::println(file, "#define PPC_CONFIG_H_INCLUDED");
fmt::println(file, "#include <ppc_context.h>"); std::println(file, "#include <ppc_context.h>");
fmt::println(file, "#ifdef _WIN32"); std::println(file, "#include <Windows.h>");
fmt::println(file, "#include <Windows.h>"); std::println(file, "#include <print>\n");
fmt::println(file, "#else"); std::println(file, "#define PPC_CHECK_VALUE_U(f, lhs, rhs) if (lhs != rhs) std::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{:X}}\", lhs)\n");
fmt::println(file, "#include <sys/mman.h>"); std::println(file, "#define PPC_CHECK_VALUE_F(f, lhs, rhs) if (lhs != rhs) std::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{}}\", lhs)\n");
fmt::println(file, "#endif");
fmt::println(file, "#include <fmt/core.h>\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) for (auto& [fn, addr] : functions)
{ {
std::ifstream in(fmt::format("{}/../{}.s", srcDirectoryPath, fn)); std::ifstream in(std::format("{}/../{}.s", srcDirectoryPath, fn));
if (in.is_open()) if (in.is_open())
{ {
std::string str; std::string str;
@@ -139,10 +135,10 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
auto symbol = symbols.find(name); auto symbol = symbols.find(name);
if (symbol != symbols.end()) if (symbol != symbols.end())
{ {
fmt::println(file, "PPC_FUNC({});\n", symbol->second); std::println(file, "PPC_FUNC({});\n", symbol->second);
fmt::println(file, "void {}(uint8_t* base) {{", name); std::println(file, "void {}(uint8_t* base) {{", name);
fmt::println(file, "\tPPCContext ctx{{}};"); std::println(file, "\tPPCContext ctx{{}};");
fmt::println(file, "\tctx.fpscr.loadFromHost();"); std::println(file, "\tctx.fpscr.loadFromHost();");
while (getline() && !str.empty() && str[0] == '#') while (getline() && !str.empty() && str[0] == '#')
{ {
@@ -162,14 +158,14 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
int commaIndex2 = str.find(',', commaIndex1 + 1); int commaIndex2 = str.find(',', commaIndex1 + 1);
int closingBracketIndex = str.find(']', commaIndex2 + 1); int closingBracketIndex = str.find(']', commaIndex2 + 1);
fmt::println(file, "\tctx.{}.u32[3] = 0x{};", reg, str.substr(openingBracketIndex + 1, commaIndex0 - openingBracketIndex - 1)); std::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)); std::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)); std::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)); std::println(file, "\tctx.{}.u32[0] = 0x{};", reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
} }
else else
{ {
fmt::println(file, "\tctx.{}.{}64 = {};", std::println(file, "\tctx.{}.{}64 = {};",
reg, reg,
str.find('.', secondSpaceIndex) != std::string::npos ? 'f' : 'u', str.find('.', secondSpaceIndex) != std::string::npos ? 'f' : 'u',
str.substr(secondSpaceIndex + 1)); str.substr(secondSpaceIndex + 1));
@@ -187,7 +183,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
{ {
if (str[i] != ' ') if (str[i] != ' ')
{ {
fmt::println(file, "\tbase[0x{} + 0x{:X}] = 0x{}{};", address, j, str[i], str[i + 1]); std::println(file, "\tbase[0x{} + 0x{:X}] = 0x{}{};", address, j, str[i], str[i + 1]);
++i; // the loop adds another ++i; // the loop adds another
++j; ++j;
} }
@@ -200,7 +196,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
while (getline() && (str.empty() || str[0] != '#')) while (getline() && (str.empty() || str[0] != '#'))
; ;
fmt::println(file, "\t{}(ctx, base);", symbol->second); std::println(file, "\t{}(ctx, base);", symbol->second);
do do
{ {
@@ -222,14 +218,14 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
int commaIndex2 = str.find(',', commaIndex1 + 1); int commaIndex2 = str.find(',', commaIndex1 + 1);
int closingBracketIndex = str.find(']', commaIndex2 + 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)); std::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)); std::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)); std::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)); std::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[0], 0x{});", name, reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
} }
else else
{ {
fmt::println(file, "\tPPC_CHECK_VALUE_{}({}, ctx.{}.{}64, {});", std::println(file, "\tPPC_CHECK_VALUE_{}({}, ctx.{}.{}64, {});",
str.find('.', secondSpaceIndex) != std::string::npos ? 'F' : 'U', str.find('.', secondSpaceIndex) != std::string::npos ? 'F' : 'U',
name, name,
reg, reg,
@@ -249,7 +245,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
{ {
if (str[i] != ' ') if (str[i] != ' ')
{ {
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, base[0x{} + 0x{:X}], 0x{}{});", name, address, j, str[i], str[i + 1]); std::println(file, "\tPPC_CHECK_VALUE_U({}, base[0x{} + 0x{:X}], 0x{}{});", name, address, j, str[i], str[i + 1]);
++i; // the loop adds another ++i; // the loop adds another
++j; ++j;
} }
@@ -259,13 +255,13 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
} }
} while (getline() && !str.empty() && str[0] == '#'); } while (getline() && !str.empty() && str[0] == '#');
fmt::println(file, "}}\n"); std::println(file, "}}\n");
fmt::format_to(std::back_inserter(main), "\t{}(base);\n", name); std::format_to(std::back_inserter(main), "\t{}(base);\n", name);
} }
else else
{ {
fmt::println("Found no symbol for {}", name); std::println("Found no symbol for {}", name);
} }
} }
} }
@@ -273,19 +269,15 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
} }
else else
{ {
fmt::println("Unable to locate source file for {}", fn); std::println("Unable to locate source file for {}", fn);
} }
} }
fmt::println(file, "int main() {{"); std::println(file, "int main() {{");
fmt::println(file, "#ifdef _WIN32"); std::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, 0x100000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));");
fmt::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, 0x100000000ull, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));");
fmt::println(file, "#else");
fmt::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(mmap(NULL, 0x100000000ull, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0));");
fmt::println(file, "#endif");
fwrite(main.data(), 1, main.size(), file); fwrite(main.data(), 1, main.size(), file);
fmt::println(file, "\treturn 0;"); std::println(file, "\treturn 0;");
fmt::println(file, "}}"); std::println(file, "}}");
fclose(file); fclose(file);
} }

1
PowerSample/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
ppc_*

View File

@@ -0,0 +1,14 @@
project("PowerSample")
add_compile_options(
"/D_HAS_EXCEPTIONS=0"
"/fp:strict"
"/GS-"
"/EHa-"
"-march=sandybridge"
"-fno-strict-aliasing")
file(GLOB RecompiledFiles *.cpp)
add_library(PowerSample ${RecompiledFiles})
target_precompile_headers(PowerSample PUBLIC "ppc_recomp_shared.h")

0
PowerSample/main.cpp Normal file
View File

13
PowerTests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,13 @@
project("PowerTests")
add_compile_options(
"-march=x86-64-v3"
"-Wno-unused-label"
"-Wno-unused-variable")
file(GLOB TestFiles *.cpp)
if(TestFiles)
add_executable(PowerTests ${TestFiles})
target_link_libraries(PowerTests PUBLIC PowerUtils)
endif()

View File

@@ -0,0 +1,5 @@
project("PowerUtils")
add_library(PowerUtils "disasm.h" "disasm.cpp" "file.h" "xex.cpp" "image.h" "image.cpp" "elf.h" "ppc_context.h" "symbol.h" "symbol_table.h" "section.h")
target_include_directories(PowerUtils PUBLIC .)
target_link_libraries(PowerUtils PUBLIC disasm)

26
PowerUtils/file.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <expected>
#include <vector>
inline static std::expected<std::vector<uint8_t>, int> LoadFile(const char* path)
{
std::vector<uint8_t> data{};
auto* stream = fopen(path, "rb");
if (stream == nullptr)
{
return std::unexpected(1);
}
fseek(stream, 0, SEEK_END);
const auto size = ftell(stream);
fseek(stream, 0, SEEK_SET);
data.resize(size);
fread(data.data(), 1, data.size(), stream);
fclose(stream);
return data;
}

View File

@@ -2,12 +2,11 @@
#include "elf.h" #include "elf.h"
#include "xex.h" #include "xex.h"
#include <cassert> #include <cassert>
#include <cstring>
void Image::Map(const std::string_view& name, size_t base, uint32_t size, uint8_t flags, uint8_t* data) void Image::Map(const std::string_view& name, size_t base, uint32_t size, uint8_t flags, uint8_t* data)
{ {
sections.insert({ std::string(name), this->base + base, sections.emplace(std::string(name), this->base + base,
size, static_cast<SectionFlags>(flags), data }); size, static_cast<SectionFlags>(flags), data);
} }
const void* Image::Find(size_t address) const const void* Image::Find(size_t address) const
@@ -29,7 +28,7 @@ const Section* Image::Find(const std::string_view& name) const
return nullptr; return nullptr;
} }
Image Image::ParseImage(const uint8_t* data, size_t size) std::expected<Image, int> Image::ParseImage(const uint8_t* data, size_t size)
{ {
if (data[0] == ELFMAG0 && data[1] == ELFMAG1 && data[2] == ELFMAG2 && data[3] == ELFMAG3) if (data[0] == ELFMAG0 && data[1] == ELFMAG1 && data[2] == ELFMAG2 && data[3] == ELFMAG3)
{ {
@@ -37,10 +36,10 @@ Image Image::ParseImage(const uint8_t* data, size_t size)
} }
else if (data[0] == 'X' && data[1] == 'E' && data[2] == 'X' && data[3] == '2') else if (data[0] == 'X' && data[1] == 'E' && data[2] == 'X' && data[3] == '2')
{ {
return Xex2LoadImage(data, size); return Xex2LoadImage(data);
} }
return {}; return std::unexpected(1);
} }
Image ElfLoadImage(const uint8_t* data, size_t size) Image ElfLoadImage(const uint8_t* data, size_t size)
@@ -51,27 +50,27 @@ Image ElfLoadImage(const uint8_t* data, size_t size)
Image image{}; Image image{};
image.size = size; image.size = size;
image.data = std::make_unique<uint8_t[]>(size); image.data = std::make_unique<uint8_t[]>(size);
image.entry_point = ByteSwap(header->e_entry); image.entry_point = std::byteswap(header->e_entry);
memcpy(image.data.get(), data, size); memcpy(image.data.get(), data, size);
auto stringTableIndex = ByteSwap(header->e_shstrndx); auto stringTableIndex = std::byteswap(header->e_shstrndx);
const auto numSections = ByteSwap(header->e_shnum); const auto numSections = std::byteswap(header->e_shnum);
const auto numpSections = ByteSwap(header->e_phnum); const auto numpSections = std::byteswap(header->e_phnum);
const auto* sections = (elf32_shdr*)(data + ByteSwap(header->e_shoff)); const auto* sections = (elf32_shdr*)(data + std::byteswap(header->e_shoff));
const auto* psections = (elf32_phdr*)(data + ByteSwap(header->e_phoff)); const auto* psections = (elf32_phdr*)(data + std::byteswap(header->e_phoff));
for (size_t i = 0; i < numpSections; i++) for (size_t i = 0; i < numpSections; i++)
{ {
if (psections[i].p_type == ByteSwap((Elf32_Word)PT_LOAD)) if (psections[i].p_type == std::byteswap((Elf32_Word)PT_LOAD))
{ {
image.base = ByteSwap(psections[i].p_vaddr); image.base = std::byteswap(psections[i].p_vaddr);
break; break;
} }
} }
auto* stringTable = reinterpret_cast<const char*>(data + ByteSwap(sections[stringTableIndex].sh_offset)); auto* stringTable = reinterpret_cast<const char*>(data + std::byteswap(sections[stringTableIndex].sh_offset));
for (size_t i = 0; i < numSections; i++) for (size_t i = 0; i < numSections; i++)
{ {
@@ -83,16 +82,16 @@ Image ElfLoadImage(const uint8_t* data, size_t size)
uint8_t flags{}; uint8_t flags{};
if (section.sh_flags & ByteSwap(SHF_EXECINSTR)) if (section.sh_flags & std::byteswap(SHF_EXECINSTR))
{ {
flags |= SectionFlags_Code; flags |= SectionFlags_Code;
} }
auto* name = section.sh_name != 0 ? stringTable + ByteSwap(section.sh_name) : nullptr; auto* name = section.sh_name != 0 ? stringTable + std::byteswap(section.sh_name) : nullptr;
const auto rva = ByteSwap(section.sh_addr) - image.base; const auto rva = std::byteswap(section.sh_addr) - image.base;
const auto size = ByteSwap(section.sh_size); const auto size = std::byteswap(section.sh_size);
image.Map(name, rva, size, flags, image.data.get() + ByteSwap(section.sh_offset)); image.Map(name, rva, size, flags, image.data.get() + std::byteswap(section.sh_offset));
} }
return image; return image;

View File

@@ -2,6 +2,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <set> #include <set>
#include <expected>
#include <section.h> #include <section.h>
#include "symbol_table.h" #include "symbol_table.h"
@@ -43,7 +44,7 @@ struct Image
* \param size Size of data * \param size Size of data
* \return Parsed image * \return Parsed image
*/ */
static Image ParseImage(const uint8_t* data, size_t size); static std::expected<Image, int> ParseImage(const uint8_t* data, size_t size);
}; };
Image ElfLoadImage(const uint8_t* data, size_t size); Image ElfLoadImage(const uint8_t* data, size_t size);

View File

@@ -5,21 +5,14 @@
#error "ppc_config.h must be included before ppc_context.h" #error "ppc_config.h must be included before ppc_context.h"
#endif #endif
#include <climits>
#include <cmath> #include <cmath>
#include <csetjmp> #include <csetjmp>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <x86intrin.h>
#ifdef _WIN32
#include <intrin.h> #include <intrin.h>
#else #include <x86intrin.h>
#include <xmmintrin.h>
#include <smmintrin.h>
#endif
#define PPC_JOIN(x, y) x##y #define PPC_JOIN(x, y) x##y
#define PPC_XSTRINGIFY(x) #x #define PPC_XSTRINGIFY(x) #x
@@ -29,7 +22,7 @@
#define PPC_EXTERN_FUNC(x) extern PPC_FUNC(x) #define PPC_EXTERN_FUNC(x) extern PPC_FUNC(x)
#define PPC_WEAK_FUNC(x) __attribute__((weak,noinline)) PPC_FUNC(x) #define PPC_WEAK_FUNC(x) __attribute__((weak,noinline)) PPC_FUNC(x)
#define PPC_FUNC_PROLOGUE() __builtin_assume(((size_t)base & 0x1F) == 0) #define PPC_FUNC_PROLOGUE() __builtin_assume(((size_t)base & 0xFFFFFFFF) == 0)
#ifndef PPC_LOAD_U8 #ifndef PPC_LOAD_U8
#define PPC_LOAD_U8(x) *(volatile uint8_t*)(base + (x)) #define PPC_LOAD_U8(x) *(volatile uint8_t*)(base + (x))
@@ -105,12 +98,8 @@
#define PPC_CALL_FUNC(x) x(ctx, base) #define PPC_CALL_FUNC(x) x(ctx, base)
#endif #endif
#define PPC_MEMORY_SIZE 0x100000000ull
#define PPC_LOOKUP_FUNC(x, y) *(PPCFunc**)(x + PPC_IMAGE_BASE + PPC_IMAGE_SIZE + (uint64_t(uint32_t(y) - PPC_CODE_BASE) * 2))
#ifndef PPC_CALL_INDIRECT_FUNC #ifndef PPC_CALL_INDIRECT_FUNC
#define PPC_CALL_INDIRECT_FUNC(x) (PPC_LOOKUP_FUNC(base, x))(ctx, base) #define PPC_CALL_INDIRECT_FUNC(x) (*(PPCFunc**)(ctx.fn + uint64_t(x) * 2))(ctx, base)
#endif #endif
typedef void PPCFunc(struct PPCContext& __restrict__ ctx, uint8_t* base); typedef void PPCFunc(struct PPCContext& __restrict__ ctx, uint8_t* base);
@@ -123,18 +112,21 @@ struct PPCFuncMapping
extern PPCFuncMapping PPCFuncMappings[]; extern PPCFuncMapping PPCFuncMappings[];
union PPCRegister struct PPCRegister
{ {
int8_t s8; union
uint8_t u8; {
int16_t s16; int8_t s8;
uint16_t u16; uint8_t u8;
int32_t s32; int16_t s16;
uint32_t u32; uint16_t u16;
int64_t s64; int32_t s32;
uint64_t u64; uint32_t u32;
float f32; int64_t s64;
double f64; uint64_t u64;
float f32;
double f64;
};
}; };
struct PPCXERRegister struct PPCXERRegister
@@ -191,18 +183,21 @@ struct PPCCRRegister
} }
}; };
union alignas(0x10) PPCVRegister struct alignas(0x10) PPCVRegister
{ {
int8_t s8[16]; union
uint8_t u8[16]; {
int16_t s16[8]; int8_t s8[16];
uint16_t u16[8]; uint8_t u8[16];
int32_t s32[4]; int16_t s16[8];
uint32_t u32[4]; uint16_t u16[8];
int64_t s64[2]; int32_t s32[4];
uint64_t u64[2]; uint32_t u32[4];
float f32[4]; int64_t s64[2];
double f64[2]; uint64_t u64[2];
float f32[4];
double f64[2];
};
}; };
#define PPC_ROUND_NEAREST 0x00 #define PPC_ROUND_NEAREST 0x00
@@ -264,9 +259,37 @@ struct PPCFPSCRRegister
} }
}; };
struct alignas(0x40) PPCContext struct PPCContext
{ {
PPCRegister r3; uint8_t* fn;
#ifndef PPC_CONFIG_SKIP_LR
uint64_t lr;
#endif
#ifndef PPC_CONFIG_CTR_AS_LOCAL
PPCRegister ctr;
#endif
#ifndef PPC_CONFIG_XER_AS_LOCAL
PPCXERRegister xer;
#endif
#ifndef PPC_CONFIG_RESERVED_AS_LOCAL
PPCRegister reserved;
#endif
#ifndef PPC_CONFIG_SKIP_MSR
uint32_t msr = 0x200A000;
#endif
#ifndef PPC_CONFIG_CR_AS_LOCAL
PPCCRRegister cr0;
PPCCRRegister cr1;
PPCCRRegister cr2;
PPCCRRegister cr3;
PPCCRRegister cr4;
PPCCRRegister cr5;
PPCCRRegister cr6;
PPCCRRegister cr7;
#endif
#ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL #ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL
PPCRegister r0; PPCRegister r0;
#endif #endif
@@ -274,6 +297,7 @@ struct alignas(0x40) PPCContext
#ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL #ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL
PPCRegister r2; PPCRegister r2;
#endif #endif
PPCRegister r3;
PPCRegister r4; PPCRegister r4;
PPCRegister r5; PPCRegister r5;
PPCRegister r6; PPCRegister r6;
@@ -307,31 +331,6 @@ struct alignas(0x40) PPCContext
PPCRegister r31; PPCRegister r31;
#endif #endif
#ifndef PPC_CONFIG_SKIP_LR
uint64_t lr;
#endif
#ifndef PPC_CONFIG_CTR_AS_LOCAL
PPCRegister ctr;
#endif
#ifndef PPC_CONFIG_XER_AS_LOCAL
PPCXERRegister xer;
#endif
#ifndef PPC_CONFIG_RESERVED_AS_LOCAL
PPCRegister reserved;
#endif
#ifndef PPC_CONFIG_SKIP_MSR
uint32_t msr = 0x200A000;
#endif
#ifndef PPC_CONFIG_CR_AS_LOCAL
PPCCRRegister cr0;
PPCCRRegister cr1;
PPCCRRegister cr2;
PPCCRRegister cr3;
PPCCRRegister cr4;
PPCCRRegister cr5;
PPCCRRegister cr6;
PPCCRRegister cr7;
#endif
PPCFPSCRRegister fpscr; PPCFPSCRRegister fpscr;
#ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL #ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL

View File

@@ -13,18 +13,9 @@ enum SymbolType
struct Symbol struct Symbol
{ {
mutable std::string name{}; mutable std::string name{};
size_t address{}; uint32_t address{};
size_t size{}; uint32_t size{};
mutable SymbolType type{}; mutable SymbolType type{};
Symbol()
{
}
Symbol(std::string name, size_t address, size_t size, SymbolType type)
: name(std::move(name)), address(address), size(size), type(type)
{
}
}; };
struct SymbolComparer struct SymbolComparer

View File

@@ -3,10 +3,49 @@
#include <type_traits> #include <type_traits>
#include <bit> #include <bit>
#include <string> #include <string>
#include "byteswap.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#else
#define near
#define far
typedef char CHAR;
typedef wchar_t WCHAR;
typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef FLOAT* PFLOAT;
typedef BOOL near* PBOOL;
typedef BOOL far* LPBOOL;
typedef BYTE near* PBYTE;
typedef BYTE far* LPBYTE;
typedef int near* PINT;
typedef int far* LPINT;
typedef WORD near* PWORD;
typedef WORD far* LPWORD;
typedef long far* LPLONG;
typedef DWORD near* PDWORD;
typedef DWORD far* LPDWORD;
typedef void far* LPVOID;
typedef const void far* LPCVOID;
typedef unsigned long ULONG;
typedef ULONG* PULONG;
typedef signed long LONG;
typedef LONG* PLONG;
typedef unsigned long long ULONGLONG;
typedef ULONGLONG* PULONGLONG;
typedef unsigned short USHORT;
typedef USHORT* PUSHORT;
typedef unsigned char UCHAR;
typedef UCHAR* PUCHAR;
typedef char* PSZ;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned int* PUINT;
#endif #endif
// real win32 handles will never use the upper bits unless something goes really wrong // real win32 handles will never use the upper bits unless something goes really wrong
@@ -42,22 +81,22 @@ struct be
{ {
if constexpr (std::is_same_v<T, double>) if constexpr (std::is_same_v<T, double>)
{ {
const uint64_t swapped = ByteSwap(*reinterpret_cast<uint64_t*>(&value)); const uint64_t swapped = std::byteswap(*reinterpret_cast<uint64_t*>(&value));
return *reinterpret_cast<const T*>(&swapped); return *reinterpret_cast<const T*>(&swapped);
} }
else if constexpr (std::is_same_v<T, float>) else if constexpr (std::is_same_v<T, float>)
{ {
const uint32_t swapped = ByteSwap(*reinterpret_cast<uint32_t*>(&value)); const uint32_t swapped = std::byteswap(*reinterpret_cast<uint32_t*>(&value));
return *reinterpret_cast<const T*>(&swapped); return *reinterpret_cast<const T*>(&swapped);
} }
else if constexpr (std::is_enum_v<T>) else if constexpr (std::is_enum_v<T>)
{ {
const std::underlying_type_t<T> swapped = ByteSwap(*reinterpret_cast<std::underlying_type_t<T>*>(&value)); const std::underlying_type_t<T> swapped = std::byteswap(*reinterpret_cast<std::underlying_type_t<T>*>(&value));
return *reinterpret_cast<const T*>(&swapped); return *reinterpret_cast<const T*>(&swapped);
} }
else else
{ {
return ByteSwap(value); return std::byteswap(value);
} }
} }
@@ -105,7 +144,7 @@ struct xpointer
{ {
} }
xpointer(T* p) : ptr(p != nullptr ? (reinterpret_cast<size_t>(p) - reinterpret_cast<size_t>(MmGetHostAddress(0))) : 0) xpointer(T* ptr) : ptr((uint32_t)ptr)
{ {
} }
@@ -136,23 +175,33 @@ struct HostObject
typedef TGuest guest_type; typedef TGuest guest_type;
}; };
typedef BYTE XBYTE;
typedef be<uint16_t> XWORD;
typedef be<uint32_t> XDWORD;
typedef be<uint64_t> XQWORD;
typedef XBYTE* XLPBYTE;
typedef XWORD* XLPWORD;
typedef XDWORD* XLPDWORD;
typedef XQWORD* XLPQWORD;
struct _XLIST_ENTRY; struct _XLIST_ENTRY;
typedef _XLIST_ENTRY XLIST_ENTRY; typedef _XLIST_ENTRY XLIST_ENTRY;
typedef xpointer<XLIST_ENTRY> PXLIST_ENTRY; typedef xpointer<XLIST_ENTRY> PXLIST_ENTRY;
typedef struct _IMAGE_CE_RUNTIME_FUNCTION typedef struct _IMAGE_CE_RUNTIME_FUNCTION
{ {
uint32_t BeginAddress; DWORD BeginAddress;
union union
{ {
uint32_t Data; DWORD Data;
struct struct
{ {
uint32_t PrologLength : 8; DWORD PrologLength : 8;
uint32_t FunctionLength : 22; DWORD FunctionLength : 22;
uint32_t ThirtyTwoBit : 1; DWORD ThirtyTwoBit : 1;
uint32_t ExceptionFlag : 1; DWORD ExceptionFlag : 1;
}; };
}; };
} IMAGE_CE_RUNTIME_FUNCTION; } IMAGE_CE_RUNTIME_FUNCTION;
@@ -161,8 +210,8 @@ static_assert(sizeof(IMAGE_CE_RUNTIME_FUNCTION) == 8);
typedef struct _XLIST_ENTRY typedef struct _XLIST_ENTRY
{ {
be<uint32_t> Flink; XDWORD Flink;
be<uint32_t> Blink; XDWORD Blink;
} XLIST_ENTRY; } XLIST_ENTRY;
typedef struct _XDISPATCHER_HEADER typedef struct _XDISPATCHER_HEADER
@@ -171,30 +220,30 @@ typedef struct _XDISPATCHER_HEADER
{ {
struct struct
{ {
uint8_t Type; UCHAR Type;
union union
{ {
uint8_t Abandoned; UCHAR Abandoned;
uint8_t Absolute; UCHAR Absolute;
uint8_t NpxIrql; UCHAR NpxIrql;
uint8_t Signalling; UCHAR Signalling;
}; };
union union
{ {
uint8_t Size; UCHAR Size;
uint8_t Hand; UCHAR Hand;
}; };
union union
{ {
uint8_t Inserted; UCHAR Inserted;
uint8_t DebugActive; UCHAR DebugActive;
uint8_t DpcActive; UCHAR DpcActive;
}; };
}; };
be<uint32_t> Lock; XDWORD Lock;
}; };
be<uint32_t> SignalState; XDWORD SignalState;
XLIST_ENTRY WaitListHead; XLIST_ENTRY WaitListHead;
} XDISPATCHER_HEADER, * XPDISPATCHER_HEADER; } XDISPATCHER_HEADER, * XPDISPATCHER_HEADER;
@@ -202,20 +251,20 @@ typedef struct _XDISPATCHER_HEADER
typedef struct _XRTL_CRITICAL_SECTION typedef struct _XRTL_CRITICAL_SECTION
{ {
XDISPATCHER_HEADER Header; XDISPATCHER_HEADER Header;
int32_t LockCount; long LockCount;
int32_t RecursionCount; int32_t RecursionCount;
uint32_t OwningThread; uint32_t OwningThread;
} XRTL_CRITICAL_SECTION; } XRTL_CRITICAL_SECTION;
typedef struct _XANSI_STRING { typedef struct _XANSI_STRING {
be<uint16_t> Length; XWORD Length;
be<uint16_t> MaximumLength; XWORD MaximumLength;
xpointer<char> Buffer; xpointer<char> Buffer;
} XANSI_STRING; } XANSI_STRING;
typedef struct _XOBJECT_ATTRIBUTES typedef struct _XOBJECT_ATTRIBUTES
{ {
be<uint32_t> RootDirectory; XDWORD RootDirectory;
xpointer<XANSI_STRING> Name; xpointer<XANSI_STRING> Name;
xpointer<void> Attributes; xpointer<void> Attributes;
} XOBJECT_ATTRIBUTES; } XOBJECT_ATTRIBUTES;
@@ -225,18 +274,18 @@ typedef XDISPATCHER_HEADER XKEVENT;
typedef struct _XIO_STATUS_BLOCK typedef struct _XIO_STATUS_BLOCK
{ {
union { union {
be<uint32_t> Status; XDWORD Status;
be<uint32_t> Pointer; XDWORD Pointer;
}; };
be<uint32_t> Information; be<uint32_t> Information;
} XIO_STATUS_BLOCK; } XIO_STATUS_BLOCK;
typedef struct _XOVERLAPPED { typedef struct _XOVERLAPPED {
be<uint32_t> Internal; XDWORD Internal;
be<uint32_t> InternalHigh; XDWORD InternalHigh;
be<uint32_t> Offset; XDWORD Offset;
be<uint32_t> OffsetHigh; XDWORD OffsetHigh;
be<uint32_t> hEvent; XDWORD hEvent;
} XOVERLAPPED; } XOVERLAPPED;
// this name is so dumb // this name is so dumb
@@ -245,8 +294,8 @@ typedef struct _XXOVERLAPPED {
{ {
struct struct
{ {
be<uint32_t> Error; XDWORD Error;
be<uint32_t> Length; XDWORD Length;
}; };
struct struct
@@ -256,24 +305,24 @@ typedef struct _XXOVERLAPPED {
}; };
}; };
uint32_t InternalContext; uint32_t InternalContext;
be<uint32_t> hEvent; XDWORD hEvent;
be<uint32_t> pCompletionRoutine; XDWORD pCompletionRoutine;
be<uint32_t> dwCompletionContext; XDWORD dwCompletionContext;
be<uint32_t> dwExtendedError; XDWORD dwExtendedError;
} XXOVERLAPPED, *PXXOVERLAPPED; } XXOVERLAPPED, *PXXOVERLAPPED;
static_assert(sizeof(_XXOVERLAPPED) == 0x1C); static_assert(sizeof(_XXOVERLAPPED) == 0x1C);
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-memorystatus // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-memorystatus
typedef struct _XMEMORYSTATUS { typedef struct _XMEMORYSTATUS {
be<uint32_t> dwLength; XDWORD dwLength;
be<uint32_t> dwMemoryLoad; XDWORD dwMemoryLoad;
be<uint32_t> dwTotalPhys; XDWORD dwTotalPhys;
be<uint32_t> dwAvailPhys; XDWORD dwAvailPhys;
be<uint32_t> dwTotalPageFile; XDWORD dwTotalPageFile;
be<uint32_t> dwAvailPageFile; XDWORD dwAvailPageFile;
be<uint32_t> dwTotalVirtual; XDWORD dwTotalVirtual;
be<uint32_t> dwAvailVirtual; XDWORD dwAvailVirtual;
} XMEMORYSTATUS, * XLPMEMORYSTATUS; } XMEMORYSTATUS, * XLPMEMORYSTATUS;
typedef struct _XVIDEO_MODE typedef struct _XVIDEO_MODE
@@ -293,28 +342,28 @@ typedef struct _XVIDEO_MODE
typedef struct _XKSEMAPHORE typedef struct _XKSEMAPHORE
{ {
XDISPATCHER_HEADER Header; XDISPATCHER_HEADER Header;
be<uint32_t> Limit; XDWORD Limit;
} XKSEMAPHORE; } XKSEMAPHORE;
typedef struct _XUSER_SIGNIN_INFO { typedef struct _XUSER_SIGNIN_INFO {
be<uint64_t> xuid; be<ULONGLONG> xuid;
be<uint32_t> dwField08; XDWORD dwField08;
be<uint32_t> SigninState; XDWORD SigninState;
be<uint32_t> dwField10; XDWORD dwField10;
be<uint32_t> dwField14; XDWORD dwField14;
char Name[16]; CHAR Name[16];
} XUSER_SIGNIN_INFO; } XUSER_SIGNIN_INFO;
typedef struct _XTIME_FIELDS typedef struct _XTIME_FIELDS
{ {
be<uint16_t> Year; XWORD Year;
be<uint16_t> Month; XWORD Month;
be<uint16_t> Day; XWORD Day;
be<uint16_t> Hour; XWORD Hour;
be<uint16_t> Minute; XWORD Minute;
be<uint16_t> Second; XWORD Second;
be<uint16_t> Milliseconds; XWORD Milliseconds;
be<uint16_t> Weekday; XWORD Weekday;
} XTIME_FIELDS, * PXTIME_FIELDS; } XTIME_FIELDS, * PXTIME_FIELDS;
// Content types // Content types
@@ -331,10 +380,10 @@ typedef struct _XTIME_FIELDS
typedef struct _XCONTENT_DATA typedef struct _XCONTENT_DATA
{ {
be<uint32_t> DeviceID; XDWORD DeviceID;
be<uint32_t> dwContentType; XDWORD dwContentType;
be<uint16_t> szDisplayName[XCONTENT_MAX_DISPLAYNAME]; be<WCHAR> szDisplayName[XCONTENT_MAX_DISPLAYNAME];
char szFileName[XCONTENT_MAX_FILENAME]; CHAR szFileName[XCONTENT_MAX_FILENAME];
} XCONTENT_DATA, * PXCONTENT_DATA; } XCONTENT_DATA, * PXCONTENT_DATA;
typedef struct _XHOSTCONTENT_DATA : _XCONTENT_DATA typedef struct _XHOSTCONTENT_DATA : _XCONTENT_DATA
@@ -349,11 +398,11 @@ typedef struct _XHOSTCONTENT_DATA : _XCONTENT_DATA
typedef struct _XDEVICE_DATA typedef struct _XDEVICE_DATA
{ {
be<uint32_t> DeviceID; XDWORD DeviceID;
be<uint32_t> DeviceType; XDWORD DeviceType;
be<uint64_t> ulDeviceBytes; XQWORD ulDeviceBytes;
be<uint64_t> ulDeviceFreeBytes; XQWORD ulDeviceFreeBytes;
be<uint16_t> wszName[XCONTENTDEVICE_MAX_NAME]; be<WCHAR> wszName[XCONTENTDEVICE_MAX_NAME];
} XDEVICE_DATA, *PXDEVICE_DATA; } XDEVICE_DATA, *PXDEVICE_DATA;
// Direct reflection of XInput structures // Direct reflection of XInput structures
@@ -378,32 +427,32 @@ typedef struct _XDEVICE_DATA
typedef struct _XAMINPUT_GAMEPAD typedef struct _XAMINPUT_GAMEPAD
{ {
uint16_t wButtons; WORD wButtons;
uint8_t bLeftTrigger; BYTE bLeftTrigger;
uint8_t bRightTrigger; BYTE bRightTrigger;
int16_t sThumbLX; SHORT sThumbLX;
int16_t sThumbLY; SHORT sThumbLY;
int16_t sThumbRX; SHORT sThumbRX;
int16_t sThumbRY; SHORT sThumbRY;
} XAMINPUT_GAMEPAD, *PXAMINPUT_GAMEPAD; } XAMINPUT_GAMEPAD, *PXAMINPUT_GAMEPAD;
typedef struct _XAMINPUT_VIBRATION typedef struct _XAMINPUT_VIBRATION
{ {
uint16_t wLeftMotorSpeed; WORD wLeftMotorSpeed;
uint16_t wRightMotorSpeed; WORD wRightMotorSpeed;
} XAMINPUT_VIBRATION, * PXAMINPUT_VIBRATION; } XAMINPUT_VIBRATION, * PXAMINPUT_VIBRATION;
typedef struct _XAMINPUT_CAPABILITIES typedef struct _XAMINPUT_CAPABILITIES
{ {
uint8_t Type; BYTE Type;
uint8_t SubType; BYTE SubType;
uint16_t Flags; WORD Flags;
XAMINPUT_GAMEPAD Gamepad; XAMINPUT_GAMEPAD Gamepad;
XAMINPUT_VIBRATION Vibration; XAMINPUT_VIBRATION Vibration;
} XAMINPUT_CAPABILITIES, * PXAMINPUT_CAPABILITIES; } XAMINPUT_CAPABILITIES, * PXAMINPUT_CAPABILITIES;
typedef struct _XAMINPUT_STATE typedef struct _XAMINPUT_STATE
{ {
uint32_t dwPacketNumber; DWORD dwPacketNumber;
XAMINPUT_GAMEPAD Gamepad; XAMINPUT_GAMEPAD Gamepad;
} XAMINPUT_STATE, * PXAMINPUT_STATE; } XAMINPUT_STATE, * PXAMINPUT_STATE;

148
PowerUtils/xex.cpp Normal file
View File

@@ -0,0 +1,148 @@
#include "xex.h"
#include "image.h"
#include <cassert>
#include <vector>
#include <unordered_map>
#define STRINGIFY(X) #X
#define XE_EXPORT(MODULE, ORDINAL, NAME, TYPE) { (ORDINAL), "__imp__" STRINGIFY(NAME) }
std::unordered_map<size_t, const char*> XamExports =
{
#include "xbox/xam_table.inc"
};
std::unordered_map<size_t, const char*> XboxKernelExports =
{
#include "xbox/xboxkrnl_table.inc"
};
Image Xex2LoadImage(const uint8_t* data)
{
auto* header = reinterpret_cast<const XEX_HEADER*>(data);
auto* security = reinterpret_cast<const XEX2_SECURITY_INFO*>(data + header->AddressOfSecurityInfo);
const auto* compressionInfo = Xex2FindOptionalHeader<XEX_FILE_FORMAT_INFO>(header, XEX_HEADER_FILE_FORMAT_INFO);
Image image{};
std::unique_ptr<uint8_t[]> result{};
size_t imageSize = security->SizeOfImage;
// Decompress image
if (compressionInfo != nullptr)
{
assert(compressionInfo->CompressionType >= XEX_COMPRESSION_BASIC);
assert(compressionInfo->EncryptionType == XEX_ENCRYPTION_NONE);
if (compressionInfo->CompressionType == XEX_COMPRESSION_NONE)
{
result = std::make_unique<uint8_t[]>(imageSize);
memcpy(result.get(), data + header->SizeOfHeader, imageSize);
}
else if (compressionInfo->CompressionType == XEX_COMPRESSION_BASIC)
{
auto* blocks = reinterpret_cast<const XEX_BASIC_FILE_COMPRESSION_INFO*>(compressionInfo + 1);
const size_t numBlocks = (compressionInfo->SizeOfHeader / sizeof(XEX_BASIC_FILE_COMPRESSION_INFO)) - 1;
imageSize = 0;
for (size_t i = 0; i < numBlocks; i++)
{
imageSize += blocks[i].SizeOfData + blocks[i].SizeOfPadding;
}
result = std::make_unique<uint8_t[]>(imageSize);
auto* srcData = data + header->SizeOfHeader;
auto* destData = result.get();
for (size_t i = 0; i < numBlocks; i++)
{
memcpy(destData, srcData, blocks[i].SizeOfData);
srcData += blocks[i].SizeOfData;
destData += blocks[i].SizeOfData;
memset(destData, 0, blocks[i].SizeOfPadding);
destData += blocks[i].SizeOfPadding;
}
}
}
image.data = std::move(result);
image.size = imageSize;
// Map image
const auto* dosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(image.data.get());
const auto* ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS32*>(image.data.get() + dosHeader->e_lfanew);
image.base = ntHeaders->OptionalHeader.ImageBase;
image.entry_point = image.base + ntHeaders->OptionalHeader.AddressOfEntryPoint;
const auto numSections = ntHeaders->FileHeader.NumberOfSections;
const auto* sections = reinterpret_cast<const IMAGE_SECTION_HEADER*>(ntHeaders + 1);
for (size_t i = 0; i < numSections; i++)
{
const auto& section = sections[i];
uint8_t flags{};
if (section.Characteristics & IMAGE_SCN_CNT_CODE)
{
flags |= SectionFlags_Code;
}
image.Map(reinterpret_cast<const char*>(section.Name), section.VirtualAddress,
section.Misc.VirtualSize, flags, image.data.get() + section.VirtualAddress);
}
auto* imports = Xex2FindOptionalHeader<XEX_IMPORT_HEADER>(header, XEX_HEADER_IMPORT_LIBRARIES);
if (imports != nullptr)
{
std::vector<std::string_view> stringTable;
auto* pStrTable = reinterpret_cast<const char*>(imports + 1);
for (size_t i = 0; i < imports->NumImports; i++)
{
stringTable.emplace_back(pStrTable);
pStrTable += strlen(pStrTable) + 1;
}
auto* library = (XEX_IMPORT_LIBRARY*)(((char*)imports) + sizeof(XEX_IMPORT_HEADER) + imports->SizeOfStringTable);
for (size_t i = 0; i < stringTable.size(); i++)
{
auto* descriptors = (XEX_IMPORT_DESCRIPTOR*)(library + 1);
static std::unordered_map<size_t, const char*> DummyExports;
const std::unordered_map<size_t, const char*>* names = &DummyExports;
if (stringTable[i] == "xam.xex")
{
names = &XamExports;
}
else if (stringTable[i] == "xboxkrnl.exe")
{
names = &XboxKernelExports;
}
for (size_t im = 0; im < library->NumberOfImports; im++)
{
auto originalThunk = (XEX_THUNK_DATA*)image.Find(descriptors[im].FirstThunk);
auto originalData = originalThunk;
originalData->Data = std::byteswap(originalData->Data);
if (originalData->OriginalData.Type != 0)
{
uint32_t thunk[4] = { 0x00000060, 0x00000060, 0x00000060, 0x2000804E };
auto name = names->find(originalData->OriginalData.Ordinal);
if (name != names->end())
{
image.symbols.emplace(name->second, descriptors[im].FirstThunk, sizeof(thunk), Symbol_Function);
}
memcpy(originalThunk, thunk, sizeof(thunk));
}
}
library = (XEX_IMPORT_LIBRARY*)((char*)(library + 1) + library->NumberOfImports * sizeof(XEX_IMPORT_DESCRIPTOR));
}
}
return image;
}

168
PowerUtils/xex.h Normal file
View File

@@ -0,0 +1,168 @@
#pragma once
#include <memory>
#include "xbox.h"
#define XEX_COMPRESSION_NONE 0
#define XEX_COMPRESSION_BASIC 1
#define XEX_ENCRYPTION_NONE 0
enum _XEX_THUNK_TYPES
{
XEX_THUNK_VARIABLE = 0,
XEX_THUNK_FUNCTION = 1,
};
enum _XEX_OPTIONAL_HEADER_TYPES
{
XEX_HEADER_RESOURCE_INFO = 0x000002FF,
XEX_HEADER_FILE_FORMAT_INFO = 0x000003FF,
XEX_HEADER_DELTA_PATCH_DESCRIPTOR = 0x000005FF,
XEX_HEADER_BASE_REFERENCE = 0x00000405,
XEX_HEADER_BOUNDING_PATH = 0x000080FF,
XEX_HEADER_DEVICE_ID = 0x00008105,
XEX_HEADER_ORIGINAL_BASE_ADDRESS = 0x00010001,
XEX_HEADER_ENTRY_POINT = 0x00010100,
XEX_HEADER_IMAGE_BASE_ADDRESS = 0x00010201,
XEX_HEADER_IMPORT_LIBRARIES = 0x000103FF,
XEX_HEADER_CHECKSUM_TIMESTAMP = 0x00018002,
XEX_HEADER_ENABLED_FOR_CALLCAP = 0x00018102,
XEX_HEADER_ENABLED_FOR_FASTCAP = 0x00018200,
XEX_HEADER_ORIGINAL_PE_NAME = 0x000183FF,
XEX_HEADER_STATIC_LIBRARIES = 0x000200FF,
XEX_HEADER_TLS_INFO = 0x00020104,
XEX_HEADER_DEFAULT_STACK_SIZE = 0x00020200,
XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE = 0x00020301,
XEX_HEADER_DEFAULT_HEAP_SIZE = 0x00020401,
XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS = 0x00028002,
XEX_HEADER_SYSTEM_FLAGS = 0x00030000,
XEX_HEADER_EXECUTION_INFO = 0x00040006,
XEX_HEADER_TITLE_WORKSPACE_SIZE = 0x00040201,
XEX_HEADER_GAME_RATINGS = 0x00040310,
XEX_HEADER_LAN_KEY = 0x00040404,
XEX_HEADER_XBOX360_LOGO = 0x000405FF,
XEX_HEADER_MULTIDISC_MEDIA_IDS = 0x000406FF,
XEX_HEADER_ALTERNATE_TITLE_IDS = 0x000407FF,
XEX_HEADER_ADDITIONAL_TITLE_MEMORY = 0x00040801,
XEX_HEADER_EXPORTS_BY_NAME = 0x00E10402,
};
typedef struct _XEX_FILE_FORMAT_INFO
{
be<uint32_t> SizeOfHeader;
be<uint16_t> EncryptionType;
be<uint16_t> CompressionType;
} XEX_FILE_FORMAT_INFO;
typedef struct _XEX_BASIC_FILE_COMPRESSION_INFO
{
be<uint32_t> SizeOfData;
be<uint32_t> SizeOfPadding;
} XEX_BASIC_FILE_COMPRESSION_INFO;
typedef struct _XEX_THUNK_DATA {
union
{
struct
{
WORD Ordinal : 16;
WORD Hint : 8;
WORD Type : 8;
} OriginalData;
XDWORD Ordinal;
XDWORD Function;
XDWORD AddressOfData;
// For easier swapping
DWORD Data;
};
} XEX_THUNK_DATA;
typedef struct _XEX_IMPORT_HEADER {
XDWORD SizeOfHeader;
XDWORD SizeOfStringTable;
XDWORD NumImports;
} XEX_IMPORT_HEADER;
typedef struct _XEX_IMPORT_LIBRARY {
XDWORD Size;
char NextImportDigest[0x14];
XDWORD ID;
XDWORD Version;
XDWORD MinVersion;
XWORD Name;
XWORD NumberOfImports;
} XEX_IMPORT_LIBRARY;
static_assert(sizeof(XEX_IMPORT_LIBRARY) == 0x28);
typedef struct _XEX_IMPORT_DESCRIPTOR {
XDWORD FirstThunk; // VA XEX_THUNK_DATA
} XEX_IMPORT_DESCRIPTOR;
typedef struct _XEX_OPTIONAL_HEADER
{
XDWORD Type;
XDWORD Address;
} XEX_OPTIONAL_HEADER;
typedef struct _XEX2_SECURITY_INFO
{
be<uint32_t> SizeOfHeader;
be<uint32_t> SizeOfImage;
char RsaSignature[0x100];
be<uint32_t> Unknown108;
be<uint32_t> ImageFlags;
be<uint32_t> ImageBase;
char SectionDigest[0x14];
be<uint32_t> NumberOfImports;
char ImportsDigest[0x14];
char Xgd2MediaID[0x10];
char AesKey[0x10];
be<uint32_t> AddressOfExports;
char HeaderDigest[0x14];
be<uint32_t> Region;
be<uint32_t> AllowedMediaTypes;
be<uint32_t> NumberOfPageDescriptors;
} XEX2_SECURITY_INFO;
typedef struct _XEX_HEADER
{
char Signature[4];
be<uint32_t> Flags;
be<uint32_t> SizeOfHeader;
char Reserved[4];
be<uint32_t> AddressOfSecurityInfo;
be<uint32_t> NumberOfOptionalHeaders;
} XEX_HEADER;
template<typename T>
inline static const T* Xex2FindOptionalHeader(const void* base, const XEX_OPTIONAL_HEADER* headers, size_t n, _XEX_OPTIONAL_HEADER_TYPES type)
{
for (size_t i = 0; i < n; i++)
{
if (headers[i].Type == (uint32_t)type)
{
if ((type & 0xFF) == 0)
{
return reinterpret_cast<const T*>(&headers[i].Address);
}
else
{
return reinterpret_cast<const T*>(static_cast<const char*>(base) + headers[i].Address);
}
}
}
return nullptr;
}
template<typename T>
inline static const T* Xex2FindOptionalHeader(const XEX_HEADER* header, _XEX_OPTIONAL_HEADER_TYPES type)
{
return Xex2FindOptionalHeader<T>(header, (XEX_OPTIONAL_HEADER*)(header + 1), header->NumberOfOptionalHeaders, type);
}
struct Image;
Image Xex2LoadImage(const uint8_t* data);

260
README.md
View File

@@ -1,260 +0,0 @@
# 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.
**DISCLAIMER:** This project does not provide a runtime implementation. It only converts the game code to C++, which is not going to function correctly without a runtime backing it. **Making the game work is your responsibility.**
## 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|Byte Pattern
-|-|-
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.|`e9 c1 ff 68`
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.|`f9 c1 ff 68`
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.|`c9 cc ff 70`
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.|`d9 cc ff 70`
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`.|`39 60 fe e0 7d cb 60 ce`
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`.|`39 60 fe e0 7d cb 61 ce`
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.|`39 60 fc 00 10 0b 60 cb`
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.|`39 60 fc 00 10 0b 61 cb`
#### 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.

View File

@@ -1,14 +0,0 @@
# cmake_minimum_required (VERSION 3.16)
project("XenonAnalyse")
add_executable(XenonAnalyse
"main.cpp"
"function.cpp")
target_link_libraries(XenonAnalyse PRIVATE XenonUtils fmt::fmt)
add_library(LibXenonAnalyse "function.cpp")
target_include_directories(LibXenonAnalyse PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(LibXenonAnalyse PUBLIC XenonUtils)

View File

@@ -1,51 +0,0 @@
#pragma once
#include <cstddef>
#include <vector>
#ifdef _DEBUG
#define DEBUG(X) X
#else
#define DEBUG(X)
#endif
struct Function
{
struct Block
{
size_t base{};
size_t size{};
size_t projectedSize{ static_cast<size_t>(-1) }; // scratch
DEBUG(size_t parent{});
Block()
{
}
Block(size_t base, size_t size)
: base(base), size(size)
{
}
Block(size_t base, size_t size, size_t projectedSize)
: base(base), size(size), projectedSize(projectedSize)
{
}
};
size_t base{};
size_t size{};
std::vector<Block> blocks{};
Function()
{
}
Function(size_t base, size_t size)
: base(base), size(size)
{
}
size_t SearchBlock(size_t address) const;
static Function Analyze(const void* code, size_t size, size_t base);
};

View File

@@ -1,29 +0,0 @@
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)
# alias attribute not supported on Apple.
if (NOT APPLE)
target_compile_definitions(XenonRecomp PRIVATE XENON_RECOMP_USE_ALIAS)
endif()
endif()
target_compile_definitions(XenonRecomp PRIVATE _CRT_SECURE_NO_WARNINGS)

View File

@@ -1,52 +0,0 @@
#include "pch.h"
#include "test_recompiler.h"
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
#else
argv[1]
#endif
;
if (std::filesystem::is_regular_file(path))
{
Recompiler recompiler;
if (!recompiler.LoadConfig(path))
return -1;
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 EXIT_SUCCESS;
}

View File

@@ -1,20 +0,0 @@
project("XenonTests")
file(GLOB TEST_FILES *.cpp)
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()

View File

@@ -1,31 +0,0 @@
project("XenonUtils")
add_library(XenonUtils
"disasm.cpp"
"xex.cpp"
"image.cpp"
"xdbf_wrapper.cpp"
"xex_patcher.cpp"
"memory_mapped_file.cpp"
"${THIRDPARTY_ROOT}/libmspack/libmspack/mspack/lzxd.c"
"${THIRDPARTY_ROOT}/tiny-AES-c/aes.c"
)
target_compile_definitions(XenonUtils
PRIVATE
NOMINMAX
)
target_include_directories(XenonUtils
PUBLIC
.
PRIVATE
"${THIRDPARTY_ROOT}/libmspack/libmspack/mspack"
"${THIRDPARTY_ROOT}/tiny-AES-c"
"${THIRDPARTY_ROOT}/TinySHA1"
)
target_link_libraries(XenonUtils
PUBLIC
disasm
)

View File

@@ -1,25 +0,0 @@
#pragma once
#include <cassert>
template<typename T>
inline T ByteSwap(T value)
{
if constexpr (sizeof(T) == 1)
return value;
else if constexpr (sizeof(T) == 2)
return static_cast<T>(__builtin_bswap16(static_cast<uint16_t>(value)));
else if constexpr (sizeof(T) == 4)
return static_cast<T>(__builtin_bswap32(static_cast<uint32_t>(value)));
else if constexpr (sizeof(T) == 8)
return static_cast<T>(__builtin_bswap64(static_cast<uint64_t>(value)));
assert(false && "Unexpected byte size.");
return value;
}
template<typename T>
inline void ByteSwapInplace(T& value)
{
value = ByteSwap(value);
}

View File

@@ -1,28 +0,0 @@
#pragma once
#include <filesystem>
#include <fstream>
#include <vector>
inline std::vector<uint8_t> LoadFile(const std::filesystem::path& path)
{
std::ifstream stream(path, std::ios::binary);
if (!stream.is_open())
{
return {};
}
stream.seekg(0, std::ios::end);
std::streampos size = stream.tellg();
stream.seekg(0, std::ios::beg);
std::vector<uint8_t> data;
data.resize(size);
stream.read((char *)(data.data()), size);
if (stream.bad())
{
return {};
}
return data;
}

View File

@@ -1,169 +0,0 @@
#include "memory_mapped_file.h"
#if !defined(_WIN32)
# include <cstring>
# include <cstdio>
# include <fcntl.h>
# include <unistd.h>
#endif
MemoryMappedFile::MemoryMappedFile()
{
// Default constructor.
}
MemoryMappedFile::MemoryMappedFile(const std::filesystem::path &path)
{
open(path);
}
MemoryMappedFile::~MemoryMappedFile()
{
close();
}
MemoryMappedFile::MemoryMappedFile(MemoryMappedFile &&other)
{
#if defined(_WIN32)
fileHandle = other.fileHandle;
fileMappingHandle = other.fileMappingHandle;
fileView = other.fileView;
fileSize = other.fileSize;
other.fileHandle = nullptr;
other.fileMappingHandle = nullptr;
other.fileView = nullptr;
other.fileSize.QuadPart = 0;
#else
fileHandle = other.fileHandle;
fileView = other.fileView;
fileSize = other.fileSize;
other.fileHandle = -1;
other.fileView = MAP_FAILED;
other.fileSize = 0;
#endif
}
bool MemoryMappedFile::open(const std::filesystem::path &path)
{
#if defined(_WIN32)
fileHandle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (fileHandle == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "CreateFileW failed with error %lu.\n", GetLastError());
fileHandle = nullptr;
return false;
}
if (!GetFileSizeEx(fileHandle, &fileSize))
{
fprintf(stderr, "GetFileSizeEx failed with error %lu.\n", GetLastError());
CloseHandle(fileHandle);
fileHandle = nullptr;
return false;
}
fileMappingHandle = CreateFileMappingW(fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (fileMappingHandle == nullptr)
{
fprintf(stderr, "CreateFileMappingW failed with error %lu.\n", GetLastError());
CloseHandle(fileHandle);
fileHandle = nullptr;
return false;
}
fileView = MapViewOfFile(fileMappingHandle, FILE_MAP_READ, 0, 0, 0);
if (fileView == nullptr)
{
fprintf(stderr, "MapViewOfFile failed with error %lu.\n", GetLastError());
CloseHandle(fileMappingHandle);
CloseHandle(fileHandle);
fileMappingHandle = nullptr;
fileHandle = nullptr;
return false;
}
return true;
#else
fileHandle = ::open(path.c_str(), O_RDONLY);
if (fileHandle == -1)
{
fprintf(stderr, "open for %s failed with error %s.\n", path.c_str(), strerror(errno));
return false;
}
fileSize = lseek(fileHandle, 0, SEEK_END);
if (fileSize == (off_t)(-1))
{
fprintf(stderr, "lseek failed with error %s.\n", strerror(errno));
::close(fileHandle);
fileHandle = -1;
return false;
}
fileView = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fileHandle, 0);
if (fileView == MAP_FAILED)
{
fprintf(stderr, "mmap failed with error %s.\n", strerror(errno));
::close(fileHandle);
fileHandle = -1;
return false;
}
return true;
#endif
}
void MemoryMappedFile::close()
{
#if defined(_WIN32)
if (fileView != nullptr)
{
UnmapViewOfFile(fileView);
}
if (fileMappingHandle != nullptr)
{
CloseHandle(fileMappingHandle);
}
if (fileHandle != nullptr)
{
CloseHandle(fileHandle);
}
#else
if (fileView != MAP_FAILED)
{
munmap(fileView, fileSize);
}
if (fileHandle != -1)
{
::close(fileHandle);
}
#endif
}
bool MemoryMappedFile::isOpen() const
{
#if defined(_WIN32)
return (fileView != nullptr);
#else
return (fileView != MAP_FAILED);
#endif
}
uint8_t *MemoryMappedFile::data() const
{
return reinterpret_cast<uint8_t *>(fileView);
}
size_t MemoryMappedFile::size() const
{
#if defined(_WIN32)
return fileSize.QuadPart;
#else
return static_cast<size_t>(fileSize);
#endif
}

View File

@@ -1,32 +0,0 @@
#pragma once
#include <filesystem>
#if defined(_WIN32)
# include <Windows.h>
#else
# include <sys/mman.h>
#endif
struct MemoryMappedFile {
#if defined(_WIN32)
HANDLE fileHandle = nullptr;
HANDLE fileMappingHandle = nullptr;
LPVOID fileView = nullptr;
LARGE_INTEGER fileSize = {};
#else
int fileHandle = -1;
void *fileView = MAP_FAILED;
off_t fileSize = 0;
#endif
MemoryMappedFile();
MemoryMappedFile(const std::filesystem::path &path);
MemoryMappedFile(MemoryMappedFile &&other);
~MemoryMappedFile();
bool open(const std::filesystem::path &path);
void close();
bool isOpen() const;
uint8_t *data() const;
size_t size() const;
};

View File

@@ -1,203 +0,0 @@
#pragma once
#include <xbox.h>
#define XDBF_SIGNATURE 0x58444246
#define XACH_SIGNATURE 0x58414348
struct XDBFHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> EntryTableLength;
be<uint32_t> EntryCount;
be<uint32_t> FreeSpaceTableLength;
be<uint32_t> FreeSpaceTableEntryCount;
};
enum EXDBFNamespace : uint16_t
{
XDBF_SPA_NAMESPACE_METADATA = 1,
XDBF_SPA_NAMESPACE_IMAGE = 2,
XDBF_SPA_NAMESPACE_STRING_TABLE = 3,
XDBF_GPD_NAMESPACE_ACHIEVEMENT = 1,
XDBF_GPD_NAMESPACE_IMAGE = 2,
XDBF_GPD_NAMESPACE_SETTING = 3,
XDBF_GPD_NAMESPACE_TITLE = 4,
XDBF_GPD_NAMESPACE_STRING = 5,
XDBF_GPD_NAMESPACE_ACHIEVEMENT_SECURITY_GFWL = 6,
XDBF_GPD_NAMESPACE_AVATAR_AWARD_360 = 6
};
#pragma pack(push, 1)
struct XDBFEntry
{
be<EXDBFNamespace> NamespaceID;
be<uint64_t> ResourceID;
be<uint32_t> Offset;
be<uint32_t> Length;
};
#pragma pack(pop)
struct XDBFFreeSpaceEntry
{
be<uint32_t> Offset;
be<uint32_t> Length;
};
enum EXDBFLanguage : uint32_t
{
XDBF_LANGUAGE_UNKNOWN = 0,
XDBF_LANGUAGE_ENGLISH = 1,
XDBF_LANGUAGE_JAPANESE = 2,
XDBF_LANGUAGE_GERMAN = 3,
XDBF_LANGUAGE_FRENCH = 4,
XDBF_LANGUAGE_SPANISH = 5,
XDBF_LANGUAGE_ITALIAN = 6,
XDBF_LANGUAGE_KOREAN = 7,
XDBF_LANGUAGE_CHINESE_TRAD = 8,
XDBF_LANGUAGE_PORTUGUESE = 9,
XDBF_LANGUAGE_CHINESE_SIMP = 10,
XDBF_LANGUAGE_POLISH = 11,
XDBF_LANGUAGE_RUSSIAN = 12,
XDBF_LANGUAGE_MAX
};
struct XSTCHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> Size;
be<EXDBFLanguage> Language;
};
#pragma pack(push, 1)
struct XSTRHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> Size;
be<uint16_t> StringCount;
};
#pragma pack(pop)
struct XSTREntry
{
be<uint16_t> ID;
be<uint16_t> Length;
};
#pragma pack(push, 1)
struct XACHHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> Size;
be<uint16_t> AchievementCount;
};
#pragma pack(pop)
enum EXACHFlags : uint32_t
{
XACH_TYPE_COMPLETION = 1U,
XACH_TYPE_LEVELING = 2U,
XACH_TYPE_UNLOCK = 3U,
XACH_TYPE_EVENT = 4U,
XACH_TYPE_TOURNAMENT = 5U,
XACH_TYPE_CHECKPOINT = 6U,
XACH_TYPE_OTHER = 7U,
XACH_TYPE_MASK = 7U,
XACH_STATUS_UNACHIEVED = (1U << 4),
XACH_STATUS_EARNED_ONLINE = (1U << 16),
XACH_STATUS_EARNED = (1U << 17),
XACH_STATUS_EDITED = (1U << 20)
};
struct XACHEntry
{
be<uint16_t> AchievementID;
be<uint16_t> NameID;
be<uint16_t> UnlockedDescID;
be<uint16_t> LockedDescID;
be<uint32_t> ImageID;
be<uint16_t> Gamerscore;
char pad0[0x02];
be<EXACHFlags> Flags;
char pad1[0x10];
};
union XDBFTitleID
{
struct
{
be<uint16_t> u16;
char u8[0x02];
};
be<uint32_t> u32;
};
struct XDBFTitleVersion
{
be<uint16_t> Major;
be<uint16_t> Minor;
be<uint16_t> Build;
be<uint16_t> Revision;
};
enum EXDBFTitleType : uint32_t
{
XDBF_TITLE_TYPE_SYSTEM = 0,
XDBF_TITLE_TYPE_FULL = 1,
XDBF_TITLE_TYPE_DEMO = 2,
XDBF_TITLE_TYPE_DOWNLOAD = 3
};
struct XTHDHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> Size;
XDBFTitleID TitleID;
be<EXDBFTitleType> Type;
XDBFTitleVersion TitleVersion;
char pad0[0x10];
};
#pragma pack(push, 1)
struct XGAAHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> Size;
be<uint16_t> Count;
};
#pragma pack(pop)
struct XGAAEntry
{
char pad0[0x04];
be<uint16_t> AvatarAwardID;
char pad1[0x06];
XDBFTitleID TitleID;
be<uint16_t> NameID;
be<uint16_t> UnlockedDescID;
be<uint16_t> LockedDescID;
char pad2[0x02];
be<uint32_t> ImageID;
char pad3[0x08];
};
struct XSRCHeader
{
be<uint32_t> Signature;
be<uint32_t> Version;
be<uint32_t> Size;
be<uint32_t> FileNameLength;
};
struct XSRCHeader2
{
be<uint32_t> UncompressedSize;
be<uint32_t> CompressedSize;
};

View File

@@ -1,152 +0,0 @@
#include "xdbf_wrapper.h"
XDBFWrapper::XDBFWrapper(const uint8_t* buffer, size_t bufferSize) : pBuffer(buffer), BufferSize(bufferSize)
{
if (!buffer || bufferSize <= sizeof(XDBFHeader))
{
pBuffer = nullptr;
return;
}
auto seek = pBuffer;
pHeader = (XDBFHeader*)seek;
seek += sizeof(XDBFHeader);
if (pHeader->Signature != XDBF_SIGNATURE)
{
pBuffer = nullptr;
return;
}
pEntries = (XDBFEntry*)seek;
seek += sizeof(XDBFEntry) * pHeader->EntryCount;
pFiles = (XDBFFreeSpaceEntry*)seek;
seek += sizeof(XDBFFreeSpaceEntry) * pHeader->FreeSpaceTableLength;
pContent = seek;
}
XDBFBlock XDBFWrapper::GetResource(EXDBFNamespace ns, uint64_t id) const
{
for (int i = 0; i < pHeader->EntryCount; i++)
{
auto& entry = pEntries[i];
if (entry.NamespaceID == ns && entry.ResourceID == id)
{
XDBFBlock block{};
block.pBuffer = pContent + entry.Offset;
block.BufferSize = entry.Length;
return block;
}
}
return { nullptr };
}
std::string XDBFWrapper::GetString(EXDBFLanguage language, uint16_t id) const
{
auto languageBlock = GetResource(XDBF_SPA_NAMESPACE_STRING_TABLE, (uint64_t)language);
if (!languageBlock)
return "";
auto pHeader = (XSTRHeader*)languageBlock.pBuffer;
auto seek = languageBlock.pBuffer + sizeof(XSTRHeader);
for (int i = 0; i < pHeader->StringCount; i++)
{
auto entry = (XSTREntry*)seek;
seek += sizeof(XSTREntry);
if (entry->ID == id)
return std::string((const char*)seek, entry->Length);
seek += entry->Length;
}
return "";
}
std::vector<Achievement> XDBFWrapper::GetAchievements(EXDBFLanguage language) const
{
std::vector<Achievement> result;
auto achievementsBlock = GetResource(XDBF_SPA_NAMESPACE_METADATA, XACH_SIGNATURE);
if (!achievementsBlock)
return result;
auto pHeader = (XACHHeader*)achievementsBlock.pBuffer;
auto seek = achievementsBlock.pBuffer + sizeof(XACHHeader);
for (int i = 0; i < pHeader->AchievementCount; i++)
{
auto entry = (XACHEntry*)seek;
seek += sizeof(XACHEntry);
Achievement achievement{};
achievement.ID = entry->AchievementID;
achievement.Name = GetString(language, entry->NameID);
achievement.UnlockedDesc = GetString(language, entry->UnlockedDescID);
achievement.LockedDesc = GetString(language, entry->LockedDescID);
achievement.Score = entry->Gamerscore;
auto imageBlock = GetResource(XDBF_SPA_NAMESPACE_IMAGE, entry->ImageID);
if (imageBlock)
{
achievement.pImageBuffer = imageBlock.pBuffer;
achievement.ImageBufferSize = imageBlock.BufferSize;
}
result.push_back(achievement);
}
return result;
}
Achievement XDBFWrapper::GetAchievement(EXDBFLanguage language, uint16_t id) const
{
Achievement result{};
auto achievementsBlock = GetResource(XDBF_SPA_NAMESPACE_METADATA, 0x58414348);
if (!achievementsBlock)
return result;
auto pHeader = (XACHHeader*)achievementsBlock.pBuffer;
auto seek = achievementsBlock.pBuffer + sizeof(XACHHeader);
for (int i = 0; i < pHeader->AchievementCount; i++)
{
auto entry = (XACHEntry*)seek;
seek += sizeof(XACHEntry);
if (entry->AchievementID == id)
{
result.ID = entry->AchievementID;
result.Name = GetString(language, entry->NameID);
result.UnlockedDesc = GetString(language, entry->UnlockedDescID);
result.LockedDesc = GetString(language, entry->LockedDescID);
result.Score = entry->Gamerscore;
auto imageBlock = GetResource(XDBF_SPA_NAMESPACE_IMAGE, entry->ImageID);
if (imageBlock)
{
result.pImageBuffer = imageBlock.pBuffer;
result.ImageBufferSize = imageBlock.BufferSize;
}
return result;
}
}
return result;
}

View File

@@ -1,46 +0,0 @@
#pragma once
#include <vector>
#include "xdbf.h"
struct Achievement
{
uint16_t ID;
std::string Name;
std::string UnlockedDesc;
std::string LockedDesc;
const uint8_t* pImageBuffer;
size_t ImageBufferSize;
uint16_t Score;
};
struct XDBFBlock
{
const uint8_t* pBuffer;
size_t BufferSize;
operator bool() const
{
return pBuffer;
}
};
class XDBFWrapper
{
public:
const uint8_t* pBuffer;
size_t BufferSize;
const uint8_t* pContent;
const XDBFHeader* pHeader;
const XDBFEntry* pEntries;
const XDBFFreeSpaceEntry* pFiles;
XDBFWrapper() {}
XDBFWrapper(const uint8_t* pBuffer, size_t bufferSize);
XDBFBlock GetResource(EXDBFNamespace ns, uint64_t id) const;
std::string GetString(EXDBFLanguage language, uint16_t id) const;
std::vector<Achievement> GetAchievements(EXDBFLanguage language) const;
Achievement GetAchievement(EXDBFLanguage language, uint16_t id) const;
};

View File

@@ -1,350 +0,0 @@
#include "xex.h"
#include "image.h"
#include <cassert>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <aes.hpp>
#include <TinySHA1.hpp>
#include <xex_patcher.h>
#define STRINGIFY(X) #X
#define XE_EXPORT(MODULE, ORDINAL, NAME, TYPE) { (ORDINAL), "__imp__" STRINGIFY(NAME) }
#ifndef _WIN32
typedef struct _IMAGE_DOS_HEADER {
uint16_t e_magic;
uint16_t e_cblp;
uint16_t e_cp;
uint16_t e_crlc;
uint16_t e_cparhdr;
uint16_t e_minalloc;
uint16_t e_maxalloc;
uint16_t e_ss;
uint16_t e_sp;
uint16_t e_csum;
uint16_t e_ip;
uint16_t e_cs;
uint16_t e_lfarlc;
uint16_t e_ovno;
uint16_t e_res[4];
uint16_t e_oemid;
uint16_t e_oeminfo;
uint16_t e_res2[10];
uint32_t e_lfanew;
} IMAGE_DOS_HEADER, * PIMAGE_DOS_HEADER;
typedef struct _IMAGE_FILE_HEADER {
uint16_t Machine;
uint16_t NumberOfSections;
uint32_t TimeDateStamp;
uint32_t PointerToSymbolTable;
uint32_t NumberOfSymbols;
uint16_t SizeOfOptionalHeader;
uint16_t Characteristics;
} IMAGE_FILE_HEADER, * PIMAGE_FILE_HEADER;
typedef struct _IMAGE_DATA_DIRECTORY {
uint32_t VirtualAddress;
uint32_t Size;
} IMAGE_DATA_DIRECTORY, * PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_OPTIONAL_HEADER {
uint16_t Magic;
uint8_t MajorLinkerVersion;
uint8_t MinorLinkerVersion;
uint32_t SizeOfCode;
uint32_t SizeOfInitializedData;
uint32_t SizeOfUninitializedData;
uint32_t AddressOfEntryPoint;
uint32_t BaseOfCode;
uint32_t BaseOfData;
uint32_t ImageBase;
uint32_t SectionAlignment;
uint32_t FileAlignment;
uint16_t MajorOperatingSystemVersion;
uint16_t MinorOperatingSystemVersion;
uint16_t MajorImageVersion;
uint16_t MinorImageVersion;
uint16_t MajorSubsystemVersion;
uint16_t MinorSubsystemVersion;
uint32_t Win32VersionValue;
uint32_t SizeOfImage;
uint32_t SizeOfHeaders;
uint32_t CheckSum;
uint16_t Subsystem;
uint16_t DllCharacteristics;
uint32_t SizeOfStackReserve;
uint32_t SizeOfStackCommit;
uint32_t SizeOfHeapReserve;
uint32_t SizeOfHeapCommit;
uint32_t LoaderFlags;
uint32_t NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_NT_HEADERS {
uint32_t Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, * PIMAGE_NT_HEADERS32;
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
uint8_t Name[IMAGE_SIZEOF_SHORT_NAME];
union {
uint32_t PhysicalAddress;
uint32_t VirtualSize;
} Misc;
uint32_t VirtualAddress;
uint32_t SizeOfRawData;
uint32_t PointerToRawData;
uint32_t PointerToRelocations;
uint32_t PointerToLinenumbers;
uint16_t NumberOfRelocations;
uint16_t NumberOfLinenumbers;
uint32_t Characteristics;
} IMAGE_SECTION_HEADER, * PIMAGE_SECTION_HEADER;
#define IMAGE_SCN_CNT_CODE 0x00000020
#endif
std::unordered_map<size_t, const char*> XamExports =
{
#include "xbox/xam_table.inc"
};
std::unordered_map<size_t, const char*> XboxKernelExports =
{
#include "xbox/xboxkrnl_table.inc"
};
Image Xex2LoadImage(const uint8_t* data, size_t dataSize)
{
auto* header = reinterpret_cast<const Xex2Header*>(data);
auto* security = reinterpret_cast<const Xex2SecurityInfo*>(data + header->securityOffset);
const auto* fileFormatInfo = reinterpret_cast<const Xex2OptFileFormatInfo*>(getOptHeaderPtr(data, XEX_HEADER_FILE_FORMAT_INFO));
Image image{};
std::unique_ptr<uint8_t[]> result{};
size_t imageSize = security->imageSize;
// Decompress image
if (fileFormatInfo != nullptr)
{
assert(fileFormatInfo->compressionType <= XEX_COMPRESSION_NORMAL);
std::unique_ptr<uint8_t[]> decryptedData;
const uint8_t* srcData = nullptr;
if (fileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL)
{
constexpr uint32_t KeySize = 16;
AES_ctx aesContext;
uint8_t decryptedKey[KeySize];
memcpy(decryptedKey, security->aesKey, KeySize);
AES_init_ctx_iv(&aesContext, Xex2RetailKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedKey, KeySize);
decryptedData = std::make_unique<uint8_t[]>(dataSize - header->headerSize);
memcpy(decryptedData.get(), data + header->headerSize, dataSize - header->headerSize);
AES_init_ctx_iv(&aesContext, decryptedKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedData.get(), dataSize - header->headerSize);
srcData = decryptedData.get();
}
else
{
srcData = data + header->headerSize;
}
if (fileFormatInfo->compressionType == XEX_COMPRESSION_NONE)
{
result = std::make_unique<uint8_t[]>(imageSize);
memcpy(result.get(), srcData, imageSize);
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_BASIC)
{
auto* blocks = reinterpret_cast<const Xex2FileBasicCompressionBlock*>(fileFormatInfo + 1);
const size_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionInfo)) - 1;
imageSize = 0;
for (size_t i = 0; i < numBlocks; i++)
{
imageSize += blocks[i].dataSize + blocks[i].zeroSize;
}
result = std::make_unique<uint8_t[]>(imageSize);
auto* destData = result.get();
for (size_t i = 0; i < numBlocks; i++)
{
memcpy(destData, srcData, blocks[i].dataSize);
srcData += blocks[i].dataSize;
destData += blocks[i].dataSize;
memset(destData, 0, blocks[i].zeroSize);
destData += blocks[i].zeroSize;
}
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL)
{
result = std::make_unique<uint8_t[]>(imageSize);
auto* destData = result.get();
const Xex2CompressedBlockInfo* blocks = &((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->firstBlock;
const uint32_t headerSize = header->headerSize.get();
const uint32_t exeLength = dataSize - headerSize;
const uint8_t* exeBuffer = srcData;
auto compressBuffer = std::make_unique<uint8_t[]>(exeLength);
const uint8_t* p = NULL;
uint8_t* d = NULL;
sha1::SHA1 s;
p = exeBuffer;
d = compressBuffer.get();
uint8_t blockCalcedDigest[0x14];
while (blocks->blockSize)
{
const uint8_t* pNext = p + blocks->blockSize;
const auto* nextBlock = (const Xex2CompressedBlockInfo*)p;
s.reset();
s.processBytes(p, blocks->blockSize);
s.finalize(blockCalcedDigest);
if (memcmp(blockCalcedDigest, blocks->blockHash, 0x14) != 0)
return {};
p += 4;
p += 20;
while (true)
{
const size_t chunkSize = (p[0] << 8) | p[1];
p += 2;
if (!chunkSize)
break;
memcpy(d, p, chunkSize);
p += chunkSize;
d += chunkSize;
}
p = pNext;
blocks = nextBlock;
}
int resultCode = 0;
uint32_t uncompressedSize = security->imageSize;
uint8_t* buffer = destData;
resultCode = lzxDecompress(compressBuffer.get(), d - compressBuffer.get(), buffer, uncompressedSize, ((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->windowSize, nullptr, 0);
if (resultCode)
return {};
}
}
image.data = std::move(result);
image.size = security->imageSize;
// Map image
const auto* dosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(image.data.get());
const auto* ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS32*>(image.data.get() + dosHeader->e_lfanew);
image.base = security->loadAddress;
const void* xex2BaseAddressPtr = getOptHeaderPtr(data, XEX_HEADER_IMAGE_BASE_ADDRESS);
if (xex2BaseAddressPtr != nullptr)
{
image.base = *reinterpret_cast<const be<uint32_t>*>(xex2BaseAddressPtr);
}
const void* xex2EntryPointPtr = getOptHeaderPtr(data, XEX_HEADER_ENTRY_POINT);
if (xex2EntryPointPtr != nullptr)
{
image.entry_point = *reinterpret_cast<const be<uint32_t>*>(xex2EntryPointPtr);
}
const auto numSections = ntHeaders->FileHeader.NumberOfSections;
const auto* sections = reinterpret_cast<const IMAGE_SECTION_HEADER*>(ntHeaders + 1);
for (size_t i = 0; i < numSections; i++)
{
const auto& section = sections[i];
uint8_t flags{};
if (section.Characteristics & IMAGE_SCN_CNT_CODE)
{
flags |= SectionFlags_Code;
}
image.Map(reinterpret_cast<const char*>(section.Name), section.VirtualAddress,
section.Misc.VirtualSize, flags, image.data.get() + section.VirtualAddress);
}
auto* imports = reinterpret_cast<const Xex2ImportHeader*>(getOptHeaderPtr(data, XEX_HEADER_IMPORT_LIBRARIES));
if (imports != nullptr)
{
std::vector<std::string_view> stringTable;
auto* pStrTable = reinterpret_cast<const char*>(imports + 1);
size_t paddedStringOffset = 0;
for (size_t i = 0; i < imports->numImports; i++)
{
stringTable.emplace_back(pStrTable + paddedStringOffset);
// pad the offset to the next multiple of 4
paddedStringOffset += ((stringTable.back().length() + 1) + 3) & ~3;
}
auto* library = (Xex2ImportLibrary*)(((char*)imports) + sizeof(Xex2ImportHeader) + imports->sizeOfStringTable);
for (size_t i = 0; i < stringTable.size(); i++)
{
auto* descriptors = (Xex2ImportDescriptor*)(library + 1);
static std::unordered_map<size_t, const char*> DummyExports;
const std::unordered_map<size_t, const char*>* names = &DummyExports;
if (stringTable[i] == "xam.xex")
{
names = &XamExports;
}
else if (stringTable[i] == "xboxkrnl.exe")
{
names = &XboxKernelExports;
}
for (size_t im = 0; im < library->numberOfImports; im++)
{
auto originalThunk = (Xex2ThunkData*)image.Find(descriptors[im].firstThunk);
auto originalData = originalThunk;
originalData->data = ByteSwap(originalData->data);
if (originalData->originalData.type != 0)
{
uint32_t thunk[4] = { 0x00000060, 0x00000060, 0x00000060, 0x2000804E };
auto name = names->find(originalData->originalData.ordinal);
if (name != names->end())
{
image.symbols.insert({ name->second, descriptors[im].firstThunk, sizeof(thunk), Symbol_Function });
}
memcpy(originalThunk, thunk, sizeof(thunk));
}
}
library = (Xex2ImportLibrary*)((char*)(library + 1) + library->numberOfImports * sizeof(Xex2ImportDescriptor));
}
}
return image;
}

View File

@@ -1,267 +0,0 @@
#pragma once
#include <memory>
#include "xbox.h"
inline constexpr uint8_t Xex2RetailKey[16] = { 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91 };
inline constexpr uint8_t AESBlankIV[16] = {};
enum Xex2ModuleFlags
{
XEX_MODULE_MODULE_PATCH = 0x10,
XEX_MODULE_PATCH_FULL = 0x20,
XEX_MODULE_PATCH_DELTA = 0x40,
};
enum Xex2HeaderKeys
{
XEX_HEADER_RESOURCE_INFO = 0x000002FF,
XEX_HEADER_FILE_FORMAT_INFO = 0x000003FF,
XEX_HEADER_DELTA_PATCH_DESCRIPTOR = 0x000005FF,
XEX_HEADER_BASE_REFERENCE = 0x00000405,
XEX_HEADER_BOUNDING_PATH = 0x000080FF,
XEX_HEADER_DEVICE_ID = 0x00008105,
XEX_HEADER_ORIGINAL_BASE_ADDRESS = 0x00010001,
XEX_HEADER_ENTRY_POINT = 0x00010100,
XEX_HEADER_IMAGE_BASE_ADDRESS = 0x00010201,
XEX_HEADER_IMPORT_LIBRARIES = 0x000103FF,
XEX_HEADER_CHECKSUM_TIMESTAMP = 0x00018002,
XEX_HEADER_ENABLED_FOR_CALLCAP = 0x00018102,
XEX_HEADER_ENABLED_FOR_FASTCAP = 0x00018200,
XEX_HEADER_ORIGINAL_PE_NAME = 0x000183FF,
XEX_HEADER_STATIC_LIBRARIES = 0x000200FF,
XEX_HEADER_TLS_INFO = 0x00020104,
XEX_HEADER_DEFAULT_STACK_SIZE = 0x00020200,
XEX_HEADER_DEFAULT_FILESYSTEM_CACHE_SIZE = 0x00020301,
XEX_HEADER_DEFAULT_HEAP_SIZE = 0x00020401,
XEX_HEADER_PAGE_HEAP_SIZE_AND_FLAGS = 0x00028002,
XEX_HEADER_SYSTEM_FLAGS = 0x00030000,
XEX_HEADER_EXECUTION_INFO = 0x00040006,
XEX_HEADER_TITLE_WORKSPACE_SIZE = 0x00040201,
XEX_HEADER_GAME_RATINGS = 0x00040310,
XEX_HEADER_LAN_KEY = 0x00040404,
XEX_HEADER_XBOX360_LOGO = 0x000405FF,
XEX_HEADER_MULTIDISC_MEDIA_IDS = 0x000406FF,
XEX_HEADER_ALTERNATE_TITLE_IDS = 0x000407FF,
XEX_HEADER_ADDITIONAL_TITLE_MEMORY = 0x00040801,
XEX_HEADER_EXPORTS_BY_NAME = 0x00E10402,
};
enum Xex2EncryptionType
{
XEX_ENCRYPTION_NONE = 0,
XEX_ENCRYPTION_NORMAL = 1,
};
enum Xex2CompressionType
{
XEX_COMPRESSION_NONE = 0,
XEX_COMPRESSION_BASIC = 1,
XEX_COMPRESSION_NORMAL = 2,
XEX_COMPRESSION_DELTA = 3,
};
enum Xex2SectionType
{
XEX_SECTION_CODE = 1,
XEX_SECTION_DATA = 2,
XEX_SECTION_READONLY_DATA = 3,
};
enum Xex2ThunkTypes
{
XEX_THUNK_VARIABLE = 0,
XEX_THUNK_FUNCTION = 1,
};
struct Xex2OptHeader
{
be<uint32_t> key;
union
{
be<uint32_t> value;
be<uint32_t> offset;
};
};
struct Xex2Header
{
be<uint32_t> magic;
be<uint32_t> moduleFlags;
be<uint32_t> headerSize;
be<uint32_t> reserved;
be<uint32_t> securityOffset;
be<uint32_t> headerCount;
};
struct Xex2PageDescriptor
{
union
{
// Must be endian-swapped before reading the bitfield.
uint32_t beValue;
struct
{
uint32_t info : 4;
uint32_t pageCount : 28;
};
};
char dataDigest[0x14];
};
struct Xex2SecurityInfo
{
be<uint32_t> headerSize;
be<uint32_t> imageSize;
char rsaSignature[0x100];
be<uint32_t> unknown;
be<uint32_t> imageFlags;
be<uint32_t> loadAddress;
char sectionDigest[0x14];
be<uint32_t> importTableCount;
char importTableDigest[0x14];
char xgd2MediaId[0x10];
char aesKey[0x10];
be<uint32_t> exportTable;
char headerDigest[0x14];
be<uint32_t> region;
be<uint32_t> allowedMediaTypes;
be<uint32_t> pageDescriptorCount;
};
struct Xex2DeltaPatch
{
be<uint32_t> oldAddress;
be<uint32_t> newAddress;
be<uint16_t> uncompressedLength;
be<uint16_t> compressedLength;
char patchData[1];
};
struct Xex2OptDeltaPatchDescriptor
{
be<uint32_t> size;
be<uint32_t> targetVersionValue;
be<uint32_t> sourceVersionValue;
uint8_t digestSource[0x14];
uint8_t imageKeySource[0x10];
be<uint32_t> sizeOfTargetHeaders;
be<uint32_t> deltaHeadersSourceOffset;
be<uint32_t> deltaHeadersSourceSize;
be<uint32_t> deltaHeadersTargetOffset;
be<uint32_t> deltaImageSourceOffset;
be<uint32_t> deltaImageSourceSize;
be<uint32_t> deltaImageTargetOffset;
Xex2DeltaPatch info;
};
struct Xex2FileBasicCompressionBlock
{
be<uint32_t> dataSize;
be<uint32_t> zeroSize;
};
struct Xex2FileBasicCompressionInfo
{
Xex2FileBasicCompressionBlock firstBlock;
};
struct Xex2CompressedBlockInfo
{
be<uint32_t> blockSize;
uint8_t blockHash[20];
};
struct Xex2FileNormalCompressionInfo
{
be<uint32_t> windowSize;
Xex2CompressedBlockInfo firstBlock;
};
struct Xex2OptFileFormatInfo
{
be<uint32_t> infoSize;
be<uint16_t> encryptionType;
be<uint16_t> compressionType;
};
struct Xex2ImportHeader
{
be<uint32_t> sizeOfHeader;
be<uint32_t> sizeOfStringTable;
be<uint32_t> numImports;
};
struct Xex2ImportLibrary
{
be<uint32_t> size;
char nextImportDigest[0x14];
be<uint32_t> id;
be<uint32_t> version;
be<uint32_t> minVersion;
be<uint16_t> name;
be<uint16_t> numberOfImports;
};
struct Xex2ImportDescriptor
{
be<uint32_t> firstThunk; // VA XEX_THUNK_DATA
};
struct Xex2ThunkData
{
union
{
struct
{
uint16_t ordinal : 16;
uint16_t hint : 8;
uint16_t type : 8;
} originalData;
be<uint32_t> ordinal;
be<uint32_t> function;
be<uint32_t> addressOfData;
// For easier swapping
uint32_t data;
};
};
struct Xex2ResourceInfo
{
be<uint32_t> sizeOfHeader;
uint8_t resourceID[8];
be<uint32_t> offset;
be<uint32_t> sizeOfData;
};
inline const void* getOptHeaderPtr(const uint8_t* moduleBytes, uint32_t headerKey)
{
const Xex2Header* xex2Header = (const Xex2Header*)(moduleBytes);
for (uint32_t i = 0; i < xex2Header->headerCount; i++)
{
const Xex2OptHeader& optHeader = ((const Xex2OptHeader*)(xex2Header + 1))[i];
if (optHeader.key == headerKey)
{
if((headerKey & 0xFF) == 0)
{
return reinterpret_cast<const uint32_t *>(&optHeader.value);
}
else if ((headerKey & 0xFF) == 1)
{
return reinterpret_cast<const void *>(&optHeader.value);
}
else
{
return reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(moduleBytes) + optHeader.offset);
}
}
}
return nullptr;
}
struct Image;
Image Xex2LoadImage(const uint8_t* data, size_t dataSize);

View File

@@ -1,568 +0,0 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/cpu/xex_module.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xex_patcher.h"
#include "xex.h"
#include <bit>
#include <cassert>
#include <climits>
#include <fstream>
#include <aes.hpp>
#include <lzx.h>
#include <mspack.h>
#include <TinySHA1.hpp>
#include "memory_mapped_file.h"
struct mspack_memory_file
{
mspack_system sys;
void *buffer;
size_t bufferSize;
size_t offset;
};
static mspack_memory_file *mspack_memory_open(mspack_system *sys, void *buffer, size_t bufferSize)
{
assert(bufferSize < INT_MAX);
if (bufferSize >= INT_MAX)
{
return nullptr;
}
mspack_memory_file *memoryFile = (mspack_memory_file *)(std::calloc(1, sizeof(mspack_memory_file)));
if (memoryFile == nullptr)
{
return memoryFile;
}
memoryFile->buffer = buffer;
memoryFile->bufferSize = bufferSize;
memoryFile->offset = 0;
return memoryFile;
}
static void mspack_memory_close(mspack_memory_file *file)
{
std::free(file);
}
static int mspack_memory_read(mspack_file *file, void *buffer, int chars)
{
mspack_memory_file *memoryFile = (mspack_memory_file *)(file);
const size_t remaining = memoryFile->bufferSize - memoryFile->offset;
const size_t total = std::min(size_t(chars), remaining);
std::memcpy(buffer, (uint8_t *)(memoryFile->buffer) + memoryFile->offset, total);
memoryFile->offset += total;
return int(total);
}
static int mspack_memory_write(mspack_file *file, void *buffer, int chars)
{
mspack_memory_file *memoryFile = (mspack_memory_file *)(file);
const size_t remaining = memoryFile->bufferSize - memoryFile->offset;
const size_t total = std::min(size_t(chars), remaining);
std::memcpy((uint8_t *)(memoryFile->buffer) + memoryFile->offset, buffer, total);
memoryFile->offset += total;
return int(total);
}
static void *mspack_memory_alloc(mspack_system *sys, size_t chars)
{
return std::calloc(chars, 1);
}
static void mspack_memory_free(void *ptr)
{
std::free(ptr);
}
static void mspack_memory_copy(void *src, void *dest, size_t chars)
{
std::memcpy(dest, src, chars);
}
static mspack_system *mspack_memory_sys_create()
{
auto sys = (mspack_system *)(std::calloc(1, sizeof(mspack_system)));
if (!sys)
{
return nullptr;
}
sys->read = mspack_memory_read;
sys->write = mspack_memory_write;
sys->alloc = mspack_memory_alloc;
sys->free = mspack_memory_free;
sys->copy = mspack_memory_copy;
return sys;
}
static void mspack_memory_sys_destroy(struct mspack_system *sys)
{
free(sys);
}
#if defined(_WIN32)
inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex)
{
return _BitScanForward((unsigned long *)(outFirstSetIndex), v) != 0;
}
inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex)
{
return _BitScanForward64((unsigned long *)(outFirstSetIndex), v) != 0;
}
#else
inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex)
{
int i = ffs(v);
*outFirstSetIndex = i - 1;
return i != 0;
}
inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex)
{
int i = __builtin_ffsll(v);
*outFirstSetIndex = i - 1;
return i != 0;
}
#endif
static int lzxDecompress(const void *lzxData, size_t lzxLength, void *dst, size_t dstLength, uint32_t windowSize, void *windowData, size_t windowDataLength)
{
int resultCode = 1;
uint32_t windowBits;
if (!bitScanForward(windowSize, &windowBits)) {
return resultCode;
}
mspack_system *sys = mspack_memory_sys_create();
mspack_memory_file *lzxSrc = mspack_memory_open(sys, (void *)(lzxData), lzxLength);
mspack_memory_file *lzxDst = mspack_memory_open(sys, dst, dstLength);
lzxd_stream *lzxd = lzxd_init(sys, (mspack_file *)(lzxSrc), (mspack_file *)(lzxDst), windowBits, 0, 0x8000, dstLength, 0);
if (lzxd != nullptr) {
if (windowData != nullptr) {
size_t paddingLength = windowSize - windowDataLength;
std::memset(&lzxd->window[0], 0, paddingLength);
std::memcpy(&lzxd->window[paddingLength], windowData, windowDataLength);
lzxd->ref_data_size = windowSize;
}
resultCode = lzxd_decompress(lzxd, dstLength);
lzxd_free(lzxd);
}
if (lzxSrc) {
mspack_memory_close(lzxSrc);
}
if (lzxDst) {
mspack_memory_close(lzxDst);
}
if (sys) {
mspack_memory_sys_destroy(sys);
}
return resultCode;
}
static int lzxDeltaApplyPatch(const Xex2DeltaPatch *deltaPatch, uint32_t patchLength, uint32_t windowSize, uint8_t *dstData)
{
const void *patchEnd = (const uint8_t *)(deltaPatch) + patchLength;
const Xex2DeltaPatch *curPatch = deltaPatch;
while (patchEnd > curPatch)
{
int patchSize = -4;
if (curPatch->compressedLength == 0 && curPatch->uncompressedLength == 0 && curPatch->newAddress == 0 && curPatch->oldAddress == 0)
{
// End of patch.
break;
}
switch (curPatch->compressedLength)
{
case 0:
// Set the data to zeroes.
std::memset(&dstData[curPatch->newAddress], 0, curPatch->uncompressedLength);
break;
case 1:
// Move the data.
std::memcpy(&dstData[curPatch->newAddress], &dstData[curPatch->oldAddress], curPatch->uncompressedLength);
break;
default:
// Decompress the data into the destination.
patchSize = curPatch->compressedLength - 4;
int result = lzxDecompress(curPatch->patchData, curPatch->compressedLength, &dstData[curPatch->newAddress], curPatch->uncompressedLength, windowSize, &dstData[curPatch->oldAddress], curPatch->uncompressedLength);
if (result != 0)
{
return result;
}
break;
}
curPatch++;
curPatch = (const Xex2DeltaPatch *)((const uint8_t *)(curPatch) + patchSize);
}
return 0;
}
XexPatcher::Result XexPatcher::apply(const uint8_t* xexBytes, size_t xexBytesSize, const uint8_t* patchBytes, size_t patchBytesSize, std::vector<uint8_t> &outBytes, bool skipData)
{
// Validate headers.
static const char Xex2Magic[] = "XEX2";
const Xex2Header *xexHeader = (const Xex2Header *)(xexBytes);
if (memcmp(xexBytes, Xex2Magic, 4) != 0)
{
return Result::XexFileInvalid;
}
const Xex2Header *patchHeader = (const Xex2Header *)(patchBytes);
if (memcmp(patchBytes, Xex2Magic, 4) != 0)
{
return Result::PatchFileInvalid;
}
if ((patchHeader->moduleFlags & (XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA | XEX_MODULE_PATCH_FULL)) == 0)
{
return Result::PatchFileInvalid;
}
// Validate patch.
const Xex2OptDeltaPatchDescriptor *patchDescriptor = (const Xex2OptDeltaPatchDescriptor *)(getOptHeaderPtr(patchBytes, XEX_HEADER_DELTA_PATCH_DESCRIPTOR));
if (patchDescriptor == nullptr)
{
return Result::PatchFileInvalid;
}
const Xex2OptFileFormatInfo *patchFileFormatInfo = (const Xex2OptFileFormatInfo *)(getOptHeaderPtr(patchBytes, XEX_HEADER_FILE_FORMAT_INFO));
if (patchFileFormatInfo == nullptr)
{
return Result::PatchFileInvalid;
}
if (patchFileFormatInfo->compressionType != XEX_COMPRESSION_DELTA)
{
return Result::PatchFileInvalid;
}
if (patchDescriptor->deltaHeadersSourceOffset > xexHeader->headerSize)
{
return Result::PatchIncompatible;
}
if (patchDescriptor->deltaHeadersSourceSize > (xexHeader->headerSize - patchDescriptor->deltaHeadersSourceOffset))
{
return Result::PatchIncompatible;
}
if (patchDescriptor->deltaHeadersTargetOffset > patchDescriptor->sizeOfTargetHeaders)
{
return Result::PatchIncompatible;
}
uint32_t deltaTargetSize = patchDescriptor->sizeOfTargetHeaders - patchDescriptor->deltaHeadersTargetOffset;
if (patchDescriptor->deltaHeadersSourceSize > deltaTargetSize)
{
return Result::PatchIncompatible;
}
// Apply patch.
uint32_t headerTargetSize = patchDescriptor->sizeOfTargetHeaders;
if (headerTargetSize == 0)
{
headerTargetSize = patchDescriptor->deltaHeadersTargetOffset + patchDescriptor->deltaHeadersSourceSize;
}
// Create the bytes for the new XEX header. Copy over the existing data.
uint32_t newXexHeaderSize = std::max(headerTargetSize, xexHeader->headerSize.get());
outBytes.resize(newXexHeaderSize);
memset(outBytes.data(), 0, newXexHeaderSize);
memcpy(outBytes.data(), xexBytes, headerTargetSize);
Xex2Header *newXexHeader = (Xex2Header *)(outBytes.data());
if (patchDescriptor->deltaHeadersSourceOffset > 0)
{
memcpy(&outBytes[patchDescriptor->deltaHeadersTargetOffset], &outBytes[patchDescriptor->deltaHeadersSourceOffset], patchDescriptor->deltaHeadersSourceSize);
}
int resultCode = lzxDeltaApplyPatch(&patchDescriptor->info, patchDescriptor->size, ((const Xex2FileNormalCompressionInfo*)(patchFileFormatInfo + 1))->windowSize, outBytes.data());
if (resultCode != 0)
{
return Result::PatchFailed;
}
// Make the header the specified size by the patch.
outBytes.resize(headerTargetSize);
newXexHeader = (Xex2Header *)(outBytes.data());
// Copy the rest of the data.
const Xex2SecurityInfo *newSecurityInfo = (const Xex2SecurityInfo *)(&outBytes[newXexHeader->securityOffset]);
outBytes.resize(outBytes.size() + newSecurityInfo->imageSize);
memset(&outBytes[headerTargetSize], 0, outBytes.size() - headerTargetSize);
memcpy(&outBytes[headerTargetSize], &xexBytes[xexHeader->headerSize], xexBytesSize - xexHeader->headerSize);
newXexHeader = (Xex2Header *)(outBytes.data());
newSecurityInfo = (const Xex2SecurityInfo *)(&outBytes[newXexHeader->securityOffset]);
// Decrypt the keys and validate that the patch is compatible with the base file.
constexpr uint32_t KeySize = 16;
const Xex2SecurityInfo *originalSecurityInfo = (const Xex2SecurityInfo *)(&xexBytes[xexHeader->securityOffset]);
const Xex2SecurityInfo *patchSecurityInfo = (const Xex2SecurityInfo *)(&patchBytes[patchHeader->securityOffset]);
uint8_t decryptedOriginalKey[KeySize];
uint8_t decryptedNewKey[KeySize];
uint8_t decryptedPatchKey[KeySize];
uint8_t decrpytedImageKeySource[KeySize];
memcpy(decryptedOriginalKey, originalSecurityInfo->aesKey, KeySize);
memcpy(decryptedNewKey, newSecurityInfo->aesKey, KeySize);
memcpy(decryptedPatchKey, patchSecurityInfo->aesKey, KeySize);
memcpy(decrpytedImageKeySource, patchDescriptor->imageKeySource, KeySize);
AES_ctx aesContext;
AES_init_ctx_iv(&aesContext, Xex2RetailKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedOriginalKey, KeySize);
AES_ctx_set_iv(&aesContext, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedNewKey, KeySize);
AES_init_ctx_iv(&aesContext, decryptedNewKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decryptedPatchKey, KeySize);
AES_ctx_set_iv(&aesContext, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, decrpytedImageKeySource, KeySize);
// Validate the patch's key matches the one from the original XEX.
if (memcmp(decrpytedImageKeySource, decryptedOriginalKey, KeySize) != 0)
{
return Result::PatchIncompatible;
}
// Don't process the rest of the patch.
if (skipData)
{
return Result::Success;
}
// Decrypt base XEX if necessary.
const Xex2OptFileFormatInfo *fileFormatInfo = (const Xex2OptFileFormatInfo *)(getOptHeaderPtr(xexBytes, XEX_HEADER_FILE_FORMAT_INFO));
if (fileFormatInfo == nullptr)
{
return Result::XexFileInvalid;
}
if (fileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL)
{
AES_init_ctx_iv(&aesContext, decryptedOriginalKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, &outBytes[headerTargetSize], xexBytesSize - xexHeader->headerSize);
}
else if (fileFormatInfo->encryptionType != XEX_ENCRYPTION_NONE)
{
return Result::XexFileInvalid;
}
// Decompress base XEX if necessary.
if (fileFormatInfo->compressionType == XEX_COMPRESSION_BASIC)
{
const Xex2FileBasicCompressionBlock *blocks = &((const Xex2FileBasicCompressionInfo*)(fileFormatInfo + 1))->firstBlock;
int32_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionBlock)) - 1;
int32_t baseCompressedSize = 0;
int32_t baseImageSize = 0;
for (int32_t i = 0; i < numBlocks; i++) {
baseCompressedSize += blocks[i].dataSize;
baseImageSize += blocks[i].dataSize + blocks[i].zeroSize;
}
if (outBytes.size() < (headerTargetSize + baseImageSize))
{
return Result::XexFileInvalid;
}
// Reverse iteration allows to perform this decompression in place.
uint8_t *srcDataCursor = outBytes.data() + headerTargetSize + baseCompressedSize;
uint8_t *outDataCursor = outBytes.data() + headerTargetSize + baseImageSize;
for (int32_t i = numBlocks - 1; i >= 0; i--)
{
outDataCursor -= blocks[i].zeroSize;
memset(outDataCursor, 0, blocks[i].zeroSize);
outDataCursor -= blocks[i].dataSize;
srcDataCursor -= blocks[i].dataSize;
memmove(outDataCursor, srcDataCursor, blocks[i].dataSize);
}
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL)
{
const Xex2CompressedBlockInfo* blocks = &((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->firstBlock;
const uint32_t exeLength = xexBytesSize - xexHeader->headerSize.get();
const uint8_t* exeBuffer = &outBytes[headerTargetSize];
auto compressBuffer = std::make_unique<uint8_t[]>(exeLength);
const uint8_t* p = NULL;
uint8_t* d = NULL;
sha1::SHA1 s;
p = exeBuffer;
d = compressBuffer.get();
uint8_t blockCalcedDigest[0x14];
while (blocks->blockSize)
{
const uint8_t* pNext = p + blocks->blockSize;
const auto* nextBlock = (const Xex2CompressedBlockInfo*)p;
s.reset();
s.processBytes(p, blocks->blockSize);
s.finalize(blockCalcedDigest);
if (memcmp(blockCalcedDigest, blocks->blockHash, 0x14) != 0)
return Result::PatchFailed;
p += 4;
p += 20;
while (true)
{
const size_t chunkSize = (p[0] << 8) | p[1];
p += 2;
if (!chunkSize)
break;
memcpy(d, p, chunkSize);
p += chunkSize;
d += chunkSize;
}
p = pNext;
blocks = nextBlock;
}
int resultCode = 0;
uint32_t uncompressedSize = originalSecurityInfo->imageSize;
uint8_t* buffer = outBytes.data() + newXexHeaderSize;
resultCode = lzxDecompress(compressBuffer.get(), d - compressBuffer.get(), buffer, uncompressedSize, ((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->windowSize, nullptr, 0);
if (resultCode)
return Result::PatchFailed;
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_DELTA)
{
return Result::XexFileUnsupported;
}
else if (fileFormatInfo->compressionType != XEX_COMPRESSION_NONE)
{
return Result::XexFileInvalid;
}
Xex2OptFileFormatInfo *newFileFormatInfo = (Xex2OptFileFormatInfo *)(getOptHeaderPtr(outBytes.data(), XEX_HEADER_FILE_FORMAT_INFO));
if (newFileFormatInfo == nullptr)
{
return Result::PatchFailed;
}
// Update the header to indicate no encryption or compression is used.
newFileFormatInfo->encryptionType = XEX_ENCRYPTION_NONE;
newFileFormatInfo->compressionType = XEX_COMPRESSION_NONE;
// Copy and decrypt patch data if necessary.
std::vector<uint8_t> patchData;
patchData.resize(patchBytesSize - patchHeader->headerSize);
memcpy(patchData.data(), &patchBytes[patchHeader->headerSize], patchData.size());
if (patchFileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL)
{
AES_init_ctx_iv(&aesContext, decryptedPatchKey, AESBlankIV);
AES_CBC_decrypt_buffer(&aesContext, patchData.data(), patchData.size());
}
else if (patchFileFormatInfo->encryptionType != XEX_ENCRYPTION_NONE)
{
return Result::PatchFileInvalid;
}
const Xex2CompressedBlockInfo *currentBlock = &((const Xex2FileNormalCompressionInfo*)(patchFileFormatInfo + 1))->firstBlock;
uint8_t *outExe = &outBytes[newXexHeader->headerSize];
if (patchDescriptor->deltaImageSourceOffset > 0)
{
memcpy(&outExe[patchDescriptor->deltaImageTargetOffset], &outExe[patchDescriptor->deltaImageSourceOffset], patchDescriptor->deltaImageSourceSize);
}
static const uint32_t DigestSize = 20;
uint8_t sha1Digest[DigestSize];
sha1::SHA1 sha1Context;
uint8_t *patchDataCursor = patchData.data();
while (currentBlock->blockSize > 0)
{
const Xex2CompressedBlockInfo *nextBlock = (const Xex2CompressedBlockInfo *)(patchDataCursor);
// Hash and validate the block.
sha1Context.reset();
sha1Context.processBytes(patchDataCursor, currentBlock->blockSize);
sha1Context.finalize(sha1Digest);
if (memcmp(sha1Digest, currentBlock->blockHash, DigestSize) != 0)
{
return Result::PatchFailed;
}
patchDataCursor += 24;
// Apply the block's patch data.
uint32_t blockDataSize = currentBlock->blockSize - 24;
if (lzxDeltaApplyPatch((const Xex2DeltaPatch *)(patchDataCursor), blockDataSize, ((const Xex2FileNormalCompressionInfo*)(patchFileFormatInfo + 1))->windowSize, outExe) != 0)
{
return Result::PatchFailed;
}
patchDataCursor += blockDataSize;
currentBlock = nextBlock;
}
return Result::Success;
}
XexPatcher::Result XexPatcher::apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath)
{
MemoryMappedFile baseXexFile(baseXexPath);
MemoryMappedFile patchFile(patchXexPath);
if (!baseXexFile.isOpen() || !patchFile.isOpen())
{
return Result::FileOpenFailed;
}
std::vector<uint8_t> newXexBytes;
Result result = apply(baseXexFile.data(), baseXexFile.size(), patchFile.data(), patchFile.size(), newXexBytes, false);
if (result != Result::Success)
{
return result;
}
std::ofstream newXexFile(newXexPath, std::ios::binary);
if (!newXexFile.is_open())
{
return Result::FileOpenFailed;
}
newXexFile.write((const char *)(newXexBytes.data()), newXexBytes.size());
newXexFile.close();
if (newXexFile.bad())
{
std::filesystem::remove(newXexPath);
return Result::FileWriteFailed;
}
return Result::Success;
}

View File

@@ -1,37 +0,0 @@
// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/cpu/xex_module.cc
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#pragma once
#include <cstdint>
#include <filesystem>
#include <span>
#include <vector>
extern int lzxDecompress(const void* lzxData, size_t lzxLength, void* dst, size_t dstLength, uint32_t windowSize, void* windowData, size_t windowDataLength);
struct XexPatcher
{
enum class Result {
Success,
FileOpenFailed,
FileWriteFailed,
XexFileUnsupported,
XexFileInvalid,
PatchFileInvalid,
PatchIncompatible,
PatchFailed,
PatchUnsupported
};
static Result apply(const uint8_t* xexBytes, size_t xexBytesSize, const uint8_t* patchBytes, size_t patchBytesSize, std::vector<uint8_t> &outBytes, bool skipData);
static Result apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath);
};

84
cmake/bin2h.cmake Normal file
View File

@@ -0,0 +1,84 @@
# https://github.com/sivachandran/cmake-bin2h
include(CMakeParseArguments)
# Function to wrap a given string into multiple lines at the given column position.
# Parameters:
# VARIABLE - The name of the CMake variable holding the string.
# AT_COLUMN - The column position at which string will be wrapped.
function(WRAP_STRING)
set(oneValueArgs VARIABLE AT_COLUMN)
cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN})
string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength)
math(EXPR offset "0")
while(stringLength GREATER 0)
if(stringLength GREATER ${WRAP_STRING_AT_COLUMN})
math(EXPR length "${WRAP_STRING_AT_COLUMN}")
else()
math(EXPR length "${stringLength}")
endif()
string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line)
set(lines "${lines}\n${line}")
math(EXPR stringLength "${stringLength} - ${length}")
math(EXPR offset "${offset} + ${length}")
endwhile()
set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE)
endfunction()
# Function to embed contents of a file as byte array in C/C++ header file(.h). The header file
# will contain a byte array and integer variable holding the size of the array.
# Parameters
# SOURCE_FILE - The path of source file whose contents will be embedded in the header file.
# VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append
# to this name and will be used a variable name for size variable.
# HEADER_FILE - The path of header file.
# ARRAY_TYPE - The type of each element of the array in the header file.
# APPEND - If specified appends to the header file instead of overwriting it
# NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be
# useful if the source file is a text file and we want to use the file contents
# as string. But the size variable holds size of the byte array without this
# null byte.
# Usage:
# bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" ARRAY_TYPE "char" VARIABLE_NAME "LOGO_PNG")
function(BIN2H)
set(options APPEND NULL_TERMINATE)
set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE ARRAY_TYPE)
cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN})
# reads source file contents as hex string
file(READ ${BIN2H_SOURCE_FILE} hexString HEX)
string(LENGTH ${hexString} hexStringLength)
# appends null byte if asked
if(BIN2H_NULL_TERMINATE)
set(hexString "${hexString}00")
endif()
# wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line)
wrap_string(VARIABLE hexString AT_COLUMN 32)
math(EXPR arraySize "${hexStringLength} / 2")
# adds '0x' prefix and comma suffix before and after every byte respectively
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString})
# removes trailing comma
string(REGEX REPLACE ", $" "" arrayValues ${arrayValues})
# converts the variable name into proper C identifier
string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME)
# declares byte array and the length variables
set(arrayDefinition "const ${BIN2H_ARRAY_TYPE} ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };")
set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_size = ${arraySize};")
set(declarations "${arrayDefinition}\n\n${arraySizeDefinition}\n\n")
if(BIN2H_APPEND)
file(APPEND ${BIN2H_HEADER_FILE} "${declarations}")
else()
file(WRITE ${BIN2H_HEADER_FILE} "${declarations}")
endif()
endfunction()

View File

@@ -0,0 +1,10 @@
int add(int a, int b)
{
int c = a + b;
return c == 0 ? 50000 : c;
}
extern "C" int _start()
{
return add(1, 2);
}

Binary file not shown.

View File

@@ -0,0 +1,9 @@
int add(int a, int b)
{
return a + b;
}
extern "C" int _start()
{
return add(1, 2);
}

BIN
tests/PowerAnalyse/add.elf Normal file

Binary file not shown.

View File

@@ -0,0 +1,15 @@
int cond(int a)
{
int v = 2;
if (a == 1)
{
v += 5;
}
return v;
}
extern "C" int _start()
{
return cond(0);
}

Binary file not shown.

View File

@@ -0,0 +1,18 @@
int cond(int a)
{
if (a == 1)
{
return 5;
}
else if (a == 4)
{
return 9;
}
return 0;
}
extern "C" int _start()
{
return cond(0);
}

BIN
tests/PowerAnalyse/cond.elf Normal file

Binary file not shown.

View File

@@ -0,0 +1,15 @@
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);
}

BIN
tests/PowerAnalyse/loop.elf Normal file

Binary file not shown.

2
tests/PowerAnalyse/private/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

5
tests/compile-all.bat Normal file
View File

@@ -0,0 +1,5 @@
@echo off
pushd PowerAnalyse
for %%f in (*.cpp) do call ..\compile.bat %%f
popd

3
tests/compile.bat Normal file
View File

@@ -0,0 +1,3 @@
@echo off
clang -target powerpc-unknown-linux-gnu -fuse-ld=lld -nostdlib -m32 -o %~n1.elf %1

View File

@@ -1,13 +0,0 @@
add_subdirectory(${THIRDPARTY_ROOT}/disasm)
if (NOT TARGET fmt::fmt)
add_subdirectory(${THIRDPARTY_ROOT}/fmt)
endif()
if (NOT TARGET tomlplusplus::tomlplusplus)
add_subdirectory(${THIRDPARTY_ROOT}/tomlplusplus)
endif()
if (NOT TARGET xxHash::xxhash)
add_subdirectory(${THIRDPARTY_ROOT}/xxHash/cmake_unofficial)
endif()

View File

@@ -1,223 +0,0 @@
/*
*
* TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based
* on the implementation in boost::uuid::details.
*
* SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1
*
* Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Taken from https://github.com/mohaps/TinySHA1
* Modified for use by Xenia
*/
#ifndef _TINY_SHA1_HPP_
#define _TINY_SHA1_HPP_
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdint.h>
namespace sha1 {
class SHA1 {
public:
typedef uint32_t digest32_t[5];
typedef uint8_t digest8_t[20];
inline static uint32_t LeftRotate(uint32_t value, size_t count) {
return (value << count) ^ (value >> (32 - count));
}
SHA1() { reset(); }
virtual ~SHA1() {}
SHA1(const SHA1& s) { *this = s; }
const SHA1& operator=(const SHA1& s) {
memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t));
memcpy(m_block, s.m_block, 64);
m_blockByteIndex = s.m_blockByteIndex;
m_byteCount = s.m_byteCount;
return *this;
}
SHA1& init(const uint32_t digest[5], const uint8_t block[64],
uint32_t count) {
std::memcpy(m_digest, digest, 20);
std::memcpy(m_block, block, count % 64);
m_byteCount = count;
m_blockByteIndex = count % 64;
return *this;
}
const uint32_t* getDigest() const { return m_digest; }
const uint8_t* getBlock() const { return m_block; }
size_t getBlockByteIndex() const { return m_blockByteIndex; }
size_t getByteCount() const { return m_byteCount; }
SHA1& reset() {
m_digest[0] = 0x67452301;
m_digest[1] = 0xEFCDAB89;
m_digest[2] = 0x98BADCFE;
m_digest[3] = 0x10325476;
m_digest[4] = 0xC3D2E1F0;
m_blockByteIndex = 0;
m_byteCount = 0;
return *this;
}
SHA1& processByte(uint8_t octet) {
this->m_block[this->m_blockByteIndex++] = octet;
++this->m_byteCount;
if (m_blockByteIndex == 64) {
this->m_blockByteIndex = 0;
processBlock();
}
return *this;
}
SHA1& processBlock(const void* const start, const void* const end) {
const uint8_t* begin = static_cast<const uint8_t*>(start);
const uint8_t* finish = static_cast<const uint8_t*>(end);
while (begin != finish) {
processByte(*begin);
begin++;
}
return *this;
}
SHA1& processBytes(const void* const data, size_t len) {
const uint8_t* block = static_cast<const uint8_t*>(data);
processBlock(block, block + len);
return *this;
}
const uint32_t* finalize(digest32_t digest) {
size_t bitCount = this->m_byteCount * 8;
processByte(0x80);
if (this->m_blockByteIndex > 56) {
while (m_blockByteIndex != 0) {
processByte(0);
}
while (m_blockByteIndex < 56) {
processByte(0);
}
} else {
while (m_blockByteIndex < 56) {
processByte(0);
}
}
processByte(0);
processByte(0);
processByte(0);
processByte(0);
processByte(static_cast<unsigned char>((bitCount >> 24) & 0xFF));
processByte(static_cast<unsigned char>((bitCount >> 16) & 0xFF));
processByte(static_cast<unsigned char>((bitCount >> 8) & 0xFF));
processByte(static_cast<unsigned char>((bitCount)&0xFF));
memcpy(digest, m_digest, 5 * sizeof(uint32_t));
return digest;
}
const uint8_t* finalize(digest8_t digest) {
digest32_t d32;
finalize(d32);
size_t di = 0;
digest[di++] = ((d32[0] >> 24) & 0xFF);
digest[di++] = ((d32[0] >> 16) & 0xFF);
digest[di++] = ((d32[0] >> 8) & 0xFF);
digest[di++] = ((d32[0]) & 0xFF);
digest[di++] = ((d32[1] >> 24) & 0xFF);
digest[di++] = ((d32[1] >> 16) & 0xFF);
digest[di++] = ((d32[1] >> 8) & 0xFF);
digest[di++] = ((d32[1]) & 0xFF);
digest[di++] = ((d32[2] >> 24) & 0xFF);
digest[di++] = ((d32[2] >> 16) & 0xFF);
digest[di++] = ((d32[2] >> 8) & 0xFF);
digest[di++] = ((d32[2]) & 0xFF);
digest[di++] = ((d32[3] >> 24) & 0xFF);
digest[di++] = ((d32[3] >> 16) & 0xFF);
digest[di++] = ((d32[3] >> 8) & 0xFF);
digest[di++] = ((d32[3]) & 0xFF);
digest[di++] = ((d32[4] >> 24) & 0xFF);
digest[di++] = ((d32[4] >> 16) & 0xFF);
digest[di++] = ((d32[4] >> 8) & 0xFF);
digest[di++] = ((d32[4]) & 0xFF);
return digest;
}
protected:
void processBlock() {
uint32_t w[80];
for (size_t i = 0; i < 16; i++) {
w[i] = (m_block[i * 4 + 0] << 24);
w[i] |= (m_block[i * 4 + 1] << 16);
w[i] |= (m_block[i * 4 + 2] << 8);
w[i] |= (m_block[i * 4 + 3]);
}
for (size_t i = 16; i < 80; i++) {
w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1);
}
uint32_t a = m_digest[0];
uint32_t b = m_digest[1];
uint32_t c = m_digest[2];
uint32_t d = m_digest[3];
uint32_t e = m_digest[4];
for (std::size_t i = 0; i < 80; ++i) {
uint32_t f = 0;
uint32_t k = 0;
if (i < 20) {
f = (b & c) | (~b & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = LeftRotate(b, 30);
b = a;
a = temp;
}
m_digest[0] += a;
m_digest[1] += b;
m_digest[2] += c;
m_digest[3] += d;
m_digest[4] += e;
}
private:
digest32_t m_digest;
uint8_t m_block[64];
size_t m_blockByteIndex;
size_t m_byteCount;
};
}
#endif

View File

@@ -1,9 +1,5 @@
#include "dis-asm.h" #include "dis-asm.h"
#ifndef EIO
#define EIO 5
#endif
/* Get LENGTH bytes from info's buffer, at target address memaddr. /* Get LENGTH bytes from info's buffer, at target address memaddr.
Transfer them to myaddr. */ Transfer them to myaddr. */
int int

View File

@@ -840,7 +840,7 @@ const struct powerpc_operand powerpc_operands[] =
{ 8, 0, insert_vperm, extract_vperm, 0 }, { 8, 0, insert_vperm, extract_vperm, 0 },
#define VD3D0 VPERM128 + 1 #define VD3D0 VPERM128 + 1
{ 7, 18, NULL, NULL, 0 }, { 3, 18, NULL, NULL, 0 },
#define VD3D1 VD3D0 + 1 #define VD3D1 VD3D0 + 1
{ 3, 16, NULL, NULL, 0 }, { 3, 16, NULL, NULL, 0 },

1
thirdparty/fmt vendored

Submodule thirdparty/fmt deleted from 873670ba3f

Submodule thirdparty/libmspack deleted from 305907723a

Submodule thirdparty/tiny-AES-c deleted from 23856752fb

Submodule thirdparty/tomlplusplus deleted from c4369ae1d8

1
thirdparty/xxHash vendored

Submodule thirdparty/xxHash deleted from 2bf8313b93