Files
sm-vita/src/snes/snes.c

459 lines
12 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <assert.h>
#include "snes.h"
#include "cpu.h"
#include "apu.h"
#include "dma.h"
#include "ppu.h"
#include "cart.h"
#include "input.h"
#include "../tracing.h"
extern bool g_is_turbo;
int snes_frame_counter;
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
extern uint8_t g_ram[0x20000];
extern void RtlApuWrite(uint32_t adr, uint8_t val);
static uint8_t snes_readReg(Snes* snes, uint16_t adr);
static void snes_writeReg(Snes* snes, uint16_t adr, uint8_t val);
Snes* snes_init(uint8_t *ram) {
Snes* snes = malloc(sizeof(Snes));
snes->ram = ram;
snes->debug_cycles = false;
snes->debug_apu_cycles = false;
snes->runningWhichVersion = 0;
snes->cpu = cpu_init(snes, 0);
snes->apu = apu_init();
snes->dma = dma_init(snes);
snes->my_ppu = ppu_init(snes);
snes->snes_ppu = ppu_init(snes);
snes->ppu = snes->snes_ppu;
snes->cart = cart_init(snes);
snes->input1 = input_init(snes);
snes->input2 = input_init(snes);
return snes;
}
void snes_free(Snes* snes) {
cpu_free(snes->cpu);
apu_free(snes->apu);
dma_free(snes->dma);
ppu_free(snes->ppu);
cart_free(snes->cart);
input_free(snes->input1);
input_free(snes->input2);
free(snes);
}
void snes_saveload(Snes *snes, SaveLoadFunc *func, void *ctx) {
cpu_saveload(snes->cpu, func, ctx);
apu_saveload(snes->apu, func, ctx);
dma_saveload(snes->dma, func, ctx);
ppu_saveload(snes->ppu, func, ctx);
cart_saveload(snes->cart, func, ctx);
func(ctx, &snes->hPos, offsetof(Snes, openBus) + 1 - offsetof(Snes, hPos));
func(ctx, snes->ram, 0x20000);
func(ctx, &snes->ramAdr, 4);
snes->runningWhichVersion = 0;
}
void snes_reset(Snes* snes, bool hard) {
cart_reset(snes->cart); // reset cart first, because resetting cpu will read from it (reset vector)
cpu_reset(snes->cpu);
apu_reset(snes->apu);
dma_reset(snes->dma);
ppu_reset(snes->my_ppu);
ppu_reset(snes->snes_ppu);
input_reset(snes->input1);
input_reset(snes->input2);
if (hard)
memset(snes->ram, 0, 0x20000);
snes->ramAdr = 0;
snes->hPos = 0;
snes->vPos = 0;
snes->frames = 0;
snes->cpuCyclesLeft = 52; // 5 reads (8) + 2 IntOp (6)
snes->cpuMemOps = 0;
snes->apuCatchupCycles = 0.0;
snes->hIrqEnabled = false;
snes->vIrqEnabled = false;
snes->nmiEnabled = false;
snes->hTimer = 0x1ff;
snes->vTimer = 0x1ff;
snes->inNmi = false;
snes->inIrq = false;
snes->inVblank = false;
memset(snes->portAutoReadX, 0, sizeof(snes->portAutoReadX));
snes->autoJoyRead = false;
snes->autoJoyTimer = 0;
snes->ppuLatch = false;
snes->multiplyA = 0xff;
snes->multiplyResult = 0xfe01;
snes->divideA = 0xffff;
snes->divideResult = 0x101;
snes->fastMem = false;
snes->openBus = 0;
}
void snes_handle_pos_stuff(Snes *snes) {
// handle positional stuff
// TODO: better timing? (especially Hpos)
if (snes->hPos == 0) {
// end of hblank, do most vPos-tests
bool startingVblank = false;
if (snes->vPos == 0) {
// end of vblank
snes->inVblank = false;
snes->inNmi = false;
dma_initHdma(snes->dma);
} else if (snes->vPos == 225) {
// ask the ppu if we start vblank now or at vPos 240 (overscan)
startingVblank = !ppu_checkOverscan(snes->ppu);
} else if (snes->vPos == 240) {
// if we are not yet in vblank, we had an overscan frame, set startingVblank
if (!snes->inVblank) startingVblank = true;
}
if (startingVblank) {
// if we are starting vblank
ppu_handleVblank(snes->ppu);
snes->inVblank = true;
snes->inNmi = true;
// if (snes->nmiEnabled) {
snes->cpu->nmiWanted = true; // request NMI on CPU
// }
if (snes->autoJoyRead) {
// TODO: this starts a little after start of vblank
snes->autoJoyTimer = 0;
}
}
} else if (snes->hPos == 512) {
// render the line halfway of the screen for better compatibility
if (!snes->inVblank && !snes->disableRender)
ppu_runLine(snes->ppu, snes->vPos);
} else if (snes->hPos == 1024) {
// start of hblank
if (!snes->inVblank)
dma_doHdma(snes->dma);
}
// handle autoJoyRead-timer
//if (snes->autoJoyTimer > 0) snes->autoJoyTimer -= 2;
// increment position
// TODO: exact frame timing (line 240 on odd frame is 4 cycles shorter,
// even frames in interlace is 1 extra line)
snes->hPos += 2;
if (snes->hPos == 1364) {
snes->hPos = 0;
snes->vPos++;
if (snes->vPos == 262) {
snes->vPos = 0;
snes->frames++;
// snes_catchupApu(snes); // catch up the apu at the end of the frame
}
}
}
#define IS_ADR(x) (x == 0xfffff )
void snes_catchupApu(Snes* snes) {
if (snes->apuCatchupCycles > 10000)
snes->apuCatchupCycles = 10000;
int catchupCycles = (int) snes->apuCatchupCycles;
for(int i = 0; i < catchupCycles; i++) {
apu_cycle(snes->apu);
}
snes->apuCatchupCycles -= (double) catchupCycles;
}
uint8_t snes_readBBus(Snes* snes, uint8_t adr) {
if(adr < 0x40) {
return ppu_read(snes->ppu, adr);
}
if(adr < 0x80) {
assert(0);
snes->apuCatchupCycles = 32;
snes_catchupApu(snes); // catch up the apu before reading
return snes->apu->outPorts[adr & 0x3];
}
if(adr == 0x80) {
uint8_t ret = snes->ram[snes->ramAdr++];
snes->ramAdr &= 0x1ffff;
return ret;
}
assert(0);
return snes->openBus;
}
#define is_uploading_apu (*(uint16_t*)(g_ram+0x617))
void snes_writeBBus(Snes* snes, uint8_t adr, uint8_t val) {
if(adr < 0x40) {
ppu_write(snes->ppu, adr, val);
return;
}
if(adr < 0x80) {
RtlApuWrite(0x2100 + adr, val);
return;
}
switch(adr) {
case 0x80: {
snes->ram[snes->ramAdr++] = val;
snes->ramAdr &= 0x1ffff;
break;
}
case 0x81: {
snes->ramAdr = (snes->ramAdr & 0x1ff00) | val;
break;
}
case 0x82: {
snes->ramAdr = (snes->ramAdr & 0x100ff) | (val << 8);
break;
}
case 0x83: {
snes->ramAdr = (snes->ramAdr & 0x0ffff) | ((val & 1) << 16);
break;
}
}
}
static uint16_t SwapInputBits(uint16_t x) {
uint16_t r = 0;
for (int i = 0; i < 16; i++, x >>= 1)
r = r * 2 + (x & 1);
return r;
}
static uint8_t snes_readReg(Snes* snes, uint16_t adr) {
switch(adr) {
case 0x4210: {
uint8_t val = 0x2; // CPU version (4 bit)
val |= snes->inNmi << 7;
return val | (snes->openBus & 0x70);
}
case 0x4211: {
uint8_t val = snes->inIrq << 7;
snes->inIrq = false;
snes->cpu->irqWanted = false;
return val | (snes->openBus & 0x7f);
}
case 0x4212: {
uint8_t val = (snes->autoJoyTimer > 0);
val |= (snes->hPos >= 1024) << 6;
val |= snes->inVblank << 7;
return val | (snes->openBus & 0x3e);
}
case 0x4213:
return snes->ppuLatch << 7; // IO-port
case 0x4214:
return snes->divideResult & 0xff;
case 0x4215:
return snes->divideResult >> 8;
case 0x4216:
return snes->multiplyResult & 0xff;
case 0x4217:
return snes->multiplyResult >> 8;
case 0x4218:
return SwapInputBits(snes->input1->currentState) & 0xff;
case 0x4219:
return SwapInputBits(snes->input1->currentState) >> 8;
case 0x421a:
case 0x421c:
case 0x421e:
case 0x421b:
case 0x421d:
case 0x421f:
return 0;
default: {
return snes->openBus;
}
}
}
static void snes_writeReg(Snes* snes, uint16_t adr, uint8_t val) {
switch(adr) {
case 0x4200: {
snes->autoJoyRead = val & 0x1;
if(!snes->autoJoyRead) snes->autoJoyTimer = 0;
snes->hIrqEnabled = val & 0x10;
snes->vIrqEnabled = val & 0x20;
snes->nmiEnabled = val & 0x80;
if(!snes->hIrqEnabled && !snes->vIrqEnabled) {
snes->inIrq = false;
snes->cpu->irqWanted = false;
}
// TODO: enabling nmi during vblank with inNmi still set generates nmi
// enabling virq (and not h) on the vPos that vTimer is at generates irq (?)
break;
}
case 0x4201: {
if(!(val & 0x80) && snes->ppuLatch) {
// latch the ppu
ppu_read(snes->ppu, 0x37);
}
snes->ppuLatch = val & 0x80;
break;
}
case 0x4202: {
snes->multiplyA = val;
break;
}
case 0x4203: {
snes->multiplyResult = snes->multiplyA * val;
break;
}
case 0x4204: {
snes->divideA = (snes->divideA & 0xff00) | val;
break;
}
case 0x4205: {
snes->divideA = (snes->divideA & 0x00ff) | (val << 8);
break;
}
case 0x4206: {
if(val == 0) {
snes->divideResult = 0xffff;
snes->multiplyResult = snes->divideA;
} else {
snes->divideResult = snes->divideA / val;
snes->multiplyResult = snes->divideA % val;
}
break;
}
case 0x4207: {
snes->hTimer = (snes->hTimer & 0x100) | val;
break;
}
case 0x4208: {
snes->hTimer = (snes->hTimer & 0x0ff) | ((val & 1) << 8);
break;
}
case 0x4209: {
snes->vTimer = (snes->vTimer & 0x100) | val;
break;
}
case 0x420a: {
snes->vTimer = (snes->vTimer & 0x0ff) | ((val & 1) << 8);
break;
}
case 0x420b: {
if (val == 2) {
uint32_t t = snes->dma->channel[1].aBank << 16 | snes->dma->channel[1].aAdr;
int data = snes_read(snes, t) | snes_read(snes, t + 1) << 8;
if (0)printf("DMA: 0x%x -> 0x%x (ppu 0x%x), 0x%x bytes, data 0x%x\n",
t, snes->dma->channel[1].bAdr,
snes->ppu->vramPointer, snes->dma->channel[1].size, data);
}
dma_startDma(snes->dma, val, false);
while (dma_cycle(snes->dma)) {}
break;
}
case 0x420c: {
dma_startDma(snes->dma, val, true);
break;
}
case 0x420d: {
snes->fastMem = val & 0x1;
break;
}
default: {
break;
}
}
}
uint8_t snes_read(Snes* snes, uint32_t adr) {
uint8_t bank = adr >> 16;
adr &= 0xffff;
if(bank == 0x7e || bank == 0x7f) {
return snes->ram[((bank & 1) << 16) | adr]; // ram
}
if(bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
if(adr < 0x2000) {
return snes->ram[adr]; // ram mirror
}
if(adr >= 0x2100 && adr < 0x2200) {
return snes_readBBus(snes, adr & 0xff); // B-bus
}
if (adr == 0x4016 || adr == 0x4017) {
assert(0);
}
if(adr >= 0x4200 && adr < 0x4220 || adr >= 0x4218 && adr < 0x4220) {
return snes_readReg(snes, adr); // internal registers
}
if(adr >= 0x4300 && adr < 0x4380) {
return dma_read(snes->dma, adr); // dma registers
}
}
// read from cart
return cart_read(snes->cart, bank, adr);
}
void LogWrite(Snes *snes, uint32_t adr, uint8_t val) {
printf("@%d: Write to 0x%x = 0x%.2x: 0x%x: 0x%x: x = 0x%x, y = 0x%x, c = %d\n",
snes_frame_counter, adr, val, snes->cpu->k << 16 | snes->cpu->pc,
snes->ram[0x12] | snes->ram[0x13] << 8, snes->cpu->x, snes->cpu->y, snes->cpu->c);
}
void snes_write(Snes* snes, uint32_t adr, uint8_t val) {
uint8_t bank = adr >> 16;
adr &= 0xffff;
if(bank == 0x7e || bank == 0x7f) {
uint32_t addr = ((bank & 1) << 16) | adr;
snes->ram[addr] = val; // ram
if (IS_ADR(addr)) {
LogWrite(snes, adr, val);
}
}
if(bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
if(adr < 0x2000) {
snes->ram[adr] = val; // ram mirror
if (IS_ADR(adr)) {
LogWrite(snes, adr, val);
}
}
if(adr >= 0x2100 && adr < 0x2200) {
snes_writeBBus(snes, adr & 0xff, val); // B-bus
}
if(adr >= 0x4200 && adr < 0x4220) {
snes_writeReg(snes, adr, val); // internal registers
}
if(adr >= 0x4300 && adr < 0x4380) {
dma_write(snes->dma, adr, val); // dma registers
}
}
// write to cart
cart_write(snes->cart, bank, adr, val);
}
uint8_t snes_cpuRead(Snes* snes, uint32_t adr) {
snes->cpuMemOps++;
snes->cpuCyclesLeft += 8;
return snes_read(snes, adr);
}
void snes_cpuWrite(Snes* snes, uint32_t adr, uint8_t val) {
snes->cpuMemOps++;
snes->cpuCyclesLeft += 8;
snes_write(snes, adr, val);
}