/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #define FORBIDDEN_SYMBOL_ALLOW_ALL #include #include "audio/mixer_intern.h" #include "base/main.h" #include "common/scummsys.h" #include "common/str.h" #include "common/fs.h" #include "common/error.h" #include "streams/file_stream.h" #include "graphics/surface.h" #ifdef _WIN32 #include #else #include #endif #ifndef _MSC_VER /** * Include libgen.h for basename() and dirname(). * @see http://linux.die.net/man/3/basename */ #include #endif /** * Include base/internal_version.h to allow access to SCUMMVM_VERSION. * @see retro_get_system_info() */ #define INCLUDED_FROM_BASE_VERSION_CPP #include "base/internal_version.h" #include "backends/platform/libretro/include/libretro-threads.h" #include "backends/platform/libretro/include/libretro-core-options.h" #include "backends/platform/libretro/include/libretro-os.h" #include "backends/platform/libretro/include/libretro-defs.h" static struct retro_game_info game_buf; static struct retro_game_info * game_buf_ptr; retro_log_printf_t log_cb = NULL; static retro_video_refresh_t video_cb = NULL; static retro_audio_sample_batch_t audio_batch_cb = NULL; static retro_environment_t environ_cb = NULL; static retro_input_poll_t poll_cb = NULL; static retro_input_state_t input_cb = NULL; static int retro_device = RETRO_DEVICE_JOYPAD; // System analog stick range is -0x8000 to 0x8000 #define ANALOG_RANGE 0x8000 // Default deadzone: 15% static int analog_deadzone = (int)(0.15f * ANALOG_RANGE); static float gampad_cursor_speed = 1.0f; static bool analog_response_is_quadratic = false; static float mouse_speed = 1.0f; static float gamepad_acceleration_time = 0.2f; static bool timing_inaccuracies_enabled = false; static bool consecutive_screen_updates = false; char cmd_params[20][200]; char cmd_params_num; int adjusted_RES_W = 0; int adjusted_RES_H = 0; static uint32 current_frame = 0; static uint8 frameskip_no; static uint8 min_auto_frameskip = 0; static uint8 min_auto_frameskip_count = 0; static uint8 frameskip_type; static uint8 frameskip_threshold; static uint32 frameskip_counter = 0; static uint8 frameskip_events = 0; static uint8 audio_status = AUDIO_STATUS_MUTE; static unsigned retro_audio_buff_occupancy = 0; static uint8 performance_switch = 0; static uint32 perf_ref_frame = 0; static uint32 perf_ref_audio_buff_occupancy = 0; float frame_rate; static uint16 samples_per_frame = 0; // length in samples per frame static size_t samples_per_frame_buffer_size = 0; static int16_t *sound_buffer = NULL; // pointer to output buffer static void log_scummvm_exit_code(void) { if (retro_get_scummvm_res() == Common::kNoError) log_cb(RETRO_LOG_INFO, "ScummVM exited successfully.\n"); else if (retro_get_scummvm_res() < Common::kNoError) log_cb(RETRO_LOG_WARN, "Unknown ScummVM exit code.\n"); else log_cb(RETRO_LOG_ERROR, "ScummVM exited with error %d.\n", retro_get_scummvm_res()); } static void audio_buffer_init(uint16 sample_rate, uint16 frame_rate) { samples_per_frame = sample_rate / frame_rate; samples_per_frame_buffer_size = samples_per_frame << 1 * sizeof(int16_t); if (sound_buffer) sound_buffer = (int16_t *)realloc(sound_buffer, samples_per_frame_buffer_size); else sound_buffer = (int16_t *)malloc(samples_per_frame_buffer_size); if (sound_buffer) memset(sound_buffer, 0, samples_per_frame_buffer_size); else log_cb(RETRO_LOG_ERROR, "audio_buffer_init error.\n"); audio_status |= AUDIO_STATUS_UPDATE_LATENCY; } static void retro_audio_buff_status_cb(bool active, unsigned occupancy, bool underrun_likely) { if (active) audio_status |= AUDIO_STATUS_BUFFER_ACTIVE; else audio_status &= ~AUDIO_STATUS_BUFFER_ACTIVE; if (underrun_likely) audio_status |= AUDIO_STATUS_BUFFER_UNDERRUN; else audio_status &= ~AUDIO_STATUS_BUFFER_UNDERRUN; retro_audio_buff_occupancy = occupancy; } static void increase_performance() { if (!(performance_switch & PERF_SWITCH_DISABLE_CONSECUTIVE_SCREEN_UPDATES)) { performance_switch |= PERF_SWITCH_DISABLE_CONSECUTIVE_SCREEN_UPDATES; log_cb(RETRO_LOG_DEBUG, "Auto performance tuner: 'Disable consecutive screen updates' enabled.\n"); return; } if (!(performance_switch & PERF_SWITCH_ENABLE_TIMING_INACCURACIES)) { performance_switch |= PERF_SWITCH_ENABLE_TIMING_INACCURACIES; log_cb(RETRO_LOG_DEBUG, "Auto performance tuner: 'Allow Timing Inaccuracies' enabled.\n"); return; } performance_switch |= PERF_SWITCH_OVER; } static void increase_accuracy() { performance_switch &= ~PERF_SWITCH_OVER; if (performance_switch & PERF_SWITCH_ENABLE_TIMING_INACCURACIES) { performance_switch &= ~PERF_SWITCH_ENABLE_TIMING_INACCURACIES; log_cb(RETRO_LOG_DEBUG, "Auto performance tuner: 'Allow Timing Inaccuracies' disabled.\n"); return; } if (performance_switch & PERF_SWITCH_DISABLE_CONSECUTIVE_SCREEN_UPDATES) { performance_switch &= ~PERF_SWITCH_DISABLE_CONSECUTIVE_SCREEN_UPDATES; log_cb(RETRO_LOG_DEBUG, "Auto performance tuner: 'Disable consecutive screen updates' disabled.\n"); return; } } void reset_performance_tuner() { if (performance_switch & PERF_SWITCH_ON) { performance_switch = PERF_SWITCH_ON; log_cb(RETRO_LOG_DEBUG, "Auto performance tuner: reset.\n"); } } void retro_osd_notification(const char* msg) { if (!msg || *msg == '\0') return; struct retro_message_ext retro_msg; retro_msg.type = RETRO_MESSAGE_TYPE_NOTIFICATION; retro_msg.target = RETRO_MESSAGE_TARGET_OSD; retro_msg.duration = 3000; retro_msg.msg = msg; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &retro_msg); } static void update_variables(void) { struct retro_variable var; const char* osd_msg = ""; var.key = "scummvm_gamepad_cursor_speed"; var.value = NULL; gampad_cursor_speed = 1.0f; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { gampad_cursor_speed = (float)atof(var.value); } var.key = "scummvm_gamepad_cursor_acceleration_time"; var.value = NULL; gamepad_acceleration_time = 0.2f; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { gamepad_acceleration_time = (float)atof(var.value); } var.key = "scummvm_analog_response"; var.value = NULL; analog_response_is_quadratic = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "quadratic") == 0) analog_response_is_quadratic = true; } var.key = "scummvm_analog_deadzone"; var.value = NULL; analog_deadzone = (int)(0.15f * ANALOG_RANGE); if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { analog_deadzone = (int)(atoi(var.value) * 0.01f * ANALOG_RANGE); } var.key = "scummvm_mouse_speed"; var.value = NULL; mouse_speed = 1.0f; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { mouse_speed = (float)atof(var.value); } var.key = "scummvm_allow_timing_inaccuracies"; var.value = NULL; timing_inaccuracies_enabled = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "enabled") == 0) timing_inaccuracies_enabled = true; } var.key = "scummvm_frameskip_threshold"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { frameskip_threshold = (uint8)strtol(var.value, NULL, 10); } var.key = "scummvm_frameskip_no"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { frameskip_no = (uint8)strtol(var.value, NULL, 10) + 1; } var.key = "scummvm_frameskip_type"; var.value = NULL; uint8 old_frameskip_type = frameskip_type; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "disabled") == 0) frameskip_type = 0; else if (strcmp(var.value, "fixed") == 0) frameskip_type = 1; else if (strcmp(var.value, "auto") == 0) frameskip_type = 2; else if (strcmp(var.value, "manual") == 0) frameskip_type = 3; } var.key = "scummvm_auto_performance_tuner"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "enabled") == 0) { if (!performance_switch) audio_status |= AUDIO_STATUS_UPDATE_LATENCY; performance_switch &= ~PERF_SWITCH_OVER; performance_switch |= PERF_SWITCH_ON; } else performance_switch = 0; } var.key = "scummvm_consecutive_screen_updates"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "enabled") == 0) { consecutive_screen_updates = false; } else consecutive_screen_updates = true; } if (!(audio_status & AUDIO_STATUS_BUFFER_SUPPORT)) { if (frameskip_type > 1) { log_cb(RETRO_LOG_WARN, "Selected frameskip mode not available.\n"); retro_osd_notification("Selected frameskip mode not available"); frameskip_type = 0; } if (performance_switch) { log_cb(RETRO_LOG_WARN, "Auto performance tuner not available.\n"); retro_osd_notification("Auto performance tuner not available"); performance_switch = 0; } } if (old_frameskip_type != frameskip_type) { audio_status |= AUDIO_STATUS_UPDATE_LATENCY; } } bool timing_inaccuracies_is_enabled(){ if (performance_switch & PERF_SWITCH_ON) return (performance_switch & PERF_SWITCH_ENABLE_TIMING_INACCURACIES); else return timing_inaccuracies_enabled; } bool consecutive_screen_updates_is_enabled(){ if (performance_switch & PERF_SWITCH_ON) return !(performance_switch & PERF_SWITCH_DISABLE_CONSECUTIVE_SCREEN_UPDATES); else return consecutive_screen_updates; } void init_command_params(void) { memset(cmd_params, 0, sizeof(cmd_params)); cmd_params_num = 1; strcpy(cmd_params[0], "scummvm\0"); } void parse_command_params(char *cmdline) { int j = 0; int cmdlen = strlen(cmdline); bool quotes = false; if (!cmdlen) return; /* Append a new line to the end of the command to signify it's finished. */ cmdline[cmdlen] = '\n'; cmdline[++cmdlen] = '\0'; /* parse command line into array of arguments */ for (int i = 0; i < cmdlen; i++) { switch (cmdline[i]) { case '\"': if (quotes) { cmdline[i] = '\0'; strcpy(cmd_params[cmd_params_num], cmdline + j); cmd_params_num++; quotes = false; } else quotes = true; j = i + 1; break; case ' ': case '\n': if (!quotes) { if (i != j && !quotes) { cmdline[i] = '\0'; strcpy(cmd_params[cmd_params_num], cmdline + j); cmd_params_num++; } j = i + 1; } break; } } } static void exit_to_frontend(void) { log_scummvm_exit_code(); environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); } static void close_emu_thread(void) { while (!retro_emu_thread_exited()) { LIBRETRO_G_SYSTEM->requestQuit(); retro_switch_to_emu_thread(); } retro_deinit_emu_thread(); } #if defined(WIIU) || defined(__SWITCH__) || defined(_MSC_VER) || defined(_3DS) #include #include char *dirname(char *path) { char *p; if (path == NULL || *path == '\0') return "."; p = path + strlen(path) - 1; while (*p == '/') { if (p == path) return path; *p-- = '\0'; } while (p >= path && *p != '/') p--; return p < path ? "." : p == path ? "/" : (*p = '\0', path); } #endif #if (defined(GEKKO) && !defined(WIIU)) || defined(__CELLOS_LV2__) int access(const char *path, int amode) { RFILE *f; int mode; switch (amode) { // we don't really care if a file exists but isn't readable case F_OK: case R_OK: mode = RETRO_VFS_FILE_ACCESS_READ; break; case W_OK: mode = RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING; break; default: return -1; } f = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (f) { filestream_close(f); return 0; } return -1; } #endif void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; } void retro_set_audio_sample(retro_audio_sample_t cb) {} void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { audio_batch_cb = cb; } void retro_set_input_poll(retro_input_poll_t cb) { poll_cb = cb; } void retro_set_input_state(retro_input_state_t cb) { input_cb = cb; } void retro_set_environment(retro_environment_t cb) { environ_cb = cb; bool tmp = true; bool has_categories; environ_cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &tmp); libretro_set_core_options(environ_cb, &has_categories); } unsigned retro_api_version(void) { return RETRO_API_VERSION; } void retro_get_system_info(struct retro_system_info *info) { info->library_name = CORE_NAME; #if defined GIT_TAG #define __GIT_VERSION GIT_TAG #elif defined GIT_HASH #define __GIT_VERSION GIT_HASH "-" SCUMMVM_VERSION #else #define __GIT_VERSION "" #endif info->library_version = __GIT_VERSION; info->valid_extensions = "scummvm"; info->need_fullpath = true; info->block_extract = false; } void retro_get_system_av_info(struct retro_system_av_info *info) { info->geometry.base_width = RES_W; info->geometry.base_height = RES_H; info->geometry.max_width = RES_W; info->geometry.max_height = RES_H; info->geometry.aspect_ratio = 4.0f / 3.0f; info->timing.fps = REFRESH_RATE; info->timing.sample_rate = SAMPLE_RATE; } const char * retro_get_system_dir(void){ const char *sysdir; const char *coredir; if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysdir)) return sysdir; else { if (log_cb) log_cb(RETRO_LOG_WARN, "No System directory specified, using current directory.\n"); if (! environ_cb(RETRO_ENVIRONMENT_GET_LIBRETRO_PATH, &coredir)) coredir = "."; return coredir; } } const char * retro_get_save_dir(void){ const char *savedir; const char *coredir; if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &savedir)) return savedir; else { if (log_cb) log_cb(RETRO_LOG_WARN, "No Save directory specified, using current directory.\n"); if (! environ_cb(RETRO_ENVIRONMENT_GET_LIBRETRO_PATH, &coredir)) coredir = "."; return coredir; } } void retro_init(void) { struct retro_log_callback log; if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) log_cb = log.log; else log_cb = NULL; log_cb(RETRO_LOG_DEBUG, "ScummVM core version: %s\n",__GIT_VERSION); struct retro_audio_buffer_status_callback buf_status_cb; buf_status_cb.callback = retro_audio_buff_status_cb; audio_status = environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK, &buf_status_cb) ? (audio_status | AUDIO_STATUS_BUFFER_SUPPORT) : (audio_status & ~AUDIO_STATUS_BUFFER_SUPPORT); frame_rate = environ_cb(RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE, &frame_rate) ? frame_rate : REFRESH_RATE; audio_buffer_init(SAMPLE_RATE, (uint16) frame_rate); update_variables(); init_command_params(); struct retro_input_descriptor desc[] = { {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Mouse Cursor Left"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Mouse Cursor Up"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Mouse Cursor Down"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Mouse Cursor Right"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "Right Mouse Button"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Left Mouse Button"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Esc"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "."}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "Enter"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Numpad 5"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "Backspace"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "Cursor Fine Control"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "F10"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Numpad 0"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "ScummVM GUI"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Virtual Keyboard"}, {0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT, "Left click"}, {0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_RIGHT, "Right click"}, {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X"}, {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y"}, {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X"}, {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y"}, {0}, }; environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc); /* Get color mode: 32 first as VGA has 6 bits per pixel */ #if 0 RDOSGFXcolorMode = RETRO_PIXEL_FORMAT_XRGB8888; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &RDOSGFXcolorMode)) { RDOSGFXcolorMode = RETRO_PIXEL_FORMAT_RGB565; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &RDOSGFXcolorMode)) RDOSGFXcolorMode = RETRO_PIXEL_FORMAT_0RGB1555; } #endif #ifdef FRONTEND_SUPPORTS_RGB565 enum retro_pixel_format rgb565 = RETRO_PIXEL_FORMAT_RGB565; if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb565) && log_cb) log_cb(RETRO_LOG_INFO, "Frontend supports RGB565 -will use that instead of XRGB1555.\n"); #endif retro_keyboard_callback cb = {LIBRETRO_G_SYSTEM->processKeyEvent}; environ_cb(RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK, &cb); g_system = new OSystem_libretro(); } void retro_deinit(void) { LIBRETRO_G_SYSTEM->destroy(); free(sound_buffer); } void retro_set_controller_port_device(unsigned port, unsigned device) { if (port != 0) { if (log_cb) log_cb(RETRO_LOG_WARN, "Invalid controller port %d.\n", port); return; } switch (device) { case RETRO_DEVICE_JOYPAD: case RETRO_DEVICE_MOUSE: retro_device = device; break; default: if (log_cb) log_cb(RETRO_LOG_WARN, "Invalid controller device class %d.\n", device); break; } } bool retro_load_game(const struct retro_game_info *game) { if (!g_system) { if (log_cb) log_cb(RETRO_LOG_ERROR, "[scummvm] Failed to initialize platform driver.\n"); return false; } #ifdef LIBRETRO_DEBUG char debug_buf [20]; sprintf(debug_buf, "--debuglevel=11"); parse_command_params(debug_buf); #endif if (game) { game_buf_ptr = &game_buf; memcpy(game_buf_ptr, game, sizeof(retro_game_info)); // Retrieve the game path. Common::FSNode detect_target = Common::FSNode(game->path); Common::FSNode parent_dir = detect_target.getParent(); char target_id[400] = {0}; char buffer[400] = {0}; int test_game_status = TEST_GAME_KO_NOT_FOUND; const char *target_file_ext = ".scummvm"; int target_file_ext_pos = strlen(game->path) - strlen(target_file_ext); // See if we are loading a .scummvm file. if (!(target_file_ext_pos < 0) && strstr(game->path + target_file_ext_pos, target_file_ext) != NULL) { // Open the file. RFILE *gamefile = filestream_open(game->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (!gamefile) { log_cb(RETRO_LOG_ERROR, "[scummvm] Failed to load given game file '%s'.\n", game->path); return false; } // Load the file data. if (filestream_gets(gamefile, target_id, sizeof(target_id)) == NULL) { filestream_close(gamefile); log_cb(RETRO_LOG_ERROR, "[scummvm] Failed to load contents of game file '%s'.\n", game->path); return false; } filestream_close(gamefile); Common::String tmp = target_id; tmp.trim(); strcpy(target_id, tmp.c_str()); if (strlen(target_id) == 0) { log_cb(RETRO_LOG_ERROR, "[scummvm] Game file '%s' does not contain any target id.\n", game->path); return false; } test_game_status = LIBRETRO_G_SYSTEM->testGame(target_id, false); } else { if (detect_target.isDirectory()) { parent_dir = detect_target; } else { // If this node has no parent node, then it returns a duplicate of this node. if (detect_target.getPath().equals(parent_dir.getPath())) { log_cb(RETRO_LOG_ERROR, "[scummvm] Autodetect not possible. No parent directory detected in '%s'.\n", game->path); return false; } } test_game_status = LIBRETRO_G_SYSTEM->testGame(parent_dir.getPath().c_str(), true); } // Preliminary game scan results switch (test_game_status) { case TEST_GAME_OK_ID_FOUND: sprintf(buffer, "-p \"%s\" %s", parent_dir.getPath().c_str(), target_id); log_cb(RETRO_LOG_DEBUG, "[scummvm] launch via target id and game dir\n"); break; case TEST_GAME_OK_TARGET_FOUND: sprintf(buffer, "%s", target_id); log_cb(RETRO_LOG_DEBUG, "[scummvm] launch via target id and scummvm.ini\n"); break; case TEST_GAME_OK_ID_AUTODETECTED: sprintf(buffer, "-p \"%s\" --auto-detect", parent_dir.getPath().c_str()); log_cb(RETRO_LOG_DEBUG, "[scummvm] launch via autodetect\n"); break; case TEST_GAME_KO_MULTIPLE_RESULTS: log_cb(RETRO_LOG_WARN, "[scummvm] Multiple targets found for '%s' in scummvm.ini\n", target_id); retro_osd_notification("Multiple targets found"); break; case TEST_GAME_KO_NOT_FOUND: default: log_cb(RETRO_LOG_WARN, "[scummvm] Game not found. Check path and content of '%s'\n", game->path); retro_osd_notification("Game not found"); } parse_command_params(buffer); } else { game_buf_ptr = NULL; } if (!retro_init_emu_thread()) { if (log_cb) log_cb(RETRO_LOG_ERROR, "[scummvm] Failed to initialize emulation thread!\n"); return false; } return true; } bool retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info) { return false; } void retro_run(void) { bool updated = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated){ update_variables(); } if (audio_status & AUDIO_STATUS_UPDATE_LATENCY){ uint32 audio_latency; float frame_time_msec = 1000.0f / frame_rate; audio_latency = (uint32)((8.0f * frame_time_msec) + 0.5f); audio_latency = (audio_latency + 0x1F) & ~0x1F; /* This can only be called from within retro_run() */ environ_cb(RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY, &audio_latency); audio_status &= ~AUDIO_STATUS_UPDATE_LATENCY; } /* Setting RA's video or audio driver to null will disable video/audio bits */ int audio_video_enable = 0; environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &audio_video_enable); bool skip_frame = false; size_t samples_count = 0; if (g_system) { /* ScummVM is not based on fixed framerate like libretro, and engines/scripts can call multiple screen updates between two retro_run calls. Hence if consecutive screen updates are detected we will loop within the same retro_run call until next pollEvent or delayMillis call in ScummVM thread. */ do { /* Determine frameskip need based on settings */ if ((frameskip_type == 2) || (performance_switch & PERF_SWITCH_ON)) { skip_frame = (audio_status & AUDIO_STATUS_BUFFER_UNDERRUN); if (skip_frame) min_auto_frameskip_count = min_auto_frameskip; else if (min_auto_frameskip_count) { skip_frame = min_auto_frameskip_count--; } } else if (frameskip_type == 1) skip_frame = !(current_frame % frameskip_no == 0); else if (frameskip_type == 3) skip_frame = (retro_audio_buff_occupancy < frameskip_threshold); /* No frame skipping if - no incoming audio (e.g. GUI) - doing a THREAD_SWITCH_UPDATE loop */ skip_frame = skip_frame && !(audio_status & AUDIO_STATUS_MUTE) && !(LIBRETRO_G_SYSTEM->getThreadSwitchCaller() & THREAD_SWITCH_UPDATE); /* Reset frameskip counter if not flagged */ if ((!skip_frame && frameskip_counter) || frameskip_counter >= FRAMESKIP_MAX) { log_cb(RETRO_LOG_DEBUG, "%d frame(s) skipped (%ld)\n", frameskip_counter, current_frame); skip_frame = false; frameskip_counter = 0; /* Keep on skipping frames if flagged */ } else if (skip_frame) { frameskip_counter++; /* Performance counter */ if (((performance_switch & PERF_SWITCH_ON) && !(performance_switch & PERF_SWITCH_OVER)) || ((frameskip_type == 2 || (performance_switch & PERF_SWITCH_ON)) && min_auto_frameskip < MIN_AUTO_FRAMESKIP_MAX)) { frameskip_events += frameskip_counter; if (frameskip_events > PERF_SWITCH_FRAMESKIP_EVENTS) { if ((frameskip_type == 2 || (performance_switch & PERF_SWITCH_ON)) && min_auto_frameskip < MIN_AUTO_FRAMESKIP_MAX) { min_auto_frameskip++; log_cb(RETRO_LOG_DEBUG, "Auto frameskip: minimum frameskip number set to %d.\n", min_auto_frameskip + 1); } else if ((performance_switch & PERF_SWITCH_ON) && !(performance_switch & PERF_SWITCH_OVER)) increase_performance(); frameskip_events = 0; perf_ref_frame = current_frame; perf_ref_audio_buff_occupancy = 0; } } } /* Performance tuner reset if average buffer occupacy is above the required threshold again */ if (!skip_frame && (((performance_switch & PERF_SWITCH_ON) && performance_switch > PERF_SWITCH_ON) || ((frameskip_type == 2 || (performance_switch & PERF_SWITCH_ON)) && min_auto_frameskip))) { perf_ref_audio_buff_occupancy += retro_audio_buff_occupancy; if ((current_frame - perf_ref_frame) % (PERF_SWITCH_RESET_REST) == 0) { uint32 avg_audio_buff_occupancy = perf_ref_audio_buff_occupancy / (current_frame + 1 - perf_ref_frame); if (avg_audio_buff_occupancy > PERF_SWITCH_RESET_THRESHOLD || avg_audio_buff_occupancy == retro_audio_buff_occupancy) if ((performance_switch & PERF_SWITCH_ON) && performance_switch > PERF_SWITCH_ON) increase_accuracy(); else if ((frameskip_type == 2 || (performance_switch & PERF_SWITCH_ON)) && min_auto_frameskip) { min_auto_frameskip--; log_cb(RETRO_LOG_DEBUG, "Auto frameskip: minimum frameskip number set to %d.\n", min_auto_frameskip + 1); } perf_ref_frame = current_frame - 1; perf_ref_audio_buff_occupancy = 0; frameskip_events = 0; } } /* Switch to ScummVM thread, unless frameskipping is ongoing */ if (!skip_frame) retro_switch_to_emu_thread(); if (retro_emu_thread_exited()) { exit_to_frontend(); return; } /* Retrieve audio */ samples_count = 0; if (audio_video_enable & 2) samples_count = ((Audio::MixerImpl *)g_system->getMixer())->mixCallback((byte *) sound_buffer, samples_per_frame_buffer_size); audio_status = samples_count ? (audio_status & ~AUDIO_STATUS_MUTE) : (audio_status | AUDIO_STATUS_MUTE); /* Retrieve video */ if ((audio_video_enable & 1) && !skip_frame) { const Graphics::Surface &screen = LIBRETRO_G_SYSTEM->getScreen(); video_cb(screen.getPixels(), screen.w, screen.h, screen.pitch); } #if defined(_3DS) /* Hack: 3DS will produce static noise * unless we manually send a zeroed * audio buffer when no samples are * available (i.e. when the overlay * is shown) */ if (audio_status & AUDIO_STATUS_MUTE) { audio_buffer_init(SAMPLE_RATE, (uint16) frame_rate); } #endif if (!(audio_status & AUDIO_STATUS_MUTE)) audio_batch_cb((int16_t *) sound_buffer, samples_count); current_frame++; } while (LIBRETRO_G_SYSTEM->getThreadSwitchCaller() & THREAD_SWITCH_UPDATE); poll_cb(); LIBRETRO_G_SYSTEM->processMouse(input_cb, retro_device, gampad_cursor_speed, gamepad_acceleration_time, analog_response_is_quadratic, analog_deadzone, mouse_speed); } } void retro_unload_game(void) { close_emu_thread(); } void retro_reset(void) { close_emu_thread(); init_command_params(); retro_load_game(game_buf_ptr); LIBRETRO_G_SYSTEM->resetQuit(); } // Stubs void *retro_get_memory_data(unsigned type) { return NULL; } size_t retro_get_memory_size(unsigned type) { return 0; } size_t retro_serialize_size(void) { return 0; } bool retro_serialize(void *data, size_t size) { return false; } bool retro_unserialize(const void *data, size_t size) { return false; } void retro_cheat_reset(void) {} void retro_cheat_set(unsigned unused, bool unused1, const char *unused2) {} unsigned retro_get_region(void) { return RETRO_REGION_NTSC; }