// 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, stateReadersReady, stateReaders2Ready, saveReadersReady, isPaused, wasmReady, bundleReady, romMode, core, wIdb, romUploadCallback, latestVersion, emulatorStarted, currentManager, romUploadsReady; var bundleCdn = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro/"; var bundleCdnLatest = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro@latest/"; 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"\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 = "nul"\ninput_game_focus_toggle_axis = "nul"\ninput_game_focus_toggle_btn = "nul"\ninput_game_focus_toggle_mbtn = "nul"\ninput_grab_mouse_toggle = "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_hold_slowmotion = "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 = "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"\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.4; var updateNotice = document.getElementById("updatenotice"); var versionIndicator = document.getElementById("versionindicator"); 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 doubleRes = document.getElementById("doubleres"); var resModifier = 1; 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 systemName = document.getElementById("systemname"); var consoleButton = document.getElementById("consolebutton"); 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 hoverMenu = document.getElementById("menu"); var hoverMenuIndicator = document.getElementById("menuindicator"); 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 managers = {}; managers.keybind = document.getElementById("keybindmanager"); managers.screenshot = document.getElementById("screenshotmanager"); managers.save = document.getElementById("savemanager"); var managerNames = {"save": "Saves & States"}; var managerTitle = document.getElementById("managertitle"); var managerClose = document.getElementById("managerclose"); var screenshotDatas = []; var screenshotObjUrls = []; var saveIDs = []; var quotaText = document.getElementById("quotatext"); var search = decodeURIComponent(window.location.search).substring(1).split("&"); var systems = {"citra": "Nintendo 3DS", "desmume": "Nintendo DS", "dolphin": "GC/Wii", "genesis_plus_gx": "Genesis", "mednafen_psx": "PS1", "mgba": "GBA", "mupen64plus_next": "Nintendo 64", "nestopia": "NES", "parallel_n64": "Nintendo 64", "ppsspp": "PSP", "snes9x": "SNES"}; var installedCores = ["genesis_plus_gx", "mgba", "mupen64plus_next", "nestopia", "snes9x"]; var fileExts = {"GBA": ".gb, .gbc, .gba", "GC/Wii": ".iso, .gcm, .dol, .tgc, .wbfs, .ciso, .gcz, .wad", "Genesis": ".mdx, .md, .smd, .gen, .sms, .gg, .sg, .68k, .chd", "NES": ".nes, .fds, .unf, .unif", "Nintendo 64": ".n64, .v64, .z64, .u1, .ndd", "Nintendo 3DS": ".3ds, .3dsx, .cci, .cxi", "Nintendo DS": ".nds, .srl", "PS1": ".ccd, .iso", "PSP": ".cso, .pbp", "SNES": ".smc, .sfc, .swc, .fig, .bs, .st"}; var allFileExts = Object.values(fileExts).join(", "); var allValidFileExts = []; for (var i = 0; i < installedCores.length; i++) { allValidFileExts.push(fileExts[systems[installedCores[i]]]); } allValidFileExts = allValidFileExts.join(", "); var baseFsBundleDir = "/home/web_user/retroarch/bundle"; var awaitLogQueue = {}; var bundleErrors = 0; var sramExt = ".srm"; 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; // make core lists var aCoreList = '
  • Select a Core
  • AutoDetect (Slower to load)
  • '; for (var i = 0; i < installedCores.length; i++) { aCoreList += '
  • ' + installedCores[i] + ' (' + systems[installedCores[i]] + ')
  • '; } // query string into object var queries = {}; for (var i = 0; i < search.length; i++) { var p = search[i].split("="); queries[p[0]] = p[1]; } // 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) { bytes = bytes || 0; var extension = -1; while (bytes >= 1000) { bytes /= 1000; extension++; } return bytes.toFixed(2) + " " + "KMGTPEZY".charAt(extension) + "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)); } } // indexedDB function openIdb() { var request = indexedDB.open("webretro", 1); request.onsuccess = function(e) { wIdb = e.target.result; } request.onupgradeneeded = function(e) { var store = e.target.result.createObjectStore("main", {keyPath: "key"}); store.transaction.oncomplete = function(e2) { wIdb = e.target.db; } } } openIdb(); function setIdbItem(key, value) { wIdb.transaction("main", "readwrite").objectStore("main").put({key: key, value: value}); } function getIdbItem(key) { return new Promise(function(resolve) { wIdb.transaction("main", "readwrite").objectStore("main").get(key).onsuccess = function(e) { resolve(e.target.result ? e.target.result.value : null); } }); } function getAllIdbItems() { return new Promise(function(resolve) { wIdb.transaction("main", "readwrite").objectStore("main").getAll().onsuccess = function(e) { resolve(e.target.result ? e.target.result : null); } }); } function removeIdbItem(key) { wIdb.transaction("main", "readwrite").objectStore("main").delete(key); } // localStorage to indexedDB async function tryLsToIdb() { var ls = Object.keys(window.localStorage); for (var i = 0; i < ls.length; i++) { if (ls[i].startsWith("RetroArch_saves_")) { setIdbItem(ls[i], new Uint8Array(JSON.parse(window.localStorage.getItem(ls[i])))); window.localStorage.removeItem(ls[i]); } if (i == ls.length - 1) return; } } // 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; canvas.style.backgroundImage = 'url("data:image/svg+xml;base64,' + btoa('Loading' + message + '') + '")'; } // remove status messages function removeStatus(message) { if (loadStatus === message) setStatus(""); } // adjust canvas size to window function adjustCanvasSize() { if (window.innerHeight >= window.innerWidth * (3/4)) { var s = window.innerWidth; var t = Math.floor(s * (3/4)); Module.setCanvasSize(s * resModifier, t * resModifier); canvasMask.style.width = s + "px"; canvasMask.style.height = t + "px"; } else { var s = window.innerHeight; var t = Math.floor(s * (4/3)); Module.setCanvasSize(t * resModifier, s * resModifier); canvasMask.style.width = t + "px"; canvasMask.style.height = s + "px"; } } // logging function log(log, userInput) { console.log(log); wconsole.textContent += (userInput ? "> " + userInput + "\n\t" + JSON.stringify(log) : log) + "\n"; wconsole.scrollTo({top: wconsole.scrollHeight}); if (typeof log == "string") { // export state if (log.includes("New state file is ready to be read")) saveStateFunc(); // await log queue if (Object.keys(awaitLogQueue).length) { var lq = Object.keys(awaitLogQueue); for (var i = 0; i < lq.length; i++) { if (log.toLowerCase().includes(lq[i].toLowerCase())) { awaitLogQueue[lq[i]](log); delete awaitLogQueue[lq[i]]; } } } } } function awaitLog(contains, callback, timeout, expire) { awaitLogQueue[contains] = callback; if (timeout && !isNaN(timeout)) { window.setTimeout(function() { if (awaitLogQueue[contains]) { expire(); delete awaitLogQueue[contains]; } }, timeout); } } // 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.onload = function() { if (req.status >= "400") { if (fail) fail(req.status); } else { if (success) success(this.response); } } req.send(); } // file readers function readFile(file, callback) { var reader = new FileReader(); reader.onload = function() { callback(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 = function() { let file = this.files[0]; readFile(file, function(data) { callback({name: file.name, data: data}); }); } 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("./" + name + "_libretro.js", callback, err); } // check for updates function checkForUpdates() { grab("https://cdn.jsdelivr.net/gh/BinBashBanana/webretro@latest/assets/info.json", "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) { log(e); } }); } // unzip file function unzipFile(data, exts, callback, empty, notfound) { new zip.ZipReader(new zip.Uint8ArrayReader(data)).getEntries().then(function(entries) { if (entries.length) { for (var i = 0; i < entries.length; i++) { if (exts.split(", ").includes("." + u8atoutf8(entries[i].rawFilename).split(".").slice(-1)[0])) { let name = u8atoutf8(entries[i].rawFilename); entries[i].getData(new zip.Uint8ArrayWriter()).then(function(uzd) { callback(name, uzd); }); break; } if (i == entries.length - 1 && notfound) notfound(); } } else if (empty) empty(); }); } // zip files async function zipFiles(files, callback, replaceName) { var u8aWriter = new zip.Uint8ArrayWriter("application/zip"); var writer = new zip.ZipWriter(u8aWriter); for (var i = 0; i < files.length; i++) { await writer.add(replaceName ? files[i].name.replace("rom", romName) : files[i].name, new zip.Uint8ArrayReader(files[i].data)); } await writer.close(); var zipped = await u8aWriter.getData(); callback(zipped); } // uauth uploads function handleWebFile(data) { if (data.message == "success") { ffd.style.display = "none"; romUploadCallback(data.name, 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); } // rom upload function readyRomUploads(exts) { romUploadsReady = true; // when a rom file is chosen upload.onclick = function() { uploadFile(exts, function(file) { ffd.style.display = "none"; log('Succesfully read ROM file "' + file.name + '"'); romUploadCallback(file.name, 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 = function(e) { if (e.dataTransfer.types.includes("Files")) { e.preventDefault(); ffd.style.display = "none"; let file = event.dataTransfer.files[0]; readFile(file, function(data) { log('Succesfully read ROM file "' + file.name + '"'); romUploadCallback(file.name, data); }); } } document.addEventListener("dragenter", fileDragEnter, false); document.addEventListener("dragover", fileDragOver, false); document.addEventListener("drop", fileDropped, false); } // rom fetch function readyRomFetch() { var romloc = (/^(http:\/\/|https:\/\/|\/\/)/i).test(queries.rom) ? queries.rom : "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(romFilename, data); }, function(error) { alert("Could not get ROM at " + romloc + " (Error " + error + ")"); romMode = "upload"; ffd.style.display = "block"; }); } // 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 = function(e) { e.stopPropagation(); if (e.keyCode == 13) { log(eval(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}); // modal windows (managers) function openManager(type) { if (managers[type]) { if (managerClosed[currentManager]) managerClosed[currentManager](); currentManager = type; if (managerOpened[type]) managerOpened[type](); managerTitle.textContent = 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"; }); } managerClose.onclick = function() { modals.style.display = "none"; clearManagers(); managerTitle.textContent = ""; if (managerClosed[currentManager]) managerClosed[currentManager](); currentManager = undefined; } // --- 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 = window.localStorage.getItem("RetroArch_settings_keybinds") ? configStrToObj(window.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] + "" + 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 (emulatorStarted) { 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); window.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); window.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(screenshotDatas, function(zd) { downloadFile(zd, "screenshots-" + getTime() + ".zip", "application/zip"); }, true); } 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] = {name: screenshots[i], data: screenshotData}; 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].name.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].name); 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) { downloadFile(data, "game-" + saveIDs[saveNo].type + "-" + saveIDs[saveNo].name + "-" + getTime() + (saveIDs[saveNo].type == "save" ? ".srm" : ".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; } } } // --- end manager-specific code --- var managerOpened = { "keybind": function() { createConfigList(); }, "screenshot": function() { createScreenshotList(); }, "save": function() { createSaveList(); } }; 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 = []; } }; // opening the managers keybindsButton.onclick = function(e) { e.preventDefault(); openManager("keybind"); } screenshotsButton.onclick = function() { openManager("screenshot"); } savesButton.onclick = function(e) { e.preventDefault(); openManager("save"); } statesButton.onclick = function(e) { e.preventDefault(); openManager("save"); }; // ---------- START LOAD ---------- (function() { checkForUpdates(); // ?system query if (!queries.core && queries.system) { var detectedCore = Object.keys(systems).find(k => systems[k].toLowerCase() == queries.system.toLowerCase()); if (installedCores.includes(detectedCore)) { queries.core = detectedCore; } else if (queries.system.toLowerCase() == "autodetect") { queries.core = "autodetect"; } else { alert("Invalid core (" + detectedCore + ")"); } } // ?core query if (queries.core) { if (!window.navigator.userAgent.toLowerCase().includes("chrom")) alert("Best performance on Chrome!"); // show hover menu hoverMenu.style.display = "block"; versionIndicator.textContent = "v" + webretroVersion.toString(); if (queries.core.toLowerCase() == "autodetect") { romUploadCallback = autodetectCoreHandler; systemName.textContent = ""; readyRomUploads(".zip, " + allFileExts); } else { romUploadCallback = initFromFile; core = queries.core; setStatus("Getting core"); if (core == "desmume") sramExt = ".dsv"; // detect system for ROM upload systemName.textContent = systems[core] || ""; getCore(core, function() { removeStatus("Getting core"); log("Got core: " + core); if (romMode != "querystring") document.title = core + " | webretro"; readyRomUploads(".zip, .bin, " + fileExts[systems[core]]); }, function() { // core loading error alert('Could not load specified core "' + core + '". Here is a list of available cores.'); ffdContent.innerHTML = ""; ffd.style.display = "block"; }); } // ?rom query if (queries.rom) { readyRomFetch(); } else { // prompt user to upload ROM file romMode = "upload"; ffd.style.display = "block"; } } else { // no core specified ffdContent.innerHTML = ""; ffd.style.display = "block"; } })(); // ----------- END LOAD ----------- // start emulator from file name and data function initFromFile(name, data) { var dataView = new Uint8Array(data); if (name.split(".").slice(-1)[0] == "zip") { log("Zip file detected, unzipping..."); unzipFile(dataView, fileExts[systems[core]], function(name, contents) { romName = name.split(".")[0]; readyForInit(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 { romName = name.split(".")[0]; readyForInit(dataView); } } // autodetect core mode function autodetectCoreHandler(name, data) { var dataView = new Uint8Array(data); if (name.split(".").slice(-1)[0] == "zip") { log("Zip file detected, unzipping..."); unzipFile(dataView, allFileExts, function(name, contents) { romName = name.split(".")[0]; 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 { romName = name.split(".")[0]; autodetectCore(name, dataView); } } function autodetectCore(name, data) { var nameExt = "." + name.split(".").slice(-1)[0]; var detectedCore; var fileExtsArray = Object.keys(fileExts); for (var i = 0; i < fileExtsArray.length; i++) { if (fileExts[fileExtsArray[i]].split(", ").includes(nameExt)) { detectedCore = Object.keys(systems).find(k => systems[k] == fileExtsArray[i]); break; } } var detectedSystem = systems[detectedCore] || "unknown"; detectedCore = detectedCore || "unknown"; if (allValidFileExts.split(", ").includes(nameExt)) { core = detectedCore; setStatus("Getting core"); if (core == "desmume") sramExt = ".dsv"; getCore(core, function() { removeStatus("Getting core"); log("Got core: " + core); readyForInit(data); }); } else { alert("That is a " + detectedSystem + " file! " + detectedCore + " (" + detectedSystem + ") is not currently supported."); } } // if the ROM is specified in the querystring, we will need to wait until the user has clicked to start the emulator function readyForInit(data) { document.title = romName + " | webretro"; if (queries.romshift) data = avShift(data, parseInt(queries.romshift)); // remove the file drop listeners if (romUploadsReady) { document.removeEventListener("dragenter", fileDragEnter); document.removeEventListener("dragover", fileDragOver); document.removeEventListener("drop", fileDropped); } if (romMode == "querystring") { // 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) startButton.style.display = "initial"; startButton.onclick = function() { startButton.style.display = "none"; initFromData(data); } } else { initFromData(data); } } // 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) { var splitData = data.split(",,,\n"); fsBundleDirs = JSON.parse(splitData[0]); fsBundleFiles = splitData[1].split("\n"); // make the paths FS.createPath("/", "home/web_user/retroarch/bundle", true, true); for (var i = 0; i < fsBundleDirs.length; i++) { FS.createPath(baseFsBundleDir + fsBundleDirs[i][0], fsBundleDirs[i][1], true, true); } // 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)); if (i == fsBundleFiles.length - 1) donePreparingBundle(performance.now() - bundleSTime); }, function() { bundleErrors += 1; if (i == fsBundleFiles.length - 1) donePreparingBundle(performance.now() - bundleSTime); }); } }, function() { log("Failed to get asset bundle, skipping"); bundleReady = true; removeStatus("Getting assets"); }); } function donePreparingBundle(tooktime) { bundleReady = true; removeStatus("Getting assets"); log("Finished bundle fetch in " + (tooktime / 1000).toFixed(1) + " seconds, " + bundleErrors + " errors"); } // tell the user to not rename the rom function doNotRename() { if (romMode == "upload" && !window.localStorage.getItem("webretro_settings_pastFirstSave")) { alert("WARNING: Do not rename your ROM file after this! The save data is specific to the ROM name!"); window.localStorage.setItem("webretro_settings_pastFirstSave", "true"); } } // save game function saveSRAM() { Module._cmd_savefiles(); window.setTimeout(function() { if (FS.analyzePath("/home/web_user/retroarch/userdata/saves/rom" + sramExt).exists) { setIdbItem("RetroArch_saves_" + romName, FS.readFile("/home/web_user/retroarch/userdata/saves/rom" + sramExt)); new sideAlert("Saved", 3000); readySaveReaders(); doNotRename(); } else { autosave.checked = false; new sideAlert("This game does not save!", 3000); } }, 1000); } // save state function saveStateFunc() { window.setTimeout(function() { 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); } }, 100); } // autosaving function autosaveSRAM() { if (autosave.checked && !document.hidden && !isPaused) { new sideAlert("Autosaving...", 3000); saveSRAM(); } window.setTimeout(function() { autosaveSRAM(); }, 300000); } // more functions for state buttons function readyStateReaders() { if (!stateReadersReady) { stateReadersReady = true; loadState.classList.remove("disabled"); exportState.classList.remove("disabled"); undoSaveState.classList.remove("disabled"); loadState.onclick = function() { Module._cmd_load_state(); readyStateReaders2(); } exportState.onclick = function() { downloadFile(FS.readFile("/home/web_user/retroarch/userdata/states/rom.state"), "game-state-" + romName + "-" + getTime() + ".state"); } undoSaveState.onclick = function() { Module._cmd_undo_save_state(); } // also allow statereaders2 on load state press document.addEventListener("keydown", function(e) { if (!stateReaders2Ready && (e.code == Object.keys(codeToConfigIDMap).find(k => codeToConfigIDMap[k] == savedKeybindsObj.input_load_state))) readyStateReaders2(); }, false); } } // even more functions for state buttons function readyStateReaders2() { if (!stateReaders2Ready) { stateReaders2Ready = true; undoLoadState.classList.remove("disabled"); undoLoadState.onclick = function() { Module._cmd_undo_load_state(); } } } // more functions for save buttons function readySaveReaders() { if (!saveReadersReady) { saveReadersReady = true; exportSave.classList.remove("disabled"); exportSave.onclick = function() { downloadFile(FS.readFile("/home/web_user/retroarch/userdata/saves/rom" + sramExt), "game-sram-" + romName + "-" + getTime() + sramExt); } } } // runs after emulator starts function afterStart() { emulatorStarted = true; // remove loading text canvas.style.background = "none"; adjustCanvasSize(); // functions for save and state buttons saveState.classList.remove("disabled"); importState.classList.remove("disabled"); saveGame.classList.remove("disabled"); importSave.classList.remove("disabled"); autosave.removeAttribute("disabled"); autosave.parentElement.classList.remove("disabled"); saveState.onclick = function() { Module._cmd_save_state(); readyStateReaders(); } importState.onclick = function() { uploadFile(".bin, .state, .save, .dat, .gam, .sav, application/*", 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 (Now press load state)", 3000); readyStateReaders(); }); } saveGame.onclick = function() { new sideAlert("Saving...", 3000); saveSRAM(); } importSave.onclick = function() { uploadFile(".bin, .srm, .sram, .ram, .gam, .sav, .dsv, application/*", function(file) { autosave.checked = false; setIdbItem("RetroArch_saves_" + romName, new Uint8Array(file.data)); if (confirm("Save imported. Reloading now for changes to take effect.")) { window.onbeforeunload = function() {} window.location.reload(); } }); } // also allow state readers on save state press document.addEventListener("keydown", function(e) { if (!stateReadersReady && (e.code == Object.keys(codeToConfigIDMap).find(k => codeToConfigIDMap[k] == savedKeybindsObj.input_save_state))) readyStateReaders(); }, false); // start autosave loop window.setTimeout(function() { autosaveSRAM(); }, 300000); // toggle between sharp and smooth canvas graphics smooth.removeAttribute("disabled"); smooth.parentElement.classList.remove("disabled"); smooth.onclick = function() { if (this.checked) { canvas.className = "textureSmooth"; } else { canvas.className = "texturePixelated"; } } // higher resolution doubleRes.removeAttribute("disabled"); doubleRes.parentElement.classList.remove("disabled"); doubleRes.onclick = function() { if (this.checked) { resModifier = 2; adjustCanvasSize(); } else { resModifier = 1; adjustCanvasSize(); } } // 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(); } // screenshot button takeScreenshot.classList.remove("disabled"); takeScreenshot.onclick = function() { Module._cmd_take_screenshot(); } // flash the menu on first use if (!window.localStorage.getItem("webretro_settings_pastFirstStart")) { hoverMenu.classList.add("show"); hoverMenuIndicator.classList.add("show"); window.setTimeout(function() { hoverMenu.classList.remove("show"); hoverMenuIndicator.classList.remove("show"); }, 3000); window.localStorage.setItem("webretro_settings_pastFirstStart", "true"); } // ctrl+v inside canvas document.addEventListener("keydown", function(e) { if (e.ctrlKey && e.key == "v") { 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) { setStatus("Waiting for emulator"); log("Initializing with " + data.byteLength + " bytes of data"); updateNotice.style.display = "none"; canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); }, false); window.addEventListener("resize", adjustCanvasSize, false); adjustCanvasSize(); // prevent defaults for key presses document.addEventListener("keydown", function(e) { if (pdKeys.includes(e.which)) e.preventDefault(); }, false); // move the saves and states from the old version (localStorage) to indexedDB await tryLsToIdb(); // rom FS.writeFile("/rom.bin", data); // SMAS brick fix if (systems[core] == "SNES") { var hash = md5(u8atoutf8(data)); if (smasBrickFix.hasOwnProperty(hash)) { FS.writeFile("/rom.ips", new Uint8Array(smasBrickFix[hash])); new sideAlert("SMAS Bricks Fixed!", 5000); } } // load save var cSave = await getIdbItem("RetroArch_saves_" + romName); if (cSave) { FS.createPath("/", "home/web_user/retroarch/userdata/saves", true, true); FS.writeFile("/home/web_user/retroarch/userdata/saves/rom" + sramExt, cSave); new sideAlert("Save loaded for " + romName, 5000); log("Save loaded for " + romName); readySaveReaders(); } // 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, 5000); log("State imported for " + romName); readyStateReaders(); } // config FS.createPath("/", "home/web_user/retroarch/userdata", true, true); FS.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", nulKeys + configObjToStr(savedKeybindsObj) + extraConfig); // start Module.callMain(Module.arguments); adjustCanvasSize(); window.setTimeout(afterStart, 2000); } else { window.setTimeout(waitForReady, 1000); } } waitForReady(); } var Module = { canvas: canvas, noInitialRun: true, arguments: ["/rom.bin", "--verbose"], onRuntimeInitialized: function() { wasmReady = true; log("WASM ready"); // 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); } };