System: Rewrite EXE override/loading

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.
This commit is contained in:
Stenzek 2024-07-28 16:16:05 +10:00
parent 7b99fcbbf3
commit 6fe0c986fa
No known key found for this signature in database
13 changed files with 327 additions and 322 deletions

View file

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

View file

@ -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[])
{
@ -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;

View file

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

View file

@ -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"
@ -13,6 +14,7 @@
#include "interrupt_controller.h"
#include "mdec.h"
#include "pad.h"
#include "psf_loader.h"
#include "settings.h"
#include "sio.h"
#include "spu.h"
@ -20,14 +22,17 @@
#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>
@ -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);
@ -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();
@ -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.
@ -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;
@ -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)
@ -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.
@ -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)
{
@ -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();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -10,6 +10,7 @@
#include <array>
#include <bitset>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
@ -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

View file

@ -28,7 +28,6 @@ Log_SetChannel(CPU::Core);
namespace CPU {
static bool ShouldUseInterpreter();
static void SetPC(u32 new_pc);
static void UpdateLoadDelay();
static void Branch(u32 target);
static void FlushLoadDelay();
@ -306,7 +305,7 @@ ALWAYS_INLINE_RELEASE bool CPU::ShouldUseInterpreter()
return (g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter || g_state.using_debug_dispatcher);
}
ALWAYS_INLINE_RELEASE void CPU::SetPC(u32 new_pc)
void CPU::SetPC(u32 new_pc)
{
DebugAssert(Common::IsAlignedPow2(new_pc, 4));
g_state.npc = new_pc;

View file

@ -7,6 +7,8 @@
namespace CPU {
void SetPC(u32 new_pc);
// exceptions
void RaiseException(Exception excode);
void RaiseException(u32 CAUSE_bits, u32 EPC);

View file

@ -202,7 +202,7 @@ bool GameList::GetPsfListEntry(const std::string& path, Entry* entry)
{
// we don't need to walk the library chain here - the top file is enough
PSFLoader::File file;
if (!file.Load(path.c_str()))
if (!file.Load(path.c_str(), nullptr))
return false;
entry->serial.clear();

View file

@ -1,21 +1,29 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "psf_loader.h"
#include "bios.h"
#include "bus.h"
#include "system.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "system.h"
#include "zlib.h"
#include <cctype>
#include <cstring>
Log_SetChannel(PSFLoader);
namespace PSFLoader {
static bool LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth = 0);
}
std::optional<std::string> File::GetTagString(const char* tag_name) const
std::optional<std::string> PSFLoader::File::GetTagString(const char* tag_name) const
{
auto it = m_tags.find(tag_name);
if (it == m_tags.end())
@ -24,7 +32,7 @@ std::optional<std::string> File::GetTagString(const char* tag_name) const
return it->second;
}
std::optional<int> File::GetTagInt(const char* tag_name) const
std::optional<int> PSFLoader::File::GetTagInt(const char* tag_name) const
{
auto it = m_tags.find(tag_name);
if (it == m_tags.end())
@ -33,7 +41,7 @@ std::optional<int> File::GetTagInt(const char* tag_name) const
return std::atoi(it->second.c_str());
}
std::optional<float> File::GetTagFloat(const char* tag_name) const
std::optional<float> PSFLoader::File::GetTagFloat(const char* tag_name) const
{
auto it = m_tags.find(tag_name);
if (it == m_tags.end())
@ -42,7 +50,7 @@ std::optional<float> File::GetTagFloat(const char* tag_name) const
return static_cast<float>(std::atof(it->second.c_str()));
}
std::string File::GetTagString(const char* tag_name, const char* default_value) const
std::string PSFLoader::File::GetTagString(const char* tag_name, const char* default_value) const
{
std::optional<std::string> value(GetTagString(tag_name));
if (value.has_value())
@ -51,24 +59,21 @@ std::string File::GetTagString(const char* tag_name, const char* default_value)
return default_value;
}
int File::GetTagInt(const char* tag_name, int default_value) const
int PSFLoader::File::GetTagInt(const char* tag_name, int default_value) const
{
return GetTagInt(tag_name).value_or(default_value);
}
float File::GetTagFloat(const char* tag_name, float default_value) const
float PSFLoader::File::GetTagFloat(const char* tag_name, float default_value) const
{
return GetTagFloat(tag_name).value_or(default_value);
}
bool File::Load(const char* path)
bool PSFLoader::File::Load(const char* path, Error* error)
{
std::optional<std::vector<u8>> file_data(FileSystem::ReadBinaryFile(path));
std::optional<std::vector<u8>> file_data(FileSystem::ReadBinaryFile(path, error));
if (!file_data.has_value() || file_data->empty())
{
ERROR_LOG("Failed to open/read PSF file '{}'", path);
return false;
}
const u8* file_pointer = file_data->data();
const u8* file_pointer_end = file_data->data() + file_data->size();
@ -81,7 +86,7 @@ bool File::Load(const char* path)
header.compressed_program_size == 0 ||
(sizeof(header) + header.reserved_area_size + header.compressed_program_size) > file_size)
{
ERROR_LOG("Invalid or incompatible header in PSF '{}'", path);
Error::SetStringView(error, "Invalid or incompatible PSF header.");
return false;
}
@ -98,7 +103,7 @@ bool File::Load(const char* path)
int err = inflateInit(&strm);
if (err != Z_OK)
{
ERROR_LOG("inflateInit() failed: {}", err);
Error::SetStringFmt(error, "inflateInit() failed: {}", err);
return false;
}
@ -106,7 +111,7 @@ bool File::Load(const char* path)
err = inflate(&strm, Z_NO_FLUSH);
if (err != Z_STREAM_END)
{
ERROR_LOG("inflate() failed: {}", err);
Error::SetStringFmt(error, "inflate() failed: {}", err);
inflateEnd(&strm);
return false;
}
@ -166,19 +171,20 @@ bool File::Load(const char* path)
return true;
}
static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
bool PSFLoader::LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth)
{
// don't recurse past 10 levels just in case of broken files
if (depth >= 10)
{
ERROR_LOG("Recursion depth exceeded when loading PSF '{}'", path);
Error::SetStringFmt(error, "Recursion depth exceeded when loading PSF '{}'", Path::GetFileName(path));
return false;
}
File file;
if (!file.Load(path))
if (!file.Load(path.c_str(), error))
{
ERROR_LOG("Failed to load main PSF '{}'", path);
Error::AddPrefixFmt(error, "Failed to load {} PSF '{}': ", (depth == 0) ? "main" : "parent",
Path::GetFileName(path));
return false;
}
@ -186,16 +192,13 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
std::optional<std::string> lib_name(file.GetTagString("_lib"));
if (lib_name.has_value())
{
const std::string lib_path(Path::BuildRelativePath(path, lib_name.value()));
INFO_LOG("Loading main parent PSF '{}'", lib_path);
const std::string lib_path = Path::BuildRelativePath(path, lib_name.value());
INFO_LOG("Loading parent PSF '{}'", Path::GetFileName(lib_path));
// We should use the initial SP/PC from the **first** parent lib.
const bool lib_use_pc_sp = (depth == 0);
if (!LoadLibraryPSF(lib_path.c_str(), lib_use_pc_sp, depth + 1))
{
ERROR_LOG("Failed to load main parent PSF '{}'", lib_path);
if (!LoadLibraryPSF(lib_path.c_str(), lib_use_pc_sp, error, depth + 1))
return false;
}
// Don't apply the PC/SP from the minipsf file.
if (lib_use_pc_sp)
@ -203,10 +206,10 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
}
// apply the main psf
if (!System::InjectEXEFromBuffer(file.GetProgramData().data(), static_cast<u32>(file.GetProgramData().size()),
use_pc_sp))
if (!Bus::InjectExecutable(file.GetProgramData(), use_pc_sp, error))
{
ERROR_LOG("Failed to parse EXE from PSF '{}'", path);
Error::AddPrefixFmt(error, "Failed to inject {} PSF '{}': ", (depth == 0) ? "main" : "parent",
Path::GetFileName(path));
return false;
}
@ -218,22 +221,17 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
if (!lib_name.has_value())
break;
const std::string lib_path(Path::BuildRelativePath(path, lib_name.value()));
INFO_LOG("Loading parent PSF '{}'", lib_path);
if (!LoadLibraryPSF(lib_path.c_str(), false, depth + 1))
{
ERROR_LOG("Failed to load parent PSF '{}'", lib_path);
const std::string lib_path = Path::BuildRelativePath(path, lib_name.value());
INFO_LOG("Loading parent PSF '{}'", Path::GetFileName(lib_path));
if (!LoadLibraryPSF(lib_path.c_str(), false, error, depth + 1))
return false;
}
}
return true;
}
bool Load(const char* path)
bool PSFLoader::Load(const std::string& path, Error* error)
{
INFO_LOG("Loading PSF file from '{}'", path);
return LoadLibraryPSF(path, true);
return LoadLibraryPSF(path, true, error);
}
} // namespace PSFLoader

View file

@ -1,14 +1,18 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "types.h"
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class Error;
namespace PSFLoader {
#pragma pack(push, 1)
@ -40,7 +44,7 @@ public:
int GetTagInt(const char* tag_name, int default_value) const;
float GetTagFloat(const char* tag_name, float default_value) const;
bool Load(const char* path);
bool Load(const char* path, Error* error);
private:
enum : u32
@ -53,6 +57,6 @@ private:
DiscRegion m_region = DiscRegion::Other;
};
bool Load(const char* path);
bool Load(const std::string& path, Error* error);
} // namespace PSFLoader

View file

@ -5,7 +5,7 @@
#include "types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
static constexpr u32 SAVE_STATE_VERSION = 67;
static constexpr u32 SAVE_STATE_VERSION = 68;
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);

View file

@ -108,8 +108,6 @@ static std::optional<ExtendedSaveStateInfo> InternalGetExtendedSaveStateInfo(Byt
static void LoadInputBindings(SettingsInterface& si, std::unique_lock<std::mutex>& lock);
static bool LoadEXE(const char* filename);
static std::string GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories);
static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name,
std::vector<u8>* out_executable_data);
@ -117,6 +115,7 @@ static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span<const
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length);
static bool LoadBIOS(Error* error);
static void ResetBootMode();
static void InternalReset();
static void ClearRunningGame();
static void DestroySystem();
@ -183,10 +182,11 @@ static BIOS::ImageInfo::Hash s_bios_hash = {};
static std::string s_running_game_path;
static std::string s_running_game_serial;
static std::string s_running_game_title;
static std::string s_exe_override;
static const GameDatabase::Entry* s_running_game_entry = nullptr;
static System::GameHash s_running_game_hash;
static System::BootMode s_boot_mode = System::BootMode::FullBoot;
static bool s_running_game_custom_title = false;
static bool s_was_fast_booted = false;
static bool s_system_executing = false;
static bool s_system_interrupted = false;
@ -585,6 +585,11 @@ const std::string& System::GetGameTitle()
return s_running_game_title;
}
const std::string& System::GetExeOverride()
{
return s_exe_override;
}
const GameDatabase::Entry* System::GetGameDatabaseEntry()
{
return s_running_game_entry;
@ -600,9 +605,9 @@ bool System::IsRunningUnknownGame()
return !s_running_game_entry;
}
bool System::WasFastBooted()
System::BootMode System::GetBootMode()
{
return s_was_fast_booted;
return s_boot_mode;
}
const BIOS::ImageInfo* System::GetBIOSImageInfo()
@ -1034,7 +1039,7 @@ DiscRegion System::GetRegionForExe(const char* path)
DiscRegion System::GetRegionForPsf(const char* path)
{
PSFLoader::File psf;
if (!psf.Load(path))
if (!psf.Load(path, nullptr))
return DiscRegion::Other;
return psf.GetRegion();
@ -1516,18 +1521,27 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
// Load CD image up and detect region.
std::unique_ptr<CDImage> disc;
DiscRegion disc_region = DiscRegion::NonPS1;
bool do_exe_boot = false;
bool do_psf_boot = false;
BootMode boot_mode = BootMode::FullBoot;
std::string exe_override;
if (!parameters.filename.empty())
{
do_exe_boot = IsExeFileName(parameters.filename);
do_psf_boot = (!do_exe_boot && IsPsfFileName(parameters.filename));
if (do_exe_boot || do_psf_boot)
if (IsExeFileName(parameters.filename))
{
boot_mode = BootMode::BootEXE;
exe_override = parameters.filename;
}
else if (IsPsfFileName(parameters.filename))
{
boot_mode = BootMode::BootPSF;
exe_override = parameters.filename;
}
if (boot_mode == BootMode::BootEXE || boot_mode == BootMode::BootPSF)
{
if (s_region == ConsoleRegion::Auto)
{
const DiscRegion file_region =
(do_exe_boot ? GetRegionForExe(parameters.filename.c_str()) : GetRegionForPsf(parameters.filename.c_str()));
((boot_mode == BootMode::BootEXE) ? GetRegionForExe(parameters.filename.c_str()) :
GetRegionForPsf(parameters.filename.c_str()));
INFO_LOG("EXE/PSF Region: {}", Settings::GetDiscRegionDisplayName(file_region));
s_region = GetConsoleRegionForDiscRegion(file_region);
}
@ -1561,6 +1575,16 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
Settings::GetDiscRegionName(disc_region), Settings::GetConsoleRegionName(s_region));
}
}
const bool wants_fast_boot =
parameters.override_fast_boot.value_or(static_cast<bool>(g_settings.bios_patch_fast_boot));
if (wants_fast_boot)
{
if (disc_region == DiscRegion::NonPS1)
ERROR_LOG("Not fast booting non-PS1 disc.");
else
boot_mode = BootMode::FastBoot;
}
}
}
else
@ -1587,7 +1611,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
UpdateRunningGame(disc ? disc->GetFileName().c_str() : parameters.filename.c_str(), disc.get(), true);
// Get boot EXE override.
std::string exe_boot;
if (!parameters.override_exe.empty())
{
if (!FileSystem::FileExists(parameters.override_exe.c_str()) || !IsExeFileName(parameters.override_exe))
@ -1601,11 +1624,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
}
INFO_LOG("Overriding boot executable: '{}'", parameters.override_exe);
exe_boot = std::move(parameters.override_exe);
}
else if (do_exe_boot)
{
exe_boot = std::move(parameters.filename);
boot_mode = BootMode::BootEXE;
exe_override = std::move(parameters.override_exe);
}
// Check for SBI.
@ -1621,12 +1641,15 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
// Check for resuming with hardcore mode.
if (parameters.disable_achievements_hardcore_mode)
Achievements::DisableHardcoreMode();
if (!parameters.save_state.empty() && Achievements::IsHardcoreModeActive())
if ((!parameters.save_state.empty() || !exe_override.empty()) && Achievements::IsHardcoreModeActive())
{
const bool is_exe_override_boot = parameters.save_state.empty();
bool cancelled;
if (FullscreenUI::IsInitialized())
{
Achievements::ConfirmHardcoreModeDisableAsync(TRANSLATE("Achievements", "Resuming state"),
Achievements::ConfirmHardcoreModeDisableAsync(is_exe_override_boot ?
TRANSLATE("Achievements", "Overriding executable") :
TRANSLATE("Achievements", "Resuming state"),
[parameters = std::move(parameters)](bool approved) mutable {
if (approved)
{
@ -1638,7 +1661,9 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
}
else
{
cancelled = !Achievements::ConfirmHardcoreModeDisable(TRANSLATE("Achievements", "Resuming state"));
cancelled = !Achievements::ConfirmHardcoreModeDisable(is_exe_override_boot ?
TRANSLATE("Achievements", "Overriding executable") :
TRANSLATE("Achievements", "Resuming state"));
}
if (cancelled)
@ -1677,45 +1702,14 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (disc)
CDROM::InsertMedia(std::move(disc), disc_region);
s_boot_mode = boot_mode;
s_exe_override = std::move(exe_override);
UpdateControllers();
UpdateMemoryCardTypes();
UpdateMultitaps();
InternalReset();
// Load EXE late after BIOS.
if (!exe_boot.empty() && !LoadEXE(exe_boot.c_str()))
{
Error::SetStringFmt(error, "Failed to load EXE file '{}'", Path::GetFileName(exe_boot));
DestroySystem();
return false;
}
else if (do_psf_boot && !PSFLoader::Load(parameters.filename.c_str()))
{
Error::SetStringFmt(error, "Failed to load PSF file '{}'", Path::GetFileName(parameters.filename));
DestroySystem();
return false;
}
// Apply fastboot patch if enabled.
if (CDROM::HasMedia() && (parameters.override_fast_boot.has_value() ? parameters.override_fast_boot.value() :
g_settings.bios_patch_fast_boot))
{
if (!CDROM::IsMediaPS1Disc())
{
ERROR_LOG("Not fast booting non-PS1 disc.");
}
else if (!s_bios_image_info || !s_bios_image_info->patch_compatible)
{
ERROR_LOG("Not patching fast boot, as BIOS is not patch compatible.");
}
else
{
// TODO: Fast boot without patches...
BIOS::PatchBIOSFastBoot(Bus::g_bios, Bus::BIOS_SIZE);
s_was_fast_booted = true;
}
}
// Texture replacement preloading.
// TODO: Move this and everything else below OnSystemStarted().
TextureReplacements::SetGameID(s_running_game_serial);
@ -1932,7 +1926,8 @@ void System::DestroySystem()
s_bios_hash = {};
s_bios_image_info = nullptr;
s_was_fast_booted = false;
s_exe_override = {};
s_boot_mode = BootMode::FullBoot;
s_cheat_list.reset();
s_state = State::Shutdown;
@ -2474,10 +2469,51 @@ void System::InternalReset()
s_internal_frame_number = 0;
InterruptExecution();
ResetPerformanceCounters();
ResetBootMode();
Achievements::ResetClient();
}
void System::ResetBootMode()
{
// Preserve exe/psf boot.
if (s_boot_mode == BootMode::BootEXE || s_boot_mode == BootMode::BootPSF)
{
DebugAssert(!s_exe_override.empty());
return;
}
// Reset fast boot flag from settings.
const bool wants_fast_boot = (g_settings.bios_patch_fast_boot && CDROM::IsMediaPS1Disc() && s_bios_image_info &&
s_bios_image_info->patch_compatible);
const System::BootMode new_boot_mode = (s_state != System::State::Starting) ?
(wants_fast_boot ? System::BootMode::FastBoot : System::BootMode::FullBoot) :
s_boot_mode;
if (new_boot_mode != s_boot_mode)
{
// Need to reload the BIOS to wipe out the patching.
Error error;
if (!LoadBIOS(&error))
ERROR_LOG("Failed to reload BIOS on boot mode change, the system may be unstable: {}", error.GetDescription());
}
s_boot_mode = new_boot_mode;
if (s_boot_mode == BootMode::FastBoot)
{
if (s_bios_image_info && s_bios_image_info->patch_compatible)
{
// Patch BIOS, this sucks.
INFO_LOG("Patching BIOS for fast boot.");
BIOS::PatchBIOSFastBoot(Bus::g_bios, Bus::BIOS_SIZE);
}
else
{
ERROR_LOG("Cannot fast boot, BIOS is incompatible.");
s_boot_mode = BootMode::FullBoot;
}
}
}
std::string System::GetMediaPathFromSaveState(const char* path)
{
std::string ret;
@ -3137,138 +3173,6 @@ void System::DoToggleCheats()
Host::OSD_QUICK_DURATION);
}
static bool LoadEXEToRAM(const char* filename, bool patch_bios)
{
std::FILE* fp = FileSystem::OpenCFile(filename, "rb");
if (!fp)
{
ERROR_LOG("Failed to open exe file '{}'", filename);
return false;
}
std::fseek(fp, 0, SEEK_END);
const u32 file_size = static_cast<u32>(std::ftell(fp));
std::fseek(fp, 0, SEEK_SET);
BIOS::PSEXEHeader header;
if (std::fread(&header, sizeof(header), 1, fp) != 1 || !BIOS::IsValidPSExeHeader(header, file_size))
{
ERROR_LOG("'{}' is not a valid PS-EXE", filename);
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++)
{
CPU::SafeWriteMemoryWord(address, 0);
address += sizeof(u32);
}
}
const u32 file_data_size = std::min<u32>(file_size - sizeof(BIOS::PSEXEHeader), header.file_size);
if (file_data_size >= 4)
{
std::vector<u32> data_words((file_data_size + 3) / 4);
if (std::fread(data_words.data(), file_data_size, 1, fp) != 1)
{
std::fclose(fp);
return false;
}
const u32 num_words = file_data_size / 4;
u32 address = header.load_address;
for (u32 i = 0; i < num_words; i++)
{
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.initial_pc;
const u32 r_gp = header.initial_gp;
const u32 r_sp = header.initial_sp_base + header.initial_sp_offset;
const u32 r_fp = header.initial_sp_base + header.initial_sp_offset;
return BIOS::PatchBIOSForEXE(Bus::g_bios, Bus::BIOS_SIZE, r_pc, r_gp, r_sp, r_fp);
}
bool System::LoadEXE(const char* filename)
{
const std::string libps_path(Path::BuildRelativePath(filename, "libps.exe"));
if (!libps_path.empty() && FileSystem::FileExists(libps_path.c_str()) && !LoadEXEToRAM(libps_path.c_str(), false))
{
ERROR_LOG("Failed to load libps.exe from '{}'", libps_path.c_str());
return false;
}
return LoadEXEToRAM(filename, true);
}
bool System::InjectEXEFromBuffer(const void* buffer, u32 buffer_size, bool patch_bios)
{
const u8* buffer_ptr = static_cast<const u8*>(buffer);
const u8* buffer_end = static_cast<const u8*>(buffer) + buffer_size;
BIOS::PSEXEHeader header;
if (buffer_size < sizeof(header))
return false;
std::memcpy(&header, buffer_ptr, sizeof(header));
buffer_ptr += sizeof(header);
const u32 file_size = static_cast<u32>(static_cast<u32>(buffer_end - buffer_ptr));
if (!BIOS::IsValidPSExeHeader(header, file_size))
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 file_data_size = std::min<u32>(file_size - sizeof(BIOS::PSEXEHeader), header.file_size);
if (file_data_size >= 4)
{
std::vector<u32> data_words((file_data_size + 3) / 4);
if ((buffer_end - buffer_ptr) < file_data_size)
return false;
std::memcpy(data_words.data(), buffer_ptr, file_data_size);
const u32 num_words = file_data_size / 4;
u32 address = header.load_address;
for (u32 i = 0; i < num_words; i++)
{
CPU::SafeWriteMemoryWord(address, data_words[i]);
address += sizeof(u32);
}
}
// patch the BIOS to jump to the executable directly
if (patch_bios)
{
const u32 r_pc = header.initial_pc;
const u32 r_gp = header.initial_gp;
const u32 r_sp = header.initial_sp_base + header.initial_sp_offset;
const u32 r_fp = header.initial_sp_base + header.initial_sp_offset;
if (!BIOS::PatchBIOSForEXE(Bus::g_bios, Bus::BIOS_SIZE, r_pc, r_gp, r_sp, r_fp))
return false;
}
return true;
}
#if 0
// currently not used until EXP1 is implemented
@ -5308,7 +5212,8 @@ void System::RequestDisplaySize(float scale /*= 0.0f*/)
u32 requested_height =
std::max<u32>(static_cast<u32>(std::ceil(static_cast<float>(g_gpu->GetCRTCDisplayHeight()) * y_scale * scale)), 1);
if (g_settings.display_rotation == DisplayRotation::Rotate90 || g_settings.display_rotation == DisplayRotation::Rotate180)
if (g_settings.display_rotation == DisplayRotation::Rotate90 ||
g_settings.display_rotation == DisplayRotation::Rotate180)
std::swap(requested_width, requested_height);
Host::RequestResizeHostDisplay(static_cast<s32>(requested_width), static_cast<s32>(requested_height));

View file

@ -109,6 +109,14 @@ enum class State
Stopping,
};
enum class BootMode
{
FullBoot,
FastBoot,
BootEXE,
BootPSF,
};
using GameHash = u64;
extern TickCount g_ticks_per_second;
@ -194,10 +202,6 @@ ALWAYS_INLINE_RELEASE TickCount UnscaleTicksToOverclock(TickCount ticks, TickCou
TickCount GetMaxSliceTicks();
void UpdateOverclock();
/// Injects a PS-EXE into memory at its specified load location. If patch_loader is set, the BIOS will be patched to
/// direct execution to this executable.
bool InjectEXEFromBuffer(const void* buffer, u32 buffer_size, bool patch_loader = true);
u32 GetGlobalTickCounter();
u32 GetFrameNumber();
u32 GetInternalFrameNumber();
@ -207,10 +211,11 @@ void FrameDone();
const std::string& GetDiscPath();
const std::string& GetGameSerial();
const std::string& GetGameTitle();
const std::string& GetExeOverride();
const GameDatabase::Entry* GetGameDatabaseEntry();
GameHash GetGameHash();
bool IsRunningUnknownGame();
bool WasFastBooted();
BootMode GetBootMode();
/// Returns the time elapsed in the current play session.
u64 GetSessionPlayedTime();