lsnes/core-freestanding.cpp

1505 lines
44 KiB
C++

#include <cstddef>
#include <functional>
#include <iostream>
#include <iomanip>
#include <sstream>
#include "c-interface-translate.hpp"
#define PROFILE_COMPATIBILITY
#define DEBUGGER
#include "snes/snes.hpp"
#include "gameboy/gameboy.hpp"
#include "snes/chip/icd2/gameboy.hpp"
#include "gameboy/gameboy.hpp"
#include "ui-libsnes/libsnes.hpp"
#define HAVE_CSTDINT
#include "libgambatte/include/gambatte.h"
namespace
{
bool disable_breakpoints = false; //Breakpoint temporary disable.
gambatte::GB* gb_instance; //Gambatte instance to use.
bool trace_cpu_enable = false; //True if tracing S-CPU is enabled.
bool trace_smp_enable = false; //True if tracing S-SMP is enabled.
unsigned delayreset_cycles_run = 0; //Number of cycles already ran for delay reset.
unsigned delayreset_cycles_target = 0; //Number of cycles to run for delay reset.
bool video_refresh_done = false; //Set to false on start of frame, set to true on video refresh.
bool forced_hook = false; //True if there is forced SNES hook function.
std::string core_bsnes_gambatte_name; //Name of core.
bool gb_raw_signal_enabled = false; //If to enable GB raw signal display.
uint32_t gb_raw_signal[160*144]; //The GB raw signal display.
uint64_t gb_tsc; //Gameboy TSC.
#include "tempmem.hpp"
#include "callbacks.hpp"
#include "controllerjson.hpp"
#include "memregions.hpp"
#include "registers.hpp"
#include "strfmt.hpp"
#include "gbdisasm.hpp"
#include "debug.hpp"
#include "sram.hpp"
#include "serialize.hpp"
using lsnes_interface::e2t;
//Interface functions return error, so NULL is success.
#define RET_OK NULL
#define def_sysregion_sgb_ntsc 0
#define def_core_bsnes_gambatte 0
#define def_region_ntsc 0
#define def_type_supergameboy 0
#define LSNES_END_LIST 0xFFFFFFFFU
#define TMP_ERROR 0
#define TMP_AUDIO 1
#define TMP_TXTBMP 2
#define TMP_TEXT 3
#define TMP_VIDEO 4
#define TMP_UNSPEC 5
#define DURATION_NTSC_FRAME 357366
#define DURATION_NTSC_FIELD 357368
#define CASE_CALL(X) \
case X : \
ret = interface(item, *(e2t<X>::t)params); \
break
#define CASE_CALL_NOITEM(X) \
case X : \
ret = interface(*(e2t<X>::t)params); \
break
#define EMIT_SUBCASE(X, I) \
case def_##I : \
ret = I (*(e2t<X>::t)params); \
break
#define CASE_CALL_CORE(X) \
case X : \
switch(item) { \
EMIT_SUBCASE(X, core_bsnes_gambatte ); \
default: \
ret = complain_unknown_core(item); \
break; \
} \
break
#define CASE_CALL_TYPE(X) \
case X : \
switch(item) { \
EMIT_SUBCASE(X, type_supergameboy ); \
default: \
ret = complain_unknown_type(item); \
break; \
} \
break
#define CASE_CALL_REGION(X) \
case X : \
switch(item) { \
EMIT_SUBCASE(X, region_ntsc ); \
default: \
ret = complain_unknown_type(item); \
break; \
} \
break
#define CASE_CALL_SYSREGION(X) \
case X : \
switch(item) { \
EMIT_SUBCASE(X, sysregion_sgb_ntsc ); \
default: \
ret = complain_unknown_type(item); \
break; \
} \
break
unsigned sysregions_supported[] = { def_sysregion_sgb_ntsc, LSNES_END_LIST };
const char* trace_cpu_list[] = {"S-CPU", "S-SMP", "GB-CPU", NULL};
static unsigned compat_regions_ntsc[] = {def_region_ntsc, LSNES_END_LIST};
unsigned regions_sgb[] = {def_region_ntsc, LSNES_END_LIST};
unsigned controller_count[] = {0, 1, 1, 4, 4, 1, 1, 2, 1, 2};
lsnes_core_get_core_info_aparam delayparam[] = {
{"delay", "int:0,999999999"},
{NULL, NULL}
};
lsnes_core_get_core_info_aparam checkparam[] = {
{"", "toggle"},
{NULL, NULL}
};
lsnes_core_get_core_info_action actions_list[] = {
{0, "reset", "Soft reset", NULL},
{1, "hardreset", "Hard reset", NULL},
{2, "delayreset", "Delayed soft reset", delayparam},
{3, "delayhardreset", "Delayed hard reset", delayparam},
{4, "bg1pri0", "Layers‣BG1 Priority 0", checkparam},
{5, "bg1pri1", "Layers‣BG1 Priority 1", checkparam},
{8, "bg2pri0", "Layers‣BG2 Priority 0", checkparam},
{9, "bg2pri1", "Layers‣BG2 Priority 1", checkparam},
{12, "bg3pri0", "Layers‣BG3 Priority 0", checkparam},
{13, "bg3pri1", "Layers‣BG3 Priority 1", checkparam},
{16, "bg4pri0", "Layers‣BG4 Priority 0", checkparam},
{17, "bg4pri1", "Layers‣BG4 Priority 1", checkparam},
{20, "oampri0", "Layers‣Sprite Priority 0", checkparam},
{21, "oampri1", "Layers‣Sprite Priority 1", checkparam},
{22, "oampri2", "Layers‣Sprite Priority 2", checkparam},
{23, "oampri3", "Layers‣Sprite Priority 3", checkparam},
{-1, NULL, NULL, NULL}
};
lsnes_core_get_type_info_romimage sgb_images[] = {
{"rom", "SGB BIOS", 1, 0, 512, "sfc;smc;swc;fig;ufo;sf2;gd3;gd7;dx2;mgd;mgh"},
{"dmg", "DMG ROM", 2, 0, 512, "gb;dmg;sgb"},
{NULL, NULL, 0, 0, 0, NULL}
};
lsnes_core_get_type_info_paramval boolean_values[] = {
{"0", "false", 0},
{"1", "true", 1},
{NULL, NULL, -1}
};
lsnes_core_get_type_info_paramval port1_values[] = {
{"none", "None", 0},
{"gamepad", "Gamepad", 1},
{"gamepad16", "Gamepad (16-button)", 2},
{"ygamepad16", "Y-cabled gamepad (16-button)", 9},
{"multitap", "Multitap", 3},
{"multitap16", "Multitap (16-button)", 4},
{"mouse", "Mouse", 5},
{NULL, NULL, -1}
};
lsnes_core_get_type_info_paramval port2_values[] = {
{"none", "None", 0},
{"gamepad", "Gamepad", 1},
{"gamepad16", "Gamepad (16-button)", 2},
{"ygamepad16", "Y-cabled gamepad (16-button)", 9},
{"multitap", "Multitap", 3},
{"multitap16", "Multitap (16-button)", 4},
{"mouse", "Mouse", 5},
{"superscope", "Super Scope", 8},
{"justifier", "Justifier", 6},
{"justifiers", "2 Justifiers", 7},
{NULL, NULL, -1}
};
lsnes_core_get_type_info_param bsnes_gambatte_settings[] = {
{"port1", "Port 1 Type", "gamepad", port1_values, NULL},
{"port2", "Port 2 Type", "none", port2_values, NULL},
{"saveevery", "Emulate saving each frame", "0", boolean_values, NULL},
{"radominit", "Random initial state", "0", boolean_values, NULL},
{NULL, NULL, NULL, NULL, NULL}
};
//Variables
bool last_interlace = false; //Last frame was interlaced.
int internal_rom = -1; //Internal ROM type.
SNES::Interface* old = NULL; //Old SNES interface object.
bool port_is_ycable[2]; //Port has Y-Cable connected flags for both ports.
int16_t soundbuf[8192] = {0}; //Sound buffer.
size_t soundbuf_fill = 0; //Amount of data in sound buffer.
bool stepping_into_save = false; //Set when running to save point, false otherwise.
bool have_saved_this_frame = false; //True if state has been saved this frame.
int do_reset_flag = -1; //Number of cycles to next reset (<0 if no reset).
bool do_hreset_flag = false; //True if next reset will be hard.
bool last_hires = false; //Last frame was hires.
bool save_every_frame = false; //True to emulate saving each frame.
bool reallocate_debug = true; //Next load will reallocate debug buffers.
gambatte::debugbuffer debugbuf; //Gambatte debug buffer.
size_t gb_cur_romsize; //Current GB ROM size.
size_t gb_cur_ramsize; //Current GB RAM size.
bool rtc_fixed; //RTC fixed?
time_t rtc_fixed_val; //Value RTC is fixed to.
std::vector<unsigned char> gb_romdata; //Gameboy ROM data.
unsigned current_ly; //Current gameboy LY.
uint8_t mlt_req; //Mlt_req value for SGB.
uint16_t current_scanline[160]; //Current scanline buffer (converted).
uint8_t sgb_button_state; //Current button state from SNES.
uint8_t last_fdiv; //Last frequency divider.
uint8_t p1_value;
bool pflag; //Polled flag.
bool pending_poll; //Poll is pending.
GameBoy::Interface* bsnes_if; //BSNES GB interface.
const char* complain_unknown_item(const char* itype, unsigned item)
{
return tmp_sprintf(TMP_ERROR, "Unknown %s type %u", itype, item);
}
const char* complain_unknown_core(unsigned type)
{
return complain_unknown_item("core", type);
}
const char* complain_unknown_type(unsigned type)
{
return complain_unknown_item("type", type);
}
const char* complain_unknown_region(unsigned type)
{
return complain_unknown_item("region", type);
}
const char* complain_unknown_sysregion(unsigned type)
{
return complain_unknown_item("sysregion", type);
}
lsnes_core_get_controllerconfig_logical_entry mklcid(unsigned p, unsigned c)
{
lsnes_core_get_controllerconfig_logical_entry e;
e.port = p;
e.controller = c;
return e;
}
struct settings_decoded
{
settings_decoded(lsnes_core_system_setting* settings)
{
type1 = 1;
type2 = 0;
esave = 0;
irandom = 0;
for(lsnes_core_system_setting* i = settings; i->name; i++) {
signed settings_decoded::*ptr = NULL;
if(!strcmp(i->name, "port1")) ptr = &settings_decoded::type1;
if(!strcmp(i->name, "port2")) ptr = &settings_decoded::type2;
if(!strcmp(i->name, "saveevery")) ptr = &settings_decoded::esave;
if(!strcmp(i->name, "radominit")) ptr = &settings_decoded::irandom;
for(lsnes_core_get_type_info_param* j = bsnes_gambatte_settings; j->iname; j++) {
if(!strcmp(j->iname, i->name)) {
for(lsnes_core_get_type_info_paramval* k = j->values; k->iname; k++) {
if(!strcmp(k->iname, i->value))
this->*ptr = k->index;
}
}
}
}
}
signed type1;
signed type2;
signed esave;
signed irandom;
};
struct bsnes_interface : public SNES::Interface
{
string path(SNES::Cartridge::Slot slot, const string &hint)
{
const char* _hint = hint;
std::string _hint2 = _hint;
std::string fwp = cb_get_firmware_path();
std::string finalpath = fwp + "/" + _hint2;
return finalpath.c_str();
}
time_t currentTime()
{
return cb_get_time();
}
time_t randomSeed()
{
return cb_get_randomseed();
}
void notifyLatched()
{
cb_notify_latch(NULL);
}
void videoRefresh(const uint32_t* data, bool hires, bool interlace, bool overscan);
void audioSample(int16_t l_sample, int16_t r_sample)
{
soundbuf[soundbuf_fill++] = l_sample;
soundbuf[soundbuf_fill++] = r_sample;
//The SMP emits a sample every 768 ticks of its clock. Use this in order to keep track of
//time.
cb_timer_tick(768, SNES::system.apu_frequency());
}
int16_t inputPoll(bool port, SNES::Input::Device device, unsigned index, unsigned id)
{
if(port_is_ycable[port ? 1 : 0]) {
int16_t bit0 = cb_get_input(port ? 2 : 1, 0, id);
int16_t bit1 = cb_get_input(port ? 2 : 1, 1, id);
return bit0 + 2 * bit1;
}
int16_t offset = 0;
//The superscope/justifier handling is nuts.
if(port && SNES::input.port2) {
SNES::SuperScope* ss = dynamic_cast<SNES::SuperScope*>(SNES::input.port2);
SNES::Justifier* js = dynamic_cast<SNES::Justifier*>(SNES::input.port2);
if(ss && index == 0) {
if(id == 0)
offset = ss->x;
if(id == 1)
offset = ss->y;
}
if(js && index == 0) {
if(id == 0)
offset = js->player1.x;
if(id == 1)
offset = js->player1.y;
}
if(js && js->chained && index == 1) {
if(id == 0)
offset = js->player2.x;
if(id == 1)
offset = js->player2.y;
}
}
return cb_get_input(port ? 2 : 1, index, id) - offset;
}
} my_interface_obj;
void bsnes_interface::videoRefresh(const uint32_t* data, bool hires, bool interlace, bool overscan)
{
last_hires = hires;
last_interlace = interlace;
if(stepping_into_save) {
(message_output() << "Got video refresh in runtosave, expect desyncs!" << std::endl).end();
}
video_refresh_done = true;
uint32_t fps_n, fps_d;
fps_n = SNES::system.cpu_frequency();
fps_d = interlace ? DURATION_NTSC_FIELD : DURATION_NTSC_FRAME;
lsnes_core_framebuffer_info inf;
inf.type = LSNES_CORE_PIXFMT_LRGB;
inf.mem = const_cast<char*>(reinterpret_cast<const char*>(data));
inf.physwidth = 512;
inf.physheight = 512;
inf.physstride = 2048;
inf.width = hires ? 512 : (gb_raw_signal_enabled ? 416 : 256);
inf.height = 224 * (interlace ? 2 : 1);
inf.stride = interlace ? 2048 : 4096;
inf.offset_x = 0;
inf.offset_y = (overscan ? 16 : 9) * 2;
if(gb_raw_signal_enabled && !hires) {
uint32_t* tmp = tmpalloc_array<uint32_t>(TMP_VIDEO, 512 * 512);
memcpy(tmp, inf.mem, 512 * 512 * sizeof(uint32_t));
inf.mem = (const char*)tmp;
for(uint32_t y = 0; y < 144; y++) {
memcpy(tmp + (inf.offset_y + y) * (inf.stride / sizeof(uint32_t)) + (inf.offset_x + 256),
gb_raw_signal + 160 * y, 160 * sizeof(uint32_t));
for(uint32_t x = 0; x < 160; x++)
gb_raw_signal[160 * y + x] &= 0x7801F;
}
}
cb_submit_frame(&inf, fps_n, fps_d);
if(soundbuf_fill > 0) {
auto freq = SNES::system.apu_frequency();
cb_submit_sound(soundbuf, soundbuf_fill / 2, true, freq / 768.0);
soundbuf_fill = 0;
}
}
class myinput : public gambatte::InputGetter
{
public:
unsigned operator()()
{
//Input is read elsewhere.
return 0;
};
} getinput;
uint8_t read_gb_inputs(bool p14, bool p15)
{
uint8_t u = 0xF;
unsigned v = 0;
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::Right) ? 0x10 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::Left) ? 0x20 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::Up) ? 0x40 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::Down) ? 0x80 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::A) ? 0x01 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::B) ? 0x02 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::Select) ? 0x04 : 0);
v |= (bsnes_if->inputPoll((unsigned)GameBoy::Input::Start) ? 0x08 : 0);
if(p14 && p15) u -= mlt_req;
if(!p14) u &= ((v >> 4) ^ 15);
if(!p15) u &= ((v & 15) ^ 15);
if(p14) u |= 0x10;
if(p15) u |= 0x20;
return u;
}
gambatte::extra_callbacks gambatte_sgb_hooks = {
.context = NULL,
.read_p1_high = [](void* context) -> uint8_t {
return 0xF - mlt_req;
},
.write_p1 = [](void* context, uint8_t value) -> void {
bool p14 = (value & 0x10);
bool p15 = (value & 0x20);
bsnes_if->joypWrite(p15, p14);
p1_value = value;
//If either line is low, mark poll to occur on next read.
pending_poll = (!p14 || !p15);
},
.lcd_scan = [](void* context, uint8_t y, const uint32_t* data)
{
current_ly = y;
if(y < 144) {
for(size_t i = 0; i < 160; i++)
current_scanline[i] = data[i];
if(gb_raw_signal_enabled) {
for(size_t i = 0; i < 160; i++)
gb_raw_signal[160 * y + i] = 0x78000 + ((~data[i]) & 3) * 0x294A;
}
bsnes_if->lcdScanline();
}
},
.read_p1 = [](void* context, uint8_t& v) -> bool
{
bool p14 = (p1_value & 0x10);
bool p15 = (p1_value & 0x20);
v = read_gb_inputs(p14, p15);
//If there is pending poll, do the actual poll.
if(pending_poll) pflag = true;
return true;
},
.ppu_update_timeslice = true
};
// FDIV Soundrate
// 8 21477272/512
// 10 21477272/640
// 14 21477272/896
// 18 21477272/1152
//
SNES::gameboy_interface gambatte_gb_if = {
.do_init = [](GameBoy::Interface* interface) -> void {
//Record the interface and reset the system.
bsnes_if = interface;
gb_instance->reset();
},
.get_ly = []() -> unsigned {
return current_ly;
},
.get_cur_scanline = []() -> uint16_t* {
return current_scanline;
},
.set_mlt_req = [](uint8_t req) -> void {
mlt_req = req;
},
.serialize = [](nall::serializer& s) -> void {
//This can't be done right, do it outside bsnes. Nothing to do here.
},
.runtosave = []() -> void {
//Nothing to do.
},
.run_timeslice = [](SNES::Coprocessor* proc) -> void {
//Dummy framebuffer.
uint32_t dummy_fb[160 * 144];
int64_t slice;
uint32_t* abuffer = NULL;
unsigned _slice;
uint16_t old_ip, new_ip;
//Calculate number of SNES master clock ticks to run.
again:
slice = -proc->clock / SNES::cpu.frequency;
//Correct for frequency divider (FIXME: this doesn't correct for divier changes).
//Also always run one cycle too long. Otherwise scheduler will lock up.
slice = slice / SNES::icd2.fdiv + 1;
if(slice <= 0)
return; //No time to run.
//Now slice is number of GB clocks to run.
if(last_fdiv != SNES::icd2.fdiv) {
last_fdiv = SNES::icd2.fdiv;
SNES::audio.coprocessor_frequency((float)SNES::cpu.frequency / last_fdiv);
}
_slice = slice;
abuffer = tmpalloc_array<uint32_t>(TMP_AUDIO, slice + 2064);
old_ip = gb_instance->get_cpureg(gambatte::GB::REG_PC);
gb_instance->runFor(dummy_fb, 160, abuffer, _slice);
new_ip = gb_instance->get_cpureg(gambatte::GB::REG_PC);
for(unsigned i = 0; i < _slice; i++) {
int16_t l, r;
l = abuffer[i];
r = abuffer[i] >> 16;
SNES::audio.coprocessor_sample(l, r);
}
gb_tsc += _slice;
proc->step(_slice * last_fdiv);
goto again;
},
.cartridge_sha256 = []() -> nall::string {
return sha256(&gb_romdata[0], gb_romdata.size());
},
.default_rates = false,
};
unsigned index_to_bsnes_type[] = {
SNES_DEVICE_NONE, SNES_DEVICE_JOYPAD, SNES_DEVICE_JOYPAD, SNES_DEVICE_MULTITAP, SNES_DEVICE_MULTITAP,
SNES_DEVICE_MOUSE, SNES_DEVICE_JUSTIFIER, SNES_DEVICE_JUSTIFIERS, SNES_DEVICE_SUPER_SCOPE,
SNES_DEVICE_JOYPAD
};
std::string get_cartridge_name()
{
std::ostringstream name;
if(gb_romdata.size() < 0x200)
return ""; //Bad.
for(unsigned i = 0; i < 16; i++) {
unsigned char ch = gb_romdata[0x134 + i];
if(ch) {
if(ch >= 32 && ch <= 126)
name << (char)ch;
else
name << hexch[ch >> 4] << hexch[ch & 15];
} else
break;
}
return name.str();
}
time_t walltime_fn()
{
if(rtc_fixed)
return rtc_fixed_val;
if(cb_get_time)
return cb_get_time();
else
return time(0);
}
const char* load_rom(unsigned ctype, lsnes_core_load_rom_image* images, lsnes_core_system_setting* settings,
uint64_t secs, uint64_t subsecs)
{
settings_decoded ds(settings);
snes_term();
snes_unload_cartridge();
SNES::config.random = (ds.irandom != 0);
save_every_frame = (ds.esave != 0);
SNES::config.expansion_port = SNES::System::ExpansionPortDevice::None;
SNES::config.cpu.alt_poll_timings = true;
//Reset Gambatte really.
gb_instance->~GB();
memset(gb_instance, 0, sizeof(gambatte::GB));
new(gb_instance) gambatte::GB;
gb_instance->setInputGetter(&getinput);
gb_instance->set_walltime_fn(walltime_fn);
gb_instance->setExtraCallbacks(&gambatte_sgb_hooks);
rtc_fixed = true;
rtc_fixed_val = secs;
//Flags is always 0.
if(gb_instance->load((const unsigned char*)images[1].data, images[1].size, 0,
GameBoy::System::BootROM::sgb, sizeof(GameBoy::System::BootROM::sgb)) < 0)
return "Error loading ROM";
size_t sramsize = gb_instance->getSaveRam().second;
size_t romsize = images[1].size;
if(reallocate_debug || gb_cur_ramsize != sramsize || gb_cur_romsize != romsize) {
if(debugbuf.cart) delete[] debugbuf.cart;
if(debugbuf.sram) delete[] debugbuf.sram;
debugbuf.cart = NULL;
debugbuf.sram = NULL;
if(sramsize) debugbuf.sram = new uint8_t[(sramsize + 4095) >> 12 << 12];
if(romsize) debugbuf.cart = new uint8_t[(romsize + 4095) >> 12 << 12];
if(sramsize) memset(debugbuf.sram, 0, (sramsize + 4095) >> 12 << 12);
if(romsize) memset(debugbuf.cart, 0, (romsize + 4095) >> 12 << 12);
memset(debugbuf.wram, 0, 32768);
memset(debugbuf.ioamhram, 0, 512);
debugbuf.wramcheat.clear();
debugbuf.sramcheat.clear();
debugbuf.cartcheat.clear();
debugbuf.trace_cpu = false;
reallocate_debug = false;
gb_cur_ramsize = sramsize;
gb_cur_romsize = romsize;
}
gb_instance->set_debug_buffer(debugbuf);
gb_instance->set_emuflags(1); //Crash on SIGILL.
rtc_fixed = false;
gb_romdata.resize(images[1].size);
memcpy(&gb_romdata[0], images[1].data, images[1].size);
//Setup palette (it is fixed for SGB).
for(unsigned i = 0; i < 12; i++)
gb_instance->setDmgPaletteColor(i >> 2, i & 3, i & 3);
SNES::gb_override_flag = true;
SNES::gb_if = &gambatte_gb_if;
//Because we pass the GB ROM to bsnes, its checksum will be correct even with default loadif.
bool r = snes_load_cartridge_super_game_boy(images[0].markup, (unsigned char*)images[0].data, images[0].size,
images[1].markup, (unsigned char*)images[1].data, images[1].size);
if(r) {
last_fdiv = SNES::icd2.fdiv;
internal_rom = ctype;
snes_set_controller_port_device(false, index_to_bsnes_type[ds.type1]);
snes_set_controller_port_device(true, index_to_bsnes_type[ds.type2]);
port_is_ycable[0] = (ds.type1 == 9);
port_is_ycable[1] = (ds.type2 == 9);
have_saved_this_frame = false;
do_reset_flag = -1;
current_ly = 0;
mlt_req = 0;
sgb_button_state = 0;
p1_value = 0;
memset(current_scanline, 0, 320);
pending_poll = false;
pflag = false;
gb_tsc = 0;
cb_notify_action_update();
}
return r ? NULL : "Error loading ROM";
}
void bsnes_call_power_or_reset(bool hard)
{
if(hard) {
SNES::gb_override_flag = true;
SNES::system.power();
//Poweron initializes this.
last_fdiv = SNES::icd2.fdiv;
} else
SNES::system.reset();
}
const char* interface(lsnes_core_enumerate_cores& arg)
{
if(getenv("GB_SHOW_DEBUG_SCREEN"))
gb_raw_signal_enabled = true;
static bool basic_init_done = false;
if(!basic_init_done) {
SNES::bus.debug_read = bsnes_debug_read;
SNES::bus.debug_write = bsnes_debug_write;
old = SNES::interface;
SNES::interface = &my_interface_obj;
SNES::system.init();
gb_instance = new gambatte::GB;
gb_instance->setInputGetter(&getinput);
gb_instance->set_walltime_fn(walltime_fn);
uint8_t* tmp = new uint8_t[98816];
memset(tmp, 0, 98816);
debugbuf.wram = tmp;
debugbuf.bus = tmp + 32768;
debugbuf.ioamhram = tmp + 98304;
debugbuf.read = gambatte_debug_read;
debugbuf.write = gambatte_debug_write;
debugbuf.trace = gambatte_debug_trace;
debugbuf.trace_cpu = false;
debugbuf.cart = NULL;
debugbuf.sram = NULL;
gb_instance->set_debug_buffer(debugbuf);
}
arg.sysregions = sysregions_supported;
record_callbacks(arg);
gb_add_disasms();
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_core_info& arg)
{
arg.json = bsnes_gambatte_controller_json;
arg.root_ptr = "ports";
arg.shortname = "bsnes-gambatte";
arg.fullname = "bsnes v085 / Gambatte r537";
core_bsnes_gambatte_name = arg.fullname;
arg.cap_flags1 = LSNES_CORE_CAP1_MULTIREGION | LSNES_CORE_CAP1_PFLAG | LSNES_CORE_CAP1_ACTION |
LSNES_CORE_CAP1_BUSMAP | LSNES_CORE_CAP1_SRAM | LSNES_CORE_CAP1_RESET |
LSNES_CORE_CAP1_SCALE | LSNES_CORE_CAP1_RUNTOSAVE | LSNES_CORE_CAP1_POWERON |
LSNES_CORE_CAP1_UNLOAD | LSNES_CORE_CAP1_DEBUG | LSNES_CORE_CAP1_TRACE |
LSNES_CORE_CAP1_CHEAT | LSNES_CORE_CAP1_COVER | LSNES_CORE_CAP1_PREEMULATE |
LSNES_CORE_CAP1_REGISTERS | LSNES_CORE_CAP1_MEMWATCH | LSNES_CORE_CAP1_LIGHTGUN;
arg.actions = actions_list;
arg.trace_cpu_list = trace_cpu_list;
return RET_OK;
}
const char* type_supergameboy(lsnes_core_get_type_info& arg)
{
arg.core = def_core_bsnes_gambatte;
arg.iname = "sgb";
arg.hname = "Super Game Boy";
arg.sysname = "SGB";
arg.bios = "sgb.sfc";
arg.regions = regions_sgb;
arg.images = sgb_images;
arg.settings = bsnes_gambatte_settings;
return RET_OK;
}
const char* region_ntsc(lsnes_core_get_region_info& arg)
{
arg.iname = "ntsc";
arg.hname = "NTSC";
arg.priority = 0;
arg.multi = 0;
//Apparently the fps is wrong way around.
arg.fps_d = 10738636;
arg.fps_n = 178683;
arg.compatible_runs = compat_regions_ntsc;
return RET_OK;
}
const char* sysregion_sgb_ntsc(lsnes_core_get_sysregion_info& arg)
{
arg.name = "gambatte_sgb_ntsc";
arg.type = def_type_supergameboy;
arg.region = def_region_ntsc;
arg.for_system = "SGB";
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_av_state& arg)
{
if(internal_rom < 0) {
arg.fps_n = 60;
arg.fps_d = 1;
arg.par = 1.0;
arg.rate_n = 64081;
arg.rate_d = 2;
} else {
arg.fps_n = SNES::system.cpu_frequency();
arg.fps_d = last_interlace ? DURATION_NTSC_FIELD : DURATION_NTSC_FRAME;
arg.par = 1.146;
arg.rate_n = SNES::system.apu_frequency();
arg.rate_d = static_cast<uint32_t>(768);
}
arg.lightgun_width = 256;
arg.lightgun_height = 224;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_emulate& arg)
{
if(internal_rom < 0)
return RET_OK;
bool was_delay_reset = false;
int16_t reset = cb_get_input(0, 0, 1);
int16_t hreset = 0;
hreset = cb_get_input(0, 0, 4);
if(reset) {
long hi = cb_get_input(0, 0, 2);
long lo = cb_get_input(0, 0, 3);
long delay = 10000 * hi + lo;
if(delay > 0) {
was_delay_reset = true;
(message_output() << "Executing delayed reset... This can take some time!"
<< std::endl).end();
video_refresh_done = false;
delayreset_cycles_run = 0;
delayreset_cycles_target = delay;
forced_hook = true;
SNES::cpu.step_event = bsnes_delayreset_fn;
again:
SNES::system.run();
if(SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::DebuggerEvent
&& SNES::debugger.break_event == SNES::Debugger::BreakEvent::BreakpointHit) {
goto again;
}
SNES::cpu.step_event = nall::function<bool()>();
forced_hook = false;
bsnes_update_trace_hook_state();
if(video_refresh_done) {
//Force the reset here.
(message_output() << "SNES reset (forced at " << delayreset_cycles_run << ")"
<< std::endl).end();
bsnes_call_power_or_reset(hreset);
goto out;
}
bsnes_call_power_or_reset(hreset);
(message_output() << "SNES reset (delayed " << delayreset_cycles_run << ")"
<< std::endl).end();
} else if(delay == 0) {
bsnes_call_power_or_reset(hreset);
(message_output() << "SNES reset" << std::endl).end();
}
}
out:
do_reset_flag = -1;
if(!have_saved_this_frame && save_every_frame && !was_delay_reset)
SNES::system.runtosave();
if(trace_cpu_enable)
SNES::cpu.step_event = bsnes_trace_fn;
again2:
SNES::system.run();
if(SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::DebuggerEvent &&
SNES::debugger.break_event == SNES::Debugger::BreakEvent::BreakpointHit) {
goto again2;
}
SNES::cpu.step_event = nall::function<bool()>();
have_saved_this_frame = false;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_savestate& arg)
{
if(internal_rom < 0)
return "No ROM loaded";
//Grab gambatte state.
std::vector<char> gb_state;
gb_instance->saveState(gb_state);
//A few other variables that need saving.
std::vector<char> oth_state;
oth_state.resize(340);
write_val32(&oth_state[0], current_ly);
write_val8(&oth_state[4], mlt_req);
write_val8(&oth_state[5], sgb_button_state);
write_val8(&oth_state[6], last_fdiv);
write_val8(&oth_state[7], p1_value);
write_val16a(&oth_state[8], current_scanline, 160);
write_val8(&oth_state[328], (pending_poll ? 1 : 0) | (pflag ? 2 : 0));
write_val32(&oth_state[332], gb_tsc >> 32);
write_val32(&oth_state[336], gb_tsc);
//Grab BSNES state.
serializer snes_state = SNES::system.serialize();
//Put the states all together.
char* tmp;
arg.size = 12 + gb_state.size() + oth_state.size() + snes_state.size();
arg.data = tmp = tmpalloc_array<char>(TMP_UNSPEC, arg.size);
write_val32(tmp + 0, gb_state.size());
write_val32(tmp + 4, oth_state.size());
write_val32(tmp + 8, snes_state.size());
size_t ptr = 12;
memcpy(tmp + ptr, &gb_state[0], gb_state.size());
ptr += gb_state.size();
memcpy(tmp + ptr, &oth_state[0], oth_state.size());
ptr += oth_state.size();
memcpy(tmp + ptr, snes_state.data(), snes_state.size());
ptr += snes_state.size();
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_loadstate& arg)
{
if(internal_rom < 0)
return "No ROM loaded";
unsigned gb_size, oth_size, snes_size;
if(arg.size < 12)
return "Bad savestate, too small";
read_val32(arg.data + 0, gb_size);
read_val32(arg.data + 4, oth_size);
read_val32(arg.data + 8, snes_size);
if(arg.size < (uint64_t)12 + gb_size + oth_size + snes_size)
return "Bad savestate, truncated";
if(oth_size != 340)
return "Bad savestate, expected 340 bytes of other data";
//Other state.
const char* oth_base = (const char*)arg.data + 12 + gb_size;
std::vector<char> oth_s(oth_base, oth_base + oth_size);
read_val32(&oth_s[0], current_ly);
read_val8(&oth_s[4], mlt_req);
read_val8(&oth_s[5], sgb_button_state);
read_val8(&oth_s[6], last_fdiv);
read_val8(&oth_s[7], p1_value);
read_val16a(&oth_s[8], current_scanline, 160);
uint8_t tmp;
uint32_t tsc_l, tsc_h;
read_val8(&oth_s[328], tmp);
read_val32(&oth_s[332], tsc_h);
read_val32(&oth_s[336], tsc_l);
//SNES state.
serializer snes_s(reinterpret_cast<const uint8_t*>(arg.data + 12 + gb_size + oth_size), snes_size);
SNES::gb_override_flag = true; //Unserialize calls power.
if(!SNES::system.unserialize(snes_s))
return "SGB core rejected savestate";
//Due to what SNES does, restore GB state after SNES state.
const char* gb_base = (const char*)arg.data + 12;
std::vector<char> gb_s(gb_base, gb_base + gb_size);
gb_instance->loadState(gb_s);
pending_poll = (tmp & 1);
pflag = (tmp & 2);
gb_tsc = ((uint64_t)tsc_h << 32) + tsc_l;
have_saved_this_frame = true;
do_reset_flag = -1;
return RET_OK;
}
const char* type_supergameboy(lsnes_core_get_controllerconfig& arg)
{
settings_decoded ds(arg.settings);
static unsigned ports[4];
static lsnes_core_get_controllerconfig_logical_entry lcid[9];
ports[0] = 11;
ports[1] = ds.type1;
ports[2] = ds.type2;
ports[3] = LSNES_END_LIST;
arg.controller_types = ports;
arg.logical_map = lcid;
unsigned p1controllers = controller_count[ds.type1];
unsigned p2controllers = controller_count[ds.type2];
if(p1controllers == 4) {
arg.logical_map[0] = mklcid(1, 0);
for(size_t j = 0; j < p2controllers; j++)
arg.logical_map[j + 1] = mklcid(2U, j);
for(size_t j = 1; j < p1controllers; j++)
arg.logical_map[j + p2controllers] = mklcid(1U, j);
} else {
for(size_t j = 0; j < p1controllers; j++)
arg.logical_map[j] = mklcid(1, j);
for(size_t j = 0; j < p2controllers; j++)
arg.logical_map[j + p1controllers] = mklcid(2U, j);
}
arg.logical_map[p1controllers + p2controllers] = mklcid(0U, 0U);
return RET_OK;
}
const char* type_supergameboy(lsnes_core_load_rom& arg)
{
return load_rom(def_type_supergameboy, arg.images, arg.settings, arg.rtc_sec, arg.rtc_subsec);
}
const char* core_bsnes_gambatte(lsnes_core_get_region& arg)
{
arg.region = def_region_ntsc;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_set_region& arg)
{
switch(arg.region) {
case def_region_ntsc:
return RET_OK;
default:
return complain_unknown_region(arg.region);
}
}
const char* interface(lsnes_core_deinitialize& arg)
{
delete gb_instance;
delete[] debugbuf.wram;
delete[] debugbuf.cart;
delete[] debugbuf.sram;
debugbuf.wramcheat.clear();
debugbuf.sramcheat.clear();
debugbuf.cartcheat.clear();
SNES::interface = old;
gb_remove_disasms();
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_pflag& arg)
{
arg.pflag = pflag ? 1 : 0;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_set_pflag& arg)
{
pflag = arg.pflag;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_action_flags& arg)
{
if(arg.action >= 0 && arg.action <= 3)
arg.flags = 1;
else if(arg.action >= 4 && arg.action <= 23) {
unsigned y = (arg.action - 4) / 4;
arg.flags = SNES::ppu.layer_enabled[y][arg.action % 4] ? 3 : 1;
} else
arg.flags = 0;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_execute_action& arg)
{
switch(arg.action) {
case 0: //Soft reset.
do_reset_flag = 0;
do_hreset_flag = false;
break;
case 1: //Hard reset.
do_reset_flag = 0;
do_hreset_flag = true;
break;
case 2: //Delayed soft reset.
do_reset_flag = arg.params[0].integer;
do_hreset_flag = false;
break;
case 3: //Delayed hard reset.
do_reset_flag = arg.params[0].integer;
do_hreset_flag = true;
break;
}
if(arg.action >= 4 && arg.action <= 23) {
unsigned y = (arg.action - 4) / 4;
SNES::ppu.layer_enabled[y][arg.action % 4] = !SNES::ppu.layer_enabled[y][arg.action % 4];
cb_notify_action_update();
}
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_bus_mapping& arg)
{
arg.base = MAP_BASE_SNES_BUS;
arg.size = 0x1010000;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_enumerate_sram& arg)
{
std::set<std::string> set;
if(internal_rom >= 0) {
for(unsigned i = 0; i < SNES::cartridge.nvram.size(); i++) {
SNES::Cartridge::NonVolatileRAM& s = SNES::cartridge.nvram[i];
set.insert(sram_name(s.id, s.slot));
}
auto g = gb_instance->getSaveRam();
if(g.second)
set.insert("gbsram");
set.insert("gbrtc");
}
arg.srams = tmpalloc_array<const char*>(TMP_UNSPEC, set.size() + 1);
unsigned key = TMP_UNSPEC + 1;
for(auto& i : set) {
arg.srams[key - TMP_UNSPEC - 1] = tmpalloc_str(key, i);
key++;
}
arg.srams[key - TMP_UNSPEC - 1] = NULL;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_save_sram& arg)
{
std::vector<std::pair<const char*, std::pair<const char*, size_t>>> out;
unsigned key = TMP_UNSPEC + 1;
if(internal_rom < 0)
goto out;
for(unsigned i = 0; i < SNES::cartridge.nvram.size(); i++) {
//SNES SRAMs
SNES::Cartridge::NonVolatileRAM& r = SNES::cartridge.nvram[i];
add_sram_entry(out, key, sram_name(r.id, r.slot), r.data, r.size);
}
{
//GB SRAMs
auto g = gb_instance->getSaveRam();
char gbrtc[8];
time_t timebase = gb_instance->getRtcBase();
for(size_t i = 0; i < sizeof(gbrtc); i++)
gbrtc[i] = ((unsigned long long)timebase >> (8 * i));
if(g.second)
add_sram_entry(out, key, "gbsram", g.first, g.second);
add_sram_entry(out, key, "gbrtc", gbrtc, sizeof(gbrtc));
}
out:
//Copy srams to output.
arg.srams = tmpalloc_array<lsnes_core_sram>(TMP_UNSPEC, out.size() + 1);
unsigned k = 0;
for(auto& i : out) {
arg.srams[k].name = i.first;
arg.srams[k].data = i.second.first;
arg.srams[k].size = i.second.second;
k++;
}
arg.srams[k].name = NULL;
arg.srams[k].data = NULL;
arg.srams[k].size = 0;
return 0;
}
const char* core_bsnes_gambatte(lsnes_core_load_sram& arg)
{
std::set<std::string> used;
if(internal_rom < 0) {
//If no ROM, warn about everything.
for(lsnes_core_sram* i = arg.srams; i->name; i++) {
(message_output() << "WARNING: SRAM '" << i->name << "': Not found on cartridge."
<< std::endl).end();
}
return RET_OK;
}
//Check for no SRAM.
if(!arg.srams->name)
return RET_OK;
for(unsigned i = 0; i < SNES::cartridge.nvram.size(); i++) {
SNES::Cartridge::NonVolatileRAM& _r = SNES::cartridge.nvram[i];
handle_sram_load(arg, sram_name(_r.id, _r.slot), _r.data, _r.size, used);
}
auto g = gb_instance->getSaveRam();
unsigned char gbrtc[8];
if(g.second)
handle_sram_load(arg, "gbsram", g.first, g.second, used);
handle_sram_load(arg, "gbrtc", gbrtc, sizeof(gbrtc), used);
//Unpack gbrtc.
time_t timebase = 0;
for(size_t i = 0; i < sizeof(gbrtc); i++)
timebase |= (unsigned long long)gbrtc[i] << (8 * i);
gb_instance->setRtcBase(timebase);
//Warn about unused SRAMs.
for(lsnes_core_sram* i = arg.srams; i->name; i++) {
if(!used.count(i->name)) {
(message_output() << "WARNING: SRAM '" << i->name << "': Not found on cartridge."
<< std::endl).end();
}
}
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_reset_action& arg)
{
arg.hardreset = 1;
arg.softreset = 0;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_compute_scale& arg)
{
arg.hfactor = (arg.width < 400 || arg.width == 416) ? 2 : 1;
arg.vfactor = (arg.height < 400) ? 2 : 1;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_runtosave& arg)
{
if(internal_rom < 0)
return RET_OK;
stepping_into_save = true;
SNES::system.runtosave();
have_saved_this_frame = true;
stepping_into_save = false;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_poweron& arg)
{
if(internal_rom < 0)
return RET_OK;
bsnes_call_power_or_reset(true);
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_unload_cartridge& arg)
{
if(internal_rom < 0)
return RET_OK;
delete[] debugbuf.cart;
delete[] debugbuf.sram;
debugbuf.cart = NULL;
debugbuf.sram = NULL;
gb_cur_ramsize = 0;
gb_cur_romsize = 0;
debugbuf.wramcheat.clear();
debugbuf.sramcheat.clear();
debugbuf.cartcheat.clear();
snes_term();
snes_unload_cartridge();
internal_rom = -1;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_debug_reset& arg)
{
//Next load will reset gambatte trace buffers.
reallocate_debug = true;
SNES::bus.clearDebugFlags();
SNES::cheat.reset();
trace_cpu_enable = false;
trace_smp_enable = false;
bsnes_update_trace_hook_state();
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_set_debug_flags& arg)
{
unsigned sflags = arg.set;
unsigned cflags = arg.clear;
uint64_t addr = arg.addr;
switch(addr) {
case 0:
//S-CPU.
if(sflags & 8) trace_cpu_enable = true;
if(cflags & 8) trace_cpu_enable = false;
bsnes_update_trace_hook_state();
break;
case 1:
//S-SMP.
if(sflags & 8) trace_smp_enable = true;
if(cflags & 8) trace_smp_enable = false;
bsnes_update_trace_hook_state();
break;
case 2:
//GB CPU.
if(sflags & 8) debugbuf.trace_cpu = true;
if(cflags & 8) debugbuf.trace_cpu = false;
break;
}
auto _addr = recognize_address(addr);
switch(classify_address_kind(_addr.first)) {
case ADDR_CLASS_ALL:
SNES::bus.debugFlags(sflags & 7, cflags & 7);
//Set/Clear every known GB debug.
gambatte_set_debugbuf_all(debugbuf.wram, 32768, sflags, cflags);
gambatte_set_debugbuf_all(debugbuf.bus, 65536, sflags, cflags);
gambatte_set_debugbuf_all(debugbuf.ioamhram, 512, sflags, cflags);
gambatte_set_debugbuf_all(debugbuf.sram, gb_instance->getSaveRam().second, sflags, cflags);
gambatte_set_debugbuf_all(debugbuf.cart, gb_romdata.size(), sflags, cflags);
break;
case ADDR_CLASS_GB:
if((sflags | cflags) & 7) {
switch(_addr.first) {
case MAP_KIND_GB_BUS:
gambatte_set_debugbuf(debugbuf.bus, _addr.second, 65536, sflags, cflags);
break;
case MAP_KIND_GB_HRAM:
gambatte_set_debugbuf(debugbuf.ioamhram, _addr.second, 512, sflags, cflags);
break;
case MAP_KIND_GB_ROM:
gambatte_set_debugbuf(debugbuf.cart, _addr.second, gb_romdata.size(), sflags, cflags);
break;
case MAP_KIND_GB_SRAM:
gambatte_set_debugbuf(debugbuf.sram, _addr.second, gb_instance->getSaveRam().second,
sflags, cflags);
break;
case MAP_KIND_GB_WRAM:
gambatte_set_debugbuf(debugbuf.wram, _addr.second, 32768, sflags, cflags);
break;
}
}
break;
case ADDR_CLASS_SNES:
if((sflags | cflags) & 7) {
SNES::bus.debugFlags(sflags & 7, cflags & 7, _addr.first, _addr.second);
}
break;
//Can't be placed on NONE.
}
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_set_cheat& arg)
{
uint64_t addr = arg.addr;
uint64_t value = arg.value;
bool set = arg.set;
bool s = false;
auto _addr = recognize_address(addr);
switch(classify_address_kind(_addr.first)) {
//Can't be placed on ALL nor NONE.
case ADDR_CLASS_GB:
switch(_addr.first) {
case MAP_KIND_GB_WRAM:
gambatte_set_cheat(debugbuf.wram, debugbuf.wramcheat, _addr.second, 32768, set, value);
break;
case MAP_KIND_GB_SRAM:
gambatte_set_cheat(debugbuf.sram, debugbuf.sramcheat, _addr.second,
gb_instance->getSaveRam().second, set, value);
break;
case MAP_KIND_GB_ROM:
gambatte_set_cheat(debugbuf.cart, debugbuf.cartcheat, _addr.second, gb_romdata.size(), set,
value);
break;
}
break;
case ADDR_CLASS_SNES: {
unsigned x = 0;
while(x < 0x1000000) {
x = SNES::bus.enumerateMirrors(_addr.first, _addr.second, x);
if(x < 0x1000000) {
if(set) {
for(size_t i = 0; i < SNES::cheat.size(); i++) {
if(SNES::cheat[i].addr == x) {
SNES::cheat[i].data = value;
s = true;
break;
}
}
if(!s) SNES::cheat.append({x, (uint8_t)value, true});
} else
for(size_t i = 0; i < SNES::cheat.size(); i++) {
if(SNES::cheat[i].addr == x) {
SNES::cheat.remove(i);
break;
}
}
}
x++;
}
SNES::cheat.synchronize();
}
}
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_draw_cover& arg)
{
lsnes_core_framebuffer_info* fbinfo = tmpalloc<lsnes_core_framebuffer_info>(TMP_UNSPEC);
uint32_t* fbmem = tmpalloc_array<uint32_t>(TMP_UNSPEC + 1, 512 * 448);
fbinfo->type = LSNES_CORE_PIXFMT_LRGB;
fbinfo->mem = (const char*)fbmem;
fbinfo->physwidth = 512;
fbinfo->physheight = 448;
fbinfo->physstride = 2048;
fbinfo->width = 512;
fbinfo->height = 448;
fbinfo->stride = 2048;
fbinfo->offset_x = 0;
fbinfo->offset_y = 0;
if(cb_render_text) {
lsnes_core_fontrender_req req = {
.cb_ctx = NULL,
.alloc = [](void*, size_t mem) -> void* { return tmpalloc_array<char*>(TMP_TXTBMP, mem); },
.text = NULL,
.text_len = -1,
.bytes_pp = 4,
.fg_color = 0x783E0, //Green.
.bg_color = 0x00000, //Black.
.bitmap = NULL,
.width = 0,
.height = 0
};
req.text = tmp_sprintf(TMP_TEXT, "Core: %s\nGame: %s\n", core_bsnes_gambatte_name.c_str(),
get_cartridge_name().c_str());
cb_render_text(&req);
//Blit the bitmap on screen.
uint32_t* dbuf = (uint32_t*)req.bitmap;
size_t cwidth = (req.width < 512) ? req.width : 512;
for(unsigned y = 0; y < 448; y++) {
for(unsigned x = 0; x < 512; x++)
fbmem[y * 512 + x] = req.bg_color;
if(y < req.height)
memcpy(fbmem + y * 512, dbuf + y * req.width, sizeof(uint32_t) * cwidth);
}
} else {
for(unsigned y = 0; y < 448; y++)
for(unsigned x = 0; x < 512; x++) {
unsigned _x = (x >> 3) & 1;
unsigned _y = (y >> 3) & 1;
fbmem[y * 512 + x] = (_x ^ _y) ? 0x7FC00 : 0x7801F;
}
}
arg.coverpage = fbinfo;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_pre_emulate& arg)
{
arg.set_input(arg.context, 0, 0, 1, (do_reset_flag >= 0) ? 1 : 0);
arg.set_input(arg.context, 0, 0, 4, do_hreset_flag ? 1 : 0);
if(do_reset_flag >= 0) {
arg.set_input(arg.context, 0, 0, 2, do_reset_flag / 10000);
arg.set_input(arg.context, 0, 0, 3, do_reset_flag % 10000);
} else {
arg.set_input(arg.context, 0, 0, 2, 0);
arg.set_input(arg.context, 0, 0, 3, 0);
}
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_device_regs& arg)
{
arg.regs = snes_gb_registers;
return RET_OK;
}
const char* core_bsnes_gambatte(lsnes_core_get_vma_list& arg)
{
static lsnes_core_get_vma_list_vma* tmpmem[32];
unsigned key = 0;
if(internal_rom >= 0) {
tmpmem[key++] = &vma_WRAM;
tmpmem[key++] = &vma_APURAM;
tmpmem[key++] = &vma_VRAM;
tmpmem[key++] = &vma_OAM;
tmpmem[key++] = &vma_CGRAM;
tmpmem[key++] = fixup_vma(vma_SRAM, SNES::cartridge.ram.data(), SNES::cartridge.ram.size());
tmpmem[key++] = fixup_vma(vma_ROM, SNES::cartridge.rom.data(), SNES::cartridge.rom.size());
tmpmem[key++] = &vma_BUS;
tmpmem[key++] = fixup_vma(vma_GBROM, &gb_romdata[0], gb_romdata.size());
tmpmem[key++] = fixup_vma(vma_GBSRAM, gb_instance->getSaveRam());
tmpmem[key++] = fixup_vma(vma_GBWRAM, gb_instance->getWorkRam());
tmpmem[key++] = fixup_vma(vma_GBHRAM, gb_instance->getIoRam());
tmpmem[key++] = fixup_vma(vma_GBVRAM, gb_instance->getVideoRam());
tmpmem[key++] = &vma_GBBUS;
}
tmpmem[key] = NULL;
arg.vmas = tmpmem;
return RET_OK;
}
}
int lsnes_core_entrypoint(unsigned action, unsigned item, void* params, const char** error)
{
try {
const char* ret;
switch(action) {
CASE_CALL_NOITEM(LSNES_CORE_ENUMERATE_CORES);
CASE_CALL_CORE(LSNES_CORE_GET_CORE_INFO);
CASE_CALL_TYPE(LSNES_CORE_GET_TYPE_INFO);
CASE_CALL_REGION(LSNES_CORE_GET_REGION_INFO);
CASE_CALL_SYSREGION(LSNES_CORE_GET_SYSREGION_INFO);
CASE_CALL_CORE(LSNES_CORE_GET_AV_STATE);
CASE_CALL_CORE(LSNES_CORE_EMULATE);
CASE_CALL_CORE(LSNES_CORE_SAVESTATE);
CASE_CALL_CORE(LSNES_CORE_LOADSTATE);
CASE_CALL_TYPE(LSNES_CORE_GET_CONTROLLERCONFIG);
CASE_CALL_TYPE(LSNES_CORE_LOAD_ROM);
CASE_CALL_CORE(LSNES_CORE_GET_REGION);
CASE_CALL_CORE(LSNES_CORE_SET_REGION);
CASE_CALL_NOITEM(LSNES_CORE_DEINITIALIZE);
CASE_CALL_CORE(LSNES_CORE_GET_PFLAG);
CASE_CALL_CORE(LSNES_CORE_SET_PFLAG);
CASE_CALL_CORE(LSNES_CORE_GET_ACTION_FLAGS);
CASE_CALL_CORE(LSNES_CORE_EXECUTE_ACTION);
CASE_CALL_CORE(LSNES_CORE_GET_BUS_MAPPING);
CASE_CALL_CORE(LSNES_CORE_ENUMERATE_SRAM);
CASE_CALL_CORE(LSNES_CORE_SAVE_SRAM);
CASE_CALL_CORE(LSNES_CORE_LOAD_SRAM);
CASE_CALL_CORE(LSNES_CORE_GET_RESET_ACTION);
CASE_CALL_CORE(LSNES_CORE_COMPUTE_SCALE);
CASE_CALL_CORE(LSNES_CORE_RUNTOSAVE);
CASE_CALL_CORE(LSNES_CORE_POWERON);
CASE_CALL_CORE(LSNES_CORE_UNLOAD_CARTRIDGE);
CASE_CALL_CORE(LSNES_CORE_DEBUG_RESET);
CASE_CALL_CORE(LSNES_CORE_SET_DEBUG_FLAGS);
CASE_CALL_CORE(LSNES_CORE_SET_CHEAT);
CASE_CALL_CORE(LSNES_CORE_DRAW_COVER);
CASE_CALL_CORE(LSNES_CORE_PRE_EMULATE);
CASE_CALL_CORE(LSNES_CORE_GET_DEVICE_REGS);
CASE_CALL_CORE(LSNES_CORE_GET_VMA_LIST);
default:
ret = tmp_sprintf(TMP_ERROR, "Unsupported request %u", action);
break;
}
*error = ret;
return *error ? -1 : 0;
} catch(std::bad_alloc& e) {
//Don't throw exceptions across module boundary.
*error = "Out of memory";
return -1;
} catch(std::exception& e) {
//Don't throw exceptions across module boundary.
try {
//Really, catch the exception if this fails due to OOM.
*error = tmpalloc_str(TMP_ERROR, std::string("Exception: ") + e.what());
return -1;
} catch(std::bad_alloc& f) {
*error = "Out of memory (while reporting exception)";
return -1;
}
}
}