System: Support sideloading EXE files via BIOS patch

This commit is contained in:
Connor McLaughlin 2019-09-14 13:22:05 +10:00
parent ae43cc838b
commit 9f36384752
10 changed files with 252 additions and 62 deletions

View file

@ -178,6 +178,8 @@ bool SDLInterface::InitializeSystem(const char* filename, s32 save_state_index /
m_system->Reset(); m_system->Reset();
//m_system->LoadEXE("tests/psxtest_cpu.psxexe");
// Resume execution. // Resume execution.
m_running = true; m_running = true;
return true; return true;

View file

@ -2,6 +2,7 @@
#include "YBaseLib/ByteStream.h" #include "YBaseLib/ByteStream.h"
#include "YBaseLib/Log.h" #include "YBaseLib/Log.h"
#include "YBaseLib/String.h" #include "YBaseLib/String.h"
#include "cpu_disasm.h"
#include "dma.h" #include "dma.h"
#include "gpu.h" #include "gpu.h"
#include <cstdio> #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); 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() bool Bus::LoadBIOS()
{ {
std::FILE* fp = std::fopen("SCPH1001.BIN", "rb"); std::FILE* fp = std::fopen("SCPH1001.BIN", "rb");
@ -97,9 +116,9 @@ bool Bus::LoadBIOS()
std::fclose(fp); std::fclose(fp);
#if 1 #if 1
auto Patch = [this](u32 address, u32 value) { std::memcpy(&m_bios[address], &value, sizeof(value)); }; // Patch to enable TTY.
Patch(0x6F0C, 0x24010001); // addiu $at, $zero, 1 PatchBIOS(BIOS_BASE + 0x6F0C, 0x24010001);
Patch(0x6F14, 0xaf81a9c0); // sw at, -0x5640(gp) PatchBIOS(BIOS_BASE + 0x6F14, 0xAF81A9C0);
#endif #endif
return true; return true;

View file

@ -29,6 +29,8 @@ public:
template<MemoryAccessType type, MemoryAccessSize size> template<MemoryAccessType type, MemoryAccessSize size>
bool DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32& value); bool DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32& value);
void PatchBIOS(u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));
private: private:
static constexpr u32 DMA_BASE = 0x1F801080; static constexpr u32 DMA_BASE = 0x1F801080;
static constexpr u32 DMA_SIZE = 0x80; static constexpr u32 DMA_SIZE = 0x80;
@ -42,6 +44,8 @@ private:
static constexpr u32 EXP2_BASE = 0x1F802000; static constexpr u32 EXP2_BASE = 0x1F802000;
static constexpr u32 EXP2_SIZE = 0x2000; static constexpr u32 EXP2_SIZE = 0x2000;
static constexpr u32 EXP2_MASK = EXP2_SIZE - 1; static constexpr u32 EXP2_MASK = EXP2_SIZE - 1;
static constexpr u32 BIOS_BASE = 0x1FC00000;
static constexpr u32 BIOS_SIZE = 0x80000;
bool LoadBIOS(); bool LoadBIOS();

View file

@ -116,13 +116,13 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres
return (type == MemoryAccessType::Read) ? ReadExpansionRegion2(size, bus_address & EXP2_MASK, value) : return (type == MemoryAccessType::Read) ? ReadExpansionRegion2(size, bus_address & EXP2_MASK, value) :
WriteExpansionRegion2(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); 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) else if (bus_address < 0x1FFE0000)
{ {

View file

@ -30,10 +30,16 @@ bool Core::DoState(StateWrapper& sw)
return false; return false;
} }
void Core::SetPC(u32 new_pc)
{
m_regs.npc = new_pc;
FlushPipeline();
}
u8 Core::ReadMemoryByte(VirtualMemoryAddress addr) u8 Core::ReadMemoryByte(VirtualMemoryAddress addr)
{ {
u32 value; u32 value;
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte, false>(addr, value); DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte, false, true>(addr, value);
return Truncate8(value); return Truncate8(value);
} }
@ -41,7 +47,7 @@ u16 Core::ReadMemoryHalfWord(VirtualMemoryAddress addr)
{ {
Assert(Common::IsAlignedPow2(addr, 2)); Assert(Common::IsAlignedPow2(addr, 2));
u32 value; u32 value;
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord, false>(addr, value); DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord, false, true>(addr, value);
return Truncate16(value); return Truncate16(value);
} }
@ -49,27 +55,65 @@ u32 Core::ReadMemoryWord(VirtualMemoryAddress addr)
{ {
Assert(Common::IsAlignedPow2(addr, 4)); Assert(Common::IsAlignedPow2(addr, 4));
u32 value; u32 value;
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, false>(addr, value); DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, false, true>(addr, value);
return value; return value;
} }
void Core::WriteMemoryByte(VirtualMemoryAddress addr, u8 value) void Core::WriteMemoryByte(VirtualMemoryAddress addr, u8 value)
{ {
u32 value32 = ZeroExtend32(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) void Core::WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value)
{ {
Assert(Common::IsAlignedPow2(addr, 2)); Assert(Common::IsAlignedPow2(addr, 2));
u32 value32 = ZeroExtend32(value); 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) void Core::WriteMemoryWord(VirtualMemoryAddress addr, u32 value)
{ {
Assert(Common::IsAlignedPow2(addr, 4)); 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) 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) void Core::DisassembleAndPrint(u32 addr)
{ {
u32 bits; u32 bits;
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true>(addr, bits); DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true, false>(addr, bits);
PrintInstruction(bits, addr); PrintInstruction(bits, addr);
} }
@ -182,22 +226,25 @@ void Core::Execute()
void Core::FetchInstruction() void Core::FetchInstruction()
{ {
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true>(static_cast<VirtualMemoryAddress>(m_regs.npc), DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true, true>(
m_next_instruction.bits); static_cast<VirtualMemoryAddress>(m_regs.npc), m_next_instruction.bits);
m_regs.pc = m_regs.npc; m_regs.pc = m_regs.npc;
m_regs.npc += sizeof(m_next_instruction.bits); m_regs.npc += sizeof(m_next_instruction.bits);
} }
void Core::ExecuteInstruction(Instruction inst, u32 inst_pc) void Core::ExecuteInstruction(Instruction inst, u32 inst_pc)
{ {
#if 0
if (inst_pc == 0xBFC06FF0)
{
TRACE_EXECUTION = true;
__debugbreak();
}
#endif
if (TRACE_EXECUTION) if (TRACE_EXECUTION)
PrintInstruction(inst.bits, inst_pc); PrintInstruction(inst.bits, inst_pc);
#if 0
if (inst_pc == 0x8005ab80)
__debugbreak();
#endif
switch (inst.op) switch (inst.op)
{ {
case InstructionOp::funct: case InstructionOp::funct:

View file

@ -23,9 +23,22 @@ public:
void Execute(); 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: private:
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch> template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch, bool raise_exceptions>
void DoMemoryAccess(VirtualMemoryAddress address, u32& value); bool DoMemoryAccess(VirtualMemoryAddress address, u32& value);
u8 ReadMemoryByte(VirtualMemoryAddress addr); u8 ReadMemoryByte(VirtualMemoryAddress addr);
u16 ReadMemoryHalfWord(VirtualMemoryAddress addr); u16 ReadMemoryHalfWord(VirtualMemoryAddress addr);

View file

@ -5,8 +5,8 @@
namespace CPU { namespace CPU {
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch> template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch, bool raise_exceptions>
void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value) bool Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
{ {
switch (address >> 29) switch (address >> 29)
{ {
@ -15,13 +15,17 @@ void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
if constexpr (type == MemoryAccessType::Write) if constexpr (type == MemoryAccessType::Write)
{ {
if (m_cop0_regs.sr.Isc) if (m_cop0_regs.sr.Isc)
return; return true;
} }
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value)) if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
{
Panic("Bus error"); Panic("Bus error");
return false;
}
return true;
} }
break;
case 0x01: // KUSEG 512M-1024M case 0x01: // KUSEG 512M-1024M
case 0x02: // KUSEG 1024M-1536M case 0x02: // KUSEG 1024M-1536M
@ -29,26 +33,36 @@ void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
{ {
// Above 512mb raises an exception. // Above 512mb raises an exception.
Panic("Bad user access"); Panic("Bad user access");
return false;
} }
break;
case 0x04: // KSEG0 - physical memory cached case 0x04: // KSEG0 - physical memory cached
{ {
if constexpr (type == MemoryAccessType::Write) if constexpr (type == MemoryAccessType::Write)
{ {
if (m_cop0_regs.sr.Isc) if (m_cop0_regs.sr.Isc)
return; return true;
} }
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value)) if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
{
Panic("Bus error"); Panic("Bus error");
return false;
}
return true;
} }
break; break;
case 0x05: // KSEG1 - physical memory uncached case 0x05: // KSEG1 - physical memory uncached
{ {
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value)) if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
{
Panic("Bus error"); Panic("Bus error");
return false;
}
return true;
} }
break; break;
@ -61,17 +75,19 @@ void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
value = m_cache_control; value = m_cache_control;
else else
WriteCacheControl(value); WriteCacheControl(value);
return true;
} }
else else
{ {
Panic("KSEG2 access"); Panic("KSEG2 access");
return false;
} }
} }
break;
default: default:
UnreachableCode(); UnreachableCode();
break; return false;
} }
} }

View file

@ -179,38 +179,38 @@ struct Registers
struct struct
{ {
u32 zero; u32 zero; // r0
u32 at; u32 at; // r1
u32 v0; u32 v0; // r2
u32 v1; u32 v1; // r3
u32 a0; u32 a0; // r4
u32 a1; u32 a1; // r5
u32 a2; u32 a2; // r6
u32 a3; u32 a3; // r7
u32 t0; u32 t0; // r8
u32 t1; u32 t1; // r9
u32 t2; u32 t2; // r10
u32 t3; u32 t3; // r11
u32 t4; u32 t4; // r12
u32 t5; u32 t5; // r13
u32 t6; u32 t6; // r14
u32 t7; u32 t7; // r15
u32 s0; u32 s0; // r16
u32 s1; u32 s1; // r17
u32 s2; u32 s2; // r18
u32 s3; u32 s3; // r19
u32 s4; u32 s4; // r20
u32 s5; u32 s5; // r21
u32 s6; u32 s6; // r22
u32 s7; u32 s7; // r23
u32 t8; u32 t8; // r24
u32 t9; u32 t9; // r25
u32 k0; u32 k0; // r26
u32 k1; u32 k1; // r27
u32 gp; u32 gp; // r28
u32 sp; u32 sp; // r29
u32 fp; u32 fp; // r30
u32 ra; u32 ra; // r31
}; };
}; };

View file

@ -47,3 +47,90 @@ void System::RunFrame()
while (current_frame_number == m_frame_number) while (current_frame_number == m_frame_number)
m_cpu->Execute(); 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;
}

View file

@ -28,6 +28,8 @@ public:
void RunFrame(); void RunFrame();
bool LoadEXE(const char* filename);
private: private:
HostInterface* m_host_interface; HostInterface* m_host_interface;
std::unique_ptr<CPU::Core> m_cpu; std::unique_ptr<CPU::Core> m_cpu;