// Source Code: https://github.com/BinBashBanana/webretro
// please dont use IE
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if (!window.fetch || !indexedDB) {
alert("Update your browser!");
throw "Update your browser!";
}
var fsBundleDirs, fsBundleFiles, loadStatus, romName, isPaused, wasmReady, bundleReady, biosReady, romMode, core, wIdb, romUploadCallback, latestVersion, mainCompleted, currentManager, romUploadsReady, realRomExt, currentTheme;
var bundleCdn = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro@master/";
var bundleCdnLatest = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro/";
var biosCdn = "https://cdn.jsdelivr.net/gh/archtaurus/RetroPieBIOS@master/BIOS/";
var infoJsonUrl = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro/assets/info.json";
var standaloneDownloadUrl = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro/utils/webretro-standalone.html";
var relativeBase = (typeof relativeBase == "string") ? relativeBase : "";
var coreDir = "cores/";
var bioses = {"a5200": {path: "", files: ["5200.rom"]}, "atari800": {path: "", files: ["5200.rom"]}, "freechaf": {path: "", files: ["sl31253.bin", "sl31254.bin", "sl90025.bin"]}, "freeintv": {path: "", files: ["exec.bin", "grom.bin"]}, "gearcoleco": {path: "", files: ["colecovision.rom"]}, "handy": {path: "", files: ["lynxboot.img"]}, "mednafen_psx": {path: "", files: ["scph5500.bin", "scph5501.bin", "scph5502.bin"]}, "mednafen_psx_hw": {path: "", files: ["scph5500.bin", "scph5501.bin", "scph5502.bin"]}, "neocd": {path: "neocd/", files: ["neocd_z.rom"]}, "o2em": {path: "", files: ["c52.bin", "g7400.bin", "jopac.bin", "o2rom.bin"]}, "opera": {path: "", files: ["panafz10-norsa.bin", "panafz10ja-anvil-kanji.bin"]}, "px68k": {path: "keropi/", files: ["cgrom.dat", "iplrom.dat", "iplrom30.dat", "iplromco.dat", "iplromxv.dat"]}, "yabasanshiro": {path: "", files: ["saturn_bios.bin"]}, "yabause": {path: "", files: ["saturn_bios.bin"]}};
var defaultKeybinds = 'input_player1_start = "enter"\ninput_player1_select = "space"\ninput_player1_l = "e"\ninput_player1_l2 = "r"\ninput_player1_r = "p"\ninput_player1_r2 = "o"\ninput_player1_a = "h"\ninput_player1_b = "g"\ninput_player1_x = "y"\ninput_player1_y = "t"\ninput_player1_up = "up"\ninput_player1_left = "left"\ninput_player1_down = "down"\ninput_player1_right = "right"\ninput_player1_l_x_minus = "a"\ninput_player1_l_x_plus = "d"\ninput_player1_l_y_minus = "w"\ninput_player1_l_y_plus = "s"\ninput_player1_l3_btn = "x"\ninput_player1_r_x_minus = "j"\ninput_player1_r_x_plus = "l"\ninput_player1_r_y_minus = "i"\ninput_player1_r_y_plus = "k"\ninput_player1_r3_btn = "comma"\ninput_menu_toggle = "f1"\ninput_save_state = "f2"\ninput_load_state = "f3"\ninput_screenshot = "f4"\ninput_hold_fast_forward = "nul"\ninput_toggle_fast_forward = "nul"\ninput_hold_slowmotion = "nul"\ninput_toggle_slowmotion = "nul"\ninput_grab_mouse_toggle = "backslash"\ninput_game_focus_toggle = "tilde"\n';
var nulKeys = 'input_ai_service = "nul"\ninput_ai_service_axis = "nul"\ninput_ai_service_btn = "nul"\ninput_ai_service_mbtn = "nul"\ninput_audio_mute = "nul"\ninput_audio_mute_axis = "nul"\ninput_audio_mute_btn = "nul"\ninput_audio_mute_mbtn = "nul"\ninput_cheat_index_minus = "nul"\ninput_cheat_index_minus_axis = "nul"\ninput_cheat_index_minus_btn = "nul"\ninput_cheat_index_minus_mbtn = "nul"\ninput_cheat_index_plus = "nul"\ninput_cheat_index_plus_axis = "nul"\ninput_cheat_index_plus_btn = "nul"\ninput_cheat_index_plus_mbtn = "nul"\ninput_cheat_toggle = "nul"\ninput_cheat_toggle_axis = "nul"\ninput_cheat_toggle_btn = "nul"\ninput_cheat_toggle_mbtn = "nul"\ninput_desktop_menu_toggle = "nul"\ninput_desktop_menu_toggle_axis = "nul"\ninput_desktop_menu_toggle_btn = "nul"\ninput_desktop_menu_toggle_mbtn = "nul"\ninput_disk_eject_toggle = "nul"\ninput_disk_eject_toggle_axis = "nul"\ninput_disk_eject_toggle_btn = "nul"\ninput_disk_eject_toggle_mbtn = "nul"\ninput_disk_next = "nul"\ninput_disk_next_axis = "nul"\ninput_disk_next_btn = "nul"\ninput_disk_next_mbtn = "nul"\ninput_disk_prev = "nul"\ninput_disk_prev_axis = "nul"\ninput_disk_prev_btn = "nul"\ninput_disk_prev_mbtn = "nul"\ninput_duty_cycle = "nul"\ninput_enable_hotkey = "nul"\ninput_enable_hotkey_axis = "nul"\ninput_enable_hotkey_btn = "nul"\ninput_enable_hotkey_mbtn = "nul"\ninput_exit_emulator = "nul"\ninput_exit_emulator_axis = "nul"\ninput_exit_emulator_btn = "nul"\ninput_exit_emulator_mbtn = "nul"\ninput_fps_toggle = "nul"\ninput_fps_toggle_axis = "nul"\ninput_fps_toggle_btn = "nul"\ninput_fps_toggle_mbtn = "nul"\ninput_frame_advance = "nul"\ninput_frame_advance_axis = "nul"\ninput_frame_advance_btn = "nul"\ninput_frame_advance_mbtn = "nul"\ninput_game_focus_toggle_axis = "nul"\ninput_game_focus_toggle_btn = "nul"\ninput_game_focus_toggle_mbtn = "nul"\ninput_grab_mouse_toggle_axis = "nul"\ninput_grab_mouse_toggle_btn = "nul"\ninput_grab_mouse_toggle_mbtn = "nul"\ninput_hold_fast_forward_axis = "nul"\ninput_hold_fast_forward_btn = "nul"\ninput_hold_fast_forward_mbtn = "nul"\ninput_slowmotion = "nul"\ninput_hold_slowmotion_axis = "nul"\ninput_hold_slowmotion_btn = "nul"\ninput_hold_slowmotion_mbtn = "nul"\ninput_hotkey_block_delay = "nul"\ninput_load_state_axis = "nul"\ninput_load_state_btn = "nul"\ninput_load_state_mbtn = "nul"\ninput_menu_toggle_axis = "nul"\ninput_menu_toggle_btn = "nul"\ninput_menu_toggle_mbtn = "nul"\ninput_movie_record_toggle = "nul"\ninput_movie_record_toggle_axis = "nul"\ninput_movie_record_toggle_btn = "nul"\ninput_movie_record_toggle_mbtn = "nul"\ninput_netplay_game_watch = "nul"\ninput_netplay_game_watch_axis = "nul"\ninput_netplay_game_watch_btn = "nul"\ninput_netplay_game_watch_mbtn = "nul"\ninput_netplay_host_toggle = "nul"\ninput_netplay_host_toggle_axis = "nul"\ninput_netplay_host_toggle_btn = "nul"\ninput_netplay_host_toggle_mbtn = "nul"\ninput_osk_toggle = "nul"\ninput_osk_toggle_axis = "nul"\ninput_osk_toggle_btn = "nul"\ninput_osk_toggle_mbtn = "nul"\ninput_overlay_next = "nul"\ninput_overlay_next_axis = "nul"\ninput_overlay_next_btn = "nul"\ninput_overlay_next_mbtn = "nul"\ninput_pause_toggle = "nul"\ninput_pause_toggle_axis = "nul"\ninput_pause_toggle_btn = "nul"\ninput_pause_toggle_mbtn = "nul"\ninput_player1_a_axis = "nul"\ninput_player1_a_btn = "nul"\ninput_player1_a_mbtn = "nul"\ninput_player1_b_axis = "nul"\ninput_player1_b_btn = "nul"\ninput_player1_b_mbtn = "nul"\ninput_player1_down_axis = "nul"\ninput_player1_down_btn = "nul"\ninput_player1_down_mbtn = "nul"\ninput_player1_gun_aux_a = "nul"\ninput_player1_gun_aux_a_axis = "nul"\ninput_player1_gun_aux_a_btn = "nul"\ninput_player1_gun_aux_a_mbtn = "nul"\ninput_player1_gun_aux_b = "nul"\ninput_player1_gun_aux_b_axis = "nul"\ninput_player1_gun_aux_b_btn = "nul"\ninput_player1_gun_aux_b_mbtn = "nul"\ninput_player1_gun_aux_c = "nul"\ninput_player1_gun_aux_c_axis = "nul"\ninput_player1_gun_aux_c_btn = "nul"\ninput_player1_gun_aux_c_mbtn = "nul"\ninput_player1_gun_dpad_down = "nul"\ninput_player1_gun_dpad_down_axis = "nul"\ninput_player1_gun_dpad_down_btn = "nul"\ninput_player1_gun_dpad_down_mbtn = "nul"\ninput_player1_gun_dpad_left = "nul"\ninput_player1_gun_dpad_left_axis = "nul"\ninput_player1_gun_dpad_left_btn = "nul"\ninput_player1_gun_dpad_left_mbtn = "nul"\ninput_player1_gun_dpad_right = "nul"\ninput_player1_gun_dpad_right_axis = "nul"\ninput_player1_gun_dpad_right_btn = "nul"\ninput_player1_gun_dpad_right_mbtn = "nul"\ninput_player1_gun_dpad_up = "nul"\ninput_player1_gun_dpad_up_axis = "nul"\ninput_player1_gun_dpad_up_btn = "nul"\ninput_player1_gun_dpad_up_mbtn = "nul"\ninput_player1_gun_offscreen_shot = "nul"\ninput_player1_gun_offscreen_shot_axis = "nul"\ninput_player1_gun_offscreen_shot_btn = "nul"\ninput_player1_gun_offscreen_shot_mbtn = "nul"\ninput_player1_gun_select = "nul"\ninput_player1_gun_select_axis = "nul"\ninput_player1_gun_select_btn = "nul"\ninput_player1_gun_select_mbtn = "nul"\ninput_player1_gun_start = "nul"\ninput_player1_gun_start_axis = "nul"\ninput_player1_gun_start_btn = "nul"\ninput_player1_gun_start_mbtn = "nul"\ninput_player1_gun_trigger = "nul"\ninput_player1_gun_trigger_axis = "nul"\ninput_player1_gun_trigger_btn = "nul"\ninput_player1_gun_trigger_mbtn = "nul"\ninput_player1_l2_axis = "nul"\ninput_player1_l2_btn = "nul"\ninput_player1_l2_mbtn = "nul"\ninput_player1_l3 = "nul"\ninput_player1_l3_axis = "nul"\ninput_player1_l3_mbtn = "nul"\ninput_player1_l_axis = "nul"\ninput_player1_l_btn = "nul"\ninput_player1_l_mbtn = "nul"\ninput_player1_l_x_minus_axis = "nul"\ninput_player1_l_x_minus_btn = "nul"\ninput_player1_l_x_minus_mbtn = "nul"\ninput_player1_l_x_plus_axis = "nul"\ninput_player1_l_x_plus_btn = "nul"\ninput_player1_l_x_plus_mbtn = "nul"\ninput_player1_l_y_minus_axis = "nul"\ninput_player1_l_y_minus_btn = "nul"\ninput_player1_l_y_minus_mbtn = "nul"\ninput_player1_l_y_plus_axis = "nul"\ninput_player1_l_y_plus_btn = "nul"\ninput_player1_l_y_plus_mbtn = "nul"\ninput_player1_left_axis = "nul"\ninput_player1_left_mbtn = "nul"\ninput_player1_r2_axis = "nul"\ninput_player1_r2_btn = "nul"\ninput_player1_r2_mbtn = "nul"\ninput_player1_r3 = "nul"\ninput_player1_r3_axis = "nul"\ninput_player1_r3_mbtn = "nul"\ninput_player1_r_axis = "nul"\ninput_player1_r_btn = "nul"\ninput_player1_r_mbtn = "nul"\ninput_player1_r_x_minus_axis = "nul"\ninput_player1_r_x_minus_btn = "nul"\ninput_player1_r_x_minus_mbtn = "nul"\ninput_player1_r_x_plus_axis = "nul"\ninput_player1_r_x_plus_btn = "nul"\ninput_player1_r_x_plus_mbtn = "nul"\ninput_player1_r_y_minus_axis = "nul"\ninput_player1_r_y_minus_btn = "nul"\ninput_player1_r_y_minus_mbtn = "nul"\ninput_player1_r_y_plus_axis = "nul"\ninput_player1_r_y_plus_btn = "nul"\ninput_player1_r_y_plus_mbtn = "nul"\ninput_player1_right_axis = "nul"\ninput_player1_right_mbtn = "nul"\ninput_player1_select_axis = "nul"\ninput_player1_select_btn = "nul"\ninput_player1_select_mbtn = "nul"\ninput_player1_start_axis = "nul"\ninput_player1_start_btn = "nul"\ninput_player1_start_mbtn = "nul"\ninput_player1_turbo = "nul"\ninput_player1_turbo_axis = "nul"\ninput_player1_turbo_btn = "nul"\ninput_player1_turbo_mbtn = "nul"\ninput_player1_up_axis = "nul"\ninput_player1_up_btn = "nul"\ninput_player1_up_mbtn = "nul"\ninput_player1_x_axis = "nul"\ninput_player1_x_btn = "nul"\ninput_player1_x_mbtn = "nul"\ninput_player1_y_axis = "nul"\ninput_player1_y_btn = "nul"\ninput_player1_y_mbtn = "nul"\ninput_poll_type_behavior = "nul"\ninput_recording_toggle = "nul"\ninput_recording_toggle_axis = "nul"\ninput_recording_toggle_btn = "nul"\ninput_recording_toggle_mbtn = "nul"\ninput_reset = "nul"\ninput_reset_axis = "nul"\ninput_reset_btn = "nul"\ninput_reset_mbtn = "nul"\ninput_rewind = "nul"\ninput_rewind_axis = "nul"\ninput_rewind_btn = "nul"\ninput_rewind_mbtn = "nul"\ninput_save_state_axis = "nul"\ninput_save_state_btn = "nul"\ninput_save_state_mbtn = "nul"\ninput_screenshot_axis = "nul"\ninput_screenshot_btn = "nul"\ninput_screenshot_mbtn = "nul"\ninput_send_debug_info = "nul"\ninput_send_debug_info_axis = "nul"\ninput_send_debug_info_btn = "nul"\ninput_send_debug_info_mbtn = "nul"\ninput_shader_next = "nul"\ninput_shader_next_axis = "nul"\ninput_shader_next_btn = "nul"\ninput_shader_next_mbtn = "nul"\ninput_shader_prev = "nul"\ninput_shader_prev_axis = "nul"\ninput_shader_prev_btn = "nul"\ninput_shader_prev_mbtn = "nul"\ninput_state_slot_decrease = "nul"\ninput_state_slot_decrease_axis = "nul"\ninput_state_slot_decrease_btn = "nul"\ninput_state_slot_decrease_mbtn = "nul"\ninput_state_slot_increase = "nul"\ninput_state_slot_increase_axis = "nul"\ninput_state_slot_increase_btn = "nul"\ninput_state_slot_increase_mbtn = "nul"\ninput_streaming_toggle = "nul"\ninput_streaming_toggle_axis = "nul"\ninput_streaming_toggle_btn = "nul"\ninput_streaming_toggle_mbtn = "nul"\ninput_toggle_fast_forward_axis = "nul"\ninput_toggle_fast_forward_btn = "nul"\ninput_toggle_fast_forward_mbtn = "nul"\ninput_toggle_fullscreen = "nul"\ninput_toggle_fullscreen_axis = "nul"\ninput_toggle_fullscreen_btn = "nul"\ninput_toggle_fullscreen_mbtn = "nul"\ninput_toggle_slowmotion_axis = "nul"\ninput_toggle_slowmotion_btn = "nul"\ninput_toggle_slowmotion_mbtn = "nul"\ninput_turbo_default_button = "nul"\ninput_turbo_mode = "nul"\ninput_turbo_period = "nul"\ninput_volume_down = "nul"\ninput_volume_down_axis = "nul"\ninput_volume_down_btn = "nul"\ninput_volume_down_mbtn = "nul"\ninput_volume_up = "nul"\ninput_volume_up_axis = "nul"\ninput_volume_up_btn = "nul"\ninput_volume_up_mbtn = "nul"\n';
var extraConfig = 'rgui_show_start_screen = "false"\nnotification_show_remap_load = "false"\nmenu_mouse_enable = "true"\nmenu_pointer_enable = "true"\n';
var pdKeys = [8, 9, 13, 19, 27, 32, 33, 34, 35, 36, 42, 44, 45, 91, 92, 93, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135];
var webretroVersion = 6.5;
var maxConsoleLength = 10000;
var updateNotice = document.getElementById("updatenotice");
var versionIndicator = document.getElementById("versionindicator");
var webretroTitle = document.getElementById("webretrotitle");
var upload = document.getElementById("upload");
var googleDriveUpload = document.getElementById("googledriveupload");
var dropboxUpload = document.getElementById("dropboxupload");
var oneDriveUpload = document.getElementById("onedriveupload");
var startButton = document.getElementById("startbutton");
var smooth = document.getElementById("smooth");
var canvas = document.getElementById("canvas");
var canvasMask = document.getElementById("canvasmask");
var saveState = document.getElementById("savestate");
var loadState = document.getElementById("loadstate");
var undoSaveState = document.getElementById("undosavestate");
var undoLoadState = document.getElementById("undoloadstate");
var exportState = document.getElementById("exportstate");
var importState = document.getElementById("importstate");
var ffd = document.getElementById("ffd");
var ffdContent = document.getElementById("ffdcontent");
var coreSelectArea = document.getElementById("coreselectarea");
var uploadArea = document.getElementById("uploadarea");
var coreOrder = document.getElementById("coreorder");
var coreList = document.getElementById("corelist");
var systemName = document.getElementById("systemname");
var consoleButton = document.getElementById("consolebutton");
var resetButton = document.getElementById("resetbutton");
var resetButton2 = document.getElementById("resetbutton2");
var mouseGrabButton = document.getElementById("mousegrabbutton");
var gameFocusButton = document.getElementById("gamefocusbutton");
var fullscreenButton = document.getElementById("fullscreenbutton");
var downloadStandaloneButton = document.getElementById("downloadstandalonebutton");
var menuButton = document.getElementById("menubutton");
var pauseButton = document.getElementById("pause");
var resumeOverlay = document.getElementById("resume");
var sideAlertHolder = document.getElementById("sidealertholder");
var saveGame = document.getElementById("savegame");
var exportSave = document.getElementById("exportsave");
var importSave = document.getElementById("importsave");
var autosave = document.getElementById("autosave");
var mainArea = document.getElementById("mainarea");
var menuBar = document.getElementById("menubar");
var menuHider = document.getElementById("menuhider");
var menuHeight = 45;
var actualMenuHeight = menuHeight;
var canvasCssWorkaroundElement = document.createElement("style");
var themeSelector = document.getElementById("themeselector");
var themes = {"iodinelight": {menuHeight: 45, id: ""}, "iodinedark": {menuHeight: 45, id: "iodinedark"}, "webplayer": {menuHeight: 65, id: "webplayer"}, "webplayernavy": {menuHeight: 65, id: "webplayer navy"}};
var defaultTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "iodinedark" : "iodinelight";
var loadingDiv = document.getElementById("loadingdiv");
var loadingText = document.getElementById("loadingtext");
var loadingBar = document.getElementById("loadingbar");
var takeScreenshot = document.getElementById("takescreenshot");
var modals = document.getElementById("modals");
var keybindTable = document.getElementById("keybindtable");
var saveKeybinds = document.getElementById("savekeybinds");
var resetKeybinds = document.getElementById("resetkeybinds");
var keybindsButton = document.getElementById("keybindsbutton");
var screenshotsButton = document.getElementById("screenshotsbutton");
var savesButton = document.getElementById("savesbutton");
var statesButton = document.getElementById("statesbutton");
var downloadAllScreenshots = document.getElementById("downloadallscreenshots");
var screenshotsDiv = document.getElementById("screenshotsdiv");
var saveTable = document.getElementById("savetable");
var romSelectorTable = document.getElementById("romselectortable");
var pso = document.getElementById("pso");
var coreOptions = {"a5200": {"lowpass": 'a5200_low_pass_filter = "enabled"\n'}, "mednafen_psx": {"highres": 'beetle_psx_internal_resolution = "2x"\n', "ditherscaling": 'beetle_psx_dither_mode = "internal resolution"\n', "widescreen": 'beetle_psx_widescreen_hack = "enabled"\n', "antijitter": 'beetle_psx_pgxp_mode = "memory only"\n'}, "mednafen_psx_hw": {"softwarerenderer": 'beetle_psx_hw_renderer = "software"\n', "highres": 'beetle_psx_hw_internal_resolution = "2x"\n', "ditherscaling": 'beetle_psx_hw_dither_mode = "internal resolution"\n', "widescreen": 'beetle_psx_hw_widescreen_hack = "enabled"\n', "antijitter": 'beetle_psx_hw_pgxp_mode = "memory only"\n'}, "mednafen_vb": {"anaglyph": 'vb_anaglyph_preset = "red & blue"\n'}, "mednafen_wswan": {"75hz": 'wswan_60hz_mode = "disabled"\n', "portrait": 'wswan_rotate_display = "portrait"\n', "lowpass": 'wswan_sound_low_pass = "enabled"\n'}, "melonds": {"leftright": 'melonds_screen_layout = "Left/Right"\n'}, "mgba": {"lowpass": 'mgba_audio_low_pass_filter = "enabled"\n'}, "mupen64plus_next": {"highres": 'mupen64plus-169screensize = "1280x720"\nmupen64plus-43screensize = "960x720"\n', "widescreen": 'mupen64plus-aspect = "16:9 adjusted"\n'}, "o2em": {"lowpass": 'o2em_low_pass_filter = "enabled"\n'}, "parallel_n64": {"highres": 'parallel-n64-screensize = "960x720"\n', "renderer2": 'parallel-n64-gfxplugin = "gln64"\n'}, "prosystem": {"lowpass": 'prosystem_low_pass_filter = "enabled"\n'}, "snes9x": {"mouse": 'input_libretro_device_p1 = "2"\n'}, "stella2014": {"lowpass": 'stella2014_low_pass_filter = "enabled"\n'}, "vecx": {"softwarerenderer": 'vecx_use_hw = "Software"\n'}, "virtualjaguar": {"fastblitter": 'virtualjaguar_usefastblitter = "enabled"\n'}, "yabause": {"frameskip": 'yabause_frameskip = "enabled"\n'}};
var managers = {};
managers.keybind = document.getElementById("keybindmanager");
managers.screenshot = document.getElementById("screenshotmanager");
managers.save = document.getElementById("savemanager");
managers.romSelector = document.getElementById("romselector");
var managerNames = {"save": "Saves & States", "romSelector": "Select the master ROM or playlist"};
var managerTitle = document.getElementById("managertitle");
var managerClose = document.getElementById("managerclose");
var screenshotDatas = [];
var screenshotObjUrls = [];
var saveIDs = [];
var quotaText = document.getElementById("quotatext");
var recommendedExtensions = document.getElementById("recommendedextensions");
var systems = {"81": "ZX81", "dosbox": "MS-DOS", "dosbox_pure": "MS-DOS", "opera": "3DO", "fsuae": "Amiga", "puae": "Amiga", "cap32": "Amstrad CPC", "fbalpha2012": "Arcade", "fbneo": "Arcade", "mame2003_plus": "Arcade", "stella": "Atari 2600", "stella2014": "Atari 2600", "atari800": "Atari 5200", "a5200": "Atari 5200", "prosystem": "Atari 7800", "virtualjaguar": "Atari Jaguar", "handy": "Atari Lynx", "mednafen_lynx": "Atari Lynx", "hatari": "Atari ST/TT", "gearcoleco": "ColecoVision", "vice_x64": "Commodore 64", "bk": "Electronika BK", "freechaf": "Fairchild Channel F", "gw": "Game & Watch", "vba_next": "GBA", "vbam": "GBA", "meteor": "GBA", "mednafen_gba": "GBA", "gpsp": "GBA", "mgba": "GB/GBC/GBA", "gambatte": "GB/GBC", "gearboy": "GB/GBC", "tgbdual": "GB/GBC", "sameboy": "GB/GBC", "o2em": "Odyssey 2", "freeintv": "Intellivision", "fmsx": "MSX", "bluemsx": "MSX", "neocd": "Neo-Geo CD", "mednafen_ngp": "Neo-Geo Pocket", "bnes": "NES", "fceumm": "NES", "quicknes": "NES", "mesen": "NES", "nestopia": "NES", "citra": "Nintendo 3DS", "mupen64plus_next": "Nintendo 64", "parallel_n64": "Nintendo 64", "desmume2015": "Nintendo DS", "desmume": "Nintendo DS", "melonds": "Nintendo DS", "dolphin": "Nintendo GC/Wii", "mednafen_pce_fast": "PC Engine", "mednafen_supergrafx": "PC Engine SuperGrafx", "quasi88": "PC-8000/8800", "np2kai": "PC-98", "mednafen_pcfx": "PC-FX", "mednafen_psx": "PlayStation", "mednafen_psx_hw": "PlayStation", "pcsx2": "PlayStation 2", "ppsspp": "PSP", "scummvm": "ScummVM", "flycast": "Sega Dreamcast", "mednafen_saturn": "Sega Saturn", "mednafen_saturn_hw": "Sega Saturn", "yabause": "Sega Saturn", "yabasanshiro": "Sega Saturn", "kronos": "Sega Saturn", "picodrive": "Sega Systems", "genesis_plus_gx": "Sega Systems", "blastem": "Sega Genesis", "px68k": "Sharp X68000", "fuse": "ZX Spectrum", "bsnes": "SNES", "bsnes_mercury_performance": "SNES", "bsnes_mercury_balanced": "SNES", "bsnes_mercury_accuracy": "SNES", "mednafen_snes": "SNES", "mesen-s": "SNES", "snes9x": "SNES", "theodore": "Thomson MO/TO", "vecx": "Vectrex", "mednafen_vb": "Virtual Boy", "mednafen_wswan": "WonderSwan"};
var coreNames = {"81": "EightyOne", "dosbox": "DOSBox", "dosbox_pure": "DOSBox Pure", "opera": "Opera", "fsuae": "FS-UAE", "puae": "PUAE", "cap32": "Caprice32", "fbalpha2012": "FB Alpha 2012", "fbneo": "FB Neo", "mame2003_plus": "MAME 2003 Plus", "stella": "Stella", "stella2014": "Stella 2014", "atari800": "Atari800", "a5200": "a5200", "prosystem": "ProSystem", "virtualjaguar": "Virtual Jaguar", "handy": "Handy", "mednafen_lynx": "Beetle Lynx", "hatari": "Hatari", "gearcoleco": "Gearcoleco", "vice_x64": "VICE", "bk": "BK", "freechaf": "FreeChaF", "gw": "GW", "vba_next": "VBA-Next", "vbam": "VBA-M", "meteor": "Meteor", "mednafen_gba": "Beetle GBA", "gpsp": "gpSP", "mgba": "mGBA", "gambatte": "Gambatte", "gearboy": "Gearboy", "tgbdual": "TGB Dual", "sameboy": "SameBoy", "o2em": "O2EM", "freeintv": "FreeIntv", "fmsx": "fMSX", "bluemsx": "blueMSX", "neocd": "NeoCD", "mednafen_ngp": "Beetle NeoPop", "bnes": "bnes", "fceumm": "FCEUmm", "quicknes": "QuickNES", "mesen": "Mesen", "nestopia": "Nestopia UE", "citra": "Citra", "mupen64plus_next": "Mupen64Plus-Next", "parallel_n64": "ParaLLEl N64", "desmume2015": "DeSmuME 2015", "desmume": "DeSmuME", "melonds": "melonDS", "dolphin": "Dolphin", "mednafen_pce_fast": "Beetle PCE Fast", "mednafen_supergrafx": "Beetle SuperGrafx", "quasi88": "QUASI88", "np2kai": "NP2kai", "mednafen_pcfx": "Beetle PCFX", "mednafen_psx": "Beetle PSX", "mednafen_psx_hw": "Beetle PSX HW", "pcsx2": "PCSX2", "ppsspp": "PPSSPP", "scummvm": "ScummVM", "flycast": "Flycast", "mednafen_saturn": "Beetle Saturn", "mednafen_saturn_hw": "Beetle Saturn HW", "yabause": "Yabause", "yabasanshiro": "YabaSanshiro", "kronos": "Kronos", "picodrive": "PicoDrive", "genesis_plus_gx": "Genesis Plus GX", "blastem": "BlastEm", "px68k": "px68k", "fuse": "Fuse", "bsnes": "bsnes", "bsnes_mercury_performance": "bsnes-mercury Performance", "bsnes_mercury_balanced": "bsnes-mercury Balanced", "bsnes_mercury_accuracy": "bsnes-mercury Accuracy", "mednafen_snes": "Beetle SNES", "mesen-s": "Mesen-S", "snes9x": "Snes9x", "theodore": "Theodore", "vecx": "Vecx", "mednafen_vb": "Beetle VB", "mednafen_wswan": "Beetle WonderSwan"};
var fileExts = {"3DO": "", "Amiga": ".adf, .adz, .dms, .ipf, .hdf, .hdz", "Amstrad CPC": ".dsk, .sna, .cdt, .voc, .cpr", "Arcade": "", "Atari 2600": ".a26", "Atari 5200": ".a52", "Atari 7800": ".a78", "Atari Jaguar": ".j64, .jag, .abs, .cof, .prg", "Atari Lynx": ".lnx", "Atari ST/TT": ".st, .stx, .msa", "ColecoVision": ".col, .cv", "Commodore 64": ".d64, .d6z, .d71, .d7z, .d80, .d81, .d82, .d8z, .g64, .g6z, .g41, .g4z, .x64, .x6z, .nib, .nbz, .d2m, .d4m, .t64, .p00", "Electronika BK": "", "Fairchild Channel F": ".chf", "GB/GBC/GBA": ".gb, .gbc, .gba", "GBA": ".gba", "Game & Watch": ".mgw", "GB/GBC": ".gb, .gbc", "Intellivision": ".int", "MS-DOS": ".exe", "MSX": ".mx1, .mx2, .dsk, .cas", "NES": ".nes, .fds, .unf, .unif", "Neo-Geo CD": "", "Neo-Geo Pocket": ".ngp, .ngc", "Nintendo 3DS": ".3ds, .3dsx, .cci, .cxi", "Nintendo 64": ".n64, .v64, .z64, .u1, .ndd", "Nintendo DS": ".nds, .srl", "Nintendo GC/Wii": ".gcm, .dol, .tgc, .wbfs, .gcz, .wad", "Odyssey 2": "", "PC Engine SuperGrafx": ".sgx", "PC Engine": ".pce", "PC-8000/8800": ".d88, .88d, .u88, .88u", "PC-98": ".d98, .98d, .fdi, .fdd, .tfd, .hdm, .hdi", "PC-FX": "", "PSP": ".pbp", "PlayStation 2": "", "PlayStation": "", "SNES": ".smc, .sfc, .swc, .fig, .bs", "ScummVM": ".exe", "Sega Dreamcast": ".cdi, .gdi, .lst", "Sega Systems": ".mdx, .md, .smd, .gen, .sms, .gg, .sg, .68k, .sgd", "Sega Genesis": ".mdx, .md, .smd, .gen, .sms, .68k, .sgd", "Sega Saturn": "", "Sharp X68000": ".dim, .dup, .2hd, .xdf, .hdf", "ZX Spectrum": ".tzx, .tap, .z80, .rzx, .scl", "ZX81": ".p, .t81", "Thomson MO/TO": ".fd, .sap, .k7, .m7, .m5", "Vectrex": ".vec", "Virtual Boy": ".vb, .vboy", "WonderSwan": ".ws, .wsc, .pc2"};
var multiFileCores = ["dosbox", "dosbox_pure", "opera", "fsuae", "puae", "cap32", "fbalpha2012", "fbneo", "mame2003_plus", "vice_x64", "neocd", "mednafen_supergrafx", "mednafen_pce_fast", "quasi88", "mednafen_pcfx", "mednafen_psx", "mednafen_psx_hw", "scummvm", "flycast", "mednafen_saturn", "mednafen_saturn_hw", "yabause", "yabasanshiro", "kronos", "px68k"];
var exclusiveMultiFileCores = ["dosbox", "dosbox_pure", "fbalpha2012", "fbneo", "mame2003_plus", "scummvm"]; // used for arcade systems, etc
var playlistExts = ".m3u, .cue, .ccd";
var playlistCores = ["opera", "fsuae", "puae", "cap32", "vice_x64", "neocd", "mednafen_supergrafx", "mednafen_pce_fast", "quasi88", "mednafen_pcfx", "mednafen_psx", "mednafen_psx_hw", "flycast", "mednafen_saturn", "mednafen_saturn_hw", "yabause", "yabasanshiro", "kronos", "px68k"]; // all of these must also be in multiFileCores
var cdromExts = ".iso, .img, .ciso, .cso, .chd";
var cdromCores = ["opera", "fsuae", "puae", "neocd", "dolphin", "mednafen_supergrafx", "mednafen_pce_fast", "ppsspp", "pcsx2", "mednafen_psx", "mednafen_psx_hw", "mednafen_saturn", "mednafen_saturn_hw", "yabause", "yabasanshiro", "kronos"]; // probably put these in playlistCores too
var multiSaveCores = ["pcsx2", "mednafen_psx", "mednafen_psx_hw", "scummvm", "flycast", "mednafen_saturn", "mednafen_saturn_hw"];
var noSaveCores = ["81", "dosbox", "dosbox_pure", "stella", "stella2014", "atari800", "a5200", "prosystem", "handy", "mednafen_lynx", "hatari", "bk", "freechaf", "gw", "freeintv", "bluemsx", "fmsx", "o2em", "np2kai", "px68k", "fuse"];
var noStateCores = ["dosbox", "atari800", "virtualjaguar", "hatari", "bk", "gw", "bluemsx", "pcsx2", "scummvm", "px68k"];
var preferredCores = ["opera", "puae", "cap32", "fbneo", "stella2014", "a5200", "prosystem", "virtualjaguar", "handy", "hatari", "gearcoleco", "vice_x64", "bk", "freechaf", "mgba", "vba_next", "gw", "sameboy", "freeintv", "dosbox_pure", "fmsx", "nestopia", "neocd", "mednafen_ngp", "citra", "mupen64plus_next", "melonds", "dolphin", "o2em", "mednafen_pce_fast", "mednafen_supergrafx", "np2kai", "quasi88", "mednafen_pcfx", "ppsspp", "mednafen_psx_hw", "pcsx2", "snes9x", "scummvm", "flycast", "blastem", "kronos", "genesis_plus_gx", "px68k", "81", "fuse", "theodore", "vecx", "mednafen_vb", "mednafen_wswan"];
var allCores = Object.keys(systems);
var allSystems = Object.keys(fileExts);
var allFileExts = Array.from(new Set(Object.values(fileExts).filter(i => i).join(", ").split(", "))).join(", ");
var systemsExperimentalFormat = Object.fromEntries(Object.values(systems).map(i => [i, allCores.filter(j => systems[j] == i)]));
var installedCores = ["a5200", "freechaf", "freeintv", "gearcoleco", "genesis_plus_gx", "handy", "mednafen_ngp", "mednafen_psx_hw", "mednafen_vb", "mednafen_wswan", "melonds", "mgba", "mupen64plus_next", "neocd", "nestopia", "o2em", "opera", "parallel_n64", "prosystem", "snes9x", "stella2014", "vecx", "virtualjaguar", "yabause"];
var installedSystems = allSystems.filter(i => installedCores.some(j => allCores.filter(k => systems[k] == i).includes(j)));
var installedFileExts = installedSystems.map(i => fileExts[i]).filter(i => i).join(", ");
var coreGithub = document.getElementById("coregithub");
var coreGithubLinks = {"81": "libretro/81-libretro", "dosbox": "libretro/dosbox-libretro", "dosbox_pure": "schellingb/dosbox-pure", "opera": "libretro/opera-libretro", "fsuae": "libretro/libretro-fsuae", "puae": "libretro/libretro-uae", "cap32": "libretro/libretro-cap32", "fbalpha2012": "libretro/fbalpha2012", "fbneo": "libretro/FBNeo", "mame2003_plus": "libretro/mame2003-plus-libretro", "stella": "stella-emu/stella", "stella2014": "libretro/stella2014-libretro", "atari800": "libretro/libretro-atari800", "a5200": "libretro/a5200", "prosystem": "libretro/prosystem-libretro", "virtualjaguar": "libretro/virtualjaguar-libretro", "handy": "libretro/libretro-handy", "mednafen_lynx": "libretro/beetle-lynx-libretro", "hatari": "libretro/hatari", "gearcoleco": "drhelius/Gearcoleco", "vice_x64": "libretro/vice-libretro", "bk": "libretro/bk-emulator", "freechaf": "libretro/FreeChaF", "gw": "libretro/gw-libretro", "vba_next": "libretro/vba-next", "vbam": "libretro/vbam-libretro", "meteor": "libretro/meteor-libretro", "mednafen_gba": "libretro/beetle-gba-libretro", "gpsp": "libretro/gpsp", "mgba": "libretro/mgba", "gambatte": "libretro/gambatte-libretro", "gearboy": "drhelius/Gearboy", "tgbdual": "libretro/tgbdual-libretro", "sameboy": "libretro/SameBoy", "o2em": "libretro/libretro-o2em", "freeintv": "libretro/FreeIntv", "fmsx": "libretro/fmsx-libretro", "bluemsx": "libretro/blueMSX-libretro", "neocd": "libretro/neocd_libretro", "mednafen_ngp": "libretro/beetle-ngp-libretro", "bnes": "libretro/bnes-libretro", "fceumm": "libretro/libretro-fceumm", "quicknes": "libretro/QuickNES_Core", "mesen": "libretro/Mesen", "nestopia": "libretro/nestopia", "citra": "libretro/citra", "mupen64plus_next": "libretro/mupen64plus-libretro-nx", "parallel_n64": "libretro/parallel-n64", "desmume2015": "libretro/desmume2015", "desmume": "libretro/desmume", "melonds": "libretro/melonds", "dolphin": "libretro/dolphin", "mednafen_pce_fast": "libretro/beetle-pce-fast-libretro", "mednafen_supergrafx": "libretro/beetle-supergrafx-libretro", "quasi88": "libretro/quasi88-libretro", "np2kai": "AZO234/NP2kai", "mednafen_pcfx": "libretro/beetle-pcfx-libretro", "mednafen_psx": "libretro/beetle-psx-libretro", "mednafen_psx_hw": "libretro/beetle-psx-libretro", "pcsx2": "libretro/pcsx2", "ppsspp": "hrydgard/ppsspp", "scummvm": "libretro/scummvm", "flycast": "flyinghead/flycast", "mednafen_saturn": "libretro/beetle-saturn-libretro", "mednafen_saturn_hw": "libretro/beetle-saturn-libretro", "yabause": "libretro/yabause", "yabasanshiro": "libretro/yabause/tree/yabasanshiro", "kronos": "libretro/yabause/tree/kronos", "picodrive": "libretro/picodrive", "genesis_plus_gx": "libretro/Genesis-Plus-GX", "blastem": "libretro/blastem", "px68k": "libretro/px68k-libretro", "fuse": "libretro/fuse-libretro", "bsnes": "libretro/bsnes-libretro", "bsnes_mercury_performance": "libretro/bsnes-mercury", "bsnes_mercury_balanced": "libretro/bsnes-mercury", "bsnes_mercury_accuracy": "libretro/bsnes-mercury", "mednafen_snes": "libretro/beetle-bsnes-libretro", "mesen-s": "libretro/Mesen-S", "snes9x": "libretro/snes9x", "theodore": "Zlika/theodore", "vecx": "libretro/libretro-vecx", "mednafen_vb": "libretro/beetle-vb-libretro", "mednafen_wswan": "libretro/beetle-wswan-libretro"};
var baseFsBundleDir = "/home/web_user/retroarch/bundle";
var baseFsSystemDir = "/home/web_user/retroarch/userdata/system/";
var baseFsConfigDir = "/home/web_user/retroarch/userdata/config/";
var baseFsSaveDir = "/home/web_user/retroarch/userdata/saves/";
var FSTracking = new EventTarget();
var writeToFileCooldown = {};
var saveObj = {};
var bundleErrors = 0;
var sramExts = ".srm, .sram, .ram, .gam, .sav, .dsv, .nvr, .SNA, .mcr";
var smasBrickFix = {"16a160ddd431a3db6fcd7453ffae9c4c": [80,65,84,67,72,0,127,160,0,8,169,1,133,160,141,0,22,107,1,191,182,0,4,34,160,255,0,6,189,164,0,4,34,160,255,0,69,79,70], "e87d43969bdf563d1148e3b35e8b5360": [80,65,84,67,72,0,129,160,0,8,169,1,133,160,141,0,22,107,1,193,182,0,4,34,160,255,0,6,191,164,0,4,34,160,255,0,69,79,70], "2071b049a463cefd7a0b7aeab8037ca0": [80,65,84,67,72,0,127,160,0,8,169,1,133,160,141,0,22,107,1,191,190,0,4,34,160,255,0,6,189,164,0,4,34,160,255,0,69,79,70]}; // Couldn't find SMAS+W SMC ROM [80,65,84,67,72,0,129,160,0,8,169,1,133,160,141,0,22,107,1,193,190,0,4,34,160,255,0,6,191,164,0,4,34,160,255,0,69,79,70]
// disable webcam for gameboy camera
var disableWebCam = true;
var appIsPwa = window.matchMedia("(display-mode: standalone)").matches;
// https://stackoverflow.com/a/11381730
var appIsPhone = false;
var appIsTouchscreen = false;
try {
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) appIsPhone = true;})(navigator.userAgent||navigator.vendor||window.opera);
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) appIsTouchscreen = true;})(navigator.userAgent||navigator.vendor||window.opera);
} catch (e) {
console.warn(e);
}
// query string into object
var queries = Object.fromEntries(window.location.search.substring(1).split("&").map(i => i.split("=")).map(i => i.map(i => i && decodeURIComponent(i))));
// core lists
function sortArray(array, sortTo) {
var orig = array.map(i => sortTo[i].toLowerCase());
var sorted = orig.map(i => i).sort();
var reSort = [];
var prev = -1;
var findex = -1;
for (var i = 0; i < sorted.length; i++) {
var curr = orig.indexOf(sorted[i]);
if (curr == findex) { // hit, find the next one
curr = orig.indexOf(sorted[i], prev + 1);
} else { // miss, set new findex
findex = curr;
}
prev = curr;
reSort.push(array[curr]);
}
return reSort;
}
function updateCoreList() {
var coreArr;
switch (coreOrder.selectedIndex) {
case 0:
coreArr = sortArray(installedCores, systems);
break;
case 1:
coreArr = sortArray(installedCores, coreNames);
break;
case 2:
coreArr = installedCores;
break;
default:
coreArr = installedCores;
break;
}
var aCoreList = "";
for (var i = 0; i < coreArr.length; i++) {
aCoreList += '
' + (coreNames[coreArr[i]] || coreArr[i]) + ' (' + systems[coreArr[i]] + ')';
}
coreList.innerHTML = aCoreList;
}
function showCoreList() {
updateCoreList();
document.body.classList.add("coreselect");
coreSelectArea.style.display = "block";
uploadArea.style.display = "none";
ffd.style.display = "block";
}
coreOrder.onchange = function() {
updateCoreList();
}
// back-forward cache fix. this was the only way that I found to do this. ULTRA STUPID!!!!!
window.addEventListener("load", function() {
window.setTimeout(function() {
updateCoreList();
}, 0);
}, false);
// Binary to UTF-8
function u8atoutf8(data) {
return new TextDecoder().decode(data);
}
function avShift(array, shift) {
for (var i = 0; i < array.length; i++) {
array[i] += shift;
}
return array;
}
// date time
function getTime() {
var dateTime = new Date();
return dateTime.getFullYear().toString()+"-"+(dateTime.getMonth()+1).toString()+"-"+dateTime.getDate().toString()+"-"+dateTime.getHours().toString()+"-"+dateTime.getMinutes().toString();
}
// bytes to human-readable string
function bytesToHumanReadable(bytes, si) {
bytes = bytes || 0;
var extension = -1;
while (bytes >= 1000) {
bytes /= si ? 1000 : 1024;
extension++;
}
return extension >= 8 ? "overflow" : bytes.toFixed(2) + " " + "KMGTPEZY".charAt(extension) + (!si && (extension > -1) ? "i" : "") + "B";
}
// js has no built-in capitalization function
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// key press stuff
function fakeKey(type, info) {
var e = new KeyboardEvent(type, {code: info.code || undefined, key: info.key || undefined, shiftKey: info.shiftKey || undefined});
document.dispatchEvent(e);
}
function fakeKeyPress(info) {
fakeKey("keydown", info);
window.setTimeout(function() {
fakeKey("keyup", info);
}, 50);
}
function fakeCharPress(key) {
if (charToCodeMap.hasOwnProperty(key)) fakeKeyPress({code: charToCodeMap[key].code, key: charToKeyMap.hasOwnProperty(key) ? charToKeyMap[key].key : key, shiftKey: charToCodeMap[key].hasOwnProperty("shift") ? true : false});
}
function sendText(text) {
for (var i = 0; i < text.length; i++) {
fakeCharPress(text.charAt(i));
}
}
function configIDToCode(configid) {
return Object.keys(codeToConfigIDMap).find(k => codeToConfigIDMap[k] == configid);
}
function isAudioAllowed() {
try {
var audio = new AudioContext();
var state = audio.state;
audio.close();
return (state == "running");
} catch (e) {
return false;
}
}
// indexedDB
function setIdbItem(key, value, customTransaction) {
(customTransaction || wIdb.transaction("main", "readwrite")).objectStore("main").put({key: key, value: value});
}
function getIdbItem(key, customTransaction) {
return new Promise(function(resolve) {
(customTransaction || wIdb.transaction("main", "readwrite")).objectStore("main").get(key).onsuccess = function(e) {
resolve(e.target.result ? e.target.result.value : null);
}
});
}
function getAllIdbItems(customTransaction) {
return new Promise(function(resolve) {
(customTransaction || wIdb.transaction("main", "readwrite")).objectStore("main").getAll().onsuccess = function(e) {
resolve(e.target.result ? e.target.result : null);
}
});
}
function removeIdbItem(key, customTransaction) {
(customTransaction || wIdb.transaction("main", "readwrite")).objectStore("main").delete(key);
}
function openIdb() {
var request = indexedDB.open("webretro", 2);
request.onsuccess = function(e) {
wIdb = e.target.result;
}
request.onupgradeneeded = function(e) {
wIdb = e.target.result;
var transaction = e.target.transaction;
switch (e.oldVersion) {
case 0:
// create the object store
wIdb.createObjectStore("main", {keyPath: "key"}).transaction.oncomplete = function() {
// look for saves in localStorage from old versions
var ls = Object.keys(localStorage);
for (var i = 0; i < ls.length; i++) {
if (ls[i].startsWith("RetroArch_saves_")) {
setIdbItem(ls[i], [{ext: ".srm", dir: "", data: new Uint8Array(JSON.parse(localStorage.getItem(ls[i])))}], transaction);
localStorage.removeItem(ls[i]);
}
}
}
break;
case 1:
// move the saves into arrays
(async function() {
var allItems = await getAllIdbItems(transaction);
for (var i = 0; i < allItems.length; i++) {
if (allItems[i].key.startsWith("RetroArch_saves_")) {
setIdbItem(allItems[i].key, [{ext: ".srm", dir: "", data: allItems[i].value}], transaction);
}
}
})();
break;
}
}
}
openIdb();
// side alerts
function sideAlert(initialText, time) {
var p = document.createElement("p");
p.className = "sidealert";
p.appendChild(document.createTextNode(initialText));
sideAlertHolder.appendChild(p);
window.setTimeout(function() {
p.classList.add("on");
}, 10);
this.dismiss = function() {
p.classList.remove("on");
window.setTimeout(function() {
p.remove();
}, 100);
}
this.setText = function(text) {
p.textContent = text;
}
if (time) window.setTimeout(this.dismiss, time);
}
// change background for status messages
function setStatus(message) {
loadStatus = message;
loadingText.textContent = message;
}
// remove status messages
function removeStatus(message) {
if (loadStatus == message) setStatus("");
}
// adjust canvas size to window
function adjustCanvasSize() {
var dpi = window.devicePixelRatio || 1;
var width = window.innerWidth * dpi;
var height = (window.innerHeight * dpi) - (actualMenuHeight * dpi);
if (Module && Module.setCanvasSize) {
Module.setCanvasSize(width, height);
} else {
canvas.width = width;
canvas.height = height;
}
}
window.addEventListener("resize", adjustCanvasSize, false);
adjustCanvasSize();
// emscripten is stupid and removes css width and height properties from the canvas - https://github.com/emscripten-core/emscripten/issues/6353
function canvasCssWorkaround(css) {
canvasCssWorkaroundElement.textContent = "#canvas { " + css + " }";
document.head.appendChild(canvasCssWorkaroundElement);
}
// adjust the menu bar height
function adjustActualMenuHeight() {
canvasCssWorkaround("top: " + actualMenuHeight + "px; height: calc(100vh - " + actualMenuHeight + "px);");
canvasMask.style.top = "" + actualMenuHeight + "px";
canvasMask.style.height = "calc(100vh - " + actualMenuHeight + "px)";
menuBar.style.height = "" + actualMenuHeight + "px";
adjustCanvasSize();
}
// menu hider
function adjustMenuHeight() {
if (menuHider.checked) {
actualMenuHeight = 0;
adjustActualMenuHeight();
} else {
actualMenuHeight = menuHeight;
adjustActualMenuHeight();
}
}
menuHider.onchange = adjustMenuHeight;
// logging
function log(log, userInput) {
console.log(log);
if (maxConsoleLength > 0 && wconsole.textContent.length > maxConsoleLength) wconsole.textContent = wconsole.textContent.substring(wconsole.textContent.indexOf("\n") + 1);
wconsole.textContent += (userInput ? "> " + userInput + "\n\t" + JSON.stringify(log) : log) + "\n";
wconsole.scrollTo({top: wconsole.scrollHeight});
}
// xhr
function grab(url, type, success, fail) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.overrideMimeType("text/plain; charset=x-user-defined");
req.responseType = type;
req.timeout = 8000;
req.onload = function() {
if (req.status >= 400) {
if (fail) fail(req.status);
} else {
if (success) success(this.response);
}
}
req.onerror = function() {
if (fail) fail(0);
}
req.ontimeout = function() {
if (fail) fail(0);
}
req.send();
}
// file readers
function readFile(file) {
return new Promise(function(resolve) {
var reader = new FileReader();
reader.onload = function() {
resolve(this.result);
}
reader.readAsArrayBuffer(file);
});
}
function downloadFile(data, name, mime) {
var a = document.createElement("a");
a.download = name;
a.href = URL.createObjectURL(new Blob([data], {type: mime || "application/octet-stream"}));
a.click();
window.setTimeout(function() {
URL.revokeObjectURL(a.href);
}, 2000);
}
function uploadFile(accept, callback) {
var input = document.createElement("input");
input.type = "file";
input.accept = accept;
input.onchange = async function() {
var file = this.files[0];
var data = await readFile(file);
callback({name: file.name, data: data});
}
input.click();
}
function uploadFileMulti(accept, callback) {
let directoryUpload = confirm("Upload a directory?");
var input = document.createElement("input");
input.type = "file";
if (directoryUpload) {
input.setAttribute("directory", "");
input.setAttribute("webkitDirectory", "");
} else {
input.setAttribute("multiple", "");
input.accept = accept;
}
input.onchange = async function() {
let datas = [];
for (var i = 0; i < this.files.length; i++) {
var name = directoryUpload ? (this.files[i].relativePath || this.files[i].webkitRelativePath || "").split("/").slice(1).join("/") : this.files[i].name;
var data = await readFile(this.files[i]);
datas.push({path: name, data: data});
if (i == this.files.length - 1 && callback) callback(datas);
}
}
input.click();
}
// scripts
function getScript(url, callback, err) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.onload = function() {
if (callback) callback();
}
script.onerror = function(e) {
document.body.removeChild(script);
if (err) err(e);
}
document.body.appendChild(script);
}
function getCore(name, callback, err) {
getScript(relativeBase + coreDir + name + "_libretro.js", callback, err);
}
// check for updates
function checkForUpdates() {
grab(infoJsonUrl, "text", function(text) {
try {
var updateObj = JSON.parse(text);
if (updateObj.webretro) {
latestVersion = updateObj.webretro;
if (updateObj.versions[webretroVersion.toString()]) versionIndicator.title = "New features in this version:\n\n- " + updateObj.versions[webretroVersion.toString()].changeList.join("\n- ");
if (latestVersion > webretroVersion && updateObj.versions[latestVersion.toString()]) {
updateNotice.textContent = "New webretro version available: v" + latestVersion.toString() + ". Features:\n\n- " + updateObj.versions[latestVersion.toString()].changeList.join("\n- ") + "\n\nThe site owner(s) can apply the update.";
updateNotice.style.display = "initial";
}
}
} catch (e) {
console.warn(e);
}
});
}
// download standalone webretro
function downloadStandaloneWebretro() {
grab(standaloneDownloadUrl, "text", function(data) {
alert("Downloading standalone webretro. Internet is required for operation.");
downloadFile(data, standaloneDownloadUrl.split("/").slice(-1)[0], "text/html");
}, function() {
alert("Failed to fetch file.");
});
}
downloadStandaloneButton.onclick = downloadStandaloneWebretro;
function getFileExtsForCore(core) {
return [fileExts[systems[core]], (cdromCores.includes(core) ? cdromExts : ""), (playlistCores.includes(core) ? playlistExts : "")].filter(i => i).join(", ");
}
// unzip file
function unzipFile(data, exts, callback, empty, notfound) {
new zip.ZipReader(new zip.Uint8ArrayReader(new Uint8Array(data))).getEntries().then(async function(entries) {
if (entries.length) {
for (var i = 0; i < entries.length; i++) {
if (!entries[i].directory && exts.split(", ").includes("." + u8atoutf8(entries[i].rawFilename).split(".").slice(-1)[0].toLowerCase())) {
var name = u8atoutf8(entries[i].rawFilename);
var uzd = await entries[i].getData(new zip.Uint8ArrayWriter());
callback(name, uzd.buffer);
break;
}
if (i == entries.length - 1 && notfound) notfound();
}
} else if (empty) empty();
});
}
// unzip all files
function unzipFileMulti(data, callback, empty) {
new zip.ZipReader(new zip.Uint8ArrayReader(new Uint8Array(data))).getEntries().then(async function(entries) {
if (entries.length) {
let datas = [];
for (var i = 0; i < entries.length; i++) {
if (!entries[i].directory) {
var name = u8atoutf8(entries[i].rawFilename);
var uzd = await entries[i].getData(new zip.Uint8ArrayWriter());
datas.push({path: name, data: uzd.buffer});
if (i == entries.length - 1 && callback) callback(datas);
}
}
} else if (empty) empty();
});
}
// zip files
async function zipFiles(files, callback) {
var u8aWriter = new zip.Uint8ArrayWriter("application/zip");
var writer = new zip.ZipWriter(u8aWriter);
for (var i = 0; i < files.length; i++) {
await writer.add(files[i].path, new zip.Uint8ArrayReader(new Uint8Array(files[i].data)));
}
await writer.close();
var zipped = await u8aWriter.getData();
callback(zipped.buffer);
}
// file renames
function replaceInFiles(files, find, replace) {
return files.map(i => ({path: i.path.replace(find, replace), data: i.data}));
}
// uauth uploads
function handleWebFile(data) {
if (data.message == "success") {
ffd.style.display = "none";
romUploadCallback([{path: data.name, data: data.data}]);
} else if (data.message == "error") {
alert("There was an error with the file picker. This may mean that you have to allow popup windows.");
}
}
function uploadWebFile(type, exts) {
uauth.open(type, exts.split(", "), handleWebFile);
}
// file tree to list, etc (for drag-and-drop files)
function readFileEntry(fileEntry) {
return new Promise(function(resolve) {
fileEntry.file(function(file) {
resolve(file);
});
});
}
function readDirectoryEntry(directoryEntry) {
return new Promise(function(resolve) {
directoryEntry.createReader().readEntries(function(entries) {
resolve(entries);
});
});
}
async function fileTreeToList(items) {
let newItems = [];
for (var i = 0; i < items.length; i++) {
if (items[i].isFile) {
newItems.push(items[i]);
} else if (items[i].isDirectory) {
var entries = await readDirectoryEntry(items[i]);
var contents = await fileTreeToList(entries);
newItems = newItems.concat(contents);
}
}
return newItems;
}
// rom upload
function readyRomUploads(exts) {
romUploadsReady = true;
// when a rom file is chosen
upload.onclick = function() {
if (multiFileCores.includes(core)) {
uploadFileMulti(exts, function(files) {
ffd.style.display = "none";
log("Succesfully read ROM files...");
romUploadCallback(files);
});
} else {
uploadFile(exts, function(file) {
ffd.style.display = "none";
log('Succesfully read ROM file "' + file.name + '"');
romUploadCallback([{path: file.name, data: file.data}]);
});
}
}
// web uploads
googleDriveUpload.onclick = function() {
uploadWebFile("drive", exts);
}
dropboxUpload.onclick = function() {
uploadWebFile("dropbox", exts);
}
oneDriveUpload.onclick = function() {
uploadWebFile("onedrive", exts);
}
// file drop (we need these to be global so they can be removed later)
window.fileDragEnter = function(e) {
if (e.dataTransfer.types.includes("Files")) ffd.classList.add("filehover");
}
window.fileDragOver = function(e) {
e.preventDefault();
}
window.fileDropped = async function(e) {
if (e.dataTransfer.types.includes("Files")) {
e.preventDefault();
ffd.style.display = "none";
let fileTree = Array.from(e.dataTransfer.items).map(i => i.webkitGetAsEntry());
let files = await fileTreeToList(fileTree);
let datas = [];
for (var i = 0; i < files.length; i++) {
var file = await readFileEntry(files[i]);
var name = files[i].fullPath.slice(1);
var data = await readFile(file);
datas.push({path: name, data: data});
}
// extract inside if only 1 directory is dropped
if (fileTree.length == 1 && fileTree[0].isDirectory) {
for (var i = 0; i < datas.length; i++) {
datas[i].path = datas[i].path.split("/").slice(1).join("/");
}
}
log("Succesfully read ROM file(s)...");
romUploadCallback(datas);
}
}
document.addEventListener("dragenter", fileDragEnter, false);
document.addEventListener("dragover", fileDragOver, false);
document.addEventListener("drop", fileDropped, false);
}
// chrome 102 launch queue https://developer.chrome.com/blog/new-in-chrome-102/#file-handlers
function readyLaunchQueue() {
if ("launchQueue" in window && LaunchParams && "files" in LaunchParams.prototype) {
launchQueue.setConsumer(async function(params) {
log("Launching with ROM file(s)...");
ffd.style.display = "none";
let datas = [];
for (var i = 0; i < params.files.length; i++) {
var file = await params.files[i].getFile();
var data = await readFile(file);
datas.push({path: params.files[i].name, data: data});
}
log("Succesfully read ROM file(s)...");
romUploadCallback(datas);
});
}
}
// rom fetch
function readyRomFetch() {
var romloc = (/^(https?:)?\/\//i).test(queries.rom) ? queries.rom : relativeBase + "roms/" + queries.rom;
var romFilename = queries.rom.split("/").slice(-1)[0];
grab(romloc, "arraybuffer", function(data) {
log("Succesfully fetched ROM from " + romloc);
romMode = "querystring";
romUploadCallback([{path: romFilename, data: data}]);
}, function(error) {
alert("Could not get ROM at " + romloc + " (Error " + error + ")");
romMode = "upload";
ffd.style.display = "block";
});
}
// safe writeFile
function safeWriteFile(path, data) {
FS.createPath("/", path.split("/").slice(1, -1).join("/"), true, true);
return FS.writeFile(path, data);
}
function uploadNCreate() {
uploadFile("", function(file) {
FS.writeFile("/" + file.name, new Uint8Array(file.data));
});
}
// console window
var conw = new jswindow({title: "Console", icon: "assets/terminal.svg"});
var wconsole = document.createElement("textarea");
wconsole.classList.add("console");
wconsole.setAttribute("spellcheck", "false");
wconsole.setAttribute("readonly", "");
wconsole.wconsolemarker = document.createElement("span");
wconsole.wconsolemarker.classList.add("consolemarker");
wconsole.wconsoleinput = document.createElement("input");
wconsole.wconsoleinput.type = "text";
wconsole.wconsoleinput.classList.add("consoleinput");
wconsole.wconsoleinput.title = "You can type things here as though you were using the browser console.";
wconsole.wconsoleinput.setAttribute("spellcheck", "false");
wconsole.wconsolemarker.onclick = function() { wconsole.wconsoleinput.focus(); }
wconsole.wconsoleinput.onkeydown = async function(e) {
e.stopPropagation();
if (e.isTrusted && e.code == "Enter") {
log(await eval("(async function() { return " + this.value + " })()"), this.value);
this.value = "";
}
}
conw.innerWindow.appendChild(wconsole);
conw.innerWindow.appendChild(wconsole.wconsolemarker);
conw.innerWindow.appendChild(wconsole.wconsoleinput);
consoleButton.onclick = function() {
conw.open({width: 450, height: 250, left: 100, top: 50});
wconsole.wconsoleinput.focus();
wconsole.scrollTo({top: wconsole.scrollHeight});
}
if (queries.hasOwnProperty("console")) conw.open({width: 450, height: 250, left: 100, top: 50});
// fullscreen button
fullscreenButton.onclick = function() {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen();
}
}
// theme selector
function setTheme(theme) {
if (themes.hasOwnProperty(theme)) {
document.body.dataset.theme = themes[theme].id;
menuHeight = themes[theme].menuHeight;
adjustMenuHeight();
}
}
currentTheme = localStorage.getItem("webretro_settings_theme") || defaultTheme;
setTheme(currentTheme);
try {
themeSelector.querySelector("[value=" + currentTheme + "]").checked = true;
} catch (e) {
console.warn(e);
}
themeSelector.onchange = function(e) {
currentTheme = e.target.value;
setTheme(currentTheme);
localStorage.setItem("webretro_settings_theme", currentTheme);
}
// modal windows (managers)
function openManager(type) {
if (managers[type]) {
if (managerClosed[currentManager]) managerClosed[currentManager]();
currentManager = type;
if (managerOpened[type]) managerOpened[type]();
managerTitle.textContent = capitalize(managerNames[type] || type + "s");
clearManagers();
managers[type].style.display = "block";
modals.style.display = "block";
}
}
function clearManagers() {
Object.values(managers).forEach(function(e) {
e.style.display = "none";
});
}
function closeManagers() {
modals.style.display = "none";
clearManagers();
managerTitle.textContent = "";
if (managerClosed[currentManager]) managerClosed[currentManager]();
currentManager = undefined;
}
managerClose.onclick = closeManagers;
// --- code for the keybind manager ---
// convert between config strings and objects
function configStrToObj(str) {
var convert1 = str.slice(0, -1).split("\n");
var convert2 = {};
for (var i = 0; i < convert1.length; i++) {
var convert3 = convert1[i].split(" = ");
convert2[convert3[0]] = convert3[1].slice(1, -1);
}
return convert2;
}
function configObjToStr(obj) {
var convert1 = Object.keys(obj);
var convert2 = "";
for (var i = 0; i < convert1.length; i++) {
convert2 += convert1[i] + ' = "' + obj[convert1[i]] + '"\n';
}
return convert2;
}
// load config saved in localStorage
var defaultKeybindsObj = configStrToObj(defaultKeybinds);
var savedKeybindsObj = localStorage.getItem("RetroArch_settings_keybinds") ? Object.assign(Object.assign({}, defaultKeybindsObj), configStrToObj(localStorage.getItem("RetroArch_settings_keybinds"))) : Object.assign({}, defaultKeybindsObj);
var keybindsObj = Object.assign({}, savedKeybindsObj);
var validKeybinds = Object.keys(defaultKeybindsObj);
// update the config list
function createConfigList() {
keybindTable.innerHTML = "";
// make the list
for (var i = 0; i < validKeybinds.length; i++) {
keybindTable.innerHTML += "" + validKeybinds[i].replace(/^input_/, "") + " | " + keybindsObj[validKeybinds[i]] + " |
";
}
// highlight conflicting keys
var keysList = Object.values(keybindsObj);
for (var i = 0; i < validKeybinds.length; i++) {
var matches = keysList.filter(v => v == keybindsObj[validKeybinds[i]]);
if (matches.length > 1 && !(matches[0] == "nul")) keybindTable.children[i].lastElementChild.classList.add("conflict");
}
}
// rebinding a key
keybindTable.onclick = function(e) {
if (e.target.tagName == "TD" && !e.target.nextElementSibling) {
let valueElement = e.target;
let keyNo = Array.from(keybindTable.children).indexOf(e.target.parentElement);
valueElement.classList.remove("conflict");
valueElement.textContent = "press a key (escape to unbind)";
function newKeyHandler(e) {
if (e.code == "Escape") {
keybindsObj[validKeybinds[keyNo]] = "nul";
createConfigList();
} else {
keybindsObj[validKeybinds[keyNo]] = codeToConfigIDMap[e.code] || "nul";
createConfigList();
}
finishKeybindInput();
}
function cancelKeybindInput() {
finishKeybindInput();
createConfigList();
}
function finishKeybindInput() {
document.removeEventListener("keydown", newKeyHandler);
document.removeEventListener("mousedown", cancelKeybindInput);
}
document.addEventListener("keydown", newKeyHandler, false);
document.addEventListener("mousedown", cancelKeybindInput, false);
}
}
function tryApplyConfig() {
if (mainCompleted) {
FS.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", nulKeys + configObjToStr(savedKeybindsObj) + extraConfig);
Module._cmd_reload_config();
}
}
// save the keybinds to localStorage, and apply them
saveKeybinds.onclick = function() {
savedKeybindsObj = Object.assign({}, keybindsObj);
localStorage.setItem("RetroArch_settings_keybinds", configObjToStr(savedKeybindsObj));
tryApplyConfig();
alert("Saved!");
}
resetKeybinds.onclick = function() {
if (confirm("Are you sure you want to reset all of the keybinds to their default values?")) {
savedKeybindsObj = Object.assign({}, defaultKeybindsObj);
keybindsObj = Object.assign({}, savedKeybindsObj);
localStorage.removeItem("RetroArch_settings_keybinds");
createConfigList();
tryApplyConfig();
}
}
// --- code for the screenshot manager ---
// zip and download all of the screenshots in the list
downloadAllScreenshots.onclick = function() {
if (screenshotDatas.length) {
zipFiles(replaceInFiles(screenshotDatas, "rom", romName), function(zd) {
downloadFile(zd, "screenshots-" + getTime() + ".zip", "application/zip");
});
} else {
alert("There are no screenshots to download!");
}
}
// update the screenshot list
function createScreenshotList() {
var screenshots = FS.analyzePath("/home/web_user/retroarch/userdata/screenshots/").exists ? FS.readdir("/home/web_user/retroarch/userdata/screenshots/").filter(k => ![".", ".."].includes(k)) : [];
screenshotsDiv.innerHTML = "";
for (var i = 0; i < screenshots.length; i++) {
var screenshotData = FS.readFile("/home/web_user/retroarch/userdata/screenshots/" + screenshots[i]);
var blobUrl = window.URL.createObjectURL(new Blob([screenshotData], {type: "image/png"}));
screenshotDatas[i] = {path: screenshots[i], data: screenshotData.buffer};
screenshotObjUrls[i] = blobUrl;
screenshotsDiv.innerHTML += '";
}
}
// why I didn't just use the DOM? I don't know
screenshotsDiv.onclick = function(e) {
if (e.target.tagName == "INPUT") {
var screenshotNo = Array.from(screenshotsDiv.children).indexOf(e.target.parentElement);
switch (e.target.dataset.action) {
case "download":
downloadFile(screenshotDatas[screenshotNo].data, screenshotDatas[screenshotNo].path.replace("rom", romName), "image/png");
break;
case "delete":
if (confirm("Are you sure you want to delete this screenshot?")) {
// doing all this is probably more efficient then reloading all of the screenshots
window.URL.revokeObjectURL(screenshotObjUrls[screenshotNo]);
FS.unlink("/home/web_user/retroarch/userdata/screenshots/" + screenshotDatas[screenshotNo].path);
screenshotObjUrls.splice(screenshotNo, 1);
screenshotDatas.splice(screenshotNo, 1);
e.target.parentElement.remove();
}
break;
}
}
}
// --- code for the save/state manager ---
function updateQuotaDisplay() {
navigator.storage.estimate().then(function(info) {
quotaText.textContent = "Storage used (estimate): " + bytesToHumanReadable(info.usage) + " / " + bytesToHumanReadable(info.quota) + " (" + (info.usage / info.quota).toFixed(2) + "%)";
});
}
// update the save list
function createSaveList() {
updateQuotaDisplay();
getAllIdbItems().then(function(items) {
saveTable.innerHTML = "";
// make the list
for (var i = 0; i < items.length; i++) {
if ((/^RetroArch_(saves|states)_/).test(items[i].key)) {
var sName = items[i].key.replace(/^RetroArch_(saves|states)_/, "");
var sType = (/^RetroArch_saves_/).test(items[i].key) ? "save" : "state";
saveIDs.push({id: items[i].key, name: sName, type: sType});
saveTable.innerHTML += "" + capitalize(sType) + ": " + sName + ' | DownloadDelete |
';
}
}
});
}
saveTable.onclick = function(e) {
if (e.target.tagName == "SPAN") {
let saveNo = Array.from(saveTable.children).indexOf(e.target.parentElement.parentElement);
switch (e.target.dataset.action) {
case "download":
getIdbItem(saveIDs[saveNo].id).then(function(data) {
if (saveIDs[saveNo].type == "save") {
var files = replaceInFiles(saveArrToFiles(data), "ROMNAME", saveIDs[saveNo].name);
if (files.length == 1) {
downloadFile(files[0].data, "game-sram-" + saveIDs[saveNo].name + "-" + getTime() + "." + files[0].path.split(".").slice(1).join("."));
} else {
zipFiles(files, function(zd) {
downloadFile(zd, "game-sram-" + saveIDs[saveNo].name + "-" + getTime() + ".zip", "application/zip");
});
}
} else {
downloadFile(data, "game-state-" + saveIDs[saveNo].name + "-" + getTime() + ".state");
}
});
break;
case "delete":
if (confirm("Are you sure you want to delete this " + saveIDs[saveNo].type + ' for "' + saveIDs[saveNo].name + '"?') && confirm("Really really sure?")) {
removeIdbItem(saveIDs[saveNo].id);
saveIDs.splice(saveNo, 1);
e.target.parentElement.parentElement.remove();
updateQuotaDisplay();
}
break;
}
}
}
// --- master rom selector ---
function getMasterRom(files) {
return new Promise(function(resolve) {
// some auto detecting
var recommendedExts = "";
if (playlistCores.includes(core)) {
recommendedExts = playlistExts;
} else if (["dosbox", "dosbox_pure", "scummvm"].includes(core)) {
recommendedExts = ".exe, .bat, .com";
}
if (recommendedExts) {
var recommendedExtsArray = recommendedExts.split(", ");
var detectedFiles = files.filter(i => recommendedExtsArray.includes("." + i.path.toLowerCase().split(".").slice(-1)[0]));
// if ONLY one match is found, use it
if (detectedFiles.length == 1) {
resolve(files.indexOf(detectedFiles[0]));
return;
}
}
openManager("romSelector");
if (recommendedExts) {
recommendedExtensions.textContent = "Recommended file extensions: " + recommendedExts;
} else {
romSelectorTable.parentElement.classList.add("fulltableparent");
}
romSelectorTable.innerHTML = "";
// make the list
for (var i = 0; i < files.length; i++) {
romSelectorTable.innerHTML += "" + files[i].path + " |
";
}
romSelectorTable.onclick = function(e) {
closeManagers();
resolve(Array.from(romSelectorTable.children).indexOf(e.target.parentElement));
return;
}
});
}
// --- end manager-specific code ---
var managerOpened = {
"keybind": function() {
createConfigList();
},
"screenshot": function() {
createScreenshotList();
},
"save": function() {
createSaveList();
},
"romSelector": function() {
managerClose.style.display = "none";
}
};
var managerClosed = {
"keybind": function() {
keybindsObj = Object.assign({}, savedKeybindsObj);
},
"screenshot": function() {
// clear the blob: urls used for the screenshots
for (var i = 0; i < screenshotObjUrls.length; i++) {
window.URL.revokeObjectURL(screenshotObjUrls[i]);
}
screenshotObjUrls = [];
screenshotDatas = [];
},
"save": function() {
saveIDs = [];
},
"romSelector": function() {
managerClose.style.display = "initial";
}
};
// opening the managers
keybindsButton.onclick = function(e) {
e.preventDefault();
openManager("keybind");
}
screenshotsButton.onclick = function(e) {
e.preventDefault();
openManager("screenshot");
}
savesButton.onclick = function(e) {
e.preventDefault();
openManager("save");
}
statesButton.onclick = function(e) {
e.preventDefault();
openManager("save");
};
// ---------- START LOAD ----------
(function() {
versionIndicator.textContent = "v" + webretroVersion.toString();
checkForUpdates();
// ?system query
if (!queries.core && queries.system) {
var detectedCores = allCores.filter(k => systems[k].toLowerCase() == queries.system.toLowerCase());
var usableCores = installedCores.filter(k => systems[k].toLowerCase() == queries.system.toLowerCase());
var usingCore = usableCores.find(k => preferredCores.includes(k)) || usableCores[0];
if (usingCore) {
queries.core = usingCore;
} else if (queries.system.toLowerCase() == "autodetect") {
queries.core = "autodetect";
} else if (!detectedCores.length) {
alert('Could not find any cores matching the system "' + queries.system + '".');
} else {
alert("Found the core(s) " + detectedCores.join(", ") + ", but none were marked as installed.");
}
}
// ?core query
if (queries.core) {
try {
if (!window.chrome) alert("Best performance on Chrome!");
} catch (e) {
console.warn(e);
}
// show menu bar
menuBar.style.display = "block";
if (queries.core.toLowerCase() == "autodetect") {
romUploadCallback = autodetectCoreHandler;
systemName.textContent = "";
readyRomUploads(".zip, " + allFileExts);
document.addEventListener("DOMContentLoaded", readyLaunchQueue, false);
} else {
romUploadCallback = initFromFile;
core = queries.core;
setStatus("Getting core");
// detect system for ROM upload
systemName.textContent = systems[core] || "";
// add an s to the upload button if using a multifile core
if (multiFileCores.includes(core)) upload.value += "s";
// core github link
if (coreGithubLinks[core]) {
coreGithub.style.setProperty("display", "inline-block", "important");
coreGithub.href = "https://github.com/" + coreGithubLinks[core];
}
// show the pre-start options
if (coreOptions[core]) {
pso.style.display = "block";
try {
pso.querySelector("[data-core=" + core + "]").style.display = "block";
} catch (e) {
console.warn(e);
}
}
getCore(core, function() {
removeStatus("Getting core");
log("Got core: " + core);
if (romMode != "querystring") document.title = (coreNames[core] || core) + (appIsPwa ? "" : " | webretro");
readyRomUploads([".zip" + (exclusiveMultiFileCores.includes(core) ? "" : ", .bin"), (allCores.includes(core) ? getFileExtsForCore(core) : allFileExts)].filter(i => i).join(", "));
}, function() {
// core loading error
alert('Could not load specified core "' + core + '". Here is a list of available cores.');
showCoreList();
});
}
// ?rom query
if (queries.rom) {
readyRomFetch();
} else {
// prompt user to upload ROM file
romMode = "upload";
ffd.style.display = "block";
}
} else {
// no core specified
showCoreList();
}
})();
// ----------- END LOAD -----------
// start emulator from file(s)
function initFromFile(files) {
if (files.length == 1 && files[0].path.split(".").slice(-1)[0].toLowerCase() == "zip") {
if (multiFileCores.includes(core)) {
log("Zip file detected, unzipping... (multi-file ROM detected... probably)");
unzipFileMulti(files[0].data, function(dataArr) {
readyForInit(dataArr);
}, function() {
alert("That zip file appears to be empty!");
});
} else {
log("Zip file detected, unzipping... (single-file ROM detected)");
unzipFile(files[0].data, [".bin", getFileExtsForCore(core)].filter(i => i).join(", "), function(name, contents) {
readyForInit([{path: name, data: contents}]);
}, function() {
alert("That zip file appears to be empty!");
}, function() {
alert("Couldn't find a valid ROM file in that zip file. Are you using the right core? This is " + systems[core] + ". (The ROM has to be at the base directory of the zip file)");
});
}
} else {
readyForInit(files);
}
}
// autodetect core mode
function autodetectCoreHandler(files) {
if (files.length == 1) {
if (files[0].path.split(".").slice(-1)[0].toLowerCase() == "zip") {
log("Zip file detected, unzipping...");
unzipFile(files[0].data, allFileExts, function(name, contents) {
autodetectCore(name, contents);
}, function() {
alert("That zip file appears to be empty!");
}, function() {
alert("Couldn't find a valid ROM file in that zip file. (The ROM has to be at the base directory of the zip file)");
});
} else {
autodetectCore(files[0].path, files[0].data);
}
} else {
alert("Unable to autodetect when multiple files are chosen");
}
}
function autodetectCore(name, data) {
var nameExt = "." + name.split(".").slice(-1)[0].toLowerCase();
var detectedSystem = allSystems.find(k => fileExts[k].split(", ").includes(nameExt));
var detectedCores = allCores.filter(k => systems[k] == detectedSystem);
var usableCores = installedCores.filter(k => systems[k] == detectedSystem);
var usingCore = usableCores.find(k => preferredCores.includes(k)) || usableCores[0];
if (usingCore) {
core = usingCore;
setStatus("Getting core");
// show the pre-start options
if (coreOptions[core]) {
pso.style.display = "block";
try {
pso.querySelector("[data-core=" + core + "]").style.display = "block";
} catch (e) {
console.warn(e);
}
}
getCore(core, function() {
removeStatus("Getting core");
log("Got core: " + core);
readyForInit([{path: name, data: data}]);
});
} else if (!detectedCores.length) {
alert('Unrecognized file extension "' + nameExt + '". This does not mean that it is unsupported, it may just mean that it is not auto-detectable.');
} else {
alert("Found the core(s) " + detectedCores.join(", ") + " for system " + detectedSystem + ", but none were marked as installed.");
}
}
// if the ROM is specified in the querystring, we will need to wait until the user has clicked to start the emulator https://goo.gl/7K7WLu
function readyForInit(files) {
// undefine romUploadCallback to make sure initialization only happens once (it shouldn't anyway)
romUploadCallback = function() {};
// set the romName now if using single-file rom
if (files.length == 1) {
romName = files[0].path.split("/").slice(-1)[0].split(".")[0];
document.title = romName + (appIsPwa ? "" : " | webretro");
}
if (queries.romshift) {
let shift = parseInt(queries.romshift);
for (var i = 0; i < files.length; i++) {
files[i].data = avShift(new Uint8Array(files[i].data), shift).buffer;
}
}
// remove the file drop listeners
if (romUploadsReady) {
document.removeEventListener("dragenter", fileDragEnter);
document.removeEventListener("dragover", fileDragOver);
document.removeEventListener("drop", fileDropped);
}
if (romMode == "querystring" && (queries.hasOwnProperty("forcestartbutton") || !isAudioAllowed())) {
// start button (don't delete this section, audio contexts are not allowed to start until a user gesture on the page, in this case, clicking the start button) https://goo.gl/7K7WLu
startButton.style.display = "initial";
startButton.onclick = function() {
startButton.style.display = "none";
initFromData(files);
}
} else {
initFromData(files);
}
}
// prepare FS with bundle
function prepareBundle() {
setStatus("Getting assets");
log("Starting bundle fetch");
let bundleSTime = performance.now();
grab(bundleCdnLatest + "bundle/indexedfiles-v2.txt", "text", function(data) {
try {
var splitData = data.split(",,,\n");
fsBundleDirs = JSON.parse(splitData[0]);
fsBundleFiles = splitData[1].split("\n");
// make the paths
FS.createPath("/", baseFsBundleDir.substring(1), true, true);
for (var i = 0; i < fsBundleDirs.length; i++) {
FS.createPath(baseFsBundleDir + fsBundleDirs[i][0], fsBundleDirs[i][1], true, true);
}
loadingBar.style.display = "initial";
loadingBar.value = 0;
let step = 1 / fsBundleFiles.length;
let num = 0;
// make the files
for (let i = 0; i < fsBundleFiles.length; i++) {
grab(bundleCdn + "bundle" + fsBundleFiles[i], "arraybuffer", function(data) {
FS.writeFile(baseFsBundleDir + fsBundleFiles[i], new Uint8Array(data));
loadingBar.value += step;
if (++num == fsBundleFiles.length) donePreparingBundle(performance.now() - bundleSTime);
}, function() {
bundleErrors++;
loadingBar.value += step;
if (++num == fsBundleFiles.length) donePreparingBundle(performance.now() - bundleSTime);
});
}
} catch (e) {
console.warn(e);
log("Failed to get asset bundle, skipping");
bundleReady = true;
removeStatus("Getting assets");
}
}, function() {
log("Failed to get asset bundle, skipping");
bundleReady = true;
removeStatus("Getting assets");
});
}
function donePreparingBundle(tooktime) {
loadingBar.style.display = "none";
extraConfig += 'menu_minimal_assets = "true"\n';
bundleReady = true;
removeStatus("Getting assets");
log("Finished bundle fetch in " + (tooktime / 1000).toFixed(1) + " seconds, " + bundleErrors + " errors");
}
// prepare FS with BIOSes
function prepareBios() {
if (bioses[core]) {
let bios = bioses[core];
let num = 0;
FS.createPath("/", baseFsSystemDir.substring(1) + bios.path, true, true);
for (let i = 0; i < bios.files.length; i++) {
grab(biosCdn + bios.files[i], "arraybuffer", function(data) {
FS.writeFile(baseFsSystemDir + bios.path + bios.files[i], new Uint8Array(data));
log("BIOS fetch: Success " + bios.files[i]);
if (++num == bios.files.length) biosReady = true;
}, function() {
log("BIOS fetch: Failed " + bios.files[i]);
if (++num == bios.files.length) biosReady = true;
});
}
} else {
biosReady = true;
}
}
// tell the user to not rename the rom
function doNotRename() {
if (romMode == "upload" && !localStorage.getItem("webretro_settings_pastFirstSave")) {
alert("WARNING: Do not rename your ROM file after this! The save data is specific to the ROM file name!");
localStorage.setItem("webretro_settings_pastFirstSave", "true");
}
}
// converting save lists
function saveArrToObj(arr) {
let obj = {};
for (var i = 0; i < arr.length; i++) {
obj[arr[i].dir + "ROMNAME" + arr[i].ext] = arr[i].data;
}
return obj;
}
function saveObjToArr(obj) {
return Object.entries(obj).map(i => ({ext: i[0].split("ROMNAME")[1], dir: i[0].split("ROMNAME")[0], data: i[1]}));
}
function saveArrToFiles(arr) {
let files = [];
for (var i = 0; i < arr.length; i++) {
files.push({path: arr[i].dir + "ROMNAME" + arr[i].ext, data: arr[i].data.buffer});
}
return files;
}
function saveFilesToArr(files) {
let arr = [];
for (var i = 0; i < files.length; i++) {
arr.push({ext: files[i].path.split("ROMNAME")[1], dir: files[i].path.split("ROMNAME")[0], data: new Uint8Array(files[i].data)});
}
return arr;
}
// save game
function saveSRAMHandler(path) {
saveObj[path.replace(baseFsSaveDir, "").replace("rom", "ROMNAME")] = FS.readFile(path);
setIdbItem("RetroArch_saves_" + romName, saveObjToArr(saveObj));
new sideAlert("Saved", 3000);
doNotRename();
}
// save state
function saveStateHandler() {
if (FS.analyzePath("/home/web_user/retroarch/userdata/states/rom.state").exists) {
setIdbItem("RetroArch_states_" + romName, FS.readFile("/home/web_user/retroarch/userdata/states/rom.state"));
doNotRename();
} else {
new sideAlert("There was an error saving state. Please try again.", 5000);
}
}
// autosaving
function autosaveSRAM() {
if (autosave.checked && !document.hidden && !isPaused) {
new sideAlert("Autosaving...", 3000);
Module._cmd_savefiles();
}
window.setTimeout(function() {
autosaveSRAM();
}, 300000);
}
// writeToFile router
function writeToFileHandler(path) {
// console.log("%c" + path, "color: #8888ff");
if (path.startsWith(baseFsSaveDir)) {
saveSRAMHandler(path);
} else if (path.startsWith("/home/web_user/retroarch/userdata/states/")) {
if (path == "/home/web_user/retroarch/userdata/states/rom.state") saveStateHandler();
}
}
// runs after emulator starts
function afterStart() {
mainCompleted = true;
adjustCanvasSize();
menuBar.classList.add("show");
// functions for save and state buttons
// states
saveState.classList.remove("disabled");
saveState.onclick = function() {
Module._cmd_save_state();
}
importState.classList.remove("disabled");
importState.onclick = function() {
if (noStateCores.includes(core)) {
alert("Core does not support save states.");
} else {
uploadFile(".bin, .state", function(file) {
setIdbItem("RetroArch_states_" + romName, new Uint8Array(file.data));
FS.writeFile("/home/web_user/retroarch/userdata/states/rom.state", new Uint8Array(file.data));
new sideAlert("Imported state (press load state)", 3000);
});
}
}
loadState.classList.remove("disabled");
loadState.onclick = function() {
Module._cmd_load_state();
}
exportState.classList.remove("disabled");
exportState.onclick = function() {
if (FS.analyzePath("/home/web_user/retroarch/userdata/states/rom.state").exists) {
downloadFile(FS.readFile("/home/web_user/retroarch/userdata/states/rom.state"), "game-state-" + romName + "-" + getTime() + ".state");
} else {
alert("No state to export.");
}
}
undoSaveState.classList.remove("disabled");
undoSaveState.onclick = function() {
Module._cmd_undo_save_state();
}
undoLoadState.classList.remove("disabled");
undoLoadState.onclick = function() {
Module._cmd_undo_load_state();
}
// saves
saveGame.classList.remove("disabled");
saveGame.onclick = function() {
new sideAlert("Saving...", 3000);
Module._cmd_savefiles();
}
importSave.classList.remove("disabled");
importSave.onclick = function() {
if (noSaveCores.includes(core)) {
alert("Core does not support SRAM.");
} else {
function done() {
if (confirm("Save imported. Reloading now for changes to take effect.")) {
autosave.checked = false;
window.onbeforeunload = function() {}
window.location.reload();
}
}
if (multiSaveCores.includes(core)) {
uploadFileMulti(".zip, .bin, " + sramExts, function(files) {
if (files.length == 1) {
if (files[0].path.split(".").slice(-1)[0].toLowerCase() == "zip") {
unzipFileMulti(files[0].data, function(uzfiles) {
setIdbItem("RetroArch_saves_" + romName, saveFilesToArr(replaceInFiles(uzfiles, romName, "ROMNAME")));
done();
}, function() {
alert("Zip File is empty");
});
} else {
setIdbItem("RetroArch_saves_" + romName, [{ext: "." + files[0].path.split(".").slice(-1)[0], dir: "", data: new Uint8Array(file.data)}]);
done();
}
} else {
setIdbItem("RetroArch_saves_" + romName, saveFilesToArr(replaceInFiles(files, romName, "ROMNAME")));
done();
}
});
} else {
uploadFile(".bin, " + sramExts, function(file) {
setIdbItem("RetroArch_saves_" + romName, [{ext: "." + file.name.split(".").slice(-1)[0], dir: "", data: new Uint8Array(file.data)}]);
done();
});
}
}
}
exportSave.classList.remove("disabled");
exportSave.onclick = function() {
var files = replaceInFiles(saveArrToFiles(saveObjToArr(saveObj)), "ROMNAME", romName);
if (!files.length) {
alert("No save to export.");
} else if (files.length == 1) {
downloadFile(files[0].data, "game-sram-" + romName + "-" + getTime() + "." + files[0].path.split(".").slice(1).join("."));
} else {
zipFiles(files, function(zd) {
downloadFile(zd, "game-sram-" + romName + "-" + getTime() + ".zip", "application/zip");
});
}
}
// start autosave loop
autosave.removeAttribute("disabled");
autosave.parentElement.parentElement.classList.remove("disabled");
window.setTimeout(function() {
autosaveSRAM();
}, 300000);
// toggle between sharp and smooth canvas graphics
smooth.removeAttribute("disabled");
smooth.parentElement.parentElement.classList.remove("disabled");
smooth.onclick = function() {
if (this.checked) {
canvas.className = "textureSmooth";
} else {
canvas.className = "texturePixelated";
}
}
// pause and resume
pause.classList.remove("disabled");
pause.onclick = function() {
if (this.textContent.trim() == "Pause") {
Module.pauseMainLoop();
isPaused = true;
this.textContent = "Resume";
document.body.classList.add("paused");
} else {
Module.resumeMainLoop();
isPaused = false;
this.textContent = "Pause";
document.body.classList.remove("paused");
}
}
resumeOverlay.onclick = function() {
pause.click();
}
// toggle menu
menuButton.classList.remove("disabled");
menuButton.onclick = function() {
Module._cmd_toggle_menu();
}
// reset
resetButton.classList.remove("disabled");
resetButton.onclick = function() {
Module._cmd_reset();
}
resetButton2.classList.remove("disabled");
resetButton2.onclick = function() {
Module._cmd_reset();
}
// toggle mouse grab
mouseGrabButton.classList.remove("disabled");
mouseGrabButton.onclick = function(e) {
e.target.parentElement.style.display = "none";
Module._cmd_toggle_grab_mouse();
window.setTimeout(function() {
canvas.focus();
canvas.requestPointerLock();
e.target.parentElement.style.display = "";
}, 20);
}
// toggle game focus
gameFocusButton.classList.remove("disabled");
gameFocusButton.onclick = function(e) {
e.target.parentElement.style.display = "none";
Module._cmd_toggle_game_focus();
window.setTimeout(function() {
canvas.focus();
canvas.requestPointerLock();
e.target.parentElement.style.display = "";
}, 20);
}
// screenshot button
takeScreenshot.classList.remove("disabled");
takeScreenshot.onclick = function() {
Module._cmd_take_screenshot();
}
// ctrl+v inside canvas
document.addEventListener("keydown", function(e) {
if (e.ctrlKey && e.code == "KeyV") {
fakeKeyPress({code: "Backspace"});
navigator.clipboard.readText().then(function(text) {
sendText(text);
});
}
}, false);
}
// start
function initFromData(data) {
window.onbeforeunload = function() { return true; }
async function waitForReady() {
if (wasmReady && bundleReady && biosReady) {
setStatus("Waiting for emulator");
log(data.length == 1 ? "Initializing with " + bytesToHumanReadable(data[0].data.byteLength) + " of data" : "Initializing with multiple files");
updateNotice.style.display = "none";
canvas.addEventListener("contextmenu", function(e) {
e.preventDefault();
}, false);
adjustCanvasSize();
// prevent defaults for key presses
document.addEventListener("keydown", function(e) {
if (pdKeys.includes(e.which)) e.preventDefault();
}, false);
// fix for iframe bug
if (window.self != window.top) {
canvas.addEventListener("mousedown", function() {
window.focus();
}, false);
if (!queries.hasOwnProperty("noautorefocus")) {
window.addEventListener("blur", function(e) {
window.setTimeout(function() {
window.focus();
}, 0);
}, false);
}
}
// create the rom(s) in the filesystem
if (data.length == 1) {
// single-rom mode
realRomExt = data[0].path.split(".").slice(-1)[0] || "bin";
FS.createPath("/", "rom", true, true);
FS.writeFile("/rom/rom." + realRomExt, new Uint8Array(data[0].data));
Module.arguments[0] = "/rom/rom." + realRomExt;
} else {
// multi-rom mode
var masterIndex = await getMasterRom(data);
// now set the romName for multi-file roms
romName = data[masterIndex].path.split("/").slice(-1)[0].split(".")[0];
document.title = romName + (appIsPwa ? "" : " | webretro");
realRomExt = data[masterIndex].path.split(".").slice(-1)[0] || "bin";
data[masterIndex].path = "rom." + realRomExt;
Module.arguments[0] = "/rom/" + data[masterIndex].path;
// optionally rename any direct dependencies to "rom"
if (exclusiveMultiFileCores.includes(core) && confirm('Rename similar files? (Use if you get "Unable to find rom" errors. Otherwise don\'t use.)')) {
for (var i = 0; i < data.length; i++) {
if (!data[i].path.includes("/")) data[i].path = data[i].path.replace(romName, "rom");
}
}
FS.createPath("/", "rom", true, true);
var parentDirs = Array.from(new Set(data.map(i => i.path.split("/").slice(0, -1).join("/")))).filter(i => i);
// create directories
for (var i = 0; i < parentDirs.length; i++) {
FS.createPath("/rom/", parentDirs[i], true, true);
}
// create files
for (var i = 0; i < data.length; i++) {
FS.writeFile("/rom/" + data[i].path, new Uint8Array(data[i].data));
}
}
// load save
var cSave = await getIdbItem("RetroArch_saves_" + romName);
if (cSave) {
saveObj = saveArrToObj(cSave);
FS.createPath("/", baseFsSaveDir.substring(1), true, true);
for (var i = 0; i < cSave.length; i++) {
safeWriteFile(baseFsSaveDir + cSave[i].dir + "rom" + cSave[i].ext, cSave[i].data);
}
new sideAlert("Save loaded for " + romName, 5000);
log("Save loaded for " + romName);
}
// import state
var cState = await getIdbItem("RetroArch_states_" + romName);
if (cState) {
FS.createPath("/", "home/web_user/retroarch/userdata/states", true, true);
FS.writeFile("/home/web_user/retroarch/userdata/states/rom.state", cState);
new sideAlert("State imported for " + romName + " (press load state)", 5000);
log("State imported for " + romName);
}
// config
safeWriteFile("/home/web_user/retroarch/userdata/retroarch.cfg", nulKeys + configObjToStr(savedKeybindsObj) + extraConfig);
// get the core options
var coreOptionsString = "";
if (coreOptions[core]) {
pso.style.display = "none";
try {
var opts = pso.querySelectorAll("[data-core=" + core + "] input");
for (var i = 0; i < opts.length; i++) {
if (opts[i].checked && coreOptions[core][opts[i].dataset.opt]) coreOptionsString += coreOptions[core][opts[i].dataset.opt];
}
} catch (e) {
console.warn(e);
}
}
// core-specific config (will be revised in the future)
switch (core) {
case "a5200":
safeWriteFile(baseFsConfigDir + "a5200/a5200.opt", coreOptionsString);
break;
case "mednafen_psx":
safeWriteFile(baseFsConfigDir + "Beetle PSX/Beetle PSX.opt", coreOptionsString);
break;
case "mednafen_psx_hw":
safeWriteFile(baseFsConfigDir + "Beetle PSX HW/Beetle PSX HW.opt", coreOptionsString);
break;
case "mednafen_vb":
safeWriteFile(baseFsConfigDir + "Beetle VB/Beetle VB.opt", coreOptionsString);
break;
case "mednafen_wswan":
safeWriteFile(baseFsConfigDir + "Beetle WonderSwan/Beetle WonderSwan.opt", coreOptionsString);
break;
case "melonds":
safeWriteFile(baseFsConfigDir + "melonDS/melonDS.opt", coreOptionsString + 'melonds_touch_mode = "Touch"\n');
break;
case "mgba":
safeWriteFile(baseFsConfigDir + "mGBA/mGBA.opt", coreOptionsString);
break;
case "mupen64plus_next":
safeWriteFile(baseFsConfigDir + "Mupen64Plus-Next/Mupen64Plus-Next.opt", coreOptionsString + 'mupen64plus-ThreadedRenderer = "False"\nmupen64plus-EnableCopyColorToRDRAM = "Off"\nmupen64plus-EnableCopyDepthToRDRAM = "Off"\n');
break;
case "o2em":
safeWriteFile(baseFsConfigDir + "O2EM/O2EM.opt", coreOptionsString);
break;
case "parallel_n64":
safeWriteFile(baseFsConfigDir + "ParaLLEl N64/ParaLLEl N64.opt", coreOptionsString);
break;
case "prosystem":
safeWriteFile(baseFsConfigDir + "ProSystem/ProSystem.opt", coreOptionsString);
break;
case "snes9x": // actually a remap
safeWriteFile(baseFsConfigDir + "remaps/Snes9x/Snes9x.rmp", coreOptionsString);
break;
case "stella2014":
safeWriteFile(baseFsConfigDir + "Stella 2014/Stella 2014.opt", coreOptionsString);
break;
case "vecx":
safeWriteFile(baseFsConfigDir + "VecX/VecX.opt", coreOptionsString);
break;
case "virtualjaguar":
safeWriteFile(baseFsConfigDir + "Virtual Jaguar/Virtual Jaguar.opt", coreOptionsString + 'virtualjaguar_bios = "enabled"\n');
break;
case "yabause":
safeWriteFile(baseFsConfigDir + "Yabause/Yabause.opt", coreOptionsString);
break;
}
// system-specific config
switch (systems[core]) {
case "SNES":
var hash = md5(u8atoutf8(new Uint8Array(data[0].data)));
if (smasBrickFix.hasOwnProperty(hash)) {
FS.writeFile("/rom/rom.ips", new Uint8Array(smasBrickFix[hash]));
new sideAlert("SMAS Bricks Fixed!", 5000);
}
break;
}
// writeToFile tracking (needs some extra stuff since it frequently fires in groups)
FS.trackingDelegate.onWriteToFile = function(path) {
if (!path.startsWith("/dev/")) {
if (writeToFileCooldown[path]) window.clearTimeout(writeToFileCooldown[path]);
writeToFileCooldown[path] = window.setTimeout(function() {
delete writeToFileCooldown[path];
FSTracking.dispatchEvent(new CustomEvent("writeToFile", {detail: path}));
// bigger delay = more lenient
}, 1000);
}
}
FSTracking.addEventListener("writeToFile", function(e) {
writeToFileHandler(e.detail);
}, false);
// start
log("Calling main...");
try {
Module.callMain(Module.arguments);
} catch (e) {
var estr = "FAILED TO CALL MAIN. CHECK BROWSER CONSOLE FOR DETAILS. (core: " + core + ")";
alert(estr);
log(estr);
console.error(e);
}
log("Main completed...");
adjustCanvasSize();
loadingDiv.style.display = "none";
window.setTimeout(afterStart, 1000);
} else {
window.setTimeout(waitForReady, 250);
}
}
waitForReady();
}
var Module = {
canvas: canvas,
noInitialRun: true,
arguments: ["/rom/rom.bin", "--verbose"],
onRuntimeInitialized: function() {
wasmReady = true;
log("WASM ready");
// fetch BIOSes
prepareBios();
// fetch asset bundle
if (queries.hasOwnProperty("nobundle")) {
bundleReady = true;
log("Skipping bundle");
} else {
prepareBundle();
}
},
print: function(text) {
log("stdout: " + text);
},
printErr: function(text) {
log("stderr: " + text);
}
};