| 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: |
| |
| 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: |
| |
| 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: |
| |
| this.halt(); |
| break; |
| case 0x05: |
| |
| this.cpu.gprs[0] = 1; |
| this.cpu.gprs[1] = 1; |
| |
| case 0x04: |
| |
| 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: |
| |
| 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: |
| |
| 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: |
| |
| var root = Math.sqrt(this.cpu.gprs[0]); |
| this.cpu.gprs[0] = root | 0; |
| break; |
| case 0x0A: |
| |
| 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: |
| |
| 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: |
| |
| 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: |
| |
| 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--) { |
| |
| |
| |
| 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; |
| |
| a = d = Math.cos(theta); |
| b = c = Math.sin(theta); |
| |
| a *= sx; |
| b *= -sx; |
| c *= sy; |
| d *= sy; |
| |
| 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: |
| |
| 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 = 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; |
| |
| a = d = Math.cos(theta); |
| b = c = Math.sin(theta); |
| |
| 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: |
| |
| this.lz77(this.cpu.gprs[0], this.cpu.gprs[1], 1); |
| break; |
| case 0x12: |
| |
| this.lz77(this.cpu.gprs[0], this.cpu.gprs[1], 2); |
| break; |
| case 0x13: |
| |
| this.huffman(this.cpu.gprs[0], this.cpu.gprs[1]); |
| break; |
| case 0x14: |
| |
| this.rl(this.cpu.gprs[0], this.cpu.gprs[1], 1); |
| break; |
| case 0x15: |
| |
| this.rl(this.cpu.gprs[0], this.cpu.gprs[1], 2); |
| break; |
| case 0x1F: |
| |
| 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) { |
| |
| currentTimer.nextEvent = currentTimer.lastEvent + currentTimer.overflowInterval; |
| } |
|
|
| |
| 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) { |
| |
| var remaining = (this.cpu.mmu.load32(source) & 0xFFFFFF00) >> 8; |
| |
| 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) { |
| |
| 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 { |
| |
| 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; |
| |
| 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') { |
| |
| 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) { |
| |
| if (node.rTerm) { |
| readBits = tree[node.r]; |
| } else { |
| offset = node.r; |
| node = tree[node.r]; |
| continue; |
| } |
| } else { |
| |
| 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; |
| |
| 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) { |
| |
| 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 { |
| |
| 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); |
| } |
| }; |
|
|