It is currently Sun Oct 22, 2017 5:38 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Sat Jan 22, 2005 9:16 am 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
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


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 10:00 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1390
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.

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 10:11 am 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
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


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 10:32 am 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
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.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 10:33 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1390
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]);
   }
}

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 2:34 pm 
Offline

Joined: Mon Sep 20, 2004 11:13 am
Posts: 134
Location: Sweden
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.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 4:16 pm 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
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


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 22, 2005 4:41 pm 
Offline

Joined: Mon Sep 20, 2004 11:13 am
Posts: 134
Location: Sweden
Judging from your pics, it still seems you're calling that hblank function during vblank. Otherwise, perhaps it's a PAL/NTSC thing?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 25, 2005 6:52 pm 
Offline
Formerly Fx3
User avatar

Joined: Fri Nov 12, 2004 4:59 pm
Posts: 3064
Location: Brazil
"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


?

_________________
Zepper
RockNES developer


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 25, 2005 6:53 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1390
The former - the values are 1KB bank numbers, but the bottom bit is always ignored.

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 25, 2005 8:27 pm 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
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


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 25, 2005 9:58 pm 
Offline
Formerly Fx3
User avatar

Joined: Fri Nov 12, 2004 4:59 pm
Posts: 3064
Location: Brazil
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.

_________________
Zepper
RockNES developer


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 25, 2005 10:31 pm 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
>>'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


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jan 25, 2005 10:32 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1390
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.

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 26, 2005 11:40 am 
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


Top
  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 17 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group