/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.mappers;
import com.grapeshot.halfnes.*;
public class MMC1Mapper extends Mapper {
private int mmc1shift = 0;
private int mmc1latch = 0;
private int mmc1ctrl = 0xc;
private int mmc1chr0 = 0;
private int mmc1chr1 = 0;
private int mmc1prg = 0;
private boolean soromlatch = false;
private double cpucycleprev = 0; // for Bill and Ted fix
private long framecountprev = 0;
@Override
public void loadrom() throws BadMapperException {
// needs to be in every mapper. Fill with initial cfg
super.loadrom();
for (int i = 0; i < 32; ++i) {
prg_map[i] = (1024 * i) & (prgsize - 1);
}
for (int i = 0; i < 8; ++i) {
chr_map[i] = (1024 * i) & (chrsize - 1);
}
setbanks();
}
@Override
public final void cartWrite(final int addr, final int data) {
if (addr < 0x8000 || addr > 0xffff) {
super.cartWrite(addr, data);
return;
}
if (cpu.clocks == cpucycleprev && cpuram.apu.nes.framecount == framecountprev) {
return; //bill and ted fix - prevents 2 writes too close together
//from being acknowledged
//if I ever go to a cycle based core instead of opcode based this needs to change.
}
framecountprev = cpuram.apu.nes.framecount;
//and this is extremely ugly/likely to break
//but is needed to prevent the Bill+Ted fix from breaking Dr Mario intro.
cpucycleprev = cpu.clocks;
if (((data & (utils.BIT7)) != 0)) {
// reset shift register
mmc1shift = 0;
mmc1latch = 0;
mmc1ctrl |= 0xc;
setbanks();
return;
}
mmc1shift = (mmc1shift >> 1) + (data & 1) * 16;
++mmc1latch;
// mmc1shift &= 0x1f;
if (mmc1latch < 5) {
return; // no need to do anything
} else {
if (addr >= 0x8000 && addr <= 0x9fff) {
// mmc1control
mmc1ctrl = mmc1shift & 0x1f;
MirrorType mirtype;
switch (mmc1ctrl & 3) {
case 0:
mirtype = MirrorType.SS_MIRROR0;
break;
case 1:
mirtype = MirrorType.SS_MIRROR1;
break;
case 2:
mirtype = MirrorType.V_MIRROR;
break;
default:
mirtype = MirrorType.H_MIRROR;
break;
}
setmirroring(mirtype);
} else if (addr >= 0xa000 && addr <= 0xbfff) {
// mmc1chr0
mmc1chr0 = mmc1shift & 0x1f;
if (prgsize > 262144) {
//SOROM boards use the high bit of CHR to switch between 1st and last
//256k of the PRG ROM
mmc1chr0 &= 0xf;
soromlatch = ((mmc1shift & (utils.BIT4)) != 0);
}
} else if (addr >= 0xc000 && addr <= 0xdfff) {
// mmc1chr1
mmc1chr1 = mmc1shift & 0x1f;
if (prgsize > 262144) {
mmc1chr1 &= 0xf;
}
} else if (addr >= 0xe000 && addr <= 0xffff) {
// mmc1prg
mmc1prg = mmc1shift & 0xf;
}
// System.err.println("Mapper Bankswitch: Write " + utils.hex(mmc1shift) + " @ " + utils.hex(addr));
setbanks();
mmc1latch = 0;
mmc1shift = 0;
}
}
private void setbanks() {
// chr bank 0
if (((mmc1ctrl & (utils.BIT4)) != 0)) {
// 4k bank mode
for (int i = 0; i < 4; ++i) {
chr_map[i] = (1024 * (i + 4 * mmc1chr0)) % chrsize;
}
for (int i = 0; i < 4; ++i) {
chr_map[i + 4] = (1024 * (i + 4 * mmc1chr1)) % chrsize;
}
} else {
// 8k bank mode
for (int i = 0; i < 8; ++i) {
chr_map[i] = (1024 * (i + 8 * (mmc1chr0 >> 1))) % chrsize;
}
}
// prg bank
if (!((mmc1ctrl & (utils.BIT3)) != 0)) {
// 32k switch
// ignore low bank bit
for (int i = 0; i < 32; ++i) {
prg_map[i] = (1024 * i + 32768 * (mmc1prg >> 1)) % prgsize;
}
} else if (!((mmc1ctrl & (utils.BIT2)) != 0)) {
// fix 1st bank, 16k switch 2nd bank
for (int i = 0; i < 16; ++i) {
prg_map[i] = (1024 * i);
}
for (int i = 0; i < 16; ++i) {
prg_map[i + 16] = (1024 * i + 16384 * mmc1prg) % prgsize;
}
} else {
// fix last bank, switch 1st bank
for (int i = 0; i < 16; ++i) {
prg_map[i] = (1024 * i + 16384 * mmc1prg) % prgsize;
}
for (int i = 1; i <= 16; ++i) {
prg_map[32 - i] = (prgsize - (1024 * i));
if ((prg_map[32 - i]) > 262144) {
prg_map[32 - i] -= 262144;
}
}
}
//if more thn 256k ROM AND SOROM latch is on
if (soromlatch && (prgsize > 262144)) {
//add 256k to all of the prg bank #s
for (int i = 0; i < prg_map.length; ++i) {
prg_map[i] += 262144;
}
}
//utils.printarray(prg_map);
}
}