Пример #1
0
 /**
  * Fetches 8x8 NES tile stored at the given offset. This is an artifact of the first renderer I
  * wrote which drew 8 scanlines at a time.
  *
  * @param offset
  * @return an 8x8 array with colors stored as RGB packed in int
  */
 private int[] debugGetTile(final int offset) {
   // read one whole tile from nametable and convert from bitplane to packed
   // only used for debugging
   int[] dat = new int[64];
   for (int i = 0; i < 8; ++i) {
     // per line of tile ( 1 byte)
     for (int j = 0; j < 8; ++j) {
       // per pixel(1 bit)
       dat[8 * i + j] =
           ((((mapper.ppuRead(i + offset) & (utils.BIT7 - j)) != 0)) ? 0x555555 : 0)
               + ((((mapper.ppuRead(i + offset + 8) & (utils.BIT7 - j)) != 0)) ? 0xaaaaaa : 0);
     }
   }
   return dat;
 }
Пример #2
0
 private void spriteFetch(
     final boolean spritesize, final int tilenum, int offset, final int oamextra) {
   int tilefetched;
   if (spritesize) {
     tilefetched = ((tilenum & 1) * 0x1000) + (tilenum & 0xfe) * 16;
   } else {
     tilefetched = tilenum * 16 + ((sprpattern) ? 0x1000 : 0);
   }
   tilefetched += offset;
   // now load up the shift registers for said sprite
   final boolean hflip = ((oamextra & (utils.BIT6)) != 0);
   if (!hflip) {
     spriteshiftregL[found] = reverseByte(mapper.ppuRead(tilefetched));
     spriteshiftregH[found] = reverseByte(mapper.ppuRead(tilefetched + 8));
   } else {
     spriteshiftregL[found] = mapper.ppuRead(tilefetched);
     spriteshiftregH[found] = mapper.ppuRead(tilefetched + 8);
   }
 }
Пример #3
0
 private void bgFetch() {
   // fetch tiles for background
   // on real PPU this logic is repurposed for sprite fetches as well
   // System.err.println(hex(loopyV));
   bgAttrShiftRegH |= ((nextattr >> 1) & 1);
   bgAttrShiftRegL |= (nextattr & 1);
   // background fetches
   switch ((cycles - 1) & 7) {
     case 1:
       fetchNTByte();
       break;
     case 3:
       // fetch attribute (FIX MATH)
       penultimateattr =
           getAttribute(((loopyV & 0xc00) + 0x23c0), (loopyV) & 0x1f, (((loopyV) & 0x3e0) >> 5));
       break;
     case 5:
       // fetch low bg byte
       linelowbits = mapper.ppuRead((tileAddr) + ((loopyV & 0x7000) >> 12));
       break;
     case 7:
       // fetch high bg byte
       linehighbits = mapper.ppuRead((tileAddr) + 8 + ((loopyV & 0x7000) >> 12));
       bgShiftRegL |= linelowbits;
       bgShiftRegH |= linehighbits;
       nextattr = penultimateattr;
       if (cycles != 256) {
         incLoopyVHoriz();
       } else {
         incLoopyVVert();
       }
       break;
     default:
       break;
   }
   if (cycles >= 321 && cycles <= 336) {
     bgShiftClock();
   }
 }
Пример #4
0
 /**
  * Read the appropriate color attribute byte for the current tile. this is fetched 2x as often as
  * it really needs to be, the MMC5 takes advantage of that for ExGrafix mode.
  *
  * @param ntstart //start of the current attribute table
  * @param tileX //x position of tile (0-31)
  * @param tileY //y position of tile (0-29)
  * @return attribute table value (0-3)
  */
 private int getAttribute(final int ntstart, final int tileX, final int tileY) {
   final int base = mapper.ppuRead(ntstart + (tileX >> 2) + 8 * (tileY >> 2));
   if (((tileY & (utils.BIT1)) != 0)) {
     if (((tileX & (utils.BIT1)) != 0)) {
       return (base >> 6) & 3;
     } else {
       return (base >> 4) & 3;
     }
   } else {
     if (((tileX & (utils.BIT1)) != 0)) {
       return (base >> 2) & 3;
     } else {
       return base & 3;
     }
   }
 }
Пример #5
0
 final void setParameters() {
   // set stuff to NTSC or PAL or Dendy values
   switch (mapper.getTVType()) {
     case NTSC:
     default:
       numscanlines = 262;
       vblankline = 241;
       cpudivider = new int[] {3, 3, 3, 3, 3};
       break;
     case PAL:
       numscanlines = 312;
       vblankline = 241;
       cpudivider = new int[] {4, 3, 3, 3, 3};
       break;
     case DENDY:
       numscanlines = 312;
       vblankline = 291;
       cpudivider = new int[] {3, 3, 3, 3, 3};
       break;
   }
 }
Пример #6
0
  /** draw all 4 nametables/tileset/pallette to debug window. (for the nametable viewer) */
  private void debugDraw() {
    for (int i = 0; i < 32; ++i) {
      for (int j = 0; j < 30; ++j) {
        nametableView.setRGB(
            i * 8,
            j * 8,
            8,
            8,
            debugGetTile(mapper.ppuRead(0x2000 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)),
            0,
            8);
      }
    }
    for (int i = 0; i < 32; ++i) {
      for (int j = 0; j < 30; ++j) {
        nametableView.setRGB(
            i * 8 + 255,
            j * 8,
            8,
            8,
            debugGetTile(mapper.ppuRead(0x2400 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)),
            0,
            8);
      }
    }
    for (int i = 0; i < 32; ++i) {
      for (int j = 0; j < 30; ++j) {
        nametableView.setRGB(
            i * 8,
            j * 8 + 239,
            8,
            8,
            debugGetTile(mapper.ppuRead(0x2800 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)),
            0,
            8);
      }
    }
    for (int i = 0; i < 32; ++i) {
      for (int j = 0; j < 30; ++j) {
        nametableView.setRGB(
            i * 8 + 255,
            j * 8 + 239,
            8,
            8,
            debugGetTile(mapper.ppuRead(0x2C00 + i + 32 * j) * 16 + (bgpattern ? 0x1000 : 0)),
            0,
            8);
      }
    }

    // draw the tileset
    //        for (int i = 0; i < 16; ++i) {
    //            for (int j = 0; j < 32; ++j) {
    //                nametableView.setRGB(i * 8, j * 8, 8, 8,
    //                        debugGetTile((i + 16 * j) * 16), 0, 8);
    //            }
    //        }
    // draw the palettes on the bottom.
    //        for (int i = 0; i < 32; ++i) {
    //            for (int j = 0; j < 16; ++j) {
    //                for (int k = 0; k < 16; ++k) {
    //                    nametableView.setRGB(j + i * 16, k + 256, nescolor[0][pal[i]]);
    //                }
    //            }
    //        }
    debuggui.setFrame(nametableView);
    // debugbuff.clear();
  }
Пример #7
0
 private void fetchNTByte() {
   // fetch nt byte
   tileAddr =
       mapper.ppuRead(((loopyV & 0xc00) | 0x2000) + (loopyV & 0x3ff)) * 16
           + (bgpattern ? 0x1000 : 0);
 }
Пример #8
0
  /** runs the emulation for one PPU clock cycle. */
  public final void clock() {

    // cycle based ppu stuff will go here
    if (cycles == 1) {
      if (scanline == 0) {
        dotcrawl = renderingOn();
      }
      if (scanline < 240) {
        bgcolors[scanline] = pal[0];
      }
    }
    if (scanline < 240 || scanline == (numscanlines - 1)) {
      // on all rendering lines
      if (renderingOn() && ((cycles >= 1 && cycles <= 256) || (cycles >= 321 && cycles <= 336))) {
        // fetch background tiles, load shift registers
        bgFetch();
      } else if (cycles == 257 && renderingOn()) {
        // x scroll reset
        // horizontal bits of loopyV = loopyT
        loopyV &= ~0x41f;
        loopyV |= loopyT & 0x41f;

      } else if (cycles > 257 && cycles <= 341) {
        // clear the oam address from pxls 257-341 continuously
        oamaddr = 0;
      }
      if ((cycles == 340) && renderingOn()) {
        // read the same nametable byte twice
        // this signals the MMC5 to increment the scanline counter
        fetchNTByte();
        fetchNTByte();
      }
      if (cycles == 65 && renderingOn()) {
        oamstart = oamaddr;
      }
      if (cycles == 260 && renderingOn()) {
        // evaluate sprites for NEXT scanline (as long as either background or sprites are enabled)
        // this does in fact happen on scanine 261 but it doesn't do anything useful
        // it's cycle 260 because that's when the first important sprite byte is read
        // actually sprite overflow should be set by sprite eval somewhat before
        // so this needs to be split into 2 parts, the eval and the data fetches
        evalSprites();
      }
      if (scanline == (numscanlines - 1)) {
        if (cycles == 0) { // turn off vblank, sprite 0, sprite overflow flags
          vblankflag = false;
          sprite0hit = false;
          spriteoverflow = false;
        } else if (cycles >= 280 && cycles <= 304 && renderingOn()) {
          // loopyV = (all of)loopyT for each of these cycles
          loopyV = loopyT;
        }
      }
    } else if (scanline == vblankline && cycles == 1) {
      // handle vblank on / off
      vblankflag = true;
    }
    if (!renderingOn() || (scanline > 240 && scanline < (numscanlines - 1))) {
      // HACK ALERT
      // handle the case of MMC3 mapper watching A12 toggle
      // even when read or write aren't asserted on the bus
      // needed to pass Blargg's mmc3 tests
      mapper.checkA12(loopyV & 0x3fff);
    }
    if (scanline < 240) {
      if (cycles >= 1 && cycles <= 256) {
        int bufferoffset = (scanline << 8) + (cycles - 1);
        // bg drawing
        if (bgOn) { // if background is on, draw a line of that
          final boolean isBG = drawBGPixel(bufferoffset);
          // sprite drawing
          drawSprites(scanline << 8, cycles - 1, isBG);

        } else if (spritesOn) {
          // just the sprites then
          int bgcolor = ((loopyV > 0x3f00 && loopyV < 0x3fff) ? mapper.ppuRead(loopyV) : pal[0]);
          bitmap[bufferoffset] = bgcolor;
          drawSprites(scanline << 8, cycles - 1, true);
        } else {
          // rendering is off, so draw either the background color OR
          // if the PPU address points to the palette, draw that color instead.
          int bgcolor = ((loopyV > 0x3f00 && loopyV < 0x3fff) ? mapper.ppuRead(loopyV) : pal[0]);
          bitmap[bufferoffset] = bgcolor;
        }
        // deal with the grayscale flag
        if (grayscale) {
          bitmap[bufferoffset] &= 0x30;
        }
        // handle color emphasis
        bitmap[bufferoffset] = (bitmap[bufferoffset] & 0x3f) | emph;
      }
    }
    // handle nmi
    if (vblankflag && nmicontrol) {
      // pull NMI line on when conditions are right
      mapper.cpu.setNMI(true);
    } else {
      mapper.cpu.setNMI(false);
    }

    // clock CPU, once every 3 ppu cycles
    div = (div + 1) % cpudivider[cpudividerctr];
    if (div == 0) {
      mapper.cpu.runcycle(scanline, cycles);
      mapper.cpucycle(1);
      cpudividerctr = (cpudividerctr + 1) % cpudivider.length;
    }
    if (cycles == 257) {
      mapper.notifyscanline(scanline);
    } else if (cycles == 340) {
      scanline = (scanline + 1) % numscanlines;
      if (scanline == 0) {
        ++framecount;
      }
    }
  }
Пример #9
0
  /**
   * Performs a write to a PPU register
   *
   * @param regnum register number from 0 to 7, memory addresses are decoded to these elsewhere
   * @param data the value to write to the register (0x00 to 0xff valid)
   */
  public final void write(final int regnum, final int data) {
    //        if (regnum != 4 /*&& regnum != 7*/) {
    //            System.err.println("PPU write - wrote " + utils.hex(data) + " to reg "
    //                    + utils.hex(regnum + 0x2000)
    //                    + " frame " + framecount + " scanline " + scanline);
    //        }
    // debugdraw();
    openbus = data;
    switch (regnum) {
      case 0: // PPUCONTROL (2000)
        // set 2 bits of vram address (nametable select)
        // bits 0 and 1 affect loopyT to change nametable start by 0x400
        loopyT &= ~0xc00;
        loopyT |= (data & 3) << 10;
        /*
        SMB1 writes here at the end of its main loop and if this write
        lands on one exact PPU clock, the address bits are set to 0.
        This only happens on one CPU/PPU alignment of real hardware
        though so it only shows up ~33% of the time.
        */
        vraminc = (((data & (utils.BIT2)) != 0) ? 32 : 1);
        sprpattern = ((data & (utils.BIT3)) != 0);
        bgpattern = ((data & (utils.BIT4)) != 0);
        spritesize = ((data & (utils.BIT5)) != 0);
        /*bit 6 is kind of a halt and catch fire situation since it outputs
        ppu color data on the EXT pins that are tied to ground if set
        and that'll make the PPU get very hot from sourcing the current.
        Only really useful for the NESRGB interposer board, kind of
        useless for emulators. I will ignore it.
        */
        nmicontrol = ((data & (utils.BIT7)) != 0);

        break;
      case 1: // PPUMASK (2001)
        grayscale = ((data & (utils.BIT0)) != 0);
        bgClip = !((data & (utils.BIT1)) != 0); // clip left 8 pixels when its on
        spriteClip = !((data & (utils.BIT2)) != 0);
        bgOn = ((data & (utils.BIT3)) != 0);
        spritesOn = ((data & (utils.BIT4)) != 0);
        emph = (data & 0xe0) << 1;
        if (numscanlines == 312) {
          // if PAL switch position of red and green emphasis bits (6 and 5)
          // red is bit 6 -> bit 7
          // green is bit 7 -> bit 6
          int red = (emph >> 6) & 1;
          int green = (emph >> 7) & 1;
          emph &= 0xf3f;
          emph |= (red << 7) | (green << 6);
        }
        break;
      case 3:
        // PPUOAMADDR (2003)
        // most games just write zero and use the dma
        oamaddr = data & 0xff;
        break;
      case 4:
        // PPUOAMDATA(2004)
        if ((oamaddr & 3) == 2) {
          OAM[oamaddr++] = (data & 0xE3);
        } else {
          OAM[oamaddr++] = data;
        }
        oamaddr &= 0xff;
        // games don't usually write this directly anyway, it's unreliable
        break;

        // PPUSCROLL(2005)
      case 5:
        if (even) {
          // update horizontal scroll
          loopyT &= ~0x1f;
          loopyX = data & 7;
          loopyT |= data >> 3;

          even = false;
        } else {
          // update vertical scroll
          loopyT &= ~0x7000;
          loopyT |= ((data & 7) << 12);
          loopyT &= ~0x3e0;
          loopyT |= (data & 0xf8) << 2;
          even = true;
        }
        break;

      case 6:
        // PPUADDR (2006)
        if (even) {
          // high byte
          loopyT &= 0xc0ff;
          loopyT |= ((data & 0x3f) << 8);
          loopyT &= 0x3fff;
          even = false;
        } else {
          loopyT &= 0xfff00;
          loopyT |= data;
          loopyV = loopyT;
          even = true;
        }
        break;
      case 7:
        // PPUDATA
        mapper.ppuWrite((loopyV & 0x3fff), data);
        if (!renderingOn() || (scanline > 240 && scanline < (numscanlines - 1))) {
          loopyV += vraminc;
        } else {
          // while rendering, it seems to drop by 1 scanline, regardless of increment mode
          if ((loopyV & 0x7000) == 0x7000) {
            int YScroll = loopyV & 0x3E0;
            loopyV &= 0xFFF;
            if (YScroll == 0x3A0) {
              loopyV ^= 0xBA0;
            } else if (YScroll == 0x3E0) {
              loopyV ^= 0x3E0;
            } else {
              loopyV += 0x20;
            }
          } else {
            loopyV += 0x1000;
          }
        }
        break;
      default:
        break;
    }
  }
Пример #10
0
  /**
   * Performs a read from a PPU register, as well as causes any side effects of reading that
   * specific register.
   *
   * @param regnum
   * @return the data in the PPU register, or open bus (the last value written to a PPU register) if
   *     the register is read only
   */
  public final int read(final int regnum) {
    switch (regnum) {
      case 2:
        even = true;
        if (scanline == 241) {
          if (cycles == 1) { // suppress NMI flag if it was just turned on this same cycle
            vblankflag = false;
          }
          // OK, uncommenting this makes blargg's NMI suppression test
          // work but breaks Antarctic Adventure.
          // I'm going to need a cycle accurate CPU to fix that...
          //                    if (cycles < 4) {
          //                        //show vblank flag but cancel pending NMI before the CPU
          //                        //can actually do anything with it
          //                        //TODO: use proper interface for this
          //                        mapper.cpu.nmiNext = false;
          //                    }
        }
        openbus =
            (vblankflag ? 0x80 : 0)
                | (sprite0hit ? 0x40 : 0)
                | (spriteoverflow ? 0x20 : 0)
                | (openbus & 0x1f);
        vblankflag = false;
        break;
      case 4:
        // reading this is NOT reliable but some games do it anyways
        openbus = OAM[oamaddr];
        // System.err.println("codemasters?");
        if (renderingOn() && (scanline <= 240)) {
          if (cycles < 64) {
            return 0xFF;
          } else if (cycles <= 256) {
            return 0x00;
          } // Micro Machines relies on this:
          else if (cycles < 320) {
            return 0xFF;
          } // and this:
          else {
            return secOAM[0]; // is this the right value @ the time?
          }
        }
        break;
      case 7:
        // PPUDATA
        // correct behavior. read is delayed by one
        // -unless- is a read from sprite pallettes
        final int temp;
        if ((loopyV & 0x3fff) < 0x3f00) {
          temp = readbuffer;
          readbuffer = mapper.ppuRead(loopyV & 0x3fff);
        } else {
          readbuffer = mapper.ppuRead((loopyV & 0x3fff) - 0x1000);
          temp = mapper.ppuRead(loopyV);
        }
        if (!renderingOn() || (scanline > 240 && scanline < (numscanlines - 1))) {
          loopyV += vraminc;
        } else {
          // if 2007 is read during rendering PPU increments both horiz
          // and vert counters erroneously.
          incLoopyVHoriz();
          incLoopyVVert();
        }
        openbus = temp;
        break;

        // and don't increment on read
      default:
        return openbus; // last value written to ppu
    }
    return openbus;
  }