mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
f49ca4edbc
* feat: implement fix and changes based on dark cloud report fix: fix GS AFAIL for RGB/alpha/Z, ZMSK fix: fix VU1 flags mask and pipeline fix: small VU1 cache fix feat: __ct__, __sinit_ are not sillent stubs anymore * feat: fix song JP pulling * feat: sound update for lotR * feat: prevent guest execution to be very slow * fix: small gs size bug * feat: refactor VU fix: fix cliping and other issues on gs fix: fix wrong vu0 register on recompiler * fix fix ACC scheduler stall feat: remove unused test fix: .fix overflow e underflow on FMAC * feat: small setting for windows test
2612 lines
83 KiB
C++
2612 lines
83 KiB
C++
#include "ps2_runtime.h"
|
|
#include "ps2_log.h"
|
|
#include "ps2_stubs.h"
|
|
#include "ps2_syscalls.h"
|
|
#include "game_overrides.h"
|
|
#include "ps2_runtime_macros.h"
|
|
#include "runtime/ps2_gs_gpu.h"
|
|
#include "ThreadNaming.h"
|
|
#include "Kernel/Stubs/Audio.h"
|
|
#include "Kernel/Stubs/GS.h"
|
|
#include "Kernel/Stubs/MPEG.h"
|
|
#include "ps2_host_backend.h"
|
|
#include "ps2_iop_host.h"
|
|
#include "ps2x/iop/iop_subsystem.h"
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <chrono>
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <sstream>
|
|
|
|
namespace ps2_stubs
|
|
{
|
|
void resetSifState();
|
|
}
|
|
|
|
#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian
|
|
#define ET_EXEC 2 // Executable file
|
|
#define EM_MIPS 8 // MIPS architecture
|
|
#define PT_LOAD 1 // Loadable segment
|
|
|
|
static constexpr int FB_WIDTH = 640;
|
|
static constexpr int FB_HEIGHT = 512;
|
|
static constexpr int DEFAULT_DISPLAY_HEIGHT = 448;
|
|
static constexpr uint32_t DEFAULT_FB_SIZE = FB_WIDTH * FB_HEIGHT * 4;
|
|
static constexpr uint32_t DEFAULT_FB_ADDR = (PS2_RAM_SIZE - DEFAULT_FB_SIZE - 0x10000u);
|
|
#if defined(PLATFORM_VITA)
|
|
static constexpr int HOST_WINDOW_WIDTH = 960;
|
|
static constexpr int HOST_WINDOW_HEIGHT = 544;
|
|
#else
|
|
static constexpr int HOST_WINDOW_WIDTH = FB_WIDTH;
|
|
static constexpr int HOST_WINDOW_HEIGHT = DEFAULT_DISPLAY_HEIGHT;
|
|
#endif
|
|
struct ElfHeader
|
|
{
|
|
uint32_t magic;
|
|
uint8_t elf_class;
|
|
uint8_t endianness;
|
|
uint8_t version;
|
|
uint8_t os_abi;
|
|
uint8_t abi_version;
|
|
uint8_t padding[7];
|
|
uint16_t type;
|
|
uint16_t machine;
|
|
uint32_t version2;
|
|
uint32_t entry;
|
|
uint32_t phoff;
|
|
uint32_t shoff;
|
|
uint32_t flags;
|
|
uint16_t ehsize;
|
|
uint16_t phentsize;
|
|
uint16_t phnum;
|
|
uint16_t shentsize;
|
|
uint16_t shnum;
|
|
uint16_t shstrndx;
|
|
};
|
|
|
|
struct ProgramHeader
|
|
{
|
|
uint32_t type;
|
|
uint32_t offset;
|
|
uint32_t vaddr;
|
|
uint32_t paddr;
|
|
uint32_t filesz;
|
|
uint32_t memsz;
|
|
uint32_t flags;
|
|
uint32_t align;
|
|
};
|
|
|
|
namespace
|
|
{
|
|
constexpr uint32_t kGuestHeapDefaultBase = 0x00100000u;
|
|
constexpr uint32_t kGuestHeapDefaultAlignment = 16u;
|
|
constexpr uint32_t kGuestHeapSafetyPad = 0x1000u;
|
|
constexpr uint32_t kGuestHeapHardLimit = 0x01F00000u;
|
|
|
|
constexpr uint32_t COP0_CAUSE_EXCCODE_MASK = 0x0000007Cu;
|
|
constexpr uint32_t COP0_CAUSE_BD = 0x80000000u;
|
|
constexpr uint32_t COP0_STATUS_EXL = 0x00000002u;
|
|
constexpr uint32_t COP0_STATUS_BEV = 0x00400000u;
|
|
constexpr uint32_t EXCEPTION_VECTOR_GENERAL = 0x80000080u;
|
|
constexpr uint32_t EXCEPTION_VECTOR_TLB_REFILL = 0x80000000u;
|
|
constexpr uint32_t EXCEPTION_VECTOR_BOOT = 0xBFC00200u;
|
|
|
|
struct DispatchHistory
|
|
{
|
|
std::array<uint32_t, 64> pcs{};
|
|
uint32_t next = 0u;
|
|
bool wrapped = false;
|
|
};
|
|
|
|
thread_local DispatchHistory g_dispatchHistory;
|
|
thread_local std::unordered_map<PS2Runtime *, uint32_t> g_guestExecutionDepths;
|
|
thread_local uint32_t g_deferredGuestYieldDepth = 0u;
|
|
thread_local bool g_deferredGuestYieldPending = false;
|
|
|
|
bool computeFileCrc32(const std::string &path, uint32_t &crcOut)
|
|
{
|
|
std::ifstream file(path, std::ios::binary);
|
|
if (!file.is_open())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static const std::array<uint32_t, 256> table = []
|
|
{
|
|
std::array<uint32_t, 256> values{};
|
|
for (uint32_t i = 0; i < values.size(); ++i)
|
|
{
|
|
uint32_t value = i;
|
|
for (uint32_t bit = 0; bit < 8; ++bit)
|
|
{
|
|
value = (value & 1u) ? (0xEDB88320u ^ (value >> 1u)) : (value >> 1u);
|
|
}
|
|
values[i] = value;
|
|
}
|
|
return values;
|
|
}();
|
|
|
|
uint32_t crc = 0xFFFFFFFFu;
|
|
std::array<uint8_t, 16 * 1024> buffer{};
|
|
while (file.good())
|
|
{
|
|
file.read(reinterpret_cast<char *>(buffer.data()), static_cast<std::streamsize>(buffer.size()));
|
|
const std::streamsize count = file.gcount();
|
|
for (std::streamsize i = 0; i < count; ++i)
|
|
{
|
|
crc = table[(crc ^ buffer[static_cast<size_t>(i)]) & 0xFFu] ^ (crc >> 8u);
|
|
}
|
|
}
|
|
if (file.bad())
|
|
{
|
|
return false;
|
|
}
|
|
crcOut = ~crc;
|
|
return true;
|
|
}
|
|
|
|
void pushDispatchPc(uint32_t pc)
|
|
{
|
|
DispatchHistory &h = g_dispatchHistory;
|
|
h.pcs[h.next] = pc;
|
|
h.next = (h.next + 1u) % static_cast<uint32_t>(h.pcs.size());
|
|
if (h.next == 0u)
|
|
{
|
|
h.wrapped = true;
|
|
}
|
|
}
|
|
|
|
std::string formatDispatchHistory()
|
|
{
|
|
const DispatchHistory &h = g_dispatchHistory;
|
|
const uint32_t count = h.wrapped ? static_cast<uint32_t>(h.pcs.size()) : h.next;
|
|
if (count == 0u)
|
|
{
|
|
return "(empty)";
|
|
}
|
|
|
|
std::ostringstream oss;
|
|
bool first = true;
|
|
for (uint32_t i = 0u; i < count; ++i)
|
|
{
|
|
const uint32_t idx = (h.next + h.pcs.size() - count + i) % static_cast<uint32_t>(h.pcs.size());
|
|
if (!first)
|
|
{
|
|
oss << " -> ";
|
|
}
|
|
first = false;
|
|
oss << "0x" << std::hex << h.pcs[idx];
|
|
}
|
|
return oss.str();
|
|
}
|
|
|
|
uint32_t selectExceptionVector(const R5900Context *ctx, bool tlbRefill)
|
|
{
|
|
if (ctx->cop0_status & COP0_STATUS_BEV)
|
|
{
|
|
return EXCEPTION_VECTOR_BOOT;
|
|
}
|
|
return tlbRefill ? EXCEPTION_VECTOR_TLB_REFILL : EXCEPTION_VECTOR_GENERAL;
|
|
}
|
|
|
|
void seedVu0IdleSuccess(R5900Context *ctx)
|
|
{
|
|
if (!ctx)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ctx->vu0_clip_flags = 0;
|
|
ctx->vu0_clip_flags2 = 0;
|
|
ctx->vu0_mac_flags = 0;
|
|
ctx->vu0_status = 0;
|
|
ctx->vu0_q = 1.0f;
|
|
ctx->vu0_r = _mm_castsi128_ps(_mm_set1_epi32(0x3F800000));
|
|
ctx->vu0_vpu_stat = 0;
|
|
ctx->vu0_vpu_stat2 = 0;
|
|
}
|
|
|
|
void copyVu0ContextToState(const R5900Context *ctx, VU1State &state)
|
|
{
|
|
std::memset(&state, 0, sizeof(state));
|
|
|
|
for (uint32_t i = 0; i < 32u; ++i)
|
|
{
|
|
_mm_storeu_ps(state.vf[i], ctx->vu0_vf[i]);
|
|
}
|
|
for (uint32_t i = 0; i < 16u; ++i)
|
|
{
|
|
state.vi[i] = static_cast<int16_t>(ctx->vi[i]);
|
|
}
|
|
|
|
_mm_storeu_ps(state.acc, ctx->vu0_acc);
|
|
state.q = ctx->vu0_q;
|
|
state.p = ctx->vu0_p;
|
|
state.i = ctx->vu0_i;
|
|
alignas(16) uint32_t rWords[4]{};
|
|
_mm_storeu_si128(reinterpret_cast<__m128i *>(rWords), _mm_castps_si128(ctx->vu0_r));
|
|
state.r = 0x3F800000u | (rWords[0] & 0x007FFFFFu);
|
|
state.pc = ctx->vu0_pc;
|
|
state.mac = ctx->vu0_mac_flags;
|
|
state.clip = ctx->vu0_clip_flags;
|
|
state.status = ctx->vu0_status;
|
|
state.itop = ctx->vu0_itop;
|
|
state.dBitEnabled = (ctx->vu0_fbrst & (1u << 2)) != 0u;
|
|
state.tBitEnabled = (ctx->vu0_fbrst & (1u << 3)) != 0u;
|
|
|
|
state.vf[0][0] = 0.0f;
|
|
state.vf[0][1] = 0.0f;
|
|
state.vf[0][2] = 0.0f;
|
|
state.vf[0][3] = 1.0f;
|
|
state.vi[0] = 0;
|
|
}
|
|
|
|
void copyVu0StateToContext(const VU1State &state, R5900Context *ctx)
|
|
{
|
|
for (uint32_t i = 0; i < 32u; ++i)
|
|
{
|
|
ctx->vu0_vf[i] = _mm_loadu_ps(state.vf[i]);
|
|
}
|
|
for (uint32_t i = 0; i < 16u; ++i)
|
|
{
|
|
ctx->vi[i] = static_cast<uint16_t>(state.vi[i]);
|
|
}
|
|
|
|
ctx->vu0_acc = _mm_loadu_ps(state.acc);
|
|
ctx->vu0_q = state.q;
|
|
ctx->vu0_p = state.p;
|
|
ctx->vu0_i = state.i;
|
|
ctx->vu0_r = _mm_castsi128_ps(_mm_set1_epi32(static_cast<int32_t>(state.r)));
|
|
ctx->vu0_mac_flags = state.mac;
|
|
ctx->vu0_clip_flags = state.clip;
|
|
ctx->vu0_clip_flags2 = state.clip;
|
|
ctx->vu0_status = static_cast<uint16_t>(state.status);
|
|
ctx->vu0_itop = state.itop;
|
|
ctx->vu0_pc = state.pc;
|
|
ctx->vu0_tpc = state.pc;
|
|
ctx->vu0_vpu_stat = (ctx->vu0_vpu_stat & 0xFF00u) | (state.stoppedByD ? (1u << 1) : 0u) | (state.stoppedByT ? (1u << 2) : 0u);
|
|
ctx->vu0_vpu_stat2 = 0;
|
|
|
|
ctx->vu0_vf[0] = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f);
|
|
ctx->vi[0] = 0;
|
|
}
|
|
|
|
void raiseCop0Exception(R5900Context *ctx, uint32_t exceptionCode, bool tlbRefill = false)
|
|
{
|
|
if (ctx->in_delay_slot)
|
|
{
|
|
ctx->cop0_epc = ctx->branch_pc;
|
|
ctx->cop0_cause = (ctx->cop0_cause & ~COP0_CAUSE_EXCCODE_MASK) |
|
|
((exceptionCode << 2) & COP0_CAUSE_EXCCODE_MASK) |
|
|
COP0_CAUSE_BD;
|
|
}
|
|
else
|
|
{
|
|
ctx->cop0_epc = ctx->pc;
|
|
ctx->cop0_cause = (ctx->cop0_cause & ~(COP0_CAUSE_EXCCODE_MASK | COP0_CAUSE_BD)) |
|
|
((exceptionCode << 2) & COP0_CAUSE_EXCCODE_MASK);
|
|
}
|
|
|
|
ctx->cop0_status |= COP0_STATUS_EXL;
|
|
ctx->pc = selectExceptionVector(ctx, tlbRefill);
|
|
ctx->in_delay_slot = false;
|
|
}
|
|
|
|
std::filesystem::path normalizeAbsolutePath(const std::filesystem::path &path)
|
|
{
|
|
if (path.empty())
|
|
{
|
|
return {};
|
|
}
|
|
|
|
#if defined(PLATFORM_VITA)
|
|
const std::string generic = path.generic_string();
|
|
const std::size_t colon = generic.find(':');
|
|
if (colon != std::string::npos && colon != 0u)
|
|
{
|
|
const std::size_t slash = generic.find_first_of("/\\");
|
|
if (slash == std::string::npos || colon < slash)
|
|
{
|
|
return path.lexically_normal();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
std::error_code ec;
|
|
const std::filesystem::path absolute = std::filesystem::absolute(path, ec);
|
|
if (ec)
|
|
{
|
|
return path.lexically_normal();
|
|
}
|
|
return absolute.lexically_normal();
|
|
}
|
|
|
|
PS2Runtime::IoPaths &runtimeIoPaths()
|
|
{
|
|
static PS2Runtime::IoPaths paths = []()
|
|
{
|
|
PS2Runtime::IoPaths defaults;
|
|
std::error_code ec;
|
|
const std::filesystem::path cwd = std::filesystem::current_path(ec);
|
|
defaults.elfDirectory = ec ? std::filesystem::path(".") : cwd.lexically_normal();
|
|
defaults.hostRoot = defaults.elfDirectory;
|
|
defaults.cdRoot = defaults.elfDirectory;
|
|
defaults.mcRoot = defaults.elfDirectory / "mc0";
|
|
return defaults;
|
|
}();
|
|
|
|
return paths;
|
|
}
|
|
|
|
std::string readGuestPrintableString(const uint8_t *rdram, uint32_t addr, size_t maxLen)
|
|
{
|
|
std::string out;
|
|
if (!rdram || maxLen == 0)
|
|
{
|
|
return out;
|
|
}
|
|
|
|
out.reserve(std::min<size_t>(maxLen, 64));
|
|
for (size_t i = 0; i < maxLen; ++i)
|
|
{
|
|
const char ch = static_cast<char>(rdram[(addr + static_cast<uint32_t>(i)) & PS2_RAM_MASK]);
|
|
if (ch == '\0')
|
|
{
|
|
break;
|
|
}
|
|
if (ch >= 0x20 && ch < 0x7F)
|
|
{
|
|
out.push_back(ch);
|
|
}
|
|
else
|
|
{
|
|
out.push_back('.');
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
|
|
PS2Runtime::GuestExecutionScope::GuestExecutionScope(PS2Runtime *runtime) noexcept
|
|
: m_runtime(runtime)
|
|
{
|
|
if (m_runtime)
|
|
{
|
|
m_runtime->enterGuestExecution();
|
|
}
|
|
}
|
|
|
|
PS2Runtime::GuestExecutionScope::~GuestExecutionScope()
|
|
{
|
|
if (m_runtime)
|
|
{
|
|
m_runtime->leaveGuestExecution();
|
|
}
|
|
}
|
|
|
|
PS2Runtime::GuestExecutionReleaseScope::GuestExecutionReleaseScope(PS2Runtime *runtime) noexcept
|
|
: m_runtime(runtime)
|
|
{
|
|
if (m_runtime)
|
|
{
|
|
m_depth = m_runtime->releaseGuestExecution();
|
|
}
|
|
}
|
|
|
|
PS2Runtime::GuestExecutionReleaseScope::~GuestExecutionReleaseScope()
|
|
{
|
|
if (m_runtime && m_depth != 0u)
|
|
{
|
|
m_runtime->reacquireGuestExecution(m_depth);
|
|
}
|
|
}
|
|
|
|
static void UploadFrame(Texture2D &tex, PS2Runtime *rt, uint32_t &outWidth, uint32_t &outHeight)
|
|
{
|
|
static uint64_t s_lastPresentationTick = std::numeric_limits<uint64_t>::max();
|
|
static bool s_hasLatchedInitialFrame = false;
|
|
static uint32_t s_lastDisplayFbp = std::numeric_limits<uint32_t>::max();
|
|
static uint32_t s_lastSourceFbp = std::numeric_limits<uint32_t>::max();
|
|
static bool s_lastPreferred = false;
|
|
static uint32_t s_lastWidth = 0u;
|
|
static uint32_t s_lastHeight = 0u;
|
|
static bool s_hasUploadedFrame = false;
|
|
static std::vector<uint8_t> s_scratch;
|
|
static std::vector<uint8_t> s_uploadBuffer(DEFAULT_FB_SIZE, 0u);
|
|
|
|
const uint64_t currentTick = ps2_syscalls::GetCurrentVSyncTick();
|
|
const bool needsLatch = !s_hasLatchedInitialFrame || currentTick != s_lastPresentationTick;
|
|
if (needsLatch)
|
|
{
|
|
rt->gs().latchHostPresentationFrame();
|
|
s_lastPresentationTick = currentTick;
|
|
s_hasLatchedInitialFrame = true;
|
|
}
|
|
else if (s_hasUploadedFrame)
|
|
{
|
|
outWidth = (s_lastWidth != 0u) ? s_lastWidth : FB_WIDTH;
|
|
outHeight = (s_lastHeight != 0u) ? s_lastHeight : DEFAULT_DISPLAY_HEIGHT;
|
|
return;
|
|
}
|
|
|
|
s_scratch.clear();
|
|
uint32_t width = 0u;
|
|
uint32_t height = 0u;
|
|
uint32_t displayFbp = 0u;
|
|
uint32_t sourceFbp = 0u;
|
|
bool usedPreferredDisplaySource = false;
|
|
if (!rt->gs().copyLatchedHostPresentationFrame(s_scratch,
|
|
width,
|
|
height,
|
|
&displayFbp,
|
|
&sourceFbp,
|
|
&usedPreferredDisplaySource))
|
|
{
|
|
Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, MAGENTA);
|
|
UpdateTexture(tex, blank.data);
|
|
UnloadImage(blank);
|
|
outWidth = FB_WIDTH;
|
|
outHeight = DEFAULT_DISPLAY_HEIGHT;
|
|
s_lastWidth = outWidth;
|
|
s_lastHeight = outHeight;
|
|
s_hasUploadedFrame = true;
|
|
return;
|
|
}
|
|
|
|
PS2_IF_AGRESSIVE_LOGS({
|
|
static uint32_t s_uploadDebugCount = 0u;
|
|
if (s_uploadDebugCount < 128u ||
|
|
displayFbp != s_lastDisplayFbp ||
|
|
sourceFbp != s_lastSourceFbp ||
|
|
usedPreferredDisplaySource != s_lastPreferred ||
|
|
width != s_lastWidth ||
|
|
height != s_lastHeight)
|
|
{
|
|
std::cout << "[frame:upload] idx=" << s_uploadDebugCount
|
|
<< " tick=" << currentTick
|
|
<< " displayFbp=" << displayFbp
|
|
<< " sourceFbp=" << sourceFbp
|
|
<< " size=" << width << "x" << height
|
|
<< " preferred=" << static_cast<uint32_t>(usedPreferredDisplaySource ? 1u : 0u)
|
|
<< std::endl;
|
|
}
|
|
++s_uploadDebugCount;
|
|
});
|
|
s_lastDisplayFbp = displayFbp;
|
|
s_lastSourceFbp = sourceFbp;
|
|
s_lastPreferred = usedPreferredDisplaySource;
|
|
s_lastWidth = width;
|
|
s_lastHeight = height;
|
|
|
|
std::fill(s_uploadBuffer.begin(), s_uploadBuffer.end(), 0u);
|
|
if (!s_scratch.empty() && width != 0u && height != 0u)
|
|
{
|
|
const uint32_t copyWidth = std::min<uint32_t>(width, FB_WIDTH);
|
|
const uint32_t copyHeight = std::min<uint32_t>(height, FB_HEIGHT);
|
|
const size_t srcRowBytes = static_cast<size_t>(width) * 4u;
|
|
const size_t dstRowBytes = static_cast<size_t>(FB_WIDTH) * 4u;
|
|
const size_t copyRowBytes = static_cast<size_t>(copyWidth) * 4u;
|
|
for (uint32_t y = 0; y < copyHeight; ++y)
|
|
{
|
|
const size_t srcOffset = static_cast<size_t>(y) * srcRowBytes;
|
|
const size_t dstOffset = static_cast<size_t>(y) * dstRowBytes;
|
|
if (srcOffset + copyRowBytes > s_scratch.size() ||
|
|
dstOffset + copyRowBytes > s_uploadBuffer.size())
|
|
{
|
|
break;
|
|
}
|
|
std::memcpy(s_uploadBuffer.data() + dstOffset, s_scratch.data() + srcOffset, copyRowBytes);
|
|
}
|
|
}
|
|
|
|
UpdateTexture(tex, s_uploadBuffer.data());
|
|
outWidth = width;
|
|
outHeight = height;
|
|
s_hasUploadedFrame = true;
|
|
}
|
|
|
|
PS2Runtime::PS2Runtime()
|
|
{
|
|
m_iopHost = std::make_unique<PS2IopHostAdapter>(*this);
|
|
m_iopSubsystem = std::make_unique<ps2x::iop::IopSubsystem>(*m_iopHost);
|
|
#if defined(PS2X_IOP_ENABLE_PLUGINS) && PS2X_IOP_ENABLE_PLUGINS && \
|
|
!defined(PLATFORM_VITA) && (defined(_WIN32) || defined(__linux__))
|
|
if (const char *applicationDirectory = GetApplicationDirectory();
|
|
applicationDirectory && applicationDirectory[0] != '\0')
|
|
{
|
|
m_iopSubsystem->setPluginSearchPaths({std::filesystem::path(applicationDirectory) / "iop_plugins"});
|
|
}
|
|
#endif
|
|
|
|
std::memset(&m_cpuContext, 0, sizeof(m_cpuContext));
|
|
|
|
// R0 is always zero in MIPS
|
|
m_cpuContext.r[0] = _mm_set1_epi32(0);
|
|
m_cpuContext.vu0_vf[0] = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f);
|
|
m_cpuContext.vu0_q = 1.0f;
|
|
m_cpuContext.vu0_r = _mm_castsi128_ps(_mm_set1_epi32(0x3F800000));
|
|
|
|
// Stack pointer (SP) and global pointer (GP) will be set by the loaded ELF
|
|
|
|
m_loadedModules.clear();
|
|
m_guestHeapBlocks.clear();
|
|
m_guestHeapBase = kGuestHeapDefaultBase;
|
|
m_guestHeapEnd = kGuestHeapDefaultBase;
|
|
m_guestHeapLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
|
m_guestHeapSuggestedBase = kGuestHeapDefaultBase;
|
|
m_guestHeapConfigured = false;
|
|
m_asyncCallbackStackFloor = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
|
m_asyncCallbackStackTop = PS2_RAM_SIZE;
|
|
}
|
|
|
|
void PS2Runtime::setDebugUiCallbacks(DebugUiCallback initCallback,
|
|
DebugUiCallback drawCallback,
|
|
DebugUiCallback shutdownCallback,
|
|
void *userData)
|
|
{
|
|
if (m_debugUiInitialized && m_debugUiShutdownCallback)
|
|
{
|
|
m_debugUiShutdownCallback(*this, m_debugUiUserData);
|
|
m_debugUiInitialized = false;
|
|
}
|
|
|
|
m_debugUiInitCallback = initCallback;
|
|
m_debugUiDrawCallback = drawCallback;
|
|
m_debugUiShutdownCallback = shutdownCallback;
|
|
m_debugUiUserData = userData;
|
|
}
|
|
|
|
PS2Runtime::~PS2Runtime()
|
|
{
|
|
try
|
|
{
|
|
requestStop();
|
|
ps2_syscalls::detachAllGuestHostThreads();
|
|
m_iopSubsystem.reset();
|
|
m_iopHost.reset();
|
|
#if defined(PLATFORM_VITA)
|
|
m_audioBackend.stopAll();
|
|
m_audioBackend.setAudioReady(false);
|
|
#else
|
|
if (IsAudioDeviceReady())
|
|
{
|
|
CloseAudioDevice();
|
|
m_audioBackend.setAudioReady(false);
|
|
}
|
|
#endif
|
|
if (m_debugUiInitialized && m_debugUiShutdownCallback)
|
|
{
|
|
m_debugUiShutdownCallback(*this, m_debugUiUserData);
|
|
m_debugUiInitialized = false;
|
|
}
|
|
|
|
if (IsWindowReady())
|
|
{
|
|
CloseWindow();
|
|
}
|
|
|
|
m_loadedModules.clear();
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << "[~PS2Runtime] cleanup exception: " << e.what() << std::endl;
|
|
}
|
|
catch (...)
|
|
{
|
|
std::cerr << "[~PS2Runtime] cleanup exception: unknown" << std::endl;
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::setIopPluginSearchPaths(std::vector<std::filesystem::path> paths)
|
|
{
|
|
m_iopSubsystem->setPluginSearchPaths(std::move(paths));
|
|
}
|
|
|
|
ps2x::iop::RpcAbi PS2Runtime::selectIopRpcAbi(const ps2x::iop::RpcAbiRequest &request) const
|
|
{
|
|
return m_iopSubsystem->selectRpcAbi(request);
|
|
}
|
|
|
|
ps2x::iop::RpcResult PS2Runtime::handleIopRpc(uint8_t *rdram, R5900Context *ctx, ps2x::iop::RpcRequest request)
|
|
{
|
|
auto scope = m_iopHost->enterCall(ctx, rdram);
|
|
request.callToken = scope.token();
|
|
return m_iopSubsystem->handleRpc(request);
|
|
}
|
|
|
|
void PS2Runtime::notifyIopSifTransfer(uint8_t *rdram, const ps2x::iop::SifTransfer &transfer)
|
|
{
|
|
auto scope = m_iopHost->enterCall(nullptr, rdram);
|
|
m_iopSubsystem->onSifTransfer(transfer);
|
|
}
|
|
|
|
void PS2Runtime::resetIop()
|
|
{
|
|
m_iopSubsystem->reset();
|
|
}
|
|
|
|
ps2x::iop::DebugSnapshot PS2Runtime::iopDebugSnapshot() const
|
|
{
|
|
return m_iopSubsystem->debugSnapshot();
|
|
}
|
|
|
|
bool PS2Runtime::syncCoreSubsystems()
|
|
{
|
|
uint8_t *const rdram = m_memory.getRDRAM();
|
|
uint8_t *const gsVram = m_memory.getGSVRAM();
|
|
if (!rdram || !gsVram)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_boundRdram == rdram && m_boundGSVram == gsVram)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
m_gs.init(gsVram, static_cast<uint32_t>(PS2_GS_VRAM_SIZE), &m_memory.gs());
|
|
m_gifArbiter.setProcessPacketFn([this](const uint8_t *data, uint32_t size)
|
|
{ m_gs.processGIFPacket(data, size); });
|
|
m_memory.setGifArbiter(&m_gifArbiter);
|
|
m_memory.setVu1MscalCallback([this](uint32_t startPC, uint32_t top, uint32_t itop)
|
|
{
|
|
m_vu1.state().dBitEnabled =
|
|
(m_cpuContext.vu0_fbrst & (1u << 10)) != 0u;
|
|
m_vu1.state().tBitEnabled =
|
|
(m_cpuContext.vu0_fbrst & (1u << 11)) != 0u;
|
|
m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
|
|
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
|
|
m_gs, &m_memory, startPC, top, itop, 65536);
|
|
m_cpuContext.vu0_vpu_stat =
|
|
(m_cpuContext.vu0_vpu_stat & ~0x0600u) |
|
|
(m_vu1.state().stoppedByD ? 0x0200u : 0u) |
|
|
(m_vu1.state().stoppedByT ? 0x0400u : 0u); });
|
|
m_memory.setVu1MscntCallback([this](uint32_t top, uint32_t itop)
|
|
{
|
|
m_vu1.state().dBitEnabled =
|
|
(m_cpuContext.vu0_fbrst & (1u << 10)) != 0u;
|
|
m_vu1.state().tBitEnabled =
|
|
(m_cpuContext.vu0_fbrst & (1u << 11)) != 0u;
|
|
m_vu1.resume(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
|
|
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
|
|
m_gs, &m_memory, top, itop, 65536);
|
|
m_cpuContext.vu0_vpu_stat =
|
|
(m_cpuContext.vu0_vpu_stat & ~0x0600u) |
|
|
(m_vu1.state().stoppedByD ? 0x0200u : 0u) |
|
|
(m_vu1.state().stoppedByT ? 0x0400u : 0u); });
|
|
resetIop();
|
|
m_vu0.reset();
|
|
m_vu1.reset();
|
|
|
|
m_boundRdram = rdram;
|
|
m_boundGSVram = gsVram;
|
|
return true;
|
|
}
|
|
|
|
bool PS2Runtime::initialize(const char *title)
|
|
{
|
|
try
|
|
{
|
|
if (!m_memory.initialize())
|
|
{
|
|
std::cerr << "Failed to initialize PS2 memory" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (!syncCoreSubsystems())
|
|
{
|
|
std::cerr << "Failed to bind runtime core subsystems" << std::endl;
|
|
return false;
|
|
}
|
|
#if defined(PS2X_IOP_ENABLE_PLUGINS) && PS2X_IOP_ENABLE_PLUGINS && \
|
|
!defined(PLATFORM_VITA) && (defined(_WIN32) || defined(__linux__))
|
|
std::string pluginError;
|
|
if (!m_iopSubsystem->loadPlugins(&pluginError))
|
|
{
|
|
std::cerr << "Failed to load IOP plugins: " << pluginError << std::endl;
|
|
return false;
|
|
}
|
|
#endif
|
|
#if defined(PLATFORM_VITA)
|
|
InitWindow(HOST_WINDOW_WIDTH, HOST_WINDOW_HEIGHT, title); // raylib vita does not support audio
|
|
#else
|
|
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
|
InitWindow(HOST_WINDOW_WIDTH, HOST_WINDOW_HEIGHT, title);
|
|
InitAudioDevice();
|
|
m_audioBackend.setAudioReady(IsAudioDeviceReady());
|
|
#endif
|
|
SetTargetFPS(60);
|
|
if (m_debugUiInitCallback)
|
|
{
|
|
m_debugUiInitCallback(*this, m_debugUiUserData);
|
|
m_debugUiInitialized = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << "Failed to initialize PS2 runtime: " << e.what() << std::endl;
|
|
}
|
|
catch (...)
|
|
{
|
|
std::cerr << "Failed to initialize PS2 runtime: unknown exception" << std::endl;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PS2Runtime::loadELF(const std::string &elfPath)
|
|
{
|
|
configureIoPathsFromElf(elfPath);
|
|
|
|
std::ifstream file(elfPath, std::ios::binary);
|
|
if (!file)
|
|
{
|
|
std::cerr << "Failed to open ELF file: " << elfPath << std::endl;
|
|
return false;
|
|
}
|
|
|
|
file.seekg(0, std::ios::end);
|
|
const std::streamoff fileSize = file.tellg();
|
|
if (fileSize < static_cast<std::streamoff>(sizeof(ElfHeader)))
|
|
{
|
|
std::cerr << "ELF file is too small: " << elfPath << std::endl;
|
|
return false;
|
|
}
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
ElfHeader header{};
|
|
if (!file.read(reinterpret_cast<char *>(&header), sizeof(header)))
|
|
{
|
|
std::cerr << "Failed to read ELF header from: " << elfPath << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (header.magic != ELF_MAGIC)
|
|
{
|
|
std::cerr << "Invalid ELF magic number" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (header.elf_class != 1u || header.endianness != 1u)
|
|
{
|
|
std::cerr << "Unsupported ELF format (expected 32-bit little-endian)." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (header.machine != EM_MIPS || header.type != ET_EXEC)
|
|
{
|
|
std::cerr << "Not a MIPS executable ELF file" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (header.phnum != 0u && header.phentsize < sizeof(ProgramHeader))
|
|
{
|
|
std::cerr << "Unsupported ELF program-header entry size: " << header.phentsize << std::endl;
|
|
return false;
|
|
}
|
|
|
|
const uint64_t programHeaderTableEnd =
|
|
static_cast<uint64_t>(header.phoff) +
|
|
static_cast<uint64_t>(header.phnum) * static_cast<uint64_t>(header.phentsize);
|
|
if (programHeaderTableEnd > static_cast<uint64_t>(fileSize))
|
|
{
|
|
std::cerr << "ELF program-header table is out of range." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
m_cpuContext.pc = header.entry;
|
|
m_debugPc.store(m_cpuContext.pc, std::memory_order_relaxed);
|
|
|
|
uint32_t maxLoadedRdramEnd = kGuestHeapDefaultBase;
|
|
uint32_t moduleBase = std::numeric_limits<uint32_t>::max();
|
|
uint32_t moduleEnd = 0u;
|
|
bool loadedAnySegment = false;
|
|
|
|
for (uint16_t i = 0; i < header.phnum; i++)
|
|
{
|
|
const uint64_t phOffset =
|
|
static_cast<uint64_t>(header.phoff) +
|
|
static_cast<uint64_t>(i) * static_cast<uint64_t>(header.phentsize);
|
|
if (phOffset + sizeof(ProgramHeader) > static_cast<uint64_t>(fileSize))
|
|
{
|
|
std::cerr << "ELF program header " << i << " is out of range." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
ProgramHeader ph{};
|
|
file.seekg(static_cast<std::streamoff>(phOffset), std::ios::beg);
|
|
if (!file.read(reinterpret_cast<char *>(&ph), sizeof(ph)))
|
|
{
|
|
std::cerr << "Failed to read ELF program header " << i << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (ph.type != PT_LOAD || ph.memsz == 0u)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ph.filesz > ph.memsz)
|
|
{
|
|
std::cerr << "ELF segment " << i << " has filesz > memsz." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
const uint64_t segmentFileEnd = static_cast<uint64_t>(ph.offset) + static_cast<uint64_t>(ph.filesz);
|
|
if (segmentFileEnd > static_cast<uint64_t>(fileSize))
|
|
{
|
|
std::cerr << "ELF segment " << i << " exceeds file bounds." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
const bool scratch =
|
|
ph.vaddr >= PS2_SCRATCHPAD_BASE &&
|
|
ph.vaddr < (PS2_SCRATCHPAD_BASE + PS2_SCRATCHPAD_SIZE);
|
|
|
|
uint32_t physAddr = 0u;
|
|
try
|
|
{
|
|
physAddr = m_memory.translateAddress(ph.vaddr);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << "Failed to translate ELF segment " << i
|
|
<< " virtual address 0x" << std::hex << ph.vaddr
|
|
<< std::dec << ": " << e.what() << std::endl;
|
|
return false;
|
|
}
|
|
const uint64_t regionSize = scratch ? static_cast<uint64_t>(PS2_SCRATCHPAD_SIZE)
|
|
: static_cast<uint64_t>(PS2_RAM_SIZE);
|
|
const uint64_t segmentMemEnd = static_cast<uint64_t>(physAddr) + static_cast<uint64_t>(ph.memsz);
|
|
if (segmentMemEnd > regionSize)
|
|
{
|
|
std::cerr << "ELF segment " << i << " exceeds "
|
|
<< (scratch ? "scratchpad" : "RDRAM")
|
|
<< " bounds (vaddr=0x" << std::hex << ph.vaddr
|
|
<< " memsz=0x" << ph.memsz << std::dec << ")." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
uint8_t *destBase = scratch ? m_memory.getScratchpad() : m_memory.getRDRAM();
|
|
if (!destBase)
|
|
{
|
|
std::cerr << "ELF segment " << i << " has no destination memory backing." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
uint8_t *dest = destBase + physAddr;
|
|
if (ph.filesz > 0u)
|
|
{
|
|
file.seekg(static_cast<std::streamoff>(ph.offset), std::ios::beg);
|
|
if (!file.read(reinterpret_cast<char *>(dest), ph.filesz))
|
|
{
|
|
std::cerr << "Failed to read ELF segment " << i << " payload." << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (ph.memsz > ph.filesz)
|
|
{
|
|
std::memset(dest + ph.filesz, 0, ph.memsz - ph.filesz);
|
|
}
|
|
|
|
RUNTIME_LOG("Loading segment: 0x" << std::hex << ph.vaddr
|
|
<< " - 0x" << (static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.memsz))
|
|
<< " (filesz: 0x" << ph.filesz
|
|
<< ", memsz: 0x" << ph.memsz << ")"
|
|
<< std::dec << std::endl);
|
|
|
|
if (!scratch)
|
|
{
|
|
maxLoadedRdramEnd = std::max(maxLoadedRdramEnd, static_cast<uint32_t>(segmentMemEnd));
|
|
}
|
|
|
|
if (ph.flags & 0x1u) // PF_X
|
|
{
|
|
const uint64_t execEnd = static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.filesz);
|
|
if (execEnd <= std::numeric_limits<uint32_t>::max())
|
|
{
|
|
m_memory.registerCodeRegion(ph.vaddr, static_cast<uint32_t>(execEnd));
|
|
}
|
|
}
|
|
|
|
loadedAnySegment = true;
|
|
moduleBase = std::min(moduleBase, ph.vaddr);
|
|
const uint64_t segmentVirtualEnd = static_cast<uint64_t>(ph.vaddr) + static_cast<uint64_t>(ph.memsz);
|
|
const uint32_t clampedVirtualEnd =
|
|
(segmentVirtualEnd > std::numeric_limits<uint32_t>::max())
|
|
? std::numeric_limits<uint32_t>::max()
|
|
: static_cast<uint32_t>(segmentVirtualEnd);
|
|
moduleEnd = std::max(moduleEnd, clampedVirtualEnd);
|
|
}
|
|
|
|
if (!loadedAnySegment)
|
|
{
|
|
std::cerr << "ELF contains no loadable PT_LOAD segments." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (maxLoadedRdramEnd > PS2_RAM_SIZE)
|
|
{
|
|
maxLoadedRdramEnd = PS2_RAM_SIZE;
|
|
}
|
|
|
|
const uint32_t paddedEnd = (maxLoadedRdramEnd > (PS2_RAM_SIZE - kGuestHeapSafetyPad))
|
|
? PS2_RAM_SIZE
|
|
: (maxLoadedRdramEnd + kGuestHeapSafetyPad);
|
|
const uint32_t suggestedHeapBase = alignGuestHeapValue(paddedEnd, kGuestHeapDefaultAlignment);
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
if (!m_guestHeapConfigured)
|
|
{
|
|
const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
|
m_guestHeapSuggestedBase = std::min(suggestedHeapBase, hardLimit);
|
|
m_guestHeapBase = m_guestHeapSuggestedBase;
|
|
m_guestHeapEnd = m_guestHeapSuggestedBase;
|
|
m_guestHeapLimit = hardLimit;
|
|
}
|
|
}
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_asyncCallbackStackMutex);
|
|
const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
|
m_asyncCallbackStackFloor = std::min(std::max(hardLimit, suggestedHeapBase), PS2_RAM_SIZE);
|
|
m_asyncCallbackStackTop = PS2_RAM_SIZE;
|
|
}
|
|
|
|
LoadedModule module;
|
|
module.name = elfPath.substr(elfPath.find_last_of("/\\") + 1);
|
|
module.baseAddress = (moduleBase == std::numeric_limits<uint32_t>::max()) ? 0x00100000u : moduleBase;
|
|
module.size = (moduleEnd > module.baseAddress) ? static_cast<size_t>(moduleEnd - module.baseAddress) : 0u;
|
|
module.active = true;
|
|
|
|
m_loadedModules.push_back(module);
|
|
|
|
uint32_t elfCrc32 = 0u;
|
|
const bool elfCrc32Valid = computeFileCrc32(elfPath, elfCrc32);
|
|
if (!elfCrc32Valid)
|
|
{
|
|
std::cerr << "[ps2xIOP] failed to compute ELF CRC32 for '" << elfPath << "'" << std::endl;
|
|
}
|
|
ps2x::iop::GameIdentity identity;
|
|
identity.elfName = module.name;
|
|
identity.entryPoint = m_cpuContext.pc;
|
|
identity.crc32 = elfCrc32;
|
|
std::string iopError;
|
|
if (!m_iopSubsystem->configure(identity, &iopError))
|
|
{
|
|
std::cerr << "[ps2xIOP] failed to configure profile: " << iopError << std::endl;
|
|
return false;
|
|
}
|
|
|
|
ps2_game_overrides::applyMatching(*this,
|
|
elfPath,
|
|
m_cpuContext.pc,
|
|
elfCrc32,
|
|
elfCrc32Valid);
|
|
|
|
RUNTIME_LOG("ELF file loaded successfully. Entry point: 0x" << std::hex << m_cpuContext.pc << std::dec);
|
|
return true;
|
|
}
|
|
|
|
const PS2Runtime::IoPaths &PS2Runtime::getIoPaths()
|
|
{
|
|
return runtimeIoPaths();
|
|
}
|
|
|
|
void PS2Runtime::setIoPaths(const IoPaths &paths)
|
|
{
|
|
IoPaths normalized = paths;
|
|
normalized.elfPath = normalizeAbsolutePath(normalized.elfPath);
|
|
normalized.elfDirectory = normalizeAbsolutePath(normalized.elfDirectory);
|
|
normalized.hostRoot = normalizeAbsolutePath(normalized.hostRoot);
|
|
normalized.cdRoot = normalizeAbsolutePath(normalized.cdRoot);
|
|
normalized.mcRoot = normalizeAbsolutePath(normalized.mcRoot);
|
|
normalized.cdImage = normalizeAbsolutePath(normalized.cdImage);
|
|
|
|
if (normalized.elfDirectory.empty() && !normalized.elfPath.empty())
|
|
{
|
|
normalized.elfDirectory = normalized.elfPath.parent_path();
|
|
}
|
|
|
|
if (normalized.hostRoot.empty())
|
|
{
|
|
normalized.hostRoot = normalized.elfDirectory;
|
|
}
|
|
if (normalized.cdRoot.empty())
|
|
{
|
|
normalized.cdRoot = normalized.elfDirectory;
|
|
}
|
|
if (normalized.mcRoot.empty())
|
|
{
|
|
normalized.mcRoot = normalized.elfDirectory / "mc0";
|
|
}
|
|
|
|
runtimeIoPaths() = normalized;
|
|
}
|
|
|
|
void PS2Runtime::configureIoPathsFromElf(const std::string &elfPath)
|
|
{
|
|
IoPaths paths = runtimeIoPaths();
|
|
paths.elfPath = normalizeAbsolutePath(std::filesystem::path(elfPath));
|
|
if (!paths.elfPath.empty())
|
|
{
|
|
paths.elfDirectory = paths.elfPath.parent_path();
|
|
}
|
|
|
|
if (!paths.elfDirectory.empty())
|
|
{
|
|
paths.hostRoot = paths.elfDirectory;
|
|
paths.cdRoot = paths.elfDirectory;
|
|
paths.mcRoot = paths.elfDirectory / "mc0";
|
|
}
|
|
|
|
setIoPaths(paths);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
bool generatedFunctionTableSlot(uint32_t address, uint32_t &slot)
|
|
{
|
|
if ((address & 3u) != 0u || g_ps2RecompiledFunctionTableSlotCount == 0u)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (address < g_ps2RecompiledFunctionTableBase || address >= g_ps2RecompiledFunctionTableEnd)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32_t offset = address - g_ps2RecompiledFunctionTableBase;
|
|
slot = offset >> 2;
|
|
return slot < g_ps2RecompiledFunctionTableSlotCount;
|
|
}
|
|
}
|
|
|
|
bool PS2Runtime::replaceFunction(uint32_t address, RecompiledFunction func)
|
|
{
|
|
uint32_t slot = 0u;
|
|
if (!generatedFunctionTableSlot(address, slot))
|
|
{
|
|
std::cerr << "[function-table] cannot replace guest PC 0x" << std::hex << address
|
|
<< ": outside generated dense table [0x" << g_ps2RecompiledFunctionTableBase
|
|
<< ", 0x" << g_ps2RecompiledFunctionTableEnd << ")"
|
|
<< std::dec << std::endl;
|
|
return false;
|
|
}
|
|
|
|
g_ps2RecompiledFunctionTable[slot] = func;
|
|
return true;
|
|
}
|
|
|
|
bool PS2Runtime::registerFunction(uint32_t address, RecompiledFunction func)
|
|
{
|
|
return replaceFunction(address, func);
|
|
}
|
|
|
|
bool PS2Runtime::hasFunction(uint32_t address) const
|
|
{
|
|
uint32_t slot = 0u;
|
|
return generatedFunctionTableSlot(address, slot) && g_ps2RecompiledFunctionTable[slot] != nullptr;
|
|
}
|
|
|
|
const char *describeGuestBranchKind(PS2Runtime::GuestBranchKind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case PS2Runtime::GuestBranchKind::DirectJump:
|
|
return "DirectJump";
|
|
case PS2Runtime::GuestBranchKind::DirectCall:
|
|
return "DirectCall";
|
|
case PS2Runtime::GuestBranchKind::IndirectJump:
|
|
return "IndirectJump";
|
|
case PS2Runtime::GuestBranchKind::IndirectCall:
|
|
return "IndirectCall";
|
|
case PS2Runtime::GuestBranchKind::Return:
|
|
return "Return";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
PS2Runtime::RecompiledFunction PS2Runtime::lookupFunction(uint32_t address)
|
|
{
|
|
pushDispatchPc(address);
|
|
|
|
uint32_t slot = 0u;
|
|
if (generatedFunctionTableSlot(address, slot))
|
|
{
|
|
RecompiledFunction fn = g_ps2RecompiledFunctionTable[slot];
|
|
if (fn != nullptr)
|
|
{
|
|
return fn;
|
|
}
|
|
}
|
|
|
|
std::cerr << "Error: No exact recompiled function for guest PC 0x" << std::hex << address
|
|
<< " tableBase=0x" << g_ps2RecompiledFunctionTableBase
|
|
<< " tableEnd=0x" << g_ps2RecompiledFunctionTableEnd
|
|
<< " codeRegion=" << (m_memory.isCodeAddress(address) ? "yes" : "no")
|
|
<< " trace=" << formatDispatchHistory()
|
|
<< std::dec << std::endl;
|
|
|
|
static RecompiledFunction missingFunction = [](uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
|
{
|
|
const uint32_t badPc = ctx->pc;
|
|
runtime->reportMissingFunction(rdram,
|
|
ctx,
|
|
badPc,
|
|
0u,
|
|
PS2Runtime::GuestBranchKind::IndirectJump,
|
|
"dispatch");
|
|
};
|
|
|
|
return missingFunction;
|
|
}
|
|
|
|
void PS2Runtime::setMissingFunctionPolicy(MissingFunctionPolicy policy)
|
|
{
|
|
m_missingFunctionPolicy.store(static_cast<uint32_t>(policy), std::memory_order_release);
|
|
}
|
|
|
|
PS2Runtime::MissingFunctionPolicy PS2Runtime::missingFunctionPolicy() const
|
|
{
|
|
return static_cast<MissingFunctionPolicy>(m_missingFunctionPolicy.load(std::memory_order_acquire));
|
|
}
|
|
|
|
void PS2Runtime::resetMissingFunctionReportOnce()
|
|
{
|
|
m_missingFunctionReported.store(false, std::memory_order_release);
|
|
}
|
|
|
|
void PS2Runtime::reportMissingFunction(uint8_t *rdram,
|
|
R5900Context *ctx,
|
|
uint32_t targetPc,
|
|
uint32_t sourcePc,
|
|
GuestBranchKind kind,
|
|
const char *debugName)
|
|
{
|
|
const MissingFunctionPolicy policy = missingFunctionPolicy();
|
|
const bool firstReport = !m_missingFunctionReported.exchange(true, std::memory_order_acq_rel);
|
|
|
|
const uint32_t pc = ctx->pc;
|
|
const uint32_t ra = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
|
|
const uint32_t sp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0));
|
|
const uint32_t gp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0));
|
|
const uint32_t a0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[4], 0));
|
|
const uint32_t a1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[5], 0));
|
|
const uint32_t a2 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[6], 0));
|
|
const uint32_t a3 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[7], 0));
|
|
const uint32_t s0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[16], 0));
|
|
const uint32_t s1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[17], 0));
|
|
const uint32_t v0 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[2], 0));
|
|
const uint32_t v1 = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[3], 0));
|
|
|
|
auto readGuestU32At = [rdram](uint32_t addr, uint32_t &out) -> bool
|
|
{
|
|
// TODO this !rdram exist only because of test fix those test later
|
|
if (!rdram || addr > PS2_RAM_SIZE - sizeof(uint32_t))
|
|
{
|
|
out = 0u;
|
|
return false;
|
|
}
|
|
|
|
std::memcpy(&out, rdram + addr, sizeof(uint32_t));
|
|
return true;
|
|
};
|
|
|
|
auto readGuestU32Offset = [&readGuestU32At](uint32_t base, uint32_t offset, uint32_t &out) -> bool
|
|
{
|
|
if (base > PS2_RAM_SIZE - sizeof(uint32_t) || offset > PS2_RAM_SIZE - sizeof(uint32_t) - base)
|
|
{
|
|
out = 0u;
|
|
return false;
|
|
}
|
|
|
|
return readGuestU32At(base + offset, out);
|
|
};
|
|
|
|
uint32_t a0Word0 = 0u;
|
|
uint32_t a0Word4 = 0u;
|
|
uint32_t a0Word8 = 0u;
|
|
uint32_t a0WordC = 0u;
|
|
const bool a0Readable =
|
|
readGuestU32Offset(a0, 0x00u, a0Word0) &&
|
|
readGuestU32Offset(a0, 0x04u, a0Word4) &&
|
|
readGuestU32Offset(a0, 0x08u, a0Word8) &&
|
|
readGuestU32Offset(a0, 0x0cu, a0WordC);
|
|
|
|
uint32_t s0Word0 = 0u;
|
|
uint32_t s0Word4 = 0u;
|
|
uint32_t s0Word8 = 0u;
|
|
uint32_t s0WordC = 0u;
|
|
const bool s0Readable =
|
|
readGuestU32Offset(s0, 0x00u, s0Word0) &&
|
|
readGuestU32Offset(s0, 0x04u, s0Word4) &&
|
|
readGuestU32Offset(s0, 0x08u, s0Word8) &&
|
|
readGuestU32Offset(s0, 0x0cu, s0WordC);
|
|
|
|
uint32_t recordWord0 = 0u;
|
|
uint32_t recordWord4 = 0u;
|
|
uint32_t recordWord8 = 0u;
|
|
uint32_t recordWordC = 0u;
|
|
const bool recordReadable =
|
|
s0Readable && s0Word4 != 0u &&
|
|
readGuestU32Offset(s0Word4, 0x00u, recordWord0) &&
|
|
readGuestU32Offset(s0Word4, 0x04u, recordWord4) &&
|
|
readGuestU32Offset(s0Word4, 0x08u, recordWord8) &&
|
|
readGuestU32Offset(s0Word4, 0x0cu, recordWordC);
|
|
|
|
uint32_t vtableSlot0 = 0u;
|
|
uint32_t vtableSlot4 = 0u;
|
|
uint32_t vtableSlot8 = 0u;
|
|
uint32_t vtableSlotC = 0u;
|
|
const bool vtableReadable =
|
|
a0Readable && a0Word0 != 0u &&
|
|
readGuestU32Offset(a0Word0, 0x00u, vtableSlot0) &&
|
|
readGuestU32Offset(a0Word0, 0x04u, vtableSlot4) &&
|
|
readGuestU32Offset(a0Word0, 0x08u, vtableSlot8) &&
|
|
readGuestU32Offset(a0Word0, 0x0cu, vtableSlotC);
|
|
|
|
if (firstReport)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "[guest-branch:missing-target] kind=" << describeGuestBranchKind(kind)
|
|
<< " op=" << (debugName ? debugName : "<unknown>")
|
|
<< " source=0x" << std::hex << sourcePc
|
|
<< " target=0x" << targetPc
|
|
<< " pc=0x" << pc
|
|
<< " ra=0x" << ra
|
|
<< " sp=0x" << sp
|
|
<< " gp=0x" << gp
|
|
<< " a0=0x" << a0
|
|
<< " a1=0x" << a1
|
|
<< " a2=0x" << a2
|
|
<< " a3=0x" << a3
|
|
<< " s0=0x" << s0
|
|
<< " s1=0x" << s1
|
|
<< " v0=0x" << v0
|
|
<< " v1=0x" << v1
|
|
<< " a0Readable=" << (a0Readable ? "yes" : "no")
|
|
<< " a0[0]=0x" << a0Word0
|
|
<< " a0[4]=0x" << a0Word4
|
|
<< " a0[8]=0x" << a0Word8
|
|
<< " a0[c]=0x" << a0WordC
|
|
<< " s0Readable=" << (s0Readable ? "yes" : "no")
|
|
<< " s0[0]=0x" << s0Word0
|
|
<< " s0[4]=0x" << s0Word4
|
|
<< " s0[8]=0x" << s0Word8
|
|
<< " s0[c]=0x" << s0WordC
|
|
<< " recordReadable=" << (recordReadable ? "yes" : "no")
|
|
<< " record[0]=0x" << recordWord0
|
|
<< " record[4]=0x" << recordWord4
|
|
<< " record[8]=0x" << recordWord8
|
|
<< " record[c]=0x" << recordWordC
|
|
<< " vtableReadable=" << (vtableReadable ? "yes" : "no")
|
|
<< " vtbl[0]=0x" << vtableSlot0
|
|
<< " vtbl[4]=0x" << vtableSlot4
|
|
<< " vtbl[8]=0x" << vtableSlot8
|
|
<< " vtbl[c]=0x" << vtableSlotC
|
|
<< " codeRegion=" << (m_memory.isCodeAddress(targetPc) ? "yes" : "no")
|
|
<< " policy=" << static_cast<uint32_t>(policy)
|
|
<< " trace=" << formatDispatchHistory()
|
|
<< std::dec;
|
|
|
|
static std::mutex s_missingFunctionLogMutex;
|
|
{
|
|
std::lock_guard<std::mutex> lock(s_missingFunctionLogMutex);
|
|
std::cerr << oss.str() << std::endl;
|
|
}
|
|
}
|
|
|
|
if (firstReport && policy == MissingFunctionPolicy::BreakOnce)
|
|
{
|
|
#if defined(_MSC_VER)
|
|
__debugbreak();
|
|
#endif // TODO others breakpoints
|
|
}
|
|
|
|
if (ctx)
|
|
{
|
|
ctx->pc = targetPc;
|
|
}
|
|
|
|
if (policy == MissingFunctionPolicy::Stop)
|
|
{
|
|
requestStop();
|
|
}
|
|
}
|
|
|
|
bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
|
|
R5900Context *ctx,
|
|
uint32_t targetPc,
|
|
uint32_t sourcePc,
|
|
uint32_t fallthroughPc,
|
|
GuestBranchKind kind,
|
|
const char *debugName)
|
|
{
|
|
ctx->pc = targetPc;
|
|
const bool isCall = (kind == GuestBranchKind::DirectCall || kind == GuestBranchKind::IndirectCall);
|
|
|
|
if (kind == GuestBranchKind::Return)
|
|
{
|
|
if (!hasFunction(targetPc))
|
|
{
|
|
reportMissingFunction(rdram, ctx, targetPc, sourcePc, kind, debugName);
|
|
}
|
|
|
|
// Prevent nested dispatch.
|
|
ctx->pc = targetPc;
|
|
return false;
|
|
}
|
|
|
|
if (!hasFunction(targetPc))
|
|
{
|
|
reportMissingFunction(rdram, ctx, targetPc, sourcePc, kind, debugName);
|
|
|
|
const MissingFunctionPolicy policy = missingFunctionPolicy();
|
|
|
|
if (policy == MissingFunctionPolicy::SkipCallDebug && isCall)
|
|
{
|
|
ctx->pc = fallthroughPc;
|
|
return true;
|
|
}
|
|
|
|
if (policy == MissingFunctionPolicy::ContinueToTarget)
|
|
{
|
|
ctx->pc = targetPc;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
RecompiledFunction targetFn = lookupFunction(targetPc);
|
|
const uint32_t entryPc = ctx->pc;
|
|
targetFn(rdram, ctx, this);
|
|
|
|
if (isStopRequested() || ctx->pc == 0u)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!isCall)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ctx->pc == entryPc)
|
|
{
|
|
ctx->pc = fallthroughPc;
|
|
}
|
|
|
|
return ctx->pc == fallthroughPc;
|
|
}
|
|
|
|
void PS2Runtime::SignalException(R5900Context *ctx, PS2Exception exception)
|
|
{
|
|
if (exception == EXCEPTION_INTEGER_OVERFLOW)
|
|
{
|
|
HandleIntegerOverflow(ctx);
|
|
return;
|
|
}
|
|
|
|
raiseCop0Exception(ctx, static_cast<uint32_t>(exception),
|
|
exception == EXCEPTION_TLB_REFILL);
|
|
}
|
|
|
|
void PS2Runtime::executeVU0Microprogram(uint8_t *rdram, R5900Context *ctx, uint32_t address)
|
|
{
|
|
(void)rdram;
|
|
|
|
uint8_t *const vu0Code = m_memory.getVU0Code();
|
|
uint8_t *const vu0Data = m_memory.getVU0Data();
|
|
const uint32_t startPC = address & ~0x7u;
|
|
|
|
if (!vu0Code || !vu0Data || startPC + 8u > PS2_VU0_CODE_SIZE)
|
|
{
|
|
seedVu0IdleSuccess(ctx);
|
|
return;
|
|
}
|
|
|
|
m_vu0.reset();
|
|
copyVu0ContextToState(ctx, m_vu0.state());
|
|
m_vu0.execute(vu0Code, PS2_VU0_CODE_SIZE,
|
|
vu0Data, PS2_VU0_DATA_SIZE,
|
|
m_gs, &m_memory,
|
|
startPC, 0u, ctx->vu0_itop, 4096);
|
|
copyVu0StateToContext(m_vu0.state(), ctx);
|
|
}
|
|
|
|
void PS2Runtime::vu0StartMicroProgram(uint8_t *rdram, R5900Context *ctx, uint32_t address)
|
|
{
|
|
// VCALLMS and VCALLMSR both route here.
|
|
executeVU0Microprogram(rdram, ctx, address);
|
|
}
|
|
|
|
void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
handleSyscall(rdram, ctx, 0);
|
|
}
|
|
|
|
void PS2Runtime::handleSyscall(uint8_t *rdram, R5900Context *ctx, uint32_t encodedSyscallId)
|
|
{
|
|
if (ctx->in_delay_slot)
|
|
{
|
|
throw std::runtime_error("Attempted to execute a syscall inside a branch delay slot! "
|
|
"This breaks the atomic basic block model and is structurally unsupported by the emulator.");
|
|
}
|
|
|
|
const uint32_t syscallId = (encodedSyscallId != 0u)
|
|
? encodedSyscallId
|
|
: getRegU32(ctx, 3); // $v1 / $3 is the EE kernel syscall number
|
|
|
|
if (ps2_syscalls::dispatchNumericSyscall(syscallId, rdram, ctx, this))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// God help you
|
|
ps2_syscalls::TODO(rdram, ctx, this, encodedSyscallId);
|
|
}
|
|
|
|
void PS2Runtime::handleBreak(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_BREAKPOINT);
|
|
}
|
|
|
|
void PS2Runtime::drainCompletedDmacHandlers(uint8_t *rdram)
|
|
{
|
|
for (uint32_t cause : m_memory.consumeCompletedDmacCauses())
|
|
{
|
|
ps2_syscalls::dispatchDmacHandlersForCause(rdram, this, cause);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::handleTrap(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_TRAP);
|
|
}
|
|
|
|
void PS2Runtime::handleTLBR(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
uint32_t vpn = 0;
|
|
uint32_t pfn = 0;
|
|
uint32_t mask = 0;
|
|
bool valid = false;
|
|
|
|
const uint32_t index = ctx->cop0_index & 0x3Fu;
|
|
if (!m_memory.tlbRead(index, vpn, pfn, mask, valid))
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION);
|
|
return;
|
|
}
|
|
|
|
// Preserve low ASID bits in EntryHi.
|
|
ctx->cop0_entryhi = (ctx->cop0_entryhi & 0x00000FFFu) | (vpn & 0xFFFFF000u);
|
|
ctx->cop0_entrylo0 = (ctx->cop0_entrylo0 & ~0x03FFFFC2u) |
|
|
((pfn & 0x000FFFFFu) << 6) |
|
|
(valid ? 0x2u : 0u);
|
|
ctx->cop0_pagemask = mask & 0x01FFE000u;
|
|
}
|
|
|
|
void PS2Runtime::handleTLBWI(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
const uint32_t index = ctx->cop0_index & 0x3Fu;
|
|
const uint32_t vpn = ctx->cop0_entryhi & 0xFFFFF000u;
|
|
const uint32_t pfn = (ctx->cop0_entrylo0 >> 6) & 0x000FFFFFu;
|
|
const uint32_t mask = ctx->cop0_pagemask & 0x01FFE000u;
|
|
const bool valid = (ctx->cop0_entrylo0 & 0x2u) != 0u;
|
|
|
|
if (!m_memory.tlbWrite(index, vpn, pfn, mask, valid))
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::handleTLBWR(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
const uint32_t entryCount = static_cast<uint32_t>(m_memory.tlbEntryCount());
|
|
if (entryCount == 0)
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION);
|
|
return;
|
|
}
|
|
|
|
const uint32_t wired = std::min(ctx->cop0_wired, entryCount - 1);
|
|
uint32_t random = ctx->cop0_random % entryCount;
|
|
if (random < wired)
|
|
{
|
|
random = wired;
|
|
}
|
|
|
|
const uint32_t vpn = ctx->cop0_entryhi & 0xFFFFF000u;
|
|
const uint32_t pfn = (ctx->cop0_entrylo0 >> 6) & 0x000FFFFFu;
|
|
const uint32_t mask = ctx->cop0_pagemask & 0x01FFE000u;
|
|
const bool valid = (ctx->cop0_entrylo0 & 0x2u) != 0u;
|
|
|
|
if (!m_memory.tlbWrite(random, vpn, pfn, mask, valid))
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_RESERVED_INSTRUCTION);
|
|
return;
|
|
}
|
|
|
|
// Keep COP0 bookkeeping in sync with the selected slot.
|
|
ctx->cop0_index = (ctx->cop0_index & ~0x3Fu) | (random & 0x3Fu);
|
|
ctx->cop0_random = (random <= wired) ? (entryCount - 1) : (random - 1);
|
|
}
|
|
|
|
void PS2Runtime::handleTLBP(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
const int32_t index = m_memory.tlbProbe(ctx->cop0_entryhi & 0xFFFFF000u);
|
|
if (index >= 0)
|
|
{
|
|
ctx->cop0_index = (ctx->cop0_index & ~0x8000003Fu) |
|
|
(static_cast<uint32_t>(index) & 0x3Fu);
|
|
}
|
|
else
|
|
{
|
|
// MIPS sets probe failure bit (P) in Index[31].
|
|
ctx->cop0_index |= 0x80000000u;
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::clearLLBit(R5900Context *ctx)
|
|
{
|
|
// LL/SC reservation is tracked separately from COP0 Status.
|
|
ctx->llbit = 0;
|
|
ctx->lladdr = 0;
|
|
}
|
|
|
|
uint32_t PS2Runtime::alignGuestHeapValue(uint32_t value, uint32_t alignment)
|
|
{
|
|
if (alignment == 0)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
const uint32_t mask = alignment - 1u;
|
|
if (value > (std::numeric_limits<uint32_t>::max() - mask))
|
|
{
|
|
return std::numeric_limits<uint32_t>::max();
|
|
}
|
|
return (value + mask) & ~mask;
|
|
}
|
|
|
|
bool PS2Runtime::isGuestHeapAlignmentValid(uint32_t alignment)
|
|
{
|
|
return alignment != 0u && (alignment & (alignment - 1u)) == 0u;
|
|
}
|
|
|
|
uint32_t PS2Runtime::normalizeGuestHeapAlignment(uint32_t alignment)
|
|
{
|
|
if (!isGuestHeapAlignmentValid(alignment))
|
|
{
|
|
return kGuestHeapDefaultAlignment;
|
|
}
|
|
return std::max(alignment, kGuestHeapDefaultAlignment);
|
|
}
|
|
|
|
uint32_t PS2Runtime::clampGuestHeapBase(uint32_t guestBase) const
|
|
{
|
|
uint32_t normalized = guestBase;
|
|
if (normalized >= PS2_RAM_SIZE)
|
|
{
|
|
normalized &= PS2_RAM_MASK;
|
|
}
|
|
const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
|
return std::min(normalized, hardLimit);
|
|
}
|
|
|
|
uint32_t PS2Runtime::clampGuestHeapLimit(uint32_t guestLimit) const
|
|
{
|
|
const uint32_t hardLimit = std::min(kGuestHeapHardLimit, PS2_RAM_SIZE);
|
|
if (guestLimit == 0u || guestLimit > hardLimit)
|
|
{
|
|
return hardLimit;
|
|
}
|
|
return guestLimit;
|
|
}
|
|
|
|
void PS2Runtime::resetGuestHeapLocked(uint32_t guestBase, uint32_t guestLimit)
|
|
{
|
|
uint32_t base = alignGuestHeapValue(clampGuestHeapBase(guestBase), kGuestHeapDefaultAlignment);
|
|
uint32_t limit = clampGuestHeapLimit(guestLimit);
|
|
if (base == 0u)
|
|
{
|
|
const uint32_t fallbackBase = (m_guestHeapSuggestedBase != 0u) ? m_guestHeapSuggestedBase : kGuestHeapDefaultBase;
|
|
base = alignGuestHeapValue(clampGuestHeapBase(fallbackBase), kGuestHeapDefaultAlignment);
|
|
}
|
|
|
|
if (limit <= base)
|
|
{
|
|
base = alignGuestHeapValue(clampGuestHeapBase(m_guestHeapSuggestedBase), kGuestHeapDefaultAlignment);
|
|
limit = clampGuestHeapLimit(0u);
|
|
}
|
|
|
|
if (limit <= base)
|
|
{
|
|
base = 0u;
|
|
limit = 0u;
|
|
}
|
|
|
|
m_guestHeapBlocks.clear();
|
|
if (limit > base)
|
|
{
|
|
m_guestHeapBlocks.push_back({base, limit - base, true});
|
|
}
|
|
|
|
m_guestHeapBase = base;
|
|
m_guestHeapEnd = base;
|
|
m_guestHeapLimit = limit;
|
|
m_guestHeapConfigured = true;
|
|
}
|
|
|
|
void PS2Runtime::ensureGuestHeapInitializedLocked()
|
|
{
|
|
if (m_guestHeapConfigured)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const uint32_t suggested = (m_guestHeapSuggestedBase == 0u) ? kGuestHeapDefaultBase : m_guestHeapSuggestedBase;
|
|
resetGuestHeapLocked(suggested, clampGuestHeapLimit(0u));
|
|
}
|
|
|
|
int32_t PS2Runtime::findGuestHeapBlockIndexLocked(uint32_t guestAddr) const
|
|
{
|
|
const uint32_t normalizedAddr = guestAddr & PS2_RAM_MASK;
|
|
for (size_t i = 0; i < m_guestHeapBlocks.size(); ++i)
|
|
{
|
|
const GuestHeapBlock &block = m_guestHeapBlocks[i];
|
|
if (!block.free && block.addr == normalizedAddr)
|
|
{
|
|
return static_cast<int32_t>(i);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint32_t PS2Runtime::allocateGuestBlockLocked(uint32_t size, uint32_t alignment)
|
|
{
|
|
if (size == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const uint32_t normalizedAlignment = normalizeGuestHeapAlignment(alignment);
|
|
if (size > (std::numeric_limits<uint32_t>::max() - (kGuestHeapDefaultAlignment - 1u)))
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const uint32_t allocSize = alignGuestHeapValue(size, kGuestHeapDefaultAlignment);
|
|
if (allocSize == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
for (size_t i = 0; i < m_guestHeapBlocks.size(); ++i)
|
|
{
|
|
const GuestHeapBlock block = m_guestHeapBlocks[i];
|
|
if (!block.free)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint64_t blockStart = block.addr;
|
|
const uint64_t blockEnd = blockStart + static_cast<uint64_t>(block.size);
|
|
const uint32_t alignedAddr = alignGuestHeapValue(block.addr, normalizedAlignment);
|
|
if (alignedAddr < block.addr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint64_t alignedStart = alignedAddr;
|
|
if (alignedStart > blockEnd)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint64_t allocEnd = alignedStart + static_cast<uint64_t>(allocSize);
|
|
if (allocEnd > blockEnd)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint32_t prefixSize = static_cast<uint32_t>(alignedStart - blockStart);
|
|
const uint32_t suffixSize = static_cast<uint32_t>(blockEnd - allocEnd);
|
|
|
|
std::vector<GuestHeapBlock> replacement;
|
|
replacement.reserve(3);
|
|
if (prefixSize > 0u)
|
|
{
|
|
replacement.push_back({block.addr, prefixSize, true});
|
|
}
|
|
replacement.push_back({alignedAddr, allocSize, false});
|
|
if (suffixSize > 0u)
|
|
{
|
|
replacement.push_back({static_cast<uint32_t>(allocEnd), suffixSize, true});
|
|
}
|
|
|
|
m_guestHeapBlocks.erase(m_guestHeapBlocks.begin() + static_cast<std::ptrdiff_t>(i));
|
|
m_guestHeapBlocks.insert(m_guestHeapBlocks.begin() + static_cast<std::ptrdiff_t>(i),
|
|
replacement.begin(),
|
|
replacement.end());
|
|
|
|
m_guestHeapEnd = std::max(m_guestHeapEnd, static_cast<uint32_t>(allocEnd));
|
|
return alignedAddr;
|
|
}
|
|
|
|
return 0u;
|
|
}
|
|
|
|
void PS2Runtime::coalesceGuestHeapLocked()
|
|
{
|
|
if (m_guestHeapBlocks.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
size_t i = 1;
|
|
while (i < m_guestHeapBlocks.size())
|
|
{
|
|
GuestHeapBlock &prev = m_guestHeapBlocks[i - 1];
|
|
GuestHeapBlock &curr = m_guestHeapBlocks[i];
|
|
const uint64_t prevEnd = static_cast<uint64_t>(prev.addr) + static_cast<uint64_t>(prev.size);
|
|
if (prev.free && curr.free && prevEnd == curr.addr)
|
|
{
|
|
prev.size += curr.size;
|
|
m_guestHeapBlocks.erase(m_guestHeapBlocks.begin() + static_cast<std::ptrdiff_t>(i));
|
|
continue;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::freeGuestBlockLocked(uint32_t guestAddr)
|
|
{
|
|
const int32_t index = findGuestHeapBlockIndexLocked(guestAddr);
|
|
if (index < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_guestHeapBlocks[static_cast<size_t>(index)].free = true;
|
|
coalesceGuestHeapLocked();
|
|
}
|
|
|
|
void PS2Runtime::configureGuestHeap(uint32_t guestBase, uint32_t guestLimit)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
uint32_t normalizedBase = alignGuestHeapValue(clampGuestHeapBase(guestBase), kGuestHeapDefaultAlignment);
|
|
if (normalizedBase == 0u)
|
|
{
|
|
normalizedBase = (m_guestHeapSuggestedBase != 0u) ? m_guestHeapSuggestedBase : kGuestHeapDefaultBase;
|
|
}
|
|
m_guestHeapSuggestedBase = normalizedBase;
|
|
resetGuestHeapLocked(normalizedBase, guestLimit);
|
|
}
|
|
|
|
uint32_t PS2Runtime::guestMalloc(uint32_t size, uint32_t alignment)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
ensureGuestHeapInitializedLocked();
|
|
return allocateGuestBlockLocked(size, alignment);
|
|
}
|
|
|
|
uint32_t PS2Runtime::guestCalloc(uint32_t count, uint32_t size, uint32_t alignment)
|
|
{
|
|
if (count == 0u || size == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
if (count > (std::numeric_limits<uint32_t>::max() / size))
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const uint32_t totalSize = count * size;
|
|
const uint32_t guestAddr = guestMalloc(totalSize, alignment);
|
|
if (guestAddr != 0u)
|
|
{
|
|
uint8_t *rdram = m_memory.getRDRAM();
|
|
if (rdram)
|
|
{
|
|
uint32_t physAddr = guestAddr & PS2_RAM_MASK;
|
|
if (physAddr + totalSize <= PS2_RAM_SIZE)
|
|
std::memset(rdram + physAddr, 0, totalSize);
|
|
}
|
|
}
|
|
|
|
return guestAddr;
|
|
}
|
|
|
|
uint32_t PS2Runtime::guestRealloc(uint32_t guestAddr, uint32_t newSize, uint32_t alignment)
|
|
{
|
|
if (guestAddr == 0u)
|
|
{
|
|
return guestMalloc(newSize, alignment);
|
|
}
|
|
if (newSize == 0u)
|
|
{
|
|
guestFree(guestAddr);
|
|
return 0u;
|
|
}
|
|
|
|
if (newSize > (std::numeric_limits<uint32_t>::max() - (kGuestHeapDefaultAlignment - 1u)))
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const uint32_t normalizedAlignment = normalizeGuestHeapAlignment(alignment);
|
|
const uint32_t requestedSize = alignGuestHeapValue(newSize, kGuestHeapDefaultAlignment);
|
|
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
ensureGuestHeapInitializedLocked();
|
|
|
|
const int32_t index = findGuestHeapBlockIndexLocked(guestAddr);
|
|
if (index < 0)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const size_t blockIndex = static_cast<size_t>(index);
|
|
const uint32_t oldAddr = m_guestHeapBlocks[blockIndex].addr;
|
|
const uint32_t oldSize = m_guestHeapBlocks[blockIndex].size;
|
|
|
|
if (requestedSize <= oldSize)
|
|
{
|
|
if (requestedSize < oldSize)
|
|
{
|
|
const uint32_t tailAddr = oldAddr + requestedSize;
|
|
const uint32_t tailSize = oldSize - requestedSize;
|
|
m_guestHeapBlocks[blockIndex].size = requestedSize;
|
|
m_guestHeapBlocks.insert(m_guestHeapBlocks.begin() + static_cast<std::ptrdiff_t>(blockIndex + 1u),
|
|
GuestHeapBlock{tailAddr, tailSize, true});
|
|
coalesceGuestHeapLocked();
|
|
}
|
|
return oldAddr;
|
|
}
|
|
|
|
if (blockIndex + 1u < m_guestHeapBlocks.size())
|
|
{
|
|
GuestHeapBlock &next = m_guestHeapBlocks[blockIndex + 1u];
|
|
const uint64_t blockEnd = static_cast<uint64_t>(m_guestHeapBlocks[blockIndex].addr) +
|
|
static_cast<uint64_t>(m_guestHeapBlocks[blockIndex].size);
|
|
if (next.free && blockEnd == next.addr)
|
|
{
|
|
const uint64_t combined = static_cast<uint64_t>(m_guestHeapBlocks[blockIndex].size) +
|
|
static_cast<uint64_t>(next.size);
|
|
if (combined >= requestedSize)
|
|
{
|
|
const uint32_t extraNeeded = requestedSize - m_guestHeapBlocks[blockIndex].size;
|
|
m_guestHeapBlocks[blockIndex].size = requestedSize;
|
|
if (next.size == extraNeeded)
|
|
{
|
|
m_guestHeapBlocks.erase(m_guestHeapBlocks.begin() + static_cast<std::ptrdiff_t>(blockIndex + 1u));
|
|
}
|
|
else
|
|
{
|
|
next.addr += extraNeeded;
|
|
next.size -= extraNeeded;
|
|
}
|
|
m_guestHeapEnd = std::max(m_guestHeapEnd, oldAddr + requestedSize);
|
|
return oldAddr;
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32_t newAddr = allocateGuestBlockLocked(newSize, normalizedAlignment);
|
|
if (newAddr == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
uint8_t *rdram = m_memory.getRDRAM();
|
|
if (rdram)
|
|
{
|
|
const uint32_t copyBytes = std::min(oldSize, newSize);
|
|
uint32_t dstPhys = newAddr & PS2_RAM_MASK;
|
|
uint32_t srcPhys = oldAddr & PS2_RAM_MASK;
|
|
if (dstPhys + copyBytes <= PS2_RAM_SIZE && srcPhys + copyBytes <= PS2_RAM_SIZE)
|
|
std::memmove(rdram + dstPhys, rdram + srcPhys, copyBytes);
|
|
}
|
|
|
|
freeGuestBlockLocked(oldAddr);
|
|
return newAddr;
|
|
}
|
|
|
|
void PS2Runtime::guestFree(uint32_t guestAddr)
|
|
{
|
|
if (guestAddr == 0u)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
ensureGuestHeapInitializedLocked();
|
|
freeGuestBlockLocked(guestAddr);
|
|
}
|
|
|
|
uint32_t PS2Runtime::guestHeapBase() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
return m_guestHeapConfigured ? m_guestHeapBase : m_guestHeapSuggestedBase;
|
|
}
|
|
|
|
uint32_t PS2Runtime::guestHeapEnd() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
return m_guestHeapConfigured ? m_guestHeapEnd : m_guestHeapSuggestedBase;
|
|
}
|
|
|
|
uint32_t PS2Runtime::guestHeapLimit() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestHeapMutex);
|
|
return m_guestHeapConfigured ? m_guestHeapLimit : m_guestHeapSuggestedBase;
|
|
}
|
|
|
|
uint32_t PS2Runtime::reserveAsyncCallbackStack(uint32_t size, uint32_t alignment)
|
|
{
|
|
if (size == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const uint32_t normalizedAlignment = normalizeGuestHeapAlignment(alignment);
|
|
const uint32_t allocSize = alignGuestHeapValue(size, kGuestHeapDefaultAlignment);
|
|
if (allocSize == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(m_asyncCallbackStackMutex);
|
|
uint32_t top = m_asyncCallbackStackTop;
|
|
if (top > PS2_RAM_SIZE)
|
|
{
|
|
top = PS2_RAM_SIZE;
|
|
}
|
|
top &= ~(kGuestHeapDefaultAlignment - 1u);
|
|
|
|
if (top <= allocSize)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
uint32_t base = top - allocSize;
|
|
base &= ~(normalizedAlignment - 1u);
|
|
if (base < m_asyncCallbackStackFloor || base >= top)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
m_asyncCallbackStackTop = base;
|
|
return top - 0x10u;
|
|
}
|
|
|
|
void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx)
|
|
{
|
|
uint32_t lastPc = std::numeric_limits<uint32_t>::max();
|
|
uint32_t samePcCount = 0;
|
|
constexpr uint32_t kSamePcYieldInterval = 0x4000u;
|
|
|
|
while (!isStopRequested())
|
|
{
|
|
const uint32_t pc = ctx->pc;
|
|
|
|
if (pc == lastPc)
|
|
{
|
|
++samePcCount;
|
|
if ((samePcCount % kSamePcYieldInterval) == 0u)
|
|
{
|
|
PS2_IF_AGRESSIVE_LOGS({
|
|
RUNTIME_LOG("CPU is doing some work at PC 0x" << std::hex << pc << ". PC not updating.");
|
|
});
|
|
std::this_thread::yield();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
samePcCount = 0;
|
|
lastPc = pc;
|
|
}
|
|
|
|
m_debugPc.store(pc, std::memory_order_relaxed);
|
|
m_debugRa.store(static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)), std::memory_order_relaxed);
|
|
m_debugSp.store(static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)), std::memory_order_relaxed);
|
|
m_debugGp.store(static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0)), std::memory_order_relaxed);
|
|
|
|
RecompiledFunction fn = lookupFunction(pc);
|
|
const uint32_t dispatchedPc = pc;
|
|
const uint32_t dispatchedRa = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
|
|
|
|
uint64_t handoffBaseline = 0u;
|
|
{
|
|
GuestExecutionScope guestExecution(this);
|
|
fn(rdram, ctx, this);
|
|
handoffBaseline = guestExecutionHandoffEpochSnapshot();
|
|
}
|
|
|
|
waitForGuestExecutionHandoff(handoffBaseline);
|
|
|
|
if (ctx->pc == 0u)
|
|
{
|
|
const uint32_t ra = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
|
|
const uint32_t sp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0));
|
|
const uint32_t gp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0));
|
|
PS2_IF_AGRESSIVE_LOGS({
|
|
std::cerr << "[dispatch:pc-zero] from=0x" << std::hex << dispatchedPc
|
|
<< " fromRa=0x" << dispatchedRa
|
|
<< " ra=0x" << ra
|
|
<< " sp=0x" << sp
|
|
<< " gp=0x" << gp
|
|
<< " trace=" << formatDispatchHistory()
|
|
<< std::dec << std::endl;
|
|
});
|
|
|
|
// PC=0 means this guest thread returned (usually via jr $ra with RA=0).
|
|
// Do not request a global runtime stop here: other guest threads may still run.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::enterGuestExecution()
|
|
{
|
|
uint32_t &depth = g_guestExecutionDepths[this];
|
|
|
|
if (depth != 0u)
|
|
{
|
|
m_guestExecutionMutex.lock();
|
|
++depth;
|
|
return;
|
|
}
|
|
|
|
m_guestExecutionWaiters.fetch_add(1u, std::memory_order_acq_rel);
|
|
m_guestExecutionMutex.lock();
|
|
m_guestExecutionWaiters.fetch_sub(1u, std::memory_order_acq_rel);
|
|
depth = 1u;
|
|
markGuestExecutionAcquired();
|
|
}
|
|
|
|
void PS2Runtime::leaveGuestExecution()
|
|
{
|
|
auto it = g_guestExecutionDepths.find(this);
|
|
if (it == g_guestExecutionDepths.end() || it->second == 0u)
|
|
{
|
|
return;
|
|
}
|
|
|
|
--it->second;
|
|
m_guestExecutionMutex.unlock();
|
|
if (it->second == 0u)
|
|
{
|
|
g_guestExecutionDepths.erase(it);
|
|
}
|
|
}
|
|
|
|
uint32_t PS2Runtime::releaseGuestExecution()
|
|
{
|
|
auto it = g_guestExecutionDepths.find(this);
|
|
if (it == g_guestExecutionDepths.end() || it->second == 0u)
|
|
{
|
|
return 0u;
|
|
}
|
|
|
|
const uint32_t depth = it->second;
|
|
for (uint32_t i = 0; i < depth; ++i)
|
|
{
|
|
m_guestExecutionMutex.unlock();
|
|
}
|
|
g_guestExecutionDepths.erase(it);
|
|
return depth;
|
|
}
|
|
|
|
void PS2Runtime::reacquireGuestExecution(uint32_t depth)
|
|
{
|
|
if (depth == 0u)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32_t &heldDepth = g_guestExecutionDepths[this];
|
|
uint32_t remaining = depth;
|
|
|
|
if (heldDepth == 0u)
|
|
{
|
|
m_guestExecutionWaiters.fetch_add(1u, std::memory_order_acq_rel);
|
|
m_guestExecutionMutex.lock();
|
|
m_guestExecutionWaiters.fetch_sub(1u, std::memory_order_acq_rel);
|
|
heldDepth = 1u;
|
|
markGuestExecutionAcquired();
|
|
--remaining;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < remaining; ++i)
|
|
{
|
|
m_guestExecutionMutex.lock();
|
|
++heldDepth;
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::markGuestExecutionAcquired()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_guestExecutionHandoffMutex);
|
|
m_guestExecutionHandoffEpoch.fetch_add(1u, std::memory_order_acq_rel);
|
|
}
|
|
m_guestExecutionHandoffCv.notify_all();
|
|
}
|
|
|
|
void PS2Runtime::waitForGuestExecutionHandoff()
|
|
{
|
|
waitForGuestExecutionHandoff(guestExecutionHandoffEpochSnapshot());
|
|
}
|
|
|
|
void PS2Runtime::waitForGuestExecutionHandoff(uint64_t baselineEpoch)
|
|
{
|
|
// Lock-free fast path
|
|
if (m_guestExecutionWaiters.load(std::memory_order_acquire) == 0u)
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> lock(m_guestExecutionHandoffMutex);
|
|
|
|
if (m_guestExecutionWaiters.load(std::memory_order_acquire) == 0u)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool handedOff = m_guestExecutionHandoffCv.wait_for(
|
|
lock,
|
|
std::chrono::milliseconds(2),
|
|
[&]()
|
|
{
|
|
return m_guestExecutionWaiters.load(std::memory_order_acquire) == 0u ||
|
|
m_guestExecutionHandoffEpoch.load(std::memory_order_relaxed) != baselineEpoch ||
|
|
isStopRequested();
|
|
});
|
|
|
|
if (!handedOff)
|
|
{
|
|
m_guestExecutionHandoffTimeouts.fetch_add(1u, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
|
|
PS2Runtime::DeferredGuestYieldScope::DeferredGuestYieldScope(bool &pendingOut) noexcept
|
|
: m_pendingOut(pendingOut)
|
|
{
|
|
++g_deferredGuestYieldDepth;
|
|
}
|
|
|
|
PS2Runtime::DeferredGuestYieldScope::~DeferredGuestYieldScope()
|
|
{
|
|
if (--g_deferredGuestYieldDepth == 0u && g_deferredGuestYieldPending)
|
|
{
|
|
g_deferredGuestYieldPending = false;
|
|
m_pendingOut = true;
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::yieldGuestExecutionAfterWake()
|
|
{
|
|
if (g_deferredGuestYieldDepth != 0u)
|
|
{
|
|
g_deferredGuestYieldPending = true;
|
|
return;
|
|
}
|
|
|
|
auto it = g_guestExecutionDepths.find(this);
|
|
if (it == g_guestExecutionDepths.end() || it->second == 0u)
|
|
{
|
|
std::this_thread::yield();
|
|
return;
|
|
}
|
|
|
|
const uint64_t handoffEpoch = m_guestExecutionHandoffEpoch.load(std::memory_order_acquire);
|
|
{
|
|
GuestExecutionReleaseScope releaseGuestExecution(this);
|
|
std::unique_lock<std::mutex> lock(m_guestExecutionHandoffMutex);
|
|
m_guestExecutionHandoffCv.wait_for(lock, std::chrono::milliseconds(1), [&]()
|
|
{ return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire) != handoffEpoch; });
|
|
}
|
|
}
|
|
|
|
bool PS2Runtime::shouldPreemptGuestExecution()
|
|
{
|
|
constexpr uint32_t kContendedYieldInterval = 1024u;
|
|
constexpr uint32_t kUncontendedYieldInterval = 16384u;
|
|
|
|
thread_local uint32_t s_backEdgeYieldCounter = 0u;
|
|
const uint32_t waiterCount = m_guestExecutionWaiters.load(std::memory_order_acquire);
|
|
const uint32_t yieldInterval = (waiterCount != 0u) ? kContendedYieldInterval : kUncontendedYieldInterval;
|
|
if (++s_backEdgeYieldCounter < yieldInterval)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
s_backEdgeYieldCounter = 0u;
|
|
return true;
|
|
}
|
|
|
|
uint8_t PS2Runtime::Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
|
|
{
|
|
try
|
|
{
|
|
return m_memory.read8(vaddr);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint16_t PS2Runtime::Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
|
|
{
|
|
try
|
|
{
|
|
return m_memory.read16(vaddr);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32_t PS2Runtime::Load32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
|
|
{
|
|
try
|
|
{
|
|
return m_memory.read32(vaddr);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint64_t PS2Runtime::Load64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
|
|
{
|
|
try
|
|
{
|
|
return m_memory.read64(vaddr);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
__m128i PS2Runtime::Load128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
|
|
{
|
|
try
|
|
{
|
|
return m_memory.read128(vaddr);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_LOAD);
|
|
return _mm_setzero_si128();
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::Store8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint8_t value)
|
|
{
|
|
ps2TraceGuestWrite(rdram, vaddr, 1u, value, 0u, "WRITE8", ctx);
|
|
try
|
|
{
|
|
m_memory.write8(vaddr, value);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::Store16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint16_t value)
|
|
{
|
|
ps2TraceGuestWrite(rdram, vaddr, 2u, value, 0u, "WRITE16", ctx);
|
|
try
|
|
{
|
|
m_memory.write16(vaddr, value);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::Store32(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint32_t value)
|
|
{
|
|
ps2TraceGuestWrite(rdram, vaddr, 4u, value, 0u, "WRITE32", ctx);
|
|
try
|
|
{
|
|
m_memory.write32(vaddr, value);
|
|
drainCompletedDmacHandlers(rdram);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::Store64(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, uint64_t value)
|
|
{
|
|
ps2TraceGuestWrite(rdram, vaddr, 8u, value, 0u, "WRITE64", ctx);
|
|
try
|
|
{
|
|
m_memory.write64(vaddr, value);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::Store128(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr, __m128i value)
|
|
{
|
|
alignas(16) uint64_t _parts[2];
|
|
_mm_storeu_si128(reinterpret_cast<__m128i *>(_parts), value);
|
|
ps2TraceGuestWrite(rdram, vaddr, 16u, _parts[0], _parts[1], "WRITE128", ctx);
|
|
try
|
|
{
|
|
m_memory.write128(vaddr, value);
|
|
}
|
|
catch (const std::exception &)
|
|
{
|
|
SignalException(ctx, EXCEPTION_ADDRESS_ERROR_STORE);
|
|
}
|
|
}
|
|
|
|
void PS2Runtime::kickGifDmaChainFromMMIO(uint8_t *rdram,
|
|
R5900Context *ctx,
|
|
uint32_t dPcrValue,
|
|
uint32_t dStatValue,
|
|
uint32_t tadr,
|
|
uint32_t chcr)
|
|
{
|
|
constexpr uint32_t D_PCR = 0x1000E020u;
|
|
constexpr uint32_t D_STAT = 0x1000E010u;
|
|
constexpr uint32_t GIF_TADR = 0x1000A030u;
|
|
constexpr uint32_t GIF_CHCR = 0x1000A000u;
|
|
|
|
ps2TraceGuestWrite(rdram, D_PCR, 4u, dPcrValue, 0u, "WRITE32", ctx);
|
|
m_memory.writeIORegister(D_PCR, dPcrValue);
|
|
ps2TraceGuestWrite(rdram, D_STAT, 4u, dStatValue, 0u, "WRITE32", ctx);
|
|
m_memory.writeIORegister(D_STAT, dStatValue);
|
|
ps2TraceGuestWrite(rdram, GIF_TADR, 4u, tadr, 0u, "WRITE32", ctx);
|
|
m_memory.writeIORegister(GIF_TADR, tadr);
|
|
ps2TraceGuestWrite(rdram, GIF_CHCR, 4u, chcr, 0u, "WRITE32", ctx);
|
|
if (m_memory.tryProcessNativeGifImageUploadChain(m_gs, tadr, chcr))
|
|
{
|
|
drainCompletedDmacHandlers(rdram);
|
|
return;
|
|
}
|
|
if (m_memory.tryProcessNativeGifPackedChain(m_gs, tadr, chcr))
|
|
{
|
|
drainCompletedDmacHandlers(rdram);
|
|
return;
|
|
}
|
|
m_memory.writeIORegister(GIF_CHCR, chcr);
|
|
m_memory.processPendingTransfers();
|
|
drainCompletedDmacHandlers(rdram);
|
|
}
|
|
|
|
void PS2Runtime::requestStop()
|
|
{
|
|
m_stopRequested.store(true, std::memory_order_relaxed);
|
|
ps2_syscalls::notifyRuntimeStop();
|
|
}
|
|
|
|
bool PS2Runtime::isStopRequested() const
|
|
{
|
|
return m_stopRequested.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
void PS2Runtime::HandleIntegerOverflow(R5900Context *ctx)
|
|
{
|
|
raiseCop0Exception(ctx, EXCEPTION_INTEGER_OVERFLOW);
|
|
}
|
|
|
|
void PS2Runtime::run()
|
|
{
|
|
m_stopRequested.store(false, std::memory_order_relaxed);
|
|
ps2_stubs::resetSifState();
|
|
resetIop();
|
|
ps2_stubs::resetAudioStubState();
|
|
ps2_stubs::resetGsSyncVCallbackState();
|
|
ps2_stubs::resetMpegStubState();
|
|
ps2_syscalls::initializeGuestKernelState(m_memory.getRDRAM());
|
|
m_cpuContext.r[4] = _mm_setzero_si128();
|
|
m_cpuContext.r[5] = _mm_setzero_si128();
|
|
m_cpuContext.r[29] = _mm_set_epi64x(0, static_cast<int64_t>(PS2_RAM_SIZE - 0x10u));
|
|
m_debugPc.store(m_cpuContext.pc, std::memory_order_relaxed);
|
|
m_debugRa.store(static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[31], 0)), std::memory_order_relaxed);
|
|
m_debugSp.store(static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[29], 0)), std::memory_order_relaxed);
|
|
m_debugGp.store(static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[28], 0)), std::memory_order_relaxed);
|
|
|
|
RUNTIME_LOG("Starting execution at address 0x" << std::hex << m_cpuContext.pc << std::dec);
|
|
|
|
// A blank image to use as a framebuffer
|
|
Image blank = GenImageColor(FB_WIDTH, FB_HEIGHT, BLANK);
|
|
Texture2D frameTex = LoadTextureFromImage(blank);
|
|
UnloadImage(blank);
|
|
|
|
g_activeThreads.store(1, std::memory_order_relaxed);
|
|
std::atomic<bool> gameThreadFinished{false};
|
|
|
|
std::thread gameThread([&]()
|
|
{
|
|
ThreadNaming::SetCurrentThreadName("GameThread");
|
|
try
|
|
{
|
|
dispatchLoop(m_memory.getRDRAM(), &m_cpuContext);
|
|
uint32_t pc = m_debugPc.load(std::memory_order_relaxed);
|
|
RUNTIME_LOG("Game thread returned. PC=0x" << std::hex << pc
|
|
<< " RA=0x" << static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[31], 0)) << std::dec << std::endl);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << "Error during program execution: " << e.what() << std::endl;
|
|
}
|
|
catch (...)
|
|
{
|
|
std::cerr << "Error during program execution: unknown exception" << std::endl;
|
|
}
|
|
g_activeThreads.fetch_sub(1, std::memory_order_relaxed);
|
|
gameThreadFinished.store(true, std::memory_order_release); });
|
|
|
|
ps2_syscalls::EnsureVSyncWorkerRunning(m_memory.getRDRAM(), this);
|
|
|
|
uint64_t tick = 0;
|
|
while (!isStopRequested() && g_activeThreads.load(std::memory_order_relaxed) > 0)
|
|
{
|
|
PS2_IF_AGRESSIVE_LOGS({
|
|
tick++;
|
|
if ((tick % 120) == 0)
|
|
{
|
|
uint64_t curDma = m_memory.dmaStartCount();
|
|
uint64_t curGif = m_memory.gifCopyCount();
|
|
uint64_t curGs = m_memory.gsWriteCount();
|
|
uint64_t curVif = m_memory.vifWriteCount();
|
|
const GSRegisters &gs = m_memory.gs();
|
|
const uint32_t dbgPc = m_debugPc.load(std::memory_order_relaxed);
|
|
const uint32_t dbgRa = m_debugRa.load(std::memory_order_relaxed);
|
|
const uint32_t dbgSp = m_debugSp.load(std::memory_order_relaxed);
|
|
const uint32_t dbgGp = m_debugGp.load(std::memory_order_relaxed);
|
|
const int activeThreads = g_activeThreads.load(std::memory_order_relaxed);
|
|
|
|
RUNTIME_LOG("[run:tick] tick=" << tick
|
|
<< " pc=0x" << std::hex << dbgPc
|
|
<< " ra=0x" << dbgRa
|
|
<< " sp=0x" << dbgSp
|
|
<< " gp=0x" << dbgGp
|
|
<< " dispfb1=0x" << gs.dispfb1
|
|
<< " display1=0x" << gs.display1
|
|
<< std::dec
|
|
<< " activeThreads=" << activeThreads
|
|
<< " dma=" << curDma
|
|
<< " gif=" << curGif
|
|
<< " gsw=" << curGs
|
|
<< " vif=" << curVif
|
|
<< std::endl);
|
|
}
|
|
});
|
|
uint32_t presentWidth = FB_WIDTH;
|
|
uint32_t presentHeight = DEFAULT_DISPLAY_HEIGHT;
|
|
UploadFrame(frameTex, this, presentWidth, presentHeight);
|
|
|
|
BeginDrawing();
|
|
ClearBackground(BLACK);
|
|
const float srcWidth = static_cast<float>(std::max<uint32_t>(1u, presentWidth));
|
|
const float srcHeight = static_cast<float>(std::max<uint32_t>(1u, presentHeight));
|
|
const float screenWidth = static_cast<float>(GetScreenWidth());
|
|
const float screenHeight = static_cast<float>(GetScreenHeight());
|
|
const float scale = std::min(screenWidth / srcWidth, screenHeight / srcHeight);
|
|
const float dstWidth = srcWidth * scale;
|
|
const float dstHeight = srcHeight * scale;
|
|
const Rectangle srcRect{0.0f, 0.0f, srcWidth, srcHeight};
|
|
const Rectangle dstRect{
|
|
(screenWidth - dstWidth) * 0.5f,
|
|
(screenHeight - dstHeight) * 0.5f,
|
|
dstWidth,
|
|
dstHeight};
|
|
DrawTexturePro(frameTex, srcRect, dstRect, Vector2{0.0f, 0.0f}, 0.0f, WHITE);
|
|
if (m_debugUiInitialized && m_debugUiDrawCallback)
|
|
{
|
|
m_debugUiDrawCallback(*this, m_debugUiUserData);
|
|
}
|
|
EndDrawing();
|
|
|
|
if (WindowShouldClose())
|
|
{
|
|
RUNTIME_LOG("[run] window close requested, breaking out of loop");
|
|
requestStop();
|
|
break;
|
|
}
|
|
}
|
|
|
|
requestStop();
|
|
|
|
const auto joinDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
|
while (!gameThreadFinished.load(std::memory_order_acquire) &&
|
|
std::chrono::steady_clock::now() < joinDeadline)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
|
|
if (gameThread.joinable())
|
|
{
|
|
if (gameThreadFinished.load(std::memory_order_acquire))
|
|
{
|
|
gameThread.join();
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "[run] game thread did not stop within timeout; detaching" << std::endl;
|
|
gameThread.detach();
|
|
}
|
|
}
|
|
|
|
const auto workerDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
|
|
while (g_activeThreads.load(std::memory_order_relaxed) > 0 &&
|
|
std::chrono::steady_clock::now() < workerDeadline)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
|
|
if (g_activeThreads.load(std::memory_order_relaxed) > 0)
|
|
{
|
|
requestStop();
|
|
const auto finalWorkerDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
|
|
while (g_activeThreads.load(std::memory_order_relaxed) > 0 &&
|
|
std::chrono::steady_clock::now() < finalWorkerDeadline)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
}
|
|
|
|
if (g_activeThreads.load(std::memory_order_relaxed) == 0)
|
|
{
|
|
ps2_syscalls::joinAllGuestHostThreads();
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "[run] guest host threads did not stop within timeout; detaching remaining worker threads"
|
|
<< std::endl;
|
|
ps2_syscalls::detachAllGuestHostThreads();
|
|
}
|
|
|
|
if (m_debugUiInitialized && m_debugUiShutdownCallback)
|
|
{
|
|
m_debugUiShutdownCallback(*this, m_debugUiUserData);
|
|
m_debugUiInitialized = false;
|
|
}
|
|
UnloadTexture(frameTex);
|
|
CloseWindow();
|
|
|
|
const int remainingThreads = g_activeThreads.load(std::memory_order_relaxed);
|
|
RUNTIME_LOG("[run] exiting loop, activeThreads=" << remainingThreads);
|
|
if (remainingThreads > 0)
|
|
{
|
|
std::cerr << "[run] warning: " << remainingThreads
|
|
<< " guest worker thread(s) still active during shutdown." << std::endl;
|
|
}
|
|
}
|