Skip to content
Snippets Groups Projects
emulator.cpp 11.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Vicki Pfau's avatar
    Vicki Pfau committed
    #include <cassert>
    #include <dlfcn.h>
    #include <fstream>
    #include <map>
    #include <sstream>
    #include <unordered_map>
    #include <vector>
    
    #include "coreinfo.h"
    #include "data.h"
    #include "emulator.h"
    #include "libretro.h"
    
    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;
    
    		dlclose(m_coreHandle);
    		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();
    	dlclose(m_coreHandle);
    	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;
    	}
    
    	m_coreHandle = dlopen(corePath.c_str(), RTLD_LAZY);
    	if (!m_coreHandle) {
    		return false;
    	}
    
    	retro_init = reinterpret_cast<void (*)()>(dlsym(m_coreHandle, "retro_init"));
    	retro_deinit = reinterpret_cast<void (*)()>(dlsym(m_coreHandle, "retro_deinit"));
    	retro_api_version = reinterpret_cast<unsigned int (*)()>(dlsym(m_coreHandle, "retro_api_version"));
    	retro_get_system_info = reinterpret_cast<void (*)(struct retro_system_info*)>(dlsym(m_coreHandle, "retro_get_system_info"));
    	retro_get_system_av_info = reinterpret_cast<void (*)(struct retro_system_av_info*)>(dlsym(m_coreHandle, "retro_get_system_av_info"));
    	retro_reset = reinterpret_cast<void (*)()>(dlsym(m_coreHandle, "retro_reset"));
    	retro_run = reinterpret_cast<void (*)()>(dlsym(m_coreHandle, "retro_run"));
    	retro_serialize_size = reinterpret_cast<size_t (*)()>(dlsym(m_coreHandle, "retro_serialize_size"));
    	retro_serialize = reinterpret_cast<bool (*)(void*, size_t)>(dlsym(m_coreHandle, "retro_serialize"));
    	retro_unserialize = reinterpret_cast<bool (*)(const void*, size_t)>(dlsym(m_coreHandle, "retro_unserialize"));
    	retro_load_game = reinterpret_cast<bool (*)(const struct retro_game_info*)>(dlsym(m_coreHandle, "retro_load_game"));
    	retro_unload_game = reinterpret_cast<void (*)()>(dlsym(m_coreHandle, "retro_unload_game"));
    	retro_get_memory_data = reinterpret_cast<void* (*) (unsigned int)>(dlsym(m_coreHandle, "retro_get_memory_data"));
    	retro_get_memory_size = reinterpret_cast<size_t (*)(unsigned int)>(dlsym(m_coreHandle, "retro_get_memory_size"));
    	retro_cheat_reset = reinterpret_cast<void (*)()>(dlsym(m_coreHandle, "retro_cheat_reset"));
    	retro_cheat_set = reinterpret_cast<void (*)(unsigned int, bool, const char*)>(dlsym(m_coreHandle, "retro_cheat_set"));
    	retro_set_environment = reinterpret_cast<void (*)(retro_environment_t)>(dlsym(m_coreHandle, "retro_set_environment"));
    	retro_set_video_refresh = reinterpret_cast<void (*)(retro_video_refresh_t)>(dlsym(m_coreHandle, "retro_set_video_refresh"));
    	retro_set_audio_sample = reinterpret_cast<void (*)(retro_audio_sample_t)>(dlsym(m_coreHandle, "retro_set_audio_sample"));
    	retro_set_audio_sample_batch = reinterpret_cast<void (*)(retro_audio_sample_batch_t)>(dlsym(m_coreHandle, "retro_set_audio_sample_batch"));
    	retro_set_input_poll = reinterpret_cast<void (*)(retro_input_poll_t)>(dlsym(m_coreHandle, "retro_set_input_poll"));
    	retro_set_input_state = reinterpret_cast<void (*)(short (*)(unsigned int, unsigned int, unsigned int, unsigned int))>(dlsym(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);
    }
    }