From 246c97ccb38aa1d10b5e518dd9831c3e2b367160 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 16 Nov 2019 15:27:57 +1000 Subject: [PATCH] System: Scaffolding for multi-system/multi-bios --- src/core/CMakeLists.txt | 2 + src/core/bios.cpp | 178 ++++++++++++++++++++++ src/core/bios.h | 37 +++++ src/core/bus.cpp | 55 +------ src/core/bus.h | 5 +- src/core/cdrom.cpp | 10 +- src/core/cdrom.h | 2 +- src/core/core.vcxproj | 2 + src/core/core.vcxproj.filters | 2 + src/core/gpu.h | 18 +-- src/core/host_interface.cpp | 132 +++++++++++----- src/core/host_interface.h | 18 ++- src/core/settings.cpp | 30 +++- src/core/settings.h | 14 +- src/core/system.cpp | 203 ++++++++++++++----------- src/core/system.h | 22 ++- src/core/types.h | 16 ++ src/duckstation/sdl_host_interface.cpp | 88 +++++------ src/duckstation/sdl_host_interface.h | 10 +- 19 files changed, 576 insertions(+), 268 deletions(-) create mode 100644 src/core/bios.cpp create mode 100644 src/core/bios.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0be317ff0..bce02fa74 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(core + bios.cpp + bios.h bus.cpp bus.h bus.inl diff --git a/src/core/bios.cpp b/src/core/bios.cpp new file mode 100644 index 000000000..ab1027604 --- /dev/null +++ b/src/core/bios.cpp @@ -0,0 +1,178 @@ +#include "bios.h" +#include "YBaseLib/Log.h" +#include "YBaseLib/MD5Digest.h" +#include "cpu_disasm.h" +Log_SetChannel(BIOS); + +namespace BIOS { +static constexpr Hash MakeHashFromString(const char str[]) +{ + Hash h{}; + for (int i = 0; str[i] != '\0'; i++) + { + u8 nibble = 0; + char ch = str[i]; + if (ch >= '0' && ch <= '9') + nibble = str[i] - '0'; + else if (ch >= 'a' && ch <= 'z') + nibble = 0xA + (str[i] - 'a'); + else if (ch >= 'A' && ch <= 'Z') + nibble = 0xA + (str[i] - 'A'); + + h.bytes[i / 2] |= nibble << (((i & 1) ^ 1) * 4); + } + return h; +} + +std::string Hash::ToString() const +{ + char str[33]; + std::snprintf(str, sizeof(str), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", bytes[0], + bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], + bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]); + return str; +} + +static constexpr Hash SCPH_1000_HASH = MakeHashFromString("239665b1a3dade1b5a52c06338011044"); +static constexpr Hash SCPH_1001_HASH = MakeHashFromString("924e392ed05558ffdb115408c263dccf"); +static constexpr Hash SCPH_1002_HASH = MakeHashFromString("54847e693405ffeb0359c6287434cbef"); +static constexpr Hash SCPH_5500_HASH = MakeHashFromString("8dd7d5296a650fac7319bce665a6a53c"); +static constexpr Hash SCPH_5501_HASH = MakeHashFromString("490f666e1afb15b7362b406ed1cea246"); +static constexpr Hash SCPH_5502_HASH = MakeHashFromString("32736f17079d0b2b7024407c39bd3050"); + +Hash GetHash(const Image& image) +{ + Hash hash; + MD5Digest digest; + digest.Update(image.data(), static_cast(image.size())); + digest.Final(hash.bytes); + return hash; +} + +std::optional LoadImageFromFile(std::string_view filename) +{ + Image ret(BIOS_SIZE); + std::string filename_str(filename); + std::FILE* fp = std::fopen(filename_str.c_str(), "rb"); + if (!fp) + { + Log_ErrorPrintf("Failed to open BIOS image '%s'", filename_str.c_str()); + return std::nullopt; + } + + std::fseek(fp, 0, SEEK_END); + const u32 size = static_cast(std::ftell(fp)); + std::fseek(fp, 0, SEEK_SET); + + if (size != BIOS_SIZE) + { + Log_ErrorPrintf("BIOS image '%s' mismatch, expecting %u bytes, got %u bytes", filename_str.c_str(), BIOS_SIZE, + size); + std::fclose(fp); + return std::nullopt; + } + + if (std::fread(ret.data(), 1, ret.size(), fp) != ret.size()) + { + Log_ErrorPrintf("Failed to read BIOS image '%s'", filename_str.c_str()); + std::fclose(fp); + return std::nullopt; + } + + std::fclose(fp); + return ret; +} + +std::optional GetHashForFile(const std::string_view filename) +{ + auto image = LoadImageFromFile(filename); + if (!image) + return std::nullopt; + + return GetHash(*image); +} + +bool IsValidHashForRegion(ConsoleRegion region, const Hash& hash) +{ + switch (region) + { + case ConsoleRegion::NTSC_J: + return (hash == SCPH_1000_HASH || hash == SCPH_5500_HASH); + + case ConsoleRegion::NTSC_U: + return (hash == SCPH_1001_HASH || hash == SCPH_5501_HASH); + + case ConsoleRegion::PAL: + return (hash == SCPH_1002_HASH || hash == SCPH_5502_HASH); + + case ConsoleRegion::Auto: + default: + return false; + } +} + +void PatchBIOS(Image& bios, 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, &bios[offset], sizeof(existing_value)); + u32 new_value = (existing_value & ~mask) | value; + std::memcpy(&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_DevPrintf("BIOS-Patch 0x%08X (+0x%X): 0x%08X %s -> %08X %s", address, offset, existing_value, + old_disasm.GetCharArray(), new_value, new_disasm.GetCharArray()); +} + +bool PatchBIOSEnableTTY(Image& image, const Hash& hash) +{ + if (hash != SCPH_1000_HASH && hash != SCPH_1001_HASH && hash != SCPH_1002_HASH && hash != SCPH_5500_HASH && + hash != SCPH_5501_HASH && hash != SCPH_5502_HASH) + { + Log_WarningPrintf("Incompatible version for TTY patch: %s", hash.ToString().c_str()); + return false; + } + + Log_InfoPrintf("Patching BIOS to enable TTY/printf"); + PatchBIOS(image, 0x1FC06F0C, 0x24010001); + PatchBIOS(image, 0x1FC06F14, 0xAF81A9C0); + return true; +} + +bool PatchBIOSFastBoot(Image& image, const Hash& hash) +{ + if (hash != SCPH_1000_HASH && hash != SCPH_1001_HASH && hash != SCPH_1002_HASH && hash != SCPH_5500_HASH && + hash != SCPH_5501_HASH && hash != SCPH_5502_HASH) + { + Log_WarningPrintf("Incompatible version for fast-boot patch: %s", hash.ToString().c_str()); + return false; + } + + // Replace the shell entry point with a return back to the bootstrap. + Log_InfoPrintf("Patching BIOS to skip intro"); + PatchBIOS(image, 0x1FC18000, 0x03E00008); + PatchBIOS(image, 0x1FC18004, 0x00000000); + return true; +} + +bool PatchBIOSForEXE(Image& image, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp) +{ + // pc has to be done first because we can't load it in the delay slot + PatchBIOS(image, 0xBFC06FF0, UINT32_C(0x3C080000) | r_pc >> 16); // lui $t0, (r_pc >> 16) + PatchBIOS(image, 0xBFC06FF4, UINT32_C(0x35080000) | (r_pc & UINT32_C(0xFFFF))); // ori $t0, $t0, (r_pc & 0xFFFF) + PatchBIOS(image, 0xBFC06FF8, UINT32_C(0x3C1C0000) | r_gp >> 16); // lui $gp, (r_gp >> 16) + PatchBIOS(image, 0xBFC06FFC, UINT32_C(0x379C0000) | (r_gp & UINT32_C(0xFFFF))); // ori $gp, $gp, (r_gp & 0xFFFF) + PatchBIOS(image, 0xBFC07000, UINT32_C(0x3C1D0000) | r_sp >> 16); // lui $sp, (r_sp >> 16) + PatchBIOS(image, 0xBFC07004, UINT32_C(0x37BD0000) | (r_sp & UINT32_C(0xFFFF))); // ori $sp, $sp, (r_sp & 0xFFFF) + PatchBIOS(image, 0xBFC07008, UINT32_C(0x3C1E0000) | r_fp >> 16); // lui $fp, (r_fp >> 16) + PatchBIOS(image, 0xBFC0700C, UINT32_C(0x01000008)); // jr $t0 + PatchBIOS(image, 0xBFC07010, UINT32_C(0x37DE0000) | (r_fp & UINT32_C(0xFFFF))); // ori $fp, $fp, (r_fp & 0xFFFF) + return true; +} + +} // namespace BIOS \ No newline at end of file diff --git a/src/core/bios.h b/src/core/bios.h new file mode 100644 index 000000000..997be8a11 --- /dev/null +++ b/src/core/bios.h @@ -0,0 +1,37 @@ +#pragma once +#include "types.h" +#include +#include +#include + +namespace BIOS { +enum : u32 +{ + BIOS_BASE = 0x1FC00000, + BIOS_SIZE = 0x80000 +}; + +using Image = std::vector; + +struct Hash +{ + u8 bytes[16]; + + ALWAYS_INLINE bool operator==(const Hash& bh) const { return (std::memcmp(bytes, bh.bytes, sizeof(bytes)) == 0); } + ALWAYS_INLINE bool operator!=(const Hash& bh) const { return (std::memcmp(bytes, bh.bytes, sizeof(bytes)) != 0); } + + std::string ToString() const; +}; + +Hash GetHash(const Image& image); +std::optional LoadImageFromFile(std::string_view filename); +std::optional GetHashForFile(std::string_view filename); + +bool IsValidHashForRegion(ConsoleRegion region, const Hash& hash); + +void PatchBIOS(Image& image, u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF)); + +bool PatchBIOSEnableTTY(Image& image, const Hash& hash); +bool PatchBIOSFastBoot(Image& image, const Hash& hash); +bool PatchBIOSForEXE(Image& image, u32 r_pc, u32 r_gp, u32 r_sp, u32 r_fp); +} // namespace BIOS \ No newline at end of file diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 2bcb8362a..506a65e7b 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -166,65 +166,20 @@ TickCount Bus::WriteWords(PhysicalMemoryAddress address, const u32* words, u32 w return static_cast(word_count + ((word_count + 15) / 16)); } -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()); -} - -void Bus::GetBIOSHash(u8 hash[16]) -{ - MD5Digest digest; - digest.Update(m_bios.data(), static_cast(m_bios.size())); - digest.Final(hash); -} - void Bus::SetExpansionROM(std::vector data) { m_exp1_rom = std::move(data); } -bool Bus::LoadBIOS(const char* filename) +void Bus::SetBIOS(const std::vector& image) { - std::FILE* fp = std::fopen(filename, "rb"); - if (!fp) + if (image.size() != static_cast(BIOS_SIZE)) { - Log_ErrorPrintf("Failed to open BIOS image '%s'", filename); - return false; + Panic("Incorrect BIOS image size"); + return; } - std::fseek(fp, 0, SEEK_END); - const u32 size = static_cast(std::ftell(fp)); - std::fseek(fp, 0, SEEK_SET); - - if (size != m_bios.size()) - { - Log_ErrorPrintf("BIOS image mismatch, expecting %u bytes, got %u bytes", static_cast(m_bios.size()), size); - std::fclose(fp); - return false; - } - - if (std::fread(m_bios.data(), 1, m_bios.size(), fp) != m_bios.size()) - { - Log_ErrorPrintf("Failed to read BIOS image"); - std::fclose(fp); - return false; - } - - std::fclose(fp); - return true; + std::copy(image.cbegin(), image.cend(), m_bios.begin()); } std::tuple Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay) diff --git a/src/core/bus.h b/src/core/bus.h index 75138b69f..68eba02a2 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -32,10 +32,6 @@ public: void Reset(); bool DoState(StateWrapper& sw); - bool LoadBIOS(const char* filename); - void PatchBIOS(u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF)); - void GetBIOSHash(u8 hash[16]); - bool ReadByte(PhysicalMemoryAddress address, u8* value); bool ReadHalfWord(PhysicalMemoryAddress address, u16* value); bool ReadWord(PhysicalMemoryAddress address, u32* value); @@ -51,6 +47,7 @@ public: TickCount WriteWords(PhysicalMemoryAddress address, const u32* words, u32 word_count); void SetExpansionROM(std::vector data); + void SetBIOS(const std::vector& image); // changing interfaces void SetGPU(GPU* gpu) { m_gpu = gpu; } diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index d4645c137..d74e03957 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -147,20 +147,12 @@ bool CDROM::DoState(StateWrapper& sw) return !sw.HasError(); } -bool CDROM::InsertMedia(const char* filename) +void CDROM::InsertMedia(std::unique_ptr media) { - auto media = CDImage::Open(filename); - if (!media) - { - Log_ErrorPrintf("Failed to open media at '%s'", filename); - return false; - } - if (HasMedia()) RemoveMedia(); m_media = std::move(media); - return true; } void CDROM::RemoveMedia() diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 117099b6d..420eb3dca 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -27,7 +27,7 @@ public: bool DoState(StateWrapper& sw); bool HasMedia() const { return static_cast(m_media); } - bool InsertMedia(const char* filename); + void InsertMedia(std::unique_ptr media); void RemoveMedia(); // I/O diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index d00e2d2fb..6b1338b25 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -35,6 +35,7 @@ + @@ -61,6 +62,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index d16e5cefb..09d0fca8e 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -25,6 +25,7 @@ + @@ -55,6 +56,7 @@ + diff --git a/src/core/gpu.h b/src/core/gpu.h index 7413aeaa1..1b136725a 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -21,6 +21,15 @@ class Timers; class GPU { public: + enum class State : u8 + { + Idle, + WaitingForParameters, + ExecutingCommand, + ReadingVRAM, + WritingVRAM + }; + enum class DMADirection : u32 { Off = 0, @@ -72,15 +81,6 @@ public: Disabled = 4 // Not a register value }; - enum class State : u8 - { - Idle, - WaitingForParameters, - ExecutingCommand, - ReadingVRAM, - WritingVRAM - }; - enum : u32 { VRAM_WIDTH = 1024, diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index b5820ec25..6af5c56c6 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -1,9 +1,11 @@ #include "host_interface.h" #include "YBaseLib/ByteStream.h" #include "YBaseLib/Log.h" +#include "bios.h" #include "common/audio_stream.h" #include "host_display.h" #include "system.h" +#include Log_SetChannel(HostInterface); HostInterface::HostInterface() @@ -13,67 +15,113 @@ HostInterface::HostInterface() HostInterface::~HostInterface() = default; -bool HostInterface::InitializeSystem(const char* filename, const char* exp1_filename) +bool HostInterface::CreateSystem() { - m_system = std::make_unique(this); - if (!m_system->Initialize()) - { - m_system.reset(); - return false; - } + m_system = System::Create(this); - m_system->Reset(); - - if (filename) - { - const StaticString filename_str(filename); - if (filename_str.EndsWith(".psexe", false) || filename_str.EndsWith(".exe", false)) - { - Log_InfoPrintf("Sideloading EXE file '%s'", filename); - if (!m_system->LoadEXE(filename)) - { - Log_ErrorPrintf("Failed to load EXE file '%s'", filename); - return false; - } - } - else - { - Log_InfoPrintf("Inserting CDROM from image file '%s'", filename); - if (!m_system->InsertMedia(filename)) - { - Log_ErrorPrintf("Failed to insert media '%s'", filename); - return false; - } - } - } - - if (exp1_filename) - m_system->SetExpansionROM(exp1_filename); - - // Resume execution. + // Pull in any invalid settings which have been reset. m_settings = m_system->GetSettings(); + m_paused = true; + UpdateAudioVisualSync(); return true; } -void HostInterface::ShutdownSystem() +bool HostInterface::BootSystem(const char* filename, const char* state_filename) +{ + if (!m_system->Boot(filename)) + return false; + + m_paused = m_settings.start_paused; + ConnectControllers(); + UpdateAudioVisualSync(); + return true; +} + +void HostInterface::DestroySystem() { m_system.reset(); m_paused = false; UpdateAudioVisualSync(); } +void HostInterface::ReportError(const char* message) +{ + Log_ErrorPrint(message); +} + +void HostInterface::ReportMessage(const char* message) +{ + Log_InfoPrintf(message); +} + +std::optional> HostInterface::GetBIOSImage(ConsoleRegion region) +{ + // Try the other default filenames in the directory of the configured BIOS. +#define TRY_FILENAME(filename) \ + do \ + { \ + std::string try_filename = filename; \ + std::optional found_image = BIOS::LoadImageFromFile(try_filename); \ + BIOS::Hash found_hash = BIOS::GetHash(*found_image); \ + Log_DevPrintf("Hash for BIOS '%s': %s", try_filename.c_str(), found_hash.ToString().c_str()); \ + if (BIOS::IsValidHashForRegion(region, found_hash)) \ + { \ + Log_InfoPrintf("Using BIOS from '%s'", try_filename.c_str()); \ + return found_image; \ + } \ + } while (0) + +#define RELATIVE_PATH(filename) std::filesystem::path(m_settings.bios_path).replace_filename(filename).string() + + // Try the configured image. + TRY_FILENAME(m_settings.bios_path); + + // Try searching in the same folder for other region's images. + switch (region) + { + case ConsoleRegion::NTSC_J: + TRY_FILENAME(RELATIVE_PATH("scph1000.bin")); + TRY_FILENAME(RELATIVE_PATH("scph5500.bin")); + break; + + case ConsoleRegion::NTSC_U: + TRY_FILENAME(RELATIVE_PATH("scph1001.bin")); + TRY_FILENAME(RELATIVE_PATH("scph5501.bin")); + break; + + case ConsoleRegion::PAL: + TRY_FILENAME(RELATIVE_PATH("scph1002.bin")); + TRY_FILENAME(RELATIVE_PATH("scph5502.bin")); + break; + + default: + break; + } + +#undef RELATIVE_PATH +#undef TRY_FILENAME + + // Fall back to the default image. + Log_WarningPrintf("No suitable BIOS image for region %s could be located, using configured image '%s'. This may " + "result in instability.", + Settings::GetConsoleRegionName(region), m_settings.bios_path.c_str()); + return BIOS::LoadImageFromFile(m_settings.bios_path); +} + +void HostInterface::ConnectControllers() {} + bool HostInterface::LoadState(const char* filename) { ByteStream* stream; if (!ByteStream_OpenFileStream(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED, &stream)) return false; - ReportMessage(SmallString::FromFormat("Loading state from %s...", filename)); + AddOSDMessage(SmallString::FromFormat("Loading state from %s...", filename)); const bool result = m_system->LoadState(stream); if (!result) { - ReportMessage(SmallString::FromFormat("Loading state from %s failed. Resetting.", filename)); + ReportError(SmallString::FromFormat("Loading state from %s failed. Resetting.", filename)); m_system->Reset(); } @@ -95,12 +143,12 @@ bool HostInterface::SaveState(const char* filename) const bool result = m_system->SaveState(stream); if (!result) { - ReportMessage(SmallString::FromFormat("Saving state to %s failed.", filename)); + ReportError(SmallString::FromFormat("Saving state to %s failed.", filename)); stream->Discard(); } else { - ReportMessage(SmallString::FromFormat("State saved to %s.", filename)); + AddOSDMessage(SmallString::FromFormat("State saved to %s.", filename)); stream->Commit(); } @@ -112,7 +160,7 @@ void HostInterface::UpdateAudioVisualSync() { const bool speed_limiter_enabled = m_settings.speed_limiter_enabled && !m_speed_limiter_temp_disabled; const bool audio_sync_enabled = speed_limiter_enabled; - const bool vsync_enabled = !m_system || (speed_limiter_enabled && m_settings.gpu_vsync); + const bool vsync_enabled = !m_system || m_paused || (speed_limiter_enabled && m_settings.gpu_vsync); Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "", (speed_limiter_enabled && vsync_enabled) ? " and video" : ""); diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 87c97d9f4..8bf69184b 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -2,6 +2,8 @@ #include "types.h" #include "settings.h" #include +#include +#include class AudioStream; class HostDisplay; @@ -23,20 +25,26 @@ public: /// Returns a settings object which can be modified. Settings& GetSettings() { return m_settings; } + bool CreateSystem(); + bool BootSystem(const char* filename, const char* state_filename); + void DestroySystem(); - bool InitializeSystem(const char* filename, const char* exp1_filename); - void ShutdownSystem(); - - - virtual void ReportMessage(const char* message) = 0; + virtual void ReportError(const char* message); + virtual void ReportMessage(const char* message); // Adds OSD messages, duration is in seconds. virtual void AddOSDMessage(const char* message, float duration = 2.0f) = 0; + /// Loads the BIOS image for the specified region. + virtual std::optional> GetBIOSImage(ConsoleRegion region); + bool LoadState(const char* filename); bool SaveState(const char* filename); protected: + /// Connects controllers. TODO: Clean this up later... + virtual void ConnectControllers(); + void UpdateAudioVisualSync(); std::unique_ptr m_display; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index c794f8977..6c3ddd4c5 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -91,11 +91,39 @@ bool Settings::Save(const char* filename) const return true; } +static std::array s_console_region_names = {{"Auto", "NTSC-J", "NTSC-U", "PAL"}}; +static std::array s_console_region_display_names = { + {"Auto-Detect", "NTSC-J (Japan)", "NTSC-U (US)", "PAL (Europe, Australia)"}}; + +std::optional Settings::ParseConsoleRegionName(const char* str) +{ + int index = 0; + for (const char* name : s_console_region_names) + { + if (strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetConsoleRegionName(ConsoleRegion region) +{ + return s_console_region_names[static_cast(region)]; +} + +const char* Settings::GetConsoleRegionDisplayName(ConsoleRegion region) +{ + return s_console_region_display_names[static_cast(region)]; +} + static std::array s_gpu_renderer_names = {{"D3D11", "OpenGL", "Software"}}; static std::array s_gpu_renderer_display_names = { {"Hardware (D3D11)", "Hardware (OpenGL)", "Software"}}; -std::optional Settings::ParseRendererName(const char* str) +std::optional Settings::ParseRendererName(const char* str) { int index = 0; for (const char* name : s_gpu_renderer_names) diff --git a/src/core/settings.h b/src/core/settings.h index 801b295f1..e08150b57 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -4,16 +4,10 @@ struct Settings { - enum class GPURenderer - { - HardwareD3D11, - HardwareOpenGL, - Software, - Count - }; - Settings(); + ConsoleRegion region = ConsoleRegion::NTSC_U; + bool start_paused = false; bool speed_limiter_enabled = true; @@ -52,6 +46,10 @@ struct Settings void Load(const char* filename); bool Save(const char* filename) const; + static std::optional ParseConsoleRegionName(const char* str); + static const char* GetConsoleRegionName(ConsoleRegion region); + static const char* GetConsoleRegionDisplayName(ConsoleRegion region); + static std::optional ParseRendererName(const char* str); static const char* GetRendererName(GPURenderer renderer); static const char* GetRendererDisplayName(GPURenderer renderer); diff --git a/src/core/system.cpp b/src/core/system.cpp index 2497659e5..226e6387b 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1,6 +1,7 @@ #include "system.h" #include "YBaseLib/AutoReleasePtr.h" #include "YBaseLib/Log.h" +#include "bios.h" #include "bus.h" #include "cdrom.h" #include "common/state_wrapper.h" @@ -19,15 +20,6 @@ #include Log_SetChannel(System); -namespace BIOSHashes { -static constexpr char SCPH_1000[] = "239665b1a3dade1b5a52c06338011044"; -static constexpr char SCPH_1001[] = "924e392ed05558ffdb115408c263dccf"; -static constexpr char SCPH_1002[] = "54847e693405ffeb0359c6287434cbef"; -static constexpr char SCPH_5500[] = "8dd7d5296a650fac7319bce665a6a53c"; -static constexpr char SCPH_5501[] = "490f666e1afb15b7362b406ed1cea246"; -static constexpr char SCPH_5502[] = "32736f17079d0b2b7024407c39bd3050"; -} // namespace BIOSHashes - System::System(HostInterface* host_interface) : m_host_interface(host_interface) { m_cpu = std::make_unique(); @@ -39,10 +31,32 @@ System::System(HostInterface* host_interface) : m_host_interface(host_interface) m_timers = std::make_unique(); m_spu = std::make_unique(); m_mdec = std::make_unique(); + m_region = host_interface->GetSettings().region; } System::~System() = default; +std::optional System::GetRegionForCDImage(const CDImage* image) +{ + // TODO: Implement me. + return ConsoleRegion::NTSC_U; +} + +bool System::IsPSExe(const char* filename) +{ + const StaticString filename_str(filename); + return filename_str.EndsWith(".psexe", false) || filename_str.EndsWith(".exe", false); +} + +std::unique_ptr System::Create(HostInterface* host_interface) +{ + std::unique_ptr system(new System(host_interface)); + if (!system->CreateGPU()) + return {}; + + return system; +} + bool System::RecreateGPU() { // save current state @@ -70,7 +84,83 @@ bool System::RecreateGPU() return true; } -bool System::Initialize() +bool System::Boot(const char* filename) +{ + // Load CD image up and detect region. + std::unique_ptr media; + bool exe_boot = false; + if (filename) + { + exe_boot = IsPSExe(filename); + if (exe_boot) + { + if (m_region == ConsoleRegion::Auto) + { + Log_InfoPrintf("Defaulting to NTSC-U region for executable."); + m_region = ConsoleRegion::NTSC_U; + } + } + else + { + Log_InfoPrintf("Loading CD image '%s'...", filename); + media = CDImage::Open(filename); + if (!media) + { + m_host_interface->ReportError(SmallString::FromFormat("Failed to load CD image '%s'", filename)); + return false; + } + + if (m_region == ConsoleRegion::Auto) + { + std::optional detected_region = GetRegionForCDImage(media.get()); + m_region = detected_region.value_or(ConsoleRegion::NTSC_U); + if (detected_region) + Log_InfoPrintf("Auto-detected %s region for '%s'", Settings::GetConsoleRegionName(m_region)); + else + Log_WarningPrintf("Could not determine region for CD. Defaulting to NTSC-U."); + } + } + } + + // Load BIOS image. + std::optional bios_image = m_host_interface->GetBIOSImage(m_region); + if (!bios_image) + { + m_host_interface->ReportError( + TinyString::FromFormat("Failed to load %s BIOS", Settings::GetConsoleRegionName(m_region))); + return false; + } + + // Component setup. + InitializeComponents(); + UpdateMemoryCards(); + + // Enable tty by patching bios. + const BIOS::Hash bios_hash = BIOS::GetHash(*bios_image); + if (GetSettings().bios_patch_tty_enable) + BIOS::PatchBIOSEnableTTY(*bios_image, bios_hash); + + // Load EXE late after BIOS. + if (exe_boot && !LoadEXE(filename, *bios_image)) + { + m_host_interface->ReportError(SmallString::FromFormat("Failed to load EXE file '%s'", filename)); + return false; + } + + // Insert CD, and apply fastboot patch if enabled. + m_cdrom->InsertMedia(std::move(media)); + if (m_cdrom->HasMedia() && GetSettings().bios_patch_fast_boot) + BIOS::PatchBIOSFastBoot(*bios_image, bios_hash); + + // Load the patched BIOS up. + m_bus->SetBIOS(*bios_image); + + // Good to go. + Reset(); + return true; +} + +void System::InitializeComponents() { m_cpu->Initialize(m_bus.get()); m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(), m_pad.get(), @@ -86,32 +176,23 @@ bool System::Initialize() m_timers->Initialize(this, m_interrupt_controller.get()); m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get()); m_mdec->Initialize(this, m_dma.get()); - - if (!CreateGPU()) - return false; - - if (!LoadBIOS()) - return false; - - UpdateMemoryCards(); - return true; } bool System::CreateGPU() { switch (m_host_interface->GetSettings().gpu_renderer) { - case Settings::GPURenderer::HardwareOpenGL: + case GPURenderer::HardwareOpenGL: m_gpu = GPU::CreateHardwareOpenGLRenderer(); break; #ifdef WIN32 - case Settings::GPURenderer::HardwareD3D11: + case GPURenderer::HardwareD3D11: m_gpu = GPU::CreateHardwareD3D11Renderer(); break; #endif - case Settings::GPURenderer::Software: + case GPURenderer::Software: default: m_gpu = GPU::CreateSoftwareRenderer(); break; @@ -122,7 +203,7 @@ bool System::CreateGPU() { Log_ErrorPrintf("Failed to initialize GPU, falling back to software"); m_gpu.reset(); - m_host_interface->GetSettings().gpu_renderer = Settings::GPURenderer::Software; + m_host_interface->GetSettings().gpu_renderer = GPURenderer::Software; m_gpu = GPU::CreateSoftwareRenderer(); if (!m_gpu->Initialize(m_host_interface->GetDisplay(), this, m_dma.get(), m_interrupt_controller.get(), m_timers.get())) @@ -136,49 +217,6 @@ bool System::CreateGPU() return true; } -bool System::LoadBIOS() -{ - if (!m_bus->LoadBIOS(GetSettings().bios_path.c_str())) - return false; - - // apply patches - u8 bios_hash[16]; - m_bus->GetBIOSHash(bios_hash); - SmallString bios_hash_string; - bios_hash_string.Format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", bios_hash[0], - bios_hash[1], bios_hash[2], bios_hash[3], bios_hash[4], bios_hash[5], bios_hash[6], - bios_hash[7], bios_hash[8], bios_hash[9], bios_hash[10], bios_hash[11], bios_hash[12], - bios_hash[13], bios_hash[14], bios_hash[15]); - Log_InfoPrintf("BIOS hash: %s", bios_hash_string.GetCharArray()); - - if (bios_hash_string == BIOSHashes::SCPH_1000 || bios_hash_string == BIOSHashes::SCPH_1001 || - bios_hash_string == BIOSHashes::SCPH_1002 || bios_hash_string == BIOSHashes::SCPH_5500 || - bios_hash_string == BIOSHashes::SCPH_5501 || bios_hash_string == BIOSHashes::SCPH_5502) - { - if (GetSettings().bios_patch_tty_enable) - { - Log_InfoPrintf("Patching BIOS to enable TTY/printf"); - m_bus->PatchBIOS(0x1FC06F0C, 0x24010001); - m_bus->PatchBIOS(0x1FC06F14, 0xAF81A9C0); - } - - if (GetSettings().bios_patch_fast_boot) - { - Log_InfoPrintf("Patching BIOS for fast boot"); - - // Replace the shell entry point with a return back to the bootstrap. - m_bus->PatchBIOS(0x1FC18000, 0x03E00008); - m_bus->PatchBIOS(0x1FC18004, 0x00000000); - } - } - else - { - Log_WarningPrintf("Unknown BIOS version, not applying patches"); - } - - return true; -} - bool System::DoState(StateWrapper& sw) { if (!sw.DoMarker("System")) @@ -260,7 +298,7 @@ void System::RunFrame() } } -bool System::LoadEXE(const char* filename) +bool System::LoadEXE(const char* filename, std::vector& bios_image) { #pragma pack(push, 1) struct EXEHeader @@ -326,25 +364,11 @@ bool System::LoadEXE(const char* filename) 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; + 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; + return BIOS::PatchBIOSForEXE(bios_image, r_pc, r_gp, r_sp, r_fp); } bool System::SetExpansionROM(const char* filename) @@ -438,7 +462,12 @@ bool System::HasMedia() const bool System::InsertMedia(const char* path) { - return m_cdrom->InsertMedia(path); + std::unique_ptr image = CDImage::Open(path); + if (!image) + return false; + + m_cdrom->InsertMedia(std::move(image)); + return true; } void System::RemoveMedia() diff --git a/src/core/system.h b/src/core/system.h index e1cd33277..ae92f89fc 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -2,8 +2,10 @@ #include "types.h" #include "host_interface.h" #include +#include class ByteStream; +class CDImage; class StateWrapper; namespace CPU { @@ -24,9 +26,17 @@ class MDEC; class System { public: - System(HostInterface* host_interface); ~System(); + /// Detects region for the specified image file. + static std::optional GetRegionForCDImage(const CDImage* image); + + /// Returns true if the filename is a PlayStation executable we can inject. + static bool IsPSExe(const char* filename); + + /// Creates a new System. + static std::unique_ptr Create(HostInterface* host_interface); + // Accessing components. HostInterface* GetHostInterface() const { return m_host_interface; } CPU::Core* GetCPU() const { return m_cpu.get(); } @@ -48,7 +58,7 @@ public: const Settings& GetSettings() { return m_host_interface->GetSettings(); } - bool Initialize(); + bool Boot(const char* filename); void Reset(); bool LoadState(ByteStream* state); @@ -59,7 +69,7 @@ public: void RunFrame(); - bool LoadEXE(const char* filename); + bool LoadEXE(const char* filename, std::vector& bios_image); bool SetExpansionROM(const char* filename); void SetDowncount(TickCount downcount); @@ -76,9 +86,12 @@ public: void RemoveMedia(); private: + System(HostInterface* host_interface); + bool DoState(StateWrapper& sw); bool CreateGPU(); - bool LoadBIOS(); + + void InitializeComponents(); HostInterface* m_host_interface; std::unique_ptr m_cpu; @@ -91,6 +104,7 @@ private: std::unique_ptr m_timers; std::unique_ptr m_spu; std::unique_ptr m_mdec; + ConsoleRegion m_region = ConsoleRegion::NTSC_U; u32 m_frame_number = 1; u32 m_internal_frame_number = 1; u32 m_global_tick_counter = 0; diff --git a/src/core/types.h b/src/core/types.h index 79a71a107..41307092a 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -22,3 +22,19 @@ using TickCount = s32; static constexpr TickCount MASTER_CLOCK = 44100 * 0x300; // 33868800Hz or 33.8688MHz, also used as CPU clock static constexpr TickCount MAX_SLICE_SIZE = MASTER_CLOCK / 10; +enum class ConsoleRegion +{ + Auto, + NTSC_J, + NTSC_U, + PAL, + Count +}; + +enum class GPURenderer : u8 +{ + HardwareD3D11, + HardwareOpenGL, + Software, + Count +}; diff --git a/src/duckstation/sdl_host_interface.cpp b/src/duckstation/sdl_host_interface.cpp index 27cb86387..5096d6a33 100644 --- a/src/duckstation/sdl_host_interface.cpp +++ b/src/duckstation/sdl_host_interface.cpp @@ -133,21 +133,7 @@ void SDLHostInterface::SaveSettings() m_settings.Save(m_settings_filename.c_str()); } -bool SDLHostInterface::InitializeSystem(const char* filename, const char* exp1_filename) -{ - if (!HostInterface::InitializeSystem(filename, exp1_filename)) - { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "System initialization failed.", m_window); - return false; - } - - ConnectDevices(); - UpdateAudioVisualSync(); - m_paused = m_system->GetSettings().start_paused; - return true; -} - -void SDLHostInterface::ConnectDevices() +void SDLHostInterface::ConnectControllers() { m_controller = DigitalController::Create(); m_system->SetController(0, m_controller); @@ -216,7 +202,7 @@ std::unique_ptr SDLHostInterface::Create(const char* filename const bool boot = (filename != nullptr || exp1_filename != nullptr || save_state_filename != nullptr); if (boot) { - if (!intf->InitializeSystem(filename, exp1_filename)) + if (!intf->CreateSystem() || !intf->BootSystem(filename, exp1_filename)) return nullptr; if (save_state_filename) @@ -235,9 +221,14 @@ TinyString SDLHostInterface::GetSaveStateFilename(u32 index) return TinyString::FromFormat("savestate_%u.bin", index); } +void SDLHostInterface::ReportError(const char* message) +{ + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DuckStation Error", message, m_window); +} + void SDLHostInterface::ReportMessage(const char* message) { - AddOSDMessage(message, 3.0f); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "DuckStation Information", message, m_window); } static inline u32 SDLButtonToHostButton(u32 button) @@ -736,13 +727,13 @@ void SDLHostInterface::DrawQuickSettingsMenu() if (ImGui::BeginMenu("Renderer")) { - const Settings::GPURenderer current = m_settings.gpu_renderer; - for (u32 i = 0; i < static_cast(Settings::GPURenderer::Count); i++) + const GPURenderer current = m_settings.gpu_renderer; + for (u32 i = 0; i < static_cast(GPURenderer::Count); i++) { - if (ImGui::MenuItem(Settings::GetRendererDisplayName(static_cast(i)), nullptr, + if (ImGui::MenuItem(Settings::GetRendererDisplayName(static_cast(i)), nullptr, i == static_cast(current))) { - m_settings.gpu_renderer = static_cast(i); + m_settings.gpu_renderer = static_cast(i); settings_changed = true; if (m_system) SwitchGPURenderer(); @@ -998,12 +989,12 @@ void SDLHostInterface::DrawSettingsWindow() if (ImGui::Combo( "##gpu_renderer", &gpu_renderer, [](void*, int index, const char** out_text) { - *out_text = Settings::GetRendererDisplayName(static_cast(index)); + *out_text = Settings::GetRendererDisplayName(static_cast(index)); return true; }, - nullptr, static_cast(Settings::GPURenderer::Count))) + nullptr, static_cast(GPURenderer::Count))) { - m_settings.gpu_renderer = static_cast(gpu_renderer); + m_settings.gpu_renderer = static_cast(gpu_renderer); SwitchGPURenderer(); } } @@ -1213,21 +1204,16 @@ void SDLHostInterface::DoReset() void SDLHostInterface::DoPowerOff() { Assert(m_system); - ShutdownSystem(); + DestroySystem(); AddOSDMessage("System powered off."); } void SDLHostInterface::DoResume() { Assert(!m_system); - if (!InitializeSystem()) - return; - - if (!LoadState(RESUME_SAVESTATE_FILENAME)) + if (!CreateSystem() || !BootSystem(nullptr, RESUME_SAVESTATE_FILENAME)) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Load state failed", - "Failed to load the resume save state. Stopping emulation.", m_window); - ShutdownSystem(); + DestroySystem(); return; } @@ -1244,8 +1230,11 @@ void SDLHostInterface::DoStartDisc() return; AddOSDMessage(SmallString::FromFormat("Starting disc from '%s'...", path)); - if (!InitializeSystem(path, nullptr)) + if (!CreateSystem() || !BootSystem(path, nullptr)) + { + DestroySystem(); return; + } ResetPerformanceCounters(); ClearImGuiFocus(); @@ -1256,8 +1245,11 @@ void SDLHostInterface::DoStartBIOS() Assert(!m_system); AddOSDMessage("Starting BIOS..."); - if (!InitializeSystem(nullptr, nullptr)) + if (!CreateSystem() || !BootSystem(nullptr, nullptr)) + { + DestroySystem(); return; + } ResetPerformanceCounters(); ClearImGuiFocus(); @@ -1282,10 +1274,19 @@ void SDLHostInterface::DoChangeDisc() void SDLHostInterface::DoLoadState(u32 index) { - if (!HasSystem() && !InitializeSystem(nullptr, nullptr)) - return; + if (HasSystem()) + { + LoadState(GetSaveStateFilename(index)); + } + else + { + if (!CreateSystem() || !BootSystem(nullptr, GetSaveStateFilename(index))) + { + DestroySystem(); + return; + } + } - LoadState(GetSaveStateFilename(index)); ResetPerformanceCounters(); ClearImGuiFocus(); } @@ -1321,16 +1322,15 @@ void SDLHostInterface::DoToggleSoftwareRendering() if (!m_system) return; - if (m_settings.gpu_renderer != Settings::GPURenderer::Software) + if (m_settings.gpu_renderer != GPURenderer::Software) { - m_settings.gpu_renderer = Settings::GPURenderer::Software; + m_settings.gpu_renderer = GPURenderer::Software; AddOSDMessage("Switched to software GPU renderer."); } else { - m_settings.gpu_renderer = m_display->GetRenderAPI() == HostDisplay::RenderAPI::D3D11 ? - Settings::GPURenderer::HardwareD3D11 : - Settings::GPURenderer::HardwareOpenGL; + m_settings.gpu_renderer = m_display->GetRenderAPI() == HostDisplay::RenderAPI::D3D11 ? GPURenderer::HardwareD3D11 : + GPURenderer::HardwareOpenGL; AddOSDMessage("Switched to hardware GPU renderer."); } @@ -1431,6 +1431,6 @@ void SDLHostInterface::Run() "Saving state failed, you will not be able to resume this session.", m_window); } - ShutdownSystem(); + DestroySystem(); } -} \ No newline at end of file +} diff --git a/src/duckstation/sdl_host_interface.h b/src/duckstation/sdl_host_interface.h index d9402f596..d95c3c217 100644 --- a/src/duckstation/sdl_host_interface.h +++ b/src/duckstation/sdl_host_interface.h @@ -17,7 +17,7 @@ class DigitalController; class MemoryCard; class AudioStream; -class SDLHostInterface : public HostInterface +class SDLHostInterface final : public HostInterface { public: SDLHostInterface(); @@ -28,6 +28,7 @@ public: static TinyString GetSaveStateFilename(u32 index); + void ReportError(const char* message) override; void ReportMessage(const char* message) override; // Adds OSD messages, duration is in seconds. @@ -35,6 +36,9 @@ public: void Run(); +protected: + void ConnectControllers() override; + private: static constexpr u32 NUM_QUICK_SAVE_STATES = 10; static constexpr char RESUME_SAVESTATE_FILENAME[] = "savestate_resume.bin"; @@ -49,7 +53,7 @@ private: bool HasSystem() const { return static_cast(m_system); } #ifdef WIN32 - bool UseOpenGLRenderer() const { return m_settings.gpu_renderer == Settings::GPURenderer::HardwareOpenGL; } + bool UseOpenGLRenderer() const { return m_settings.gpu_renderer == GPURenderer::HardwareOpenGL; } #else bool UseOpenGLRenderer() const { return true; } #endif @@ -64,8 +68,6 @@ private: void SaveSettings(); - bool InitializeSystem(const char* filename = nullptr, const char* exp1_filename = nullptr); - void ConnectDevices(); void ResetPerformanceCounters(); void SwitchGPURenderer(); void UpdateFullscreen();