Initial commit

This commit is contained in:
snesrev
2023-03-05 07:03:54 +01:00
committed by Snesrev
commit 09c1bdd874
100 changed files with 142957 additions and 0 deletions

194
src/snes/apu.c Normal file
View File

@@ -0,0 +1,194 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "apu.h"
#include "snes.h"
#include "spc.h"
#include "dsp.h"
#include "../tracing.h"
static const uint8_t bootRom[0x40] = {
0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa, 0xf4, 0x8f, 0xbb, 0xf5, 0x78,
0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19, 0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5,
0xcb, 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e, 0xf4, 0x10, 0xeb, 0xba,
0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4, 0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff
};
Apu* apu_init(void) {
Apu* apu = malloc(sizeof(Apu));
apu->spc = spc_init(apu);
apu->dsp = dsp_init(apu->ram);
return apu;
}
void apu_free(Apu* apu) {
spc_free(apu->spc);
dsp_free(apu->dsp);
free(apu);
}
void apu_reset(Apu* apu) {
apu->romReadable = true; // before resetting spc, because it reads reset vector from it
spc_reset(apu->spc);
dsp_reset(apu->dsp);
memset(apu->ram, 0, sizeof(apu->ram));
apu->dspAdr = 0;
apu->cycles = 0;
memset(apu->inPorts, 0, sizeof(apu->inPorts));
memset(apu->outPorts, 0, sizeof(apu->outPorts));
for(int i = 0; i < 3; i++) {
apu->timer[i].cycles = 0;
apu->timer[i].divider = 0;
apu->timer[i].target = 0;
apu->timer[i].counter = 0;
apu->timer[i].enabled = false;
}
apu->cpuCyclesLeft = 7;
apu->hist.count = 0;
}
void apu_saveload(Apu *apu, SaveLoadFunc *func, void *ctx) {
func(ctx, apu->ram, offsetof(Apu, hist) - offsetof(Apu, ram));
dsp_saveload(apu->dsp, func, ctx);
spc_saveload(apu->spc, func, ctx);
}
bool g_debug_apu_cycles;
void apu_cycle(Apu* apu) {
if(apu->cpuCyclesLeft == 0) {
if (g_debug_apu_cycles) {
char line[80];
getProcessorStateSpc(apu, line);
puts(line);
}
apu->cpuCyclesLeft = spc_runOpcode(apu->spc);
}
apu->cpuCyclesLeft--;
if((apu->cycles & 0x1f) == 0) {
// every 32 cycles
dsp_cycle(apu->dsp);
}
// handle timers
for(int i = 0; i < 3; i++) {
if(apu->timer[i].cycles == 0) {
apu->timer[i].cycles = i == 2 ? 16 : 128;
if(apu->timer[i].enabled) {
apu->timer[i].divider++;
if(apu->timer[i].divider == apu->timer[i].target) {
apu->timer[i].divider = 0;
apu->timer[i].counter++;
apu->timer[i].counter &= 0xf;
}
}
}
apu->timer[i].cycles--;
}
apu->cycles++;
}
uint8_t apu_cpuRead(Apu* apu, uint16_t adr) {
switch(adr) {
case 0xf0:
case 0xf1:
case 0xfa:
case 0xfb:
case 0xfc: {
return 0;
}
case 0xf2: {
return apu->dspAdr;
}
case 0xf3: {
return dsp_read(apu->dsp, apu->dspAdr & 0x7f);
}
case 0xf4:
case 0xf5:
case 0xf6:
case 0xf7:
case 0xf8:
case 0xf9: {
return apu->inPorts[adr - 0xf4];
}
case 0xfd:
case 0xfe:
case 0xff: {
uint8_t ret = apu->timer[adr - 0xfd].counter;
apu->timer[adr - 0xfd].counter = 0;
return ret;
}
}
if(apu->romReadable && adr >= 0xffc0) {
return bootRom[adr - 0xffc0];
}
return apu->ram[adr];
}
void apu_cpuWrite(Apu* apu, uint16_t adr, uint8_t val) {
switch(adr) {
case 0xf0: {
break; // test register
}
case 0xf1: {
for(int i = 0; i < 3; i++) {
if(!apu->timer[i].enabled && (val & (1 << i))) {
apu->timer[i].divider = 0;
apu->timer[i].counter = 0;
}
apu->timer[i].enabled = val & (1 << i);
}
if(val & 0x10) {
apu->inPorts[0] = 0;
apu->inPorts[1] = 0;
}
if(val & 0x20) {
apu->inPorts[2] = 0;
apu->inPorts[3] = 0;
}
apu->romReadable = val & 0x80;
break;
}
case 0xf2: {
apu->dspAdr = val;
break;
}
case 0xf3: {
int i = apu->hist.count;
if (i != 256) {
apu->hist.count = i + 1;
apu->hist.addr[i] = (uint8_t)apu->dspAdr;
apu->hist.val[i] = val;
}
if(apu->dspAdr < 0x80) dsp_write(apu->dsp, apu->dspAdr, val);
break;
}
case 0xf4:
case 0xf5:
case 0xf6:
case 0xf7: {
apu->outPorts[adr - 0xf4] = val;
break;
}
case 0xf8:
case 0xf9: {
apu->inPorts[adr - 0xf4] = val;
break;
}
case 0xfa:
case 0xfb:
case 0xfc: {
apu->timer[adr - 0xfa].target = val;
break;
}
}
apu->ram[adr] = val;
}

50
src/snes/apu.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef APU_H
#define APU_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Apu Apu;
#include "snes.h"
#include "spc.h"
#include "dsp.h"
typedef struct Timer {
uint8_t cycles;
uint8_t divider;
uint8_t target;
uint8_t counter;
bool enabled;
} Timer;
struct Apu {
Spc* spc;
Dsp* dsp;
uint8_t ram[0x10000];
bool romReadable;
uint8_t dspAdr;
uint32_t cycles;
uint8_t inPorts[6]; // includes 2 bytes of ram
uint8_t outPorts[4];
Timer timer[3];
uint8_t cpuCyclesLeft;
union {
struct DspRegWriteHistory hist;
void *padpad;
};
};
Apu* apu_init();
void apu_free(Apu* apu);
void apu_reset(Apu* apu);
void apu_cycle(Apu* apu);
uint8_t apu_cpuRead(Apu* apu, uint16_t adr);
void apu_cpuWrite(Apu* apu, uint16_t adr, uint8_t val);
void apu_saveload(Apu *apu, SaveLoadFunc *func, void *ctx);
#endif

116
src/snes/cart.c Normal file
View File

@@ -0,0 +1,116 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include "cart.h"
#include "snes.h"
static uint8_t cart_readLorom(Cart* cart, uint8_t bank, uint16_t adr);
static void cart_writeLorom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val);
static uint8_t cart_readHirom(Cart* cart, uint8_t bank, uint16_t adr);
static void cart_writeHirom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val);
Cart* cart_init(Snes* snes) {
Cart* cart = malloc(sizeof(Cart));
cart->snes = snes;
cart->type = 0;
cart->rom = NULL;
cart->romSize = 0;
cart->ram = NULL;
cart->ramSize = 0;
return cart;
}
void cart_free(Cart* cart) {
free(cart);
}
void cart_reset(Cart* cart) {
//if(cart->ramSize > 0 && cart->ram != NULL) memset(cart->ram, 0, cart->ramSize); // for now
}
void cart_saveload(Cart *cart, SaveLoadFunc *func, void *ctx) {
func(ctx, cart->ram, cart->ramSize);
}
void cart_load(Cart* cart, int type, uint8_t* rom, int romSize, int ramSize) {
cart->type = type;
if(cart->rom != NULL) free(cart->rom);
if(cart->ram != NULL) free(cart->ram);
cart->rom = malloc(romSize);
cart->romSize = romSize;
if(ramSize > 0) {
cart->ram = malloc(ramSize);
memset(cart->ram, 0, ramSize);
} else {
cart->ram = NULL;
}
cart->ramSize = ramSize;
memcpy(cart->rom, rom, romSize);
}
uint8_t cart_read(Cart* cart, uint8_t bank, uint16_t adr) {
switch(cart->type) {
case 0:
assert(0);
return cart->snes->openBus;
case 1: return cart_readLorom(cart, bank, adr);
case 2: return cart_readHirom(cart, bank, adr);
}
assert(0);
return cart->snes->openBus;
}
void cart_write(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val) {
switch(cart->type) {
case 0: break;
case 1: cart_writeLorom(cart, bank, adr, val); break;
case 2: cart_writeHirom(cart, bank, adr, val); break;
}
}
static uint8_t cart_readLorom(Cart* cart, uint8_t bank, uint16_t adr) {
if(((bank >= 0x70 && bank < 0x7e) || bank >= 0xf0) && adr < 0x8000 && cart->ramSize > 0) {
// banks 70-7e and f0-ff, adr 0000-7fff
return cart->ram[(((bank & 0xf) << 15) | adr) & (cart->ramSize - 1)];
}
bank &= 0x7f;
if(adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return cart->rom[((bank << 15) | (adr & 0x7fff)) & (cart->romSize - 1)];
}
assert(0);
return cart->snes->openBus;
}
static void cart_writeLorom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val) {
if(((bank >= 0x70 && bank < 0x7e) || bank > 0xf0) && adr < 0x8000 && cart->ramSize > 0) {
// banks 70-7e and f0-ff, adr 0000-7fff
cart->ram[(((bank & 0xf) << 15) | adr) & (cart->ramSize - 1)] = val;
}
}
static uint8_t cart_readHirom(Cart* cart, uint8_t bank, uint16_t adr) {
bank &= 0x7f;
if(bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && cart->ramSize > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
return cart->ram[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (cart->ramSize - 1)];
}
if(adr >= 0x8000 || bank >= 0x40) {
// adr 8000-ffff in all banks or all addresses in banks 40-7f and c0-ff
return cart->rom[(((bank & 0x3f) << 16) | adr) & (cart->romSize - 1)];
}
assert(0);
return cart->snes->openBus;
}
static void cart_writeHirom(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val) {
bank &= 0x7f;
if(bank < 0x40 && adr >= 0x6000 && adr < 0x8000 && cart->ramSize > 0) {
// banks 00-3f and 80-bf, adr 6000-7fff
cart->ram[(((bank & 0x3f) << 13) | (adr & 0x1fff)) & (cart->ramSize - 1)] = val;
}
}

34
src/snes/cart.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef CART_H
#define CART_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Cart Cart;
#include "snes.h"
struct Cart {
Snes* snes;
uint8_t type;
uint8_t* rom;
uint32_t romSize;
uint8_t* ram;
uint32_t ramSize;
};
// TODO: how to handle reset & load? (especially where to init ram)
Cart* cart_init(Snes* snes);
void cart_free(Cart* cart);
void cart_reset(Cart* cart); // will reset special chips etc, general reading is set up in load
void cart_load(Cart* cart, int type, uint8_t* rom, int romSize, int ramSize); // TODO: figure out how to handle (battery, cart-chips etc)
uint8_t cart_read(Cart* cart, uint8_t bank, uint16_t adr);
void cart_write(Cart* cart, uint8_t bank, uint16_t adr, uint8_t val);
void cart_saveload(Cart *cart, SaveLoadFunc *func, void *ctx);
#endif

2356
src/snes/cpu.c Normal file

File diff suppressed because it is too large Load Diff

59
src/snes/cpu.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef CPU_H
#define CPU_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "saveload.h"
typedef struct Cpu Cpu;
struct Cpu {
// reference to memory handler, for reading//writing
void* mem;
uint8_t memType; // used to define which type mem is
// registers
uint16_t a;
uint16_t x;
uint16_t y;
uint16_t sp;
uint16_t pc;
uint16_t dp; // direct page (D)
uint8_t k; // program bank (PB)
uint8_t db; // data bank (B)
// flags
bool c;
bool z;
bool v;
bool n;
bool i;
bool d;
bool xf;
bool mf;
bool e;
// interrupts
bool irqWanted;
bool nmiWanted;
// power state (WAI/STP)
bool waiting;
bool stopped;
// internal use
uint8_t cyclesUsed; // indicates how many cycles an opcode used
uint16_t spBreakpoint;
bool in_emu;
};
extern struct Cpu *g_cpu;
bool HookedFunctionRts(int is_long);
Cpu* cpu_init(void* mem, int memType);
void cpu_free(Cpu* cpu);
void cpu_reset(Cpu* cpu);
int cpu_runOpcode(Cpu* cpu);
uint8_t cpu_getFlags(Cpu *cpu);
void cpu_setFlags(Cpu *cpu, uint8_t val);
void cpu_saveload(Cpu *cpu, SaveLoadFunc *func, void *ctx);
#endif

332
src/snes/dma.c Normal file
View File

@@ -0,0 +1,332 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <assert.h>
#include "dma.h"
#include "snes.h"
static const int bAdrOffsets[8][4] = {
{0, 0, 0, 0},
{0, 1, 0, 1},
{0, 0, 0, 0},
{0, 0, 1, 1},
{0, 1, 2, 3},
{0, 1, 0, 1},
{0, 0, 0, 0},
{0, 0, 1, 1}
};
static const int transferLength[8] = {
1, 2, 2, 4, 4, 4, 2, 4
};
static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAdr, bool fromB);
Dma* dma_init(Snes* snes) {
Dma* dma = malloc(sizeof(Dma));
dma->snes = snes;
return dma;
}
void dma_free(Dma* dma) {
free(dma);
}
void dma_reset(Dma* dma) {
for(int i = 0; i < 8; i++) {
dma->channel[i].bAdr = 0xff;
dma->channel[i].aAdr = 0xffff;
dma->channel[i].aBank = 0xff;
dma->channel[i].size = 0xffff;
dma->channel[i].indBank = 0xff;
dma->channel[i].tableAdr = 0xffff;
dma->channel[i].repCount = 0xff;
dma->channel[i].unusedByte = 0xff;
dma->channel[i].dmaActive = false;
dma->channel[i].hdmaActive = false;
dma->channel[i].mode = 7;
dma->channel[i].fixed = true;
dma->channel[i].decrement = true;
dma->channel[i].indirect = true;
dma->channel[i].fromB = true;
dma->channel[i].unusedBit = true;
dma->channel[i].doTransfer = false;
dma->channel[i].terminated = false;
dma->channel[i].offIndex = 0;
}
dma->hdmaTimer = 0;
dma->dmaTimer = 0;
dma->dmaBusy = false;
}
void dma_saveload(Dma *dma, SaveLoadFunc *func, void *ctx) {
func(ctx, &dma->channel, sizeof(Dma) - offsetof(Dma, channel));
}
uint8_t dma_read(Dma* dma, uint16_t adr) {
uint8_t c = (adr & 0x70) >> 4;
switch(adr & 0xf) {
case 0x0: {
uint8_t val = dma->channel[c].mode;
val |= dma->channel[c].fixed << 3;
val |= dma->channel[c].decrement << 4;
val |= dma->channel[c].unusedBit << 5;
val |= dma->channel[c].indirect << 6;
val |= dma->channel[c].fromB << 7;
return val;
}
case 0x1: {
return dma->channel[c].bAdr;
}
case 0x2: {
return dma->channel[c].aAdr & 0xff;
}
case 0x3: {
return dma->channel[c].aAdr >> 8;
}
case 0x4: {
return dma->channel[c].aBank;
}
case 0x5: {
return dma->channel[c].size & 0xff;
}
case 0x6: {
return dma->channel[c].size >> 8;
}
case 0x7: {
return dma->channel[c].indBank;
}
case 0x8: {
return dma->channel[c].tableAdr & 0xff;
}
case 0x9: {
return dma->channel[c].tableAdr >> 8;
}
case 0xa: {
return dma->channel[c].repCount;
}
case 0xb:
case 0xf: {
return dma->channel[c].unusedByte;
}
default: {
assert(0);
return dma->snes->openBus;
}
}
}
void dma_write(Dma* dma, uint16_t adr, uint8_t val) {
uint8_t c = (adr & 0x70) >> 4;
switch(adr & 0xf) {
case 0x0: {
dma->channel[c].mode = val & 0x7;
dma->channel[c].fixed = val & 0x8;
dma->channel[c].decrement = val & 0x10;
dma->channel[c].unusedBit = val & 0x20;
dma->channel[c].indirect = val & 0x40;
dma->channel[c].fromB = val & 0x80;
break;
}
case 0x1: {
dma->channel[c].bAdr = val;
break;
}
case 0x2: {
dma->channel[c].aAdr = (dma->channel[c].aAdr & 0xff00) | val;
break;
}
case 0x3: {
dma->channel[c].aAdr = (dma->channel[c].aAdr & 0xff) | (val << 8);
break;
}
case 0x4: {
dma->channel[c].aBank = val;
break;
}
case 0x5: {
dma->channel[c].size = (dma->channel[c].size & 0xff00) | val;
break;
}
case 0x6: {
dma->channel[c].size = (dma->channel[c].size & 0xff) | (val << 8);
break;
}
case 0x7: {
dma->channel[c].indBank = val;
break;
}
case 0x8: {
dma->channel[c].tableAdr = (dma->channel[c].tableAdr & 0xff00) | val;
break;
}
case 0x9: {
dma->channel[c].tableAdr = (dma->channel[c].tableAdr & 0xff) | (val << 8);
break;
}
case 0xa: {
dma->channel[c].repCount = val;
break;
}
case 0xb:
case 0xf: {
dma->channel[c].unusedByte = val;
break;
}
default: {
break;
}
}
}
void dma_doDma(Dma* dma) {
if(dma->dmaTimer > 0) {
dma->dmaTimer -= 2;
return;
}
// figure out first channel that is active
int i = 0;
for(i = 0; i < 8; i++) {
if(dma->channel[i].dmaActive) {
break;
}
}
if(i == 8) {
// no active channels
dma->dmaBusy = false;
return;
}
// do channel i
dma_transferByte(
dma, dma->channel[i].aAdr, dma->channel[i].aBank,
dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][dma->channel[i].offIndex++], dma->channel[i].fromB
);
dma->channel[i].offIndex &= 3;
dma->dmaTimer += 6; // 8 cycles for each byte taken, -2 for this cycle
if(!dma->channel[i].fixed) {
dma->channel[i].aAdr += dma->channel[i].decrement ? -1 : 1;
}
dma->channel[i].size--;
if(dma->channel[i].size == 0) {
dma->channel[i].offIndex = 0; // reset offset index
dma->channel[i].dmaActive = false;
dma->dmaTimer += 8; // 8 cycle overhead per channel
}
}
void dma_initHdma(Dma* dma) {
dma->hdmaTimer = 0;
bool hdmaHappened = false;
for(int i = 0; i < 8; i++) {
if(dma->channel[i].hdmaActive) {
hdmaHappened = true;
// terminate any dma
dma->channel[i].dmaActive = false;
dma->channel[i].offIndex = 0;
// load address, repCount, and indirect address if needed
dma->channel[i].tableAdr = dma->channel[i].aAdr;
dma->channel[i].repCount = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++);
dma->hdmaTimer += 8; // 8 cycle overhead for each active channel
if(dma->channel[i].indirect) {
dma->channel[i].size = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++);
dma->channel[i].size |= snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++) << 8;
dma->hdmaTimer += 16; // another 16 cycles for indirect (total 24)
}
dma->channel[i].doTransfer = true;
} else {
dma->channel[i].doTransfer = false;
}
dma->channel[i].terminated = false;
}
if(hdmaHappened) dma->hdmaTimer += 16; // 18 cycles overhead, -2 for this cycle
}
void dma_doHdma(Dma* dma) {
dma->hdmaTimer = 0;
bool hdmaHappened = false;
for(int i = 0; i < 8; i++) {
if(dma->channel[i].hdmaActive && !dma->channel[i].terminated) {
// printf("DMA %d: 0x%x\n", i, dma->channel[i].bAdr);
hdmaHappened = true;
// terminate any dma
dma->channel[i].dmaActive = false;
dma->channel[i].offIndex = 0;
// do the hdma
dma->hdmaTimer += 8; // 8 cycles overhead for each active channel
if(dma->channel[i].doTransfer) {
for(int j = 0; j < transferLength[dma->channel[i].mode]; j++) {
dma->hdmaTimer += 8; // 8 cycles for each byte transferred
if(dma->channel[i].indirect) {
dma_transferByte(
dma, dma->channel[i].size++, dma->channel[i].indBank,
dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][j], dma->channel[i].fromB
);
} else {
dma_transferByte(
dma, dma->channel[i].tableAdr++, dma->channel[i].aBank,
dma->channel[i].bAdr + bAdrOffsets[dma->channel[i].mode][j], dma->channel[i].fromB
);
}
}
}
dma->channel[i].repCount--;
dma->channel[i].doTransfer = dma->channel[i].repCount & 0x80;
if((dma->channel[i].repCount & 0x7f) == 0) {
dma->channel[i].repCount = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++);
if(dma->channel[i].indirect) {
// TODO: oddness with not fetching high byte if last active channel and reCount is 0
dma->channel[i].size = snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++);
dma->channel[i].size |= snes_read(dma->snes, (dma->channel[i].aBank << 16) | dma->channel[i].tableAdr++) << 8;
dma->hdmaTimer += 16; // 16 cycles for new indirect address
}
if(dma->channel[i].repCount == 0) dma->channel[i].terminated = true;
dma->channel[i].doTransfer = true;
}
}
}
if(hdmaHappened) dma->hdmaTimer += 16; // 18 cycles overhead, -2 for this cycle
}
static void dma_transferByte(Dma* dma, uint16_t aAdr, uint8_t aBank, uint8_t bAdr, bool fromB) {
// TODO: invalid writes:
// accesing b-bus via a-bus gives open bus,
// $2180-$2183 while accessing ram via a-bus open busses $2180-$2183
// cannot access $4300-$437f (dma regs), or $420b / $420c
if(fromB) {
snes_write(dma->snes, (aBank << 16) | aAdr, snes_readBBus(dma->snes, bAdr));
} else {
snes_writeBBus(dma->snes, bAdr, snes_read(dma->snes, (aBank << 16) | aAdr));
}
}
bool dma_cycle(Dma* dma) {
if(dma->hdmaTimer > 0) {
dma->hdmaTimer -= 2;
return true;
} else if(dma->dmaBusy) {
dma_doDma(dma);
return true;
}
return false;
}
void dma_startDma(Dma* dma, uint8_t val, bool hdma) {
for(int i = 0; i < 8; i++) {
if(hdma) {
dma->channel[i].hdmaActive = val & (1 << i);
} else {
dma->channel[i].dmaActive = val & (1 << i);
}
}
if(!hdma) {
dma->dmaBusy = val;
dma->dmaTimer += dma->dmaBusy ? 16 : 0; // 12-24 cycle overhead for entire dma transfer
}
}

57
src/snes/dma.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef DMA_H
#define DMA_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Dma Dma;
#include "snes.h"
typedef struct DmaChannel {
uint8_t bAdr;
uint16_t aAdr;
uint8_t aBank;
uint16_t size; // also indirect hdma adr
uint8_t indBank; // hdma
uint16_t tableAdr; // hdma
uint8_t repCount; // hdma
uint8_t unusedByte;
bool dmaActive;
bool hdmaActive;
uint8_t mode;
bool fixed;
bool decrement;
bool indirect; // hdma
bool fromB;
bool unusedBit;
bool doTransfer; // hdma
bool terminated; // hdma
uint8_t offIndex;
} DmaChannel;
struct Dma {
Snes* snes;
DmaChannel channel[8];
uint16_t hdmaTimer;
uint32_t dmaTimer;
bool dmaBusy;
};
Dma* dma_init(Snes* snes);
void dma_free(Dma* dma);
void dma_reset(Dma* dma);
uint8_t dma_read(Dma* dma, uint16_t adr); // 43x0-43xf
void dma_write(Dma* dma, uint16_t adr, uint8_t val); // 43x0-43xf
void dma_doDma(Dma* dma);
void dma_initHdma(Dma* dma);
void dma_doHdma(Dma* dma);
bool dma_cycle(Dma* dma);
void dma_startDma(Dma* dma, uint8_t val, bool hdma);
void dma_saveload(Dma *dma, SaveLoadFunc *func, void *ctx);
#endif

578
src/snes/dsp.c Normal file
View File

@@ -0,0 +1,578 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "dsp.h"
#include "apu.h"
#define MY_CHANGES 1
static const int rateValues[32] = {
0, 2048, 1536, 1280, 1024, 768, 640, 512,
384, 320, 256, 192, 160, 128, 96, 80,
64, 48, 40, 32, 24, 20, 16, 12,
10, 8, 6, 5, 4, 3, 2, 1
};
static const uint16_t gaussValues[512] = {
0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002,
0x002, 0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004, 0x005, 0x005, 0x005, 0x005,
0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007, 0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00A, 0x00A, 0x00A,
0x00B, 0x00B, 0x00B, 0x00C, 0x00C, 0x00D, 0x00D, 0x00E, 0x00E, 0x00F, 0x00F, 0x00F, 0x010, 0x010, 0x011, 0x011,
0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016, 0x017, 0x017, 0x018, 0x018, 0x019, 0x01A, 0x01B, 0x01B,
0x01C, 0x01D, 0x01D, 0x01E, 0x01F, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026, 0x027, 0x028,
0x029, 0x02A, 0x02B, 0x02C, 0x02D, 0x02E, 0x02F, 0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038,
0x03A, 0x03B, 0x03C, 0x03D, 0x03E, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04A, 0x04C, 0x04D,
0x04E, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05A, 0x05C, 0x05E, 0x05F, 0x061, 0x063, 0x064, 0x066,
0x068, 0x06A, 0x06B, 0x06D, 0x06F, 0x071, 0x073, 0x075, 0x076, 0x078, 0x07A, 0x07C, 0x07E, 0x080, 0x082, 0x084,
0x086, 0x089, 0x08B, 0x08D, 0x08F, 0x091, 0x093, 0x096, 0x098, 0x09A, 0x09C, 0x09F, 0x0A1, 0x0A3, 0x0A6, 0x0A8,
0x0AB, 0x0AD, 0x0AF, 0x0B2, 0x0B4, 0x0B7, 0x0BA, 0x0BC, 0x0BF, 0x0C1, 0x0C4, 0x0C7, 0x0C9, 0x0CC, 0x0CF, 0x0D2,
0x0D4, 0x0D7, 0x0DA, 0x0DD, 0x0E0, 0x0E3, 0x0E6, 0x0E9, 0x0EC, 0x0EF, 0x0F2, 0x0F5, 0x0F8, 0x0FB, 0x0FE, 0x101,
0x104, 0x107, 0x10B, 0x10E, 0x111, 0x114, 0x118, 0x11B, 0x11E, 0x122, 0x125, 0x129, 0x12C, 0x130, 0x133, 0x137,
0x13A, 0x13E, 0x141, 0x145, 0x148, 0x14C, 0x150, 0x153, 0x157, 0x15B, 0x15F, 0x162, 0x166, 0x16A, 0x16E, 0x172,
0x176, 0x17A, 0x17D, 0x181, 0x185, 0x189, 0x18D, 0x191, 0x195, 0x19A, 0x19E, 0x1A2, 0x1A6, 0x1AA, 0x1AE, 0x1B2,
0x1B7, 0x1BB, 0x1BF, 0x1C3, 0x1C8, 0x1CC, 0x1D0, 0x1D5, 0x1D9, 0x1DD, 0x1E2, 0x1E6, 0x1EB, 0x1EF, 0x1F3, 0x1F8,
0x1FC, 0x201, 0x205, 0x20A, 0x20F, 0x213, 0x218, 0x21C, 0x221, 0x226, 0x22A, 0x22F, 0x233, 0x238, 0x23D, 0x241,
0x246, 0x24B, 0x250, 0x254, 0x259, 0x25E, 0x263, 0x267, 0x26C, 0x271, 0x276, 0x27B, 0x280, 0x284, 0x289, 0x28E,
0x293, 0x298, 0x29D, 0x2A2, 0x2A6, 0x2AB, 0x2B0, 0x2B5, 0x2BA, 0x2BF, 0x2C4, 0x2C9, 0x2CE, 0x2D3, 0x2D8, 0x2DC,
0x2E1, 0x2E6, 0x2EB, 0x2F0, 0x2F5, 0x2FA, 0x2FF, 0x304, 0x309, 0x30E, 0x313, 0x318, 0x31D, 0x322, 0x326, 0x32B,
0x330, 0x335, 0x33A, 0x33F, 0x344, 0x349, 0x34E, 0x353, 0x357, 0x35C, 0x361, 0x366, 0x36B, 0x370, 0x374, 0x379,
0x37E, 0x383, 0x388, 0x38C, 0x391, 0x396, 0x39B, 0x39F, 0x3A4, 0x3A9, 0x3AD, 0x3B2, 0x3B7, 0x3BB, 0x3C0, 0x3C5,
0x3C9, 0x3CE, 0x3D2, 0x3D7, 0x3DC, 0x3E0, 0x3E5, 0x3E9, 0x3ED, 0x3F2, 0x3F6, 0x3FB, 0x3FF, 0x403, 0x408, 0x40C,
0x410, 0x415, 0x419, 0x41D, 0x421, 0x425, 0x42A, 0x42E, 0x432, 0x436, 0x43A, 0x43E, 0x442, 0x446, 0x44A, 0x44E,
0x452, 0x455, 0x459, 0x45D, 0x461, 0x465, 0x468, 0x46C, 0x470, 0x473, 0x477, 0x47A, 0x47E, 0x481, 0x485, 0x488,
0x48C, 0x48F, 0x492, 0x496, 0x499, 0x49C, 0x49F, 0x4A2, 0x4A6, 0x4A9, 0x4AC, 0x4AF, 0x4B2, 0x4B5, 0x4B7, 0x4BA,
0x4BD, 0x4C0, 0x4C3, 0x4C5, 0x4C8, 0x4CB, 0x4CD, 0x4D0, 0x4D2, 0x4D5, 0x4D7, 0x4D9, 0x4DC, 0x4DE, 0x4E0, 0x4E3,
0x4E5, 0x4E7, 0x4E9, 0x4EB, 0x4ED, 0x4EF, 0x4F1, 0x4F3, 0x4F5, 0x4F6, 0x4F8, 0x4FA, 0x4FB, 0x4FD, 0x4FF, 0x500,
0x502, 0x503, 0x504, 0x506, 0x507, 0x508, 0x50A, 0x50B, 0x50C, 0x50D, 0x50E, 0x50F, 0x510, 0x511, 0x511, 0x512,
0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518, 0x518, 0x518, 0x518, 0x518, 0x519, 0x519
};
static void dsp_cycleChannel(Dsp* dsp, int ch);
static void dsp_handleEcho(Dsp* dsp, int* outputL, int* outputR);
static void dsp_handleGain(Dsp* dsp, int ch);
static void dsp_decodeBrr(Dsp* dsp, int ch);
static int16_t dsp_getSample(Dsp* dsp, int ch, int sampleNum, int offset);
static void dsp_handleNoise(Dsp* dsp);
Dsp* dsp_init(uint8_t *ram) {
Dsp* dsp = malloc(sizeof(Dsp));
dsp->apu_ram = ram;
return dsp;
}
void dsp_free(Dsp* dsp) {
free(dsp);
}
void dsp_reset(Dsp* dsp) {
memset(dsp->ram, 0, sizeof(dsp->ram));
dsp->ram[0x7c] = 0xff; // set ENDx
for(int i = 0; i < 8; i++) {
dsp->channel[i].pitch = 0;
dsp->channel[i].pitchCounter = 0;
dsp->channel[i].pitchModulation = false;
memset(dsp->channel[i].decodeBuffer, 0, sizeof(dsp->channel[i].decodeBuffer));
dsp->channel[i].srcn = 0;
dsp->channel[i].decodeOffset = 0;
dsp->channel[i].previousFlags = 0;
dsp->channel[i].old = 0;
dsp->channel[i].older = 0;
dsp->channel[i].useNoise = false;
memset(dsp->channel[i].adsrRates, 0, sizeof(dsp->channel[i].adsrRates));
dsp->channel[i].rateCounter = 0;
dsp->channel[i].adsrState = 0;
dsp->channel[i].sustainLevel = 0;
dsp->channel[i].useGain = false;
dsp->channel[i].gainMode = 0;
dsp->channel[i].directGain = false;
dsp->channel[i].gainValue = 0;
dsp->channel[i].gain = 0;
dsp->channel[i].keyOn = false;
dsp->channel[i].keyOff = false;
dsp->channel[i].sampleOut = 0;
dsp->channel[i].volumeL = 0;
dsp->channel[i].volumeR = 0;
dsp->channel[i].echoEnable = false;
}
dsp->dirPage = 0;
dsp->evenCycle = false;
dsp->mute = true;
dsp->reset = true;
dsp->masterVolumeL = 0;
dsp->masterVolumeR = 0;
dsp->noiseSample = -0x4000;
dsp->noiseRate = 0;
dsp->noiseCounter = 0;
dsp->echoWrites = false;
dsp->echoVolumeL = 0;
dsp->echoVolumeR = 0;
dsp->feedbackVolume = 0;
dsp->echoBufferAdr = 0;
dsp->echoDelay = 1;
dsp->echoRemain = 1;
dsp->echoBufferIndex = 0;
dsp->firBufferIndex = 0;
memset(dsp->firValues, 0, sizeof(dsp->firValues));
memset(dsp->firBufferL, 0, sizeof(dsp->firBufferL));
memset(dsp->firBufferR, 0, sizeof(dsp->firBufferR));
memset(dsp->sampleBuffer, 0, sizeof(dsp->sampleBuffer));
dsp->sampleOffset = 0;
}
void dsp_saveload(Dsp *dsp, SaveLoadFunc *func, void *ctx) {
func(ctx, &dsp->ram, sizeof(Dsp) - offsetof(Dsp, ram));
}
void dsp_cycle(Dsp* dsp) {
int totalL = 0;
int totalR = 0;
for(int i = 0; i < 8; i++) {
dsp_cycleChannel(dsp, i);
totalL += (dsp->channel[i].sampleOut * dsp->channel[i].volumeL) >> 6;
totalR += (dsp->channel[i].sampleOut * dsp->channel[i].volumeR) >> 6;
totalL = totalL < -0x8000 ? -0x8000 : (totalL > 0x7fff ? 0x7fff : totalL); // clamp 16-bit
totalR = totalR < -0x8000 ? -0x8000 : (totalR > 0x7fff ? 0x7fff : totalR); // clamp 16-bit
}
totalL = (totalL * dsp->masterVolumeL) >> 7;
totalR = (totalR * dsp->masterVolumeR) >> 7;
totalL = totalL < -0x8000 ? -0x8000 : (totalL > 0x7fff ? 0x7fff : totalL); // clamp 16-bit
totalR = totalR < -0x8000 ? -0x8000 : (totalR > 0x7fff ? 0x7fff : totalR); // clamp 16-bit
dsp_handleEcho(dsp, &totalL, &totalR);
if(dsp->mute) {
totalL = 0;
totalR = 0;
}
dsp_handleNoise(dsp);
// put it in the samplebuffer
if (dsp->sampleOffset < 534) {
dsp->sampleBuffer[dsp->sampleOffset * 2] = totalL;
dsp->sampleBuffer[dsp->sampleOffset * 2 + 1] = totalR;
// prevent sampleOffset from going above 534-1 (out of sampleBuffer bounds)
dsp->sampleOffset++;
}
dsp->evenCycle = !dsp->evenCycle;
}
static void dsp_handleEcho(Dsp* dsp, int* outputL, int* outputR) {
// get value out of ram
uint16_t adr = dsp->echoBufferAdr + dsp->echoBufferIndex * 4;
dsp->firBufferL[dsp->firBufferIndex] = (
dsp->apu_ram[adr] + (dsp->apu_ram[(adr + 1) & 0xffff] << 8)
);
dsp->firBufferL[dsp->firBufferIndex] >>= 1;
dsp->firBufferR[dsp->firBufferIndex] = (
dsp->apu_ram[(adr + 2) & 0xffff] + (dsp->apu_ram[(adr + 3) & 0xffff] << 8)
);
dsp->firBufferR[dsp->firBufferIndex] >>= 1;
// calculate FIR-sum
int sumL = 0, sumR = 0;
for(int i = 0; i < 8; i++) {
sumL += (dsp->firBufferL[(dsp->firBufferIndex + i + 1) & 0x7] * dsp->firValues[i]) >> 6;
sumR += (dsp->firBufferR[(dsp->firBufferIndex + i + 1) & 0x7] * dsp->firValues[i]) >> 6;
if(i == 6) {
// clip to 16-bit before last addition
sumL = ((int16_t) (sumL & 0xffff)); // clip 16-bit
sumR = ((int16_t) (sumR & 0xffff)); // clip 16-bit
}
}
sumL = sumL < -0x8000 ? -0x8000 : (sumL > 0x7fff ? 0x7fff : sumL); // clamp 16-bit
sumR = sumR < -0x8000 ? -0x8000 : (sumR > 0x7fff ? 0x7fff : sumR); // clamp 16-bit
// modify output with sum
int outL = *outputL + ((sumL * dsp->echoVolumeL) >> 7);
int outR = *outputR + ((sumR * dsp->echoVolumeR) >> 7);
*outputL = outL < -0x8000 ? -0x8000 : (outL > 0x7fff ? 0x7fff : outL); // clamp 16-bit
*outputR = outR < -0x8000 ? -0x8000 : (outR > 0x7fff ? 0x7fff : outR); // clamp 16-bit
// get echo input
int inL = 0, inR = 0;
for(int i = 0; i < 8; i++) {
if(dsp->channel[i].echoEnable) {
inL += (dsp->channel[i].sampleOut * dsp->channel[i].volumeL) >> 6;
inR += (dsp->channel[i].sampleOut * dsp->channel[i].volumeR) >> 6;
inL = inL < -0x8000 ? -0x8000 : (inL > 0x7fff ? 0x7fff : inL); // clamp 16-bit
inR = inR < -0x8000 ? -0x8000 : (inR > 0x7fff ? 0x7fff : inR); // clamp 16-bit
}
}
// write this to ram
inL += (sumL * dsp->feedbackVolume) >> 7;
inR += (sumR * dsp->feedbackVolume) >> 7;
inL = inL < -0x8000 ? -0x8000 : (inL > 0x7fff ? 0x7fff : inL); // clamp 16-bit
inR = inR < -0x8000 ? -0x8000 : (inR > 0x7fff ? 0x7fff : inR); // clamp 16-bit
inL &= 0xfffe;
inR &= 0xfffe;
if(dsp->echoWrites) {
dsp->apu_ram[adr] = inL & 0xff;
dsp->apu_ram[(adr + 1) & 0xffff] = inL >> 8;
dsp->apu_ram[(adr + 2) & 0xffff] = inR & 0xff;
dsp->apu_ram[(adr + 3) & 0xffff] = inR >> 8;
}
// handle indexes
dsp->firBufferIndex++;
dsp->firBufferIndex &= 7;
dsp->echoBufferIndex++;
dsp->echoRemain--;
if(dsp->echoRemain == 0) {
dsp->echoRemain = dsp->echoDelay;
dsp->echoBufferIndex = 0;
}
}
static void dsp_cycleChannel(Dsp* dsp, int ch) {
// handle pitch counter
uint16_t pitch = dsp->channel[ch].pitch;
if(ch > 0 && dsp->channel[ch].pitchModulation) {
int factor = (dsp->channel[ch - 1].sampleOut >> 4) + 0x400;
pitch = (pitch * factor) >> 10;
if(pitch > 0x3fff) pitch = 0x3fff;
}
int newCounter = dsp->channel[ch].pitchCounter + pitch;
if(newCounter > 0xffff) {
// next sample
dsp_decodeBrr(dsp, ch);
}
dsp->channel[ch].pitchCounter = newCounter;
int16_t sample = 0;
if(dsp->channel[ch].useNoise) {
sample = dsp->noiseSample;
} else {
sample = dsp_getSample(dsp, ch, dsp->channel[ch].pitchCounter >> 12, (dsp->channel[ch].pitchCounter >> 4) & 0xff);
}
#if !MY_CHANGES
if(dsp->evenCycle) {
// handle keyon/off (every other cycle)
if(dsp->channel[ch].keyOff) {
// go to release
dsp->channel[ch].adsrState = 4;
} else if(dsp->channel[ch].keyOn) {
dsp->channel[ch].keyOn = false;
// restart current sample
dsp->channel[ch].previousFlags = 0;
uint16_t samplePointer = dsp->dirPage + 4 * dsp->channel[ch].srcn;
dsp->channel[ch].decodeOffset = dsp->apu_ram[samplePointer];
dsp->channel[ch].decodeOffset |= dsp->apu_ram[(samplePointer + 1) & 0xffff] << 8;
memset(dsp->channel[ch].decodeBuffer, 0, sizeof(dsp->channel[ch].decodeBuffer));
dsp->channel[ch].gain = 0;
dsp->channel[ch].adsrState = dsp->channel[ch].useGain ? 3 : 0;
}
}
#endif
// handle reset
if(dsp->reset) {
dsp->channel[ch].adsrState = 4;
dsp->channel[ch].gain = 0;
}
// handle envelope/adsr
bool doingDirectGain = dsp->channel[ch].adsrState != 4 && dsp->channel[ch].useGain && dsp->channel[ch].directGain;
uint16_t rate = dsp->channel[ch].adsrState == 4 ? 0 : dsp->channel[ch].adsrRates[dsp->channel[ch].adsrState];
if(dsp->channel[ch].adsrState != 4 && !doingDirectGain && rate != 0) {
dsp->channel[ch].rateCounter++;
}
if(dsp->channel[ch].adsrState == 4 || (!doingDirectGain && dsp->channel[ch].rateCounter >= rate && rate != 0)) {
if(dsp->channel[ch].adsrState != 4) dsp->channel[ch].rateCounter = 0;
dsp_handleGain(dsp, ch);
}
if(doingDirectGain) dsp->channel[ch].gain = dsp->channel[ch].gainValue;
// set outputs
dsp->ram[(ch << 4) | 8] = dsp->channel[ch].gain >> 4;
sample = (sample * dsp->channel[ch].gain) >> 11;
dsp->ram[(ch << 4) | 9] = sample >> 7;
dsp->channel[ch].sampleOut = sample;
}
static void dsp_handleGain(Dsp* dsp, int ch) {
switch(dsp->channel[ch].adsrState) {
case 0: { // attack
uint16_t rate = dsp->channel[ch].adsrRates[dsp->channel[ch].adsrState];
dsp->channel[ch].gain += rate == 1 ? 1024 : 32;
if(dsp->channel[ch].gain >= 0x7e0) dsp->channel[ch].adsrState = 1;
if(dsp->channel[ch].gain > 0x7ff) dsp->channel[ch].gain = 0x7ff;
break;
}
case 1: { // decay
dsp->channel[ch].gain -= ((dsp->channel[ch].gain - 1) >> 8) + 1;
if(dsp->channel[ch].gain < dsp->channel[ch].sustainLevel) dsp->channel[ch].adsrState = 2;
break;
}
case 2: { // sustain
dsp->channel[ch].gain -= ((dsp->channel[ch].gain - 1) >> 8) + 1;
break;
}
case 3: { // gain
switch(dsp->channel[ch].gainMode) {
case 0: { // linear decrease
dsp->channel[ch].gain -= 32;
// decreasing below 0 will underflow to above 0x7ff
if(dsp->channel[ch].gain > 0x7ff) dsp->channel[ch].gain = 0;
break;
}
case 1: { // exponential decrease
dsp->channel[ch].gain -= ((dsp->channel[ch].gain - 1) >> 8) + 1;
break;
}
case 2: { // linear increase
dsp->channel[ch].gain += 32;
if(dsp->channel[ch].gain > 0x7ff) dsp->channel[ch].gain = 0x7ff;
break;
}
case 3: { // bent increase
dsp->channel[ch].gain += dsp->channel[ch].gain < 0x600 ? 32 : 8;
if(dsp->channel[ch].gain > 0x7ff) dsp->channel[ch].gain = 0x7ff;
break;
}
}
break;
}
case 4: { // release
dsp->channel[ch].gain -= 8;
// decreasing below 0 will underflow to above 0x7ff
if(dsp->channel[ch].gain > 0x7ff) dsp->channel[ch].gain = 0;
break;
}
}
}
static int16_t dsp_getSample(Dsp* dsp, int ch, int sampleNum, int offset) {
int16_t news = dsp->channel[ch].decodeBuffer[sampleNum + 3];
int16_t olds = dsp->channel[ch].decodeBuffer[sampleNum + 2];
int16_t olders = dsp->channel[ch].decodeBuffer[sampleNum + 1];
int16_t oldests = dsp->channel[ch].decodeBuffer[sampleNum];
int out = (gaussValues[0xff - offset] * oldests) >> 10;
out += (gaussValues[0x1ff - offset] * olders) >> 10;
out += (gaussValues[0x100 + offset] * olds) >> 10;
out = ((int16_t) (out & 0xffff)); // clip 16-bit
out += (gaussValues[offset] * news) >> 10;
out = out < -0x8000 ? -0x8000 : (out > 0x7fff ? 0x7fff : out); // clamp 16-bit
return out >> 1;
}
static void dsp_decodeBrr(Dsp* dsp, int ch) {
// copy last 3 samples (16-18) to first 3 for interpolation
dsp->channel[ch].decodeBuffer[0] = dsp->channel[ch].decodeBuffer[16];
dsp->channel[ch].decodeBuffer[1] = dsp->channel[ch].decodeBuffer[17];
dsp->channel[ch].decodeBuffer[2] = dsp->channel[ch].decodeBuffer[18];
// handle flags from previous block
if(dsp->channel[ch].previousFlags == 1 || dsp->channel[ch].previousFlags == 3) {
// loop sample
uint16_t samplePointer = dsp->dirPage + 4 * dsp->channel[ch].srcn;
dsp->channel[ch].decodeOffset = dsp->apu_ram[(samplePointer + 2) & 0xffff];
dsp->channel[ch].decodeOffset |= (dsp->apu_ram[(samplePointer + 3) & 0xffff]) << 8;
if(dsp->channel[ch].previousFlags == 1) {
// also release and clear gain
dsp->channel[ch].adsrState = 4;
dsp->channel[ch].gain = 0;
}
dsp->ram[0x7c] |= 1 << ch; // set ENDx
}
uint8_t header = dsp->apu_ram[dsp->channel[ch].decodeOffset++];
int shift = header >> 4;
int filter = (header & 0xc) >> 2;
dsp->channel[ch].previousFlags = header & 0x3;
uint8_t curByte = 0;
int old = dsp->channel[ch].old;
int older = dsp->channel[ch].older;
for(int i = 0; i < 16; i++) {
int s = 0;
if(i & 1) {
s = curByte & 0xf;
} else {
curByte = dsp->apu_ram[dsp->channel[ch].decodeOffset++];
s = curByte >> 4;
}
if(s > 7) s -= 16;
if(shift <= 0xc) {
s = (s << shift) >> 1;
} else {
s = (s >> 3) << 12;
}
switch(filter) {
case 1: s += old + (-old >> 4); break;
case 2: s += 2 * old + ((3 * -old) >> 5) - older + (older >> 4); break;
case 3: s += 2 * old + ((13 * -old) >> 6) - older + ((3 * older) >> 4); break;
}
s = s < -0x8000 ? -0x8000 : (s > 0x7fff ? 0x7fff : s); // clamp 16-bit
s = ((int16_t) ((s & 0x7fff) << 1)) >> 1; // clip 15-bit
older = old;
old = s;
dsp->channel[ch].decodeBuffer[i + 3] = s;
}
dsp->channel[ch].older = older;
dsp->channel[ch].old = old;
}
static void dsp_handleNoise(Dsp* dsp) {
if(dsp->noiseRate != 0) {
dsp->noiseCounter++;
}
if(dsp->noiseCounter >= dsp->noiseRate && dsp->noiseRate != 0) {
int bit = (dsp->noiseSample & 1) ^ ((dsp->noiseSample >> 1) & 1);
dsp->noiseSample = ((dsp->noiseSample >> 1) & 0x3fff) | (bit << 14);
dsp->noiseSample = ((int16_t) ((dsp->noiseSample & 0x7fff) << 1)) >> 1;
dsp->noiseCounter = 0;
}
}
uint8_t dsp_read(Dsp* dsp, uint8_t adr) {
return dsp->ram[adr];
}
void dsp_write(Dsp* dsp, uint8_t adr, uint8_t val) {
int ch = adr >> 4;
switch(adr) {
case 0x00: case 0x10: case 0x20: case 0x30: case 0x40: case 0x50: case 0x60: case 0x70: {
dsp->channel[ch].volumeL = val;
break;
}
case 0x01: case 0x11: case 0x21: case 0x31: case 0x41: case 0x51: case 0x61: case 0x71: {
dsp->channel[ch].volumeR = val;
break;
}
case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: case 0x62: case 0x72: {
dsp->channel[ch].pitch = (dsp->channel[ch].pitch & 0x3f00) | val;
break;
}
case 0x03: case 0x13: case 0x23: case 0x33: case 0x43: case 0x53: case 0x63: case 0x73: {
dsp->channel[ch].pitch = ((dsp->channel[ch].pitch & 0x00ff) | (val << 8)) & 0x3fff;
break;
}
case 0x04: case 0x14: case 0x24: case 0x34: case 0x44: case 0x54: case 0x64: case 0x74: {
dsp->channel[ch].srcn = val;
break;
}
case 0x05: case 0x15: case 0x25: case 0x35: case 0x45: case 0x55: case 0x65: case 0x75: {
dsp->channel[ch].adsrRates[0] = rateValues[(val & 0xf) * 2 + 1];
dsp->channel[ch].adsrRates[1] = rateValues[((val & 0x70) >> 4) * 2 + 16];
dsp->channel[ch].useGain = (val & 0x80) == 0;
break;
}
case 0x06: case 0x16: case 0x26: case 0x36: case 0x46: case 0x56: case 0x66: case 0x76: {
dsp->channel[ch].adsrRates[2] = rateValues[val & 0x1f];
dsp->channel[ch].sustainLevel = (((val & 0xe0) >> 5) + 1) * 0x100;
break;
}
case 0x07: case 0x17: case 0x27: case 0x37: case 0x47: case 0x57: case 0x67: case 0x77: {
dsp->channel[ch].directGain = (val & 0x80) == 0;
if(val & 0x80) {
dsp->channel[ch].gainMode = (val & 0x60) >> 5;
dsp->channel[ch].adsrRates[3] = rateValues[val & 0x1f];
} else {
dsp->channel[ch].gainValue = (val & 0x7f) * 16;
}
break;
}
case 0x0c: {
dsp->masterVolumeL = val;
break;
}
case 0x1c: {
dsp->masterVolumeR = val;
break;
}
case 0x2c: {
dsp->echoVolumeL = val;
break;
}
case 0x3c: {
dsp->echoVolumeR = val;
break;
}
case 0x4c: {
for(int ch = 0; ch < 8; ch++) {
dsp->channel[ch].keyOn = val & (1 << ch);
#if MY_CHANGES
if (dsp->channel[ch].keyOn) {
dsp->channel[ch].keyOn = false;
// restart current sample
dsp->channel[ch].previousFlags = 0;
uint16_t samplePointer = dsp->dirPage + 4 * dsp->channel[ch].srcn;
dsp->channel[ch].decodeOffset = dsp->apu_ram[samplePointer];
dsp->channel[ch].decodeOffset |= dsp->apu_ram[(samplePointer + 1) & 0xffff] << 8;
memset(dsp->channel[ch].decodeBuffer, 0, sizeof(dsp->channel[ch].decodeBuffer));
dsp->channel[ch].gain = 0;
dsp->channel[ch].adsrState = dsp->channel[ch].useGain ? 3 : 0;
}
#endif
}
break;
}
case 0x5c: {
for(int ch = 0; ch < 8; ch++) {
dsp->channel[ch].keyOff = val & (1 << ch);
#if MY_CHANGES
if (dsp->channel[ch].keyOff) {
// go to release
dsp->channel[ch].adsrState = 4;
}
#endif
}
break;
}
case 0x6c: {
dsp->reset = val & 0x80;
dsp->mute = val & 0x40;
dsp->echoWrites = (val & 0x20) == 0;
dsp->noiseRate = rateValues[val & 0x1f];
break;
}
case 0x7c: {
val = 0; // any write clears ENDx
break;
}
case 0x0d: {
dsp->feedbackVolume = val;
break;
}
case 0x2d: {
for(int i = 0; i < 8; i++) {
dsp->channel[i].pitchModulation = val & (1 << i);
}
break;
}
case 0x3d: {
for(int i = 0; i < 8; i++) {
dsp->channel[i].useNoise = val & (1 << i);
}
break;
}
case 0x4d: {
for(int i = 0; i < 8; i++) {
dsp->channel[i].echoEnable = val & (1 << i);
}
break;
}
case 0x5d: {
dsp->dirPage = val << 8;
break;
}
case 0x6d: {
dsp->echoBufferAdr = val << 8;
break;
}
case 0x7d: {
dsp->echoDelay = (val & 0xf) * 512; // 2048-byte steps, stereo sample is 4 bytes
if(dsp->echoDelay == 0) dsp->echoDelay = 1;
break;
}
case 0x0f: case 0x1f: case 0x2f: case 0x3f: case 0x4f: case 0x5f: case 0x6f: case 0x7f: {
dsp->firValues[ch] = val;
break;
}
}
dsp->ram[adr] = val;
}
void dsp_getSamples(Dsp* dsp, int16_t* sampleData, int samplesPerFrame) {
// resample from 534 samples per frame to wanted value
double adder = 534.0 / samplesPerFrame;
double location = 0.0;
for(int i = 0; i < samplesPerFrame; i++) {
sampleData[i * 2] = dsp->sampleBuffer[((int) location) * 2];
sampleData[i * 2 + 1] = dsp->sampleBuffer[((int) location) * 2 + 1];
location += adder;
}
dsp->sampleOffset = 0;
}

99
src/snes/dsp.h Normal file
View File

@@ -0,0 +1,99 @@
#ifndef DSP_H
#define DSP_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "saveload.h"
typedef struct Dsp Dsp;
typedef struct Apu Apu;
typedef struct DspChannel {
// pitch
uint16_t pitch;
uint16_t pitchCounter;
bool pitchModulation;
// brr decoding
int16_t decodeBuffer[19]; // 16 samples per brr-block, +3 for interpolation
uint8_t srcn;
uint16_t decodeOffset;
uint8_t previousFlags; // from last sample
int16_t old;
int16_t older;
bool useNoise;
// adsr, envelope, gain
uint16_t adsrRates[4]; // attack, decay, sustain, gain
uint16_t rateCounter;
uint8_t adsrState; // 0: attack, 1: decay, 2: sustain, 3: gain, 4: release
uint16_t sustainLevel;
bool useGain;
uint8_t gainMode;
bool directGain;
uint16_t gainValue; // for direct gain
uint16_t gain;
// keyon/off
bool keyOn;
bool keyOff;
// output
int16_t sampleOut; // final sample, to be multiplied by channel volume
int8_t volumeL;
int8_t volumeR;
bool echoEnable;
} DspChannel;
struct Dsp {
uint8_t *apu_ram;
// mirror ram
uint8_t ram[0x80];
// 8 channels
DspChannel channel[8];
// overarching
uint16_t dirPage;
bool evenCycle;
bool mute;
bool reset;
int8_t masterVolumeL;
int8_t masterVolumeR;
// noise
int16_t noiseSample;
uint16_t noiseRate;
uint16_t noiseCounter;
// echo
bool echoWrites;
int8_t echoVolumeL;
int8_t echoVolumeR;
int8_t feedbackVolume;
uint16_t echoBufferAdr;
uint16_t echoDelay;
uint16_t echoRemain;
uint16_t echoBufferIndex;
uint8_t firBufferIndex;
int8_t firValues[8];
int16_t firBufferL[8];
int16_t firBufferR[8];
// sample buffer (1 frame at 32040 Hz: 534 samples, *2 for stereo)
int16_t sampleBuffer[534 * 2];
uint16_t sampleOffset; // current offset in samplebuffer
};
typedef struct DspRegWriteHistory {
uint32_t count;
uint8_t addr[256];
uint8_t val[256];
} DspRegWriteHistory;
Dsp *dsp_init(uint8_t *ram);
void dsp_free(Dsp* dsp);
void dsp_reset(Dsp* dsp);
void dsp_cycle(Dsp* dsp);
uint8_t dsp_read(Dsp* dsp, uint8_t adr);
void dsp_write(Dsp* dsp, uint8_t adr, uint8_t val);
void dsp_getSamples(Dsp* dsp, int16_t* sampleData, int samplesPerFrame);
void dsp_saveload(Dsp *dsp, SaveLoadFunc *func, void *ctx);
#endif

109
src/snes/dsp_regs.h Normal file
View File

@@ -0,0 +1,109 @@
#ifndef DSP_REGS_H
#define DSP_REGS_H
enum DspReg {
V0VOLL = 0x00,
V0VOLR = 0x01,
V0PITCHL = 0x02,
V0PITCHH = 0x03,
V0SRCN = 0x04,
V0ADSR1 = 0x05,
V0ADSR2 = 0x06,
V0GAIN = 0x07,
V0ENVX = 0x08,
V0OUTX = 0x09,
MVOLL = 0x0C,
EFB = 0x0D,
FIR0 = 0x0F,
V1VOLL = 0x10,
V1VOLR = 0x11,
V1PL = 0x12,
V1PH = 0x13,
V1SRCN = 0x14,
V1ADSR1 = 0x15,
V1ADSR2 = 0x16,
V1GAIN = 0x17,
V1ENVX = 0x18,
V1OUTX = 0x19,
MVOLR = 0x1C,
FIR1 = 0x1F,
V2VOLL = 0x20,
V2VOLR = 0x21,
V2PL = 0x22,
V2PH = 0x23,
V2SRCN = 0x24,
V2ADSR1 = 0x25,
V2ADSR2 = 0x26,
V2GAIN = 0x27,
V2ENVX = 0x28,
V2OUTX = 0x29,
EVOLL = 0x2C,
PMON = 0x2D,
FIR2 = 0x2F,
V3VOLL = 0x30,
V3VOLR = 0x31,
V3PL = 0x32,
V3PH = 0x33,
V3SRCN = 0x34,
V3ADSR1 = 0x35,
V3ADSR2 = 0x36,
V3GAIN = 0x37,
V3ENVX = 0x38,
V3OUTX = 0x39,
EVOLR = 0x3C,
NON = 0x3D,
FIR3 = 0x3F,
V4VOLL = 0x40,
V4VOLR = 0x41,
V4PL = 0x42,
V4PH = 0x43,
V4SRCN = 0x44,
V4ADSR1 = 0x45,
V4ADSR2 = 0x46,
V4GAIN = 0x47,
V4ENVX = 0x48,
V4OUTX = 0x49,
KON = 0x4C,
EON = 0x4D,
FIR4 = 0x4F,
V5VOLL = 0x50,
V5VOLR = 0x51,
V5PL = 0x52,
V5PH = 0x53,
V5SRCN = 0x54,
V5ADSR1 = 0x55,
V5ADSR2 = 0x56,
V5GAIN = 0x57,
V5ENVX = 0x58,
V5OUTX = 0x59,
KOF = 0x5C,
DIR = 0x5D,
FIR5 = 0x5F,
V6VOLL = 0x60,
V6VOLR = 0x61,
V6PL = 0x62,
V6PH = 0x63,
V6SRCN = 0x64,
V6ADSR1 = 0x65,
V6ADSR2 = 0x66,
V6GAIN = 0x67,
V6ENVX = 0x68,
V6OUTX = 0x69,
FLG = 0x6C,
ESA = 0x6D,
FIR6 = 0x6F,
V7VOLL = 0x70,
V7VOLR = 0x71,
V7PL = 0x72,
V7PH = 0x73,
V7SRCN = 0x74,
V7ADSR1 = 0x75,
V7ADSR2 = 0x76,
V7GAIN = 0x77,
V7ENVX = 0x78,
V7OUTX = 0x79,
ENDX = 0x7C,
EDL = 0x7D,
FIR7 = 0x7F,
};
#endif // DSP_REGS_H

26
src/snes/input.c Normal file
View File

@@ -0,0 +1,26 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "input.h"
#include "snes.h"
Input* input_init(Snes* snes) {
Input* input = malloc(sizeof(Input));
input->snes = snes;
// TODO: handle (where?)
input->type = 1;
input->currentState = 0;
return input;
}
void input_free(Input* input) {
free(input);
}
void input_reset(Input* input) {
}

26
src/snes/input.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef INPUT_H
#define INPUT_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Input Input;
#include "snes.h"
struct Input {
Snes* snes;
uint8_t type;
// for controller
uint16_t currentState; // actual state
};
Input* input_init(Snes* snes);
void input_free(Input* input);
void input_reset(Input* input);
#endif

1619
src/snes/ppu.c Normal file

File diff suppressed because it is too large Load Diff

187
src/snes/ppu.h Normal file
View File

@@ -0,0 +1,187 @@
#ifndef PPU_H
#define PPU_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Ppu Ppu;
#include "snes.h"
typedef struct BgLayer {
uint16_t hScroll;
uint16_t vScroll;
bool tilemapWider;
bool tilemapHigher;
uint16_t tilemapAdr;
uint16_t tileAdr;
bool bigTiles;
bool mosaicEnabled;
} BgLayer;
enum {
kPpuXPixels = 256,
kPpuExtraLeftRight = 0,
};
typedef uint16_t PpuZbufType;
typedef struct PpuPixelPrioBufs {
// This holds the prio in the upper 8 bits and the color in the lower 8 bits.
PpuZbufType data[kPpuXPixels];
} PpuPixelPrioBufs;
enum {
kPpuRenderFlags_NewRenderer = 1,
// Render mode7 upsampled by 4x4
kPpuRenderFlags_4x4Mode7 = 2,
// Use 240 height instead of 224
kPpuRenderFlags_Height240 = 4,
// Disable sprite render limits
kPpuRenderFlags_NoSpriteLimits = 8,
};
typedef struct Layer {
bool mainScreenEnabled;
bool subScreenEnabled;
bool mainScreenWindowed;
bool subScreenWindowed;
} Layer;
typedef struct WindowLayer {
bool window1enabled;
bool window2enabled;
bool window1inversed;
bool window2inversed;
uint8_t maskLogic;
} WindowLayer;
struct Ppu {
Snes* snes;
// vram access
uint16_t vram[0x8000];
uint16_t vramPointer;
bool vramIncrementOnHigh;
uint16_t vramIncrement;
uint8_t vramRemapMode;
uint16_t vramReadBuffer;
// cgram access
uint16_t cgram[0x100];
uint8_t cgramPointer;
bool cgramSecondWrite;
uint8_t cgramBuffer;
// oam access
uint16_t oam[0x100];
uint8_t highOam[0x20];
uint8_t oamAdr;
uint8_t oamAdrWritten;
bool oamInHigh;
bool oamInHighWritten;
bool oamSecondWrite;
uint8_t oamBuffer;
// object/sprites
bool objPriority;
uint16_t objTileAdr1;
uint16_t objTileAdr2;
uint8_t objSize;
uint8_t objPixelBufferXX[256]; // line buffers
uint8_t objPriorityBufferXX[256];
bool timeOver;
bool rangeOver;
bool objInterlace;
// background layers
BgLayer bgLayer[4];
uint8_t scrollPrev;
uint8_t scrollPrev2;
uint8_t mosaicSize;
uint8_t mosaicStartLine;
// layers
Layer layer[5];
// mode 7
int16_t m7matrix[8]; // a, b, c, d, x, y, h, v
uint8_t m7prev;
bool m7largeField;
bool m7charFill;
bool m7xFlip;
bool m7yFlip;
bool m7extBg;
// mode 7 internal
int32_t m7startX;
int32_t m7startY;
// windows
WindowLayer windowLayer[6];
uint8_t window1left;
uint8_t window1right;
uint8_t window2left;
uint8_t window2right;
// color math
uint8_t clipMode;
uint8_t preventMathMode;
bool addSubscreen;
bool subtractColor;
bool halfColor;
bool mathEnabled[6];
uint8_t fixedColorR;
uint8_t fixedColorG;
uint8_t fixedColorB;
// settings
bool forcedBlank;
uint8_t brightness;
uint8_t mode;
bool bg3priority;
bool evenFrame;
bool pseudoHires;
bool overscan;
bool frameOverscan; // if we are overscanning this frame (determined at 0,225)
bool interlace;
bool frameInterlace; // if we are interlacing this frame (determined at start vblank)
bool directColor;
// latching
uint16_t hCount;
uint16_t vCount;
bool hCountSecond;
bool vCountSecond;
bool countersLatched;
uint8_t ppu1openBus;
uint8_t ppu2openBus;
// pixel buffer (xbgr)
// times 2 for even and odd frame
uint8_t pixelbuffer_placeholder;
uint32_t windowsel;
uint8_t extraLeftCur, extraRightCur, extraLeftRight;
uint8_t screenEnabled[2];
uint8_t screenWindowed[2];
uint8_t mosaicEnabled;
uint8_t lastBrightnessMult;
bool lineHasSprites;
PpuPixelPrioBufs bgBuffers[2];
PpuPixelPrioBufs objBuffer;
uint32_t renderPitch;
uint8_t *renderBuffer;
uint8_t brightnessMult[32 + 31];
uint8_t brightnessMultHalf[32 * 2];
uint8_t mosaicModulo[kPpuXPixels];
};
Ppu* ppu_init(Snes* snes);
void ppu_free(Ppu* ppu);
void ppu_copy(Ppu *ppu, Ppu *ppu_src);
void ppu_reset(Ppu* ppu);
bool ppu_checkOverscan(Ppu* ppu);
void ppu_handleVblank(Ppu* ppu);
void ppu_runLine(Ppu* ppu, int line);
uint8_t ppu_read(Ppu* ppu, uint8_t adr);
void ppu_write(Ppu* ppu, uint8_t adr, uint8_t val);
void ppu_saveload(Ppu *ppu, SaveLoadFunc *func, void *ctx);
void PpuBeginDrawing(Ppu *ppu, uint8_t *pixels, size_t pitch, uint32_t render_flags);
int PpuGetCurrentRenderScale(Ppu *ppu, uint32_t render_flags);
#endif

5
src/snes/saveload.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
typedef void SaveLoadFunc(void *ctx, void *data, size_t data_size);
#define SL(x) func(ctx, &x, sizeof(x))

458
src/snes/snes.c Normal file
View File

@@ -0,0 +1,458 @@
#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);
}

95
src/snes/snes.h Normal file
View File

@@ -0,0 +1,95 @@
#ifndef SNES_H
#define SNES_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Snes Snes;
#include "cpu.h"
#include "apu.h"
#include "dma.h"
#include "ppu.h"
#include "cart.h"
#include "input.h"
#include "saveload.h"
struct Snes {
Cpu* cpu;
Apu* apu;
Ppu* ppu, *snes_ppu, *my_ppu;
Dma* dma;
Cart* cart;
// input
bool debug_cycles;
bool debug_apu_cycles;
bool disableRender;
uint8_t runningWhichVersion;
Input* input1;
Input* input2;
// ram
uint8_t *ram;
uint32_t ramAdr;
// frame timing
uint16_t hPos;
uint16_t vPos;
uint32_t frames;
// cpu handling
uint8_t cpuCyclesLeft;
uint8_t cpuMemOps;
double apuCatchupCycles;
// nmi / irq
bool hIrqEnabled;
bool vIrqEnabled;
bool nmiEnabled;
uint16_t hTimer;
uint16_t vTimer;
bool inNmi;
bool inIrq;
bool inVblank;
// joypad handling
uint16_t portAutoReadX[4]; // as read by auto-joypad read
bool autoJoyRead;
uint16_t autoJoyTimer; // times how long until reading is done
bool ppuLatch;
// multiplication/division
uint8_t multiplyA;
uint16_t multiplyResult;
uint16_t divideA;
uint16_t divideResult;
// misc
bool fastMem;
uint8_t openBus;
};
Snes* snes_init(uint8_t *ram);
void snes_free(Snes* snes);
void snes_reset(Snes* snes, bool hard);
void snes_runFrame(Snes* snes);
// used by dma, cpu
uint8_t snes_readBBus(Snes* snes, uint8_t adr);
void snes_writeBBus(Snes* snes, uint8_t adr, uint8_t val);
uint8_t snes_read(Snes* snes, uint32_t adr);
void snes_write(Snes* snes, uint32_t adr, uint8_t val);
uint8_t snes_cpuRead(Snes* snes, uint32_t adr);
void snes_cpuWrite(Snes* snes, uint32_t adr, uint8_t val);
// debugging
void snes_debugCycle(Snes* snes, bool* cpuNext, bool* spcNext);
void snes_handle_pos_stuff(Snes *snes);
// snes_other.c functions:
bool snes_loadRom(Snes* snes, const uint8_t* data, int length);
void snes_setPixels(Snes* snes, uint8_t* pixelData);
void snes_setSamples(Snes* snes, int16_t* sampleData, int samplesPerFrame);
void snes_saveload(Snes *snes, SaveLoadFunc *func, void *ctx);
uint8_t snes_readBBusOrg(Snes *snes, uint8_t adr);
void snes_catchupApu(Snes *snes);
extern int snes_frame_counter;
#endif

204
src/snes/snes_other.c Normal file
View File

@@ -0,0 +1,204 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "snes.h"
#include "cart.h"
#include "ppu.h"
#include "dsp.h"
typedef struct CartHeader {
// normal header
uint8_t headerVersion; // 1, 2, 3
char name[22]; // $ffc0-$ffd4 (max 21 bytes + \0), $ffd4=$00: header V2
uint8_t speed; // $ffd5.7-4 (always 2 or 3)
uint8_t type; // $ffd5.3-0
uint8_t coprocessor; // $ffd6.7-4
uint8_t chips; // $ffd6.3-0
uint32_t romSize; // $ffd7 (0x400 << x)
uint32_t ramSize; // $ffd8 (0x400 << x)
uint8_t region; // $ffd9 (also NTSC/PAL)
uint8_t maker; // $ffda ($33: header V3)
uint8_t version; // $ffdb
uint16_t checksumComplement; // $ffdc,$ffdd
uint16_t checksum; // $ffde,$ffdf
// v2/v3 (v2 only exCoprocessor)
char makerCode[3]; // $ffb0,$ffb1: (2 chars + \0)
char gameCode[5]; // $ffb2-$ffb5: (4 chars + \0)
uint32_t flashSize; // $ffbc (0x400 << x)
uint32_t exRamSize; // $ffbd (0x400 << x) (used for GSU?)
uint8_t specialVersion; // $ffbe
uint8_t exCoprocessor; // $ffbf (if coprocessor = $f)
// calculated stuff
int16_t score; // score for header, to see which mapping is most likely
bool pal; // if this is a rom for PAL regions instead of NTSC
uint8_t cartType; // calculated type
} CartHeader;
static void readHeader(const uint8_t* data, int length, int location, CartHeader* header);
bool snes_loadRom(Snes* snes, const uint8_t* data, int length) {
// if smaller than smallest possible, don't load
if(length < 0x8000) {
printf("Failed to load rom: rom to small (%d bytes)\n", length);
return false;
}
// check headers
CartHeader headers[4];
memset(headers, 0, sizeof(headers));
for(int i = 0; i < 4; i++) {
headers[i].score = -50;
}
if(length >= 0x8000) readHeader(data, length, 0x7fc0, &headers[0]);
if(length >= 0x8200) readHeader(data, length, 0x81c0, &headers[1]);
if(length >= 0x10000) readHeader(data, length, 0xffc0, &headers[2]);
if(length >= 0x10200) readHeader(data, length, 0x101c0, &headers[3]);
// see which it is
int max = 0;
int used = 0;
for(int i = 0; i < 4; i++) {
if(headers[i].score > max) {
max = headers[i].score;
used = i;
}
}
if(used & 1) {
// odd-numbered ones are for headered roms
data += 0x200; // move pointer past header
length -= 0x200; // and subtract from size
}
// check if we can load it
if(headers[used].cartType > 2) {
printf("Failed to load rom: unsupported type (%d)\n", headers[used].cartType);
return false;
}
// expand to a power of 2
int newLength = 0x8000;
while(true) {
if(length <= newLength) {
break;
}
newLength *= 2;
}
uint8_t* newData = malloc(newLength);
memcpy(newData, data, length);
int test = 1;
while(length != newLength) {
if(length & test) {
memcpy(newData + length, newData + length - test, test);
length += test;
}
test *= 2;
}
// load it
printf("Loaded %s rom\n\"%s\"\n", headers[used].cartType == 2 ? "HiROM" : "LoROM", headers[used].name);
cart_load(
snes->cart, headers[used].cartType,
newData, newLength, headers[used].chips > 0 ? headers[used].ramSize : 0
);
snes_reset(snes, true); // reset after loading
free(newData);
return true;
}
void snes_setSamples(Snes* snes, int16_t* sampleData, int samplesPerFrame) {
// size is 2 (int16) * 2 (stereo) * samplesPerFrame
// sets samples in the sampleData
dsp_getSamples(snes->apu->dsp, sampleData, samplesPerFrame);
}
static void readHeader(const uint8_t* data, int length, int location, CartHeader* header) {
// read name, TODO: non-ASCII names?
for(int i = 0; i < 21; i++) {
uint8_t ch = data[location + i];
if(ch >= 0x20 && ch < 0x7f) {
header->name[i] = ch;
} else {
header->name[i] = '.';
}
}
header->name[21] = 0;
// read rest
header->speed = data[location + 0x15] >> 4;
header->type = data[location + 0x15] & 0xf;
header->coprocessor = data[location + 0x16] >> 4;
header->chips = data[location + 0x16] & 0xf;
header->romSize = 0x400 << data[location + 0x17];
header->ramSize = 0x400 << data[location + 0x18];
header->region = data[location + 0x19];
header->maker = data[location + 0x1a];
header->version = data[location + 0x1b];
header->checksumComplement = (data[location + 0x1d] << 8) + data[location + 0x1c];
header->checksum = (data[location + 0x1f] << 8) + data[location + 0x1e];
// read v3 and/or v2
header->headerVersion = 1;
if(header->maker == 0x33) {
header->headerVersion = 3;
// maker code
for(int i = 0; i < 2; i++) {
uint8_t ch = data[location - 0x10 + i];
if(ch >= 0x20 && ch < 0x7f) {
header->makerCode[i] = ch;
} else {
header->makerCode[i] = '.';
}
}
header->makerCode[2] = 0;
// game code
for(int i = 0; i < 4; i++) {
uint8_t ch = data[location - 0xe + i];
if(ch >= 0x20 && ch < 0x7f) {
header->gameCode[i] = ch;
} else {
header->gameCode[i] = '.';
}
}
header->gameCode[4] = 0;
header->flashSize = 0x400 << data[location - 4];
header->exRamSize = 0x400 << data[location - 3];
header->specialVersion = data[location - 2];
header->exCoprocessor = data[location - 1];
} else if(data[location + 0x14] == 0) {
header->headerVersion = 2;
header->exCoprocessor = data[location - 1];
}
// get region
header->pal = (header->region >= 0x2 && header->region <= 0xc) || header->region == 0x11;
header->cartType = location < 0x9000 ? 1 : 2;
// get score
// TODO: check name, maker/game-codes (if V3) for ASCII, more vectors,
// more first opcode, rom-sizes (matches?), type (matches header location?)
int score = 0;
score += (header->speed == 2 || header->speed == 3) ? 5 : -4;
score += (header->type <= 3 || header->type == 5) ? 5 : -2;
score += (header->coprocessor <= 5 || header->coprocessor >= 0xe) ? 5 : -2;
score += (header->chips <= 6 || header->chips == 9 || header->chips == 0xa) ? 5 : -2;
score += (header->region <= 0x14) ? 5 : -2;
score += (header->checksum + header->checksumComplement == 0xffff) ? 8 : -6;
uint16_t resetVector = data[location + 0x3c] | (data[location + 0x3d] << 8);
score += (resetVector >= 0x8000) ? 8 : -20;
// check first opcode after reset
int opcodeLoc = location + 0x40 - 0x8000 + (resetVector & 0x7fff);
uint8_t opcode = 0xff;
if(opcodeLoc < length) {
opcode = data[opcodeLoc];
} else {
score -= 14;
}
if(opcode == 0x78 || opcode == 0x18) {
// sei, clc (for clc:xce)
score += 6;
}
if(opcode == 0x4c || opcode == 0x5c || opcode == 0x9c) {
// jmp abs, jml abl, stz abs
score += 3;
}
if(opcode == 0x00 || opcode == 0xff || opcode == 0xdb) {
// brk, sbc alx, stp
score -= 6;
}
header->score = score;
}

1527
src/snes/spc.c Normal file

File diff suppressed because it is too large Load Diff

44
src/snes/spc.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef SPC_H
#define SPC_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct Spc Spc;
#include "apu.h"
#include "saveload.h"
struct Spc {
Apu* apu;
// registers
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t sp;
uint16_t pc;
// flags
bool c;
bool z;
bool v;
bool n;
bool i;
bool h;
bool p;
bool b;
// stopping
bool stopped;
// internal use
uint8_t cyclesUsed; // indicates how many cycles an opcode used
};
Spc* spc_init(Apu* apu);
void spc_free(Spc* spc);
void spc_reset(Spc* spc);
int spc_runOpcode(Spc* spc);
void spc_saveload(Spc *spc, SaveLoadFunc *func, void *ctx);
#endif