Files
PS2Recomp/ps2xRuntime/src/lib/ps2_iop_host.cpp
T
Ran-j c8c86661fa 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.
2026-09-02 18:05:55 -03:00

533 lines
15 KiB
C++

#include "ps2_iop_host.h"
#include "ps2_runtime.h"
#include "ps2_stubs.h"
#include "Kernel/Stubs/SIF.h"
#include "runtime/ps2_memory.h"
#include "Kernel/Stubs/MemoryCard.h"
#include "Kernel/Syscalls/Common.h"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <limits>
#include <utility>
#if !defined(_WIN32)
#include <sys/types.h>
#endif
PS2IopHostAdapter::CallScope::CallScope(PS2IopHostAdapter &owner, R5900Context *context, uint8_t *rdram)
: m_lock(owner.m_callMutex),
m_owner(&owner),
m_previousContext(owner.m_activeContext),
m_previousRdram(owner.m_activeRdram),
m_previousToken(owner.m_activeToken)
{
m_token = owner.m_nextToken++;
if (m_token == 0)
{
m_token = owner.m_nextToken++;
}
owner.m_activeContext = context;
owner.m_activeRdram = rdram;
owner.m_activeToken = m_token;
}
PS2IopHostAdapter::CallScope::~CallScope()
{
release();
}
PS2IopHostAdapter::CallScope::CallScope(CallScope &&other) noexcept
: m_lock(std::move(other.m_lock)),
m_owner(std::exchange(other.m_owner, nullptr)),
m_previousContext(other.m_previousContext),
m_previousRdram(other.m_previousRdram),
m_previousToken(other.m_previousToken),
m_token(other.m_token)
{
}
PS2IopHostAdapter::CallScope &PS2IopHostAdapter::CallScope::operator=(CallScope &&other) noexcept
{
if (this != &other)
{
release();
m_lock = std::move(other.m_lock);
m_owner = std::exchange(other.m_owner, nullptr);
m_previousContext = other.m_previousContext;
m_previousRdram = other.m_previousRdram;
m_previousToken = other.m_previousToken;
m_token = other.m_token;
}
return *this;
}
void PS2IopHostAdapter::CallScope::release()
{
if (m_owner)
{
m_owner->m_activeContext = m_previousContext;
m_owner->m_activeRdram = m_previousRdram;
m_owner->m_activeToken = m_previousToken;
m_owner = nullptr;
}
}
PS2IopHostAdapter::PS2IopHostAdapter(PS2Runtime &runtime)
: m_runtime(runtime)
{
}
PS2IopHostAdapter::~PS2IopHostAdapter()
{
std::lock_guard<std::mutex> lock(m_hostFileMutex);
for (auto &[handle, file] : m_hostFiles)
{
(void)handle;
if (file.stream)
{
std::fclose(file.stream);
}
}
m_hostFiles.clear();
}
PS2IopHostAdapter::CallScope PS2IopHostAdapter::enterCall(R5900Context *context, uint8_t *rdram)
{
return CallScope(*this, context, rdram);
}
bool PS2IopHostAdapter::guestRange(uint32_t address, size_t size, uint8_t *&begin) const
{
begin = nullptr;
uint8_t *const rdram = m_activeRdram
? m_activeRdram
: m_runtime.memory().getRDRAM();
if (!rdram)
{
return false;
}
if (size == 0)
{
begin = getMemPtr(rdram, address);
return begin != nullptr;
}
if (size - 1 > std::numeric_limits<uint32_t>::max() - address)
{
return false;
}
uint8_t *const first = getMemPtr(rdram, address);
uint8_t *const last = getMemPtr(rdram, address + static_cast<uint32_t>(size - 1));
if (!first || !last || last < first || static_cast<size_t>(last - first) != size - 1)
{
return false;
}
begin = first;
return true;
}
bool PS2IopHostAdapter::readGuest(uint32_t address, void *destination, size_t size) const
{
if (!destination && size != 0)
{
return false;
}
uint8_t *source = nullptr;
if (!guestRange(address, size, source))
{
return false;
}
if (size != 0)
{
std::memcpy(destination, source, size);
}
return true;
}
bool PS2IopHostAdapter::writeGuest(uint32_t address, const void *source, size_t size)
{
if (!source && size != 0)
{
return false;
}
uint8_t *destination = nullptr;
if (!guestRange(address, size, destination))
{
return false;
}
if (size != 0)
{
uint8_t *const rdram = m_activeRdram ? m_activeRdram : m_runtime.memory().getRDRAM();
ps2TraceGuestRangeWrite(rdram, address, static_cast<uint32_t>(size), "IopHost::writeGuest", nullptr);
std::memcpy(destination, source, size);
}
return true;
}
bool PS2IopHostAdapter::zeroGuest(uint32_t address, size_t size)
{
uint8_t *destination = nullptr;
if (!guestRange(address, size, destination))
{
return false;
}
if (size != 0)
{
uint8_t *const rdram = m_activeRdram ? m_activeRdram : m_runtime.memory().getRDRAM();
ps2TraceGuestRangeWrite(rdram, address, static_cast<uint32_t>(size), "IopHost::zeroGuest", nullptr);
std::memset(destination, 0, size);
}
return true;
}
bool PS2IopHostAdapter::normalizeGuestAddress(uint32_t address, uint32_t &normalized) const
{
bool scratchpad = false;
if (!ps2ResolveGuestPointer(address, normalized, scratchpad) || scratchpad)
{
normalized = 0;
return false;
}
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
? m_activeRdram
: m_runtime.memory().getRDRAM();
if (!rdram)
{
return 0;
}
return kind == ps2x::iop::IopHandleKind::RpcPacket
? rpcAllocPacketAddr(rdram)
: rpcAllocServerAddr(rdram);
}
uint32_t PS2IopHostAdapter::allocateGuest(uint32_t size, uint32_t alignment)
{
return m_runtime.guestMalloc(size, alignment);
}
void PS2IopHostAdapter::freeGuest(uint32_t address)
{
m_runtime.guestFree(address);
}
void PS2IopHostAdapter::audioCommand(uint32_t sid,
uint32_t function,
ps2x::iop::GuestBuffer send,
ps2x::iop::GuestBuffer receive)
{
uint8_t *sendPointer = nullptr;
uint8_t *receivePointer = nullptr;
if (send.address && !guestRange(send.address, send.size, sendPointer))
{
sendPointer = nullptr;
}
if (receive.address && !guestRange(receive.address, receive.size, receivePointer))
{
receivePointer = nullptr;
}
m_runtime.audioBackend().onSoundCommand(sid,
function,
sendPointer,
send.size,
receivePointer,
receive.size);
}
std::string PS2IopHostAdapter::hostPath(ps2x::iop::HostPathKind kind) const
{
const PS2Runtime::IoPaths &paths = PS2Runtime::getIoPaths();
switch (kind)
{
case ps2x::iop::HostPathKind::CdRoot:
return paths.cdRoot.string();
case ps2x::iop::HostPathKind::CdImage:
return paths.cdImage.string();
case ps2x::iop::HostPathKind::HostRoot:
return paths.hostRoot.string();
case ps2x::iop::HostPathKind::MemoryCardRoot:
return paths.mcRoot.string();
case ps2x::iop::HostPathKind::ElfDirectory:
default:
return paths.elfDirectory.string();
}
}
std::string PS2IopHostAdapter::translateGuestPath(std::string_view path) const
{
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)
{
if (path.empty())
{
return 0u;
}
const std::filesystem::path hostPath{std::string(path)};
#if defined(_WIN32)
std::FILE *stream = ::_wfopen(hostPath.c_str(), L"rb");
#else
std::FILE *stream = std::fopen(hostPath.string().c_str(), "rb");
#endif
if (!stream)
{
return 0u;
}
std::error_code error;
const uint64_t size = std::filesystem::file_size(hostPath, error);
if (error)
{
std::fclose(stream);
return 0u;
}
std::lock_guard<std::mutex> lock(m_hostFileMutex);
uint64_t handle = m_nextHostFileHandle++;
if (handle == 0u)
{
handle = m_nextHostFileHandle++;
}
m_hostFiles.emplace(handle, HostFile{stream, size});
return handle;
}
bool PS2IopHostAdapter::hostFileSize(uint64_t handle, uint64_t &size) const
{
size = 0u;
std::lock_guard<std::mutex> lock(m_hostFileMutex);
const auto it = m_hostFiles.find(handle);
if (it == m_hostFiles.end() || !it->second.stream)
{
return false;
}
size = it->second.size;
return true;
}
bool PS2IopHostAdapter::readHostFile(uint64_t handle,
uint64_t offset,
void *destination,
size_t size,
size_t &bytesRead)
{
bytesRead = 0u;
if (!destination && size != 0u)
{
return false;
}
std::lock_guard<std::mutex> lock(m_hostFileMutex);
const auto it = m_hostFiles.find(handle);
if (it == m_hostFiles.end() || !it->second.stream)
{
return false;
}
#if defined(_WIN32)
if (offset > static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) ||
_fseeki64(it->second.stream, static_cast<int64_t>(offset), SEEK_SET) != 0)
#else
if (offset > static_cast<uint64_t>(std::numeric_limits<off_t>::max()) ||
fseeko(it->second.stream, static_cast<off_t>(offset), SEEK_SET) != 0)
#endif
{
return false;
}
bytesRead = size == 0u
? 0u
: std::fread(destination, 1u, size, it->second.stream);
if (bytesRead < size && std::ferror(it->second.stream))
{
std::clearerr(it->second.stream);
return false;
}
return true;
}
void PS2IopHostAdapter::closeHostFile(uint64_t handle)
{
std::FILE *stream = nullptr;
{
std::lock_guard<std::mutex> lock(m_hostFileMutex);
const auto it = m_hostFiles.find(handle);
if (it == m_hostFiles.end())
{
return;
}
stream = it->second.stream;
m_hostFiles.erase(it);
}
if (stream)
{
std::fclose(stream);
}
}
int32_t PS2IopHostAdapter::memoryCard(const ps2x::iop::MemoryCardRequest &request)
{
using Handler = void (*)(uint8_t *, R5900Context *, PS2Runtime *);
Handler handler = nullptr;
switch (request.operation)
{
case ps2x::iop::MemoryCardOperation::Init:
handler = ps2_stubs::sceMcInit;
break;
case ps2x::iop::MemoryCardOperation::GetInfo:
handler = ps2_stubs::sceMcGetInfo;
break;
case ps2x::iop::MemoryCardOperation::Open:
handler = ps2_stubs::sceMcOpen;
break;
case ps2x::iop::MemoryCardOperation::Close:
handler = ps2_stubs::sceMcClose;
break;
case ps2x::iop::MemoryCardOperation::Seek:
handler = ps2_stubs::sceMcSeek;
break;
case ps2x::iop::MemoryCardOperation::Read:
handler = ps2_stubs::sceMcRead;
break;
case ps2x::iop::MemoryCardOperation::Write:
handler = ps2_stubs::sceMcWrite;
break;
case ps2x::iop::MemoryCardOperation::Flush:
handler = ps2_stubs::sceMcFlush;
break;
case ps2x::iop::MemoryCardOperation::Chdir:
handler = ps2_stubs::sceMcChdir;
break;
case ps2x::iop::MemoryCardOperation::GetDir:
handler = ps2_stubs::sceMcGetDir;
break;
case ps2x::iop::MemoryCardOperation::SetFileInfo:
handler = ps2_stubs::sceMcSetFileInfo;
break;
case ps2x::iop::MemoryCardOperation::Delete:
handler = ps2_stubs::sceMcDelete;
break;
case ps2x::iop::MemoryCardOperation::Format:
handler = ps2_stubs::sceMcFormat;
break;
case ps2x::iop::MemoryCardOperation::Unformat:
handler = ps2_stubs::sceMcUnformat;
break;
case ps2x::iop::MemoryCardOperation::Mkdir:
handler = ps2_stubs::sceMcMkdir;
break;
}
if (!handler)
{
return -1;
}
R5900Context context{};
for (size_t i = 0; i < 4u; ++i)
{
setRegU32(&context, static_cast<int>(4 + i), request.arguments[i]);
}
// EE n32 ABI: the fifth argument travels in $t0, matching the sceMc* stubs.
setRegU32(&context, 8, request.arguments[4]);
handler(m_activeRdram ? m_activeRdram : m_runtime.memory().getRDRAM(),
&context,
&m_runtime);
return ps2_stubs::getMemoryCardDebugSnapshot().lastResult;
}
bool PS2IopHostAdapter::hasGuestFunction(uint32_t address) const
{
return m_runtime.hasFunction(address);
}
bool PS2IopHostAdapter::invokeGuestFunction(uint64_t callToken,
uint32_t address,
uint32_t a0,
uint32_t a1,
uint32_t a2,
uint32_t a3,
uint32_t *resultAddress)
{
(void)callToken;
(void)address;
(void)a0;
(void)a1;
(void)a2;
(void)a3;
if (resultAddress)
{
*resultAddress = 0u;
}
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]";
if (level == ps2x::iop::LogLevel::Warning)
{
prefix = "[ps2xIOP:warning]";
}
else if (level == ps2x::iop::LogLevel::Error)
{
prefix = "[ps2xIOP:error]";
}
std::cerr << prefix << ' ' << message << std::endl;
}