Add IOP, audio, pad, fio changes, VAG handling, and call-stack logger (#76)

- Add IOP with RPC handling (handleRPC, LibSd SID) and init/reset using RDRAM
- Add IOP audio layer for LibSd RPC (handleLibSdRpc)
- Add PS2AudioBackend with VAG decode, onVagTransfer/onSoundCommand, raylib playback
- Add PSPadBackend with readState and raylib input
- Add IOP RAM mapping in Memory (getIOPRAM) for EE↔IOP access
- Add fio syscalls: fioOpen, fioRead, fioWrite, fioClose, fioLseek, fioMkdir
- Add VAG accumulation for fio reads (multi-chunk VagAccumEntry)
- Add ps2_log.h call-stack logger (tab-indented, Debug only, ps2_log.txt next to exe)
- Code generator: emit ps2_log include and PS_LOG_ENTRY for full functions
- Recompiler: emit ps2_log include and PS_LOG_ENTRY for stubs
- Runtime: print "[PS2 LOG] Logs saved at <path>" on exit (Debug only)
This commit is contained in:
Aslan Hud
2026-02-24 15:45:52 +01:00
committed by GitHub
parent bac1fc9ef8
commit c1e98c9398
20 changed files with 849 additions and 23 deletions
+8 -1
View File
@@ -821,6 +821,9 @@ namespace ps2recomp
ss << "#include \"ps2_recompiled_stubs.h\"\n\n";
ss << "#include \"ps2_syscalls.h\"\n";
ss << "#include \"ps2_stubs.h\"\n\n";
ss << "#ifdef _DEBUG\n";
ss << "#include \"ps2_log.h\"\n";
ss << "#endif\n\n";
}
AnalysisResult analysisResult = collectInternalBranchTargets(function, instructions);
@@ -836,7 +839,11 @@ namespace ps2recomp
sanitizedName = nameBuilder.str();
}
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n\n";
ss << "void " << sanitizedName << "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n";
ss << "#ifdef _DEBUG\n";
ss << " PS_LOG_ENTRY(\"" << sanitizedName << "\");\n";
ss << "#endif\n";
ss << "\n";
ss << " ctx->pc = 0x" << std::hex << function.start << "u;\n"
<< std::dec;
ss << "\n";
+13 -3
View File
@@ -984,8 +984,11 @@ namespace ps2recomp
std::string generatedName = m_codeGenerator->getFunctionName(function.start);
std::stringstream stub;
stub << "void " << generatedName
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n"
<< " const uint32_t __entryPc = ctx->pc;\n"
<< "(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime) {\n";
stub << "#ifdef _DEBUG\n";
stub << " PS_LOG_ENTRY(\"" << generatedName << "\");\n";
stub << "#endif\n";
stub << " const uint32_t __entryPc = ctx->pc;\n"
<< " ";
if (function.isSkipped)
@@ -1044,6 +1047,9 @@ namespace ps2recomp
combinedOutput << "#include \"ps2_recompiled_stubs.h\"\n";
combinedOutput << "#include \"ps2_syscalls.h\"\n";
combinedOutput << "#include \"ps2_stubs.h\"\n";
combinedOutput << "#ifdef _DEBUG\n";
combinedOutput << "#include \"ps2_log.h\"\n";
combinedOutput << "#endif\n";
combinedOutput << "\n";
for (const auto &function : m_functions)
@@ -1100,7 +1106,11 @@ namespace ps2recomp
std::stringstream stubFile;
stubFile << "#include \"ps2_runtime.h\"\n";
stubFile << "#include \"ps2_syscalls.h\"\n";
stubFile << "#include \"ps2_stubs.h\"\n\n";
stubFile << "#include \"ps2_stubs.h\"\n";
stubFile << "#ifdef _DEBUG\n";
stubFile << "#include \"ps2_log.h\"\n";
stubFile << "#endif\n";
stubFile << "\n";
stubFile << m_generatedStubs.at(function.start) << "\n";
code = stubFile.str();
}
+5
View File
@@ -20,9 +20,14 @@ FetchContent_MakeAvailable(raylib)
add_library(ps2_runtime STATIC
src/lib/game_overrides.cpp
src/lib/ps2_audio.cpp
src/lib/ps2_audio_vag.cpp
src/lib/ps2_gs_gpu.cpp
src/lib/ps2_gs_rasterizer.cpp
src/lib/ps2_iop.cpp
src/lib/ps2_iop_audio.cpp
src/lib/ps2_memory.cpp
src/lib/ps2_pad.cpp
src/lib/ps2_runtime.cpp
src/lib/ps2_stubs.cpp
src/lib/ps2_syscalls.cpp
+48
View File
@@ -0,0 +1,48 @@
#ifndef PS2_AUDIO_H
#define PS2_AUDIO_H
#include <cstdint>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
class PS2AudioBackend
{
public:
PS2AudioBackend();
~PS2AudioBackend();
void onVagTransfer(const uint8_t *rdram, uint32_t srcAddr, uint32_t sizeBytes);
void onVagTransferFromBuffer(const uint8_t *data, uint32_t sizeBytes, uint32_t keyAddr);
void onSoundCommand(uint32_t sid, uint32_t rpcNum,
const uint8_t *sendBuf, uint32_t sendSize,
uint8_t *recvBuf, uint32_t recvSize);
void play(uint32_t sampleAddr, float pitch = 1.0f, float volume = 1.0f,
uint32_t voiceIndex = 0xFFFFFFFFu);
void stop(uint32_t voiceId);
void stopAll();
void setAudioReady(bool ready) { m_audioReady = ready; }
private:
struct DecodedSample
{
std::vector<int16_t> pcm;
uint32_t sampleRate = 44100;
};
struct Impl;
std::unique_ptr<Impl> m_impl;
bool m_audioReady = false;
uint32_t m_mostRecentSampleKey = 0;
std::vector<DecodedSample> m_loadOrderSamples;
std::unordered_map<uint32_t, DecodedSample> m_sampleBank;
std::mutex m_mutex;
void playDecodedSample(uint32_t sampleKey, DecodedSample &sample, float pitch, float volume,
bool isBgm = false);
void pruneFinishedSounds();
};
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef PS2_IOP_H
#define PS2_IOP_H
#include <cstdint>
constexpr uint32_t IOP_SID_LIBSD = 0x80000701u;
class IOP
{
public:
IOP();
~IOP() = default;
void init(uint8_t *rdram);
void reset();
bool handleRPC(uint32_t sid, uint32_t rpcNum,
uint32_t sendBufAddr, uint32_t sendSize,
uint32_t recvBufAddr, uint32_t recvSize);
private:
uint8_t *m_rdram = nullptr;
};
#endif
+15
View File
@@ -0,0 +1,15 @@
#ifndef PS2_IOP_AUDIO_H
#define PS2_IOP_AUDIO_H
#include <cstdint>
class PS2Runtime;
namespace ps2_iop_audio
{
void handleLibSdRpc(PS2Runtime *runtime, uint32_t sid, uint32_t rpcNum,
const uint8_t *sendBuf, uint32_t sendSize,
uint8_t *recvBuf, uint32_t recvSize);
}
#endif
+77
View File
@@ -0,0 +1,77 @@
#ifndef PS2_LOG_H
#define PS2_LOG_H
#include <fstream>
#include <iostream>
#include <filesystem>
#if defined(_WIN32)
#include <windows.h>
#endif
#ifdef _DEBUG
namespace ps2_log
{
inline std::string log_path()
{
static std::string path;
if (path.empty())
{
#if defined(_WIN32)
char buf[MAX_PATH];
if (GetModuleFileNameA(nullptr, buf, sizeof(buf)))
path = (std::filesystem::path(buf).parent_path() / "ps2_log.txt").string();
#endif
if (path.empty())
path = (std::filesystem::current_path() / "ps2_log.txt").string();
}
return path;
}
inline std::ostream &log_stream()
{
static std::ofstream f(log_path(), std::ios::out);
return f.is_open() ? f : std::cerr;
}
inline int &depth()
{
static thread_local int d = 0;
return d;
}
inline void log_entry(const char *name)
{
for (int i = 0; i < depth(); ++i)
log_stream() << '\t';
log_stream() << ">> " << name << " enter\n";
log_stream().flush();
depth()++;
}
inline void log_exit(const char *name)
{
depth()--;
for (int i = 0; i < depth(); ++i)
log_stream() << '\t';
log_stream() << "<< " << name << " exit\n";
log_stream().flush();
}
inline void print_saved_location()
{
std::cout << "[PS2 LOG] Logs saved at " << log_path() << std::endl;
}
}
#define PS_LOG_ENTRY(name) \
ps2_log::log_entry(name); \
struct _ps2_log_guard_ { const char *_n; _ps2_log_guard_(const char *n) : _n(n) {} \
~_ps2_log_guard_() { ps2_log::log_exit(_n); } } _ps2_log_guard_(name)
#else
namespace ps2_log
{
inline void print_saved_location() {}
}
#define PS_LOG_ENTRY(name) ((void)0)
#endif
#endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef PS2_PAD_H
#define PS2_PAD_H
#include <cstddef>
#include <cstdint>
class PSPadBackend
{
public:
PSPadBackend() = default;
~PSPadBackend() = default;
bool readState(int port, int slot, uint8_t *data, size_t size);
};
#endif
+13
View File
@@ -22,6 +22,9 @@
#include <iomanip>
#include "ps2_memory.h"
#include "ps2_iop.h"
#include "ps2_audio.h"
#include "ps2_pad.h"
enum PS2Exception
{
@@ -457,6 +460,13 @@ public:
inline PS2Memory &memory() { return m_memory; }
inline const PS2Memory &memory() const { return m_memory; }
inline IOP &iop() { return m_iop; }
inline const IOP &iop() const { return m_iop; }
inline PS2AudioBackend &audioBackend() { return m_audioBackend; }
inline const PS2AudioBackend &audioBackend() const { return m_audioBackend; }
inline PSPadBackend &padBackend() { return m_padBackend; }
inline const PSPadBackend &padBackend() const { return m_padBackend; }
private:
struct GuestHeapBlock
{
@@ -481,6 +491,9 @@ private:
private:
PS2Memory m_memory;
IOP m_iop;
PS2AudioBackend m_audioBackend;
PSPadBackend m_padBackend;
R5900Context m_cpuContext;
mutable std::mutex m_guestHeapMutex;
std::vector<GuestHeapBlock> m_guestHeapBlocks;
+282
View File
@@ -0,0 +1,282 @@
#include "ps2_audio.h"
#include "ps2_memory.h"
#include "raylib.h"
#include <cstring>
#include <vector>
namespace
{
std::vector<uint8_t> buildWavFromPcm(const int16_t *pcm, size_t sampleCount, uint32_t sampleRate)
{
const uint32_t dataSize = static_cast<uint32_t>(sampleCount * 2);
const uint32_t fileSize = 36 + dataSize;
std::vector<uint8_t> wav(8 + fileSize);
uint8_t *p = wav.data();
p[0] = 'R'; p[1] = 'I'; p[2] = 'F'; p[3] = 'F';
p[4] = static_cast<uint8_t>(fileSize);
p[5] = static_cast<uint8_t>(fileSize >> 8);
p[6] = static_cast<uint8_t>(fileSize >> 16);
p[7] = static_cast<uint8_t>(fileSize >> 24);
p[8] = 'W'; p[9] = 'A'; p[10] = 'V'; p[11] = 'E';
p[12] = 'f'; p[13] = 'm'; p[14] = 't'; p[15] = ' ';
p[16] = 16; p[17] = 0; p[18] = 0; p[19] = 0;
p[20] = 1; p[21] = 0;
p[22] = 1; p[23] = 0;
p[24] = static_cast<uint8_t>(sampleRate);
p[25] = static_cast<uint8_t>(sampleRate >> 8);
p[26] = static_cast<uint8_t>(sampleRate >> 16);
p[27] = static_cast<uint8_t>(sampleRate >> 24);
const uint32_t byteRate = sampleRate * 2;
p[28] = static_cast<uint8_t>(byteRate);
p[29] = static_cast<uint8_t>(byteRate >> 8);
p[30] = static_cast<uint8_t>(byteRate >> 16);
p[31] = static_cast<uint8_t>(byteRate >> 24);
p[32] = 2; p[33] = 0;
p[34] = 16; p[35] = 0;
p[36] = 'd'; p[37] = 'a'; p[38] = 't'; p[39] = 'a';
p[40] = static_cast<uint8_t>(dataSize);
p[41] = static_cast<uint8_t>(dataSize >> 8);
p[42] = static_cast<uint8_t>(dataSize >> 16);
p[43] = static_cast<uint8_t>(dataSize >> 24);
std::memcpy(p + 44, pcm, dataSize);
return wav;
}
}
namespace ps2_vag
{
bool decode(const uint8_t *data, uint32_t sizeBytes,
std::vector<int16_t> &outPcm, uint32_t &outSampleRate);
}
struct PS2AudioBackend::Impl
{
struct TrackedSound { Sound snd; uint32_t sampleKey; };
std::vector<TrackedSound> activeSounds;
};
PS2AudioBackend::PS2AudioBackend() : m_impl(std::make_unique<Impl>())
{
}
PS2AudioBackend::~PS2AudioBackend()
{
if (m_impl)
stopAll();
}
void PS2AudioBackend::onVagTransfer(const uint8_t *rdram, uint32_t srcAddr, uint32_t sizeBytes)
{
if (!rdram || sizeBytes < 48)
return;
const uint32_t physAddr = srcAddr & PS2_RAM_MASK;
if (physAddr + sizeBytes > PS2_RAM_SIZE)
return;
std::vector<int16_t> pcm;
uint32_t sampleRate = 44100;
if (!ps2_vag::decode(rdram + physAddr, sizeBytes, pcm, sampleRate))
return;
std::lock_guard<std::mutex> lock(m_mutex);
DecodedSample sample;
sample.pcm = std::move(pcm);
sample.sampleRate = sampleRate;
m_sampleBank[physAddr] = std::move(sample);
m_mostRecentSampleKey = physAddr;
}
void PS2AudioBackend::onVagTransferFromBuffer(const uint8_t *data, uint32_t sizeBytes, uint32_t keyAddr)
{
if (!data || sizeBytes < 48)
return;
std::vector<int16_t> pcm;
uint32_t sampleRate = 44100;
if (!ps2_vag::decode(data, sizeBytes, pcm, sampleRate))
return;
const uint32_t physAddr = keyAddr & PS2_RAM_MASK;
std::lock_guard<std::mutex> lock(m_mutex);
DecodedSample sample;
sample.pcm = std::move(pcm);
sample.sampleRate = sampleRate;
m_sampleBank[physAddr] = sample;
m_mostRecentSampleKey = physAddr;
m_loadOrderSamples.push_back(std::move(sample));
constexpr size_t kMaxLoadOrderSamples = 32;
if (m_loadOrderSamples.size() > kMaxLoadOrderSamples)
m_loadOrderSamples.erase(m_loadOrderSamples.begin());
}
namespace
{
constexpr uint32_t LIBSD_CMD_SET_VOICE = 0x8010u;
}
void PS2AudioBackend::onSoundCommand(uint32_t sid, uint32_t rpcNum,
const uint8_t *sendBuf, uint32_t sendSize,
uint8_t *recvBuf, uint32_t recvSize)
{
if (sid != 0x80000701u)
return;
if ((rpcNum == LIBSD_CMD_SET_VOICE || (rpcNum & 0xFF00u) == 0x8100u) &&
sendBuf && sendSize >= 20)
{
uint32_t sampleAddr = 0;
uint32_t voiceIndex = 0xFFFFFFFFu;
for (int vo = 4; vo >= 0 && voiceIndex == 0xFFFFFFFFu; vo -= 4)
{
if (vo < static_cast<int>(sendSize))
{
uint32_t v = 0;
std::memcpy(&v, sendBuf + vo, sizeof(v));
if (v < 24u)
voiceIndex = v;
}
}
constexpr uint32_t kMinPlausibleAddr = 0x1000u;
for (int off = 12; off <= 24 && sampleAddr == 0; off += 4)
{
if (sendSize >= static_cast<uint32_t>(off + 4))
{
uint32_t cand = 0;
std::memcpy(&cand, sendBuf + off, sizeof(cand));
if (cand >= kMinPlausibleAddr && (cand <= PS2_RAM_MASK || (cand & ~PS2_RAM_MASK) == 0))
sampleAddr = cand;
}
}
if (sampleAddr == 0)
sampleAddr = m_mostRecentSampleKey;
float pitch = 1.0f;
if (sendSize >= 12)
{
uint16_t pitchHalf = 0;
std::memcpy(&pitchHalf, sendBuf + 8, sizeof(pitchHalf));
if (pitchHalf != 0)
pitch = 4096.0f / static_cast<float>(pitchHalf);
}
play(sampleAddr, pitch, 1.0f, voiceIndex);
}
}
void PS2AudioBackend::play(uint32_t sampleAddr, float pitch, float volume, uint32_t voiceIndex)
{
std::lock_guard<std::mutex> lock(m_mutex);
DecodedSample *sampleToPlay = nullptr;
uint32_t sampleKey = 0;
auto it = m_sampleBank.find(sampleAddr & PS2_RAM_MASK);
if (it != m_sampleBank.end())
{
sampleToPlay = &it->second;
sampleKey = it->first;
}
else if (voiceIndex != 0xFFFFFFFFu && voiceIndex < m_loadOrderSamples.size())
{
sampleToPlay = &m_loadOrderSamples[voiceIndex];
sampleKey = 0x1719740u + voiceIndex;
}
else
{
it = m_sampleBank.find(m_mostRecentSampleKey);
if (it == m_sampleBank.end())
return;
sampleToPlay = &it->second;
sampleKey = it->first;
}
if (!sampleToPlay || sampleToPlay->pcm.empty())
return;
const bool isBgm = (sampleToPlay->pcm.size() > static_cast<size_t>(sampleToPlay->sampleRate * 5));
playDecodedSample(sampleKey, *sampleToPlay, pitch, volume, isBgm);
}
void PS2AudioBackend::pruneFinishedSounds()
{
auto &sounds = m_impl->activeSounds;
auto it = sounds.begin();
while (it != sounds.end())
{
if (!IsSoundPlaying(it->snd))
{
UnloadSound(it->snd);
it = sounds.erase(it);
}
else
{
++it;
}
}
}
void PS2AudioBackend::playDecodedSample(uint32_t sampleKey, DecodedSample &sample, float pitch, float volume,
bool isBgm)
{
if (!m_audioReady || sample.pcm.empty())
return;
pruneFinishedSounds();
for (const auto &t : m_impl->activeSounds)
{
if (t.sampleKey == sampleKey && IsSoundPlaying(t.snd))
return;
}
auto &sounds = m_impl->activeSounds;
if (isBgm)
{
for (auto it = sounds.begin(); it != sounds.end();)
{
if (IsSoundPlaying(it->snd))
{
StopSound(it->snd);
UnloadSound(it->snd);
it = sounds.erase(it);
}
else
++it;
}
}
constexpr int kMaxConcurrentSounds = 4;
while (static_cast<int>(sounds.size()) >= kMaxConcurrentSounds)
{
StopSound(sounds.front().snd);
UnloadSound(sounds.front().snd);
sounds.erase(sounds.begin());
}
std::vector<uint8_t> wav = buildWavFromPcm(sample.pcm.data(), sample.pcm.size(), sample.sampleRate);
Wave wave = LoadWaveFromMemory(".wav", wav.data(), static_cast<int>(wav.size()));
if (wave.frameCount <= 0)
return;
Sound snd = LoadSoundFromWave(wave);
UnloadWave(wave);
SetSoundPitch(snd, pitch);
SetSoundVolume(snd, volume);
m_impl->activeSounds.push_back({snd, sampleKey});
PlaySound(snd);
}
void PS2AudioBackend::stop(uint32_t voiceId)
{
(void)voiceId;
}
void PS2AudioBackend::stopAll()
{
std::lock_guard<std::mutex> lock(m_mutex);
for (auto &t : m_impl->activeSounds)
{
StopSound(t.snd);
UnloadSound(t.snd);
}
m_impl->activeSounds.clear();
}
+112
View File
@@ -0,0 +1,112 @@
#include "ps2_memory.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
namespace
{
inline int16_t clamp16(int32_t v)
{
if (v < -32768) return -32768;
if (v > 32767) return 32767;
return static_cast<int16_t>(v);
}
inline int8_t signExtend4(uint8_t nibble)
{
uint8_t s = nibble & 0x0F;
return static_cast<int8_t>((s & 8) ? static_cast<int8_t>(s | 0xF0) : static_cast<int8_t>(s));
}
}
namespace ps2_vag
{
bool decode(const uint8_t *data, uint32_t sizeBytes,
std::vector<int16_t> &outPcm, uint32_t &outSampleRate)
{
if (!data || sizeBytes < 48)
return false;
const uint32_t magic = (static_cast<uint32_t>(data[0]) << 24) |
(static_cast<uint32_t>(data[1]) << 16) |
(static_cast<uint32_t>(data[2]) << 8) |
static_cast<uint32_t>(data[3]);
if (magic != 0x56414770u)
{
const uint32_t magicLE = (static_cast<uint32_t>(data[3]) << 24) |
(static_cast<uint32_t>(data[2]) << 16) |
(static_cast<uint32_t>(data[1]) << 8) |
static_cast<uint32_t>(data[0]);
if (magicLE != 0x56414770u)
return false;
}
uint32_t dataSize = (static_cast<uint32_t>(data[0x0c]) << 24) |
(static_cast<uint32_t>(data[0x0d]) << 16) |
(static_cast<uint32_t>(data[0x0e]) << 8) |
static_cast<uint32_t>(data[0x0f]);
outSampleRate = (static_cast<uint32_t>(data[0x10]) << 24) |
(static_cast<uint32_t>(data[0x11]) << 16) |
(static_cast<uint32_t>(data[0x12]) << 8) |
static_cast<uint32_t>(data[0x13]);
if (outSampleRate == 0)
outSampleRate = 44100;
const uint32_t numBlocks = (dataSize + 15) / 16;
outPcm.clear();
outPcm.reserve(numBlocks * 28);
int16_t s1 = 0, s2 = 0;
const uint8_t *block = data + 48;
for (uint32_t b = 0; b < numBlocks && (block + 16) <= data + sizeBytes; ++b, block += 16)
{
uint8_t shift = block[0] & 0x0F;
if (shift > 12)
shift = 9;
uint8_t filter = (block[0] >> 4) & 0x07;
if (filter > 4)
filter = 0;
for (int sampleIdx = 0; sampleIdx < 28; ++sampleIdx)
{
const uint8_t byte = block[2 + sampleIdx / 2];
const uint8_t nibble = (sampleIdx & 1) ? (byte >> 4) : (byte & 0x0F);
const int8_t rawSample = signExtend4(nibble);
const int32_t shiftedSample = rawSample << (12 - shift);
int32_t filteredSample;
const int32_t old = s1;
const int32_t older = s2;
switch (filter)
{
case 0:
filteredSample = shiftedSample;
break;
case 1:
filteredSample = shiftedSample + (60 * old + 32) / 64;
break;
case 2:
filteredSample = shiftedSample + (115 * old - 52 * older + 32) / 64;
break;
case 3:
filteredSample = shiftedSample + (98 * old - 55 * older + 32) / 64;
break;
case 4:
filteredSample = shiftedSample + (122 * old - 60 * older + 32) / 64;
break;
default:
filteredSample = shiftedSample;
break;
}
const int16_t clamped = clamp16(filteredSample);
s2 = s1;
s1 = clamped;
outPcm.push_back(clamped);
}
}
return true;
}
}
+22
View File
@@ -0,0 +1,22 @@
#include "ps2_iop.h"
IOP::IOP()
{
reset();
}
void IOP::init(uint8_t *rdram)
{
m_rdram = rdram;
}
void IOP::reset()
{
}
bool IOP::handleRPC(uint32_t /*sid*/, uint32_t /*rpcNum*/,
uint32_t /*sendBufAddr*/, uint32_t /*sendSize*/,
uint32_t /*recvBufAddr*/, uint32_t /*recvSize*/)
{
return false;
}
+14
View File
@@ -0,0 +1,14 @@
#include "ps2_iop_audio.h"
#include "ps2_runtime.h"
namespace ps2_iop_audio
{
void handleLibSdRpc(PS2Runtime *runtime, uint32_t sid, uint32_t rpcNum,
const uint8_t *sendBuf, uint32_t sendSize,
uint8_t *recvBuf, uint32_t recvSize)
{
if (!runtime)
return;
runtime->audioBackend().onSoundCommand(sid, rpcNum, sendBuf, sendSize, recvBuf, recvSize);
}
}
+94
View File
@@ -0,0 +1,94 @@
#include "ps2_pad.h"
#include "raylib.h"
#include <cstring>
namespace
{
constexpr uint8_t kPadAnalogMarker = 0x73;
constexpr uint8_t kPadStickCenter = 0x80;
constexpr uint16_t PAD_LEFT = 0x0080u;
constexpr uint16_t PAD_DOWN = 0x0040u;
constexpr uint16_t PAD_RIGHT = 0x0020u;
constexpr uint16_t PAD_UP = 0x0010u;
constexpr uint16_t PAD_START = 0x0008u;
constexpr uint16_t PAD_R3 = 0x0004u;
constexpr uint16_t PAD_L3 = 0x0002u;
constexpr uint16_t PAD_SELECT = 0x0001u;
constexpr uint16_t PAD_SQUARE = 0x8000u;
constexpr uint16_t PAD_CROSS = 0x4000u;
constexpr uint16_t PAD_CIRCLE = 0x2000u;
constexpr uint16_t PAD_TRIANGLE = 0x1000u;
constexpr uint16_t PAD_R1 = 0x0800u;
constexpr uint16_t PAD_L1 = 0x0400u;
constexpr uint16_t PAD_R2 = 0x0200u;
constexpr uint16_t PAD_L2 = 0x0100u;
}
bool PSPadBackend::readState(int /*port*/, int /*slot*/, uint8_t *data, size_t size)
{
if (!data || size < 32)
return false;
std::memset(data, 0, 32);
data[0] = 0x01;
data[1] = kPadAnalogMarker;
data[2] = 0xFF;
data[3] = 0xFF;
data[4] = data[5] = data[6] = data[7] = kPadStickCenter;
uint16_t btns = 0xFFFFu;
constexpr int kGamepad = 0;
const bool useGamepad = IsGamepadAvailable(kGamepad);
auto clearBit = [&btns](uint16_t mask) { btns &= ~mask; };
if (useGamepad)
{
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_UP)) clearBit(PAD_UP);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_DOWN)) clearBit(PAD_DOWN);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_LEFT)) clearBit(PAD_LEFT);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)) clearBit(PAD_RIGHT);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)) clearBit(PAD_CROSS);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)) clearBit(PAD_CIRCLE);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_LEFT)) clearBit(PAD_SQUARE);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_FACE_UP)) clearBit(PAD_TRIANGLE);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_1)) clearBit(PAD_L1);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_1)) clearBit(PAD_R1);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_TRIGGER_2)) clearBit(PAD_L2);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_TRIGGER_2)) clearBit(PAD_R2);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_MIDDLE_RIGHT)) clearBit(PAD_START);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)) clearBit(PAD_SELECT);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_LEFT_THUMB)) clearBit(PAD_L3);
if (IsGamepadButtonDown(kGamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) clearBit(PAD_R3);
float lx = GetGamepadAxisMovement(kGamepad, GAMEPAD_AXIS_LEFT_X);
float ly = GetGamepadAxisMovement(kGamepad, GAMEPAD_AXIS_LEFT_Y);
float rx = GetGamepadAxisMovement(kGamepad, GAMEPAD_AXIS_RIGHT_X);
float ry = GetGamepadAxisMovement(kGamepad, GAMEPAD_AXIS_RIGHT_Y);
data[6] = static_cast<uint8_t>(128 + lx * 127);
data[7] = static_cast<uint8_t>(128 + ly * 127);
data[4] = static_cast<uint8_t>(128 + rx * 127);
data[5] = static_cast<uint8_t>(128 + ry * 127);
}
else
{
if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) clearBit(PAD_UP);
if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) clearBit(PAD_DOWN);
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) clearBit(PAD_LEFT);
if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) clearBit(PAD_RIGHT);
if (IsKeyDown(KEY_ENTER) || IsKeyDown(KEY_SPACE)) clearBit(PAD_CROSS);
if (IsKeyDown(KEY_ESCAPE)) clearBit(PAD_CIRCLE);
if (IsKeyDown(KEY_KP_0) || IsKeyDown(KEY_Z)) clearBit(PAD_SQUARE);
if (IsKeyDown(KEY_KP_1) || IsKeyDown(KEY_X)) clearBit(PAD_TRIANGLE);
if (IsKeyDown(KEY_Q)) clearBit(PAD_L1);
if (IsKeyDown(KEY_E)) clearBit(PAD_R1);
if (IsKeyDown(KEY_LEFT_SHIFT)) clearBit(PAD_L2);
if (IsKeyDown(KEY_RIGHT_SHIFT)) clearBit(PAD_R2);
if (IsKeyDown(KEY_ENTER)) clearBit(PAD_START);
if (IsKeyDown(KEY_TAB)) clearBit(PAD_SELECT);
}
data[2] = static_cast<uint8_t>(btns & 0xFF);
data[3] = static_cast<uint8_t>(btns >> 8);
return true;
}
+5
View File
@@ -296,8 +296,13 @@ bool PS2Runtime::initialize(const char *title)
return false;
}
m_iop.init(m_memory.getRDRAM());
m_iop.reset();
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(FB_WIDTH, FB_HEIGHT, title);
InitAudioDevice();
m_audioBackend.setAudioReady(IsAudioDeviceReady());
SetTargetFPS(60);
return true;
+1
View File
@@ -1,5 +1,6 @@
#include "ps2_syscalls.h"
#include "ps2_runtime.h"
#include "ps2_iop_audio.h"
#include "ps2_runtime_macros.h"
#include "ps2_stubs.h"
#include <iostream>
+11 -13
View File
@@ -1500,26 +1500,24 @@ void scePadPortOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void scePadRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
(void)runtime;
const uint32_t dataAddr = getRegU32(ctx, 6); // a2
const int port = static_cast<int>(getRegU32(ctx, 4));
const int slot = static_cast<int>(getRegU32(ctx, 5));
const uint32_t dataAddr = getRegU32(ctx, 6);
uint8_t *data = getMemPtr(rdram, dataAddr);
if (!data)
{
setReturnS32(ctx, 0);
return;
}
// struct padButtonStatus (32 bytes): neutral state, no buttons pressed.
if (runtime && runtime->padBackend().readState(port, slot, data, 32))
{
setReturnS32(ctx, 1);
return;
}
std::memset(data, 0, 32);
data[1] = 0x73; // analog/dualshock mode marker
data[2] = 0xFF; // btns low (active-low)
data[3] = 0xFF; // btns high
data[4] = 0x80; // rjoy_h
data[5] = 0x80; // rjoy_v
data[6] = 0x80; // ljoy_h
data[7] = 0x80; // ljoy_v
data[1] = 0x73;
data[2] = data[3] = 0xFF;
data[4] = data[5] = data[6] = data[7] = 0x80;
setReturnS32(ctx, 1);
}
@@ -26,6 +26,15 @@ static void releasePs2Fd(int ps2Fd)
g_fileDescriptors.erase(ps2Fd);
}
struct VagAccumEntry
{
std::vector<uint8_t> data;
uint32_t firstBufAddr = 0;
};
static std::unordered_map<int, VagAccumEntry> g_vagAccum;
static std::mutex g_vagAccumMutex;
static constexpr size_t kVagAccumMaxBytes = 16 * 1024 * 1024;
static const char *translateFioMode(int ps2Flags)
{
bool read = (ps2Flags & PS2_FIO_O_RDONLY) || (ps2Flags & PS2_FIO_O_RDWR);
@@ -106,21 +115,47 @@ void fioOpen(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
void fioClose(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
int ps2Fd = (int)getRegU32(ctx, 4); // $a0
std::cout << "fioClose: fd=" << ps2Fd << std::endl;
int ps2Fd = (int)getRegU32(ctx, 4);
FILE *fp = getHostFile(ps2Fd);
if (!fp)
{
std::cerr << "fioClose warning: Invalid PS2 file descriptor " << ps2Fd << std::endl;
setReturnS32(ctx, -1); // e.g., -EBADF
setReturnS32(ctx, -1);
return;
}
int ret = ::fclose(fp);
releasePs2Fd(ps2Fd);
// returns 0 on success, -1 on error
{
std::lock_guard<std::mutex> lock(g_vagAccumMutex);
auto it = g_vagAccum.find(ps2Fd);
if (it != g_vagAccum.end())
{
VagAccumEntry &e = it->second;
if (e.data.size() >= 48)
{
const uint32_t magic = (static_cast<uint32_t>(e.data[0]) << 24) |
(static_cast<uint32_t>(e.data[1]) << 16) |
(static_cast<uint32_t>(e.data[2]) << 8) |
static_cast<uint32_t>(e.data[3]);
const uint32_t magicLE = (static_cast<uint32_t>(e.data[3]) << 24) |
(static_cast<uint32_t>(e.data[2]) << 16) |
(static_cast<uint32_t>(e.data[1]) << 8) |
static_cast<uint32_t>(e.data[0]);
if (magic == 0x56414770u || magicLE == 0x56414770u)
{
if (runtime)
runtime->audioBackend().onVagTransferFromBuffer(
e.data.data(), static_cast<uint32_t>(e.data.size()),
e.firstBufAddr ? e.firstBufAddr : 0u);
}
}
g_vagAccum.erase(it);
}
}
setReturnS32(ctx, ret == 0 ? 0 : -1);
}
@@ -161,11 +196,39 @@ void fioRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
{
std::cerr << "fioRead error: fread failed for fd " << ps2Fd << ": " << strerror(errno) << std::endl;
clearerr(fp);
setReturnS32(ctx, -1); // -EIO or other appropriate error
setReturnS32(ctx, -1);
return;
}
// returns number of bytes read (can be 0 for EOF)
{
std::lock_guard<std::mutex> lock(g_vagAccumMutex);
auto it = g_vagAccum.find(ps2Fd);
if (it != g_vagAccum.end())
{
VagAccumEntry &e = it->second;
if (e.data.size() + bytesRead <= kVagAccumMaxBytes)
e.data.insert(e.data.end(), hostBuf, hostBuf + bytesRead);
}
else if (bytesRead >= 4)
{
const uint32_t magic = (static_cast<uint32_t>(hostBuf[0]) << 24) |
(static_cast<uint32_t>(hostBuf[1]) << 16) |
(static_cast<uint32_t>(hostBuf[2]) << 8) |
static_cast<uint32_t>(hostBuf[3]);
const uint32_t magicLE = (static_cast<uint32_t>(hostBuf[3]) << 24) |
(static_cast<uint32_t>(hostBuf[2]) << 16) |
(static_cast<uint32_t>(hostBuf[1]) << 8) |
static_cast<uint32_t>(hostBuf[0]);
if (magic == 0x56414770u || magicLE == 0x56414770u)
{
VagAccumEntry &e = g_vagAccum[ps2Fd];
e.firstBufAddr = bufAddr;
if (bytesRead <= kVagAccumMaxBytes)
e.data.assign(hostBuf, hostBuf + bytesRead);
}
}
}
setReturnS32(ctx, (int32_t)bytesRead);
}
@@ -321,6 +321,19 @@ void SifCallRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
return true;
};
if (!handled && sid != 0 && runtime)
{
if (!runtime->iop().handleRPC(sid, rpcNum, sendBuf, sendSize, recvBuf, recvSize) &&
sid == IOP_SID_LIBSD)
{
const uint8_t *sendPtr = sendBuf ? getConstMemPtr(rdram, sendBuf) : nullptr;
uint8_t *recvPtr = recvBuf ? getMemPtr(rdram, recvBuf) : nullptr;
ps2_iop_audio::handleLibSdRpc(runtime, sid, rpcNum, sendPtr, sendSize, recvPtr, recvSize);
handled = true;
resultPtr = recvBuf;
}
}
const bool isDtxUrpc = (sid == kDtxRpcSid) && (rpcNum >= 0x400u) && (rpcNum < 0x500u);
uint32_t dtxUrpcCommand = isDtxUrpc ? (rpcNum & 0xFFu) : 0u;
uint32_t dtxUrpcFn = 0;
+6
View File
@@ -1,6 +1,9 @@
#include "ps2_runtime.h"
#include "register_functions.h"
#include "games_database.h"
#ifdef _DEBUG
#include "ps2_log.h"
#endif
#include <iostream>
#include <string>
@@ -63,5 +66,8 @@ int main(int argc, char* argv[])
runtime.run();
#ifdef _DEBUG
ps2_log::print_saved_location();
#endif
return 0;
}