mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-30 10:14:24 -04:00
feat: explode runtime in folder to be more easy to place recompiled code
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include "raylib.h"
|
||||
|
||||
#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian
|
||||
#define ET_EXEC 2 // Executable file
|
||||
|
||||
#define EM_MIPS 8 // MIPS architecture
|
||||
|
||||
struct ElfHeader
|
||||
{
|
||||
uint32_t magic;
|
||||
uint8_t elf_class;
|
||||
uint8_t endianness;
|
||||
uint8_t version;
|
||||
uint8_t os_abi;
|
||||
uint8_t abi_version;
|
||||
uint8_t padding[7];
|
||||
uint16_t type;
|
||||
uint16_t machine;
|
||||
uint32_t version2;
|
||||
uint32_t entry;
|
||||
uint32_t phoff;
|
||||
uint32_t shoff;
|
||||
uint32_t flags;
|
||||
uint16_t ehsize;
|
||||
uint16_t phentsize;
|
||||
uint16_t phnum;
|
||||
uint16_t shentsize;
|
||||
uint16_t shnum;
|
||||
uint16_t shstrndx;
|
||||
};
|
||||
|
||||
struct ProgramHeader
|
||||
{
|
||||
uint32_t type;
|
||||
uint32_t offset;
|
||||
uint32_t vaddr;
|
||||
uint32_t paddr;
|
||||
uint32_t filesz;
|
||||
uint32_t memsz;
|
||||
uint32_t flags;
|
||||
uint32_t align;
|
||||
};
|
||||
|
||||
#define PT_LOAD 1 // Loadable segment
|
||||
|
||||
static constexpr int FB_WIDTH = 640;
|
||||
static constexpr int FB_HEIGHT = 448;
|
||||
static constexpr uint32_t DEFAULT_FB_ADDR = 0x00100000; // location in RDRAM the guest will draw to
|
||||
|
||||
static void UploadFrame(Texture2D &tex, PS2Runtime *rt)
|
||||
{
|
||||
uint8_t *src = rt->memory().getRDRAM() + (DEFAULT_FB_ADDR & 0x1FFFFFFF);
|
||||
UpdateTexture(tex, src);
|
||||
}
|
||||
|
||||
PS2Runtime::PS2Runtime()
|
||||
{
|
||||
std::memset(&m_cpuContext, 0, sizeof(m_cpuContext));
|
||||
|
||||
// R0 is always zero in MIPS
|
||||
m_cpuContext.r[0] = _mm_set1_epi32(0);
|
||||
|
||||
// Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF
|
||||
|
||||
m_functionTable.clear();
|
||||
|
||||
m_loadedModules.clear();
|
||||
}
|
||||
|
||||
PS2Runtime::~PS2Runtime()
|
||||
{
|
||||
m_loadedModules.clear();
|
||||
|
||||
m_functionTable.clear();
|
||||
}
|
||||
|
||||
bool PS2Runtime::initialize(const char *title)
|
||||
{
|
||||
if (!m_memory.initialize())
|
||||
{
|
||||
std::cerr << "Failed to initialize PS2 memory" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
||||
InitWindow(FB_WIDTH, FB_HEIGHT, title);
|
||||
SetTargetFPS(60);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2Runtime::loadELF(const std::string &elfPath)
|
||||
{
|
||||
std::ifstream file(elfPath, std::ios::binary);
|
||||
if (!file)
|
||||
{
|
||||
std::cerr << "Failed to open ELF file: " << elfPath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
ElfHeader header;
|
||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
||||
|
||||
if (header.magic != ELF_MAGIC)
|
||||
{
|
||||
std::cerr << "Invalid ELF magic number" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.machine != EM_MIPS || header.type != ET_EXEC)
|
||||
{
|
||||
std::cerr << "Not a MIPS executable ELF file" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_cpuContext.pc = header.entry;
|
||||
|
||||
for (uint16_t i = 0; i < header.phnum; i++)
|
||||
{
|
||||
ProgramHeader ph;
|
||||
file.seekg(header.phoff + i * header.phentsize);
|
||||
file.read(reinterpret_cast<char *>(&ph), sizeof(ph));
|
||||
|
||||
if (ph.type == PT_LOAD && ph.filesz > 0)
|
||||
{
|
||||
std::cout << "Loading segment: 0x" << std::hex << ph.vaddr
|
||||
<< " - 0x" << (ph.vaddr + ph.memsz)
|
||||
<< " (size: 0x" << ph.memsz << ")" << std::dec << std::endl;
|
||||
|
||||
// Allocate temporary buffer for the segment
|
||||
std::vector<uint8_t> buffer(ph.filesz);
|
||||
|
||||
// Read segment data
|
||||
file.seekg(ph.offset);
|
||||
file.read(reinterpret_cast<char *>(buffer.data()), ph.filesz);
|
||||
|
||||
// Copy to memory
|
||||
uint32_t physAddr = m_memory.translateAddress(ph.vaddr);
|
||||
uint8_t *dest = nullptr;
|
||||
if (ph.vaddr >= PS2_SCRATCHPAD_BASE && ph.vaddr < PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE)
|
||||
{
|
||||
dest = m_memory.getScratchpad() + physAddr;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest = m_memory.getRDRAM() + physAddr;
|
||||
}
|
||||
std::memcpy(dest, buffer.data(), ph.filesz);
|
||||
|
||||
if (ph.memsz > ph.filesz)
|
||||
{
|
||||
std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz);
|
||||
}
|
||||
|
||||
// Track executable regions for self-modifying code invalidation
|
||||
if (ph.flags & 0x1) // PF_X
|
||||
{
|
||||
m_memory.registerCodeRegion(ph.vaddr, ph.vaddr + ph.memsz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadedModule module;
|
||||
module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1);
|
||||
module.baseAddress = 0x00100000; // Typical base address for PS2 executables
|
||||
module.size = 0; // Would need to calculate from segments
|
||||
module.active = true;
|
||||
|
||||
m_loadedModules.push_back(module);
|
||||
|
||||
std::cout << "ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func)
|
||||
{
|
||||
m_functionTable[address] = func;
|
||||
}
|
||||
|
||||
PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
|
||||
{
|
||||
auto it = m_functionTable.find(address);
|
||||
if (it != m_functionTable.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::cerr << "Warning: Function at address 0x" << std::hex << address << std::dec << " not found" << std::endl;
|
||||
|
||||
static RecompiledFunction defaultFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
std::cerr << "Error: Called unimplemented function at address 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
};
|
||||
|
||||
return defaultFunction;
|
||||
}
|
||||
|
||||
void PS2Runtime::SignalException(R5900Context *ctx, PS2Exception exception)
|
||||
{
|
||||
if (exception == EXCEPTION_INTEGER_OVERFLOW)
|
||||
{
|
||||
// PS2 behavior: jump to exception handler
|
||||
HandleIntegerOverflow(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address)
|
||||
{
|
||||
std::cout << "VU0 microprogram call to address 0x" << std::hex << address
|
||||
<< " - not implemented" << std::dec << std::endl;
|
||||
|
||||
// mayve implement like this or a vu0_interpreter
|
||||
// Placeholder for VU0 microprogram execution
|
||||
// auto microprog = findCompiledMicroprogram(address);
|
||||
// if (microprog) microprog(rdram, ctx);
|
||||
}
|
||||
|
||||
void PS2Runtime::vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address)
|
||||
{
|
||||
std::cout << "VU0 microprogram call to address 0x" << std::hex << address
|
||||
<< " - not implemented" << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "Syscall encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleBreak(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "Break encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleTrap(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "Trap encountered at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleTLBR(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "TLBR (TLB Read) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleTLBWI(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "TLBWI (TLB Write Indexed) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleTLBWR(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "TLBWR (TLB Write Random) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::handleTLBP(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
std::cout << "TLBP (TLB Probe) at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::clearLLBit(R5900Context *ctx)
|
||||
{
|
||||
ctx->cop0_status &= ~0x00000002; // LL bit is bit 1 in the status register
|
||||
std::cout << "LL bit cleared at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
}
|
||||
|
||||
void PS2Runtime::HandleIntegerOverflow(R5900Context *ctx)
|
||||
{
|
||||
std::cerr << "Integer overflow exception at PC: 0x" << std::hex << ctx->pc << std::dec << std::endl;
|
||||
|
||||
// Set the EPC (Exception Program Counter) to the current PC
|
||||
m_cpuContext.cop0_epc = ctx->pc;
|
||||
|
||||
// Set the cause register to indicate an integer overflow
|
||||
m_cpuContext.cop0_cause |= (EXCEPTION_INTEGER_OVERFLOW << 2);
|
||||
|
||||
// Jump to the exception handler (usually at 0x80000000)
|
||||
m_cpuContext.pc = 0x80000000; // Default PS2 exception handler address
|
||||
}
|
||||
|
||||
void PS2Runtime::run()
|
||||
{
|
||||
RecompiledFunction entryPoint = lookupFunction(m_cpuContext.pc);
|
||||
|
||||
m_cpuContext.r[4] = _mm_set1_epi32(0); // A0 = 0 (argc)
|
||||
m_cpuContext.r[5] = _mm_set1_epi32(0); // A1 = 0 (argv)
|
||||
m_cpuContext.r[29] = _mm_set1_epi32(0x02000000); // SP = top of RAM
|
||||
|
||||
std::cout << "Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec << std::endl;
|
||||
|
||||
// A blank image to use as a framebuffer
|
||||
Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK);
|
||||
Texture2D frameTex = LoadTextureFromImage(blank);
|
||||
UnloadImage(blank);
|
||||
|
||||
std::atomic<bool> running{true};
|
||||
|
||||
std::thread gameThread([&, entryPoint]() {
|
||||
try
|
||||
{
|
||||
entryPoint(m_memory.getRDRAM(), &m_cpuContext, this);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error during program execution: " << e.what() << std::endl;
|
||||
}
|
||||
running = false;
|
||||
});
|
||||
|
||||
while (running && !WindowShouldClose())
|
||||
{
|
||||
UploadFrame(frameTex, this);
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
DrawTexture(frameTex, 0, 0, WHITE);
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
if (!running)
|
||||
{
|
||||
// Game thread finished on its own
|
||||
if (gameThread.joinable())
|
||||
{
|
||||
gameThread.join();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Window was closed while the game thread is still running
|
||||
if (gameThread.joinable())
|
||||
{
|
||||
gameThread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
UnloadTexture(frameTex);
|
||||
CloseWindow();
|
||||
}
|
||||
Reference in New Issue
Block a user