Skip to content

Commit

Permalink
System: Rewrite EXE override/loading
Browse files Browse the repository at this point in the history
Relies on POST=7 as a kernel initialization indicator, instead of
patching the BIOS.

Fixes EXE loading with OpenBIOS and PS2 BIOS, and fast boot getting
baked into save states.
  • Loading branch information
stenzek committed Jul 28, 2024
1 parent 7b99fcb commit 6fe0c98
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 322 deletions.
2 changes: 1 addition & 1 deletion src/core/achievements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ std::string Achievements::GetGameHash(CDImage* image)
BIOS::PSEXEHeader header = {};
if (executable_data.size() >= sizeof(header))
std::memcpy(&header, executable_data.data(), sizeof(header));
if (!BIOS::IsValidPSExeHeader(header, static_cast<u32>(executable_data.size())))
if (!BIOS::IsValidPSExeHeader(header, executable_data.size()))
{
ERROR_LOG("PS-EXE header is invalid in '{}' ({} bytes)", executable_name, executable_data.size());
return {};
Expand Down
45 changes: 4 additions & 41 deletions src/core/bios.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Log_SetChannel(BIOS);

namespace BIOS {
static const ImageInfo* GetInfoForHash(const std::span<u8> image, const ImageInfo::Hash& hash);
static void PatchBIOS(u8* image, u32 image_size, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));

static constexpr ImageInfo::Hash MakeHashFromString(const char str[])
{
Expand Down Expand Up @@ -255,54 +256,16 @@ bool BIOS::PatchBIOSFastBoot(u8* image, u32 image_size)
return true;
}

bool BIOS::PatchBIOSForEXE(u8* image, u32 image_size, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp)
{
#define PATCH(offset, value) PatchBIOS(image, image_size, (offset), (value))

// pc has to be done first because we can't load it in the delay slot
PATCH(0xBFC06FF0, UINT32_C(0x3C080000) | r_pc >> 16); // lui $t0, (r_pc >> 16)
PATCH(0xBFC06FF4, UINT32_C(0x35080000) | (r_pc & UINT32_C(0xFFFF))); // ori $t0, $t0, (r_pc & 0xFFFF)
PATCH(0xBFC06FF8, UINT32_C(0x3C1C0000) | r_gp >> 16); // lui $gp, (r_gp >> 16)
PATCH(0xBFC06FFC, UINT32_C(0x379C0000) | (r_gp & UINT32_C(0xFFFF))); // ori $gp, $gp, (r_gp & 0xFFFF)

if (r_sp != 0)
{
PATCH(0xBFC07000, UINT32_C(0x3C1D0000) | r_sp >> 16); // lui $sp, (r_sp >> 16)
PATCH(0xBFC07004, UINT32_C(0x37BD0000) | (r_sp & UINT32_C(0xFFFF))); // ori $sp, $sp, (r_sp & 0xFFFF)
}
else
{
PATCH(0xBFC07000, UINT32_C(0x00000000)); // nop
PATCH(0xBFC07004, UINT32_C(0x00000000)); // nop
}
if (r_fp != 0)
{
PATCH(0xBFC07008, UINT32_C(0x3C1E0000) | r_fp >> 16); // lui $fp, (r_fp >> 16)
PATCH(0xBFC0700C, UINT32_C(0x01000008)); // jr $t0
PATCH(0xBFC07010, UINT32_C(0x37DE0000) | (r_fp & UINT32_C(0xFFFF))); // ori $fp, $fp, (r_fp & 0xFFFF)
}
else
{
PATCH(0xBFC07008, UINT32_C(0x00000000)); // nop
PATCH(0xBFC0700C, UINT32_C(0x01000008)); // jr $t0
PATCH(0xBFC07010, UINT32_C(0x00000000)); // nop
}

#undef PATCH

return true;
}

bool BIOS::IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size)
bool BIOS::IsValidPSExeHeader(const PSEXEHeader& header, size_t file_size)
{
static constexpr char expected_id[] = {'P', 'S', '-', 'X', ' ', 'E', 'X', 'E'};
if (std::memcmp(header.id, expected_id, sizeof(expected_id)) != 0)
if (file_size < sizeof(expected_id) || std::memcmp(header.id, expected_id, sizeof(expected_id)) != 0)
return false;

if ((header.file_size + sizeof(PSEXEHeader)) > file_size)
{
WARNING_LOG("Incorrect file size in PS-EXE header: {} bytes should not be greater than {} bytes", header.file_size,
static_cast<unsigned>(file_size - sizeof(PSEXEHeader)));
file_size - sizeof(PSEXEHeader));
}

return true;
Expand Down
5 changes: 1 addition & 4 deletions src/core/bios.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,9 @@ std::optional<Image> LoadImageFromFile(const char* filename, Error* error);

bool IsValidBIOSForRegion(ConsoleRegion console_region, ConsoleRegion bios_region);

void PatchBIOS(u8* image, u32 image_size, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));

bool PatchBIOSFastBoot(u8* image, u32 image_size);
bool PatchBIOSForEXE(u8* image, u32 image_size, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp);

bool IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size);
bool IsValidPSExeHeader(const PSEXEHeader& header, size_t file_size);
DiscRegion GetPSExeDiscRegion(const PSEXEHeader& header);

/// Loads the BIOS image for the specified region.
Expand Down
200 changes: 164 additions & 36 deletions src/core/bus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)

#include "bus.h"
#include "bios.h"
#include "cdrom.h"
#include "cpu_code_cache.h"
#include "cpu_core.h"
Expand All @@ -13,21 +14,25 @@
#include "interrupt_controller.h"
#include "mdec.h"
#include "pad.h"
#include "psf_loader.h"
#include "settings.h"
#include "sio.h"
#include "spu.h"
#include "system.h"
#include "timers.h"
#include "timing_event.h"

#include "util/cd_image.h"
#include "util/state_wrapper.h"

#include "common/align.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/intrin.h"
#include "common/log.h"
#include "common/memmap.h"
#include "common/path.h"

#include <cstdio>
#include <tuple>
Expand Down Expand Up @@ -146,6 +151,8 @@ static std::vector<std::pair<u8*, size_t>> s_fastmem_ram_views;

static u8** s_fastmem_lut = nullptr;

static bool s_kernel_initialize_hook_run = false;

static void SetRAMSize(bool enable_8mb_ram);

static std::tuple<TickCount, TickCount, TickCount> CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay);
Expand All @@ -155,6 +162,9 @@ static u8* GetLUTFastmemPointer(u32 address, u8* ram_ptr);

static void SetRAMPageWritable(u32 page_index, bool writable);

static void KernelInitializedHook();
static bool SideloadEXE(const std::string& path, Error* error);

static void SetHandlers();
static void UpdateMappedRAMSize();

Expand Down Expand Up @@ -348,6 +358,7 @@ void Bus::Reset()
s_MEMCTRL.exp2_delay_size.bits = 0x00070777;
s_MEMCTRL.common_delay.bits = 0x00031125;
g_ram_code_bits = {};
s_kernel_initialize_hook_run = false;
RecalculateMemoryTimings();

// Avoid remapping if unchanged.
Expand All @@ -358,35 +369,6 @@ void Bus::Reset()
}
}

void Bus::AddTTYCharacter(char ch)
{
if (ch == '\r')
{
}
else if (ch == '\n')
{
if (!s_tty_line_buffer.empty())
{
Log::FastWrite("TTY", "", LOGLEVEL_INFO, "\033[1;34m{}\033[0m", s_tty_line_buffer);
#ifdef _DEBUG
if (CPU::IsTraceEnabled())
CPU::WriteToExecutionLog("TTY: %s\n", s_tty_line_buffer.c_str());
#endif
}
s_tty_line_buffer.clear();
}
else
{
s_tty_line_buffer += ch;
}
}

void Bus::AddTTYString(std::string_view str)
{
for (char ch : str)
AddTTYCharacter(ch);
}

bool Bus::DoState(StateWrapper& sw)
{
u32 ram_size = g_ram_size;
Expand Down Expand Up @@ -420,12 +402,10 @@ bool Bus::DoState(StateWrapper& sw)
UpdateMappedRAMSize();

sw.Do(&s_tty_line_buffer);
return !sw.HasError();
}

void Bus::SetExpansionROM(std::vector<u8> data)
{
s_exp1_rom = std::move(data);
sw.DoEx(&s_kernel_initialize_hook_run, 68, true);

return !sw.HasError();
}

std::tuple<TickCount, TickCount, TickCount> Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay)
Expand Down Expand Up @@ -863,6 +843,146 @@ std::optional<PhysicalMemoryAddress> Bus::SearchMemory(PhysicalMemoryAddress sta
return std::nullopt;
}

void Bus::SetExpansionROM(std::vector<u8> data)
{
s_exp1_rom = std::move(data);
}

void Bus::AddTTYCharacter(char ch)
{
if (ch == '\r')
{
}
else if (ch == '\n')
{
if (!s_tty_line_buffer.empty())
{
Log::FastWrite("TTY", "", LOGLEVEL_INFO, "\033[1;34m{}\033[0m", s_tty_line_buffer);
#ifdef _DEBUG
if (CPU::IsTraceEnabled())
CPU::WriteToExecutionLog("TTY: %s\n", s_tty_line_buffer.c_str());
#endif
}
s_tty_line_buffer.clear();
}
else
{
s_tty_line_buffer += ch;
}
}

void Bus::AddTTYString(std::string_view str)
{
for (char ch : str)
AddTTYCharacter(ch);
}

bool Bus::InjectExecutable(std::span<const u8> buffer, bool set_pc, Error* error)
{
BIOS::PSEXEHeader header;
if (buffer.size() < sizeof(header))
{
Error::SetStringView(error, "Executable does not contain a header.");
return false;
}

std::memcpy(&header, buffer.data(), sizeof(header));
if (!BIOS::IsValidPSExeHeader(header, buffer.size()))
{
Error::SetStringView(error, "Executable does not contain a valid header.");
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++)
{
CPU::SafeWriteMemoryWord(address, 0);
address += sizeof(u32);
}
}

const u32 data_load_size =
std::min(static_cast<u32>(static_cast<u32>(buffer.size() - sizeof(BIOS::PSEXEHeader))), header.file_size);
if (data_load_size > 0)
{
if (!CPU::SafeWriteMemoryBytes(header.load_address, &buffer[sizeof(header)], data_load_size))
{
Error::SetStringFmt(error, "Failed to upload {} bytes to memory at address 0x{:08X}.", data_load_size,
header.load_address);
}
}

// patch the BIOS to jump to the executable directly
if (set_pc)
{
const u32 r_pc = header.initial_pc;
CPU::g_state.regs.gp = header.initial_gp;
CPU::g_state.regs.sp = header.initial_sp_base + header.initial_sp_offset;
CPU::g_state.regs.fp = header.initial_sp_base + header.initial_sp_offset;
CPU::SetPC(r_pc);
}

return true;
}

void Bus::KernelInitializedHook()
{
if (s_kernel_initialize_hook_run)
return;

INFO_LOG("Kernel initialized.");
s_kernel_initialize_hook_run = true;

const System::BootMode boot_mode = System::GetBootMode();
if (boot_mode == System::BootMode::BootEXE || boot_mode == System::BootMode::BootPSF)
{
Error error;
if (((boot_mode == System::BootMode::BootEXE) ? SideloadEXE(System::GetExeOverride(), &error) :
PSFLoader::Load(System::GetExeOverride(), &error)))
{
// Clear all state, since we're blatently overwriting memory.
CPU::CodeCache::Reset();
CPU::ClearICache();

// Stop executing the current block and shell init, and jump straight to the new code.
DebugAssert(!TimingEvents::IsRunningEvents());
CPU::ExitExecution();
}
else
{
// Shut down system on load failure.
Host::ReportErrorAsync("EXE/PSF Load Failed", error.GetDescription());
System::ShutdownSystem(false);
}
}
}

bool Bus::SideloadEXE(const std::string& path, Error* error)
{
// look for a libps.exe next to the exe, if it exists, load it
bool okay = true;
if (const std::string libps_path = Path::BuildRelativePath(path, "libps.exe");
FileSystem::FileExists(libps_path.c_str()))
{
const std::optional<std::vector<u8>> exe_data = FileSystem::ReadBinaryFile(libps_path.c_str(), error);
okay = (exe_data.has_value() && InjectExecutable(exe_data.value(), false, error));
if (!okay)
Error::AddPrefix(error, "Failed to load libps.exe: ");
}
if (okay)
{
const std::optional<std::vector<u8>> exe_data = FileSystem::ReadBinaryFile(System::GetExeOverride().c_str(), error);
okay = (exe_data.has_value() && InjectExecutable(exe_data.value(), true, error));
if (!okay)
Error::AddPrefixFmt(error, "Failed to load {}: ", Path::GetFileName(path));
}

return okay;
}

#define BUS_CYCLES(n) CPU::g_state.pending_ticks += n

// TODO: Move handlers to own files for better inlining.
Expand Down Expand Up @@ -1192,7 +1312,10 @@ void Bus::EXP2WriteHandler(VirtualMemoryAddress address, u32 value)
}
else if (offset == 0x41 || offset == 0x42)
{
DEV_LOG("BIOS POST status: {:02X}", value & UINT32_C(0x0F));
const u32 post_code = value & UINT32_C(0x0F);
DEV_LOG("BIOS POST status: {:02X}", post_code);
if (post_code == 0x07)
KernelInitializedHook();
}
else if (offset == 0x70)
{
Expand Down Expand Up @@ -1233,7 +1356,12 @@ void Bus::EXP3WriteHandler(VirtualMemoryAddress address, u32 value)
{
const u32 offset = address & EXP3_MASK;
if (offset == 0)
WARNING_LOG("BIOS POST3 status: {:02X}", value & UINT32_C(0x0F));
{
const u32 post_code = value & UINT32_C(0x0F);
WARNING_LOG("BIOS POST3 status: {:02X}", post_code);
if (post_code == 0x07)
KernelInitializedHook();
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
4 changes: 4 additions & 0 deletions src/core/bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <array>
#include <bitset>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
Expand Down Expand Up @@ -218,4 +219,7 @@ std::optional<PhysicalMemoryAddress> SearchMemory(PhysicalMemoryAddress start_ad
void AddTTYCharacter(char ch);
void AddTTYString(std::string_view str);

/// Injects a PS-EXE into memory at its specified load location. If set_pc is set, execution will be redirected.
bool InjectExecutable(std::span<const u8> buffer, bool set_pc, Error* error);

} // namespace Bus
Loading

0 comments on commit 6fe0c98

Please sign in to comment.