nestopia/source/fltkui/jg.cpp
2025-03-23 09:31:10 -06:00

1625 lines
53 KiB
C++

/*
* Nestopia JG
*
* Copyright (C) 2008-2018 Nestopia UE Contributors
* Copyright (C) 2020-2025 Rupert Carmichael
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstring>
#include <cstdlib>
#include <stdint.h>
#include <jg/jg.h>
#include <jg/jg_nes.h>
#include "core/api/NstApiMachine.hpp"
#include "core/api/NstApiEmulator.hpp"
#include "core/api/NstApiVideo.hpp"
#include "core/api/NstApiCheats.hpp"
#include "core/api/NstApiSound.hpp"
#include "core/api/NstApiInput.hpp"
#include "core/api/NstApiCartridge.hpp"
#include "core/api/NstApiUser.hpp"
#include "core/api/NstApiFds.hpp"
#include "version.h"
#define AFILT_IMPL
#include "afilt.h"
#define SAMPLERATE 48000
#define FRAMERATE 60.098814
#define FRAMERATE_PAL 50.006978
#define CHANNELS 1
#define NUMINPUTS 5
#define ASPECT_NTSC 8.0 / 7.0
#define ASPECT_PAL 7375000.0 / 5320342.5
#define NTSC_FILTER_RATIO 2.3515625 // 602 / 256
using namespace Nes::Api;
static Emulator emulator;
static Video::Output *NstVideo;
static Sound::Output *NstSound;
static Input::Controllers *NstInput;
static jg_cb_audio_t jg_cb_audio;
static jg_cb_frametime_t jg_cb_frametime;
static jg_cb_log_t jg_cb_log;
static jg_cb_rumble_t jg_cb_rumble;
static jg_coreinfo_t coreinfo = {
"nestopia", "Nestopia JG", JG_VERSION, "nes", NUMINPUTS, 0
};
static jg_fileinfo_t biosinfo;
static jg_fileinfo_t gameinfo;
static jg_pathinfo_t pathinfo;
static jg_videoinfo_t vidinfo = {
JG_PIXFMT_XRGB8888, // pixfmt
Video::Output::NTSC_WIDTH, // wmax
Video::Output::HEIGHT, // hmax
Video::Output::WIDTH, // w
Video::Output::HEIGHT, // h
0, // x
0, // y
Video::Output::NTSC_WIDTH, // p
(Video::Output::WIDTH * ASPECT_NTSC) / Video::Output::HEIGHT, // aspect
NULL
};
static jg_audioinfo_t audinfo = {
JG_SAMPFMT_INT16,
SAMPLERATE,
CHANNELS,
(SAMPLERATE / (unsigned)FRAMERATE) * CHANNELS,
NULL
};
// Input Devices
static jg_inputinfo_t inputinfo[NUMINPUTS];
static jg_inputstate_t *input_device[NUMINPUTS];
// Emulator-specific settings
static jg_setting_t settings_nst[] = {
{ "port1", "Controller Port 1",
"0 = Auto, 1 = Controller, 2 = Zapper, 3 = Arkanoid Paddle, "
"4 = Power Pad, 5 = Power Glove",
"Select the device plugged into controller port 1",
0, 0, 5, JG_SETTING_INPUT
},
{ "port2", "Controller Port 2",
"0 = Auto, 1 = Controller, 2 = Zapper, 3 = Arkanoid Paddle, "
"4 = Power Pad, 5 = Power Glove",
"Select the device plugged into controller port 2",
0, 0, 5, JG_SETTING_INPUT
},
{ "port3", "Controller Port 3",
"0 = Auto, 1 = Controller",
"Select the device plugged into controller port 3",
0, 0, 1, JG_SETTING_INPUT
},
{ "port4", "Controller Port 4",
"0 = Auto, 1 = Controller",
"Select the device plugged into controller port 4",
0, 0, 1, JG_SETTING_INPUT
},
{ "portexp", "Expansion Port",
"0 = Auto, 1 = Family Trainer, 2 = Pachinko, 3 = Oeka Kids Tablet, "
"4 = Konami Hypershot, 5 = Bandai Hypershot, 6 = Crazy Climber, "
"7 = Mahjong, 8 = Exciting Boxing, 9 = Top Rider, 10 = Pokkun Moguraa, "
"11 = PartyTap",
"Select the device plugged into the expansion port",
0, 0, 11, JG_SETTING_INPUT
},
{ "turbo_rate", "Turbo Pulse Rate",
"N = Pulse every N frames",
"Set the speed (in frames) at which the turbo buttons are pulsed",
3, 2, 9, JG_SETTING_INPUT
},
{ "palette", "Palette",
"0 = Canonical, 1 = Consumer, 2 = Alternative, 3 = CXA2025AS (JP), "
"4 = CXA2025AS (US), 5 = Royaltea, 6 = Nobilitea, "
"7 = Digital Prime (FBX), 8 = Magnum (FBX), 9 = PVM Style D93 (FBX), "
"10 = Smooth V2 (FBX), 11 = RGB, 12 = Custom",
"Set the colour palette",
Video::DECODER_CXA2025AS_US, 0, 12, 0
},
{ "ntsc_filter", "NTSC Filter",
"0 = Disable, 1 = Enable",
"Use blargg's NTSC filter (required for Chroma Crosstalk emulation)",
0, 0, 1, 0
},
{ "ntsc_mode", "NTSC Filter Mode",
"0 = Composite, 1 = S-Video, 2 = RGB, 3 = Monochrome",
"Set the NTSC Filter Mode",
0, 0, 3, 0
},
{ "overscan_t", "Overscan Mask (Top)",
"N = Hide N pixels of Overscan (Top)",
"Hide N pixels of Overscan (Top)",
8, 0, 12, 0
},
{ "overscan_b", "Overscan Mask (Bottom)",
"N = Hide N pixels of Overscan (Bottom)",
"Hide N pixels of Overscan (Bottom)",
8, 0, 12, 0
},
{ "overscan_l", "Overscan Mask (Left)",
"N = Hide N pixels of Overscan (Left)",
"Hide N pixels of Overscan (Left)",
0, 0, 12, 0
},
{ "overscan_r", "Overscan Mask (Right)",
"N = Hide N pixels of Overscan (Right)",
"Hide N pixels of Overscan (Right)",
0, 0, 12, 0
},
{ "audio_filter", "Audio Output Filter",
"0 = Disable, 1 = Enable",
"Enable or Disable the audio output filter, which includes First Order "
"High Pass and First Order Low Pass filtering similar to real hardware",
0, 0, 1, 0
},
{ "favored_system", "Favored/System",
"0 = NTSC, 1 = PAL, 2 = Famicom, 3 = Dendy",
"Set the desired system in cases where a database entry does not exist",
0, 0, 3, JG_SETTING_RESTART
},
{ "force_region", "Force Region",
"0 = Auto, 1 = Force",
"Force the system to be the Favored System",
0, 0, 1, JG_SETTING_RESTART
},
{ "ram_power_state", "RAM Power-on State",
"0 = 0x00, 1 = 0xff, 2 = Random",
"Set the initial values in System RAM when the system is powered on",
2, 0, 2, JG_SETTING_RESTART
},
{ "unlimited_sprites", "Unlimited Sprites",
"0 = Disable, 1 = Enable",
"Disables the 8 sprite per line limit - may cause glitches",
0, 0, 1, 0
},
{ "genie_distortion", "Game Genie Sound Distortion",
"0 = Disable, 1 = Enable",
"Some games do not clear APU registers at boot, causing distortion when "
"a Game Genie is in use (Mega Man, Mega Man 2)",
0, 0, 1, JG_SETTING_RESTART
},
{ "softpatch", "Soft Patching",
"0 = Disable, 1 = Enable",
"Automatically patch games when a .ips or .ups patch file with the same "
"name as the ROM is present",
0, 0, 1, JG_SETTING_RESTART
}
};
enum {
PORT1,
PORT2,
PORT3,
PORT4,
PORTEXP,
TURBORATE,
PALETTE,
NTSC,
NTSCMODE,
OVERSCAN_T,
OVERSCAN_B,
OVERSCAN_L,
OVERSCAN_R,
AUDIOFILTER,
FAVSYSTEM,
FORCEREGION,
RAMPOWERSTATE,
UNLIMITEDSPRITES,
GENIEDISTORTION,
SOFTPATCH
};
// Microphone Input
static unsigned micstate = 0;
static bool kstudio = false;
// Nestopia Helper Functions
static void nst_fds_info(void) {
Fds fds(emulator);
if (fds.IsAnyDiskInserted()) {
jg_cb_log(JG_LOG_INF, "Disk %c Side %c\n",
fds.GetCurrentDisk() == 0 ? '1' : '2',
fds.GetCurrentDiskSide() == 0 ? 'A' : 'B');
jg_cb_log(JG_LOG_SCR, "Disk %c Side %c.",
fds.GetCurrentDisk() == 0 ? '1' : '2',
fds.GetCurrentDiskSide() == 0 ? 'A' : 'B');
}
else {
jg_cb_log(JG_LOG_INF, "Disk Drive Empty\n");
jg_cb_log(JG_LOG_SCR, "Disk Drive Empty.");
}
}
static int nst_db_load(void) {
Cartridge::Database db(emulator);
std::string dbpath = std::string(pathinfo.core) + "/NstDatabase.xml";
std::ifstream dbfile(dbpath.c_str(),
std::ifstream::in|std::ifstream::binary);
if (dbfile.is_open()) {
db.Load(dbfile);
db.Enable(true);
dbfile.close();
return 1;
}
return 0;
}
static void nst_sample_load(User::File& file, const char *path, bool rel) {
std::string filepath;
if (rel) {
filepath = std::string(path);
}
else {
char wavfile[] = "xx.wav";
unsigned id = file.GetId();
if (id > 99) {
return;
}
wavfile[0] = '0' + id / 10;
wavfile[1] = '0' + id % 10;
filepath = std::string(pathinfo.user) + "/" + path + "/" + wavfile;
}
std::ifstream is(filepath.c_str(), std::ifstream::binary);
if (is) {
is.seekg(0, is.end);
int length = is.tellg();
is.seekg(0, is.beg);
uint8_t *buffer = new uint8_t[length];
is.read((char*)buffer, length);
is.close();
uint32_t datasize = buffer[0x2b] << 24 | buffer[0x2a] << 16 |
buffer[0x29] << 8 | buffer[0x28];
uint16_t blockalign = buffer[0x21] << 8 | buffer[0x20];
uint16_t numchannels = buffer[0x17] << 8 | buffer[0x16];
uint16_t bitspersample = buffer[0x23] << 8 | buffer[0x22];
uint32_t samplerate = buffer[0x1b] << 24 | buffer[0x1a] << 16 |
buffer[0x19] << 8 | buffer[0x18];
file.SetSampleContent(&buffer[0x2c], datasize / blockalign,
numchannels == 2, bitspersample, samplerate);
delete[] buffer;
}
}
// Nestopia Callbacks
static void NST_CALLBACK nst_cb_event(void *userdata, User::Event event,
const void *data) {
// Handle special events
switch (event) {
case User::EVENT_CPU_JAM:
jg_cb_log(JG_LOG_SCR, "Cpu: Jammed.");
break;
case User::EVENT_CPU_UNOFFICIAL_OPCODE:
jg_cb_log(JG_LOG_DBG, "Cpu: Unofficial Opcode %s\n",
(const char*)data);
break;
case User::EVENT_DISPLAY_TIMER:
jg_cb_log(JG_LOG_SCR, (const char*)data);
break;
default: break;
}
}
static void NST_CALLBACK nst_cb_file(void *userdata, User::File& file) {
switch (file.GetAction()) {
case User::File::LOAD_ROM: { // XML Romset loading
std::wstring wfname = std::wstring(file.GetName());
std::string fname(wfname.begin(), wfname.end());
std::string basedir = std::string(gameinfo.path);
basedir = basedir.substr(0, basedir.rfind("/"));
fname = basedir + "/" + fname;
std::ifstream romfile(fname.c_str(),
std::ifstream::in|std::ifstream::binary);
if (romfile.is_open()) {
file.SetContent(romfile);
romfile.close();
}
break;
}
case User::File::LOAD_SAMPLE: {
std::wstring wfname = std::wstring(file.GetName());
std::string fname(wfname.begin(), wfname.end());
std::string basedir = std::string(gameinfo.path);
basedir = basedir.substr(0, basedir.rfind("/"));
fname = basedir + "/" + fname;
nst_sample_load(file, fname.c_str(), true);
break;
}
case User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU: {
nst_sample_load(file, "moepro", false);
break;
}
case User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU_88: {
nst_sample_load(file, "moepro88", false);
break;
}
case User::File::LOAD_SAMPLE_MOERO_PRO_TENNIS: {
nst_sample_load(file, "mptennis", false);
break;
}
case User::File::LOAD_SAMPLE_TERAO_NO_DOSUKOI_OOZUMOU: {
nst_sample_load(file, "terao", false);
break;
}
case User::File::LOAD_SAMPLE_AEROBICS_STUDIO: {
nst_sample_load(file, "ftaerobi", false);
break;
}
case User::File::LOAD_BATTERY:
case User::File::LOAD_EEPROM:
case User::File::LOAD_TAPE:
case User::File::LOAD_TURBOFILE: {
std::string savename = std::string(pathinfo.save) + "/" +
std::string(gameinfo.name) + ".sav";
std::ifstream savefile(savename.c_str(),
std::ifstream::in|std::ifstream::binary);
if (savefile.is_open()) {
file.SetContent(savefile);
savefile.close();
}
break;
}
case User::File::SAVE_BATTERY:
case User::File::SAVE_EEPROM:
case User::File::SAVE_TAPE:
case User::File::SAVE_TURBOFILE: {
std::string savename = std::string(pathinfo.save) + "/" +
std::string(gameinfo.name) + ".sav";
std::ofstream savefile(savename.c_str(),
std::ifstream::out|std::ifstream::binary);
const void *savedata;
unsigned long datasize;
file.GetContent(savedata, datasize);
if (savefile.is_open()) {
savefile.write((const char*)savedata, datasize);
savefile.close();
}
break;
}
case User::File::LOAD_FDS: {
std::string savename = std::string(pathinfo.save) + "/" +
std::string(gameinfo.name) + ".ups";
std::ifstream savefile(savename.c_str(),
std::ifstream::in|std::ifstream::binary);
if (savefile.is_open()) {
file.SetPatchContent(savefile);
savefile.close();
}
break;
}
case User::File::SAVE_FDS: {
std::string savename = std::string(pathinfo.save) + "/" +
std::string(gameinfo.name) + ".ups";
std::ofstream savefile(savename.c_str(),
std::ifstream::out|std::ifstream::binary);
if (savefile.is_open()) {
file.GetPatchContent(User::File::PATCH_UPS, savefile);
savefile.close();
}
break;
}
}
}
static void NST_CALLBACK nst_cb_log(void *udata, const char *str,
unsigned long int length) {
jg_cb_log(JG_LOG_DBG, str);
}
static bool NST_CALLBACK nst_cb_videolock(void *udata, Video::Output& video) {
video.pixels = vidinfo.buf;
video.pitch = Video::Output::NTSC_WIDTH * sizeof(uint32_t);
return true;
}
static void NST_CALLBACK nst_cb_videounlock(void *udata, Video::Output& video) {
}
static bool NST_CALLBACK nst_cb_soundlock(void* udata, Sound::Output& sound) {
if (settings_nst[AUDIOFILTER].val) {
afilt(&afilter[FILTER_FO_HPF], (int16_t*)audinfo.buf, audinfo.spf);
afilt(&afilter[FILTER_FO_LPF], (int16_t*)audinfo.buf, audinfo.spf);
}
jg_cb_audio(audinfo.spf);
return true;
}
static void NST_CALLBACK nst_cb_soundunlock(void* udata, Sound::Output& sound) {
}
// Input Poll Callbacks
static uint8_t NESMap[] = {
Input::Controllers::Pad::UP, Input::Controllers::Pad::DOWN,
Input::Controllers::Pad::LEFT, Input::Controllers::Pad::RIGHT,
Input::Controllers::Pad::SELECT, Input::Controllers::Pad::START,
Input::Controllers::Pad::A, Input::Controllers::Pad::B
};
static bool NST_CALLBACK nst_poll_pad(Input::UserData data,
Input::Controllers::Pad& pad, unsigned port) {
unsigned buttons = 0;
for (int i = 0; i < inputinfo[port].numbuttons - 2; ++i)
if (input_device[port]->button[i]) buttons |= NESMap[i];
// Turbo
if (input_device[port]->button[8]) {
if (input_device[port]->button[8] == settings_nst[TURBORATE].val) {
buttons |= Input::Controllers::Pad::A;
input_device[port]->button[8] = 1;
}
else {
++input_device[port]->button[8];
}
}
if (input_device[port]->button[9]) {
if (input_device[port]->button[9] == settings_nst[TURBORATE].val) {
buttons |= Input::Controllers::Pad::B;
input_device[port]->button[9] = 1;
}
else {
++input_device[port]->button[9];
}
}
if (port == 1)
pad.mic = micstate;
pad.buttons = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_zapper(Input::UserData data,
Input::Controllers::Zapper& zapper) {
jg_inputstate_t *zdev = static_cast<jg_inputstate_t*>(data);
zapper.fire = false;
if (zdev->button[0]) {
zapper.fire = true;
if (settings_nst[NTSC].val)
zapper.x = zdev->coord[0] / NTSC_FILTER_RATIO;
else
zapper.x = zdev->coord[0];
zapper.y = zdev->coord[1];
}
else if (zdev->button[1]) {
zapper.fire = true;
zapper.x = ~0U;
zapper.y = ~0U;
}
return true;
}
static bool NST_CALLBACK nst_poll_paddle(Input::UserData data,
Input::Controllers::Paddle& paddle) {
jg_inputstate_t *adev = static_cast<jg_inputstate_t*>(data);
paddle.x = (adev->axis[0] / 546) + 106; // 46-166 - super magical numbers!
paddle.button = adev->button[0];
return true;
}
static bool NST_CALLBACK nst_poll_powerpad(Input::UserData data,
Input::Controllers::PowerPad& powerPad) {
jg_inputstate_t *ppdev = static_cast<jg_inputstate_t*>(data);
for (int i = 0; i < 12; ++i)
powerPad.sideA[i] = ppdev->button[i];
for (int i = 0; i < 8; ++i)
powerPad.sideB[i] = ppdev->button[i + 12];
return true;
}
static bool NST_CALLBACK nst_poll_powerglove(Input::UserData data,
Input::Controllers::PowerGlove& glove) {
jg_inputstate_t *pgdev = static_cast<jg_inputstate_t*>(data);
unsigned pad = 0;
if (pgdev->button[0]) pad |= Input::Controllers::PowerGlove::SELECT;
if (pgdev->button[1]) pad |= Input::Controllers::PowerGlove::START;
glove.buttons = pad;
if (pgdev->button[2])
glove.distance = Input::Controllers::PowerGlove::DISTANCE_IN;
else if (pgdev->button[3])
glove.distance = Input::Controllers::PowerGlove::DISTANCE_OUT;
else
glove.distance = 0;
if (pgdev->button[4])
glove.wrist = Input::Controllers::PowerGlove::ROLL_LEFT;
else if (pgdev->button[5])
glove.wrist = Input::Controllers::PowerGlove::ROLL_RIGHT;
else
glove.wrist = 0;
if (pgdev->button[6])
glove.gesture = Input::Controllers::PowerGlove::GESTURE_FIST;
else if (pgdev->button[7])
glove.gesture = Input::Controllers::PowerGlove::GESTURE_FINGER;
else
glove.gesture = Input::Controllers::PowerGlove::GESTURE_OPEN;
if (settings_nst[NTSC].val)
glove.x = pgdev->coord[0] / NTSC_FILTER_RATIO;
else
glove.x = pgdev->coord[0];
glove.y = pgdev->coord[1];
return true;
}
static bool NST_CALLBACK nst_poll_familytrainer(Input::UserData data,
Input::Controllers::FamilyTrainer& familyTrainer) {
jg_inputstate_t *ftdev = static_cast<jg_inputstate_t*>(data);
for (int i = 0; i < 12; ++i)
familyTrainer.sideA[i] = ftdev->button[i];
for (int i = 0; i < 8; ++i)
familyTrainer.sideB[i] = ftdev->button[i + 12];
return true;
}
static bool NST_CALLBACK nst_poll_pachinko(Input::UserData data,
Input::Controllers::Pachinko& pachinko) {
jg_inputstate_t *pchdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
for (int i = 0; i < 8; ++i)
if (pchdev->button[i]) buttons |= NESMap[i];
pachinko.buttons = buttons;
pachinko.throttle = pchdev->axis[0] / 512;
return true;
}
static bool NST_CALLBACK nst_poll_oekakids(Input::UserData data,
Input::Controllers::OekaKidsTablet& tablet) {
jg_inputstate_t *okdev = static_cast<jg_inputstate_t*>(data);
if (settings_nst[NTSC].val)
tablet.x = okdev->coord[0] / NTSC_FILTER_RATIO;
else
tablet.x = okdev->coord[0];
tablet.y = okdev->coord[1];
tablet.button = okdev->button[0];
return true;
}
static bool NST_CALLBACK nst_poll_konamihypershot(Input::UserData data,
Input::Controllers::KonamiHyperShot& konamiHyperShot) {
jg_inputstate_t *khsdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
if (khsdev->button[0])
buttons |= Input::Controllers::KonamiHyperShot::PLAYER1_BUTTON_1;
if (khsdev->button[1])
buttons |= Input::Controllers::KonamiHyperShot::PLAYER1_BUTTON_2;
if (khsdev->button[2])
buttons |= Input::Controllers::KonamiHyperShot::PLAYER2_BUTTON_1;
if (khsdev->button[3])
buttons |= Input::Controllers::KonamiHyperShot::PLAYER2_BUTTON_2;
konamiHyperShot.buttons = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_bandaihypershot(Input::UserData data,
Input::Controllers::BandaiHyperShot& bandaiHyperShot) {
jg_inputstate_t *bhsdev = static_cast<jg_inputstate_t*>(data);
bandaiHyperShot.fire = false;
if (bhsdev->button[0]) {
bandaiHyperShot.fire = true;
if (settings_nst[NTSC].val)
bandaiHyperShot.x = bhsdev->coord[0] / NTSC_FILTER_RATIO;
else
bandaiHyperShot.x = bhsdev->coord[0];
bandaiHyperShot.y = bhsdev->coord[1];
}
else if (bhsdev->button[1]) {
bandaiHyperShot.fire = true;
bandaiHyperShot.x = ~0U;
bandaiHyperShot.y = ~0U;
}
bandaiHyperShot.move = bhsdev->button[2];
return true;
}
static bool NST_CALLBACK nst_poll_crazyclimber(Input::UserData data,
Input::Controllers::CrazyClimber& crazyClimber) {
jg_inputstate_t *ccdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
if (ccdev->button[0]) buttons |= Input::Controllers::CrazyClimber::UP;
if (ccdev->button[1]) buttons |= Input::Controllers::CrazyClimber::DOWN;
if (ccdev->button[2]) buttons |= Input::Controllers::CrazyClimber::LEFT;
if (ccdev->button[3]) buttons |= Input::Controllers::CrazyClimber::RIGHT;
crazyClimber.left = buttons;
buttons = 0;
if (ccdev->button[4]) buttons |= Input::Controllers::CrazyClimber::UP;
if (ccdev->button[5]) buttons |= Input::Controllers::CrazyClimber::DOWN;
if (ccdev->button[6]) buttons |= Input::Controllers::CrazyClimber::LEFT;
if (ccdev->button[7]) buttons |= Input::Controllers::CrazyClimber::RIGHT;
crazyClimber.right = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_mahjong(Input::UserData data,
Input::Controllers::Mahjong& mahjong, unsigned part) {
jg_inputstate_t *mjdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
switch (part) {
case Input::Controllers::Mahjong::PART_1:
if (mjdev->button[8])
buttons |= Input::Controllers::Mahjong::PART_1_I;
if (mjdev->button[9])
buttons |= Input::Controllers::Mahjong::PART_1_J;
if (mjdev->button[10])
buttons |= Input::Controllers::Mahjong::PART_1_K;
if (mjdev->button[11])
buttons |= Input::Controllers::Mahjong::PART_1_L;
if (mjdev->button[12])
buttons |= Input::Controllers::Mahjong::PART_1_M;
if (mjdev->button[13])
buttons |= Input::Controllers::Mahjong::PART_1_N;
break;
case Input::Controllers::Mahjong::PART_2:
if (mjdev->button[0])
buttons |= Input::Controllers::Mahjong::PART_2_A;
if (mjdev->button[1])
buttons |= Input::Controllers::Mahjong::PART_2_B;
if (mjdev->button[2])
buttons |= Input::Controllers::Mahjong::PART_2_C;
if (mjdev->button[3])
buttons |= Input::Controllers::Mahjong::PART_2_D;
if (mjdev->button[4])
buttons |= Input::Controllers::Mahjong::PART_2_E;
if (mjdev->button[5])
buttons |= Input::Controllers::Mahjong::PART_2_F;
if (mjdev->button[6])
buttons |= Input::Controllers::Mahjong::PART_2_G;
if (mjdev->button[7])
buttons |= Input::Controllers::Mahjong::PART_2_H;
break;
case Input::Controllers::Mahjong::PART_3:
if (mjdev->button[14])
buttons |= Input::Controllers::Mahjong::PART_3_SELECT;
if (mjdev->button[15])
buttons |= Input::Controllers::Mahjong::PART_3_START;
if (mjdev->button[16])
buttons |= Input::Controllers::Mahjong::PART_3_KAN;
if (mjdev->button[17])
buttons |= Input::Controllers::Mahjong::PART_3_PON;
if (mjdev->button[18])
buttons |= Input::Controllers::Mahjong::PART_3_CHI;
if (mjdev->button[19])
buttons |= Input::Controllers::Mahjong::PART_3_REACH;
if (mjdev->button[20])
buttons |= Input::Controllers::Mahjong::PART_3_RON;
break;
}
mahjong.buttons = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_excitingboxing(Input::UserData data,
Input::Controllers::ExcitingBoxing& excitingBoxing, unsigned part) {
jg_inputstate_t *ebdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
switch (part) {
case Input::Controllers::ExcitingBoxing::PART_1:
if (ebdev->button[5])
buttons |=
Input::Controllers::ExcitingBoxing::PART_1_RIGHT_HOOK;
if (ebdev->button[3])
buttons |=
Input::Controllers::ExcitingBoxing::PART_1_RIGHT_MOVE;
if (ebdev->button[4])
buttons |= Input::Controllers::ExcitingBoxing::PART_1_LEFT_HOOK;
if (ebdev->button[2])
buttons |= Input::Controllers::ExcitingBoxing::PART_1_LEFT_MOVE;
break;
case Input::Controllers::ExcitingBoxing::PART_2:
if (ebdev->button[0])
buttons |= Input::Controllers::ExcitingBoxing::PART_2_STRAIGHT;
if (ebdev->button[7])
buttons |= Input::Controllers::ExcitingBoxing::PART_2_RIGHT_JAB;
if (ebdev->button[1])
buttons |= Input::Controllers::ExcitingBoxing::PART_2_BODY;
if (ebdev->button[6])
buttons |= Input::Controllers::ExcitingBoxing::PART_2_LEFT_JAB;
break;
}
excitingBoxing.buttons = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_toprider(Input::UserData data,
Input::Controllers::TopRider& topRider) {
jg_inputstate_t *trdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
if (trdev->button[0]) buttons |= Input::Controllers::TopRider::SHIFT_GEAR;
if (trdev->button[1]) buttons |= Input::Controllers::TopRider::REAR;
if (trdev->button[2]) buttons |= Input::Controllers::TopRider::STEER_LEFT;
if (trdev->button[3]) buttons |= Input::Controllers::TopRider::STEER_RIGHT;
if (trdev->button[4]) buttons |= Input::Controllers::TopRider::SELECT;
if (trdev->button[5]) buttons |= Input::Controllers::TopRider::START;
if (trdev->button[6]) buttons |= Input::Controllers::TopRider::ACCEL;
if (trdev->button[7]) buttons |= Input::Controllers::TopRider::BRAKE;
topRider.buttons = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_pokkunmoguraa(Input::UserData data,
Input::Controllers::PokkunMoguraa& pokkunMoguraa, unsigned row) {
jg_inputstate_t *pmdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
if (row & Input::Controllers::PokkunMoguraa::ROW_1) {
if (pmdev->button[0])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_1;
if (pmdev->button[1])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_2;
if (pmdev->button[2])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_3;
if (pmdev->button[3])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_4;
}
if (row & Input::Controllers::PokkunMoguraa::ROW_2) {
if (pmdev->button[4])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_1;
if (pmdev->button[5])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_2;
if (pmdev->button[6])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_3;
if (pmdev->button[7])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_4;
}
if (row & Input::Controllers::PokkunMoguraa::ROW_3) {
if (pmdev->button[8])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_1;
if (pmdev->button[9])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_2;
if (pmdev->button[10])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_3;
if (pmdev->button[11])
buttons |= Input::Controllers::PokkunMoguraa::BUTTON_4;
}
pokkunMoguraa.buttons = buttons;
return true;
}
static bool NST_CALLBACK nst_poll_partytap(Input::UserData data,
Input::Controllers::PartyTap& partyTap) {
jg_inputstate_t *ptdev = static_cast<jg_inputstate_t*>(data);
unsigned units = 0;
if (ptdev->button[0]) units |= Input::Controllers::PartyTap::UNIT_1;
if (ptdev->button[1]) units |= Input::Controllers::PartyTap::UNIT_2;
if (ptdev->button[2]) units |= Input::Controllers::PartyTap::UNIT_3;
if (ptdev->button[3]) units |= Input::Controllers::PartyTap::UNIT_4;
if (ptdev->button[4]) units |= Input::Controllers::PartyTap::UNIT_5;
if (ptdev->button[5]) units |= Input::Controllers::PartyTap::UNIT_6;
partyTap.units = units;
return true;
}
static bool NST_CALLBACK nst_poll_vssys(Input::UserData data,
Input::Controllers::VsSystem& vsSystem) {
jg_inputstate_t *vsdev = static_cast<jg_inputstate_t*>(data);
unsigned slots = 0;
if (vsdev->button[0]) slots |= Input::Controllers::VsSystem::COIN_1;
if (vsdev->button[1]) slots |= Input::Controllers::VsSystem::COIN_2;
vsSystem.insertCoin = slots;
return true;
}
static bool NST_CALLBACK nst_poll_karaokestudio(Input::UserData data,
Input::Controllers::KaraokeStudio& karaokeStudio) {
jg_inputstate_t *ksdev = static_cast<jg_inputstate_t*>(data);
unsigned buttons = 0;
if (ksdev->button[0]) buttons |= Input::Controllers::KaraokeStudio::A;
if (ksdev->button[1]) buttons |= Input::Controllers::KaraokeStudio::B;
buttons |= micstate; // 2nd bit for mic, like Famicom built-in mic
karaokeStudio.buttons = buttons;
return true;
}
static void nst_params_input(void) {
// Autoselect the adapter
Input(emulator).AutoSelectAdapter();
// Select input devices for controller ports
for (int i = 0; i < 4; ++i) {
switch (settings_nst[PORT1 + i].val) {
case 0: default:
Input(emulator).AutoSelectController(i); break;
case 1:
Input(emulator).ConnectController(i,
(Input::Type)(Input::PAD1 + i)); break;
case 2:
Input(emulator).ConnectController(i, Input::ZAPPER); break;
case 3:
Input(emulator).ConnectController(i, Input::PADDLE); break;
case 4:
Input(emulator).ConnectController(i, Input::POWERPAD); break;
case 5:
Input(emulator).ConnectController(i, Input::POWERGLOVE); break;
}
}
// Select input device for expansion port
switch (settings_nst[PORTEXP].val) {
case 0: default:
Input(emulator).AutoSelectController(4); break;
case 1:
Input(emulator).ConnectController(4, Input::FAMILYTRAINER); break;
case 2:
Input(emulator).ConnectController(4, Input::PACHINKO); break;
case 3:
Input(emulator).ConnectController(4, Input::OEKAKIDSTABLET); break;
case 4:
Input(emulator).ConnectController(4, Input::KONAMIHYPERSHOT); break;
case 5:
Input(emulator).ConnectController(4, Input::BANDAIHYPERSHOT); break;
case 6:
Input(emulator).ConnectController(4, Input::CRAZYCLIMBER); break;
case 7:
Input(emulator).ConnectController(4, Input::MAHJONG); break;
case 8:
Input(emulator).ConnectController(4, Input::EXCITINGBOXING); break;
case 9:
Input(emulator).ConnectController(4, Input::TOPRIDER); break;
case 10:
Input(emulator).ConnectController(4, Input::POKKUNMOGURAA); break;
case 11:
Input(emulator).ConnectController(4, Input::PARTYTAP); break;
}
for (int i = 0; i < NUMINPUTS; ++i) {
int type = Input(emulator).GetConnectedController(i);
i < 4 ? jg_cb_log(JG_LOG_DBG, "Controller Port %d: %s\n", i + 1,
jg_nes_input_name[type]) :
jg_cb_log(JG_LOG_DBG, "Expansion Port: %s\n", jg_nes_input_name[type]);
// Assign input info for connected devices
inputinfo[i] = jg_nes_inputinfo(i, type);
if (kstudio)
inputinfo[4] = jg_nes_inputinfo(4, JG_NES_KARAOKESTUDIO);
switch (type) {
case Input::ZAPPER: {
Input::Controllers::Zapper::callback.Set(nst_poll_zapper,
input_device[i]);
break;
}
case Input::PADDLE: {
Input::Controllers::Paddle::callback.Set(nst_poll_paddle,
input_device[i]);
break;
}
case Input::POWERPAD: {
Input::Controllers::PowerPad::callback.Set(nst_poll_powerpad,
input_device[i]);
break;
}
case Input::FAMILYTRAINER: {
Input::Controllers::FamilyTrainer::callback.Set(
nst_poll_familytrainer, input_device[i]);
break;
}
case Input::POWERGLOVE: {
Input::Controllers::PowerGlove::callback.Set(
nst_poll_powerglove, input_device[i]);
break;
}
case Input::PACHINKO: {
Input::Controllers::Pachinko::callback.Set(
nst_poll_pachinko, input_device[i]);
break;
}
case Input::OEKAKIDSTABLET: {
Input::Controllers::OekaKidsTablet::callback.Set(
nst_poll_oekakids, input_device[i]);
break;
}
case Input::KONAMIHYPERSHOT: {
Input::Controllers::KonamiHyperShot::callback.Set(
nst_poll_konamihypershot, input_device[i]);
break;
}
case Input::BANDAIHYPERSHOT: {
Input::Controllers::BandaiHyperShot::callback.Set(
nst_poll_bandaihypershot, input_device[i]);
break;
}
case Input::CRAZYCLIMBER: {
Input::Controllers::CrazyClimber::callback.Set(
nst_poll_crazyclimber, input_device[i]);
break;
}
case Input::MAHJONG: {
Input::Controllers::Mahjong::callback.Set(nst_poll_mahjong,
input_device[i]);
break;
}
case Input::EXCITINGBOXING: {
Input::Controllers::ExcitingBoxing::callback.Set(
nst_poll_excitingboxing, input_device[i]);
break;
}
case Input::TOPRIDER: {
Input::Controllers::TopRider::callback.Set(nst_poll_toprider,
input_device[i]);
break;
}
case Input::POKKUNMOGURAA: {
Input::Controllers::PokkunMoguraa::callback.Set(
nst_poll_pokkunmoguraa, input_device[i]);
break;
}
case Input::PARTYTAP: {
Input::Controllers::PartyTap::callback.Set(nst_poll_partytap,
input_device[i]);
break;
}
default: break;
}
}
// Set Pad callback separately
Input::Controllers::Pad::callback.Set(nst_poll_pad, 0);
}
static void nst_params_video(void) {
// Set up video parameters
int renderwidth = settings_nst[NTSC].val ?
Video::Output::NTSC_WIDTH : Video::Output::WIDTH;
unsigned aspect_w = Video::Output::WIDTH - (settings_nst[OVERSCAN_L].val +
settings_nst[OVERSCAN_R].val);
unsigned w = renderwidth - (settings_nst[OVERSCAN_L].val +
settings_nst[OVERSCAN_R].val);
unsigned h = Video::Output::HEIGHT - (settings_nst[OVERSCAN_T].val +
settings_nst[OVERSCAN_B].val);
vidinfo.w = w;
vidinfo.h = h;
vidinfo.x = settings_nst[OVERSCAN_L].val;
vidinfo.y = settings_nst[OVERSCAN_T].val;
bool pal = Machine(emulator).GetMode() == Machine::PAL;
vidinfo.aspect = (aspect_w * (pal ? ASPECT_PAL : ASPECT_NTSC)) / (double)h;
Video video(emulator);
Video::RenderState renderState;
renderState.bits.mask.r = 0x00ff0000;
renderState.bits.mask.g = 0x0000ff00;
renderState.bits.mask.b = 0x000000ff;
renderState.filter = settings_nst[NTSC].val ?
Video::RenderState::FILTER_NTSC : Video::RenderState::FILTER_NONE;
renderState.width = renderwidth;
renderState.height = Video::Output::HEIGHT;
renderState.bits.count = 32;
switch (settings_nst[NTSCMODE].val) {
case 0: { // Composite
video.SetSaturation(Video::DEFAULT_SATURATION_COMP);
video.SetSharpness(Video::DEFAULT_SHARPNESS_COMP);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_COMP);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_COMP);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_COMP);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_COMP);
break;
}
case 1: { // S-Video
video.SetSaturation(Video::DEFAULT_SATURATION_SVIDEO);
video.SetSharpness(Video::DEFAULT_SHARPNESS_SVIDEO);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_SVIDEO);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_SVIDEO);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_SVIDEO);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_SVIDEO);
break;
}
case 2: { // RGB
video.SetSaturation(Video::DEFAULT_SATURATION_RGB);
video.SetSharpness(Video::DEFAULT_SHARPNESS_RGB);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_RGB);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_RGB);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_RGB);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_RGB);
break;
}
case 3: { // Monochrome
video.SetSaturation(Video::DEFAULT_SATURATION_MONO);
video.SetSharpness(Video::DEFAULT_SHARPNESS_MONO);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_MONO);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_MONO);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_MONO);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_MONO);
break;
}
}
// Path to external palette file
std::string palpath = std::string(pathinfo.core) + "/palettes/";
switch (settings_nst[PALETTE].val) {
case 0: case 1: case 2: case 3: case 4: // YUV Palettes
video.GetPalette().SetMode(Video::Palette::MODE_YUV);
video.SetDecoder((Video::DecoderPreset)settings_nst[PALETTE].val);
break;
case 5: { // Royaltea
palpath += "Royaltea.pal";
break;
}
case 6: { // Nobilitea
palpath += "Nobilitea.pal";
break;
}
case 7: { // Digital Prime (FBX)
palpath += "Digital_Prime_FBX.pal";
break;
}
case 8: { // Magnum (FBX)
palpath += "Magnum_FBX.pal";
break;
}
case 9: { // PVM Style D93 (FBX)
palpath += "PVM_Style_D93_FBX.pal";
break;
}
case 10: { // Smooth V2 (FBX)
palpath += "Smooth_V2_FBX.pal";
break;
}
case 11: // RGB Palette
video.GetPalette().SetMode(Video::Palette::MODE_RGB);
break;
case 12: { // Custom
palpath = std::string(pathinfo.user) + "/custom.pal";
break;
}
default: break;
}
// Load the palette from an external file
if (settings_nst[PALETTE].val > 4 && settings_nst[PALETTE].val != 11) {
std::ifstream ifs(palpath.c_str(), std::ifstream::binary);
if (ifs.is_open()) {
std::filebuf *pbuf = ifs.rdbuf();
std::size_t size = pbuf->pubseekoff(0, ifs.end, ifs.in);
pbuf->pubseekpos(0, ifs.in);
char *buffer = new char[size];
pbuf->sgetn(buffer, size);
video.GetPalette().SetMode(Video::Palette::MODE_CUSTOM);
video.GetPalette().SetCustom((Video::Palette::Colors)buffer,
Video::Palette::STD_PALETTE);
ifs.close();
delete[] buffer;
}
}
if (!Machine(emulator).Is(Machine::DISK)) {
switch (Cartridge(emulator).GetProfile()->system.type) {
case Cartridge::Profile::System::VS_UNISYSTEM:
case Cartridge::Profile::System::VS_DUALSYSTEM: {
if (settings_nst[PALETTE].val >= 4) {
video.GetPalette().SetMode(Video::Palette::MODE_YUV);
video.SetDecoder(Video::DECODER_CONSUMER);
}
}
default: break;
}
}
video.EnableUnlimSprites(settings_nst[UNLIMITEDSPRITES].val);
if (NES_FAILED(video.SetRenderState(renderState))) {
jg_cb_log(JG_LOG_ERR, "Nestopia core rejected render state\n");
}
video.Blit(*NstVideo); // Make changes visible if emulation is paused
}
void jg_set_cb_audio(jg_cb_audio_t func) {
jg_cb_audio = func;
}
void jg_set_cb_frametime(jg_cb_frametime_t func) {
jg_cb_frametime = func;
}
void jg_set_cb_log(jg_cb_log_t func) {
jg_cb_log = func;
}
void jg_set_cb_rumble(jg_cb_rumble_t func) {
jg_cb_rumble = func;
}
int jg_init(void) {
if (!nst_db_load()) {
jg_cb_log(JG_LOG_ERR, "Failed to load NstDatabase.xml\n");
return 0;
}
NstVideo = new Video::Output;
NstSound = new Sound::Output;
NstInput = new Input::Controllers;
// Set internal callbacks
Video::Output::lockCallback.Set(nst_cb_videolock, 0);
Video::Output::unlockCallback.Set(nst_cb_videounlock, 0);
User::eventCallback.Set(nst_cb_event, 0);
User::fileIoCallback.Set(nst_cb_file, 0);
User::logCallback.Set(nst_cb_log, 0);
// Initialize the audio filters
afilter[FILTER_FO_LPF] = afilt_init(FILTER_FO_LPF, 14000, SAMPLERATE);
afilter[FILTER_FO_HPF] = afilt_init(FILTER_FO_HPF, 220, SAMPLERATE);
return 1;
}
void jg_deinit(void) {
if (NstVideo)
delete NstVideo;
if (NstSound)
delete NstSound;
if (NstInput)
delete NstInput;
}
void jg_reset(int hard) {
if (hard)
Machine(emulator).SetRamPowerState(settings_nst[RAMPOWERSTATE].val);
Machine(emulator).Reset(hard);
Fds(emulator).EjectDisk();
Fds(emulator).InsertDisk(0, 0);
}
void jg_exec_frame(void) {
emulator.Execute(NstVideo, NstSound, NstInput);
}
int jg_game_load(void) {
Machine machine(emulator);
Nes::Result result;
// Load the game into memory
std::string rombuf((const char*)gameinfo.data, gameinfo.size);
std::istringstream file(rombuf);
// Check if it's an FDS game
if (strstr(gameinfo.fname, ".fds") || strstr(gameinfo.fname, ".FDS")) {
Fds fds(emulator);
// Load the FDS BIOS as an auxiliary file if one was specified
if (biosinfo.size) {
std::string biosbuf((const char*)biosinfo.data, biosinfo.size);
std::istringstream fdsbios(biosbuf);
fds.SetBIOS(&fdsbios);
}
else { // Load the BIOS from the default path
// Set up the path to the FDS BIOS
std::string fdspath = std::string(pathinfo.bios) + "/disksys.rom";
jg_cb_log(JG_LOG_INF, "FDS BIOS Path: %s\n", fdspath.c_str());
// Load the FDS BIOS
std::ifstream *fdsbios = new std::ifstream(fdspath.c_str(),
std::ifstream::in|std::ifstream::binary);
if (fdsbios->is_open()) {
fds.SetBIOS(fdsbios);
delete fdsbios;
}
else {
jg_cb_log(JG_LOG_INF, "Failed to load FDS BIOS\n");
}
}
}
if (settings_nst[SOFTPATCH].val) {
// Attempt to open an IPS patch matching the filename of the loaded ROM
std::string ppath(gameinfo.path);
ppath = ppath.substr(0, ppath.find_last_of(".")) + ".ips";
std::ifstream pfile(ppath.c_str(), std::ios::in|std::ios::binary);
// If there is no IPS patch, try to find a UPS patch
if (!pfile.is_open()) {
ppath = ppath.substr(0, ppath.find_last_of(".")) + ".ups";
std::ifstream pfile(ppath.c_str(), std::ios::in|std::ios::binary);
}
// If a patch was found, apply it
if (pfile.is_open()) {
Machine::Patch patch(pfile);
result = machine.Load(file,
(Machine::FavoredSystem)settings_nst[FAVSYSTEM].val, patch);
jg_cb_log(JG_LOG_INF, "Patch Applied: %s\n", ppath.c_str());
}
else {
result = machine.Load(file,
(Machine::FavoredSystem)settings_nst[FAVSYSTEM].val);
}
}
else {
result = machine.Load(file,
(Machine::FavoredSystem)settings_nst[FAVSYSTEM].val);
}
if (NES_FAILED(result)) {
switch (result) {
case Nes::RESULT_ERR_INVALID_FILE:
jg_cb_log(JG_LOG_ERR, "Invalid file\n");
break;
case Nes::RESULT_ERR_OUT_OF_MEMORY:
jg_cb_log(JG_LOG_ERR, "Out of Memory\n");
break;
case Nes::RESULT_ERR_CORRUPT_FILE:
jg_cb_log(JG_LOG_ERR, "Corrupt or Missing File\n");
break;
case Nes::RESULT_ERR_UNSUPPORTED_MAPPER:
jg_cb_log(JG_LOG_ERR, "Unsupported Mapper\n");
break;
case Nes::RESULT_ERR_MISSING_BIOS:
jg_cb_log(JG_LOG_ERR, "Missing FDS BIOS\n");
break;
default:
jg_cb_log(JG_LOG_ERR, "%d\n", result);
break;
}
return 0;
}
// Force the region based on "Favored System", or use the automatic region
if (settings_nst[FORCEREGION].val) {
if (settings_nst[FAVSYSTEM].val & 0x1)
machine.SetMode(Machine::PAL);
else
machine.SetMode(Machine::NTSC);
}
else {
machine.SetMode(machine.GetDesiredMode());
}
// Adjustments for PAL mode
if (machine.GetMode() == Machine::PAL) {
audinfo.spf = (SAMPLERATE / (unsigned)FRAMERATE_PAL) * CHANNELS;
jg_cb_frametime(FRAMERATE_PAL);
}
else {
audinfo.spf = (SAMPLERATE / (unsigned)FRAMERATE) * CHANNELS;
jg_cb_frametime(FRAMERATE);
}
nst_params_input();
// Set up FDS and Vs. System
if (machine.Is(Machine::DISK)) { // Auto-insert FDS Disk
coreinfo.hints |= JG_HINT_INPUT_AUDIO;
Fds fds(emulator);
fds.InsertDisk(0, 0);
}
else { // Famicom and VS. System
const Cartridge::Profile *profile = Cartridge(emulator).GetProfile();
switch (profile->system.type) {
case Cartridge::Profile::System::FAMICOM: {
coreinfo.hints |= JG_HINT_INPUT_AUDIO;
// Jump through flaming hoops to get the mapper number
unsigned mapper = Cartridge::Database(emulator).FindEntry(
profile->hash, Machine::FAVORED_FAMICOM).GetMapper();
// Check if it's Karaoke Studio
if (mapper == 188) {
inputinfo[4] = jg_nes_inputinfo(4, JG_NES_KARAOKESTUDIO);
Input::Controllers::KaraokeStudio::callback.Set(
nst_poll_karaokestudio, input_device[4]);
kstudio = true;
}
break;
}
case Cartridge::Profile::System::VS_UNISYSTEM:
case Cartridge::Profile::System::VS_DUALSYSTEM: {
inputinfo[4] = jg_nes_inputinfo(4, JG_NES_VSSYS);
Input::Controllers::VsSystem::callback.Set(nst_poll_vssys,
input_device[4]);
break;
}
default: break;
}
}
// Set the RAM Power State
machine.SetRamPowerState(settings_nst[RAMPOWERSTATE].val);
// Start the machine
machine.Power(true);
return 1;
}
int jg_game_unload(void) {
Machine(emulator).Power(false);
Machine(emulator).Unload();
return 1;
}
int jg_state_load(const char *filename) {
std::ifstream statefile(filename, std::ifstream::in|std::ifstream::binary);
if (statefile.is_open()) {
Machine(emulator).LoadState(statefile);
statefile.close();
return 1;
}
return 0;
}
void jg_state_load_raw(const void*) {
}
int jg_state_save(const char *filename) {
std::ofstream statefile(filename, std::ifstream::out|std::ifstream::binary);
if (statefile.is_open()) {
Machine(emulator).SaveState(statefile,
Nes::Api::Machine::NO_COMPRESSION);
statefile.close();
return 1;
}
return 0;
}
const void* jg_state_save_raw(void) {
return NULL;
}
size_t jg_state_size(void) {
return 0;
}
void jg_media_select(void) {
if (Machine(emulator).Is(Machine::DISK)) {
Fds fds(emulator);
int disk = fds.GetCurrentDisk();
int side = fds.GetCurrentDiskSide();
// Switch disks if it's a multi-disk game on Side B
if (fds.GetNumDisks() > 1 && side == 1) {
fds.EjectDisk();
fds.InsertDisk(!disk, 0);
}
else if (fds.CanChangeDiskSide()) {
fds.ChangeSide();
}
nst_fds_info();
}
}
void jg_media_insert(void) {
if (Machine(emulator).Is(Machine::DISK)) {
Fds fds(emulator);
fds.IsAnyDiskInserted() ? fds.EjectDisk() : fds.InsertDisk(0, 0);
nst_fds_info();
}
}
void jg_cheat_clear(void) {
Cheats cheats(emulator);
cheats.ClearCodes();
}
void jg_cheat_set(const char *code) {
Cheats cheats(emulator);
Cheats::Code cheatcode;
// Check if the cheat is Raw
if (strstr(code, "0x")) {
std::vector<std::string> tokens;
std::stringstream rawcode(code);
std::string token;
while (std::getline(rawcode, token, ' '))
tokens.push_back(token);
if ((tokens.size() == 2) || (tokens.size() == 3)) {
cheatcode.address = strtoul(tokens[0].c_str(), NULL, 16);
cheatcode.value = strtoul(tokens[1].c_str(), NULL, 16);
if (tokens.size() == 3) {
cheatcode.compare = strtoul(tokens[2].c_str(), NULL, 16);
cheatcode.useCompare = true;
}
cheats.SetCode(cheatcode);
}
else {
jg_cb_log(JG_LOG_WRN, "Invalid cheat: %s\n", code);
}
}
// Game Genie
else if (cheats.GameGenieDecode(code, cheatcode) == Nes::RESULT_OK) {
cheats.SetCode(cheatcode);
}
// Pro Action Rocky
else if (cheats.ProActionRockyDecode(code, cheatcode) == Nes::RESULT_OK) {
cheats.SetCode(cheatcode);
}
else {
jg_cb_log(JG_LOG_WRN, "Invalid cheat: %s\n", code);
}
}
void jg_rehash(void) {
nst_params_video();
nst_params_input();
}
void jg_data_push(uint32_t type, int, const void *ptr, size_t size) {
if (type == JG_DATA_AUDIO) {
int16_t *buf = (int16_t*)(((jg_audioinfo_t*)ptr)->buf);
size_t total = 0;
for (size_t i = 0; i < size; ++i)
total += abs(buf[i]);
if (total / size > 2500)
micstate ^= 0x04;
}
}
jg_coreinfo_t* jg_get_coreinfo(const char *sys) {
return &coreinfo;
}
jg_videoinfo_t* jg_get_videoinfo(void) {
return &vidinfo;
}
jg_audioinfo_t* jg_get_audioinfo(void) {
return &audinfo;
}
jg_inputinfo_t* jg_get_inputinfo(int port) {
return &inputinfo[port];
}
jg_setting_t* jg_get_settings(size_t *numsettings) {
*numsettings = sizeof(settings_nst) / sizeof(jg_setting_t);
return settings_nst;
}
void jg_setup_video(void) {
nst_params_video();
}
void jg_setup_audio(void) {
NstSound->samples[0] = audinfo.buf;
NstSound->length[0] = audinfo.spf;
NstSound->samples[1] = NULL;
NstSound->length[1] = 0;
// Set up sound parameters
Sound sound(emulator);
sound.SetSampleRate(audinfo.rate);
sound.SetSpeaker(Sound::SPEAKER_MONO);
sound.SetSpeed(Sound::DEFAULT_SPEED);
sound.SetVolume(Sound::ALL_CHANNELS, 100);
sound.SetGenie(settings_nst[GENIEDISTORTION].val);
Sound::Output::lockCallback.Set(nst_cb_soundlock, 0);
Sound::Output::unlockCallback.Set(nst_cb_soundunlock, 0);
}
void jg_set_inputstate(jg_inputstate_t *ptr, int port) {
input_device[port] = ptr;
}
void jg_set_gameinfo(jg_fileinfo_t info) {
gameinfo = info;
}
void jg_set_auxinfo(jg_fileinfo_t info, int index) {
if (index)
return;
biosinfo = info;
}
void jg_set_paths(jg_pathinfo_t paths) {
pathinfo = paths;
}