content: /*
   * 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 VRC4Mapper extends Mapper {
  
      int[][][] registerselectbits = {{{1, 2}, {6, 7}},
      {{2, 3}, {0, 1}},
      {{3, 2}, {1, 0}}};
      int[][] registers;
      int prgbank0, prgbank1 = 0;
      int[] chrbank = {0, 0, 0, 0, 0, 0, 0, 0};
      boolean prgmode, irqmode, irqenable, irqack, firedinterrupt = false;
      int irqreload, irqcounter = 22;
      boolean vrc2mirror;
  
      public VRC4Mapper(int mappernum) {
          super();
          switch (mappernum) {
              //vrc4 has 3 different mapper numbers, for 3 different ways to assign the registers
              case 21:
                  registers = registerselectbits[0];
                  break;
              case 23:
                  registers = registerselectbits[1];
                  break;
              case 25:
                  registers = registerselectbits[2];
                  break;
              default:
                  registers = registerselectbits[0];
                  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);
          }
          //detect Konami Wai Wai World on VRC2
          System.err.println(utils.hex(crc));
          vrc2mirror = 
                    ((crc == 0xB790FF4CL) //ZnO trans (remember why it's a long...)
                  || (crc == 0x2D953C3DL) //demiforce 3
                  || (crc == 0x64818FC5L) //(J)
                  || (crc == 0x1E12AF8AL) //french trans
                  || (crc == 0x3480F7DBL)); //demiforce 1?
          System.err.println(vrc2mirror);
      }
  
      @Override
      public final void cartWrite(int addr, int data) {
          if (addr < 0x8000 || addr > 0xffff) {
              super.cartWrite(addr, data);
              return;
          }
  
          boolean bit0 = ((addr & (1 << registers[0][0])) != 0) || ((addr & (1 << registers[1][0])) != 0);
          boolean bit1 = ((addr & (1 << registers[0][1])) != 0) || ((addr & (1 << registers[1][1])) != 0);
          switch (addr >> 12) {
              case 0x8:
                  prgbank0 = data & 0x1f;
                  break;
              case 0x9:
                  if (!bit1) {
                      //mirroring select
                      if (vrc2mirror) {
                          //vrc2 doesn't have single screen mirroring option
                          switch (data & 1) {
                              case 0:
                                  setmirroring(Mapper.MirrorType.V_MIRROR);
                                  break;
                              case 1:
                                  setmirroring(Mapper.MirrorType.H_MIRROR);
                                  break;
                          }
                      } else {
                          switch (data & 3) {
                              case 0:
                                  setmirroring(MirrorType.V_MIRROR);
                                  break;
                              case 1:
                                  setmirroring(MirrorType.H_MIRROR);
                                  break;
                              case 2:
                                  setmirroring(MirrorType.SS_MIRROR0);
                                  break;
                              case 3:
                                  setmirroring(MirrorType.SS_MIRROR1);
                                  break;
                          }
                      }
                  } else {
                      prgmode = ((data & (utils.BIT1)) != 0);
                  }
                  break;
              case 0xa:
                  prgbank1 = data & 0x1f;
                  break;
              case 0xb:
              case 0xc:
              case 0xd:
              case 0xe:
                  //chr bank select. black magic
                  data &= 0xf;
                  int whichreg = ((addr - 0xb000) >> 11) + ((bit1) ? 1 : 0);
                  int oldval = chrbank[whichreg];
                  if (!bit0) {
                      oldval &= 0xf0;
                      oldval |= data;
                  } else {
                      oldval &= 0xf;
                      oldval |= (data << 4);
                  }
                  chrbank[whichreg] = oldval;
                  break;
              case 0xf:
                  //irq registers.
                  if (!bit1) {
                      if (!bit0) {
                          irqreload &= 0xf0;
                          irqreload |= data & 0xf;
                      } else {
                          irqreload &= 0xf;
                          irqreload |= (data & 0xf) << 4;
                      }
                      // System.err.println("reload set to " + irqreload);
                  } else if (!bit0) {
                      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 {
                      irqenable = irqack;
                      if (firedinterrupt) {
                          --cpu.interrupt;
                      }
                      firedinterrupt = false;
                  }
          }
          if (addr < 0xf000) {
              setbanks();
          }
      }
  
      private void setbanks() {
          //map prg banks
          if (!prgmode) {
              //last 2 banks fixed to last two in rom
              for (int i = 1; i <= 16; ++i) {
                  prg_map[32 - i] = prgsize - (1024 * i);
              }
              //first bank set to prg0 register
              for (int i = 0; i < 8; ++i) {
                  prg_map[i] = (1024 * (i + 8 * prgbank0)) % prgsize;
              }
              //second bank set to prg1 register
              for (int i = 0; i < 8; ++i) {
                  prg_map[i + 8] = (1024 * (i + 8 * prgbank1)) % prgsize;
              }
          } else {
              //fixed banks 1 and 4
              for (int i = 1; i <= 8; ++i) {
                  prg_map[8 - i] = prgsize - (1024 * i);
              }
              for (int i = 1; i <= 8; ++i) {
                  prg_map[32 - i] = prgsize - (1024 * i);
              }
              //second bank set to prg0 register
              for (int i = 0; i < 8; ++i) {
                  prg_map[i + 8] = (1024 * (i + 8 * prgbank0)) % prgsize;
              }
              //third 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(int banksize, int bankpos, 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 (irqenable) {
              if (irqcounter == 255) {
                  irqcounter = irqreload;
                  //System.err.println("Interrupt @ Scanline " + scanline + " reload " + irqreload);
                  if (!firedinterrupt) {
                      ++cpu.interrupt;
                  }
                  firedinterrupt = true;
              } else {
                  ++irqcounter;
              }
          }
      }
  }
  