[
  {
    "path": ".gitignore",
    "content": "node_modules\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Julien Chichignoud\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "Gameboy.js\n==========\n\nGameboy.js is a Gameboy emulator written in JavaScript.\n\nIt's a work in progress, see the Features section.\n\n## Browser support\n\nTested on:\n* Firefox\n* Chrome\n* Safari\n\n## Usage\n\n### Run now\n\nYou can try the emulator directly on the [demo page](http://juchi.github.io/gameboy.js/).\n\nMany tests ROM can be downloaded [here](https://github.com/c-sp/game-boy-test-roms) and are runnable, but some tests fail.\nSee the Tests section for more details.\n\nCurrently, most tested games run (tested with Tetris, Super Mario, Pokémon Red)\nbut have some glitches.\n\n### Run on a custom page\n\nYou can directly use the distributed compiled file in a custom HTML page of your own,\nand create a new Gameboy object. It will expect a Canvas element and an optional options object.\n\n```javascript\nvar canvas = document.getElementById('canvas');\nnew GameboyJS.Gameboy(canvas);\n```\n\n### Options\n\nYou can customize the configuration by passing a list of options to the Gameboy.\n\n```javascript\nvar options, canvas;\n//...\nnew GameboyJS.Gameboy(canvas, options);\n```\n\n* `pad`: Object representing the pad to use as a physical gamepad. The `class` key is mandatory and\n  should contain the class implementing the device you want to play with.\n  You can implement any kind of pad as long as this class implements the init() method.\n  See GameboyJS.Keyboard class for an example of implementation. You may also provide\n  a `mapping` object that will be used if you choose the GameboyJS.Gamepad class.\n  Default is `{class: GameboyJS.Keyboard, mapping: null}`\n* `zoom`: The zoom level as an integer. Default is 1\n* `romReaders`: An array of ROM reader objects that can read a ROM file\n  and send the data to the Gameboy.\n  Default is empty (`[]`), leading to a GameboyJS.RomFileReader to be created.\n* `statusContainerId`: ID of the HTML element for status display. Default is 'status'.\n* `gameNameContainerId`: ID of the HTML element for game name display. Default is 'game-name'.\n* `errorContainerId`: ID of the HTML element for error display. Default is 'error'.\n\n### Build from source\n\nIf you want to build the compiled JavaScript file from source\nto be sure you have the latest updates, just clone the repository\nand run the build script:\n\n```\ngit clone https://github.com/juchi/gameboy.js\nnpm install\nnpm run build\n```\n\n## Features\n\n### Devices\n\nThe LCD screen is working but still has some sprite glitches.\n\nUser input is available : arrow keys are mapped to the keyboard arrows,\nand A, B, START and SELECT are mapped to G, B, H, N respectively.\nAlso, it's possible to use a gamepad using a custom `pad` option.\n\nGame saves are working fine and are stored as serialized data in the LocalStorage.\n\nSound is partially implemented (the noise channel is not done yet) and is quite\ngood on Firefox (latest release) but really bad on Chrome and Safari.\nThis seems to be due to the implementation of the Web Audio API in webkit.\n\nThe serial port can be used by the program as an output,\nthe received bytes are displayed in the console (this is mainly used for tests).\n\nThe ROM files are accessed using an explorer on your computer.\nOther methods may be included (AJAX and Drag & Drop are supported).\n\n### Internal processes\n\nThere is no boot program provided (nor supported).\nThe execution starts automatically at address 0x0100 which is the start address of all ROMs.\n\nAll the standard Gameboy instructions are implemented. Super Gameboy and Gameboy Color are not supported.\n\nThe following features are in progress or partially working:\n* sprites (some glitches)\n* MBC (only MBC 1, MBC 3 and MBC 5 are partially supported)\n* sound (ok on Firefox, poor on Chrome)\n\nThe following features are not currently supported at all:\n* boot program\n\n## Tests\n\nThe tests perform as follow :\n\n| Test               |  status  |\n|--------------------|:--------:|\n| CPU instructions   | pass     |\n| instruction timing | pass     |\n| memory timing      | fail     |\n| DMG sound          | fail     |\n| OAM bug            | fail     |\n"
  },
  {
    "path": "dist/gameboy.js",
    "content": "function loadboot(p) {\n    var boot = [\n        0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E,\n        0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0,\n        0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B,\n        0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9,\n        0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20,\n        0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04,\n        0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2,\n        0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06,\n        0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xE2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20,\n        0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17,\n        0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,\n        0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,\n        0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,\n        0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C,\n        0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x00, 0x00, 0x23, 0x7D, 0xFE, 0x34, 0x20,\n        0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x00, 0x00, 0x3E, 0x01, 0xE0, 0x50\n    ];\n\n    for (var i in boot) {\n        p.memory[i] = boot[i];\n    }\n    p.r.pc = 0;\n    p.usingBootRom = true;\n}\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// CPU class\nvar CPU = function(gameboy) {\n    this.gameboy = gameboy;\n\n    this.r = {A:0, F: 0, B:0, C:0, D:0, E:0, H:0, L:0, pc:0, sp:0};\n    this.IME = true;\n    this.clock = {c: 0, serial: 0};\n    this.isHalted = false;\n    this.isPaused = false;\n    this.usingBootRom = false;\n\n    this.createDevices();\n};\n\nCPU.INTERRUPTS = {\n    VBLANK: 0,\n    LCDC:   1,\n    TIMER:  2,\n    SERIAL: 3,\n    HILO:   4\n};\nCPU.interruptRoutines = {\n    0: function(p){GameboyJS.cpuOps.RSTn(p, 0x40);},\n    1: function(p){GameboyJS.cpuOps.RSTn(p, 0x48);},\n    2: function(p){GameboyJS.cpuOps.RSTn(p, 0x50);},\n    3: function(p){GameboyJS.cpuOps.RSTn(p, 0x58);},\n    4: function(p){GameboyJS.cpuOps.RSTn(p, 0x60);}\n};\n\nCPU.prototype.createDevices = function() {\n    this.memory = new GameboyJS.Memory(this);\n    this.timer = new GameboyJS.Timer(this, this.memory);\n    this.apu = new GameboyJS.APU(this.memory);\n\n    this.SERIAL_INTERNAL_INSTR = 512; // instr to wait per bit if internal clock\n    this.enableSerial = 0;\n    this.serialHandler = GameboyJS.ConsoleSerial;\n};\n\nCPU.prototype.reset = function() {\n    this.memory.reset();\n\n    this.r.sp = 0xFFFE;\n};\n\nCPU.prototype.loadRom = function(data) {\n    this.memory.setRomData(data);\n};\n\nCPU.prototype.getRamSize = function() {\n    var size = 0;\n    switch (this.memory.rb(0x149)) {\n        case 1:\n            size = 2048;\n            break;\n        case 2:\n            size = 2048 * 4;\n            break;\n        case 3:\n            size = 2048 * 16;\n            break;\n    }\n\n    return size;\n};\n\nCPU.prototype.getGameName = function() {\n    var name = '';\n    for (var i = 0x134; i < 0x143; i++) {\n        var char = this.memory.rb(i) || 32;\n        name += String.fromCharCode(char);\n    }\n\n    return name;\n};\n\n// Start the execution of the emulator\nCPU.prototype.run = function() {\n    if (this.usingBootRom) {\n        this.r.pc = 0x0000;\n    } else {\n        this.r.pc = 0x0100;\n    }\n    this.frame();\n};\n\nCPU.prototype.stop = function() {\n    clearTimeout(this.nextFrameTimer);\n};\n\n// Fetch-and-execute loop\n// Will execute instructions for the duration of a frame\n//\n// The screen unit will notify the vblank period which\n// is considered the end of a frame\n//\n// The function is called on a regular basis with a timeout\nCPU.prototype.frame = function() {\n    if (!this.isPaused) {\n        this.nextFrameTimer = setTimeout(this.frame.bind(this), 1000 / GameboyJS.Screen.physics.FREQUENCY);\n    }\n\n    try {\n        var vblank = false;\n        while (!vblank) {\n            var oldInstrCount = this.clock.c;\n            if (!this.isHalted) {\n                var opcode = this.fetchOpcode();\n                GameboyJS.opcodeMap[opcode](this);\n                this.r.F &= 0xF0; // tmp fix\n\n                if (this.enableSerial) {\n                    var instr = this.clock.c - oldInstrCount;\n                    this.clock.serial += instr;\n                    if (this.clock.serial >= 8 * this.SERIAL_INTERNAL_INSTR) {\n                        this.endSerialTransfer();\n                    }\n                }\n            } else {\n                this.clock.c += 4;\n            }\n\n            var elapsed = this.clock.c - oldInstrCount;\n            vblank = this.gpu.update(elapsed);\n            this.timer.update(elapsed);\n            this.input.update();\n            this.apu.update(elapsed);\n            this.checkInterrupt();\n        }\n        this.clock.c = 0;\n    } catch (e) {\n        this.gameboy.handleException(e);\n    }\n};\n\nCPU.prototype.fetchOpcode = function() {\n    var opcode = this.memory.rb(this.r.pc++);\n    if (opcode === undefined) {console.log(opcode + ' at ' + (this.r.pc-1).toString(16));this.stop();return;}\n    if (!GameboyJS.opcodeMap[opcode]) {\n        console.error('Unknown opcode '+opcode.toString(16)+' at address '+(this.r.pc-1).toString(16)+', stopping execution...');\n        this.stop();\n        return null;\n    }\n\n    return opcode;\n};\n\n// read register\nCPU.prototype.rr = function(register) {\n    return this.r[register];\n};\n\n// write register\nCPU.prototype.wr = function(register, value) {\n    this.r[register] = value;\n};\n\nCPU.prototype.halt = function() {\n    this.isHalted = true;\n};\nCPU.prototype.unhalt = function() {\n    this.isHalted = false;\n};\nCPU.prototype.pause = function() {\n    this.isPaused = true;\n};\nCPU.prototype.unpause = function() {\n    if (this.isPaused) {\n        this.isPaused = false;\n        this.frame();\n    }\n};\n\n// Look for interrupt flags\nCPU.prototype.checkInterrupt = function() {\n    if (!this.IME) {\n        return;\n    }\n    for (var i = 0; i < 5; i++) {\n        var IFval = this.memory.rb(0xFF0F);\n        if (GameboyJS.Util.readBit(IFval, i) && this.isInterruptEnable(i)) {\n            IFval &= (0xFF - (1<<i));\n            this.memory.wb(0xFF0F, IFval);\n            this.disableInterrupts();\n            this.clock.c += 4; // 20 clocks to serve interrupt, with 16 for RSTn\n            CPU.interruptRoutines[i](this);\n            break;\n        }\n    }\n};\n\n// Set an interrupt flag\nCPU.prototype.requestInterrupt = function(type) {\n    var IFval = this.memory.rb(0xFF0F);\n    IFval |= (1 << type)\n    this.memory.wb(0xFF0F, IFval) ;\n    this.unhalt();\n};\n\nCPU.prototype.isInterruptEnable = function(type) {\n    return GameboyJS.Util.readBit(this.memory.rb(0xFFFF), type) != 0;\n};\n\nCPU.prototype.enableInterrupts = function() {\n    this.IME = true;\n};\nCPU.prototype.disableInterrupts = function() {\n    this.IME = false;\n};\n\nCPU.prototype.enableSerialTransfer = function() {\n    this.enableSerial = 1;\n    this.clock.serial = 0;\n};\n\nCPU.prototype.endSerialTransfer = function() {\n    this.enableSerial = 0;\n    var data = this.memory.rb(0xFF01);\n    this.memory.wb(0xFF02, 0);\n    this.serialHandler.out(data);\n    this.memory.wb(0xFF01, this.serialHandler.in());\n};\n\nCPU.prototype.resetDivTimer = function() {\n    this.timer.resetDiv();\n};\nGameboyJS.CPU = CPU;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\nvar Debug = {};\n// Output a range of 16 memory addresses\nDebug.view_memory = function(addr, gameboy) {\n    var memory = gameboy.cpu.memory;\n    addr = addr & 0xFFF0;\n    var pad = '00';\n    var str = addr.toString(16) + ':';\n    for (var i = addr; i < addr + 0x10; i++) {\n        if ((i & 0x1) == 0) {\n            str += ' ';\n        }\n        var val = memory[i] || 0;\n\n        val = val.toString(16);\n        str += pad.substring(val.length) + val;\n    }\n\n    return str;\n};\n\nDebug.view_tile = function(gameboy, index, dataStart) {\n    var memory = gameboy.cpu.memory;\n    var screen = gameboy.screen;\n    var LCDC = screen.deviceram(screen.LCDC);\n    if (typeof dataStart === 'undefined') {\n        dataStart = 0x8000;\n        if (!GameboyJS.Util.readBit(LCDC, 4)) {\n            dataStart = 0x8800;\n            index = GameboyJS.cpuOps._getSignedValue(index) + 128;\n        }\n    }\n\n    var tileData = screen.readTileData(index, dataStart);\n\n    var pixelData = new Array(8 * 8)\n    for (var line = 0; line < 8; line++) {\n        var b1 = tileData.shift();\n        var b2 = tileData.shift();\n\n        for (var pixel = 0; pixel < 8; pixel++) {\n            var mask = (1 << (7-pixel));\n            var colorValue = ((b1 & mask) >> (7-pixel)) + ((b2 & mask) >> (7-pixel))*2;\n            pixelData[line * 8 + pixel] = colorValue;\n        }\n    }\n\n    var i = 0;\n    while (pixelData.length) {\n        console.log(i++ + ' ' + pixelData.splice(0, 8).join(''));\n    }\n};\n\nDebug.list_visible_sprites = function(gameboy) {\n    var memory = gameboy.cpu.memory;\n    var indexes = new Array();\n    for (var i = 0xFE00; i < 0xFE9F; i += 4) {\n        var x = memory.oamram(i + 1);\n        var y = memory.oamram(i);\n        var tileIndex = memory.oamram(i + 2);\n        if (x == 0 || x >= 168) {\n            continue;\n        }\n        indexes.push({oamIndex:i, x:x, y:y, tileIndex:tileIndex});\n    }\n\n    return indexes;\n};\nGameboyJS.Debug = Debug;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\nvar Screen;\nvar GPU = function(screen, cpu) {\n    this.cpu = cpu;\n    this.screen = screen;\n\n    this.LCDC= 0xFF40;\n    this.STAT= 0xFF41;\n    this.SCY = 0xFF42;\n    this.SCX = 0xFF43;\n    this.LY  = 0xFF44;\n    this.LYC = 0xFF45;\n    this.BGP = 0xFF47;\n    this.OBP0= 0xFF48;\n    this.OBP1= 0xFF49;\n    this.WY  = 0xFF4A;\n    this.WX  = 0xFF4B;\n\n    this.vram = cpu.memory.vram.bind(cpu.memory);\n\n    this.OAM_START = 0xFE00;\n    this.OAM_END   = 0xFE9F;\n    this.deviceram = cpu.memory.deviceram.bind(cpu.memory);\n    this.oamram = cpu.memory.oamram.bind(cpu.memory);\n    this.VBLANK_TIME = 70224;\n    this.clock = 0;\n    this.mode = 2;\n    this.line = 0;\n\n    Screen = GameboyJS.Screen;\n    this.buffer = new Array(Screen.physics.WIDTH * Screen.physics.HEIGHT);\n    this.tileBuffer = new Array(8);\n    this.bgTileCache = {};\n};\n\nGPU.tilemap = {\n    HEIGHT: 32,\n    WIDTH: 32,\n    START_0: 0x9800,\n    START_1: 0x9C00,\n    LENGTH: 0x0400 // 1024 bytes = 32*32\n};\n\nGPU.prototype.update = function(clockElapsed) {\n    this.clock += clockElapsed;\n    var vblank = false;\n\n    switch (this.mode) {\n        case 0: // HBLANK\n            if (this.clock >= 204) {\n                this.clock -= 204;\n                this.line++;\n                this.updateLY();\n                if (this.line == 144) {\n                    this.setMode(1);\n                    vblank = true;\n                    this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.VBLANK);\n                    this.drawFrame();\n                } else {\n                    this.setMode(2);\n                }\n            }\n            break;\n        case 1: // VBLANK\n            if (this.clock >= 456) {\n                this.clock -= 456;\n                this.line++;\n                if (this.line > 153) {\n                    this.line = 0;\n                    this.setMode(2);\n                }\n                this.updateLY();\n            }\n\n            break;\n        case 2: // SCANLINE OAM\n            if (this.clock >= 80) {\n                this.clock -= 80;\n                this.setMode(3);\n            }\n            break;\n        case 3: // SCANLINE VRAM\n            if (this.clock >= 172) {\n                this.clock -= 172;\n                this.drawScanLine(this.line);\n                this.setMode(0);\n            }\n            break;\n    }\n\n    return vblank;\n};\n\nGPU.prototype.updateLY = function() {\n    this.deviceram(this.LY, this.line);\n    var STAT = this.deviceram(this.STAT);\n    if (this.deviceram(this.LY) == this.deviceram(this.LYC)) {\n        this.deviceram(this.STAT, STAT | (1 << 2));\n        if (STAT & (1 << 6)) {\n            this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.LCDC);\n        }\n    } else {\n        this.deviceram(this.STAT, STAT & (0xFF - (1 << 2)));\n    }\n};\n\nGPU.prototype.setMode = function(mode) {\n    this.mode = mode;\n    var newSTAT = this.deviceram(this.STAT);\n    newSTAT &= 0xFC;\n    newSTAT |= mode;\n    this.deviceram(this.STAT, newSTAT);\n\n    if (mode < 3) {\n        if (newSTAT & (1 << (3+mode))) {\n            this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.LCDC);\n        }\n    }\n};\n\n// Push one scanline into the main buffer\nGPU.prototype.drawScanLine = function(line) {\n    var LCDC = this.deviceram(this.LCDC);\n    var enable = GameboyJS.Util.readBit(LCDC, 7);\n    if (enable) {\n        var lineBuffer = new Array(Screen.physics.WIDTH);\n        this.drawBackground(LCDC, line, lineBuffer);\n        this.drawSprites(LCDC, line, lineBuffer);\n        // TODO draw a line for the window here too\n    }\n};\n\nGPU.prototype.drawFrame = function() {\n    var LCDC = this.deviceram(this.LCDC);\n    var enable = GameboyJS.Util.readBit(LCDC, 7);\n    if (enable) {\n        //this.drawSprites(LCDC);\n        this.drawWindow(LCDC);\n    }\n    this.bgTileCache = {};\n    this.screen.render(this.buffer);\n};\n\nGPU.prototype.drawBackground = function(LCDC, line, lineBuffer) {\n    if (!GameboyJS.Util.readBit(LCDC, 0)) {\n        return;\n    }\n\n    var mapStart = GameboyJS.Util.readBit(LCDC, 3) ? GPU.tilemap.START_1 : GPU.tilemap.START_0;\n\n    var dataStart, signedIndex = false;\n    if (GameboyJS.Util.readBit(LCDC, 4)) {\n        dataStart = 0x8000;\n    } else {\n        dataStart = 0x8800;\n        signedIndex = true;\n    }\n\n    var bgx = this.deviceram(this.SCX);\n    var bgy = this.deviceram(this.SCY);\n    var tileLine = ((line + bgy) & 7);\n\n    // browse BG tilemap for the line to render\n    var tileRow = ((((bgy + line) / 8) | 0) & 0x1F);\n    var firstTile = ((bgx / 8) | 0) + 32 * tileRow;\n    var lastTile = firstTile + Screen.physics.WIDTH / 8 + 1;\n    if ((lastTile & 0x1F) < (firstTile & 0x1F)) {\n        lastTile -= 32;\n    }\n    var x = (firstTile & 0x1F) * 8 - bgx; // x position of the first tile's leftmost pixel\n    for (var i = firstTile; i != lastTile; i++, (i & 0x1F) == 0 ? i-=32 : null) {\n        var tileIndex = this.vram(i + mapStart);\n\n        if (signedIndex) {\n            tileIndex = GameboyJS.Util.getSignedValue(tileIndex) + 128;\n        }\n\n        // try to retrieve the tile data from the cache, or use readTileData() to read from ram\n        // TODO find a better cache system now that the BG is rendered line by line\n        var tileData = this.bgTileCache[tileIndex] || (this.bgTileCache[tileIndex] = this.readTileData(tileIndex, dataStart));\n\n        this.drawTileLine(tileData, tileLine);\n        this.copyBGTileLine(lineBuffer, this.tileBuffer, x);\n        x += 8;\n    }\n\n    this.copyLineToBuffer(lineBuffer, line);\n};\n\n// Copy a tile line from a tileBuffer to a line buffer, at a given x position\nGPU.prototype.copyBGTileLine = function(lineBuffer, tileBuffer, x) {\n    // copy tile line to buffer\n    for (var k = 0; k < 8; k++, x++) {\n        if (x < 0 || x >= Screen.physics.WIDTH) continue;\n        lineBuffer[x] = tileBuffer[k];\n    }\n};\n\n// Copy a scanline into the main buffer\nGPU.prototype.copyLineToBuffer = function(lineBuffer, line) {\n    var bgPalette = GPU.getPalette(this.deviceram(this.BGP));\n\n    for (var x = 0; x < Screen.physics.WIDTH; x++) {\n        var color = lineBuffer[x];\n        this.drawPixel(x, line, bgPalette[color]);\n    }\n};\n\n// Write a line of a tile (8 pixels) into a buffer array\nGPU.prototype.drawTileLine = function(tileData, line, xflip, yflip) {\n    xflip = xflip | 0;\n    yflip = yflip | 0;\n    var l = yflip ? 7 - line : line;\n    var byteIndex = l * 2;\n    var b1 = tileData[byteIndex++];\n    var b2 = tileData[byteIndex++];\n\n    var offset = 8;\n    for (var pixel = 0; pixel < 8; pixel++) {\n        offset--;\n        var mask = (1 << offset);\n        var colorValue = ((b1 & mask) >> offset) + ((b2 & mask) >> offset)*2;\n        var p = xflip ? offset : pixel;\n        this.tileBuffer[p] = colorValue;\n    }\n};\n\nGPU.prototype.drawSprites = function(LCDC, line, lineBuffer) {\n    if (!GameboyJS.Util.readBit(LCDC, 1)) {\n        return;\n    }\n    var spriteHeight = GameboyJS.Util.readBit(LCDC, 2) ? 16 : 8;\n\n    var sprites = new Array();\n    for (var i = this.OAM_START; i < this.OAM_END && sprites.length < 10; i += 4) {\n        var y = this.oamram(i);\n        var x = this.oamram(i+1);\n        var index = this.oamram(i+2);\n        var flags = this.oamram(i+3);\n\n        if (y - 16 > line || y - 16 < line - spriteHeight) {\n            continue;\n        }\n        sprites.push({x:x, y:y, index:index, flags:flags})\n    }\n\n    if (sprites.length == 0) return;\n\n    // cache object to store read tiles from this frame\n    var cacheTile = {};\n    var spriteLineBuffer = new Array(Screen.physics.WIDTH);\n\n    for (var i = 0; i < sprites.length; i++) {\n        var sprite = sprites[i];\n        var tileLine = line - sprite.y + 16;\n        var paletteNumber = GameboyJS.Util.readBit(flags, 4);\n        var xflip = GameboyJS.Util.readBit(sprite.flags, 5);\n        var yflip = GameboyJS.Util.readBit(sprite.flags, 6);\n        var tileData = cacheTile[sprite.index] || (cacheTile[sprite.index] = this.readTileData(sprite.index, 0x8000, spriteHeight * 2));\n        this.drawTileLine(tileData, tileLine, xflip, yflip);\n        this.copySpriteTileLine(spriteLineBuffer, this.tileBuffer, sprite.x - 8, paletteNumber);\n    }\n\n    this.copySpriteLineToBuffer(spriteLineBuffer, line);\n};\n\n// Copy a tile line from a tileBuffer to a line buffer, at a given x position\nGPU.prototype.copySpriteTileLine = function(lineBuffer, tileBuffer, x, palette) {\n    // copy tile line to buffer\n    for (var k = 0; k < 8; k++, x++) {\n        if (x < 0 || x >= Screen.physics.WIDTH || tileBuffer[k] == 0) continue;\n        lineBuffer[x] = {color:tileBuffer[k], palette: palette};\n    }\n};\n\n// Copy a sprite scanline into the main buffer\nGPU.prototype.copySpriteLineToBuffer = function(spriteLineBuffer, line) {\n    var spritePalettes = {};\n    spritePalettes[0] = GPU.getPalette(this.deviceram(this.OBP0));\n    spritePalettes[1] = GPU.getPalette(this.deviceram(this.OBP1));\n\n    for (var x = 0; x < Screen.physics.WIDTH; x++) {\n        if (!spriteLineBuffer[x]) continue;\n        var color = spriteLineBuffer[x].color;\n        if (color === 0) continue;\n        var paletteNumber = spriteLineBuffer[x].palette;\n        this.drawPixel(x, line, spritePalettes[paletteNumber][color]);\n    }\n};\n\nGPU.prototype.drawTile = function(tileData, x, y, buffer, bufferWidth, xflip, yflip, spriteMode) {\n    xflip = xflip | 0;\n    yflip = yflip | 0;\n    spriteMode = spriteMode | 0;\n    var byteIndex = 0;\n    for (var line = 0; line < 8; line++) {\n        var l = yflip ? 7 - line : line;\n        var b1 = tileData[byteIndex++];\n        var b2 = tileData[byteIndex++];\n\n        for (var pixel = 0; pixel < 8; pixel++) {\n            var mask = (1 << (7-pixel));\n            var colorValue = ((b1 & mask) >> (7-pixel)) + ((b2 & mask) >> (7-pixel))*2;\n            if (spriteMode && colorValue == 0) continue;\n            var p = xflip ? 7 - pixel : pixel;\n            var bufferIndex = (x + p) + (y + l) * bufferWidth;\n            buffer[bufferIndex] = colorValue;\n        }\n    }\n};\n\n// get an array of tile bytes data (16 entries for 8*8px)\nGPU.prototype.readTileData = function(tileIndex, dataStart, tileSize) {\n    tileSize = tileSize || 0x10; // 16 bytes / tile by default (8*8 px)\n    var tileData = new Array();\n\n    var tileAddressStart = dataStart + (tileIndex * 0x10);\n    for (var i = tileAddressStart; i < tileAddressStart + tileSize; i++) {\n        tileData.push(this.vram(i));\n    }\n\n    return tileData;\n};\n\nGPU.prototype.drawWindow = function(LCDC) {\n    if (!GameboyJS.Util.readBit(LCDC, 5)) {\n        return;\n    }\n\n    var buffer = new Array(256*256);\n    var mapStart = GameboyJS.Util.readBit(LCDC, 6) ? GPU.tilemap.START_1 : GPU.tilemap.START_0;\n\n    var dataStart, signedIndex = false;\n    if (GameboyJS.Util.readBit(LCDC, 4)) {\n        dataStart = 0x8000;\n    } else {\n        dataStart = 0x8800;\n        signedIndex = true;\n    }\n\n    // browse Window tilemap\n    for (var i = 0; i < GPU.tilemap.LENGTH; i++) {\n        var tileIndex = this.vram(i + mapStart);\n\n        if (signedIndex) {\n            tileIndex = GameboyJS.Util.getSignedValue(tileIndex) + 128;\n        }\n\n        var tileData = this.readTileData(tileIndex, dataStart);\n        var x = i % GPU.tilemap.WIDTH;\n        var y = (i / GPU.tilemap.WIDTH) | 0;\n        this.drawTile(tileData, x * 8, y * 8, buffer, 256);\n    }\n\n    var wx = this.deviceram(this.WX) - 7;\n    var wy = this.deviceram(this.WY);\n    for (var x = Math.max(0, -wx); x < Math.min(Screen.physics.WIDTH, Screen.physics.WIDTH - wx); x++) {\n        for (var y = Math.max(0, -wy); y < Math.min(Screen.physics.HEIGHT, Screen.physics.HEIGHT - wy); y++) {\n            var color = buffer[(x & 255) + (y & 255) * 256];\n            this.drawPixel(x + wx, y + wy, color);\n        }\n    }\n};\n\nGPU.prototype.drawPixel = function(x, y, color) {\n    this.buffer[y * 160 + x] = color;\n};\n\nGPU.prototype.getPixel = function(x, y) {\n    return this.buffer[y * 160 + x];\n};\n\n// Get the palette mapping from a given palette byte as stored in memory\n// A palette will map a tile color to a final palette color index\n// used with Screen.colors to get a shade of grey\nGPU.getPalette = function(paletteByte) {\n    var palette = [];\n    for (var i = 0; i < 8; i += 2) {\n        var shade = (paletteByte & (3 << i)) >> i;\n        palette.push(shade);\n    }\n    return palette;\n};\n\nGameboyJS.GPU = GPU;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Screen device\nvar Screen = function(canvas, pixelSize) {\n    this.context = canvas.getContext('2d');\n    this.canvas = canvas;\n    this.pixelSize = pixelSize || 1;\n    this.initImageData();\n};\n\nScreen.colors = [\n    0xFF,\n    0xAA,\n    0x55,\n    0x00\n];\n\nScreen.physics = {\n    WIDTH    : 160,\n    HEIGHT   : 144,\n    FREQUENCY: 60\n};\n\nScreen.prototype.setPixelSize = function(pixelSize) {\n    this.pixelSize = pixelSize;\n    this.initImageData();\n};\n\nScreen.prototype.initImageData = function() {\n    this.canvas.width = Screen.physics.WIDTH * this.pixelSize;\n    this.canvas.height = Screen.physics.HEIGHT * this.pixelSize;\n    this.imageData = this.context.createImageData(this.canvas.width, this.canvas.height);\n};\n\nScreen.prototype.clearScreen = function() {\n    this.context.fillStyle = '#FFF';\n    this.context.fillRect(0, 0, Screen.physics.WIDTH * this.pixelSize, Screen.physics.HEIGHT * this.pixelSize);\n};\n\nScreen.prototype.fillImageData = function(buffer) {\n    for (var y = 0; y < Screen.physics.HEIGHT; y++) {\n        for (var py = 0; py < this.pixelSize; py++) {\n            var _y = y * this.pixelSize + py;\n            for (var x = 0; x < Screen.physics.WIDTH; x++) {\n                for (var px = 0; px < this.pixelSize; px++) {\n                    var offset = _y * this.canvas.width + (x * this.pixelSize + px);\n                    var v = Screen.colors[buffer[y * Screen.physics.WIDTH + x]];\n                    this.imageData.data[offset * 4] = v;\n                    this.imageData.data[offset * 4 + 1] = v;\n                    this.imageData.data[offset * 4 + 2] = v;\n                    this.imageData.data[offset * 4 + 3] = 255;\n                }\n            }\n        }\n    }\n};\n\nScreen.prototype.render = function(buffer) {\n    this.fillImageData(buffer);\n    this.context.putImageData(this.imageData, 0, 0);\n};\n\nGameboyJS.Screen = Screen;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// This exception should be thrown whenever a critical feature that\n// has not been implemented is requested\nfunction UnimplementedException(message, fatal) {\n    this.message = message;\n    this.name = UnimplementedException;\n    if (fatal === undefined) {\n        fatal = true;\n    }\n    this.fatal = fatal;\n}\nGameboyJS.UnimplementedException = UnimplementedException;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Object for mapping the cartridge RAM\nvar ExtRam = function() {\n    this.extRam = null;\n    this.ramSize = 0;\n    this.ramBank = 0;\n};\n\nExtRam.prototype.loadRam = function(game, size) {\n    this.gameName = game;\n\n    this.ramSize = size;\n    this.ramBanksize = this.ramSize >= 0x2000 ? 8192 : 2048;\n\n    var key = this.getStorageKey();\n    var data = localStorage.getItem(key);\n    if (data == null) {\n        this.extRam = Array.apply(null, new Array(this.ramSize)).map(function(){return 0;});\n    } else {\n        this.extRam = JSON.parse(data);\n        if (this.extRam.length != size) {\n            console.error('Found RAM data but not matching expected size.');\n        }\n    }\n};\n\nExtRam.prototype.setRamBank = function(bank) {\n    this.ramBank = bank;\n};\n\nExtRam.prototype.manageWrite = function(offset, value) {\n    this.extRam[this.ramBank * 8192 + offset] = value;\n};\n\nExtRam.prototype.manageRead = function(offset) {\n    return this.extRam[this.ramBank * 8192 + offset];\n};\n\nExtRam.prototype.getStorageKey = function() {\n    return this.gameName + '_EXTRAM';;\n};\n// Actually save the RAM in the physical storage (localStorage)\nExtRam.prototype.saveRamData = function() {\n    localStorage.setItem(this.getStorageKey(), JSON.stringify(this.extRam));\n};\nGameboyJS.ExtRam = ExtRam;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// This is the default buttons mapping for the Gamepad\n// It's optimized for the XBOX pad\n//\n// Any other mapping can be provided as a constructor argument of the Gamepad object\n// An alternative mapping should be an object with keys being the indexes\n// of the gamepad buttons and values the normalized gameboy button names\nvar xboxMapping = {\n    0: 'UP',\n    1: 'DOWN',\n    2: 'LEFT',\n    3: 'RIGHT',\n    4: 'START',\n    5: 'SELECT',\n    11: 'A',\n    12: 'B'\n};\n\n// Gamepad listener\n// Communication layer between the Gamepad API and the Input class\n// Any physical controller can be used but the mapping should be provided\n// in order to get an optimal layout of the buttons (see above)\nvar Gamepad = function(mapping) {\n    this.gamepad = null;\n    this.state = {A:0,B:0,START:0,SELECT:0,LEFT:0,RIGHT:0,UP:0,DOWN:0};\n    this.pullInterval = null;\n    this.buttonMapping = mapping || xboxMapping;\n};\n\n// Initialize the keyboard listeners and set up the callbacks\n// for button press / release\nGamepad.prototype.init = function(onPress, onRelease) {\n    this.onPress = onPress;\n    this.onRelease = onRelease;\n\n    var self = this;\n    window.addEventListener('gamepadconnected', function(e) {\n        self.gamepad = e.gamepad;\n        self.activatePull();\n    });\n    window.addEventListener('gamepaddisconnected', function(e) {\n        self.gamepad = null;\n        self.deactivatePull();\n    });\n};\n\nGamepad.prototype.activatePull = function() {\n    this.deactivatePull();\n    this.pullInterval = setInterval(this.pullState.bind(this), 100);\n};\n\nGamepad.prototype.deactivatePull = function() {\n    clearInterval(this.pullInterval);\n};\n\n// Check the state of the current gamepad in order to detect any press/release action\nGamepad.prototype.pullState = function() {\n    for (var index in this.buttonMapping) {\n        var button = this.buttonMapping[index];\n        var oldState = this.state[button];\n        this.state[button] = this.gamepad.buttons[index].pressed;\n\n        if (this.state[button] == 1 && oldState == 0) {\n            this.managePress(button);\n        } else if (this.state[button] == 0 && oldState == 1) {\n            this.manageRelease(button);\n        }\n    }\n};\n\nGamepad.prototype.managePress = function(key) {\n    this.onPress(key);\n};\n\nGamepad.prototype.manageRelease = function(key) {\n    this.onRelease(key);\n};\n\nGameboyJS.Gamepad = Gamepad;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// The Input management system\n//\n// The pressKey() and releaseKey() functions should be called by a device class\n// like GameboyJS.Keyboard after a physical button trigger event\n//\n// They rely on the name of the original buttons as parameters (see Input.keys)\nvar Input = function(cpu, pad) {\n    this.cpu = cpu;\n    this.memory = cpu.memory;\n    this.P1 = 0xFF00;\n    this.state = 0;\n\n    pad.init(this.pressKey.bind(this), this.releaseKey.bind(this));\n};\n\nInput.keys = {\n    START:  0x80,\n    SELECT: 0x40,\n    B:      0x20,\n    A:      0x10,\n    DOWN:   0x08,\n    UP:     0x04,\n    LEFT:   0x02,\n    RIGHT:  0x01\n};\n\nInput.prototype.pressKey = function(key) {\n    this.state |= Input.keys[key];\n\n    this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.HILO);\n};\n\nInput.prototype.releaseKey = function(key) {\n    var mask = 0xFF - Input.keys[key];\n    this.state &= mask;\n};\n\nInput.prototype.update = function() {\n    var value = this.memory.rb(this.P1);\n    value = ((~value) & 0x30); // invert the value so 1 means 'active'\n    if (value & 0x10) { // direction keys listened\n        value |= (this.state & 0x0F);\n    } else if (value & 0x20) { // action keys listened\n        value |= ((this.state & 0xF0) >> 4);\n    } else if ((value & 0x30) == 0) { // no keys listened\n        value &= 0xF0;\n    }\n\n    value = ((~value) & 0x3F); // invert back\n    this.memory[this.P1] = value;\n};\nGameboyJS.Input = Input;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Keyboard listener\n// Does the mapping between the keyboard and the Input class\nvar Keyboard = function() {};\n\n// Initialize the keyboard listeners and set up the callbacks\n// for button press / release\nKeyboard.prototype.init = function(onPress, onRelease) {\n    this.onPress = onPress;\n    this.onRelease = onRelease;\n\n    var self = this;\n    document.addEventListener('keydown', function(e) {\n        self.managePress(e.keyCode);\n    });\n    document.addEventListener('keyup', function(e) {\n        self.manageRelease(e.keyCode);\n    });\n}\n\nKeyboard.prototype.managePress = function(keycode) {\n    var key = this.translateKey(keycode);\n    if (key) {\n        this.onPress(key);\n    }\n};\n\nKeyboard.prototype.manageRelease = function(keycode) {\n    var key = this.translateKey(keycode);\n    if (key) {\n        this.onRelease(key);\n    }\n};\n\n// Transform a keyboard keycode into a key of the Input.keys object\nKeyboard.prototype.translateKey = function(keycode) {\n    var key = null;\n    switch (keycode) {\n        case 71: // G\n            key = 'A';\n            break;\n        case 66: // B\n            key = 'B';\n            break;\n        case 72: // H\n            key = 'START';\n            break;\n        case 78: // N\n            key = 'SELECT';\n            break;\n        case 37: // left\n            key = 'LEFT';\n            break;\n        case 38: // up\n            key = 'UP';\n            break;\n        case 39: // right\n            key = 'RIGHT';\n            break;\n        case 40: // down\n            key = 'DOWN';\n            break;\n    }\n\n    return key;\n};\nGameboyJS.Keyboard = Keyboard;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// List of CPU operations\n// Most operations have been factorized here to limit code redundancy\n//\n// How to read operations:\n// Uppercase letters qualify the kind of operation (LD = LOAD, INC = INCREMENT, etc.)\n// Lowercase letters are used to hint parameters :\n// r = register, n = 1 memory byte, sp = sp register,\n// a = suffix for memory address, i = bit index\n// Example : LDrrar = LOAD operation with two-registers memory address\n// as first parameter and one register value as second\n//\n// Underscore-prefixed functions are here to delegate the logic between similar operations,\n// they should not be called from outside\n//\n// It's up to each operation to update the CPU clock\nvar ops = {\n    LDrrnn: function(p, r1, r2) {p.wr(r2, p.memory.rb(p.r.pc));p.wr(r1, p.memory.rb(p.r.pc+1)); p.r.pc+=2;p.clock.c += 12;},\n    LDrrar: function(p, r1, r2, r3) {ops._LDav(p, GameboyJS.Util.getRegAddr(p, r1, r2), p.r[r3]);p.clock.c += 8;},\n    LDrrra: function(p, r1, r2, r3) {p.wr(r1, p.memory.rb(GameboyJS.Util.getRegAddr(p, r2, r3)));p.clock.c += 8;},\n    LDrn:   function(p, r1) {p.wr(r1, p.memory.rb(p.r.pc++));p.clock.c += 8;},\n    LDrr:   function(p, r1, r2) {p.wr(r1, p.r[r2]);p.clock.c += 4;},\n    LDrar:  function(p, r1, r2) {p.memory.wb(p.r[r1]+0xFF00, p.r[r2]);p.clock.c += 8;},\n    LDrra:  function(p, r1, r2) {p.wr(r1, p.memory.rb(p.r[r2]+0xFF00));p.clock.c += 8;},\n    LDspnn: function(p) {p.wr('sp', (p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc));p.r.pc+=2;p.clock.c += 12;},\n    LDsprr: function(p, r1, r2) {p.wr('sp', GameboyJS.Util.getRegAddr(p, r1, r2));p.clock.c += 8;},\n    LDnnar: function(p, r1) {var addr=(p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc);p.memory.wb(addr,p.r[r1]);p.r.pc+=2; p.clock.c += 16;},\n    LDrnna: function(p, r1) {var addr=(p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc);p.wr(r1, p.memory.rb(addr));p.r.pc+=2; p.clock.c += 16;},\n    LDrrspn:function(p, r1, r2) {var rel = p.memory.rb(p.r.pc++);rel=GameboyJS.Util.getSignedValue(rel);var val=p.r.sp + rel;\n        var c = (p.r.sp&0xFF) + (rel&0xFF) > 0xFF;var h = (p.r.sp & 0xF) + (rel & 0xF) > 0xF;val &= 0xFFFF;\n        var f = 0; if(h)f|=0x20;if(c)f|=0x10;p.wr('F', f);\n        p.wr(r1, val >> 8);p.wr(r2, val&0xFF);\n        p.clock.c+=12;},\n    LDnnsp: function(p) {var addr = p.memory.rb(p.r.pc++) + (p.memory.rb(p.r.pc++)<<8); ops._LDav(p, addr, p.r.sp & 0xFF);ops._LDav(p, addr+1, p.r.sp >> 8);p.clock.c+=20;},\n    LDrran: function(p, r1, r2){var addr = GameboyJS.Util.getRegAddr(p, r1, r2);ops._LDav(p, addr, p.memory.rb(p.r.pc++));p.clock.c+=12;},\n    _LDav:  function(p, addr, val){p.memory.wb(addr, val);},\n    LDHnar: function(p, r1){p.memory.wb(0xFF00 + p.memory.rb(p.r.pc++), p.r[r1]);p.clock.c+=12;},\n    LDHrna: function(p, r1){p.wr(r1, p.memory.rb(0xFF00 + p.memory.rb(p.r.pc++)));p.clock.c+=12;},\n    INCrr:  function(p, r1, r2) {p.wr(r2, (p.r[r2]+1)&0xFF); if (p.r[r2] == 0) p.wr(r1, (p.r[r1]+1)&0xFF);p.clock.c += 8;},\n    INCrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var val = (p.memory.rb(addr)+1)&0xFF;var z = val==0;var h=(p.memory.rb(addr)&0xF)+1 > 0xF;\n        p.memory.wb(addr, val);\n        p.r.F&=0x10;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c+=12;},\n    INCsp:  function(p){p.wr('sp', p.r.sp+1); p.r.sp &= 0xFFFF; p.clock.c+=8;},\n    INCr:   function(p, r1) {var h = ((p.r[r1]&0xF) + 1)&0x10;p.wr(r1, (p.r[r1] + 1)&0xFF);var z = p.r[r1]==0;\n        p.r.F&=0x10;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c += 4;},\n    DECrr:  function(p, r1, r2) {p.wr(r2, (p.r[r2] - 1) & 0xFF); if (p.r[r2] == 0xFF) p.wr(r1, (p.r[r1] - 1)&0xFF);p.clock.c += 8;},\n    DECsp:  function(p){p.wr('sp', p.r.sp-1); p.r.sp &= 0xFFFF; p.clock.c+=8;},\n    DECr:   function(p, r1) {var h = (p.r[r1]&0xF) < 1;p.wr(r1, (p.r[r1] - 1) & 0xFF);var z = p.r[r1]==0;\n        p.r.F&=0x10;p.r.F|=0x40;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c += 4;},\n    DECrra: function(p, r1, r2){var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var val = (p.memory.rb(addr)-1)&0xFF;var z = val==0;var h=(p.memory.rb(addr)&0xF) < 1;\n        p.memory.wb(addr, val);\n        p.r.F&=0x10;p.r.F|=0x40;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c+=12;},\n    ADDrr:  function(p, r1, r2) {var n = p.r[r2];ops._ADDrn(p, r1, n); p.clock.c += 4;},\n    ADDrn:  function(p, r1) {var n = p.memory.rb(p.r.pc++);ops._ADDrn(p, r1, n); p.clock.c+=8;},\n    _ADDrn: function(p, r1, n) {var h=((p.r[r1]&0xF)+(n&0xF))&0x10;p.wr(r1, p.r[r1]+n);var c=p.r[r1]&0x100;p.r[r1]&=0xFF;\n            var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);},\n    ADDrrrr:function(p, r1, r2, r3, r4) {ops._ADDrrn(p, r1, r2, (p.r[r3]<<8) + p.r[r4]); p.clock.c+=8;},\n    ADDrrsp:function(p, r1, r2) {ops._ADDrrn(p, r1, r2, p.r.sp); p.clock.c += 8;},\n    ADDspn: function(p) {var v = p.memory.rb(p.r.pc++);v = GameboyJS.Util.getSignedValue(v);\n        var c = ((p.r.sp&0xFF) + (v&0xFF)) > 0xFF; var h = (p.r.sp & 0xF) + (v&0xF) > 0xF;\n        var f = 0; if(h)f|=0x20;if(c)f|=0x10;p.wr('F', f);\n        p.wr('sp', (p.r.sp + v) & 0xFFFF);\n        p.clock.c+=16;},\n    _ADDrrn:function(p, r1, r2, n) {var v1 = (p.r[r1]<<8) + p.r[r2];var v2 = n;\n        var res = v1 + v2;var c = res&0x10000;var h = ((v1&0xFFF) + (v2&0xFFF))&0x1000;var z = p.r.F&0x80;\n        res&=0xFFFF;p.r[r2]=res&0xFF;res=res>>8;p.r[r1]=res&0xFF;\n        var f=0;if(z)f|=0x80;if(h)f|=0x20;if(c)f|=0x10;p.r.F=f;},\n    ADCrr:  function(p, r1, r2) {var n = p.r[r2]; ops._ADCrn(p, r1, n); p.clock.c += 4;},\n    ADCrn:  function(p, r1) {var n = p.memory.rb(p.r.pc++); ops._ADCrn(p, r1, n); p.clock.c += 8;},\n    _ADCrn: function(p, r1, n) {\n        var c = p.r.F&0x10?1:0;var h=((p.r[r1]&0xF)+(n&0xF)+c)&0x10;\n        p.wr(r1, p.r[r1]+n+c);c=p.r[r1]&0x100;p.r[r1]&=0xFF;\n        var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.r.F=f;},\n    ADCrrra:function(p, r1, r2, r3) {var n = p.memory.rb(GameboyJS.Util.getRegAddr(p, r2, r3)); ops._ADCrn(p, r1, n); p.clock.c += 8;},\n    ADDrrra:function(p, r1, r2, r3) {var v = p.memory.rb(GameboyJS.Util.getRegAddr(p, r2, r3));var h=((p.r[r1]&0xF)+(v&0xF))&0x10;p.wr(r1, p.r[r1]+v);var c=p.r[r1]&0x100;p.r[r1]&=0xFF;\n        var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);\n        p.clock.c += 8;},\n    SUBr:   function(p, r1) {var n = p.r[r1];ops._SUBn(p, n);p.clock.c += 4;},\n    SUBn:   function(p) {var n = p.memory.rb(p.r.pc++);ops._SUBn(p, n);p.clock.c += 8;},\n    SUBrra: function(p, r1, r2) {var n = p.memory.rb(GameboyJS.Util.getRegAddr(p, r1, r2));ops._SUBn(p, n);p.clock.c+=8;},\n    _SUBn:  function(p, n) {var c = p.r.A < n;var h = (p.r.A&0xF) < (n&0xF);\n        p.wr('A', p.r.A - n);p.r.A&=0xFF; var z = p.r.A==0;\n        var f = 0x40;if (z)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);},\n    SBCn:   function(p) {var n = p.memory.rb(p.r.pc++); ops._SBCn(p, n); p.clock.c += 8;},\n    SBCr:   function(p, r1) {var n = p.r[r1]; ops._SBCn(p, n); p.clock.c += 4;},\n    SBCrra: function(p, r1, r2) {var v = p.memory.rb((p.r[r1] << 8) + p.r[r2]); ops._SBCn(p, v); p.clock.c += 8;},\n    _SBCn:  function(p, n) {var carry = p.r.F&0x10 ? 1 : 0;\n        var c = p.r.A < n + carry;var h = (p.r.A&0xF) < (n&0xF) + carry;\n        p.wr('A', p.r.A - n - carry); p.r.A&=0xFF; var z = p.r.A == 0;\n        var f = 0x40;if (z)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.r.F=f;},\n    ORr:    function(p, r1) {p.r.A|=p.r[r1];p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 4;},\n    ORn:    function(p) {p.r.A|=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    ORrra:  function(p, r1, r2) {p.r.A|=p.memory.rb((p.r[r1] << 8)+ p.r[r2]);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    ANDr:   function(p, r1) {p.r.A&=p.r[r1];p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 4;},\n    ANDn:   function(p) {p.r.A&=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 8;},\n    ANDrra: function(p, r1, r2) {p.r.A&=p.memory.rb(GameboyJS.Util.getRegAddr(p, r1, r2));p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 8;},\n    XORr:   function(p, r1) {p.r.A^=p.r[r1];p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 4;},\n    XORn:   function(p) {p.r.A^=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    XORrra: function(p, r1, r2) {p.r.A^=p.memory.rb((p.r[r1] << 8)+ p.r[r2]);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    CPr:    function(p, r1) {var n = p.r[r1];ops._CPn(p, n); p.clock.c += 4;},\n    CPn:    function(p) {var n =p.memory.rb(p.r.pc++);ops._CPn(p, n);p.clock.c+=8;},\n    CPrra:  function(p, r1, r2) {var n = p.memory.rb(GameboyJS.Util.getRegAddr(p, r1, r2));ops._CPn(p, n);p.clock.c+=8;},\n    _CPn:   function(p, n) {\n        var c = p.r.A < n;var z = p.r.A == n;var h = (p.r.A&0xF) < (n&0xF);\n        var f = 0x40;if(z)f+=0x80;if (h)f+=0x20;if (c)f+=0x10;p.r.F=f;},\n    RRCr:   function(p, r1) {p.r.F=0;var out=p.r[r1] & 0x01;if(out)p.r.F|=0x10;p.r[r1]=(p.r[r1]>>1)|(out*0x80);if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RRCrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F=0;var out=p.memory.rb(addr)&0x01;if(out)p.r.F|=0x10;p.memory.wb(addr, (p.memory.rb(addr)>>1)|(out*0x80));if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    RLCr:   function(p, r1) {p.r.F=0;var out=p.r[r1]&0x80?1:0;if(out)p.r.F|=0x10;p.r[r1]=((p.r[r1]<<1)+out)&0xFF;if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RLCrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F=0;var out=p.memory.rb(addr)&0x80?1:0;if(out)p.r.F|=0x10;p.memory.wb(addr, ((p.memory.rb(addr)<<1)+out)&0xFF);if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    RLr:    function(p, r1) {var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.r[r1]&0x80;out?p.r.F|=0x10:p.r.F&=0xEF;p.r[r1]=((p.r[r1]<<1)+c)&0xFF;if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RLrra:  function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.memory.rb(addr)&0x80;out?p.r.F|=0x10:p.r.F&=0xEF;p.memory.wb(addr,((p.memory.rb(addr)<<1)+c)&0xFF);if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    RRr:    function(p, r1) {var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.r[r1]&0x01;out?p.r.F|=0x10:p.r.F&=0xEF;p.r[r1]=(p.r[r1]>>1)|(c*0x80);if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RRrra:  function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.memory.rb(addr)&0x01;out?p.r.F|=0x10:p.r.F&=0xEF;p.memory.wb(addr,(p.memory.rb(addr)>>1)|(c*0x80));if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    SRAr:   function(p, r1) {p.r.F = 0;if (p.r[r1]&0x01)p.r.F|=0x10;var msb=p.r[r1]&0x80;p.r[r1]=(p.r[r1]>>1)|msb;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    SRArra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x01)p.r.F|=0x10;var msb=p.memory.rb(addr)&0x80;p.memory.wb(addr, (p.memory.rb(addr)>>1)|msb);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    SLAr:   function(p, r1) {p.r.F = 0;if (p.r[r1]&0x80)p.r.F|=0x10;p.r[r1]=(p.r[r1]<<1)&0xFF;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    SLArra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x80)p.r.F|=0x10;p.memory.wb(addr, (p.memory.rb(addr)<<1)&0xFF);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    SRLr:   function(p, r1) {p.r.F = 0;if (p.r[r1]&0x01)p.r.F|=0x10;p.r[r1]=p.r[r1]>>1;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    SRLrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x01)p.r.F|=0x10;p.memory.wb(addr, p.memory.rb(addr)>>1);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    BITir:  function(p, i, r1) {var mask=1<<i;var z=(p.r[r1]&mask)?0:1;var f=p.r.F&0x10;f |= 0x20;if(z)f|=0x80;p.r.F=f;p.clock.c+=4;},\n    BITirra:function(p, i, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var mask=1<<i;var z=(p.memory.rb(addr)&mask)?0:1;var f=p.r.F&0x10;f |= 0x20;if(z)f|=0x80;p.r.F=f;p.clock.c+=8;},\n    SETir:  function(p, i, r1) {var mask=1<<i;p.r[r1]|=mask;p.clock.c += 4;},\n    SETirra:function(p, i, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var mask=1<<i;p.memory.wb(addr, p.memory.rb(addr)|mask);p.clock.c += 12;},\n    RESir:  function(p, i, r1) {var mask=0xFF - (1<<i);p.r[r1]&=mask;p.clock.c += 4;},\n    RESirra:function(p, i, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var mask=0xFF - (1<<i);p.memory.wb(addr, p.memory.rb(addr)&mask);p.clock.c += 12;},\n    SWAPr:  function(p, r1) {p.r[r1] = ops._SWAPn(p, p.r[r1]);p.clock.c+=4;},\n    SWAPrra:function(p, r1, r2){var addr = (p.r[r1] << 8)+ p.r[r2]; p.memory.wb(addr, ops._SWAPn(p, p.memory.rb(addr))); p.clock.c+=12;},\n    _SWAPn: function(p, n){p.r.F = n==0?0x80:0;return ((n&0xF0) >> 4) | ((n&0x0F) << 4);},\n    JPnn:   function(p) {p.wr('pc', (p.memory.rb(p.r.pc+1) << 8) + p.memory.rb(p.r.pc));p.clock.c += 16;},\n    JRccn:  function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){var v=p.memory.rb(p.r.pc++);v=GameboyJS.Util.getSignedValue(v);p.r.pc += v;p.clock.c+=4;}else{p.r.pc++;}p.clock.c += 8;},\n    JPccnn: function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){p.wr('pc', (p.memory.rb(p.r.pc+1) << 8) + p.memory.rb(p.r.pc));p.clock.c+=4;}else{p.r.pc+=2;}p.clock.c += 12;},\n    JPrr:   function(p, r1, r2) {p.r.pc = (p.r[r1] << 8) + p.r[r2];p.clock.c += 4;},\n    JRn:    function(p) {var v=p.memory.rb(p.r.pc++);v=GameboyJS.Util.getSignedValue(v);p.r.pc += v;p.clock.c += 12;},\n    PUSHrr: function(p, r1, r2) {p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp, p.r[r1]);p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp, p.r[r2]);p.clock.c+=16;},\n    POPrr:  function(p, r1, r2) {p.wr(r2, p.memory.rb(p.r.sp));p.wr('sp', p.r.sp+1);p.wr(r1, p.memory.rb(p.r.sp));p.wr('sp', p.r.sp+1);p.clock.c+=12;},\n    RSTn:   function(p, n) {p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp,p.r.pc>>8);p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp,p.r.pc&0xFF);p.r.pc=n;p.clock.c+=16;},\n    RET:    function(p) {p.r.pc = p.memory.rb(p.r.sp);p.wr('sp', p.r.sp+1);p.r.pc+=p.memory.rb(p.r.sp)<<8;p.wr('sp', p.r.sp+1);p.clock.c += 16;},\n    RETcc:  function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){p.r.pc = p.memory.rb(p.r.sp);p.wr('sp', p.r.sp+1);p.r.pc+=p.memory.rb(p.r.sp)<<8;p.wr('sp', p.r.sp+1);p.clock.c+=12;}p.clock.c+=8;},\n    CALLnn: function(p) {ops._CALLnn(p); p.clock.c+=24;},\n    CALLccnn:function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){ops._CALLnn(p);p.clock.c+=12;}else{p.r.pc+=2;}p.clock.c+=12; },\n    _CALLnn:function(p){p.wr('sp', p.r.sp - 1); p.memory.wb(p.r.sp, ((p.r.pc+2)&0xFF00)>>8);\n        p.wr('sp', p.r.sp - 1); p.memory.wb(p.r.sp, (p.r.pc+2)&0x00FF);\n        var j=p.memory.rb(p.r.pc)+(p.memory.rb(p.r.pc+1)<<8);p.r.pc=j;},\n    CPL:    function(p) {p.wr('A', (~p.r.A)&0xFF);p.r.F|=0x60,p.clock.c += 4;},\n    CCF:    function(p) {p.r.F&=0x9F;p.r.F&0x10?p.r.F&=0xE0:p.r.F|=0x10;p.clock.c += 4;},\n    SCF:    function(p) {p.r.F&=0x9F;p.r.F|=0x10;p.clock.c+=4;},\n    DAA:    function(p) {\n        var sub = (p.r.F&0x40) ? 1 : 0; var h = (p.r.F&0x20)?1:0;var c = (p.r.F&0x10)?1:0;\n        if (sub) {\n            if (h) {\n                p.r.A = (p.r.A - 0x6) & 0xFF;\n            }\n            if (c) {\n                p.r.A -= 0x60;\n            }\n        } else {\n            if ((p.r.A&0xF) > 9 || h) {\n                p.r.A += 0x6;\n            }\n            if (p.r.A > 0x9F || c) {\n                p.r.A += 0x60;\n            }\n        }\n        if (p.r.A&0x100) c = 1;\n\n        p.r.A &= 0xFF;\n        p.r.F &= 0x40;if (p.r.A == 0) p.r.F|=0x80;if (c) p.r.F|=0x10;\n        p.clock.c += 4;\n    },\n    HALT:   function(p) {p.halt(); p.clock.c+=4;},\n    DI:     function(p) {p.disableInterrupts();p.clock.c += 4;},\n    EI:     function(p) {p.enableInterrupts();p.clock.c += 4;},\n    RETI:   function(p) {p.enableInterrupts();ops.RET(p);},\n    CB:     function(p) {var opcode = p.memory.rb(p.r.pc++);\n        GameboyJS.opcodeCbmap[opcode](p);\n        p.clock.c+=4;}\n};\nGameboyJS.cpuOps = ops;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\nvar defaultOptions = {\n    pad: {class: GameboyJS.Keyboard, mapping: null},\n    zoom: 1,\n    romReaders: [],\n    statusContainerId: 'status',\n    gameNameContainerId: 'game-name',\n    errorContainerId: 'error'\n};\n\n// Gameboy class\n//\n// This object is the entry point of the application\n// Will delegate user actions to the emulated devices\n// and provide information where needed\nvar Gameboy = function(canvas, options) {\n    options = options || {};\n    this.options = GameboyJS.Util.extend({}, defaultOptions, options);\n\n    var cpu = new GameboyJS.CPU(this);\n    var screen = new GameboyJS.Screen(canvas, this.options.zoom);\n    var gpu = new GameboyJS.GPU(screen, cpu);\n    cpu.gpu = gpu;\n\n    var pad = new this.options.pad.class(this.options.pad.mapping);\n    var input = new GameboyJS.Input(cpu, pad);\n    cpu.input = input;\n\n    this.cpu = cpu;\n    this.screen = screen;\n    this.input = input;\n    this.pad = pad;\n\n    this.createRom(this.options.romReaders);\n\n    this.statusContainer   = document.getElementById(this.options.statusContainerId) || document.createElement('div');\n    this.gameNameContainer = document.getElementById(this.options.gameNameContainerId) || document.createElement('div');\n    this.errorContainer    = document.getElementById(this.options.errorContainerId) || document.createElement('div');\n};\n\n// Create the ROM object and bind one or more readers\nGameboy.prototype.createRom = function (readers) {\n    var rom = new GameboyJS.Rom(this);\n    if (readers.length == 0) {\n        // add the default rom reader\n        var romReader = new GameboyJS.RomFileReader();\n        rom.addReader(romReader);\n    } else {\n        for (var i in readers) {\n            if (readers.hasOwnProperty(i)) {\n                rom.addReader(readers[i]);\n            }\n        }\n    }\n};\n\nGameboy.prototype.startRom = function(rom) {\n    this.errorContainer.classList.add('hide');\n    this.cpu.reset();\n    try {\n        this.cpu.loadRom(rom.data);\n        this.setStatus('Game Running :');\n        this.setGameName(this.cpu.getGameName());\n        this.cpu.run();\n    } catch (e) {\n        this.handleException(e);\n    }\n};\n\nGameboy.prototype.pause = function(value) {\n    if (value) {\n        this.setStatus('Game Paused :');\n        this.cpu.pause();\n    } else {\n        this.setStatus('Game Running :');\n        this.cpu.unpause();\n    }\n};\n\nGameboy.prototype.error = function(message) {\n    this.setStatus('Error during execution');\n    this.setError('An error occurred during execution:' + message);\n    this.cpu.stop();\n};\n\nGameboy.prototype.setStatus = function(status) {\n    this.statusContainer.innerHTML = status;\n};\n// Display an error message\nGameboy.prototype.setError = function(message) {\n    this.errorContainer.classList.remove('hide');\n    this.errorContainer.innerHTML = message;\n};\n// Display the name of the game running\nGameboy.prototype.setGameName = function(name) {\n    this.gameNameContainer.innerHTML = name;\n};\nGameboy.prototype.setSoundEnabled = function(value) {\n    if (value) {\n        this.cpu.apu.connect();\n    } else {\n        this.cpu.apu.disconnect();\n    }\n};\nGameboy.prototype.setScreenZoom = function(value) {\n    this.screen.setPixelSize(value);\n};\nGameboy.prototype.handleException = function(e) {\n    if (e instanceof GameboyJS.UnimplementedException) {\n        if (e.fatal) {\n            this.error('This cartridge is not supported ('+ e.message +')');\n        } else {\n            console.error(e.message);\n        }\n    } else {\n        throw e;\n    }\n};\nGameboyJS.Gameboy = Gameboy;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Memory bank controllers\n\nvar MBC = {};\n\n// Create an MBC instance depending on the type specified in the cartridge\nMBC.getMbcInstance = function(memory, type) {\n    var instance;\n    switch (type) {\n        case 0x00:\n            instance = new MBC0(memory);\n            break;\n        case 0x01: case 0x02: case 0x03:\n            instance = new MBC1(memory);\n            break;\n        case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13:\n            instance = new MBC3(memory);\n            break;\n        case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E:\n            instance = new MBC5(memory);\n            break;\n        default:\n            throw new GameboyJS.UnimplementedException('MBC type not supported');\n    }\n\n    return instance;\n};\n\nvar MBC1 = function(memory) {\n    this.memory = memory;\n    this.romBankNumber = 1;\n    this.mode = 0; // mode 0 = ROM, mode 1 = RAM\n    this.ramEnabled = true;\n    this.extRam = new GameboyJS.ExtRam();\n};\n\nMBC1.prototype.loadRam = function(game, size) {\n    this.extRam.loadRam(game, size);\n};\n\nMBC1.prototype.manageWrite = function(addr, value) {\n    switch (addr & 0xF000) {\n        case 0x0000: case 0x1000: // enable RAM\n            this.ramEnabled = (value & 0x0A) ? true : false;\n            if (this.ramEnabled) {\n                this.extRam.saveRamData();\n            }\n            break;\n        case 0x2000: case 0x3000: // ROM bank number lower 5 bits\n            value &= 0x1F;\n            if (value == 0) value = 1;\n            var mask = this.mode ? 0 : 0xE0;\n            this.romBankNumber = (this.romBankNumber & mask) +value;\n            this.memory.loadRomBank(this.romBankNumber);\n            break;\n        case 0x4000: case 0x5000: // RAM bank or high bits ROM\n            value &= 0x03;\n            if (this.mode == 0) { // ROM upper bits\n                this.romBankNumber = (this.romBankNumber&0x1F) | (value << 5);\n                this.memory.loadRomBank(this.romBankNumber);\n            } else { // RAM bank\n                this.extRam.setRamBank(value);\n            }\n            break;\n        case 0x6000: case 0x7000: // ROM / RAM mode\n            this.mode = value & 1;\n            break;\n        case 0xA000: case 0xB000:\n            this.extRam.manageWrite(addr - 0xA000, value);\n            break;\n    }\n};\nMBC1.prototype.readRam = function(addr) {\n    return this.extRam.manageRead(addr - 0xA000);\n};\n\nvar MBC3 = function(memory) {\n    this.memory = memory;\n    this.romBankNumber = 1;\n    this.ramEnabled = true;\n    this.extRam = new GameboyJS.ExtRam();\n};\n\nMBC3.prototype.loadRam = function(game, size) {\n    this.extRam.loadRam(game, size);\n};\n\nMBC3.prototype.manageWrite = function(addr, value) {\n    switch (addr & 0xF000) {\n        case 0x0000: case 0x1000: // enable RAM\n            this.ramEnabled = (value & 0x0A) ? true : false;\n            if (this.ramEnabled) {\n                this.extRam.saveRamData();\n            }\n            break;\n        case 0x2000: case 0x3000: // ROM bank number\n            value &= 0x7F;\n            if (value == 0) value = 1;\n            this.romBankNumber = value;\n            this.memory.loadRomBank(this.romBankNumber);\n            break;\n        case 0x4000: case 0x5000: // RAM bank\n            this.extRam.setRamBank(value);\n            break;\n        case 0x6000: case 0x7000: // Latch clock data\n            throw new GameboyJS.UnimplementedException('cartridge clock not supported', false);\n            break;\n        case 0xA000: case 0xB000:\n            this.extRam.manageWrite(addr - 0xA000, value);\n            break;\n    }\n};\nMBC3.prototype.readRam = function(addr) {\n    return this.extRam.manageRead(addr - 0xA000);\n};\n\n// declare MBC5 for compatibility with most cartriges\n// does not support rumble feature\nvar MBC5 = MBC3;\n\n// MBC0 exists for consistency and manages the no-MBC cartriges\nvar MBC0 = function(memory) {this.memory = memory;};\n\nMBC0.prototype.manageWrite = function(addr, value) {\n    this.memory.loadRomBank(value);\n};\nMBC0.prototype.readRam = function(addr) {return 0;};\nMBC0.prototype.loadRam = function() {};\n\nGameboyJS.MBC = MBC;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Memory unit\nvar Memory = function(cpu) {\n    this.MEM_SIZE = 65536; // 64KB\n\n    this.MBCtype = 0;\n    this.banksize = 0x4000;\n    this.rom = null;\n    this.mbc = null;\n    this.cpu = cpu;\n};\n\nMemory.addresses = {\n    VRAM_START : 0x8000,\n    VRAM_END   : 0x9FFF,\n\n    EXTRAM_START : 0xA000,\n    EXTRAM_END   : 0xBFFF,\n\n    OAM_START : 0xFE00,\n    OAM_END   : 0xFE9F,\n\n    DEVICE_START: 0xFF00,\n    DEVICE_END:   0xFF7F\n};\n\n// Memory can be accessed as an Array\nMemory.prototype = new Array();\n\nMemory.prototype.reset = function() {\n    this.length = this.MEM_SIZE;\n    for (var i = Memory.addresses.VRAM_START; i <= Memory.addresses.VRAM_END; i++) {\n        this[i] = 0;\n    }\n    for (var i = Memory.addresses.DEVICE_START; i <= Memory.addresses.DEVICE_END; i++) {\n        this[i] = 0;\n    }\n    this[0xFFFF] = 0;\n};\n\nMemory.prototype.setRomData = function(data) {\n    this.rom = data;\n    this.loadRomBank(0);\n    this.mbc = GameboyJS.MBC.getMbcInstance(this, this[0x147]);\n    this.loadRomBank(1);\n    this.mbc.loadRam(this.cpu.getGameName(), this.cpu.getRamSize());\n};\n\nMemory.prototype.loadRomBank = function(index) {\n    var start = index ? 0x4000 : 0x0;\n    var romStart = index * 0x4000;\n    for (var i = 0; i < this.banksize; i++) {\n        this[i + start] = this.rom[romStart + i];\n    }\n};\n\n// Video ram accessor\nMemory.prototype.vram = function(address) {\n    if (address < Memory.addresses.VRAM_START || address > Memory.addresses.VRAM_END) {\n        throw 'VRAM access in out of bounds address ' + address;\n    }\n\n    return this[address];\n};\n\n// OAM ram accessor\nMemory.prototype.oamram = function(address) {\n    if (address < Memory.addresses.OAM_START || address > Memory.addresses.OAM_END) {\n        throw 'OAMRAM access in out of bounds address ' + address;\n    }\n\n    return this[address];\n};\n\n// Device ram accessor\nMemory.prototype.deviceram = function(address, value) {\n    if (address < Memory.addresses.DEVICERAM_START || address > Memory.addresses.DEVICERAM_END) {\n        throw 'Device RAM access in out of bounds address ' + address;\n    }\n    if (typeof value === \"undefined\") {\n        return this[address];\n    } else {\n        this[address] = value;\n    }\n\n};\n\n// Memory read proxy function\n// Used to centralize memory read access\nMemory.prototype.rb = function (addr) {\n    if (addr >= 0xFF10 && addr < 0xFF40) {\n        var mask = apuMask[addr - 0xFF10];\n        return this[addr] | mask;\n    }\n    if ((addr >= 0xA000 && addr < 0xC000)) {\n        return this.mbc.readRam(addr);\n    }\n    return this[addr];\n};\n\n// Bitmasks for audio addresses reads\nvar apuMask = [\n0x80,0x3F,0x00,0xFF,0xBF, // NR10-NR15\n0xFF,0x3F,0x00,0xFF,0xBF, // NR20-NR25\n0x7F,0xFF,0x9F,0xFF,0xBF, // NR30-NR35\n0xFF,0xFF,0x00,0x00,0xBF, // NR40-NR45\n0x00,0x00,0x70,           // NR50-NR52\n0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,\n0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Wave RAM\n0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00\n];\n\n// Memory write proxy function\n// Used to centralize memory writes and delegate specific behaviour\n// to the correct units\nMemory.prototype.wb = function(addr, value) {\n    if (addr < 0x8000 || (addr >= 0xA000 && addr < 0xC000)) { // MBC\n        this.mbc.manageWrite(addr, value);\n    } else if (addr >= 0xFF10 && addr <= 0xFF3F) { // sound registers\n        this.cpu.apu.manageWrite(addr, value);\n    } else if (addr == 0xFF00) { // input register\n        this[addr] = ((this[addr] & 0x0F) | (value & 0x30));\n    } else {\n        this[addr] = value;\n        if ((addr & 0xFF00) == 0xFF00) {\n            if (addr == 0xFF02) {\n                if (value & 0x80) {\n                    this.cpu.enableSerialTransfer();\n                }\n            }\n            if (addr == 0xFF04) {\n                this.cpu.resetDivTimer();\n            }\n            if (addr == 0xFF46) { // OAM DMA transfer\n                this.dmaTransfer(value);\n            }\n        }\n    }\n}\n\n// Start a DMA transfer (OAM data from cartrige to RAM)\nMemory.prototype.dmaTransfer = function(startAddressPrefix) {\n    var startAddress = (startAddressPrefix << 8);\n    for (var i = 0; i < 0xA0; i++) {\n        this[Memory.addresses.OAM_START + i] = this[startAddress + i];\n    }\n};\n\nGameboyJS.Memory = Memory;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\nvar ops = GameboyJS.cpuOps;\n// Each opcode (0 to 0xFF) is associated to a CPU operation\n// CPU operations are implemented separately\n// The cbmap object holds operations for CB prefixed opcodes (0xCB00 to 0xCBFF)\n// Non existent opcodes are commented out and marked empty\nvar map = {\n    0x00: function(p){p.clock.c += 4;},\n    0x01: function(p){ops.LDrrnn(p, 'B', 'C');},\n    0x02: function(p){ops.LDrrar(p, 'B', 'C', 'A');},\n    0x03: function(p){ops.INCrr(p, 'B', 'C');},\n    0x04: function(p){ops.INCr(p, 'B');},\n    0x05: function(p){ops.DECr(p, 'B');},\n    0x06: function(p){ops.LDrn(p, 'B');},\n    0x07: function(p){var out=p.r.A & 0x80?1:0; out ? p.r.F=0x10:p.r.F=0; p.wr('A', ((p.r.A<<1)+out)&0xFF);p.clock.c+=4;},\n    0x08: function(p){ops.LDnnsp(p);},\n    0x09: function(p){ops.ADDrrrr(p, 'H', 'L', 'B', 'C');},\n    0x0A: function(p){ops.LDrrra(p, 'A', 'B', 'C');},\n    0x0B: function(p){ops.DECrr(p, 'B', 'C');},\n    0x0C: function(p){ops.INCr(p, 'C');},\n    0x0D: function(p){ops.DECr(p, 'C');},\n    0x0E: function(p){ops.LDrn(p, 'C');},\n    0x0F: function(p){var out=p.r.A & 0x01; out ? p.r.F=0x10:p.r.F=0; p.wr('A', (p.r.A>>1)|(out*0x80));p.clock.c+=4;},\n\n    0x10: function(p){p.r.pc++;p.clock.c+=4;},\n    0x11: function(p){ops.LDrrnn(p, 'D', 'E');},\n    0x12: function(p){ops.LDrrar(p, 'D', 'E', 'A');},\n    0x13: function(p){ops.INCrr(p, 'D', 'E');},\n    0x14: function(p){ops.INCr(p, 'D');},\n    0x15: function(p){ops.DECr(p, 'D');},\n    0x16: function(p){ops.LDrn(p, 'D');},\n    0x17: function(p){var c = (p.r.F&0x10)?1:0;var out=p.r.A & 0x80?1:0; out ? p.r.F=0x10:p.r.F=0; p.wr('A',((p.r.A<<1)+c)&0xFF);p.clock.c+=4;},\n    0x18: function(p){ops.JRn(p);},\n    0x19: function(p){ops.ADDrrrr(p, 'H', 'L', 'D', 'E');},\n    0x1A: function(p){ops.LDrrra(p, 'A', 'D', 'E');},\n    0x1B: function(p){ops.DECrr(p, 'D', 'E');},\n    0x1C: function(p){ops.INCr(p, 'E');},\n    0x1D: function(p){ops.DECr(p, 'E');},\n    0x1E: function(p){ops.LDrn(p, 'E');},\n    0x1F: function(p){var c = (p.r.F&0x10)?1:0;var out=p.r.A & 0x01; out ? p.r.F=0x10:p.r.F=0; p.wr('A', (p.r.A>>1)|(c*0x80));p.clock.c+=4;},\n\n    0x20: function(p){ops.JRccn(p, 'NZ');},\n    0x21: function(p){ops.LDrrnn(p, 'H', 'L');},\n    0x22: function(p){ops.LDrrar(p, 'H', 'L', 'A');ops.INCrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x23: function(p){ops.INCrr(p, 'H', 'L');},\n    0x24: function(p){ops.INCr(p, 'H');},\n    0x25: function(p){ops.DECr(p, 'H');},\n    0x26: function(p){ops.LDrn(p, 'H');},\n    0x27: function(p){ops.DAA(p);},\n    0x28: function(p){ops.JRccn(p, 'Z');},\n    0x29: function(p){ops.ADDrrrr(p, 'H', 'L', 'H', 'L');},\n    0x2A: function(p){ops.LDrrra(p, 'A', 'H', 'L');ops.INCrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x2B: function(p){ops.DECrr(p, 'H', 'L');},\n    0x2C: function(p){ops.INCr(p, 'L');},\n    0x2D: function(p){ops.DECr(p, 'L');},\n    0x2E: function(p){ops.LDrn(p, 'L');},\n    0x2F: function(p){ops.CPL(p);},\n\n    0x30: function(p){ops.JRccn(p, 'NC');},\n    0x31: function(p){ops.LDspnn(p);},\n    0x32: function(p){ops.LDrrar(p, 'H', 'L', 'A');ops.DECrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x33: function(p){ops.INCsp(p);},\n    0x34: function(p){ops.INCrra(p, 'H', 'L');},\n    0x35: function(p){ops.DECrra(p, 'H', 'L');},\n    0x36: function(p){ops.LDrran(p, 'H', 'L');},\n    0x37: function(p){ops.SCF(p);},\n    0x38: function(p){ops.JRccn(p, 'C');},\n    0x39: function(p){ops.ADDrrsp(p, 'H', 'L');},\n    0x3A: function(p){ops.LDrrra(p, 'A', 'H', 'L');ops.DECrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x3B: function(p){ops.DECsp(p);},\n    0x3C: function(p){ops.INCr(p, 'A');},\n    0x3D: function(p){ops.DECr(p, 'A');},\n    0x3E: function(p){ops.LDrn(p, 'A');},\n    0x3F: function(p){ops.CCF(p);},\n\n    0x40: function(p){ops.LDrr(p, 'B', 'B');},\n    0x41: function(p){ops.LDrr(p, 'B', 'C');},\n    0x42: function(p){ops.LDrr(p, 'B', 'D');},\n    0x43: function(p){ops.LDrr(p, 'B', 'E');},\n    0x44: function(p){ops.LDrr(p, 'B', 'H');},\n    0x45: function(p){ops.LDrr(p, 'B', 'L');},\n    0x46: function(p){ops.LDrrra(p, 'B', 'H', 'L');},\n    0x47: function(p){ops.LDrr(p, 'B', 'A');},\n    0x48: function(p){ops.LDrr(p, 'C', 'B');},\n    0x49: function(p){ops.LDrr(p, 'C', 'C');},\n    0x4A: function(p){ops.LDrr(p, 'C', 'D');},\n    0x4B: function(p){ops.LDrr(p, 'C', 'E');},\n    0x4C: function(p){ops.LDrr(p, 'C', 'H');},\n    0x4D: function(p){ops.LDrr(p, 'C', 'L');},\n    0x4E: function(p){ops.LDrrra(p, 'C', 'H', 'L');},\n    0x4F: function(p){ops.LDrr(p, 'C', 'A');},\n\n    0x50: function(p){ops.LDrr(p, 'D', 'B');},\n    0x51: function(p){ops.LDrr(p, 'D', 'C');},\n    0x52: function(p){ops.LDrr(p, 'D', 'D');},\n    0x53: function(p){ops.LDrr(p, 'D', 'E');},\n    0x54: function(p){ops.LDrr(p, 'D', 'H');},\n    0x55: function(p){ops.LDrr(p, 'D', 'L');},\n    0x56: function(p){ops.LDrrra(p, 'D', 'H', 'L');},\n    0x57: function(p){ops.LDrr(p, 'D', 'A');},\n    0x58: function(p){ops.LDrr(p, 'E', 'B');},\n    0x59: function(p){ops.LDrr(p, 'E', 'C');},\n    0x5A: function(p){ops.LDrr(p, 'E', 'D');},\n    0x5B: function(p){ops.LDrr(p, 'E', 'E');},\n    0x5C: function(p){ops.LDrr(p, 'E', 'H');},\n    0x5D: function(p){ops.LDrr(p, 'E', 'L');},\n    0x5E: function(p){ops.LDrrra(p, 'E', 'H', 'L');},\n    0x5F: function(p){ops.LDrr(p, 'E', 'A');},\n\n    0x60: function(p){ops.LDrr(p, 'H', 'B');},\n    0x61: function(p){ops.LDrr(p, 'H', 'C');},\n    0x62: function(p){ops.LDrr(p, 'H', 'D');},\n    0x63: function(p){ops.LDrr(p, 'H', 'E');},\n    0x64: function(p){ops.LDrr(p, 'H', 'H');},\n    0x65: function(p){ops.LDrr(p, 'H', 'L');},\n    0x66: function(p){ops.LDrrra(p, 'H', 'H', 'L');},\n    0x67: function(p){ops.LDrr(p, 'H', 'A');},\n    0x68: function(p){ops.LDrr(p, 'L', 'B');},\n    0x69: function(p){ops.LDrr(p, 'L', 'C');},\n    0x6A: function(p){ops.LDrr(p, 'L', 'D');},\n    0x6B: function(p){ops.LDrr(p, 'L', 'E');},\n    0x6C: function(p){ops.LDrr(p, 'L', 'H');},\n    0x6D: function(p){ops.LDrr(p, 'L', 'L');},\n    0x6E: function(p){ops.LDrrra(p, 'L', 'H', 'L');},\n    0x6F: function(p){ops.LDrr(p, 'L', 'A');},\n\n    0x70: function(p){ops.LDrrar(p, 'H', 'L', 'B');},\n    0x71: function(p){ops.LDrrar(p, 'H', 'L', 'C');},\n    0x72: function(p){ops.LDrrar(p, 'H', 'L', 'D');},\n    0x73: function(p){ops.LDrrar(p, 'H', 'L', 'E');},\n    0x74: function(p){ops.LDrrar(p, 'H', 'L', 'H');},\n    0x75: function(p){ops.LDrrar(p, 'H', 'L', 'L');},\n    0x76: function(p){ops.HALT(p);},\n    0x77: function(p){ops.LDrrar(p, 'H', 'L', 'A');},\n    0x78: function(p){ops.LDrr(p, 'A', 'B');},\n    0x79: function(p){ops.LDrr(p, 'A', 'C');},\n    0x7A: function(p){ops.LDrr(p, 'A', 'D');},\n    0x7B: function(p){ops.LDrr(p, 'A', 'E');},\n    0x7C: function(p){ops.LDrr(p, 'A', 'H');},\n    0x7D: function(p){ops.LDrr(p, 'A', 'L');},\n    0x7E: function(p){ops.LDrrra(p, 'A', 'H', 'L');},\n    0x7F: function(p){ops.LDrr(p, 'A', 'A');},\n\n    0x80: function(p){ops.ADDrr(p, 'A', 'B');},\n    0x81: function(p){ops.ADDrr(p, 'A', 'C');},\n    0x82: function(p){ops.ADDrr(p, 'A', 'D');},\n    0x83: function(p){ops.ADDrr(p, 'A', 'E');},\n    0x84: function(p){ops.ADDrr(p, 'A', 'H');},\n    0x85: function(p){ops.ADDrr(p, 'A', 'L');},\n    0x86: function(p){ops.ADDrrra(p, 'A', 'H', 'L');},\n    0x87: function(p){ops.ADDrr(p, 'A', 'A');},\n    0x88: function(p){ops.ADCrr(p, 'A', 'B');},\n    0x89: function(p){ops.ADCrr(p, 'A', 'C');},\n    0x8A: function(p){ops.ADCrr(p, 'A', 'D');},\n    0x8B: function(p){ops.ADCrr(p, 'A', 'E');},\n    0x8C: function(p){ops.ADCrr(p, 'A', 'H');},\n    0x8D: function(p){ops.ADCrr(p, 'A', 'L');},\n    0x8E: function(p){ops.ADCrrra(p, 'A', 'H', 'L');},\n    0x8F: function(p){ops.ADCrr(p, 'A', 'A');},\n\n    0x90: function(p){ops.SUBr(p, 'B');},\n    0x91: function(p){ops.SUBr(p, 'C');},\n    0x92: function(p){ops.SUBr(p, 'D');},\n    0x93: function(p){ops.SUBr(p, 'E');},\n    0x94: function(p){ops.SUBr(p, 'H');},\n    0x95: function(p){ops.SUBr(p, 'L');},\n    0x96: function(p){ops.SUBrra(p, 'H', 'L');},\n    0x97: function(p){ops.SUBr(p, 'A');},\n    0x98: function(p){ops.SBCr(p, 'B');},\n    0x99: function(p){ops.SBCr(p, 'C');},\n    0x9A: function(p){ops.SBCr(p, 'D');},\n    0x9B: function(p){ops.SBCr(p, 'E');},\n    0x9C: function(p){ops.SBCr(p, 'H');},\n    0x9D: function(p){ops.SBCr(p, 'L');},\n    0x9E: function(p){ops.SBCrra(p, 'H', 'L');},\n    0x9F: function(p){ops.SBCr(p, 'A');},\n\n    0xA0: function(p){ops.ANDr(p, 'B');},\n    0xA1: function(p){ops.ANDr(p, 'C');},\n    0xA2: function(p){ops.ANDr(p, 'D');},\n    0xA3: function(p){ops.ANDr(p, 'E');},\n    0xA4: function(p){ops.ANDr(p, 'H');},\n    0xA5: function(p){ops.ANDr(p, 'L');},\n    0xA6: function(p){ops.ANDrra(p, 'H', 'L');},\n    0xA7: function(p){ops.ANDr(p, 'A');},\n    0xA8: function(p){ops.XORr(p, 'B');},\n    0xA9: function(p){ops.XORr(p, 'C');},\n    0xAA: function(p){ops.XORr(p, 'D');},\n    0xAB: function(p){ops.XORr(p, 'E');},\n    0xAC: function(p){ops.XORr(p, 'H');},\n    0xAD: function(p){ops.XORr(p, 'L');},\n    0xAE: function(p){ops.XORrra(p, 'H', 'L');},\n    0xAF: function(p){ops.XORr(p, 'A');},\n\n    0xB0: function(p){ops.ORr(p, 'B');},\n    0xB1: function(p){ops.ORr(p, 'C');},\n    0xB2: function(p){ops.ORr(p, 'D');},\n    0xB3: function(p){ops.ORr(p, 'E');},\n    0xB4: function(p){ops.ORr(p, 'H');},\n    0xB5: function(p){ops.ORr(p, 'L');},\n    0xB6: function(p){ops.ORrra(p, 'H', 'L');},\n    0xB7: function(p){ops.ORr(p, 'A');},\n    0xB8: function(p){ops.CPr(p, 'B');},\n    0xB9: function(p){ops.CPr(p, 'C');},\n    0xBA: function(p){ops.CPr(p, 'D');},\n    0xBB: function(p){ops.CPr(p, 'E');},\n    0xBC: function(p){ops.CPr(p, 'H');},\n    0xBD: function(p){ops.CPr(p, 'L');},\n    0xBE: function(p){ops.CPrra(p, 'H', 'L');},\n    0xBF: function(p){ops.CPr(p, 'A');},\n\n    0xC0: function(p){ops.RETcc(p, 'NZ');},\n    0xC1: function(p){ops.POPrr(p, 'B', 'C');},\n    0xC2: function(p){ops.JPccnn(p, 'NZ');},\n    0xC3: function(p){ops.JPnn(p);},\n    0xC4: function(p){ops.CALLccnn(p, 'NZ');},\n    0xC5: function(p){ops.PUSHrr(p, 'B', 'C');},\n    0xC6: function(p){ops.ADDrn(p, 'A');},\n    0xC7: function(p){ops.RSTn(p, 0x00);},\n    0xC8: function(p){ops.RETcc(p, 'Z');},\n    0xC9: function(p){ops.RET(p);},\n    0xCA: function(p){ops.JPccnn(p, 'Z');},\n    0xCB: function(p){ops.CB(p);},\n    0xCC: function(p){ops.CALLccnn(p, 'Z');},\n    0xCD: function(p){ops.CALLnn(p);},\n    0xCE: function(p){ops.ADCrn(p, 'A');},\n    0xCF: function(p){ops.RSTn(p, 0x08);},\n\n    0xD0: function(p){ops.RETcc(p, 'NC');},\n    0xD1: function(p){ops.POPrr(p, 'D', 'E');},\n    0xD2: function(p){ops.JPccnn(p, 'NC');},\n    //0xD3 empty\n    0xD4: function(p){ops.CALLccnn(p, 'NC');},\n    0xD5: function(p){ops.PUSHrr(p, 'D', 'E');},\n    0xD6: function(p){ops.SUBn(p);},\n    0xD7: function(p){ops.RSTn(p, 0x10);},\n    0xD8: function(p){ops.RETcc(p, 'C');},\n    0xD9: function(p){ops.RETI(p);},\n    0xDA: function(p){ops.JPccnn(p, 'C');},\n    //0xDB empty\n    0xDC: function(p){ops.CALLccnn(p, 'C');},\n    //0xDD empty\n    0xDE: function(p){ops.SBCn(p);},\n    0xDF: function(p){ops.RSTn(p, 0x18);},\n\n    0xE0: function(p){ops.LDHnar(p, 'A');},\n    0xE1: function(p){ops.POPrr(p, 'H', 'L');},\n    0xE2: function(p){ops.LDrar(p, 'C', 'A');},\n    //0xE3 empty\n    //0xE4 empty\n    0xE5: function(p){ops.PUSHrr(p, 'H', 'L');},\n    0xE6: function(p){ops.ANDn(p);},\n    0xE7: function(p){ops.RSTn(p, 0x20);},\n    0xE8: function(p){ops.ADDspn(p);},\n    0xE9: function(p){ops.JPrr(p, 'H', 'L');},\n    0xEA: function(p){ops.LDnnar(p, 'A');},\n    //0xEB empty\n    //0xEC empty\n    //0xED empty\n    0xEE: function(p){ops.XORn(p);},\n    0xEF: function(p){ops.RSTn(p, 0x28);},\n\n    0xF0: function(p){ops.LDHrna(p, 'A');},\n    0xF1: function(p){ops.POPrr(p, 'A', 'F');},\n    0xF2: function(p){ops.LDrra(p, 'A', 'C');},\n    0xF3: function(p){ops.DI(p);},\n    //0xF4 empty\n    0xF5: function(p){ops.PUSHrr(p, 'A', 'F');},\n    0xF6: function(p){ops.ORn(p);},\n    0xF7: function(p){ops.RSTn(p, 0x30);},\n    0xF8: function(p){ops.LDrrspn(p, 'H', 'L');},\n    0xF9: function(p){ops.LDsprr(p, 'H', 'L');},\n    0xFA: function(p){ops.LDrnna(p, 'A');},\n    0xFB: function(p){ops.EI(p);},\n    //0xFC empty\n    //0xFD empty\n    0xFE: function(p){ops.CPn(p);},\n    0xFF: function(p){ops.RSTn(p, 0x38);}\n};\n\nvar cbmap = {\n    0x00: function(p){ops.RLCr(p, 'B');},\n    0x01: function(p){ops.RLCr(p, 'C');},\n    0x02: function(p){ops.RLCr(p, 'D');},\n    0x03: function(p){ops.RLCr(p, 'E');},\n    0x04: function(p){ops.RLCr(p, 'H');},\n    0x05: function(p){ops.RLCr(p, 'L');},\n    0x06: function(p){ops.RLCrra(p, 'H', 'L');},\n    0x07: function(p){ops.RLCr(p, 'A');},\n    0x08: function(p){ops.RRCr(p, 'B');},\n    0x09: function(p){ops.RRCr(p, 'C');},\n    0x0A: function(p){ops.RRCr(p, 'D');},\n    0x0B: function(p){ops.RRCr(p, 'E');},\n    0x0C: function(p){ops.RRCr(p, 'H');},\n    0x0D: function(p){ops.RRCr(p, 'L');},\n    0x0E: function(p){ops.RRCrra(p, 'H', 'L');},\n    0x0F: function(p){ops.RRCr(p, 'A');},\n\n    0x10: function(p){ops.RLr(p, 'B');},\n    0x11: function(p){ops.RLr(p, 'C');},\n    0x12: function(p){ops.RLr(p, 'D');},\n    0x13: function(p){ops.RLr(p, 'E');},\n    0x14: function(p){ops.RLr(p, 'H');},\n    0x15: function(p){ops.RLr(p, 'L');},\n    0x16: function(p){ops.RLrra(p, 'H', 'L');},\n    0x17: function(p){ops.RLr(p, 'A');},\n    0x18: function(p){ops.RRr(p, 'B');},\n    0x19: function(p){ops.RRr(p, 'C');},\n    0x1A: function(p){ops.RRr(p, 'D');},\n    0x1B: function(p){ops.RRr(p, 'E');},\n    0x1C: function(p){ops.RRr(p, 'H');},\n    0x1D: function(p){ops.RRr(p, 'L');},\n    0x1E: function(p){ops.RRrra(p, 'H', 'L');},\n    0x1F: function(p){ops.RRr(p, 'A');},\n\n    0x20: function(p){ops.SLAr(p, 'B');},\n    0x21: function(p){ops.SLAr(p, 'C');},\n    0x22: function(p){ops.SLAr(p, 'D');},\n    0x23: function(p){ops.SLAr(p, 'E');},\n    0x24: function(p){ops.SLAr(p, 'H');},\n    0x25: function(p){ops.SLAr(p, 'L');},\n    0x26: function(p){ops.SLArra(p, 'H', 'L');},\n    0x27: function(p){ops.SLAr(p, 'A');},\n    0x28: function(p){ops.SRAr(p, 'B');},\n    0x29: function(p){ops.SRAr(p, 'C');},\n    0x2A: function(p){ops.SRAr(p, 'D');},\n    0x2B: function(p){ops.SRAr(p, 'E');},\n    0x2C: function(p){ops.SRAr(p, 'H');},\n    0x2D: function(p){ops.SRAr(p, 'L');},\n    0x2E: function(p){ops.SRArra(p, 'H', 'L');},\n    0x2F: function(p){ops.SRAr(p, 'A');},\n\n    0x30: function(p){ops.SWAPr(p, 'B');},\n    0x31: function(p){ops.SWAPr(p, 'C');},\n    0x32: function(p){ops.SWAPr(p, 'D');},\n    0x33: function(p){ops.SWAPr(p, 'E');},\n    0x34: function(p){ops.SWAPr(p, 'H');},\n    0x35: function(p){ops.SWAPr(p, 'L');},\n    0x36: function(p){ops.SWAPrra(p, 'H', 'L');},\n    0x37: function(p){ops.SWAPr(p, 'A');},\n    0x38: function(p){ops.SRLr(p, 'B');},\n    0x39: function(p){ops.SRLr(p, 'C');},\n    0x3A: function(p){ops.SRLr(p, 'D');},\n    0x3B: function(p){ops.SRLr(p, 'E');},\n    0x3C: function(p){ops.SRLr(p, 'H');},\n    0x3D: function(p){ops.SRLr(p, 'L');},\n    0x3E: function(p){ops.SRLrra(p, 'H', 'L');},\n    0x3F: function(p){ops.SRLr(p, 'A');},\n\n    0x40: function(p){ops.BITir(p, 0, 'B');},\n    0x41: function(p){ops.BITir(p, 0, 'C');},\n    0x42: function(p){ops.BITir(p, 0, 'D');},\n    0x43: function(p){ops.BITir(p, 0, 'E');},\n    0x44: function(p){ops.BITir(p, 0, 'H');},\n    0x45: function(p){ops.BITir(p, 0, 'L');},\n    0x46: function(p){ops.BITirra(p, 0, 'H', 'L');},\n    0x47: function(p){ops.BITir(p, 0, 'A');},\n    0x48: function(p){ops.BITir(p, 1, 'B');},\n    0x49: function(p){ops.BITir(p, 1, 'C');},\n    0x4A: function(p){ops.BITir(p, 1, 'D');},\n    0x4B: function(p){ops.BITir(p, 1, 'E');},\n    0x4C: function(p){ops.BITir(p, 1, 'H');},\n    0x4D: function(p){ops.BITir(p, 1, 'L');},\n    0x4E: function(p){ops.BITirra(p, 1, 'H', 'L');},\n    0x4F: function(p){ops.BITir(p, 1, 'A');},\n\n    0x50: function(p){ops.BITir(p, 2, 'B');},\n    0x51: function(p){ops.BITir(p, 2, 'C');},\n    0x52: function(p){ops.BITir(p, 2, 'D');},\n    0x53: function(p){ops.BITir(p, 2, 'E');},\n    0x54: function(p){ops.BITir(p, 2, 'H');},\n    0x55: function(p){ops.BITir(p, 2, 'L');},\n    0x56: function(p){ops.BITirra(p, 2, 'H', 'L');},\n    0x57: function(p){ops.BITir(p, 2, 'A');},\n    0x58: function(p){ops.BITir(p, 3, 'B');},\n    0x59: function(p){ops.BITir(p, 3, 'C');},\n    0x5A: function(p){ops.BITir(p, 3, 'D');},\n    0x5B: function(p){ops.BITir(p, 3, 'E');},\n    0x5C: function(p){ops.BITir(p, 3, 'H');},\n    0x5D: function(p){ops.BITir(p, 3, 'L');},\n    0x5E: function(p){ops.BITirra(p, 3, 'H', 'L');},\n    0x5F: function(p){ops.BITir(p, 3, 'A');},\n\n    0x60: function(p){ops.BITir(p, 4, 'B');},\n    0x61: function(p){ops.BITir(p, 4, 'C');},\n    0x62: function(p){ops.BITir(p, 4, 'D');},\n    0x63: function(p){ops.BITir(p, 4, 'E');},\n    0x64: function(p){ops.BITir(p, 4, 'H');},\n    0x65: function(p){ops.BITir(p, 4, 'L');},\n    0x66: function(p){ops.BITirra(p, 4, 'H', 'L');},\n    0x67: function(p){ops.BITir(p, 4, 'A');},\n    0x68: function(p){ops.BITir(p, 5, 'B');},\n    0x69: function(p){ops.BITir(p, 5, 'C');},\n    0x6A: function(p){ops.BITir(p, 5, 'D');},\n    0x6B: function(p){ops.BITir(p, 5, 'E');},\n    0x6C: function(p){ops.BITir(p, 5, 'H');},\n    0x6D: function(p){ops.BITir(p, 5, 'L');},\n    0x6E: function(p){ops.BITirra(p, 5, 'H', 'L');},\n    0x6F: function(p){ops.BITir(p, 5, 'A');},\n\n    0x70: function(p){ops.BITir(p, 6, 'B');},\n    0x71: function(p){ops.BITir(p, 6, 'C');},\n    0x72: function(p){ops.BITir(p, 6, 'D');},\n    0x73: function(p){ops.BITir(p, 6, 'E');},\n    0x74: function(p){ops.BITir(p, 6, 'H');},\n    0x75: function(p){ops.BITir(p, 6, 'L');},\n    0x76: function(p){ops.BITirra(p, 6, 'H', 'L');},\n    0x77: function(p){ops.BITir(p, 6, 'A');},\n    0x78: function(p){ops.BITir(p, 7, 'B');},\n    0x79: function(p){ops.BITir(p, 7, 'C');},\n    0x7A: function(p){ops.BITir(p, 7, 'D');},\n    0x7B: function(p){ops.BITir(p, 7, 'E');},\n    0x7C: function(p){ops.BITir(p, 7, 'H');},\n    0x7D: function(p){ops.BITir(p, 7, 'L');},\n    0x7E: function(p){ops.BITirra(p, 7, 'H', 'L');},\n    0x7F: function(p){ops.BITir(p, 7, 'A');},\n\n    0x80: function(p){ops.RESir(p, 0, 'B');},\n    0x81: function(p){ops.RESir(p, 0, 'C');},\n    0x82: function(p){ops.RESir(p, 0, 'D');},\n    0x83: function(p){ops.RESir(p, 0, 'E');},\n    0x84: function(p){ops.RESir(p, 0, 'H');},\n    0x85: function(p){ops.RESir(p, 0, 'L');},\n    0x86: function(p){ops.RESirra(p, 0, 'H', 'L');},\n    0x87: function(p){ops.RESir(p, 0, 'A');},\n    0x88: function(p){ops.RESir(p, 1, 'B');},\n    0x89: function(p){ops.RESir(p, 1, 'C');},\n    0x8A: function(p){ops.RESir(p, 1, 'D');},\n    0x8B: function(p){ops.RESir(p, 1, 'E');},\n    0x8C: function(p){ops.RESir(p, 1, 'H');},\n    0x8D: function(p){ops.RESir(p, 1, 'L');},\n    0x8E: function(p){ops.RESirra(p, 1, 'H', 'L');},\n    0x8F: function(p){ops.RESir(p, 1, 'A');},\n\n    0x90: function(p){ops.RESir(p, 2, 'B');},\n    0x91: function(p){ops.RESir(p, 2, 'C');},\n    0x92: function(p){ops.RESir(p, 2, 'D');},\n    0x93: function(p){ops.RESir(p, 2, 'E');},\n    0x94: function(p){ops.RESir(p, 2, 'H');},\n    0x95: function(p){ops.RESir(p, 2, 'L');},\n    0x96: function(p){ops.RESirra(p, 2, 'H', 'L');},\n    0x97: function(p){ops.RESir(p, 2, 'A');},\n    0x98: function(p){ops.RESir(p, 3, 'B');},\n    0x99: function(p){ops.RESir(p, 3, 'C');},\n    0x9A: function(p){ops.RESir(p, 3, 'D');},\n    0x9B: function(p){ops.RESir(p, 3, 'E');},\n    0x9C: function(p){ops.RESir(p, 3, 'H');},\n    0x9D: function(p){ops.RESir(p, 3, 'L');},\n    0x9E: function(p){ops.RESirra(p, 3, 'H', 'L');},\n    0x9F: function(p){ops.RESir(p, 3, 'A');},\n\n    0xA0: function(p){ops.RESir(p, 4, 'B');},\n    0xA1: function(p){ops.RESir(p, 4, 'C');},\n    0xA2: function(p){ops.RESir(p, 4, 'D');},\n    0xA3: function(p){ops.RESir(p, 4, 'E');},\n    0xA4: function(p){ops.RESir(p, 4, 'H');},\n    0xA5: function(p){ops.RESir(p, 4, 'L');},\n    0xA6: function(p){ops.RESirra(p, 4, 'H', 'L');},\n    0xA7: function(p){ops.RESir(p, 4, 'A');},\n    0xA8: function(p){ops.RESir(p, 5, 'B');},\n    0xA9: function(p){ops.RESir(p, 5, 'C');},\n    0xAA: function(p){ops.RESir(p, 5, 'D');},\n    0xAB: function(p){ops.RESir(p, 5, 'E');},\n    0xAC: function(p){ops.RESir(p, 5, 'H');},\n    0xAD: function(p){ops.RESir(p, 5, 'L');},\n    0xAE: function(p){ops.RESirra(p, 5, 'H', 'L');},\n    0xAF: function(p){ops.RESir(p, 5, 'A');},\n\n    0xB0: function(p){ops.RESir(p, 6, 'B');},\n    0xB1: function(p){ops.RESir(p, 6, 'C');},\n    0xB2: function(p){ops.RESir(p, 6, 'D');},\n    0xB3: function(p){ops.RESir(p, 6, 'E');},\n    0xB4: function(p){ops.RESir(p, 6, 'H');},\n    0xB5: function(p){ops.RESir(p, 6, 'L');},\n    0xB6: function(p){ops.RESirra(p, 6, 'H', 'L');},\n    0xB7: function(p){ops.RESir(p, 6, 'A');},\n    0xB8: function(p){ops.RESir(p, 7, 'B');},\n    0xB9: function(p){ops.RESir(p, 7, 'C');},\n    0xBA: function(p){ops.RESir(p, 7, 'D');},\n    0xBB: function(p){ops.RESir(p, 7, 'E');},\n    0xBC: function(p){ops.RESir(p, 7, 'H');},\n    0xBD: function(p){ops.RESir(p, 7, 'L');},\n    0xBE: function(p){ops.RESirra(p, 7, 'H', 'L');},\n    0xBF: function(p){ops.RESir(p, 7, 'A');},\n\n    0xC0: function(p){ops.SETir(p, 0, 'B');},\n    0xC1: function(p){ops.SETir(p, 0, 'C');},\n    0xC2: function(p){ops.SETir(p, 0, 'D');},\n    0xC3: function(p){ops.SETir(p, 0, 'E');},\n    0xC4: function(p){ops.SETir(p, 0, 'H');},\n    0xC5: function(p){ops.SETir(p, 0, 'L');},\n    0xC6: function(p){ops.SETirra(p, 0, 'H', 'L');},\n    0xC7: function(p){ops.SETir(p, 0, 'A');},\n    0xC8: function(p){ops.SETir(p, 1, 'B');},\n    0xC9: function(p){ops.SETir(p, 1, 'C');},\n    0xCA: function(p){ops.SETir(p, 1, 'D');},\n    0xCB: function(p){ops.SETir(p, 1, 'E');},\n    0xCC: function(p){ops.SETir(p, 1, 'H');},\n    0xCD: function(p){ops.SETir(p, 1, 'L');},\n    0xCE: function(p){ops.SETirra(p, 1, 'H', 'L');},\n    0xCF: function(p){ops.SETir(p, 1, 'A');},\n\n    0xD0: function(p){ops.SETir(p, 2, 'B');},\n    0xD1: function(p){ops.SETir(p, 2, 'C');},\n    0xD2: function(p){ops.SETir(p, 2, 'D');},\n    0xD3: function(p){ops.SETir(p, 2, 'E');},\n    0xD4: function(p){ops.SETir(p, 2, 'H');},\n    0xD5: function(p){ops.SETir(p, 2, 'L');},\n    0xD6: function(p){ops.SETirra(p, 2, 'H', 'L');},\n    0xD7: function(p){ops.SETir(p, 2, 'A');},\n    0xD8: function(p){ops.SETir(p, 3, 'B');},\n    0xD9: function(p){ops.SETir(p, 3, 'C');},\n    0xDA: function(p){ops.SETir(p, 3, 'D');},\n    0xDB: function(p){ops.SETir(p, 3, 'E');},\n    0xDC: function(p){ops.SETir(p, 3, 'H');},\n    0xDD: function(p){ops.SETir(p, 3, 'L');},\n    0xDE: function(p){ops.SETirra(p, 3, 'H', 'L');},\n    0xDF: function(p){ops.SETir(p, 3, 'A');},\n\n    0xE0: function(p){ops.SETir(p, 4, 'B');},\n    0xE1: function(p){ops.SETir(p, 4, 'C');},\n    0xE2: function(p){ops.SETir(p, 4, 'D');},\n    0xE3: function(p){ops.SETir(p, 4, 'E');},\n    0xE4: function(p){ops.SETir(p, 4, 'H');},\n    0xE5: function(p){ops.SETir(p, 4, 'L');},\n    0xE6: function(p){ops.SETirra(p, 4, 'H', 'L');},\n    0xE7: function(p){ops.SETir(p, 4, 'A');},\n    0xE8: function(p){ops.SETir(p, 5, 'B');},\n    0xE9: function(p){ops.SETir(p, 5, 'C');},\n    0xEA: function(p){ops.SETir(p, 5, 'D');},\n    0xEB: function(p){ops.SETir(p, 5, 'E');},\n    0xEC: function(p){ops.SETir(p, 5, 'H');},\n    0xED: function(p){ops.SETir(p, 5, 'L');},\n    0xEE: function(p){ops.SETirra(p, 5, 'H', 'L');},\n    0xEF: function(p){ops.SETir(p, 5, 'A');},\n\n    0xF0: function(p){ops.SETir(p, 6, 'B');},\n    0xF1: function(p){ops.SETir(p, 6, 'C');},\n    0xF2: function(p){ops.SETir(p, 6, 'D');},\n    0xF3: function(p){ops.SETir(p, 6, 'E');},\n    0xF4: function(p){ops.SETir(p, 6, 'H');},\n    0xF5: function(p){ops.SETir(p, 6, 'L');},\n    0xF6: function(p){ops.SETirra(p, 6, 'H', 'L');},\n    0xF7: function(p){ops.SETir(p, 6, 'A');},\n    0xF8: function(p){ops.SETir(p, 7, 'B');},\n    0xF9: function(p){ops.SETir(p, 7, 'C');},\n    0xFA: function(p){ops.SETir(p, 7, 'D');},\n    0xFB: function(p){ops.SETir(p, 7, 'E');},\n    0xFC: function(p){ops.SETir(p, 7, 'H');},\n    0xFD: function(p){ops.SETir(p, 7, 'L');},\n    0xFE: function(p){ops.SETirra(p, 7, 'H', 'L');},\n    0xFF: function(p){ops.SETir(p, 7, 'A');}\n};\nGameboyJS.opcodeMap = map;\nGameboyJS.opcodeCbmap = cbmap;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// A RomAjaxReader is able to load a file through an AJAX request\nvar RomAjaxReader = function() {\n\n};\n\n// The callback argument will be called when a file is successfully\n// read, with the data as argument (Uint8Array)\nRomAjaxReader.prototype.setCallback = function(onLoadCallback) {\n    this.callback = onLoadCallback;\n};\n\n// This function should be called by application code\n// and will trigger the AJAX call itself and push data to the ROM object\nRomAjaxReader.prototype.loadFromUrl = function(url) {\n    if (!url) {\n        throw 'No url has been set in order to load a ROM file.';\n    }\n    var cb = this.callback;\n\n    var xhr = new XMLHttpRequest();\n    xhr.open('GET', url, true);\n    xhr.responseType = \"arraybuffer\";\n    xhr.onload = function() {\n        var rom = new Uint8Array(xhr.response);\n        cb && cb(rom);\n    };\n\n    xhr.send();\n};\n\nGameboyJS.RomAjaxReader = RomAjaxReader;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// A RomDropFileReader is able to load a drag and dropped file\nvar RomDropFileReader = function(el) {\n    this.dropElement = el;\n    if (!this.dropElement) {\n        throw 'The RomDropFileReader needs a drop zone.';\n    }\n\n    var self = this;\n    this.dropElement.addEventListener('dragenter', function(e) {\n        e.preventDefault();\n        e.target.classList.add('drag-active');\n    });\n    this.dropElement.addEventListener('dragleave', function(e) {\n        e.preventDefault();\n        e.target.classList.remove('drag-active');\n    });\n    this.dropElement.addEventListener('dragover', function(e) {\n        e.preventDefault();\n    });\n    this.dropElement.addEventListener('drop', function (e) {\n        e.target.classList.remove('drag-active');\n        if (e.dataTransfer.files.length == 0) {\n            return;\n        }\n        e.preventDefault();\n        self.loadFromFile(e.dataTransfer.files[0]);\n    });\n};\n\n// The callback argument will be called when a file is successfully\n// read, with the data as argument (Uint8Array)\nRomDropFileReader.prototype.setCallback = function(onLoadCallback) {\n    this.callback = onLoadCallback;\n};\n\n// The file loading logic is the same as the regular file reader\nRomDropFileReader.prototype.loadFromFile = function(file) {\n    if (file === undefined) {\n        return;\n    }\n    var fr = new FileReader();\n    var cb = this.callback;\n\n    fr.onload = function() {\n        cb && cb(new Uint8Array(fr.result));\n    };\n    fr.onerror = function(e) {\n        console.log('Error reading the file', e.target.error.code)\n    };\n    fr.readAsArrayBuffer(file);\n};\n\nGameboyJS.RomDropFileReader = RomDropFileReader;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// A RomFileReader is able to load a local file from an input element\n//\n// Expects to be provided a file input element,\n// or will try to find one with the \"file\" DOM ID\nvar RomFileReader = function(el) {\n    this.domElement = el || document.getElementById('file');\n    if (!this.domElement) {\n        throw 'The RomFileReader needs a valid input element.';\n    }\n\n    var self = this;\n    this.domElement.addEventListener('change', function(e){\n        self.loadFromFile(e.target.files[0]);\n    });\n};\n\n// The callback argument will be called when a file is successfully\n// read, with the data as argument (Uint8Array)\nRomFileReader.prototype.setCallback = function(onLoadCallback) {\n    this.callback = onLoadCallback;\n};\n\n// Automatically called when the DOM input is provided with a file\nRomFileReader.prototype.loadFromFile = function(file) {\n    if (file === undefined) {\n        return;\n    }\n    var fr = new FileReader();\n    var cb = this.callback;\n\n    fr.onload = function() {\n        cb && cb(new Uint8Array(fr.result));\n    };\n    fr.onerror = function(e) {\n        console.log('Error reading the file', e.target.error.code)\n    };\n    fr.readAsArrayBuffer(file);\n};\n\nGameboyJS.RomFileReader = RomFileReader;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n\nvar Rom = function(gameboy, romReader) {\n    this.gameboy = gameboy;\n    if (romReader) {\n        this.addReader(romReader);\n    }\n};\n\nRom.prototype.addReader = function(romReader) {\n    var self = this;\n    romReader.setCallback(function(data) {\n        if (!validate(data)) {\n            self.gameboy.error('The file is not a valid GameBoy ROM.');\n            return;\n        }\n        self.data = data;\n        self.gameboy.startRom(self);\n    });\n};\n\n// Validate the checksum of the cartridge header\nfunction validate(data) {\n    var hash = 0;\n    for (var i = 0x134; i <= 0x14C; i++) {\n        hash = hash - data[i] - 1;\n    }\n    return (hash & 0xFF) == data[0x14D];\n};\n\nGameboyJS.Rom = Rom;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Handlers for the Serial port of the Gameboy\n\n// The ConsoleSerial is an output-only serial port\n// designed for debug purposes as some test roms output data on the serial port\n//\n// Will regularly output the received byte (converted to string) in the console logs\n// This handler always push the value 0xFF as an input\nvar ConsoleSerial = {\n    current: '',\n    timeout: null,\n    out: function(data) {\n        ConsoleSerial.current += String.fromCharCode(data);\n        if (data == 10) {\n            ConsoleSerial.print();\n        } else {\n            clearTimeout(ConsoleSerial.timeout);\n            ConsoleSerial.timeout = setTimeout(ConsoleSerial.print, 500);\n        }\n    },\n    in: function() {\n        return 0xFF;\n    },\n    print: function() {\n        clearTimeout(ConsoleSerial.timeout);\n        console.log('serial: '+ConsoleSerial.current);\n        ConsoleSerial.current = '';\n    }\n};\nGameboyJS.ConsoleSerial = ConsoleSerial;\n\n// A DummySerial outputs nothing and always inputs 0xFF\nvar DummySerial = {\n    out: function() {},\n    in: function() {\n        return 0xFF;\n    }\n};\nGameboyJS.DummySerial = DummySerial;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Audio Processing unit\n// Listens the write accesses to the audio-reserved memory addresses\n// and dispatches the data to the sound channels\nvar APU = function(memory) {\n    this.memory = memory;\n    this.enabled = false;\n\n    AudioContext = window.AudioContext || window.webkitAudioContext;\n    var audioContext = new AudioContext();\n\n    this.channel1 = new GameboyJS.Channel1(this, 1, audioContext);\n    this.channel2 = new GameboyJS.Channel1(this, 2, audioContext);\n    this.channel3 = new GameboyJS.Channel3(this, 3, audioContext);\n    this.channel4 = new GameboyJS.Channel4(this, 4, audioContext);\n\n};\nAPU.prototype.connect = function() {\n    this.channel1.enable();\n    this.channel2.enable();\n    this.channel3.enable();\n};\nAPU.prototype.disconnect = function() {\n    this.channel1.disable();\n    this.channel2.disable();\n    this.channel3.disable();\n};\n// Updates the states of each channel given the elapsed time\n// (in instructions) since last update\nAPU.prototype.update = function(clockElapsed) {\n    if (this.enabled == false) return;\n\n    this.channel1.update(clockElapsed);\n    this.channel2.update(clockElapsed);\n    this.channel3.update(clockElapsed);\n    this.channel4.update(clockElapsed);\n};\nAPU.prototype.setSoundFlag = function(channel, value) {\n    var mask = 0xFF - (1 << (channel - 1));\n    value = value << (channel - 1)\n    var byteValue = this.memory.rb(APU.registers.NR52);\n    byteValue &= mask;\n    byteValue |= value;\n    this.memory[APU.registers.NR52] = byteValue;\n};\n// Manage writes to audio registers\n// Will update the channels depending on the address\nAPU.prototype.manageWrite = function(addr, value) {\n    if (this.enabled == false && addr < APU.registers.NR52) {\n        return;\n    }\n    this.memory[addr] = value;\n\n    switch (addr) {\n        // Channel 1 addresses\n        case 0xFF10:\n            this.channel1.clockSweep = 0;\n            this.channel1.sweepTime = ((value & 0x70) >> 4);\n            this.channel1.sweepSign = (value & 0x08) ? -1 : 1;\n            this.channel1.sweepShifts = (value & 0x07);\n            this.channel1.sweepCount = this.channel1.sweepShifts;\n            break;\n        case 0xFF11:\n            // todo : bits 6-7\n            this.channel1.setLength(value & 0x3F);\n            break;\n        case 0xFF12:\n            this.channel1.envelopeSign = (value & 0x08) ? 1 : -1;\n            var envelopeVolume = (value & 0xF0) >> 4;\n            this.channel1.setEnvelopeVolume(envelopeVolume);\n            this.channel1.envelopeStep = (value & 0x07);\n            break;\n        case 0xFF13:\n            var frequency = this.channel1.getFrequency();\n            frequency &= 0xF00;\n            frequency |= value;\n            this.channel1.setFrequency(frequency);\n            break;\n        case 0xFF14:\n            var frequency = this.channel1.getFrequency();\n            frequency &= 0xFF;\n            frequency |= (value & 7) << 8;\n            this.channel1.setFrequency(frequency);\n            this.channel1.lengthCheck = (value & 0x40) ? true : false;\n            if (value & 0x80) this.channel1.play();\n            break;\n\n        // Channel 2 addresses\n        case 0xFF16:\n            // todo : bits 6-7\n            this.channel2.setLength(value & 0x3F);\n            break;\n        case 0xFF17:\n            this.channel2.envelopeSign = (value & 0x08) ? 1 : -1;\n            var envelopeVolume = (value & 0xF0) >> 4;\n            this.channel2.setEnvelopeVolume(envelopeVolume);\n            this.channel2.envelopeStep = (value & 0x07);\n            break;\n        case 0xFF18:\n            var frequency = this.channel2.getFrequency();\n            frequency &= 0xF00;\n            frequency |= value;\n            this.channel2.setFrequency(frequency);\n            break;\n        case 0xFF19:\n            var frequency = this.channel2.getFrequency();\n            frequency &= 0xFF;\n            frequency |= (value & 7) << 8;\n            this.channel2.setFrequency(frequency);\n            this.channel2.lengthCheck = (value & 0x40) ? true : false;\n            if (value & 0x80) {\n                this.channel2.play();\n            }\n            break;\n\n        // Channel 3 addresses\n        case 0xFF1A:\n            // todo\n            break;\n        case 0xFF1B:\n            this.channel3.setLength(value);\n            break;\n        case 0xFF1C:\n            // todo\n            break;\n        case 0xFF1D:\n            var frequency = this.channel3.getFrequency();\n            frequency &= 0xF00;\n            frequency |= value;\n            this.channel3.setFrequency(frequency);\n            break;\n        case 0xFF1E:\n            var frequency = this.channel3.getFrequency();\n            frequency &= 0xFF;\n            frequency |= (value & 7) << 8;\n            this.channel3.setFrequency(frequency);\n            this.channel3.lengthCheck = (value & 0x40) ? true : false;\n            if (value & 0x80) {\n                this.channel3.play();\n            }\n            break;\n\n        // Channel 4 addresses\n        case 0xFF20:\n            this.channel4.setLength(value & 0x3F);\n            break;\n        case 0xFF21:\n            // todo\n            break;\n        case 0xFF22:\n            // todo\n            break;\n        case 0xFF23:\n            this.channel4.lengthCheck = (value & 0x40) ? true : false;\n            if (value & 0x80) {\n                this.channel4.play();\n            }\n            break;\n\n        // channel 3 wave bytes\n        case 0xFF30:case 0xFF31:case 0xFF32:case 0xFF33:case 0xFF34:case 0xFF35:case 0xFF36:case 0xFF37:\n        case 0xFF38:case 0xFF39:case 0xFF3A:case 0xFF3B:case 0xFF3C:case 0xFF3D:case 0xFF3E:case 0xFF3F:\n            var index = addr - 0xFF30;\n            this.channel3.setWaveBufferByte(index, value);\n            break;\n\n        // general audio switch\n        case 0xFF26:\n            value &= 0xF0;\n            this.memory[addr] = value;\n            this.enabled = (value & 0x80) == 0 ? false : true;\n            if (!this.enabled) {\n                for (var i = 0xFF10; i < 0xFF27; i++)\n                    this.memory[i] = 0;\n                // todo stop sound\n            }\n            break;\n    }\n};\n\nAPU.registers = {\n    NR10: 0xFF10,\n    NR11: 0xFF11,\n    NR12: 0xFF12,\n    NR13: 0xFF13,\n    NR14: 0xFF14,\n\n    NR21: 0xFF16,\n    NR22: 0xFF17,\n    NR23: 0xFF18,\n    NR24: 0xFF19,\n\n    NR30: 0xFF1A,\n    NR31: 0xFF1B,\n    NR32: 0xFF1C,\n    NR33: 0xFF1D,\n    NR34: 0xFF1E,\n\n    NR41: 0xFF20,\n    NR42: 0xFF21,\n    NR43: 0xFF22,\n    NR44: 0xFF23,\n\n    NR50: 0xFF24,\n    NR51: 0xFF25,\n    NR52: 0xFF26\n};\nGameboyJS.APU = APU;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\nvar Channel1 = function(apu, channelNumber, audioContext) {\n    this.apu = apu;\n    this.channelNumber = channelNumber;\n    this.playing = false;\n\n    this.soundLengthUnit = 0x4000; // 1 / 256 second of instructions\n    this.soundLength = 64; // defaults to 64 periods\n    this.lengthCheck = false;\n\n    this.sweepTime = 0; // from 0 to 7\n    this.sweepStepLength = 0x8000; // 1 / 128 seconds of instructions\n    this.sweepCount = 0;\n    this.sweepShifts = 0;\n    this.sweepSign = 1; // +1 / -1 for increase / decrease freq\n\n    this.frequency = 0;\n\n    this.envelopeStep = 0;\n    this.envelopeStepLength = 0x10000;// 1 / 64 seconds of instructions\n    this.envelopeCheck = false;\n    this.envelopeSign = 1;\n\n    this.clockLength = 0;\n    this.clockEnvelop = 0;\n    this.clockSweep = 0;\n\n    var gainNode = audioContext.createGain();\n    gainNode.gain.value = 0;\n    var oscillator = audioContext.createOscillator();\n    oscillator.type = 'square';\n    oscillator.frequency.value = 1000;\n    oscillator.connect(gainNode);\n    oscillator.start(0);\n\n    this.audioContext = audioContext;\n    this.gainNode = gainNode;\n    this.oscillator = oscillator;\n};\n\nChannel1.prototype.play = function() {\n    if (this.playing) return;\n    this.playing = true;\n    this.apu.setSoundFlag(this.channelNumber, 1);\n    this.gainNode.connect(this.audioContext.destination);\n    this.clockLength = 0;\n    this.clockEnvelop = 0;\n    this.clockSweep = 0;\n    if (this.sweepShifts > 0) this.checkFreqSweep();\n};\nChannel1.prototype.stop = function() {\n    this.playing = false;\n    this.apu.setSoundFlag(this.channelNumber, 0);\n    this.gainNode.disconnect();\n};\nChannel1.prototype.checkFreqSweep = function() {\n    var oldFreq = this.getFrequency();\n    var newFreq = oldFreq + this.sweepSign * (oldFreq >> this.sweepShifts);\n    if (newFreq > 0x7FF) {\n        newFreq = 0;\n        this.stop();\n    }\n\n    return newFreq;\n};\nChannel1.prototype.update = function(clockElapsed) {\n    this.clockEnvelop += clockElapsed;\n    this.clockSweep   += clockElapsed;\n\n    if ((this.sweepCount || this.sweepTime) && this.clockSweep > (this.sweepStepLength * this.sweepTime)) {\n        this.clockSweep -= (this.sweepStepLength * this.sweepTime);\n        this.sweepCount--;\n\n        var newFreq = this.checkFreqSweep(); // process and check new freq\n\n        this.apu.memory[0xFF13] = newFreq & 0xFF;\n        this.apu.memory[0xFF14] &= 0xF8;\n        this.apu.memory[0xFF14] |= (newFreq & 0x700) >> 8;\n        this.setFrequency(newFreq);\n\n        this.checkFreqSweep(); // check again with new value\n    }\n\n    if (this.envelopeCheck && this.clockEnvelop > this.envelopeStepLength) {\n        this.clockEnvelop -= this.envelopeStepLength;\n        this.envelopeStep--;\n        this.setEnvelopeVolume(this.envelopeVolume + this.envelopeSign);\n        if (this.envelopeStep <= 0) {\n            this.envelopeCheck = false;\n        }\n    }\n\n    if (this.lengthCheck) {\n        this.clockLength += clockElapsed;\n        if (this.clockLength > this.soundLengthUnit) {\n            this.soundLength--;\n            this.clockLength -= this.soundLengthUnit;\n            if (this.soundLength == 0) {\n                this.setLength(0);\n                this.stop();\n            }\n        }\n    }\n};\nChannel1.prototype.setFrequency = function(value) {\n    this.frequency = value;\n    this.oscillator.frequency.value = 131072 / (2048 - this.frequency);\n};\nChannel1.prototype.getFrequency = function() {\n    return this.frequency;\n};\nChannel1.prototype.setLength = function(value) {\n    this.soundLength = 64 - (value & 0x3F);\n};\nChannel1.prototype.setEnvelopeVolume = function(volume) {\n    this.envelopeCheck = volume > 0 && volume < 16 ? true : false;\n    this.envelopeVolume = volume;\n    this.gainNode.gain.value = this.envelopeVolume * 1/100;\n};\nChannel1.prototype.disable = function() {\n    this.oscillator.disconnect();\n};\nChannel1.prototype.enable = function() {\n    this.oscillator.connect(this.gainNode);\n};\nGameboyJS.Channel1 = Channel1;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\nvar Channel3 = function(apu, channelNumber, audioContext) {\n    this.apu = apu;\n    this.channelNumber = channelNumber;\n    this.playing = false;\n\n    this.soundLength = 0;\n    this.soundLengthUnit = 0x4000; // 1 / 256 second of instructions\n    this.lengthCheck = false;\n\n    this.clockLength = 0;\n\n    this.buffer = new Float32Array(32);\n\n    var gainNode = audioContext.createGain();\n    gainNode.gain.value = 1;\n    this.gainNode = gainNode;\n\n    this.baseSpeed = 65536;\n    var waveBuffer = audioContext.createBuffer(1, 32, this.baseSpeed);\n\n    var bufferSource = audioContext.createBufferSource();\n    bufferSource.buffer = waveBuffer;\n    bufferSource.loop = true;\n    bufferSource.connect(gainNode);\n    bufferSource.start(0);\n\n    this.audioContext = audioContext;\n    this.waveBuffer = waveBuffer;\n    this.bufferSource = bufferSource;\n\n};\nChannel3.prototype.play = function() {\n    if (this.playing) return;\n    this.playing = true;\n    this.apu.setSoundFlag(this.channelNumber, 1);\n    this.waveBuffer.copyToChannel(this.buffer, 0, 0);\n\n    this.gainNode.connect(this.audioContext.destination);\n    this.clockLength = 0;\n};\nChannel3.prototype.stop = function() {\n    this.playing = false;\n    this.apu.setSoundFlag(this.channelNumber, 0);\n    this.gainNode.disconnect();\n};\nChannel3.prototype.update = function(clockElapsed) {\n    if (this.lengthCheck){\n        this.clockLength  += clockElapsed;\n        if (this.clockLength > this.soundLengthUnit) {\n            this.soundLength--;\n            this.clockLength -= this.soundLengthUnit;\n            if (this.soundLength == 0) {\n                this.setLength(0);\n                this.stop();\n            }\n        }\n    }\n};\nChannel3.prototype.setFrequency = function(value) {\n    value = 65536 / (2048  - value);\n    this.bufferSource.playbackRate.value = value / this.baseSpeed;\n};\nChannel3.prototype.getFrequency = function() {\n    var freq = 2048 - 65536 / (this.bufferSource.playbackRate.value * this.baseSpeed);\n    return freq | 1;\n};\nChannel3.prototype.setLength = function(value) {\n    this.soundLength = 256 - value;\n};\nChannel3.prototype.setWaveBufferByte = function(index, value) {\n    var bufferIndex = index * 2;\n\n    this.buffer[bufferIndex]   = (value >> 4) / 8 - 1; // value in buffer is in -1 -> 1\n    this.buffer[bufferIndex+1] = (value & 0x0F) / 8 - 1;\n};\nChannel3.prototype.disable = function() {\n    this.bufferSource.disconnect();\n};\nChannel3.prototype.enable = function() {\n    this.bufferSource.connect(this.gainNode);\n};\nGameboyJS.Channel3 = Channel3;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\nvar Channel4 = function(apu, channelNumber, audioContext) {\n    this.apu = apu;\n    this.channelNumber = channelNumber;\n    this.playing = false;\n\n    this.soundLengthUnit = 0x4000; // 1 / 256 second of instructions\n    this.soundLength = 64; // defaults to 64 periods\n    this.lengthCheck = false;\n\n    this.clockLength = 0;\n\n    this.audioContext = audioContext;\n};\n\nChannel4.prototype.play = function() {\n    if (this.playing) return;\n    this.playing = true;\n    this.apu.setSoundFlag(this.channelNumber, 1);\n    this.clockLength = 0;\n};\nChannel4.prototype.stop = function() {\n    this.playing = false;\n    this.apu.setSoundFlag(this.channelNumber, 0);\n};\nChannel4.prototype.update = function(clockElapsed) {\n    if (this.lengthCheck) {\n        this.clockLength  += clockElapsed;\n        if (this.clockLength > this.soundLengthUnit) {\n            this.soundLength--;\n            this.clockLength -= this.soundLengthUnit;\n            if (this.soundLength == 0) {\n                this.setLength(0);\n                this.stop();\n            }\n        }\n    }\n};\nChannel4.prototype.setLength = function(value) {\n    this.soundLength = 64 - (value & 0x3F);\n};\nGameboyJS.Channel4 = Channel4;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\nvar Timer = function(cpu, memory) {\n    this.cpu    = cpu;\n    this.memory = memory;\n\n    this.DIV  = 0xFF04;\n    this.TIMA = 0xFF05;\n    this.TMA  = 0xFF06;\n    this.TAC  = 0xFF07;\n\n    this.mainTime  = 0;\n    this.divTime   = 0;\n};\n\nTimer.prototype.update = function(clockElapsed) {\n    this.updateDiv(clockElapsed);\n    this.updateTimer(clockElapsed);\n};\n\nTimer.prototype.updateTimer = function(clockElapsed) {\n    if (!(this.memory.rb(this.TAC) & 0x4)) {\n        return;\n    }\n    this.mainTime += clockElapsed;\n\n    var threshold = 64;\n    switch (this.memory.rb(this.TAC) & 3) {\n        case 0: threshold=64; break; // 4KHz\n        case 1: threshold=1;  break; // 256KHz\n        case 2: threshold=4;  break; // 64KHz\n        case 3: threshold=16; break; // 16KHz\n    }\n    threshold *= 16;\n\n    while (this.mainTime >= threshold) {\n        this.mainTime -= threshold;\n\n        this.memory.wb(this.TIMA, this.memory.rb(this.TIMA) + 1);\n        if (this.memory.rb(this.TIMA) > 0xFF) {\n            this.memory.wb(this.TIMA, this.memory.rb(this.TMA));\n            this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.TIMER);\n        }\n    }\n};\n// Update the DIV register internal clock\n// Increment it if the clock threshold is elapsed and\n// reset it if its value overflows\nTimer.prototype.updateDiv = function(clockElapsed) {\n    var divThreshold = 256; // DIV is 16KHz\n    this.divTime += clockElapsed;\n    if (this.divTime > divThreshold) {\n        this.divTime -= divThreshold;\n        var div = this.memory.rb(this.DIV) + 1;\n        this.memory.wb(this.DIV, div&0xFF);\n    }\n};\n\nTimer.prototype.resetDiv = function() {\n    this.divTime = 0;\n    this.memory[this.DIV] = 0; // direct write to avoid looping\n};\nGameboyJS.Timer = Timer;\n}(GameboyJS || (GameboyJS = {})));\n\nvar GameboyJS;\n(function (GameboyJS) {\n\"use strict\";\n\n// Utility functions\nvar Util = {\n    // Add to the first argument the properties of all other arguments\n    extend: function(target /*, source1, source2, etc. */) {\n        var sources = Array.prototype.slice.call(arguments);\n        for (var i in sources) {\n            var source = sources[i];\n            for (var name in source) {\n                target[name] = source[name];\n            }\n        }\n\n        return target;\n    },\n    testFlag: function(p, cc) {\n        var test=1;\n        var mask=0x10;\n        if (cc=='NZ'||cc=='NC') test=0;\n        if (cc=='NZ'||cc=='Z')  mask=0x80;\n        return (test && p.r.F&mask) || (!test && !(p.r.F&mask));\n    },\n    getRegAddr: function(p, r1, r2) {return Util.makeword(p.r[r1], p.r[r2]);},\n\n    // make a 16 bits word from 2 bytes\n    makeword: function(b1, b2) {return (b1 << 8) + b2;},\n\n    // return the integer signed value of a given byte\n    getSignedValue: function(v) {return v & 0x80 ? v-256 : v;},\n\n    // extract a bit from a byte\n    readBit: function(byte, index) {\n        return (byte >> index) & 1;\n    }\n};\n\nGameboyJS.Util = Util;\n}(GameboyJS || (GameboyJS = {})));\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"gameboy-js\",\n    \"author\": \"Julien Chichignoud\",\n    \"description\": \"JavaScript Gameboy emulator\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/juchi/gameboy.js\"\n    },\n    \"homepage\": \"https://juchi.github.io/gameboy.js/\",\n    \"license\": \"MIT\",\n    \"scripts\": {\n        \"build\": \"webpack\"\n    },\n    \"devDependencies\": {\n        \"ts-loader\": \"^9.5.2\",\n        \"webpack\": \"^5.99.9\",\n        \"webpack-cli\": \"^6.0.1\"\n    }\n}\n"
  },
  {
    "path": "src/cpu.ts",
    "content": "import Memory from './memory';\nimport Timer from './timer';\nimport APU from './sound/apu';\nimport Screen from './display/screen';\nimport GPU from './display/gpu';\nimport Util from './util';\nimport { ConsoleSerial, SerialInterface } from './serial'\nimport {cpuOps} from './instructions'\nimport {opcodeMap} from './opcodes'\n\n// CPU class\nclass CPU {\n    gameboy;\n    r;\n    clock;\n    gpu: GPU;\n    apu: APU;\n    input;\n    timer: Timer;\n    memory: Memory;\n    IME = false;\n    isHalted = false;\n    isPaused = false;\n    usingBootRom = false;\n\n    SERIAL_INTERNAL_INSTR = 512; // instr to wait per bit if internal clock\n    enableSerial = 0;\n    serialHandler: SerialInterface;\n\n    nextFrameTimer: ReturnType<typeof setTimeout>;\n\n    constructor(gameboy) {\n        this.gameboy = gameboy;\n        this.r = {A:0, F: 0, B:0, C:0, D:0, E:0, H:0, L:0, pc:0, sp:0};\n        this.clock = {c: 0, serial: 0};\n\n        this.createDevices();\n    }\n\n    static INTERRUPTS = {\n        VBLANK: 0,\n        LCDC:   1,\n        TIMER:  2,\n        SERIAL: 3,\n        HILO:   4\n    };\n    static interruptRoutines = {\n        0: function(p){cpuOps.RSTn(p, 0x40);},\n        1: function(p){cpuOps.RSTn(p, 0x48);},\n        2: function(p){cpuOps.RSTn(p, 0x50);},\n        3: function(p){cpuOps.RSTn(p, 0x58);},\n        4: function(p){cpuOps.RSTn(p, 0x60);}\n    }\n\n    createDevices() {\n        this.memory = new Memory(this);\n        this.timer = new Timer(this, this.memory);\n        this.apu = new APU(this.memory);\n\n        this.enableSerial = 0;\n        this.serialHandler = new ConsoleSerial;\n    }\n\n    reset() {\n        this.memory.reset();\n        this.r = {A:0x01, F: 0, B:0xFF, C:0x13, D:0, E:0xC1, H:0x84, L:0x03, pc:0, sp:0xFFFE};\n    }\n\n    loadRom(data) {\n        this.memory.setRomData(data);\n    }\n\n    getRamSize() {\n        let size = 0;\n        switch (this.memory.rb(0x149)) {\n            case 1:\n                size = 2048;\n                break;\n            case 2:\n                size = 2048 * 4;\n                break;\n            case 3:\n                size = 2048 * 16;\n                break;\n        }\n\n        return size;\n    }\n\n    getGameName() {\n        var name = '';\n        for (var i = 0x134; i < 0x143; i++) {\n            var char = this.memory.rb(i) || 32;\n            name += String.fromCharCode(char);\n        }\n\n        return name;\n    }\n\n    // Start the execution of the emulator\n    run() {\n        if (this.usingBootRom) {\n            this.r.pc = 0x0000;\n        } else {\n            this.r.pc = 0x0100;\n        }\n        this.frame();\n    }\n\n    stop() {\n        clearTimeout(this.nextFrameTimer);\n    }\n\n    // Fetch-and-execute loop\n    // Will execute instructions for the duration of a frame\n    //\n    // The screen unit will notify the vblank period which\n    // is considered the end of a frame\n    //\n    // The function is called on a regular basis with a timeout\n    frame() {\n        if (!this.isPaused) {\n            this.nextFrameTimer = setTimeout(this.frame.bind(this), 1000 / Screen.physics.FREQUENCY);\n        }\n\n        try {\n            var vblank = false;\n            while (!vblank) {\n                var oldInstrCount = this.clock.c;\n                if (!this.isHalted) {\n                    let opcode = this.fetchOpcode();\n                    opcodeMap[opcode](this);\n                    this.r.F &= 0xF0; // tmp fix\n\n                    if (this.enableSerial) {\n                        var instr = this.clock.c - oldInstrCount;\n                        this.clock.serial += instr;\n                        if (this.clock.serial >= 8 * this.SERIAL_INTERNAL_INSTR) {\n                            this.endSerialTransfer();\n                        }\n                    }\n                } else {\n                    this.clock.c += 4;\n                }\n\n                var elapsed = this.clock.c - oldInstrCount;\n                vblank = this.gpu.update(elapsed);\n                this.timer.update(elapsed);\n                this.input.update();\n                this.apu.update(elapsed);\n                this.checkInterrupt();\n            }\n            this.clock.c = 0;\n        } catch (e) {\n            this.gameboy.handleException(e);\n        }\n    }\n\n    fetchOpcode(): number {\n        let opcode = this.memory.rb(this.r.pc++);\n\n        if (!opcodeMap[opcode]) {\n            this.stop();\n            throw 'Unknown opcode '+opcode.toString(16)+' at address '+(this.r.pc-1).toString(16)+', stopping execution...';\n        }\n\n        return opcode;\n    }\n\n    // read register\n    rr(register) {\n        return this.r[register];\n    }\n\n    // write register\n    wr(register, value) {\n        this.r[register] = value;\n    }\n\n    halt() {\n        this.isHalted = true;\n    }\n    unhalt() {\n        this.isHalted = false;\n    }\n    pause() {\n        this.isPaused = true;\n    }\n    unpause() {\n        if (this.isPaused) {\n            this.isPaused = false;\n            this.frame();\n        }\n    }\n\n    // Look for interrupt flags\n    checkInterrupt() {\n        if (!this.IME) {\n            return;\n        }\n        for (var i = 0; i < 5 && this.IME; i++) {\n            var IFval = this.memory.rb(0xFF0F);\n            if (Util.readBit(IFval, i) && this.isInterruptEnable(i)) {\n                IFval &= (0xFF - (1<<i));\n                this.memory.wb(0xFF0F, IFval);\n                this.disableInterrupts();\n                this.clock.c += 4; // 20 clocks to serve interrupt, with 16 for RSTn\n                CPU.interruptRoutines[i](this);\n            }\n        }\n    }\n\n    // Set an interrupt flag\n    requestInterrupt(type) {\n        var IFval = this.memory.rb(0xFF0F);\n        IFval |= (1 << type)\n        this.memory.wb(0xFF0F, IFval) ;\n        this.unhalt();\n    }\n\n    isInterruptEnable(type) {\n        return Util.readBit(this.memory.rb(0xFFFF), type) != 0;\n    }\n\n    enableInterrupts() {\n        this.IME = true;\n    }\n    disableInterrupts() {\n        this.IME = false;\n    }\n\n    enableSerialTransfer() {\n        this.enableSerial = 1;\n        this.clock.serial = 0;\n    }\n\n    endSerialTransfer() {\n        this.enableSerial = 0;\n        var data = this.memory.rb(0xFF01);\n        this.memory.wb(0xFF02, 0);\n        this.serialHandler.out(data);\n        this.memory.wb(0xFF01, this.serialHandler.in());\n    }\n\n    resetDivTimer() {\n        this.timer.resetDiv();\n    }\n}\n\nexport default CPU;\n"
  },
  {
    "path": "src/debug.ts",
    "content": "import {Gameboy} from './main';\nimport Util from './util';\n\nlet Debug: any = {};\n// Output a range of 16 memory addresses\nDebug.view_memory = function(addr: number, gameboy: Gameboy): string {\n    let memory = gameboy.cpu.memory;\n    addr = addr & 0xFFF0;\n    let pad = '00';\n    let str = addr.toString(16) + ':';\n    for (let i = addr; i < addr + 0x10; i++) {\n        if ((i & 0x1) == 0) {\n            str += ' ';\n        }\n        let val: number = memory[i] || 0;\n        let stringVal = val.toString(16)\n        str += pad.substring(stringVal.length) + stringVal;\n    }\n\n    return str;\n};\n\nDebug.view_tile = function(gameboy: Gameboy, index: number, dataStart?: number): void {\n    let memory = gameboy.cpu.memory;\n    let gpu = gameboy.cpu.gpu;\n    let LCDC = memory.deviceram(gpu.LCDC);\n    if (typeof dataStart === 'undefined') {\n        dataStart = 0x8000;\n        if (!Util.readBit(LCDC, 4)) {\n            dataStart = 0x8800;\n            index = Util.getSignedValue(index) + 128;\n        }\n    }\n\n    let tileData = gameboy.cpu.gpu.readTileData(index, dataStart);\n\n    let pixelData = new Array(8 * 8)\n    for (let line = 0; line < 8; line++) {\n        let b1 = tileData.shift();\n        let b2 = tileData.shift();\n\n        for (let pixel = 0; pixel < 8; pixel++) {\n            let mask = (1 << (7-pixel));\n            let colorValue = ((b1 & mask) >> (7-pixel)) + ((b2 & mask) >> (7-pixel))*2;\n            pixelData[line * 8 + pixel] = colorValue;\n        }\n    }\n\n    let i = 0;\n    while (pixelData.length) {\n        console.log(i++ + ' ' + pixelData.splice(0, 8).join(''));\n    }\n};\n\nDebug.list_visible_sprites = function(gameboy: Gameboy) {\n    let memory = gameboy.cpu.memory;\n    let indexes: Array<object> = [];\n    for (let i = 0xFE00; i < 0xFE9F; i += 4) {\n        let x = memory.oamram(i + 1);\n        let y = memory.oamram(i);\n        let tileIndex = memory.oamram(i + 2);\n        if (x == 0 || x >= 168) {\n            continue;\n        }\n        indexes.push({oamIndex:i, x:x, y:y, tileIndex:tileIndex});\n    }\n\n    return indexes;\n};\n\nexport default Debug;\n"
  },
  {
    "path": "src/display/gpu.ts",
    "content": "import Screen from './screen';\nimport CPU from '../cpu';\nimport Util from '../util';\n\nclass GPU {\n    LCDC= 0xFF40;\n    STAT= 0xFF41;\n    SCY = 0xFF42;\n    SCX = 0xFF43;\n    LY  = 0xFF44;\n    LYC = 0xFF45;\n    BGP = 0xFF47;\n    OBP0= 0xFF48;\n    OBP1= 0xFF49;\n    WY  = 0xFF4A;\n    WX  = 0xFF4B;\n\n    OAM_START = 0xFE00;\n    OAM_END   = 0xFE9F;\n    VBLANK_TIME = 70224;\n\n    cpu: CPU;\n    screen: Screen;\n    vram: Function;\n    deviceram: Function;\n    oamram: Function;\n    clock: number;\n    mode: number;\n    line: number;\n    buffer: number[];\n    tileBuffer: number[];\n\n    constructor(screen, cpu) {\n        this.cpu = cpu;\n        this.screen = screen;\n        this.vram = cpu.memory.vram.bind(cpu.memory);\n        this.deviceram = cpu.memory.deviceram.bind(cpu.memory);\n        this.oamram = cpu.memory.oamram.bind(cpu.memory);\n        this.clock = 0;\n        this.mode = 2;\n        this.line = 0;\n        this.buffer = new Array(Screen.physics.WIDTH * Screen.physics.HEIGHT);\n        this.tileBuffer = new Array(8);\n    }\n\n    static tilemap = {\n        HEIGHT: 32,\n        WIDTH: 32,\n        START_0: 0x9800,\n        START_1: 0x9C00,\n        LENGTH: 0x0400 // 1024 bytes = 32*32\n    };\n\n    update(clockElapsed) {\n        this.clock += clockElapsed;\n        var vblank = false;\n\n        switch (this.mode) {\n            case 0: // HBLANK\n                if (this.clock >= 204) {\n                    this.clock -= 204;\n                    this.line++;\n                    this.updateLY();\n                    if (this.line == 144) {\n                        this.setMode(1);\n                        vblank = true;\n                        this.cpu.requestInterrupt(CPU.INTERRUPTS.VBLANK);\n                        this.drawFrame();\n                    } else {\n                        this.setMode(2);\n                    }\n                }\n                break;\n            case 1: // VBLANK\n                if (this.clock >= 456) {\n                    this.clock -= 456;\n                    this.line++;\n                    if (this.line > 153) {\n                        this.line = 0;\n                        this.setMode(2);\n                    }\n                    this.updateLY();\n                }\n\n                break;\n            case 2: // SCANLINE OAM\n                if (this.clock >= 80) {\n                    this.clock -= 80;\n                    this.setMode(3);\n                }\n                break;\n            case 3: // SCANLINE VRAM\n                if (this.clock >= 172) {\n                    this.clock -= 172;\n                    this.drawScanLine(this.line);\n                    this.setMode(0);\n                }\n                break;\n        }\n\n        return vblank;\n    }\n\n    updateLY() {\n        this.deviceram(this.LY, this.line);\n        var STAT = this.deviceram(this.STAT);\n        if (this.deviceram(this.LY) == this.deviceram(this.LYC)) {\n            this.deviceram(this.STAT, STAT | (1 << 2));\n            if (STAT & (1 << 6)) {\n                this.cpu.requestInterrupt(CPU.INTERRUPTS.LCDC);\n            }\n        } else {\n            this.deviceram(this.STAT, STAT & (0xFF - (1 << 2)));\n        }\n    }\n\n    setMode(mode) {\n        this.mode = mode;\n        var newSTAT = this.deviceram(this.STAT);\n        newSTAT &= 0xFC;\n        newSTAT |= mode;\n        this.deviceram(this.STAT, newSTAT);\n\n        if (mode < 3) {\n            if (newSTAT & (1 << (3+mode))) {\n                this.cpu.requestInterrupt(CPU.INTERRUPTS.LCDC);\n            }\n        }\n    }\n\n    // Push one scanline into the main buffer\n    drawScanLine(line) {\n        var LCDC = this.deviceram(this.LCDC);\n        var enable = Util.readBit(LCDC, 7);\n        if (enable) {\n            var lineBuffer = new Array(Screen.physics.WIDTH);\n            this.drawBackground(LCDC, line, lineBuffer);\n            this.drawSprites(LCDC, line, lineBuffer);\n            // TODO draw a line for the window here too\n        }\n    }\n\n    drawFrame() {\n        var LCDC = this.deviceram(this.LCDC);\n        var enable = Util.readBit(LCDC, 7);\n        if (enable) {\n            //this.drawSprites(LCDC);\n            this.drawWindow(LCDC);\n        }\n        this.screen.render(this.buffer);\n    }\n\n    drawBackground(LCDC, line, lineBuffer) {\n        if (!Util.readBit(LCDC, 0)) {\n            return;\n        }\n\n        var mapStart = Util.readBit(LCDC, 3) ? GPU.tilemap.START_1 : GPU.tilemap.START_0;\n\n        var dataStart, signedIndex = false;\n        if (Util.readBit(LCDC, 4)) {\n            dataStart = 0x8000;\n        } else {\n            dataStart = 0x8800;\n            signedIndex = true;\n        }\n\n        var bgx = this.deviceram(this.SCX);\n        var bgy = this.deviceram(this.SCY);\n        var tileLine = ((line + bgy) & 7);\n\n        // browse BG tilemap for the line to render\n        var tileRow = ((((bgy + line) / 8) | 0) & 0x1F);\n        var firstTile = ((bgx / 8) | 0) + 32 * tileRow;\n        var lastTile = firstTile + Screen.physics.WIDTH / 8 + 1;\n        if ((lastTile & 0x1F) < (firstTile & 0x1F)) {\n            lastTile -= 32;\n        }\n        var x = (firstTile & 0x1F) * 8 - bgx; // x position of the first tile's leftmost pixel\n        for (var i = firstTile; i != lastTile; i++, (i & 0x1F) == 0 ? i-=32 : null) {\n            var tileIndex = this.vram(i + mapStart);\n\n            if (signedIndex) {\n                tileIndex = Util.getSignedValue(tileIndex) + 128;\n            }\n\n            var tileData = this.readTileData(tileIndex, dataStart);\n\n            this.drawTileLine(tileData, tileLine);\n            this.copyBGTileLine(lineBuffer, this.tileBuffer, x);\n            x += 8;\n        }\n\n        this.copyLineToBuffer(lineBuffer, line);\n    }\n\n    // Copy a tile line from a tileBuffer to a line buffer, at a given x position\n    copyBGTileLine(lineBuffer, tileBuffer, x) {\n        // copy tile line to buffer\n        for (var k = 0; k < 8; k++, x++) {\n            if (x < 0 || x >= Screen.physics.WIDTH) continue;\n            lineBuffer[x] = tileBuffer[k];\n        }\n    }\n\n    // Copy a scanline into the main buffer\n    copyLineToBuffer(lineBuffer, line) {\n        var bgPalette = GPU.getPalette(this.deviceram(this.BGP));\n\n        for (var x = 0; x < Screen.physics.WIDTH; x++) {\n            var color = lineBuffer[x];\n            this.drawPixel(x, line, bgPalette[color]);\n        }\n    }\n\n    // Write a line of a tile (8 pixels) into a buffer array\n    drawTileLine(tileData, line: number, xflip = 0, yflip = 0) {\n        var l = yflip ? 7 - line : line;\n        var byteIndex = l * 2;\n        var b1 = tileData[byteIndex++];\n        var b2 = tileData[byteIndex++];\n\n        var offset = 8;\n        for (var pixel = 0; pixel < 8; pixel++) {\n            offset--;\n            var mask = (1 << offset);\n            var colorValue = ((b1 & mask) >> offset) + ((b2 & mask) >> offset)*2;\n            var p = xflip ? offset : pixel;\n            this.tileBuffer[p] = colorValue;\n        }\n    }\n\n    drawSprites(LCDC, line, bgLineBuffer) {\n        if (!Util.readBit(LCDC, 1)) {\n            return;\n        }\n        var spriteHeight = Util.readBit(LCDC, 2) ? 16 : 8;\n\n        var sprites = new Array();\n        for (var i = this.OAM_START; i < this.OAM_END && sprites.length < 10; i += 4) {\n            var y = this.oamram(i);\n            var x = this.oamram(i+1);\n            var index = this.oamram(i+2);\n            if (spriteHeight === 16) index = index & 0xFE;\n            var flags = this.oamram(i+3);\n\n            if (y - 16 > line || y - 16 < line - spriteHeight) {\n                continue;\n            }\n            sprites.push({x:x, y:y, index:index, flags:flags})\n        }\n        sprites.sort((a, b) => a.x - b.x);\n\n        if (sprites.length == 0) return;\n\n        // cache object to store read tiles from this frame\n        var cacheTile = {};\n        var spriteLineBuffer = new Array(Screen.physics.WIDTH);\n\n        for (var i = 0; i < sprites.length; i++) {\n            var sprite = sprites[i];\n            var tileLine = line - sprite.y + 16;\n            var paletteNumber = Util.readBit(sprite.flags, 4);\n            var xflip = Util.readBit(sprite.flags, 5);\n            var yflip = Util.readBit(sprite.flags, 6);\n            var priority = Util.readBit(sprite.flags, 7);\n            var tileData = cacheTile[sprite.index] || (cacheTile[sprite.index] = this.readTileData(sprite.index, 0x8000, spriteHeight * 2));\n            this.drawTileLine(tileData, tileLine, xflip, yflip);\n            this.copySpriteTileLine(spriteLineBuffer, this.tileBuffer, sprite.x - 8, paletteNumber, priority, bgLineBuffer);\n        }\n\n        this.copySpriteLineToBuffer(spriteLineBuffer, line);\n    }\n\n    // Copy a tile line from a tileBuffer to a line buffer, at a given x position\n    copySpriteTileLine = function(lineBuffer, tileBuffer, x, palette, priority, bgLineBuffer) {\n        // copy tile line to buffer\n        for (var k = 0; k < 8; k++, x++) {\n            if (x < 0 || x >= Screen.physics.WIDTH || tileBuffer[k] == 0) continue;\n            if (lineBuffer[x]) continue;\n            if (priority === 1 && bgLineBuffer[x] > 0) {\n                lineBuffer[x] = {color:0, palette: palette};\n                continue;\n            }\n            lineBuffer[x] = {color:tileBuffer[k], palette: palette};\n        }\n    }\n\n    // Copy a sprite scanline into the main buffer\n    copySpriteLineToBuffer(spriteLineBuffer, line) {\n        var spritePalettes = {};\n        spritePalettes[0] = GPU.getPalette(this.deviceram(this.OBP0));\n        spritePalettes[1] = GPU.getPalette(this.deviceram(this.OBP1));\n\n        for (var x = 0; x < Screen.physics.WIDTH; x++) {\n            if (!spriteLineBuffer[x]) continue;\n            var color = spriteLineBuffer[x].color;\n            if (color === 0) continue;\n            var paletteNumber = spriteLineBuffer[x].palette;\n            this.drawPixel(x, line, spritePalettes[paletteNumber][color]);\n        }\n    }\n\n    drawTile(tileData, x, y, buffer, bufferWidth, xflip = 0, yflip = 0) {\n        var byteIndex = 0;\n        for (var line = 0; line < 8; line++) {\n            var l = yflip ? 7 - line : line;\n            var b1 = tileData[byteIndex++];\n            var b2 = tileData[byteIndex++];\n\n            for (var pixel = 0; pixel < 8; pixel++) {\n                var mask = (1 << (7-pixel));\n                var colorValue = ((b1 & mask) >> (7-pixel)) + ((b2 & mask) >> (7-pixel))*2;\n                var p = xflip ? 7 - pixel : pixel;\n                var bufferIndex = (x + p) + (y + l) * bufferWidth;\n                buffer[bufferIndex] = colorValue;\n            }\n        }\n    }\n\n    // get an array of tile bytes data (16 entries for 8*8px)\n    readTileData(tileIndex: number, dataStart: number, tileSize?: number) {\n        tileSize = tileSize || 0x10; // 16 bytes / tile by default (8*8 px)\n        var tileData = new Array();\n\n        var tileAddressStart = dataStart + (tileIndex * 0x10);\n        for (var i = tileAddressStart; i < tileAddressStart + tileSize; i++) {\n            tileData.push(this.vram(i));\n        }\n\n        return tileData;\n    }\n\n    drawWindow(LCDC) {\n        if (!Util.readBit(LCDC, 5)) {\n            return;\n        }\n\n        var buffer = new Array(256*256);\n        var mapStart = Util.readBit(LCDC, 6) ? GPU.tilemap.START_1 : GPU.tilemap.START_0;\n\n        var dataStart, signedIndex = false;\n        if (Util.readBit(LCDC, 4)) {\n            dataStart = 0x8000;\n        } else {\n            dataStart = 0x8800;\n            signedIndex = true;\n        }\n\n        // browse Window tilemap\n        for (var i = 0; i < GPU.tilemap.LENGTH; i++) {\n            var tileIndex = this.vram(i + mapStart);\n\n            if (signedIndex) {\n                tileIndex = Util.getSignedValue(tileIndex) + 128;\n            }\n\n            var tileData = this.readTileData(tileIndex, dataStart);\n            var x = i % GPU.tilemap.WIDTH;\n            var y = (i / GPU.tilemap.WIDTH) | 0;\n            this.drawTile(tileData, x * 8, y * 8, buffer, 256);\n        }\n\n        var wx = this.deviceram(this.WX) - 7;\n        var wy = this.deviceram(this.WY);\n        for (var x = Math.max(0, -wx); x < Math.min(Screen.physics.WIDTH, Screen.physics.WIDTH - wx); x++) {\n            for (var y = Math.max(0, -wy); y < Math.min(Screen.physics.HEIGHT, Screen.physics.HEIGHT - wy); y++) {\n                var color = buffer[(x & 255) + (y & 255) * 256];\n                this.drawPixel(x + wx, y + wy, color);\n            }\n        }\n    }\n\n    drawPixel(x, y, color) {\n        this.buffer[y * 160 + x] = color;\n    }\n\n    getPixel(x, y) {\n        return this.buffer[y * 160 + x];\n    }\n\n    // Get the palette mapping from a given palette byte as stored in memory\n    // A palette will map a tile color to a final palette color index\n    // used with Screen.colors to get a shade of grey\n    static getPalette(paletteByte) {\n        let palette: number[] = [];\n        for (var i = 0; i < 8; i += 2) {\n            let shade = (paletteByte & (3 << i)) >> i;\n            palette.push(shade);\n        }\n        return palette;\n    }\n}\n\n\nexport default GPU;\n"
  },
  {
    "path": "src/display/screen.ts",
    "content": "// Screen device\nclass Screen {\n    canvas: HTMLCanvasElement;\n    context: CanvasRenderingContext2D;\n    pixelSize: number;\n    imageData: ImageData;\n\n    constructor(canvas: HTMLCanvasElement, pixelSize: number) {\n        this.context = canvas.getContext('2d') as CanvasRenderingContext2D;\n        this.canvas = canvas;\n        this.pixelSize = pixelSize || 1;\n        this.initImageData();\n    }\n\n    // Palette colors (RGB)\n    static colors = [\n        [0xFF, 0xFF, 0xFF],\n        [0xAA, 0xAA, 0xAA],\n        [0x55, 0x55, 0x55],\n        [0x00, 0x00, 0x00]\n    ];\n\n    static physics = {\n        WIDTH    : 160,\n        HEIGHT   : 144,\n        FREQUENCY: 60\n    };\n\n    setPixelSize(pixelSize: number) {\n        this.pixelSize = pixelSize;\n        this.initImageData();\n    }\n\n    initImageData() {\n        this.canvas.width = Screen.physics.WIDTH * this.pixelSize;\n        this.canvas.height = Screen.physics.HEIGHT * this.pixelSize;\n        this.imageData = this.context.createImageData(this.canvas.width, this.canvas.height);\n        for (var i = 0; i < this.imageData.data.length; i++) {\n            this.imageData.data[i] = 255;\n        }\n    }\n\n    clearScreen() {\n        this.context.fillStyle = '#FFF';\n        this.context.fillRect(0, 0, Screen.physics.WIDTH * this.pixelSize, Screen.physics.HEIGHT * this.pixelSize);\n    }\n\n    fillImageData(buffer) {\n        for (var y = 0; y < Screen.physics.HEIGHT; y++) {\n            for (var py = 0; py < this.pixelSize; py++) {\n                var yOffset = (y * this.pixelSize + py) * this.canvas.width;\n                for (var x = 0; x < Screen.physics.WIDTH; x++) {\n                    for (var px = 0; px < this.pixelSize; px++) {\n                        var offset = yOffset + (x * this.pixelSize + px);\n                        var v = Screen.colors[buffer[y * Screen.physics.WIDTH + x] | 0];\n                        // set RGB values\n                        this.imageData.data[offset * 4] = v[0];\n                        this.imageData.data[offset * 4 + 1] = v[1];\n                        this.imageData.data[offset * 4 + 2] = v[2];\n                    }\n                }\n            }\n        }\n    }\n\n    render(buffer) {\n        this.fillImageData(buffer);\n        this.context.putImageData(this.imageData, 0, 0);\n    }\n}\n\nexport default Screen;\n"
  },
  {
    "path": "src/exception.ts",
    "content": "// This exception should be thrown whenever a critical feature that\n// has not been implemented is requested\nclass UnimplementedException extends Error {\n    fatal: boolean;\n\n    constructor(message: string, fatal?: boolean) {\n        super();\n        this.message = message;\n        if (fatal === undefined) {\n            fatal = true;\n        }\n        this.fatal = fatal || false;\n    }\n}\nexport default UnimplementedException;\n"
  },
  {
    "path": "src/ext_ram.ts",
    "content": "// Object for mapping the cartridge RAM\nclass ExtRam {\n    gameName: string;\n    extRam: number[];\n    ramSize: number;\n    ramBanksize: number;\n    ramBank: number;\n\n    constructor() {\n        this.ramSize = 0;\n        this.ramBank = 0;\n    }\n\n    loadRam(game: string, size: number) {\n        this.gameName = game;\n\n        this.ramSize = size;\n        this.ramBanksize = this.ramSize >= 0x2000 ? 8192 : 2048;\n\n        let key = this.getStorageKey();\n        let data = localStorage.getItem(key);\n        if (data == null) {\n            this.extRam = Array.apply(null, new Array(this.ramSize)).map(function(){return 0;});\n        } else {\n            this.extRam = JSON.parse(data);\n            if (this.extRam.length != size) {\n                console.error('Found RAM data but not matching expected size.');\n            }\n        }\n    }\n\n    setRamBank(bank: number) {\n        this.ramBank = bank;\n    }\n\n    manageWrite(offset: number, value: number) {\n        this.extRam[this.ramBank * 8192 + offset] = value;\n    }\n\n    manageRead(offset: number) {\n        return this.extRam[this.ramBank * 8192 + offset];\n    }\n\n    getStorageKey() {\n        return this.gameName + '_EXTRAM';\n    }\n\n    // Actually save the RAM in the physical storage (localStorage)\n    saveRamData() {\n        localStorage.setItem(this.getStorageKey(), JSON.stringify(this.extRam));\n    }\n}\n\nexport default ExtRam;\n"
  },
  {
    "path": "src/input/gamepad.ts",
    "content": "import {JoypadDevice} from './input';\n\n// This is the default buttons mapping for the Gamepad API\n//\n// Any other mapping can be provided as a constructor argument of the Gamepad object\n// An alternative mapping should be an object with keys being the indexes\n// of the gamepad buttons and values the normalized gameboy button names\nlet standardMapping = {\n    0: 'A',\n    1: 'B',\n    8: 'SELECT',\n    9: 'START',\n    12: 'UP',\n    13: 'DOWN',\n    14: 'LEFT',\n    15: 'RIGHT',\n};\n\n// Gamepad listener\n// Communication layer between the Gamepad API and the Input class\n// Any physical controller can be used but the mapping should be provided\n// in order to get an optimal layout of the buttons (see above)\nclass Gamepad implements JoypadDevice {\n    gamepad: globalThis.Gamepad;\n    state = {A:0,B:0,START:0,SELECT:0,LEFT:0,RIGHT:0,UP:0,DOWN:0};\n    pullInterval: ReturnType<typeof setInterval>;\n    onPress: Function;\n    onRelease: Function;\n    buttonMapping: object;\n\n    constructor(mapping?: object) {\n        this.buttonMapping = mapping || standardMapping;\n    }\n\n    // Initialize the keyboard listeners and set up the callbacks\n    // for button press / release\n    init(canvas: HTMLElement, onPress: Function, onRelease: Function) {\n        this.onPress = onPress;\n        this.onRelease = onRelease;\n\n        let self = this;\n        window.addEventListener('gamepadconnected', function(e) {\n            self.gamepad = e.gamepad;\n            self.activatePull();\n        });\n        window.addEventListener('gamepaddisconnected', function(e) {\n            self.deactivatePull();\n        });\n    }\n\n    activatePull() {\n        this.deactivatePull();\n        this.pullInterval = setInterval(this.pullState.bind(this), 100);\n    }\n\n    deactivatePull() {\n        clearInterval(this.pullInterval);\n    }\n\n    // Check the state of the current gamepad in order to detect any press/release action\n    pullState() {\n        for (let index in this.buttonMapping) {\n            let button = this.buttonMapping[index];\n            let oldState = this.state[button];\n            this.state[button] = this.gamepad.buttons[index].pressed;\n\n            if (this.state[button] == 1 && oldState == 0) {\n                this.managePress(button);\n            } else if (this.state[button] == 0 && oldState == 1) {\n                this.manageRelease(button);\n            }\n        }\n    }\n\n    managePress(key) {\n        this.onPress(key);\n    }\n\n    manageRelease(key) {\n        this.onRelease(key);\n    }\n}\n\nexport default Gamepad;\n"
  },
  {
    "path": "src/input/input.ts",
    "content": "import CPU from '../cpu';\nimport Memory from '../memory';\n\n// The Input management system\n//\n// The pressKey() and releaseKey() functions should be called by a device class\n// like GameboyJS.Keyboard after a physical button trigger event\n//\n// They rely on the name of the original buttons as parameters (see Input.keys)\n\nexport interface JoypadDevice {\n    init(canvas: HTMLElement, onPress: Function, onRelease: Function): void;\n}\n\nclass Input {\n    cpu: CPU;\n    memory: Memory;\n    P1: number;\n    state: number;\n    interruptQueue: Array<any>;\n    constructor(cpu: CPU, pad: JoypadDevice, canvas) {\n        this.cpu = cpu;\n        this.memory = cpu.memory;\n        this.P1 = 0xFF00;\n        this.state = 0;\n        this.interruptQueue = [];\n\n        pad.init(canvas, this.pressKey.bind(this), this.releaseKey.bind(this));\n    }\n\n    pressKey(key) {\n        this.delayInterrupt(key);\n    }\n\n    releaseKey(key) {\n        var mask = 0xFF - Input.keys[key];\n        this.state &= mask;\n    }\n\n    // do not send the interrupt right away, due to the way javascript works :\n    // the key event fires when no other code is running, meaning when the frame()\n    // in the GPU has finished rendering. This means the interrupt will always run\n    // at LY = 144, which prevents the game to generate entropy for the key press actions\n    //\n    // the event is stored in a queue which is processed the next time the LY register is\n    // at the randomly determined value\n    delayInterrupt(key) {\n        let ly = (Math.random() * 153) | 0;\n        this.interruptQueue.push({ly: ly, key: key});\n    }\n\n    update() {\n        if (this.interruptQueue.length > 0) { // check for interrupt to fire\n            if (this.interruptQueue[0].ly === this.memory.rb(this.cpu.gpu.LY)) {\n                let v = this.interruptQueue.shift();\n                this.state |= Input.keys[v.key];\n                this.cpu.requestInterrupt(CPU.INTERRUPTS.HILO);\n            }\n        }\n\n        var value = this.memory.rb(this.P1);\n        value = ((~value) & 0x30); // invert the value so 1 means 'active'\n        if (value & 0x10) { // direction keys listened\n            value |= (this.state & 0x0F);\n        } else if (value & 0x20) { // action keys listened\n            value |= ((this.state & 0xF0) >> 4);\n        } else if ((value & 0x30) === 0) { // no keys listened\n            value &= 0xF0;\n        }\n\n        value = ((~value) & 0x3F); // invert back\n        this.memory[this.P1] = value;\n    }\n\n    static keys = {\n        START:  0x80,\n        SELECT: 0x40,\n        B:      0x20,\n        A:      0x10,\n        DOWN:   0x08,\n        UP:     0x04,\n        LEFT:   0x02,\n        RIGHT:  0x01\n    };\n}\n\n\nexport default Input;\n"
  },
  {
    "path": "src/input/keyboard.ts",
    "content": "import {JoypadDevice} from './input';\n\n// Keyboard listener\n// Does the mapping between the keyboard and the Input class\nclass Keyboard implements JoypadDevice {\n    onPress: Function;\n    onRelease: Function;\n\n    // Initialize the keyboard listeners and set up the callbacks\n    // for button press / release\n    init(canvas: HTMLElement, onPress: Function, onRelease: Function) {\n        this.onPress = onPress;\n        this.onRelease = onRelease;\n        if (canvas.getAttribute('tabIndex') === null)  {\n            canvas.setAttribute('tabIndex', '1');\n        }\n\n        let self = this;\n        canvas.addEventListener('keydown', function(e) {\n            self.managePress(e.keyCode);\n            if (e.keyCode !== 9) // only keep Tab active\n                e.preventDefault();\n        });\n        canvas.addEventListener('keyup', function(e) {\n            self.manageRelease(e.keyCode);\n            if (e.keyCode !== 9) // only keep Tab active\n                e.preventDefault();\n        });\n    }\n\n    managePress(keycode) {\n        let key = this.translateKey(keycode);\n        if (key) {\n            this.onPress(key);\n        }\n    }\n\n    manageRelease(keycode) {\n        let key = this.translateKey(keycode);\n        if (key) {\n            this.onRelease(key);\n        }\n    }\n\n    // Transform a keyboard keycode into a key of the Input.keys object\n    translateKey(keycode) {\n        let key = '';\n        switch (keycode) {\n            case 71: // G\n                key = 'A';\n                break;\n            case 66: // B\n                key = 'B';\n                break;\n            case 72: // H\n                key = 'START';\n                break;\n            case 78: // N\n                key = 'SELECT';\n                break;\n            case 37: // left\n                key = 'LEFT';\n                break;\n            case 38: // up\n                key = 'UP';\n                break;\n            case 39: // right\n                key = 'RIGHT';\n                break;\n            case 40: // down\n                key = 'DOWN';\n                break;\n        }\n\n        return key;\n    }\n}\n\nexport default Keyboard;\n"
  },
  {
    "path": "src/instructions.ts",
    "content": "import Util from './util';\nimport {opcodeCbmap} from './opcodes';\n\n// List of CPU operations\n// Most operations have been factorized here to limit code redundancy\n//\n// How to read operations:\n// Uppercase letters qualify the kind of operation (LD = LOAD, INC = INCREMENT, etc.)\n// Lowercase letters are used to hint parameters :\n// r = register, n = 1 memory byte, sp = sp register,\n// a = suffix for memory address, i = bit index\n// Example : LDrrar = LOAD operation with two-registers memory address\n// as first parameter and one register value as second\n//\n// Underscore-prefixed functions are here to delegate the logic between similar operations,\n// they should not be called from outside\n//\n// It's up to each operation to update the CPU clock\nlet ops = {\n    LDrrnn: function(p, r1, r2) {p.wr(r2, p.memory.rb(p.r.pc));p.wr(r1, p.memory.rb(p.r.pc+1)); p.r.pc+=2;p.clock.c += 12;},\n    LDrrar: function(p, r1, r2, r3) {ops._LDav(p, Util.getRegAddr(p, r1, r2), p.r[r3]);p.clock.c += 8;},\n    LDrrra: function(p, r1, r2, r3) {p.wr(r1, p.memory.rb(Util.getRegAddr(p, r2, r3)));p.clock.c += 8;},\n    LDrn:   function(p, r1) {p.wr(r1, p.memory.rb(p.r.pc++));p.clock.c += 8;},\n    LDrr:   function(p, r1, r2) {p.wr(r1, p.r[r2]);p.clock.c += 4;},\n    LDrar:  function(p, r1, r2) {p.memory.wb(p.r[r1]+0xFF00, p.r[r2]);p.clock.c += 8;},\n    LDrra:  function(p, r1, r2) {p.wr(r1, p.memory.rb(p.r[r2]+0xFF00));p.clock.c += 8;},\n    LDspnn: function(p) {p.wr('sp', (p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc));p.r.pc+=2;p.clock.c += 12;},\n    LDsprr: function(p, r1, r2) {p.wr('sp', Util.getRegAddr(p, r1, r2));p.clock.c += 8;},\n    LDnnar: function(p, r1) {var addr=(p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc);p.memory.wb(addr,p.r[r1]);p.r.pc+=2; p.clock.c += 16;},\n    LDrnna: function(p, r1) {var addr=(p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc);p.wr(r1, p.memory.rb(addr));p.r.pc+=2; p.clock.c += 16;},\n    LDrrspn:function(p, r1, r2) {var rel = p.memory.rb(p.r.pc++);rel=Util.getSignedValue(rel);var val=p.r.sp + rel;\n        var c = (p.r.sp&0xFF) + (rel&0xFF) > 0xFF;var h = (p.r.sp & 0xF) + (rel & 0xF) > 0xF;val &= 0xFFFF;\n        var f = 0; if(h)f|=0x20;if(c)f|=0x10;p.wr('F', f);\n        p.wr(r1, val >> 8);p.wr(r2, val&0xFF);\n        p.clock.c+=12;},\n    LDnnsp: function(p) {var addr = p.memory.rb(p.r.pc++) + (p.memory.rb(p.r.pc++)<<8); ops._LDav(p, addr, p.r.sp & 0xFF);ops._LDav(p, addr+1, p.r.sp >> 8);p.clock.c+=20;},\n    LDrran: function(p, r1, r2){var addr = Util.getRegAddr(p, r1, r2);ops._LDav(p, addr, p.memory.rb(p.r.pc++));p.clock.c+=12;},\n    _LDav:  function(p, addr, val){p.memory.wb(addr, val);},\n    LDHnar: function(p, r1){p.memory.wb(0xFF00 + p.memory.rb(p.r.pc++), p.r[r1]);p.clock.c+=12;},\n    LDHrna: function(p, r1){p.wr(r1, p.memory.rb(0xFF00 + p.memory.rb(p.r.pc++)));p.clock.c+=12;},\n    INCrr:  function(p, r1, r2) {p.wr(r2, (p.r[r2]+1)&0xFF); if (p.r[r2] == 0) p.wr(r1, (p.r[r1]+1)&0xFF);p.clock.c += 8;},\n    INCrra: function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);var val = (p.memory.rb(addr)+1)&0xFF;var z = val==0;var h=(p.memory.rb(addr)&0xF)+1 > 0xF;\n        p.memory.wb(addr, val);\n        p.r.F&=0x10;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c+=12;},\n    INCsp:  function(p){p.wr('sp', p.r.sp+1); p.r.sp &= 0xFFFF; p.clock.c+=8;},\n    INCr:   function(p, r1) {var h = ((p.r[r1]&0xF) + 1)&0x10;p.wr(r1, (p.r[r1] + 1)&0xFF);var z = p.r[r1]==0;\n        p.r.F&=0x10;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c += 4;},\n    DECrr:  function(p, r1, r2) {p.wr(r2, (p.r[r2] - 1) & 0xFF); if (p.r[r2] == 0xFF) p.wr(r1, (p.r[r1] - 1)&0xFF);p.clock.c += 8;},\n    DECsp:  function(p){p.wr('sp', p.r.sp-1); p.r.sp &= 0xFFFF; p.clock.c+=8;},\n    DECr:   function(p, r1) {var h = (p.r[r1]&0xF) < 1;p.wr(r1, (p.r[r1] - 1) & 0xFF);var z = p.r[r1]==0;\n        p.r.F&=0x10;p.r.F|=0x40;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c += 4;},\n    DECrra: function(p, r1, r2){var addr = Util.getRegAddr(p, r1, r2);var val = (p.memory.rb(addr)-1)&0xFF;var z = val==0;var h=(p.memory.rb(addr)&0xF) < 1;\n        p.memory.wb(addr, val);\n        p.r.F&=0x10;p.r.F|=0x40;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;\n        p.clock.c+=12;},\n    ADDrr:  function(p, r1, r2) {var n = p.r[r2];ops._ADDrn(p, r1, n); p.clock.c += 4;},\n    ADDrn:  function(p, r1) {var n = p.memory.rb(p.r.pc++);ops._ADDrn(p, r1, n); p.clock.c+=8;},\n    _ADDrn: function(p, r1, n) {var h=((p.r[r1]&0xF)+(n&0xF))&0x10;p.wr(r1, p.r[r1]+n);var c=p.r[r1]&0x100;p.r[r1]&=0xFF;\n            var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);},\n    ADDrrrr:function(p, r1, r2, r3, r4) {ops._ADDrrn(p, r1, r2, (p.r[r3]<<8) + p.r[r4]); p.clock.c+=8;},\n    ADDrrsp:function(p, r1, r2) {ops._ADDrrn(p, r1, r2, p.r.sp); p.clock.c += 8;},\n    ADDspn: function(p) {var v = p.memory.rb(p.r.pc++);v = Util.getSignedValue(v);\n        var c = ((p.r.sp&0xFF) + (v&0xFF)) > 0xFF; var h = (p.r.sp & 0xF) + (v&0xF) > 0xF;\n        var f = 0; if(h)f|=0x20;if(c)f|=0x10;p.wr('F', f);\n        p.wr('sp', (p.r.sp + v) & 0xFFFF);\n        p.clock.c+=16;},\n    _ADDrrn:function(p, r1, r2, n) {var v1 = (p.r[r1]<<8) + p.r[r2];var v2 = n;\n        var res = v1 + v2;var c = res&0x10000;var h = ((v1&0xFFF) + (v2&0xFFF))&0x1000;var z = p.r.F&0x80;\n        res&=0xFFFF;p.r[r2]=res&0xFF;res=res>>8;p.r[r1]=res&0xFF;\n        var f=0;if(z)f|=0x80;if(h)f|=0x20;if(c)f|=0x10;p.r.F=f;},\n    ADCrr:  function(p, r1, r2) {var n = p.r[r2]; ops._ADCrn(p, r1, n); p.clock.c += 4;},\n    ADCrn:  function(p, r1) {var n = p.memory.rb(p.r.pc++); ops._ADCrn(p, r1, n); p.clock.c += 8;},\n    _ADCrn: function(p, r1, n) {\n        var c = p.r.F&0x10?1:0;var h=((p.r[r1]&0xF)+(n&0xF)+c)&0x10;\n        p.wr(r1, p.r[r1]+n+c);c=p.r[r1]&0x100;p.r[r1]&=0xFF;\n        var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.r.F=f;},\n    ADCrrra:function(p, r1, r2, r3) {var n = p.memory.rb(Util.getRegAddr(p, r2, r3)); ops._ADCrn(p, r1, n); p.clock.c += 8;},\n    ADDrrra:function(p, r1, r2, r3) {var v = p.memory.rb(Util.getRegAddr(p, r2, r3));var h=((p.r[r1]&0xF)+(v&0xF))&0x10;p.wr(r1, p.r[r1]+v);var c=p.r[r1]&0x100;p.r[r1]&=0xFF;\n        var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);\n        p.clock.c += 8;},\n    SUBr:   function(p, r1) {var n = p.r[r1];ops._SUBn(p, n);p.clock.c += 4;},\n    SUBn:   function(p) {var n = p.memory.rb(p.r.pc++);ops._SUBn(p, n);p.clock.c += 8;},\n    SUBrra: function(p, r1, r2) {var n = p.memory.rb(Util.getRegAddr(p, r1, r2));ops._SUBn(p, n);p.clock.c+=8;},\n    _SUBn:  function(p, n) {var c = p.r.A < n;var h = (p.r.A&0xF) < (n&0xF);\n        p.wr('A', p.r.A - n);p.r.A&=0xFF; var z = p.r.A==0;\n        var f = 0x40;if (z)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);},\n    SBCn:   function(p) {var n = p.memory.rb(p.r.pc++); ops._SBCn(p, n); p.clock.c += 8;},\n    SBCr:   function(p, r1) {var n = p.r[r1]; ops._SBCn(p, n); p.clock.c += 4;},\n    SBCrra: function(p, r1, r2) {var v = p.memory.rb((p.r[r1] << 8) + p.r[r2]); ops._SBCn(p, v); p.clock.c += 8;},\n    _SBCn:  function(p, n) {var carry = p.r.F&0x10 ? 1 : 0;\n        var c = p.r.A < n + carry;var h = (p.r.A&0xF) < (n&0xF) + carry;\n        p.wr('A', p.r.A - n - carry); p.r.A&=0xFF; var z = p.r.A == 0;\n        var f = 0x40;if (z)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.r.F=f;},\n    ORr:    function(p, r1) {p.r.A|=p.r[r1];p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 4;},\n    ORn:    function(p) {p.r.A|=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    ORrra:  function(p, r1, r2) {p.r.A|=p.memory.rb((p.r[r1] << 8)+ p.r[r2]);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    ANDr:   function(p, r1) {p.r.A&=p.r[r1];p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 4;},\n    ANDn:   function(p) {p.r.A&=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 8;},\n    ANDrra: function(p, r1, r2) {p.r.A&=p.memory.rb(Util.getRegAddr(p, r1, r2));p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 8;},\n    XORr:   function(p, r1) {p.r.A^=p.r[r1];p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 4;},\n    XORn:   function(p) {p.r.A^=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    XORrra: function(p, r1, r2) {p.r.A^=p.memory.rb((p.r[r1] << 8)+ p.r[r2]);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},\n    CPr:    function(p, r1) {var n = p.r[r1];ops._CPn(p, n); p.clock.c += 4;},\n    CPn:    function(p) {var n =p.memory.rb(p.r.pc++);ops._CPn(p, n);p.clock.c+=8;},\n    CPrra:  function(p, r1, r2) {var n = p.memory.rb(Util.getRegAddr(p, r1, r2));ops._CPn(p, n);p.clock.c+=8;},\n    _CPn:   function(p, n) {\n        var c = p.r.A < n;var z = p.r.A == n;var h = (p.r.A&0xF) < (n&0xF);\n        var f = 0x40;if(z)f+=0x80;if (h)f+=0x20;if (c)f+=0x10;p.r.F=f;},\n    RRCr:   function(p, r1) {p.r.F=0;var out=p.r[r1] & 0x01;if(out)p.r.F|=0x10;p.r[r1]=(p.r[r1]>>1)|(out*0x80);if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RRCrra: function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);p.r.F=0;var out=p.memory.rb(addr)&0x01;if(out)p.r.F|=0x10;p.memory.wb(addr, (p.memory.rb(addr)>>1)|(out*0x80));if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    RLCr:   function(p, r1) {p.r.F=0;var out=p.r[r1]&0x80?1:0;if(out)p.r.F|=0x10;p.r[r1]=((p.r[r1]<<1)+out)&0xFF;if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RLCrra: function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);p.r.F=0;var out=p.memory.rb(addr)&0x80?1:0;if(out)p.r.F|=0x10;p.memory.wb(addr, ((p.memory.rb(addr)<<1)+out)&0xFF);if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    RLr:    function(p, r1) {var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.r[r1]&0x80;out?p.r.F|=0x10:p.r.F&=0xEF;p.r[r1]=((p.r[r1]<<1)+c)&0xFF;if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RLrra:  function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.memory.rb(addr)&0x80;out?p.r.F|=0x10:p.r.F&=0xEF;p.memory.wb(addr,((p.memory.rb(addr)<<1)+c)&0xFF);if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    RRr:    function(p, r1) {var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.r[r1]&0x01;out?p.r.F|=0x10:p.r.F&=0xEF;p.r[r1]=(p.r[r1]>>1)|(c*0x80);if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    RRrra:  function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.memory.rb(addr)&0x01;out?p.r.F|=0x10:p.r.F&=0xEF;p.memory.wb(addr,(p.memory.rb(addr)>>1)|(c*0x80));if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    SRAr:   function(p, r1) {p.r.F = 0;if (p.r[r1]&0x01)p.r.F|=0x10;var msb=p.r[r1]&0x80;p.r[r1]=(p.r[r1]>>1)|msb;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    SRArra: function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x01)p.r.F|=0x10;var msb=p.memory.rb(addr)&0x80;p.memory.wb(addr, (p.memory.rb(addr)>>1)|msb);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    SLAr:   function(p, r1) {p.r.F = 0;if (p.r[r1]&0x80)p.r.F|=0x10;p.r[r1]=(p.r[r1]<<1)&0xFF;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    SLArra: function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x80)p.r.F|=0x10;p.memory.wb(addr, (p.memory.rb(addr)<<1)&0xFF);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    SRLr:   function(p, r1) {p.r.F = 0;if (p.r[r1]&0x01)p.r.F|=0x10;p.r[r1]=p.r[r1]>>1;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},\n    SRLrra: function(p, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x01)p.r.F|=0x10;p.memory.wb(addr, p.memory.rb(addr)>>1);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},\n    BITir:  function(p, i, r1) {var mask=1<<i;var z=(p.r[r1]&mask)?0:1;var f=p.r.F&0x10;f |= 0x20;if(z)f|=0x80;p.r.F=f;p.clock.c+=4;},\n    BITirra:function(p, i, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);var mask=1<<i;var z=(p.memory.rb(addr)&mask)?0:1;var f=p.r.F&0x10;f |= 0x20;if(z)f|=0x80;p.r.F=f;p.clock.c+=8;},\n    SETir:  function(p, i, r1) {var mask=1<<i;p.r[r1]|=mask;p.clock.c += 4;},\n    SETirra:function(p, i, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);var mask=1<<i;p.memory.wb(addr, p.memory.rb(addr)|mask);p.clock.c += 12;},\n    RESir:  function(p, i, r1) {var mask=0xFF - (1<<i);p.r[r1]&=mask;p.clock.c += 4;},\n    RESirra:function(p, i, r1, r2) {var addr = Util.getRegAddr(p, r1, r2);var mask=0xFF - (1<<i);p.memory.wb(addr, p.memory.rb(addr)&mask);p.clock.c += 12;},\n    SWAPr:  function(p, r1) {p.r[r1] = ops._SWAPn(p, p.r[r1]);p.clock.c+=4;},\n    SWAPrra:function(p, r1, r2){var addr = (p.r[r1] << 8)+ p.r[r2]; p.memory.wb(addr, ops._SWAPn(p, p.memory.rb(addr))); p.clock.c+=12;},\n    _SWAPn: function(p, n){p.r.F = n==0?0x80:0;return ((n&0xF0) >> 4) | ((n&0x0F) << 4);},\n    JPnn:   function(p) {p.wr('pc', (p.memory.rb(p.r.pc+1) << 8) + p.memory.rb(p.r.pc));p.clock.c += 16;},\n    JRccn:  function(p, cc) {if (Util.testFlag(p, cc)){var v=p.memory.rb(p.r.pc++);v=Util.getSignedValue(v);p.r.pc += v;p.clock.c+=4;}else{p.r.pc++;}p.clock.c += 8;},\n    JPccnn: function(p, cc) {if (Util.testFlag(p, cc)){p.wr('pc', (p.memory.rb(p.r.pc+1) << 8) + p.memory.rb(p.r.pc));p.clock.c+=4;}else{p.r.pc+=2;}p.clock.c += 12;},\n    JPrr:   function(p, r1, r2) {p.r.pc = (p.r[r1] << 8) + p.r[r2];p.clock.c += 4;},\n    JRn:    function(p) {var v=p.memory.rb(p.r.pc++);v=Util.getSignedValue(v);p.r.pc += v;p.clock.c += 12;},\n    PUSHrr: function(p, r1, r2) {p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp, p.r[r1]);p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp, p.r[r2]);p.clock.c+=16;},\n    POPrr:  function(p, r1, r2) {p.wr(r2, p.memory.rb(p.r.sp));p.wr('sp', p.r.sp+1);p.wr(r1, p.memory.rb(p.r.sp));p.wr('sp', p.r.sp+1);p.clock.c+=12;},\n    RSTn:   function(p, n) {p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp,p.r.pc>>8);p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp,p.r.pc&0xFF);p.r.pc=n;p.clock.c+=16;},\n    RET:    function(p) {p.r.pc = p.memory.rb(p.r.sp);p.wr('sp', p.r.sp+1);p.r.pc+=p.memory.rb(p.r.sp)<<8;p.wr('sp', p.r.sp+1);p.clock.c += 16;},\n    RETcc:  function(p, cc) {if (Util.testFlag(p, cc)){p.r.pc = p.memory.rb(p.r.sp);p.wr('sp', p.r.sp+1);p.r.pc+=p.memory.rb(p.r.sp)<<8;p.wr('sp', p.r.sp+1);p.clock.c+=12;}p.clock.c+=8;},\n    CALLnn: function(p) {ops._CALLnn(p); p.clock.c+=24;},\n    CALLccnn:function(p, cc) {if (Util.testFlag(p, cc)){ops._CALLnn(p);p.clock.c+=12;}else{p.r.pc+=2;}p.clock.c+=12; },\n    _CALLnn:function(p){p.wr('sp', p.r.sp - 1); p.memory.wb(p.r.sp, ((p.r.pc+2)&0xFF00)>>8);\n        p.wr('sp', p.r.sp - 1); p.memory.wb(p.r.sp, (p.r.pc+2)&0x00FF);\n        var j=p.memory.rb(p.r.pc)+(p.memory.rb(p.r.pc+1)<<8);p.r.pc=j;},\n    CPL:    function(p) {p.wr('A', (~p.r.A)&0xFF);p.r.F|=0x60,p.clock.c += 4;},\n    CCF:    function(p) {p.r.F&=0x9F;p.r.F&0x10?p.r.F&=0xE0:p.r.F|=0x10;p.clock.c += 4;},\n    SCF:    function(p) {p.r.F&=0x9F;p.r.F|=0x10;p.clock.c+=4;},\n    DAA:    function(p) {\n        var sub = (p.r.F&0x40) ? 1 : 0; var h = (p.r.F&0x20)?1:0;var c = (p.r.F&0x10)?1:0;\n        if (sub) {\n            if (h) {\n                p.r.A = (p.r.A - 0x6) & 0xFF;\n            }\n            if (c) {\n                p.r.A -= 0x60;\n            }\n        } else {\n            if ((p.r.A&0xF) > 9 || h) {\n                p.r.A += 0x6;\n            }\n            if (p.r.A > 0x9F || c) {\n                p.r.A += 0x60;\n            }\n        }\n        if (p.r.A&0x100) c = 1;\n\n        p.r.A &= 0xFF;\n        p.r.F &= 0x40;if (p.r.A == 0) p.r.F|=0x80;if (c) p.r.F|=0x10;\n        p.clock.c += 4;\n    },\n    HALT:   function(p) {p.halt(); p.clock.c+=4;},\n    DI:     function(p) {p.disableInterrupts();p.clock.c += 4;},\n    EI:     function(p) {p.enableInterrupts();p.clock.c += 4;},\n    RETI:   function(p) {p.enableInterrupts();ops.RET(p);},\n    CB:     function(p) {var opcode = p.memory.rb(p.r.pc++);\n        opcodeCbmap[opcode](p);\n        p.clock.c+=4;}\n};\n\nexport {ops as cpuOps};\n"
  },
  {
    "path": "src/main.ts",
    "content": "import Rom from './rom/rom';\nimport RomFileReader from './rom/file_reader';\nimport RomDropFileReader from './rom/drop_file_reader';\nimport RomAjaxReader from './rom/ajax_reader';\nimport Keyboard from './input/keyboard';\nimport Util from './util';\nimport CPU from './cpu';\nimport GPU from './display/gpu';\nimport Screen from './display/screen';\nimport Input, { JoypadDevice } from './input/input'\nimport UnimplementedException from './exception';\nimport Debug from './debug';\n\nlet defaultOptions = {\n    pad: {class: Keyboard, mapping: null},\n    zoom: 1,\n    romReaders: [],\n    statusContainerId: 'status',\n    gameNameContainerId: 'game-name',\n    errorContainerId: 'error'\n};\n\n// Gameboy class\n//\n// This object is the entry point of the application\n// Will delegate user actions to the emulated devices\n// and provide information where needed\nclass Gameboy {\n    options;\n    cpu: CPU;\n    screen: Screen;\n    input: Input;\n    pad: JoypadDevice;\n\n    statusContainer: HTMLElement;\n    gameNameContainer: HTMLElement;\n    errorContainer: HTMLElement;\n\n    constructor(canvas, options) {\n        options = options || {};\n        this.options = Util.extend({}, defaultOptions, options);\n\n        var cpu = new CPU(this);\n        var screen = new Screen(canvas, this.options.zoom);\n        var gpu = new GPU(screen, cpu);\n        cpu.gpu = gpu;\n\n        var pad = new this.options.pad.class(this.options.pad.mapping);\n        var input = new Input(cpu, pad, canvas);\n        cpu.input = input;\n\n        this.cpu = cpu;\n        this.screen = screen;\n        this.input = input;\n        this.pad = pad;\n\n        this.createRom(this.options.romReaders);\n\n        this.statusContainer   = document.getElementById(this.options.statusContainerId) || document.createElement('div');\n        this.gameNameContainer = document.getElementById(this.options.gameNameContainerId) || document.createElement('div');\n        this.errorContainer    = document.getElementById(this.options.errorContainerId) || document.createElement('div');\n    }\n\n    // Create the ROM object and bind one or more readers\n    createRom(readers: any[]) {\n        var rom = new Rom(this);\n        if (readers.length == 0) {\n            // add the default rom reader\n            var romReader = new RomFileReader();\n            rom.addReader(romReader);\n        } else {\n            for (var i in readers) {\n                if (readers.hasOwnProperty(i)) {\n                    rom.addReader(readers[i]);\n                }\n            }\n        }\n    }\n\n    startRom(rom) {\n        this.errorContainer.classList.add('hide');\n        this.cpu.reset();\n        try {\n            this.cpu.loadRom(rom.data);\n            this.setStatus('Game Running :');\n            this.setGameName(this.cpu.getGameName());\n            this.cpu.run();\n            this.screen.canvas.focus();\n        } catch (e) {\n            this.handleException(e);\n        }\n    }\n\n    pause(value) {\n        if (value) {\n            this.setStatus('Game Paused :');\n            this.cpu.pause();\n        } else {\n            this.setStatus('Game Running :');\n            this.cpu.unpause();\n        }\n    }\n\n    error(message) {\n        this.setStatus('Error during execution');\n        this.setError('An error occurred during execution:' + message);\n        this.cpu.stop();\n    }\n\n    setStatus(status) {\n        this.statusContainer.innerHTML = status;\n    }\n\n    // Display an error message\n    setError(message) {\n        this.errorContainer.classList.remove('hide');\n        this.errorContainer.innerHTML = message;\n    }\n\n    // Display the name of the game running\n    setGameName(name) {\n        this.gameNameContainer.innerHTML = name;\n    }\n\n    setSoundEnabled(value) {\n        if (value) {\n            this.cpu.apu.connect();\n        } else {\n            this.cpu.apu.disconnect();\n        }\n    }\n    setScreenZoom(value) {\n        this.screen.setPixelSize(value);\n    }\n    handleException(e) {\n        if (e instanceof UnimplementedException) {\n            if (e.fatal) {\n                this.error('This cartridge is not supported ('+ e.message +')');\n            } else {\n                console.error(e.message);\n            }\n        } else {\n            throw e;\n        }\n    }\n}\n\nexport {\n    Gameboy,\n    RomFileReader,\n    RomDropFileReader,\n    RomAjaxReader,\n    Util,\n    Debug\n};\n"
  },
  {
    "path": "src/mbc.ts",
    "content": "import ExtRam from './ext_ram';\nimport Memory from './memory';\nimport UnimplementedException from './exception';\n\n// Memory bank controllers\n\nabstract class MBC {\n    memory: Memory;\n    extRam: ExtRam;\n\n    constructor(memory: Memory) {\n        this.memory = memory;\n        this.extRam = new ExtRam();\n    }\n\n    // Create an MBC instance depending on the type specified in the cartridge\n    static getMbcInstance(memory, type) {\n        var instance;\n        switch (type) {\n            case 0x00:\n                instance = new MBC0(memory);\n                break;\n            case 0x01: case 0x02: case 0x03:\n                instance = new MBC1(memory);\n                break;\n            case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13:\n                instance = new MBC3(memory);\n                break;\n            case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E:\n                instance = new MBC5(memory);\n                break;\n            default:\n                throw new UnimplementedException('MBC type not supported');\n        }\n\n        return instance;\n    }\n}\n\n\nclass MBC1 extends MBC {\n    romBankNumber = 1;\n    mode = 0; // mode 0 = ROM, mode 1 = RAM\n    ramEnabled = true;\n\n    loadRam(game, size) {\n        this.extRam.loadRam(game, size);\n    }\n\n    manageWrite(addr, value) {\n        switch (addr & 0xF000) {\n            case 0x0000: case 0x1000: // enable RAM\n                this.ramEnabled = (value & 0x0A) ? true : false;\n                if (!this.ramEnabled) {\n                    this.extRam.saveRamData();\n                }\n                break;\n            case 0x2000: case 0x3000: // ROM bank number lower 5 bits\n                value &= 0x1F;\n                if (value == 0) value = 1;\n                var mask = this.mode ? 0 : 0xE0;\n                this.romBankNumber = (this.romBankNumber & mask) +value;\n                this.memory.loadRomBank(this.romBankNumber);\n                break;\n            case 0x4000: case 0x5000: // RAM bank or high bits ROM\n                value &= 0x03;\n                if (this.mode == 0) { // ROM upper bits\n                    this.romBankNumber = (this.romBankNumber&0x1F) | (value << 5);\n                    this.memory.loadRomBank(this.romBankNumber);\n                } else { // RAM bank\n                    this.extRam.setRamBank(value);\n                }\n                break;\n            case 0x6000: case 0x7000: // ROM / RAM mode\n                this.mode = value & 1;\n                break;\n            case 0xA000: case 0xB000:\n                this.extRam.manageWrite(addr - 0xA000, value);\n                break;\n        }\n    }\n    readRam(addr) {\n        return this.extRam.manageRead(addr - 0xA000);\n    }\n}\n\nclass MBC3 extends MBC {\n    romBankNumber = 1;\n    ramEnabled = true;\n\n    loadRam(game, size) {\n        this.extRam.loadRam(game, size);\n    }\n\n    manageWrite(addr, value) {\n        switch (addr & 0xF000) {\n            case 0x0000: case 0x1000: // enable RAM\n                this.ramEnabled = (value & 0x0A) ? true : false;\n                if (!this.ramEnabled) {\n                    this.extRam.saveRamData();\n                }\n                break;\n            case 0x2000: case 0x3000: // ROM bank number\n                value &= 0x7F;\n                if (value == 0) value = 1;\n                this.romBankNumber = value;\n                this.memory.loadRomBank(this.romBankNumber);\n                break;\n            case 0x4000: case 0x5000: // RAM bank\n                this.extRam.setRamBank(value);\n                break;\n            case 0x6000: case 0x7000: // Latch clock data\n                throw new UnimplementedException('cartridge clock not supported', false);\n                break;\n            case 0xA000: case 0xB000:\n                this.extRam.manageWrite(addr - 0xA000, value);\n                break;\n        }\n    }\n    readRam(addr) {\n        return this.extRam.manageRead(addr - 0xA000);\n    }\n}\n\n\n// declare MBC5 for compatibility with most cartriges\n// does not support rumble feature\nlet MBC5 = MBC3;\n\n// MBC0 exists for consistency and manages the no-MBC cartriges\nclass MBC0 extends MBC {\n    manageWrite(addr, value) {\n        this.memory.loadRomBank(value);\n        if (addr >= 0xA000 && addr < 0xC000) {\n            this.extRam.manageWrite(addr - 0xA000, value);\n            this.extRam.saveRamData();\n        }\n    }\n    readRam(addr) {\n        return this.extRam.manageRead(addr - 0xA000);\n    }\n    loadRam(game, size) {\n        this.extRam.loadRam(game, size);\n    }\n}\n\nexport default MBC;\n"
  },
  {
    "path": "src/memory.ts",
    "content": "import MBC from './mbc';\nimport CPU from './cpu';\n\n// Memory unit\nclass Memory extends Array {\n    MEM_SIZE = 65536; // 64KB\n    MBCtype = 0;\n    banksize = 0x4000;\n    rom: Uint8Array;\n    mbc;\n    cpu: CPU;\n\n    constructor(cpu: CPU) {\n        super();\n        this.cpu = cpu;\n    }\n\n    static addresses = {\n        VRAM_START : 0x8000,\n        VRAM_END   : 0x9FFF,\n\n        EXTRAM_START : 0xA000,\n        EXTRAM_END   : 0xBFFF,\n\n        OAM_START : 0xFE00,\n        OAM_END   : 0xFE9F,\n\n        DEVICE_START: 0xFF00,\n        DEVICE_END:   0xFF7F\n    };\n\n    reset() {\n        this.length = this.MEM_SIZE;\n        for (let i = Memory.addresses.VRAM_START; i <= Memory.addresses.VRAM_END; i++) {\n            this[i] = 0;\n        }\n        for (let i = Memory.addresses.DEVICE_START; i <= Memory.addresses.DEVICE_END; i++) {\n            this[i] = 0;\n        }\n        this[0xFFFF] = 0;\n        this[0xFF47] = 0xFC;\n        this[0xFF04] = 0x18;\n    }\n\n    setRomData(data: Uint8Array) {\n        this.rom = data;\n        this.loadRomBank(0);\n        this.mbc = MBC.getMbcInstance(this, this[0x147]);\n        this.loadRomBank(1);\n        this.mbc.loadRam(this.cpu.getGameName(), this.cpu.getRamSize());\n    }\n\n    loadRomBank(index) {\n        var start = index ? 0x4000 : 0x0;\n        var romStart = index * 0x4000;\n        for (var i = 0; i < this.banksize; i++) {\n            this[i + start] = this.rom[romStart + i];\n        }\n    }\n\n    // Video ram accessor\n    vram(address) {\n        if (address < Memory.addresses.VRAM_START || address > Memory.addresses.VRAM_END) {\n            throw 'VRAM access in out of bounds address ' + address;\n        }\n\n        return this[address];\n    }\n\n    // OAM ram accessor\n    oamram(address) {\n        if (address < Memory.addresses.OAM_START || address > Memory.addresses.OAM_END) {\n            throw 'OAMRAM access in out of bounds address ' + address;\n        }\n\n        return this[address];\n    }\n\n    // Device ram accessor\n    deviceram(address: number, value?: number) {\n        if (address < Memory.addresses.DEVICE_START || address > Memory.addresses.DEVICE_END) {\n            throw 'Device RAM access in out of bounds address ' + address;\n        }\n        if (typeof value === \"undefined\") {\n            return this[address];\n        } else {\n            this[address] = value;\n        }\n\n    }\n\n    // Memory read proxy function\n    // Used to centralize memory read access\n    rb(addr: number): number {\n        if (addr >= 0xFF10 && addr < 0xFF40) {\n            var mask = apuMask[addr - 0xFF10];\n            return this[addr] | mask;\n        }\n        if ((addr >= 0xA000 && addr < 0xC000)) {\n            return this.mbc.readRam(addr);\n        }\n        return this[addr];\n    }\n\n    // Memory write proxy function\n    // Used to centralize memory writes and delegate specific behaviour\n    // to the correct units\n    wb(addr: number, value: number) {\n        if (addr < 0x8000 || (addr >= 0xA000 && addr < 0xC000)) { // MBC\n            this.mbc.manageWrite(addr, value);\n        } else if (addr >= 0xFF10 && addr <= 0xFF3F) { // sound registers\n            this.cpu.apu.manageWrite(addr, value);\n        } else if (addr == 0xFF00) { // input register\n            this[addr] = ((this[addr] & 0x0F) | (value & 0x30));\n        } else {\n            this[addr] = value;\n            if ((addr & 0xFF00) == 0xFF00) {\n                if (addr == 0xFF02) {\n                    if (value & 0x80) {\n                        this.cpu.enableSerialTransfer();\n                    }\n                }\n                if (addr == 0xFF04) {\n                    this.cpu.resetDivTimer();\n                }\n                if (addr == 0xFF46) { // OAM DMA transfer\n                    this.dmaTransfer(value);\n                }\n            }\n        }\n    }\n\n    // Start a DMA transfer (OAM data from cartrige to RAM)\n    dmaTransfer(startAddressPrefix) {\n        var startAddress = (startAddressPrefix << 8);\n        for (var i = 0; i < 0xA0; i++) {\n            this[Memory.addresses.OAM_START + i] = this[startAddress + i];\n        }\n    }\n}\n\n// Bitmasks for audio addresses reads\nlet apuMask = [\n0x80,0x3F,0x00,0xFF,0xBF, // NR10-NR15\n0xFF,0x3F,0x00,0xFF,0xBF, // NR20-NR25\n0x7F,0xFF,0x9F,0xFF,0xBF, // NR30-NR35\n0xFF,0xFF,0x00,0x00,0xBF, // NR40-NR45\n0x00,0x00,0x70,           // NR50-NR52\n0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,\n0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Wave RAM\n0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00\n];\n\nexport default Memory;\n"
  },
  {
    "path": "src/opcodes.ts",
    "content": "import {cpuOps as ops} from './instructions';\n\n// Each opcode (0 to 0xFF) is associated to a CPU operation\n// CPU operations are implemented separately\n// The cbmap object holds operations for CB prefixed opcodes (0xCB00 to 0xCBFF)\n// Non-existent opcodes are commented out and marked empty\nlet map = {\n    0x00: function(p){p.clock.c += 4;},\n    0x01: function(p){ops.LDrrnn(p, 'B', 'C');},\n    0x02: function(p){ops.LDrrar(p, 'B', 'C', 'A');},\n    0x03: function(p){ops.INCrr(p, 'B', 'C');},\n    0x04: function(p){ops.INCr(p, 'B');},\n    0x05: function(p){ops.DECr(p, 'B');},\n    0x06: function(p){ops.LDrn(p, 'B');},\n    0x07: function(p){var out=p.r.A & 0x80?1:0; out ? p.r.F=0x10:p.r.F=0; p.wr('A', ((p.r.A<<1)+out)&0xFF);p.clock.c+=4;},\n    0x08: function(p){ops.LDnnsp(p);},\n    0x09: function(p){ops.ADDrrrr(p, 'H', 'L', 'B', 'C');},\n    0x0A: function(p){ops.LDrrra(p, 'A', 'B', 'C');},\n    0x0B: function(p){ops.DECrr(p, 'B', 'C');},\n    0x0C: function(p){ops.INCr(p, 'C');},\n    0x0D: function(p){ops.DECr(p, 'C');},\n    0x0E: function(p){ops.LDrn(p, 'C');},\n    0x0F: function(p){var out=p.r.A & 0x01; out ? p.r.F=0x10:p.r.F=0; p.wr('A', (p.r.A>>1)|(out*0x80));p.clock.c+=4;},\n\n    0x10: function(p){p.r.pc++;p.clock.c+=4;},\n    0x11: function(p){ops.LDrrnn(p, 'D', 'E');},\n    0x12: function(p){ops.LDrrar(p, 'D', 'E', 'A');},\n    0x13: function(p){ops.INCrr(p, 'D', 'E');},\n    0x14: function(p){ops.INCr(p, 'D');},\n    0x15: function(p){ops.DECr(p, 'D');},\n    0x16: function(p){ops.LDrn(p, 'D');},\n    0x17: function(p){var c = (p.r.F&0x10)?1:0;var out=p.r.A & 0x80?1:0; out ? p.r.F=0x10:p.r.F=0; p.wr('A',((p.r.A<<1)+c)&0xFF);p.clock.c+=4;},\n    0x18: function(p){ops.JRn(p);},\n    0x19: function(p){ops.ADDrrrr(p, 'H', 'L', 'D', 'E');},\n    0x1A: function(p){ops.LDrrra(p, 'A', 'D', 'E');},\n    0x1B: function(p){ops.DECrr(p, 'D', 'E');},\n    0x1C: function(p){ops.INCr(p, 'E');},\n    0x1D: function(p){ops.DECr(p, 'E');},\n    0x1E: function(p){ops.LDrn(p, 'E');},\n    0x1F: function(p){var c = (p.r.F&0x10)?1:0;var out=p.r.A & 0x01; out ? p.r.F=0x10:p.r.F=0; p.wr('A', (p.r.A>>1)|(c*0x80));p.clock.c+=4;},\n\n    0x20: function(p){ops.JRccn(p, 'NZ');},\n    0x21: function(p){ops.LDrrnn(p, 'H', 'L');},\n    0x22: function(p){ops.LDrrar(p, 'H', 'L', 'A');ops.INCrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x23: function(p){ops.INCrr(p, 'H', 'L');},\n    0x24: function(p){ops.INCr(p, 'H');},\n    0x25: function(p){ops.DECr(p, 'H');},\n    0x26: function(p){ops.LDrn(p, 'H');},\n    0x27: function(p){ops.DAA(p);},\n    0x28: function(p){ops.JRccn(p, 'Z');},\n    0x29: function(p){ops.ADDrrrr(p, 'H', 'L', 'H', 'L');},\n    0x2A: function(p){ops.LDrrra(p, 'A', 'H', 'L');ops.INCrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x2B: function(p){ops.DECrr(p, 'H', 'L');},\n    0x2C: function(p){ops.INCr(p, 'L');},\n    0x2D: function(p){ops.DECr(p, 'L');},\n    0x2E: function(p){ops.LDrn(p, 'L');},\n    0x2F: function(p){ops.CPL(p);},\n\n    0x30: function(p){ops.JRccn(p, 'NC');},\n    0x31: function(p){ops.LDspnn(p);},\n    0x32: function(p){ops.LDrrar(p, 'H', 'L', 'A');ops.DECrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x33: function(p){ops.INCsp(p);},\n    0x34: function(p){ops.INCrra(p, 'H', 'L');},\n    0x35: function(p){ops.DECrra(p, 'H', 'L');},\n    0x36: function(p){ops.LDrran(p, 'H', 'L');},\n    0x37: function(p){ops.SCF(p);},\n    0x38: function(p){ops.JRccn(p, 'C');},\n    0x39: function(p){ops.ADDrrsp(p, 'H', 'L');},\n    0x3A: function(p){ops.LDrrra(p, 'A', 'H', 'L');ops.DECrr(p, 'H', 'L');p.clock.c -= 8;},\n    0x3B: function(p){ops.DECsp(p);},\n    0x3C: function(p){ops.INCr(p, 'A');},\n    0x3D: function(p){ops.DECr(p, 'A');},\n    0x3E: function(p){ops.LDrn(p, 'A');},\n    0x3F: function(p){ops.CCF(p);},\n\n    0x40: function(p){ops.LDrr(p, 'B', 'B');},\n    0x41: function(p){ops.LDrr(p, 'B', 'C');},\n    0x42: function(p){ops.LDrr(p, 'B', 'D');},\n    0x43: function(p){ops.LDrr(p, 'B', 'E');},\n    0x44: function(p){ops.LDrr(p, 'B', 'H');},\n    0x45: function(p){ops.LDrr(p, 'B', 'L');},\n    0x46: function(p){ops.LDrrra(p, 'B', 'H', 'L');},\n    0x47: function(p){ops.LDrr(p, 'B', 'A');},\n    0x48: function(p){ops.LDrr(p, 'C', 'B');},\n    0x49: function(p){ops.LDrr(p, 'C', 'C');},\n    0x4A: function(p){ops.LDrr(p, 'C', 'D');},\n    0x4B: function(p){ops.LDrr(p, 'C', 'E');},\n    0x4C: function(p){ops.LDrr(p, 'C', 'H');},\n    0x4D: function(p){ops.LDrr(p, 'C', 'L');},\n    0x4E: function(p){ops.LDrrra(p, 'C', 'H', 'L');},\n    0x4F: function(p){ops.LDrr(p, 'C', 'A');},\n\n    0x50: function(p){ops.LDrr(p, 'D', 'B');},\n    0x51: function(p){ops.LDrr(p, 'D', 'C');},\n    0x52: function(p){ops.LDrr(p, 'D', 'D');},\n    0x53: function(p){ops.LDrr(p, 'D', 'E');},\n    0x54: function(p){ops.LDrr(p, 'D', 'H');},\n    0x55: function(p){ops.LDrr(p, 'D', 'L');},\n    0x56: function(p){ops.LDrrra(p, 'D', 'H', 'L');},\n    0x57: function(p){ops.LDrr(p, 'D', 'A');},\n    0x58: function(p){ops.LDrr(p, 'E', 'B');},\n    0x59: function(p){ops.LDrr(p, 'E', 'C');},\n    0x5A: function(p){ops.LDrr(p, 'E', 'D');},\n    0x5B: function(p){ops.LDrr(p, 'E', 'E');},\n    0x5C: function(p){ops.LDrr(p, 'E', 'H');},\n    0x5D: function(p){ops.LDrr(p, 'E', 'L');},\n    0x5E: function(p){ops.LDrrra(p, 'E', 'H', 'L');},\n    0x5F: function(p){ops.LDrr(p, 'E', 'A');},\n\n    0x60: function(p){ops.LDrr(p, 'H', 'B');},\n    0x61: function(p){ops.LDrr(p, 'H', 'C');},\n    0x62: function(p){ops.LDrr(p, 'H', 'D');},\n    0x63: function(p){ops.LDrr(p, 'H', 'E');},\n    0x64: function(p){ops.LDrr(p, 'H', 'H');},\n    0x65: function(p){ops.LDrr(p, 'H', 'L');},\n    0x66: function(p){ops.LDrrra(p, 'H', 'H', 'L');},\n    0x67: function(p){ops.LDrr(p, 'H', 'A');},\n    0x68: function(p){ops.LDrr(p, 'L', 'B');},\n    0x69: function(p){ops.LDrr(p, 'L', 'C');},\n    0x6A: function(p){ops.LDrr(p, 'L', 'D');},\n    0x6B: function(p){ops.LDrr(p, 'L', 'E');},\n    0x6C: function(p){ops.LDrr(p, 'L', 'H');},\n    0x6D: function(p){ops.LDrr(p, 'L', 'L');},\n    0x6E: function(p){ops.LDrrra(p, 'L', 'H', 'L');},\n    0x6F: function(p){ops.LDrr(p, 'L', 'A');},\n\n    0x70: function(p){ops.LDrrar(p, 'H', 'L', 'B');},\n    0x71: function(p){ops.LDrrar(p, 'H', 'L', 'C');},\n    0x72: function(p){ops.LDrrar(p, 'H', 'L', 'D');},\n    0x73: function(p){ops.LDrrar(p, 'H', 'L', 'E');},\n    0x74: function(p){ops.LDrrar(p, 'H', 'L', 'H');},\n    0x75: function(p){ops.LDrrar(p, 'H', 'L', 'L');},\n    0x76: function(p){ops.HALT(p);},\n    0x77: function(p){ops.LDrrar(p, 'H', 'L', 'A');},\n    0x78: function(p){ops.LDrr(p, 'A', 'B');},\n    0x79: function(p){ops.LDrr(p, 'A', 'C');},\n    0x7A: function(p){ops.LDrr(p, 'A', 'D');},\n    0x7B: function(p){ops.LDrr(p, 'A', 'E');},\n    0x7C: function(p){ops.LDrr(p, 'A', 'H');},\n    0x7D: function(p){ops.LDrr(p, 'A', 'L');},\n    0x7E: function(p){ops.LDrrra(p, 'A', 'H', 'L');},\n    0x7F: function(p){ops.LDrr(p, 'A', 'A');},\n\n    0x80: function(p){ops.ADDrr(p, 'A', 'B');},\n    0x81: function(p){ops.ADDrr(p, 'A', 'C');},\n    0x82: function(p){ops.ADDrr(p, 'A', 'D');},\n    0x83: function(p){ops.ADDrr(p, 'A', 'E');},\n    0x84: function(p){ops.ADDrr(p, 'A', 'H');},\n    0x85: function(p){ops.ADDrr(p, 'A', 'L');},\n    0x86: function(p){ops.ADDrrra(p, 'A', 'H', 'L');},\n    0x87: function(p){ops.ADDrr(p, 'A', 'A');},\n    0x88: function(p){ops.ADCrr(p, 'A', 'B');},\n    0x89: function(p){ops.ADCrr(p, 'A', 'C');},\n    0x8A: function(p){ops.ADCrr(p, 'A', 'D');},\n    0x8B: function(p){ops.ADCrr(p, 'A', 'E');},\n    0x8C: function(p){ops.ADCrr(p, 'A', 'H');},\n    0x8D: function(p){ops.ADCrr(p, 'A', 'L');},\n    0x8E: function(p){ops.ADCrrra(p, 'A', 'H', 'L');},\n    0x8F: function(p){ops.ADCrr(p, 'A', 'A');},\n\n    0x90: function(p){ops.SUBr(p, 'B');},\n    0x91: function(p){ops.SUBr(p, 'C');},\n    0x92: function(p){ops.SUBr(p, 'D');},\n    0x93: function(p){ops.SUBr(p, 'E');},\n    0x94: function(p){ops.SUBr(p, 'H');},\n    0x95: function(p){ops.SUBr(p, 'L');},\n    0x96: function(p){ops.SUBrra(p, 'H', 'L');},\n    0x97: function(p){ops.SUBr(p, 'A');},\n    0x98: function(p){ops.SBCr(p, 'B');},\n    0x99: function(p){ops.SBCr(p, 'C');},\n    0x9A: function(p){ops.SBCr(p, 'D');},\n    0x9B: function(p){ops.SBCr(p, 'E');},\n    0x9C: function(p){ops.SBCr(p, 'H');},\n    0x9D: function(p){ops.SBCr(p, 'L');},\n    0x9E: function(p){ops.SBCrra(p, 'H', 'L');},\n    0x9F: function(p){ops.SBCr(p, 'A');},\n\n    0xA0: function(p){ops.ANDr(p, 'B');},\n    0xA1: function(p){ops.ANDr(p, 'C');},\n    0xA2: function(p){ops.ANDr(p, 'D');},\n    0xA3: function(p){ops.ANDr(p, 'E');},\n    0xA4: function(p){ops.ANDr(p, 'H');},\n    0xA5: function(p){ops.ANDr(p, 'L');},\n    0xA6: function(p){ops.ANDrra(p, 'H', 'L');},\n    0xA7: function(p){ops.ANDr(p, 'A');},\n    0xA8: function(p){ops.XORr(p, 'B');},\n    0xA9: function(p){ops.XORr(p, 'C');},\n    0xAA: function(p){ops.XORr(p, 'D');},\n    0xAB: function(p){ops.XORr(p, 'E');},\n    0xAC: function(p){ops.XORr(p, 'H');},\n    0xAD: function(p){ops.XORr(p, 'L');},\n    0xAE: function(p){ops.XORrra(p, 'H', 'L');},\n    0xAF: function(p){ops.XORr(p, 'A');},\n\n    0xB0: function(p){ops.ORr(p, 'B');},\n    0xB1: function(p){ops.ORr(p, 'C');},\n    0xB2: function(p){ops.ORr(p, 'D');},\n    0xB3: function(p){ops.ORr(p, 'E');},\n    0xB4: function(p){ops.ORr(p, 'H');},\n    0xB5: function(p){ops.ORr(p, 'L');},\n    0xB6: function(p){ops.ORrra(p, 'H', 'L');},\n    0xB7: function(p){ops.ORr(p, 'A');},\n    0xB8: function(p){ops.CPr(p, 'B');},\n    0xB9: function(p){ops.CPr(p, 'C');},\n    0xBA: function(p){ops.CPr(p, 'D');},\n    0xBB: function(p){ops.CPr(p, 'E');},\n    0xBC: function(p){ops.CPr(p, 'H');},\n    0xBD: function(p){ops.CPr(p, 'L');},\n    0xBE: function(p){ops.CPrra(p, 'H', 'L');},\n    0xBF: function(p){ops.CPr(p, 'A');},\n\n    0xC0: function(p){ops.RETcc(p, 'NZ');},\n    0xC1: function(p){ops.POPrr(p, 'B', 'C');},\n    0xC2: function(p){ops.JPccnn(p, 'NZ');},\n    0xC3: function(p){ops.JPnn(p);},\n    0xC4: function(p){ops.CALLccnn(p, 'NZ');},\n    0xC5: function(p){ops.PUSHrr(p, 'B', 'C');},\n    0xC6: function(p){ops.ADDrn(p, 'A');},\n    0xC7: function(p){ops.RSTn(p, 0x00);},\n    0xC8: function(p){ops.RETcc(p, 'Z');},\n    0xC9: function(p){ops.RET(p);},\n    0xCA: function(p){ops.JPccnn(p, 'Z');},\n    0xCB: function(p){ops.CB(p);},\n    0xCC: function(p){ops.CALLccnn(p, 'Z');},\n    0xCD: function(p){ops.CALLnn(p);},\n    0xCE: function(p){ops.ADCrn(p, 'A');},\n    0xCF: function(p){ops.RSTn(p, 0x08);},\n\n    0xD0: function(p){ops.RETcc(p, 'NC');},\n    0xD1: function(p){ops.POPrr(p, 'D', 'E');},\n    0xD2: function(p){ops.JPccnn(p, 'NC');},\n    //0xD3 empty\n    0xD4: function(p){ops.CALLccnn(p, 'NC');},\n    0xD5: function(p){ops.PUSHrr(p, 'D', 'E');},\n    0xD6: function(p){ops.SUBn(p);},\n    0xD7: function(p){ops.RSTn(p, 0x10);},\n    0xD8: function(p){ops.RETcc(p, 'C');},\n    0xD9: function(p){ops.RETI(p);},\n    0xDA: function(p){ops.JPccnn(p, 'C');},\n    //0xDB empty\n    0xDC: function(p){ops.CALLccnn(p, 'C');},\n    //0xDD empty\n    0xDE: function(p){ops.SBCn(p);},\n    0xDF: function(p){ops.RSTn(p, 0x18);},\n\n    0xE0: function(p){ops.LDHnar(p, 'A');},\n    0xE1: function(p){ops.POPrr(p, 'H', 'L');},\n    0xE2: function(p){ops.LDrar(p, 'C', 'A');},\n    //0xE3 empty\n    //0xE4 empty\n    0xE5: function(p){ops.PUSHrr(p, 'H', 'L');},\n    0xE6: function(p){ops.ANDn(p);},\n    0xE7: function(p){ops.RSTn(p, 0x20);},\n    0xE8: function(p){ops.ADDspn(p);},\n    0xE9: function(p){ops.JPrr(p, 'H', 'L');},\n    0xEA: function(p){ops.LDnnar(p, 'A');},\n    //0xEB empty\n    //0xEC empty\n    //0xED empty\n    0xEE: function(p){ops.XORn(p);},\n    0xEF: function(p){ops.RSTn(p, 0x28);},\n\n    0xF0: function(p){ops.LDHrna(p, 'A');},\n    0xF1: function(p){ops.POPrr(p, 'A', 'F');},\n    0xF2: function(p){ops.LDrra(p, 'A', 'C');},\n    0xF3: function(p){ops.DI(p);},\n    //0xF4 empty\n    0xF5: function(p){ops.PUSHrr(p, 'A', 'F');},\n    0xF6: function(p){ops.ORn(p);},\n    0xF7: function(p){ops.RSTn(p, 0x30);},\n    0xF8: function(p){ops.LDrrspn(p, 'H', 'L');},\n    0xF9: function(p){ops.LDsprr(p, 'H', 'L');},\n    0xFA: function(p){ops.LDrnna(p, 'A');},\n    0xFB: function(p){ops.EI(p);},\n    //0xFC empty\n    //0xFD empty\n    0xFE: function(p){ops.CPn(p);},\n    0xFF: function(p){ops.RSTn(p, 0x38);}\n};\n\nvar cbmap = {\n    0x00: function(p){ops.RLCr(p, 'B');},\n    0x01: function(p){ops.RLCr(p, 'C');},\n    0x02: function(p){ops.RLCr(p, 'D');},\n    0x03: function(p){ops.RLCr(p, 'E');},\n    0x04: function(p){ops.RLCr(p, 'H');},\n    0x05: function(p){ops.RLCr(p, 'L');},\n    0x06: function(p){ops.RLCrra(p, 'H', 'L');},\n    0x07: function(p){ops.RLCr(p, 'A');},\n    0x08: function(p){ops.RRCr(p, 'B');},\n    0x09: function(p){ops.RRCr(p, 'C');},\n    0x0A: function(p){ops.RRCr(p, 'D');},\n    0x0B: function(p){ops.RRCr(p, 'E');},\n    0x0C: function(p){ops.RRCr(p, 'H');},\n    0x0D: function(p){ops.RRCr(p, 'L');},\n    0x0E: function(p){ops.RRCrra(p, 'H', 'L');},\n    0x0F: function(p){ops.RRCr(p, 'A');},\n\n    0x10: function(p){ops.RLr(p, 'B');},\n    0x11: function(p){ops.RLr(p, 'C');},\n    0x12: function(p){ops.RLr(p, 'D');},\n    0x13: function(p){ops.RLr(p, 'E');},\n    0x14: function(p){ops.RLr(p, 'H');},\n    0x15: function(p){ops.RLr(p, 'L');},\n    0x16: function(p){ops.RLrra(p, 'H', 'L');},\n    0x17: function(p){ops.RLr(p, 'A');},\n    0x18: function(p){ops.RRr(p, 'B');},\n    0x19: function(p){ops.RRr(p, 'C');},\n    0x1A: function(p){ops.RRr(p, 'D');},\n    0x1B: function(p){ops.RRr(p, 'E');},\n    0x1C: function(p){ops.RRr(p, 'H');},\n    0x1D: function(p){ops.RRr(p, 'L');},\n    0x1E: function(p){ops.RRrra(p, 'H', 'L');},\n    0x1F: function(p){ops.RRr(p, 'A');},\n\n    0x20: function(p){ops.SLAr(p, 'B');},\n    0x21: function(p){ops.SLAr(p, 'C');},\n    0x22: function(p){ops.SLAr(p, 'D');},\n    0x23: function(p){ops.SLAr(p, 'E');},\n    0x24: function(p){ops.SLAr(p, 'H');},\n    0x25: function(p){ops.SLAr(p, 'L');},\n    0x26: function(p){ops.SLArra(p, 'H', 'L');},\n    0x27: function(p){ops.SLAr(p, 'A');},\n    0x28: function(p){ops.SRAr(p, 'B');},\n    0x29: function(p){ops.SRAr(p, 'C');},\n    0x2A: function(p){ops.SRAr(p, 'D');},\n    0x2B: function(p){ops.SRAr(p, 'E');},\n    0x2C: function(p){ops.SRAr(p, 'H');},\n    0x2D: function(p){ops.SRAr(p, 'L');},\n    0x2E: function(p){ops.SRArra(p, 'H', 'L');},\n    0x2F: function(p){ops.SRAr(p, 'A');},\n\n    0x30: function(p){ops.SWAPr(p, 'B');},\n    0x31: function(p){ops.SWAPr(p, 'C');},\n    0x32: function(p){ops.SWAPr(p, 'D');},\n    0x33: function(p){ops.SWAPr(p, 'E');},\n    0x34: function(p){ops.SWAPr(p, 'H');},\n    0x35: function(p){ops.SWAPr(p, 'L');},\n    0x36: function(p){ops.SWAPrra(p, 'H', 'L');},\n    0x37: function(p){ops.SWAPr(p, 'A');},\n    0x38: function(p){ops.SRLr(p, 'B');},\n    0x39: function(p){ops.SRLr(p, 'C');},\n    0x3A: function(p){ops.SRLr(p, 'D');},\n    0x3B: function(p){ops.SRLr(p, 'E');},\n    0x3C: function(p){ops.SRLr(p, 'H');},\n    0x3D: function(p){ops.SRLr(p, 'L');},\n    0x3E: function(p){ops.SRLrra(p, 'H', 'L');},\n    0x3F: function(p){ops.SRLr(p, 'A');},\n\n    0x40: function(p){ops.BITir(p, 0, 'B');},\n    0x41: function(p){ops.BITir(p, 0, 'C');},\n    0x42: function(p){ops.BITir(p, 0, 'D');},\n    0x43: function(p){ops.BITir(p, 0, 'E');},\n    0x44: function(p){ops.BITir(p, 0, 'H');},\n    0x45: function(p){ops.BITir(p, 0, 'L');},\n    0x46: function(p){ops.BITirra(p, 0, 'H', 'L');},\n    0x47: function(p){ops.BITir(p, 0, 'A');},\n    0x48: function(p){ops.BITir(p, 1, 'B');},\n    0x49: function(p){ops.BITir(p, 1, 'C');},\n    0x4A: function(p){ops.BITir(p, 1, 'D');},\n    0x4B: function(p){ops.BITir(p, 1, 'E');},\n    0x4C: function(p){ops.BITir(p, 1, 'H');},\n    0x4D: function(p){ops.BITir(p, 1, 'L');},\n    0x4E: function(p){ops.BITirra(p, 1, 'H', 'L');},\n    0x4F: function(p){ops.BITir(p, 1, 'A');},\n\n    0x50: function(p){ops.BITir(p, 2, 'B');},\n    0x51: function(p){ops.BITir(p, 2, 'C');},\n    0x52: function(p){ops.BITir(p, 2, 'D');},\n    0x53: function(p){ops.BITir(p, 2, 'E');},\n    0x54: function(p){ops.BITir(p, 2, 'H');},\n    0x55: function(p){ops.BITir(p, 2, 'L');},\n    0x56: function(p){ops.BITirra(p, 2, 'H', 'L');},\n    0x57: function(p){ops.BITir(p, 2, 'A');},\n    0x58: function(p){ops.BITir(p, 3, 'B');},\n    0x59: function(p){ops.BITir(p, 3, 'C');},\n    0x5A: function(p){ops.BITir(p, 3, 'D');},\n    0x5B: function(p){ops.BITir(p, 3, 'E');},\n    0x5C: function(p){ops.BITir(p, 3, 'H');},\n    0x5D: function(p){ops.BITir(p, 3, 'L');},\n    0x5E: function(p){ops.BITirra(p, 3, 'H', 'L');},\n    0x5F: function(p){ops.BITir(p, 3, 'A');},\n\n    0x60: function(p){ops.BITir(p, 4, 'B');},\n    0x61: function(p){ops.BITir(p, 4, 'C');},\n    0x62: function(p){ops.BITir(p, 4, 'D');},\n    0x63: function(p){ops.BITir(p, 4, 'E');},\n    0x64: function(p){ops.BITir(p, 4, 'H');},\n    0x65: function(p){ops.BITir(p, 4, 'L');},\n    0x66: function(p){ops.BITirra(p, 4, 'H', 'L');},\n    0x67: function(p){ops.BITir(p, 4, 'A');},\n    0x68: function(p){ops.BITir(p, 5, 'B');},\n    0x69: function(p){ops.BITir(p, 5, 'C');},\n    0x6A: function(p){ops.BITir(p, 5, 'D');},\n    0x6B: function(p){ops.BITir(p, 5, 'E');},\n    0x6C: function(p){ops.BITir(p, 5, 'H');},\n    0x6D: function(p){ops.BITir(p, 5, 'L');},\n    0x6E: function(p){ops.BITirra(p, 5, 'H', 'L');},\n    0x6F: function(p){ops.BITir(p, 5, 'A');},\n\n    0x70: function(p){ops.BITir(p, 6, 'B');},\n    0x71: function(p){ops.BITir(p, 6, 'C');},\n    0x72: function(p){ops.BITir(p, 6, 'D');},\n    0x73: function(p){ops.BITir(p, 6, 'E');},\n    0x74: function(p){ops.BITir(p, 6, 'H');},\n    0x75: function(p){ops.BITir(p, 6, 'L');},\n    0x76: function(p){ops.BITirra(p, 6, 'H', 'L');},\n    0x77: function(p){ops.BITir(p, 6, 'A');},\n    0x78: function(p){ops.BITir(p, 7, 'B');},\n    0x79: function(p){ops.BITir(p, 7, 'C');},\n    0x7A: function(p){ops.BITir(p, 7, 'D');},\n    0x7B: function(p){ops.BITir(p, 7, 'E');},\n    0x7C: function(p){ops.BITir(p, 7, 'H');},\n    0x7D: function(p){ops.BITir(p, 7, 'L');},\n    0x7E: function(p){ops.BITirra(p, 7, 'H', 'L');},\n    0x7F: function(p){ops.BITir(p, 7, 'A');},\n\n    0x80: function(p){ops.RESir(p, 0, 'B');},\n    0x81: function(p){ops.RESir(p, 0, 'C');},\n    0x82: function(p){ops.RESir(p, 0, 'D');},\n    0x83: function(p){ops.RESir(p, 0, 'E');},\n    0x84: function(p){ops.RESir(p, 0, 'H');},\n    0x85: function(p){ops.RESir(p, 0, 'L');},\n    0x86: function(p){ops.RESirra(p, 0, 'H', 'L');},\n    0x87: function(p){ops.RESir(p, 0, 'A');},\n    0x88: function(p){ops.RESir(p, 1, 'B');},\n    0x89: function(p){ops.RESir(p, 1, 'C');},\n    0x8A: function(p){ops.RESir(p, 1, 'D');},\n    0x8B: function(p){ops.RESir(p, 1, 'E');},\n    0x8C: function(p){ops.RESir(p, 1, 'H');},\n    0x8D: function(p){ops.RESir(p, 1, 'L');},\n    0x8E: function(p){ops.RESirra(p, 1, 'H', 'L');},\n    0x8F: function(p){ops.RESir(p, 1, 'A');},\n\n    0x90: function(p){ops.RESir(p, 2, 'B');},\n    0x91: function(p){ops.RESir(p, 2, 'C');},\n    0x92: function(p){ops.RESir(p, 2, 'D');},\n    0x93: function(p){ops.RESir(p, 2, 'E');},\n    0x94: function(p){ops.RESir(p, 2, 'H');},\n    0x95: function(p){ops.RESir(p, 2, 'L');},\n    0x96: function(p){ops.RESirra(p, 2, 'H', 'L');},\n    0x97: function(p){ops.RESir(p, 2, 'A');},\n    0x98: function(p){ops.RESir(p, 3, 'B');},\n    0x99: function(p){ops.RESir(p, 3, 'C');},\n    0x9A: function(p){ops.RESir(p, 3, 'D');},\n    0x9B: function(p){ops.RESir(p, 3, 'E');},\n    0x9C: function(p){ops.RESir(p, 3, 'H');},\n    0x9D: function(p){ops.RESir(p, 3, 'L');},\n    0x9E: function(p){ops.RESirra(p, 3, 'H', 'L');},\n    0x9F: function(p){ops.RESir(p, 3, 'A');},\n\n    0xA0: function(p){ops.RESir(p, 4, 'B');},\n    0xA1: function(p){ops.RESir(p, 4, 'C');},\n    0xA2: function(p){ops.RESir(p, 4, 'D');},\n    0xA3: function(p){ops.RESir(p, 4, 'E');},\n    0xA4: function(p){ops.RESir(p, 4, 'H');},\n    0xA5: function(p){ops.RESir(p, 4, 'L');},\n    0xA6: function(p){ops.RESirra(p, 4, 'H', 'L');},\n    0xA7: function(p){ops.RESir(p, 4, 'A');},\n    0xA8: function(p){ops.RESir(p, 5, 'B');},\n    0xA9: function(p){ops.RESir(p, 5, 'C');},\n    0xAA: function(p){ops.RESir(p, 5, 'D');},\n    0xAB: function(p){ops.RESir(p, 5, 'E');},\n    0xAC: function(p){ops.RESir(p, 5, 'H');},\n    0xAD: function(p){ops.RESir(p, 5, 'L');},\n    0xAE: function(p){ops.RESirra(p, 5, 'H', 'L');},\n    0xAF: function(p){ops.RESir(p, 5, 'A');},\n\n    0xB0: function(p){ops.RESir(p, 6, 'B');},\n    0xB1: function(p){ops.RESir(p, 6, 'C');},\n    0xB2: function(p){ops.RESir(p, 6, 'D');},\n    0xB3: function(p){ops.RESir(p, 6, 'E');},\n    0xB4: function(p){ops.RESir(p, 6, 'H');},\n    0xB5: function(p){ops.RESir(p, 6, 'L');},\n    0xB6: function(p){ops.RESirra(p, 6, 'H', 'L');},\n    0xB7: function(p){ops.RESir(p, 6, 'A');},\n    0xB8: function(p){ops.RESir(p, 7, 'B');},\n    0xB9: function(p){ops.RESir(p, 7, 'C');},\n    0xBA: function(p){ops.RESir(p, 7, 'D');},\n    0xBB: function(p){ops.RESir(p, 7, 'E');},\n    0xBC: function(p){ops.RESir(p, 7, 'H');},\n    0xBD: function(p){ops.RESir(p, 7, 'L');},\n    0xBE: function(p){ops.RESirra(p, 7, 'H', 'L');},\n    0xBF: function(p){ops.RESir(p, 7, 'A');},\n\n    0xC0: function(p){ops.SETir(p, 0, 'B');},\n    0xC1: function(p){ops.SETir(p, 0, 'C');},\n    0xC2: function(p){ops.SETir(p, 0, 'D');},\n    0xC3: function(p){ops.SETir(p, 0, 'E');},\n    0xC4: function(p){ops.SETir(p, 0, 'H');},\n    0xC5: function(p){ops.SETir(p, 0, 'L');},\n    0xC6: function(p){ops.SETirra(p, 0, 'H', 'L');},\n    0xC7: function(p){ops.SETir(p, 0, 'A');},\n    0xC8: function(p){ops.SETir(p, 1, 'B');},\n    0xC9: function(p){ops.SETir(p, 1, 'C');},\n    0xCA: function(p){ops.SETir(p, 1, 'D');},\n    0xCB: function(p){ops.SETir(p, 1, 'E');},\n    0xCC: function(p){ops.SETir(p, 1, 'H');},\n    0xCD: function(p){ops.SETir(p, 1, 'L');},\n    0xCE: function(p){ops.SETirra(p, 1, 'H', 'L');},\n    0xCF: function(p){ops.SETir(p, 1, 'A');},\n\n    0xD0: function(p){ops.SETir(p, 2, 'B');},\n    0xD1: function(p){ops.SETir(p, 2, 'C');},\n    0xD2: function(p){ops.SETir(p, 2, 'D');},\n    0xD3: function(p){ops.SETir(p, 2, 'E');},\n    0xD4: function(p){ops.SETir(p, 2, 'H');},\n    0xD5: function(p){ops.SETir(p, 2, 'L');},\n    0xD6: function(p){ops.SETirra(p, 2, 'H', 'L');},\n    0xD7: function(p){ops.SETir(p, 2, 'A');},\n    0xD8: function(p){ops.SETir(p, 3, 'B');},\n    0xD9: function(p){ops.SETir(p, 3, 'C');},\n    0xDA: function(p){ops.SETir(p, 3, 'D');},\n    0xDB: function(p){ops.SETir(p, 3, 'E');},\n    0xDC: function(p){ops.SETir(p, 3, 'H');},\n    0xDD: function(p){ops.SETir(p, 3, 'L');},\n    0xDE: function(p){ops.SETirra(p, 3, 'H', 'L');},\n    0xDF: function(p){ops.SETir(p, 3, 'A');},\n\n    0xE0: function(p){ops.SETir(p, 4, 'B');},\n    0xE1: function(p){ops.SETir(p, 4, 'C');},\n    0xE2: function(p){ops.SETir(p, 4, 'D');},\n    0xE3: function(p){ops.SETir(p, 4, 'E');},\n    0xE4: function(p){ops.SETir(p, 4, 'H');},\n    0xE5: function(p){ops.SETir(p, 4, 'L');},\n    0xE6: function(p){ops.SETirra(p, 4, 'H', 'L');},\n    0xE7: function(p){ops.SETir(p, 4, 'A');},\n    0xE8: function(p){ops.SETir(p, 5, 'B');},\n    0xE9: function(p){ops.SETir(p, 5, 'C');},\n    0xEA: function(p){ops.SETir(p, 5, 'D');},\n    0xEB: function(p){ops.SETir(p, 5, 'E');},\n    0xEC: function(p){ops.SETir(p, 5, 'H');},\n    0xED: function(p){ops.SETir(p, 5, 'L');},\n    0xEE: function(p){ops.SETirra(p, 5, 'H', 'L');},\n    0xEF: function(p){ops.SETir(p, 5, 'A');},\n\n    0xF0: function(p){ops.SETir(p, 6, 'B');},\n    0xF1: function(p){ops.SETir(p, 6, 'C');},\n    0xF2: function(p){ops.SETir(p, 6, 'D');},\n    0xF3: function(p){ops.SETir(p, 6, 'E');},\n    0xF4: function(p){ops.SETir(p, 6, 'H');},\n    0xF5: function(p){ops.SETir(p, 6, 'L');},\n    0xF6: function(p){ops.SETirra(p, 6, 'H', 'L');},\n    0xF7: function(p){ops.SETir(p, 6, 'A');},\n    0xF8: function(p){ops.SETir(p, 7, 'B');},\n    0xF9: function(p){ops.SETir(p, 7, 'C');},\n    0xFA: function(p){ops.SETir(p, 7, 'D');},\n    0xFB: function(p){ops.SETir(p, 7, 'E');},\n    0xFC: function(p){ops.SETir(p, 7, 'H');},\n    0xFD: function(p){ops.SETir(p, 7, 'L');},\n    0xFE: function(p){ops.SETirra(p, 7, 'H', 'L');},\n    0xFF: function(p){ops.SETir(p, 7, 'A');}\n};\n\nexport {map as opcodeMap, cbmap as opcodeCbmap};\n"
  },
  {
    "path": "src/rom/ajax_reader.ts",
    "content": "import { RomReader } from './rom'\n\n// A RomAjaxReader is able to load a file through an AJAX request\nclass RomAjaxReader implements RomReader {\n    callback: Function;\n\n    // The callback argument will be called when a file is successfully\n    // read, with the data as argument (Uint8Array)\n    setCallback(onLoadCallback: Function) {\n        this.callback = onLoadCallback;\n    }\n\n    // This function should be called by application code\n    // and will trigger the AJAX call itself and push data to the ROM object\n    loadFromUrl(url: string) {\n        if (!url) {\n            throw 'No url has been set in order to load a ROM file.';\n        }\n        let cb = this.callback;\n\n        let xhr = new XMLHttpRequest();\n        xhr.open('GET', url, true);\n        xhr.responseType = \"arraybuffer\";\n        xhr.onload = function() {\n            let rom = new Uint8Array(xhr.response);\n            cb && cb(rom);\n        };\n\n        xhr.send();\n    }\n}\n\nexport default RomAjaxReader;\n"
  },
  {
    "path": "src/rom/drop_file_reader.ts",
    "content": "import { RomReader } from './rom'\n\n// A RomDropFileReader is able to load a drag and dropped file\nclass RomDropFileReader implements RomReader {\n    dropElement: HTMLElement;\n    callback: Function;\n\n    constructor(el) {\n        this.dropElement = el;\n        if (!this.dropElement) {\n            throw 'The RomDropFileReader needs a drop zone.';\n        }\n\n        let self = this;\n        this.dropElement.addEventListener('dragenter', function(e) {\n            e.preventDefault();\n            if (e.target !== self.dropElement) {\n                return;\n            }\n            self.dropElement.classList.add('drag-active');\n        });\n        this.dropElement.addEventListener('dragleave', function(e) {\n            e.preventDefault();\n            if (e.target !== self.dropElement) {\n                return;\n            }\n            self.dropElement.classList.remove('drag-active');\n        });\n        this.dropElement.addEventListener('dragover', function(e) {\n            e.preventDefault();\n            self.dropElement.classList.add('drag-active');\n        });\n        this.dropElement.addEventListener('drop', function (e) {\n            self.dropElement.classList.remove('drag-active');\n            if (e.dataTransfer.files.length == 0) {\n                return;\n            }\n            e.preventDefault();\n            self.loadFromFile(e.dataTransfer.files[0]);\n        });\n    }\n\n    // The callback argument will be called when a file is successfully\n    // read, with the data as argument (Uint8Array)\n    setCallback(onLoadCallback: Function) {\n        this.callback = onLoadCallback;\n    }\n\n    // The file loading logic is the same as the regular file reader\n    loadFromFile(file) {\n        if (file === undefined) {\n            return;\n        }\n        let fr = new FileReader();\n        let cb = this.callback;\n\n        fr.onload = function() {\n            cb && cb(new Uint8Array(fr.result as ArrayBuffer));\n        };\n        fr.onerror = function(e) {\n            console.log('Error reading the file', e.target.error.code)\n        };\n        fr.readAsArrayBuffer(file);\n    }\n}\n\nexport default RomDropFileReader;\n"
  },
  {
    "path": "src/rom/file_reader.ts",
    "content": "import { RomReader } from './rom';\n\n// A RomFileReader is able to load a local file from an input element\n//\n// Expects to be provided a file input element,\n// or will try to find one with the \"file\" DOM ID\nclass RomFileReader implements RomReader {\n    domElement: HTMLElement;\n    callback: Function;\n\n    constructor(el?: HTMLElement) {\n        this.domElement = el || document.getElementById('file');\n        if (!this.domElement) {\n            throw 'The RomFileReader needs a valid input element.';\n        }\n\n        let self = this;\n        this.domElement.addEventListener('change', function(e) {\n            self.loadFromFile((e.target as HTMLInputElement).files[0]);\n        });\n    }\n\n    // The callback argument will be called when a file is successfully\n    // read, with the data as argument (Uint8Array)\n    setCallback(onLoadCallback: Function) {\n        this.callback = onLoadCallback;\n    }\n\n    // Automatically called when the DOM input is provided with a file\n    loadFromFile(file) {\n        if (file === undefined) {\n            return;\n        }\n        let fr = new FileReader();\n        let cb = this.callback;\n\n        fr.onload = function() {\n            cb && cb(new Uint8Array(fr.result as ArrayBuffer));\n        };\n        fr.onerror = function(e) {\n            console.log('Error reading the file', e.target.error.code)\n        };\n        fr.readAsArrayBuffer(file);\n    }\n}\n\n\n\nexport default RomFileReader;\n"
  },
  {
    "path": "src/rom/rom.ts",
    "content": "class Rom {\n    gameboy;\n    data: Uint8Array;\n\n    constructor(gameboy, romReader?) {\n        this.gameboy = gameboy;\n        if (romReader) {\n            this.addReader(romReader);\n        }\n    }\n\n    addReader(romReader: RomReader) {\n        let self = this;\n        romReader.setCallback(function(data: Uint8Array) {\n            if (!validate(data)) {\n                self.gameboy.error('The file is not a valid GameBoy ROM.');\n                return;\n            }\n            self.data = data;\n            self.gameboy.startRom(self);\n        });\n    }\n}\n\n\n// Validate the checksum of the cartridge header\nfunction validate(data: Uint8Array) {\n    let hash = 0;\n    for (let i = 0x134; i <= 0x14C; i++) {\n        hash = hash - data[i] - 1;\n    }\n    return (hash & 0xFF) == data[0x14D];\n}\n\nexport interface RomReader {\n    setCallback(fn: Function): void;\n}\n\nexport default Rom;\n"
  },
  {
    "path": "src/serial.ts",
    "content": "// Handlers for the Serial port of the Gameboy\n\ninterface SerialInterface {\n    out(data: number): void;\n    in(): number;\n}\n\n// The ConsoleSerial is an output-only serial port\n// designed for debug purposes as some test roms output data on the serial port\n//\n// Will regularly output the received byte (converted to string) in the console logs\n// This handler always push the value 0xFF as an input\nclass ConsoleSerial implements SerialInterface {\n    current: string = '';\n    timeout: ReturnType<typeof setTimeout>;\n    out(data: number): void {\n        this.current += String.fromCharCode(data);\n        if (data == 10) {\n            this.print();\n        } else {\n            clearTimeout(this.timeout);\n            this.timeout = setTimeout(this.print.bind(this), 500);\n        }\n    }\n    in(): number {\n        return 0xFF;\n    }\n    print(): void {\n        clearTimeout(this.timeout);\n        console.log('serial: ' + this.current);\n        this.current = '';\n    }\n}\n\n// A DummySerial outputs nothing and always inputs 0xFF\nclass DummySerial implements SerialInterface {\n    out(): void {}\n    in(): number {\n        return 0xFF;\n    }\n}\n\nexport {ConsoleSerial, DummySerial, SerialInterface};\n"
  },
  {
    "path": "src/sound/apu.ts",
    "content": "import Channel1 from './channel1';\nimport Channel3 from './channel3';\nimport Channel4 from './channel4';\nimport Memory from '../memory';\n\n// Audio Processing unit\n// Listens the write accesses to the audio-reserved memory addresses\n// and dispatches the data to the sound channels\nclass APU {\n    memory: Memory;\n    enabled = false;\n    channel1;\n    channel2;\n    channel3;\n    channel4;\n\n    constructor(memory: Memory) {\n        this.memory = memory;\n        this.enabled = false;\n\n        var audioContext = new AudioContext();\n\n        this.channel1 = new Channel1(this, 1, audioContext);\n        this.channel2 = new Channel1(this, 2, audioContext);\n        this.channel3 = new Channel3(this, 3, audioContext);\n        this.channel4 = new Channel4(this, 4, audioContext);\n\n    }\n\n    connect() {\n        this.channel1.enable();\n        this.channel2.enable();\n        this.channel3.enable();\n    }\n\n    disconnect() {\n        this.channel1.disable();\n        this.channel2.disable();\n        this.channel3.disable();\n    }\n\n    // Updates the states of each channel given the elapsed time\n    // (in instructions) since last update\n    update(clockElapsed) {\n        if (this.enabled == false) return;\n\n        this.channel1.update(clockElapsed);\n        this.channel2.update(clockElapsed);\n        this.channel3.update(clockElapsed);\n        this.channel4.update(clockElapsed);\n    }\n\n    setSoundFlag(channel, value) {\n        var mask = 0xFF - (1 << (channel - 1));\n        value = value << (channel - 1)\n        var byteValue = this.memory.rb(APU.registers.NR52);\n        byteValue &= mask;\n        byteValue |= value;\n        this.memory[APU.registers.NR52] = byteValue;\n    }\n\n    // Manage writes to audio registers\n    // Will update the channels depending on the address\n    manageWrite(addr, value) {\n        if (this.enabled == false && addr < APU.registers.NR52) {\n            return;\n        }\n        this.memory[addr] = value;\n\n        switch (addr) {\n            // Channel 1 addresses\n            case 0xFF10:\n                this.channel1.clockSweep = 0;\n                this.channel1.sweepTime = ((value & 0x70) >> 4);\n                this.channel1.sweepSign = (value & 0x08) ? -1 : 1;\n                this.channel1.sweepShifts = (value & 0x07);\n                this.channel1.sweepCount = this.channel1.sweepShifts;\n                break;\n            case 0xFF11:\n                // todo : bits 6-7\n                this.channel1.setLength(value & 0x3F);\n                break;\n            case 0xFF12:\n                this.channel1.envelopeSign = (value & 0x08) ? 1 : -1;\n                var envelopeVolume = (value & 0xF0) >> 4;\n                this.channel1.setEnvelopeVolume(envelopeVolume);\n                this.channel1.envelopeStep = (value & 0x07);\n                this.channel1.updateDAC(value);\n                break;\n            case 0xFF13:\n                var frequency = this.channel1.getFrequency();\n                frequency &= 0xF00;\n                frequency |= value;\n                this.channel1.setFrequency(frequency);\n                break;\n            case 0xFF14:\n                var frequency = this.channel1.getFrequency();\n                frequency &= 0xFF;\n                frequency |= (value & 7) << 8;\n                this.channel1.setFrequency(frequency);\n                this.channel1.lengthCheck = (value & 0x40) ? true : false;\n                if (value & 0x80) this.channel1.play();\n                break;\n\n            // Channel 2 addresses\n            case 0xFF16:\n                // todo : bits 6-7\n                this.channel2.setLength(value & 0x3F);\n                break;\n            case 0xFF17:\n                this.channel2.envelopeSign = (value & 0x08) ? 1 : -1;\n                var envelopeVolume = (value & 0xF0) >> 4;\n                this.channel2.setEnvelopeVolume(envelopeVolume);\n                this.channel2.envelopeStep = (value & 0x07);\n                this.channel2.updateDAC(value);\n                break;\n            case 0xFF18:\n                var frequency = this.channel2.getFrequency();\n                frequency &= 0xF00;\n                frequency |= value;\n                this.channel2.setFrequency(frequency);\n                break;\n            case 0xFF19:\n                var frequency = this.channel2.getFrequency();\n                frequency &= 0xFF;\n                frequency |= (value & 7) << 8;\n                this.channel2.setFrequency(frequency);\n                this.channel2.lengthCheck = (value & 0x40) ? true : false;\n                if (value & 0x80) {\n                    this.channel2.play();\n                }\n                break;\n\n            // Channel 3 addresses\n            case 0xFF1A:\n                // todo\n                this.channel3.updateDAC(value);\n                break;\n            case 0xFF1B:\n                this.channel3.setLength(value);\n                break;\n            case 0xFF1C:\n                // todo\n                break;\n            case 0xFF1D:\n                var frequency = this.channel3.getFrequency();\n                frequency &= 0xF00;\n                frequency |= value;\n                this.channel3.setFrequency(frequency);\n                break;\n            case 0xFF1E:\n                var frequency = this.channel3.getFrequency();\n                frequency &= 0xFF;\n                frequency |= (value & 7) << 8;\n                this.channel3.setFrequency(frequency);\n                this.channel3.lengthCheck = (value & 0x40) ? true : false;\n                if (value & 0x80) {\n                    this.channel3.play();\n                }\n                break;\n\n            // Channel 4 addresses\n            case 0xFF20:\n                this.channel4.setLength(value & 0x3F);\n                break;\n            case 0xFF21:\n                // todo\n                this.channel4.updateDAC(value);\n                break;\n            case 0xFF22:\n                // todo\n                break;\n            case 0xFF23:\n                this.channel4.lengthCheck = (value & 0x40) ? true : false;\n                if (value & 0x80) {\n                    this.channel4.play();\n                }\n                break;\n\n            // channel 3 wave bytes\n            case 0xFF30:case 0xFF31:case 0xFF32:case 0xFF33:case 0xFF34:case 0xFF35:case 0xFF36:case 0xFF37:\n            case 0xFF38:case 0xFF39:case 0xFF3A:case 0xFF3B:case 0xFF3C:case 0xFF3D:case 0xFF3E:case 0xFF3F:\n                var index = addr - 0xFF30;\n                this.channel3.setWaveBufferByte(index, value);\n                break;\n\n            // general audio switch\n            case 0xFF26:\n                value &= 0xF0;\n                this.memory[addr] = value;\n                this.enabled = (value & 0x80) == 0 ? false : true;\n                if (!this.enabled) {\n                    for (var i = 0xFF10; i < 0xFF27; i++)\n                        this.memory[i] = 0;\n                    // todo stop sound\n                }\n                break;\n        }\n    }\n\n    static registers = {\n        NR10: 0xFF10,\n        NR11: 0xFF11,\n        NR12: 0xFF12,\n        NR13: 0xFF13,\n        NR14: 0xFF14,\n\n        NR21: 0xFF16,\n        NR22: 0xFF17,\n        NR23: 0xFF18,\n        NR24: 0xFF19,\n\n        NR30: 0xFF1A,\n        NR31: 0xFF1B,\n        NR32: 0xFF1C,\n        NR33: 0xFF1D,\n        NR34: 0xFF1E,\n\n        NR41: 0xFF20,\n        NR42: 0xFF21,\n        NR43: 0xFF22,\n        NR44: 0xFF23,\n\n        NR50: 0xFF24,\n        NR51: 0xFF25,\n        NR52: 0xFF26\n    };\n}\n\nexport default APU;\n"
  },
  {
    "path": "src/sound/channel.ts",
    "content": "import APU from './apu'\n\ninterface AudioChannel {\n    play(): void;\n    stop(): void;\n    updateDAC(controlRegister: number): void;\n    update(clockElapsed: number): void;\n}\n\nabstract class AbstractAudioChannel implements AudioChannel {\n    apu: APU;\n    channelNumber: number;\n    audioContext: AudioContext;\n    playing: boolean = false;\n    dac: boolean = false;\n    lengthCheck = false;\n    clockLength = 0;\n    soundLength = 64; // defaults to 64 periods\n    soundLengthUnit = 0x4000; // 1 / 256 second of instructions\n\n    abstract play(): void;\n    abstract stop(): void;\n    abstract updateDAC(controlRegister: number): void;\n    abstract update(clockElapsed: number): void;\n    abstract setLength(value: number): void;\n\n    checkLength(clockElapsed: number) {\n        if (this.lengthCheck) {\n            this.clockLength += clockElapsed;\n            if (this.clockLength > this.soundLengthUnit) {\n                this.soundLength--;\n                this.clockLength -= this.soundLengthUnit;\n                if (this.soundLength == 0) {\n                    this.setLength(0);\n                    this.stop();\n                }\n            }\n        }\n    }\n\n    protected setDAC(value: boolean) {\n        this.dac = value;\n        if (!value) this.stop();\n    }\n}\n\nexport {AudioChannel, AbstractAudioChannel};\n"
  },
  {
    "path": "src/sound/channel1.ts",
    "content": "import APU from './apu';\nimport {AbstractAudioChannel} from './channel';\n\nclass Channel1 extends AbstractAudioChannel {\n    soundLength = 64; // defaults to 64 periods\n\n    sweepTime = 0; // from 0 to 7\n    sweepStepLength = 0x8000; // 1 / 128 seconds of instructions\n    sweepCount = 0;\n    sweepShifts = 0;\n    sweepSign = 1; // +1 / -1 for increase / decrease freq\n\n    frequency = 0;\n\n    envelopeStep = 0;\n    envelopeStepLength = 0x10000;// 1 / 64 seconds of instructions\n    envelopeCheck = false;\n    envelopeSign = 1;\n    envelopeVolume;\n\n    clockEnvelop = 0;\n    clockSweep = 0;\n\n    gainNode;\n    oscillator;\n\n    constructor(apu: APU, channelNumber, audioContext) {\n        super();\n        this.apu = apu;\n        this.channelNumber = channelNumber;\n\n        var gainNode = audioContext.createGain();\n        gainNode.gain.value = 0;\n        var oscillator = audioContext.createOscillator();\n        oscillator.type = 'square';\n        oscillator.frequency.value = 1000;\n        oscillator.connect(gainNode);\n        oscillator.start(0);\n\n        this.audioContext = audioContext;\n        this.gainNode = gainNode;\n        this.oscillator = oscillator;\n    }\n\n    play() {\n        if (this.playing || !this.dac) return;\n        this.playing = true;\n        this.apu.setSoundFlag(this.channelNumber, 1);\n        this.gainNode.connect(this.audioContext.destination);\n        this.clockLength = 0;\n        this.clockEnvelop = 0;\n        this.clockSweep = 0;\n        if (this.sweepShifts > 0) this.checkFreqSweep();\n    }\n\n    stop() {\n        this.playing = false;\n        this.apu.setSoundFlag(this.channelNumber, 0);\n        this.gainNode.disconnect();\n    }\n\n    updateDAC(controlRegister: number): void {\n        this.setDAC((controlRegister & 0xF8) > 0);\n    }\n\n    checkFreqSweep() {\n        var oldFreq = this.getFrequency();\n        var newFreq = oldFreq + this.sweepSign * (oldFreq >> this.sweepShifts);\n        if (newFreq > 0x7FF) {\n            newFreq = 0;\n            this.stop();\n        }\n\n        return newFreq;\n    }\n    update(clockElapsed) {\n        this.clockEnvelop += clockElapsed;\n        this.clockSweep   += clockElapsed;\n\n        if ((this.sweepCount || this.sweepTime) && this.clockSweep > (this.sweepStepLength * this.sweepTime)) {\n            this.clockSweep -= (this.sweepStepLength * this.sweepTime);\n            this.sweepCount--;\n\n            var newFreq = this.checkFreqSweep(); // process and check new freq\n\n            this.apu.memory[0xFF13] = newFreq & 0xFF;\n            this.apu.memory[0xFF14] &= 0xF8;\n            this.apu.memory[0xFF14] |= (newFreq & 0x700) >> 8;\n            this.setFrequency(newFreq);\n\n            this.checkFreqSweep(); // check again with new value\n        }\n\n        if (this.envelopeCheck && this.clockEnvelop > this.envelopeStepLength) {\n            this.clockEnvelop -= this.envelopeStepLength;\n            this.envelopeStep--;\n            this.setEnvelopeVolume(this.envelopeVolume + this.envelopeSign);\n            if (this.envelopeStep <= 0) {\n                this.envelopeCheck = false;\n            }\n        }\n\n        this.checkLength(clockElapsed);\n    }\n    setFrequency(value) {\n        this.frequency = value;\n        this.oscillator.frequency.value = 131072 / (2048 - this.frequency);\n    }\n    getFrequency() {\n        return this.frequency;\n    }\n    setLength(value) {\n        this.soundLength = 64 - (value & 0x3F);\n    }\n    setEnvelopeVolume(volume) {\n        this.envelopeCheck = volume > 0 && volume < 16;\n        this.envelopeVolume = volume;\n        this.gainNode.gain.value = this.envelopeVolume * 1/100;\n    }\n    disable() {\n        this.oscillator.disconnect();\n    }\n    enable() {\n        this.oscillator.connect(this.gainNode);\n    }\n}\n\nexport default Channel1;\n"
  },
  {
    "path": "src/sound/channel3.ts",
    "content": "import APU from './apu';\nimport {AbstractAudioChannel} from './channel';\n\nclass Channel3 extends AbstractAudioChannel {\n    soundLengthUnit = 0x4000; // 1 / 256 second of instructions\n\n    buffer;\n    gainNode;\n    baseSpeed = 65536;\n\n    waveBuffer;\n    bufferSource;\n\n    constructor(apu: APU, channelNumber, audioContext) {\n        super();\n        this.apu = apu;\n        this.channelNumber = channelNumber;\n\n        this.buffer = new Float32Array(32);\n\n        var gainNode = audioContext.createGain();\n        gainNode.gain.value = 1;\n        this.gainNode = gainNode;\n\n        var waveBuffer = audioContext.createBuffer(1, 32, this.baseSpeed);\n\n        var bufferSource = audioContext.createBufferSource();\n        bufferSource.buffer = waveBuffer;\n        bufferSource.loop = true;\n        bufferSource.connect(gainNode);\n        bufferSource.start(0);\n\n        this.audioContext = audioContext;\n        this.waveBuffer = waveBuffer;\n        this.bufferSource = bufferSource;\n    }\n\n    play() {\n        if (this.playing || !this.dac) return;\n        this.playing = true;\n        this.apu.setSoundFlag(this.channelNumber, 1);\n        this.waveBuffer.copyToChannel(this.buffer, 0, 0);\n\n        this.gainNode.connect(this.audioContext.destination);\n        this.clockLength = 0;\n    }\n    stop() {\n        this.playing = false;\n        this.apu.setSoundFlag(this.channelNumber, 0);\n        this.gainNode.disconnect();\n    };\n    updateDAC(controlRegister) {\n        this.setDAC((controlRegister & 0x80) > 0);\n    }\n\n    update(clockElapsed) {\n        this.checkLength(clockElapsed);\n    }\n    setFrequency(value) {\n        value = 65536 / (2048  - value);\n        this.bufferSource.playbackRate.value = value / this.baseSpeed;\n    }\n    getFrequency() {\n        var freq = 2048 - 65536 / (this.bufferSource.playbackRate.value * this.baseSpeed);\n        return freq | 1;\n    }\n    setLength(value) {\n        this.soundLength = 256 - value;\n    }\n    setWaveBufferByte(index, value) {\n        var bufferIndex = index * 2;\n\n        this.buffer[bufferIndex]   = (value >> 4) / 8 - 1; // value in buffer is in -1 -> 1\n        this.buffer[bufferIndex+1] = (value & 0x0F) / 8 - 1;\n    }\n    disable() {\n        this.bufferSource.disconnect();\n    }\n    enable() {\n        this.bufferSource.connect(this.gainNode);\n    }\n}\n\nexport default Channel3;\n"
  },
  {
    "path": "src/sound/channel4.ts",
    "content": "import APU from './apu';\nimport {AbstractAudioChannel} from './channel';\n\nclass Channel4 extends AbstractAudioChannel {\n    constructor(apu, channelNumber, audioContext) {\n        super();\n        this.apu = apu;\n        this.channelNumber = channelNumber;\n\n        this.audioContext = audioContext;\n    }\n\n    play() {\n        if (this.playing || !this.dac) return;\n        this.playing = true;\n        this.apu.setSoundFlag(this.channelNumber, 1);\n        this.clockLength = 0;\n    }\n    stop() {\n        this.playing = false;\n        this.apu.setSoundFlag(this.channelNumber, 0);\n    }\n    updateDAC(controlRegister) {\n        this.setDAC((controlRegister & 0xF8) > 0);\n    }\n\n    update(clockElapsed) {\n        this.checkLength(clockElapsed);\n    }\n    setLength(value) {\n        this.soundLength = 64 - (value & 0x3F);\n    }\n}\n\nexport default Channel4;\n"
  },
  {
    "path": "src/timer.ts",
    "content": "import CPU from './cpu';\nimport Memory from './memory'\n\nclass Timer {\n    DIV  = 0xFF04;\n    TIMA = 0xFF05;\n    TMA  = 0xFF06;\n    TAC  = 0xFF07;\n\n    mainTime  = 0;\n    divTime   = 0;\n    cpu: CPU;\n    memory: Memory;\n\n    constructor(cpu: CPU, memory: Memory) {\n        this.cpu    = cpu;\n        this.memory = memory;\n    }\n\n    update(clockElapsed: number) {\n        this.updateDiv(clockElapsed);\n        this.updateTimer(clockElapsed);\n    }\n\n    updateTimer(clockElapsed: number) {\n        if (!(this.memory.rb(this.TAC) & 0x4)) {\n            return;\n        }\n        this.mainTime += clockElapsed;\n\n        let threshold = 64;\n        switch (this.memory.rb(this.TAC) & 3) {\n            case 0: threshold=64; break; // 4KHz\n            case 1: threshold=1;  break; // 256KHz\n            case 2: threshold=4;  break; // 64KHz\n            case 3: threshold=16; break; // 16KHz\n        }\n        threshold *= 16;\n\n        while (this.mainTime >= threshold) {\n            this.mainTime -= threshold;\n\n            this.memory.wb(this.TIMA, this.memory.rb(this.TIMA) + 1);\n            if (this.memory.rb(this.TIMA) > 0xFF) {\n                this.memory.wb(this.TIMA, this.memory.rb(this.TMA));\n                this.cpu.requestInterrupt(CPU.INTERRUPTS.TIMER);\n            }\n        }\n    }\n\n    // Update the DIV register internal clock\n    // Increment it if the clock threshold is elapsed and\n    // reset it if its value overflows\n    updateDiv(clockElapsed: number) {\n        let divThreshold = 256; // DIV is 16KHz\n        this.divTime += clockElapsed;\n        if (this.divTime > divThreshold) {\n            this.divTime -= divThreshold;\n            let div = this.memory.rb(this.DIV) + 1;\n            this.memory.wb(this.DIV, div&0xFF);\n        }\n    }\n\n    resetDiv() {\n        this.divTime = 0;\n        this.memory[this.DIV] = 0; // direct write to avoid looping\n    }\n}\n\nexport default Timer;\n"
  },
  {
    "path": "src/util.ts",
    "content": "import CPU from './cpu';\n\n// Utility functions\nlet Util = {\n    // Add to the first argument the properties of all other arguments\n    extend: function(target , ...sources/*, source1, source2, etc. */) {\n        for (let i in sources) {\n            let source = sources[i];\n            for (let name in source) {\n                target[name] = source[name];\n            }\n        }\n\n        return target;\n    },\n    testFlag: function(p: CPU, cc: string) {\n        let test: number = 1;\n        let mask: number = 0x10;\n        if (cc == 'NZ' || cc == 'NC') test = 0;\n        if (cc == 'NZ' || cc == 'Z')  mask = 0x80;\n        return (test && p.r.F & mask) || (!test && !(p.r.F & mask));\n    },\n    getRegAddr: function(p: CPU, r1: string, r2: string) {\n        return Util.makeword(p.r[r1], p.r[r2]);\n    },\n\n    // make a 16 bits word from 2 bytes\n    makeword: function(b1: number, b2: number) {\n        return (b1 << 8) + b2;\n    },\n\n    // return the integer signed value of a given byte\n    getSignedValue: function(v: number) {\n        return v & 0x80 ? v-256 : v;\n        },\n\n    // extract a bit from a byte\n    readBit: function(byte: number, index: number) {\n        return (byte >> index) & 1;\n    }\n};\n\nexport default Util;\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/\",\n    \"allowJs\": true,\n    \"target\": \"es6\",\n    \"moduleResolution\": \"node\",\n    \"noImplicitAny\": false\n  }\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n  entry: './src/main.ts',\n  module: {\n    rules: [\n      {\n        test: /\\.tsx?$/,\n        use: 'ts-loader',\n        exclude: /node_modules/,\n      },\n    ],\n  },\n  resolve: {\n    extensions: ['.tsx', '.ts', '.js'],\n  },\n  output: {\n    filename: 'gameboy.js',\n    path: path.resolve(__dirname, 'dist'),\n    library: {\n      name: 'GameboyJS',\n      type: 'global',\n    },\n  },\n  optimization: {\n    minimize: false\n  },\n  mode: 'none'\n};\n"
  }
]