summaryrefslogtreecommitdiffstats
path: root/gearboy/platforms/libretro/libretro.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gearboy/platforms/libretro/libretro.cpp')
-rw-r--r--gearboy/platforms/libretro/libretro.cpp595
1 files changed, 595 insertions, 0 deletions
diff --git a/gearboy/platforms/libretro/libretro.cpp b/gearboy/platforms/libretro/libretro.cpp
new file mode 100644
index 00000000..a01ca8a9
--- /dev/null
+++ b/gearboy/platforms/libretro/libretro.cpp
@@ -0,0 +1,595 @@
+/*
+ * Gearboy - Nintendo Game Boy Emulator
+ * Copyright (C) 2012 Ignacio Sanchez
+ * Copyright (C) 2017 Andrés Suárez
+ * Copyright (C) 2017 Brad Parker
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <math.h>
+
+#include <stdio.h>
+#include "libretro.h"
+
+#include "../../src/gearboy.h"
+
+#define VIDEO_WIDTH 160
+#define VIDEO_HEIGHT 144
+
+#ifdef _WIN32
+static const char slash = '\\';
+#else
+static const char slash = '/';
+#endif
+
+static u16* gearboy_frame_buf;
+
+static struct retro_log_callback logging;
+static retro_log_printf_t log_cb;
+static char retro_system_directory[4096];
+static char retro_game_path[4096];
+
+static s16 audio_buf[AUDIO_BUFFER_SIZE];
+static int audio_sample_count;
+
+static bool force_dmg = false;
+static bool force_gba = false;
+static bool allow_up_down = false;
+static bool bootrom_dmg = false;
+static bool bootrom_gbc = false;
+static bool libretro_supports_bitmasks;
+
+static void fallback_log(enum retro_log_level level, const char *fmt, ...)
+{
+ (void)level;
+ va_list va;
+ va_start(va, fmt);
+ vfprintf(stderr, fmt, va);
+ va_end(va);
+}
+
+static GearboyCore* core;
+static Cartridge::CartridgeTypes mapper = Cartridge::CartridgeNotSupported;
+
+static retro_environment_t environ_cb;
+
+static const struct retro_variable vars[] = {
+ { "gearboy_model", "Game Boy Model (restart); Auto|Game Boy DMG|Game Boy Advance" },
+ { "gearboy_mapper", "Mapper (restart); Auto|ROM Only|MBC 1|MBC 2|MBC 3|MBC 5|MBC 1 Multicart" },
+ { "gearboy_palette", "DMG Palette; Original|Sharp|B/W|Autumn|Soft|Slime" },
+ { "gearboy_bootrom_dmg", "DMG Bootrom (restart); Disabled|Enabled" },
+ { "gearboy_bootrom_gbc", "Game Boy Color Bootrom (restart); Disabled|Enabled" },
+ { "gearboy_up_down_allowed", "Allow Up+Down / Left+Right; Disabled|Enabled" },
+
+ { NULL }
+};
+
+// red, green, blue
+static GB_Color original_palette[4] = {{0x87, 0x96, 0x03},{0x4D, 0x6B, 0x03},{0x2B, 0x55, 0x03},{0x14, 0x44, 0x03}};
+static GB_Color sharp_palette[4] = {{0xF5, 0xFA, 0xEF},{0x86, 0xC2, 0x70},{0x2F, 0x69, 0x57},{0x0B, 0x19, 0x20}};
+static GB_Color bw_palette[4] = {{0xFF, 0xFF, 0xFF},{0xAA, 0xAA, 0xAA},{0x55, 0x55, 0x55},{0x00, 0x00, 0x00}};
+static GB_Color autumn_palette[4] = {{0xF8, 0xE8, 0xC8},{0xD8, 0x90, 0x48},{0xA8, 0x34, 0x20},{0x30, 0x18, 0x50}};
+static GB_Color soft_palette[4] = {{0xE0, 0xE0, 0xAA},{0xB0, 0xB8, 0x7C},{0x72, 0x82, 0x5B},{0x39, 0x34, 0x17}};
+static GB_Color slime_palette[4] = {{0xD4, 0xEB, 0xA5},{0x62, 0xB8, 0x7C},{0x27, 0x76, 0x5D},{0x1D, 0x39, 0x39}};
+
+static GB_Color* current_palette = original_palette;
+
+void retro_init(void)
+{
+ const char *dir = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir) {
+ snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", dir);
+ }
+ else {
+ snprintf(retro_system_directory, sizeof(retro_system_directory), "%s", ".");
+ }
+
+ core = new GearboyCore();
+
+#ifdef PS2
+ core->Init(GB_PIXEL_BGR555);
+#else
+ core->Init(GB_PIXEL_RGB565);
+#endif
+
+ gearboy_frame_buf = new u16[VIDEO_WIDTH * VIDEO_HEIGHT];
+
+ audio_sample_count = 0;
+ libretro_supports_bitmasks = environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL);
+}
+
+void retro_deinit(void)
+{
+ SafeDeleteArray(gearboy_frame_buf);
+ SafeDelete(core);
+}
+
+unsigned retro_api_version(void)
+{
+ return RETRO_API_VERSION;
+}
+
+void retro_set_controller_port_device(unsigned port, unsigned device)
+{
+ log_cb(RETRO_LOG_INFO, "Plugging device %u into port %u.\n", device, port);
+}
+
+void retro_get_system_info(struct retro_system_info *info)
+{
+ memset(info, 0, sizeof(*info));
+ info->library_name = GEARBOY_TITLE;
+ info->library_version = GEARBOY_VERSION;
+ info->need_fullpath = false;
+ info->valid_extensions = "gb|dmg|gbc|cgb|sgb";
+}
+
+static retro_video_refresh_t video_cb;
+static retro_audio_sample_t audio_cb;
+static retro_audio_sample_batch_t audio_batch_cb;
+static retro_input_poll_t input_poll_cb;
+static retro_input_state_t input_state_cb;
+
+void retro_get_system_av_info(struct retro_system_av_info *info)
+{
+ float aspect = (float)VIDEO_WIDTH/VIDEO_HEIGHT;
+ info->geometry.base_width = VIDEO_WIDTH;
+ info->geometry.base_height = VIDEO_HEIGHT;
+ info->geometry.max_width = VIDEO_WIDTH;
+ info->geometry.max_height = VIDEO_HEIGHT;
+ info->geometry.aspect_ratio = aspect;
+ info->timing.fps = 4194304.0 / 70224.0;
+ info->timing.sample_rate = 44100.0f;
+}
+
+void retro_set_environment(retro_environment_t cb)
+{
+ environ_cb = cb;
+
+ if (cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &logging))
+ log_cb = logging.log;
+ else
+ log_cb = fallback_log;
+
+ static const struct retro_controller_description controllers[] = {
+ { "Nintendo Gameboy", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) },
+ };
+
+ static const struct retro_controller_info ports[] = {
+ { controllers, 1 },
+ { NULL, 0 },
+ };
+
+ cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
+
+ environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void *)vars);
+}
+
+void retro_set_audio_sample(retro_audio_sample_t cb)
+{
+ audio_cb = cb;
+}
+
+void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
+{
+ audio_batch_cb = cb;
+}
+
+void retro_set_input_poll(retro_input_poll_t cb)
+{
+ input_poll_cb = cb;
+}
+
+void retro_set_input_state(retro_input_state_t cb)
+{
+ input_state_cb = cb;
+}
+
+void retro_set_video_refresh(retro_video_refresh_t cb)
+{
+ video_cb = cb;
+}
+
+static void load_bootroms(void)
+{
+ char bootrom_dmg_path[4112];
+ char bootrom_gbc_path[4112];
+
+ sprintf(bootrom_dmg_path, "%s%cdmg_boot.bin", retro_system_directory, slash);
+ sprintf(bootrom_gbc_path, "%s%ccgb_boot.bin", retro_system_directory, slash);
+
+ core->GetMemory()->LoadBootromDMG(bootrom_dmg_path);
+ core->GetMemory()->LoadBootromGBC(bootrom_gbc_path);
+ core->GetMemory()->EnableBootromDMG(bootrom_dmg);
+ core->GetMemory()->EnableBootromGBC(bootrom_gbc);
+}
+
+static void update_input(void)
+{
+ input_poll_cb();
+
+ int16_t ib;
+ if (libretro_supports_bitmasks)
+ ib = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
+ else
+ {
+ unsigned int i;
+ ib = 0;
+ for (i = 0; i <= RETRO_DEVICE_ID_JOYPAD_R3; i++)
+ ib |= input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, i) ? (1 << i) : 0;
+ }
+
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_UP))
+ {
+ if (allow_up_down || !(ib & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)))
+ core->KeyPressed(Up_Key);
+ }
+ else
+ core->KeyReleased(Up_Key);
+
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN))
+ {
+ if (allow_up_down || !(ib & (1 << RETRO_DEVICE_ID_JOYPAD_UP)))
+ core->KeyPressed(Down_Key);
+ }
+ else
+ core->KeyReleased(Down_Key);
+
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT))
+ {
+ if (allow_up_down || !(ib & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT)))
+ core->KeyPressed(Left_Key);
+ }
+ else
+ core->KeyReleased(Left_Key);
+
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_RIGHT))
+ {
+ if (allow_up_down || !(ib & (1 << RETRO_DEVICE_ID_JOYPAD_LEFT)))
+ core->KeyPressed(Right_Key);
+ }
+ else
+ core->KeyReleased(Right_Key);
+
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_B))
+ core->KeyPressed(B_Key);
+ else
+ core->KeyReleased(B_Key);
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_A))
+ core->KeyPressed(A_Key);
+ else
+ core->KeyReleased(A_Key);
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_START))
+ core->KeyPressed(Start_Key);
+ else
+ core->KeyReleased(Start_Key);
+ if (ib & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT))
+ core->KeyPressed(Select_Key);
+ else
+ core->KeyReleased(Select_Key);
+}
+
+static void check_variables(void)
+{
+ struct retro_variable var = {0};
+
+ var.key = "gearboy_model";
+ var.value = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "Game Boy DMG") == 0)
+ {
+ force_dmg = true;
+ force_gba = false;
+ }
+ else if (strcmp(var.value, "Game Boy Advance") == 0)
+ {
+ force_gba = true;
+ force_dmg = false;
+ }
+ else
+ {
+ force_dmg = false;
+ force_gba = false;
+ }
+ }
+
+ var.key = "gearboy_mapper";
+ var.value = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "Auto") == 0)
+ mapper = Cartridge::CartridgeNotSupported;
+ else if (strcmp(var.value, "ROM Only") == 0)
+ mapper = Cartridge::CartridgeNoMBC;
+ else if (strcmp(var.value, "MBC 1") == 0)
+ mapper = Cartridge::CartridgeMBC1;
+ else if (strcmp(var.value, "MBC 2") == 0)
+ mapper = Cartridge::CartridgeMBC2;
+ else if (strcmp(var.value, "MBC 3") == 0)
+ mapper = Cartridge::CartridgeMBC3;
+ else if (strcmp(var.value, "MBC 5") == 0)
+ mapper = Cartridge::CartridgeMBC5;
+ else if (strcmp(var.value, "MBC 1 Multicart") == 0)
+ mapper = Cartridge::CartridgeMBC1Multi;
+ else
+ mapper = Cartridge::CartridgeNotSupported;
+ }
+
+ var.key = "gearboy_palette";
+ var.value = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "Original") == 0)
+ current_palette = original_palette;
+ else if (strcmp(var.value, "Sharp") == 0)
+ current_palette = sharp_palette;
+ else if (strcmp(var.value, "B/W") == 0)
+ current_palette = bw_palette;
+ else if (strcmp(var.value, "Autumn") == 0)
+ current_palette = autumn_palette;
+ else if (strcmp(var.value, "Soft") == 0)
+ current_palette = soft_palette;
+ else if (strcmp(var.value, "Slime") == 0)
+ current_palette = slime_palette;
+ else
+ current_palette = original_palette;
+ }
+
+ var.key = "gearboy_bootrom_dmg";
+ var.value = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "Enabled") == 0)
+ bootrom_dmg = true;
+ else
+ bootrom_dmg = false;
+ }
+
+ var.key = "gearboy_bootrom_gbc";
+ var.value = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "Enabled") == 0)
+ bootrom_gbc = true;
+ else
+ bootrom_gbc = false;
+ }
+
+ var.key = "gearboy_up_down_allowed";
+ var.value = NULL;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "Enabled") == 0)
+ allow_up_down = true;
+ else
+ allow_up_down = false;
+ }
+}
+
+void retro_run(void)
+{
+ bool updated = false;
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
+ {
+ check_variables();
+ core->SetDMGPalette(current_palette[0], current_palette[1], current_palette[2], current_palette[3]);
+ }
+
+ update_input();
+
+ core->RunToVBlank(gearboy_frame_buf, audio_buf, &audio_sample_count);
+
+ video_cb((uint8_t*)gearboy_frame_buf, VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * sizeof(u16));
+
+ if (audio_sample_count > 0)
+ audio_batch_cb(audio_buf, audio_sample_count / 2);
+
+ audio_sample_count = 0;
+}
+
+void retro_reset(void)
+{
+ check_variables();
+ load_bootroms();
+
+ core->SetDMGPalette(current_palette[0], current_palette[1], current_palette[2], current_palette[3]);
+
+ core->ResetROMPreservingRAM(force_dmg, mapper, force_gba);
+}
+
+
+bool retro_load_game(const struct retro_game_info *info)
+{
+ check_variables();
+ load_bootroms();
+
+ core->SetDMGPalette(current_palette[0], current_palette[1], current_palette[2], current_palette[3]);
+
+ if (!core->LoadROMFromBuffer(reinterpret_cast<const u8*>(info->data), info->size, force_dmg, mapper, force_gba))
+ {
+ log_cb(RETRO_LOG_ERROR, "Invalid or corrupted ROM.\n");
+ return false;
+ }
+
+ struct retro_input_descriptor desc[] = {
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
+ { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
+ { 0 },
+ };
+
+ environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
+
+ enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_RGB565;
+
+ if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
+ {
+ log_cb(RETRO_LOG_INFO, "RETRO_PIXEL_FORMAT_RGB565 is not supported.\n");
+ return false;
+ }
+
+ snprintf(retro_game_path, sizeof(retro_game_path), "%s", info->path);
+
+ struct retro_memory_descriptor descs[11];
+
+ memset(descs, 0, sizeof(descs));
+
+ // IE
+ descs[0].ptr = core->GetMemory()->GetMemoryMap() + 0xFFFF;
+ descs[0].start = 0xFFFF;
+ descs[0].len = 1;
+ // HRAM
+ descs[1].ptr = core->GetMemory()->GetMemoryMap() + 0xFF80;
+ descs[1].start = 0xFF80;
+ descs[1].len = 0x0080;
+ // RAM bank 0
+ descs[2].ptr = core->IsCGB() ? core->GetMemory()->GetCGBRAM() : (core->GetMemory()->GetMemoryMap() + 0xC000);
+ descs[2].start = 0xC000;
+ descs[2].len = 0x1000;
+ // RAM bank 1
+ descs[3].ptr = core->IsCGB() ? (core->GetMemory()->GetCGBRAM() + (0x1000)) : (core->GetMemory()->GetMemoryMap() + 0xD000);
+ descs[3].start = 0xD000;
+ descs[3].len = 0x1000;
+ // CART RAM
+ descs[4].ptr = core->GetMemory()->GetCurrentRule()->GetCurrentRamBank();
+ descs[4].start = 0xA000;
+ descs[4].len = 0x2000;
+ // VRAM
+ descs[5].ptr = core->GetMemory()->GetMemoryMap() + 0x8000; // todo: fix GBC
+ descs[5].start = 0x8000;
+ descs[5].len = 0x2000;
+ // ROM bank 0
+ descs[6].ptr = core->GetMemory()->GetCurrentRule()->GetRomBank0();
+ descs[6].start = 0x0000;
+ descs[6].len = 0x4000;
+ // ROM bank x
+ descs[7].ptr = core->GetMemory()->GetCurrentRule()->GetCurrentRomBank1();
+ descs[7].start = 0x4000;
+ descs[7].len = 0x4000;
+ // OAM
+ descs[8].ptr = core->GetMemory()->GetMemoryMap() + 0xFE00;
+ descs[8].start = 0xFE00;
+ descs[8].len = 0x00A0;
+ descs[8].select= 0xFFFFFF00;
+ // CGB RAM banks 2-7
+ descs[9].ptr = core->IsCGB() ? (core->GetMemory()->GetCGBRAM() + 0x2000) : (core->GetMemory()->GetMemoryMap() + 0xD000);
+ descs[9].start = 0x10000;
+ descs[9].len = core->IsCGB() ? 0x6000 : 0;
+ descs[9].select= 0xFFFF0000;
+ // IO PORTS
+ descs[10].ptr = core->GetMemory()->GetMemoryMap() + 0xFF00;
+ descs[10].start = 0xFF00;
+ descs[10].len = 0x0080;
+ descs[10].select= 0xFFFFFF00;
+
+ struct retro_memory_map mmaps;
+ mmaps.descriptors = descs;
+ mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]);
+ environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
+
+ bool achievements = true;
+ environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &achievements);
+
+ return true;
+}
+
+void retro_unload_game(void)
+{
+}
+
+unsigned retro_get_region(void)
+{
+ return RETRO_REGION_NTSC;
+}
+
+bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num)
+{
+ return false;
+}
+
+size_t retro_serialize_size(void)
+{
+ size_t size;
+ core->SaveState(NULL, size);
+ return size;
+}
+
+bool retro_serialize(void *data, size_t size)
+{
+ return core->SaveState(reinterpret_cast<u8*>(data), size);
+}
+
+bool retro_unserialize(const void *data, size_t size)
+{
+ return core->LoadState(reinterpret_cast<const u8*>(data), size);
+}
+
+void *retro_get_memory_data(unsigned id)
+{
+ switch (id)
+ {
+ case RETRO_MEMORY_SAVE_RAM:
+ return core->GetMemory()->GetCurrentRule()->GetRamBanks();
+ case RETRO_MEMORY_RTC:
+ return core->GetMemory()->GetCurrentRule()->GetRTCMemory();
+ case RETRO_MEMORY_SYSTEM_RAM:
+ return core->IsCGB() ? core->GetMemory()->GetCGBRAM() : core->GetMemory()->GetMemoryMap() + 0xC000;
+ }
+
+ return NULL;
+}
+
+size_t retro_get_memory_size(unsigned id)
+{
+ switch (id)
+ {
+ case RETRO_MEMORY_SAVE_RAM:
+ return core->GetMemory()->GetCurrentRule()->GetRamSize();
+ case RETRO_MEMORY_RTC:
+ return core->GetMemory()->GetCurrentRule()->GetRTCSize();
+ case RETRO_MEMORY_SYSTEM_RAM:
+ return core->IsCGB() ? 0x8000 : 0x2000;
+ }
+
+ return 0;
+}
+
+void retro_cheat_reset(void)
+{
+ core->ClearCheats();
+}
+
+void retro_cheat_set(unsigned index, bool enabled, const char *code)
+{
+ core->SetCheat(code);
+}