feat: refactor analyzer to not realy only on debug symbols (#53)

fix: patching NOP things that code gen already know how to handle
feat: added a bug on JAL/J/JAR code gen
feat: enhanced tom file
fix: fix memory layout for ps2 macros
feat: added a lot of not working garbabe to runtime (fix later)
feat: some code organization
feat: added new tests
feat: update readme
This commit is contained in:
Ranieri
2026-02-13 15:02:34 -03:00
committed by GitHub
parent 1d1b79d5f0
commit 8f747334d6
26 changed files with 10987 additions and 2111 deletions
+69 -47
View File
@@ -1,88 +1,108 @@
## PS2Recomp: PlayStation 2 Static Recompiler (Not ready)
## PS2Recomp: PlayStation 2 Static Recompiler (Experimental)
[![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white)](https://discord.gg/JQ8mawxUEf)
* Note this is an experiment and doesn't work as it should, feel free to open a PR to help the project.
Also check our [WIKI](https://github.com/ran-j/PS2Recomp/wiki)
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.
This project statically recompiles PS2 ELF binaries into C++ and provides a runtime to execute the generated code.
### Modules
* `ps2xAnalyzer`: scans ELF/functions and writes TOML config (`stubs`, `skip`, instruction patches).
* `ps2xRecomp`: reads TOML + ELF, decodes R5900 instructions, and generates C++ output.
* `ps2xRuntime`: hosts memory, function registration, syscall dispatch, and hardware stubs.
### 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
* PS2-specific MMI and VU0 macro support.
* Single-file or multi-file output.
* Configurable stubs, skips, and instruction patches.
* Instruction-driven syscall handling.
### 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
* 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);`.
### Current Behavior
* `stubs` entries generate wrappers that call known runtime syscall/stub handlers by name.
* `skip` entries are not recompiled and generate explicit `ps2_stubs::TODO_NAMED(...)` wrappers.
* Recompiled `SYSCALL` now calls `runtime->handleSyscall(...)` with the encoded syscall immediate.
* Runtime syscall dispatch tries encoded syscall ID first, then falls back to `$v1`.
### Requirements
* CMake 3.20 or higher
* C++20 compatible compiler (I only test with MSVC)
* SSE4/AVX support for 128-bit operations
* CMake 3.20+
* C++20 compiler (currently tested mainly with MSVC)
* SSE4/AVX host support for some vector paths
### Build
#### 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 .
cmake -S . -B out/build
cmake --build out/build --config Debug
```
### Usage
1. **Analyze the ELF**: Use the `ps2_analyzer` tool to generate an initial configuration.
1. Analyze ELF and generate config:
```bash
./ps2_analyzer your_game.elf config.toml
```
*For better results on retail games, see the [Ghidra Workflow](ps2xAnalyzer/Readme.md#3-ghidra-integration-recommended-for-complex-games).*
2. **Recompile**: Run the recompiler using the generated configuration.
2. Recompile using generated TOML:
```bash
./ps2recomp config.toml
./ps2_recomp config.toml
```
3. **Compile Output**:
* Compile the generated C++ code in the `output/` directory.
* Link with the `ps2xRuntime` implementation.
3. Build generated output and link with `ps2xRuntime`.
### Configuration
PS2Recomp uses TOML configuration files to specify:
* Input ELF file
* Output directory
* Functions to stub or skip
* Instruction patches
Main fields in `config.toml`:
* `general.input`: source ELF path.
* `general.ghidra_output`: optional function map CSV.
* `general.output`: generated C++ output folder.
* `general.single_file_output`: one combined cpp or one file per function.
* `general.patch_syscalls`: apply configured patches to `SYSCALL` instructions (`false` recommended).
* `general.patch_cop0`: apply configured patches to COP0 instructions.
* `general.patch_cache`: apply configured patches to CACHE instructions.
* `general.stubs`: names to force as stubs.
* `general.skip`: names to force as skipped wrappers.
* `patches.instructions`: raw instruction replacements by address.
Example:
#### Example configuration:
```toml
[general]
input = "path/to/game.elf"
ghidra_output = ""
output = "output/"
single_file_output = false
# Functions to stub
single_file_output = true
patch_syscalls = false
patch_cop0 = true
patch_cache = true
stubs = ["printf", "malloc", "free"]
# Functions to skip
skip = ["abort", "exit"]
# Patches
[patches]
instructions = [
{ address = "0x100004", value = "0x00000000" }
@@ -90,23 +110,25 @@ instructions = [
```
### 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
To execute the recompiled code.
A basic runtime lib is provided in `ps2xRuntime` folder.
`ps2xRuntime` currently provides:
* Guest memory model and function dispatch table.
* Some syscall dispatcher with common kernel IDs.
* Basic GS/VU/file/system stubs.
* Foundation to expand and port your game.
### 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
* VU1 microcode is not complete.
* Hardware emulation is partial and many paths are stubbed.
### Acknowledgments
* Inspired by N64Recomp
* Uses ELFIO for ELF parsing
* Uses toml11 for TOML parsing
* Uses fmt for string formatting
* Uses fmt for string formatting
+22 -13
View File
@@ -3,23 +3,32 @@ 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"
set(PS2ANALYZER_LIB_SOURCES
src/elf_analyzer.cpp
)
add_executable(ps2_analyzer ${PS2ANALYZER_SOURCES})
target_include_directories(ps2_analyzer PRIVATE
add_library(ps2_analyzer_lib STATIC ${PS2ANALYZER_LIB_SOURCES})
target_include_directories(ps2_analyzer_lib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/ps2xRecomp/include
)
target_link_libraries(ps2_analyzer PRIVATE
fmt::fmt
target_link_libraries(ps2_analyzer_lib PUBLIC
ps2_recomp_lib
)
install(TARGETS ps2_analyzer
add_executable(ps2_analyzer
src/analyzer_main.cpp
)
target_link_libraries(ps2_analyzer PRIVATE
ps2_analyzer_lib
)
install(TARGETS ps2_analyzer ps2_analyzer_lib
RUNTIME DESTINATION bin
)
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
+51 -19
View File
@@ -8,30 +8,43 @@
#include <memory>
#include <map>
#include <set>
#include <functional>
namespace ps2recomp
{
struct CFGNode;
struct Instruction;
struct FunctionCall;
struct JumpTable;
struct Relocation;
struct Section;
struct Symbol;
struct Function;
class R5900Decoder;
class ElfParser;
struct CFGNode;
struct Instruction;
struct FunctionCall;
struct JumpTable;
struct Relocation;
struct Section;
struct Symbol;
struct Function;
class R5900Decoder;
class ElfParser;
using CFG = std::unordered_map<uint32_t, CFGNode>;
using CFG = std::unordered_map<uint32_t, CFGNode>;
class ElfAnalyzer
class ElfAnalyzer
{
public:
explicit ElfAnalyzer(const std::string &elfPath);
explicit ElfAnalyzer(const std::string &elfPath);
~ElfAnalyzer();
bool analyze();
bool generateToml(const std::string &outputPath);
bool isLibrarySymbolNameForHeuristics(const std::string &name) const;
static bool isReliableSymbolNameForHeuristics(const std::string &name);
static bool isSystemSymbolNameForHeuristics(const std::string &name);
static bool shouldAutoSkipNameForHeuristics(const std::string &name);
static int findEntryFunctionIndexForHeuristics(const std::vector<Function> &functions, uint32_t entryAddress);
static int findFallbackEntryFunctionIndexForHeuristics(const std::vector<Function> &functions);
static bool hasHardwareIOSignalForHeuristics(const std::vector<Instruction> &instructions);
static bool hasLargeComplexMMISignalForHeuristics(const std::vector<Instruction> &instructions, size_t largeInstructionThreshold = 500);
static bool hasSelfModifyingSignalForHeuristics(const std::vector<Instruction> &instructions, const std::vector<Section> &sections);
static bool shouldSkipForPatchDensityForHeuristics(const std::string &functionName, uint32_t functionSizeBytes, size_t patchCount, bool isLibraryFunction);
static std::vector<JumpTable> detectJumpTablesForHeuristics(const std::vector<Instruction> &instructions, const std::vector<Section> &sections, const std::function<bool(uint32_t, uint32_t &)> &readWord);
static std::unordered_set<std::string> findRecursiveFunctionsForHeuristics(const std::unordered_map<std::string, std::vector<std::string>> &callGraph);
private:
std::string m_elfPath;
@@ -42,24 +55,42 @@ namespace ps2recomp
std::vector<Symbol> m_symbols;
std::vector<Section> m_sections;
std::vector<Relocation> m_relocations;
std::unordered_set<std::string> m_libFunctions;
std::unordered_set<std::string> m_skipFunctions;
std::unordered_set<uint32_t> m_forceRecompileStarts;
std::unordered_set<std::string> m_knownLibNames;
std::unordered_map<std::string, std::set<std::string>> m_functionDataUsage;
std::unordered_map<uint32_t, std::string> m_commonDataAccess;
std::map<uint32_t, uint32_t> m_patches;
std::map<uint32_t, std::string> m_patchReasons;
std::unordered_map<uint32_t, CFG> m_functionCFGs;
std::vector<JumpTable> m_jumpTables;
std::unordered_map<uint32_t, std::vector<FunctionCall>> m_functionCalls;
std::unordered_map<uint32_t, uint32_t> m_mmioByInstructionAddress;
void initializeLibraryFunctions();
void analyzeEntryPoint();
void analyzeLibraryFunctions();
void analyzeDataUsage();
void identifyPotentialPatches();
bool tryPatchSelfModifyingStore(const Function &func,
const std::vector<Instruction> &instructions,
size_t index);
bool tryResolveBasePlusOffset(const std::vector<Instruction> &instructions,
size_t index,
uint32_t reg,
int16_t offset,
uint32_t &baseAddr) const;
bool tryResolveLuiBase(const std::vector<Instruction> &instructions,
size_t index,
uint32_t reg,
uint32_t &baseAddr) const;
bool isCodeAddress(uint32_t addr) const;
void analyzeControlFlow();
void detectJumpTables();
void analyzePerformanceCriticalPaths() const;
@@ -67,12 +98,12 @@ namespace ps2recomp
void analyzeRegisterUsage() const;
void analyzeFunctionSignatures() const;
void optimizePatches();
bool identifyMemcpyPattern(const Function &func) const;
bool identifyMemsetPattern(const Function &func) const;
bool identifyStringOperationPattern(const Function &func) const;
bool identifyMathPattern(const Function &func) const;
bool isSystemFunction(const std::string &name) const;
bool isLibraryFunction(const std::string &name) const;
std::vector<Instruction> decodeFunction(const Function &function) const;
@@ -81,6 +112,7 @@ namespace ps2recomp
std::string escapeBackslashes(const std::string &path);
bool hasMMIInstructions(const Function &function) const;
bool hasVUInstructions(const Function &function) const;
bool shouldAutoSkipByHeuristic(const Function &function) const;
bool identifyFunctionType(const Function &function);
void categorizeFunction(Function &function);
uint32_t getSuccessor(const Instruction &inst, uint32_t currentAddr);
@@ -89,4 +121,4 @@ namespace ps2recomp
};
}
#endif // PS2RECOMP_ELF_ANALYZER_H
#endif // PS2RECOMP_ELF_ANALYZER_H
File diff suppressed because it is too large Load Diff
@@ -120,7 +120,6 @@ namespace ps2recomp
// Jump Table Generation
std::string generateJumpTableSwitch(const Instruction &inst, uint32_t tableAddress,
const std::vector<JumpTableEntry> &entries);
std::string generateBootstrapFunction() const;
const Symbol *findSymbolByAddress(uint32_t address) const;
std::string getFunctionName(uint32_t address) const;
@@ -35,6 +35,7 @@ namespace ps2recomp
uint32_t getSectionAddress(const std::string &sectionName) const;
uint32_t getSectionSize(const std::string &sectionName) const;
uint32_t getEntryPoint() const;
void debugAddress(uint32_t address) const;
private:
std::string m_filePath;
+18 -7
View File
@@ -11,19 +11,28 @@
namespace ps2recomp
{
class R5900Decoder;
class ElfParser;
class R5900Decoder;
class ElfParser;
class PS2Recompiler
enum class StubTarget
{
Unknown,
Syscall,
Stub
};
class PS2Recompiler
{
public:
explicit PS2Recompiler(const std::string &configPath);
explicit PS2Recompiler(const std::string &configPath);
~PS2Recompiler();
bool initialize();
bool recompile();
void generateOutput();
static StubTarget resolveStubTarget(const std::string& name);
private:
ConfigManager m_configManager;
std::unique_ptr<ElfParser> m_elfParser;
@@ -38,20 +47,22 @@ namespace ps2recomp
std::unordered_map<uint32_t, std::vector<Instruction>> m_decodedFunctions;
std::unordered_map<std::string, bool> m_skipFunctions;
std::unordered_set<uint32_t> m_skipFunctionStarts;
std::unordered_set<std::string> m_stubFunctions;
std::unordered_set<uint32_t> m_stubFunctionStarts;
std::map<uint32_t, std::string> m_generatedStubs;
std::unordered_map<uint32_t, std::string> m_functionRenames;
CodeGenerator::BootstrapInfo m_bootstrapInfo;
bool decodeFunction(Function &function);
void discoverAdditionalEntryPoints();
bool shouldSkipFunction(const std::string &name) const;
bool isStubFunction(const std::string &name) const;
bool shouldSkipFunction(const Function &function) const;
bool isStubFunction(const Function &function) const;
bool generateFunctionHeader();
bool generateStubHeader();
bool writeToFile(const std::string &path, const std::string &content);
std::filesystem::path getOutputPath(const Function &function) const;
std::string sanitizeFunctionName(const std::string &name) const;
std::string sanitizeFunctionName(const std::string &name) const;
};
}
+15 -7
View File
@@ -43,6 +43,9 @@ namespace ps2recomp
uint8_t pmfhlVariation; // For PMFHL instructions
uint8_t vuFunction; // For VU instructions
bool isMmio = false;
uint32_t mmioAddress = 0;
struct
{
bool isVector; // Uses vector operations
@@ -59,8 +62,8 @@ namespace ps2recomp
bool modifiesGPR; // Modifies general purpose register
bool modifiesFPR; // Modifies floating point register
bool modifiesVFR; // Modifies vector float register
bool modifiesVIR; // Modifies vector integer register
bool modifiesVIC; // Modifies vector integer control register
bool modifiesVIR; // Modifies vector integer register
bool modifiesVIC; // Modifies vector integer control register
bool modifiesMemory; // Modifies memory
bool modifiesControl; // Modifies control register
} modificationInfo;
@@ -69,7 +72,7 @@ namespace ps2recomp
immediate(0), simmediate(0), target(0), raw(0),
isMMI(false), isVU(false), isBranch(false), isJump(false), isCall(false),
isReturn(false), hasDelaySlot(false), isMultimedia(false), isStore(false), isLoad(false),
mmiType(0), mmiFunction(0), pmfhlVariation(0), vuFunction(0)
mmiType(0), mmiFunction(0), pmfhlVariation(0), vuFunction(0), isMmio(false), mmioAddress(0)
{
vectorInfo = {};
modificationInfo = {};
@@ -85,8 +88,9 @@ namespace ps2recomp
std::vector<Instruction> instructions;
std::vector<uint32_t> callers;
std::vector<uint32_t> callees;
bool isRecompiled;
bool isStub;
bool isRecompiled = false;
bool isStub = false;
bool isSkipped = false;
};
// Symbol information
@@ -166,12 +170,16 @@ namespace ps2recomp
std::string inputPath;
std::string outputPath;
std::string ghidraMapPath;
bool singleFileOutput;
bool singleFileOutput = false;
bool patchSyscalls = false;
bool patchCop0 = true;
bool patchCache = true;
std::vector<std::string> skipFunctions;
std::unordered_map<uint32_t, std::string> patches;
std::vector<std::string> stubImplementations;
std::unordered_map<uint32_t, uint32_t> mmioByInstructionAddress;
};
} // namespace ps2recomp
#endif // PS2RECOMP_TYPES_H
#endif // PS2RECOMP_TYPES_H
File diff suppressed because it is too large Load Diff
+39
View File
@@ -29,6 +29,9 @@ namespace ps2recomp
config.ghidraMapPath = toml::find_or<std::string>(general, "ghidra_output", "");
config.outputPath = toml::find<std::string>(general, "output");
config.singleFileOutput = toml::find_or<bool>(general, "single_file_output", false);
config.patchSyscalls = toml::find_or<bool>(general, "patch_syscalls", config.patchSyscalls);
config.patchCop0 = toml::find_or<bool>(general, "patch_cop0", config.patchCop0);
config.patchCache = toml::find_or<bool>(general, "patch_cache", config.patchCache);
if (general.contains("stubs") && general.at("stubs").is_array())
{
@@ -90,6 +93,25 @@ namespace ps2recomp
}
}
}
if (data.contains("mmio") && data.at("mmio").is_table())
{
const auto &mmioTable = toml::find(data, "mmio").as_table();
for (const auto &[key, value] : mmioTable)
{
uint32_t instAddr = std::stoul(key, nullptr, 0);
uint32_t mmioAddr = 0;
if (value.is_string())
{
mmioAddr = std::stoul(value.as_string(), nullptr, 0);
}
else if (value.is_integer())
{
mmioAddr = static_cast<uint32_t>(value.as_integer());
}
config.mmioByInstructionAddress[instAddr] = mmioAddr;
}
}
}
catch (const std::exception &e)
{
@@ -109,10 +131,27 @@ namespace ps2recomp
general["ghidra_output"] = config.ghidraMapPath;
general["output"] = config.outputPath;
general["single_file_output"] = config.singleFileOutput;
general["patch_syscalls"] = config.patchSyscalls;
general["patch_cop0"] = config.patchCop0;
general["patch_cache"] = config.patchCache;
general["skip"] = config.skipFunctions;
general["stubs"] = config.stubImplementations;
data["general"] = general;
if (!config.mmioByInstructionAddress.empty())
{
toml::table mmioTable;
for (const auto &[instAddr, mmioAddr] : config.mmioByInstructionAddress)
{
std::ostringstream keyStream;
keyStream << "0x" << std::hex << instAddr;
std::ostringstream valStream;
valStream << "0x" << std::hex << mmioAddr;
mmioTable[keyStream.str()] = valStream.str();
}
data["mmio"] = mmioTable;
}
toml::table patches;
toml::array instPatches;
for (const auto &[addr, value] : config.patches)
+52
View File
@@ -352,6 +352,7 @@ namespace
func.end = highPc;
func.isRecompiled = false;
func.isStub = false;
func.isSkipped = false;
if (func.name.empty())
{
@@ -458,6 +459,7 @@ namespace
func.end = (end > start) ? end : (start + 4);
func.isRecompiled = false;
func.isStub = false;
func.isSkipped = false;
outFunctions.push_back(std::move(func));
}
@@ -522,6 +524,7 @@ namespace ps2recomp
}
existing.isStub = existing.isStub || newFunction.isStub;
existing.isSkipped = existing.isSkipped || newFunction.isSkipped;
};
for (const auto &symbol : m_symbols)
@@ -536,6 +539,7 @@ namespace ps2recomp
func.end = (symbol.size > 0) ? (symbol.address + symbol.size) : 0;
func.isRecompiled = false;
func.isStub = false;
func.isSkipped = false;
addOrMerge(func);
}
@@ -675,6 +679,53 @@ namespace ps2recomp
return 0;
}
void ElfParser::debugAddress(uint32_t address) const
{
for (const auto &section : m_sections)
{
if (address < section.address || address >= (section.address + section.size))
{
continue;
}
const uint32_t offset = address - section.address;
std::printf(
"Address 0x%08X -> section '%s'\n"
" section.address=0x%08X section.size=0x%08X section.offset=0x%08X\n"
" isCode=%d isData=%d isBSS=%d isReadOnly=%d data=%p\n"
" offsetInSection=0x%08X\n",
address,
section.name.c_str(),
section.address, section.size, section.offset,
section.isCode ? 1 : 0,
section.isData ? 1 : 0,
section.isBSS ? 1 : 0,
section.isReadOnly ? 1 : 0,
(void *)section.data,
offset);
if (!section.data)
{
std::printf(" section.data == nullptr (possible SHT_NOBITS/BSS)\n");
return;
}
const uint32_t dumpStart = (offset >= 16) ? (offset - 16) : 0;
const uint32_t dumpEnd = std::min(section.size, offset + 32);
std::printf(" bytes around address:\n ");
for (uint32_t dumpOffset = dumpStart; dumpOffset < dumpEnd; ++dumpOffset)
{
std::printf("%02X ", section.data[dumpOffset]);
}
std::printf("\n");
return;
}
std::printf("Address 0x%08X not covered by any section in m_sections\n", address);
}
uint32_t ElfParser::getEntryPoint() const
{
return static_cast<uint32_t>(m_elf->get_entry());
@@ -728,6 +779,7 @@ namespace ps2recomp
func.end = end;
func.isRecompiled = false;
func.isStub = false;
func.isSkipped = false;
m_extraFunctions.push_back(std::move(func));
count++;
+305 -120
View File
@@ -13,7 +13,7 @@
#include <cctype>
#include <unordered_set>
#include <optional>
#include <limits>
#include <limits>
namespace fs = std::filesystem;
@@ -21,13 +21,6 @@ namespace ps2recomp
{
namespace
{
enum class StubTarget
{
Unknown,
Syscall,
Stub
};
uint32_t decodeAbsoluteJumpTarget(uint32_t address, uint32_t target)
{
return ((address + 4) & 0xF0000000u) | (target << 2);
@@ -78,17 +71,167 @@ namespace ps2recomp
return sanitized;
}
StubTarget resolveStubTarget(const std::string &name)
bool shouldGenerateCodeForFunction(const Function &function)
{
if (ps2_runtime_calls::isSyscallName(name))
return function.isRecompiled || function.isStub || function.isSkipped;
}
enum class PatchClass
{
Generic,
Syscall,
Cop0,
Cache
};
PatchClass classifyPatchedInstruction(uint32_t rawInstruction)
{
const uint32_t opcode = OPCODE(rawInstruction);
if (opcode == OPCODE_SPECIAL && FUNCTION(rawInstruction) == SPECIAL_SYSCALL)
{
return StubTarget::Syscall;
return PatchClass::Syscall;
}
if (ps2_runtime_calls::isStubName(name))
if (opcode == OPCODE_COP0)
{
return StubTarget::Stub;
return PatchClass::Cop0;
}
return StubTarget::Unknown;
if (opcode == OPCODE_CACHE)
{
return PatchClass::Cache;
}
return PatchClass::Generic;
}
bool shouldApplyConfiguredPatch(PatchClass patchClass, const RecompilerConfig &config)
{
switch (patchClass)
{
case PatchClass::Syscall:
return config.patchSyscalls;
case PatchClass::Cop0:
return config.patchCop0;
case PatchClass::Cache:
return config.patchCache;
default:
return true;
}
}
std::string escapeCStringLiteral(const std::string &value)
{
std::string escaped;
escaped.reserve(value.size());
for (char c : value)
{
switch (c)
{
case '\\':
escaped += "\\\\";
break;
case '"':
escaped += "\\\"";
break;
case '\n':
escaped += "\\n";
break;
case '\r':
escaped += "\\r";
break;
case '\t':
escaped += "\\t";
break;
default:
escaped.push_back(c);
break;
}
}
return escaped;
}
std::string trimAsciiWhitespace(const std::string &value)
{
const auto first = std::find_if_not(value.begin(), value.end(),
[](unsigned char c)
{ return std::isspace(c) != 0; });
if (first == value.end())
{
return {};
}
const auto last = std::find_if_not(value.rbegin(), value.rend(),
[](unsigned char c)
{ return std::isspace(c) != 0; })
.base();
return std::string(first, last);
}
bool tryParseU32AddressLiteral(const std::string &literal, uint32_t &outAddress)
{
if (literal.empty())
{
return false;
}
try
{
size_t parsedCount = 0;
const unsigned long parsed = std::stoul(literal, &parsedCount, 0);
if (parsedCount != literal.size() || parsed > std::numeric_limits<uint32_t>::max())
{
return false;
}
outAddress = static_cast<uint32_t>(parsed);
return true;
}
catch (...)
{
return false;
}
}
struct FunctionSelector
{
std::string name;
std::optional<uint32_t> start;
};
FunctionSelector parseFunctionSelector(const std::string &rawSelector)
{
FunctionSelector selector{};
const std::string trimmed = trimAsciiWhitespace(rawSelector);
if (trimmed.empty())
{
return selector;
}
const std::size_t at = trimmed.rfind('@');
if (at != std::string::npos)
{
selector.name = trimAsciiWhitespace(trimmed.substr(0, at));
uint32_t parsedAddress = 0;
const std::string addressLiteral = trimAsciiWhitespace(trimmed.substr(at + 1));
if (tryParseU32AddressLiteral(addressLiteral, parsedAddress))
{
selector.start = parsedAddress;
return selector;
}
// for now backward compatibility
selector.name = trimmed;
return selector;
}
uint32_t parsedAddress = 0;
if (tryParseU32AddressLiteral(trimmed, parsedAddress))
{
selector.start = parsedAddress;
return selector;
}
selector.name = trimmed;
return selector;
}
}
@@ -107,11 +250,27 @@ namespace ps2recomp
for (const auto &name : m_config.skipFunctions)
{
m_skipFunctions[name] = true;
const FunctionSelector selector = parseFunctionSelector(name);
if (!selector.name.empty())
{
m_skipFunctions[selector.name] = true;
}
if (selector.start.has_value())
{
m_skipFunctionStarts.insert(*selector.start);
}
}
for (const auto &name : m_config.stubImplementations)
{
m_stubFunctions.insert(name);
const FunctionSelector selector = parseFunctionSelector(name);
if (!selector.name.empty())
{
m_stubFunctions.insert(selector.name);
}
if (selector.start.has_value())
{
m_stubFunctionStarts.insert(*selector.start);
}
}
m_elfParser = std::make_unique<ElfParser>(m_config.inputPath);
@@ -222,16 +381,18 @@ namespace ps2recomp
{
std::cout << "processing function: " << function.name << std::endl;
if (isStubFunction(function.name))
if (isStubFunction(function))
{
function.isStub = true;
function.isSkipped = false;
continue;
}
if (shouldSkipFunction(function.name))
if (shouldSkipFunction(function))
{
std::cout << "Skipping function (stubbed): " << function.name << std::endl;
function.isStub = true;
std::cout << "Skipping function (runtime TODO wrapper): " << function.name << std::endl;
function.isSkipped = true;
function.isStub = false;
continue;
}
@@ -239,6 +400,7 @@ namespace ps2recomp
{
++failedCount;
std::cerr << "Skipping function due decode failure: " << function.name << std::endl;
function.isSkipped = true;
continue;
}
@@ -280,40 +442,19 @@ namespace ps2recomp
std::string sanitized = sanitizeFunctionName(function.name);
if (sanitized.empty())
{
std::stringstream ss;
ss << "func_" << std::hex << function.start;
sanitized = ss.str();
sanitized = "func";
}
return sanitized;
std::stringstream ss;
ss << sanitized << "_0x" << std::hex << function.start;
return ss.str();
};
std::unordered_map<std::string, int> nameCounts;
for (const auto &function : m_functions)
{
if (!function.isRecompiled && !function.isStub)
continue;
std::string sanitized = makeName(function);
nameCounts[sanitized]++;
}
for (const auto &function : m_functions)
{
if (!function.isRecompiled && !function.isStub)
if (!shouldGenerateCodeForFunction(function))
continue;
std::string sanitized = makeName(function);
bool isDuplicate = nameCounts[sanitized] > 1;
std::stringstream ss;
if (isDuplicate)
{
ss << sanitized << "_0x" << std::hex << function.start;
}
else
{
ss << sanitized;
}
m_functionRenames[function.start] = ss.str();
m_functionRenames[function.start] = makeName(function);
}
if (m_codeGenerator)
@@ -345,24 +486,31 @@ namespace ps2recomp
m_generatedStubs.clear();
for (const auto &function : m_functions)
{
if (function.isStub)
if (function.isStub || function.isSkipped)
{
std::string generatedName = m_codeGenerator->getFunctionName(function.start);
std::stringstream stub;
stub << "void " << generatedName
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) { ";
switch (resolveStubTarget(function.name))
if (function.isSkipped)
{
case StubTarget::Syscall:
stub << "ps2_syscalls::" << function.name << "(rdram, ctx, runtime); ";
break;
case StubTarget::Stub:
stub << "ps2_stubs::" << function.name << "(rdram, ctx, runtime); ";
break;
default:
stub << "ps2_stubs::TODO(rdram, ctx, runtime); ";
break;
stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(function.name) << "\", rdram, ctx, runtime); ";
}
else
{
switch (resolveStubTarget(function.name))
{
case StubTarget::Syscall:
stub << "ps2_syscalls::" << function.name << "(rdram, ctx, runtime); ";
break;
case StubTarget::Stub:
stub << "ps2_stubs::" << function.name << "(rdram, ctx, runtime); ";
break;
default:
stub << "ps2_stubs::TODO_NAMED(\"" << escapeCStringLiteral(function.name) << "\", rdram, ctx, runtime); ";
break;
}
}
stub << "}";
@@ -382,22 +530,18 @@ namespace ps2recomp
combinedOutput << "#include \"ps2_recompiled_stubs.h\"\n";
combinedOutput << "#include \"ps2_syscalls.h\"\n";
combinedOutput << "#include \"ps2_stubs.h\"\n";
if (m_bootstrapInfo.valid)
{
combinedOutput << "\n"
<< m_codeGenerator->generateBootstrapFunction() << "\n\n";
}
combinedOutput << "\n";
for (const auto &function : m_functions)
{
if (!function.isRecompiled && !function.isStub)
if (!shouldGenerateCodeForFunction(function))
{
continue;
}
try
{
if (function.isStub)
if (function.isStub || function.isSkipped)
{
combinedOutput << m_generatedStubs.at(function.start) << "\n\n";
}
@@ -419,25 +563,17 @@ namespace ps2recomp
}
fs::path outputPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.cpp";
writeToFile(outputPath.string(), combinedOutput.str());
if (!writeToFile(outputPath.string(), combinedOutput.str()))
{
throw std::runtime_error("Failed to write combined output: " + outputPath.string());
}
std::cout << "Wrote recompiled to combined output to: " << outputPath << std::endl;
}
else
{
if (m_bootstrapInfo.valid)
{
std::stringstream boot;
boot << "#include \"ps2_recompiled_functions.h\"\n\n";
boot << "#include \"ps2_runtime_macros.h\"\n";
boot << "#include \"ps2_runtime.h\"\n\n";
boot << m_codeGenerator->generateBootstrapFunction() << "\n";
fs::path bootPath = fs::path(m_config.outputPath) / "ps2_entry_bootstrap.cpp";
writeToFile(bootPath.string(), boot.str());
}
for (const auto &function : m_functions)
{
if (!function.isRecompiled && !function.isStub)
if (!shouldGenerateCodeForFunction(function))
{
continue;
}
@@ -445,7 +581,7 @@ namespace ps2recomp
std::string code;
try
{
if (function.isStub)
if (function.isStub || function.isSkipped)
{
std::stringstream stubFile;
stubFile << "#include \"ps2_runtime.h\"\n";
@@ -471,7 +607,10 @@ namespace ps2recomp
fs::path outputPath = getOutputPath(function);
fs::create_directories(outputPath.parent_path());
writeToFile(outputPath.string(), code);
if (!writeToFile(outputPath.string(), code))
{
throw std::runtime_error("Failed to write function output: " + outputPath.string());
}
}
std::cout << "Wrote individual function files to: " << m_config.outputPath << std::endl;
@@ -480,7 +619,10 @@ namespace ps2recomp
std::string registerFunctions = m_codeGenerator->generateFunctionRegistration(m_functions, m_generatedStubs);
fs::path registerPath = fs::path(m_config.outputPath) / "register_functions.cpp";
writeToFile(registerPath.string(), registerFunctions);
if (!writeToFile(registerPath.string(), registerFunctions))
{
throw std::runtime_error("Failed to write function registration file: " + registerPath.string());
}
std::cout << "Generated function registration file: " << registerPath << std::endl;
generateStubHeader();
@@ -505,12 +647,25 @@ namespace ps2recomp
// ss << "namespace stubs {\n\n";
std::unordered_set<std::string> stubNames;
stubNames.insert(m_config.skipFunctions.begin(), m_config.skipFunctions.end());
stubNames.insert(m_config.stubImplementations.begin(), m_config.stubImplementations.end());
for (const auto &funcName : stubNames)
for (const auto &function : m_functions)
{
ss << "void " << sanitizeFunctionName(funcName) << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n";
if (!function.isStub && !function.isSkipped)
{
continue;
}
const std::string generatedName = m_codeGenerator->getFunctionName(function.start);
if (generatedName.empty())
{
continue;
}
if (!stubNames.insert(generatedName).second)
{
continue;
}
ss << "void " << generatedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime* runtime);\n";
}
// ss << "\n} // namespace stubs\n";
@@ -544,7 +699,7 @@ namespace ps2recomp
for (const auto &function : m_functions)
{
if (!function.isRecompiled && !function.isStub)
if (!shouldGenerateCodeForFunction(function))
{
continue;
}
@@ -554,12 +709,6 @@ namespace ps2recomp
ss << "void " << finalName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n";
}
if (m_bootstrapInfo.valid)
{
ss << "void entry_" << std::hex << m_bootstrapInfo.entry << std::dec
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);\n";
}
ss << "\n#endif // PS2_RECOMPILED_FUNCTIONS_H\n";
fs::path headerPath = fs::path(m_config.outputPath) / "ps2_recompiled_functions.h";
@@ -583,7 +732,7 @@ namespace ps2recomp
existingStarts.insert(function.start);
}
auto getStaticBranchTarget = [](const Instruction &inst) -> std::optional<uint32_t>
auto getStaticEntryTarget = [](const Instruction &inst) -> std::optional<uint32_t>
{
if (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL)
{
@@ -596,12 +745,6 @@ namespace ps2recomp
return std::nullopt;
}
if (inst.isBranch)
{
int32_t offset = static_cast<int32_t>(inst.simmediate) << 2;
return inst.address + 4 + offset;
}
return std::nullopt;
};
@@ -621,7 +764,7 @@ namespace ps2recomp
for (const auto &function : m_functions)
{
if (!function.isRecompiled || function.isStub)
if (!function.isRecompiled || function.isStub || function.isSkipped)
{
continue;
}
@@ -636,7 +779,7 @@ namespace ps2recomp
for (const auto &inst : instructions)
{
auto targetOpt = getStaticBranchTarget(inst);
auto targetOpt = getStaticEntryTarget(inst);
if (!targetOpt.has_value())
{
continue;
@@ -655,7 +798,13 @@ namespace ps2recomp
}
const Function *containingFunction = findContainingFunction(target);
if (!containingFunction || containingFunction->isStub || !containingFunction->isRecompiled)
if (!containingFunction || containingFunction->isStub || containingFunction->isSkipped || !containingFunction->isRecompiled)
{
continue;
}
// Internal branches within the same function are handled as labels/gotos and should not produce separate entry wrappers.
if (containingFunction->start == function.start)
{
continue;
}
@@ -687,6 +836,7 @@ namespace ps2recomp
entryFunction.end = containingFunction->end;
entryFunction.isRecompiled = true;
entryFunction.isStub = false;
entryFunction.isSkipped = false;
newEntries.push_back(entryFunction);
existingStarts.insert(target);
@@ -727,25 +877,37 @@ namespace ps2recomp
}
uint32_t rawInstruction = m_elfParser->readWord(address);
const uint32_t originalInstruction = rawInstruction;
auto patchIt = m_config.patches.find(address);
if (patchIt != m_config.patches.end())
{
try
const PatchClass patchClass = classifyPatchedInstruction(originalInstruction);
if (shouldApplyConfiguredPatch(patchClass, m_config))
{
rawInstruction = std::stoul(patchIt->second, nullptr, 0);
std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "Invalid patch value at 0x" << std::hex << address << std::dec
<< " (" << patchIt->second << "): " << e.what()
<< ". Using original instruction." << std::endl;
try
{
rawInstruction = std::stoul(patchIt->second, nullptr, 0);
std::cout << "Applied patch at 0x" << std::hex << address << std::dec << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "Invalid patch value at 0x" << std::hex << address << std::dec
<< " (" << patchIt->second << "): " << e.what()
<< ". Using original instruction." << std::endl;
}
}
}
Instruction inst = m_decoder->decodeInstruction(address, rawInstruction);
auto mmioIt = m_config.mmioByInstructionAddress.find(address);
if (mmioIt != m_config.mmioByInstructionAddress.end())
{
inst.isMmio = true;
inst.mmioAddress = mmioIt->second;
}
instructions.push_back(inst);
}
catch (const std::exception &e)
@@ -775,18 +937,28 @@ namespace ps2recomp
return true;
}
bool PS2Recompiler::shouldSkipFunction(const std::string &name) const
bool PS2Recompiler::shouldSkipFunction(const Function &function) const
{
return m_skipFunctions.contains(name);
}
bool PS2Recompiler::isStubFunction(const std::string &name) const
{
if (m_stubFunctions.contains(name))
if (m_skipFunctionStarts.contains(function.start))
{
return true;
}
return ps2_runtime_calls::isStubName(name);
return m_skipFunctions.contains(function.name);
}
bool PS2Recompiler::isStubFunction(const Function &function) const
{
if (m_stubFunctionStarts.contains(function.start))
{
return true;
}
if (m_stubFunctions.contains(function.name))
{
return true;
}
return ps2_runtime_calls::isStubName(function.name);
}
bool PS2Recompiler::writeToFile(const std::string &path, const std::string &content)
@@ -864,4 +1036,17 @@ namespace ps2recomp
return sanitized;
}
StubTarget PS2Recompiler::resolveStubTarget(const std::string &name)
{
if (ps2_runtime_calls::isSyscallName(name))
{
return StubTarget::Syscall;
}
if (ps2_runtime_calls::isStubName(name))
{
return StubTarget::Stub;
}
return StubTarget::Unknown;
}
}
+3 -1
View File
@@ -14,7 +14,7 @@ namespace ps2recomp
}
Instruction R5900Decoder::decodeInstruction(uint32_t address, uint32_t rawInstruction) const
{
{
Instruction inst;
inst.address = address;
@@ -39,12 +39,14 @@ namespace ps2recomp
inst.isMultimedia = false;
inst.isLoad = false;
inst.isStore = false;
inst.isMmio = false;
// Initialize the enhanced fields
inst.mmiType = 0;
inst.mmiFunction = 0;
inst.pmfhlVariation = 0;
inst.vuFunction = 0;
inst.mmioAddress = 0;
inst.vectorInfo.isVector = false;
inst.vectorInfo.usesQReg = false;
+3
View File
@@ -111,6 +111,9 @@
X(_printf) \
X(_printf_r) \
X(abs) \
X(__ieee754_rem_pio2f) \
X(__kernel_cosf) \
X(__kernel_sinf) \
X(atan) \
X(atan2) \
X(calloc) \
+419 -113
View File
@@ -15,53 +15,67 @@
#include <smmintrin.h> // For SSE4.1 instructions
#endif
#include <atomic>
#include <mutex>
#include <filesystem>
#include <iostream>
#include <iomanip>
constexpr uint32_t PS2_RAM_SIZE = 32 * 1024 * 1024; // 32MB
constexpr uint32_t PS2_RAM_MASK = 0x1FFFFFF; // Mask for 32MB alignment
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
constexpr uint32_t PS2_RAM_SIZE = 32u * 1024u * 1024u; // 32MB
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1u; // Mask for 32MB alignment
constexpr uint32_t PS2_RAM_BASE = 0x00000000; // Physical base of RDRAM
constexpr uint32_t PS2_SCRATCHPAD_BASE = 0x70000000;
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16 * 1024; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG
constexpr uint32_t PS2_BIOS_SIZE = 4 * 1024 * 1024; // 4MB
constexpr uint32_t PS2_SCRATCHPAD_SIZE = 16u * 1024u; // 16KB
constexpr uint32_t PS2_IO_BASE = 0x10000000; // Base for many I/O regs (Timers, DMAC, INTC)
constexpr uint32_t PS2_IO_SIZE = 0x10000; // 64KB
constexpr uint32_t PS2_BIOS_BASE = 0x1FC00000; // Or BFC00000 depending on KSEG
constexpr uint32_t PS2_BIOS_SIZE = 4u * 1024u * 1024u; // 4MB
constexpr uint32_t PS2_VU0_CODE_BASE = 0x11000000; // Base address as seen from EE
constexpr uint32_t PS2_VU0_DATA_BASE = 0x11004000;
constexpr uint32_t PS2_VU0_CODE_SIZE = 4 * 1024; // 4KB Micro Memory
constexpr uint32_t PS2_VU0_DATA_SIZE = 4 * 1024; // 4KB Data Memory (VU Mem)
constexpr uint32_t PS2_VU0_CODE_SIZE = 4u * 1024u; // 4KB Micro Memory
constexpr uint32_t PS2_VU0_DATA_SIZE = 4u * 1024u; // 4KB Data Memory (VU Mem)
constexpr uint32_t PS2_VU1_MEM_BASE = 0x11008000; // Base address as seen from EE
constexpr uint32_t PS2_VU1_CODE_SIZE = 16 * 1024; // 16KB Micro Memory
constexpr uint32_t PS2_VU1_DATA_SIZE = 16 * 1024; // 16KB Data Memory (VU Mem)
constexpr uint32_t PS2_VU1_CODE_BASE = 0x11008000;
constexpr uint32_t PS2_VU1_DATA_BASE = 0x1100C000;
constexpr uint32_t PS2_VU1_MEM_BASE = PS2_VU1_CODE_BASE; // Alias used by older code paths
constexpr uint32_t PS2_VU1_CODE_SIZE = 16u * 1024u; // 16KB Micro Memory
constexpr uint32_t PS2_VU1_DATA_SIZE = 16u * 1024u; // 16KB Data Memory (VU Mem)
constexpr uint32_t PS2_GS_BASE = 0x12000000;
constexpr uint32_t PS2_GS_PRIV_REG_BASE = 0x12000000; // GS Privileged Registers
constexpr uint32_t PS2_GS_PRIV_REG_BASE = PS2_GS_BASE; // GS Privileged Registers
constexpr uint32_t PS2_GS_PRIV_REG_SIZE = 0x2000;
constexpr size_t PS2_GS_VRAM_SIZE = 4 * 1024 * 1024; // 4MB GS VRAM
constexpr size_t PS2_GS_VRAM_SIZE = 4u * 1024u * 1024u; // 4MB GS VRAM
#define PS2_FIO_O_RDONLY 0x0001
#define PS2_FIO_O_WRONLY 0x0002
#define PS2_FIO_O_RDWR 0x0003
#define PS2_FIO_O_APPEND 0x0100
#define PS2_FIO_O_CREAT 0x0200
#define PS2_FIO_O_TRUNC 0x0400
#define PS2_FIO_O_EXCL 0x0800
inline constexpr uint32_t PS2_FIO_O_RDONLY = 0x0001;
inline constexpr uint32_t PS2_FIO_O_WRONLY = 0x0002;
inline constexpr uint32_t PS2_FIO_O_RDWR = 0x0003;
inline constexpr uint32_t PS2_FIO_O_NBLOCK = 0x0010;
inline constexpr uint32_t PS2_FIO_O_APPEND = 0x0100;
inline constexpr uint32_t PS2_FIO_O_CREAT = 0x0200;
inline constexpr uint32_t PS2_FIO_O_TRUNC = 0x0400;
inline constexpr uint32_t PS2_FIO_O_EXCL = 0x0800;
inline constexpr uint32_t PS2_FIO_O_NOWAIT = 0x8000;
#define PS2_FIO_SEEK_SET 0
#define PS2_FIO_SEEK_CUR 1
#define PS2_FIO_SEEK_END 2
inline constexpr uint32_t PS2_FIO_SEEK_SET = 0;
inline constexpr uint32_t PS2_FIO_SEEK_CUR = 1;
inline constexpr uint32_t PS2_FIO_SEEK_END = 2;
#define PS2_FIO_S_IFDIR 0x1000
#define PS2_FIO_S_IFREG 0x2000
inline constexpr uint32_t PS2_FIO_S_IFDIR = 0x1000;
inline constexpr uint32_t PS2_FIO_S_IFREG = 0x2000;
static_assert((PS2_RAM_SIZE & (PS2_RAM_SIZE - 1u)) == 0u, "PS2_RAM_SIZE must be a power of two");
static_assert(PS2_RAM_MASK == (PS2_RAM_SIZE - 1u), "PS2_RAM_MASK must match PS2_RAM_SIZE");
enum PS2Exception
{
EXCEPTION_TLB_REFILL = 0x02, // TLB refill/load exception
EXCEPTION_ADDRESS_ERROR_LOAD = 0x04, // Address error on load
EXCEPTION_ADDRESS_ERROR_STORE = 0x05, // Address error on store
EXCEPTION_SYSCALL = 0x08, // SYSCALL instruction
EXCEPTION_BREAKPOINT = 0x09, // BREAK instruction
EXCEPTION_RESERVED_INSTRUCTION = 0x0A,
EXCEPTION_INTEGER_OVERFLOW = 0x0C, // From MIPS spec
EXCEPTION_TRAP = 0x0D, // Trap instruction condition met
};
// PS2 CPU context (R5900)
@@ -106,7 +120,7 @@ struct alignas(16) R5900Context
uint32_t vu0_itop;
uint32_t vu0_info;
uint32_t vu0_xitop; // VU0 XITOP - input ITOP for VIF/VU sync
uint32_t vu0_pc;
uint32_t vu0_pc;
float vu0_cf[4]; // VU0 FMAC control floating-point registers
@@ -134,6 +148,10 @@ struct alignas(16) R5900Context
uint32_t cop0_taghi;
uint32_t cop0_errorepc;
// LL/SC reservation state (not part of COP0 Status bits).
uint32_t llbit;
uint32_t lladdr;
// COP2 control registers (VU0 integer + control)
uint32_t cop2_ccr[32];
@@ -143,81 +161,18 @@ struct alignas(16) R5900Context
R5900Context()
{
for (int i = 0; i < 32; i++)
{
r[i] = _mm_setzero_si128();
f[i] = 0.0f;
vu0_vf[i] = _mm_setzero_ps();
}
for (int i = 0; i < 4; i++)
{
vu0_cf[i] = 0.0f;
}
for (int i = 0; i < 16; ++i)
{
vi[i] = 0;
}
pc = 0;
insn_count = 0;
lo = hi = lo1 = hi1 = 0;
sa = 0;
std::memset(this, 0, sizeof(*this));
// Initialize VU0 registers
vu0_q = 1.0f; // Q register usually initialized to 1.0
vu0_p = 0.0f;
vu0_i = 0.0f;
vu0_r = _mm_setzero_ps();
vu0_acc = _mm_setzero_ps();
vu0_status = 0;
vu0_mac_flags = 0;
vu0_clip_flags = 0;
vu0_cmsar0 = 0;
vu0_fbrst = 0;
vu0_fbrst2 = 0;
vu0_fbrst3 = 0;
vu0_fbrst4 = 0;
vu0_xitop = 0;
vu0_pc = 0;
vu0_tpc = 0;
vu0_vpu_stat2 = 0;
vu0_tpc2 = 0;
vu0_cmsar1 = 0;
vu0_vpu_stat3 = 0;
vu0_cmsar2 = 0;
vu0_vpu_stat4 = 0;
vu0_itop = 0;
vu0_info = 0;
// Reset COP0 registers
cop0_index = 0;
cop0_random = 47; // Start at maximum value
cop0_entrylo0 = 0;
cop0_entrylo1 = 0;
cop0_context = 0;
cop0_pagemask = 0;
cop0_wired = 0;
cop0_badvaddr = 0;
cop0_count = 0;
cop0_entryhi = 0;
cop0_compare = 0;
cop0_status = 0x400000; // BEV set, ERL clear, kernel mode
cop0_cause = 0;
cop0_epc = 0;
// cop0_status = 0x400000; // BEV set, ERL clear, kernel mode
// 0x00400000 = BEV (Boot Exception Vectors).
// 0x00000000 = Normal mode (after BIOS handoff).
cop0_status = 0x00000000;
cop0_prid = 0x00002e20; // CPU ID for R5900
cop0_config = 0;
cop0_badpaddr = 0;
cop0_debug = 0;
cop0_perf = 0;
cop0_taglo = 0;
cop0_taghi = 0;
cop0_errorepc = 0;
// Reset COP1 state
fcr31 = 0;
}
void dump() const
@@ -233,9 +188,9 @@ struct alignas(16) R5900Context
{
std::cout << "R" << std::setw(2) << std::dec << i << ": 0x" << std::hex
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 3))
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 2)) << "_"
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 2)) << "_"
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 1))
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 0)) << "\n";
<< std::setw(8) << static_cast<uint32_t>(_mm_extract_epi32(r[i], 0)) << "\n";
}
std::cout << "Status: 0x" << std::setw(8) << cop0_status
<< " Cause: 0x" << std::setw(8) << cop0_cause
@@ -252,36 +207,276 @@ inline uint32_t getRegU32(const R5900Context *ctx, int reg)
// Check if reg is valid (0-31)
if (reg < 0 || reg > 31)
return 0;
if (reg == 0)
return 0;
return static_cast<uint32_t>(_mm_extract_epi32(ctx->r[reg], 0));
}
inline void setReturnU32(R5900Context *ctx, uint32_t value)
{
ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0
// Keep low 64-bits coherent for helpers that read GPRs as 64-bit.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value)); // $v0
}
inline void setReturnS32(R5900Context *ctx, int32_t value)
{
ctx->r[2] = _mm_set_epi32(0, 0, 0, value); // $v0 Sign extension handled by cast? TODO Check MIPS ABI.
// Signed 32-bit return should be sign-extended when observed as 64-bit.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value)); // $v0
}
inline void setReturnU64(R5900Context *ctx, uint64_t value)
{
// 64-bit returns use $v0/$v1 (r2/r3)
ctx->r[2] = _mm_set_epi32(0, 0, 0, static_cast<uint32_t>(value));
ctx->r[3] = _mm_set_epi32(0, 0, 0, static_cast<uint32_t>(value >> 32));
// Keep both conventions: full 64-bit value in $v0 and high 32-bit in $v1.
ctx->r[2] = _mm_set_epi64x(0, static_cast<int64_t>(value));
ctx->r[3] = _mm_set_epi64x(0, static_cast<int64_t>(static_cast<uint32_t>(value >> 32)));
}
inline constexpr uint32_t PS2_PATH_WATCH_ADDR = 0x00369F2Fu;
inline constexpr uint32_t PS2_PATH_WATCH_BYTES = 32u;
inline constexpr uint32_t PS2_PATH_WATCH_MAX_LOGS = 512u;
inline std::atomic<uint32_t> g_ps2PathWatchLogCount{0};
inline uint32_t ps2PathWatchPhysAddr()
{
return PS2_PATH_WATCH_ADDR & PS2_RAM_MASK;
}
inline bool ps2PathWatchIntersects(uint32_t writeAddr, uint32_t writeSize)
{
const uint64_t writeStart = writeAddr;
const uint64_t writeEnd = writeStart + static_cast<uint64_t>(writeSize);
const uint64_t watchStart = ps2PathWatchPhysAddr();
const uint64_t watchEnd = watchStart + static_cast<uint64_t>(PS2_PATH_WATCH_BYTES);
return writeEnd > watchStart && writeStart < watchEnd;
}
inline void ps2PathWatchDumpPrefix(const uint8_t *rdram)
{
if (!rdram)
{
return;
}
const uint32_t base = ps2PathWatchPhysAddr();
auto flags = std::cout.flags();
std::cout << " buf=" << std::hex;
for (uint32_t i = 0; i < 16u; ++i)
{
const uint32_t addr = (base + i) & PS2_RAM_MASK;
std::cout << static_cast<uint32_t>(rdram[addr]);
if (i + 1u < 16u)
{
std::cout << '.';
}
}
std::cout.flags(flags);
}
inline uint8_t ps2PathWatchExtractByteFromWrite(uint32_t writeAddr, uint32_t watchAddr, uint64_t valueLo, uint64_t valueHi)
{
const uint32_t byteIndex = watchAddr - writeAddr;
if (byteIndex < 8u)
{
return static_cast<uint8_t>((valueLo >> (byteIndex * 8u)) & 0xFFu);
}
return static_cast<uint8_t>((valueHi >> ((byteIndex - 8u) * 8u)) & 0xFFu);
}
inline void ps2TraceGuestWrite(uint8_t *rdram,
uint32_t guestAddr,
uint32_t size,
uint64_t valueLo,
uint64_t valueHi,
const char *op,
const R5900Context *ctx)
{
if (!rdram || size == 0u)
{
return;
}
const uint32_t writeAddr = guestAddr & PS2_RAM_MASK;
if (!ps2PathWatchIntersects(writeAddr, size))
{
return;
}
const uint32_t logIndex = g_ps2PathWatchLogCount.fetch_add(1, std::memory_order_relaxed);
if (logIndex >= PS2_PATH_WATCH_MAX_LOGS)
{
return;
}
const uint32_t watchAddr = ps2PathWatchPhysAddr();
const bool touchesFirstByte = (watchAddr >= writeAddr) && (watchAddr < writeAddr + size);
const uint8_t oldByte = rdram[watchAddr];
const uint8_t newByte = touchesFirstByte ? ps2PathWatchExtractByteFromWrite(writeAddr, watchAddr, valueLo, valueHi) : oldByte;
const uint32_t pc = ctx ? ctx->pc : 0u;
const uint32_t ra = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)) : 0u;
const uint32_t sp = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)) : 0u;
auto flags = std::cout.flags();
std::cout << "[watch:path-write] #" << (logIndex + 1u)
<< " op=" << op
<< " addr=0x" << std::hex << writeAddr
<< " size=0x" << size
<< " pc=0x" << pc
<< " ra=0x" << ra
<< " sp=0x" << sp
<< " vLo=0x" << valueLo;
if (size > 8u)
{
std::cout << " vHi=0x" << valueHi;
}
if (touchesFirstByte)
{
std::cout << " firstByte:" << static_cast<uint32_t>(oldByte)
<< "->" << static_cast<uint32_t>(newByte);
if (oldByte != 0u && newByte == 0u)
{
std::cout << " (ZEROED)";
}
}
ps2PathWatchDumpPrefix(rdram);
std::cout.flags(flags);
std::cout << std::endl;
}
inline void ps2TraceGuestRangeWrite(uint8_t *rdram,
uint32_t guestAddr,
uint32_t size,
const char *op,
const R5900Context *ctx)
{
if (!rdram || size == 0u)
{
return;
}
const uint32_t writeAddr = guestAddr & PS2_RAM_MASK;
if (!ps2PathWatchIntersects(writeAddr, size))
{
return;
}
const uint32_t logIndex = g_ps2PathWatchLogCount.fetch_add(1, std::memory_order_relaxed);
if (logIndex >= PS2_PATH_WATCH_MAX_LOGS)
{
return;
}
const uint32_t pc = ctx ? ctx->pc : 0u;
const uint32_t ra = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)) : 0u;
const uint32_t sp = ctx ? static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)) : 0u;
const uint8_t firstByte = rdram[ps2PathWatchPhysAddr()];
auto flags = std::cout.flags();
std::cout << "[watch:path-range] #" << (logIndex + 1u)
<< " op=" << op
<< " addr=0x" << std::hex << writeAddr
<< " size=0x" << size
<< " pc=0x" << pc
<< " ra=0x" << ra
<< " sp=0x" << sp
<< " firstByte=" << static_cast<uint32_t>(firstByte);
ps2PathWatchDumpPrefix(rdram);
std::cout.flags(flags);
std::cout << std::endl;
}
inline std::atomic<uint8_t *> &ps2ScratchpadHostPtrStorage()
{
static std::atomic<uint8_t *> ptr{nullptr};
return ptr;
}
inline void ps2SetScratchpadHostPtr(uint8_t *ptr)
{
ps2ScratchpadHostPtrStorage().store(ptr, std::memory_order_relaxed);
}
inline uint8_t *ps2GetScratchpadHostPtr()
{
return ps2ScratchpadHostPtrStorage().load(std::memory_order_relaxed);
}
inline bool ps2ResolveGuestPointer(uint32_t addr, uint32_t &offset, bool &scratch)
{
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
{
scratch = true;
offset = addr - PS2_SCRATCHPAD_BASE;
return true;
}
uint32_t phys = 0;
if (addr < 0x20000000u)
{
phys = addr;
}
else if ((addr >= 0x20000000u && addr < 0x40000000u) ||
(addr >= 0x80000000u && addr < 0xC0000000u))
{
phys = addr & 0x1FFFFFFFu;
}
else
{
// Keep legacy runtime behavior for odd upper-bit aliases used by game code.
phys = addr & PS2_RAM_MASK;
}
if (phys >= PS2_RAM_SIZE)
{
phys &= PS2_RAM_MASK;
}
scratch = false;
offset = phys;
return true;
}
inline uint8_t *getMemPtr(uint8_t *rdram, uint32_t addr)
{
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1;
return rdram + (addr & PS2_RAM_MASK);
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
inline const uint8_t *getConstMemPtr(uint8_t *rdram, uint32_t addr)
inline const uint8_t *getConstMemPtr(const uint8_t *rdram, uint32_t addr)
{
constexpr uint32_t PS2_RAM_MASK = PS2_RAM_SIZE - 1;
return rdram + (addr & PS2_RAM_MASK);
if (rdram == nullptr)
{
return nullptr;
}
uint32_t offset = 0;
bool scratch = false;
if (!ps2ResolveGuestPointer(addr, offset, scratch))
{
return nullptr;
}
if (scratch)
{
const uint8_t *scratchpad = ps2GetScratchpadHostPtr();
return scratchpad ? (scratchpad + offset) : nullptr;
}
return rdram + offset;
}
// PS2 GS (Graphics Synthesizer) registers
@@ -307,6 +502,8 @@ struct GSRegisters
uint64_t busdir; // Bus direction
uint64_t siglblid; // Signal label ID
};
static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit");
// PS2 VIF (VPU Interface) registers
struct VIFRegisters
@@ -329,6 +526,7 @@ struct VIFRegisters
uint32_t row[4]; // Transfer row data
uint32_t col[4]; // Transfer column data
};
static_assert(sizeof(VIFRegisters) == (23u * sizeof(uint32_t)), "VIFRegisters layout changed unexpectedly");
// PS2 DMA registers
struct DMARegisters
@@ -341,11 +539,12 @@ struct DMARegisters
uint32_t asr1; // Address stack 1
uint32_t sadr; // Source address
};
static_assert(sizeof(DMARegisters) == (7u * sizeof(uint32_t)), "DMARegisters layout changed unexpectedly");
struct JumpTable
{
uint32_t address; // Base address of the jump table
uint32_t baseRegister; // Register used for index
uint32_t address = 0; // Base address of the jump table
uint32_t baseRegister = 0; // Register used for index
std::vector<uint32_t> targets; // Jump targets
};
@@ -355,6 +554,11 @@ public:
PS2Memory();
~PS2Memory();
PS2Memory(const PS2Memory &) = delete;
PS2Memory &operator=(const PS2Memory &) = delete;
PS2Memory(PS2Memory &&) = delete;
PS2Memory &operator=(PS2Memory &&) = delete;
// Initialize memory
bool initialize(size_t ramSize = PS2_RAM_SIZE);
@@ -382,6 +586,10 @@ public:
// TLB handling
uint32_t translateAddress(uint32_t virtualAddress);
bool tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const;
bool tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid);
int32_t tlbProbe(uint32_t vpn) const;
size_t tlbEntryCount() const { return m_tlbEntries.size(); }
// Hardware register interface
bool writeIORegister(uint32_t address, uint32_t value);
@@ -449,6 +657,15 @@ public:
class PS2Runtime
{
public:
struct IoPaths
{
std::filesystem::path elfPath;
std::filesystem::path elfDirectory;
std::filesystem::path hostRoot;
std::filesystem::path cdRoot;
std::filesystem::path cdImage;
};
PS2Runtime();
~PS2Runtime();
@@ -462,6 +679,10 @@ public:
RecompiledFunction lookupFunction(uint32_t address);
bool hasFunction(uint32_t address) const;
static const IoPaths &getIoPaths();
static void setIoPaths(const IoPaths &paths);
static void configureIoPathsFromElf(const std::string &elfPath);
void SignalException(R5900Context *ctx, PS2Exception exception);
void executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address);
@@ -469,6 +690,7 @@ public:
public:
void handleSyscall(uint8_t *rdram, R5900Context *ctx);
void handleSyscall(uint8_t *rdram, R5900Context *ctx, uint32_t encodedSyscallId);
void handleBreak(uint8_t *rdram, R5900Context *ctx);
void handleTrap(uint8_t *rdram, R5900Context *ctx);
@@ -477,6 +699,60 @@ public:
void handleTLBWR(uint8_t *rdram, R5900Context *ctx);
void handleTLBP(uint8_t *rdram, R5900Context *ctx);
void clearLLBit(R5900Context *ctx);
void configureGuestHeap(uint32_t guestBase, uint32_t guestLimit = PS2_RAM_SIZE);
uint32_t guestMalloc(uint32_t size, uint32_t alignment = 16u);
uint32_t guestCalloc(uint32_t count, uint32_t size, uint32_t alignment = 16u);
uint32_t guestRealloc(uint32_t guestAddr, uint32_t newSize, uint32_t alignment = 16u);
void guestFree(uint32_t guestAddr);
uint32_t guestHeapBase() const;
uint32_t guestHeapEnd() const;
void dispatchLoop(uint8_t *rdram, R5900Context *ctx);
void requestStop();
bool isStopRequested() const;
uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint32_t Load32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
uint64_t Load64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
__m128i Load128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
void Store8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint8_t value);
void Store16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint16_t value);
void Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value);
void Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value);
void Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value);
static inline bool isSpecialAddress(uint32_t addr)
{
// BIOS (physical + cached/uncached aliases)
if ((addr >= PS2_BIOS_BASE && addr < (PS2_BIOS_BASE + PS2_BIOS_SIZE)) ||
(addr >= 0xBFC00000u && addr < (0xBFC00000u + PS2_BIOS_SIZE)))
{
return true;
}
// Scratchpad (16KB)
if (addr >= PS2_SCRATCHPAD_BASE && addr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE))
return true;
// EE MMIO window (Timers, DMAC, INTC, etc)
if (addr >= PS2_IO_BASE && addr < (PS2_IO_BASE + PS2_IO_SIZE))
return true;
// GS privileged regs
if (addr >= PS2_GS_PRIV_REG_BASE && addr < (PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE))
return true;
// KSEG2/KSEG3 (TLB mapped)
if (addr >= 0xC0000000u)
return true;
// VU Memory (Micro/Data) mapped into EE space
if (addr >= PS2_VU0_CODE_BASE && addr < (PS2_VU1_DATA_BASE + PS2_VU1_DATA_SIZE))
return true;
return false;
}
public:
inline R5900Context &cpu() { return m_cpuContext; }
@@ -485,17 +761,47 @@ public:
inline PS2Memory &memory() { return m_memory; }
inline const PS2Memory &memory() const { return m_memory; }
public:
bool check_overflow = false;
private:
struct GuestHeapBlock
{
uint32_t addr = 0;
uint32_t size = 0;
bool free = true;
};
static uint32_t alignGuestHeapValue(uint32_t value, uint32_t alignment);
static bool isGuestHeapAlignmentValid(uint32_t alignment);
static uint32_t normalizeGuestHeapAlignment(uint32_t alignment);
uint32_t clampGuestHeapBase(uint32_t guestBase) const;
uint32_t clampGuestHeapLimit(uint32_t guestLimit) const;
void resetGuestHeapLocked(uint32_t guestBase, uint32_t guestLimit);
void ensureGuestHeapInitializedLocked();
int32_t findGuestHeapBlockIndexLocked(uint32_t guestAddr) const;
uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment);
void freeGuestBlockLocked(uint32_t guestAddr);
void coalesceGuestHeapLocked();
void HandleIntegerOverflow(R5900Context *ctx);
private:
PS2Memory m_memory;
R5900Context m_cpuContext;
mutable std::mutex m_guestHeapMutex;
std::vector<GuestHeapBlock> m_guestHeapBlocks;
uint32_t m_guestHeapBase = 0x00100000u;
uint32_t m_guestHeapEnd = 0x00100000u;
uint32_t m_guestHeapLimit = PS2_RAM_SIZE;
uint32_t m_guestHeapSuggestedBase = 0x00100000u;
bool m_guestHeapConfigured = false;
std::unordered_map<uint32_t, RecompiledFunction> m_functionTable;
std::atomic<bool> m_stopRequested{false};
// TODO remove this later
std::atomic<uint32_t> m_debugPc{0};
std::atomic<uint32_t> m_debugRa{0};
std::atomic<uint32_t> m_debugSp{0};
std::atomic<uint32_t> m_debugGp{0};
struct LoadedModule
{
+320 -92
View File
@@ -1,44 +1,85 @@
#ifndef PS2_RUNTIME_MACROS_H
#define PS2_RUNTIME_MACROS_H
#include <cstdint>
#include <bit>
#if defined(_MSC_VER)
#include <intrin.h>
#include <intrin.h>
#elif defined(USE_SSE2NEON)
#include "sse2neon.h"
#include "sse2neon.h"
#else
#include <immintrin.h> // For SSE/AVX intrinsics
#include <immintrin.h> // For SSE/AVX intrinsics
#endif
inline uint32_t ps2_clz32(uint32_t val) {
#if defined(_MSC_VER)
unsigned long idx;
if (_BitScanReverse(&idx, val)) {
return 31u - idx;
#include "ps2_runtime.h"
static inline int32_t Ps2ExtractEpi32(__m128i v, int index)
{
switch (index & 3)
{
case 0:
return _mm_extract_epi32(v, 0);
case 1:
return _mm_extract_epi32(v, 1);
case 2:
return _mm_extract_epi32(v, 2);
default:
return _mm_extract_epi32(v, 3);
}
return 32u;
#else
return val == 0 ? 32u : (uint32_t)__builtin_clz(val);
#endif
}
static inline int64_t Ps2ExtractEpi64(__m128i v, int index)
{
if ((index & 1) == 0)
{
return _mm_cvtsi128_si64(v);
}
else
{
return _mm_extract_epi64(v, 1);
}
}
static inline uint32_t ps2_clz32(uint32_t x)
{
return static_cast<uint32_t>(std::countl_zero(x));
}
#define PS2_BLENDV_PS(a, b, mask) _mm_blendv_ps((a), (b), (mask))
#define PS2_MIN_EPI32(a, b) _mm_min_epi32((a), (b))
#define PS2_MAX_EPI32(a, b) _mm_max_epi32((a), (b))
#define PS2_EXTRACT_EPI32(v, i) Ps2ExtractEpi32((v), (i))
#define PS2_EXTRACT_EPI64(v, i) Ps2ExtractEpi64((v), (i))
#define PS2_EXTRACT_EPI32_0(v) Ps2ExtractEpi32((v), 0)
#define PS2_EXTRACT_EPI32_1(v) Ps2ExtractEpi32((v), 1)
#define PS2_EXTRACT_EPI32_2(v) Ps2ExtractEpi32((v), 2)
#define PS2_EXTRACT_EPI32_3(v) Ps2ExtractEpi32((v), 3)
#define PS2_EXTRACT_EPI64_0(v) Ps2ExtractEpi64((v), 0)
#define PS2_EXTRACT_EPI64_1(v) Ps2ExtractEpi64((v), 1)
// Basic MIPS arithmetic operations
#define ADD32(a, b) ((uint32_t)((a) + (b)))
#define ADD32_OV(rs, rt, result32, overflow) \
do { \
int32_t _a = (int32_t)(rs); \
int32_t _b = (int32_t)(rt); \
int32_t _r = _a + _b; \
overflow = (((_a ^ _b) >= 0) && ((_a ^ _r) < 0)); \
result32 = (uint32_t)_r; \
} while (0);
#define ADD32_OV(rs, rt, result32, overflow) \
do \
{ \
int32_t _a = (int32_t)(rs); \
int32_t _b = (int32_t)(rt); \
int32_t _r = _a + _b; \
overflow = (((_a ^ _b) >= 0) && ((_a ^ _r) < 0)); \
result32 = (uint32_t)_r; \
} while (0);
#define SUB32(a, b) ((uint32_t)((a) - (b)))
#define SUB32_OV(rs, rt, result32, overflow) \
do { \
int32_t _a = (int32_t)(rs); \
int32_t _b = (int32_t)(rt); \
int32_t _r = _a - _b; \
overflow = (((_a ^ _b) < 0) && ((_a ^ _r) < 0)); \
result32 = (uint32_t)_r; \
} while (0);
#define SUB32_OV(rs, rt, result32, overflow) \
do \
{ \
int32_t _a = (int32_t)(rs); \
int32_t _b = (int32_t)(rt); \
int32_t _r = _a - _b; \
overflow = (((_a ^ _b) < 0) && ((_a ^ _r) < 0)); \
result32 = (uint32_t)_r; \
} while (0);
#define MUL32(a, b) ((uint32_t)((a) * (b)))
#define DIV32(a, b) ((uint32_t)((a) / (b)))
#define AND32(a, b) ((uint32_t)((a) & (b)))
@@ -60,8 +101,8 @@ inline uint32_t ps2_clz32(uint32_t val) {
#define PS2_PEXTUB(a, b) _mm_unpackhi_epi8((__m128i)(b), (__m128i)(a))
#define PS2_PADDW(a, b) _mm_add_epi32((__m128i)(a), (__m128i)(b))
#define PS2_PSUBW(a, b) _mm_sub_epi32((__m128i)(a), (__m128i)(b))
#define PS2_PMAXW(a, b) _mm_max_epi32((__m128i)(a), (__m128i)(b))
#define PS2_PMINW(a, b) _mm_min_epi32((__m128i)(a), (__m128i)(b))
#define PS2_PMAXW(a, b) PS2_MAX_EPI32((__m128i)(a), (__m128i)(b))
#define PS2_PMINW(a, b) PS2_MIN_EPI32((__m128i)(a), (__m128i)(b))
#define PS2_PADDH(a, b) _mm_add_epi16((__m128i)(a), (__m128i)(b))
#define PS2_PSUBH(a, b) _mm_sub_epi16((__m128i)(a), (__m128i)(b))
#define PS2_PMAXH(a, b) _mm_max_epi16((__m128i)(a), (__m128i)(b))
@@ -79,18 +120,175 @@ inline uint32_t ps2_clz32(uint32_t val) {
#define PS2_VMUL(a, b) _mm_mul_ps((__m128)(a), (__m128)(b))
#define PS2_VDIV(a, b) _mm_div_ps((__m128)(a), (__m128)(b))
#define PS2_VMULQ(a, q) _mm_mul_ps((__m128)(a), _mm_set1_ps(q))
#define PS2_VBLEND(a, b, mask) PS2_BLENDV_PS((__m128)(a), (__m128)(b), (__m128)(mask))
// Memory access helpers
#define READ8(addr) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
#define READ16(addr) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
#define READ32(addr) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
#define READ64(addr) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK)))
#define READ128(addr) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK))))
#define WRITE8(addr, val) (*(uint8_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
#define WRITE16(addr, val) (*(uint16_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
#define WRITE32(addr, val) (*(uint32_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
#define WRITE64(addr, val) (*(uint64_t*)((rdram) + ((addr) & PS2_RAM_MASK)) = (val))
#define WRITE128(addr, val) (*((__m128i*)((rdram) + ((addr) & PS2_RAM_MASK))) = (val))
// Memory access helpers - Hybrid Fast/Slow Path
// Fast path: Direct RDRAM access (masked).
// Slow path: Full runtime->Load/Store
static inline uint8_t Ps2FastRead8(const uint8_t *rdram, uint32_t addr)
{
return rdram[addr & PS2_RAM_MASK];
}
static inline uint16_t Ps2FastRead16(const uint8_t *rdram, uint32_t addr)
{
uint16_t value;
std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value));
return value;
}
static inline uint32_t Ps2FastRead32(const uint8_t *rdram, uint32_t addr)
{
uint32_t value;
std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value));
return value;
}
static inline uint64_t Ps2FastRead64(const uint8_t *rdram, uint32_t addr)
{
uint64_t value;
std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value));
return value;
}
static inline __m128i Ps2FastRead128(const uint8_t *rdram, uint32_t addr)
{
__m128i value;
std::memcpy(&value, rdram + (addr & PS2_RAM_MASK), sizeof(value));
return value;
}
static inline void Ps2FastWrite8(uint8_t *rdram, uint32_t addr, uint8_t value)
{
rdram[addr & PS2_RAM_MASK] = value;
}
static inline void Ps2FastWrite16(uint8_t *rdram, uint32_t addr, uint16_t value)
{
std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value));
}
static inline void Ps2FastWrite32(uint8_t *rdram, uint32_t addr, uint32_t value)
{
std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value));
}
static inline void Ps2FastWrite64(uint8_t *rdram, uint32_t addr, uint64_t value)
{
std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value));
}
static inline void Ps2FastWrite128(uint8_t *rdram, uint32_t addr, __m128i value)
{
std::memcpy(rdram + (addr & PS2_RAM_MASK), &value, sizeof(value));
}
#define FAST_READ8(addr) Ps2FastRead8(rdram, (uint32_t)(addr))
#define FAST_READ16(addr) Ps2FastRead16(rdram, (uint32_t)(addr))
#define FAST_READ32(addr) Ps2FastRead32(rdram, (uint32_t)(addr))
#define FAST_READ64(addr) Ps2FastRead64(rdram, (uint32_t)(addr))
#define FAST_READ128(addr) Ps2FastRead128(rdram, (uint32_t)(addr))
#define FAST_WRITE8(addr, val) Ps2FastWrite8(rdram, (uint32_t)(addr), (uint8_t)(val))
#define FAST_WRITE16(addr, val) Ps2FastWrite16(rdram, (uint32_t)(addr), (uint16_t)(val))
#define FAST_WRITE32(addr, val) Ps2FastWrite32(rdram, (uint32_t)(addr), (uint32_t)(val))
#define FAST_WRITE64(addr, val) Ps2FastWrite64(rdram, (uint32_t)(addr), (uint64_t)(val))
#define FAST_WRITE128(addr, val) Ps2FastWrite128(rdram, (uint32_t)(addr), (val))
#define READ8(addr) ([&]() -> uint8_t { \
uint32_t _addr = (uint32_t)(addr); \
return PS2Runtime::isSpecialAddress(_addr) \
? runtime->Load8(rdram, ctx, _addr) \
: FAST_READ8(_addr); }())
#define READ16(addr) ([&]() -> uint16_t { \
uint32_t _addr = (uint32_t)(addr); \
return PS2Runtime::isSpecialAddress(_addr) \
? runtime->Load16(rdram, ctx, _addr) \
: FAST_READ16(_addr); }())
#define READ32(addr) ([&]() -> uint32_t { \
uint32_t _addr = (uint32_t)(addr); \
return PS2Runtime::isSpecialAddress(_addr) \
? runtime->Load32(rdram, ctx, _addr) \
: FAST_READ32(_addr); }())
#define READ64(addr) ([&]() -> uint64_t { \
uint32_t _addr = (uint32_t)(addr); \
return PS2Runtime::isSpecialAddress(_addr) \
? runtime->Load64(rdram, ctx, _addr) \
: FAST_READ64(_addr); }())
#define READ128(addr) ([&]() -> __m128i { \
uint32_t _addr = (uint32_t)(addr); \
return PS2Runtime::isSpecialAddress(_addr) \
? runtime->Load128(rdram, ctx, _addr) \
: FAST_READ128(_addr); }())
#define WRITE8(addr, val) \
do \
{ \
uint32_t _addr = (addr); \
if (PS2Runtime::isSpecialAddress(_addr)) \
runtime->Store8(rdram, ctx, _addr, (val)); \
else \
{ \
ps2TraceGuestWrite(rdram, _addr, 1u, (uint8_t)(val), 0u, "WRITE8", ctx); \
FAST_WRITE8(_addr, (val)); \
} \
} while (0)
#define WRITE16(addr, val) \
do \
{ \
uint32_t _addr = (addr); \
if (PS2Runtime::isSpecialAddress(_addr)) \
runtime->Store16(rdram, ctx, _addr, (val)); \
else \
{ \
ps2TraceGuestWrite(rdram, _addr, 2u, (uint16_t)(val), 0u, "WRITE16", ctx); \
FAST_WRITE16(_addr, (val)); \
} \
} while (0)
#define WRITE32(addr, val) \
do \
{ \
uint32_t _addr = (addr); \
if (PS2Runtime::isSpecialAddress(_addr)) \
runtime->Store32(rdram, ctx, _addr, (val)); \
else \
{ \
ps2TraceGuestWrite(rdram, _addr, 4u, (uint32_t)(val), 0u, "WRITE32", ctx); \
FAST_WRITE32(_addr, (val)); \
} \
} while (0)
#define WRITE64(addr, val) \
do \
{ \
uint32_t _addr = (addr); \
if (PS2Runtime::isSpecialAddress(_addr)) \
runtime->Store64(rdram, ctx, _addr, (val)); \
else \
{ \
ps2TraceGuestWrite(rdram, _addr, 8u, (uint64_t)(val), 0u, "WRITE64", ctx); \
FAST_WRITE64(_addr, (val)); \
} \
} while (0)
#define WRITE128(addr, val) \
do \
{ \
uint32_t _addr = (addr); \
if (PS2Runtime::isSpecialAddress(_addr)) \
runtime->Store128(rdram, ctx, _addr, (val)); \
else \
{ \
FAST_WRITE128(_addr, (val)); \
} \
} while (0)
// Packed Compare Greater Than (PCGT)
#define PS2_PCGTW(a, b) _mm_cmpgt_epi32((__m128i)(a), (__m128i)(b))
@@ -113,54 +311,71 @@ inline uint32_t ps2_clz32(uint32_t val) {
#define PS2_PPACB(a, b) _mm_packus_epi16(_mm_packs_epi32((__m128i)(b), (__m128i)(a)), _mm_setzero_si128())
// Packed Interleave (PINT)
#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)))
#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)))
#define PS2_PINTH(a, b) _mm_unpacklo_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0)))
#define PS2_PINTEH(a, b) _mm_unpackhi_epi16(_mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0)))
// Packed Multiply-Add (PMADD)
#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1,0,3,2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1,0,3,2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3,2,1,0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3,2,1,0))))
#define PS2_PMADDW(a, b) _mm_add_epi32(_mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(1, 0, 3, 2)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(1, 0, 3, 2))), _mm_mullo_epi32(_mm_shuffle_epi32((__m128i)(a), _MM_SHUFFLE(3, 2, 1, 0)), _mm_shuffle_epi32((__m128i)(b), _MM_SHUFFLE(3, 2, 1, 0))))
// Packed Variable Shifts
#define PS2_PSLLVW(a, b) _mm_custom_sllv_epi32((__m128i)(a), (__m128i)(b))
#define PS2_PSRLVW(a, b) _mm_custom_srlv_epi32((__m128i)(a), (__m128i)(b))
#define PS2_PSRAVW(a, b) _mm_custom_srav_epi32((__m128i)(a), (__m128i)(b))
// Helper function declarations for custom variable shifts
inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count) {
int32_t a_arr[4], count_arr[4], result[4];
_mm_storeu_si128((__m128i*)a_arr, a);
_mm_storeu_si128((__m128i*)count_arr, count);
for (int i = 0; i < 4; i++) {
inline __m128i _mm_custom_sllv_epi32(__m128i a, __m128i count)
{
alignas(16) int32_t a_arr[4];
alignas(16) int32_t count_arr[4];
alignas(16) int32_t result[4];
std::memcpy(a_arr, &a, sizeof(a));
std::memcpy(count_arr, &count, sizeof(count));
for (int i = 0; i < 4; i++)
{
result[i] = a_arr[i] << (count_arr[i] & 0x1F);
}
return _mm_loadu_si128((__m128i*)result);
__m128i out;
std::memcpy(&out, result, sizeof(out));
return out;
}
inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count) {
inline __m128i _mm_custom_srlv_epi32(__m128i a, __m128i count)
{
int32_t a_arr[4], count_arr[4], result[4];
_mm_storeu_si128((__m128i*)a_arr, a);
_mm_storeu_si128((__m128i*)count_arr, count);
for (int i = 0; i < 4; i++) {
_mm_storeu_si128((__m128i *)a_arr, a);
_mm_storeu_si128((__m128i *)count_arr, count);
for (int i = 0; i < 4; i++)
{
result[i] = (uint32_t)a_arr[i] >> (count_arr[i] & 0x1F);
}
return _mm_loadu_si128((__m128i*)result);
return _mm_loadu_si128((__m128i *)result);
}
inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) {
inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count)
{
int32_t a_arr[4], count_arr[4], result[4];
_mm_storeu_si128((__m128i*)a_arr, a);
_mm_storeu_si128((__m128i*)count_arr, count);
for (int i = 0; i < 4; i++) {
_mm_storeu_si128((__m128i *)a_arr, a);
_mm_storeu_si128((__m128i *)count_arr, count);
for (int i = 0; i < 4; i++)
{
result[i] = a_arr[i] >> (count_arr[i] & 0x1F);
}
return _mm_loadu_si128((__m128i*)result);
return _mm_loadu_si128((__m128i *)result);
}
// PMFHL function implementations
#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(lo, hi)
#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(lo, hi)
#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(lo, hi)
#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0))
#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(lo, hi), _MM_SHUFFLE(3,1,2,0)), _MM_SHUFFLE(3,1,2,0))
inline __m128i ps2_u64_to_epi64_pair(uint64_t value)
{
return _mm_set1_epi64x(static_cast<long long>(value));
}
#define PS2_PMFHL_LW(hi, lo) _mm_unpacklo_epi64(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi))
#define PS2_PMFHL_UW(hi, lo) _mm_unpackhi_epi64(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi))
#define PS2_PMFHL_SLW(hi, lo) _mm_packs_epi32(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi))
#define PS2_PMFHL_LH(hi, lo) _mm_shuffle_epi32(_mm_packs_epi32(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)), _MM_SHUFFLE(3, 1, 2, 0))
#define PS2_PMFHL_SH(hi, lo) _mm_shufflehi_epi16(_mm_shufflelo_epi16(_mm_packs_epi32(ps2_u64_to_epi64_pair(lo), ps2_u64_to_epi64_pair(hi)), _MM_SHUFFLE(3, 1, 2, 0)), _MM_SHUFFLE(3, 1, 2, 0))
// FPU (COP1) operations
#define FPU_ADD_S(a, b) ((float)(a) + (float)(b))
@@ -212,45 +427,58 @@ inline __m128i _mm_custom_srav_epi32(__m128i a, __m128i count) {
#define PS2_VCALLMS(addr) // VU0 microprogram calls not supported directly
#define PS2_VCALLMSR(reg) // VU0 microprogram calls not supported directly
#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : static_cast<uint32_t>(_mm_extract_epi32(ctx_ptr->r[reg_idx], 0)))
#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : _mm_extract_epi32(ctx_ptr->r[reg_idx], 0))
#define GPR_U64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0ULL : static_cast<uint32_t>(_mm_extract_epi64(ctx_ptr->r[reg_idx], 0)))
#define GPR_S64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0LL : _mm_extract_epi64(ctx_ptr->r[reg_idx], 0))
#define GPR_U32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0U : static_cast<uint32_t>(PS2_EXTRACT_EPI32_0(ctx_ptr->r[reg_idx])))
#define GPR_S32(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0 : PS2_EXTRACT_EPI32_0(ctx_ptr->r[reg_idx]))
#define GPR_U64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0ULL : static_cast<uint64_t>(PS2_EXTRACT_EPI64_0(ctx_ptr->r[reg_idx])))
#define GPR_S64(ctx_ptr, reg_idx) ((reg_idx == 0) ? 0LL : PS2_EXTRACT_EPI64_0(ctx_ptr->r[reg_idx]))
#define GPR_VEC(ctx_ptr, reg_idx) ((reg_idx == 0) ? _mm_setzero_si128() : ctx_ptr->r[reg_idx])
#define SET_GPR_U32(ctx_ptr, reg_idx, val) \
do \
{ \
if (reg_idx != 0) \
ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \
static inline void Ps2SetGprLow64(R5900Context *ctx, int reg, __m128i new_low)
{
if (reg != 0)
{
ctx->r[reg] = _mm_castpd_si128(_mm_move_sd(_mm_castsi128_pd(ctx->r[reg]), _mm_castsi128_pd(new_low)));
}
}
#define SET_GPR_U32(ctx_ptr, reg_idx, val) \
do \
{ \
if ((reg_idx) != 0) \
{ \
__m128i _newVal = _mm_cvtsi32_si128((int)(val)); \
\
Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \
} \
} while (0)
#define SET_GPR_S32(ctx_ptr, reg_idx, val) \
do \
{ \
if (reg_idx != 0) \
ctx_ptr->r[reg_idx] = _mm_set_epi32(0, 0, 0, (val)); \
#define SET_GPR_S32(ctx_ptr, reg_idx, val) \
do \
{ \
if ((reg_idx) != 0) \
{ \
__m128i _newVal = _mm_cvtsi64_si128((int64_t)(int32_t)(val)); \
Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \
} \
} while (0)
#define SET_GPR_U64(ctx_ptr, reg_idx, val) \
do \
{ \
if (reg_idx != 0) \
ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \
#define SET_GPR_U64(ctx_ptr, reg_idx, val) \
do \
{ \
if ((reg_idx) != 0) \
{ \
__m128i _newVal = _mm_cvtsi64_si128((int64_t)(val)); \
Ps2SetGprLow64(ctx_ptr, reg_idx, _newVal); \
} \
} while (0)
#define SET_GPR_S64(ctx_ptr, reg_idx, val) \
do \
{ \
if (reg_idx != 0) \
ctx_ptr->r[reg_idx] = _mm_set_epi64x(0, (val)); \
} while (0)
#define SET_GPR_S64(ctx_ptr, reg_idx, val) SET_GPR_U64(ctx_ptr, reg_idx, val)
#define SET_GPR_VEC(ctx_ptr, reg_idx, val) \
do \
{ \
if (reg_idx != 0) \
ctx_ptr->r[reg_idx] = (val); \
ctx_ptr->r[reg_idx] = (val); \
} while (0)
#endif // PS2_RUNTIME_MACROS_H
+3
View File
@@ -11,6 +11,9 @@ namespace ps2_stubs
PS2_STUB_LIST(PS2_DECLARE_STUB)
#undef PS2_DECLARE_STUB
void syMalloc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sndr_trans_func(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void TODO_NAMED(const char *name, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
}
+7 -17
View File
@@ -11,27 +11,17 @@ extern std::atomic<int> g_activeThreads;
static std::mutex g_sys_fd_mutex;
#define PS2_FIO_O_RDONLY 0x0001
#define PS2_FIO_O_WRONLY 0x0002
#define PS2_FIO_O_RDWR 0x0003
#define PS2_FIO_O_NBLOCK 0x0010
#define PS2_FIO_O_APPEND 0x0100
#define PS2_FIO_O_CREAT 0x0200
#define PS2_FIO_O_TRUNC 0x0400
#define PS2_FIO_O_EXCL 0x0800
#define PS2_FIO_O_NOWAIT 0x8000
#define PS2_SEEK_SET 0
#define PS2_SEEK_CUR 1
#define PS2_SEEK_END 2
namespace ps2_syscalls
{
#define PS2_DECLARE_SYSCALL(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
#define PS2_DECLARE_SYSCALL(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
PS2_SYSCALL_LIST(PS2_DECLARE_SYSCALL)
#undef PS2_DECLARE_SYSCALL
#undef PS2_DECLARE_SYSCALL
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
}
#endif // PS2_SYSCALLS_H
+300 -307
View File
@@ -2,10 +2,34 @@
#include <iostream>
#include <cstring>
#include <stdexcept>
#include <unordered_map>
#include <algorithm>
namespace
{
inline void inRange(uint32_t offset, size_t bytes, size_t regionSize, const char *op, uint32_t address)
{
if (static_cast<uint64_t>(offset) + static_cast<uint64_t>(bytes) > static_cast<uint64_t>(regionSize))
{
throw std::runtime_error(std::string(op) + " out-of-bounds at address: 0x" + std::to_string(address));
}
}
template <typename T>
inline T loadScalar(const uint8_t *base, uint32_t offset, size_t regionSize, const char *op, uint32_t address)
{
inRange(offset, sizeof(T), regionSize, op, address);
T value{};
std::memcpy(&value, base + offset, sizeof(T));
return value;
}
template <typename T>
inline void storeScalar(uint8_t *base, uint32_t offset, size_t regionSize, T value, const char *op, uint32_t address)
{
inRange(offset, sizeof(T), regionSize, op, address);
std::memcpy(base + offset, &value, sizeof(T));
}
inline bool isGsPrivReg(uint32_t addr)
{
return addr >= PS2_GS_PRIV_REG_BASE && addr < PS2_GS_PRIV_REG_BASE + PS2_GS_PRIV_REG_SIZE;
@@ -59,38 +83,9 @@ namespace
}
}
inline void logGsWrite(uint32_t addr, uint64_t value)
{
static std::unordered_map<uint32_t, int> logCount;
int &count = logCount[addr];
if (count < 10)
{
std::cout << "[GS] write 0x" << std::hex << addr << " = 0x" << value << std::dec << std::endl;
}
++count;
}
constexpr uint32_t kSchedulerBase = 0x00363a10;
constexpr uint32_t kSchedulerSpan = 0x00000420;
static int g_schedWriteLogCount = 0;
inline void logSchedulerWrite(uint32_t physAddr, uint32_t size, uint64_t value)
{
if (physAddr < kSchedulerBase || physAddr >= kSchedulerBase + kSchedulerSpan)
{
return;
}
if (g_schedWriteLogCount >= 64)
{
return;
}
std::cout << "[sched write" << size << "] addr=0x" << std::hex << physAddr
<< " val=0x" << value << std::dec << std::endl;
++g_schedWriteLogCount;
}
}
// Helpers for GS VRAM addressing (PSMCT32 only in this minimal path).
// Helpers for GS VRAM addressing (PSMCT32 path).
static inline uint32_t gs_vram_offset(uint32_t basePage, uint32_t x, uint32_t y, uint32_t fbw)
{
// basePage is in 2048-byte units; fbw is in blocks of 64 pixels.
@@ -99,8 +94,9 @@ static inline uint32_t gs_vram_offset(uint32_t basePage, uint32_t x, uint32_t y,
}
PS2Memory::PS2Memory()
: m_rdram(nullptr), m_scratchpad(nullptr), m_gsVRAM(nullptr), m_seenGifCopy(false)
: m_rdram(nullptr), m_scratchpad(nullptr), iop_ram(nullptr), m_seenGifCopy(false), m_gsVRAM(nullptr)
{
ps2SetScratchpadHostPtr(nullptr);
}
PS2Memory::~PS2Memory()
@@ -113,6 +109,7 @@ PS2Memory::~PS2Memory()
if (m_scratchpad)
{
ps2SetScratchpadHostPtr(nullptr);
delete[] m_scratchpad;
m_scratchpad = nullptr;
}
@@ -122,45 +119,53 @@ PS2Memory::~PS2Memory()
delete[] m_gsVRAM;
m_gsVRAM = nullptr;
}
if (iop_ram)
{
delete[] iop_ram;
iop_ram = nullptr;
}
}
bool PS2Memory::initialize(size_t ramSize)
{
auto cleanup = [this]()
{
delete[] m_rdram;
delete[] m_scratchpad;
delete[] iop_ram;
delete[] m_gsVRAM;
m_rdram = nullptr;
m_scratchpad = nullptr;
ps2SetScratchpadHostPtr(nullptr);
iop_ram = nullptr;
m_gsVRAM = nullptr;
};
cleanup();
m_seenGifCopy = false;
m_dmaStartCount.store(0, std::memory_order_relaxed);
m_gifCopyCount.store(0, std::memory_order_relaxed);
m_gsWriteCount.store(0, std::memory_order_relaxed);
m_vifWriteCount.store(0, std::memory_order_relaxed);
m_codeRegions.clear();
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);
ps2SetScratchpadHostPtr(m_scratchpad);
// Initialize TLB entries
m_tlbEntries.clear();
// Initialize EE TLB entries (R5900 has 48 entries).
m_tlbEntries.assign(48, TLBEntry{0, 0, 0, false});
// Allocate IOP RAM
iop_ram = new uint8_t[2 * 1024 * 1024]; // 2MB
if (!iop_ram)
{
delete[] m_rdram;
delete[] m_scratchpad;
m_rdram = nullptr;
m_scratchpad = nullptr;
return false;
}
// Initialize IOP RAM with zeros
std::memset(iop_ram, 0, 2 * 1024 * 1024);
@@ -173,16 +178,6 @@ bool PS2Memory::initialize(size_t ramSize)
// Allocate GS VRAM (4MB)
m_gsVRAM = new uint8_t[PS2_GS_VRAM_SIZE];
if (!m_gsVRAM)
{
delete[] m_rdram;
delete[] m_scratchpad;
delete[] iop_ram;
m_rdram = nullptr;
m_scratchpad = nullptr;
iop_ram = nullptr;
return false;
}
std::memset(m_gsVRAM, 0, PS2_GS_VRAM_SIZE);
// Initialize VIF registers
@@ -197,6 +192,7 @@ bool PS2Memory::initialize(size_t ramSize)
catch (const std::exception &e)
{
std::cerr << "Error initializing PS2 memory: " << e.what() << std::endl;
cleanup();
return false;
}
}
@@ -214,34 +210,93 @@ uint32_t PS2Memory::translateAddress(uint32_t virtualAddress)
return virtualAddress - PS2_SCRATCHPAD_BASE;
}
if (virtualAddress < PS2_RAM_SIZE ||
(virtualAddress >= 0x80000000 && virtualAddress < 0x80000000 + PS2_RAM_SIZE))
// KSEG0/KSEG1 direct-mapped window.
if (virtualAddress >= 0x80000000 && virtualAddress < 0xC0000000)
{
return virtualAddress & 0x1FFFFFFF;
}
// In this runtime, low segments are treated as physical-style addresses already.
if (virtualAddress < 0x80000000)
{
return virtualAddress;
}
// KSEG2/KSEG3 are TLB mapped.
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)
// PageMask uses bits [24:13]. Build an address-level mask (plus 4KB base page bits).
const uint32_t mask = entry.mask & 0x01FFE000u;
const uint32_t compareMask = ~(mask | 0xFFFu);
if ((virtualAddress & compareMask) == (entry.vpn & compareMask))
{
// TLB hit
uint32_t offset = virtualAddress & 0xFFF; // Page offset
uint32_t page = entry.pfn | (virtualAddress & entry.mask);
return (page << 12) | offset;
const uint32_t pageOffsetMask = mask | 0xFFFu;
const uint32_t physBase = entry.pfn << 12;
return physBase | (virtualAddress & pageOffsetMask);
}
}
}
throw std::runtime_error("TLB miss for address: 0x" + std::to_string(virtualAddress));
}
return virtualAddress & 0x1FFFFFFF;
return virtualAddress;
}
bool PS2Memory::tlbRead(uint32_t index, uint32_t &vpn, uint32_t &pfn, uint32_t &mask, bool &valid) const
{
if (index >= m_tlbEntries.size())
{
return false;
}
const TLBEntry &entry = m_tlbEntries[index];
vpn = entry.vpn;
pfn = entry.pfn;
mask = entry.mask;
valid = entry.valid;
return true;
}
bool PS2Memory::tlbWrite(uint32_t index, uint32_t vpn, uint32_t pfn, uint32_t mask, bool valid)
{
if (index >= m_tlbEntries.size())
{
return false;
}
TLBEntry &entry = m_tlbEntries[index];
entry.vpn = vpn & 0xFFFFF000u;
entry.pfn = pfn & 0x000FFFFFu;
entry.mask = mask & 0x01FFE000u;
entry.valid = valid;
return true;
}
int32_t PS2Memory::tlbProbe(uint32_t vpn) const
{
const uint32_t normalizedVpn = vpn & 0xFFFFF000u;
for (uint32_t i = 0; i < static_cast<uint32_t>(m_tlbEntries.size()); ++i)
{
const TLBEntry &entry = m_tlbEntries[i];
if (!entry.valid)
{
continue;
}
const uint32_t mask = entry.mask & 0x01FFE000u;
const uint32_t compareMask = ~(mask | 0xFFFu);
if ((normalizedVpn & compareMask) == (entry.vpn & compareMask))
{
return static_cast<int32_t>(i);
}
}
return -1;
}
uint8_t PS2Memory::read8(uint32_t address)
@@ -260,16 +315,11 @@ uint8_t PS2Memory::read8(uint32_t address)
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
uint32_t regAddr = physAddr & ~0x3;
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;
uint32_t value = readIORegister(regAddr);
uint32_t shift = (physAddr & 3) * 8;
return static_cast<uint8_t>((value >> shift) & 0xFF);
}
// TODO: Handle other memory regions
return 0;
}
@@ -285,22 +335,18 @@ uint16_t PS2Memory::read16(uint32_t address)
if (scratch)
{
return *reinterpret_cast<uint16_t *>(&m_scratchpad[physAddr]);
return loadScalar<uint16_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read16 scratchpad", address);
}
if (physAddr < PS2_RAM_SIZE)
{
return *reinterpret_cast<uint16_t *>(&m_rdram[physAddr]);
return loadScalar<uint16_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read16 rdram", address);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
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;
uint32_t value = readIORegister(regAddr);
uint32_t shift = (physAddr & 2) * 8;
return static_cast<uint16_t>((value >> shift) & 0xFFFF);
}
return 0;
@@ -326,19 +372,15 @@ uint32_t PS2Memory::read32(uint32_t address)
if (scratch)
{
return *reinterpret_cast<uint32_t *>(&m_scratchpad[physAddr]);
return loadScalar<uint32_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read32 scratchpad", address);
}
if (physAddr < PS2_RAM_SIZE)
{
return *reinterpret_cast<uint32_t *>(&m_rdram[physAddr]);
return loadScalar<uint32_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read32 rdram", address);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
if (m_ioRegisters.find(physAddr) != m_ioRegisters.end())
{
return m_ioRegisters[physAddr];
}
return 0;
return readIORegister(physAddr);
}
return 0;
@@ -362,11 +404,11 @@ uint64_t PS2Memory::read64(uint32_t address)
if (scratch)
{
return *reinterpret_cast<uint64_t *>(&m_scratchpad[physAddr]);
return loadScalar<uint64_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, "read64 scratchpad", address);
}
if (physAddr < PS2_RAM_SIZE)
{
return *reinterpret_cast<uint64_t *>(&m_rdram[physAddr]);
return loadScalar<uint64_t>(m_rdram, physAddr, PS2_RAM_SIZE, "read64 rdram", address);
}
// 64-bit IO operations are not common, but who knows
@@ -385,10 +427,12 @@ __m128i PS2Memory::read128(uint32_t address)
if (scratch)
{
inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "read128 scratchpad", address);
return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr]));
}
if (physAddr < PS2_RAM_SIZE)
{
inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "read128 rdram", address);
return _mm_loadu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]));
}
@@ -409,7 +453,6 @@ void PS2Memory::write8(uint32_t address, uint8_t value)
else if (physAddr < PS2_RAM_SIZE)
{
m_rdram[physAddr] = value;
logSchedulerWrite(physAddr, 8, value);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
@@ -418,9 +461,7 @@ void PS2Memory::write8(uint32_t address, uint8_t value)
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;
// TODO: Handle potential side effects of IO register writes
writeIORegister(regAddr, newValue);
}
}
@@ -436,12 +477,11 @@ void PS2Memory::write16(uint32_t address, uint16_t value)
if (scratch)
{
*reinterpret_cast<uint16_t *>(&m_scratchpad[physAddr]) = value;
storeScalar<uint16_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write16 scratchpad", address);
}
else if (physAddr < PS2_RAM_SIZE)
{
*reinterpret_cast<uint16_t *>(&m_rdram[physAddr]) = value;
logSchedulerWrite(physAddr, 16, value);
storeScalar<uint16_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write16 rdram", address);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
@@ -449,9 +489,7 @@ void PS2Memory::write16(uint32_t address, uint16_t value)
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;
// TODO: Handle potential side effects of IO register writes
writeIORegister(regAddr, newValue);
}
}
@@ -471,7 +509,6 @@ void PS2Memory::write32(uint32_t address, uint32_t value)
uint64_t mask = 0xFFFFFFFFULL << (off * 8);
uint64_t newVal = (*reg & ~mask) | ((uint64_t)value << (off * 8));
*reg = newVal;
logGsWrite(address, newVal);
}
return;
}
@@ -481,25 +518,17 @@ void PS2Memory::write32(uint32_t address, uint32_t value)
if (scratch)
{
*reinterpret_cast<uint32_t *>(&m_scratchpad[physAddr]) = value;
storeScalar<uint32_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write32 scratchpad", address);
}
else if (physAddr < PS2_RAM_SIZE)
{
// Check if this might be code modification
markModified(address, 4);
*reinterpret_cast<uint32_t *>(&m_rdram[physAddr]) = value;
logSchedulerWrite(physAddr, 32, value);
storeScalar<uint32_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write32 rdram", address);
}
else if (physAddr >= PS2_IO_BASE && physAddr < PS2_IO_BASE + PS2_IO_SIZE)
{
static int ioLogCount = 0;
if (ioLogCount < 64)
{
std::cout << "[IO write32] addr=0x" << std::hex << physAddr << " val=0x" << value << std::dec << std::endl;
++ioLogCount;
}
// Handle IO register writes with potential side effects
writeIORegister(physAddr, value);
}
}
@@ -517,7 +546,6 @@ void PS2Memory::write64(uint32_t address, uint64_t value)
if (reg)
{
*reg = value;
logGsWrite(address, value);
}
return;
}
@@ -527,12 +555,11 @@ void PS2Memory::write64(uint32_t address, uint64_t value)
if (scratch)
{
*reinterpret_cast<uint64_t *>(&m_scratchpad[physAddr]) = value;
storeScalar<uint64_t>(m_scratchpad, physAddr, PS2_SCRATCHPAD_SIZE, value, "write64 scratchpad", address);
}
else if (physAddr < PS2_RAM_SIZE)
{
*reinterpret_cast<uint64_t *>(&m_rdram[physAddr]) = value;
logSchedulerWrite(physAddr, 64, value);
storeScalar<uint64_t>(m_rdram, physAddr, PS2_RAM_SIZE, value, "write64 rdram", address);
}
else
{
@@ -553,18 +580,17 @@ void PS2Memory::write128(uint32_t address, __m128i value)
if (scratch)
{
inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "write128 scratchpad", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_scratchpad[physAddr]), value);
}
else if (physAddr < PS2_RAM_SIZE)
{
inRange(physAddr, sizeof(__m128i), PS2_RAM_SIZE, "write128 rdram", address);
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_rdram[physAddr]), value);
}
else if (physAddr < PS2_GS_VRAM_SIZE)
{
_mm_storeu_si128(reinterpret_cast<__m128i *>(&m_gsVRAM[physAddr]), value);
}
else
{
// Non-RAM 128-bit stores are modeled as two 64-bit stores.
uint64_t lo = _mm_extract_epi64(value, 0);
uint64_t hi = _mm_extract_epi64(value, 1);
@@ -575,180 +601,108 @@ void PS2Memory::write128(uint32_t address, __m128i value)
bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
{
m_ioRegisters[address] = value;
if (address >= 0x10008000 && address < 0x1000F000)
{
static int dmaLogCount = 0;
if (dmaLogCount < 100)
if ((address & 0xFF) == 0x00 && (value & 0x100))
{
uint32_t channelBase = address & 0xFFFFFF00;
uint32_t offset = address & 0xFF;
std::cout << "[DMA reg] ch=0x" << std::hex << channelBase
<< " off=0x" << offset << " = 0x" << value << std::dec << std::endl;
dmaLogCount++;
if (offset == 0x00 && (value & 0x100))
const uint32_t channelBase = address & 0xFFFFFF00;
const uint32_t madr = m_ioRegisters[channelBase + 0x10];
const uint32_t qwc = m_ioRegisters[channelBase + 0x20];
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
if ((channelBase == 0x1000A000 || channelBase == 0x10009000) && m_gsVRAM)
{
uint32_t madr = m_ioRegisters[channelBase + 0x10];
uint32_t qwc = m_ioRegisters[channelBase + 0x20];
uint32_t tadr = m_ioRegisters[channelBase + 0x30];
std::cout << "[DMA start] ch=0x" << std::hex << channelBase
<< " madr=0x" << madr << " qwc=0x" << qwc
<< " tadr=0x" << tadr << std::dec << std::endl;
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
auto doCopy = [&](uint32_t srcAddr, uint32_t qwCount)
{
const uint64_t bytes64 = static_cast<uint64_t>(qwCount) * 16ull;
uint32_t bytes = (bytes64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(bytes64);
uint32_t src = 0;
try
{
src = translateAddress(srcAddr);
}
catch (const std::exception &)
{
return;
}
uint32_t basePage = static_cast<uint32_t>(gs_regs.dispfb1 & 0x1FF);
uint32_t dest = basePage * 2048;
if (dest >= PS2_GS_VRAM_SIZE)
{
return;
}
if (dest + bytes > PS2_GS_VRAM_SIZE)
{
bytes = std::min<uint32_t>(bytes, PS2_GS_VRAM_SIZE - dest);
}
if (src >= PS2_RAM_SIZE)
{
return;
}
if (src + bytes > PS2_RAM_SIZE)
{
bytes = std::min<uint32_t>(bytes, PS2_RAM_SIZE - src);
}
if (bytes == 0)
{
return;
}
std::memcpy(m_gsVRAM + dest, m_rdram + src, bytes);
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
};
if (qwc > 0)
{
doCopy(madr, qwc);
}
else
{
uint32_t tadr = m_ioRegisters[channelBase + 0x30];
uint32_t physTag = translateAddress(tadr);
if (physTag + 16 <= PS2_RAM_SIZE)
{
const uint8_t *tp = m_rdram + physTag;
uint64_t tag = loadScalar<uint64_t>(tp, 0, 16, "dma chain tag", tadr);
uint16_t tagQwc = static_cast<uint16_t>(tag & 0xFFFF);
uint32_t id = static_cast<uint32_t>((tag >> 28) & 0x7);
uint32_t addr = static_cast<uint32_t>((tag >> 32) & 0x7FFFFFF);
if (id == 0 || id == 1 || id == 2)
{
doCopy(addr, tagQwc);
}
}
}
m_ioRegisters[address] &= ~0x100;
}
}
return true;
}
m_ioRegisters[address] = value;
if (address >= 0x10000000 && address < 0x10010000)
{
// Timer/counter registers
if (address >= 0x10000000 && address < 0x10000100)
{
std::cout << "Timer register write: " << std::hex << address << " = " << value << std::dec << std::endl;
return true;
}
// VIF0/VIF1 registers
if (address >= 0x10003800 && address < 0x10003A00)
{
static int vif0Log = 0;
if (vif0Log < 50)
{
std::cout << "[VIF0] write 0x" << std::hex << address << " = 0x" << value << std::dec << std::endl;
++vif0Log;
}
m_vifWriteCount.fetch_add(1, std::memory_order_relaxed);
}
if (address >= 0x10003C00 && address < 0x10003E00)
{
static int vif1Log = 0;
if (vif1Log < 50)
{
std::cout << "[VIF1] write 0x" << std::hex << address << " = 0x" << value << std::dec << std::endl;
++vif1Log;
}
m_vifWriteCount.fetch_add(1, std::memory_order_relaxed);
}
// DMA registers
if (address >= 0x10008000 && address < 0x1000F000)
{
std::cout << "DMA register write: " << std::hex << address << " = " << value << std::dec << std::endl;
// Dump current DMA regs for all channels
static bool dumpedDma = false;
if (!dumpedDma)
{
for (int ch = 0; ch < 10; ++ch)
{
uint32_t base = 0x10008000 + ch * 0x100;
uint32_t chcr_v = m_ioRegisters[base + 0x00];
uint32_t madr_v = m_ioRegisters[base + 0x10];
uint32_t qwc_v = m_ioRegisters[base + 0x20];
uint32_t tadr_v = m_ioRegisters[base + 0x30];
std::cout << "[DMA dump] ch" << ch
<< " chcr=0x" << std::hex << chcr_v
<< " madr=0x" << madr_v
<< " qwc=0x" << qwc_v
<< " tadr=0x" << tadr_v << std::dec << std::endl;
}
dumpedDma = true;
}
if ((address & 0xFF) == 0x00)
{ // CHCR registers
if (value & 0x100)
{
uint32_t channelBase = address & 0xFFFFFF00;
uint32_t madr = m_ioRegisters[channelBase + 0x10]; // Memory address
uint32_t qwc = m_ioRegisters[channelBase + 0x20]; // Quadword count
std::cout << "Starting DMA transfer on channel " << ((address >> 8) & 0xF)
<< ", MADR: " << std::hex << madr
<< ", QWC: " << qwc << std::dec << std::endl;
// Minimal GIF (channel 2) and VIF1 (channel 1) image transfer: copy from EE memory to GS VRAM.
// Only handles simple linear IMAGE transfers; treats destination as current DISPFBUF1 FBP.
if ((channelBase == 0x1000A000 || channelBase == 0x10009000) && m_gsVRAM)
{
auto doCopy = [&](uint32_t srcAddr, uint32_t qwCount)
{
uint32_t bytes = qwCount * 16;
uint32_t src = translateAddress(srcAddr);
uint32_t basePage = static_cast<uint32_t>(gs_regs.dispfb1 & 0x1FF);
uint32_t dest = basePage * 2048;
std::cout << "[GIF] ch=" << ((channelBase == 0x1000A000) ? 2 : 1)
<< " IMAGE copy bytes=" << bytes
<< " src=0x" << std::hex << srcAddr
<< " (phys 0x" << src << ")"
<< " dest=0x" << dest << std::dec << std::endl;
if (dest + bytes > PS2_GS_VRAM_SIZE)
{
bytes = std::min<uint32_t>(bytes, PS2_GS_VRAM_SIZE - dest);
}
if (src + bytes > PS2_RAM_SIZE)
{
bytes = std::min<uint32_t>(bytes, PS2_RAM_SIZE - src);
}
std::memcpy(m_gsVRAM + dest, m_rdram + src, bytes);
m_seenGifCopy = true;
m_gifCopyCount.fetch_add(1, std::memory_order_relaxed);
};
// Dump GIF tag/header
uint32_t phys = translateAddress(madr);
if (phys + 16 <= PS2_RAM_SIZE)
{
const uint8_t *p = m_rdram + phys;
uint64_t tag0 = *reinterpret_cast<const uint64_t *>(p + 0);
uint64_t tag1 = *reinterpret_cast<const uint64_t *>(p + 8);
std::cout << "[GIF] tag0=0x" << std::hex << tag0 << " tag1=0x" << tag1 << std::dec << std::endl;
}
if (qwc > 0)
{
doCopy(madr, qwc);
}
else
{
// Simple DMA chain walker for one tag from TADR (REF/NEXT).
uint32_t tadr = m_ioRegisters[channelBase + 0x30];
uint32_t physTag = translateAddress(tadr);
if (physTag + 16 <= PS2_RAM_SIZE)
{
const uint8_t *tp = m_rdram + physTag;
uint64_t tag = *reinterpret_cast<const uint64_t *>(tp);
uint16_t tagQwc = static_cast<uint16_t>(tag & 0xFFFF);
uint32_t id = static_cast<uint32_t>((tag >> 28) & 0x7);
uint32_t addr = static_cast<uint32_t>((tag >> 32) & 0x7FFFFFF);
std::cout << "[DMA chain] ch=" << ((channelBase == 0x1000A000) ? 2 : 1)
<< " tag id=0x" << std::hex << id
<< " qwc=" << tagQwc
<< " addr=0x" << addr
<< " raw=0x" << tag << std::dec << std::endl;
if (id == 0 || id == 1 || id == 2)
{
doCopy(addr, tagQwc);
}
}
}
m_ioRegisters[address] &= ~0x100;
}
}
}
return true;
}
if (address >= 0x10000200 && address < 0x10000300)
{
std::cout << "Interrupt register write: " << std::hex << address << " = " << value << std::dec << std::endl;
return true;
}
if (address >= 0x10000000 && address < 0x10000100)
{
return true;
}
}
else if (address >= 0x12000000 && address < 0x12001000)
if (address >= 0x12000000 && address < 0x12001000)
{
// GS registers
std::cout << "GS register write: " << std::hex << address << " = " << value << std::dec << std::endl;
m_gsWriteCount.fetch_add(1, std::memory_order_relaxed);
return true;
}
@@ -758,55 +712,70 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
uint32_t PS2Memory::readIORegister(uint32_t address)
{
auto it = m_ioRegisters.find(address);
if (it != m_ioRegisters.end())
{
return it->second;
}
if (address >= 0x10000000 && address < 0x10010000)
{
// Timer registers
if (address >= 0x10000000 && address < 0x10000100)
{
if ((address & 0xF) == 0x00)
{ // COUNT registers
uint32_t timerCount = 0; // Should calculate based on elapsed time
std::cout << "Timer COUNT read: " << std::hex << address << " = " << timerCount << std::dec << std::endl;
return timerCount;
{
return 0;
}
}
// DMA status registers
if (address >= 0x10008000 && address < 0x1000F000)
{
if ((address & 0xFF) == 0x00)
{ // CHCR registers
uint32_t channelStatus = m_ioRegisters[address] & ~0x100; // Clear busy bit
std::cout << "DMA status read: " << std::hex << address << " = " << channelStatus << std::dec << std::endl;
{
uint32_t channelStatus = m_ioRegisters[address] & ~0x100;
m_ioRegisters[address] = channelStatus;
return channelStatus;
}
}
// Interrupt status registers
if (address >= 0x10000200 && address < 0x10000300)
{
std::cout << "Interrupt status read: " << std::hex << address << std::dec << std::endl;
// Should calculate based on pending interrupts
return 0;
}
}
auto it = m_ioRegisters.find(address);
if (it != m_ioRegisters.end())
{
return it->second;
}
return 0;
}
void PS2Memory::registerCodeRegion(uint32_t start, uint32_t end)
{
if (end <= start)
{
std::cerr << "Ignoring invalid code region: start=0x" << std::hex << start
<< " end=0x" << end << std::dec << std::endl;
return;
}
if ((end - start) > PS2_RAM_SIZE)
{
std::cerr << "Ignoring oversized code region: start=0x" << std::hex << start
<< " end=0x" << end << std::dec << std::endl;
return;
}
for (const auto &existing : m_codeRegions)
{
if (existing.start == start && existing.end == end)
{
return;
}
}
CodeRegion region;
region.start = start;
region.end = end;
size_t sizeInWords = (end - start) / 4;
size_t sizeInWords = (end - start + 3u) / 4u;
region.modified.resize(sizeInWords, false);
m_codeRegions.push_back(region);
@@ -820,15 +789,23 @@ bool PS2Memory::isAddressInRegion(uint32_t address, const CodeRegion &region)
void PS2Memory::markModified(uint32_t address, uint32_t size)
{
if (size == 0)
{
return;
}
const uint64_t writeEnd = static_cast<uint64_t>(address) + static_cast<uint64_t>(size);
for (auto &region : m_codeRegions)
{
if (address + size <= region.start || address >= region.end)
const uint64_t regionStart = region.start;
const uint64_t regionEnd = region.end;
if (writeEnd <= regionStart || static_cast<uint64_t>(address) >= regionEnd)
{
continue;
}
uint32_t overlapStart = std::max(address, region.start);
uint32_t overlapEnd = std::min(address + size, region.end);
uint32_t overlapStart = static_cast<uint32_t>(std::max<uint64_t>(address, regionStart));
uint32_t overlapEnd = static_cast<uint32_t>(std::min<uint64_t>(writeEnd, regionEnd));
for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4)
{
@@ -844,15 +821,23 @@ void PS2Memory::markModified(uint32_t address, uint32_t size)
bool PS2Memory::isCodeModified(uint32_t address, uint32_t size)
{
if (size == 0)
{
return false;
}
const uint64_t writeEnd = static_cast<uint64_t>(address) + static_cast<uint64_t>(size);
for (const auto &region : m_codeRegions)
{
if (address + size <= region.start || address >= region.end)
const uint64_t regionStart = region.start;
const uint64_t regionEnd = region.end;
if (writeEnd <= regionStart || static_cast<uint64_t>(address) >= regionEnd)
{
continue;
}
uint32_t overlapStart = std::max(address, region.start);
uint32_t overlapEnd = std::min(address + size, region.end);
uint32_t overlapStart = static_cast<uint32_t>(std::max<uint64_t>(address, regionStart));
uint32_t overlapEnd = static_cast<uint32_t>(std::min<uint64_t>(writeEnd, regionEnd));
for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4)
{
@@ -869,15 +854,23 @@ bool PS2Memory::isCodeModified(uint32_t address, uint32_t size)
void PS2Memory::clearModifiedFlag(uint32_t address, uint32_t size)
{
if (size == 0)
{
return;
}
const uint64_t writeEnd = static_cast<uint64_t>(address) + static_cast<uint64_t>(size);
for (auto &region : m_codeRegions)
{
if (address + size <= region.start || address >= region.end)
const uint64_t regionStart = region.start;
const uint64_t regionEnd = region.end;
if (writeEnd <= regionStart || static_cast<uint64_t>(address) >= regionEnd)
{
continue;
}
uint32_t overlapStart = std::max(address, region.start);
uint32_t overlapEnd = std::min(address + size, region.end);
uint32_t overlapStart = static_cast<uint32_t>(std::max<uint64_t>(address, regionStart));
uint32_t overlapEnd = static_cast<uint32_t>(std::min<uint64_t>(writeEnd, regionEnd));
for (uint32_t addr = overlapStart; addr < overlapEnd; addr += 4)
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8
View File
@@ -9,13 +9,21 @@ add_executable(ps2x_tests
src/main.cpp
src/code_generator_tests.cpp
src/r5900_decoder_tests.cpp
src/elf_analyzer_tests.cpp
)
option(PRINT_GENERATED_CODE "Print generated code in tests" OFF)
if(PRINT_GENERATED_CODE)
target_compile_definitions(ps2x_tests PRIVATE PRINT_GENERATED_CODE)
endif()
target_include_directories(ps2x_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/ps2xRecomp/include
${CMAKE_SOURCE_DIR}/ps2xAnalyzer/include
)
target_link_libraries(ps2x_tests PRIVATE
ps2_recomp_lib
ps2_analyzer_lib
)
+229 -2
View File
@@ -30,6 +30,51 @@ static Instruction makeNop(uint32_t address)
return inst;
}
static Instruction makeJal(uint32_t address, uint32_t target)
{
Instruction inst{};
inst.address = address;
inst.opcode = OPCODE_JAL;
inst.target = (target >> 2) & 0x3FFFFFF;
inst.hasDelaySlot = true;
inst.raw = (OPCODE_JAL << 26) | inst.target;
return inst;
}
static Instruction makeJalr(uint32_t address, uint8_t rs, uint8_t rd)
{
Instruction inst{};
inst.address = address;
inst.opcode = OPCODE_SPECIAL;
inst.function = SPECIAL_JALR;
inst.rs = rs;
inst.rd = rd; // Destination for link address (default 31)
inst.hasDelaySlot = true;
inst.raw = (OPCODE_SPECIAL << 26) | (rs << 21) | (0 << 16) | (rd << 11) | (0 << 6) | SPECIAL_JALR;
return inst;
}
static Instruction makeJr(uint32_t address, uint8_t rs)
{
Instruction inst{};
inst.address = address;
inst.opcode = OPCODE_SPECIAL;
inst.function = SPECIAL_JR;
inst.rs = rs;
inst.hasDelaySlot = true;
inst.raw = (OPCODE_SPECIAL << 26) | (rs << 21) | SPECIAL_JR;
return inst;
}
static void printGeneratedCode(const std::string& name, const std::string& code)
{
#ifdef PRINT_GENERATED_CODE
std::cout << "=== Generated Code for " << name << " ===" << std::endl;
std::cout << code << std::endl;
std::cout << "========================================" << std::endl;
#endif
}
void register_code_generator_tests()
{
MiniTest::Case("CodeGenerator", [](TestCase &tc)
@@ -57,6 +102,7 @@ void register_code_generator_tests()
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("emits labels and gotos for internal branches", generated);
t.IsTrue(generated.find("label_100c:") != std::string::npos, "branch target should emit a label");
t.IsTrue(generated.find("goto label_100c;") != std::string::npos, "internal branch should jump via goto");
@@ -79,6 +125,7 @@ void register_code_generator_tests()
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("labels delay slot when it is a branch target", generated);
t.IsTrue(generated.find("label_2004:") != std::string::npos, "delay slot that is a target should emit a label");
t.IsTrue(generated.find("goto label_2004;") != std::string::npos, "branch to delay slot should use goto");
@@ -102,6 +149,7 @@ void register_code_generator_tests()
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("branches outside function still set pc", generated);
t.IsTrue(generated.find("ctx->pc = 0x") != std::string::npos, "external branch should set ctx->pc");
t.IsTrue(generated.find("goto label_") == std::string::npos, "external branch should not use goto");
@@ -133,6 +181,7 @@ void register_code_generator_tests()
CodeGenerator gen({targetSym});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("jumps to known symbols call by name", generated);
t.IsTrue(generated.find("target_func(rdram, ctx, runtime); return;") != std::string::npos,
"jump to known function should emit direct call");
@@ -158,6 +207,7 @@ void register_code_generator_tests()
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("jump to unknown target sets pc", generated);
t.IsTrue(generated.find("ctx->pc = 0x") != std::string::npos, "unknown jump target should set ctx->pc");
t.IsTrue(generated.find("goto label_") == std::string::npos, "external jump should not use goto");
@@ -183,6 +233,7 @@ void register_code_generator_tests()
gen.setRenamedFunctions({{0x8000, "renamed_target"}});
std::string sw = gen.generateJumpTableSwitch(inst, 0x0, entries);
printGeneratedCode("renamed function used in jump table", sw);
t.IsTrue(sw.find("renamed_target(rdram, ctx, runtime);") != std::string::npos,
"jump table should use renamed function name");
@@ -215,10 +266,186 @@ void register_code_generator_tests()
gen.setRenamedFunctions({{targetSym.address, "ps2___is_pointer"}});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("reserved identifiers are sanitized and used in calls", generated);
t.IsTrue(generated.find("void ps2___is_pointer(") != std::string::npos,
"definition should use sanitized name");
t.IsTrue(generated.find("ps2___is_pointer(rdram, ctx, runtime); return;") != std::string::npos,
"call should use sanitized name");
}); });
"call should use sanitized name but got: " + generated);
});
tc.Run("JAL to known function emits call and check", [](TestCase &t) {
Function func;
func.name = "jal_test";
func.start = 0xA000;
func.end = 0xA020;
func.isRecompiled = true;
func.isStub = false;
Symbol targetSym;
targetSym.name = "some_func";
targetSym.address = 0xB000;
targetSym.isFunction = true;
// 0xA000: JAL 0xB000
// 0xA004: NOP (delay slot)
Instruction jal = makeJal(0xA000, 0xB000);
Instruction delay = makeNop(0xA004);
CodeGenerator gen({targetSym});
std::string generated = gen.generateFunction(func, {jal, delay}, false);
printGeneratedCode("JAL to known function emits call and check", generated);
// Expect:
// SET_GPR_U32(ctx, 31, 0xA008u);
// ctx->pc = 0xA004u;
// ... delay slot ...
// some_func(rdram, ctx, runtime);
// if (ctx->pc != 0xA008u) { return; }
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xA008u);") != std::string::npos, "JAL should set RA");
t.IsTrue(generated.find("some_func(rdram, ctx, runtime);") != std::string::npos, "JAL should call function");
t.IsTrue(generated.find("if (ctx->pc != 0xA008u) { return; }") != std::string::npos, "JAL should check return PC");
});
tc.Run("JAL to internal target becomes goto", [](TestCase &t) {
Function func;
func.name = "jal_internal";
func.start = 0xC000;
func.end = 0xC020;
func.isRecompiled = true;
func.isStub = false;
// 0xC000: JAL 0xC010
// 0xC004: NOP
// ...
// 0xC010: NOP
Instruction jal = makeJal(0xC000, 0xC010);
Instruction delay = makeNop(0xC004);
Instruction targetInst = makeNop(0xC010);
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, {jal, delay, targetInst}, false);
printGeneratedCode("JAL to internal target becomes goto", generated);
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xC008u);") != std::string::npos, "Internal JAL should set RA");
t.IsTrue(generated.find("goto label_c010;") != std::string::npos, "Internal JAL should use goto");
});
tc.Run("JALR emits indirect call", [](TestCase &t) {
Function func;
func.name = "jalr_test";
func.start = 0xD000;
func.end = 0xD020;
func.isRecompiled = true;
func.isStub = false;
// 0xD000: JALR $4, $31 (call addr in $4, link to $31)
// 0xD004: NOP
Instruction jalr = makeJalr(0xD000, 4, 31);
Instruction delay = makeNop(0xD004);
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, {jalr, delay}, false);
printGeneratedCode("JALR emits indirect call", generated);
t.IsTrue(generated.find("uint32_t jumpTarget = GPR_U32(ctx, 4);") != std::string::npos, "JALR should read target from RS");
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xD008u);") != std::string::npos, "JALR should set link register");
t.IsTrue(generated.find("auto targetFn = runtime->lookupFunction(jumpTarget);") != std::string::npos, "JALR should lookup function");
t.IsTrue(generated.find("targetFn(rdram, ctx, runtime);") != std::string::npos, "JALR should call function");
t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC");
});
tc.Run("backward BEQ emits label and goto (sign-extended offset)", [](TestCase &t) {
Function func;
func.name = "backward_branch";
func.start = 0x1100;
func.end = 0x1120;
func.isRecompiled = true;
func.isStub = false;
// 0x1100: nop
// 0x1104: beq $1,$1, target 0x1100 (offset = -2 words)
// 0x1108: nop (delay)
std::vector<Instruction> instructions;
instructions.push_back(makeNop(0x1100));
Instruction br = makeBranch(0x1104, 0);
br.simmediate = static_cast<uint32_t>(static_cast<int16_t>(-2));
instructions.push_back(br);
instructions.push_back(makeNop(0x1108));
instructions.push_back(makeNop(0x110c));
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, instructions, false);
printGeneratedCode("backward BEQ emits label and goto (sign-extended offset)", generated);
t.IsTrue(generated.find("label_1100:") != std::string::npos, "target should emit a label");
t.IsTrue(generated.find("goto label_1100;") != std::string::npos, "backward internal branch should goto label");
});
tc.Run("branch-likely places delay slot only in taken path", [](TestCase &t) {
Function func;
func.name = "branch_likely";
func.start = 0x1200;
func.end = 0x1220;
func.isRecompiled = true;
func.isStub = false;
Instruction br{};
br.address = 0x1200;
br.opcode = OPCODE_BEQL; // likely
br.rs = 1;
br.rt = 2;
br.simmediate = 1; // target = 0x1208
br.isBranch = true;
br.hasDelaySlot = true;
br.raw = 0;
Instruction delay{};
delay.address = 0x1204;
delay.opcode = OPCODE_ADDIU;
delay.rs = 0;
delay.rt = 7; // make it non-nop so translation is distinctive
delay.simmediate = 123;
delay.raw = 0;
Instruction target = makeNop(0x1208);
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, { br, delay, target }, false);
printGeneratedCode("branch-likely places delay slot only in taken path", generated);
t.IsTrue(generated.find("SET_GPR_S32(ctx, 7,") != std::string::npos, "delay slot should be translated");
t.IsTrue(generated.find("if (branch_taken_0x1200)") != std::string::npos, "should generate branch_taken variable and if for likely branch");
});
tc.Run("JR $31 emits switch for internal return targets", [](TestCase &t) {
Function func;
func.name = "jr_ra_switch";
func.start = 0x1300;
func.end = 0x1340;
func.isRecompiled = true;
func.isStub = false;
// Create an internal JAL so collectInternalBranchTargets inserts returnAddr (0x1308) as internal target.
Instruction jal = makeJal(0x1300, 0x1310);
Instruction jalDelay = makeNop(0x1304);
Instruction atTarget = makeNop(0x1310);
// JR $31 at 0x1314 with delay slot at 0x1318
Instruction jr = makeJr(0x1314, 31);
Instruction jrDelay = makeNop(0x1318);
CodeGenerator gen({});
std::string generated = gen.generateFunction(func, { jal, jalDelay, atTarget, jr, jrDelay }, false);
printGeneratedCode("JR $31 emits switch for internal return targets", generated);
t.IsTrue(generated.find("switch (jumpTarget)") != std::string::npos, "JR $31 should emit switch for internal targets");
t.IsTrue(generated.find("case 0x1308u: goto label_1308;") != std::string::npos, "switch should include return address from internal JAL");
});
});
}
+235
View File
@@ -0,0 +1,235 @@
#include "MiniTest.h"
#include "ps2recomp/elf_analyzer.h"
#include "ps2recomp/instructions.h"
#include "ps2recomp/types.h"
#include <unordered_map>
#include <vector>
using namespace ps2recomp;
namespace
{
Instruction makeInstruction(uint32_t address, uint32_t opcode)
{
Instruction inst;
inst.address = address;
inst.opcode = opcode;
return inst;
}
}
void register_elf_analyzer_tests()
{
MiniTest::Case("ElfAnalyzerHeuristics", [](TestCase &tc)
{
tc.Run("library-symbol classification table", [](TestCase &t)
{
ElfAnalyzer analyzer("dummy.elf");
t.IsTrue(analyzer.isLibrarySymbolNameForHeuristics("printf"),
"printf should be classified as library");
t.IsTrue(analyzer.isLibrarySymbolNameForHeuristics("_printf"),
"_printf should be classified as library");
t.IsTrue(analyzer.isLibrarySymbolNameForHeuristics("sceCdRead"),
"sce-prefixed PS2 API should be classified as library");
t.IsFalse(analyzer.isLibrarySymbolNameForHeuristics("bhEne13_Brain"),
"named game function should not be classified as library");
t.IsFalse(analyzer.isLibrarySymbolNameForHeuristics("sub_00100C00"),
"unreliable auto-generated names should not be classified as library"); });
tc.Run("reliable-symbol heuristic filters autogenerated names", [](TestCase &t)
{
t.IsTrue(ElfAnalyzer::isReliableSymbolNameForHeuristics("bhEne13_Brain"),
"expected game symbol to be considered reliable");
t.IsTrue(ElfAnalyzer::isReliableSymbolNameForHeuristics("SetupSoundDriver"),
"expected named function to be considered reliable");
t.IsTrue(ElfAnalyzer::isReliableSymbolNameForHeuristics("sceCdRead"),
"expected PS2 API symbol to be considered reliable");
t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("sub_00100C00"),
"sub_ prefix should be treated as unreliable");
t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("func_1ABC"),
"func_ prefix should be treated as unreliable");
t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("entry_001000"),
"entry_ prefix should be treated as unreliable");
t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("LAB_00001234"),
"LAB_ prefix should be treated as unreliable");
t.IsFalse(ElfAnalyzer::isReliableSymbolNameForHeuristics("0x00100ABC"),
"pure hex-style symbol should be treated as unreliable"); });
tc.Run("system-symbol heuristic is strict to system patterns", [](TestCase &t)
{
t.IsTrue(ElfAnalyzer::isSystemSymbolNameForHeuristics("__main"),
"__main should be classified as system");
t.IsTrue(ElfAnalyzer::isSystemSymbolNameForHeuristics("_start"),
"_start should be classified as system");
t.IsTrue(ElfAnalyzer::isSystemSymbolNameForHeuristics(".text.startup"),
".text.* should be classified as system");
t.IsFalse(ElfAnalyzer::isSystemSymbolNameForHeuristics("bhObj001"),
"game symbol should not be classified as system");
t.IsFalse(ElfAnalyzer::isSystemSymbolNameForHeuristics("SetupSoundDriver"),
"engine/game symbol should not be classified as system");
t.IsFalse(ElfAnalyzer::isSystemSymbolNameForHeuristics("sub_00100C00"),
"unreliable names should not be considered system by this classifier"); });
tc.Run("entry-point mapping handles exact inside and fallback", [](TestCase &t)
{
Function f1;
f1.name = "funcA";
f1.start = 0x1000;
f1.end = 0x1100;
Function f2;
f2.name = "funcB";
f2.start = 0x1100;
f2.end = 0x1200;
Function f3;
f3.name = "fallbackA";
f3.start = 0x100000;
f3.end = 0x100100;
std::vector<Function> functions{f1, f2, f3};
t.Equals(ElfAnalyzer::findEntryFunctionIndexForHeuristics(functions, 0x1100), 1,
"exact entry should map to function start");
t.Equals(ElfAnalyzer::findEntryFunctionIndexForHeuristics(functions, 0x10F0), 0,
"entry inside range should map to containing function");
t.Equals(ElfAnalyzer::findEntryFunctionIndexForHeuristics(functions, 0x2000), -1,
"unknown entry should return no mapping");
t.Equals(ElfAnalyzer::findFallbackEntryFunctionIndexForHeuristics(functions), 2,
"fallback should find 0x100000 entry");
Function fallbackB;
fallbackB.name = "fallbackB";
fallbackB.start = 0x80100000;
fallbackB.end = 0x80100100;
std::vector<Function> fallbackOnly{fallbackB};
t.Equals(ElfAnalyzer::findFallbackEntryFunctionIndexForHeuristics(fallbackOnly), 0,
"fallback should also accept 0x80100000"); });
tc.Run("signal-based skip heuristics keep reliable names and skip unreliable/system", [](TestCase &t)
{
// Hardware I/O signal via LUI upper address in I/O region.
Instruction hw = makeInstruction(0x1000, OPCODE_LUI);
hw.immediate = 0x1002; // 0x10020000
std::vector<Instruction> hwInst{hw};
const bool hasHardwareIO = ElfAnalyzer::hasHardwareIOSignalForHeuristics(hwInst);
t.IsTrue(hasHardwareIO, "hardware I/O signal should be detected");
// Large + complex MMI signal.
std::vector<Instruction> largeMmi(501);
largeMmi[250] = makeInstruction(0x2000, OPCODE_MMI);
largeMmi[250].isMMI = true;
largeMmi[250].function = MMI_MMI1;
const bool hasLargeComplexMMI = ElfAnalyzer::hasLargeComplexMMISignalForHeuristics(largeMmi);
t.IsTrue(hasLargeComplexMMI, "large complex MMI signal should be detected");
// Self-modifying signal: SW into a code section, with base from preceding LUI.
Instruction lui = makeInstruction(0x3000, OPCODE_LUI);
lui.rt = 9;
lui.immediate = 0x1000; // base 0x10000000
Instruction sw = makeInstruction(0x3004, OPCODE_SW);
sw.rs = 9;
sw.immediate = 0x2000; // target 0x10002000
std::vector<Instruction> smcInst{lui, sw};
Section code{};
code.name = ".text";
code.address = 0x10002000;
code.size = 0x100;
code.isCode = true;
std::vector<Section> sections{code};
const bool hasSelfModifying = ElfAnalyzer::hasSelfModifyingSignalForHeuristics(smcInst, sections);
t.IsTrue(hasSelfModifying, "self-modifying signal should be detected");
// Decision behavior by name reliability/system-ness.
t.IsFalse(hasHardwareIO && ElfAnalyzer::shouldAutoSkipNameForHeuristics("bhEne13_Brain"),
"reliable game symbol should not auto-skip from hardware signal alone");
t.IsTrue(hasHardwareIO && ElfAnalyzer::shouldAutoSkipNameForHeuristics("sub_00100C00"),
"unreliable symbol should auto-skip when risky signals exist");
t.IsTrue(hasLargeComplexMMI && ElfAnalyzer::shouldAutoSkipNameForHeuristics("__main"),
"system symbol should auto-skip when risky signals exist");
t.IsFalse(hasSelfModifying && ElfAnalyzer::shouldAutoSkipNameForHeuristics("topThread"),
"do-not-skip list should override auto-skip"); });
tc.Run("patch-density threshold behavior", [](TestCase &t)
{
t.IsTrue(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("sub_00100C00", 100, 6, false),
"high-density patches on unreliable names should skip");
t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("sub_00100C00", 200, 6, false),
"density below threshold should not skip");
t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("sub_00100C00", 100, 5, false),
"patch count <= 5 should not skip");
t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("printf", 100, 6, true),
"library functions should not be auto-skipped by patch density");
t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("bhEne13_Brain", 100, 6, false),
"reliable game function should not be auto-skipped by patch density");
t.IsFalse(ElfAnalyzer::shouldSkipForPatchDensityForHeuristics("topThread", 100, 6, false),
"do-not-skip names should never be auto-skipped"); });
tc.Run("jump-table detection finds canonical sltiu/bne/lw/jr pattern", [](TestCase &t)
{
// sltiu -> bne/beq bounds check -> ... -> lui/addiu base -> lw -> jr loadedReg
Instruction sltiu = makeInstruction(0x4000, OPCODE_SLTIU);
sltiu.immediate = 3; // number of entries
Instruction bne = makeInstruction(0x4004, OPCODE_BNE);
Instruction filler = makeInstruction(0x4008, OPCODE_ADDIU);
Instruction jtLui = makeInstruction(0x400C, OPCODE_LUI);
jtLui.rt = 8;
jtLui.immediate = 0x2000;
Instruction jtAddiu = makeInstruction(0x4010, OPCODE_ADDIU);
jtAddiu.rs = 8;
jtAddiu.rt = 9; // load base register
jtAddiu.immediate = 0x0100;
Instruction jtLoad = makeInstruction(0x4014, OPCODE_LW);
jtLoad.rs = 9;
jtLoad.rt = 10;
Instruction jtJump = makeInstruction(0x4018, OPCODE_SPECIAL);
jtJump.function = SPECIAL_JR;
jtJump.rs = 10;
std::vector<Instruction> instructions{sltiu, bne, filler, jtLui, jtAddiu, jtLoad, jtJump};
const uint32_t base = (0x2000u << 16) | 0x0100u;
std::unordered_map<uint32_t, uint32_t> tableMemory{
{base + 0, 0x101000},
{base + 4, 0x102000},
{base + 8, 0x103000},
};
auto readWord = [&tableMemory](uint32_t address, uint32_t &outWord) -> bool
{
auto it = tableMemory.find(address);
if (it == tableMemory.end())
{
return false;
}
outWord = it->second;
return true;
};
auto jumpTables = ElfAnalyzer::detectJumpTablesForHeuristics(instructions, std::vector<Section>(), readWord);
t.Equals(jumpTables.size(), static_cast<size_t>(1), "one jump table should be detected");
if (!jumpTables.empty())
{
t.Equals(jumpTables[0].address, base, "jump table base address should match LUI/ADDIU pattern");
t.Equals(jumpTables[0].baseRegister, static_cast<uint32_t>(9), "base register should match LW base");
t.Equals(jumpTables[0].entries.size(), static_cast<size_t>(3), "entry count should match SLTIU bound");
t.Equals(jumpTables[0].entries[0].target, static_cast<uint32_t>(0x101000), "entry 0 target should match");
t.Equals(jumpTables[0].entries[1].target, static_cast<uint32_t>(0x102000), "entry 1 target should match");
t.Equals(jumpTables[0].entries[2].target, static_cast<uint32_t>(0x103000), "entry 2 target should match");
}
Instruction invalid = sltiu;
invalid.immediate = 1001; // rejected by guard
auto invalidTables = ElfAnalyzer::detectJumpTablesForHeuristics(
std::vector<Instruction>{invalid, bne, filler, jtLui, jtAddiu, jtLoad, jtJump}, std::vector<Section>(),
readWord);
t.Equals(invalidTables.size(), static_cast<size_t>(0),
"bounds over guard limit should not produce a jump table"); }); });
}
+2
View File
@@ -2,10 +2,12 @@
void register_code_generator_tests();
void register_r5900_decoder_tests();
void register_elf_analyzer_tests();
int main()
{
register_code_generator_tests();
register_r5900_decoder_tests();
register_elf_analyzer_tests();
return MiniTest::Run();
}