GameCube Game Patching - Mega Man Anniversary Collection

Hey there. I know, I haven't made a new post in quite a while, its not like I haven't done anything the past few months, I just couldn't really find the will to write anything down, but honestly this latest project of mine deserves a post for sure.
In this post I will detail my process on how my latest patch for the gamecube version of megaman anniversary collection came to be and a bit about how it works, which you can find here:
https://github.com/FIX94/gc-mm-collection-patcher

Yes, it is indeed another patch for megaman stuff! I don't know why I am doing so many of these lately, first all my PAL audio patches and now I had to move on to gamecube, I guess it just comes with me working so' much on nintendont that I also wanted to fix up these issues.

First off, we have to look at nintendont way back at its early stages in 2014, when I noticed the music in it played very broken, which I fixed in this commit:
https://github.com/FIX94/Nintendont...cdf0013#diff-bc9dbcadd0a6ecd07feeedc1c9f1be65
Fun fact about the function I fixed, it actually became a mistake of my very first version of my newly developed patch to sound broken on a real gamecube but working fine in dolphin and in nintendont, I'll get back to that later :P

At this time after fixing the audio, I also realized just how terrible its quality was in the NES ports of megaman 1-6, but I did not do anything about it at the time, still it for sure got into my head. Oh and of course, I also noticed that the controls were backwards and I did release some very hacky patch back then that just globally reversed the A and B buttons for everything, sure that meant the main menu was messed up but at least ingame it made things far more playable:
https://gbatemp.net/threads/nintendont.349258/page-549#post-5063513

Now fast forward all the way to 2016 when I first looked at some things regarding the audio, I did just try re-encode the music files that are streamed from disc straight up and tested that with megaman 2, because I wondered if the audio was so bad because of the codec selected or if it was just their recording, and well I clearly proved it was just their recording, see this video I released at the time:


This project did get abandoned by me shortly after this demo video because I realized just how broken the whole concept of playing streamed audio was, looping was pretty much impossible and also sound effects were actually recorded at even LOWER quality, only 18kHz vs the 32kHz music has, so even a re-encode did not help a whole lot here sadly and also audio balancing became a problem, but during all that I DID notice something super interesting because of actually another project I to this day haven't released that I may get back to at some point because I think its really cool too...
Anyways, what I noticed was there was parts of the original ROM image of megaman 2 in the game files even though its not emulated, that's as much notice as I took of it because back in 2016 I did not know a whole lot about NES ROMs and emulation, but that changed a whole lot.

So then in 2017 I started writing my own NES emulator and I still have been working on it from time to time all the way up till now:
https://github.com/FIX94/fixNES
This project really helped me learn a ton about not just emulation but optimization and hacking, it has been really cool just seeing a NES game being emulated by something I somehow wrote up myself from scratch, no matter how often I run something.

With that, we now come to the start of this month, where I just had one of those moments where I suddenly remembered, oh yea there was this weird thing I found about ROMs in the gamecube anniversary collection, and I went to investigate a bit more, and well my findings were rather incredible...
EVERY single NES game you load actually loads a full NES ROM into gamecube memory on bootup without using it! This ROM is in japanese and heavily modified, they replaced all graphics with some form of addresses, I assume maybe at some point they did emulate the games and showed the graphics natively on the system using these addresses but changed their mind later in development, but never removed it from their big archive file. You see, every NES port loads one big archive file with many files inside, so it was probably an early leftover or something that I am now extremely lucky to have!
When I tried to run these files in a regular NES emu, they most certainly look weird, here is a comparison of the original ROM:
Rockman-3-Dr-Wily-no-Saigo-Japan-0.png

and what it looks like from the extracted archive:
Rockman-3-J-Extracted-GC-File-2.png

You can still very clearly make out what everything is supposed to be, this is because while the graphics were replaced with addresses, only 3 quarters of each tile was actually replaced, they left the last quarter intact with the original game data, so you can still make out what everything is. Now the incredible part is, ONLY the graphics are messed up, ALL of the 6 extracted files play and sound perfectly fine, the code itself is practically untouched!

This finally leads me into my crazy idea, with the game loading a functional ROM on boot as well as me writing a very portable NES emu, what if I were to try strip down my emulator to only do audio and emulate the sound while running the port as normal?

This immediately was followed by the question of how does one add additional code to an already fully compiled game there's no source code for without requiring some rebuilding of the ISO file or something like that.
Miraculously this game comes with a so called .elf executable on disc. To explain the significance of this, normally gamecube games come in .dol execuables only, those have no function or variable names embedded, so when you try and reverse engineer it, you only see pure machine code without anything helping you along with names they had in the original source. In this case, this .elf executable though is the exact version that the .dol executable is based on with ALL FUNCTION AND VARIABLE NAMES! This really makes things so much easier, because it was immediately clear what I had to find out and test without having to spend a incredibly long amount of time going through each function and giving it some names I come up with on the fly on what they could be.
Now I was on the hunt for unused space in the data section of this executable, and very quickly I found one particular function "BinkLogoAddress" which is part of the games video player that would hand over so called "LogoData" that takes up 14.5KB of data, and this function seemingly is never called! So this gave me a pretty good start but it turned out very quickly that it was not nearly enough for my NES emulator and all the code on the side, so I went on the hunt again and again as part of the video player I found several parts that went unused, you see to decode an image, it uses a big table full of the needed numbers to decode it back into an image you can see, but it also included additional tables for decode methods that in fact were never used, I found 2 tables that both take 4KB very close to each other, so in the executable memory it was basically laid out like this:
table 1 - 4KB of used table
table 2 - 4KB of unused table
table 3 - 4KB of used table
table 4 - 4KB of unused table
In this case what I did was copy table 3 into the place of table 2 and wrote some additional patches for the functions using that table to now point to the position where that unused table 2 is like this:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/dolinject/main.c#L179-L188
Which gives me a little over 8KB of additional free space! What I mean by a little over is that there were some "alignment" blocks in between that were not needed so I removed those to save a couple extra bytes.

So great, now I have a 14.5KB block and a 8.125KB block of data free, and they are at different places in memory, so I cant just get all my code into that space easily... Now I turned to another project of mine:
https://github.com/FIX94/dolxz
I wrote that to compress executables using the LZMA2 algorithm you find in for example .7z archives and include a decompressor so you can make homebrew executables smaller, and I for example used this project:
https://github.com/FIX94/wii-ds-rom-sender
to go from a 5.48MB executable to only be 452KB executable for example, I was really hoping it would also be useful on a small scale.
This turned out to be VERY tricky, I wanted to fit the decompressor part into those 8.1KB which was a pretty interesting task to be honest.
My first try at stripping it down to just decompress one test binary and jump to it turned out to be about 10KB which was far too big, so I turned to compiler optimizations first and made it optimize for size. This still was slightly too big, but I noticed that it added some additional calls to functions now it imported from other libraries, this is a info page from the final file I released:
#Sections:
Idx Name Size VMA LMA File off Algn
0 .code 00002080 800bd220 800bd220 00000054 2**2
CONTENTS, ALLOC, LOAD, CODE
1 .text.__ashldi3 00000040 800bf2a0 800bf2a0 000020d4 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .text.memcmp 00000094 800bf2e0 800bf2e0 00002114 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
3 .text.memmove 00000144 800bf374 800bf374 000021a8 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
the .code part is mine, but you see those additional calls to __ashldi3, memcmp and memmove? Well I was able to not have those functions (and their bytes) be included in the final binary I built, thanks to the game executable again miraculously containing all 3 functions already! So in this case I was able to just point the compiler to where they are in the games executable and it now uses them instead:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/decmp/decmp.ld#L15-L18
At this point, it was SUPER CLOSE to fitting but not quite, what I ended up doing is a little bit of a hack I suppose, when decompressing LZMA2 data with the library I used, it also checks for checksums on the data and blocks using CRC32, so I just removed any and all references to these checks and just made them to be always "true" in code as bypass. I mean in the end they should never fail anyways cause this data is embedded permanently, and if it was to fail, it would crash either way, with those hacked in changes I went down to 8KB in the end, hooray!

Now I had a decompressor that was able to put my emulator and needed side code easily into the 14.5KB, but that brought up another question, where to extract my code to? Well as it turns out, the video player actually has about 41.5KB of work RAM it uses that is at a fixed location in the executable when its decoding video at this location:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/emu.ld#L65
and well since the NES games never actually play back any videos, this meant it was the perfect spot to extract it into when playing them! So my decompressor now either decompresses the emulator when it sees a NES title is loaded, or clears the whole video space again back to 0 if any other module is loaded:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/decmp/main.c#L36-L57

This of course brings me into my big hooks, the way I attach my new code onto the existing games code.
As great as it is to have some new code I fit into the executable, nothing was ever calling it yet, I had to actually make existing functions jump to my new code at the right time to make anything happen. Every loading screen you see basically loads a new "module" into memory, with the main menu and all games all being their own modules. So all I did as the big hook for the decompressor was hook into the start of the MODULE_Init function:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/dolinject/main.c#L159-L163
This patch will effectively jump to the decompressor every time a new module gets loaded, so we can immediately add new patches from there if needed. Also the 2 patches you see below that in that source code are for getting some space in audio RAM for our new NES audio to actually fit as well as some small function to skip over the whole game logo display on bootup when start is held down on boot.

With all this, I now have an automated way to load up lots of code on bootup of any NES title from where I can do whatever I want, I wont go into full detail of all my issues and developments that went on at this point because this process took me several weeks to get to where it is now, it would just get too long.
After extracting the emulator with its code, the decompressor calls a initialization function of that new code first:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L67-L82
This just takes the current game ID, the audio RAM location and applies some new patches for when its ready to load the module, transferring the NES ROM and unloading the module. The first thing that happens after that is of course it calling that freshly patched function of being ready to load the module, as this happens right after the decompressor is done:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L189-L353
This thing does a LOT. At first, it finishes up loading the module (which is the part I overwrote), and then applies patches such as reversing the A and B controls properly just for jump and shoot, as well as getting ready to patch the most important addresses of this module yet, the function thats responsible for taking whatever audio request the game has and translating it to some filename the game then plays back from disc. Basically what it will now do instead is to save this request ID coming from the game and save it onto a new list for our emu later, and then exit the function, bypassing all the disc play stuff. This way, there never is any low quality audio played from disc but we still get to keep what it wanted to play, this request will get answered with the next big function part;
The transfer of the NES ROM into memory:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L445-L602
What I had to do here is analyze each game in a NES emulator and find out where in the ROM the audio driver is and how the game communicates with it, luckily in all cases there is always a part of the ROM that updates the audio and checks if there is a new audio request, and if there is it tells the audio driver that, all in a single bit of code without it being split apart or anything. So what I do here is tell my NES emulator hey, here at this address in the ROM is the audio driver, load that up as current CPU code location, and hey, here's the function that does the updates and checks in memory. What I then do is every frame in a separate thread from the main game I just let my emulated CPU jump to the start of the audio update function, and once its done, I just set the emulated CPU back to sleep, and on the next frame start the update start again and so on, so it never executes anything else from the game ROM.
On top of initializing all the emulator things, it also sets up my method of then actually playing back the audio my emulator spits out, obviously as great as it is to have the emulator code running and updating every frame, I still have to somehow push this audio into the gamecube audio driver, for this we have this next part of this transfer function:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L603-L639
Here I just call the standard gamecube API for audio called AX to create a new "voice", think of it as an audio source, and I tell it to just loop this new audio buffer over and over again, I also tell it that every time the audio driver updates to call this function in my code after that point:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L90-L110
All that does is to resume my emulator thread every time it notices half of the looping audio buffer was played, so it refills it with new audio from my emu, what this effectively means is it will never have any skips in the audio because it results in this simple logic:
audio buffer 1st half fully played -> updates audio buffer 1st half with new data -> audio buffer 2nd half fully played -> updates audio buffer 2nd half with new data -> cycle repeats from 1st half fully played (which was updated before etc)

To get back to my patch from 2014 of the whole audio playing wrong, a little fun fact about this update process was that I actually first messed it up because I forgot to write the buffer down into main memory from cache before transferring it which worked fine in dolphin (because it has no cache like that) and nintendont (because it forces the cache to main memory automatically) so the sound on a real gamecube was very broken and crackly, that one took me a bit to figure out, and in the end all I had to add was this line before copying the audio to the audio memory to fix it:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L115

So with all this to summarize, I now take the audio requests from the game, forward them to the original NES audio driver, thankfully most request IDs are the exact same as they were in the original, I only had to patch a few, and it plays back the audio in a constantly repeating buffer with those new requests within milliseconds of those requests coming in having very little delay, all this results in a patch that honestly is incredibly good in my opinion, I never thought it would sound even half as good as this.
During this project I ran into many issues, especially megaman 6 which had me rework my emulator because I ran out of memory and because I had to make a stupid amount of patches for it:
https://github.com/FIX94/gc-mm-collection-patcher/blob/master/emu/main.c#L294-L351
Which is a result of it sounding so different to the original NES game and I had to do my best to restore it to be as close as possible to work with the original NES driver, but in the end I somehow pulled everything off pretty well, to now have a patch that runs my emulator in the background of this port, taking the original audio requests and seamlessly playing them back as if it was designed that way thanks to some unused japanese roms left over in the games archives!

I guess just at the end of this writeup let me demonstrate how my result sounds by playing through airmans stage in megaman 2 first in an unpatched, original ISO file so it can really burn in at how bad it really sounded to start out with, which took me many attempts by the way thanks to the backwards controls:

and then now with the patch applied and the audio emulated through my own nes emulator instead:


Well, I hope you enjoyed reading through my latest bit of rambling, sorry for not making it any more detailed, it was just too much to really fit into a post without more planning beforehand I guess, I just wrote this in one go because I felt like it was a pretty cool project to talk a little bit about.
So again if you are interested for some reason in trying it out yourself, just check out the project on github and read the short README at the bottom for details on how to apply it, I tested in in dolphin, nintendont and on a real gamecube so let me know if you find any issues:
https://github.com/FIX94/gc-mm-collection-patcher
  • Like
Reactions: 14 people

Comments

Wow, what a nice blog post!
I am really impressed by your skills and dedication!

You've come a long way, and still manage to pull things that seemed crazy or impossible to do!
 
  • Like
Reactions: 1 person
I'm a Megaman/Rockman fan and player, not a completionist, but really enjoy my gaming sessions.

I actually own Mega Man Anniversary Collection and Mega Man X Collection for my Gamecube, though I play mainly on my 25th Anniversary Wii, using a Gamecube controller.

This patch brings the Megaman Anniversary Collection up to a point where we can really enjoy playing it while listening to the music at a good volume level, it really changes everything!
Somehow makes me remember that great collection of Rockman 1-6 for the PSX/PSOne (which sadly I don't own). But I own Mega Man 2-6 cartridges for the NES, just one missing.

All this previous comments are to try to show how fond I am about Megaman/Rockman games, and to express my gratitude for your patch.

Thanks a lot!!!
 
  • Like
Reactions: 1 person

Blog entry information

Author
FIX94
Views
1,476
Comments
10
Last update

More entries in Personal Blogs

More entries from FIX94

General chit-chat
Help Users
  • BakerMan
    I rather enjoy a life of taking it easy. I haven't reached that life yet though.
  • K3Nv2 @ K3Nv2:
    That's called yuzu
    +1
  • BigOnYa @ BigOnYa:
    I want a 120hz 4k tv but crazy how more expensive the 120hz over the 60hz are. Or even more crazy is the price of 8k's.
  • K3Nv2 @ K3Nv2:
    No real point since movies are 30fps
  • BigOnYa @ BigOnYa:
    Not a big movie buff, more of a gamer tbh. And Series X is 120hz 8k ready, but yea only 120hz 4k games out right now, but thinking of in the future.
  • K3Nv2 @ K3Nv2:
    Mostly why you never see TV manufacturers going post 60hz
  • BigOnYa @ BigOnYa:
    I only watch tv when i goto bed, it puts me to sleep, and I have a nas drive filled w my fav shows so i can watch them in order, commercial free. I usually watch Married w Children, or South Park
  • K3Nv2 @ K3Nv2:
    Stremio ruined my need for nas
  • BigOnYa @ BigOnYa:
    I stream from Nas to firestick, one on every tv, and use Kodi. I'm happy w it, plays everything. (I pirate/torrent shows/movies on pc, and put on nas)
  • K3Nv2 @ K3Nv2:
    Kodi repost are still pretty popular
  • BigOnYa @ BigOnYa:
    What the hell is Kodi reposts? what do you mean, or "Wut?" -xdqwerty
  • K3Nv2 @ K3Nv2:
    Google them basically web crawlers to movie sites
  • BigOnYa @ BigOnYa:
    oh you mean the 3rd party apps on Kodi, yea i know what you mean, yea there are still a few cool ones, in fact watched the new planet of the apes movie other night w wifey thru one, was good pic surprisingly, not a cam
  • BigOnYa @ BigOnYa:
    Damn, only $2.06 and free shipping. Gotta cost more for them to ship than $2.06
    +1
  • BigOnYa @ BigOnYa:
    I got my Dad a firestick for Xmas and showed him those 3rd party sites on Kodi, he loves it, all he watches anymore. He said he has got 3 letters from AT&T already about pirating, but he says f them, let them shut my internet off (He wants out of his AT&T contract anyways)
  • K3Nv2 @ K3Nv2:
    That's where stremio comes to play never got a letter about it
  • BigOnYa @ BigOnYa:
    I just use a VPN, even give him my login and password so can use it also, and he refuses, he's funny.
  • BigOnYa @ BigOnYa:
    I had to find and get him an old style flip phone even without text, cause thats what he wanted. No text, no internet, only phone calls. Old, old school.
  • Psionic Roshambo @ Psionic Roshambo:
    @BigOnYa, Lol I bought a new USB card reader thing on AliExpress last month for I think like 87 cents. Free shipping from China... It arrived it works and honestly I don't understand how it was so cheap.
    +1
  • BakerMan @ BakerMan:
    fellas
  • BakerMan @ BakerMan:
    would you rather have a 9-5 desk job with poor pay or work for an intergalactic space militia with no guarantee of being paid?
  • BakerMan @ BakerMan:
    basically, normal boring job or halo and/or helldivers irl
    BakerMan @ BakerMan: basically, normal boring job or halo and/or helldivers irl