package com.pixelutilitys.arcade.emulators.AEPgb; /** * this source file released under the GNU Public Licence. * see the accompanying copyright.txt for more information. * Copyright (C) 2000-2001 Ben Mazur * modified by retroK, XTale and baka0815 2004 http://aepgb.aep-emu.de/ */ /** * PgbMemory controls the entire memory map of the Gameboy. * * It also contains some executing code, mostly in cycle(), * which is responsible for controlling the hardware and * setting the interrupt flag each CPU cycle. * * Also, DMA functions are handled here and SGB calls are * interpreted here and then (usually) passed on to the * video hardware. */ public final class PgbMemory { public PgbCart cart; public PgbVideo video; public PgbJoypad joy; public PgbNetplay net; // by retroK public SoundChip soundChip; boolean soundOn; byte soundIO[]; // end retroK public static final byte INT_JOYPAD = 0x10; public static final byte INT_SERIAL = 0x08; public static final byte INT_TIMER = 0x04; public static final byte INT_LCD = 0x02; public static final byte INT_VBLANK = 0x01; protected byte[] hiRAM; protected byte[] loRAM; public byte IF; public byte IE; public int tima; public int tma; public byte tac; protected boolean timerOn; protected int timeCounter; protected int timeLimit; public int div; // how many cycles left until something happens? public int cycles; public int cyclesLeft; public int cyclesSkipped; // Super Gameboy stuff protected int sgbBitCounter; protected int sgbPacketCounter; protected byte[] sgbBuffer; protected boolean sgbListening; protected int sgbCommand; protected int sgbPackets; // gameboy color byte gbcRAM; int loRAMOffset; byte gbcSpeed; byte rHDMA1; byte rHDMA2; byte rHDMA3; byte rHDMA4; int hdmaSrc; int hdmaDst; boolean hdmaDone; byte hdmaLastMode; int hdmaStop; public PgbMemory( PgbCart cart, PgbVideo video, PgbJoypad joy, PgbNetplay net) { this.cart = cart; this.video = video; this.joy = joy; this.net = net; hiRAM = new byte[0x80]; loRAM = new byte[0x8000]; sgbBuffer = new byte[0x80]; // max 8 packets for now // by retroK add sound soundOn = true; soundChip = new SoundChip(); soundIO = new byte[64]; // end by retroK reset(); } // by retroK: public void soundPlay() { soundChip.outputSound(); } public int unsign(byte b) { return (b & 0xff); } public void reset() { tima = 0; tma = 0; tac = 0; timerOn = false; timeCounter = 0; timeLimit = 0; div = 0xAF * 0x100; IF = 1; IE = 0; sgbBitCounter = 0; sgbPacketCounter = 0; sgbListening = false; sgbPackets = 1; gbcRAM = (byte) 0x00; loRAMOffset = 0xC000; gbcSpeed = (byte) 0x7E; cycles = 0; cyclesLeft = Integer.MAX_VALUE; recalcCyclesLeft(); hdmaDone = true; hdmaLastMode = PgbVideo.STAT_HBLANK; // by retroK: reset sound soundIO[16] = -128; soundIO[17] = -65; soundIO[18] = -13; soundIO[20] = -65; soundIO[22] = 63; soundIO[23] = 0; soundIO[25] = -65; soundIO[26] = 127; soundIO[27] = -1; soundIO[28] = -97; soundIO[30] = -65; soundIO[32] = -1; soundIO[33] = 0; soundIO[34] = 0; soundIO[35] = -65; soundIO[36] = 119; soundIO[37] = -13; soundIO[38] = -15; } /** * Recaclulates the number of cycles left. Called * when the timer, LCDC, LCY, or serial control is * changed. */ public void recalcCyclesLeft() { cyclesLeft = Integer.MAX_VALUE; // check cycles until timer if (timerOn && (timeLimit - timeCounter) < cyclesLeft) { cyclesLeft = (timeLimit - timeCounter); } // check cycles until next video mode switch if (video.lcd_on && video.cyclesLeft() < cyclesLeft) { cyclesLeft = video.cyclesLeft(); } // check cycles until next serial check if (net.cyclesLeft() < cyclesLeft) { cyclesLeft = net.cyclesLeft(); } // of course, this can't guess when a joystick // interrupt will occur. } /** * Advances the state of the hardware as though the number * of cpu cycles specified had passed and sets the interrupt * register appropriately. * * @param cv the number of cpu cycles to advance. */ public final void cycle(int cv) { cycles += cv; if (cycles > cyclesLeft) { // divider register (is this right at all?) // (does anything really use the divider register?) div -= cyclesLeft; if (div < 0) { div = 0xFF * 0x100; } // timer (does timer run w/o timer interrupt enabled?) if (timerOn) { // yes? timeCounter += cyclesLeft; if (timeCounter >= timeLimit) { timeCounter = 0; tima++; } if (tima > 255) { tima = tma; IF |= INT_TIMER; } } // netplay cycle /*if(true || (IE & INT_SERIAL) == INT_SERIAL)*/ { IF |= net.cycle(cyclesLeft); } // video cycle if (video.lcd_on) { IF |= video.cycle(cyclesLeft, this); } // hdma transfer if (!hdmaDone) { if (hdmaLastMode != PgbVideo.STAT_HBLANK && (byte) (video.getStat() & 0x03) == PgbVideo.STAT_HBLANK) { // transfer 16 bytes write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); write(hdmaDst++, read(hdmaSrc++)); if (hdmaDst >= hdmaStop) { hdmaDone = true; } } // eat up the rest of the hblank //IF |= video.cycle(video.cycles); hdmaLastMode = (byte) (video.getStat() & 0x03); } cycles -= cyclesLeft; recalcCyclesLeft(); } } /** * Set the timer control. */ public void setTac(byte tac) { timerOn = (tac & 0x04) == 0x04; timeCounter = 0; switch (tac & 0x03) { case 0 : timeLimit = 1024; break; case 1 : timeLimit = 16; break; case 2 : timeLimit = 64; break; case 3 : timeLimit = 256; break; } //recalcCyclesLeft(); } /** * Reads (signed) bytes from GB memory. * * @param address the gameboy memory address to read from. */ public final byte read(int address) { switch (address) { // Joypad Register (R/W) case 0xFF00 : return joy.read(); // Serial Transfer Data (R/W) case 0xFF01 : return net.getSerialData(); // SIO control (R/W) case 0xFF02 : return net.getSerialControl(); // empty GB registers? case 0xFF03 : return 0; // Divider Register (R/W) case 0xFF04 : // System.out.println("read Divider Register:" + (byte)(div / 0x100)); return (byte) (div / 0x100); // Timer counter (R/W) case 0xFF05 : //System.out.println("read Timer counter:" + tima); return (byte) tima; // Timer Modulo (R/W) case 0xFF06 : return (byte) tma; // Timer Control case 0xFF07 : return tac; // Interrupt Flag (R/W) case 0xFF0F : return IF; // LCD Control (R/W) case 0xFF40 : return video.getLcdc(); // LCDC Status (R/W) case 0xFF41 : return video.getStat(); // Scroll Y (R/W) case 0xFF42 : return (byte) video.scy; // Scroll X (R/W) case 0xFF43 : return (byte) video.scx; // LCDC Y-Coordinate (R) case 0xFF44 : return (byte) video.ly; // LY Compare (R/W) case 0xFF45 : return (byte) video.lyc; // BG & Window Palette Data (R/W) case 0xFF47 : return (byte) video.bgpal; // Object Palette 0 Data (R/W) case 0xFF48 : return (byte) video.objpal0; // Object Palette 1 Data (R/W) case 0xFF49 : return (byte) video.objpal1; // Window Y Position (R/W) case 0xFF4A : return (byte) video.wy; // Window X Position (R/W) case 0xFF4B : return (byte) video.wx; // GBC CPU Speed (R/W) case 0xFF4D : return gbcGetSpeed(); // GBC VRAM bank case 0xFF4F : //System.out.println("read GBC VRAM bank:" + Integer.toHexString(address)); return video.gbcGetVram(); // GBC rHDMA5 (DMA Mode / Control) case 0xFF55 : //System.out.println("read GBC rHDMA5 (DMA Mode / Control):" + Integer.toHexString(getHDMAControl() & 0xFF)); return getHDMAControl(); // GBC IR port (R/W) case 0xFF56 : return net.getIR(); // Color BG Palette Index (W) case 0xFF68 : return video.gbcGetBgpi(); // Color BG Palette Data (W) case 0xFF69 : return video.gbcGetBgpd(); // Color OBJ Palette Index (W) case 0xFF6A : return video.gbcGetObpi(); // Color OBJ Palette Data (W) case 0xFF6B : return video.gbcGetObpd(); //GBC RamBaml case 0xFF70: return gbcGetRamBank(); //whats this? case 0xFF7F: return 0; // Interrupt Enable (R/W) case 0xFFFF: return IE; } // cart ROM if (address < 0x8000) { return cart.read(address); } // VRAM if (address < 0xA000) { return video.read(address); } // cart RAM if (address < 0xC000) { return cart.read(address); } // internal (low) RAM bank 0 if (address < 0xD000) { return loRAM[address - 0xC000]; } // internal (low) RAM bank 1+ if (address < 0xE000) { return loRAM[address - loRAMOffset]; } // echo RAM if (address < 0xFE00) { return loRAM[address - 0xE000]; } // Object Attribute Memory (OAM) if (address < 0xFEA0) { return video.read(address); } // empty GB registers? //XXX bis FF80 IO register? if (address < 0xFF0F) { return 0; } // added by retroK sound: if (address < 0xFF40) { //some games look for this satus register, just set them correctly before returning value if ((address & 0xff) == 38) { if (soundChip.channel1.getLength() <= 0) soundIO[38] &= ~ 0x01; else soundIO[38] |= 0x01; if (soundChip.channel2.getLength() <= 0) soundIO[38] &= ~ 0x02; else soundIO[38] |= 0x02; if (soundChip.channel3.getLength() <= 0) soundIO[38] &= ~ 0x04; else soundIO[38] |= 0x04; if (soundChip.channel4.getLength() <= 0) soundIO[38] &= ~ 0x08; else soundIO[38] |= 0x08; } return soundIO[address & 0xff]; } // empty GBC registers? if (address < 0xFF55) { System.out.println( "read GBC register:" + Integer.toHexString(address)); return 0; } // empty GBC registers? if (address < 0xFF70) { System.out.println( "read GBC register:" + Integer.toHexString(address)); return 0; } // GBC RAM bank // empty GBC registers? if (address < 0xFF7F) { System.out.println( "read GBC register:" + Integer.toHexString(address)); return 0; } // internal (high) RAM if (address < 0xFFFF) { return hiRAM[address - 0xFF80]; } System.out.println( "Read from unmapped memory:" + Integer.toHexString(address)); //PgbSettings.paused = true; return 0; } /** * Write bytes into the gameboy memory. * * @param address the gameboy memory address to write to. * @param towrite the byte to write into memory. */ public final void write(int address, byte towrite) { switch (address) { // Joypad Register (R/W) [P1] case 0xFF00 : //System.out.println("write to Joypad Register:" + Integer.toHexString(towrite & 0xFF)); if (towrite == 0x30 && PgbSettings.system == PgbSettings.SYS_SGB) { sgbCommandBit(joy.selected); } joy.write(towrite); return; // Serial transfer data (R/W) [SB] case 0xFF01 : //System.out.println("write to Serial transfer data:" + Integer.toHexString(towrite & 0xFF)); net.setSerialData(towrite); return; // SIO control (R/W) [SC] case 0xFF02 : //System.out.println("write to SIO control:" + Integer.toHexString(towrite & 0xFF)); net.setSerialControl(towrite); recalcCyclesLeft(); return; // Divider Register (R/W) [DIV] case 0xFF04 : //System.out.println("write to Divider Register:" + Integer.toHexString(towrite & 0xFF)); div = 0; return; // Timer counter (R/W) [TIMA] case 0xFF05 : //System.out.println("write to Timer counter:" + Integer.toHexString(towrite & 0xFF)); tima = towrite; return; // Timer Modulo (R/W) [TMA] case 0xFF06 : //System.out.println("write to Timer Modulo:" + Integer.toHexString(towrite & 0xFF)); tma = towrite; return; // Timer Control [TAC] case 0xFF07 : //System.out.println("write to Timer Control:" + Integer.toHexString(towrite & 0xFF)); setTac(towrite); recalcCyclesLeft(); return; // sound by retroK modified and fixed by XTale ;) case 0xFF10 : if (soundOn) soundChip.channel1.setSweep( (unsign(towrite) & 0x70) >> 4, unsign(towrite) & 7, (unsign(towrite) & 8) == 1); soundIO[16] = towrite; return; case 0xFF11 : if (soundOn) { soundChip.channel1.setDutyCycle( (unsign(towrite) & 0xc0) >> 6); //use length if Bit6 in 20 is set if ((soundIO[20] & 0x40) == 1) soundChip.channel1.setLength( unsign(towrite) & 0x3f); } soundIO[17] = towrite; return; case 0xFF12 : if (soundOn) soundChip.channel1.setEnvelope( (unsign(towrite) & 0xf0) >> 4, unsign(towrite) & 7, (unsign(towrite) & 8) == 8); soundIO[18] = towrite; return; case 0xFF13 : soundIO[19] = towrite; if (soundOn) soundChip.channel1.setFrequency( ((unsign(soundIO[20]) & 7) << 8) + unsign(soundIO[19])); return; case 0xFF14 : soundIO[20] = towrite; if (soundOn) { if ((soundIO[20] & 0x80) != 0) { soundChip.channel1.setLength( unsign(soundIO[17]) & 0x3f); soundChip.channel1.setEnvelope( (unsign(soundIO[18]) & 0xf0) >> 4, unsign(soundIO[18]) & 7, (unsign(soundIO[18]) & 8) == 8); //update statusregister soundIO[38] |= 0x01; } if ((soundIO[20] & 0x40) == 0) { //XXX ==1? soundChip.channel1.setLength(-1); soundIO[38] &= ~ 0x01; } soundChip.channel1.setFrequency( ((unsign(soundIO[20]) & 7) << 8) + unsign(soundIO[19])); } return; case 0xFF15 : soundIO[21] = towrite; return; case 0xFF16 : if (soundOn) { soundChip.channel2.setDutyCycle( (unsign(towrite) & 0xc0) >> 6); soundChip.channel2.setLength(unsign(towrite) & 0x3f); } soundIO[22] = towrite; return; case 0xFF17 : if (soundOn) soundChip.channel2.setEnvelope( (unsign(towrite) & 0xf0) >> 4, unsign(towrite) & 7, (unsign(towrite) & 8) == 8); soundIO[23] = towrite; return; case 0xFF18 : soundIO[24] = towrite; if (soundOn) soundChip.channel2.setFrequency( ((unsign(soundIO[25]) & 7) << 8) + unsign(soundIO[24])); return; case 0xFF19 : soundIO[25] = towrite; if (soundOn) { if ((soundIO[25] & 0x80) != 0) { soundChip.channel2.setLength( unsign(soundIO[22]) & 0x3f); soundChip.channel2.setEnvelope( (unsign(soundIO[23]) & 0xf0) >> 4, unsign(soundIO[23]) & 7, (unsign(soundIO[23]) & 8) == 8); //update statusregister soundIO[38] |= 0x02; } if ((soundIO[25] & 0x40) == 0) { ///XXX ==1? soundChip.channel2.setLength(-1); soundIO[39] &= ~0x02; } soundChip.channel2.setFrequency( ((unsign(soundIO[25]) & 7) << 8) + unsign(soundIO[24])); } return; case 0xFF1A : if (soundOn) if ((unsign(towrite) & 0x80) != 0) soundChip.channel3.setVolume( (unsign(soundIO[28]) & 0x60) >> 5); else soundChip.channel3.setVolume(0); soundIO[26] = towrite; return; case 0xFF1B : soundIO[27] = towrite; if (soundOn) //only use if Bit6 in 30 is set if ((soundIO[30] & 0x40) == 1) soundChip.channel3.setLength(unsign(towrite)); return; case 0xFF1C : soundIO[28] = towrite; if (soundOn) soundChip.channel3.setVolume( (unsign(soundIO[28]) & 0x60) >> 5); return; case 0xFF1D : soundIO[29] = towrite; if (soundOn) soundChip.channel3.setFrequency( ((unsign(soundIO[30]) & 7) << 8) + unsign(soundIO[29])); return; case 0xFF1E : soundIO[30] = towrite; if (soundOn) { if ((soundIO[25] & 0x80) != 0) { soundChip.channel3.setLength(unsign(soundIO[27])); //update status soundIO[38] |= 0x04; } soundChip.channel3.setFrequency( ((unsign(soundIO[30]) & 7) << 8) + unsign(soundIO[29])); // if bit 6 = stop??? if ((soundIO[30] & 0x40) == 0) { //XXX ==1? soundChip.channel3.setLength(-1); soundIO[38] &= ~0x04; } } return; case 0xFF20 : if (soundOn) soundChip.channel4.setLength(unsign(towrite) & 0x3f); soundIO[32] = towrite; return; case 0xFF21 : if (soundOn) soundChip.channel4.setEnvelope( (unsign(towrite) & 0xf0) >> 4, unsign(towrite) & 7, (unsign(towrite) & 8) == 8); soundIO[33] = towrite; return; case 0xFF22 : soundIO[34] = towrite; //Channel4 plynomial counter return; case 0xFF23 : soundIO[35] = towrite; if (soundOn) { if ((soundIO[35] & 0x80) != 0) { soundChip.channel4.setLength( unsign(soundIO[32]) & 0x3f); //update status soundIO[38] |= 0x08; } if ((soundIO[35] & 0x40) == 0) { //XXX ==1? soundChip.channel4.setLength(-1); soundIO[38] &= ~0x08; } } return; case 0xFF24 : //Channel Controll - useless in emu ,) //System.out.println("call with" + Integer.toBinaryString((towrite & 0xff))); soundIO[36] = towrite; return; case 0xFF25 : //System.out.println(Integer.toBinaryString(unsign(towrite))); soundIO[37] = towrite; if (soundOn) { //Channel 1 = Bit 0 (left) or Bit 4 (right) int j = 0; if ((unsign(towrite) & 1) != 0) j |= 1; if ((unsign(towrite) & 0x10) != 0) j |= 2; soundChip.channel1.setChannel(j); // Channel 2 = Bit 1 (left) or Bit 5 (right) j = 0; if ((unsign(towrite) & 2) != 0) j |= 1; if ((unsign(towrite) & 0x20) != 0) j |= 2; soundChip.channel2.setChannel(j); // Channel 3 = Bit 2 (left) or Bit 6 (right) j = 0; if ((unsign(towrite) & 4) != 0) j |= 1; if ((unsign(towrite) & 0x40) != 0) j |= 2; soundChip.channel3.setChannel(j); // Channel 4 = Bit 3 (left) or Bit 7 (right) j = 0; if ((unsign(towrite) & 8) != 0) j |= 1; if ((unsign(towrite) & 0x80) != 0) j |= 2; soundChip.channel4.setChannel(j); } return; case 0xFF26 : //bit 7 - sound off if ((towrite & 0x80) == 0) { soundChip.channel1.setVolume3(0); soundChip.channel2.setVolume3(0); soundChip.channel3.setVolume(0); soundChip.channel4.setLength(-1); } soundIO[38] = towrite; return; // Interrupt Flag (R/W) case 0xFF0F : //System.out.println("write to Interrupt Flag: " + Integer.toHexString(towrite & 0xFF)); IF = towrite; return; // LCD Control (R/W) case 0xFF40 : //System.out.println("write to LCD Control:" + Integer.toHexString(towrite & 0xFF)); video.setLcdc(towrite); recalcCyclesLeft(); return; // LCDC Status (R/W) case 0xFF41 : //System.out.println("write to LCDC Status:" + Integer.toHexString(towrite & 0xFF)); video.setStat(towrite); recalcCyclesLeft(); return; // Scroll Y (R/W) case 0xFF42 : video.scy = towrite & 0xFF; //System.out.println("write to scrolly:" + (towrite & 0xFF)); return; // Scroll X (R/W) case 0xFF43 : video.scx = towrite & 0xFF; //System.out.println("write to scrollx:" + (towrite & 0xFF) + " sxo:" + ((towrite & 0xFF) % 8)); return; // LCDC Y-Coordinate (R) case 0xFF44 : //System.out.println("write to LCDC Y-Coordinate:" + (towrite & 0xFF)); video.ly = 0; recalcCyclesLeft(); return; // LY Compare (R/W) case 0xFF45 : //System.out.println("write to LY Compare:" + (towrite & 0xFF)); video.lyc = towrite & 0xFF; return; // OAM-DMA Transfer and Start Address (W) case 0xFF46 : oamDMA(towrite); return; // BG Palette Data (W) case 0xFF47 : //System.out.println("write to BG Palette Data:" + (towrite & 0xFF)); video.setBgPal(towrite & 0xFF); return; // Object Palette 0 Data (W) case 0xFF48 : //System.out.println("write to Object Palette 0 Data:" + (towrite & 0xFF)); video.setObjPal0(towrite & 0xFF); return; // Object Palette 1 Data (W) case 0xFF49 : //System.out.println("write to Object Palette 1 Data:" + (towrite & 0xFF)); video.setObjPal1(towrite & 0xFF); return; // Window Y Position (R/W) case 0xFF4A : video.wy = towrite & 0xFF; return; // Window X Position (R/W) case 0xFF4B : //System.out.println("write to Window X Position:" + (towrite & 0xFF)); video.wx = towrite & 0xFF; return; // GBC CPU speed [KEY1] case 0xFF4D : //System.out.println("write to GBC CPU speed:" + (towrite & 0xFF)); gbcSetSpeed(towrite); return; // GBC VRAM bank [VBK] case 0xFF4F : //System.out.println("write to GBC VRAM bank:" + (towrite & 0xFF)); video.gbcSetVram(towrite); return; // GBC rHDMA1 (bit 7-0 of Source MSB) [HDMA1] case 0xFF51 : //System.out.println("write to GBC rHDMA1:" + (towrite & 0xFF)); rHDMA1 = towrite; return; // GBC rHDMA2 (bit 7-4 of Source LSB) [HDMA2] case 0xFF52 : //System.out.println("write to GBC rHDMA2:" + (towrite & 0xFF)); rHDMA2 = towrite; return; // GBC rHDMA3 (bit 4-0 of Destination MSB) [HDMA3] case 0xFF53 : //System.out.println("write to GBC rHDMA3:" + (towrite & 0xFF)); rHDMA3 = towrite; return; // GBC rHDMA4 (bit 7-4 of Destination LSB) [HDMA4] case 0xFF54 : //System.out.println("write to GBC rHDMA4:" + (towrite & 0xFF)); rHDMA4 = towrite; return; // GBC rHDMA5 (DMA Mode / Control) [HDMA5] case 0xFF55 : //System.out.println("write to GBC rHDMA5 (DMA Mode / Control):" + (towrite & 0xFF)); setHDMAControl(towrite); return; // GBC IR port (R/W) [RP] case 0xFF56 : net.setIR(towrite); return; // Color BG Palette Index (W) [BCPS] case 0xFF68 : // only set this on GBC if (PgbSettings.system == PgbSettings.SYS_GBC) { video.gbcSetBgpi(towrite); } return; // Color BG Palette Data (W) [BCPD] case 0xFF69 : // only set this on GBC if (PgbSettings.system == PgbSettings.SYS_GBC) { video.gbcSetBgpd(towrite); } return; // Color OBJ Palette Index (W) [OCPS] case 0xFF6A : // only set this on GBC if (PgbSettings.system == PgbSettings.SYS_GBC) { video.gbcSetObpi(towrite); } return; // Color OBJ Palette Data (W) [OCPD] case 0xFF6B : // only set this on GBC if (PgbSettings.system == PgbSettings.SYS_GBC) { video.gbcSetObpd(towrite); } return; // GBC RAM bank [SVBK] case 0xFF70 : //System.out.println("write to GBC RAM bank:" + (towrite & 0xFF)); gbcSetRamBank(towrite); return; // GBC mystery register case 0xFF7F : System.out.println( "write to GBC mystery register FF7F:" + Integer.toHexString(towrite & 0xFF) + " " + Integer.toBinaryString(towrite & 0xFF)); return; // Interrupt Enable (R/W) case 0xFFFF: IE = towrite; recalcCyclesLeft(); return; // strange register called by the demotronic demo... case 0xFF1F: System.out.println("write to GBC mystery register FF1F:" + Integer.toHexString(towrite & 0xFF) + " " + Integer.toBinaryString(towrite & 0xFF)); // IE = towrite; // recalcCyclesLeft(); return; } // cart ROM (and MBC registers) if (address < 0x8000) { cart.write(address, towrite); return; } // VRAM if (address < 0xA000) { video.write(address, towrite); return; } // cart RAM if (address < 0xC000) { cart.write(address, towrite); return; } // internal (low) RAM bank 0 if (address < 0xD000) { loRAM[address - 0xC000] = towrite; return; } // internal (low) RAM bank 1+ if (address < 0xE000) { loRAM[address - loRAMOffset] = towrite; return; } // echo RAM if (address < 0xFE00) { loRAM[address - 0xE000] = towrite; return; } // Object Attribute Memory (OAM) if (address < 0xFEA0) { video.write(address, towrite); return; } // empty ??? if (address < 0xFF00 && address >= 0xFEA0) { if (PgbSettings.DEBUG && towrite != 0) { // tell me if they put anything but zeros here System.out.println( "write to empty ???:" + Integer.toHexString(towrite & 0xFF)); } return; } if (address >= 0xFF26 && address <= 0xFF2F) { soundIO[address & 0xff] = towrite; return; } if (address >= 0xFF30 && address <= 0xFF3F) { soundIO[address & 0xff] = towrite; if (soundOn) soundChip.channel3.setSamplePair( address & 0xf, unsign(towrite)); return; } // internal (high) RAM if (address < 0xFFFF && address >= 0xFF80) { hiRAM[address - 0xFF80] = towrite; return; } System.out.println( "Write to unmapped memory:" + Integer.toHexString(address) + ", " + Integer.toHexString(towrite)); //PgbSettings.paused = true; } public final void write(int address, int towrite) { write(address, (byte) towrite); } /** * write two bytes */ public void writeWord(int address, int word) { write(address, word & 0xFF); write(address + 1, word >> 8); } /** * set the gameboy color ram bank */ public void gbcSetRamBank(byte control) { if (PgbSettings.system == PgbSettings.SYS_GBC) { //System.out.println("set gameboy color ram bank: " + Integer.toHexString(control)); gbcRAM = control; int bank = ((gbcRAM & 0x07) == 0) ? 0 : (gbcRAM & 0x07) - 1; loRAMOffset = 0xC000 - bank * 0x1000; } } public byte gbcGetRamBank() { return gbcRAM; } /** * set the gameboy color speed */ public void gbcSetSpeed(byte control) { gbcSpeed = control; } public byte gbcGetSpeed() { if (PgbSettings.system == PgbSettings.SYS_GBC) { return gbcSpeed; } else { return 0; } } /** * this does GB Classic DMA transfers */ public void oamDMA(byte address) { int i, start; //System.out.println("DMA transfer:" + address); start = (address & 0xFF) * 0x0100; for (i = 0; i < 0xA0; i++) { video.write(0xFE00 + i, read(start + i)); } } /** * this does GBC HDMA transfers */ public void setHDMAControl(byte control) { boolean mode = (control & 0x80) == 0x80; int i, length = ((control & 0x7F) + 1) * 0x10; hdmaSrc = ((rHDMA1 & 0xFF) << 8) | (rHDMA2 & 0xF0); hdmaDst = ((rHDMA3 & 0x1F) << 8) | (rHDMA4 & 0xF0) + 0x8000; if (mode) { //System.out.println("HDMA (" + Integer.toHexString(hdmaSrc) + "-" + Integer.toHexString(hdmaSrc + length) + " : " + Integer.toHexString(hdmaDst) + "-" + Integer.toHexString(hdmaDst + length) + ") begun"); hdmaDone = false; hdmaStop = hdmaDst + length; } else { // GDMA //System.out.println("GDMA (" + Integer.toHexString(hdmaSrc) + "-" + Integer.toHexString(hdmaSrc + length) + " : " + Integer.toHexString(hdmaDst) + "-" + Integer.toHexString(hdmaDst + length) + ") begun"); for (i = 0; i < length; i++) { write(hdmaDst++, read(hdmaSrc++)); } // halt cpu for 110+n*7.68 microseconds? //cycle(440 + (length / 16) * 32); } } public byte getHDMAControl() { return hdmaDone ? (byte) 0x01 : (byte) 0x00; } /** * this is called every time 0x30 is written to the * joystick register. it is responsible for decoding * the sgb command bits */ public void sgbCommandBit(byte b) { if (sgbPacketCounter == 0 && sgbBitCounter == 8) { sgbCheckCommand(); } else if (sgbBitCounter == 128) { if (++sgbPacketCounter == sgbPackets) { sgbCommandExec(); sgbPacketCounter = 0; } sgbBitCounter = 0; sgbListening = false; } else if (b == 0x00) { // wake up! sgbListening = true; } else if (sgbListening && b == 0x10) { // one? sgbBuffer[sgbPacketCounter * 16 + sgbBitCounter / 8] |= (byte) ( 1 << (sgbBitCounter & 7) ); sgbBitCounter++; } else if (sgbListening && b == 0x20) { // zero? sgbBuffer[sgbPacketCounter * 16 + sgbBitCounter / 8] &= ~(byte) ( 1 << (sgbBitCounter & 7) ); sgbBitCounter++; } } /** * check out the first byte of the buffer to * parse the command and packets */ public void sgbCheckCommand() { // first packet sgbCommand = (sgbBuffer[0] & 0xF8) >> 3; sgbPackets = sgbBuffer[0] & 0x07; //System.out.println("recieved SGB sgbCommand: " + Integer.toHexString(sgbCommand) + ", sgbPackets:" + sgbPackets); } /** * called to process the current SGB command buffer, * executing it if all packets have been written */ public void sgbCommandExec() { int i; String desc; /* sgbPacketCounter++; if(sgbPacketCounter == 1) { // first packet sgbCommand = (sgbBuffer[0] & 0xF8) >> 3; sgbPackets = sgbBuffer[0] & 0x07; } if(sgbPacketCounter < sgbPackets) { // don't process yet... return; } */ if (PgbSettings.DEBUG) { System.out.println( "recieved SGB sgbCommand: " + Integer.toHexString(sgbCommand) + ", sgbPackets:" + sgbPackets); System.out.print("bits: "); for (i = 0; i < 16 * sgbPackets; i++) { System.out.print(Integer.toHexString(sgbBuffer[i] & 0xFF)); System.out.print(" "); if ((i & 15) == 15) { System.out.print("\n"); } } } switch (sgbCommand) { case 0x00 : desc = "Download color palettes 0 & 1"; video.sgbSetPalette(0, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(0, 1, sgbBuffer[4], sgbBuffer[3]); video.sgbSetPalette(0, 2, sgbBuffer[6], sgbBuffer[5]); video.sgbSetPalette(0, 3, sgbBuffer[8], sgbBuffer[7]); video.sgbSetPalette(1, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(1, 1, sgbBuffer[10], sgbBuffer[9]); video.sgbSetPalette(1, 2, sgbBuffer[12], sgbBuffer[11]); video.sgbSetPalette(1, 3, sgbBuffer[14], sgbBuffer[13]); break; case 0x01 : desc = "Download color palettes 2 & 3"; video.sgbSetPalette(2, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(2, 1, sgbBuffer[4], sgbBuffer[3]); video.sgbSetPalette(2, 2, sgbBuffer[6], sgbBuffer[5]); video.sgbSetPalette(2, 3, sgbBuffer[8], sgbBuffer[7]); video.sgbSetPalette(3, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(3, 1, sgbBuffer[10], sgbBuffer[9]); video.sgbSetPalette(3, 2, sgbBuffer[12], sgbBuffer[11]); video.sgbSetPalette(3, 3, sgbBuffer[14], sgbBuffer[13]); break; case 0x02 : desc = "Download color palettes 0 & 3"; video.sgbSetPalette(0, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(0, 1, sgbBuffer[4], sgbBuffer[3]); video.sgbSetPalette(0, 2, sgbBuffer[6], sgbBuffer[5]); video.sgbSetPalette(0, 3, sgbBuffer[8], sgbBuffer[7]); video.sgbSetPalette(3, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(3, 1, sgbBuffer[10], sgbBuffer[9]); video.sgbSetPalette(3, 2, sgbBuffer[12], sgbBuffer[11]); video.sgbSetPalette(3, 3, sgbBuffer[14], sgbBuffer[13]); break; case 0x03 : desc = "Download color palettes 1 & 2"; video.sgbSetPalette(1, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(1, 1, sgbBuffer[4], sgbBuffer[3]); video.sgbSetPalette(1, 2, sgbBuffer[6], sgbBuffer[5]); video.sgbSetPalette(1, 3, sgbBuffer[8], sgbBuffer[7]); video.sgbSetPalette(2, 0, sgbBuffer[2], sgbBuffer[1]); video.sgbSetPalette(2, 1, sgbBuffer[10], sgbBuffer[9]); video.sgbSetPalette(2, 2, sgbBuffer[12], sgbBuffer[11]); video.sgbSetPalette(2, 3, sgbBuffer[14], sgbBuffer[13]); break; case 0x04 : desc = "'Block' Area Designation Mode"; for (i = 0; i < sgbBuffer[1]; i++) { video.sgbBlockDesignate( sgbBuffer[i * 6 + 2], sgbBuffer[i * 6 + 3], sgbBuffer[i * 6 + 4], sgbBuffer[i * 6 + 5], sgbBuffer[i * 6 + 6], sgbBuffer[i * 6 + 7]); } break; case 0x05 : desc = "'Line' Area Designation Mode"; for (i = 0; i < sgbBuffer[1]; i++) { video.sgbLineDesignate(sgbBuffer[i + 2]); } break; case 0x06 : desc = "'Divide' Area Designation Mode"; video.sgbDivideDesignate(sgbBuffer[1], sgbBuffer[2]); break; case 0x07 : desc = "'1CHR' Area Designation Mode"; for (i = 0; i < 90; i++) { //video.sgbSetPaletteOverlayByte(i, sgbBuffer[i + 6]); video.sgbSetPaletteOverlay( i * 4 , (sgbBuffer[i + 6]) & 0x03); video.sgbSetPaletteOverlay( i * 4 + 1, (sgbBuffer[i + 6]) & 0x0C >> 2); video.sgbSetPaletteOverlay( i * 4 + 2, (sgbBuffer[i + 6]) & 0x30 >> 4); video.sgbSetPaletteOverlay( i * 4 + 3, (sgbBuffer[i + 6]) & 0xC0 >> 6); } break; case 0x08 : desc = "Sound On/Off"; break; case 0x0A : desc = "Set SGB Palette Indirect"; video.sgbSetPaletteIndirect( (sgbBuffer[2] & 0x01) << 8 | (sgbBuffer[1] & 0xFF), (sgbBuffer[4] & 0x01) << 8 | (sgbBuffer[3] & 0xFF), (sgbBuffer[6] & 0x01) << 8 | (sgbBuffer[5] & 0xFF), (sgbBuffer[8] & 0x01) << 8 | (sgbBuffer[7] & 0xFF), (sgbBuffer[10] & 0x01) << 8 | (sgbBuffer[9] & 0xFF)); break; case 0x0B : desc = "Set System Color Palette Data"; video.sgbPaletteTransfer(); break; case 0x0F : desc = "Super NES WRAM Transfer 1"; break; case 0x11 : desc = "Controller 2 Request"; joy.setSgbPlayer(sgbBuffer[1]); break; case 0x13 : desc = "CharSet Transfer"; video.sgbCharsetTransfer( (sgbBuffer[1] & 0x04) == 0x04, (sgbBuffer[1] & 0x01) == 0x01); break; case 0x14 : desc = "Picture Transfer"; video.sgbPictureTransfer(); break; case 0x15 : desc = "Set Attribute from ATF"; video.sgbAtfTransfer(); break; case 0x16 : desc = "Set Data from ATF"; video.sgbSetOverlayFromAtf(sgbBuffer[1]); break; case 0x17 : desc = "GameBoy Window Mask"; if (sgbBuffer[1] == 0) { video.sgbvramon = false; } if (sgbBuffer[1] == 1 || sgbBuffer[1] == 3) { video.sgbvramon = true; } break; default : desc = "Unprogrammed"; break; } if (PgbSettings.DEBUG) { System.out.println(desc + "\n"); } // done //sgbBitCounter = 0; //sgbPacketCounter = 0; //sgbPackets = 1; } }