content: /*
   * HalfNES by Andrew Hoffman
   * Licensed under the GNU GPL Version 3. See LICENSE file
   */
  package com.grapeshot.halfnes;
  
  import com.grapeshot.halfnes.mappers.BadMapperException;
  import com.grapeshot.halfnes.mappers.Mapper;
  
  public class ROMLoader {
      //this is the oldest code in the project... I'm honestly ashamed
      //at how it's structured but for now it works.
      //TODO: fix this up
  
      //this SHOULD do just enough to figure out the file type
      //and the correct mapper for it, and no more.
      public String name;
      public int prgsize;
      public int chrsize;
      public Mapper.MirrorType scrolltype;
      public Mapper.TVType tvtype;
      public int mappertype;
      public int submapper;
      public int prgoff, chroff;
      public boolean savesram = false;
      public int[] header;
      private final int[] therom;
  
      public ROMLoader(String filename) {
          therom = FileUtils.readfromfile(filename);
          name = filename;
      }
  
      private void ReadHeader(int len) {
          // iNES header is 16 bytes, nsf header is 128,
          //other headers increasingly large
          header = new int[len];
          System.arraycopy(therom, 0, header, 0, len);
      }
  
      public void parseHeader() throws BadMapperException {
          ReadHeader(16);
          // decode iNES 1.0 headers
          // 1st 4 bytes : $4E $45 $53 $1A
          if (header[0] == 'N' && header[1] == 'E'
                  && header[2] == 'S' && header[3] == 0x1A) {
              //a valid iNES file, proceed
  
              scrolltype = ((header[6] & (utils.BIT3)) != 0)
                      ? Mapper.MirrorType.FOUR_SCREEN_MIRROR
                      : ((header[6] & (utils.BIT0)) != 0)
                              ? Mapper.MirrorType.V_MIRROR
                              : Mapper.MirrorType.H_MIRROR;
              savesram = ((header[6] & (utils.BIT1)) != 0);
              mappertype = (header[6] >> 4);
              //detect NES 2.0 format for the rest of the header
              if (((header[7] >> 2) & 3) == 2) {
                  System.err.println("NES 2 format");
                  //nes 2
                  //mapper buts 4-7 in byte 7
                  mappertype += ((header[7] >> 4) << 4);
                  //mapper bits 8-12 in byte 8
                  mappertype += ((header[8] & 15) << 8);
                  //submapper number is the high 4 bits of byte 8
                  submapper = (header[8] >> 4);
                  //extra prg and chr bits in byte 9
                  prgsize = Math.min(therom.length - 16,
                          16384 * (header[4] + ((header[9] & 15) << 8)));
                  if (prgsize == 0) {
                      throw new BadMapperException("No PRG ROM size in header");
                  }
                  chrsize = Math.min(therom.length - 16 - prgsize,
                          8192 * (header[5] + ((header[9] >> 4) << 8)));
                  //prg ram size in header byte 10
                  //chr ram size byte 11
                  //tv type is byte 12
                  if ((header[12] & 3) == 1) {
                      //pal mode only rom
                      tvtype = Mapper.TVType.PAL;
                      System.err.println("pal");
                  } else {
                      //if ntsc only or works on both we'll use ntsc
                      tvtype = Mapper.TVType.NTSC;
                  }
                  //byte 13 is Vs. System palettes that i don't deal with yet
                  //byte 14 and 15 must be zero
  
              } else {
                  //nes 1 format, with hacks
                  prgsize = Math.min(therom.length - 16, 16384 * header[4]);
                  if (prgsize == 0) {
                      throw new BadMapperException("No PRG ROM size in header");
                      //someone made this field zero on a 4mb multicart ROM
                      //and someone ELSE made this zero for an 8k PRG dump (no-intro)
                      //so if anyone gets this error make some heuristics to fix it.
                      //basically no multicarts > 2mb in iNES 1.0 format
                  }
                  chrsize = Math.min(therom.length - 16 - prgsize, 8192 * header[5]);
                  if (header[11] + header[12] + header[13] + header[14]
                          + header[15] == 0) {
                      //only consider upper bytes of mapper # if the end bytes are zero
                      mappertype += ((header[7] >> 4) << 4);
                      if (((header[9] & (utils.BIT0)) != 0)) {
                          //detect tv type though it's not really used
                          tvtype = Mapper.TVType.PAL;
                          System.err.println("pal header type 1");
                      } else if ((header[10] & 3) == 2) {
                          tvtype = Mapper.TVType.PAL;
                          System.err.println("pal header type 2");
                      } else {
                          tvtype = Mapper.TVType.NTSC;
                      }
                  } else {
                      System.err.println("diskdude (please clean your roms)");
                      tvtype = Mapper.TVType.NTSC;
                  }
              }
              // calc offsets; header not incl. here
              prgoff = 0;
              chroff = 0 + prgsize;
          } else if (('N' == header[0]) && ('E' == header[1])
                  && ('S' == header[2]) && ('M' == header[3])
                  && (0x1a == header[4])) {
              //nsf file
              mappertype = -1;
              //reread header since it's 128 bytes
              ReadHeader(128);
              prgsize = therom.length - 128;
          } else if (header[0] == 'U') {
              throw new BadMapperException("This is a UNIF file with the wrong extension");
          } else {
              throw new BadMapperException("iNES Header Invalid");
          }
      }
  
      public int[] load(int size, int offset) {
          int[] bindata = new int[size];
          System.arraycopy(therom, offset + header.length, bindata, 0, size);
          return bindata;
      }
  
      public int romlen() {
          return therom.length - header.length;
      }
  }
  