mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
migrate from private cloud
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
/bin/
|
||||
/intermediate/
|
||||
.vs/
|
||||
out
|
||||
.vscode
|
||||
build
|
||||
|
||||
*.exe
|
||||
*.ilk
|
||||
*.exp
|
||||
*.log
|
||||
*.tlog
|
||||
*.ipch
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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 §ionName);
|
||||
uint32_t getSectionAddress(const std::string §ionName);
|
||||
uint32_t getSectionSize(const std::string §ionName);
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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 §ion : 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 §ion : 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 §ionName)
|
||||
{
|
||||
for (const auto §ion : m_sections)
|
||||
{
|
||||
if (section.name == sectionName)
|
||||
{
|
||||
return section.data;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t ElfParser::getSectionAddress(const std::string §ionName)
|
||||
{
|
||||
for (const auto §ion : m_sections)
|
||||
{
|
||||
if (section.name == sectionName)
|
||||
{
|
||||
return section.address;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t ElfParser::getSectionSize(const std::string §ionName)
|
||||
{
|
||||
for (const auto §ion : 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user