985 lines
25 KiB
JavaScript
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);
|
|
}
|
|
};
|