Initial commit
This commit is contained in:
194
src/snes/apu.c
Normal file
194
src/snes/apu.c
Normal 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
50
src/snes/apu.h
Normal 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
116
src/snes/cart.c
Normal 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
34
src/snes/cart.h
Normal 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
2356
src/snes/cpu.c
Normal file
File diff suppressed because it is too large
Load Diff
59
src/snes/cpu.h
Normal file
59
src/snes/cpu.h
Normal 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
332
src/snes/dma.c
Normal 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
57
src/snes/dma.h
Normal 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
578
src/snes/dsp.c
Normal 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
99
src/snes/dsp.h
Normal 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
109
src/snes/dsp_regs.h
Normal 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
26
src/snes/input.c
Normal 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
26
src/snes/input.h
Normal 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
1619
src/snes/ppu.c
Normal file
File diff suppressed because it is too large
Load Diff
187
src/snes/ppu.h
Normal file
187
src/snes/ppu.h
Normal 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
5
src/snes/saveload.h
Normal 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
458
src/snes/snes.c
Normal 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
95
src/snes/snes.h
Normal 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
204
src/snes/snes_other.c
Normal 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
1527
src/snes/spc.c
Normal file
File diff suppressed because it is too large
Load Diff
44
src/snes/spc.h
Normal file
44
src/snes/spc.h
Normal 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
|
||||
Reference in New Issue
Block a user