#include #include #include #include "libretro_core_options.h" #include "mednafen/settings.h" #include "mednafen/git.h" #include "mednafen/wswan/wswan.h" #include "mednafen/mempatcher.h" #include "mednafen/wswan/gfx.h" #include "mednafen/wswan/interrupt.h" #include "mednafen/wswan/wswan-memory.h" #include "mednafen/wswan/start.inc" #include "mednafen/wswan/sound.h" #include "mednafen/wswan/v30mz.h" #include "mednafen/wswan/rtc.h" #include "mednafen/wswan/eeprom.h" #if defined(_3DS) void* linearMemAlign(size_t size, size_t alignment); void linearFree(void* mem); #endif #define MEDNAFEN_CORE_NAME_MODULE "wswan" #define MEDNAFEN_CORE_NAME "Beetle WonderSwan" #define MEDNAFEN_CORE_VERSION "v0.9.35.1" #define MEDNAFEN_CORE_EXTENSIONS "ws|wsc|pc2" #define MEDNAFEN_CORE_TIMING_FPS (3072000.0 / (159.0 * 256.0)) #define MEDNAFEN_CORE_GEOMETRY_BASE_W 224 #define MEDNAFEN_CORE_GEOMETRY_BASE_H 144 #define MEDNAFEN_CORE_GEOMETRY_MAX_W 224 #define MEDNAFEN_CORE_GEOMETRY_MAX_H 144 #define MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO (14.0 / 9.0) #define FB_WIDTH 224 #define FB_HEIGHT 144 #define FB_MAX_HEIGHT FB_HEIGHT /* Forward declarations */ void MDFN_LoadGameCheats(void *override_ptr); void MDFN_FlushGameCheats(int nosave); /* core options */ static int RETRO_SAMPLE_RATE = 44100; static int RETRO_PIX_BYTES = 2; static int RETRO_PIX_DEPTH = 15; struct retro_perf_callback perf_cb; retro_log_printf_t log_cb; 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_environment_t environ_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; static bool libretro_supports_bitmasks = false; typedef enum { DISPLAY_ROTATION_MANUAL = 0, DISPLAY_ROTATION_PORTRAIT, DISPLAY_ROTATION_LANDSCAPE } retro_display_rotation_t; static retro_display_rotation_t retro_display_rotation = DISPLAY_ROTATION_MANUAL; static bool rotate_tall = false; static bool select_pressed_last_frame = false; static bool hw_rotate_enabled = false; static unsigned rotate_joymap = 0; static MDFN_Surface *surf = NULL; static uint16_t *rotate_buf = NULL; #define ROTATE_PIXEL_BUF(typename_t, src, width, height, dst) \ { \ typename_t *in_ptr = (typename_t*)src; \ typename_t *out_ptr = (typename_t*)dst; \ size_t x, y; \ for (x = 0; x < width; x++) \ for (y = 0; y < height; y++) \ *(out_ptr + y + (((width - 1) - x) * height)) = *(in_ptr + x + (y * width)); \ } static int16_t *audio_samples_buf = NULL; static int32_t audio_samples_buf_size = 0; #define RETRO_60HZ_FPS ((4.0 * MEDNAFEN_CORE_TIMING_FPS) / 5.0) #define RETRO_60HZ_CYCLE_INDEX 4 typedef struct { int16_t *samples_buf; int32_t samples_buf_size; int32_t samples_buf_pos; int32_t samples_per_frame; } retro_60hz_audio_t; static bool retro_60hz_enabled = false; static uint16_t retro_60hz_counter = 0; static retro_60hz_audio_t retro_60hz_audio = {0}; static void retro_60hz_deinit(void) { if (retro_60hz_audio.samples_buf) free(retro_60hz_audio.samples_buf); retro_60hz_audio.samples_buf = NULL; retro_60hz_audio.samples_buf_size = 0; retro_60hz_audio.samples_buf_pos = 0; retro_60hz_audio.samples_per_frame = 0; retro_60hz_counter = 0; } static void retro_60hz_init(void) { retro_60hz_deinit(); if (retro_60hz_enabled) { /* Get expected number of samples per 60Hz * frame * > Round down - any excess samples will be read * out at the end of each 4/5 frame cycle */ retro_60hz_audio.samples_per_frame = ((int32_t)(RETRO_SAMPLE_RATE / RETRO_60HZ_FPS)); /* Initial buffer size should be *twice* that of * the expected number of samples per 75Hz frame */ retro_60hz_audio.samples_buf_size = ((int32_t)(RETRO_SAMPLE_RATE / MEDNAFEN_CORE_TIMING_FPS) + 1) << 2; retro_60hz_audio.samples_buf = (int16_t*)malloc( retro_60hz_audio.samples_buf_size * sizeof(int16_t)); if (!retro_60hz_audio.samples_buf) { retro_60hz_deinit(); retro_60hz_enabled = false; } } } static void retro_60hz_cache_audio_samples(int16_t *samples, int32_t frames) { int32_t buffer_capacity = retro_60hz_audio.samples_buf_size - retro_60hz_audio.samples_buf_pos; int32_t samples_to_write = frames << 1; /* Resize buffer if necessary */ if (buffer_capacity < samples_to_write) { int16_t *tmp_buffer = NULL; int32_t tmp_buffer_size; tmp_buffer_size = retro_60hz_audio.samples_buf_size + (samples_to_write - buffer_capacity); tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1); tmp_buffer = (int16_t *)malloc(tmp_buffer_size * sizeof(int16_t)); memcpy(tmp_buffer, retro_60hz_audio.samples_buf, retro_60hz_audio.samples_buf_pos * sizeof(int16_t)); free(retro_60hz_audio.samples_buf); retro_60hz_audio.samples_buf = tmp_buffer; retro_60hz_audio.samples_buf_size = tmp_buffer_size; } /* Copy samples */ memcpy(retro_60hz_audio.samples_buf + retro_60hz_audio.samples_buf_pos, samples, samples_to_write * sizeof(int16_t)); retro_60hz_audio.samples_buf_pos += samples_to_write; } static bool audio_low_pass_enabled = false; static int64_t audio_low_pass_acc_left = 0; static int64_t audio_low_pass_acc_right = 0; static void audio_low_pass_apply(int16_t *samples, int32_t frames) { while (frames-- > 0) { int64_t drop_current_left; int64_t drop_current_right; /* Left channel */ drop_current_left = ((*samples << 16) - audio_low_pass_acc_left) >> 3; audio_low_pass_acc_left += drop_current_left; *samples++ = (int16_t)((audio_low_pass_acc_left >> 16) & 0xFFFF); /* Right channel */ drop_current_right = ((*samples << 16) - audio_low_pass_acc_right) >> 3; audio_low_pass_acc_right += drop_current_right; *samples++ = (int16_t)((audio_low_pass_acc_right >> 16) & 0xFFFF); } } /* Mono palettes */ struct ws_mono_palette { const char *name; uint32 start; uint32 end; }; static struct ws_mono_palette ws_mono_palettes[] = { { "default", 0x000000, 0xFFFFFF }, { "wonderswan", 0x3E3D20, 0x9B9D66 }, { "wondeswan_color", 0x1B201E, 0xD7D49D }, { "swancrystal", 0x151108, 0xFFFCCA }, { "gb_dmg", 0x00420C, 0x578200 }, { "gb_pocket", 0x2A3325, 0xA7B19A }, { "gb_light", 0x00778D, 0x01CBDF }, { "blossom_pink", 0x180F0F, 0xF09898 }, { "bubbles_blue", 0x0D1418, 0x88D0F0 }, { "buttercup_green", 0x12160D, 0xB8E088 }, { "digivice", 0x000000, 0x8C8C73 }, { "game_com", 0x000000, 0xA7BF6B }, { "gameking", 0x184221, 0x8CCE94 }, { "game_master", 0x2D2D2B, 0x829FA6 }, { "golden_wild", 0x120F0A, 0xB99F65 }, { "greenscale", 0x0C360C, 0x9CBE0C }, { "hokage_orange", 0x170D08, 0xEA8352 }, { "labo_fawn", 0x15110B, 0xD7AA73 }, { "legendary_super_saiyan", 0x101509, 0xA5DB5A }, { "microvision", 0x303030, 0xA0A0A0 }, { "million_live_gold", 0x141109, 0xCDB261 }, { "odyssey_gold", 0x131000, 0xC2A000 }, { "shiny_sky_blue", 0x0E1216, 0x8CB6DF }, { "slime_blue", 0x040E14, 0x2F8CCC }, { "ti_83", 0x181810, 0x9CA684 }, { "travel_wood", 0x482810, 0xF8D8B0 }, { "virtual_boy", 0x000000, 0xE30000 }, { NULL, 0, 0 }, }; static uint32 mono_pal_start = 0x000000; static uint32 mono_pal_end = 0xFFFFFF; static bool find_mono_palette(const char* name, uint32 *start, uint32 *end) { bool palette_found = false; unsigned i; for (i = 0; ws_mono_palettes[i].name; i++) { if (!strcmp(ws_mono_palettes[i].name, name)) { *start = ws_mono_palettes[i].start; *end = ws_mono_palettes[i].end; palette_found = true; break; } } if (!palette_found) { *start = 0x000000; *end = 0xFFFFFF; } return palette_found; } /* Frameskipping */ static unsigned frameskip_type = 0; static unsigned frameskip_threshold = 0; static uint16_t frameskip_counter = 0; static bool retro_audio_buff_active = false; static unsigned retro_audio_buff_occupancy = 0; static bool retro_audio_buff_underrun = false; /* Maximum number of consecutive frames that * can be skipped */ #define FRAMESKIP_MAX 30 static unsigned audio_latency = 0; static bool update_audio_latency = false; static void retro_audio_buff_status_cb( bool active, unsigned occupancy, bool underrun_likely) { retro_audio_buff_active = active; retro_audio_buff_occupancy = occupancy; retro_audio_buff_underrun = underrun_likely; } static void init_frameskip(void) { if (frameskip_type > 0) { struct retro_audio_buffer_status_callback buf_status_cb; buf_status_cb.callback = retro_audio_buff_status_cb; if (!environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &buf_status_cb)) { if (log_cb) log_cb(RETRO_LOG_WARN, "Frameskip disabled - frontend does not support audio buffer status monitoring.\n"); retro_audio_buff_active = false; retro_audio_buff_occupancy = 0; retro_audio_buff_underrun = false; audio_latency = 0; } else { float fps; float latency_factor; float frame_time_msec; if (retro_60hz_enabled) { fps = (float)RETRO_60HZ_FPS; latency_factor = 6.0f; } else { fps = (float)MEDNAFEN_CORE_TIMING_FPS; latency_factor = 8.0f; } /* Frameskip is enabled - increase frontend * audio latency to minimise potential * buffer underruns */ frame_time_msec = 1000.0f / fps; /* Set latency to an integer multiple of * the current frame time... * > At 60Hz we normally use a 6x multiplier * > The native (unusually high) 75Hz of the * WonderSwan seems to place greater demands * on the frontend, so we increase the * multiplier to 8x; this gives the frontend * more room to manoeuvre, and improves the * efficacy of the 'Auto' frameskip setting */ audio_latency = (unsigned)((latency_factor * frame_time_msec) + 0.5f); /* ...then round up to nearest multiple of 32 */ audio_latency = (audio_latency + 0x1F) & ~0x1F; } } else { environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, NULL); audio_latency = 0; } update_audio_latency = true; } /* Cygne * * Copyright notice for this file: * Copyright (C) 2002 Dox dox@space.pl * * 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 2 of the License, or * (at your option) 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ int wsc = 1; /*color/mono*/ uint32 rom_size; uint16 WSButtonStatus; static void Reset(void) { int u0; v30mz_reset(); /* Reset CPU */ WSwan_MemoryReset(); WSwan_GfxReset(); WSwan_SoundReset(); WSwan_InterruptReset(); WSwan_RTCReset(); WSwan_EEPROMReset(); for(u0=0;u0<0xc9;u0++) { if(u0 != 0xC4 && u0 != 0xC5 && u0 != 0xBA && u0 != 0xBB) WSwan_writeport(u0,startio[u0]); } v30mz_set_reg(NEC_SS,0); v30mz_set_reg(NEC_SP,0x2000); } static uint8 *chee = NULL; static void Emulate(EmulateSpecStruct *espec, int skip_frame, int update_sample_rate) { espec->surface = surf; espec->DisplayRect.w = 224; espec->DisplayRect.h = 144; espec->skip = skip_frame; espec->SoundBufSize = 0; if (update_sample_rate) WSwan_SetSoundRate(RETRO_SAMPLE_RATE); WSButtonStatus = chee[0] | (chee[1] << 8); MDFNMP_ApplyPeriodicCheats(); while (!wsExecuteLine(espec->surface, espec->skip)); espec->SoundBufSize = WSwan_SoundFlush(&audio_samples_buf, &audio_samples_buf_size); if (audio_low_pass_enabled) audio_low_pass_apply(audio_samples_buf, espec->SoundBufSize); v30mz_timestamp = 0; } #if 0 typedef struct { const uint8 id; const char *name; } DLEntry; static const DLEntry Developers[] = { { 0x01, "Bandai" }, { 0x02, "Taito" }, { 0x03, "Tomy" }, { 0x04, "Koei" }, { 0x05, "Data East" }, { 0x06, "Asmik" }, /* Asmik Ace? */ { 0x07, "Media Entertainment" }, { 0x08, "Nichibutsu" }, { 0x0A, "Coconuts Japan" }, { 0x0B, "Sammy" }, { 0x0C, "Sunsoft" }, { 0x0D, "Mebius" }, { 0x0E, "Banpresto" }, { 0x10, "Jaleco" }, { 0x11, "Imagineer" }, { 0x12, "Konami" }, { 0x16, "Kobunsha" }, { 0x17, "Bottom Up" }, { 0x18, "Kaga Tech" }, { 0x19, "Sunrise" }, { 0x1A, "Cyber Front" }, { 0x1B, "Mega House" }, { 0x1D, "Interbec" }, { 0x1E, "Nihon Application" }, { 0x1F, "Bandai Visual" }, { 0x20, "Athena" }, { 0x21, "KID" }, { 0x22, "HAL Corporation" }, { 0x23, "Yuki Enterprise" }, { 0x24, "Omega Micott" }, { 0x25, "Layup" }, { 0x26, "Kadokawa Shoten" }, { 0x27, "Shall Luck" }, { 0x28, "Squaresoft" }, { 0x2B, "Tom Create" }, { 0x2D, "Namco" }, { 0x2E, "Movic" }, /* ???? */ { 0x2F, "E3 Staff" }, /* ???? */ { 0x31, "Vanguard" }, { 0x32, "Megatron" }, { 0x33, "Wiz" }, { 0x36, "Capcom" } }; #endif static uint32 SRAMSize; static int Load(const uint8_t *data, size_t size) { uint32 pow_size = 0; uint32 real_rom_size = 0; uint8 header[10]; if(size < 65536) return 0; real_rom_size = (size + 0xFFFF) & ~0xFFFF; pow_size = next_pow2(real_rom_size); rom_size = pow_size + (pow_size == 0); wsCartROM = (uint8 *)calloc(1, rom_size); /* This real_rom_size vs rom_size funny business * is intended primarily for handling * WSR files. */ if(real_rom_size < rom_size) memset(wsCartROM, 0xFF, rom_size - real_rom_size); memcpy(wsCartROM + (rom_size - real_rom_size), data, size); memcpy(header, wsCartROM + rom_size - 10, 10); SRAMSize = 0; eeprom_size = 0; switch(header[5]) { case 0x01: SRAMSize = 8 * 1024; break; case 0x02: SRAMSize = 32 * 1024; break; case 0x03: SRAMSize = 128 * 1024; break; case 0x04: SRAMSize = 256 * 1024; break; /* Dicing Knight!, Judgement Silver */ case 0x05: SRAMSize = 512 * 1024; break; /* Wonder Gate */ case 0x10: eeprom_size = 128; break; case 0x20: eeprom_size = 2 *1024; break; case 0x50: eeprom_size = 1024; break; } if((header[8] | (header[9] << 8)) == 0x8de1 && (header[0]==0x01)&&(header[2]==0x27)) /* Detective Conan */ { /* WS cpu is using cache/pipeline or there's protected ROM bank where pointing CS */ wsCartROM[0xfffe8]=0xea; wsCartROM[0xfffe9]=0x00; wsCartROM[0xfffea]=0x00; wsCartROM[0xfffeb]=0x00; wsCartROM[0xfffec]=0x20; } #if 0 if(header[6] & 0x1) EmulatedWSwan.rotated = MDFN_ROTATE90; #endif MDFNMP_Init(16384, (1 << 20) / 1024); v30mz_init(WSwan_readmem20, WSwan_writemem20, WSwan_readport, WSwan_writeport); WSwan_MemoryInit(MDFN_GetSettingB("wswan.language"), wsc, SRAMSize, false); /* EEPROM and SRAM are loaded in this func. */ WSwan_GfxInit(); WSwan_SoundInit(); wsMakeTiles(); Reset(); return 1; } static void CloseGame(void) { WSwan_MemoryKill(); WSwan_SoundKill(); if(wsCartROM) { free(wsCartROM); wsCartROM = NULL; } } int StateAction(StateMem *sm, int load, int data_only) { if(!v30mz_StateAction(sm, load, data_only)) return 0; /* Call MemoryStateAction before others StateActions... */ if(!WSwan_MemoryStateAction(sm, load, data_only)) return 0; if(!WSwan_GfxStateAction(sm, load, data_only)) return 0; if(!WSwan_RTCStateAction(sm, load, data_only)) return 0; if(!WSwan_InterruptStateAction(sm, load, data_only)) return 0; if(!WSwan_SoundStateAction(sm, load, data_only)) return 0; if(!WSwan_EEPROMStateAction(sm, load, data_only)) return 0; return 1; } static void DoSimpleCommand(int cmd) { switch(cmd) { case MDFN_MSC_POWER: case MDFN_MSC_RESET: Reset(); break; } } static const InputDeviceInputInfoStruct IDII[] = { { "up-x", "UP ↑, X Cursors", 0, IDIT_BUTTON, "down-x", { "right-x", "down-x", "left-x" } }, { "right-x", "RIGHT →, X Cursors", 3, IDIT_BUTTON, "left-x", { "down-x", "left-x", "up-x" } }, { "down-x", "DOWN ↓, X Cursors", 1, IDIT_BUTTON, "up-x", { "left-x", "up-x", "right-x" } }, { "left-x", "LEFT ←, X Cursors", 2, IDIT_BUTTON, "right-x", { "up-x", "right-x", "down-x" } }, { "up-y", "UP ↑, Y Cur: MUST NOT = X CURSORS", 4, IDIT_BUTTON, "down-y", { "right-y", "down-y", "left-y" } }, { "right-y", "RIGHT →, Y Cur: MUST NOT = X CURSORS", 7, IDIT_BUTTON, "left-y", { "down-y", "left-y", "up-y" } }, { "down-y", "DOWN ↓, Y Cur: MUST NOT = X CURSORS", 5, IDIT_BUTTON, "up-y", { "left-y", "up-y", "right-y" } }, { "left-y", "LEFT ←, Y Cur: MUST NOT = X CURSORS", 6, IDIT_BUTTON, "right-y", { "up-y", "right-y", "down-y" } }, { "start", "Start", 8, IDIT_BUTTON, NULL }, { "a", "A", 10, IDIT_BUTTON_CAN_RAPID, NULL }, { "b", "B", 9, IDIT_BUTTON_CAN_RAPID, NULL }, }; static InputDeviceInfoStruct InputDeviceInfo[] = { { "gamepad", "Gamepad", NULL, NULL, sizeof(IDII) / sizeof(InputDeviceInputInfoStruct), IDII, } }; static const InputPortInfoStruct PortInfo[] = { { "builtin", "Built-In", sizeof(InputDeviceInfo) / sizeof(InputDeviceInfoStruct), InputDeviceInfo, "gamepad" } }; static InputInfoStruct InputInfo = { sizeof(PortInfo) / sizeof(InputPortInfoStruct), PortInfo }; static bool update_audio = false; static bool update_video = false; static bool MDFNI_LoadGame( const char *force_module, const uint8_t *data, size_t size) { if(Load(data, size) <= 0) return false; MDFN_LoadGameCheats(NULL); MDFNMP_InstallReadPatches(); return true; } static void MDFNI_CloseGame(void) { MDFN_FlushGameCheats(0); CloseGame(); MDFNMP_Kill(); } static void check_system_specs(void) { /* TODO - should theoretically be level 4, but apparently * doesn't run at fullspeed on PSP (yet) */ unsigned level = 4; environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level); } static void check_depth(void) { if (RETRO_PIX_DEPTH == 24) { enum retro_pixel_format rgb888 = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb888)) { if (log_cb) log_cb(RETRO_LOG_ERROR, "Pixel format XRGB8888 not supported by platform.\n"); RETRO_PIX_BYTES = 2; RETRO_PIX_DEPTH = 15; } } #if defined(FRONTEND_SUPPORTS_RGB565) if (RETRO_PIX_BYTES == 2) { enum retro_pixel_format rgb565 = RETRO_PIXEL_FORMAT_RGB565; if (environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb565)) { if (log_cb) log_cb(RETRO_LOG_INFO, "Frontend supports RGB565 - will use that instead of XRGB1555.\n"); RETRO_PIX_DEPTH = 16; } } #endif } static void rotate_display(void) { if (hw_rotate_enabled) { struct retro_game_geometry new_geom = { FB_WIDTH, FB_HEIGHT, FB_WIDTH, FB_HEIGHT, rotate_tall ? (9.0 / 14.0) : (14.0 / 9.0) }; unsigned rot_angle = rotate_tall ? 1 : /* 90 degrees */ 0; /* 0 degrees */ environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, (void*)&new_geom); environ_cb(RETRO_ENVIRONMENT_SET_ROTATION, (void*)&rot_angle); } else { struct retro_game_geometry new_geom = { rotate_tall ? FB_HEIGHT : FB_WIDTH, rotate_tall ? FB_WIDTH : FB_HEIGHT, FB_WIDTH, FB_WIDTH, rotate_tall ? (9.0 / 14.0) : (14.0 / 9.0) }; environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, (void*)&new_geom); } } static void check_variables(int startup) { struct retro_variable var = {0}; uint32 prev_mono_pal_start; uint32 prev_mono_pal_end; unsigned prev_frameskip_type; bool update_60hz_mode = false; var.key = "wswan_rotate_display", var.value = NULL; retro_display_rotation = DISPLAY_ROTATION_MANUAL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "portrait")) retro_display_rotation = DISPLAY_ROTATION_PORTRAIT; else if (!strcmp(var.value, "landscape")) retro_display_rotation = DISPLAY_ROTATION_LANDSCAPE; } switch (retro_display_rotation) { case DISPLAY_ROTATION_PORTRAIT: if (!rotate_tall) { rotate_tall = true; rotate_display(); } break; case DISPLAY_ROTATION_LANDSCAPE: if (rotate_tall) { rotate_tall = false; rotate_display(); } break; default: break; } var.key = "wswan_rotate_keymap", var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "disabled")) rotate_joymap = 0; else if (!strcmp(var.value, "enabled")) rotate_joymap = 1; else if (!strcmp(var.value, "auto")) rotate_joymap = 2; } var.key = "wswan_mono_palette", var.value = NULL; prev_mono_pal_start = mono_pal_start; prev_mono_pal_end = mono_pal_end; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) find_mono_palette(var.value, &mono_pal_start, &mono_pal_end); if ((mono_pal_start != prev_mono_pal_start) || (mono_pal_end != prev_mono_pal_end)) WSwan_SetMonoPalette(RETRO_PIX_DEPTH, mono_pal_start, mono_pal_end); var.key = "wswan_frameskip"; var.value = NULL; prev_frameskip_type = frameskip_type; frameskip_type = 0; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "auto") == 0) frameskip_type = 1; else if (strcmp(var.value, "manual") == 0) frameskip_type = 2; } var.key = "wswan_frameskip_threshold"; var.value = NULL; frameskip_threshold = 33; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) frameskip_threshold = strtol(var.value, NULL, 10); var.key = "wswan_60hz_mode"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { bool old_value = retro_60hz_enabled; if (!strcmp(var.value, "disabled")) retro_60hz_enabled = false; else if (!strcmp(var.value, "enabled")) retro_60hz_enabled = true; if (!startup && (old_value != retro_60hz_enabled)) { update_video = true; update_60hz_mode = true; } } var.key = "wswan_sound_sample_rate"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { int old_value = RETRO_SAMPLE_RATE; RETRO_SAMPLE_RATE = atoi(var.value); RETRO_SAMPLE_RATE = (RETRO_SAMPLE_RATE < 11025) ? 11025 : RETRO_SAMPLE_RATE; RETRO_SAMPLE_RATE = (RETRO_SAMPLE_RATE > 48000) ? 48000 : RETRO_SAMPLE_RATE; if (!startup && (old_value != RETRO_SAMPLE_RATE)) { update_audio = true; /* If audio sample rate changes, must reinitialise * 60Hz mode (will be a no-op if 60Hz mode is * currently inactive) */ update_60hz_mode = true; } } var.key = "wswan_sound_low_pass"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "disabled")) audio_low_pass_enabled = false; else if (!strcmp(var.value, "enabled")) audio_low_pass_enabled = true; } if (startup) { var.key = "wswan_gfx_colors"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "16bit")) { RETRO_PIX_BYTES = 2; RETRO_PIX_DEPTH = 16; } else if (!strcmp(var.value, "24bit")) { RETRO_PIX_BYTES = 4; RETRO_PIX_DEPTH = 24; } } } if (update_60hz_mode) retro_60hz_init(); /* (Re)Initialise frameskipping, if required */ if (startup || (frameskip_type != prev_frameskip_type) || update_60hz_mode) init_frameskip(); } void retro_init(void) { struct retro_log_callback log; if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) log_cb = log.log; else log_cb = NULL; frameskip_type = 0; frameskip_threshold = 0; frameskip_counter = 0; retro_audio_buff_active = false; retro_audio_buff_occupancy = 0; retro_audio_buff_underrun = false; audio_latency = 0; update_audio_latency = false; audio_low_pass_enabled = false; audio_low_pass_acc_left = 0; audio_low_pass_acc_right = 0; check_system_specs(); check_variables(true); check_depth(); if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) libretro_supports_bitmasks = true; } void retro_reset(void) { DoSimpleCommand(MDFN_MSC_RESET); } bool retro_load_game_special(unsigned a, const struct retro_game_info *b, size_t c) { return false; } #define MAX_PLAYERS 1 #define MAX_BUTTONS 11 static uint16_t input_buf; bool retro_load_game(const struct retro_game_info *info) { const unsigned rot_angle = 0; struct retro_input_descriptor desc[] = { { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "X Cursor Left" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "X Cursor Up" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "X Cursor Down" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "X Cursor Right" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "Y Cursor Left" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "Y Cursor Up" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "Y Cursor Down" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Y Cursor Right" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }, { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Rotate screen + active D-Pad" }, { 0 }, }; if (!info) goto error; environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); if (!MDFNI_LoadGame(MEDNAFEN_CORE_NAME_MODULE, (const uint8_t*)info->data, info->size)) goto error; chee = (uint8 *)&input_buf; surf = (MDFN_Surface*)calloc(1, sizeof(*surf)); if (!surf) goto error; surf->width = FB_WIDTH; surf->height = FB_HEIGHT; surf->pitch = FB_WIDTH; surf->depth = RETRO_PIX_DEPTH; #if defined(_3DS) surf->pixels = (uint16_t*)linearMemAlign(FB_WIDTH * FB_HEIGHT * sizeof(uint32_t), 128); #else surf->pixels = (uint16_t*)calloc(1, FB_WIDTH * FB_HEIGHT * sizeof(uint32_t)); #endif if (!surf->pixels) goto error; /* Check whether 'hardware' rotation (via frontend * gfx driver) is supported */ hw_rotate_enabled = environ_cb(RETRO_ENVIRONMENT_SET_ROTATION, (void*)&rot_angle); if (!hw_rotate_enabled && !rotate_buf) { #if defined(_3DS) rotate_buf = (uint16_t*)linearMemAlign(FB_WIDTH * FB_HEIGHT * sizeof(uint32_t), 128); #else rotate_buf = (uint16_t*)calloc(1, FB_WIDTH * FB_HEIGHT * sizeof(uint32_t)); #endif if (!rotate_buf) goto error; } rotate_tall = false; select_pressed_last_frame = false; rotate_joymap = 0; check_variables(false); /* Allocate an audio buffer of sufficient size * for the expected number of samples per frame * (size will be increased automatically if * configuration changes) */ audio_samples_buf_size = ((int32_t)(RETRO_SAMPLE_RATE / MEDNAFEN_CORE_TIMING_FPS) + 1) << 1; audio_samples_buf = (int16_t*)malloc(audio_samples_buf_size * sizeof(int16_t)); if (!audio_samples_buf) goto error; retro_60hz_init(); WSwan_SetPixelFormat(RETRO_PIX_DEPTH, mono_pal_start, mono_pal_end); WSwan_SetSoundRate(RETRO_SAMPLE_RATE); return true; error: if (surf) { if (surf->pixels) #if defined(_3DS) linearFree(surf->pixels); #else free(surf->pixels); #endif free(surf); } surf = NULL; if (rotate_buf) #if defined(_3DS) linearFree(rotate_buf); #else free(rotate_buf); #endif rotate_buf = NULL; if (audio_samples_buf) free(audio_samples_buf); audio_samples_buf = NULL; audio_samples_buf_size = 0; return false; } void retro_unload_game(void) { MDFNI_CloseGame(); if (surf) { if (surf->pixels) #if defined(_3DS) linearFree(surf->pixels); #else free(surf->pixels); #endif free(surf); } surf = NULL; if (rotate_buf) #if defined(_3DS) linearFree(rotate_buf); #else free(rotate_buf); #endif rotate_buf = NULL; if (audio_samples_buf) free(audio_samples_buf); audio_samples_buf = NULL; audio_samples_buf_size = 0; retro_60hz_deinit(); } static void update_input(void) { static unsigned map[2][11] = { { RETRO_DEVICE_ID_JOYPAD_UP, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_RIGHT, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_DOWN, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_LEFT, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_R2, /* Y Cursor UP vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_R, /* Y Cursor RIGHT vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_L2, /* Y Cursor DOWN vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_L, /* Y Cursor LEFT vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_START, RETRO_DEVICE_ID_JOYPAD_A, RETRO_DEVICE_ID_JOYPAD_B, }, { RETRO_DEVICE_ID_JOYPAD_Y, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_X, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_A, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_B, /* X Cursor horizontal-layout games */ RETRO_DEVICE_ID_JOYPAD_LEFT, /* Y Cursor UP vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_UP, /* Y Cursor RIGHT vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_RIGHT, /* Y Cursor DOWN vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_DOWN, /* Y Cursor LEFT vertical-layout games */ RETRO_DEVICE_ID_JOYPAD_START, RETRO_DEVICE_ID_JOYPAD_L, RETRO_DEVICE_ID_JOYPAD_R, } }; unsigned i; int16_t bitmask = 0; bool select_button; bool joymap; #ifdef MSB_FIRST union { uint8_t b[2]; uint16_t s; } u; #endif input_buf = 0; if (libretro_supports_bitmasks) bitmask = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); else { for (i = 0; i < (RETRO_DEVICE_ID_JOYPAD_R3+1); i++) bitmask |= input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, i) ? 1 << i : 0; } select_button = bitmask & (1 << RETRO_DEVICE_ID_JOYPAD_SELECT); if ((retro_display_rotation == DISPLAY_ROTATION_MANUAL) && select_button && !select_pressed_last_frame) { rotate_tall = !rotate_tall; rotate_display(); } select_pressed_last_frame = select_button; joymap = (rotate_joymap == 2) ? rotate_tall : (rotate_joymap ? true : false); for (i = 0; i < MAX_BUTTONS; i++) input_buf |= map[joymap][i] != -1u && ((1 << map[joymap][i]) & bitmask) ? (1 << i) : 0; #ifdef MSB_FIRST u.s = input_buf; input_buf = u.b[0] | u.b[1] << 8; #endif } void retro_run(void) { int total; unsigned width, height; int32 SoundBufSize; EmulateSpecStruct spec; bool updated = false; int skip_frame = 0; int update_sample_rate; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(false); update_sample_rate = update_audio; input_poll_cb(); update_input(); /* Check whether current frame should * be skipped */ if ((frameskip_type > 0) && retro_audio_buff_active) { switch (frameskip_type) { case 1: /* auto */ skip_frame = retro_audio_buff_underrun ? 1 : 0; break; case 2: /* manual */ skip_frame = (retro_audio_buff_occupancy < frameskip_threshold) ? 1 : 0; break; default: skip_frame = 0; break; } if (!skip_frame || (frameskip_counter >= FRAMESKIP_MAX)) { skip_frame = 0; frameskip_counter = 0; } else frameskip_counter++; } /* If frameskip settings have changed, update * frontend audio latency */ if (update_audio_latency) { environ_cb(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY, &audio_latency); update_audio_latency = false; } if (retro_60hz_enabled) { /* If we are running in 60Hz mode, then: * - Audio data must be buffered * - On every 4th call of retro_run(), an * extra frame must be emulated */ /* Emulate 'force skipped frame' */ if (retro_60hz_counter == 0) { Emulate(&spec, 1, update_sample_rate); update_sample_rate = 0; retro_60hz_cache_audio_samples(audio_samples_buf, spec.SoundBufSize); } /* Run 'regular' frame */ Emulate(&spec, skip_frame, update_sample_rate); retro_60hz_cache_audio_samples(audio_samples_buf, spec.SoundBufSize); retro_60hz_counter++; } else Emulate(&spec, skip_frame, update_sample_rate); if (update_audio || update_video) { struct retro_system_av_info system_av_info; retro_get_system_av_info(&system_av_info); environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &system_av_info); update_audio = false; update_video = false; } width = spec.DisplayRect.w; height = spec.DisplayRect.h; if (hw_rotate_enabled || !rotate_tall) { if (!skip_frame) video_cb(surf->pixels, width, height, FB_WIDTH * RETRO_PIX_BYTES); else video_cb(NULL, width, height, FB_WIDTH * RETRO_PIX_BYTES); } else { if (!skip_frame) { /* Perform software-based display rotation */ if (RETRO_PIX_BYTES == 4) ROTATE_PIXEL_BUF( uint32_t, surf->pixels, width, height, rotate_buf) else ROTATE_PIXEL_BUF( uint16_t, surf->pixels, width, height, rotate_buf) video_cb(rotate_buf, height, width, FB_HEIGHT * RETRO_PIX_BYTES); } else video_cb(NULL, height, width, FB_HEIGHT * RETRO_PIX_BYTES); } if (retro_60hz_enabled) { /* If we are running in 60Hz mode, then * read out the expected number of samples * on each frame plus empty the buffer at * the end of each 4/5 frame cycle */ int32_t frames_available = retro_60hz_audio.samples_buf_pos >> 1; int32_t frames_to_read = retro_60hz_audio.samples_per_frame; int32_t samples_to_read; frames_to_read = (frames_to_read > frames_available) ? frames_available : frames_to_read; if (retro_60hz_counter >= RETRO_60HZ_CYCLE_INDEX) { frames_to_read = frames_available; retro_60hz_counter = 0; } samples_to_read = frames_to_read << 1; for (total = 0; total < frames_to_read; ) total += audio_batch_cb( retro_60hz_audio.samples_buf + (total << 1), frames_to_read - total); /* Remove uploaded samples from the buffer */ if (frames_to_read < frames_available) memmove(retro_60hz_audio.samples_buf, retro_60hz_audio.samples_buf + samples_to_read, (retro_60hz_audio.samples_buf_pos - samples_to_read) * sizeof(int16_t)); retro_60hz_audio.samples_buf_pos -= samples_to_read; } else for (total = 0; total < spec.SoundBufSize; ) total += audio_batch_cb( audio_samples_buf + (total << 1), spec.SoundBufSize - total); } void retro_get_system_info(struct retro_system_info *info) { memset(info, 0, sizeof(*info)); info->library_name = MEDNAFEN_CORE_NAME; #ifndef GIT_VERSION #define GIT_VERSION "" #endif info->library_version = MEDNAFEN_CORE_VERSION GIT_VERSION; info->need_fullpath = false; info->valid_extensions = MEDNAFEN_CORE_EXTENSIONS; info->block_extract = false; } void retro_get_system_av_info(struct retro_system_av_info *info) { memset(info, 0, sizeof(*info)); info->timing.fps = retro_60hz_enabled ? RETRO_60HZ_FPS : MEDNAFEN_CORE_TIMING_FPS; info->timing.sample_rate = RETRO_SAMPLE_RATE; if (hw_rotate_enabled || !rotate_tall) { info->geometry.base_width = MEDNAFEN_CORE_GEOMETRY_BASE_W; info->geometry.base_height = MEDNAFEN_CORE_GEOMETRY_BASE_H; } else { info->geometry.base_width = MEDNAFEN_CORE_GEOMETRY_BASE_H; info->geometry.base_height = MEDNAFEN_CORE_GEOMETRY_BASE_W; } info->geometry.max_width = MEDNAFEN_CORE_GEOMETRY_MAX_W; if (hw_rotate_enabled) info->geometry.max_height = MEDNAFEN_CORE_GEOMETRY_MAX_H; else info->geometry.max_height = MEDNAFEN_CORE_GEOMETRY_MAX_W; if (rotate_tall) info->geometry.aspect_ratio = 1.0f / MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO; else info->geometry.aspect_ratio = MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO; } void retro_deinit(void) { if (surf) { if (surf->pixels) #if defined(_3DS) linearFree(surf->pixels); #else free(surf->pixels); #endif free(surf); } surf = NULL; if (rotate_buf) #if defined(_3DS) linearFree(rotate_buf); #else free(rotate_buf); #endif rotate_buf = NULL; if (audio_samples_buf) free(audio_samples_buf); audio_samples_buf = NULL; audio_samples_buf_size = 0; retro_60hz_deinit(); retro_60hz_enabled = false; libretro_supports_bitmasks = false; } unsigned retro_get_region(void) { return RETRO_REGION_NTSC; /* No real regions of sorts for this handheld, so just set to NTSC. */ } unsigned retro_api_version(void) { return RETRO_API_VERSION; } void retro_set_controller_port_device(unsigned in_port, unsigned device) { } void retro_set_environment(retro_environment_t cb) { bool option_cats_supported = false; environ_cb = cb; libretro_set_core_options(environ_cb, &option_cats_supported); } void retro_set_audio_sample(retro_audio_sample_t 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; } size_t retro_serialize_size(void) { StateMem st; st.data = NULL; st.loc = 0; st.len = 0; st.malloced = 0; st.initial_malloc = 0; if (!MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL)) return 0; free(st.data); return st.len; } bool retro_serialize(void *data, size_t size) { StateMem st; bool ret = false; uint8_t *_dat = (uint8_t*)malloc(size); if (!_dat) return false; /* Mednafen can realloc the buffer so we need to ensure this is safe. */ st.data = _dat; st.loc = 0; st.len = 0; st.malloced = size; st.initial_malloc = 0; ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL); memcpy(data, st.data, size); free(st.data); return ret; } bool retro_unserialize(const void *data, size_t size) { StateMem st; st.data = (uint8_t*)data; st.loc = 0; st.len = size; st.malloced = 0; st.initial_malloc = 0; return MDFNSS_LoadSM(&st, 0, 0); } void *retro_get_memory_data(unsigned type) { switch (type) { case RETRO_MEMORY_SAVE_RAM: if (eeprom_size) return (uint8_t*)wsEEPROM; else if (SRAMSize) return wsSRAM; break; case RETRO_MEMORY_SYSTEM_RAM: return (uint8_t*)wsRAM; default: break; } return NULL; } size_t retro_get_memory_size(unsigned type) { switch (type) { case RETRO_MEMORY_SAVE_RAM: if (eeprom_size) return eeprom_size; else if (SRAMSize) return SRAMSize; break; case RETRO_MEMORY_SYSTEM_RAM: return wsRAMSize; default: break; } return 0; } void retro_cheat_reset(void) { } void retro_cheat_set(unsigned a, bool b, const char *c) { }