function GameBoyAdvanceIO() {
	// Video
	this.DISPCNT = 0x000;
	this.GREENSWP = 0x002;
	this.DISPSTAT = 0x004;
	this.VCOUNT = 0x006;
	this.BG0CNT = 0x008;
	this.BG1CNT = 0x00A;
	this.BG2CNT = 0x00C;
	this.BG3CNT = 0x00E;
	this.BG0HOFS = 0x010;
	this.BG0VOFS = 0x012;
	this.BG1HOFS = 0x014;
	this.BG1VOFS = 0x016;
	this.BG2HOFS = 0x018;
	this.BG2VOFS = 0x01A;
	this.BG3HOFS = 0x01C;
	this.BG3VOFS = 0x01E;
	this.BG2PA = 0x020;
	this.BG2PB = 0x022;
	this.BG2PC = 0x024;
	this.BG2PD = 0x026;
	this.BG2X_LO = 0x028;
	this.BG2X_HI = 0x02A;
	this.BG2Y_LO = 0x02C;
	this.BG2Y_HI = 0x02E;
	this.BG3PA = 0x030;
	this.BG3PB = 0x032;
	this.BG3PC = 0x034;
	this.BG3PD = 0x036;
	this.BG3X_LO = 0x038;
	this.BG3X_HI = 0x03A;
	this.BG3Y_LO = 0x03C;
	this.BG3Y_HI = 0x03E;
	this.WIN0H = 0x040;
	this.WIN1H = 0x042;
	this.WIN0V = 0x044;
	this.WIN1V = 0x046;
	this.WININ = 0x048;
	this.WINOUT = 0x04A;
	this.MOSAIC = 0x04C;
	this.BLDCNT = 0x050;
	this.BLDALPHA = 0x052;
	this.BLDY = 0x054;

	// Sound
	this.SOUND1CNT_LO = 0x060;
	this.SOUND1CNT_HI = 0x062;
	this.SOUND1CNT_X = 0x064;
	this.SOUND2CNT_LO = 0x068;
	this.SOUND2CNT_HI = 0x06C;
	this.SOUND3CNT_LO = 0x070;
	this.SOUND3CNT_HI = 0x072;
	this.SOUND3CNT_X = 0x074;
	this.SOUND4CNT_LO = 0x078;
	this.SOUND4CNT_HI = 0x07C;
	this.SOUNDCNT_LO = 0x080;
	this.SOUNDCNT_HI = 0x082;
	this.SOUNDCNT_X = 0x084;
	this.SOUNDBIAS = 0x088;
	this.WAVE_RAM0_LO = 0x090;
	this.WAVE_RAM0_HI = 0x092;
	this.WAVE_RAM1_LO = 0x094;
	this.WAVE_RAM1_HI = 0x096;
	this.WAVE_RAM2_LO = 0x098;
	this.WAVE_RAM2_HI = 0x09A;
	this.WAVE_RAM3_LO = 0x09C;
	this.WAVE_RAM3_HI = 0x09E;
	this.FIFO_A_LO = 0x0A0;
	this.FIFO_A_HI = 0x0A2;
	this.FIFO_B_LO = 0x0A4;
	this.FIFO_B_HI = 0x0A6;

	// DMA
	this.DMA0SAD_LO = 0x0B0;
	this.DMA0SAD_HI = 0x0B2;
	this.DMA0DAD_LO = 0x0B4;
	this.DMA0DAD_HI = 0x0B6;
	this.DMA0CNT_LO = 0x0B8;
	this.DMA0CNT_HI = 0x0BA;
	this.DMA1SAD_LO = 0x0BC;
	this.DMA1SAD_HI = 0x0BE;
	this.DMA1DAD_LO = 0x0C0;
	this.DMA1DAD_HI = 0x0C2;
	this.DMA1CNT_LO = 0x0C4;
	this.DMA1CNT_HI = 0x0C6;
	this.DMA2SAD_LO = 0x0C8;
	this.DMA2SAD_HI = 0x0CA;
	this.DMA2DAD_LO = 0x0CC;
	this.DMA2DAD_HI = 0x0CE;
	this.DMA2CNT_LO = 0x0D0;
	this.DMA2CNT_HI = 0x0D2;
	this.DMA3SAD_LO = 0x0D4;
	this.DMA3SAD_HI = 0x0D6;
	this.DMA3DAD_LO = 0x0D8;
	this.DMA3DAD_HI = 0x0DA;
	this.DMA3CNT_LO = 0x0DC;
	this.DMA3CNT_HI = 0x0DE;

	// Timers
	this.TM0CNT_LO = 0x100;
	this.TM0CNT_HI = 0x102;
	this.TM1CNT_LO = 0x104;
	this.TM1CNT_HI = 0x106;
	this.TM2CNT_LO = 0x108;
	this.TM2CNT_HI = 0x10A;
	this.TM3CNT_LO = 0x10C;
	this.TM3CNT_HI = 0x10E;

	// SIO (note: some of these are repeated)
	this.SIODATA32_LO = 0x120;
	this.SIOMULTI0 = 0x120;
	this.SIODATA32_HI = 0x122;
	this.SIOMULTI1 = 0x122;
	this.SIOMULTI2 = 0x124;
	this.SIOMULTI3 = 0x126;
	this.SIOCNT = 0x128;
	this.SIOMLT_SEND = 0x12A;
	this.SIODATA8 = 0x12A;
	this.RCNT = 0x134;
	this.JOYCNT = 0x140;
	this.JOY_RECV = 0x150;
	this.JOY_TRANS = 0x154;
	this.JOYSTAT = 0x158;

	// Keypad
	this.KEYINPUT = 0x130;
	this.KEYCNT = 0x132;

	// Interrupts, etc
	this.IE = 0x200;
	this.IF = 0x202;
	this.WAITCNT = 0x204;
	this.IME = 0x208;

	this.POSTFLG = 0x300;
	this.HALTCNT = 0x301;

	this.DEFAULT_DISPCNT = 0x0080;
	this.DEFAULT_SOUNDBIAS = 0x200;
	this.DEFAULT_BGPA = 1;
	this.DEFAULT_BGPD = 1;
	this.DEFAULT_RCNT = 0x8000;
};

GameBoyAdvanceIO.prototype.clear = function() {
	this.registers = new Uint16Array(this.cpu.mmu.SIZE_IO);

	this.registers[this.DISPCNT >> 1] = this.DEFAULT_DISPCNT;
	this.registers[this.SOUNDBIAS >> 1] = this.DEFAULT_SOUNDBIAS;
	this.registers[this.BG2PA >> 1] = this.DEFAULT_BGPA;
	this.registers[this.BG2PD >> 1] = this.DEFAULT_BGPD;
	this.registers[this.BG3PA >> 1] = this.DEFAULT_BGPA;
	this.registers[this.BG3PD >> 1] = this.DEFAULT_BGPD;
	this.registers[this.RCNT >> 1] = this.DEFAULT_RCNT;
};

GameBoyAdvanceIO.prototype.freeze = function() {
	return {
		'registers': Serializer.prefix(this.registers.buffer)
	};
};

GameBoyAdvanceIO.prototype.defrost = function(frost) {
	this.registers = new Uint16Array(frost.registers);
	// Video registers don't serialize themselves
	for (var i = 0; i <= this.BLDY; i += 2) {
		this.store16(this.registers[i >> 1]);
	}
};

GameBoyAdvanceIO.prototype.load8 = function(offset) {
	throw 'Unimplmeneted unaligned I/O access';
}

GameBoyAdvanceIO.prototype.load16 = function(offset) {
	return (this.loadU16(offset) << 16) >> 16;
}

GameBoyAdvanceIO.prototype.load32 = function(offset) {
	offset &= 0xFFFFFFFC;
	switch (offset) {
	case this.DMA0CNT_LO:
	case this.DMA1CNT_LO:
	case this.DMA2CNT_LO:
	case this.DMA3CNT_LO:
		return this.loadU16(offset | 2) << 16;
	case this.IME:
		return this.loadU16(offset) & 0xFFFF;
	case this.JOY_RECV:
	case this.JOY_TRANS:
 		this.core.STUB('Unimplemented JOY register read: 0x' + offset.toString(16));
 		return 0;
	}

	return this.loadU16(offset) | (this.loadU16(offset | 2) << 16);
};

GameBoyAdvanceIO.prototype.loadU8 = function(offset) {
	var odd = offset & 0x0001;
	var value = this.loadU16(offset & 0xFFFE);
	return (value >>> (odd << 3)) & 0xFF;
}

GameBoyAdvanceIO.prototype.loadU16 = function(offset) {
	switch (offset) {
	case this.DISPCNT:
	case this.BG0CNT:
	case this.BG1CNT:
	case this.BG2CNT:
	case this.BG3CNT:
	case this.WININ:
	case this.WINOUT:
	case this.SOUND1CNT_LO:
	case this.SOUND3CNT_LO:
	case this.SOUNDCNT_LO:
	case this.SOUNDCNT_HI:
	case this.SOUNDBIAS:
	case this.BLDCNT:
	case this.BLDALPHA:

	case this.TM0CNT_HI:
	case this.TM1CNT_HI:
	case this.TM2CNT_HI:
	case this.TM3CNT_HI:
	case this.DMA0CNT_HI:
	case this.DMA1CNT_HI:
	case this.DMA2CNT_HI:
	case this.DMA3CNT_HI:
	case this.RCNT:
	case this.WAITCNT:
	case this.IE:
	case this.IF:
	case this.IME:
	case this.POSTFLG:
		// Handled transparently by the written registers
		break;

	// Video
	case this.DISPSTAT:
		return this.registers[offset >> 1] | this.video.readDisplayStat();
	case this.VCOUNT:
		return this.video.vcount;

	// Sound
	case this.SOUND1CNT_HI:
	case this.SOUND2CNT_LO:
		return this.registers[offset >> 1] & 0xFFC0;
	case this.SOUND1CNT_X:
	case this.SOUND2CNT_HI:
	case this.SOUND3CNT_X:
		return this.registers[offset >> 1] & 0x4000;
	case this.SOUND3CNT_HI:
		return this.registers[offset >> 1] & 0xE000;
	case this.SOUND4CNT_LO:
		return this.registers[offset >> 1] & 0xFF00;
	case this.SOUND4CNT_HI:
		return this.registers[offset >> 1] & 0x40FF;
	case this.SOUNDCNT_X:
		this.core.STUB('Unimplemented sound register read: SOUNDCNT_X');
		return this.registers[offset >> 1] | 0x0000;

	// Timers
	case this.TM0CNT_LO:
		return this.cpu.irq.timerRead(0);
	case this.TM1CNT_LO:
		return this.cpu.irq.timerRead(1);
	case this.TM2CNT_LO:
		return this.cpu.irq.timerRead(2);
	case this.TM3CNT_LO:
		return this.cpu.irq.timerRead(3);

	// SIO
	case this.SIOCNT:
		return this.sio.readSIOCNT();

	case this.KEYINPUT:
		this.keypad.pollGamepads();
		return this.keypad.currentDown;
	case this.KEYCNT:
		this.core.STUB('Unimplemented I/O register read: KEYCNT');
		return 0;

	case this.BG0HOFS:
	case this.BG0VOFS:
	case this.BG1HOFS:
	case this.BG1VOFS:
	case this.BG2HOFS:
	case this.BG2VOFS:
	case this.BG3HOFS:
	case this.BG3VOFS:
	case this.BG2PA:
	case this.BG2PB:
	case this.BG2PC:
	case this.BG2PD:
	case this.BG3PA:
	case this.BG3PB:
	case this.BG3PC:
	case this.BG3PD:
	case this.BG2X_LO:
	case this.BG2X_HI:
	case this.BG2Y_LO:
	case this.BG2Y_HI:
	case this.BG3X_LO:
	case this.BG3X_HI:
	case this.BG3Y_LO:
	case this.BG3Y_HI:
	case this.WIN0H:
	case this.WIN1H:
	case this.WIN0V:
	case this.WIN1V:
	case this.BLDY:
	case this.DMA0SAD_LO:
	case this.DMA0SAD_HI:
	case this.DMA0DAD_LO:
	case this.DMA0DAD_HI:
	case this.DMA0CNT_LO:
	case this.DMA1SAD_LO:
	case this.DMA1SAD_HI:
	case this.DMA1DAD_LO:
	case this.DMA1DAD_HI:
	case this.DMA1CNT_LO:
	case this.DMA2SAD_LO:
	case this.DMA2SAD_HI:
	case this.DMA2DAD_LO:
	case this.DMA2DAD_HI:
	case this.DMA2CNT_LO:
	case this.DMA3SAD_LO:
	case this.DMA3SAD_HI:
	case this.DMA3DAD_LO:
	case this.DMA3DAD_HI:
	case this.DMA3CNT_LO:
	case this.FIFO_A_LO:
	case this.FIFO_A_HI:
	case this.FIFO_B_LO:
	case this.FIFO_B_HI:
		this.core.WARN('Read for write-only register: 0x' + offset.toString(16));
		return this.core.mmu.badMemory.loadU16(0);

	case this.MOSAIC:
		this.core.WARN('Read for write-only register: 0x' + offset.toString(16));
		return 0;

	case this.SIOMULTI0:
 	case this.SIOMULTI1:
 	case this.SIOMULTI2:
 	case this.SIOMULTI3:
 		return this.sio.read((offset - this.SIOMULTI0) >> 1);

 	case this.SIODATA8:
 		this.core.STUB('Unimplemented SIO register read: 0x' + offset.toString(16));
 		return 0;
	case this.JOYCNT:
	case this.JOYSTAT:
 		this.core.STUB('Unimplemented JOY register read: 0x' + offset.toString(16));
 		return 0;

	default:
		this.core.WARN('Bad I/O register read: 0x' + offset.toString(16));
		return this.core.mmu.badMemory.loadU16(0);
	}
	return this.registers[offset >> 1];
};

GameBoyAdvanceIO.prototype.store8 = function(offset, value) {
	switch (offset) {
	case this.WININ:
		this.value & 0x3F;
		break;
	case this.WININ | 1:
		this.value & 0x3F;
		break;
	case this.WINOUT:
		this.value & 0x3F;
		break;
	case this.WINOUT | 1:
		this.value & 0x3F;
		break;
	case this.SOUND1CNT_LO:
	case this.SOUND1CNT_LO | 1:
	case this.SOUND1CNT_HI:
	case this.SOUND1CNT_HI | 1:
	case this.SOUND1CNT_X:
	case this.SOUND1CNT_X | 1:
	case this.SOUND2CNT_LO:
	case this.SOUND2CNT_LO | 1:
	case this.SOUND2CNT_HI:
	case this.SOUND2CNT_HI | 1:
	case this.SOUND3CNT_LO:
	case this.SOUND3CNT_LO | 1:
	case this.SOUND3CNT_HI:
	case this.SOUND3CNT_HI | 1:
	case this.SOUND3CNT_X:
	case this.SOUND3CNT_X | 1:
	case this.SOUND4CNT_LO:
	case this.SOUND4CNT_LO | 1:
	case this.SOUND4CNT_HI:
	case this.SOUND4CNT_HI | 1:
	case this.SOUNDCNT_LO:
	case this.SOUNDCNT_LO | 1:
	case this.SOUNDCNT_X:
	case this.IF:
	case this.IME:
		break;
	case this.SOUNDBIAS | 1:
		this.STUB_REG('sound', offset);
		break;
	case this.HALTCNT:
		value &= 0x80;
		if (!value) {
			this.core.irq.halt();
		} else {
			this.core.STUB('Stop');
		}
		return;
	default:
		this.STUB_REG('8-bit I/O', offset);
		break;
	}

	if (offset & 1) {
		value <<= 8;
		value |= (this.registers[offset >> 1] & 0x00FF);
	} else {
		value &= 0x00FF;
		value |= (this.registers[offset >> 1] & 0xFF00);
	}
	this.store16(offset & 0xFFFFFFE, value);
};

GameBoyAdvanceIO.prototype.store16 = function(offset, value) {
	switch (offset) {
	// Video
	case this.DISPCNT:
		this.video.renderPath.writeDisplayControl(value);
		break;
	case this.DISPSTAT:
		value &= this.video.DISPSTAT_MASK;
		this.video.writeDisplayStat(value);
		break;
	case this.BG0CNT:
		this.video.renderPath.writeBackgroundControl(0, value);
		break;
	case this.BG1CNT:
		this.video.renderPath.writeBackgroundControl(1, value);
		break;
	case this.BG2CNT:
		this.video.renderPath.writeBackgroundControl(2, value);
		break;
	case this.BG3CNT:
		this.video.renderPath.writeBackgroundControl(3, value);
		break;
	case this.BG0HOFS:
		this.video.renderPath.writeBackgroundHOffset(0, value);
		break;
	case this.BG0VOFS:
		this.video.renderPath.writeBackgroundVOffset(0, value);
		break;
	case this.BG1HOFS:
		this.video.renderPath.writeBackgroundHOffset(1, value);
		break;
	case this.BG1VOFS:
		this.video.renderPath.writeBackgroundVOffset(1, value);
		break;
	case this.BG2HOFS:
		this.video.renderPath.writeBackgroundHOffset(2, value);
		break;
	case this.BG2VOFS:
		this.video.renderPath.writeBackgroundVOffset(2, value);
		break;
	case this.BG3HOFS:
		this.video.renderPath.writeBackgroundHOffset(3, value);
		break;
	case this.BG3VOFS:
		this.video.renderPath.writeBackgroundVOffset(3, value);
		break;
	case this.BG2X_LO:
		this.video.renderPath.writeBackgroundRefX(2, (this.registers[(offset >> 1) | 1] << 16) | value);
		break;
	case this.BG2X_HI:
		this.video.renderPath.writeBackgroundRefX(2, this.registers[(offset >> 1) ^ 1] | (value << 16));
		break;
	case this.BG2Y_LO:
		this.video.renderPath.writeBackgroundRefY(2, (this.registers[(offset >> 1) | 1] << 16) | value);
		break;
	case this.BG2Y_HI:
		this.video.renderPath.writeBackgroundRefY(2, this.registers[(offset >> 1) ^ 1] | (value << 16));
		break;
	case this.BG2PA:
		this.video.renderPath.writeBackgroundParamA(2, value);
		break;
	case this.BG2PB:
		this.video.renderPath.writeBackgroundParamB(2, value);
		break;
	case this.BG2PC:
		this.video.renderPath.writeBackgroundParamC(2, value);
		break;
	case this.BG2PD:
		this.video.renderPath.writeBackgroundParamD(2, value);
		break;
	case this.BG3X_LO:
		this.video.renderPath.writeBackgroundRefX(3, (this.registers[(offset >> 1) | 1] << 16) | value);
		break;
	case this.BG3X_HI:
		this.video.renderPath.writeBackgroundRefX(3, this.registers[(offset >> 1) ^ 1] | (value << 16));
		break;
	case this.BG3Y_LO:
		this.video.renderPath.writeBackgroundRefY(3, (this.registers[(offset >> 1) | 1] << 16) | value);
		break;
	case this.BG3Y_HI:
		this.video.renderPath.writeBackgroundRefY(3, this.registers[(offset >> 1) ^ 1] | (value << 16));
		break;
	case this.BG3PA:
		this.video.renderPath.writeBackgroundParamA(3, value);
		break;
	case this.BG3PB:
		this.video.renderPath.writeBackgroundParamB(3, value);
		break;
	case this.BG3PC:
		this.video.renderPath.writeBackgroundParamC(3, value);
		break;
	case this.BG3PD:
		this.video.renderPath.writeBackgroundParamD(3, value);
		break;
	case this.WIN0H:
		this.video.renderPath.writeWin0H(value);
		break;
	case this.WIN1H:
		this.video.renderPath.writeWin1H(value);
		break;
	case this.WIN0V:
		this.video.renderPath.writeWin0V(value);
		break;
	case this.WIN1V:
		this.video.renderPath.writeWin1V(value);
		break;
	case this.WININ:
		value &= 0x3F3F;
		this.video.renderPath.writeWinIn(value);
		break;
	case this.WINOUT:
		value &= 0x3F3F;
		this.video.renderPath.writeWinOut(value);
		break;
	case this.BLDCNT:
		value &= 0x7FFF;
		this.video.renderPath.writeBlendControl(value);
		break;
	case this.BLDALPHA:
		value &= 0x1F1F;
		this.video.renderPath.writeBlendAlpha(value);
		break;
	case this.BLDY:
		value &= 0x001F;
		this.video.renderPath.writeBlendY(value);
		break;
	case this.MOSAIC:
		this.video.renderPath.writeMosaic(value);
		break;

	// Sound
	case this.SOUND1CNT_LO:
		value &= 0x007F;
		this.audio.writeSquareChannelSweep(0, value);
		break;
	case this.SOUND1CNT_HI:
		this.audio.writeSquareChannelDLE(0, value);
		break;
	case this.SOUND1CNT_X:
		value &= 0xC7FF;
		this.audio.writeSquareChannelFC(0, value);
		value &= ~0x8000;
		break;
	case this.SOUND2CNT_LO:
		this.audio.writeSquareChannelDLE(1, value);
		break;
	case this.SOUND2CNT_HI:
		value &= 0xC7FF;
		this.audio.writeSquareChannelFC(1, value);
		value &= ~0x8000;
		break;
	case this.SOUND3CNT_LO:
		value &= 0x00E0;
		this.audio.writeChannel3Lo(value);
		break;
	case this.SOUND3CNT_HI:
		value &= 0xE0FF;
		this.audio.writeChannel3Hi(value);
		break;
	case this.SOUND3CNT_X:
		value &= 0xC7FF;
		this.audio.writeChannel3X(value);
		value &= ~0x8000;
		break;
	case this.SOUND4CNT_LO:
		value &= 0xFF3F;
		this.audio.writeChannel4LE(value);
		break;
	case this.SOUND4CNT_HI:
		value &= 0xC0FF;
		this.audio.writeChannel4FC(value);
		value &= ~0x8000;
		break;
	case this.SOUNDCNT_LO:
		value &= 0xFF77;
		this.audio.writeSoundControlLo(value);
		break;
	case this.SOUNDCNT_HI:
		value &= 0xFF0F;
		this.audio.writeSoundControlHi(value);
		break;
	case this.SOUNDCNT_X:
		value &= 0x0080;
		this.audio.writeEnable(value);
		break;
	case this.WAVE_RAM0_LO:
	case this.WAVE_RAM0_HI:
	case this.WAVE_RAM1_LO:
	case this.WAVE_RAM1_HI:
	case this.WAVE_RAM2_LO:
	case this.WAVE_RAM2_HI:
	case this.WAVE_RAM3_LO:
	case this.WAVE_RAM3_HI:
		this.audio.writeWaveData(offset - this.WAVE_RAM0_LO, value, 2);
		break;

	// DMA
	case this.DMA0SAD_LO:
	case this.DMA0DAD_LO:
	case this.DMA1SAD_LO:
	case this.DMA1DAD_LO:
	case this.DMA2SAD_LO:
	case this.DMA2DAD_LO:
	case this.DMA3SAD_LO:
	case this.DMA3DAD_LO:
		this.store32(offset, (this.registers[(offset >> 1) + 1] << 16) | value);
		return;

	case this.DMA0SAD_HI:
	case this.DMA0DAD_HI:
	case this.DMA1SAD_HI:
	case this.DMA1DAD_HI:
	case this.DMA2SAD_HI:
	case this.DMA2DAD_HI:
	case this.DMA3SAD_HI:
	case this.DMA3DAD_HI:
		this.store32(offset - 2, this.registers[(offset >> 1) - 1] | (value << 16));
		return;

	case this.DMA0CNT_LO:
		this.cpu.irq.dmaSetWordCount(0, value);
		break;
	case this.DMA0CNT_HI:
		// The DMA registers need to set the values before writing the control, as writing the
		// control can synchronously trigger a DMA transfer
		this.registers[offset >> 1] = value & 0xFFE0;
		this.cpu.irq.dmaWriteControl(0, value);
		return;
	case this.DMA1CNT_LO:
		this.cpu.irq.dmaSetWordCount(1, value);
		break;
	case this.DMA1CNT_HI:
		this.registers[offset >> 1] = value & 0xFFE0;
		this.cpu.irq.dmaWriteControl(1, value);
		return;
	case this.DMA2CNT_LO:
		this.cpu.irq.dmaSetWordCount(2, value);
		break;
	case this.DMA2CNT_HI:
		this.registers[offset >> 1] = value & 0xFFE0;
		this.cpu.irq.dmaWriteControl(2, value);
		return;
	case this.DMA3CNT_LO:
		this.cpu.irq.dmaSetWordCount(3, value);
		break;
	case this.DMA3CNT_HI:
		this.registers[offset >> 1] = value & 0xFFE0;
		this.cpu.irq.dmaWriteControl(3, value);
		return;

	// Timers
	case this.TM0CNT_LO:
		this.cpu.irq.timerSetReload(0, value);
		return;
	case this.TM1CNT_LO:
		this.cpu.irq.timerSetReload(1, value);
		return;
	case this.TM2CNT_LO:
		this.cpu.irq.timerSetReload(2, value);
		return;
	case this.TM3CNT_LO:
		this.cpu.irq.timerSetReload(3, value);
		return;

	case this.TM0CNT_HI:
		value &= 0x00C7
		this.cpu.irq.timerWriteControl(0, value);
		break;
	case this.TM1CNT_HI:
		value &= 0x00C7
		this.cpu.irq.timerWriteControl(1, value);
		break;
	case this.TM2CNT_HI:
		value &= 0x00C7
		this.cpu.irq.timerWriteControl(2, value);
		break;
	case this.TM3CNT_HI:
		value &= 0x00C7
		this.cpu.irq.timerWriteControl(3, value);
		break;

	// SIO
	case this.SIOMULTI0:
 	case this.SIOMULTI1:
 	case this.SIOMULTI2:
 	case this.SIOMULTI3:
 	case this.SIODATA8:
 		this.STUB_REG('SIO', offset);
 		break;
	case this.RCNT:
		this.sio.setMode(((value >> 12) & 0xC) | ((this.registers[this.SIOCNT >> 1] >> 12) & 0x3));
		this.sio.writeRCNT(value);
		break;
	case this.SIOCNT:
		this.sio.setMode(((value >> 12) & 0x3) | ((this.registers[this.RCNT >> 1] >> 12) & 0xC));
		this.sio.writeSIOCNT(value);
		return;
	case this.JOYCNT:
	case this.JOYSTAT:
		this.STUB_REG('JOY', offset);
		break;

	// Misc
	case this.IE:
		value &= 0x3FFF;
		this.cpu.irq.setInterruptsEnabled(value);
		break;
	case this.IF:
		this.cpu.irq.dismissIRQs(value);
		return;
	case this.WAITCNT:
		value &= 0xDFFF;
		this.cpu.mmu.adjustTimings(value);
		break;
	case this.IME:
		value &= 0x0001;
		this.cpu.irq.masterEnable(value);
		break;
	default:
		this.STUB_REG('I/O', offset);
	}
	this.registers[offset >> 1] = value;
};

GameBoyAdvanceIO.prototype.store32 = function(offset, value) {
	switch (offset) {
	case this.BG2X_LO:
		value &= 0x0FFFFFFF;
		this.video.renderPath.writeBackgroundRefX(2, value);
		break;
	case this.BG2Y_LO:
		value &= 0x0FFFFFFF;
		this.video.renderPath.writeBackgroundRefY(2, value);
		break;
	case this.BG3X_LO:
		value &= 0x0FFFFFFF;
		this.video.renderPath.writeBackgroundRefX(3, value);
		break;
	case this.BG3Y_LO:
		value &= 0x0FFFFFFF;
		this.video.renderPath.writeBackgroundRefY(3, value);
		break;
	case this.DMA0SAD_LO:
		this.cpu.irq.dmaSetSourceAddress(0, value);
		break;
	case this.DMA0DAD_LO:
		this.cpu.irq.dmaSetDestAddress(0, value);
		break;
	case this.DMA1SAD_LO:
		this.cpu.irq.dmaSetSourceAddress(1, value);
		break;
	case this.DMA1DAD_LO:
		this.cpu.irq.dmaSetDestAddress(1, value);
		break;
	case this.DMA2SAD_LO:
		this.cpu.irq.dmaSetSourceAddress(2, value);
		break;
	case this.DMA2DAD_LO:
		this.cpu.irq.dmaSetDestAddress(2, value);
		break;
	case this.DMA3SAD_LO:
		this.cpu.irq.dmaSetSourceAddress(3, value);
		break;
	case this.DMA3DAD_LO:
		this.cpu.irq.dmaSetDestAddress(3, value);
		break;
	case this.FIFO_A_LO:
		this.audio.appendToFifoA(value);
		return;
	case this.FIFO_B_LO:
		this.audio.appendToFifoB(value);
		return;

	// High bits of this write should be ignored
	case this.IME:
		this.store16(offset, value & 0xFFFF);
		return;
	case this.JOY_RECV:
	case this.JOY_TRANS:
		this.STUB_REG('JOY', offset);
		return;
	default:
		this.store16(offset, value & 0xFFFF);
		this.store16(offset | 2, value >>> 16);
		return;
	}

	this.registers[offset >> 1] = value & 0xFFFF;
	this.registers[(offset >> 1) + 1] = value >>> 16;
};

GameBoyAdvanceIO.prototype.invalidatePage = function(address) {};

GameBoyAdvanceIO.prototype.STUB_REG = function(type, offset) {
	this.core.STUB('Unimplemented ' + type + ' register write: ' + offset.toString(16));
};
