GBA003/Cores/NESDeltaCore/NestopiaJS/NESEmulatorBridge.cpp
2024-05-30 10:22:15 +08:00

364 lines
9.1 KiB
C++

//
// NESEmulatorBridge.cpp
// NESDeltaCore
//
// Created by Riley Testut on 6/1/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
#include "NESEmulatorBridge.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"
// Nestopia
#include "NstBase.hpp"
#include "NstApiEmulator.hpp"
#include "NstApiMachine.hpp"
#include "NstApiCartridge.hpp"
#include "NstApiUser.hpp"
#include "NstApiInput.hpp"
#include "NstApiSound.hpp"
#include "NstApiVideo.hpp"
#include "NstApiCheats.hpp"
#pragma clang diagnostic pop
// C++
#include <iostream>
#include <fstream>
// Variables
Nes::Api::Emulator nes_emulator;
Nes::Api::Sound::Output nes_audioOutput;
Nes::Api::Video::Output nes_videoOutput;
Nes::Api::Input::Controllers nes_controllers;
Nes::Api::Machine nes_machine(nes_emulator);
Nes::Api::Cartridge::Database nes_database(nes_emulator);
Nes::Api::Input nes_input(nes_emulator);
Nes::Api::Sound nes_audio(nes_emulator);
Nes::Api::Video nes_video(nes_emulator);
Nes::Api::Cheats nes_cheats(nes_emulator);
VoidCallback saveCallback = NULL;
BufferCallback audioCallback = NULL;
BufferCallback videoCallback = NULL;
uint16_t audioBuffer[0x8000];
uint8_t videoBuffer[Nes::Api::Video::Output::WIDTH * Nes::Api::Video::Output::HEIGHT * 2];
char *gameSaveSavePath = NULL;
char *gameSaveLoadPath = NULL;
bool gameLoaded = false;
char *gamePath = NULL;
static bool NST_CALLBACK AudioLock(void *context, Nes::Api::Sound::Output& audioOutput);
static void NST_CALLBACK AudioUnlock(void *context, Nes::Api::Sound::Output& audioOutput);
static bool NST_CALLBACK VideoLock(void *context, Nes::Api::Video::Output& videoOutput);
static void NST_CALLBACK VideoUnlock(void *context, Nes::Api::Video::Output& videoOutput);
static void NST_CALLBACK FileIO(void *context, Nes::Api::User::File& file);
unsigned int NESPreferredAudioFrameLength()
{
unsigned int frameRate = (nes_machine.GetMode() == Nes::Api::Machine::PAL) ? 50 : 60;
unsigned int preferredAudioFrameLength = (44100 / frameRate);
return preferredAudioFrameLength;
}
double NESFrameDuration()
{
double frameDuration = (nes_machine.GetMode() == Nes::Api::Machine::PAL) ? (1.0 / 50.0) : (1.0 / 60.0);
return frameDuration;
}
#pragma mark - Initialization/Deallocation -
void NESInitialize(const char *databasePath)
{
/* Load Database */
std::ifstream databaseFileStream(databasePath, std::ifstream::in | std::ifstream::binary);
nes_database.Load(databaseFileStream);
nes_database.Enable();
/* Prepare Callbacks */
Nes::Api::Sound::Output::lockCallback.Set(AudioLock, NULL);
Nes::Api::Sound::Output::unlockCallback.Set(AudioUnlock, NULL);
Nes::Api::Video::Output::lockCallback.Set(VideoLock, NULL);
Nes::Api::Video::Output::unlockCallback.Set(VideoUnlock, NULL);
Nes::Api::User::fileIoCallback.Set(FileIO, NULL);
}
#pragma mark - Emulation -
bool NESStartEmulation(const char *gameFilepath)
{
gamePath = strdup(gameFilepath);
/* Load Game */
std::ifstream gameFileStream(gameFilepath, std::ios::in | std::ios::binary);
Nes::Result result = nes_machine.Load(gameFileStream, Nes::Api::Machine::FAVORED_NES_NTSC);
if (NES_FAILED(result))
{
std::cout << "Failed to launch game at " << gameFilepath << ". Error Code: " << result << std::endl;
return false;
}
nes_machine.SetMode(nes_machine.GetDesiredMode());
/* Prepare Audio */
nes_audio.SetSampleBits(16);
nes_audio.SetSampleRate(44100);
nes_audio.SetVolume(Nes::Api::Sound::ALL_CHANNELS, 85);
nes_audio.SetSpeaker(Nes::Api::Sound::SPEAKER_MONO);
nes_audioOutput.samples[0] = audioBuffer;
nes_audioOutput.length[0] = NESPreferredAudioFrameLength();
nes_audioOutput.samples[1] = NULL;
nes_audioOutput.length[1] = 0;
/* Prepare Video */
nes_video.EnableUnlimSprites(true);
nes_videoOutput.pixels = videoBuffer;
nes_videoOutput.pitch = Nes::Api::Video::Output::WIDTH * 2;
Nes::Api::Video::RenderState renderState;
renderState.filter = Nes::Api::Video::RenderState::FILTER_NONE;
renderState.width = Nes::Api::Video::Output::WIDTH;
renderState.height = Nes::Api::Video::Output::HEIGHT;
// RGB 565
renderState.bits.count = 16;
renderState.bits.mask.r = 0xF800;
renderState.bits.mask.g = 0x07E0;
renderState.bits.mask.b = 0x001F;
if (NES_FAILED(nes_video.SetRenderState(renderState)))
{
return false;
}
/* Prepare Inputs */
nes_input.ConnectController(0, Nes::Api::Input::PAD1);
/* Start Emulation */
nes_machine.Power(true);
gameLoaded = true;
return true;
}
void NESStopEmulation()
{
gamePath = NULL;
gameLoaded = false;
nes_machine.Unload();
}
#pragma mark - Game Loop -
void NESRunFrame()
{
nes_emulator.Execute(&nes_videoOutput, &nes_audioOutput, &nes_controllers);
}
#pragma mark - Inputs -
void NESActivateInput(int input, int playerIndex)
{
nes_controllers.pad[playerIndex].buttons |= input;
}
void NESDeactivateInput(int input, int playerIndex)
{
nes_controllers.pad[playerIndex].buttons &= ~input;
}
void NESResetInputs()
{
for (int index = 0; index < Nes::Api::Input::NUM_PADS; index++)
{
nes_controllers.pad[index].buttons = 0;
}
}
#pragma mark - Save States -
void NESSaveSaveState(const char *saveStateFilepath)
{
std::ofstream fileStream(saveStateFilepath, std::ifstream::out | std::ifstream::binary);
nes_machine.SaveState(fileStream);
}
void NESLoadSaveState(const char *saveStateFilepath)
{
std::ifstream fileStream(saveStateFilepath, std::ifstream::in | std::ifstream::binary);
nes_machine.LoadState(fileStream);
}
#pragma mark - Game Saves -
void NESSaveGameSave(const char *gameSavePath)
{
gameSaveSavePath = strdup(gameSavePath);
std::string saveStatePath(gameSavePath);
saveStatePath += ".temp";
// Create tempoary save state.
NESSaveSaveState(saveStatePath.c_str());
// Unload cartridge, which forces emulator to save game.
nes_machine.Unload();
// Check after machine.Unload but before restarting to make sure we aren't starting emulator when no game is loaded.
if (!gameLoaded)
{
return;
}
// Restart emulation.
NESStartEmulation(gamePath);
// Load previous save save.
NESLoadSaveState(saveStatePath.c_str());
// Delete temporary save state.
remove(saveStatePath.c_str());
}
void NESLoadGameSave(const char *gameSavePath)
{
gameSaveLoadPath = strdup(gameSavePath);
// Restart emulation so FileIO callback is called.
NESStartEmulation(gamePath);
}
#pragma mark - Cheats -
bool NESAddCheatCode(const char *cheatCode)
{
Nes::Api::Cheats::Code code;
if (NES_FAILED(Nes::Api::Cheats::GameGenieDecode(cheatCode, code)))
{
return false;
}
if (NES_FAILED(nes_cheats.SetCode(code)))
{
return false;
}
return true;
}
void NESResetCheats()
{
nes_cheats.ClearCodes();
}
#pragma mark - Callbacks -
void NESSetAudioCallback(BufferCallback callback)
{
audioCallback = callback;
}
void NESSetVideoCallback(BufferCallback callback)
{
videoCallback = callback;
}
void NESSetSaveCallback(VoidCallback callback)
{
saveCallback = callback;
}
static bool NST_CALLBACK AudioLock(void *context, Nes::Api::Sound::Output& audioOutput)
{
return true;
}
static void NST_CALLBACK AudioUnlock(void *context, Nes::Api::Sound::Output& audioOutput)
{
if (audioCallback == NULL)
{
return;
}
audioCallback((unsigned char *)audioBuffer, NESPreferredAudioFrameLength() * sizeof(int16_t));
}
static bool NST_CALLBACK VideoLock(void *context, Nes::Api::Video::Output& videoOutput)
{
return true;
}
static void NST_CALLBACK VideoUnlock(void *context, Nes::Api::Video::Output& videoOutput)
{
if (videoCallback == NULL)
{
return;
}
(*videoCallback)((const unsigned char *)videoBuffer, Nes::Api::Video::Output::WIDTH * Nes::Api::Video::Output::HEIGHT * 2);
}
static void NST_CALLBACK FileIO(void *context, Nes::Api::User::File& file)
{
switch (file.GetAction())
{
case Nes::Api::User::File::LOAD_BATTERY:
case Nes::Api::User::File::LOAD_EEPROM:
{
if (gameSaveLoadPath == NULL)
{
return;
}
std::ifstream fileStream(gameSaveLoadPath);
file.SetContent(fileStream);
gameSaveLoadPath = NULL;
break;
}
case Nes::Api::User::File::SAVE_BATTERY:
case Nes::Api::User::File::SAVE_EEPROM:
{
if (gameSaveSavePath == NULL)
{
if (saveCallback != NULL)
{
saveCallback();
}
return;
}
std::ofstream fileStream(gameSaveSavePath);
file.GetContent(fileStream);
gameSaveSavePath = NULL;
break;
}
default:
break;
}
}