/* * HalfNES by Andrew Hoffman * Licensed under the GNU GPL Version 3. See LICENSE file */ package com.grapeshot.halfnes.mappers; import com.grapeshot.halfnes.utils; import com.grapeshot.halfnes.*; import com.grapeshot.halfnes.audio.*; /** * * @author Andrew */ public class FME7Mapper extends Mapper { private int commandRegister = 0; private int soundCommand = 0; private int[] charbanks = new int[8]; //8 1k char rom banks private int[] prgbanks = new int[4]; //4 8k prg banks - PLUS 1 8k fixed one private boolean ramEnable = true; private boolean ramSelect = false; private int irqcounter = 0xffff; //really needs to be unsigned but we'll cheese it private boolean irqenabled; private boolean irqclock; private boolean hasInitSound = false; private final ExpansionSoundChip sndchip = new Sunsoft5BSoundChip(); private boolean interrupted = false; public void loadrom() throws BadMapperException { //needs to be in every mapper. Fill with initial cfg super.loadrom(); //on startup: prg_map = new int[40]; //(trollface) //fixed bank maps to last 8k of rom, set everything else to last chunk //as well. for (int i = 1; i <= 40; ++i) { prg_map[40 - i] = prgsize - (1024 * i); } for (int i = 0; i < 8; ++i) { chr_map[i] = 0; } } @Override public final int cartRead(int addr) { //five possible rom banks. if (addr >= 0x6000) { if (addr < 0x8000 && ramSelect) { if (ramEnable) { return prgram[addr - 0x6000]; } else { return addr >> 8; //open bus } } return prg[prg_map[(addr - 0x6000) >> 10] + (addr & 1023)]; } return addr >> 8; //open bus } @Override public final void cartWrite(final int addr, final int data) { if (addr < 0x8000 || addr > 0xffff) { super.cartWrite(addr, data); return; } if (addr == 0x8000) { //command register commandRegister = data & 0xf; } else if (addr == 0xc000) { //sound command register soundCommand = data & 0xf; if (!hasInitSound) { //only initialize the sound chip if anything writes a sound command. cpuram.apu.addExpnSound(sndchip); hasInitSound = true; } } else if (addr == 0xa000) { //mapper data register switch (commandRegister) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: //char bank switches charbanks[commandRegister] = data; setbanks(); break; case 8: ramEnable = ((data & (utils.BIT7)) != 0); ramSelect = ((data & (utils.BIT6)) != 0); prgbanks[0] = data & 0x3f; setbanks(); break; case 9: case 0xa: case 0xb: //prg bank switch prgbanks[commandRegister - 8] = data; setbanks(); break; case 0xc: //mirroring select switch (data & 3) { case 0: setmirroring(Mapper.MirrorType.V_MIRROR); break; case 1: setmirroring(Mapper.MirrorType.H_MIRROR); break; case 2: setmirroring(Mapper.MirrorType.SS_MIRROR0); break; case 3: setmirroring(Mapper.MirrorType.SS_MIRROR1); break; } case 0xd: //irq - let's put this in and hope it works irqclock = ((data & (utils.BIT7)) != 0); //2015-05: test by Teppples says that any value written here //will acknowledge a pending interrupt irqenabled = ((data & (utils.BIT0)) != 0); if (interrupted && cpu.interrupt > 0) { --cpu.interrupt; } interrupted = false; //System.err.println(cpu.interrupt); break; case 0xe: irqcounter &= 0xff00; irqcounter |= data; break; case 0xf: irqcounter &= 0xff; irqcounter |= (data << 8); break; } } else if (addr == 0xe000) { sndchip.write(soundCommand, data); } } @Override public void cpucycle(int cycles) { if (irqclock) { if (irqcounter == 0) { irqcounter = 0xffff; if (irqenabled && !interrupted) { interrupted = true; ++cpu.interrupt; //System.err.println("FME7 Interrupt"); } } else { --irqcounter; } } } private void setbanks() { for (int i = 0; i < 8; ++i) { for (int j = 0; j < 4; ++j) { prg_map[i + 8 * j] = (1024 * (i + (prgbanks[j] * 8))) % prgsize; } } for (int i = 0; i < 8; ++i) { chr_map[i] = (1024 * charbanks[i]) % chrsize; } } }