o add bsnes

This commit is contained in:
optixx
2009-04-08 21:29:36 +02:00
parent bcb4a055e9
commit 27b58a09f2
413 changed files with 71887 additions and 0 deletions

10
bsnes/snes/audio/audio.cpp Executable file
View File

@@ -0,0 +1,10 @@
#ifdef SNES_CPP
void SNES::Audio::update(uint16 l_sample, uint16 r_sample) {
snesinterface.audio_sample(l_sample, r_sample);
}
void SNES::Audio::init() {
}
#endif //ifdef SNES_CPP

7
bsnes/snes/audio/audio.hpp Executable file
View File

@@ -0,0 +1,7 @@
class Audio {
public:
void update(uint16 l_sample, uint16 r_sample);
void init();
friend class SNES;
} audio;

341
bsnes/snes/input/input.cpp Executable file
View File

@@ -0,0 +1,341 @@
#ifdef SNES_CPP
uint8 SNES::Input::port_read(bool portnumber) {
port_t &p = port[portnumber];
switch(p.device) {
case DeviceJoypad: {
if(p.counter0 >= 16) return 1;
unsigned deviceid = (portnumber == 0 ? DeviceIDJoypad1 : DeviceIDJoypad2);
return snesinterface.input_poll(deviceid, p.counter0++);
} //case DeviceJoypad
case DeviceMultitap: {
if(cpu.joylatch()) return 2; //when latch is high -- data2 = 1, data1 = 0
unsigned deviceidx, deviceid0, deviceid1;
if(portnumber == 0) {
if(cpu.pio() & 0x40) {
deviceidx = p.counter0;
if(deviceidx >= 16) return 3;
p.counter0++;
deviceid0 = DeviceIDMultitap1A;
deviceid1 = DeviceIDMultitap1B;
} else {
deviceidx = p.counter1;
if(deviceidx >= 16) return 3;
p.counter1++;
deviceid0 = DeviceIDMultitap1C;
deviceid1 = DeviceIDMultitap1D;
}
} else {
if(cpu.pio() & 0x80) {
deviceidx = p.counter0;
if(deviceidx >= 16) return 3;
p.counter0++;
deviceid0 = DeviceIDMultitap2A;
deviceid1 = DeviceIDMultitap2B;
} else {
deviceidx = p.counter1;
if(deviceidx >= 16) return 3;
p.counter1++;
deviceid0 = DeviceIDMultitap2C;
deviceid1 = DeviceIDMultitap2D;
}
}
return (snesinterface.input_poll(deviceid0, deviceidx) << 0)
| (snesinterface.input_poll(deviceid1, deviceidx) << 1);
} //case DeviceMultitap
case DeviceMouse: {
if(p.counter0 >= 32) return 1;
unsigned deviceid = (portnumber == 0 ? DeviceIDMouse1 : DeviceIDMouse2);
int position_x = snesinterface.input_poll(deviceid, MouseX); //-n = left, 0 = center, +n = right
int position_y = snesinterface.input_poll(deviceid, MouseY); //-n = up, 0 = center, +n = right
bool direction_x = position_x < 0; //0 = right, 1 = left
bool direction_y = position_y < 0; //0 = down, 1 = up
if(position_x < 0) position_x = -position_x; //abs(position_x)
if(position_y < 0) position_y = -position_y; //abs(position_x)
position_x = min(127, position_x); //range = 0 - 127
position_y = min(127, position_y); //range = 0 - 127
switch(p.counter0++) { default:
case 0: return 0;
case 1: return 0;
case 2: return 0;
case 3: return 0;
case 4: return 0;
case 5: return 0;
case 6: return 0;
case 7: return 0;
case 8: return snesinterface.input_poll(deviceid, MouseRight);
case 9: return snesinterface.input_poll(deviceid, MouseLeft);
case 10: return 0; //speed (0 = slow, 1 = normal, 2 = fast, 3 = unused)
case 11: return 0; // ||
case 12: return 0; //signature
case 13: return 0; // ||
case 14: return 0; // ||
case 15: return 1; // ||
case 16: return (direction_y) & 1;
case 17: return (position_y >> 6) & 1;
case 18: return (position_y >> 5) & 1;
case 19: return (position_y >> 4) & 1;
case 20: return (position_y >> 3) & 1;
case 21: return (position_y >> 2) & 1;
case 22: return (position_y >> 1) & 1;
case 23: return (position_y >> 0) & 1;
case 24: return (direction_x) & 1;
case 25: return (position_x >> 6) & 1;
case 26: return (position_x >> 5) & 1;
case 27: return (position_x >> 4) & 1;
case 28: return (position_x >> 3) & 1;
case 29: return (position_x >> 2) & 1;
case 30: return (position_x >> 1) & 1;
case 31: return (position_x >> 0) & 1;
}
} //case DeviceMouse
case DeviceSuperScope: {
if(portnumber == 0) break; //Super Scope in port 1 not supported ...
if(p.counter0 >= 8) return 1;
if(p.counter0 == 0) {
//turbo is a switch; toggle is edge sensitive
bool turbo = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeTurbo);
if(turbo && !p.superscope.turbolock) {
p.superscope.turbo = !p.superscope.turbo; //toggle state
p.superscope.turbolock = true;
} else if(!turbo) {
p.superscope.turbolock = false;
}
//trigger is a button
//if turbo is active, trigger is level sensitive; otherwise it is edge sensitive
p.superscope.trigger = false;
bool trigger = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeTrigger);
if(trigger && (p.superscope.turbo || !p.superscope.triggerlock)) {
p.superscope.trigger = true;
p.superscope.triggerlock = true;
} else if(!trigger) {
p.superscope.triggerlock = false;
}
//cursor is a button; it is always level sensitive
p.superscope.cursor = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeCursor);
//pause is a button; it is always edge sensitive
p.superscope.pause = false;
bool pause = snesinterface.input_poll(DeviceIDSuperScope, SuperScopePause);
if(pause && !p.superscope.pauselock) {
p.superscope.pause = true;
p.superscope.pauselock = true;
} else if(!pause) {
p.superscope.pauselock = false;
}
p.superscope.offscreen =
p.superscope.x < 0 || p.superscope.x >= 256
|| p.superscope.y < 0 || p.superscope.y >= (ppu.overscan() ? 240 : 225);
}
switch(p.counter0++) {
case 0: return p.superscope.trigger;
case 1: return p.superscope.cursor;
case 2: return p.superscope.turbo;
case 3: return p.superscope.pause;
case 4: return 0;
case 5: return 0;
case 6: return p.superscope.offscreen;
case 7: return 0; //noise (1 = yes)
}
} //case DeviceSuperScope
case DeviceJustifier:
case DeviceJustifiers: {
if(portnumber == 0) break; //Justifier in port 1 not supported ...
if(p.counter0 >= 32) return 1;
if(p.counter0 == 0) {
p.justifier.trigger1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierTrigger);
p.justifier.start1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierStart);
if(p.device == DeviceJustifiers) {
p.justifier.trigger2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierTrigger);
p.justifier.start2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierStart);
} else {
p.justifier.x2 = -1;
p.justifier.y2 = -1;
p.justifier.trigger2 = false;
p.justifier.start2 = false;
}
}
switch(p.counter0++) {
case 0: return 0;
case 1: return 0;
case 2: return 0;
case 3: return 0;
case 4: return 0;
case 5: return 0;
case 6: return 0;
case 7: return 0;
case 8: return 0;
case 9: return 0;
case 10: return 0;
case 11: return 0;
case 12: return 1; //signature
case 13: return 1; // ||
case 14: return 1; // ||
case 15: return 0; // ||
case 16: return 0;
case 17: return 1;
case 18: return 0;
case 19: return 1;
case 20: return 0;
case 21: return 1;
case 22: return 0;
case 23: return 1;
case 24: return p.justifier.trigger1;
case 25: return p.justifier.trigger2;
case 26: return p.justifier.start1;
case 27: return p.justifier.start2;
case 28: return p.justifier.active;
case 29: return 0;
case 30: return 0;
case 31: return 0;
}
} //case DeviceJustifier(s)
} //switch(p.device)
//no device connected
return 0;
}
//scan all input; update cursor positions if needed
void SNES::Input::update() {
snesinterface.input_poll();
port_t &p = port[1];
switch(p.device) {
case DeviceSuperScope: {
int x = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeX);
int y = snesinterface.input_poll(DeviceIDSuperScope, SuperScopeY);
x += p.superscope.x;
y += p.superscope.y;
p.superscope.x = max(-16, min(256 + 16, x));
p.superscope.y = max(-16, min(240 + 16, y));
latchx = p.superscope.x;
latchy = p.superscope.y;
} break;
case DeviceJustifier:
case DeviceJustifiers: {
int x1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierX);
int y1 = snesinterface.input_poll(DeviceIDJustifier1, JustifierY);
x1 += p.justifier.x1;
y1 += p.justifier.y1;
p.justifier.x1 = max(-16, min(256 + 16, x1));
p.justifier.y1 = max(-16, min(240 + 16, y1));
int x2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierX);
int y2 = snesinterface.input_poll(DeviceIDJustifier2, JustifierY);
x2 += p.justifier.x2;
y2 += p.justifier.y2;
p.justifier.x2 = max(-16, min(256 + 16, x2));
p.justifier.y2 = max(-16, min(240 + 16, y2));
if(p.justifier.active == 0) {
latchx = p.justifier.x1;
latchy = p.justifier.y1;
} else {
latchx = (p.device == DeviceJustifiers ? p.justifier.x2 : -1);
latchy = (p.device == DeviceJustifiers ? p.justifier.y2 : -1);
}
} break;
}
}
void SNES::Input::port_set_device(bool portnumber, unsigned device) {
port_t &p = port[portnumber];
p.device = device;
p.counter0 = 0;
p.counter1 = 0;
//set iobit to true if device is capable of latching PPU counters
iobit = port[1].device == DeviceSuperScope
|| port[1].device == DeviceJustifier
|| port[1].device == DeviceJustifiers;
latchx = -1;
latchy = -1;
if(device == DeviceSuperScope) {
p.superscope.x = 256 / 2;
p.superscope.y = 240 / 2;
p.superscope.trigger = false;
p.superscope.cursor = false;
p.superscope.turbo = false;
p.superscope.pause = false;
p.superscope.offscreen = false;
p.superscope.turbolock = false;
p.superscope.triggerlock = false;
p.superscope.pauselock = false;
} else if(device == DeviceJustifier) {
p.justifier.active = 0;
p.justifier.x1 = 256 / 2;
p.justifier.y1 = 240 / 2;
p.justifier.x2 = -1;
p.justifier.y2 = -1;
p.justifier.trigger1 = false;
p.justifier.trigger2 = false;
p.justifier.start1 = false;
p.justifier.start2 = false;
} else if(device == DeviceJustifiers) {
p.justifier.active = 0;
p.justifier.x1 = 256 / 2 - 16;
p.justifier.y1 = 240 / 2;
p.justifier.x2 = 256 / 2 + 16;
p.justifier.y2 = 240 / 2;
p.justifier.trigger1 = false;
p.justifier.trigger2 = false;
p.justifier.start1 = false;
p.justifier.start2 = false;
}
}
void SNES::Input::poll() {
port[0].counter0 = 0;
port[0].counter1 = 0;
port[1].counter0 = 0;
port[1].counter1 = 0;
port[1].justifier.active = !port[1].justifier.active;
}
void SNES::Input::init() {
}
#endif //ifdef SNES_CPP

114
bsnes/snes/input/input.hpp Executable file
View File

@@ -0,0 +1,114 @@
class Input {
public:
enum Device {
DeviceNone,
DeviceJoypad,
DeviceMultitap,
DeviceMouse,
DeviceSuperScope,
DeviceJustifier,
DeviceJustifiers,
};
enum DeviceID {
DeviceIDNone,
DeviceIDJoypad1,
DeviceIDJoypad2,
DeviceIDMultitap1A,
DeviceIDMultitap1B,
DeviceIDMultitap1C,
DeviceIDMultitap1D,
DeviceIDMultitap2A,
DeviceIDMultitap2B,
DeviceIDMultitap2C,
DeviceIDMultitap2D,
DeviceIDMouse1,
DeviceIDMouse2,
DeviceIDSuperScope,
DeviceIDJustifier1,
DeviceIDJustifier2,
};
enum JoypadID {
JoypadB = 0, JoypadY = 1,
JoypadSelect = 2, JoypadStart = 3,
JoypadUp = 4, JoypadDown = 5,
JoypadLeft = 6, JoypadRight = 7,
JoypadA = 8, JoypadX = 9,
JoypadL = 10, JoypadR = 11,
};
enum MouseID {
MouseX = 0, MouseY = 1,
MouseLeft = 2, MouseRight = 3,
};
enum SuperScopeID {
SuperScopeX = 0, SuperScopeY = 1,
SuperScopeTrigger = 2, SuperScopeCursor = 3,
SuperScopeTurbo = 4, SuperScopePause = 5,
};
enum JustifierID {
JustifierX = 0, JustifierY = 1,
JustifierTrigger = 2, JustifierStart = 3,
};
uint8 port_read(bool port);
void port_set_device(bool port, unsigned device);
void init();
void poll();
void update();
//light guns (Super Scope, Justifier(s)) strobe IOBit whenever the CRT
//beam cannon is detected. this needs to be tested at the cycle level
//(hence inlining here for speed) to avoid 'dead space' during DRAM refresh.
//iobit is updated during port_set_device(),
//latchx, latchy are updated during update() (once per frame)
alwaysinline void tick() {
//only test if Super Scope or Justifier is connected
if(iobit) {
if(ppu.vcounter() == latchy //test Y cursor position
&& ppu.hcounter() == latchx << 2 //test X cursor position (cycles == pixels << 2)
&& latchy < (ppu.overscan() ? 240 : 225) //verify Y is not offscreen
&& latchx < 256 //verify X is not offscreen
) ppu.latch_counters();
}
}
private:
bool iobit;
uint16_t latchx, latchy;
struct port_t {
unsigned device;
unsigned counter0; //read counters
unsigned counter1;
struct superscope_t {
int x, y;
bool trigger;
bool cursor;
bool turbo;
bool pause;
bool offscreen;
bool turbolock;
bool triggerlock;
bool pauselock;
} superscope;
struct justifier_t {
bool active;
int x1, x2;
int y1, y2;
bool trigger1, trigger2;
bool start1, start2;
} justifier;
} port[2];
friend class SNES;
} input;

View File

@@ -0,0 +1,17 @@
//====================
//SNES interface class
//====================
//Interfaces SNES core with platform-specific functionality (video, audio, input, ...)
class SNESInterface {
public:
void video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height);
void audio_sample(uint16_t l_sample, uint16_t r_sample);
void input_poll();
int16_t input_poll(unsigned deviceid, unsigned id);
void init();
void term();
};
extern SNESInterface snesinterface;

View File

@@ -0,0 +1,56 @@
#ifdef SNES_CPP
Scheduler scheduler;
void threadentry_cpu() { cpu.enter(); }
void threadentry_smp() { smp.enter(); }
void threadentry_ppu() { ppu.enter(); }
void threadentry_dsp() { dsp.enter(); }
void Scheduler::enter() {
switch(clock.active) {
case THREAD_CPU: co_switch(thread_cpu); break;
case THREAD_SMP: co_switch(thread_smp); break;
case THREAD_PPU: co_switch(thread_ppu); break;
case THREAD_DSP: co_switch(thread_dsp); break;
}
}
void Scheduler::exit() {
co_switch(thread_snes);
}
void Scheduler::init() {
clock.cpu_freq = snes.region() == SNES::NTSC
? snes.config.cpu.ntsc_clock_rate
: snes.config.cpu.pal_clock_rate;
clock.smp_freq = snes.region() == SNES::NTSC
? snes.config.smp.ntsc_clock_rate
: snes.config.smp.pal_clock_rate;
clock.active = THREAD_CPU;
clock.cpuppu = 0;
clock.cpusmp = 0;
clock.smpdsp = 0;
if(thread_cpu) co_delete(thread_cpu);
if(thread_smp) co_delete(thread_smp);
if(thread_ppu) co_delete(thread_ppu);
if(thread_dsp) co_delete(thread_dsp);
thread_snes = co_active();
thread_cpu = co_create(65536 * sizeof(void*), threadentry_cpu);
thread_smp = co_create(65536 * sizeof(void*), threadentry_smp);
thread_ppu = co_create(65536 * sizeof(void*), threadentry_ppu);
thread_dsp = co_create(65536 * sizeof(void*), threadentry_dsp);
}
Scheduler::Scheduler() {
thread_snes = 0;
thread_cpu = 0;
thread_smp = 0;
thread_ppu = 0;
thread_dsp = 0;
}
#endif //ifdef SNES_CPP

View File

@@ -0,0 +1,123 @@
class Scheduler {
public:
cothread_t thread_snes;
cothread_t thread_cpu;
cothread_t thread_smp;
cothread_t thread_ppu;
cothread_t thread_dsp;
enum ActiveThread {
THREAD_CPU,
THREAD_SMP,
THREAD_PPU,
THREAD_DSP,
};
struct {
unsigned cpu_freq;
unsigned smp_freq;
ActiveThread active;
int64 cpuppu;
int64 cpusmp;
int64 smpdsp;
} clock;
//==========
//CPU <> PPU
//==========
alwaysinline void sync_cpuppu() {
if(clock.cpuppu < 0) {
clock.active = THREAD_PPU;
co_switch(thread_ppu);
}
}
alwaysinline void sync_ppucpu() {
if(clock.cpuppu >= 0) {
clock.active = THREAD_CPU;
co_switch(thread_cpu);
}
}
//==========
//CPU <> SMP
//==========
alwaysinline void sync_cpusmp() {
if(clock.cpusmp < 0) {
clock.active = THREAD_SMP;
co_switch(thread_smp);
}
}
alwaysinline void sync_smpcpu() {
if(clock.cpusmp >= 0) {
clock.active = THREAD_CPU;
co_switch(thread_cpu);
}
}
//==========
//SMP <> DSP
//==========
alwaysinline void sync_smpdsp() {
if(clock.smpdsp < 0) {
clock.active = THREAD_DSP;
co_switch(thread_dsp);
}
}
alwaysinline void sync_dspsmp() {
if(clock.smpdsp >= 0) {
clock.active = THREAD_SMP;
co_switch(thread_smp);
}
}
//======
//timing
//======
alwaysinline void addclocks_cpu(unsigned clocks) {
clock.cpuppu -= clocks;
sync_cpuppu();
clock.cpusmp -= clocks * (uint64)clock.smp_freq;
if(clock.cpusmp < -(20000 * (int64)24000000)) sync_cpusmp();
}
alwaysinline void addclocks_ppu(unsigned clocks) {
clock.cpuppu += clocks;
sync_ppucpu();
}
alwaysinline void addclocks_smp(unsigned clocks) {
clock.cpusmp += clocks * (uint64)clock.cpu_freq;
if(clock.cpusmp > +(20000 * (int64)24000000)) sync_smpcpu();
clock.smpdsp -= clocks;
#if !defined(USE_STATE_MACHINE)
sync_smpdsp();
#else
while(clock.smpdsp < 0) dsp.enter();
#endif
}
alwaysinline void addclocks_dsp(unsigned clocks) {
clock.smpdsp += clocks;
#if !defined(USE_STATE_MACHINE)
sync_dspsmp();
#endif
}
void enter();
void exit();
void init();
Scheduler();
};
extern Scheduler scheduler;

210
bsnes/snes/snes.cpp Executable file
View File

@@ -0,0 +1,210 @@
#include <../base.hpp>
#include <../chip/chip.hpp>
#include <../cart/cart.hpp>
#define SNES_CPP
SNES snes;
BUSCORE bus;
CPUCORE cpu;
SMPCORE smp;
DSPCORE dsp;
PPUCORE ppu;
BSXBase bsxbase;
BSXCart bsxcart;
BSXFlash bsxflash;
SRTC srtc;
SDD1 sdd1;
SPC7110 spc7110;
Cx4 cx4;
DSP1 dsp1;
DSP2 dsp2;
DSP3 dsp3;
DSP4 dsp4;
OBC1 obc1;
ST010 st010;
#include "scheduler/scheduler.cpp"
#include "tracer/tracer.cpp"
#include "video/video.cpp"
#include "audio/audio.cpp"
#include "input/input.cpp"
void SNES::run() {
}
void SNES::runtoframe() {
scheduler.enter();
}
void SNES::init() {
bsxbase.init();
bsxcart.init();
bsxflash.init();
srtc.init();
sdd1.init();
spc7110.init();
cx4.init();
dsp1.init();
dsp2.init();
dsp3.init();
dsp4.init();
obc1.init();
st010.init();
video.init();
audio.init();
input.init();
snesinterface.init();
}
void SNES::term() {
snesinterface.term();
}
void SNES::power() {
snes_region = max(0, min(2, snes.config.region));
snes_expansion = max(0, min(1, snes.config.expansion_port));
if(snes_region == Autodetect) {
snes_region = (cartridge.region() == Cartridge::NTSC ? NTSC : PAL);
}
scheduler.init();
ppu.PPUcounter::reset();
cpu.power();
smp.power();
dsp.power();
ppu.power();
bus.power();
if(expansion() == ExpansionBSX) bsxbase.power();
if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.power();
if(cartridge.bsx_flash_loaded()) bsxflash.power();
if(cartridge.has_srtc()) srtc.power();
if(cartridge.has_sdd1()) sdd1.power();
if(cartridge.has_spc7110()) spc7110.power();
if(cartridge.has_cx4()) cx4.power();
if(cartridge.has_dsp1()) dsp1.power();
if(cartridge.has_dsp2()) dsp2.power();
if(cartridge.has_dsp3()) dsp3.power();
if(cartridge.has_dsp4()) dsp4.power();
if(cartridge.has_obc1()) obc1.power();
if(cartridge.has_st010()) st010.power();
for(unsigned i = 0x2100; i <= 0x213f; i++) memory::mmio.map(i, ppu);
for(unsigned i = 0x2140; i <= 0x217f; i++) memory::mmio.map(i, cpu);
for(unsigned i = 0x2180; i <= 0x2183; i++) memory::mmio.map(i, cpu);
for(unsigned i = 0x4016; i <= 0x4017; i++) memory::mmio.map(i, cpu);
for(unsigned i = 0x4200; i <= 0x421f; i++) memory::mmio.map(i, cpu);
for(unsigned i = 0x4300; i <= 0x437f; i++) memory::mmio.map(i, cpu);
if(expansion() == ExpansionBSX) bsxbase.enable();
if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.enable();
if(cartridge.bsx_flash_loaded()) bsxflash.enable();
if(cartridge.has_srtc()) srtc.enable();
if(cartridge.has_sdd1()) sdd1.enable();
if(cartridge.has_spc7110()) spc7110.enable();
if(cartridge.has_cx4()) cx4.enable();
if(cartridge.has_dsp1()) dsp1.enable();
if(cartridge.has_dsp2()) dsp2.enable();
if(cartridge.has_dsp3()) dsp3.enable();
if(cartridge.has_dsp4()) dsp4.enable();
if(cartridge.has_obc1()) obc1.enable();
if(cartridge.has_st010()) st010.enable();
input.port_set_device(0, snes.config.controller_port1);
input.port_set_device(1, snes.config.controller_port2);
input.update();
video.update();
}
void SNES::reset() {
scheduler.init();
ppu.PPUcounter::reset();
cpu.reset();
smp.reset();
dsp.reset();
ppu.reset();
bus.reset();
if(expansion() == ExpansionBSX) bsxbase.reset();
if(cartridge.mode() == Cartridge::ModeBsx) bsxcart.reset();
if(cartridge.bsx_flash_loaded()) bsxflash.reset();
if(cartridge.has_srtc()) srtc.reset();
if(cartridge.has_sdd1()) sdd1.reset();
if(cartridge.has_spc7110()) spc7110.reset();
if(cartridge.has_cx4()) cx4.reset();
if(cartridge.has_dsp1()) dsp1.reset();
if(cartridge.has_dsp2()) dsp2.reset();
if(cartridge.has_dsp3()) dsp3.reset();
if(cartridge.has_dsp4()) dsp4.reset();
if(cartridge.has_obc1()) obc1.reset();
if(cartridge.has_st010()) st010.reset();
input.port_set_device(0, snes.config.controller_port1);
input.port_set_device(1, snes.config.controller_port2);
input.update();
video.update();
}
void SNES::scanline() {
video.scanline();
if(ppu.vcounter() == 241) {
input.update();
video.update();
scheduler.exit();
}
}
void SNES::frame() {
}
SNES::Region SNES::region() const {
return (SNES::Region)snes_region;
}
SNES::ExpansionPortDevice SNES::expansion() const {
return (SNES::ExpansionPortDevice)snes_expansion;
}
SNES::SNES() : snes_region(NTSC), snes_expansion(ExpansionNone) {
config.controller_port1 = Input::DeviceJoypad;
config.controller_port2 = Input::DeviceJoypad;
config.expansion_port = ExpansionBSX;
config.region = Autodetect;
config.file.autodetect_type = false;
config.file.bypass_patch_crc32 = false;
config.path.base = "";
config.path.user = "";
config.path.current = "";
config.path.rom = "";
config.path.save = "";
config.path.patch = "";
config.path.cheat = "";
config.path.data = "";
config.path.bsx = "";
config.path.st = "";
config.cpu.version = 2;
config.cpu.ntsc_clock_rate = 21477272;
config.cpu.pal_clock_rate = 21281370;
config.cpu.alu_mul_delay = 2;
config.cpu.alu_div_delay = 2;
config.cpu.wram_init_value = 0x55;
config.smp.ntsc_clock_rate = 32041 * 768;
config.smp.pal_clock_rate = 32041 * 768;
config.ppu1.version = 1;
config.ppu2.version = 3;
}

84
bsnes/snes/snes.hpp Executable file
View File

@@ -0,0 +1,84 @@
#include "interface/interface.hpp"
#include "scheduler/scheduler.hpp"
#include "tracer/tracer.hpp"
class VideoFilter;
class SNES {
public:
enum Region { NTSC = 0, PAL = 1 };
enum RegionAutodetect { Autodetect = 2 };
enum ExpansionPortDevice { ExpansionNone = 0, ExpansionBSX = 1 };
struct Config {
unsigned controller_port1;
unsigned controller_port2;
unsigned expansion_port;
unsigned region;
struct File {
bool autodetect_type;
bool bypass_patch_crc32;
} file;
struct Path {
string base; //binary path
string user; //user profile path (bsnes.cfg, ...)
string current; //current working directory (path to currently loaded cartridge)
string rom, save, patch, cheat, data;
string bsx, st;
} path;
struct CPU {
unsigned version;
unsigned ntsc_clock_rate;
unsigned pal_clock_rate;
unsigned alu_mul_delay;
unsigned alu_div_delay;
unsigned wram_init_value;
} cpu;
struct SMP {
unsigned ntsc_clock_rate;
unsigned pal_clock_rate;
} smp;
struct PPU1 {
unsigned version;
} ppu1;
struct PPU2 {
unsigned version;
} ppu2;
} config;
//system functions
virtual void run();
virtual void runtoframe();
virtual void init();
virtual void term();
virtual void power();
virtual void reset();
virtual void frame();
virtual void scanline();
//return *active* region / expansion port device information
//settings cached upon power-on
Region region() const;
ExpansionPortDevice expansion() const;
#include "video/video.hpp"
#include "audio/audio.hpp"
#include "input/input.hpp"
SNES();
virtual ~SNES() {}
private:
unsigned snes_region;
unsigned snes_expansion;
};
extern SNES snes;

94
bsnes/snes/tracer/tracer.cpp Executable file
View File

@@ -0,0 +1,94 @@
#ifdef SNES_CPP
Tracer tracer;
void tprintf(const char *s, ...) {
if(tracer.enabled() == false) return;
char str[4096];
va_list args;
va_start(args, s);
vsprintf(str, s, args);
va_end(args);
fprintf(tracer.fp, "%s\r\n", str);
}
void Tracer::trace_cpuop() {
if(enabled() == false) return;
if(cpuop_enabled() == false) return;
if(cpu.in_opcode() == true) return;
if(cpuopmask_enabled() == true) {
unsigned addr = cpu.regs.pc.d;
if(settings.cpuopmasktbl[addr >> 3] & 0x80 >> (addr & 7)) return;
settings.cpuopmasktbl[addr >> 3] |= 0x80 >> (addr & 7);
}
char t[1024];
cpu.disassemble_opcode(t);
fprintf(fp, "%s\r\n", t);
}
void Tracer::trace_smpop() {
if(enabled() == false) return;
if(smpop_enabled() == false) return;
if(smp.in_opcode() == true) return;
if(smpopmask_enabled() == true) {
unsigned addr = smp.regs.pc;
if(settings.smpopmasktbl[addr >> 3] & 0x80 >> (addr & 7)) return;
settings.smpopmasktbl[addr >> 3] |= 0x80 >> (addr & 7);
}
char t[1024];
smp.disassemble_opcode(t);
fprintf(fp, "%s\r\n", t);
}
void Tracer::enable(bool en) {
if(en == true && enabled() == false) {
fp = fopen(Cartridge::filepath("trace.log", snes.config.path.data), "wb");
} else if(en == false && enabled() == true) {
fclose(fp);
fp = 0;
}
settings.enabled = en;
}
void Tracer::cpuopmask_enable(bool en) {
if(en == true && cpuopmask_enabled() == false) {
settings.cpuopmasktbl = new(zeromemory) uint8_t[0x200000];
} else if(en == false && cpuopmask_enabled() == true) {
delete[] settings.cpuopmasktbl;
}
settings.cpuopmask = en;
}
void Tracer::smpopmask_enable(bool en) {
if(en == true && smpopmask_enabled() == false) {
settings.smpopmasktbl = new(zeromemory) uint8_t[0x2000];
} else if(en == false && smpopmask_enabled() == true) {
delete[] settings.smpopmasktbl;
}
settings.smpopmask = en;
}
Tracer::Tracer() {
fp = 0;
settings.cpuop = false;
settings.cpuopmask = false;
settings.cpuopmasktbl = 0;
settings.smpop = false;
settings.smpopmask = false;
settings.smpopmasktbl = 0;
}
Tracer::~Tracer() {
}
#endif //ifdef SNES_CPP

45
bsnes/snes/tracer/tracer.hpp Executable file
View File

@@ -0,0 +1,45 @@
void tprintf(const char *s, ...);
class Tracer {
private:
FILE *fp;
struct {
bool enabled;
bool cpuop;
bool cpuopmask;
uint8 *cpuopmasktbl;
bool smpop;
bool smpopmask;
uint8 *smpopmasktbl;
} settings;
public:
void enable(bool en);
bool enabled() { return settings.enabled; }
void cpuop_enable(bool en) { settings.cpuop = en; }
bool cpuop_enabled() { return settings.cpuop; }
void cpuopmask_enable(bool en);
bool cpuopmask_enabled() { return settings.cpuopmask; }
void smpop_enable(bool en) { settings.smpop = en; }
bool smpop_enabled() { return settings.smpop; }
void smpopmask_enable(bool en);
bool smpopmask_enabled() { return settings.smpopmask; }
void trace_cpuop();
void trace_smpop();
Tracer();
~Tracer();
friend void tprintf(const char *s, ...);
};
extern Tracer tracer;

103
bsnes/snes/video/video.cpp Executable file
View File

@@ -0,0 +1,103 @@
#ifdef SNES_CPP
const uint8_t SNES::Video::cursor[15 * 15] = {
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,
0,0,0,1,2,2,1,2,1,2,2,1,0,0,0,
0,0,1,2,1,1,0,1,0,1,1,2,1,0,0,
0,1,2,1,0,0,0,1,0,0,0,1,2,1,0,
0,1,2,1,0,0,1,2,1,0,0,1,2,1,0,
1,2,1,0,0,1,1,2,1,1,0,0,1,2,1,
1,2,2,1,1,2,2,2,2,2,1,1,2,2,1,
1,2,1,0,0,1,1,2,1,1,0,0,1,2,1,
0,1,2,1,0,0,1,2,1,0,0,1,2,1,0,
0,1,2,1,0,0,0,1,0,0,0,1,2,1,0,
0,0,1,2,1,1,0,1,0,1,1,2,1,0,0,
0,0,0,1,2,2,1,2,1,2,2,1,0,0,0,
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
};
void SNES::Video::draw_cursor(uint16_t color, int x, int y) {
for(int cy = 0; cy < 15; cy++) {
int vy = y + cy - 7;
if(vy <= 0 || vy >= 240) continue; //do not draw offscreen
bool hires = (pline_width[vy] == 512);
for(int cx = 0; cx < 15; cx++) {
int vx = x + cx - 7;
if(vx < 0 || vx >= 256) continue; //do not draw offscreen
uint8_t pixel = cursor[cy * 15 + cx];
if(pixel == 0) continue;
uint16_t pixelcolor = (pixel == 1) ? 0 : color;
if(hires == false) {
*((uint16_t*)ppu.output + vy * 1024 + 0 + vx) = pixelcolor;
*((uint16_t*)ppu.output + vy * 1024 + 512 + vx) = pixelcolor;
} else {
*((uint16_t*)ppu.output + vy * 1024 + 0 + vx * 2 + 0) = pixelcolor;
*((uint16_t*)ppu.output + vy * 1024 + 512 + vx * 2 + 0) = pixelcolor;
*((uint16_t*)ppu.output + vy * 1024 + 0 + vx * 2 + 1) = pixelcolor;
*((uint16_t*)ppu.output + vy * 1024 + 512 + vx * 2 + 1) = pixelcolor;
}
}
}
}
void SNES::Video::update() {
uint16_t *data = (uint16_t*)ppu.output;
unsigned width, height;
switch(snes.input.port[1].device) {
case SNES::Input::DeviceSuperScope: draw_cursor(0x001f, snes.input.port[1].superscope.x, snes.input.port[1].superscope.y); break;
case SNES::Input::DeviceJustifiers: draw_cursor(0x02e0, snes.input.port[1].justifier.x2, snes.input.port[1].justifier.y2); //fallthrough
case SNES::Input::DeviceJustifier: draw_cursor(0x001f, snes.input.port[1].justifier.x1, snes.input.port[1].justifier.y1); break;
}
unsigned yoffset = 1; //scanline 0 is always black, skip this line for video output
if(mode == ModeNTSC && ppu.overscan()) yoffset += 8; //NTSC overscan centers x240 height image
switch(mode) { default:
case ModeNTSC: { width = 256; height = 224; } break;
case ModePAL: { width = 256; height = 239; } break;
}
if(frame_hires) width <<= 1;
if(frame_interlace) height <<= 1;
snesinterface.video_refresh(
data + yoffset * 1024,
/* pitch = */ height <= 240 ? 2048 : 1024,
/* line[] = */ height <= 240 ? (pline_width + yoffset) : (iline_width + yoffset * 2),
width, height
);
frame_hires = false;
frame_interlace = false;
}
void SNES::Video::scanline() {
unsigned y = ppu.vcounter();
if(y >= 240) return;
unsigned width = (ppu.hires() == false ? 256 : 512);
pline_width[y] = width;
iline_width[y * 2 + (int)ppu.field()] = width;
frame_hires |= ppu.hires();
frame_interlace |= ppu.interlace();
}
void SNES::Video::set_mode(Mode mode_) {
mode = mode_;
}
void SNES::Video::init() {
for(unsigned i = 0; i < 240; i++) pline_width[i] = 256;
for(unsigned i = 0; i < 480; i++) iline_width[i] = 256;
frame_hires = false;
frame_interlace = false;
set_mode(ModeNTSC);
}
#endif //ifdef SNES_CPP

25
bsnes/snes/video/video.hpp Executable file
View File

@@ -0,0 +1,25 @@
class Video {
public:
enum Mode {
ModeNTSC,
ModePAL,
};
void set_mode(Mode);
private:
Mode mode;
bool frame_hires;
bool frame_interlace;
unsigned pline_width[240]; //progressive
unsigned iline_width[480]; //interlace
void update();
void scanline();
void init();
static const uint8_t cursor[15 * 15];
void draw_cursor(uint16_t color, int x, int y);
friend class SNES;
} video;