/* This file is part of Flycast. Flycast 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. Flycast 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 Flycast. If not, see . */ #include #include #include #include "types.h" #ifndef _WIN32 #include #endif #include #ifdef __SWITCH__ #include #include #include "nswitch.h" #endif #include #include #include #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) #include #include "wsi/gl_context.h" #endif #ifdef HAVE_VULKAN #include "rend/vulkan/vulkan_context.h" #include #endif #ifdef HAVE_D3D11 #include #include "rend/dx11/dx11context_lr.h" #endif #include "emulator.h" #include "hw/sh4/sh4_mem.h" #include "hw/sh4/sh4_sched.h" #include "hw/sh4/dyna/blockmanager.h" #include "keyboard_map.h" #include "hw/maple/maple_cfg.h" #include "hw/maple/maple_if.h" #include "hw/maple/maple_cfg.h" #include "hw/pvr/spg.h" #include "hw/naomi/naomi_cart.h" #include "hw/naomi/card_reader.h" #include "imgread/common.h" #include "LogManager.h" #include "cheats.h" #include "rend/CustomTexture.h" #include "rend/osd.h" #include "cfg/option.h" #include "version.h" #include "rend/transform_matrix.h" constexpr char slash = path_default_slash_c(); #define RETRO_DEVICE_TWINSTICK RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_JOYPAD, 1 ) #define RETRO_DEVICE_TWINSTICK_SATURN RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_JOYPAD, 2 ) #define RETRO_DEVICE_ASCIISTICK RETRO_DEVICE_SUBCLASS( RETRO_DEVICE_JOYPAD, 3 ) #define RETRO_ENVIRONMENT_RETROARCH_START_BLOCK 0x800000 #define RETRO_ENVIRONMENT_SET_SAVE_STATE_IN_BACKGROUND (2 | RETRO_ENVIRONMENT_RETROARCH_START_BLOCK) /* bool * -- * Boolean value that tells the front end to save states in the * background or not. */ #define RETRO_ENVIRONMENT_POLL_TYPE_OVERRIDE (4 | RETRO_ENVIRONMENT_RETROARCH_START_BLOCK) /* unsigned * -- * Tells the frontend to override the poll type behavior. * Allows the frontend to influence the polling behavior of the * frontend. * * Will be unset when retro_unload_game is called. * * 0 - Don't Care, no changes, frontend still determines polling type behavior. * 1 - Early * 2 - Normal * 3 - Late */ #include "libretro_core_option_defines.h" #include "libretro_core_options.h" #include "vmu_xhair.h" extern void retro_audio_init(void); extern void retro_audio_deinit(void); extern void retro_audio_flush_buffer(void); extern void retro_audio_upload(void); std::string arcadeFlashPath; static bool boot_to_bios; static bool devices_need_refresh = false; static int device_type[4] = {-1,-1,-1,-1}; static int astick_deadzone = 0; static int trigger_deadzone = 0; static bool digital_triggers = false; static bool allow_service_buttons = false; static bool haveCardReader; static bool libretro_supports_bitmasks = false; static bool categoriesSupported = false; static bool platformIsDreamcast = true; static bool platformIsArcade = false; static bool threadedRenderingEnabled = true; static bool oitEnabled = false; static bool autoSkipFrameEnabled = false; #ifdef _OPENMP static bool textureUpscaleEnabled = false; #endif static bool vmuScreenSettingsShown = true; static bool lightgunSettingsShown = true; u32 kcode[4] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; u8 rt[4]; u8 lt[4]; u32 vks[4]; s8 joyx[4], joyy[4]; s8 joyrx[4], joyry[4]; // Mouse buttons // bit 0: Button C // bit 1: Right button (B) // bit 2: Left button (A) // bit 3: Wheel button u8 mo_buttons[4] = { 0xFF, 0xFF, 0xFF, 0xFF }; // Relative mouse coordinates [-512:511] float mo_x_delta[4]; float mo_y_delta[4]; float mo_wheel_delta[4]; std::mutex relPosMutex; // Absolute mouse coordinates // Range [0:639] [0:479] // but may be outside this range if the pointer is offscreen or outside the 4:3 window. s32 mo_x_abs[4]; s32 mo_y_abs[4]; static bool enable_purupuru = true; static u32 vib_stop_time[4]; static double vib_strength[4]; static double vib_delta[4]; unsigned per_content_vmus = 0; static bool first_run = true; static bool rotate_screen; static bool rotate_game; static int framebufferWidth; static int framebufferHeight; static int maxFramebufferWidth; static int maxFramebufferHeight; static float framebufferAspectRatio = 4.f / 3.f; float libretro_expected_audio_samples_per_run; unsigned libretro_vsync_swap_interval = 1; bool libretro_detect_vsync_swap_interval = false; static retro_perf_callback perf_cb; static retro_get_cpu_features_t perf_get_cpu_features_cb; // Callbacks static retro_log_printf_t log_cb; static retro_video_refresh_t video_cb; static retro_input_poll_t poll_cb; static retro_input_state_t input_cb; retro_audio_sample_batch_t audio_batch_cb; retro_environment_t environ_cb; static retro_rumble_interface rumble; static void refresh_devices(bool first_startup); static void init_disk_control_interface(); static bool read_m3u(const char *file); void UpdateInputState(); void gui_display_notification(const char *msg, int duration); static void updateVibration(u32 port, float power, float inclination, u32 durationMs); static std::string game_data; static char g_base_name[128]; static char game_dir[1024]; char game_dir_no_slash[1024]; char vmu_dir_no_slash[PATH_MAX]; char content_name[PATH_MAX]; static char g_roms_dir[PATH_MAX]; static std::mutex mtx_serialization; static bool gl_ctx_resetting = false; static bool is_dupe; static u64 startTime; // Disk swapping static struct retro_disk_control_callback retro_disk_control_cb; static struct retro_disk_control_ext_callback retro_disk_control_ext_cb; static unsigned disk_initial_index = 0; static std::string disk_initial_path; static unsigned disk_index = 0; static std::vector disk_paths; static std::vector disk_labels; static bool disc_tray_open = false; void UpdateInputState(); static bool set_variable_visibility(void); void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; } void retro_set_audio_sample(retro_audio_sample_t cb) { // Nothing to do here } 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) { poll_cb = cb; } void retro_set_input_state(retro_input_state_t cb) { input_cb = cb; } static void input_set_deadzone_stick(int percent) { if (percent >= 0 && percent <= 100) astick_deadzone = (int)(percent * 0.01f * 0x8000); } static void input_set_deadzone_trigger(int percent) { if (percent >= 0 && percent <= 100) trigger_deadzone = (int)(percent * 0.01f * 0x8000); } void retro_set_environment(retro_environment_t cb) { environ_cb = cb; // An annoyance: retro_set_environment() can be called // multiple times, and depending upon the current frontend // state various environment callbacks may be disabled. // This means the reported 'categories_supported' status // may change on subsequent iterations. We therefore have // to record whether 'categories_supported' is true on any // iteration, and latch the result bool optionCategoriesSupported = false; libretro_set_core_options(environ_cb, &optionCategoriesSupported); categoriesSupported |= optionCategoriesSupported; struct retro_core_options_update_display_callback update_display_cb; update_display_cb.callback = set_variable_visibility; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb); static const struct retro_controller_description ports_default[] = { { "Controller", RETRO_DEVICE_JOYPAD }, { "Arcade Stick", RETRO_DEVICE_ASCIISTICK }, { "Keyboard", RETRO_DEVICE_KEYBOARD }, { "Mouse", RETRO_DEVICE_MOUSE }, { "Light Gun", RETRO_DEVICE_LIGHTGUN }, { "Twin Stick", RETRO_DEVICE_TWINSTICK }, { "Saturn Twin-Stick", RETRO_DEVICE_TWINSTICK_SATURN }, { 0 }, }; static const struct retro_controller_info ports[] = { { ports_default, 7 }, { ports_default, 7 }, { ports_default, 7 }, { ports_default, 7 }, { 0 }, }; environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); } static void retro_keyboard_event(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); // Now comes the interesting stuff void retro_init() { static bool emuInited; // Logging struct retro_log_callback log; if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) log_cb = log.log; else log_cb = NULL; LogManager::Init((void *)log_cb); NOTICE_LOG(BOOT, "retro_init"); if (environ_cb(RETRO_ENVIRONMENT_GET_PERF_INTERFACE, &perf_cb)) perf_get_cpu_features_cb = perf_cb.get_cpu_features; else perf_get_cpu_features_cb = NULL; // Set color mode unsigned color_mode = RETRO_PIXEL_FORMAT_XRGB8888; environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &color_mode); init_kb_map(); struct retro_keyboard_callback kb_callback = { &retro_keyboard_event }; environ_cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &kb_callback); if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL)) libretro_supports_bitmasks = true; init_disk_control_interface(); retro_audio_init(); if (!_vmem_reserve()) ERROR_LOG(VMEM, "Cannot reserve memory space"); os_InstallFaultHandler(); MapleConfigMap::UpdateVibration = updateVibration; #if defined(__GNUC__) && defined(__linux__) && !defined(__ANDROID__) if (!emuInited) #endif emu.init(); emuInited = true; } void retro_deinit() { INFO_LOG(COMMON, "retro_deinit"); first_run = true; //When auto-save states are enabled this is needed to prevent the core from shutting down before //any save state actions are still running - which results in partial saves { std::lock_guard lock(mtx_serialization); } os_UninstallFaultHandler(); #if defined(__GNUC__) && defined(__linux__) && !defined(__ANDROID__) _vmem_release(); #else emu.term(); #endif libretro_supports_bitmasks = false; categoriesSupported = false; platformIsDreamcast = true; platformIsArcade = false; threadedRenderingEnabled = true; oitEnabled = false; autoSkipFrameEnabled = false; #ifdef _OPENMP textureUpscaleEnabled = false; #endif vmuScreenSettingsShown = true; lightgunSettingsShown = true; libretro_vsync_swap_interval = 1; libretro_detect_vsync_swap_interval = false; LogManager::Shutdown(); retro_audio_deinit(); } static bool set_variable_visibility(void) { struct retro_core_option_display option_display; struct retro_variable var; bool updated = false; bool platformWasDreamcast = platformIsDreamcast; bool platformWasArcade = platformIsArcade; platformIsDreamcast = settings.platform.isConsole(); platformIsArcade = settings.platform.isArcade(); // Show/hide platform-dependent options if (first_run || (platformIsDreamcast != platformWasDreamcast) || (platformIsArcade != platformWasArcade)) { // Show/hide NAOMI/Atomiswave options option_display.visible = platformIsArcade; option_display.key = CORE_OPTION_NAME "_allow_service_buttons"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); // Show/hide Dreamcast options option_display.visible = platformIsDreamcast; option_display.key = CORE_OPTION_NAME "_boot_to_bios"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_hle_bios"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_gdrom_fast_loading"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_cable_type"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_broadcast"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_language"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_force_wince"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_enable_purupuru"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_per_content_vmus"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); vmuScreenSettingsShown = option_display.visible; for (unsigned i = 0; i < 4; i++) { char key[256]; option_display.key = key; snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_display"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_position"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_size_mult"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_pixel_on_color"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_pixel_off_color"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_opacity"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); } // Show/hide manual option visibility toggles // > Only show if categories are not supported option_display.visible = platformIsDreamcast && !categoriesSupported; option_display.key = CORE_OPTION_NAME "_show_vmu_screen_settings"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); updated = true; } // Show/hide additional manual option visibility toggles // > Only show if categories are not supported if (first_run) { option_display.visible = !categoriesSupported; option_display.key = CORE_OPTION_NAME "_show_lightgun_settings"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); updated = true; } // Show/hide settings-dependent options // Only for threaded renderer bool threadedRenderingWasEnabled = threadedRenderingEnabled; threadedRenderingEnabled = true; var.key = CORE_OPTION_NAME "_threaded_rendering"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "disabled")) threadedRenderingEnabled = false; if (first_run || (threadedRenderingEnabled != threadedRenderingWasEnabled)) { option_display.visible = threadedRenderingEnabled; option_display.key = CORE_OPTION_NAME "_auto_skip_frame"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); updated = true; } #if defined(HAVE_OIT) || defined(HAVE_VULKAN) || defined(HAVE_D3D11) // Only for per-pixel renderers bool oitWasEnabled = oitEnabled; oitEnabled = false; var.key = CORE_OPTION_NAME "_alpha_sorting"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "per-pixel (accurate)")) oitEnabled = true; if (first_run || (oitEnabled != oitWasEnabled)) { option_display.visible = oitEnabled; option_display.key = CORE_OPTION_NAME "_oit_abuffer_size"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); option_display.key = CORE_OPTION_NAME "_oit_layers"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); updated = true; } #endif #ifdef _OPENMP // Only if texture upscaling is enabled bool textureUpscaleWasEnabled = textureUpscaleEnabled; textureUpscaleEnabled = false; var.key = CORE_OPTION_NAME "_texupscale"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strcmp(var.value, "off")) textureUpscaleEnabled = true; if (first_run || (textureUpscaleEnabled != textureUpscaleWasEnabled)) { option_display.visible = textureUpscaleEnabled; option_display.key = CORE_OPTION_NAME "_texupscale_max_filtered_texture_size"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); updated = true; } #endif // Only if automatic frame skipping is disabled bool autoSkipFrameWasEnabled = autoSkipFrameEnabled; autoSkipFrameEnabled = false; var.key = CORE_OPTION_NAME "_auto_skip_frame"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strcmp(var.value, "disabled")) autoSkipFrameEnabled = true; if (first_run || (autoSkipFrameEnabled != autoSkipFrameWasEnabled) || (threadedRenderingEnabled != threadedRenderingWasEnabled)) { option_display.visible = (!autoSkipFrameEnabled || !threadedRenderingEnabled); option_display.key = CORE_OPTION_NAME "_detect_vsync_swap_interval"; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); updated = true; } // If categories are supported, no further action is required if (categoriesSupported) return updated; // Show/hide VMU screen options bool vmuScreenSettingsWereShown = vmuScreenSettingsShown; if (platformIsDreamcast) { vmuScreenSettingsShown = true; var.key = CORE_OPTION_NAME "_show_vmu_screen_settings"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "disabled")) vmuScreenSettingsShown = false; } else vmuScreenSettingsShown = false; if (first_run || (vmuScreenSettingsShown != vmuScreenSettingsWereShown)) { option_display.visible = vmuScreenSettingsShown; for (unsigned i = 0; i < 4; i++) { char key[256]; option_display.key = key; snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_display"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_position"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_size_mult"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_pixel_on_color"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_pixel_off_color"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_vmu", i + 1, "_screen_opacity"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); } updated = true; } // Show/hide light gun options bool lightgunSettingsWereShown = lightgunSettingsShown; lightgunSettingsShown = true; var.key = CORE_OPTION_NAME "_show_lightgun_settings"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "disabled")) lightgunSettingsShown = false; if (first_run || (lightgunSettingsShown != lightgunSettingsWereShown)) { option_display.visible = lightgunSettingsShown; for (unsigned i = 0; i < 4; i++) { char key[256]; option_display.key = key; snprintf(key, sizeof(key), "%s%u%s", CORE_OPTION_NAME "_lightgun", i + 1, "_crosshair"); environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display); } updated = true; } return updated; } static void setGameGeometry(retro_game_geometry& geometry) { geometry.aspect_ratio = framebufferAspectRatio; if (rotate_screen) geometry.aspect_ratio = 1 / geometry.aspect_ratio; geometry.max_width = std::max(framebufferHeight * 16 / 9, framebufferWidth); geometry.max_height = geometry.max_width; // Avoid gigantic window size at startup geometry.base_width = 640; geometry.base_height = 480; } void setAVInfo(retro_system_av_info& avinfo) { double sample_rate = 44100.0; double fps = SPG_CONTROL.NTSC ? 59.94 : SPG_CONTROL.PAL ? 50.0 : 60.0; setGameGeometry(avinfo.geometry); avinfo.timing.sample_rate = sample_rate; avinfo.timing.fps = fps / (double)libretro_vsync_swap_interval; libretro_expected_audio_samples_per_run = sample_rate / fps; } void retro_resize_renderer(int w, int h, float aspectRatio) { if (w == framebufferWidth && h == framebufferHeight && aspectRatio == framebufferAspectRatio) return; framebufferWidth = w; framebufferHeight = h; framebufferAspectRatio = aspectRatio; bool avinfoNeeded = framebufferHeight > maxFramebufferHeight || framebufferWidth > maxFramebufferWidth; maxFramebufferHeight = std::max(maxFramebufferHeight, framebufferHeight); maxFramebufferWidth = std::max(maxFramebufferWidth, framebufferWidth); if (avinfoNeeded) { retro_system_av_info avinfo; setAVInfo(avinfo); environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo); } else { retro_game_geometry geometry; setGameGeometry(geometry); environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geometry); } } static void setRotation() { int rotation = 0; if (rotate_game) { if (!rotate_screen) rotation = 1; rotate_screen = !rotate_screen; } else { if (rotate_screen) rotation = 3; } environ_cb(RETRO_ENVIRONMENT_SET_ROTATION, &rotation); } static void update_variables(bool first_startup) { bool wasThreadedRendering = config::ThreadedRendering; bool prevRotateScreen = rotate_screen; bool prevDetectVsyncSwapInterval = libretro_detect_vsync_swap_interval; config::Settings::instance().setRetroEnvironment(environ_cb); config::Settings::instance().setOptionDefinitions(option_defs_us); config::Settings::instance().load(false); retro_variable var; var.key = CORE_OPTION_NAME "_per_content_vmus"; unsigned previous_per_content_vmus = per_content_vmus; per_content_vmus = 0; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("VMU A1", var.value)) per_content_vmus = 1; else if (!strcmp("All VMUs", var.value)) per_content_vmus = 2; } if (!first_startup && per_content_vmus != previous_per_content_vmus && settings.platform.isConsole()) { // Recreate the VMUs so that the save location is taken into account. // Don't do this at startup because we don't know the system type yet // and the VMUs haven't been created anyway maple_ReconnectDevices(); } var.key = CORE_OPTION_NAME "_screen_rotation"; rotate_screen = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp("vertical", var.value)) rotate_screen = true; var.key = CORE_OPTION_NAME "_internal_resolution"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { char str[100]; snprintf(str, sizeof(str), "%s", var.value); char *pch = strtok(str, "x"); pch = strtok(NULL, "x"); if (pch != nullptr) config::RenderResolution = strtoul(pch, NULL, 0); DEBUG_LOG(COMMON, "Got height: %u", (int)config::RenderResolution); } var.key = CORE_OPTION_NAME "_boot_to_bios"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) boot_to_bios = true; else if (!strcmp(var.value, "disabled")) boot_to_bios = false; } else boot_to_bios = false; var.key = CORE_OPTION_NAME "_alpha_sorting"; var.value = nullptr; RenderType previous_renderer = config::RendererType; environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var); if (var.value != nullptr && !strcmp(var.value, "per-pixel (accurate)")) { switch (config::RendererType) { case RenderType::Vulkan: config::RendererType = RenderType::Vulkan_OIT; break; case RenderType::DirectX11: config::RendererType = RenderType::DirectX11_OIT; break; case RenderType::OpenGL: config::RendererType = RenderType::OpenGL_OIT; break; default: break; } config::PerStripSorting = false; // Not used } else { switch (config::RendererType) { case RenderType::Vulkan_OIT: config::RendererType = RenderType::Vulkan; break; case RenderType::DirectX11_OIT: config::RendererType = RenderType::DirectX11; break; case RenderType::OpenGL_OIT: config::RendererType = RenderType::OpenGL; break; default: break; } config::PerStripSorting = var.value != nullptr && !strcmp(var.value, "per-strip (fast, least accurate)"); } if (!first_startup && previous_renderer != config::RendererType) { rend_term_renderer(); rend_init_renderer(); } #if defined(HAVE_OIT) || defined(HAVE_VULKAN) || defined(HAVE_D3D11) var.key = CORE_OPTION_NAME "_oit_abuffer_size"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "512MB")) config::PixelBufferSize = 0x20000000u; else if (!strcmp(var.value, "1GB")) config::PixelBufferSize = 0x40000000u; else if (!strcmp(var.value, "2GB")) config::PixelBufferSize = 0x7ff00000u; else if (!strcmp(var.value, "4GB")) config::PixelBufferSize = 0xFFFFFFFFu; else config::PixelBufferSize = 0x20000000u; } else config::PixelBufferSize = 0x20000000u; #endif if ((config::AutoSkipFrame != 0) && config::ThreadedRendering) libretro_detect_vsync_swap_interval = false; else { var.key = CORE_OPTION_NAME "_detect_vsync_swap_interval"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp(var.value, "enabled")) libretro_detect_vsync_swap_interval = true; else if (!strcmp(var.value, "disabled")) libretro_detect_vsync_swap_interval = false; } else libretro_detect_vsync_swap_interval = false; } if (first_startup) { if (config::ThreadedRendering) { bool save_state_in_background = true ; unsigned poll_type_early = 1; /* POLL_TYPE_EARLY */ environ_cb(RETRO_ENVIRONMENT_SET_SAVE_STATE_IN_BACKGROUND, &save_state_in_background); environ_cb(RETRO_ENVIRONMENT_POLL_TYPE_OVERRIDE, &poll_type_early); } config::Cable = 3; var.key = CORE_OPTION_NAME "_cable_type"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("VGA", var.value)) config::Cable = 0; else if (!strcmp("TV (RGB)", var.value)) config::Cable = 2; } } var.key = CORE_OPTION_NAME "_enable_purupuru"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (enable_purupuru != (strcmp("enabled", var.value) == 0) && settings.platform.isConsole()) { enable_purupuru = strcmp("enabled", var.value) == 0; for (int i = 0; i < MAPLE_PORTS; i++) { if (config::MapleMainDevices[i] == MDT_SegaController) config::MapleExpansionDevices[i][1] = enable_purupuru ? MDT_PurupuruPack : MDT_SegaVMU; else if (config::MapleMainDevices[i] == MDT_LightGun || config::MapleMainDevices[i] == MDT_TwinStick || config::MapleMainDevices[i] == MDT_AsciiStick) config::MapleExpansionDevices[i][0] = enable_purupuru ? MDT_PurupuruPack : MDT_SegaVMU; } if (!first_startup) maple_ReconnectDevices(); } } var.key = CORE_OPTION_NAME "_analog_stick_deadzone"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) input_set_deadzone_stick( atoi( var.value ) ); var.key = CORE_OPTION_NAME "_trigger_deadzone"; var.value = NULL; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) input_set_deadzone_trigger( atoi( var.value ) ); var.key = CORE_OPTION_NAME "_digital_triggers"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("enabled", var.value)) digital_triggers = true; else digital_triggers = false; } else digital_triggers = false; var.key = CORE_OPTION_NAME "_allow_service_buttons"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("enabled", var.value)) allow_service_buttons = true; else allow_service_buttons = false; } else allow_service_buttons = false; char key[256]; key[0] = '\0'; var.key = key ; for (int i = 0 ; i < 4 ; i++) { lightgun_params[i].offscreen = true; lightgun_params[i].x = 0; lightgun_params[i].y = 0; lightgun_params[i].dirty = true; lightgun_params[i].colour = LIGHTGUN_COLOR_OFF; snprintf(key, sizeof(key), CORE_OPTION_NAME "_lightgun%d_crosshair", i+1) ; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value ) { if (!strcmp("disabled", var.value)) lightgun_params[i].colour = LIGHTGUN_COLOR_OFF; else if (!strcmp("White", var.value)) lightgun_params[i].colour = LIGHTGUN_COLOR_WHITE; else if (!strcmp("Red", var.value)) lightgun_params[i].colour = LIGHTGUN_COLOR_RED; else if (!strcmp("Green", var.value)) lightgun_params[i].colour = LIGHTGUN_COLOR_GREEN; else if (!strcmp("Blue", var.value)) lightgun_params[i].colour = LIGHTGUN_COLOR_BLUE; } if (lightgun_params[i].colour == LIGHTGUN_COLOR_OFF) config::CrosshairColor[i] = 0; else config::CrosshairColor[i] = lightgun_palette[lightgun_params[i].colour * 3] | (lightgun_palette[lightgun_params[i].colour * 3 + 1] << 8) | (lightgun_palette[lightgun_params[i].colour * 3 + 2] << 16) | 0xff000000; vmu_lcd_status[i * 2] = false; vmu_lcd_changed[i * 2] = true; vmu_screen_params[i].vmu_screen_position = UPPER_LEFT; vmu_screen_params[i].vmu_screen_size_mult = 1; vmu_screen_params[i].vmu_pixel_on_R = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_ON].r; vmu_screen_params[i].vmu_pixel_on_G = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_ON].g; vmu_screen_params[i].vmu_pixel_on_B = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_ON].b; vmu_screen_params[i].vmu_pixel_off_R = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_OFF].r; vmu_screen_params[i].vmu_pixel_off_G = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_OFF].g; vmu_screen_params[i].vmu_pixel_off_B = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_OFF].b; vmu_screen_params[i].vmu_screen_opacity = 0xFF; snprintf(key, sizeof(key), CORE_OPTION_NAME "_vmu%d_screen_display", i+1); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp("enabled", var.value) ) vmu_lcd_status[i * 2] = true; snprintf(key, sizeof(key), CORE_OPTION_NAME "_vmu%d_screen_position", i+1); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("Upper Left", var.value)) vmu_screen_params[i].vmu_screen_position = UPPER_LEFT; else if (!strcmp("Upper Right", var.value)) vmu_screen_params[i].vmu_screen_position = UPPER_RIGHT; else if (!strcmp("Lower Left", var.value)) vmu_screen_params[i].vmu_screen_position = LOWER_LEFT; else if (!strcmp("Lower Right", var.value)) vmu_screen_params[i].vmu_screen_position = LOWER_RIGHT; } snprintf(key, sizeof(key), CORE_OPTION_NAME "_vmu%d_screen_size_mult", i+1); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("1x", var.value)) vmu_screen_params[i].vmu_screen_size_mult = 1; else if (!strcmp("2x", var.value)) vmu_screen_params[i].vmu_screen_size_mult = 2; else if (!strcmp("3x", var.value)) vmu_screen_params[i].vmu_screen_size_mult = 3; else if (!strcmp("4x", var.value)) vmu_screen_params[i].vmu_screen_size_mult = 4; else if (!strcmp("5x", var.value)) vmu_screen_params[i].vmu_screen_size_mult = 5; } snprintf(key, sizeof(key), CORE_OPTION_NAME "_vmu%d_screen_opacity", i + 1); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (!strcmp("100%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 255; else if (!strcmp("90%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 9*25.5; else if (!strcmp("80%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 8*25.5; else if (!strcmp("70%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 7*25.5; else if (!strcmp("60%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 6*25.5; else if (!strcmp("50%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 5*25.5; else if (!strcmp("40%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 4*25.5; else if (!strcmp("30%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 3*25.5; else if (!strcmp("20%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 2*25.5; else if (!strcmp("10%", var.value)) vmu_screen_params[i].vmu_screen_opacity = 1*25.5; } snprintf(key, sizeof(key), CORE_OPTION_NAME "_vmu%d_pixel_on_color", i + 1); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strlen(var.value)>1) { int color_idx = atoi(var.value+(strlen(var.value)-2)); vmu_screen_params[i].vmu_pixel_on_R = VMU_SCREEN_COLOR_MAP[color_idx].r; vmu_screen_params[i].vmu_pixel_on_G = VMU_SCREEN_COLOR_MAP[color_idx].g; vmu_screen_params[i].vmu_pixel_on_B = VMU_SCREEN_COLOR_MAP[color_idx].b; } snprintf(key, sizeof(key), CORE_OPTION_NAME "_vmu%d_pixel_off_color", i+1); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strlen(var.value)>1) { int color_idx = atoi(var.value+(strlen(var.value)-2)); vmu_screen_params[i].vmu_pixel_off_R = VMU_SCREEN_COLOR_MAP[color_idx].r; vmu_screen_params[i].vmu_pixel_off_G = VMU_SCREEN_COLOR_MAP[color_idx].g; vmu_screen_params[i].vmu_pixel_off_B = VMU_SCREEN_COLOR_MAP[color_idx].b; } } set_variable_visibility(); if (!first_startup) { if (wasThreadedRendering != config::ThreadedRendering) { config::ThreadedRendering = wasThreadedRendering; emu.stop(); config::ThreadedRendering = !wasThreadedRendering; emu.start(); } if (rotate_screen != (prevRotateScreen ^ rotate_game)) { setRotation(); retro_game_geometry geometry; setGameGeometry(geometry); environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geometry); } else rotate_screen ^= rotate_game; if (rotate_game) config::Widescreen.override(false); if ((libretro_detect_vsync_swap_interval != prevDetectVsyncSwapInterval) && !libretro_detect_vsync_swap_interval && (libretro_vsync_swap_interval != 1)) { libretro_vsync_swap_interval = 1; retro_system_av_info avinfo; setAVInfo(avinfo); environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avinfo); } } } void retro_run() { bool updated = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) update_variables(false); if (devices_need_refresh) refresh_devices(false); #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) if (isOpenGL(config::RendererType)) glsm_ctl(GLSM_CTL_STATE_BIND, nullptr); #endif // On the first call, we start the emulator if (first_run) emu.start(); poll_cb(); UpdateInputState(); bool fastforward = false; if (environ_cb(RETRO_ENVIRONMENT_GET_FASTFORWARDING, &fastforward)) settings.input.fastForwardMode = fastforward; is_dupe = true; try { if (config::ThreadedRendering) { // Render for (int i = 0; i < 5 && is_dupe; i++) is_dupe = !emu.render(); } else { startTime = sh4_sched_now64(); emu.render(); } } catch (const FlycastException& e) { ERROR_LOG(COMMON, "%s", e.what()); gui_display_notification(e.what(), 5000); environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); } #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) if (isOpenGL(config::RendererType)) glsm_ctl(GLSM_CTL_STATE_UNBIND, nullptr); #endif video_cb(is_dupe ? 0 : RETRO_HW_FRAME_BUFFER_VALID, framebufferWidth, framebufferHeight, 0); if (!config::ThreadedRendering || config::LimitFPS) retro_audio_upload(); else retro_audio_flush_buffer(); first_run = false; } static bool loadGame() { try { emu.loadGame(game_data.c_str()); } catch (const FlycastException& e) { ERROR_LOG(BOOT, "%s", e.what()); gui_display_notification(e.what(), 5000); return false; } return true; } void retro_reset() { std::lock_guard lock(mtx_serialization); emu.unloadGame(); config::ScreenStretching = 100; loadGame(); if (rotate_game) config::Widescreen.override(false); config::Rotate90 = false; retro_game_geometry geometry; setGameGeometry(geometry); environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geometry); blankVmus(); retro_audio_flush_buffer(); emu.start(); } #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) static void context_reset() { INFO_LOG(RENDERER, "GL context_reset"); gl_ctx_resetting = false; glsm_ctl(GLSM_CTL_STATE_CONTEXT_RESET, NULL); glsm_ctl(GLSM_CTL_STATE_SETUP, NULL); rend_term_renderer(); theGLContext.init(); rend_init_renderer(); } static void context_destroy() { gl_ctx_resetting = true; rend_term_renderer(); glsm_ctl(GLSM_CTL_STATE_CONTEXT_DESTROY, NULL); } #endif static void extract_directory(char *buf, const char *path, size_t size) { strncpy(buf, path, size - 1); buf[size - 1] = '\0'; char *base = find_last_slash(buf); if (base) *base = '\0'; else strncpy(buf, ".", size - 1); } static uint32_t map_gamepad_button(unsigned device, unsigned id) { static const uint32_t dc_joymap[] = { /* JOYPAD_B */ DC_BTN_A, /* JOYPAD_Y */ DC_BTN_X, /* JOYPAD_SELECT */ 0, /* JOYPAD_START */ DC_BTN_START, /* JOYPAD_UP */ DC_DPAD_UP, /* JOYPAD_DOWN */ DC_DPAD_DOWN, /* JOYPAD_LEFT */ DC_DPAD_LEFT, /* JOYPAD_RIGHT */ DC_DPAD_RIGHT, /* JOYPAD_A */ DC_BTN_B, /* JOYPAD_X */ DC_BTN_Y, }; static const uint32_t dc_lg_joymap[] = { /* deprecated */ 0, /* deprecated */ 0, /* LIGHTGUN_TRIGGER */ DC_BTN_A, /* LIGHTGUN_AUX_A */ DC_BTN_B, /* LIGHTGUN_AUX_B */ 0, /* deprecated */ 0, /* LIGHTGUN_START */ DC_BTN_START, /* LIGHTGUN_SELECT */ 0, /* LIGHTGUN_AUX_C */ 0, /* LIGHTGUN_UP */ DC_DPAD_UP, /* LIGHTGUN_DOWN */ DC_DPAD_DOWN, /* LIGHTGUN_LEFT */ DC_DPAD_LEFT, /* LIGHTGUN_RIGHT */ DC_DPAD_RIGHT, }; static const uint32_t aw_joymap[] = { /* JOYPAD_B */ AWAVE_BTN0_KEY, /* BTN1 */ /* JOYPAD_Y */ AWAVE_BTN2_KEY, /* BTN3 */ /* JOYPAD_SELECT */ AWAVE_COIN_KEY, /* JOYPAD_START */ AWAVE_START_KEY, /* JOYPAD_UP */ AWAVE_UP_KEY, /* JOYPAD_DOWN */ AWAVE_DOWN_KEY, /* JOYPAD_LEFT */ AWAVE_LEFT_KEY, /* JOYPAD_RIGHT */ AWAVE_RIGHT_KEY, /* JOYPAD_A */ AWAVE_BTN1_KEY, /* BTN2 */ /* JOYPAD_X */ AWAVE_BTN3_KEY, /* BTN4 */ /* JOYPAD_L */ 0, /* JOYPAD_R */ AWAVE_BTN4_KEY, /* BTN5 */ /* JOYPAD_L2 */ 0, /* JOYPAD_R2 */ 0, /* JOYPAD_L3 */ AWAVE_TEST_KEY, /* JOYPAD_R3 */ AWAVE_SERVICE_KEY, }; static const uint32_t aw_lg_joymap[] = { /* deprecated */ 0, /* deprecated */ 0, /* LIGHTGUN_TRIGGER */ AWAVE_TRIGGER_KEY, /* LIGHTGUN_AUX_A */ AWAVE_BTN0_KEY, /* LIGHTGUN_AUX_B */ AWAVE_BTN1_KEY, /* deprecated */ 0, /* LIGHTGUN_START */ AWAVE_START_KEY, /* LIGHTGUN_SELECT */ AWAVE_COIN_KEY, /* LIGHTGUN_AUX_C */ AWAVE_BTN2_KEY, /* LIGHTGUN_UP */ AWAVE_UP_KEY, /* LIGHTGUN_DOWN */ AWAVE_DOWN_KEY, /* LIGHTGUN_LEFT */ AWAVE_LEFT_KEY, /* LIGHTGUN_RIGHT */ AWAVE_RIGHT_KEY, }; static const uint32_t nao_joymap[] = { /* JOYPAD_B */ NAOMI_BTN0_KEY, /* BTN1 */ /* JOYPAD_Y */ NAOMI_BTN2_KEY, /* BTN3 */ /* JOYPAD_SELECT */ NAOMI_COIN_KEY, /* JOYPAD_START */ NAOMI_START_KEY, /* JOYPAD_UP */ NAOMI_UP_KEY, /* JOYPAD_DOWN */ NAOMI_DOWN_KEY, /* JOYPAD_LEFT */ NAOMI_LEFT_KEY, /* JOYPAD_RIGHT */ NAOMI_RIGHT_KEY, /* JOYPAD_A */ NAOMI_BTN1_KEY, /* BTN2 */ /* JOYPAD_X */ NAOMI_BTN3_KEY, /* BTN4 */ /* JOYPAD_L */ NAOMI_BTN5_KEY, /* BTN6 */ /* JOYPAD_R */ NAOMI_BTN4_KEY, /* BTN5 */ /* JOYPAD_L2 */ NAOMI_BTN7_KEY, /* BTN8 */ /* JOYPAD_R2 */ NAOMI_BTN6_KEY, /* BTN7 */ /* JOYPAD_L3 */ NAOMI_TEST_KEY, /* JOYPAD_R3 */ NAOMI_SERVICE_KEY, }; static const uint32_t nao_lg_joymap[] = { /* deprecated */ 0, /* deprecated */ 0, /* LIGHTGUN_TRIGGER */ NAOMI_BTN0_KEY, /* LIGHTGUN_AUX_A */ NAOMI_BTN1_KEY, /* LIGHTGUN_AUX_B */ NAOMI_BTN2_KEY, /* deprecated */ 0, /* LIGHTGUN_START */ NAOMI_START_KEY, /* LIGHTGUN_SELECT */ NAOMI_COIN_KEY, /* LIGHTGUN_AUX_C */ NAOMI_BTN3_KEY, /* LIGHTGUN_UP */ NAOMI_UP_KEY, /* LIGHTGUN_DOWN */ NAOMI_DOWN_KEY, /* LIGHTGUN_LEFT */ NAOMI_LEFT_KEY, /* LIGHTGUN_RIGHT */ NAOMI_RIGHT_KEY, }; const uint32_t *joymap; size_t joymap_size; switch (settings.platform.system) { case DC_PLATFORM_DREAMCAST: case DC_PLATFORM_DEV_UNIT: switch (device) { case RETRO_DEVICE_JOYPAD: joymap = dc_joymap; joymap_size = ARRAY_SIZE(dc_joymap); break; case RETRO_DEVICE_LIGHTGUN: joymap = dc_lg_joymap; joymap_size = ARRAY_SIZE(dc_lg_joymap); break; default: return 0; } break; case DC_PLATFORM_NAOMI: case DC_PLATFORM_NAOMI2: switch (device) { case RETRO_DEVICE_JOYPAD: joymap = nao_joymap; joymap_size = ARRAY_SIZE(nao_joymap); break; case RETRO_DEVICE_LIGHTGUN: joymap = nao_lg_joymap; joymap_size = ARRAY_SIZE(nao_lg_joymap); break; default: return 0; } break; case DC_PLATFORM_ATOMISWAVE: switch (device) { case RETRO_DEVICE_JOYPAD: joymap = aw_joymap; joymap_size = ARRAY_SIZE(aw_joymap); break; case RETRO_DEVICE_LIGHTGUN: joymap = aw_lg_joymap; joymap_size = ARRAY_SIZE(aw_lg_joymap); break; default: return 0; } break; default: return 0; } if (id >= joymap_size) return 0; uint32_t mapped = joymap[id]; // Hack to bind Button 9 instead of Service when not used if (id == RETRO_DEVICE_ID_JOYPAD_R3 && device == RETRO_DEVICE_JOYPAD && settings.platform.isNaomi() && !allow_service_buttons) mapped = NAOMI_BTN8_KEY; return mapped; } static const char *get_button_name(unsigned device, unsigned id, const char *default_name) { if (NaomiGameInputs == NULL) return default_name; uint32_t mask = map_gamepad_button(device, id); if (mask == 0) return NULL; for (int i = 0; NaomiGameInputs->buttons[i].source != 0; i++) if (NaomiGameInputs->buttons[i].source == mask) { if (NaomiGameInputs->buttons[i].name[0] != '\0') return NaomiGameInputs->buttons[i].name; else return default_name; } return NULL; } static const char *get_axis_name(unsigned index, const char *default_name) { if (NaomiGameInputs == NULL) return default_name; for (int i = 0; NaomiGameInputs->axes[i].name != NULL; i++) if (NaomiGameInputs->axes[i].axis == index) { if (NaomiGameInputs->axes[i].name[0] != '\0') return NaomiGameInputs->axes[i].name; else return default_name; } return NULL; } static void set_input_descriptors() { struct retro_input_descriptor desc[22 * 4 + 1]; int descriptor_index = 0; if (settings.platform.isArcade()) { const char *name; for (unsigned i = 0; i < MAPLE_PORTS; i++) { switch (config::MapleMainDevices[i]) { case MDT_LightGun: name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT, "D-Pad Left"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP, "D-Pad Up"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN, "D-Pad Down"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT, "D-Pad Right") ; if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER, "Trigger"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_A, "Button 1"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_B, "Button 2"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_B, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_C, "Button 3"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_C, name }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD, "Reload" }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_SELECT, "Coin"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SELECT, name }; name = get_button_name(RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_START, "Start"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START, name }; break; case MDT_SegaController: name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_B, "Button 1"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_A, "Button 2"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_Y, "Button 3"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_X, "Button 4"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_R, "Button 5"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, name }; name = haveCardReader ? "Insert Card" : get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_L, "Button 6"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_R2, "Button 7"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_L2, "Button 8"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_START, "Start"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_SELECT, "Coin"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_L3, "Test"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, name }; name = get_button_name(RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_R3, "Service"); if (name != NULL) desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, name }; name = get_axis_name(0, "Axis 1"); if (name != NULL && name[0] != '\0') desc[descriptor_index++] = { i, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, name }; name = get_axis_name(1, "Axis 2"); if (name != NULL && name[0] != '\0') desc[descriptor_index++] = { i, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, name }; name = get_axis_name(2, "Axis 3"); if (name != NULL && name[0] != '\0') desc[descriptor_index++] = { i, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, name }; name = get_axis_name(3, "Axis 4"); if (name != NULL && name[0] != '\0') desc[descriptor_index++] = { i, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, name }; name = get_axis_name(4, NULL); if (name != NULL && name[0] != '\0') desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, name }; name = get_axis_name(5, NULL); if (name != NULL && name[0] != '\0') desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, name }; break; default: break; } } } else { for (unsigned i = 0; i < MAPLE_PORTS; i++) { switch (config::MapleMainDevices[i]) { case MDT_SegaController: desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "A" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "B" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Y" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "X" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "L Trigger" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "R Trigger" }; desc[descriptor_index++] = { i, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }; desc[descriptor_index++] = { i, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Analog X" }; desc[descriptor_index++] = { i, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Analog Y" }; break; case MDT_TwinStick: desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "L-Stick Left" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_UP, "L-Stick Up" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "L-Stick Down" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "L-Stick Right" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_B, "R-Stick Down" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_A, "R-Stick Right" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_X, "R-Stick Up" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_Y, "R-Stick Left" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_L, "L Turbo" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_R, "R Turbo" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_L2, "L Trigger" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_R2, "R Trigger" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Special" }; break; case MDT_AsciiStick: desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Stick Left" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Stick Up" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Stick Down" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Stick Right" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_B, "A" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_A, "B" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_X, "Y" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_Y, "X" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_L, "C" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_R, "Z" }; desc[descriptor_index++] = { i, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" }; break; case MDT_LightGun: desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT, "D-Pad Left" }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP, "D-Pad Up" }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN, "D-Pad Down" }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT, "D-Pad Right" }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER, "A" }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START, "Start" }; desc[descriptor_index++] = { i, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A, "B" }; break; default: break; } } } desc[descriptor_index++] = { 0 }; environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); } static void extract_basename(char *buf, const char *path, size_t size) { const char *base = find_last_slash(path); if (!base) base = path; else base++; strncpy(buf, base, size - 1); buf[size - 1] = '\0'; } static void remove_extension(char *buf, const char *path, size_t size) { char *base; strncpy(buf, path, size - 1); buf[size - 1] = '\0'; base = strrchr(buf, '.'); if (base) *base = '\0'; } #ifdef HAVE_VULKAN static VulkanContext theVulkanContext; static void retro_vk_context_reset() { NOTICE_LOG(RENDERER, "retro_vk_context_reset"); retro_hw_render_interface* vulkan; if (!environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void**)&vulkan) || !vulkan) { ERROR_LOG(RENDERER, "Get Vulkan HW interface failed"); return; } theVulkanContext.init((retro_hw_render_interface_vulkan *)vulkan); rend_term_renderer(); rend_init_renderer(); } static void retro_vk_context_destroy() { NOTICE_LOG(RENDERER, "retro_vk_context_destroy"); rend_term_renderer(); theVulkanContext.term(); } static bool set_vulkan_hw_render() { retro_hw_render_callback hw_render{}; hw_render.context_type = RETRO_HW_CONTEXT_VULKAN; hw_render.version_major = VK_API_VERSION_1_0; hw_render.version_minor = 0; hw_render.context_reset = retro_vk_context_reset; hw_render.context_destroy = retro_vk_context_destroy; hw_render.debug_context = false; if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) return false; static const struct retro_hw_render_context_negotiation_interface_vulkan negotiation_interface = { RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN, RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, VkGetApplicationInfo, VkCreateDevice, nullptr, }; environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, (void *)&negotiation_interface); if (config::RendererType == RenderType::OpenGL_OIT || config::RendererType == RenderType::DirectX11_OIT) config::RendererType = RenderType::Vulkan_OIT; else if (config::RendererType != RenderType::Vulkan_OIT) config::RendererType = RenderType::Vulkan; return true; } #else static bool set_vulkan_hw_render() { return false; } #endif static bool set_opengl_hw_render(u32 preferred) { #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) glsm_ctx_params_t params = {0}; params.context_reset = context_reset; params.context_destroy = context_destroy; params.environ_cb = environ_cb; #if defined(TARGET_NO_STENCIL) params.stencil = false; #else params.stencil = true; #endif params.imm_vbo_draw = NULL; params.imm_vbo_disable = NULL; #if defined(__APPLE__) && defined(HAVE_OPENGL) preferred = RETRO_HW_CONTEXT_OPENGL_CORE; #endif #ifdef HAVE_OIT if (config::RendererType == RenderType::OpenGL_OIT) { params.context_type = (retro_hw_context_type)preferred; if (preferred == RETRO_HW_CONTEXT_OPENGL) { // There are some weirdness with RA's gl context's versioning : // - any value above 3.0 won't provide a valid context, while the GLSM_CTL_STATE_CONTEXT_INIT call returns true... // - the only way to overwrite previously set version with zero values is to set them directly in hw_render, otherwise they are ignored (see glsm_state_ctx_init logic) // FIXME what's the point of this? retro_hw_render_callback hw_render; hw_render.version_major = 3; hw_render.version_minor = 0; } else { params.major = 4; params.minor = 3; } } else #endif { #ifndef HAVE_OPENGLES params.context_type = (retro_hw_context_type)preferred; params.major = 3; params.minor = preferred == RETRO_HW_CONTEXT_OPENGL_CORE ? 2 : 0; #endif config::RendererType = RenderType::OpenGL; } if (glsm_ctl(GLSM_CTL_STATE_CONTEXT_INIT, ¶ms)) return true; #if defined(HAVE_GL3) params.context_type = (retro_hw_context_type)preferred; params.major = 3; params.minor = 0; #else params.context_type = (retro_hw_context_type)preferred; params.major = 0; params.minor = 0; #endif config::RendererType = RenderType::OpenGL; return glsm_ctl(GLSM_CTL_STATE_CONTEXT_INIT, ¶ms); #else return false; #endif } #ifdef HAVE_D3D11 static void dx11_context_reset() { NOTICE_LOG(RENDERER, "DX11 context reset"); retro_hw_render_interface_d3d11 *hw_render = nullptr; if (!environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &hw_render) || hw_render == nullptr || hw_render->interface_type != RETRO_HW_RENDER_INTERFACE_D3D11) return; if (hw_render->interface_version != RETRO_HW_RENDER_INTERFACE_D3D11_VERSION) { WARN_LOG(RENDERER, "Unsupported interface version %d, expecting %d", hw_render->interface_version, RETRO_HW_RENDER_INTERFACE_D3D11_VERSION); return; } rend_term_renderer(); theDX11Context.term(); theDX11Context.init(hw_render->device, hw_render->context, hw_render->D3DCompile, hw_render->featureLevel); if (config::RendererType == RenderType::OpenGL_OIT || config::RendererType == RenderType::Vulkan_OIT) config::RendererType = RenderType::DirectX11_OIT; else if (config::RendererType != RenderType::DirectX11_OIT) config::RendererType = RenderType::DirectX11; rend_init_renderer(); } static void dx11_context_destroy() { NOTICE_LOG(RENDERER, "DX11 context destroyed"); rend_term_renderer(); theDX11Context.term(); } #endif static bool set_dx11_hw_render() { #ifdef HAVE_D3D11 retro_hw_render_callback hw_render_{}; hw_render_.context_type = RETRO_HW_CONTEXT_DIRECT3D; hw_render_.version_major = 11; hw_render_.version_minor = 0; hw_render_.context_reset = dx11_context_reset; hw_render_.context_destroy = dx11_context_destroy; if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render_)) { WARN_LOG(RENDERER, "DX11 hardware rendering not available"); return false; } return true; #else return false; #endif } // Loading/unloading games bool retro_load_game(const struct retro_game_info *game) { NOTICE_LOG(BOOT, "retro_load_game: %s", game->path); extract_basename(g_base_name, game->path, sizeof(g_base_name)); extract_directory(game_dir, game->path, sizeof(game_dir)); // Storing rom dir for later use snprintf(g_roms_dir, sizeof(g_roms_dir), "%s%c", game_dir, slash); if (environ_cb(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumble) && log_cb) log_cb(RETRO_LOG_DEBUG, "Rumble interface supported!\n"); const char *dir = NULL; if (!(environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir)) dir = game_dir; snprintf(game_dir, sizeof(game_dir), "%s%cdc%c", dir, slash, slash); snprintf(game_dir_no_slash, sizeof(game_dir_no_slash), "%s%cdc", dir, slash); // Per-content VMU additions START // > Get save directory const char *vmu_dir = NULL; if (!(environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &vmu_dir) && vmu_dir)) vmu_dir = game_dir; snprintf(vmu_dir_no_slash, sizeof(vmu_dir_no_slash), "%s", vmu_dir); // > Get content name remove_extension(content_name, g_base_name, sizeof(content_name)); if (content_name[0] == '\0') snprintf(content_name, sizeof(content_name), "vmu_save"); // Per-content VMU additions END update_variables(true); char *ext = strrchr(g_base_name, '.'); { /* Check for extension .lst, .bin, .dat or .zip. If found, we will set the system type * automatically to Naomi or AtomisWave. */ if (ext) { log_cb(RETRO_LOG_INFO, "File extension is: %s\n", ext); if (!strcmp(".lst", ext) || !strcmp(".bin", ext) || !strcmp(".BIN", ext) || !strcmp(".dat", ext) || !strcmp(".DAT", ext) || !strcmp(".zip", ext) || !strcmp(".ZIP", ext) || !strcmp(".7z", ext) || !strcmp(".7Z", ext)) { settings.platform.system = naomi_cart_GetPlatform(game->path); // Users should use the superior format instead, let's warn them if (!strcmp(".lst", ext) || !strcmp(".bin", ext) || !strcmp(".BIN", ext) || !strcmp(".dat", ext) || !strcmp(".DAT", ext)) { struct retro_message msg; // Sadly, this callback is only able to display short messages, so we can't give proper explanations... msg.msg = "Please upgrade to MAME romsets or expect issues"; msg.frames = 1200; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } } // If m3u playlist found load the paths into array else if (!strcmp(".m3u", ext) || !strcmp(".M3U", ext)) { if (!read_m3u(game->path)) { if (log_cb) log_cb(RETRO_LOG_ERROR, "%s\n", "[libretro]: failed to read m3u file ...\n"); return false; } } } } if (game->path[0] == '\0') { if (settings.platform.isConsole()) boot_to_bios = true; else return false; } if (settings.platform.isArcade()) boot_to_bios = false; if (boot_to_bios) game_data.clear(); // if an m3u file was loaded, disk_paths will already be populated so load the game from there else if (disk_paths.size() > 0) { disk_index = 0; // Attempt to set initial disk index if (disk_paths.size() > 1 && disk_initial_index > 0 && disk_initial_index < disk_paths.size() && disk_paths[disk_initial_index].compare(disk_initial_path) == 0) disk_index = disk_initial_index; game_data = disk_paths[disk_index]; } else { char disk_label[PATH_MAX]; disk_label[0] = '\0'; disk_paths.push_back(game->path); fill_short_pathname_representation(disk_label, game->path, sizeof(disk_label)); disk_labels.push_back(disk_label); game_data = game->path; } { char data_dir[1024]; snprintf(data_dir, sizeof(data_dir), "%s%s", game_dir, "data"); INFO_LOG(COMMON, "Creating dir: %s", data_dir); struct stat buf; if (stat(data_dir, &buf) < 0) { path_mkdir(data_dir); } } u32 preferred; if (!environ_cb(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred)) preferred = RETRO_HW_CONTEXT_DUMMY; bool foundRenderApi = false; if (preferred == RETRO_HW_CONTEXT_OPENGL || preferred == RETRO_HW_CONTEXT_OPENGL_CORE || preferred == RETRO_HW_CONTEXT_OPENGLES2 || preferred == RETRO_HW_CONTEXT_OPENGLES3 || preferred == RETRO_HW_CONTEXT_OPENGLES_VERSION) { foundRenderApi = set_opengl_hw_render(preferred); } else if (preferred == RETRO_HW_CONTEXT_VULKAN) { foundRenderApi = set_vulkan_hw_render(); } else if (preferred == RETRO_HW_CONTEXT_DIRECT3D) { foundRenderApi = set_dx11_hw_render(); } else { // fallback when not supported (or auto-switching disabled), let's try all supported drivers foundRenderApi = set_dx11_hw_render(); if (!foundRenderApi) foundRenderApi = set_vulkan_hw_render(); #if defined(HAVE_OPENGLES) if (!foundRenderApi) foundRenderApi = set_opengl_hw_render(RETRO_HW_CONTEXT_OPENGLES3); if (!foundRenderApi) foundRenderApi = set_opengl_hw_render(RETRO_HW_CONTEXT_OPENGLES2); #else if (!foundRenderApi) foundRenderApi = set_opengl_hw_render(RETRO_HW_CONTEXT_OPENGL_CORE); if (!foundRenderApi) foundRenderApi = set_opengl_hw_render(RETRO_HW_CONTEXT_OPENGL); #endif } if (!foundRenderApi) return false; if (settings.platform.isArcade()) { if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir != nullptr && strcmp(dir, g_roms_dir) != 0) { static char save_dir[PATH_MAX]; snprintf(save_dir, sizeof(save_dir), "%s%creicast%c", dir, slash, slash); struct stat buf; if (stat(save_dir, &buf) < 0) { DEBUG_LOG(BOOT, "Creating dir: %s", save_dir); path_mkdir(save_dir); } arcadeFlashPath = std::string(save_dir) + g_base_name; } else { arcadeFlashPath = std::string(g_roms_dir) + g_base_name; } INFO_LOG(BOOT, "Setting flash base path to %s", arcadeFlashPath.c_str()); } config::ScreenStretching = 100; if (!loadGame()) return false; rotate_game = config::Rotate90; if (rotate_game) config::Widescreen.override(false); config::Rotate90 = false; // actual framebuffer rotation is done by frontend setRotation(); if (settings.content.gameId == "INITIAL D" || settings.content.gameId == "INITIAL D Ver.2" || settings.content.gameId == "INITIAL D Ver.3" || settings.content.gameId == "INITIAL D CYCRAFT") haveCardReader = true; else haveCardReader = false; refresh_devices(true); // System may have changed - have to update hidden core options set_variable_visibility(); return true; } bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info) { return false; } void retro_unload_game() { INFO_LOG(COMMON, "Flycast unloading game"); emu.unloadGame(); game_data.clear(); disk_paths.clear(); disk_labels.clear(); blankVmus(); } // Memory/Serialization void *retro_get_memory_data(unsigned type) { if (type == RETRO_MEMORY_SYSTEM_RAM) return mem_b.data; return nullptr; } size_t retro_get_memory_size(unsigned type) { if (type == RETRO_MEMORY_SYSTEM_RAM) return mem_b.size; return 0; } size_t retro_serialize_size() { DEBUG_LOG(SAVESTATE, "retro_serialize_size"); std::lock_guard lock(mtx_serialization); if (!first_run) emu.stop(); Serializer ser; dc_serialize(ser); if (!first_run) emu.start(); return ser.size(); } bool retro_serialize(void *data, size_t size) { DEBUG_LOG(SAVESTATE, "retro_serialize %d bytes", (int)size); std::lock_guard lock(mtx_serialization); if (!first_run) emu.stop(); Serializer ser(data, size); dc_serialize(ser); if (!first_run) emu.start(); return true; } bool retro_unserialize(const void * data, size_t size) { DEBUG_LOG(SAVESTATE, "retro_unserialize"); std::lock_guard lock(mtx_serialization); if (!first_run) emu.stop(); try { Deserializer deser(data, size); dc_loadstate(deser); retro_audio_flush_buffer(); if (!first_run) emu.start(); return true; } catch (const Deserializer::Exception& e) { ERROR_LOG(SAVESTATE, "Loading state failed: %s", e.what()); return false; } } // Cheats void retro_cheat_reset() { // Nothing to do here } void retro_cheat_set(unsigned unused, bool unused1, const char* unused2) { // Nothing to do here } // Get info const char* retro_get_system_directory() { const char* dir; environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir); return dir ? dir : "."; } void retro_get_system_info(struct retro_system_info *info) { info->library_name = "Flycast"; #ifndef GIT_VERSION #define GIT_VERSION "undefined" #endif info->library_version = GIT_VERSION; info->valid_extensions = "chd|cdi|elf|cue|gdi|lst|bin|dat|zip|7z|m3u"; info->need_fullpath = true; info->block_extract = true; } void retro_get_system_av_info(retro_system_av_info *info) { NOTICE_LOG(RENDERER, "retro_get_system_av_info: Res=%d", (int)config::RenderResolution); if (cheatManager.isWidescreen()) { retro_message msg; msg.msg = "Widescreen cheat activated"; msg.frames = 120; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } framebufferWidth = config::RenderResolution * 16 / 9; framebufferHeight = config::RenderResolution; setAVInfo(*info); maxFramebufferWidth = info->geometry.max_width; maxFramebufferHeight = info->geometry.max_height; } unsigned retro_get_region() { return config::Broadcast == 0 ? RETRO_REGION_NTSC : RETRO_REGION_PAL; } // Controller void retro_set_controller_port_device(unsigned in_port, unsigned device) { if (device_type[in_port] != (int)device && in_port < MAPLE_PORTS) { devices_need_refresh = true; device_type[in_port] = device; switch (device) { case RETRO_DEVICE_JOYPAD: config::MapleMainDevices[in_port] = MDT_SegaController; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = MDT_SegaVMU; config::MapleExpansionDevices[in_port][1] = enable_purupuru ? MDT_PurupuruPack : MDT_SegaVMU; } break; case RETRO_DEVICE_TWINSTICK: case RETRO_DEVICE_TWINSTICK_SATURN: config::MapleMainDevices[in_port] = MDT_TwinStick; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = enable_purupuru ? MDT_PurupuruPack : MDT_SegaVMU; config::MapleExpansionDevices[in_port][1] = MDT_None; } break; case RETRO_DEVICE_ASCIISTICK: config::MapleMainDevices[in_port] = MDT_AsciiStick; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = enable_purupuru ? MDT_PurupuruPack : MDT_SegaVMU; config::MapleExpansionDevices[in_port][1] = MDT_None; } break; case RETRO_DEVICE_KEYBOARD: config::MapleMainDevices[in_port] = MDT_Keyboard; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = MDT_None; config::MapleExpansionDevices[in_port][1] = MDT_None; } break; case RETRO_DEVICE_MOUSE: config::MapleMainDevices[in_port] = MDT_Mouse; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = MDT_None; config::MapleExpansionDevices[in_port][1] = MDT_None; } break; case RETRO_DEVICE_LIGHTGUN: config::MapleMainDevices[in_port] = MDT_LightGun; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = enable_purupuru ? MDT_PurupuruPack : MDT_SegaVMU; config::MapleExpansionDevices[in_port][1] = MDT_None; } break; default: config::MapleMainDevices[in_port] = MDT_None; if (settings.platform.isConsole()) { config::MapleExpansionDevices[in_port][0] = MDT_None; config::MapleExpansionDevices[in_port][1] = MDT_None; } break; } } } static void refresh_devices(bool first_startup) { devices_need_refresh = false; set_input_descriptors(); if (!first_startup) { if (settings.platform.isConsole()) maple_ReconnectDevices(); if (rumble.set_rumble_state) { for(int i = 0; i < MAPLE_PORTS; i++) { rumble.set_rumble_state(i, RETRO_RUMBLE_STRONG, 0); rumble.set_rumble_state(i, RETRO_RUMBLE_WEAK, 0); } } } else if (settings.platform.isConsole()) { mcfg_DestroyDevices(); mcfg_CreateDevices(); } } // API version (to detect version mismatch) unsigned retro_api_version() { return RETRO_API_VERSION; } void retro_rend_present() { if (!config::ThreadedRendering) is_dupe = false; } static uint32_t get_time_ms() { return (uint32_t)(os_GetSeconds() * 1000.0); } static void get_analog_stick( retro_input_state_t input_state_cb, int player_index, int stick, s8* p_analog_x, s8* p_analog_y ) { int analog_x, analog_y; analog_x = input_state_cb( player_index, RETRO_DEVICE_ANALOG, stick, RETRO_DEVICE_ID_ANALOG_X ); analog_y = input_state_cb( player_index, RETRO_DEVICE_ANALOG, stick, RETRO_DEVICE_ID_ANALOG_Y ); // Analog stick deadzone (borrowed code from parallel-n64 core) if ( astick_deadzone > 0 ) { static const int ASTICK_MAX = 0x8000; // Convert cartesian coordinate analog stick to polar coordinates double radius = sqrt(analog_x * analog_x + analog_y * analog_y); double angle = atan2(analog_y, analog_x); if (radius > astick_deadzone) { // Re-scale analog stick range to negate deadzone (makes slow movements possible) radius = (radius - astick_deadzone)*((float)ASTICK_MAX/(ASTICK_MAX - astick_deadzone)); // Convert back to cartesian coordinates analog_x = (int)round(radius * cos(angle)); analog_y = (int)round(radius * sin(angle)); // Clamp to correct range if (analog_x > +32767) analog_x = +32767; if (analog_x < -32767) analog_x = -32767; if (analog_y > +32767) analog_y = +32767; if (analog_y < -32767) analog_y = -32767; } else { analog_x = 0; analog_y = 0; } } // output *p_analog_x = (s8)(analog_x >> 8); *p_analog_y = (s8)(analog_y >> 8); } static uint16_t apply_trigger_deadzone( uint16_t input ) { if ( trigger_deadzone > 0 ) { if ( input > trigger_deadzone ) { // Re-scale analog range static const int TRIGGER_MAX = 0x8000; const float scale = ((float)TRIGGER_MAX/(float)(TRIGGER_MAX - trigger_deadzone)); float scaled = (input - trigger_deadzone)*scale; input = (int)round(scaled); if (input > +32767) input = +32767; } else input = 0; } return input; } static uint16_t get_analog_trigger( int16_t ret, retro_input_state_t input_state_cb, int player_index, int id ) { // NOTE: Analog triggers were added Nov 2017. Not all front-ends support this // feature (or pre-date it) so we need to handle this in a graceful way. // First, try and get an analog value using the new libretro API constant uint16_t trigger = input_state_cb( player_index, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_BUTTON, id ); if ( trigger == 0 ) { // If we got exactly zero, we're either not pressing the button, or the front-end // is not reporting analog values. We need to do a second check using the classic // digital API method, to at least get some response - better than nothing. // NOTE: If we're really just not holding the trigger, we're still going to get zero. trigger = (ret & (1 << id)) ? 0x7FFF : 0; } else { // We got something, which means the front-end can handle analog buttons. // So we apply a deadzone to the input and use it. trigger = apply_trigger_deadzone( trigger ); } return trigger; } static void setDeviceButtonState(u32 port, int deviceType, int btnId) { uint32_t dc_key = map_gamepad_button(deviceType, btnId); bool is_down = input_cb(port, deviceType, 0, btnId); if (is_down) kcode[port] &= ~dc_key; else kcode[port] |= dc_key; } static void setDeviceButtonStateFromBitmap(u32 bitmap, u32 port, int deviceType, int btnId) { uint32_t dc_key = map_gamepad_button(deviceType, btnId); bool is_down = bitmap & (1 << btnId); if (is_down) kcode[port] &= ~dc_key; else kcode[port] |= dc_key; } // don't call map_gamepad_button, we supply the DC bit directly. static void setDeviceButtonStateDirect(u32 bitmap, u32 port, int deviceType, int btnId, int dc_bit) { uint32_t dc_key = 1 << dc_bit; bool is_down = bitmap & (1 << btnId); if (is_down) kcode[port] &= ~dc_key; else kcode[port] |= dc_key; } static void updateMouseState(u32 port) { std::lock_guard lock(relPosMutex); mo_x_delta[port] += input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_X); mo_y_delta[port] += input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_Y); bool btn_state = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT); if (btn_state) mo_buttons[port] &= ~(1 << 2); else mo_buttons[port] |= 1 << 2; btn_state = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_RIGHT); if (btn_state) mo_buttons[port] &= ~(1 << 1); else mo_buttons[port] |= 1 << 1; btn_state = input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_MIDDLE); if (btn_state) mo_buttons[port] &= ~(1 << 3); else mo_buttons[port] |= 1 << 3; if (input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_WHEELDOWN)) mo_wheel_delta[port] -= 10; else if (input_cb(port, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_WHEELUP)) mo_wheel_delta[port] += 10; } static void updateLightgunCoordinates(u32 port) { int x = input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X); int y = input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y); if (config::Widescreen && config::ScreenStretching == 100 && !config::EmulateFramebuffer) mo_x_abs[port] = 640.f * ((x + 0x8000) * 4.f / 3.f / 0x10000 - (4.f / 3.f - 1.f) / 2.f); else mo_x_abs[port] = (x + 0x8000) * 640.f / 0x10000; mo_y_abs[port] = (y + 0x8000) * 480.f / 0x10000; lightgun_params[port].offscreen = false; lightgun_params[port].x = mo_x_abs[port]; lightgun_params[port].y = mo_y_abs[port]; } static void UpdateInputStateNaomi(u32 port) { switch (config::MapleMainDevices[port]) { case MDT_LightGun: { // // -- buttons setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_A); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_B); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_C); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_START); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_SELECT); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT); bool force_offscreen = false; if (input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD)) { force_offscreen = true; if (settings.platform.isAtomiswave()) kcode[port] &= ~AWAVE_TRIGGER_KEY; else kcode[port] &= ~NAOMI_BTN0_KEY; } if (force_offscreen || input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN)) { mo_x_abs[port] = 0; mo_y_abs[port] = 0; lightgun_params[port].offscreen = true; if (input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER) || input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD)) { if (settings.platform.isNaomi()) kcode[port] &= ~NAOMI_BTN1_KEY; } } else { updateLightgunCoordinates(port); } } break; default: { // // -- buttons int16_t ret = 0; if (libretro_supports_bitmasks) ret = input_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); else { for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_R3; ++id) if (input_cb(port, RETRO_DEVICE_JOYPAD, 0, id)) ret |= (1 << id); } for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_R3; ++id) { switch (id) { case RETRO_DEVICE_ID_JOYPAD_L3: if (allow_service_buttons) setDeviceButtonStateFromBitmap(ret, port, RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_L3); break; case RETRO_DEVICE_ID_JOYPAD_R3: if (settings.platform.isNaomi() || allow_service_buttons) setDeviceButtonStateFromBitmap(ret, port, RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_R3); break; case RETRO_DEVICE_ID_JOYPAD_L: if (haveCardReader) { if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L)) card_reader::insertCard(); } else setDeviceButtonStateFromBitmap(ret, port, RETRO_DEVICE_JOYPAD, RETRO_DEVICE_ID_JOYPAD_L); break; default: setDeviceButtonStateFromBitmap(ret, port, RETRO_DEVICE_JOYPAD, id); break; } } // // -- analog stick get_analog_stick(input_cb, port, RETRO_DEVICE_INDEX_ANALOG_LEFT, &joyx[port], &joyy[port] ); get_analog_stick(input_cb, port, RETRO_DEVICE_INDEX_ANALOG_RIGHT, &joyrx[port], &joyry[port]); lt[port] = get_analog_trigger(ret, input_cb, port, RETRO_DEVICE_ID_JOYPAD_L2) / 128; rt[port] = get_analog_trigger(ret, input_cb, port, RETRO_DEVICE_ID_JOYPAD_R2) / 128; if (NaomiGameInputs != NULL) { for (int i = 0; NaomiGameInputs->axes[i].name != NULL; i++) { if (NaomiGameInputs->axes[i].type == Half) { /* Note: * - Analog stick axes have a range of [-128, 127] * - Analog triggers have a range of [0, 255] */ switch (NaomiGameInputs->axes[i].axis) { case 0: /* Left stick X: [-128, 127] */ joyx[port] = std::max((int)joyx[port], 0) * 2; break; case 1: /* Left stick Y: [-128, 127] */ joyy[port] = std::max((int)joyy[port], 0) * 2; break; case 2: /* Right stick X: [-128, 127] */ joyrx[port] = std::max((int)joyrx[port], 0) * 2; break; case 3: /* Right stick Y: [-128, 127] */ joyry[port] = std::max((int)joyry[port], 0) * 2; break; /* Case 4/5 correspond to right/left trigger. * These inputs are always classified as 'Half', * and already have the correct range - so no * further action is required */ } } } } // -- mouse, for rotary encoders updateMouseState(port); } break; } // Avoid Left+Right or Up+Down buttons being pressed together as this crashes some games if (settings.platform.isAtomiswave()) { if ((kcode[port] & (AWAVE_UP_KEY|AWAVE_DOWN_KEY)) == 0) kcode[port] |= AWAVE_UP_KEY|AWAVE_DOWN_KEY; if ((kcode[port] & (AWAVE_LEFT_KEY|AWAVE_RIGHT_KEY)) == 0) kcode[port] |= AWAVE_LEFT_KEY|AWAVE_RIGHT_KEY; } else { if ((kcode[port] & (NAOMI_UP_KEY|NAOMI_DOWN_KEY)) == 0) kcode[port] |= NAOMI_UP_KEY|NAOMI_DOWN_KEY; if ((kcode[port] & (NAOMI_LEFT_KEY|NAOMI_RIGHT_KEY)) == 0) kcode[port] |= NAOMI_LEFT_KEY|NAOMI_RIGHT_KEY; } } static void UpdateInputState(u32 port) { if (gl_ctx_resetting) return; if (settings.platform.isArcade()) { UpdateInputStateNaomi(port); return; } if (rumble.set_rumble_state != NULL && vib_stop_time[port] > 0) { if (get_time_ms() >= vib_stop_time[port]) { vib_stop_time[port] = 0; rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0); } else if (vib_delta[port] > 0.0) { u32 rem_time = vib_stop_time[port] - get_time_ms(); rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535 * vib_strength[port] * rem_time * vib_delta[port]); } } lightgun_params[port].offscreen = true; switch (config::MapleMainDevices[port]) { case MDT_SegaController: { int16_t ret = 0; // // -- buttons if (libretro_supports_bitmasks) ret = input_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK); else { for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_R3; ++id) if (input_cb(port, RETRO_DEVICE_JOYPAD, 0, id)) ret |= (1 << id); } for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_X; ++id) setDeviceButtonStateFromBitmap(ret, port, RETRO_DEVICE_JOYPAD, id); // // -- analog stick get_analog_stick( input_cb, port, RETRO_DEVICE_INDEX_ANALOG_LEFT, &(joyx[port]), &(joyy[port]) ); // // -- triggers if ( digital_triggers ) { // -- digital left trigger if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L2)) lt[port]=0xFF; else lt[port]=0; // -- digital right trigger if (ret & (1 << RETRO_DEVICE_ID_JOYPAD_R2)) rt[port]=0xFF; else rt[port]=0; } else { // -- analog triggers lt[port] = get_analog_trigger(ret, input_cb, port, RETRO_DEVICE_ID_JOYPAD_L2 ) / 128; rt[port] = get_analog_trigger(ret, input_cb, port, RETRO_DEVICE_ID_JOYPAD_R2 ) / 128; } } break; case MDT_AsciiStick: { int16_t ret = 0; if (libretro_supports_bitmasks) ret = input_cb(port, RETRO_DEVICE_ASCIISTICK, 0, RETRO_DEVICE_ID_JOYPAD_MASK); else { for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_R3; ++id) if (input_cb(port, RETRO_DEVICE_ASCIISTICK, 0, id)) ret |= (1 << id); } kcode[port] = 0xFFFF; // active-low // stick setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_UP, 4 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_DOWN, 5 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_LEFT, 6 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_RIGHT, 7 ); // buttons setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_B, 2 ); // A setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_A, 1 ); // B setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_Y, 10 ); // X setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_X, 9 ); // Y // Z { uint32_t dc_key = 1 << 8; // Z bool is_down = (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L )) || (ret & (1 << RETRO_DEVICE_ID_JOYPAD_L2)); if (is_down) kcode[port] &= ~dc_key; else kcode[port] |= dc_key; } // C { uint32_t dc_key = 1 << 0; // C bool is_down = (ret & (1 << RETRO_DEVICE_ID_JOYPAD_R)) || (ret & (1 << RETRO_DEVICE_ID_JOYPAD_R2)); if (is_down) kcode[port] &= ~dc_key; else kcode[port] |= dc_key; } setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_ASCIISTICK, RETRO_DEVICE_ID_JOYPAD_START, 3 ); // Start // unused inputs lt[port]=0; rt[port]=0; joyx[port]=0; joyy[port]=0; } break; case MDT_TwinStick: { int16_t ret = 0; kcode[port] = 0xFFFF; // active-low if ( device_type[port] == RETRO_DEVICE_TWINSTICK_SATURN ) { // NOTE: This is a remapping of the RetroPad layout in the block below to make using a real // Saturn Twin-Stick controller (via a USB adapter) less effort. // The Saturn Twin-Stick identifies as a regular Saturn controller internally but with its controls // wired to the two sticks without much rhyme or reason. The mapping below untangles that layout // into DC compatible inputs, without requiring a change for the Reicast and Beetle Saturn cores. // Hope that makes sense!! // NOTE: the dc_bits below are the same, only the retro id values have been rearranged. if (libretro_supports_bitmasks) ret = input_cb(port, RETRO_DEVICE_TWINSTICK_SATURN, 0, RETRO_DEVICE_ID_JOYPAD_MASK); else { for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_R3; ++id) if (input_cb(port, RETRO_DEVICE_TWINSTICK_SATURN, 0, id)) ret |= (1 << id); } // left-stick setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_UP, 4 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_DOWN, 5 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_LEFT, 6 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_RIGHT, 7 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_L2, 10 ); // left-trigger setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_R2, 9 ); // left-turbo // right-stick setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_X, 12 ); // up setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_L, 15 ); // right setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_A, 13 ); // down setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_Y, 14 ); // left setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_B, 2 ); // right-trigger setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_R, 1 ); // right-turbo // misc control setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_START, 3 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK_SATURN, RETRO_DEVICE_ID_JOYPAD_SELECT, 11 ); //D } else { int analog; const int thresh = 11000; // about 33%, allows for 8-way movement if (libretro_supports_bitmasks) ret = input_cb(port, RETRO_DEVICE_TWINSTICK, 0, RETRO_DEVICE_ID_JOYPAD_MASK); else { for (int id = RETRO_DEVICE_ID_JOYPAD_B; id <= RETRO_DEVICE_ID_JOYPAD_R3; ++id) if (input_cb(port, RETRO_DEVICE_TWINSTICK, 0, id)) ret |= (1 << id); } // LX analog = input_cb( port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X ); if ( analog < -thresh ) kcode[port] &= ~( 1 << 6 ); // L else if ( analog > thresh ) kcode[port] &= ~( 1 << 7 ); // R else { // digital setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_LEFT, 6 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_RIGHT, 7 ); } // LY analog = input_cb( port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y ); if ( analog < -thresh ) kcode[port] &= ~( 1 << 4 ); // U else if ( analog > thresh ) kcode[port] &= ~( 1 << 5 ); // D else { // digital setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_UP, 4 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_DOWN, 5 ); } // RX analog = input_cb( port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X ); if ( analog < -thresh ) kcode[port] &= ~( 1 << 14 ); // L else if ( analog > thresh ) kcode[port] &= ~( 1 << 15 ); // R else { // digital setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_Y, 14 ); // left setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_A, 15 ); // right } // RY analog = input_cb( port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y ); if ( analog < -thresh ) kcode[port] &= ~( 1 << 12 ); // U else if ( analog > thresh ) kcode[port] &= ~( 1 << 13 ); // D else { // digital setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_X, 12 ); // up setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_B, 13 ); // down } // left-stick buttons setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_L2, 10 ); // left-trigger setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_L, 9 ); // left-turbo // right-stick buttons setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_R2, 2 ); // right-trigger setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_R, 1 ); // right-turbo // misc control setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_START, 3 ); setDeviceButtonStateDirect(ret, port, RETRO_DEVICE_TWINSTICK, RETRO_DEVICE_ID_JOYPAD_SELECT, 11 ); //D } // unused inputs lt[port]=0; rt[port]=0; joyx[port]=0; joyy[port]=0; } break; case MDT_LightGun: { // // -- buttons setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_AUX_A); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_START); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_UP); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_DOWN); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_LEFT); setDeviceButtonState(port, RETRO_DEVICE_LIGHTGUN, RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT); bool force_offscreen = false; if (input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD)) { force_offscreen = true; kcode[port] &= ~DC_BTN_A; } if (force_offscreen || input_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN)) { mo_x_abs[port] = -1000; mo_y_abs[port] = -1000; lightgun_params[port].offscreen = true; lightgun_params[port].x = mo_x_abs[port]; lightgun_params[port].y = mo_y_abs[port]; } else { updateLightgunCoordinates(port); } } break; case MDT_Mouse: updateMouseState(port); break; default: break; } } void UpdateInputState() { UpdateInputState(0); UpdateInputState(1); UpdateInputState(2); UpdateInputState(3); } static void updateVibration(u32 port, float power, float inclination, u32 durationMs) { if (!rumble.set_rumble_state) return; vib_strength[port] = power; rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, (u16)(65535 * power)); vib_stop_time[port] = get_time_ms() + durationMs; vib_delta[port] = inclination; } u8 kb_key[4][6]; // normal keys pressed u8 kb_shift[4]; // modifier keys pressed (bitmask) static int kb_used; static void release_key(unsigned dc_keycode) { if (dc_keycode == 0) return; if (kb_used > 0) { for (int i = 0; i < 6; i++) { if (kb_key[0][i] == dc_keycode) { kb_used--; for (int j = i; j < 5; j++) kb_key[0][j] = kb_key[0][j + 1]; kb_key[0][5] = 0; } } } } static void retro_keyboard_event(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers) { // Dreamcast keyboard emulation if (keycode == RETROK_LSHIFT || keycode == RETROK_RSHIFT) { if (!down) kb_shift[0] &= ~(0x02 | 0x20); else kb_shift[0] |= (0x02 | 0x20); } if (keycode == RETROK_LCTRL || keycode == RETROK_RCTRL) { if (!down) kb_shift[0] &= ~(0x01 | 0x10); else kb_shift[0] |= (0x01 | 0x10); } // Make sure modifier keys are released if ((key_modifiers & RETROKMOD_SHIFT) == 0) { release_key(kb_map[RETROK_LSHIFT]); release_key(kb_map[RETROK_LSHIFT]); } if ((key_modifiers & RETROKMOD_CTRL) == 0) { release_key(kb_map[RETROK_LCTRL]); release_key(kb_map[RETROK_RCTRL]); } u8 dc_keycode = kb_map[keycode]; if (dc_keycode != 0) { if (down) { if (kb_used < 6) { bool found = false; for (int i = 0; !found && i < 6; i++) { if (kb_key[0][i] == dc_keycode) found = true; } if (!found) { kb_key[0][kb_used] = dc_keycode; kb_used++; } } } else { release_key(dc_keycode); } } } void fatal_error(const char* text, ...) { if (log_cb) { va_list args; char temp[2048]; va_start(args, text); vsprintf(temp, text, args); va_end(args); strcat(temp, "\n"); log_cb(RETRO_LOG_ERROR, temp); } } void os_DebugBreak() { ERROR_LOG(COMMON, "DEBUGBREAK!"); //exit(-1); #ifdef __SWITCH__ svcExitProcess(); #else __builtin_trap(); #endif } static bool retro_set_eject_state(bool ejected) { disc_tray_open = ejected; if (ejected) { DiscOpenLid(); return true; } else { try { return DiscSwap(disk_paths[disk_index]); } catch (const FlycastException& e) { ERROR_LOG(GDROM, "%s", e.what()); return false; } } } static bool retro_get_eject_state() { return disc_tray_open; } static unsigned retro_get_image_index() { return disk_index; } static bool retro_set_image_index(unsigned index) { disk_index = index; if (disk_index >= disk_paths.size()) { // No disk in drive settings.content.path.clear(); return true; } settings.content.path = disk_paths[index]; if (disc_tray_open) return true; try { return DiscSwap(settings.content.path); } catch (const FlycastException& e) { ERROR_LOG(GDROM, "%s", e.what()); return false; } } static unsigned retro_get_num_images() { return disk_paths.size(); } static bool retro_add_image_index() { disk_paths.push_back(""); disk_labels.push_back(""); return true; } static bool retro_replace_image_index(unsigned index, const struct retro_game_info *info) { if (index >= disk_paths.size() || index >= disk_labels.size()) return false; if (info == nullptr) { disk_paths.erase(disk_paths.begin() + index); disk_labels.erase(disk_labels.begin() + index); if (disk_index >= index && disk_index > 0) disk_index--; } else { char disk_label[PATH_MAX]; disk_label[0] = '\0'; disk_paths[index] = info->path; fill_short_pathname_representation(disk_label, info->path, sizeof(disk_label)); disk_labels[index] = disk_label; } return true; } static bool retro_set_initial_image(unsigned index, const char *path) { if (!path || *path == '\0') return false; disk_initial_index = index; disk_initial_path = path; return true; } static bool retro_get_image_path(unsigned index, char *path, size_t len) { if (len < 1) return false; if (index >= disk_paths.size()) return false; if (disk_paths[index].empty()) return false; strncpy(path, disk_paths[index].c_str(), len - 1); path[len - 1] = '\0'; return true; } static bool retro_get_image_label(unsigned index, char *label, size_t len) { if (len < 1) return false; if (index >= disk_paths.size() || index >= disk_labels.size()) return false; if (disk_labels[index].empty()) return false; strncpy(label, disk_labels[index].c_str(), len - 1); label[len - 1] = '\0'; return true; } static void init_disk_control_interface() { unsigned dci_version = 0; retro_disk_control_cb.set_eject_state = retro_set_eject_state; retro_disk_control_cb.get_eject_state = retro_get_eject_state; retro_disk_control_cb.set_image_index = retro_set_image_index; retro_disk_control_cb.get_image_index = retro_get_image_index; retro_disk_control_cb.get_num_images = retro_get_num_images; retro_disk_control_cb.add_image_index = retro_add_image_index; retro_disk_control_cb.replace_image_index = retro_replace_image_index; retro_disk_control_ext_cb.set_eject_state = retro_set_eject_state; retro_disk_control_ext_cb.get_eject_state = retro_get_eject_state; retro_disk_control_ext_cb.set_image_index = retro_set_image_index; retro_disk_control_ext_cb.get_image_index = retro_get_image_index; retro_disk_control_ext_cb.get_num_images = retro_get_num_images; retro_disk_control_ext_cb.add_image_index = retro_add_image_index; retro_disk_control_ext_cb.replace_image_index = retro_replace_image_index; retro_disk_control_ext_cb.set_initial_image = retro_set_initial_image; retro_disk_control_ext_cb.get_image_path = retro_get_image_path; retro_disk_control_ext_cb.get_image_label = retro_get_image_label; disk_initial_index = 0; disk_initial_path.clear(); if (environ_cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &dci_version) && (dci_version >= 1)) environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &retro_disk_control_ext_cb); else environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &retro_disk_control_cb); } static bool read_m3u(const char *file) { char line[PATH_MAX]; char name[PATH_MAX]; FILE *f = fopen(file, "r"); if (!f) { log_cb(RETRO_LOG_ERROR, "Could not read file\n"); return false; } while (fgets(line, sizeof(line), f) && disk_index <= disk_paths.size()) { if (line[0] == '#') continue; char *carriage_return = strchr(line, '\r'); if (carriage_return) *carriage_return = '\0'; char *newline = strchr(line, '\n'); if (newline) *newline = '\0'; // Remove any beginning and ending quotes as these can cause issues when feeding the paths into command line later if (line[0] == '"') memmove(line, line + 1, strlen(line)); if (line[strlen(line) - 1] == '"') line[strlen(line) - 1] = '\0'; if (line[0] != '\0') { char disk_label[PATH_MAX]; disk_label[0] = '\0'; if (path_is_absolute(line)) snprintf(name, sizeof(name), "%s", line); else snprintf(name, sizeof(name), "%s%s", g_roms_dir, line); disk_paths.push_back(name); fill_short_pathname_representation(disk_label, name, sizeof(disk_label)); disk_labels.push_back(disk_label); disk_index++; } } fclose(f); return disk_index != 0; } void gui_display_notification(const char *msg, int duration) { retro_message retromsg; retromsg.msg = msg; retromsg.frames = duration / 17; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &retromsg); } void os_RunInstance(int argc, const char *argv[]) { }