diff --git a/src/pse-sdl/sdl_interface.cpp b/src/pse-sdl/sdl_interface.cpp index c2a404e6c..daefd8f1e 100644 --- a/src/pse-sdl/sdl_interface.cpp +++ b/src/pse-sdl/sdl_interface.cpp @@ -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; diff --git a/src/pse/bus.cpp b/src/pse/bus.cpp index 7a9e1b6c0..fb566d9f6 100644 --- a/src/pse/bus.cpp +++ b/src/pse/bus.cpp @@ -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 @@ -70,6 +71,24 @@ bool Bus::WriteWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus return DispatchAccess(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; diff --git a/src/pse/bus.h b/src/pse/bus.h index 084ea7a72..81d7dd86f 100644 --- a/src/pse/bus.h +++ b/src/pse/bus.h @@ -29,6 +29,8 @@ public: template 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(); diff --git a/src/pse/bus.inl b/src/pse/bus.inl index 649290b0b..b2ffc92dc 100644 --- a/src/pse/bus.inl +++ b/src/pse/bus.inl @@ -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(static_cast(bus_address - UINT32_C(0x1FC00000)), value); + return DoBIOSAccess(static_cast(bus_address - BIOS_BASE), value); } else if (bus_address < 0x1FFE0000) { diff --git a/src/pse/cpu_core.cpp b/src/pse/cpu_core.cpp index d9717cc68..db46f21e0 100644 --- a/src/pse/cpu_core.cpp +++ b/src/pse/cpu_core.cpp @@ -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(addr, value); + DoMemoryAccess(addr, value); return Truncate8(value); } @@ -41,7 +47,7 @@ u16 Core::ReadMemoryHalfWord(VirtualMemoryAddress addr) { Assert(Common::IsAlignedPow2(addr, 2)); u32 value; - DoMemoryAccess(addr, value); + DoMemoryAccess(addr, value); return Truncate16(value); } @@ -49,27 +55,65 @@ u32 Core::ReadMemoryWord(VirtualMemoryAddress addr) { Assert(Common::IsAlignedPow2(addr, 4)); u32 value; - DoMemoryAccess(addr, value); + DoMemoryAccess(addr, value); return value; } void Core::WriteMemoryByte(VirtualMemoryAddress addr, u8 value) { u32 value32 = ZeroExtend32(value); - DoMemoryAccess(addr, value32); + DoMemoryAccess(addr, value32); } void Core::WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value) { Assert(Common::IsAlignedPow2(addr, 2)); u32 value32 = ZeroExtend32(value); - DoMemoryAccess(addr, value32); + DoMemoryAccess(addr, value32); } void Core::WriteMemoryWord(VirtualMemoryAddress addr, u32 value) { Assert(Common::IsAlignedPow2(addr, 4)); - DoMemoryAccess(addr, value); + DoMemoryAccess(addr, value); +} + +bool Core::SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value) +{ + u32 temp = 0; + const bool result = DoMemoryAccess(addr, temp); + *value = Truncate8(temp); + return result; +} + +bool Core::SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value) +{ + u32 temp = 0; + const bool result = DoMemoryAccess(addr, temp); + *value = Truncate16(temp); + return result; +} + +bool Core::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value) +{ + return DoMemoryAccess(addr, *value); +} + +bool Core::SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value) +{ + u32 temp = ZeroExtend32(value); + return DoMemoryAccess(addr, temp); +} + +bool Core::SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value) +{ + u32 temp = ZeroExtend32(value); + return DoMemoryAccess(addr, temp); +} + +bool Core::SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value) +{ + return DoMemoryAccess(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(addr, bits); + DoMemoryAccess(addr, bits); PrintInstruction(bits, addr); } @@ -182,22 +226,25 @@ void Core::Execute() void Core::FetchInstruction() { - DoMemoryAccess(static_cast(m_regs.npc), - m_next_instruction.bits); + DoMemoryAccess( + static_cast(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: diff --git a/src/pse/cpu_core.h b/src/pse/cpu_core.h index 79735f9e1..a5255b171 100644 --- a/src/pse/cpu_core.h +++ b/src/pse/cpu_core.h @@ -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 - void DoMemoryAccess(VirtualMemoryAddress address, u32& value); + template + bool DoMemoryAccess(VirtualMemoryAddress address, u32& value); u8 ReadMemoryByte(VirtualMemoryAddress addr); u16 ReadMemoryHalfWord(VirtualMemoryAddress addr); diff --git a/src/pse/cpu_core.inl b/src/pse/cpu_core.inl index 9d4fbc22d..68a1caecf 100644 --- a/src/pse/cpu_core.inl +++ b/src/pse/cpu_core.inl @@ -5,8 +5,8 @@ namespace CPU { -template -void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value) +template +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(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(address, address & UINT32_C(0x1FFFFFFF), value)) + { Panic("Bus error"); + return false; + } + + return true; } break; case 0x05: // KSEG1 - physical memory uncached { if (!m_bus->DispatchAccess(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; } } diff --git a/src/pse/cpu_types.h b/src/pse/cpu_types.h index a550f151f..e52e3bb2e 100644 --- a/src/pse/cpu_types.h +++ b/src/pse/cpu_types.h @@ -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 }; }; diff --git a/src/pse/system.cpp b/src/pse/system.cpp index 6ff0d4541..35afe7b06 100644 --- a/src/pse/system.cpp +++ b/src/pse/system.cpp @@ -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 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; +} diff --git a/src/pse/system.h b/src/pse/system.h index 0338f3e9f..615818885 100644 --- a/src/pse/system.h +++ b/src/pse/system.h @@ -28,6 +28,8 @@ public: void RunFrame(); + bool LoadEXE(const char* filename); + private: HostInterface* m_host_interface; std::unique_ptr m_cpu;