emulator/gba3/js/irq.js
2019-06-14 09:33:19 +08:00

985 lines
25 KiB
JavaScript

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);
}
};