/*
* HalfNES by Andrew Hoffman
* Licensed under the GNU GPL Version 3. See LICENSE file
*/
package com.grapeshot.halfnes.mappers;
import com.grapeshot.halfnes.audio.*;
import com.grapeshot.halfnes.utils;
import java.util.Arrays;
/**
*
* @author Andrew
*/
public class NamcoMapper extends Mapper {
private int soundAddr = 0;
private boolean autoincrement = false, irqenable = false,
interrupted = false, chrramenable0 = false, chrramenable1 = false;
Namco163SoundChip sound = new Namco163SoundChip();
private boolean hasInitSound = false;
private int irqcounter = 0x3fff;
private int[] chrbanks = new int[8], chr_ram = new int[16384];
@Override
public void loadrom() throws BadMapperException {
super.loadrom();
// needs to be in every mapper. Fill with initial cfg
for (int i = 1; i <= 32; ++i) {
//map last banks in to start off
prg_map[32 - i] = prgsize - (1024 * i);
}
for (int i = 0; i < 8; ++i) {
chr_map[i] = (1024 * i) & (chrsize - 1);
}
}
@Override
public int cartRead(final int addr) {
if (addr >= 0x4800 && addr < 0x5000) {
//read sound ram
if (!hasInitSound) {
cpuram.apu.addExpnSound((ExpansionSoundChip) sound);
hasInitSound = true;
}
int retval = sound.read(soundAddr);
if (autoincrement) {
soundAddr = ++soundAddr & 0x7f;
}
return retval;
} else if (addr < 0x5800) {
irqack();
return irqcounter & 0xff;
} else if (addr < 0x6000) {
//read high bits of irq ctr and enable bit and ack irqs
irqack();
return ((irqcounter >> 8) & 0x7f) | (irqenable ? 0x80 : 0);
} else if (addr >= 0x8000) {
return prg[prg_map[((addr & 0x7fff)) >> 10] + (addr & 1023)];
} else if (addr >= 0x6000 && hasprgram) {
return prgram[addr & 0x1fff];
}
return addr >> 8; //open bus
}
public final void cartWrite(final int addr, final int data) {
if (addr < 0x4800 || ((addr >= 0x6000) && (addr < 0x8000)) || addr > 0xffff) {
//need to add WRAM protection here
super.cartWrite(addr, data);
return;
} else if (addr <= 0x4fff) {
//write to sound chip
if (!hasInitSound) {
cpuram.apu.addExpnSound((ExpansionSoundChip) sound);
hasInitSound = true;
}
sound.write(soundAddr, data);
if (autoincrement) {
soundAddr = ++soundAddr & 0x7f;
}
} else if (addr <= 0x57ff) {
//irq counter low bits
irqcounter &= 0x7f00;
irqcounter |= data;
irqack();
} else if (addr <= 0x5fff) {
//irq counter high 7 bits
irqcounter &= 0xff;
irqcounter |= ((data & 0x7f) << 8);
irqenable = ((data & (utils.BIT7)) != 0);
irqack();
//and bit 7 is irq enable
} else if (addr <= 0xbfff) {
//select chr pages
int bank = (addr >> 11) & 7;
setppubank(1, bank, data);
chrbanks[bank] = data;
//note: pages E0-FF are chr ram if a bit is set
} else if (addr <= 0xc7ff) {
//nametable select (can map chr rom in: how?)
//on namco 175 this is PRG RAM enable instead?
if (data < 0xe0) {
//use chr rom as the nametable
//i hope it doesnt try to write while it's chr rom
nt0 = Arrays.copyOfRange(chr, (data * 1024), (data + 1) * 1024);
} else {
nt0 = (((data & (utils.BIT0)) != 0) ? pput1 : pput0);
}
} else if (addr <= 0xc8ff) {
//nametable select 2
if (data < 0xe0) {
nt1 = Arrays.copyOfRange(chr, (data * 1024), (data + 1) * 1024);
} else {
nt1 = (((data & (utils.BIT0)) != 0) ? pput1 : pput0);
}
} else if (addr <= 0xd7ff) {
//nametable select 3
if (data < 0xe0) {
nt2 = Arrays.copyOfRange(chr, (data * 1024), (data + 1) * 1024);
} else {
nt2 = (((data & (utils.BIT0)) != 0) ? pput1 : pput0);
}
} else if (addr <= 0xdfff) {
//nametable select 4
if (data < 0xe0) {
nt3 = Arrays.copyOfRange(chr, (data * 1024), (data + 1) * 1024);
} else {
nt3 = (((data & (utils.BIT0)) != 0) ? pput1 : pput0);
}
} else if (addr <= 0xe7ff) {
//prg select 1 (1st 6 bits) and mirroring
for (int i = 0; i < 8; ++i) {
prg_map[i] = (1024 * (i + 8 * (data & 63))) % prgsize;
}
} else if (addr <= 0xefff) {
//prg select 2 (1st 6 bits) and CHR RAM enable
for (int i = 0; i < 8; ++i) {
prg_map[i + 8] = (1024 * (i + 8 * (data & 63))) % prgsize;
}
chrramenable0 = !((data & (utils.BIT6)) != 0);
chrramenable1 = !((data & (utils.BIT7)) != 0);
} else if (addr <= 0xf7ff) {
for (int i = 0; i < 8; ++i) {
prg_map[i + 16] = (1024 * (i + 8 * (data & 63))) % prgsize;
}
//prg select 3 (1st 6 bits)
} else if (addr <= 0xffff) {
//write protect for prg ram on namco 163
//also sound address port (7 bits)
autoincrement = ((data & (utils.BIT7)) != 0);
soundAddr = data & 0x7f;
}
}
private void irqack() {
if (interrupted) {
--cpu.interrupt;
interrupted = false;
}
}
@Override
public void cpucycle(int cycles) {
irqcounter += cycles;
if (irqcounter > 0x7fff) {
irqcounter = 0x7fff;
}
if (irqcounter == 0x7fff && irqenable && !interrupted) {
++cpu.interrupt;
interrupted = true;
}
}
private void setppubank(final int banksize, final int bankpos, final int banknum) {
// System.err.println(banksize + ", " + bankpos + ", "+ banknum);
for (int i = 0; i < banksize; ++i) {
chr_map[i + bankpos] = (1024 * ((banknum) + i)) & (chrsize - 1);
}
// utils.printarray(chr_map);
}
@Override
public int ppuRead(int addr) {
//i can't find any games that use this additional chr ram
//so who knows if this works?
if (addr < 0x1000) {
if (chrramenable0 && chrbanks[addr >> 10] > 0xe0) {
return chr_ram[chr_map[addr >> 10] + (addr & 1023)];
} else {
return chr[chr_map[addr >> 10] + (addr & 1023)];
}
} else if (addr < 0x2000) {
if (chrramenable1 && chrbanks[addr >> 10] > 0xe0) {
return chr_ram[chr_map[addr >> 10] - (0xe0 << 10) + (addr & 1023)];
} else {
return chr[chr_map[addr >> 10] + (addr & 1023)];
}
} else {
return super.ppuRead(addr);
}
}
@Override
public void ppuWrite(int addr, final int data) {
addr &= 0x3fff;
if (addr < 0x1000) {
if (chrramenable0 && chrbanks[addr >> 10] > 0xe0) {
chr_ram[chr_map[addr >> 10] - (0xe0 << 10) + (addr & 1023)] = data;
} else {
chr[chr_map[addr >> 10] + (addr & 1023)] = data;
}
} else if (addr < 0x2000) {
if (chrramenable1 && chrbanks[addr >> 10] > 0xe0) {
chr_ram[chr_map[addr >> 10] - (0xe0 << 10) + (addr & 1023)] = data;
} else {
chr[chr_map[addr >> 10] + (addr & 1023)] = data;
}
} else {
super.ppuWrite(addr, data);
}
}
}