nesdev.com
http://forums.nesdev.com/

Problems with RBI Baseball and SMB3... MMC3 problem?
http://forums.nesdev.com/viewtopic.php?f=3&t=152
Page 1 of 2

Author:  James [ Sat Jan 22, 2005 9:16 am ]
Post subject:  Problems with RBI Baseball and SMB3... MMC3 problem?

Hello -- I'm having some problems with RBI Baseball and SMB3 in my emulator. The title screen of RBI Baseball is ok, but as soon as I go to the team selection, everything is messed up:

Image

Super Mario Bros. 3 also has some problems. There's some glitching at the title screen, and the status bar scrolls along with playfield vertical scrolling:

Image

I suspect my MMC3 implementation might be a bit messed up, since these are the only games I'm currently having problems with. Here is my MMC3 code -- any glaring errors?

Code:
#include "StdAfx.h"
#include ".\mapper4.h"

CMapper4::CMapper4(void)
{
   mapperName = "MMC3";
}

CMapper4::~CMapper4(void)
{
}

void CMapper4::WriteByte(WORD Address, BYTE Value)
{
   unsigned char *c, *p;
   switch (Address)
   {
   case 0x8000:
      commandNumber = Value & 0x07;
      prgSelect = Value & 0x40 ? true : false;
      chrXor = Value & 0x80 ? true : false;
      if (prgSelect)
         prgBank[PRG_8000] = pPrgRom + (Header->PrgRomPageCount - 1) * 0x4000;
      else
         prgBank[PRG_C000] = pPrgRom + (Header->PrgRomPageCount - 1) * 0x4000;
      break;
   case 0x8001:
      c = pChrRom + 0x400 * Value;
      p = pPrgRom + 0x2000 * Value;
      switch (commandNumber)
      {
      case 0:
         if (chrXor)
         {
            chrBank[CHR_1000] = c;
            chrBank[CHR_1400] = c + 0x400;
         }
         else
         {
            chrBank[CHR_0000] = c;
            chrBank[CHR_0400] = c + 0x400;
         }
         break;
      case 1:
         if (chrXor)
         {
            chrBank[CHR_1800] = c;
            chrBank[CHR_1C00] = c + 0x400;
         }
         else
         {
            chrBank[CHR_0800] = c;
            chrBank[CHR_0C00] = c + 0x400;
         }
         break;
      case 2:
         if (chrXor)
            chrBank[CHR_0000] = c;
         else
            chrBank[CHR_1000] = c;
         break;
      case 3:
         if (chrXor)
            chrBank[CHR_0400] = c;
         else
            chrBank[CHR_1400] = c;
         break;
      case 4:
         if (chrXor)
            chrBank[CHR_0800] = c;
         else
            chrBank[CHR_1800] = c;
         break;
      case 5:
         if (chrXor)
            chrBank[CHR_0C00] = c;
         else
            chrBank[CHR_1C00] = c;
         break;
      case 6:
         if (prgSelect)
            prgBank[PRG_C000] = p;
         else
            prgBank[PRG_8000] = p;
         break;
      case 7:
         prgBank[PRG_A000] = p;
         break;
      }
      break;
   case 0xa000:
      Mirroring = Value & 0x01 ? MIRRORING_HORIZONTAL : MIRRORING_VERTICAL;
      break;
   case 0xa001:
      writeProtectSram = Value & 0x40 ? true : false;
      break;
   case 0xc000:
      irqReload = Value;
      break;
   case 0xc001:
      irqCounter = 0;
      reloadIrq = true;
      break;
   case 0xe000:
      irqEnabled = false;
      break;
   case 0xe001:
      irqEnabled = true;
      break;
   default:
      break;
   }
}

bool CMapper4::HBlank(void)
{
   if (reloadIrq)
   {
      reloadIrq = false;
      irqCounter = irqReload;
   }
   else if (--irqCounter == 0)
   {
      irqCounter = irqReload;
      if (irqEnabled)
      {
         return true;
      }
   }
   return false;
}

void CMapper4::Initialize(void)
{
   unsigned char *x = pPrgRom + (Header->PrgRomPageCount - 1) * 0x4000;

   prgBank[PRG_C000] = x;
   prgBank[PRG_E000] = x + 0x2000;

   irq = false;
   irqCounter = 0;
   irqReload = 0;
   irqEnabled = false;
   reloadIrq = false;
}


Thanks for any help/suggestions!
James

Author:  Quietust [ Sat Jan 22, 2005 10:00 am ]
Post subject: 

Your 'hblank' function must ONLY be called while the PPU is rendering, not in VBLANK.
Also, your PRG and CHR banking is bad. The CHRxor and PRGselect are instantly applied to ALL banks when you write to $8000 (where one write can instantly swap PPU $0000-$0FFF/$1000-$1FFF and/or $8000-$9FFF/$C000-$DFFF), NOT individual banks on writes to $8001.
A much easier way to handle this would be to simply keep track of $8000 and the eight registers in $8001 internally, and simply "synch banks" upon writing to any of them.

Author:  James [ Sat Jan 22, 2005 10:11 am ]
Post subject: 

HBlank is only called during rendering -- the logic is handled in my main loop (I know I'm not checking if background/sprite rendering is enabled, though, which may be my SMB3 problem... but when I step through in my debugger, it looks like drawing is enabled whenever the MMC3 IRQ counter is decremented anyway).

So when a $8000 write occurs, are the banks actually swapped? That is, does the $000 chr bank swap with $1000, or does $000 simply point to the bank that was last written to $8001 (in effect, causing both $000 and $1000 to point to the same bank)?

Thanks,
James

Author:  Disch [ Sat Jan 22, 2005 10:32 am ]
Post subject: 

The CHR XOR deal basically flip-flops both pattern tables by XORing the desired address of fetched data.

So basically when the PPU wants to draw the image at ppu $0000, it requests $0000 from the cart... but will actually get either $0000 or $1000 based on the chr xor bit.

So writes to $8000 don't actually swap anything... but if you change the high bit (the chr xor bit), it will appear that the left and right pattern tables have been switched. You can emulate this easily with the setup you currently have, by switching around the first 4k CHR pages with the last 4k CHR pages when the high bit of $8000 is changed.


The PRG select thing presumably works the same way. When $40 of $8000 is switched... the 8k at $8000 and $C000 appear as though they're swapping places with each other.

Author:  Quietust [ Sat Jan 22, 2005 10:33 am ]
Post subject: 

It is exactly as I explained - the upper bits of $8000 affect ALL BANKS simultaneously.

Something like this:

Code:
unsigned char Cmd, CHR[6], PRG[2];

void Write8000 (unsigned char Val)
{
   Cmd = Val;
   Sync();
}
void Write8001 (unsigned char Val)
{
   switch (Cmd & 7) {
   case 0: CHR[0] = Val; break;
   case 1: CHR[1] = Val; break;
   case 2: CHR[2] = Val; break;
   case 3: CHR[3] = Val; break;
   case 4: CHR[4] = Val; break;
   case 5: CHR[5] = Val; break;
   case 6: PRG[0] = Val; break;
   case 7: PRG[1] = Val; break;
   }
   Sync();
}
void Sync (void)
{
   SetPRG_ROM8(0xA,PRG[1]);
   if (Cmd & 0x40)
   {
      SetPRG_ROM8(0x8,NUM8KBANKS-2);
      SetPRG_ROM8(0xC,PRG[0]);
   }
   else
   {
      SetPRG_ROM8(0x8,PRG[0]);
      SetPRG_ROM8(0xC,NUM8KBANKS-2);
   }
   if (Cmd & 0x80)
   {
      SetCHR_ROM1(0,CHR[2]);
      SetCHR_ROM1(1,CHR[3]);
      SetCHR_ROM1(2,CHR[4]);
      SetCHR_ROM1(3,CHR[5]);
      SetCHR_ROM2(4,CHR[0]);
      SetCHR_ROM2(6,CHR[1]);
   }
   else
   {
      SetCHR_ROM2(0,CHR[0]);
      SetCHR_ROM2(2,CHR[1]);
      SetCHR_ROM1(4,CHR[2]);
      SetCHR_ROM1(5,CHR[3]);
      SetCHR_ROM1(6,CHR[4]);
      SetCHR_ROM1(7,CHR[5]);
   }
}

Author:  Nessie [ Sat Jan 22, 2005 2:34 pm ]
Post subject: 

Q's sync() way of implementing most mappers is probably best. Sometimes I wish I had done it like that. My mapper code is more like Nestopia's, which is pretty ok too.

Author:  James [ Sat Jan 22, 2005 4:16 pm ]
Post subject: 

Well, I implemented the sync method (thanks for the example)... unfortunately, I'm still having the same problem, so it must be somewhere else in my code...

Thanks for the help,
James

Author:  Nessie [ Sat Jan 22, 2005 4:41 pm ]
Post subject: 

Judging from your pics, it still seems you're calling that hblank function during vblank. Otherwise, perhaps it's a PAL/NTSC thing?

Author:  Zepper [ Tue Jan 25, 2005 6:52 pm ]
Post subject: 

"one thing... I don't know why..." ;)

:arrow: The value written to 8001h:0 or 1 should be:

Code:
ppubank[0] = value AND FEh * 400h
ppubank[1] = value OR 01h * 400h


Is this correct... or should it be:

Code:
ppubank[0] = value << 1 * 400h
ppubank[1] = (value << 1) OR 01h * 400h


?

Author:  Quietust [ Tue Jan 25, 2005 6:53 pm ]
Post subject: 

The former - the values are 1KB bank numbers, but the bottom bit is always ignored.

Author:  James [ Tue Jan 25, 2005 8:27 pm ]
Post subject: 

Woo-hoo! Well, after a couple of hours of debugging, I finally fixed RBI Baseball! Here's what I found: At some point during start up, the game writes 0x1C to 0x8001 for command number 6. This is telling the mapper to select 8k prg rom page #28?!? Considering there are only 16 8k prg rom pages, this didn't make any sense. So what I did was wrap the value by modifying my code as follows:

Code:
case 0x8001:
      switch (commandNumber)
      {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
         chr[commandNumber] = Value;
         break;
      case 6:
      case 7:
         while (Value > (PrgRomPageCount8k))
            Value -= (PrgRomPageCount8k);
         prg[commandNumber - 6] = Value;
         break;
      }
      Sync();
      break;


I don't recall reading anywhere that the mapper worked like this. Am I doing this correctly... and if so, are all mappers like this (i.e. should I implement this logic elsewhere)? Is this what Fx3 is referring to in the previous post (...I can't see that it does, but I've been staring at my debuger for a while now...)?

...or am I just missing something completely obvious? :)

James

Author:  Zepper [ Tue Jan 25, 2005 9:58 pm ]
Post subject: 

PRG masking is a 'must-have' :) and there's an easier method... err... I know 2 of them. You do 'value MOD prg_banks' (MOD = %).

About my previous post, I was asking about the lower bit of 8001:0 or 1 (value) for 2 x 400h CHR page swapping.

Author:  James [ Tue Jan 25, 2005 10:31 pm ]
Post subject: 

>>'value MOD prg_banks'

:oops: ugh... spend too long looking at the tiny details and you miss the obvious, huh?

So should PRG masking be implemented for every mapper, or is this a MMC3 specific issue?

Thanks a lot!
James

Author:  Quietust [ Tue Jan 25, 2005 10:32 pm ]
Post subject: 

Masking needs to be done on PRG *and* CHR banks for EVERY mapper. To avoid having to explicitly mask the bank numbers on every single bank select command in your emulator, you can just make SetPRG/SetCHR functions that do the masking internally. If you're using C++ (and you are), you can even make them inline to improve performance.

Author:  Guest [ Wed Jan 26, 2005 11:40 am ]
Post subject: 

I added some SetPRG and SetCHR functions based on bank size. Cleaned things up nicely. I believe my SMB3 problem is in my PPU. I don't think I'm handling 2005/2006 writes correctly (Zelda 2 title screen also has problems when scrolling). When I wrote my PPU code, I did so based off of Brad Taylor's document. It's a little messy (a lot of variables and a lot of bitwise operations during rendering). I think I will try to rewrite my PPU using Loopy's docs, now that I actually understand them.

As a side note, all of my mappers are classes derived from the base cartridge class (i.e. Mapper 0). I can not inline any of the SetPRG/SetCHR functions defined in the base class even though they are not defined as virtual. Anyone know how to work around this?

James

Page 1 of 2 All times are UTC - 7 hours
Powered by phpBB® Forum Software © phpBB Group
http://www.phpbb.com/