ref: d44f55b337b4b6e6e00ef698f656e17655694d24
dir: /snes/snes.c/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <stddef.h>
#include "saveload.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"
#include "snes_regs.h"
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
static void snes_runCpu(Snes* snes);
static void snes_catchupApu(Snes* snes);
static uint8_t snes_readReg(Snes* snes, uint16_t adr);
static void snes_writeReg(Snes* snes, uint16_t adr, uint8_t val);
static uint8_t snes_rread(Snes* snes, uint32_t adr); // wrapped by read, to set open bus
static int snes_getAccessTime(Snes* snes, uint32_t adr);
Snes* snes_init(uint8_t *ram) {
Snes* snes = (Snes * )malloc(sizeof(Snes));
snes->ram = ram;
snes->cpu = cpu_init(snes, 0);
snes->apu = apu_init();
snes->dma = dma_init(snes);
snes->ppu = ppu_init(snes);
snes->cart = cart_init(snes);
snes->input1 = input_init(snes);
snes->input2 = input_init(snes);
snes->debug_cycles = false;
snes->disableHpos = false;
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->disableHpos = false;
}
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->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->portAutoRead, 0, sizeof(snes->portAutoRead));
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;
}
static uint8_t g_last_module;
void snes_printCpuLine(Snes *snes) {
if (snes->debug_cycles) {
static FILE *fout;
if (!fout)
fout = stdout;
char line[80];
getProcessorStateCpu(snes, line);
fputs(line, fout);
fprintf(fout, " 0x%x", snes->ppu->vram[0]);
fputs("\n", fout);
fflush(fout);
}
}
static void snes_runCpu(Snes* snes) {
if(snes->cpuCyclesLeft == 0) {
snes->cpuMemOps = 0;
uint32_t pc = snes->cpu->pc | snes->cpu->k << 16;
if (snes->debug_cycles) {
char line[80];
getProcessorStateCpu(snes, line);
puts(line);
}
int cycles = cpu_runOpcode(snes->cpu);
snes->cpuCyclesLeft += (cycles - snes->cpuMemOps) * 6;
}
snes->cpuCyclesLeft -= 2;
}
static void snes_catchupApu(Snes* snes) {
int catchupCycles = (int) snes->apuCatchupCycles;
for(int i = 0; i < catchupCycles; i++) {
apu_cycle(snes->apu);
}
snes->apuCatchupCycles -= (double) catchupCycles;
}
void snes_doAutoJoypad(Snes* snes) {
// TODO: improve? (now calls input_cycle)
memset(snes->portAutoRead, 0, sizeof(snes->portAutoRead));
snes->input1->latchLine = true;
snes->input2->latchLine = true;
input_cycle(snes->input1); // latches the controllers
input_cycle(snes->input2);
snes->input1->latchLine = false;
snes->input2->latchLine = false;
for(int i = 0; i < 16; i++) {
uint8_t val = input_read(snes->input1);
snes->portAutoRead[0] |= ((val & 1) << (15 - i));
snes->portAutoRead[2] |= (((val >> 1) & 1) << (15 - i));
val = input_read(snes->input2);
snes->portAutoRead[1] |= ((val & 1) << (15 - i));
snes->portAutoRead[3] |= (((val >> 1) & 1) << (15 - i));
}
}
uint8_t snes_readBBus(Snes* snes, uint8_t adr) {
if(adr < 0x40) {
return ppu_read(snes->ppu, adr);
}
if(adr < 0x80) {
//apu_cycle(snes->apu);//spc_runOpcode(snes->apu->spc);
return snes->apu->outPorts[adr & 0x3];
}
if(adr == 0x80) {
uint8_t ret = snes->ram[snes->ramAdr++];
snes->ramAdr &= 0x1ffff;
return ret;
}
return snes->openBus;
}
void snes_writeBBus(Snes* snes, uint8_t adr, uint8_t val) {
if(adr < 0x40) {
ppu_write(snes->ppu, adr, val);
return;
}
if(adr < 0x80) {
snes_catchupApu(snes); // catch up the apu before writing
snes->apu->inPorts[adr & 0x3] = 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 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;
snes->inNmi = false;
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:
case 0x421a:
case 0x421c:
case 0x421e: {
return snes->portAutoRead[(adr - 0x4218) / 2] & 0xff;
}
case 0x4219:
case 0x421b:
case 0x421d:
case 0x421f: {
return snes->portAutoRead[(adr - 0x4219) / 2] >> 8;
}
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: {
dma_startDma(snes->dma, val, false);
break;
}
case 0x420c: {
dma_startDma(snes->dma, val, true);
break;
}
case 0x420d: {
snes->fastMem = val & 0x1;
break;
}
default: {
break;
}
}
}
static uint8_t snes_rread(Snes* snes, uint32_t adr) {
uint8_t bank = adr >> 16;
adr &= 0xffff;
if((bank & 0x7f) < 0x40 && adr < 0x4380) {
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) {
return input_read(snes->input1) | (snes->openBus & 0xfc);
}
if(adr == 0x4017) {
return input_read(snes->input2) | (snes->openBus & 0xe0) | 0x1c;
}
if(adr >= 0x4200 && adr < 0x4220) {
return snes_readReg(snes, adr); // internal registers
}
if(adr >= 0x4300 && adr < 0x4380) {
return dma_read(snes->dma, adr); // dma registers
}
} else if ((bank & ~1) == 0x7e) {
return snes->ram[((bank & 1) << 16) | adr]; // ram
}
// read from cart
return cart_read(snes->cart, bank, adr);
}
int g_bp_addr = 0;
void snes_write(Snes* snes, uint32_t adr, uint8_t val) {
snes->openBus = val;
uint8_t bank = adr >> 16;
adr &= 0xffff;
if(bank == 0x7e || bank == 0x7f) {
if ((adr & 0xffff) == g_bp_addr && g_bp_addr) {
printf("@0x%x: Writing1 0x%X to 0x%x (frame %d) %.2x %.2x %.2x\n", snes->cpu->k * 65536 + snes->cpu->pc, val, adr & 0xffff, snes->ram[0x1a],
snes->ram[snes->cpu->sp+1], snes->ram[snes->cpu->sp+2], snes->ram[snes->cpu->sp+3]);
}
snes->ram[((bank & 1) << 16) | adr] = val; // ram
} else if(bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
if (adr < 0x2000) {
if ((adr & 0xffff) == g_bp_addr && g_bp_addr) {
printf("@0x%x: Writing2 0x%X to 0x%x (frame %d) %.2x %.2x %.2x\n", snes->cpu->k * 65536 + snes->cpu->pc, val, adr & 0xffff, snes->ram[0x1a],
snes->ram[snes->cpu->sp+1], snes->ram[snes->cpu->sp+2], snes->ram[snes->cpu->sp+3]);
}
snes->ram[adr] = val; // ram mirror
} else if(adr >= 0x2100 && adr < 0x2200) {
snes_writeBBus(snes, adr & 0xff, val); // B-bus
} else if(adr == 0x4016) {
snes->input1->latchLine = val & 1;
snes->input2->latchLine = val & 1;
} else if(adr >= 0x4200 && adr < 0x4220) {
snes_writeReg(snes, adr, val); // internal registers
} else if(adr >= 0x4300 && adr < 0x4380) {
dma_write(snes->dma, adr, val); // dma registers
}
}
cart_write(snes->cart, bank, adr, val);
// write to cart
}
static int snes_getAccessTime(Snes* snes, uint32_t adr) {
// optimization
return 6;
}
uint8_t snes_read(Snes* snes, uint32_t adr) {
uint8_t val = snes_rread(snes, adr);
snes->openBus = val;
return val;
}
uint8_t snes_cpuRead(Snes* snes, uint32_t adr) {
snes->cpuMemOps++;
snes->cpuCyclesLeft += snes_getAccessTime(snes, adr);
return snes_read(snes, adr);
}
void snes_cpuWrite(Snes* snes, uint32_t adr, uint8_t val) {
snes->cpuMemOps++;
snes->cpuCyclesLeft += snes_getAccessTime(snes, adr);
snes_write(snes, adr, val);
}
// debugging