#include #include #include #include #include #include #include #include #include #include #if __has_include() #include namespace fs = std::filesystem; #define HAS_STD_FILESYSTEM 1 #elif __has_include() #include 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 _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 _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 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 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 _keyManager; static std::unique_ptr _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 _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 _monoBuffer; }; static std::unique_ptr _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 _console = std::dynamic_pointer_cast(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(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 for thread-safe registration KeyManager::RegisterKeyManager(std::shared_ptr(_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()); _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 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 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 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 lastDesc; std::vector 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/ // /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(_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 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(_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(_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; } }