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.PPU;
  import java.util.Arrays;
  import java.util.prefs.Preferences;
  import java.util.zip.CRC32;
  
  public abstract class Mapper {
  
      protected ROMLoader loader;
      protected int mappertype, submapper, prgsize, prgoff, chroff, chrsize;
      public CPU cpu;
      public CPURAM cpuram;
      public PPU ppu;
      protected int[] prg, chr, chr_map, prg_map, prgram = new int[8192];
      protected MirrorType scrolltype;
      protected boolean haschrram = false, hasprgram = true, savesram = false;
      // PPU nametables
      protected final int[] pput0 = new int[0x400], pput1 = new int[0x400],
              pput2 = new int[0x400], pput3 = new int[0x400];
      //99% of games only use 2 of these, but we have to create 4 and use ptrs to them
      //for those with extra RAM for 4 screen mirror
      protected int[] nt0, nt1, nt2, nt3;
      //and these are pointers to the nametables, so  for singlescreen when we switch
      //and then switch back the data in the other singlescreen NT isn't gone.
      long crc;
      TVType region;
      Preferences prefs = PrefsSingleton.get();
  
      public boolean supportsSaves() {
          return savesram;
      }
  
      public void destroy() {
          cpu = null;
          cpuram = null;
          ppu = null;
      }
  
      public static enum MirrorType {
  
          H_MIRROR, V_MIRROR, SS_MIRROR0, SS_MIRROR1, FOUR_SCREEN_MIRROR
      };
  
      public static enum TVType {
  
          NTSC,
          PAL,
          DENDY;
      }
  
      public static long crc32(int[] array) {
          CRC32 c = new CRC32();
          for (int i : array) {
              c.update(i);
          }
          return c.getValue();
      }
  
      public void loadrom() throws BadMapperException {
          loader.parseHeader();
          prgsize = loader.prgsize;
          mappertype = loader.mappertype;
          prgoff = loader.prgoff;
          chroff = loader.chroff;
          chrsize = loader.chrsize;
          scrolltype = loader.scrolltype;
          savesram = loader.savesram;
          prg = loader.load(prgsize, prgoff);
          region = loader.tvtype;
          submapper = loader.submapper;
          crc = crc32(prg);
          //System.err.println(utils.hex(crc));
          //crc "database" for certain impossible-to-recognize games
          if ((crc == 0x41243492L) //low g man (u)
                  || (crc == 0x98CCD385L)//low g man (e)
                  ) {
              hasprgram = false;
          }
          chr = loader.load(chrsize, chroff);
  
          if (chrsize == 0) {//chr ram
              haschrram = true;
              chrsize = 8192;
              chr = new int[8192];
          }
          prg_map = new int[32];
          for (int i = 0; i < 32; ++i) {
              prg_map[i] = (1024 * i) & (prgsize - 1);
          }
          chr_map = new int[8];
          for (int i = 0; i < 8; ++i) {
              chr_map[i] = (1024 * i) & (chrsize - 1);
          }
          cpuram = new CPURAM(this);
          cpu = new CPU(cpuram);
          ppu = new PPU(this);
          Arrays.fill(pput0, 0xa0);
          Arrays.fill(pput1, 0xb0);
          Arrays.fill(pput2, 0xc0);
          Arrays.fill(pput3, 0xd0);
          setmirroring(scrolltype);
      }
  
      public void reset() {
          //this is empty so that mappers w/o some specific instructions
          //on soft reset need not implement this
      }
  
      //write into the cartridge's address space
      public void cartWrite(final int addr, final int data) {
          //default no-mapper operation just writes if in PRG RAM range
          if (addr >= 0x6000 && addr < 0x8000) {
              prgram[addr & 0x1fff] = data;
          }
      }
  
      public int cartRead(final int addr) {
          // by default has wram at 0x6000 and cartridge at 0x8000-0xfff
          // but some mappers have different so override for those
          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 int ppuRead(int addr) {
          if (addr < 0x2000) {
              return chr[chr_map[addr >> 10] + (addr & 1023)];
          } else {
              switch (addr & 0xc00) {
                  case 0:
                      return nt0[addr & 0x3ff];
                  case 0x400:
                      return nt1[addr & 0x3ff];
                  case 0x800:
                      return nt2[addr & 0x3ff];
                  case 0xc00:
                  default:
                      if (addr >= 0x3f00) {
                          addr &= 0x1f;
                          if (addr >= 0x10 && ((addr & 3) == 0)) {
                              addr -= 0x10;
                          }
                          return ppu.pal[addr];
                      } else {
                          return nt3[addr & 0x3ff];
                      }
              }
          }
      }
  
      public void ppuWrite(int addr, final int data) {
          addr &= 0x3fff;
          if (addr < 0x2000) {
              if (haschrram) {
                  // Shame on you, Milon's Secret Castle. What possible
                  // reason could you have to write to your own chr rom?
                  chr[chr_map[addr >> 10] + (addr & 1023)] = data;
                  // anyway, only allowing writes when there's actual ram here.
              }
          } else {
              switch (addr & 0xc00) {
                  case 0x0:
                      nt0[addr & 0x3ff] = data;
                      break;
                  case 0x400:
                      nt1[addr & 0x3ff] = data;
                      break;
                  case 0x800:
                      nt2[addr & 0x3ff] = data;
                      break;
                  case 0xc00:
                      if (addr >= 0x3f00 && addr <= 0x3fff) {
                          addr &= 0x1f;
                          //System.err.println("wrote "+utils.hex(data)+" to palette index " + utils.hex(addr));
                          if (addr >= 0x10 && ((addr & 3) == 0)) { //0x10,0x14,0x18 etc are mirrors of 0x0, 0x4,0x8 etc
                              addr -= 0x10;
                          }
                          ppu.pal[addr] = (data & 0x3f);
                      } else {
                          nt3[addr & 0x3ff] = data;
                      }
                      break;
                  default:
                      System.err.println("where?");
              }
          }
      }
  
      public void notifyscanline(final int scanline) {
          //this is empty so that mappers w/o a scanline counter need not implement
      }
  
      public void cpucycle(int cycles) {
          //do it right for once
      }
  
      public static Mapper getCorrectMapper(final ROMLoader l) throws BadMapperException {
          int type = l.mappertype;
          boolean haschr = (l.chrsize == 0);
          switch (type) {
              case -1:
                  return new NSFMapper();
              case 0:
                  return new NromMapper();
              case 1:
                  return new MMC1Mapper();
              case 2:
                  return new UnromMapper();
              case 3:
                  return new CnromMapper();
              case 4:
                  return new MMC3Mapper();
              case 5:
                  return new MMC5Mapper();
              case 7:
                  return new AnromMapper();
              case 9:
                  return new MMC2Mapper();
              case 10:
                  return new MMC4Mapper();
              case 11:
                  return new ColorDreamsMapper();
              case 15:
              case 169:
                  return new Mapper15();
              case 19:
                  return new NamcoMapper();
              case 21:
              case 23:
              case 25:
                  //VRC4 has three different mapper numbers for six different address line layouts
                  //some of which really should be VRC2
                  //but they're all handled in the same file
                  //there's a proposal for submapper #s in iNES 2.0
                  return new VRC4Mapper(type);
              case 22:
                  return new VRC2Mapper();
              case 24:
              case 26:
                  return new VRC6Mapper(type);
              case 31:
                  return new Mapper31();
              case 33:
                  return new Mapper33();
              case 34:
                  if (haschr) {
                      return new BnromMapper();
                  } else {
                      return new NINA_001_Mapper();
                  }
              case 36:
                  return new Mapper36();
              case 38:
                  return new CrimeBustersMapper();
              case 41:
                  return new CaltronMapper();
              case 47:
                  return new Mapper47();
              case 48:
                  return new Mapper48();
              case 58:
                  return new Mapper58();
              case 60:
                  return new Mapper60();
              case 61:
                  return new Mapper61();
              case 62:
                  return new Mapper62();
              case 64:
                  return new TengenRamboMapper();
              case 65:
                  return new IremH3001Mapper();
              case 66:
                  return new GnromMapper();
              case 67:
                  return new Sunsoft03Mapper();
              case 68:
                  return new AfterburnerMapper();
              case 69:
                  return new FME7Mapper();
              case 70:
                  return new Mapper70();
              case 71:
                  return new CodemastersMapper();
              case 72:
                  return new Mapper72();
              case 73:
                  return new VRC3Mapper();
              case 75:
                  return new VRC1Mapper();
              case 76:
                  return new Mapper76();
              case 78:
                  return new Mapper78();
              case 79:
              case 113:
                  return new NINA_003_006_Mapper(type);
              case 85:
                  return new VRC7Mapper();
              case 86:
                  return new Mapper86();
              case 87:
                  return new Mapper87();
              case 88:
              case 154:
                  return new Namcot34x3Mapper(type);
              case 89:
              case 93:
                  return new Sunsoft02Mapper(type);
              case 92:
                  return new Mapper92();
              case 94:
                  return new Mapper94();
              case 97:
                  return new Mapper97();
              case 107:
                  return new Mapper107();
              case 112:
                  return new Mapper112();
              case 119:
                  return new Mapper119();
              case 140:
                  return new Mapper140();
              case 152:
                  return new Mapper152();
              case 180:
                  return new CrazyClimberMapper();
              case 182:
                  return new Mapper182();
              case 184:
                  return new Sunsoft01Mapper();
              case 185:
                  return new Mapper185();
              case 200:
                  return new Mapper200();
              case 201:
                  return new Mapper201();
              case 203:
                  return new Mapper203();
              case 206:
                  return new MIMICMapper();
              case 212:
                  return new Mapper212();
              case 213:
                  return new Mapper213();
              case 214:
                  return new Mapper214();
              case 225:
                  return new Mapper225();
              case 226:
                  return new Mapper226();
              case 228:
                  return new Action52Mapper();
              case 229:
                  return new Mapper229();
              case 231:
                  return new Mapper231();
              case 240:
                  return new Mapper240();
              case 241:
                  return new Mapper241();
              case 242:
                  return new Mapper242();
              case 244:
                  return new Mapper244();
              case 246:
                  return new Mapper246();
              case 255:
                  return new Mapper255();
              default:
                  System.err.println("unsupported mapper # " + type);
                  throw new BadMapperException("Unsupported mapper: " + type);
          }
      }
  
      public String getrominfo() {
          return ("ROM INFO: \n"
                  + "Filename:     " + loader.name + "\n"
                  + "Mapper:       " + mappertype + "\n"
                  + "PRG Size:     " + prgsize / 1024 + " K\n"
                  + "CHR Size:     " + (haschrram ? 0 : chrsize / 1024) + " K\n"
                  + "Mirroring:    " + scrolltype.toString() + "\n"
                  + "Battery Save: " + ((savesram) ? "Yes" : "No")) + "\n"
                  + "CRC:          " + utils.hex(this.crc);
      }
  
      public boolean hasSRAM() {
          return savesram;
      }
  
      public void setLoader(final ROMLoader l) {
          loader = l;
      }
  
      public CPURAM getCPURAM() {
          return cpuram;
      }
  
      public void checkA12(int addr) {
          //needed for mmc3 irq counter
      }
  
      public void setPRGRAM(final int[] newprgram) {
          prgram = newprgram.clone();
  
      }
  
      public int[] getPRGRam() {
          return prgram.clone();
      }
  
      public final void setmirroring(final Mapper.MirrorType type) {
          switch (type) {
              case H_MIRROR:
                  nt0 = pput0;
                  nt1 = pput0;
                  nt2 = pput1;
                  nt3 = pput1;
                  break;
              case V_MIRROR:
                  nt0 = pput0;
                  nt1 = pput1;
                  nt2 = pput0;
                  nt3 = pput1;
  
                  break;
              case SS_MIRROR0:
                  nt0 = pput0;
                  nt1 = pput0;
                  nt2 = pput0;
                  nt3 = pput0;
                  break;
              case SS_MIRROR1:
                  nt0 = pput1;
                  nt1 = pput1;
                  nt2 = pput1;
                  nt3 = pput1;
                  break;
              case FOUR_SCREEN_MIRROR:
              default:
                  nt0 = pput0;
                  nt1 = pput1;
                  nt2 = pput2;
                  nt3 = pput3;
                  break;
          }
      }
  
      public TVType getTVType() {
          int prefsregion = prefs.getInt("region", 0);
          switch (prefsregion) {
              case 0:
              default://auto detect
                  return region;
              case 1: //ntsc
                  return TVType.NTSC;
              case 2: //pal
                  return TVType.PAL;
              case 3: //dendy
                  return TVType.DENDY;
          }
      }
  
      public void init() {
      } //needed for NSF initialization
  }
  