System: Scaffolding for multi-system/multi-bios

This commit is contained in:
Connor McLaughlin 2019-11-16 15:27:57 +10:00
parent d6209937fb
commit 246c97ccb3
19 changed files with 576 additions and 268 deletions

View file

@ -1,4 +1,6 @@
add_library(core
bios.cpp
bios.h
bus.cpp
bus.h
bus.inl

178
src/core/bios.cpp Normal file
View file

@ -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<u32>(image.size()));
digest.Final(hash.bytes);
return hash;
}
std::optional<Image> 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<u32>(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<Hash> 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

37
src/core/bios.h Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include "types.h"
#include <optional>
#include <string_view>
#include <vector>
namespace BIOS {
enum : u32
{
BIOS_BASE = 0x1FC00000,
BIOS_SIZE = 0x80000
};
using Image = std::vector<u8>;
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<Image> LoadImageFromFile(std::string_view filename);
std::optional<Hash> 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

View file

@ -166,65 +166,20 @@ TickCount Bus::WriteWords(PhysicalMemoryAddress address, const u32* words, u32 w
return static_cast<TickCount>(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<u32>(m_bios.size()));
digest.Final(hash);
}
void Bus::SetExpansionROM(std::vector<u8> data)
{
m_exp1_rom = std::move(data);
}
bool Bus::LoadBIOS(const char* filename)
void Bus::SetBIOS(const std::vector<u8>& image)
{
std::FILE* fp = std::fopen(filename, "rb");
if (!fp)
if (image.size() != static_cast<u32>(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<u32>(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<u32>(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<TickCount, TickCount, TickCount> Bus::CalculateMemoryTiming(MEMDELAY mem_delay, COMDELAY common_delay)

View file

@ -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<u8> data);
void SetBIOS(const std::vector<u8>& image);
// changing interfaces
void SetGPU(GPU* gpu) { m_gpu = gpu; }

View file

@ -147,20 +147,12 @@ bool CDROM::DoState(StateWrapper& sw)
return !sw.HasError();
}
bool CDROM::InsertMedia(const char* filename)
void CDROM::InsertMedia(std::unique_ptr<CDImage> 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()

View file

@ -27,7 +27,7 @@ public:
bool DoState(StateWrapper& sw);
bool HasMedia() const { return static_cast<bool>(m_media); }
bool InsertMedia(const char* filename);
void InsertMedia(std::unique_ptr<CDImage> media);
void RemoveMedia();
// I/O

View file

@ -35,6 +35,7 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="bios.cpp" />
<ClCompile Include="bus.cpp" />
<ClCompile Include="cdrom.cpp" />
<ClCompile Include="cpu_core.cpp" />
@ -61,6 +62,7 @@
<ClCompile Include="timers.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="bios.h" />
<ClInclude Include="bus.h" />
<ClInclude Include="cdrom.h" />
<ClInclude Include="cpu_core.h" />

View file

@ -25,6 +25,7 @@
<ClCompile Include="gpu_sw.cpp" />
<ClCompile Include="gpu_hw_shadergen.cpp" />
<ClCompile Include="gpu_hw_d3d11.cpp" />
<ClCompile Include="bios.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -55,6 +56,7 @@
<ClInclude Include="gpu_hw_shadergen.h" />
<ClInclude Include="gpu_hw_d3d11.h" />
<ClInclude Include="host_display.h" />
<ClInclude Include="bios.h" />
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />

View file

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

View file

@ -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 <filesystem>
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<System>(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<std::vector<u8>> 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<BIOS::Image> 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" : "");

View file

@ -2,6 +2,8 @@
#include "types.h"
#include "settings.h"
#include <memory>
#include <optional>
#include <vector>
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<std::vector<u8>> 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<HostDisplay> m_display;

View file

@ -91,11 +91,39 @@ bool Settings::Save(const char* filename) const
return true;
}
static std::array<const char*, 4> s_console_region_names = {{"Auto", "NTSC-J", "NTSC-U", "PAL"}};
static std::array<const char*, 4> s_console_region_display_names = {
{"Auto-Detect", "NTSC-J (Japan)", "NTSC-U (US)", "PAL (Europe, Australia)"}};
std::optional<ConsoleRegion> Settings::ParseConsoleRegionName(const char* str)
{
int index = 0;
for (const char* name : s_console_region_names)
{
if (strcasecmp(name, str) == 0)
return static_cast<ConsoleRegion>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetConsoleRegionName(ConsoleRegion region)
{
return s_console_region_names[static_cast<int>(region)];
}
const char* Settings::GetConsoleRegionDisplayName(ConsoleRegion region)
{
return s_console_region_display_names[static_cast<int>(region)];
}
static std::array<const char*, 3> s_gpu_renderer_names = {{"D3D11", "OpenGL", "Software"}};
static std::array<const char*, 3> s_gpu_renderer_display_names = {
{"Hardware (D3D11)", "Hardware (OpenGL)", "Software"}};
std::optional<Settings::GPURenderer> Settings::ParseRendererName(const char* str)
std::optional<GPURenderer> Settings::ParseRendererName(const char* str)
{
int index = 0;
for (const char* name : s_gpu_renderer_names)

View file

@ -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<ConsoleRegion> ParseConsoleRegionName(const char* str);
static const char* GetConsoleRegionName(ConsoleRegion region);
static const char* GetConsoleRegionDisplayName(ConsoleRegion region);
static std::optional<GPURenderer> ParseRendererName(const char* str);
static const char* GetRendererName(GPURenderer renderer);
static const char* GetRendererDisplayName(GPURenderer renderer);

View file

@ -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 <imgui.h>
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<CPU::Core>();
@ -39,10 +31,32 @@ System::System(HostInterface* host_interface) : m_host_interface(host_interface)
m_timers = std::make_unique<Timers>();
m_spu = std::make_unique<SPU>();
m_mdec = std::make_unique<MDEC>();
m_region = host_interface->GetSettings().region;
}
System::~System() = default;
std::optional<ConsoleRegion> 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> System::Create(HostInterface* host_interface)
{
std::unique_ptr<System> 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<CDImage> 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<ConsoleRegion> 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> 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<u8>& 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<CDImage> image = CDImage::Open(path);
if (!image)
return false;
m_cdrom->InsertMedia(std::move(image));
return true;
}
void System::RemoveMedia()

View file

@ -2,8 +2,10 @@
#include "types.h"
#include "host_interface.h"
#include <memory>
#include <optional>
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<ConsoleRegion> 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<System> 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<u8>& 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<CPU::Core> m_cpu;
@ -91,6 +104,7 @@ private:
std::unique_ptr<Timers> m_timers;
std::unique_ptr<SPU> m_spu;
std::unique_ptr<MDEC> 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;

View file

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

View file

@ -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> 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<u32>(Settings::GPURenderer::Count); i++)
const GPURenderer current = m_settings.gpu_renderer;
for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++)
{
if (ImGui::MenuItem(Settings::GetRendererDisplayName(static_cast<Settings::GPURenderer>(i)), nullptr,
if (ImGui::MenuItem(Settings::GetRendererDisplayName(static_cast<GPURenderer>(i)), nullptr,
i == static_cast<u32>(current)))
{
m_settings.gpu_renderer = static_cast<Settings::GPURenderer>(i);
m_settings.gpu_renderer = static_cast<GPURenderer>(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<Settings::GPURenderer>(index));
*out_text = Settings::GetRendererDisplayName(static_cast<GPURenderer>(index));
return true;
},
nullptr, static_cast<int>(Settings::GPURenderer::Count)))
nullptr, static_cast<int>(GPURenderer::Count)))
{
m_settings.gpu_renderer = static_cast<Settings::GPURenderer>(gpu_renderer);
m_settings.gpu_renderer = static_cast<GPURenderer>(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();
}
}

View file

@ -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<bool>(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();