migrate from private cloud

This commit is contained in:
Ran-j
2025-04-12 03:49:35 -03:00
commit 6e9049be40
29 changed files with 5353 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
/bin/
/intermediate/
.vs/
out
.vscode
build
*.exe
*.ilk
*.exp
*.log
*.tlog
*.ipch
+9
View File
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.21)
project("PS2 Retro X")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_subdirectory("ps2xRecomp")
add_subdirectory("ps2xRuntime")
add_subdirectory("ps2xAnalyzer")
+104
View File
@@ -0,0 +1,104 @@
## PS2Recomp: PlayStation 2 Static Recompiler (Not ready)
* Note this is an experiment and does work as it should, feel free to open a PR to help the project.
PS2Recomp is a tool designed to statically recompile PlayStation 2 ELF binaries into C++ code that can be compiled for any modern platform. This enables running PS2 games natively on PC and other platforms without traditional emulation.
### Features
* Translates MIPS R5900 instructions to C++ code
* Supports PS2-specific 128-bit MMI instructions
* Handles VU0 in macro mode
* Supports relocations and overlays
* Configurable via TOML files
* Single-file or multi-file output options
* Function stubbing and skipping
### How It Works
PS2Recomp works by:
Parsing a PS2 ELF file to extract functions, symbols, and relocations
Decoding the MIPS R5900 instructions in each function
Translating those instructions to equivalent C++ code
Generating a runtime that can execute the recompiled code
The translated code is very literal, with each MIPS instruction mapping to a C++ operation. For example, `addiu $r4, $r4, 0x20 becomes ctx->r4 = ADD32(ctx->r4, 0X20);`.
### Requirements
* CMake 3.20 or higher
* C++20 compatible compiler (I only test with MSVC)
* SSE4/AVX support for 128-bit operations
#### Building
```bash
git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git
cd PS2Recomp
# Create build directory
mkdir build
cd build
cmake ..
cmake --build .
```
### Usage
1. Create a configuration file (see `./ps2xRecomp/example_config.toml`)
2. Run the recompiler:
```
./ps2recomp your_config.toml
```
Compile the generated C++ code
Link with a runtime implementation
### Configuration
PS2Recomp uses TOML configuration files to specify:
* Input ELF file
* Output directory
* Functions to stub or skip
* Instruction patches
#### Example configuration:
```toml
toml[general]
input = "path/to/game.elf"
output = "output/"
single_file_output = false
# Functions to stub
stubs = ["printf", "malloc", "free"]
# Functions to skip
skip = ["abort", "exit"]
# Patches
[patches]
instructions = [
{ address = "0x100004", value = "0x00000000" }
]
```
### Runtime
To execute the recompiled code, you'll need to implement or use a runtime that provides:
* Memory management
* System call handling
* PS2-specific hardware simulation
A basic runtime header is provided in `ps2xRuntime` folder.
### Limitations
* VU1 microcode support is limited
* Graphics Synthesizer and other hardware components need external implementation
* Some PS2-specific features may not be fully supported yet
### Acknowledgments
* Inspired by N64Recomp
* Uses ELFIO for ELF parsing
* Uses toml11 for TOML parsing
* Uses fmt for string formatting
+25
View File
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.20)
project(PS2Analyzer VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
file(GLOB_RECURSE PS2ANALYZER_SOURCES
"src/*.cpp"
)
add_executable(ps2_analyzer ${PS2ANALYZER_SOURCES})
target_include_directories(ps2_analyzer PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/ps2xRecomp/include
)
target_link_libraries(ps2_analyzer PRIVATE
fmt::fmt
ps2_recomp
)
install(TARGETS ps2_analyzer
RUNTIME DESTINATION bin
)
+84
View File
@@ -0,0 +1,84 @@
# PS2 ELF Analyzer Tool
The PS2 ELF Analyzer Tool helps automate the process of creating TOML configuration files for the PS2Recomp static recompiler. It analyzes PlayStation 2 ELF files and generates a recommended configuration based on the binary's characteristics.
## Key Features
* Analyzes PS2 ELF binaries to extract symbols, functions, and structure
* Identifies common library functions that should be stubbed
* Flags system functions that should be skipped during recompilation
* Detects potential instruction patterns that may need patching
* Generates a ready-to-use TOML configuration file for PS2Recomp
## Using the Analyzer
```bash
ps2_analyzer <input_elf> <output_toml>
```
### Where:
* `input_elf` is the path to the PS2 ELF file you want to analyze
* `output_toml` is the path where the generated TOML configuration will be saved
## Example:
```bash
ps2_analyzer path/to/your/ps2_game.elf config.toml
```
## How It Works
The analyzer performs the following steps:
* Parses the ELF file using the same ElfParser used by PS2Recomp
* Extracts functions, symbols, sections, and relocations
* Analyzes the entry point to understand initialization patterns
* Identifies library functions by name patterns and signatures
* Maps the call graph to understand relationships between functions
* Analyzes data usage patterns (basic implementation)
* Scans for problematic instructions that might need patching
* Generates a TOML configuration file with all findings
## Generated Configuration
The tool creates a TOML file with the following sections:
```toml
[general]
input = "path/to/your/ps2_game.elf"
output = "output/"
single_file_output = false
runtime_header = "include/ps2_runtime.h"
stubs = [
# List of identified library functions to stub
"printf",
"malloc",
# ...
]
skip = [
# List of system functions to skip
"entry",
"_start",
# ...
]
[patches]
instructions = [
# Potential instruction patches
{ address = "0x100008", value = "0x00000000" },
# ...
]
```
## Extending the Analyzer
The analyzer is designed to be extensible. You can enhance its capabilities by:
* Adding more library function patterns in initializeLibraryFunctions()
* Improving the call graph analysis in analyzeCallGraph()
* Enhancing data usage pattern detection in analyzeDataUsage()
* Refining patch detection logic in identifyPotentialPatches()
## Limitations
* The analyzer uses basic heuristics and may not catch all special cases
* Function identification relies heavily on symbol names
* Patch recommendations are preliminary and may need manual review
* Complex game-specific behaviors may not be detected
@@ -0,0 +1,59 @@
#ifndef PS2RECOMP_ELF_ANALYZER_H
#define PS2RECOMP_ELF_ANALYZER_H
#include "ps2recomp/types.h"
#include "ps2recomp/elf_parser.h"
#include "ps2recomp/r5900_decoder.h"
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <fstream>
#include <filesystem>
namespace ps2recomp
{
class ElfAnalyzer
{
public:
ElfAnalyzer(const std::string &elfPath);
~ElfAnalyzer();
bool analyze();
bool generateToml(const std::string &outputPath);
private:
std::string m_elfPath;
std::unique_ptr<ElfParser> m_elfParser;
std::unique_ptr<R5900Decoder> m_decoder;
std::vector<Function> m_functions;
std::vector<Symbol> m_symbols;
std::vector<Section> m_sections;
std::vector<Relocation> m_relocations;
std::unordered_set<std::string> m_libFunctions; // Library functions to stub
std::unordered_set<std::string> m_skipFunctions; // Functions to skip
std::unordered_map<uint32_t, uint32_t> m_patches; // Address -> instruction patches
// Common PS2 library function names
void initializeLibraryFunctions();
// Analysis methods
void analyzeEntryPoint();
void analyzeLibraryFunctions();
void analyzeCallGraph();
void analyzeDataUsage();
void identifyPotentialPatches();
std::string escapeBackslashes(const std::string &path);
// Helpers
bool isSystemFunction(const std::string &name) const;
bool isLibraryFunction(const std::string &name) const;
void decodeFunction(const Function &function);
std::string formatAddress(uint32_t address) const;
};
}
#endif // PS2RECOMP_ELF_ANALYZER_H
+58
View File
@@ -0,0 +1,58 @@
#include "ps2recomp/elf_analyzer.h"
#include <iostream>
#include <string>
void printUsage()
{
std::cout << "PS2 ELF Analyzer\n";
std::cout << "A tool to analyze PS2 ELF files and generate TOML configuration for PS2Recomp\n\n";
std::cout << "Usage: ps2_analyzer <input_elf> <output_toml>\n";
std::cout << " input_elf Path to the PS2 ELF file\n";
std::cout << " output_toml Path to output TOML configuration file\n";
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
printUsage();
return 1;
}
std::string elfPath = argv[1];
std::string tomlPath = argv[2];
std::cout << "PS2 ELF Analyzer\n";
std::cout << "----------------\n";
std::cout << "Input ELF: " << elfPath << "\n";
std::cout << "Output TOML: " << tomlPath << "\n\n";
try
{
ps2recomp::ElfAnalyzer analyzer(elfPath);
if (!analyzer.analyze())
{
std::cerr << "Failed to analyze ELF file\n";
return 1;
}
if (!analyzer.generateToml(tomlPath))
{
std::cerr << "Failed to generate TOML configuration\n";
return 1;
}
std::cout << "\nAnalysis complete\n";
std::cout << "TOML configuration has been written to: " << tomlPath << "\n";
std::cout << "\nYou can now use this configuration with PS2Recomp:\n";
std::cout << " ps2recomp " << tomlPath << "\n";
return 0;
}
catch (const std::exception &e)
{
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
}
+354
View File
@@ -0,0 +1,354 @@
#include "ps2recomp/elf_analyzer.h"
#include <iostream>
#include <sstream>
#include <algorithm>
#include <filesystem>
namespace fs = std::filesystem;
namespace ps2recomp
{
ElfAnalyzer::ElfAnalyzer(const std::string &elfPath)
: m_elfPath(elfPath)
{
m_elfParser = std::make_unique<ElfParser>(elfPath);
m_decoder = std::make_unique<R5900Decoder>();
initializeLibraryFunctions();
}
ElfAnalyzer::~ElfAnalyzer() = default;
bool ElfAnalyzer::analyze()
{
std::cout << "Analyzing ELF file: " << m_elfPath << std::endl;
if (!m_elfParser->parse())
{
std::cerr << "Failed to parse ELF file" << std::endl;
return false;
}
m_functions = m_elfParser->extractFunctions();
m_symbols = m_elfParser->extractSymbols();
m_sections = m_elfParser->getSections();
m_relocations = m_elfParser->getRelocations();
std::cout << "Extracted " << m_functions.size() << " functions" << std::endl;
std::cout << "Extracted " << m_symbols.size() << " symbols" << std::endl;
std::cout << "Extracted " << m_sections.size() << " sections" << std::endl;
std::cout << "Extracted " << m_relocations.size() << " relocations" << std::endl;
analyzeEntryPoint();
analyzeLibraryFunctions();
analyzeCallGraph();
analyzeDataUsage();
identifyPotentialPatches();
std::cout << "Analysis completed" << std::endl;
std::cout << "- " << m_libFunctions.size() << " library functions to stub" << std::endl;
std::cout << "- " << m_skipFunctions.size() << " functions to skip" << std::endl;
std::cout << "- " << m_patches.size() << " potential patches identified" << std::endl;
return true;
}
bool ElfAnalyzer::generateToml(const std::string &outputPath)
{
std::ofstream file(outputPath);
if (!file)
{
std::cerr << "Failed to open output file: " << outputPath << std::endl;
return false;
}
fs::path elfPathObj(m_elfPath);
std::string elfFileName = elfPathObj.filename().string();
fs::path outputPathObj(outputPath);
fs::path outputDir = outputPathObj.parent_path();
std::string outputDirStr = outputDir.string() + "\\output\\";
file << "# PS2Recomp configuration for: " << elfFileName << "\n";
file << "# Generated by ElfAnalyzer\n\n";
file << "[general]\n";
file << "# Path to input ELF file\n";
file << "input = \"" << escapeBackslashes(m_elfPath) << "\"\n\n";
file << "# Path to output directory\n";
file << "output = \"" << escapeBackslashes(outputDirStr) << "\"\n\n";
file << "# Single file output mode (false for one file per function)\n";
file << "single_file_output = false\n\n";
file << "# Functions to stub (these will generate empty implementations)\n";
file << "stubs = [\n";
for (const auto &func : m_libFunctions)
{
file << " \"" << func << "\",\n";
}
file << "]\n\n";
file << "# Functions to skip (these will not be recompiled)\n";
file << "skip = [\n";
for (const auto &func : m_skipFunctions)
{
file << " \"" << func << "\",\n";
}
file << "]\n\n";
if (!m_patches.empty())
{
file << "# Patches to apply during recompilation\n";
file << "[patches]\n";
file << "# Individual instruction patches\n";
file << "instructions = [\n";
for (const auto &[address, value] : m_patches)
{
file << " { address = \"0x" << std::hex << address << "\", value = \"0x"
<< std::hex << value << "\" }, # Identified potential patch\n";
}
file << "]\n\n";
}
// file << "# Function hook patches\n";
// file << "#[[patches.hook]]\n";
// file << "#function = \"printf\"\n";
// file << "#code = '''\n";
// file << "#// Custom printf implementation\n";
// file << "#void printf(uint8_t* rdram, R5900Context* ctx) {\n";
// file << "# // Implementation here\n";
// file << "#}\n";
// file << "#'''\n\n";
std::cout << "Generated TOML configuration: " << outputPath << std::endl;
return true;
}
void ElfAnalyzer::initializeLibraryFunctions()
{
// Standard C library functions
const std::vector<std::string> stdLibFuncs = {
"printf", "sprintf", "snprintf", "fprintf", "vprintf", "vfprintf",
"malloc", "free", "calloc", "realloc",
"memcpy", "memset", "memmove", "memcmp",
"strcpy", "strncpy", "strcat", "strncat",
"strcmp", "strncmp", "strlen", "strstr",
"fopen", "fclose", "fread", "fwrite", "fseek",
"atoi", "atof", "rand", "srand"};
// PS2-specific system functions
const std::vector<std::string> ps2SysFuncs = {
"FlushCache", "EI", "DI", "SYNC",
"syscall", "ResetEE", "SetGsCrt", "Exit",
"LoadExecPS2", "ExecPS2", "GetThreadId",
"RFU009", "InitRCnt", "GetOsTick", "ResetRCnt",
"DisableFPUExceptions", "EnableFPUExceptions"};
// PS2-specific library functions
const std::vector<std::string> ps2LibFuncs = {
// GS
"GsSetCrt", "GsGetIMR", "GsPutIMR", "GsSetIMR",
"GsInit", "GsSyncV", "GsGetVideoMode", "GsSetVideoMode",
// Pad
"PadInit", "PadPortOpen", "PadGetState", "PadRead",
"PadPortClose", "PadSetActAlign", "PadSetActDirect",
// SIF
"SifInitRpc", "SifExitRpc", "SifBindRpc", "SifCallRpc",
"SifRegisterRpc", "SifCheckStatRpc", "SifSetRpcQueue",
"SifRpcLoop", "SifGetOtherData",
// IPU
"sceSifAddCmdHandler", "sceSifRemoveCmdHandler", "sceSifSendCmd",
"sceSifInitCmd", "sceSifExitCmd", "sceSifSetCmdBuffer"};
// Add all to our library functions set
m_libFunctions.insert(stdLibFuncs.begin(), stdLibFuncs.end());
m_libFunctions.insert(ps2SysFuncs.begin(), ps2SysFuncs.end());
m_libFunctions.insert(ps2LibFuncs.begin(), ps2LibFuncs.end());
}
void ElfAnalyzer::analyzeEntryPoint()
{
auto it = std::find_if(m_functions.begin(), m_functions.end(),
[](const Function &f)
{ return f.name == "entry" || f.name == "_start"; });
if (it != m_functions.end())
{
std::cout << "Found entry point: " << it->name << " at 0x" << std::hex << it->start << std::dec << std::endl;
m_skipFunctions.insert(it->name);
decodeFunction(*it);
}
else
{
std::cout << "Entry point not found" << std::endl;
}
}
void ElfAnalyzer::analyzeLibraryFunctions()
{
for (const auto &symbol : m_symbols)
{
if (symbol.isFunction)
{
if (isLibraryFunction(symbol.name))
{
m_libFunctions.insert(symbol.name);
}
if (isSystemFunction(symbol.name))
{
m_skipFunctions.insert(symbol.name);
}
}
}
}
void ElfAnalyzer::analyzeCallGraph()
{
// functions called by the entry point are likely initialization and should be skipped
for (const auto &func : m_functions)
{
if (func.name.find("init") != std::string::npos ||
func.name.find("Init") != std::string::npos ||
func.name.find("start") != std::string::npos ||
func.name.find("Start") != std::string::npos)
{
m_skipFunctions.insert(func.name);
}
}
}
void ElfAnalyzer::analyzeDataUsage()
{
// TODO
}
void ElfAnalyzer::identifyPotentialPatches()
{
// This is a very basic implementation that looks for potentially problematic instructions
for (const auto &func : m_functions)
{
if (m_skipFunctions.find(func.name) != m_skipFunctions.end())
{
continue; // Skip functions that we're going to skip anyway
}
// Decode the function
decodeFunction(func);
}
// Example: If we find syscall instructions, suggest patching them to NOP
for (uint32_t addr = 0x100000; addr < 0x101000; addr += 4)
{
if (m_elfParser->isValidAddress(addr))
{
uint32_t instr = m_elfParser->readWord(addr);
if ((instr & 0xFC00003F) == 0x0000000C)
{ // syscall instruction
m_patches[addr] = 0x00000000; // NOP
}
}
}
}
std::string ElfAnalyzer::escapeBackslashes(const std::string &path)
{
std::string result;
for (char ch : path)
{
if (ch == '\\')
result.append("\\\\");
else
result.push_back(ch);
}
return result;
}
bool ElfAnalyzer::isSystemFunction(const std::string &name) const
{
static const std::unordered_set<std::string> systemFuncs = {
"entry", "_start", "_init", "_fini",
"abort", "exit", "_exit",
"_profiler_start", "_profiler_stop"};
return systemFuncs.find(name) != systemFuncs.end();
}
bool ElfAnalyzer::isLibraryFunction(const std::string &name) const
{
if (name.empty())
return false;
if (name[0] == '_' && name.size() > 1 && std::isalpha(name[1]))
{
return true; // Many library functions start with underscore
}
// Check for common prefixes by Claude
const std::vector<std::string> libraryPrefixes = {
"sce", "Sce", "SCE", // Sony prefixes
"sif", "Sif", "SIF", // SIF functions
"pad", "Pad", "PAD", // Pad functions
"gs", "Gs", "GS", // Graphics Synthesizer
"dma", "Dma", "DMA", // DMA functions
"iop", "Iop", "IOP" // IOP functions
};
for (const auto &prefix : libraryPrefixes)
{
if (name.rfind(prefix, 0) == 0)
{
return true;
}
}
return false;
}
void ElfAnalyzer::decodeFunction(const Function &function)
{
for (uint32_t addr = function.start; addr < function.end; addr += 4)
{
if (!m_elfParser->isValidAddress(addr))
{
continue;
}
uint32_t rawInstruction = m_elfParser->readWord(addr);
try
{
Instruction inst = m_decoder->decodeInstruction(addr, rawInstruction);
// TODO Analyze instructions for potential issues
// Example: Look for syscalls that might need to be patched
if (inst.opcode == 0 && inst.function == 0xC)
{ // syscall
std::cout << "Found syscall at " << formatAddress(addr) << std::endl;
}
}
catch (const std::exception &e)
{
std::cerr << "Error decoding instruction at " << formatAddress(addr)
<< ": " << e.what() << std::endl;
}
}
}
std::string ElfAnalyzer::formatAddress(uint32_t address) const
{
std::stringstream ss;
ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << address;
return ss.str();
}
}
+73
View File
@@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.20)
project(PS2Recomp VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
elfio
GIT_REPOSITORY https://github.com/serge1/ELFIO.git
GIT_TAG main
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(elfio)
FetchContent_Declare(
toml11
GIT_REPOSITORY https://github.com/ToruNiina/toml11.git
GIT_TAG master
)
FetchContent_MakeAvailable(toml11)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG master
)
FetchContent_MakeAvailable(fmt)
file(GLOB_RECURSE PS2RECOMP_SOURCES
"src/*.cpp"
)
file(GLOB_RECURSE PS2RECOMP_HEADERS
"include/*.h"
"include/*.hpp"
)
add_executable(ps2recomp ${PS2RECOMP_SOURCES})
add_library(ps2_recomp STATIC ${PS2RECOMP_SOURCES})
target_include_directories(ps2recomp PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${elfio_SOURCE_DIR}
)
target_include_directories(ps2_recomp PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${elfio_SOURCE_DIR}
)
target_link_libraries(ps2recomp PRIVATE
fmt::fmt
toml11::toml11
)
target_link_libraries(ps2_recomp PUBLIC
fmt::fmt
toml11::toml11
)
install(TARGETS ps2recomp ps2_recomp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(DIRECTORY include/
DESTINATION include
)
+46
View File
@@ -0,0 +1,46 @@
[general]
# Path to input ELF file
input = "path/to/your/ps2_game.elf"
# Path to output directory
output = "output/"
# Single file output mode (false for one file per function)
single_file_output = false
# Path to runtime header (optional)
runtime_header = "include/ps2_runtime.h"
# Functions to stub (these will generate empty implementations)
stubs = ["printf", "malloc", "free", "memcpy", "memset", "strncpy", "sprintf"]
# Functions to skip (these will not be recompiled)
skip = ["abort", "exit", "_exit"]
# Patches to apply during recompilation
[patches]
# Individual instruction patches
instructions = [
{ address = "0x100004", value = "0x00000000" }, # NOP an instruction
{ address = "0x100104", value = "0x24040000" }, # Change an immediate value
]
# Function hook patches (not yet implemented)
[[patches.hook]]
function = "printf"
code = '''
// Custom printf implementation
void printf(uint8_t* rdram, R5900Context* ctx) {
// Implementation here
}
'''
# Function replacement patches (not yet implemented)
[[patches.func]]
address = "0x100000"
code = '''
// Custom implementation for function at 0x100000
void func_00100000(uint8_t* rdram, R5900Context* ctx) {
// Implementation here
}
'''
@@ -0,0 +1,38 @@
#ifndef PS2RECOMP_CODE_GENERATOR_H
#define PS2RECOMP_CODE_GENERATOR_H
#include "ps2recomp/types.h"
#include <string>
#include <vector>
namespace ps2recomp
{
class CodeGenerator
{
public:
CodeGenerator(const std::vector<Symbol> &symbols);
~CodeGenerator();
std::string generateFunction(const Function &function, const std::vector<Instruction> &instructions);
std::string generateMacroHeader();
std::string handleBranchDelaySlots(const Instruction &branchInst, const Instruction &delaySlot);
private:
std::vector<Symbol> m_symbols;
std::string translateInstruction(const Instruction &inst);
std::string translateMMIInstruction(const Instruction &inst);
std::string translateVUInstruction(const Instruction &inst);
std::string translateFPUInstruction(const Instruction& inst);
std::string translateCOP0Instruction(const Instruction& inst);
std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress,
const std::vector<JumpTableEntry> &entries);
Symbol *findSymbolByAddress(uint32_t address);
};
}
#endif // PS2RECOMP_CODE_GENERATOR_H
@@ -0,0 +1,25 @@
#ifndef PS2RECOMP_CONFIG_MANAGER_H
#define PS2RECOMP_CONFIG_MANAGER_H
#include "ps2recomp/types.h"
#include <string>
namespace ps2recomp
{
class ConfigManager
{
public:
ConfigManager(const std::string &configPath);
~ConfigManager();
RecompilerConfig loadConfig();
void saveConfig(const RecompilerConfig &config);
private:
std::string m_configPath;
};
}
#endif // PS2RECOMP_CONFIG_MANAGER_H
+50
View File
@@ -0,0 +1,50 @@
#ifndef PS2RECOMP_ELF_PARSER_H
#define PS2RECOMP_ELF_PARSER_H
#include "ps2recomp/types.h"
#include <elfio/elfio.hpp>
#include <string>
#include <vector>
#include <memory>
namespace ps2recomp
{
class ElfParser
{
public:
ElfParser(const std::string &filePath);
~ElfParser();
bool parse();
std::vector<Function> extractFunctions();
std::vector<Symbol> extractSymbols();
std::vector<Section> getSections();
std::vector<Relocation> getRelocations();
// Helper methods
bool isValidAddress(uint32_t address) const;
uint32_t readWord(uint32_t address) const;
uint8_t *getSectionData(const std::string &sectionName);
uint32_t getSectionAddress(const std::string &sectionName);
uint32_t getSectionSize(const std::string &sectionName);
private:
std::string m_filePath;
std::unique_ptr<ELFIO::elfio> m_elf;
std::vector<Section> m_sections;
std::vector<Symbol> m_symbols;
std::vector<Relocation> m_relocations;
void loadSections();
void loadSymbols();
void loadRelocations();
bool isExecutableSection(const ELFIO::section *section) const;
bool isDataSection(const ELFIO::section *section) const;
};
} // namespace ps2recomp
#endif // PS2RECOMP_ELF_PARSER_H
+376
View File
@@ -0,0 +1,376 @@
#ifndef PS2RECOMP_INSTRUCTIONS_H
#define PS2RECOMP_INSTRUCTIONS_H
#include <cstdint>
namespace ps2recomp
{
// Basic MIPS opcodes (shared with R4300i)
enum MipsOpcodes
{
OPCODE_SPECIAL = 0x00,
OPCODE_REGIMM = 0x01,
OPCODE_J = 0x02,
OPCODE_JAL = 0x03,
OPCODE_BEQ = 0x04,
OPCODE_BNE = 0x05,
OPCODE_BLEZ = 0x06,
OPCODE_BGTZ = 0x07,
OPCODE_ADDI = 0x08,
OPCODE_ADDIU = 0x09,
OPCODE_SLTI = 0x0A,
OPCODE_SLTIU = 0x0B,
OPCODE_ANDI = 0x0C,
OPCODE_ORI = 0x0D,
OPCODE_XORI = 0x0E,
OPCODE_LUI = 0x0F,
OPCODE_COP0 = 0x10,
OPCODE_COP1 = 0x11,
OPCODE_COP2 = 0x12, // VU0 macro instructions
OPCODE_COP3 = 0x13, // Unused on PS2
OPCODE_BEQL = 0x14,
OPCODE_BNEL = 0x15,
OPCODE_BLEZL = 0x16,
OPCODE_BGTZL = 0x17,
OPCODE_DADDI = 0x18,
OPCODE_DADDIU = 0x19,
OPCODE_LDL = 0x1A,
OPCODE_LDR = 0x1B,
OPCODE_MMI = 0x1C, // PS2 specific multimedia instructions
OPCODE_LQ = 0x1E, // PS2 specific 128-bit load
OPCODE_SQ = 0x1F, // PS2 specific 128-bit store
OPCODE_LB = 0x20,
OPCODE_LH = 0x21,
OPCODE_LWL = 0x22,
OPCODE_LW = 0x23,
OPCODE_LBU = 0x24,
OPCODE_LHU = 0x25,
OPCODE_LWR = 0x26,
OPCODE_LWU = 0x27,
OPCODE_SB = 0x28,
OPCODE_SH = 0x29,
OPCODE_SWL = 0x2A,
OPCODE_SW = 0x2B,
OPCODE_SDL = 0x2C,
OPCODE_SDR = 0x2D,
OPCODE_SWR = 0x2E,
OPCODE_CACHE = 0x2F,
OPCODE_LL = 0x30,
OPCODE_LWC1 = 0x31,
OPCODE_LWC2 = 0x32,
OPCODE_PREF = 0x33,
OPCODE_LLD = 0x34,
OPCODE_LDC1 = 0x35,
OPCODE_LDC2 = 0x36,
OPCODE_LD = 0x37,
OPCODE_SC = 0x38,
OPCODE_SWC1 = 0x39,
OPCODE_SWC2 = 0x3A,
OPCODE_SCD = 0x3C,
OPCODE_SDC1 = 0x3D,
OPCODE_SDC2 = 0x3E,
OPCODE_SD = 0x3F
};
// SPECIAL function codes
enum SpecialFunctions
{
SPECIAL_SLL = 0x00,
SPECIAL_SRL = 0x02,
SPECIAL_SRA = 0x03,
SPECIAL_SLLV = 0x04,
SPECIAL_SRLV = 0x06,
SPECIAL_SRAV = 0x07,
SPECIAL_JR = 0x08,
SPECIAL_JALR = 0x09,
SPECIAL_MOVZ = 0x0A,
SPECIAL_MOVN = 0x0B,
SPECIAL_SYSCALL = 0x0C,
SPECIAL_BREAK = 0x0D,
SPECIAL_SYNC = 0x0F,
SPECIAL_MFHI = 0x10,
SPECIAL_MTHI = 0x11,
SPECIAL_MFLO = 0x12,
SPECIAL_MTLO = 0x13,
SPECIAL_DSLLV = 0x14,
SPECIAL_DSRLV = 0x16,
SPECIAL_DSRAV = 0x17,
SPECIAL_MULT = 0x18,
SPECIAL_MULTU = 0x19,
SPECIAL_DIV = 0x1A,
SPECIAL_DIVU = 0x1B,
SPECIAL_ADD = 0x20,
SPECIAL_ADDU = 0x21,
SPECIAL_SUB = 0x22,
SPECIAL_SUBU = 0x23,
SPECIAL_AND = 0x24,
SPECIAL_OR = 0x25,
SPECIAL_XOR = 0x26,
SPECIAL_NOR = 0x27,
SPECIAL_MFSA = 0x28,
SPECIAL_MTSA = 0x29,
SPECIAL_SLT = 0x2A,
SPECIAL_SLTU = 0x2B,
SPECIAL_DADD = 0x2C,
SPECIAL_DADDU = 0x2D,
SPECIAL_DSUB = 0x2E,
SPECIAL_DSUBU = 0x2F,
SPECIAL_TGE = 0x30,
SPECIAL_TGEU = 0x31,
SPECIAL_TLT = 0x32,
SPECIAL_TLTU = 0x33,
SPECIAL_TEQ = 0x34,
SPECIAL_TNE = 0x36,
SPECIAL_DSLL = 0x38,
SPECIAL_DSRL = 0x3A,
SPECIAL_DSRA = 0x3B,
SPECIAL_DSLL32 = 0x3C,
SPECIAL_DSRL32 = 0x3E,
SPECIAL_DSRA32 = 0x3F
};
// REGIMM function codes
enum RegimmFunctions
{
REGIMM_BLTZ = 0x00,
REGIMM_BGEZ = 0x01,
REGIMM_BLTZL = 0x02,
REGIMM_BGEZL = 0x03,
REGIMM_TGEI = 0x08,
REGIMM_TGEIU = 0x09,
REGIMM_TLTI = 0x0A,
REGIMM_TLTIU = 0x0B,
REGIMM_TEQI = 0x0C,
REGIMM_TNEI = 0x0E,
REGIMM_BLTZAL = 0x10,
REGIMM_BGEZAL = 0x11,
REGIMM_BLTZALL = 0x12,
REGIMM_BGEZALL = 0x13,
REGIMM_MTSAB = 0x18,
REGIMM_MTSAH = 0x19
};
// PS2-specific MMI function codes
enum MMIFunctions
{
MMI_MADD = 0x00,
MMI_MADDU = 0x01,
MMI_PLZCW = 0x04,
MMI_MMI0 = 0x08,
MMI_MMI2 = 0x09,
MMI_MFHI1 = 0x10,
MMI_MTHI1 = 0x11,
MMI_MFLO1 = 0x12,
MMI_MTLO1 = 0x13,
MMI_MULT1 = 0x18,
MMI_MULTU1 = 0x19,
MMI_DIV1 = 0x1A,
MMI_DIVU1 = 0x1B,
MMI_MADD1 = 0x20,
MMI_MADDU1 = 0x21,
MMI_MMI1 = 0x28,
MMI_MMI3 = 0x29,
MMI_PMFHL = 0x30,
MMI_PMTHL = 0x31,
MMI_PSLLH = 0x34,
MMI_PSRLH = 0x36,
MMI_PSRAH = 0x37,
MMI_PSLLW = 0x3C,
MMI_PSRLW = 0x3E,
MMI_PSRAW = 0x3F,
MMI_MSUB = 0x02,
MMI_MSUBU = 0x03
};
// PS2-specific MMI0 function codes
enum MMI0Functions
{
MMI0_PADDW = 0x00,
MMI0_PSUBW = 0x01,
MMI0_PCGTW = 0x02,
MMI0_PMAXW = 0x03,
MMI0_PADDH = 0x04,
MMI0_PSUBH = 0x05,
MMI0_PCGTH = 0x06,
MMI0_PMAXH = 0x07,
MMI0_PADDB = 0x08,
MMI0_PSUBB = 0x09,
MMI0_PCGTB = 0x0A,
MMI0_PADDSW = 0x10,
MMI0_PSUBSW = 0x11,
MMI0_PEXTLW = 0x12,
MMI0_PPACW = 0x13,
MMI0_PADDSH = 0x14,
MMI0_PSUBSH = 0x15,
MMI0_PEXTLH = 0x16,
MMI0_PPACH = 0x17,
MMI0_PADDSB = 0x18,
MMI0_PSUBSB = 0x19,
MMI0_PEXTLB = 0x1A,
MMI0_PPACB = 0x1B,
MMI0_PEXT5 = 0x1E,
MMI0_PPAC5 = 0x1F
};
// PS2-specific MMI1 function codes
enum MMI1Functions
{
MMI1_PABSW = 0x01,
MMI1_PCEQW = 0x02,
MMI1_PMINW = 0x03,
MMI1_PADSBH = 0x04,
MMI1_PABSH = 0x05,
MMI1_PCEQH = 0x06,
MMI1_PMINH = 0x07,
MMI1_PCEQB = 0x0A,
MMI1_PADDUW = 0x10,
MMI1_PSUBUW = 0x11,
MMI1_PEXTUW = 0x12,
MMI1_PADDUH = 0x14,
MMI1_PSUBUH = 0x15,
MMI1_PEXTUH = 0x16,
MMI1_PADDUB = 0x18,
MMI1_PSUBUB = 0x19,
MMI1_PEXTUB = 0x1A,
MMI1_QFSRV = 0x1B
};
// PS2-specific MMI2 function codes
enum MMI2Functions
{
MMI2_PMADDW = 0x00,
MMI2_PSLLVW = 0x02,
MMI2_PSRLVW = 0x03,
MMI2_PMSUBW = 0x04,
MMI2_PMFHI = 0x08,
MMI2_PMFLO = 0x09,
MMI2_PINTH = 0x0A,
MMI2_PMULTW = 0x0C,
MMI2_PDIVW = 0x0D,
MMI2_PCPYLD = 0x0E,
MMI2_PAND = 0x12,
MMI2_PXOR = 0x13,
MMI2_PMADDH = 0x14,
MMI2_PHMADH = 0x15,
MMI2_PAND_ = 0x16,
MMI2_PXOR_ = 0x17,
MMI2_PMSUBH = 0x18,
MMI2_PHMSBH = 0x19,
MMI2_PEXEH = 0x1A,
MMI2_PREVH = 0x1B,
MMI2_PMULTH = 0x1C,
MMI2_PDIVBW = 0x1D,
MMI2_PEXEW = 0x1E,
MMI2_PROT3W = 0x1F
};
// PS2-specific MMI3 function codes
enum MMI3Functions
{
MMI3_PMADDUW = 0x00,
MMI3_PSRAVW = 0x03,
MMI3_PINTEH = 0x0A,
MMI3_PMULTUW = 0x0C,
MMI3_PDIVUW = 0x0D,
MMI3_PCPYUD = 0x0E,
MMI3_POR = 0x12,
MMI3_PNOR = 0x13,
MMI3_PEXCH = 0x1A,
MMI3_PCPYH = 0x1B,
MMI3_PEXCW = 0x1E,
};
// COP0 (System Control) function codes
enum Cop0Functions
{
COP0_MF = 0x00,
COP0_MT = 0x04,
COP0_CO = 0x10 // COProcessor commands
};
// COP0 CO (COProcessor) function codes
enum Cop0CoFunctions
{
COP0_CO_TLBR = 0x01,
COP0_CO_TLBWI = 0x02,
COP0_CO_TLBWR = 0x06,
COP0_CO_TLBP = 0x08,
COP0_CO_ERET = 0x18,
COP0_CO_EI = 0x38,
COP0_CO_DI = 0x39
};
// COP1 (FPU) function codes
enum Cop1Functions
{
COP1_MF = 0x00,
COP1_CF = 0x02,
COP1_MT = 0x04,
COP1_CT = 0x06,
COP1_BC = 0x08,
COP1_S = 0x10,
COP1_W = 0x14,
COP1_BC_BCF = 0x00,
COP1_BC_BCT = 0x01
};
// COP2 (VU0 macro) function codes
enum Cop2Functions
{
COP2_QMFC2 = 0x00, // Move From Coprocessor 2 (128-bit)
COP2_CFC2 = 0x02, // Move Control From Coprocessor 2
COP2_QMTC2 = 0x04, // Move To Coprocessor 2 (128-bit)
COP2_CTC2 = 0x06, // Move Control To Coprocessor 2
COP2_BC2 = 0x08, // Branch On Coprocessor 2 Condition
COP2_CO = 0x10, // COProcessor instructions (VU0 macro)
COP2_BCF = 0x00,
COP2_BCT = 0x01,
COP2_MFC2 = 0x02,
COP2_MTC2 = 0x0A
};
// VU0 macro instruction function codes (subset - there are many more)
enum VU0MacroFunctions
{
VU0_VADD = 0x00,
VU0_VSUB = 0x01,
VU0_VMUL = 0x02,
VU0_VDIV = 0x03,
VU0_VSQRT = 0x04,
VU0_VRSQRT = 0x05,
VU0_VMULQ = 0x06,
VU0_VIADD = 0x10,
VU0_VISUB = 0x11,
VU0_VIADDI = 0x12,
VU0_VIAND = 0x13,
VU0_VIOR = 0x14,
VU0_VILWR = 0x15,
VU0_VISWR = 0x16,
VU0_VCALLMS = 0x20,
VU0_VCALLMSR = 0x21
};
// PMFHL functions (sa field)
enum PMFHLFunctions
{
PMFHL_LW = 0x00,
PMFHL_UW = 0x01,
PMFHL_SLW = 0x02,
PMFHL_LH = 0x03,
PMFHL_SH = 0x04
};
// Instruction decoding helper macros
#define OPCODE(inst) ((inst >> 26) & 0x3F)
#define RS(inst) ((inst >> 21) & 0x1F)
#define RT(inst) ((inst >> 16) & 0x1F)
#define RD(inst) ((inst >> 11) & 0x1F)
#define SA(inst) ((inst >> 6) & 0x1F)
#define FUNCTION(inst) ((inst) & 0x3F)
#define IMMEDIATE(inst) ((inst) & 0xFFFF)
#define SIMMEDIATE(inst) ((int16_t)((inst) & 0xFFFF))
#define TARGET(inst) ((inst) & 0x3FFFFFF)
} // namespace ps2recomp
#endif // PS2RECOMP_INSTRUCTIONS_H
@@ -0,0 +1,55 @@
#ifndef PS2RECOMP_PS2_RECOMPILER_H
#define PS2RECOMP_PS2_RECOMPILER_H
#include "ps2recomp/types.h"
#include "ps2recomp/elf_parser.h"
#include "ps2recomp/r5900_decoder.h"
#include "ps2recomp/code_generator.h"
#include "ps2recomp/config_manager.h"
#include <string>
#include <vector>
#include <unordered_map>
#include <filesystem>
namespace ps2recomp
{
class PS2Recompiler
{
public:
PS2Recompiler(const std::string &configPath);
~PS2Recompiler() = default;
bool initialize();
bool recompile();
void generateOutput();
private:
ConfigManager m_configManager;
std::unique_ptr<ElfParser> m_elfParser;
std::unique_ptr<R5900Decoder> m_decoder;
std::unique_ptr<CodeGenerator> m_codeGenerator;
RecompilerConfig m_config;
std::vector<Function> m_functions;
std::vector<Symbol> m_symbols;
std::vector<Section> m_sections;
std::vector<Relocation> m_relocations;
std::unordered_map<uint32_t, std::vector<Instruction>> m_decodedFunctions;
std::unordered_map<std::string, bool> m_stubFunctions;
std::unordered_map<std::string, bool> m_skipFunctions;
std::map<uint32_t, std::string> m_generatedStubs;
bool decodeFunction(Function &function);
bool shouldStubFunction(const std::string &name) const;
bool shouldSkipFunction(const std::string &name) const;
std::string generateRuntimeHeader();
std::string generateStubFunction(const Function& function);
bool writeToFile(const std::string &path, const std::string &content);
std::filesystem::path getOutputPath(const Function &function) const;
};
}
#endif
@@ -0,0 +1,52 @@
#ifndef PS2RECOMP_R5900_DECODER_H
#define PS2RECOMP_R5900_DECODER_H
#include "ps2recomp/types.h"
#include "ps2recomp/instructions.h"
#include <cstdint>
namespace ps2recomp
{
class R5900Decoder
{
public:
R5900Decoder();
~R5900Decoder();
Instruction decodeInstruction(uint32_t address, uint32_t rawInstruction);
bool isBranchInstruction(const Instruction &inst) const;
bool isJumpInstruction(const Instruction &inst) const;
bool isCallInstruction(const Instruction &inst) const;
bool isReturnInstruction(const Instruction &inst) const;
bool isMMIInstruction(const Instruction &inst) const;
bool isVUInstruction(const Instruction &inst) const;
bool isStore(const Instruction &inst) const;
bool isLoad(const Instruction &inst) const;
bool hasDelaySlot(const Instruction &inst) const;
uint32_t getBranchTarget(const Instruction &inst) const;
uint32_t getJumpTarget(const Instruction &inst) const;
private:
void decodeRType(Instruction &inst) const;
void decodeIType(Instruction &inst) const;
void decodeJType(Instruction &inst) const;
void decodeSpecial(Instruction &inst) const;
void decodeRegimm(Instruction &inst) const;
void decodeMMI(Instruction &inst) const;
void decodeCOP0(Instruction& inst) const;
void decodeCOP1(Instruction &inst) const;
void decodeCOP2(Instruction &inst) const;
void decodeMMI0(Instruction &inst) const;
void decodeMMI1(Instruction &inst) const;
void decodeMMI2(Instruction &inst) const;
void decodeMMI3(Instruction &inst) const;
void decodePMFHL(Instruction &inst) const;
};
} // namespace ps2recomp
#endif // PS2RECOMP_R5900_DECODER_H
+139
View File
@@ -0,0 +1,139 @@
#ifndef PS2RECOMP_TYPES_H
#define PS2RECOMP_TYPES_H
#include <string>
#include <vector>
#include <cstdint>
#include <unordered_map>
#include <map>
namespace ps2recomp
{
// Instruction representation
struct Instruction
{
uint32_t address;
uint32_t opcode;
uint32_t rs; // Source register
uint32_t rt; // Target register
uint32_t rd; // Destination register
uint32_t sa; // Shift amount
uint32_t function; // Function code for R-type instructions
uint32_t immediate; // Immediate value for I-type instructions
uint32_t target; // Jump target for J-type instructions
uint32_t raw; // Raw instruction value
bool isMMI; // Is MMI instruction (PS2 specific)
bool isVU; // Is VU instruction (PS2 specific)
bool isBranch; // Is branch instruction
bool isJump; // Is jump instruction
bool isCall; // Is function call
bool isReturn; // Is return instruction
bool hasDelaySlot; // Has delay slot
bool isMultimedia; // PS2-specific multimedia operations
bool isStore;
bool isLoad;
uint8_t pmfhlVariation;
};
// Function information
struct Function
{
std::string name;
uint32_t start;
uint32_t end;
std::vector<Instruction> instructions;
std::vector<uint32_t> callers;
std::vector<uint32_t> callees;
bool isRecompiled;
bool isStub;
};
// Symbol information
struct Symbol
{
std::string name;
uint32_t address;
uint32_t size;
bool isFunction;
bool isImported;
bool isExported;
};
// Section information
struct Section
{
std::string name;
uint32_t address;
uint32_t size;
uint32_t offset;
bool isCode;
bool isData;
bool isBSS;
bool isReadOnly;
uint8_t *data;
};
// Relocation information
struct Relocation
{
uint32_t offset;
uint32_t info;
uint32_t symbol;
uint32_t type;
int32_t addend;
};
// Jump table entry
struct JumpTableEntry
{
uint32_t index;
uint32_t target;
};
// Jump table
struct JumpTable
{
uint32_t address;
uint32_t baseRegister;
std::vector<JumpTableEntry> entries;
};
// Control flow graph
struct CFGNode
{
uint32_t startAddress;
uint32_t endAddress;
std::vector<Instruction> instructions;
std::vector<uint32_t> predecessors;
std::vector<uint32_t> successors;
bool isJumpTarget;
bool hasJumpTable;
JumpTable jumpTable;
};
using CFG = std::unordered_map<uint32_t, CFGNode>;
// Function call
struct FunctionCall
{
uint32_t callerAddress;
uint32_t calleeAddress;
std::string calleeName;
};
// Recompiler configuration
struct RecompilerConfig
{
std::string inputPath;
std::string outputPath;
bool singleFileOutput;
std::vector<std::string> stubFunctions;
std::vector<std::string> skipFunctions;
std::unordered_map<uint32_t, std::string> patches;
std::map<std::string, std::string> stubImplementations;
};
} // namespace ps2recomp
#endif // PS2RECOMP_TYPES_H
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
#include "ps2recomp/config_manager.h"
#include <toml.hpp>
#include <fstream>
#include <iostream>
#include <stdexcept>
namespace ps2recomp
{
ConfigManager::ConfigManager(const std::string &configPath)
: m_configPath(configPath)
{
}
ConfigManager::~ConfigManager() = default;
RecompilerConfig ConfigManager::loadConfig()
{
RecompilerConfig config;
try
{
auto data = toml::parse(m_configPath);
config.inputPath = toml::find<std::string>(data, "general", "input");
config.outputPath = toml::find<std::string>(data, "general", "output");
config.singleFileOutput = toml::find<bool>(data, "general", "single_file_output");
config.stubFunctions = toml::find<std::vector<std::string>>(data, "general", "stubs");
config.skipFunctions = toml::find<std::vector<std::string>>(data, "general", "skip");
if (data.contains("patches") && data.at("patches").is_table())
{
const auto &patches = toml::find(data, "patches");
if (patches.contains("instructions") && patches.at("instructions").is_array())
{
const auto &instPatches = toml::find(patches, "instructions").as_array();
for (const auto &patch : instPatches)
{
if (patch.contains("address") && patch.contains("value"))
{
uint32_t address = std::stoul(toml::find<std::string>(patch, "address"), nullptr, 0);
std::string value = toml::find<std::string>(patch, "value");
config.patches[address] = value;
}
}
}
}
if (data.contains("stub_implementations") && data.at("stub_implementations").is_table())
{
const auto &stubImpls = toml::find(data, "stub_implementations");
for (const auto &item : stubImpls.as_table())
{
const std::string &funcName = item.first;
const std::string &implementation = toml::find<std::string>(stubImpls, funcName);
config.stubImplementations[funcName] = implementation;
}
}
}
catch (const std::exception &e)
{
std::cerr << "Error parsing configuration file: " << e.what() << std::endl;
throw;
}
return config;
}
void ConfigManager::saveConfig(const RecompilerConfig &config)
{
toml::value data;
toml::table general;
general["input"] = config.inputPath;
general["output"] = config.outputPath;
general["single_file_output"] = config.singleFileOutput;
data["general"] = general;
toml::array stubs;
for (const auto &stub : config.stubFunctions)
{
stubs.push_back(stub);
}
data["stubs"] = stubs;
toml::array skips;
for (const auto &skip : config.skipFunctions)
{
skips.push_back(skip);
}
data["skip"] = skips;
toml::table patches;
toml::array instPatches;
for (const auto &patch : config.patches)
{
toml::table p;
p["address"] = "0x" + std::to_string(patch.first);
p["value"] = patch.second;
instPatches.push_back(p);
}
patches["instructions"] = instPatches;
data["patches"] = patches;
if (!config.stubImplementations.empty())
{
toml::table stubImpls;
for (const auto &impl : config.stubImplementations)
{
stubImpls[impl.first] = impl.second;
}
data["stub_implementations"] = stubImpls;
}
std::ofstream file(m_configPath);
if (!file)
{
throw std::runtime_error("Failed to open file for writing: " + m_configPath);
}
file << data;
}
} // namespace ps2recomp
+291
View File
@@ -0,0 +1,291 @@
#include "ps2recomp/elf_parser.h"
#include <iostream>
#include <stdexcept>
namespace ps2recomp
{
ElfParser::ElfParser(const std::string &filePath)
: m_filePath(filePath), m_elf(new ELFIO::elfio())
{
}
bool ElfParser::isExecutableSection(const ELFIO::section *section) const
{
return (section->get_flags() & ELFIO::SHF_EXECINSTR) != 0;
}
bool ElfParser::isDataSection(const ELFIO::section *section) const
{
return (section->get_flags() & ELFIO::SHF_ALLOC) != 0 &&
!(section->get_flags() & ELFIO::SHF_EXECINSTR);
}
std::vector<Function> ElfParser::extractFunctions()
{
std::vector<Function> functions;
for (const auto &symbol : m_symbols)
{
if (symbol.isFunction && symbol.size > 0)
{
Function func;
func.name = symbol.name;
func.start = symbol.address;
func.end = symbol.address + symbol.size;
func.isRecompiled = false;
func.isStub = false;
functions.push_back(func);
}
}
std::sort(functions.begin(), functions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
return functions;
}
std::vector<Symbol> ElfParser::extractSymbols()
{
return m_symbols;
}
std::vector<Section> ElfParser::getSections()
{
return m_sections;
}
std::vector<Relocation> ElfParser::getRelocations()
{
return m_relocations;
}
bool ElfParser::isValidAddress(uint32_t address) const
{
for (const auto &section : m_sections)
{
if (address >= section.address && address < (section.address + section.size))
{
return true;
}
}
return false;
}
uint32_t ElfParser::readWord(uint32_t address) const
{
for (const auto &section : m_sections)
{
if (address >= section.address && address < (section.address + section.size))
{
if (section.data)
{
uint32_t offset = address - section.address;
return *reinterpret_cast<uint32_t *>(section.data + offset);
}
}
}
throw std::runtime_error("Invalid address for readWord: " + std::to_string(address));
}
uint8_t *ElfParser::getSectionData(const std::string &sectionName)
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
{
return section.data;
}
}
return nullptr;
}
uint32_t ElfParser::getSectionAddress(const std::string &sectionName)
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
{
return section.address;
}
}
return 0;
}
uint32_t ElfParser::getSectionSize(const std::string &sectionName)
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
{
return section.size;
}
}
return 0;
}
ElfParser::~ElfParser() = default;
bool ElfParser::parse()
{
if (!m_elf->load(m_filePath))
{
std::cerr << "Error: Could not load ELF file: " << m_filePath << std::endl;
return false;
}
// Check if this is a PS2 ELF (MIPS R5900)
if (m_elf->get_machine() != ELFIO::EM_MIPS)
{
std::cerr << "Error: Not a MIPS ELF file" << std::endl;
return false;
}
loadSections();
loadSymbols();
loadRelocations();
return true;
}
void ElfParser::loadSections()
{
m_sections.clear();
ELFIO::Elf_Half sec_num = m_elf->sections.size();
for (ELFIO::Elf_Half i = 0; i < sec_num; ++i)
{
ELFIO::section *psec = m_elf->sections[i];
Section section;
section.name = psec->get_name();
section.address = psec->get_address();
section.size = psec->get_size();
section.offset = psec->get_offset();
section.isCode = isExecutableSection(psec);
section.isData = isDataSection(psec);
section.isBSS = (psec->get_type() == ELFIO::SHT_NOBITS);
section.isReadOnly = !(psec->get_flags() & ELFIO::SHF_WRITE);
if (psec->get_size() > 0 && psec->get_type() != ELFIO::SHT_NOBITS)
{
section.data = (uint8_t *)psec->get_data();
}
else
{
section.data = nullptr;
}
m_sections.push_back(section);
}
}
void ElfParser::loadSymbols()
{
m_symbols.clear();
for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i)
{
ELFIO::section *psec = m_elf->sections[i];
if (psec->get_type() == ELFIO::SHT_SYMTAB || psec->get_type() == ELFIO::SHT_DYNSYM)
{
ELFIO::symbol_section_accessor symbols(*m_elf, psec);
ELFIO::Elf_Xword sym_num = symbols.get_symbols_num();
ELFIO::section *pstrSec = m_elf->sections[psec->get_link()];
ELFIO::string_section_accessor strings(pstrSec);
for (ELFIO::Elf_Xword j = 0; j < sym_num; ++j)
{
std::string name;
ELFIO::Elf64_Addr value;
ELFIO::Elf_Xword size;
unsigned char bind;
unsigned char type;
ELFIO::Elf_Half section_index;
unsigned char other;
symbols.get_symbol(j, name, value, size, bind, type, section_index, other);
// Skip empty symbols or those with invalid section index
if (name.empty() || section_index == ELFIO::SHN_UNDEF)
{
continue;
}
Symbol symbol;
symbol.name = name;
symbol.address = static_cast<uint32_t>(value);
symbol.size = static_cast<uint32_t>(size);
symbol.isFunction = (type == ELFIO::STT_FUNC);
symbol.isImported = (bind == ELFIO::STB_GLOBAL && section_index == ELFIO::SHN_UNDEF);
symbol.isExported = (bind == ELFIO::STB_GLOBAL && section_index != ELFIO::SHN_UNDEF);
m_symbols.push_back(symbol);
}
}
}
}
void ElfParser::loadRelocations()
{
m_relocations.clear();
for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i)
{
ELFIO::section *psec = m_elf->sections[i];
if (psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA)
{
ELFIO::relocation_section_accessor relocs(*m_elf, psec);
ELFIO::section *symSec = m_elf->sections[psec->get_link()];
ELFIO::symbol_section_accessor symbols(*m_elf, symSec);
ELFIO::section *strSec = m_elf->sections[symSec->get_link()];
ELFIO::string_section_accessor strings(strSec);
for (ELFIO::Elf_Xword j = 0; j < relocs.get_entries_num(); ++j)
{
ELFIO::Elf64_Addr offset;
ELFIO::Elf_Word symbol;
ELFIO::Elf_Word type;
ELFIO::Elf_Sxword addend;
// Always use the 5-parameter version
if (psec->get_type() == ELFIO::SHT_REL)
{
// Pass addend even for REL sections
relocs.get_entry(j, offset, symbol, type, addend);
// Reset addend for REL sections since it's not part of the section
addend = 0;
}
else
{
relocs.get_entry(j, offset, symbol, type, addend);
}
Relocation reloc;
reloc.offset = static_cast<uint32_t>(offset);
reloc.info = (symbol << 8) | (type & 0xFF);
reloc.symbol = symbol;
reloc.type = type;
reloc.addend = static_cast<int32_t>(addend);
m_relocations.push_back(reloc);
}
}
}
}
}
+50
View File
@@ -0,0 +1,50 @@
#include "ps2recomp/ps2_recompiler.h"
#include <iostream>
#include <string>
using namespace ps2recomp;
void printUsage()
{
std::cout << "PS2Recomp - A static recompiler for PlayStation 2 ELF files\n";
std::cout << "Usage: ps2recomp <config.toml>\n";
std::cout << " config.toml: Configuration file for the recompiler\n";
}
int main(int argc, char *argv[])
{
if (argc < 2)
{
printUsage();
return 1;
}
std::string configPath = argv[1];
try
{
PS2Recompiler recompiler(configPath);
if (!recompiler.initialize())
{
std::cerr << "Failed to initialize recompiler\n";
return 1;
}
if (!recompiler.recompile())
{
std::cerr << "Recompilation failed\n";
return 1;
}
recompiler.generateOutput();
std::cout << "Recompilation completed successfully\n";
return 0;
}
catch (const std::exception &e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
}
+388
View File
@@ -0,0 +1,388 @@
#include "ps2recomp/ps2_recompiler.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdexcept>
#include <filesystem>
namespace fs = std::filesystem;
namespace ps2recomp
{
PS2Recompiler::PS2Recompiler(const std::string &configPath)
: m_configManager(configPath)
{
}
bool PS2Recompiler::initialize()
{
try
{
m_config = m_configManager.loadConfig();
for (const auto &name : m_config.stubFunctions)
{
m_stubFunctions[name] = true;
}
for (const auto &name : m_config.skipFunctions)
{
m_skipFunctions[name] = true;
}
m_elfParser = std::make_unique<ElfParser>(m_config.inputPath);
if (!m_elfParser->parse())
{
std::cerr << "Failed to parse ELF file: " << m_config.inputPath << std::endl;
return false;
}
m_functions = m_elfParser->extractFunctions();
m_symbols = m_elfParser->extractSymbols();
m_sections = m_elfParser->getSections();
m_relocations = m_elfParser->getRelocations();
m_decoder = std::make_unique<R5900Decoder>();
m_codeGenerator = std::make_unique<CodeGenerator>(m_symbols);
fs::create_directories(m_config.outputPath);
return true;
}
catch (const std::exception &e)
{
std::cerr << "Error during initialization: " << e.what() << std::endl;
return false;
}
}
bool PS2Recompiler::recompile()
{
try
{
std::cout << "Recompiling " << m_functions.size() << " functions..." << std::endl;
std::string runtimeHeader = generateRuntimeHeader();
fs::path runtimeHeaderPath = fs::path(m_config.outputPath) / "ps2_runtime_macros.h";
writeToFile(runtimeHeaderPath.string(), runtimeHeader);
size_t processedCount = 0;
for (auto &function : m_functions)
{
if (shouldSkipFunction(function.name))
{
std::cout << "Skipping function: " << function.name << std::endl;
continue;
}
if (shouldStubFunction(function.name))
{
std::cout << "Stubbing function: " << function.name << std::endl;
function.isStub = true;
// TODO: Generate stub implementation
continue;
}
if (shouldStubFunction(function.name))
{
std::cout << "Stubbing function: " << function.name << std::endl;
function.isStub = true;
function.isRecompiled = true; // we're generating code for it
// Generate stub implementation and store it
std::string stubCode = generateStubFunction(function);
m_generatedStubs[function.start] = stubCode;
continue;
}
if (!decodeFunction(function))
{
std::cerr << "Failed to decode function: " << function.name << std::endl;
return false;
}
function.isRecompiled = true;
#if _DEBUG
processedCount++;
if (processedCount % 100 == 0)
{
std::cout << "Processed " << processedCount << " functions." << std::endl;
}
#endif
}
std::cout << "Recompilation completed successfully." << std::endl;
return true;
}
catch (const std::exception &e)
{
std::cerr << "Error during recompilation: " << e.what() << std::endl;
return false;
}
}
void PS2Recompiler::generateOutput()
{
try
{
if (m_config.singleFileOutput)
{
std::stringstream combinedOutput;
combinedOutput << "#include \"ps2_runtime_macros.h\"\n";
combinedOutput << "#include \"ps2_runtime.h\"\n\n";
for (const auto &function : m_functions)
{
if (!function.isRecompiled)
{
continue;
}
if (function.isStub)
{
combinedOutput << m_generatedStubs[function.start] << "\n\n";
}
else
{
const auto &instructions = m_decodedFunctions[function.start];
std::string code = m_codeGenerator->generateFunction(function, instructions);
combinedOutput << code << "\n\n";
}
}
fs::path outputPath = fs::path(m_config.outputPath) / "recompiled.cpp";
writeToFile(outputPath.string(), combinedOutput.str());
std::cout << "Wrote combined output to: " << outputPath << std::endl;
}
else
{
for (const auto &function : m_functions)
{
if (!function.isRecompiled || function.isStub)
{
continue;
}
std::string code;
if (function.isStub)
{
code = m_generatedStubs[function.start];
}
else
{
const auto &instructions = m_decodedFunctions[function.start];
code = m_codeGenerator->generateFunction(function, instructions);
}
fs::path outputPath = getOutputPath(function);
fs::create_directories(outputPath.parent_path());
writeToFile(outputPath.string(), code);
}
std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl;
}
}
catch (const std::exception &e)
{
std::cerr << "Error during output generation: " << e.what() << std::endl;
}
}
bool PS2Recompiler::decodeFunction(Function &function)
{
std::vector<Instruction> instructions;
uint32_t start = function.start;
uint32_t end = function.end;
for (uint32_t address = start; address < end; address += 4)
{
try
{
if (!m_elfParser->isValidAddress(address))
{
std::cerr << "Invalid address: 0x" << std::hex << address << std::dec
<< " in function: " << function.name << std::endl;
return false;
}
uint32_t rawInstruction = m_elfParser->readWord(address);
auto patchIt = m_config.patches.find(address);
if (patchIt != m_config.patches.end())
{
rawInstruction = std::stoul(patchIt->second, nullptr, 0);
std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl;
}
Instruction inst = m_decoder->decodeInstruction(address, rawInstruction);
instructions.push_back(inst);
}
catch (const std::exception &e)
{
std::cerr << "Error decoding instruction at 0x" << std::hex << address << std::dec
<< " in function: " << function.name << ": " << e.what() << std::endl;
return false;
}
}
m_decodedFunctions[function.start] = instructions;
return true;
}
bool PS2Recompiler::shouldStubFunction(const std::string &name) const
{
return m_stubFunctions.find(name) != m_stubFunctions.end();
}
bool PS2Recompiler::shouldSkipFunction(const std::string &name) const
{
return m_skipFunctions.find(name) != m_skipFunctions.end();
}
std::string PS2Recompiler::generateRuntimeHeader()
{
return m_codeGenerator->generateMacroHeader();
}
std::string PS2Recompiler::generateStubFunction(const Function &function)
{
std::stringstream ss;
ss << "#include \"ps2_runtime_macros.h\"\n";
ss << "#include \"ps2_runtime.h\"\n\n";
ss << "// STUB FUNCTION: " << function.name << "\n";
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
ss << "void " << function.name << "(uint8_t* rdram, R5900Context* ctx) {\n";
auto stubImpl = m_config.stubImplementations.find(function.name);
if (stubImpl != m_config.stubImplementations.end())
{
ss << " // Custom stub implementation\n";
ss << " " << stubImpl->second << "\n";
}
else
{
// Default stub implementation based on common functions
if (function.name == "printf" || function.name == "fprintf" ||
function.name == "sprintf" || function.name == "snprintf")
{
ss << " // Format string is in $a0 (r4), args start at $a1 (r5)\n";
ss << " #ifdef PS2_RECOMP_DEBUG\n";
ss << " printf(\"Stub called: " << function.name << " with format at 0x%08X\\n\", ctx->r[4]);\n";
ss << " #endif\n";
ss << " // Return success (number of characters, but we'll just say 1)\n";
ss << " ctx->r[2] = 1;\n";
}
else if (function.name == "malloc" || function.name == "calloc" ||
function.name == "realloc" || function.name == "memalign")
{
ss << " // Memory allocation - Would call the runtime's allocation system\n";
ss << " uint32_t size = ctx->r[4]; // Size is in $a0\n";
ss << " #ifdef PS2_RECOMP_DEBUG\n";
ss << " printf(\"Stub called: " << function.name << " size=%u\\n\", size);\n";
ss << " #endif\n";
ss << " // In a real implementation, call runtime->allocateMemory(size)\n";
ss << " ctx->r[2] = 0; // Return NULL for now - replace with actual allocation in real implementation\n";
}
else if (function.name == "free")
{
ss << " // Free memory - Would call the runtime's free system\n";
ss << " uint32_t ptr = ctx->r[4]; // Pointer is in $a0\n";
ss << " #ifdef PS2_RECOMP_DEBUG\n";
ss << " printf(\"Stub called: free(0x%08X)\\n\", ptr);\n";
ss << " #endif\n";
ss << " // In a real implementation, call runtime->freeMemory(ptr)\n";
}
else if (function.name == "memcpy" || function.name == "memmove")
{
ss << " // Memory copy\n";
ss << " uint32_t dst = ctx->r[4]; // Destination in $a0\n";
ss << " uint32_t src = ctx->r[5]; // Source in $a1\n";
ss << " uint32_t size = ctx->r[6]; // Size in $a2\n";
ss << " #ifdef PS2_RECOMP_DEBUG\n";
ss << " printf(\"Stub called: " << function.name << "(dst=0x%08X, src=0x%08X, size=%u)\\n\", dst, src, size);\n";
ss << " #endif\n";
ss << " // Only copy if within valid memory range\n";
ss << " if (dst < 0x2000000 && src < 0x2000000 && dst + size < 0x2000000 && src + size < 0x2000000) {\n";
ss << " memcpy(rdram + dst, rdram + src, size);\n";
ss << " }\n";
ss << " ctx->r[2] = dst; // Return destination pointer\n";
}
else if (function.name == "memset")
{
ss << " // Memory set\n";
ss << " uint32_t dst = ctx->r[4]; // Destination in $a0\n";
ss << " uint8_t value = (uint8_t)ctx->r[5]; // Value in $a1\n";
ss << " uint32_t size = ctx->r[6]; // Size in $a2\n";
ss << " #ifdef PS2_RECOMP_DEBUG\n";
ss << " printf(\"Stub called: memset(dst=0x%08X, value=%u, size=%u)\\n\", dst, value, size);\n";
ss << " #endif\n";
ss << " // Only set if within valid memory range\n";
ss << " if (dst < 0x2000000 && dst + size < 0x2000000) {\n";
ss << " memset(rdram + dst, value, size);\n";
ss << " }\n";
ss << " ctx->r[2] = dst; // Return destination pointer\n";
}
else
{
// Generic stub for unknown functions
ss << " // Default stub implementation\n";
ss << " #ifdef PS2_RECOMP_DEBUG\n";
ss << " printf(\"Stub function called: " << function.name << " at PC=0x%08X\\n\", ctx->pc);\n";
ss << " #endif\n";
ss << " // Default return value (0)\n";
ss << " ctx->r[2] = 0;\n";
}
}
ss << "}\n";
return ss.str();
}
bool PS2Recompiler::writeToFile(const std::string &path, const std::string &content)
{
std::ofstream file(path);
if (!file)
{
std::cerr << "Failed to open file for writing: " << path << std::endl;
return false;
}
file << content;
file.close();
return true;
}
std::filesystem::path PS2Recompiler::getOutputPath(const Function &function) const
{
std::string safeName = function.name;
std::replace_if(safeName.begin(), safeName.end(), [](char c)
{ return c == '/' || c == '\\' || c == ':' || c == '*' ||
c == '?' || c == '"' || c == '<' || c == '>' ||
c == '|' || c == '$'; }, '_');
if (safeName.empty())
{
std::stringstream ss;
ss << "func_" << std::hex << function.start;
safeName = ss.str();
}
std::filesystem::path outputPath = m_config.outputPath;
outputPath /= safeName + ".cpp";
return outputPath;
}
}
+636
View File
@@ -0,0 +1,636 @@
#include "ps2recomp/r5900_decoder.h"
namespace ps2recomp
{
R5900Decoder::R5900Decoder()
{
}
R5900Decoder::~R5900Decoder()
{
}
Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction)
{
Instruction inst;
inst.address = address;
inst.raw = rawInstruction;
inst.opcode = OPCODE(rawInstruction);
inst.rs = RS(rawInstruction);
inst.rt = RT(rawInstruction);
inst.rd = RD(rawInstruction);
inst.sa = SA(rawInstruction);
inst.function = FUNCTION(rawInstruction);
inst.immediate = IMMEDIATE(rawInstruction);
inst.target = TARGET(rawInstruction);
inst.isMMI = false;
inst.isVU = false;
inst.isBranch = false;
inst.isJump = false;
inst.isCall = false;
inst.isReturn = false;
inst.hasDelaySlot = false;
inst.isMultimedia = false;
switch (inst.opcode)
{
case OPCODE_SPECIAL:
decodeSpecial(inst);
break;
case OPCODE_REGIMM:
decodeRegimm(inst);
break;
case OPCODE_J:
decodeJType(inst);
inst.isJump = true;
inst.hasDelaySlot = true;
break;
case OPCODE_JAL:
decodeJType(inst);
inst.isJump = true;
inst.isCall = true;
inst.hasDelaySlot = true;
break;
case OPCODE_BEQ:
case OPCODE_BNE:
case OPCODE_BLEZ:
case OPCODE_BGTZ:
case OPCODE_BEQL:
case OPCODE_BNEL:
case OPCODE_BLEZL:
case OPCODE_BGTZL:
decodeIType(inst);
inst.isBranch = true;
inst.hasDelaySlot = true;
break;
case OPCODE_ADDI:
case OPCODE_ADDIU:
case OPCODE_SLTI:
case OPCODE_SLTIU:
case OPCODE_ANDI:
case OPCODE_ORI:
case OPCODE_XORI:
case OPCODE_LUI:
decodeIType(inst);
break;
case OPCODE_MMI:
decodeMMI(inst);
inst.isMMI = true;
inst.isMultimedia = true;
break;
case OPCODE_LQ:
decodeIType(inst);
inst.isLoad = true;
inst.isMultimedia = true; // 128-bit load
break;
case OPCODE_SQ:
decodeIType(inst);
inst.isStore = true;
inst.isMultimedia = true; // 128-bit store
break;
case OPCODE_LB:
case OPCODE_LH:
case OPCODE_LWL:
case OPCODE_LW:
case OPCODE_LBU:
case OPCODE_LHU:
case OPCODE_LWR:
case OPCODE_LWU:
case OPCODE_LD:
case OPCODE_LDL:
case OPCODE_LDR:
case OPCODE_LL:
case OPCODE_LWC1:
case OPCODE_LDC1:
case OPCODE_LWC2:
case OPCODE_LDC2:
decodeIType(inst);
inst.isLoad = true;
break;
case OPCODE_SB:
case OPCODE_SH:
case OPCODE_SWL:
case OPCODE_SW:
case OPCODE_SWR:
case OPCODE_SD:
case OPCODE_SDL:
case OPCODE_SDR:
case OPCODE_SC:
case OPCODE_SWC1:
case OPCODE_SDC1:
case OPCODE_SWC2:
case OPCODE_SDC2:
case OPCODE_SCD:
decodeIType(inst);
inst.isStore = true;
break;
case OPCODE_COP0:
decodeCOP0(inst);
break;
case OPCODE_COP1:
decodeCOP1(inst);
break;
case OPCODE_COP2:
decodeCOP2(inst);
inst.isVU = true;
inst.isMultimedia = true;
break;
case OPCODE_PREF:
case OPCODE_CACHE:
// Prefetch and cache operations
decodeIType(inst);
break;
default:
// Default to I-type for most other instructions
decodeIType(inst);
break;
}
return inst;
}
void R5900Decoder::decodeRType(Instruction &inst) const
{
// R-type instructions already have all fields set correctly
}
void R5900Decoder::decodeIType(Instruction &inst) const
{
// I-type instructions already have all fields set correctly
}
void R5900Decoder::decodeJType(Instruction &inst) const
{
// J-type instructions already have all fields set correctly
}
void R5900Decoder::decodeSpecial(Instruction &inst) const
{
switch (inst.function)
{
case SPECIAL_JR:
inst.isJump = true;
inst.hasDelaySlot = true;
if (inst.rs == 31)
{
// jr $ra is typically a return
inst.isReturn = true;
}
break;
case SPECIAL_JALR:
inst.isJump = true;
inst.isCall = true;
inst.hasDelaySlot = true;
break;
case SPECIAL_SYSCALL:
case SPECIAL_BREAK:
// Special handling for syscall/break
break;
case SPECIAL_MFHI:
case SPECIAL_MTHI:
case SPECIAL_MFLO:
case SPECIAL_MTLO:
// HI/LO register operations
break;
case SPECIAL_MULT:
case SPECIAL_MULTU:
case SPECIAL_DIV:
case SPECIAL_DIVU:
// Multiplication and division operations
inst.isMultimedia = true;
break;
case SPECIAL_ADD:
case SPECIAL_ADDU:
case SPECIAL_SUB:
case SPECIAL_SUBU:
case SPECIAL_AND:
case SPECIAL_OR:
case SPECIAL_XOR:
case SPECIAL_NOR:
case SPECIAL_SLT:
case SPECIAL_SLTU:
// ALU operations
break;
case SPECIAL_SLL:
case SPECIAL_SRL:
case SPECIAL_SRA:
case SPECIAL_SLLV:
case SPECIAL_SRLV:
case SPECIAL_SRAV:
// Shift operations
break;
// 64-bit specific operations
case SPECIAL_DADD:
case SPECIAL_DADDU:
case SPECIAL_DSUB:
case SPECIAL_DSUBU:
case SPECIAL_DSLL:
case SPECIAL_DSRL:
case SPECIAL_DSRA:
case SPECIAL_DSLL32:
case SPECIAL_DSRL32:
case SPECIAL_DSRA32:
case SPECIAL_DSLLV:
case SPECIAL_DSRLV:
case SPECIAL_DSRAV:
// 64-bit operations
break;
default:
// Other R-type instructions
break;
}
}
void R5900Decoder::decodeRegimm(Instruction &inst) const
{
uint32_t rt = inst.rt;
switch (rt)
{
case REGIMM_BLTZ:
case REGIMM_BGEZ:
case REGIMM_BLTZL:
case REGIMM_BGEZL:
inst.isBranch = true;
inst.hasDelaySlot = true;
break;
case REGIMM_BLTZAL:
case REGIMM_BGEZAL:
case REGIMM_BLTZALL:
case REGIMM_BGEZALL:
inst.isBranch = true;
inst.isCall = true;
inst.hasDelaySlot = true;
break;
case REGIMM_MTSAB:
case REGIMM_MTSAH:
// PS2 specific MTSAB/MTSAH instructions (for QMFC2/QMTC2)
inst.isMultimedia = true;
break;
default:
// Other REGIMM instructions
break;
}
}
void R5900Decoder::decodeMMI(Instruction &inst) const
{
inst.isMMI = true;
inst.isMultimedia = true;
// The function field is actually determined by the lowest 6 bits (as in R-type)
uint32_t mmiFunction = inst.function;
// Categorize the MMI instruction type based on the rs field
uint32_t rs = inst.rs;
switch (mmiFunction)
{
case MMI_MADD:
case MMI_MADDU:
case MMI_MADD1:
case MMI_MADDU1:
// Multiply-add operations
break;
case MMI_PLZCW:
// Count leading zeros/ones
break;
case MMI_MFHI1:
case MMI_MTHI1:
case MMI_MFLO1:
case MMI_MTLO1:
// Secondary HI/LO register operations
break;
case MMI_MULT1:
case MMI_MULTU1:
case MMI_DIV1:
case MMI_DIVU1:
// Secondary multiply/divide operations
break;
case MMI_MMI0:
// First set of multimedia instructions
decodeMMI0(inst);
break;
case MMI_MMI1:
// Second set of multimedia instructions
decodeMMI1(inst);
break;
case MMI_MMI2:
// Third set of multimedia instructions
decodeMMI2(inst);
break;
case MMI_MMI3:
// Fourth set of multimedia instructions
decodeMMI3(inst);
break;
case MMI_PMFHL:
// PMFHL variations based on sa field
decodePMFHL(inst);
break;
case MMI_PMTHL:
// PMTHL operations
break;
case MMI_PSLLH:
case MMI_PSRLH:
case MMI_PSRAH:
case MMI_PSLLW:
case MMI_PSRLW:
case MMI_PSRAW:
// SIMD shift operations
break;
default:
// Unknown or unsupported MMI function
break;
}
}
void R5900Decoder::decodeCOP0(Instruction &inst) const
{
// COP0 (System Control) instructions
uint32_t rs = inst.rs; // Actually the cop0 format field
if (rs == COP0_MF)
{
// Move From COP0 register
}
else if (rs == COP0_MT)
{
// Move To COP0 register
}
else if (rs == COP0_CO)
{
// COProcessor operations
uint32_t function = inst.function;
if (function == COP0_CO_ERET)
{
inst.isReturn = true;
inst.hasDelaySlot = true;
}
else if (function == COP0_CO_TLBR ||
function == COP0_CO_TLBWI ||
function == COP0_CO_TLBWR ||
function == COP0_CO_TLBP)
{
// TLB operations
}
else if (function == COP0_CO_EI || function == COP0_CO_DI)
{
// Enable/Disable Interrupts
}
}
}
void R5900Decoder::decodeCOP1(Instruction &inst) const
{
// COP1 (FPU) instructions
uint32_t rs = inst.rs; // The FPU format field
if (rs == COP1_MF)
{
// Move From FPU register
}
else if (rs == COP1_CF)
{
// Move From FPU Control register
}
else if (rs == COP1_MT)
{
// Move To FPU register
}
else if (rs == COP1_CT)
{
// Move To FPU Control register
}
else if (rs == COP1_BC)
{
// FPU Branch on Condition
uint32_t rt = inst.rt; // The condition code
if (rt == COP1_BC_BCF || rt == COP1_BC_BCT)
{
inst.isBranch = true;
inst.hasDelaySlot = true;
}
}
else if (rs == COP1_S || rs == COP1_W)
{
// FPU operations (single precision or word)
uint32_t function = inst.function;
// Decode specific FPU operation based on function field
}
}
void R5900Decoder::decodeCOP2(Instruction &inst) const
{
// COP2 (VU0 macro mode) instructions
inst.isVU = true;
inst.isMultimedia = true;
uint32_t rs = inst.rs; // The VU0 format field
if (rs == COP2_MFC2)
{
// Move From COP2 register
}
else if (rs == COP2_CFC2)
{
// Move From COP2 Control register
}
else if (rs == COP2_MTC2)
{
// Move To COP2 register
}
else if (rs == COP2_CTC2)
{
// Move To COP2 Control register
}
else if (rs == COP2_BCF || rs == COP2_BCT)
{
// VU0 Branch on Condition
inst.isBranch = true;
inst.hasDelaySlot = true;
}
else
{
// VU0 vector operations
// These would need detailed decoding based on function field
}
}
void R5900Decoder::decodeMMI0(Instruction &inst) const
{
// Decode MMI0 subfunctions (based on function field)
uint32_t subFunction = inst.function & 0x3F;
// The implementation would set appropriate flags or properties based on the specific MMI0 operation
}
void R5900Decoder::decodeMMI1(Instruction &inst) const
{
// Decode MMI1 subfunctions (based on function field)
uint32_t subFunction = inst.function & 0x3F;
// The implementation would set appropriate flags or properties based on the specific MMI1 operation
}
void R5900Decoder::decodeMMI2(Instruction &inst) const
{
// Decode MMI2 subfunctions (based on function field)
uint32_t subFunction = inst.function & 0x3F;
// The implementation would set appropriate flags or properties based on the specific MMI2 operation
}
void R5900Decoder::decodeMMI3(Instruction &inst) const
{
// Decode MMI3 subfunctions (based on function field)
uint32_t subFunction = inst.function & 0x3F;
// The implementation would set appropriate flags or properties based on the specific MMI3 operation
}
void R5900Decoder::decodePMFHL(Instruction &inst) const
{
// PMFHL has different variations based on the sa field
uint32_t saField = inst.sa;
switch (saField)
{
case PMFHL_LW:
case PMFHL_UW:
case PMFHL_SLW:
case PMFHL_LH:
case PMFHL_SH:
// Set the appropriate flag for the PMFHL variation
inst.pmfhlVariation = saField;
break;
default:
// Unknown PMFHL variation
inst.pmfhlVariation = 0xFF;
break;
}
}
bool R5900Decoder::isBranchInstruction(const Instruction &inst) const
{
return inst.isBranch;
}
bool R5900Decoder::isJumpInstruction(const Instruction &inst) const
{
return inst.isJump;
}
bool R5900Decoder::isCallInstruction(const Instruction &inst) const
{
return inst.isCall;
}
bool R5900Decoder::isReturnInstruction(const Instruction &inst) const
{
return inst.isReturn;
}
bool R5900Decoder::isMMIInstruction(const Instruction &inst) const
{
return inst.isMMI;
}
bool R5900Decoder::isVUInstruction(const Instruction &inst) const
{
return inst.isVU;
}
bool R5900Decoder::isStore(const Instruction &inst) const
{
return inst.isStore;
}
bool R5900Decoder::isLoad(const Instruction &inst) const
{
return inst.isLoad;
}
bool R5900Decoder::hasDelaySlot(const Instruction &inst) const
{
return inst.hasDelaySlot;
}
uint32_t R5900Decoder::getBranchTarget(const Instruction &inst) const
{
if (!inst.isBranch)
{
return 0;
}
// Calculate branch target: PC + 4 + (sign-extended immediate << 2)
int32_t offset = static_cast<int16_t>(inst.immediate) << 2;
return inst.address + 4 + offset;
}
uint32_t R5900Decoder::getJumpTarget(const Instruction &inst) const
{
if (!inst.isJump)
{
return 0;
}
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
// J/JAL: target is in the lower 26 bits, shifted left by 2
// and combined with the upper 4 bits of PC + 4
uint32_t pc_upper = (inst.address + 4) & 0xF0000000;
return pc_upper | (inst.target << 2);
}
else if (inst.opcode == OPCODE_SPECIAL &&
(inst.function == SPECIAL_JR || inst.function == SPECIAL_JALR))
{
// JR/JALR: target is in the rs register (can't be determined statically)
return 0;
}
return 0;
}
} // namespace ps2recomp
+24
View File
@@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.20)
project(PS2Runtime VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(ps2_runtime STATIC
src/ps2_memory.cpp
src/ps2_runtime.cpp
)
target_include_directories(ps2_runtime PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
install(TARGETS ps2_runtime
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
install(DIRECTORY include/
DESTINATION include
)
+40
View File
@@ -0,0 +1,40 @@
# Runtime Library
The runtime library provides the execution environment for recompiled code, including:
* Memory management (32MB main RAM, scratchpad, etc.)
* Register context (128-bit GPRs, VU0 registers, etc.)
* Function table for dynamic linking
* Basic PS2 system call stubs
## Adding Custom Function Implementations
You can add custom implementations for PS2 system calls or game functions by:
1. Creating function implementations that match the signature:
```cpp
void function_name(uint8_t* rdram, R5900Context* ctx);
```
2. Registering them with the runtime:
```cpp
runtime.registerFunction(address, function_name);
```
## Advanced Features
Memory Translation
The runtime handles PS2's memory addressing, including:
* KSEG0/KSEG1 direct mapping
* TLB lookups for user memory
* Special memory areas (scratchpad, I/O registers)
## Vector Unit Support
PS2-specific 128-bit MMI instructions and VU0 macro mode instructions are supported via SSE/AVX intrinsics.
## Instruction Patching
You can patch specific instructions in the recompiled code to fix game issues or implement custom behavior.
## Limitations
* Graphics and sound output require external implementations
* Some PS2-specific hardware features may not be fully supported
* Performance may vary based on the complexity of the game
+130
View File
@@ -0,0 +1,130 @@
#ifndef PS2_RUNTIME_H
#define PS2_RUNTIME_H
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <string>
#include <functional>
#include <immintrin.h> // For SSE/AVX instructions
// PS2 CPU context (R5900)
struct R5900Context
{
// General Purpose Registers (128-bit)
__m128i r[32];
// Program Counter Hi/Lo registers (64-bit)
uint32_t pc;
uint32_t hi; // High result register
uint32_t lo; // Low result register
uint32_t sa; // Shift amount register
// VU0 registers (when used in macro mode)
__m128 vu0_vf[32];
float vu0_acc[4]; // VU0 ACC (accumulator)
float vu0_q; // VU0 Q register (quotient)
float vu0_p; // VU0 P register
uint16_t vu0_status; // VU0 status/flags
// COP0 System control registers (some critical ones)
uint32_t cop0_registers[32];
uint32_t cop0_status; // Status register
uint32_t cop0_cause; // Cause register
uint32_t cop0_epc; // Exception PC
// FPU registers (COP1)
float f[32];
// FPU control registers
uint32_t fcr0; // Implementation/revision register
uint32_t fcr31; // Control/status register
};
class PS2Memory
{
public:
PS2Memory();
~PS2Memory();
// Initialize memory
bool initialize(size_t ramSize = 32 * 1024 * 1024);
// Memory access methods
uint8_t *getRDRAM() { return m_rdram; }
uint8_t *getScratchpad() { return m_scratchpad; }
// Read/write memory
uint8_t read8(uint32_t address);
uint16_t read16(uint32_t address);
uint32_t read32(uint32_t address);
uint64_t read64(uint32_t address);
__m128i read128(uint32_t address);
void write8(uint32_t address, uint8_t value);
void write16(uint32_t address, uint16_t value);
void write32(uint32_t address, uint32_t value);
void write64(uint32_t address, uint64_t value);
void write128(uint32_t address, __m128i value);
// TLB handling
uint32_t translateAddress(uint32_t virtualAddress);
private:
// Main RAM (32MB)
uint8_t *m_rdram;
// Scratchpad (16KB)
uint8_t *m_scratchpad;
// I/O registers
std::unordered_map<uint32_t, uint32_t> m_ioRegisters;
// TLB entries
struct TLBEntry
{
uint32_t vpn;
uint32_t pfn;
uint32_t mask;
bool valid;
};
std::vector<TLBEntry> m_tlbEntries;
};
class PS2Runtime
{
public:
PS2Runtime();
~PS2Runtime();
bool initialize();
bool loadELF(const std::string &elfPath);
void run();
using RecompiledFunction = void (*)(uint8_t *, R5900Context *);
void registerFunction(uint32_t address, RecompiledFunction func);
RecompiledFunction lookupFunction(uint32_t address);
void registerBuiltinStubs();
private:
PS2Memory m_memory;
R5900Context m_cpuContext;
// Function table for recompiled code
std::unordered_map<uint32_t, RecompiledFunction> m_functionTable;
// Currently loaded modules
struct LoadedModule
{
std::string name;
uint32_t baseAddress;
size_t size;
bool active;
};
std::vector<LoadedModule> m_loadedModules;
};
#endif // PS2_RUNTIME_H
+108
View File
@@ -0,0 +1,108 @@
#include "ps2_runtime.h"
#include <iostream>
#include <string>
// Example of how to use the PS2 runtime with recompiled code
// Stub implementation for PS2 syscalls
void syscall(uint8_t *rdram, R5900Context *ctx)
{
uint32_t syscallNum = ctx->r[4].m128i_u32[0];
std::cout << "Syscall " << syscallNum << " called" << std::endl;
switch (syscallNum)
{
case 0x01: // Exit program
std::cout << "Program requested exit with code: " << ctx->r[5].m128i_u32[0] << std::endl;
break;
case 0x3C: // PutChar - print a character to stdout
std::cout << (char)ctx->r[5].m128i_u32[0];
break;
case 0x3D: // PutString - print a string to stdout
{
uint32_t strAddr = ctx->r[5].m128i_u32[0];
if (strAddr == 0)
{
std::cout << "(null)";
}
else
{
uint32_t physAddr = strAddr & 0x1FFFFFFF;
const char *str = reinterpret_cast<const char *>(rdram + physAddr);
std::cout << str;
}
}
break;
default:
std::cout << "Unhandled syscall: " << syscallNum << std::endl;
break;
}
}
// Example implementation of FlushCache
void FlushCache(uint8_t *rdram, R5900Context *ctx)
{
uint32_t cacheType = ctx->r[4].m128i_u32[0];
std::cout << "FlushCache called with type: " << cacheType << std::endl;
}
// Example implementation of a recompiled function
void recompiled_main(uint8_t *rdram, R5900Context *ctx)
{
std::cout << "Running recompiled main function" << std::endl;
// Example of memory access
uint32_t addr = 0x100000; // Some address in memory
uint32_t physAddr = addr & 0x1FFFFFFF;
uint32_t value = *reinterpret_cast<uint32_t *>(rdram + physAddr);
std::cout << "Value at 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl;
// Example of register manipulation
ctx->r[2] = _mm_set1_epi32(0x12345678); // Set register v0
ctx->r[4] = _mm_set1_epi32(0x3D); // Set register a0 for syscall (PutString)
ctx->r[5] = _mm_set1_epi32(0x10000); // Set register a1 with string address
// Call a "syscall" function
syscall(rdram, ctx);
// Example of returning a value
ctx->r[2] = _mm_set1_epi32(0); // Return 0 (success)
}
int main(int argc, char *argv[])
{
if (argc < 2)
{
std::cout << "Usage: " << argv[0] << " <elf_file>" << std::endl;
return 1;
}
std::string elfPath = argv[1];
PS2Runtime runtime;
if (!runtime.initialize())
{
std::cerr << "Failed to initialize PS2 runtime" << std::endl;
return 1;
}
// Register built-in functions
runtime.registerFunction(0x00000001, syscall);
runtime.registerFunction(0x00000002, FlushCache);
runtime.registerFunction(0x00100000, recompiled_main); // Example address for main
// Load the ELF file
if (!runtime.loadELF(elfPath))
{
std::cerr << "Failed to load ELF file: " << elfPath << std::endl;
return 1;
}
// Run the program
runtime.run();
return 0;
}
+401
View File
@@ -0,0 +1,401 @@
#include "ps2_runtime.h"
#include <iostream>
#include <cstring>
#include <stdexcept>
constexpr uint32_t PS2_RAM_BASE = 0x00000000;
constexpr uint32_t PS2_RAM_SIZE = 32 * 1024 * 1024; // 32MB
constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000;
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16 * 1024; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000;
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000;
constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000;
constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000;
constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000;
constexpr uint32_t PS2_GS_BASE = 0x12000000;
PS2Memory::PS2Memory()
: m_rdram(nullptr), m_scratchpad(nullptr)
{
}
PS2Memory::~PS2Memory()
{
if (m_rdram)
{
delete[] m_rdram;
m_rdram = nullptr;
}
if (m_scratchpad)
{
delete[] m_scratchpad;
m_scratchpad = nullptr;
}
}
bool PS2Memory::initialize(size_t ramSize)
{
try
{
// Allocate main RAM
m_rdram = new uint8_t[ramSize];
if (!m_rdram)
{
std::cerr << "Failed to allocate " << ramSize << " bytes for RDRAM" << std::endl;
return false;
}
std::memset(m_rdram, 0, ramSize);
// Allocate scratchpad
m_scratchpad = new uint8_t[PS2_SCRATCHPAD_SIZE];
if (!m_scratchpad)
{
std::cerr << "Failed to allocate " << PS2_SCRATCHPAD_SIZE << " bytes for scratchpad" << std::endl;
delete[] m_rdram;
m_rdram = nullptr;
return false;
}
std::memset(m_scratchpad, 0, PS2_SCRATCHPAD_SIZE);
// Initialize IO registers with default values
m_ioRegisters.clear();
// Initialize TLB entries
m_tlbEntries.clear();
return true;
}
catch (const std::exception &e)
{
std::cerr << "Error initializing PS2 memory: " << e.what() << std::endl;
return false;
}
}
uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
{
// Handle special memory regions
if (virtualAddress >= PS2_SCRATCHPAD_BASE && virtualAddress < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
// Scratchpad is directly mapped
return virtualAddress - PS2_SCRATCHPAD_BASE;
}
// For RDRAM, mask the address to get the physical address
if (virtualAddress < PS2_RAM_SIZE ||
(virtualAddress >= 0x80000000 && virtualAddress < 0x80000000 + PS2_RAM_SIZE))
{
// KSEG0 is directly mapped, just mask out the high bits
return virtualAddress & 0x1FFFFFFF;
}
// For addresses that need TLB lookup
if (virtualAddress >= 0xC0000000)
{
for (const auto &entry : m_tlbEntries)
{
if (entry.valid)
{
uint32_t vpn_masked = (virtualAddress >> 12) & ~entry.mask;
uint32_t entry_vpn_masked = entry.vpn & ~entry.mask;
if (vpn_masked == entry_vpn_masked)
{
// TLB hit
uint32_t offset = virtualAddress & 0xFFF; // Page offset
uint32_t page = entry.pfn | (virtualAddress & entry.mask);
return (page << 12) | offset;
}
}
}
// TLB miss
throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress));
}
// Default to simple masking for other addresses
return virtualAddress & 0x1FFFFFFF;
}
uint8_t PS2Memory::read8(uint32_t address)
{
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
return m_rdram[physAddr];
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
return m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE];
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
// IO registers - often not handled byte by byte
uint32_t regAddr = physAddr & ~0x3; // Align to word boundary
if (m_ioRegisters.find(regAddr) != m_ioRegisters.end())
{
uint32_t value = m_ioRegisters[regAddr];
uint32_t shift = (physAddr & 3) * 8;
return (value >> shift) & 0xFF;
}
return 0; // Unimplemented IO register
}
// Handle other memory regions ,for now return 0 for unimplemented regions
return 0;
}
uint16_t PS2Memory::read16(uint32_t address)
{
// Check alignment
if (address & 1)
{
throw std::runtime_error("Unaligned 16-bit read at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
return *reinterpret_cast<uint16_t *>(&m_rdram[physAddr]);
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
return *reinterpret_cast<uint16_t *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
// IO registers - align to word boundary and extract relevant bits
uint32_t regAddr = physAddr & ~0x3;
if (m_ioRegisters.find(regAddr) != m_ioRegisters.end())
{
uint32_t value = m_ioRegisters[regAddr];
uint32_t shift = (physAddr & 2) * 8;
return (value >> shift) & 0xFFFF;
}
return 0; // Unimplemented IO register
}
return 0;
}
uint32_t PS2Memory::read32(uint32_t address)
{
// Check alignment
if (address & 3)
{
throw std::runtime_error("Unaligned 32-bit read at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
return *reinterpret_cast<uint32_t *>(&m_rdram[physAddr]);
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
return *reinterpret_cast<uint32_t *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
// IO registers
if (m_ioRegisters.find(physAddr) != m_ioRegisters.end())
{
return m_ioRegisters[physAddr];
}
return 0; // Unimplemented IO register
}
return 0;
}
uint64_t PS2Memory::read64(uint32_t address)
{
// Check alignment
if (address & 7)
{
throw std::runtime_error("Unaligned 64-bit read at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
return *reinterpret_cast<uint64_t *>(&m_rdram[physAddr]);
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
return *reinterpret_cast<uint64_t *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]);
}
// 64-bit IO operations are not common, but who knows
return (uint64_t)read32(address) | ((uint64_t)read32(address + 4) << 32);
}
__m128i PS2Memory::read128(uint32_t address)
{
// Check alignment
if (address & 15)
{
throw std::runtime_error("Unaligned 128-bit read at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]));
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]));
}
// 128-bit reads are primarily for quad-word loads in the EE, which are only valid for RAM areas
// Return zeroes for unsupported areas
return _mm_setzero_si128();
}
void PS2Memory::write8(uint32_t address, uint8_t value)
{
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
m_rdram[physAddr] = value;
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE] = value;
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
// IO registers - handle byte writes by modifying the appropriate byte in the word
uint32_t regAddr = physAddr & ~0x3;
uint32_t shift = (physAddr & 3) * 8;
uint32_t mask = ~(0xFF << shift);
uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift);
m_ioRegisters[regAddr] = newValue;
// Handle potential side effects of IO register writes
}
}
void PS2Memory::write16(uint32_t address, uint16_t value)
{
// Check alignment
if (address & 1)
{
throw std::runtime_error("Unaligned 16-bit write at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
*reinterpret_cast<uint16_t *>(&m_rdram[physAddr]) = value;
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
*reinterpret_cast<uint16_t *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value;
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
// IO registers - handle halfword writes
uint32_t regAddr = physAddr & ~0x3;
uint32_t shift = (physAddr & 2) * 8;
uint32_t mask = ~(0xFFFF << shift);
uint32_t newValue = (m_ioRegisters[regAddr] & mask) | ((uint32_t)value << shift);
m_ioRegisters[regAddr] = newValue;
// Handle potential side effects of IO register writes
}
}
void PS2Memory::write32(uint32_t address, uint32_t value)
{
// Check alignment
if (address & 3)
{
throw std::runtime_error("Unaligned 32-bit write at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
*reinterpret_cast<uint32_t *>(&m_rdram[physAddr]) = value;
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
*reinterpret_cast<uint32_t *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value;
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
// IO registers
m_ioRegisters[physAddr] = value;
// Handle potential side effects of IO register writes
// This would be where we handle the various hardware effects
// For example, writing to a DMA control register might trigger a transfer
}
}
void PS2Memory::write64(uint32_t address, uint64_t value)
{
// Check alignment
if (address & 7)
{
throw std::runtime_error("Unaligned 64-bit write at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
*reinterpret_cast<uint64_t *>(&m_rdram[physAddr]) = value;
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
*reinterpret_cast<uint64_t *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]) = value;
}
else
{
// Split into two 32-bit writes for other memory regions
write32(address, (uint32_t)value);
write32(address + 4, (uint32_t)(value >> 32));
}
}
void PS2Memory::write128(uint32_t address, __m128i value)
{
// Check alignment
if (address & 15)
{
throw std::runtime_error("Unaligned 128-bit write at address: 0x" + std::to_string(address));
}
uint32_t physAddr = translateAddress(address);
if (physAddr < PS2_RAM_SIZE)
{
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value);
}
else if (physAddr >= PS2_SCRATCHPAD_BASE && physAddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
{
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr - PS2_SCRATCHPAD_BASE]), value);
}
else
{
// Split into smaller writes for other memory regions
// Extract the data using SSE intrinsics
uint64_t lo = _mm_extract_epi64(value, 0);
uint64_t hi = _mm_extract_epi64(value, 1);
write64(address, lo);
write64(address + 8, hi);
}
}
+218
View File
@@ -0,0 +1,218 @@
#include "ps2_runtime.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian
#define ET_EXEC 2 // Executable file
#define EM_MIPS 8 // MIPS architecture
struct ElfHeader
{
uint32_t magic;
uint8_t elf_class;
uint8_t endianness;
uint8_t version;
uint8_t os_abi;
uint8_t abi_version;
uint8_t padding[7];
uint16_t type;
uint16_t machine;
uint32_t version2;
uint32_t entry;
uint32_t phoff;
uint32_t shoff;
uint32_t flags;
uint16_t ehsize;
uint16_t phentsize;
uint16_t phnum;
uint16_t shentsize;
uint16_t shnum;
uint16_t shstrndx;
};
struct ProgramHeader
{
uint32_t type;
uint32_t offset;
uint32_t vaddr;
uint32_t paddr;
uint32_t filesz;
uint32_t memsz;
uint32_t flags;
uint32_t align;
};
#define PT_LOAD 1 // Loadable segment
PS2Runtime::PS2Runtime()
{
std::memset(&m_cpuContext, 0, sizeof(m_cpuContext));
// R0 is always zero in MIPS
m_cpuContext.r[0] = _mm_set1_epi32(0);
// Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF
m_functionTable.clear();
m_loadedModules.clear();
}
PS2Runtime::~PS2Runtime()
{
m_loadedModules.clear();
m_functionTable.clear();
}
bool PS2Runtime::initialize()
{
if (!m_memory.initialize())
{
std::cerr << "Failed to initialize PS2 memory" << std::endl;
return false;
}
registerBuiltinStubs();
return true;
}
void PS2Runtime::registerBuiltinStubs()
{
// Register common PS2 library functions as stubs
// Standard C library stubs
registerFunction(0xFFFFFFFF, [](uint8_t *rdram, R5900Context *ctx)
{ std::cout << "Stub: printf called" << std::endl; });
// PS2-specific system call stubs
registerFunction(0xFFFFFFFE, [](uint8_t *rdram, R5900Context *ctx)
{ std::cout << "Stub: FlushCache called with mode: " << ctx->r[4].m128i_u32[0] << std::endl; });
}
bool PS2Runtime::loadELF(const std::string &elfPath)
{
std::ifstream file(elfPath, std::ios::binary);
if (!file)
{
std::cerr << "Failed to open ELF file: " << elfPath << std::endl;
return false;
}
// Read ELF header
ElfHeader header;
file.read(reinterpret_cast<char *>(&header), sizeof(header));
// Check ELF magic number
if (header.magic != ELF_MAGIC)
{
std::cerr << "Invalid ELF magic number" << std::endl;
return false;
}
// Check if it's a MIPS executable
if (header.machine != EM_MIPS || header.type != ET_EXEC)
{
std::cerr << "Not a MIPS executable ELF file" << std::endl;
return false;
}
// Store entry point
m_cpuContext.pc = header.entry;
// Read program headers and load segments
for (uint16_t i = 0; i < header.phnum; i++)
{
ProgramHeader ph;
file.seekg(header.phoff + i * header.phentsize);
file.read(reinterpret_cast<char *>(&ph), sizeof(ph));
if (ph.type == PT_LOAD && ph.filesz > 0)
{
std::cout << "Loading segment: 0x" << std::hex << ph.vaddr
<< " - 0x" << (ph.vaddr + ph.memsz)
<< " (size: 0x" << ph.memsz << ")" << std::dec << std::endl;
// Allocate temporary buffer for the segment
std::vector<uint8_t> buffer(ph.filesz);
// Read segment data
file.seekg(ph.offset);
file.read(reinterpret_cast<char *>(buffer.data()), ph.filesz);
// Copy to memory
uint32_t physAddr = m_memory.translateAddress(ph.vaddr);
uint8_t *dest = m_memory.getRDRAM() + physAddr;
std::memcpy(dest, buffer.data(), ph.filesz);
// Zero-initialize the rest (bss-like sections)
if (ph.memsz > ph.filesz)
{
std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz);
}
}
}
// Create a loaded module entry
LoadedModule module;
module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1);
module.baseAddress = 0x00100000; // Typical base address for PS2 executables
module.size = 0; // Would need to calculate from segments
module.active = true;
m_loadedModules.push_back(module);
std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl;
return true;
}
void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func)
{
m_functionTable[address] = func;
}
PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
{
auto it = m_functionTable.find(address);
if (it != m_functionTable.end())
{
return it->second;
}
std::cerr << "Warning: Function at address 0x" << std::hex << address << std::dec << " not found" << std::endl;
static RecompiledFunction defaultFunction = [](uint8_t* rdram, R5900Context* ctx)
{
std::cerr << "Error: Called unimplemented function at address 0x" << std::hex << ctx->pc << std::dec << std::endl;
};
return defaultFunction;
}
void PS2Runtime::run()
{
RecompiledFunction entryPoint = lookupFunction(m_cpuContext.pc);
// Set up initial CPU state
m_cpuContext.r[4] = _mm_set1_epi32(0); // A0 = 0 (argc)
m_cpuContext.r[5] = _mm_set1_epi32(0); // A1 = 0 (argv)
m_cpuContext.r[29] = _mm_set1_epi32(0x02000000); // SP = top of RAM
std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl;
try
{
// Call the entry point function
entryPoint(m_memory.getRDRAM(), &m_cpuContext);
std::cout << "Program execution completed successfully" << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "Error during program execution: " << e.what() << std::endl;
}
}