From 8d3b2af1950906d5445c46e9f3d41eccfcc39d42 Mon Sep 17 00:00:00 2001 From: Evan Barger Date: Sun, 5 Jul 2020 10:54:47 -0400 Subject: [PATCH] Remove js-emulator (that's on a separate branch now) --- js-emulator/README.md | 43 - js-emulator/css/style.css | 317 - js-emulator/index.html | 44 - js-emulator/js/GameBoyCore.js | 9535 ----------------- js-emulator/js/GameBoyIO.js | 417 - js-emulator/js/other/XAudioJS.swf | Bin 1348 -> 0 bytes js-emulator/js/other/XAudioServer.js | 537 - .../js/other/XAudioServerMediaStreamWorker.js | 68 - js-emulator/js/other/base64.js | 96 - js-emulator/js/other/controls.js | 399 - js-emulator/js/other/mobile.js | 107 - js-emulator/js/other/resampler.js | 206 - js-emulator/js/other/resize.js | 20 - js-emulator/js/other/swfobject.js | 4 - js-emulator/manifest.webapp | 10 - js-emulator/rom/README.md | 2 - js-emulator/rom/game.gb | Bin 1048576 -> 0 bytes 17 files changed, 11805 deletions(-) delete mode 100755 js-emulator/README.md delete mode 100644 js-emulator/css/style.css delete mode 100755 js-emulator/index.html delete mode 100755 js-emulator/js/GameBoyCore.js delete mode 100755 js-emulator/js/GameBoyIO.js delete mode 100755 js-emulator/js/other/XAudioJS.swf delete mode 100755 js-emulator/js/other/XAudioServer.js delete mode 100755 js-emulator/js/other/XAudioServerMediaStreamWorker.js delete mode 100755 js-emulator/js/other/base64.js delete mode 100644 js-emulator/js/other/controls.js delete mode 100755 js-emulator/js/other/mobile.js delete mode 100755 js-emulator/js/other/resampler.js delete mode 100755 js-emulator/js/other/resize.js delete mode 100755 js-emulator/js/other/swfobject.js delete mode 100755 js-emulator/manifest.webapp delete mode 100644 js-emulator/rom/README.md delete mode 100644 js-emulator/rom/game.gb diff --git a/js-emulator/README.md b/js-emulator/README.md deleted file mode 100755 index 0133462..0000000 --- a/js-emulator/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# JavaScript GameBoy Color Emulator - -Fork of Grant Galitz's JavaScript GameBoy Color Emulator made for running a single ROM -when uploading homebrew games to services like Itch.io. - -This version makes the following changes. - -- Canvas fills browser window on desktop/tablet while keeping aspect ratio -- Touch controls displayed on mobile/tablet -- Using css `image-rendering: pixelated` rather than bilinear filtering -- Touch dpad controls for mobile using touch move with a deadzone -- Keyboard fix for iPad keyboard case that doesn't report keyup event keycode -- Wait for keyboard or touch input before starting AudioContext to fix issues in Chrome and iOS not playing Audio -- No ability to switch ROM, or save/load states, this version is intended for deploying a single game - -## Usage - -- Clone this repository -- Add your ROM file as `rom/game.gb` (or edit romPath in js/other/mobile.js to point to your ROM file) -- Upload to a webserver and visit index.html - -## Keyboard Controls - -Up - Up Arrow / W -Down - Down Arrow / S -Left - Left Arrow / A -Right - Right Arrow / D -A - Alt / Z / J -B - Ctrl / K / X -Start - Enter -Select - Shift - -Edit by changing `bindKeyboard` in `js/other/controls.js`. - -## License - -**Copyright (C) 2010 - 2016 Grant Galitz** - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/js-emulator/css/style.css b/js-emulator/css/style.css deleted file mode 100644 index 7c3953a..0000000 --- a/js-emulator/css/style.css +++ /dev/null @@ -1,317 +0,0 @@ -body { - background: #031921; - color: #fff; - font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", - Helvetica, Arial, "Lucida Grande", sans-serif; - font-weight: 300; - margin: 0px; - padding: 0px; - touch-action: none; - text-align: center; - -webkit-touch-callout: none; - user-select: none; - overflow: hidden; -} - -#game { - width: 100%; - touch-action: none; - text-align: center; -} - -#game canvas { - image-rendering: -moz-crisp-edges; - image-rendering: -webkit-crisp-edges; - image-rendering: pixelated; - image-rendering: crisp-edges; -} - -#controller { - display: none; - position: fixed; - bottom: 0px; - height: 210px; - width: 100%; - touch-action: none; - opacity: 0.8; -} - -#controller_dpad { - position: absolute; - bottom: 20px; - left: 0px; - width: 184px; - height: 184px; -} - -#controller_dpad:before { - content: ""; - display: block; - width: 48px; - height: 48px; - background: #5c5c5c; - background: radial-gradient( - ellipse at center, - #5c5c5c 0%, - #555 59%, - #5c5c5c 60% - ); - position: absolute; - left: 68px; - top: 68px; -} - -#controller_left { - position: absolute; - left: 20px; - top: 68px; - width: 48px; - height: 48px; - background: #666; - background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%); - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} - -#controller_right { - position: absolute; - left: 116px; - top: 68px; - width: 48px; - height: 48px; - background: #666; - background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%); - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} - -#controller_up { - position: absolute; - left: 68px; - top: 20px; - width: 48px; - height: 48px; - background: #666; - background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%); - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} - -#controller_down { - position: absolute; - left: 68px; - top: 116px; - width: 48px; - height: 48px; - background: #666; - background: radial-gradient(ellipse at center, #666 0%, #5c5c5c 80%); - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -} - -#controller_a { - position: absolute; - bottom: 110px; - right: 20px; -} - -#controller_b { - position: absolute; - bottom: 80px; - right: 100px; -} - -.roundBtn { - display: flex; - justify-content: center; - align-items: center; - font-weight: bold; - font-size: 32px; - color: #440f1f; - line-height: 64px; - width: 64px; - height: 64px; - border-radius: 64px; - background: #870a4c; - background: radial-gradient(ellipse at center, #ab1465 0%, #8b1e57 100%); - box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2); -} - -.capsuleBtn { - font-weight: bold; - font-size: 10px; - color: #111; - display: flex; - justify-content: center; - align-items: center; - line-height: 40px; - text-transform: uppercase; - width: 64px; - height: 32px; - border-radius: 40px; - background: #222; - background: radial-gradient(ellipse at center, #666 0%, #555 100%); - box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.2); -} - -#controller_start { - position: absolute; - bottom: 20px; - right: 15px; -} - -#controller_select { - position: absolute; - bottom: 20px; - right: 100px; -} - -.btnPressed { - opacity: 0.5; -} - -.spinner { - height: 50px; - width: 50px; - margin: 0px auto; - -webkit-animation: rotation 0.8s linear infinite; - -moz-animation: rotation 0.8s linear infinite; - -o-animation: rotation 0.8s linear infinite; - animation: rotation 0.8s linear infinite; - border-left: 10px solid #306850; - border-right: 10px solid #306850; - border-bottom: 10px solid #306850; - border-top: 10px solid #88c070; - border-radius: 100%; - background-color: #031921; -} -@-webkit-keyframes rotation { - from { - -webkit-transform: rotate(0deg); - } - to { - -webkit-transform: rotate(360deg); - } -} -@-moz-keyframes rotation { - from { - -moz-transform: rotate(0deg); - } - to { - -moz-transform: rotate(360deg); - } -} -@-o-keyframes rotation { - from { - -o-transform: rotate(0deg); - } - to { - -o-transform: rotate(360deg); - } -} -@keyframes rotation { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media only screen and (max-width: 640px) { - #game canvas { - margin-top: 0px; - width: 100%; - max-width: 480px; - border: 0px; - border-radius: 0px; - } -} - -@media only screen and (max-device-width: 812px) and (orientation: portrait) { - body { - margin: 0; - } - - #game { - width: 100%; - position: fixed; - touch-action: none; - } - - #game canvas { - margin: 0; - display: block; - width: 100% !important; - height: auto !important; - } -} - -@media only screen and (max-device-width: 320px) and (orientation: portrait) { - #controller_dpad { - left: -5px; - bottom: -5px; - } - - #controller_a { - right: 5px; - bottom: 95px; - } - - #controller_b { - right: 80px; - } - - #controller_start { - right: 5px; - } - - #controller_select { - right: 80px; - } -} - -@media only screen and (max-width: 500px) and (max-height: 400px) { - #controller { - display: none; - } -} - -/* Small devices in landscape */ -@media only screen and (max-device-width: 300px) and (orientation: landscape) { - html, - body { - height: 100%; - } - body { - display: flex; - justify-content: center; - align-items: center; - } - - #game:after { - content: "PLEASE ROTATE ↻"; - font-size: 24px; - font-weight: bold; - color: #fff; - } - - #game canvas { - display: none; - max-width: 480px; - } - - #controller { - display: none; - } -} - -/* Devices large enough for landscape */ -@media only screen and (min-width: 300px) and (orientation: landscape) { - #controller { - bottom: 50%; - transform: translateY(50%); - opacity: 0.5; - } -} - diff --git a/js-emulator/index.html b/js-emulator/index.html deleted file mode 100755 index 66af3b8..0000000 --- a/js-emulator/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - ___PROJECT_NAME___ - - ___COLORS_HEAD___ - ___PROJECT_HEAD___ - - -
-
- No Canvas Support -
-
-
-
-
-
-
-
-
Select
-
Start
-
B
-
A
-
-
- - - - - - - - - - - - diff --git a/js-emulator/js/GameBoyCore.js b/js-emulator/js/GameBoyCore.js deleted file mode 100755 index 1c2afe2..0000000 --- a/js-emulator/js/GameBoyCore.js +++ /dev/null @@ -1,9535 +0,0 @@ -"use strict"; - /* - JavaScript GameBoy Color Emulator - Copyright (C) 2010-2016 Grant Galitz - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -function GameBoyCore(canvas, ROMImage) { - //Params, etc... - this.canvas = canvas; //Canvas DOM object for drawing out the graphics to. - this.drawContext = null; // LCD Context - this.ROMImage = ROMImage; //The game's ROM. - //CPU Registers and Flags: - this.registerA = 0x01; //Register A (Accumulator) - this.FZero = true; //Register F - Result was zero - this.FSubtract = false; //Register F - Subtraction was executed - this.FHalfCarry = true; //Register F - Half carry or half borrow - this.FCarry = true; //Register F - Carry or borrow - this.registerB = 0x00; //Register B - this.registerC = 0x13; //Register C - this.registerD = 0x00; //Register D - this.registerE = 0xD8; //Register E - this.registersHL = 0x014D; //Registers H and L combined - this.stackPointer = 0xFFFE; //Stack Pointer - this.programCounter = 0x0100; //Program Counter - //Some CPU Emulation State Variables: - this.CPUCyclesTotal = 0; //Relative CPU clocking to speed set, rounded appropriately. - this.CPUCyclesTotalBase = 0; //Relative CPU clocking to speed set base. - this.CPUCyclesTotalCurrent = 0; //Relative CPU clocking to speed set, the directly used value. - this.CPUCyclesTotalRoundoff = 0; //Clocking per iteration rounding catch. - this.baseCPUCyclesPerIteration = 0; //CPU clocks per iteration at 1x speed. - this.remainingClocks = 0; //HALT clocking overrun carry over. - this.inBootstrap = true; //Whether we're in the GBC boot ROM. - this.usedBootROM = false; //Updated upon ROM loading... - this.usedGBCBootROM = false; //Did we boot to the GBC boot ROM? - this.halt = false; //Has the CPU been suspended until the next interrupt? - this.skipPCIncrement = false; //Did we trip the DMG Halt bug? - this.stopEmulator = 3; //Has the emulation been paused or a frame has ended? - this.IME = true; //Are interrupts enabled? - this.IRQLineMatched = 0; //CPU IRQ assertion. - this.interruptsRequested = 0; //IF Register - this.interruptsEnabled = 0; //IE Register - this.hdmaRunning = false; //HDMA Transfer Flag - GBC only - this.CPUTicks = 0; //The number of clock cycles emulated. - this.doubleSpeedShifter = 0; //GBC double speed clocking shifter. - this.JoyPad = 0xFF; //Joypad State (two four-bit states actually) - this.CPUStopped = false; //CPU STOP status. - //Main RAM, MBC RAM, GBC Main RAM, VRAM, etc. - this.memoryReader = []; //Array of functions mapped to read back memory - this.memoryWriter = []; //Array of functions mapped to write to memory - this.memoryHighReader = []; //Array of functions mapped to read back 0xFFXX memory - this.memoryHighWriter = []; //Array of functions mapped to write to 0xFFXX memory - this.ROM = []; //The full ROM file dumped to an array. - this.memory = []; //Main Core Memory - this.MBCRam = []; //Switchable RAM (Used by games for more RAM) for the main memory range 0xA000 - 0xC000. - this.VRAM = []; //Extra VRAM bank for GBC. - this.GBCMemory = []; //GBC main RAM Banks - this.MBC1Mode = false; //MBC1 Type (4/32, 16/8) - this.MBCRAMBanksEnabled = false; //MBC RAM Access Control. - this.currMBCRAMBank = 0; //MBC Currently Indexed RAM Bank - this.currMBCRAMBankPosition = -0xA000; //MBC Position Adder; - this.cGBC = false; //GameBoy Color detection. - this.gbcRamBank = 1; //Currently Switched GameBoy Color ram bank - this.gbcRamBankPosition = -0xD000; //GBC RAM offset from address start. - this.gbcRamBankPositionECHO = -0xF000; //GBC RAM (ECHO mirroring) offset from address start. - this.RAMBanks = [0, 1, 2, 4, 16]; //Used to map the RAM banks to maximum size the MBC used can do. - this.ROMBank1offs = 0; //Offset of the ROM bank switching. - this.currentROMBank = 0; //The parsed current ROM bank selection. - this.cartridgeType = 0; //Cartridge Type - this.name = ""; //Name of the game - this.gameCode = ""; //Game code (Suffix for older games) - this.fromSaveState = false; //A boolean to see if this was loaded in as a save state. - this.savedStateFileName = ""; //When loaded in as a save state, this will not be empty. - this.STATTracker = 0; //Tracker for STAT triggering. - this.modeSTAT = 0; //The scan line mode (for lines 1-144 it's 2-3-0, for 145-154 it's 1) - this.spriteCount = 252; //Mode 3 extra clocking counter (Depends on how many sprites are on the current line.). - this.LYCMatchTriggerSTAT = false; //Should we trigger an interrupt if LY==LYC? - this.mode2TriggerSTAT = false; //Should we trigger an interrupt if in mode 2? - this.mode1TriggerSTAT = false; //Should we trigger an interrupt if in mode 1? - this.mode0TriggerSTAT = false; //Should we trigger an interrupt if in mode 0? - this.LCDisOn = false; //Is the emulated LCD controller on? - this.LINECONTROL = []; //Array of functions to handle each scan line we do (onscreen + offscreen) - this.DISPLAYOFFCONTROL = [function (parentObj) { - //Array of line 0 function to handle the LCD controller when it's off (Do nothing!). - }]; - this.LCDCONTROL = null; //Pointer to either LINECONTROL or DISPLAYOFFCONTROL. - this.initializeLCDController(); //Compile the LCD controller functions. - //RTC (Real Time Clock for MBC3): - this.RTCisLatched = false; - this.latchedSeconds = 0; //RTC latched seconds. - this.latchedMinutes = 0; //RTC latched minutes. - this.latchedHours = 0; //RTC latched hours. - this.latchedLDays = 0; //RTC latched lower 8-bits of the day counter. - this.latchedHDays = 0; //RTC latched high-bit of the day counter. - this.RTCSeconds = 0; //RTC seconds counter. - this.RTCMinutes = 0; //RTC minutes counter. - this.RTCHours = 0; //RTC hours counter. - this.RTCDays = 0; //RTC days counter. - this.RTCDayOverFlow = false; //Did the RTC overflow and wrap the day counter? - this.RTCHALT = false; //Is the RTC allowed to clock up? - //Gyro: - this.highX = 127; - this.lowX = 127; - this.highY = 127; - this.lowY = 127; - //Sound variables: - this.audioHandle = null; //XAudioJS handle - this.numSamplesTotal = 0; //Length of the sound buffers. - this.dutyLookup = [ //Map the duty values given to ones we can work with. - [false, false, false, false, false, false, false, true], - [true, false, false, false, false, false, false, true], - [true, false, false, false, false, true, true, true], - [false, true, true, true, true, true, true, false] - ]; - this.bufferContainAmount = 0; //Buffer maintenance metric. - this.LSFR15Table = null; - this.LSFR7Table = null; - this.noiseSampleTable = null; - this.initializeAudioStartState(); - this.soundMasterEnabled = false; //As its name implies - this.channel3PCM = null; //Channel 3 adjusted sample buffer. - //Vin Shit: - this.VinLeftChannelMasterVolume = 8; //Computed post-mixing volume. - this.VinRightChannelMasterVolume = 8; //Computed post-mixing volume. - //Channel paths enabled: - this.leftChannel1 = false; - this.leftChannel2 = false; - this.leftChannel3 = false; - this.leftChannel4 = false; - this.rightChannel1 = false; - this.rightChannel2 = false; - this.rightChannel3 = false; - this.rightChannel4 = false; - this.audioClocksUntilNextEvent = 1; - this.audioClocksUntilNextEventCounter = 1; - //Channel output level caches: - this.channel1currentSampleLeft = 0; - this.channel1currentSampleRight = 0; - this.channel2currentSampleLeft = 0; - this.channel2currentSampleRight = 0; - this.channel3currentSampleLeft = 0; - this.channel3currentSampleRight = 0; - this.channel4currentSampleLeft = 0; - this.channel4currentSampleRight = 0; - this.channel1currentSampleLeftSecondary = 0; - this.channel1currentSampleRightSecondary = 0; - this.channel2currentSampleLeftSecondary = 0; - this.channel2currentSampleRightSecondary = 0; - this.channel3currentSampleLeftSecondary = 0; - this.channel3currentSampleRightSecondary = 0; - this.channel4currentSampleLeftSecondary = 0; - this.channel4currentSampleRightSecondary = 0; - this.channel1currentSampleLeftTrimary = 0; - this.channel1currentSampleRightTrimary = 0; - this.channel2currentSampleLeftTrimary = 0; - this.channel2currentSampleRightTrimary = 0; - this.mixerOutputCache = 0; - //Pre-multipliers to cache some calculations: - this.emulatorSpeed = 1; - this.initializeTiming(); - //Audio generation counters: - this.audioTicks = 0; //Used to sample the audio system every x CPU instructions. - this.audioIndex = 0; //Used to keep alignment on audio generation. - this.downsampleInput = 0; - this.audioDestinationPosition = 0; //Used to keep alignment on audio generation. - this.rollover = 0; //Used to keep alignment on the number of samples to output (Realign from counter alias). - //Timing Variables - this.emulatorTicks = 0; //Times for how many instructions to execute before ending the loop. - this.DIVTicks = 56; //DIV Ticks Counter (Invisible lower 8-bit) - this.LCDTicks = 60; //Counter for how many instructions have been executed on a scanline so far. - this.timerTicks = 0; //Counter for the TIMA timer. - this.TIMAEnabled = false; //Is TIMA enabled? - this.TACClocker = 1024; //Timer Max Ticks - this.serialTimer = 0; //Serial IRQ Timer - this.serialShiftTimer = 0; //Serial Transfer Shift Timer - this.serialShiftTimerAllocated = 0; //Serial Transfer Shift Timer Refill - this.IRQEnableDelay = 0; //Are the interrupts on queue to be enabled? - var dateVar = new Date(); - this.lastIteration = dateVar.getTime();//The last time we iterated the main loop. - dateVar = new Date(); - this.firstIteration = dateVar.getTime(); - this.iterations = 0; - this.actualScanLine = 0; //Actual scan line... - this.lastUnrenderedLine = 0; //Last rendered scan line... - this.queuedScanLines = 0; - this.totalLinesPassed = 0; - this.haltPostClocks = 0; //Post-Halt clocking. - //ROM Cartridge Components: - this.cMBC1 = false; //Does the cartridge use MBC1? - this.cMBC2 = false; //Does the cartridge use MBC2? - this.cMBC3 = false; //Does the cartridge use MBC3? - this.cMBC5 = false; //Does the cartridge use MBC5? - this.cMBC7 = false; //Does the cartridge use MBC7? - this.cSRAM = false; //Does the cartridge use save RAM? - this.cMMMO1 = false; //... - this.cRUMBLE = false; //Does the cartridge use the RUMBLE addressing (modified MBC5)? - this.cCamera = false; //Is the cartridge actually a GameBoy Camera? - this.cTAMA5 = false; //Does the cartridge use TAMA5? (Tamagotchi Cartridge) - this.cHuC3 = false; //Does the cartridge use HuC3 (Hudson Soft / modified MBC3)? - this.cHuC1 = false; //Does the cartridge use HuC1 (Hudson Soft / modified MBC1)? - this.cTIMER = false; //Does the cartridge have an RTC? - this.ROMBanks = [ // 1 Bank = 16 KBytes = 256 Kbits - 2, 4, 8, 16, 32, 64, 128, 256, 512 - ]; - this.ROMBanks[0x52] = 72; - this.ROMBanks[0x53] = 80; - this.ROMBanks[0x54] = 96; - this.numRAMBanks = 0; //How many RAM banks were actually allocated? - ////Graphics Variables - this.currVRAMBank = 0; //Current VRAM bank for GBC. - this.backgroundX = 0; //Register SCX (X-Scroll) - this.backgroundY = 0; //Register SCY (Y-Scroll) - this.gfxWindowDisplay = false; //Is the windows enabled? - this.gfxSpriteShow = false; //Are sprites enabled? - this.gfxSpriteNormalHeight = true; //Are we doing 8x8 or 8x16 sprites? - this.bgEnabled = true; //Is the BG enabled? - this.BGPriorityEnabled = true; //Can we flag the BG for priority over sprites? - this.gfxWindowCHRBankPosition = 0; //The current bank of the character map the window uses. - this.gfxBackgroundCHRBankPosition = 0; //The current bank of the character map the BG uses. - this.gfxBackgroundBankOffset = 0x80; //Fast mapping of the tile numbering/ - this.windowY = 0; //Current Y offset of the window. - this.windowX = 0; //Current X offset of the window. - this.drewBlank = 0; //To prevent the repeating of drawing a blank screen. - this.drewFrame = false; //Throttle how many draws we can do to once per iteration. - this.midScanlineOffset = -1; //mid-scanline rendering offset. - this.pixelEnd = 0; //track the x-coord limit for line rendering (mid-scanline usage). - this.currentX = 0; //The x-coord we left off at for mid-scanline rendering. - //BG Tile Pointer Caches: - this.BGCHRBank1 = null; - this.BGCHRBank2 = null; - this.BGCHRCurrentBank = null; - //Tile Data Cache: - this.tileCache = null; - //Palettes: - this.colors = [0xEFFFDE, 0xADD794, 0x529273, 0x183442]; //"Classic" GameBoy palette colors. - this.OBJPalette = null; - this.BGPalette = null; - this.gbcOBJRawPalette = null; - this.gbcBGRawPalette = null; - this.gbOBJPalette = null; - this.gbBGPalette = null; - this.gbcOBJPalette = null; - this.gbcBGPalette = null; - this.gbBGColorizedPalette = null; - this.gbOBJColorizedPalette = null; - this.cachedBGPaletteConversion = null; - this.cachedOBJPaletteConversion = null; - this.updateGBBGPalette = this.updateGBRegularBGPalette; - this.updateGBOBJPalette = this.updateGBRegularOBJPalette; - this.colorizedGBPalettes = false; - this.BGLayerRender = null; //Reference to the BG rendering function. - this.WindowLayerRender = null; //Reference to the window rendering function. - this.SpriteLayerRender = null; //Reference to the OAM rendering function. - this.frameBuffer = []; //The internal frame-buffer. - this.swizzledFrame = null; //The secondary gfx buffer that holds the converted RGBA values. - this.canvasBuffer = null; //imageData handle - this.pixelStart = 0; //Temp variable for holding the current working framebuffer offset. - //Variables used for scaling in JS: - this.onscreenWidth = this.offscreenWidth = 160; - this.onscreenHeight = this.offscreenHeight = 144; - this.offscreenRGBCount = this.onscreenWidth * this.onscreenHeight * 4; - this.resizePathClear = true; - //Initialize the white noise cache tables ahead of time: - this.intializeWhiteNoise(); -} -GameBoyCore.prototype.GBBOOTROM = [ //GB BOOT ROM - //Add 256 byte boot rom here if you are going to use it. -]; -GameBoyCore.prototype.GBCBOOTROM = [ //GBC BOOT ROM - //Add 2048 byte boot rom here if you are going to use it. -]; -GameBoyCore.prototype.ffxxDump = [ //Dump of the post-BOOT I/O register state (From gambatte): - 0x0F, 0x00, 0x7C, 0xFF, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, - 0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF, - 0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, - 0x91, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7E, 0xFF, 0xFE, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xC1, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, - 0xF8, 0xFF, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, - 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, - 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, - 0x45, 0xEC, 0x52, 0xFA, 0x08, 0xB7, 0x07, 0x5D, 0x01, 0xFD, 0xC0, 0xFF, 0x08, 0xFC, 0x00, 0xE5, - 0x0B, 0xF8, 0xC2, 0xCE, 0xF4, 0xF9, 0x0F, 0x7F, 0x45, 0x6D, 0x3D, 0xFE, 0x46, 0x97, 0x33, 0x5E, - 0x08, 0xEF, 0xF1, 0xFF, 0x86, 0x83, 0x24, 0x74, 0x12, 0xFC, 0x00, 0x9F, 0xB4, 0xB7, 0x06, 0xD5, - 0xD0, 0x7A, 0x00, 0x9E, 0x04, 0x5F, 0x41, 0x2F, 0x1D, 0x77, 0x36, 0x75, 0x81, 0xAA, 0x70, 0x3A, - 0x98, 0xD1, 0x71, 0x02, 0x4D, 0x01, 0xC1, 0xFF, 0x0D, 0x00, 0xD3, 0x05, 0xF9, 0x00, 0x0B, 0x00 -]; -GameBoyCore.prototype.OPCODE = [ - //NOP - //#0x00: - function (parentObj) { - //Do Nothing... - }, - //LD BC, nn - //#0x01: - function (parentObj) { - parentObj.registerC = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.registerB = parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - }, - //LD (BC), A - //#0x02: - function (parentObj) { - parentObj.memoryWrite((parentObj.registerB << 8) | parentObj.registerC, parentObj.registerA); - }, - //INC BC - //#0x03: - function (parentObj) { - var temp_var = ((parentObj.registerB << 8) | parentObj.registerC) + 1; - parentObj.registerB = (temp_var >> 8) & 0xFF; - parentObj.registerC = temp_var & 0xFF; - }, - //INC B - //#0x04: - function (parentObj) { - parentObj.registerB = (parentObj.registerB + 1) & 0xFF; - parentObj.FZero = (parentObj.registerB == 0); - parentObj.FHalfCarry = ((parentObj.registerB & 0xF) == 0); - parentObj.FSubtract = false; - }, - //DEC B - //#0x05: - function (parentObj) { - parentObj.registerB = (parentObj.registerB - 1) & 0xFF; - parentObj.FZero = (parentObj.registerB == 0); - parentObj.FHalfCarry = ((parentObj.registerB & 0xF) == 0xF); - parentObj.FSubtract = true; - }, - //LD B, n - //#0x06: - function (parentObj) { - parentObj.registerB = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //RLCA - //#0x07: - function (parentObj) { - parentObj.FCarry = (parentObj.registerA > 0x7F); - parentObj.registerA = ((parentObj.registerA << 1) & 0xFF) | (parentObj.registerA >> 7); - parentObj.FZero = parentObj.FSubtract = parentObj.FHalfCarry = false; - }, - //LD (nn), SP - //#0x08: - function (parentObj) { - var temp_var = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - parentObj.memoryWrite(temp_var, parentObj.stackPointer & 0xFF); - parentObj.memoryWrite((temp_var + 1) & 0xFFFF, parentObj.stackPointer >> 8); - }, - //ADD HL, BC - //#0x09: - function (parentObj) { - var dirtySum = parentObj.registersHL + ((parentObj.registerB << 8) | parentObj.registerC); - parentObj.FHalfCarry = ((parentObj.registersHL & 0xFFF) > (dirtySum & 0xFFF)); - parentObj.FCarry = (dirtySum > 0xFFFF); - parentObj.registersHL = dirtySum & 0xFFFF; - parentObj.FSubtract = false; - }, - //LD A, (BC) - //#0x0A: - function (parentObj) { - parentObj.registerA = parentObj.memoryRead((parentObj.registerB << 8) | parentObj.registerC); - }, - //DEC BC - //#0x0B: - function (parentObj) { - var temp_var = (((parentObj.registerB << 8) | parentObj.registerC) - 1) & 0xFFFF; - parentObj.registerB = temp_var >> 8; - parentObj.registerC = temp_var & 0xFF; - }, - //INC C - //#0x0C: - function (parentObj) { - parentObj.registerC = (parentObj.registerC + 1) & 0xFF; - parentObj.FZero = (parentObj.registerC == 0); - parentObj.FHalfCarry = ((parentObj.registerC & 0xF) == 0); - parentObj.FSubtract = false; - }, - //DEC C - //#0x0D: - function (parentObj) { - parentObj.registerC = (parentObj.registerC - 1) & 0xFF; - parentObj.FZero = (parentObj.registerC == 0); - parentObj.FHalfCarry = ((parentObj.registerC & 0xF) == 0xF); - parentObj.FSubtract = true; - }, - //LD C, n - //#0x0E: - function (parentObj) { - parentObj.registerC = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //RRCA - //#0x0F: - function (parentObj) { - parentObj.registerA = (parentObj.registerA >> 1) | ((parentObj.registerA & 1) << 7); - parentObj.FCarry = (parentObj.registerA > 0x7F); - parentObj.FZero = parentObj.FSubtract = parentObj.FHalfCarry = false; - }, - //STOP - //#0x10: - function (parentObj) { - if (parentObj.cGBC) { - if ((parentObj.memory[0xFF4D] & 0x01) == 0x01) { //Speed change requested. - if (parentObj.memory[0xFF4D] > 0x7F) { //Go back to single speed mode. - cout("Going into single clock speed mode.", 0); - parentObj.doubleSpeedShifter = 0; - parentObj.memory[0xFF4D] &= 0x7F; //Clear the double speed mode flag. - } - else { //Go to double speed mode. - cout("Going into double clock speed mode.", 0); - parentObj.doubleSpeedShifter = 1; - parentObj.memory[0xFF4D] |= 0x80; //Set the double speed mode flag. - } - parentObj.memory[0xFF4D] &= 0xFE; //Reset the request bit. - } - else { - parentObj.handleSTOP(); - } - } - else { - parentObj.handleSTOP(); - } - }, - //LD DE, nn - //#0x11: - function (parentObj) { - parentObj.registerE = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.registerD = parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - }, - //LD (DE), A - //#0x12: - function (parentObj) { - parentObj.memoryWrite((parentObj.registerD << 8) | parentObj.registerE, parentObj.registerA); - }, - //INC DE - //#0x13: - function (parentObj) { - var temp_var = ((parentObj.registerD << 8) | parentObj.registerE) + 1; - parentObj.registerD = (temp_var >> 8) & 0xFF; - parentObj.registerE = temp_var & 0xFF; - }, - //INC D - //#0x14: - function (parentObj) { - parentObj.registerD = (parentObj.registerD + 1) & 0xFF; - parentObj.FZero = (parentObj.registerD == 0); - parentObj.FHalfCarry = ((parentObj.registerD & 0xF) == 0); - parentObj.FSubtract = false; - }, - //DEC D - //#0x15: - function (parentObj) { - parentObj.registerD = (parentObj.registerD - 1) & 0xFF; - parentObj.FZero = (parentObj.registerD == 0); - parentObj.FHalfCarry = ((parentObj.registerD & 0xF) == 0xF); - parentObj.FSubtract = true; - }, - //LD D, n - //#0x16: - function (parentObj) { - parentObj.registerD = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //RLA - //#0x17: - function (parentObj) { - var carry_flag = (parentObj.FCarry) ? 1 : 0; - parentObj.FCarry = (parentObj.registerA > 0x7F); - parentObj.registerA = ((parentObj.registerA << 1) & 0xFF) | carry_flag; - parentObj.FZero = parentObj.FSubtract = parentObj.FHalfCarry = false; - }, - //JR n - //#0x18: - function (parentObj) { - parentObj.programCounter = (parentObj.programCounter + ((parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24) + 1) & 0xFFFF; - }, - //ADD HL, DE - //#0x19: - function (parentObj) { - var dirtySum = parentObj.registersHL + ((parentObj.registerD << 8) | parentObj.registerE); - parentObj.FHalfCarry = ((parentObj.registersHL & 0xFFF) > (dirtySum & 0xFFF)); - parentObj.FCarry = (dirtySum > 0xFFFF); - parentObj.registersHL = dirtySum & 0xFFFF; - parentObj.FSubtract = false; - }, - //LD A, (DE) - //#0x1A: - function (parentObj) { - parentObj.registerA = parentObj.memoryRead((parentObj.registerD << 8) | parentObj.registerE); - }, - //DEC DE - //#0x1B: - function (parentObj) { - var temp_var = (((parentObj.registerD << 8) | parentObj.registerE) - 1) & 0xFFFF; - parentObj.registerD = temp_var >> 8; - parentObj.registerE = temp_var & 0xFF; - }, - //INC E - //#0x1C: - function (parentObj) { - parentObj.registerE = (parentObj.registerE + 1) & 0xFF; - parentObj.FZero = (parentObj.registerE == 0); - parentObj.FHalfCarry = ((parentObj.registerE & 0xF) == 0); - parentObj.FSubtract = false; - }, - //DEC E - //#0x1D: - function (parentObj) { - parentObj.registerE = (parentObj.registerE - 1) & 0xFF; - parentObj.FZero = (parentObj.registerE == 0); - parentObj.FHalfCarry = ((parentObj.registerE & 0xF) == 0xF); - parentObj.FSubtract = true; - }, - //LD E, n - //#0x1E: - function (parentObj) { - parentObj.registerE = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //RRA - //#0x1F: - function (parentObj) { - var carry_flag = (parentObj.FCarry) ? 0x80 : 0; - parentObj.FCarry = ((parentObj.registerA & 1) == 1); - parentObj.registerA = (parentObj.registerA >> 1) | carry_flag; - parentObj.FZero = parentObj.FSubtract = parentObj.FHalfCarry = false; - }, - //JR NZ, n - //#0x20: - function (parentObj) { - if (!parentObj.FZero) { - parentObj.programCounter = (parentObj.programCounter + ((parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24) + 1) & 0xFFFF; - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - } - }, - //LD HL, nn - //#0x21: - function (parentObj) { - parentObj.registersHL = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - }, - //LDI (HL), A - //#0x22: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerA); - parentObj.registersHL = (parentObj.registersHL + 1) & 0xFFFF; - }, - //INC HL - //#0x23: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL + 1) & 0xFFFF; - }, - //INC H - //#0x24: - function (parentObj) { - var H = ((parentObj.registersHL >> 8) + 1) & 0xFF; - parentObj.FZero = (H == 0); - parentObj.FHalfCarry = ((H & 0xF) == 0); - parentObj.FSubtract = false; - parentObj.registersHL = (H << 8) | (parentObj.registersHL & 0xFF); - }, - //DEC H - //#0x25: - function (parentObj) { - var H = ((parentObj.registersHL >> 8) - 1) & 0xFF; - parentObj.FZero = (H == 0); - parentObj.FHalfCarry = ((H & 0xF) == 0xF); - parentObj.FSubtract = true; - parentObj.registersHL = (H << 8) | (parentObj.registersHL & 0xFF); - }, - //LD H, n - //#0x26: - function (parentObj) { - parentObj.registersHL = (parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 8) | (parentObj.registersHL & 0xFF); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //DAA - //#0x27: - function (parentObj) { - if (!parentObj.FSubtract) { - if (parentObj.FCarry || parentObj.registerA > 0x99) { - parentObj.registerA = (parentObj.registerA + 0x60) & 0xFF; - parentObj.FCarry = true; - } - if (parentObj.FHalfCarry || (parentObj.registerA & 0xF) > 0x9) { - parentObj.registerA = (parentObj.registerA + 0x06) & 0xFF; - parentObj.FHalfCarry = false; - } - } - else if (parentObj.FCarry && parentObj.FHalfCarry) { - parentObj.registerA = (parentObj.registerA + 0x9A) & 0xFF; - parentObj.FHalfCarry = false; - } - else if (parentObj.FCarry) { - parentObj.registerA = (parentObj.registerA + 0xA0) & 0xFF; - } - else if (parentObj.FHalfCarry) { - parentObj.registerA = (parentObj.registerA + 0xFA) & 0xFF; - parentObj.FHalfCarry = false; - } - parentObj.FZero = (parentObj.registerA == 0); - }, - //JR Z, n - //#0x28: - function (parentObj) { - if (parentObj.FZero) { - parentObj.programCounter = (parentObj.programCounter + ((parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24) + 1) & 0xFFFF; - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - } - }, - //ADD HL, HL - //#0x29: - function (parentObj) { - parentObj.FHalfCarry = ((parentObj.registersHL & 0xFFF) > 0x7FF); - parentObj.FCarry = (parentObj.registersHL > 0x7FFF); - parentObj.registersHL = (parentObj.registersHL << 1) & 0xFFFF; - parentObj.FSubtract = false; - }, - //LDI A, (HL) - //#0x2A: - function (parentObj) { - parentObj.registerA = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.registersHL = (parentObj.registersHL + 1) & 0xFFFF; - }, - //DEC HL - //#0x2B: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL - 1) & 0xFFFF; - }, - //INC L - //#0x2C: - function (parentObj) { - var L = (parentObj.registersHL + 1) & 0xFF; - parentObj.FZero = (L == 0); - parentObj.FHalfCarry = ((L & 0xF) == 0); - parentObj.FSubtract = false; - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | L; - }, - //DEC L - //#0x2D: - function (parentObj) { - var L = (parentObj.registersHL - 1) & 0xFF; - parentObj.FZero = (L == 0); - parentObj.FHalfCarry = ((L & 0xF) == 0xF); - parentObj.FSubtract = true; - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | L; - }, - //LD L, n - //#0x2E: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //CPL - //#0x2F: - function (parentObj) { - parentObj.registerA ^= 0xFF; - parentObj.FSubtract = parentObj.FHalfCarry = true; - }, - //JR NC, n - //#0x30: - function (parentObj) { - if (!parentObj.FCarry) { - parentObj.programCounter = (parentObj.programCounter + ((parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24) + 1) & 0xFFFF; - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - } - }, - //LD SP, nn - //#0x31: - function (parentObj) { - parentObj.stackPointer = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - }, - //LDD (HL), A - //#0x32: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerA); - parentObj.registersHL = (parentObj.registersHL - 1) & 0xFFFF; - }, - //INC SP - //#0x33: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer + 1) & 0xFFFF; - }, - //INC (HL) - //#0x34: - function (parentObj) { - var temp_var = (parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) + 1) & 0xFF; - parentObj.FZero = (temp_var == 0); - parentObj.FHalfCarry = ((temp_var & 0xF) == 0); - parentObj.FSubtract = false; - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - }, - //DEC (HL) - //#0x35: - function (parentObj) { - var temp_var = (parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) - 1) & 0xFF; - parentObj.FZero = (temp_var == 0); - parentObj.FHalfCarry = ((temp_var & 0xF) == 0xF); - parentObj.FSubtract = true; - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - }, - //LD (HL), n - //#0x36: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter)); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //SCF - //#0x37: - function (parentObj) { - parentObj.FCarry = true; - parentObj.FSubtract = parentObj.FHalfCarry = false; - }, - //JR C, n - //#0x38: - function (parentObj) { - if (parentObj.FCarry) { - parentObj.programCounter = (parentObj.programCounter + ((parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24) + 1) & 0xFFFF; - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - } - }, - //ADD HL, SP - //#0x39: - function (parentObj) { - var dirtySum = parentObj.registersHL + parentObj.stackPointer; - parentObj.FHalfCarry = ((parentObj.registersHL & 0xFFF) > (dirtySum & 0xFFF)); - parentObj.FCarry = (dirtySum > 0xFFFF); - parentObj.registersHL = dirtySum & 0xFFFF; - parentObj.FSubtract = false; - }, - //LDD A, (HL) - //#0x3A: - function (parentObj) { - parentObj.registerA = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.registersHL = (parentObj.registersHL - 1) & 0xFFFF; - }, - //DEC SP - //#0x3B: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - }, - //INC A - //#0x3C: - function (parentObj) { - parentObj.registerA = (parentObj.registerA + 1) & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) == 0); - parentObj.FSubtract = false; - }, - //DEC A - //#0x3D: - function (parentObj) { - parentObj.registerA = (parentObj.registerA - 1) & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) == 0xF); - parentObj.FSubtract = true; - }, - //LD A, n - //#0x3E: - function (parentObj) { - parentObj.registerA = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //CCF - //#0x3F: - function (parentObj) { - parentObj.FCarry = !parentObj.FCarry; - parentObj.FSubtract = parentObj.FHalfCarry = false; - }, - //LD B, B - //#0x40: - function (parentObj) { - //Do nothing... - }, - //LD B, C - //#0x41: - function (parentObj) { - parentObj.registerB = parentObj.registerC; - }, - //LD B, D - //#0x42: - function (parentObj) { - parentObj.registerB = parentObj.registerD; - }, - //LD B, E - //#0x43: - function (parentObj) { - parentObj.registerB = parentObj.registerE; - }, - //LD B, H - //#0x44: - function (parentObj) { - parentObj.registerB = parentObj.registersHL >> 8; - }, - //LD B, L - //#0x45: - function (parentObj) { - parentObj.registerB = parentObj.registersHL & 0xFF; - }, - //LD B, (HL) - //#0x46: - function (parentObj) { - parentObj.registerB = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - }, - //LD B, A - //#0x47: - function (parentObj) { - parentObj.registerB = parentObj.registerA; - }, - //LD C, B - //#0x48: - function (parentObj) { - parentObj.registerC = parentObj.registerB; - }, - //LD C, C - //#0x49: - function (parentObj) { - //Do nothing... - }, - //LD C, D - //#0x4A: - function (parentObj) { - parentObj.registerC = parentObj.registerD; - }, - //LD C, E - //#0x4B: - function (parentObj) { - parentObj.registerC = parentObj.registerE; - }, - //LD C, H - //#0x4C: - function (parentObj) { - parentObj.registerC = parentObj.registersHL >> 8; - }, - //LD C, L - //#0x4D: - function (parentObj) { - parentObj.registerC = parentObj.registersHL & 0xFF; - }, - //LD C, (HL) - //#0x4E: - function (parentObj) { - parentObj.registerC = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - }, - //LD C, A - //#0x4F: - function (parentObj) { - parentObj.registerC = parentObj.registerA; - }, - //LD D, B - //#0x50: - function (parentObj) { - parentObj.registerD = parentObj.registerB; - }, - //LD D, C - //#0x51: - function (parentObj) { - parentObj.registerD = parentObj.registerC; - }, - //LD D, D - //#0x52: - function (parentObj) { - //Do nothing... - }, - //LD D, E - //#0x53: - function (parentObj) { - parentObj.registerD = parentObj.registerE; - }, - //LD D, H - //#0x54: - function (parentObj) { - parentObj.registerD = parentObj.registersHL >> 8; - }, - //LD D, L - //#0x55: - function (parentObj) { - parentObj.registerD = parentObj.registersHL & 0xFF; - }, - //LD D, (HL) - //#0x56: - function (parentObj) { - parentObj.registerD = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - }, - //LD D, A - //#0x57: - function (parentObj) { - parentObj.registerD = parentObj.registerA; - }, - //LD E, B - //#0x58: - function (parentObj) { - parentObj.registerE = parentObj.registerB; - }, - //LD E, C - //#0x59: - function (parentObj) { - parentObj.registerE = parentObj.registerC; - }, - //LD E, D - //#0x5A: - function (parentObj) { - parentObj.registerE = parentObj.registerD; - }, - //LD E, E - //#0x5B: - function (parentObj) { - //Do nothing... - }, - //LD E, H - //#0x5C: - function (parentObj) { - parentObj.registerE = parentObj.registersHL >> 8; - }, - //LD E, L - //#0x5D: - function (parentObj) { - parentObj.registerE = parentObj.registersHL & 0xFF; - }, - //LD E, (HL) - //#0x5E: - function (parentObj) { - parentObj.registerE = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - }, - //LD E, A - //#0x5F: - function (parentObj) { - parentObj.registerE = parentObj.registerA; - }, - //LD H, B - //#0x60: - function (parentObj) { - parentObj.registersHL = (parentObj.registerB << 8) | (parentObj.registersHL & 0xFF); - }, - //LD H, C - //#0x61: - function (parentObj) { - parentObj.registersHL = (parentObj.registerC << 8) | (parentObj.registersHL & 0xFF); - }, - //LD H, D - //#0x62: - function (parentObj) { - parentObj.registersHL = (parentObj.registerD << 8) | (parentObj.registersHL & 0xFF); - }, - //LD H, E - //#0x63: - function (parentObj) { - parentObj.registersHL = (parentObj.registerE << 8) | (parentObj.registersHL & 0xFF); - }, - //LD H, H - //#0x64: - function (parentObj) { - //Do nothing... - }, - //LD H, L - //#0x65: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF) * 0x101; - }, - //LD H, (HL) - //#0x66: - function (parentObj) { - parentObj.registersHL = (parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) << 8) | (parentObj.registersHL & 0xFF); - }, - //LD H, A - //#0x67: - function (parentObj) { - parentObj.registersHL = (parentObj.registerA << 8) | (parentObj.registersHL & 0xFF); - }, - //LD L, B - //#0x68: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.registerB; - }, - //LD L, C - //#0x69: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.registerC; - }, - //LD L, D - //#0x6A: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.registerD; - }, - //LD L, E - //#0x6B: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.registerE; - }, - //LD L, H - //#0x6C: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | (parentObj.registersHL >> 8); - }, - //LD L, L - //#0x6D: - function (parentObj) { - //Do nothing... - }, - //LD L, (HL) - //#0x6E: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - }, - //LD L, A - //#0x6F: - function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | parentObj.registerA; - }, - //LD (HL), B - //#0x70: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerB); - }, - //LD (HL), C - //#0x71: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerC); - }, - //LD (HL), D - //#0x72: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerD); - }, - //LD (HL), E - //#0x73: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerE); - }, - //LD (HL), H - //#0x74: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registersHL >> 8); - }, - //LD (HL), L - //#0x75: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registersHL & 0xFF); - }, - //HALT - //#0x76: - function (parentObj) { - //See if there's already an IRQ match: - if ((parentObj.interruptsEnabled & parentObj.interruptsRequested & 0x1F) > 0) { - if (!parentObj.cGBC && !parentObj.usedBootROM) { - //HALT bug in the DMG CPU model (Program Counter fails to increment for one instruction after HALT): - parentObj.skipPCIncrement = true; - } - else { - //CGB gets around the HALT PC bug by doubling the hidden NOP. - parentObj.CPUTicks += 4; - } - } - else { - //CPU is stalled until the next IRQ match: - parentObj.calculateHALTPeriod(); - } - }, - //LD (HL), A - //#0x77: - function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.registerA); - }, - //LD A, B - //#0x78: - function (parentObj) { - parentObj.registerA = parentObj.registerB; - }, - //LD A, C - //#0x79: - function (parentObj) { - parentObj.registerA = parentObj.registerC; - }, - //LD A, D - //#0x7A: - function (parentObj) { - parentObj.registerA = parentObj.registerD; - }, - //LD A, E - //#0x7B: - function (parentObj) { - parentObj.registerA = parentObj.registerE; - }, - //LD A, H - //#0x7C: - function (parentObj) { - parentObj.registerA = parentObj.registersHL >> 8; - }, - //LD A, L - //#0x7D: - function (parentObj) { - parentObj.registerA = parentObj.registersHL & 0xFF; - }, - //LD, A, (HL) - //#0x7E: - function (parentObj) { - parentObj.registerA = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - }, - //LD A, A - //#0x7F: - function (parentObj) { - //Do Nothing... - }, - //ADD A, B - //#0x80: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerB; - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, C - //#0x81: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerC; - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, D - //#0x82: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerD; - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, E - //#0x83: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerE; - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, H - //#0x84: - function (parentObj) { - var dirtySum = parentObj.registerA + (parentObj.registersHL >> 8); - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, L - //#0x85: - function (parentObj) { - var dirtySum = parentObj.registerA + (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, (HL) - //#0x86: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADD A, A - //#0x87: - function (parentObj) { - parentObj.FHalfCarry = ((parentObj.registerA & 0x8) == 0x8); - parentObj.FCarry = (parentObj.registerA > 0x7F); - parentObj.registerA = (parentObj.registerA << 1) & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, B - //#0x88: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerB + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (parentObj.registerB & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, C - //#0x89: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerC + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (parentObj.registerC & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, D - //#0x8A: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerD + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (parentObj.registerD & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, E - //#0x8B: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.registerE + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (parentObj.registerE & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, H - //#0x8C: - function (parentObj) { - var tempValue = (parentObj.registersHL >> 8); - var dirtySum = parentObj.registerA + tempValue + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (tempValue & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, L - //#0x8D: - function (parentObj) { - var tempValue = (parentObj.registersHL & 0xFF); - var dirtySum = parentObj.registerA + tempValue + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (tempValue & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, (HL) - //#0x8E: - function (parentObj) { - var tempValue = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - var dirtySum = parentObj.registerA + tempValue + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (tempValue & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //ADC A, A - //#0x8F: - function (parentObj) { - //shift left register A one bit for some ops here as an optimization: - var dirtySum = (parentObj.registerA << 1) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((((parentObj.registerA << 1) & 0x1E) | ((parentObj.FCarry) ? 1 : 0)) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //SUB A, B - //#0x90: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerB; - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, C - //#0x91: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerC; - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, D - //#0x92: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerD; - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, E - //#0x93: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerE; - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, H - //#0x94: - function (parentObj) { - var dirtySum = parentObj.registerA - (parentObj.registersHL >> 8); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, L - //#0x95: - function (parentObj) { - var dirtySum = parentObj.registerA - (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, (HL) - //#0x96: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //SUB A, A - //#0x97: - function (parentObj) { - //number - same number == 0 - parentObj.registerA = 0; - parentObj.FHalfCarry = parentObj.FCarry = false; - parentObj.FZero = parentObj.FSubtract = true; - }, - //SBC A, B - //#0x98: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerB - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (parentObj.registerB & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, C - //#0x99: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerC - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (parentObj.registerC & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, D - //#0x9A: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerD - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (parentObj.registerD & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, E - //#0x9B: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerE - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (parentObj.registerE & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, H - //#0x9C: - function (parentObj) { - var temp_var = parentObj.registersHL >> 8; - var dirtySum = parentObj.registerA - temp_var - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (temp_var & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, L - //#0x9D: - function (parentObj) { - var dirtySum = parentObj.registerA - (parentObj.registersHL & 0xFF) - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (parentObj.registersHL & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, (HL) - //#0x9E: - function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - var dirtySum = parentObj.registerA - temp_var - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (temp_var & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //SBC A, A - //#0x9F: - function (parentObj) { - //Optimized SBC A: - if (parentObj.FCarry) { - parentObj.FZero = false; - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = true; - parentObj.registerA = 0xFF; - } - else { - parentObj.FHalfCarry = parentObj.FCarry = false; - parentObj.FSubtract = parentObj.FZero = true; - parentObj.registerA = 0; - } - }, - //AND B - //#0xA0: - function (parentObj) { - parentObj.registerA &= parentObj.registerB; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND C - //#0xA1: - function (parentObj) { - parentObj.registerA &= parentObj.registerC; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND D - //#0xA2: - function (parentObj) { - parentObj.registerA &= parentObj.registerD; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND E - //#0xA3: - function (parentObj) { - parentObj.registerA &= parentObj.registerE; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND H - //#0xA4: - function (parentObj) { - parentObj.registerA &= (parentObj.registersHL >> 8); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND L - //#0xA5: - function (parentObj) { - parentObj.registerA &= parentObj.registersHL; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND (HL) - //#0xA6: - function (parentObj) { - parentObj.registerA &= parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //AND A - //#0xA7: - function (parentObj) { - //number & same number = same number - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //XOR B - //#0xA8: - function (parentObj) { - parentObj.registerA ^= parentObj.registerB; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR C - //#0xA9: - function (parentObj) { - parentObj.registerA ^= parentObj.registerC; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR D - //#0xAA: - function (parentObj) { - parentObj.registerA ^= parentObj.registerD; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR E - //#0xAB: - function (parentObj) { - parentObj.registerA ^= parentObj.registerE; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR H - //#0xAC: - function (parentObj) { - parentObj.registerA ^= (parentObj.registersHL >> 8); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR L - //#0xAD: - function (parentObj) { - parentObj.registerA ^= (parentObj.registersHL & 0xFF); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR (HL) - //#0xAE: - function (parentObj) { - parentObj.registerA ^= parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //XOR A - //#0xAF: - function (parentObj) { - //number ^ same number == 0 - parentObj.registerA = 0; - parentObj.FZero = true; - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //OR B - //#0xB0: - function (parentObj) { - parentObj.registerA |= parentObj.registerB; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR C - //#0xB1: - function (parentObj) { - parentObj.registerA |= parentObj.registerC; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR D - //#0xB2: - function (parentObj) { - parentObj.registerA |= parentObj.registerD; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR E - //#0xB3: - function (parentObj) { - parentObj.registerA |= parentObj.registerE; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR H - //#0xB4: - function (parentObj) { - parentObj.registerA |= (parentObj.registersHL >> 8); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR L - //#0xB5: - function (parentObj) { - parentObj.registerA |= (parentObj.registersHL & 0xFF); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR (HL) - //#0xB6: - function (parentObj) { - parentObj.registerA |= parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //OR A - //#0xB7: - function (parentObj) { - //number | same number == same number - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //CP B - //#0xB8: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerB; - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP C - //#0xB9: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerC; - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP D - //#0xBA: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerD; - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP E - //#0xBB: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.registerE; - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP H - //#0xBC: - function (parentObj) { - var dirtySum = parentObj.registerA - (parentObj.registersHL >> 8); - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP L - //#0xBD: - function (parentObj) { - var dirtySum = parentObj.registerA - (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP (HL) - //#0xBE: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //CP A - //#0xBF: - function (parentObj) { - parentObj.FHalfCarry = parentObj.FCarry = false; - parentObj.FZero = parentObj.FSubtract = true; - }, - //RET !FZ - //#0xC0: - function (parentObj) { - if (!parentObj.FZero) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - parentObj.CPUTicks += 12; - } - }, - //POP BC - //#0xC1: - function (parentObj) { - parentObj.registerC = parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.registerB = parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - }, - //JP !FZ, nn - //#0xC2: - function (parentObj) { - if (!parentObj.FZero) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //JP nn - //#0xC3: - function (parentObj) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - }, - //CALL !FZ, nn - //#0xC4: - function (parentObj) { - if (!parentObj.FZero) { - var temp_pc = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = temp_pc; - parentObj.CPUTicks += 12; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //PUSH BC - //#0xC5: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registerB); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registerC); - }, - //ADD, n - //#0xC6: - function (parentObj) { - var dirtySum = parentObj.registerA + parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.FHalfCarry = ((dirtySum & 0xF) < (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //RST 0 - //#0xC7: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0; - }, - //RET FZ - //#0xC8: - function (parentObj) { - if (parentObj.FZero) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - parentObj.CPUTicks += 12; - } - }, - //RET - //#0xC9: - function (parentObj) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - }, - //JP FZ, nn - //#0xCA: - function (parentObj) { - if (parentObj.FZero) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //Secondary OP Code Set: - //#0xCB: - function (parentObj) { - var opcode = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - //Increment the program counter to the next instruction: - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - //Get how many CPU cycles the current 0xCBXX op code counts for: - parentObj.CPUTicks += parentObj.SecondaryTICKTable[opcode]; - //Execute secondary OP codes for the 0xCB OP code call. - parentObj.CBOPCODE[opcode](parentObj); - }, - //CALL FZ, nn - //#0xCC: - function (parentObj) { - if (parentObj.FZero) { - var temp_pc = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = temp_pc; - parentObj.CPUTicks += 12; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //CALL nn - //#0xCD: - function (parentObj) { - var temp_pc = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = temp_pc; - }, - //ADC A, n - //#0xCE: - function (parentObj) { - var tempValue = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - var dirtySum = parentObj.registerA + tempValue + ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) + (tempValue & 0xF) + ((parentObj.FCarry) ? 1 : 0) > 0xF); - parentObj.FCarry = (dirtySum > 0xFF); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = false; - }, - //RST 0x8 - //#0xCF: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x8; - }, - //RET !FC - //#0xD0: - function (parentObj) { - if (!parentObj.FCarry) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - parentObj.CPUTicks += 12; - } - }, - //POP DE - //#0xD1: - function (parentObj) { - parentObj.registerE = parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.registerD = parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - }, - //JP !FC, nn - //#0xD2: - function (parentObj) { - if (!parentObj.FCarry) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //0xD3 - Illegal - //#0xD3: - function (parentObj) { - cout("Illegal op code 0xD3 called, pausing emulation.", 2); - pause(); - }, - //CALL !FC, nn - //#0xD4: - function (parentObj) { - if (!parentObj.FCarry) { - var temp_pc = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = temp_pc; - parentObj.CPUTicks += 12; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //PUSH DE - //#0xD5: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registerD); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registerE); - }, - //SUB A, n - //#0xD6: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) < (dirtySum & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //RST 0x10 - //#0xD7: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x10; - }, - //RET FC - //#0xD8: - function (parentObj) { - if (parentObj.FCarry) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - parentObj.CPUTicks += 12; - } - }, - //RETI - //#0xD9: - function (parentObj) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - //Immediate for HALT: - parentObj.IRQEnableDelay = (parentObj.IRQEnableDelay == 2 || parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) == 0x76) ? 1 : 2; - }, - //JP FC, nn - //#0xDA: - function (parentObj) { - if (parentObj.FCarry) { - parentObj.programCounter = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.CPUTicks += 4; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //0xDB - Illegal - //#0xDB: - function (parentObj) { - cout("Illegal op code 0xDB called, pausing emulation.", 2); - pause(); - }, - //CALL FC, nn - //#0xDC: - function (parentObj) { - if (parentObj.FCarry) { - var temp_pc = (parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = temp_pc; - parentObj.CPUTicks += 12; - } - else { - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - } - }, - //0xDD - Illegal - //#0xDD: - function (parentObj) { - cout("Illegal op code 0xDD called, pausing emulation.", 2); - pause(); - }, - //SBC A, n - //#0xDE: - function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - var dirtySum = parentObj.registerA - temp_var - ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = ((parentObj.registerA & 0xF) - (temp_var & 0xF) - ((parentObj.FCarry) ? 1 : 0) < 0); - parentObj.FCarry = (dirtySum < 0); - parentObj.registerA = dirtySum & 0xFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = true; - }, - //RST 0x18 - //#0xDF: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x18; - }, - //LDH (n), A - //#0xE0: - function (parentObj) { - parentObj.memoryHighWrite(parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter), parentObj.registerA); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //POP HL - //#0xE1: - function (parentObj) { - parentObj.registersHL = (parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - }, - //LD (0xFF00 + C), A - //#0xE2: - function (parentObj) { - parentObj.memoryHighWriter[parentObj.registerC](parentObj, parentObj.registerC, parentObj.registerA); - }, - //0xE3 - Illegal - //#0xE3: - function (parentObj) { - cout("Illegal op code 0xE3 called, pausing emulation.", 2); - pause(); - }, - //0xE4 - Illegal - //#0xE4: - function (parentObj) { - cout("Illegal op code 0xE4 called, pausing emulation.", 2); - pause(); - }, - //PUSH HL - //#0xE5: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registersHL >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registersHL & 0xFF); - }, - //AND n - //#0xE6: - function (parentObj) { - parentObj.registerA &= parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FHalfCarry = true; - parentObj.FSubtract = parentObj.FCarry = false; - }, - //RST 0x20 - //#0xE7: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x20; - }, - //ADD SP, n - //#0xE8: - function (parentObj) { - var temp_value2 = (parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24; - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - var temp_value = (parentObj.stackPointer + temp_value2) & 0xFFFF; - temp_value2 = parentObj.stackPointer ^ temp_value2 ^ temp_value; - parentObj.stackPointer = temp_value; - parentObj.FCarry = ((temp_value2 & 0x100) == 0x100); - parentObj.FHalfCarry = ((temp_value2 & 0x10) == 0x10); - parentObj.FZero = parentObj.FSubtract = false; - }, - //JP, (HL) - //#0xE9: - function (parentObj) { - parentObj.programCounter = parentObj.registersHL; - }, - //LD n, A - //#0xEA: - function (parentObj) { - parentObj.memoryWrite((parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter), parentObj.registerA); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - }, - //0xEB - Illegal - //#0xEB: - function (parentObj) { - cout("Illegal op code 0xEB called, pausing emulation.", 2); - pause(); - }, - //0xEC - Illegal - //#0xEC: - function (parentObj) { - cout("Illegal op code 0xEC called, pausing emulation.", 2); - pause(); - }, - //0xED - Illegal - //#0xED: - function (parentObj) { - cout("Illegal op code 0xED called, pausing emulation.", 2); - pause(); - }, - //XOR n - //#0xEE: - function (parentObj) { - parentObj.registerA ^= parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FSubtract = parentObj.FHalfCarry = parentObj.FCarry = false; - }, - //RST 0x28 - //#0xEF: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x28; - }, - //LDH A, (n) - //#0xF0: - function (parentObj) { - parentObj.registerA = parentObj.memoryHighRead(parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter)); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - }, - //POP AF - //#0xF1: - function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.stackPointer](parentObj, parentObj.stackPointer); - parentObj.FZero = (temp_var > 0x7F); - parentObj.FSubtract = ((temp_var & 0x40) == 0x40); - parentObj.FHalfCarry = ((temp_var & 0x20) == 0x20); - parentObj.FCarry = ((temp_var & 0x10) == 0x10); - parentObj.registerA = parentObj.memoryRead((parentObj.stackPointer + 1) & 0xFFFF); - parentObj.stackPointer = (parentObj.stackPointer + 2) & 0xFFFF; - }, - //LD A, (0xFF00 + C) - //#0xF2: - function (parentObj) { - parentObj.registerA = parentObj.memoryHighReader[parentObj.registerC](parentObj, parentObj.registerC); - }, - //DI - //#0xF3: - function (parentObj) { - parentObj.IME = false; - parentObj.IRQEnableDelay = 0; - }, - //0xF4 - Illegal - //#0xF4: - function (parentObj) { - cout("Illegal op code 0xF4 called, pausing emulation.", 2); - pause(); - }, - //PUSH AF - //#0xF5: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.registerA); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, ((parentObj.FZero) ? 0x80 : 0) | ((parentObj.FSubtract) ? 0x40 : 0) | ((parentObj.FHalfCarry) ? 0x20 : 0) | ((parentObj.FCarry) ? 0x10 : 0)); - }, - //OR n - //#0xF6: - function (parentObj) { - parentObj.registerA |= parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.FSubtract = parentObj.FCarry = parentObj.FHalfCarry = false; - }, - //RST 0x30 - //#0xF7: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x30; - }, - //LDHL SP, n - //#0xF8: - function (parentObj) { - var temp_var = (parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) << 24) >> 24; - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.registersHL = (parentObj.stackPointer + temp_var) & 0xFFFF; - temp_var = parentObj.stackPointer ^ temp_var ^ parentObj.registersHL; - parentObj.FCarry = ((temp_var & 0x100) == 0x100); - parentObj.FHalfCarry = ((temp_var & 0x10) == 0x10); - parentObj.FZero = parentObj.FSubtract = false; - }, - //LD SP, HL - //#0xF9: - function (parentObj) { - parentObj.stackPointer = parentObj.registersHL; - }, - //LD A, (nn) - //#0xFA: - function (parentObj) { - parentObj.registerA = parentObj.memoryRead((parentObj.memoryRead((parentObj.programCounter + 1) & 0xFFFF) << 8) | parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter)); - parentObj.programCounter = (parentObj.programCounter + 2) & 0xFFFF; - }, - //EI - //#0xFB: - function (parentObj) { - //Immediate for HALT: - parentObj.IRQEnableDelay = (parentObj.IRQEnableDelay == 2 || parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter) == 0x76) ? 1 : 2; - }, - //0xFC - Illegal - //#0xFC: - function (parentObj) { - cout("Illegal op code 0xFC called, pausing emulation.", 2); - pause(); - }, - //0xFD - Illegal - //#0xFD: - function (parentObj) { - cout("Illegal op code 0xFD called, pausing emulation.", 2); - pause(); - }, - //CP n - //#0xFE: - function (parentObj) { - var dirtySum = parentObj.registerA - parentObj.memoryReader[parentObj.programCounter](parentObj, parentObj.programCounter); - parentObj.programCounter = (parentObj.programCounter + 1) & 0xFFFF; - parentObj.FHalfCarry = ((dirtySum & 0xF) > (parentObj.registerA & 0xF)); - parentObj.FCarry = (dirtySum < 0); - parentObj.FZero = (dirtySum == 0); - parentObj.FSubtract = true; - }, - //RST 0x38 - //#0xFF: - function (parentObj) { - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter >> 8); - parentObj.stackPointer = (parentObj.stackPointer - 1) & 0xFFFF; - parentObj.memoryWriter[parentObj.stackPointer](parentObj, parentObj.stackPointer, parentObj.programCounter & 0xFF); - parentObj.programCounter = 0x38; - } -]; -GameBoyCore.prototype.CBOPCODE = [ - //RLC B - //#0x00: - function (parentObj) { - parentObj.FCarry = (parentObj.registerB > 0x7F); - parentObj.registerB = ((parentObj.registerB << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //RLC C - //#0x01: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerC > 0x7F); - parentObj.registerC = ((parentObj.registerC << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //RLC D - //#0x02: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerD > 0x7F); - parentObj.registerD = ((parentObj.registerD << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //RLC E - //#0x03: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerE > 0x7F); - parentObj.registerE = ((parentObj.registerE << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //RLC H - //#0x04: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registersHL > 0x7FFF); - parentObj.registersHL = ((parentObj.registersHL << 1) & 0xFE00) | ((parentObj.FCarry) ? 0x100 : 0) | (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //RLC L - //#0x05: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x80) == 0x80); - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.registersHL << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //RLC (HL) - //#0x06: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FCarry = (temp_var > 0x7F); - temp_var = ((temp_var << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var == 0); - } - //RLC A - //#0x07: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerA > 0x7F); - parentObj.registerA = ((parentObj.registerA << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //RRC B - //#0x08: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerB & 0x01) == 0x01); - parentObj.registerB = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerB >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //RRC C - //#0x09: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerC & 0x01) == 0x01); - parentObj.registerC = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerC >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //RRC D - //#0x0A: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerD & 0x01) == 0x01); - parentObj.registerD = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerD >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //RRC E - //#0x0B: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerE & 0x01) == 0x01); - parentObj.registerE = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerE >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //RRC H - //#0x0C: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x0100) == 0x0100); - parentObj.registersHL = ((parentObj.FCarry) ? 0x8000 : 0) | ((parentObj.registersHL >> 1) & 0xFF00) | (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //RRC L - //#0x0D: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x01) == 0x01); - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.FCarry) ? 0x80 : 0) | ((parentObj.registersHL & 0xFF) >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //RRC (HL) - //#0x0E: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FCarry = ((temp_var & 0x01) == 0x01); - temp_var = ((parentObj.FCarry) ? 0x80 : 0) | (temp_var >> 1); - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var == 0); - } - //RRC A - //#0x0F: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerA & 0x01) == 0x01); - parentObj.registerA = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerA >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //RL B - //#0x10: - ,function (parentObj) { - var newFCarry = (parentObj.registerB > 0x7F); - parentObj.registerB = ((parentObj.registerB << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //RL C - //#0x11: - ,function (parentObj) { - var newFCarry = (parentObj.registerC > 0x7F); - parentObj.registerC = ((parentObj.registerC << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //RL D - //#0x12: - ,function (parentObj) { - var newFCarry = (parentObj.registerD > 0x7F); - parentObj.registerD = ((parentObj.registerD << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //RL E - //#0x13: - ,function (parentObj) { - var newFCarry = (parentObj.registerE > 0x7F); - parentObj.registerE = ((parentObj.registerE << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //RL H - //#0x14: - ,function (parentObj) { - var newFCarry = (parentObj.registersHL > 0x7FFF); - parentObj.registersHL = ((parentObj.registersHL << 1) & 0xFE00) | ((parentObj.FCarry) ? 0x100 : 0) | (parentObj.registersHL & 0xFF); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //RL L - //#0x15: - ,function (parentObj) { - var newFCarry = ((parentObj.registersHL & 0x80) == 0x80); - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.registersHL << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //RL (HL) - //#0x16: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - var newFCarry = (temp_var > 0x7F); - temp_var = ((temp_var << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var == 0); - } - //RL A - //#0x17: - ,function (parentObj) { - var newFCarry = (parentObj.registerA > 0x7F); - parentObj.registerA = ((parentObj.registerA << 1) & 0xFF) | ((parentObj.FCarry) ? 1 : 0); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //RR B - //#0x18: - ,function (parentObj) { - var newFCarry = ((parentObj.registerB & 0x01) == 0x01); - parentObj.registerB = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerB >> 1); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //RR C - //#0x19: - ,function (parentObj) { - var newFCarry = ((parentObj.registerC & 0x01) == 0x01); - parentObj.registerC = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerC >> 1); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //RR D - //#0x1A: - ,function (parentObj) { - var newFCarry = ((parentObj.registerD & 0x01) == 0x01); - parentObj.registerD = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerD >> 1); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //RR E - //#0x1B: - ,function (parentObj) { - var newFCarry = ((parentObj.registerE & 0x01) == 0x01); - parentObj.registerE = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerE >> 1); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //RR H - //#0x1C: - ,function (parentObj) { - var newFCarry = ((parentObj.registersHL & 0x0100) == 0x0100); - parentObj.registersHL = ((parentObj.FCarry) ? 0x8000 : 0) | ((parentObj.registersHL >> 1) & 0xFF00) | (parentObj.registersHL & 0xFF); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //RR L - //#0x1D: - ,function (parentObj) { - var newFCarry = ((parentObj.registersHL & 0x01) == 0x01); - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.FCarry) ? 0x80 : 0) | ((parentObj.registersHL & 0xFF) >> 1); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //RR (HL) - //#0x1E: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - var newFCarry = ((temp_var & 0x01) == 0x01); - temp_var = ((parentObj.FCarry) ? 0x80 : 0) | (temp_var >> 1); - parentObj.FCarry = newFCarry; - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var == 0); - } - //RR A - //#0x1F: - ,function (parentObj) { - var newFCarry = ((parentObj.registerA & 0x01) == 0x01); - parentObj.registerA = ((parentObj.FCarry) ? 0x80 : 0) | (parentObj.registerA >> 1); - parentObj.FCarry = newFCarry; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //SLA B - //#0x20: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerB > 0x7F); - parentObj.registerB = (parentObj.registerB << 1) & 0xFF; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //SLA C - //#0x21: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerC > 0x7F); - parentObj.registerC = (parentObj.registerC << 1) & 0xFF; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //SLA D - //#0x22: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerD > 0x7F); - parentObj.registerD = (parentObj.registerD << 1) & 0xFF; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //SLA E - //#0x23: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerE > 0x7F); - parentObj.registerE = (parentObj.registerE << 1) & 0xFF; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //SLA H - //#0x24: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registersHL > 0x7FFF); - parentObj.registersHL = ((parentObj.registersHL << 1) & 0xFE00) | (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //SLA L - //#0x25: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x0080) == 0x0080); - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.registersHL << 1) & 0xFF); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //SLA (HL) - //#0x26: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FCarry = (temp_var > 0x7F); - temp_var = (temp_var << 1) & 0xFF; - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var == 0); - } - //SLA A - //#0x27: - ,function (parentObj) { - parentObj.FCarry = (parentObj.registerA > 0x7F); - parentObj.registerA = (parentObj.registerA << 1) & 0xFF; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //SRA B - //#0x28: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerB & 0x01) == 0x01); - parentObj.registerB = (parentObj.registerB & 0x80) | (parentObj.registerB >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //SRA C - //#0x29: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerC & 0x01) == 0x01); - parentObj.registerC = (parentObj.registerC & 0x80) | (parentObj.registerC >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //SRA D - //#0x2A: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerD & 0x01) == 0x01); - parentObj.registerD = (parentObj.registerD & 0x80) | (parentObj.registerD >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //SRA E - //#0x2B: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerE & 0x01) == 0x01); - parentObj.registerE = (parentObj.registerE & 0x80) | (parentObj.registerE >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //SRA H - //#0x2C: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x0100) == 0x0100); - parentObj.registersHL = ((parentObj.registersHL >> 1) & 0xFF00) | (parentObj.registersHL & 0x80FF); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //SRA L - //#0x2D: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x0001) == 0x0001); - parentObj.registersHL = (parentObj.registersHL & 0xFF80) | ((parentObj.registersHL & 0xFF) >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //SRA (HL) - //#0x2E: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FCarry = ((temp_var & 0x01) == 0x01); - temp_var = (temp_var & 0x80) | (temp_var >> 1); - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var == 0); - } - //SRA A - //#0x2F: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerA & 0x01) == 0x01); - parentObj.registerA = (parentObj.registerA & 0x80) | (parentObj.registerA >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //SWAP B - //#0x30: - ,function (parentObj) { - parentObj.registerB = ((parentObj.registerB & 0xF) << 4) | (parentObj.registerB >> 4); - parentObj.FZero = (parentObj.registerB == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP C - //#0x31: - ,function (parentObj) { - parentObj.registerC = ((parentObj.registerC & 0xF) << 4) | (parentObj.registerC >> 4); - parentObj.FZero = (parentObj.registerC == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP D - //#0x32: - ,function (parentObj) { - parentObj.registerD = ((parentObj.registerD & 0xF) << 4) | (parentObj.registerD >> 4); - parentObj.FZero = (parentObj.registerD == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP E - //#0x33: - ,function (parentObj) { - parentObj.registerE = ((parentObj.registerE & 0xF) << 4) | (parentObj.registerE >> 4); - parentObj.FZero = (parentObj.registerE == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP H - //#0x34: - ,function (parentObj) { - parentObj.registersHL = ((parentObj.registersHL & 0xF00) << 4) | ((parentObj.registersHL & 0xF000) >> 4) | (parentObj.registersHL & 0xFF); - parentObj.FZero = (parentObj.registersHL < 0x100); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP L - //#0x35: - ,function (parentObj) { - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.registersHL & 0xF) << 4) | ((parentObj.registersHL & 0xF0) >> 4); - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP (HL) - //#0x36: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - temp_var = ((temp_var & 0xF) << 4) | (temp_var >> 4); - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var); - parentObj.FZero = (temp_var == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SWAP A - //#0x37: - ,function (parentObj) { - parentObj.registerA = ((parentObj.registerA & 0xF) << 4) | (parentObj.registerA >> 4); - parentObj.FZero = (parentObj.registerA == 0); - parentObj.FCarry = parentObj.FHalfCarry = parentObj.FSubtract = false; - } - //SRL B - //#0x38: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerB & 0x01) == 0x01); - parentObj.registerB >>= 1; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerB == 0); - } - //SRL C - //#0x39: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerC & 0x01) == 0x01); - parentObj.registerC >>= 1; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerC == 0); - } - //SRL D - //#0x3A: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerD & 0x01) == 0x01); - parentObj.registerD >>= 1; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerD == 0); - } - //SRL E - //#0x3B: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerE & 0x01) == 0x01); - parentObj.registerE >>= 1; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerE == 0); - } - //SRL H - //#0x3C: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x0100) == 0x0100); - parentObj.registersHL = ((parentObj.registersHL >> 1) & 0xFF00) | (parentObj.registersHL & 0xFF); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registersHL < 0x100); - } - //SRL L - //#0x3D: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registersHL & 0x0001) == 0x0001); - parentObj.registersHL = (parentObj.registersHL & 0xFF00) | ((parentObj.registersHL & 0xFF) >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0xFF) == 0); - } - //SRL (HL) - //#0x3E: - ,function (parentObj) { - var temp_var = parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL); - parentObj.FCarry = ((temp_var & 0x01) == 0x01); - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, temp_var >> 1); - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (temp_var < 2); - } - //SRL A - //#0x3F: - ,function (parentObj) { - parentObj.FCarry = ((parentObj.registerA & 0x01) == 0x01); - parentObj.registerA >>= 1; - parentObj.FHalfCarry = parentObj.FSubtract = false; - parentObj.FZero = (parentObj.registerA == 0); - } - //BIT 0, B - //#0x40: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x01) == 0); - } - //BIT 0, C - //#0x41: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x01) == 0); - } - //BIT 0, D - //#0x42: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x01) == 0); - } - //BIT 0, E - //#0x43: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x01) == 0); - } - //BIT 0, H - //#0x44: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0100) == 0); - } - //BIT 0, L - //#0x45: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0001) == 0); - } - //BIT 0, (HL) - //#0x46: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x01) == 0); - } - //BIT 0, A - //#0x47: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x01) == 0); - } - //BIT 1, B - //#0x48: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x02) == 0); - } - //BIT 1, C - //#0x49: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x02) == 0); - } - //BIT 1, D - //#0x4A: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x02) == 0); - } - //BIT 1, E - //#0x4B: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x02) == 0); - } - //BIT 1, H - //#0x4C: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0200) == 0); - } - //BIT 1, L - //#0x4D: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0002) == 0); - } - //BIT 1, (HL) - //#0x4E: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x02) == 0); - } - //BIT 1, A - //#0x4F: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x02) == 0); - } - //BIT 2, B - //#0x50: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x04) == 0); - } - //BIT 2, C - //#0x51: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x04) == 0); - } - //BIT 2, D - //#0x52: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x04) == 0); - } - //BIT 2, E - //#0x53: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x04) == 0); - } - //BIT 2, H - //#0x54: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0400) == 0); - } - //BIT 2, L - //#0x55: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0004) == 0); - } - //BIT 2, (HL) - //#0x56: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x04) == 0); - } - //BIT 2, A - //#0x57: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x04) == 0); - } - //BIT 3, B - //#0x58: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x08) == 0); - } - //BIT 3, C - //#0x59: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x08) == 0); - } - //BIT 3, D - //#0x5A: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x08) == 0); - } - //BIT 3, E - //#0x5B: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x08) == 0); - } - //BIT 3, H - //#0x5C: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0800) == 0); - } - //BIT 3, L - //#0x5D: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0008) == 0); - } - //BIT 3, (HL) - //#0x5E: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x08) == 0); - } - //BIT 3, A - //#0x5F: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x08) == 0); - } - //BIT 4, B - //#0x60: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x10) == 0); - } - //BIT 4, C - //#0x61: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x10) == 0); - } - //BIT 4, D - //#0x62: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x10) == 0); - } - //BIT 4, E - //#0x63: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x10) == 0); - } - //BIT 4, H - //#0x64: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x1000) == 0); - } - //BIT 4, L - //#0x65: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0010) == 0); - } - //BIT 4, (HL) - //#0x66: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x10) == 0); - } - //BIT 4, A - //#0x67: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x10) == 0); - } - //BIT 5, B - //#0x68: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x20) == 0); - } - //BIT 5, C - //#0x69: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x20) == 0); - } - //BIT 5, D - //#0x6A: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x20) == 0); - } - //BIT 5, E - //#0x6B: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x20) == 0); - } - //BIT 5, H - //#0x6C: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x2000) == 0); - } - //BIT 5, L - //#0x6D: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0020) == 0); - } - //BIT 5, (HL) - //#0x6E: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x20) == 0); - } - //BIT 5, A - //#0x6F: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x20) == 0); - } - //BIT 6, B - //#0x70: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x40) == 0); - } - //BIT 6, C - //#0x71: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x40) == 0); - } - //BIT 6, D - //#0x72: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x40) == 0); - } - //BIT 6, E - //#0x73: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x40) == 0); - } - //BIT 6, H - //#0x74: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x4000) == 0); - } - //BIT 6, L - //#0x75: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0040) == 0); - } - //BIT 6, (HL) - //#0x76: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x40) == 0); - } - //BIT 6, A - //#0x77: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x40) == 0); - } - //BIT 7, B - //#0x78: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerB & 0x80) == 0); - } - //BIT 7, C - //#0x79: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerC & 0x80) == 0); - } - //BIT 7, D - //#0x7A: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerD & 0x80) == 0); - } - //BIT 7, E - //#0x7B: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerE & 0x80) == 0); - } - //BIT 7, H - //#0x7C: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x8000) == 0); - } - //BIT 7, L - //#0x7D: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registersHL & 0x0080) == 0); - } - //BIT 7, (HL) - //#0x7E: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x80) == 0); - } - //BIT 7, A - //#0x7F: - ,function (parentObj) { - parentObj.FHalfCarry = true; - parentObj.FSubtract = false; - parentObj.FZero = ((parentObj.registerA & 0x80) == 0); - } - //RES 0, B - //#0x80: - ,function (parentObj) { - parentObj.registerB &= 0xFE; - } - //RES 0, C - //#0x81: - ,function (parentObj) { - parentObj.registerC &= 0xFE; - } - //RES 0, D - //#0x82: - ,function (parentObj) { - parentObj.registerD &= 0xFE; - } - //RES 0, E - //#0x83: - ,function (parentObj) { - parentObj.registerE &= 0xFE; - } - //RES 0, H - //#0x84: - ,function (parentObj) { - parentObj.registersHL &= 0xFEFF; - } - //RES 0, L - //#0x85: - ,function (parentObj) { - parentObj.registersHL &= 0xFFFE; - } - //RES 0, (HL) - //#0x86: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xFE); - } - //RES 0, A - //#0x87: - ,function (parentObj) { - parentObj.registerA &= 0xFE; - } - //RES 1, B - //#0x88: - ,function (parentObj) { - parentObj.registerB &= 0xFD; - } - //RES 1, C - //#0x89: - ,function (parentObj) { - parentObj.registerC &= 0xFD; - } - //RES 1, D - //#0x8A: - ,function (parentObj) { - parentObj.registerD &= 0xFD; - } - //RES 1, E - //#0x8B: - ,function (parentObj) { - parentObj.registerE &= 0xFD; - } - //RES 1, H - //#0x8C: - ,function (parentObj) { - parentObj.registersHL &= 0xFDFF; - } - //RES 1, L - //#0x8D: - ,function (parentObj) { - parentObj.registersHL &= 0xFFFD; - } - //RES 1, (HL) - //#0x8E: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xFD); - } - //RES 1, A - //#0x8F: - ,function (parentObj) { - parentObj.registerA &= 0xFD; - } - //RES 2, B - //#0x90: - ,function (parentObj) { - parentObj.registerB &= 0xFB; - } - //RES 2, C - //#0x91: - ,function (parentObj) { - parentObj.registerC &= 0xFB; - } - //RES 2, D - //#0x92: - ,function (parentObj) { - parentObj.registerD &= 0xFB; - } - //RES 2, E - //#0x93: - ,function (parentObj) { - parentObj.registerE &= 0xFB; - } - //RES 2, H - //#0x94: - ,function (parentObj) { - parentObj.registersHL &= 0xFBFF; - } - //RES 2, L - //#0x95: - ,function (parentObj) { - parentObj.registersHL &= 0xFFFB; - } - //RES 2, (HL) - //#0x96: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xFB); - } - //RES 2, A - //#0x97: - ,function (parentObj) { - parentObj.registerA &= 0xFB; - } - //RES 3, B - //#0x98: - ,function (parentObj) { - parentObj.registerB &= 0xF7; - } - //RES 3, C - //#0x99: - ,function (parentObj) { - parentObj.registerC &= 0xF7; - } - //RES 3, D - //#0x9A: - ,function (parentObj) { - parentObj.registerD &= 0xF7; - } - //RES 3, E - //#0x9B: - ,function (parentObj) { - parentObj.registerE &= 0xF7; - } - //RES 3, H - //#0x9C: - ,function (parentObj) { - parentObj.registersHL &= 0xF7FF; - } - //RES 3, L - //#0x9D: - ,function (parentObj) { - parentObj.registersHL &= 0xFFF7; - } - //RES 3, (HL) - //#0x9E: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xF7); - } - //RES 3, A - //#0x9F: - ,function (parentObj) { - parentObj.registerA &= 0xF7; - } - //RES 3, B - //#0xA0: - ,function (parentObj) { - parentObj.registerB &= 0xEF; - } - //RES 4, C - //#0xA1: - ,function (parentObj) { - parentObj.registerC &= 0xEF; - } - //RES 4, D - //#0xA2: - ,function (parentObj) { - parentObj.registerD &= 0xEF; - } - //RES 4, E - //#0xA3: - ,function (parentObj) { - parentObj.registerE &= 0xEF; - } - //RES 4, H - //#0xA4: - ,function (parentObj) { - parentObj.registersHL &= 0xEFFF; - } - //RES 4, L - //#0xA5: - ,function (parentObj) { - parentObj.registersHL &= 0xFFEF; - } - //RES 4, (HL) - //#0xA6: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xEF); - } - //RES 4, A - //#0xA7: - ,function (parentObj) { - parentObj.registerA &= 0xEF; - } - //RES 5, B - //#0xA8: - ,function (parentObj) { - parentObj.registerB &= 0xDF; - } - //RES 5, C - //#0xA9: - ,function (parentObj) { - parentObj.registerC &= 0xDF; - } - //RES 5, D - //#0xAA: - ,function (parentObj) { - parentObj.registerD &= 0xDF; - } - //RES 5, E - //#0xAB: - ,function (parentObj) { - parentObj.registerE &= 0xDF; - } - //RES 5, H - //#0xAC: - ,function (parentObj) { - parentObj.registersHL &= 0xDFFF; - } - //RES 5, L - //#0xAD: - ,function (parentObj) { - parentObj.registersHL &= 0xFFDF; - } - //RES 5, (HL) - //#0xAE: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xDF); - } - //RES 5, A - //#0xAF: - ,function (parentObj) { - parentObj.registerA &= 0xDF; - } - //RES 6, B - //#0xB0: - ,function (parentObj) { - parentObj.registerB &= 0xBF; - } - //RES 6, C - //#0xB1: - ,function (parentObj) { - parentObj.registerC &= 0xBF; - } - //RES 6, D - //#0xB2: - ,function (parentObj) { - parentObj.registerD &= 0xBF; - } - //RES 6, E - //#0xB3: - ,function (parentObj) { - parentObj.registerE &= 0xBF; - } - //RES 6, H - //#0xB4: - ,function (parentObj) { - parentObj.registersHL &= 0xBFFF; - } - //RES 6, L - //#0xB5: - ,function (parentObj) { - parentObj.registersHL &= 0xFFBF; - } - //RES 6, (HL) - //#0xB6: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0xBF); - } - //RES 6, A - //#0xB7: - ,function (parentObj) { - parentObj.registerA &= 0xBF; - } - //RES 7, B - //#0xB8: - ,function (parentObj) { - parentObj.registerB &= 0x7F; - } - //RES 7, C - //#0xB9: - ,function (parentObj) { - parentObj.registerC &= 0x7F; - } - //RES 7, D - //#0xBA: - ,function (parentObj) { - parentObj.registerD &= 0x7F; - } - //RES 7, E - //#0xBB: - ,function (parentObj) { - parentObj.registerE &= 0x7F; - } - //RES 7, H - //#0xBC: - ,function (parentObj) { - parentObj.registersHL &= 0x7FFF; - } - //RES 7, L - //#0xBD: - ,function (parentObj) { - parentObj.registersHL &= 0xFF7F; - } - //RES 7, (HL) - //#0xBE: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) & 0x7F); - } - //RES 7, A - //#0xBF: - ,function (parentObj) { - parentObj.registerA &= 0x7F; - } - //SET 0, B - //#0xC0: - ,function (parentObj) { - parentObj.registerB |= 0x01; - } - //SET 0, C - //#0xC1: - ,function (parentObj) { - parentObj.registerC |= 0x01; - } - //SET 0, D - //#0xC2: - ,function (parentObj) { - parentObj.registerD |= 0x01; - } - //SET 0, E - //#0xC3: - ,function (parentObj) { - parentObj.registerE |= 0x01; - } - //SET 0, H - //#0xC4: - ,function (parentObj) { - parentObj.registersHL |= 0x0100; - } - //SET 0, L - //#0xC5: - ,function (parentObj) { - parentObj.registersHL |= 0x01; - } - //SET 0, (HL) - //#0xC6: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x01); - } - //SET 0, A - //#0xC7: - ,function (parentObj) { - parentObj.registerA |= 0x01; - } - //SET 1, B - //#0xC8: - ,function (parentObj) { - parentObj.registerB |= 0x02; - } - //SET 1, C - //#0xC9: - ,function (parentObj) { - parentObj.registerC |= 0x02; - } - //SET 1, D - //#0xCA: - ,function (parentObj) { - parentObj.registerD |= 0x02; - } - //SET 1, E - //#0xCB: - ,function (parentObj) { - parentObj.registerE |= 0x02; - } - //SET 1, H - //#0xCC: - ,function (parentObj) { - parentObj.registersHL |= 0x0200; - } - //SET 1, L - //#0xCD: - ,function (parentObj) { - parentObj.registersHL |= 0x02; - } - //SET 1, (HL) - //#0xCE: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x02); - } - //SET 1, A - //#0xCF: - ,function (parentObj) { - parentObj.registerA |= 0x02; - } - //SET 2, B - //#0xD0: - ,function (parentObj) { - parentObj.registerB |= 0x04; - } - //SET 2, C - //#0xD1: - ,function (parentObj) { - parentObj.registerC |= 0x04; - } - //SET 2, D - //#0xD2: - ,function (parentObj) { - parentObj.registerD |= 0x04; - } - //SET 2, E - //#0xD3: - ,function (parentObj) { - parentObj.registerE |= 0x04; - } - //SET 2, H - //#0xD4: - ,function (parentObj) { - parentObj.registersHL |= 0x0400; - } - //SET 2, L - //#0xD5: - ,function (parentObj) { - parentObj.registersHL |= 0x04; - } - //SET 2, (HL) - //#0xD6: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x04); - } - //SET 2, A - //#0xD7: - ,function (parentObj) { - parentObj.registerA |= 0x04; - } - //SET 3, B - //#0xD8: - ,function (parentObj) { - parentObj.registerB |= 0x08; - } - //SET 3, C - //#0xD9: - ,function (parentObj) { - parentObj.registerC |= 0x08; - } - //SET 3, D - //#0xDA: - ,function (parentObj) { - parentObj.registerD |= 0x08; - } - //SET 3, E - //#0xDB: - ,function (parentObj) { - parentObj.registerE |= 0x08; - } - //SET 3, H - //#0xDC: - ,function (parentObj) { - parentObj.registersHL |= 0x0800; - } - //SET 3, L - //#0xDD: - ,function (parentObj) { - parentObj.registersHL |= 0x08; - } - //SET 3, (HL) - //#0xDE: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x08); - } - //SET 3, A - //#0xDF: - ,function (parentObj) { - parentObj.registerA |= 0x08; - } - //SET 4, B - //#0xE0: - ,function (parentObj) { - parentObj.registerB |= 0x10; - } - //SET 4, C - //#0xE1: - ,function (parentObj) { - parentObj.registerC |= 0x10; - } - //SET 4, D - //#0xE2: - ,function (parentObj) { - parentObj.registerD |= 0x10; - } - //SET 4, E - //#0xE3: - ,function (parentObj) { - parentObj.registerE |= 0x10; - } - //SET 4, H - //#0xE4: - ,function (parentObj) { - parentObj.registersHL |= 0x1000; - } - //SET 4, L - //#0xE5: - ,function (parentObj) { - parentObj.registersHL |= 0x10; - } - //SET 4, (HL) - //#0xE6: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x10); - } - //SET 4, A - //#0xE7: - ,function (parentObj) { - parentObj.registerA |= 0x10; - } - //SET 5, B - //#0xE8: - ,function (parentObj) { - parentObj.registerB |= 0x20; - } - //SET 5, C - //#0xE9: - ,function (parentObj) { - parentObj.registerC |= 0x20; - } - //SET 5, D - //#0xEA: - ,function (parentObj) { - parentObj.registerD |= 0x20; - } - //SET 5, E - //#0xEB: - ,function (parentObj) { - parentObj.registerE |= 0x20; - } - //SET 5, H - //#0xEC: - ,function (parentObj) { - parentObj.registersHL |= 0x2000; - } - //SET 5, L - //#0xED: - ,function (parentObj) { - parentObj.registersHL |= 0x20; - } - //SET 5, (HL) - //#0xEE: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x20); - } - //SET 5, A - //#0xEF: - ,function (parentObj) { - parentObj.registerA |= 0x20; - } - //SET 6, B - //#0xF0: - ,function (parentObj) { - parentObj.registerB |= 0x40; - } - //SET 6, C - //#0xF1: - ,function (parentObj) { - parentObj.registerC |= 0x40; - } - //SET 6, D - //#0xF2: - ,function (parentObj) { - parentObj.registerD |= 0x40; - } - //SET 6, E - //#0xF3: - ,function (parentObj) { - parentObj.registerE |= 0x40; - } - //SET 6, H - //#0xF4: - ,function (parentObj) { - parentObj.registersHL |= 0x4000; - } - //SET 6, L - //#0xF5: - ,function (parentObj) { - parentObj.registersHL |= 0x40; - } - //SET 6, (HL) - //#0xF6: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x40); - } - //SET 6, A - //#0xF7: - ,function (parentObj) { - parentObj.registerA |= 0x40; - } - //SET 7, B - //#0xF8: - ,function (parentObj) { - parentObj.registerB |= 0x80; - } - //SET 7, C - //#0xF9: - ,function (parentObj) { - parentObj.registerC |= 0x80; - } - //SET 7, D - //#0xFA: - ,function (parentObj) { - parentObj.registerD |= 0x80; - } - //SET 7, E - //#0xFB: - ,function (parentObj) { - parentObj.registerE |= 0x80; - } - //SET 7, H - //#0xFC: - ,function (parentObj) { - parentObj.registersHL |= 0x8000; - } - //SET 7, L - //#0xFD: - ,function (parentObj) { - parentObj.registersHL |= 0x80; - } - //SET 7, (HL) - //#0xFE: - ,function (parentObj) { - parentObj.memoryWriter[parentObj.registersHL](parentObj, parentObj.registersHL, parentObj.memoryReader[parentObj.registersHL](parentObj, parentObj.registersHL) | 0x80); - } - //SET 7, A - //#0xFF: - ,function (parentObj) { - parentObj.registerA |= 0x80; - } -]; -GameBoyCore.prototype.TICKTable = [ //Number of machine cycles for each instruction: -/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F*/ - 4, 12, 8, 8, 4, 4, 8, 4, 20, 8, 8, 8, 4, 4, 8, 4, //0 - 4, 12, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 4, 4, 8, 4, //1 - 8, 12, 8, 8, 4, 4, 8, 4, 8, 8, 8, 8, 4, 4, 8, 4, //2 - 8, 12, 8, 8, 12, 12, 12, 4, 8, 8, 8, 8, 4, 4, 8, 4, //3 - - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //4 - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //5 - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //6 - 8, 8, 8, 8, 8, 8, 4, 8, 4, 4, 4, 4, 4, 4, 8, 4, //7 - - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //8 - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //9 - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //A - 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, //B - - 8, 12, 12, 16, 12, 16, 8, 16, 8, 16, 12, 0, 12, 24, 8, 16, //C - 8, 12, 12, 4, 12, 16, 8, 16, 8, 16, 12, 4, 12, 4, 8, 16, //D - 12, 12, 8, 4, 4, 16, 8, 16, 16, 4, 16, 4, 4, 4, 8, 16, //E - 12, 12, 8, 4, 4, 16, 8, 16, 12, 8, 16, 4, 0, 4, 8, 16 //F -]; -GameBoyCore.prototype.SecondaryTICKTable = [ //Number of machine cycles for each 0xCBXX instruction: -/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F*/ - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //0 - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //1 - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //2 - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //3 - - 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, //4 - 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, //5 - 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, //6 - 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, //7 - - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //8 - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //9 - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //A - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //B - - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //C - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //D - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, //E - 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8 //F -]; -GameBoyCore.prototype.saveSRAMState = function () { - if (!this.cBATT || this.MBCRam.length == 0) { - //No battery backup... - return []; - } - else { - //Return the MBC RAM for backup... - return this.fromTypedArray(this.MBCRam); - } -} -GameBoyCore.prototype.saveRTCState = function () { - if (!this.cTIMER) { - //No battery backup... - return []; - } - else { - //Return the MBC RAM for backup... - return [ - this.lastIteration, - this.RTCisLatched, - this.latchedSeconds, - this.latchedMinutes, - this.latchedHours, - this.latchedLDays, - this.latchedHDays, - this.RTCSeconds, - this.RTCMinutes, - this.RTCHours, - this.RTCDays, - this.RTCDayOverFlow, - this.RTCHALT - ]; - } -} -GameBoyCore.prototype.saveState = function () { - return [ - this.fromTypedArray(this.ROM), - this.inBootstrap, - this.registerA, - this.FZero, - this.FSubtract, - this.FHalfCarry, - this.FCarry, - this.registerB, - this.registerC, - this.registerD, - this.registerE, - this.registersHL, - this.stackPointer, - this.programCounter, - this.halt, - this.IME, - this.hdmaRunning, - this.CPUTicks, - this.doubleSpeedShifter, - this.fromTypedArray(this.memory), - this.fromTypedArray(this.MBCRam), - this.fromTypedArray(this.VRAM), - this.currVRAMBank, - this.fromTypedArray(this.GBCMemory), - this.MBC1Mode, - this.MBCRAMBanksEnabled, - this.currMBCRAMBank, - this.currMBCRAMBankPosition, - this.cGBC, - this.gbcRamBank, - this.gbcRamBankPosition, - this.ROMBank1offs, - this.currentROMBank, - this.cartridgeType, - this.name, - this.gameCode, - this.modeSTAT, - this.LYCMatchTriggerSTAT, - this.mode2TriggerSTAT, - this.mode1TriggerSTAT, - this.mode0TriggerSTAT, - this.LCDisOn, - this.gfxWindowCHRBankPosition, - this.gfxWindowDisplay, - this.gfxSpriteShow, - this.gfxSpriteNormalHeight, - this.gfxBackgroundCHRBankPosition, - this.gfxBackgroundBankOffset, - this.TIMAEnabled, - this.DIVTicks, - this.LCDTicks, - this.timerTicks, - this.TACClocker, - this.serialTimer, - this.serialShiftTimer, - this.serialShiftTimerAllocated, - this.IRQEnableDelay, - this.lastIteration, - this.cMBC1, - this.cMBC2, - this.cMBC3, - this.cMBC5, - this.cMBC7, - this.cSRAM, - this.cMMMO1, - this.cRUMBLE, - this.cCamera, - this.cTAMA5, - this.cHuC3, - this.cHuC1, - this.drewBlank, - this.fromTypedArray(this.frameBuffer), - this.bgEnabled, - this.BGPriorityEnabled, - this.channel1FrequencyTracker, - this.channel1FrequencyCounter, - this.channel1totalLength, - this.channel1envelopeVolume, - this.channel1envelopeType, - this.channel1envelopeSweeps, - this.channel1envelopeSweepsLast, - this.channel1consecutive, - this.channel1frequency, - this.channel1SweepFault, - this.channel1ShadowFrequency, - this.channel1timeSweep, - this.channel1lastTimeSweep, - this.channel1Swept, - this.channel1frequencySweepDivider, - this.channel1decreaseSweep, - this.channel2FrequencyTracker, - this.channel2FrequencyCounter, - this.channel2totalLength, - this.channel2envelopeVolume, - this.channel2envelopeType, - this.channel2envelopeSweeps, - this.channel2envelopeSweepsLast, - this.channel2consecutive, - this.channel2frequency, - this.channel3canPlay, - this.channel3totalLength, - this.channel3patternType, - this.channel3frequency, - this.channel3consecutive, - this.fromTypedArray(this.channel3PCM), - this.channel4FrequencyPeriod, - this.channel4lastSampleLookup, - this.channel4totalLength, - this.channel4envelopeVolume, - this.channel4currentVolume, - this.channel4envelopeType, - this.channel4envelopeSweeps, - this.channel4envelopeSweepsLast, - this.channel4consecutive, - this.channel4BitRange, - this.soundMasterEnabled, - this.VinLeftChannelMasterVolume, - this.VinRightChannelMasterVolume, - this.leftChannel1, - this.leftChannel2, - this.leftChannel3, - this.leftChannel4, - this.rightChannel1, - this.rightChannel2, - this.rightChannel3, - this.rightChannel4, - this.channel1currentSampleLeft, - this.channel1currentSampleRight, - this.channel2currentSampleLeft, - this.channel2currentSampleRight, - this.channel3currentSampleLeft, - this.channel3currentSampleRight, - this.channel4currentSampleLeft, - this.channel4currentSampleRight, - this.channel1currentSampleLeftSecondary, - this.channel1currentSampleRightSecondary, - this.channel2currentSampleLeftSecondary, - this.channel2currentSampleRightSecondary, - this.channel3currentSampleLeftSecondary, - this.channel3currentSampleRightSecondary, - this.channel4currentSampleLeftSecondary, - this.channel4currentSampleRightSecondary, - this.channel1currentSampleLeftTrimary, - this.channel1currentSampleRightTrimary, - this.channel2currentSampleLeftTrimary, - this.channel2currentSampleRightTrimary, - this.mixerOutputCache, - this.channel1DutyTracker, - this.channel1CachedDuty, - this.channel2DutyTracker, - this.channel2CachedDuty, - this.channel1Enabled, - this.channel2Enabled, - this.channel3Enabled, - this.channel4Enabled, - this.sequencerClocks, - this.sequencePosition, - this.channel3Counter, - this.channel4Counter, - this.cachedChannel3Sample, - this.cachedChannel4Sample, - this.channel3FrequencyPeriod, - this.channel3lastSampleLookup, - this.actualScanLine, - this.lastUnrenderedLine, - this.queuedScanLines, - this.RTCisLatched, - this.latchedSeconds, - this.latchedMinutes, - this.latchedHours, - this.latchedLDays, - this.latchedHDays, - this.RTCSeconds, - this.RTCMinutes, - this.RTCHours, - this.RTCDays, - this.RTCDayOverFlow, - this.RTCHALT, - this.usedBootROM, - this.skipPCIncrement, - this.STATTracker, - this.gbcRamBankPositionECHO, - this.numRAMBanks, - this.windowY, - this.windowX, - this.fromTypedArray(this.gbcOBJRawPalette), - this.fromTypedArray(this.gbcBGRawPalette), - this.fromTypedArray(this.gbOBJPalette), - this.fromTypedArray(this.gbBGPalette), - this.fromTypedArray(this.gbcOBJPalette), - this.fromTypedArray(this.gbcBGPalette), - this.fromTypedArray(this.gbBGColorizedPalette), - this.fromTypedArray(this.gbOBJColorizedPalette), - this.fromTypedArray(this.cachedBGPaletteConversion), - this.fromTypedArray(this.cachedOBJPaletteConversion), - this.fromTypedArray(this.BGCHRBank1), - this.fromTypedArray(this.BGCHRBank2), - this.haltPostClocks, - this.interruptsRequested, - this.interruptsEnabled, - this.remainingClocks, - this.colorizedGBPalettes, - this.backgroundY, - this.backgroundX, - this.CPUStopped, - this.audioClocksUntilNextEvent, - this.audioClocksUntilNextEventCounter - ]; -} -GameBoyCore.prototype.returnFromState = function (returnedFrom) { - var index = 0; - var state = returnedFrom.slice(0); - this.ROM = this.toTypedArray(state[index++], "uint8"); - this.ROMBankEdge = Math.floor(this.ROM.length / 0x4000); - this.inBootstrap = state[index++]; - this.registerA = state[index++]; - this.FZero = state[index++]; - this.FSubtract = state[index++]; - this.FHalfCarry = state[index++]; - this.FCarry = state[index++]; - this.registerB = state[index++]; - this.registerC = state[index++]; - this.registerD = state[index++]; - this.registerE = state[index++]; - this.registersHL = state[index++]; - this.stackPointer = state[index++]; - this.programCounter = state[index++]; - this.halt = state[index++]; - this.IME = state[index++]; - this.hdmaRunning = state[index++]; - this.CPUTicks = state[index++]; - this.doubleSpeedShifter = state[index++]; - this.memory = this.toTypedArray(state[index++], "uint8"); - this.MBCRam = this.toTypedArray(state[index++], "uint8"); - this.VRAM = this.toTypedArray(state[index++], "uint8"); - this.currVRAMBank = state[index++]; - this.GBCMemory = this.toTypedArray(state[index++], "uint8"); - this.MBC1Mode = state[index++]; - this.MBCRAMBanksEnabled = state[index++]; - this.currMBCRAMBank = state[index++]; - this.currMBCRAMBankPosition = state[index++]; - this.cGBC = state[index++]; - this.gbcRamBank = state[index++]; - this.gbcRamBankPosition = state[index++]; - this.ROMBank1offs = state[index++]; - this.currentROMBank = state[index++]; - this.cartridgeType = state[index++]; - this.name = state[index++]; - this.gameCode = state[index++]; - this.modeSTAT = state[index++]; - this.LYCMatchTriggerSTAT = state[index++]; - this.mode2TriggerSTAT = state[index++]; - this.mode1TriggerSTAT = state[index++]; - this.mode0TriggerSTAT = state[index++]; - this.LCDisOn = state[index++]; - this.gfxWindowCHRBankPosition = state[index++]; - this.gfxWindowDisplay = state[index++]; - this.gfxSpriteShow = state[index++]; - this.gfxSpriteNormalHeight = state[index++]; - this.gfxBackgroundCHRBankPosition = state[index++]; - this.gfxBackgroundBankOffset = state[index++]; - this.TIMAEnabled = state[index++]; - this.DIVTicks = state[index++]; - this.LCDTicks = state[index++]; - this.timerTicks = state[index++]; - this.TACClocker = state[index++]; - this.serialTimer = state[index++]; - this.serialShiftTimer = state[index++]; - this.serialShiftTimerAllocated = state[index++]; - this.IRQEnableDelay = state[index++]; - this.lastIteration = state[index++]; - this.cMBC1 = state[index++]; - this.cMBC2 = state[index++]; - this.cMBC3 = state[index++]; - this.cMBC5 = state[index++]; - this.cMBC7 = state[index++]; - this.cSRAM = state[index++]; - this.cMMMO1 = state[index++]; - this.cRUMBLE = state[index++]; - this.cCamera = state[index++]; - this.cTAMA5 = state[index++]; - this.cHuC3 = state[index++]; - this.cHuC1 = state[index++]; - this.drewBlank = state[index++]; - this.frameBuffer = this.toTypedArray(state[index++], "int32"); - this.bgEnabled = state[index++]; - this.BGPriorityEnabled = state[index++]; - this.channel1FrequencyTracker = state[index++]; - this.channel1FrequencyCounter = state[index++]; - this.channel1totalLength = state[index++]; - this.channel1envelopeVolume = state[index++]; - this.channel1envelopeType = state[index++]; - this.channel1envelopeSweeps = state[index++]; - this.channel1envelopeSweepsLast = state[index++]; - this.channel1consecutive = state[index++]; - this.channel1frequency = state[index++]; - this.channel1SweepFault = state[index++]; - this.channel1ShadowFrequency = state[index++]; - this.channel1timeSweep = state[index++]; - this.channel1lastTimeSweep = state[index++]; - this.channel1Swept = state[index++]; - this.channel1frequencySweepDivider = state[index++]; - this.channel1decreaseSweep = state[index++]; - this.channel2FrequencyTracker = state[index++]; - this.channel2FrequencyCounter = state[index++]; - this.channel2totalLength = state[index++]; - this.channel2envelopeVolume = state[index++]; - this.channel2envelopeType = state[index++]; - this.channel2envelopeSweeps = state[index++]; - this.channel2envelopeSweepsLast = state[index++]; - this.channel2consecutive = state[index++]; - this.channel2frequency = state[index++]; - this.channel3canPlay = state[index++]; - this.channel3totalLength = state[index++]; - this.channel3patternType = state[index++]; - this.channel3frequency = state[index++]; - this.channel3consecutive = state[index++]; - this.channel3PCM = this.toTypedArray(state[index++], "int8"); - this.channel4FrequencyPeriod = state[index++]; - this.channel4lastSampleLookup = state[index++]; - this.channel4totalLength = state[index++]; - this.channel4envelopeVolume = state[index++]; - this.channel4currentVolume = state[index++]; - this.channel4envelopeType = state[index++]; - this.channel4envelopeSweeps = state[index++]; - this.channel4envelopeSweepsLast = state[index++]; - this.channel4consecutive = state[index++]; - this.channel4BitRange = state[index++]; - this.soundMasterEnabled = state[index++]; - this.VinLeftChannelMasterVolume = state[index++]; - this.VinRightChannelMasterVolume = state[index++]; - this.leftChannel1 = state[index++]; - this.leftChannel2 = state[index++]; - this.leftChannel3 = state[index++]; - this.leftChannel4 = state[index++]; - this.rightChannel1 = state[index++]; - this.rightChannel2 = state[index++]; - this.rightChannel3 = state[index++]; - this.rightChannel4 = state[index++]; - this.channel1currentSampleLeft = state[index++]; - this.channel1currentSampleRight = state[index++]; - this.channel2currentSampleLeft = state[index++]; - this.channel2currentSampleRight = state[index++]; - this.channel3currentSampleLeft = state[index++]; - this.channel3currentSampleRight = state[index++]; - this.channel4currentSampleLeft = state[index++]; - this.channel4currentSampleRight = state[index++]; - this.channel1currentSampleLeftSecondary = state[index++]; - this.channel1currentSampleRightSecondary = state[index++]; - this.channel2currentSampleLeftSecondary = state[index++]; - this.channel2currentSampleRightSecondary = state[index++]; - this.channel3currentSampleLeftSecondary = state[index++]; - this.channel3currentSampleRightSecondary = state[index++]; - this.channel4currentSampleLeftSecondary = state[index++]; - this.channel4currentSampleRightSecondary = state[index++]; - this.channel1currentSampleLeftTrimary = state[index++]; - this.channel1currentSampleRightTrimary = state[index++]; - this.channel2currentSampleLeftTrimary = state[index++]; - this.channel2currentSampleRightTrimary = state[index++]; - this.mixerOutputCache = state[index++]; - this.channel1DutyTracker = state[index++]; - this.channel1CachedDuty = state[index++]; - this.channel2DutyTracker = state[index++]; - this.channel2CachedDuty = state[index++]; - this.channel1Enabled = state[index++]; - this.channel2Enabled = state[index++]; - this.channel3Enabled = state[index++]; - this.channel4Enabled = state[index++]; - this.sequencerClocks = state[index++]; - this.sequencePosition = state[index++]; - this.channel3Counter = state[index++]; - this.channel4Counter = state[index++]; - this.cachedChannel3Sample = state[index++]; - this.cachedChannel4Sample = state[index++]; - this.channel3FrequencyPeriod = state[index++]; - this.channel3lastSampleLookup = state[index++]; - this.actualScanLine = state[index++]; - this.lastUnrenderedLine = state[index++]; - this.queuedScanLines = state[index++]; - this.RTCisLatched = state[index++]; - this.latchedSeconds = state[index++]; - this.latchedMinutes = state[index++]; - this.latchedHours = state[index++]; - this.latchedLDays = state[index++]; - this.latchedHDays = state[index++]; - this.RTCSeconds = state[index++]; - this.RTCMinutes = state[index++]; - this.RTCHours = state[index++]; - this.RTCDays = state[index++]; - this.RTCDayOverFlow = state[index++]; - this.RTCHALT = state[index++]; - this.usedBootROM = state[index++]; - this.skipPCIncrement = state[index++]; - this.STATTracker = state[index++]; - this.gbcRamBankPositionECHO = state[index++]; - this.numRAMBanks = state[index++]; - this.windowY = state[index++]; - this.windowX = state[index++]; - this.gbcOBJRawPalette = this.toTypedArray(state[index++], "uint8"); - this.gbcBGRawPalette = this.toTypedArray(state[index++], "uint8"); - this.gbOBJPalette = this.toTypedArray(state[index++], "int32"); - this.gbBGPalette = this.toTypedArray(state[index++], "int32"); - this.gbcOBJPalette = this.toTypedArray(state[index++], "int32"); - this.gbcBGPalette = this.toTypedArray(state[index++], "int32"); - this.gbBGColorizedPalette = this.toTypedArray(state[index++], "int32"); - this.gbOBJColorizedPalette = this.toTypedArray(state[index++], "int32"); - this.cachedBGPaletteConversion = this.toTypedArray(state[index++], "int32"); - this.cachedOBJPaletteConversion = this.toTypedArray(state[index++], "int32"); - this.BGCHRBank1 = this.toTypedArray(state[index++], "uint8"); - this.BGCHRBank2 = this.toTypedArray(state[index++], "uint8"); - this.haltPostClocks = state[index++]; - this.interruptsRequested = state[index++]; - this.interruptsEnabled = state[index++]; - this.checkIRQMatching(); - this.remainingClocks = state[index++]; - this.colorizedGBPalettes = state[index++]; - this.backgroundY = state[index++]; - this.backgroundX = state[index++]; - this.CPUStopped = state[index++]; - this.audioClocksUntilNextEvent = state[index++]; - this.audioClocksUntilNextEventCounter = state[index]; - this.fromSaveState = true; - this.TICKTable = this.toTypedArray(this.TICKTable, "uint8"); - this.SecondaryTICKTable = this.toTypedArray(this.SecondaryTICKTable, "uint8"); - this.initializeReferencesFromSaveState(); - this.memoryReadJumpCompile(); - this.memoryWriteJumpCompile(); - this.initLCD(); - this.initSound(); - this.noiseSampleTable = (this.channel4BitRange == 0x7FFF) ? this.LSFR15Table : this.LSFR7Table; - this.channel4VolumeShifter = (this.channel4BitRange == 0x7FFF) ? 15 : 7; -} -GameBoyCore.prototype.returnFromRTCState = function () { - if (typeof this.openRTC == "function" && this.cTIMER) { - var rtcData = this.openRTC(this.name); - var index = 0; - this.lastIteration = rtcData[index++]; - this.RTCisLatched = rtcData[index++]; - this.latchedSeconds = rtcData[index++]; - this.latchedMinutes = rtcData[index++]; - this.latchedHours = rtcData[index++]; - this.latchedLDays = rtcData[index++]; - this.latchedHDays = rtcData[index++]; - this.RTCSeconds = rtcData[index++]; - this.RTCMinutes = rtcData[index++]; - this.RTCHours = rtcData[index++]; - this.RTCDays = rtcData[index++]; - this.RTCDayOverFlow = rtcData[index++]; - this.RTCHALT = rtcData[index]; - } -} -GameBoyCore.prototype.start = function () { - this.initMemory(); //Write the startup memory. - this.ROMLoad(); //Load the ROM into memory and get cartridge information from it. - this.initLCD(); //Initialize the graphics. - this.initSound(); //Sound object initialization. - this.run(); //Start the emulation. -} -GameBoyCore.prototype.initMemory = function () { - //Initialize the RAM: - this.memory = this.getTypedArray(0x10000, 0, "uint8"); - this.frameBuffer = this.getTypedArray(23040, 0xF8F8F8, "int32"); - this.BGCHRBank1 = this.getTypedArray(0x800, 0, "uint8"); - this.TICKTable = this.toTypedArray(this.TICKTable, "uint8"); - this.SecondaryTICKTable = this.toTypedArray(this.SecondaryTICKTable, "uint8"); - this.channel3PCM = this.getTypedArray(0x20, 0, "int8"); -} -GameBoyCore.prototype.generateCacheArray = function (tileAmount) { - var tileArray = []; - var tileNumber = 0; - while (tileNumber < tileAmount) { - tileArray[tileNumber++] = this.getTypedArray(64, 0, "uint8"); - } - return tileArray; -} -GameBoyCore.prototype.initSkipBootstrap = function () { - //Fill in the boot ROM set register values - //Default values to the GB boot ROM values, then fill in the GBC boot ROM values after ROM loading - var index = 0xFF; - while (index >= 0) { - if (index >= 0x30 && index < 0x40) { - this.memoryWrite(0xFF00 | index, this.ffxxDump[index]); - } - else { - switch (index) { - case 0x00: - case 0x01: - case 0x02: - case 0x05: - case 0x07: - case 0x0F: - case 0xFF: - this.memoryWrite(0xFF00 | index, this.ffxxDump[index]); - break; - default: - this.memory[0xFF00 | index] = this.ffxxDump[index]; - } - } - --index; - } - if (this.cGBC) { - this.memory[0xFF6C] = 0xFE; - this.memory[0xFF74] = 0xFE; - } - else { - this.memory[0xFF48] = 0xFF; - this.memory[0xFF49] = 0xFF; - this.memory[0xFF6C] = 0xFF; - this.memory[0xFF74] = 0xFF; - } - //Start as an unset device: - cout("Starting without the GBC boot ROM.", 0); - this.registerA = (this.cGBC) ? 0x11 : 0x1; - this.registerB = 0; - this.registerC = 0x13; - this.registerD = 0; - this.registerE = 0xD8; - this.FZero = true; - this.FSubtract = false; - this.FHalfCarry = true; - this.FCarry = true; - this.registersHL = 0x014D; - this.LCDCONTROL = this.LINECONTROL; - this.IME = false; - this.IRQLineMatched = 0; - this.interruptsRequested = 225; - this.interruptsEnabled = 0; - this.hdmaRunning = false; - this.CPUTicks = 12; - this.STATTracker = 0; - this.modeSTAT = 1; - this.spriteCount = 252; - this.LYCMatchTriggerSTAT = false; - this.mode2TriggerSTAT = false; - this.mode1TriggerSTAT = false; - this.mode0TriggerSTAT = false; - this.LCDisOn = true; - this.channel1FrequencyTracker = 0x2000; - this.channel1DutyTracker = 0; - this.channel1CachedDuty = this.dutyLookup[2]; - this.channel1totalLength = 0; - this.channel1envelopeVolume = 0; - this.channel1envelopeType = false; - this.channel1envelopeSweeps = 0; - this.channel1envelopeSweepsLast = 0; - this.channel1consecutive = true; - this.channel1frequency = 1985; - this.channel1SweepFault = true; - this.channel1ShadowFrequency = 1985; - this.channel1timeSweep = 1; - this.channel1lastTimeSweep = 0; - this.channel1Swept = false; - this.channel1frequencySweepDivider = 0; - this.channel1decreaseSweep = false; - this.channel2FrequencyTracker = 0x2000; - this.channel2DutyTracker = 0; - this.channel2CachedDuty = this.dutyLookup[2]; - this.channel2totalLength = 0; - this.channel2envelopeVolume = 0; - this.channel2envelopeType = false; - this.channel2envelopeSweeps = 0; - this.channel2envelopeSweepsLast = 0; - this.channel2consecutive = true; - this.channel2frequency = 0; - this.channel3canPlay = false; - this.channel3totalLength = 0; - this.channel3patternType = 4; - this.channel3frequency = 0; - this.channel3consecutive = true; - this.channel3Counter = 0x418; - this.channel4FrequencyPeriod = 8; - this.channel4totalLength = 0; - this.channel4envelopeVolume = 0; - this.channel4currentVolume = 0; - this.channel4envelopeType = false; - this.channel4envelopeSweeps = 0; - this.channel4envelopeSweepsLast = 0; - this.channel4consecutive = true; - this.channel4BitRange = 0x7FFF; - this.channel4VolumeShifter = 15; - this.channel1FrequencyCounter = 0x200; - this.channel2FrequencyCounter = 0x200; - this.channel3Counter = 0x800; - this.channel3FrequencyPeriod = 0x800; - this.channel3lastSampleLookup = 0; - this.channel4lastSampleLookup = 0; - this.VinLeftChannelMasterVolume = 8; - this.VinRightChannelMasterVolume = 8; - this.soundMasterEnabled = true; - this.leftChannel1 = true; - this.leftChannel2 = true; - this.leftChannel3 = true; - this.leftChannel4 = true; - this.rightChannel1 = true; - this.rightChannel2 = true; - this.rightChannel3 = false; - this.rightChannel4 = false; - this.DIVTicks = 27044; - this.LCDTicks = 160; - this.timerTicks = 0; - this.TIMAEnabled = false; - this.TACClocker = 1024; - this.serialTimer = 0; - this.serialShiftTimer = 0; - this.serialShiftTimerAllocated = 0; - this.IRQEnableDelay = 0; - this.actualScanLine = 144; - this.lastUnrenderedLine = 0; - this.gfxWindowDisplay = false; - this.gfxSpriteShow = false; - this.gfxSpriteNormalHeight = true; - this.bgEnabled = true; - this.BGPriorityEnabled = true; - this.gfxWindowCHRBankPosition = 0; - this.gfxBackgroundCHRBankPosition = 0; - this.gfxBackgroundBankOffset = 0; - this.windowY = 0; - this.windowX = 0; - this.drewBlank = 0; - this.midScanlineOffset = -1; - this.currentX = 0; -} -GameBoyCore.prototype.initBootstrap = function () { - //Start as an unset device: - cout("Starting the selected boot ROM.", 0); - this.programCounter = 0; - this.stackPointer = 0; - this.IME = false; - this.LCDTicks = 0; - this.DIVTicks = 0; - this.registerA = 0; - this.registerB = 0; - this.registerC = 0; - this.registerD = 0; - this.registerE = 0; - this.FZero = this.FSubtract = this.FHalfCarry = this.FCarry = false; - this.registersHL = 0; - this.leftChannel1 = false; - this.leftChannel2 = false; - this.leftChannel3 = false; - this.leftChannel4 = false; - this.rightChannel1 = false; - this.rightChannel2 = false; - this.rightChannel3 = false; - this.rightChannel4 = false; - this.channel2frequency = this.channel1frequency = 0; - this.channel4consecutive = this.channel2consecutive = this.channel1consecutive = false; - this.VinLeftChannelMasterVolume = 8; - this.VinRightChannelMasterVolume = 8; - this.memory[0xFF00] = 0xF; //Set the joypad state. -} -GameBoyCore.prototype.ROMLoad = function () { - //Load the first two ROM banks (0x0000 - 0x7FFF) into regular gameboy memory: - this.ROM = []; - this.usedBootROM = settings[1] && ((!settings[11] && this.GBCBOOTROM.length == 0x800) || (settings[11] && this.GBBOOTROM.length == 0x100)); - var maxLength = this.ROMImage.length; - if (maxLength < 0x4000) { - throw(new Error("ROM image size too small.")); - } - this.ROM = this.getTypedArray(maxLength, 0, "uint8"); - var romIndex = 0; - if (this.usedBootROM) { - if (!settings[11]) { - //Patch in the GBC boot ROM into the memory map: - for (; romIndex < 0x100; ++romIndex) { - this.memory[romIndex] = this.GBCBOOTROM[romIndex]; //Load in the GameBoy Color BOOT ROM. - this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); //Decode the ROM binary for the switch out. - } - for (; romIndex < 0x200; ++romIndex) { - this.memory[romIndex] = this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); //Load in the game ROM. - } - for (; romIndex < 0x900; ++romIndex) { - this.memory[romIndex] = this.GBCBOOTROM[romIndex - 0x100]; //Load in the GameBoy Color BOOT ROM. - this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); //Decode the ROM binary for the switch out. - } - this.usedGBCBootROM = true; - } - else { - //Patch in the GBC boot ROM into the memory map: - for (; romIndex < 0x100; ++romIndex) { - this.memory[romIndex] = this.GBBOOTROM[romIndex]; //Load in the GameBoy Color BOOT ROM. - this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); //Decode the ROM binary for the switch out. - } - } - for (; romIndex < 0x4000; ++romIndex) { - this.memory[romIndex] = this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); //Load in the game ROM. - } - } - else { - //Don't load in the boot ROM: - for (; romIndex < 0x4000; ++romIndex) { - this.memory[romIndex] = this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); //Load in the game ROM. - } - } - //Finish the decoding of the ROM binary: - for (; romIndex < maxLength; ++romIndex) { - this.ROM[romIndex] = (this.ROMImage.charCodeAt(romIndex) & 0xFF); - } - this.ROMBankEdge = Math.floor(this.ROM.length / 0x4000); - //Set up the emulator for the cartidge specifics: - this.interpretCartridge(); - //Check for IRQ matching upon initialization: - this.checkIRQMatching(); -} -GameBoyCore.prototype.getROMImage = function () { - //Return the binary version of the ROM image currently running: - if (this.ROMImage.length > 0) { - return this.ROMImage.length; - } - var length = this.ROM.length; - for (var index = 0; index < length; index++) { - this.ROMImage += String.fromCharCode(this.ROM[index]); - } - return this.ROMImage; -} -GameBoyCore.prototype.interpretCartridge = function () { - // ROM name - for (var index = 0x134; index < 0x13F; index++) { - if (this.ROMImage.charCodeAt(index) > 0) { - this.name += this.ROMImage[index]; - } - } - // ROM game code (for newer games) - for (var index = 0x13F; index < 0x143; index++) { - if (this.ROMImage.charCodeAt(index) > 0) { - this.gameCode += this.ROMImage[index]; - } - } - cout("Game Title: " + this.name + "[" + this.gameCode + "][" + this.ROMImage[0x143] + "]", 0); - cout("Game Code: " + this.gameCode, 0); - // Cartridge type - this.cartridgeType = this.ROM[0x147]; - cout("Cartridge type #" + this.cartridgeType, 0); - //Map out ROM cartridge sub-types. - var MBCType = ""; - switch (this.cartridgeType) { - case 0x00: - //ROM w/o bank switching - if (!settings[9]) { - MBCType = "ROM"; - break; - } - case 0x01: - this.cMBC1 = true; - MBCType = "MBC1"; - break; - case 0x02: - this.cMBC1 = true; - this.cSRAM = true; - MBCType = "MBC1 + SRAM"; - break; - case 0x03: - this.cMBC1 = true; - this.cSRAM = true; - this.cBATT = true; - MBCType = "MBC1 + SRAM + BATT"; - break; - case 0x05: - this.cMBC2 = true; - MBCType = "MBC2"; - break; - case 0x06: - this.cMBC2 = true; - this.cBATT = true; - MBCType = "MBC2 + BATT"; - break; - case 0x08: - this.cSRAM = true; - MBCType = "ROM + SRAM"; - break; - case 0x09: - this.cSRAM = true; - this.cBATT = true; - MBCType = "ROM + SRAM + BATT"; - break; - case 0x0B: - this.cMMMO1 = true; - MBCType = "MMMO1"; - break; - case 0x0C: - this.cMMMO1 = true; - this.cSRAM = true; - MBCType = "MMMO1 + SRAM"; - break; - case 0x0D: - this.cMMMO1 = true; - this.cSRAM = true; - this.cBATT = true; - MBCType = "MMMO1 + SRAM + BATT"; - break; - case 0x0F: - this.cMBC3 = true; - this.cTIMER = true; - this.cBATT = true; - MBCType = "MBC3 + TIMER + BATT"; - break; - case 0x10: - this.cMBC3 = true; - this.cTIMER = true; - this.cBATT = true; - this.cSRAM = true; - MBCType = "MBC3 + TIMER + BATT + SRAM"; - break; - case 0x11: - this.cMBC3 = true; - MBCType = "MBC3"; - break; - case 0x12: - this.cMBC3 = true; - this.cSRAM = true; - MBCType = "MBC3 + SRAM"; - break; - case 0x13: - this.cMBC3 = true; - this.cSRAM = true; - this.cBATT = true; - MBCType = "MBC3 + SRAM + BATT"; - break; - case 0x19: - this.cMBC5 = true; - MBCType = "MBC5"; - break; - case 0x1A: - this.cMBC5 = true; - this.cSRAM = true; - MBCType = "MBC5 + SRAM"; - break; - case 0x1B: - this.cMBC5 = true; - this.cSRAM = true; - this.cBATT = true; - MBCType = "MBC5 + SRAM + BATT"; - break; - case 0x1C: - this.cRUMBLE = true; - MBCType = "RUMBLE"; - break; - case 0x1D: - this.cRUMBLE = true; - this.cSRAM = true; - MBCType = "RUMBLE + SRAM"; - break; - case 0x1E: - this.cRUMBLE = true; - this.cSRAM = true; - this.cBATT = true; - MBCType = "RUMBLE + SRAM + BATT"; - break; - case 0x1F: - this.cCamera = true; - MBCType = "GameBoy Camera"; - break; - case 0x22: - this.cMBC7 = true; - this.cSRAM = true; - this.cBATT = true; - MBCType = "MBC7 + SRAM + BATT"; - break; - case 0xFD: - this.cTAMA5 = true; - MBCType = "TAMA5"; - break; - case 0xFE: - this.cHuC3 = true; - MBCType = "HuC3"; - break; - case 0xFF: - this.cHuC1 = true; - MBCType = "HuC1"; - break; - default: - MBCType = "Unknown"; - cout("Cartridge type is unknown.", 2); - pause(); - } - cout("Cartridge Type: " + MBCType + ".", 0); - // ROM and RAM banks - this.numROMBanks = this.ROMBanks[this.ROM[0x148]]; - cout(this.numROMBanks + " ROM banks.", 0); - switch (this.RAMBanks[this.ROM[0x149]]) { - case 0: - cout("No RAM banking requested for allocation or MBC is of type 2.", 0); - break; - case 2: - cout("1 RAM bank requested for allocation.", 0); - break; - case 3: - cout("4 RAM banks requested for allocation.", 0); - break; - case 4: - cout("16 RAM banks requested for allocation.", 0); - break; - default: - cout("RAM bank amount requested is unknown, will use maximum allowed by specified MBC type.", 0); - } - //Check the GB/GBC mode byte: - if (!this.usedBootROM) { - switch (this.ROM[0x143]) { - case 0x00: //Only GB mode - this.cGBC = false; - cout("Only GB mode detected.", 0); - break; - case 0x32: //Exception to the GBC identifying code: - if (!settings[2] && this.name + this.gameCode + this.ROM[0x143] == "Game and Watch 50") { - this.cGBC = true; - cout("Created a boot exception for Game and Watch Gallery 2 (GBC ID byte is wrong on the cartridge).", 1); - } - else { - this.cGBC = false; - } - break; - case 0x80: //Both GB + GBC modes - this.cGBC = !settings[2]; - cout("GB and GBC mode detected.", 0); - break; - case 0xC0: //Only GBC mode - this.cGBC = true; - cout("Only GBC mode detected.", 0); - break; - default: - this.cGBC = false; - cout("Unknown GameBoy game type code #" + this.ROM[0x143] + ", defaulting to GB mode (Old games don't have a type code).", 1); - } - this.inBootstrap = false; - this.setupRAM(); //CPU/(V)RAM initialization. - this.initSkipBootstrap(); - } - else { - this.cGBC = this.usedGBCBootROM; //Allow the GBC boot ROM to run in GBC mode... - this.setupRAM(); //CPU/(V)RAM initialization. - this.initBootstrap(); - } - this.initializeModeSpecificArrays(); - //License Code Lookup: - var cOldLicense = this.ROM[0x14B]; - var cNewLicense = (this.ROM[0x144] & 0xFF00) | (this.ROM[0x145] & 0xFF); - if (cOldLicense != 0x33) { - //Old Style License Header - cout("Old style license code: " + cOldLicense, 0); - } - else { - //New Style License Header - cout("New style license code: " + cNewLicense, 0); - } - this.ROMImage = ""; //Memory consumption reduction. -} -GameBoyCore.prototype.disableBootROM = function () { - //Remove any traces of the boot ROM from ROM memory. - for (var index = 0; index < 0x100; ++index) { - this.memory[index] = this.ROM[index]; //Replace the GameBoy or GameBoy Color boot ROM with the game ROM. - } - if (this.usedGBCBootROM) { - //Remove any traces of the boot ROM from ROM memory. - for (index = 0x200; index < 0x900; ++index) { - this.memory[index] = this.ROM[index]; //Replace the GameBoy Color boot ROM with the game ROM. - } - if (!this.cGBC) { - //Clean up the post-boot (GB mode only) state: - this.GBCtoGBModeAdjust(); - } - else { - this.recompileBootIOWriteHandling(); - } - } - else { - this.recompileBootIOWriteHandling(); - } -} -GameBoyCore.prototype.initializeTiming = function () { - //Emulator Timing: - this.clocksPerSecond = this.emulatorSpeed * 0x400000; - this.baseCPUCyclesPerIteration = this.clocksPerSecond / 1000 * settings[6]; - this.CPUCyclesTotalRoundoff = this.baseCPUCyclesPerIteration % 4; - this.CPUCyclesTotalBase = this.CPUCyclesTotal = (this.baseCPUCyclesPerIteration - this.CPUCyclesTotalRoundoff) | 0; - this.CPUCyclesTotalCurrent = 0; -} -GameBoyCore.prototype.setSpeed = function (speed) { - this.emulatorSpeed = speed; - this.initializeTiming(); - if (this.audioHandle) { - this.initSound(); - } -} -GameBoyCore.prototype.setupRAM = function () { - //Setup the auxilliary/switchable RAM: - if (this.cMBC2) { - this.numRAMBanks = 1 / 16; - } - else if (this.cMBC1 || this.cRUMBLE || this.cMBC3 || this.cHuC3) { - this.numRAMBanks = 4; - } - else if (this.cMBC5) { - this.numRAMBanks = 16; - } - else if (this.cSRAM) { - this.numRAMBanks = 1; - } - if (this.numRAMBanks > 0) { - if (!this.MBCRAMUtilized()) { - //For ROM and unknown MBC cartridges using the external RAM: - this.MBCRAMBanksEnabled = true; - } - //Switched RAM Used - var MBCRam = (typeof this.openMBC == "function") ? this.openMBC(this.name) : []; - if (MBCRam.length > 0) { - //Flash the SRAM into memory: - this.MBCRam = this.toTypedArray(MBCRam, "uint8"); - } - else { - this.MBCRam = this.getTypedArray(this.numRAMBanks * 0x2000, 0, "uint8"); - } - } - cout("Actual bytes of MBC RAM allocated: " + (this.numRAMBanks * 0x2000), 0); - this.returnFromRTCState(); - //Setup the RAM for GBC mode. - if (this.cGBC) { - this.VRAM = this.getTypedArray(0x2000, 0, "uint8"); - this.GBCMemory = this.getTypedArray(0x7000, 0, "uint8"); - } - this.memoryReadJumpCompile(); - this.memoryWriteJumpCompile(); -} -GameBoyCore.prototype.MBCRAMUtilized = function () { - return this.cMBC1 || this.cMBC2 || this.cMBC3 || this.cMBC5 || this.cMBC7 || this.cRUMBLE; -} -GameBoyCore.prototype.recomputeDimension = function () { - initNewCanvas(); - - //Cache some dimension info: - this.onscreenWidth = this.canvas.width; - this.onscreenHeight = this.canvas.height; - if (window && window.mozRequestAnimationFrame || (navigator.userAgent.toLowerCase().indexOf("gecko") != -1 && navigator.userAgent.toLowerCase().indexOf("like gecko") == -1)) { - //Firefox slowness hack: - this.canvas.width = this.onscreenWidth = (!settings[12]) ? 160 : this.canvas.width; - this.canvas.height = this.onscreenHeight = (!settings[12]) ? 144 : this.canvas.height; - } - else { - this.onscreenWidth = this.canvas.width; - this.onscreenHeight = this.canvas.height; - } - this.offscreenWidth = (!settings[12]) ? 160 : this.canvas.width; - this.offscreenHeight = (!settings[12]) ? 144 : this.canvas.height; - this.offscreenRGBCount = this.offscreenWidth * this.offscreenHeight * 4; - - console.log({ - w: this.canvas.width, - h: this.canvas.height - }) - - -} -GameBoyCore.prototype.initLCD = function () { - this.recomputeDimension(); - if (this.offscreenRGBCount != 92160) { - //Only create the resizer handle if we need it: - this.compileResizeFrameBufferFunction(); - } - else { - //Resizer not needed: - this.resizer = null; - } - try { - this.canvasOffscreen = document.createElement("canvas"); - this.canvasOffscreen.width = this.offscreenWidth; - this.canvasOffscreen.height = this.offscreenHeight; - this.drawContextOffscreen = this.canvasOffscreen.getContext("2d"); - this.drawContextOnscreen = this.canvas.getContext("2d"); - - //Get a CanvasPixelArray buffer: - try { - this.canvasBuffer = this.drawContextOffscreen.createImageData(this.offscreenWidth, this.offscreenHeight); - } - catch (error) { - cout("Falling back to the getImageData initialization (Error \"" + error.message + "\").", 1); - this.canvasBuffer = this.drawContextOffscreen.getImageData(0, 0, this.offscreenWidth, this.offscreenHeight); - } - var index = this.offscreenRGBCount; - while (index > 0) { - this.canvasBuffer.data[index -= 4] = 0xF8; - this.canvasBuffer.data[index + 1] = 0xF8; - this.canvasBuffer.data[index + 2] = 0xF8; - this.canvasBuffer.data[index + 3] = 0xFF; - } - this.graphicsBlit(); - if (this.swizzledFrame == null) { - this.swizzledFrame = this.getTypedArray(69120, 0xFF, "uint8"); - } - //Test the draw system and browser vblank latching: - this.drewFrame = true; //Copy the latest graphics to buffer. - this.requestDraw(); - } - catch (error) { - throw(new Error("HTML5 Canvas support required: " + error.message + "file: " + error.fileName + ", line: " + error.lineNumber)); - } -} -GameBoyCore.prototype.graphicsBlit = function () { - if (this.offscreenWidth == this.onscreenWidth && this.offscreenHeight == this.onscreenHeight) { - this.drawContextOnscreen.putImageData(this.canvasBuffer, 0, 0); - } - else { - this.drawContextOffscreen.putImageData(this.canvasBuffer, 0, 0); - this.drawContextOnscreen.drawImage(this.canvasOffscreen, 0, 0, this.onscreenWidth, this.onscreenHeight); - } -} -GameBoyCore.prototype.JoyPadEvent = function (key, down) { - if (down) { - this.JoyPad &= 0xFF ^ (1 << key); - if (!this.cGBC && (!this.usedBootROM || !this.usedGBCBootROM)) { - this.interruptsRequested |= 0x10; //A real GBC doesn't set this! - this.remainingClocks = 0; - this.checkIRQMatching(); - } - } - else { - this.JoyPad |= (1 << key); - } - this.memory[0xFF00] = (this.memory[0xFF00] & 0x30) + ((((this.memory[0xFF00] & 0x20) == 0) ? (this.JoyPad >> 4) : 0xF) & (((this.memory[0xFF00] & 0x10) == 0) ? (this.JoyPad & 0xF) : 0xF)); - this.CPUStopped = false; -} -GameBoyCore.prototype.GyroEvent = function (x, y) { - x *= -100; - x += 2047; - this.highX = x >> 8; - this.lowX = x & 0xFF; - y *= -100; - y += 2047; - this.highY = y >> 8; - this.lowY = y & 0xFF; -} -GameBoyCore.prototype.initSound = function () { - console.log("INT SOUND") - this.audioResamplerFirstPassFactor = Math.max(Math.min(Math.floor(this.clocksPerSecond / 44100), Math.floor(0xFFFF / 0x1E0)), 1); - this.downSampleInputDivider = 0.5 / (this.audioResamplerFirstPassFactor * 0xF0); // Reduced to 0.5 from 1 to half volume. - if (settings[0]) { - this.audioHandle = new XAudioServer(2, this.clocksPerSecond / this.audioResamplerFirstPassFactor, 0, Math.max(this.baseCPUCyclesPerIteration * settings[8] / this.audioResamplerFirstPassFactor, 8192) << 1, null, settings[3], function () { - settings[0] = false; - }); - this.initAudioBuffer(); - } - else if (this.audioHandle) { - console.log("MUTE") - //Mute the audio output, as it has an immediate silencing effect: - this.audioHandle.changeVolume(0); - } -} -GameBoyCore.prototype.changeVolume = function () { - if (settings[0] && this.audioHandle) { - this.audioHandle.changeVolume(settings[3]); - } -} -GameBoyCore.prototype.initAudioBuffer = function () { - this.audioIndex = 0; - this.audioDestinationPosition = 0; - this.downsampleInput = 0; - this.bufferContainAmount = Math.max(this.baseCPUCyclesPerIteration * settings[7] / this.audioResamplerFirstPassFactor, 4096) << 1; - this.numSamplesTotal = (this.baseCPUCyclesPerIteration / this.audioResamplerFirstPassFactor) << 1; - this.audioBuffer = this.getTypedArray(this.numSamplesTotal, 0, "float32"); -} -GameBoyCore.prototype.intializeWhiteNoise = function () { - //Noise Sample Tables: - var randomFactor = 1; - //15-bit LSFR Cache Generation: - this.LSFR15Table = this.getTypedArray(0x80000, 0, "int8"); - var LSFR = 0x7FFF; //Seed value has all its bits set. - var LSFRShifted = 0x3FFF; - for (var index = 0; index < 0x8000; ++index) { - //Normalize the last LSFR value for usage: - randomFactor = 1 - (LSFR & 1); //Docs say it's the inverse. - //Cache the different volume level results: - this.LSFR15Table[0x08000 | index] = randomFactor; - this.LSFR15Table[0x10000 | index] = randomFactor * 0x2; - this.LSFR15Table[0x18000 | index] = randomFactor * 0x3; - this.LSFR15Table[0x20000 | index] = randomFactor * 0x4; - this.LSFR15Table[0x28000 | index] = randomFactor * 0x5; - this.LSFR15Table[0x30000 | index] = randomFactor * 0x6; - this.LSFR15Table[0x38000 | index] = randomFactor * 0x7; - this.LSFR15Table[0x40000 | index] = randomFactor * 0x8; - this.LSFR15Table[0x48000 | index] = randomFactor * 0x9; - this.LSFR15Table[0x50000 | index] = randomFactor * 0xA; - this.LSFR15Table[0x58000 | index] = randomFactor * 0xB; - this.LSFR15Table[0x60000 | index] = randomFactor * 0xC; - this.LSFR15Table[0x68000 | index] = randomFactor * 0xD; - this.LSFR15Table[0x70000 | index] = randomFactor * 0xE; - this.LSFR15Table[0x78000 | index] = randomFactor * 0xF; - //Recompute the LSFR algorithm: - LSFRShifted = LSFR >> 1; - LSFR = LSFRShifted | (((LSFRShifted ^ LSFR) & 0x1) << 14); - } - //7-bit LSFR Cache Generation: - this.LSFR7Table = this.getTypedArray(0x800, 0, "int8"); - LSFR = 0x7F; //Seed value has all its bits set. - for (index = 0; index < 0x80; ++index) { - //Normalize the last LSFR value for usage: - randomFactor = 1 - (LSFR & 1); //Docs say it's the inverse. - //Cache the different volume level results: - this.LSFR7Table[0x080 | index] = randomFactor; - this.LSFR7Table[0x100 | index] = randomFactor * 0x2; - this.LSFR7Table[0x180 | index] = randomFactor * 0x3; - this.LSFR7Table[0x200 | index] = randomFactor * 0x4; - this.LSFR7Table[0x280 | index] = randomFactor * 0x5; - this.LSFR7Table[0x300 | index] = randomFactor * 0x6; - this.LSFR7Table[0x380 | index] = randomFactor * 0x7; - this.LSFR7Table[0x400 | index] = randomFactor * 0x8; - this.LSFR7Table[0x480 | index] = randomFactor * 0x9; - this.LSFR7Table[0x500 | index] = randomFactor * 0xA; - this.LSFR7Table[0x580 | index] = randomFactor * 0xB; - this.LSFR7Table[0x600 | index] = randomFactor * 0xC; - this.LSFR7Table[0x680 | index] = randomFactor * 0xD; - this.LSFR7Table[0x700 | index] = randomFactor * 0xE; - this.LSFR7Table[0x780 | index] = randomFactor * 0xF; - //Recompute the LSFR algorithm: - LSFRShifted = LSFR >> 1; - LSFR = LSFRShifted | (((LSFRShifted ^ LSFR) & 0x1) << 6); - } - //Set the default noise table: - this.noiseSampleTable = this.LSFR15Table; -} -GameBoyCore.prototype.audioUnderrunAdjustment = function () { - if (settings[0]) { - var underrunAmount = this.audioHandle.remainingBuffer(); - if (typeof underrunAmount == "number") { - underrunAmount = this.bufferContainAmount - Math.max(underrunAmount, 0); - if (underrunAmount > 0) { - this.recalculateIterationClockLimitForAudio((underrunAmount >> 1) * this.audioResamplerFirstPassFactor); - } - } - } -} -GameBoyCore.prototype.initializeAudioStartState = function () { - this.channel1FrequencyTracker = 0x2000; - this.channel1DutyTracker = 0; - this.channel1CachedDuty = this.dutyLookup[2]; - this.channel1totalLength = 0; - this.channel1envelopeVolume = 0; - this.channel1envelopeType = false; - this.channel1envelopeSweeps = 0; - this.channel1envelopeSweepsLast = 0; - this.channel1consecutive = true; - this.channel1frequency = 0; - this.channel1SweepFault = false; - this.channel1ShadowFrequency = 0; - this.channel1timeSweep = 1; - this.channel1lastTimeSweep = 0; - this.channel1Swept = false; - this.channel1frequencySweepDivider = 0; - this.channel1decreaseSweep = false; - this.channel2FrequencyTracker = 0x2000; - this.channel2DutyTracker = 0; - this.channel2CachedDuty = this.dutyLookup[2]; - this.channel2totalLength = 0; - this.channel2envelopeVolume = 0; - this.channel2envelopeType = false; - this.channel2envelopeSweeps = 0; - this.channel2envelopeSweepsLast = 0; - this.channel2consecutive = true; - this.channel2frequency = 0; - this.channel3canPlay = false; - this.channel3totalLength = 0; - this.channel3patternType = 4; - this.channel3frequency = 0; - this.channel3consecutive = true; - this.channel3Counter = 0x800; - this.channel4FrequencyPeriod = 8; - this.channel4totalLength = 0; - this.channel4envelopeVolume = 0; - this.channel4currentVolume = 0; - this.channel4envelopeType = false; - this.channel4envelopeSweeps = 0; - this.channel4envelopeSweepsLast = 0; - this.channel4consecutive = true; - this.channel4BitRange = 0x7FFF; - this.noiseSampleTable = this.LSFR15Table; - this.channel4VolumeShifter = 15; - this.channel1FrequencyCounter = 0x2000; - this.channel2FrequencyCounter = 0x2000; - this.channel3Counter = 0x800; - this.channel3FrequencyPeriod = 0x800; - this.channel3lastSampleLookup = 0; - this.channel4lastSampleLookup = 0; - this.VinLeftChannelMasterVolume = 8; - this.VinRightChannelMasterVolume = 8; - this.mixerOutputCache = 0; - this.sequencerClocks = 0x2000; - this.sequencePosition = 0; - this.channel4FrequencyPeriod = 8; - this.channel4Counter = 8; - this.cachedChannel3Sample = 0; - this.cachedChannel4Sample = 0; - this.channel1Enabled = false; - this.channel2Enabled = false; - this.channel3Enabled = false; - this.channel4Enabled = false; - this.channel1canPlay = false; - this.channel2canPlay = false; - this.channel4canPlay = false; - this.audioClocksUntilNextEvent = 1; - this.audioClocksUntilNextEventCounter = 1; - this.channel1OutputLevelCache(); - this.channel2OutputLevelCache(); - this.channel3OutputLevelCache(); - this.channel4OutputLevelCache(); - this.noiseSampleTable = this.LSFR15Table; -} -GameBoyCore.prototype.outputAudio = function () { - this.audioBuffer[this.audioDestinationPosition++] = (this.downsampleInput >>> 16) * this.downSampleInputDivider; - this.audioBuffer[this.audioDestinationPosition++] = (this.downsampleInput & 0xFFFF) * this.downSampleInputDivider; //had - 1, removing fixes pop - if (this.audioDestinationPosition == this.numSamplesTotal) { - this.audioHandle.writeAudioNoCallback(this.audioBuffer); - this.audioDestinationPosition = 0; - } - this.downsampleInput = 0; -} -//Below are the audio generation functions timed against the CPU: -GameBoyCore.prototype.generateAudio = function (numSamples) { - var multiplier = 0; - if (this.soundMasterEnabled && !this.CPUStopped) { - for (var clockUpTo = 0; numSamples > 0;) { - clockUpTo = Math.min(this.audioClocksUntilNextEventCounter, this.sequencerClocks, numSamples); - this.audioClocksUntilNextEventCounter -= clockUpTo; - this.sequencerClocks -= clockUpTo; - numSamples -= clockUpTo; - while (clockUpTo > 0) { - multiplier = Math.min(clockUpTo, this.audioResamplerFirstPassFactor - this.audioIndex); - clockUpTo -= multiplier; - this.audioIndex += multiplier; - this.downsampleInput += this.mixerOutputCache * multiplier; - if (this.audioIndex == this.audioResamplerFirstPassFactor) { - this.audioIndex = 0; - this.outputAudio(); - } - } - if (this.sequencerClocks == 0) { - this.audioComputeSequencer(); - this.sequencerClocks = 0x2000; - } - if (this.audioClocksUntilNextEventCounter == 0) { - this.computeAudioChannels(); - } - } - } - else { - //SILENT OUTPUT: - while (numSamples > 0) { - multiplier = Math.min(numSamples, this.audioResamplerFirstPassFactor - this.audioIndex); - numSamples -= multiplier; - this.audioIndex += multiplier; - if (this.audioIndex == this.audioResamplerFirstPassFactor) { - this.audioIndex = 0; - this.outputAudio(); - } - } - } -} -//Generate audio, but don't actually output it (Used for when sound is disabled by user/browser): -GameBoyCore.prototype.generateAudioFake = function (numSamples) { - if (this.soundMasterEnabled && !this.CPUStopped) { - for (var clockUpTo = 0; numSamples > 0;) { - clockUpTo = Math.min(this.audioClocksUntilNextEventCounter, this.sequencerClocks, numSamples); - this.audioClocksUntilNextEventCounter -= clockUpTo; - this.sequencerClocks -= clockUpTo; - numSamples -= clockUpTo; - if (this.sequencerClocks == 0) { - this.audioComputeSequencer(); - this.sequencerClocks = 0x2000; - } - if (this.audioClocksUntilNextEventCounter == 0) { - this.computeAudioChannels(); - } - } - } -} -GameBoyCore.prototype.audioJIT = function () { - //Audio Sample Generation Timing: - if (settings[0]) { - this.generateAudio(this.audioTicks); - } - else { - this.generateAudioFake(this.audioTicks); - } - this.audioTicks = 0; -} -GameBoyCore.prototype.audioComputeSequencer = function () { - switch (this.sequencePosition++) { - case 0: - this.clockAudioLength(); - break; - case 2: - this.clockAudioLength(); - this.clockAudioSweep(); - break; - case 4: - this.clockAudioLength(); - break; - case 6: - this.clockAudioLength(); - this.clockAudioSweep(); - break; - case 7: - this.clockAudioEnvelope(); - this.sequencePosition = 0; - } -} -GameBoyCore.prototype.clockAudioLength = function () { - //Channel 1: - if (this.channel1totalLength > 1) { - --this.channel1totalLength; - } - else if (this.channel1totalLength == 1) { - this.channel1totalLength = 0; - this.channel1EnableCheck(); - this.memory[0xFF26] &= 0xFE; //Channel #1 On Flag Off - } - //Channel 2: - if (this.channel2totalLength > 1) { - --this.channel2totalLength; - } - else if (this.channel2totalLength == 1) { - this.channel2totalLength = 0; - this.channel2EnableCheck(); - this.memory[0xFF26] &= 0xFD; //Channel #2 On Flag Off - } - //Channel 3: - if (this.channel3totalLength > 1) { - --this.channel3totalLength; - } - else if (this.channel3totalLength == 1) { - this.channel3totalLength = 0; - this.channel3EnableCheck(); - this.memory[0xFF26] &= 0xFB; //Channel #3 On Flag Off - } - //Channel 4: - if (this.channel4totalLength > 1) { - --this.channel4totalLength; - } - else if (this.channel4totalLength == 1) { - this.channel4totalLength = 0; - this.channel4EnableCheck(); - this.memory[0xFF26] &= 0xF7; //Channel #4 On Flag Off - } -} -GameBoyCore.prototype.clockAudioSweep = function () { - //Channel 1: - if (!this.channel1SweepFault && this.channel1timeSweep > 0) { - if (--this.channel1timeSweep == 0) { - this.runAudioSweep(); - } - } -} -GameBoyCore.prototype.runAudioSweep = function () { - //Channel 1: - if (this.channel1lastTimeSweep > 0) { - if (this.channel1frequencySweepDivider > 0) { - this.channel1Swept = true; - if (this.channel1decreaseSweep) { - this.channel1ShadowFrequency -= this.channel1ShadowFrequency >> this.channel1frequencySweepDivider; - this.channel1frequency = this.channel1ShadowFrequency & 0x7FF; - this.channel1FrequencyTracker = (0x800 - this.channel1frequency) << 2; - } - else { - this.channel1ShadowFrequency += this.channel1ShadowFrequency >> this.channel1frequencySweepDivider; - this.channel1frequency = this.channel1ShadowFrequency; - if (this.channel1ShadowFrequency <= 0x7FF) { - this.channel1FrequencyTracker = (0x800 - this.channel1frequency) << 2; - //Run overflow check twice: - if ((this.channel1ShadowFrequency + (this.channel1ShadowFrequency >> this.channel1frequencySweepDivider)) > 0x7FF) { - this.channel1SweepFault = true; - this.channel1EnableCheck(); - this.memory[0xFF26] &= 0xFE; //Channel #1 On Flag Off - } - } - else { - this.channel1frequency &= 0x7FF; - this.channel1SweepFault = true; - this.channel1EnableCheck(); - this.memory[0xFF26] &= 0xFE; //Channel #1 On Flag Off - } - } - this.channel1timeSweep = this.channel1lastTimeSweep; - } - else { - //Channel has sweep disabled and timer becomes a length counter: - this.channel1SweepFault = true; - this.channel1EnableCheck(); - } - } -} -GameBoyCore.prototype.channel1AudioSweepPerformDummy = function () { - //Channel 1: - if (this.channel1frequencySweepDivider > 0) { - if (!this.channel1decreaseSweep) { - var channel1ShadowFrequency = this.channel1ShadowFrequency + (this.channel1ShadowFrequency >> this.channel1frequencySweepDivider); - if (channel1ShadowFrequency <= 0x7FF) { - //Run overflow check twice: - if ((channel1ShadowFrequency + (channel1ShadowFrequency >> this.channel1frequencySweepDivider)) > 0x7FF) { - this.channel1SweepFault = true; - this.channel1EnableCheck(); - this.memory[0xFF26] &= 0xFE; //Channel #1 On Flag Off - } - } - else { - this.channel1SweepFault = true; - this.channel1EnableCheck(); - this.memory[0xFF26] &= 0xFE; //Channel #1 On Flag Off - } - } - } -} -GameBoyCore.prototype.clockAudioEnvelope = function () { - //Channel 1: - if (this.channel1envelopeSweepsLast > -1) { - if (this.channel1envelopeSweeps > 0) { - --this.channel1envelopeSweeps; - } - else { - if (!this.channel1envelopeType) { - if (this.channel1envelopeVolume > 0) { - --this.channel1envelopeVolume; - this.channel1envelopeSweeps = this.channel1envelopeSweepsLast; - this.channel1OutputLevelCache(); - } - else { - this.channel1envelopeSweepsLast = -1; - } - } - else if (this.channel1envelopeVolume < 0xF) { - ++this.channel1envelopeVolume; - this.channel1envelopeSweeps = this.channel1envelopeSweepsLast; - this.channel1OutputLevelCache(); - } - else { - this.channel1envelopeSweepsLast = -1; - } - } - } - //Channel 2: - if (this.channel2envelopeSweepsLast > -1) { - if (this.channel2envelopeSweeps > 0) { - --this.channel2envelopeSweeps; - } - else { - if (!this.channel2envelopeType) { - if (this.channel2envelopeVolume > 0) { - --this.channel2envelopeVolume; - this.channel2envelopeSweeps = this.channel2envelopeSweepsLast; - this.channel2OutputLevelCache(); - } - else { - this.channel2envelopeSweepsLast = -1; - } - } - else if (this.channel2envelopeVolume < 0xF) { - ++this.channel2envelopeVolume; - this.channel2envelopeSweeps = this.channel2envelopeSweepsLast; - this.channel2OutputLevelCache(); - } - else { - this.channel2envelopeSweepsLast = -1; - } - } - } - //Channel 4: - if (this.channel4envelopeSweepsLast > -1) { - if (this.channel4envelopeSweeps > 0) { - --this.channel4envelopeSweeps; - } - else { - if (!this.channel4envelopeType) { - if (this.channel4envelopeVolume > 0) { - this.channel4currentVolume = --this.channel4envelopeVolume << this.channel4VolumeShifter; - this.channel4envelopeSweeps = this.channel4envelopeSweepsLast; - this.channel4UpdateCache(); - } - else { - this.channel4envelopeSweepsLast = -1; - } - } - else if (this.channel4envelopeVolume < 0xF) { - this.channel4currentVolume = ++this.channel4envelopeVolume << this.channel4VolumeShifter; - this.channel4envelopeSweeps = this.channel4envelopeSweepsLast; - this.channel4UpdateCache(); - } - else { - this.channel4envelopeSweepsLast = -1; - } - } - } -} -GameBoyCore.prototype.computeAudioChannels = function () { - //Clock down the four audio channels to the next closest audio event: - this.channel1FrequencyCounter -= this.audioClocksUntilNextEvent; - this.channel2FrequencyCounter -= this.audioClocksUntilNextEvent; - this.channel3Counter -= this.audioClocksUntilNextEvent; - this.channel4Counter -= this.audioClocksUntilNextEvent; - //Channel 1 counter: - if (this.channel1FrequencyCounter == 0) { - this.channel1FrequencyCounter = this.channel1FrequencyTracker; - this.channel1DutyTracker = (this.channel1DutyTracker + 1) & 0x7; - this.channel1OutputLevelTrimaryCache(); - } - //Channel 2 counter: - if (this.channel2FrequencyCounter == 0) { - this.channel2FrequencyCounter = this.channel2FrequencyTracker; - this.channel2DutyTracker = (this.channel2DutyTracker + 1) & 0x7; - this.channel2OutputLevelTrimaryCache(); - } - //Channel 3 counter: - if (this.channel3Counter == 0) { - if (this.channel3canPlay) { - this.channel3lastSampleLookup = (this.channel3lastSampleLookup + 1) & 0x1F; - } - this.channel3Counter = this.channel3FrequencyPeriod; - this.channel3UpdateCache(); - } - //Channel 4 counter: - if (this.channel4Counter == 0) { - this.channel4lastSampleLookup = (this.channel4lastSampleLookup + 1) & this.channel4BitRange; - this.channel4Counter = this.channel4FrequencyPeriod; - this.channel4UpdateCache(); - } - //Find the number of clocks to next closest counter event: - this.audioClocksUntilNextEventCounter = this.audioClocksUntilNextEvent = Math.min(this.channel1FrequencyCounter, this.channel2FrequencyCounter, this.channel3Counter, this.channel4Counter); -} -GameBoyCore.prototype.channel1EnableCheck = function () { - this.channel1Enabled = ((this.channel1consecutive || this.channel1totalLength > 0) && !this.channel1SweepFault && this.channel1canPlay); - this.channel1OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel1VolumeEnableCheck = function () { - this.channel1canPlay = (this.memory[0xFF12] > 7); - this.channel1EnableCheck(); - this.channel1OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel1OutputLevelCache = function () { - this.channel1currentSampleLeft = (this.leftChannel1) ? this.channel1envelopeVolume : 0; - this.channel1currentSampleRight = (this.rightChannel1) ? this.channel1envelopeVolume : 0; - this.channel1OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel1OutputLevelSecondaryCache = function () { - if (this.channel1Enabled) { - this.channel1currentSampleLeftSecondary = this.channel1currentSampleLeft; - this.channel1currentSampleRightSecondary = this.channel1currentSampleRight; - } - else { - this.channel1currentSampleLeftSecondary = 0; - this.channel1currentSampleRightSecondary = 0; - } - this.channel1OutputLevelTrimaryCache(); -} -GameBoyCore.prototype.channel1OutputLevelTrimaryCache = function () { - if (this.channel1CachedDuty[this.channel1DutyTracker] && settings[14][0]) { - this.channel1currentSampleLeftTrimary = this.channel1currentSampleLeftSecondary; - this.channel1currentSampleRightTrimary = this.channel1currentSampleRightSecondary; - } - else { - this.channel1currentSampleLeftTrimary = 0; - this.channel1currentSampleRightTrimary = 0; - } - this.mixerOutputLevelCache(); -} -GameBoyCore.prototype.channel2EnableCheck = function () { - this.channel2Enabled = ((this.channel2consecutive || this.channel2totalLength > 0) && this.channel2canPlay); - this.channel2OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel2VolumeEnableCheck = function () { - this.channel2canPlay = (this.memory[0xFF17] > 7); - this.channel2EnableCheck(); - this.channel2OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel2OutputLevelCache = function () { - this.channel2currentSampleLeft = (this.leftChannel2) ? this.channel2envelopeVolume : 0; - this.channel2currentSampleRight = (this.rightChannel2) ? this.channel2envelopeVolume : 0; - this.channel2OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel2OutputLevelSecondaryCache = function () { - if (this.channel2Enabled) { - this.channel2currentSampleLeftSecondary = this.channel2currentSampleLeft; - this.channel2currentSampleRightSecondary = this.channel2currentSampleRight; - } - else { - this.channel2currentSampleLeftSecondary = 0; - this.channel2currentSampleRightSecondary = 0; - } - this.channel2OutputLevelTrimaryCache(); -} -GameBoyCore.prototype.channel2OutputLevelTrimaryCache = function () { - if (this.channel2CachedDuty[this.channel2DutyTracker] && settings[14][1]) { - this.channel2currentSampleLeftTrimary = this.channel2currentSampleLeftSecondary; - this.channel2currentSampleRightTrimary = this.channel2currentSampleRightSecondary; - } - else { - this.channel2currentSampleLeftTrimary = 0; - this.channel2currentSampleRightTrimary = 0; - } - this.mixerOutputLevelCache(); -} -GameBoyCore.prototype.channel3EnableCheck = function () { - this.channel3Enabled = (/*this.channel3canPlay && */(this.channel3consecutive || this.channel3totalLength > 0)); - this.channel3OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel3OutputLevelCache = function () { - this.channel3currentSampleLeft = (this.leftChannel3) ? this.cachedChannel3Sample : 0; - this.channel3currentSampleRight = (this.rightChannel3) ? this.cachedChannel3Sample : 0; - this.channel3OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel3OutputLevelSecondaryCache = function () { - if (this.channel3Enabled && settings[14][2]) { - this.channel3currentSampleLeftSecondary = this.channel3currentSampleLeft; - this.channel3currentSampleRightSecondary = this.channel3currentSampleRight; - } - else { - this.channel3currentSampleLeftSecondary = 0; - this.channel3currentSampleRightSecondary = 0; - } - this.mixerOutputLevelCache(); -} -GameBoyCore.prototype.channel4EnableCheck = function () { - this.channel4Enabled = ((this.channel4consecutive || this.channel4totalLength > 0) && this.channel4canPlay); - this.channel4OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel4VolumeEnableCheck = function () { - this.channel4canPlay = (this.memory[0xFF21] > 7); - this.channel4EnableCheck(); - this.channel4OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel4OutputLevelCache = function () { - this.channel4currentSampleLeft = (this.leftChannel4) ? this.cachedChannel4Sample : 0; - this.channel4currentSampleRight = (this.rightChannel4) ? this.cachedChannel4Sample : 0; - this.channel4OutputLevelSecondaryCache(); -} -GameBoyCore.prototype.channel4OutputLevelSecondaryCache = function () { - if (this.channel4Enabled && settings[14][3]) { - this.channel4currentSampleLeftSecondary = this.channel4currentSampleLeft; - this.channel4currentSampleRightSecondary = this.channel4currentSampleRight; - } - else { - this.channel4currentSampleLeftSecondary = 0; - this.channel4currentSampleRightSecondary = 0; - } - this.mixerOutputLevelCache(); -} -GameBoyCore.prototype.mixerOutputLevelCache = function () { - this.mixerOutputCache = ((((this.channel1currentSampleLeftTrimary + this.channel2currentSampleLeftTrimary + this.channel3currentSampleLeftSecondary + this.channel4currentSampleLeftSecondary) * this.VinLeftChannelMasterVolume) << 16) | - ((this.channel1currentSampleRightTrimary + this.channel2currentSampleRightTrimary + this.channel3currentSampleRightSecondary + this.channel4currentSampleRightSecondary) * this.VinRightChannelMasterVolume)); -} -GameBoyCore.prototype.channel3UpdateCache = function () { - this.cachedChannel3Sample = this.channel3PCM[this.channel3lastSampleLookup] >> this.channel3patternType; - this.channel3OutputLevelCache(); -} -GameBoyCore.prototype.channel3WriteRAM = function (address, data) { - if (this.channel3canPlay) { - this.audioJIT(); - //address = this.channel3lastSampleLookup >> 1; - } - this.memory[0xFF30 | address] = data; - address <<= 1; - this.channel3PCM[address] = data >> 4; - this.channel3PCM[address | 1] = data & 0xF; -} -GameBoyCore.prototype.channel4UpdateCache = function () { - this.cachedChannel4Sample = this.noiseSampleTable[this.channel4currentVolume | this.channel4lastSampleLookup]; - this.channel4OutputLevelCache(); -} -GameBoyCore.prototype.run = function () { - //The preprocessing before the actual iteration loop: - if ((this.stopEmulator & 2) == 0) { - if ((this.stopEmulator & 1) == 1) { - if (!this.CPUStopped) { - this.stopEmulator = 0; - this.audioUnderrunAdjustment(); - this.clockUpdate(); //RTC clocking. - if (!this.halt) { - this.executeIteration(); - } - else { //Finish the HALT rundown execution. - this.CPUTicks = 0; - this.calculateHALTPeriod(); - if (this.halt) { - this.updateCore(); - this.iterationEndRoutine(); - } - else { - this.executeIteration(); - } - } - //Request the graphics target to be updated: - this.requestDraw(); - } - else { - this.audioUnderrunAdjustment(); - this.audioTicks += this.CPUCyclesTotal; - this.audioJIT(); - this.stopEmulator |= 1; //End current loop. - } - } - else { //We can only get here if there was an internal error, but the loop was restarted. - cout("Iterator restarted a faulted core.", 2); - pause(); - } - } -} -GameBoyCore.prototype.executeIteration = function () { - //Iterate the interpreter loop: - var opcodeToExecute = 0; - var timedTicks = 0; - while (this.stopEmulator == 0) { - //Interrupt Arming: - switch (this.IRQEnableDelay) { - case 1: - this.IME = true; - this.checkIRQMatching(); - case 2: - --this.IRQEnableDelay; - } - //Is an IRQ set to fire?: - if (this.IRQLineMatched > 0) { - //IME is true and and interrupt was matched: - this.launchIRQ(); - } - //Fetch the current opcode: - opcodeToExecute = this.memoryReader[this.programCounter](this, this.programCounter); - //Increment the program counter to the next instruction: - this.programCounter = (this.programCounter + 1) & 0xFFFF; - //Check for the program counter quirk: - if (this.skipPCIncrement) { - this.programCounter = (this.programCounter - 1) & 0xFFFF; - this.skipPCIncrement = false; - } - //Get how many CPU cycles the current instruction counts for: - this.CPUTicks = this.TICKTable[opcodeToExecute]; - //Execute the current instruction: - this.OPCODE[opcodeToExecute](this); - //Update the state (Inlined updateCoreFull manually here): - //Update the clocking for the LCD emulation: - this.LCDTicks += this.CPUTicks >> this.doubleSpeedShifter; //LCD Timing - this.LCDCONTROL[this.actualScanLine](this); //Scan Line and STAT Mode Control - //Single-speed relative timing for A/V emulation: - timedTicks = this.CPUTicks >> this.doubleSpeedShifter; //CPU clocking can be updated from the LCD handling. - this.audioTicks += timedTicks; //Audio Timing - this.emulatorTicks += timedTicks; //Emulator Timing - //CPU Timers: - this.DIVTicks += this.CPUTicks; //DIV Timing - if (this.TIMAEnabled) { //TIMA Timing - this.timerTicks += this.CPUTicks; - while (this.timerTicks >= this.TACClocker) { - this.timerTicks -= this.TACClocker; - if (++this.memory[0xFF05] == 0x100) { - this.memory[0xFF05] = this.memory[0xFF06]; - this.interruptsRequested |= 0x4; - this.checkIRQMatching(); - } - } - } - if (this.serialTimer > 0) { //Serial Timing - //IRQ Counter: - this.serialTimer -= this.CPUTicks; - if (this.serialTimer <= 0) { - this.interruptsRequested |= 0x8; - this.checkIRQMatching(); - } - //Bit Shit Counter: - this.serialShiftTimer -= this.CPUTicks; - if (this.serialShiftTimer <= 0) { - this.serialShiftTimer = this.serialShiftTimerAllocated; - this.memory[0xFF01] = ((this.memory[0xFF01] << 1) & 0xFE) | 0x01; //We could shift in actual link data here if we were to implement such!!! - } - } - //End of iteration routine: - if (this.emulatorTicks >= this.CPUCyclesTotal) { - this.iterationEndRoutine(); - } - } -} -GameBoyCore.prototype.iterationEndRoutine = function () { - if ((this.stopEmulator & 0x1) == 0) { - this.audioJIT(); //Make sure we at least output once per iteration. - //Update DIV Alignment (Integer overflow safety): - this.memory[0xFF04] = (this.memory[0xFF04] + (this.DIVTicks >> 8)) & 0xFF; - this.DIVTicks &= 0xFF; - //Update emulator flags: - this.stopEmulator |= 1; //End current loop. - this.emulatorTicks -= this.CPUCyclesTotal; - this.CPUCyclesTotalCurrent += this.CPUCyclesTotalRoundoff; - this.recalculateIterationClockLimit(); - } -} -GameBoyCore.prototype.handleSTOP = function () { - this.CPUStopped = true; //Stop CPU until joypad input changes. - this.iterationEndRoutine(); - if (this.emulatorTicks < 0) { - this.audioTicks -= this.emulatorTicks; - this.audioJIT(); - } -} -GameBoyCore.prototype.recalculateIterationClockLimit = function () { - var endModulus = this.CPUCyclesTotalCurrent % 4; - this.CPUCyclesTotal = this.CPUCyclesTotalBase + this.CPUCyclesTotalCurrent - endModulus; - this.CPUCyclesTotalCurrent = endModulus; -} -GameBoyCore.prototype.recalculateIterationClockLimitForAudio = function (audioClocking) { - this.CPUCyclesTotal += Math.min((audioClocking >> 2) << 2, this.CPUCyclesTotalBase << 1); -} -GameBoyCore.prototype.scanLineMode2 = function () { //OAM Search Period - if (this.STATTracker != 1) { - if (this.mode2TriggerSTAT) { - this.interruptsRequested |= 0x2; - this.checkIRQMatching(); - } - this.STATTracker = 1; - this.modeSTAT = 2; - } -} -GameBoyCore.prototype.scanLineMode3 = function () { //Scan Line Drawing Period - if (this.modeSTAT != 3) { - if (this.STATTracker == 0 && this.mode2TriggerSTAT) { - this.interruptsRequested |= 0x2; - this.checkIRQMatching(); - } - this.STATTracker = 1; - this.modeSTAT = 3; - } -} -GameBoyCore.prototype.scanLineMode0 = function () { //Horizontal Blanking Period - if (this.modeSTAT != 0) { - if (this.STATTracker != 2) { - if (this.STATTracker == 0) { - if (this.mode2TriggerSTAT) { - this.interruptsRequested |= 0x2; - this.checkIRQMatching(); - } - this.modeSTAT = 3; - } - this.incrementScanLineQueue(); - this.updateSpriteCount(this.actualScanLine); - this.STATTracker = 2; - } - if (this.LCDTicks >= this.spriteCount) { - if (this.hdmaRunning) { - this.executeHDMA(); - } - if (this.mode0TriggerSTAT) { - this.interruptsRequested |= 0x2; - this.checkIRQMatching(); - } - this.STATTracker = 3; - this.modeSTAT = 0; - } - } -} -GameBoyCore.prototype.clocksUntilLYCMatch = function () { - if (this.memory[0xFF45] != 0) { - if (this.memory[0xFF45] > this.actualScanLine) { - return 456 * (this.memory[0xFF45] - this.actualScanLine); - } - return 456 * (154 - this.actualScanLine + this.memory[0xFF45]); - } - return (456 * ((this.actualScanLine == 153 && this.memory[0xFF44] == 0) ? 154 : (153 - this.actualScanLine))) + 8; -} -GameBoyCore.prototype.clocksUntilMode0 = function () { - switch (this.modeSTAT) { - case 0: - if (this.actualScanLine == 143) { - this.updateSpriteCount(0); - return this.spriteCount + 5016; - } - this.updateSpriteCount(this.actualScanLine + 1); - return this.spriteCount + 456; - case 2: - case 3: - this.updateSpriteCount(this.actualScanLine); - return this.spriteCount; - case 1: - this.updateSpriteCount(0); - return this.spriteCount + (456 * (154 - this.actualScanLine)); - } -} -GameBoyCore.prototype.updateSpriteCount = function (line) { - this.spriteCount = 252; - if (this.cGBC && this.gfxSpriteShow) { //Is the window enabled and are we in CGB mode? - var lineAdjusted = line + 0x10; - var yoffset = 0; - var yCap = (this.gfxSpriteNormalHeight) ? 0x8 : 0x10; - for (var OAMAddress = 0xFE00; OAMAddress < 0xFEA0 && this.spriteCount < 312; OAMAddress += 4) { - yoffset = lineAdjusted - this.memory[OAMAddress]; - if (yoffset > -1 && yoffset < yCap) { - this.spriteCount += 6; - } - } - } -} -GameBoyCore.prototype.matchLYC = function () { //LYC Register Compare - if (this.memory[0xFF44] == this.memory[0xFF45]) { - this.memory[0xFF41] |= 0x04; - if (this.LYCMatchTriggerSTAT) { - this.interruptsRequested |= 0x2; - this.checkIRQMatching(); - } - } - else { - this.memory[0xFF41] &= 0x7B; - } -} -GameBoyCore.prototype.updateCore = function () { - //Update the clocking for the LCD emulation: - this.LCDTicks += this.CPUTicks >> this.doubleSpeedShifter; //LCD Timing - this.LCDCONTROL[this.actualScanLine](this); //Scan Line and STAT Mode Control - //Single-speed relative timing for A/V emulation: - var timedTicks = this.CPUTicks >> this.doubleSpeedShifter; //CPU clocking can be updated from the LCD handling. - this.audioTicks += timedTicks; //Audio Timing - this.emulatorTicks += timedTicks; //Emulator Timing - //CPU Timers: - this.DIVTicks += this.CPUTicks; //DIV Timing - if (this.TIMAEnabled) { //TIMA Timing - this.timerTicks += this.CPUTicks; - while (this.timerTicks >= this.TACClocker) { - this.timerTicks -= this.TACClocker; - if (++this.memory[0xFF05] == 0x100) { - this.memory[0xFF05] = this.memory[0xFF06]; - this.interruptsRequested |= 0x4; - this.checkIRQMatching(); - } - } - } - if (this.serialTimer > 0) { //Serial Timing - //IRQ Counter: - this.serialTimer -= this.CPUTicks; - if (this.serialTimer <= 0) { - this.interruptsRequested |= 0x8; - this.checkIRQMatching(); - } - //Bit Shit Counter: - this.serialShiftTimer -= this.CPUTicks; - if (this.serialShiftTimer <= 0) { - this.serialShiftTimer = this.serialShiftTimerAllocated; - this.memory[0xFF01] = ((this.memory[0xFF01] << 1) & 0xFE) | 0x01; //We could shift in actual link data here if we were to implement such!!! - } - } -} -GameBoyCore.prototype.updateCoreFull = function () { - //Update the state machine: - this.updateCore(); - //End of iteration routine: - if (this.emulatorTicks >= this.CPUCyclesTotal) { - this.iterationEndRoutine(); - } -} -GameBoyCore.prototype.initializeLCDController = function () { - //Display on hanlding: - var line = 0; - while (line < 154) { - if (line < 143) { - //We're on a normal scan line: - this.LINECONTROL[line] = function (parentObj) { - if (parentObj.LCDTicks < 80) { - parentObj.scanLineMode2(); - } - else if (parentObj.LCDTicks < 252) { - parentObj.scanLineMode3(); - } - else if (parentObj.LCDTicks < 456) { - parentObj.scanLineMode0(); - } - else { - //We're on a new scan line: - parentObj.LCDTicks -= 456; - if (parentObj.STATTracker != 3) { - //Make sure the mode 0 handler was run at least once per scan line: - if (parentObj.STATTracker != 2) { - if (parentObj.STATTracker == 0 && parentObj.mode2TriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - } - parentObj.incrementScanLineQueue(); - } - if (parentObj.hdmaRunning) { - parentObj.executeHDMA(); - } - if (parentObj.mode0TriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - } - } - //Update the scanline registers and assert the LYC counter: - parentObj.actualScanLine = ++parentObj.memory[0xFF44]; - //Perform a LYC counter assert: - if (parentObj.actualScanLine == parentObj.memory[0xFF45]) { - parentObj.memory[0xFF41] |= 0x04; - if (parentObj.LYCMatchTriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - } - } - else { - parentObj.memory[0xFF41] &= 0x7B; - } - parentObj.checkIRQMatching(); - //Reset our mode contingency variables: - parentObj.STATTracker = 0; - parentObj.modeSTAT = 2; - parentObj.LINECONTROL[parentObj.actualScanLine](parentObj); //Scan Line and STAT Mode Control. - } - } - } - else if (line == 143) { - //We're on the last visible scan line of the LCD screen: - this.LINECONTROL[143] = function (parentObj) { - if (parentObj.LCDTicks < 80) { - parentObj.scanLineMode2(); - } - else if (parentObj.LCDTicks < 252) { - parentObj.scanLineMode3(); - } - else if (parentObj.LCDTicks < 456) { - parentObj.scanLineMode0(); - } - else { - //Starting V-Blank: - //Just finished the last visible scan line: - parentObj.LCDTicks -= 456; - if (parentObj.STATTracker != 3) { - //Make sure the mode 0 handler was run at least once per scan line: - if (parentObj.STATTracker != 2) { - if (parentObj.STATTracker == 0 && parentObj.mode2TriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - } - parentObj.incrementScanLineQueue(); - } - if (parentObj.hdmaRunning) { - parentObj.executeHDMA(); - } - if (parentObj.mode0TriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - } - } - //Update the scanline registers and assert the LYC counter: - parentObj.actualScanLine = parentObj.memory[0xFF44] = 144; - //Perform a LYC counter assert: - if (parentObj.memory[0xFF45] == 144) { - parentObj.memory[0xFF41] |= 0x04; - if (parentObj.LYCMatchTriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - } - } - else { - parentObj.memory[0xFF41] &= 0x7B; - } - //Reset our mode contingency variables: - parentObj.STATTracker = 0; - //Update our state for v-blank: - parentObj.modeSTAT = 1; - parentObj.interruptsRequested |= (parentObj.mode1TriggerSTAT) ? 0x3 : 0x1; - parentObj.checkIRQMatching(); - //Attempt to blit out to our canvas: - if (parentObj.drewBlank == 0) { - //Ensure JIT framing alignment: - if (parentObj.totalLinesPassed < 144 || (parentObj.totalLinesPassed == 144 && parentObj.midScanlineOffset > -1)) { - //Make sure our gfx are up-to-date: - parentObj.graphicsJITVBlank(); - //Draw the frame: - parentObj.prepareFrame(); - } - } - else { - //LCD off takes at least 2 frames: - --parentObj.drewBlank; - } - parentObj.LINECONTROL[144](parentObj); //Scan Line and STAT Mode Control. - } - } - } - else if (line < 153) { - //In VBlank - this.LINECONTROL[line] = function (parentObj) { - if (parentObj.LCDTicks >= 456) { - //We're on a new scan line: - parentObj.LCDTicks -= 456; - parentObj.actualScanLine = ++parentObj.memory[0xFF44]; - //Perform a LYC counter assert: - if (parentObj.actualScanLine == parentObj.memory[0xFF45]) { - parentObj.memory[0xFF41] |= 0x04; - if (parentObj.LYCMatchTriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - parentObj.checkIRQMatching(); - } - } - else { - parentObj.memory[0xFF41] &= 0x7B; - } - parentObj.LINECONTROL[parentObj.actualScanLine](parentObj); //Scan Line and STAT Mode Control. - } - } - } - else { - //VBlank Ending (We're on the last actual scan line) - this.LINECONTROL[153] = function (parentObj) { - if (parentObj.LCDTicks >= 8) { - if (parentObj.STATTracker != 4 && parentObj.memory[0xFF44] == 153) { - parentObj.memory[0xFF44] = 0; //LY register resets to 0 early. - //Perform a LYC counter assert: - if (parentObj.memory[0xFF45] == 0) { - parentObj.memory[0xFF41] |= 0x04; - if (parentObj.LYCMatchTriggerSTAT) { - parentObj.interruptsRequested |= 0x2; - parentObj.checkIRQMatching(); - } - } - else { - parentObj.memory[0xFF41] &= 0x7B; - } - parentObj.STATTracker = 4; - } - if (parentObj.LCDTicks >= 456) { - //We reset back to the beginning: - parentObj.LCDTicks -= 456; - parentObj.STATTracker = parentObj.actualScanLine = 0; - parentObj.LINECONTROL[0](parentObj); //Scan Line and STAT Mode Control. - } - } - } - } - ++line; - } -} -GameBoyCore.prototype.DisplayShowOff = function () { - if (this.drewBlank == 0) { - //Output a blank screen to the output framebuffer: - this.clearFrameBuffer(); - this.drewFrame = true; - } - this.drewBlank = 2; -} -GameBoyCore.prototype.executeHDMA = function () { - this.DMAWrite(1); - if (this.halt) { - if ((this.LCDTicks - this.spriteCount) < ((4 >> this.doubleSpeedShifter) | 0x20)) { - //HALT clocking correction: - this.CPUTicks = 4 + ((0x20 + this.spriteCount) << this.doubleSpeedShifter); - this.LCDTicks = this.spriteCount + ((4 >> this.doubleSpeedShifter) | 0x20); - } - } - else { - this.LCDTicks += (4 >> this.doubleSpeedShifter) | 0x20; //LCD Timing Update For HDMA. - } - if (this.memory[0xFF55] == 0) { - this.hdmaRunning = false; - this.memory[0xFF55] = 0xFF; //Transfer completed ("Hidden last step," since some ROMs don't imply this, but most do). - } - else { - --this.memory[0xFF55]; - } -} -GameBoyCore.prototype.clockUpdate = function () { - if (this.cTIMER) { - var dateObj = new Date(); - var newTime = dateObj.getTime(); - var timeElapsed = newTime - this.lastIteration; //Get the numnber of milliseconds since this last executed. - this.lastIteration = newTime; - if (this.cTIMER && !this.RTCHALT) { - //Update the MBC3 RTC: - this.RTCSeconds += timeElapsed / 1000; - while (this.RTCSeconds >= 60) { //System can stutter, so the seconds difference can get large, thus the "while". - this.RTCSeconds -= 60; - ++this.RTCMinutes; - if (this.RTCMinutes >= 60) { - this.RTCMinutes -= 60; - ++this.RTCHours; - if (this.RTCHours >= 24) { - this.RTCHours -= 24 - ++this.RTCDays; - if (this.RTCDays >= 512) { - this.RTCDays -= 512; - this.RTCDayOverFlow = true; - } - } - } - } - } - } -} -GameBoyCore.prototype.prepareFrame = function () { - //Copy the internal frame buffer to the output buffer: - this.swizzleFrameBuffer(); - this.drewFrame = true; -} -GameBoyCore.prototype.requestDraw = function () { - if (this.drewFrame) { - this.dispatchDraw(); - } -} -GameBoyCore.prototype.dispatchDraw = function () { - if (this.offscreenRGBCount > 0) { - //We actually updated the graphics internally, so copy out: - if (this.offscreenRGBCount == 92160) { - this.processDraw(this.swizzledFrame); - } - else { - this.resizeFrameBuffer(); - } - } -} -GameBoyCore.prototype.processDraw = function (frameBuffer) { - var canvasRGBALength = this.offscreenRGBCount; - var canvasData = this.canvasBuffer.data; - var bufferIndex = 0; - for (var canvasIndex = 0; canvasIndex < canvasRGBALength; ++canvasIndex) { - canvasData[canvasIndex++] = frameBuffer[bufferIndex++]; - canvasData[canvasIndex++] = frameBuffer[bufferIndex++]; - canvasData[canvasIndex++] = frameBuffer[bufferIndex++]; - } - this.graphicsBlit(); - this.drewFrame = false; -} -GameBoyCore.prototype.swizzleFrameBuffer = function () { - //Convert our dirty 24-bit (24-bit, with internal render flags above it) framebuffer to an 8-bit buffer with separate indices for the RGB channels: - var frameBuffer = this.frameBuffer; - var swizzledFrame = this.swizzledFrame; - var bufferIndex = 0; - for (var canvasIndex = 0; canvasIndex < 69120;) { - swizzledFrame[canvasIndex++] = (frameBuffer[bufferIndex] >> 16) & 0xFF; //Red - swizzledFrame[canvasIndex++] = (frameBuffer[bufferIndex] >> 8) & 0xFF; //Green - swizzledFrame[canvasIndex++] = frameBuffer[bufferIndex++] & 0xFF; //Blue - } -} -GameBoyCore.prototype.clearFrameBuffer = function () { - var bufferIndex = 0; - var frameBuffer = this.swizzledFrame; - if (this.cGBC || this.colorizedGBPalettes) { - while (bufferIndex < 69120) { - frameBuffer[bufferIndex++] = 248; - } - } - else { - while (bufferIndex < 69120) { - frameBuffer[bufferIndex++] = 239; - frameBuffer[bufferIndex++] = 255; - frameBuffer[bufferIndex++] = 222; - } - } -} -GameBoyCore.prototype.resizeFrameBuffer = function () { - //Resize in javascript with resize.js: - if (this.resizePathClear) { - this.resizePathClear = false; - this.resizer.resize(this.swizzledFrame); - } -} -GameBoyCore.prototype.compileResizeFrameBufferFunction = function () { - if (this.offscreenRGBCount > 0) { - var parentObj = this; - this.resizer = new Resize(160, 144, this.offscreenWidth, this.offscreenHeight, false, settings[13], false, function (buffer) { - if ((buffer.length / 3 * 4) == parentObj.offscreenRGBCount) { - parentObj.processDraw(buffer); - } - parentObj.resizePathClear = true; - }); - } -} -GameBoyCore.prototype.renderScanLine = function (scanlineToRender) { - this.pixelStart = scanlineToRender * 160; - if (this.bgEnabled) { - this.pixelEnd = 160; - this.BGLayerRender(scanlineToRender); - this.WindowLayerRender(scanlineToRender); - } - else { - var pixelLine = (scanlineToRender + 1) * 160; - var defaultColor = (this.cGBC || this.colorizedGBPalettes) ? 0xF8F8F8 : 0xEFFFDE; - for (var pixelPosition = (scanlineToRender * 160) + this.currentX; pixelPosition < pixelLine; pixelPosition++) { - this.frameBuffer[pixelPosition] = defaultColor; - } - } - this.SpriteLayerRender(scanlineToRender); - this.currentX = 0; - this.midScanlineOffset = -1; -} -GameBoyCore.prototype.renderMidScanLine = function () { - if (this.actualScanLine < 144 && this.modeSTAT == 3) { - //TODO: Get this accurate: - if (this.midScanlineOffset == -1) { - this.midScanlineOffset = this.backgroundX & 0x7; - } - if (this.LCDTicks >= 82) { - this.pixelEnd = this.LCDTicks - 74; - this.pixelEnd = Math.min(this.pixelEnd - this.midScanlineOffset - (this.pixelEnd % 0x8), 160); - if (this.bgEnabled) { - this.pixelStart = this.lastUnrenderedLine * 160; - this.BGLayerRender(this.lastUnrenderedLine); - this.WindowLayerRender(this.lastUnrenderedLine); - //TODO: Do midscanline JIT for sprites... - } - else { - var pixelLine = (this.lastUnrenderedLine * 160) + this.pixelEnd; - var defaultColor = (this.cGBC || this.colorizedGBPalettes) ? 0xF8F8F8 : 0xEFFFDE; - for (var pixelPosition = (this.lastUnrenderedLine * 160) + this.currentX; pixelPosition < pixelLine; pixelPosition++) { - this.frameBuffer[pixelPosition] = defaultColor; - } - } - this.currentX = this.pixelEnd; - } - } -} -GameBoyCore.prototype.initializeModeSpecificArrays = function () { - this.LCDCONTROL = (this.LCDisOn) ? this.LINECONTROL : this.DISPLAYOFFCONTROL; - if (this.cGBC) { - this.gbcOBJRawPalette = this.getTypedArray(0x40, 0, "uint8"); - this.gbcBGRawPalette = this.getTypedArray(0x40, 0, "uint8"); - this.gbcOBJPalette = this.getTypedArray(0x20, 0x1000000, "int32"); - this.gbcBGPalette = this.getTypedArray(0x40, 0, "int32"); - this.BGCHRBank2 = this.getTypedArray(0x800, 0, "uint8"); - this.BGCHRCurrentBank = (this.currVRAMBank > 0) ? this.BGCHRBank2 : this.BGCHRBank1; - this.tileCache = this.generateCacheArray(0xF80); - } - else { - this.gbOBJPalette = this.getTypedArray(8, 0, "int32"); - this.gbBGPalette = this.getTypedArray(4, 0, "int32"); - this.BGPalette = this.gbBGPalette; - this.OBJPalette = this.gbOBJPalette; - this.tileCache = this.generateCacheArray(0x700); - this.sortBuffer = this.getTypedArray(0x100, 0, "uint8"); - this.OAMAddressCache = this.getTypedArray(10, 0, "int32"); - } - this.renderPathBuild(); -} -GameBoyCore.prototype.GBCtoGBModeAdjust = function () { - cout("Stepping down from GBC mode.", 0); - this.VRAM = this.GBCMemory = this.BGCHRCurrentBank = this.BGCHRBank2 = null; - this.tileCache.length = 0x700; - if (settings[4]) { - this.gbBGColorizedPalette = this.getTypedArray(4, 0, "int32"); - this.gbOBJColorizedPalette = this.getTypedArray(8, 0, "int32"); - this.cachedBGPaletteConversion = this.getTypedArray(4, 0, "int32"); - this.cachedOBJPaletteConversion = this.getTypedArray(8, 0, "int32"); - this.BGPalette = this.gbBGColorizedPalette; - this.OBJPalette = this.gbOBJColorizedPalette; - this.gbOBJPalette = this.gbBGPalette = null; - this.getGBCColor(); - } - else { - this.gbOBJPalette = this.getTypedArray(8, 0, "int32"); - this.gbBGPalette = this.getTypedArray(4, 0, "int32"); - this.BGPalette = this.gbBGPalette; - this.OBJPalette = this.gbOBJPalette; - } - this.sortBuffer = this.getTypedArray(0x100, 0, "uint8"); - this.OAMAddressCache = this.getTypedArray(10, 0, "int32"); - this.renderPathBuild(); - this.memoryReadJumpCompile(); - this.memoryWriteJumpCompile(); -} -GameBoyCore.prototype.renderPathBuild = function () { - if (!this.cGBC) { - this.BGLayerRender = this.BGGBLayerRender; - this.WindowLayerRender = this.WindowGBLayerRender; - this.SpriteLayerRender = this.SpriteGBLayerRender; - } - else { - this.priorityFlaggingPathRebuild(); - this.SpriteLayerRender = this.SpriteGBCLayerRender; - } -} -GameBoyCore.prototype.priorityFlaggingPathRebuild = function () { - if (this.BGPriorityEnabled) { - this.BGLayerRender = this.BGGBCLayerRender; - this.WindowLayerRender = this.WindowGBCLayerRender; - } - else { - this.BGLayerRender = this.BGGBCLayerRenderNoPriorityFlagging; - this.WindowLayerRender = this.WindowGBCLayerRenderNoPriorityFlagging; - } -} -GameBoyCore.prototype.initializeReferencesFromSaveState = function () { - this.LCDCONTROL = (this.LCDisOn) ? this.LINECONTROL : this.DISPLAYOFFCONTROL; - var tileIndex = 0; - if (!this.cGBC) { - if (this.colorizedGBPalettes) { - this.BGPalette = this.gbBGColorizedPalette; - this.OBJPalette = this.gbOBJColorizedPalette; - this.updateGBBGPalette = this.updateGBColorizedBGPalette; - this.updateGBOBJPalette = this.updateGBColorizedOBJPalette; - - } - else { - this.BGPalette = this.gbBGPalette; - this.OBJPalette = this.gbOBJPalette; - } - this.tileCache = this.generateCacheArray(0x700); - for (tileIndex = 0x8000; tileIndex < 0x9000; tileIndex += 2) { - this.generateGBOAMTileLine(tileIndex); - } - for (tileIndex = 0x9000; tileIndex < 0x9800; tileIndex += 2) { - this.generateGBTileLine(tileIndex); - } - this.sortBuffer = this.getTypedArray(0x100, 0, "uint8"); - this.OAMAddressCache = this.getTypedArray(10, 0, "int32"); - } - else { - this.BGCHRCurrentBank = (this.currVRAMBank > 0) ? this.BGCHRBank2 : this.BGCHRBank1; - this.tileCache = this.generateCacheArray(0xF80); - for (; tileIndex < 0x1800; tileIndex += 0x10) { - this.generateGBCTileBank1(tileIndex); - this.generateGBCTileBank2(tileIndex); - } - } - this.renderPathBuild(); -} -GameBoyCore.prototype.RGBTint = function (value) { - //Adjustment for the GBC's tinting (According to Gambatte): - var r = value & 0x1F; - var g = (value >> 5) & 0x1F; - var b = (value >> 10) & 0x1F; - return ((r * 13 + g * 2 + b) >> 1) << 16 | (g * 3 + b) << 9 | (r * 3 + g * 2 + b * 11) >> 1; -} -GameBoyCore.prototype.getGBCColor = function () { - //GBC Colorization of DMG ROMs: - //BG - for (var counter = 0; counter < 4; counter++) { - var adjustedIndex = counter << 1; - //BG - this.cachedBGPaletteConversion[counter] = this.RGBTint((this.gbcBGRawPalette[adjustedIndex | 1] << 8) | this.gbcBGRawPalette[adjustedIndex]); - //OBJ 1 - this.cachedOBJPaletteConversion[counter] = this.RGBTint((this.gbcOBJRawPalette[adjustedIndex | 1] << 8) | this.gbcOBJRawPalette[adjustedIndex]); - } - //OBJ 2 - for (counter = 4; counter < 8; counter++) { - adjustedIndex = counter << 1; - this.cachedOBJPaletteConversion[counter] = this.RGBTint((this.gbcOBJRawPalette[adjustedIndex | 1] << 8) | this.gbcOBJRawPalette[adjustedIndex]); - } - //Update the palette entries: - this.updateGBBGPalette = this.updateGBColorizedBGPalette; - this.updateGBOBJPalette = this.updateGBColorizedOBJPalette; - this.updateGBBGPalette(this.memory[0xFF47]); - this.updateGBOBJPalette(0, this.memory[0xFF48]); - this.updateGBOBJPalette(1, this.memory[0xFF49]); - this.colorizedGBPalettes = true; -} -GameBoyCore.prototype.updateGBRegularBGPalette = function (data) { - this.gbBGPalette[0] = this.colors[data & 0x03] | 0x2000000; - this.gbBGPalette[1] = this.colors[(data >> 2) & 0x03]; - this.gbBGPalette[2] = this.colors[(data >> 4) & 0x03]; - this.gbBGPalette[3] = this.colors[data >> 6]; -} -GameBoyCore.prototype.updateGBColorizedBGPalette = function (data) { - //GB colorization: - this.gbBGColorizedPalette[0] = this.cachedBGPaletteConversion[data & 0x03] | 0x2000000; - this.gbBGColorizedPalette[1] = this.cachedBGPaletteConversion[(data >> 2) & 0x03]; - this.gbBGColorizedPalette[2] = this.cachedBGPaletteConversion[(data >> 4) & 0x03]; - this.gbBGColorizedPalette[3] = this.cachedBGPaletteConversion[data >> 6]; -} -GameBoyCore.prototype.updateGBRegularOBJPalette = function (index, data) { - this.gbOBJPalette[index | 1] = this.colors[(data >> 2) & 0x03]; - this.gbOBJPalette[index | 2] = this.colors[(data >> 4) & 0x03]; - this.gbOBJPalette[index | 3] = this.colors[data >> 6]; -} -GameBoyCore.prototype.updateGBColorizedOBJPalette = function (index, data) { - //GB colorization: - this.gbOBJColorizedPalette[index | 1] = this.cachedOBJPaletteConversion[index | ((data >> 2) & 0x03)]; - this.gbOBJColorizedPalette[index | 2] = this.cachedOBJPaletteConversion[index | ((data >> 4) & 0x03)]; - this.gbOBJColorizedPalette[index | 3] = this.cachedOBJPaletteConversion[index | (data >> 6)]; -} -GameBoyCore.prototype.updateGBCBGPalette = function (index, data) { - if (this.gbcBGRawPalette[index] != data) { - this.midScanLineJIT(); - //Update the color palette for BG tiles since it changed: - this.gbcBGRawPalette[index] = data; - if ((index & 0x06) == 0) { - //Palette 0 (Special tile Priority stuff) - data = 0x2000000 | this.RGBTint((this.gbcBGRawPalette[index | 1] << 8) | this.gbcBGRawPalette[index & 0x3E]); - index >>= 1; - this.gbcBGPalette[index] = data; - this.gbcBGPalette[0x20 | index] = 0x1000000 | data; - } - else { - //Regular Palettes (No special crap) - data = this.RGBTint((this.gbcBGRawPalette[index | 1] << 8) | this.gbcBGRawPalette[index & 0x3E]); - index >>= 1; - this.gbcBGPalette[index] = data; - this.gbcBGPalette[0x20 | index] = 0x1000000 | data; - } - } -} -GameBoyCore.prototype.updateGBCOBJPalette = function (index, data) { - if (this.gbcOBJRawPalette[index] != data) { - //Update the color palette for OBJ tiles since it changed: - this.gbcOBJRawPalette[index] = data; - if ((index & 0x06) > 0) { - //Regular Palettes (No special crap) - this.midScanLineJIT(); - this.gbcOBJPalette[index >> 1] = 0x1000000 | this.RGBTint((this.gbcOBJRawPalette[index | 1] << 8) | this.gbcOBJRawPalette[index & 0x3E]); - } - } -} -GameBoyCore.prototype.BGGBLayerRender = function (scanlineToRender) { - var scrollYAdjusted = (this.backgroundY + scanlineToRender) & 0xFF; //The line of the BG we're at. - var tileYLine = (scrollYAdjusted & 7) << 3; - var tileYDown = this.gfxBackgroundCHRBankPosition | ((scrollYAdjusted & 0xF8) << 2); //The row of cached tiles we're fetching from. - var scrollXAdjusted = (this.backgroundX + this.currentX) & 0xFF; //The scroll amount of the BG. - var pixelPosition = this.pixelStart + this.currentX; //Current pixel we're working on. - var pixelPositionEnd = this.pixelStart + ((this.gfxWindowDisplay && (scanlineToRender - this.windowY) >= 0) ? Math.min(Math.max(this.windowX, 0) + this.currentX, this.pixelEnd) : this.pixelEnd); //Make sure we do at most 160 pixels a scanline. - var tileNumber = tileYDown + (scrollXAdjusted >> 3); - var chrCode = this.BGCHRBank1[tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - var tile = this.tileCache[chrCode]; - for (var texel = (scrollXAdjusted & 0x7); texel < 8 && pixelPosition < pixelPositionEnd && scrollXAdjusted < 0x100; ++scrollXAdjusted) { - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[tileYLine | texel++]]; - } - var scrollXAdjustedAligned = Math.min(pixelPositionEnd - pixelPosition, 0x100 - scrollXAdjusted) >> 3; - scrollXAdjusted += scrollXAdjustedAligned << 3; - scrollXAdjustedAligned += tileNumber; - while (tileNumber < scrollXAdjustedAligned) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - tile = this.tileCache[chrCode]; - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - if (scrollXAdjusted < 0x100) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - tile = this.tileCache[chrCode]; - for (texel = tileYLine - 1; pixelPosition < pixelPositionEnd && scrollXAdjusted < 0x100; ++scrollXAdjusted) { - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[++texel]]; - } - } - scrollXAdjustedAligned = ((pixelPositionEnd - pixelPosition) >> 3) + tileYDown; - while (tileYDown < scrollXAdjustedAligned) { - chrCode = this.BGCHRBank1[tileYDown++]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - tile = this.tileCache[chrCode]; - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - chrCode = this.BGCHRBank1[tileYDown]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - tile = this.tileCache[chrCode]; - switch (pixelPositionEnd - pixelPosition) { - case 7: - this.frameBuffer[pixelPosition + 6] = this.BGPalette[tile[tileYLine | 6]]; - case 6: - this.frameBuffer[pixelPosition + 5] = this.BGPalette[tile[tileYLine | 5]]; - case 5: - this.frameBuffer[pixelPosition + 4] = this.BGPalette[tile[tileYLine | 4]]; - case 4: - this.frameBuffer[pixelPosition + 3] = this.BGPalette[tile[tileYLine | 3]]; - case 3: - this.frameBuffer[pixelPosition + 2] = this.BGPalette[tile[tileYLine | 2]]; - case 2: - this.frameBuffer[pixelPosition + 1] = this.BGPalette[tile[tileYLine | 1]]; - case 1: - this.frameBuffer[pixelPosition] = this.BGPalette[tile[tileYLine]]; - } - } - } -} -GameBoyCore.prototype.BGGBCLayerRender = function (scanlineToRender) { - var scrollYAdjusted = (this.backgroundY + scanlineToRender) & 0xFF; //The line of the BG we're at. - var tileYLine = (scrollYAdjusted & 7) << 3; - var tileYDown = this.gfxBackgroundCHRBankPosition | ((scrollYAdjusted & 0xF8) << 2); //The row of cached tiles we're fetching from. - var scrollXAdjusted = (this.backgroundX + this.currentX) & 0xFF; //The scroll amount of the BG. - var pixelPosition = this.pixelStart + this.currentX; //Current pixel we're working on. - var pixelPositionEnd = this.pixelStart + ((this.gfxWindowDisplay && (scanlineToRender - this.windowY) >= 0) ? Math.min(Math.max(this.windowX, 0) + this.currentX, this.pixelEnd) : this.pixelEnd); //Make sure we do at most 160 pixels a scanline. - var tileNumber = tileYDown + (scrollXAdjusted >> 3); - var chrCode = this.BGCHRBank1[tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - var attrCode = this.BGCHRBank2[tileNumber]; - var tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - var palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - for (var texel = (scrollXAdjusted & 0x7); texel < 8 && pixelPosition < pixelPositionEnd && scrollXAdjusted < 0x100; ++scrollXAdjusted) { - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[tileYLine | texel++]]; - } - var scrollXAdjustedAligned = Math.min(pixelPositionEnd - pixelPosition, 0x100 - scrollXAdjusted) >> 3; - scrollXAdjusted += scrollXAdjustedAligned << 3; - scrollXAdjustedAligned += tileNumber; - while (tileNumber < scrollXAdjustedAligned) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - if (scrollXAdjusted < 0x100) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - for (texel = tileYLine - 1; pixelPosition < pixelPositionEnd && scrollXAdjusted < 0x100; ++scrollXAdjusted) { - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[++texel]]; - } - } - scrollXAdjustedAligned = ((pixelPositionEnd - pixelPosition) >> 3) + tileYDown; - while (tileYDown < scrollXAdjustedAligned) { - chrCode = this.BGCHRBank1[tileYDown]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileYDown++]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - chrCode = this.BGCHRBank1[tileYDown]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileYDown]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - switch (pixelPositionEnd - pixelPosition) { - case 7: - this.frameBuffer[pixelPosition + 6] = this.gbcBGPalette[palette | tile[tileYLine | 6]]; - case 6: - this.frameBuffer[pixelPosition + 5] = this.gbcBGPalette[palette | tile[tileYLine | 5]]; - case 5: - this.frameBuffer[pixelPosition + 4] = this.gbcBGPalette[palette | tile[tileYLine | 4]]; - case 4: - this.frameBuffer[pixelPosition + 3] = this.gbcBGPalette[palette | tile[tileYLine | 3]]; - case 3: - this.frameBuffer[pixelPosition + 2] = this.gbcBGPalette[palette | tile[tileYLine | 2]]; - case 2: - this.frameBuffer[pixelPosition + 1] = this.gbcBGPalette[palette | tile[tileYLine | 1]]; - case 1: - this.frameBuffer[pixelPosition] = this.gbcBGPalette[palette | tile[tileYLine]]; - } - } - } -} -GameBoyCore.prototype.BGGBCLayerRenderNoPriorityFlagging = function (scanlineToRender) { - var scrollYAdjusted = (this.backgroundY + scanlineToRender) & 0xFF; //The line of the BG we're at. - var tileYLine = (scrollYAdjusted & 7) << 3; - var tileYDown = this.gfxBackgroundCHRBankPosition | ((scrollYAdjusted & 0xF8) << 2); //The row of cached tiles we're fetching from. - var scrollXAdjusted = (this.backgroundX + this.currentX) & 0xFF; //The scroll amount of the BG. - var pixelPosition = this.pixelStart + this.currentX; //Current pixel we're working on. - var pixelPositionEnd = this.pixelStart + ((this.gfxWindowDisplay && (scanlineToRender - this.windowY) >= 0) ? Math.min(Math.max(this.windowX, 0) + this.currentX, this.pixelEnd) : this.pixelEnd); //Make sure we do at most 160 pixels a scanline. - var tileNumber = tileYDown + (scrollXAdjusted >> 3); - var chrCode = this.BGCHRBank1[tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - var attrCode = this.BGCHRBank2[tileNumber]; - var tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - var palette = (attrCode & 0x7) << 2; - for (var texel = (scrollXAdjusted & 0x7); texel < 8 && pixelPosition < pixelPositionEnd && scrollXAdjusted < 0x100; ++scrollXAdjusted) { - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[tileYLine | texel++]]; - } - var scrollXAdjustedAligned = Math.min(pixelPositionEnd - pixelPosition, 0x100 - scrollXAdjusted) >> 3; - scrollXAdjusted += scrollXAdjustedAligned << 3; - scrollXAdjustedAligned += tileNumber; - while (tileNumber < scrollXAdjustedAligned) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = (attrCode & 0x7) << 2; - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - if (scrollXAdjusted < 0x100) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = (attrCode & 0x7) << 2; - for (texel = tileYLine - 1; pixelPosition < pixelPositionEnd && scrollXAdjusted < 0x100; ++scrollXAdjusted) { - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[++texel]]; - } - } - scrollXAdjustedAligned = ((pixelPositionEnd - pixelPosition) >> 3) + tileYDown; - while (tileYDown < scrollXAdjustedAligned) { - chrCode = this.BGCHRBank1[tileYDown]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileYDown++]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = (attrCode & 0x7) << 2; - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - chrCode = this.BGCHRBank1[tileYDown]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileYDown]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = (attrCode & 0x7) << 2; - switch (pixelPositionEnd - pixelPosition) { - case 7: - this.frameBuffer[pixelPosition + 6] = this.gbcBGPalette[palette | tile[tileYLine | 6]]; - case 6: - this.frameBuffer[pixelPosition + 5] = this.gbcBGPalette[palette | tile[tileYLine | 5]]; - case 5: - this.frameBuffer[pixelPosition + 4] = this.gbcBGPalette[palette | tile[tileYLine | 4]]; - case 4: - this.frameBuffer[pixelPosition + 3] = this.gbcBGPalette[palette | tile[tileYLine | 3]]; - case 3: - this.frameBuffer[pixelPosition + 2] = this.gbcBGPalette[palette | tile[tileYLine | 2]]; - case 2: - this.frameBuffer[pixelPosition + 1] = this.gbcBGPalette[palette | tile[tileYLine | 1]]; - case 1: - this.frameBuffer[pixelPosition] = this.gbcBGPalette[palette | tile[tileYLine]]; - } - } - } -} -GameBoyCore.prototype.WindowGBLayerRender = function (scanlineToRender) { - if (this.gfxWindowDisplay) { //Is the window enabled? - var scrollYAdjusted = scanlineToRender - this.windowY; //The line of the BG we're at. - if (scrollYAdjusted >= 0) { - var scrollXRangeAdjusted = (this.windowX > 0) ? (this.windowX + this.currentX) : this.currentX; - var pixelPosition = this.pixelStart + scrollXRangeAdjusted; - var pixelPositionEnd = this.pixelStart + this.pixelEnd; - if (pixelPosition < pixelPositionEnd) { - var tileYLine = (scrollYAdjusted & 0x7) << 3; - var tileNumber = (this.gfxWindowCHRBankPosition | ((scrollYAdjusted & 0xF8) << 2)) + (this.currentX >> 3); - var chrCode = this.BGCHRBank1[tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - var tile = this.tileCache[chrCode]; - var texel = (scrollXRangeAdjusted - this.windowX) & 0x7; - scrollXRangeAdjusted = Math.min(8, texel + pixelPositionEnd - pixelPosition); - while (texel < scrollXRangeAdjusted) { - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[tileYLine | texel++]]; - } - scrollXRangeAdjusted = tileNumber + ((pixelPositionEnd - pixelPosition) >> 3); - while (tileNumber < scrollXRangeAdjusted) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - tile = this.tileCache[chrCode]; - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.BGPalette[tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - tile = this.tileCache[chrCode]; - switch (pixelPositionEnd - pixelPosition) { - case 7: - this.frameBuffer[pixelPosition + 6] = this.BGPalette[tile[tileYLine | 6]]; - case 6: - this.frameBuffer[pixelPosition + 5] = this.BGPalette[tile[tileYLine | 5]]; - case 5: - this.frameBuffer[pixelPosition + 4] = this.BGPalette[tile[tileYLine | 4]]; - case 4: - this.frameBuffer[pixelPosition + 3] = this.BGPalette[tile[tileYLine | 3]]; - case 3: - this.frameBuffer[pixelPosition + 2] = this.BGPalette[tile[tileYLine | 2]]; - case 2: - this.frameBuffer[pixelPosition + 1] = this.BGPalette[tile[tileYLine | 1]]; - case 1: - this.frameBuffer[pixelPosition] = this.BGPalette[tile[tileYLine]]; - } - } - } - } - } -} -GameBoyCore.prototype.WindowGBCLayerRender = function (scanlineToRender) { - if (this.gfxWindowDisplay) { //Is the window enabled? - var scrollYAdjusted = scanlineToRender - this.windowY; //The line of the BG we're at. - if (scrollYAdjusted >= 0) { - var scrollXRangeAdjusted = (this.windowX > 0) ? (this.windowX + this.currentX) : this.currentX; - var pixelPosition = this.pixelStart + scrollXRangeAdjusted; - var pixelPositionEnd = this.pixelStart + this.pixelEnd; - if (pixelPosition < pixelPositionEnd) { - var tileYLine = (scrollYAdjusted & 0x7) << 3; - var tileNumber = (this.gfxWindowCHRBankPosition | ((scrollYAdjusted & 0xF8) << 2)) + (this.currentX >> 3); - var chrCode = this.BGCHRBank1[tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - var attrCode = this.BGCHRBank2[tileNumber]; - var tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - var palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - var texel = (scrollXRangeAdjusted - this.windowX) & 0x7; - scrollXRangeAdjusted = Math.min(8, texel + pixelPositionEnd - pixelPosition); - while (texel < scrollXRangeAdjusted) { - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[tileYLine | texel++]]; - } - scrollXRangeAdjusted = tileNumber + ((pixelPositionEnd - pixelPosition) >> 3); - while (tileNumber < scrollXRangeAdjusted) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = ((attrCode & 0x7) << 2) | ((attrCode & 0x80) >> 2); - switch (pixelPositionEnd - pixelPosition) { - case 7: - this.frameBuffer[pixelPosition + 6] = this.gbcBGPalette[palette | tile[tileYLine | 6]]; - case 6: - this.frameBuffer[pixelPosition + 5] = this.gbcBGPalette[palette | tile[tileYLine | 5]]; - case 5: - this.frameBuffer[pixelPosition + 4] = this.gbcBGPalette[palette | tile[tileYLine | 4]]; - case 4: - this.frameBuffer[pixelPosition + 3] = this.gbcBGPalette[palette | tile[tileYLine | 3]]; - case 3: - this.frameBuffer[pixelPosition + 2] = this.gbcBGPalette[palette | tile[tileYLine | 2]]; - case 2: - this.frameBuffer[pixelPosition + 1] = this.gbcBGPalette[palette | tile[tileYLine | 1]]; - case 1: - this.frameBuffer[pixelPosition] = this.gbcBGPalette[palette | tile[tileYLine]]; - } - } - } - } - } -} -GameBoyCore.prototype.WindowGBCLayerRenderNoPriorityFlagging = function (scanlineToRender) { - if (this.gfxWindowDisplay) { //Is the window enabled? - var scrollYAdjusted = scanlineToRender - this.windowY; //The line of the BG we're at. - if (scrollYAdjusted >= 0) { - var scrollXRangeAdjusted = (this.windowX > 0) ? (this.windowX + this.currentX) : this.currentX; - var pixelPosition = this.pixelStart + scrollXRangeAdjusted; - var pixelPositionEnd = this.pixelStart + this.pixelEnd; - if (pixelPosition < pixelPositionEnd) { - var tileYLine = (scrollYAdjusted & 0x7) << 3; - var tileNumber = (this.gfxWindowCHRBankPosition | ((scrollYAdjusted & 0xF8) << 2)) + (this.currentX >> 3); - var chrCode = this.BGCHRBank1[tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - var attrCode = this.BGCHRBank2[tileNumber]; - var tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - var palette = (attrCode & 0x7) << 2; - var texel = (scrollXRangeAdjusted - this.windowX) & 0x7; - scrollXRangeAdjusted = Math.min(8, texel + pixelPositionEnd - pixelPosition); - while (texel < scrollXRangeAdjusted) { - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[tileYLine | texel++]]; - } - scrollXRangeAdjusted = tileNumber + ((pixelPositionEnd - pixelPosition) >> 3); - while (tileNumber < scrollXRangeAdjusted) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = (attrCode & 0x7) << 2; - texel = tileYLine; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel++]]; - this.frameBuffer[pixelPosition++] = this.gbcBGPalette[palette | tile[texel]]; - } - if (pixelPosition < pixelPositionEnd) { - chrCode = this.BGCHRBank1[++tileNumber]; - if (chrCode < this.gfxBackgroundBankOffset) { - chrCode |= 0x100; - } - attrCode = this.BGCHRBank2[tileNumber]; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | chrCode]; - palette = (attrCode & 0x7) << 2; - switch (pixelPositionEnd - pixelPosition) { - case 7: - this.frameBuffer[pixelPosition + 6] = this.gbcBGPalette[palette | tile[tileYLine | 6]]; - case 6: - this.frameBuffer[pixelPosition + 5] = this.gbcBGPalette[palette | tile[tileYLine | 5]]; - case 5: - this.frameBuffer[pixelPosition + 4] = this.gbcBGPalette[palette | tile[tileYLine | 4]]; - case 4: - this.frameBuffer[pixelPosition + 3] = this.gbcBGPalette[palette | tile[tileYLine | 3]]; - case 3: - this.frameBuffer[pixelPosition + 2] = this.gbcBGPalette[palette | tile[tileYLine | 2]]; - case 2: - this.frameBuffer[pixelPosition + 1] = this.gbcBGPalette[palette | tile[tileYLine | 1]]; - case 1: - this.frameBuffer[pixelPosition] = this.gbcBGPalette[palette | tile[tileYLine]]; - } - } - } - } - } -} -GameBoyCore.prototype.SpriteGBLayerRender = function (scanlineToRender) { - if (this.gfxSpriteShow) { //Are sprites enabled? - var lineAdjusted = scanlineToRender + 0x10; - var OAMAddress = 0xFE00; - var yoffset = 0; - var xcoord = 1; - var xCoordStart = 0; - var xCoordEnd = 0; - var attrCode = 0; - var palette = 0; - var tile = null; - var data = 0; - var spriteCount = 0; - var length = 0; - var currentPixel = 0; - var linePixel = 0; - //Clear our x-coord sort buffer: - while (xcoord < 168) { - this.sortBuffer[xcoord++] = 0xFF; - } - if (this.gfxSpriteNormalHeight) { - //Draw the visible sprites: - for (var length = this.findLowestSpriteDrawable(lineAdjusted, 0x7); spriteCount < length; ++spriteCount) { - OAMAddress = this.OAMAddressCache[spriteCount]; - yoffset = (lineAdjusted - this.memory[OAMAddress]) << 3; - attrCode = this.memory[OAMAddress | 3]; - palette = (attrCode & 0x10) >> 2; - tile = this.tileCache[((attrCode & 0x60) << 4) | this.memory[OAMAddress | 0x2]]; - linePixel = xCoordStart = this.memory[OAMAddress | 1]; - xCoordEnd = Math.min(168 - linePixel, 8); - xcoord = (linePixel > 7) ? 0 : (8 - linePixel); - for (currentPixel = this.pixelStart + ((linePixel > 8) ? (linePixel - 8) : 0); xcoord < xCoordEnd; ++xcoord, ++currentPixel, ++linePixel) { - if (this.sortBuffer[linePixel] > xCoordStart) { - if (this.frameBuffer[currentPixel] >= 0x2000000) { - data = tile[yoffset | xcoord]; - if (data > 0) { - this.frameBuffer[currentPixel] = this.OBJPalette[palette | data]; - this.sortBuffer[linePixel] = xCoordStart; - } - } - else if (this.frameBuffer[currentPixel] < 0x1000000) { - data = tile[yoffset | xcoord]; - if (data > 0 && attrCode < 0x80) { - this.frameBuffer[currentPixel] = this.OBJPalette[palette | data]; - this.sortBuffer[linePixel] = xCoordStart; - } - } - } - } - } - } - else { - //Draw the visible sprites: - for (var length = this.findLowestSpriteDrawable(lineAdjusted, 0xF); spriteCount < length; ++spriteCount) { - OAMAddress = this.OAMAddressCache[spriteCount]; - yoffset = (lineAdjusted - this.memory[OAMAddress]) << 3; - attrCode = this.memory[OAMAddress | 3]; - palette = (attrCode & 0x10) >> 2; - if ((attrCode & 0x40) == (0x40 & yoffset)) { - tile = this.tileCache[((attrCode & 0x60) << 4) | (this.memory[OAMAddress | 0x2] & 0xFE)]; - } - else { - tile = this.tileCache[((attrCode & 0x60) << 4) | this.memory[OAMAddress | 0x2] | 1]; - } - yoffset &= 0x3F; - linePixel = xCoordStart = this.memory[OAMAddress | 1]; - xCoordEnd = Math.min(168 - linePixel, 8); - xcoord = (linePixel > 7) ? 0 : (8 - linePixel); - for (currentPixel = this.pixelStart + ((linePixel > 8) ? (linePixel - 8) : 0); xcoord < xCoordEnd; ++xcoord, ++currentPixel, ++linePixel) { - if (this.sortBuffer[linePixel] > xCoordStart) { - if (this.frameBuffer[currentPixel] >= 0x2000000) { - data = tile[yoffset | xcoord]; - if (data > 0) { - this.frameBuffer[currentPixel] = this.OBJPalette[palette | data]; - this.sortBuffer[linePixel] = xCoordStart; - } - } - else if (this.frameBuffer[currentPixel] < 0x1000000) { - data = tile[yoffset | xcoord]; - if (data > 0 && attrCode < 0x80) { - this.frameBuffer[currentPixel] = this.OBJPalette[palette | data]; - this.sortBuffer[linePixel] = xCoordStart; - } - } - } - } - } - } - } -} -GameBoyCore.prototype.findLowestSpriteDrawable = function (scanlineToRender, drawableRange) { - var address = 0xFE00; - var spriteCount = 0; - var diff = 0; - while (address < 0xFEA0 && spriteCount < 10) { - diff = scanlineToRender - this.memory[address]; - if ((diff & drawableRange) == diff) { - this.OAMAddressCache[spriteCount++] = address; - } - address += 4; - } - return spriteCount; -} -GameBoyCore.prototype.SpriteGBCLayerRender = function (scanlineToRender) { - if (this.gfxSpriteShow) { //Are sprites enabled? - var OAMAddress = 0xFE00; - var lineAdjusted = scanlineToRender + 0x10; - var yoffset = 0; - var xcoord = 0; - var endX = 0; - var xCounter = 0; - var attrCode = 0; - var palette = 0; - var tile = null; - var data = 0; - var currentPixel = 0; - var spriteCount = 0; - if (this.gfxSpriteNormalHeight) { - for (; OAMAddress < 0xFEA0 && spriteCount < 10; OAMAddress += 4) { - yoffset = lineAdjusted - this.memory[OAMAddress]; - if ((yoffset & 0x7) == yoffset) { - xcoord = this.memory[OAMAddress | 1] - 8; - endX = Math.min(160, xcoord + 8); - attrCode = this.memory[OAMAddress | 3]; - palette = (attrCode & 7) << 2; - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | this.memory[OAMAddress | 2]]; - xCounter = (xcoord > 0) ? xcoord : 0; - xcoord -= yoffset << 3; - for (currentPixel = this.pixelStart + xCounter; xCounter < endX; ++xCounter, ++currentPixel) { - if (this.frameBuffer[currentPixel] >= 0x2000000) { - data = tile[xCounter - xcoord]; - if (data > 0) { - this.frameBuffer[currentPixel] = this.gbcOBJPalette[palette | data]; - } - } - else if (this.frameBuffer[currentPixel] < 0x1000000) { - data = tile[xCounter - xcoord]; - if (data > 0 && attrCode < 0x80) { //Don't optimize for attrCode, as LICM-capable JITs should optimize its checks. - this.frameBuffer[currentPixel] = this.gbcOBJPalette[palette | data]; - } - } - } - ++spriteCount; - } - } - } - else { - for (; OAMAddress < 0xFEA0 && spriteCount < 10; OAMAddress += 4) { - yoffset = lineAdjusted - this.memory[OAMAddress]; - if ((yoffset & 0xF) == yoffset) { - xcoord = this.memory[OAMAddress | 1] - 8; - endX = Math.min(160, xcoord + 8); - attrCode = this.memory[OAMAddress | 3]; - palette = (attrCode & 7) << 2; - if ((attrCode & 0x40) == (0x40 & (yoffset << 3))) { - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | (this.memory[OAMAddress | 0x2] & 0xFE)]; - } - else { - tile = this.tileCache[((attrCode & 0x08) << 8) | ((attrCode & 0x60) << 4) | this.memory[OAMAddress | 0x2] | 1]; - } - xCounter = (xcoord > 0) ? xcoord : 0; - xcoord -= (yoffset & 0x7) << 3; - for (currentPixel = this.pixelStart + xCounter; xCounter < endX; ++xCounter, ++currentPixel) { - if (this.frameBuffer[currentPixel] >= 0x2000000) { - data = tile[xCounter - xcoord]; - if (data > 0) { - this.frameBuffer[currentPixel] = this.gbcOBJPalette[palette | data]; - } - } - else if (this.frameBuffer[currentPixel] < 0x1000000) { - data = tile[xCounter - xcoord]; - if (data > 0 && attrCode < 0x80) { //Don't optimize for attrCode, as LICM-capable JITs should optimize its checks. - this.frameBuffer[currentPixel] = this.gbcOBJPalette[palette | data]; - } - } - } - ++spriteCount; - } - } - } - } -} -//Generate only a single tile line for the GB tile cache mode: -GameBoyCore.prototype.generateGBTileLine = function (address) { - var lineCopy = (this.memory[0x1 | address] << 8) | this.memory[0x9FFE & address]; - var tileBlock = this.tileCache[(address & 0x1FF0) >> 4]; - address = (address & 0xE) << 2; - tileBlock[address | 7] = ((lineCopy & 0x100) >> 7) | (lineCopy & 0x1); - tileBlock[address | 6] = ((lineCopy & 0x200) >> 8) | ((lineCopy & 0x2) >> 1); - tileBlock[address | 5] = ((lineCopy & 0x400) >> 9) | ((lineCopy & 0x4) >> 2); - tileBlock[address | 4] = ((lineCopy & 0x800) >> 10) | ((lineCopy & 0x8) >> 3); - tileBlock[address | 3] = ((lineCopy & 0x1000) >> 11) | ((lineCopy & 0x10) >> 4); - tileBlock[address | 2] = ((lineCopy & 0x2000) >> 12) | ((lineCopy & 0x20) >> 5); - tileBlock[address | 1] = ((lineCopy & 0x4000) >> 13) | ((lineCopy & 0x40) >> 6); - tileBlock[address] = ((lineCopy & 0x8000) >> 14) | ((lineCopy & 0x80) >> 7); -} -//Generate only a single tile line for the GBC tile cache mode (Bank 1): -GameBoyCore.prototype.generateGBCTileLineBank1 = function (address) { - var lineCopy = (this.memory[0x1 | address] << 8) | this.memory[0x9FFE & address]; - address &= 0x1FFE; - var tileBlock1 = this.tileCache[address >> 4]; - var tileBlock2 = this.tileCache[0x200 | (address >> 4)]; - var tileBlock3 = this.tileCache[0x400 | (address >> 4)]; - var tileBlock4 = this.tileCache[0x600 | (address >> 4)]; - address = (address & 0xE) << 2; - var addressFlipped = 0x38 - address; - tileBlock4[addressFlipped] = tileBlock2[address] = tileBlock3[addressFlipped | 7] = tileBlock1[address | 7] = ((lineCopy & 0x100) >> 7) | (lineCopy & 0x1); - tileBlock4[addressFlipped | 1] = tileBlock2[address | 1] = tileBlock3[addressFlipped | 6] = tileBlock1[address | 6] = ((lineCopy & 0x200) >> 8) | ((lineCopy & 0x2) >> 1); - tileBlock4[addressFlipped | 2] = tileBlock2[address | 2] = tileBlock3[addressFlipped | 5] = tileBlock1[address | 5] = ((lineCopy & 0x400) >> 9) | ((lineCopy & 0x4) >> 2); - tileBlock4[addressFlipped | 3] = tileBlock2[address | 3] = tileBlock3[addressFlipped | 4] = tileBlock1[address | 4] = ((lineCopy & 0x800) >> 10) | ((lineCopy & 0x8) >> 3); - tileBlock4[addressFlipped | 4] = tileBlock2[address | 4] = tileBlock3[addressFlipped | 3] = tileBlock1[address | 3] = ((lineCopy & 0x1000) >> 11) | ((lineCopy & 0x10) >> 4); - tileBlock4[addressFlipped | 5] = tileBlock2[address | 5] = tileBlock3[addressFlipped | 2] = tileBlock1[address | 2] = ((lineCopy & 0x2000) >> 12) | ((lineCopy & 0x20) >> 5); - tileBlock4[addressFlipped | 6] = tileBlock2[address | 6] = tileBlock3[addressFlipped | 1] = tileBlock1[address | 1] = ((lineCopy & 0x4000) >> 13) | ((lineCopy & 0x40) >> 6); - tileBlock4[addressFlipped | 7] = tileBlock2[address | 7] = tileBlock3[addressFlipped] = tileBlock1[address] = ((lineCopy & 0x8000) >> 14) | ((lineCopy & 0x80) >> 7); -} -//Generate all the flip combinations for a full GBC VRAM bank 1 tile: -GameBoyCore.prototype.generateGBCTileBank1 = function (vramAddress) { - var address = vramAddress >> 4; - var tileBlock1 = this.tileCache[address]; - var tileBlock2 = this.tileCache[0x200 | address]; - var tileBlock3 = this.tileCache[0x400 | address]; - var tileBlock4 = this.tileCache[0x600 | address]; - var lineCopy = 0; - vramAddress |= 0x8000; - address = 0; - var addressFlipped = 56; - do { - lineCopy = (this.memory[0x1 | vramAddress] << 8) | this.memory[vramAddress]; - tileBlock4[addressFlipped] = tileBlock2[address] = tileBlock3[addressFlipped | 7] = tileBlock1[address | 7] = ((lineCopy & 0x100) >> 7) | (lineCopy & 0x1); - tileBlock4[addressFlipped | 1] = tileBlock2[address | 1] = tileBlock3[addressFlipped | 6] = tileBlock1[address | 6] = ((lineCopy & 0x200) >> 8) | ((lineCopy & 0x2) >> 1); - tileBlock4[addressFlipped | 2] = tileBlock2[address | 2] = tileBlock3[addressFlipped | 5] = tileBlock1[address | 5] = ((lineCopy & 0x400) >> 9) | ((lineCopy & 0x4) >> 2); - tileBlock4[addressFlipped | 3] = tileBlock2[address | 3] = tileBlock3[addressFlipped | 4] = tileBlock1[address | 4] = ((lineCopy & 0x800) >> 10) | ((lineCopy & 0x8) >> 3); - tileBlock4[addressFlipped | 4] = tileBlock2[address | 4] = tileBlock3[addressFlipped | 3] = tileBlock1[address | 3] = ((lineCopy & 0x1000) >> 11) | ((lineCopy & 0x10) >> 4); - tileBlock4[addressFlipped | 5] = tileBlock2[address | 5] = tileBlock3[addressFlipped | 2] = tileBlock1[address | 2] = ((lineCopy & 0x2000) >> 12) | ((lineCopy & 0x20) >> 5); - tileBlock4[addressFlipped | 6] = tileBlock2[address | 6] = tileBlock3[addressFlipped | 1] = tileBlock1[address | 1] = ((lineCopy & 0x4000) >> 13) | ((lineCopy & 0x40) >> 6); - tileBlock4[addressFlipped | 7] = tileBlock2[address | 7] = tileBlock3[addressFlipped] = tileBlock1[address] = ((lineCopy & 0x8000) >> 14) | ((lineCopy & 0x80) >> 7); - address += 8; - addressFlipped -= 8; - vramAddress += 2; - } while (addressFlipped > -1); -} -//Generate only a single tile line for the GBC tile cache mode (Bank 2): -GameBoyCore.prototype.generateGBCTileLineBank2 = function (address) { - var lineCopy = (this.VRAM[0x1 | address] << 8) | this.VRAM[0x1FFE & address]; - var tileBlock1 = this.tileCache[0x800 | (address >> 4)]; - var tileBlock2 = this.tileCache[0xA00 | (address >> 4)]; - var tileBlock3 = this.tileCache[0xC00 | (address >> 4)]; - var tileBlock4 = this.tileCache[0xE00 | (address >> 4)]; - address = (address & 0xE) << 2; - var addressFlipped = 0x38 - address; - tileBlock4[addressFlipped] = tileBlock2[address] = tileBlock3[addressFlipped | 7] = tileBlock1[address | 7] = ((lineCopy & 0x100) >> 7) | (lineCopy & 0x1); - tileBlock4[addressFlipped | 1] = tileBlock2[address | 1] = tileBlock3[addressFlipped | 6] = tileBlock1[address | 6] = ((lineCopy & 0x200) >> 8) | ((lineCopy & 0x2) >> 1); - tileBlock4[addressFlipped | 2] = tileBlock2[address | 2] = tileBlock3[addressFlipped | 5] = tileBlock1[address | 5] = ((lineCopy & 0x400) >> 9) | ((lineCopy & 0x4) >> 2); - tileBlock4[addressFlipped | 3] = tileBlock2[address | 3] = tileBlock3[addressFlipped | 4] = tileBlock1[address | 4] = ((lineCopy & 0x800) >> 10) | ((lineCopy & 0x8) >> 3); - tileBlock4[addressFlipped | 4] = tileBlock2[address | 4] = tileBlock3[addressFlipped | 3] = tileBlock1[address | 3] = ((lineCopy & 0x1000) >> 11) | ((lineCopy & 0x10) >> 4); - tileBlock4[addressFlipped | 5] = tileBlock2[address | 5] = tileBlock3[addressFlipped | 2] = tileBlock1[address | 2] = ((lineCopy & 0x2000) >> 12) | ((lineCopy & 0x20) >> 5); - tileBlock4[addressFlipped | 6] = tileBlock2[address | 6] = tileBlock3[addressFlipped | 1] = tileBlock1[address | 1] = ((lineCopy & 0x4000) >> 13) | ((lineCopy & 0x40) >> 6); - tileBlock4[addressFlipped | 7] = tileBlock2[address | 7] = tileBlock3[addressFlipped] = tileBlock1[address] = ((lineCopy & 0x8000) >> 14) | ((lineCopy & 0x80) >> 7); -} -//Generate all the flip combinations for a full GBC VRAM bank 2 tile: -GameBoyCore.prototype.generateGBCTileBank2 = function (vramAddress) { - var address = vramAddress >> 4; - var tileBlock1 = this.tileCache[0x800 | address]; - var tileBlock2 = this.tileCache[0xA00 | address]; - var tileBlock3 = this.tileCache[0xC00 | address]; - var tileBlock4 = this.tileCache[0xE00 | address]; - var lineCopy = 0; - address = 0; - var addressFlipped = 56; - do { - lineCopy = (this.VRAM[0x1 | vramAddress] << 8) | this.VRAM[vramAddress]; - tileBlock4[addressFlipped] = tileBlock2[address] = tileBlock3[addressFlipped | 7] = tileBlock1[address | 7] = ((lineCopy & 0x100) >> 7) | (lineCopy & 0x1); - tileBlock4[addressFlipped | 1] = tileBlock2[address | 1] = tileBlock3[addressFlipped | 6] = tileBlock1[address | 6] = ((lineCopy & 0x200) >> 8) | ((lineCopy & 0x2) >> 1); - tileBlock4[addressFlipped | 2] = tileBlock2[address | 2] = tileBlock3[addressFlipped | 5] = tileBlock1[address | 5] = ((lineCopy & 0x400) >> 9) | ((lineCopy & 0x4) >> 2); - tileBlock4[addressFlipped | 3] = tileBlock2[address | 3] = tileBlock3[addressFlipped | 4] = tileBlock1[address | 4] = ((lineCopy & 0x800) >> 10) | ((lineCopy & 0x8) >> 3); - tileBlock4[addressFlipped | 4] = tileBlock2[address | 4] = tileBlock3[addressFlipped | 3] = tileBlock1[address | 3] = ((lineCopy & 0x1000) >> 11) | ((lineCopy & 0x10) >> 4); - tileBlock4[addressFlipped | 5] = tileBlock2[address | 5] = tileBlock3[addressFlipped | 2] = tileBlock1[address | 2] = ((lineCopy & 0x2000) >> 12) | ((lineCopy & 0x20) >> 5); - tileBlock4[addressFlipped | 6] = tileBlock2[address | 6] = tileBlock3[addressFlipped | 1] = tileBlock1[address | 1] = ((lineCopy & 0x4000) >> 13) | ((lineCopy & 0x40) >> 6); - tileBlock4[addressFlipped | 7] = tileBlock2[address | 7] = tileBlock3[addressFlipped] = tileBlock1[address] = ((lineCopy & 0x8000) >> 14) | ((lineCopy & 0x80) >> 7); - address += 8; - addressFlipped -= 8; - vramAddress += 2; - } while (addressFlipped > -1); -} -//Generate only a single tile line for the GB tile cache mode (OAM accessible range): -GameBoyCore.prototype.generateGBOAMTileLine = function (address) { - var lineCopy = (this.memory[0x1 | address] << 8) | this.memory[0x9FFE & address]; - address &= 0x1FFE; - var tileBlock1 = this.tileCache[address >> 4]; - var tileBlock2 = this.tileCache[0x200 | (address >> 4)]; - var tileBlock3 = this.tileCache[0x400 | (address >> 4)]; - var tileBlock4 = this.tileCache[0x600 | (address >> 4)]; - address = (address & 0xE) << 2; - var addressFlipped = 0x38 - address; - tileBlock4[addressFlipped] = tileBlock2[address] = tileBlock3[addressFlipped | 7] = tileBlock1[address | 7] = ((lineCopy & 0x100) >> 7) | (lineCopy & 0x1); - tileBlock4[addressFlipped | 1] = tileBlock2[address | 1] = tileBlock3[addressFlipped | 6] = tileBlock1[address | 6] = ((lineCopy & 0x200) >> 8) | ((lineCopy & 0x2) >> 1); - tileBlock4[addressFlipped | 2] = tileBlock2[address | 2] = tileBlock3[addressFlipped | 5] = tileBlock1[address | 5] = ((lineCopy & 0x400) >> 9) | ((lineCopy & 0x4) >> 2); - tileBlock4[addressFlipped | 3] = tileBlock2[address | 3] = tileBlock3[addressFlipped | 4] = tileBlock1[address | 4] = ((lineCopy & 0x800) >> 10) | ((lineCopy & 0x8) >> 3); - tileBlock4[addressFlipped | 4] = tileBlock2[address | 4] = tileBlock3[addressFlipped | 3] = tileBlock1[address | 3] = ((lineCopy & 0x1000) >> 11) | ((lineCopy & 0x10) >> 4); - tileBlock4[addressFlipped | 5] = tileBlock2[address | 5] = tileBlock3[addressFlipped | 2] = tileBlock1[address | 2] = ((lineCopy & 0x2000) >> 12) | ((lineCopy & 0x20) >> 5); - tileBlock4[addressFlipped | 6] = tileBlock2[address | 6] = tileBlock3[addressFlipped | 1] = tileBlock1[address | 1] = ((lineCopy & 0x4000) >> 13) | ((lineCopy & 0x40) >> 6); - tileBlock4[addressFlipped | 7] = tileBlock2[address | 7] = tileBlock3[addressFlipped] = tileBlock1[address] = ((lineCopy & 0x8000) >> 14) | ((lineCopy & 0x80) >> 7); -} -GameBoyCore.prototype.graphicsJIT = function () { - if (this.LCDisOn) { - this.totalLinesPassed = 0; //Mark frame for ensuring a JIT pass for the next framebuffer output. - this.graphicsJITScanlineGroup(); - } -} -GameBoyCore.prototype.graphicsJITVBlank = function () { - //JIT the graphics to v-blank framing: - this.totalLinesPassed += this.queuedScanLines; - this.graphicsJITScanlineGroup(); -} -GameBoyCore.prototype.graphicsJITScanlineGroup = function () { - //Normal rendering JIT, where we try to do groups of scanlines at once: - while (this.queuedScanLines > 0) { - this.renderScanLine(this.lastUnrenderedLine); - if (this.lastUnrenderedLine < 143) { - ++this.lastUnrenderedLine; - } - else { - this.lastUnrenderedLine = 0; - } - --this.queuedScanLines; - } -} -GameBoyCore.prototype.incrementScanLineQueue = function () { - if (this.queuedScanLines < 144) { - ++this.queuedScanLines; - } - else { - this.currentX = 0; - this.midScanlineOffset = -1; - if (this.lastUnrenderedLine < 143) { - ++this.lastUnrenderedLine; - } - else { - this.lastUnrenderedLine = 0; - } - } -} -GameBoyCore.prototype.midScanLineJIT = function () { - this.graphicsJIT(); - this.renderMidScanLine(); -} -//Check for the highest priority IRQ to fire: -GameBoyCore.prototype.launchIRQ = function () { - var bitShift = 0; - var testbit = 1; - do { - //Check to see if an interrupt is enabled AND requested. - if ((testbit & this.IRQLineMatched) == testbit) { - this.IME = false; //Reset the interrupt enabling. - this.interruptsRequested -= testbit; //Reset the interrupt request. - this.IRQLineMatched = 0; //Reset the IRQ assertion. - //Interrupts have a certain clock cycle length: - this.CPUTicks = 20; - //Set the stack pointer to the current program counter value: - this.stackPointer = (this.stackPointer - 1) & 0xFFFF; - this.memoryWriter[this.stackPointer](this, this.stackPointer, this.programCounter >> 8); - this.stackPointer = (this.stackPointer - 1) & 0xFFFF; - this.memoryWriter[this.stackPointer](this, this.stackPointer, this.programCounter & 0xFF); - //Set the program counter to the interrupt's address: - this.programCounter = 0x40 | (bitShift << 3); - //Clock the core for mid-instruction updates: - this.updateCore(); - return; //We only want the highest priority interrupt. - } - testbit = 1 << ++bitShift; - } while (bitShift < 5); -} -/* - Check for IRQs to be fired while not in HALT: -*/ -GameBoyCore.prototype.checkIRQMatching = function () { - if (this.IME) { - this.IRQLineMatched = this.interruptsEnabled & this.interruptsRequested & 0x1F; - } -} -/* - Handle the HALT opcode by predicting all IRQ cases correctly, - then selecting the next closest IRQ firing from the prediction to - clock up to. This prevents hacky looping that doesn't predict, but - instead just clocks through the core update procedure by one which - is very slow. Not many emulators do this because they have to cover - all the IRQ prediction cases and they usually get them wrong. -*/ -GameBoyCore.prototype.calculateHALTPeriod = function () { - //Initialize our variables and start our prediction: - if (!this.halt) { - this.halt = true; - var currentClocks = -1; - var temp_var = 0; - if (this.LCDisOn) { - //If the LCD is enabled, then predict the LCD IRQs enabled: - if ((this.interruptsEnabled & 0x1) == 0x1) { - currentClocks = ((456 * (((this.modeSTAT == 1) ? 298 : 144) - this.actualScanLine)) - this.LCDTicks) << this.doubleSpeedShifter; - } - if ((this.interruptsEnabled & 0x2) == 0x2) { - if (this.mode0TriggerSTAT) { - temp_var = (this.clocksUntilMode0() - this.LCDTicks) << this.doubleSpeedShifter; - if (temp_var <= currentClocks || currentClocks == -1) { - currentClocks = temp_var; - } - } - if (this.mode1TriggerSTAT && (this.interruptsEnabled & 0x1) == 0) { - temp_var = ((456 * (((this.modeSTAT == 1) ? 298 : 144) - this.actualScanLine)) - this.LCDTicks) << this.doubleSpeedShifter; - if (temp_var <= currentClocks || currentClocks == -1) { - currentClocks = temp_var; - } - } - if (this.mode2TriggerSTAT) { - temp_var = (((this.actualScanLine >= 143) ? (456 * (154 - this.actualScanLine)) : 456) - this.LCDTicks) << this.doubleSpeedShifter; - if (temp_var <= currentClocks || currentClocks == -1) { - currentClocks = temp_var; - } - } - if (this.LYCMatchTriggerSTAT && this.memory[0xFF45] <= 153) { - temp_var = (this.clocksUntilLYCMatch() - this.LCDTicks) << this.doubleSpeedShifter; - if (temp_var <= currentClocks || currentClocks == -1) { - currentClocks = temp_var; - } - } - } - } - if (this.TIMAEnabled && (this.interruptsEnabled & 0x4) == 0x4) { - //CPU timer IRQ prediction: - temp_var = ((0x100 - this.memory[0xFF05]) * this.TACClocker) - this.timerTicks; - if (temp_var <= currentClocks || currentClocks == -1) { - currentClocks = temp_var; - } - } - if (this.serialTimer > 0 && (this.interruptsEnabled & 0x8) == 0x8) { - //Serial IRQ prediction: - if (this.serialTimer <= currentClocks || currentClocks == -1) { - currentClocks = this.serialTimer; - } - } - } - else { - var currentClocks = this.remainingClocks; - } - var maxClocks = (this.CPUCyclesTotal - this.emulatorTicks) << this.doubleSpeedShifter; - if (currentClocks >= 0) { - if (currentClocks <= maxClocks) { - //Exit out of HALT normally: - this.CPUTicks = Math.max(currentClocks, this.CPUTicks); - this.updateCoreFull(); - this.halt = false; - this.CPUTicks = 0; - } - else { - //Still in HALT, clock only up to the clocks specified per iteration: - this.CPUTicks = Math.max(maxClocks, this.CPUTicks); - this.remainingClocks = currentClocks - this.CPUTicks; - } - } - else { - //Still in HALT, clock only up to the clocks specified per iteration: - //Will stay in HALT forever (Stuck in HALT forever), but the APU and LCD are still clocked, so don't pause: - this.CPUTicks += maxClocks; - } -} -//Memory Reading: -GameBoyCore.prototype.memoryRead = function (address) { - //Act as a wrapper for reading the returns from the compiled jumps to memory. - return this.memoryReader[address](this, address); //This seems to be faster than the usual if/else. -} -GameBoyCore.prototype.memoryHighRead = function (address) { - //Act as a wrapper for reading the returns from the compiled jumps to memory. - return this.memoryHighReader[address](this, address); //This seems to be faster than the usual if/else. -} -GameBoyCore.prototype.memoryReadJumpCompile = function () { - //Faster in some browsers, since we are doing less conditionals overall by implementing them in advance. - for (var index = 0x0000; index <= 0xFFFF; index++) { - if (index < 0x4000) { - this.memoryReader[index] = this.memoryReadNormal; - } - else if (index < 0x8000) { - this.memoryReader[index] = this.memoryReadROM; - } - else if (index < 0x9800) { - this.memoryReader[index] = (this.cGBC) ? this.VRAMDATAReadCGBCPU : this.VRAMDATAReadDMGCPU; - } - else if (index < 0xA000) { - this.memoryReader[index] = (this.cGBC) ? this.VRAMCHRReadCGBCPU : this.VRAMCHRReadDMGCPU; - } - else if (index >= 0xA000 && index < 0xC000) { - if ((this.numRAMBanks == 1 / 16 && index < 0xA200) || this.numRAMBanks >= 1) { - if (this.cMBC7) { - this.memoryReader[index] = this.memoryReadMBC7; - } - else if (!this.cMBC3) { - this.memoryReader[index] = this.memoryReadMBC; - } - else { - //MBC3 RTC + RAM: - this.memoryReader[index] = this.memoryReadMBC3; - } - } - else { - this.memoryReader[index] = this.memoryReadBAD; - } - } - else if (index >= 0xC000 && index < 0xE000) { - if (!this.cGBC || index < 0xD000) { - this.memoryReader[index] = this.memoryReadNormal; - } - else { - this.memoryReader[index] = this.memoryReadGBCMemory; - } - } - else if (index >= 0xE000 && index < 0xFE00) { - if (!this.cGBC || index < 0xF000) { - this.memoryReader[index] = this.memoryReadECHONormal; - } - else { - this.memoryReader[index] = this.memoryReadECHOGBCMemory; - } - } - else if (index < 0xFEA0) { - this.memoryReader[index] = this.memoryReadOAM; - } - else if (this.cGBC && index >= 0xFEA0 && index < 0xFF00) { - this.memoryReader[index] = this.memoryReadNormal; - } - else if (index >= 0xFF00) { - switch (index) { - case 0xFF00: - //JOYPAD: - this.memoryHighReader[0] = this.memoryReader[0xFF00] = function (parentObj, address) { - return 0xC0 | parentObj.memory[0xFF00]; //Top nibble returns as set. - } - break; - case 0xFF01: - //SB - this.memoryHighReader[0x01] = this.memoryReader[0xFF01] = function (parentObj, address) { - return (parentObj.memory[0xFF02] < 0x80) ? parentObj.memory[0xFF01] : 0xFF; - } - break; - case 0xFF02: - //SC - if (this.cGBC) { - this.memoryHighReader[0x02] = this.memoryReader[0xFF02] = function (parentObj, address) { - return ((parentObj.serialTimer <= 0) ? 0x7C : 0xFC) | parentObj.memory[0xFF02]; - } - } - else { - this.memoryHighReader[0x02] = this.memoryReader[0xFF02] = function (parentObj, address) { - return ((parentObj.serialTimer <= 0) ? 0x7E : 0xFE) | parentObj.memory[0xFF02]; - } - } - break; - case 0xFF03: - this.memoryHighReader[0x03] = this.memoryReader[0xFF03] = this.memoryReadBAD; - break; - case 0xFF04: - //DIV - this.memoryHighReader[0x04] = this.memoryReader[0xFF04] = function (parentObj, address) { - parentObj.memory[0xFF04] = (parentObj.memory[0xFF04] + (parentObj.DIVTicks >> 8)) & 0xFF; - parentObj.DIVTicks &= 0xFF; - return parentObj.memory[0xFF04]; - - } - break; - case 0xFF05: - case 0xFF06: - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF07: - this.memoryHighReader[0x07] = this.memoryReader[0xFF07] = function (parentObj, address) { - return 0xF8 | parentObj.memory[0xFF07]; - } - break; - case 0xFF08: - case 0xFF09: - case 0xFF0A: - case 0xFF0B: - case 0xFF0C: - case 0xFF0D: - case 0xFF0E: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadBAD; - break; - case 0xFF0F: - //IF - this.memoryHighReader[0x0F] = this.memoryReader[0xFF0F] = function (parentObj, address) { - return 0xE0 | parentObj.interruptsRequested; - } - break; - case 0xFF10: - this.memoryHighReader[0x10] = this.memoryReader[0xFF10] = function (parentObj, address) { - return 0x80 | parentObj.memory[0xFF10]; - } - break; - case 0xFF11: - this.memoryHighReader[0x11] = this.memoryReader[0xFF11] = function (parentObj, address) { - return 0x3F | parentObj.memory[0xFF11]; - } - break; - case 0xFF12: - this.memoryHighReader[0x12] = this.memoryHighReadNormal; - this.memoryReader[0xFF12] = this.memoryReadNormal; - break; - case 0xFF13: - this.memoryHighReader[0x13] = this.memoryReader[0xFF13] = this.memoryReadBAD; - break; - case 0xFF14: - this.memoryHighReader[0x14] = this.memoryReader[0xFF14] = function (parentObj, address) { - return 0xBF | parentObj.memory[0xFF14]; - } - break; - case 0xFF15: - this.memoryHighReader[0x15] = this.memoryReadBAD; - this.memoryReader[0xFF15] = this.memoryReadBAD; - break; - case 0xFF16: - this.memoryHighReader[0x16] = this.memoryReader[0xFF16] = function (parentObj, address) { - return 0x3F | parentObj.memory[0xFF16]; - } - break; - case 0xFF17: - this.memoryHighReader[0x17] = this.memoryHighReadNormal; - this.memoryReader[0xFF17] = this.memoryReadNormal; - break; - case 0xFF18: - this.memoryHighReader[0x18] = this.memoryReader[0xFF18] = this.memoryReadBAD; - break; - case 0xFF19: - this.memoryHighReader[0x19] = this.memoryReader[0xFF19] = function (parentObj, address) { - return 0xBF | parentObj.memory[0xFF19]; - } - break; - case 0xFF1A: - this.memoryHighReader[0x1A] = this.memoryReader[0xFF1A] = function (parentObj, address) { - return 0x7F | parentObj.memory[0xFF1A]; - } - break; - case 0xFF1B: - this.memoryHighReader[0x1B] = this.memoryReader[0xFF1B] = this.memoryReadBAD; - break; - case 0xFF1C: - this.memoryHighReader[0x1C] = this.memoryReader[0xFF1C] = function (parentObj, address) { - return 0x9F | parentObj.memory[0xFF1C]; - } - break; - case 0xFF1D: - this.memoryHighReader[0x1D] = this.memoryReader[0xFF1D] = this.memoryReadBAD; - break; - case 0xFF1E: - this.memoryHighReader[0x1E] = this.memoryReader[0xFF1E] = function (parentObj, address) { - return 0xBF | parentObj.memory[0xFF1E]; - } - break; - case 0xFF1F: - case 0xFF20: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadBAD; - break; - case 0xFF21: - case 0xFF22: - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF23: - this.memoryHighReader[0x23] = this.memoryReader[0xFF23] = function (parentObj, address) { - return 0xBF | parentObj.memory[0xFF23]; - } - break; - case 0xFF24: - case 0xFF25: - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF26: - this.memoryHighReader[0x26] = this.memoryReader[0xFF26] = function (parentObj, address) { - parentObj.audioJIT(); - return 0x70 | parentObj.memory[0xFF26]; - } - break; - case 0xFF27: - case 0xFF28: - case 0xFF29: - case 0xFF2A: - case 0xFF2B: - case 0xFF2C: - case 0xFF2D: - case 0xFF2E: - case 0xFF2F: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadBAD; - break; - case 0xFF30: - case 0xFF31: - case 0xFF32: - case 0xFF33: - case 0xFF34: - case 0xFF35: - case 0xFF36: - case 0xFF37: - case 0xFF38: - case 0xFF39: - case 0xFF3A: - case 0xFF3B: - case 0xFF3C: - case 0xFF3D: - case 0xFF3E: - case 0xFF3F: - this.memoryReader[index] = function (parentObj, address) { - return (parentObj.channel3canPlay) ? parentObj.memory[0xFF00 | (parentObj.channel3lastSampleLookup >> 1)] : parentObj.memory[address]; - } - this.memoryHighReader[index & 0xFF] = function (parentObj, address) { - return (parentObj.channel3canPlay) ? parentObj.memory[0xFF00 | (parentObj.channel3lastSampleLookup >> 1)] : parentObj.memory[0xFF00 | address]; - } - break; - case 0xFF40: - this.memoryHighReader[0x40] = this.memoryHighReadNormal; - this.memoryReader[0xFF40] = this.memoryReadNormal; - break; - case 0xFF41: - this.memoryHighReader[0x41] = this.memoryReader[0xFF41] = function (parentObj, address) { - return 0x80 | parentObj.memory[0xFF41] | parentObj.modeSTAT; - } - break; - case 0xFF42: - this.memoryHighReader[0x42] = this.memoryReader[0xFF42] = function (parentObj, address) { - return parentObj.backgroundY; - } - break; - case 0xFF43: - this.memoryHighReader[0x43] = this.memoryReader[0xFF43] = function (parentObj, address) { - return parentObj.backgroundX; - } - break; - case 0xFF44: - this.memoryHighReader[0x44] = this.memoryReader[0xFF44] = function (parentObj, address) { - return ((parentObj.LCDisOn) ? parentObj.memory[0xFF44] : 0); - } - break; - case 0xFF45: - case 0xFF46: - case 0xFF47: - case 0xFF48: - case 0xFF49: - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF4A: - //WY - this.memoryHighReader[0x4A] = this.memoryReader[0xFF4A] = function (parentObj, address) { - return parentObj.windowY; - } - break; - case 0xFF4B: - this.memoryHighReader[0x4B] = this.memoryHighReadNormal; - this.memoryReader[0xFF4B] = this.memoryReadNormal; - break; - case 0xFF4C: - this.memoryHighReader[0x4C] = this.memoryReader[0xFF4C] = this.memoryReadBAD; - break; - case 0xFF4D: - this.memoryHighReader[0x4D] = this.memoryHighReadNormal; - this.memoryReader[0xFF4D] = this.memoryReadNormal; - break; - case 0xFF4E: - this.memoryHighReader[0x4E] = this.memoryReader[0xFF4E] = this.memoryReadBAD; - break; - case 0xFF4F: - this.memoryHighReader[0x4F] = this.memoryReader[0xFF4F] = function (parentObj, address) { - return parentObj.currVRAMBank; - } - break; - case 0xFF50: - case 0xFF51: - case 0xFF52: - case 0xFF53: - case 0xFF54: - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF55: - if (this.cGBC) { - this.memoryHighReader[0x55] = this.memoryReader[0xFF55] = function (parentObj, address) { - if (!parentObj.LCDisOn && parentObj.hdmaRunning) { //Undocumented behavior alert: HDMA becomes GDMA when LCD is off (Worms Armageddon Fix). - //DMA - parentObj.DMAWrite((parentObj.memory[0xFF55] & 0x7F) + 1); - parentObj.memory[0xFF55] = 0xFF; //Transfer completed. - parentObj.hdmaRunning = false; - } - return parentObj.memory[0xFF55]; - } - } - else { - this.memoryReader[0xFF55] = this.memoryReadNormal; - this.memoryHighReader[0x55] = this.memoryHighReadNormal; - } - break; - case 0xFF56: - if (this.cGBC) { - this.memoryHighReader[0x56] = this.memoryReader[0xFF56] = function (parentObj, address) { - //Return IR "not connected" status: - return 0x3C | ((parentObj.memory[0xFF56] >= 0xC0) ? (0x2 | (parentObj.memory[0xFF56] & 0xC1)) : (parentObj.memory[0xFF56] & 0xC3)); - } - } - else { - this.memoryReader[0xFF56] = this.memoryReadNormal; - this.memoryHighReader[0x56] = this.memoryHighReadNormal; - } - break; - case 0xFF57: - case 0xFF58: - case 0xFF59: - case 0xFF5A: - case 0xFF5B: - case 0xFF5C: - case 0xFF5D: - case 0xFF5E: - case 0xFF5F: - case 0xFF60: - case 0xFF61: - case 0xFF62: - case 0xFF63: - case 0xFF64: - case 0xFF65: - case 0xFF66: - case 0xFF67: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadBAD; - break; - case 0xFF68: - case 0xFF69: - case 0xFF6A: - case 0xFF6B: - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF6C: - if (this.cGBC) { - this.memoryHighReader[0x6C] = this.memoryReader[0xFF6C] = function (parentObj, address) { - return 0xFE | parentObj.memory[0xFF6C]; - } - } - else { - this.memoryHighReader[0x6C] = this.memoryReader[0xFF6C] = this.memoryReadBAD; - } - break; - case 0xFF6D: - case 0xFF6E: - case 0xFF6F: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadBAD; - break; - case 0xFF70: - if (this.cGBC) { - //SVBK - this.memoryHighReader[0x70] = this.memoryReader[0xFF70] = function (parentObj, address) { - return 0x40 | parentObj.memory[0xFF70]; - } - } - else { - this.memoryHighReader[0x70] = this.memoryReader[0xFF70] = this.memoryReadBAD; - } - break; - case 0xFF71: - this.memoryHighReader[0x71] = this.memoryReader[0xFF71] = this.memoryReadBAD; - break; - case 0xFF72: - case 0xFF73: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadNormal; - break; - case 0xFF74: - if (this.cGBC) { - this.memoryHighReader[0x74] = this.memoryReader[0xFF74] = this.memoryReadNormal; - } - else { - this.memoryHighReader[0x74] = this.memoryReader[0xFF74] = this.memoryReadBAD; - } - break; - case 0xFF75: - this.memoryHighReader[0x75] = this.memoryReader[0xFF75] = function (parentObj, address) { - return 0x8F | parentObj.memory[0xFF75]; - } - break; - case 0xFF76: - //Undocumented realtime PCM amplitude readback: - this.memoryHighReader[0x76] = this.memoryReader[0xFF76] = function (parentObj, address) { - parentObj.audioJIT(); - return (parentObj.channel2envelopeVolume << 4) | parentObj.channel1envelopeVolume; - } - break; - case 0xFF77: - //Undocumented realtime PCM amplitude readback: - this.memoryHighReader[0x77] = this.memoryReader[0xFF77] = function (parentObj, address) { - parentObj.audioJIT(); - return (parentObj.channel4envelopeVolume << 4) | parentObj.channel3envelopeVolume; - } - break; - case 0xFF78: - case 0xFF79: - case 0xFF7A: - case 0xFF7B: - case 0xFF7C: - case 0xFF7D: - case 0xFF7E: - case 0xFF7F: - this.memoryHighReader[index & 0xFF] = this.memoryReader[index] = this.memoryReadBAD; - break; - case 0xFFFF: - //IE - this.memoryHighReader[0xFF] = this.memoryReader[0xFFFF] = function (parentObj, address) { - return parentObj.interruptsEnabled; - } - break; - default: - this.memoryReader[index] = this.memoryReadNormal; - this.memoryHighReader[index & 0xFF] = this.memoryHighReadNormal; - } - } - else { - this.memoryReader[index] = this.memoryReadBAD; - } - } -} -GameBoyCore.prototype.memoryReadNormal = function (parentObj, address) { - return parentObj.memory[address]; -} -GameBoyCore.prototype.memoryHighReadNormal = function (parentObj, address) { - return parentObj.memory[0xFF00 | address]; -} -GameBoyCore.prototype.memoryReadROM = function (parentObj, address) { - return parentObj.ROM[parentObj.currentROMBank + address]; -} -GameBoyCore.prototype.memoryReadMBC = function (parentObj, address) { - //Switchable RAM - if (parentObj.MBCRAMBanksEnabled || settings[10]) { - return parentObj.MBCRam[address + parentObj.currMBCRAMBankPosition]; - } - //cout("Reading from disabled RAM.", 1); - return 0xFF; -} -GameBoyCore.prototype.memoryReadMBC7 = function (parentObj, address) { - //Switchable RAM - if (parentObj.MBCRAMBanksEnabled || settings[10]) { - switch (address) { - case 0xA000: - case 0xA060: - case 0xA070: - return 0; - case 0xA080: - //TODO: Gyro Control Register - return 0; - case 0xA050: - //Y High Byte - return parentObj.highY; - case 0xA040: - //Y Low Byte - return parentObj.lowY; - case 0xA030: - //X High Byte - return parentObj.highX; - case 0xA020: - //X Low Byte: - return parentObj.lowX; - default: - return parentObj.MBCRam[address + parentObj.currMBCRAMBankPosition]; - } - } - //cout("Reading from disabled RAM.", 1); - return 0xFF; -} -GameBoyCore.prototype.memoryReadMBC3 = function (parentObj, address) { - //Switchable RAM - if (parentObj.MBCRAMBanksEnabled || settings[10]) { - switch (parentObj.currMBCRAMBank) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - return parentObj.MBCRam[address + parentObj.currMBCRAMBankPosition]; - break; - case 0x08: - return parentObj.latchedSeconds; - break; - case 0x09: - return parentObj.latchedMinutes; - break; - case 0x0A: - return parentObj.latchedHours; - break; - case 0x0B: - return parentObj.latchedLDays; - break; - case 0x0C: - return (((parentObj.RTCDayOverFlow) ? 0x80 : 0) + ((parentObj.RTCHALT) ? 0x40 : 0)) + parentObj.latchedHDays; - } - } - //cout("Reading from invalid or disabled RAM.", 1); - return 0xFF; -} -GameBoyCore.prototype.memoryReadGBCMemory = function (parentObj, address) { - return parentObj.GBCMemory[address + parentObj.gbcRamBankPosition]; -} -GameBoyCore.prototype.memoryReadOAM = function (parentObj, address) { - return (parentObj.modeSTAT > 1) ? 0xFF : parentObj.memory[address]; -} -GameBoyCore.prototype.memoryReadECHOGBCMemory = function (parentObj, address) { - return parentObj.GBCMemory[address + parentObj.gbcRamBankPositionECHO]; -} -GameBoyCore.prototype.memoryReadECHONormal = function (parentObj, address) { - return parentObj.memory[address - 0x2000]; -} -GameBoyCore.prototype.memoryReadBAD = function (parentObj, address) { - return 0xFF; -} -GameBoyCore.prototype.VRAMDATAReadCGBCPU = function (parentObj, address) { - //CPU Side Reading The VRAM (Optimized for GameBoy Color) - return (parentObj.modeSTAT > 2) ? 0xFF : ((parentObj.currVRAMBank == 0) ? parentObj.memory[address] : parentObj.VRAM[address & 0x1FFF]); -} -GameBoyCore.prototype.VRAMDATAReadDMGCPU = function (parentObj, address) { - //CPU Side Reading The VRAM (Optimized for classic GameBoy) - return (parentObj.modeSTAT > 2) ? 0xFF : parentObj.memory[address]; -} -GameBoyCore.prototype.VRAMCHRReadCGBCPU = function (parentObj, address) { - //CPU Side Reading the Character Data Map: - return (parentObj.modeSTAT > 2) ? 0xFF : parentObj.BGCHRCurrentBank[address & 0x7FF]; -} -GameBoyCore.prototype.VRAMCHRReadDMGCPU = function (parentObj, address) { - //CPU Side Reading the Character Data Map: - return (parentObj.modeSTAT > 2) ? 0xFF : parentObj.BGCHRBank1[address & 0x7FF]; -} -GameBoyCore.prototype.setCurrentMBC1ROMBank = function () { - //Read the cartridge ROM data from RAM memory: - switch (this.ROMBank1offs) { - case 0x00: - case 0x20: - case 0x40: - case 0x60: - //Bank calls for 0x00, 0x20, 0x40, and 0x60 are really for 0x01, 0x21, 0x41, and 0x61. - this.currentROMBank = (this.ROMBank1offs % this.ROMBankEdge) << 14; - break; - default: - this.currentROMBank = ((this.ROMBank1offs % this.ROMBankEdge) - 1) << 14; - } -} -GameBoyCore.prototype.setCurrentMBC2AND3ROMBank = function () { - //Read the cartridge ROM data from RAM memory: - //Only map bank 0 to bank 1 here (MBC2 is like MBC1, but can only do 16 banks, so only the bank 0 quirk appears for MBC2): - this.currentROMBank = Math.max((this.ROMBank1offs % this.ROMBankEdge) - 1, 0) << 14; -} -GameBoyCore.prototype.setCurrentMBC5ROMBank = function () { - //Read the cartridge ROM data from RAM memory: - this.currentROMBank = ((this.ROMBank1offs % this.ROMBankEdge) - 1) << 14; -} -//Memory Writing: -GameBoyCore.prototype.memoryWrite = function (address, data) { - //Act as a wrapper for writing by compiled jumps to specific memory writing functions. - this.memoryWriter[address](this, address, data); -} -//0xFFXX fast path: -GameBoyCore.prototype.memoryHighWrite = function (address, data) { - //Act as a wrapper for writing by compiled jumps to specific memory writing functions. - this.memoryHighWriter[address](this, address, data); -} -GameBoyCore.prototype.memoryWriteJumpCompile = function () { - //Faster in some browsers, since we are doing less conditionals overall by implementing them in advance. - for (var index = 0x0000; index <= 0xFFFF; index++) { - if (index < 0x8000) { - if (this.cMBC1) { - if (index < 0x2000) { - this.memoryWriter[index] = this.MBCWriteEnable; - } - else if (index < 0x4000) { - this.memoryWriter[index] = this.MBC1WriteROMBank; - } - else if (index < 0x6000) { - this.memoryWriter[index] = this.MBC1WriteRAMBank; - } - else { - this.memoryWriter[index] = this.MBC1WriteType; - } - } - else if (this.cMBC2) { - if (index < 0x1000) { - this.memoryWriter[index] = this.MBCWriteEnable; - } - else if (index >= 0x2100 && index < 0x2200) { - this.memoryWriter[index] = this.MBC2WriteROMBank; - } - else { - this.memoryWriter[index] = this.cartIgnoreWrite; - } - } - else if (this.cMBC3) { - if (index < 0x2000) { - this.memoryWriter[index] = this.MBCWriteEnable; - } - else if (index < 0x4000) { - this.memoryWriter[index] = this.MBC3WriteROMBank; - } - else if (index < 0x6000) { - this.memoryWriter[index] = this.MBC3WriteRAMBank; - } - else { - this.memoryWriter[index] = this.MBC3WriteRTCLatch; - } - } - else if (this.cMBC5 || this.cRUMBLE || this.cMBC7) { - if (index < 0x2000) { - this.memoryWriter[index] = this.MBCWriteEnable; - } - else if (index < 0x3000) { - this.memoryWriter[index] = this.MBC5WriteROMBankLow; - } - else if (index < 0x4000) { - this.memoryWriter[index] = this.MBC5WriteROMBankHigh; - } - else if (index < 0x6000) { - this.memoryWriter[index] = (this.cRUMBLE) ? this.RUMBLEWriteRAMBank : this.MBC5WriteRAMBank; - } - else { - this.memoryWriter[index] = this.cartIgnoreWrite; - } - } - else if (this.cHuC3) { - if (index < 0x2000) { - this.memoryWriter[index] = this.MBCWriteEnable; - } - else if (index < 0x4000) { - this.memoryWriter[index] = this.MBC3WriteROMBank; - } - else if (index < 0x6000) { - this.memoryWriter[index] = this.HuC3WriteRAMBank; - } - else { - this.memoryWriter[index] = this.cartIgnoreWrite; - } - } - else { - this.memoryWriter[index] = this.cartIgnoreWrite; - } - } - else if (index < 0x9000) { - this.memoryWriter[index] = (this.cGBC) ? this.VRAMGBCDATAWrite : this.VRAMGBDATAWrite; - } - else if (index < 0x9800) { - this.memoryWriter[index] = (this.cGBC) ? this.VRAMGBCDATAWrite : this.VRAMGBDATAUpperWrite; - } - else if (index < 0xA000) { - this.memoryWriter[index] = (this.cGBC) ? this.VRAMGBCCHRMAPWrite : this.VRAMGBCHRMAPWrite; - } - else if (index < 0xC000) { - if ((this.numRAMBanks == 1 / 16 && index < 0xA200) || this.numRAMBanks >= 1) { - if (!this.cMBC3) { - this.memoryWriter[index] = this.memoryWriteMBCRAM; - } - else { - //MBC3 RTC + RAM: - this.memoryWriter[index] = this.memoryWriteMBC3RAM; - } - } - else { - this.memoryWriter[index] = this.cartIgnoreWrite; - } - } - else if (index < 0xE000) { - if (this.cGBC && index >= 0xD000) { - this.memoryWriter[index] = this.memoryWriteGBCRAM; - } - else { - this.memoryWriter[index] = this.memoryWriteNormal; - } - } - else if (index < 0xFE00) { - if (this.cGBC && index >= 0xF000) { - this.memoryWriter[index] = this.memoryWriteECHOGBCRAM; - } - else { - this.memoryWriter[index] = this.memoryWriteECHONormal; - } - } - else if (index <= 0xFEA0) { - this.memoryWriter[index] = this.memoryWriteOAMRAM; - } - else if (index < 0xFF00) { - if (this.cGBC) { //Only GBC has access to this RAM. - this.memoryWriter[index] = this.memoryWriteNormal; - } - else { - this.memoryWriter[index] = this.cartIgnoreWrite; - } - } - else { - //Start the I/O initialization by filling in the slots as normal memory: - this.memoryWriter[index] = this.memoryWriteNormal; - this.memoryHighWriter[index & 0xFF] = this.memoryHighWriteNormal; - } - } - this.registerWriteJumpCompile(); //Compile the I/O write functions separately... -} -GameBoyCore.prototype.MBCWriteEnable = function (parentObj, address, data) { - //MBC RAM Bank Enable/Disable: - parentObj.MBCRAMBanksEnabled = ((data & 0x0F) == 0x0A); //If lower nibble is 0x0A, then enable, otherwise disable. -} -GameBoyCore.prototype.MBC1WriteROMBank = function (parentObj, address, data) { - //MBC1 ROM bank switching: - parentObj.ROMBank1offs = (parentObj.ROMBank1offs & 0x60) | (data & 0x1F); - parentObj.setCurrentMBC1ROMBank(); -} -GameBoyCore.prototype.MBC1WriteRAMBank = function (parentObj, address, data) { - //MBC1 RAM bank switching - if (parentObj.MBC1Mode) { - //4/32 Mode - parentObj.currMBCRAMBank = data & 0x03; - parentObj.currMBCRAMBankPosition = (parentObj.currMBCRAMBank << 13) - 0xA000; - } - else { - //16/8 Mode - parentObj.ROMBank1offs = ((data & 0x03) << 5) | (parentObj.ROMBank1offs & 0x1F); - parentObj.setCurrentMBC1ROMBank(); - } -} -GameBoyCore.prototype.MBC1WriteType = function (parentObj, address, data) { - //MBC1 mode setting: - parentObj.MBC1Mode = ((data & 0x1) == 0x1); - if (parentObj.MBC1Mode) { - parentObj.ROMBank1offs &= 0x1F; - parentObj.setCurrentMBC1ROMBank(); - } - else { - parentObj.currMBCRAMBank = 0; - parentObj.currMBCRAMBankPosition = -0xA000; - } -} -GameBoyCore.prototype.MBC2WriteROMBank = function (parentObj, address, data) { - //MBC2 ROM bank switching: - parentObj.ROMBank1offs = data & 0x0F; - parentObj.setCurrentMBC2AND3ROMBank(); -} -GameBoyCore.prototype.MBC3WriteROMBank = function (parentObj, address, data) { - //MBC3 ROM bank switching: - parentObj.ROMBank1offs = data & 0x7F; - parentObj.setCurrentMBC2AND3ROMBank(); -} -GameBoyCore.prototype.MBC3WriteRAMBank = function (parentObj, address, data) { - parentObj.currMBCRAMBank = data; - if (data < 4) { - //MBC3 RAM bank switching - parentObj.currMBCRAMBankPosition = (parentObj.currMBCRAMBank << 13) - 0xA000; - } -} -GameBoyCore.prototype.MBC3WriteRTCLatch = function (parentObj, address, data) { - if (data == 0) { - parentObj.RTCisLatched = false; - } - else if (!parentObj.RTCisLatched) { - //Copy over the current RTC time for reading. - parentObj.RTCisLatched = true; - parentObj.latchedSeconds = parentObj.RTCSeconds | 0; - parentObj.latchedMinutes = parentObj.RTCMinutes; - parentObj.latchedHours = parentObj.RTCHours; - parentObj.latchedLDays = (parentObj.RTCDays & 0xFF); - parentObj.latchedHDays = parentObj.RTCDays >> 8; - } -} -GameBoyCore.prototype.MBC5WriteROMBankLow = function (parentObj, address, data) { - //MBC5 ROM bank switching: - parentObj.ROMBank1offs = (parentObj.ROMBank1offs & 0x100) | data; - parentObj.setCurrentMBC5ROMBank(); -} -GameBoyCore.prototype.MBC5WriteROMBankHigh = function (parentObj, address, data) { - //MBC5 ROM bank switching (by least significant bit): - parentObj.ROMBank1offs = ((data & 0x01) << 8) | (parentObj.ROMBank1offs & 0xFF); - parentObj.setCurrentMBC5ROMBank(); -} -GameBoyCore.prototype.MBC5WriteRAMBank = function (parentObj, address, data) { - //MBC5 RAM bank switching - parentObj.currMBCRAMBank = data & 0xF; - parentObj.currMBCRAMBankPosition = (parentObj.currMBCRAMBank << 13) - 0xA000; -} -GameBoyCore.prototype.RUMBLEWriteRAMBank = function (parentObj, address, data) { - //MBC5 RAM bank switching - //Like MBC5, but bit 3 of the lower nibble is used for rumbling and bit 2 is ignored. - parentObj.currMBCRAMBank = data & 0x03; - parentObj.currMBCRAMBankPosition = (parentObj.currMBCRAMBank << 13) - 0xA000; -} -GameBoyCore.prototype.HuC3WriteRAMBank = function (parentObj, address, data) { - //HuC3 RAM bank switching - parentObj.currMBCRAMBank = data & 0x03; - parentObj.currMBCRAMBankPosition = (parentObj.currMBCRAMBank << 13) - 0xA000; -} -GameBoyCore.prototype.cartIgnoreWrite = function (parentObj, address, data) { - //We might have encountered illegal RAM writing or such, so just do nothing... -} -GameBoyCore.prototype.memoryWriteNormal = function (parentObj, address, data) { - parentObj.memory[address] = data; -} -GameBoyCore.prototype.memoryHighWriteNormal = function (parentObj, address, data) { - parentObj.memory[0xFF00 | address] = data; -} -GameBoyCore.prototype.memoryWriteMBCRAM = function (parentObj, address, data) { - if (parentObj.MBCRAMBanksEnabled || settings[10]) { - parentObj.MBCRam[address + parentObj.currMBCRAMBankPosition] = data; - } -} -GameBoyCore.prototype.memoryWriteMBC3RAM = function (parentObj, address, data) { - if (parentObj.MBCRAMBanksEnabled || settings[10]) { - switch (parentObj.currMBCRAMBank) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - parentObj.MBCRam[address + parentObj.currMBCRAMBankPosition] = data; - break; - case 0x08: - if (data < 60) { - parentObj.RTCSeconds = data; - } - else { - cout("(Bank #" + parentObj.currMBCRAMBank + ") RTC write out of range: " + data, 1); - } - break; - case 0x09: - if (data < 60) { - parentObj.RTCMinutes = data; - } - else { - cout("(Bank #" + parentObj.currMBCRAMBank + ") RTC write out of range: " + data, 1); - } - break; - case 0x0A: - if (data < 24) { - parentObj.RTCHours = data; - } - else { - cout("(Bank #" + parentObj.currMBCRAMBank + ") RTC write out of range: " + data, 1); - } - break; - case 0x0B: - parentObj.RTCDays = (data & 0xFF) | (parentObj.RTCDays & 0x100); - break; - case 0x0C: - parentObj.RTCDayOverFlow = (data > 0x7F); - parentObj.RTCHalt = (data & 0x40) == 0x40; - parentObj.RTCDays = ((data & 0x1) << 8) | (parentObj.RTCDays & 0xFF); - break; - default: - cout("Invalid MBC3 bank address selected: " + parentObj.currMBCRAMBank, 0); - } - } -} -GameBoyCore.prototype.memoryWriteGBCRAM = function (parentObj, address, data) { - parentObj.GBCMemory[address + parentObj.gbcRamBankPosition] = data; -} -GameBoyCore.prototype.memoryWriteOAMRAM = function (parentObj, address, data) { - if (parentObj.modeSTAT < 2) { //OAM RAM cannot be written to in mode 2 & 3 - if (parentObj.memory[address] != data) { - parentObj.graphicsJIT(); - parentObj.memory[address] = data; - } - } -} -GameBoyCore.prototype.memoryWriteECHOGBCRAM = function (parentObj, address, data) { - parentObj.GBCMemory[address + parentObj.gbcRamBankPositionECHO] = data; -} -GameBoyCore.prototype.memoryWriteECHONormal = function (parentObj, address, data) { - parentObj.memory[address - 0x2000] = data; -} -GameBoyCore.prototype.VRAMGBDATAWrite = function (parentObj, address, data) { - if (parentObj.modeSTAT < 3) { //VRAM cannot be written to during mode 3 - if (parentObj.memory[address] != data) { - //JIT the graphics render queue: - parentObj.graphicsJIT(); - parentObj.memory[address] = data; - parentObj.generateGBOAMTileLine(address); - } - } -} -GameBoyCore.prototype.VRAMGBDATAUpperWrite = function (parentObj, address, data) { - if (parentObj.modeSTAT < 3) { //VRAM cannot be written to during mode 3 - if (parentObj.memory[address] != data) { - //JIT the graphics render queue: - parentObj.graphicsJIT(); - parentObj.memory[address] = data; - parentObj.generateGBTileLine(address); - } - } -} -GameBoyCore.prototype.VRAMGBCDATAWrite = function (parentObj, address, data) { - if (parentObj.modeSTAT < 3) { //VRAM cannot be written to during mode 3 - if (parentObj.currVRAMBank == 0) { - if (parentObj.memory[address] != data) { - //JIT the graphics render queue: - parentObj.graphicsJIT(); - parentObj.memory[address] = data; - parentObj.generateGBCTileLineBank1(address); - } - } - else { - address &= 0x1FFF; - if (parentObj.VRAM[address] != data) { - //JIT the graphics render queue: - parentObj.graphicsJIT(); - parentObj.VRAM[address] = data; - parentObj.generateGBCTileLineBank2(address); - } - } - } -} -GameBoyCore.prototype.VRAMGBCHRMAPWrite = function (parentObj, address, data) { - if (parentObj.modeSTAT < 3) { //VRAM cannot be written to during mode 3 - address &= 0x7FF; - if (parentObj.BGCHRBank1[address] != data) { - //JIT the graphics render queue: - parentObj.graphicsJIT(); - parentObj.BGCHRBank1[address] = data; - } - } -} -GameBoyCore.prototype.VRAMGBCCHRMAPWrite = function (parentObj, address, data) { - if (parentObj.modeSTAT < 3) { //VRAM cannot be written to during mode 3 - address &= 0x7FF; - if (parentObj.BGCHRCurrentBank[address] != data) { - //JIT the graphics render queue: - parentObj.graphicsJIT(); - parentObj.BGCHRCurrentBank[address] = data; - } - } -} -GameBoyCore.prototype.DMAWrite = function (tilesToTransfer) { - if (!this.halt) { - //Clock the CPU for the DMA transfer (CPU is halted during the transfer): - this.CPUTicks += 4 | ((tilesToTransfer << 5) << this.doubleSpeedShifter); - } - //Source address of the transfer: - var source = (this.memory[0xFF51] << 8) | this.memory[0xFF52]; - //Destination address in the VRAM memory range: - var destination = (this.memory[0xFF53] << 8) | this.memory[0xFF54]; - //Creating some references: - var memoryReader = this.memoryReader; - //JIT the graphics render queue: - this.graphicsJIT(); - var memory = this.memory; - //Determining which bank we're working on so we can optimize: - if (this.currVRAMBank == 0) { - //DMA transfer for VRAM bank 0: - do { - if (destination < 0x1800) { - memory[0x8000 | destination] = memoryReader[source](this, source++); - memory[0x8001 | destination] = memoryReader[source](this, source++); - memory[0x8002 | destination] = memoryReader[source](this, source++); - memory[0x8003 | destination] = memoryReader[source](this, source++); - memory[0x8004 | destination] = memoryReader[source](this, source++); - memory[0x8005 | destination] = memoryReader[source](this, source++); - memory[0x8006 | destination] = memoryReader[source](this, source++); - memory[0x8007 | destination] = memoryReader[source](this, source++); - memory[0x8008 | destination] = memoryReader[source](this, source++); - memory[0x8009 | destination] = memoryReader[source](this, source++); - memory[0x800A | destination] = memoryReader[source](this, source++); - memory[0x800B | destination] = memoryReader[source](this, source++); - memory[0x800C | destination] = memoryReader[source](this, source++); - memory[0x800D | destination] = memoryReader[source](this, source++); - memory[0x800E | destination] = memoryReader[source](this, source++); - memory[0x800F | destination] = memoryReader[source](this, source++); - this.generateGBCTileBank1(destination); - destination += 0x10; - } - else { - destination &= 0x7F0; - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - this.BGCHRBank1[destination++] = memoryReader[source](this, source++); - destination = (destination + 0x1800) & 0x1FF0; - } - source &= 0xFFF0; - --tilesToTransfer; - } while (tilesToTransfer > 0); - } - else { - var VRAM = this.VRAM; - //DMA transfer for VRAM bank 1: - do { - if (destination < 0x1800) { - VRAM[destination] = memoryReader[source](this, source++); - VRAM[destination | 0x1] = memoryReader[source](this, source++); - VRAM[destination | 0x2] = memoryReader[source](this, source++); - VRAM[destination | 0x3] = memoryReader[source](this, source++); - VRAM[destination | 0x4] = memoryReader[source](this, source++); - VRAM[destination | 0x5] = memoryReader[source](this, source++); - VRAM[destination | 0x6] = memoryReader[source](this, source++); - VRAM[destination | 0x7] = memoryReader[source](this, source++); - VRAM[destination | 0x8] = memoryReader[source](this, source++); - VRAM[destination | 0x9] = memoryReader[source](this, source++); - VRAM[destination | 0xA] = memoryReader[source](this, source++); - VRAM[destination | 0xB] = memoryReader[source](this, source++); - VRAM[destination | 0xC] = memoryReader[source](this, source++); - VRAM[destination | 0xD] = memoryReader[source](this, source++); - VRAM[destination | 0xE] = memoryReader[source](this, source++); - VRAM[destination | 0xF] = memoryReader[source](this, source++); - this.generateGBCTileBank2(destination); - destination += 0x10; - } - else { - destination &= 0x7F0; - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - this.BGCHRBank2[destination++] = memoryReader[source](this, source++); - destination = (destination + 0x1800) & 0x1FF0; - } - source &= 0xFFF0; - --tilesToTransfer; - } while (tilesToTransfer > 0); - } - //Update the HDMA registers to their next addresses: - memory[0xFF51] = source >> 8; - memory[0xFF52] = source & 0xF0; - memory[0xFF53] = destination >> 8; - memory[0xFF54] = destination & 0xF0; -} -GameBoyCore.prototype.registerWriteJumpCompile = function () { - //I/O Registers (GB + GBC): - //JoyPad - this.memoryHighWriter[0] = this.memoryWriter[0xFF00] = function (parentObj, address, data) { - parentObj.memory[0xFF00] = (data & 0x30) | ((((data & 0x20) == 0) ? (parentObj.JoyPad >> 4) : 0xF) & (((data & 0x10) == 0) ? (parentObj.JoyPad & 0xF) : 0xF)); - } - //SB (Serial Transfer Data) - this.memoryHighWriter[0x1] = this.memoryWriter[0xFF01] = function (parentObj, address, data) { - if (parentObj.memory[0xFF02] < 0x80) { //Cannot write while a serial transfer is active. - parentObj.memory[0xFF01] = data; - } - } - //SC (Serial Transfer Control): - this.memoryHighWriter[0x2] = this.memoryHighWriteNormal; - this.memoryWriter[0xFF02] = this.memoryWriteNormal; - //Unmapped I/O: - this.memoryHighWriter[0x3] = this.memoryWriter[0xFF03] = this.cartIgnoreWrite; - //DIV - this.memoryHighWriter[0x4] = this.memoryWriter[0xFF04] = function (parentObj, address, data) { - parentObj.DIVTicks &= 0xFF; //Update DIV for realignment. - parentObj.memory[0xFF04] = 0; - } - //TIMA - this.memoryHighWriter[0x5] = this.memoryWriter[0xFF05] = function (parentObj, address, data) { - parentObj.memory[0xFF05] = data; - } - //TMA - this.memoryHighWriter[0x6] = this.memoryWriter[0xFF06] = function (parentObj, address, data) { - parentObj.memory[0xFF06] = data; - } - //TAC - this.memoryHighWriter[0x7] = this.memoryWriter[0xFF07] = function (parentObj, address, data) { - parentObj.memory[0xFF07] = data & 0x07; - parentObj.TIMAEnabled = (data & 0x04) == 0x04; - parentObj.TACClocker = Math.pow(4, ((data & 0x3) != 0) ? (data & 0x3) : 4) << 2; //TODO: Find a way to not make a conditional in here... - } - //Unmapped I/O: - this.memoryHighWriter[0x8] = this.memoryWriter[0xFF08] = this.cartIgnoreWrite; - this.memoryHighWriter[0x9] = this.memoryWriter[0xFF09] = this.cartIgnoreWrite; - this.memoryHighWriter[0xA] = this.memoryWriter[0xFF0A] = this.cartIgnoreWrite; - this.memoryHighWriter[0xB] = this.memoryWriter[0xFF0B] = this.cartIgnoreWrite; - this.memoryHighWriter[0xC] = this.memoryWriter[0xFF0C] = this.cartIgnoreWrite; - this.memoryHighWriter[0xD] = this.memoryWriter[0xFF0D] = this.cartIgnoreWrite; - this.memoryHighWriter[0xE] = this.memoryWriter[0xFF0E] = this.cartIgnoreWrite; - //IF (Interrupt Request) - this.memoryHighWriter[0xF] = this.memoryWriter[0xFF0F] = function (parentObj, address, data) { - parentObj.interruptsRequested = data; - parentObj.checkIRQMatching(); - } - //NR10: - this.memoryHighWriter[0x10] = this.memoryWriter[0xFF10] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (parentObj.channel1decreaseSweep && (data & 0x08) == 0) { - if (parentObj.channel1Swept) { - parentObj.channel1SweepFault = true; - } - } - parentObj.channel1lastTimeSweep = (data & 0x70) >> 4; - parentObj.channel1frequencySweepDivider = data & 0x07; - parentObj.channel1decreaseSweep = ((data & 0x08) == 0x08); - parentObj.memory[0xFF10] = data; - parentObj.channel1EnableCheck(); - } - } - //NR11: - this.memoryHighWriter[0x11] = this.memoryWriter[0xFF11] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled || !parentObj.cGBC) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - } - else { - data &= 0x3F; - } - parentObj.channel1CachedDuty = parentObj.dutyLookup[data >> 6]; - parentObj.channel1totalLength = 0x40 - (data & 0x3F); - parentObj.memory[0xFF11] = data; - parentObj.channel1EnableCheck(); - } - } - //NR12: - this.memoryHighWriter[0x12] = this.memoryWriter[0xFF12] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (parentObj.channel1Enabled && parentObj.channel1envelopeSweeps == 0) { - //Zombie Volume PAPU Bug: - if (((parentObj.memory[0xFF12] ^ data) & 0x8) == 0x8) { - if ((parentObj.memory[0xFF12] & 0x8) == 0) { - if ((parentObj.memory[0xFF12] & 0x7) == 0x7) { - parentObj.channel1envelopeVolume += 2; - } - else { - ++parentObj.channel1envelopeVolume; - } - } - parentObj.channel1envelopeVolume = (16 - parentObj.channel1envelopeVolume) & 0xF; - } - else if ((parentObj.memory[0xFF12] & 0xF) == 0x8) { - parentObj.channel1envelopeVolume = (1 + parentObj.channel1envelopeVolume) & 0xF; - } - parentObj.channel1OutputLevelCache(); - } - parentObj.channel1envelopeType = ((data & 0x08) == 0x08); - parentObj.memory[0xFF12] = data; - parentObj.channel1VolumeEnableCheck(); - } - } - //NR13: - this.memoryHighWriter[0x13] = this.memoryWriter[0xFF13] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - parentObj.channel1frequency = (parentObj.channel1frequency & 0x700) | data; - parentObj.channel1FrequencyTracker = (0x800 - parentObj.channel1frequency) << 2; - } - } - //NR14: - this.memoryHighWriter[0x14] = this.memoryWriter[0xFF14] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - parentObj.channel1consecutive = ((data & 0x40) == 0x0); - parentObj.channel1frequency = ((data & 0x7) << 8) | (parentObj.channel1frequency & 0xFF); - parentObj.channel1FrequencyTracker = (0x800 - parentObj.channel1frequency) << 2; - if (data > 0x7F) { - //Reload 0xFF10: - parentObj.channel1timeSweep = parentObj.channel1lastTimeSweep; - parentObj.channel1Swept = false; - //Reload 0xFF12: - var nr12 = parentObj.memory[0xFF12]; - parentObj.channel1envelopeVolume = nr12 >> 4; - parentObj.channel1OutputLevelCache(); - parentObj.channel1envelopeSweepsLast = (nr12 & 0x7) - 1; - if (parentObj.channel1totalLength == 0) { - parentObj.channel1totalLength = 0x40; - } - if (parentObj.channel1lastTimeSweep > 0 || parentObj.channel1frequencySweepDivider > 0) { - parentObj.memory[0xFF26] |= 0x1; - } - else { - parentObj.memory[0xFF26] &= 0xFE; - } - if ((data & 0x40) == 0x40) { - parentObj.memory[0xFF26] |= 0x1; - } - parentObj.channel1ShadowFrequency = parentObj.channel1frequency; - //Reset frequency overflow check + frequency sweep type check: - parentObj.channel1SweepFault = false; - //Supposed to run immediately: - parentObj.channel1AudioSweepPerformDummy(); - } - parentObj.channel1EnableCheck(); - parentObj.memory[0xFF14] = data; - } - } - //NR20 (Unused I/O): - this.memoryHighWriter[0x15] = this.memoryWriter[0xFF15] = this.cartIgnoreWrite; - //NR21: - this.memoryHighWriter[0x16] = this.memoryWriter[0xFF16] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled || !parentObj.cGBC) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - } - else { - data &= 0x3F; - } - parentObj.channel2CachedDuty = parentObj.dutyLookup[data >> 6]; - parentObj.channel2totalLength = 0x40 - (data & 0x3F); - parentObj.memory[0xFF16] = data; - parentObj.channel2EnableCheck(); - } - } - //NR22: - this.memoryHighWriter[0x17] = this.memoryWriter[0xFF17] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (parentObj.channel2Enabled && parentObj.channel2envelopeSweeps == 0) { - //Zombie Volume PAPU Bug: - if (((parentObj.memory[0xFF17] ^ data) & 0x8) == 0x8) { - if ((parentObj.memory[0xFF17] & 0x8) == 0) { - if ((parentObj.memory[0xFF17] & 0x7) == 0x7) { - parentObj.channel2envelopeVolume += 2; - } - else { - ++parentObj.channel2envelopeVolume; - } - } - parentObj.channel2envelopeVolume = (16 - parentObj.channel2envelopeVolume) & 0xF; - } - else if ((parentObj.memory[0xFF17] & 0xF) == 0x8) { - parentObj.channel2envelopeVolume = (1 + parentObj.channel2envelopeVolume) & 0xF; - } - parentObj.channel2OutputLevelCache(); - } - parentObj.channel2envelopeType = ((data & 0x08) == 0x08); - parentObj.memory[0xFF17] = data; - parentObj.channel2VolumeEnableCheck(); - } - } - //NR23: - this.memoryHighWriter[0x18] = this.memoryWriter[0xFF18] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - parentObj.channel2frequency = (parentObj.channel2frequency & 0x700) | data; - parentObj.channel2FrequencyTracker = (0x800 - parentObj.channel2frequency) << 2; - } - } - //NR24: - this.memoryHighWriter[0x19] = this.memoryWriter[0xFF19] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (data > 0x7F) { - //Reload 0xFF17: - var nr22 = parentObj.memory[0xFF17]; - parentObj.channel2envelopeVolume = nr22 >> 4; - parentObj.channel2OutputLevelCache(); - parentObj.channel2envelopeSweepsLast = (nr22 & 0x7) - 1; - if (parentObj.channel2totalLength == 0) { - parentObj.channel2totalLength = 0x40; - } - if ((data & 0x40) == 0x40) { - parentObj.memory[0xFF26] |= 0x2; - } - } - parentObj.channel2consecutive = ((data & 0x40) == 0x0); - parentObj.channel2frequency = ((data & 0x7) << 8) | (parentObj.channel2frequency & 0xFF); - parentObj.channel2FrequencyTracker = (0x800 - parentObj.channel2frequency) << 2; - parentObj.memory[0xFF19] = data; - parentObj.channel2EnableCheck(); - } - } - //NR30: - this.memoryHighWriter[0x1A] = this.memoryWriter[0xFF1A] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (!parentObj.channel3canPlay && data >= 0x80) { - parentObj.channel3lastSampleLookup = 0; - parentObj.channel3UpdateCache(); - } - parentObj.channel3canPlay = (data > 0x7F); - if (parentObj.channel3canPlay && parentObj.memory[0xFF1A] > 0x7F && !parentObj.channel3consecutive) { - parentObj.memory[0xFF26] |= 0x4; - } - parentObj.memory[0xFF1A] = data; - //parentObj.channel3EnableCheck(); - } - } - //NR31: - this.memoryHighWriter[0x1B] = this.memoryWriter[0xFF1B] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled || !parentObj.cGBC) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - } - parentObj.channel3totalLength = 0x100 - data; - parentObj.channel3EnableCheck(); - } - } - //NR32: - this.memoryHighWriter[0x1C] = this.memoryWriter[0xFF1C] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - data &= 0x60; - parentObj.memory[0xFF1C] = data; - parentObj.channel3patternType = (data == 0) ? 4 : ((data >> 5) - 1); - } - } - //NR33: - this.memoryHighWriter[0x1D] = this.memoryWriter[0xFF1D] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - parentObj.channel3frequency = (parentObj.channel3frequency & 0x700) | data; - parentObj.channel3FrequencyPeriod = (0x800 - parentObj.channel3frequency) << 1; - } - } - //NR34: - this.memoryHighWriter[0x1E] = this.memoryWriter[0xFF1E] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (data > 0x7F) { - if (parentObj.channel3totalLength == 0) { - parentObj.channel3totalLength = 0x100; - } - parentObj.channel3lastSampleLookup = 0; - if ((data & 0x40) == 0x40) { - parentObj.memory[0xFF26] |= 0x4; - } - } - parentObj.channel3consecutive = ((data & 0x40) == 0x0); - parentObj.channel3frequency = ((data & 0x7) << 8) | (parentObj.channel3frequency & 0xFF); - parentObj.channel3FrequencyPeriod = (0x800 - parentObj.channel3frequency) << 1; - parentObj.memory[0xFF1E] = data; - parentObj.channel3EnableCheck(); - } - } - //NR40 (Unused I/O): - this.memoryHighWriter[0x1F] = this.memoryWriter[0xFF1F] = this.cartIgnoreWrite; - //NR41: - this.memoryHighWriter[0x20] = this.memoryWriter[0xFF20] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled || !parentObj.cGBC) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - } - parentObj.channel4totalLength = 0x40 - (data & 0x3F); - parentObj.channel4EnableCheck(); - } - } - //NR42: - this.memoryHighWriter[0x21] = this.memoryWriter[0xFF21] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - if (parentObj.channel4Enabled && parentObj.channel4envelopeSweeps == 0) { - //Zombie Volume PAPU Bug: - if (((parentObj.memory[0xFF21] ^ data) & 0x8) == 0x8) { - if ((parentObj.memory[0xFF21] & 0x8) == 0) { - if ((parentObj.memory[0xFF21] & 0x7) == 0x7) { - parentObj.channel4envelopeVolume += 2; - } - else { - ++parentObj.channel4envelopeVolume; - } - } - parentObj.channel4envelopeVolume = (16 - parentObj.channel4envelopeVolume) & 0xF; - } - else if ((parentObj.memory[0xFF21] & 0xF) == 0x8) { - parentObj.channel4envelopeVolume = (1 + parentObj.channel4envelopeVolume) & 0xF; - } - parentObj.channel4currentVolume = parentObj.channel4envelopeVolume << parentObj.channel4VolumeShifter; - } - parentObj.channel4envelopeType = ((data & 0x08) == 0x08); - parentObj.memory[0xFF21] = data; - parentObj.channel4UpdateCache(); - parentObj.channel4VolumeEnableCheck(); - } - } - //NR43: - this.memoryHighWriter[0x22] = this.memoryWriter[0xFF22] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - parentObj.channel4FrequencyPeriod = Math.max((data & 0x7) << 4, 8) << (data >> 4); - var bitWidth = (data & 0x8); - if ((bitWidth == 0x8 && parentObj.channel4BitRange == 0x7FFF) || (bitWidth == 0 && parentObj.channel4BitRange == 0x7F)) { - parentObj.channel4lastSampleLookup = 0; - parentObj.channel4BitRange = (bitWidth == 0x8) ? 0x7F : 0x7FFF; - parentObj.channel4VolumeShifter = (bitWidth == 0x8) ? 7 : 15; - parentObj.channel4currentVolume = parentObj.channel4envelopeVolume << parentObj.channel4VolumeShifter; - parentObj.noiseSampleTable = (bitWidth == 0x8) ? parentObj.LSFR7Table : parentObj.LSFR15Table; - } - parentObj.memory[0xFF22] = data; - parentObj.channel4UpdateCache(); - } - } - //NR44: - this.memoryHighWriter[0x23] = this.memoryWriter[0xFF23] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled) { - parentObj.audioJIT(); - parentObj.memory[0xFF23] = data; - parentObj.channel4consecutive = ((data & 0x40) == 0x0); - if (data > 0x7F) { - var nr42 = parentObj.memory[0xFF21]; - parentObj.channel4envelopeVolume = nr42 >> 4; - parentObj.channel4currentVolume = parentObj.channel4envelopeVolume << parentObj.channel4VolumeShifter; - parentObj.channel4envelopeSweepsLast = (nr42 & 0x7) - 1; - if (parentObj.channel4totalLength == 0) { - parentObj.channel4totalLength = 0x40; - } - if ((data & 0x40) == 0x40) { - parentObj.memory[0xFF26] |= 0x8; - } - } - parentObj.channel4EnableCheck(); - } - } - //NR50: - this.memoryHighWriter[0x24] = this.memoryWriter[0xFF24] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled && parentObj.memory[0xFF24] != data) { - parentObj.audioJIT(); - parentObj.memory[0xFF24] = data; - parentObj.VinLeftChannelMasterVolume = ((data >> 4) & 0x07) + 1; - parentObj.VinRightChannelMasterVolume = (data & 0x07) + 1; - parentObj.mixerOutputLevelCache(); - } - } - //NR51: - this.memoryHighWriter[0x25] = this.memoryWriter[0xFF25] = function (parentObj, address, data) { - if (parentObj.soundMasterEnabled && parentObj.memory[0xFF25] != data) { - parentObj.audioJIT(); - parentObj.memory[0xFF25] = data; - parentObj.rightChannel1 = ((data & 0x01) == 0x01); - parentObj.rightChannel2 = ((data & 0x02) == 0x02); - parentObj.rightChannel3 = ((data & 0x04) == 0x04); - parentObj.rightChannel4 = ((data & 0x08) == 0x08); - parentObj.leftChannel1 = ((data & 0x10) == 0x10); - parentObj.leftChannel2 = ((data & 0x20) == 0x20); - parentObj.leftChannel3 = ((data & 0x40) == 0x40); - parentObj.leftChannel4 = (data > 0x7F); - parentObj.channel1OutputLevelCache(); - parentObj.channel2OutputLevelCache(); - parentObj.channel3OutputLevelCache(); - parentObj.channel4OutputLevelCache(); - } - } - //NR52: - this.memoryHighWriter[0x26] = this.memoryWriter[0xFF26] = function (parentObj, address, data) { - parentObj.audioJIT(); - if (!parentObj.soundMasterEnabled && data > 0x7F) { - parentObj.memory[0xFF26] = 0x80; - parentObj.soundMasterEnabled = true; - parentObj.initializeAudioStartState(); - } - else if (parentObj.soundMasterEnabled && data < 0x80) { - parentObj.memory[0xFF26] = 0; - parentObj.soundMasterEnabled = false; - //GBDev wiki says the registers are written with zeros on power off: - for (var index = 0xFF10; index < 0xFF26; index++) { - parentObj.memoryWriter[index](parentObj, index, 0); - } - } - } - //0xFF27 to 0xFF2F don't do anything... - this.memoryHighWriter[0x27] = this.memoryWriter[0xFF27] = this.cartIgnoreWrite; - this.memoryHighWriter[0x28] = this.memoryWriter[0xFF28] = this.cartIgnoreWrite; - this.memoryHighWriter[0x29] = this.memoryWriter[0xFF29] = this.cartIgnoreWrite; - this.memoryHighWriter[0x2A] = this.memoryWriter[0xFF2A] = this.cartIgnoreWrite; - this.memoryHighWriter[0x2B] = this.memoryWriter[0xFF2B] = this.cartIgnoreWrite; - this.memoryHighWriter[0x2C] = this.memoryWriter[0xFF2C] = this.cartIgnoreWrite; - this.memoryHighWriter[0x2D] = this.memoryWriter[0xFF2D] = this.cartIgnoreWrite; - this.memoryHighWriter[0x2E] = this.memoryWriter[0xFF2E] = this.cartIgnoreWrite; - this.memoryHighWriter[0x2F] = this.memoryWriter[0xFF2F] = this.cartIgnoreWrite; - //WAVE PCM RAM: - this.memoryHighWriter[0x30] = this.memoryWriter[0xFF30] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0, data); - } - this.memoryHighWriter[0x31] = this.memoryWriter[0xFF31] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x1, data); - } - this.memoryHighWriter[0x32] = this.memoryWriter[0xFF32] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x2, data); - } - this.memoryHighWriter[0x33] = this.memoryWriter[0xFF33] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x3, data); - } - this.memoryHighWriter[0x34] = this.memoryWriter[0xFF34] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x4, data); - } - this.memoryHighWriter[0x35] = this.memoryWriter[0xFF35] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x5, data); - } - this.memoryHighWriter[0x36] = this.memoryWriter[0xFF36] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x6, data); - } - this.memoryHighWriter[0x37] = this.memoryWriter[0xFF37] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x7, data); - } - this.memoryHighWriter[0x38] = this.memoryWriter[0xFF38] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x8, data); - } - this.memoryHighWriter[0x39] = this.memoryWriter[0xFF39] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0x9, data); - } - this.memoryHighWriter[0x3A] = this.memoryWriter[0xFF3A] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0xA, data); - } - this.memoryHighWriter[0x3B] = this.memoryWriter[0xFF3B] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0xB, data); - } - this.memoryHighWriter[0x3C] = this.memoryWriter[0xFF3C] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0xC, data); - } - this.memoryHighWriter[0x3D] = this.memoryWriter[0xFF3D] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0xD, data); - } - this.memoryHighWriter[0x3E] = this.memoryWriter[0xFF3E] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0xE, data); - } - this.memoryHighWriter[0x3F] = this.memoryWriter[0xFF3F] = function (parentObj, address, data) { - parentObj.channel3WriteRAM(0xF, data); - } - //SCY - this.memoryHighWriter[0x42] = this.memoryWriter[0xFF42] = function (parentObj, address, data) { - if (parentObj.backgroundY != data) { - parentObj.midScanLineJIT(); - parentObj.backgroundY = data; - } - } - //SCX - this.memoryHighWriter[0x43] = this.memoryWriter[0xFF43] = function (parentObj, address, data) { - if (parentObj.backgroundX != data) { - parentObj.midScanLineJIT(); - parentObj.backgroundX = data; - } - } - //LY - this.memoryHighWriter[0x44] = this.memoryWriter[0xFF44] = function (parentObj, address, data) { - //Read Only: - if (parentObj.LCDisOn) { - //Gambatte says to do this: - parentObj.modeSTAT = 2; - parentObj.midScanlineOffset = -1; - parentObj.totalLinesPassed = parentObj.currentX = parentObj.queuedScanLines = parentObj.lastUnrenderedLine = parentObj.LCDTicks = parentObj.STATTracker = parentObj.actualScanLine = parentObj.memory[0xFF44] = 0; - } - } - //LYC - this.memoryHighWriter[0x45] = this.memoryWriter[0xFF45] = function (parentObj, address, data) { - if (parentObj.memory[0xFF45] != data) { - parentObj.memory[0xFF45] = data; - if (parentObj.LCDisOn) { - parentObj.matchLYC(); //Get the compare of the first scan line. - } - } - } - //WY - this.memoryHighWriter[0x4A] = this.memoryWriter[0xFF4A] = function (parentObj, address, data) { - if (parentObj.windowY != data) { - parentObj.midScanLineJIT(); - parentObj.windowY = data; - } - } - //WX - this.memoryHighWriter[0x4B] = this.memoryWriter[0xFF4B] = function (parentObj, address, data) { - if (parentObj.memory[0xFF4B] != data) { - parentObj.midScanLineJIT(); - parentObj.memory[0xFF4B] = data; - parentObj.windowX = data - 7; - } - } - this.memoryHighWriter[0x72] = this.memoryWriter[0xFF72] = function (parentObj, address, data) { - parentObj.memory[0xFF72] = data; - } - this.memoryHighWriter[0x73] = this.memoryWriter[0xFF73] = function (parentObj, address, data) { - parentObj.memory[0xFF73] = data; - } - this.memoryHighWriter[0x75] = this.memoryWriter[0xFF75] = function (parentObj, address, data) { - parentObj.memory[0xFF75] = data; - } - this.memoryHighWriter[0x76] = this.memoryWriter[0xFF76] = this.cartIgnoreWrite; - this.memoryHighWriter[0x77] = this.memoryWriter[0xFF77] = this.cartIgnoreWrite; - //IE (Interrupt Enable) - this.memoryHighWriter[0xFF] = this.memoryWriter[0xFFFF] = function (parentObj, address, data) { - parentObj.interruptsEnabled = data; - parentObj.checkIRQMatching(); - } - this.recompileModelSpecificIOWriteHandling(); - this.recompileBootIOWriteHandling(); -} -GameBoyCore.prototype.recompileModelSpecificIOWriteHandling = function () { - if (this.cGBC) { - //GameBoy Color Specific I/O: - //SC (Serial Transfer Control Register) - this.memoryHighWriter[0x2] = this.memoryWriter[0xFF02] = function (parentObj, address, data) { - if (((data & 0x1) == 0x1)) { - //Internal clock: - parentObj.memory[0xFF02] = (data & 0x7F); - parentObj.serialTimer = ((data & 0x2) == 0) ? 4096 : 128; //Set the Serial IRQ counter. - parentObj.serialShiftTimer = parentObj.serialShiftTimerAllocated = ((data & 0x2) == 0) ? 512 : 16; //Set the transfer data shift counter. - } - else { - //External clock: - parentObj.memory[0xFF02] = data; - parentObj.serialShiftTimer = parentObj.serialShiftTimerAllocated = parentObj.serialTimer = 0; //Zero the timers, since we're emulating as if nothing is connected. - } - } - this.memoryHighWriter[0x40] = this.memoryWriter[0xFF40] = function (parentObj, address, data) { - if (parentObj.memory[0xFF40] != data) { - parentObj.midScanLineJIT(); - var temp_var = (data > 0x7F); - if (temp_var != parentObj.LCDisOn) { - //When the display mode changes... - parentObj.LCDisOn = temp_var; - parentObj.memory[0xFF41] &= 0x78; - parentObj.midScanlineOffset = -1; - parentObj.totalLinesPassed = parentObj.currentX = parentObj.queuedScanLines = parentObj.lastUnrenderedLine = parentObj.STATTracker = parentObj.LCDTicks = parentObj.actualScanLine = parentObj.memory[0xFF44] = 0; - if (parentObj.LCDisOn) { - parentObj.modeSTAT = 2; - parentObj.matchLYC(); //Get the compare of the first scan line. - parentObj.LCDCONTROL = parentObj.LINECONTROL; - } - else { - parentObj.modeSTAT = 0; - parentObj.LCDCONTROL = parentObj.DISPLAYOFFCONTROL; - parentObj.DisplayShowOff(); - } - parentObj.interruptsRequested &= 0xFD; - } - parentObj.gfxWindowCHRBankPosition = ((data & 0x40) == 0x40) ? 0x400 : 0; - parentObj.gfxWindowDisplay = ((data & 0x20) == 0x20); - parentObj.gfxBackgroundBankOffset = ((data & 0x10) == 0x10) ? 0 : 0x80; - parentObj.gfxBackgroundCHRBankPosition = ((data & 0x08) == 0x08) ? 0x400 : 0; - parentObj.gfxSpriteNormalHeight = ((data & 0x04) == 0); - parentObj.gfxSpriteShow = ((data & 0x02) == 0x02); - parentObj.BGPriorityEnabled = ((data & 0x01) == 0x01); - parentObj.priorityFlaggingPathRebuild(); //Special case the priority flagging as an optimization. - parentObj.memory[0xFF40] = data; - } - } - this.memoryHighWriter[0x41] = this.memoryWriter[0xFF41] = function (parentObj, address, data) { - parentObj.LYCMatchTriggerSTAT = ((data & 0x40) == 0x40); - parentObj.mode2TriggerSTAT = ((data & 0x20) == 0x20); - parentObj.mode1TriggerSTAT = ((data & 0x10) == 0x10); - parentObj.mode0TriggerSTAT = ((data & 0x08) == 0x08); - parentObj.memory[0xFF41] = data & 0x78; - } - this.memoryHighWriter[0x46] = this.memoryWriter[0xFF46] = function (parentObj, address, data) { - parentObj.memory[0xFF46] = data; - if (data < 0xE0) { - data <<= 8; - address = 0xFE00; - var stat = parentObj.modeSTAT; - parentObj.modeSTAT = 0; - var newData = 0; - do { - newData = parentObj.memoryReader[data](parentObj, data++); - if (newData != parentObj.memory[address]) { - //JIT the graphics render queue: - parentObj.modeSTAT = stat; - parentObj.graphicsJIT(); - parentObj.modeSTAT = 0; - parentObj.memory[address++] = newData; - break; - } - } while (++address < 0xFEA0); - if (address < 0xFEA0) { - do { - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - } while (address < 0xFEA0); - } - parentObj.modeSTAT = stat; - } - } - //KEY1 - this.memoryHighWriter[0x4D] = this.memoryWriter[0xFF4D] = function (parentObj, address, data) { - parentObj.memory[0xFF4D] = (data & 0x7F) | (parentObj.memory[0xFF4D] & 0x80); - } - this.memoryHighWriter[0x4F] = this.memoryWriter[0xFF4F] = function (parentObj, address, data) { - parentObj.currVRAMBank = data & 0x01; - if (parentObj.currVRAMBank > 0) { - parentObj.BGCHRCurrentBank = parentObj.BGCHRBank2; - } - else { - parentObj.BGCHRCurrentBank = parentObj.BGCHRBank1; - } - //Only writable by GBC. - } - this.memoryHighWriter[0x51] = this.memoryWriter[0xFF51] = function (parentObj, address, data) { - if (!parentObj.hdmaRunning) { - parentObj.memory[0xFF51] = data; - } - } - this.memoryHighWriter[0x52] = this.memoryWriter[0xFF52] = function (parentObj, address, data) { - if (!parentObj.hdmaRunning) { - parentObj.memory[0xFF52] = data & 0xF0; - } - } - this.memoryHighWriter[0x53] = this.memoryWriter[0xFF53] = function (parentObj, address, data) { - if (!parentObj.hdmaRunning) { - parentObj.memory[0xFF53] = data & 0x1F; - } - } - this.memoryHighWriter[0x54] = this.memoryWriter[0xFF54] = function (parentObj, address, data) { - if (!parentObj.hdmaRunning) { - parentObj.memory[0xFF54] = data & 0xF0; - } - } - this.memoryHighWriter[0x55] = this.memoryWriter[0xFF55] = function (parentObj, address, data) { - if (!parentObj.hdmaRunning) { - if ((data & 0x80) == 0) { - //DMA - parentObj.DMAWrite((data & 0x7F) + 1); - parentObj.memory[0xFF55] = 0xFF; //Transfer completed. - } - else { - //H-Blank DMA - parentObj.hdmaRunning = true; - parentObj.memory[0xFF55] = data & 0x7F; - } - } - else if ((data & 0x80) == 0) { - //Stop H-Blank DMA - parentObj.hdmaRunning = false; - parentObj.memory[0xFF55] |= 0x80; - } - else { - parentObj.memory[0xFF55] = data & 0x7F; - } - } - this.memoryHighWriter[0x68] = this.memoryWriter[0xFF68] = function (parentObj, address, data) { - parentObj.memory[0xFF69] = parentObj.gbcBGRawPalette[data & 0x3F]; - parentObj.memory[0xFF68] = data; - } - this.memoryHighWriter[0x69] = this.memoryWriter[0xFF69] = function (parentObj, address, data) { - parentObj.updateGBCBGPalette(parentObj.memory[0xFF68] & 0x3F, data); - if (parentObj.memory[0xFF68] > 0x7F) { // high bit = autoincrement - var next = ((parentObj.memory[0xFF68] + 1) & 0x3F); - parentObj.memory[0xFF68] = (next | 0x80); - parentObj.memory[0xFF69] = parentObj.gbcBGRawPalette[next]; - } - else { - parentObj.memory[0xFF69] = data; - } - } - this.memoryHighWriter[0x6A] = this.memoryWriter[0xFF6A] = function (parentObj, address, data) { - parentObj.memory[0xFF6B] = parentObj.gbcOBJRawPalette[data & 0x3F]; - parentObj.memory[0xFF6A] = data; - } - this.memoryHighWriter[0x6B] = this.memoryWriter[0xFF6B] = function (parentObj, address, data) { - parentObj.updateGBCOBJPalette(parentObj.memory[0xFF6A] & 0x3F, data); - if (parentObj.memory[0xFF6A] > 0x7F) { // high bit = autoincrement - var next = ((parentObj.memory[0xFF6A] + 1) & 0x3F); - parentObj.memory[0xFF6A] = (next | 0x80); - parentObj.memory[0xFF6B] = parentObj.gbcOBJRawPalette[next]; - } - else { - parentObj.memory[0xFF6B] = data; - } - } - //SVBK - this.memoryHighWriter[0x70] = this.memoryWriter[0xFF70] = function (parentObj, address, data) { - var addressCheck = (parentObj.memory[0xFF51] << 8) | parentObj.memory[0xFF52]; //Cannot change the RAM bank while WRAM is the source of a running HDMA. - if (!parentObj.hdmaRunning || addressCheck < 0xD000 || addressCheck >= 0xE000) { - parentObj.gbcRamBank = Math.max(data & 0x07, 1); //Bank range is from 1-7 - parentObj.gbcRamBankPosition = ((parentObj.gbcRamBank - 1) << 12) - 0xD000; - parentObj.gbcRamBankPositionECHO = parentObj.gbcRamBankPosition - 0x2000; - } - parentObj.memory[0xFF70] = data; //Bit 6 cannot be written to. - } - this.memoryHighWriter[0x74] = this.memoryWriter[0xFF74] = function (parentObj, address, data) { - parentObj.memory[0xFF74] = data; - } - } - else { - //Fill in the GameBoy Color I/O registers as normal RAM for GameBoy compatibility: - //SC (Serial Transfer Control Register) - this.memoryHighWriter[0x2] = this.memoryWriter[0xFF02] = function (parentObj, address, data) { - if (((data & 0x1) == 0x1)) { - //Internal clock: - parentObj.memory[0xFF02] = (data & 0x7F); - parentObj.serialTimer = 4096; //Set the Serial IRQ counter. - parentObj.serialShiftTimer = parentObj.serialShiftTimerAllocated = 512; //Set the transfer data shift counter. - } - else { - //External clock: - parentObj.memory[0xFF02] = data; - parentObj.serialShiftTimer = parentObj.serialShiftTimerAllocated = parentObj.serialTimer = 0; //Zero the timers, since we're emulating as if nothing is connected. - } - } - this.memoryHighWriter[0x40] = this.memoryWriter[0xFF40] = function (parentObj, address, data) { - if (parentObj.memory[0xFF40] != data) { - parentObj.midScanLineJIT(); - var temp_var = (data > 0x7F); - if (temp_var != parentObj.LCDisOn) { - //When the display mode changes... - parentObj.LCDisOn = temp_var; - parentObj.memory[0xFF41] &= 0x78; - parentObj.midScanlineOffset = -1; - parentObj.totalLinesPassed = parentObj.currentX = parentObj.queuedScanLines = parentObj.lastUnrenderedLine = parentObj.STATTracker = parentObj.LCDTicks = parentObj.actualScanLine = parentObj.memory[0xFF44] = 0; - if (parentObj.LCDisOn) { - parentObj.modeSTAT = 2; - parentObj.matchLYC(); //Get the compare of the first scan line. - parentObj.LCDCONTROL = parentObj.LINECONTROL; - } - else { - parentObj.modeSTAT = 0; - parentObj.LCDCONTROL = parentObj.DISPLAYOFFCONTROL; - parentObj.DisplayShowOff(); - } - parentObj.interruptsRequested &= 0xFD; - } - parentObj.gfxWindowCHRBankPosition = ((data & 0x40) == 0x40) ? 0x400 : 0; - parentObj.gfxWindowDisplay = (data & 0x20) == 0x20; - parentObj.gfxBackgroundBankOffset = ((data & 0x10) == 0x10) ? 0 : 0x80; - parentObj.gfxBackgroundCHRBankPosition = ((data & 0x08) == 0x08) ? 0x400 : 0; - parentObj.gfxSpriteNormalHeight = ((data & 0x04) == 0); - parentObj.gfxSpriteShow = (data & 0x02) == 0x02; - parentObj.bgEnabled = ((data & 0x01) == 0x01); - parentObj.memory[0xFF40] = data; - } - } - this.memoryHighWriter[0x41] = this.memoryWriter[0xFF41] = function (parentObj, address, data) { - parentObj.LYCMatchTriggerSTAT = ((data & 0x40) == 0x40); - parentObj.mode2TriggerSTAT = ((data & 0x20) == 0x20); - parentObj.mode1TriggerSTAT = ((data & 0x10) == 0x10); - parentObj.mode0TriggerSTAT = ((data & 0x08) == 0x08); - parentObj.memory[0xFF41] = data & 0x78; - if ((!parentObj.usedBootROM || !parentObj.usedGBCBootROM) && parentObj.LCDisOn && parentObj.modeSTAT < 2) { - parentObj.interruptsRequested |= 0x2; - parentObj.checkIRQMatching(); - } - } - this.memoryHighWriter[0x46] = this.memoryWriter[0xFF46] = function (parentObj, address, data) { - parentObj.memory[0xFF46] = data; - if (data > 0x7F && data < 0xE0) { //DMG cannot DMA from the ROM banks. - data <<= 8; - address = 0xFE00; - var stat = parentObj.modeSTAT; - parentObj.modeSTAT = 0; - var newData = 0; - do { - newData = parentObj.memoryReader[data](parentObj, data++); - if (newData != parentObj.memory[address]) { - //JIT the graphics render queue: - parentObj.modeSTAT = stat; - parentObj.graphicsJIT(); - parentObj.modeSTAT = 0; - parentObj.memory[address++] = newData; - break; - } - } while (++address < 0xFEA0); - if (address < 0xFEA0) { - do { - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - parentObj.memory[address++] = parentObj.memoryReader[data](parentObj, data++); - } while (address < 0xFEA0); - } - parentObj.modeSTAT = stat; - } - } - this.memoryHighWriter[0x47] = this.memoryWriter[0xFF47] = function (parentObj, address, data) { - if (parentObj.memory[0xFF47] != data) { - parentObj.midScanLineJIT(); - parentObj.updateGBBGPalette(data); - parentObj.memory[0xFF47] = data; - } - } - this.memoryHighWriter[0x48] = this.memoryWriter[0xFF48] = function (parentObj, address, data) { - if (parentObj.memory[0xFF48] != data) { - parentObj.midScanLineJIT(); - parentObj.updateGBOBJPalette(0, data); - parentObj.memory[0xFF48] = data; - } - } - this.memoryHighWriter[0x49] = this.memoryWriter[0xFF49] = function (parentObj, address, data) { - if (parentObj.memory[0xFF49] != data) { - parentObj.midScanLineJIT(); - parentObj.updateGBOBJPalette(4, data); - parentObj.memory[0xFF49] = data; - } - } - this.memoryHighWriter[0x4D] = this.memoryWriter[0xFF4D] = function (parentObj, address, data) { - parentObj.memory[0xFF4D] = data; - } - this.memoryHighWriter[0x4F] = this.memoryWriter[0xFF4F] = this.cartIgnoreWrite; //Not writable in DMG mode. - this.memoryHighWriter[0x55] = this.memoryWriter[0xFF55] = this.cartIgnoreWrite; - this.memoryHighWriter[0x68] = this.memoryWriter[0xFF68] = this.cartIgnoreWrite; - this.memoryHighWriter[0x69] = this.memoryWriter[0xFF69] = this.cartIgnoreWrite; - this.memoryHighWriter[0x6A] = this.memoryWriter[0xFF6A] = this.cartIgnoreWrite; - this.memoryHighWriter[0x6B] = this.memoryWriter[0xFF6B] = this.cartIgnoreWrite; - this.memoryHighWriter[0x6C] = this.memoryWriter[0xFF6C] = this.cartIgnoreWrite; - this.memoryHighWriter[0x70] = this.memoryWriter[0xFF70] = this.cartIgnoreWrite; - this.memoryHighWriter[0x74] = this.memoryWriter[0xFF74] = this.cartIgnoreWrite; - } -} -GameBoyCore.prototype.recompileBootIOWriteHandling = function () { - //Boot I/O Registers: - if (this.inBootstrap) { - this.memoryHighWriter[0x50] = this.memoryWriter[0xFF50] = function (parentObj, address, data) { - cout("Boot ROM reads blocked: Bootstrap process has ended.", 0); - parentObj.inBootstrap = false; - parentObj.disableBootROM(); //Fill in the boot ROM ranges with ROM bank 0 ROM ranges - parentObj.memory[0xFF50] = data; //Bits are sustained in memory? - } - if (this.cGBC) { - this.memoryHighWriter[0x6C] = this.memoryWriter[0xFF6C] = function (parentObj, address, data) { - if (parentObj.inBootstrap) { - parentObj.cGBC = ((data & 0x1) == 0); - //Exception to the GBC identifying code: - if (parentObj.name + parentObj.gameCode + parentObj.ROM[0x143] == "Game and Watch 50") { - parentObj.cGBC = true; - cout("Created a boot exception for Game and Watch Gallery 2 (GBC ID byte is wrong on the cartridge).", 1); - } - cout("Booted to GBC Mode: " + parentObj.cGBC, 0); - } - parentObj.memory[0xFF6C] = data; - } - } - } - else { - //Lockout the ROMs from accessing the BOOT ROM control register: - this.memoryHighWriter[0x50] = this.memoryWriter[0xFF50] = this.cartIgnoreWrite; - } -} -//Helper Functions -GameBoyCore.prototype.toTypedArray = function (baseArray, memtype) { - try { - if (settings[5]) { - return baseArray; - } - if (!baseArray || !baseArray.length) { - return []; - } - var length = baseArray.length; - switch (memtype) { - case "uint8": - var typedArrayTemp = new Uint8Array(length); - break; - case "int8": - var typedArrayTemp = new Int8Array(length); - break; - case "int32": - var typedArrayTemp = new Int32Array(length); - break; - case "float32": - var typedArrayTemp = new Float32Array(length); - } - for (var index = 0; index < length; index++) { - typedArrayTemp[index] = baseArray[index]; - } - return typedArrayTemp; - } - catch (error) { - cout("Could not convert an array to a typed array: " + error.message, 1); - return baseArray; - } -} -GameBoyCore.prototype.fromTypedArray = function (baseArray) { - try { - if (!baseArray || !baseArray.length) { - return []; - } - var arrayTemp = []; - for (var index = 0; index < baseArray.length; ++index) { - arrayTemp[index] = baseArray[index]; - } - return arrayTemp; - } - catch (error) { - cout("Conversion from a typed array failed: " + error.message, 1); - return baseArray; - } -} -GameBoyCore.prototype.getTypedArray = function (length, defaultValue, numberType) { - try { - if (settings[5]) { - throw(new Error("Settings forced typed arrays to be disabled.")); - } - switch (numberType) { - case "int8": - var arrayHandle = new Int8Array(length); - break; - case "uint8": - var arrayHandle = new Uint8Array(length); - break; - case "int32": - var arrayHandle = new Int32Array(length); - break; - case "float32": - var arrayHandle = new Float32Array(length); - } - if (defaultValue != 0) { - var index = 0; - while (index < length) { - arrayHandle[index++] = defaultValue; - } - } - } - catch (error) { - cout("Could not convert an array to a typed array: " + error.message, 1); - var arrayHandle = []; - var index = 0; - while (index < length) { - arrayHandle[index++] = defaultValue; - } - } - return arrayHandle; -} diff --git a/js-emulator/js/GameBoyIO.js b/js-emulator/js/GameBoyIO.js deleted file mode 100755 index 97236fb..0000000 --- a/js-emulator/js/GameBoyIO.js +++ /dev/null @@ -1,417 +0,0 @@ -"use strict"; -var gameboy = null; //GameBoyCore object. -var gbRunInterval = null; //GameBoyCore Timer -var settings = [ //Some settings. - window.location.href.indexOf("audio=true") > -1, //Turn on sound. - true, //Boot with boot ROM first? - false, //Give priority to GameBoy mode - 1, //Volume level set. - true, //Colorize GB mode? - false, //Disallow typed arrays? - 8, //Interval for the emulator loop. - 10, //Audio buffer minimum span amount over x interpreter iterations. - 20, //Audio buffer maximum span amount over x interpreter iterations. - false, //Override to allow for MBC1 instead of ROM only (compatibility for broken 3rd-party cartridges). - false, //Override MBC RAM disabling and always allow reading and writing to the banks. - false, //Use the GameBoy boot ROM instead of the GameBoy Color boot ROM. - false, //Scale the canvas in JS, or let the browser scale the canvas? - true, //Use image smoothing based scaling? - [true, true, true, true] //User controlled channel enables. -]; -function start(canvas, ROM) { - clearLastEmulation(); - autoSave(); //If we are about to load a new game, then save the last one... - gameboy = new GameBoyCore(canvas, ROM); - gameboy.openMBC = openSRAM; - gameboy.openRTC = openRTC; - gameboy.start(); - run(); -} -function run() { - if (GameBoyEmulatorInitialized()) { - if (!GameBoyEmulatorPlaying()) { - gameboy.stopEmulator &= 1; - cout("Starting the iterator.", 0); - var dateObj = new Date(); - gameboy.firstIteration = dateObj.getTime(); - gameboy.iterations = 0; - gbRunInterval = setInterval(function () { - if (!document.hidden && !document.msHidden && !document.mozHidden && !document.webkitHidden) { - gameboy.run(); - } - }, settings[6]); - } - else { - cout("The GameBoy core is already running.", 1); - } - } - else { - cout("GameBoy core cannot run while it has not been initialized.", 1); - } -} -function pause() { - if (GameBoyEmulatorInitialized()) { - if (GameBoyEmulatorPlaying()) { - autoSave(); - clearLastEmulation(); - } - else { - cout("GameBoy core has already been paused.", 1); - } - } - else { - cout("GameBoy core cannot be paused while it has not been initialized.", 1); - } -} -function clearLastEmulation() { - if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) { - clearInterval(gbRunInterval); - gameboy.stopEmulator |= 2; - cout("The previous emulation has been cleared.", 0); - } - else { - cout("No previous emulation was found to be cleared.", 0); - } -} -function save() { - if (GameBoyEmulatorInitialized()) { - var state_suffix = 0; - while (findValue("FREEZE_" + gameboy.name + "_" + state_suffix) != null) { - state_suffix++; - } - saveState("FREEZE_" + gameboy.name + "_" + state_suffix); - } - else { - cout("GameBoy core cannot be saved while it has not been initialized.", 1); - } -} -function saveSRAM() { - if (GameBoyEmulatorInitialized()) { - if (gameboy.cBATT) { - try { - var sram = gameboy.saveSRAMState(); - if (sram.length > 0) { - cout("Saving the SRAM...", 0); - if (findValue("SRAM_" + gameboy.name) != null) { - //Remove the outdated storage format save: - cout("Deleting the old SRAM save due to outdated format.", 0); - deleteValue("SRAM_" + gameboy.name); - } - setValue("B64_SRAM_" + gameboy.name, arrayToBase64(sram)); - } - else { - cout("SRAM could not be saved because it was empty.", 1); - } - } - catch (error) { - cout("Could not save the current emulation state(\"" + error.message + "\").", 2); - } - } - else { - cout("Cannot save a game that does not have battery backed SRAM specified.", 1); - } - saveRTC(); - } - else { - cout("GameBoy core cannot be saved while it has not been initialized.", 1); - } -} -function saveRTC() { //Execute this when SRAM is being saved as well. - if (GameBoyEmulatorInitialized()) { - if (gameboy.cTIMER) { - try { - cout("Saving the RTC...", 0); - setValue("RTC_" + gameboy.name, gameboy.saveRTCState()); - } - catch (error) { - cout("Could not save the RTC of the current emulation state(\"" + error.message + "\").", 2); - } - } - } - else { - cout("GameBoy core cannot be saved while it has not been initialized.", 1); - } -} -function autoSave() { - if (GameBoyEmulatorInitialized()) { - cout("Automatically saving the SRAM.", 0); - saveSRAM(); - saveRTC(); - } -} -function openSRAM(filename) { - try { - if (findValue("B64_SRAM_" + filename) != null) { - cout("Found a previous SRAM state (Will attempt to load).", 0); - return base64ToArray(findValue("B64_SRAM_" + filename)); - } - else if (findValue("SRAM_" + filename) != null) { - cout("Found a previous SRAM state (Will attempt to load).", 0); - return findValue("SRAM_" + filename); - } - else { - cout("Could not find any previous SRAM copy for the current ROM.", 0); - } - } - catch (error) { - cout("Could not open the SRAM of the saved emulation state.", 2); - } - return []; -} -function openRTC(filename) { - try { - if (findValue("RTC_" + filename) != null) { - cout("Found a previous RTC state (Will attempt to load).", 0); - return findValue("RTC_" + filename); - } - else { - cout("Could not find any previous RTC copy for the current ROM.", 0); - } - } - catch (error) { - cout("Could not open the RTC data of the saved emulation state.", 2); - } - return []; -} -function saveState(filename) { - if (GameBoyEmulatorInitialized()) { - try { - setValue(filename, gameboy.saveState()); - cout("Saved the current state as: " + filename, 0); - } - catch (error) { - cout("Could not save the current emulation state(\"" + error.message + "\").", 2); - } - } - else { - cout("GameBoy core cannot be saved while it has not been initialized.", 1); - } -} -function openState(filename, canvas) { - try { - if (findValue(filename) != null) { - try { - clearLastEmulation(); - cout("Attempting to run a saved emulation state.", 0); - gameboy = new GameBoyCore(canvas, ""); - gameboy.savedStateFileName = filename; - gameboy.returnFromState(findValue(filename)); - run(); - } - catch (error) { - alert(error.message + " file: " + error.fileName + " line: " + error.lineNumber); - } - } - else { - cout("Could not find the save state " + filename + "\".", 2); - } - } - catch (error) { - cout("Could not open the saved emulation state.", 2); - } -} -function import_save(blobData) { - blobData = decodeBlob(blobData); - if (blobData && blobData.blobs) { - if (blobData.blobs.length > 0) { - for (var index = 0; index < blobData.blobs.length; ++index) { - cout("Importing blob \"" + blobData.blobs[index].blobID + "\"", 0); - if (blobData.blobs[index].blobContent) { - if (blobData.blobs[index].blobID.substring(0, 5) == "SRAM_") { - setValue("B64_" + blobData.blobs[index].blobID, base64(blobData.blobs[index].blobContent)); - } - else { - setValue(blobData.blobs[index].blobID, JSON.parse(blobData.blobs[index].blobContent)); - } - } - else if (blobData.blobs[index].blobID) { - cout("Save file imported had blob \"" + blobData.blobs[index].blobID + "\" with no blob data interpretable.", 2); - } - else { - cout("Blob chunk information missing completely.", 2); - } - } - } - else { - cout("Could not decode the imported file.", 2); - } - } - else { - cout("Could not decode the imported file.", 2); - } -} -function generateBlob(keyName, encodedData) { - //Append the file format prefix: - var saveString = "EMULATOR_DATA"; - var consoleID = "GameBoy"; - //Figure out the length: - var totalLength = (saveString.length + 4 + (1 + consoleID.length)) + ((1 + keyName.length) + (4 + encodedData.length)); - //Append the total length in bytes: - saveString += to_little_endian_dword(totalLength); - //Append the console ID text's length: - saveString += to_byte(consoleID.length); - //Append the console ID text: - saveString += consoleID; - //Append the blob ID: - saveString += to_byte(keyName.length); - saveString += keyName; - //Now append the save data: - saveString += to_little_endian_dword(encodedData.length); - saveString += encodedData; - return saveString; -} -function generateMultiBlob(blobPairs) { - var consoleID = "GameBoy"; - //Figure out the initial length: - var totalLength = 13 + 4 + 1 + consoleID.length; - //Append the console ID text's length: - var saveString = to_byte(consoleID.length); - //Append the console ID text: - saveString += consoleID; - var keyName = ""; - var encodedData = ""; - //Now append all the blobs: - for (var index = 0; index < blobPairs.length; ++index) { - keyName = blobPairs[index][0]; - encodedData = blobPairs[index][1]; - //Append the blob ID: - saveString += to_byte(keyName.length); - saveString += keyName; - //Now append the save data: - saveString += to_little_endian_dword(encodedData.length); - saveString += encodedData; - //Update the total length: - totalLength += 1 + keyName.length + 4 + encodedData.length; - } - //Now add the prefix: - saveString = "EMULATOR_DATA" + to_little_endian_dword(totalLength) + saveString; - return saveString; -} -function decodeBlob(blobData) { - /*Format is as follows: - - 13 byte string "EMULATOR_DATA" - - 4 byte total size (including these 4 bytes). - - 1 byte Console type ID length - - Console type ID text of 8 bit size - blobs { - - 1 byte blob ID length - - blob ID text (Used to say what the data is (SRAM/freeze state/etc...)) - - 4 byte blob length - - blob length of 32 bit size - } - */ - var length = blobData.length; - var blobProperties = {}; - blobProperties.consoleID = null; - var blobsCount = -1; - blobProperties.blobs = []; - if (length > 17) { - if (blobData.substring(0, 13) == "EMULATOR_DATA") { - var length = Math.min(((blobData.charCodeAt(16) & 0xFF) << 24) | ((blobData.charCodeAt(15) & 0xFF) << 16) | ((blobData.charCodeAt(14) & 0xFF) << 8) | (blobData.charCodeAt(13) & 0xFF), length); - var consoleIDLength = blobData.charCodeAt(17) & 0xFF; - if (length > 17 + consoleIDLength) { - blobProperties.consoleID = blobData.substring(18, 18 + consoleIDLength); - var blobIDLength = 0; - var blobLength = 0; - for (var index = 18 + consoleIDLength; index < length;) { - blobIDLength = blobData.charCodeAt(index++) & 0xFF; - if (index + blobIDLength < length) { - blobProperties.blobs[++blobsCount] = {}; - blobProperties.blobs[blobsCount].blobID = blobData.substring(index, index + blobIDLength); - index += blobIDLength; - if (index + 4 < length) { - blobLength = ((blobData.charCodeAt(index + 3) & 0xFF) << 24) | ((blobData.charCodeAt(index + 2) & 0xFF) << 16) | ((blobData.charCodeAt(index + 1) & 0xFF) << 8) | (blobData.charCodeAt(index) & 0xFF); - index += 4; - if (index + blobLength <= length) { - blobProperties.blobs[blobsCount].blobContent = blobData.substring(index, index + blobLength); - index += blobLength; - } - else { - cout("Blob length check failed, blob determined to be incomplete.", 2); - break; - } - } - else { - cout("Blob was incomplete, bailing out.", 2); - break; - } - } - else { - cout("Blob was incomplete, bailing out.", 2); - break; - } - } - } - } - } - return blobProperties; -} -function matchKey(key) { //Maps a keyboard key to a gameboy key. - //Order: Right, Left, Up, Down, A, B, Select, Start - var keymap = ["right", "left", "up", "down", "a", "b", "select", "start"]; //Keyboard button map. - for (var index = 0; index < keymap.length; index++) { - if (keymap[index] == key) { - return index; - } - } - return -1; -} -function GameBoyEmulatorInitialized() { - return (typeof gameboy == "object" && gameboy != null); -} -function GameBoyEmulatorPlaying() { - return ((gameboy.stopEmulator & 2) == 0); -} -function GameBoyKeyDown(key) { - if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) { - GameBoyJoyPadEvent(matchKey(key), true); - } -} -function GameBoyJoyPadEvent(keycode, down) { - if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) { - if (keycode >= 0 && keycode < 8) { - gameboy.JoyPadEvent(keycode, down); - } - } -} -function GameBoyKeyUp(key) { - if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) { - GameBoyJoyPadEvent(matchKey(key), false); - } -} -function GameBoyGyroSignalHandler(e) { - if (GameBoyEmulatorInitialized() && GameBoyEmulatorPlaying()) { - if (e.gamma || e.beta) { - gameboy.GyroEvent(e.gamma * Math.PI / 180, e.beta * Math.PI / 180); - } - else { - gameboy.GyroEvent(e.x, e.y); - } - try { - e.preventDefault(); - } - catch (error) { } - } -} -//The emulator will call this to sort out the canvas properties for (re)initialization. -function initNewCanvas() { - if (GameBoyEmulatorInitialized()) { - gameboy.canvas.width = 160; - gameboy.canvas.height = 144; - } -} -//Call this when resizing the canvas: -function initNewCanvasSize() { - if (GameBoyEmulatorInitialized()) { - if (!settings[12]) { - if (gameboy.onscreenWidth != 160 || gameboy.onscreenHeight != 144) { - gameboy.initLCD(); - } - } - else { - if (gameboy.onscreenWidth != gameboy.canvas.clientWidth || gameboy.onscreenHeight != gameboy.canvas.clientHeight) { - gameboy.initLCD(); - } - } - } -} diff --git a/js-emulator/js/other/XAudioJS.swf b/js-emulator/js/other/XAudioJS.swf deleted file mode 100755 index 0b84014ecbe444607a476ac913fd66bd906e9ab1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1348 zcmV-K1-tq~S5pfC2mk`8ZyQAvKC`>_*p9D><0MYnxQ%mZXly5~K#5YTZW8yR zwgG~w3aN@G>)Ck8de`i(6PK6frN03F00s38i5H$*@dms&DFh)QA;AOu3v*`e=29*z zU*??o&Ue0Z&d#(z`5hqo9AKEhYGMQco(z0X0G92lw)FmLIn$~ern7|BwdtDcHkR`F zy}iBM-j$qXZ|CP17Z>w|h5W+893tkN$EM3#bEY#fUGxZ6xue>8!__S_BW$*1HQj5| z(>>OzdQ94A+J;A2RrB27b#A&&em*ykL{)W3v+O!^i>%Qwbd?FZd~43BS?bOn+vRhb z!JOK1{sbsuT-`Nzu~fCTc&2RdR_1D^bQ15OyAT1co(L_TDwB!moNCqc4cn?VRn(** zl^1+EL{K!ETZZn`xLs_TJEpbQO>yEC7}e&?way}5`^3J%%WwC-)MHvgYzrGYWy#G8CIlDr}JS*#sT~BtN8wW54`7_YpoyO z!GV7ldRS^!b?eRsyh#4w13({WHmmb_h(iu!==>%DTmfa*(yRXa&H5I%=?%*=I5WfE z1af>=j4igo>J5XhGMB9hN8mMaq>HNRPQzf2{TmHice&58nr5}HyH)2^o%uF|C!*Gv zX>#L6Q`5NJx4Bu`xU#vK+vTeN0avkMfgZq-b<_3lT1K>ZtAYi4E+-x z7T9h6zyn2X{&AB#Zday*iMYB`w(M?NPtFQ6j4h__T&8xE#wsU zM=MhQ-ogG9N%>NNVVdeoMSb9dArcr#({wsLnjTA^OP^1T%MltN!I07yjznW|fB3X@ zi4uttnG%Iceo82ngOr4*FHA{<`eWxpVmnSrf|7np1}I5VGDt~^k|9clgE2xA8A>it za*>h=O0twpQ8Go@@^K|t;&pbQZ3C5brRgc6@ZAb@OvpxiFp zM3;hvUuqZHmBEU%K_>UFPu-Ae!S-#aq%J4hT6}#-!qs3~OW+CvBv}rICNSVpJ0t;GgkbUIVFrf&iG zWNgC}O4Vp4If`+Q0(SzyDB2Z88*gjL^^AnZ`UO##U%?)HhT5}m)Gqf!KNp$8*o&(G z@o}L_yNHSoXI~y^=hr958|I= (XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated) && minBufferSize < maxBufferSize) ? (minBufferSize & (-XAudioJSChannelsAllocated)) : (XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated); - XAudioJSMaxBufferSize = (Math.floor(maxBufferSize) > XAudioJSMinBufferSize + XAudioJSChannelsAllocated) ? (maxBufferSize & (-XAudioJSChannelsAllocated)) : (XAudioJSMinBufferSize * XAudioJSChannelsAllocated); - this.underRunCallback = (typeof underRunCallback == "function") ? underRunCallback : function () {}; - XAudioJSVolume = (volume >= 0 && volume <= 1) ? volume : 1; - this.failureCallback = (typeof failureCallback == "function") ? failureCallback : function () { throw(new Error("XAudioJS has encountered a fatal error.")); }; - this.initializeAudio(); -} -XAudioServer.prototype.MOZWriteAudioNoCallback = function (buffer) { - //Resample before passing to the moz audio api: - var bufferLength = buffer.length; - for (var bufferIndex = 0; bufferIndex < bufferLength;) { - var sliceLength = Math.min(bufferLength - bufferIndex, XAudioJSMaxBufferSize); - for (var sliceIndex = 0; sliceIndex < sliceLength; ++sliceIndex) { - XAudioJSAudioContextSampleBuffer[sliceIndex] = buffer[bufferIndex++]; - } - var resampleLength = XAudioJSResampleControl.resampler(XAudioJSGetArraySlice(XAudioJSAudioContextSampleBuffer, sliceIndex)); - if (resampleLength > 0) { - var resampledResult = XAudioJSResampleControl.outputBuffer; - var resampledBuffer = XAudioJSGetArraySlice(resampledResult, resampleLength); - this.samplesAlreadyWritten += this.audioHandleMoz.mozWriteAudio(resampledBuffer); - } - } -} -XAudioServer.prototype.callbackBasedWriteAudioNoCallback = function (buffer) { - //Callback-centered audio APIs: - var length = buffer.length; - for (var bufferCounter = 0; bufferCounter < length && XAudioJSAudioBufferSize < XAudioJSMaxBufferSize;) { - XAudioJSAudioContextSampleBuffer[XAudioJSAudioBufferSize++] = buffer[bufferCounter++]; - } -} -/*Pass your samples into here! -Pack your samples as a one-dimenional array -With the channel samples packed uniformly. -examples: - mono - [left, left, left, left] - stereo - [left, right, left, right, left, right, left, right] -*/ -XAudioServer.prototype.writeAudio = function (buffer) { - switch (this.audioType) { - case 0: - this.MOZWriteAudioNoCallback(buffer); - this.MOZExecuteCallback(); - break; - case 2: - this.checkFlashInit(); - case 1: - case 3: - this.callbackBasedWriteAudioNoCallback(buffer); - this.callbackBasedExecuteCallback(); - break; - default: - this.failureCallback(); - } -} -/*Pass your samples into here if you don't want automatic callback calling: -Pack your samples as a one-dimenional array -With the channel samples packed uniformly. -examples: - mono - [left, left, left, left] - stereo - [left, right, left, right, left, right, left, right] -Useful in preventing infinite recursion issues with calling writeAudio inside your callback. -*/ -XAudioServer.prototype.writeAudioNoCallback = function (buffer) { - switch (this.audioType) { - case 0: - this.MOZWriteAudioNoCallback(buffer); - break; - case 2: - this.checkFlashInit(); - case 1: - case 3: - this.callbackBasedWriteAudioNoCallback(buffer); - break; - default: - this.failureCallback(); - } -} -//Developer can use this to see how many samples to write (example: minimum buffer allotment minus remaining samples left returned from this function to make sure maximum buffering is done...) -//If null is returned, then that means metric could not be done. -XAudioServer.prototype.remainingBuffer = function () { - switch (this.audioType) { - case 0: - return Math.floor((this.samplesAlreadyWritten - this.audioHandleMoz.mozCurrentSampleOffset()) * XAudioJSResampleControl.ratioWeight / XAudioJSChannelsAllocated) * XAudioJSChannelsAllocated; - case 2: - this.checkFlashInit(); - case 1: - case 3: - return (Math.floor((XAudioJSResampledSamplesLeft() * XAudioJSResampleControl.ratioWeight) / XAudioJSChannelsAllocated) * XAudioJSChannelsAllocated) + XAudioJSAudioBufferSize; - default: - this.failureCallback(); - return null; - } -} -XAudioServer.prototype.MOZExecuteCallback = function () { - //mozAudio: - var samplesRequested = XAudioJSMinBufferSize - this.remainingBuffer(); - if (samplesRequested > 0) { - this.MOZWriteAudioNoCallback(this.underRunCallback(samplesRequested)); - } -} -XAudioServer.prototype.callbackBasedExecuteCallback = function () { - //WebKit /Flash Audio: - var samplesRequested = XAudioJSMinBufferSize - this.remainingBuffer(); - if (samplesRequested > 0) { - this.callbackBasedWriteAudioNoCallback(this.underRunCallback(samplesRequested)); - } -} -//If you just want your callback called for any possible refill (Execution of callback is still conditional): -XAudioServer.prototype.executeCallback = function () { - switch (this.audioType) { - case 0: - this.MOZExecuteCallback(); - break; - case 2: - this.checkFlashInit(); - case 1: - case 3: - this.callbackBasedExecuteCallback(); - break; - default: - this.failureCallback(); - } -} -//DO NOT CALL THIS, the lib calls this internally! -XAudioServer.prototype.initializeAudio = function () { - try { - this.initializeMozAudio(); - } - catch (error) { - try { - this.initializeWebAudio(); - } - catch (error) { - try { - this.initializeMediaStream(); - } - catch (error) { - try { - this.initializeFlashAudio(); - } - catch (error) { - this.audioType = -1; - this.failureCallback(); - } - } - } - } -} -XAudioServer.prototype.initializeMediaStream = function () { - this.audioHandleMediaStream = new Audio(); - this.resetCallbackAPIAudioBuffer(XAudioJSMediaStreamSampleRate); - if (XAudioJSMediaStreamWorker) { - //WebWorker is not GC'd, so manually collect it: - XAudioJSMediaStreamWorker.terminate(); - } - XAudioJSMediaStreamWorker = new Worker(XAudioJSsourceHandle.substring(0, XAudioJSsourceHandle.length - 3) + "MediaStreamWorker.js"); - this.audioHandleMediaStreamProcessing = new ProcessedMediaStream(XAudioJSMediaStreamWorker, XAudioJSMediaStreamSampleRate, XAudioJSChannelsAllocated); - this.audioHandleMediaStream.src = this.audioHandleMediaStreamProcessing; - this.audioHandleMediaStream.volume = XAudioJSVolume; - XAudioJSMediaStreamWorker.onmessage = XAudioJSMediaStreamPushAudio; - XAudioJSMediaStreamWorker.postMessage([1, XAudioJSResampleBufferSize, XAudioJSChannelsAllocated]); - this.audioHandleMediaStream.play(); - this.audioType = 3; -} -XAudioServer.prototype.initializeMozAudio = function () { - this.audioHandleMoz = new Audio(); - this.audioHandleMoz.mozSetup(XAudioJSChannelsAllocated, XAudioJSMozAudioSampleRate); - this.audioHandleMoz.volume = XAudioJSVolume; - this.samplesAlreadyWritten = 0; - this.audioType = 0; - //if (navigator.platform != "MacIntel" && navigator.platform != "MacPPC") { - //Add some additional buffering space to workaround a moz audio api issue: - var bufferAmount = (this.XAudioJSSampleRate * XAudioJSChannelsAllocated / 10) | 0; - bufferAmount -= bufferAmount % XAudioJSChannelsAllocated; - this.samplesAlreadyWritten -= bufferAmount; - //} - this.initializeResampler(XAudioJSMozAudioSampleRate); -} -XAudioServer.prototype.initializeWebAudio = function () { - if (!XAudioJSWebAudioLaunchedContext) { - try { - XAudioJSWebAudioContextHandle = new AudioContext(); //Create a system audio context. - } - catch (error) { - XAudioJSWebAudioContextHandle = new webkitAudioContext(); //Create a system audio context. - } - XAudioJSWebAudioLaunchedContext = true; - } - if (XAudioJSWebAudioAudioNode) { - XAudioJSWebAudioAudioNode.disconnect(); - XAudioJSWebAudioAudioNode.onaudioprocess = null; - XAudioJSWebAudioAudioNode = null; - } - //Android chrome exception has stuttery audio, firefox android is fine with 2048 - if (navigator.userAgent.includes("Android") && navigator.userAgent.includes("Chrome")) { - XAudioJSSamplesPerCallback = 4096; - } - try { - XAudioJSWebAudioAudioNode = XAudioJSWebAudioContextHandle.createScriptProcessor(XAudioJSSamplesPerCallback, 0, XAudioJSChannelsAllocated); //Create the js event node. - } - catch (error) { - XAudioJSWebAudioAudioNode = XAudioJSWebAudioContextHandle.createJavaScriptNode(XAudioJSSamplesPerCallback, 0, XAudioJSChannelsAllocated); //Create the js event node. - } - XAudioJSWebAudioAudioNode.onaudioprocess = XAudioJSWebAudioEvent; - var gainNode = XAudioJSWebAudioContextHandle.createGain(); - XAudioJSWebAudioAudioNode.connect(gainNode); //Connect the audio processing event to a handling function so we can manipulate output - //Connect the audio processing event to a handling function so we can manipulate output - gainNode.connect(XAudioJSWebAudioContextHandle.destination); - // Ramp up gain to avoid pop when initialised, these can be relaxed/removed with the fixed dc offset in GameBoyCore.js no longer popping - gainNode.gain.value = 0.2; - gainNode.gain.exponentialRampToValueAtTime( - 1, - XAudioJSWebAudioContextHandle.currentTime + 0.5 - ); - - document.addEventListener("visibilitychange", function(event) { - if (document.hidden) { - // Ramp down gain to avoid pop when switching tab away - gainNode.gain.exponentialRampToValueAtTime( - 0.0001, - XAudioJSWebAudioContextHandle.currentTime + 0.5 - ); - } else { - // Ramp up gain to avoid pop when switching back to tab - gainNode.gain.exponentialRampToValueAtTime( - 1, - XAudioJSWebAudioContextHandle.currentTime + 0.5 - ); - } - }); - - this.resetCallbackAPIAudioBuffer(XAudioJSWebAudioContextHandle.sampleRate); - this.audioType = 1; - /* - Firefox has a bug in its web audio implementation... - The node may randomly stop playing on Mac OS X for no - good reason. Keep a watchdog timer to restart the failed - node if it glitches. Google Chrome never had this issue. - */ - XAudioJSWebAudioWatchDogLast = (new Date()).getTime(); - if (navigator.userAgent.indexOf('Gecko/') > -1) { - if (XAudioJSWebAudioWatchDogTimer) { - clearInterval(XAudioJSWebAudioWatchDogTimer); - } - var parentObj = this; - XAudioJSWebAudioWatchDogTimer = setInterval(function () { - var timeDiff = (new Date()).getTime() - XAudioJSWebAudioWatchDogLast; - if (timeDiff > 500) { - parentObj.initializeWebAudio(); - } - }, 500); - } -} -XAudioServer.prototype.initializeFlashAudio = function () { - var existingFlashload = document.getElementById("XAudioJS"); - this.flashInitialized = false; - this.resetCallbackAPIAudioBuffer(44100); - switch (XAudioJSChannelsAllocated) { - case 1: - XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashMonoString; - break; - case 2: - XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashStereoString; - break; - default: - XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashSurroundString; - } - if (existingFlashload == null) { - this.audioHandleFlash = null; - var thisObj = this; - var mainContainerNode = document.createElement("div"); - mainContainerNode.setAttribute("style", "position: fixed; bottom: 0px; right: 0px; margin: 0px; padding: 0px; border: none; width: 8px; height: 8px; overflow: hidden; z-index: -1000; "); - var containerNode = document.createElement("div"); - containerNode.setAttribute("style", "position: static; border: none; width: 0px; height: 0px; visibility: hidden; margin: 8px; padding: 0px;"); - containerNode.setAttribute("id", "XAudioJS"); - mainContainerNode.appendChild(containerNode); - document.getElementsByTagName("body")[0].appendChild(mainContainerNode); - swfobject.embedSWF( - XAudioJSsourceHandle.substring(0, XAudioJSsourceHandle.length - 9) + "JS.swf", - "XAudioJS", - "8", - "8", - "9.0.0", - "", - {}, - {"allowscriptaccess":"always"}, - {"style":"position: static; visibility: hidden; margin: 8px; padding: 0px; border: none"}, - function (event) { - if (event.success) { - thisObj.audioHandleFlash = event.ref; - thisObj.checkFlashInit(); - } - else { - thisObj.failureCallback(); - thisObj.audioType = -1; - } - } - ); - } - else { - this.audioHandleFlash = existingFlashload; - this.checkFlashInit(); - } - this.audioType = 2; -} -XAudioServer.prototype.changeVolume = function (newVolume) { - if (newVolume >= 0 && newVolume <= 1) { - XAudioJSVolume = newVolume; - switch (this.audioType) { - case 0: - this.audioHandleMoz.volume = XAudioJSVolume; - case 1: - break; - case 2: - if (this.flashInitialized) { - this.audioHandleFlash.changeVolume(XAudioJSVolume); - } - else { - this.checkFlashInit(); - } - break; - case 3: - this.audioHandleMediaStream.volume = XAudioJSVolume; - break; - default: - this.failureCallback(); - } - } -} -//Checks to see if the NPAPI Adobe Flash bridge is ready yet: -XAudioServer.prototype.checkFlashInit = function () { - if (!this.flashInitialized) { - try { - if (this.audioHandleFlash && this.audioHandleFlash.initialize) { - this.flashInitialized = true; - this.audioHandleFlash.initialize(XAudioJSChannelsAllocated, XAudioJSVolume); - } - } - catch (error) { - this.flashInitialized = false; - } - } -} -//Set up the resampling: -XAudioServer.prototype.resetCallbackAPIAudioBuffer = function (APISampleRate) { - XAudioJSAudioBufferSize = XAudioJSResampleBufferEnd = XAudioJSResampleBufferStart = 0; - this.initializeResampler(APISampleRate); - XAudioJSResampledBuffer = this.getFloat32(XAudioJSResampleBufferSize); -} -XAudioServer.prototype.initializeResampler = function (sampleRate) { - XAudioJSAudioContextSampleBuffer = this.getFloat32(XAudioJSMaxBufferSize); - XAudioJSResampleBufferSize = Math.max(XAudioJSMaxBufferSize * Math.ceil(sampleRate / this.XAudioJSSampleRate) + XAudioJSChannelsAllocated, XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated); - XAudioJSResampleControl = new Resampler(this.XAudioJSSampleRate, sampleRate, XAudioJSChannelsAllocated, XAudioJSResampleBufferSize, true); -} -XAudioServer.prototype.getFloat32 = function (size) { - try { - return new Float32Array(size); - } - catch (error) { - return []; - } -} -function XAudioJSFlashAudioEvent() { //The callback that flash calls... - XAudioJSResampleRefill(); - return XAudioJSFlashTransportEncoder(); -} -function XAudioJSGenerateFlashSurroundString() { //Convert the arrays to one long string for speed. - var XAudioJSTotalSamples = XAudioJSSamplesPerCallback << 1; - if (XAudioJSBinaryString.length > XAudioJSTotalSamples) { - XAudioJSBinaryString = []; - } - XAudioJSTotalSamples = 0; - for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd; ++index) { - //Sanitize the buffer: - XAudioJSBinaryString[XAudioJSTotalSamples++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); - XAudioJSBinaryString[XAudioJSTotalSamples++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); - XAudioJSResampleBufferStart += XAudioJSChannelsAllocated - 2; - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - return XAudioJSBinaryString.join(""); -} -function XAudioJSGenerateFlashStereoString() { //Convert the arrays to one long string for speed. - var XAudioJSTotalSamples = XAudioJSSamplesPerCallback << 1; - if (XAudioJSBinaryString.length > XAudioJSTotalSamples) { - XAudioJSBinaryString = []; - } - for (var index = 0; index < XAudioJSTotalSamples && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd;) { - //Sanitize the buffer: - XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); - XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - return XAudioJSBinaryString.join(""); -} -function XAudioJSGenerateFlashMonoString() { //Convert the array to one long string for speed. - if (XAudioJSBinaryString.length > XAudioJSSamplesPerCallback) { - XAudioJSBinaryString = []; - } - for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd;) { - //Sanitize the buffer: - XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000); - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - return XAudioJSBinaryString.join(""); -} -//Some Required Globals: -var XAudioJSWebAudioContextHandle = null; -var XAudioJSWebAudioAudioNode = null; -var XAudioJSWebAudioWatchDogTimer = null; -var XAudioJSWebAudioWatchDogLast = false; -var XAudioJSWebAudioLaunchedContext = false; -var XAudioJSAudioContextSampleBuffer = []; -var XAudioJSResampledBuffer = []; -var XAudioJSMinBufferSize = 15000; -var XAudioJSMaxBufferSize = 25000; -var XAudioJSChannelsAllocated = 1; -var XAudioJSVolume = 1; -var XAudioJSResampleControl = null; -var XAudioJSAudioBufferSize = 0; -var XAudioJSResampleBufferStart = 0; -var XAudioJSResampleBufferEnd = 0; -var XAudioJSResampleBufferSize = 0; -var XAudioJSMediaStreamWorker = null; -var XAudioJSMediaStreamBuffer = []; -var XAudioJSMediaStreamSampleRate = 44100; -var XAudioJSMozAudioSampleRate = 44100; -var XAudioJSSamplesPerCallback = 2048; //Has to be between 2048 and 4096 (If over, then samples are ignored, if under then silence is added, 4096 fixes chrome android). -var XAudioJSFlashTransportEncoder = null; -var XAudioJSMediaStreamLengthAliasCounter = 0; -var XAudioJSBinaryString = []; -function XAudioJSWebAudioEvent(event) { //Web Audio API callback... - if (XAudioJSWebAudioWatchDogTimer) { - XAudioJSWebAudioWatchDogLast = (new Date()).getTime(); - } - //Find all output channels: - for (var bufferCount = 0, buffers = []; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) { - buffers[bufferCount] = event.outputBuffer.getChannelData(bufferCount); - } - //Make sure we have resampled samples ready: - XAudioJSResampleRefill(); - //Copy samples from XAudioJS to the Web Audio API: - for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd; ++index) { - for (bufferCount = 0; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) { - buffers[bufferCount][index] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] * XAudioJSVolume; - } - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - //Pad with silence if we're underrunning: - while (index < XAudioJSSamplesPerCallback) { - for (bufferCount = 0; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) { - buffers[bufferCount][index] = 0; - } - ++index; - } -} -//MediaStream API buffer push -function XAudioJSMediaStreamPushAudio(event) { - var index = 0; - var audioLengthRequested = event.data; - var samplesPerCallbackAll = XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated; - var XAudioJSMediaStreamLengthAlias = audioLengthRequested % XAudioJSSamplesPerCallback; - audioLengthRequested = audioLengthRequested - (XAudioJSMediaStreamLengthAliasCounter - (XAudioJSMediaStreamLengthAliasCounter % XAudioJSSamplesPerCallback)) - XAudioJSMediaStreamLengthAlias + XAudioJSSamplesPerCallback; - XAudioJSMediaStreamLengthAliasCounter -= XAudioJSMediaStreamLengthAliasCounter - (XAudioJSMediaStreamLengthAliasCounter % XAudioJSSamplesPerCallback); - XAudioJSMediaStreamLengthAliasCounter += XAudioJSSamplesPerCallback - XAudioJSMediaStreamLengthAlias; - if (XAudioJSMediaStreamBuffer.length != samplesPerCallbackAll) { - XAudioJSMediaStreamBuffer = new Float32Array(samplesPerCallbackAll); - } - XAudioJSResampleRefill(); - while (index < audioLengthRequested) { - var index2 = 0; - while (index2 < samplesPerCallbackAll && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd) { - XAudioJSMediaStreamBuffer[index2++] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++]; - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - XAudioJSMediaStreamWorker.postMessage([0, XAudioJSMediaStreamBuffer]); - index += XAudioJSSamplesPerCallback; - } -} -function XAudioJSResampleRefill() { - if (XAudioJSAudioBufferSize > 0) { - //Resample a chunk of audio: - var resampleLength = XAudioJSResampleControl.resampler(XAudioJSGetBufferSamples()); - var resampledResult = XAudioJSResampleControl.outputBuffer; - for (var index2 = 0; index2 < resampleLength;) { - XAudioJSResampledBuffer[XAudioJSResampleBufferEnd++] = resampledResult[index2++]; - if (XAudioJSResampleBufferEnd == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferEnd = 0; - } - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferEnd) { - XAudioJSResampleBufferStart += XAudioJSChannelsAllocated; - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - } - XAudioJSAudioBufferSize = 0; - } -} -function XAudioJSResampledSamplesLeft() { - return ((XAudioJSResampleBufferStart <= XAudioJSResampleBufferEnd) ? 0 : XAudioJSResampleBufferSize) + XAudioJSResampleBufferEnd - XAudioJSResampleBufferStart; -} -function XAudioJSGetBufferSamples() { - return XAudioJSGetArraySlice(XAudioJSAudioContextSampleBuffer, XAudioJSAudioBufferSize); -} -function XAudioJSGetArraySlice(buffer, lengthOf) { - //Typed array and normal array buffer section referencing: - try { - return buffer.subarray(0, lengthOf); - } - catch (error) { - try { - //Regular array pass: - buffer.length = lengthOf; - return buffer; - } - catch (error) { - //Nightly Firefox 4 used to have the subarray function named as slice: - return buffer.slice(0, lengthOf); - } - } -} \ No newline at end of file diff --git a/js-emulator/js/other/XAudioServerMediaStreamWorker.js b/js-emulator/js/other/XAudioServerMediaStreamWorker.js deleted file mode 100755 index 966bf7c..0000000 --- a/js-emulator/js/other/XAudioServerMediaStreamWorker.js +++ /dev/null @@ -1,68 +0,0 @@ -//This file is part of the XAudioJS library. -var XAudioJSResampledBuffer = []; -var XAudioJSOutputBuffer = []; -var XAudioJSResampleBufferStart = 0; -var XAudioJSResampleBufferEnd = 0; -var XAudioJSResampleBufferSize = 0; -var XAudioJSChannelsAllocated = 1; -//Message Receiver: -self.onmessage = function (event) { - var data = event.data; - switch (data[0]) { - case 0: - //Add new audio samples to our ring buffer: - var resampledResult = data[1]; - var length = resampledResult.length; - for (var i = 0; i < length; ++i) { - XAudioJSResampledBuffer[XAudioJSResampleBufferEnd++] = resampledResult[i]; - if (XAudioJSResampleBufferEnd == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferEnd = 0; - } - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferEnd) { - XAudioJSResampleBufferStart += XAudioJSChannelsAllocated; - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - } - break; - case 1: - //Initialize: - XAudioJSResampleBufferSize = data[1]; - XAudioJSChannelsAllocated = data[2]; - XAudioJSResampledBuffer = new Float32Array(XAudioJSResampleBufferSize); - } -} -//MediaStream Polyfill Event: -self.onprocessmedia = function (event) { - //Get some buffer length computations: - var apiBufferLength = event.audioLength; - var apiBufferLengthAll = apiBufferLength * event.audioChannels; - if (apiBufferLengthAll > XAudioJSOutputBuffer.length) { - XAudioJSOutputBuffer = new Float32Array(apiBufferLengthAll); - } - //De-interleave the buffered audio while looping through our ring buffer: - var sampleFramesCount = Math.min(apiBufferLength, XAudioJSResampledSamplesLeft() / XAudioJSChannelsAllocated); - for (var sampleFramePosition = 0, channelOffset = 0; sampleFramePosition < sampleFramesCount; ++sampleFramePosition) { - for (channelOffset = sampleFramePosition; channelOffset < apiBufferLengthAll; channelOffset += apiBufferLength) { - XAudioJSOutputBuffer[channelOffset] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++]; - if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) { - XAudioJSResampleBufferStart = 0; - } - } - } - //Add some zero fill if we underran the required buffer fill amount: - while (sampleFramePosition < apiBufferLength) { - for (channelOffset = sampleFramePosition++; channelOffset < apiBufferLengthAll; channelOffset += apiBufferLength) { - XAudioJSOutputBuffer[channelOffset] = 0; - } - } - //Write some buffered audio: - event.writeAudio(XAudioJSOutputBuffer.subarray(0, apiBufferLengthAll)); - //Request a buffer from the main thread: - self.postMessage(event.audioLength); -} -//Accessory function used to determine remaining samples in the ring buffer: -function XAudioJSResampledSamplesLeft() { - return ((XAudioJSResampleBufferStart <= XAudioJSResampleBufferEnd) ? 0 : XAudioJSResampleBufferSize) + XAudioJSResampleBufferEnd - XAudioJSResampleBufferStart; -} \ No newline at end of file diff --git a/js-emulator/js/other/base64.js b/js-emulator/js/other/base64.js deleted file mode 100755 index fb092d8..0000000 --- a/js-emulator/js/other/base64.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; -var toBase64 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+" , "/", "="]; -var fromBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; -function base64(data) { - try { - var base64 = window.btoa(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below. - } - catch (error) { - //Defaulting to non-native base64 encoding... - var base64 = ""; - var dataLength = data.length; - if (dataLength > 0) { - var bytes = [0, 0, 0]; - var index = 0; - var remainder = dataLength % 3; - while (data.length % 3 > 0) { - //Make sure we don't do fuzzy math in the next loop... - data[data.length] = " "; - } - while (index < dataLength) { - //Keep this loop small for speed. - bytes = [data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF]; - base64 += toBase64[bytes[0] >> 2] + toBase64[((bytes[0] & 0x3) << 4) | (bytes[1] >> 4)] + toBase64[((bytes[1] & 0xF) << 2) | (bytes[2] >> 6)] + toBase64[bytes[2] & 0x3F]; - } - if (remainder > 0) { - //Fill in the padding and recalulate the trailing six-bit group... - base64[base64.length - 1] = "="; - if (remainder == 2) { - base64[base64.length - 2] = "="; - base64[base64.length - 3] = toBase64[(bytes[0] & 0x3) << 4]; - } - else { - base64[base64.length - 2] = toBase64[(bytes[1] & 0xF) << 2]; - } - } - } - } - return base64; -} -function base64_decode(data) { - try { - var decode64 = window.atob(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below. - } - catch (error) { - //Defaulting to non-native base64 decoding... - var decode64 = ""; - var dataLength = data.length; - if (dataLength > 3 && dataLength % 4 == 0) { - var sixbits = [0, 0, 0, 0]; //Declare this out of the loop, to speed up the ops. - var index = 0; - while (index < dataLength) { - //Keep this loop small for speed. - sixbits = [fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++))]; - decode64 += String.fromCharCode((sixbits[0] << 2) | (sixbits[1] >> 4)) + String.fromCharCode(((sixbits[1] & 0x0F) << 4) | (sixbits[2] >> 2)) + String.fromCharCode(((sixbits[2] & 0x03) << 6) | sixbits[3]); - } - //Check for the '=' character after the loop, so we don't hose it up. - if (sixbits[3] >= 0x40) { - decode64.length -= 1; - if (sixbits[2] >= 0x40) { - decode64.length -= 1; - } - } - } - } - return decode64; -} -function to_little_endian_dword(str) { - return to_little_endian_word(str) + to_little_endian_word(str >> 16); -} -function to_little_endian_word(str) { - return to_byte(str) + to_byte(str >> 8); -} -function to_byte(str) { - return String.fromCharCode(str & 0xFF); -} -function arrayToBase64(arrayIn) { - var binString = ""; - var length = arrayIn.length; - for (var index = 0; index < length; ++index) { - if (typeof arrayIn[index] == "number") { - binString += String.fromCharCode(arrayIn[index]); - } - } - return base64(binString); -} -function base64ToArray(b64String) { - var binString = base64_decode(b64String); - var outArray = []; - var length = binString.length; - for (var index = 0; index < length;) { - outArray.push(binString.charCodeAt(index++) & 0xFF); - } - return outArray; -} \ No newline at end of file diff --git a/js-emulator/js/other/controls.js b/js-emulator/js/other/controls.js deleted file mode 100644 index d303c41..0000000 --- a/js-emulator/js/other/controls.js +++ /dev/null @@ -1,399 +0,0 @@ -const JS_KEY_ALT = 18; -const JS_KEY_CTRL = 17; - -const DEADZONE = 0.1; - -var defaultKeys = { - up: ["ArrowUp", "w", "W"], - down: ["ArrowDown", "s", "S"], - left: ["ArrowLeft", "a", "A"], - right: ["ArrowRight", "d", "D"], - a: ["Alt", "z", "j", "Z", "J"], - b: ["Control", "k", "x", "K", "X"], - start: ["Enter"], - select: ["Shift"] -}; - -// Build keybindings object using -// value from customControls if defined or defaultKeys if not -var keyBindings = {}; -for (var key in defaultKeys) { - var keys = customControls[key] ? customControls[key] : defaultKeys[key]; - for (var i = 0; i < keys.length; i++) { - keyBindings[keys[i]] = key; - keyBindings[String(keys[i]).toLowerCase()] = key; - keyBindings[String(keys[i]).toUpperCase()] = key; - } -} - -var isTouchEnabled = "ontouchstart" in document.documentElement; - -var controller = document.getElementById("controller"); -var btnA = document.getElementById("controller_a"); -var btnB = document.getElementById("controller_b"); -var btnStart = document.getElementById("controller_start"); -var btnSelect = document.getElementById("controller_select"); -var dpad = document.getElementById("controller_dpad"); - - -// HTML Gamepad API support -// Poll for gamepad input about ~4 times per gameboy frame (~240 times second) -const GAMEPAD_POLLING_INTERVAL = 1000 / 60 / 4; -const GAMEPAD_KEYMAP_STANDARD_STR = "standard" - -// When gamepad.mapping reports "standard" -const GAMEPAD_KEYMAP_STANDARD = [ - {gb_key: "b", gp_button: 0, type: "button"}, - {gb_key: "a", gp_button: 1, type: "button"}, - {gb_key: "select", gp_button: 8, type: "button"}, - {gb_key: "start", gp_button: 9, type: "button"}, - {gb_key: "up", gp_button: 12, type: "button"}, - {gb_key: "down", gp_button: 13, type: "button"}, - {gb_key: "left", gp_button: 14, type: "button"}, - {gb_key: "right", gp_button: 15, type: "button"} - ]; - -const GAMEPAD_KEYMAP_DEFAULT = [ - {gb_key: "a", gp_button: 0, type: "button"}, - {gb_key: "b", gp_button: 1, type: "button"}, - {gb_key: "select", gp_button: 2, type: "button"}, - {gb_key: "start", gp_button: 3, type: "button"}, - {gb_key: "up", gp_button: 2, type: "axis"}, - {gb_key: "down", gp_button: 3, type: "axis"}, - {gb_key: "left", gp_button: 0, type: "axis"}, - {gb_key: "right", gp_button: 1, type: "axis"} - ]; - -// gamepad related vars -var gp = { - apiID: undefined, - timerID: undefined, - keybinds: undefined, - axes: { - last: undefined, - cur: [], - changed: [] }, - buttons: { - last: undefined, - cur: [], - changed: [] } - }; - - -function bindButton(el, code) { - el.addEventListener("touchstart", function(e) { - e.preventDefault(); - e.stopPropagation(); - e.currentTarget.className = e.currentTarget.className + " btnPressed"; - GameBoyKeyDown(code); - }); - - el.addEventListener("touchend", function(e) { - e.preventDefault(); - e.stopPropagation(); - initSound(); - e.currentTarget.className = e.currentTarget.className.replace( - / btnPressed/, - "" - ); - GameBoyKeyUp(code); - }); -} - -function bindDpad(el) { - el.addEventListener("touchstart", function(e) { - e.preventDefault(); - e.stopPropagation(); - var rect = e.currentTarget.getBoundingClientRect(); - var x = (2 * (e.targetTouches[0].clientX - rect.left)) / rect.width - 1; - var y = (2 * (e.targetTouches[0].clientY - rect.top)) / rect.height - 1; - move(x, y); - }); - - el.addEventListener("touchmove", function(e) { - e.preventDefault(); - e.stopPropagation(); - var rect = e.currentTarget.getBoundingClientRect(); - var x = (2 * (e.targetTouches[0].clientX - rect.left)) / rect.width - 1; - var y = (2 * (e.targetTouches[0].clientY - rect.top)) / rect.height - 1; - move(x, y); - }); - - function move(x, y) { - if (x < -DEADZONE || x > DEADZONE) { - if (y > x && y < -x) { - GameBoyKeyUp("right"); - GameBoyKeyDown("left"); - } else if (y > -x && y < x) { - GameBoyKeyUp("left"); - GameBoyKeyDown("right"); - } - - if (y > -DEADZONE && y < DEADZONE) { - GameBoyKeyUp("up"); - GameBoyKeyUp("down"); - } - } - - if (y < -DEADZONE || y > DEADZONE) { - if (x > y && x < -y) { - GameBoyKeyUp("down"); - GameBoyKeyDown("up"); - } else if (x > -y && x < y) { - GameBoyKeyUp("up"); - GameBoyKeyDown("down"); - } - - if (x > -DEADZONE && x < DEADZONE) { - GameBoyKeyUp("left"); - GameBoyKeyUp("right"); - } - } - } - - el.addEventListener("touchend", function(e) { - e.preventDefault(); - e.stopPropagation(); - initSound(); - GameBoyKeyUp("left"); - GameBoyKeyUp("right"); - GameBoyKeyUp("up"); - GameBoyKeyUp("down"); - }); -} - -function bindTouchRestore() { - window.addEventListener("touchstart", function(e) { - controller.style.display = "block"; - isTouchEnabled = true; - }) -} - -function bindKeyboard() { - window.onkeydown = function(e) { - initSound(); - if (isTouchEnabled) { - controller.style.display = "none"; - isTouchEnabled = false; - } - if ( - e.keyCode !== JS_KEY_CTRL && - e.keyCode !== JS_KEY_ALT && - (e.altKey || e.ctrlKey || e.metaKey) - ) { - return; - } - if (keyBindings[e.key]) { - GameBoyKeyDown(keyBindings[e.key]); - } - e.preventDefault(); - }; - - window.onkeyup = function(e) { - if (e.key === "Dead") { - // Ipad keyboard fix :-/ - // Doesn't register which key was released, so release all of them - ["right", "left", "up", "down", "a", "b", "select", "start"].forEach( - key => { - GameBoyKeyUp(key); - } - ); - } - if (keyBindings[e.key]) { - GameBoyKeyUp(keyBindings[e.key]); - } - e.preventDefault(); - }; -} - -function bindClick() { - window.addEventListener("click", function(e) { - initSound(); - }) -} - -if (isTouchEnabled) { - controller.style.display = "block"; - bindButton(btnA, "a"); - bindButton(btnB, "b"); - bindButton(btnStart, "start"); - bindButton(btnSelect, "select"); - bindDpad(dpad); - bindTouchRestore(); -} else { - controller.style.display = "none"; -} -bindKeyboard(); -bindClick(); - -function resetKeys() { - for (var key in defaultKeys) { - GameBoyKeyUp(key); - } -} - -window.addEventListener("focus", resetKeys); -window.addEventListener("blur", resetKeys); - -// HTML Gamepad API Support - -// Load a key map for gamepad-to-gameboy buttons -function gamepadBindKeys(strMapping) { - - // Try to use the w3c "standard" gamepad mapping if available - // (Chrome/V8 seems to do that better than Firefox) - // - // Otherwise use a default mapping that assigns - // A/B/Select/Start to the first four buttons, - // and U/D/L/R to the first two axes. - - if (strMapping === GAMEPAD_KEYMAP_STANDARD_STR) - gp.keybinds = GAMEPAD_KEYMAP_STANDARD; - else - gp.keybinds = GAMEPAD_KEYMAP_DEFAULT; -} - - -function gamepadCacheValues(gamepad) { - - // Read Buttons - for(let k=0; k 0 || - gamepad.buttons[k].pressed == true); - - // Update state changed if not on first input pass - if (gp.buttons.last !== undefined) - gp.buttons.changed[k] = (gp.buttons.cur[k] != gp.buttons.last[k]); - } - - // Read Axes - for(let k=0; k 0); - - // Update state changed if not on first input pass - if (gp.axes.last !== undefined) { - gp.axes.changed[(k*2) ] = (gp.axes.cur[(k*2) ] != gp.axes.last[(k*2) ]); - gp.axes.changed[(k*2)+1] = (gp.axes.cur[(k*2)+1] != gp.axes.last[(k*2)+1]); - } - } - - // Save current state for comparison on next input - gp.axes.last = gp.axes.cur.slice(0); - gp.buttons.last = gp.buttons.cur.slice(0); -} - - -function gamepadHandleButton(keyBind) { - - var buttonCache; - - // Select button / axis cache based on key bind type - if (keyBind.type === "button") - buttonCache = gp.buttons; - else if (keyBind.type === "axis") - buttonCache = gp.axes; - - // Make sure the button exists in the cache array - if (keyBind.gp_button < buttonCache.changed.length) { - - // Send the button state if it's changed - if (buttonCache.changed[keyBind.gp_button]) - if (buttonCache.cur[keyBind.gp_button]) - GameBoyKeyDown(keyBind.gb_key); - else - GameBoyKeyUp(keyBind.gb_key); - } -} - - -function gamepadGetCurrent() { - - // Chrome requires retrieving a new gamepad object - // every time button state is queried (the existing object - // will have stale button state). Just do that for all browsers - var gamepad = navigator.getGamepads()[gp.apiID]; - - if (gamepad) - if (gamepad.connected) - return gamepad; - - return undefined; -} - - -function gamepadUpdate() { - - var gamepad = gamepadGetCurrent(); - - if (gamepad !== undefined) { - - // Cache gamepad input values - gamepadCacheValues(gamepad); - - // Loop through buttons and send changes if needed - for (let i=0; i= 2) && (gamepad.buttons.length >= 4))) { - - // Save API index for polling (required by Chrome/V8) - gp.apiID = gamepad.index; - - // Assign gameboy keys to the gamepad - gamepadBindKeys(gamepad.mapping); - - // Start polling the gamepad for input - gp.timerID = setInterval( () => gamepadUpdate(), GAMEPAD_POLLING_INTERVAL); - } -} - - -function gamepadStop() { - - // Stop polling the gamepad for input - if (gp.timerID !== undefined) - clearInterval(gp.timerID); - - // Clear previous button history and controller info - gp.axes.last = undefined; - gp.buttons.last = undefined; - gp.keybinds = undefined; - - gp.apiID = undefined; -} - - -function initGamePad() -{ - // When a gamepad connects, start polling it for input - window.addEventListener("gamepadconnected", (event) => { - initSound(); - gamepadStart( navigator.getGamepads()[event.gamepad.index] ); - }); - - // When a gamepad disconnects, shut down polling for input - window.addEventListener("gamepaddisconnected", (event) => { - gamepadStop(); - }); -} - - -initGamePad(); - - - - diff --git a/js-emulator/js/other/mobile.js b/js-emulator/js/other/mobile.js deleted file mode 100755 index d596918..0000000 --- a/js-emulator/js/other/mobile.js +++ /dev/null @@ -1,107 +0,0 @@ -var romPath = "rom/game.gb"; -var mainCanvas = null; -var soundReady = false; - -var cout = console.log.bind(console); -function startGame(blob) { - var binaryHandle = new FileReader(); - binaryHandle.onload = function() { - if (this.readyState === 2) { - try { - start(mainCanvas, this.result); - } catch (e) { - alert(e.message); - } - } - }; - binaryHandle.readAsBinaryString(blob); -} - -function loadViaXHR() { - var xhr = new XMLHttpRequest(); - xhr.open("GET", romPath); - xhr.responseType = "blob"; - xhr.onload = function() { - startGame(new Blob([this.response], { type: "text/plain" })); - }; - xhr.send(); -} - -function windowingInitialize() { - cout("windowingInitialize() called.", 0); - mainCanvas = document.getElementById("mainCanvas"); - window.onunload = autoSave; - loadViaXHR(); -} - -//Wrapper for localStorage getItem, so that data can be retrieved in various types. -function findValue(key) { - try { - if (window.localStorage.getItem(key) != null) { - return JSON.parse(window.localStorage.getItem(key)); - } - } catch (error) { - //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): - if (window.globalStorage[location.hostname].getItem(key) != null) { - return JSON.parse(window.globalStorage[location.hostname].getItem(key)); - } - } - return null; -} -//Wrapper for localStorage setItem, so that data can be set in various types. -function setValue(key, value) { - try { - window.localStorage.setItem(key, JSON.stringify(value)); - } catch (error) { - //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): - window.globalStorage[location.hostname].setItem(key, JSON.stringify(value)); - } -} -//Wrapper for localStorage removeItem, so that data can be set in various types. -function deleteValue(key) { - try { - window.localStorage.removeItem(key); - } catch (error) { - //An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole): - window.globalStorage[location.hostname].removeItem(key); - } -} - -// Allow Audio context to be created in places which require -// user input first. Hook this up to keyboard and touch inputs -function initSound() { - if (!soundReady && !settings[0] && GameBoyEmulatorInitialized()) { - window.AudioContext = window.AudioContext || window.webkitAudioContext; - window.audioContext = new AudioContext(); - if (window.audioContext) { - // Create empty buffer - var buffer = window.audioContext.createBuffer(1, 1, 22050); - var source = window.audioContext.createBufferSource(); - source.buffer = buffer; - // Connect to output (speakers) - source.connect(window.audioContext.destination); - // Play sound - if (source.start) { - source.start(0); - } else if (source.play) { - source.play(0); - } else if (source.noteOn) { - source.noteOn(0); - } - window.audioContext.resume().then(() => { - settings[0] = true; - gameboy.initSound(); - soundReady = true; - }); - } - } -} - -var soundInitTimer = setInterval(function() { - if (GameBoyEmulatorInitialized()) { - initSound(); - clearTimeout(soundInitTimer); - } -}, 16); - -window.addEventListener("DOMContentLoaded", windowingInitialize); diff --git a/js-emulator/js/other/resampler.js b/js-emulator/js/other/resampler.js deleted file mode 100755 index 9cd7373..0000000 --- a/js-emulator/js/other/resampler.js +++ /dev/null @@ -1,206 +0,0 @@ -//JavaScript Audio Resampler (c) 2011 - Grant Galitz -function Resampler(fromSampleRate, toSampleRate, channels, outputBufferSize, noReturn) { - this.fromSampleRate = fromSampleRate; - this.toSampleRate = toSampleRate; - this.channels = channels | 0; - this.outputBufferSize = outputBufferSize; - this.noReturn = !!noReturn; - this.initialize(); -} -Resampler.prototype.initialize = function () { - //Perform some checks: - if (this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0) { - if (this.fromSampleRate == this.toSampleRate) { - //Setup a resampler bypass: - this.resampler = this.bypassResampler; //Resampler just returns what was passed through. - this.ratioWeight = 1; - } - else { - this.ratioWeight = this.fromSampleRate / this.toSampleRate; - if (this.fromSampleRate < this.toSampleRate) { - /* - Use generic linear interpolation if upsampling, - as linear interpolation produces a gradient that we want - and works fine with two input sample points per output in this case. - */ - this.compileLinearInterpolationFunction(); - this.lastWeight = 1; - } - else { - /* - Custom resampler I wrote that doesn't skip samples - like standard linear interpolation in high downsampling. - This is more accurate than linear interpolation on downsampling. - */ - this.compileMultiTapFunction(); - this.tailExists = false; - this.lastWeight = 0; - } - this.initializeBuffers(); - } - } - else { - throw(new Error("Invalid settings specified for the resampler.")); - } -} -Resampler.prototype.compileLinearInterpolationFunction = function () { - var toCompile = "var bufferLength = buffer.length;\ - var outLength = this.outputBufferSize;\ - if ((bufferLength % " + this.channels + ") == 0) {\ - if (bufferLength > 0) {\ - var weight = this.lastWeight;\ - var firstWeight = 0;\ - var secondWeight = 0;\ - var sourceOffset = 0;\ - var outputOffset = 0;\ - var outputBuffer = this.outputBuffer;\ - for (; weight < 1; weight += " + this.ratioWeight + ") {\ - secondWeight = weight % 1;\ - firstWeight = 1 - secondWeight;"; - for (var channel = 0; channel < this.channels; ++channel) { - toCompile += "outputBuffer[outputOffset++] = (this.lastOutput[" + channel + "] * firstWeight) + (buffer[" + channel + "] * secondWeight);"; - } - toCompile += "}\ - weight -= 1;\ - for (bufferLength -= " + this.channels + ", sourceOffset = Math.floor(weight) * " + this.channels + "; outputOffset < outLength && sourceOffset < bufferLength;) {\ - secondWeight = weight % 1;\ - firstWeight = 1 - secondWeight;"; - for (var channel = 0; channel < this.channels; ++channel) { - toCompile += "outputBuffer[outputOffset++] = (buffer[sourceOffset" + ((channel > 0) ? (" + " + channel) : "") + "] * firstWeight) + (buffer[sourceOffset + " + (this.channels + channel) + "] * secondWeight);"; - } - toCompile += "weight += " + this.ratioWeight + ";\ - sourceOffset = Math.floor(weight) * " + this.channels + ";\ - }"; - for (var channel = 0; channel < this.channels; ++channel) { - toCompile += "this.lastOutput[" + channel + "] = buffer[sourceOffset++];"; - } - toCompile += "this.lastWeight = weight % 1;\ - return this.bufferSlice(outputOffset);\ - }\ - else {\ - return (this.noReturn) ? 0 : [];\ - }\ - }\ - else {\ - throw(new Error(\"Buffer was of incorrect sample length.\"));\ - }"; - this.resampler = Function("buffer", toCompile); -} -Resampler.prototype.compileMultiTapFunction = function () { - var toCompile = "var bufferLength = buffer.length;\ - var outLength = this.outputBufferSize;\ - if ((bufferLength % " + this.channels + ") == 0) {\ - if (bufferLength > 0) {\ - var weight = 0;"; - for (var channel = 0; channel < this.channels; ++channel) { - toCompile += "var output" + channel + " = 0;" - } - toCompile += "var actualPosition = 0;\ - var amountToNext = 0;\ - var alreadyProcessedTail = !this.tailExists;\ - this.tailExists = false;\ - var outputBuffer = this.outputBuffer;\ - var outputOffset = 0;\ - var currentPosition = 0;\ - do {\ - if (alreadyProcessedTail) {\ - weight = " + this.ratioWeight + ";"; - for (channel = 0; channel < this.channels; ++channel) { - toCompile += "output" + channel + " = 0;" - } - toCompile += "}\ - else {\ - weight = this.lastWeight;"; - for (channel = 0; channel < this.channels; ++channel) { - toCompile += "output" + channel + " = this.lastOutput[" + channel + "];" - } - toCompile += "alreadyProcessedTail = true;\ - }\ - while (weight > 0 && actualPosition < bufferLength) {\ - amountToNext = 1 + actualPosition - currentPosition;\ - if (weight >= amountToNext) {"; - for (channel = 0; channel < this.channels; ++channel) { - toCompile += "output" + channel + " += buffer[actualPosition++] * amountToNext;" - } - toCompile += "currentPosition = actualPosition;\ - weight -= amountToNext;\ - }\ - else {"; - for (channel = 0; channel < this.channels; ++channel) { - toCompile += "output" + channel + " += buffer[actualPosition" + ((channel > 0) ? (" + " + channel) : "") + "] * weight;" - } - toCompile += "currentPosition += weight;\ - weight = 0;\ - break;\ - }\ - }\ - if (weight <= 0) {"; - for (channel = 0; channel < this.channels; ++channel) { - toCompile += "outputBuffer[outputOffset++] = output" + channel + " / " + this.ratioWeight + ";" - } - toCompile += "}\ - else {\ - this.lastWeight = weight;"; - for (channel = 0; channel < this.channels; ++channel) { - toCompile += "this.lastOutput[" + channel + "] = output" + channel + ";" - } - toCompile += "this.tailExists = true;\ - break;\ - }\ - } while (actualPosition < bufferLength && outputOffset < outLength);\ - return this.bufferSlice(outputOffset);\ - }\ - else {\ - return (this.noReturn) ? 0 : [];\ - }\ - }\ - else {\ - throw(new Error(\"Buffer was of incorrect sample length.\"));\ - }"; - this.resampler = Function("buffer", toCompile); -} -Resampler.prototype.bypassResampler = function (buffer) { - if (this.noReturn) { - //Set the buffer passed as our own, as we don't need to resample it: - this.outputBuffer = buffer; - return buffer.length; - } - else { - //Just return the buffer passsed: - return buffer; - } -} -Resampler.prototype.bufferSlice = function (sliceAmount) { - if (this.noReturn) { - //If we're going to access the properties directly from this object: - return sliceAmount; - } - else { - //Typed array and normal array buffer section referencing: - try { - return this.outputBuffer.subarray(0, sliceAmount); - } - catch (error) { - try { - //Regular array pass: - this.outputBuffer.length = sliceAmount; - return this.outputBuffer; - } - catch (error) { - //Nightly Firefox 4 used to have the subarray function named as slice: - return this.outputBuffer.slice(0, sliceAmount); - } - } - } -} -Resampler.prototype.initializeBuffers = function () { - //Initialize the internal buffer: - try { - this.outputBuffer = new Float32Array(this.outputBufferSize); - this.lastOutput = new Float32Array(this.channels); - } - catch (error) { - this.outputBuffer = []; - this.lastOutput = []; - } -} \ No newline at end of file diff --git a/js-emulator/js/other/resize.js b/js-emulator/js/other/resize.js deleted file mode 100755 index cf8326f..0000000 --- a/js-emulator/js/other/resize.js +++ /dev/null @@ -1,20 +0,0 @@ -function bindResize() { - var canvas = document.getElementById("mainCanvas"); - var gameRatio = 160 / 144; - - function onResize() { - var windowRatio = window.innerWidth / window.innerHeight; - if (windowRatio < gameRatio) { - canvas.style.width = window.innerWidth + "px"; - canvas.style.height = "auto"; - } else { - canvas.style.height = window.innerHeight + "px"; - canvas.style.width = "auto"; - } - } - - window.addEventListener("resize", onResize); - onResize(); -} - -bindResize(); diff --git a/js-emulator/js/other/swfobject.js b/js-emulator/js/other/swfobject.js deleted file mode 100755 index 8eafe9d..0000000 --- a/js-emulator/js/other/swfobject.js +++ /dev/null @@ -1,4 +0,0 @@ -/* SWFObject v2.2 - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab`x0Tm`-HKG>qM1>&=7*rHn5A0#_s@iS0+ICw&5s1m6?Xs(` z?pAhfvAgZsW4oxhYg<}Ph>B?(39@Kg+Zv=on;u}T0s%As@0-aaA?UXL+WzunAaWrkQiFTM1{8NH)j?A?J2PrYfpg1Q^@fScQWLJis85DaUx}5glfYuGMyNbA zo*FJ8X(wLix@6w3A(i_k07EI72uI+g=14I0Llc9FouWGDz`Bz%r=-p)uOk&sah+3I zcXFFEw9cuhBXMU?oinVC!bq^2bNX=w`IKZO! zLMauh+<;uGNvBS!wG|Mes(h7rqHPJGP^Z?~HZXK3(3&9EwAusCX|=ZJUDIoGU0crP z0Yhgi&*U8r(`na4E(+gaO&ej>2Qh@e4RjwaalI1vhZpQ~iR7TG?q zeQwigq_!Sg3-yK2wp4@J7HF`I&gr$A06?l#Xq$PwQevBPyiP*B9&Ee&_~;PZy~kA{ zww1?&M7E8msAm+^xiD)JF2uk+Ti)>y5*IPWwWug&itABP!W8qNh!k@*;V%8z6t{?g z0p--61;i;8BPjupj*lSWh&qc9;y6<==Xe-d1uivMFEjiw^(nKf#hmiX^~G)zA+lX+ zw%T0H)~1g1Yw8*7NXTGDLWkD2-Jzu_M9>LUj!Z{749uq;T9S6?s2Y(Pe7O!`q8@_b zJgs)W<{QLoMLebsGkjlx3VImAxHG*L4ecwAbf^rb>mAy@0}oX`1C`a&Yi)mNuu4Sg z!xPcy&96Zhzf`L ztbQh^Z23&2ELSn(`(QRHc%f!xIICsm>NN z8ZKqlCQ!7M5V2?t(t+|DM6g;^ryK}NjY^{y)F}mZ5GyHA4F%zT5cW(^=1m0Q0FMv{ zq4Vie&Ygh7uTK>@e{*V+^LM8F4^gqrDEb5 zYX3J4R@W!yl$;rWZ_!B{5OscG24bs+a557P#A({1lL93E)J+x&G}%_!C?Skwfh z-1(W=`MKG7*{pN5nM*;@dhG-$bz+3G^sM@_xi~L1H8wuCI0sH^9>SFvwR`fku;F1r zg@(L;*IE5pf49JwV$P}mgaWKW420$@fq15*FnGcV@RTDw6``EKbfv-zJ3g>lk;t(bl^cADcfZmRjNC&6ciQa$je|Oa@nG0KTie{Uz?GXFh_db zxn#}*&U;|Ek5L`?nMW#imFngf?$>A-qgrG%ku}OXJ&?H07+e4bk>@VBk}60HR_|Vk zj9df}i-|5ME%B5%nS#8R(NU21p)&(xY6!ynh^-QCYOHWqi6CHkowjEg-tNh8Q=4-c z-%=4T2)UhCbLgLLM;&a#=nCC(n{{rx5t#Q(K%up_*-~@prQ585;cZo3hyi@?bs zCmddNs&R6$GZrUba;D*=2=P!!Z89t2e(;hxMHg=yg)+=gSNaSz{J&!6H!0?H;Y#SK zW#ukLaJ!Fb(ZxK76d2cv;5PVcae))PHCQJK#gsxVOpvHpeGVzuk_#8hLZ^dmONnct z<)~Pe-^sKO`VuXS`>6ez5V#K$kqT7^yl;{XMR#U!v+FhUaiO!>%&k3EDA>(lmtET7 zW^bDkv*MT(-nwvJg8BWL={jZFOf_bulH_cw*BN>nrImv`RTHLDV(+T4r$e!**zgd- z1@3y=P-2N{>{n1z^+glN6aE4kh5L!?4OT?PhBNbwMGI!SmU?LjiK`c&bBFett>pM- z6)S|vqQ>r5Vta8+1pc8F6I<#f8J#N*ExJvAca%bnty5y9IHm&4o0wUx#P;}^S?(n{ zGgeq%G2*6HkN^#IHTJDm7;9Eicq>y#a9gHObTC}drI^~I0LyA@IfW^$3|~b3LusYP zg;_~>;{Zr1yUn;3y$eM-6=; zHV)nzIKso>POS=5Pc&VIK49N8_**kKG^5JI->l#T+*85IjIfL-fj896%6L3d8DCKx zhJ27<;Axi`?Af%$3|E&~7m9D*xxVOQZ0)96l5-g0XCO=g z8xmaghR1+@ebE^(pwHWQrmumU3Yf~6Wt5Tu4cxUhfj{a={%_W}H)wizza@ar3jCpa z6`;EYXpqFU`Ej!lUS71Wq!OQd(Q5Ky)c|}7$1p0IL`XX5tMI7>4EAqpR!KiA!O1~h zz$UQqdXOSZ0II7jRDrBisH2$-5S{c?VX^5`C4hyhNHELNu>OuR6CYM4B_==@>c~{0 zx7cxmfe(h6uC^lFmQy=9+?HD_2v;YCE8*i5F&bsJSS~wmTrXvoK1(SLYo^MrewwKR zH{PENQ9l`}+(Y9NnDSpIDeW|_V9IY#qMnRo$KI{&n*8jNiK)a5wVkH+&?}YJ`w7L4~l3=0FjCSD4$J*IJ~(NaM$pAJzOgU|;= zMeGMe>dAvpe4&g_n2DrgZI5Vr$ej*tlAFfEX{8xc3ymFaWYqmFj?w_)ODqVCvjq~` zg5fOMAv4iiE4T?lR{}B79q-O0Zq|KC5-yCA=J3R)FXqb zcLr192)JxwCdwKC-99);xg3da0h22YcfSO0E+0rzZa`{Q^$Jw7xu9$rGgv($NoiuL z$KvWs7}XWwio?~HnZgKM{V7w3#oaFxDiF8)*s=`)Tc;$FwdB;`KD_g&%D@8X+b zxh-<)YSmfbHj+{cBRbq}rK!p>k4$vH!swW%z(^-rl@}%{9Uc`cAjmoc6ENPuEOE9X z%{9gu*L+;JX$P}zx^jHQS+(n3y4tlH!Tq?nO$eAAyoMM8+HVm`@ER8Dlaf48$@Mtz zV|Ziu_GwIFzG)0shFth8rbr`};I_BN#F2Z9Yd1zGw#wbX$%5!9;I?f_golinvL&}e z+ghrTbj)ZK60S|-;aY${-rN9h%bPCSHWEWSWT`-+jfb_g$GYiv ziTL2Wjk|L%o7EWB6r3jv&J{*B4Bd2y_}aQLc0l0)qT33V2NWD6x~%ZATgj;o8a^8y z_!Qo5D=)xDt%c61wP;wto9ob0Nw9jAD*jRkycx2RCdVvN152BAYcssFiF_b#NmKJk z#ht>>I%sERt!=A=W*q>GOB`BPX03yUdlyHqy4<&ds^6aIT}{=e{mLKumA~*S_xhDX z{Ok<#E06Unr}&k#{mS?Il^^jd7x|S>`;|ZRD}Uiv-s>w{i(93qbfMS@61zkuUXm!H zW%0STnWO?f`XLa<7B!q`7Zabdlv#XA^-py~lsEnktX1e15-D8gFq1K$YY>m?CPq6{ zE%8}+*6L49u|5s`?w_89OR9hR@&5n9Tjmkq5#SNv5#SNv5#SNv5#SNv5#SNv5#SNv z5#SNv5#SNv5#SNv5%_Ts@c)$eyOhOZt+u#0Bg1HvK}t;I>J$}mC53_@wAze}Es)%9 z3=@ft9yLPRyZ0}DQK<-`x0fJNQr4___0`}0cJ*ouBZv%0t5=Ieti7TlnGF3E6qJ{5 z*nq5NWfd2nI+c|*b7o440uljf!-lM^XPz+{xmGbOIa#AgPWI8?t)!bdFP%tV_u#6B zkyV0Nl6YsLmibygvGBpio_z4pRS#;3j0ab(U;QAFvEmmiw8YF6k3I}FRy?{EBuveP zPySp>5KEI*J@PP-N}%F+Pcdh%cD_yv-^7;vA_>EU@Tt1&R}(NiWWA(Bg8r6EHb_=W zej&kwxe_PAW&WH8B~MCluQsBP9xg&UDQ?p#WLA*lzn0Tf5 zaq%kg6JlJpTKtQ?90}5SQ2eA%N6gh)BYujlCFD5B&RTK47?=BV7|Zf?;sWtQ;zIGy z#2dvAi#Lh!kbXKL{}&yscKnW)_q@F0z@7s;-q`cT4$~eJCcx@29LEI*CP06|*b(%= z*a_827D({W8Irr;=wF^Gxm$uu(W6(sfkJ`wah}syrU?j9L@-8^~muHT=dnB|mav>ZuMlOP5=E%h(p$)_lF&q)Y z2}PRNpoy`2;`cCZV6pMzr_G#m_j1K7;c}HuxSYrmu2U=#u2ba-*AYvF%N4Ul%T;qk z%Za(7b&7jL>s0rO))DuKMCy=L!`f2YOX$@0VQnF+L~0ECq}xq@@=Y&YxJ-r(D7SyJ z+%we+?*{snwo2So{3#?yp$_Qd_K25kRdkfS*@q+!(MvVT9^cM2o#F# z63{6w90~MPDl5c35p+uJg+qaMqVx(*<%HM^6+mYbGKRI>=`6&8umP{v3aml)s8{e` zV}%2No>_H)#yZ5r6DLXwBY+kXPn~!r>DSoIb?XY%K^V?C3qBWOGEA5aMm*_Fz~4W; z=}$m;C#LGdnE@0mu-;S%@&Qg&&n2Q(@d}ppTHyeoUy*Oe)YvQdcH#2E2%xb?dTCFM zl$me7^yW)5Q!uQkXr?!zm3+&VlPA&ojMnfiTQDpoW#&w+cGfJUM3Nkxk|GeGHJPJ* za@HPq2&ey67$N=)h=1y5uJqdd zwd1CqXzKCqSE3{&^^wRgZqL^>XuzZf?N`f$O-&FxKqn#n)c#ws;Qi(i;1T#~AwbP1 zEtr;ilC*5pQY%Tz8#BtgAPZ&-zQ+}Q~MrI=ZY^Ho>CRzk>JA6@_ zxmH+RxLx&)8nfGq37O4(4cfY5Z6;~>jy|w7U)r7`uGp?O7db`t%}$}cis3&+NU3F{ z?P!(NC9ei{9Z)~54*P+l#RKh?{C&y8<1maa z8I8BWeIi{Ff(0vB`lJS(gB9#qI#x0Q(P@JF2cff8sD=AiVpesQMzlMG?Cz`IStEt({q?($#I-1_Rxu)j)`S$kn=MkT#E066u zva`BWf92}cP9I)fUA{6Z#l*1me;aVD*L{yyJFi^Da4z1h+qYJ2-R{$Y~kcru3GBKpTpqeGNj+tZMJlEtEM7-vqis4ze_nm3ICkG za+RiWVN6;~8ZJa~T!{2p8nuD+F&veyTxDC}?6Y1VIa?~#bM}!QilpxuQCB+=0th7( zO!i=f!aqlSsni);JgcY$oh%mLHc?FIcQx!?b zUsjH!r8|)v!^@xBUa2>1uVnE;bR|P?uawH!_+rv9HCLa-o^4G94-qw6SY*%<#o%bZ zausESuwg4rtEMU^2uDffLMa}nNTO+}e0wFz<Y)rD={1!&Duq@EE4|5u2v)= z4r*T($;vqY5v53C`;EgNtf-X}O7$o{R?fAD`X?q$Z|Le~>zC?%{vteC+iV?#4@c{F zp;64y*k}yb@2aRlbm>mB1yDC&O5`7k6C&M@Mk^Yx7~a*5M&H)$@)!)aSnB@rHb z@a)#@>?lLD-Y_gx9^+_EE=2yLR=N0^TacV>2}TezKRBA}f0jo6VM^5hh(;7qb<%pl7wF#{T zeRIiweA3RCnVymPe|M8m$HtAgW8^5!EM3;@IdgB@khgwf()bAzCrzG`eA_1ehbv$L zkwg|85~>Ir6rqY57EQ#C7^#ULduQVKiIbD3rI5H#EDahkP_7IgJS1}H@ECR69iv8% z8JCbWVbYYT(^J#3`Sfo^;J@vDmG_rNfJfkmL*Q1ICa#5&+6533X~r>c&PgD-~VysjW^$_dHbEC z#{%UhbFJn0iIa7wtnb=RQ)lV}<@ScNjsI*qcmBQiKe+JWM;~7dlslR)wS029we8c- zoS%R3^FJ}?B1w7by#fQ`Tsh<5mCE&|E0z3jgEd<#0Gc7Gm zJ!VpR(d9oNnx&?s&MjtXj>E}0+R{r+?py!HZvyBtmS&IiRC;+Inx=>Kq4%PlslG&~ z(K$Yf`A8FeCE1&3BV88QTVCOJEZo>~W6Oj0rWd6VV+2 zhAg2)bPkiVX*R3rX1WdXR@y+nK)*!SfmRtkp8ndC{q{e9$AE9423tUdBB@NyiQR62 zTM8Y((lIeanzp2r?()!GU5XCEp*5A3a{4$f6i7vK8ArRp01XDX`e{UomIebX-PP5h za1bV~fBo;?zEDW_#&jionschMf@|-)o&(2AObmu`(V-95FVV4SE_x`PPwQz!2SHZQ z4(ON=dQfK@GyY0TwI;%WvctjF_e94SNQsZ9hgC9adhx^u3f% zQ10^J`CX&$ugBuiYw0!6l}u(B7~S{(2Z40S))E7KmC3AufaCYD*GqHx22-NxKl>X; zp4?8B!({W%93zTW*@k_wdmQm0ck}-LzV^7dxpUw6OFZO1>3#GzKhYfe1U$Op`efES z%rhp@EM-T)Df$@Q4rPv3(!F#MqTP6(ObY|Df!+?;KzlI2XcJ`CCPoi~(l92ooDJ}m z0hIqm{NL9e7dLm7!c_$25(uY=Eg(FO-!1TTCae;B?5>Nb| zQPAs&-{f_%Z{%o>OZ{YKx7n@t8vy~mFAVgbrQLvU7kvc=JDVhMYf=O}_*XoWQs@*q z8M4Ylqg#|Z+Cry$Xm*gLfhTM-GhXkd_ju?lKpW}j>1Yp~Ojpo5>2O*HU(e~A`;PuD zDH|KkpS_x=6jLE-+o3O2ZbRwMyH+d+rLzV3`M5Cd#7e*Bh7EmG~4+Bmq zZDwd4bGug$H-hlB2bBEdhp%Y#3Yn0&PA2@L{SpJfzo3P%a{P_s}Ph!6>+TCrj`0AtB+S}XOKK~q9Y;Q;L8w{PDJ&=wa zvsh65o}PD@R8@8HBBI$;S9kt=UEPm8`ncBo&lU^n3lx_b7Nxqn*Iq+C`}3bKTUJwZ z?b=&!-Ol(x(ao5#3DT-n_uSLghT1uDM6aJR<;anD-a+3-L*wb<#qHaV9C`A|O`8Jo z4eSq^?(Ed-J3A3NZEf4P`~3c4@PhG#m0()PIr1Et7A6)h?3P9ji5$}1*)5$qisk3X za%3gRph!<0)!o)DWwW?Pcmw%^ZXGHedmvXZYZJOU(YO>cjaPX16(Fl{R61x}4jB}< z4A<)^^pr0re;L|6`Nm{8qa1p}-R;JE4eT|-Odm6sl%|)?V={N12OQe`eaA}|U3#OG zDy=UyLAGKQX>?jiT1g3WgsZC_#kZB-T92}<601No%(x0R?_h2SfEGf&TH993^mh#N zoxYefX^c?%ZT=zrkxedroBj_ikLYgg{;1_b8#6J%iDG>z^HB`?b|gwCr+%#N5m{1F zSdwn-rmjw_n_MsP&~MZ|y7S!Bb5j)_`obtn+C|7Q9vYT&cWdj{U&HssyWle$zz01A z>4_&yCi)5XrU8BZ=k)39Y*_PW(CI&Pe1Z17Zg~oVKn73q2=EB-2=EB-2=EB-2=EB- z2=EB-2=EB-2=EB-2=EB-2=EB-2=EB-2=EB-2=EB-2=EB-2=EB-2=EB}*CEhvW4DLF zeQ&^T|9>AF10Jk67Z~uQE zg`3JnD6wi@IVSWb_$knTB}YFM`aR{)RKNBaqn^XJ$#2%afBpZP9QObF841Lv53F9o zho6_#r@+fu1p0vDM|@X&IK7|h`M>Ulg?E@ofJfj5Lx5c}|Nrd&hwcCHZEs&k3YP1^XU*VbAApJnKvvY?IDm_R}K9kFD=%^rok) zbT#B%-rRTh*W!TPVS)4_*kwsUwtBMx_N1Psn~(KYLh!JW$Ia{F2i7^(F0T0D&Afz_!q< z&|as1p$=Qga{w)`I%mHA)uy*Cul@6xYrg&}E%{je+)6+Fi!Cv$D*XbuX>l*?8m{!q zXumB=x7rr!@N5^wJbF{%aV%)JFqb_J?xT0WlUSNzXkYY0;C|@PHZ%a*)9OnumY{an z_`gLR5XhnPka~Z8!iNB3K877%oIX?OUmW~nM`cLe@Vt>OI1h*2y%XUqq6gBjZ;@_* z655uO0IuZ0c`ZaXgNwVn9x_z@E~yK4L!%wu_4Fx5eFAJAZif6BJ&&Q!0CZ>Qa@f(m zlcATxb$%`68Gr&x&Cmy5Kt@NDF2N|Q@MJ_!fT4xvjVB(&x@BVHXL|u1X*E*yVM?#` zr=gtbPh*~b=QiKLK0Qw|LB}6+q&YacgiZrad&wF4q-O-7b?*kmw2Xeoqa=qXE>y!d zu6(W%bOU;P&kUd)AiLNV({%<`3p3C$fZx@$o|p>ykFZmoO+tDTtR~Z7FFl(apyQv= z%nnSx$k`IR1Kt;ZV5!s(8zl2Z&X!6X5NQkKOKA{&T1z_|Aat~$eGT1utQ9@_;27E# z-wa{tXq`tsfsTb|7=_Z39$JW&mk=0hpif6%?1Fhx2n)+px&U%vm%Ix-Ut$|PDj=CI zyx6^|z5wkpMo%}IM^hzKI~{|DVIquFW=NEPkB}^IIjW%FM4Rk!K>dcC$!S;8&|dtl zy}#-Hwd>ce?OofuFc_O%@}jN1QZ@(1k3le682(zw^Xa?5njQl2<+K(O^4p%>_%xl? zn$~fw9xnMRCdcfyZgbjSQz{h(1`OS5>VJ)2wr&-6Fd7P4#cVKj)5$P0NGRm zF+=v%uYe)2x6?;&huB*xVFt0OGmXjKl^w;rwHIA`9L%IgQFqBZpgzjn*@wbi5c7P9 zJ&lpey^-s|VvMJapYkd2e(*n?cEe<8rQe6e{UUVRRoKYi3sVn`N3>eRGbJ=O+41=* zjFK4UB7ngpb;Cuz1XkI#um=5#?g6d(AME|Ze>!fwe>?&=LjYz9zyF`#|NoQS|1X5A z0H1gScm#L^cm#L^cm#L^cm#L^cm#L^cm#L^cm#L^cm#L^cm#L^cm#L^cm#L^cm#L^ zcm#L^cm#L^cm)2t5%AyB?O*1;EAwsp|2x?I|5yCd*T@c6U)X>DzrbG}Ub187Z+Gqf z-S3~>Q}KsC?tSk0%91{EtYqH{fBN%_Fa2fz%dfop+FxJ)TUCk3zrL{Kz`;Y+hyVVM zBX7LFPJvu7~-zkIGNw7obz0#Nks?vS@MN z%&Ph1OLF_>K>57^DNuf2K;nj^o}Yehm9w<50LA`>_AtzbaF{o%;PM-)8%K{r(7W`2W<;|J`>ly!Si;JObYb z0d{5l|Fi!e_Jl3>Ux~r*VSQ+L06-ra9stmXh6e!jp>Og40Dt*+DgW5}|6wm&A#5I7 z0l&XGo6e#Mh?#_~jw9i>SVzE)xq8?*DS!<<0%m)T1h(>^4TL$&{y4NpP7V9+(ALYX zu)*vF`X$&p=!T!39S^@G$7bVqiSfJW;J8pA70G3suG{Svz!tGImX3)@BTBTe>CE$c z^IcsX3I}1*mX>n*@H=;cUXh#RXs%vgy0_OyqZZNb3zSMqz&317bYDW05N&EwwQK6L zDk~8EVc`w=;-L^yz0pa0JzHb!9gyS(rxW*20`yP zwv6d{``%`M%o}!8*JQkv`P!n_7RfxdUcYzT2V*b1GgIxMd!#$o*%QtupeL1l+Z7zl z_nK^_HKqGXz4oyTEE8LVE#iJ-IQU-K2U!=enVgsKE6b0F5~PfMPm?qk6GwYvYPoDV zZa8g7mI`lNwpcE5adT%*pF4ALbLXp_miju#a8ugeNw>7Mw{P8Qc-HW&t@A=%3td-l zv9Q1U-r3pGa`9qa-PKOB#cVU%F86%das~7a@MGu}P(pt#EiHihpPx+{aAm-XKN{LB zVBy>F36w(!7aJQdTxe-&YrAm4V&UT8&RiVax&C6`%<6S~(*?g&?@L~^JpC?}%RTaz zHh2_9OLKc&J@Wl(CqlwfXV|*SGd_BHATs7x&e3nb(-1MzH(RWp@x}SYomniA1p|$E zf$@DCjbgS~E+H+#((}dU;F?;~h=AT*r>x&hNY5xZ!l7U$}qmwbx%q zzY))2X7D}$1O1kKTidhGvg3fJ+uQZ}FCl#dsiuaektUAcfYj5o6`t?GCa(Us#rs3c z1AlM6r)S^3ni_(5^;L9!5uT%?*T4Qc_xI34$lGI{R8vz|2an(BwpgZ2LBDI?+1cHV zVry@2Yy13j#6e@Ay&Z;pXAh)f$Ji;|)AJ6Ks;VwtWTQvNALjS$zYQN>d%lvzg8Bl* zWrj!9)YZNA8tU1f|9si9nwo3Z-g@hH#s^LJ^vsyC3DT-n_uSLghFUptM6aJR<;anD z-eG438mHU0A35^mlbbdL;v3i>G~L;$*LQXzblTdsZ})lq!{GH(-~Z44K7Cu81JWm- zoIcHtZg>t3B$|d_sb^9flj`e{9HrBq)Z5F_dPv>fS0S-jPR#DY?bp<}U6NK6D<*ocIB`2U$0@curFLhq-5Fvh zwN8oMIlvCe!|hcPnKA`aYwh`wVxp%?B2ua_HF1`q$Wq5h5_8-^${b;iAr3XrgB+2L zIN(K*H1P8sBS;Kdv@9b}G*P26)tKG(m0D_#FN-Cy-;9qxH%C|eF_t)x%9ugvGO4^7 zM3-vB_zl#q8PtKf)QdnO$vvz@EwghKK17E8rVi$<%Ut?55bq zt#F=nDvnHH!po1bDv7fs9vY^KRVJgt5LKjd0V)hrDU|C_L9LP^9Z_hQIt-cE82gwN zO2~+cF%nyRQVfO)4!(s-_92as&K-8KsJP`&e9C69a`F@&qiHI*zz1_t`qIt#5q7!etT&Y|`K=P+oJ zfWJHJ5EccU1Nus2{klrdS*yxNj4M#lHPrqlVRj9(zd_WvM%Y_BG>19vYYg`#mMa7< zV{`5d&PZ|Va*e>|MwY%nE;y&!-B_VUk}PWm(k)Hwawgj!T0VO8Xgq3RP<&o|ZoG4( zJuco!+T)#R_F`v>Jv@AqbBKMTQ)RbuR>^&_ajqfuSeFX^M%sZ$hQd_%Bki&36zDZN_ z?I_a&R)nrRm%-RJB@dMM6G|5GQ0ST*=mMQe;}$22;gS5R7`!aQHcFpEV%v1V>xIE< zfeGVKhJ#!2Iq^EEuC*80->`pU7i5jjN}N4+_Qu&)vi>ofdJ*~ImcsIsY%h#0U?xOW z{uO*jhUFiblqOry8w%v3795b%%Yw$wk=rpHi#slT5M2=R_Y2 z%gEP}0ws-EUd-SiEOwjTb4J=Ncg^y0Y$c~``Gn*$Rgm6X46?Z5{Tk7vIMt#!ZfJ3; zxDpG3peP-A9x0e7F7VpgHFbevqca8)o$Z^E0k~Rjd^sjRk@I zph@^OI*8P&B=|RUP<&awZB!zj;EcAzFC+k|8Jy#aw(BmRpuWm6XEevfyD`l`)imf8 zrZ`BIP+*(76S}L?rXIq+bcIJXL9R%Q$spVq}?P00$MP0lXK zI-1p#6*~K#**~9+P{&co*j%?0fXCGuV%(G9XX>a|Nz^$7xLgNA@MO}v#>|srV9cCZuy&T1!y%3tspBp6nH&cn2_54O1`8=E z`8hD|@n9?nmXX-UhYp24zh{BP*jINy7*0)PRYM@|ye0k`(9OuD{Tj=}qr|bQ{P1v) zqHExz>R`J@s21CiIG_4N2mE+le9mR)>LP$lM5f(I+wK?wul)p`(wvXx!G2A!T4IOE zGaf3yj6n1Nj<(`qnC1T2Fo~e=Q|x#+W_dq@!J|Z@p&rkI2v3HfrREH#ix&v}UT8Gz zN3#J!gJK(7Tt5;UJuhQ)5PtlwAn)pl`ViTao-2(--r9&{DR?|DLp87zKxwL_0(Wy93G9S4&b9O)oZv0Q~d=>$lDN1^+$Xlrusb| zim7(u^1dd}x~3~`l^;Nc0)?$|5OuI31*STQ!{KAg@k|M(8gQ=OJEA$C3h)6~(0B<3 zeWVhHqd5R~9b?=h66J0pd9ZCA5o7x_u57&O6I%f>$ksI`R`r2x0}*WNkV>W3rTMDU zO=F7jAS|d1?i}Ez0U!#BACF&Rr}kP+^yOuXVBDSj0>)E(Sh(M-7_^&<-HOCew+wDq z*3VEWse&1*5Vsa6{S3Hyd1i(S?t0MBhpRdbU75!@jqW{)p@(x_;c)LW$i6myzeZtO zPY6`O@oyzdi0n;Ale4Ir9MI2$@#|G1J7T>T?`iB!7#VEHBe7?4qhiDRy{cki3gtKW z4@Za;kOGJ#?h(m?@O*FVO>leZ?`|GM?QJ*Swq6qv7hgjti{By!;v%$^6Jn}mu1bt< zecxl6@PuM3J{1=S2bF>g&ciXqG1rwSw<&orTxw2DGU45HP%&JBg0C-Vy_j#sU35@Y zz6HYem@0-VlqT z<+E(t^4i#WmJRcoZT|rHM_;DK&u@k~!3=WhhHYGvcD%E6Pm%+`8<+*t< za)V|m#aJYa!FQ+6%PBx&C>%uM-_1kQD(5)VhYMnSt2Fg!;()60#U~ZX1E`_%W5-N8 z`hjs?9EyXnG5-YKK?m9XLVZ3j7u7H`)(9!});!I98|U<~`8j7(qxM)mr$`QAtd5Dl zEiMcUV=jBgSptfti;j%XS<(zQ-?4@J zHPIXQYoa_?D!8_N1zpg(JzsSPET%z|h>;xjY)1^XyN7AQP}JK2aJW~85?X=KIaed0 z4$em<9h4^Lpi<5$OK}d~12%Cn7S!5dy^KJf&-tTFz#?aP+HO`Vu*iI62OSAY4C*dA zlD&kYanHOZxP`vXh5RZo{jJrc@w!xCd(><8O(+{2QiHPhl@qPNO)Az!z49=HT| zAd-X>2SIADmpbCC@p5^qNe7cpmXOk#c0fmJLqaYM95}Gx(!kssP?-u7e8&x(R>$?x4^_i6Z_|h?sEst{7usY~;-1yitb2U@B!@hN+!Wa56-9 zxqxcOK^G64#)2Lh2}2m%4cUANoYZPWhcBrH9=?QX; zkNB%NmJ$g zytXqW&*9|Ei@N!FW?+bo3g{D*BNsn+8V9~ufa2(p3kP^&r z;I1C{@WURA6#MI|DTA1D7>2pBK@+P=M8`Zi!>>;n%Gtf@Oxk2x*2O4$EhCXnGOO=marGOc}T2>A!y5PJi{`D`gAS3Op`z?bEMO z?d}iX+Vj++@e2B@k51RTTCpvE&Ekv+u}Td1726=?Lt2C60ndm=pPylViTymy$KXC+ z<2VDS#2d}`jojVwi=8Jgt1J&I!(t~)%vii+O@6_)ia)$s6E^FaGw@W@4_{ubW`5f7 zp4DRP6fMFE*RkiLaPEK~s`<)u9{!LRgCB6g?)RME!QRd~DmYeP(l*Xll{Kqm6mPjZ23b7eyI!BaI7G z#<@d`3nPr#gN<2(j2Yp^xr2=hh8VLVjoCwu3x^pOL>qHa#YM)Y#ysOu;jx0ol$bq@ zWk8&EN>#c$FedHk1X%QXLZUB_YtR*T}Ur)@*R;l1uUkQrB zEA_SMv0eS-qUzm?s;d|I8)=vm4KaBl^+W^#RYxznWmpKMhPzZ{a{x=OYQtR-0d#l( ztqh=VUf+v)19VN_1_BJ^1Q^I+9q<{*2{4fJLmRjS1l0={SMOe2-L%+?&9`{cGFIJq)Ft_+Ze1js}B${7eq zU&ssub9^)dfxjF<;4eoI_{$Ll{&EC?zZ^l}C!aPH07&z-H)|*WFxN*3hrrr4eEx1d)YpEdPIO6O*enJGC+=Io4-6HKpxUpehUDq z|Fo2wM&Bib#+0%q{~%OlS$&*{C=1jGXEfL=O)gw68kR;I7Y#S&!ewNrac-n>p~{#I z*OmxlhOuF;%D5oXm^IXxJ>0l3#<;+k>%Gbxy1ekaDFb!(RHkgJq++{FDf!gqfB?c4Gu+(6RYglG5 zjRaOaqQ)V9BGQ@wtjVa(R8%_!>>M_wHDoFbrgX%Zg+#NFVm2t|plWv`1B;QlrNFu$ zQF(~J5@|gMtaYf)!>IP7$gRg4bY9rzBiI)9f$d%dTOJ1+P#)Moi-is9cwp0kZ!@3) z4{9Kof{gj02Gz$PV=QVwc~Ao_7B#5jK@FS=ZOHPXmV;0OwWx+gUfAwKur23c1Ihy% zXt1zBB@b+1C=ERGU5y88d(% zf$s6LVL}cyE*)-M5CgY&_ZyED!2L_v*=**n?yj=4WoOat+`h9*qJe|%;+7#Ex_djk z?<{BrC?2kUZ`r=1qGO9q+6x+0dDHl^iDehYl{Y1njW0Xgk8?p2piv$DK)|bC9as?{ z$g^|6N959yGOv@+boH0Z&`}&{g*AFJTMxkMk{r!k%F>u?Ji16UB}ew-4C=>`2XdxH z!xaIo`I&GfKx=i=)X09ELH#)LKo0nWE?qOgALPwE#-BhAveQ=w+3CXpe~>rn;1BX9 zhw&$nGpL`AJdl$b-N){NvZmC?ew;x8oWo72@&Hy784d2t^tdx0daj9#?8h0@k0TG{ z^mC}XE$=4HrG%Gh2bIkn3^xW%SrMo|<};TMbV-XWn;FIMhcf&ac-MliHRTQWGw+Jv zsP&oqGdnT@}Z=2Q0E2{5W5m5C-{l252H=VuxCQtv#dtpWOg%$nuz#dH4 zGqd*{KCrKT_8FauDd^73W(%{=%t3`c^?SI6`>qLK((@9oJWw42e;W8pS_w7gpehfT zv}ZLju*{4rODNNL&iy%@ggYOu-nR0)ybkO;bbJNs)RJQb`x-|?qW84yk$J0qd1dwG zl^#a9J$_i3y$q<^EFG>6U3HUMf7(}m;m&Zd70I0ai9Hv*?8DXfuY#Aqw7hY|aEAV_ z=fl;{t_pYsRAm~B_VX^8609~esixki6${|)1ak^2vTXz%N)#o3q{^CJD zWB%33kBU!V$L?sKzAtSbxvwo{&-6X*(?j-sp0@9B8@VTSPs*Mdd#YEhX(vOF@B&Cd zwl}MM&(&EkA3F5l!FRqnDuYj#CaTKMZ);32o+r08YRjjVPv0}mcz)`(#wo^sAv)YR zo~bs@cs^xYBUwJBe5$wF;c9A4^+DzZ1!_%8qH1?z3aBI+TM~9RPA{KooL=5CaW}L^ zmTQgY$CrbF3sX?{v?Q{cDV%2cbmN7I<)C?CLV06a`GuS4CK@ktR<|V>FaArr{<>{< zLf1`V{GV2Sang>I-Hp?B9Qy4Vc0GHbVMN3>dwh^lzAebuFp?_+*JzJtcpkY=270$$ zr(tA>@$l_e3o;(AzO=SFa^1Hc552k;)`urnA9!%bnwQr;x%$w9zg_d{+NuY)ui3x0 zU>&RvDMvBp#(1e}%fc;7w=^y^A^=3d3)P=p8e&*zSZY8_0k&)1D_n&V(*tKG#~3nI zhU^Ff=o_KJ0#j_+*~y`X%n(C%kOB0MuX~@{3ehZui`9xE}GG{Rl?GUH0)TI14RvS;MhFmNk)OXE_Nhqu(Y?3YLfs>eN~WqvV^ z`wW^t^9b+=@Cfh-@Cfh-@Cfh-@Cfh-@Cfh-@Cfh-@Cfh-@Cfh-@Cfh-@Cf{WK|n0| z0h_T{Dk^qVmR0I2ES6Tfqn~Aip`xO+LSWePY-z=VbZbAkUcbGv)F7?ID)ss{dU-#& z!E*2*aj;as@8G_JU(k>AlS8dGN81V}7t)0{k#DcW3|lKp8T*cYa)SZc-*>R|V5#AY zo3!5psmoQ+RnWb$re^Uq73mqbgVMSFF0TR(Yna&d8JaWu+gB`xnKA3Ai? zCafT86Uxui&-as8LXyU8t&~=7t!%rAeHQ=Ho%#xs`R4fRD@qL*(+6vA65pF|K3!4z zH1Wc|gPJ!Vp}*?aKI%UV`cDEqw3c4mPu|XwKMz|VIUsk@j+PR-tDpS;!r*pQ@Mj(Y9swQ!9swQ!9swQ!9swQ!9swQ!9swQ! z9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ! z9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9swQ!9)TYO0r+t?EMuOI7&k9- z{^D^2Vc2Z=m0`I-Z}^$vm-_pNf0>X2Znr>?k)hEj6f*dgIfY^Yq`M(4TC`^mhKWQ4 z1u~i2ErXPph-g+$QgMKAIa8sQ1>aSq^E<8nz!(QszS{E=EV>;Ve%YJ65Ga zbaCrkwhYuip3&GNWN;-8c3~>R;@y`#j?VaEEYu9)s;coy0xtAWZZtN@<=PoF&p&41|srO^M|3@Z&w^!IH!4dY;?A^YZ60)%>Tu|RiFy_6vf5p+E7x$ ztr$q|vxlgkPWt}+-)}(h3?2a<0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM! z0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM!0UiM! z0UiM!0UiM!0UiM!0UiM!0UiM!fqypwmIkG-{=GY_bV7L&hRS%(>n7`D~&TXxf>w1hMM*hA=F8{hdPqb++e=)KB-M5Zggl{OB~uN z=TH;FyVjB6)+Q_5>k~s=H_S&gk`xV|>d;+z-!<5TPCb{KPFzzDG!?fBiNUs$q|}M2 zsSmZT5hf@A-&S_T+|X>jjEZMKm9mSd`Z?6AB48ulZ*gRWi6eI(5>Oux zq<$f&az>e)!%Psm6E`^pCN&1?q3Rt{YHlbsCA6o?DKt6nFsY+V6V=1uZ=`7=^@6}9 zKRcKTLtks z@s~n~ZfmHH{Nz#yWanU$Gs5H?VzNpwsZ`rGLzaQf*)dl-6HCW*SQa)Lqva&wz&ao& zw90j)f-NJO%fPoyIgsNDHNEnJQ*J7BhMLGk2WEB2O|7M;NRczuMEyKeEi=_CaS=6M zNDAMrmH;^!kDDy8hVGCAs|O0fFEQb=k{>x{ks271R&}_!crtdR_K?I1mRk?F515?c z<|=2n$y)T5X>*OW=nd26H?3gr+U8yBi~g|oij_)+^3L@|d!T%+YF8eK{UH~XcIJ@S z9vv$EZasR3)4+L=`+UN@vF_7*@d_P#MFlg@#c%*1}zIIC0H31)>iP zrs{>dE3NWUv(>#SSx8n`Pe|y{VA0ORUVTo1ZHnG(ZK{eE*;I+R-liT-TyL;8!H9~G z+3v6nvZ)gV1?_{0tH4ykHfzN-h8u1R+xP|{*z{(ybW=s%#yz=XucQ*6%$?a_-P&Fc ze-Fehf#dwRS=Ox#5m#?rwx}6ommgkfMd#1r!@>s?9w54`O(3wIn3vIPt%AvZguqjE zp_of)TJH)ot-U;Y^ytf@69u7oLnJyXM+Y(I28t8{jcq=6etpr$%C&_bV>yXmE6*z< zi=1Jm%}%9hN8*iASQxC=Nc<|y9p?yB4}eJuR9CK%Le&@7NRg`T8Yxy?x<*P=A6+A* zs`uif>2{sd1 zo8uXq<4n#3lj}~CGm)`*Cu?&Y*o21ON=p>O9MmAMYSQdb9A&bCvWI^svdr?w!K51i z3&vs*HALu=ncTuek@|hX<+>0_wq&>DElIQF1L+I0v$8=!6N1`;vVvVfrn*6rNfK&Y zXcfC2p;hWHMuvWo(L}C!KMJJ(Njn3km&nwF8L-^VDDFj-P0 z$jdjcF6N^MqF;c^BF|uoh{D6~JnbLZ#QDYIm9I@}-YU z^C1^Vi>1(GNERwbs@E9v&|1<;Uu$_QQMCo;Bd5rFz&Q2cN~DUd;^hA=yNI_8{`GBbXUc zvxAlftqpoK=*ghp1pO}Pe9*^1$g|Cf?|G*Pw0-x_AR5mIvY@h+`rX6K=~wH$aaK9+ zHajPqoHxwKJm??WU`40Vk9j(_X*dx~-SrsK0}V9NG-0F)E^Fv&1T`t01o+H=sDd7V zStf&S01Nq#Swkh1IO#phA!bTl4Z0CDBzRIVl7XkFIdJlC8(2)qI{*fDULwiL|3}%o z07g+=ec-c?Y&NgWZW6)}Lb8*Du!Im6$Ya4|2(N5FAV_(MK*CcHkbp=M0!sun`1lmH zR-pZq*4EngtI>Y{Hu~9*?o3#cAzlqAh*4WoP1LCxP!U~q^Z%Wh-4L|x|N94K=RVFo z_uTin=bU?Ii!+)sptM?hv>+>OHASqBR?9z$nCl=z@iYCWAC&z4LM#%QA@ zMq-5};m}~L)V?;ZKJG}|8*$0;Q{tDzvp*)ZsFji$uQhWCH4rB$tU2Mj4#+-p2w!Yq zH&5&kU1(c{J(s{9-5i_?1~`U2exqp75*arKZIO^D3TaWfH&l%pY{OVsAA!y| z%AUT3-E0(N0E47+Bps@hpp2?XBCb|Ktj8~or%_~4he>U7CW83uip>66E?S8)mO1-E zi(aXmgnCuj9qwQyDzWV^al#y_$j=Jh!9-NEn2Lc(HBZoLBDDe~uhz=%2T1#0;{FnU zCSHss0v3WYR+b<}wXF@8V-8UhM{za$--WqSkuMbMvb6fy@8cA4I`y$XZ)U&G3#0uW z5!BUHG*oOGI7Lw9Or+LwVyfxV8z;zh_a&ZwdJ^8HD%Y)=yA^^%r||We{E=AxB3z+M zZtHmvPe6e2wYrQ&3X8V0m8&Az#85V7X+eg8?K17aP{A>-Lci<5V>qr(pYWi`Cbcn3SAH zM+X%#Pybp|+B-oETJd(wl*Oyp@r?cdRAwbB=Q6pKR5J90|2{n>qgJd!1 zh2lI$F_yx<2u2_8ODqG=DQuAXgI0LzA~{HE^zon(Q_}Il5W#S~zqVdaJ{T@!91j|# zWdN;apC?i$*=1_zOU!B@nhD#U=KjXA&XRs2aw_%}*f>y)2)W1kQ}oU_j&yn+2pe<< zIZzWaP(-U4ISAiwV|iv|)9*_{yC6{baASW3Isa@H#_V%&-G=2f`-DUIK%uYC>h<;6 ztAZl~A&bjU+0ZKN-{&$3Q9gvQG_zx2w}Bs$0bZ$iY_v4-TT7{|hZJ|9!6CFVX%o-$E~y4<}Jcu|*S^fF8AmnLuL zw0>V_xzk9^7K%m>iB_W|fCy_f*dGE3sZwW!E9$(#ZD*yf9oecnmFSs-%|?G*($?|0$&I>?b<=eQHWL zPFQ#=pB%=zVB58FI1Wn&XQUqVVm>n@y8c=LDUUy+__AHCF}U=E?!tfX5-6)Z5eH4m*Ov z$tnxy@SGg%_Ee}lZzJI8CklLazC>ZvoPM9T&$O)5 z?vA5wVyxQJ_uUJFUIVJC~mRtr<}UJ10i7GG3=dn7@t2-eCLv*~|{y zPEn`K!Y4k5z1vV3XJhE}!>^%^eRJ`lVtRR=$lmYrk$`8}esf1i3;+N*x1bwZGE@phJDc4Qqz ztjUq1zt-xn_4Ly58eH4?Ze+*KY(s$P1}8wic&%t~3c6>LCkb-bnO{ zutS))okr?D^$U=2B|^d%(rbAjDi1{Esh>+jW3hUTYX}#ucRSVWR;oVCJs3>QV9Z#` zFm`SfczSEM8sDuTb74s>RjPeRHE+;DhE~(ITH+!xh2B|9NQXx4i$vY?gQ)+|@Msk1 zG=YdvtWyyF;R~btT$_0{bi(Vt!AV*8#%Cyn(1XOXLiK(_Ifq=SPb0(`FO=*zxWeQL zPwl@pasSzT-gu?Cxmmb!Tff5}o&+yb{|jvCugG4IeXf`NCUtcx#}1f!&eSI*HzXx5 zIESF@)mXgFG|>7YBySB~lA8M!8}jUEP%lYrOYM1MGu2e`*8V*9cB&5#YBWB0oAi?N zT?I5^K(JO*QZm&iQVEk9A+2h427xaR63#MLLYXT^$*W$P&E^f~Kkj{vd3Xa?AG3H> zPd3QHEWUa;yMH);w)Zsz^`6Vl{vJq+w+CaX#}LKE@V_67>t#RY&j5pxJ+<$WE4#C# z_uQ4$*}gu*ty_?RgR390YVqYB$fi4|(MW{28nB(-j$R+eUr)AD=87+KjVp7NumT>e zOTc1dSBK+&y~TXk>;AkK?Ywpi5g8-VuI6tF;PN1Jg(ME%D-a9Xn3c!>f0>|nL7=|B zV(pe%slIjwUl&TM$-hqvH2o9{m} z%y}|9o9KR7>Csuc-n3TN$YgKd{uf>Gt*$(8S~3={Apeo}{+92VwXf&#s`{UKDes|= zl9t@+dRa?1u?qZQ=k(L>@o8BNA(JZ+{D!UbCa0-|j}Zs>s5IV~HX*BXy3?4J1-|PX z{~(Z;MmfLX4?^OZU6Fk*U`U{@ES(XZSEHPlGzN6i2y~oau`F<}_Xmsob=BlfnoI6` z)gu1_3%5mE?8iE|CNwd{4$ij<>on8S@pUQtKNcbadh1t&Tg>ckoAu#YrR4pPs|5ExN;o}QPiL_AhgqZ z$8Iz)$Zqjstc9cBq-3I902o$g?vZe;<0rQaqPQYxK$pJ{qnFn4yOlbmqv`(E4yptV z)wNt4{`;X7Ii<~v!ljjHhT3We*KFVnH0`63kIw=Ll<<*NaA>Xd%qoEW7kM2IZVVHG z=DcGgg?MfoBrTkmh0CUeJY1xAZzZV5sFK1VQMD8@uMr`{V$R%4HIYPJm^#U&l@9J+ zot*EJ_6U*Ym^|kiDz3?KayGh+P~PNtXcioVp>_>{6>j(^V0w>`%))zh1oNl*Fbq=N zyM;m~Z2Ms<2Ke;bDK>F;I2Iw=&~S{u86jFF-?w547nd0azO^C~TSVUi#n& zNh;^dGs<(yfuEwf0&G!G(4rp^^xTeVoPgGDzF zU-9yaJ8z+`Qhtx#3{+Efl-lw17C10dh>JM_!LM=y_>@A|dBL}9OFu)k-tLD&z+uuO93@?PixptDGY)VA2)c6fq;@64jK}qS^zhBj7+sgZzLmZ+q4Tg`sFJ?1nqd3iHiNtA;G0@O$*4L`B_5EOtFh!2=3EYtp zaP-`fy)(P+3@v%cmUi_%&{Vywu`jW@bJCu^#ASOzv0aU!7)^gO4mGjw|53P(*p8GA zPKi}uA8>_BwsF~Y$8xYLsV|L4D=5tMo1hp1K}gG`U3 zFFITTQrt-OV*?e=ZLIH3cHlWPGzy2B-Z0#^$zAtX4Y)x%4REb*t0n-H<#s%VbgZ8yT-DY zcR}JPI}Ea6AVE{-3EVk{;ao(yCvfK-hVz<_hA~zE?3|+|Cea?h{lJKaMjRXQ%m`u5 zNyl8;#RsFLI@-|)C!txw{s9sF#Zl1iXEQmQ{Ue(V%V9He6qFu93inBe|D??dfnRT6 z_?2_-tjs=>d|85=q9?(rD%j~K>2d|Qvhp#U*xlI}N$i2-ph=zt(LkMGkPDUG{2(WL zNiI+Vxxf|ntL(ZeF}DA5X9+ob8lYJ2EO$jYF08ql)>#S1hvZn;;rI$~1!=K$5d0?{ zTo*QEpk3g)9ABLz(TTQ!6YYN^k~F}o9{{%N^e2r1i`{t2UY+2)tU)gO0pwR<$j0;5 z>L@2>o#_DnR6z|`%E7V6>qQl9{deT=OpkJYj84 zVt+{L>1FrkVB^D{$Wc1^uDtWn6H2S6(r?xsdO(a8fL{beaJ_sD*FH50^MpkP&g|?8 zF{1W*r6oq;6KY{HM!Ks-^$hcHaa@iZC#$1W?r>eym-}O4lrLXy2DqB zCAQZmOj_H&AdP-~Jy{J&=sEuoQ~SldzbTaV`ou9;(W}|Z;lxo$yh=-qUo9~@@jt|o z{o?4WH26q{m}W*E0d^@@>dB76*4V&Vds*}tr=_9QX^h`VC5du=qWD62PGfO&>PTN` zL`NvCBh?nl=tvzE%I+WwomR6x+^GU=&=sq3vKlX|39@RF(Z6?(>0yh;NH8IKAAAhT z7;{zR`-5?ER$5j%{s$A}tYCcD7qrP)iVv#&1c>TlWij?mpBf|gc2=BxW=v0S=Yo^3 z(rf9-m&WvTu0HwU7_AmXOaFqw0yXrrpSTos!XZJYF7~rq5g0u*5E7T>=2J2Kj6+P! z4`TXRo=dUC24m&UDp!gx6w|?yUE}#r9Ea8#{(CK3>{6zNVqB&K{;LKQRj~~aWOW#_ z%#{zwzpgdJtRu)gf{Z6fo2>K}$B521eOJ@!M2JlXV(WSoo0SqTbERt8>~$fY|A(V+ zouPUi3%F4IF)j>{uUqg=4JNE54xqmVY(D`4KV1xCt?2B7~O{G zoS1P{TgJc;tr`oFL3-zYR~o^b^TB0kF>Dz>5KB^e!=a6eC~OZdl&gX_$=-d+m3?86 zdZ4qkb5!Kn2T6RtFA+j^sS3tmPmX1$$1-~!tH@(p^T66Q%A{#FzH%Z@i3oDLg4tmS zb#`8Oym{GMN$k-)M6k;RqTPrNFNMs0EooPj}QOi>?}f}>Bijj0Nb`5{bS5=bBl$7q>3L80s4K)E7N5P`#j&8#g4 zSOUX7tOCmvb|MeW=ndwae9>=lwEZd{E`8M*QY{*7=hz2TXAnzu<1i}hn>>~{4q8&S zY8;!L&lcsk$>Rj>n8R?)Fip1sa*WY9|BP{t97%{eW-vTSDnz^Na7u8qgSMbuR+HqQ zLspYz#W7F9l9wu^%i+j$S5js??Xb@Z{|NLkH27F;d2I;G%5snzQ;Tdb#^RP&DoQv#5RS~6P5ZIv>`M`1?z zP}BEv2xiD?;;~?+tWG!vR*mdUo>dc&`FK1wJO#%T_~C_*JOE~A3Go`NReEQ?R7!RH$Kh~5L{+}fdp!qcO4#9bHb!yv|+$TsEa-$jS)xK6I zEZHR0c`^)fNuu5Q3pU}Bgq;)FjVQt8a%%_nV-_=s6Z4Y5FDZu6qlcvoD_mbxTr_O5 z!{NB+p4_6k6o8)I{bm`Kj$R9_JD08&6$+a(ZI(NlBt*+BZ!&{4i(&A z@KC|y1zqZN{c4rmP%1oFj6u(=Vpo9b;BtW!ESI+j z7s&rx(h;np-&Q4<=FfC+Z+lD*?j4xxSdO=SIf2Y}xVJ6GS=jl`HRrpU^K;mGf1@RZ zK?e<+w;kNO7B}F7BmAe5LnA%L^CML_6Pla1i^m`L?mO?UqMUNy@fdU823&!AI0yzk*f7p(E-9$_?gPSw)-=)%wcU~zk z`X{LwQVQ{~tqR6V?fcvZ-7mYDY2pR`9}wH1iZGnQMEqeATXrM6Z$8YPu8~6aI0m&^ z9wdhUZ}u(h#SAd5(g@N?4S3ksSuA-{xG-8t*ieLbO&r9a*o;EnL_P|yNfU#`{*bw= zxj#=gPw~MtMJRzO{7V;#Iic8JSb{?j<>iMDzu)zK*WttEAJ`geqtIJOZ zM}(4v_1Zf{xJ`T8gj($#E$pT@_R$iQXdM%=MVAb^GTAAqIg)PiquB99tjOaqo1-F6 z627q)d6J_n(JWySRne45Y!|A!0QIBSzXH=Vdq9DRko{pqc)veJ>U@oSv~L%{SaY`i*RXwr2{@*zW^Z$I6V%p$Ce=uj zmKQ_6Ni_r%OFRtTYxvZ_(tK}R65 zuSqp^tI^$BSuAds(ZZo1$R=Xorm&YLi8=fwde!nD1vkl-+EBD>xMf$!>T+1>!Is7; zmKD6kGJ`i-mO@x@-mYtCgNAN3hH^^iu7^n-jO(tM)G6%I$-*xu_XbBx?Z2FSYVxBc zIi3j%|7+oMH(a>kfzr=PCzs7ByLrVUD}J)7ZbSUWHMdR^bC$+WA3eQFyie?z1#?Ra z*Eh{&HfOrOtyhx*3d>prR3Q%9;cBw;i;;whuq5j!LclmlornQYG3OPGmZvANE0d`? zbEWp^!l{K%6e@*p6uw(HP#9H|R+LqgS(sN;RJ67zm=tKQE7}+S_mkp+5?~!Iv6&@o zX9;UAA&XCcM_;)!Mogsem<@`iDhJeR8EoHZsV!+fd$5olF9=!#)D2<+XsKXq08JN+ z3!u@1@c}fo-n&@FZmk76*u!OAut*@QzR7;I&`%?C)N(R!oB(;JB65g=U12Ot7&Rvc z=l>}Wl8I_53|e9%5&f6>#FxPAZzRkT|5cVCtqtYK*e%`&3dAHLfLHlI+rT7P;h~(S zg&3=NPg|&GV7f+~MRTsCbg7hXq3RBxhH?T(IVr#@Zej1-0z8ArhZB%pT_+QAPR#|E zkQ5+clszCkRE*^}Xb9Z19Se8R7`XdZEa>c=@$7d+UF=mn_F@H9?Zwb}(K#GdT#X7| zh7k7lGEX!}%#~V|l4~Ge3g^1L4g!9o`}7< zSd)gJzFffO7W)B@vk)U;lUWWEeH1WPicr#l#zHaBq*NGdWIm+5uTG$iV~2`i!X)W0 zMSq>qUvD+2iQtmN1WKLvGLBU!_!jBF0Dgw9`WnT3`!Ot&Fepc%LC1yST=A%6%GyM~ z?EZ3?u@Vimaq_&+%2#>*2J=q}DVO!F8Zg+A(%~A@%l0f_D;D%LgskqD8XV=)RbP^t z8Yt#+&sPXD<=1LqIE9o|*8g{RJQ#3K~o zbjf;;>#71*QP?$ph$|BNdym6xVJo776a7Lt?5fbs#^DT_nH|1KVI2$n!Qp{W`dPLp z#Gk!?5wfKr`39)hCJ7V|G#um%#=B-{e|N)ay6bK;R z-gBBr0k&oSz=$(GVIoWzd&*i}(Xf2^*_56$T8h3zXJGvJI+A3bH)rwd#=!)nu*{X| zJ7vwH2exa2t`=Jq`(R3fGgzk}5KjHK1ui~GgCkQnEfIwwZ8j`tbh6Sw+Q_gIviqj2 zVmmfLkTN$r;~?LNCK@a-v>AxR6#pb&11Ua&H?^HGe=1mIf!4p#-N8b?n%%9A=~npi zO{hTlTjdKLYV7?}Y3IA(27{|xv|h< z&0sN?50@wZ4m#n)dpM2<&&Mysvm84xe8`kQ_7w8)N&_}$C*;&spL2haB$@uw+Ze|vk1>Ec-G_DhG#dP z{TxRt{HYuCWs?^9OE-Lb!=zGADUHoJrB&g7`%CXB{XXQEHw-To0jaHpG-GQM!1q>R zS||0T_y-F#B?zz5+MIy-78vw3F|uOQ1WD@}sJXWN!R&}N${j=R{vx+QTlBExX$whs z{??ZfA{h)p>T)X0Go`-{|2tRuVJRgdlvpDLtSni9Wy~&i5lU@!=-ls*tYD%WXA_90 zjz2x~M?HKPO1u(f@J3HUtPnt7~Pp zbXtdyD0kiq@1Q6IS{X>v5xIAo=bN_s#4zaIX%`6eC*K3b?4{vx4FP>x2Yu)|(0mP0 znKl$q(X)T@I>7N7;M3vE*UDkS5=d_p*S|V{Y417q>9jLF<44`m+1MdAk$h<=)Oj&+ zC{+3#jUnrWAA-FKy@8HwV)>b#l%3GT18Ne^gbYRYSJ5#9YS_-sJ;)J2E$SK>fEYD# zv0&>{t2XOk!DLTw_OLblxgcm5w=iLU5ggmBg5nB&7Pm{<2S#dxQ1; z9%xAs7!r8B}^^wJsJIBbn>g?MHzum^9IaqtIQm4C5GK`Lxv+OQGUW(L~)l;aHK zYMC}B+LVDUXL}JijV;rEJpHZdEi+rx@&I@gVS1oY5oQF6V2Yr1ZN3izIpLP+n5WR) zLjy*G0YAn8a)k^4&uew27Wu~&ii?RZ+Ec4Dv0cEiC10rg0_FeGB>v~Y>5$SU5Mn>c zRR6qye@M&4Ud*?sWpb%HJ;2T`ZF~GiUnlgrGjXm0Os}z}PIz-V`gNI%m|e9%(*F7M zu`_1RSURJA#^xEnp7G}yF*9e*Tm$N71j0oX0_sx8n%u<%( zxEDxs(((BdliNzCBOe@IG8D06Gg;13eLpG=!-R@Bgz3>Z+f>X`z~&if-1*|Ir(wo2 zGf-^c({&gO@n}{$y#=S?Oz%tY~(!!D;v(F?vJp*MCsVuv9?W`S4 zZ53ll$nx<9oundHDymWVdIl{G*6w?%f-&89d)p4oL;{L1%mkL10dhu>b5;Or&FlaY zN46SVX~v3%hK46t&dhqjNig;R<|knmC?hbV!NfkXLx@Mn4M2`M0*R%edO9C5Gk^sJ zSy{Vr$V)Tb4qdt+x~b~ysJbwb;bzP*1h-37bR0+BEw#_ujjbveWaLBIn}4dwrxWgb*71VG=s|^gwWI0J~Pl=m7ieEEi7M2h_>5U}guz z;n>%<-Lrx{vSLW9qZ*MWPfL@lrLps;zXxI>kZ~Hwm@vq%zXxI_kbDiq6$ZKcdJugC zpq|peM@O?cH~L!B{4Gh!C2Otyl6|M0<969O>u%Vx)Z00Jm)p+mvD>YC?RNWKx1HbP zW(kvN_ zvp4J6(b+)I>gUhG19_^90RGhVxE$OYWN&jFeuP2!fM5p-E%L>9-c%YuA*7Hoc{V?ZYa z0S^ymM&SlM9H=Q=!*zYOU4yA3G!7=i$wrNo`5@&7RB3nzLA5Fv9VwB9(=b>W6s}WB zFE@O~!O9??UWtRa!t`B3GFU-cvVTjJR#zIP!CHIoUrF#o^VBhTM&rqq z(I2tznkr+%`lo`PGX=)Rd$SO==iWL*_OfjSJs1<^vU;P8hEwOs=uYZGn)?^K?ve6LcA9SB2M<`BdM_En|A65ys(m+??a6%SHt;UkB{QJR!% z>F8{Tfxl5_d1vV?Z<)sO^2LZV82AZ@GYs%9iZk#6#TocCN@oPCY(hJLRR*L{W0j2t zsv_cueHup$MNr^MK|<$lm*p~cH9U@8T?PxS2sg;S!oEbm;15`GdRfh!9*3R7*7FN8 zc*5Q~@44|%O8MrnC+4u7^9R)%uxfh4($qr7@{kfqEIDBjM%dgbul3~bfeT73n9E#J zU~=Xq{eK?pZD6|8+oHN;)hV-8$*gNmPtYaTDM6bZI2RjssZhw0Uuz+4_Rv2HMA|GQ&4MB_3yR3BUN*zQUcR#@I7RP<=x}XE z>%+Ci`F`z+8DXx+YDGcuUrGJ%3DunRoR24%eakD@d-bL>*+9gG1Om8kFkXVg1z_9E{kfA~-djymHEhQij(2M;k>*1A(T*&l}_ zLlxGL*&7Yfm;8C^SoDZIxzp#e1SiYt#DK4Fzgk4qPX|?eYJuiNB~)~JSgu33C=S-ncI3?>MrK@q>+n->l$?R7Jn*}(#^p5x@Z)CscY z%!ku02gjoXvN{RI+BLF*ILtb38B4VmNCEIdbSZ;3odkQ8BG`x#zt}gASios=8BSH^ z0V)q5c>v8Ls88p8HZM5OIR8QWgYLQZxo#3SJ?O6BD!2#XqxW2d(7V!3m2V^|{`Xpd zs0Zh0#f9gveG`pBF?8fjL@VL*|9uq8ekdsl@j)!a2n!ai3i(1D?(N+M!b=<(N{?_(vplE(m*V%`brhDOB!N;fGr`k z08Rb?R34H)_^_l74=^aj^&~Au_KSt$6l};^Fdv_@G+{PC-d{&4fP{7gxD2#o9!y=A zdp@=fdK04p6__}Wc5-1^4tB4!{|u`fI!`QU7_<%Z2EDMTsFarphc>pokRO~3vGC9- z2$k;5hOG!J_-HnISUShvvbR4|^_!~SSG`bmhEG3}!t-a+`IIx~SfJ|6xfJa;oqki& z2lK_$hSe9ELY-JDnsP(ao@?DDI1I8iS9piwlGtZj*BLQI ztV9|0E&@?jDC$CsIxG+t(`#J~_d2Y{84CzmS?Wl1H_T6bAec?Z*qck$5doGz1$C?& z5s)M{#ET_oQEjkTGdbhyL(yUhb~(k6t%b~DvHTWSA4(Yv!lC5b7Br|^vC+z(qUDdh z{jdP=Kxgnv(iyB-6tbNDU{NUPbo7q~p>_HiLU!ERrJa@UoL0(Pb)s^9) z6mCld&T6&B#KhQ>l2e8KTG-ftl0<*W_!|c0e!q|UF?v6ykd#1K``b6gE-G03vmbev ztX%Y8OZY`o7MT{mf76&BU0UK>ymFEL@4uJkE{nLYf3p>faH(Q|x55Fj??`HqYFuMN zJJ4W`^peNWpr!?I^O9)Oh({B)yj5@dYAxq2W9)#NLFG{~N#) zi_{6y5aicTJg1{b7i=1ee+ejBAVxv?!`ERDm1a;mh)7JL_?)5fhN1Ccc{pYjXi6a` zg|3v;V^DuU9eDld*Eds`30xDv1BZ;7nEH?7hf1H$K^k?Y5nA#+VI4X2&s8+WbODqm z^TFm5Qj02!88T>3TtjJ}sR{=4Z7UZ=WW^-5jfm+%N{mKI_~3t6gr;*eHION!HZ#FY z)M6*0TLn#8k!%KAl3F{K8guPUXykHWpB{iV9MU4-CsB{06?kYRgb8R1xFb1IPFl&a zghKfhGF})IBve9>z;@pRF@rb@1hh4+fR1)FP1S>yN^-P@GXB{gVjCSuxrRD7Fie~D z^o+&~jdDS1A&iFM-3d<=P2+UsOSmYND2i4Nu@>3Gj$v#-7#qmT%Bn*G$Jl_y#B!Dh zxdBIBz+xoln1Ewkz+%9JY~!*|I8DVIRx#YkJJ6n2earm;vC>8(vHfSN&2PnIQR}xCw$k zaS3R^Rbw)aoWW9aiL8*EbCH~P!u=(iV-GHo(Gd~cUcPkU(zQ!>FRjN00^A-p5bVLF z7x*{e$E7J+$aXD6)u|VoMGHL=T#6-fnXKz}U_j|iU^nb8aT=%L z-n1KW;P3w3`Ww}-ni|j)ZdhGV8L6cfu>V6C=g3Zg1(ym_UD*7;l^Ev|$3MI1`m?2_z zGT_pvq2?d#zU7v9ek#2T{ABHgEFc>&xJHMX+`@U|kZlu$QdEigxD5%0SkTbI469yK zZ3{nL4mQ!?>=w;~T3=ENi}_gR3ykraTT{dTDccwiY2;f9DUOA(NmrI`(xv=r`3uYc zce%3sD~|gLw-|EeR%;EpdCkphZmW~$%K1n*x%nFCpD<>Nbjq8$%A6B}?f@7_2dVDu zj!A*E(7@@@Q`>5$c!QHrI>>cPal*=2Ru-`y!#W%|g|c?*39VWxz>lvOk7WY9+mLF| zwfVx|Zbp&!-K9%&_cleuxk#Dk!jQuy1fzme0)3gG3{MJSb-iM_MSFXqEN+J@Zx4jL zjj$rC*TuhD5?ry_J$N&USgR(H?mLx!!X{@x5Rv{u27RxdIc(JI-D*m=5;6=JpTrNdZDb6sHE z%0{wr%DyFR=F~|=!RA%d>V=bQ`GlIzmcih6=sFNdY{8l4TcB>hghrXeS_;UnNu^2F zN;tKOc;(LvuHg=rVs3CyxXl&IUbVM(u6T9DJ1a9?ey$u z8Hx_b5on&HiI1i@ZGO{cJ%z>JLZzG(@V1J#bkv8U#DWX;p(JtD1#t)NI*vq!2E>IO z^{wJ&fLO%aG?1wm#4QR^R_ldi#W!O2r(k=C`-3rRCJ?cH(F00YT@~1V_W4}gPZHrn zZX+I9;Li4Avt9?4fQBhF;Ecnn1zwD7jFd)nP;hPD0NwT`RjE!Xz%s^34UqG|#=QL& z7;L}AExUbtxOa&jw-MLWxxsq9cguy&5F{Yq+iPHpirIl8PoR~j1ekXvyK`kQ8(_hF z$-@V$<<_9}4cM#V&O0c7FW@lrQggL}`}zKnr*bxHEB_<6~c$BXwRJ%IN@6784U(5W3`{Xx(czvm#c6z z8yy#b2EX8x!gmNNIuQ9Fn-bTfe>kNaX{?kdWlQ5Er?hUvx^*kotqYEWjdG$TlQ5Dg zskn|tfwsj1pFkXFdY}vx`RnlmBh%GT_ZMQGCwa@=tI?K=XewHY>xvfJueXXK+o-Xc zg;bvQi@o5IDU_Xdv16K=M+pnM9h17-K3`W`ziTf97U5kaRmM{z*>6{~@+6jD&Hl0) zwX#5hi*|Q|*n~Cb zkRCzDFoN@?c4M{vZ*+A|wXgv9P8Pdi`GXi7nCmZIj^z2p%f&KOtuP-J>lz6E1+p)f z*t2)4Gqm5C)W9LLGlA|oFu5&YD5qnSig*O~ zY%t(d2MSG>=jyy8b%lV|6`rmZ+%*n9e1?YXtvXAD536B&9lj1qaMkEXdl6OIj8%cM z!i9mpW-JcuADRUgG}zjullqdN)HqYQ8c(XT)|pyf>q)Kgz)H#EN_AGkI;qkHe?9z% z;jf4PaB59WE#MCUz83I@Qfm%Z;`q)XJbXHL*qOl{UXFK71_x7TbeQ%fHA`z&)~v1B zRC8y|k{aDu(N4ySe5^1Azqk+(*VjK(1Cw-EEDD}$#iJsq2Ab-sB!#T`?lKq;+{Kv= zkgeY!_9ejj-!)w|JvCQq!iB{P*D2Dd!c}*%Cprqn$<%wpH4!p&6zKu_AjGw3dLHao zi3)Z%$V``!X;zTqNdu@^M~a0iF5!MtYlqEiFg;dDVIn+hrmUH^#)E$g*Q{GZCXZx~ z3ByNW-Wpsj-?k#^0B5)h`}bDS=!}kB(2RWa+yukg!S+MHX@&_pne*f7FD;1&opgDG zXFz;e`!CnLyyonhkJfN&1;^S!CO}B)c5H#MAx=~YBd!m=Jsd1tAG|FbJoftFT0L0t zEavQDnr zQNGX(uE}+fd3BY7Dup7zi$%MW>nXl?YJC}kb*DlvUudf9{o<*5l|a$7o_dj0JLaFb zo+gQWy;}Uj%=MU&`EE768`s1OUh<6~3(Zm8$b?P;dtkBY$n6GJ+ZVX9p!rl&(j(5O zQKPhKOxKR=gImO(k*h2>a~=Fnv&H{{7r(#-`q!mClMb=u%LjdIPnBb_Xa;ICpG7 zU2C%~o%-nz^XV{10p?|eLmhJEMs9gcF6zm}Ew9bvT;6I)pGi?9y2Kx8- zhM#VDZbSEmp(H-L!C@g1VXtB|%|e>?|Jl&C;gt;^ZeST3MF(v)Nr+`92QvTEpe9MD z7ffv7Y5576I!I=q*)ZXJ60T|1c*Xpbk!h*+q)ewUDkFbbdfISDvgQU4qM1lsOEg$1 zQ~}%4S;9#)^BzP5 zE%N`k;j+jndqP&v-*60{6a%As!&Y)hu!hEQTALvlEx+h7*8@?|D!=H4%PK>!XZ7Hu zXcX?qu*ioyr!}27O(-R-(Fn0#3yB8bjq5l#3y14CT3z#&g|Wk8PTLf_rUJ)ZRY-<& zMqJ>3UMaaXatF@X`sP!{euMK!Ok;DiqxROTu(lX-2-TM)^=M=~6OU5^o+mXm7q2%t z>ALvHzOf~8vIs4r9X-;%L*l+xqXv@^hU-SNANDKlTFlHXKC}qI6r0r;q0bUkC zf@z7m%N%tek_?>go1g-0AzmJXg80 zgR9)|B3Co_JXZsM&2&6#p5qmw>%3`rYS@~xY9Z@wS@C?~`vqAu)Gkzbqv99o!x!pKE$Y{DZK6>zgLBWeH{DM4YXs7O8%fe} zU<+n9I;<_4*jn0rT%@6MBAxvMS~U+lILa~m2>T+1B+@f%Yz!RT?9N&$jsZIPN^pvQdvL1%)<1TP+lLz1iyHXjU|_Zu z1aRH_NV&b7T#ry01pQ*cbBxKgln7th7F zBd7%8d~jNtSsZ=fg>4)rI62(v)j}Vo0CtUl8@ArM^_i`I+4|Pj;MU>Wh?whgEkdqE zY|^&Sp|cNc3DwB+woxv#D3^-1dE1~??7IXyPCu}vvjlIi7<*W79v!^I7quMaigq*& zK&}1tJ``myuGVeA)w--cg-E!B>B+(#*aA0OwT;zn^M#iC$@AlFs9#v#w6LdB#2VJL zjX*ov>QgAkL}VAqtmax~ta)qOz7zoBqWM^$^My7MF1^sb7Wwg2>hEiov0`giyT80R^QQmOy;E;oFb{pPxxF zn2qF*H-x^$2c6h!+u9Cq!S=w{l(iE_l6-gJ)+B1WkGENGue@FBJL zTFB)}4Y^&J3OkSjtK9Rcs)M-EVfa=8h&*Mk!&INzjcHGlRch_G2DO1p6fgkLh_HYL zC8z8
}Y$Vu;x^6kC1e}_`d-Od7Ag0bD9S)5l&ArNL3RxJqj1;a)zPswu{>mkd z#9tB$$+m45E@Sc@R|KPZ8mohJF^mY%QRyc#784musT|FxQn281-TkW;E4CP6MK?9A zy>nXUv{N;(o4~m&I-8Y12Lox;|9tyT@edcQZrhIB+VX~>C=hl=x&(bErW7MqVLyeu z&yQuw=}Hgf`B`c$i>-xVk%VW+we&@@PQC=VEwxuP`=@5y{&tytm<%J2lckvT*vBcj z;TE%OEyRHd(31gDliioK1ya~z8Xlw}7iwtMV@Gd8G+UoamMFJ%voF`NjVVG$AkRy8 zVI1miR`2i5>lGgCHb=Rl*aNk^35j}q!jHS*5sqrjjoJGwA%&{f+jpt61Rn^i%g%15 z(nA&0oTZ2IA7^FdWI1!3&c#cYEL*tfrXMX|aq}%JSFftBS-WQ4kZrJEKQkI2<mp9F6XSJ5KND*)ePf42uto8>W3Z zgXQg{3+YT)cs5aKP&%0Jlt&g++;fQ$h*b`mu?;)Lwdmf^8%`MvrnH#qeuH5!PMhj* zqk*q4o^Jg6@+59N&`F>*QjEjbNVG!A-=*lm__kM3+w(D4rLW+Ua1&d(^UR(hi=CYo zyEvGOH6Ux)i_x;zSFZ{u6$ie=@!Q(A{`|7`?K|ss?%&zG(=qm_;aDx7>iEqO!;`hV z&GEqz!!K%ii{l?h49^fK6>W3|dl{7CBP>{&sE}G6;BzbooHY1f+WGd*QFoxYE=}@g z>DU*-XTvVTfTH`9SxpYW1Z1soa7yy_Mf=&nPG-Hs7dq^W63QU!3q3o?4{+UuXsSY` zULTX*U^3l5K+;J|clh&3JicoKjMTaCHL2+t#M*q2%Fa`d3R4Lh(Mud$hkgYNx{ELc zHB5Q;B4>1zki{JZG9WP;Ew6Epivw|45d$D>5F<=KY zwgsI}n_Y(%rCj83w!#s^ln=`)dtN5;f#A(@vfc=Ftl1~ws1f3n6u6N*F~+$^t`B! zdoN`3U$cr`tZ^5hcR*LF-}bj^7jcprX9n#cOjfVmSoIIO5-PYuF6{ukBJ7kMcgThq zu6Te1J3Giu6zYZW=P%f;NqvbO!moBgfVxB3Dc>RQlJAsv%XMv&Ruu*3bioWWk0*Ks=n1}Yshq`lIZqfeA6SnN_gZ>jAq?^ z2A>ig0{K25W3NGi%_p#&Djlc__`>MguK|H${Jo|)aQ^|<;QV4JsGNFdL+076JFfvp z^CQwmrjN?VOu?rq8|4!nleAZw~uSY;CbN$?@5FWMDHW}bAM7?*#s zzuS|bY>HMN0`EblYn}+2!uXB7kS`9jD){a#H-OXr9LE{(i5~oX zj|hLKq!VEgNO6uNgWH|R^=)(;o7CgS*5ER3aKrU&VN%9la8!Yv=yX%VTrq|7{TU#fT;<{fJsN=t6TkHJn(r$9A zntMQV|5bC_>JW~xEIyA<3gH`v6o>`q1~B+j@U_Ew0}LJUMYnTx*rF8sdC^f54tXma zlJ9_|6EJ4>_qq!pA!L@FS@_FeF67vldUh<071vYs!2P)9z~rZ!ltOBH z7)JEMdq=>0T4Kbf>PD2JgNjeyr9N<^RDI;gO&JE^gCnfIUQmy)E%hnFCz|KdkzST) z?_u}Ud%nytD7|dtHBbGiC$kz(Jvj;U2DY|wip|77MMmJh_Eil-|DE2a`=0R8`%~?A zRfD)c0@K#`#=hq?9}Me#?I{f@4X-q&?|pRdvAyr^dw1VgdyntS^&R)6?C-N9W_~_mz@Eh&>pZ#y{e}6x&`hjwW{>({n)Y&u;IZ{o(8{XMZSMu?N0JW~Gh+ zPh?eqg;SZ!s(lm1G7MLErz@id@g?Gjurrel>~UMrW6ti_gL*3Mo7p)~hp;X^2uE`o z!ySqrO`taFTrpImZFOzoByEud7xa+M?Ji57sje-1Z)bn!n`M3P5oEX&gSm%GbE-kV zL#QCJ)90K3vZ==|gXtRe>}prk9&8wu&OPEmGUF*otW!E0N%LD;N^T;^%Es;Sn2 z7V=N)sw0eV8P+Y_-a=PU6&M#hQmuQNFoB*+XAkZ>Oy zzZz~6_M=90fpL4z{>L6B`r7b&hYxJ+IBM;?d=xv8DjDAlzh^M~F)e%?h38`M7Z!$R z02*&J?hq6Xxi;>K^uiPyt`(uA5ecFi zY$+{@u$@I?g~_#&-ri>oz8aXDLEAep6T?Y={JtXEn zj(cFha7#IkSEq1I2hID-%d76LoKrd1%pDjQFmv~U@3R^pC>`{fIcwO5AnUboX!Oj4 zA!Iauc6ZXFNoRGaCeu>~4?p!(IEiWCKHL~pIj^#^qr+rvj{WM}udD+DO$VV`Fs|xb^WNu=!Tb5^XOzv4zIgcJrBCxip|rHS@4n~md+za3&QB$R zxTlUBIdbrb-D-;ek5?Xi=bc@PckRl_+2O7+T{QLm$Htv&7cE*)@Y!dj>80y|tbX(6 z9e3Pkjg5}}pyKoQJ~usf@6ku@i)m^yz1BScwb$m)XYc6teG=r&S*LqbM)A;qeqV%Ipn$D>8`1{c=4SJV@88I@0bM( z9=hv4O-Gv!Cd8VZo7Wc>7N(^gyH7dLshD5Czi#L5-5WO^Ir7xQ=B5KrJ=}CK4rM>N z9)It@zy5IUBs5Lh{SUo*s_)d_|9+{kgc?puYnn9Cmiht`&lA5TmBRJA4AY@dD_8pz17*)kSo+@E2|Flztq&A82uUKDM# zSv`XA!QIVw&zxbJd2s#AC8hz`sSG^Ab)ZpCo%|s8z4tEJzxj3`8Z}6zN%!2ne_)^u zg`4>Q689$XO;*|8_|2ZSX_M~hLX)Nol{KZMEp2Ji4XUCP+OjCpbfFY1G-Yd%(xj|a zQE(fJID*cM<1oG>jymFqHVi80An>{}iV~I)9TCDJ4Up#ff6wzIT>zcm`~SS3&wFp4 z=ehUXbIv{I+~rwvmyz>pZEH+M5V(-I^^v=#>|ZP4*6GG1kCLc1JlnN>JGT;vMtS!J zC`UDt+iF@?Ij3^Ul!KcfvC?keyZ7jkg9qMw?;rmFqJs|Ug6)ls$@T5|@4mosJ(efJ zpLpWZsqZE$6u}`J*XzFPk0~VF%oX=)k~ksmOj*C)H5`Q zjfxOSU{_GU)e9n#M1uC@h4zmp1SSN6403d$LMY5uP5iobXV#oZ^tC=a-+aLfwV=N# zu17>7M!l3c^(KK@NS8qXKkh>uw|O(y&T$TEnhu53b9LQSG$5&nXURlAm)V&}e)|R{ zdb!Ll61`Z0u0+hFT5-NOS==CAEPgAt%uVTu5;=|(WO3Euq(VHF)mlp>8I6n|{Vx9O3UVT(p1xX_|iuK}Vu{(^n4pNpPzr*5HDh5j( z6JJ4&R01xflMWVGmX|>;@!{?V_SfR2KKesqH>4G$1isXWQgtbfNHxKx`@}wMv4n8s zl*b`ZUlN~^lZ09^3xRx66|O2NG~+Qy$D`nTRk|9ka$c}RsbU=>a+^;Lb4dNy&%EH5? zWN(s%uUD{YK2i}!&1^uOQ$czwIC3aKvbj-g5p(fOYEeO3s`)lHlB|!vQ2B$2$BA>r zW5lUqCeD`|#QCfV1H3Kry3YBMh7F6G+S}W#N+z{w>ZCSY+ty$Nxa1*)L?KTIp)?KB z&+c>Yn;QPke;7f0ef!83%`Gny|nY?zwBavef@8{{|?yp=YRKyKe9jl`G39gDtqmXH{W_2us!cO z{_!6B;J`-*53wVkeD=jrz~1TEyYGFr|HFTNe3|5tacKW;T|8tg| zb6vcA1+cHbIey|4`}dh2e*B4@zi{d2-qEmz6yH2(DPb(4qKCsL5GjE)`r12A-eI`2 z_Rf=c)=qh0%E>7w?=sx|LWN=K3zaX-cwyEH_q;IGP+nVpvfMDOcG?Tm4AWnj{=yuE zLao$k5<(NI=FXgZ@1)YHQ_uZSeP!{2Nq4JMmF<=74u?~Zm0Voqt|P7hyJsyp6DeKQ~uc> z-|jj7ZO7`~-s8s(A2|Hc{mu3Bv$G>2!onCkcC0r$`^#_ouC9FG?^mvHrt5K+Dn7lG z`rUWmjT%)}rc~l-Q5`lcDSvX?RO@Ef+oyYu^&I+Y{@qJwwJx(2m~?7~!{{p8{ayU2 zgkJS)Q~x#gYU|ZA+s|w}lW{23+wO6@a9b7cfA7ridq3yi?R%Qr*8AI4b5~Ez8#yde ztwv>HF4uva)A6U{FYh_K=aYg^uJ8KQ-Y+xGg>+B;-c=pRQkxex~*EPsjFbS>|%J=QAdih6DzNc$&69cmJYG9#_v} z+qXSBc`{=rQ?!<3(Ce+`5pf8_1(pg5B7N47@II*(&W56I!Y(Cwm$U8D%&HEPBaNHdZavb=+%~U z=U#33%ej^dbB>)n`Ruc`C;c#s(}922Ok;7FZX2tM8=GTFDHv%QVM@`31*?OCNqSGa z_nOD!_1mJXUlRA#GTHWr;Q?5OCWz4k?xNSfEIPXkkFYR^u@T#EeVHZ!H z{6EQ`+uYH^IXIJpn{$e@ZQQ7k4$a@oi8zmhv)f!j9?sME-7n?;#8N>5A^(^Jb($_B zZ@8&K7cn-Yy2e)4I_rV^awkSaOC(hP4(7W9JrU=Yc;|T{u6@_H?|e*e@Ry^%7*n!G z>GpbUHgD$--+umQ8`hts2S%mZXqw-4-gad(%gFfh=Y`K*RxR7KY}4|0daJz)J(cdXw%teFIqsuwjVoHK)23yQ zHJXfi6P7 zed7Vwu8@$RV1*PfU4f_5)e74^tL|I8NF5j% zmym$jc#QSc?SKi_O?)~}VCswOarV>fY z@Zl-oP-&ZMt7@IQx_U*m$+Q9-M~}Rjeoy;X-qi10F4qx(9_>$S8g5E64fkH{eWkV0 zG>ZGr@fTYg>*jLr?!yi&PN#d;<+GK0&Twy4pY)dOIo*DG&p*5q4sSny`Rp^l2?>$n z?L)#+#KXi|MXusC^?Y4xaJDKtBu^19(J=+Hvo2O{x7w%e2z33$^`g7iedXwt?u&cQ zx#FE3<{|@Xh8c8vou$Xpe8l9q@5OsxY~Ipi9#$JN0S}o9e2~!VkL7>zmuU~Izb3m9 zu&3_m{L3M3`ODMWe&fn#ZnA+^o1A}7!@cu+96gRb`?}8*+lo4p^RehG@AC7DI^Nkw zzE9F8S-p1RzD|yOQAa*{e{w2SSistY9=2@H}{3bv@+L+_%2G`t;ch&#CHD`Ja9N zlEVRU#i!%lU2O+;H+HohF*@}QJn5Mo4hPR3C%Oa6WvZZ*+Km{e5s-rOn@MU&Xl&pZL}_^|eGmFV+} zw?4Nj`zLPn8@90X|4z_(&YY3UP?GW=<9^usL(H^Sx46!nS@q6eu6*w{x^`KQwz+qo zef@RMlqrH0)atAW)fSt%$AP=A{rr|!jx3#$Gdv+QHWHKEbz_akoi+z~le5RN-TH0% z@vP(7&D-WbyY#TjrSklv;Dqjin*1rZ;o& z_wAc_>Z`14Uo4qh-~JEpSKmD_Y1x#V9MgI3IA`kR4!hrdvtaD_g6R0TVHoHgNdIQ5Uw^a#%W)&=Z+p6V zTkF9~=YL86%eJ37BDf!X`rGYRp9Ruyu)wct&)8SJhp$fVsH=AGpLptOt>m5cJPDe;)c@;`Vc|>@LX89UGYt9giosehc8q_g5{T#`DHh+p8D${bSw6;GnqZ zjIqm`=T*pQBv3Qh<89S)0gmur`+8~5vjp0l2pJuiA+^nUQ} z-S5(KWxh$Okjv2C#kz$2jGF4Dwt@nh8biH;7|*YA?#sTz@GJV-e{>x_b?o%lQ(Hga zKJ3|OtL6&4XV3OU2}n!y63Nfpa&&R|9=BVICkEWN zjJvGvwxcL4 zr|=crsp>ygqi?)He+%h9k!<4c|5V(!*AKxrT)pSa+2ddDS+`2f6>uLPj?-oFb{f!s zHuP_W{)s23tOWc0cm4D8ZN-;1o!EZzxr>jsOwAcSDkCZ;NQI6A&o`=|)>ZWR{_w&0 z@G}8wHcJ8@d*22Biqh7nF;7;n+*gh|~|JnUH zr*#VMpvmEgk6*aroqcyLP0710dqmbpSCWr4c9-um;(c_MJK4j1=KT}*eSStG=bIuS_!AiVcdFbuGZ1Rf*mVokIB~Qbef#uEmN0FePD9la9iby`_6Zt-_4j#XR-A-Y|p-ZzWq|c ziH=TAe2w!c_UB1FPqZg_6^iG!)^JhY4=4O5?)3J{o7*;?3%wHK;WQDyQL|)V}R)$J>rw?{U4`GV9Cd9_!iB^O9+_E>fon2~a2=>`zY_MZm9ka zU?@C-{(W4~b*blM0_jiM-*fJV-oEQ5?y(m4A33LYuXXC`;?>0~C)ZSa)_T@@f5-Lu zEiltP<@#p#M$g|q-st(`w;6|e4{>LEetPrc$sKvQB77l8?HlgVxwk){yS{n#;ryU$ zA5M6G;^z~8e&_q$-)r%-<x9?0GN$>E<^U z57vKFzAjFuiHXwaBH|1alk$JK@0?kxVL4CQHsK$hE^P{kmIb^$Tac3 zbF@;f(1?_B;-la>M*s&LaGPc#@T5VCOte)d7Rf|gnD1gCx=1;}pXNNu_ZQQ>^Cy0_X>Ewach zK^BcA(h_S)wv4glSc)uDFbUscS!h{i>9BNJ9=AMedC{`V@^{N2%Sp=>OJGex&A6IL zHN=lU^!TzzpR?ssgz$$3!Hon%NG?SPagl%`W_po;rwjxPE95d3s01uTCD0O{z<}^T zs|?f-9$Rw8Afz>n}iORRt=@FP6Hh?NLTAp-Oue%R9) zQVCp08RE=~CITM#Y4DX-6cGhHZ7lHO@zH`sMF1~M1H6QIy+{U!eCQL*8prAGuc<~94Dk=g}g=wH_LcEdq85?WbYPxE=YhJ3^U2~wuWWrNZUT#iy z*7(efap^|9L<3ZipNlv-Qw|*epG9Gz9xgi*NTWuijYvzybO^m3k1;mexN+mgj2Xit z@NuxpumAgM|g zEDuqrg}bf)9O4hy?U?ssAI8Ur2|y;1io|##Rt5wG2IEaw6B-d778w;i!Z&PLpJ+%* zPD#ZiJ}oM)R3cL<PSz#u}!3W`AWk8mD99U+6TDp^2)3jf&Cp$Vn`?gIhIUfFLjq2pWt^;h|2U6p4eek|Q1e zMSx(rNGuNu!0*3u#e83-L<-f3>tG8|_y#AJ2TG)YazKZwCB%jxe_ywyK4+eD9%k7j z;T9%U1Ss*@5^GQfhDAu^u#YN86&V>7791EHqD#~pVpOu&NNuz#I6h7`EHXG@(&Xgi zqLSj$l;Npqw~ZVzN+KUUCVgD`xUt5lu~VWHU9e31ZVnEI5)AjX)I{9i<(nOVG;{a%HeOL_>V=(J3N5V_arpk|CMwM)o5a z%!OH5lsnmu>^2|?W5uNi!XlHIW^EBI zIg*oRZ;_m3cies_!P4)WlXeQ7Dr2>rP1hKT%R7eP*vDiSAf&Vb5>c-Z7~5mo`2zYPd>GwwQ=_C6LLqy2L5Ja z+tXd$xYG!?Y20n-casx~`2>Fwo+yT*_|tuCel>jvCm$P(y&@5a4eo>BehdCcqjVl; zXzsYDim@^grQu$E>J!6{G(gW61!NeS!42v72rh{E3-q^0_gR4S zymk5WQv39y@DK-~NpV4bo_;8TFC7IyRSBrMERDxg7-=8j2#hdl_r~B*2FXS(j zKlC4(9wwJq%}U0~F%G57=fmX8Xf@k!f+?7k5!8>TNoCm#17QZ!)`W#>vO`15u%Y~H z9OPKktze;Hn)H^Tc%g|kn(*iwVNth$-3pI~6?P8Fm*>|KdNV9qRH(fXFIp5a7#6tm zCdYQ%1lq}V-i%1F8=-9H%|Nyj3-S{ zFP2DU3Qbs~R-Yo4$^#-(a;jEtMPG(BYw*Dp555$_TIi^}jJ325bpWCE-3SB7P+L0~ z3M_F^QGNXom_eUWla!TFHwefK=DNh<8(<~oy285fjJo0hRB^GnF1@%eJp-s=b(47x z;$mZQO-5Z-Iu9$!2%F#93cMAC88umiR}?-uvpzhll=QF;%9Das zR#8!LZR$`vXO+~I)J)2pG+<{*=A@GP!um-l&hVl-*gj)?B4Eo(GHVNyfmc{JIeOXB zWi7YX18Uq{uWE*B(b^gsnhL3hmQiR}YI<$a&^jbCWEIxkP_M*ElS(q{Z_qa>D|1|N zec}!J78l>F@3I^96&c25c4n5Amd=D~iEYK1Gqw)G8*iAO_+Uj=Mq68V_pAqJZ|Iu+ptv+_Sz`CHqT*iKGpbicjL;ZH*~KoPMxeoUyyv;_z4px{CX&$B>m&R1-=nC|D%8zAKv$J zz#ZwOtZd0$OQtWGxnx#LQ;WUj$(EN|_Ou*nIo;xJQ7y4Ad2-21OZF_uSv+O2W%07b zU5lSxyle4$i;pfoyO=dcG^aJ^HziW6iE+sa0zoWi?wXt&P@p z>lW+pt-GxUtxoG@tFk4&#n@8PBKmrXT_lQHzJ;*__E(Nm$W@9>O0_yziI>L!6E{K9 zmw%4Ew$?L|^GxjFIH$#`PuJj2xAG&_U0Xa{2%{uqVSf@4s;Q-c!*&$NAm17lU zj25uyT^i#NiV8kd6Aq6*Zhc<+eRpGbBe>u@-^StksbAZxu7jB$U~0_;3@GX3 z)NGA|**7S6RtX-kGXdF>rf)NEHXgm<5k*Cu@x}QJKmRR$F*0oiwGo|GZS69!T zZM%=pCZK8~U4ak~(M5=qFGl^BR#i>wvdy;5CccQzEt*^2MPcM?P)2MlFcgeHG(Z3n z3n1M-V#J77NHB_%5_j1WF|Y(;`Qo?RguhuRBN7T;68{L)L`H-UFKI=bGQ!VVZ7Y=6 z5GS63*@W~0!%%vj75=_~mXdf(a#+(}7M=L+C|R$IhgZZu%3moAFOkT(hc`3wEoylC zdbyha5Vyv*S;1{^E6B%ka*v!-d7zvV|K?(dx+{c9C34*ckCVLolzpA-)TvWC-P+9@ z$Q~CScQ@Myd*qZV0%aJW}@K-}wC;f7-9$6rP7qapc$9;e+gJG2%wS2e{+H&E-1ts?oIC2UT2Or6; z*WoW_T_m^3Whd`-kq`H-cM&+4dk6|}7$*cTi-&|4I?2m7Z}3{l%Ujlw_<@|oe)sQ;^+4c)w`lHom9-b2(9}p6) zjf+>S75I}}ETO@>3cEz=;Sy>hN~8?XtRz7^~4v|J4on?-Hb*VF`Ju4@_QNJh#Sk zN~=>SH0(iv9ftj3wo#kgZr89a7u6Tlr*;3#*Eu;yK_dQAWm{Ajd|+`}@P~{RSHgOg zMm+pa2dKY08cjjB8ajU|#OK})a5 zt-y4p2)3o$mUm|LzSV2n*LoyZH+Ep=!Wtu{T}h#sXC0}Bx?Ive9D{d~*&2z&E|EQg z5>hMG%IltBokS7B9(TFE*Pcr_Y#Mg`s^U<=vmUocia#Dw3pSq&F5qyqo(zdCU?&qF zSt-|q7)PAMwjkF4v7b}{sw&X?uJ)6Ozg>w-Sm{V~X6DHBS5gad)++mK>F_+GcD6@0eUYHZPZOp6V z6b??}=G;h8pxOYWhQ*O+vU3tn;RHb8L88d5qeO+%ghUT;2;aoO31No6gMlw{tBI$? zi7F!UC@`uDtkrfNRYxVRb`k}ra}bsnTtVo&c#y)}U;_lV+CvB~9nTFH;34KBbf*ay zx7uk#7A~EGb8-?VvOa-}jd5p49W*CS0XEQ40YVRH?oqfo&cvKxLUsW@rkpX4iN|qr zeU!)$p+sO3rE@zjHeVDbBuX8s7cRhMV9RYrfGpZeUnDf=zCHgN{9VE>e%O=#os^~8 zcZ>S6^}q~${Wy!Tdsv%IRKzYTux{Zfku!j8v(IMEF)UkiP-2m@2gB@2^H%%pNA02P zHkMiXno>Vq#J&unr6UUM&xzRYkjLXPme?s3os_a>d>;4;+1l%wMT5%`F2F0;svHy8bmt`WeL256F5m~@RE zDH>L2Ff*l?l}mA-z)qJBDOnm)Ld9Jyxl=0r-{KUYnV*#E3mMzdi0?8|_WQ0dSsH*d zLiKjWik@7vjy@+QvNeVX^E3&Gf|W3oYxL3sTM!g0nOs>W728>-NNj%6&?PR_Lo$8g z&g8^ECS&H%Ff-Q4U=f`R&RkN0|IWa}EH1~M9-2fYD}f?qTl8fmOl%jw#9ruP0p)sz ztPSzFXjp8hIZO{lq~bL%neAdM_C|JcM9A!zh@}jZ1cqU0a=is2b~490HchH$293r6 zo7qJgU@)^@yn~s`6U&nWuw5`SB#X@Dm~u$sutGCiBUKs$*6786r4{L=N{u*?6-K13 zG1~3+wNL!cjDJVPnl4dd#oC3CA5{hNQ;h*N>JzFDRVNV-lJI8*f5!9#z;%3;1QIHy z4fUwQ160=~7gf&V`#^2y)EL>1s))NHbMz>PuQ!h(XcolRYuY75uJaK)r;{MX&1sO* z&21JrxxF6Dvf^}Jt{4Kjk#6VqaR7OTfH0{*#k`w2E|IxyUT!1!U*Jq~Mh^p8=Go2> zxy{3=MGo!~X96zz8x8Xm@Cmq8Zx-3lBSFr-Jx>&E?{ZunbE4U86xq)}Q!zUP9aStJ zYHOGk3fC|PlJ_(FRkSk^I|XaW*?T+^o>jnutdh>R!~J|C1rkiElaNWli42HjAf4fp zw@V=OpakF^Mq%)58JheAzAOj?5|9HKgh;pHPitQUDJU64jsp!eC=`aFg2Q-H8~&iC zPDaAZqyq-^JOV#R<H z-|nTH{^u^itT!9XBh0zxJIwc(7n&b1KV-)Ad|LAc?iuql=0BU?F&{RcFkdpu3u6jL z6`BgC7tSkeD}1IT8Js{PZs^D=-r}Ei+(8LiXw|g z78e%JEM8Q+w)mOiSBno6A1}UA99)uIl2=kua(~I%l5HigmHe~hRLS*{h|g z+e#lR-B~J(AHkz2)aM<2&=rgD$mNTpm*F8I@eUsZT_HaEio^prVkJQcW@maI7A745 zgjYXb+^`d`cmScL*&PC`B!dUx8$RfKFhduf<#^^!0f~u6bOC@zJmhB!NBZ; zmKeCMNDME6pmNL)b(x6)D={3(>k5?E-ICyZ$%qijbVRd$i9Gc0Tdck^zho)44 zuL#Q}kXf8W&sf8`gBfpcmBTK?!|sz8)y!}(6qCF#Iae%q`U;usiegs41ah%RVYdq^ zmsEw1IM)@bkYgMgOztoXn1w1}Ofg@aP97|sq@I%T=dDWUC{~D+ zn2NxI8+IP7g5X>6G$0k>9%i-*g<8^juFLTDH=#<5(Sr6^3xh{z4TlyV%-EK<6>tf@ zEGww%BZuN9*M(`PB3MRlwK#crNkdobH-@dDfGQQJH5$wgTDfmfOh}Lu zfip))Fznnkd}o(o=Ys)Kg-jBFdI59lK_e{{kKi6sMv4M3(ngFCHRbMdo>fVSxH_;J zqrd{7aDY^-EEfonx8dz`Z=;HVA^@eUw787KiA_=(-aaU8eUK=m0VoCiOG5Q8DG5cR z-??*Z07kk=U{<@sX(5u5)-E&_!#ANZj1&xxh{Uj(_@tEKw~ZJ*W^6`Yet{_gBhF;_ zS|1opBX2P7CN>U(BZjAq7zK>%JPfo@Vvtj4ctn&o4m$wQupJDV7&T^WdUjqx91|-7 z0x?n_gGC}D`N2Iz?59MIJZWTf48K~TJ}Kok5M~gi6!NGeqqKaAFdPU2F(6cSUJL{Z z4h;uY{4lTsiE(^Z_Jq8NrYJ>lcqE{}PZ=qIF+#$m6)PYDB_yQ)IxZ9FIeGa=xTV_A z8iJG7!L-rpP?a;X;{}y(p>alb0mds~O!+PJjYB;obzz4awJnJKWo4sD*PB$GXo|dr zrV~vOLluRZP%@m9B~93=S9$5&=Tkx_wZ6nt?Bg_Vt>3KJ^^6%$`b{};rM z`hO_C!)|7s4w1P??66CUIvwM0ueyEt?N8mluk@QzPif_(Z%Sh)RZeo>n7SdJiwexe5}uSO2pCM9_|UJMz);Hg6R{GUPgF4X35dVG z1=hiMhvb8NfKNvnxf@vErB=oZhYLm$lIDSf^^jwHqu&N9VF=tHEs;uk1_dbbAx_{f z=nEZh5Q^B4t^C}H3#dqQh>VQEoL8T6gVa6)Vmz`?8Z=6c7X&9>0$wgYq&N!VQ!#c=&1r^c^ZQuYjO66+2Ak%S$L2 zl8ziP45NC*SFG^<@)#2G)jz;|Ty6;Q8b|o*A1~oxSz=;hB4c96_ChFFDhiB=j>42f zDg;W4iBS!bUZNO5B|YL+{9<5nflG{^A9VBaBOCB}#rVq2Z-X28iT#y0fX`Q#pwoeZ zwA8^1TI#URE+mCnbO{Lpo=_)%=X1XDrQ$$cgF{{5^+*T+F(geOQXzzkKZHC08`LiS z4E^=_#`;I&7X^(VoG*k69974GR31cn_~oO*rJ@z2B4nX_`PRipc@jQ!a~%)RDdmD) zP+5p6Y78+We!g0lkl^Ezpc6O)pU*=F6@4MUq@)8}Fwcr4B=c#Bn-=m3sl<;=Mn9f2 zLOf@b29Xa=^0@#s7S@q-hjA_`xKSd*lb z#)o(kga+HMpC4fnF7g#v3+2IwNm9WLPVJ`{%#Vr}4&{3z1<7&?*dRP!&>{Nz>ffLS zhaq&n!P0|!kU^ZO@8f%Z+&@r%(dwWt-}T|*tIfXl!Amn(D*lc`cMW-flD}tB2yKU& zkAO$jeNcV5seSnI=+5Tj3}S@%*2bsuZLuG3D8E7N!)Gc#7atykgLH`o*xvdfTK!h`&hlm~NoAy}=mA5Z;LGqA`Stg#==!KO3C5#y=Z5jA%R^U;uPrQHVyNS4OUFem4Xv1z z6p>LkBXrW5y86QLlaiO#%*(8*3oD6S6V}kVJS-}-r6MCNYDr?|^vLmsb#%QGvI!e^w^S4M3Zw|r?- zX-P{)XH<$IZ26K&wIx;KN|)4*Up}dLdU{oOYDG!a^6@RHE26ZiX(^>CT8up}O&Ok< z6&0B|JTodqo01ZxjnZb8W@ZUKB`dQuGHrZT%5cI4yfg}sC|omB0Lj9E6oTVAoURCE zjYrs*0_>C&N~L@VA7O;3^ncCaZvy-&f$t?aZiR=}*8obRE2Y!bA0CYFhyBvkPdk|A zm%=yZ;g@mnmBQo(%RM;kPk-Zf*&S7P{NawD?r+hvo<*wy-y? z1bXcA-*pG>zc+;fk^<;A`2N9n_|#qbCHTd=nvmb1KF2RT^{n&&jV{b5AFnTeU*+!* z{38abDaS(_@BGgeJWK%b{Tfd*^m&bxg&S&n;bum%2=WXK2{*XGlKLKBZn*zaZo+ek zKa>RXkXOnFLPDBfQi=_--;nZvX(?4sHWos@fKN)m`k!g~pNGJ35Wid1A)(lP4VY9G zkOo2n_r{yZhsfub3fCL&5McHzcNhNdMmFq!e!1a&W)NpSNaah9bG$Wt=AiQM!3OyK zvTjs!kP0H=a%0GMqr8<3e63cX7_il^@Ds%8zk7qS-+>t(P=7@b2uGa3LTJl3N$Qgx z2xua(AK39v2ZCZC9$K?WTfu5Kg?#VCyfyk8++ay>(NbV=U(X=0qnbzn{Qi6U7?6M{ zc&R{}B+w3oh?$V;&tp*e@)a9yA4XpyUgmHXY!eA5<>_#Cf{r*(4?qK_o%7XobHJ1h(%moLEp+Ea9k3O?8=wwT0rKAsZnPwo z!Vo5Y`N)1~mgIa^^S4^Q69$Y@2Vg_-{POX<{r2PgViEuVDqonDmvc}a#09iuGeIsO z@FMf|j|3Ir(;s=VUpVin{s|$H^nVf}`$h2H|GaV2^MO#edCmEtU;_#YA^w}&fb;|U zmK4=jPOnQ!4vQ>|+E6!sT}w%7;`HK_%&5+&iVdO5vl^y{Ms;S^g%(#7msHlyOBpwQ zX>I6+sEU%JQm&$;zLGP9mTm|)qyRarBx+4&Rm$=u21DnD)TGX+x(%g>)y`X1yP_&} z?Y!E^^xDMfVW}mR^~Dv-ibJC+r!}Om$Xb^iniMsC{J7eAEz3&`QI*q6>ob<7XHA}xmSUA6B0Yg$0_EU(#pYuc} zgO9-YsiNt{8H7U+xM&P)f&&&gfQVyp<-GAsP78M;5=03s0$-~z#4HNS3Tg>GqGDNU z1OClK(aMMs*3%$~Vq(IhguFl!WmpomfzOIK@uC7HngXaufm#+GuGQ8hrY-{@oR|dh zazsEPag6Zqd|)7p5VMSms$z_d=j9V{pbCqM#ep$7QK3jt)mGG(V(|{xhZi|o4spc9 zPs5@D5DtPK$(2ht0E5>&ieYFCRZL{TBnSrrSsctZRAh{cp}cqmAR#Bg%q%#Z#Zr`2 zj!&%RRR^aiQU}0T7^OcsDE6W3K{RFV0hS0t~xfE5!(sl3hNqzy}xi^x8}!4Cpr z!3i=X1CAsJLd3+eq@|d1gkTsk15}B;FpU-ApdhFkMrE40tTqW`SYHHepaq0S$4FdA z9+@@{G)s!d*HiuBON&@g<$wiA91MwIGF%huQ>K#%C1e}IP|`IJRV0{2!A>$3ESp!8 zz7|;_I|vjQh?+pULsGuniH$fWbGl)W7?(hJ9*f0A@e+V0b4lV1{M83hAv1WB zMzO#ZtV4A*kX3|kC<+&JKq?DD?ZJpd9yJ8DP)nw$EQ;!+a`)BMm;i(!kCf_a05z2e z8LKE>9!WTn(17X>H66Zgpyp_`kRepUYL}))VB9?~%t)a@oFQmsF)?TsY+6x9eSiDny9GWo&}g-odk11kL|xSQ7UH9T zCmRPqSn3s`5kj$xBanYaXJr(VLh4A25QTaH$O*_|EtXHms2Yg`hE7^mS45`a%|aF+ z(IgCyEQCrff@MY-`1-?(0u;)N>_ej`NhcMIQjy%!fFMy*6~Rz!(jBb=fiS3un$MCe zR*duM#g_$G!-=D*wbO#}7^0`GFpOV8VnSxB^GpeP;)684wo(64nUs}I^72iGicJcI zpcTnNoCw!Y(wXYh3KJCoh?4R>3*RoN5K$pZlEYT;;tGsn0+1JeF%X9rllbuV!S&Z% z*s{VINT#1Me$*k#DN(jkeD%V$uz>}+ZQhq zY7d&T1~}9p)i^3sGnNzIDACfu8I&ln0RAfUK!JQW8e6$0 zE0T(UPC-bP3ieTkfM-}Zm7AQ(G*r095Emq%nX_25Pm~kRfh<YhQx;0sY!ISFb&km zoM%`@BZ}gdkBh05(OD3Jp{;vE0hHSzv7BhH`J4y zloxcyy#Q?)Ver92fEf1-nJjrJU!Qp_!iA$97x)XEi7?X;HIM8=V6rkCR=kQqpFp9Q z01cX0N=?x+p)9C>MuCDE-HrhZhaR#Bc@$e-v$f`(niDm$+S_XHs%@!#s`jngZ)!z# zDRsBkEvkF0?v1*C)%DdS)|b{VsDGsX)%wrtuhtKnS2%Cpya(s~uX+D_-p})58YVW> zG^}sQyXu;hf4gwbikbHigt#tt8yD@q%aGBPbbIaiVpJ328pH#cx(Y+Oce zt~z0uAwGdeNX;-Ll^LNC%Jvf?l{D^hI4WWflr zy-rrORlIzb+_+VSpM9&Mb*qF`iL6N?y`GgTnGqFF!^%UMQ6@GT<(jR5(g9BaQH$1)tN&#@-sbF3+6H)}F~#+q*bf;HXych*!7UW-0q?b)C;zYF@^tbI1< zYjM4Q7if{T+6mfckT>!vI)=QFXXPiz8!|M2_h8!T$oF2r>M1|uLGs@DI%}!`&77}T z(>&zA7__axD?uJpa8x0WxenIeup9FW7&Fh3vhs4wwU{MGT&Xe7QfbP|12yHdLNvyF zrZJY_sKhY`$0Qunag^aGXPUNhGi#ew#4Ki}u>jUM3x@?qBMv)`F2X4X&UDsRJ_FZT z%)-iXP~4(vn^mj%_}ZexoV{(m1wEv1^I_sh2W|IxYyI5|X_c-L1QcG~(J{64&fy{1iRVP3r>F_7N? zJ~70%4?Ln@NLwk2i?Av4=Ed6=zp}VzapIyys4kG}rUM3${+L1cL= zpW3p7QkJphD_AR12i70r*B;_&22SrCWv{dM|Ha;5FFyO+f7l;?yZuKtx4EWyezUc? zz4_th-!}iX`J-ktvzP5(QZ`|UaMm;X(x)a1;idOazW2p@fN<|U_l*9-y`%5F*O&C@ z1mGjJY{`VBPnGo}mpwWGP+vadL1Xg%CAkYHFKbWQ{K$lUhMSjS zVt1OrcHRSNv(P!1QJ=c>m8>gm1~Y2^!HoLXnKA7sGv@q_8O?i`u?+F@ub6Qb!j?nm zQa-|@nLW&$_AN6Tam_)z{ClQH=Z}tq>C}xRkT_jph(-$#Teq9w;#o5nhanCz{e5dD~o=yteX&#;(!+_!6_^7vwcF%_APjB4W`rtP&eEs|D>;C-N&d>k; z{*?!Tw?Xm3@7GmUl?H6%xch#0?;lpJ|8v!!tKKf{#Xx3aPg7Hvl>40!gb5IZf8&nU zu0JB*!QS2}7wpBIu)MmZXG_mB{2`pTTqwV=>w@dmT{x33=h+Y|^76g__}Tm+oCC0b z1~zaB4#t^$Ah7P?58>>m?7#l}Ci%Xez8>zO9%$clyb9jFURJgI=RQ$a-}Rlm$2{bn zf9bJzaj*PDz6WvaXd}YDa|6l>+tO{&px+XFLa#H*Emn?II$z$_*PfmZF%`nB}lE7DX_e#;;poImi1l! z>HEFry$&k;>M)wVp7uAhD@=-+Z* z7(x&3foi2hcQ{I|8Wyh8p{U9)Xs)`iHeN8kb>OO7sG!(sNgW$axd`#ScI4vn`@N1t z=uAG*DaG2m*dI#-`%s1GO4OeqiJgO*RF{tD6{z;%Al1id*9psDUSzN#yqwR&6EB5O z*+eg>G0Q$TTL&El!A{VAnY>^i#zq8#1$lnQ^SmH_;tiPSpnzf3FZpOeIs5yo?%8ge z)TXe>Yzlw-gKEwetH|yXSgP)xs%}=aDu5+zR|wR^ELtZuJ$55AlO+4mjEsozh=|At z8JP#4H`k&j_?WScYIQ_hTy(TVw??g&=$?wsPQ!j0rhzKs@$AARWf_)ZcDnxTI&w5^ zcca_A!{T(BG2O)Bu-RgxaiDP2fmn*bM~?bN!;#iFol@8CT`t!y=MK!+KXSyl3xt*( zzeJ4L=_)n9eG*ky^kNFERPe+Ay^uMmB@QWjSgkMkU*|AZ3U^HHCE!#v>*fqLPT~fjDe0;Z|y(-bqy^8bj{9pH{^G_CB{B|5>uXl1S zHZfM6rOqQBn?`)c9psL3UhV__iYd%D`D5G_U^%$G@Q#~O8MXrWn48F*Jybg8|k7?1f(-k7Nih$s$@Z(h^>nZ!gLo=XNp9$cErd4iHLyf0aOM`MGvPA z7l}rVRtAbhfNqM!f3W;CyR_Rxc8O>`MF3wDDa8T`Yo*daOwExJeP^U12t0jR5VtX7 zL;5)z(*xtBd~8i*kVN!Q{4kN=heCb8VLs@S!G`c)(EzR}I6OR9#hwg7qZY;YBa0#< zBGdqDLPG%_7Db$6kRFLepH-}DSn{yoa6@pkG`=gA7$y%(1~iE!fsI-#6>W^8z_6qc zwlOIQJi-9$ilaa>OQJXW|FMbJj2Sy%f``b;B9Sp&^nYOIqT*ZGxl1aYlpzvL$n~3A z9<;Fp^D)Qe4KnxSGP&r%{D~q_=6Js7s0eP)2#jApE~~(V0(uhhJ4y$PnVmB(k1t7- z$L2xC6qySN`~SV^VR$<}7>n0Pq*}}e7fHpP5`32tUA5XpqQ>QR1TVJQMNIP2GvT4B zk(vZuj3JT>`qMT|QgUci6sIZc&Q6~=UXwY#ctUo8_rdPCkuk$VlS1QSxXACj(~C+b z6qk5!+Zs1AInods5z57OZ7nvH7G!!~-s&UyVQbPz{BIN*7wS!WMswRpZD=$&7(zKi z*E8lKQ+84MMDI(_;0f91ya%4q3}iAqZCi2aIE^21!M5a)(Yj=v7XtE*8+BltIXxpU zJ$t^5JPw{<6_42ucZh9-E+y8V1{h4}q3F8!tMV)OWn3F-Nia+x%=6Y#oMWLx#m9g(h%$U0ZoBP*LFK{lOOipW6m$!WDJ-W8M$_A&rKc zoaPf=VAD7##@%_qAM<{{4O(ZGVl86uRr)3Orcr3z?LOLW4R$<*V2Ed$fgX?5~4BTH@{hyQ>wS!R0{%UoB>GB?$;8 z-Gj9r&`oT@e_-}GEV*kg%WNBgv{6VK1NaP|yu1=$O=jTh$qaq@tV(@(c{#qCOxG|yv`xcd#!-gDf};_K z9S1DYhPTcx9Bp{>?81?ZEy0qNVVY!3geF-Vg}L!s%#DxLnA_r+*`C16u%;Q-G&f>h z4Qm+U5r{`IbC;HtH^#DZYdkA&OTeaX_&(NUV3x*YX0fI+OIsSV*her+*C^z1H}WV) z9@CLWCFbPMz?}S9n3I1G=H$=mpO0^D9D_WJ$YUJx$V47l$Rh`Nw(R9r~s%Z0x|FIQ1a zVd0#HEx4ITIRASJ3@&Q8i_SmX<)HK3bUr)v2%TI&e7c+fPferq-!~!~RTnTVS2U?JZX|Pi{*C*pT&ndV^o;dl{sSmDm zT;|c&j%|@}?w@BX^u95*WN$H-tZG;si;(9JZF~{1luoaD5VP$Jg8!T+Ee2z2~{>XTLnZ zja&TX>6tI=yD(w=)%8~|wd}_BR*)Ciz(Mu_>?1uNC%i*GI~1+1FeDmEF^}f8tMH<8K3dXCS73 z;++z!i`zQcxA+%B4V`Udxm$QY+>PxFo&Da0y$J)wjy;7!yN3!6sM6~!ZC>aF!U3Lq3nZSHB%4DUoav3mCGEPEHoH^q{?NKjy1{3)b z7%3NF(!1Tnc>^~H)=Sd3Bl1kJ7nO4r@Gat&aI3lHxKnY3TnYCf&a~|CwHZRkoq-!9 zp9dm)k}{gxE&mkUHX$%4zm*H&M#u+o(Ku(%_q(}IxkB9A+rzyk z%V8sB9tX4a2>lc$%ZK2a0)yvUJD4^noI`F#9A>(F8Opzec$YX z@;Sim#a$i@n4jRvQB!6nr5xts@sIEdm@^ON47fXnwpMZhxT{5~A>fMUVbnc@v*F)k zP#uR1&v1)SCnXodUBZ$(C6h6%&U_OftZXuT}wnk6gUSLVWZF%Se9FvFr{o_#fy1f zhnU%G_hr9RP|H*6);c*$2%?aH9b|kN=!2Y?Q6v^*aWwNE6%*kQ-v>6Qd=xa>rA}4f z2TnF#YeWBGMdAU>jSJ8yhY=#$t2yYD+ayx-9j%(Gwb0lFhpioXMMPh6{FOsSGtuTe zv|WQYa}51T2{Rrz{00OY1+s)`JyAl_NoRQNVj)KMdzi1WF>T;0+`1fBXP*pWJ_AJq;Cr zSpq3@ceTWv!sZ#nRwx!q((}_rx9B1cAF__|GcdjerHZ`BKw$PbPY9l+tBh37jMS{FawnSN)4Wxl_kn(3|uo# zSmihIv0{l~lZ$5E(XYLb8a+B8!DRX)pqLTrj&O2fCj6IR!kHPMBxcGVc;JEicl?Jy z0+v##Jd7Yf-XQLn&F~A2hT@p}O zGzR!Vpu1?#kS*cP08KCiQ-C*xf2jES&rn-A-f0>T1l|@m2zjCn=-1Jl0au113It4N zliN^nCz4^2#rNj)R`mYx>^|Mg>{?P+!-d<@*x18}E|W>=oF$XTBA3cgac6d{I*m9* zR|?9(P|KP;M#WE5#%T!`y0v!?113>_&L~SW%q0OnpU%kpld@)}@~m?NksO2r@t}_XiLz9T+!>3-^F?5C-jV+(pYv zxL0tkzyLVK72&Q7uizN)2YUfdc{EbAztE8=ZxB?zu~s*88@V;yari{|*h~Tp4Ti-pRPsDiBltn2rfl4jbtvoY zqRP^TpIt;NIl5We-pbxjD~&e}!RqbPWpn9SuF+D@(HwW}a8h+htII7HZ6u3qq7WY+is;euG|{Gtj){qm9vWkf!9*lHrAu!E8kX199pNmB&_MJDFi)9s2b8gH`U@a8HbQq?M-ZPs&mi2ZqE|-< z$Hv|X;JUgy;cGD53EqH!@NfoQ2>z0s*w`^+ywFWy(0!c{Mry|oC{860&>BKU3>`Tz zCI%LWRWPH66O#yHb2?La*Q+yKu9;3yO5~s7+;V8D-!+~n`MpLvY zTBpWQqiI5HWPivKcn>Mqabg#zT8bbtVkO#Tc3PxC+Z zG=Hiq_Rq~X82tSV26kYeQUJ4MrKTxhktr-BzISY_ybXFUrvQ2`{obTAm#+k!8R~Xl zITJsanNZ(O>Ag{u-k|go`iU^p7!?)WRc|nabTJqQ#(K0xo6-7%XsX#^w%Z-WB~z15 zI-O?6NoVY0#-(g9C>8i&!c#%B7A(j61~AYm{j{N7*mIZb0PlWv7p3ph_oZ&`>(@1x z^)A=qDwlg@Xn?O$N#0PY7_dD(0PXMkryk&NCh@Kh=16pI(+oP|#?|_+)pxvpb_^11 z(1du;(gY!OoY4r~vG1zFLW}?bAr5S2es?!XF->o;NUYiSb#t~m5-_-aG~_prIWu|Sa6`CCRqSx-LoX=%E?jSu{i=9Z5u(_y*zNzj?~(gLFSzu;J`UJI1X)yp zAJ652Hh2Zg91XdDqr$4j(PN?k%t~W zqdpnybU?8*l%#p`%ITOBM(qE%x3>3o<4EHG%aS6 z>n2I)Y_6$JC%(5kYu7IQ`WgEfy2ST0_B4cdO#bRWPyQeF)ldIXAr4*t4B>wo&|*2j-*@2M{50qog7kmv{W-IkEn8%=)oX#VYv_Sfl0;;cYP zivK$C$IrOkJ6$e(v9QWh5(tYy{u;YUnGa)T-l)ToRHQw^ZUTW-=R>yWNKeuW8r0PC zV0oY>FhH&9E$iudq9%vQe6fdpuPG<#O?@xD#+f93PBaQ)Pb8VLIDTF@ETlz01rCy@ zb(3H%ZC-dBpDC*)vE&F@AqG0CA|^-A58c}99p&GYr{z6Duju{}9qg!OypsP}r+n|9 zqtNzF2?U@J(uL6@;xL4BVN0=p>$#jM8G0St^C(5MFw-}L=#vHN%B03=9Ws4RGGuBKI9yAUJ3m1g+vBvuhT|&Bfk|P6$y|IzrnA=PrlO8`4M``Ip z(m?mnEBzhC9_I)NHbxnvMn)OJj3Y-5H1-J(!;yZ6^jD5aH>I0%Zn|enbI#o-{dQos zE3#wy|DSZjkG3+ObqJ< zf1ZcgGVtAhKZe@JAXDw1=V5l~8XClwA{_^jE4Jex%y+^-MbiIQ2hscgg+avDLYuv7 zp+COB;*JB5y})bXKMd`-j!_cpzev9Muafg_%bQq1xv_%!?7zDL!vYGsb6Ez*`}pz- z8}xN3WE!JFW`j*EUXkIa@dhALPhyFJ?U*P7vC6nkxbee6N8X8u@?Z z-rM6-^v#Gjni60#CQ3mvbAI^vr>e`{Nan^LKRJEs=g!13yOY`S1Q;IXA*cBruV9Nv ze3B!!MSt@IYe?3Dh2-hQPch#dB$_+Wlcgx1YJot7D4%8_phZRzF3N&43rmzGibLp3 z3$c=@KtiHG?UK*5;RYU3@F1&vIIDON5tWcG66JoNk1Mt84iXf9tm#VRh{{9}w1UGO z^I4+0wOv_xptIm-iMk6vkWr#o7@#4KM)~dGqI{?YI-6qs8F%``m?*vfp~Zex?SPn+9BKv9vJ+IljFzk zyW@x2#NmB-uI#=&j3xBN4>d;ywm33Gln=C$xK8YzXi4hyQ!>>f87Elo`m7|-)jYH3Eq3hRxA+Z zvn}XZceKKMs}<(9R)G2*6sis(nQC=VkXnsC8;TqJ7$w3(V~8FSGc3axGXg{-xn1s1 z+p{knX}Z+d_y)*VYO^xYvWQ=px%8Q|w3H-9C|VyH8W$QLKWx~lVHm875_ul!s+7;D z(G@|TQWH&;@^ohPxpUhK3lJ6^EtfKqqOUCkNkfWFyJDBp5#P9W$dDC7R`7{7Y!i89 zW`HIxE*8W_&Fz5DP`w^81<*eD5HJPkvt#wq(Md^W^B?FXj41jiiprmeD6GA=i6};6 zOytnNaN&AvT@|URY2%GUs?Y6oeO+?n<=x->ZSm|$9~XXnVAltaJkqakaPYZv@#o#g zT2iMQe+}EU`s%D7hJE(z`>Wqy^W2Ruzx-}mLBZ#rH|$evdu0Z1oQxeD~fz z_CB(_ch3jMy}EJF=GxkFI=cSqxyJ`yTlw9q>xYFueebW&HE#PdcfsM0=0Emf{XG>G zwKd;tTC!wBzo4MV$eA-|&wTos>ob1cfAQd@L-dvO@y6j5Sr1J7^wTrn?OZ%<=;QbO zbl=OXqJH`D?Ar$yt-O?1tM~e0-`9JEA`XCw5en7uS}@ z+`awrGegruV|=$Nepa38^Wl`l?dReOSBBq=xma6Hf=C80tIXr7t%8vkEL2XcO|=}& zHda^CYt#t;%MN?9E3dWs(z3>TTh7q~&HEZYY-nuY8~GJ-y0Mksr1dw+FK`!e8nbOh zea-0d1m-UFI0eGQZ2E!le3Z~n=tmzZ<=O6V<8HG8T(Ph=aUMT4 zd*<93(Yk0Il5h@-9XW?uv5}W)NWsYnEROP1CZr7=IJj$Y=TKO|3Gnv`>CAO~DW=cJ zJ_p~f{-pHdOwvbwzJMB8TAp;vEh}2tN?9uzJ-p9sqbe$w~!z9zq z^Y?KBH{91+{lt%Fw=l+Sd6eD6*6xRv^=uBxuq z4d^qV4-4D%HdkydE#^uK*i#mIwa#DFs%oX}nXz)L6sK+=Ot;^9vSP_QI+nPj13-*i zM0Qj}RGzV(`1&!P-=iG9V-yWs?iq!3e zE6Ij9&yg`ZHU{#Rm*e=ek^2^8^h(qbXCpW^b>XfYN3L-={U9)A5{)|$=wWZ9Le369 z?Rc7wYaHS*|1cI4Wbtssvg7o`$=Q*^3FO&`+}XjZfm?wz70AKPo#UKshp3(-;vVeq znH}P?vJvlAN=L_(ZO>7+zX(Mt$lZF6g4Zx%#hF~gc-gZcRt3~a8&+~5H zqu%z`-ti1A&<7B!R>>K%?&S$_@nMO{OiJ7TSr^I6FU`SgNFDzRF;w3`8Byu^ib)>rEaLuNO1;rg?`5Y~_ z7`IE$ahl!k_QE0N$SJcKVU+CwIeV_7IB9BZBr`FWyFPb4&u>I9b6D#}7~>Ja1fFi@ z(%f-}({MnW3xwbs8WbBD8@c?U_Foo79yi<6mxL(ZVu4i{C6H+EP z^PTw(FL<-Ydk;@cw@F&bo3qKD;usLrdX{ym+d_@@9lHAfrC(|^_vMX|KzY*Rr z@aKU)PyR=-+1os|=sB=*WlEXCY9L41yI&7F8GX6n<70CZ`^3Jm{n!+PWRnup4dF1) zAyHlpKqSy{Qi{uD$4UN+b{)-Kr|}ExJiO0uCoY7pTRr4A&zYAYQ_bg4#hI%a1dU<=e;`rR&K8$Y{&6WZa(L4g;ju$|&iwe^vS~51 zMw}k8B{}NXTT7wZL)DdEU3qVH)riaSu1WUxrO<;Xjjj<_(oSD{zw#?uXZ)8dAvUb& zxu$Qv{qSVT@4KB%ow_zQ{kG+|A3tjPrdncSfeo`%I;(QqhA_Xm+Uf~am2)c}t9;_C zxnHGv#s)_==soibn`L_OB#-HM=-^ZyjsSnMj`3u9!ogp`SeH7)Vcc5aukeQ88JJB1 zxaV`yC&TXISr(uoVmWQmcS=R$L?TyRZrCZWUtXA&o3LjmD> zd>9P~ED^YKxs)V7AmI7u?`#YX2QOc~-Iz~9L%ViuZ%k6soH_qlTTn!47I+ok{P#_f zp(#7PO|jc?tcjKc1b7?5^P!;~+aaPI*qaw?q1DLWhb;q#3?6@IayqbZ|SrF0~WYx0~c5_KC8?Y&L1hYAO18*U!5yVy^nFv%3;z0;M9U z`Vc0PYi#fkAc@s<4JMKmp21RGQ{BAf#+DmzV{)5v)Yym(pt#F3y?SO|2Cnn3mwzsk zMfRDTP-q*XT@v=~;5X)1e4&Mj!!@?kaTgbS&Rc)uxFl+#hssXJy}htJp)A2}3cB#D zb$Hn^=cjwWn)JoE+Bdv&KYLf>$Ci&4MK-gux!pUvN9&j|VL#Zsfi)a7LnbRWGtKWNY)3gm;V1Pp}ML12yn)n`yY-{`gd2^mCqk2eZ52@py$29ZIx z{aDJt?~fvqsH^n2qg;+c8Cj+#7X(Z?t8gsPIA2kl`7G%)mgY z2(Yp;cvc~dGgW7g*Hb@mG^x6Iyg}VHB&f$9emHh4wDsuGGoX*HR}VZ~+oQ)l1Q`xy z6rMO+-;QB1EX?E$Lz=q-B5l3dbCxl(vG(Yx>tpSO09%iHL{T}$!l)z}U;fe;L76@7 zu}ppFN0EORp{nV&Us<5F$KAR}YwufG6et&17AY567AqIPeCq=1lPK?LD|xbmciF%6 zuDJEo@+7EDmbR44qw47K@962=(G%S6@f2>jo!{S3ucuf=EQ_GHc`>d}g0%G{yJFF* z7{_1}4-(9+kOx@EsP>SyxPL?1r##SNNv%DxtMTRm&)vP-J$QwFeNZ&Z9QT^Ro;dAk zWi2a-Ox95NuIg3Vr<1&imi@XuD1p2 zk0D&g>Gy_0b&A&KxNpM?r`~B@yJY&ZM;6Um^jO`gy1VOo)%9u$+B0p>v~5pqdun*( zzMaf9A7$>;PAromxWPYc?N-z^*RS{8;bC=hY=%9~X3errIZ=D2c6QC2dE3oJ5!DgZ zGWyEGb;(vCQ_!{;eviM{`s=C4eT!FLK6UwMT8_KWo#xJil~-numthc2R_-E?e3N*7 z$oc*Ax6ZE~Qa$+CUM1Dj2#oN9%wlWODR#F%$##<**D=@L61kY;bQI)YxeWdiPg!b`U6fRmWOO*~a~3`9LnuP(U>Y$8Bnm6;{Q?&JBMhFg|>B zpflQujocFU~(!p6ihk-9ZD^F{T7DhwlI)DL%%4!^pY5+EgneInUCEr|V zM-soZ3d#Bme=&bnsr!Nj! z*6`_^ThY3<$-gd4I7zF7X{&B-XpK28tQDBb0H9dB_q+sJ-z(sof-S#+RSG zF#Y;VD_>fXw&0Oxqexo~M*c2~Gc4^*H^AHdTL@$7f@|>{`b& zIZroC&TOu7O?6eJp;|?H zLsvHUYBgHU??v@d^^Z3;q;5?;J%aXW{etpFfDK$57MTcMV>`@oyU`c3MB)$yvyn3> z$Oakup}^BP$?gDCqM)YHZo8A%Rm5@_XCBz$aya=we+DILW6Q=$MHbxg4W3$9s=y=xup9l@sglsUKe7tX*B-4D!#&4R?>MYj^P1Z(vW`9Q@i1YVAMT z9Wrp_ICZYS{OOTaQXf=g0IfXwm|h$%Ykz7n`$;9!j4bg;W-=0tGLuR|;ux15{EJs_Wm#+<~j zGd3LG$)OlgEpqF)h1iu%M5-W!GaL#w8nx)-rDNrSC65|h`j(KE5a0=p;=BmvLOnFD z{RGBhT?T!1Rdtn1f2U*dU3zqIEkiGqus8-i_{`hnl^KERHbG7S>*xB@v0ev8Mp|56 zT3*^tRQszd6lBqtJUJWmcRGLvhWx;?so_GEs4N;L0*^RBc#OraL4zl-+=p1P2w@mT zR1%#YPXZ9`=~Z$=z6DrLMl52P-K#P{j(w{<9lW~+kaHkHQH`GgcS=4qS8HwLgh@zT&2p*t<0;g zK6MJu^0ab3GcmEYwyX@KC%YY-Bx}^IbOKZKQq{W zTX0}7C{ZPu4#XDis2r6K$%u)Gk4Fz-)e+P@YlxQ~g>%jgIo7P8jT0XQs8 zN7x{ODt9gE02zevFIgb5Ux47}Q`>=aE1?%QmSi6W8i?sJvE@!=qX3Thc9+hRtrE~< zyJIBk_Gh^AWMlfRXNZHT!HY{g|M0nhYHLXa$s-1y6tUDDT^iWvlgNBB4s285B>p)@ za29llm}TQZ$4Uf;_)1XJXO9-z6f`OFkywHB#*qppPrF| z%`&V4|M2Coj8$R%tHM={tGa{#zZ{P3jNAu@%bv^>B*UKsKt1AL=cyo8T_=qGARYEu zo>CDK*cl;r1tNrH(N_Lum{R;>wdLqgUC4$|5+5qWW7jHZq3@-zYhc&FsEB^O&Lw<1 z$zeA;;+*mBBB_X03YCJK9MH-P+VFrw;Ri$ABV9@RvX=x!O|K7md)C$^bcvm@qB7MQ z4wtjp*;u3EJt{l`jK(yhle!_OxY5-RS>r5ss%o@0qlTwBi)bIIzPb8*sh#fS>2}F| z#eKGU->E?-YrEHthHP9qW{*_bB+*u>h6y@ijj*m&sGU>3iSNPV5bkDu!}i+rinKg; zk=sb=6=%L8jV>0Vxi$kDKJ)GJ83X@1y-m^ML-{qiz~Nd?NzHJ z`|NgCtg*6md38O3{>gv}jv?%El{I1bSXn;Q=Y?=HWFp%`miHi#YktH_GzLX4o{U;PBxCSA;gD z)F+*B#M(1cc#YkUtkq(d<@O^s>@57rS_3VQOY{hfI&L56sET$ni9_(`LTC|m;uZG?in#`AS`pO4N^s?|!c&KmTR({10W@{`AtG)>Y}2f?NfXuze5jgE~- zVyQE<3IPY}>j3c|>A*ZD&b5J}4%I zY<6WOW0oB|efmS$!ehc?2%90Fx?%3z#~u@&@I>UNZ+z^rQKKGz+~b>`4F3~PBqTiH zd5XM!g0ff{sDjVbB32O=k2Pwd&?7z+7Uo&D%n!#WZ$mS1!+Z}4-Hiq)d1?21o^K?C z<;E%v$A`kt@?_1N3sFd+!}DSBJ^1)GKPv~ViQYDlc(3cq&$h6yw z%@GcO$u2oW9axTdu*Gh~P7fOyb&K2Iz^ektCa3eV{#tmYF|4Iobt1md?ZWBn8y$rb zv~sQ1%Tyl#hStGDdIB?fc=3Kp@yK+PBvt*1A(o*=#*xp??oi&J(>s!!ZkHs{Rlg6} zy`uT&t9LP}hT9CaSP2BhfiN`^KP=ROjQE-GOBhZvfrYe03tpZ8oV1HUI{f+c!mRB( zz@KD{7(qs0R>42!nHVb>F_2`A8Z~;Hfz+_38O}ni8@fQ?qB!7LsOPrpKQ{F->tk;}CcEo#C!S<@{#!i%6K1iHR`#!B>>CzZdh>v*q^6{?xXy8@=F)2& zTn$9tq4ZAfRnW#Ly7cIk{b%+=gm+`*{>oBZ)tVMf3!=#^f`V>DWSv|muM4UR(li0h z!;c=A>&8-s(TAy@w$%^P`D+I?KGBFP)H}5rTPsH{3rqHo(Zcz6a9VF=TyNJD*Oj7D zHI@60?`N*HrFFw`l-{^)_gUsjt4qJ$eYUZcrLP#NRTx`6xwNHc&8aJHEN*aIc3vxK z+S_o*T}n$)GOgT~i$)x*EOobB*)JTWMtPeGLOFd6j~ln4k|P^i!Fkb2{Uj+_+vHy# zRyoqhWY#QlT3`{Vj8fKqs=l+`Cy_ix8^%}4PRM$f9jQS}YACe{;Ff3!YZ=gTJhig) zQq5a6C&~h3@v>IFS*1d4j;9X5Q?yQH1EA5mb01gltw!r=%6OW`;Ily`60=xpFM^;F zeWQ$))|6HsIDO#9-RBxx9d=Q~VvxkDhA>EKYvm%Sve?Gqq(o#N1z9hO$H0h`gIdR6=Z^#{@&Xd_&nkd5EoDSM~vobv9ZcPIVbh7Vt=w7p+3{?i7K&o2SW>hre^-7~n=e?IH=|;7dhSCf4hOs8)YqXfEue21lpMgu1kN zg6$OFhwr1vQx%3>@w|{n?DyN3+84k*XI~EYEZ-Y0mtW6+$^XPZ%4cU}cyxtw2lU& zBhXrTf&P;L_m7)8d27bU+3#7uc>RkvUak9ko$Kun8XANmp>bvQEB9)8%y}wYQ8c7- z|M*uR#93~9M|fZO!We*c2(g&mh$1`U;l+&46Hc1s;wwzBFY{$<2I$A8+KlcMoJSau=b$BR6VG+) zJfiQQLCAvh!$DE40-Ly_f{w##yBJEW9nnBHC?q}#86s?80gNJDVhevT#dOJ)f#n?UDJO4J~e9Pzit^36N*8NucnGOHUu^Nn@JvU{>)Fd;MZ4l&FKlDnJ%SgLB zgffQZbfihgoDMakr0VGDqaz|)^erxx`~`s%Wc$R_Q7t`ax0Y^QmFzIkQ!AE+DJKX@9%q$CAA40~w!L_rGR4 zJ@NDd)l=xqsu@5%CQA2N_S+6*xJvhK*}I1+1`#c4Y0B|qyNYi90}F4gL6!9%94y$| zx#WtGOqTs5`%N|2 zsvA}Qc=_pa;qD!aK3N200HnZE5J&GsCiG5ZLg_DeO(DlgnRb_KyR4b}io2=$Y-~m3 z%drP_*}8)0`+%uV%$rr#U^}n66r3>+ugm93;LO+VucFeW$DRC3 zbYq}GQtm6m{CmuO%HTmaC4ay-H(UQbz5%5EHNIK4@HjcF-UZf^;@rTr%>Kd4 z`!cIcF4K!wj774^JQxRMiCmM)-_N-J`I1Y$f}TW%B( zn&8aUe&X?upStcS+@UXA z-nU`R`Q^DebQ>1MZhrU?TiQD-53Jg>>i$p94*p{B%gf(?cHgr{8*AusJU2K_EN=*Z z|M4GUkHj924qkaSdjHB^Ef-hpPafn+zcDGsRYhmlzcF=sB8r8}SzYc3X300(VkFUQ@|I+N}Yo;{dex~ICS zr5dkGYONSBLd@Zq?d3AxS>r7v2xU;NeJFjkunP5cb& zhZy^YhQ!vGl+kfctxae$s%SO+3bQ&4WMKJZ=fq+hm1i@L%4Gfa`OY$5P+Sw3VHDBg z)1BjdWTQGSDb4!>X75B_Czl16a1*Q6JfZY2)~!HoVun&&_s%;l*u89QL63oMum6Qr zD^D}^q?bNUkL6cfTwHqGhC5bVoBoI--+vfAQ#LEL1^Tf+gmPR_0NKrKF$Lfsdg7*MTc~9)ay3XXP-6&y&A<7G0G1*6931nu=AbYl0P~h76QOURBP0|_M zDPQ>dar|f*FAEGDeow#QodbPwqCSf6#4qJbu^0e~S@!m#(dDz?;_G_X?YShtrC;=- zn7?BsyMykbr97?ePy3UvNP+%dtRepzsn0d8NLp7g3~R`z|6&cPm0H~Y9;a{r*Ek(0 zl|h_~8$&;*Go{<>>OfX4N{?dezFjR%xxYjHM+c>@U-Qb?KgAHMy4d=CmMmMQ0Pe;1X)Oo_jM)5Acha3% z=wOw?V#)6wX#2xovU`yFKm^pn}2e%t>R zy3J(hcJhZhlJQ^0xDc8$LF7$f1}SDtvrOZs%co_gr-Pa;FhUrJ5!2V+xN+BAFY({Y zU)o?avUL=Lb|4u2%48N^cGe0GqKWEFy0>~fIQC`hN1Ixop=nvx5yO{_9Y1#2SmW@n zoRpR!mYEW;SM>tDg{~}pf8dVZqd)%%Xm(BAlXZI*jjU^}J5(olP|h8bO#L4xnd|=& z=~f$?fOLmydj1cP?xBB!bP4NRdv&pqdv#Xsd+;I4NTE6`D2y=;tAlYS$@B>d%0#dg zq~k7KZpIx476T2$a-z8i_*@Y8IVN|>k3s8|3+KZuPMX>bz7NXn=T7sj8_z=9XJcz) z>4(){Z`;b1LIc)W?kZ!pC=_^snGsM4AwsZV5VY9XYH`&wCntpx2;UxOmt2xtIws{v zBEpz%g&eUt7G!s(7SDO}aUiwG97jsEVDL6-jfN3n#^B+s>>30YYqAbP8m}Tn;yg+-LT>3kqE*^q6!;N^;qyBq=mr6V4OOFnK(FOkZT0?=$S& z*4NAtV#dcx7!?=12Wf6QK9Gd8wB5=XnFQ*JK)e9;KFkmw7Y&;7q=J&gCrivH9U=h# zHjFGPz%DfQX8te5=Zjqhw1Sq@?{XCg%Gh(4ZxX8~9M zfVA({NarOy{cTV~jm5G-CRrt`8~Q##VzH_1EoaZ}+yCkJR&4ogs=xBcX~zEjr~mZg za1K$r_3YI@rUUgkT9_b=PaB^yWrcCk$VDR)s&3MrF8W9($?9M?70FvQ?Z9}{NmX6$0{Re938^-?eCLiqIn z_a(PC{qJX_{?btQPA5zMmdQk%#c1>EbA4tg#w!fLw3hle-7o}8=h14=(+vh=1fZoJ zm+AS4`I+F7ieM5jJP$3a&>yx|J%66(Bil4jn(vmoQn22#1I~kl(G1R`x4u%8XW5M9 zxnSx5=VM+tpRXxvZau!SjH##a!Wj%M?9hSIZ(Bx|fceQjL9KRR)S5?ynIrqA3oTjnokM45uNY)B1lUF1we_<5y8C+E1*YREKWVW` zb3OgHymb{>tSzMQf28{+1GYk#d5%y+7_`qqZEQk9;`||l4PgNRYPFmc+eI9$JD{W% zA_NEA=i;RBrP432-gND~3{5~EA3KJz5J}b~JTZ0+M#lA^pr9sP!vg~Y&*A!~k4M!c z*uK^%$Kpr0Ia4915z zHtS}<3KPi1^2k70AQ-MiHbozY#5csevqcl!q`@Z{P5sMQBz|kgm>@q^W!@f zZ;2fr+A|=K2d{YcH1%fP&iutC51bEMx!%oH_0rdMx*B>u_Ha_RJsL;vJUM9ae|F?3 zqdhriS>dvRWd+IEMpM`Roukw-N?3R#uB057GG|3B?O(kz&#kQLeL3dZy(iWbfWOaq zEGb1rwm|z0ukr5`(mSM=Awtnj;ivTB<%IVkuh{Jo_UNSSs$Q3e?p#^5z^N8-2#v{r zkX$T)TIgwE3DlAm(K@-13))fpH-sBPwEnzdU01EYAq2;b(2kkI zab8J3D2;&yQn?JefuNHpS{KQ{0^4|SN*`|Qs2!u<UZ< z^n zIK)_F$XjMC(i&|UsuZGP8vlS{M+?*H57ZxMDx|p}*jj{9!srfTA2{~hU<1&ap`mmh zsL9LY&wKC>?tvT17CE2KD&FJ(y(?bI$i088E59^oHn$ntcze$7J zum`kovI>}lDNO}UTd%`8i!K+fnzH7P8T_Vgu58veM>X$i-qU>Tw?m)bLO(}me?R;D z?4M`z&e?1|>74T>!Fx}d%RJF@Jsu~l4C1IU$;>V)mB!CU>!&q_2?LkSttkEKfVA(} z{)KZ>`xp)KfB>!Ay%)NzJZikAHU!qzUL60+2iMP}T%PGJb~wz@DMn+ru12FTv)AOC zmt9fucHNBUqDF>?jTpgn(`mvPa$q0#UTKW936{gAV|71V+H~zZw<{tk$*F=NDStmd z962W&>*wA$LBDf1(K@MO&xy+CpC7{W-txV9)YQDVV$XErFVeL`?l&*k?IJ$j@|j7T zJm1qfmDMe!sk5Ik_UY3{>7&*9D3#vwOPtby`j4ugqu&eFYbxm%)fa1}^fM-Odf+!4 zs%zB-J?07%2!b95Y#(6=0gpb~?^3%%ojT^w{+@MrU+e3JV9}Y`OdK^S$!Ij_&^tVN zp#6vI-b|im?2oWGj|6Kd%J-yo=`r7U-aC8u(19vbMJUwzr>^Z_qI==e+F7qqIbrV2tZ?HxrR2_oQZQdR8T0s%vdj6slRyQNY=W%t)WJ}G13@mFa-EPPAGu! zY!$eotDr`1s#ACV{E>yvqyNQT1`la*$;qUoJGBr_8K1atZt{Tmuy8Gu@A&%#1Ymml z3nheShPqryC&A>A#OAkUG4;u`zP_>|D`&&jps2W*p-8M!sbtt@A$eO5b~?&xde**_ z{Mph2D=%NhU}v-$W{FG{p!LT$Z|xUHcGW+zB7Q*EARg-FlOqw!-B=X?8~R>OLLH@y@~=?lw``}brh9Eq^uNCDp_dnXTF=^#`g?nD zRz}6)`VWMu#{OtOYkPb9b+^lJ9^R^uy8Yh8tN07U7u*!{L@==}c#8lTZIr;auus^L zx+C?KL<=WmP;#2edtuAa%SL-q?sUe)EfcqJ>1~%y%!d<9fRU66B93S&2d(uS7$n#( z1AO7^SVU^&K`R57jf^to=i5oTUlvRFR}K-=8s3kj4zr&ont>OvWaKrBfpYi_1d5m(4i3JuIIHMGWM+j4^<+aCKWa zQVOxRO7iA{?U$KbhWlH!;?h}-JT+bBxdW#JD<`8wmlVyI$iZfA$F&4HinFcn@~oas*d~T^gOBv? zv2@|w$+I4qIl*pnrCgbGeT=kFdiLBmT{_L2aHs3i=}tFu!mI~a{=<5(5;!iuOQ#>c zS%ng=jPaI%_Apq_yeZz1ghMNdO^}MO4WBHGXP8pOE0Low>cab+y&U4uEAM zDPo=}(Zq!n*HyF(SrQ!M|GTC(+?H;RU@j@TL|2mUDyTY&G;6rT_eKs15A7Tt0_ZI= ztx8Dm{@(7AqS;e+^*mkRCykJj-N~($&GEmr9&QPC9pw(?pEz6nx;!(nj%)#oa$Mx%M=8T@HgOmDL0l~bqAo~^DvefrcXsLj@6cZ$HG>Z+=$YW7!E z01o6*WwgB1H(HKN3al~?THn@la;UubeBrY)g(?;ve8*VyW~fN7yK(7tHv#u=hMM&5 zgOYh4uzdCIL%<@O!IQ{)Sdc%0BJUyIG=2NwN#A~LY5ETUN02ViHxP8PjQ#`Kl0^?f z=v`d`y4;1(XvlyfiHh_efD7VyOB=`(Yw=;odSFzCWFjAdAa9zrs=@sSzzW1#c3~XK zYUr+l9(X9QVjRpov{muQqmv~N7pUMuF%}ghHCbR+OBWUx=Vgu-d6T6lQt~EiPft=W z2>vOIWQBhNTSWRok%<+)cn1n(hYbt^7E;=)7ck)I(|4jfFgGxEFc8o1YJu(ScVat9 zVn{AM{r3OAMt*FY1oIa66LLR$s%}`#s717qo;f#e8`K?bVv<(xvJ~K9!$#X!o zJTbf?t}^b6__6Wx4TH3yJhL5=n7A9HU4sLX(-vh@ePv~R)B4-AN-BkY@|FcHh!`3gIx_X|5XFMxia;q)3X_7RA?_aTajr8CPG%s3wA~GT z3_VeD82^d`Yz-FWS4GrG6dtUUo&xrX&3NZQCGQbUrT11%_5W; zQ6gA)`218=PGezhY=u)hPa5yCQ!&%0MHn%B`09qn`fd4+3C;=5#c=GJ?eyodw@~@0 zG`xy!4PTyPkt_vu=V>@vBgvGqbdMjb)91sd82N3?rfR)j>+0%qK=i1TUNSfgHQ^Pp z`GStd@-%<;8(&9R1SQDqCR*oGGM;O5piFtwHg7uwt~L}noP=#K?T~sUYnjj@23Qsq z(MJTF!0pBsijX{7R$7wx6D(JJWiOk2qNtwsll-Lf;k&gZT2WX|1Dvqd5_{AY?Zo6Q z;#7-&jk|x=O0w@YpNYNX zzAs#BB!@7od4KYBkSf758BO+shAKmLOoO7z^(t1v7u**@zl%FF^s;Mj!G#MY4wv0Uu9Nd238_*Bb#0RKD4LsyQGfK!vFtPH;Y)^Eb_5@ZR zA5lKQ;sXXkq{>Dr_8eh@@Pwsdh(-x13nZ4`ivtAYYC&O}Rm2xsaXbDPO5Ci5!bRfW z@yF2OX62uQy_p1Hi`MbSGO?&32Fc1{wepk?1kCEy7VK%OSNz1=Oix54u=%jUEJ{2> zoWFn@k=?ZfOT!nQ_Mk*7l-&sy2X9{Mz{bO&GPC;(yfVh21~W3?Pk>^(jZ72zEypWu z2nYX^RjHJxvjiJGiOuuOLb{NS_}SjL?Rm=`asmklb0*7|5~Zh*jZ!*Xd#)&hr4`Iz zCiIpGmTZD`@~yInCyQ)vDtK76yh&~qD_3Qp7qrz$?#Krx97eAI{0Am4I5de3=e7XZ z?;sZVYm$Q&^{O0Gm|>JBYnu8G9lE6dll_0~J)q~c?)4G%y?&lqKeN7|y5wZZ9!IiW zuy^g0+$lMb`h@s2DOmdVpV_nW?FF~f-+HcYl7lmayk;(itByWdlAQlzG|kWVn6idB z>ync{CN)K4YQb!;1yLLN`LRS`^lgQMiB@BC2X=2z!K|G+>jLqIyP&&p7sL!(cIk?{ zE?xhxhv|b+t{%2CtbyGE11;UaeI7_;XP0>TQkzN`;OBM)FpOtS1`s;o+eWS&T^cK182Gk+IJlvD@v=Gx+D*KAvp-WWm9~A%h*y zeEO-~>M%3qlEFTDtjbQGwpMQisegN7vhcLGmUuxu!Q%?i6SPsT&cLu$+>6x=&<&5% z33;HEU}fX){f8pbwg*sXFJnI?*BRnSgrK(Uzsvt64-}l=68=rAWp%NAf_^E!Fn!O=^B@RI6_`KmCNAziG%D~hq%HK)@!T7 zPq!=^b4P#Eho%=6h%x>p)9w*1N_oa>-N(<$_`)|Giwb%1dM-M9rR;|K<;d}|O7c<< z5`h_8V>gp%Uw){Z4+r@XYlY8s#OL>=IwHw-l1g!oI9K_Yk8abFg84ZsG6yM_lXRJz zJDT&5Zn1K2moomnBg5rCn+ONl^9j1C5xx#xgwCuY`?x^Jp5VQ_0WVQzcZ-8nVvU%r}b-6uYEq+J`oEAlehrw?MYN>8SESR z$xRU9l8p(@sL?5jhDn8GROm;=BByhEx|?tw(xlEaYo}(Y^Lwi!2;Pp)-e32+ye|pD zsfK5L)`h&YIO?k;^2u1h8LVoZUIgflG8}P7_Y_gLG zMzD|2IYtp|VNBUXw8~9tUH(af=~D3X;;`(CFvk7TQ}gt%jN{TikUjF|q)h}`Ki(O& zw(mt>|A*fVdUp(aH;cE}BZ%W9@?lq2k^4gKFe5N~zIqbW=qN;p zk3ELxek4Mx;jx4vJs^FvC2~U0mqETk6NiZ;j*PwU@6#u4^0-f5;y&SgWq+3`#D~R) z#JlZFbZhVX*!T0WCDxMhk9MEga}@ZdI!(}tiZ7>HyB{Tvb>!i9$UCq51o>?8>HUA0 zdlRsxj<$bza&oc|0!hG1zz6|caA^coq!l3`6>Y5$T)P^jg)T0rZ9%2vBp{--7Oicu zwJOxMc6DhVwVMR7RK*2tYi+d}K!pbvqFALQB;RjN0_gKR@B4rM*L!`}_nk@R%zDo~ z%bDe#nfvx33auaWcca`aohjwi;lBv&2Z{Dw5>3tvNEg`?!ac}w5j&qJsWaa&%k zAF(=D+45uj-2RyM-@dY}Yv(zkilmaco2HNVpWy_q zgKA=zO}F-mAWI-V!Hlc$MP;DfU`B?&6@Se2R+LM~)pub2Nl4101?c3h)1(E$PLYWS z`X*KjS{EQ?=-Pc}S>CXZ$766!;5n6<3d!10ciq6a>02R=^IQ8bI!AWxAHLsL$Cx`d^3U5Pj9O&m8PzZP51mVcH89 z)vxCg*DdLQ^2Yui6q-psMuZxmqPR0Iv1`o!q@>$*U$mOKZli!YP|Eu9t+TlNhj$LC zXF)a)5+egdOGySJ-Nua9=8BU^@StQeOHAfQre0GHFZ)TopzI^{{PkIzCI=Gtp)s5Z z7g*f~#{Cq(X$~!Pf^`aU|1xarY@a<7_dK(4z!`DJ;4|vW9pX(wwY>b0u6?)a;Eu=W=RX1K%`=|1hV}IH(_8(|C9kK@o8!CjFph_nG&dXk>v7uv>S;?H@Fmg27f(wwxiF_&{c2rjk>IIk4h=v0Nq{%WTLC{PH z!gE4WCbkN+q?FZa%dGf3wFC@$%{cyub+JgwO3~l@O=h2D9X@r?fW;W_a*!@y=yqgP?NzwMDT z61#uJljeLb7hSn~;!3Fi*wT4<%>YtON{q@D-&^7bL?J|R-_TM?A6bM?nZHWj5_BLq z%$86S^^5Q-X$w5cd_Qpvu^CyLTu+Q^2{_0_p=>7j5qny^?B&uirDLp#)`Smd-JikO zWLCzkV%}u$g8(dbB+1V&-XM%5`gj5E+7b6wtV;)A(A%z0iH??r9Nd;^d|!IUI?fhQ z+>eB6@3YGd+a($P)uGmyqvYN2&ABBhYos=9jd3&SU`n5%MQtT0cMcz`O=FxAJtgK+ zh$W#mKH@Rui;#Nu21cGFIzeU~o`!X|-ls`u$K3)R5$ZnIZ|?hBXX}%~Ly|X1&M-Ih z>Van>&xqBzGMl0(TGRrerG8u@+v?Y-e&$`{$4YEn-X})u3uXmGYF#50*}}>eYG<=! z^^`(j8n>(v3l3!)T62l$=a;@)?P>+pxr&O|ow#?}XF)A@<3i1BJ8=-Fz#0CB_AJKw zTYan56TX^UF^wD#W*hzVx(8CNV?`|*?it_H;lw@MYQ$}4LbhW}l!E=SdaHP8mgc%@ z_2{(t$S1iHalSJ8UF}2=wz-l%Y(U0)12?41?-%ikX<^lsEwk&YoTv5QA@3b2-*AOU z-KlJm*j8p#=|4|a9wGDp5J{~4ts^m=N#x*L+s8uolLrasBsMT7AUeW9a`X0FImG)W z2n%e=B4x?ak_To+O&!ja_!gM9bPT+o6$JeA2a7+Fmt>_XgC`6$Sp-U0ep@1XXXb?+ z?sB5KUIcLwU_Ny&yk2~RUAIhBFENIaF6P6jQRAb}hpRts37WVe(&}#vGgim-Gp<-1 z!;Fs}v~F-&N{serCE51a%6u_yhCKAGz}3e#2>cWR$OE;+eLh^dW6_$&$XpFkY@KZ^ zA78p>64PZ=IT;+Gc95LUaTj+PzFyJLKA(}>M&@~Z3^tjPLv%B^5R?Xz1A-~hxxSbI z%7q&RNqZz6`0B4RKFcCF&$@aYyMQ`HhrsWH(R%ovdRk z+rqwrFJhXuBnnebGut6iOt_>JGJY~vI!HZ1dDR-vKx^E&1T zYe=6f^mAk#=_Rwx=_spGJ@W(aGNSYS_Ug%)aX*L^yUD!r+( z$?Q30jjF+9fohOyfvQ$8LRpJWOm1g8X;(^{At7a8{jrbqY0+NbOoM)x5Tl5^9*1JLL`)dIUz@m>=6z$fwclDHS&!kavve`{s`S=^vYiu;kpW7q{?c${Gz<$p z#zHUza1wDp5M2BqF=U8zu22o#x@$u_R z*Yunu$lM>%eM0*Zb6-w6SWSvUM??<=C5pO^#-Peeb3mdiXnrvU0utvV4Z?cd2dM&z z)Cb9$gJBAXON7ToIuroIIccnU2nPi%TH6WF=e5fdy8nc^!Jtfb~?{MzuX)2y*B! zCp;{yGanVz6(2i%__!ao}S z4p(68Ay&+dt-<8&vpBEVPkCP;IIsozD#F5Iw7q0mo4xWmHKpDrfqF|NK1xJwVU$eU z0Wxh%$I(=)9yju%T5&IO{r<7M9`E>_!pY!&r}Gz(Fhr(u;2j&(PS(v zG@9_xx6znnGA>-0WlEx+G)O`Mr5hzAP%06e60^W7h{`oaP&^2dXW|J$OO`BOzHnhq z4&c+MJbY6jC`kQ`a9U_uZc3s_nhJBWkVIjUkS4uGE$>zGEF1?#0$JufVVpS;U0V8fA^2PvzGn$qr=tO@j1f4N2twp%6~7LJywLCa!6^HR$- z8c-EOLxBMVU?e3bq-?CH*qD+4e|nHM8|7DkZlp+zoELbG*g|wrktmT;;_!JDi3+oz zcvE7QF)PvQhT`N+$cWL@V>0rlB;&%YBj_VOH8JPIo`xk7ClJPg7ikC)GGj9MC|j)St%VolX9lc)lE2Ojh@&SOtQZ@yF3Z z&AIsM@Uev@VVvh5V#5PEBKi{q1?Qe5lgaDlb~)`f*ju5HL66`FLA%3w8H`%|Lx>Z{mgsgfeFS{Oq+X~UG^Ukcu}efC9)pNxEDv0Co}*78;r`E-8tGB^GqtXsDqm0PFUKhH6q&Q`6ks+(sH28k*YM z^N7vXP+jxWN`koO&u`w0(m^K1jU&}HH9!4y;y90K{?esb#PAt1Enoik4V)bYn5Kps zb{eeN?RJv36NsFU03JFxD+NmK8(YGX5QT&SZfIyIN+D|8(xppp+_*u`pL2HnjrWcA z_I98`8V>yUQEF&FWbsFp`Lcl<7ro(#tElzzE5Ht5I(}O22P#>mPhP# z49LRA$UB(EC%6mU*>|ZzcS0dfS0)gr+L;f9yKPjt)d*MUhEGo%agw}HQy7m8(|_|Z zX-pcN&(4Mq4=t30$`dsRoq!Y8^Z4-cFnK7TVbVrp8zFmu;&E4;o^-nXdZ2mZP^|rv z05{Qtqz0kS>r?)sPF#!Wx z0qTSS*$b96)zo;}UFgWB+H-emYR}ob?M8e-R%;eW`1Ens*3hTd-)^;@!~cADc(2|2 z)se(3L_SedLpy%@XyR#qtyXI=k)#jpw}rx*nr`?Mvdw@X;vS96wVO zJn_+3+kZZL;-_wVVixrSf(0IbkG=i;(E?-`5As)2>+FV4OMn#CSaUUEVNDIjGWsYw zcnqO=>^!`h+S;S}y>@iWkQ~x$KYRA9ySWD?409Ojx=}*rv-wfV3oq<8D_1L{wF^5u z@u4SXg)ON$vD{-vZqMDmtBBzmI?`yhcH@WgLg=4PoW%tw?~|ByyraiIbYygNf*l+n zz4#$aQWhUx$b$g<<7eBOkS+M|C4g3dAOCm&%H?1j&p$``Tq8>D(K8)ayK5!h8@juQ z5bH9Ezys0)qBjYTov(=QT7eVqUrXI^pO3X?^<~(-z+j>0VR9KT6>cJltr6-4R z3Em=w6Vkq?NMO&N7k`gG>d0F~G(74gAscqCs2x`NAPOQS%FpC_>~6aGWkd_1k2lA( zsQ5>t?k!&4!P>ZJY>0UFNRKxnWN&nDe0)GJ2HhV2@4PAQCmdAwz~>t}{W7ihre`#I z?5lD7R2=G$(F$ULFvYO93)|a;_qeidu@DqqaVcgm8VH#;DhW#^rBOL)@zEHr=dp3< z$BR6CNVV_`AZsxk3bBRfpP+%r@1rs>t%sB}ck#%Pabpk_EJpE2iL8co%W9-xItMl% z;u`*jjo1Zbt=7Ih5Lt|%Yg0KQ`0Sw#L~!t-aw2F3TPoqunVX14aN{A+3~7KeB5a*u z6KI94vz!9N*`VLRZLn@2Z|UFS-m<G+lPxG_0=v%V~O!h)Oy(G0C zJ%9T$?FH#4-QC5Ay}hN?`@O`)d0N$+V=HMCQnH6nf^_$a-T3!*2_-pmbF$ojqi(w9 zgK%7;QE!s_zrlf*P4RbMiHopX>uu4NXiP*Igh&cCCC#U`JD)}nTdI;UrL8Kds=`6> zy*tP3-ARb~G5|CcHC5MOYCK!dk7xHfsY-u-WqQNy!7Uuvf<>){ zq;dZ3`Iun@MTn69!?{?1_8hyt$G{(?#R)awMbD?vL%Q%kyY}8wYH-4_TvP+nanobD zaKQEcd(W!D&2{hjUmA_z957~xSpJ$Hz`>SPqvPT|KUgfI9{{JuQE2|?F8NIu5e}k^ zl9N?f-ZCssqJWlc5#x-ZITpuFMuMCol+jg%5c}QGl|_V%gvTiA5y(6B<@I2A0L+G1 zGCU^c{=5GKfHsT-IRWxV?)pPSCT7Ile=m@`;ZpN|59IFTsM!nTZn)G; zffS=ihRb7$@8@$-jKYY9BO(F$Vl+Un%^OmOkR$kz|3wMooPy^`(5NG+i3}X3?Kfzc zMx-60p?2jkwRXtRq$I(h;gCE$BuO?rB!as84GE^NBre2KCbnOd2g2GH4(`j8+cQ zXpqyyVhjMFNnHF{O#^k=4j zIQ{bU!84wl@yQI3Cs-DI=K0&R{pEqtFAknIdD`U6DTef@!J)wt2}JouYi(PNTa7!7 zoAM7PHWjYhq6!O7N;X+savWj13jnsnSR88hrRSvwM~={@r(kO%sTTkhoYHcprGrWv zyLr7+{P)K<6qFq{X3JC_&?IcE4U{euW!`U1`hCo=iBrZH!yXV*Vi|on`~{?hoFgqt zr7aq}&PL8ktZ)SY3^JsnqocH=^u!y-SDh$5R(iBY;X)~b(LIC*Nhn#%ZRRNd+0u@d z^0qC%zwswno&tcH=Q8o1IF&6jg(c!}KcPRXkT^T!>?`tR;$`CHV#kH$l2`jTHfl6>`&V!N@P1B+ z+r_f1zbsJYQDG`9;nl;24IOs>h=Gx#Bg1ev5(RYEW3xtUH))O*pF3EvNo%uO`9dVA zSkT@l@CzR}GUn#Z#fxQl0s=m+JXYP%LgU(OR4QdOc*jLWwM2~=F@MOT$?3^q5~sAo zP61$zjr&Rx0=Bgz$wWv_4(93ph)4%Q{H-<{VAzNiOK+zOv(d|-%jLo^p8um35)vk) zY%y280kvj!`t0-(xT!=1fY8!F7_5TV6c;kCCRaPo*4}Ym?(ny3Z1L7y4nVOgB`7mU zHDL=1jusaezh3+&m!I$Z;Fyua2gK7PC{IbZLQq&3FSXmB8SvwehY!z<*s1;g`)%KU zzv;=;gakI3lnCTPHLH{=eS$@+Ma66y8-!DIypqs79w0_8Mz7MUv@y8em!ePAXXy1< zt3g?*e*}4xj3-$nPB4aKs|p440tbdvguUWN4;%vk+IS{f`);y_d`(VkUF3AslUoO# zsqI*kOS$Z_N?~|_KAe!P$#Ztk+5MI6&i1ctU)i2`X;z-@fv`}CWUH-1t+^;3n!UP6 za7FZ)sGsON(N})AWwj4#Aj!^V!^u!wrN9@>f6z=qf>A#S56W`V>`4ppF{1pnb~_a= zWrQjW9Vav`ePmMC@~-h+Lz+F z{J4(0=DKvwd9DL43oh@xb>}U}<~T_GQHv<(o`(uQ@sS272YFc+%!Bmcl7C@cz;RZT zA;-~}C_}rwwDe$c5c-4I&o`gmm_*MjE1Hdw+iIP0dH)LqeG&!~KA!U|zPgT% z;^IonH|EbvUr8Ms(Vv&^iZL7y%~vlJj??S4+K&dceNs51V|&4qoxA^>er(dpm8{EU zv3y|JW&UK{w(>^j1EK!(vJ&EHG`_y*ZFx`_{-tZ=t5^TH;@OA39Xf5p_Lg6!eY$(b z<8QoP;p?YFgT~)R}3Ir_Rt%&}XC?piHJGQcX!N=ZsHXt{I;akVw>OZKQs(-oU-ZeaL-b{=)q6S8vWV%$__Vk`4+yVe_V1OcCU8rE0H7bYh}CPW4Y^$U{X`kUP@ zl^#8~*^(rz^+uRRxdH0@A}yB zW&8WCtf!J<{QVh5E)VIO7~!g|y7FYfPTcf#I!|Smf(0JSw_wap=~v_#81XT&;KQj2 zOo+@+qpBb>Da>wH=4w5k5k94iXlZp{uTWv21coW%(N{jG4E6B?SMk_@g@c~I z|GB8=qUH@46BvvF=&)~+J}FHoJrj*{+PUYRE4=vj*M0kh_K6$rJTxnsa)U1~J6d`< zbxFhnDvi{i#Zex#A%Q?46N|cA5N(QRMeOm_%VxYZ{LFLv&NxRc=zM##!jEA*)q~AxQss{Xp4&+ZC>qRQ$JHVS^3V^$PzF*Y*Bo9l)6W?Ka+* z|J{X@f4!kcZ|B{61CY0YIRF*0qG2f!pNd$qL6i!JGZ3Q!@hWgrL%fQev59dxf)o|} zp!cY1;vP?M{Vq#v^~fW|U9*w6Y=`))k(tBfu=5~~)X3YAS%w!%3=(7(6;6!zJZ3() z0`j4G@xvq8Xl5LQ4v)lzz;@<67DC>|{g|<^IPKm1w=@t=a%&!7u?@2e==c2P?)d6m);e_zMTShZ0XE}wZFgTw(HyP zU&?Q($K}?oMk8iDdMhq^+UP+MZt#BY?9-AqA6k$ata*_lb{OR_seeybWhG;RuO#yiG#r z-ifHs3Y==djW@DVA%#g9AcSKub)+Cx0fd~W0x|fBL#bmAB;48%51=j{X#WPAPl>`U zrf@moP?jc!)Vnc*8!oUREx7eSy$3%(;O@`k)|;pQBGY{^;ryQ!ibWwp@z4YzF&d`s z!tfLnMYx0_tPGIRqN2!_&@xa3^7d{-J*d&Ry-+M@0koK~%BEm|GqnzYl|n(fFh$4s z{%hih1~j}eyfyb<=h8#8dfxaph+oF7M6mlQ4!!j6S%0AS8~H5s|DRkNuA&#Rx)nA+ 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 z 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 zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y z2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0P zh=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%) zfCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y x2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph`>q&{y!;PU(5gi