Friday, February 22, 2008

How do I usually fix stuff in desmume

I've not worked much on desmume lately. I really like low level coding, but I prefer graphics coding, and that's what I've mostly been doing the past 14 years. I still work on desmume from time to time, but it's not my only open project as of now.

But that's not what I want to talk about today, I want to give an example of how I fixed a certain issue on desmume, and the whole process that lead to fixing the bug: usually involves hell a lot of work to fix small bugs. It's strange to fix major problems with little work, or at least that's what I'm used to. Well, let's get to the good stuff.

Recently, NHervé (from ngemu forums) asked if distributing desmume "mods" was ok. The answer was simple: desmume is GPL, an thus, distributing modified versions is ok, as long as full source code is provided for the modified version. After that, he did the first mod. He is quite focused on fixing New Super Mario Bros. To get it ingame, emulating the GFX FIFO irq is needed, which official version doesn't support. Anyway, there was (at least), another bug: when starting mini-games (on main menu), desmume froze. That's what I'll talk about today, how I fixed that freeze. Let me clear up, that blog entry is a readable version of a what I explained in this thread.

The first thing I usually do is double-check where it freezes, obvious, but needed. I've seen plenty of people fixing "bugs" without even checking the exact reason, and it's losing time. First thing I saw, is that it jumped to 0x00000000, and there wasn't anything resembling code: basically it was all zeros. That jump could be on purpose, or the reason of the freeze. I assumed that it was meant to jump there, as I had to pick an option, and both were possible. So assuming than that memory region wasn't being initialized properly, I thought that it was some DMA failing, but after a quick peek at the DS available documentation, I saw that on that region resides the ITCM, which (explained in a simple way) is basically a small area of memory (32kb), where code is executed faster. On the DS, you can't DMA to the ITCM, so that couldn't be the failure. Next step was a bit more hard, and involved reading a bit more on how the internals of the DS work.

As I didn't knew how the ITCM worked, I spent some time reading about it. The most interesting bit, is that by setting it's virtual size (up to 32MB) bigger to it's physical size (32kb), it'll mirror it's contents from 0x00000000 to the virtual size. I guessed New Super Mario Bros was abusing that, by writing somewhere in the virtual memory area of the ITCM, and then reading from 0x00000000, which would be perfectly working code on the DS. I checked if it set the virtual size of the ITCM bigger than the physical size, and seeing that on boot it's set to 32MB by the game, I quickly hacked mirroring. Even with that, the first routine residing on 0x00000000 would freeze too: when called, the return address was set to 0x00000000 too, so when it tried to return to the calling routine, it jumped to itself. Forever.

I started to think that the fault was not on ITCM handling (even if desmume's current implementation is technically wrong as of now), but on the calling routine. After debugging it for a few minutes, one thing was clear: it was reading from uninitialized memory, and that's why it jumped to 0x00000000. Hell, even all the register were set to 0x00000000 due to reading from memory not initialized, which was rather fishy. To make a long story short, I compared that memory region with two other DS emulators, and it was clear that desmume was not copying data there for some reason. First, I assumed that the DTCM (again, a 16kb memory where data is read faster than from main memory) mirroring was the culprit. I was wrong again. I thought that I was really missing something obvious.

I went back to the available documentation, and searched for the address of memory that the game was reading from before jumping to 0x00000000, to be exact, 0x027FFE00 (plus some offsets on the various reads), et voila, it was simpler than I expected: on boot, the DS copies the game card header there. Quickly checked if desmume was copying it too, and saw that it just copied the header partially. I implemented simple copy loop in a minute and I got the mini-games to work. In the end, it was WAY simpler than I expected :/

Still the mini-games have some problems with the sprites looking bad, but that is another task I'll might talk about another day. Oh, and I committed a few fixes (including this one) to the official CVS, just in case anyone wants to check it out. A few screenshots, as usual:

Have fun :)