mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
215 lines
8.4 KiB
C
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, ©);
|
|
}
|
|
|
|
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;
|
|
}
|