mirror of
https://github.com/hedge-dev/XenonRecomp.git
synced 2025-09-13 14:56:39 +00:00
Compare commits
42 Commits
943fcafd37
...
BBB_MoreIn
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9ff80d8321 | ||
![]() |
830be1f69a | ||
![]() |
a5d6382975 | ||
![]() |
1d452c60a8 | ||
![]() |
cea0b2fc38 | ||
![]() |
f6193ebe43 | ||
![]() |
f23d22bc7f | ||
![]() |
847b750786 | ||
![]() |
865319a39c | ||
![]() |
6df2397610 | ||
![]() |
49c5e3b4f5 | ||
![]() |
0bfeaed44a | ||
![]() |
c017eb630a | ||
![]() |
82b4cd3bb7 | ||
![]() |
c3934c624f | ||
![]() |
1c571c8576 | ||
![]() |
7b8e37aa37 | ||
![]() |
0bf1fd5477 | ||
![]() |
dc4460eefd | ||
![]() |
3ee19542c9 | ||
![]() |
2991a3b698 | ||
![]() |
04e716178b | ||
![]() |
cd6fcb33bd | ||
![]() |
0fc545a6e2 | ||
![]() |
73b75e197a | ||
![]() |
3280a7cf9f | ||
![]() |
87e350906b | ||
![]() |
7fb8af1bad | ||
![]() |
2cd41adf42 | ||
![]() |
ca39a3b992 | ||
![]() |
b4b4aac788 | ||
![]() |
de2840970f | ||
![]() |
4650dc69fb | ||
![]() |
45c00cfec6 | ||
![]() |
847842cd28 | ||
![]() |
02d23b3463 | ||
![]() |
ea3e60cb0d | ||
![]() |
dd7cac76ea | ||
![]() |
675b482ec4 | ||
![]() |
23f3389171 | ||
![]() |
699c078c94 | ||
![]() |
dd85501f11 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -397,3 +397,13 @@ FodyWeavers.xsd
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
# IntelliJ IDEs
|
||||
.idea/
|
||||
|
||||
# macOS metadata
|
||||
*.DS_Store
|
||||
|
||||
# CMake Files
|
||||
**/cmake-build-debug
|
||||
**/CMakeCache.txt
|
||||
|
15
.gitmodules
vendored
Normal file
15
.gitmodules
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
[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
|
@@ -1,6 +1,8 @@
|
||||
cmake_minimum_required (VERSION 3.20)
|
||||
|
||||
set(THIRDPARTY_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
# Enable Hot Reload for MSVC compilers if supported.
|
||||
@@ -11,35 +13,18 @@ endif()
|
||||
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
include("cmake/bin2h.cmake")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
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)
|
||||
add_subdirectory(${THIRDPARTY_ROOT})
|
||||
set(XENONANALYSE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/XenonAnalyse)
|
||||
set(XENONUTILS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/XenonUtils)
|
||||
set(XENONRECOMP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/XenonRecomp)
|
||||
|
||||
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)
|
||||
project ("XenonRecomp-ALL")
|
||||
|
||||
project ("PowerRecomp-ALL")
|
||||
add_subdirectory(${XENONANALYSE_ROOT})
|
||||
add_subdirectory(${XENONRECOMP_ROOT})
|
||||
add_subdirectory(${XENONUTILS_ROOT})
|
||||
|
||||
add_subdirectory(${POWERANALYSE_ROOT})
|
||||
add_subdirectory(${POWERRECOMP_ROOT})
|
||||
add_subdirectory(${POWERUTILS_ROOT})
|
||||
|
||||
# Only build sample and tests if this is the top level project
|
||||
# Only tests if this is the top level project
|
||||
if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
|
||||
add_subdirectory(PowerSample)
|
||||
add_subdirectory(PowerTests)
|
||||
add_subdirectory(XenonTests)
|
||||
endif()
|
||||
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2025 hedge-dev and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
- The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@@ -1,11 +0,0 @@
|
||||
# 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)
|
@@ -1,28 +0,0 @@
|
||||
#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);
|
||||
};
|
@@ -1,15 +0,0 @@
|
||||
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
3
PowerRecomp/generated/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
*
|
||||
|
||||
!.gitignore
|
@@ -1,34 +0,0 @@
|
||||
#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;
|
||||
}
|
1
PowerSample/.gitignore
vendored
1
PowerSample/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
ppc_*
|
@@ -1,14 +0,0 @@
|
||||
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")
|
@@ -1,13 +0,0 @@
|
||||
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()
|
@@ -1,5 +0,0 @@
|
||||
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)
|
@@ -1,26 +0,0 @@
|
||||
#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;
|
||||
}
|
@@ -1,148 +0,0 @@
|
||||
#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
168
PowerUtils/xex.h
@@ -1,168 +0,0 @@
|
||||
#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
Normal file
260
README.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# 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.
|
14
XenonAnalyse/CMakeLists.txt
Normal file
14
XenonAnalyse/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# 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)
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include <bit>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <byteswap.h>
|
||||
|
||||
size_t Function::SearchBlock(size_t address) const
|
||||
{
|
||||
@@ -63,7 +64,7 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
// TODO: Branch fallthrough
|
||||
for (; data <= dataEnd ; ++data)
|
||||
{
|
||||
const auto addr = base + ((data - dataStart) * sizeof(*data));
|
||||
const size_t addr = base + ((data - dataStart) * sizeof(*data));
|
||||
if (blockStack.empty())
|
||||
{
|
||||
break; // it's hideover
|
||||
@@ -71,11 +72,11 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
|
||||
auto& curBlock = blocks[blockStack.back()];
|
||||
DEBUG(const auto blockBase = curBlock.base);
|
||||
const auto instruction = std::byteswap(*data);
|
||||
const uint32_t instruction = ByteSwap(*data);
|
||||
|
||||
const auto op = PPC_OP(instruction);
|
||||
const auto xop = PPC_XOP(instruction);
|
||||
const auto isLink = PPC_BL(instruction); // call
|
||||
const uint32_t op = PPC_OP(instruction);
|
||||
const uint32_t xop = PPC_XOP(instruction);
|
||||
const uint32_t isLink = PPC_BL(instruction); // call
|
||||
|
||||
ppc_insn insn;
|
||||
ppc::Disassemble(data, addr, insn);
|
||||
@@ -103,13 +104,13 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
|
||||
// TODO: Handle absolute branches?
|
||||
assert(!PPC_BA(instruction));
|
||||
const auto branchDest = addr + PPC_BD(instruction);
|
||||
const size_t branchDest = addr + PPC_BD(instruction);
|
||||
|
||||
// true/false paths
|
||||
// left block: false case
|
||||
// right block: true case
|
||||
const auto lBase = (addr - base) + 4;
|
||||
const auto rBase = (addr + PPC_BD(instruction)) - base;
|
||||
const size_t lBase = (addr - base) + 4;
|
||||
const size_t rBase = (addr + PPC_BD(instruction)) - base;
|
||||
|
||||
// these will be -1 if it's our first time seeing these blocks
|
||||
auto lBlock = fn.SearchBlock(base + lBase);
|
||||
@@ -124,7 +125,7 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
blockStack.emplace_back(lBlock);
|
||||
}
|
||||
|
||||
auto rBlock = fn.SearchBlock(base + rBase);
|
||||
size_t rBlock = fn.SearchBlock(base + rBase);
|
||||
if (rBlock == -1)
|
||||
{
|
||||
blocks.emplace_back(branchDest - base, 0);
|
||||
@@ -145,10 +146,10 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
if (op == PPC_OP_B)
|
||||
{
|
||||
assert(!PPC_BA(instruction));
|
||||
const auto branchDest = addr + PPC_BI(instruction);
|
||||
const size_t branchDest = addr + PPC_BI(instruction);
|
||||
|
||||
const auto branchBase = branchDest - base;
|
||||
const auto branchBlock = fn.SearchBlock(branchDest);
|
||||
const size_t branchBase = branchDest - base;
|
||||
const size_t branchBlock = fn.SearchBlock(branchDest);
|
||||
|
||||
if (branchDest < base)
|
||||
{
|
||||
@@ -158,8 +159,8 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
}
|
||||
|
||||
// carry over our projection if blocks are next to each other
|
||||
const auto isContinuous = branchBase == curBlock.base + curBlock.size;
|
||||
auto sizeProjection = (size_t)-1;
|
||||
const bool isContinuous = branchBase == curBlock.base + curBlock.size;
|
||||
size_t sizeProjection = (size_t)-1;
|
||||
|
||||
if (curBlock.projectedSize != -1 && isContinuous)
|
||||
{
|
||||
@@ -180,12 +181,12 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
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
|
||||
const auto conditional = !(PPC_BO(instruction) & 0x10);
|
||||
const bool conditional = !(PPC_BO(instruction) & 0x10);
|
||||
if (conditional)
|
||||
{
|
||||
// right block's just going to return
|
||||
const auto lBase = (addr - base) + 4;
|
||||
auto lBlock = fn.SearchBlock(lBase);
|
||||
const size_t lBase = (addr - base) + 4;
|
||||
size_t lBlock = fn.SearchBlock(lBase);
|
||||
if (lBlock == -1)
|
||||
{
|
||||
blocks.emplace_back(lBase, 0);
|
||||
@@ -212,7 +213,7 @@ Function Function::Analyze(const void* code, size_t size, size_t base)
|
||||
// Sort and invalidate discontinuous blocks
|
||||
if (blocks.size() > 1)
|
||||
{
|
||||
std::ranges::sort(blocks, [](const Block& a, const Block& b)
|
||||
std::sort(blocks.begin(), blocks.end(), [](const Block& a, const Block& b)
|
||||
{
|
||||
return a.base < b.base;
|
||||
});
|
51
XenonAnalyse/function.h
Normal file
51
XenonAnalyse/function.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#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);
|
||||
};
|
@@ -1,10 +1,11 @@
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <file.h>
|
||||
#include <disasm.h>
|
||||
#include <image.h>
|
||||
#include "function.h"
|
||||
#include <print>
|
||||
#include <xbox.h>
|
||||
#include <fmt/core.h>
|
||||
#include "function.h"
|
||||
|
||||
#define SWITCH_ABSOLUTE 0
|
||||
#define SWITCH_COMPUTED 1
|
||||
@@ -139,7 +140,7 @@ void MakeMask(const uint32_t* instructions, size_t count)
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
ppc::Disassemble(&instructions[i], 0, insn);
|
||||
std::println("0x{:X}, // {}", std::byteswap(insn.opcode->opcode | (insn.instruction & insn.opcode->mask)), insn.opcode->name);
|
||||
fmt::println("0x{:X}, // {}", ByteSwap(insn.opcode->opcode | (insn.instruction & insn.opcode->mask)), insn.opcode->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,32 +172,25 @@ void* SearchMask(const void* source, const uint32_t* compare, size_t compareCoun
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int main()
|
||||
static std::string out;
|
||||
|
||||
template<class... Args>
|
||||
static void println(fmt::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
const auto file = LoadFile("private/default.xex").value();
|
||||
auto image = Image::ParseImage(file.data(), file.size()).value();
|
||||
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
|
||||
out += '\n';
|
||||
};
|
||||
|
||||
std::string out;
|
||||
auto println = [&]<class... Args>(std::format_string<Args...> fmt, Args&&... args)
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::vformat_to(std::back_inserter(out), fmt.get(), std::make_format_args(args...));
|
||||
out += '\n';
|
||||
};
|
||||
//for (const auto& section : image.sections)
|
||||
//{
|
||||
// image.symbols.emplace(section.name, section.base, section.size, Symbol_Section);
|
||||
//}
|
||||
printf("Usage: XenonAnalyse [input XEX file path] [output jump table TOML file path]");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// MakeMask((uint32_t*)image.Find(0x82C40D84), 6);
|
||||
|
||||
//auto data = "\x4D\x99\x00\x20";
|
||||
//auto data2 = 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);
|
||||
const auto file = LoadFile(argv[1]);
|
||||
auto image = Image::ParseImage(file.data(), file.size());
|
||||
|
||||
auto printTable = [&](const SwitchTable& table)
|
||||
{
|
||||
@@ -216,27 +210,7 @@ int main()
|
||||
|
||||
std::vector<SwitchTable> switches{};
|
||||
|
||||
auto insertTable = [&](size_t base, size_t defaultLabel, size_t r, size_t nLabels, uint32_t type)
|
||||
{
|
||||
auto& sw = switches.emplace_back();
|
||||
sw.base = base;
|
||||
sw.defaultLabel = defaultLabel;
|
||||
sw.r = r;
|
||||
sw.labels.resize(nLabels);
|
||||
sw.type = type;
|
||||
};
|
||||
|
||||
println("# Generated by PowerAnalyse");
|
||||
insertTable(0x830ADAD8, 0x830ADB28, 11, 0x1B, SWITCH_COMPUTED);
|
||||
insertTable(0x830AE1B0, 0x830AE21C, 11, 0x1B, SWITCH_BYTEOFFSET);
|
||||
insertTable(0x82CFE120, 0x82CFDE68, 11, 0x10, SWITCH_SHORTOFFSET);
|
||||
|
||||
println("# ---- MANUAL JUMPTABLE ----");
|
||||
for (auto& table : switches)
|
||||
{
|
||||
ReadTable(image, table);
|
||||
printTable(table);
|
||||
}
|
||||
println("# Generated by XenonAnalyse");
|
||||
|
||||
auto scanPattern = [&](uint32_t* pattern, size_t count, size_t type)
|
||||
{
|
||||
@@ -261,7 +235,7 @@ int main()
|
||||
table.type = type;
|
||||
ScanTable((uint32_t*)data, base + (data - dataStart), table);
|
||||
|
||||
// std::println("{:X} ; jmptable - {}", base + (data - dataStart), table.labels.size());
|
||||
// fmt::println("{:X} ; jmptable - {}", base + (data - dataStart), table.labels.size());
|
||||
if (table.base != 0)
|
||||
{
|
||||
ReadTable(image, table);
|
||||
@@ -331,122 +305,8 @@ int main()
|
||||
scanPattern(offsetSwitch, std::size(offsetSwitch), SWITCH_BYTEOFFSET);
|
||||
scanPattern(wordOffsetSwitch, std::size(wordOffsetSwitch), SWITCH_SHORTOFFSET);
|
||||
|
||||
FILE* f = fopen("out/switches.toml", "w");
|
||||
fwrite(out.data(), 1, out.size(), f);
|
||||
fclose(f);
|
||||
std::ofstream f(argv[2]);
|
||||
f.write(out.data(), out.size());
|
||||
|
||||
uint32_t cxxFrameHandler = 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;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
29
XenonRecomp/CMakeLists.txt
Normal file
29
XenonRecomp/CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
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)
|
52
XenonRecomp/main.cpp
Normal file
52
XenonRecomp/main.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#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;
|
||||
}
|
@@ -1,22 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// Workaround for the intellisense for some reason not seeing C++23 features
|
||||
#ifdef __INTELLISENSE__
|
||||
#undef __cplusplus
|
||||
#define __cplusplus 202302L
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <charconv>
|
||||
#include <disasm.h>
|
||||
#include <file.h>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <function.h>
|
||||
#include <image.h>
|
||||
#include <print>
|
||||
#include <toml++/toml.hpp>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <xbox.h>
|
||||
#include <xxhash.h>
|
||||
#include "generated/ppc_context.gen.h"
|
||||
#include <fmt/core.h>
|
||||
#include <xmmintrin.h>
|
@@ -1,5 +1,6 @@
|
||||
#include "pch.h"
|
||||
#include "recompiler.h"
|
||||
#include <xex_patcher.h>
|
||||
|
||||
static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop)
|
||||
{
|
||||
@@ -9,12 +10,87 @@ static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop)
|
||||
return mstart <= mstop ? value : ~value;
|
||||
}
|
||||
|
||||
void Recompiler::LoadConfig(const std::string_view& configFilePath)
|
||||
bool Recompiler::LoadConfig(const std::string_view& configFilePath)
|
||||
{
|
||||
config.Load(configFilePath);
|
||||
|
||||
const auto file = LoadFile((config.directoryPath + config.filePath).c_str()).value();
|
||||
image = Image::ParseImage(file.data(), file.size()).value();
|
||||
std::vector<uint8_t> file;
|
||||
if (!config.patchedFilePath.empty())
|
||||
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()
|
||||
@@ -23,55 +99,79 @@ void Recompiler::Analyse()
|
||||
{
|
||||
if (i < 32)
|
||||
{
|
||||
auto& restgpr = functions.emplace_back();
|
||||
restgpr.base = config.restGpr14Address + (i - 14) * 4;
|
||||
restgpr.size = (32 - i) * 4 + 12;
|
||||
image.symbols.emplace(std::format("__restgprlr_{}", i), restgpr.base, restgpr.size, Symbol_Function);
|
||||
if (config.restGpr14Address != 0)
|
||||
{
|
||||
auto& restgpr = functions.emplace_back();
|
||||
restgpr.base = config.restGpr14Address + (i - 14) * 4;
|
||||
restgpr.size = (32 - i) * 4 + 12;
|
||||
image.symbols.emplace(Symbol{ fmt::format("__restgprlr_{}", i), restgpr.base, restgpr.size, Symbol_Function });
|
||||
}
|
||||
|
||||
auto& savegpr = functions.emplace_back();
|
||||
savegpr.base = config.saveGpr14Address + (i - 14) * 4;
|
||||
savegpr.size = (32 - i) * 4 + 8;
|
||||
image.symbols.emplace(std::format("__savegprlr_{}", i), savegpr.base, savegpr.size, Symbol_Function);
|
||||
if (config.saveGpr14Address != 0)
|
||||
{
|
||||
auto& savegpr = functions.emplace_back();
|
||||
savegpr.base = config.saveGpr14Address + (i - 14) * 4;
|
||||
savegpr.size = (32 - i) * 4 + 8;
|
||||
image.symbols.emplace(fmt::format("__savegprlr_{}", i), savegpr.base, savegpr.size, Symbol_Function);
|
||||
}
|
||||
|
||||
auto& restfpr = functions.emplace_back();
|
||||
restfpr.base = config.restFpr14Address + (i - 14) * 4;
|
||||
restfpr.size = (32 - i) * 4 + 4;
|
||||
image.symbols.emplace(std::format("__restfpr_{}", i), restfpr.base, restfpr.size, Symbol_Function);
|
||||
if (config.restFpr14Address != 0)
|
||||
{
|
||||
auto& restfpr = functions.emplace_back();
|
||||
restfpr.base = config.restFpr14Address + (i - 14) * 4;
|
||||
restfpr.size = (32 - i) * 4 + 4;
|
||||
image.symbols.emplace(fmt::format("__restfpr_{}", i), restfpr.base, restfpr.size, Symbol_Function);
|
||||
}
|
||||
|
||||
auto& savefpr = functions.emplace_back();
|
||||
savefpr.base = config.saveFpr14Address + (i - 14) * 4;
|
||||
savefpr.size = (32 - i) * 4 + 4;
|
||||
image.symbols.emplace(std::format("__savefpr_{}", i), savefpr.base, savefpr.size, Symbol_Function);
|
||||
if (config.saveFpr14Address != 0)
|
||||
{
|
||||
auto& savefpr = functions.emplace_back();
|
||||
savefpr.base = config.saveFpr14Address + (i - 14) * 4;
|
||||
savefpr.size = (32 - i) * 4 + 4;
|
||||
image.symbols.emplace(fmt::format("__savefpr_{}", i), savefpr.base, savefpr.size, Symbol_Function);
|
||||
}
|
||||
|
||||
auto& restvmx = functions.emplace_back();
|
||||
restvmx.base = config.restVmx14Address + (i - 14) * 8;
|
||||
restvmx.size = (32 - i) * 8 + 4;
|
||||
image.symbols.emplace(std::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
|
||||
if (config.restVmx14Address != 0)
|
||||
{
|
||||
auto& restvmx = functions.emplace_back();
|
||||
restvmx.base = config.restVmx14Address + (i - 14) * 8;
|
||||
restvmx.size = (32 - i) * 8 + 4;
|
||||
image.symbols.emplace(fmt::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
|
||||
}
|
||||
|
||||
auto& savevmx = functions.emplace_back();
|
||||
savevmx.base = config.saveVmx14Address + (i - 14) * 8;
|
||||
savevmx.size = (32 - i) * 8 + 4;
|
||||
image.symbols.emplace(std::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
|
||||
if (config.saveVmx14Address != 0)
|
||||
{
|
||||
auto& savevmx = functions.emplace_back();
|
||||
savevmx.base = config.saveVmx14Address + (i - 14) * 8;
|
||||
savevmx.size = (32 - i) * 8 + 4;
|
||||
image.symbols.emplace(fmt::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= 64)
|
||||
{
|
||||
auto& restvmx = functions.emplace_back();
|
||||
restvmx.base = config.restVmx64Address + (i - 64) * 8;
|
||||
restvmx.size = (128 - i) * 8 + 4;
|
||||
image.symbols.emplace(std::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
|
||||
if (config.restVmx64Address != 0)
|
||||
{
|
||||
auto& restvmx = functions.emplace_back();
|
||||
restvmx.base = config.restVmx64Address + (i - 64) * 8;
|
||||
restvmx.size = (128 - i) * 8 + 4;
|
||||
image.symbols.emplace(fmt::format("__restvmx_{}", i), restvmx.base, restvmx.size, Symbol_Function);
|
||||
}
|
||||
|
||||
auto& savevmx = functions.emplace_back();
|
||||
savevmx.base = config.saveVmx64Address + (i - 64) * 8;
|
||||
savevmx.size = (128 - i) * 8 + 4;
|
||||
image.symbols.emplace(std::format("__savevmx_{}", i), savevmx.base, savevmx.size, Symbol_Function);
|
||||
if (config.saveVmx64Address != 0)
|
||||
{
|
||||
auto& savevmx = functions.emplace_back();
|
||||
savevmx.base = config.saveVmx64Address + (i - 64) * 8;
|
||||
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)
|
||||
{
|
||||
functions.emplace_back(address, size);
|
||||
image.symbols.emplace(std::format("sub_{:X}", address), address, size, Symbol_Function);
|
||||
image.symbols.emplace(fmt::format("sub_{:X}", address), address, size, Symbol_Function);
|
||||
}
|
||||
|
||||
auto& pdata = *image.Find(".pdata");
|
||||
@@ -80,8 +180,8 @@ void Recompiler::Analyse()
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
auto fn = pf[i];
|
||||
fn.BeginAddress = std::byteswap(fn.BeginAddress);
|
||||
fn.Data = std::byteswap(fn.Data);
|
||||
fn.BeginAddress = ByteSwap(fn.BeginAddress);
|
||||
fn.Data = ByteSwap(fn.Data);
|
||||
|
||||
if (image.symbols.find(fn.BeginAddress) == image.symbols.end())
|
||||
{
|
||||
@@ -89,7 +189,7 @@ void Recompiler::Analyse()
|
||||
f.base = fn.BeginAddress;
|
||||
f.size = fn.FunctionLength * 4;
|
||||
|
||||
image.symbols.emplace(std::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function);
|
||||
image.symbols.emplace(fmt::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +205,7 @@ void Recompiler::Analyse()
|
||||
|
||||
while (data < dataEnd)
|
||||
{
|
||||
uint32_t insn = std::byteswap(*(uint32_t*)data);
|
||||
uint32_t insn = ByteSwap(*(uint32_t*)data);
|
||||
if (PPC_OP(insn) == PPC_OP_B && PPC_BL(insn))
|
||||
{
|
||||
size_t address = base + (data - section.data) + PPC_BI(insn);
|
||||
@@ -114,7 +214,7 @@ void Recompiler::Analyse()
|
||||
{
|
||||
auto data = section.data + address - section.base;
|
||||
auto& fn = functions.emplace_back(Function::Analyze(data, section.base + section.size - address, address));
|
||||
image.symbols.emplace(std::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function);
|
||||
image.symbols.emplace(fmt::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function);
|
||||
}
|
||||
}
|
||||
data += 4;
|
||||
@@ -124,7 +224,7 @@ void Recompiler::Analyse()
|
||||
|
||||
while (data < dataEnd)
|
||||
{
|
||||
auto invalidInstr = config.invalidInstructions.find(std::byteswap(*(uint32_t*)data));
|
||||
auto invalidInstr = config.invalidInstructions.find(ByteSwap(*(uint32_t*)data));
|
||||
if (invalidInstr != config.invalidInstructions.end())
|
||||
{
|
||||
base += invalidInstr->second;
|
||||
@@ -143,7 +243,7 @@ void Recompiler::Analyse()
|
||||
else
|
||||
{
|
||||
auto& fn = functions.emplace_back(Function::Analyze(data, dataEnd - data, base));
|
||||
image.symbols.emplace(std::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function);
|
||||
image.symbols.emplace(fmt::format("sub_{:X}", fn.base), fn.base, fn.size, Symbol_Function);
|
||||
|
||||
base += fn.size;
|
||||
data += fn.size;
|
||||
@@ -172,9 +272,9 @@ bool Recompiler::Recompile(
|
||||
(config.nonVolatileRegistersAsLocalVariables && index >= 14))
|
||||
{
|
||||
localVariables.r[index] = true;
|
||||
return std::format("r{}", index);
|
||||
return fmt::format("r{}", index);
|
||||
}
|
||||
return std::format("ctx.r{}", index);
|
||||
return fmt::format("ctx.r{}", index);
|
||||
};
|
||||
|
||||
auto f = [&](size_t index)
|
||||
@@ -183,9 +283,9 @@ bool Recompiler::Recompile(
|
||||
(config.nonVolatileRegistersAsLocalVariables && index >= 14))
|
||||
{
|
||||
localVariables.f[index] = true;
|
||||
return std::format("f{}", index);
|
||||
return fmt::format("f{}", index);
|
||||
}
|
||||
return std::format("ctx.f{}", index);
|
||||
return fmt::format("ctx.f{}", index);
|
||||
};
|
||||
|
||||
auto v = [&](size_t index)
|
||||
@@ -194,9 +294,9 @@ bool Recompiler::Recompile(
|
||||
(config.nonVolatileRegistersAsLocalVariables && ((index >= 14 && index <= 31) || (index >= 64 && index <= 127))))
|
||||
{
|
||||
localVariables.v[index] = true;
|
||||
return std::format("v{}", index);
|
||||
return fmt::format("v{}", index);
|
||||
}
|
||||
return std::format("ctx.v{}", index);
|
||||
return fmt::format("ctx.v{}", index);
|
||||
};
|
||||
|
||||
auto cr = [&](size_t index)
|
||||
@@ -204,9 +304,9 @@ bool Recompiler::Recompile(
|
||||
if (config.crRegistersAsLocalVariables)
|
||||
{
|
||||
localVariables.cr[index] = true;
|
||||
return std::format("cr{}", index);
|
||||
return fmt::format("cr{}", index);
|
||||
}
|
||||
return std::format("ctx.cr{}", index);
|
||||
return fmt::format("ctx.cr{}", index);
|
||||
};
|
||||
|
||||
auto ctr = [&]()
|
||||
@@ -278,8 +378,9 @@ bool Recompiler::Recompile(
|
||||
else if (address == config.setJmpAddress)
|
||||
{
|
||||
println("\t{} = ctx;", env());
|
||||
println("\t{}.s64 = setjmp(*reinterpret_cast<jmp_buf*>(base + {}.u32));", r(3), r(3));
|
||||
println("\tif ({}.s64 != 0) ctx = {};", r(3), env());
|
||||
println("\t{}.s64 = setjmp(*reinterpret_cast<jmp_buf*>(base + {}.u32));", temp(), r(3));
|
||||
println("\tif ({}.s64 != 0) ctx = {};", temp(), env());
|
||||
println("\t{} = {};", r(3), temp());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -287,7 +388,7 @@ bool Recompiler::Recompile(
|
||||
|
||||
if (targetSymbol != image.symbols.end() && targetSymbol->address == address && targetSymbol->type == Symbol_Function)
|
||||
{
|
||||
if (config.nonVolatileRegistersAsLocalVariables && (targetSymbol->name.starts_with("__rest") || targetSymbol->name.starts_with("__save")))
|
||||
if (config.nonVolatileRegistersAsLocalVariables && (targetSymbol->name.find("__rest") == 0 || targetSymbol->name.find("__save") == 0))
|
||||
{
|
||||
// print nothing
|
||||
}
|
||||
@@ -333,84 +434,88 @@ bool Recompiler::Recompile(
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
print("\t");
|
||||
if (returnsBool)
|
||||
print("if (");
|
||||
|
||||
print("{}(", midAsmHook->second.name);
|
||||
for (auto& reg : midAsmHook->second.registers)
|
||||
auto printMidAsmHook = [&]()
|
||||
{
|
||||
if (out.back() != '(')
|
||||
out += ", ";
|
||||
bool returnsBool = midAsmHook->second.returnOnFalse || midAsmHook->second.returnOnTrue ||
|
||||
midAsmHook->second.jumpAddressOnFalse != NULL || midAsmHook->second.jumpAddressOnTrue != NULL;
|
||||
|
||||
switch (reg[0])
|
||||
print("\t");
|
||||
if (returnsBool)
|
||||
print("if (");
|
||||
|
||||
print("{}(", midAsmHook->second.name);
|
||||
for (auto& reg : midAsmHook->second.registers)
|
||||
{
|
||||
case 'c':
|
||||
if (reg == "ctr")
|
||||
out += ctr();
|
||||
else
|
||||
out += cr(std::atoi(reg.c_str() + 2));
|
||||
break;
|
||||
if (out.back() != '(')
|
||||
out += ", ";
|
||||
|
||||
case 'x':
|
||||
out += xer();
|
||||
break;
|
||||
switch (reg[0])
|
||||
{
|
||||
case 'c':
|
||||
if (reg == "ctr")
|
||||
out += ctr();
|
||||
else
|
||||
out += cr(std::atoi(reg.c_str() + 2));
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
if (reg == "reserved")
|
||||
out += reserved();
|
||||
else
|
||||
out += r(std::atoi(reg.c_str() + 1));
|
||||
break;
|
||||
case 'x':
|
||||
out += xer();
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
if (reg == "fpscr")
|
||||
out += "ctx.fpscr";
|
||||
else
|
||||
out += f(std::atoi(reg.c_str() + 1));
|
||||
break;
|
||||
case 'r':
|
||||
if (reg == "reserved")
|
||||
out += reserved();
|
||||
else
|
||||
out += r(std::atoi(reg.c_str() + 1));
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
out += v(std::atoi(reg.c_str() + 1));
|
||||
break;
|
||||
case 'f':
|
||||
if (reg == "fpscr")
|
||||
out += "ctx.fpscr";
|
||||
else
|
||||
out += f(std::atoi(reg.c_str() + 1));
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
out += v(std::atoi(reg.c_str() + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (returnsBool)
|
||||
{
|
||||
println(")) {{");
|
||||
if (returnsBool)
|
||||
{
|
||||
println(")) {{");
|
||||
|
||||
if (midAsmHook->second.returnOnTrue)
|
||||
println("\t\treturn;");
|
||||
else if (midAsmHook->second.jumpAddressOnTrue != NULL)
|
||||
println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnTrue);
|
||||
if (midAsmHook->second.returnOnTrue)
|
||||
println("\t\treturn;");
|
||||
else if (midAsmHook->second.jumpAddressOnTrue != NULL)
|
||||
println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnTrue);
|
||||
|
||||
println("\t}}");
|
||||
println("\t}}");
|
||||
|
||||
println("\telse {{");
|
||||
println("\telse {{");
|
||||
|
||||
if (midAsmHook->second.returnOnFalse)
|
||||
println("\t\treturn;");
|
||||
else if (midAsmHook->second.jumpAddressOnFalse != NULL)
|
||||
println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnFalse);
|
||||
if (midAsmHook->second.returnOnFalse)
|
||||
println("\t\treturn;");
|
||||
else if (midAsmHook->second.jumpAddressOnFalse != NULL)
|
||||
println("\t\tgoto loc_{:X};", midAsmHook->second.jumpAddressOnFalse);
|
||||
|
||||
println("\t}}");
|
||||
}
|
||||
else
|
||||
{
|
||||
println(");");
|
||||
println("\t}}");
|
||||
}
|
||||
else
|
||||
{
|
||||
println(");");
|
||||
|
||||
if (midAsmHook->second.ret)
|
||||
println("\treturn;");
|
||||
else if (midAsmHook->second.jumpAddress != NULL)
|
||||
println("\tgoto loc_{:X};", midAsmHook->second.jumpAddress);
|
||||
}
|
||||
}
|
||||
if (midAsmHook->second.ret)
|
||||
println("\treturn;");
|
||||
else if (midAsmHook->second.jumpAddress != NULL)
|
||||
println("\tgoto loc_{:X};", midAsmHook->second.jumpAddress);
|
||||
}
|
||||
};
|
||||
|
||||
if (midAsmHook != config.midAsmHooks.end() && !midAsmHook->second.afterInstruction)
|
||||
printMidAsmHook();
|
||||
|
||||
int id = insn.opcode->id;
|
||||
|
||||
@@ -528,7 +633,7 @@ bool Recompiler::Recompile(
|
||||
if (label < fn.base || label >= fn.base + fn.size)
|
||||
{
|
||||
println("\t\t// ERROR: 0x{:X}", label);
|
||||
std::println("ERROR: Switch case at {:X} is trying to jump outside function: {:X}", base, label);
|
||||
fmt::println("ERROR: Switch case at {:X} is trying to jump outside function: {:X}", base, label);
|
||||
println("\t\treturn;");
|
||||
}
|
||||
else
|
||||
@@ -640,7 +745,7 @@ bool Recompiler::Recompile(
|
||||
break;
|
||||
|
||||
case PPC_INST_BLRL:
|
||||
println("__debugbreak();");
|
||||
println("__builtin_debugtrap();");
|
||||
break;
|
||||
|
||||
case PPC_INST_BLT:
|
||||
@@ -717,11 +822,11 @@ bool Recompiler::Recompile(
|
||||
break;
|
||||
|
||||
case PPC_INST_CNTLZD:
|
||||
println("\t{}.u64 = __lzcnt64({}.u64);", r(insn.operands[0]), r(insn.operands[1]));
|
||||
println("\t{0}.u64 = {1}.u64 == 0 ? 64 : __builtin_clzll({1}.u64);", r(insn.operands[0]), r(insn.operands[1]));
|
||||
break;
|
||||
|
||||
case PPC_INST_CNTLZW:
|
||||
println("\t{}.u64 = __lzcnt({}.u32);", r(insn.operands[0]), r(insn.operands[1]));
|
||||
println("\t{0}.u64 = {1}.u32 == 0 ? 32 : __builtin_clz({1}.u32);", r(insn.operands[0]), r(insn.operands[1]));
|
||||
break;
|
||||
|
||||
case PPC_INST_CROR:
|
||||
@@ -1387,43 +1492,43 @@ bool Recompiler::Recompile(
|
||||
break;
|
||||
|
||||
case PPC_INST_RLDICL:
|
||||
println("\t{}.u64 = _rotl64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(insn.operands[3], 63));
|
||||
println("\t{}.u64 = __builtin_rotateleft64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(insn.operands[3], 63));
|
||||
break;
|
||||
|
||||
case PPC_INST_RLDICR:
|
||||
println("\t{}.u64 = _rotl64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(0, insn.operands[3]));
|
||||
println("\t{}.u64 = __builtin_rotateleft64({}.u64, {}) & 0x{:X};", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2], ComputeMask(0, insn.operands[3]));
|
||||
break;
|
||||
|
||||
case PPC_INST_RLDIMI:
|
||||
{
|
||||
const uint64_t mask = ComputeMask(insn.operands[3], ~insn.operands[2]);
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
case PPC_INST_RLWIMI:
|
||||
{
|
||||
const uint64_t mask = ComputeMask(insn.operands[3] + 32, insn.operands[4] + 32);
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
case PPC_INST_RLWINM:
|
||||
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));
|
||||
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));
|
||||
if (strchr(insn.opcode->name, '.'))
|
||||
println("\t{}.compare<int32_t>({}.s32, 0, {});", cr(0), r(insn.operands[0]), xer());
|
||||
break;
|
||||
|
||||
case PPC_INST_ROTLDI:
|
||||
println("\t{}.u64 = _rotl64({}.u64, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]);
|
||||
println("\t{}.u64 = __builtin_rotateleft64({}.u64, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]);
|
||||
break;
|
||||
|
||||
case PPC_INST_ROTLW:
|
||||
println("\t{}.u64 = _rotl({}.u32, {}.u8 & 0x1F);", r(insn.operands[0]), r(insn.operands[1]), r(insn.operands[2]));
|
||||
println("\t{}.u64 = __builtin_rotateleft32({}.u32, {}.u8 & 0x1F);", r(insn.operands[0]), r(insn.operands[1]), r(insn.operands[2]));
|
||||
break;
|
||||
|
||||
case PPC_INST_ROTLWI:
|
||||
println("\t{}.u64 = _rotl({}.u32, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]);
|
||||
println("\t{}.u64 = __builtin_rotateleft32({}.u32, {});", r(insn.operands[0]), r(insn.operands[1]), insn.operands[2]);
|
||||
if (strchr(insn.opcode->name, '.'))
|
||||
println("\t{}.compare<int32_t>({}.s32, 0, {});", cr(0), r(insn.operands[0]), xer());
|
||||
break;
|
||||
@@ -1528,10 +1633,10 @@ bool Recompiler::Recompile(
|
||||
case PPC_INST_STDCX:
|
||||
println("\t{}.lt = 0;", cr(0));
|
||||
println("\t{}.gt = 0;", cr(0));
|
||||
print("\t{}.eq = _InterlockedCompareExchange64(reinterpret_cast<__int64*>(base + ", cr(0));
|
||||
print("\t{}.eq = __sync_bool_compare_and_swap(reinterpret_cast<uint64_t*>(base + ", cr(0));
|
||||
if (insn.operands[1] != 0)
|
||||
print("{}.u32 + ", r(insn.operands[1]));
|
||||
println("{}.u32), __builtin_bswap64({}.s64), {}.s64) == {}.s64;", r(insn.operands[2]), r(insn.operands[0]), reserved(), reserved());
|
||||
println("{}.u32), {}.s64, __builtin_bswap64({}.s64));", r(insn.operands[2]), reserved(), r(insn.operands[0]));
|
||||
println("\t{}.so = {}.so;", cr(0), xer());
|
||||
break;
|
||||
|
||||
@@ -1724,10 +1829,10 @@ bool Recompiler::Recompile(
|
||||
case PPC_INST_STWCX:
|
||||
println("\t{}.lt = 0;", cr(0));
|
||||
println("\t{}.gt = 0;", cr(0));
|
||||
print("\t{}.eq = _InterlockedCompareExchange(reinterpret_cast<long*>(base + ", cr(0));
|
||||
print("\t{}.eq = __sync_bool_compare_and_swap(reinterpret_cast<uint32_t*>(base + ", cr(0));
|
||||
if (insn.operands[1] != 0)
|
||||
print("{}.u32 + ", r(insn.operands[1]));
|
||||
println("{}.u32), __builtin_bswap32({}.s32), {}.s32) == {}.s32;", r(insn.operands[2]), r(insn.operands[0]), reserved(), reserved());
|
||||
println("{}.u32), {}.s32, __builtin_bswap32({}.s32));", r(insn.operands[2]), reserved(), r(insn.operands[0]));
|
||||
println("\t{}.so = {}.so;", cr(0), xer());
|
||||
break;
|
||||
|
||||
@@ -1932,7 +2037,7 @@ bool Recompiler::Recompile(
|
||||
|
||||
case PPC_INST_VCMPBFP:
|
||||
case PPC_INST_VCMPBFP128:
|
||||
println("\t__debugbreak();");
|
||||
println("\t__builtin_debugtrap();");
|
||||
break;
|
||||
|
||||
case PPC_INST_VCMPEQFP:
|
||||
@@ -2132,8 +2237,8 @@ bool Recompiler::Recompile(
|
||||
switch (insn.operands[2])
|
||||
{
|
||||
case 0: // D3D color
|
||||
if (insn.operands[3] != 1 || insn.operands[4] != 3)
|
||||
std::println("Unexpected D3D color pack instruction at {:X}", base);
|
||||
if (insn.operands[3] != 1)
|
||||
fmt::println("Unexpected D3D color pack instruction at {:X}", base);
|
||||
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
{
|
||||
@@ -2142,11 +2247,33 @@ 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{}.u32 {}= uint32_t({}.u8[{}]) << {};", temp(), i == 0 ? "" : "|", vTemp(), i * 4, indices[i] * 8);
|
||||
}
|
||||
println("\t{}.u32[3] = {}.u32;", v(insn.operands[0]), temp());
|
||||
println("\t{}.u32[{}] = {}.u32;", v(insn.operands[0]), insn.operands[4], 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;
|
||||
|
||||
default:
|
||||
println("\t__debugbreak();");
|
||||
println("\t__builtin_debugtrap();");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -2397,7 +2524,7 @@ bool Recompiler::Recompile(
|
||||
break;
|
||||
|
||||
default:
|
||||
println("\t__debugbreak();");
|
||||
println("\t__builtin_debugtrap();");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -2456,9 +2583,12 @@ bool Recompiler::Recompile(
|
||||
{
|
||||
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)
|
||||
std::println("{} at {:X} has RC bit enabled but no comparison was generated", insn.opcode->name, base);
|
||||
fmt::println("{} at {:X} has RC bit enabled but no comparison was generated", insn.opcode->name, base);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (midAsmHook != config.midAsmHooks.end() && midAsmHook->second.afterInstruction)
|
||||
printMidAsmHook();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2474,7 +2604,7 @@ bool Recompiler::Recompile(const Function& fn)
|
||||
|
||||
for (size_t addr = base; addr < end; addr += 4)
|
||||
{
|
||||
const uint32_t instruction = std::byteswap(*(uint32_t*)((char*)data + addr - base));
|
||||
const uint32_t instruction = ByteSwap(*(uint32_t*)((char*)data + addr - base));
|
||||
if (!PPC_BL(instruction))
|
||||
{
|
||||
const size_t op = PPC_OP(instruction);
|
||||
@@ -2559,10 +2689,13 @@ bool Recompiler::Recompile(const Function& fn)
|
||||
}
|
||||
else
|
||||
{
|
||||
name = std::format("sub_{}", fn.base);
|
||||
name = fmt::format("sub_{}", fn.base);
|
||||
}
|
||||
|
||||
#ifdef XENON_RECOMP_USE_ALIAS
|
||||
println("__attribute__((alias(\"__imp__{}\"))) PPC_WEAK_FUNC({});", name, name);
|
||||
#endif
|
||||
|
||||
println("PPC_FUNC_IMPL(__imp__{}) {{", name);
|
||||
println("\tPPC_FUNC_PROLOGUE();");
|
||||
|
||||
@@ -2579,7 +2712,7 @@ bool Recompiler::Recompile(const Function& fn)
|
||||
ppc_insn insn;
|
||||
while (base < end)
|
||||
{
|
||||
if (labels.contains(base))
|
||||
if (labels.find(base) != labels.end())
|
||||
{
|
||||
println("loc_{:X}:", base);
|
||||
|
||||
@@ -2597,17 +2730,17 @@ bool Recompiler::Recompile(const Function& fn)
|
||||
println("\t// {}", insn.op_str);
|
||||
#if 1
|
||||
if (*data != 0)
|
||||
std::println("Unable to decode instruction {:X} at {:X}", *data, base);
|
||||
fmt::println("Unable to decode instruction {:X} at {:X}", *data, base);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
if (insn.opcode->id == PPC_INST_BCTR && (*(data - 1) == 0x07008038 || *(data - 1) == 0x00000060) && switchTable == config.switchTables.end())
|
||||
std::println("Found a switch jump table at {:X} with no switch table entry present", base);
|
||||
fmt::println("Found a switch jump table at {:X} with no switch table entry present", base);
|
||||
|
||||
if (!Recompile(fn, base, insn, data, switchTable, localVariables, csrState))
|
||||
{
|
||||
std::println("Unrecognized instruction at 0x{:X}: {}", base, insn.opcode->name);
|
||||
fmt::println("Unrecognized instruction at 0x{:X}: {}", base, insn.opcode->name);
|
||||
allRecompiled = false;
|
||||
}
|
||||
}
|
||||
@@ -2618,11 +2751,17 @@ bool Recompiler::Recompile(const Function& fn)
|
||||
|
||||
#if 0
|
||||
if (insn.opcode == nullptr || (insn.opcode->id != PPC_INST_B && insn.opcode->id != PPC_INST_BCTR && insn.opcode->id != PPC_INST_BLR))
|
||||
std::println("Function at {:X} ends prematurely with instruction {} at {:X}", fn.base, insn.opcode != nullptr ? insn.opcode->name : "INVALID", base - 4);
|
||||
fmt::println("Function at {:X} ends prematurely with instruction {} at {:X}", fn.base, insn.opcode != nullptr ? insn.opcode->name : "INVALID", base - 4);
|
||||
#endif
|
||||
|
||||
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);
|
||||
if (localVariables.ctr)
|
||||
println("\tPPCRegister ctr{{}};");
|
||||
@@ -2672,7 +2811,7 @@ bool Recompiler::Recompile(const Function& fn)
|
||||
return allRecompiled;
|
||||
}
|
||||
|
||||
void Recompiler::Recompile()
|
||||
void Recompiler::Recompile(const std::filesystem::path& headerFilePath)
|
||||
{
|
||||
out.reserve(10 * 1024 * 1024);
|
||||
|
||||
@@ -2701,6 +2840,30 @@ void Recompiler::Recompile()
|
||||
|
||||
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("#include \"ppc_detail.h\"");
|
||||
println("#endif");
|
||||
@@ -2714,7 +2877,14 @@ void Recompiler::Recompile()
|
||||
println("#pragma once");
|
||||
|
||||
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");
|
||||
}
|
||||
@@ -2752,7 +2922,7 @@ void Recompiler::Recompile()
|
||||
}
|
||||
|
||||
if ((i % 2048) == 0 || (i == (functions.size() - 1)))
|
||||
std::println("Recompiling functions... {}%", static_cast<float>(i + 1) / functions.size() * 100.0f);
|
||||
fmt::println("Recompiling functions... {}%", static_cast<float>(i + 1) / functions.size() * 100.0f);
|
||||
|
||||
Recompile(functions[i]);
|
||||
}
|
||||
@@ -2768,14 +2938,18 @@ void Recompiler::SaveCurrentOutData(const std::string_view& name)
|
||||
|
||||
if (name.empty())
|
||||
{
|
||||
cppName = std::format("ppc_recomp.{}.cpp", cppFileIndex);
|
||||
cppName = fmt::format("ppc_recomp.{}.cpp", cppFileIndex);
|
||||
++cppFileIndex;
|
||||
}
|
||||
|
||||
bool shouldWrite = true;
|
||||
|
||||
// Check if an identical file already exists first to not trigger recompilation
|
||||
std::string filePath = std::format("{}/{}/{}", config.directoryPath, config.outDirectoryPath, name.empty() ? cppName : name);
|
||||
std::string directoryPath = config.directoryPath;
|
||||
if (!directoryPath.empty())
|
||||
directoryPath += "/";
|
||||
|
||||
std::string filePath = fmt::format("{}{}/{}", directoryPath, config.outDirectoryPath, name.empty() ? cppName : name);
|
||||
FILE* f = fopen(filePath.c_str(), "rb");
|
||||
if (f)
|
||||
{
|
@@ -35,18 +35,18 @@ struct Recompiler
|
||||
size_t cppFileIndex = 0;
|
||||
RecompilerConfig config;
|
||||
|
||||
void LoadConfig(const std::string_view& configFilePath);
|
||||
bool LoadConfig(const std::string_view& configFilePath);
|
||||
|
||||
template<class... Args>
|
||||
void print(std::format_string<Args...> fmt, Args&&... args)
|
||||
void print(fmt::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
std::vformat_to(std::back_inserter(out), fmt.get(), std::make_format_args(args...));
|
||||
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
void println(std::format_string<Args...> fmt, Args&&... args)
|
||||
void println(fmt::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
std::vformat_to(std::back_inserter(out), fmt.get(), std::make_format_args(args...));
|
||||
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
|
||||
out += '\n';
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ struct Recompiler
|
||||
|
||||
bool Recompile(const Function& fn);
|
||||
|
||||
void Recompile();
|
||||
void Recompile(const std::filesystem::path& headerFilePath);
|
||||
|
||||
void SaveCurrentOutData(const std::string_view& name = std::string_view());
|
||||
};
|
@@ -3,12 +3,18 @@
|
||||
void RecompilerConfig::Load(const std::string_view& configFilePath)
|
||||
{
|
||||
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())
|
||||
{
|
||||
const auto& main = *mainPtr;
|
||||
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>("");
|
||||
switchTableFilePath = main["switch_table_file_path"].value_or<std::string>("");
|
||||
|
||||
@@ -32,6 +38,15 @@ void RecompilerConfig::Load(const std::string_view& configFilePath)
|
||||
longJmpAddress = main["longjmp_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())
|
||||
{
|
||||
for (auto& func : *functionsArray)
|
||||
@@ -56,7 +71,11 @@ void RecompilerConfig::Load(const std::string_view& configFilePath)
|
||||
|
||||
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())
|
||||
{
|
||||
for (auto& entry : *switchArray)
|
||||
@@ -100,16 +119,18 @@ void RecompilerConfig::Load(const std::string_view& configFilePath)
|
||||
(midAsmHook.returnOnTrue && midAsmHook.jumpAddressOnTrue != NULL) ||
|
||||
(midAsmHook.returnOnFalse && midAsmHook.jumpAddressOnFalse != NULL))
|
||||
{
|
||||
std::println("{}: can't return and jump at the same time", midAsmHook.name);
|
||||
fmt::println("{}: can't return and jump at the same time", midAsmHook.name);
|
||||
}
|
||||
|
||||
if ((midAsmHook.ret || midAsmHook.jumpAddress != NULL) &&
|
||||
(midAsmHook.returnOnFalse != NULL || midAsmHook.returnOnTrue != NULL ||
|
||||
midAsmHook.jumpAddressOnFalse != NULL || midAsmHook.jumpAddressOnTrue != NULL))
|
||||
{
|
||||
std::println("{}: can't mix direct and conditional return/jump", midAsmHook.name);
|
||||
fmt::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));
|
||||
}
|
||||
}
|
@@ -18,12 +18,16 @@ struct RecompilerMidAsmHook
|
||||
uint32_t jumpAddress = 0;
|
||||
uint32_t jumpAddressOnTrue = 0;
|
||||
uint32_t jumpAddressOnFalse = 0;
|
||||
|
||||
bool afterInstruction = false;
|
||||
};
|
||||
|
||||
struct RecompilerConfig
|
||||
{
|
||||
std::string directoryPath;
|
||||
std::string filePath;
|
||||
std::string patchFilePath;
|
||||
std::string patchedFilePath;
|
||||
std::string outDirectoryPath;
|
||||
std::string switchTableFilePath;
|
||||
std::unordered_map<uint32_t, RecompilerSwitchTable> switchTables;
|
@@ -22,7 +22,7 @@ void TestRecompiler::Analyse(const std::string_view& testName)
|
||||
}
|
||||
|
||||
auto& fn = functions.emplace_back(Function::Analyze(data, dataEnd - data, base));
|
||||
image.symbols.emplace(std::format("{}_{:X}", testName, fn.base), fn.base, fn.size, Symbol_Function);
|
||||
image.symbols.emplace(fmt::format("{}_{:X}", testName, fn.base), fn.base, fn.size, Symbol_Function);
|
||||
|
||||
base += fn.size;
|
||||
data += fn.size;
|
||||
@@ -40,18 +40,18 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
{
|
||||
if (file.path().extension() == ".o")
|
||||
{
|
||||
const auto exeFile = LoadFile(file.path().string().c_str()).value();
|
||||
const auto exeFile = LoadFile(file.path().string().c_str());
|
||||
|
||||
TestRecompiler recompiler;
|
||||
recompiler.config.outDirectoryPath = dstDirectoryPath;
|
||||
recompiler.image = Image::ParseImage(exeFile.data(), exeFile.size()).value();
|
||||
recompiler.image = Image::ParseImage(exeFile.data(), exeFile.size());
|
||||
|
||||
auto stem = file.path().stem().string();
|
||||
recompiler.Analyse(stem);
|
||||
|
||||
recompiler.println("#define PPC_CONFIG_H_INCLUDED");
|
||||
recompiler.println("#include <ppc_context.h>\n");
|
||||
recompiler.println("#define __debugbreak()\n");
|
||||
recompiler.println("#define __builtin_debugtrap()\n");
|
||||
|
||||
for (auto& fn : recompiler.functions)
|
||||
{
|
||||
@@ -61,7 +61,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println("Function {:X} in {} has unimplemented instructions", fn.base, stem);
|
||||
fmt::println("Function {:X} in {} has unimplemented instructions", fn.base, stem);
|
||||
}
|
||||
}
|
||||
stem += ".cpp";
|
||||
@@ -73,7 +73,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
|
||||
for (auto& [fn, addr] : functions)
|
||||
{
|
||||
std::ifstream in(std::format("{}/{}.dis", srcDirectoryPath, fn));
|
||||
std::ifstream in(fmt::format("{}/{}.dis", srcDirectoryPath, fn));
|
||||
if (in.is_open())
|
||||
{
|
||||
std::string line;
|
||||
@@ -86,30 +86,34 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
size_t address = ~0;
|
||||
std::from_chars(&line[0], &line[spaceIndex], address, 16);
|
||||
address &= 0xFFFFF;
|
||||
if (addr.contains(address))
|
||||
symbols.emplace(line.substr(spaceIndex + 2, bracketIndex - spaceIndex - 2), std::format("{}_{:X}", fn, address));
|
||||
if (addr.find(address) != addr.end())
|
||||
symbols.emplace(line.substr(spaceIndex + 2, bracketIndex - spaceIndex - 2), fmt::format("{}_{:X}", fn, address));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println("Unable to locate disassembly file for {}", fn);
|
||||
fmt::println("Unable to locate disassembly file for {}", fn);
|
||||
}
|
||||
}
|
||||
|
||||
FILE* file = fopen(std::format("{}/main.cpp", dstDirectoryPath).c_str(), "w");
|
||||
FILE* file = fopen(fmt::format("{}/main.cpp", dstDirectoryPath).c_str(), "w");
|
||||
std::string main;
|
||||
|
||||
std::println(file, "#define PPC_CONFIG_H_INCLUDED");
|
||||
std::println(file, "#include <ppc_context.h>");
|
||||
std::println(file, "#include <Windows.h>");
|
||||
std::println(file, "#include <print>\n");
|
||||
std::println(file, "#define PPC_CHECK_VALUE_U(f, lhs, rhs) if (lhs != rhs) std::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{:X}}\", lhs)\n");
|
||||
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, "#define PPC_CONFIG_H_INCLUDED");
|
||||
fmt::println(file, "#include <ppc_context.h>");
|
||||
fmt::println(file, "#ifdef _WIN32");
|
||||
fmt::println(file, "#include <Windows.h>");
|
||||
fmt::println(file, "#else");
|
||||
fmt::println(file, "#include <sys/mman.h>");
|
||||
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)
|
||||
{
|
||||
std::ifstream in(std::format("{}/../{}.s", srcDirectoryPath, fn));
|
||||
std::ifstream in(fmt::format("{}/../{}.s", srcDirectoryPath, fn));
|
||||
if (in.is_open())
|
||||
{
|
||||
std::string str;
|
||||
@@ -135,10 +139,10 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
auto symbol = symbols.find(name);
|
||||
if (symbol != symbols.end())
|
||||
{
|
||||
std::println(file, "PPC_FUNC({});\n", symbol->second);
|
||||
std::println(file, "void {}(uint8_t* base) {{", name);
|
||||
std::println(file, "\tPPCContext ctx{{}};");
|
||||
std::println(file, "\tctx.fpscr.loadFromHost();");
|
||||
fmt::println(file, "PPC_FUNC({});\n", symbol->second);
|
||||
fmt::println(file, "void {}(uint8_t* base) {{", name);
|
||||
fmt::println(file, "\tPPCContext ctx{{}};");
|
||||
fmt::println(file, "\tctx.fpscr.loadFromHost();");
|
||||
|
||||
while (getline() && !str.empty() && str[0] == '#')
|
||||
{
|
||||
@@ -158,14 +162,14 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
int commaIndex2 = str.find(',', commaIndex1 + 1);
|
||||
int closingBracketIndex = str.find(']', commaIndex2 + 1);
|
||||
|
||||
std::println(file, "\tctx.{}.u32[3] = 0x{};", reg, str.substr(openingBracketIndex + 1, commaIndex0 - openingBracketIndex - 1));
|
||||
std::println(file, "\tctx.{}.u32[2] = 0x{};", reg, str.substr(commaIndex0 + 2, commaIndex1 - commaIndex0 - 2));
|
||||
std::println(file, "\tctx.{}.u32[1] = 0x{};", reg, str.substr(commaIndex1 + 2, commaIndex2 - commaIndex1 - 2));
|
||||
std::println(file, "\tctx.{}.u32[0] = 0x{};", reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
|
||||
fmt::println(file, "\tctx.{}.u32[3] = 0x{};", reg, str.substr(openingBracketIndex + 1, commaIndex0 - openingBracketIndex - 1));
|
||||
fmt::println(file, "\tctx.{}.u32[2] = 0x{};", reg, str.substr(commaIndex0 + 2, commaIndex1 - commaIndex0 - 2));
|
||||
fmt::println(file, "\tctx.{}.u32[1] = 0x{};", reg, str.substr(commaIndex1 + 2, commaIndex2 - commaIndex1 - 2));
|
||||
fmt::println(file, "\tctx.{}.u32[0] = 0x{};", reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println(file, "\tctx.{}.{}64 = {};",
|
||||
fmt::println(file, "\tctx.{}.{}64 = {};",
|
||||
reg,
|
||||
str.find('.', secondSpaceIndex) != std::string::npos ? 'f' : 'u',
|
||||
str.substr(secondSpaceIndex + 1));
|
||||
@@ -183,7 +187,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
{
|
||||
if (str[i] != ' ')
|
||||
{
|
||||
std::println(file, "\tbase[0x{} + 0x{:X}] = 0x{}{};", address, j, str[i], str[i + 1]);
|
||||
fmt::println(file, "\tbase[0x{} + 0x{:X}] = 0x{}{};", address, j, str[i], str[i + 1]);
|
||||
++i; // the loop adds another
|
||||
++j;
|
||||
}
|
||||
@@ -196,7 +200,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
while (getline() && (str.empty() || str[0] != '#'))
|
||||
;
|
||||
|
||||
std::println(file, "\t{}(ctx, base);", symbol->second);
|
||||
fmt::println(file, "\t{}(ctx, base);", symbol->second);
|
||||
|
||||
do
|
||||
{
|
||||
@@ -218,14 +222,14 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
int commaIndex2 = str.find(',', commaIndex1 + 1);
|
||||
int closingBracketIndex = str.find(']', commaIndex2 + 1);
|
||||
|
||||
std::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[2], 0x{});", name, reg, str.substr(commaIndex0 + 2, commaIndex1 - commaIndex0 - 2));
|
||||
std::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[0], 0x{});", name, reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
|
||||
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[3], 0x{});", name, reg, str.substr(openingBracketIndex + 1, commaIndex0 - openingBracketIndex - 1));
|
||||
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[2], 0x{});", name, reg, str.substr(commaIndex0 + 2, commaIndex1 - commaIndex0 - 2));
|
||||
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[1], 0x{});", name, reg, str.substr(commaIndex1 + 2, commaIndex2 - commaIndex1 - 2));
|
||||
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, ctx.{}.u32[0], 0x{});", name, reg, str.substr(commaIndex2 + 2, closingBracketIndex - commaIndex2 - 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println(file, "\tPPC_CHECK_VALUE_{}({}, ctx.{}.{}64, {});",
|
||||
fmt::println(file, "\tPPC_CHECK_VALUE_{}({}, ctx.{}.{}64, {});",
|
||||
str.find('.', secondSpaceIndex) != std::string::npos ? 'F' : 'U',
|
||||
name,
|
||||
reg,
|
||||
@@ -245,7 +249,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
{
|
||||
if (str[i] != ' ')
|
||||
{
|
||||
std::println(file, "\tPPC_CHECK_VALUE_U({}, base[0x{} + 0x{:X}], 0x{}{});", name, address, j, str[i], str[i + 1]);
|
||||
fmt::println(file, "\tPPC_CHECK_VALUE_U({}, base[0x{} + 0x{:X}], 0x{}{});", name, address, j, str[i], str[i + 1]);
|
||||
++i; // the loop adds another
|
||||
++j;
|
||||
}
|
||||
@@ -255,13 +259,13 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
}
|
||||
} while (getline() && !str.empty() && str[0] == '#');
|
||||
|
||||
std::println(file, "}}\n");
|
||||
fmt::println(file, "}}\n");
|
||||
|
||||
std::format_to(std::back_inserter(main), "\t{}(base);\n", name);
|
||||
fmt::format_to(std::back_inserter(main), "\t{}(base);\n", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println("Found no symbol for {}", name);
|
||||
fmt::println("Found no symbol for {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,15 +273,19 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
|
||||
}
|
||||
else
|
||||
{
|
||||
std::println("Unable to locate source file for {}", fn);
|
||||
fmt::println("Unable to locate source file for {}", fn);
|
||||
}
|
||||
}
|
||||
|
||||
std::println(file, "int main() {{");
|
||||
std::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, 0x100000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));");
|
||||
fmt::println(file, "int main() {{");
|
||||
fmt::println(file, "#ifdef _WIN32");
|
||||
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);
|
||||
std::println(file, "\treturn 0;");
|
||||
std::println(file, "}}");
|
||||
fmt::println(file, "\treturn 0;");
|
||||
fmt::println(file, "}}");
|
||||
|
||||
fclose(file);
|
||||
}
|
20
XenonTests/CMakeLists.txt
Normal file
20
XenonTests/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
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()
|
31
XenonUtils/CMakeLists.txt
Normal file
31
XenonUtils/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
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
|
||||
)
|
25
XenonUtils/byteswap.h
Normal file
25
XenonUtils/byteswap.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#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);
|
||||
}
|
28
XenonUtils/file.h
Normal file
28
XenonUtils/file.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#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;
|
||||
}
|
@@ -2,11 +2,12 @@
|
||||
#include "elf.h"
|
||||
#include "xex.h"
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
void Image::Map(const std::string_view& name, size_t base, uint32_t size, uint8_t flags, uint8_t* data)
|
||||
{
|
||||
sections.emplace(std::string(name), this->base + base,
|
||||
size, static_cast<SectionFlags>(flags), data);
|
||||
sections.insert({ std::string(name), this->base + base,
|
||||
size, static_cast<SectionFlags>(flags), data });
|
||||
}
|
||||
|
||||
const void* Image::Find(size_t address) const
|
||||
@@ -28,7 +29,7 @@ const Section* Image::Find(const std::string_view& name) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::expected<Image, int> Image::ParseImage(const uint8_t* data, size_t size)
|
||||
Image Image::ParseImage(const uint8_t* data, size_t size)
|
||||
{
|
||||
if (data[0] == ELFMAG0 && data[1] == ELFMAG1 && data[2] == ELFMAG2 && data[3] == ELFMAG3)
|
||||
{
|
||||
@@ -36,10 +37,10 @@ std::expected<Image, int> Image::ParseImage(const uint8_t* data, size_t size)
|
||||
}
|
||||
else if (data[0] == 'X' && data[1] == 'E' && data[2] == 'X' && data[3] == '2')
|
||||
{
|
||||
return Xex2LoadImage(data);
|
||||
return Xex2LoadImage(data, size);
|
||||
}
|
||||
|
||||
return std::unexpected(1);
|
||||
return {};
|
||||
}
|
||||
|
||||
Image ElfLoadImage(const uint8_t* data, size_t size)
|
||||
@@ -50,27 +51,27 @@ Image ElfLoadImage(const uint8_t* data, size_t size)
|
||||
Image image{};
|
||||
image.size = size;
|
||||
image.data = std::make_unique<uint8_t[]>(size);
|
||||
image.entry_point = std::byteswap(header->e_entry);
|
||||
image.entry_point = ByteSwap(header->e_entry);
|
||||
memcpy(image.data.get(), data, size);
|
||||
|
||||
auto stringTableIndex = std::byteswap(header->e_shstrndx);
|
||||
auto stringTableIndex = ByteSwap(header->e_shstrndx);
|
||||
|
||||
const auto numSections = std::byteswap(header->e_shnum);
|
||||
const auto numpSections = std::byteswap(header->e_phnum);
|
||||
const auto numSections = ByteSwap(header->e_shnum);
|
||||
const auto numpSections = ByteSwap(header->e_phnum);
|
||||
|
||||
const auto* sections = (elf32_shdr*)(data + std::byteswap(header->e_shoff));
|
||||
const auto* psections = (elf32_phdr*)(data + std::byteswap(header->e_phoff));
|
||||
const auto* sections = (elf32_shdr*)(data + ByteSwap(header->e_shoff));
|
||||
const auto* psections = (elf32_phdr*)(data + ByteSwap(header->e_phoff));
|
||||
|
||||
for (size_t i = 0; i < numpSections; i++)
|
||||
{
|
||||
if (psections[i].p_type == std::byteswap((Elf32_Word)PT_LOAD))
|
||||
if (psections[i].p_type == ByteSwap((Elf32_Word)PT_LOAD))
|
||||
{
|
||||
image.base = std::byteswap(psections[i].p_vaddr);
|
||||
image.base = ByteSwap(psections[i].p_vaddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto* stringTable = reinterpret_cast<const char*>(data + std::byteswap(sections[stringTableIndex].sh_offset));
|
||||
auto* stringTable = reinterpret_cast<const char*>(data + ByteSwap(sections[stringTableIndex].sh_offset));
|
||||
|
||||
for (size_t i = 0; i < numSections; i++)
|
||||
{
|
||||
@@ -82,16 +83,16 @@ Image ElfLoadImage(const uint8_t* data, size_t size)
|
||||
|
||||
uint8_t flags{};
|
||||
|
||||
if (section.sh_flags & std::byteswap(SHF_EXECINSTR))
|
||||
if (section.sh_flags & ByteSwap(SHF_EXECINSTR))
|
||||
{
|
||||
flags |= SectionFlags_Code;
|
||||
}
|
||||
|
||||
auto* name = section.sh_name != 0 ? stringTable + std::byteswap(section.sh_name) : nullptr;
|
||||
const auto rva = std::byteswap(section.sh_addr) - image.base;
|
||||
const auto size = std::byteswap(section.sh_size);
|
||||
auto* name = section.sh_name != 0 ? stringTable + ByteSwap(section.sh_name) : nullptr;
|
||||
const auto rva = ByteSwap(section.sh_addr) - image.base;
|
||||
const auto size = ByteSwap(section.sh_size);
|
||||
|
||||
image.Map(name, rva, size, flags, image.data.get() + std::byteswap(section.sh_offset));
|
||||
image.Map(name, rva, size, flags, image.data.get() + ByteSwap(section.sh_offset));
|
||||
}
|
||||
|
||||
return image;
|
@@ -2,7 +2,6 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <expected>
|
||||
#include <section.h>
|
||||
#include "symbol_table.h"
|
||||
|
||||
@@ -44,7 +43,7 @@ struct Image
|
||||
* \param size Size of data
|
||||
* \return Parsed image
|
||||
*/
|
||||
static std::expected<Image, int> ParseImage(const uint8_t* data, size_t size);
|
||||
static Image ParseImage(const uint8_t* data, size_t size);
|
||||
};
|
||||
|
||||
Image ElfLoadImage(const uint8_t* data, size_t size);
|
169
XenonUtils/memory_mapped_file.cpp
Normal file
169
XenonUtils/memory_mapped_file.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#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
|
||||
}
|
32
XenonUtils/memory_mapped_file.h
Normal file
32
XenonUtils/memory_mapped_file.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#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;
|
||||
};
|
@@ -5,15 +5,22 @@
|
||||
#error "ppc_config.h must be included before ppc_context.h"
|
||||
#endif
|
||||
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <csetjmp>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <intrin.h>
|
||||
#include <x86intrin.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <xmmintrin.h>
|
||||
#include <smmintrin.h>
|
||||
#endif
|
||||
|
||||
#define PPC_JOIN(x, y) x##y
|
||||
#define PPC_XSTRINGIFY(x) #x
|
||||
#define PPC_STRINGIFY(x) PPC_XSTRINGIFY(x)
|
||||
@@ -22,7 +29,7 @@
|
||||
#define PPC_EXTERN_FUNC(x) extern PPC_FUNC(x)
|
||||
#define PPC_WEAK_FUNC(x) __attribute__((weak,noinline)) PPC_FUNC(x)
|
||||
|
||||
#define PPC_FUNC_PROLOGUE() __builtin_assume(((size_t)base & 0xFFFFFFFF) == 0)
|
||||
#define PPC_FUNC_PROLOGUE() __builtin_assume(((size_t)base & 0x1F) == 0)
|
||||
|
||||
#ifndef PPC_LOAD_U8
|
||||
#define PPC_LOAD_U8(x) *(volatile uint8_t*)(base + (x))
|
||||
@@ -98,8 +105,12 @@
|
||||
#define PPC_CALL_FUNC(x) x(ctx, base)
|
||||
#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
|
||||
#define PPC_CALL_INDIRECT_FUNC(x) (*(PPCFunc**)(ctx.fn + uint64_t(x) * 2))(ctx, base)
|
||||
#define PPC_CALL_INDIRECT_FUNC(x) (PPC_LOOKUP_FUNC(base, x))(ctx, base)
|
||||
#endif
|
||||
|
||||
typedef void PPCFunc(struct PPCContext& __restrict__ ctx, uint8_t* base);
|
||||
@@ -112,21 +123,18 @@ struct PPCFuncMapping
|
||||
|
||||
extern PPCFuncMapping PPCFuncMappings[];
|
||||
|
||||
struct PPCRegister
|
||||
union PPCRegister
|
||||
{
|
||||
union
|
||||
{
|
||||
int8_t s8;
|
||||
uint8_t u8;
|
||||
int16_t s16;
|
||||
uint16_t u16;
|
||||
int32_t s32;
|
||||
uint32_t u32;
|
||||
int64_t s64;
|
||||
uint64_t u64;
|
||||
float f32;
|
||||
double f64;
|
||||
};
|
||||
int8_t s8;
|
||||
uint8_t u8;
|
||||
int16_t s16;
|
||||
uint16_t u16;
|
||||
int32_t s32;
|
||||
uint32_t u32;
|
||||
int64_t s64;
|
||||
uint64_t u64;
|
||||
float f32;
|
||||
double f64;
|
||||
};
|
||||
|
||||
struct PPCXERRegister
|
||||
@@ -183,21 +191,18 @@ struct PPCCRRegister
|
||||
}
|
||||
};
|
||||
|
||||
struct alignas(0x10) PPCVRegister
|
||||
union alignas(0x10) PPCVRegister
|
||||
{
|
||||
union
|
||||
{
|
||||
int8_t s8[16];
|
||||
uint8_t u8[16];
|
||||
int16_t s16[8];
|
||||
uint16_t u16[8];
|
||||
int32_t s32[4];
|
||||
uint32_t u32[4];
|
||||
int64_t s64[2];
|
||||
uint64_t u64[2];
|
||||
float f32[4];
|
||||
double f64[2];
|
||||
};
|
||||
int8_t s8[16];
|
||||
uint8_t u8[16];
|
||||
int16_t s16[8];
|
||||
uint16_t u16[8];
|
||||
int32_t s32[4];
|
||||
uint32_t u32[4];
|
||||
int64_t s64[2];
|
||||
uint64_t u64[2];
|
||||
float f32[4];
|
||||
double f64[2];
|
||||
};
|
||||
|
||||
#define PPC_ROUND_NEAREST 0x00
|
||||
@@ -259,37 +264,9 @@ struct PPCFPSCRRegister
|
||||
}
|
||||
};
|
||||
|
||||
struct PPCContext
|
||||
struct alignas(0x40) PPCContext
|
||||
{
|
||||
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
|
||||
|
||||
PPCRegister r3;
|
||||
#ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL
|
||||
PPCRegister r0;
|
||||
#endif
|
||||
@@ -297,7 +274,6 @@ struct PPCContext
|
||||
#ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL
|
||||
PPCRegister r2;
|
||||
#endif
|
||||
PPCRegister r3;
|
||||
PPCRegister r4;
|
||||
PPCRegister r5;
|
||||
PPCRegister r6;
|
||||
@@ -331,6 +307,31 @@ struct PPCContext
|
||||
PPCRegister r31;
|
||||
#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;
|
||||
|
||||
#ifndef PPC_CONFIG_NON_ARGUMENT_AS_LOCAL
|
@@ -13,9 +13,18 @@ enum SymbolType
|
||||
struct Symbol
|
||||
{
|
||||
mutable std::string name{};
|
||||
uint32_t address{};
|
||||
uint32_t size{};
|
||||
size_t address{};
|
||||
size_t size{};
|
||||
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
|
@@ -3,49 +3,10 @@
|
||||
#include <type_traits>
|
||||
#include <bit>
|
||||
#include <string>
|
||||
|
||||
#include "byteswap.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#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
|
||||
|
||||
// real win32 handles will never use the upper bits unless something goes really wrong
|
||||
@@ -81,22 +42,22 @@ struct be
|
||||
{
|
||||
if constexpr (std::is_same_v<T, double>)
|
||||
{
|
||||
const uint64_t swapped = std::byteswap(*reinterpret_cast<uint64_t*>(&value));
|
||||
const uint64_t swapped = ByteSwap(*reinterpret_cast<uint64_t*>(&value));
|
||||
return *reinterpret_cast<const T*>(&swapped);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, float>)
|
||||
{
|
||||
const uint32_t swapped = std::byteswap(*reinterpret_cast<uint32_t*>(&value));
|
||||
const uint32_t swapped = ByteSwap(*reinterpret_cast<uint32_t*>(&value));
|
||||
return *reinterpret_cast<const T*>(&swapped);
|
||||
}
|
||||
else if constexpr (std::is_enum_v<T>)
|
||||
{
|
||||
const std::underlying_type_t<T> swapped = std::byteswap(*reinterpret_cast<std::underlying_type_t<T>*>(&value));
|
||||
const std::underlying_type_t<T> swapped = ByteSwap(*reinterpret_cast<std::underlying_type_t<T>*>(&value));
|
||||
return *reinterpret_cast<const T*>(&swapped);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::byteswap(value);
|
||||
return ByteSwap(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +105,7 @@ struct xpointer
|
||||
{
|
||||
}
|
||||
|
||||
xpointer(T* ptr) : ptr((uint32_t)ptr)
|
||||
xpointer(T* p) : ptr(p != nullptr ? (reinterpret_cast<size_t>(p) - reinterpret_cast<size_t>(MmGetHostAddress(0))) : 0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -175,33 +136,23 @@ struct HostObject
|
||||
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;
|
||||
typedef _XLIST_ENTRY XLIST_ENTRY;
|
||||
typedef xpointer<XLIST_ENTRY> PXLIST_ENTRY;
|
||||
|
||||
typedef struct _IMAGE_CE_RUNTIME_FUNCTION
|
||||
{
|
||||
DWORD BeginAddress;
|
||||
uint32_t BeginAddress;
|
||||
|
||||
union
|
||||
{
|
||||
DWORD Data;
|
||||
uint32_t Data;
|
||||
struct
|
||||
{
|
||||
DWORD PrologLength : 8;
|
||||
DWORD FunctionLength : 22;
|
||||
DWORD ThirtyTwoBit : 1;
|
||||
DWORD ExceptionFlag : 1;
|
||||
uint32_t PrologLength : 8;
|
||||
uint32_t FunctionLength : 22;
|
||||
uint32_t ThirtyTwoBit : 1;
|
||||
uint32_t ExceptionFlag : 1;
|
||||
};
|
||||
};
|
||||
} IMAGE_CE_RUNTIME_FUNCTION;
|
||||
@@ -210,8 +161,8 @@ static_assert(sizeof(IMAGE_CE_RUNTIME_FUNCTION) == 8);
|
||||
|
||||
typedef struct _XLIST_ENTRY
|
||||
{
|
||||
XDWORD Flink;
|
||||
XDWORD Blink;
|
||||
be<uint32_t> Flink;
|
||||
be<uint32_t> Blink;
|
||||
} XLIST_ENTRY;
|
||||
|
||||
typedef struct _XDISPATCHER_HEADER
|
||||
@@ -220,30 +171,30 @@ typedef struct _XDISPATCHER_HEADER
|
||||
{
|
||||
struct
|
||||
{
|
||||
UCHAR Type;
|
||||
uint8_t Type;
|
||||
union
|
||||
{
|
||||
UCHAR Abandoned;
|
||||
UCHAR Absolute;
|
||||
UCHAR NpxIrql;
|
||||
UCHAR Signalling;
|
||||
uint8_t Abandoned;
|
||||
uint8_t Absolute;
|
||||
uint8_t NpxIrql;
|
||||
uint8_t Signalling;
|
||||
};
|
||||
union
|
||||
{
|
||||
UCHAR Size;
|
||||
UCHAR Hand;
|
||||
uint8_t Size;
|
||||
uint8_t Hand;
|
||||
};
|
||||
union
|
||||
{
|
||||
UCHAR Inserted;
|
||||
UCHAR DebugActive;
|
||||
UCHAR DpcActive;
|
||||
uint8_t Inserted;
|
||||
uint8_t DebugActive;
|
||||
uint8_t DpcActive;
|
||||
};
|
||||
};
|
||||
XDWORD Lock;
|
||||
be<uint32_t> Lock;
|
||||
};
|
||||
|
||||
XDWORD SignalState;
|
||||
be<uint32_t> SignalState;
|
||||
XLIST_ENTRY WaitListHead;
|
||||
} XDISPATCHER_HEADER, * XPDISPATCHER_HEADER;
|
||||
|
||||
@@ -251,20 +202,20 @@ typedef struct _XDISPATCHER_HEADER
|
||||
typedef struct _XRTL_CRITICAL_SECTION
|
||||
{
|
||||
XDISPATCHER_HEADER Header;
|
||||
long LockCount;
|
||||
int32_t LockCount;
|
||||
int32_t RecursionCount;
|
||||
uint32_t OwningThread;
|
||||
} XRTL_CRITICAL_SECTION;
|
||||
|
||||
typedef struct _XANSI_STRING {
|
||||
XWORD Length;
|
||||
XWORD MaximumLength;
|
||||
be<uint16_t> Length;
|
||||
be<uint16_t> MaximumLength;
|
||||
xpointer<char> Buffer;
|
||||
} XANSI_STRING;
|
||||
|
||||
typedef struct _XOBJECT_ATTRIBUTES
|
||||
{
|
||||
XDWORD RootDirectory;
|
||||
be<uint32_t> RootDirectory;
|
||||
xpointer<XANSI_STRING> Name;
|
||||
xpointer<void> Attributes;
|
||||
} XOBJECT_ATTRIBUTES;
|
||||
@@ -274,18 +225,18 @@ typedef XDISPATCHER_HEADER XKEVENT;
|
||||
typedef struct _XIO_STATUS_BLOCK
|
||||
{
|
||||
union {
|
||||
XDWORD Status;
|
||||
XDWORD Pointer;
|
||||
be<uint32_t> Status;
|
||||
be<uint32_t> Pointer;
|
||||
};
|
||||
be<uint32_t> Information;
|
||||
} XIO_STATUS_BLOCK;
|
||||
|
||||
typedef struct _XOVERLAPPED {
|
||||
XDWORD Internal;
|
||||
XDWORD InternalHigh;
|
||||
XDWORD Offset;
|
||||
XDWORD OffsetHigh;
|
||||
XDWORD hEvent;
|
||||
be<uint32_t> Internal;
|
||||
be<uint32_t> InternalHigh;
|
||||
be<uint32_t> Offset;
|
||||
be<uint32_t> OffsetHigh;
|
||||
be<uint32_t> hEvent;
|
||||
} XOVERLAPPED;
|
||||
|
||||
// this name is so dumb
|
||||
@@ -294,8 +245,8 @@ typedef struct _XXOVERLAPPED {
|
||||
{
|
||||
struct
|
||||
{
|
||||
XDWORD Error;
|
||||
XDWORD Length;
|
||||
be<uint32_t> Error;
|
||||
be<uint32_t> Length;
|
||||
};
|
||||
|
||||
struct
|
||||
@@ -305,24 +256,24 @@ typedef struct _XXOVERLAPPED {
|
||||
};
|
||||
};
|
||||
uint32_t InternalContext;
|
||||
XDWORD hEvent;
|
||||
XDWORD pCompletionRoutine;
|
||||
XDWORD dwCompletionContext;
|
||||
XDWORD dwExtendedError;
|
||||
be<uint32_t> hEvent;
|
||||
be<uint32_t> pCompletionRoutine;
|
||||
be<uint32_t> dwCompletionContext;
|
||||
be<uint32_t> dwExtendedError;
|
||||
} XXOVERLAPPED, *PXXOVERLAPPED;
|
||||
|
||||
static_assert(sizeof(_XXOVERLAPPED) == 0x1C);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-memorystatus
|
||||
typedef struct _XMEMORYSTATUS {
|
||||
XDWORD dwLength;
|
||||
XDWORD dwMemoryLoad;
|
||||
XDWORD dwTotalPhys;
|
||||
XDWORD dwAvailPhys;
|
||||
XDWORD dwTotalPageFile;
|
||||
XDWORD dwAvailPageFile;
|
||||
XDWORD dwTotalVirtual;
|
||||
XDWORD dwAvailVirtual;
|
||||
be<uint32_t> dwLength;
|
||||
be<uint32_t> dwMemoryLoad;
|
||||
be<uint32_t> dwTotalPhys;
|
||||
be<uint32_t> dwAvailPhys;
|
||||
be<uint32_t> dwTotalPageFile;
|
||||
be<uint32_t> dwAvailPageFile;
|
||||
be<uint32_t> dwTotalVirtual;
|
||||
be<uint32_t> dwAvailVirtual;
|
||||
} XMEMORYSTATUS, * XLPMEMORYSTATUS;
|
||||
|
||||
typedef struct _XVIDEO_MODE
|
||||
@@ -342,28 +293,28 @@ typedef struct _XVIDEO_MODE
|
||||
typedef struct _XKSEMAPHORE
|
||||
{
|
||||
XDISPATCHER_HEADER Header;
|
||||
XDWORD Limit;
|
||||
be<uint32_t> Limit;
|
||||
} XKSEMAPHORE;
|
||||
|
||||
typedef struct _XUSER_SIGNIN_INFO {
|
||||
be<ULONGLONG> xuid;
|
||||
XDWORD dwField08;
|
||||
XDWORD SigninState;
|
||||
XDWORD dwField10;
|
||||
XDWORD dwField14;
|
||||
CHAR Name[16];
|
||||
be<uint64_t> xuid;
|
||||
be<uint32_t> dwField08;
|
||||
be<uint32_t> SigninState;
|
||||
be<uint32_t> dwField10;
|
||||
be<uint32_t> dwField14;
|
||||
char Name[16];
|
||||
} XUSER_SIGNIN_INFO;
|
||||
|
||||
typedef struct _XTIME_FIELDS
|
||||
{
|
||||
XWORD Year;
|
||||
XWORD Month;
|
||||
XWORD Day;
|
||||
XWORD Hour;
|
||||
XWORD Minute;
|
||||
XWORD Second;
|
||||
XWORD Milliseconds;
|
||||
XWORD Weekday;
|
||||
be<uint16_t> Year;
|
||||
be<uint16_t> Month;
|
||||
be<uint16_t> Day;
|
||||
be<uint16_t> Hour;
|
||||
be<uint16_t> Minute;
|
||||
be<uint16_t> Second;
|
||||
be<uint16_t> Milliseconds;
|
||||
be<uint16_t> Weekday;
|
||||
} XTIME_FIELDS, * PXTIME_FIELDS;
|
||||
|
||||
// Content types
|
||||
@@ -380,10 +331,10 @@ typedef struct _XTIME_FIELDS
|
||||
|
||||
typedef struct _XCONTENT_DATA
|
||||
{
|
||||
XDWORD DeviceID;
|
||||
XDWORD dwContentType;
|
||||
be<WCHAR> szDisplayName[XCONTENT_MAX_DISPLAYNAME];
|
||||
CHAR szFileName[XCONTENT_MAX_FILENAME];
|
||||
be<uint32_t> DeviceID;
|
||||
be<uint32_t> dwContentType;
|
||||
be<uint16_t> szDisplayName[XCONTENT_MAX_DISPLAYNAME];
|
||||
char szFileName[XCONTENT_MAX_FILENAME];
|
||||
} XCONTENT_DATA, * PXCONTENT_DATA;
|
||||
|
||||
typedef struct _XHOSTCONTENT_DATA : _XCONTENT_DATA
|
||||
@@ -398,11 +349,11 @@ typedef struct _XHOSTCONTENT_DATA : _XCONTENT_DATA
|
||||
|
||||
typedef struct _XDEVICE_DATA
|
||||
{
|
||||
XDWORD DeviceID;
|
||||
XDWORD DeviceType;
|
||||
XQWORD ulDeviceBytes;
|
||||
XQWORD ulDeviceFreeBytes;
|
||||
be<WCHAR> wszName[XCONTENTDEVICE_MAX_NAME];
|
||||
be<uint32_t> DeviceID;
|
||||
be<uint32_t> DeviceType;
|
||||
be<uint64_t> ulDeviceBytes;
|
||||
be<uint64_t> ulDeviceFreeBytes;
|
||||
be<uint16_t> wszName[XCONTENTDEVICE_MAX_NAME];
|
||||
} XDEVICE_DATA, *PXDEVICE_DATA;
|
||||
|
||||
// Direct reflection of XInput structures
|
||||
@@ -427,32 +378,32 @@ typedef struct _XDEVICE_DATA
|
||||
|
||||
typedef struct _XAMINPUT_GAMEPAD
|
||||
{
|
||||
WORD wButtons;
|
||||
BYTE bLeftTrigger;
|
||||
BYTE bRightTrigger;
|
||||
SHORT sThumbLX;
|
||||
SHORT sThumbLY;
|
||||
SHORT sThumbRX;
|
||||
SHORT sThumbRY;
|
||||
uint16_t wButtons;
|
||||
uint8_t bLeftTrigger;
|
||||
uint8_t bRightTrigger;
|
||||
int16_t sThumbLX;
|
||||
int16_t sThumbLY;
|
||||
int16_t sThumbRX;
|
||||
int16_t sThumbRY;
|
||||
} XAMINPUT_GAMEPAD, *PXAMINPUT_GAMEPAD;
|
||||
|
||||
typedef struct _XAMINPUT_VIBRATION
|
||||
{
|
||||
WORD wLeftMotorSpeed;
|
||||
WORD wRightMotorSpeed;
|
||||
uint16_t wLeftMotorSpeed;
|
||||
uint16_t wRightMotorSpeed;
|
||||
} XAMINPUT_VIBRATION, * PXAMINPUT_VIBRATION;
|
||||
|
||||
typedef struct _XAMINPUT_CAPABILITIES
|
||||
{
|
||||
BYTE Type;
|
||||
BYTE SubType;
|
||||
WORD Flags;
|
||||
uint8_t Type;
|
||||
uint8_t SubType;
|
||||
uint16_t Flags;
|
||||
XAMINPUT_GAMEPAD Gamepad;
|
||||
XAMINPUT_VIBRATION Vibration;
|
||||
} XAMINPUT_CAPABILITIES, * PXAMINPUT_CAPABILITIES;
|
||||
|
||||
typedef struct _XAMINPUT_STATE
|
||||
{
|
||||
DWORD dwPacketNumber;
|
||||
uint32_t dwPacketNumber;
|
||||
XAMINPUT_GAMEPAD Gamepad;
|
||||
} XAMINPUT_STATE, * PXAMINPUT_STATE;
|
203
XenonUtils/xdbf.h
Normal file
203
XenonUtils/xdbf.h
Normal file
@@ -0,0 +1,203 @@
|
||||
#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;
|
||||
};
|
152
XenonUtils/xdbf_wrapper.cpp
Normal file
152
XenonUtils/xdbf_wrapper.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#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;
|
||||
}
|
46
XenonUtils/xdbf_wrapper.h
Normal file
46
XenonUtils/xdbf_wrapper.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#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;
|
||||
};
|
350
XenonUtils/xex.cpp
Normal file
350
XenonUtils/xex.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
#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;
|
||||
}
|
267
XenonUtils/xex.h
Normal file
267
XenonUtils/xex.h
Normal file
@@ -0,0 +1,267 @@
|
||||
#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);
|
568
XenonUtils/xex_patcher.cpp
Normal file
568
XenonUtils/xex_patcher.cpp
Normal file
@@ -0,0 +1,568 @@
|
||||
// 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;
|
||||
}
|
37
XenonUtils/xex_patcher.h
Normal file
37
XenonUtils/xex_patcher.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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);
|
||||
};
|
@@ -1,84 +0,0 @@
|
||||
# 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()
|
@@ -1,10 +0,0 @@
|
||||
int add(int a, int b)
|
||||
{
|
||||
int c = a + b;
|
||||
return c == 0 ? 50000 : c;
|
||||
}
|
||||
|
||||
extern "C" int _start()
|
||||
{
|
||||
return add(1, 2);
|
||||
}
|
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
int add(int a, int b)
|
||||
{
|
||||
return a + b;
|
||||
}
|
||||
|
||||
extern "C" int _start()
|
||||
{
|
||||
return add(1, 2);
|
||||
}
|
Binary file not shown.
@@ -1,15 +0,0 @@
|
||||
int cond(int a)
|
||||
{
|
||||
int v = 2;
|
||||
if (a == 1)
|
||||
{
|
||||
v += 5;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
extern "C" int _start()
|
||||
{
|
||||
return cond(0);
|
||||
}
|
Binary file not shown.
@@ -1,18 +0,0 @@
|
||||
int cond(int a)
|
||||
{
|
||||
if (a == 1)
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
else if (a == 4)
|
||||
{
|
||||
return 9;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int _start()
|
||||
{
|
||||
return cond(0);
|
||||
}
|
Binary file not shown.
@@ -1,15 +0,0 @@
|
||||
int loop(int a)
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < a; i++)
|
||||
{
|
||||
result = result * 31 + i;
|
||||
result *= result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C" int _start()
|
||||
{
|
||||
return loop(30);
|
||||
}
|
Binary file not shown.
2
tests/PowerAnalyse/private/.gitignore
vendored
2
tests/PowerAnalyse/private/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
@@ -1,5 +0,0 @@
|
||||
@echo off
|
||||
|
||||
pushd PowerAnalyse
|
||||
for %%f in (*.cpp) do call ..\compile.bat %%f
|
||||
popd
|
@@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
|
||||
clang -target powerpc-unknown-linux-gnu -fuse-ld=lld -nostdlib -m32 -o %~n1.elf %1
|
13
thirdparty/CMakeLists.txt
vendored
Normal file
13
thirdparty/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
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()
|
223
thirdparty/TinySHA1/TinySHA1.hpp
vendored
Normal file
223
thirdparty/TinySHA1/TinySHA1.hpp
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
4
thirdparty/disasm/disasm.c
vendored
4
thirdparty/disasm/disasm.c
vendored
@@ -1,5 +1,9 @@
|
||||
#include "dis-asm.h"
|
||||
|
||||
#ifndef EIO
|
||||
#define EIO 5
|
||||
#endif
|
||||
|
||||
/* Get LENGTH bytes from info's buffer, at target address memaddr.
|
||||
Transfer them to myaddr. */
|
||||
int
|
||||
|
2
thirdparty/disasm/ppc-dis.c
vendored
2
thirdparty/disasm/ppc-dis.c
vendored
@@ -840,7 +840,7 @@ const struct powerpc_operand powerpc_operands[] =
|
||||
{ 8, 0, insert_vperm, extract_vperm, 0 },
|
||||
|
||||
#define VD3D0 VPERM128 + 1
|
||||
{ 3, 18, NULL, NULL, 0 },
|
||||
{ 7, 18, NULL, NULL, 0 },
|
||||
|
||||
#define VD3D1 VD3D0 + 1
|
||||
{ 3, 16, NULL, NULL, 0 },
|
||||
|
1
thirdparty/fmt
vendored
Submodule
1
thirdparty/fmt
vendored
Submodule
Submodule thirdparty/fmt added at 873670ba3f
1
thirdparty/libmspack
vendored
Submodule
1
thirdparty/libmspack
vendored
Submodule
Submodule thirdparty/libmspack added at 305907723a
1
thirdparty/tiny-AES-c
vendored
Submodule
1
thirdparty/tiny-AES-c
vendored
Submodule
Submodule thirdparty/tiny-AES-c added at 23856752fb
1
thirdparty/tomlplusplus
vendored
Submodule
1
thirdparty/tomlplusplus
vendored
Submodule
Submodule thirdparty/tomlplusplus added at c4369ae1d8
1
thirdparty/xxHash
vendored
Submodule
1
thirdparty/xxHash
vendored
Submodule
Submodule thirdparty/xxHash added at 2bf8313b93
Reference in New Issue
Block a user