mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-28 17:39:16 -04:00
feat: remove recompiled version of GetRomName
refactor: split IOP emulator logic feat: added more HLE IOP modules feat: added ps2_path
This commit is contained in:
+23
-8
@@ -6,16 +6,26 @@ option(PS2X_IOP_ENABLE_PLUGINS "Enable dynamic ps2xIOP plugins" OFF)
|
||||
option(PS2X_IOP_BUILD_TESTS "Build ps2xIOP emulator smoke tests" OFF)
|
||||
|
||||
add_library(ps2_iop STATIC
|
||||
src/ps2_path.cpp
|
||||
src/iop_module_manager.cpp
|
||||
src/iop_subsystem.cpp
|
||||
src/emulator/iop_emulator.cpp
|
||||
src/emulator/iop_cdvd.cpp
|
||||
src/emulator/iop_cpu.cpp
|
||||
src/emulator/iop_imports.cpp
|
||||
src/emulator/iop_kernel.cpp
|
||||
src/emulator/iop_memory.cpp
|
||||
src/emulator/iop_module_loader.cpp
|
||||
src/emulator/iop_rpc.cpp
|
||||
src/emulator/iop_sysclib.cpp
|
||||
src/emulator/core/iop_cpu.cpp
|
||||
src/emulator/core/iop_kernel.cpp
|
||||
src/emulator/core/iop_memory.cpp
|
||||
src/emulator/services/iop_module_loader.cpp
|
||||
src/emulator/services/iop_rpc.cpp
|
||||
src/emulator/imports/iop_cdvd.cpp
|
||||
src/emulator/imports/iop_heaplib.cpp
|
||||
src/emulator/imports/iop_imports.cpp
|
||||
src/emulator/imports/iop_intrman.cpp
|
||||
src/emulator/imports/iop_ioman.cpp
|
||||
src/emulator/imports/iop_loadcore.cpp
|
||||
src/emulator/imports/iop_stdio.cpp
|
||||
src/emulator/imports/iop_sysclib.cpp
|
||||
src/emulator/imports/iop_sysmem.cpp
|
||||
src/emulator/imports/iop_timrman.cpp
|
||||
src/emulator/imports/iop_vblank.cpp
|
||||
src/builtin_profiles.cpp
|
||||
src/plugin_loader.cpp
|
||||
src/modules/dbcman.cpp
|
||||
@@ -53,6 +63,11 @@ if(PS2X_IOP_BUILD_TESTS)
|
||||
add_executable(ps2_iop_emulator_tests tests/iop_emulator_tests.cpp)
|
||||
target_link_libraries(ps2_iop_emulator_tests PRIVATE ps2_iop)
|
||||
add_test(NAME ps2_iop_emulator_tests COMMAND ps2_iop_emulator_tests)
|
||||
|
||||
add_executable(ps2_iop_import_tests tests/iop_import_tests.cpp)
|
||||
target_link_libraries(ps2_iop_import_tests PRIVATE ps2_iop)
|
||||
target_include_directories(ps2_iop_import_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
add_test(NAME ps2_iop_import_tests COMMAND ps2_iop_import_tests)
|
||||
endif()
|
||||
|
||||
install(TARGETS ps2_iop
|
||||
|
||||
@@ -62,6 +62,34 @@ namespace ps2x::iop
|
||||
virtual bool writeGuest(uint32_t address, const void *source, size_t size) = 0;
|
||||
virtual bool zeroGuest(uint32_t address, size_t size) = 0;
|
||||
virtual bool normalizeGuestAddress(uint32_t address, uint32_t &normalized) const = 0;
|
||||
|
||||
// IOP RAM is a distinct address space from the EE guest. TODO remove this later
|
||||
virtual bool readIopMemory(uint32_t address, void *destination, size_t size) const
|
||||
{
|
||||
(void)address;
|
||||
(void)destination;
|
||||
(void)size;
|
||||
return false;
|
||||
}
|
||||
virtual bool writeIopMemory(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
(void)address;
|
||||
(void)source;
|
||||
(void)size;
|
||||
return false;
|
||||
}
|
||||
virtual bool zeroIopMemory(uint32_t address, size_t size)
|
||||
{
|
||||
(void)address;
|
||||
(void)size;
|
||||
return false;
|
||||
}
|
||||
virtual bool normalizeIopAddress(uint32_t address, uint32_t &normalized) const
|
||||
{
|
||||
(void)address;
|
||||
normalized = 0u;
|
||||
return false;
|
||||
}
|
||||
virtual uint32_t allocateIopHandle(IopHandleKind kind) = 0;
|
||||
virtual uint32_t allocateGuest(uint32_t size, uint32_t alignment) = 0;
|
||||
virtual void freeGuest(uint32_t address) = 0;
|
||||
@@ -90,6 +118,18 @@ namespace ps2x::iop
|
||||
uint32_t a3,
|
||||
uint32_t *resultAddress) = 0;
|
||||
|
||||
// Deliver an IOP -> EE SIF command packet. The default keeps hosts
|
||||
// which do not emulate the EE command dispatcher source-compatible.
|
||||
virtual bool sendSifCommand(uint32_t commandId,
|
||||
const void *packet,
|
||||
size_t packetSize)
|
||||
{
|
||||
(void)commandId;
|
||||
(void)packet;
|
||||
(void)packetSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void log(LogLevel level, std::string_view message) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,14 @@ namespace ps2x::iop
|
||||
[[nodiscard]] RpcResult handleRpc(const RpcRequest &request);
|
||||
void onSifTransfer(const SifTransfer &transfer);
|
||||
|
||||
// Physical IOP RAM access shared by the emulator, SIF DMA, and HLE services. Addresses are IOP addresses.
|
||||
[[nodiscard]] uint32_t allocateMemory(uint32_t size, uint32_t alignment = 16u);
|
||||
[[nodiscard]] bool freeMemory(uint32_t address);
|
||||
[[nodiscard]] bool readMemory(uint32_t address, void *destination, size_t size) const;
|
||||
[[nodiscard]] bool writeMemory(uint32_t address, const void *source, size_t size);
|
||||
[[nodiscard]] bool zeroMemory(uint32_t address, size_t size);
|
||||
[[nodiscard]] bool isMemoryRange(uint32_t address, size_t size) const;
|
||||
|
||||
[[nodiscard]] DebugSnapshot debugSnapshot() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -139,6 +139,7 @@ namespace ps2x::iop
|
||||
std::string name;
|
||||
std::vector<uint32_t> sids;
|
||||
bool profileSpecific = false;
|
||||
bool active = true;
|
||||
std::vector<DebugMetric> metrics;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ps2x::iop
|
||||
{
|
||||
enum class Ps2PathDevice
|
||||
{
|
||||
Invalid,
|
||||
Host,
|
||||
Cdrom,
|
||||
MemoryCard0,
|
||||
Rom0,
|
||||
NativeHost,
|
||||
};
|
||||
|
||||
struct ParsedPs2Path
|
||||
{
|
||||
Ps2PathDevice device = Ps2PathDevice::Invalid;
|
||||
std::string deviceName;
|
||||
std::string path;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept
|
||||
{
|
||||
return device != Ps2PathDevice::Invalid;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] ParsedPs2Path parsePs2Path(std::string_view path);
|
||||
|
||||
// Returns a lower-case module/file leaf without an optional .irx suffix.
|
||||
[[nodiscard]] std::string ps2PathLeafKey(const ParsedPs2Path &path);
|
||||
[[nodiscard]] std::string ps2PathLeafKey(std::string_view path);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "iop_kernel.h"
|
||||
|
||||
#include "iop_memory.h"
|
||||
#include "iop_emulator_const.h"
|
||||
#include "../iop_emulator_const.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -502,6 +502,28 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
}
|
||||
|
||||
int IopKernel::createInternalEventFlag(uint32_t attr, uint32_t option, uint32_t bits)
|
||||
{
|
||||
EventFlag event;
|
||||
event.id = static_cast<int>(m_nextEventFlagId++);
|
||||
event.attr = attr;
|
||||
event.option = option;
|
||||
event.bits = bits;
|
||||
const int id = event.id;
|
||||
m_eventFlags.emplace(id, event);
|
||||
return id;
|
||||
}
|
||||
|
||||
bool IopKernel::setInternalEventFlag(int id, uint32_t bits)
|
||||
{
|
||||
const auto event = m_eventFlags.find(id);
|
||||
if (event == m_eventFlags.end())
|
||||
return false;
|
||||
event->second.bits |= bits;
|
||||
wakeEventWaiters(event->second);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IopKernel::dispatchEventImport(uint16_t ordinal, IopCpuState &cpu)
|
||||
{
|
||||
const auto setV0 = [&](int32_t value)
|
||||
@@ -514,13 +536,9 @@ namespace ps2x::iop::detail
|
||||
case 4:
|
||||
{
|
||||
const uint32_t descriptor = cpu.gpr[4];
|
||||
EventFlag event;
|
||||
event.id = static_cast<int>(m_nextEventFlagId++);
|
||||
event.attr = m_memory.read32(descriptor + 0u);
|
||||
event.option = m_memory.read32(descriptor + 4u);
|
||||
event.bits = m_memory.read32(descriptor + 8u);
|
||||
m_eventFlags.emplace(event.id, event);
|
||||
setV0(event.id);
|
||||
setV0(createInternalEventFlag(m_memory.read32(descriptor + 0u),
|
||||
m_memory.read32(descriptor + 4u),
|
||||
m_memory.read32(descriptor + 8u)));
|
||||
return true;
|
||||
}
|
||||
case 5:
|
||||
@@ -546,14 +564,11 @@ namespace ps2x::iop::detail
|
||||
case 6:
|
||||
case 7:
|
||||
{
|
||||
const auto event = m_eventFlags.find(static_cast<int>(cpu.gpr[4]));
|
||||
if (event == m_eventFlags.end())
|
||||
if (!setInternalEventFlag(static_cast<int>(cpu.gpr[4]), cpu.gpr[5]))
|
||||
{
|
||||
setV0(-1);
|
||||
return true;
|
||||
}
|
||||
event->second.bits |= cpu.gpr[5];
|
||||
wakeEventWaiters(event->second);
|
||||
setV0(0);
|
||||
return true;
|
||||
}
|
||||
@@ -54,6 +54,9 @@ namespace ps2x::iop::detail
|
||||
[[nodiscard]] bool dispatchSemaphoreImport(uint16_t ordinal, IopCpuState &cpu);
|
||||
[[nodiscard]] bool dispatchEventImport(uint16_t ordinal, IopCpuState &cpu);
|
||||
|
||||
[[nodiscard]] int createInternalEventFlag(uint32_t attr, uint32_t option, uint32_t bits);
|
||||
[[nodiscard]] bool setInternalEventFlag(int id, uint32_t bits);
|
||||
|
||||
void sleepCurrent(IopCpuState &cpu);
|
||||
void delayCurrentUntil(uint64_t wakeCycle, IopCpuState &cpu);
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
if ((phys & 3u) == 0u && isHardwareAddress(phys))
|
||||
return readHardware32(phys);
|
||||
|
||||
return static_cast<uint32_t>(read8(address)) |
|
||||
(static_cast<uint32_t>(read8(address + 1u)) << 8u) |
|
||||
(static_cast<uint32_t>(read8(address + 2u)) << 16u) |
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "iop_cdvd.h"
|
||||
|
||||
#include "iop_cpu.h"
|
||||
#include "iop_memory.h"
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_kernel.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "ps2x/iop/iop_host.h"
|
||||
#include "ps2x/iop/ps2_path.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -32,6 +34,11 @@ namespace ps2x::iop::detail
|
||||
constexpr uint32_t kCdvdInitExit = 5u;
|
||||
constexpr uint32_t kCdvdCallbackRead = 1u;
|
||||
constexpr uint32_t kCdvdCallbackSeek = 4u;
|
||||
constexpr uint32_t kCdvdInterruptReadyBits = 0x29u;
|
||||
constexpr uint32_t kEventFlagMulti = 2u;
|
||||
constexpr uint32_t kCdvdStreamTimeout = 5000u;
|
||||
constexpr uint32_t kCdvdSyncTimeout = 15000u;
|
||||
constexpr uint32_t kCdvdmanVersion = 0x0226u;
|
||||
|
||||
uint32_t alignSectors(uint64_t bytes)
|
||||
{
|
||||
@@ -109,6 +116,23 @@ namespace ps2x::iop::detail
|
||||
return static_cast<uint32_t>(std::max<uint64_t>(kSectorSize, alignSectors(cursor) * kSectorSize));
|
||||
}
|
||||
|
||||
std::string normalizedIsoComponent(std::string_view value)
|
||||
{
|
||||
std::string result(value);
|
||||
const size_t semicolon = result.rfind(';');
|
||||
if (semicolon != std::string::npos && semicolon + 1u < result.size() &&
|
||||
std::all_of(result.begin() + static_cast<std::ptrdiff_t>(semicolon + 1u), result.end(),
|
||||
[](unsigned char ch)
|
||||
{ return std::isdigit(ch) != 0; }))
|
||||
{
|
||||
result.resize(semicolon);
|
||||
}
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char ch)
|
||||
{ return static_cast<char>(std::toupper(ch)); });
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t writeDirectoryRecord(uint8_t *destination,
|
||||
uint32_t lsn,
|
||||
uint32_t size,
|
||||
@@ -151,8 +175,8 @@ namespace ps2x::iop::detail
|
||||
std::vector<size_t> children;
|
||||
};
|
||||
|
||||
Impl(IopHost &hostRef, IopMemory &memoryRef)
|
||||
: host(hostRef), memory(memoryRef)
|
||||
Impl(IopHost &hostRef, IopMemory &memoryRef, IopKernel &kernelRef)
|
||||
: host(hostRef), memory(memoryRef), kernel(kernelRef)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -169,6 +193,9 @@ namespace ps2x::iop::detail
|
||||
mediaMode = 0u;
|
||||
currentLsn = 0u;
|
||||
lastError = kCdvdErrorNone;
|
||||
streamFlag = 0u;
|
||||
lastReadTimeout = 0u;
|
||||
interruptEventFlagId = 0;
|
||||
virtualIsoBuilt = false;
|
||||
virtualIsoValid = false;
|
||||
completionCallback.reset();
|
||||
@@ -203,6 +230,7 @@ namespace ps2x::iop::detail
|
||||
case 6: // sceCdRead
|
||||
if (readSectors(a0, a1, a2))
|
||||
{
|
||||
signalCommandComplete();
|
||||
if (callback.address != 0u)
|
||||
{
|
||||
completionCallback = CompletionCallback{
|
||||
@@ -220,6 +248,7 @@ namespace ps2x::iop::detail
|
||||
case 7: // sceCdSeek
|
||||
currentLsn = a0;
|
||||
lastError = kCdvdErrorNone;
|
||||
signalCommandComplete();
|
||||
if (callback.address != 0u)
|
||||
{
|
||||
completionCallback = CompletionCallback{
|
||||
@@ -235,6 +264,10 @@ namespace ps2x::iop::detail
|
||||
cpu.gpr[2] = lastError;
|
||||
return true;
|
||||
|
||||
case 10: // sceCdSearchFile
|
||||
cpu.gpr[2] = searchFile(a0, a1) ? 1u : 0u;
|
||||
return true;
|
||||
|
||||
case 11: // sceCdSync
|
||||
cpu.gpr[2] = 0u;
|
||||
return true;
|
||||
@@ -259,6 +292,52 @@ namespace ps2x::iop::detail
|
||||
return true;
|
||||
}
|
||||
|
||||
case 50: // sceCdSC
|
||||
{
|
||||
const int32_t code = static_cast<int32_t>(a0);
|
||||
switch (code)
|
||||
{
|
||||
case -23: // Translate a logical sector for a dual-layer disc.
|
||||
// The host image exposes one continuous LSN space, so no layer offset is required.
|
||||
cpu.gpr[2] = a1 != 0u ? memory.read32(a1) : 0u;
|
||||
return true;
|
||||
case -18:
|
||||
lastReadTimeout = a1 != 0u ? memory.read32(a1) : 0u;
|
||||
cpu.gpr[2] = 0u;
|
||||
return true;
|
||||
case -17:
|
||||
cpu.gpr[2] = kCdvdStreamTimeout;
|
||||
return true;
|
||||
case -15:
|
||||
cpu.gpr[2] = kCdvdSyncTimeout;
|
||||
return true;
|
||||
case -11:
|
||||
cpu.gpr[2] = static_cast<uint32_t>(ensureInterruptEventFlag());
|
||||
return true;
|
||||
case -9:
|
||||
cpu.gpr[2] = kCdvdmanVersion;
|
||||
return true;
|
||||
case -2:
|
||||
lastError = a1 != 0u ? memory.read8(a1) : kCdvdErrorNone;
|
||||
cpu.gpr[2] = lastError;
|
||||
return true;
|
||||
case -1:
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if (a1 != 0u)
|
||||
memory.write32(a1, lastError & 0xFFu);
|
||||
if (code != -1)
|
||||
streamFlag = static_cast<uint32_t>(code);
|
||||
cpu.gpr[2] = streamFlag;
|
||||
return true;
|
||||
default:
|
||||
// sceCdSC is intentionally extensible; unsupported controls are no-ops in cdvdman.
|
||||
cpu.gpr[2] = 0u;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
case 75: // sceCdMmode
|
||||
mediaMode = a0;
|
||||
cpu.gpr[2] = 1u;
|
||||
@@ -277,6 +356,22 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
|
||||
private:
|
||||
int ensureInterruptEventFlag()
|
||||
{
|
||||
if (interruptEventFlagId == 0)
|
||||
{
|
||||
interruptEventFlagId = kernel.createInternalEventFlag(
|
||||
kEventFlagMulti, 0u, kCdvdInterruptReadyBits);
|
||||
}
|
||||
return interruptEventFlagId;
|
||||
}
|
||||
|
||||
void signalCommandComplete()
|
||||
{
|
||||
if (interruptEventFlagId != 0)
|
||||
(void)kernel.setInternalEventFlag(interruptEventFlagId, kCdvdInterruptReadyBits);
|
||||
}
|
||||
|
||||
void closeFiles()
|
||||
{
|
||||
if (imageHandle != 0u)
|
||||
@@ -463,6 +558,62 @@ namespace ps2x::iop::detail
|
||||
return true;
|
||||
}
|
||||
|
||||
IsoNode *findVirtualIsoNode(std::string_view guestPath)
|
||||
{
|
||||
if (!buildVirtualIso())
|
||||
return nullptr;
|
||||
|
||||
const ParsedPs2Path parsed = parsePs2Path(guestPath);
|
||||
if (!parsed || parsed.device != Ps2PathDevice::Cdrom)
|
||||
return nullptr;
|
||||
|
||||
size_t current = 0u;
|
||||
size_t begin = 0u;
|
||||
while (begin <= parsed.path.size())
|
||||
{
|
||||
const size_t end = parsed.path.find('/', begin);
|
||||
const size_t length = (end == std::string::npos) ? parsed.path.size() - begin : end - begin;
|
||||
const std::string_view component(parsed.path.data() + begin, length);
|
||||
begin = (end == std::string::npos) ? parsed.path.size() + 1u : end + 1u;
|
||||
|
||||
if (component.empty() || component == ".")
|
||||
continue;
|
||||
if (component == "..")
|
||||
return nullptr;
|
||||
|
||||
const std::string wanted = normalizedIsoComponent(component);
|
||||
const auto child = std::find_if(nodes[current].children.begin(), nodes[current].children.end(),
|
||||
[&](size_t childIndex)
|
||||
{
|
||||
return normalizedIsoComponent(nodes[childIndex].identifier) == wanted;
|
||||
});
|
||||
if (child == nodes[current].children.end())
|
||||
return nullptr;
|
||||
current = *child;
|
||||
}
|
||||
return &nodes[current];
|
||||
}
|
||||
|
||||
bool searchFile(uint32_t resultAddress, uint32_t nameAddress)
|
||||
{
|
||||
if (resultAddress == 0u || nameAddress == 0u)
|
||||
return false;
|
||||
|
||||
const std::string guestPath = memory.readString(nameAddress, 1024u);
|
||||
IsoNode *node = findVirtualIsoNode(guestPath);
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
// sceCdlFILE: lsn, size, name[16], date/flags[8].
|
||||
std::array<uint8_t, 32u> result{};
|
||||
writeLe32(result.data(), node->lsn);
|
||||
writeLe32(result.data() + 4u, node->size);
|
||||
const std::string leaf = normalizedIsoComponent(node->identifier);
|
||||
std::memcpy(result.data() + 8u, leaf.data(), std::min<size_t>(16u, leaf.size()));
|
||||
result[24u] = node->directory ? 2u : 0u;
|
||||
return memory.writeRam(resultAddress, result.data(), result.size());
|
||||
}
|
||||
|
||||
IsoNode *fileForSector(uint32_t lsn)
|
||||
{
|
||||
for (IsoNode &node : nodes)
|
||||
@@ -558,12 +709,16 @@ namespace ps2x::iop::detail
|
||||
|
||||
IopHost &host;
|
||||
IopMemory &memory;
|
||||
IopKernel &kernel;
|
||||
Callback callback;
|
||||
std::optional<CompletionCallback> completionCallback;
|
||||
bool initialized = false;
|
||||
uint32_t mediaMode = 0u;
|
||||
uint32_t currentLsn = 0u;
|
||||
uint32_t lastError = kCdvdErrorNone;
|
||||
uint32_t streamFlag = 0u;
|
||||
uint32_t lastReadTimeout = 0u;
|
||||
int interruptEventFlagId = 0;
|
||||
uint64_t imageHandle = 0u;
|
||||
bool virtualIsoBuilt = false;
|
||||
bool virtualIsoValid = false;
|
||||
@@ -572,8 +727,8 @@ namespace ps2x::iop::detail
|
||||
std::unordered_map<uint32_t, std::array<uint8_t, kSectorSize>> metadataSectors;
|
||||
};
|
||||
|
||||
IopCdvd::IopCdvd(IopHost &host, IopMemory &memory)
|
||||
: m_impl(std::make_unique<Impl>(host, memory))
|
||||
IopCdvd::IopCdvd(IopHost &host, IopMemory &memory, IopKernel &kernel)
|
||||
: m_impl(std::make_unique<Impl>(host, memory, kernel))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace ps2x::iop
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopKernel;
|
||||
class IopMemory;
|
||||
|
||||
class IopCdvd
|
||||
@@ -24,7 +25,7 @@ namespace ps2x::iop::detail
|
||||
uint32_t reason = 0u;
|
||||
};
|
||||
|
||||
IopCdvd(IopHost &host, IopMemory &memory);
|
||||
IopCdvd(IopHost &host, IopMemory &memory, IopKernel &kernel);
|
||||
~IopCdvd();
|
||||
|
||||
IopCdvd(const IopCdvd &) = delete;
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "iop_heaplib.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_memory.h"
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopHeaplib::IopHeaplib(IopMemory &memory) noexcept
|
||||
: m_memory(memory)
|
||||
{
|
||||
}
|
||||
|
||||
bool IopHeaplib::dispatchImport(uint16_t ordinal, IopCpuState &cpu)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const uint32_t a1 = cpu.gpr[5];
|
||||
const auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 4: // CreateHeap
|
||||
setV0(m_memory.allocate(16u, 16u));
|
||||
return true;
|
||||
case 5: // DeleteHeap
|
||||
if (a0 != 0u)
|
||||
(void)m_memory.freeAllocation(a0);
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 6:
|
||||
setV0(m_memory.allocate(a1, 16u));
|
||||
return true;
|
||||
case 7:
|
||||
setV0(m_memory.freeAllocation(a1) ? 0u : 0xFFFFFFFFu);
|
||||
return true;
|
||||
case 8:
|
||||
setV0(m_memory.maxFreeMemory());
|
||||
return true;
|
||||
case 11:
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 15:
|
||||
if (const auto block = m_memory.allocationContaining(a0))
|
||||
setV0(block->size);
|
||||
else
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopMemory;
|
||||
|
||||
class IopHeaplib
|
||||
{
|
||||
public:
|
||||
explicit IopHeaplib(IopMemory &memory) noexcept;
|
||||
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu);
|
||||
|
||||
private:
|
||||
IopMemory &m_memory;
|
||||
};
|
||||
}
|
||||
+21
-1
@@ -1,6 +1,6 @@
|
||||
#include "iop_imports.h"
|
||||
|
||||
#include "iop_memory.h"
|
||||
#include "../core/iop_memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -147,6 +147,26 @@ namespace ps2x::iop::detail
|
||||
return found->second.functions[ordinal];
|
||||
}
|
||||
|
||||
int32_t IopImportRegistry::setRebootTimeLibraryHandlingMode(uint32_t address, uint32_t mode)
|
||||
{
|
||||
constexpr int32_t kLibraryNotFound = -213;
|
||||
constexpr int32_t kIllegalLibrary = -214;
|
||||
|
||||
if (address == 0u)
|
||||
return kIllegalLibrary;
|
||||
const uint32_t physical = IopMemory::physicalAddress(address);
|
||||
if (physical + 12u > IopMemory::RamSize)
|
||||
return kLibraryNotFound;
|
||||
|
||||
const bool registered = m_libraries.find(physical) != m_libraries.end();
|
||||
if (!registered && m_memory.read32(physical) != kExportMagic)
|
||||
return kLibraryNotFound;
|
||||
|
||||
const uint16_t oldMode = m_memory.read16(physical + 10u);
|
||||
m_memory.write16(physical + 10u, static_cast<uint16_t>((oldMode & ~6u) | (mode & 6u)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IopImportRegistry::eraseRange(uint32_t base, uint32_t size)
|
||||
{
|
||||
for (auto library = m_libraries.begin(); library != m_libraries.end();)
|
||||
@@ -29,6 +29,7 @@ namespace ps2x::iop::detail
|
||||
[[nodiscard]] bool releaseExportTable(uint32_t address);
|
||||
[[nodiscard]] uint32_t findTable(std::string_view library) const;
|
||||
[[nodiscard]] uint32_t resolve(std::string_view library, uint16_t ordinal) const;
|
||||
[[nodiscard]] int32_t setRebootTimeLibraryHandlingMode(uint32_t address, uint32_t mode);
|
||||
void eraseRange(uint32_t base, uint32_t size);
|
||||
|
||||
private:
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "iop_intrman.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "../services/iop_rpc.h"
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopIntrman::IopIntrman(IopMemory &memory) noexcept
|
||||
: m_memory(memory)
|
||||
{
|
||||
}
|
||||
|
||||
void IopIntrman::reset()
|
||||
{
|
||||
m_handlers.clear();
|
||||
m_enabled.clear();
|
||||
}
|
||||
|
||||
bool IopIntrman::dispatchImport(uint16_t ordinal, IopCpuState &cpu, IopGuestExecutor &executor)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const uint32_t a1 = cpu.gpr[5];
|
||||
const uint32_t a2 = cpu.gpr[6];
|
||||
const uint32_t a3 = cpu.gpr[7];
|
||||
const auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 3:
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 4: // RegisterIntrHandler
|
||||
m_handlers[static_cast<int>(a0)] = {a2, a3, cpu.gpr[28]};
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 5: // ReleaseIntrHandler
|
||||
m_handlers.erase(static_cast<int>(a0));
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 6: // EnableIntr
|
||||
m_enabled[static_cast<int>(a0)] = true;
|
||||
if (a0 < 32u)
|
||||
m_memory.setInterruptMask(m_memory.interruptMask() | (1u << a0));
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 7: // DisableIntr
|
||||
if (a1 != 0u)
|
||||
m_memory.write32(a1, a0);
|
||||
if (a0 < 32u)
|
||||
m_memory.setInterruptMask(m_memory.interruptMask() & ~(1u << a0));
|
||||
m_enabled[static_cast<int>(a0)] = false;
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 8: // CpuDisableIntr
|
||||
m_memory.setInterruptControl(0u);
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 9: // CpuEnableIntr
|
||||
m_memory.setInterruptControl(1u);
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 14:
|
||||
setV0(a0 != 0u
|
||||
? executor.executeGuestFunctionWithBudget(a0, a1, a2, a3, 0u, cpu.gpr[28], 100000u)
|
||||
: 0u);
|
||||
return true;
|
||||
case 15:
|
||||
case 16:
|
||||
case 23:
|
||||
case 24:
|
||||
case 25:
|
||||
case 28:
|
||||
case 30:
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 17:
|
||||
if (a0 != 0u)
|
||||
m_memory.write32(a0, m_memory.interruptControl());
|
||||
m_memory.setInterruptControl(0u);
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 18:
|
||||
m_memory.setInterruptControl(a0 != 0u ? 1u : 0u);
|
||||
setV0(0u);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IopIntrman::dispatchInterrupt(int irq, IopGuestExecutor &executor) const
|
||||
{
|
||||
const auto enabled = m_enabled.find(irq);
|
||||
if (enabled == m_enabled.end() || !enabled->second)
|
||||
return false;
|
||||
const auto handler = m_handlers.find(irq);
|
||||
if (handler == m_handlers.end() || handler->second.function == 0u)
|
||||
return false;
|
||||
|
||||
(void)executor.executeGuestFunctionWithBudget(handler->second.function,
|
||||
handler->second.argument,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
handler->second.gp,
|
||||
100000u);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopGuestExecutor;
|
||||
class IopMemory;
|
||||
|
||||
class IopIntrman
|
||||
{
|
||||
public:
|
||||
explicit IopIntrman(IopMemory &memory) noexcept;
|
||||
|
||||
void reset();
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu, IopGuestExecutor &executor);
|
||||
[[nodiscard]] bool dispatchInterrupt(int irq, IopGuestExecutor &executor) const;
|
||||
|
||||
private:
|
||||
struct Handler
|
||||
{
|
||||
uint32_t function = 0u;
|
||||
uint32_t argument = 0u;
|
||||
uint32_t gp = 0u;
|
||||
};
|
||||
|
||||
IopMemory &m_memory;
|
||||
std::map<int, Handler> m_handlers;
|
||||
std::map<int, bool> m_enabled;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "iop_ioman.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "../services/iop_rpc.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopIoman::IopIoman(IopMemory &memory) noexcept
|
||||
: m_memory(memory)
|
||||
{
|
||||
}
|
||||
|
||||
void IopIoman::reset()
|
||||
{
|
||||
m_devices.clear();
|
||||
}
|
||||
|
||||
bool IopIoman::dispatchImport(uint16_t ordinal, IopCpuState &cpu, IopGuestExecutor &executor)
|
||||
{
|
||||
constexpr size_t kMaxDevices = 16u;
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 20: // AddDrv
|
||||
{
|
||||
if (a0 == 0u || m_devices.size() >= kMaxDevices)
|
||||
{
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t nameAddress = m_memory.read32(a0);
|
||||
const uint32_t operations = m_memory.read32(a0 + 16u);
|
||||
const std::string name = m_memory.readString(nameAddress, 64u);
|
||||
if (nameAddress == 0u || operations == 0u || name.empty())
|
||||
{
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_devices.push_back({a0, cpu.gpr[28], name});
|
||||
const uint32_t init = m_memory.read32(operations);
|
||||
if (init != 0u)
|
||||
{
|
||||
const int32_t result = static_cast<int32_t>(
|
||||
executor.executeGuestFunction(init, a0, 0u, 0u, 0u, cpu.gpr[28]));
|
||||
if (result < 0)
|
||||
{
|
||||
m_devices.pop_back();
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 21: // DelDrv
|
||||
{
|
||||
const std::string name = m_memory.readString(a0, 64u);
|
||||
const auto device = std::find_if(
|
||||
m_devices.begin(), m_devices.end(),
|
||||
[&](const Device &candidate)
|
||||
{ return candidate.name == name; });
|
||||
if (device == m_devices.end())
|
||||
{
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t operations = m_memory.read32(device->address + 16u);
|
||||
const uint32_t deinit = operations != 0u ? m_memory.read32(operations + 4u) : 0u;
|
||||
if (deinit != 0u)
|
||||
(void)executor.executeGuestFunction(deinit, device->address, 0u, 0u, 0u, device->gp);
|
||||
m_devices.erase(device);
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopGuestExecutor;
|
||||
class IopMemory;
|
||||
|
||||
class IopIoman
|
||||
{
|
||||
public:
|
||||
explicit IopIoman(IopMemory &memory) noexcept;
|
||||
|
||||
void reset();
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu, IopGuestExecutor &executor);
|
||||
|
||||
private:
|
||||
struct Device
|
||||
{
|
||||
uint32_t address = 0u;
|
||||
uint32_t gp = 0u;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
IopMemory &m_memory;
|
||||
std::vector<Device> m_devices;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "iop_loadcore.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "iop_imports.h"
|
||||
#include "../core/iop_memory.h"
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopLoadcore::IopLoadcore(IopMemory &memory, IopImportRegistry &imports) noexcept
|
||||
: m_memory(memory), m_imports(imports)
|
||||
{
|
||||
}
|
||||
|
||||
bool IopLoadcore::dispatchImport(uint16_t ordinal, IopCpuState &cpu)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 8:
|
||||
case 9:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 20:
|
||||
case 21:
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 6:
|
||||
case 10:
|
||||
setV0(m_imports.registerExportTable(a0) ? 0u : 0xFFFFFFFFu);
|
||||
return true;
|
||||
case 7:
|
||||
setV0(m_imports.releaseExportTable(a0) ? 0u : 0xFFFFFFFFu);
|
||||
return true;
|
||||
case 11:
|
||||
setV0(m_imports.findTable(m_memory.readString(a0 + 12u, 8u)));
|
||||
return true;
|
||||
case 27: // SetRebootTimeLibraryHandlingMode
|
||||
setV0(static_cast<uint32_t>(m_imports.setRebootTimeLibraryHandlingMode(a0, cpu.gpr[5])));
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopImportRegistry;
|
||||
class IopMemory;
|
||||
|
||||
class IopLoadcore
|
||||
{
|
||||
public:
|
||||
IopLoadcore(IopMemory &memory, IopImportRegistry &imports) noexcept;
|
||||
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu);
|
||||
|
||||
private:
|
||||
IopMemory &m_memory;
|
||||
IopImportRegistry &m_imports;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "iop_stdio.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "ps2x/iop/iop_host.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopStdio::IopStdio(IopHost &host, IopMemory &memory) noexcept
|
||||
: m_host(host), m_memory(memory)
|
||||
{
|
||||
}
|
||||
|
||||
bool IopStdio::dispatchImport(uint16_t ordinal, IopCpuState &cpu)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const uint32_t a1 = cpu.gpr[5];
|
||||
const auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
const auto logString = [&](std::string_view prefix, uint32_t address, uint32_t resultBias = 0u)
|
||||
{
|
||||
const std::string text = m_memory.readString(address, 2048u);
|
||||
m_host.log(LogLevel::Info, std::string(prefix) + text);
|
||||
setV0(static_cast<uint32_t>(text.size()) + resultBias);
|
||||
};
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 4: // printf
|
||||
logString("[IOP printf] ", a0);
|
||||
return true;
|
||||
case 5: // getchar
|
||||
case 10:
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
case 6: // putchar
|
||||
m_host.log(LogLevel::Info, std::string("[IOP putchar] ") + static_cast<char>(a0 & 0xFFu));
|
||||
setV0(a0 & 0xFFu);
|
||||
return true;
|
||||
case 7: // puts
|
||||
logString("[IOP puts] ", a0, 1u);
|
||||
return true;
|
||||
case 8: // gets
|
||||
case 13:
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 9: // fdprintf
|
||||
logString("[IOP fdprintf] ", a1);
|
||||
return true;
|
||||
case 11:
|
||||
setV0(a0 & 0xFFu);
|
||||
return true;
|
||||
case 12: // fdputs
|
||||
logString("[IOP fdputs] ", a0);
|
||||
return true;
|
||||
case 14: // vfdprintf
|
||||
logString("[IOP vfdprintf] ", a1);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2x::iop
|
||||
{
|
||||
class IopHost;
|
||||
}
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopMemory;
|
||||
|
||||
class IopStdio
|
||||
{
|
||||
public:
|
||||
IopStdio(IopHost &host, IopMemory &memory) noexcept;
|
||||
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu);
|
||||
|
||||
private:
|
||||
IopHost &m_host;
|
||||
IopMemory &m_memory;
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "iop_sysclib.h"
|
||||
|
||||
#include "iop_cpu.h"
|
||||
#include "iop_memory.h"
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_memory.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
@@ -64,7 +64,7 @@ namespace ps2x::iop::detail
|
||||
case 4: // setjmp - enough for callers which only test the initial return.
|
||||
setV0(0);
|
||||
return true;
|
||||
case 5: // longjmp cannot be safely synthesized without the BIOS jmp_buf ABI.
|
||||
case 5: // longjmp, TODO bc w can do it without the BIOS jmp_buf ABI.
|
||||
setV0(a1 == 0u ? 1u : a1);
|
||||
return true;
|
||||
case 6:
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "iop_sysmem.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "ps2x/iop/iop_host.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopSysmem::IopSysmem(IopHost &host, IopMemory &memory) noexcept
|
||||
: m_host(host), m_memory(memory)
|
||||
{
|
||||
}
|
||||
|
||||
bool IopSysmem::dispatchImport(uint16_t ordinal, IopCpuState &cpu)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const uint32_t a1 = cpu.gpr[5];
|
||||
const uint32_t a2 = cpu.gpr[6];
|
||||
const auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 4: // AllocSysMemory
|
||||
{
|
||||
const uint32_t address = a0 == 2u
|
||||
? m_memory.allocate(a1, 16u, a2)
|
||||
: m_memory.allocate(a1, 16u);
|
||||
setV0(address);
|
||||
return true;
|
||||
}
|
||||
case 5: // FreeSysMemory
|
||||
setV0(m_memory.freeAllocation(a0) ? 0u : 0xFFFFFFFFu);
|
||||
return true;
|
||||
case 6: // QueryMemSize
|
||||
setV0(IopMemory::RamSize);
|
||||
return true;
|
||||
case 7: // QueryMaxFreeMemSize
|
||||
case 8: // QueryTotalFreeMemSize
|
||||
setV0(m_memory.maxFreeMemory());
|
||||
return true;
|
||||
case 9: // QueryBlockTopAddress
|
||||
if (const auto block = m_memory.allocationContaining(a0))
|
||||
setV0(block->address);
|
||||
else
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 10: // QueryBlockSize
|
||||
if (const auto block = m_memory.allocationContaining(a0))
|
||||
setV0(block->size);
|
||||
else
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
case 14: // Kprintf
|
||||
{
|
||||
const std::string format = m_memory.readString(a0, 512u);
|
||||
m_host.log(LogLevel::Info, std::string("[IOP Kprintf] ") + format);
|
||||
setV0(static_cast<uint32_t>(format.size()));
|
||||
return true;
|
||||
}
|
||||
case 15:
|
||||
setV0(0u);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2x::iop
|
||||
{
|
||||
class IopHost;
|
||||
}
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopMemory;
|
||||
|
||||
class IopSysmem
|
||||
{
|
||||
public:
|
||||
IopSysmem(IopHost &host, IopMemory &memory) noexcept;
|
||||
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu);
|
||||
|
||||
private:
|
||||
IopHost &m_host;
|
||||
IopMemory &m_memory;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
#include "iop_timrman.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../services/iop_rpc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr int32_t kNoTimer = -150;
|
||||
constexpr int32_t kIllegalTimerId = -151;
|
||||
constexpr int32_t kIllegalSource = -152;
|
||||
constexpr int32_t kIllegalPrescale = -153;
|
||||
constexpr int32_t kTimerBusy = -154;
|
||||
constexpr int32_t kTimerNotConfigured = -155;
|
||||
constexpr int32_t kTimerNotRunning = -156;
|
||||
constexpr int32_t kIllegalMode = -405;
|
||||
constexpr uint64_t kIopClockHz = 36'864'000ull;
|
||||
constexpr uint64_t kPixelClockHz = 13'500'000ull;
|
||||
constexpr uint64_t kHlineClockHz = 15'734ull;
|
||||
|
||||
constexpr std::array<size_t, 6> kAllocationOrder{2u, 5u, 4u, 3u, 0u, 1u};
|
||||
constexpr std::array<uint32_t, 6> kAddresses{
|
||||
0xBF801100u,
|
||||
0xBF801110u,
|
||||
0xBF801120u,
|
||||
0xBF801480u,
|
||||
0xBF801490u,
|
||||
0xBF8014A0u,
|
||||
};
|
||||
constexpr std::array<uint8_t, 6> kSources{0x0Bu, 0x0Du, 0x01u, 0x05u, 0x01u, 0x01u};
|
||||
constexpr std::array<uint8_t, 6> kWidths{16u, 16u, 16u, 32u, 32u, 32u};
|
||||
constexpr std::array<uint16_t, 6> kMaxPrescales{1u, 1u, 8u, 1u, 256u, 256u};
|
||||
constexpr std::array<uint8_t, 6> kIrqs{4u, 5u, 6u, 14u, 15u, 16u};
|
||||
|
||||
uint32_t errorValue(int32_t error) noexcept
|
||||
{
|
||||
return static_cast<uint32_t>(error);
|
||||
}
|
||||
}
|
||||
|
||||
void IopTimrman::reset() noexcept
|
||||
{
|
||||
for (size_t i = 0u; i < m_timers.size(); ++i)
|
||||
{
|
||||
m_timers[i] = {};
|
||||
m_timers[i].address = kAddresses[i];
|
||||
m_timers[i].sources = kSources[i];
|
||||
m_timers[i].width = kWidths[i];
|
||||
m_timers[i].maxPrescale = kMaxPrescales[i];
|
||||
m_timers[i].irq = kIrqs[i];
|
||||
}
|
||||
m_holdMode = 0u;
|
||||
m_servicing = false;
|
||||
}
|
||||
|
||||
uint32_t IopTimrman::timerId(size_t index) noexcept
|
||||
{
|
||||
return (static_cast<uint32_t>(index + 1u) << 28u) | (kAddresses[index] >> 4u);
|
||||
}
|
||||
|
||||
IopTimrman::Timer *IopTimrman::timerFromId(uint32_t id) noexcept
|
||||
{
|
||||
const uint32_t encoded = id >> 28u;
|
||||
if (encoded == 0u || encoded > m_timers.size())
|
||||
return nullptr;
|
||||
Timer &timer = m_timers[encoded - 1u];
|
||||
return timer.users != 0u && (id & 0x0FFFFFFFu) == (timer.address >> 4u)
|
||||
? &timer
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const IopTimrman::Timer *IopTimrman::timerFromId(uint32_t id) const noexcept
|
||||
{
|
||||
return const_cast<IopTimrman *>(this)->timerFromId(id);
|
||||
}
|
||||
|
||||
uint64_t IopTimrman::ticksToCycles(const Timer &timer, uint64_t ticks) noexcept
|
||||
{
|
||||
const uint64_t prescale = std::max<uint64_t>(timer.prescale, 1u);
|
||||
const uint64_t sourceHz = timer.source == 2u
|
||||
? kPixelClockHz
|
||||
: (timer.source == 4u ? kHlineClockHz : kIopClockHz);
|
||||
if (ticks == 0u)
|
||||
ticks = timer.width == 16u ? (1ull << 16u) : (1ull << 32u);
|
||||
const unsigned long long scaled = ticks * prescale;
|
||||
if (sourceHz == kIopClockHz)
|
||||
return std::max<uint64_t>(scaled, 1u);
|
||||
const uint64_t whole = (scaled / sourceHz) * kIopClockHz;
|
||||
const uint64_t remainder = scaled % sourceHz;
|
||||
return std::max<uint64_t>(1u, whole + (remainder * kIopClockHz + sourceHz - 1u) / sourceHz);
|
||||
}
|
||||
|
||||
uint64_t IopTimrman::elapsedTicks(const Timer &timer, uint64_t currentCycle) noexcept
|
||||
{
|
||||
if (!timer.running || currentCycle <= timer.counterBaseCycle)
|
||||
return 0u;
|
||||
const uint64_t elapsed = currentCycle - timer.counterBaseCycle;
|
||||
const uint64_t sourceHz = timer.source == 2u
|
||||
? kPixelClockHz
|
||||
: (timer.source == 4u ? kHlineClockHz : kIopClockHz);
|
||||
return (elapsed * sourceHz) / (kIopClockHz * std::max<uint64_t>(timer.prescale, 1u));
|
||||
}
|
||||
|
||||
uint32_t IopTimrman::counterValue(const Timer &timer, uint64_t currentCycle) noexcept
|
||||
{
|
||||
const uint64_t value = static_cast<uint64_t>(timer.counterBase) + elapsedTicks(timer, currentCycle);
|
||||
return timer.width == 16u ? static_cast<uint32_t>(value & 0xFFFFu) : static_cast<uint32_t>(value);
|
||||
}
|
||||
|
||||
void IopTimrman::schedule(Timer &timer, uint64_t currentCycle) noexcept
|
||||
{
|
||||
timer.compareCycle = UINT64_MAX;
|
||||
timer.overflowCycle = UINT64_MAX;
|
||||
if (!timer.running)
|
||||
return;
|
||||
|
||||
const uint64_t current = counterValue(timer, currentCycle);
|
||||
timer.counterBase = static_cast<uint32_t>(current);
|
||||
timer.counterBaseCycle = currentCycle;
|
||||
|
||||
if (timer.compareCallback.function != 0u)
|
||||
{
|
||||
const uint64_t modulus = timer.width == 16u ? (1ull << 16u) : (1ull << 32u);
|
||||
const uint64_t compare = timer.width == 16u ? (timer.compare & 0xFFFFu) : timer.compare;
|
||||
uint64_t delta = (compare + modulus - current) % modulus;
|
||||
if (delta == 0u)
|
||||
delta = modulus;
|
||||
timer.compareCycle = currentCycle + ticksToCycles(timer, delta);
|
||||
}
|
||||
|
||||
if (timer.overflowCallback.function != 0u)
|
||||
{
|
||||
const uint64_t modulus = timer.width == 16u ? (1ull << 16u) : (1ull << 32u);
|
||||
uint64_t delta = modulus - current;
|
||||
if (delta == 0u)
|
||||
delta = modulus;
|
||||
timer.overflowCycle = currentCycle + ticksToCycles(timer, delta);
|
||||
}
|
||||
}
|
||||
|
||||
void IopTimrman::stop(Timer &timer, uint64_t currentCycle) noexcept
|
||||
{
|
||||
timer.counterBase = counterValue(timer, currentCycle);
|
||||
timer.counterBaseCycle = currentCycle;
|
||||
timer.running = false;
|
||||
timer.liveMode = 0u;
|
||||
timer.compareCycle = UINT64_MAX;
|
||||
timer.overflowCycle = UINT64_MAX;
|
||||
}
|
||||
|
||||
bool IopTimrman::dispatchImport(uint16_t ordinal, IopCpuState &cpu, uint64_t currentCycle)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const uint32_t a1 = cpu.gpr[5];
|
||||
const uint32_t a2 = cpu.gpr[6];
|
||||
const uint32_t a3 = cpu.gpr[7];
|
||||
const auto setV0 = [&](uint32_t value) { cpu.gpr[2] = value; };
|
||||
|
||||
switch (ordinal)
|
||||
{
|
||||
case 3: // GetTimersTable
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 4: // AllocHardTimer
|
||||
for (const size_t index : kAllocationOrder)
|
||||
{
|
||||
Timer &timer = m_timers[index];
|
||||
if (timer.users != 0u || (timer.sources & a0) == 0u || timer.width != a1 || timer.maxPrescale < a2)
|
||||
continue;
|
||||
timer.users = 1u;
|
||||
timer.source = a0;
|
||||
timer.prescale = std::max(a2, 1u);
|
||||
timer.counterBaseCycle = currentCycle;
|
||||
setV0(timerId(index));
|
||||
return true;
|
||||
}
|
||||
setV0(errorValue(kNoTimer));
|
||||
return true;
|
||||
case 5: // ReferHardTimer
|
||||
for (size_t index = 0u; index < m_timers.size(); ++index)
|
||||
{
|
||||
Timer &timer = m_timers[index];
|
||||
if (timer.users == 0u || timer.liveMode == 0u || (timer.sources & a0) == 0u ||
|
||||
timer.width != a1 || (timer.liveMode & a3) != a2)
|
||||
continue;
|
||||
++timer.users;
|
||||
setV0(timerId(index));
|
||||
return true;
|
||||
}
|
||||
setV0(errorValue(kNoTimer));
|
||||
return true;
|
||||
case 6: // FreeHardTimer
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
if (--timer->users == 0u)
|
||||
{
|
||||
const uint32_t address = timer->address;
|
||||
const uint8_t sources = timer->sources;
|
||||
const uint8_t width = timer->width;
|
||||
const uint16_t maxPrescale = timer->maxPrescale;
|
||||
const uint8_t irq = timer->irq;
|
||||
*timer = {};
|
||||
timer->address = address;
|
||||
timer->sources = sources;
|
||||
timer->width = width;
|
||||
timer->maxPrescale = maxPrescale;
|
||||
timer->irq = irq;
|
||||
}
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 7: // SetTimerMode
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
if (a1 == 0u)
|
||||
stop(*timer, currentCycle);
|
||||
else
|
||||
{
|
||||
timer->liveMode = a1;
|
||||
timer->running = true;
|
||||
timer->counterBaseCycle = currentCycle;
|
||||
schedule(*timer, currentCycle);
|
||||
}
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 8: // GetTimerStatus
|
||||
case 17: // GetTimerMode
|
||||
{
|
||||
const Timer *timer = timerFromId(a0);
|
||||
setV0(timer ? timer->liveMode : errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
case 9: // SetTimerCounter
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
timer->counterBase = timer->width == 16u ? (a1 & 0xFFFFu) : a1;
|
||||
timer->counterBaseCycle = currentCycle;
|
||||
schedule(*timer, currentCycle);
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 10: // GetTimerCounter
|
||||
{
|
||||
const Timer *timer = timerFromId(a0);
|
||||
setV0(timer ? counterValue(*timer, currentCycle) : errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
case 11: // SetTimerCompare
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
timer->compare = timer->width == 16u ? (a1 & 0xFFFFu) : a1;
|
||||
schedule(*timer, currentCycle);
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 12: // GetTimerCompare
|
||||
{
|
||||
const Timer *timer = timerFromId(a0);
|
||||
setV0(timer ? timer->compare : errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
case 13: // SetHoldMode
|
||||
m_holdMode = (m_holdMode & ~(0xFu << ((a0 & 7u) * 4u))) | ((a1 & 0xFu) << ((a0 & 7u) * 4u));
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 14: // GetHoldMode
|
||||
setV0((m_holdMode >> ((a0 & 7u) * 4u)) & 0xFu);
|
||||
return true;
|
||||
case 15: // GetHoldReg
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 16: // GetHardTimerIntrCode
|
||||
{
|
||||
const Timer *timer = timerFromId(a0);
|
||||
setV0(timer ? timer->irq : errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
case 18: // GetTimerReadFunc
|
||||
// Returning a host-side register reader as a guest function is not meaningful.
|
||||
setV0(0u);
|
||||
return true;
|
||||
case 20: // SetTimerHandler
|
||||
case 21: // SetOverflowHandler
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
if (timer->running)
|
||||
{
|
||||
setV0(errorValue(kTimerNotRunning));
|
||||
return true;
|
||||
}
|
||||
if (ordinal == 20u)
|
||||
{
|
||||
timer->compare = timer->width == 16u ? (a1 & 0xFFFFu) : a1;
|
||||
timer->compareCallback = {a2, a3, cpu.gpr[28]};
|
||||
}
|
||||
else
|
||||
{
|
||||
timer->overflowCallback = {a1, a2, cpu.gpr[28]};
|
||||
}
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 22: // SetupHardTimer
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
if (timer->running)
|
||||
{
|
||||
setV0(errorValue(kTimerBusy));
|
||||
return true;
|
||||
}
|
||||
if ((a2 != 0u && a2 != 1u && a2 != 3u && a2 != 5u && a2 != 7u))
|
||||
{
|
||||
setV0(errorValue(kIllegalMode));
|
||||
return true;
|
||||
}
|
||||
if ((timer->sources & a1) == 0u)
|
||||
{
|
||||
setV0(errorValue(kIllegalSource));
|
||||
return true;
|
||||
}
|
||||
if (a3 == 0u || a3 > timer->maxPrescale)
|
||||
{
|
||||
setV0(errorValue(kIllegalPrescale));
|
||||
return true;
|
||||
}
|
||||
timer->source = a1;
|
||||
timer->setupMode = a2;
|
||||
timer->prescale = a3;
|
||||
timer->configured = true;
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 23: // StartHardTimer
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
if (timer->running)
|
||||
{
|
||||
setV0(errorValue(kTimerBusy));
|
||||
return true;
|
||||
}
|
||||
if (!timer->configured)
|
||||
{
|
||||
setV0(errorValue(kTimerNotConfigured));
|
||||
return true;
|
||||
}
|
||||
timer->counterBase = 0u;
|
||||
timer->counterBaseCycle = currentCycle;
|
||||
timer->liveMode = 0x80000000u | timer->setupMode;
|
||||
timer->running = true;
|
||||
schedule(*timer, currentCycle);
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
case 24: // StopHardTimer
|
||||
{
|
||||
Timer *timer = timerFromId(a0);
|
||||
if (!timer)
|
||||
{
|
||||
setV0(errorValue(kIllegalTimerId));
|
||||
return true;
|
||||
}
|
||||
if (!timer->running)
|
||||
{
|
||||
setV0(errorValue(kTimerNotRunning));
|
||||
return true;
|
||||
}
|
||||
stop(*timer, currentCycle);
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void IopTimrman::serviceDue(uint64_t currentCycle, IopGuestExecutor &executor)
|
||||
{
|
||||
if (m_servicing)
|
||||
return;
|
||||
m_servicing = true;
|
||||
struct ServiceGuard
|
||||
{
|
||||
bool &flag;
|
||||
~ServiceGuard() { flag = false; }
|
||||
} guard{m_servicing};
|
||||
|
||||
for (Timer &timer : m_timers)
|
||||
{
|
||||
if (!timer.running)
|
||||
continue;
|
||||
|
||||
const bool compareDue = timer.compareCycle <= currentCycle;
|
||||
const bool overflowDue = timer.overflowCycle <= currentCycle;
|
||||
if (!compareDue && !overflowDue)
|
||||
continue;
|
||||
|
||||
const Callback callback = compareDue ? timer.compareCallback : timer.overflowCallback;
|
||||
timer.compareCycle = UINT64_MAX;
|
||||
timer.overflowCycle = UINT64_MAX;
|
||||
const uint32_t result = callback.function != 0u
|
||||
? executor.executeGuestFunctionWithBudget(callback.function,
|
||||
callback.common,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
callback.gp,
|
||||
100000u)
|
||||
: 0u;
|
||||
if (!timer.running)
|
||||
continue;
|
||||
if (result == 0u)
|
||||
{
|
||||
stop(timer, currentCycle);
|
||||
continue;
|
||||
}
|
||||
if (compareDue)
|
||||
timer.compare = timer.width == 16u ? (result & 0xFFFFu) : result;
|
||||
timer.counterBase = 0u;
|
||||
timer.counterBaseCycle = currentCycle;
|
||||
schedule(timer, currentCycle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint64_t IopTimrman::nextEventCycle(uint64_t fallback) const noexcept
|
||||
{
|
||||
uint64_t next = fallback;
|
||||
for (const Timer &timer : m_timers)
|
||||
{
|
||||
next = std::min(next, timer.compareCycle);
|
||||
next = std::min(next, timer.overflowCycle);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopGuestExecutor;
|
||||
|
||||
class IopTimrman
|
||||
{
|
||||
public:
|
||||
void reset() noexcept;
|
||||
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu, uint64_t currentCycle);
|
||||
void serviceDue(uint64_t currentCycle, IopGuestExecutor &executor);
|
||||
[[nodiscard]] uint64_t nextEventCycle(uint64_t fallback) const noexcept;
|
||||
|
||||
private:
|
||||
struct Callback
|
||||
{
|
||||
uint32_t function = 0u;
|
||||
uint32_t common = 0u;
|
||||
uint32_t gp = 0u;
|
||||
};
|
||||
|
||||
struct Timer
|
||||
{
|
||||
uint32_t address = 0u;
|
||||
uint8_t sources = 0u;
|
||||
uint8_t width = 0u;
|
||||
uint16_t maxPrescale = 0u;
|
||||
uint8_t irq = 0u;
|
||||
uint8_t users = 0u;
|
||||
|
||||
uint32_t source = 1u;
|
||||
uint32_t prescale = 1u;
|
||||
uint32_t setupMode = 0u;
|
||||
uint32_t liveMode = 0u;
|
||||
uint32_t counterBase = 0u;
|
||||
uint32_t compare = 0u;
|
||||
uint64_t counterBaseCycle = 0u;
|
||||
uint64_t compareCycle = UINT64_MAX;
|
||||
uint64_t overflowCycle = UINT64_MAX;
|
||||
bool configured = false;
|
||||
bool running = false;
|
||||
|
||||
Callback compareCallback;
|
||||
Callback overflowCallback;
|
||||
};
|
||||
|
||||
[[nodiscard]] Timer *timerFromId(uint32_t timerId) noexcept;
|
||||
[[nodiscard]] const Timer *timerFromId(uint32_t timerId) const noexcept;
|
||||
[[nodiscard]] static uint32_t timerId(size_t index) noexcept;
|
||||
[[nodiscard]] static uint64_t ticksToCycles(const Timer &timer, uint64_t ticks) noexcept;
|
||||
[[nodiscard]] static uint64_t elapsedTicks(const Timer &timer, uint64_t currentCycle) noexcept;
|
||||
[[nodiscard]] static uint32_t counterValue(const Timer &timer, uint64_t currentCycle) noexcept;
|
||||
static void schedule(Timer &timer, uint64_t currentCycle) noexcept;
|
||||
static void stop(Timer &timer, uint64_t currentCycle) noexcept;
|
||||
|
||||
std::array<Timer, 6> m_timers{};
|
||||
uint32_t m_holdMode = 0u;
|
||||
bool m_servicing = false;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "iop_vblank.h"
|
||||
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../iop_emulator_const.h"
|
||||
#include "../core/iop_kernel.h"
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopVblank::IopVblank(IopKernel &kernel) noexcept
|
||||
: m_kernel(kernel)
|
||||
{
|
||||
}
|
||||
|
||||
bool IopVblank::dispatchImport(uint16_t ordinal, IopCpuState &cpu, uint64_t currentCycle)
|
||||
{
|
||||
switch (ordinal)
|
||||
{
|
||||
case 4: // WaitVblankStart
|
||||
case 5: // WaitVblankEnd
|
||||
case 6: // WaitVblank
|
||||
case 7: // WaitNonVblank
|
||||
{
|
||||
const bool waitForEnd = ordinal == 5u || ordinal == 7u;
|
||||
const uint64_t phase = waitForEnd ? kVblankEndPhaseCycles : 0u;
|
||||
const uint64_t fieldStart = currentCycle - (currentCycle % kVblankPeriodCycles);
|
||||
uint64_t wakeCycle = fieldStart + phase;
|
||||
if (wakeCycle <= currentCycle)
|
||||
wakeCycle += kVblankPeriodCycles;
|
||||
m_kernel.delayCurrentUntil(wakeCycle, cpu);
|
||||
cpu.gpr[2] = 0u;
|
||||
return true;
|
||||
}
|
||||
case 8: // RegisterVblankHandler
|
||||
case 9: // ReleaseVblankHandler
|
||||
// Callback delivery is not required by the scheduler wait ABI yet.
|
||||
cpu.gpr[2] = 0u;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
struct IopCpuState;
|
||||
class IopKernel;
|
||||
|
||||
class IopVblank
|
||||
{
|
||||
public:
|
||||
explicit IopVblank(IopKernel &kernel) noexcept;
|
||||
|
||||
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu, uint64_t currentCycle);
|
||||
|
||||
private:
|
||||
IopKernel &m_kernel;
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
#include "iop_emulator.h"
|
||||
#include "iop_cdvd.h"
|
||||
#include "iop_cpu.h"
|
||||
#include "iop_imports.h"
|
||||
#include "iop_kernel.h"
|
||||
#include "iop_memory.h"
|
||||
#include "iop_module_loader.h"
|
||||
#include "iop_rpc.h"
|
||||
#include "iop_sysclib.h"
|
||||
#include "imports/iop_cdvd.h"
|
||||
#include "core/iop_cpu.h"
|
||||
#include "imports/iop_heaplib.h"
|
||||
#include "imports/iop_imports.h"
|
||||
#include "imports/iop_intrman.h"
|
||||
#include "imports/iop_ioman.h"
|
||||
#include "core/iop_kernel.h"
|
||||
#include "imports/iop_loadcore.h"
|
||||
#include "core/iop_memory.h"
|
||||
#include "services/iop_module_loader.h"
|
||||
#include "services/iop_rpc.h"
|
||||
#include "imports/iop_stdio.h"
|
||||
#include "imports/iop_sysclib.h"
|
||||
#include "imports/iop_sysmem.h"
|
||||
#include "imports/iop_timrman.h"
|
||||
#include "imports/iop_vblank.h"
|
||||
#include "iop_emulator_const.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -15,7 +23,6 @@
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
@@ -73,13 +80,6 @@ namespace ps2x::iop::detail
|
||||
bool resident = false;
|
||||
};
|
||||
|
||||
struct InterruptHandler
|
||||
{
|
||||
uint32_t function = 0;
|
||||
uint32_t argument = 0;
|
||||
uint32_t gp = 0;
|
||||
};
|
||||
|
||||
struct GuestCallback
|
||||
{
|
||||
uint32_t function = 0;
|
||||
@@ -93,21 +93,22 @@ namespace ps2x::iop::detail
|
||||
uint32_t argument = 0u;
|
||||
};
|
||||
|
||||
struct IomanDevice
|
||||
{
|
||||
uint32_t address = 0;
|
||||
uint32_t gp = 0;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
explicit Impl(IopHost &hostRef)
|
||||
: host(hostRef),
|
||||
cdvd(host, memory),
|
||||
sysmem(host, memory),
|
||||
kernel(memory),
|
||||
cdvd(host, memory, kernel),
|
||||
vblank(kernel),
|
||||
rpc(host, memory, kernel),
|
||||
sysclib(memory),
|
||||
stdio(host, memory),
|
||||
heaplib(memory),
|
||||
intrman(memory),
|
||||
timrman(),
|
||||
ioman(memory),
|
||||
cpuCore(memory),
|
||||
imports(memory)
|
||||
imports(memory),
|
||||
loadcore(memory, imports)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@@ -120,9 +121,9 @@ namespace ps2x::iop::detail
|
||||
imports.reset();
|
||||
rpc.reset();
|
||||
cdvd.reset();
|
||||
interruptHandlers.clear();
|
||||
iomanDevices.clear();
|
||||
interruptEnabled.clear();
|
||||
intrman.reset();
|
||||
timrman.reset();
|
||||
ioman.reset();
|
||||
pendingDmaInterrupts.clear();
|
||||
pendingGuestCallbacks.clear();
|
||||
nextModuleId = 1;
|
||||
@@ -209,16 +210,6 @@ namespace ps2x::iop::detail
|
||||
return memory.freeAllocation(address);
|
||||
}
|
||||
|
||||
uint32_t maxFreeMemory() const
|
||||
{
|
||||
return memory.maxFreeMemory();
|
||||
}
|
||||
|
||||
std::string readString(uint32_t address, size_t limit = 1024) const
|
||||
{
|
||||
return memory.readString(address, limit);
|
||||
}
|
||||
|
||||
void log(LogLevel level, std::string_view text)
|
||||
{
|
||||
host.log(level, text);
|
||||
@@ -249,77 +240,15 @@ namespace ps2x::iop::detail
|
||||
ImportDisposition dispatchImport(const IopImportCall &call, CpuState &cpu)
|
||||
{
|
||||
const uint32_t a0 = cpu.gpr[4];
|
||||
const uint32_t a1 = cpu.gpr[5];
|
||||
const uint32_t a2 = cpu.gpr[6];
|
||||
const uint32_t a3 = cpu.gpr[7];
|
||||
auto setV0 = [&](uint32_t value)
|
||||
{
|
||||
cpu.gpr[2] = value;
|
||||
};
|
||||
|
||||
// TODO this will become a dispatch table once we have more imports implemented, but for now this is fine.
|
||||
if (iequals(call.library, "sysmem"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 4:
|
||||
{
|
||||
const uint32_t mode = a0;
|
||||
const uint32_t size = a1;
|
||||
const uint32_t ptr = a2;
|
||||
const uint32_t address = mode == 2u ? allocate(size, 16u, ptr) : allocate(size, 16u);
|
||||
setV0(address);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 5:
|
||||
setV0(freeAllocation(a0) ? 0u : 0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
case 6:
|
||||
setV0(kRamSize);
|
||||
return ImportDisposition::Handled;
|
||||
case 7:
|
||||
setV0(maxFreeMemory());
|
||||
return ImportDisposition::Handled;
|
||||
case 8:
|
||||
setV0(maxFreeMemory());
|
||||
return ImportDisposition::Handled;
|
||||
case 9:
|
||||
{
|
||||
if (const auto block = memory.allocationContaining(a0))
|
||||
{
|
||||
setV0(block->address);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
setV0(0u);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 10:
|
||||
{
|
||||
if (const auto block = memory.allocationContaining(a0))
|
||||
{
|
||||
setV0(block->size);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 14:
|
||||
{
|
||||
const std::string format = readString(a0, 512);
|
||||
log(LogLevel::Info, std::string("[IOP Kprintf] ") + format);
|
||||
setV0(static_cast<uint32_t>(format.size()));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 15:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "sysmem") && sysmem.dispatchImport(call.ordinal, cpu))
|
||||
return ImportDisposition::Handled;
|
||||
|
||||
if (iequals(call.library, "cdvdman") &&
|
||||
cdvd.dispatchImport(call.ordinal, cpu))
|
||||
if (iequals(call.library, "cdvdman") && cdvd.dispatchImport(call.ordinal, cpu))
|
||||
{
|
||||
if (const auto callback = cdvd.takeCompletionCallback())
|
||||
{
|
||||
@@ -334,50 +263,8 @@ namespace ps2x::iop::detail
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
|
||||
if (iequals(call.library, "loadcore"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 3:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 4:
|
||||
case 5:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 6:
|
||||
case 10:
|
||||
setV0(imports.registerExportTable(a0) ? 0u : 0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
case 7:
|
||||
setV0(imports.releaseExportTable(a0) ? 0u : 0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
case 8:
|
||||
case 9:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 11:
|
||||
{
|
||||
const std::string name = readString(a0 + 12u, 8u);
|
||||
setV0(imports.findTable(name));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 12:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 20:
|
||||
case 21:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "loadcore") && loadcore.dispatchImport(call.ordinal, cpu))
|
||||
return ImportDisposition::Handled;
|
||||
|
||||
if (iequals(call.library, "thbase") || iequals(call.library, "threadman"))
|
||||
{
|
||||
@@ -403,83 +290,8 @@ namespace ps2x::iop::detail
|
||||
? ImportDisposition::Handled
|
||||
: ImportDisposition::Missing;
|
||||
}
|
||||
if (iequals(call.library, "intrman"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 3:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 4: // RegisterIntrHandler
|
||||
interruptHandlers[static_cast<int>(a0)] = {a2, a3, cpu.gpr[28]};
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 5:
|
||||
interruptHandlers.erase(static_cast<int>(a0));
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 6:
|
||||
interruptEnabled[static_cast<int>(a0)] = true;
|
||||
if (a0 < 32u)
|
||||
memory.setInterruptMask(memory.interruptMask() | (1u << a0));
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 7:
|
||||
if (a1)
|
||||
write32(a1, a0);
|
||||
if (a0 < 32u)
|
||||
memory.setInterruptMask(memory.interruptMask() & ~(1u << a0));
|
||||
interruptEnabled[static_cast<int>(a0)] = false;
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 8:
|
||||
memory.setInterruptControl(0u);
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 9:
|
||||
memory.setInterruptControl(1u);
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 14:
|
||||
if (a0)
|
||||
{
|
||||
const uint32_t ret = callFunction(a0, a1, a2, a3, cpu.gpr[28], 100000u);
|
||||
setV0(ret);
|
||||
}
|
||||
else
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 15:
|
||||
case 16:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 17:
|
||||
if (a0)
|
||||
write32(a0, memory.interruptControl());
|
||||
memory.setInterruptControl(0u);
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 18:
|
||||
memory.setInterruptControl(a0 ? 1u : 0u);
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 23:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 24:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 25:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 28:
|
||||
case 30:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "intrman") && intrman.dispatchImport(call.ordinal, cpu, *this))
|
||||
return ImportDisposition::Handled;
|
||||
if (iequals(call.library, "secrman"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
@@ -498,228 +310,37 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
if (iequals(call.library, "modload") && call.ordinal == 13u)
|
||||
{
|
||||
// SetCheckKelfPathCallback. MODLOAD owns this callback in the
|
||||
// real IOP; retain both the address and the registering
|
||||
// module's GP so a later KELF load can invoke it faithfully.
|
||||
checkKelfPathCallback = {a0, cpu.gpr[28]};
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
if (iequals(call.library, "ioman"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 20: // AddDrv
|
||||
{
|
||||
constexpr size_t kMaxIomanDevices = 16u;
|
||||
if (a0 == 0u || iomanDevices.size() >= kMaxIomanDevices)
|
||||
{
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
|
||||
const uint32_t nameAddress = read32(a0 + 0u);
|
||||
const uint32_t operations = read32(a0 + 16u);
|
||||
const std::string name = readString(nameAddress, 64u);
|
||||
if (nameAddress == 0u || operations == 0u || name.empty())
|
||||
{
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
|
||||
// The original IOMAN exposes the device before invoking
|
||||
// init, then removes it again if initialization fails.
|
||||
iomanDevices.push_back({a0, cpu.gpr[28], name});
|
||||
const uint32_t init = read32(operations + 0u);
|
||||
if (init != 0u)
|
||||
{
|
||||
const int32_t initResult = static_cast<int32_t>(
|
||||
callFunction(init, a0, 0u, 0u, 0u, cpu.gpr[28]));
|
||||
if (initResult < 0)
|
||||
{
|
||||
iomanDevices.pop_back();
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
}
|
||||
|
||||
setV0(0u);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 21: // DelDrv
|
||||
{
|
||||
const std::string name = readString(a0, 64u);
|
||||
const auto device = std::find_if(
|
||||
iomanDevices.begin(), iomanDevices.end(),
|
||||
[&](const IomanDevice &candidate)
|
||||
{ return candidate.name == name; });
|
||||
if (device == iomanDevices.end())
|
||||
{
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
|
||||
const uint32_t operations = read32(device->address + 16u);
|
||||
const uint32_t deinit = operations != 0u ? read32(operations + 4u) : 0u;
|
||||
if (deinit != 0u)
|
||||
(void)callFunction(deinit, device->address, 0u, 0u, 0u, device->gp);
|
||||
iomanDevices.erase(device);
|
||||
setV0(0u);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "ioman") && ioman.dispatchImport(call.ordinal, cpu, *this))
|
||||
return ImportDisposition::Handled;
|
||||
if (iequals(call.library, "sifman"))
|
||||
{
|
||||
return rpc.dispatchSifManImport(call.ordinal, cpu)
|
||||
? ImportDisposition::Handled
|
||||
: ImportDisposition::Missing;
|
||||
}
|
||||
if (iequals(call.library, "vblank"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 4: // WaitVblankStart
|
||||
case 5: // WaitVblankEnd
|
||||
case 6: // WaitVblank
|
||||
case 7: // WaitNonVblank
|
||||
{
|
||||
const bool waitForEnd = call.ordinal == 5u || call.ordinal == 7u;
|
||||
const uint64_t phase = waitForEnd ? kVblankEndPhaseCycles : 0u;
|
||||
const uint64_t fieldStart = totalCycles - (totalCycles % kVblankPeriodCycles);
|
||||
uint64_t wakeCycle = fieldStart + phase;
|
||||
if (wakeCycle <= totalCycles)
|
||||
wakeCycle += kVblankPeriodCycles;
|
||||
kernel.delayCurrentUntil(wakeCycle, cpu);
|
||||
}
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 8: // RegisterVblankHandler
|
||||
case 9: // ReleaseVblankHandler
|
||||
// Callback delivery is not required by the scheduler wait
|
||||
// ABI yet.
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "timrman") || iequals(call.library, "dmacman"))
|
||||
if (iequals(call.library, "vblank") && vblank.dispatchImport(call.ordinal, cpu, totalCycles))
|
||||
return ImportDisposition::Handled;
|
||||
if (iequals(call.library, "timrman") && timrman.dispatchImport(call.ordinal, cpu, totalCycles))
|
||||
return ImportDisposition::Handled;
|
||||
if (iequals(call.library, "dmacman"))
|
||||
{
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
if (iequals(call.library, "stdio"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 4: // printf
|
||||
{
|
||||
const std::string text = readString(a0, 2048);
|
||||
log(LogLevel::Info, std::string("[IOP printf] ") + text);
|
||||
setV0(static_cast<uint32_t>(text.size()));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 5:
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled; // getchar
|
||||
case 6:
|
||||
{
|
||||
const char ch = static_cast<char>(a0 & 0xFFu);
|
||||
log(LogLevel::Info, std::string("[IOP putchar] ") + ch);
|
||||
setV0(a0 & 0xFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 7:
|
||||
{
|
||||
const std::string text = readString(a0, 2048);
|
||||
log(LogLevel::Info, std::string("[IOP puts] ") + text);
|
||||
setV0(static_cast<uint32_t>(text.size() + 1u));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 8:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled; // gets
|
||||
case 9:
|
||||
{
|
||||
const std::string text = readString(a1, 2048);
|
||||
log(LogLevel::Info, std::string("[IOP fdprintf] ") + text);
|
||||
setV0(static_cast<uint32_t>(text.size()));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 10:
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
case 11:
|
||||
setV0(a0 & 0xFFu);
|
||||
return ImportDisposition::Handled;
|
||||
case 12:
|
||||
{
|
||||
const std::string text = readString(a0, 2048);
|
||||
log(LogLevel::Info, std::string("[IOP fdputs] ") + text);
|
||||
setV0(static_cast<uint32_t>(text.size()));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
case 13:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 14:
|
||||
{
|
||||
const std::string text = readString(a1, 2048);
|
||||
log(LogLevel::Info, std::string("[IOP vfdprintf] ") + text);
|
||||
setV0(static_cast<uint32_t>(text.size()));
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "stdio") && stdio.dispatchImport(call.ordinal, cpu))
|
||||
return ImportDisposition::Handled;
|
||||
if (iequals(call.library, "sysclib"))
|
||||
{
|
||||
return sysclib.dispatchImport(call.ordinal, cpu)
|
||||
? ImportDisposition::Handled
|
||||
: ImportDisposition::Missing;
|
||||
}
|
||||
if (iequals(call.library, "heaplib"))
|
||||
{
|
||||
switch (call.ordinal)
|
||||
{
|
||||
case 4: // CreateHeap - heap identity is opaque to callers.
|
||||
setV0(allocate(16u, 16u));
|
||||
return ImportDisposition::Handled;
|
||||
case 5: // DeleteHeap
|
||||
if (a0)
|
||||
freeAllocation(a0);
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 6:
|
||||
setV0(allocate(a1, 16u));
|
||||
return ImportDisposition::Handled;
|
||||
case 7:
|
||||
setV0(freeAllocation(a1) ? 0u : 0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
case 8:
|
||||
setV0(maxFreeMemory());
|
||||
return ImportDisposition::Handled;
|
||||
case 11:
|
||||
setV0(0);
|
||||
return ImportDisposition::Handled;
|
||||
case 15:
|
||||
{
|
||||
if (const auto block = memory.allocationContaining(a0))
|
||||
{
|
||||
setV0(block->size);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
setV0(0xFFFFFFFFu);
|
||||
return ImportDisposition::Handled;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iequals(call.library, "heaplib") && heaplib.dispatchImport(call.ordinal, cpu))
|
||||
return ImportDisposition::Handled;
|
||||
|
||||
const uint32_t target = imports.resolve(call.library, call.ordinal);
|
||||
if (target != 0u)
|
||||
@@ -847,6 +468,17 @@ namespace ps2x::iop::detail
|
||||
return callFunction(address, a0, a1, a2, a3, gp);
|
||||
}
|
||||
|
||||
uint32_t executeGuestFunctionWithBudget(uint32_t address,
|
||||
uint32_t a0,
|
||||
uint32_t a1,
|
||||
uint32_t a2,
|
||||
uint32_t a3,
|
||||
uint32_t gp,
|
||||
uint32_t instructionBudget) override
|
||||
{
|
||||
return callFunction(address, a0, a1, a2, a3, gp, instructionBudget);
|
||||
}
|
||||
|
||||
// Not that good to use exception handling for control flow but will do for now
|
||||
void servicePendingDmaInterrupts()
|
||||
{
|
||||
@@ -869,22 +501,7 @@ namespace ps2x::iop::detail
|
||||
try
|
||||
{
|
||||
for (const int irq : completed)
|
||||
{
|
||||
const auto enabled = interruptEnabled.find(irq);
|
||||
if (enabled == interruptEnabled.end() || !enabled->second)
|
||||
continue;
|
||||
const auto handler = interruptHandlers.find(irq);
|
||||
if (handler == interruptHandlers.end() || handler->second.function == 0u)
|
||||
continue;
|
||||
|
||||
(void)callFunction(handler->second.function,
|
||||
handler->second.argument,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
handler->second.gp,
|
||||
100000u);
|
||||
}
|
||||
(void)intrman.dispatchInterrupt(irq, *this);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -944,6 +561,7 @@ namespace ps2x::iop::detail
|
||||
{
|
||||
servicePendingDmaInterrupts();
|
||||
servicePendingGuestCallbacks();
|
||||
timrman.serviceDue(totalCycles, *this);
|
||||
IopThread *next = kernel.beginNextReady(totalCycles);
|
||||
if (!next)
|
||||
{
|
||||
@@ -952,6 +570,7 @@ namespace ps2x::iop::detail
|
||||
nextWake = std::min(nextWake, completionCycle);
|
||||
if (!pendingGuestCallbacks.empty())
|
||||
nextWake = std::min(nextWake, pendingGuestCallbacks.begin()->first);
|
||||
nextWake = timrman.nextEventCycle(nextWake);
|
||||
totalCycles = std::max(totalCycles + 1u, std::min(target, nextWake));
|
||||
continue;
|
||||
}
|
||||
@@ -1060,16 +679,21 @@ namespace ps2x::iop::detail
|
||||
|
||||
IopHost &host;
|
||||
IopMemory memory;
|
||||
IopCdvd cdvd;
|
||||
IopSysmem sysmem;
|
||||
IopKernel kernel;
|
||||
IopCdvd cdvd;
|
||||
IopVblank vblank;
|
||||
IopRpcBridge rpc;
|
||||
IopSysclib sysclib;
|
||||
IopStdio stdio;
|
||||
IopHeaplib heaplib;
|
||||
IopIntrman intrman;
|
||||
IopTimrman timrman;
|
||||
IopIoman ioman;
|
||||
IopCpuCore cpuCore;
|
||||
IopImportRegistry imports;
|
||||
IopLoadcore loadcore;
|
||||
std::map<int, Module> modules;
|
||||
std::unordered_map<int, InterruptHandler> interruptHandlers;
|
||||
std::vector<IomanDevice> iomanDevices;
|
||||
std::map<int, bool> interruptEnabled;
|
||||
std::map<int, uint64_t> pendingDmaInterrupts;
|
||||
std::multimap<uint64_t, ScheduledGuestCallback> pendingGuestCallbacks;
|
||||
uint32_t nextModuleId = 1;
|
||||
@@ -1138,6 +762,45 @@ namespace ps2x::iop::detail
|
||||
m_impl->rpc.onSifTransfer(transfer);
|
||||
}
|
||||
|
||||
uint32_t IopEmulator::allocateMemory(uint32_t size, uint32_t alignment)
|
||||
{
|
||||
return m_impl->memory.allocate(size, alignment);
|
||||
}
|
||||
|
||||
bool IopEmulator::freeMemory(uint32_t address)
|
||||
{
|
||||
return m_impl->memory.freeAllocation(address);
|
||||
}
|
||||
|
||||
bool IopEmulator::readMemory(uint32_t address, void *destination, size_t size) const
|
||||
{
|
||||
return isMemoryRange(address, size) &&
|
||||
m_impl->memory.readRam(address, destination, size);
|
||||
}
|
||||
|
||||
bool IopEmulator::writeMemory(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
return isMemoryRange(address, size) &&
|
||||
m_impl->memory.writeRam(address, source, size);
|
||||
}
|
||||
|
||||
bool IopEmulator::zeroMemory(uint32_t address, size_t size)
|
||||
{
|
||||
return isMemoryRange(address, size) &&
|
||||
m_impl->memory.zeroRam(address, size);
|
||||
}
|
||||
|
||||
bool IopEmulator::isMemoryRange(uint32_t address, size_t size) const
|
||||
{
|
||||
const bool physicalSegment = address < IopMemory::RamSize;
|
||||
const bool cachedSegment = address >= 0x80000000u && address < 0x80200000u;
|
||||
const bool uncachedSegment = address >= 0xA0000000u && address < 0xA0200000u;
|
||||
if (!physicalSegment && !cachedSegment && !uncachedSegment)
|
||||
return false;
|
||||
const uint32_t physical = IopMemory::physicalAddress(address);
|
||||
return physical <= IopMemory::RamSize && size <= IopMemory::RamSize - physical;
|
||||
}
|
||||
|
||||
uint64_t IopEmulator::cycles() const noexcept
|
||||
{
|
||||
return m_impl->totalCycles;
|
||||
|
||||
@@ -29,6 +29,13 @@ namespace ps2x::iop::detail
|
||||
[[nodiscard]] bool hasRpcServer(uint32_t sid) const noexcept;
|
||||
void onSifTransfer(const SifTransfer &transfer);
|
||||
|
||||
[[nodiscard]] uint32_t allocateMemory(uint32_t size, uint32_t alignment = 16u);
|
||||
[[nodiscard]] bool freeMemory(uint32_t address);
|
||||
[[nodiscard]] bool readMemory(uint32_t address, void *destination, size_t size) const;
|
||||
[[nodiscard]] bool writeMemory(uint32_t address, const void *source, size_t size);
|
||||
[[nodiscard]] bool zeroMemory(uint32_t address, size_t size);
|
||||
[[nodiscard]] bool isMemoryRange(uint32_t address, size_t size) const;
|
||||
|
||||
[[nodiscard]] uint64_t cycles() const noexcept;
|
||||
[[nodiscard]] uint64_t instructions() const noexcept;
|
||||
[[nodiscard]] uint32_t loadedModuleCount() const noexcept;
|
||||
|
||||
+7
-18
@@ -1,6 +1,6 @@
|
||||
#include "iop_module_loader.h"
|
||||
|
||||
#include "iop_memory.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "ps2x/iop/iop_subsystem.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -170,9 +170,7 @@ namespace ps2x::iop::detail
|
||||
if (relsec.link < sections.size())
|
||||
{
|
||||
const Elf32Shdr &symsec = sections[relsec.link];
|
||||
if (symsec.type == SHT_SYMTAB &&
|
||||
symsec.entsize >= sizeof(Elf32Sym) &&
|
||||
checkedRange(image.size(), symsec.offset, symsec.size))
|
||||
if (symsec.type == SHT_SYMTAB && symsec.entsize >= sizeof(Elf32Sym) && checkedRange(image.size(), symsec.offset, symsec.size))
|
||||
{
|
||||
const size_t count = symsec.size / symsec.entsize;
|
||||
symbolStorage.resize(count);
|
||||
@@ -198,9 +196,7 @@ namespace ps2x::iop::detail
|
||||
if (relsec.type == SHT_RELA)
|
||||
{
|
||||
Elf32Rela relocation{};
|
||||
std::memcpy(&relocation,
|
||||
image.data() + relsec.offset + offset,
|
||||
sizeof(relocation));
|
||||
std::memcpy(&relocation, image.data() + relsec.offset + offset, sizeof(relocation));
|
||||
relocationOffset = relocation.offset;
|
||||
relocationInfo = relocation.info;
|
||||
explicitAddend = relocation.addend;
|
||||
@@ -224,8 +220,7 @@ namespace ps2x::iop::detail
|
||||
symbolValue = symbol.value;
|
||||
if (symbol.shndx != 0u)
|
||||
{
|
||||
symbolValue = static_cast<uint32_t>(
|
||||
static_cast<int64_t>(symbolValue) + delta);
|
||||
symbolValue = static_cast<uint32_t>(static_cast<int64_t>(symbolValue) + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,16 +253,12 @@ namespace ps2x::iop::detail
|
||||
break;
|
||||
case R_MIPS_32:
|
||||
case R_MIPS_REL32:
|
||||
memory.write32(place,
|
||||
static_cast<uint32_t>(
|
||||
static_cast<int64_t>(addend) + symbolValue));
|
||||
memory.write32(place, static_cast<uint32_t>(static_cast<int64_t>(addend) + symbolValue));
|
||||
break;
|
||||
case R_MIPS_26:
|
||||
{
|
||||
const uint32_t target = ((word & 0x03FFFFFFu) << 2u) + symbolValue;
|
||||
memory.write32(place,
|
||||
(word & 0xFC000000u) |
|
||||
((target >> 2u) & 0x03FFFFFFu));
|
||||
memory.write32(place, (word & 0xFC000000u) | ((target >> 2u) & 0x03FFFFFFu));
|
||||
break;
|
||||
}
|
||||
case R_MIPS_HI16:
|
||||
@@ -501,9 +492,7 @@ namespace ps2x::iop::detail
|
||||
else
|
||||
{
|
||||
if (!checkedRange(image.size(), section.offset, section.size) ||
|
||||
!memory.writeRam(destination,
|
||||
image.data() + section.offset,
|
||||
section.size))
|
||||
!memory.writeRam(destination, image.data() + section.offset, section.size))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "iop_rpc.h"
|
||||
|
||||
#include "iop_cpu.h"
|
||||
#include "iop_kernel.h"
|
||||
#include "iop_memory.h"
|
||||
#include "../core/iop_cpu.h"
|
||||
#include "../core/iop_kernel.h"
|
||||
#include "../core/iop_memory.h"
|
||||
#include "ps2x/iop/iop_host.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
@@ -119,8 +120,6 @@ namespace ps2x::iop::detail
|
||||
return true;
|
||||
}
|
||||
case 8: // sceSifDmaStat
|
||||
// Transfers are applied synchronously above. The IOP API reports
|
||||
// a negative value once the transaction is no longer active.
|
||||
setV0(0xFFFFFFFFu);
|
||||
return true;
|
||||
case 29: // sceSifCheckInit
|
||||
@@ -148,13 +147,67 @@ namespace ps2x::iop::detail
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14: // InitRpc
|
||||
case 15:
|
||||
case 16:
|
||||
setV0(0);
|
||||
return true;
|
||||
case 12: // sceSifSendCmd
|
||||
case 13: // isceSifSendCmd
|
||||
{
|
||||
constexpr uint32_t kHeaderSize = 16u;
|
||||
constexpr uint32_t kMaxPacketSize = 112u;
|
||||
const uint32_t commandId = cpu.gpr[4];
|
||||
const uint32_t packetAddress = cpu.gpr[5];
|
||||
const uint32_t packetSize = cpu.gpr[6];
|
||||
const uint32_t extraSource = cpu.gpr[7];
|
||||
const uint32_t stackPointer = cpu.gpr[29];
|
||||
const uint32_t extraDestination = m_memory.read32(stackPointer + 16u);
|
||||
const int32_t signedExtraSize = static_cast<int32_t>(m_memory.read32(stackPointer + 20u));
|
||||
|
||||
if (packetAddress == 0u || packetSize < kHeaderSize || packetSize > kMaxPacketSize ||
|
||||
!m_memory.ownsRamRange(packetAddress, packetSize))
|
||||
{
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<uint8_t, kMaxPacketSize> packet{};
|
||||
if (!m_memory.readRam(packetAddress, packet.data(), packetSize))
|
||||
{
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t extraSize = 0u;
|
||||
if (signedExtraSize > 0)
|
||||
{
|
||||
extraSize = static_cast<uint32_t>(signedExtraSize);
|
||||
if (extraSource == 0u || extraDestination == 0u ||
|
||||
!m_memory.ownsRamRange(extraSource, extraSize) ||
|
||||
!m_host.writeGuest(extraDestination, m_memory.ram().data() + IopMemory::physicalAddress(extraSource), extraSize))
|
||||
{
|
||||
setV0(0u);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t sizeWord = packetSize | (extraSize << 8u);
|
||||
std::memcpy(packet.data() + 0u, &sizeWord, sizeof(sizeWord));
|
||||
std::memcpy(packet.data() + 4u, &extraDestination, sizeof(extraDestination));
|
||||
std::memcpy(packet.data() + 8u, &commandId, sizeof(commandId));
|
||||
|
||||
if (!m_host.sendSifCommand(commandId, packet.data(), packetSize))
|
||||
{
|
||||
// A command without an EE handler is still a completed DMA on real hardware. Only malformed packets fail above.
|
||||
}
|
||||
|
||||
const uint32_t dmaId = m_nextDmaId++;
|
||||
if (m_nextDmaId == 0u || m_nextDmaId > static_cast<uint32_t>(std::numeric_limits<int32_t>::max()))
|
||||
m_nextDmaId = 1u;
|
||||
setV0(dmaId);
|
||||
return true;
|
||||
}
|
||||
case 17: // sceSifRegisterRpc
|
||||
{
|
||||
RpcServer server;
|
||||
@@ -255,8 +308,7 @@ namespace ps2x::iop::detail
|
||||
const uint32_t copySize = std::min<uint32_t>(request.receive.size, IopMemory::RamSize - physical);
|
||||
(void)m_host.writeGuest(request.receive.address, m_memory.ram().data() + physical, copySize);
|
||||
if (copySize < request.receive.size)
|
||||
(void)m_host.zeroGuest(request.receive.address + copySize,
|
||||
request.receive.size - copySize);
|
||||
(void)m_host.zeroGuest(request.receive.address + copySize, request.receive.size - copySize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,39 +322,11 @@ namespace ps2x::iop::detail
|
||||
|
||||
void IopRpcBridge::onSifTransfer(const SifTransfer &transfer)
|
||||
{
|
||||
if (transfer.size == 0u)
|
||||
return;
|
||||
|
||||
if (transfer.kind == SifTransferKind::SetDma)
|
||||
{
|
||||
// sceSifSetDma transfers EE memory to IOP RAM. The runtime performs its
|
||||
// legacy EE-side mirror first, then gives the physical emulator the same
|
||||
// payload so the IOP observes it at the requested destination.
|
||||
if (transfer.phase != SifTransferPhase::AfterCopy)
|
||||
return;
|
||||
const uint32_t destination = IopMemory::physicalAddress(transfer.destinationAddress);
|
||||
if (destination >= IopMemory::RamSize)
|
||||
return;
|
||||
const size_t copySize = std::min<size_t>(transfer.size, IopMemory::RamSize - destination);
|
||||
std::vector<uint8_t> data(copySize);
|
||||
if (m_host.readGuest(transfer.sourceAddress, data.data(), data.size()))
|
||||
(void)m_memory.writeRam(destination, data.data(), data.size());
|
||||
return;
|
||||
}
|
||||
|
||||
if (transfer.kind == SifTransferKind::GetOtherData && transfer.phase == SifTransferPhase::BeforeCopy)
|
||||
{
|
||||
// sceSifGetOtherData is the reverse direction: its source is an IOP
|
||||
// address and its destination is in EE memory. Stage the physical IOP
|
||||
// bytes at the source's EE mirror before the runtime performs the copy.
|
||||
const uint32_t source = IopMemory::physicalAddress(transfer.sourceAddress);
|
||||
if (source >= IopMemory::RamSize)
|
||||
return;
|
||||
const size_t copySize = std::min<size_t>(transfer.size, IopMemory::RamSize - source);
|
||||
if (!m_memory.ownsRamRange(source, copySize))
|
||||
return;
|
||||
(void)m_host.writeGuest(transfer.sourceAddress, m_memory.ram().data() + source, copySize);
|
||||
}
|
||||
// The EE SIF transport owns the actual directional memory movement.
|
||||
// Services still receive both phases through IopSubsystem, but mirroring
|
||||
// IOP bytes through an equal-numbered EE address would alias two distinct
|
||||
// PS2 address spaces and can overwrite live game data.
|
||||
(void)transfer;
|
||||
}
|
||||
|
||||
void IopRpcBridge::removeServersInRange(uint32_t base, uint32_t size)
|
||||
@@ -28,6 +28,16 @@ namespace ps2x::iop::detail
|
||||
uint32_t a2,
|
||||
uint32_t a3,
|
||||
uint32_t gp) = 0;
|
||||
[[nodiscard]] virtual uint32_t executeGuestFunctionWithBudget(uint32_t address,
|
||||
uint32_t a0,
|
||||
uint32_t a1,
|
||||
uint32_t a2,
|
||||
uint32_t a3,
|
||||
uint32_t gp,
|
||||
uint32_t instructionBudget)
|
||||
{
|
||||
return executeGuestFunction(address, a0, a1, a2, a3, gp);
|
||||
}
|
||||
};
|
||||
|
||||
class IopRpcBridge
|
||||
@@ -0,0 +1,177 @@
|
||||
#include "iop_module_manager.h"
|
||||
|
||||
#include "ps2x/iop/ps2_path.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
IopModuleManager::IopModuleManager()
|
||||
{
|
||||
// ROM modules that the no-BIOS HLE environment can legitimately provide.
|
||||
// Entries with RPC services become routable only after load.
|
||||
constexpr std::string_view modules[] = {
|
||||
"sysmem",
|
||||
"loadcore",
|
||||
"intrman",
|
||||
"sifman",
|
||||
"sifcmd",
|
||||
"sifinit",
|
||||
"ioman",
|
||||
"iomanx",
|
||||
"modload",
|
||||
"stdio",
|
||||
"sysclib",
|
||||
"thbase",
|
||||
"thevent",
|
||||
"thsemap",
|
||||
"thmsgbx",
|
||||
"timrman",
|
||||
"vblank",
|
||||
"secrman",
|
||||
"sio2man",
|
||||
"sio2d",
|
||||
"padman",
|
||||
"mcman",
|
||||
"mcserv",
|
||||
"libsd",
|
||||
"cdvdman",
|
||||
"cdvdfsv",
|
||||
"dev9",
|
||||
"usbd",
|
||||
"usbhdfsd",
|
||||
"udnl",
|
||||
"fileio",
|
||||
"poweroff",
|
||||
"netman",
|
||||
"ps2ip",
|
||||
"dbcman",
|
||||
"dbcm",
|
||||
};
|
||||
for (const std::string_view module : modules)
|
||||
m_builtinKeys.emplace(module);
|
||||
}
|
||||
|
||||
void IopModuleManager::reset()
|
||||
{
|
||||
m_records.clear();
|
||||
m_hleIdsByKey.clear();
|
||||
m_loadedKeyReferences.clear();
|
||||
m_nextHleId = 0x40000000;
|
||||
}
|
||||
|
||||
void IopModuleManager::setServiceModuleKeys(std::vector<std::string> keys)
|
||||
{
|
||||
m_serviceKeys.clear();
|
||||
for (std::string &key : keys)
|
||||
{
|
||||
const std::string normalized = ps2PathLeafKey(key);
|
||||
if (!normalized.empty())
|
||||
m_serviceKeys.emplace(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
ModuleLoadResult IopModuleManager::loadHle(std::string_view path)
|
||||
{
|
||||
ModuleLoadResult result{true, -1, -1};
|
||||
const std::string key = ps2PathLeafKey(path);
|
||||
if (key.empty() || (!m_builtinKeys.contains(key) && !m_serviceKeys.contains(key)))
|
||||
return result;
|
||||
|
||||
const auto existing = m_hleIdsByKey.find(key);
|
||||
if (existing != m_hleIdsByKey.end())
|
||||
{
|
||||
Record &record = m_records[existing->second];
|
||||
++record.references;
|
||||
addLoadedKey(key);
|
||||
result.moduleId = existing->second;
|
||||
result.startResult = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (m_nextHleId <= 0)
|
||||
return result;
|
||||
const int32_t id = m_nextHleId++;
|
||||
m_records.emplace(id, Record{key, 1u, false});
|
||||
m_hleIdsByKey.emplace(key, id);
|
||||
addLoadedKey(key);
|
||||
result.moduleId = id;
|
||||
result.startResult = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void IopModuleManager::observePhysicalLoad(int32_t moduleId, std::string_view path)
|
||||
{
|
||||
if (moduleId <= 0)
|
||||
return;
|
||||
const std::string key = ps2PathLeafKey(path);
|
||||
if (key.empty())
|
||||
return;
|
||||
m_records[moduleId] = Record{key, 1u, true};
|
||||
addLoadedKey(key);
|
||||
}
|
||||
|
||||
bool IopModuleManager::stopHle(int32_t moduleId, int32_t *result)
|
||||
{
|
||||
const auto found = m_records.find(moduleId);
|
||||
if (found == m_records.end() || found->second.physical)
|
||||
return false;
|
||||
|
||||
Record &record = found->second;
|
||||
removeLoadedKey(record.key);
|
||||
if (record.references > 1u)
|
||||
{
|
||||
--record.references;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hleIdsByKey.erase(record.key);
|
||||
m_records.erase(found);
|
||||
}
|
||||
if (result)
|
||||
*result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void IopModuleManager::observePhysicalStop(int32_t moduleId)
|
||||
{
|
||||
const auto found = m_records.find(moduleId);
|
||||
if (found == m_records.end() || !found->second.physical)
|
||||
return;
|
||||
removeLoadedKey(found->second.key);
|
||||
m_records.erase(found);
|
||||
}
|
||||
|
||||
bool IopModuleManager::isLoaded(std::span<const std::string_view> aliases) const
|
||||
{
|
||||
if (aliases.empty())
|
||||
return true;
|
||||
return std::any_of(aliases.begin(), aliases.end(), [&](std::string_view alias)
|
||||
{
|
||||
const std::string key = ps2PathLeafKey(alias);
|
||||
const auto found = m_loadedKeyReferences.find(key);
|
||||
return found != m_loadedKeyReferences.end() && found->second != 0u; });
|
||||
}
|
||||
|
||||
bool IopModuleManager::recognizes(std::string_view path) const
|
||||
{
|
||||
const std::string key = ps2PathLeafKey(path);
|
||||
return m_builtinKeys.contains(key) || m_serviceKeys.contains(key);
|
||||
}
|
||||
|
||||
void IopModuleManager::addLoadedKey(std::string_view key)
|
||||
{
|
||||
++m_loadedKeyReferences[std::string(key)];
|
||||
}
|
||||
|
||||
void IopModuleManager::removeLoadedKey(std::string_view key)
|
||||
{
|
||||
const auto found = m_loadedKeyReferences.find(std::string(key));
|
||||
if (found == m_loadedKeyReferences.end())
|
||||
return;
|
||||
if (found->second > 1u)
|
||||
--found->second;
|
||||
else
|
||||
m_loadedKeyReferences.erase(found);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "ps2x/iop/iop_types.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace ps2x::iop::detail
|
||||
{
|
||||
class IopModuleManager
|
||||
{
|
||||
public:
|
||||
IopModuleManager();
|
||||
|
||||
void reset();
|
||||
void setServiceModuleKeys(std::vector<std::string> keys);
|
||||
|
||||
[[nodiscard]] ModuleLoadResult loadHle(std::string_view path);
|
||||
void observePhysicalLoad(int32_t moduleId, std::string_view path);
|
||||
[[nodiscard]] bool stopHle(int32_t moduleId, int32_t *result);
|
||||
void observePhysicalStop(int32_t moduleId);
|
||||
|
||||
[[nodiscard]] bool isLoaded(std::span<const std::string_view> aliases) const;
|
||||
[[nodiscard]] bool recognizes(std::string_view path) const;
|
||||
|
||||
private:
|
||||
struct Record
|
||||
{
|
||||
std::string key;
|
||||
uint32_t references = 0u;
|
||||
bool physical = false;
|
||||
};
|
||||
|
||||
void addLoadedKey(std::string_view key);
|
||||
void removeLoadedKey(std::string_view key);
|
||||
|
||||
std::unordered_set<std::string> m_builtinKeys;
|
||||
std::unordered_set<std::string> m_serviceKeys;
|
||||
std::unordered_map<int32_t, Record> m_records;
|
||||
std::unordered_map<std::string, int32_t> m_hleIdsByKey;
|
||||
std::unordered_map<std::string, uint32_t> m_loadedKeyReferences;
|
||||
int32_t m_nextHleId = 0x40000000;
|
||||
};
|
||||
}
|
||||
@@ -18,6 +18,13 @@ namespace ps2x::iop::detail
|
||||
|
||||
[[nodiscard]] virtual std::string_view name() const = 0;
|
||||
[[nodiscard]] virtual std::span<const uint32_t> sids() const = 0;
|
||||
// A service with aliases is dormant until one of these IOP modules is
|
||||
// actually loaded. Profile services can omit aliases when the profile
|
||||
// itself is the explicit compatibility contract.
|
||||
[[nodiscard]] virtual std::span<const std::string_view> moduleAliases() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual void reset() = 0;
|
||||
|
||||
[[nodiscard]] virtual RpcAbi selectRpcAbi(const RpcAbiRequest &request) const
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "ps2x/iop/iop_subsystem.h"
|
||||
|
||||
#include "iop_service.h"
|
||||
#include "iop_module_manager.h"
|
||||
#include "emulator/iop_emulator.h"
|
||||
#include "plugin_loader.h"
|
||||
#include "ps2x/iop/ps2_path.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -69,11 +71,39 @@ namespace ps2x::iop
|
||||
{
|
||||
public:
|
||||
explicit Impl(IopHost &hostRef)
|
||||
: host(hostRef), pluginCatalog(hostRef), coreServices(detail::createCoreServices(hostRef)), profiles(detail::createBuiltinProfiles()), emulator(hostRef)
|
||||
: host(hostRef),
|
||||
pluginCatalog(hostRef),
|
||||
coreServices(detail::createCoreServices(hostRef)),
|
||||
profiles(detail::createBuiltinProfiles()),
|
||||
emulator(hostRef)
|
||||
{
|
||||
refreshServiceModuleKeys();
|
||||
rebuildRoutes();
|
||||
}
|
||||
|
||||
bool serviceActive(const detail::IopService &service) const
|
||||
{
|
||||
return moduleManager.isLoaded(service.moduleAliases());
|
||||
}
|
||||
|
||||
void refreshServiceModuleKeys()
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
auto collect = [&](const detail::ServiceList &services)
|
||||
{
|
||||
for (const auto &service : services)
|
||||
{
|
||||
if (!service)
|
||||
continue;
|
||||
for (std::string_view alias : service->moduleAliases())
|
||||
keys.emplace_back(alias);
|
||||
}
|
||||
};
|
||||
collect(coreServices);
|
||||
collect(profileServices);
|
||||
moduleManager.setServiceModuleKeys(std::move(keys));
|
||||
}
|
||||
|
||||
void rebuildRoutes()
|
||||
{
|
||||
routes.clear();
|
||||
@@ -82,7 +112,7 @@ namespace ps2x::iop
|
||||
std::unordered_map<uint32_t, detail::IopService *> layer;
|
||||
for (const auto &service : services)
|
||||
{
|
||||
if (!service)
|
||||
if (!service || !serviceActive(*service))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -119,6 +149,7 @@ namespace ps2x::iop
|
||||
std::string activeProvider;
|
||||
std::string lastError;
|
||||
bool routesValid = true;
|
||||
detail::IopModuleManager moduleManager;
|
||||
detail::IopEmulator emulator;
|
||||
};
|
||||
|
||||
@@ -180,6 +211,7 @@ namespace ps2x::iop
|
||||
{
|
||||
*error = m_impl->lastError;
|
||||
}
|
||||
m_impl->refreshServiceModuleKeys();
|
||||
m_impl->rebuildRoutes();
|
||||
return false;
|
||||
}
|
||||
@@ -199,6 +231,7 @@ namespace ps2x::iop
|
||||
{
|
||||
*error = m_impl->lastError;
|
||||
}
|
||||
m_impl->refreshServiceModuleKeys();
|
||||
m_impl->rebuildRoutes();
|
||||
return false;
|
||||
}
|
||||
@@ -209,11 +242,13 @@ namespace ps2x::iop
|
||||
{
|
||||
*error = m_impl->lastError;
|
||||
}
|
||||
m_impl->refreshServiceModuleKeys();
|
||||
m_impl->rebuildRoutes();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_impl->refreshServiceModuleKeys();
|
||||
m_impl->rebuildRoutes();
|
||||
if (!m_impl->routesValid)
|
||||
{
|
||||
@@ -221,6 +256,7 @@ namespace ps2x::iop
|
||||
m_impl->profileServices.clear();
|
||||
m_impl->activeProfile.clear();
|
||||
m_impl->activeProvider.clear();
|
||||
m_impl->refreshServiceModuleKeys();
|
||||
m_impl->rebuildRoutes();
|
||||
m_impl->lastError = routeError;
|
||||
if (error)
|
||||
@@ -236,6 +272,7 @@ namespace ps2x::iop
|
||||
|
||||
void IopSubsystem::reset()
|
||||
{
|
||||
m_impl->moduleManager.reset();
|
||||
for (auto &service : m_impl->coreServices)
|
||||
{
|
||||
if (service)
|
||||
@@ -251,11 +288,31 @@ namespace ps2x::iop
|
||||
}
|
||||
}
|
||||
m_impl->emulator.reset();
|
||||
m_impl->refreshServiceModuleKeys();
|
||||
m_impl->rebuildRoutes();
|
||||
}
|
||||
|
||||
ModuleLoadResult IopSubsystem::loadModule(std::string_view path, const void *arguments, uint32_t argumentSize)
|
||||
{
|
||||
return m_impl->emulator.loadModule(path, arguments, argumentSize);
|
||||
const ParsedPs2Path parsed = parsePs2Path(path);
|
||||
if (!parsed)
|
||||
return {true, -1, -1};
|
||||
|
||||
if (parsed.device != Ps2PathDevice::Rom0)
|
||||
{
|
||||
ModuleLoadResult physical = m_impl->emulator.loadModule(path, arguments, argumentSize);
|
||||
if (physical.moduleId > 0)
|
||||
{
|
||||
m_impl->moduleManager.observePhysicalLoad(physical.moduleId, path);
|
||||
m_impl->rebuildRoutes();
|
||||
return physical;
|
||||
}
|
||||
}
|
||||
|
||||
ModuleLoadResult hle = m_impl->moduleManager.loadHle(path);
|
||||
if (hle.moduleId > 0)
|
||||
m_impl->rebuildRoutes();
|
||||
return hle;
|
||||
}
|
||||
|
||||
ModuleLoadResult IopSubsystem::loadModuleBuffer(uint32_t guestAddress, const void *arguments, uint32_t argumentSize)
|
||||
@@ -265,7 +322,16 @@ namespace ps2x::iop
|
||||
|
||||
bool IopSubsystem::stopModule(int32_t moduleId, int32_t *result)
|
||||
{
|
||||
return m_impl->emulator.stopModule(moduleId, result);
|
||||
if (m_impl->moduleManager.stopHle(moduleId, result))
|
||||
{
|
||||
m_impl->rebuildRoutes();
|
||||
return true;
|
||||
}
|
||||
if (!m_impl->emulator.stopModule(moduleId, result))
|
||||
return false;
|
||||
m_impl->moduleManager.observePhysicalStop(moduleId);
|
||||
m_impl->rebuildRoutes();
|
||||
return true;
|
||||
}
|
||||
|
||||
void IopSubsystem::runEeCycles(uint64_t eeCycles) noexcept
|
||||
@@ -277,7 +343,7 @@ namespace ps2x::iop
|
||||
{
|
||||
for (const auto &service : m_impl->profileServices)
|
||||
{
|
||||
if (service)
|
||||
if (service && m_impl->serviceActive(*service))
|
||||
{
|
||||
const RpcAbi selected = service->selectRpcAbi(request);
|
||||
if (selected != RpcAbi::RuntimeDefault)
|
||||
@@ -288,7 +354,7 @@ namespace ps2x::iop
|
||||
}
|
||||
for (const auto &service : m_impl->coreServices)
|
||||
{
|
||||
if (service)
|
||||
if (service && m_impl->serviceActive(*service))
|
||||
{
|
||||
const RpcAbi selected = service->selectRpcAbi(request);
|
||||
if (selected != RpcAbi::RuntimeDefault)
|
||||
@@ -341,14 +407,14 @@ namespace ps2x::iop
|
||||
{
|
||||
for (auto &service : m_impl->coreServices)
|
||||
{
|
||||
if (service)
|
||||
if (service && m_impl->serviceActive(*service))
|
||||
{
|
||||
service->onSifTransfer(transfer);
|
||||
}
|
||||
}
|
||||
for (auto &service : m_impl->profileServices)
|
||||
{
|
||||
if (service)
|
||||
if (service && m_impl->serviceActive(*service))
|
||||
{
|
||||
service->onSifTransfer(transfer);
|
||||
}
|
||||
@@ -356,6 +422,36 @@ namespace ps2x::iop
|
||||
m_impl->emulator.onSifTransfer(transfer);
|
||||
}
|
||||
|
||||
uint32_t IopSubsystem::allocateMemory(uint32_t size, uint32_t alignment)
|
||||
{
|
||||
return m_impl->emulator.allocateMemory(size, alignment);
|
||||
}
|
||||
|
||||
bool IopSubsystem::freeMemory(uint32_t address)
|
||||
{
|
||||
return m_impl->emulator.freeMemory(address);
|
||||
}
|
||||
|
||||
bool IopSubsystem::readMemory(uint32_t address, void *destination, size_t size) const
|
||||
{
|
||||
return m_impl->emulator.readMemory(address, destination, size);
|
||||
}
|
||||
|
||||
bool IopSubsystem::writeMemory(uint32_t address, const void *source, size_t size)
|
||||
{
|
||||
return m_impl->emulator.writeMemory(address, source, size);
|
||||
}
|
||||
|
||||
bool IopSubsystem::zeroMemory(uint32_t address, size_t size)
|
||||
{
|
||||
return m_impl->emulator.zeroMemory(address, size);
|
||||
}
|
||||
|
||||
bool IopSubsystem::isMemoryRange(uint32_t address, size_t size) const
|
||||
{
|
||||
return m_impl->emulator.isMemoryRange(address, size);
|
||||
}
|
||||
|
||||
DebugSnapshot IopSubsystem::debugSnapshot() const
|
||||
{
|
||||
DebugSnapshot snapshot;
|
||||
@@ -384,6 +480,7 @@ namespace ps2x::iop
|
||||
row.name = service->name();
|
||||
row.sids.assign(service->sids().begin(), service->sids().end());
|
||||
row.profileSpecific = profileSpecific;
|
||||
row.active = m_impl->serviceActive(*service);
|
||||
service->appendDebugMetrics(row.metrics);
|
||||
snapshot.services.push_back(std::move(row));
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace ps2x::iop::detail
|
||||
uint32_t normalizedSource = 0u;
|
||||
uint32_t normalizedDestination = 0u;
|
||||
if (!m_host.normalizeGuestAddress(transfer.sourceAddress, normalizedSource) ||
|
||||
!m_host.normalizeGuestAddress(transfer.destinationAddress, normalizedDestination))
|
||||
!m_host.normalizeIopAddress(transfer.destinationAddress, normalizedDestination))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -333,6 +333,13 @@ namespace ps2x::iop::detail
|
||||
return normalized;
|
||||
}
|
||||
|
||||
uint32_t normalizeIopAddress(uint32_t address) const
|
||||
{
|
||||
uint32_t normalized = 0u;
|
||||
(void)m_host.normalizeIopAddress(address, normalized);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
RpcResult handleCreateTransport(const RpcRequest &request, RpcResult result)
|
||||
{
|
||||
uint32_t dtxId = 0u;
|
||||
@@ -351,7 +358,7 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
|
||||
const uint32_t normalizedEeWorkAddress = normalizeAddress(eeWorkAddress);
|
||||
const uint32_t normalizedIopWorkAddress = normalizeAddress(iopWorkAddress);
|
||||
const uint32_t normalizedIopWorkAddress = normalizeIopAddress(iopWorkAddress);
|
||||
|
||||
uint32_t remoteHandle = 0u;
|
||||
{
|
||||
|
||||
@@ -33,6 +33,11 @@ namespace ps2x::iop::detail
|
||||
return kSids;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<const std::string_view> moduleAliases() const override
|
||||
{
|
||||
return kModuleAliases;
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
@@ -99,6 +104,7 @@ namespace ps2x::iop::detail
|
||||
|
||||
private:
|
||||
inline static constexpr std::array<uint32_t, 1> kSids{kDbcManSid};
|
||||
inline static constexpr std::array<std::string_view, 3> kModuleAliases{"dbcman", "dbcm", "dbcmserv"};
|
||||
|
||||
IopHost &m_host;
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
@@ -27,6 +27,11 @@ namespace ps2x::iop::detail
|
||||
return kSids;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::span<const std::string_view> moduleAliases() const override
|
||||
{
|
||||
return kModuleAliases;
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
}
|
||||
@@ -51,6 +56,7 @@ namespace ps2x::iop::detail
|
||||
|
||||
private:
|
||||
inline static constexpr std::array<uint32_t, 1> kSids{kLibSdSid};
|
||||
inline static constexpr std::array<std::string_view, 1> kModuleAliases{"libsd"};
|
||||
|
||||
IopHost &m_host;
|
||||
};
|
||||
|
||||
@@ -82,43 +82,75 @@ namespace ps2x::iop::detail
|
||||
flavor = Flavor::NewXmcserv;
|
||||
switch (function)
|
||||
{
|
||||
case 0xFEu: return Operation::Init;
|
||||
case 0x01u: return Operation::GetInfo;
|
||||
case 0x02u: return Operation::Open;
|
||||
case 0x03u: return Operation::Close;
|
||||
case 0x04u: return Operation::Seek;
|
||||
case 0x05u: return Operation::Read;
|
||||
case 0x06u: return Operation::Write;
|
||||
case 0x0Au: return Operation::Flush;
|
||||
case 0x0Cu: return Operation::Chdir;
|
||||
case 0x0Du: return Operation::GetDir;
|
||||
case 0x0Eu: return Operation::SetInfo;
|
||||
case 0x0Fu: return Operation::Delete;
|
||||
case 0x10u: return Operation::Format;
|
||||
case 0x11u: return Operation::Unformat;
|
||||
case 0x12u: return Operation::GetEnt;
|
||||
case 0x14u: return Operation::ChangePriority;
|
||||
default: break;
|
||||
case 0xFEu:
|
||||
return Operation::Init;
|
||||
case 0x01u:
|
||||
return Operation::GetInfo;
|
||||
case 0x02u:
|
||||
return Operation::Open;
|
||||
case 0x03u:
|
||||
return Operation::Close;
|
||||
case 0x04u:
|
||||
return Operation::Seek;
|
||||
case 0x05u:
|
||||
return Operation::Read;
|
||||
case 0x06u:
|
||||
return Operation::Write;
|
||||
case 0x0Au:
|
||||
return Operation::Flush;
|
||||
case 0x0Cu:
|
||||
return Operation::Chdir;
|
||||
case 0x0Du:
|
||||
return Operation::GetDir;
|
||||
case 0x0Eu:
|
||||
return Operation::SetInfo;
|
||||
case 0x0Fu:
|
||||
return Operation::Delete;
|
||||
case 0x10u:
|
||||
return Operation::Format;
|
||||
case 0x11u:
|
||||
return Operation::Unformat;
|
||||
case 0x12u:
|
||||
return Operation::GetEnt;
|
||||
case 0x14u:
|
||||
return Operation::ChangePriority;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
flavor = Flavor::OldMcserv;
|
||||
switch (function)
|
||||
{
|
||||
case 0x70u: return Operation::Init;
|
||||
case 0x71u: return Operation::Open;
|
||||
case 0x72u: return Operation::Close;
|
||||
case 0x73u: return Operation::Read;
|
||||
case 0x74u: return Operation::Write;
|
||||
case 0x75u: return Operation::Seek;
|
||||
case 0x76u: return Operation::GetDir;
|
||||
case 0x77u: return Operation::Format;
|
||||
case 0x78u: return Operation::GetInfo;
|
||||
case 0x79u: return Operation::Delete;
|
||||
case 0x7Au: return Operation::Flush;
|
||||
case 0x7Bu: return Operation::Chdir;
|
||||
case 0x7Cu: return Operation::SetInfo;
|
||||
case 0x80u: return Operation::Unformat;
|
||||
default: return Operation::Unknown;
|
||||
case 0x70u:
|
||||
return Operation::Init;
|
||||
case 0x71u:
|
||||
return Operation::Open;
|
||||
case 0x72u:
|
||||
return Operation::Close;
|
||||
case 0x73u:
|
||||
return Operation::Read;
|
||||
case 0x74u:
|
||||
return Operation::Write;
|
||||
case 0x75u:
|
||||
return Operation::Seek;
|
||||
case 0x76u:
|
||||
return Operation::GetDir;
|
||||
case 0x77u:
|
||||
return Operation::Format;
|
||||
case 0x78u:
|
||||
return Operation::GetInfo;
|
||||
case 0x79u:
|
||||
return Operation::Delete;
|
||||
case 0x7Au:
|
||||
return Operation::Flush;
|
||||
case 0x7Bu:
|
||||
return Operation::Chdir;
|
||||
case 0x7Cu:
|
||||
return Operation::SetInfo;
|
||||
case 0x80u:
|
||||
return Operation::Unformat;
|
||||
default:
|
||||
return Operation::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +168,7 @@ namespace ps2x::iop::detail
|
||||
|
||||
[[nodiscard]] std::string_view name() const override { return "MCSERV"; }
|
||||
[[nodiscard]] std::span<const uint32_t> sids() const override { return m_sids; }
|
||||
[[nodiscard]] std::span<const std::string_view> moduleAliases() const override { return m_moduleAliases; }
|
||||
|
||||
void reset() override
|
||||
{
|
||||
@@ -245,8 +278,7 @@ namespace ps2x::iop::detail
|
||||
(void)m_host.writeGuest(receive.address, &result, sizeof(result));
|
||||
if (receive.size > sizeof(result))
|
||||
{
|
||||
(void)m_host.zeroGuest(receive.address + sizeof(result),
|
||||
receive.size - sizeof(result));
|
||||
(void)m_host.zeroGuest(receive.address + sizeof(result), receive.size - sizeof(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,8 +302,7 @@ namespace ps2x::iop::detail
|
||||
uint32_t sendAddress,
|
||||
const NameParameter ¶meter)
|
||||
{
|
||||
const uint32_t nameAddress =
|
||||
sendAddress + static_cast<uint32_t>(offsetof(NameParameter, name));
|
||||
const uint32_t nameAddress = sendAddress + static_cast<uint32_t>(offsetof(NameParameter, name));
|
||||
const uint32_t port = static_cast<uint32_t>(parameter.port);
|
||||
const uint32_t slot = static_cast<uint32_t>(parameter.slot);
|
||||
switch (operation)
|
||||
@@ -281,12 +312,9 @@ namespace ps2x::iop::detail
|
||||
{
|
||||
return call(MemoryCardOperation::Mkdir, port, slot, nameAddress);
|
||||
}
|
||||
return call(MemoryCardOperation::Open,
|
||||
port, slot, nameAddress,
|
||||
static_cast<uint32_t>(parameter.flags));
|
||||
return call(MemoryCardOperation::Open, port, slot, nameAddress, static_cast<uint32_t>(parameter.flags));
|
||||
case Operation::Chdir:
|
||||
return call(MemoryCardOperation::Chdir,
|
||||
port, slot, nameAddress, parameter.pointer);
|
||||
return call(MemoryCardOperation::Chdir, port, slot, nameAddress, parameter.pointer);
|
||||
case Operation::SetInfo:
|
||||
return call(MemoryCardOperation::SetFileInfo, port, slot, nameAddress);
|
||||
case Operation::Delete:
|
||||
@@ -302,9 +330,7 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
}
|
||||
|
||||
int32_t handleDescriptorOperation(Operation operation,
|
||||
Flavor flavor,
|
||||
const DescriptorParameter ¶meter)
|
||||
int32_t handleDescriptorOperation(Operation operation, Flavor flavor, const DescriptorParameter ¶meter)
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
@@ -392,6 +418,7 @@ namespace ps2x::iop::detail
|
||||
mutable std::mutex m_mutex;
|
||||
uint32_t m_unknownRpcLogCount = 0u;
|
||||
const std::array<uint32_t, 2> m_sids = {kMcservSid, kMcservDev9Sid};
|
||||
const std::array<std::string_view, 2> m_moduleAliases = {"mcserv", "xmcserv"};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,19 @@ namespace ps2x::iop::detail
|
||||
return host.writeGuest(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool readIopPod(const IopHost &host, uint32_t address, T &value)
|
||||
{
|
||||
value = {};
|
||||
return host.readIopMemory(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool writeIopPod(IopHost &host, uint32_t address, const T &value)
|
||||
{
|
||||
return host.writeIopMemory(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
template <typename T, size_t Size>
|
||||
bool hasAnyNonZero(const std::array<T, Size> &values)
|
||||
{
|
||||
@@ -174,9 +187,7 @@ namespace ps2x::iop::detail
|
||||
handleCommandBuffer(request.send);
|
||||
result.handled = true;
|
||||
}
|
||||
else if (request.sid == kStateSid &&
|
||||
(request.function == kGetStatusAddressFunction ||
|
||||
request.function == kGetAddressTableFunction))
|
||||
else if (request.sid == kStateSid && (request.function == kGetStatusAddressFunction || request.function == kGetAddressTableFunction))
|
||||
{
|
||||
uint32_t responseAddress = 0u;
|
||||
{
|
||||
@@ -195,8 +206,7 @@ namespace ps2x::iop::detail
|
||||
(void)writeGuestPod(m_host, request.receive.address, responseAddress);
|
||||
if (request.receive.size > sizeof(uint32_t))
|
||||
{
|
||||
(void)m_host.zeroGuest(request.receive.address + sizeof(uint32_t),
|
||||
request.receive.size - sizeof(uint32_t));
|
||||
(void)m_host.zeroGuest(request.receive.address + sizeof(uint32_t), request.receive.size - sizeof(uint32_t));
|
||||
}
|
||||
result.resultAddress = request.receive.address;
|
||||
}
|
||||
@@ -210,7 +220,8 @@ namespace ps2x::iop::detail
|
||||
const auto rule = std::find_if(
|
||||
m_bindings.completionRules.begin(),
|
||||
m_bindings.completionRules.end(),
|
||||
[&](const TsnddrvCompletionRule &candidate) {
|
||||
[&](const TsnddrvCompletionRule &candidate)
|
||||
{
|
||||
return candidate.eeFunction == request.endFunction;
|
||||
});
|
||||
if (rule != m_bindings.completionRules.end())
|
||||
@@ -223,9 +234,7 @@ namespace ps2x::iop::detail
|
||||
if (rule->clearBusy)
|
||||
{
|
||||
constexpr uint32_t idle = 0u;
|
||||
(void)writeGuestPod(m_host,
|
||||
m_bindings.busyFlagAddress,
|
||||
idle);
|
||||
(void)writeGuestPod(m_host, m_bindings.busyFlagAddress, idle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,15 +289,10 @@ namespace ps2x::iop::detail
|
||||
{
|
||||
const TsnddrvGuestArena &arena = m_bindings.arena;
|
||||
const uint32_t statusAddress = alignUp(arena.base, arena.statusAlignment);
|
||||
const uint32_t addressTableAddress =
|
||||
alignUp(statusAddress + kStatusSize, arena.tableAlignment);
|
||||
const uint32_t hdBaseAddress =
|
||||
alignUp(addressTableAddress + (kAddressTableEntries * sizeof(uint32_t)),
|
||||
arena.storageAlignment);
|
||||
const uint32_t sqBaseAddress =
|
||||
alignUp(hdBaseAddress + arena.hdBytes, arena.storageAlignment);
|
||||
const uint32_t dataBaseAddress =
|
||||
alignUp(sqBaseAddress + arena.sqBytes, arena.storageAlignment);
|
||||
const uint32_t addressTableAddress = alignUp(statusAddress + kStatusSize, arena.tableAlignment);
|
||||
const uint32_t hdBaseAddress = alignUp(addressTableAddress + (kAddressTableEntries * sizeof(uint32_t)), arena.storageAlignment);
|
||||
const uint32_t sqBaseAddress = alignUp(hdBaseAddress + arena.hdBytes, arena.storageAlignment);
|
||||
const uint32_t dataBaseAddress = alignUp(sqBaseAddress + arena.sqBytes, arena.storageAlignment);
|
||||
const uint32_t storageEnd = dataBaseAddress + arena.dataBytes;
|
||||
if (storageEnd > arena.limit)
|
||||
{
|
||||
@@ -313,23 +317,17 @@ namespace ps2x::iop::detail
|
||||
|
||||
if (!m_state.initialized)
|
||||
{
|
||||
if (!m_host.zeroGuest(m_state.statusAddress, kStatusSize) ||
|
||||
!m_host.zeroGuest(m_state.addressTableAddress,
|
||||
kAddressTableEntries * sizeof(uint32_t)) ||
|
||||
!m_host.zeroGuest(m_state.storageBaseAddress, m_state.storageSize))
|
||||
if (!m_host.zeroIopMemory(m_state.statusAddress, kStatusSize) ||
|
||||
!m_host.zeroIopMemory(m_state.addressTableAddress, kAddressTableEntries * sizeof(uint32_t)) ||
|
||||
!m_host.zeroIopMemory(m_state.storageBaseAddress, m_state.storageSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!writeGuestPod(m_host,
|
||||
m_state.addressTableAddress + (0u * sizeof(uint32_t)),
|
||||
m_state.hdBaseAddress) ||
|
||||
!writeGuestPod(m_host,
|
||||
m_state.addressTableAddress + (1u * sizeof(uint32_t)),
|
||||
m_state.sqBaseAddress) ||
|
||||
!writeGuestPod(m_host,
|
||||
m_state.addressTableAddress + (2u * sizeof(uint32_t)),
|
||||
m_state.dataBaseAddress))
|
||||
if (
|
||||
!writeIopPod(m_host, m_state.addressTableAddress + (0u * sizeof(uint32_t)), m_state.hdBaseAddress) ||
|
||||
!writeIopPod(m_host, m_state.addressTableAddress + (1u * sizeof(uint32_t)), m_state.sqBaseAddress) ||
|
||||
!writeIopPod(m_host, m_state.addressTableAddress + (2u * sizeof(uint32_t)), m_state.dataBaseAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -339,9 +337,7 @@ namespace ps2x::iop::detail
|
||||
return true;
|
||||
}
|
||||
|
||||
int16_t checkValue(bool seTable,
|
||||
uint32_t index,
|
||||
uint32_t count) const
|
||||
int16_t checkValue(bool seTable, uint32_t index, uint32_t count) const
|
||||
{
|
||||
if (index >= count)
|
||||
{
|
||||
@@ -352,10 +348,7 @@ namespace ps2x::iop::detail
|
||||
{
|
||||
const uint32_t base = seTable ? candidate.seAddress : candidate.midiAddress;
|
||||
int16_t value = 0;
|
||||
if (readGuestPod(m_host,
|
||||
base + (index * sizeof(int16_t)),
|
||||
value) &&
|
||||
value != 0)
|
||||
if (readGuestPod(m_host, base + (index * sizeof(int16_t)), value) && value != 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
@@ -370,19 +363,13 @@ namespace ps2x::iop::detail
|
||||
{
|
||||
std::array<int16_t, 5> seValues{};
|
||||
std::array<int16_t, 4> midiValues{};
|
||||
const bool seReadable = m_host.readGuest(candidate.seAddress,
|
||||
seValues.data(),
|
||||
sizeof(seValues));
|
||||
const bool midiReadable = m_host.readGuest(candidate.midiAddress,
|
||||
midiValues.data(),
|
||||
sizeof(midiValues));
|
||||
const bool seReadable = m_host.readGuest(candidate.seAddress, seValues.data(), sizeof(seValues));
|
||||
const bool midiReadable = m_host.readGuest(candidate.midiAddress, midiValues.data(), sizeof(midiValues));
|
||||
if (seReadable && midiReadable && !firstReadable)
|
||||
{
|
||||
firstReadable = &candidate;
|
||||
}
|
||||
const bool looksLive =
|
||||
(seReadable && hasAnyNonZero(seValues)) ||
|
||||
(midiReadable && hasAnyNonZero(midiValues));
|
||||
const bool looksLive = (seReadable && hasAnyNonZero(seValues)) || (midiReadable && hasAnyNonZero(midiValues));
|
||||
if (seReadable && midiReadable && looksLive)
|
||||
{
|
||||
seBase = candidate.seAddress;
|
||||
@@ -409,35 +396,23 @@ namespace ps2x::iop::detail
|
||||
return;
|
||||
}
|
||||
|
||||
auto backfillSlots = [&](uint32_t statusOffset,
|
||||
uint32_t compatBase,
|
||||
uint32_t slotCount)
|
||||
auto backfillSlots = [&](uint32_t statusOffset, uint32_t compatBase, uint32_t slotCount)
|
||||
{
|
||||
for (uint32_t slot = 0u; slot < slotCount; ++slot)
|
||||
{
|
||||
int16_t liveValue = 0;
|
||||
if (!readGuestPod(m_host,
|
||||
m_state.statusAddress + statusOffset +
|
||||
(slot * sizeof(int16_t)),
|
||||
liveValue) ||
|
||||
liveValue != 0)
|
||||
if (!readIopPod(m_host, m_state.statusAddress + statusOffset + (slot * sizeof(int16_t)), liveValue) || liveValue != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int16_t compatValue = 0;
|
||||
if (!readGuestPod(m_host,
|
||||
compatBase + (slot * sizeof(int16_t)),
|
||||
compatValue) ||
|
||||
compatValue == 0)
|
||||
if (!readGuestPod(m_host, compatBase + (slot * sizeof(int16_t)), compatValue) || compatValue == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)writeGuestPod(m_host,
|
||||
m_state.statusAddress + statusOffset +
|
||||
(slot * sizeof(int16_t)),
|
||||
compatValue);
|
||||
(void)writeIopPod(m_host, m_state.statusAddress + statusOffset + (slot * sizeof(int16_t)), compatValue);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -458,28 +433,23 @@ namespace ps2x::iop::detail
|
||||
{
|
||||
const uint32_t port = command[1] & 0x0Fu;
|
||||
uint16_t midiInfo = 0u;
|
||||
(void)readGuestPod(m_host,
|
||||
m_state.statusAddress + kMidiInfoOffset,
|
||||
midiInfo);
|
||||
(void)readIopPod(m_host,
|
||||
m_state.statusAddress + kMidiInfoOffset,
|
||||
midiInfo);
|
||||
midiInfo = static_cast<uint16_t>(midiInfo |
|
||||
static_cast<uint16_t>(1u << port));
|
||||
(void)writeGuestPod(m_host,
|
||||
m_state.statusAddress + kMidiInfoOffset,
|
||||
midiInfo);
|
||||
(void)writeIopPod(m_host,
|
||||
m_state.statusAddress + kMidiInfoOffset,
|
||||
midiInfo);
|
||||
break;
|
||||
}
|
||||
case 0x21u: // SdrBgmStop
|
||||
{
|
||||
const uint32_t port = command[1] & 0x0Fu;
|
||||
uint16_t midiInfo = 0u;
|
||||
(void)readGuestPod(m_host,
|
||||
m_state.statusAddress + kMidiInfoOffset,
|
||||
midiInfo);
|
||||
midiInfo = static_cast<uint16_t>(midiInfo &
|
||||
~static_cast<uint16_t>(1u << port));
|
||||
(void)writeGuestPod(m_host,
|
||||
m_state.statusAddress + kMidiInfoOffset,
|
||||
midiInfo);
|
||||
(void)readIopPod(m_host, m_state.statusAddress + kMidiInfoOffset, midiInfo);
|
||||
midiInfo = static_cast<uint16_t>(midiInfo & ~static_cast<uint16_t>(1u << port));
|
||||
(void)writeIopPod(m_host, m_state.statusAddress + kMidiInfoOffset, midiInfo);
|
||||
break;
|
||||
}
|
||||
case 0x28u: // SdrHDDataSet
|
||||
@@ -490,10 +460,7 @@ namespace ps2x::iop::detail
|
||||
break;
|
||||
}
|
||||
const int16_t checksum = checkValue(false, port, 4u);
|
||||
(void)writeGuestPod(m_host,
|
||||
m_state.statusAddress + kMidiSumOffset +
|
||||
(port * sizeof(int16_t)),
|
||||
checksum);
|
||||
(void)writeIopPod(m_host, m_state.statusAddress + kMidiSumOffset + (port * sizeof(int16_t)), checksum);
|
||||
break;
|
||||
}
|
||||
case 0x29u: // SdrHDDataSet2
|
||||
@@ -504,15 +471,11 @@ namespace ps2x::iop::detail
|
||||
break;
|
||||
}
|
||||
const int16_t checksum = checkValue(true, port, 5u);
|
||||
(void)writeGuestPod(m_host,
|
||||
m_state.statusAddress + kSeSumOffset +
|
||||
(port * sizeof(int16_t)),
|
||||
checksum);
|
||||
(void)writeIopPod(m_host, m_state.statusAddress + kSeSumOffset + (port * sizeof(int16_t)), checksum);
|
||||
break;
|
||||
}
|
||||
case 0x10u: // SdrSeAllStop
|
||||
(void)m_host.zeroGuest(m_state.statusAddress + kSeInfoOffset,
|
||||
6u * sizeof(uint16_t));
|
||||
(void)m_host.zeroIopMemory(m_state.statusAddress + kSeInfoOffset, 6u * sizeof(uint16_t));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -569,12 +532,13 @@ namespace ps2x::iop::detail
|
||||
std::unique_ptr<IopService> createTsnddrvService(IopHost &host,
|
||||
TsnddrvBindings bindings)
|
||||
{
|
||||
const auto isPowerOfTwo = [](uint32_t value) {
|
||||
const auto isPowerOfTwo = [](uint32_t value)
|
||||
{
|
||||
return value != 0u && (value & (value - 1u)) == 0u;
|
||||
};
|
||||
const auto alignUp64 = [](uint64_t value, uint32_t alignment) {
|
||||
return (value + (alignment - 1u)) &
|
||||
~static_cast<uint64_t>(alignment - 1u);
|
||||
const auto alignUp64 = [](uint64_t value, uint32_t alignment)
|
||||
{
|
||||
return (value + (alignment - 1u)) & ~static_cast<uint64_t>(alignment - 1u);
|
||||
};
|
||||
|
||||
const TsnddrvGuestArena &arena = bindings.arena;
|
||||
@@ -590,8 +554,7 @@ namespace ps2x::iop::detail
|
||||
}
|
||||
|
||||
uint64_t end = alignUp64(arena.base, arena.statusAlignment) + kStatusSize;
|
||||
end = alignUp64(end, arena.tableAlignment) +
|
||||
(kAddressTableEntries * sizeof(uint32_t));
|
||||
end = alignUp64(end, arena.tableAlignment) + (kAddressTableEntries * sizeof(uint32_t));
|
||||
end = alignUp64(end, arena.storageAlignment) + arena.hdBytes;
|
||||
end = alignUp64(end, arena.storageAlignment) + arena.sqBytes;
|
||||
end = alignUp64(end, arena.storageAlignment) + arena.dataBytes;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "ps2x/iop/ps2_path.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace ps2x::iop
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string lowerAscii(std::string_view value)
|
||||
{
|
||||
std::string result(value);
|
||||
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char ch)
|
||||
{ return static_cast<char>(std::tolower(ch)); });
|
||||
return result;
|
||||
}
|
||||
|
||||
void normalizeSuffix(std::string &suffix)
|
||||
{
|
||||
std::replace(suffix.begin(), suffix.end(), '\\', '/');
|
||||
while (!suffix.empty() && suffix.front() == '/')
|
||||
suffix.erase(suffix.begin());
|
||||
|
||||
const size_t semicolon = suffix.rfind(';');
|
||||
if (semicolon == std::string::npos || semicolon + 1u == suffix.size())
|
||||
return;
|
||||
|
||||
const bool numeric = std::all_of(suffix.begin() + static_cast<std::ptrdiff_t>(semicolon + 1u),
|
||||
suffix.end(),
|
||||
[](unsigned char ch)
|
||||
{ return std::isdigit(ch) != 0; });
|
||||
if (numeric)
|
||||
suffix.erase(semicolon);
|
||||
}
|
||||
}
|
||||
|
||||
ParsedPs2Path parsePs2Path(std::string_view value)
|
||||
{
|
||||
ParsedPs2Path result;
|
||||
if (value.empty())
|
||||
return result;
|
||||
|
||||
const std::string lower = lowerAscii(value);
|
||||
size_t prefixLength = 0u;
|
||||
if (lower.rfind("host0:", 0u) == 0u)
|
||||
{
|
||||
result.device = Ps2PathDevice::Host;
|
||||
result.deviceName = "host0";
|
||||
prefixLength = 6u;
|
||||
}
|
||||
else if (lower.rfind("host:", 0u) == 0u)
|
||||
{
|
||||
result.device = Ps2PathDevice::Host;
|
||||
result.deviceName = "host";
|
||||
prefixLength = 5u;
|
||||
}
|
||||
else if (lower.rfind("cdrom0:", 0u) == 0u)
|
||||
{
|
||||
result.device = Ps2PathDevice::Cdrom;
|
||||
result.deviceName = "cdrom0";
|
||||
prefixLength = 7u;
|
||||
}
|
||||
else if (lower.rfind("cdrom:", 0u) == 0u)
|
||||
{
|
||||
result.device = Ps2PathDevice::Cdrom;
|
||||
result.deviceName = "cdrom";
|
||||
prefixLength = 6u;
|
||||
}
|
||||
else if (lower.rfind("mc0:", 0u) == 0u)
|
||||
{
|
||||
result.device = Ps2PathDevice::MemoryCard0;
|
||||
result.deviceName = "mc0";
|
||||
prefixLength = 4u;
|
||||
}
|
||||
else if (lower.rfind("rom0:", 0u) == 0u)
|
||||
{
|
||||
result.device = Ps2PathDevice::Rom0;
|
||||
result.deviceName = "rom0";
|
||||
prefixLength = 5u;
|
||||
}
|
||||
else if (value.size() > 2u && std::isalpha(static_cast<unsigned char>(value[0])) &&
|
||||
value[1] == ':' && (value[2] == '/' || value[2] == '\\'))
|
||||
{
|
||||
result.device = Ps2PathDevice::NativeHost;
|
||||
result.deviceName = "native";
|
||||
}
|
||||
else if (value.find(':') != std::string_view::npos)
|
||||
{
|
||||
// TODO maybe log an error here, but don't fail the parse. This is a non-standard device name.
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.device = Ps2PathDevice::Cdrom;
|
||||
result.deviceName = "cdrom0";
|
||||
}
|
||||
|
||||
result.path.assign(value.substr(prefixLength));
|
||||
if (result.device != Ps2PathDevice::NativeHost)
|
||||
normalizeSuffix(result.path);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ps2PathLeafKey(const ParsedPs2Path &parsed)
|
||||
{
|
||||
if (!parsed)
|
||||
return {};
|
||||
std::string path = parsed.path;
|
||||
std::replace(path.begin(), path.end(), '\\', '/');
|
||||
const size_t slash = path.find_last_of('/');
|
||||
if (slash != std::string::npos)
|
||||
path.erase(0u, slash + 1u);
|
||||
path = lowerAscii(path);
|
||||
if (path.size() > 4u && path.ends_with(".irx"))
|
||||
path.resize(path.size() - 4u);
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string ps2PathLeafKey(std::string_view path)
|
||||
{
|
||||
return ps2PathLeafKey(parsePs2Path(path));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
#include "emulator/core/iop_cpu.h"
|
||||
#include "emulator/core/iop_kernel.h"
|
||||
#include "emulator/core/iop_memory.h"
|
||||
#include "emulator/imports/iop_cdvd.h"
|
||||
#include "emulator/imports/iop_imports.h"
|
||||
#include "emulator/imports/iop_loadcore.h"
|
||||
#include "emulator/imports/iop_timrman.h"
|
||||
#include "emulator/services/iop_rpc.h"
|
||||
#include "ps2x/iop/iop_host.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace ps2x::iop;
|
||||
using namespace ps2x::iop::detail;
|
||||
|
||||
constexpr uint32_t kExportMagic = 0x41C00000u;
|
||||
constexpr int32_t kLibraryNotFound = -213;
|
||||
constexpr int32_t kIllegalLibrary = -214;
|
||||
|
||||
class NullHost : public IopHost
|
||||
{
|
||||
public:
|
||||
bool readGuest(uint32_t, void *, size_t) const override { return false; }
|
||||
bool writeGuest(uint32_t, const void *, size_t) override { return false; }
|
||||
bool zeroGuest(uint32_t, size_t) override { return false; }
|
||||
bool normalizeGuestAddress(uint32_t, uint32_t &) const override { return false; }
|
||||
uint32_t allocateIopHandle(IopHandleKind) override { return 1u; }
|
||||
uint32_t allocateGuest(uint32_t, uint32_t) override { return 0u; }
|
||||
void freeGuest(uint32_t) override {}
|
||||
void audioCommand(uint32_t, uint32_t, GuestBuffer, GuestBuffer) override {}
|
||||
std::string hostPath(HostPathKind) const override { return {}; }
|
||||
std::string translateGuestPath(std::string_view path) const override { return std::string(path); }
|
||||
uint64_t openHostFile(std::string_view) override { return 0u; }
|
||||
bool hostFileSize(uint64_t, uint64_t &) const override { return false; }
|
||||
bool readHostFile(uint64_t, uint64_t, void *, size_t, size_t &) override { return false; }
|
||||
void closeHostFile(uint64_t) override {}
|
||||
int32_t memoryCard(const MemoryCardRequest &) override { return 0; }
|
||||
bool hasGuestFunction(uint32_t) const override { return false; }
|
||||
bool invokeGuestFunction(uint64_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t *) override { return false; }
|
||||
void log(LogLevel, std::string_view) override {}
|
||||
};
|
||||
|
||||
class CdRootHost final : public NullHost
|
||||
{
|
||||
public:
|
||||
explicit CdRootHost(std::filesystem::path rootPath)
|
||||
: root(std::move(rootPath))
|
||||
{
|
||||
}
|
||||
|
||||
std::string hostPath(HostPathKind kind) const override
|
||||
{
|
||||
return kind == HostPathKind::CdRoot ? root.string() : std::string{};
|
||||
}
|
||||
|
||||
private:
|
||||
std::filesystem::path root;
|
||||
};
|
||||
|
||||
class RecordingExecutor final : public IopGuestExecutor
|
||||
{
|
||||
public:
|
||||
uint32_t executeGuestFunction(uint32_t address,
|
||||
uint32_t a0,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t gp) override
|
||||
{
|
||||
++calls;
|
||||
lastAddress = address;
|
||||
lastArgument = a0;
|
||||
lastGp = gp;
|
||||
return callbackResult;
|
||||
}
|
||||
|
||||
uint32_t callbackResult = 0u;
|
||||
uint32_t calls = 0u;
|
||||
uint32_t lastAddress = 0u;
|
||||
uint32_t lastArgument = 0u;
|
||||
uint32_t lastGp = 0u;
|
||||
};
|
||||
|
||||
bool expect(bool condition, std::string_view message)
|
||||
{
|
||||
if (condition)
|
||||
return true;
|
||||
std::cerr << "FAIL: " << message << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
bool testLoadcoreRebootLibraryMode()
|
||||
{
|
||||
IopMemory memory;
|
||||
IopImportRegistry imports(memory);
|
||||
IopLoadcore loadcore(memory, imports);
|
||||
|
||||
IopCpuState cpu{};
|
||||
cpu.gpr[4] = 0u;
|
||||
cpu.gpr[5] = 2u;
|
||||
if (!expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 was not handled") ||
|
||||
!expect(static_cast<int32_t>(cpu.gpr[2]) == kIllegalLibrary,
|
||||
"loadcore:27 did not reject a null export table"))
|
||||
return false;
|
||||
|
||||
constexpr uint32_t table = 0x1000u;
|
||||
memory.write32(table, kExportMagic);
|
||||
memory.write16(table + 8u, 0x0101u);
|
||||
memory.write16(table + 10u, 0x1234u);
|
||||
const char name[8] = {'t', 'e', 's', 't', 'l', 'i', 'b', '\0'};
|
||||
(void)memory.writeRam(table + 12u, name, sizeof(name));
|
||||
memory.write32(table + 20u, 0u);
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = table;
|
||||
cpu.gpr[5] = 2u;
|
||||
if (!expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 rejected a valid export table") ||
|
||||
!expect(cpu.gpr[2] == 0u, "loadcore:27 returned an error for a valid export table") ||
|
||||
!expect(memory.read16(table + 10u) == 0x1232u,
|
||||
"loadcore:27 did not replace only export mode bits 1 and 2"))
|
||||
return false;
|
||||
|
||||
constexpr uint32_t invalidTable = 0x1100u;
|
||||
memory.write32(invalidTable, 0xDEADBEEFu);
|
||||
cpu = {};
|
||||
cpu.gpr[4] = invalidTable;
|
||||
cpu.gpr[5] = 6u;
|
||||
if (!expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 did not consume an invalid-table call") ||
|
||||
!expect(static_cast<int32_t>(cpu.gpr[2]) == kLibraryNotFound,
|
||||
"loadcore:27 returned the wrong invalid-table error"))
|
||||
return false;
|
||||
|
||||
if (!expect(imports.registerExportTable(table), "test export table did not register"))
|
||||
return false;
|
||||
memory.write32(table, 0u);
|
||||
cpu = {};
|
||||
cpu.gpr[4] = table;
|
||||
cpu.gpr[5] = 6u;
|
||||
return expect(loadcore.dispatchImport(27u, cpu), "loadcore:27 rejected a registered table") &&
|
||||
expect(cpu.gpr[2] == 0u, "loadcore:27 returned an error for a registered table") &&
|
||||
expect(memory.read16(table + 10u) == 0x1236u,
|
||||
"loadcore:27 did not update a registered table's mode");
|
||||
}
|
||||
|
||||
bool pollEvent(IopKernel &kernel, int eventId, uint32_t bits, uint32_t resultAddress, int32_t expected)
|
||||
{
|
||||
IopCpuState cpu{};
|
||||
cpu.gpr[4] = static_cast<uint32_t>(eventId);
|
||||
cpu.gpr[5] = bits;
|
||||
cpu.gpr[6] = 0u; // WEF_AND
|
||||
cpu.gpr[7] = resultAddress;
|
||||
return expect(kernel.dispatchEventImport(11u, cpu), "PollEventFlag was not handled") &&
|
||||
expect(static_cast<int32_t>(cpu.gpr[2]) == expected, "PollEventFlag returned an unexpected result");
|
||||
}
|
||||
|
||||
bool testCdvdSpecialControl()
|
||||
{
|
||||
NullHost host;
|
||||
IopMemory memory;
|
||||
IopKernel kernel(memory);
|
||||
kernel.reset();
|
||||
IopCdvd cdvd(host, memory, kernel);
|
||||
cdvd.reset();
|
||||
|
||||
constexpr uint32_t param = 0x2000u;
|
||||
constexpr uint32_t eventResult = 0x2010u;
|
||||
IopCpuState cpu{};
|
||||
cpu.gpr[4] = static_cast<uint32_t>(-11); // sceCdSC: return cdvdman interrupt event flag
|
||||
cpu.gpr[5] = param;
|
||||
if (!expect(cdvd.dispatchImport(50u, cpu), "cdvdman:50 was not handled") ||
|
||||
!expect(static_cast<int32_t>(cpu.gpr[2]) > 0, "sceCdSC(-11) did not return a valid event flag"))
|
||||
return false;
|
||||
const int eventId = static_cast<int>(cpu.gpr[2]);
|
||||
|
||||
if (!pollEvent(kernel, eventId, 0x29u, eventResult, 0) ||
|
||||
!expect(memory.read32(eventResult) == 0x29u, "cdvdman event flag did not start with bits 0x29"))
|
||||
return false;
|
||||
|
||||
IopCpuState clear{};
|
||||
clear.gpr[4] = static_cast<uint32_t>(eventId);
|
||||
clear.gpr[5] = ~0x29u;
|
||||
if (!expect(kernel.dispatchEventImport(8u, clear), "ClearEventFlag was not handled") ||
|
||||
!pollEvent(kernel, eventId, 0x29u, eventResult, -418))
|
||||
return false;
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = 0x12345u;
|
||||
if (!expect(cdvd.dispatchImport(7u, cpu), "sceCdSeek was not handled") ||
|
||||
!pollEvent(kernel, eventId, 0x29u, eventResult, 0))
|
||||
return false;
|
||||
|
||||
memory.write8(param, 0x30u);
|
||||
cpu = {};
|
||||
cpu.gpr[4] = static_cast<uint32_t>(-2);
|
||||
cpu.gpr[5] = param;
|
||||
if (!expect(cdvd.dispatchImport(50u, cpu), "sceCdSC(-2) was not handled") ||
|
||||
!expect(cpu.gpr[2] == 0x30u, "sceCdSC(-2) did not store the low-byte error"))
|
||||
return false;
|
||||
|
||||
memory.write32(param, 0u);
|
||||
cpu = {};
|
||||
cpu.gpr[4] = static_cast<uint32_t>(-1);
|
||||
cpu.gpr[5] = param;
|
||||
if (!expect(cdvd.dispatchImport(50u, cpu), "sceCdSC(-1) was not handled") ||
|
||||
!expect(cpu.gpr[2] == 0u, "sceCdSC(-1) returned the wrong initial stream state") ||
|
||||
!expect(memory.read32(param) == 0x30u, "sceCdSC(-1) did not publish the last error"))
|
||||
return false;
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = 2u;
|
||||
cpu.gpr[5] = param;
|
||||
if (!expect(cdvd.dispatchImport(50u, cpu), "sceCdSC(2) was not handled") ||
|
||||
!expect(cpu.gpr[2] == 2u, "sceCdSC(2) did not update the stream state"))
|
||||
return false;
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = static_cast<uint32_t>(-1);
|
||||
cpu.gpr[5] = param;
|
||||
return expect(cdvd.dispatchImport(50u, cpu), "second sceCdSC(-1) was not handled") &&
|
||||
expect(cpu.gpr[2] == 2u, "sceCdSC(-1) did not preserve the stream state");
|
||||
}
|
||||
|
||||
bool testCdvdSearchFile()
|
||||
{
|
||||
const auto suffix = std::to_string(
|
||||
static_cast<unsigned long long>(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
const std::filesystem::path root =
|
||||
std::filesystem::temp_directory_path() / ("ps2x-iop-cdvd-search-" + suffix);
|
||||
const std::filesystem::path movieDirectory = root / "MOVIE";
|
||||
const std::filesystem::path moviePath = movieDirectory / "OPENING.PSS";
|
||||
std::error_code error;
|
||||
std::filesystem::create_directories(movieDirectory, error);
|
||||
if (!expect(!error, "could not create the temporary CD root"))
|
||||
return false;
|
||||
{
|
||||
std::ofstream movie(moviePath, std::ios::binary);
|
||||
movie.write("PSS!", 4);
|
||||
}
|
||||
|
||||
CdRootHost host(root);
|
||||
IopMemory memory;
|
||||
IopKernel kernel(memory);
|
||||
kernel.reset();
|
||||
IopCdvd cdvd(host, memory, kernel);
|
||||
cdvd.reset();
|
||||
|
||||
constexpr uint32_t resultAddress = 0x2400u;
|
||||
constexpr uint32_t pathAddress = 0x2480u;
|
||||
const char path[] = "cdrom0:\\movie\\opening.pss;1";
|
||||
(void)memory.writeRam(pathAddress, path, sizeof(path));
|
||||
|
||||
IopCpuState cpu{};
|
||||
cpu.gpr[4] = resultAddress;
|
||||
cpu.gpr[5] = pathAddress;
|
||||
const bool handled = cdvd.dispatchImport(10u, cpu);
|
||||
const bool passed =
|
||||
expect(handled, "cdvdman:10 was not handled") &&
|
||||
expect(cpu.gpr[2] == 1u, "sceCdSearchFile did not find a case-insensitive ISO path") &&
|
||||
expect(memory.read32(resultAddress) >= 20u, "sceCdSearchFile returned an invalid LSN") &&
|
||||
expect(memory.read32(resultAddress + 4u) == 4u, "sceCdSearchFile returned the wrong size") &&
|
||||
expect(memory.readString(resultAddress + 8u, 16u) == "OPENING.PSS",
|
||||
"sceCdSearchFile returned the wrong file name");
|
||||
|
||||
std::filesystem::remove_all(root, error);
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool testTimrmanPeriodicCallback()
|
||||
{
|
||||
IopTimrman timrman;
|
||||
timrman.reset();
|
||||
IopCpuState cpu{};
|
||||
|
||||
cpu.gpr[4] = 1u; // SYSCLK
|
||||
cpu.gpr[5] = 32u;
|
||||
cpu.gpr[6] = 1u;
|
||||
if (!expect(timrman.dispatchImport(4u, cpu, 100u), "AllocHardTimer was not handled") ||
|
||||
!expect(static_cast<int32_t>(cpu.gpr[2]) > 0, "AllocHardTimer did not allocate a 32-bit timer"))
|
||||
return false;
|
||||
const uint32_t timerId = cpu.gpr[2];
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = timerId;
|
||||
cpu.gpr[5] = 100u;
|
||||
cpu.gpr[6] = 0x12340u;
|
||||
cpu.gpr[7] = 0x45670u;
|
||||
cpu.gpr[28] = 0x89AB0u;
|
||||
if (!expect(timrman.dispatchImport(20u, cpu, 100u), "SetTimerHandler was not handled") ||
|
||||
!expect(cpu.gpr[2] == 0u, "SetTimerHandler failed"))
|
||||
return false;
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = timerId;
|
||||
cpu.gpr[5] = 1u;
|
||||
cpu.gpr[6] = 0u;
|
||||
cpu.gpr[7] = 1u;
|
||||
if (!expect(timrman.dispatchImport(22u, cpu, 100u), "SetupHardTimer was not handled") ||
|
||||
!expect(cpu.gpr[2] == 0u, "SetupHardTimer failed"))
|
||||
return false;
|
||||
|
||||
cpu = {};
|
||||
cpu.gpr[4] = timerId;
|
||||
if (!expect(timrman.dispatchImport(23u, cpu, 100u), "StartHardTimer was not handled") ||
|
||||
!expect(cpu.gpr[2] == 0u, "StartHardTimer failed") ||
|
||||
!expect(timrman.nextEventCycle(1000u) == 200u, "timer compare was scheduled at the wrong cycle"))
|
||||
return false;
|
||||
|
||||
RecordingExecutor executor;
|
||||
executor.callbackResult = 100u;
|
||||
timrman.serviceDue(199u, executor);
|
||||
if (!expect(executor.calls == 0u, "timer callback ran too early"))
|
||||
return false;
|
||||
timrman.serviceDue(200u, executor);
|
||||
return expect(executor.calls == 1u, "timer callback did not run") &&
|
||||
expect(executor.lastAddress == 0x12340u, "timer called the wrong handler") &&
|
||||
expect(executor.lastArgument == 0x45670u, "timer passed the wrong common argument") &&
|
||||
expect(executor.lastGp == 0x89AB0u, "timer callback lost the registering module GP") &&
|
||||
expect(timrman.nextEventCycle(1000u) == 300u, "timer callback return did not rearm compare");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
if (!testLoadcoreRebootLibraryMode() || !testCdvdSpecialControl() || !testCdvdSearchFile() ||
|
||||
!testTimrmanPeriodicCallback())
|
||||
return 1;
|
||||
std::cout << "ps2xIOP import tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -115,7 +115,7 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
"fioWrite", "fioLseek", "fioMkdir", "fioChdir", "fioRmdir", "fioGetstat",
|
||||
"fioRemove", "SetGsCrt", "GsSetCrt", "GsGetIMR", "iGsGetIMR", "GsPutIMR",
|
||||
"iGsPutIMR", "SetVSyncFlag", "SetSyscall", "GsSetVideoMode", "GetOsdConfigParam", "SetOsdConfigParam",
|
||||
"EnableCache", "DisableCache", "GetRomName", "SifLoadElfPart", "sceSifLoadElf", "sceSifLoadElfPart",
|
||||
"EnableCache", "DisableCache", "SifLoadElfPart", "sceSifLoadElf", "sceSifLoadElfPart",
|
||||
"sceSifLoadModule", "sceSifLoadModuleBuffer", "SetupThread", "EndOfHeap", "GetMemorySize", "Deci2Call",
|
||||
"QueryBootMode", "GetThreadTLS", "Copy", "GetEntryAddress", "RegisterExitHandler", "ret0", "ret1", "reta0",
|
||||
"calloc_r", "free_r", "realloc_r", "memalign_r", "malloc_r", "malloc_extend_top", "malloc_trim_r", "mbtowc_r", "printf_r",
|
||||
|
||||
Reference in New Issue
Block a user