mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-28 09:24:41 -04:00
278 lines
8.8 KiB
C++
278 lines
8.8 KiB
C++
#include "ps2x/iop/iop_subsystem.h"
|
|
|
|
#include "iop_service.h"
|
|
#include "iop_module_manager.h"
|
|
#include "emulator/iop_emulator.h"
|
|
#include "module_factories.h"
|
|
#include "ps2x/iop/ps2_path.h"
|
|
|
|
#include <sstream>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
|
|
namespace ps2x::iop
|
|
{
|
|
class IopSubsystem::Impl
|
|
{
|
|
public:
|
|
explicit Impl(IopHost &hostRef)
|
|
: host(hostRef),
|
|
emulator(hostRef)
|
|
{
|
|
coreServices.emplace_back(detail::createMcservService(host));
|
|
coreServices.emplace_back(detail::createDbcmanService(host));
|
|
coreServices.emplace_back(detail::createLibSdService(host));
|
|
refreshServiceModuleKeys();
|
|
rebuildRoutes();
|
|
}
|
|
|
|
bool serviceActive(const detail::IopService &service) const
|
|
{
|
|
return moduleManager.isLoaded(service.moduleAliases());
|
|
}
|
|
|
|
void refreshServiceModuleKeys()
|
|
{
|
|
std::vector<std::string> keys;
|
|
for (const auto &service : coreServices)
|
|
{
|
|
for (std::string_view alias : service->moduleAliases())
|
|
keys.emplace_back(alias);
|
|
}
|
|
moduleManager.setServiceModuleKeys(std::move(keys));
|
|
}
|
|
|
|
void rebuildRoutes()
|
|
{
|
|
routes.clear();
|
|
lastError.clear();
|
|
for (const auto &service : coreServices)
|
|
{
|
|
if (!serviceActive(*service))
|
|
continue;
|
|
for (const uint32_t sid : service->sids())
|
|
{
|
|
if (!routes.emplace(sid, service.get()).second)
|
|
{
|
|
std::ostringstream out;
|
|
out << "duplicate IOP SID 0x" << std::hex << sid << " in core services";
|
|
lastError = out.str();
|
|
routes.clear();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void recordLoadOutcome(std::string_view path, bool hle)
|
|
{
|
|
constexpr size_t maxOutcomes = 32u;
|
|
if (loadOutcomes.size() >= maxOutcomes || !loggedLoadPaths.emplace(path).second)
|
|
return;
|
|
std::string message = hle ? "[IOP:HLE] fallback module='" : "[IOP:load-failed] module='";
|
|
message.append(path);
|
|
message += hle ? "' physical IRX unavailable; using registered HLE provider"
|
|
: "' no HLE provider accepted the module; physical IRX was not loaded";
|
|
loadOutcomes.push_back(message);
|
|
host.log(hle ? LogLevel::Info : LogLevel::Warning, message);
|
|
}
|
|
|
|
IopHost &host;
|
|
detail::ServiceList coreServices;
|
|
std::unordered_map<uint32_t, detail::IopService *> routes;
|
|
std::vector<std::string> loadOutcomes;
|
|
std::unordered_set<std::string> loggedLoadPaths;
|
|
std::string lastError;
|
|
detail::IopModuleManager moduleManager;
|
|
detail::IopEmulator emulator;
|
|
};
|
|
|
|
IopSubsystem::IopSubsystem(IopHost &host)
|
|
: m_impl(std::make_unique<Impl>(host))
|
|
{
|
|
}
|
|
|
|
IopSubsystem::~IopSubsystem() = default;
|
|
IopSubsystem::IopSubsystem(IopSubsystem &&) noexcept = default;
|
|
IopSubsystem &IopSubsystem::operator=(IopSubsystem &&) noexcept = default;
|
|
|
|
void IopSubsystem::reset()
|
|
{
|
|
m_impl->moduleManager.reset();
|
|
m_impl->loadOutcomes.clear();
|
|
m_impl->loggedLoadPaths.clear();
|
|
for (auto &service : m_impl->coreServices)
|
|
{
|
|
if (service)
|
|
{
|
|
service->reset();
|
|
}
|
|
}
|
|
m_impl->emulator.reset();
|
|
m_impl->refreshServiceModuleKeys();
|
|
m_impl->rebuildRoutes();
|
|
}
|
|
|
|
ModuleLoadResult IopSubsystem::loadModule(std::string_view path, const void *arguments, uint32_t 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();
|
|
if (parsed.device != Ps2PathDevice::Rom0)
|
|
m_impl->recordLoadOutcome(path, true);
|
|
}
|
|
else
|
|
{
|
|
m_impl->recordLoadOutcome(path, false);
|
|
}
|
|
return hle;
|
|
}
|
|
|
|
ModuleLoadResult IopSubsystem::loadModuleBuffer(uint32_t guestAddress, const void *arguments, uint32_t argumentSize)
|
|
{
|
|
return m_impl->emulator.loadModuleBuffer(guestAddress, arguments, argumentSize);
|
|
}
|
|
|
|
bool IopSubsystem::stopModule(int32_t moduleId, int32_t *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
|
|
{
|
|
m_impl->emulator.runEeCycles(eeCycles);
|
|
}
|
|
|
|
RpcAbi IopSubsystem::selectRpcAbi(const RpcAbiRequest &request) const
|
|
{
|
|
for (const auto &service : m_impl->coreServices)
|
|
{
|
|
if (service && m_impl->serviceActive(*service))
|
|
{
|
|
const RpcAbi selected = service->selectRpcAbi(request);
|
|
if (selected != RpcAbi::RuntimeDefault)
|
|
{
|
|
return selected;
|
|
}
|
|
}
|
|
}
|
|
return RpcAbi::RuntimeDefault;
|
|
}
|
|
|
|
bool IopSubsystem::canBindRpc(uint32_t sid) const noexcept
|
|
{
|
|
if (m_impl->routes.find(sid) != m_impl->routes.end())
|
|
{
|
|
return true;
|
|
}
|
|
return m_impl->emulator.hasRpcServer(sid);
|
|
}
|
|
|
|
RpcResult IopSubsystem::handleRpc(const RpcRequest &request)
|
|
{
|
|
const auto route = m_impl->routes.find(request.sid);
|
|
detail::IopService *hle = route != m_impl->routes.end() ? route->second : nullptr;
|
|
|
|
RpcResult emulated = m_impl->emulator.handleRpc(request);
|
|
if (emulated.handled || !hle)
|
|
{
|
|
return emulated;
|
|
}
|
|
return hle->handleRpc(request);
|
|
}
|
|
|
|
void IopSubsystem::onSifTransfer(const SifTransfer &transfer)
|
|
{
|
|
for (auto &service : m_impl->coreServices)
|
|
{
|
|
if (service && m_impl->serviceActive(*service))
|
|
{
|
|
service->onSifTransfer(transfer);
|
|
}
|
|
}
|
|
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;
|
|
snapshot.emulatorCycles = m_impl->emulator.cycles();
|
|
snapshot.emulatorInstructions = m_impl->emulator.instructions();
|
|
snapshot.emulatorLoadedModules = m_impl->emulator.loadedModuleCount();
|
|
snapshot.emulatorThreads = m_impl->emulator.threadCount();
|
|
snapshot.emulatorRpcServers = m_impl->emulator.rpcServerCount();
|
|
snapshot.diagnostics = m_impl->loadOutcomes;
|
|
if (!m_impl->lastError.empty())
|
|
{
|
|
snapshot.diagnostics.push_back(m_impl->lastError);
|
|
}
|
|
|
|
for (const auto &service : m_impl->coreServices)
|
|
{
|
|
DebugService row;
|
|
row.name = service->name();
|
|
row.sids.assign(service->sids().begin(), service->sids().end());
|
|
row.active = m_impl->serviceActive(*service);
|
|
service->appendDebugMetrics(row.metrics);
|
|
snapshot.services.push_back(std::move(row));
|
|
}
|
|
return snapshot;
|
|
}
|
|
}
|