function GameBoyAdvanceInterruptHandler() { this.inherit(); this.FREQUENCY = 0x1000000; this.cpu = null; this.enable = false; this.IRQ_VBLANK = 0x0; this.IRQ_HBLANK = 0x1; this.IRQ_VCOUNTER = 0x2; this.IRQ_TIMER0 = 0x3; this.IRQ_TIMER1 = 0x4; this.IRQ_TIMER2 = 0x5; this.IRQ_TIMER3 = 0x6; this.IRQ_SIO = 0x7; this.IRQ_DMA0 = 0x8; this.IRQ_DMA1 = 0x9; this.IRQ_DMA2 = 0xA; this.IRQ_DMA3 = 0xB; this.IRQ_KEYPAD = 0xC; this.IRQ_GAMEPAK = 0xD; this.MASK_VBLANK = 0x0001; this.MASK_HBLANK = 0x0002; this.MASK_VCOUNTER = 0x0004; this.MASK_TIMER0 = 0x0008; this.MASK_TIMER1 = 0x0010; this.MASK_TIMER2 = 0x0020; this.MASK_TIMER3 = 0x0040; this.MASK_SIO = 0x0080; this.MASK_DMA0 = 0x0100; this.MASK_DMA1 = 0x0200; this.MASK_DMA2 = 0x0400; this.MASK_DMA3 = 0x0800; this.MASK_KEYPAD = 0x1000; this.MASK_GAMEPAK = 0x2000; }; GameBoyAdvanceInterruptHandler.prototype.clear = function() { this.enable = false; this.enabledIRQs = 0; this.interruptFlags = 0; this.dma = new Array(); for (var i = 0; i < 4; ++i) { this.dma.push({ source: 0, dest: 0, count: 0, nextSource: 0, nextDest: 0, nextCount: 0, srcControl: 0, dstControl: 0, repeat: false, width: 0, drq: false, timing: 0, doIrq: false, enable: false, nextIRQ: 0 }); } this.timersEnabled = 0; this.timers = new Array(); for (var i = 0; i < 4; ++i) { this.timers.push({ reload: 0, oldReload: 0, prescaleBits: 0, countUp: false, doIrq: false, enable: false, lastEvent: 0, nextEvent: 0, overflowInterval: 1 }); } this.nextEvent = 0; this.springIRQ = false; this.resetSP(); }; GameBoyAdvanceInterruptHandler.prototype.freeze = function() { return { 'enable': this.enable, 'enabledIRQs': this.enabledIRQs, 'interruptFlags': this.interruptFlags, 'dma': this.dma, 'timers': this.timers, 'nextEvent': this.nextEvent, 'springIRQ': this.springIRQ }; }; GameBoyAdvanceInterruptHandler.prototype.defrost = function(frost) { this.enable = frost.enable; this.enabledIRQs = frost.enabledIRQs; this.interruptFlags = frost.interruptFlags; this.dma = frost.dma; this.timers = frost.timers; this.timersEnabled = 0; if (this.timers[0].enable) { ++this.timersEnabled; } if (this.timers[1].enable) { ++this.timersEnabled; } if (this.timers[2].enable) { ++this.timersEnabled; } if (this.timers[3].enable) { ++this.timersEnabled; } this.nextEvent = frost.nextEvent; this.springIRQ = frost.springIRQ; }; GameBoyAdvanceInterruptHandler.prototype.updateTimers = function() { if (this.nextEvent > this.cpu.cycles) { return; } if (this.springIRQ) { this.cpu.raiseIRQ(); this.springIRQ = false; } this.video.updateTimers(this.cpu); this.audio.updateTimers(); if (this.timersEnabled) { var timer = this.timers[0]; if (timer.enable) { if (this.cpu.cycles >= timer.nextEvent) { timer.lastEvent = timer.nextEvent; timer.nextEvent += timer.overflowInterval; this.io.registers[this.io.TM0CNT_LO >> 1] = timer.reload; timer.oldReload = timer.reload; if (timer.doIrq) { this.raiseIRQ(this.IRQ_TIMER0); } if (this.audio.enabled) { if (this.audio.enableChannelA && !this.audio.soundTimerA && this.audio.dmaA >= 0) { this.audio.sampleFifoA(); } if (this.audio.enableChannelB && !this.audio.soundTimerB && this.audio.dmaB >= 0) { this.audio.sampleFifoB(); } } timer = this.timers[1]; if (timer.countUp) { if (++this.io.registers[this.io.TM1CNT_LO >> 1] == 0x10000) { timer.nextEvent = this.cpu.cycles; } } } } timer = this.timers[1]; if (timer.enable) { if (this.cpu.cycles >= timer.nextEvent) { timer.lastEvent = timer.nextEvent; timer.nextEvent += timer.overflowInterval; if (!timer.countUp || this.io.registers[this.io.TM1CNT_LO >> 1] == 0x10000) { this.io.registers[this.io.TM1CNT_LO >> 1] = timer.reload; } timer.oldReload = timer.reload; if (timer.doIrq) { this.raiseIRQ(this.IRQ_TIMER1); } if (timer.countUp) { timer.nextEvent = 0; } if (this.audio.enabled) { if (this.audio.enableChannelA && this.audio.soundTimerA && this.audio.dmaA >= 0) { this.audio.sampleFifoA(); } if (this.audio.enableChannelB && this.audio.soundTimerB && this.audio.dmaB >= 0) { this.audio.sampleFifoB(); } } timer = this.timers[2]; if (timer.countUp) { if (++this.io.registers[this.io.TM2CNT_LO >> 1] == 0x10000) { timer.nextEvent = this.cpu.cycles; } } } } timer = this.timers[2]; if (timer.enable) { if (this.cpu.cycles >= timer.nextEvent) { timer.lastEvent = timer.nextEvent; timer.nextEvent += timer.overflowInterval; if (!timer.countUp || this.io.registers[this.io.TM2CNT_LO >> 1] == 0x10000) { this.io.registers[this.io.TM2CNT_LO >> 1] = timer.reload; } timer.oldReload = timer.reload; if (timer.doIrq) { this.raiseIRQ(this.IRQ_TIMER2); } if (timer.countUp) { timer.nextEvent = 0; } timer = this.timers[3]; if (timer.countUp) { if (++this.io.registers[this.io.TM3CNT_LO >> 1] == 0x10000) { timer.nextEvent = this.cpu.cycles; } } } } timer = this.timers[3]; if (timer.enable) { if (this.cpu.cycles >= timer.nextEvent) { timer.lastEvent = timer.nextEvent; timer.nextEvent += timer.overflowInterval; if (!timer.countUp || this.io.registers[this.io.TM3CNT_LO >> 1] == 0x10000) { this.io.registers[this.io.TM3CNT_LO >> 1] = timer.reload; } timer.oldReload = timer.reload; if (timer.doIrq) { this.raiseIRQ(this.IRQ_TIMER3); } if (timer.countUp) { timer.nextEvent = 0; } } } } var dma = this.dma[0]; if (dma.enable && dma.doIrq && dma.nextIRQ && this.cpu.cycles >= dma.nextIRQ) { dma.nextIRQ = 0; this.raiseIRQ(this.IRQ_DMA0); } dma = this.dma[1]; if (dma.enable && dma.doIrq && dma.nextIRQ && this.cpu.cycles >= dma.nextIRQ) { dma.nextIRQ = 0; this.raiseIRQ(this.IRQ_DMA1); } dma = this.dma[2]; if (dma.enable && dma.doIrq && dma.nextIRQ && this.cpu.cycles >= dma.nextIRQ) { dma.nextIRQ = 0; this.raiseIRQ(this.IRQ_DMA2); } dma = this.dma[3]; if (dma.enable && dma.doIrq && dma.nextIRQ && this.cpu.cycles >= dma.nextIRQ) { dma.nextIRQ = 0; this.raiseIRQ(this.IRQ_DMA3); } this.pollNextEvent(); } GameBoyAdvanceInterruptHandler.prototype.resetSP = function() { this.cpu.switchMode(this.cpu.MODE_SUPERVISOR); this.cpu.gprs[this.cpu.SP] = 0x3007FE0; this.cpu.switchMode(this.cpu.MODE_IRQ); this.cpu.gprs[this.cpu.SP] = 0x3007FA0; this.cpu.switchMode(this.cpu.MODE_SYSTEM); this.cpu.gprs[this.cpu.SP] = 0x3007F00; }; GameBoyAdvanceInterruptHandler.prototype.swi32 = function(opcode) { this.swi(opcode >> 16); }; GameBoyAdvanceInterruptHandler.prototype.swi = function(opcode) { if (this.core.mmu.bios.real) { this.cpu.raiseTrap(); return; } switch (opcode) { case 0x00: // SoftReset var mem = this.core.mmu.memory[this.core.mmu.REGION_WORKING_IRAM]; var flag = mem.loadU8(0x7FFA); for (var i = 0x7E00; i < 0x8000; i += 4) { mem.store32(i, 0); } this.resetSP(); if (!flag) { this.cpu.gprs[this.cpu.LR] = 0x08000000; } else { this.cpu.gprs[this.cpu.LR] = 0x02000000; } this.cpu.switchExecMode(this.cpu.MODE_ARM); this.cpu.instruction.writesPC = true; this.cpu.gprs[this.cpu.PC] = this.cpu.gprs[this.cpu.LR]; break; case 0x01: // RegisterRamReset var regions = this.cpu.gprs[0]; if (regions & 0x01) { this.core.mmu.memory[this.core.mmu.REGION_WORKING_RAM] = new MemoryBlock(this.core.mmu.SIZE_WORKING_RAM, 9); } if (regions & 0x02) { for (var i = 0; i < this.core.mmu.SIZE_WORKING_IRAM - 0x200; i += 4) { this.core.mmu.memory[this.core.mmu.REGION_WORKING_IRAM].store32(i, 0); } } if (regions & 0x1C) { this.video.renderPath.clearSubsets(this.core.mmu, regions); } if (regions & 0xE0) { this.core.STUB('Unimplemented RegisterRamReset'); } break; case 0x02: // Halt this.halt(); break; case 0x05: // VBlankIntrWait this.cpu.gprs[0] = 1; this.cpu.gprs[1] = 1; // Fall through: case 0x04: // IntrWait if (!this.enable) { this.io.store16(this.io.IME, 1); } if (!this.cpu.gprs[0] && this.interruptFlags & this.cpu.gprs[1]) { return; } this.dismissIRQs(0xFFFFFFFF); this.cpu.raiseTrap(); break; case 0x06: // Div var result = (this.cpu.gprs[0] | 0) / (this.cpu.gprs[1] | 0); var mod = (this.cpu.gprs[0] | 0) % (this.cpu.gprs[1] | 0); this.cpu.gprs[0] = result | 0; this.cpu.gprs[1] = mod | 0; this.cpu.gprs[3] = Math.abs(result | 0); break; case 0x07: // DivArm var result = (this.cpu.gprs[1] | 0) / (this.cpu.gprs[0] | 0); var mod = (this.cpu.gprs[1] | 0) % (this.cpu.gprs[0] | 0); this.cpu.gprs[0] = result | 0; this.cpu.gprs[1] = mod | 0; this.cpu.gprs[3] = Math.abs(result | 0); break; case 0x08: // Sqrt var root = Math.sqrt(this.cpu.gprs[0]); this.cpu.gprs[0] = root | 0; // Coerce down to int break; case 0x0A: // ArcTan2 var x = this.cpu.gprs[0] / 16384; var y = this.cpu.gprs[1] / 16384; this.cpu.gprs[0] = (Math.atan2(y, x) / (2 * Math.PI)) * 0x10000; break; case 0x0B: // CpuSet var source = this.cpu.gprs[0]; var dest = this.cpu.gprs[1]; var mode = this.cpu.gprs[2]; var count = mode & 0x000FFFFF; var fill = mode & 0x01000000; var wordsize = (mode & 0x04000000) ? 4 : 2; if (fill) { if (wordsize == 4) { source &= 0xFFFFFFFC; dest &= 0xFFFFFFFC; var word = this.cpu.mmu.load32(source); for (var i = 0; i < count; ++i) { this.cpu.mmu.store32(dest + (i << 2), word); } } else { source &= 0xFFFFFFFE; dest &= 0xFFFFFFFE; var word = this.cpu.mmu.load16(source); for (var i = 0; i < count; ++i) { this.cpu.mmu.store16(dest + (i << 1), word); } } } else { if (wordsize == 4) { source &= 0xFFFFFFFC; dest &= 0xFFFFFFFC; for (var i = 0; i < count; ++i) { var word = this.cpu.mmu.load32(source + (i << 2)); this.cpu.mmu.store32(dest + (i << 2), word); } } else { source &= 0xFFFFFFFE; dest &= 0xFFFFFFFE; for (var i = 0; i < count; ++i) { var word = this.cpu.mmu.load16(source + (i << 1)); this.cpu.mmu.store16(dest + (i << 1), word); } } } return; case 0x0C: // FastCpuSet var source = this.cpu.gprs[0] & 0xFFFFFFFC; var dest = this.cpu.gprs[1] & 0xFFFFFFFC; var mode = this.cpu.gprs[2]; var count = mode & 0x000FFFFF; count = ((count + 7) >> 3) << 3; var fill = mode & 0x01000000; if (fill) { var word = this.cpu.mmu.load32(source); for (var i = 0; i < count; ++i) { this.cpu.mmu.store32(dest + (i << 2), word); } } else { for (var i = 0; i < count; ++i) { var word = this.cpu.mmu.load32(source + (i << 2)); this.cpu.mmu.store32(dest + (i << 2), word); } } return; case 0x0E: // BgAffineSet var i = this.cpu.gprs[2]; var ox, oy; var cx, cy; var sx, sy; var theta; var offset = this.cpu.gprs[0]; var destination = this.cpu.gprs[1]; var a, b, c, d; var rx, ry; while (i--) { // [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ] // [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ] // [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] ox = this.core.mmu.load32(offset) / 256; oy = this.core.mmu.load32(offset + 4) / 256; cx = this.core.mmu.load16(offset + 8); cy = this.core.mmu.load16(offset + 10); sx = this.core.mmu.load16(offset + 12) / 256; sy = this.core.mmu.load16(offset + 14) / 256; theta = (this.core.mmu.loadU16(offset + 16) >> 8) / 128 * Math.PI; offset += 20; // Rotation a = d = Math.cos(theta); b = c = Math.sin(theta); // Scale a *= sx; b *= -sx; c *= sy; d *= sy; // Translate rx = ox - (a * cx + b * cy); ry = oy - (c * cx + d * cy); this.core.mmu.store16(destination, (a * 256) | 0); this.core.mmu.store16(destination + 2, (b * 256) | 0); this.core.mmu.store16(destination + 4, (c * 256) | 0); this.core.mmu.store16(destination + 6, (d * 256) | 0); this.core.mmu.store32(destination + 8, (rx * 256) | 0); this.core.mmu.store32(destination + 12, (ry * 256) | 0); destination += 16; } break; case 0x0F: // ObjAffineSet var i = this.cpu.gprs[2]; var sx, sy; var theta; var offset = this.cpu.gprs[0]; var destination = this.cpu.gprs[1] var diff = this.cpu.gprs[3]; var a, b, c, d; while (i--) { // [ sx 0 ] [ cos(theta) -sin(theta) ] [ A B ] // [ 0 sy ] * [ sin(theta) cos(theta) ] = [ C D ] sx = this.core.mmu.load16(offset) / 256; sy = this.core.mmu.load16(offset + 2) / 256; theta = (this.core.mmu.loadU16(offset + 4) >> 8) / 128 * Math.PI; offset += 6; // Rotation a = d = Math.cos(theta); b = c = Math.sin(theta); // Scale a *= sx; b *= -sx; c *= sy; d *= sy; this.core.mmu.store16(destination, (a * 256) | 0); this.core.mmu.store16(destination + diff, (b * 256) | 0); this.core.mmu.store16(destination + diff * 2, (c * 256) | 0); this.core.mmu.store16(destination + diff * 3, (d * 256) | 0); destination += diff * 4; } break; case 0x11: // LZ77UnCompWram this.lz77(this.cpu.gprs[0], this.cpu.gprs[1], 1); break; case 0x12: // LZ77UnCompVram this.lz77(this.cpu.gprs[0], this.cpu.gprs[1], 2); break; case 0x13: // HuffUnComp this.huffman(this.cpu.gprs[0], this.cpu.gprs[1]); break; case 0x14: // RlUnCompWram this.rl(this.cpu.gprs[0], this.cpu.gprs[1], 1); break; case 0x15: // RlUnCompVram this.rl(this.cpu.gprs[0], this.cpu.gprs[1], 2); break; case 0x1F: // MidiKey2Freq var key = this.cpu.mmu.load32(this.cpu.gprs[0] + 4); this.cpu.gprs[0] = key / Math.pow(2, (180 - this.cpu.gprs[1] - this.cpu.gprs[2] / 256) / 12) >>> 0; break; default: throw "Unimplemented software interrupt: 0x" + opcode.toString(16); } }; GameBoyAdvanceInterruptHandler.prototype.masterEnable = function(value) { this.enable = value; if (this.enable && this.enabledIRQs & this.interruptFlags) { this.cpu.raiseIRQ(); } }; GameBoyAdvanceInterruptHandler.prototype.setInterruptsEnabled = function(value) { this.enabledIRQs = value; if (this.enabledIRQs & this.MASK_SIO) { this.core.STUB('Serial I/O interrupts not implemented'); } if (this.enabledIRQs & this.MASK_KEYPAD) { this.core.STUB('Keypad interrupts not implemented'); } if (this.enable && this.enabledIRQs & this.interruptFlags) { this.cpu.raiseIRQ(); } }; GameBoyAdvanceInterruptHandler.prototype.pollNextEvent = function() { var nextEvent = this.video.nextEvent; var test; if (this.audio.enabled) { test = this.audio.nextEvent; if (!nextEvent || test < nextEvent) { nextEvent = test; } } if (this.timersEnabled) { var timer = this.timers[0]; test = timer.nextEvent; if (timer.enable && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } timer = this.timers[1]; test = timer.nextEvent; if (timer.enable && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } timer = this.timers[2]; test = timer.nextEvent; if (timer.enable && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } timer = this.timers[3]; test = timer.nextEvent; if (timer.enable && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } } var dma = this.dma[0]; test = dma.nextIRQ; if (dma.enable && dma.doIrq && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } dma = this.dma[1]; test = dma.nextIRQ; if (dma.enable && dma.doIrq && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } dma = this.dma[2]; test = dma.nextIRQ; if (dma.enable && dma.doIrq && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } dma = this.dma[3]; test = dma.nextIRQ; if (dma.enable && dma.doIrq && test && (!nextEvent || test < nextEvent)) { nextEvent = test; } this.core.ASSERT(nextEvent >= this.cpu.cycles, "Next event is before present"); this.nextEvent = nextEvent; }; GameBoyAdvanceInterruptHandler.prototype.waitForIRQ = function() { var timer; var irqPending = this.testIRQ() || this.video.hblankIRQ || this.video.vblankIRQ || this.video.vcounterIRQ; if (this.timersEnabled) { timer = this.timers[0]; irqPending = irqPending || timer.doIrq; timer = this.timers[1]; irqPending = irqPending || timer.doIrq; timer = this.timers[2]; irqPending = irqPending || timer.doIrq; timer = this.timers[3]; irqPending = irqPending || timer.doIrq; } if (!irqPending) { return false; } for (;;) { this.pollNextEvent(); if (!this.nextEvent) { return false; } else { this.cpu.cycles = this.nextEvent; this.updateTimers(); if (this.interruptFlags) { return true; } } } }; GameBoyAdvanceInterruptHandler.prototype.testIRQ = function() { if (this.enable && this.enabledIRQs & this.interruptFlags) { this.springIRQ = true; this.nextEvent = this.cpu.cycles; return true; } return false; }; GameBoyAdvanceInterruptHandler.prototype.raiseIRQ = function(irqType) { this.interruptFlags |= 1 << irqType; this.io.registers[this.io.IF >> 1] = this.interruptFlags; if (this.enable && (this.enabledIRQs & 1 << irqType)) { this.cpu.raiseIRQ(); } }; GameBoyAdvanceInterruptHandler.prototype.dismissIRQs = function(irqMask) { this.interruptFlags &= ~irqMask; this.io.registers[this.io.IF >> 1] = this.interruptFlags; }; GameBoyAdvanceInterruptHandler.prototype.dmaSetSourceAddress = function(dma, address) { this.dma[dma].source = address & 0xFFFFFFFE; }; GameBoyAdvanceInterruptHandler.prototype.dmaSetDestAddress = function(dma, address) { this.dma[dma].dest = address & 0xFFFFFFFE; }; GameBoyAdvanceInterruptHandler.prototype.dmaSetWordCount = function(dma, count) { this.dma[dma].count = count ? count : (dma == 3 ? 0x10000 : 0x4000); }; GameBoyAdvanceInterruptHandler.prototype.dmaWriteControl = function(dma, control) { var currentDma = this.dma[dma]; var wasEnabled = currentDma.enable; currentDma.dstControl = (control & 0x0060) >> 5; currentDma.srcControl = (control & 0x0180) >> 7; currentDma.repeat = !!(control & 0x0200); currentDma.width = (control & 0x0400) ? 4 : 2; currentDma.drq = !!(control & 0x0800); currentDma.timing = (control & 0x3000) >> 12; currentDma.doIrq = !!(control & 0x4000); currentDma.enable = !!(control & 0x8000); currentDma.nextIRQ = 0; if (currentDma.drq) { this.core.WARN('DRQ not implemented'); } if (!wasEnabled && currentDma.enable) { currentDma.nextSource = currentDma.source; currentDma.nextDest = currentDma.dest; currentDma.nextCount = currentDma.count; this.cpu.mmu.scheduleDma(dma, currentDma); } }; GameBoyAdvanceInterruptHandler.prototype.timerSetReload = function(timer, reload) { this.timers[timer].reload = reload & 0xFFFF; }; GameBoyAdvanceInterruptHandler.prototype.timerWriteControl = function(timer, control) { var currentTimer = this.timers[timer]; var oldPrescale = currentTimer.prescaleBits; switch (control & 0x0003) { case 0x0000: currentTimer.prescaleBits = 0; break; case 0x0001: currentTimer.prescaleBits = 6; break; case 0x0002: currentTimer.prescaleBits = 8; break; case 0x0003: currentTimer.prescaleBits = 10; break; } currentTimer.countUp = !!(control & 0x0004); currentTimer.doIrq = !!(control & 0x0040); currentTimer.overflowInterval = (0x10000 - currentTimer.reload) << currentTimer.prescaleBits; var wasEnabled = currentTimer.enable; currentTimer.enable = !!(((control & 0x0080) >> 7) << timer); if (!wasEnabled && currentTimer.enable) { if (!currentTimer.countUp) { currentTimer.lastEvent = this.cpu.cycles; currentTimer.nextEvent = this.cpu.cycles + currentTimer.overflowInterval; } else { currentTimer.nextEvent = 0; } this.io.registers[(this.io.TM0CNT_LO + (timer << 2)) >> 1] = currentTimer.reload; currentTimer.oldReload = currentTimer.reload; ++this.timersEnabled; } else if (wasEnabled && !currentTimer.enable) { if (!currentTimer.countUp) { this.io.registers[(this.io.TM0CNT_LO + (timer << 2)) >> 1] = currentTimer.oldReload + (this.cpu.cycles - currentTimer.lastEvent) >> oldPrescale; } --this.timersEnabled; } else if (currentTimer.prescaleBits != oldPrescale && !currentTimer.countUp) { // FIXME: this might be before present currentTimer.nextEvent = currentTimer.lastEvent + currentTimer.overflowInterval; } // We've changed the timers somehow...we need to reset the next event this.pollNextEvent(); }; GameBoyAdvanceInterruptHandler.prototype.timerRead = function(timer) { var currentTimer = this.timers[timer]; if (currentTimer.enable && !currentTimer.countUp) { return currentTimer.oldReload + (this.cpu.cycles - currentTimer.lastEvent) >> currentTimer.prescaleBits; } else { return this.io.registers[(this.io.TM0CNT_LO + (timer << 2)) >> 1]; } }; GameBoyAdvanceInterruptHandler.prototype.halt = function() { if (!this.enable) { throw "Requested HALT when interrupts were disabled!"; } if (!this.waitForIRQ()) { throw "Waiting on interrupt forever."; } } GameBoyAdvanceInterruptHandler.prototype.lz77 = function(source, dest, unitsize) { // TODO: move to a different file var remaining = (this.cpu.mmu.load32(source) & 0xFFFFFF00) >> 8; // We assume the signature byte (0x10) is correct var blockheader; var sPointer = source + 4; var dPointer = dest; var blocksRemaining = 0; var block; var disp; var bytes; var buffer = 0; var loaded; while (remaining > 0) { if (blocksRemaining) { if (blockheader & 0x80) { // Compressed block = this.cpu.mmu.loadU8(sPointer) | (this.cpu.mmu.loadU8(sPointer + 1) << 8); sPointer += 2; disp = dPointer - (((block & 0x000F) << 8) | ((block & 0xFF00) >> 8)) - 1; bytes = ((block & 0x00F0) >> 4) + 3; while (bytes-- && remaining) { loaded = this.cpu.mmu.loadU8(disp++); if (unitsize == 2) { buffer >>= 8; buffer |= loaded << 8; if (dPointer & 1) { this.cpu.mmu.store16(dPointer - 1, buffer); } } else { this.cpu.mmu.store8(dPointer, loaded); } --remaining; ++dPointer; } } else { // Uncompressed loaded = this.cpu.mmu.loadU8(sPointer++); if (unitsize == 2) { buffer >>= 8; buffer |= loaded << 8; if (dPointer & 1) { this.cpu.mmu.store16(dPointer - 1, buffer); } } else { this.cpu.mmu.store8(dPointer, loaded); } --remaining; ++dPointer; } blockheader <<= 1; --blocksRemaining; } else { blockheader = this.cpu.mmu.loadU8(sPointer++); blocksRemaining = 8; } } }; GameBoyAdvanceInterruptHandler.prototype.huffman = function(source, dest) { source = source & 0xFFFFFFFC; var header = this.cpu.mmu.load32(source); var remaining = header >> 8; var bits = header & 0xF; if (32 % bits) { throw 'Unimplemented unaligned Huffman'; } var padding = (4 - remaining) & 0x3; remaining &= 0xFFFFFFFC; // We assume the signature byte (0x20) is correct var tree = []; var treesize = (this.cpu.mmu.loadU8(source + 4) << 1) + 1; var block; var sPointer = source + 5 + treesize; var dPointer = dest & 0xFFFFFFFC; var i; for (i = 0; i < treesize; ++i) { tree.push(this.cpu.mmu.loadU8(source + 5 + i)); } var node; var offset = 0; var bitsRemaining; var readBits; var bitsSeen = 0; node = tree[0]; while (remaining > 0) { var bitstream = this.cpu.mmu.load32(sPointer); sPointer += 4; for (bitsRemaining = 32; bitsRemaining > 0; --bitsRemaining, bitstream <<= 1) { if (typeof (node) === 'number') { // Lazily construct tree var next = (offset - 1 | 1) + ((node & 0x3F) << 1) + 2; node = { l: next, r: next + 1, lTerm: node & 0x80, rTerm: node & 0x40 }; tree[offset] = node; } if (bitstream & 0x80000000) { // Go right if (node.rTerm) { readBits = tree[node.r]; } else { offset = node.r; node = tree[node.r]; continue; } } else { // Go left if (node.lTerm) { readBits = tree[node.l]; } else { offset = node.l; node = tree[offset]; continue; } } block |= (readBits & ((1 << bits) - 1)) << bitsSeen; bitsSeen += bits; offset = 0; node = tree[0]; if (bitsSeen == 32) { bitsSeen = 0; this.cpu.mmu.store32(dPointer, block); dPointer += 4; remaining -= 4; block = 0; } } } if (padding) { this.cpu.mmu.store32(dPointer, block); } }; GameBoyAdvanceInterruptHandler.prototype.rl = function(source, dest, unitsize) { source = source & 0xFFFFFFFC; var remaining = (this.cpu.mmu.load32(source) & 0xFFFFFF00) >> 8; var padding = (4 - remaining) & 0x3; // We assume the signature byte (0x30) is correct var blockheader; var block; var sPointer = source + 4; var dPointer = dest; var buffer = 0; while (remaining > 0) { blockheader = this.cpu.mmu.loadU8(sPointer++); if (blockheader & 0x80) { // Compressed blockheader &= 0x7F; blockheader += 3; block = this.cpu.mmu.loadU8(sPointer++); while (blockheader-- && remaining) { --remaining; if (unitsize == 2) { buffer >>= 8; buffer |= block << 8; if (dPointer & 1) { this.cpu.mmu.store16(dPointer - 1, buffer); } } else { this.cpu.mmu.store8(dPointer, block); } ++dPointer; } } else { // Uncompressed blockheader++; while (blockheader-- && remaining) { --remaining; block = this.cpu.mmu.loadU8(sPointer++); if (unitsize == 2) { buffer >>= 8; buffer |= block << 8; if (dPointer & 1) { this.cpu.mmu.store16(dPointer - 1, buffer); } } else { this.cpu.mmu.store8(dPointer, block); } ++dPointer; } } } while (padding--) { this.cpu.mmu.store8(dPointer++, 0); } };