| @@ -0,0 +1,214 @@ | ||
| +"use strict"; | ||
| +/* | ||
| + * This file is part of IodineGBA | ||
| + * | ||
| + * Copyright (C) 2012-2013 Grant Galitz | ||
| + * | ||
| + * This program is free software; you can redistribute it and/or | ||
| + * modify it under the terms of the GNU General Public License | ||
| + * version 2 as published by the Free Software Foundation. | ||
| + * The full license is available at http://www.gnu.org/licenses/gpl.html | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + */ | ||
| +var games = { | ||
| + "advancewars":"Advance Wars", | ||
| + "advancewars2":"Advance Wars 2", | ||
| + "aladdin":"Aladdin", | ||
| + "alienhominid":"Alien Hominid", | ||
| + "bomberman_max2blue":"Bomberman Max 2 - Blue Advance", | ||
| + "bomberman_tournament":"Bomberman Tournament", | ||
| + "bubblebobble":"Bubble Bobble", | ||
| + "croket1":"Croket! - Yume no Banker Survival!", | ||
| + "croket2":"Croket! 2 - Yami no Bank to Banqueen", | ||
| + "croket3":"Croket! 3 - Granu Oukoku no Nazo", | ||
| + "croket4":"Croket! 4 - Bank no Mori no Mamorigami", | ||
| + "digimon_racing":"Digimon Racing", | ||
| + "dbz_supersonic":"Dragon Ball Z - Supersonic Warriors", | ||
| + "drilldozer":"Drill Dozer", | ||
| + "earthwormjim":"Earthworm Jim", | ||
| + "earthwormjim2":"Earthworm Jim 2", | ||
| + "ff1and2":"Final Fantasy 1 & 2 Advance", | ||
| + "ff4S":"Final Fantasy IV Advance (Sound Restoration Mod)", | ||
| + "ff6":"Final Fantasy VI Advance", | ||
| + "final_fantasy_tactics":"Final Fantasy Tactics Advance", | ||
| + "fire_emblem":"Fire Emblem", | ||
| + "frogger1":"Frogger Advance - The Great Quest", | ||
| + "frogger2":"Frogger's Adventures - Temple of the Frog", | ||
| + "frogger3":"Frogger's Adventures 2 - The Lost Wand", | ||
| + "fzero_gp":"F-Zero - GP Legend", | ||
| + "fzero_max":"F-Zero - Maximum Velocity", | ||
| + "gamewatch4":"Game & Watch Gallery 4", | ||
| + "goldensun":"Golden Sun", | ||
| + "gunstar_super_heroes":"Gunstar Super Heroes", | ||
| + "hamtaro_heartbreak":"Hamtaro - Ham-Ham Heartbreak", | ||
| + "kirbymirror":"Kirby & The Amazing Mirror", | ||
| + "kirbynightmare":"Kirby: Nightmare in Dreamland", | ||
| + "mariokart":"Mario Kart: Super Circuit", | ||
| + "marioparty":"Mario Party Advance", | ||
| + "mariopinball":"Mario Pinball Land", | ||
| + "megamanbass":"Megaman & Bass", | ||
| + "megaman_battle1":"Megaman Battle Network 1", | ||
| + "megaman_battle2":"Megaman Battle Network 2", | ||
| + "megaman_battle3_blue":"Megaman Battle Network 3 Blue", | ||
| + "megaman_battle4_blue":"Megaman Battle Network 4 Blue Moon", | ||
| + "megaman_battle4_red":"Megaman Battle Network 4 Red Sun", | ||
| + "megaman_battle5":"Megaman Battle Network 5 Team Protoman", | ||
| + "megaman_battle6":"Megaman Battle Network 6 Cybeast Falzar", | ||
| + "megaman_zero1":"Megaman Zero", | ||
| + "megaman_zero2":"Megaman Zero 2", | ||
| + "megaman_zero3":"Megaman Zero 3", | ||
| + "megaman_zero4":"Megaman Zero 4", | ||
| + "metalslug":"Metal Slug Advance", | ||
| + "metroid_fusion":"Metroid Fusion", | ||
| + "momotarou_dentetsu":"Momotarou Dentetsu G Gold Deck wo Tsukure!", | ||
| + "monopoly":"Monopoly", | ||
| + "monster_force":"Monster Force", | ||
| + "mortal_kombat":"Mortal Kombat Advance", | ||
| + "pacman_world":"Pacman World", | ||
| + "pacman_world2":"Pacman World 2", | ||
| + "pokemonflorasky":"Pokemon Flora Sky Rom Hack", | ||
| + "pokemonemerald":"Pokemon Emerald", | ||
| + "pokemongreen":"Pokemon Leaf Green", | ||
| + "mysteryred":"Pokemon Mystery Dungeon Red", | ||
| + "pokemonruby":"Pokemon Ruby", | ||
| + "pokemonsapphire":"Pokemon Sapphire", | ||
| + "pokemonred":"Pokemon Fire Red", | ||
| + "gba_video_pokemon_1":"Pokemon Video Pak 1", | ||
| + "gba_video_pokemon_2":"Pokemon Video Pak 2", | ||
| + "gba_video_pokemon_3":"Pokemon Video Pak 3", | ||
| + "gba_video_pokemon_4":"Pokemon Video Pak 4", | ||
| + "sonic_advance":"Sonic Advance", | ||
| + "sonic_advance2":"Sonic Advance 2", | ||
| + "sonic_advance3":"Sonic Advance 3", | ||
| + "sonicbattle":"Sonic Battle", | ||
| + "supermonkeyballjr":"Super Monkey Ball Jr", | ||
| + "superstar":"Mario & Luigi: Superstar Saga", | ||
| + "supermarioadvance":"Super Mario Advance", | ||
| + "supermarioadvance2":"Super Mario Advance 2", | ||
| + "supermarioadvance3":"Super Mario Advance 3", | ||
| + "supermarioadvance4":"Super Mario Advance 4", | ||
| + "simpsons":"The Simpsons: Road Rage", | ||
| + "sonicpinball":"Sonic Pinball", | ||
| + "super_street_fighter_2_turbo_revival":"Super Street Fighter II: Turbo Revival", | ||
| + "super_street_fighter_3_alpha":"Super Street Fighter III: Alpha", | ||
| + "tales_of_phantasia":"Tales of Phantasia", | ||
| + "tak2_staff_of_dreams":"Tak 2: The Staff of Dreams", | ||
| + "tetris_worlds":"Tetris Worlds", | ||
| + "tmnt":"Teenage Mutant Ninja Turtles", | ||
| + "sims_bustin_out":"The Sims: Bustin' Out", | ||
| + "sims2":"The Sims 2", | ||
| + "spyro_adventure":"Spyro Adventure", | ||
| + "spyro_ice":"Spyro: Season of Ice", | ||
| + "spyro_flame":"Spyro 2: Season of Flame", | ||
| + "turok_evolution":"Turok Evolution", | ||
| + "warioland4":"Wario Land 4", | ||
| + "wario_ware":"Wario Ware Inc", | ||
| + "zelda_past":"The Legend of Zelda: A Link to the Past", | ||
| + "zelda_minish":"The Legend of Zelda: The Minish Cap" | ||
| +}; | ||
| +var Iodine = null; | ||
| +var Blitter = null; | ||
| +var Mixer = null; | ||
| +var MixerInput = null; | ||
| +var timerID = null; | ||
| +window.onload = function () { | ||
| + if (!games[location.hash.substr(1)]) { | ||
| + alert("Invalid game request!"); | ||
| + return; | ||
| + } | ||
| + //Initialize Iodine: | ||
| + Iodine = new GameBoyAdvanceEmulator(); | ||
| + //Initialize the graphics: | ||
| + registerBlitterHandler(); | ||
| + //Initialize the audio: | ||
| + registerAudioHandler(); | ||
| + //Register the save handler callbacks: | ||
| + registerSaveHandlers(); | ||
| + //Hook the GUI controls. | ||
| + registerGUIEvents(); | ||
| + //Enable Sound: | ||
| + Iodine.enableAudio(); | ||
| + //Download the BIOS: | ||
| + downloadBIOS(); | ||
| +} | ||
| +function downloadBIOS() { | ||
| + downloadFile("Binaries/gba_bios.bin", registerBIOS); | ||
| +} | ||
| +function registerBIOS() { | ||
| + processDownload(this, attachBIOS); | ||
| + downloadROM(location.hash.substr(1)); | ||
| +} | ||
| +function downloadROM(gamename) { | ||
| + Iodine.pause(); | ||
| + showTempString("Downloading \"" + games[gamename] + ".\""); | ||
| + downloadFile("Binaries/" + gamename + ".gba", registerROM); | ||
| +} | ||
| +function registerROM() { | ||
| + clearTempString(); | ||
| + processDownload(this, attachROM); | ||
| + if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/iPad/i)) { | ||
| + Iodine.disableAudio(); | ||
| + } | ||
| + Iodine.play(); | ||
| +} | ||
| +function registerBlitterHandler() { | ||
| + Blitter = new GlueCodeGfx(); | ||
| + Blitter.attachCanvas(document.getElementById("emulator_target")); | ||
| + Blitter.setSmoothScaling(false); | ||
| + Iodine.attachGraphicsFrameHandler(function (buffer) {Blitter.copyBuffer(buffer);}); | ||
| +} | ||
| +function registerAudioHandler() { | ||
| + Mixer = new GlueCodeMixer(); | ||
| + MixerInput = new GlueCodeMixerInput(Mixer); | ||
| + Iodine.attachAudioHandler(MixerInput); | ||
| +} | ||
| +function registerGUIEvents() { | ||
| + addEvent("keydown", document, keyDown); | ||
| + addEvent("keyup", document, keyUpPreprocess); | ||
| + addEvent("unload", window, ExportSave); | ||
| + Iodine.attachSpeedHandler(function (speed) { | ||
| + document.title = games[location.hash.substr(1)] + " - " + speed; | ||
| + }); | ||
| +} | ||
| +function lowerVolume() { | ||
| + Iodine.incrementVolume(-0.04); | ||
| +} | ||
| +function raiseVolume() { | ||
| + Iodine.incrementVolume(0.04); | ||
| +} | ||
| +function writeRedTemporaryText(textString) { | ||
| + if (timerID) { | ||
| + clearTimeout(timerID); | ||
| + } | ||
| + showTempString(textString); | ||
| + timerID = setTimeout(clearTempString, 5000); | ||
| +} | ||
| +function showTempString(textString) { | ||
| + document.getElementById("tempMessage").style.display = "block"; | ||
| + document.getElementById("tempMessage").textContent = textString; | ||
| +} | ||
| +function clearTempString() { | ||
| + document.getElementById("tempMessage").style.display = "none"; | ||
| +} | ||
| +//Some wrappers and extensions for non-DOM3 browsers: | ||
| +function addEvent(sEvent, oElement, fListener) { | ||
| + try { | ||
| + oElement.addEventListener(sEvent, fListener, false); | ||
| + } | ||
| + catch (error) { | ||
| + oElement.attachEvent("on" + sEvent, fListener); //Pity for IE. | ||
| + } | ||
| +} | ||
| +function removeEvent(sEvent, oElement, fListener) { | ||
| + try { | ||
| + oElement.removeEventListener(sEvent, fListener, false); | ||
| + } | ||
| + catch (error) { | ||
| + oElement.detachEvent("on" + sEvent, fListener); //Pity for IE. | ||
| + } | ||
| +} |
| @@ -0,0 +1,193 @@ | ||
| +"use strict"; | ||
| +/* | ||
| + Copyright (C) 2012-2014 Grant Galitz | ||
| + | ||
| + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| + | ||
| + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| + | ||
| + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| + */ | ||
| +function GlueCodeGfx() { | ||
| + this.didRAF = false; //Set when rAF has been used. | ||
| + this.graphicsFound = 0; //Do we have graphics output sink found yet? | ||
| + this.offscreenWidth = 240; //Width of the GBA screen. | ||
| + this.offscreenHeight = 160; //Height of the GBA screen. | ||
| + this.doSmoothing = true; | ||
| + //Cache some frame buffer lengths: | ||
| + var offscreenRGBCount = this.offscreenWidth * this.offscreenHeight * 3; | ||
| + this.swizzledFrameFree = [getUint8Array(offscreenRGBCount), getUint8Array(offscreenRGBCount)]; | ||
| + this.swizzledFrameReady = []; | ||
| + this.initializeGraphicsBuffer(); //Pre-set the swizzled buffer for first frame. | ||
| +} | ||
| +GlueCodeGfx.prototype.attachCanvas = function (canvas) { | ||
| + this.canvas = canvas; | ||
| + this.graphicsFound = this.initializeCanvasTarget(); | ||
| + this.setSmoothScaling(this.doSmoothing); | ||
| +} | ||
| +GlueCodeGfx.prototype.detachCanvas = function () { | ||
| + this.canvas = null; | ||
| +} | ||
| +GlueCodeGfx.prototype.recomputeDimension = function () { | ||
| + //Cache some dimension info: | ||
| + this.canvasLastWidth = this.canvas.clientWidth; | ||
| + this.canvasLastHeight = this.canvas.clientHeight; | ||
| + if (window.mozRequestAnimationFrame) { //Sniff out firefox for selecting this path. | ||
| + //Set target as unscaled: | ||
| + this.onscreenWidth = this.canvas.width = this.offscreenWidth; | ||
| + this.onscreenHeight = this.canvas.height = this.offscreenHeight; | ||
| + } | ||
| + else { | ||
| + //Set target canvas as scaled: | ||
| + this.onscreenWidth = this.canvas.width = this.canvas.clientWidth; | ||
| + this.onscreenHeight = this.canvas.height = this.canvas.clientHeight; | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.initializeCanvasTarget = function () { | ||
| + try { | ||
| + //Obtain dimensional information: | ||
| + this.recomputeDimension(); | ||
| + //Get handles on the canvases: | ||
| + this.canvasOffscreen = document.createElement("canvas"); | ||
| + this.canvasOffscreen.width = this.offscreenWidth; | ||
| + this.canvasOffscreen.height = this.offscreenHeight; | ||
| + this.drawContextOffscreen = this.canvasOffscreen.getContext("2d"); | ||
| + this.drawContextOnscreen = this.canvas.getContext("2d"); | ||
| + //Get a CanvasPixelArray buffer: | ||
| + this.canvasBuffer = this.getBuffer(this.drawContextOffscreen, this.offscreenWidth, this.offscreenHeight); | ||
| + //Initialize Alpha Channel: | ||
| + this.initializeAlpha(this.canvasBuffer.data); | ||
| + //Draw swizzled buffer out as a test: | ||
| + this.requestDraw(); | ||
| + this.checkRAF(); | ||
| + //Success: | ||
| + return true; | ||
| + } | ||
| + catch (error) { | ||
| + //Failure: | ||
| + return false; | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.setSmoothScaling = function (doSmoothing) { | ||
| + this.doSmoothing = doSmoothing; | ||
| + if (this.graphicsFound) { | ||
| + this.canvas.setAttribute("style", (this.canvas.getAttribute("style") || "") + "; image-rendering: " + ((doSmoothing) ? "auto" : "-webkit-optimize-contrast") + ";" + | ||
| + "image-rendering: " + ((doSmoothing) ? "optimizeQuality" : "-o-crisp-edges") + ";" + | ||
| + "image-rendering: " + ((doSmoothing) ? "optimizeQuality" : "-moz-crisp-edges") + ";" + | ||
| + "-ms-interpolation-mode: " + ((doSmoothing) ? "bicubic" : "nearest-neighbor") + ";"); | ||
| + this.drawContextOnscreen.mozImageSmoothingEnabled = doSmoothing; | ||
| + this.drawContextOnscreen.webkitImageSmoothingEnabled = doSmoothing; | ||
| + this.drawContextOnscreen.imageSmoothingEnabled = doSmoothing; | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.initializeAlpha = function (canvasData) { | ||
| + var length = canvasData.length; | ||
| + for (var indexGFXIterate = 3; indexGFXIterate < length; indexGFXIterate += 4) { | ||
| + canvasData[indexGFXIterate] = 0xFF; | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.getBuffer = function (canvasContext, width, height) { | ||
| + //Get a CanvasPixelArray buffer: | ||
| + var buffer = null; | ||
| + try { | ||
| + buffer = this.drawContextOffscreen.createImageData(width, height); | ||
| + } | ||
| + catch (error) { | ||
| + buffer = this.drawContextOffscreen.getImageData(0, 0, width, height); | ||
| + } | ||
| + return buffer; | ||
| +} | ||
| +GlueCodeGfx.prototype.copyBuffer = function (buffer) { | ||
| + if (this.graphicsFound) { | ||
| + if (this.swizzledFrameFree.length == 0) { | ||
| + if (this.didRAF) { | ||
| + this.requestDrawSingle(); | ||
| + } | ||
| + else { | ||
| + this.swizzledFrameFree.push(this.swizzledFrameReady.shift()); | ||
| + } | ||
| + } | ||
| + var swizzledFrame = this.swizzledFrameFree.shift(); | ||
| + var length = swizzledFrame.length; | ||
| + if (buffer.buffer) { | ||
| + swizzledFrame.set(buffer); | ||
| + } | ||
| + else { | ||
| + for (var bufferIndex = 0; bufferIndex < length; ++bufferIndex) { | ||
| + swizzledFrame[bufferIndex] = buffer[bufferIndex]; | ||
| + } | ||
| + } | ||
| + this.swizzledFrameReady.push(swizzledFrame); | ||
| + if (!window.requestAnimationFrame) { | ||
| + this.requestDraw(); | ||
| + } | ||
| + else if (!this.didRAF) { | ||
| + //Prime RAF draw: | ||
| + var parentObj = this; | ||
| + window.requestAnimationFrame(function () { | ||
| + if (parentObj.canvas) { | ||
| + parentObj.requestRAFDraw(); | ||
| + } | ||
| + }); | ||
| + } | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.requestRAFDraw = function () { | ||
| + this.didRAF = true; | ||
| + this.requestDraw(); | ||
| +} | ||
| +GlueCodeGfx.prototype.requestDrawSingle = function () { | ||
| + if (this.swizzledFrameReady.length > 0) { | ||
| + var canvasData = this.canvasBuffer.data; | ||
| + var bufferIndex = 0; | ||
| + var swizzledFrame = this.swizzledFrameReady.shift(); | ||
| + var length = canvasData.length; | ||
| + for (var canvasIndex = 0; canvasIndex < length; ++canvasIndex) { | ||
| + canvasData[canvasIndex++] = swizzledFrame[bufferIndex++]; | ||
| + canvasData[canvasIndex++] = swizzledFrame[bufferIndex++]; | ||
| + canvasData[canvasIndex++] = swizzledFrame[bufferIndex++]; | ||
| + } | ||
| + this.swizzledFrameFree.push(swizzledFrame); | ||
| + this.graphicsBlit(); | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.requestDraw = function () { | ||
| + this.requestDrawSingle(); | ||
| + if (this.didRAF) { | ||
| + var parentObj = this; | ||
| + window.requestAnimationFrame(function () { | ||
| + if (parentObj.canvas) { | ||
| + parentObj.requestDraw(); | ||
| + } | ||
| + }); | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.graphicsBlit = function () { | ||
| + if (this.canvasLastWidth != this.canvas.clientWidth || this.canvasLastHeight != this.canvas.clientHeight) { | ||
| + this.recomputeDimension(); | ||
| + this.setSmoothScaling(this.doSmoothing); | ||
| + } | ||
| + if (this.offscreenWidth == this.onscreenWidth && this.offscreenHeight == this.onscreenHeight) { | ||
| + //Canvas does not need to scale, draw directly to final: | ||
| + this.drawContextOnscreen.putImageData(this.canvasBuffer, 0, 0); | ||
| + } | ||
| + else { | ||
| + //Canvas needs to scale, draw to offscreen first: | ||
| + this.drawContextOffscreen.putImageData(this.canvasBuffer, 0, 0); | ||
| + //Scale offscreen canvas image onto the final: | ||
| + this.drawContextOnscreen.drawImage(this.canvasOffscreen, 0, 0, this.onscreenWidth, this.onscreenHeight); | ||
| + } | ||
| +} | ||
| +GlueCodeGfx.prototype.initializeGraphicsBuffer = function () { | ||
| + //Initialize the first frame to a white screen: | ||
| + var swizzledFrame = this.swizzledFrameFree.shift(); | ||
| + var length = swizzledFrame.length; | ||
| + for (var bufferIndex = 0; bufferIndex < length; ++bufferIndex) { | ||
| + swizzledFrame[bufferIndex] = 0xF8; | ||
| + } | ||
| + this.swizzledFrameReady.push(swizzledFrame); | ||
| +} | ||
| +GlueCodeGfx.prototype.checkRAF = function () { | ||
| + window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || | ||
| + window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; | ||
| +} |
| @@ -0,0 +1,80 @@ | ||
| +"use strict"; | ||
| +/* | ||
| + Copyright (C) 2012-2015 Grant Galitz | ||
| + | ||
| + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| + | ||
| + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| + | ||
| + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| + */ | ||
| +var keyZones = [ | ||
| + //Use this to control the key mapping: | ||
| + //A: | ||
| + [88, 74], | ||
| + //B: | ||
| + [90, 81, 89], | ||
| + //Select: | ||
| + [16], | ||
| + //Start: | ||
| + [13], | ||
| + //Right: | ||
| + [39], | ||
| + //Left: | ||
| + [37], | ||
| + //Up: | ||
| + [38], | ||
| + //Down: | ||
| + [40], | ||
| + //R: | ||
| + [50], | ||
| + //L: | ||
| + [49] | ||
| +]; | ||
| +function keyDown(e) { | ||
| + var keyCode = e.keyCode | 0; | ||
| + for (var keyMapIndex = 0; (keyMapIndex | 0) < 10; keyMapIndex = ((keyMapIndex | 0) + 1) | 0) { | ||
| + var keysMapped = keyZones[keyMapIndex | 0]; | ||
| + var keysTotal = keysMapped.length | 0; | ||
| + for (var matchingIndex = 0; (matchingIndex | 0) < (keysTotal | 0); matchingIndex = ((matchingIndex | 0) + 1) | 0) { | ||
| + if ((keysMapped[matchingIndex | 0] | 0) == (keyCode | 0)) { | ||
| + Iodine.keyDown(keyMapIndex | 0); | ||
| + if (e.preventDefault) { | ||
| + e.preventDefault(); | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| +} | ||
| +function keyUp(keyCode) { | ||
| + keyCode = keyCode | 0; | ||
| + for (var keyMapIndex = 0; (keyMapIndex | 0) < 10; keyMapIndex = ((keyMapIndex | 0) + 1) | 0) { | ||
| + var keysMapped = keyZones[keyMapIndex | 0]; | ||
| + var keysTotal = keysMapped.length | 0; | ||
| + for (var matchingIndex = 0; (matchingIndex | 0) < (keysTotal | 0); matchingIndex = ((matchingIndex | 0) + 1) | 0) { | ||
| + if ((keysMapped[matchingIndex | 0] | 0) == (keyCode | 0)) { | ||
| + Iodine.keyUp(keyMapIndex | 0); | ||
| + } | ||
| + } | ||
| + } | ||
| +} | ||
| +function keyUpPreprocess(e) { | ||
| + var keyCode = e.keyCode | 0; | ||
| + switch (keyCode | 0) { | ||
| + case 68: | ||
| + lowerVolume(); | ||
| + break; | ||
| + case 82: | ||
| + raiseVolume(); | ||
| + break; | ||
| + case 51: | ||
| + Iodine.incrementSpeed(0.10); | ||
| + break; | ||
| + case 52: | ||
| + Iodine.incrementSpeed(-0.10); | ||
| + break; | ||
| + default: | ||
| + //Control keys / other | ||
| + keyUp(keyCode); | ||
| + } | ||
| +} |
| @@ -0,0 +1,81 @@ | ||
| +"use strict"; | ||
| +/* | ||
| + Copyright (C) 2012-2013 Grant Galitz | ||
| + | ||
| + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| + | ||
| + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| + | ||
| + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| + */ | ||
| +function attachBIOS(BIOS) { | ||
| + try { | ||
| + Iodine.attachBIOS(new Uint8Array(BIOS)); | ||
| + } | ||
| + catch (error) { | ||
| + Iodine.attachBIOS(BIOS); | ||
| + } | ||
| +} | ||
| +function attachROM(ROM) { | ||
| + try { | ||
| + Iodine.attachROM(new Uint8Array(ROM)); | ||
| + } | ||
| + catch (error) { | ||
| + Iodine.attachROM(ROM); | ||
| + } | ||
| +} | ||
| +function fileLoadShimCode(files, ROMHandler) { | ||
| + if (typeof files != "undefined") { | ||
| + if (files.length >= 1) { | ||
| + //Gecko 1.9.2+ (Standard Method) | ||
| + try { | ||
| + var binaryHandle = new FileReader(); | ||
| + binaryHandle.onloadend = function () { | ||
| + ROMHandler(this.result); | ||
| + } | ||
| + binaryHandle.readAsArrayBuffer(files[files.length - 1]); | ||
| + } | ||
| + catch (error) { | ||
| + try { | ||
| + var result = files[files.length - 1].getAsBinary(); | ||
| + var resultConverted = []; | ||
| + for (var index = 0; index < result.length; ++index) { | ||
| + resultConverted[index] = result.charCodeAt(index) & 0xFF; | ||
| + } | ||
| + ROMHandler(resultConverted); | ||
| + } | ||
| + catch (error) { | ||
| + alert("Could not load the processed ROM file!"); | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| +} | ||
| +function fileLoadBIOS() { | ||
| + fileLoadShimCode(this.files, attachBIOS); | ||
| +} | ||
| +function fileLoadROM() { | ||
| + fileLoadShimCode(this.files, attachROM); | ||
| +} | ||
| +function downloadFile(fileName, registrationHandler) { | ||
| + var ajax = new XMLHttpRequest(); | ||
| + ajax.onload = registrationHandler; | ||
| + ajax.open("GET", "./" + fileName, true); | ||
| + ajax.responseType = "arraybuffer"; | ||
| + ajax.overrideMimeType("text/plain; charset=x-user-defined"); | ||
| + ajax.send(null); | ||
| +} | ||
| +function processDownload(parentObj, attachHandler) { | ||
| + try { | ||
| + attachHandler(new Uint8Array(parentObj.response)); | ||
| + } | ||
| + catch (error) { | ||
| + var data = parentObj.responseText; | ||
| + var length = data.length; | ||
| + var dataArray = []; | ||
| + for (var index = 0; index < length; index++) { | ||
| + dataArray[index] = data.charCodeAt(index) & 0xFF; | ||
| + } | ||
| + attachHandler(dataArray); | ||
| + } | ||
| +} |
| @@ -0,0 +1,75 @@ | ||
| +"use strict"; | ||
| +/* | ||
| + Copyright (C) 2012-2013 Grant Galitz | ||
| + | ||
| + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| + | ||
| + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| + | ||
| + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| + */ | ||
| +function ImportSaveCallback(name) { | ||
| + try { | ||
| + var save = findValue("SAVE_" + name); | ||
| + if (save != null) { | ||
| + writeRedTemporaryText("Loaded save."); | ||
| + return base64ToArray(save); | ||
| + } | ||
| + } | ||
| + catch (error) { | ||
| + alert("Could not read save: " + error.message); | ||
| + } | ||
| + return null; | ||
| +} | ||
| +function ExportSave() { | ||
| + Iodine.exportSave(); | ||
| +} | ||
| +function ExportSaveCallback(name, save) { | ||
| + if (name != "") { | ||
| + try { | ||
| + setValue("SAVE_" + name, arrayToBase64(save)); | ||
| + } | ||
| + catch (error) { | ||
| + alert("Could not store save: " + error.message); | ||
| + } | ||
| + } | ||
| +} | ||
| +function registerSaveHandlers() { | ||
| + Iodine.attachSaveExportHandler(ExportSaveCallback); | ||
| + Iodine.attachSaveImportHandler(ImportSaveCallback); | ||
| +} | ||
| +//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); | ||
| + } | ||
| +} |
| @@ -0,0 +1,87 @@ | ||
| +"use strict"; | ||
| +var toBase64 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", | ||
| + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", | ||
| + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+" , "/", "="]; | ||
| +var fromBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; | ||
| +function base64(data) { | ||
| + try { | ||
| + var base64 = window.btoa(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below. | ||
| + } | ||
| + catch (error) { | ||
| + //Defaulting to non-native base64 encoding... | ||
| + var base64 = ""; | ||
| + var dataLength = data.length; | ||
| + if (dataLength > 0) { | ||
| + var bytes = [0, 0, 0]; | ||
| + var index = 0; | ||
| + var remainder = dataLength % 3; | ||
| + while (data.length % 3 > 0) { | ||
| + //Make sure we don't do fuzzy math in the next loop... | ||
| + data[data.length] = " "; | ||
| + } | ||
| + while (index < dataLength) { | ||
| + //Keep this loop small for speed. | ||
| + bytes = [data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF]; | ||
| + base64 += toBase64[bytes[0] >> 2] + toBase64[((bytes[0] & 0x3) << 4) | (bytes[1] >> 4)] + toBase64[((bytes[1] & 0xF) << 2) | (bytes[2] >> 6)] + toBase64[bytes[2] & 0x3F]; | ||
| + } | ||
| + if (remainder > 0) { | ||
| + //Fill in the padding and recalulate the trailing six-bit group... | ||
| + base64[base64.length - 1] = "="; | ||
| + if (remainder == 2) { | ||
| + base64[base64.length - 2] = "="; | ||
| + base64[base64.length - 3] = toBase64[(bytes[0] & 0x3) << 4]; | ||
| + } | ||
| + else { | ||
| + base64[base64.length - 2] = toBase64[(bytes[1] & 0xF) << 2]; | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| + return base64; | ||
| +} | ||
| +function base64_decode(data) { | ||
| + try { | ||
| + var decode64 = window.atob(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below. | ||
| + } | ||
| + catch (error) { | ||
| + //Defaulting to non-native base64 decoding... | ||
| + var decode64 = ""; | ||
| + var dataLength = data.length; | ||
| + if (dataLength > 3 && dataLength % 4 == 0) { | ||
| + var sixbits = [0, 0, 0, 0]; //Declare this out of the loop, to speed up the ops. | ||
| + var index = 0; | ||
| + while (index < dataLength) { | ||
| + //Keep this loop small for speed. | ||
| + sixbits = [fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++))]; | ||
| + decode64 += String.fromCharCode((sixbits[0] << 2) | (sixbits[1] >> 4)) + String.fromCharCode(((sixbits[1] & 0x0F) << 4) | (sixbits[2] >> 2)) + String.fromCharCode(((sixbits[2] & 0x03) << 6) | sixbits[3]); | ||
| + } | ||
| + //Check for the '=' character after the loop, so we don't hose it up. | ||
| + if (sixbits[3] >= 0x40) { | ||
| + decode64.length -= 1; | ||
| + if (sixbits[2] >= 0x40) { | ||
| + decode64.length -= 1; | ||
| + } | ||
| + } | ||
| + } | ||
| + } | ||
| + return decode64; | ||
| +} | ||
| +function arrayToBase64(arrayIn) { | ||
| + var binString = ""; | ||
| + var length = arrayIn.length; | ||
| + for (var index = 0; index < length; ++index) { | ||
| + if (typeof arrayIn[index] == "number") { | ||
| + binString += String.fromCharCode(arrayIn[index]); | ||
| + } | ||
| + } | ||
| + return base64(binString); | ||
| +} | ||
| +function base64ToArray(b64String) { | ||
| + var binString = base64_decode(b64String); | ||
| + var outArray = []; | ||
| + var length = binString.length; | ||
| + for (var index = 0; index < length;) { | ||
| + outArray.push(binString.charCodeAt(index++) & 0xFF); | ||
| + } | ||
| + return outArray; | ||
| +} |