CPU: HLE implementation of PCDrv (host file access)

This commit is contained in:
Stenzek 2023-04-29 20:45:39 +10:00
parent 5439718ec3
commit 84e5fbe0c6
13 changed files with 430 additions and 5 deletions

View File

@ -76,6 +76,8 @@ add_library(core
negcon.h
pad.cpp
pad.h
pcdrv.cpp
pcdrv.h
pgxp.cpp
pgxp.h
playstation_mouse.cpp

View File

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

View File

@ -64,6 +64,7 @@
<ClCompile Include="negcon.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="controller.cpp" />
<ClCompile Include="pcdrv.cpp" />
<ClCompile Include="pgxp.cpp" />
<ClCompile Include="playstation_mouse.cpp" />
<ClCompile Include="psf_loader.cpp" />
@ -139,6 +140,7 @@
<ClInclude Include="negcon.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="controller.h" />
<ClInclude Include="pcdrv.h" />
<ClInclude Include="pgxp.h" />
<ClInclude Include="playstation_mouse.h" />
<ClInclude Include="psf_loader.h" />

View File

@ -59,6 +59,7 @@
<ClCompile Include="gpu_hw_d3d12.cpp" />
<ClCompile Include="host.cpp" />
<ClCompile Include="game_database.cpp" />
<ClCompile Include="pcdrv.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -125,5 +126,6 @@
<ClInclude Include="achievements.h" />
<ClInclude Include="game_database.h" />
<ClInclude Include="input_types.h" />
<ClInclude Include="pcdrv.h" />
</ItemGroup>
</Project>

View File

@ -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<u8>(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;

View File

@ -8,6 +8,7 @@
#include "types.h"
#include <array>
#include <optional>
#include <string>
#include <vector>
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);

View File

@ -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()
{

View File

@ -931,8 +931,17 @@ void CodeGenerator::GenerateExceptionExit(const CodeBlockInstruction& cbi, Excep
m_register_cache.FlushAllGuestRegisters(true, true);
m_register_cache.FlushLoadDelay(true);
if (excode == Exception::BP)
{
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32, u32)>(&CPU::RaiseBreakException), CAUSE_bits,
GetCurrentInstructionPC(), Value::FromConstantU32(cbi.instruction.bits));
}
else
{
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), CAUSE_bits,
GetCurrentInstructionPC());
}
return;
}

330
src/core/pcdrv.cpp Normal file
View File

@ -0,0 +1,330 @@
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com>
// 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<FileSystem::ManagedCFilePtr> 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<s32>(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<s32>(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<u32>(s_files.size()) || !s_files[handle])
{
Log_ErrorPrintf("Invalid file handle %d", static_cast<s32>(handle));
return nullptr;
}
return s_files[handle].get();
}
static bool CloseFileHandle(u32 handle)
{
if (handle >= static_cast<u32>(s_files.size()) || !s_files[handle])
{
Log_ErrorPrintf("Invalid file handle %d", static_cast<s32>(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<u32>(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<s32>(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<u32>(static_cast<s32>(FileSystem::FTell64(fp)));
return true;
}
default:
return false;
}
}

18
src/core/pcdrv.h Normal file
View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com>
// 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);
}

View File

@ -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.");

View File

@ -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<TinyString, NUM_CONTROLLER_AND_CARD_PORTS> GeneratePortLabels() const;
LOGLEVEL log_level = DEFAULT_LOG_LEVEL;

View File

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