/* * HalfNES by Andrew Hoffman * Licensed under the GNU GPL Version 3. See LICENSE file */ package com.grapeshot.halfnes.mappers; import com.grapeshot.halfnes.*; /** * * @author Andrew */ public class TengenRamboMapper extends Mapper { private int whichbank = 0; private boolean prgconfig = false, chrconfig = false, chrmode1k = false, irqmode = false; private int irqctrreload = 0; private int irqctr = 0; private boolean irqenable = false; private boolean irqreload = false; private int prgreg0 = 0, prgreg1 = 0, prgreg2 = 0; private int[] chrreg = new int[8]; private boolean interrupted = false; @Override public void loadrom() throws BadMapperException { //needs to be in every mapper. Fill with initial cfg super.loadrom(); //on startup: for (int i = 0; i < 8; ++i) { prg_map[i] = (1024 * i); prg_map[i + 8] = (1024 * i); //yes this actually matters; MMC3 does NOT start up in a random state //(at least Smash TV and TMNT3 expect certain banks w/o even setting up mapper) } for (int i = 1; i <= 32; ++i) { prg_map[32 - i] = prgsize - (1024 * i); } for (int i = 0; i < 8; ++i) { chr_map[i] = 0; } setprgregs(); //cpuram.setPrgRAMEnable(false); } @Override public final void cartWrite(int addr, int data) { if (addr < 0x8000 || addr > 0xffff) { super.cartWrite(addr, data); return; } //bankswitches here //different register for even/odd writes if (((addr & (utils.BIT0)) != 0)) { //odd registers if ((addr >= 0x8000) && (addr <= 0x9fff)) { //bank change //System.err.println("setting " + whichbank + " " + data + " " + prgconfig); switch (whichbank) { case 0: case 1: case 2: case 3: case 4: case 5: chrreg[whichbank] = data; setupchr(); break; case 6: prgreg0 = data; setprgregs(); break; case 7: //bank 7 always swappable, always in same place prgreg1 = data; setprgregs(); break; case 8: case 9: //setup extra chr banks; chrreg[whichbank - 2] = data; break; case 0xf: prgreg2 = data; setprgregs(); break; } } else if ((addr >= 0xA000) && (addr <= 0xbfff)) { //prg ram write protect //cpuram.setPrgRAMEnable(!utils.getbit(data, 7)); } else if ((addr >= 0xc000) && (addr <= 0xdfff)) { //any value here reloads irq counter irqreload = true; irqmode = ((data & (utils.BIT0)) != 0); } else if ((addr >= 0xe000) && (addr <= 0xffff)) { //iany value here enables interrupts irqenable = true; } } else { //even registers if ((addr >= 0x8000) && (addr <= 0x9fff)) { //bank select whichbank = data & 0xf; chrmode1k = ((data & (utils.BIT5)) != 0); prgconfig = ((data & (utils.BIT6)) != 0); //if bit is false, 8000-9fff swappable and c000-dfff fixed to 2nd to last bank //if bit is true, c000-dfff swappable and 8000-9fff fixed to 2nd to last bank chrconfig = ((data & (utils.BIT7)) != 0); //if false: 2 2k banks @ 0000-0fff, 4 1k banks in 1000-1fff //if true: 4 1k banks @ 0000-0fff, 2 2k banks @ 1000-1fff setupchr(); setprgregs(); } else if ((addr >= 0xA000) && (addr <= 0xbfff)) { //mirroring setup if (scrolltype != MirrorType.FOUR_SCREEN_MIRROR) { setmirroring(((data & (utils.BIT0)) != 0) ? MirrorType.H_MIRROR : MirrorType.V_MIRROR); } } else if ((addr >= 0xc000) && (addr <= 0xdfff)) { //value written here used to reload irq counter _@ end of scanline_ irqctrreload = data; irqreload = true; } else if ((addr >= 0xe000) && (addr <= 0xffff)) { //any value here disables IRQ and acknowledges if (interrupted) { --cpu.interrupt; } interrupted = false; irqenable = false; irqctr = irqctrreload; } } } private void setupchr() { if (chrconfig) { if (chrmode1k) { setppubank(1, 0, chrreg[2]); setppubank(1, 1, chrreg[3]); setppubank(1, 2, chrreg[4]); setppubank(1, 3, chrreg[5]); setppubank(1, 4, chrreg[0]); setppubank(1, 5, chrreg[6]); setppubank(1, 6, chrreg[1]); setppubank(1, 7, chrreg[7]); } else { setppubank(1, 0, chrreg[2]); setppubank(1, 1, chrreg[3]); setppubank(1, 2, chrreg[4]); setppubank(1, 3, chrreg[5]); //Lowest bit of bank number IS IGNORED for the 2k banks setppubank(2, 4, (chrreg[0] >> 1) << 1); setppubank(2, 6, (chrreg[1] >> 1) << 1); } } else { if (chrmode1k) { setppubank(1, 0, chrreg[0]); setppubank(1, 1, chrreg[6]); setppubank(1, 2, chrreg[1]); setppubank(1, 3, chrreg[7]); setppubank(1, 4, chrreg[2]); setppubank(1, 5, chrreg[3]); setppubank(1, 6, chrreg[4]); setppubank(1, 7, chrreg[5]); } else { setppubank(1, 4, chrreg[2]); setppubank(1, 5, chrreg[3]); setppubank(1, 6, chrreg[4]); setppubank(1, 7, chrreg[5]); setppubank(2, 0, (chrreg[0] >> 1) << 1); setppubank(2, 2, (chrreg[1] >> 1) << 1); } } } private void setprgregs() { //no matter what, c000-dfff is last bank if (!prgconfig) { //map r6 to first 8k, r7 to 2nd, rf to 3rd for (int i = 0; i < 8; ++i) { prg_map[i] = (1024 * (i + (prgreg0 * 8))) % prgsize; prg_map[i + 8] = (1024 * (i + (prgreg1 * 8))) % prgsize; prg_map[i + 16] = (1024 * (i + (prgreg2 * 8))) % prgsize; } } else { //map rf to 1st 8k, r6 to 2nd, r7 to 3rd for (int i = 0; i < 8; ++i) { prg_map[i] = (1024 * (i + (prgreg2 * 8))) % prgsize; prg_map[i + 8] = (1024 * (i + (prgreg0 * 8))) % prgsize; prg_map[i + 16] = (1024 * (i + (prgreg1 * 8))) % prgsize; } } } @Override public void notifyscanline(int scanline) { if (irqmode) { return; } //Scanline counter if (scanline > 239 && scanline != 261) { //clocked on LAST line of vblank and all lines of frame. Not on 240. return; } if (!ppu.mmc3CounterClocking()) { return; } clockscanlinecounter(); } int remainder; boolean intnextcycle = false; @Override public void cpucycle(int cycles) { if (intnextcycle) { intnextcycle = false; if (!interrupted) { ++cpu.interrupt; interrupted = true; } } if (!irqmode) { return; } remainder += cycles; for (int i = 0; i < remainder; ++i) { if ((i & 3) == 0) { clockscanlinecounter(); } } remainder %= 4; } public void clockscanlinecounter() { if (irqreload) { irqreload = false; irqctr = irqctrreload + 1; } else if (irqctr == 0) { irqctr = irqctrreload; } else { if (--irqctr == 0 && irqenable) { intnextcycle = true; } } } private void setppubank(int banksize, int bankpos, int banknum) { // System.err.println(banksize + ", " + bankpos + ", "+ banknum); for (int i = 0; i < banksize; ++i) { chr_map[i + bankpos] = (1024 * ((banknum) + i)) % chrsize; } } }