Files

2575 lines
101 KiB
C++

#include <string>
#include <cstdlib>
#include <cstdio>
#include <cstdarg>
#include <sstream>
#include <algorithm>
#include <fstream>
#include <vector>
#include <iterator>
#include <sys/stat.h>
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#define HAS_STD_FILESYSTEM 1
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#define HAS_STD_FILESYSTEM 1
#endif
#include "LibretroKeyManager.h"
#include "LibretroMessageManager.h"
#include "libretro.h"
#include "../Core/Shared/Audio/SoundMixer.h"
#include "../Core/Shared/BaseControlManager.h"
#include "../Core/NES/NesConsole.h"
#include "../Core/SNES/SnesConsole.h"
#include "../Core/Gameboy/Gameboy.h"
#include "../Core/GBA/GbaConsole.h"
#include "../Core/PCE/PceConsole.h"
#include "../Core/SMS/SmsConsole.h"
#include "../Core/WS/WsConsole.h"
#include "../Core/Shared/Video/VideoDecoder.h"
#include "../Core/Shared/Video/BaseVideoFilter.h"
#include "../Core/Shared/Video/VideoRenderer.h"
#include "../Core/Shared/ColorUtilities.h"
#include "../Core/NES/NesMemoryManager.h"
#include "../Core/NES/BaseMapper.h"
#include "../Core/Shared/EmuSettings.h"
#include "../Core/Shared/CheatManager.h"
#include "../Core/NES/HdPacks/HdData.h"
#include "../Core/Shared/SaveStateManager.h"
#include "../Core/Debugger/DebugTypes.h"
#include "../Core/NES/GameDatabase.h"
#include "../Core/NES/NesSoundMixer.h"
#include "../Core/Shared/Interfaces/IAudioDevice.h"
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/VirtualFile.h"
#include "VfsFile.h"
#include "../Utilities/UTF8Util.h"
#define DEVICE_AUTO RETRO_DEVICE_JOYPAD
#define DEVICE_GAMEPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0)
#define DEVICE_POWERPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 1)
#define DEVICE_FAMILYTRAINER RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 2)
#define DEVICE_PARTYTAP RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 3)
#define DEVICE_PACHINKO RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 4)
#define DEVICE_EXCITINGBOXING RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 5)
#define DEVICE_KONAMIHYPERSHOT RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 6)
#define DEVICE_SNESGAMEPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 7)
#define DEVICE_GAMEBOYGAMEPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 8)
#define DEVICE_VBGAMEPAD RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 9)
#define DEVICE_ZAPPER RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_POINTER, 0)
#define DEVICE_OEKAKIDS RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_POINTER, 1)
#define DEVICE_BANDAIHYPERSHOT RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_POINTER, 2)
#define DEVICE_ARKANOID RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 0)
#define DEVICE_HORITRACK RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 1)
#define DEVICE_SNESMOUSE RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_MOUSE, 2)
#define DEVICE_ASCIITURBOFILE RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_NONE, 0)
#define DEVICE_BATTLEBOX RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_NONE, 1)
#define DEVICE_FOURPLAYERADAPTER RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_NONE, 2)
static retro_log_printf_t logCallback = nullptr;
retro_environment_t env_cb = nullptr;
static unsigned _inputDevices[5] = { DEVICE_AUTO, DEVICE_AUTO, DEVICE_AUTO, DEVICE_AUTO, DEVICE_AUTO };
static bool _hdPacksEnabled = false;
static string _mesenVersion = "";
static int32_t _saveStateSize = -1;
static bool _shiftButtonsClockwise = false;
static int32_t _audioSampleRate = 44100;
//Include game database as a byte array (representing the MesenDB.txt file)
#include "MesenDB.inc"
static std::unique_ptr<Emulator> _emu;
// shared_ptr to the console (IConsole interface, may be null until emulator initialized)
// Supports all console types: NES, SNES, Gameboy, GBA, PC Engine, SMS, Wonderswan
static std::shared_ptr<IConsole> _console;
static bool FolderExists(const string& folder)
{
if(VfsIo::SupportsFolderOps()) {
//The path may not exist on the real filesystem (e.g. Android SAF)
return VfsIo::IsFolder(folder);
}
#ifdef HAS_STD_FILESYSTEM
return fs::exists(folder) && fs::is_directory(folder);
#else
struct stat st;
return stat(folder.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
#endif
}
static bool CreatePceFirmwareStub(const string& systemFolder, string& createdPath, string& backupPath)
{
if(systemFolder.empty()) {
return false;
}
static const vector<string> firmwareSources = { "syscard3.pce", "syscard3.bin", "syscard.pce", "syscard.bin" };
const string targetName = "[BIOS] Super CD-ROM System (Japan) (v3.0).pce";
string target = FolderUtilities::CombinePath(systemFolder, targetName);
createdPath.clear();
backupPath.clear();
VirtualFile targetFile(target);
if(targetFile.IsValid() && targetFile.GetSize() == 0x40000) {
return false;
}
for(const string& sourceName : firmwareSources) {
string sourcePath = FolderUtilities::CombinePath(systemFolder, sourceName);
VirtualFile sourceFile(sourcePath);
if(!sourceFile.IsValid()) {
continue;
}
uint64_t sourceSize = sourceFile.GetSize();
if(sourceSize < 0x40000) {
continue;
}
vector<uint8_t> sourceData;
if(!sourceFile.ReadFile(sourceData) || sourceData.size() < 0x40000) {
continue;
}
size_t sourceOffset = 0;
if(sourceSize == 0x60200 || sourceSize == 0x40200) {
sourceOffset = 0x200;
}
if(targetFile.IsValid()) {
backupPath = target + ".libretro.bak";
int backupIndex = 1;
while(utf8::ifstream(backupPath, std::ios::binary).good()) {
backupPath = target + ".libretro.bak" + std::to_string(backupIndex);
backupIndex++;
}
if(!VfsIo::Rename(target, backupPath)) {
backupPath.clear();
break;
}
}
utf8::ofstream out(target, std::ios::binary | std::ios::trunc);
if(!out) {
if(!backupPath.empty()) {
VfsIo::Rename(backupPath, target);
}
continue;
}
out.write((const char*)sourceData.data() + sourceOffset, 0x40000);
out.close();
VirtualFile verified(target);
if(verified.IsValid() && verified.GetSize() == 0x40000) {
createdPath = target;
return true;
}
if(!backupPath.empty()) {
VfsIo::Rename(backupPath, target);
}
}
return false;
}
static void RestorePceFirmwareStub(const string& createdPath, const string& backupPath)
{
if(createdPath.empty()) {
return;
}
if(!backupPath.empty()) {
VfsIo::Rename(backupPath, createdPath);
} else {
VfsIo::Remove(createdPath);
}
}
static std::shared_ptr<LibretroKeyManager> _keyManager;
static std::unique_ptr<LibretroMessageManager> _message_manager;
static retro_audio_sample_batch_t _audioSampleBatch = nullptr;
static retro_video_refresh_t _videoRefresh = nullptr;
// Saved input callbacks (set by frontend before core may be ready)
static retro_input_state_t _savedGetInputState = nullptr;
static retro_input_poll_t _savedPollInput = nullptr;
// Store user palette locally until renderer API is wired
static std::vector<uint32_t> _userRgbPalette;
// keep selected filter string until we map to EmuSettings
static std::string _selectedNtscFilter;
// local stubs for settings not yet mapped
static bool _videoFilterRaw = false;
static int _ppuNmiBefore = 0;
static int _ppuNmiAfter = 0;
static uint8_t _overscanLeft = 0, _overscanRight = 0, _overscanTop = 0, _overscanBottom = 0;
// store other high-level choices locally until mapped to EmuSettings
static std::string _selectedAspectRatio;
static std::string _selectedRegion;
static std::string _selectedRamState;
// new local stub for screen rotation
static int _screenRotation = 0;
extern "C" void logMessage(retro_log_level level, const char* message);
static bool isSgbDebugEnabled()
{
static bool initialized = false;
static bool enabled = false;
if(!initialized) {
enabled = getenv("MESEN_LIBRETRO_DEBUG_SGB") != nullptr;
initialized = true;
}
return enabled;
}
static void logSgbDebugf(const char* fmt, ...)
{
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
fprintf(stderr, "[libretro] %s\n", buffer);
fflush(stderr);
if(logCallback) {
logCallback(RETRO_LOG_INFO, buffer);
}
}
// Geometry tracking
static bool _geometryDirty = false;
static VideoConfig _lastVideoConfig = {};
static NesConfig _lastNesConfig = {};
static uint32_t _lastReportedWidth = 256;
static uint32_t _lastReportedHeight = 240;
// Small audio device implementation that forwards audio from the core's
// SoundMixer to libretro's audio callback (_audioSampleBatch).
class LibretroAudioDevice : public IAudioDevice {
public:
LibretroAudioDevice() {
}
~LibretroAudioDevice() override {
}
void PlayBuffer(int16_t *soundBuffer, uint32_t bufferSize, uint32_t sampleRate, bool isStereo) override {
// bufferSize is number of frames. _audioSampleBatch expects interleaved int16_t samples and frame count.
if(!_audioSampleBatch) {
return;
}
// Choose output buffer pointer (interleaved samples)
const int16_t* outPtr = nullptr;
if(isStereo) {
outPtr = soundBuffer;
} else {
// Convert mono -> stereo by duplicating samples into a temporary buffer
_monoBuffer.resize(bufferSize * 2);
for(uint32_t i = 0; i < bufferSize; ++i) {
int16_t s = soundBuffer[i];
_monoBuffer[i * 2 + 0] = s;
_monoBuffer[i * 2 + 1] = s;
}
outPtr = _monoBuffer.data();
}
// Forward frames (bufferSize is frame count)
_audioSampleBatch((const int16_t*)outPtr, (size_t)bufferSize);
}
void Stop() override {}
void Pause() override {}
void ProcessEndOfFrame() override {}
string GetAvailableDevices() override { return string(); }
void SetAudioDevice(string deviceName) override { (void)deviceName; }
AudioStatistics GetStatistics() override { return AudioStatistics(); }
private:
// reused buffer for mono->stereo conversion
std::vector<int16_t> _monoBuffer;
};
static std::unique_ptr<LibretroAudioDevice> _audioDevice;
static constexpr const char* MesenNtscFilter = "mesen_ntsc_filter";
static constexpr const char* MesenPalette = "mesen_palette";
static constexpr const char* MesenSpriteLimit = "mesen_sprite_limit";
static constexpr const char* MesenEnablePalBorders = "mesen_enable_pal_borders";
static constexpr const char* MesenSpritesEnabled = "mesen_sprites_enabled";
static constexpr const char* MesenBackgroundEnabled = "mesen_background_enabled";
static constexpr const char* MesenAllowInvalidInput = "mesen_allow_invalid_input";
static constexpr const char* MesenDisableGameGenieBusConflicts = "mesen_disable_game_genie_bus_conflicts";
static constexpr const char* MesenRandomizeMapperPowerOnState = "mesen_randomize_mapper_power_on_state";
static constexpr const char* MesenRandomizeCpuPpuAlignment = "mesen_randomize_cpu_ppu_alignment";
static constexpr const char* MesenOverclock = "mesen_overclock";
static constexpr const char* MesenOverclockType = "mesen_overclock_type";
//static constexpr const char* MesenOverscanLeft = "mesen_overscan_left";
//static constexpr const char* MesenOverscanRight = "mesen_overscan_right";
//static constexpr const char* MesenOverscanTop = "mesen_overscan_up";
//static constexpr const char* MesenOverscanBottom = "mesen_overscan_down";
//static constexpr const char* MesenAspectRatio = "mesen_aspect_ratio";
static constexpr const char* MesenRegion = "mesen_region";
static constexpr const char* MesenRamState = "mesen_ramstate";
static constexpr const char* MesenControllerTurboSpeed = "mesen_controllerturbospeed";
static constexpr const char* MesenFdsAutoSelectDisk = "mesen_fdsautoinsertdisk";
static constexpr const char* MesenFdsFastForwardLoad = "mesen_fdsfastforwardload";
// static constexpr const char* MesenHdPacks = "mesen_hdpacks";
// static constexpr const char* MesenScreenRotation = "mesen_screenrotation";
static constexpr const char* MesenFakeStereo = "mesen_fake_stereo";
static constexpr const char* MesenMuteTriangleUltrasonic = "mesen_mute_triangle_ultrasonic";
static constexpr const char* MesenReduceDmcPopping = "mesen_reduce_dmc_popping";
static constexpr const char* MesenSwapDutyCycle = "mesen_swap_duty_cycle";
static constexpr const char* MesenDisableNoiseModeFlag = "mesen_disable_noise_mode_flag";
// static constexpr const char* MesenShiftButtonsClockwise = "mesen_shift_buttons_clockwise";
static constexpr const char* MesenAudioSampleRate = "mesen_audio_sample_rate";
uint32_t defaultPalette[0x40] { 0xFF666666, 0xFF002A88, 0xFF1412A7, 0xFF3B00A4, 0xFF5C007E, 0xFF6E0040, 0xFF6C0600, 0xFF561D00, 0xFF333500, 0xFF0B4800, 0xFF005200, 0xFF004F08, 0xFF00404D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFADADAD, 0xFF155FD9, 0xFF4240FF, 0xFF7527FE, 0xFFA01ACC, 0xFFB71E7B, 0xFFB53120, 0xFF994E00, 0xFF6B6D00, 0xFF388700, 0xFF0C9300, 0xFF008F32, 0xFF007C8D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFF64B0FF, 0xFF9290FF, 0xFFC676FF, 0xFFF36AFF, 0xFFFE6ECC, 0xFFFE8170, 0xFFEA9E22, 0xFFBCBE00, 0xFF88D800, 0xFF5CE430, 0xFF45E082, 0xFF48CDDE, 0xFF4F4F4F, 0xFF000000, 0xFF000000, 0xFFFFFEFF, 0xFFC0DFFF, 0xFFD3D2FF, 0xFFE8C8FF, 0xFFFBC2FF, 0xFFFEC4EA, 0xFFFECCC5, 0xFFF7D8A5, 0xFFE4E594, 0xFFCFEF96, 0xFFBDF4AB, 0xFFB3F3CC, 0xFFB5EBF2, 0xFFB8B8B8, 0xFF000000, 0xFF000000 };
uint32_t unsaturatedPalette[0x40] { 0xFF6B6B6B, 0xFF001E87, 0xFF1F0B96, 0xFF3B0C87, 0xFF590D61, 0xFF5E0528, 0xFF551100, 0xFF461B00, 0xFF303200, 0xFF0A4800, 0xFF004E00, 0xFF004619, 0xFF003A58, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFF1A53D1, 0xFF4835EE, 0xFF7123EC, 0xFF9A1EB7, 0xFFA51E62, 0xFFA52D19, 0xFF874B00, 0xFF676900, 0xFF298400, 0xFF038B00, 0xFF008240, 0xFF007891, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF63ADFD, 0xFF908AFE, 0xFFB977FC, 0xFFE771FE, 0xFFF76FC9, 0xFFF5836A, 0xFFDD9C29, 0xFFBDB807, 0xFF84D107, 0xFF5BDC3B, 0xFF48D77D, 0xFF48CCCE, 0xFF555555, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFC4E3FE, 0xFFD7D5FE, 0xFFE6CDFE, 0xFFF9CAFE, 0xFFFEC9F0, 0xFFFED1C7, 0xFFF7DCAC, 0xFFE8E89C, 0xFFD1F29D, 0xFFBFF4B1, 0xFFB7F5CD, 0xFFB7F0EE, 0xFFBEBEBE, 0xFF000000, 0xFF000000 };
uint32_t yuvPalette[0x40] { 0xFF666666, 0xFF002A88, 0xFF1412A7, 0xFF3B00A4, 0xFF5C007E, 0xFF6E0040, 0xFF6C0700, 0xFF561D00, 0xFF333500, 0xFF0C4800, 0xFF005200, 0xFF004C18, 0xFF003E5B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFADADAD, 0xFF155FD9, 0xFF4240FF, 0xFF7527FE, 0xFFA01ACC, 0xFFB71E7B, 0xFFB53120, 0xFF994E00, 0xFF6B6D00, 0xFF388700, 0xFF0D9300, 0xFF008C47, 0xFF007AA0, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF64B0FF, 0xFF9290FF, 0xFFC676FF, 0xFFF26AFF, 0xFFFF6ECC, 0xFFFF8170, 0xFFEA9E22, 0xFFBCBE00, 0xFF88D800, 0xFF5CE430, 0xFF45E082, 0xFF48CDDE, 0xFF4F4F4F, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFC0DFFF, 0xFFD3D2FF, 0xFFE8C8FF, 0xFFFAC2FF, 0xFFFFC4EA, 0xFFFFCCC5, 0xFFF7D8A5, 0xFFE4E594, 0xFFCFEF96, 0xFFBDF4AB, 0xFFB3F3CC, 0xFFB5EBF2, 0xFFB8B8B8, 0xFF000000, 0xFF000000 };
uint32_t nestopiaRgbPalette[0x40] { 0xFF6D6D6D, 0xFF002492, 0xFF0000DB, 0xFF6D49DB, 0xFF92006D, 0xFFB6006D, 0xFFB62400, 0xFF924900, 0xFF6D4900, 0xFF244900, 0xFF006D24, 0xFF009200, 0xFF004949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB6B6B6, 0xFF006DDB, 0xFF0049FF, 0xFF9200FF, 0xFFB600FF, 0xFFFF0092, 0xFFFF0000, 0xFFDB6D00, 0xFF926D00, 0xFF249200, 0xFF009200, 0xFF00B66D, 0xFF009292, 0xFF242424, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF6DB6FF, 0xFF9292FF, 0xFFDB6DFF, 0xFFFF00FF, 0xFFFF6DFF, 0xFFFF9200, 0xFFFFB600, 0xFFDBDB00, 0xFF6DDB00, 0xFF00FF00, 0xFF49FFDB, 0xFF00FFFF, 0xFF494949, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFB6DBFF, 0xFFDBB6FF, 0xFFFFB6FF, 0xFFFF92FF, 0xFFFFB6B6, 0xFFFFDB92, 0xFFFFFF49, 0xFFFFFF6D, 0xFFB6FF49, 0xFF92FF6D, 0xFF49FFDB, 0xFF92DBFF, 0xFF929292, 0xFF000000, 0xFF000000 };
uint32_t compositeDirectPalette[0x40] { 0xFF656565, 0xFF00127D, 0xFF18008E, 0xFF360082, 0xFF56005D, 0xFF5A0018, 0xFF4F0500, 0xFF381900, 0xFF1D3100, 0xFF003D00, 0xFF004100, 0xFF003B17, 0xFF002E55, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFF194EC8, 0xFF472FE3, 0xFF6B1FD7, 0xFF931BAE, 0xFF9E1A5E, 0xFF993200, 0xFF7B4B00, 0xFF5B6700, 0xFF267A00, 0xFF008200, 0xFF007A3E, 0xFF006E8A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF64A9FF, 0xFF8E89FF, 0xFFB676FF, 0xFFE06FFF, 0xFFEF6CC4, 0xFFF0806A, 0xFFD8982C, 0xFFB9B40A, 0xFF83CB0C, 0xFF5BD63F, 0xFF4AD17E, 0xFF4DC7CB, 0xFF4C4C4C, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFC7E5FF, 0xFFD9D9FF, 0xFFE9D1FF, 0xFFF9CEFF, 0xFFFFCCF1, 0xFFFFD4CB, 0xFFF8DFB1, 0xFFEDEAA4, 0xFFD6F4A4, 0xFFC5F8B8, 0xFFBEF6D3, 0xFFBFF1F1, 0xFFB9B9B9, 0xFF000000, 0xFF000000 };
uint32_t nesClassicPalette[0x40] { 0xFF60615F, 0xFF000083, 0xFF1D0195, 0xFF340875, 0xFF51055E, 0xFF56000F, 0xFF4C0700, 0xFF372308, 0xFF203A0B, 0xFF0F4B0E, 0xFF194C16, 0xFF02421E, 0xFF023154, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA9AAA8, 0xFF104BBF, 0xFF4712D8, 0xFF6300CA, 0xFF8800A9, 0xFF930B46, 0xFF8A2D04, 0xFF6F5206, 0xFF5C7114, 0xFF1B8D12, 0xFF199509, 0xFF178448, 0xFF206B8E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFBFBFB, 0xFF6699F8, 0xFF8974F9, 0xFFAB58F8, 0xFFD557EF, 0xFFDE5FA9, 0xFFDC7F59, 0xFFC7A224, 0xFFA7BE03, 0xFF75D703, 0xFF60E34F, 0xFF3CD68D, 0xFF56C9CC, 0xFF414240, 0xFF000000, 0xFF000000, 0xFFFBFBFB, 0xFFBED4FA, 0xFFC9C7F9, 0xFFD7BEFA, 0xFFE8B8F9, 0xFFF5BAE5, 0xFFF3CAC2, 0xFFDFCDA7, 0xFFD9E09C, 0xFFC9EB9E, 0xFFC0EDB8, 0xFFB5F4C7, 0xFFB9EAE9, 0xFFABABAB, 0xFF000000, 0xFF000000 };
uint32_t originalHardwarePalette[0x40] { 0xFF6A6D6A, 0xFF00127D, 0xFF1E008A, 0xFF3B007D, 0xFF56005D, 0xFF5A0018, 0xFF4F0D00, 0xFF381E00, 0xFF203100, 0xFF003D00, 0xFF004000, 0xFF003B1E, 0xFF002E55, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB9BCB9, 0xFF194EC8, 0xFF472FE3, 0xFF751FD7, 0xFF931EAD, 0xFF9E245E, 0xFF963800, 0xFF7B5000, 0xFF5B6700, 0xFF267A00, 0xFF007F00, 0xFF007842, 0xFF006E8A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF69AEFF, 0xFF9798FF, 0xFFB687FF, 0xFFE278FF, 0xFFF279C7, 0xFFF58F6F, 0xFFDDA932, 0xFFBCB70D, 0xFF88D015, 0xFF60DB49, 0xFF4FD687, 0xFF50CACE, 0xFF515451, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFCCEAFF, 0xFFDEE2FF, 0xFFEEDAFF, 0xFFFAD7FD, 0xFFFDD7F6, 0xFFFDDCD0, 0xFFFAE8B6, 0xFFF2F1A9, 0xFFDBFBA9, 0xFFCAFFBD, 0xFFC3FBD8, 0xFFC4F6F6, 0xFFBEC1BE, 0xFF000000, 0xFF000000 };
uint32_t pvmStylePalette[0x40] { 0xFF696964, 0xFF001774, 0xFF28007D, 0xFF3E006D, 0xFF560057, 0xFF5E0013, 0xFF531A00, 0xFF3B2400, 0xFF2A3000, 0xFF143A00, 0xFF003F00, 0xFF003B1E, 0xFF003050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB9B9B4, 0xFF1453B9, 0xFF4D2CDA, 0xFF7A1EC8, 0xFF98189C, 0xFF9D2344, 0xFFA03E00, 0xFF8D5500, 0xFF656D00, 0xFF2C7900, 0xFF008100, 0xFF007D42, 0xFF00788A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF69A8FF, 0xFF9A96FF, 0xFFC28AFA, 0xFFEA7DFA, 0xFFF387B4, 0xFFF1986C, 0xFFE6B327, 0xFFD7C805, 0xFF90DF07, 0xFF64E53C, 0xFF45E27D, 0xFF48D5D9, 0xFF4B4B46, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFD2EAFF, 0xFFE2E2FF, 0xFFF2D8FF, 0xFFF8D2FF, 0xFFF8D9EA, 0xFFFADEB9, 0xFFF9E89B, 0xFFF3F28C, 0xFFD3FA91, 0xFFB8FCA8, 0xFFAEFACA, 0xFFCAF3F3, 0xFFBEBEB9, 0xFF000000, 0xFF000000 };
uint32_t sonyCxa2025AsPalette[0x40] { 0xFF585858, 0xFF00238C, 0xFF00139B, 0xFF2D0585, 0xFF5D0052, 0xFF7A0017, 0xFF7A0800, 0xFF5F1800, 0xFF352A00, 0xFF093900, 0xFF003F00, 0xFF003C22, 0xFF00325D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA1A1A1, 0xFF0053EE, 0xFF153CFE, 0xFF6028E4, 0xFFA91D98, 0xFFD41E41, 0xFFD22C00, 0xFFAA4400, 0xFF6C5E00, 0xFF2D7300, 0xFF007D06, 0xFF007852, 0xFF0069A9, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF1FA5FE, 0xFF5E89FE, 0xFFB572FE, 0xFFFE65F6, 0xFFFE6790, 0xFFFE773C, 0xFFFE9308, 0xFFC4B200, 0xFF79CA10, 0xFF3AD54A, 0xFF11D1A4, 0xFF06BFFE, 0xFF424242, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFA0D9FE, 0xFFBDCCFE, 0xFFE1C2FE, 0xFFFEBCFB, 0xFFFEBDD0, 0xFFFEC5A9, 0xFFFED18E, 0xFFE9DE86, 0xFFC7E992, 0xFFA8EEB0, 0xFF95ECD9, 0xFF91E4FE, 0xFFACACAC, 0xFF000000, 0xFF000000 };
uint32_t wavebeamPalette[0x40] { 0xFF6B6B6B, 0xFF001B88, 0xFF21009A, 0xFF40008C, 0xFF600067, 0xFF64001E, 0xFF590800, 0xFF481600, 0xFF283600, 0xFF004500, 0xFF004908, 0xFF00421D, 0xFF003659, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB4B4B4, 0xFF1555D3, 0xFF4337EF, 0xFF7425DF, 0xFF9C19B9, 0xFFAC0F64, 0xFFAA2C00, 0xFF8A4B00, 0xFF666B00, 0xFF218300, 0xFF008A00, 0xFF008144, 0xFF007691, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFF63B2FF, 0xFF7C9CFF, 0xFFC07DFE, 0xFFE977FF, 0xFFF572CD, 0xFFF4886B, 0xFFDDA029, 0xFFBDBD0A, 0xFF89D20E, 0xFF5CDE3E, 0xFF4BD886, 0xFF4DCFD2, 0xFF525252, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFBCDFFF, 0xFFD2D2FF, 0xFFE1C8FF, 0xFFEFC7FF, 0xFFFFC3E1, 0xFFFFCAC6, 0xFFF2DAAD, 0xFFEBE3A0, 0xFFD2EDA2, 0xFFBCF4B4, 0xFFB5F1CE, 0xFFB6ECF1, 0xFFBFBFBF, 0xFF000000, 0xFF000000 };
extern "C" {
void logMessage(retro_log_level level, const char* message)
{
if(logCallback) {
logCallback(level, message);
}
}
RETRO_API unsigned retro_api_version()
{
return RETRO_API_VERSION;
}
RETRO_API void retro_init()
{
struct retro_log_callback log;
if(env_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
logCallback = log.log;
if(logCallback) {
logMessage(RETRO_LOG_INFO, "Mesen2 libretro core initialized\n");
}
} else {
logCallback = nullptr;
}
fprintf(stderr, "[libretro] retro_init: logCallback=%p\n", (void*)logCallback);
fflush(stderr);
// Create the emulator and initialize its subsystems
_emu.reset(new Emulator());
_emu->Initialize(); // sets up settings, video/audio subsystems, etc.
// Provide the global KeyManager with the emulator settings so calls like
// KeyManager::SetForceFeedback can safely read configuration values.
KeyManager::SetSettings(_emu->GetSettings());
// Grab the IConsole instance and dynamic_cast to NesConsole when needed
auto consoleIf = _emu->GetConsole(); // shared_ptr<IConsole>
_console = std::dynamic_pointer_cast<NesConsole>(consoleIf);
// Key manager accepts the IConsole shared_ptr returned by the emulator
// and also needs a pointer to the Emulator for mouse positioning.
_keyManager = std::make_shared<LibretroKeyManager>(consoleIf, _emu.get());
// Forward any previously saved input callbacks into the key manager and register it
if(_savedGetInputState) _keyManager->SetGetInputState(_savedGetInputState);
if(_savedPollInput) _keyManager->SetPollInput(_savedPollInput);
// KeyManager now expects a shared_ptr<IKeyManager> for thread-safe registration
KeyManager::RegisterKeyManager(std::shared_ptr<IKeyManager>(_keyManager));
_message_manager.reset(new LibretroMessageManager(logCallback, env_cb));
std::stringstream databaseData;
databaseData.write((const char*)MesenDatabase, sizeof(MesenDatabase));
GameDatabase::LoadGameDb(databaseData);
// Map sample rate into the new EmuSettings API
AudioConfig ac = _emu->GetSettings()->GetAudioConfig();
ac.SampleRate = _audioSampleRate;
_emu->GetSettings()->SetAudioConfig(ac);
// Create libretro audio device so we can register it later once a ROM is loaded
_audioDevice.reset(new LibretroAudioDevice());
if(_emu->GetSoundMixer()) {
_emu->GetSoundMixer()->RegisterAudioDevice(_audioDevice.get());
}
// NOTE: many NES-specific flags moved into NesConfig inside EmuSettings.
// Example: to set FDS auto-load you'd edit NesConfig and call SetNesConfig.
// _emu->GetSettings()->GetNesConfig().FdsAutoLoadDisk = true; // then SetNesConfig(...)
if (env_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
_keyManager->SetSupportsInputBitmasks(true);
}
RETRO_API void retro_deinit()
{
if(_keyManager) _keyManager->SetSupportsInputBitmasks(false);
// Unregister the key manager from the global KeyManager to avoid a
// dangling pointer during emulator shutdown. KeyManager::RegisterKeyManager
// accepts an empty shared_ptr to clear the registered backend.
KeyManager::RegisterKeyManager(std::shared_ptr<IKeyManager>());
_keyManager.reset();
_message_manager.reset();
// Properly shut down the emulator instance
if(_emu) {
// Unregister libretro audio device before releasing the emulator
if(_audioDevice) {
if(_emu->GetSoundMixer()) _emu->GetSoundMixer()->RegisterAudioDevice(nullptr);
_audioDevice.reset();
}
// Destroy console(s) first so their destructors run while the emulator
// (and subsystems like SoundMixer) are still available.
_console.reset();
// Make sure KeyManager is unregistered before calling Release()
// (do nothing here because we already unregistered above)
_emu->Release();
_emu.reset();
}
// Now that the emulator instance has been released, clear the settings
// pointer stored in the global KeyManager to avoid dangling references.
KeyManager::SetSettings(nullptr);
}
RETRO_API void retro_set_environment(retro_environment_t env)
{
env_cb = env;
// Core Options v2: Categories
static const retro_core_option_v2_category option_cats[] = {
{ "system", "System", "System settings (region, RAM, overclock)" },
{ "video", "Video", "Video settings (palette, filters, overscan)" },
{ "audio", "Audio", "Audio settings (filters, channels, sample rate)" },
{ "input", "Input", "Input controller settings" },
{ "enhancements", "Enhancements", "Enhancement options (HD packs, sprite limit)" },
{ NULL, NULL, NULL }
};
// Core Options v2: Definitions
static const retro_core_option_v2_definition option_defs[] = {
// System category
{ MesenRegion, "System - Region", "Region", "Select NES region", NULL, "system",
{{ "Auto", "Auto" }, { "NTSC", "NTSC" }, { "PAL", "PAL" }, { "Dendy", "Dendy" }, { NULL, NULL }},
"Auto" },
{ MesenRamState, "System - RAM Power-On State", "RAM Power-On State", "Default power-on state for RAM", NULL, "system",
{{ "All 0s (Default)", "All 0s" }, { "All 1s", "All 1s" }, { "Random Values", "Random" }, { NULL, NULL }},
"All 0s (Default)" },
{ MesenOverclock, "System - Overclock", "Overclock", "Overclock the NES CPU", NULL, "system",
{{ "None", "None" }, { "Low", "Low" }, { "Medium", "Medium" }, { "High", "High" }, { "Very High", "Very High" }, { NULL, NULL }},
"None" },
{ MesenOverclockType, "System - Overclock Type", "Overclock Type", "When to apply overclock", NULL, "system",
{{ "Before NMI (Recommended)", "Before NMI" }, { "After NMI", "After NMI" }, { NULL, NULL }},
"Before NMI (Recommended)" },
{ MesenFdsAutoSelectDisk, "System - FDS Auto Insert Disk", "FDS Auto Insert", "Automatically insert disks on FDS games", NULL, "system",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenFdsFastForwardLoad, "System - FDS Fast Forward On Load", "FDS Fast Forward", "Fast forward while FDS is loading", NULL, "system",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenAllowInvalidInput, "System - Allow Invalid Input", "Allow Invalid Input", "Allow invalid input combinations", NULL, "system",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenRandomizeMapperPowerOnState, "System - Randomize Mapper Power-On State", "Randomize Mapper Power-On", "Randomize mapper power-on state (for testing)", NULL, "system",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenRandomizeCpuPpuAlignment, "System - Randomize CPU/PPU Alignment", "Randomize CPU/PPU Alignment", "Randomize CPU/PPU alignment (for testing)", NULL, "system",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
// Video category
{ MesenPalette, "Video - Palette", "Palette", "Select color palette", NULL, "video",
// TODO:FIXME RAW palette doesn't work
{{ "Default", "Default" }, { "Composite Direct (by FirebrandX)", }, { "Nes Classic", "NES Classic" }, { "Nestopia (RGB)", "Nestopia RGB" }, { "Original Hardware (by FirebrandX)", "Original Hardware" }, { "PVM Style (by FirebrandX)", "PVM Style" }, { "Sony CXA2025AS", "Sony CXA2025AS" }, { "Unsaturated v6 (by FirebrandX)", "Unsaturated v6" }, { "YUV v3 (by FirebrandX)", "YUV v3" }, { "Wavebeam (by nakedarthur)", "Wavebeam" }, { "Custom", "Custom" }, /*{ "Raw", "Raw" },*/ { NULL, NULL }},
"Default" },
// TODO:FIXME the commented NTSC filters don't work
{ MesenNtscFilter, "Video - NTSC Filter", "NTSC Filter", "NTSC video filter", NULL, "video",
{{ "Disabled", "Disabled" }, { "Composite (Blargg)", "Composite (Blargg)" }, /*{ "S-Video (Blargg)", "S-Video (Blargg)" }, { "RGB (Blargg)", "RGB (Blargg)" }, { "Monochrome (Blargg)", "Monochrome (Blargg)" }, { "Bisqwit 2x", "Bisqwit 2x" }, { "Bisqwit 4x", "Bisqwit 4x" }, { "Bisqwit 8x", "Bisqwit 8x" },*/ { NULL, NULL }},
"Disabled" },
// TODO:FIXME overscan cropping and AR options don't work
/* { MesenOverscanLeft, "Video - Overscan Left", "Overscan Left", "Left overscan", NULL, "video",
{{ "None", "None" }, { "4px", "4px" }, { "8px", "8px" }, { "12px", "12px" }, { "16px", "16px" }, { NULL, NULL }},
"None" },
{ MesenOverscanRight, "Video - Overscan Right", "Overscan Right", "Right overscan", NULL, "video",
{{ "None", "None" }, { "4px", "4px" }, { "8px", "8px" }, { "12px", "12px" }, { "16px", "16px" }, { NULL, NULL }},
"None" },
{ MesenOverscanTop, "Video - Overscan Top", "Overscan Top", "Top overscan", NULL, "video",
{{ "None", "None" }, { "4px", "4px" }, { "8px", "8px" }, { "12px", "12px" }, { "16px", "16px" }, { NULL, NULL }},
"None" },
{ MesenOverscanBottom, "Video - Overscan Bottom", "Overscan Bottom", "Bottom overscan", NULL, "video",
{{ "None", "None" }, { "4px", "4px" }, { "8px", "8px" }, { "12px", "12px" }, { "16px", "16px" }, { NULL, NULL }},
"None" },
{ MesenAspectRatio, "Video - Aspect Ratio", "Aspect Ratio", "Display aspect ratio", NULL, "video",
{{ "Auto", "Auto" }, { "No Stretching", "No Stretching" }, { "NTSC", "NTSC" }, { "PAL", "PAL" }, { "4:3", "4:3" }, { "4:3 (Preserved)", "4:3 (Preserved)" }, { "16:9", "16:9" }, { "16:9 (Preserved)", "16:9 (Preserved)" }, { NULL, NULL }},
"Auto" },
{ MesenScreenRotation, "Video - Screen Rotation", "Screen Rotation", "Rotate screen display", NULL, "video",
{{ "None", "None" }, { "90 degrees", "90 degrees" }, { "180 degrees", "180 degrees" }, { "270 degrees", "270 degrees" }, { NULL, NULL }},
"None" },
*/
{ MesenEnablePalBorders, "Video - Enable PAL Borders", "PAL Borders", "Show borders in PAL mode", NULL, "video",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
// Audio category
{ MesenFakeStereo, "Audio - Fake Stereo", "Fake Stereo", "Enable fake stereo effect", NULL, "audio",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenMuteTriangleUltrasonic, "Audio - Reduce Triangle Popping", "Reduce Triangle Popping", "Mute Triangle channel ultrasonic frequencies", NULL, "audio",
{{ "enabled", "On" }, { "disabled", "Off" }, { NULL, NULL }},
"enabled" },
{ MesenReduceDmcPopping, "Audio - Reduce DMC Popping", "Reduce DMC Popping", "Reduce popping on DMC channel", NULL, "audio",
{{ "enabled", "On" }, { "disabled", "Off" }, { NULL, NULL }},
"enabled" },
{ MesenSwapDutyCycle, "Audio - Swap Duty Cycles", "Swap Duty Cycles", "Swap Square channel duty cycles", NULL, "audio",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenDisableNoiseModeFlag, "Audio - Disable Noise Mode Flag", "Disable Noise Mode", "Disable Noise channel mode flag", NULL, "audio",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
{ MesenAudioSampleRate, "Audio - Sample Rate", "Sample Rate", "Audio output sample rate", NULL, "audio",
{{ "48000", "48000 Hz" }, { "96000", "96000 Hz" }, { "11025", "11025 Hz" }, { "22050", "22050 Hz" }, { "44100", "44100 Hz" }, { NULL, NULL }},
"44100" },
// Input category
{ MesenControllerTurboSpeed, "Input - Controller Turbo Speed", "Turbo Speed", "Turbo button speed", NULL, "input",
{{ "Fast", "Fast" }, { "Very Fast", "Very Fast" }, { "Disabled", "Disabled" }, { "Slow", "Slow" }, { "Normal", "Normal" }, { NULL, NULL }},
"Normal" },
/*
{ MesenShiftButtonsClockwise, "Input - Shift Buttons Clockwise", "Shift Buttons", "Shift A/B/X/Y buttons clockwise", NULL, "input",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
*/
// Enhancements category (additional options)
{ MesenSpritesEnabled, "Enhancements - Sprites Enabled", "Sprites Enabled", "Enable sprite rendering", NULL, "enhancements",
{{ "enabled", "On" }, { "disabled", "Off" }, { NULL, NULL }},
"enabled" },
{ MesenBackgroundEnabled, "Enhancements - Background Enabled", "Background Enabled", "Enable background rendering", NULL, "enhancements",
{{ "enabled", "On" }, { "disabled", "Off" }, { NULL, NULL }},
"enabled" },
{ MesenDisableGameGenieBusConflicts, "Enhancements - Disable Game Genie Bus Conflicts", "Game Genie Bus Conflicts", "Disable Game Genie bus conflicts", NULL, "enhancements",
{{ "disabled", "Off" }, { "enabled", "On" }, { NULL, NULL }},
"disabled" },
/*
// Enhancements category
{ MesenHdPacks, "Enhancements - HD Packs", "HD Packs", "Enable HD graphics packs", NULL, "enhancements",
{{ "enabled", "On" }, { "disabled", "Off" }, { NULL, NULL }},
"enabled" },
*/
{ MesenSpriteLimit, "Enhancements - Sprite Limit", "Sprite Limit", "8-sprite scanline limit", NULL, "enhancements",
{{ "normal", "Normal" }, { "adaptive", "Adaptive" }, { "off", "Off" }, { NULL, NULL }},
"normal" },
{ NULL, NULL, NULL, NULL, NULL, NULL, {{ NULL, NULL }}, NULL }
};
static retro_core_options_v2 core_opt_info = {
(retro_core_option_v2_category*)option_cats,
(retro_core_option_v2_definition*)option_defs
};
env_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, &core_opt_info);
static constexpr struct retro_controller_description pads1[] = {
{ "Auto", DEVICE_AUTO },
{ "Standard Controller", DEVICE_GAMEPAD },
{ "Zapper", DEVICE_ZAPPER },
{ "Power Pad", DEVICE_POWERPAD },
{ "Arkanoid", DEVICE_ARKANOID },
{ "SNES Controller", DEVICE_SNESGAMEPAD },
{ "SNES Mouse", DEVICE_SNESMOUSE },
{ "Gameboy Controller", DEVICE_GAMEBOYGAMEPAD },
{ "Virtual Boy Controller" ,DEVICE_VBGAMEPAD },
{ NULL, 0 },
};
static constexpr struct retro_controller_description pads2[] = {
{ "Auto", DEVICE_AUTO },
{ "Standard Controller", DEVICE_GAMEPAD },
{ "Zapper", DEVICE_ZAPPER },
{ "Power Pad", DEVICE_POWERPAD },
{ "Arkanoid", DEVICE_ARKANOID },
{ "SNES Controller", DEVICE_SNESGAMEPAD },
{ "SNES Mouse", DEVICE_SNESMOUSE },
{ "Virtual Boy Controller", DEVICE_VBGAMEPAD },
{ NULL, 0 },
};
static constexpr struct retro_controller_description pads3[] = {
{ "Auto", DEVICE_AUTO },
{ "Standard Controller", DEVICE_GAMEPAD },
{ NULL, 0 },
};
static constexpr struct retro_controller_description pads4[] = {
{ "Auto", DEVICE_AUTO },
{ "Standard Controller", DEVICE_GAMEPAD },
{ NULL, 0 },
};
static constexpr struct retro_controller_description pads5[] = {
{ "Auto", RETRO_DEVICE_JOYPAD },
{ "Arkanoid", DEVICE_ARKANOID },
{ "Ascii Turbo File", DEVICE_ASCIITURBOFILE },
{ "Bandai Hypershot", DEVICE_BANDAIHYPERSHOT },
{ "Battle Box", DEVICE_BATTLEBOX },
{ "Exciting Boxing", DEVICE_EXCITINGBOXING },
{ "Family Trainer", DEVICE_FAMILYTRAINER },
{ "Four Player Adapter", DEVICE_FOURPLAYERADAPTER },
{ "Hori Track", DEVICE_HORITRACK },
{ "Konami Hypershot", DEVICE_KONAMIHYPERSHOT },
{ "Pachinko", DEVICE_PACHINKO },
{ "Partytap", DEVICE_PARTYTAP },
{ "Oeka Kids Tablet", DEVICE_OEKAKIDS },
{ NULL, 0 },
};
static constexpr struct retro_controller_info ports[] = {
{ pads1, 7 },
{ pads2, 7 },
{ pads3, 2 },
{ pads4, 2 },
{ pads5, 13 },
{ 0 },
};
static const struct retro_system_content_info_override content_overrides[] = {
{
"nes|fds|unf|unif|sfc|smc|gb|gbc|gba|pce|sgx|sms|gg|ws|wsc", /* extensions */
false, /* need_fullpath */
false /* persistent_data */
},
{ NULL, false, false }
};
env_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
env_cb(RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE, (void*)content_overrides);
static const struct retro_subsystem_rom_info sgbSubsystemRoms[] = {
{ "Game Boy ROM", "gb|gbc|gbx", false, false, true },
{ "Super Game Boy ROM", "sfc|smc", false, false, true },
{ NULL, NULL, false, false, false }
};
static const struct retro_subsystem_info subsystem_infos[] = {
{ "Super Game Boy", "sgb", sgbSubsystemRoms, 2, 2 },
{ NULL, NULL, NULL, 0, 0 }
};
env_cb(RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO, (void*)subsystem_infos);
//Ask the frontend for its VFS interface - required to load content the
//core cannot open itself (Android SAF content:// URIs in particular).
//v3 is requested so the directory calls (stat/mkdir/opendir) are usable too.
static struct retro_vfs_interface_info vfs_iface_info;
vfs_iface_info.required_interface_version = 3;
vfs_iface_info.iface = nullptr;
if(env_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info) && vfs_iface_info.iface) {
VfsIo::SetInterface(vfs_iface_info.iface, vfs_iface_info.required_interface_version);
} else {
//Fall back to v2 (file I/O only, no directory calls)
vfs_iface_info.required_interface_version = 2;
vfs_iface_info.iface = nullptr;
if(env_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info) && vfs_iface_info.iface) {
VfsIo::SetInterface(vfs_iface_info.iface, vfs_iface_info.required_interface_version);
}
}
}
RETRO_API void retro_set_video_refresh(retro_video_refresh_t sendFrame)
{
// VideoRenderer no longer exposes SetVideoCallback — keep the libretro callback here.
_videoRefresh = sendFrame;
}
RETRO_API void retro_set_audio_sample(retro_audio_sample_t sendAudioSample)
{
}
RETRO_API void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioSampleBatch)
{
// Mixer API changed; keep the libretro callback for later use by audio path
_audioSampleBatch = audioSampleBatch;
}
RETRO_API void retro_set_input_poll(retro_input_poll_t pollInput)
{
// store the callback for later use and forward to key manager if it's active
_savedPollInput = pollInput;
if(_keyManager) _keyManager->SetPollInput(pollInput);
// Probe input callbacks immediately from the thread that set them to
// help debug frontend timing/order issues. This is noisy by default so
// gate it behind an environment variable (MESEN_LIBRETRO_VERBOSE_INPUT).
if((pollInput || _savedGetInputState) && getenv("MESEN_LIBRETRO_VERBOSE_INPUT")) {
extern void libretro_probe_inputs(const char*);
libretro_probe_inputs("setter_poll");
}
}
RETRO_API void retro_set_input_state(retro_input_state_t getInputState)
{
_savedGetInputState = getInputState;
if(_keyManager) _keyManager->SetGetInputState(getInputState);
if((getInputState || _savedPollInput) && getenv("MESEN_LIBRETRO_VERBOSE_INPUT")) {
extern void libretro_probe_inputs(const char*);
libretro_probe_inputs("setter_getstate");
}
}
// Shared diagnostic probe called from multiple places to exercise the saved
// input callbacks and print their results. The argument is a short tag
// describing the caller (for log clarity).
void libretro_probe_inputs(const char* tag)
{
if(!env_cb) return;
if(_savedPollInput) {
try { _savedPollInput(); } catch(...) { }
}
// Query bitmask capability but avoid calling the frontend get_state callback here.
// Some libretro frontends crash if get_state is invoked outside their expected
// input polling context (we observed a segmentation fault). To be safe, only
// log availability and defer actual get_state calls to the normal per-frame
// RefreshState path which runs on the emulator/main thread.
bool bitmasks = false;
if (env_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) bitmasks = true;
}
RETRO_API void retro_reset()
{
// Reset signature changed — call parameterless Reset if available
_console->Reset();
}
bool readVariable(const char* key, retro_variable &var)
{
var.key = key;
var.value = nullptr;
if(env_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value != nullptr)
return true;
return false;
}
uint8_t readOverscanValue(const char* key)
{
retro_variable var = {};
if(readVariable(key, var)) {
string value = string(var.value);
if(value == "4px") {
return 4;
} else if(value == "8px") {
return 8;
} else if(value == "12px") {
return 12;
} else if(value == "16px") {
return 16;
}
}
return 0;
}
void set_flag(const char* flagName, uint64_t flagValue)
{
struct retro_variable var = {};
if(readVariable(flagName, var)) {
string value = string(var.value);
if(value == "disabled") {
_emu->GetSettings()->ClearFlag((EmulationFlags)flagValue);
} else {
_emu->GetSettings()->SetFlag((EmulationFlags)flagValue);
}
}
}
void load_custom_palette()
{
//Setup default palette in case we can't load the custom one
// Ensure video config is accessed so types are instantiated (no-op)
_emu->GetSettings()->GetVideoConfig();
// Store default palette locally; renderer wiring to use this will be added later.
_userRgbPalette.assign(std::begin(defaultPalette), std::end(defaultPalette));
//Try to load the custom palette from the MesenPalette.pal file
string palettePath = FolderUtilities::CombinePath(FolderUtilities::GetHomeFolder(), "MesenPalette.pal");
uint8_t fileData[512 * 3] = {};
utf8::ifstream palette(palettePath, std::ios::binary);
if(palette) {
palette.seekg(0, std::ios::end);
std::streamoff fileSize = palette.tellg();
palette.seekg(0, std::ios::beg);
if((fileSize == 64 * 3) || (fileSize == 512 * 3)) {
palette.read((char*)fileData, fileSize);
uint32_t customPalette[512];
int paletteCount = (int)(fileSize / 3);
for(int i = 0; i < paletteCount; i++) {
customPalette[i] = 0xFF000000 | fileData[i * 3 + 2] | (fileData[i * 3 + 1] << 8) | (fileData[i * 3] << 16);
}
_userRgbPalette.assign(customPalette, customPalette + paletteCount);
}
}
}
void update_settings()
{
struct retro_variable var = { };
NesConfig nesCfg = _emu->GetSettings()->GetNesConfig();
VideoConfig videoCfg = _emu->GetSettings()->GetVideoConfig();
AudioConfig audioCfg = _emu->GetSettings()->GetAudioConfig();
// ===== SYSTEM SETTINGS =====
// Region
if(readVariable(MesenRegion, var)) {
string value = string(var.value);
if(value == "NTSC") {
nesCfg.Region = ConsoleRegion::Ntsc;
} else if(value == "PAL") {
nesCfg.Region = ConsoleRegion::Pal;
} else if(value == "Dendy") {
nesCfg.Region = ConsoleRegion::Dendy;
} else {
nesCfg.Region = ConsoleRegion::Auto;
}
}
// RAM Power-On State
if(readVariable(MesenRamState, var)) {
string value = string(var.value);
if(value == "All 1s") {
nesCfg.RamPowerOnState = RamState::AllOnes;
} else if(value == "Random Values") {
nesCfg.RamPowerOnState = RamState::Random;
} else {
nesCfg.RamPowerOnState = RamState::AllZeros;
}
}
// Overclock
int lineCountBefore = 0;
int lineCountAfter = 0;
bool beforeNmi = true;
if(readVariable(MesenOverclockType, var)) {
string value = string(var.value);
beforeNmi = (value != "After NMI");
}
if(readVariable(MesenOverclock, var)) {
string value = string(var.value);
int lineCount = 0;
if(value == "Low") {
lineCount = 100;
} else if(value == "Medium") {
lineCount = 250;
} else if(value == "High") {
lineCount = 500;
} else if(value == "Very High") {
lineCount = 1000;
}
if(beforeNmi) {
lineCountBefore = lineCount;
} else {
lineCountAfter = lineCount;
}
}
nesCfg.PpuExtraScanlinesBeforeNmi = lineCountBefore;
nesCfg.PpuExtraScanlinesAfterNmi = lineCountAfter;
// FDS options
if(readVariable(MesenFdsAutoSelectDisk, var)) {
nesCfg.FdsAutoInsertDisk = (string(var.value) == "enabled");
}
if(readVariable(MesenFdsFastForwardLoad, var)) {
nesCfg.FdsFastForwardOnLoad = (string(var.value) == "enabled");
}
// ===== VIDEO SETTINGS =====
// Palette
if(readVariable(MesenPalette, var)) {
string value = string(var.value);
if(value == "Default") {
_userRgbPalette.assign(std::begin(defaultPalette), std::end(defaultPalette));
} else if(value == "Composite Direct (by FirebrandX)") {
_userRgbPalette.assign(std::begin(compositeDirectPalette), std::end(compositeDirectPalette));
} else if(value == "Nes Classic") {
_userRgbPalette.assign(std::begin(nesClassicPalette), std::end(nesClassicPalette));
} else if(value == "Nestopia (RGB)") {
_userRgbPalette.assign(std::begin(nestopiaRgbPalette), std::end(nestopiaRgbPalette));
} else if(value == "Original Hardware (by FirebrandX)") {
_userRgbPalette.assign(std::begin(originalHardwarePalette), std::end(originalHardwarePalette));
} else if(value == "PVM Style (by FirebrandX)") {
_userRgbPalette.assign(std::begin(pvmStylePalette), std::end(pvmStylePalette));
} else if(value == "Sony CXA2025AS") {
_userRgbPalette.assign(std::begin(sonyCxa2025AsPalette), std::end(sonyCxa2025AsPalette));
} else if(value == "Unsaturated v6 (by FirebrandX)") {
_userRgbPalette.assign(std::begin(unsaturatedPalette), std::end(unsaturatedPalette));
} else if(value == "YUV v3 (by FirebrandX)") {
_userRgbPalette.assign(std::begin(yuvPalette), std::end(yuvPalette));
} else if(value == "Wavebeam (by nakedarthur)") {
_userRgbPalette.assign(std::begin(wavebeamPalette), std::end(wavebeamPalette));
} else if(value == "Custom") {
load_custom_palette();
} else if(value == "Raw") {
_videoFilterRaw = true;
// For raw mode, use the default palette
_userRgbPalette.assign(std::begin(defaultPalette), std::end(defaultPalette));
}
}
// Copy palette into NesConfig
if(_userRgbPalette.size() >= 512) {
for(size_t i = 0; i < 512; ++i) nesCfg.UserPalette[i] = (i < _userRgbPalette.size()) ? _userRgbPalette[i] : 0xFF000000;
nesCfg.IsFullColorPalette = true;
} else {
for(size_t i = 0; i < 64; ++i) nesCfg.UserPalette[i] = (i < _userRgbPalette.size()) ? _userRgbPalette[i] : 0xFF000000;
nesCfg.IsFullColorPalette = false;
}
// NTSC Filter - map string selection to VideoFilterType enum and apply settings
if(readVariable(MesenNtscFilter, var)) {
string filterValue = string(var.value);
if(filterValue == "Disabled") {
videoCfg.VideoFilter = VideoFilterType::None;
} else if(filterValue == "Composite (Blargg)") {
videoCfg.VideoFilter = VideoFilterType::NtscBlargg;
videoCfg.NtscBlarggPreset_Value = NtscBlarggPreset::Composite;
}/* else if(filterValue == "S-Video (Blargg)") {
videoCfg.VideoFilter = VideoFilterType::NtscBlargg;
videoCfg.NtscBlarggPreset_Value = NtscBlarggPreset::Svideo;
} else if(filterValue == "RGB (Blargg)") {
videoCfg.VideoFilter = VideoFilterType::NtscBlargg;
videoCfg.NtscBlarggPreset_Value = NtscBlarggPreset::Rgb;
} else if(filterValue == "Monochrome (Blargg)") {
videoCfg.VideoFilter = VideoFilterType::NtscBlargg;
videoCfg.NtscBlarggPreset_Value = NtscBlarggPreset::Monochrome;
} else if(filterValue == "Bisqwit 2x") {
videoCfg.VideoFilter = VideoFilterType::NtscBisqwit;
videoCfg.NtscScale = NtscBisqwitFilterScale::_2x;
} else if(filterValue == "Bisqwit 4x") {
videoCfg.VideoFilter = VideoFilterType::NtscBisqwit;
videoCfg.NtscScale = NtscBisqwitFilterScale::_4x;
} else if(filterValue == "Bisqwit 8x") {
videoCfg.VideoFilter = VideoFilterType::NtscBisqwit;
videoCfg.NtscScale = NtscBisqwitFilterScale::_8x;
}*/
}
/*
// Overscan
nesCfg.NtscOverscan.Left = readOverscanValue(MesenOverscanLeft);
nesCfg.NtscOverscan.Right = readOverscanValue(MesenOverscanRight);
nesCfg.NtscOverscan.Top = readOverscanValue(MesenOverscanTop);
nesCfg.NtscOverscan.Bottom = readOverscanValue(MesenOverscanBottom);
// Use same overscan for PAL
nesCfg.PalOverscan = nesCfg.NtscOverscan;
// Aspect Ratio
if(readVariable(MesenAspectRatio, var)) {
_selectedAspectRatio = std::string(var.value ? var.value : "");
}
// Screen Rotation
if(readVariable(MesenScreenRotation, var)) {
string value = string(var.value);
if(value == "90 degrees") {
videoCfg.ScreenRotation = 90;
} else if(value == "180 degrees") {
videoCfg.ScreenRotation = 180;
} else if(value == "270 degrees") {
videoCfg.ScreenRotation = 270;
} else {
videoCfg.ScreenRotation = 0;
}
}
*/
// PAL Borders
if(readVariable(MesenEnablePalBorders, var)) {
nesCfg.EnablePalBorders = (string(var.value) == "enabled");
}
// ===== AUDIO SETTINGS =====
// Fake Stereo
if(readVariable(MesenFakeStereo, var)) {
nesCfg.StereoFilter = (string(var.value) == "enabled") ? StereoFilterType::Delay : StereoFilterType::None;
}
// Reduce Triangle Popping
if(readVariable(MesenMuteTriangleUltrasonic, var)) {
nesCfg.SilenceTriangleHighFreq = (string(var.value) == "enabled");
}
// Reduce DMC Popping
if(readVariable(MesenReduceDmcPopping, var)) {
nesCfg.ReduceDmcPopping = (string(var.value) == "enabled");
}
// Swap Duty Cycles
if(readVariable(MesenSwapDutyCycle, var)) {
nesCfg.SwapDutyCycles = (string(var.value) == "enabled");
}
// Disable Noise Mode Flag
if(readVariable(MesenDisableNoiseModeFlag, var)) {
nesCfg.DisableNoiseModeFlag = (string(var.value) == "enabled");
}
// Audio Sample Rate
if(readVariable(MesenAudioSampleRate, var)) {
int old_value = audioCfg.SampleRate;
audioCfg.SampleRate = atoi(var.value);
audioCfg.SampleRate = (audioCfg.SampleRate > 96000) ? 96000 : audioCfg.SampleRate;
if(old_value != audioCfg.SampleRate) {
// If core is running, notify frontend of geometry change
if(_saveStateSize != -1) {
struct retro_system_av_info system_av_info;
retro_get_system_av_info(&system_av_info);
env_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &system_av_info);
}
}
}
// ===== INPUT SETTINGS =====
// Controller Turbo Speed
int turboSpeed = 1; // default to Normal
bool turboEnabled = true;
if(readVariable(MesenControllerTurboSpeed, var)) {
string value = string(var.value);
if(value == "Slow") {
turboSpeed = 0;
} else if(value == "Normal") {
turboSpeed = 1;
} else if(value == "Fast") {
turboSpeed = 2;
} else if(value == "Very Fast") {
turboSpeed = 3;
} else if(value == "Disabled") {
turboEnabled = false;
}
}
/*
// Shift Buttons Clockwise
_shiftButtonsClockwise = false;
if(readVariable(MesenShiftButtonsClockwise, var)) {
_shiftButtonsClockwise = (string(var.value) == "enabled");
}
*/
auto getKeyCode = [](int port, int retroKey) {
return (port << 8) | (retroKey + 1);
};
auto getKeyBindings = [=](int port) {
KeyMappingSet keyMappings;
keyMappings.TurboSpeed = turboSpeed;
// Default NES-style mapping with optional clockwise shift
keyMappings.Mapping1.A = getKeyCode(port, _shiftButtonsClockwise ? RETRO_DEVICE_ID_JOYPAD_B : RETRO_DEVICE_ID_JOYPAD_A);
keyMappings.Mapping1.B = getKeyCode(port, _shiftButtonsClockwise ? RETRO_DEVICE_ID_JOYPAD_Y : RETRO_DEVICE_ID_JOYPAD_B);
if(turboEnabled) {
keyMappings.Mapping1.TurboA = getKeyCode(port, _shiftButtonsClockwise ? RETRO_DEVICE_ID_JOYPAD_A : RETRO_DEVICE_ID_JOYPAD_X);
keyMappings.Mapping1.TurboB = getKeyCode(port, _shiftButtonsClockwise ? RETRO_DEVICE_ID_JOYPAD_X : RETRO_DEVICE_ID_JOYPAD_Y);
}
keyMappings.Mapping1.Start = getKeyCode(port, RETRO_DEVICE_ID_JOYPAD_START);
keyMappings.Mapping1.Select = getKeyCode(port, RETRO_DEVICE_ID_JOYPAD_SELECT);
keyMappings.Mapping1.Up = getKeyCode(port, RETRO_DEVICE_ID_JOYPAD_UP);
keyMappings.Mapping1.Down = getKeyCode(port, RETRO_DEVICE_ID_JOYPAD_DOWN);
keyMappings.Mapping1.Left = getKeyCode(port, RETRO_DEVICE_ID_JOYPAD_LEFT);
keyMappings.Mapping1.Right = getKeyCode(port, RETRO_DEVICE_ID_JOYPAD_RIGHT);
return keyMappings;
};
nesCfg.Port1.Keys = getKeyBindings(0);
nesCfg.Port2.Keys = getKeyBindings(1);
nesCfg.Port1SubPorts[0].Keys = getKeyBindings(0);
nesCfg.Port1SubPorts[1].Keys = getKeyBindings(1);
nesCfg.Port1SubPorts[2].Keys = getKeyBindings(2);
nesCfg.Port1SubPorts[3].Keys = getKeyBindings(3);
nesCfg.ExpPort.Keys = getKeyBindings(4);
// ===== ENHANCEMENTS =====
/*
// HD Packs
_hdPacksEnabled = true;
if(readVariable(MesenHdPacks, var)) {
_hdPacksEnabled = (string(var.value) != "disabled");
}
nesCfg.EnableHdPacks = _hdPacksEnabled;
*/
// HD packs disabled - not working properly
_hdPacksEnabled = false;
nesCfg.EnableHdPacks = _hdPacksEnabled;
// Sprite Limit (3-choice: normal, adaptive, off)
nesCfg.RemoveSpriteLimit = false;
nesCfg.AdaptiveSpriteLimit = false;
if(readVariable(MesenSpriteLimit, var)) {
string value = string(var.value);
if(value == "adaptive") {
nesCfg.AdaptiveSpriteLimit = true;
} else if(value == "off") {
nesCfg.RemoveSpriteLimit = true;
}
}
// Sprites Enabled
if(readVariable(MesenSpritesEnabled, var)) {
nesCfg.SpritesEnabled = (string(var.value) == "enabled");
}
// Background Enabled
if(readVariable(MesenBackgroundEnabled, var)) {
nesCfg.BackgroundEnabled = (string(var.value) == "enabled");
}
// Disable Game Genie Bus Conflicts
if(readVariable(MesenDisableGameGenieBusConflicts, var)) {
nesCfg.DisableGameGenieBusConflicts = (string(var.value) == "enabled");
}
// ===== SYSTEM OPTIONS =====
// Allow Invalid Input
if(readVariable(MesenAllowInvalidInput, var)) {
nesCfg.AllowInvalidInput = (string(var.value) == "enabled");
}
// Randomize Mapper Power-On State
if(readVariable(MesenRandomizeMapperPowerOnState, var)) {
nesCfg.RandomizeMapperPowerOnState = (string(var.value) == "enabled");
}
// Randomize CPU/PPU Alignment
if(readVariable(MesenRandomizeCpuPpuAlignment, var)) {
nesCfg.RandomizeCpuPpuAlignment = (string(var.value) == "enabled");
}
// ===== APPLY ALL SETTINGS =====
_emu->GetSettings()->SetNesConfig(nesCfg);
_emu->GetSettings()->SetVideoConfig(videoCfg);
_emu->GetSettings()->SetAudioConfig(audioCfg);
// Check if geometry-related settings changed
bool videoFilterChanged = (_lastVideoConfig.VideoFilter != videoCfg.VideoFilter) ||
(_lastVideoConfig.NtscScale != videoCfg.NtscScale) ||
(_lastVideoConfig.ScreenRotation != videoCfg.ScreenRotation);
bool overscanChanged = (nesCfg.NtscOverscan.Left != _lastNesConfig.NtscOverscan.Left) ||
(nesCfg.NtscOverscan.Right != _lastNesConfig.NtscOverscan.Right) ||
(nesCfg.NtscOverscan.Top != _lastNesConfig.NtscOverscan.Top) ||
(nesCfg.NtscOverscan.Bottom != _lastNesConfig.NtscOverscan.Bottom) ||
(nesCfg.PalOverscan.Left != _lastNesConfig.PalOverscan.Left) ||
(nesCfg.PalOverscan.Right != _lastNesConfig.PalOverscan.Right) ||
(nesCfg.PalOverscan.Top != _lastNesConfig.PalOverscan.Top) ||
(nesCfg.PalOverscan.Bottom != _lastNesConfig.PalOverscan.Bottom);
if(videoFilterChanged || overscanChanged) {
_geometryDirty = true;
if(_emu && _emu->GetVideoDecoder()) {
_emu->GetVideoDecoder()->ForceFilterUpdate();
}
}
// Save current settings for next comparison
_lastVideoConfig = videoCfg;
_lastNesConfig = nesCfg;
}
RETRO_API void retro_run()
{
// ForceMaxSpeed flag API changed / unavailable here — skip fast-forward behavior for now.
if(false) {
#if 0
//Skip frames to speed up emulation while still outputting at 50/60 fps (needed for FDS fast forward while loading)
_console->GetVideoRenderer()->SetSkipMode(true);
_console->GetSoundMixer()->SetSkipMode(true);
for(int i = 0; i < 9; i++) {
//Attempt to speed up to 1000% speed
_console->RunSingleFrame();
}
_console->GetVideoRenderer()->SetSkipMode(false);
_console->GetSoundMixer()->SetSkipMode(false);
#endif
}
bool updated = false;
if(env_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) {
update_settings();
// Settings API moved; read flag from Emulator settings if available, otherwise skip.
bool hdPacksEnabled = false;
if(hdPacksEnabled != _hdPacksEnabled) {
// Defer HD pack update to emulator API (call removed here) — just track the change
_hdPacksEnabled = hdPacksEnabled;
}
}
// Frame stepping: run a single emulated frame and submit video/audio to libretro callbacks.
if(_emu && _emu->GetConsole()) {
// Refresh input state before running the frame
auto consoleIf = _emu->GetConsole();
if(_keyManager) {
consoleIf->GetControlManager()->UpdateInputState();
}
consoleIf->RunFrame();
// Allow emulator to run pre-frame hooks (UI, stats, etc.)
_emu->OnBeforeSendFrame();
// Get the PPU frame (core provides a 16-bit PPU buffer containing
// palette indices/intensity bits). Use the emulator's video filter
// to convert that buffer to a 32-bit ARGB frame so color/palette
// handling (including emphasis, user palettes, and filters) is
// consistent with the rest of the emulator.
PpuFrameInfo frame = _emu->GetPpuFrame();
// Check that we have a valid buffer with meaningful size (each system allocates differently)
if(_videoRefresh && frame.FrameBuffer && frame.FrameBufferSize > 0) {
// Create a temporary video filter (same pattern used by LuaApi)
std::unique_ptr<BaseVideoFilter> filter(_emu->GetVideoFilter());
FrameInfo baseSize = { frame.Width, frame.Height };
filter->SetBaseFrameInfo(baseSize);
FrameInfo outInfo = filter->SendFrame((uint16_t*)frame.FrameBuffer, _emu->GetFrameCount(), _emu->GetFrameCount() & 0x01, nullptr, false);
uint32_t* src = filter->GetOutputBuffer();
// Ensure we have valid output before displaying
if(src) {
size_t pixels = (size_t)outInfo.Width * (size_t)outInfo.Height;
static std::vector<uint32_t> rotBuffer;
if(_screenRotation == 0) {
_videoRefresh(src, outInfo.Width, outInfo.Height, outInfo.Width * 4);
} else {
int outW = (_screenRotation == 180) ? outInfo.Width : outInfo.Height;
int outH = (_screenRotation == 180) ? outInfo.Height : outInfo.Width;
rotBuffer.resize((size_t)outW * (size_t)outH);
if(_screenRotation == 180) {
for(size_t i = 0; i < pixels; ++i) rotBuffer[pixels - 1 - i] = src[i];
_videoRefresh(rotBuffer.data(), outInfo.Width, outInfo.Height, outInfo.Width * 4);
} else if(_screenRotation == 90) {
for(int y = 0; y < outInfo.Height; ++y) {
for(int x = 0; x < outInfo.Width; ++x) {
size_t srcIdx = (size_t)y * outInfo.Width + x;
int dstX = outInfo.Height - 1 - y;
int dstY = x;
size_t dstIdx = (size_t)dstY * outW + (size_t)dstX;
rotBuffer[dstIdx] = src[srcIdx];
}
}
_videoRefresh(rotBuffer.data(), outW, outH, outW * 4);
} else if(_screenRotation == 270) {
for(int y = 0; y < outInfo.Height; ++y) {
for(int x = 0; x < outInfo.Width; ++x) {
size_t srcIdx = (size_t)y * outInfo.Width + x;
int dstX = y;
int dstY = outInfo.Width - 1 - x;
size_t dstIdx = (size_t)dstY * outW + (size_t)dstX;
rotBuffer[dstIdx] = src[srcIdx];
}
}
_videoRefresh(rotBuffer.data(), outW, outH, outW * 4);
} else {
// Unknown rotation: fallback to non-rotated output
_videoRefresh(src, outInfo.Width, outInfo.Height, outInfo.Width * 4);
}
}
}
}
}
if(updated) {
// Only update geometry if something changed or if flag is set
if(_geometryDirty) {
retro_system_av_info avInfo = {};
retro_get_system_av_info(&avInfo);
uint32_t newWidth = avInfo.geometry.base_width;
uint32_t newHeight = avInfo.geometry.base_height;
// Only call SET_GEOMETRY if dimensions actually changed
if(newWidth != _lastReportedWidth || newHeight != _lastReportedHeight) {
_lastReportedWidth = newWidth;
_lastReportedHeight = newHeight;
env_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &avInfo);
}
_geometryDirty = false;
}
}
// Audio upload handled by emulator/sound subsystem; no-op here for now.
}
RETRO_API size_t retro_serialize_size()
{
return _saveStateSize;
}
RETRO_API bool retro_serialize(void *data, size_t size)
{
if(!_emu) return false;
try {
std::stringstream ss;
_emu->Serialize(ss, true, 1);
std::string out = ss.str();
if(out.size() > size) return false;
memcpy(data, out.data(), out.size());
return true;
} catch(...) {
return false;
}
}
RETRO_API bool retro_unserialize(const void *data, size_t size)
{
if(!_emu) return false;
try {
std::string in((const char*)data, size);
std::stringstream ss(in);
auto res = _emu->Deserialize(ss, SaveStateManager::FileFormatVersion, true);
return (res == DeserializeResult::Success);
} catch(...) {
return false;
}
}
RETRO_API void retro_cheat_reset()
{
if(_emu) {
// Cheat manager API moved; no-op for now.
}
}
RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *codeStr)
{
static const string validGgLetters = "APZLGITYEOXUKSVN";
static const string validParLetters = "0123456789ABCDEF";
int chl = 0;
string code = codeStr;
std::transform(code.begin(), code.end(), code.begin(), ::toupper);
if(code[4] == ':') {
for(;;) {
string address = code.substr((0 + chl), 4);
string value = code.substr((5 + chl), 2);
// Cheat API moved — ignore for now
if(code[(7 + chl)] != '+') {
return;
}
chl = (chl + 8);
}
}
else if(code[4] == '?' && code[7] == ':') {
for(;;) {
string address = code.substr((0 + chl), 4);
string comparison = code.substr((5 + chl), 2);
string value = code.substr((8 + chl), 2);
// Cheat API moved; ignore custom cheat add here for now.
if(code[(10 + chl)] != '+') {
return;
}
chl = (chl + 11);
}
}
else {
//This is either a GG or PAR code
bool isValidGgCode = true;
bool isValidParCode = true;
for(size_t i = 0; i < 6; i++) {
if(validGgLetters.find(code[i]) == string::npos) {
isValidGgCode = false;
}
}
for(size_t i = 0; i < 8; i++) {
if(validParLetters.find(code[i]) == string::npos) {
isValidParCode = false;
}
}
if(isValidGgCode && code[6] == '+') {
for(;;) {
string code1 = code.substr((0 + chl), 6);
// Cheat API moved; ignore Game Genie add here for now.
if(code[(6 + chl)] != '+') {
return;
}
chl = (chl + 7);
}
}
else if(isValidGgCode && code[8] == '+') {
for(;;) {
string code1 = code.substr((0 + chl), 8);
// Cheat API moved; ignore Game Genie add here for now.
if(code[(8 + chl)] != '+') {
return;
}
chl = (chl + 9);
}
}
else if(isValidGgCode) {
// Cheat API moved; ignore Game Genie add here for now.
}
else if(isValidParCode && code[8] == '+') {
for(;;) {
string code1 = code.substr((0 + chl), 8);
// Cheat API moved; ignore Pro Action Rocky code add here for now.
if(code[(8 + chl)] != '+') {
return;
}
chl = (chl + 9);
}
}
else if(isValidParCode) {
// Cheat API moved; ignore Pro Action Rocky code add here for now.
}
}
}
void update_input_descriptors()
{
if(!_emu || !_emu->GetConsole()) return;
vector<retro_input_descriptor> desc;
auto addDesc = [&desc](unsigned port, unsigned button, const char* name) {
retro_input_descriptor d = { port, RETRO_DEVICE_JOYPAD, 0, button, name };
desc.push_back(d);
};
// Detect the current system type
ConsoleType consoleType = _emu->GetConsole()->GetConsoleType();
// Generic SNES button layout (works for SNES, GBA, etc.)
auto setupSnesButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "A");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "B");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_X, "X");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_Y, "Y");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_L, "L");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_R, "R");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Start");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select");
};
// Gameboy button layout
auto setupGameboyButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "A");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "B");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Start");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select");
};
// NES button layout (with turbo support)
auto setupNesButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "A");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "B");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_X, "Turbo A");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_Y, "Turbo B");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Start");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select");
if(port == 0) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_L, "(FDS) Insert Next Disk");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_R, "(FDS) Switch Disk Side");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_L2, "(VS) Insert Coin 1");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_R2, "(VS) Insert Coin 2");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_L3, "(Famicom) Microphone (P2)");
}
};
// SMS/GG button layout
auto setupSmsButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "Button 1");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "Button 2");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Start");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_SELECT, "Pause");
};
// PCE/TG16 button layout
auto setupPceButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "Button 1");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "Button 2");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_Y, "Button 3");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_X, "Button 4");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_L, "Button 5");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_R, "Button 6");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Run");
};
// Wonderswan button layout
auto setupWsButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "A");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "B");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Start");
};
// GBA button layout
auto setupGbaButtons = [&addDesc](int port) {
addDesc(port, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_A, "A");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_B, "B");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_L, "L");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_R, "R");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_START, "Start");
addDesc(port, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select");
};
// Set up descriptors based on system type
switch(consoleType) {
case ConsoleType::Snes:
setupSnesButtons(0);
setupSnesButtons(1);
setupSnesButtons(2);
setupSnesButtons(3);
break;
case ConsoleType::Gameboy:
setupGameboyButtons(0);
setupGameboyButtons(1);
break;
case ConsoleType::Nes:
setupNesButtons(0);
setupNesButtons(1);
break;
case ConsoleType::PcEngine:
setupPceButtons(0);
setupPceButtons(1);
setupPceButtons(2);
setupPceButtons(3);
setupPceButtons(4);
break;
case ConsoleType::Sms:
setupSmsButtons(0);
setupSmsButtons(1);
break;
case ConsoleType::Gba:
setupGbaButtons(0);
break;
case ConsoleType::Ws:
setupWsButtons(0);
break;
default:
setupNesButtons(0);
break;
}
retro_input_descriptor end = { 0 };
desc.push_back(end);
// Avoid sending identical input descriptors repeatedly (RetroArch logs each SET_INPUT_DESCRIPTORS call).
// Build a lightweight representation we can compare to the last one and skip the env_cb if unchanged.
struct SimpleDesc { unsigned port; unsigned device; unsigned index; unsigned id; std::string name; };
static std::vector<SimpleDesc> lastDesc;
std::vector<SimpleDesc> curDesc;
curDesc.reserve(desc.size());
for(size_t i = 0; i < desc.size(); ++i) {
const retro_input_descriptor &d = desc[i];
if(d.description == nullptr) break; // end sentinel
curDesc.push_back({ d.port, d.device, d.index, d.id, std::string(d.description) });
}
bool same = (curDesc.size() == lastDesc.size());
if(same) {
for(size_t i = 0; i < curDesc.size(); ++i) {
const SimpleDesc &a = curDesc[i];
const SimpleDesc &b = lastDesc[i];
if(a.port != b.port || a.device != b.device || a.index != b.index || a.id != b.id || a.name != b.name) { same = false; break; }
}
}
if(!same) {
env_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc.data());
lastDesc.swap(curDesc);
}
}
void update_core_controllers()
{
// Map libretro-selected devices into the emulator's settings.
// Guard against being called before a console/emulator exists.
if(!_emu || !_emu->GetConsole()) return;
// Ensure ports 0/1 default to gamepads when still "auto"
if(_inputDevices[0] == DEVICE_AUTO) _inputDevices[0] = DEVICE_GAMEPAD;
if(_inputDevices[1] == DEVICE_AUTO) _inputDevices[1] = DEVICE_GAMEPAD;
// make sure other ports have a sane default
for(int port = 2; port < 5; ++port) {
if(_inputDevices[port] == DEVICE_AUTO) _inputDevices[port] = RETRO_DEVICE_NONE;
}
ConsoleType consoleType = _emu->GetConsole()->GetConsoleType();
// Map port 0/1 controller types based on the system
switch(consoleType) {
case ConsoleType::Snes: {
SnesConfig snesCfg = _emu->GetSettings()->GetSnesConfig();
// Always set controller types based on frontend selection or default to gamepad
snesCfg.Port1.Type = (_inputDevices[0] == DEVICE_GAMEPAD) ? ControllerType::SnesController : ControllerType::None;
snesCfg.Port2.Type = (_inputDevices[1] == DEVICE_GAMEPAD) ? ControllerType::SnesController : ControllerType::None;
_emu->GetSettings()->SetSnesConfig(snesCfg);
break;
}
case ConsoleType::Gameboy: {
GameboyConfig gbCfg = _emu->GetSettings()->GetGameboyConfig();
gbCfg.Controller.Type = (_inputDevices[0] == DEVICE_GAMEPAD) ? ControllerType::GameboyController : ControllerType::None;
_emu->GetSettings()->SetGameboyConfig(gbCfg);
break;
}
case ConsoleType::Gba: {
GbaConfig gbaCfg = _emu->GetSettings()->GetGbaConfig();
gbaCfg.Controller.Type = (_inputDevices[0] == DEVICE_GAMEPAD) ? ControllerType::GbaController : ControllerType::None;
_emu->GetSettings()->SetGbaConfig(gbaCfg);
break;
}
case ConsoleType::PcEngine: {
PcEngineConfig pceCfg = _emu->GetSettings()->GetPcEngineConfig();
pceCfg.Port1.Type = (_inputDevices[0] == DEVICE_GAMEPAD) ? ControllerType::PceController : ControllerType::None;
// Note: PCE has only Port1 but we still support multi-port in libretro
_emu->GetSettings()->SetPcEngineConfig(pceCfg);
break;
}
case ConsoleType::Sms: {
SmsConfig smsCfg = _emu->GetSettings()->GetSmsConfig();
smsCfg.Port1.Type = (_inputDevices[0] == DEVICE_GAMEPAD) ? ControllerType::SmsController : ControllerType::None;
smsCfg.Port2.Type = (_inputDevices[1] == DEVICE_GAMEPAD) ? ControllerType::SmsController : ControllerType::None;
for(int i = 0; i < 4; i++) {
smsCfg.ChannelVolumes[i] = 100;
}
_emu->GetSettings()->SetSmsConfig(smsCfg);
break;
}
case ConsoleType::Ws: {
WsConfig wsCfg = _emu->GetSettings()->GetWsConfig();
wsCfg.ControllerHorizontal.Type = (_inputDevices[0] == DEVICE_GAMEPAD) ? ControllerType::WsController : ControllerType::None;
_emu->GetSettings()->SetWsConfig(wsCfg);
break;
}
case ConsoleType::Nes:
default: {
// Default to NES handling for backward compatibility
NesConfig nesCfg = _emu->GetSettings()->GetNesConfig();
for(int port = 0; port <= 1; ++port) {
ControllerType type = ControllerType::NesController;
switch(_inputDevices[port]) {
case RETRO_DEVICE_NONE: type = ControllerType::None; break;
case DEVICE_GAMEPAD: type = ControllerType::NesController; break;
case DEVICE_ZAPPER: type = ControllerType::NesZapper; break;
case DEVICE_POWERPAD: type = ControllerType::PowerPadSideA; break;
case DEVICE_ARKANOID: type = ControllerType::NesArkanoidController; break;
default: break;
}
if(port == 0) nesCfg.Port1.Type = type;
else if(port == 1) nesCfg.Port2.Type = type;
}
_emu->GetSettings()->SetNesConfig(nesCfg);
break;
}
}
}
void setup_default_key_mappings()
{
// Set up default libretro button mappings for all systems
// Keycode format: (port << 8) | (retroKey + 1)
// This enables input even when no custom key mappings have been configured
if(!_emu || !_emu->GetConsole()) return;
ConsoleType consoleType = _emu->GetConsole()->GetConsoleType();
// Helper lambda to set a KeyMapping button
auto setButton = [](uint16_t& dest, int port, int retroButton) {
dest = (port << 8) | (retroButton + 1);
};
// Helper lambda to set up a full KeyMappingSet with default buttons
auto setupDefaultMapping = [&](KeyMappingSet& keys, int port) {
// Only set up Mapping1 if it doesn't already have keys
if(!keys.Mapping1.HasKeySet()) {
// D-Pad
setButton(keys.Mapping1.Up, port, RETRO_DEVICE_ID_JOYPAD_UP);
setButton(keys.Mapping1.Down, port, RETRO_DEVICE_ID_JOYPAD_DOWN);
setButton(keys.Mapping1.Left, port, RETRO_DEVICE_ID_JOYPAD_LEFT);
setButton(keys.Mapping1.Right, port, RETRO_DEVICE_ID_JOYPAD_RIGHT);
// Action buttons
setButton(keys.Mapping1.A, port, RETRO_DEVICE_ID_JOYPAD_A);
setButton(keys.Mapping1.B, port, RETRO_DEVICE_ID_JOYPAD_B);
setButton(keys.Mapping1.Start, port, RETRO_DEVICE_ID_JOYPAD_START);
setButton(keys.Mapping1.Select, port, RETRO_DEVICE_ID_JOYPAD_SELECT);
// Additional buttons (X, Y, L, R for systems that have them)
setButton(keys.Mapping1.X, port, RETRO_DEVICE_ID_JOYPAD_X);
setButton(keys.Mapping1.Y, port, RETRO_DEVICE_ID_JOYPAD_Y);
setButton(keys.Mapping1.L, port, RETRO_DEVICE_ID_JOYPAD_L);
setButton(keys.Mapping1.R, port, RETRO_DEVICE_ID_JOYPAD_R);
}
};
switch(consoleType) {
case ConsoleType::Snes: {
SnesConfig snesCfg = _emu->GetSettings()->GetSnesConfig();
setupDefaultMapping(snesCfg.Port1.Keys, 0);
setupDefaultMapping(snesCfg.Port2.Keys, 1);
_emu->GetSettings()->SetSnesConfig(snesCfg);
break;
}
case ConsoleType::Gameboy: {
GameboyConfig gbCfg = _emu->GetSettings()->GetGameboyConfig();
setupDefaultMapping(gbCfg.Controller.Keys, 0);
_emu->GetSettings()->SetGameboyConfig(gbCfg);
break;
}
case ConsoleType::Gba: {
GbaConfig gbaCfg = _emu->GetSettings()->GetGbaConfig();
setupDefaultMapping(gbaCfg.Controller.Keys, 0);
_emu->GetSettings()->SetGbaConfig(gbaCfg);
break;
}
case ConsoleType::PcEngine: {
PcEngineConfig pceCfg = _emu->GetSettings()->GetPcEngineConfig();
setupDefaultMapping(pceCfg.Port1.Keys, 0);
setupDefaultMapping(pceCfg.Port1.Keys, 1); // Note: PcEngine only has Port1, treating as multi-port
_emu->GetSettings()->SetPcEngineConfig(pceCfg);
break;
}
case ConsoleType::Sms: {
SmsConfig smsCfg = _emu->GetSettings()->GetSmsConfig();
setupDefaultMapping(smsCfg.Port1.Keys, 0);
setupDefaultMapping(smsCfg.Port2.Keys, 1);
_emu->GetSettings()->SetSmsConfig(smsCfg);
break;
}
case ConsoleType::Ws: {
WsConfig wsCfg = _emu->GetSettings()->GetWsConfig();
setupDefaultMapping(wsCfg.ControllerHorizontal.Keys, 0);
_emu->GetSettings()->SetWsConfig(wsCfg);
break;
}
case ConsoleType::Nes:
default: {
// For NES, set up default keys if not already configured
NesConfig nesCfg = _emu->GetSettings()->GetNesConfig();
setupDefaultMapping(nesCfg.Port1.Keys, 0);
setupDefaultMapping(nesCfg.Port2.Keys, 1);
_emu->GetSettings()->SetNesConfig(nesCfg);
break;
}
}
}
void retro_set_memory_maps()
{
// The console/Memory APIs were refactored. Provide an empty memory map for now.
retro_memory_map memoryMap = {};
memoryMap.descriptors = nullptr;
memoryMap.num_descriptors = 0;
env_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &memoryMap);
/* //Expose internal RAM and work/save RAM for retroachievements
retro_memory_descriptor descriptors[256] = {};
retro_memory_map memoryMap = {};
int count = 0;
for(int i = 0; i <= 0xFFFF; i += 0x100) {
uint8_t* ram = _console->GetRamBuffer(i);
if(ram) {
descriptors[count].ptr = ram;
descriptors[count].start = i;
descriptors[count].len = 0x100;
count++;
}
}
memoryMap.descriptors = descriptors;
memoryMap.num_descriptors = count;
env_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &memoryMap);
*/ }
RETRO_API void retro_set_controller_port_device(unsigned port, unsigned device)
{
if(port < 5 && _inputDevices[port] != device) {
_inputDevices[port] = device;
update_core_controllers();
update_input_descriptors();
}
}
RETRO_API bool retro_load_game(const struct retro_game_info *game)
{
logSgbDebugf("retro_load_game called: path=%s", game && game->path ? game->path : "(null)");
char *saveFolder;
char *systemFolder;
if(!env_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &systemFolder) || !systemFolder)
return false;
if(!env_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &saveFolder)) {
logMessage(RETRO_LOG_ERROR, "Could not find save directory.\n");
}
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
if(!env_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) {
logMessage(RETRO_LOG_ERROR, "XRGB8888 is not supported.\n");
return false;
}
//Expect the following structure:
// /system/disksys.rom
// /system/HdPacks/*
// /system/<BIOS files>
// /saves/*.sav
FolderUtilities::SetHomeFolder(systemFolder);
// SetFolderOverrides signature expects 4 strings (save, savestate, screenshot, firmware).
// Point firmware lookups at the system directory root.
FolderUtilities::SetFolderOverrides(saveFolder, std::string(""), std::string(""), string(systemFolder));
update_settings();
// Controller/Settings API changed; skip initial controller setup here
//Plug in 2 standard controllers by default, game database will switch the controller types for recognized games
/* _console->GetSettings()->SetMasterVolume(10.0);
_console->GetSettings()->SetControllerType(0, ControllerType::StandardController);
_console->GetSettings()->SetControllerType(1, ControllerType::StandardController);
_console->GetSettings()->SetControllerType(2, ControllerType::None);
_console->GetSettings()->SetControllerType(3, ControllerType::None);
*/
// Attempt to fetch extended game info
const struct retro_game_info_ext *gameExt = NULL;
const void *gameData = NULL;
size_t gameSize = 0;
string gamePath("");
if (env_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &gameExt)) {
gameData = gameExt->data;
gameSize = gameExt->size;
if (gameExt->file_in_archive) {
// We don't have a 'physical' file in this
// case, but the core still needs a filename
// in order to detect associated content
// (i.e. HdPacks). We therefore fake it, using
// the content directory, canonical content
// name, and content file extension
#if defined(_WIN32)
char slash = '\\';
#else
char slash = '/';
#endif
gamePath = string(gameExt->dir) +
string(1, slash) +
string(gameExt->name) +
"." +
string(gameExt->ext);
} else {
gamePath = gameExt->full_path;
}
} else {
// No extended game info; all we have is the
// content fullpath from the retro_game_info
// struct
gamePath = game->path;
}
/* // Load content
VirtualFile romData(gameData, gameSize, gamePath);
bool result = _console->Initialize(romData);
if(result) {
//Set default dipswitches for some VS System games
switch(_console->GetRomInfo().Hash.PrgCrc32) {
case 0x8850924B: _console->GetSettings()->SetDipSwitches(32); break; //VS Tetris
case 0xE1AA8214: _console->GetSettings()->SetDipSwitches(32); break; //StarLuster
default: _console->GetSettings()->SetDipSwitches(0); break;
}
update_core_controllers();
update_input_descriptors();
//Savestates in Mesen may change size over time
//Retroarch doesn't like this for netplay or rewinding - it requires the states to always be the exact same size
//So we need to send a large enough size to Retroarch to ensure Mesen's state will always fit within that buffer.
std::stringstream ss;
_console->GetSaveStateManager()->SaveState(ss);
//Round up to the next 1kb multiple
_saveStateSize = ((ss.str().size() * 2) + 0x400) & ~0x3FF;
retro_set_memory_maps();
}
return result;
*/
// Build a VirtualFile for the ROM (either from memory or from a path)
VirtualFile romData;
if(gameData && gameSize > 0) {
romData = VirtualFile(gameData, gameSize, gamePath);
} else {
romData = VirtualFile(gamePath);
}
// Set HD pack configuration BEFORE loading ROM (LoadHdPack checks this during ROM load)
{
NesConfig nesCfg = _emu->GetSettings()->GetNesConfig();
nesCfg.EnableHdPacks = _hdPacksEnabled;
_emu->GetSettings()->SetNesConfig(nesCfg);
}
// Attempt to load the ROM via the Emulator API
bool result = false;
string tempPceFirmwarePath;
string tempPceFirmwareBackup;
if(romData.GetFileExtension() == ".cue") {
char *systemFolder;
if(env_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &systemFolder) && systemFolder) {
if(CreatePceFirmwareStub(string(systemFolder), tempPceFirmwarePath, tempPceFirmwareBackup)) {
fprintf(stderr, "[libretro] Created temporary PCE firmware stub: %s\n", tempPceFirmwarePath.c_str());
fflush(stderr);
}
}
}
try {
// Do not instruct the emulator to stop any existing ROM here - letting it avoid
// the Stop() path which can trigger complex shutdown behavior inside a libretro host.
fprintf(stderr, "[libretro] Attempting to load ROM: %s\n", gamePath.c_str());
fprintf(stderr, "[libretro] Path: %s\n", romData.GetFilePath().c_str());
fprintf(stderr, "[libretro] Folder: %s\n", romData.GetFolderPath().c_str());
fprintf(stderr, "[libretro] Extension: %s\n", romData.GetFileExtension().c_str());
fprintf(stderr, "[libretro] From memory: %s\n", (gameData && gameSize > 0) ? "yes" : "no");
fflush(stderr);
result = _emu->LoadRom(romData, VirtualFile(), false, false);
fprintf(stderr, "[libretro] LoadRom returned: %s\n", result ? "success" : "failure");
fflush(stderr);
} catch(const std::exception& e) {
fprintf(stderr, "[libretro] LoadRom threw exception: %s\n", e.what());
fflush(stderr);
result = false;
} catch(...) {
fprintf(stderr, "[libretro] LoadRom threw unknown exception\n");
fflush(stderr);
result = false;
}
if(!tempPceFirmwarePath.empty()) {
RestorePceFirmwareStub(tempPceFirmwarePath, tempPceFirmwareBackup);
fprintf(stderr, "[libretro] Restored temporary PCE firmware stub: %s\n", tempPceFirmwarePath.c_str());
fflush(stderr);
}
if(result) {
// Update the console shared_ptr now that a ROM is loaded
// Use the generic IConsole interface to support all system types
auto consoleIf = _emu->GetConsole();
_console = consoleIf;
// Disable frame blending for libretro - we want raw frames
// Post-processing should happen in the frontend
ConsoleType type = _console->GetConsoleType();
switch(type) {
case ConsoleType::Snes: {
SnesConfig cfg = _emu->GetSettings()->GetSnesConfig();
cfg.HighResBlendMode = SnesHighResBlendMode::None;
_emu->GetSettings()->SetSnesConfig(cfg);
break;
}
case ConsoleType::Gameboy: {
GameboyConfig cfg = _emu->GetSettings()->GetGameboyConfig();
cfg.BlendFrames = false;
_emu->GetSettings()->SetGameboyConfig(cfg);
break;
}
case ConsoleType::Gba: {
GbaConfig cfg = _emu->GetSettings()->GetGbaConfig();
cfg.BlendFrames = false;
_emu->GetSettings()->SetGbaConfig(cfg);
break;
}
case ConsoleType::Sms: {
SmsConfig cfg = _emu->GetSettings()->GetSmsConfig();
cfg.GgBlendFrames = false;
_emu->GetSettings()->SetSmsConfig(cfg);
break;
}
case ConsoleType::PcEngine: {
// Initialize PCE config with default palette (RGB333)
// PCE uses a 9-bit color space (RGB333 - 3 bits each for R, G, B)
PcEngineConfig cfg = _emu->GetSettings()->GetPcEngineConfig();
// Generate default RGB333 palette if not already set
if(cfg.Palette[0] == 0) { // If palette is all zeros, initialize it
for(int rgb333 = 0; rgb333 < 512; rgb333++) {
// Extract 3-bit R, G, B components and scale to 8-bit
// Values for red and green channels are swapped, but it
// works, so whatever. TODO/FIXME
uint8_t r = ((rgb333 >> 3) & 0x7) * 255 / 7; // bits 8,7,6
uint8_t g = ((rgb333 >> 6) & 0x7) * 255 / 7; // bits 5,4,3
uint8_t b = ((rgb333 >> 0) & 0x7) * 255 / 7; // bits 2,1,0
cfg.Palette[rgb333] = 0xFF000000 | (r << 16) | (g << 8) | b;
}
}
_emu->GetSettings()->SetPcEngineConfig(cfg);
break;
}
default:
break;
}
// Inform the LibretroKeyManager of the concrete console/emulator so it can
// safely mark itself ready to be polled and access emulator data (mouse pos, etc.).
if(_keyManager) {
_keyManager->SetConsole(consoleIf, _emu.get());
}
// Initialize audio settings to enable sound output
// Ensure audio is enabled and channel volumes are set
{
AudioConfig audioCfg = _emu->GetSettings()->GetAudioConfig();
audioCfg.EnableAudio = true;
audioCfg.MasterVolume = 100;
_emu->GetSettings()->SetAudioConfig(audioCfg);
NesConfig nesCfg = _emu->GetSettings()->GetNesConfig();
// Initialize all channel volumes to 100 (full volume) if they're zero
for(size_t i = 0; i < 11; ++i) {
if(nesCfg.ChannelVolumes[i] == 0) {
nesCfg.ChannelVolumes[i] = 100;
}
// Initialize panning to center (50) if zero
if(nesCfg.ChannelPanning[i] == 0) {
nesCfg.ChannelPanning[i] = 50;
}
}
_emu->GetSettings()->SetNesConfig(nesCfg);
}
// Allow the game/db-specific controller initialization to run
update_core_controllers();
// Set up default libretro button mappings for systems that don't have custom key configs
setup_default_key_mappings();
// Re-run controller initialization with the new key mappings
update_core_controllers();
update_input_descriptors();
// Compute a safe save state size to report to the frontend
std::stringstream ss;
_emu->GetSaveStateManager()->SaveState(ss);
_saveStateSize = ((ss.str().size() * 2) + 0x400) & ~0x3FF;
// Update memory maps (still a stubbed implementation for now)
retro_set_memory_maps();
// Register the libretro audio device now that the emulator is initialized and consoles are ready
if(_audioDevice && _emu && _emu->GetSoundMixer()) {
_emu->GetSoundMixer()->RegisterAudioDevice(_audioDevice.get());
}
// Update geometry if HD packs are loaded (they may change the resolution)
// HD packs are NES-specific, so only check for NES consoles
if(_console && _console->GetConsoleType() == ConsoleType::Nes) {
auto nesConsole = std::dynamic_pointer_cast<NesConsole>(_console);
if(nesConsole && nesConsole->GetHdData()) {
retro_system_av_info avInfo = {};
retro_get_system_av_info(&avInfo);
env_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &avInfo);
}
}
// Forward any saved input callbacks into the key manager now that console/key manager are initialized.
if(_keyManager) {
if(_savedGetInputState) _keyManager->SetGetInputState(_savedGetInputState);
if(_savedPollInput) _keyManager->SetPollInput(_savedPollInput);
// Re-check bitmask support
if (env_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
_keyManager->SetSupportsInputBitmasks(true);
}
// Diagnostic probe: call saved callbacks once from this thread and log their values.
// This uses the shared probe helper so we don't duplicate the implementation.
if(_savedPollInput || _savedGetInputState) {
// forward-declare the shared probe helper (defined later in this file)
if(getenv("MESEN_LIBRETRO_VERBOSE_INPUT")) {
void libretro_probe_inputs(const char* tag);
libretro_probe_inputs("retro_load_game");
}
}
} else {
logMessage(RETRO_LOG_ERROR, "retro_load_game: Failed to load ROM via Emulator::LoadRom.\n");
_saveStateSize = 0;
}
return result;
}
static constexpr unsigned SubsystemId_Sgb = 2;
static bool isSupportedSubsystem(unsigned game_type)
{
return game_type == SubsystemId_Sgb; // Super Game Boy subsystem
}
static bool isGameboyRom(const struct retro_game_info* info)
{
if(!info) {
return false;
}
if(info->path) {
string path = info->path;
transform(path.begin(), path.end(), path.begin(), ::tolower);
if(path.find(".gb") != string::npos || path.find(".gbc") != string::npos || path.find(".gbx") != string::npos) {
return true;
}
}
return false;
}
static bool isSgbFirmwareRom(const struct retro_game_info* info, bool& useSgb2)
{
if(!info) {
return false;
}
logSgbDebugf("isSgbFirmwareRom: info=%p data=%p size=%zu path=%s", (const void*)info, info->data, info->size, info->path ? info->path : "(null)");
auto checkSize = [&](size_t size) {
if(size == 0x40000) {
useSgb2 = false;
return true;
}
if(size == 0x80000) {
useSgb2 = true;
return true;
}
return false;
};
if(info->data && info->size > 0) {
return checkSize(info->size);
}
if(info->path) {
string path = info->path;
transform(path.begin(), path.end(), path.begin(), ::tolower);
if(path.find(".sfc") != string::npos || path.find(".smc") != string::npos) {
if(info->size > 0) {
return checkSize(info->size);
}
VirtualFile firmwareFile(info->path);
if(firmwareFile.IsValid()) {
return checkSize(firmwareFile.GetSize());
}
}
}
return false;
}
static bool writeGameInfoToFile(const struct retro_game_info* info, const string& outputPath)
{
if(!info || outputPath.empty()) {
return false;
}
vector<uint8_t> sourceData;
if(info->data && info->size > 0) {
sourceData.assign((const uint8_t*)info->data, (const uint8_t*)info->data + info->size);
} else if(info->path) {
VirtualFile sourceFile(info->path);
if(!sourceFile.IsValid()) {
return false;
}
if(!sourceFile.ReadFile(sourceData)) {
return false;
}
} else {
return false;
}
utf8::ofstream output(outputPath, std::ios::binary | std::ios::trunc);
if(!output) {
return false;
}
output.write((const char*)sourceData.data(), sourceData.size());
return output.good();
}
static bool copySgbFirmwareToSystem(const struct retro_game_info* info, string& createdFirmwarePath, string& backupFirmwarePath)
{
if(!info) {
return false;
}
bool useSgb2 = false;
if(!isSgbFirmwareRom(info, useSgb2)) {
logSgbDebugf("copySgbFirmwareToSystem: info is not recognized as SGB firmware");
return false;
}
string firmwareFilename = useSgb2 ? "SGB2.sfc" : "SGB1.sfc";
char *systemFolder;
if(!env_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &systemFolder) || !systemFolder) {
logSgbDebugf("copySgbFirmwareToSystem: RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY failed");
return false;
}
string firmwarePath = FolderUtilities::CombinePath(string(systemFolder), firmwareFilename);
logSgbDebugf("copySgbFirmwareToSystem: system folder=%s firmware=%s", systemFolder, firmwarePath.c_str());
createdFirmwarePath.clear();
backupFirmwarePath.clear();
{
utf8::ifstream existing(firmwarePath, std::ios::binary);
if(existing.good()) {
backupFirmwarePath = firmwarePath + ".libretro.bak";
int backupIndex = 1;
while(utf8::ifstream(backupFirmwarePath, std::ios::binary).good()) {
backupFirmwarePath = firmwarePath + ".libretro.bak" + std::to_string(backupIndex);
backupIndex++;
}
if(!VfsIo::Rename(firmwarePath, backupFirmwarePath)) {
return false;
}
}
}
if(!writeGameInfoToFile(info, firmwarePath)) {
logSgbDebugf("copySgbFirmwareToSystem: failed to write custom firmware to %s", firmwarePath.c_str());
if(!backupFirmwarePath.empty()) {
VfsIo::Rename(backupFirmwarePath, firmwarePath);
}
return false;
}
createdFirmwarePath = firmwarePath;
logSgbDebugf("copySgbFirmwareToSystem: wrote custom firmware to %s", firmwarePath.c_str());
return true;
}
static void restoreSgbFirmware(const string& firmwarePath, const string& backupFirmwarePath)
{
if(firmwarePath.empty()) {
return;
}
if(!backupFirmwarePath.empty()) {
VfsIo::Rename(backupFirmwarePath, firmwarePath);
} else {
VfsIo::Remove(firmwarePath);
}
}
static bool loadSubsystemGame(unsigned game_type, const struct retro_game_info *info, size_t num_info)
{
logSgbDebugf("loadSubsystemGame called: game_type=%u num_info=%zu", game_type, num_info);
if(!isSupportedSubsystem(game_type) || !info || num_info == 0) {
logSgbDebugf("loadSubsystemGame: unsupported subsystem or invalid info");
return false;
}
if(game_type == SubsystemId_Sgb) {
logSgbDebugf("retro_load_game_special: starting SGB subsystem load");
logSgbDebugf("loadSubsystemGame: game_type=%u num_info=%zu", game_type, num_info);
if(info[0].path) logSgbDebugf(" gbInfo.path=%s size=%zu", info[0].path, info[0].size);
if(num_info > 1 && info[1].path) logSgbDebugf(" sgbInfo.path=%s size=%zu", info[1].path, info[1].size);
const struct retro_game_info* gbInfo = &info[0];
const struct retro_game_info* sgbInfo = (num_info > 1 ? &info[1] : nullptr);
if(!gbInfo || !isGameboyRom(gbInfo)) {
logMessage(RETRO_LOG_ERROR, "retro_load_game_special: Super Game Boy load requires a Game Boy ROM as the first entry.");
return false;
}
string firmwarePath;
string backupPath;
bool customFirmwareInstalled = false;
if(sgbInfo) {
bool useSgb2 = false;
if(isSgbFirmwareRom(sgbInfo, useSgb2)) {
customFirmwareInstalled = copySgbFirmwareToSystem(sgbInfo, firmwarePath, backupPath);
if(customFirmwareInstalled) {
logSgbDebugf("Installed custom SGB firmware: %s", firmwarePath.c_str());
} else {
logSgbDebugf("copySgbFirmwareToSystem returned false for firmware path %s", sgbInfo->path ? sgbInfo->path : "(null)");
}
} else {
logMessage(RETRO_LOG_INFO, "retro_load_game_special: Second entry is not valid SGB firmware; falling back to existing system firmware if available.");
}
}
// Tell the emulator to allow SGB for the next Gameboy console that is created
if(_emu) _emu->SetAllowSgbForNextLoad(true);
// Temporarily prefer SGB model when selecting the Gameboy model so SGB will be chosen
GameboyConfig _libretro_prevGbCfg;
bool _libretro_havePrevGbCfg = false;
if(_emu) {
_libretro_prevGbCfg = _emu->GetSettings()->GetGameboyConfig();
_libretro_havePrevGbCfg = true;
GameboyConfig tmpGbCfg = _libretro_prevGbCfg;
tmpGbCfg.Model = GameboyModel::AutoFavorSgb;
_emu->GetSettings()->SetGameboyConfig(tmpGbCfg);
}
bool result = retro_load_game(gbInfo);
logSgbDebugf("retro_load_game returned %s for GB ROM %s", result ? "success" : "failure", gbInfo->path ? gbInfo->path : "(null)");
// Restore previous Gameboy config
if(_emu && _libretro_havePrevGbCfg) {
_emu->GetSettings()->SetGameboyConfig(_libretro_prevGbCfg);
}
if(customFirmwareInstalled) {
restoreSgbFirmware(firmwarePath, backupPath);
logSgbDebugf("Restored SGB firmware %s", firmwarePath.c_str());
}
return result;
}
return false;
}
RETRO_API bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info)
{
return loadSubsystemGame(game_type, info, num_info);
}
RETRO_API void retro_unload_game()
{
}
RETRO_API unsigned retro_get_region()
{
/* NesModel model = _console->GetModel();
return model == NesModel::NTSC ? RETRO_REGION_NTSC : RETRO_REGION_PAL;
*/
// The NesConsole / Emulator APIs were refactored. Return NTSC for now.
return RETRO_REGION_NTSC;
}
RETRO_API void retro_get_system_info(struct retro_system_info *info)
{
// _mesenVersion = EmuSettings::GetVersionString();
/*
// Set library name based on loaded console. This pushes configs into
// separate folders for each sub-core
static std::string libraryName = "Mesen2";
if(_console) {
ConsoleType type = _console->GetConsoleType();
switch(type) {
case ConsoleType::Nes: libraryName = "Mesen2-NES"; break;
case ConsoleType::Snes: libraryName = "Mesen2-SNES"; break;
case ConsoleType::Gameboy: libraryName = "Mesen2-Gameboy"; break;
case ConsoleType::Gba: libraryName = "Mesen2-GBA"; break;
case ConsoleType::PcEngine: libraryName = "Mesen2-PCEngine"; break;
case ConsoleType::Sms: libraryName = "Mesen2-SMS"; break;
case ConsoleType::Ws: libraryName = "Mesen2-Wonderswan"; break;
default: libraryName = "Mesen2"; break;
}
}
*/
info->library_name = "Mesen2";
info->library_version = "2.0.0";
info->need_fullpath = true;
// Set valid extensions based on loaded console
static std::string validExtensions;
if(_console) {
ConsoleType type = _console->GetConsoleType();
switch(type) {
case ConsoleType::Nes:
validExtensions = "nes|fds|unf|unif";
break;
case ConsoleType::Snes:
validExtensions = "sfc|smc";
break;
case ConsoleType::Gameboy:
validExtensions = "gb|gbc";
break;
case ConsoleType::Gba:
validExtensions = "gba";
break;
case ConsoleType::PcEngine:
validExtensions = "pce|sgx|cue";
break;
case ConsoleType::Sms:
validExtensions = "sms|gg";
break;
case ConsoleType::Ws:
validExtensions = "ws|wsc";
break;
default:
validExtensions = "nes|fds|unf|unif|sfc|smc|gb|gbc|gba|pce|sgx|sms|gg|ws|wsc";
break;
}
} else {
// No console loaded yet, use all supported extensions
validExtensions = "nes|fds|unf|unif|sfc|smc|gb|gbc|gba|pce|sgx|cue|sms|gg|ws|wsc";
}
info->valid_extensions = validExtensions.c_str();
info->block_extract = false;
}
RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info)
{
memset(info, 0, sizeof(*info));
uint32_t width = 256;
uint32_t height = 240;
// Get actual filtered frame size if available
if(_emu && _emu->GetVideoDecoder()) {
FrameInfo frameSize = _emu->GetVideoDecoder()->GetFrameInfo();
width = frameSize.Width;
height = frameSize.Height;
}
// Check if HD packs are loaded and apply additional scaling
// HD packs are NES-specific
uint32_t hscale = 1;
uint32_t vscale = 1;
if(_console && _console->GetConsoleType() == ConsoleType::Nes) {
auto nesConsole = std::dynamic_pointer_cast<NesConsole>(_console);
if(nesConsole) {
auto hdData = nesConsole->GetHdData().lock();
if(hdData) {
hscale = hdData->Scale;
vscale = hdData->Scale;
}
}
}
// Apply HD scale to final dimensions
width *= hscale;
height *= vscale;
info->geometry.base_width = width;
info->geometry.base_height = height;
// TODO/FIXME: set these to something bigger when HD packs are implemented
info->geometry.max_width = 512;//256 * 8 * 4; // generous max (8x scale + 4x HD)
info->geometry.max_height = 480;//240 * 8 * 4;
float system_ar;
if(_console) {
ConsoleType type = _console->GetConsoleType();
switch(type) {
case ConsoleType::Nes: system_ar = 1.306; break;
case ConsoleType::Snes: system_ar = 64.0f / 49.0f; break;
case ConsoleType::Gameboy: system_ar = 10.0f / 9.0f; break;
case ConsoleType::Gba: system_ar = 3.0f / 2.0f; break;
case ConsoleType::PcEngine: system_ar = 4.0f / 3.0f; break;
case ConsoleType::Sms: system_ar = 4.0f / 3.0f; break;
case ConsoleType::Ws: system_ar = 14.0f / 9.0f; break;
default: system_ar = 4.0f / 3.0f; break;
}
}
info->geometry.aspect_ratio = system_ar;
// Get FPS from console, or use default
double fps = 60.0988; // NES NTSC default
if(_console) {
fps = _console->GetFps();
}
info->timing.fps = fps;
info->timing.sample_rate = 44100.0;
}
RETRO_API void *retro_get_memory_data(unsigned id)
{
// Memory access is NES-specific for now
if(!_console || _console->GetConsoleType() != ConsoleType::Nes) {
return nullptr;
}
auto nesConsole = std::dynamic_pointer_cast<NesConsole>(_console);
if(!nesConsole) return nullptr;
BaseMapper* mapper = nesConsole->GetMapper();
switch(id) {
case RETRO_MEMORY_SAVE_RAM:
// Save-ram accessor moved/hidden in BaseMapper; return nullptr until mapped to new API.
(void)mapper;
return nullptr;
case RETRO_MEMORY_SYSTEM_RAM:
// Use the renamed GetInternalRam() if available on NesMemoryManager.
if(nesConsole->GetMemoryManager()) {
return nesConsole->GetMemoryManager()->GetInternalRam();
}
return nullptr;
}
return nullptr;
}
RETRO_API size_t retro_get_memory_size(unsigned id)
{
switch(id) {
case RETRO_MEMORY_SAVE_RAM: //return mapper->GetMemorySize(DebugMemoryType::SaveRam);
// Mapper API changed; save-RAM access moved. Return 0 for now.
// TODO: replace with mapper->GetSaveRam() equivalent when API is available.
return 0;
case RETRO_MEMORY_SYSTEM_RAM: //return MemoryManager::InternalRAMSize;
// Memory manager API renamed to GetInternalRam()
// Use the new method if available; otherwise return 0.
return 0;
}
return 0;
}
}