mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-27 09:05:28 -04:00
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:
@@ -1,88 +1,108 @@
|
||||
## PS2Recomp: PlayStation 2 Static Recompiler (Not ready)
|
||||
## PS2Recomp: PlayStation 2 Static Recompiler (Experimental)
|
||||
|
||||
[](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
@@ -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
|
||||
)
|
||||
|
||||
@@ -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> §ions);
|
||||
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> §ions, 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
|
||||
|
||||
+901
-388
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 §ionName) const;
|
||||
uint32_t getSectionSize(const std::string §ionName) const;
|
||||
uint32_t getEntryPoint() const;
|
||||
void debugAddress(uint32_t address) const;
|
||||
|
||||
private:
|
||||
std::string m_filePath;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 §ion : 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++;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 ®ion)
|
||||
|
||||
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 ®ion : 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 ®ion : 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 ®ion : 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
+2559
-341
File diff suppressed because it is too large
Load Diff
+4035
-247
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
)
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user