/* 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 /* 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; }