var inFullscreen = false; var mainCanvas = null; var fullscreenCanvas = null; var showAsMinimal = false; var keyZones = [ ["right", [39]], ["left", [37]], ["up", [38]], ["down", [40]], ["a", [88, 74]], ["b", [90, 81, 89]], ["select", [16]], ["start", [13]] ]; function windowingInitialize() { cout("windowingInitialize() called.", 0); windowStacks[0] = windowCreate("GameBoy", true); windowStacks[1] = windowCreate("terminal", false); windowStacks[2] = windowCreate("about", false); windowStacks[3] = windowCreate("settings", false); windowStacks[4] = windowCreate("input_select", false); windowStacks[5] = windowCreate("instructions", false); windowStacks[6] = windowCreate("local_storage_popup", false); windowStacks[7] = windowCreate("local_storage_listing", false); windowStacks[8] = windowCreate("freeze_listing", false); windowStacks[9] = windowCreate("save_importer", false); mainCanvas = document.getElementById("mainCanvas"); fullscreenCanvas = document.getElementById("fullscreen"); try { //Hook the GUI controls. registerGUIEvents(); } catch (error) { cout("Fatal windowing error: \"" + error.message + "\" file:" + error.fileName + " line: " + error.lineNumber, 2); } //Update the settings to the emulator's default: document.getElementById("enable_sound").checked = settings[0]; document.getElementById("enable_gbc_bios").checked = settings[1]; document.getElementById("disable_colors").checked = settings[2]; document.getElementById("rom_only_override").checked = settings[9]; document.getElementById("mbc_enable_override").checked = settings[10]; document.getElementById("enable_colorization").checked = settings[4]; document.getElementById("do_minimal").checked = showAsMinimal; document.getElementById("software_resizing").checked = settings[12]; document.getElementById("typed_arrays_disallow").checked = settings[5]; document.getElementById("gb_boot_rom_utilized").checked = settings[11]; document.getElementById("resize_smoothing").checked = settings[13]; document.getElementById("channel1").checked = settings[14][0]; document.getElementById("channel2").checked = settings[14][1]; document.getElementById("channel3").checked = settings[14][2]; document.getElementById("channel4").checked = settings[14][3]; } function registerGUIEvents() { cout("In registerGUIEvents() : Registering GUI Events.", -1); addEvent("click", document.getElementById("terminal_clear_button"), clear_terminal); addEvent("click", document.getElementById("local_storage_list_refresh_button"), refreshStorageListing); addEvent("click", document.getElementById("terminal_close_button"), function () { windowStacks[1].hide() }); addEvent("click", document.getElementById("about_close_button"), function () { windowStacks[2].hide() }); addEvent("click", document.getElementById("settings_close_button"), function () { windowStacks[3].hide() }); addEvent("click", document.getElementById("input_select_close_button"), function () { windowStacks[4].hide() }); addEvent("click", document.getElementById("instructions_close_button"), function () { windowStacks[5].hide() }); addEvent("click", document.getElementById("local_storage_list_close_button"), function () { windowStacks[7].hide() }); addEvent("click", document.getElementById("local_storage_popup_close_button"), function () { windowStacks[6].hide() }); addEvent("click", document.getElementById("save_importer_close_button"), function () { windowStacks[9].hide() }); addEvent("click", document.getElementById("freeze_list_close_button"), function () { windowStacks[8].hide() }); addEvent("click", document.getElementById("GameBoy_about_menu"), function () { windowStacks[2].show() }); addEvent("click", document.getElementById("GameBoy_settings_menu"), function () { windowStacks[3].show() }); addEvent("click", document.getElementById("local_storage_list_menu"), function () { refreshStorageListing(); windowStacks[7].show(); }); addEvent("click", document.getElementById("freeze_list_menu"), function () { refreshFreezeListing(); windowStacks[8].show(); }); addEvent("click", document.getElementById("view_importer"), function () { windowStacks[9].show() }); addEvent("keydown", document, keyDown); addEvent("keyup", document, function (event) { if (event.keyCode == 27) { //Fullscreen on/off fullscreenPlayer(); } else { //Control keys / other keyUp(event); } }); addEvent("MozOrientation", window, GameBoyGyroSignalHandler); addEvent("deviceorientation", window, GameBoyGyroSignalHandler); new popupMenu(document.getElementById("GameBoy_file_menu"), document.getElementById("GameBoy_file_popup")); addEvent("click", document.getElementById("data_uri_clicker"), function () { var datauri = prompt("Please input the ROM image's Base 64 Encoded Text:", ""); if (datauri != null && datauri.length > 0) { try { cout(Math.floor(datauri.length * 3 / 4) + " bytes of data submitted by form (text length of " + datauri.length + ").", 0); initPlayer(); start(mainCanvas, base64_decode(datauri)); } catch (error) { alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); } } }); addEvent("click", document.getElementById("set_volume"), function () { if (GameBoyEmulatorInitialized()) { var volume = prompt("Set the volume here:", "1.0"); if (volume != null && volume.length > 0) { settings[3] = Math.min(Math.max(parseFloat(volume), 0), 1); gameboy.changeVolume(); } } }); addEvent("click", document.getElementById("set_speed"), function () { if (GameBoyEmulatorInitialized()) { var speed = prompt("Set the emulator speed here:", "1.0"); if (speed != null && speed.length > 0) { gameboy.setSpeed(Math.max(parseFloat(speed), 0.001)); } } }); addEvent("click", document.getElementById("internal_file_clicker"), function () { var file_opener = document.getElementById("local_file_open"); windowStacks[4].show(); file_opener.click(); }); addEvent("blur", document.getElementById("input_select"), function () { windowStacks[4].hide(); }); addEvent("change", document.getElementById("local_file_open"), function () { windowStacks[4].hide(); if (typeof this.files != "undefined") { try { if (this.files.length >= 1) { cout("Reading the local file \"" + this.files[0].name + "\"", 0); try { //Gecko 1.9.2+ (Standard Method) var binaryHandle = new FileReader(); binaryHandle.onload = function () { if (this.readyState == 2) { cout("file loaded.", 0); try { initPlayer(); start(mainCanvas, this.result); } catch (error) { alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); } } else { cout("loading file, please wait...", 0); } } binaryHandle.readAsBinaryString(this.files[this.files.length - 1]); } catch (error) { cout("Browser does not support the FileReader object, falling back to the non-standard File object access,", 2); //Gecko 1.9.0, 1.9.1 (Non-Standard Method) var romImageString = this.files[this.files.length - 1].getAsBinary(); try { initPlayer(); start(mainCanvas, romImageString); } catch (error) { alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); } } } else { cout("Incorrect number of files selected for local loading.", 1); } } catch (error) { cout("Could not load in a locally stored ROM file.", 2); } } else { cout("could not find the handle on the file to open.", 2); } }); addEvent("change", document.getElementById("save_open"), function () { windowStacks[9].hide(); if (typeof this.files != "undefined") { try { if (this.files.length >= 1) { cout("Reading the local file \"" + this.files[0].name + "\" for importing.", 0); try { //Gecko 1.9.2+ (Standard Method) var binaryHandle = new FileReader(); binaryHandle.onload = function () { if (this.readyState == 2) { cout("file imported.", 0); try { import_save(this.result); refreshStorageListing(); } catch (error) { alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); } } else { cout("importing file, please wait...", 0); } } binaryHandle.readAsBinaryString(this.files[this.files.length - 1]); } catch (error) { cout("Browser does not support the FileReader object, falling back to the non-standard File object access,", 2); //Gecko 1.9.0, 1.9.1 (Non-Standard Method) var romImageString = this.files[this.files.length - 1].getAsBinary(); try { import_save(romImageString); refreshStorageListing(); } catch (error) { alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); } } } else { cout("Incorrect number of files selected for local loading.", 1); } } catch (error) { cout("Could not load in a locally stored ROM file.", 2); } } else { cout("could not find the handle on the file to open.", 2); } }); addEvent("click", document.getElementById("restart_cpu_clicker"), function () { if (GameBoyEmulatorInitialized()) { try { if (!gameboy.fromSaveState) { initPlayer(); start(mainCanvas, gameboy.getROMImage()); } else { initPlayer(); openState(gameboy.savedStateFileName, mainCanvas); } } catch (error) { alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); } } else { cout("Could not restart, as a previous emulation session could not be found.", 1); } }); addEvent("click", document.getElementById("run_cpu_clicker"), function () { run(); }); addEvent("click", document.getElementById("kill_cpu_clicker"), function () { pause(); }); addEvent("click", document.getElementById("save_state_clicker"), function () { save(); }); addEvent("click", document.getElementById("save_SRAM_state_clicker"), function () { saveSRAM(); }); addEvent("click", document.getElementById("enable_sound"), function () { settings[0] = document.getElementById("enable_sound").checked; if (GameBoyEmulatorInitialized()) { gameboy.initSound(); } }); addEvent("click", document.getElementById("disable_colors"), function () { settings[2] = document.getElementById("disable_colors").checked; }); addEvent("click", document.getElementById("rom_only_override"), function () { settings[9] = document.getElementById("rom_only_override").checked; }); addEvent("click", document.getElementById("mbc_enable_override"), function () { settings[10] = document.getElementById("mbc_enable_override").checked; }); addEvent("click", document.getElementById("enable_gbc_bios"), function () { settings[1] = document.getElementById("enable_gbc_bios").checked; }); addEvent("click", document.getElementById("enable_colorization"), function () { settings[4] = document.getElementById("enable_colorization").checked; }); addEvent("click", document.getElementById("do_minimal"), function () { showAsMinimal = document.getElementById("do_minimal").checked; fullscreenCanvas.className = (showAsMinimal) ? "minimum" : "maximum"; }); addEvent("click", document.getElementById("software_resizing"), function () { settings[12] = document.getElementById("software_resizing").checked; if (GameBoyEmulatorInitialized()) { gameboy.initLCD(); } }); addEvent("click", document.getElementById("typed_arrays_disallow"), function () { settings[5] = document.getElementById("typed_arrays_disallow").checked; }); addEvent("click", document.getElementById("gb_boot_rom_utilized"), function () { settings[11] = document.getElementById("gb_boot_rom_utilized").checked; }); addEvent("click", document.getElementById("resize_smoothing"), function () { settings[13] = document.getElementById("resize_smoothing").checked; if (GameBoyEmulatorInitialized()) { gameboy.initLCD(); } }); addEvent("click", document.getElementById("channel1"), function () { settings[14][0] = document.getElementById("channel1").checked; }); addEvent("click", document.getElementById("channel2"), function () { settings[14][1] = document.getElementById("channel2").checked; }); addEvent("click", document.getElementById("channel3"), function () { settings[14][2] = document.getElementById("channel3").checked; }); addEvent("click", document.getElementById("channel4"), function () { settings[14][3] = document.getElementById("channel4").checked; }); addEvent("click", document.getElementById("view_fullscreen"), fullscreenPlayer); new popupMenu(document.getElementById("GameBoy_view_menu"), document.getElementById("GameBoy_view_popup")); addEvent("click", document.getElementById("view_terminal"), function () { windowStacks[1].show() }); addEvent("click", document.getElementById("view_instructions"), function () { windowStacks[5].show() }); addEvent("mouseup", document.getElementById("gfx"), initNewCanvasSize); addEvent("resize", window, initNewCanvasSize); addEvent("unload", window, function () { autoSave(); }); } function keyDown(event) { var keyCode = event.keyCode; var keyMapLength = keyZones.length; for (var keyMapIndex = 0; keyMapIndex < keyMapLength; ++keyMapIndex) { var keyCheck = keyZones[keyMapIndex]; var keysMapped = keyCheck[1]; var keysTotal = keysMapped.length; for (var index = 0; index < keysTotal; ++index) { if (keysMapped[index] == keyCode) { GameBoyKeyDown(keyCheck[0]); try { event.preventDefault(); } catch (error) { } } } } } function keyUp(event) { var keyCode = event.keyCode; var keyMapLength = keyZones.length; for (var keyMapIndex = 0; keyMapIndex < keyMapLength; ++keyMapIndex) { var keyCheck = keyZones[keyMapIndex]; var keysMapped = keyCheck[1]; var keysTotal = keysMapped.length; for (var index = 0; index < keysTotal; ++index) { if (keysMapped[index] == keyCode) { GameBoyKeyUp(keyCheck[0]); try { event.preventDefault(); } catch (error) { } } } } } function initPlayer() { document.getElementById("title").style.display = "none"; document.getElementById("port_title").style.display = "none"; document.getElementById("fullscreenContainer").style.display = "none"; } function fullscreenPlayer() { if (GameBoyEmulatorInitialized()) { if (!inFullscreen) { gameboy.canvas = fullscreenCanvas; fullscreenCanvas.className = (showAsMinimal) ? "minimum" : "maximum"; document.getElementById("fullscreenContainer").style.display = "block"; windowStacks[0].hide(); } else { gameboy.canvas = mainCanvas; document.getElementById("fullscreenContainer").style.display = "none"; windowStacks[0].show(); } gameboy.initLCD(); inFullscreen = !inFullscreen; } else { cout("Cannot go into fullscreen mode.", 2); } } function runFreeze(keyName) { try { windowStacks[8].hide(); initPlayer(); openState(keyName, mainCanvas); } catch (error) { cout("A problem with attempting to open the selected save state occurred.", 2); } } //Wrapper for localStorage getItem, so that data can be retrieved in various types. function findValue(key) { try { if (window.localStorage.getItem(key) != null) { return JSON.parse(window.localStorage.getItem(key)); } } catch (error) { //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): if (window.globalStorage[location.hostname].getItem(key) != null) { return JSON.parse(window.globalStorage[location.hostname].getItem(key)); } } return null; } //Wrapper for localStorage setItem, so that data can be set in various types. function setValue(key, value) { try { window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): window.globalStorage[location.hostname].setItem(key, JSON.stringify(value)); } } //Wrapper for localStorage removeItem, so that data can be set in various types. function deleteValue(key) { try { window.localStorage.removeItem(key); } catch (error) { //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): window.globalStorage[location.hostname].removeItem(key); } } function outputLocalStorageLink(keyName, dataFound, downloadName) { return generateDownloadLink("data:application/octet-stream;base64," + dataFound, keyName, downloadName); } function refreshFreezeListing() { var storageListMasterDivSub = document.getElementById("freezeListingMasterContainerSub"); var storageListMasterDiv = document.getElementById("freezeListingMasterContainer"); storageListMasterDiv.removeChild(storageListMasterDivSub); storageListMasterDivSub = document.createElement("div"); storageListMasterDivSub.id = "freezeListingMasterContainerSub"; var keys = getLocalStorageKeys(); while (keys.length > 0) { key = keys.shift(); if (key.substring(0, 7) == "FREEZE_") { storageListMasterDivSub.appendChild(outputFreezeStateRequestLink(key)); } } storageListMasterDiv.appendChild(storageListMasterDivSub); } function outputFreezeStateRequestLink(keyName) { var linkNode = generateLink("javascript:runFreeze(\"" + keyName + "\")", keyName); var storageContainerDiv = document.createElement("div"); storageContainerDiv.className = "storageListingContainer"; storageContainerDiv.appendChild(linkNode) return storageContainerDiv; } function refreshStorageListing() { var storageListMasterDivSub = document.getElementById("storageListingMasterContainerSub"); var storageListMasterDiv = document.getElementById("storageListingMasterContainer"); storageListMasterDiv.removeChild(storageListMasterDivSub); storageListMasterDivSub = document.createElement("div"); storageListMasterDivSub.id = "storageListingMasterContainerSub"; var keys = getLocalStorageKeys(); var blobPairs = []; for (var index = 0; index < keys.length; ++index) { blobPairs[index] = getBlobPreEncoded(keys[index]); storageListMasterDivSub.appendChild(outputLocalStorageRequestLink(keys[index])); } storageListMasterDiv.appendChild(storageListMasterDivSub); var linkToManipulate = document.getElementById("download_local_storage_dba"); linkToManipulate.href = "data:application/octet-stream;base64," + base64(generateMultiBlob(blobPairs)); linkToManipulate.download = "gameboy_color_saves.export"; } function getBlobPreEncoded(keyName) { if (keyName.substring(0, 9) == "B64_SRAM_") { return [keyName.substring(4), base64_decode(findValue(keyName))]; } else if (keyName.substring(0, 5) == "SRAM_") { return [keyName, convertToBinary(findValue(keyName))]; } else { return [keyName, JSON.stringify(findValue(keyName))]; } } function outputLocalStorageRequestLink(keyName) { var linkNode = generateLink("javascript:popupStorageDialog(\"" + keyName + "\")", keyName); var storageContainerDiv = document.createElement("div"); storageContainerDiv.className = "storageListingContainer"; storageContainerDiv.appendChild(linkNode) return storageContainerDiv; } function popupStorageDialog(keyName) { var subContainer = document.getElementById("storagePopupMasterContainer"); var parentContainer = document.getElementById("storagePopupMasterParent"); parentContainer.removeChild(subContainer); subContainer = document.createElement("div"); subContainer.id = "storagePopupMasterContainer"; parentContainer.appendChild(subContainer); var downloadDiv = document.createElement("div"); downloadDiv.id = "storagePopupDownload"; if (keyName.substring(0, 9) == "B64_SRAM_") { var downloadDiv2 = document.createElement("div"); downloadDiv2.id = "storagePopupDownloadRAW"; downloadDiv2.appendChild(outputLocalStorageLink("Download RAW save data.", findValue(keyName), keyName)); subContainer.appendChild(downloadDiv2); downloadDiv.appendChild(outputLocalStorageLink("Download in import compatible format.", base64(generateBlob(keyName.substring(4), base64_decode(findValue(keyName)))), keyName)); } else if (keyName.substring(0, 5) == "SRAM_") { var downloadDiv2 = document.createElement("div"); downloadDiv2.id = "storagePopupDownloadRAW"; downloadDiv2.appendChild(outputLocalStorageLink("Download RAW save data.", base64(convertToBinary(findValue(keyName))), keyName)); subContainer.appendChild(downloadDiv2); downloadDiv.appendChild(outputLocalStorageLink("Download in import compatible format.", base64(generateBlob(keyName, convertToBinary(findValue(keyName)))), keyName)); } else { downloadDiv.appendChild(outputLocalStorageLink("Download in import compatible format.", base64(generateBlob(keyName, JSON.stringify(findValue(keyName)))), keyName)); } var deleteLink = generateLink("javascript:deleteStorageSlot(\"" + keyName + "\")", "Delete data item from HTML5 local storage."); deleteLink.id = "storagePopupDelete"; subContainer.appendChild(downloadDiv); subContainer.appendChild(deleteLink); windowStacks[6].show(); } function convertToBinary(jsArray) { var length = jsArray.length; var binString = ""; for (var indexBin = 0; indexBin < length; indexBin++) { binString += String.fromCharCode(jsArray[indexBin]); } return binString; } function deleteStorageSlot(keyName) { deleteValue(keyName); windowStacks[6].hide(); refreshStorageListing(); } function generateLink(address, textData) { var link = document.createElement("a"); link.href = address; link.appendChild(document.createTextNode(textData)); return link; } function generateDownloadLink(address, textData, keyName) { var link = generateLink(address, textData); link.download = keyName + ".sav"; return link; } function checkStorageLength() { try { return window.localStorage.length; } catch (error) { //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): return window.globalStorage[location.hostname].length; } } function getLocalStorageKeys() { var storageLength = checkStorageLength(); var keysFound = []; var index = 0; var nextKey = null; while (index < storageLength) { nextKey = findKey(index++); if (nextKey !== null && nextKey.length > 0) { if (nextKey.substring(0, 5) == "SRAM_" || nextKey.substring(0, 9) == "B64_SRAM_" || nextKey.substring(0, 7) == "FREEZE_" || nextKey.substring(0, 4) == "RTC_") { keysFound.push(nextKey); } } else { break; } } return keysFound; } function findKey(keyNum) { try { return window.localStorage.key(keyNum); } catch (error) { //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): return window.globalStorage[location.hostname].key(keyNum); } return null; } //Some wrappers and extensions for non-DOM3 browsers: function isDescendantOf(ParentElement, toCheck) { if (!ParentElement || !toCheck) { return false; } //Verify an object as either a direct or indirect child to another object. function traverseTree(domElement) { while (domElement != null) { if (domElement.nodeType == 1) { if (isSameNode(domElement, toCheck)) { return true; } if (hasChildNodes(domElement)) { if (traverseTree(domElement.firstChild)) { return true; } } } domElement = domElement.nextSibling; } return false; } return traverseTree(ParentElement.firstChild); } function hasChildNodes(oElement) { return (typeof oElement.hasChildNodes == "function") ? oElement.hasChildNodes() : ((oElement.firstChild != null) ? true : false); } function isSameNode(oCheck1, oCheck2) { return (typeof oCheck1.isSameNode == "function") ? oCheck1.isSameNode(oCheck2) : (oCheck1 === oCheck2); } function pageXCoord(event) { if (typeof event.pageX == "undefined") { return event.clientX + document.documentElement.scrollLeft; } return event.pageX; } function pageYCoord(event) { if (typeof event.pageY == "undefined") { return event.clientY + document.documentElement.scrollTop; } return event.pageY; } function mouseLeaveVerify(oElement, event) { //Hook target element with onmouseout and use this function to verify onmouseleave. return isDescendantOf(oElement, (typeof event.target != "undefined") ? event.target : event.srcElement) && !isDescendantOf(oElement, (typeof event.relatedTarget != "undefined") ? event.relatedTarget : event.toElement); } function mouseEnterVerify(oElement, event) { //Hook target element with onmouseover and use this function to verify onmouseenter. return !isDescendantOf(oElement, (typeof event.target != "undefined") ? event.target : event.srcElement) && isDescendantOf(oElement, (typeof event.relatedTarget != "undefined") ? event.relatedTarget : event.fromElement); } function addEvent(sEvent, oElement, fListener) { try { oElement.addEventListener(sEvent, fListener, false); cout("In addEvent() : Standard addEventListener() called to add a(n) \"" + sEvent + "\" event.", -1); } catch (error) { oElement.attachEvent("on" + sEvent, fListener); //Pity for IE. cout("In addEvent() : Nonstandard attachEvent() called to add an \"on" + sEvent + "\" event.", -1); } } function removeEvent(sEvent, oElement, fListener) { try { oElement.removeEventListener(sEvent, fListener, false); cout("In removeEvent() : Standard removeEventListener() called to remove a(n) \"" + sEvent + "\" event.", -1); } catch (error) { oElement.detachEvent("on" + sEvent, fListener); //Pity for IE. cout("In removeEvent() : Nonstandard detachEvent() called to remove an \"on" + sEvent + "\" event.", -1); } }