public void ioWrite(int num, short data) {
		boolean soundOn = (dmgcpu.getSoundChip() != null);

		if (num <= 0x4B) {
			//  System.out.println("Write of register " + JavaBoy.hexByte(num) + " to " + JavaBoy.hexWord(data) + " at " + JavaBoy.hexWord(dmgcpu.pc));
		}

		switch (num) {
		case 0x00 :           // FF00 - Joypad
			short output = 0x0F;
			if ((data & 0x10) == 0x00) {   // P14
				if (padRight) {
					output &= ~1;
				}
				if (padLeft) {
					output &= ~2;
				}
				if (padUp) {
					output &= ~4;
				}
				if (padDown) {
					output &= ~8;
				}
			}
			if ((data & 0x20) == 0x00) {   // P15
				if (padA) {
					output &= ~0x01;
				}
				if (padB) {
					output &= ~0x02;
				}
				if (padSelect) {
					output &= ~0x04;
				}
				if (padStart) {
					output &= ~0x08;
				}
			}
			output |= (data & 0xF0);
			registers[0x00] = (byte) (output);
			//    System.out.println("Joypad port = " + JavaBoy.hexByte(data) + " output = " + JavaBoy.hexByte(output) + "(PC=" + JavaBoy.hexWord(dmgcpu.pc) + ")");
			break;

		case 0x02 :           // Serial

			registers[0x02] = (byte) data;

			if (dmgcpu.gameLink != null) {					// Game Link is connected to serial port
				if (((unsign(data) & 0x81) == 0x81)) {
					dmgcpu.gameLink.send(registers[0x01]);
				}
			} else {
				if ((registers[0x02] & 0x01) == 1) {
					registers[0x01] = (byte) 0xFF; // when no LAN connection, always receive 0xFF from port.  Simulates empty socket.
					if (dmgcpu.isRunning()){
						dmgcpu.triggerInterruptIfEnabled(dmgcpu.INT_SER);
					}
					registers[0x02] &= 0x7F;
				}
			}

			/*    if (dmgcpu.gameLink == null) {  // Simulate no gameboy present
     if ((registers[0x02] & 0x01) == 1) {
	  //System.out.println("Sent byte: " + JavaBoy.hexByte(unsign(registers[0x01])));
      registers[0x01] = (byte) 0xFF; // when no LAN connection
      dmgcpu.triggerInterrupt(dmgcpu.INT_SER);
      registers[0x02] &= 0x7F;
     }
    } else if (((unsign(data) & 0x81) == 0x81) && (dmgcpu.gameLink != null)) {
     dmgcpu.gameLink.send(registers[0x01]);
    }
//    System.out.println(JavaBoy.hexWord(dmgcpu.pc));*/
			break;

		case 0x04 :           // DIV
			registers[04] = 0;
			break;    

		case 0x07 :           // TAC
			if ((data & 0x04) == 0) {
				dmgcpu.timaEnabled = false;
			} else {
				dmgcpu.timaEnabled = true;
			}

			int instrsPerSecond = dmgcpu.INSTRS_PER_VBLANK * 60;
			int clockFrequency = (data & 0x03);

			switch (clockFrequency) {
			case 0: dmgcpu.instrsPerTima = (instrsPerSecond / 4096);
			break;
			case 1: dmgcpu.instrsPerTima = (instrsPerSecond / 262144);
			break;
			case 2: dmgcpu.instrsPerTima = (instrsPerSecond / 65536);
			break;
			case 3: dmgcpu.instrsPerTima = (instrsPerSecond / 16384);
			break;
			}
			break;

		case 0x10 :           // Sound channel 1, sweep
			if (soundOn)
				dmgcpu.getSoundChip().channel1.setSweep(
						(unsign(data) & 0x70) >> 4,
						(unsign(data) & 0x07),
						(unsign(data) & 0x08) == 1);
			registers[0x10] = (byte) data;
			break;

		case 0x11 :           // Sound channel 1, length and wave duty
			if (soundOn) {
				dmgcpu.getSoundChip().channel1.setDutyCycle((unsign(data) & 0xC0) >> 6);
				dmgcpu.getSoundChip().channel1.setLength(unsign(data) & 0x3F);
			}
			registers[0x11] = (byte) data;
			break;

		case 0x12 :           // Sound channel 1, volume envelope
			if (soundOn) {
				dmgcpu.getSoundChip().channel1.setEnvelope(
						(unsign(data) & 0xF0) >> 4,
						(unsign(data) & 0x07),
						(unsign(data) & 0x08) == 8);
			}
			registers[0x12] = (byte) data;
			break;

		case 0x13 :           // Sound channel 1, frequency low
			registers[0x13] = (byte) data;
			if (soundOn) {
				dmgcpu.getSoundChip().channel1.setFrequency(
						((int) (unsign(registers[0x14]) & 0x07) << 8) + unsign(registers[0x13]));
			}
			break;

		case 0x14 :           // Sound channel 1, frequency high
			registers[0x14] = (byte) data;

			if (soundOn) {
				if ((registers[0x14] & 0x80) != 0) {
					dmgcpu.getSoundChip().channel1.setLength(unsign(registers[0x11]) & 0x3F);
					dmgcpu.getSoundChip().channel1.setEnvelope(
							(unsign(registers[0x12]) & 0xF0) >> 4,
							(unsign(registers[0x12]) & 0x07),
							(unsign(registers[0x12]) & 0x08) == 8);
				}
				if ((registers[0x14] & 0x40) == 0) {
					dmgcpu.getSoundChip().channel1.setLength(-1);
				}

				dmgcpu.getSoundChip().channel1.setFrequency(
						((int) (unsign(registers[0x14]) & 0x07) << 8) + unsign(registers[0x13]));
			}
			break;

		case 0x17 :           // Sound channel 2, volume envelope
			if (soundOn) {
				dmgcpu.getSoundChip().channel2.setEnvelope(
						(unsign(data) & 0xF0) >> 4,
						(unsign(data) & 0x07),
						(unsign(data) & 0x08) == 8);
			}
			registers[0x17] = (byte) data;
			break;

		case 0x18 :           // Sound channel 2, frequency low
			registers[0x18] = (byte) data;
			if (soundOn) {
				dmgcpu.getSoundChip().channel2.setFrequency(
						((int) (unsign(registers[0x19]) & 0x07) << 8) + unsign(registers[0x18]));
			}
			break;

		case 0x19 :           // Sound channel 2, frequency high
			registers[0x19] = (byte) data;

			if (soundOn) {
				if ((registers[0x19] & 0x80) != 0) {
					dmgcpu.getSoundChip().channel2.setLength(unsign(registers[0x21]) & 0x3F);
					dmgcpu.getSoundChip().channel2.setEnvelope(
							(unsign(registers[0x17]) & 0xF0) >> 4,
							(unsign(registers[0x17]) & 0x07),
							(unsign(registers[0x17]) & 0x08) == 8);
				}
				if ((registers[0x19] & 0x40) == 0) {
					dmgcpu.getSoundChip().channel2.setLength(-1);
				}
				dmgcpu.getSoundChip().channel2.setFrequency(
						((int) (unsign(registers[0x19]) & 0x07) << 8) + unsign(registers[0x18]));
			}
			break;

		case 0x16 :           // Sound channel 2, length and wave duty
			if (soundOn) {
				dmgcpu.getSoundChip().channel2.setDutyCycle((unsign(data) & 0xC0) >> 6);
				dmgcpu.getSoundChip().channel2.setLength(unsign(data) & 0x3F);
			}
			registers[0x16] = (byte) data;
			break;

		case 0x1A :           // Sound channel 3, on/off
			if (soundOn) {
				if ((unsign(data) & 0x80) != 0) {
					dmgcpu.getSoundChip().channel3.setVolume((unsign(registers[0x1C]) & 0x60) >> 5);
				} else {
					dmgcpu.getSoundChip().channel3.setVolume(0);
				}
			}
			//    System.out.println("Channel 3 enable: " + data);
			registers[0x1A] = (byte) data;
			break;

		case 0x1B :           // Sound channel 3, length
			//    System.out.println("D:" + data);
			registers[0x1B] = (byte) data;
			if (soundOn) dmgcpu.getSoundChip().channel3.setLength(unsign(data));
			break;

		case 0x1C :           // Sound channel 3, volume
			registers[0x1C] = (byte) data;
			if (soundOn) dmgcpu.getSoundChip().channel3.setVolume((unsign(registers[0x1C]) & 0x60) >> 5);
			break;

		case 0x1D :           // Sound channel 3, frequency lower 8-bit
			registers[0x1D] = (byte) data;
			if (soundOn) dmgcpu.getSoundChip().channel3.setFrequency(
					((int) (unsign(registers[0x1E]) & 0x07) << 8) + unsign(registers[0x1D]));
			break;

		case 0x1E :           // Sound channel 3, frequency higher 3-bit
			registers[0x1E] = (byte) data;
			if (soundOn) {
				if ((registers[0x19] & 0x80) != 0) {
					dmgcpu.getSoundChip().channel3.setLength(unsign(registers[0x1B]));
				}
				dmgcpu.getSoundChip().channel3.setFrequency(
						((int) (unsign(registers[0x1E]) & 0x07) << 8) + unsign(registers[0x1D]));
			}
			break;

		case 0x20 :           // Sound channel 4, length
			if (soundOn) dmgcpu.getSoundChip().channel4.setLength(unsign(data) & 0x3F);
			registers[0x20] = (byte) data;
			break;


		case 0x21 :           // Sound channel 4, volume envelope
			if (soundOn) dmgcpu.getSoundChip().channel4.setEnvelope(
					(unsign(data) & 0xF0) >> 4,
					(unsign(data) & 0x07),
					(unsign(data) & 0x08) == 8);
			registers[0x21] = (byte) data;
			break;

		case 0x22 :           // Sound channel 4, polynomial parameters
			if (soundOn) dmgcpu.getSoundChip().channel4.setParameters(
					(unsign(data) & 0x07),
					(unsign(data) & 0x08) == 8,
					(unsign(data) & 0xF0) >> 4);
			registers[0x22] = (byte) data;
			break;

		case 0x23 :          // Sound channel 4, initial/consecutive
			registers[0x23] = (byte) data;
			if (soundOn) {
				if ((registers[0x23] & 0x80) != 0) {
					dmgcpu.getSoundChip().channel4.setLength(unsign(registers[0x20]) & 0x3F);
				}
				if ((registers[0x23] & 0x40) == 0) {
					dmgcpu.getSoundChip().channel4.setLength(-1);
				}
			}
			break;

		case 0x25 :           // Stereo select
			int chanData;

			registers[0x25] = (byte) data;

			if (soundOn) {
				chanData = 0;
				if ((unsign(data) & 0x01) != 0) {
					chanData |= SquareWaveGenerator.CHAN_LEFT;
				}
				if ((unsign(data) & 0x10) != 0) {
					chanData |= SquareWaveGenerator.CHAN_RIGHT;
				}
				dmgcpu.getSoundChip().channel1.setChannel(chanData);

				chanData = 0;
				if ((unsign(data) & 0x02) != 0) {
					chanData |= SquareWaveGenerator.CHAN_LEFT;
				}
				if ((unsign(data) & 0x20) != 0) {
					chanData |= SquareWaveGenerator.CHAN_RIGHT;
				}
				dmgcpu.getSoundChip().channel2.setChannel(chanData);

				chanData = 0;
				if ((unsign(data) & 0x04) != 0) {
					chanData |= SquareWaveGenerator.CHAN_LEFT;
				}
				if ((unsign(data) & 0x40) != 0) {
					chanData |= SquareWaveGenerator.CHAN_RIGHT;
				}
				dmgcpu.getSoundChip().channel3.setChannel(chanData);
			}

			break;

		case 0x30 :
		case 0x31 :
		case 0x32 :
		case 0x33 :
		case 0x34 :
		case 0x35 :
		case 0x36 :
		case 0x37 :
		case 0x38 :
		case 0x39 :
		case 0x3A :
		case 0x3B :
		case 0x3C :
		case 0x3D :
		case 0x3E :
		case 0x3F :
			if (soundOn) dmgcpu.getSoundChip().channel3.setSamplePair(num - 0x30, unsign(data));
			registers[num] = (byte) data;
			break;


		case 0x40 :           // LCDC
		//    System.out.println("LCDC write at " + JavaBoy.hexWord(dmgcpu.pc) + " = " + JavaBoy.hexWord(data));
			dmgcpu.graphicsChip.bgEnabled = true;

			if ((data & 0x20) == 0x20) {     // BIT 5
				dmgcpu.graphicsChip.winEnabled = true;
			} else {
				dmgcpu.graphicsChip.winEnabled = false;
			}

			if ((data & 0x10) == 0x10) {     // BIT 4
				dmgcpu.graphicsChip.bgWindowDataSelect = true;
			} else {
				dmgcpu.graphicsChip.bgWindowDataSelect = false;
			}

			if ((data & 0x08) == 0x08) {
				dmgcpu.graphicsChip.hiBgTileMapAddress = true;
			} else {
				dmgcpu.graphicsChip.hiBgTileMapAddress = false;
			}

			if ((data & 0x04) == 0x04) {      // BIT 2
				dmgcpu.graphicsChip.doubledSprites = true;
			} else {
				dmgcpu.graphicsChip.doubledSprites = false;
			}

			if ((data & 0x02) == 0x02) {     // BIT 1
				dmgcpu.graphicsChip.spritesEnabled = true;
			} else {
				dmgcpu.graphicsChip.spritesEnabled = false;
			}

			if ((data & 0x01) == 0x00) {     // BIT 0
				dmgcpu.graphicsChip.bgEnabled = false;
				dmgcpu.graphicsChip.winEnabled = false;
			}

			registers[0x40] = (byte) data;
			break;

		case 0x41 :
			//    System.out.println("STAT set to " + data + " lcdc is " + unsign(registers[0x44]) + " pc is " + JavaBoy.hexWord(dmgcpu.pc));
			registers[0x41] = (byte) data;
			break;

		case 0x42 :           // SCY
			//    System.out.println("SCY set to " + data + " lcdc is " + unsign(registers[0x44]) + " pc is " + JavaBoy.hexWord(dmgcpu.pc));
			registers[0x42] = (byte) data;
			break;

		case 0x43 :           // SCX
			//    System.out.println("SCX set to " + data + " lcdc is " + unsign(registers[0x44]) + " pc is " + JavaBoy.hexWord(dmgcpu.pc));
			registers[0x43] = (byte) data;
			break;

		case 0x46 :           // DMA
			int sourceAddress = (data << 8);
			//    System.out.println("DMA Transfer initiated from " + JavaBoy.hexWord(sourceAddress) + "!");

			// This could be sped up using System.arrayCopy, but hey.
			for (int i = 0x00; i < 0xA0; i++) {
				dmgcpu.addressWrite(0xFE00 + i, dmgcpu.addressRead(sourceAddress + i));
			}
			// This is meant to be run at the same time as the CPU is executing
			// instructions, but I don't think it's crucial.
			break;
		case 0x47 :           // FF47 - BKG and WIN palette
			//    System.out.println("Palette created!");
			dmgcpu.graphicsChip.backgroundPalette.decodePalette(data);
			if (registers[num] != (byte) data) {
				registers[num] = (byte) data;
				dmgcpu.graphicsChip.invalidateAll(GraphicsChip.TILE_BKG);
			}
			break;
		case 0x48 :           // FF48 - OBJ1 palette
			dmgcpu.graphicsChip.obj1Palette.decodePalette(data);
			if (registers[num] != (byte) data) {
				registers[num] = (byte) data;
				dmgcpu.graphicsChip.invalidateAll(GraphicsChip.TILE_OBJ1);
			}
			break;
		case 0x49 :           // FF49 - OBJ2 palette
			dmgcpu.graphicsChip.obj2Palette.decodePalette(data);
			if (registers[num] != (byte) data) {
				registers[num] = (byte) data;
				dmgcpu.graphicsChip.invalidateAll(GraphicsChip.TILE_OBJ2);
			}
			break;

		case 0x4F :
			if (dmgcpu.isGbcFeatures()) {
				dmgcpu.graphicsChip.tileStart = (data & 0x01) * 384;
				dmgcpu.graphicsChip.vidRamStart = (data & 0x01) * 0x2000;
			}
			registers[0x4F] = (byte) data;
			break;


		case 0x55 :
			if ((!hdmaRunning) && ((registers[0x55] & 0x80) == 0) && ((data & 0x80) == 0) ) {
				int dmaSrc = (unsign(registers[0x51]) << 8) +
				(unsign(registers[0x52]) & 0xF0);
				int dmaDst = ((unsign(registers[0x53]) & 0x1F) << 8) +
				(unsign(registers[0x54]) & 0xF0) + 0x8000;
				int dmaLen = ((unsign(data) & 0x7F) * 16) + 16;

				if (dmaLen > 2048) dmaLen = 2048;

				for (int r = 0; r < dmaLen; r++) {
					dmgcpu.addressWrite(dmaDst + r, dmgcpu.addressRead(dmaSrc + r));
				}
			} else {
				if ((unsign(data) & 0x80) == 0x80) {
					hdmaRunning = true;
					//      System.out.println("HDMA started");
					registers[0x55] = (byte) (data & 0x7F);
					break;
				} else if ((hdmaRunning) && ((unsign(data) & 0x80) == 0)) {
					hdmaRunning = false;
					//      System.out.println("HDMA stopped");
				}
			}

			registers[0x55] = (byte) data;
			break;



		case 0x69 :           // FF69 - BCPD: GBC BG Palette data write

			if (dmgcpu.isGbcFeatures()) {
				int palNumber = (registers[0x68] & 0x38) >> 3;
				dmgcpu.graphicsChip.gbcBackground[palNumber].setGbcColours(
						(unsign(registers[0x68]) & 0x06) >> 1,
						(unsign(registers[0x68]) & 0x01) == 1, unsign(data));
				dmgcpu.graphicsChip.invalidateAll(palNumber * 4);

				if ((unsign(registers[0x68]) & 0x80) != 0) {
					registers[0x68]++;
				}

			}


			registers[0x69] = (byte) data;
			break;

		case 0x6B :           // FF6B - OCPD: GBC Sprite Palette data write

			if (dmgcpu.isGbcFeatures()) {
				int palNumber = (registers[0x6A] & 0x38) >> 3;
			//     System.out.print("Pal " + palNumber + "  ");
			dmgcpu.graphicsChip.gbcSprite[palNumber].setGbcColours(
					(unsign(registers[0x6A]) & 0x06) >> 1,
					(unsign(registers[0x6A]) & 0x01) == 1, unsign(data));
			dmgcpu.graphicsChip.invalidateAll((palNumber * 4) + 32);

			if ((unsign(registers[0x6A]) & 0x80) != 0) {
				if ((registers[0x6A] & 0x3F) == 0x3F) {
					registers[0x6A] = (byte) 0x80;
				} else {
					registers[0x6A]++;
				}
			}
			}

			registers[0x6B] = (byte) data;
			break;


		case 0x70 :           // FF70 - GBC Work RAM bank
			if (dmgcpu.isGbcFeatures()) {
				if (((data & 0x07) == 0) || ((data & 0x07) == 1)) {
					dmgcpu.setGbcRamBank(1);
				} else {
					dmgcpu.setGbcRamBank(data & 0x07);
				}
			}
			registers[0x70] = (byte) data;
			break;


		default:

			registers[num] = (byte) data;
		break;
		}
	}