Konami VRC4 and the iNES Mess

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Konami VRC4 and the iNES Mess
by on (#35429)
So I thought it would be nice to add Konami VRC4 support to my emulator. I understand the different address lines being used differently in different variants.

But what I don't understand is when variants share the same number, and how you are supposed to know which address lines to use. For example mapper 21:

VRC4A uses A1 & A2.
VRC4C uses A6 & A7.

How am I supposed to know where to have the register when they use the same mapper? Will the games work properly if I have both sets for mapper 21 as valid? No information on this was on the Wiki. The information that is there is incomplete.

Edit: So far I've just started a small Checksum database and entering games into it and it uses a default if it fails to find a match. But while doing this I noticed some strange things. Like Akumajou Special (Kid Dracula) writes to $8FFF and $AFFF. But the documents tell you only to do PRG regs at $8000 to $8006 and same for $A000. If I do the whole range then it breaks the other games, not to mention Dracula doesn't seem to run then anyway. So I had to make a special case. I can't believe have disorganized all of this is.

by on (#35439)
- With respect, the mapper docs (probably the same I'm using) are slightly inaccurate. Well, I don't mind - they are different mappers, even with similarities. Here's a scop of my mapper 23:

8000 or 8FFF - PRG bankswitch
9000 - mirroring
9008 - WRAM enable/disable (japanese docs cover this register)
A000 or AFFF - PRG bankswitch

- For CHR bankswitch or IRQ, take xxx0,xxx1,xxx2,xxx3 as mirrors of xxx0,xxx4,xxx8,xxxC.

- Be clear that I didn't make any recent deep analysis in the Konami mappers. Most of my "corrections" dates ~5 years ago.

- For mapper 21, yes, I couldn't rewrite it in order to match the docs, so I left my old stuff. For IRQ register writes:

Code:
if(addr&0x0f) paddr=(addr&0xff00)|((addr>>1)&0x000f);
else if(addr&0xf0) paddr=(addr&0xff00)|((addr>>6)&0x000f);

switch(paddr&0xf003)

by on (#35440)
One way I've seen it done (and the way I do it myself) is to just mask out the desired bits and OR them together.

For example for 021:

Code:
void Write_Mapper021(u16 a, u8 v)
{
  a = (a & 0xF000) |
      (a & 0x0006) |
      ((a & 0x00C0) >> 5);

  Write_VRC4(a,v);
}


While this doesn't really provide correct mirroring, it works for all commercial games I've tried. (Getting correct mirroring in all cases in impossible without using a database of sorts -- the iNES number alone is not enough to know which address lines to use)

Quote:
Like Akumajou Special (Kid Dracula) writes to $8FFF and $AFFF. But the documents tell you only to do PRG regs at $8000 to $8006


$8000 to $8006 and mirrors. Remember that unused address lines are ignored, so Akumajou Special writing to $8FFF is effectively writing to $8006.

by on (#35441)
I tried out a few months ago, but it didn't work here, unfortunately. :P

by on (#35442)
Fx3 wrote:
I tried out a few months ago, but it didn't work here, unfortunately. :P


I haven't had any problems doing it that way. What games were giving you trouble?

by on (#35444)
Some of Wai Wai World titles (map21). I tried to rewrite the mapper in order to match your doc, but it never worked. Well, perhaps my mistake regarding the address masking..?

by on (#35446)
Oh so I should actually have masked for $8007, and then I suppose I could check if its $8007 instead of a number between $8000 and $8006. For some reason reading the docs made this all very unclear.

I'll try that out. But I see a checksum/hashing function is basically required to identify a database entry to ensure you use the proper lines due to iNES having not allocated each configuration a separate mapper. Does anyone have a complete list of Konami VRC games somewhere? I'm sure I don't know them all. I'd really like to know of a VRC4 game that uses the second PRG mode where $8000 and $E000 are locked but $A000 and $C000 are swappable.

As far as I've tested, every VRC4 game I've tried works fine. Including Wai Wai World as far as I could tell.

Edit: I changed this to this and everything seems to work.


Code:
   if((Address&0xF007)>=0x8000 && (Address&0xF007)<=0x8007)
   {
      VRC4_PRGUpdate();
      return;
   }

   if((Address&0xF007)>=0xA000 && (Address&0xF007)<=0xA007)
   {
      VRC4_PRGUpdate();
      return;
   }

by on (#35448)
MottZilla wrote:
Oh so I should actually have masked for $8007, and then I suppose I could check if its $8007 instead of a number between $8000 and $8006. For some reason reading the docs made this all very unclear.


Well.. you wouldn't mask with $F007... rather, you'd mask with the address lines the mapper uses. So for example if the game uses A4 and A5, you'd mask with $F030.

I probably could've explained it better in my doc. But it sounds like you've got it working now anyway.

by on (#35454)
I believe this is a complete list of VRC games.

VRC1:
Ganbare Goemon! - Karakuri Douchuu
King Kong 2 - Ikari no Megaton Punch
Tetsuwan Atom

VRC3:
Salamander

VRC4 and VRC2:
Akumajou Special - Boku Dracula Kun
Bio Miracle Bokutte Upa
Crisis Force
Ganbare Goemon 2
Ganbare Goemon Gaiden 2 - Tenka no Zaihou
Ganbare Goemon Gaiden - Kieta Ougon Kiseru
Ganbare Pennant Race!
Getsufuu Maden
Gradius 2
Gryzor
Jarinko Chie - Bakudan Musume no Shiawase Sagashi
Parodius da!
Racer Mini Yonku - Japan Cup
Teenage Mutant Ninja Turtles 2 (J)
Teenage Mutant Ninja Turtles (J)
Tiny Toon Adventures (J)
TwinBee 3 - Poko Poko Dai Maou
Wai Wai World 2 - SOS!! Paseri Jou
Wai Wai World

VRC6:
Akumajou Densetsu
Esper Dream 2 - Aratanaru Tatakai
Mouryou Senki Madara

VRC7:
Lagrange Point
Tiny Toon Adventures 2 - Montana Land he Youkoso (J)

If any games are missing let me know. Otherwise I consider this list complete.

by on (#35456)
Salamander is VRC3 (073)

I also have a few more 075 games as well -- but I don't know if they're VRC1 or a similar mapper.

by on (#35461)
Ah yes I forgot to list Salamander, but I do have that in my folder. I don't believe any of the other mapper 075 games are made by Konami, which makes me doubt they would be VRC1.

by on (#35527)
Disch, would this be more accurate?

Code:
   int Mask = 0xF000;
   Mask = Mask | VRC4.LA;
   Mask = Mask | VRC4.LB;
...
   if( (Address&Mask) >= 0x8000 && (Address&Mask) <= 0x8FFF )
   {
      VRC4.PRG0 = (Byte&0x1F) & ((PRG_Pages*2)-1);
      VRC4_PRGUpdate();
      return;
   }


I tried this, and it works for all the games I've tried. Well except the Kid Dracula translation.

by on (#35535)
That should equate to the same thing. I just prefer switch statements, myself, rather than range checks. But yeah that should work fine.

How I go about it:

021
Code:
   void Write(u16 a,u8 v,int cu)
   {
      a |= (a>>5) & 6;
      NESMapperVRC4::Write(a,v,cu);
   }


025
Code:
   void Write(u16 a,u8 v,int cu)
   {
      a = (a & 0xF000) | ((a>>2) & 3) | (a & 3);
      if(a & 1)   a += 4 - 1;      // move bit 0 to bit 2

      NESMapperVRC4::Write(a,v,cu);
   }


Code:
void NESMapperVRC4::Write(u16 a,u8 v,int cu)
{
   u8 v5 = v & 0x1F;
   v &= 0x0F;

   switch(a & 0xF006)
   {
   case 0x8000: case 0x8002: case 0x8004: case 0x8006:
      nPRG[0] = v5;      SyncPRG(cu);      break;
...


As you might be able to see... with all the masking, the PRG reg is spread across all of $8xxx.

by on (#35566)
Quote:
I just prefer switch statements, myself, rather than range checks.


Range checks? Pssh, all the cool kids use bitmasks :P

if(addr >= 0x8000 && addr <= 0x8fff) == if((addr & 0xf000) == 0x8000)
if(addr >= 0x4200 && addr <= 0x421f) == if((addr & 0xffe0) == 0x4200)

In your specific case, you have a mask of either 0xf006 or 0xf0c0. Yet the lower-byte masking makes no difference since those particular bits can be anything and your test will still pass.

It would seem as though you could simply reduce it to: if((Address&0xf000) == 0x8000), and get the same result. Perhaps I'm missing something in the elipses. No big deal, just a quick observation.

by on (#35567)
byuu wrote:
It would seem as though you could simply reduce it to: if((Address&0xf000) == 0x8000), and get the same result. Perhaps I'm missing something in the elipses. No big deal, just a quick observation.


It would be fine except that IRQs use 3 registers with different behaviours. Yaya, if addr AND F000h == F000h, "another" statement could start, but it's dumb. :P

by on (#35571)
byuu wrote:
Quote:
I just prefer switch statements, myself, rather than range checks.


Range checks? Pssh, all the cool kids use bitmasks :P

if(addr >= 0x8000 && addr <= 0x8fff) == if((addr & 0xf000) == 0x8000)
if(addr >= 0x4200 && addr <= 0x421f) == if((addr & 0xffe0) == 0x4200)

In your specific case, you have a mask of either 0xf006 or 0xf0c0. Yet the lower-byte masking makes no difference since those particular bits can be anything and your test will still pass.

It would seem as though you could simply reduce it to: if((Address&0xf000) == 0x8000), and get the same result. Perhaps I'm missing something in the elipses. No big deal, just a quick observation.


When I tried doing address & 0xF000, Kid Dracula failed to run. I forget if others did too, but something was wrong and that is why I didn't do that.

by on (#35573)
Quote:
When I tried doing address & 0xF000, Kid Dracula failed to run. I forget if others did too, but something was wrong and that is why I didn't do that.


That's odd ...

Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
  for(unsigned addr = 0; addr < 65536; addr++) {
    bool x = ((addr & 0xf006) >= 0x8000) && ((addr & 0xf006) <= 0x8fff);
    bool y = ((addr & 0xf0c0) >= 0x8000) && ((addr & 0xf0c0) <= 0x8fff);
    bool z = (addr & 0xf000) == 0x8000;
    if(x != y || y != z) printf("mismatch with %d\n", addr);
  }
  return 0;
}


This produces no output, as expected. The operations are all identical.

Meh, not really a big deal. The ranged version is easier to read anyway :)

by on (#35784)
More on this subject. Tiny Toon Adventures (J), uses both the registers sets for it's mapper number, 4,8,C, as well as 1,2,3. It actually uses the IRQ with the 4,8,C set, but everything else I believe is the latter set.

So, should I be taking the mapper number, and checking for writes to both sets? Then I wouldn't need to have any DB at all? I've also noticed the sets that have the same number, could be shifted a few places to get the other set. I'm guessing this is why iNES choose to have these games share their numbers?

Just to be clear, no game as far as I know besides Tiny Toons uses both sets of possible registers for their mapper number. Right now I treat it specially so it works. But I am guessing I could handle both sets for each mapper and not need that Tiny Toons hack.

by on (#35785)
I managed to replace a Database lookup with this.

Code:
         switch(MapperNumber)
         {
         case 21:
            LA = 0x02;
            LB = 0x04;
            LS = 5;
            break;
         case 22:
            LA = 0x02;
            LB = 0x01;
            LS = 0;
            break;
         case 23:
            LA = 0x01;
            LB = 0x02;
            LS = 2;
            break;
         case 25:
            LA = 0x02;
            LB = 0x01;
            LS = 2;
            break;
         }


Basically I check if the appropriate lines are set for register writes, as well as their left shifted value according to LS. So far, this seems to make all games work without the need for a database. I wish the document had explained that when emulating each mapper number that both sets were valid. This mainly only seems to affect the proper emulation of Tiny Toons.

by on (#35786)
Why not just OR the register addresses?

by on (#35787)
Didn't think of that. Suppose either works.

by on (#35788)
You must consider 8000 and 8FFF, as well as A000 and AFFF, so address masking wouldn't work. Plus, I consider *both* settings:

Code:
case 0x8000: case 0x8FFF:
case 0xA000: case 0xAFFF:
...
case 0xF001: case 0xF004:
case 0xF002: case 0xF008:
case 0xF003: case 0xF00C:


AFAIK, this has no impact on program performance... but you can use:
Code:
switch( ((address & 0xC) >> 2) | address )