Files
psxrecomp/runtime/src/mod_guest_services.c
2026-10-25 12:55:31 -07:00

215 lines
8.4 KiB
C

/* Guest services for seamless-loading adapters (mod_plugins.h). Each is the
* one shared implementation of an effect several titles' adapters need; see
* the header for the contract. */
#include "mod_plugins.h"
#include "mod_resident.h"
#include "cpu_state.h"
#include "cdrom.h"
#include "dma.h"
#include "gpu.h"
#include "interrupts.h"
#include "psx_cycle_freeze.h"
#include "psx_memory.h"
#include "spu.h"
#include <string.h>
/* mod_runtime.cpp: 1 while a netplay host-output plugin's hook runs; every
* guest-mutating service refuses then (docs/NETPLAY.md). */
int psx_mod_host_output_scope(void);
typedef struct ResidentCDCopy {
uint32_t address, bytes, lba;
const void* data;
} ResidentCDCopy;
static int resident_cd_copy(void* opaque) {
ResidentCDCopy* copy = (ResidentCDCopy*)opaque;
return psx_mod_dma_write_ram(copy->address, copy->data, copy->bytes, (int)copy->lba);
}
int psx_mod_resident_cd_read(uint32_t address, const void* data,
uint32_t lba, uint32_t sectors) {
if (psx_mod_host_output_scope() || !data || !sectors ||
sectors > UINT32_MAX / 2048u || (address & 3u)) return 0;
ResidentCDCopy copy = {address, sectors * 2048u, lba, data};
return cdrom_resident_complete_read(lba, sectors,
(const uint8_t*)data + copy.bytes - 2048u, resident_cd_copy, &copy);
}
int psx_mod_resident_cd_sector(uint32_t lba, const void* user_data) {
if (psx_mod_host_output_scope()) return 0;
return cdrom_resident_feed_sector(lba, (const uint8_t*)user_data);
}
uint32_t psx_mod_call_guest(struct CPUState* cpu, uint32_t function,
uint32_t return_address, uint32_t a0, uint32_t a1,
uint32_t a2, uint32_t a3) {
if (psx_mod_host_output_scope()) return 0;
uint32_t regs[32];
const uint32_t pc = cpu->pc, hi = cpu->hi, lo = cpu->lo;
memcpy(regs, cpu->gpr, sizeof regs);
cpu->gpr[4] = a0;
cpu->gpr[5] = a1;
cpu->gpr[6] = a2;
cpu->gpr[7] = a3;
cpu->gpr[31] = return_address;
psx_snapshot_host_call_begin();
psx_dispatch_call(cpu, function, return_address);
const uint32_t result = cpu->gpr[2];
memcpy(cpu->gpr, regs, sizeof regs);
cpu->pc = pc;
cpu->hi = hi;
cpu->lo = lo;
psx_snapshot_host_call_end();
return result;
}
/* The caller's in-flight timing state: deadlines are absolute guest cycles
* and the load pipeline describes the instructions around the call, so an
* uncharged callee must leave them as they were. */
typedef struct CallerTiming {
uint64_t muldiv_ts_done, gte_ts_done;
uint8_t read_absorb[33];
uint8_t read_absorb_which, read_fudge, ld_which_t;
uint32_t ld_absorb;
} CallerTiming;
static void timing_save(CallerTiming* t, const struct CPUState* cpu) {
t->muldiv_ts_done = cpu->muldiv_ts_done;
t->gte_ts_done = cpu->gte_ts_done;
memcpy(t->read_absorb, cpu->read_absorb, sizeof t->read_absorb);
t->read_absorb_which = cpu->read_absorb_which;
t->read_fudge = cpu->read_fudge;
t->ld_which_t = cpu->ld_which_t;
t->ld_absorb = cpu->ld_absorb;
}
static void timing_restore(struct CPUState* cpu, const CallerTiming* t) {
cpu->muldiv_ts_done = t->muldiv_ts_done;
cpu->gte_ts_done = t->gte_ts_done;
memcpy(cpu->read_absorb, t->read_absorb, sizeof t->read_absorb);
cpu->read_absorb_which = t->read_absorb_which;
cpu->read_fudge = t->read_fudge;
cpu->ld_which_t = t->ld_which_t;
cpu->ld_absorb = t->ld_absorb;
}
uint32_t psx_mod_call_guest_uncharged_counted(struct CPUState* cpu,
uint32_t function, uint32_t return_address, uint32_t a0, uint32_t a1,
uint32_t a2, uint32_t a3, uint32_t budget_cycles, int* charged,
uint64_t* counted_cycles) {
if (psx_mod_host_output_scope()) {
if (charged) *charged = 0;
if (counted_cycles) *counted_cycles = 0;
return 0;
}
PsxCycleFreeze save;
CallerTiming timing;
timing_save(&timing, cpu);
const int frozen = psx_cycle_uncharged_begin(&save, budget_cycles);
const uint32_t result =
psx_mod_call_guest(cpu, function, return_address, a0, a1, a2, a3);
const uint64_t counted = frozen ? psx_cycle_uncharged_counted() : 0;
const int uncharged = frozen ? psx_cycle_uncharged_end(&save) : 1;
if (frozen && uncharged) timing_restore(cpu, &timing);
if (charged) *charged = !uncharged;
if (counted_cycles) *counted_cycles = counted;
return result;
}
uint32_t psx_mod_call_guest_uncharged(struct CPUState* cpu, uint32_t function,
uint32_t return_address, uint32_t a0,
uint32_t a1, uint32_t a2, uint32_t a3,
uint32_t budget_cycles, int* charged) {
if (psx_mod_host_output_scope()) { if (charged) *charged = 0; return 0; }
return psx_mod_call_guest_uncharged_counted(cpu, function, return_address,
a0, a1, a2, a3, budget_cycles, charged, NULL);
}
int psx_mod_dma_write_ram(uint32_t address, const void* data, uint32_t bytes,
int lba) {
if (psx_mod_host_output_scope()) return 0;
return dma_host_cdrom_write(address, (const uint8_t*)data, bytes, lba);
}
static int ram_span(uint32_t address, uint32_t bytes) {
const uint32_t phys = address & 0x1FFFFFFFu;
const uint32_t live = psx_ram_live_bytes();
return address < 0xC0000000u && phys < live && bytes <= live - phys;
}
int psx_mod_host_write_ram(uint32_t address, const void* data, uint32_t bytes) {
if (psx_mod_host_output_scope()) return 0;
const uint8_t* p = (const uint8_t*)data;
if ((!p && bytes) || !ram_span(address, bytes)) return 0;
while (bytes && (address & 3u)) { psx_host_write_byte(address++, *p++); --bytes; }
for (; bytes >= 4; address += 4, p += 4, bytes -= 4) {
uint32_t word;
memcpy(&word, p, 4);
psx_host_write_word(address, word);
}
while (bytes--) psx_host_write_byte(address++, *p++);
return 1;
}
#define SPU_RAM_BYTES 0x80000u
#define SPU_TRANSFER_ADDR 0x1F801DA6u
#define SPU_CONTROL 0x1F801DAAu
int psx_mod_spu_sample_bank(uint32_t bank,const void *adpcm,uint32_t bytes) {
if (psx_mod_host_output_scope()) return 0;
return spu_register_sample_bank(bank,adpcm,bytes);
}
int psx_mod_spu_bind_voice_bank(unsigned voice,uint32_t bank) {
if (psx_mod_host_output_scope()) return 0;
return spu_bind_next_voice_bank(voice,bank);
}
int psx_mod_spu_private_voice_play(unsigned slot,uint32_t bank,const uint16_t regs[8],unsigned mode) {
if (psx_mod_host_output_scope()) return 0;
return spu_private_voice_play(slot,bank,regs,mode);
}
void psx_mod_spu_private_voice_stop(uint32_t mask) {
if (psx_mod_host_output_scope()) return;
spu_private_voice_stop(mask);
}
int psx_mod_spu_private_voice_volume(unsigned slot,uint16_t left,uint16_t right) {
if (psx_mod_host_output_scope()) return 0;
return spu_private_voice_volume(slot,left,right);
}
uint32_t psx_mod_spu_private_voice_active(void) {return spu_private_voice_active();}
int psx_mod_spu_upload(uint32_t spu_address, uint32_t guest_source,
uint32_t bytes, int stop_after) {
if (psx_mod_host_output_scope()) return 0;
if ((spu_address & 7u) || (guest_source & 3u) || (bytes & 3u) ||
spu_address >= SPU_RAM_BYTES || bytes > SPU_RAM_BYTES ||
!ram_span(guest_source, bytes))
return 0;
spu_write(SPU_TRANSFER_ADDR, spu_address >> 3);
const uint16_t control = spu_ctrl_read();
spu_write(SPU_CONTROL, (control & ~0x30u) | 0x20u);
for (uint32_t i = 0; i < bytes; i += 4u) spu_dma_write(psx_mod_read_word(guest_source + i));
if (stop_after) spu_write(SPU_CONTROL, control & ~0x30u);
return 1;
}
int psx_mod_psyq_load_image(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
uint32_t guest_source) {
if (psx_mod_host_output_scope()) return 0;
if (!w || w > 1024u || !h || h > 512u || x >= 1024u || y >= 512u ||
(guest_source & 3u))
return 0;
const uint32_t words = (w * h + 1u) / 2u;
if (!ram_span(guest_source, words * 4u)) return 0;
gpu_set_gp0_source(guest_source);
gpu_write_gp1(0x04000000u);
gpu_write_gp0(0x01000000u);
gpu_write_gp0(0xA0000000u);
gpu_write_gp0(x | (y << 16));
gpu_write_gp0(w | (h << 16));
for (uint32_t i = 0; i < words; i++) {
gpu_set_gp0_source(guest_source + 4u * i);
gpu_write_gp0(psx_mod_read_word(guest_source + 4u * i));
}
if (words >= 16u) gpu_write_gp1(0x04000002u);
return 1;
}