content: /*
   * HalfNES by Andrew Hoffman
   * Licensed under the GNU GPL Version 3. See LICENSE file
   */
  package com.grapeshot.halfnes.mappers;
  
  import com.grapeshot.halfnes.*;
  import com.grapeshot.halfnes.audio.*;
  
  public class VRC6Mapper extends Mapper {
  
      int[][] registerselectbits = {{0, 1}, {1, 0}};
      int[] registers;
      int prgbank0, prgbank1 = 0;
      int[] chrbank = {0, 0, 0, 0, 0, 0, 0, 0};
      boolean irqmode, irqenable, irqack, firedinterrupt = false;
      int irqreload, irqcounter = 22;
      VRC6SoundChip sndchip;
      boolean hasInitSound = false;
  
      public VRC6Mapper(int mappernum) {
          super();
          sndchip = new VRC6SoundChip();
  
          switch (mappernum) {
              //vrc6 has 2 different mapper numbers, for 2 different ways to assign the registers
              case 24:
                  registers = registerselectbits[0];
                  break;
              case 26:
              default:
                  registers = registerselectbits[1];
                  break;
  
          }
      }
  
      @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 final void cartWrite(final int addr, final int data) {
          if (addr < 0x8000 || addr > 0xffff) {
              super.cartWrite(addr, data);
              return;
          }
  
          final boolean bit0 = ((addr & (1 << registers[0])) != 0);
          final boolean bit1 = ((addr & (1 << registers[1])) != 0);
          switch (addr >> 12) {
              case 0x8:
                  //8000-8003: prg bank 0 select
                  prgbank0 = data;
                  setbanks();
                  break;
              case 0x9:
              case 0xa:
                  //sound registers here
                  sndchip.write((addr & 0xf000) + (bit1 ? 2 : 0) + (bit0 ? 1 : 0), data);
                  break;
              case 0xc:
                  //c000-c003: prg bank 1 select
                  prgbank1 = data;
                  setbanks();
                  break;
              case 0xb:
                  if (bit0 && bit1) {
                      //mirroring select
                      switch ((data >> 2) & 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;
                      }
                  } else {
                      //expansion sound register here as well
                      sndchip.write((addr & 0xf000) + (bit1 ? 2 : 0) + (bit0 ? 1 : 0), data);
                  }
                  break;
              case 0xd:
                  //character bank selects
                  chrbank[(bit1 ? 2 : 0) + (bit0 ? 1 : 0)] = data;
                  setbanks();
                  break;
              case 0xe:
                  chrbank[(bit1 ? 2 : 0) + (bit0 ? 1 : 0) + 4] = data;
                  setbanks();
                  break;
              case 0xf:
                  //irq control
                  if (!bit1) {
                      if (!bit0) {
                          irqreload = data;
                      } else {
                          irqack = ((data & (utils.BIT0)) != 0);
                          irqenable = ((data & (utils.BIT1)) != 0);
                          irqmode = ((data & (utils.BIT2)) != 0);
                          if (irqenable) {
                              irqcounter = irqreload;
                              prescaler = 341;
                          }
                          if (firedinterrupt) {
                              --cpu.interrupt;
                          }
                          firedinterrupt = false;
                      }
                  } else {
                      if (!bit0) {
                          irqenable = irqack;
                          if (firedinterrupt) {
                              --cpu.interrupt;
                          }
                          firedinterrupt = false;
                      }
                  }
  
          }
      }
  
      private void setbanks() {
          //map prg banks
          //last 8k fixed to end of rom
          for (int i = 1; i <= 8; ++i) {
              prg_map[32 - i] = prgsize - (1024 * i);
          }
          //first bank set to prg0 register
          for (int i = 0; i < 16; ++i) {
              prg_map[i] = (1024 * (i + 16 * prgbank0)) % prgsize;
          }
          //second bank set to prg1 register
          for (int i = 0; i < 8; ++i) {
              prg_map[i + 16] = (1024 * (i + 8 * prgbank1)) % prgsize;
          }
  
          //map chr banks
          for (int i = 0; i < 8; ++i) {
              setppubank(1, i, chrbank[i]);
          }
      }
  
      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;
          }
  //        utils.printarray(chr_map);
      }
  
      int prescaler = 341;
  
      @Override
      public void cpucycle(int cycles) {
          if (irqenable) {
              if (irqmode) {
                  scanlinecount();
                  //clock regardless of prescaler state
              } else {
                  prescaler -= 3;
                  if (prescaler <= 0) {
                      prescaler += 341;
                      scanlinecount();
                  }
              }
          }
      }
  
      public void scanlinecount() {
          if (!hasInitSound) {
              //tiny hack, because the APU is not initialized until AFTER this happens
              //TODO: this really should not need to be here.
              cpuram.apu.addExpnSound(sndchip);
              hasInitSound = true;
          }
          if (irqenable) {
              if (irqcounter == 255) {
                  irqcounter = irqreload;
                  //System.err.println("Interrupt @ Scanline " + scanline + " reload " + irqreload);
                  if (!firedinterrupt) {
                      ++cpu.interrupt;
                  }
                  firedinterrupt = true;
              } else {
                  ++irqcounter;
              }
          }
      }
  }
  