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 251 additions and 61 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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)
{

View file

@ -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 (TRACE_EXECUTION)
PrintInstruction(inst.bits, inst_pc);
#if 0
if (inst_pc == 0x8005ab80)
if (inst_pc == 0xBFC06FF0)
{
TRACE_EXECUTION = true;
__debugbreak();
}
#endif
if (TRACE_EXECUTION)
PrintInstruction(inst.bits, inst_pc);
switch (inst.op)
{
case InstructionOp::funct:

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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
};
};

View file

@ -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;
}

View file

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