content: /*
   * 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);
          }
      }
  }