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