base.js 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327
  1. // Source Code: https://github.com/BinBashBanana/webretro
  2. // please dont use IE
  3. var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
  4. if (!window.fetch || !indexedDB) {
  5. alert("Update your browser!");
  6. throw "Update your browser!";
  7. }
  8. var fsBundleDirs, fsBundleFiles, loadStatus, romName, stateReadersReady, stateReaders2Ready, saveReadersReady, isPaused, wasmReady, bundleReady, romMode, core, wIdb, romUploadCallback, latestVersion, emulatorStarted, currentManager, romUploadsReady;
  9. var bundleCdn = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro/";
  10. var bundleCdnLatest = "https://cdn.jsdelivr.net/gh/BinBashBanana/webretro@latest/";
  11. 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';
  12. 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';
  13. var extraConfig = 'rgui_show_start_screen = "false"\n';
  14. 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];
  15. var webretroVersion = 6.4;
  16. var updateNotice = document.getElementById("updatenotice");
  17. var versionIndicator = document.getElementById("versionindicator");
  18. var upload = document.getElementById("upload");
  19. var googleDriveUpload = document.getElementById("googledriveupload");
  20. var dropboxUpload = document.getElementById("dropboxupload");
  21. var oneDriveUpload = document.getElementById("onedriveupload");
  22. var startButton = document.getElementById("startbutton");
  23. var smooth = document.getElementById("smooth");
  24. var doubleRes = document.getElementById("doubleres");
  25. var resModifier = 1;
  26. var canvas = document.getElementById("canvas");
  27. var canvasMask = document.getElementById("canvasmask");
  28. var saveState = document.getElementById("savestate");
  29. var loadState = document.getElementById("loadstate");
  30. var undoSaveState = document.getElementById("undosavestate");
  31. var undoLoadState = document.getElementById("undoloadstate");
  32. var exportState = document.getElementById("exportstate");
  33. var importState = document.getElementById("importstate");
  34. var ffd = document.getElementById("ffd");
  35. var ffdContent = document.getElementById("ffdcontent");
  36. var systemName = document.getElementById("systemname");
  37. var consoleButton = document.getElementById("consolebutton");
  38. var menuButton = document.getElementById("menubutton");
  39. var pauseButton = document.getElementById("pause");
  40. var resumeOverlay = document.getElementById("resume");
  41. var sideAlertHolder = document.getElementById("sidealertholder");
  42. var saveGame = document.getElementById("savegame");
  43. var exportSave = document.getElementById("exportsave");
  44. var importSave = document.getElementById("importsave");
  45. var autosave = document.getElementById("autosave");
  46. var mainArea = document.getElementById("mainarea");
  47. var hoverMenu = document.getElementById("menu");
  48. var hoverMenuIndicator = document.getElementById("menuindicator");
  49. var takeScreenshot = document.getElementById("takescreenshot");
  50. var modals = document.getElementById("modals");
  51. var keybindTable = document.getElementById("keybindtable");
  52. var saveKeybinds = document.getElementById("savekeybinds");
  53. var resetKeybinds = document.getElementById("resetkeybinds");
  54. var keybindsButton = document.getElementById("keybindsbutton");
  55. var screenshotsButton = document.getElementById("screenshotsbutton");
  56. var savesButton = document.getElementById("savesbutton");
  57. var statesButton = document.getElementById("statesbutton");
  58. var downloadAllScreenshots = document.getElementById("downloadallscreenshots");
  59. var screenshotsDiv = document.getElementById("screenshotsdiv");
  60. var saveTable = document.getElementById("savetable");
  61. var managers = {};
  62. managers.keybind = document.getElementById("keybindmanager");
  63. managers.screenshot = document.getElementById("screenshotmanager");
  64. managers.save = document.getElementById("savemanager");
  65. var managerNames = {"save": "Saves & States"};
  66. var managerTitle = document.getElementById("managertitle");
  67. var managerClose = document.getElementById("managerclose");
  68. var screenshotDatas = [];
  69. var screenshotObjUrls = [];
  70. var saveIDs = [];
  71. var quotaText = document.getElementById("quotatext");
  72. var search = decodeURIComponent(window.location.search).substring(1).split("&");
  73. 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"};
  74. var installedCores = ["genesis_plus_gx", "mgba", "mupen64plus_next", "nestopia", "snes9x"];
  75. 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"};
  76. var allFileExts = Object.values(fileExts).join(", ");
  77. var allValidFileExts = [];
  78. for (var i = 0; i < installedCores.length; i++) {
  79. allValidFileExts.push(fileExts[systems[installedCores[i]]]);
  80. }
  81. allValidFileExts = allValidFileExts.join(", ");
  82. var baseFsBundleDir = "/home/web_user/retroarch/bundle";
  83. var awaitLogQueue = {};
  84. var bundleErrors = 0;
  85. var sramExt = ".srm";
  86. 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]
  87. // disable webcam for gameboy camera
  88. var disableWebCam = true;
  89. // make core lists
  90. var aCoreList = '<li><b>Select a Core</b></li><li><a href="?core=autodetect" class="greyer">AutoDetect (Slower to load)</a></li>';
  91. for (var i = 0; i < installedCores.length; i++) {
  92. aCoreList += '<li><a href="?core=' + installedCores[i] + '">' + installedCores[i] + ' (' + systems[installedCores[i]] + ')</a></li>';
  93. }
  94. // query string into object
  95. var queries = {};
  96. for (var i = 0; i < search.length; i++) {
  97. var p = search[i].split("=");
  98. queries[p[0]] = p[1];
  99. }
  100. // Binary to UTF-8
  101. function u8atoutf8(data) {
  102. return new TextDecoder().decode(data);
  103. }
  104. function avShift(array, shift) {
  105. for (var i = 0; i < array.length; i++) {
  106. array[i] += shift;
  107. }
  108. return array;
  109. }
  110. // date time
  111. function getTime() {
  112. var dateTime = new Date();
  113. return dateTime.getFullYear().toString()+"-"+(dateTime.getMonth()+1).toString()+"-"+dateTime.getDate().toString()+"-"+dateTime.getHours().toString()+"-"+dateTime.getMinutes().toString();
  114. }
  115. // bytes to human-readable string
  116. function bytesToHumanReadable(bytes) {
  117. bytes = bytes || 0;
  118. var extension = -1;
  119. while (bytes >= 1000) {
  120. bytes /= 1000;
  121. extension++;
  122. }
  123. return bytes.toFixed(2) + " " + "KMGTPEZY".charAt(extension) + "B";
  124. }
  125. // js has no built-in capitalization function
  126. function capitalize(str) {
  127. return str.charAt(0).toUpperCase() + str.slice(1);
  128. }
  129. // key press stuff
  130. function fakeKey(type, info) {
  131. var e = new KeyboardEvent(type, {code: info.code || undefined, key: info.key || undefined, shiftKey: info.shiftKey || undefined});
  132. document.dispatchEvent(e);
  133. }
  134. function fakeKeyPress(info) {
  135. fakeKey("keydown", info);
  136. window.setTimeout(function() {
  137. fakeKey("keyup", info);
  138. }, 50);
  139. }
  140. function fakeCharPress(key) {
  141. if (charToCodeMap.hasOwnProperty(key)) fakeKeyPress({code: charToCodeMap[key].code, key: charToKeyMap.hasOwnProperty(key) ? charToKeyMap[key].key : key, shiftKey: charToCodeMap[key].hasOwnProperty("shift") ? true : false});
  142. }
  143. function sendText(text) {
  144. for (var i = 0; i < text.length; i++) {
  145. fakeCharPress(text.charAt(i));
  146. }
  147. }
  148. // indexedDB
  149. function openIdb() {
  150. var request = indexedDB.open("webretro", 1);
  151. request.onsuccess = function(e) {
  152. wIdb = e.target.result;
  153. }
  154. request.onupgradeneeded = function(e) {
  155. var store = e.target.result.createObjectStore("main", {keyPath: "key"});
  156. store.transaction.oncomplete = function(e2) {
  157. wIdb = e.target.db;
  158. }
  159. }
  160. }
  161. openIdb();
  162. function setIdbItem(key, value) {
  163. wIdb.transaction("main", "readwrite").objectStore("main").put({key: key, value: value});
  164. }
  165. function getIdbItem(key) {
  166. return new Promise(function(resolve) {
  167. wIdb.transaction("main", "readwrite").objectStore("main").get(key).onsuccess = function(e) {
  168. resolve(e.target.result ? e.target.result.value : null);
  169. }
  170. });
  171. }
  172. function getAllIdbItems() {
  173. return new Promise(function(resolve) {
  174. wIdb.transaction("main", "readwrite").objectStore("main").getAll().onsuccess = function(e) {
  175. resolve(e.target.result ? e.target.result : null);
  176. }
  177. });
  178. }
  179. function removeIdbItem(key) {
  180. wIdb.transaction("main", "readwrite").objectStore("main").delete(key);
  181. }
  182. // localStorage to indexedDB
  183. async function tryLsToIdb() {
  184. var ls = Object.keys(window.localStorage);
  185. for (var i = 0; i < ls.length; i++) {
  186. if (ls[i].startsWith("RetroArch_saves_")) {
  187. setIdbItem(ls[i], new Uint8Array(JSON.parse(window.localStorage.getItem(ls[i]))));
  188. window.localStorage.removeItem(ls[i]);
  189. }
  190. if (i == ls.length - 1) return;
  191. }
  192. }
  193. // side alerts
  194. function sideAlert(initialText, time) {
  195. var p = document.createElement("p");
  196. p.className = "sidealert";
  197. p.appendChild(document.createTextNode(initialText));
  198. sideAlertHolder.appendChild(p);
  199. window.setTimeout(function() {
  200. p.classList.add("on");
  201. }, 10);
  202. this.dismiss = function() {
  203. p.classList.remove("on");
  204. window.setTimeout(function() {
  205. p.remove();
  206. }, 100);
  207. }
  208. this.setText = function(text) {
  209. p.textContent = text;
  210. }
  211. if (time) window.setTimeout(this.dismiss, time);
  212. }
  213. // change background for status messages
  214. function setStatus(message) {
  215. loadStatus = message;
  216. canvas.style.backgroundImage = 'url("data:image/svg+xml;base64,' + btoa('<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150"><text style="font: 30px sans-serif;" fill="white" x="50%" y="40%" dominant-baseline="middle" text-anchor="middle">Loading</text><text style="font: 15px sans-serif;" fill="white" x="50%" y="60%" dominant-baseline="middle" text-anchor="middle">' + message + '</text></svg>') + '")';
  217. }
  218. // remove status messages
  219. function removeStatus(message) {
  220. if (loadStatus === message) setStatus("");
  221. }
  222. // adjust canvas size to window
  223. function adjustCanvasSize() {
  224. if (window.innerHeight >= window.innerWidth * (3/4)) {
  225. var s = window.innerWidth;
  226. var t = Math.floor(s * (3/4));
  227. Module.setCanvasSize(s * resModifier, t * resModifier);
  228. canvasMask.style.width = s + "px";
  229. canvasMask.style.height = t + "px";
  230. } else {
  231. var s = window.innerHeight;
  232. var t = Math.floor(s * (4/3));
  233. Module.setCanvasSize(t * resModifier, s * resModifier);
  234. canvasMask.style.width = t + "px";
  235. canvasMask.style.height = s + "px";
  236. }
  237. }
  238. // logging
  239. function log(log, userInput) {
  240. console.log(log);
  241. wconsole.textContent += (userInput ? "> " + userInput + "\n\t" + JSON.stringify(log) : log) + "\n";
  242. wconsole.scrollTo({top: wconsole.scrollHeight});
  243. if (typeof log == "string") {
  244. // export state
  245. if (log.includes("New state file is ready to be read")) saveStateFunc();
  246. // await log queue
  247. if (Object.keys(awaitLogQueue).length) {
  248. var lq = Object.keys(awaitLogQueue);
  249. for (var i = 0; i < lq.length; i++) {
  250. if (log.toLowerCase().includes(lq[i].toLowerCase())) {
  251. awaitLogQueue[lq[i]](log);
  252. delete awaitLogQueue[lq[i]];
  253. }
  254. }
  255. }
  256. }
  257. }
  258. function awaitLog(contains, callback, timeout, expire) {
  259. awaitLogQueue[contains] = callback;
  260. if (timeout && !isNaN(timeout)) {
  261. window.setTimeout(function() {
  262. if (awaitLogQueue[contains]) {
  263. expire();
  264. delete awaitLogQueue[contains];
  265. }
  266. }, timeout);
  267. }
  268. }
  269. // xhr
  270. function grab(url, type, success, fail) {
  271. var req = new XMLHttpRequest();
  272. req.open("GET", url, true);
  273. req.overrideMimeType("text/plain; charset=x-user-defined");
  274. req.responseType = type;
  275. req.onload = function() {
  276. if (req.status >= "400") {
  277. if (fail) fail(req.status);
  278. } else {
  279. if (success) success(this.response);
  280. }
  281. }
  282. req.send();
  283. }
  284. // file readers
  285. function readFile(file, callback) {
  286. var reader = new FileReader();
  287. reader.onload = function() {
  288. callback(this.result);
  289. }
  290. reader.readAsArrayBuffer(file);
  291. }
  292. function downloadFile(data, name, mime) {
  293. var a = document.createElement("a");
  294. a.download = name;
  295. a.href = URL.createObjectURL(new Blob([data], {type: mime || "application/octet-stream"}));
  296. a.click();
  297. window.setTimeout(function() {
  298. URL.revokeObjectURL(a.href);
  299. }, 2000);
  300. }
  301. function uploadFile(accept, callback) {
  302. var input = document.createElement("input");
  303. input.type = "file";
  304. input.accept = accept;
  305. input.onchange = function() {
  306. let file = this.files[0];
  307. readFile(file, function(data) {
  308. callback({name: file.name, data: data});
  309. });
  310. }
  311. input.click();
  312. }
  313. // scripts
  314. function getScript(url, callback, err) {
  315. var script = document.createElement("script");
  316. script.type = "text/javascript";
  317. script.src = url;
  318. script.onload = function() {
  319. if (callback) callback();
  320. }
  321. script.onerror = function(e) {
  322. document.body.removeChild(script);
  323. if (err) err(e);
  324. }
  325. document.body.appendChild(script);
  326. }
  327. function getCore(name, callback, err) {
  328. getScript("./" + name + "_libretro.js", callback, err);
  329. }
  330. // check for updates
  331. function checkForUpdates() {
  332. grab("https://cdn.jsdelivr.net/gh/BinBashBanana/webretro@latest/assets/info.json", "text", function(text) {
  333. try {
  334. var updateObj = JSON.parse(text);
  335. if (updateObj.webretro) {
  336. latestVersion = updateObj.webretro;
  337. if (updateObj.versions[webretroVersion.toString()]) versionIndicator.title = "New features in this version:\n\n- " + updateObj.versions[webretroVersion.toString()].changeList.join("\n- ");
  338. if (latestVersion > webretroVersion && updateObj.versions[latestVersion.toString()]) {
  339. 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.";
  340. updateNotice.style.display = "initial";
  341. }
  342. }
  343. } catch (e) {
  344. log(e);
  345. }
  346. });
  347. }
  348. // unzip file
  349. function unzipFile(data, exts, callback, empty, notfound) {
  350. new zip.ZipReader(new zip.Uint8ArrayReader(data)).getEntries().then(function(entries) {
  351. if (entries.length) {
  352. for (var i = 0; i < entries.length; i++) {
  353. if (exts.split(", ").includes("." + u8atoutf8(entries[i].rawFilename).split(".").slice(-1)[0])) {
  354. let name = u8atoutf8(entries[i].rawFilename);
  355. entries[i].getData(new zip.Uint8ArrayWriter()).then(function(uzd) {
  356. callback(name, uzd);
  357. });
  358. break;
  359. }
  360. if (i == entries.length - 1 && notfound) notfound();
  361. }
  362. } else if (empty) empty();
  363. });
  364. }
  365. // zip files
  366. async function zipFiles(files, callback, replaceName) {
  367. var u8aWriter = new zip.Uint8ArrayWriter("application/zip");
  368. var writer = new zip.ZipWriter(u8aWriter);
  369. for (var i = 0; i < files.length; i++) {
  370. await writer.add(replaceName ? files[i].name.replace("rom", romName) : files[i].name, new zip.Uint8ArrayReader(files[i].data));
  371. }
  372. await writer.close();
  373. var zipped = await u8aWriter.getData();
  374. callback(zipped);
  375. }
  376. // uauth uploads
  377. function handleWebFile(data) {
  378. if (data.message == "success") {
  379. ffd.style.display = "none";
  380. romUploadCallback(data.name, data.data);
  381. } else if (data.message == "error") {
  382. alert("There was an error with the file picker. This may mean that you have to allow popup windows.");
  383. }
  384. }
  385. function uploadWebFile(type, exts) {
  386. uauth.open(type, exts.split(", "), handleWebFile);
  387. }
  388. // rom upload
  389. function readyRomUploads(exts) {
  390. romUploadsReady = true;
  391. // when a rom file is chosen
  392. upload.onclick = function() {
  393. uploadFile(exts, function(file) {
  394. ffd.style.display = "none";
  395. log('Succesfully read ROM file "' + file.name + '"');
  396. romUploadCallback(file.name, file.data);
  397. });
  398. }
  399. // web uploads
  400. googleDriveUpload.onclick = function() {
  401. uploadWebFile("drive", exts);
  402. }
  403. dropboxUpload.onclick = function() {
  404. uploadWebFile("dropbox", exts);
  405. }
  406. oneDriveUpload.onclick = function() {
  407. uploadWebFile("onedrive", exts);
  408. }
  409. // file drop (we need these to be global so they can be removed later)
  410. window.fileDragEnter = function(e) {
  411. if (e.dataTransfer.types.includes("Files")) ffd.classList.add("filehover");
  412. }
  413. window.fileDragOver = function(e) {
  414. e.preventDefault();
  415. }
  416. window.fileDropped = function(e) {
  417. if (e.dataTransfer.types.includes("Files")) {
  418. e.preventDefault();
  419. ffd.style.display = "none";
  420. let file = event.dataTransfer.files[0];
  421. readFile(file, function(data) {
  422. log('Succesfully read ROM file "' + file.name + '"');
  423. romUploadCallback(file.name, data);
  424. });
  425. }
  426. }
  427. document.addEventListener("dragenter", fileDragEnter, false);
  428. document.addEventListener("dragover", fileDragOver, false);
  429. document.addEventListener("drop", fileDropped, false);
  430. }
  431. // rom fetch
  432. function readyRomFetch() {
  433. var romloc = (/^(http:\/\/|https:\/\/|\/\/)/i).test(queries.rom) ? queries.rom : "roms/" + queries.rom;
  434. var romFilename = queries.rom.split("/").slice(-1)[0];
  435. grab(romloc, "arraybuffer", function(data) {
  436. log("Succesfully fetched ROM from " + romloc);
  437. romMode = "querystring";
  438. romUploadCallback(romFilename, data);
  439. }, function(error) {
  440. alert("Could not get ROM at " + romloc + " (Error " + error + ")");
  441. romMode = "upload";
  442. ffd.style.display = "block";
  443. });
  444. }
  445. // console window
  446. var conw = new jswindow({title: "Console", icon: "assets/terminal.svg"});
  447. var wconsole = document.createElement("textarea");
  448. wconsole.classList.add("console");
  449. wconsole.setAttribute("spellcheck", "false");
  450. wconsole.setAttribute("readonly", "");
  451. wconsole.wconsolemarker = document.createElement("span");
  452. wconsole.wconsolemarker.classList.add("consolemarker");
  453. wconsole.wconsoleinput = document.createElement("input");
  454. wconsole.wconsoleinput.type = "text";
  455. wconsole.wconsoleinput.classList.add("consoleinput");
  456. wconsole.wconsoleinput.title = "You can type things here as though you were using the browser console.";
  457. wconsole.wconsoleinput.setAttribute("spellcheck", "false");
  458. wconsole.wconsolemarker.onclick = function() { wconsole.wconsoleinput.focus(); }
  459. wconsole.wconsoleinput.onkeydown = function(e) {
  460. e.stopPropagation();
  461. if (e.keyCode == 13) {
  462. log(eval(this.value), this.value);
  463. this.value = "";
  464. }
  465. }
  466. conw.innerWindow.appendChild(wconsole);
  467. conw.innerWindow.appendChild(wconsole.wconsolemarker);
  468. conw.innerWindow.appendChild(wconsole.wconsoleinput);
  469. consoleButton.onclick = function() {
  470. conw.open({width: 450, height: 250, left: 100, top: 50});
  471. wconsole.wconsoleinput.focus();
  472. wconsole.scrollTo({top: wconsole.scrollHeight});
  473. }
  474. if (queries.hasOwnProperty("console")) conw.open({width: 450, height: 250, left: 100, top: 50});
  475. // modal windows (managers)
  476. function openManager(type) {
  477. if (managers[type]) {
  478. if (managerClosed[currentManager]) managerClosed[currentManager]();
  479. currentManager = type;
  480. if (managerOpened[type]) managerOpened[type]();
  481. managerTitle.textContent = managerNames[type] || type + "s";
  482. clearManagers();
  483. managers[type].style.display = "block";
  484. modals.style.display = "block";
  485. }
  486. }
  487. function clearManagers() {
  488. Object.values(managers).forEach(function(e) {
  489. e.style.display = "none";
  490. });
  491. }
  492. managerClose.onclick = function() {
  493. modals.style.display = "none";
  494. clearManagers();
  495. managerTitle.textContent = "";
  496. if (managerClosed[currentManager]) managerClosed[currentManager]();
  497. currentManager = undefined;
  498. }
  499. // --- code for the keybind manager ---
  500. // convert between config strings and objects
  501. function configStrToObj(str) {
  502. var convert1 = str.slice(0, -1).split("\n");
  503. var convert2 = {};
  504. for (var i = 0; i < convert1.length; i++) {
  505. var convert3 = convert1[i].split(" = ");
  506. convert2[convert3[0]] = convert3[1].slice(1, -1);
  507. }
  508. return convert2;
  509. }
  510. function configObjToStr(obj) {
  511. var convert1 = Object.keys(obj);
  512. var convert2 = "";
  513. for (var i = 0; i < convert1.length; i++) {
  514. convert2 += convert1[i] + ' = "' + obj[convert1[i]] + '"\n';
  515. }
  516. return convert2;
  517. }
  518. // load config saved in localStorage
  519. var defaultKeybindsObj = configStrToObj(defaultKeybinds);
  520. var savedKeybindsObj = window.localStorage.getItem("RetroArch_settings_keybinds") ? configStrToObj(window.localStorage.getItem("RetroArch_settings_keybinds")) : Object.assign({}, defaultKeybindsObj);
  521. var keybindsObj = Object.assign({}, savedKeybindsObj);
  522. var validKeybinds = Object.keys(defaultKeybindsObj);
  523. // update the config list
  524. function createConfigList() {
  525. keybindTable.innerHTML = "";
  526. // make the list
  527. for (var i = 0; i < validKeybinds.length; i++) {
  528. keybindTable.innerHTML += "<tr><td>" + validKeybinds[i] + "</td><td>" + keybindsObj[validKeybinds[i]] + "</td></tr>";
  529. }
  530. // highlight conflicting keys
  531. var keysList = Object.values(keybindsObj);
  532. for (var i = 0; i < validKeybinds.length; i++) {
  533. var matches = keysList.filter(v => v == keybindsObj[validKeybinds[i]]);
  534. if (matches.length > 1 && !(matches[0] == "nul")) keybindTable.children[i].lastElementChild.classList.add("conflict");
  535. }
  536. }
  537. // rebinding a key
  538. keybindTable.onclick = function(e) {
  539. if (e.target.tagName == "TD" && !e.target.nextElementSibling) {
  540. let valueElement = e.target;
  541. let keyNo = Array.from(keybindTable.children).indexOf(e.target.parentElement);
  542. valueElement.classList.remove("conflict");
  543. valueElement.textContent = "press a key (escape to unbind)";
  544. function newKeyHandler(e) {
  545. if (e.code == "Escape") {
  546. keybindsObj[validKeybinds[keyNo]] = "nul";
  547. createConfigList();
  548. } else {
  549. keybindsObj[validKeybinds[keyNo]] = codeToConfigIDMap[e.code] || "nul";
  550. createConfigList();
  551. }
  552. finishKeybindInput();
  553. }
  554. function cancelKeybindInput() {
  555. finishKeybindInput();
  556. createConfigList();
  557. }
  558. function finishKeybindInput() {
  559. document.removeEventListener("keydown", newKeyHandler);
  560. document.removeEventListener("mousedown", cancelKeybindInput);
  561. }
  562. document.addEventListener("keydown", newKeyHandler, false);
  563. document.addEventListener("mousedown", cancelKeybindInput, false);
  564. }
  565. }
  566. function tryApplyConfig() {
  567. if (emulatorStarted) {
  568. FS.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", nulKeys + configObjToStr(savedKeybindsObj) + extraConfig);
  569. Module._cmd_reload_config();
  570. }
  571. }
  572. // save the keybinds to localStorage, and apply them
  573. saveKeybinds.onclick = function() {
  574. savedKeybindsObj = Object.assign({}, keybindsObj);
  575. window.localStorage.setItem("RetroArch_settings_keybinds", configObjToStr(savedKeybindsObj));
  576. tryApplyConfig();
  577. alert("Saved!");
  578. }
  579. resetKeybinds.onclick = function() {
  580. if (confirm("Are you sure you want to reset all of the keybinds to their default values?")) {
  581. savedKeybindsObj = Object.assign({}, defaultKeybindsObj);
  582. keybindsObj = Object.assign({}, savedKeybindsObj);
  583. window.localStorage.removeItem("RetroArch_settings_keybinds");
  584. createConfigList();
  585. tryApplyConfig();
  586. }
  587. }
  588. // --- code for the screenshot manager ---
  589. // zip and download all of the screenshots in the list
  590. downloadAllScreenshots.onclick = function() {
  591. if (screenshotDatas.length) {
  592. zipFiles(screenshotDatas, function(zd) {
  593. downloadFile(zd, "screenshots-" + getTime() + ".zip", "application/zip");
  594. }, true);
  595. } else {
  596. alert("There are no screenshots to download!");
  597. }
  598. }
  599. // update the screenshot list
  600. function createScreenshotList() {
  601. var screenshots = FS.analyzePath("/home/web_user/retroarch/userdata/screenshots/").exists ? FS.readdir("/home/web_user/retroarch/userdata/screenshots/").filter(k => ![".", ".."].includes(k)) : [];
  602. screenshotsDiv.innerHTML = "";
  603. for (var i = 0; i < screenshots.length; i++) {
  604. var screenshotData = FS.readFile("/home/web_user/retroarch/userdata/screenshots/" + screenshots[i]);
  605. var blobUrl = window.URL.createObjectURL(new Blob([screenshotData], {type: "image/png"}));
  606. screenshotDatas[i] = {name: screenshots[i], data: screenshotData};
  607. screenshotObjUrls[i] = blobUrl;
  608. screenshotsDiv.innerHTML += '<div class="screenshot"><img src="' + blobUrl + '"><input type="button" data-action="download" value="Download"><input type="button" data-action="delete" value="Delete">' + "</div>";
  609. }
  610. }
  611. // why I didn't just use the DOM? I don't know
  612. screenshotsDiv.onclick = function(e) {
  613. if (e.target.tagName == "INPUT") {
  614. var screenshotNo = Array.from(screenshotsDiv.children).indexOf(e.target.parentElement);
  615. switch(e.target.dataset.action) {
  616. case "download":
  617. downloadFile(screenshotDatas[screenshotNo].data, screenshotDatas[screenshotNo].name.replace("rom", romName), "image/png");
  618. break;
  619. case "delete":
  620. if (confirm("Are you sure you want to delete this screenshot?")) {
  621. // doing all this is probably more efficient then reloading all of the screenshots
  622. window.URL.revokeObjectURL(screenshotObjUrls[screenshotNo]);
  623. FS.unlink("/home/web_user/retroarch/userdata/screenshots/" + screenshotDatas[screenshotNo].name);
  624. screenshotObjUrls.splice(screenshotNo, 1);
  625. screenshotDatas.splice(screenshotNo, 1);
  626. e.target.parentElement.remove();
  627. }
  628. break;
  629. }
  630. }
  631. }
  632. // --- code for the save/state manager ---
  633. function updateQuotaDisplay() {
  634. navigator.storage.estimate().then(function(info) {
  635. quotaText.textContent = "Storage used (estimate): " + bytesToHumanReadable(info.usage) + " / " + bytesToHumanReadable(info.quota) + " (" + (info.usage / info.quota).toFixed(2) + "%)";
  636. });
  637. }
  638. // update the save list
  639. function createSaveList() {
  640. updateQuotaDisplay();
  641. getAllIdbItems().then(function(items) {
  642. saveTable.innerHTML = "";
  643. // make the list
  644. for (var i = 0; i < items.length; i++) {
  645. if ((/^RetroArch_(saves|states)_/).test(items[i].key)) {
  646. var sName = items[i].key.replace(/^RetroArch_(saves|states)_/, "");
  647. var sType = (/^RetroArch_saves_/).test(items[i].key) ? "save" : "state";
  648. saveIDs.push({id: items[i].key, name: sName, type: sType});
  649. saveTable.innerHTML += "<tr><td>" + capitalize(sType) + ": " + sName + '</td><td><span data-action="download">Download</span><span data-action="delete">Delete</span></td></tr>';
  650. }
  651. }
  652. });
  653. }
  654. saveTable.onclick = function(e) {
  655. if (e.target.tagName == "SPAN") {
  656. let saveNo = Array.from(saveTable.children).indexOf(e.target.parentElement.parentElement);
  657. switch(e.target.dataset.action) {
  658. case "download":
  659. getIdbItem(saveIDs[saveNo].id).then(function(data) {
  660. downloadFile(data, "game-" + saveIDs[saveNo].type + "-" + saveIDs[saveNo].name + "-" + getTime() + (saveIDs[saveNo].type == "save" ? ".srm" : ".state"));
  661. });
  662. break;
  663. case "delete":
  664. if (confirm("Are you sure you want to delete this " + saveIDs[saveNo].type + ' for "' + saveIDs[saveNo].name + '"?') && confirm("Really really sure?")) {
  665. removeIdbItem(saveIDs[saveNo].id);
  666. saveIDs.splice(saveNo, 1);
  667. e.target.parentElement.parentElement.remove();
  668. updateQuotaDisplay();
  669. }
  670. break;
  671. }
  672. }
  673. }
  674. // --- end manager-specific code ---
  675. var managerOpened = {
  676. "keybind": function() {
  677. createConfigList();
  678. },
  679. "screenshot": function() {
  680. createScreenshotList();
  681. },
  682. "save": function() {
  683. createSaveList();
  684. }
  685. };
  686. var managerClosed = {
  687. "keybind": function() {
  688. keybindsObj = Object.assign({}, savedKeybindsObj);
  689. },
  690. "screenshot": function() {
  691. // clear the blob: urls used for the screenshots
  692. for (var i = 0; i < screenshotObjUrls.length; i++) {
  693. window.URL.revokeObjectURL(screenshotObjUrls[i]);
  694. }
  695. screenshotObjUrls = [];
  696. screenshotDatas = [];
  697. },
  698. "save": function() {
  699. saveIDs = [];
  700. }
  701. };
  702. // opening the managers
  703. keybindsButton.onclick = function(e) {
  704. e.preventDefault();
  705. openManager("keybind");
  706. }
  707. screenshotsButton.onclick = function() {
  708. openManager("screenshot");
  709. }
  710. savesButton.onclick = function(e) {
  711. e.preventDefault();
  712. openManager("save");
  713. }
  714. statesButton.onclick = function(e) {
  715. e.preventDefault();
  716. openManager("save");
  717. };
  718. // ---------- START LOAD ----------
  719. (function() {
  720. checkForUpdates();
  721. // ?system query
  722. if (!queries.core && queries.system) {
  723. var detectedCore = Object.keys(systems).find(k => systems[k].toLowerCase() == queries.system.toLowerCase());
  724. if (installedCores.includes(detectedCore)) {
  725. queries.core = detectedCore;
  726. } else if (queries.system.toLowerCase() == "autodetect") {
  727. queries.core = "autodetect";
  728. } else {
  729. alert("Invalid core (" + detectedCore + ")");
  730. }
  731. }
  732. // ?core query
  733. if (queries.core) {
  734. if (!window.navigator.userAgent.toLowerCase().includes("chrom")) alert("Best performance on Chrome!");
  735. // show hover menu
  736. hoverMenu.style.display = "block";
  737. versionIndicator.textContent = "v" + webretroVersion.toString();
  738. if (queries.core.toLowerCase() == "autodetect") {
  739. romUploadCallback = autodetectCoreHandler;
  740. systemName.textContent = "";
  741. readyRomUploads(".zip, " + allFileExts);
  742. } else {
  743. romUploadCallback = initFromFile;
  744. core = queries.core;
  745. setStatus("Getting core");
  746. if (core == "desmume") sramExt = ".dsv";
  747. // detect system for ROM upload
  748. systemName.textContent = systems[core] || "";
  749. getCore(core, function() {
  750. removeStatus("Getting core");
  751. log("Got core: " + core);
  752. if (romMode != "querystring") document.title = core + " | webretro";
  753. readyRomUploads(".zip, .bin, " + fileExts[systems[core]]);
  754. }, function() {
  755. // core loading error
  756. alert('Could not load specified core "' + core + '". Here is a list of available cores.');
  757. ffdContent.innerHTML = "<ul>" + aCoreList + "</ul>";
  758. ffd.style.display = "block";
  759. });
  760. }
  761. // ?rom query
  762. if (queries.rom) {
  763. readyRomFetch();
  764. } else {
  765. // prompt user to upload ROM file
  766. romMode = "upload";
  767. ffd.style.display = "block";
  768. }
  769. } else {
  770. // no core specified
  771. ffdContent.innerHTML = "<ul>" + aCoreList + "</ul>";
  772. ffd.style.display = "block";
  773. }
  774. })();
  775. // ----------- END LOAD -----------
  776. // start emulator from file name and data
  777. function initFromFile(name, data) {
  778. var dataView = new Uint8Array(data);
  779. if (name.split(".").slice(-1)[0] == "zip") {
  780. log("Zip file detected, unzipping...");
  781. unzipFile(dataView, fileExts[systems[core]], function(name, contents) {
  782. romName = name.split(".")[0];
  783. readyForInit(contents);
  784. }, function() {
  785. alert("That zip file appears to be empty!");
  786. }, function() {
  787. 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)");
  788. });
  789. } else {
  790. romName = name.split(".")[0];
  791. readyForInit(dataView);
  792. }
  793. }
  794. // autodetect core mode
  795. function autodetectCoreHandler(name, data) {
  796. var dataView = new Uint8Array(data);
  797. if (name.split(".").slice(-1)[0] == "zip") {
  798. log("Zip file detected, unzipping...");
  799. unzipFile(dataView, allFileExts, function(name, contents) {
  800. romName = name.split(".")[0];
  801. autodetectCore(name, contents);
  802. }, function() {
  803. alert("That zip file appears to be empty!");
  804. }, function() {
  805. 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)");
  806. });
  807. } else {
  808. romName = name.split(".")[0];
  809. autodetectCore(name, dataView);
  810. }
  811. }
  812. function autodetectCore(name, data) {
  813. var nameExt = "." + name.split(".").slice(-1)[0];
  814. var detectedCore;
  815. var fileExtsArray = Object.keys(fileExts);
  816. for (var i = 0; i < fileExtsArray.length; i++) {
  817. if (fileExts[fileExtsArray[i]].split(", ").includes(nameExt)) {
  818. detectedCore = Object.keys(systems).find(k => systems[k] == fileExtsArray[i]);
  819. break;
  820. }
  821. }
  822. var detectedSystem = systems[detectedCore] || "unknown";
  823. detectedCore = detectedCore || "unknown";
  824. if (allValidFileExts.split(", ").includes(nameExt)) {
  825. core = detectedCore;
  826. setStatus("Getting core");
  827. if (core == "desmume") sramExt = ".dsv";
  828. getCore(core, function() {
  829. removeStatus("Getting core");
  830. log("Got core: " + core);
  831. readyForInit(data);
  832. });
  833. } else {
  834. alert("That is a " + detectedSystem + " file! " + detectedCore + " (" + detectedSystem + ") is not currently supported.");
  835. }
  836. }
  837. // if the ROM is specified in the querystring, we will need to wait until the user has clicked to start the emulator
  838. function readyForInit(data) {
  839. document.title = romName + " | webretro";
  840. if (queries.romshift) data = avShift(data, parseInt(queries.romshift));
  841. // remove the file drop listeners
  842. if (romUploadsReady) {
  843. document.removeEventListener("dragenter", fileDragEnter);
  844. document.removeEventListener("dragover", fileDragOver);
  845. document.removeEventListener("drop", fileDropped);
  846. }
  847. if (romMode == "querystring") {
  848. // 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)
  849. startButton.style.display = "initial";
  850. startButton.onclick = function() {
  851. startButton.style.display = "none";
  852. initFromData(data);
  853. }
  854. } else {
  855. initFromData(data);
  856. }
  857. }
  858. // prepare FS with bundle
  859. function prepareBundle() {
  860. setStatus("Getting assets");
  861. log("Starting bundle fetch");
  862. let bundleSTime = performance.now();
  863. grab(bundleCdnLatest + "bundle/indexedfiles-v2.txt", "text", function(data) {
  864. var splitData = data.split(",,,\n");
  865. fsBundleDirs = JSON.parse(splitData[0]);
  866. fsBundleFiles = splitData[1].split("\n");
  867. // make the paths
  868. FS.createPath("/", "home/web_user/retroarch/bundle", true, true);
  869. for (var i = 0; i < fsBundleDirs.length; i++) {
  870. FS.createPath(baseFsBundleDir + fsBundleDirs[i][0], fsBundleDirs[i][1], true, true);
  871. }
  872. // make the files
  873. for (let i = 0; i < fsBundleFiles.length; i++) {
  874. grab(bundleCdn + "bundle" + fsBundleFiles[i], "arraybuffer", function(data) {
  875. FS.writeFile(baseFsBundleDir + fsBundleFiles[i], new Uint8Array(data));
  876. if (i == fsBundleFiles.length - 1) donePreparingBundle(performance.now() - bundleSTime);
  877. }, function() {
  878. bundleErrors += 1;
  879. if (i == fsBundleFiles.length - 1) donePreparingBundle(performance.now() - bundleSTime);
  880. });
  881. }
  882. }, function() {
  883. log("Failed to get asset bundle, skipping");
  884. bundleReady = true;
  885. removeStatus("Getting assets");
  886. });
  887. }
  888. function donePreparingBundle(tooktime) {
  889. bundleReady = true;
  890. removeStatus("Getting assets");
  891. log("Finished bundle fetch in " + (tooktime / 1000).toFixed(1) + " seconds, " + bundleErrors + " errors");
  892. }
  893. // tell the user to not rename the rom
  894. function doNotRename() {
  895. if (romMode == "upload" && !window.localStorage.getItem("webretro_settings_pastFirstSave")) {
  896. alert("WARNING: Do not rename your ROM file after this! The save data is specific to the ROM name!");
  897. window.localStorage.setItem("webretro_settings_pastFirstSave", "true");
  898. }
  899. }
  900. // save game
  901. function saveSRAM() {
  902. Module._cmd_savefiles();
  903. window.setTimeout(function() {
  904. if (FS.analyzePath("/home/web_user/retroarch/userdata/saves/rom" + sramExt).exists) {
  905. setIdbItem("RetroArch_saves_" + romName, FS.readFile("/home/web_user/retroarch/userdata/saves/rom" + sramExt));
  906. new sideAlert("Saved", 3000);
  907. readySaveReaders();
  908. doNotRename();
  909. } else {
  910. autosave.checked = false;
  911. new sideAlert("This game does not save!", 3000);
  912. }
  913. }, 1000);
  914. }
  915. // save state
  916. function saveStateFunc() {
  917. window.setTimeout(function() {
  918. if (FS.analyzePath("/home/web_user/retroarch/userdata/states/rom.state").exists) {
  919. setIdbItem("RetroArch_states_" + romName, FS.readFile("/home/web_user/retroarch/userdata/states/rom.state"));
  920. doNotRename();
  921. } else {
  922. new sideAlert("There was an error saving state. Please try again.", 5000);
  923. }
  924. }, 100);
  925. }
  926. // autosaving
  927. function autosaveSRAM() {
  928. if (autosave.checked && !document.hidden && !isPaused) {
  929. new sideAlert("Autosaving...", 3000);
  930. saveSRAM();
  931. }
  932. window.setTimeout(function() {
  933. autosaveSRAM();
  934. }, 300000);
  935. }
  936. // more functions for state buttons
  937. function readyStateReaders() {
  938. if (!stateReadersReady) {
  939. stateReadersReady = true;
  940. loadState.classList.remove("disabled");
  941. exportState.classList.remove("disabled");
  942. undoSaveState.classList.remove("disabled");
  943. loadState.onclick = function() {
  944. Module._cmd_load_state();
  945. readyStateReaders2();
  946. }
  947. exportState.onclick = function() {
  948. downloadFile(FS.readFile("/home/web_user/retroarch/userdata/states/rom.state"), "game-state-" + romName + "-" + getTime() + ".state");
  949. }
  950. undoSaveState.onclick = function() {
  951. Module._cmd_undo_save_state();
  952. }
  953. // also allow statereaders2 on load state press
  954. document.addEventListener("keydown", function(e) {
  955. if (!stateReaders2Ready && (e.code == Object.keys(codeToConfigIDMap).find(k => codeToConfigIDMap[k] == savedKeybindsObj.input_load_state))) readyStateReaders2();
  956. }, false);
  957. }
  958. }
  959. // even more functions for state buttons
  960. function readyStateReaders2() {
  961. if (!stateReaders2Ready) {
  962. stateReaders2Ready = true;
  963. undoLoadState.classList.remove("disabled");
  964. undoLoadState.onclick = function() {
  965. Module._cmd_undo_load_state();
  966. }
  967. }
  968. }
  969. // more functions for save buttons
  970. function readySaveReaders() {
  971. if (!saveReadersReady) {
  972. saveReadersReady = true;
  973. exportSave.classList.remove("disabled");
  974. exportSave.onclick = function() {
  975. downloadFile(FS.readFile("/home/web_user/retroarch/userdata/saves/rom" + sramExt), "game-sram-" + romName + "-" + getTime() + sramExt);
  976. }
  977. }
  978. }
  979. // runs after emulator starts
  980. function afterStart() {
  981. emulatorStarted = true;
  982. // remove loading text
  983. canvas.style.background = "none";
  984. adjustCanvasSize();
  985. // functions for save and state buttons
  986. saveState.classList.remove("disabled");
  987. importState.classList.remove("disabled");
  988. saveGame.classList.remove("disabled");
  989. importSave.classList.remove("disabled");
  990. autosave.removeAttribute("disabled");
  991. autosave.parentElement.classList.remove("disabled");
  992. saveState.onclick = function() {
  993. Module._cmd_save_state();
  994. readyStateReaders();
  995. }
  996. importState.onclick = function() {
  997. uploadFile(".bin, .state, .save, .dat, .gam, .sav, application/*", function(file) {
  998. setIdbItem("RetroArch_states_" + romName, new Uint8Array(file.data));
  999. FS.writeFile("/home/web_user/retroarch/userdata/states/rom.state", new Uint8Array(file.data));
  1000. new sideAlert("Imported state (Now press load state)", 3000);
  1001. readyStateReaders();
  1002. });
  1003. }
  1004. saveGame.onclick = function() {
  1005. new sideAlert("Saving...", 3000);
  1006. saveSRAM();
  1007. }
  1008. importSave.onclick = function() {
  1009. uploadFile(".bin, .srm, .sram, .ram, .gam, .sav, .dsv, application/*", function(file) {
  1010. autosave.checked = false;
  1011. setIdbItem("RetroArch_saves_" + romName, new Uint8Array(file.data));
  1012. if (confirm("Save imported. Reloading now for changes to take effect.")) {
  1013. window.onbeforeunload = function() {}
  1014. window.location.reload();
  1015. }
  1016. });
  1017. }
  1018. // also allow state readers on save state press
  1019. document.addEventListener("keydown", function(e) {
  1020. if (!stateReadersReady && (e.code == Object.keys(codeToConfigIDMap).find(k => codeToConfigIDMap[k] == savedKeybindsObj.input_save_state))) readyStateReaders();
  1021. }, false);
  1022. // start autosave loop
  1023. window.setTimeout(function() {
  1024. autosaveSRAM();
  1025. }, 300000);
  1026. // toggle between sharp and smooth canvas graphics
  1027. smooth.removeAttribute("disabled");
  1028. smooth.parentElement.classList.remove("disabled");
  1029. smooth.onclick = function() {
  1030. if (this.checked) {
  1031. canvas.className = "textureSmooth";
  1032. } else {
  1033. canvas.className = "texturePixelated";
  1034. }
  1035. }
  1036. // higher resolution
  1037. doubleRes.removeAttribute("disabled");
  1038. doubleRes.parentElement.classList.remove("disabled");
  1039. doubleRes.onclick = function() {
  1040. if (this.checked) {
  1041. resModifier = 2;
  1042. adjustCanvasSize();
  1043. } else {
  1044. resModifier = 1;
  1045. adjustCanvasSize();
  1046. }
  1047. }
  1048. // pause and resume
  1049. pause.classList.remove("disabled");
  1050. pause.onclick = function() {
  1051. if (this.textContent.trim() == "Pause") {
  1052. Module.pauseMainLoop();
  1053. isPaused = true;
  1054. this.textContent = "Resume";
  1055. document.body.classList.add("paused");
  1056. } else {
  1057. Module.resumeMainLoop();
  1058. isPaused = false;
  1059. this.textContent = "Pause";
  1060. document.body.classList.remove("paused");
  1061. }
  1062. }
  1063. resumeOverlay.onclick = function() {
  1064. pause.click();
  1065. }
  1066. // toggle menu
  1067. menuButton.classList.remove("disabled");
  1068. menuButton.onclick = function() {
  1069. Module._cmd_toggle_menu();
  1070. }
  1071. // screenshot button
  1072. takeScreenshot.classList.remove("disabled");
  1073. takeScreenshot.onclick = function() {
  1074. Module._cmd_take_screenshot();
  1075. }
  1076. // flash the menu on first use
  1077. if (!window.localStorage.getItem("webretro_settings_pastFirstStart")) {
  1078. hoverMenu.classList.add("show");
  1079. hoverMenuIndicator.classList.add("show");
  1080. window.setTimeout(function() {
  1081. hoverMenu.classList.remove("show");
  1082. hoverMenuIndicator.classList.remove("show");
  1083. }, 3000);
  1084. window.localStorage.setItem("webretro_settings_pastFirstStart", "true");
  1085. }
  1086. // ctrl+v inside canvas
  1087. document.addEventListener("keydown", function(e) {
  1088. if (e.ctrlKey && e.key == "v") {
  1089. fakeKeyPress({code: "Backspace"});
  1090. navigator.clipboard.readText().then(function(text) {
  1091. sendText(text);
  1092. });
  1093. }
  1094. }, false);
  1095. }
  1096. // start
  1097. function initFromData(data) {
  1098. window.onbeforeunload = function() { return true; }
  1099. async function waitForReady() {
  1100. if (wasmReady && bundleReady) {
  1101. setStatus("Waiting for emulator");
  1102. log("Initializing with " + data.byteLength + " bytes of data");
  1103. updateNotice.style.display = "none";
  1104. canvas.addEventListener("contextmenu", function(e) {
  1105. e.preventDefault();
  1106. }, false);
  1107. window.addEventListener("resize", adjustCanvasSize, false);
  1108. adjustCanvasSize();
  1109. // prevent defaults for key presses
  1110. document.addEventListener("keydown", function(e) {
  1111. if (pdKeys.includes(e.which)) e.preventDefault();
  1112. }, false);
  1113. // move the saves and states from the old version (localStorage) to indexedDB
  1114. await tryLsToIdb();
  1115. // rom
  1116. FS.writeFile("/rom.bin", data);
  1117. // SMAS brick fix
  1118. if (systems[core] == "SNES") {
  1119. var hash = md5(u8atoutf8(data));
  1120. if (smasBrickFix.hasOwnProperty(hash)) {
  1121. FS.writeFile("/rom.ips", new Uint8Array(smasBrickFix[hash]));
  1122. new sideAlert("SMAS Bricks Fixed!", 5000);
  1123. }
  1124. }
  1125. // load save
  1126. var cSave = await getIdbItem("RetroArch_saves_" + romName);
  1127. if (cSave) {
  1128. FS.createPath("/", "home/web_user/retroarch/userdata/saves", true, true);
  1129. FS.writeFile("/home/web_user/retroarch/userdata/saves/rom" + sramExt, cSave);
  1130. new sideAlert("Save loaded for " + romName, 5000);
  1131. log("Save loaded for " + romName);
  1132. readySaveReaders();
  1133. }
  1134. // import state
  1135. var cState = await getIdbItem("RetroArch_states_" + romName);
  1136. if (cState) {
  1137. FS.createPath("/", "home/web_user/retroarch/userdata/states", true, true);
  1138. FS.writeFile("/home/web_user/retroarch/userdata/states/rom.state", cState);
  1139. new sideAlert("State imported for " + romName, 5000);
  1140. log("State imported for " + romName);
  1141. readyStateReaders();
  1142. }
  1143. // config
  1144. FS.createPath("/", "home/web_user/retroarch/userdata", true, true);
  1145. FS.writeFile("/home/web_user/retroarch/userdata/retroarch.cfg", nulKeys + configObjToStr(savedKeybindsObj) + extraConfig);
  1146. // start
  1147. Module.callMain(Module.arguments);
  1148. adjustCanvasSize();
  1149. window.setTimeout(afterStart, 2000);
  1150. } else {
  1151. window.setTimeout(waitForReady, 1000);
  1152. }
  1153. }
  1154. waitForReady();
  1155. }
  1156. var Module = {
  1157. canvas: canvas,
  1158. noInitialRun: true,
  1159. arguments: ["/rom.bin", "--verbose"],
  1160. onRuntimeInitialized: function() {
  1161. wasmReady = true;
  1162. log("WASM ready");
  1163. // fetch asset bundle
  1164. if (queries.hasOwnProperty("nobundle")) {
  1165. bundleReady = true;
  1166. log("Skipping bundle");
  1167. } else {
  1168. prepareBundle();
  1169. }
  1170. },
  1171. print: function(text) {
  1172. log("stdout: " + text);
  1173. },
  1174. printErr: function(text) {
  1175. log("stderr: " + text);
  1176. }
  1177. };