diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp index 3f8559a54..92cba6839 100644 --- a/src/core/achievements.cpp +++ b/src/core/achievements.cpp @@ -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(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 {}; diff --git a/src/core/bios.cpp b/src/core/bios.cpp index 3974adc1c..2a9a2a180 100644 --- a/src/core/bios.cpp +++ b/src/core/bios.cpp @@ -19,6 +19,7 @@ Log_SetChannel(BIOS); namespace BIOS { static const ImageInfo* GetInfoForHash(const std::span 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(file_size - sizeof(PSEXEHeader))); + file_size - sizeof(PSEXEHeader)); } return true; diff --git a/src/core/bios.h b/src/core/bios.h index 833fdf0d2..3e6a56f2f 100644 --- a/src/core/bios.h +++ b/src/core/bios.h @@ -70,12 +70,9 @@ std::optional 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. diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 780187a11..f08c9255a 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -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 #include @@ -146,6 +151,8 @@ static std::vector> 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 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 data) -{ - s_exp1_rom = std::move(data); + sw.DoEx(&s_kernel_initialize_hook_run, 68, true); + + return !sw.HasError(); } std::tuple Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay) @@ -863,6 +843,146 @@ std::optional Bus::SearchMemory(PhysicalMemoryAddress sta return std::nullopt; } +void Bus::SetExpansionROM(std::vector 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 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(static_cast(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> 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> 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(); + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/bus.h b/src/core/bus.h index ead3a359a..af5013134 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -218,4 +219,7 @@ std::optional 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 buffer, bool set_pc, Error* error); + } // namespace Bus diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index a54bfd83d..192de70c2 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -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; diff --git a/src/core/cpu_core_private.h b/src/core/cpu_core_private.h index e2d89f45e..1cfe91697 100644 --- a/src/core/cpu_core_private.h +++ b/src/core/cpu_core_private.h @@ -7,6 +7,8 @@ namespace CPU { +void SetPC(u32 new_pc); + // exceptions void RaiseException(Exception excode); void RaiseException(u32 CAUSE_bits, u32 EPC); diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 1b84f212e..aef933b73 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -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(); diff --git a/src/core/psf_loader.cpp b/src/core/psf_loader.cpp index 73e9e0056..966a5447a 100644 --- a/src/core/psf_loader.cpp +++ b/src/core/psf_loader.cpp @@ -1,21 +1,29 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // 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 #include + Log_SetChannel(PSFLoader); namespace PSFLoader { +static bool LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth = 0); +} -std::optional File::GetTagString(const char* tag_name) const +std::optional 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 File::GetTagString(const char* tag_name) const return it->second; } -std::optional File::GetTagInt(const char* tag_name) const +std::optional 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 File::GetTagInt(const char* tag_name) const return std::atoi(it->second.c_str()); } -std::optional File::GetTagFloat(const char* tag_name) const +std::optional 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 File::GetTagFloat(const char* tag_name) const return static_cast(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 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> file_data(FileSystem::ReadBinaryFile(path)); + std::optional> 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 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(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 \ No newline at end of file diff --git a/src/core/psf_loader.h b/src/core/psf_loader.h index 29cc30716..5e5879e32 100644 --- a/src/core/psf_loader.h +++ b/src/core/psf_loader.h @@ -1,14 +1,18 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once + #include "types.h" + #include #include #include #include #include +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 \ No newline at end of file diff --git a/src/core/save_state_version.h b/src/core/save_state_version.h index 0275e5d51..4c66cc043 100644 --- a/src/core/save_state_version.h +++ b/src/core/save_state_version.h @@ -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); diff --git a/src/core/system.cpp b/src/core/system.cpp index 604d343bc..be32d0d2d 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -108,8 +108,6 @@ static std::optional InternalGetExtendedSaveStateInfo(Byt static void LoadInputBindings(SettingsInterface& si, std::unique_lock& 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* out_executable_data); @@ -117,6 +115,7 @@ static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span 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(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(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(file_size - sizeof(BIOS::PSEXEHeader), header.file_size); - if (file_data_size >= 4) - { - std::vector 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(buffer); - const u8* buffer_end = static_cast(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(static_cast(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(file_size - sizeof(BIOS::PSEXEHeader), header.file_size); - if (file_data_size >= 4) - { - std::vector 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(static_cast(std::ceil(static_cast(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(requested_width), static_cast(requested_height)); diff --git a/src/core/system.h b/src/core/system.h index 0ebb28f8c..3a920b7ab 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -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();