From 84e5fbe0c629131fbebe438420fdf6d433f23943 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 29 Apr 2023 20:45:39 +1000 Subject: [PATCH] CPU: HLE implementation of PCDrv (host file access) --- src/core/CMakeLists.txt | 2 + src/core/bus.cpp | 21 ++ src/core/core.vcxproj | 2 + src/core/core.vcxproj.filters | 2 + src/core/cpu_core.cpp | 20 +- src/core/cpu_core.h | 2 + src/core/cpu_core_private.h | 1 + src/core/cpu_recompiler_code_generator.cpp | 13 +- src/core/pcdrv.cpp | 330 +++++++++++++++++++++ src/core/pcdrv.h | 18 ++ src/core/settings.cpp | 14 + src/core/settings.h | 6 +- src/core/system.cpp | 4 + 13 files changed, 430 insertions(+), 5 deletions(-) create mode 100644 src/core/pcdrv.cpp create mode 100644 src/core/pcdrv.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 31f4d449f..158af6b30 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -76,6 +76,8 @@ add_library(core negcon.h pad.cpp pad.h + pcdrv.cpp + pcdrv.h pgxp.cpp pgxp.h playstation_mouse.cpp diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 13c591ef1..5460a5379 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -2019,6 +2019,27 @@ bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value) return true; } +bool SafeReadMemoryCString(VirtualMemoryAddress addr, std::string* value, u32 max_length /*= 1024*/) +{ + value->clear(); + + u8 ch; + while (SafeReadMemoryByte(addr, &ch)) + { + if (ch == 0) + return true; + + value->push_back(ch); + if (value->size() >= max_length) + return true; + + addr++; + } + + value->clear(); + return false; +} + bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value) { u32 temp = ZeroExtend32(value); diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index fc2d34c2a..a07ce3903 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -64,6 +64,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index b0951bd14..871da6173 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -59,6 +59,7 @@ + @@ -125,5 +126,6 @@ + \ No newline at end of file diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 427ba36da..62296e25e 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -11,6 +11,7 @@ #include "cpu_recompiler_thunks.h" #include "gte.h" #include "host.h" +#include "pcdrv.h" #include "pgxp.h" #include "settings.h" #include "system.h" @@ -293,6 +294,20 @@ void RaiseException(Exception excode) g_state.current_instruction_pc, GetExceptionVector()); } +void RaiseBreakException(u32 CAUSE_bits, u32 EPC, u32 instruction_bits) +{ + if (PCDrv::HandleSyscall(instruction_bits, g_state.regs)) + { + // immediately return + g_state.regs.npc = EPC + 4; + FlushPipeline(); + return; + } + + // normal exception + RaiseException(CAUSE_bits, EPC, GetExceptionVector()); +} + void SetExternalInterrupt(u8 bit) { g_state.cop0_regs.cause.Ip |= static_cast(1u << bit); @@ -1109,7 +1124,10 @@ restart_instruction: case InstructionFunct::break_: { - RaiseException(Exception::BP); + RaiseBreakException(Cop0Registers::CAUSE::MakeValueForException( + Exception::BP, g_state.current_instruction_in_branch_delay_slot, + g_state.current_instruction_was_branch_taken, g_state.current_instruction.cop.cop_n), + g_state.current_instruction_pc, g_state.current_instruction.bits); } break; diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index d2e0e07e1..4e749eb72 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -8,6 +8,7 @@ #include "types.h" #include #include +#include #include class StateWrapper; @@ -147,6 +148,7 @@ ALWAYS_INLINE bool InKernelMode() bool SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value); bool SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value); bool SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value); +bool SafeReadMemoryCString(VirtualMemoryAddress addr, std::string* value, u32 max_length = 1024); bool SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value); bool SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value); bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value); diff --git a/src/core/cpu_core_private.h b/src/core/cpu_core_private.h index eb5e4b97c..8a5caae39 100644 --- a/src/core/cpu_core_private.h +++ b/src/core/cpu_core_private.h @@ -10,6 +10,7 @@ namespace CPU { // exceptions void RaiseException(Exception excode); void RaiseException(u32 CAUSE_bits, u32 EPC); +void RaiseBreakException(u32 CAUSE_bits, u32 EPC, u32 instruction_bits); ALWAYS_INLINE bool HasPendingInterrupt() { diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index 10c69c880..02965cc2e 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -931,8 +931,17 @@ void CodeGenerator::GenerateExceptionExit(const CodeBlockInstruction& cbi, Excep m_register_cache.FlushAllGuestRegisters(true, true); m_register_cache.FlushLoadDelay(true); - EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), CAUSE_bits, - GetCurrentInstructionPC()); + if (excode == Exception::BP) + { + EmitFunctionCall(nullptr, static_cast(&CPU::RaiseBreakException), CAUSE_bits, + GetCurrentInstructionPC(), Value::FromConstantU32(cbi.instruction.bits)); + } + else + { + EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), CAUSE_bits, + GetCurrentInstructionPC()); + } + return; } diff --git a/src/core/pcdrv.cpp b/src/core/pcdrv.cpp new file mode 100644 index 000000000..4a055e9f5 --- /dev/null +++ b/src/core/pcdrv.cpp @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "pcdrv.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/path.h" +#include "common/string_util.h" +#include "cpu_core.h" +#include "settings.h" +Log_SetChannel(PCDrv); + +static constexpr u32 MAX_FILES = 100; + +static std::vector s_files; + +enum PCDrvAttribute : u32 +{ + PCDRV_ATTRIBUTE_READ_ONLY = (1 << 0), + PCDRV_ATTRIBUTE_HIDDEN = (1 << 1), + PCDRV_ATTRIBUTE_SYSTEM = (1 << 2), + PCDRV_ATTRIBUTE_DIRECTORY = (1 << 4), + PCDRV_ATTRIBUTE_ARCHIVE = (1 << 5), +}; + +static s32 GetFreeFileHandle() +{ + for (s32 i = 0; i < static_cast(s_files.size()); i++) + { + if (!s_files[i]) + return i; + } + + if (s_files.size() >= MAX_FILES) + { + Log_ErrorPrint("Too many open files."); + return -1; + } + + const s32 index = static_cast(s_files.size()); + s_files.emplace_back(nullptr, [](std::FILE*) {}); + return index; +} + +static void CloseAllFiles() +{ + if (!s_files.empty()) + Log_DevPrintf("Closing %zu open files.", s_files.size()); + + s_files.clear(); +} + +static FILE* GetFileFromHandle(u32 handle) +{ + if (handle >= static_cast(s_files.size()) || !s_files[handle]) + { + Log_ErrorPrintf("Invalid file handle %d", static_cast(handle)); + return nullptr; + } + + return s_files[handle].get(); +} + +static bool CloseFileHandle(u32 handle) +{ + if (handle >= static_cast(s_files.size()) || !s_files[handle]) + { + Log_ErrorPrintf("Invalid file handle %d", static_cast(handle)); + return false; + } + + s_files[handle].reset(); + while (!s_files.empty() && !s_files.back()) + s_files.pop_back(); + return true; +} + +static std::string ResolveHostPath(const std::string& path) +{ + // Double-check that it falls within the directory of the elf. + // Not a real sandbox, but emulators shouldn't be treated as such. Don't run untrusted code! + const std::string& root = g_settings.pcdrv_root; + std::string canonicalized_path = Path::Canonicalize(Path::Combine(root, path)); + if (canonicalized_path.length() < root.length() || // Length has to be longer (a file), + !StringUtil::StartsWith(canonicalized_path, root) || // and start with the host root, + canonicalized_path[root.length()] != FS_OSPATH_SEPARATOR_CHARACTER) // and we can't access a sibling. + { + Log_ErrorPrintf("Denying access to path outside of PCDrv directory. Requested path: '%s', " + "Resolved path: '%s', Root directory: '%s'", + path.c_str(), root.c_str(), canonicalized_path.c_str()); + canonicalized_path.clear(); + } + + return canonicalized_path; +} + +void PCDrv::Initialize() +{ + if (!g_settings.pcdrv_enable) + return; + + Log_WarningPrintf("%s PCDrv is enabled at '%s'", g_settings.pcdrv_enable_writes ? "Read/Write" : "Read-Only", + g_settings.pcdrv_root.c_str()); +} + +void PCDrv::Reset() +{ + CloseAllFiles(); +} + +void PCDrv::Shutdown() +{ + CloseAllFiles(); +} + +bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs) +{ + // Based on https://problemkaputt.de/psxspx-bios-pc-file-server.htm + +#define RETURN_ERROR() \ + regs.v0 = 0xffffffff; \ + regs.v1 = 0xffffffff; // error code + + if (!g_settings.pcdrv_enable) + return false; + + const u32 code = (instruction_bits >> 6) & 0xfffff; // 20 bits, funct = 0 + switch (code) + { + case 0x101: // PCinit + { + Log_DevPrintf("PCinit"); + CloseAllFiles(); + regs.v0 = 0; + regs.v1 = 0; + return true; + } + + case 0x102: // PCcreat + case 0x103: // PCopen + { + const bool is_open = (code == 0x103); + const char* func = (code == 0x102) ? "PCcreat" : "PCopen"; + const u32 mode = regs.a2; + std::string filename; + if (!CPU::SafeReadMemoryCString(regs.a1, &filename)) + { + Log_ErrorPrintf("%s: Invalid string", func); + return false; + } + + Log_DebugPrintf("%s: '%s' mode %u", func, filename.c_str(), mode); + if ((filename = ResolveHostPath(filename)).empty()) + { + RETURN_ERROR(); + return true; + } + + if (!is_open && !g_settings.pcdrv_enable_writes) + { + Log_ErrorPrintf("%s: Writes are not enabled", func); + RETURN_ERROR(); + return true; + } + + // Directories are unsupported for now, ignore other attributes + if (mode & PCDRV_ATTRIBUTE_DIRECTORY) + { + Log_ErrorPrintf("%s: Directories are unsupported", func); + RETURN_ERROR(); + return true; + } + + // Create empty file, truncate if exists. + const s32 handle = GetFreeFileHandle(); + if (handle < 0) + { + RETURN_ERROR(); + return true; + } + + s_files[handle] = FileSystem::OpenManagedCFile(filename.c_str(), + is_open ? (g_settings.pcdrv_enable_writes ? "r+b" : "rb") : "w+b"); + if (!s_files[handle]) + { + Log_ErrorPrintf("%s: Failed to open '%s'", func, filename.c_str()); + RETURN_ERROR(); + return true; + } + + Log_DebugPrintf("PCDrv: Opened '%s' => %d", filename.c_str(), handle); + regs.v0 = 0; + regs.v1 = static_cast(handle); + return true; + } + + case 0x104: // PCclose + { + Log_DebugPrintf("PCclose(%u)", regs.a1); + + if (!CloseFileHandle(regs.a1)) + { + RETURN_ERROR(); + return true; + } + + regs.v0 = 0; + regs.v1 = 0; + return true; + } + + case 0x105: // PCread + { + Log_DebugPrintf("PCread(%u, %u, 0x%08x)", regs.a1, regs.a2, regs.a3); + + std::FILE* fp = GetFileFromHandle(regs.a1); + if (!fp) + { + RETURN_ERROR(); + return true; + } + + const u32 count = regs.a2; + u32 dstaddr = regs.a3; + for (u32 i = 0; i < count; i++) + { + // Certainly less than optimal, but it's not like you're going to be reading megabytes of data here. + u8 val; + if (std::fread(&val, 1, 1, fp) != 1) + { + // Does not stop at EOF according to psx-spx. + if (std::ferror(fp) != 0) + { + RETURN_ERROR(); + return true; + } + + val = 0; + } + + CPU::SafeWriteMemoryByte(dstaddr, val); + dstaddr++; + } + + regs.v0 = 0; + regs.v1 = count; + return true; + } + + case 0x106: // PCwrite + { + Log_DebugPrintf("PCwrite(%u, %u, 0x%08x)", regs.a1, regs.a2, regs.a3); + + std::FILE* fp = GetFileFromHandle(regs.a1); + if (!fp) + { + RETURN_ERROR(); + return true; + } + + const u32 count = regs.a2; + u32 srcaddr = regs.a3; + u32 written = 0; + for (u32 i = 0; i < count; i++) + { + u8 val; + if (!CPU::SafeReadMemoryByte(srcaddr, &val)) + break; + + if (std::fwrite(&val, 1, 1, fp) != 1) + { + RETURN_ERROR(); + return true; + } + + srcaddr++; + written++; + } + + regs.v0 = 0; + regs.v1 = written; + return true; + } + + case 0x107: // PClseek + { + Log_DebugPrintf("PClseek(%u, %u, %u)", regs.a1, regs.a2, regs.a3); + + std::FILE* fp = GetFileFromHandle(regs.a1); + if (!fp) + { + RETURN_ERROR(); + return true; + } + + const s32 offset = static_cast(regs.a2); + const u32 mode = regs.a3; + int hmode; + switch (mode) + { + case 0: + hmode = SEEK_SET; + break; + case 1: + hmode = SEEK_CUR; + break; + case 2: + hmode = SEEK_END; + break; + default: + RETURN_ERROR(); + return true; + } + + if (FileSystem::FSeek64(fp, offset, hmode) != 0) + { + Log_ErrorPrintf("FSeek for PCDrv failed: %d %u", offset, hmode); + RETURN_ERROR(); + return true; + } + + regs.v0 = 0; + regs.v1 = static_cast(static_cast(FileSystem::FTell64(fp))); + return true; + } + + default: + return false; + } +} diff --git a/src/core/pcdrv.h b/src/core/pcdrv.h new file mode 100644 index 000000000..fd7a2eaf8 --- /dev/null +++ b/src/core/pcdrv.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once +#include "cpu_types.h" +#include "types.h" + +////////////////////////////////////////////////////////////////////////// +// HLE Implementation of PCDrv +////////////////////////////////////////////////////////////////////////// + +namespace PCDrv { +void Initialize(); +void Reset(); +void Shutdown(); + +bool HandleSyscall(u32 instruction_bits, CPU::Registers& regs); +} diff --git a/src/core/settings.cpp b/src/core/settings.cpp index f3a927969..bd9c67648 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -303,6 +303,9 @@ void Settings::Load(SettingsInterface& si) audio_dump_on_boot = si.GetBoolValue("Audio", "DumpOnBoot", false); use_old_mdec_routines = si.GetBoolValue("Hacks", "UseOldMDECRoutines", false); + pcdrv_enable = si.GetBoolValue("PCDrv", "Enabled", false); + pcdrv_enable_writes = si.GetBoolValue("PCDrv", "EnableWrites", false); + pcdrv_root = si.GetStringValue("PCDrv", "Root"); dma_max_slice_ticks = si.GetIntValue("Hacks", "DMAMaxSliceTicks", DEFAULT_DMA_MAX_SLICE_TICKS); dma_halt_ticks = si.GetIntValue("Hacks", "DMAHaltTicks", DEFAULT_DMA_HALT_TICKS); @@ -522,6 +525,10 @@ void Settings::Save(SettingsInterface& si) const si.SetIntValue("Hacks", "GPUFIFOSize", gpu_fifo_size); si.SetIntValue("Hacks", "GPUMaxRunAhead", gpu_max_run_ahead); + si.SetBoolValue("PCDrv", "Enabled", pcdrv_enable); + si.SetBoolValue("PCDrv", "EnableWrites", pcdrv_enable_writes); + si.SetStringValue("PCDrv", "Root", pcdrv_root.c_str()); + si.SetBoolValue("BIOS", "PatchTTYEnable", bios_patch_tty_enable); si.SetBoolValue("BIOS", "PatchFastBoot", bios_patch_fast_boot); @@ -610,10 +617,17 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) g_settings.cdrom_mute_cd_audio = false; g_settings.texture_replacements.enable_vram_write_replacements = false; g_settings.use_old_mdec_routines = false; + g_settings.pcdrv_enable = false; g_settings.bios_patch_fast_boot = false; g_settings.bios_patch_tty_enable = false; } + if (g_settings.pcdrv_enable && g_settings.pcdrv_root.empty()) + { + Log_WarningPrintf("Disabling PCDrv because no root directory is specified."); + g_settings.pcdrv_enable = false; + } + if (g_settings.display_integer_scaling && g_settings.display_linear_filtering) { Log_WarningPrintf("Disabling linear filter due to integer upscaling."); diff --git a/src/core/settings.h b/src/core/settings.h index 5d383f566..061e4483c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -170,6 +170,7 @@ struct Settings bool audio_dump_on_boot = false; bool use_old_mdec_routines = false; + bool pcdrv_enable = false; // timing hacks section TickCount dma_max_slice_ticks = DEFAULT_DMA_MAX_SLICE_TICKS; @@ -228,8 +229,6 @@ struct Settings } } texture_replacements; - // TODO: Controllers, memory cards, etc. - bool bios_patch_tty_enable = false; bool bios_patch_fast_boot = DEFAULT_FAST_BOOT_VALUE; bool enable_8mb_ram = false; @@ -243,6 +242,9 @@ struct Settings MultitapMode multitap_mode = DEFAULT_MULTITAP_MODE; + std::string pcdrv_root; + bool pcdrv_enable_writes = false; + std::array GeneratePortLabels() const; LOGLEVEL log_level = DEFAULT_LOG_LEVEL; diff --git a/src/core/system.cpp b/src/core/system.cpp index db2e4b8ee..e2a7141d2 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -34,6 +34,7 @@ #include "memory_card.h" #include "multitap.h" #include "pad.h" +#include "pcdrv.h" #include "pgxp.h" #include "psf_loader.h" #include "save_state_version.h" @@ -1424,6 +1425,7 @@ bool System::Initialize(bool force_software_renderer) SPU::Initialize(); MDEC::Initialize(); SIO::Initialize(); + PCDrv::Initialize(); static constexpr float WARNING_DURATION = 15.0f; @@ -1479,6 +1481,7 @@ void System::DestroySystem() g_texture_replacements.Shutdown(); + PCDrv::Shutdown(); SIO::Shutdown(); MDEC::Shutdown(); SPU::Shutdown(); @@ -1832,6 +1835,7 @@ void System::InternalReset() SPU::Reset(); MDEC::Reset(); SIO::Reset(); + PCDrv::Reset(); s_frame_number = 1; s_internal_frame_number = 0; TimingEvents::Reset();