mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-25 07:05:40 +00:00
System: Support sideloading EXE files via BIOS patch
This commit is contained in:
parent
ae43cc838b
commit
9f36384752
|
@ -178,6 +178,8 @@ bool SDLInterface::InitializeSystem(const char* filename, s32 save_state_index /
|
|||
|
||||
m_system->Reset();
|
||||
|
||||
//m_system->LoadEXE("tests/psxtest_cpu.psxexe");
|
||||
|
||||
// Resume execution.
|
||||
m_running = true;
|
||||
return true;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "YBaseLib/ByteStream.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "YBaseLib/String.h"
|
||||
#include "cpu_disasm.h"
|
||||
#include "dma.h"
|
||||
#include "gpu.h"
|
||||
#include <cstdio>
|
||||
|
@ -70,6 +71,24 @@ bool Bus::WriteWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus
|
|||
return DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(cpu_address, bus_address, value);
|
||||
}
|
||||
|
||||
void Bus::PatchBIOS(u32 address, u32 value, u32 mask /*= UINT32_C(0xFFFFFFFF)*/)
|
||||
{
|
||||
const u32 phys_address = address & UINT32_C(0x1FFFFFFF);
|
||||
const u32 offset = phys_address - BIOS_BASE;
|
||||
Assert(phys_address >= BIOS_BASE && offset < BIOS_SIZE);
|
||||
|
||||
u32 existing_value;
|
||||
std::memcpy(&existing_value, &m_bios[offset], sizeof(existing_value));
|
||||
u32 new_value = (existing_value & ~mask) | value;
|
||||
std::memcpy(&m_bios[offset], &new_value, sizeof(new_value));
|
||||
|
||||
SmallString old_disasm, new_disasm;
|
||||
CPU::DisassembleInstruction(&old_disasm, address, existing_value);
|
||||
CPU::DisassembleInstruction(&new_disasm, address, new_value);
|
||||
Log_InfoPrintf("BIOS-Patch 0x%08X (+0x%X): 0x%08X %s -> %08X %s", address, offset, existing_value,
|
||||
old_disasm.GetCharArray(), new_value, new_disasm.GetCharArray());
|
||||
}
|
||||
|
||||
bool Bus::LoadBIOS()
|
||||
{
|
||||
std::FILE* fp = std::fopen("SCPH1001.BIN", "rb");
|
||||
|
@ -97,9 +116,9 @@ bool Bus::LoadBIOS()
|
|||
std::fclose(fp);
|
||||
|
||||
#if 1
|
||||
auto Patch = [this](u32 address, u32 value) { std::memcpy(&m_bios[address], &value, sizeof(value)); };
|
||||
Patch(0x6F0C, 0x24010001); // addiu $at, $zero, 1
|
||||
Patch(0x6F14, 0xaf81a9c0); // sw at, -0x5640(gp)
|
||||
// Patch to enable TTY.
|
||||
PatchBIOS(BIOS_BASE + 0x6F0C, 0x24010001);
|
||||
PatchBIOS(BIOS_BASE + 0x6F14, 0xAF81A9C0);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
|
|
|
@ -29,6 +29,8 @@ public:
|
|||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32& value);
|
||||
|
||||
void PatchBIOS(u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));
|
||||
|
||||
private:
|
||||
static constexpr u32 DMA_BASE = 0x1F801080;
|
||||
static constexpr u32 DMA_SIZE = 0x80;
|
||||
|
@ -42,6 +44,8 @@ private:
|
|||
static constexpr u32 EXP2_BASE = 0x1F802000;
|
||||
static constexpr u32 EXP2_SIZE = 0x2000;
|
||||
static constexpr u32 EXP2_MASK = EXP2_SIZE - 1;
|
||||
static constexpr u32 BIOS_BASE = 0x1FC00000;
|
||||
static constexpr u32 BIOS_SIZE = 0x80000;
|
||||
|
||||
bool LoadBIOS();
|
||||
|
||||
|
|
|
@ -116,13 +116,13 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres
|
|||
return (type == MemoryAccessType::Read) ? ReadExpansionRegion2(size, bus_address & EXP2_MASK, value) :
|
||||
WriteExpansionRegion2(size, bus_address & EXP2_MASK, value);
|
||||
}
|
||||
else if (bus_address < 0x1FC00000)
|
||||
else if (bus_address < BIOS_BASE)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < 0x20000000)
|
||||
else if (bus_address < (BIOS_BASE + BIOS_SIZE))
|
||||
{
|
||||
return DoBIOSAccess<type, size>(static_cast<u32>(bus_address - UINT32_C(0x1FC00000)), value);
|
||||
return DoBIOSAccess<type, size>(static_cast<u32>(bus_address - BIOS_BASE), value);
|
||||
}
|
||||
else if (bus_address < 0x1FFE0000)
|
||||
{
|
||||
|
|
|
@ -30,10 +30,16 @@ bool Core::DoState(StateWrapper& sw)
|
|||
return false;
|
||||
}
|
||||
|
||||
void Core::SetPC(u32 new_pc)
|
||||
{
|
||||
m_regs.npc = new_pc;
|
||||
FlushPipeline();
|
||||
}
|
||||
|
||||
u8 Core::ReadMemoryByte(VirtualMemoryAddress addr)
|
||||
{
|
||||
u32 value;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte, false>(addr, value);
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte, false, true>(addr, value);
|
||||
return Truncate8(value);
|
||||
}
|
||||
|
||||
|
@ -41,7 +47,7 @@ u16 Core::ReadMemoryHalfWord(VirtualMemoryAddress addr)
|
|||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 2));
|
||||
u32 value;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord, false>(addr, value);
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord, false, true>(addr, value);
|
||||
return Truncate16(value);
|
||||
}
|
||||
|
||||
|
@ -49,27 +55,65 @@ u32 Core::ReadMemoryWord(VirtualMemoryAddress addr)
|
|||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 4));
|
||||
u32 value;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, false>(addr, value);
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, false, true>(addr, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Core::WriteMemoryByte(VirtualMemoryAddress addr, u8 value)
|
||||
{
|
||||
u32 value32 = ZeroExtend32(value);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte, false>(addr, value32);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte, false, true>(addr, value32);
|
||||
}
|
||||
|
||||
void Core::WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value)
|
||||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 2));
|
||||
u32 value32 = ZeroExtend32(value);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord, false>(addr, value32);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord, false, true>(addr, value32);
|
||||
}
|
||||
|
||||
void Core::WriteMemoryWord(VirtualMemoryAddress addr, u32 value)
|
||||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 4));
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word, false>(addr, value);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word, false, true>(addr, value);
|
||||
}
|
||||
|
||||
bool Core::SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value)
|
||||
{
|
||||
u32 temp = 0;
|
||||
const bool result = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte, false, false>(addr, temp);
|
||||
*value = Truncate8(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Core::SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value)
|
||||
{
|
||||
u32 temp = 0;
|
||||
const bool result = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord, false, false>(addr, temp);
|
||||
*value = Truncate16(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Core::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value)
|
||||
{
|
||||
return DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, false, false>(addr, *value);
|
||||
}
|
||||
|
||||
bool Core::SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value)
|
||||
{
|
||||
u32 temp = ZeroExtend32(value);
|
||||
return DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte, false, false>(addr, temp);
|
||||
}
|
||||
|
||||
bool Core::SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value)
|
||||
{
|
||||
u32 temp = ZeroExtend32(value);
|
||||
return DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord, false, false>(addr, temp);
|
||||
}
|
||||
|
||||
bool Core::SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value)
|
||||
{
|
||||
return DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word, false, false>(addr, value);
|
||||
}
|
||||
|
||||
void Core::Branch(u32 target)
|
||||
|
@ -153,7 +197,7 @@ static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value)
|
|||
void Core::DisassembleAndPrint(u32 addr)
|
||||
{
|
||||
u32 bits;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true>(addr, bits);
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true, false>(addr, bits);
|
||||
PrintInstruction(bits, addr);
|
||||
}
|
||||
|
||||
|
@ -182,22 +226,25 @@ void Core::Execute()
|
|||
|
||||
void Core::FetchInstruction()
|
||||
{
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true>(static_cast<VirtualMemoryAddress>(m_regs.npc),
|
||||
m_next_instruction.bits);
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true, true>(
|
||||
static_cast<VirtualMemoryAddress>(m_regs.npc), m_next_instruction.bits);
|
||||
m_regs.pc = m_regs.npc;
|
||||
m_regs.npc += sizeof(m_next_instruction.bits);
|
||||
}
|
||||
|
||||
void Core::ExecuteInstruction(Instruction inst, u32 inst_pc)
|
||||
{
|
||||
#if 0
|
||||
if (inst_pc == 0xBFC06FF0)
|
||||
{
|
||||
TRACE_EXECUTION = true;
|
||||
__debugbreak();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (TRACE_EXECUTION)
|
||||
PrintInstruction(inst.bits, inst_pc);
|
||||
|
||||
#if 0
|
||||
if (inst_pc == 0x8005ab80)
|
||||
__debugbreak();
|
||||
#endif
|
||||
|
||||
switch (inst.op)
|
||||
{
|
||||
case InstructionOp::funct:
|
||||
|
|
|
@ -23,9 +23,22 @@ public:
|
|||
|
||||
void Execute();
|
||||
|
||||
const Registers& GetRegs() const { return m_regs; }
|
||||
Registers& GetRegs() { return m_regs; }
|
||||
|
||||
// Sets the PC and flushes the pipeline.
|
||||
void SetPC(u32 new_pc);
|
||||
|
||||
bool SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value);
|
||||
bool SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value);
|
||||
bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value);
|
||||
bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value);
|
||||
bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
|
||||
bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
||||
|
||||
private:
|
||||
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch>
|
||||
void DoMemoryAccess(VirtualMemoryAddress address, u32& value);
|
||||
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch, bool raise_exceptions>
|
||||
bool DoMemoryAccess(VirtualMemoryAddress address, u32& value);
|
||||
|
||||
u8 ReadMemoryByte(VirtualMemoryAddress addr);
|
||||
u16 ReadMemoryHalfWord(VirtualMemoryAddress addr);
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
namespace CPU {
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch>
|
||||
void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
|
||||
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch, bool raise_exceptions>
|
||||
bool Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
|
||||
{
|
||||
switch (address >> 29)
|
||||
{
|
||||
|
@ -15,13 +15,17 @@ void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
|
|||
if constexpr (type == MemoryAccessType::Write)
|
||||
{
|
||||
if (m_cop0_regs.sr.Isc)
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
|
||||
{
|
||||
Panic("Bus error");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01: // KUSEG 512M-1024M
|
||||
case 0x02: // KUSEG 1024M-1536M
|
||||
|
@ -29,26 +33,36 @@ void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
|
|||
{
|
||||
// Above 512mb raises an exception.
|
||||
Panic("Bad user access");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: // KSEG0 - physical memory cached
|
||||
{
|
||||
if constexpr (type == MemoryAccessType::Write)
|
||||
{
|
||||
if (m_cop0_regs.sr.Isc)
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
|
||||
{
|
||||
Panic("Bus error");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x05: // KSEG1 - physical memory uncached
|
||||
{
|
||||
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
|
||||
{
|
||||
Panic("Bus error");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -61,17 +75,19 @@ void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
|
|||
value = m_cache_control;
|
||||
else
|
||||
WriteCacheControl(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Panic("KSEG2 access");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -179,38 +179,38 @@ struct Registers
|
|||
|
||||
struct
|
||||
{
|
||||
u32 zero;
|
||||
u32 at;
|
||||
u32 v0;
|
||||
u32 v1;
|
||||
u32 a0;
|
||||
u32 a1;
|
||||
u32 a2;
|
||||
u32 a3;
|
||||
u32 t0;
|
||||
u32 t1;
|
||||
u32 t2;
|
||||
u32 t3;
|
||||
u32 t4;
|
||||
u32 t5;
|
||||
u32 t6;
|
||||
u32 t7;
|
||||
u32 s0;
|
||||
u32 s1;
|
||||
u32 s2;
|
||||
u32 s3;
|
||||
u32 s4;
|
||||
u32 s5;
|
||||
u32 s6;
|
||||
u32 s7;
|
||||
u32 t8;
|
||||
u32 t9;
|
||||
u32 k0;
|
||||
u32 k1;
|
||||
u32 gp;
|
||||
u32 sp;
|
||||
u32 fp;
|
||||
u32 ra;
|
||||
u32 zero; // r0
|
||||
u32 at; // r1
|
||||
u32 v0; // r2
|
||||
u32 v1; // r3
|
||||
u32 a0; // r4
|
||||
u32 a1; // r5
|
||||
u32 a2; // r6
|
||||
u32 a3; // r7
|
||||
u32 t0; // r8
|
||||
u32 t1; // r9
|
||||
u32 t2; // r10
|
||||
u32 t3; // r11
|
||||
u32 t4; // r12
|
||||
u32 t5; // r13
|
||||
u32 t6; // r14
|
||||
u32 t7; // r15
|
||||
u32 s0; // r16
|
||||
u32 s1; // r17
|
||||
u32 s2; // r18
|
||||
u32 s3; // r19
|
||||
u32 s4; // r20
|
||||
u32 s5; // r21
|
||||
u32 s6; // r22
|
||||
u32 s7; // r23
|
||||
u32 t8; // r24
|
||||
u32 t9; // r25
|
||||
u32 k0; // r26
|
||||
u32 k1; // r27
|
||||
u32 gp; // r28
|
||||
u32 sp; // r29
|
||||
u32 fp; // r30
|
||||
u32 ra; // r31
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -47,3 +47,90 @@ void System::RunFrame()
|
|||
while (current_frame_number == m_frame_number)
|
||||
m_cpu->Execute();
|
||||
}
|
||||
|
||||
bool System::LoadEXE(const char* filename)
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct EXEHeader
|
||||
{
|
||||
char id[8]; // 0x000-0x007 PS-X EXE
|
||||
char pad1[8]; // 0x008-0x00F
|
||||
u32 initial_pc; // 0x010
|
||||
u32 initial_gp; // 0x014
|
||||
u32 load_address; // 0x018
|
||||
u32 file_size; // 0x01C excluding 0x800-byte header
|
||||
u32 unk0; // 0x020
|
||||
u32 unk1; // 0x024
|
||||
u32 memfill_start; // 0x028
|
||||
u32 memfill_size; // 0x02C
|
||||
u32 initial_sp_base; // 0x030
|
||||
u32 initial_sp_offset; // 0x034
|
||||
u32 reserved[5]; // 0x038-0x04B
|
||||
char marker[0x7B4]; // 0x04C-0x7FF
|
||||
};
|
||||
static_assert(sizeof(EXEHeader) == 0x800);
|
||||
#pragma pack(pop)
|
||||
|
||||
std::FILE* fp = std::fopen(filename, "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
EXEHeader header;
|
||||
if (std::fread(&header, sizeof(header), 1, fp) != 1)
|
||||
{
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.memfill_size > 0)
|
||||
{
|
||||
const u32 words_to_write = header.memfill_size / 4;
|
||||
u32 address = header.memfill_start & ~UINT32_C(3);
|
||||
for (u32 i = 0; i < words_to_write; i++)
|
||||
{
|
||||
m_cpu->SafeWriteMemoryWord(address, 0);
|
||||
address += sizeof(u32);
|
||||
}
|
||||
}
|
||||
|
||||
if (header.file_size >= 4)
|
||||
{
|
||||
std::vector<u32> data_words(header.file_size / 4);
|
||||
if (std::fread(data_words.data(), header.file_size, 1, fp) != 1)
|
||||
{
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 num_words = header.file_size / 4;
|
||||
u32 address = header.load_address;
|
||||
for (u32 i = 0; i < num_words; i++)
|
||||
{
|
||||
m_cpu->SafeWriteMemoryWord(address, data_words[i]);
|
||||
address += sizeof(u32);
|
||||
}
|
||||
}
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
// patch the BIOS to jump to the executable directly
|
||||
{
|
||||
const u32 r_pc = header.load_address;
|
||||
const u32 r_gp = header.initial_gp;
|
||||
const u32 r_sp = header.initial_sp_base;
|
||||
const u32 r_fp = header.initial_sp_base + header.initial_sp_offset;
|
||||
|
||||
// pc has to be done first because we can't load it in the delay slot
|
||||
m_bus->PatchBIOS(0xBFC06FF0, UINT32_C(0x3C080000) | r_pc >> 16); // lui $t0, (r_pc >> 16)
|
||||
m_bus->PatchBIOS(0xBFC06FF4, UINT32_C(0x35080000) | (r_pc & UINT32_C(0xFFFF))); // ori $t0, $t0, (r_pc & 0xFFFF)
|
||||
m_bus->PatchBIOS(0xBFC06FF8, UINT32_C(0x3C1C0000) | r_gp >> 16); // lui $gp, (r_gp >> 16)
|
||||
m_bus->PatchBIOS(0xBFC06FFC, UINT32_C(0x379C0000) | (r_gp & UINT32_C(0xFFFF))); // ori $gp, $gp, (r_gp & 0xFFFF)
|
||||
m_bus->PatchBIOS(0xBFC07000, UINT32_C(0x3C1D0000) | r_sp >> 16); // lui $sp, (r_sp >> 16)
|
||||
m_bus->PatchBIOS(0xBFC07004, UINT32_C(0x37BD0000) | (r_sp & UINT32_C(0xFFFF))); // ori $sp, $sp, (r_sp & 0xFFFF)
|
||||
m_bus->PatchBIOS(0xBFC07008, UINT32_C(0x3C1E0000) | r_fp >> 16); // lui $fp, (r_fp >> 16)
|
||||
m_bus->PatchBIOS(0xBFC0700C, UINT32_C(0x01000008)); // jr $t0
|
||||
m_bus->PatchBIOS(0xBFC07010, UINT32_C(0x37DE0000) | (r_fp & UINT32_C(0xFFFF))); // ori $fp, $fp, (r_fp & 0xFFFF)
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ public:
|
|||
|
||||
void RunFrame();
|
||||
|
||||
bool LoadEXE(const char* filename);
|
||||
|
||||
private:
|
||||
HostInterface* m_host_interface;
|
||||
std::unique_ptr<CPU::Core> m_cpu;
|
||||
|
|
Loading…
Reference in a new issue