#include <cassert> #ifndef _WIN32 #include <dlfcn.h> #endif #include <fstream> #include <map> #include <sstream> #include <unordered_map> #include <vector> #include "coreinfo.h" #include "data.h" #include "emulator.h" #include "libretro.h" #ifndef _WIN32 #define GETSYM dlsym #else #define GETSYM GetProcAddress #endif using namespace std; namespace Retro { static Emulator* s_loadedEmulator = nullptr; static map<string, const char*> s_envVariables = { { "genesis_plus_gx_bram", "per game" }, { "genesis_plus_gx_render", "single field" }, { "genesis_plus_gx_blargg_ntsc_filter", "disabled" } }; static void (*retro_init)(void); static void (*retro_deinit)(void); static unsigned (*retro_api_version)(void); static void (*retro_get_system_info)(struct retro_system_info* info); static void (*retro_get_system_av_info)(struct retro_system_av_info* info); static void (*retro_reset)(void); static void (*retro_run)(void); static size_t (*retro_serialize_size)(void); static bool (*retro_serialize)(void* data, size_t size); static bool (*retro_unserialize)(const void* data, size_t size); static bool (*retro_load_game)(const struct retro_game_info* game); static void (*retro_unload_game)(void); static void* (*retro_get_memory_data)(unsigned id); static size_t (*retro_get_memory_size)(unsigned id); static void (*retro_cheat_reset)(void); static void (*retro_cheat_set)(unsigned index, bool enabled, const char* code); static void (*retro_set_environment)(retro_environment_t); static void (*retro_set_video_refresh)(retro_video_refresh_t); static void (*retro_set_audio_sample)(retro_audio_sample_t); static void (*retro_set_audio_sample_batch)(retro_audio_sample_batch_t); static void (*retro_set_input_poll)(retro_input_poll_t); static void (*retro_set_input_state)(retro_input_state_t); Emulator::Emulator() { } Emulator::~Emulator() { if (m_romLoaded) { unloadRom(); } if (m_coreHandle) { unloadCore(); } } bool Emulator::isLoaded() { return s_loadedEmulator; } bool Emulator::loadRom(const string& romPath) { if (m_romLoaded) { unloadRom(); } auto core = coreForRom(romPath); if (core.size() == 0) { return false; } if (m_coreHandle && m_core != core) { unloadCore(); } if (!m_coreHandle) { string lib = libForCore(core) + "_libretro."; #ifdef __APPLE__ lib += "dylib"; #elif defined(_WIN32) lib += "dll"; #else lib += "so"; #endif if (!loadCore(corePath() + "/" + lib)) { return false; } m_core = core; } retro_game_info gameInfo; ifstream in(romPath, ios::binary | ios::ate); if (in.fail()) { return false; } ostringstream out; gameInfo.size = in.tellg(); if (in.fail()) { return false; } char* romData = new char[gameInfo.size]; gameInfo.path = romPath.c_str(); gameInfo.data = romData; in.seekg(0, ios::beg); in.read(romData, gameInfo.size); if (in.fail()) { delete[] romData; return false; } in.close(); auto res = retro_load_game(&gameInfo); delete[] romData; if (!res) { return false; } retro_get_system_av_info(&m_avInfo); fixScreenSize(romPath); m_romLoaded = true; m_romPath = romPath; return true; } void Emulator::run() { assert(s_loadedEmulator == this); m_audioData.clear(); retro_run(); } void Emulator::reset() { assert(s_loadedEmulator == this); retro_system_info systemInfo; retro_get_system_info(&systemInfo); if (!strcmp(systemInfo.library_name, "Stella")) { // Stella does not properly clear everything when reseting or loading a savestate memset(m_buttonMask, 0, sizeof(m_buttonMask)); string romPath = m_romPath; #ifdef _WIN32 FreeLibrary(m_coreHandle); #else dlclose(m_coreHandle); #endif m_coreHandle = nullptr; s_loadedEmulator = nullptr; m_romLoaded = false; loadRom(m_romPath); if (m_addressSpace) { m_addressSpace->reset(); m_addressSpace->addBlock(Retro::ramBase(m_core), retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM), retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM)); } } retro_reset(); } void Emulator::unloadCore() { if (!m_coreHandle) { return; } if (m_romLoaded) { unloadRom(); } retro_deinit(); #ifdef _WIN32 FreeLibrary(m_coreHandle); #else dlclose(m_coreHandle); #endif m_coreHandle = nullptr; s_loadedEmulator = nullptr; } void Emulator::unloadRom() { if (!m_romLoaded) { return; } retro_unload_game(); m_romLoaded = false; m_romPath.clear(); m_addressSpace = nullptr; m_map.clear(); } bool Emulator::serialize(void* data, size_t size) { assert(s_loadedEmulator == this); return retro_serialize(data, size); } bool Emulator::unserialize(const void* data, size_t size) { assert(s_loadedEmulator == this); try { retro_system_info systemInfo; retro_get_system_info(&systemInfo); if (!strcmp(systemInfo.library_name, "Stella")) { reset(); } return retro_unserialize(data, size); } catch (...) { return false; } } size_t Emulator::serializeSize() { assert(s_loadedEmulator == this); return retro_serialize_size(); } void Emulator::clearCheats() { assert(s_loadedEmulator == this); retro_cheat_reset(); } void Emulator::setCheat(unsigned index, bool enabled, const char* code) { assert(s_loadedEmulator == this); retro_cheat_set(index, enabled, code); } bool Emulator::loadCore(const string& corePath) { if (s_loadedEmulator) { return false; } #ifdef _WIN32 m_coreHandle = LoadLibrary(corePath.c_str()); #else m_coreHandle = dlopen(corePath.c_str(), RTLD_LAZY); #endif if (!m_coreHandle) { return false; } retro_init = reinterpret_cast<void (*)()>(GETSYM(m_coreHandle, "retro_init")); retro_deinit = reinterpret_cast<void (*)()>(GETSYM(m_coreHandle, "retro_deinit")); retro_api_version = reinterpret_cast<unsigned int (*)()>(GETSYM(m_coreHandle, "retro_api_version")); retro_get_system_info = reinterpret_cast<void (*)(struct retro_system_info*)>(GETSYM(m_coreHandle, "retro_get_system_info")); retro_get_system_av_info = reinterpret_cast<void (*)(struct retro_system_av_info*)>(GETSYM(m_coreHandle, "retro_get_system_av_info")); retro_reset = reinterpret_cast<void (*)()>(GETSYM(m_coreHandle, "retro_reset")); retro_run = reinterpret_cast<void (*)()>(GETSYM(m_coreHandle, "retro_run")); retro_serialize_size = reinterpret_cast<size_t (*)()>(GETSYM(m_coreHandle, "retro_serialize_size")); retro_serialize = reinterpret_cast<bool (*)(void*, size_t)>(GETSYM(m_coreHandle, "retro_serialize")); retro_unserialize = reinterpret_cast<bool (*)(const void*, size_t)>(GETSYM(m_coreHandle, "retro_unserialize")); retro_load_game = reinterpret_cast<bool (*)(const struct retro_game_info*)>(GETSYM(m_coreHandle, "retro_load_game")); retro_unload_game = reinterpret_cast<void (*)()>(GETSYM(m_coreHandle, "retro_unload_game")); retro_get_memory_data = reinterpret_cast<void* (*) (unsigned int)>(GETSYM(m_coreHandle, "retro_get_memory_data")); retro_get_memory_size = reinterpret_cast<size_t (*)(unsigned int)>(GETSYM(m_coreHandle, "retro_get_memory_size")); retro_cheat_reset = reinterpret_cast<void (*)()>(GETSYM(m_coreHandle, "retro_cheat_reset")); retro_cheat_set = reinterpret_cast<void (*)(unsigned int, bool, const char*)>(GETSYM(m_coreHandle, "retro_cheat_set")); retro_set_environment = reinterpret_cast<void (*)(retro_environment_t)>(GETSYM(m_coreHandle, "retro_set_environment")); retro_set_video_refresh = reinterpret_cast<void (*)(retro_video_refresh_t)>(GETSYM(m_coreHandle, "retro_set_video_refresh")); retro_set_audio_sample = reinterpret_cast<void (*)(retro_audio_sample_t)>(GETSYM(m_coreHandle, "retro_set_audio_sample")); retro_set_audio_sample_batch = reinterpret_cast<void (*)(retro_audio_sample_batch_t)>(GETSYM(m_coreHandle, "retro_set_audio_sample_batch")); retro_set_input_poll = reinterpret_cast<void (*)(retro_input_poll_t)>(GETSYM(m_coreHandle, "retro_set_input_poll")); retro_set_input_state = reinterpret_cast<void (*)(short (*)(unsigned int, unsigned int, unsigned int, unsigned int))>(GETSYM(m_coreHandle, "retro_set_input_state")); // The default according to the docs m_imgDepth = 15; s_loadedEmulator = this; retro_set_environment(cbEnvironment); retro_set_video_refresh(cbVideoRefresh); retro_set_audio_sample(cbAudioSample); retro_set_audio_sample_batch(cbAudioSampleBatch); retro_set_input_poll(cbInputPoll); retro_set_input_state(cbInputState); retro_init(); return true; } void Emulator::fixScreenSize(const string& romName) { retro_system_info systemInfo; retro_get_system_info(&systemInfo); if (!strcmp(systemInfo.library_name, "Genesis Plus GX")) { switch (romName.back()) { case 'd': // Mega Drive // Genesis Plus GX gives us too small a resolution initially m_avInfo.geometry.base_width = 320; m_avInfo.geometry.base_height = 224; break; case 's': // Master System // Genesis Plus GX gives us too small a resolution initially m_avInfo.geometry.base_width = 256; m_avInfo.geometry.base_height = 192; break; case 'g': // Game Gear m_avInfo.geometry.base_width = 160; m_avInfo.geometry.base_height = 144; break; } } else if (!strcmp(systemInfo.library_name, "Stella")) { // Stella gives confusing values to pretend the pixel width is 2x m_avInfo.geometry.base_width = 160; m_avInfo.geometry.base_height = 210; } } void Emulator::reconfigureAddressSpace() { if (!m_addressSpace) { return; } if (!m_map.empty() && m_addressSpace->blocks().empty()) { for (const auto& desc : m_map) { if (desc.flags & RETRO_MEMDESC_CONST) { continue; } m_addressSpace->addBlock(desc.start, desc.len, desc.ptr); } } } bool Emulator::cbEnvironment(unsigned cmd, void* data) { assert(s_loadedEmulator); switch (cmd) { case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: switch (*reinterpret_cast<retro_pixel_format*>(data)) { case RETRO_PIXEL_FORMAT_XRGB8888: s_loadedEmulator->m_imgDepth = 32; break; case RETRO_PIXEL_FORMAT_RGB565: s_loadedEmulator->m_imgDepth = 16; break; case RETRO_PIXEL_FORMAT_0RGB1555: s_loadedEmulator->m_imgDepth = 15; break; default: s_loadedEmulator->m_imgDepth = 0; break; } return true; case RETRO_ENVIRONMENT_GET_VARIABLE: { struct retro_variable* var = reinterpret_cast<struct retro_variable*>(data); if (s_envVariables.count(string(var->key))) { var->value = s_envVariables[string(var->key)]; return true; } return false; } case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY: *reinterpret_cast<const char**>(data) = corePath().c_str(); return true; case RETRO_ENVIRONMENT_GET_CAN_DUPE: *reinterpret_cast<bool*>(data) = true; return true; case RETRO_ENVIRONMENT_SET_MEMORY_MAPS: s_loadedEmulator->m_map.clear(); for (size_t i = 0; i < static_cast<const retro_memory_map*>(data)->num_descriptors; ++i) { s_loadedEmulator->m_map.emplace_back(static_cast<const retro_memory_map*>(data)->descriptors[i]); } s_loadedEmulator->reconfigureAddressSpace(); return true; default: return false; } return false; } void Emulator::cbVideoRefresh(const void* data, unsigned, unsigned, size_t pitch) { assert(s_loadedEmulator); if (data) { s_loadedEmulator->m_imgData = data; } if (pitch) { s_loadedEmulator->m_imgPitch = pitch; } } void Emulator::cbAudioSample(int16_t left, int16_t right) { assert(s_loadedEmulator); s_loadedEmulator->m_audioData.push_back(left); s_loadedEmulator->m_audioData.push_back(right); } size_t Emulator::cbAudioSampleBatch(const int16_t* data, size_t frames) { assert(s_loadedEmulator); s_loadedEmulator->m_audioData.insert(s_loadedEmulator->m_audioData.end(), data, &data[frames * 2]); return frames; } void Emulator::cbInputPoll() { assert(s_loadedEmulator); } int16_t Emulator::cbInputState(unsigned port, unsigned, unsigned, unsigned id) { assert(s_loadedEmulator); return s_loadedEmulator->m_buttonMask[port][id]; } void Emulator::configureData(GameData* data) { m_addressSpace = &data->addressSpace(); m_addressSpace->reset(); Retro::configureData(data, m_core); reconfigureAddressSpace(); if (m_addressSpace->blocks().empty() && retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM)) { m_addressSpace->addBlock(Retro::ramBase(m_core), retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM), retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM)); } } vector<string> Emulator::buttons() const { return Retro::buttons(m_core); } vector<string> Emulator::keybinds() const { return Retro::keybinds(m_core); } }