mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
refactor: Refactor PS2 IOP Host Adapter and Memory Management
feat: Added PS2Vfs for virtual file system operations, including file opening, reading, writing, and path resolution. feat: Improve VIF1 data processing to handle GIF image packets more efficiently.
This commit is contained in:
@@ -10,8 +10,8 @@ set(PS2X_RUNNER_UNITY_BUILD_BATCH_SIZE 32 CACHE STRING "Unity build batch size f
|
||||
option(PS2X_ENABLE_RUNNER_PCH "Precompile the heavy runtime headers for ps2EntryRunner" ON)
|
||||
option(PS2X_ENABLE_SCCACHE "Use sccache as compiler launcher when available" ON)
|
||||
|
||||
option(PS2X_ENABLE_RUNTIME_LOGS "Enable PS2 runtime logs" OFF)
|
||||
option(PS2X_ENABLE_AGRESSIVE_LOGS "Enable very verbose/agressive PS2 runtime logs" OFF)
|
||||
option(PS2X_ENABLE_RUNTIME_LOGS "Enable PS2 runtime logs" ON)
|
||||
option(PS2X_ENABLE_AGRESSIVE_LOGS "Enable very verbose/agressive PS2 runtime logs" ON)
|
||||
option(PS2X_ENABLE_IOP_RPC_TRACE "Log unhandled IOP/SIF RPC trace suggestions" ON)
|
||||
option(PS2X_STRICT_RETURN_DIAGNOSTICS "Route generated JR $ra returns through runtime branch diagnostics" OFF)
|
||||
option(PS2X_SHOW_WINDOWS_CONSOLE "Show a console window for ps2EntryRunner on Windows release builds" ON)
|
||||
@@ -386,6 +386,8 @@ add_library(ps2_runtime STATIC
|
||||
src/lib/ps2_iop_host.cpp
|
||||
src/lib/ps2_memory.cpp
|
||||
src/lib/ps2_pad.cpp
|
||||
src/lib/ps2_rom_device.cpp
|
||||
src/lib/ps2_vfs.cpp
|
||||
src/lib/ps2_runtime.cpp
|
||||
src/lib/ps2_vif1_interpreter.cpp
|
||||
src/lib/vu/ps2_vu1_core.cpp
|
||||
|
||||
@@ -120,7 +120,6 @@
|
||||
X(SetOsdConfigParam2) \
|
||||
X(EnableCache) \
|
||||
X(DisableCache) \
|
||||
X(GetRomName) \
|
||||
X(SifLoadElfPart) \
|
||||
X(sceSifLoadElf) \
|
||||
X(sceSifLoadElfPart) \
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "runtime/ps2_vu1.h"
|
||||
#include "runtime/ps2_audio.h"
|
||||
#include "runtime/ps2_pad.h"
|
||||
#include "runtime/ps2_rom_device.h"
|
||||
#include "runtime/ps2_vfs.h"
|
||||
#include "ps2x/iop/iop_types.h"
|
||||
|
||||
namespace ps2x::iop
|
||||
@@ -293,6 +295,12 @@ public:
|
||||
[[nodiscard]] ps2x::iop::ModuleLoadResult loadIopModuleBuffer(uint32_t guestAddress, const void *arguments = nullptr, uint32_t argumentSize = 0);
|
||||
[[nodiscard]] bool stopIopModule(int32_t moduleId, int32_t *result = nullptr);
|
||||
[[nodiscard]] ps2x::iop::DebugSnapshot iopDebugSnapshot() const;
|
||||
uint32_t allocateIopMemory(uint32_t size, uint32_t alignment = 16u);
|
||||
bool freeIopMemory(uint32_t address);
|
||||
bool readIopMemory(uint32_t address, void *destination, size_t size) const;
|
||||
bool writeIopMemory(uint32_t address, const void *source, size_t size);
|
||||
bool zeroIopMemory(uint32_t address, size_t size);
|
||||
bool isIopMemoryRange(uint32_t address, size_t size) const;
|
||||
|
||||
using DebugUiCallback = void (*)(PS2Runtime &runtime, void *userData);
|
||||
void setDebugUiCallbacks(DebugUiCallback initCallback,
|
||||
@@ -444,6 +452,10 @@ public:
|
||||
inline const PS2AudioBackend &audioBackend() const { return m_audioBackend; }
|
||||
inline PSPadBackend &padBackend() { return m_padBackend; }
|
||||
inline const PSPadBackend &padBackend() const { return m_padBackend; }
|
||||
inline PS2RomDevice &romDevice() { return m_romDevice; }
|
||||
inline const PS2RomDevice &romDevice() const { return m_romDevice; }
|
||||
inline PS2Vfs &vfs() { return m_vfs; }
|
||||
inline const PS2Vfs &vfs() const { return m_vfs; }
|
||||
|
||||
private:
|
||||
struct GuestHeapBlock
|
||||
@@ -484,6 +496,8 @@ private:
|
||||
std::unique_ptr<ps2x::iop::IopSubsystem> m_iopSubsystem;
|
||||
PS2AudioBackend m_audioBackend;
|
||||
PSPadBackend m_padBackend;
|
||||
PS2RomDevice m_romDevice;
|
||||
PS2Vfs m_vfs;
|
||||
VU1Interpreter m_vu0{VU1Interpreter::Unit::VU0};
|
||||
VU1Interpreter m_vu1{VU1Interpreter::Unit::VU1};
|
||||
R5900Context m_cpuContext;
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
std::string translatePs2Path(const char *ps2Path);
|
||||
|
||||
inline std::mutex g_sys_fd_mutex;
|
||||
|
||||
namespace ps2_syscalls
|
||||
{
|
||||
#define PS2_DECLARE_SYSCALL(name) void name(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -90,6 +90,7 @@ enum class GuestInvocationKind : uint8_t
|
||||
SyscallOverride,
|
||||
ExitHandler,
|
||||
HleCall,
|
||||
SifCommand,
|
||||
};
|
||||
|
||||
struct GuestInvocation
|
||||
@@ -181,6 +182,9 @@ struct EeThreadSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t pc = 0;
|
||||
uint32_t ra = 0;
|
||||
uint32_t sp = 0;
|
||||
uint32_t contextGp = 0;
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
@@ -192,6 +196,7 @@ struct EeThreadSnapshot
|
||||
int waitId = 0;
|
||||
int suspendCount = 0;
|
||||
uint32_t wakeupCount = 0;
|
||||
uint32_t invocationDepth = 0;
|
||||
};
|
||||
|
||||
struct EeSemaphoreSnapshot
|
||||
@@ -390,6 +395,8 @@ private:
|
||||
[[nodiscard]] bool hasReadyAtOrAbovePriority(int priority) const;
|
||||
void renewTimeSlice();
|
||||
void copyMainContextToRuntime();
|
||||
void publishDebugContext(const R5900Context &context);
|
||||
void publishIdleDebugContext();
|
||||
|
||||
PS2Runtime &m_runtime;
|
||||
uint8_t *m_rdram = nullptr;
|
||||
|
||||
@@ -14,6 +14,7 @@ public:
|
||||
virtual void Reset() = 0;
|
||||
|
||||
virtual void Submit(const GSPrimitiveBatch &batch) = 0;
|
||||
virtual void LoadClut(const GSTex0Reg &tex0, const GSTexClutReg &texclut) = 0;
|
||||
|
||||
virtual void BeginTransfer(const GSTransferCommand &command) = 0;
|
||||
virtual void UploadImage(const uint8_t *data, uint32_t sizeBytes) = 0;
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
void Reset() override;
|
||||
|
||||
void Submit(const GSPrimitiveBatch &batch) override;
|
||||
void LoadClut(const GSTex0Reg &tex0, const GSTexClutReg &texclut) override;
|
||||
void BeginTransfer(const GSTransferCommand &command) override;
|
||||
void UploadImage(const uint8_t *data, uint32_t sizeBytes) override;
|
||||
|
||||
@@ -34,7 +35,9 @@ public:
|
||||
|
||||
private:
|
||||
void ResetUnlocked();
|
||||
void LoadClutUnlocked(const GSTex0Reg &tex0, const GSTexClutReg &texclut);
|
||||
uint32_t ReadVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const;
|
||||
uint32_t ReadTextureVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y);
|
||||
void WriteVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value);
|
||||
|
||||
void DrawPrimitive(const GSPrimitiveBatch &batch);
|
||||
@@ -43,7 +46,7 @@ private:
|
||||
void DrawLine(const GSPrimitiveBatch &batch);
|
||||
void WritePixel(const GSDrawState &state, int x, int y, int z, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint8_t fog);
|
||||
uint32_t SampleTexture(const GSDrawState &state, float s, float t, float q, uint16_t u, uint16_t v);
|
||||
uint32_t LookupCLUT(const GSDrawState &state, uint8_t index, uint32_t cbp, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm);
|
||||
uint32_t LookupCLUT(const GSDrawState &state, uint8_t index, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm);
|
||||
|
||||
void PerformLocalToLocalTransfer();
|
||||
void PerformLocalToHostTransfer();
|
||||
@@ -67,6 +70,10 @@ private:
|
||||
uint32_t m_vramSize = 0;
|
||||
std::array<ReadVramFunc, kPsmHandlerCount> m_readVramFuncs{};
|
||||
std::array<WriteVramFunc, kPsmHandlerCount> m_writeVramFuncs{};
|
||||
std::array<uint16_t, 512> m_clut{};
|
||||
std::array<uint32_t, 2> m_clutCbp{};
|
||||
std::vector<uint8_t> m_texturePageBuffer;
|
||||
uint32_t m_texturePageIndex = UINT32_MAX;
|
||||
|
||||
GSTransferCommand m_transfer{};
|
||||
GSTransferSnapshot m_transferState{};
|
||||
|
||||
@@ -449,6 +449,8 @@ public:
|
||||
};
|
||||
|
||||
std::array<EeTimer, 4> m_eeTimers{};
|
||||
bool tryProcessScratchpadDma(uint32_t channelBase, uint32_t chcr);
|
||||
void completeDmacChannel(uint32_t channelBase, uint32_t cause);
|
||||
void queueCompletedDmacCause(uint32_t cause);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "ps2x/iop/iop_types.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct PS2RomProfile
|
||||
{
|
||||
std::string id;
|
||||
std::string provider = "application";
|
||||
ps2x::iop::GameMatcher matcher;
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> files;
|
||||
};
|
||||
|
||||
class PS2RomDevice
|
||||
{
|
||||
public:
|
||||
PS2RomDevice();
|
||||
|
||||
static void registerProfile(PS2RomProfile profile);
|
||||
|
||||
bool configure(const ps2x::iop::GameIdentity &identity, std::string *error = nullptr);
|
||||
[[nodiscard]] bool readFile(std::string_view ps2Path, std::vector<uint8_t> &bytes) const;
|
||||
[[nodiscard]] bool fileSize(std::string_view ps2Path, uint64_t &size) const;
|
||||
[[nodiscard]] bool contains(std::string_view ps2Path) const;
|
||||
[[nodiscard]] std::string_view activeProfile() const noexcept { return m_activeProfile; }
|
||||
[[nodiscard]] std::string_view activeProvider() const noexcept { return m_activeProvider; }
|
||||
|
||||
private:
|
||||
static std::string normalizePath(std::string_view path);
|
||||
void mountBaseProfile();
|
||||
void mountFiles(const std::unordered_map<std::string, std::vector<uint8_t>> &files);
|
||||
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> m_files;
|
||||
std::string m_activeProfile;
|
||||
std::string m_activeProvider;
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "ps2x/iop/ps2_path.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class PS2RomDevice;
|
||||
|
||||
struct PS2VfsMounts
|
||||
{
|
||||
std::filesystem::path hostRoot;
|
||||
std::filesystem::path cdRoot;
|
||||
std::filesystem::path memoryCard0Root;
|
||||
};
|
||||
|
||||
struct PS2VfsStat
|
||||
{
|
||||
bool directory = false;
|
||||
bool readOnly = false;
|
||||
uint64_t size = 0u;
|
||||
std::time_t created = 0;
|
||||
std::time_t accessed = 0;
|
||||
std::time_t modified = 0;
|
||||
};
|
||||
|
||||
struct PS2VfsDescriptorInfo
|
||||
{
|
||||
int32_t descriptor = -1;
|
||||
std::string device;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
class IPS2OpenFile
|
||||
{
|
||||
public:
|
||||
virtual ~IPS2OpenFile() = default;
|
||||
|
||||
[[nodiscard]] virtual int64_t read(void *destination, size_t size) = 0;
|
||||
[[nodiscard]] virtual int64_t write(const void *source, size_t size) = 0;
|
||||
[[nodiscard]] virtual int64_t seek(int64_t offset, int whence) = 0;
|
||||
};
|
||||
|
||||
class PS2Vfs
|
||||
{
|
||||
public:
|
||||
PS2Vfs() = default;
|
||||
~PS2Vfs();
|
||||
|
||||
PS2Vfs(const PS2Vfs &) = delete;
|
||||
PS2Vfs &operator=(const PS2Vfs &) = delete;
|
||||
|
||||
[[nodiscard]] int32_t open(std::string_view path, uint32_t flags, const PS2VfsMounts &mounts, const PS2RomDevice &rom);
|
||||
[[nodiscard]] int32_t close(int32_t descriptor);
|
||||
[[nodiscard]] int64_t read(int32_t descriptor, void *destination, size_t size);
|
||||
[[nodiscard]] int64_t write(int32_t descriptor, const void *source, size_t size);
|
||||
[[nodiscard]] int64_t seek(int32_t descriptor, int64_t offset, int whence);
|
||||
|
||||
[[nodiscard]] bool stat(std::string_view path, const PS2VfsMounts &mounts, const PS2RomDevice &rom, PS2VfsStat &result) const;
|
||||
[[nodiscard]] bool resolveHostPath(std::string_view path, const PS2VfsMounts &mounts, std::filesystem::path &result) const;
|
||||
[[nodiscard]] std::vector<PS2VfsDescriptorInfo> descriptors() const;
|
||||
|
||||
private:
|
||||
struct OpenDescriptor
|
||||
{
|
||||
std::unique_ptr<IPS2OpenFile> file;
|
||||
std::string device;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::unordered_map<int32_t, OpenDescriptor> m_descriptors;
|
||||
int32_t m_nextDescriptor = 3;
|
||||
};
|
||||
@@ -170,6 +170,8 @@ void EeScheduler::run()
|
||||
GuestThread *next = selectReady();
|
||||
if (!next && m_pendingInvocations.empty())
|
||||
{
|
||||
copyMainContextToRuntime();
|
||||
publishIdleDebugContext();
|
||||
publishSnapshot();
|
||||
waitForEvent();
|
||||
continue;
|
||||
@@ -224,10 +226,7 @@ void EeScheduler::run()
|
||||
--m_debugPublishCountdown;
|
||||
}
|
||||
|
||||
m_runtime.m_debugPc.store(context.pc, std::memory_order_relaxed);
|
||||
m_runtime.m_debugRa.store(getRegU32(&context, 31), std::memory_order_relaxed);
|
||||
m_runtime.m_debugSp.store(getRegU32(&context, 29), std::memory_order_relaxed);
|
||||
m_runtime.m_debugGp.store(getRegU32(&context, 28), std::memory_order_relaxed);
|
||||
publishDebugContext(context);
|
||||
|
||||
if (context.pc == 0u)
|
||||
{
|
||||
@@ -249,10 +248,13 @@ void EeScheduler::run()
|
||||
}
|
||||
makeDormant(*running);
|
||||
m_currentThreadId = 0;
|
||||
copyMainContextToRuntime();
|
||||
publishIdleDebugContext();
|
||||
publishSnapshot();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_pendingInvocations.empty())
|
||||
if (!m_pendingInvocations.empty() && running->invocations.empty())
|
||||
{
|
||||
GuestInvocation invocation = std::move(m_pendingInvocations.front());
|
||||
m_pendingInvocations.pop_front();
|
||||
@@ -1497,7 +1499,11 @@ void EeScheduler::publishSnapshot()
|
||||
}
|
||||
EeThreadSnapshot snapshot{};
|
||||
snapshot.id = id;
|
||||
snapshot.pc = item.activeContext().pc;
|
||||
const R5900Context &context = item.activeContext();
|
||||
snapshot.pc = context.pc;
|
||||
snapshot.ra = getRegU32(&context, 31);
|
||||
snapshot.sp = getRegU32(&context, 29);
|
||||
snapshot.contextGp = getRegU32(&context, 28);
|
||||
snapshot.entry = item.entry;
|
||||
snapshot.stack = item.stack;
|
||||
snapshot.stackSize = item.stackSize;
|
||||
@@ -1509,6 +1515,7 @@ void EeScheduler::publishSnapshot()
|
||||
snapshot.waitId = waitObjectId(item.wait);
|
||||
snapshot.suspendCount = item.suspendCount;
|
||||
snapshot.wakeupCount = item.wakeupCount;
|
||||
snapshot.invocationDepth = static_cast<uint32_t>(item.invocations.size());
|
||||
next.threads.push_back(snapshot);
|
||||
}
|
||||
std::sort(next.threads.begin(), next.threads.end(), [](const auto &left, const auto &right)
|
||||
@@ -2105,3 +2112,35 @@ void EeScheduler::copyMainContextToRuntime()
|
||||
m_runtime.m_cpuContext = main->context;
|
||||
}
|
||||
}
|
||||
|
||||
void EeScheduler::publishDebugContext(const R5900Context &context)
|
||||
{
|
||||
m_runtime.m_debugPc.store(context.pc, std::memory_order_relaxed);
|
||||
m_runtime.m_debugRa.store(getRegU32(&context, 31), std::memory_order_relaxed);
|
||||
m_runtime.m_debugSp.store(getRegU32(&context, 29), std::memory_order_relaxed);
|
||||
m_runtime.m_debugGp.store(getRegU32(&context, 28), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void EeScheduler::publishIdleDebugContext()
|
||||
{
|
||||
// Temporary IRQ/RPC/alarm invocations deliberately return to PC=0. Once
|
||||
// the scheduler is idle, show a real EE thread context instead of leaving
|
||||
// the debugger pinned to that completed dispatcher frame.
|
||||
const GuestThread *selected = thread(kMainThreadId);
|
||||
if (!selected)
|
||||
{
|
||||
for (const auto &[id, candidate] : m_threads)
|
||||
{
|
||||
if (id > 0 && candidate.status != EeThreadStatus::Dormant)
|
||||
{
|
||||
selected = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selected)
|
||||
{
|
||||
publishDebugContext(selected->activeContext());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,6 @@ namespace
|
||||
uint32_t g_cdStreamingEndLbn = 0xFFFFFFFFu;
|
||||
bool g_cdInitialized = false;
|
||||
|
||||
constexpr uint32_t kIopHeapBase = 0x04000000;
|
||||
constexpr uint32_t kIopHeapLimit = 0x04500000;
|
||||
constexpr uint32_t kIopHeapAlign = 64;
|
||||
uint32_t g_iopHeapNext = kIopHeapBase;
|
||||
|
||||
std::string toLowerAscii(std::string value)
|
||||
{
|
||||
std::transform(value.begin(), value.end(), value.begin(),
|
||||
@@ -1360,7 +1355,11 @@ namespace
|
||||
uint32_t madr = 0;
|
||||
uint32_t qwc = 0;
|
||||
uint32_t tadr = payloadPhys;
|
||||
uint32_t chcr = 0x00000181u; // DIR=1, TIE=1, STR=1 (normal mode).
|
||||
PS2Memory &mem = runtime->memory();
|
||||
|
||||
const uint32_t configuredChcr = mem.readIORegister(channelBase + 0x00u);
|
||||
const uint32_t transferTagEnable = configuredChcr & 0x00000040u;
|
||||
uint32_t chcr = 0x00000181u | transferTagEnable; // DIR=1, TIE=1, STR=1 (normal mode).
|
||||
|
||||
if (preferNormalCount)
|
||||
{
|
||||
@@ -1369,10 +1368,9 @@ namespace
|
||||
}
|
||||
else
|
||||
{
|
||||
chcr = 0x00000185u; // MODE=1 chain, DIR=1, TIE=1, STR=1.
|
||||
chcr = 0x00000185u | transferTagEnable; // MODE=1 chain, DIR=1, TIE=1, STR=1.
|
||||
}
|
||||
|
||||
PS2Memory &mem = runtime->memory();
|
||||
mem.writeIORegister(channelBase + 0x20u, qwc & 0xFFFFu);
|
||||
mem.writeIORegister(channelBase + 0x10u, madr);
|
||||
mem.writeIORegister(channelBase + 0x30u, tadr);
|
||||
@@ -1402,10 +1400,10 @@ namespace
|
||||
if (g_dmaStubLogCount < kMaxDmaStubLogs)
|
||||
{
|
||||
RUNTIME_LOG("[sceDmaSend] ch=0x" << std::hex << channelBase
|
||||
<< " madr=0x" << madr
|
||||
<< " qwc=0x" << qwc
|
||||
<< " tadr=0x" << tadr
|
||||
<< " chcr=0x" << chcr << std::dec << std::endl);
|
||||
<< " madr=0x" << madr
|
||||
<< " qwc=0x" << qwc
|
||||
<< " tadr=0x" << tadr
|
||||
<< " chcr=0x" << chcr << std::dec << std::endl);
|
||||
|
||||
if (!preferNormalCount && (channelBase == 0x10009000u || channelBase == 0x1000A000u))
|
||||
{
|
||||
@@ -1418,13 +1416,13 @@ namespace
|
||||
std::memcpy(&w2, tagPtr + 8u, sizeof(w2));
|
||||
std::memcpy(&w3, tagPtr + 12u, sizeof(w3));
|
||||
RUNTIME_LOG("[sceDmaSend:head] ch=0x" << std::hex << channelBase
|
||||
<< " tagQwc=0x" << static_cast<uint32_t>(tagLo & 0xFFFFu)
|
||||
<< " id=0x" << static_cast<uint32_t>((tagLo >> 28u) & 0x7u)
|
||||
<< " irq=0x" << static_cast<uint32_t>((tagLo >> 31u) & 0x1u)
|
||||
<< " addr=0x" << static_cast<uint32_t>((tagLo >> 32u) & 0x7FFFFFFFu)
|
||||
<< " w2=0x" << w2
|
||||
<< " w3=0x" << w3
|
||||
<< std::dec << std::endl);
|
||||
<< " tagQwc=0x" << static_cast<uint32_t>(tagLo & 0xFFFFu)
|
||||
<< " id=0x" << static_cast<uint32_t>((tagLo >> 28u) & 0x7u)
|
||||
<< " irq=0x" << static_cast<uint32_t>((tagLo >> 31u) & 0x1u)
|
||||
<< " addr=0x" << static_cast<uint32_t>((tagLo >> 32u) & 0x7FFFFFFFu)
|
||||
<< " w2=0x" << w2
|
||||
<< " w3=0x" << w3
|
||||
<< std::dec << std::endl);
|
||||
}
|
||||
}
|
||||
++g_dmaStubLogCount;
|
||||
@@ -1838,9 +1836,9 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readGsDBuff(uint8_t* rdram, uint32_t addr, GsDBuffMem& out)
|
||||
static bool readGsDBuff(uint8_t *rdram, uint32_t addr, GsDBuffMem &out)
|
||||
{
|
||||
const uint8_t* ptr = getConstMemPtr(rdram, addr);
|
||||
const uint8_t *ptr = getConstMemPtr(rdram, addr);
|
||||
if (!ptr)
|
||||
return false;
|
||||
std::memcpy(&out, ptr, sizeof(out));
|
||||
@@ -1856,9 +1854,9 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool writeGsDBuff(uint8_t* rdram, uint32_t addr, const GsDBuffMem& db)
|
||||
static bool writeGsDBuff(uint8_t *rdram, uint32_t addr, const GsDBuffMem &db)
|
||||
{
|
||||
uint8_t* ptr = getMemPtr(rdram, addr);
|
||||
uint8_t *ptr = getMemPtr(rdram, addr);
|
||||
if (!ptr)
|
||||
return false;
|
||||
std::memcpy(ptr, &db, sizeof(db));
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
#include "../Syscalls/RPC.h"
|
||||
#include "../../ps2_iop_transport.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2_stubs
|
||||
@@ -28,15 +29,22 @@ namespace ps2_stubs
|
||||
const uint32_t size = readStackU32(rdram, ctx, 20);
|
||||
if (size != 0u && srcAddr != 0u && dstAddr != 0u)
|
||||
{
|
||||
std::vector<uint8_t> payload(size);
|
||||
bool valid = runtime != nullptr;
|
||||
for (uint32_t i = 0; i < size; ++i)
|
||||
{
|
||||
const uint8_t *src = getConstMemPtr(rdram, srcAddr + i);
|
||||
uint8_t *dst = getMemPtr(rdram, dstAddr + i);
|
||||
if (!src || !dst)
|
||||
if (!src)
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
*dst = *src;
|
||||
payload[i] = *src;
|
||||
}
|
||||
if (!valid || !runtime->writeIopMemory(dstAddr, payload.data(), payload.size()))
|
||||
{
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +65,15 @@ namespace ps2_stubs
|
||||
std::mutex g_sifDmaTransferMutex;
|
||||
uint32_t g_nextSifDmaTransferId = 1u;
|
||||
std::mutex g_sifCmdStateMutex;
|
||||
std::mutex g_sifHeapMutex;
|
||||
std::unordered_map<uint32_t, uint32_t> g_sifRegs;
|
||||
std::unordered_map<uint32_t, uint32_t> g_sifSregs;
|
||||
std::unordered_map<uint32_t, uint32_t> g_sifCmdHandlers;
|
||||
std::map<uint32_t, uint32_t> g_sifHeapAllocations;
|
||||
std::array<uint8_t, kIopHeapLimit - kIopHeapBase> g_sifHeapStorage{};
|
||||
struct SifCmdHandler
|
||||
{
|
||||
uint32_t function = 0u;
|
||||
uint32_t argument = 0u;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, SifCmdHandler> g_sifCmdHandlers;
|
||||
uint32_t g_sifCmdBuffer = 0u;
|
||||
uint32_t g_sifSysCmdBuffer = 0u;
|
||||
bool g_sifCmdInitialized = false;
|
||||
@@ -127,92 +138,6 @@ namespace ps2_stubs
|
||||
return id;
|
||||
}
|
||||
|
||||
uint32_t alignIopHeapSize(uint32_t size)
|
||||
{
|
||||
return (size + (kIopHeapAlign - 1u)) & ~(kIopHeapAlign - 1u);
|
||||
}
|
||||
|
||||
uint32_t allocateSifHeapBlock(uint32_t requestSize)
|
||||
{
|
||||
const uint32_t alignedSize = alignIopHeapSize(requestSize);
|
||||
if (alignedSize == 0u)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
uint32_t candidate = kIopHeapBase;
|
||||
for (const auto &[addr, size] : g_sifHeapAllocations)
|
||||
{
|
||||
if (candidate + alignedSize <= addr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t blockEnd = alignIopHeapSize(addr + size);
|
||||
if (blockEnd > candidate)
|
||||
{
|
||||
candidate = blockEnd;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidate < kIopHeapBase || candidate + alignedSize > kIopHeapLimit)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
g_sifHeapAllocations[candidate] = alignedSize;
|
||||
std::fill_n(g_sifHeapStorage.data() + (candidate - kIopHeapBase),
|
||||
alignedSize,
|
||||
uint8_t{0});
|
||||
g_iopHeapNext = candidate + alignedSize;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
bool freeSifHeapBlock(uint32_t addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
const auto it = g_sifHeapAllocations.find(addr);
|
||||
if (it == g_sifHeapAllocations.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
g_sifHeapAllocations.erase(it);
|
||||
if (g_sifHeapAllocations.empty())
|
||||
{
|
||||
g_iopHeapNext = kIopHeapBase;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void resetSifHeapState()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
g_sifHeapAllocations.clear();
|
||||
g_sifHeapStorage.fill(0u);
|
||||
g_iopHeapNext = kIopHeapBase;
|
||||
}
|
||||
|
||||
bool isAllocatedSifHeapRangeLocked(uint32_t address, size_t size)
|
||||
{
|
||||
if (address < kIopHeapBase || address >= kIopHeapLimit || size > static_cast<size_t>(kIopHeapLimit - address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = g_sifHeapAllocations.upper_bound(address);
|
||||
if (it == g_sifHeapAllocations.begin())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
--it;
|
||||
|
||||
const uint64_t allocationEnd = static_cast<uint64_t>(it->first) + it->second;
|
||||
const uint64_t rangeEnd = static_cast<uint64_t>(address) + size;
|
||||
return address >= it->first && rangeEnd <= allocationEnd;
|
||||
}
|
||||
|
||||
bool isCopyableGuestAddress(uint32_t addr)
|
||||
{
|
||||
if (Ps2AddressInRange(addr, PS2_SCRATCHPAD_BASE, PS2_SCRATCHPAD_SIZE))
|
||||
@@ -238,13 +163,9 @@ namespace ps2_stubs
|
||||
return false;
|
||||
}
|
||||
|
||||
bool canCopyAddressRange(const uint8_t *rdram, uint32_t address, uint32_t sizeBytes)
|
||||
bool canAccessEeRange(const uint8_t *rdram, uint32_t address, uint32_t sizeBytes)
|
||||
{
|
||||
if (isSifIopHeapRange(address, sizeBytes))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (isSifIopHeapAddress(address) || !rdram)
|
||||
if (!rdram)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -259,7 +180,7 @@ namespace ps2_stubs
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
const uint32_t byteAddress = address + i;
|
||||
if (!isCopyableGuestAddress(byteAddress) ||getConstMemPtr(rdram, byteAddress) == nullptr)
|
||||
if (!isCopyableGuestAddress(byteAddress) || getConstMemPtr(rdram, byteAddress) == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -267,200 +188,129 @@ namespace ps2_stubs
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canCopyGuestByteRange(const uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes)
|
||||
bool readEeRange(const uint8_t *rdram, uint32_t address, void *destination, uint32_t sizeBytes)
|
||||
{
|
||||
return canCopyAddressRange(rdram, srcAddr, sizeBytes) && canCopyAddressRange(rdram, dstAddr, sizeBytes);
|
||||
}
|
||||
|
||||
bool copyGuestByteRange(uint8_t *rdram, uint32_t dstAddr, uint32_t srcAddr, uint32_t sizeBytes)
|
||||
{
|
||||
if (!canCopyGuestByteRange(rdram, dstAddr, srcAddr, sizeBytes))
|
||||
{
|
||||
if ((!destination && sizeBytes != 0u) || !canAccessEeRange(rdram, address, sizeBytes))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sizeBytes == 0u)
|
||||
auto *bytes = static_cast<uint8_t *>(destination);
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool sourceIsIop = isSifIopHeapRange(srcAddr, sizeBytes);
|
||||
const bool destinationIsIop = isSifIopHeapRange(dstAddr, sizeBytes);
|
||||
if (sourceIsIop || destinationIsIop)
|
||||
{
|
||||
std::vector<uint8_t> payload(sizeBytes);
|
||||
if (sourceIsIop)
|
||||
{
|
||||
if (!readSifIopHeap(srcAddr, payload.data(), payload.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
const uint8_t *src = getConstMemPtr(rdram, srcAddr + i);
|
||||
if (!src)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
payload[i] = *src;
|
||||
}
|
||||
}
|
||||
|
||||
if (destinationIsIop)
|
||||
{
|
||||
return writeSifIopHeap(dstAddr, payload.data(), payload.size());
|
||||
}
|
||||
|
||||
ps2TraceGuestRangeWrite(rdram, dstAddr, sizeBytes, "sifCopyGuestByteRange", nullptr);
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
uint8_t *dst = getMemPtr(rdram, dstAddr + i);
|
||||
if (!dst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*dst = payload[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ps2TraceGuestRangeWrite(rdram, dstAddr, sizeBytes, "sifCopyGuestByteRange", nullptr);
|
||||
|
||||
const uint64_t srcBegin = srcAddr;
|
||||
const uint64_t srcEnd = srcBegin + static_cast<uint64_t>(sizeBytes);
|
||||
const uint64_t dstBegin = dstAddr;
|
||||
const bool copyBackward = (dstBegin > srcBegin) && (dstBegin < srcEnd);
|
||||
|
||||
if (copyBackward)
|
||||
{
|
||||
for (uint32_t i = sizeBytes; i > 0u; --i)
|
||||
{
|
||||
const uint32_t index = i - 1u;
|
||||
const uint8_t *src = getConstMemPtr(rdram, srcAddr + index);
|
||||
uint8_t *dst = getMemPtr(rdram, dstAddr + index);
|
||||
if (!src || !dst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*dst = *src;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < sizeBytes; ++i)
|
||||
{
|
||||
const uint8_t *src = getConstMemPtr(rdram, srcAddr + i);
|
||||
uint8_t *dst = getMemPtr(rdram, dstAddr + i);
|
||||
if (!src || !dst)
|
||||
{
|
||||
const uint8_t *source = getConstMemPtr(rdram, address + i);
|
||||
if (!source)
|
||||
return false;
|
||||
}
|
||||
*dst = *src;
|
||||
bytes[i] = *source;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSifIopHeapAddress(uint32_t address)
|
||||
{
|
||||
return address >= kIopHeapBase && address < kIopHeapLimit;
|
||||
}
|
||||
|
||||
bool isSifIopHeapRange(uint32_t address, size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
return isAllocatedSifHeapRangeLocked(address, size);
|
||||
}
|
||||
|
||||
bool readSifIopHeap(uint32_t address, void *destination, size_t size)
|
||||
{
|
||||
if (!destination && size != 0u)
|
||||
bool writeEeRange(uint8_t *rdram, uint32_t address, const void *source, uint32_t sizeBytes)
|
||||
{
|
||||
return false;
|
||||
if ((!source && sizeBytes != 0u) || !canAccessEeRange(rdram, address, sizeBytes))
|
||||
return false;
|
||||
ps2TraceGuestRangeWrite(rdram, address, sizeBytes, "SIF IOP-to-EE DMA", nullptr);
|
||||
const auto *bytes = static_cast<const uint8_t *>(source);
|
||||
for (uint32_t i = 0u; i < sizeBytes; ++i)
|
||||
{
|
||||
uint8_t *destination = getMemPtr(rdram, address + i);
|
||||
if (!destination)
|
||||
return false;
|
||||
*destination = bytes[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
if (!isAllocatedSifHeapRangeLocked(address, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (size != 0u)
|
||||
{
|
||||
std::memcpy(destination,
|
||||
g_sifHeapStorage.data() + (address - kIopHeapBase),
|
||||
size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeSifIopHeap(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
if (!source && size != 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
if (!isAllocatedSifHeapRangeLocked(address, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (size != 0u)
|
||||
{
|
||||
std::memcpy(g_sifHeapStorage.data() + (address - kIopHeapBase),
|
||||
source,
|
||||
size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool zeroSifIopHeap(uint32_t address, size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifHeapMutex);
|
||||
if (!isAllocatedSifHeapRangeLocked(address, size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (size != 0u)
|
||||
{
|
||||
std::memset(g_sifHeapStorage.data() + (address - kIopHeapBase), 0, size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void resetSifState()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifCmdStateMutex);
|
||||
seedDefaultSifRegsLocked();
|
||||
resetSifHeapState();
|
||||
}
|
||||
|
||||
bool dispatchSifCommand(uint8_t *rdram,
|
||||
PS2Runtime *runtime,
|
||||
uint32_t commandId,
|
||||
const void *packet,
|
||||
size_t packetSize) noexcept
|
||||
{
|
||||
if (!rdram || !runtime || !packet || packetSize < 16u || packetSize > 112u)
|
||||
return false;
|
||||
|
||||
SifCmdHandler registered{};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sifCmdStateMutex);
|
||||
const auto handler = g_sifCmdHandlers.find(commandId);
|
||||
if (handler == g_sifCmdHandlers.end() || handler->second.function == 0u)
|
||||
return false;
|
||||
registered = handler->second;
|
||||
}
|
||||
|
||||
if (!runtime->hasFunction(registered.function))
|
||||
return false;
|
||||
|
||||
const uint32_t packetAddress = runtime->guestMalloc(static_cast<uint32_t>(packetSize), 16u);
|
||||
if (packetAddress == 0u)
|
||||
return false;
|
||||
|
||||
uint8_t *const first = getMemPtr(rdram, packetAddress);
|
||||
uint8_t *const last = getMemPtr(rdram, packetAddress + static_cast<uint32_t>(packetSize - 1u));
|
||||
if (!first || !last || last < first || static_cast<size_t>(last - first) != packetSize - 1u)
|
||||
{
|
||||
runtime->guestFree(packetAddress);
|
||||
return false;
|
||||
}
|
||||
|
||||
ps2TraceGuestRangeWrite(rdram, packetAddress, static_cast<uint32_t>(packetSize), "SIF command packet", nullptr);
|
||||
std::memcpy(first, packet, packetSize);
|
||||
|
||||
try
|
||||
{
|
||||
GuestInvocation invocation{};
|
||||
invocation.kind = GuestInvocationKind::SifCommand;
|
||||
invocation.tag = commandId;
|
||||
invocation.context = runtime->cpu();
|
||||
invocation.context.pc = registered.function;
|
||||
SET_GPR_U32(&invocation.context, 4, packetAddress);
|
||||
SET_GPR_U32(&invocation.context, 5, registered.argument);
|
||||
SET_GPR_U32(&invocation.context, 6, 0u);
|
||||
SET_GPR_U32(&invocation.context, 7, 0u);
|
||||
SET_GPR_U32(&invocation.context, 29, 0u);
|
||||
SET_GPR_U32(&invocation.context, 31, 0u);
|
||||
invocation.onComplete = [runtime, packetAddress](const R5900Context &, R5900Context &)
|
||||
{
|
||||
runtime->guestFree(packetAddress);
|
||||
};
|
||||
runtime->eeScheduler().queueInvocation(std::move(invocation));
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
runtime->guestFree(packetAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void sceSifAddCmdHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cid = getRegU32(ctx, 4);
|
||||
const uint32_t handler = getRegU32(ctx, 5);
|
||||
const uint32_t argument = getRegU32(ctx, 6);
|
||||
std::lock_guard<std::mutex> lock(g_sifCmdStateMutex);
|
||||
g_sifCmdHandlers[cid] = handler;
|
||||
g_sifCmdHandlers[cid] = SifCmdHandler{handler, argument};
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void sceSifAllocIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)rdram;
|
||||
(void)runtime;
|
||||
|
||||
const uint32_t reqSize = getRegU32(ctx, 4);
|
||||
setReturnU32(ctx, allocateSifHeapBlock(reqSize));
|
||||
setReturnU32(ctx, runtime ? runtime->allocateIopMemory(reqSize, 64u) : 0u);
|
||||
}
|
||||
|
||||
void sceSifAllocSysMemory(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)rdram;
|
||||
(void)runtime;
|
||||
|
||||
const uint32_t size = getRegU32(ctx, 5);
|
||||
setReturnU32(ctx, allocateSifHeapBlock(size));
|
||||
setReturnU32(ctx, runtime ? runtime->allocateIopMemory(size, 64u) : 0u);
|
||||
}
|
||||
|
||||
void sceSifBindRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -503,19 +353,15 @@ namespace ps2_stubs
|
||||
void sceSifFreeIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)rdram;
|
||||
(void)runtime;
|
||||
|
||||
const uint32_t addr = getRegU32(ctx, 4);
|
||||
setReturnS32(ctx, freeSifHeapBlock(addr) ? 0 : -1);
|
||||
setReturnS32(ctx, runtime && runtime->freeIopMemory(addr) ? 0 : -1);
|
||||
}
|
||||
|
||||
void sceSifFreeSysMemory(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)rdram;
|
||||
(void)runtime;
|
||||
|
||||
const uint32_t addr = getRegU32(ctx, 4);
|
||||
setReturnS32(ctx, freeSifHeapBlock(addr) ? 0 : -1);
|
||||
setReturnS32(ctx, runtime && runtime->freeIopMemory(addr) ? 0 : -1);
|
||||
}
|
||||
|
||||
void sceSifGetDataTable(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -564,15 +410,19 @@ namespace ps2_stubs
|
||||
if (runtime)
|
||||
{
|
||||
PS2IopTransport::notifyTransfer(runtime, rdram, {
|
||||
ps2x::iop::SifTransferKind::GetOtherData,
|
||||
ps2x::iop::SifTransferPhase::BeforeCopy,
|
||||
srcAddr,
|
||||
dstAddr,
|
||||
size,
|
||||
});
|
||||
ps2x::iop::SifTransferKind::GetOtherData,
|
||||
ps2x::iop::SifTransferPhase::BeforeCopy,
|
||||
srcAddr,
|
||||
dstAddr,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
if (!copyGuestByteRange(rdram, dstAddr, srcAddr, size))
|
||||
std::vector<uint8_t> payload(size);
|
||||
if (!runtime || !runtime->isIopMemoryRange(srcAddr, size) ||
|
||||
!canAccessEeRange(rdram, dstAddr, size) ||
|
||||
!runtime->readIopMemory(srcAddr, payload.data(), payload.size()) ||
|
||||
!writeEeRange(rdram, dstAddr, payload.data(), size))
|
||||
{
|
||||
static uint32_t warnCount = 0;
|
||||
if (warnCount < 32u)
|
||||
@@ -600,12 +450,12 @@ namespace ps2_stubs
|
||||
if (runtime)
|
||||
{
|
||||
PS2IopTransport::notifyTransfer(runtime, rdram, {
|
||||
ps2x::iop::SifTransferKind::GetOtherData,
|
||||
ps2x::iop::SifTransferPhase::AfterCopy,
|
||||
srcAddr,
|
||||
dstAddr,
|
||||
size,
|
||||
});
|
||||
ps2x::iop::SifTransferKind::GetOtherData,
|
||||
ps2x::iop::SifTransferPhase::AfterCopy,
|
||||
srcAddr,
|
||||
dstAddr,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
setReturnS32(ctx, 0);
|
||||
@@ -668,7 +518,7 @@ namespace ps2_stubs
|
||||
|
||||
void sceSifInitIopHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
resetSifHeapState();
|
||||
// The physical IOP allocator is initialized by IopSubsystem::reset().
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
@@ -840,7 +690,7 @@ namespace ps2_stubs
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (!canCopyGuestByteRange(rdram, xfer.dest, xfer.src, sizeBytes))
|
||||
if (!runtime || !canAccessEeRange(rdram, xfer.src, sizeBytes) || !runtime->isIopMemoryRange(xfer.dest, sizeBytes))
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
@@ -857,14 +707,16 @@ namespace ps2_stubs
|
||||
if (runtime)
|
||||
{
|
||||
PS2IopTransport::notifyTransfer(runtime, rdram, {
|
||||
ps2x::iop::SifTransferKind::SetDma,
|
||||
ps2x::iop::SifTransferPhase::BeforeCopy,
|
||||
xfer.src,
|
||||
xfer.dest,
|
||||
static_cast<uint32_t>(xfer.size),
|
||||
});
|
||||
ps2x::iop::SifTransferKind::SetDma,
|
||||
ps2x::iop::SifTransferPhase::BeforeCopy,
|
||||
xfer.src,
|
||||
xfer.dest,
|
||||
static_cast<uint32_t>(xfer.size),
|
||||
});
|
||||
}
|
||||
if (!copyGuestByteRange(rdram, xfer.dest, xfer.src, static_cast<uint32_t>(xfer.size)))
|
||||
const uint32_t sizeBytes = static_cast<uint32_t>(xfer.size);
|
||||
std::vector<uint8_t> payload(sizeBytes);
|
||||
if (!readEeRange(rdram, xfer.src, payload.data(), sizeBytes) || !runtime->writeIopMemory(xfer.dest, payload.data(), payload.size()))
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
@@ -872,12 +724,12 @@ namespace ps2_stubs
|
||||
if (runtime)
|
||||
{
|
||||
PS2IopTransport::notifyTransfer(runtime, rdram, {
|
||||
ps2x::iop::SifTransferKind::SetDma,
|
||||
ps2x::iop::SifTransferPhase::AfterCopy,
|
||||
xfer.src,
|
||||
xfer.dest,
|
||||
static_cast<uint32_t>(xfer.size),
|
||||
});
|
||||
ps2x::iop::SifTransferKind::SetDma,
|
||||
ps2x::iop::SifTransferPhase::AfterCopy,
|
||||
xfer.src,
|
||||
xfer.dest,
|
||||
static_cast<uint32_t>(xfer.size),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,9 @@
|
||||
|
||||
#include "ps2_stubs.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
bool isSifIopHeapAddress(uint32_t address);
|
||||
bool isSifIopHeapRange(uint32_t address, size_t size);
|
||||
bool readSifIopHeap(uint32_t address, void *destination, size_t size);
|
||||
bool writeSifIopHeap(uint32_t address, const void *source, size_t size);
|
||||
bool zeroSifIopHeap(uint32_t address, size_t size);
|
||||
|
||||
bool dispatchSifCommand(uint8_t *rdram, PS2Runtime *runtime, uint32_t commandId, const void *packet, size_t packetSize) noexcept;
|
||||
void sceSifCmdIntrHdlr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifSendCmd(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "runtime/ee_scheduler.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "ps2x/iop/ps2_path.h"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
@@ -253,6 +253,9 @@ namespace ps2_syscalls
|
||||
case 0x64:
|
||||
FlushCache(rdram, ctx, runtime);
|
||||
return true;
|
||||
case static_cast<uint32_t>(-0x68):
|
||||
iFlushCache(rdram, ctx, runtime);
|
||||
return true;
|
||||
case 0x6E:
|
||||
SetOsdConfigParam2(rdram, ctx, runtime);
|
||||
return true;
|
||||
|
||||
@@ -3,32 +3,10 @@
|
||||
|
||||
namespace ps2_syscalls
|
||||
{
|
||||
static int allocatePs2Fd(FILE *file)
|
||||
static PS2VfsMounts currentVfsMounts()
|
||||
{
|
||||
if (!file)
|
||||
return -1;
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_fd_mutex);
|
||||
int fd = g_nextFd++;
|
||||
g_fileDescriptors[fd] = file;
|
||||
return fd;
|
||||
}
|
||||
|
||||
static FILE *getHostFile(int ps2Fd)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_fd_mutex);
|
||||
auto it = g_fileDescriptors.find(ps2Fd);
|
||||
if (it != g_fileDescriptors.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void releasePs2Fd(int ps2Fd)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_fd_mutex);
|
||||
g_fileDescriptors.erase(ps2Fd);
|
||||
const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths();
|
||||
return {paths.hostRoot, paths.cdRoot, paths.mcRoot};
|
||||
}
|
||||
|
||||
struct VagAccumEntry
|
||||
@@ -40,39 +18,6 @@ namespace ps2_syscalls
|
||||
static std::mutex g_vagAccumMutex;
|
||||
static constexpr size_t kVagAccumMaxBytes = 16 * 1024 * 1024;
|
||||
|
||||
static const char *translateFioMode(int ps2Flags)
|
||||
{
|
||||
bool read = (ps2Flags & PS2_FIO_O_RDONLY) || (ps2Flags & PS2_FIO_O_RDWR);
|
||||
bool write = (ps2Flags & PS2_FIO_O_WRONLY) || (ps2Flags & PS2_FIO_O_RDWR);
|
||||
bool append = (ps2Flags & PS2_FIO_O_APPEND);
|
||||
bool create = (ps2Flags & PS2_FIO_O_CREAT);
|
||||
bool truncate = (ps2Flags & PS2_FIO_O_TRUNC);
|
||||
|
||||
if (read && write)
|
||||
{
|
||||
if (create && truncate)
|
||||
return "w+b";
|
||||
if (create)
|
||||
return "a+b";
|
||||
return "r+b";
|
||||
}
|
||||
else if (write)
|
||||
{
|
||||
if (append)
|
||||
return "ab";
|
||||
if (create && truncate)
|
||||
return "wb";
|
||||
if (create)
|
||||
return "wx";
|
||||
return "r+b";
|
||||
}
|
||||
else if (read)
|
||||
{
|
||||
return "rb";
|
||||
}
|
||||
return "rb";
|
||||
}
|
||||
|
||||
void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
|
||||
@@ -86,52 +31,32 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
std::string hostPath = translatePs2Path(ps2Path);
|
||||
if (hostPath.empty())
|
||||
if (!runtime)
|
||||
{
|
||||
std::cerr << "fioOpen error: Failed to translate path '" << ps2Path << "'" << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *mode = translateFioMode(flags);
|
||||
RUNTIME_LOG("fioOpen: '" << hostPath << "' flags=0x" << std::hex << flags << std::dec << " mode='" << mode << "'");
|
||||
|
||||
FILE *fp = ::fopen(hostPath.c_str(), mode);
|
||||
if (!fp)
|
||||
{
|
||||
std::cerr << "fioOpen error: fopen failed for '" << hostPath << "': " << strerror(errno) << std::endl;
|
||||
setReturnS32(ctx, -1); // e.g., -ENOENT, -EACCES
|
||||
return;
|
||||
}
|
||||
|
||||
int ps2Fd = allocatePs2Fd(fp);
|
||||
if (ps2Fd < 0)
|
||||
{
|
||||
std::cerr << "fioOpen error: Failed to allocate PS2 file descriptor" << std::endl;
|
||||
::fclose(fp);
|
||||
setReturnS32(ctx, -1); // e.g., -EMFILE
|
||||
return;
|
||||
}
|
||||
|
||||
// returns the PS2 file descriptor
|
||||
setReturnS32(ctx, ps2Fd);
|
||||
const int32_t descriptor = runtime->vfs().open(ps2Path, static_cast<uint32_t>(flags), currentVfsMounts(), runtime->romDevice());
|
||||
setReturnS32(ctx, descriptor);
|
||||
}
|
||||
|
||||
void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
int ps2Fd = (int)getRegU32(ctx, 4);
|
||||
|
||||
FILE *fp = getHostFile(ps2Fd);
|
||||
if (!fp)
|
||||
if (!runtime)
|
||||
{
|
||||
std::cerr << "fioClose warning: Invalid PS2 file descriptor " << ps2Fd << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = ::fclose(fp);
|
||||
releasePs2Fd(ps2Fd);
|
||||
const int32_t ret = runtime->vfs().close(ps2Fd);
|
||||
if (ret < 0)
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vagAccumMutex);
|
||||
@@ -161,7 +86,7 @@ namespace ps2_syscalls
|
||||
}
|
||||
}
|
||||
|
||||
setReturnS32(ctx, ret == 0 ? 0 : -1);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -171,15 +96,13 @@ namespace ps2_syscalls
|
||||
size_t size = getRegU32(ctx, 6); // $a2
|
||||
|
||||
uint8_t *hostBuf = getMemPtr(rdram, bufAddr);
|
||||
FILE *fp = getHostFile(ps2Fd);
|
||||
|
||||
if (!hostBuf)
|
||||
{
|
||||
std::cerr << "fioRead error: Invalid buffer address for fd " << ps2Fd << std::endl;
|
||||
setReturnS32(ctx, -1); // -EFAULT
|
||||
return;
|
||||
}
|
||||
if (!fp)
|
||||
if (!runtime)
|
||||
{
|
||||
std::cerr << "fioRead error: Invalid file descriptor " << ps2Fd << std::endl;
|
||||
setReturnS32(ctx, -1); // -EBADF
|
||||
@@ -191,24 +114,18 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
size_t bytesRead = 0;
|
||||
const int64_t readResult = runtime->vfs().read(ps2Fd, hostBuf, size);
|
||||
if (readResult < 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sys_fd_mutex);
|
||||
bytesRead = fread(hostBuf, 1, size, fp);
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
const size_t bytesRead = static_cast<size_t>(readResult);
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
ps2TraceGuestRangeWrite(rdram, bufAddr, static_cast<uint32_t>(bytesRead), "fioRead", ctx);
|
||||
}
|
||||
|
||||
if (bytesRead < size && ferror(fp))
|
||||
{
|
||||
std::cerr << "fioRead error: fread failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
|
||||
clearerr(fp);
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vagAccumMutex);
|
||||
auto it = g_vagAccum.find(ps2Fd);
|
||||
@@ -254,8 +171,7 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *fp = getHostFile(ps2Fd);
|
||||
if (!fp)
|
||||
if (!runtime)
|
||||
{
|
||||
setReturnS32(ctx, -1); // -EFAULT
|
||||
return;
|
||||
@@ -267,20 +183,15 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
size_t bytesWritten = 0;
|
||||
const int64_t writeResult = runtime->vfs().write(ps2Fd, hostBuf, size);
|
||||
if (writeResult < 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sys_fd_mutex);
|
||||
bytesWritten = ::fwrite(hostBuf, 1, size, fp);
|
||||
if (bytesWritten < size && ferror(fp))
|
||||
{
|
||||
clearerr(fp);
|
||||
setReturnS32(ctx, -1); // -EIO, -ENOSPC etc.
|
||||
return;
|
||||
}
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// returns number of bytes written
|
||||
setReturnS32(ctx, (int32_t)bytesWritten);
|
||||
setReturnS32(ctx, static_cast<int32_t>(writeResult));
|
||||
}
|
||||
|
||||
void fioLseek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -289,8 +200,7 @@ namespace ps2_syscalls
|
||||
int32_t offset = getRegU32(ctx, 5); // $a1 (PS2 seems to use 32-bit offset here commonly)
|
||||
int whence = (int)getRegU32(ctx, 6); // $a2 (PS2 FIO_SEEK constants)
|
||||
|
||||
FILE *fp = getHostFile(ps2Fd);
|
||||
if (!fp)
|
||||
if (!runtime)
|
||||
{
|
||||
std::cerr << "fioLseek error: Invalid file descriptor " << ps2Fd << std::endl;
|
||||
setReturnS32(ctx, -1); // -EBADF
|
||||
@@ -315,22 +225,14 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
if (::fseek(fp, static_cast<long>(offset), hostWhence) != 0)
|
||||
{
|
||||
std::cerr << "fioLseek error: fseek failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
|
||||
setReturnS32(ctx, -1); // Return error code
|
||||
return;
|
||||
}
|
||||
|
||||
long newPos = ::ftell(fp);
|
||||
const int64_t newPos = runtime->vfs().seek(ps2Fd, offset, hostWhence);
|
||||
if (newPos < 0)
|
||||
{
|
||||
std::cerr << "fioLseek error: ftell failed after fseek for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newPos > 0xFFFFFFFFL)
|
||||
if (static_cast<uint64_t>(newPos) > 0x7FFFFFFFu)
|
||||
{
|
||||
std::cerr << "fioLseek warning: New position exceeds 32-bit for fd " << ps2Fd << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
@@ -354,8 +256,8 @@ namespace ps2_syscalls
|
||||
setReturnS32(ctx, -1); // -EFAULT
|
||||
return;
|
||||
}
|
||||
std::string hostPath = translatePs2Path(ps2Path);
|
||||
if (hostPath.empty())
|
||||
std::filesystem::path hostPath;
|
||||
if (!runtime || !runtime->vfs().resolveHostPath(ps2Path, currentVfsMounts(), hostPath))
|
||||
{
|
||||
std::cerr << "fioMkdir error: Failed to translate path '" << ps2Path << "'" << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
@@ -366,13 +268,13 @@ namespace ps2_syscalls
|
||||
|
||||
if (!success && ec)
|
||||
{
|
||||
std::cerr << "fioMkdir error: create_directory failed for '" << hostPath
|
||||
std::cerr << "fioMkdir error: create_directory failed for '" << hostPath.string()
|
||||
<< "': " << ec.message() << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RUNTIME_LOG("fioMkdir: Created directory '" << hostPath << "'");
|
||||
RUNTIME_LOG("fioMkdir: Created directory '" << hostPath.string() << "'");
|
||||
setReturnS32(ctx, 0); // Success
|
||||
}
|
||||
}
|
||||
@@ -388,27 +290,14 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
std::string hostPath = translatePs2Path(ps2Path);
|
||||
if (hostPath.empty())
|
||||
PS2VfsStat status;
|
||||
if (!runtime || !runtime->vfs().stat(ps2Path, currentVfsMounts(), runtime->romDevice(), status) || !status.directory)
|
||||
{
|
||||
std::cerr << "fioChdir error: Failed to translate path '" << ps2Path << "'" << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::current_path(hostPath, ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
std::cerr << "fioChdir error: current_path failed for '" << hostPath
|
||||
<< "': " << ec.message() << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RUNTIME_LOG("fioChdir: Changed directory to '" << hostPath << "'");
|
||||
setReturnS32(ctx, 0); // Success
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,8 +311,8 @@ namespace ps2_syscalls
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
std::string hostPath = translatePs2Path(ps2Path);
|
||||
if (hostPath.empty())
|
||||
std::filesystem::path hostPath;
|
||||
if (!runtime || !runtime->vfs().resolveHostPath(ps2Path, currentVfsMounts(), hostPath))
|
||||
{
|
||||
std::cerr << "fioRmdir error: Failed to translate path '" << ps2Path << "'" << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
@@ -435,20 +324,18 @@ namespace ps2_syscalls
|
||||
|
||||
if (!success || ec)
|
||||
{
|
||||
std::cerr << "fioRmdir error: remove failed for '" << hostPath
|
||||
<< "': " << ec.message() << std::endl;
|
||||
std::cerr << "fioRmdir error: remove failed for '" << hostPath.string() << "': " << ec.message() << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RUNTIME_LOG("fioRmdir: Removed directory '" << hostPath << "'");
|
||||
RUNTIME_LOG("fioRmdir: Removed directory '" << hostPath.string() << "'");
|
||||
setReturnS32(ctx, 0); // Success
|
||||
}
|
||||
}
|
||||
|
||||
void fioGetstat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
// we wont implement this for now.
|
||||
uint32_t pathAddr = getRegU32(ctx, 4); // $a0
|
||||
uint32_t statBufAddr = getRegU32(ctx, 5); // $a1
|
||||
|
||||
@@ -468,15 +355,29 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
std::string hostPath = translatePs2Path(ps2Path);
|
||||
if (hostPath.empty())
|
||||
if (!runtime)
|
||||
{
|
||||
std::cerr << "fioGetstat error: Bad path translate" << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
setReturnS32(ctx, -1);
|
||||
PS2VfsStat status;
|
||||
if (!runtime->vfs().stat(ps2Path, currentVfsMounts(), runtime->romDevice(), status))
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
io_stat_t guest{};
|
||||
guest.mode = (status.directory ? kFioSoIfDir : kFioSoIfReg) | kFioSoIROth | kFioSoIXOth | (status.readOnly ? 0u : kFioSoIWOth);
|
||||
guest.size = static_cast<uint32_t>(status.size & 0xFFFFFFFFu);
|
||||
guest.hisize = static_cast<uint32_t>(status.size >> 32u);
|
||||
encodePs2Time(status.created, guest.ctime);
|
||||
encodePs2Time(status.accessed, guest.atime);
|
||||
encodePs2Time(status.modified, guest.mtime);
|
||||
std::memcpy(ps2StatBuf, &guest, sizeof(guest));
|
||||
ps2TraceGuestRangeWrite(rdram, statBufAddr, sizeof(guest), "fioGetstat", ctx);
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void fioRemove(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -490,8 +391,8 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
std::string hostPath = translatePs2Path(ps2Path);
|
||||
if (hostPath.empty())
|
||||
std::filesystem::path hostPath;
|
||||
if (!runtime || !runtime->vfs().resolveHostPath(ps2Path, currentVfsMounts(), hostPath))
|
||||
{
|
||||
std::cerr << "fioRemove error: Path translate fail" << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
@@ -503,13 +404,12 @@ namespace ps2_syscalls
|
||||
|
||||
if (!success || ec)
|
||||
{
|
||||
std::cerr << "fioRemove error: remove failed for '" << hostPath
|
||||
<< "': " << ec.message() << std::endl;
|
||||
std::cerr << "fioRemove error: remove failed for '" << hostPath.string() << "': " << ec.message() << std::endl;
|
||||
setReturnS32(ctx, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RUNTIME_LOG("fioRemove: Removed file '" << hostPath << "'");
|
||||
RUNTIME_LOG("fioRemove: Removed file '" << hostPath.string() << "'");
|
||||
setReturnS32(ctx, 0); // Success
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,52 +105,6 @@ namespace
|
||||
++g_sif_module_log_count;
|
||||
}
|
||||
|
||||
int32_t trackSifModuleLoad(const std::string &path)
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string pathKey = normalizeSifModulePathKey(path);
|
||||
if (pathKey.empty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
|
||||
|
||||
auto byPathIt = g_sif_module_id_by_path.find(pathKey);
|
||||
if (byPathIt != g_sif_module_id_by_path.end())
|
||||
{
|
||||
auto byIdIt = g_sif_modules_by_id.find(byPathIt->second);
|
||||
if (byIdIt != g_sif_modules_by_id.end())
|
||||
{
|
||||
SifModuleRecord &record = byIdIt->second;
|
||||
record.loaded = true;
|
||||
++record.refCount;
|
||||
return record.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_next_sif_module_id <= 0)
|
||||
{
|
||||
g_next_sif_module_id = 1;
|
||||
}
|
||||
|
||||
const int32_t moduleId = g_next_sif_module_id++;
|
||||
SifModuleRecord record;
|
||||
record.id = moduleId;
|
||||
record.path = path;
|
||||
record.pathKey = pathKey;
|
||||
record.refCount = 1;
|
||||
record.loaded = true;
|
||||
|
||||
g_sif_module_id_by_path[pathKey] = moduleId;
|
||||
g_sif_modules_by_id[moduleId] = record;
|
||||
return moduleId;
|
||||
}
|
||||
|
||||
int32_t trackSifModuleLoadExternal(const std::string &path, int32_t moduleId)
|
||||
{
|
||||
if (path.empty() || moduleId <= 0)
|
||||
|
||||
@@ -112,8 +112,11 @@ inline std::string translatePs2Path(const char *ps2Path)
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string pathStr(ps2Path);
|
||||
std::string lower = toLowerAscii(pathStr);
|
||||
const ps2x::iop::ParsedPs2Path parsed = ps2x::iop::parsePs2Path(ps2Path);
|
||||
if (!parsed)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto resolveWithBase = [&](const std::filesystem::path &base, const std::string &suffix) -> std::string
|
||||
{
|
||||
@@ -126,35 +129,19 @@ inline std::string translatePs2Path(const char *ps2Path)
|
||||
return resolved.lexically_normal().string();
|
||||
};
|
||||
|
||||
if (lower.rfind("host0:", 0) == 0 || lower.rfind("host:", 0) == 0)
|
||||
switch (parsed.device)
|
||||
{
|
||||
const std::size_t prefixLength = (lower.rfind("host0:", 0) == 0) ? 6 : 5;
|
||||
return resolveWithBase(getConfiguredHostRoot(), pathStr.substr(prefixLength));
|
||||
case ps2x::iop::Ps2PathDevice::Host:
|
||||
return resolveWithBase(getConfiguredHostRoot(), parsed.path);
|
||||
case ps2x::iop::Ps2PathDevice::Cdrom:
|
||||
return resolveWithBase(getConfiguredCdRoot(), parsed.path);
|
||||
case ps2x::iop::Ps2PathDevice::MemoryCard0:
|
||||
return resolveWithBase(getConfiguredMcRoot(), parsed.path);
|
||||
case ps2x::iop::Ps2PathDevice::NativeHost:
|
||||
return std::filesystem::path(parsed.path).lexically_normal().string();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
if (lower.rfind("cdrom0:", 0) == 0 || lower.rfind("cdrom:", 0) == 0)
|
||||
{
|
||||
const std::size_t prefixLength = (lower.rfind("cdrom0:", 0) == 0) ? 7 : 6;
|
||||
return resolveWithBase(getConfiguredCdRoot(), pathStr.substr(prefixLength));
|
||||
}
|
||||
|
||||
if (lower.rfind(kMc0Prefix, 0) == 0)
|
||||
{
|
||||
const std::size_t prefixLength = sizeof(kMc0Prefix) - 1;
|
||||
return resolveWithBase(getConfiguredMcRoot(), pathStr.substr(prefixLength));
|
||||
}
|
||||
|
||||
if (!pathStr.empty() && (pathStr.front() == '/' || pathStr.front() == '\\'))
|
||||
{
|
||||
return resolveWithBase(getConfiguredCdRoot(), pathStr);
|
||||
}
|
||||
|
||||
if (pathStr.size() > 1 && pathStr[1] == ':')
|
||||
{
|
||||
return pathStr;
|
||||
}
|
||||
|
||||
return resolveWithBase(getConfiguredCdRoot(), pathStr);
|
||||
}
|
||||
|
||||
static bool localtimeSafe(const std::time_t *t, std::tm *out)
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
inline std::unordered_map<int, FILE *> g_fileDescriptors;
|
||||
inline int g_nextFd = 3; // Start after stdin, stdout, stderr
|
||||
|
||||
// Thread status
|
||||
#define THS_RUN 0x01
|
||||
#define THS_READY 0x02
|
||||
@@ -156,8 +153,6 @@ static constexpr uint32_t kFioSoIROth = 0x0004;
|
||||
static constexpr uint32_t kFioSoIWOth = 0x0002;
|
||||
static constexpr uint32_t kFioSoIXOth = 0x0001;
|
||||
|
||||
inline std::mutex g_fd_mutex;
|
||||
|
||||
struct RpcServerState
|
||||
{
|
||||
uint32_t sid = 0;
|
||||
|
||||
@@ -207,40 +207,22 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
const auto emulated = runtime->loadIopModule(modulePath, arguments.empty() ? nullptr : arguments.data(), static_cast<uint32_t>(arguments.size()));
|
||||
if (emulated.handled)
|
||||
{
|
||||
if (emulated.moduleId <= 0)
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
trackSifModuleLoadExternal(modulePath, emulated.moduleId);
|
||||
logSifModuleAction("load-emulated", emulated.moduleId, modulePath, 1u);
|
||||
setReturnS32(ctx, emulated.moduleId);
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t moduleId = trackSifModuleLoad(modulePath);
|
||||
if (moduleId <= 0)
|
||||
if (!runtime)
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t refs = 0;
|
||||
const auto loaded = runtime->loadIopModule(modulePath, arguments.empty() ? nullptr : arguments.data(), static_cast<uint32_t>(arguments.size()));
|
||||
if (!loaded.handled || loaded.moduleId <= 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
|
||||
auto it = g_sif_modules_by_id.find(moduleId);
|
||||
if (it != g_sif_modules_by_id.end())
|
||||
{
|
||||
refs = it->second.refCount;
|
||||
}
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
logSifModuleAction("load", moduleId, modulePath, refs);
|
||||
|
||||
setReturnS32(ctx, moduleId);
|
||||
trackSifModuleLoadExternal(modulePath, loaded.moduleId);
|
||||
logSifModuleAction("load-emulated", loaded.moduleId, modulePath, 1u);
|
||||
setReturnS32(ctx, loaded.moduleId);
|
||||
}
|
||||
|
||||
void SifInitRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
|
||||
@@ -250,32 +250,6 @@ namespace ps2_syscalls
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
uint32_t bufAddr = getRegU32(ctx, 4); // $a0
|
||||
size_t bufSize = getRegU32(ctx, 5); // $a1
|
||||
char *hostBuf = reinterpret_cast<char *>(getMemPtr(rdram, bufAddr));
|
||||
const char *romName = "ROMVER 0100";
|
||||
|
||||
if (!hostBuf)
|
||||
{
|
||||
std::cerr << "GetRomName error: Invalid buffer address" << std::endl;
|
||||
setReturnS32(ctx, -1); // Error
|
||||
return;
|
||||
}
|
||||
if (bufSize == 0)
|
||||
{
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(hostBuf, romName, bufSize - 1);
|
||||
hostBuf[bufSize - 1] = '\0';
|
||||
|
||||
// returns the length of the string (excluding null?) or error
|
||||
setReturnS32(ctx, (int32_t)strlen(hostBuf));
|
||||
}
|
||||
|
||||
void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t pathAddr = getRegU32(ctx, 4); // $a0 - path
|
||||
@@ -331,39 +305,22 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
const auto emulated = runtime->loadIopModuleBuffer(bufferAddr, arguments.empty() ? nullptr : arguments.data(), static_cast<uint32_t>(arguments.size()));
|
||||
if (emulated.handled)
|
||||
{
|
||||
if (emulated.moduleId <= 0)
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
trackSifModuleLoadExternal(moduleTag, emulated.moduleId);
|
||||
logSifModuleAction("load-buffer-emulated", emulated.moduleId, moduleTag, 1u);
|
||||
setReturnS32(ctx, emulated.moduleId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Profile mode keeps the existing deterministic synthetic IDs.
|
||||
const int32_t moduleId = trackSifModuleLoad(moduleTag);
|
||||
if (moduleId <= 0)
|
||||
if (!runtime)
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t refs = 0;
|
||||
const auto loaded = runtime->loadIopModuleBuffer(bufferAddr, arguments.empty() ? nullptr : arguments.data(), static_cast<uint32_t>(arguments.size()));
|
||||
if (!loaded.handled || loaded.moduleId <= 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sif_module_mutex);
|
||||
auto it = g_sif_modules_by_id.find(moduleId);
|
||||
if (it != g_sif_modules_by_id.end())
|
||||
{
|
||||
refs = it->second.refCount;
|
||||
}
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
logSifModuleAction("load-buffer", moduleId, moduleTag, refs);
|
||||
setReturnS32(ctx, moduleId);
|
||||
|
||||
trackSifModuleLoadExternal(moduleTag, loaded.moduleId);
|
||||
logSifModuleAction("load-buffer-emulated", loaded.moduleId, moduleTag, 1u);
|
||||
setReturnS32(ctx, loaded.moduleId);
|
||||
}
|
||||
|
||||
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId)
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace ps2_syscalls
|
||||
void SetOsdConfigParam(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void SetOsdConfigParam2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void GetOsdConfigParam2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void GetRomName(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void SifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifLoadElf(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifLoadElfPart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -281,41 +281,40 @@ namespace
|
||||
return (index & ~0x18u) | ((index & 0x08u) << 1u) | ((index & 0x10u) >> 1u);
|
||||
}
|
||||
|
||||
// TODO: clut cache
|
||||
uint32_t resolveClutIndex(uint8_t index, uint8_t cpsm, uint8_t csm, uint8_t csa, uint8_t sourcePsm)
|
||||
bool isFourBitIndexedPsm(uint8_t psm)
|
||||
{
|
||||
uint32_t clutIndex = static_cast<uint32_t>(index);
|
||||
return psm == GS_PSM_T4 || psm == GS_PSM_T4HL || psm == GS_PSM_T4HH;
|
||||
}
|
||||
|
||||
// CSM2 addresses the source directly through TEXCLUT. CSA is required
|
||||
// to be zero there, so it must not offset the source coordinates.
|
||||
if (csm != 0u)
|
||||
return (sourcePsm == GS_PSM_T4 ||
|
||||
sourcePsm == GS_PSM_T4HH ||
|
||||
sourcePsm == GS_PSM_T4HL)
|
||||
? (clutIndex & 0x0Fu)
|
||||
: clutIndex;
|
||||
bool isEightBitIndexedPsm(uint8_t psm)
|
||||
{
|
||||
return psm == GS_PSM_T8 || psm == GS_PSM_T8H;
|
||||
}
|
||||
|
||||
const bool is16BitClut = cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S;
|
||||
const uint32_t csaMask = is16BitClut ? 0x1Fu : 0x0Fu;
|
||||
const uint32_t clutIndexMask = is16BitClut ? 0x1FFu : 0x0FFu;
|
||||
const uint32_t clutBase = (static_cast<uint32_t>(csa) & csaMask) << 4u;
|
||||
|
||||
switch (sourcePsm)
|
||||
uint32_t texturePageIndex(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y)
|
||||
{
|
||||
switch (psm & 0x3Fu)
|
||||
{
|
||||
case GS_PSM_T4:
|
||||
case GS_PSM_T4HH:
|
||||
case GS_PSM_T4HL:
|
||||
clutIndex = clutBase + (clutIndex & 0x0Fu);
|
||||
break;
|
||||
case GS_PSM_T8:
|
||||
case GS_PSM_CT32:
|
||||
case GS_PSM_CT24:
|
||||
case GS_PSM_Z32:
|
||||
case GS_PSM_Z24:
|
||||
case GS_PSM_T8H:
|
||||
clutIndex = clutBase + clutIndex;
|
||||
break;
|
||||
case GS_PSM_T4HL:
|
||||
case GS_PSM_T4HH:
|
||||
return static_cast<uint32_t>(GSMem::PixelStorageTraits<GSMem::C32>::PageId(base, bw, x, y));
|
||||
case GS_PSM_CT16:
|
||||
case GS_PSM_CT16S:
|
||||
case GS_PSM_Z16:
|
||||
case GS_PSM_Z16S:
|
||||
return static_cast<uint32_t>(GSMem::PixelStorageTraits<GSMem::C16>::PageId(base, bw, x, y));
|
||||
case GS_PSM_T8:
|
||||
return static_cast<uint32_t>(GSMem::PixelStorageTraits<GSMem::P8>::PageId(base, bw, x, y));
|
||||
case GS_PSM_T4:
|
||||
return static_cast<uint32_t>(GSMem::PixelStorageTraits<GSMem::P4>::PageId(base, bw, x, y));
|
||||
default:
|
||||
return clutIndex;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
return swizzleClutIndexCSM1(clutIndex & clutIndexMask);
|
||||
}
|
||||
|
||||
uint8_t lerpChannel(uint8_t c00, uint8_t c10, uint8_t c01, uint8_t c11, float fx, float fy)
|
||||
@@ -544,6 +543,7 @@ void GSCpuBackend::Initialize(uint8_t *vram, uint32_t vramSize)
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_vram = vram;
|
||||
m_vramSize = vramSize;
|
||||
m_texturePageBuffer.resize(vramSize);
|
||||
ResetUnlocked();
|
||||
}
|
||||
|
||||
@@ -555,6 +555,9 @@ void GSCpuBackend::Reset()
|
||||
|
||||
void GSCpuBackend::ResetUnlocked()
|
||||
{
|
||||
m_clut.fill(0u);
|
||||
m_clutCbp.fill(0u);
|
||||
m_texturePageIndex = UINT32_MAX;
|
||||
m_transfer = {};
|
||||
m_transfer.direction = 3u;
|
||||
m_transferState = {};
|
||||
@@ -571,6 +574,92 @@ void GSCpuBackend::Submit(const GSPrimitiveBatch &batch)
|
||||
DrawPrimitive(batch);
|
||||
}
|
||||
|
||||
void GSCpuBackend::LoadClut(const GSTex0Reg &tex0, const GSTexClutReg &texclut)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_vram || (!isFourBitIndexedPsm(tex0.psm) && !isEightBitIndexedPsm(tex0.psm)))
|
||||
return;
|
||||
|
||||
switch (tex0.cld)
|
||||
{
|
||||
case 0u:
|
||||
case 6u:
|
||||
case 7u:
|
||||
return;
|
||||
case 1u:
|
||||
break;
|
||||
case 2u:
|
||||
m_clutCbp[0] = tex0.cbp;
|
||||
break;
|
||||
case 3u:
|
||||
m_clutCbp[1] = tex0.cbp;
|
||||
break;
|
||||
case 4u:
|
||||
if (m_clutCbp[0] == tex0.cbp)
|
||||
return;
|
||||
m_clutCbp[0] = tex0.cbp;
|
||||
break;
|
||||
case 5u:
|
||||
if (m_clutCbp[1] == tex0.cbp)
|
||||
return;
|
||||
m_clutCbp[1] = tex0.cbp;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
LoadClutUnlocked(tex0, texclut);
|
||||
}
|
||||
|
||||
void GSCpuBackend::LoadClutUnlocked(const GSTex0Reg &tex0, const GSTexClutReg &texclut)
|
||||
{
|
||||
const bool fourBit = isFourBitIndexedPsm(tex0.psm);
|
||||
const bool sixteenBit = tex0.cpsm == GS_PSM_CT16 || tex0.cpsm == GS_PSM_CT16S;
|
||||
const bool thirtyTwoBit = tex0.cpsm == GS_PSM_CT32 || tex0.cpsm == GS_PSM_CT24;
|
||||
if (!sixteenBit && !thirtyTwoBit)
|
||||
return;
|
||||
|
||||
const uint32_t entryCount = fourBit ? 16u : 256u;
|
||||
const uint32_t csaMask = sixteenBit ? 0x1Fu : 0x0Fu;
|
||||
const uint32_t destinationBase = (static_cast<uint32_t>(tex0.csa) & csaMask) << 4u;
|
||||
|
||||
const bool loadCsm1Suffix = tex0.csm == 0u && thirtyTwoBit && !fourBit;
|
||||
const uint32_t firstEntry = loadCsm1Suffix ? destinationBase : 0u;
|
||||
|
||||
for (uint32_t entry = firstEntry; entry < entryCount; ++entry)
|
||||
{
|
||||
uint32_t sourceX = 0u;
|
||||
uint32_t sourceY = 0u;
|
||||
uint32_t sourceWidth = 1u;
|
||||
|
||||
if (tex0.csm == 0u)
|
||||
{
|
||||
const uint32_t sourceIndex = swizzleClutIndexCSM1(entry);
|
||||
sourceX = sourceIndex & 0x0Fu;
|
||||
sourceY = sourceIndex >> 4u;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceWidth = texclut.cbw != 0u ? static_cast<uint32_t>(texclut.cbw) : 1u;
|
||||
sourceX = (static_cast<uint32_t>(texclut.cou) << 4u) + entry;
|
||||
sourceY = static_cast<uint32_t>(texclut.cov);
|
||||
}
|
||||
|
||||
const uint32_t raw = ReadTextureVramUnlocked(tex0.cpsm, tex0.cbp, sourceWidth, sourceX, sourceY);
|
||||
const uint32_t destination = (loadCsm1Suffix ? entry : destinationBase + entry) &
|
||||
(sixteenBit ? 0x1FFu : 0x0FFu);
|
||||
if (sixteenBit)
|
||||
{
|
||||
m_clut[destination] = static_cast<uint16_t>(raw);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_clut[destination] = static_cast<uint16_t>(raw & 0xFFFFu);
|
||||
m_clut[destination + 256u] = static_cast<uint16_t>(raw >> 16u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSCpuBackend::Flush()
|
||||
{
|
||||
// CPU backend is immediate. GPU backends may submit command buffers here.
|
||||
@@ -578,8 +667,8 @@ void GSCpuBackend::Flush()
|
||||
|
||||
void GSCpuBackend::TextureFlush()
|
||||
{
|
||||
// CPU texture reads are coherent with local memory. Future cached/GPU
|
||||
// backends use this boundary to invalidate texture views.
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_texturePageIndex = UINT32_MAX;
|
||||
}
|
||||
|
||||
void GSCpuBackend::Sync(GSSyncReason)
|
||||
@@ -600,6 +689,27 @@ uint32_t GSCpuBackend::ReadVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw
|
||||
return m_readVramFuncs[psm & 0x3Fu](m_vram, base, bw, x, y);
|
||||
}
|
||||
|
||||
uint32_t GSCpuBackend::ReadTextureVramUnlocked(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y)
|
||||
{
|
||||
if (!m_vram)
|
||||
return 0u;
|
||||
|
||||
const uint32_t pageCount = m_vramSize / static_cast<uint32_t>(GSMem::GS_PAGE_SIZE);
|
||||
uint32_t page = texturePageIndex(psm, base, bw, x, y);
|
||||
if (page == UINT32_MAX || pageCount == 0u || m_texturePageBuffer.size() < m_vramSize)
|
||||
return ReadVramUnlocked(psm, base, bw, x, y);
|
||||
|
||||
page %= pageCount;
|
||||
if (m_texturePageIndex != page)
|
||||
{
|
||||
const size_t pageOffset = static_cast<size_t>(page) * GSMem::GS_PAGE_SIZE;
|
||||
std::memcpy(m_texturePageBuffer.data() + pageOffset, m_vram + pageOffset, GSMem::GS_PAGE_SIZE);
|
||||
m_texturePageIndex = page;
|
||||
}
|
||||
|
||||
return m_readVramFuncs[psm & 0x3Fu](m_texturePageBuffer.data(), base, bw, x, y);
|
||||
}
|
||||
|
||||
void GSCpuBackend::WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
@@ -941,27 +1051,40 @@ void GSCpuBackend::WritePixel(const GSDrawState &state, int x, int y, int z, uin
|
||||
|
||||
uint32_t GSCpuBackend::LookupCLUT(const GSDrawState &state,
|
||||
uint8_t index,
|
||||
uint32_t cbp,
|
||||
uint8_t cpsm,
|
||||
uint8_t csm,
|
||||
uint8_t csa,
|
||||
uint8_t sourcePsm)
|
||||
{
|
||||
const uint32_t clutIndex = resolveClutIndex(index, cpsm, csm, csa, sourcePsm);
|
||||
const uint32_t clutWidth = (state.texclut.cbw != 0u) ? static_cast<uint32_t>(state.texclut.cbw) : 1u;
|
||||
const uint32_t clutX = static_cast<uint32_t>(state.texclut.cou) + (clutIndex & 0x0Fu);
|
||||
const uint32_t clutY = static_cast<uint32_t>(state.texclut.cov) + (clutIndex >> 4);
|
||||
const bool sixteenBit = cpsm == GS_PSM_CT16 || cpsm == GS_PSM_CT16S;
|
||||
const uint32_t csaMask = sixteenBit ? 0x1Fu : 0x0Fu;
|
||||
const uint32_t clutBase = (static_cast<uint32_t>(csa) & csaMask) << 4u;
|
||||
const uint32_t sourceIndex = isFourBitIndexedPsm(sourcePsm)
|
||||
? (static_cast<uint32_t>(index) & 0x0Fu)
|
||||
: static_cast<uint32_t>(index);
|
||||
|
||||
uint32_t clutIndex = (clutBase + sourceIndex) & (sixteenBit ? 0x1FFu : 0x0FFu);
|
||||
if (!sixteenBit && csm == 0u && isEightBitIndexedPsm(sourcePsm))
|
||||
{
|
||||
const uint32_t block = std::min((sourceIndex & 0xF0u) + clutBase, 240u);
|
||||
clutIndex = block + (sourceIndex & 0x0Fu);
|
||||
}
|
||||
|
||||
switch (cpsm)
|
||||
{
|
||||
case GS_PSM_CT32:
|
||||
return applyTexa(state.texa, cpsm, GSMem::ReadCT32(m_vram, cbp, clutWidth, clutX, clutY));
|
||||
{
|
||||
const uint32_t raw = static_cast<uint32_t>(m_clut[clutIndex]) | (static_cast<uint32_t>(m_clut[clutIndex + 256u]) << 16u);
|
||||
return applyTexa(state.texa, cpsm, raw);
|
||||
}
|
||||
case GS_PSM_CT24:
|
||||
return applyTexa(state.texa, cpsm, GSMem::ReadCT24(m_vram, cbp, clutWidth, clutX, clutY));
|
||||
{
|
||||
const uint32_t raw = static_cast<uint32_t>(m_clut[clutIndex]) | (static_cast<uint32_t>(m_clut[clutIndex + 256u]) << 16u);
|
||||
return applyTexa(state.texa, cpsm, raw & 0x00FFFFFFu);
|
||||
}
|
||||
case GS_PSM_CT16:
|
||||
return applyTexa(state.texa, cpsm, Rgba5551ToRgba8888(GSMem::ReadCT16(m_vram, cbp, clutWidth, clutX, clutY)));
|
||||
case GS_PSM_CT16S:
|
||||
return applyTexa(state.texa, cpsm, Rgba5551ToRgba8888(GSMem::ReadCT16S(m_vram, cbp, clutWidth, clutX, clutY)));
|
||||
return applyTexa(state.texa, cpsm, Rgba5551ToRgba8888(m_clut[clutIndex]));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1002,7 +1125,7 @@ uint32_t GSCpuBackend::SampleTexture(const GSDrawState &state, float s, float t,
|
||||
sampleU = wrapTextureCoordinate(sampleU, texW, wrapU, minU, maxU);
|
||||
sampleV = wrapTextureCoordinate(sampleV, texH, wrapV, minV, maxV);
|
||||
|
||||
u32 out = ReadVramUnlocked(tex.psm, tex.tbp0, tex.tbw, sampleU, sampleV);
|
||||
u32 out = ReadTextureVramUnlocked(tex.psm, tex.tbp0, tex.tbw, sampleU, sampleV);
|
||||
|
||||
switch (tex.psm)
|
||||
{
|
||||
@@ -1021,7 +1144,7 @@ uint32_t GSCpuBackend::SampleTexture(const GSDrawState &state, float s, float t,
|
||||
case GS_PSM_T4:
|
||||
case GS_PSM_T4HL:
|
||||
case GS_PSM_T4HH:
|
||||
return LookupCLUT(state, static_cast<u8>(out), tex.cbp, tex.cpsm, tex.csm, tex.csa, tex.psm);
|
||||
return LookupCLUT(state, static_cast<u8>(out), tex.cpsm, tex.csm, tex.csa, tex.psm);
|
||||
}
|
||||
|
||||
return 0xFFFF00FFu;
|
||||
|
||||
@@ -1239,6 +1239,7 @@ void GS::writeRegisterUnlocked(uint8_t regAddr, uint64_t value)
|
||||
t.csm = static_cast<uint8_t>((value >> 55) & 0x1);
|
||||
t.csa = static_cast<uint8_t>((value >> 56) & 0x1F);
|
||||
t.cld = static_cast<uint8_t>((value >> 61) & 0x7);
|
||||
m_backend->LoadClut(t, m_texclut);
|
||||
break;
|
||||
}
|
||||
case GS_REG_CLAMP_1:
|
||||
@@ -1269,6 +1270,7 @@ void GS::writeRegisterUnlocked(uint8_t regAddr, uint64_t value)
|
||||
t.csm = static_cast<uint8_t>((value >> 55) & 0x1);
|
||||
t.csa = static_cast<uint8_t>((value >> 56) & 0x1F);
|
||||
t.cld = static_cast<uint8_t>((value >> 61) & 0x7);
|
||||
m_backend->LoadClut(t, m_texclut);
|
||||
break;
|
||||
}
|
||||
case GS_REG_XYOFFSET_1:
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace
|
||||
{
|
||||
for (const ps2x::iop::DebugService &service : snapshot.services)
|
||||
{
|
||||
if (std::find(service.sids.begin(), service.sids.end(), sid) !=
|
||||
if (service.active && std::find(service.sids.begin(), service.sids.end(), sid) !=
|
||||
service.sids.end())
|
||||
{
|
||||
return service.name;
|
||||
@@ -699,13 +699,48 @@ namespace
|
||||
|
||||
void drawCpuTab(PS2Runtime &runtime, bool showRegisters)
|
||||
{
|
||||
const uint32_t pc = runtime.m_debugPc.load(std::memory_order_relaxed);
|
||||
const uint32_t ra = runtime.m_debugRa.load(std::memory_order_relaxed);
|
||||
const uint32_t sp = runtime.m_debugSp.load(std::memory_order_relaxed);
|
||||
const uint32_t gp = runtime.m_debugGp.load(std::memory_order_relaxed);
|
||||
const bool executingGuest = runtime.eeScheduler().isExecutingGuest();
|
||||
const EeKernelSnapshot schedulerSnapshot = runtime.eeScheduler().snapshot();
|
||||
const EeThreadSnapshot *selectedThread = nullptr;
|
||||
if (!executingGuest)
|
||||
{
|
||||
const auto running = std::find_if(schedulerSnapshot.threads.begin(),
|
||||
schedulerSnapshot.threads.end(),
|
||||
[&](const EeThreadSnapshot &thread)
|
||||
{ return thread.id == schedulerSnapshot.runningThreadId; });
|
||||
if (running != schedulerSnapshot.threads.end())
|
||||
{
|
||||
selectedThread = &*running;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto blocked = std::find_if(schedulerSnapshot.threads.begin(),
|
||||
schedulerSnapshot.threads.end(),
|
||||
[](const EeThreadSnapshot &thread)
|
||||
{ return thread.status != EeThreadStatus::Dormant; });
|
||||
if (blocked != schedulerSnapshot.threads.end())
|
||||
{
|
||||
selectedThread = &*blocked;
|
||||
}
|
||||
else if (!schedulerSnapshot.threads.empty())
|
||||
{
|
||||
selectedThread = &schedulerSnapshot.threads.front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t pc = selectedThread ? selectedThread->pc : runtime.m_debugPc.load(std::memory_order_relaxed);
|
||||
const uint32_t ra = selectedThread ? selectedThread->ra : runtime.m_debugRa.load(std::memory_order_relaxed);
|
||||
const uint32_t sp = selectedThread ? selectedThread->sp : runtime.m_debugSp.load(std::memory_order_relaxed);
|
||||
const uint32_t gp = selectedThread ? selectedThread->contextGp : runtime.m_debugGp.load(std::memory_order_relaxed);
|
||||
|
||||
ImGui::Text("Runtime: %s", runtime.isStopRequested() ? "stop requested" : "running");
|
||||
ImGui::Text("EE executor: %s", runtime.eeScheduler().isExecutingGuest() ? "guest" : "scheduler");
|
||||
ImGui::Text("EE executor: %s", executingGuest ? "guest" : "scheduler");
|
||||
if (selectedThread)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(thread %d: %s/%s)", selectedThread->id, threadStatusName(selectedThread->status), waitTypeName(selectedThread->waitReason));
|
||||
}
|
||||
ImGui::Separator();
|
||||
textHex32("PC", pc);
|
||||
ImGui::SameLine();
|
||||
@@ -771,19 +806,23 @@ namespace
|
||||
static_cast<unsigned long long>(snapshot.eeCycle),
|
||||
static_cast<unsigned long long>(snapshot.sliceEndCycle),
|
||||
static_cast<unsigned long long>(snapshot.nextEventCycle));
|
||||
if (ImGui::BeginTable("threads", 11, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 320)))
|
||||
if (ImGui::BeginTable("threads", 15, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 320)))
|
||||
{
|
||||
ImGui::TableSetupColumn("ID");
|
||||
ImGui::TableSetupColumn("Status");
|
||||
ImGui::TableSetupColumn("Wait");
|
||||
ImGui::TableSetupColumn("WaitId");
|
||||
ImGui::TableSetupColumn("PC");
|
||||
ImGui::TableSetupColumn("RA");
|
||||
ImGui::TableSetupColumn("SP");
|
||||
ImGui::TableSetupColumn("Ctx GP");
|
||||
ImGui::TableSetupColumn("Entry");
|
||||
ImGui::TableSetupColumn("Stack");
|
||||
ImGui::TableSetupColumn("GP");
|
||||
ImGui::TableSetupColumn("Prio");
|
||||
ImGui::TableSetupColumn("Wake");
|
||||
ImGui::TableSetupColumn("Susp");
|
||||
ImGui::TableSetupColumn("Inv");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const EeThreadSnapshot &row : snapshot.threads)
|
||||
{
|
||||
@@ -799,6 +838,12 @@ namespace
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.pc);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.ra);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.sp);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.contextGp);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.entry);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X/%u", row.stack, row.stackSize);
|
||||
@@ -810,6 +855,8 @@ namespace
|
||||
ImGui::Text("%u", row.wakeupCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", row.suspendCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%u", row.invocationDepth);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
@@ -1028,14 +1075,11 @@ namespace
|
||||
? "builtin"
|
||||
: iopSnapshot.activeProvider.c_str());
|
||||
|
||||
if (ImGui::BeginTable("iop_hle_services",
|
||||
5,
|
||||
ImGuiTableFlags_Borders |
|
||||
ImGuiTableFlags_RowBg |
|
||||
ImGuiTableFlags_Resizable))
|
||||
if (ImGui::BeginTable("iop_hle_services", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
||||
{
|
||||
ImGui::TableSetupColumn("Service");
|
||||
ImGui::TableSetupColumn("Layer");
|
||||
ImGui::TableSetupColumn("Active");
|
||||
ImGui::TableSetupColumn("SID");
|
||||
ImGui::TableSetupColumn("EE server");
|
||||
ImGui::TableSetupColumn("Metrics");
|
||||
@@ -1050,6 +1094,8 @@ namespace
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(service.profileSpecific ? "profile" : "core");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(service.active ? "yes" : "no");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted("-");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted("-");
|
||||
@@ -1066,6 +1112,8 @@ namespace
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(service.profileSpecific ? "profile" : "core");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(service.active ? "yes" : "no");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", sid);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(hasServer(sid) ? "yes" : "no");
|
||||
@@ -1761,27 +1809,25 @@ namespace
|
||||
struct FdRow
|
||||
{
|
||||
int fd = 0;
|
||||
FILE *file = nullptr;
|
||||
std::string device;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
std::vector<FdRow> fds;
|
||||
for (const PS2VfsDescriptorInfo &descriptor : runtime.vfs().descriptors())
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_fd_mutex);
|
||||
fds.reserve(g_fileDescriptors.size());
|
||||
for (const auto &[fd, file] : g_fileDescriptors)
|
||||
{
|
||||
fds.push_back(FdRow{fd, file});
|
||||
}
|
||||
fds.push_back({descriptor.descriptor, descriptor.device, descriptor.path});
|
||||
}
|
||||
std::sort(fds.begin(), fds.end(), [](const FdRow &a, const FdRow &b)
|
||||
{ return a.fd < b.fd; });
|
||||
|
||||
ImGui::SeparatorText("FileIO descriptors");
|
||||
ImGui::Text("Open host FILE* descriptors: %zu", fds.size());
|
||||
if (ImGui::BeginTable("fileio_fds", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable, ImVec2(0, 90)))
|
||||
ImGui::Text("Open VFS descriptors: %zu", fds.size());
|
||||
if (ImGui::BeginTable("fileio_fds", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable, ImVec2(0, 90)))
|
||||
{
|
||||
ImGui::TableSetupColumn("FD");
|
||||
ImGui::TableSetupColumn("FILE*");
|
||||
ImGui::TableSetupColumn("Device");
|
||||
ImGui::TableSetupColumn("Path");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const FdRow &row : fds)
|
||||
{
|
||||
@@ -1789,7 +1835,9 @@ namespace
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", row.fd);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%p", static_cast<void *>(row.file));
|
||||
ImGui::TextUnformatted(row.device.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(row.path.c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
@@ -135,11 +135,6 @@ bool PS2IopHostAdapter::readGuest(uint32_t address, void *destination, size_t si
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
return ps2_stubs::readSifIopHeap(address, destination, size);
|
||||
}
|
||||
|
||||
uint8_t *source = nullptr;
|
||||
if (!guestRange(address, size, source))
|
||||
{
|
||||
@@ -158,11 +153,6 @@ bool PS2IopHostAdapter::writeGuest(uint32_t address, const void *source, size_t
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
return ps2_stubs::writeSifIopHeap(address, source, size);
|
||||
}
|
||||
|
||||
uint8_t *destination = nullptr;
|
||||
if (!guestRange(address, size, destination))
|
||||
{
|
||||
@@ -179,11 +169,6 @@ bool PS2IopHostAdapter::writeGuest(uint32_t address, const void *source, size_t
|
||||
|
||||
bool PS2IopHostAdapter::zeroGuest(uint32_t address, size_t size)
|
||||
{
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
return ps2_stubs::zeroSifIopHeap(address, size);
|
||||
}
|
||||
|
||||
uint8_t *destination = nullptr;
|
||||
if (!guestRange(address, size, destination))
|
||||
{
|
||||
@@ -200,12 +185,6 @@ bool PS2IopHostAdapter::zeroGuest(uint32_t address, size_t size)
|
||||
|
||||
bool PS2IopHostAdapter::normalizeGuestAddress(uint32_t address, uint32_t &normalized) const
|
||||
{
|
||||
if (ps2_stubs::isSifIopHeapAddress(address))
|
||||
{
|
||||
normalized = address;
|
||||
return ps2_stubs::isSifIopHeapRange(address, 0u);
|
||||
}
|
||||
|
||||
bool scratchpad = false;
|
||||
if (!ps2ResolveGuestPointer(address, normalized, scratchpad) || scratchpad)
|
||||
{
|
||||
@@ -215,6 +194,32 @@ bool PS2IopHostAdapter::normalizeGuestAddress(uint32_t address, uint32_t &normal
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2IopHostAdapter::readIopMemory(uint32_t address, void *destination, size_t size) const
|
||||
{
|
||||
return m_runtime.readIopMemory(address, destination, size);
|
||||
}
|
||||
|
||||
bool PS2IopHostAdapter::writeIopMemory(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
return m_runtime.writeIopMemory(address, source, size);
|
||||
}
|
||||
|
||||
bool PS2IopHostAdapter::zeroIopMemory(uint32_t address, size_t size)
|
||||
{
|
||||
return m_runtime.zeroIopMemory(address, size);
|
||||
}
|
||||
|
||||
bool PS2IopHostAdapter::normalizeIopAddress(uint32_t address, uint32_t &normalized) const
|
||||
{
|
||||
if (!m_runtime.isIopMemoryRange(address, 0u))
|
||||
{
|
||||
normalized = 0u;
|
||||
return false;
|
||||
}
|
||||
normalized = address & 0x1FFFFFFFu;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t PS2IopHostAdapter::allocateIopHandle(ps2x::iop::IopHandleKind kind)
|
||||
{
|
||||
uint8_t *const rdram = m_activeRdram
|
||||
@@ -283,7 +288,12 @@ std::string PS2IopHostAdapter::hostPath(ps2x::iop::HostPathKind kind) const
|
||||
|
||||
std::string PS2IopHostAdapter::translateGuestPath(std::string_view path) const
|
||||
{
|
||||
return translatePs2Path(std::string(path).c_str());
|
||||
const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths();
|
||||
const PS2VfsMounts mounts{paths.hostRoot, paths.cdRoot, paths.mcRoot};
|
||||
std::filesystem::path hostPath;
|
||||
if (!m_runtime.vfs().resolveHostPath(path, mounts, hostPath))
|
||||
return {};
|
||||
return hostPath.string();
|
||||
}
|
||||
|
||||
uint64_t PS2IopHostAdapter::openHostFile(std::string_view path)
|
||||
@@ -493,6 +503,20 @@ bool PS2IopHostAdapter::invokeGuestFunction(uint64_t callToken,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PS2IopHostAdapter::sendSifCommand(uint32_t commandId,
|
||||
const void *packet,
|
||||
size_t packetSize)
|
||||
{
|
||||
uint8_t *const rdram = m_activeRdram
|
||||
? m_activeRdram
|
||||
: m_runtime.memory().getRDRAM();
|
||||
return ps2_stubs::dispatchSifCommand(rdram,
|
||||
&m_runtime,
|
||||
commandId,
|
||||
packet,
|
||||
packetSize);
|
||||
}
|
||||
|
||||
void PS2IopHostAdapter::log(ps2x::iop::LogLevel level, std::string_view message)
|
||||
{
|
||||
const char *prefix = "[ps2xIOP]";
|
||||
|
||||
@@ -48,6 +48,10 @@ public:
|
||||
bool writeGuest(uint32_t address, const void *source, size_t size) override;
|
||||
bool zeroGuest(uint32_t address, size_t size) override;
|
||||
bool normalizeGuestAddress(uint32_t address, uint32_t &normalized) const override;
|
||||
bool readIopMemory(uint32_t address, void *destination, size_t size) const override;
|
||||
bool writeIopMemory(uint32_t address, const void *source, size_t size) override;
|
||||
bool zeroIopMemory(uint32_t address, size_t size) override;
|
||||
bool normalizeIopAddress(uint32_t address, uint32_t &normalized) const override;
|
||||
uint32_t allocateIopHandle(ps2x::iop::IopHandleKind kind) override;
|
||||
uint32_t allocateGuest(uint32_t size, uint32_t alignment) override;
|
||||
void freeGuest(uint32_t address) override;
|
||||
@@ -78,6 +82,7 @@ public:
|
||||
uint32_t a2,
|
||||
uint32_t a3,
|
||||
uint32_t *resultAddress) override;
|
||||
bool sendSifCommand(uint32_t commandId, const void *packet, size_t packetSize) override;
|
||||
|
||||
void log(ps2x::iop::LogLevel level, std::string_view message) override;
|
||||
|
||||
|
||||
@@ -411,6 +411,12 @@ uint32_t PS2Memory::advanceEeTimers(uint64_t eeCycles) noexcept
|
||||
return 0u;
|
||||
}
|
||||
|
||||
constexpr uint32_t kGifStat = 0x10003020u;
|
||||
constexpr uint32_t kGifFqcMask = 0x1F000000u;
|
||||
auto gifStatIt = m_ioRegisters.find(kGifStat);
|
||||
if (gifStatIt != m_ioRegisters.end())
|
||||
gifStatIt->second &= ~kGifFqcMask;
|
||||
|
||||
uint32_t interruptMask = 0u;
|
||||
for (size_t index = 0; index < m_eeTimers.size(); ++index)
|
||||
{
|
||||
@@ -1064,6 +1070,21 @@ void PS2Memory::write128(uint32_t address, __m128i value)
|
||||
const bool scratch = isScratchpad(address);
|
||||
uint32_t physAddr = translateAddress(address);
|
||||
|
||||
if (!scratch && physAddr == 0x10004000u) // VIF0_FIFO
|
||||
{
|
||||
alignas(16) uint8_t fifoData[16];
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i *>(fifoData), value);
|
||||
processVIF0Data(fifoData, sizeof(fifoData));
|
||||
return;
|
||||
}
|
||||
if (!scratch && physAddr == 0x10005000u) // VIF1_FIFO
|
||||
{
|
||||
alignas(16) uint8_t fifoData[16];
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i *>(fifoData), value);
|
||||
processVIF1Data(fifoData, sizeof(fifoData));
|
||||
return;
|
||||
}
|
||||
|
||||
if (scratch)
|
||||
{
|
||||
inRange(physAddr, sizeof(__m128i), PS2_SCRATCHPAD_SIZE, "write128 scratchpad", address);
|
||||
@@ -1117,7 +1138,7 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
case kEeTimerModeOffset:
|
||||
{
|
||||
const uint32_t previousMode = timer.mode;
|
||||
const uint32_t status = (previousMode & kEeTimerModeStatusMask) &~(value & kEeTimerModeStatusMask);
|
||||
const uint32_t status = (previousMode & kEeTimerModeStatusMask) & ~(value & kEeTimerModeStatusMask);
|
||||
timer.mode = (value & kEeTimerModeConfigMask) | status;
|
||||
if (((previousMode ^ timer.mode) & (kEeTimerModeClksMask | kEeTimerModeCue)) != 0u)
|
||||
{
|
||||
@@ -1206,9 +1227,13 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
case 0x10003C10u: // VIF1_FBRST
|
||||
if (value & 0x1u) // RST
|
||||
{
|
||||
const bool wasPath3Masked = m_path3Masked;
|
||||
std::memset(&vif1_regs, 0, sizeof(vif1_regs));
|
||||
m_vif1PendingPath2ImageQwc = 0u;
|
||||
m_vif1PendingPath2DirectHl = false;
|
||||
m_path3Masked = false;
|
||||
if (wasPath3Masked)
|
||||
flushMaskedPath3Packets();
|
||||
}
|
||||
if (value & 0x8u) // STC
|
||||
{
|
||||
@@ -1281,8 +1306,12 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
const uint32_t qwc = m_ioRegisters[channelBase + 0x20];
|
||||
m_dmaStartCount.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
if ((channelBase == 0x1000A000u || channelBase == 0x10009000u || channelBase == 0x10008000u) &&
|
||||
(m_gsVRAM || channelBase == 0x10008000u))
|
||||
if (tryProcessScratchpadDma(channelBase, value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((channelBase == 0x1000A000u || channelBase == 0x10009000u || channelBase == 0x10008000u) && (m_gsVRAM || channelBase == 0x10008000u))
|
||||
{
|
||||
auto enqueueTransfer = [&](uint32_t srcAddr, uint32_t qwCount)
|
||||
{
|
||||
@@ -1353,7 +1382,7 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
}
|
||||
};
|
||||
|
||||
auto appendCompactVif1TagData = [&](uint32_t localTagAddr, uint32_t qwCount)
|
||||
auto appendVifTagData = [&](uint32_t localTagAddr)
|
||||
{
|
||||
uint32_t tagPhys = 0u;
|
||||
const bool tagScratch = isScratchpad(localTagAddr);
|
||||
@@ -1364,11 +1393,15 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
if (tagPhys + 16u > localMax)
|
||||
return;
|
||||
|
||||
// VIF packet helpers embed 8 bytes of VIF stream in the DMAtag's upper half.
|
||||
// CHCR.TTE sends the DMAtag's upper 64 bits to the channel before
|
||||
// the tag payload. VIF chains use those bytes for two VIFcodes.
|
||||
chainBuf.insert(chainBuf.end(), localBase + tagPhys + 8u, localBase + tagPhys + 16u);
|
||||
appendData(localTagAddr + 16u, qwCount);
|
||||
};
|
||||
|
||||
const bool isVifChannel =
|
||||
channelBase == 0x10009000u || channelBase == 0x10008000u;
|
||||
const bool transferTagData = isVifChannel && (chcr & 0x40u) != 0u;
|
||||
|
||||
int tagsProcessed = 0;
|
||||
uint32_t lastTagUpper = (chcr >> 16) & 0xFFFFu;
|
||||
|
||||
@@ -1477,19 +1510,11 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
break;
|
||||
}
|
||||
|
||||
const bool compactVifLocalTag =
|
||||
(channelBase == 0x10009000u || channelBase == 0x10008000u) &&
|
||||
(id == 1u || id == 2u || id == 5u || id == 6u || id == 7u);
|
||||
if (compactVifLocalTag)
|
||||
appendCompactVif1TagData(currentTagAddr, 0u);
|
||||
if (transferTagData)
|
||||
appendVifTagData(currentTagAddr);
|
||||
|
||||
if (hasPayload)
|
||||
{
|
||||
if (compactVifLocalTag)
|
||||
appendData(currentTagAddr + 16u, tagQwc);
|
||||
else
|
||||
appendData(dataAddr, tagQwc);
|
||||
}
|
||||
appendData(dataAddr, tagQwc);
|
||||
if (irq && tieEnabled)
|
||||
endChain = true;
|
||||
if (endChain)
|
||||
@@ -1559,9 +1584,101 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PS2Memory::tryProcessScratchpadDma(uint32_t channelBase, uint32_t chcr)
|
||||
{
|
||||
static constexpr uint32_t kSprFromChannel = 0x1000D000u;
|
||||
static constexpr uint32_t kSprToChannel = 0x1000D400u;
|
||||
if (channelBase != kSprFromChannel && channelBase != kSprToChannel)
|
||||
return false;
|
||||
|
||||
const uint32_t mode = (chcr >> 2u) & 0x3u;
|
||||
if (mode != 0u)
|
||||
return false;
|
||||
|
||||
const uint32_t qwc = m_ioRegisters[channelBase + 0x20u] & 0xFFFFu;
|
||||
const uint32_t byteCount = qwc * 16u;
|
||||
const uint32_t originalMadr = m_ioRegisters[channelBase + 0x10u] & 0x7FFFFFF0u;
|
||||
const uint32_t originalSadr = m_ioRegisters[channelBase + 0x80u] & 0x3FF0u;
|
||||
|
||||
uint32_t mainOffset = 0u;
|
||||
try
|
||||
{
|
||||
mainOffset = translateAddress(originalMadr);
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mainOffset > PS2_RAM_SIZE || byteCount > PS2_RAM_SIZE - mainOffset)
|
||||
return false;
|
||||
|
||||
const bool fromScratchpad = channelBase == kSprFromChannel;
|
||||
uint32_t scratchOffset = originalSadr;
|
||||
uint32_t bytesLeft = byteCount;
|
||||
uint32_t copied = 0u;
|
||||
while (bytesLeft != 0u)
|
||||
{
|
||||
const uint32_t scratchChunk = PS2_SCRATCHPAD_SIZE - scratchOffset;
|
||||
const uint32_t chunk = std::min(bytesLeft, scratchChunk);
|
||||
if (fromScratchpad)
|
||||
{
|
||||
std::memcpy(m_rdram + mainOffset + copied, m_scratchpad + scratchOffset, chunk);
|
||||
markModified(mainOffset + copied, chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(m_scratchpad + scratchOffset, m_rdram + mainOffset + copied, chunk);
|
||||
}
|
||||
|
||||
copied += chunk;
|
||||
bytesLeft -= chunk;
|
||||
scratchOffset = (scratchOffset + chunk) & (PS2_SCRATCHPAD_SIZE - 1u);
|
||||
}
|
||||
|
||||
m_ioRegisters[channelBase + 0x10u] = (originalMadr + byteCount) & 0x7FFFFFF0u;
|
||||
m_ioRegisters[channelBase + 0x20u] = 0u;
|
||||
m_ioRegisters[channelBase + 0x80u] = (originalSadr + byteCount) & 0x3FF0u;
|
||||
completeDmacChannel(channelBase, fromScratchpad ? 8u : 9u);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PS2Memory::completeDmacChannel(uint32_t channelBase, uint32_t cause)
|
||||
{
|
||||
static constexpr uint32_t kDStat = 0x1000E010u;
|
||||
m_ioRegisters[channelBase] &= ~0x100u;
|
||||
|
||||
uint32_t dstat = m_ioRegisters.count(kDStat) ? m_ioRegisters[kDStat] : 0u;
|
||||
dstat |= 1u << cause;
|
||||
const uint32_t status = dstat & 0x3FFu;
|
||||
const uint32_t mask = (dstat >> 16u) & 0x3FFu;
|
||||
if ((status & mask) != 0u)
|
||||
dstat |= 1u << 31u;
|
||||
else
|
||||
dstat &= ~(1u << 31u);
|
||||
m_ioRegisters[kDStat] = dstat;
|
||||
queueCompletedDmacCause(cause);
|
||||
}
|
||||
|
||||
void PS2Memory::processPendingTransfers()
|
||||
{
|
||||
const bool hadGif = !m_pendingGifTransfers.empty();
|
||||
uint32_t observedGifQwc = 0u;
|
||||
for (const auto &transfer : m_pendingGifTransfers)
|
||||
{
|
||||
const uint64_t transferQwc = !transfer.chainData.empty()
|
||||
? (transfer.chainData.size() / 16u)
|
||||
: transfer.qwc;
|
||||
observedGifQwc = static_cast<uint32_t>(std::min<uint64_t>(16u, static_cast<uint64_t>(observedGifQwc) + transferQwc));
|
||||
}
|
||||
if (observedGifQwc != 0u)
|
||||
{
|
||||
constexpr uint32_t kGifStat = 0x10003020u;
|
||||
constexpr uint32_t kGifFqcMask = 0x1F000000u;
|
||||
uint32_t &gifStat = m_ioRegisters[kGifStat];
|
||||
gifStat = (gifStat & ~kGifFqcMask) | (observedGifQwc << 24u);
|
||||
}
|
||||
|
||||
for (size_t idx = 0; idx < m_pendingGifTransfers.size(); ++idx)
|
||||
{
|
||||
auto &p = m_pendingGifTransfers[idx];
|
||||
@@ -2221,6 +2338,23 @@ uint32_t PS2Memory::readIORegister(uint32_t address)
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
if (address == 0x10003020u) // GIF_STAT
|
||||
{
|
||||
uint32_t stat = m_ioRegisters.count(address) ? m_ioRegisters[address] : 0u;
|
||||
const uint32_t mode = m_ioRegisters.count(0x10003010u) ? m_ioRegisters[0x10003010u] : 0u;
|
||||
const uint32_t ctrl = m_ioRegisters.count(0x10003000u) ? m_ioRegisters[0x10003000u] : 0u;
|
||||
|
||||
// M3R and IMT mirror GIF_MODE, PSE mirrors GIF_CTRL, and M3P is the
|
||||
// effective PATH3 mask controlled by the VIF1 MSKPATH3 command.
|
||||
stat = (stat & ~0xFu) |
|
||||
(mode & 0x1u) |
|
||||
(m_path3Masked ? 0x2u : 0u) |
|
||||
(mode & 0x4u) |
|
||||
(ctrl & 0x8u);
|
||||
return stat;
|
||||
}
|
||||
|
||||
if (address >= 0x10000000 && address < 0x10010000)
|
||||
{
|
||||
if (address >= 0x10008000 && address < 0x1000F000)
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#include "runtime/ps2_rom_device.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::mutex &profileMutex()
|
||||
{
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
std::vector<PS2RomProfile> &profileRegistry()
|
||||
{
|
||||
static std::vector<PS2RomProfile> profiles;
|
||||
return profiles;
|
||||
}
|
||||
|
||||
bool equalsIgnoreCaseAscii(std::string_view lhs, std::string_view rhs)
|
||||
{
|
||||
if (lhs.size() != rhs.size())
|
||||
return false;
|
||||
for (size_t i = 0; i < lhs.size(); ++i)
|
||||
{
|
||||
const auto left = static_cast<unsigned char>(lhs[i]);
|
||||
const auto right = static_cast<unsigned char>(rhs[i]);
|
||||
if (std::tolower(left) != std::tolower(right))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int matchSpecificity(const ps2x::iop::GameMatcher &matcher, const ps2x::iop::GameIdentity &identity)
|
||||
{
|
||||
int specificity = 0;
|
||||
if (!matcher.elfName.empty())
|
||||
{
|
||||
if (!equalsIgnoreCaseAscii(matcher.elfName, identity.elfName))
|
||||
return -1;
|
||||
++specificity;
|
||||
}
|
||||
if (matcher.entryPoint != 0u)
|
||||
{
|
||||
if (matcher.entryPoint != identity.entryPoint)
|
||||
return -1;
|
||||
++specificity;
|
||||
}
|
||||
if (matcher.crc32 != 0u)
|
||||
{
|
||||
if (matcher.crc32 != identity.crc32)
|
||||
return -1;
|
||||
++specificity;
|
||||
}
|
||||
return specificity;
|
||||
}
|
||||
}
|
||||
|
||||
PS2RomDevice::PS2RomDevice()
|
||||
{
|
||||
mountBaseProfile();
|
||||
}
|
||||
|
||||
void PS2RomDevice::registerProfile(PS2RomProfile profile)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(profileMutex());
|
||||
profileRegistry().push_back(std::move(profile));
|
||||
}
|
||||
|
||||
bool PS2RomDevice::configure(const ps2x::iop::GameIdentity &identity, std::string *error)
|
||||
{
|
||||
m_files.clear();
|
||||
m_activeProfile.clear();
|
||||
m_activeProvider.clear();
|
||||
mountBaseProfile();
|
||||
|
||||
std::vector<PS2RomProfile> profiles;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(profileMutex());
|
||||
profiles = profileRegistry();
|
||||
}
|
||||
|
||||
const PS2RomProfile *selected = nullptr;
|
||||
const PS2RomProfile *tie = nullptr;
|
||||
int selectedSpecificity = -1;
|
||||
for (const PS2RomProfile &profile : profiles)
|
||||
{
|
||||
const int specificity = matchSpecificity(profile.matcher, identity);
|
||||
if (specificity < 0)
|
||||
continue;
|
||||
if (specificity > selectedSpecificity)
|
||||
{
|
||||
selected = &profile;
|
||||
tie = nullptr;
|
||||
selectedSpecificity = specificity;
|
||||
}
|
||||
else if (specificity == selectedSpecificity && selected)
|
||||
{
|
||||
tie = &profile;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected && tie)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = "ambiguous ROM profiles '" + selected->provider + ":" + selected->id + "' and '" + tie->provider + ":" + tie->id + "'";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selected)
|
||||
{
|
||||
mountFiles(selected->files);
|
||||
m_activeProfile = selected->id;
|
||||
m_activeProvider = selected->provider;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2RomDevice::readFile(std::string_view ps2Path, std::vector<uint8_t> &bytes) const
|
||||
{
|
||||
const auto file = m_files.find(normalizePath(ps2Path));
|
||||
if (file == m_files.end())
|
||||
{
|
||||
bytes.clear();
|
||||
return false;
|
||||
}
|
||||
bytes = file->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2RomDevice::fileSize(std::string_view ps2Path, uint64_t &size) const
|
||||
{
|
||||
const auto file = m_files.find(normalizePath(ps2Path));
|
||||
if (file == m_files.end())
|
||||
{
|
||||
size = 0u;
|
||||
return false;
|
||||
}
|
||||
size = file->second.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2RomDevice::contains(std::string_view ps2Path) const
|
||||
{
|
||||
return m_files.contains(normalizePath(ps2Path));
|
||||
}
|
||||
|
||||
std::string PS2RomDevice::normalizePath(std::string_view path)
|
||||
{
|
||||
constexpr std::string_view prefix = "rom0:";
|
||||
if (path.size() >= prefix.size() && equalsIgnoreCaseAscii(path.substr(0, prefix.size()), prefix))
|
||||
path.remove_prefix(prefix.size());
|
||||
while (!path.empty() && (path.front() == '/' || path.front() == '\\'))
|
||||
path.remove_prefix(1u);
|
||||
|
||||
std::string normalized(path);
|
||||
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
||||
std::transform(normalized.begin(), normalized.end(), normalized.begin(), [](unsigned char value)
|
||||
{ return static_cast<char>(std::tolower(value)); });
|
||||
return normalized;
|
||||
}
|
||||
|
||||
void PS2RomDevice::mountBaseProfile()
|
||||
{
|
||||
// TODO expose this to cmake
|
||||
constexpr char romVersion[] = "0200AC20040614";
|
||||
static_assert(sizeof(romVersion) - 1u == 14u);
|
||||
m_files[normalizePath("ROMVER")] = std::vector<uint8_t>(romVersion, romVersion + 14u);
|
||||
}
|
||||
|
||||
void PS2RomDevice::mountFiles(const std::unordered_map<std::string, std::vector<uint8_t>> &files)
|
||||
{
|
||||
for (const auto &[path, bytes] : files)
|
||||
m_files[normalizePath(path)] = bytes;
|
||||
}
|
||||
@@ -630,6 +630,36 @@ ps2x::iop::DebugSnapshot PS2Runtime::iopDebugSnapshot() const
|
||||
return m_iopSubsystem->debugSnapshot();
|
||||
}
|
||||
|
||||
uint32_t PS2Runtime::allocateIopMemory(uint32_t size, uint32_t alignment)
|
||||
{
|
||||
return m_iopSubsystem ? m_iopSubsystem->allocateMemory(size, alignment) : 0u;
|
||||
}
|
||||
|
||||
bool PS2Runtime::freeIopMemory(uint32_t address)
|
||||
{
|
||||
return m_iopSubsystem && m_iopSubsystem->freeMemory(address);
|
||||
}
|
||||
|
||||
bool PS2Runtime::readIopMemory(uint32_t address, void *destination, size_t size) const
|
||||
{
|
||||
return m_iopSubsystem && m_iopSubsystem->readMemory(address, destination, size);
|
||||
}
|
||||
|
||||
bool PS2Runtime::writeIopMemory(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
return m_iopSubsystem && m_iopSubsystem->writeMemory(address, source, size);
|
||||
}
|
||||
|
||||
bool PS2Runtime::zeroIopMemory(uint32_t address, size_t size)
|
||||
{
|
||||
return m_iopSubsystem && m_iopSubsystem->zeroMemory(address, size);
|
||||
}
|
||||
|
||||
bool PS2Runtime::isIopMemoryRange(uint32_t address, size_t size) const
|
||||
{
|
||||
return m_iopSubsystem && m_iopSubsystem->isMemoryRange(address, size);
|
||||
}
|
||||
|
||||
bool PS2Runtime::syncCoreSubsystems()
|
||||
{
|
||||
uint8_t *const rdram = m_memory.getRDRAM();
|
||||
@@ -983,6 +1013,12 @@ bool PS2Runtime::loadELF(const std::string &elfPath)
|
||||
identity.elfName = module.name;
|
||||
identity.entryPoint = m_cpuContext.pc;
|
||||
identity.crc32 = elfCrc32;
|
||||
std::string romError;
|
||||
if (!m_romDevice.configure(identity, &romError))
|
||||
{
|
||||
std::cerr << "[ROM0] failed to configure profile: " << romError << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::string iopError;
|
||||
if (!m_iopSubsystem->configure(identity, &iopError))
|
||||
{
|
||||
@@ -1374,7 +1410,8 @@ bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
|
||||
if (policy == MissingFunctionPolicy::ContinueToTarget)
|
||||
{
|
||||
ctx->pc = targetPc;
|
||||
return true;
|
||||
// if you need the app to keep open to open debug pannel change this to false
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2403,6 +2440,7 @@ void PS2Runtime::run()
|
||||
<< " gsw=" << curGs
|
||||
<< " vif=" << curVif
|
||||
<< std::endl);
|
||||
|
||||
}
|
||||
});
|
||||
uint32_t presentWidth = FB_WIDTH;
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
#include "runtime/ps2_vfs.h"
|
||||
|
||||
#include "runtime/ps2_memory.h"
|
||||
#include "runtime/ps2_rom_device.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
class HostOpenFile final : public IPS2OpenFile
|
||||
{
|
||||
public:
|
||||
explicit HostOpenFile(FILE *file) : m_file(file) {}
|
||||
~HostOpenFile() override
|
||||
{
|
||||
if (m_file)
|
||||
std::fclose(m_file);
|
||||
}
|
||||
|
||||
int64_t read(void *destination, size_t size) override
|
||||
{
|
||||
if ((!destination && size != 0u) || !m_file)
|
||||
return -1;
|
||||
const size_t bytes = std::fread(destination, 1u, size, m_file);
|
||||
if (bytes < size && std::ferror(m_file))
|
||||
{
|
||||
std::clearerr(m_file);
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int64_t>(bytes);
|
||||
}
|
||||
|
||||
int64_t write(const void *source, size_t size) override
|
||||
{
|
||||
if ((!source && size != 0u) || !m_file)
|
||||
return -1;
|
||||
const size_t bytes = std::fwrite(source, 1u, size, m_file);
|
||||
if (bytes < size && std::ferror(m_file))
|
||||
{
|
||||
std::clearerr(m_file);
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int64_t>(bytes);
|
||||
}
|
||||
|
||||
int64_t seek(int64_t offset, int whence) override
|
||||
{
|
||||
if (!m_file || offset < std::numeric_limits<long>::min() || offset > std::numeric_limits<long>::max() || std::fseek(m_file, static_cast<long>(offset), whence) != 0)
|
||||
return -1;
|
||||
const long position = std::ftell(m_file);
|
||||
return position < 0 ? -1 : static_cast<int64_t>(position);
|
||||
}
|
||||
|
||||
private:
|
||||
FILE *m_file = nullptr;
|
||||
};
|
||||
|
||||
class MemoryOpenFile final : public IPS2OpenFile
|
||||
{
|
||||
public:
|
||||
explicit MemoryOpenFile(std::vector<uint8_t> bytes) : m_bytes(std::move(bytes)) {}
|
||||
|
||||
int64_t read(void *destination, size_t size) override
|
||||
{
|
||||
if (!destination && size != 0u)
|
||||
return -1;
|
||||
const size_t available = m_position < m_bytes.size() ? m_bytes.size() - m_position : 0u;
|
||||
const size_t count = std::min(size, available);
|
||||
if (count != 0u)
|
||||
std::memcpy(destination, m_bytes.data() + m_position, count);
|
||||
m_position += count;
|
||||
return static_cast<int64_t>(count);
|
||||
}
|
||||
|
||||
int64_t write(const void *, size_t) override
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t seek(int64_t offset, int whence) override
|
||||
{
|
||||
int64_t base = 0;
|
||||
if (whence == SEEK_CUR)
|
||||
base = static_cast<int64_t>(m_position);
|
||||
else if (whence == SEEK_END)
|
||||
base = static_cast<int64_t>(m_bytes.size());
|
||||
else if (whence != SEEK_SET)
|
||||
return -1;
|
||||
|
||||
const int64_t position = base + offset;
|
||||
if (position < 0 || static_cast<uint64_t>(position) > m_bytes.size())
|
||||
return -1;
|
||||
m_position = static_cast<size_t>(position);
|
||||
return position;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> m_bytes;
|
||||
size_t m_position = 0u;
|
||||
};
|
||||
|
||||
const char *hostMode(uint32_t flags)
|
||||
{
|
||||
const bool read = (flags & PS2_FIO_O_RDONLY) != 0u || (flags & PS2_FIO_O_RDWR) == PS2_FIO_O_RDWR;
|
||||
const bool write = (flags & PS2_FIO_O_WRONLY) != 0u || (flags & PS2_FIO_O_RDWR) == PS2_FIO_O_RDWR;
|
||||
const bool create = (flags & PS2_FIO_O_CREAT) != 0u;
|
||||
const bool truncate = (flags & PS2_FIO_O_TRUNC) != 0u;
|
||||
const bool append = (flags & PS2_FIO_O_APPEND) != 0u;
|
||||
|
||||
if (read && write)
|
||||
{
|
||||
if (truncate)
|
||||
return "w+b";
|
||||
if (append)
|
||||
return "a+b";
|
||||
return "r+b";
|
||||
}
|
||||
if (write)
|
||||
{
|
||||
if (append)
|
||||
return "ab";
|
||||
if (create || truncate)
|
||||
return "wb";
|
||||
return "r+b";
|
||||
}
|
||||
return "rb";
|
||||
}
|
||||
|
||||
bool safeRelativePath(std::string_view suffix, std::filesystem::path &relative)
|
||||
{
|
||||
relative = std::filesystem::path(suffix).lexically_normal();
|
||||
if (relative.is_absolute() || relative.has_root_name())
|
||||
return false;
|
||||
for (const auto &part : relative)
|
||||
{
|
||||
if (part == "..")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::time_t toTimeT(std::filesystem::file_time_type value)
|
||||
{
|
||||
const auto systemValue = std::chrono::time_point_cast<std::chrono::system_clock::duration>(value - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return std::chrono::system_clock::to_time_t(systemValue);
|
||||
}
|
||||
}
|
||||
|
||||
PS2Vfs::~PS2Vfs() = default;
|
||||
|
||||
int32_t PS2Vfs::open(std::string_view path, uint32_t flags, const PS2VfsMounts &mounts, const PS2RomDevice &rom)
|
||||
{
|
||||
const ps2x::iop::ParsedPs2Path parsed = ps2x::iop::parsePs2Path(path);
|
||||
if (!parsed)
|
||||
return -1;
|
||||
|
||||
std::unique_ptr<IPS2OpenFile> file;
|
||||
if (parsed.device == ps2x::iop::Ps2PathDevice::Rom0)
|
||||
{
|
||||
const uint32_t access = flags & PS2_FIO_O_RDWR;
|
||||
if (access != PS2_FIO_O_RDONLY || (flags & (PS2_FIO_O_CREAT | PS2_FIO_O_TRUNC)) != 0u)
|
||||
return -1;
|
||||
std::vector<uint8_t> bytes;
|
||||
if (!rom.readFile(parsed.path, bytes))
|
||||
return -1;
|
||||
file = std::make_unique<MemoryOpenFile>(std::move(bytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::filesystem::path hostPath;
|
||||
if (!resolveHostPath(path, mounts, hostPath))
|
||||
return -1;
|
||||
|
||||
std::error_code existsError;
|
||||
const bool exists = std::filesystem::exists(hostPath, existsError);
|
||||
if (existsError || (exists && (flags & (PS2_FIO_O_CREAT | PS2_FIO_O_EXCL)) == (PS2_FIO_O_CREAT | PS2_FIO_O_EXCL)))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *stream = std::fopen(hostPath.string().c_str(), hostMode(flags));
|
||||
const uint32_t access = flags & PS2_FIO_O_RDWR;
|
||||
if (!stream && !exists && (flags & PS2_FIO_O_CREAT) != 0u &&
|
||||
access == PS2_FIO_O_RDWR &&
|
||||
(flags & (PS2_FIO_O_TRUNC | PS2_FIO_O_APPEND)) == 0u)
|
||||
{
|
||||
stream = std::fopen(hostPath.string().c_str(), "w+b");
|
||||
}
|
||||
if (!stream)
|
||||
return -1;
|
||||
file = std::make_unique<HostOpenFile>(stream);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_nextDescriptor < 3)
|
||||
m_nextDescriptor = 3;
|
||||
const int32_t descriptor = m_nextDescriptor++;
|
||||
m_descriptors.emplace(descriptor, OpenDescriptor{std::move(file), parsed.deviceName, std::string(path)});
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
int32_t PS2Vfs::close(int32_t descriptor)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_descriptors.erase(descriptor) == 1u ? 0 : -1;
|
||||
}
|
||||
|
||||
int64_t PS2Vfs::read(int32_t descriptor, void *destination, size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto found = m_descriptors.find(descriptor);
|
||||
return found == m_descriptors.end() ? -1 : found->second.file->read(destination, size);
|
||||
}
|
||||
|
||||
int64_t PS2Vfs::write(int32_t descriptor, const void *source, size_t size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto found = m_descriptors.find(descriptor);
|
||||
return found == m_descriptors.end() ? -1 : found->second.file->write(source, size);
|
||||
}
|
||||
|
||||
int64_t PS2Vfs::seek(int32_t descriptor, int64_t offset, int whence)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto found = m_descriptors.find(descriptor);
|
||||
return found == m_descriptors.end() ? -1 : found->second.file->seek(offset, whence);
|
||||
}
|
||||
|
||||
bool PS2Vfs::stat(std::string_view path, const PS2VfsMounts &mounts, const PS2RomDevice &rom, PS2VfsStat &result) const
|
||||
{
|
||||
result = {};
|
||||
const ps2x::iop::ParsedPs2Path parsed = ps2x::iop::parsePs2Path(path);
|
||||
if (!parsed)
|
||||
return false;
|
||||
if (parsed.device == ps2x::iop::Ps2PathDevice::Rom0)
|
||||
{
|
||||
if (!rom.fileSize(parsed.path, result.size))
|
||||
return false;
|
||||
result.readOnly = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path hostPath;
|
||||
if (!resolveHostPath(path, mounts, hostPath))
|
||||
return false;
|
||||
std::error_code error;
|
||||
const auto status = std::filesystem::status(hostPath, error);
|
||||
if (error || !std::filesystem::exists(status))
|
||||
return false;
|
||||
result.directory = std::filesystem::is_directory(status);
|
||||
if (!result.directory)
|
||||
{
|
||||
result.size = std::filesystem::file_size(hostPath, error);
|
||||
if (error)
|
||||
return false;
|
||||
}
|
||||
const auto modified = std::filesystem::last_write_time(hostPath, error);
|
||||
if (!error)
|
||||
result.created = result.accessed = result.modified = toTimeT(modified);
|
||||
result.readOnly = (status.permissions() & std::filesystem::perms::owner_write) == std::filesystem::perms::none;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PS2Vfs::resolveHostPath(std::string_view path, const PS2VfsMounts &mounts, std::filesystem::path &result) const
|
||||
{
|
||||
result.clear();
|
||||
const ps2x::iop::ParsedPs2Path parsed = ps2x::iop::parsePs2Path(path);
|
||||
if (!parsed || parsed.device == ps2x::iop::Ps2PathDevice::Rom0)
|
||||
return false;
|
||||
if (parsed.device == ps2x::iop::Ps2PathDevice::NativeHost)
|
||||
{
|
||||
result = std::filesystem::path(parsed.path).lexically_normal();
|
||||
return !result.empty();
|
||||
}
|
||||
|
||||
std::filesystem::path base;
|
||||
switch (parsed.device)
|
||||
{
|
||||
case ps2x::iop::Ps2PathDevice::Host:
|
||||
base = mounts.hostRoot;
|
||||
break;
|
||||
case ps2x::iop::Ps2PathDevice::Cdrom:
|
||||
base = mounts.cdRoot;
|
||||
break;
|
||||
case ps2x::iop::Ps2PathDevice::MemoryCard0:
|
||||
base = mounts.memoryCard0Root;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
std::filesystem::path relative;
|
||||
if (base.empty() || !safeRelativePath(parsed.path, relative))
|
||||
return false;
|
||||
result = (base / relative).lexically_normal();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<PS2VfsDescriptorInfo> PS2Vfs::descriptors() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::vector<PS2VfsDescriptorInfo> result;
|
||||
result.reserve(m_descriptors.size());
|
||||
for (const auto &[descriptor, entry] : m_descriptors)
|
||||
result.push_back({descriptor, entry.device, entry.path});
|
||||
return result;
|
||||
}
|
||||
@@ -30,18 +30,54 @@ namespace
|
||||
{
|
||||
constexpr uint8_t kGifFmtImage = 2u;
|
||||
|
||||
uint32_t gifImageQwcFromTag(const uint8_t *data, uint32_t sizeBytes)
|
||||
uint32_t pendingGifImageQwc(const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
if (!data || sizeBytes < 16u)
|
||||
return 0u;
|
||||
|
||||
uint64_t tagLo = 0u;
|
||||
std::memcpy(&tagLo, data, sizeof(tagLo));
|
||||
const uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3u);
|
||||
if (flg != kGifFmtImage)
|
||||
return 0u;
|
||||
uint32_t offset = 0u;
|
||||
while (offset + 16u <= sizeBytes)
|
||||
{
|
||||
uint64_t tagLo = 0u;
|
||||
std::memcpy(&tagLo, data + offset, sizeof(tagLo));
|
||||
offset += 16u;
|
||||
|
||||
return static_cast<uint32_t>(tagLo & 0x7FFFu);
|
||||
const uint32_t nloop = static_cast<uint32_t>(tagLo & 0x7FFFu);
|
||||
const uint8_t flg = static_cast<uint8_t>((tagLo >> 58) & 0x3u);
|
||||
uint32_t nreg = static_cast<uint32_t>((tagLo >> 60) & 0xFu);
|
||||
if (nreg == 0u)
|
||||
nreg = 16u;
|
||||
|
||||
uint64_t payloadBytes = 0u;
|
||||
if (flg == 0u) // PACKED
|
||||
{
|
||||
payloadBytes = static_cast<uint64_t>(nloop) * nreg * 16ull;
|
||||
}
|
||||
else if (flg == 1u) // REGLIST, padded to a quadword
|
||||
{
|
||||
payloadBytes = static_cast<uint64_t>(nloop) * nreg * 8ull;
|
||||
payloadBytes = (payloadBytes + 15ull) & ~15ull;
|
||||
}
|
||||
else if (flg == kGifFmtImage)
|
||||
{
|
||||
payloadBytes = static_cast<uint64_t>(nloop) * 16ull;
|
||||
const uint64_t availableBytes = sizeBytes - offset;
|
||||
if (payloadBytes > availableBytes)
|
||||
{
|
||||
return static_cast<uint32_t>((payloadBytes - availableBytes) / 16ull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
if (payloadBytes > static_cast<uint64_t>(sizeBytes - offset))
|
||||
return 0u;
|
||||
offset += static_cast<uint32_t>(payloadBytes);
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,11 +320,7 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
(static_cast<uint64_t>(kGifFmtImage) << 58);
|
||||
std::memcpy(imagePacket.data(), &imageTag, sizeof(imageTag));
|
||||
std::memcpy(imagePacket.data() + 16u, data + pos, static_cast<size_t>(chunkQw) * 16u);
|
||||
submitGifPacket(GifPathId::Path2,
|
||||
imagePacket.data(),
|
||||
static_cast<uint32_t>(imagePacket.size()),
|
||||
true,
|
||||
m_vif1PendingPath2DirectHl);
|
||||
submitGifPacket(GifPathId::Path2, imagePacket.data(), static_cast<uint32_t>(imagePacket.size()), true, m_vif1PendingPath2DirectHl);
|
||||
|
||||
pos += chunkQw * 16u;
|
||||
m_vif1PendingPath2ImageQwc -= chunkQw;
|
||||
@@ -372,9 +404,6 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
uint32_t startPC = (uint32_t)imm * 8u;
|
||||
|
||||
// Values visible to the VU program for this MSCAL.
|
||||
// DobieStation semantics: ITOP = ITOPS; TOP = current TOPS;
|
||||
// then TOPS/DBF are prepared for the next buffer.
|
||||
const uint32_t runTop = vif1_regs.tops & 0x3FFu;
|
||||
const uint32_t runItop = vif1_regs.itops & 0x3FFu;
|
||||
vif1_regs.top = runTop;
|
||||
@@ -438,8 +467,6 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
else if (opcode == VIF_MPG)
|
||||
{
|
||||
uint32_t destAddr = (uint32_t)imm * 8u;
|
||||
// VIF MPG semantics: NUM==0 means 256 instructions (2048 bytes).
|
||||
// MPG payload is instruction-packed and should not be QW-aligned.
|
||||
const uint32_t instructionCount = (num == 0u) ? 256u : static_cast<uint32_t>(num);
|
||||
const uint32_t mpgBytes = instructionCount * 8u;
|
||||
if (m_vu1Code && destAddr < PS2_VU1_CODE_SIZE && mpgBytes > 0)
|
||||
@@ -473,15 +500,11 @@ void PS2Memory::processVIF1Data(const uint8_t *data, uint32_t sizeBytes)
|
||||
const bool directHl = (opcode == VIF_DIRECTHL);
|
||||
submitGifPacket(GifPathId::Path2, data + pos, qwCount * 16, true, directHl);
|
||||
|
||||
const uint32_t imageQw = gifImageQwcFromTag(data + pos, qwCount * 16u);
|
||||
if (imageQw != 0u)
|
||||
const uint32_t pendingImageQw = pendingGifImageQwc(data + pos, qwCount * 16u);
|
||||
if (pendingImageQw != 0u)
|
||||
{
|
||||
const uint32_t inlineImageQw = (qwCount > 0u) ? (qwCount - 1u) : 0u;
|
||||
if (imageQw > inlineImageQw)
|
||||
{
|
||||
m_vif1PendingPath2ImageQwc = imageQw - inlineImageQw;
|
||||
m_vif1PendingPath2DirectHl = directHl;
|
||||
}
|
||||
m_vif1PendingPath2ImageQwc = pendingImageQw;
|
||||
m_vif1PendingPath2DirectHl = directHl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user