|Home | Downloads | Screenshots | Forums | Source code | RSS|
Fixing Pokémon White
Apr 26th 2017, by StapleButter
This post will get into the bugfixing process for a particular bug. Bugfixing in emulation may look like black magic, but it's not so different from general bugfixing-- it boils down to understanding the bug and why it occurs. Of course, emulation makes it harder when it involves blobs of machine code for which you have no source code, but nothing insurmontable.
Anyway, the issue with Pokémon White (and probably others) was that the game wouldn't boot unless it was launched from the firmware. Not really convenient, especially as at the time of writing this, firmware boot in melonDS is unstable.
I first suspected the RAM setup done prior to a direct boot (NDS::SetupDirectBoot). There are several variables stored in memory by the firmware, which games can use for various purposes. For example, the firmware stores the cartridge chip ID there, games then check the cartridge chip ID against the stored value, and throw an exception if it has changed (which typically means that the cart was ejected).
However, some testing revealed that there was nothing the direct boot setup was missing that could have broken Pokémon, atleast regarding the RAM.
So I had to dig deeper into the issue. It turned out that during initialization, the ARM9 got interrupted by an IRQ, but for some reason, it never returned to its work after processing the IRQ.
DS games often use multiple threads, so it isn't uncommon to switch to a different task after an IRQ. But that wasn't the case here, it wasn't even picking a thread to return to. It got stuck inside the IRQ handler.
The IRQ occured upon receiving data from the IPC FIFO. In particular, the ARM7 sent the word 0x0008000C. The ARM9-side handler was coded to panic and enter an infinite loop upon receiving this word.
More investigation of the FIFO traffic showed that the ARM9 first sent the word 0x0000004D, which is part of the initialization procedure. To which the ARM7 replied with 0x0008000C. But it appeared that the ARM7-side FIFO handler was coded to do that. For a while, that stumped me. I couldn't understand how it was supposed to work.
I then logged FIFO traffic when booting the game from the firmware, whenever it successfully booted, to see where the exchange differed. The ARM9 sent 0x0000004D, to which the ARM7 replied 0x0000004D. But it appeared that in that case, the ARM7 was using a different handler.
I checked the ARM7 code responsible for setting up the FIFO handler. It first set up the 'good' handler, then called a function, then set up the 'bad' handler. Looking at the function inbetween, I noticed that it checked register 0x04000300, which is POSTFLG, and is set to 1 by the firmware.
Hey, what if...
So I quickly checked the direct boot setup, and it wasn't initializing the POSTFLG registers. And, surprise, addressing that fixed the issue, letting Pokémon White boot directly.
Well, not bad. Next up is fixing the issues that plague firmware boot, I guess.
|2 comments (last by Johnny Doe-Eye) | Post a comment|
melonDS 0.2, there it is
Apr 24th 2017, by StapleButter
You can check it out on the downloads page.
I'll let you find out what the novelties are ;)
|No comments yet | Post a comment|
Breaking the silence
Apr 8th 2017, by StapleButter
You may have noticed that there's one thing melonDS 0.1 lacks sorely: sound. It's an integral part of the gaming experience.
It's also one of the big items in the TODO list, so let's do this.
Getting basic sound playback going wasn't too difficult. The DS sound hardware is pretty simple: 16 channels that can play sound data encoded in PCM8, PCM16 or IMA-ADPCM. Channels 8 to 13 also support rectangular waves (PSG), and channels 14 and 15 can produce white noise.
I first worked on PSG as it's pretty simple, there would be less things that could go wrong. And indeed, the issues I got were mostly dumb little things like forgetting to clear intermediate buffers. Once that was working, I completed it with support for the remaining sound formats.
And there it was, sound in melonDS. That thing finally begins resembling a DS emulator :)
The sound core in melonDS is synchronous. The major downside is that it sounds like shit if emulation isn't running fullspeed. But it's been stated that the goal of melonDS is to do things right. There are other reasons why it has to be synchronous, too; the main one is sound capture.
The DS has two sound capture units, which can record audio output and write it to memory. Those are used in some games to apply a surround effect to the audio output, or to do reverb. The idea is to send the mixer output to the capture units instead of outputting directly, then use channels 1 and 3 to output the captured audio data after it's been altered by software.
Setups using sound capture expect that capture buffers will be filled at a fixed interval. This breaks apart if your sound core is asynchronous, because there is no guarantee that it will produce sound at a regular rate. What if you end up producing more sound than the game's buffer can hold? In these situations, the best way is to ignore those effects entirely. Same reason why the old HLE audio implementation of Dolphin didn't support reverb effects.
Anyway, sound output is still in the works, but it's fairly promising so far.
Oh by the way, have I mentioned that there are other DS emulators being worked on? Check out medusa!
|1 comment (by naknow) | Post a comment|
melonDS 0.1 is out!
Mar 30th 2017, by StapleButter
It's here, finally! Without further ado, check it out on the downloads page!
You can also read the release notes on the board for more information.
|17 comments (last by YueR) | Post a comment|
The aging cart
Mar 21st 2017, by StapleButter
So I got the UI far enough to be able to run things again. Of course, I tried the aging cart.
It's a nice help when it comes to making emulators accurate. It has a nice big set of tests. The code is also well-structured and not too hard to understand. There is a table at 0x021F2FD0 that lists all the tests, pointing to each test's name and the corresponding function.
melonDS failed the DMA priority test, like DeSmuME.
The failure was actually not related to DMA priority, which is handled correctly in melonDS. The DMA priority test runs two DMA transfers, a long one that starts immediately and a short one that starts upon HBlank but has higher priority. Each DMA fills a buffer with values pulled from a shared timer. It then checks the continuity of the stored values to find out whether and when the long DMA was interrupted.
This pointed to something I wanted to do since a while: rework timer emulation in melonDS as it was a bit complex and grossly inaccurate. After doing so, melonDS passed the DMA priority test, but it also fixed a bunch of issues, like FMVs playing at shitty speeds.
After implementing some more obscure DMA types, I was able to pass more tests. For example, DMA type 3 is triggered at the start of each scanline, but it runs on scanlines 2 to 193 included, and, unlike HBlank DMA, it always stops on scanline 194. It's not clear what purpose this DMA type would serve -- maybe it was intended for some external device acquiring video from the screens.
Then came DMA type 4, which is used for feeding the display FIFO. The current implementation in melonDS is a gross hack, but it is pretty much impossible to use the display FIFO without DMA due to its tight timing, and emulating it properly would be resource-intensive. The display FIFO is another obscure feature -- I don't know of any retail game that uses it, but I have yet to be surprised.
At this point, melonDS gets to the capture control test, but fails it. Again, the issue is unrelated to the screen capture logic. The test renders 3D graphics, and checks correctness by checksumming the captured image. Which basically requires pixel-perfect 3D graphics.
The aging cart has been a fun ride so far, and it's still far from being finished :)
|5 comments (last by Grenwood) | Post a comment|
melonDS 0.1: soon a thing!
Mar 18th 2017, by StapleButter
And yet, it may very well take a while.
As stated in the TODO list, the main remaining thing to do, besides fixes to timers, is building an actual UI for melonDS. The current one was something I had thrown together quickly early in development so I could see graphics. melonDS was barely beginning to run things, it supported VRAM display so it could run ARMWrestler, then got some extra graphics support, enough to render the DS firmware interface.
Things have changed a lot since then. melonDS now has an almost-complete 2D renderer with more accurate colors than the other emulators out there, and even a 3D renderer that is more than satisfying for a first release. It got support for a bunch of things that don't sound very exciting but are required to get games working. All in all, I believe we have a fairly solid emulator base.
But the interface is still lame. It still loads a ROM from a hardcoded filename. It's still a barebones video output and a console spewing nonsense as the game runs. It still has hardcoded (and retarded) key mappings.
The current interface is made of Windows-specific code, which is why I didn't build upon it; I want something cross-platform.
I'm still having trouble deciding what I will work with for this UI. I'm considering libui and SDL, if I can get them to cooperate. I want something lightweight, melonDS is going to stay pretty simple. As for SDL, I'm going to need it (or an equivalent library) for things like joystick input or audio output.
I initially hoped to be able to stay away from Visual Studio as far as Windows is concerned, but to my regret, alternate solutions are more or less of a headache to get working. So I'm likely going to ditch the CodeBlocks project and use CMake.
Which reminds me why I have been postponing the UI stuff: I would rather work on interesting emulation stuff than go through this trouble for a cross-platform UI. I recently discovered that there is an aging cart (Nintendo-internal test ROM) for the DS, and of course, I want to run it in melonDS to see how good it does. None of the emulators I know of pass the whole test, which means that either the test ROM is tricky or the emulators aren't as good as we think. Well, in its current state, melonDS wouldn't pass it either, but there's room for improvement.
No screenshots for this post, I'm keeping some surprise for the release ;)
|21 comments (last by Darwin) | Post a comment|
VRAM? Lots of fun!
Feb 24th 2017, by StapleButter
First of all, a little status update. From the previous list, DMA timings are covered, they're properly emulated except for cart DMA. I have also been fixing several GXFIFO related bugs, which lets most games run fine. Some badly programmed games (SM64DS or Rayman RR2, using immediate DMA instead of proper GXFIFO DMA) still overflow the FIFO a little, and since I haven't yet figured out why, I added a little hack to cover that for now.
Improving the 3D renderer, the other big TODO item, mostly means implementing textures. And also that weird focus I developed on making the renderer pixel-perfect.
Pixel perfection can be postponed. Most games would still be playable fine without it.
Textures mean supporting texture memory, which is a special case of VRAM mapping. I first want to revamp VRAM emulation to better match the hardware. There are notes about VRAM mapping in the melonDS code, and they have been there since a while now, waiting for a proper implementation.
Thing is, VRAM mapping on the DS isn't linear like with most old consoles. You get 9 VRAM banks of different sizes (from 16K to 128K), each can be mapped to different addresses for different purposes. GBAtek documents the mapping modes for each, but it's missing details like how the banks are mirrored and what happens if two or more VRAM banks overlap (which does happen in some games). So, I wanted to test that.
I first assumed the mapping was 1:1, as most DS emulators out there handle it. Which would mean that if two banks overlapped, it would either map the last one, or apply a priority order. So the first test was to map banks A and B to different addresses, write 0x1111 into A and 0x2222 into B, then map them at the same address and read, then do it again but map in a different order. The results would tell whether A has priority over B, or B over A, or whether it just maps the last bank.
The result wasn't quite what I expected. The tests didn't read 0x1111 or 0x2222, they both read 0x3333.
VRAM mapping isn't a 1:1 table. It basically just tells each bank which address range it should respond to. When two banks share an address range, they respond at the same time: writes go to both banks, and reads read from both banks too, ORing the values together, as shown above.
VRAM mirroring is also funky, each bank is mirrored in its own way, and the mirroring scheme doesn't depend on the bank's size. But for special cases like extended palettes or texture memory, mirroring doesn't matter.
I have yet to finish covering the VRAM mapping oddities without completely killing performance, and make the 2D GPU code suck less. Once that's done, we can get to the fun part of implementing textures in the 3D renderer.
|7 comments (last by StapleButter) | Post a comment|
On the way to 3D graphics
Feb 14th 2017, by StapleButter
Unrelated note: I changed the way the site displays comments, to start from the oldest. It feels more natural this way, atleast to me.
So a while ago, I was at the point where getting further with melonDS required emulating the 3D engine.
As tempting as it is to hack things together to get some graphics showing up, I decided I'd take the time to do things right. Plus, GBAtek documents the 3D engine well, so let's make good use of it.
This also means that the renderer I'm building is a software one. This doesn't exclude the possibility of implementing hardware rendering for 3D or even 2D later on, but starting this way is more interesting than mapping the DS 3D features to OpenGL. I'm able to learn about deeper aspects of the 3D engine, and emulate it more closely than a hardware renderer can get. There are also features and details of the original hardware that can't be reproduced well with OpenGL, as is typical with older console GPUs, and it isn't always super-specific details, it can also be about basic features.
For example, the DS is able to draw triangles and quads natively, but modern GPUs can only draw triangles -- the more complex primitives supported by OpenGL are broken down into triangles. This can affect how vertex attributes (color and texture coordinates) are interpolated across a quad. Below are two renders of a quad with the same attributes:
The left one shows color interpolation across the original quad. The right one shows color interpolation after the quad was broken down into triangles. You can notice the difference.
At this point, melonDS's renderer is able to draw polygons without exploding, which is a good start. It can do color interpolation, but it lacks perspective correction for now. It also lacks more important features, like texturing.
Anyway, melonDS isn't too far away from a first release. Here's a quick list of the things that remain to be done before it can be shipped:
* a better 3D renderer, as explained above
* emulating DMA timings
* building a somewhat proper interface
* misc. tidbits: timers that suck less, a few missing bits of the 2D renderer, etc...
Stay tuned for more!
|1 comment (by SonicBlader) | Post a comment|
Deeper than it seems
Feb 7th 2017, by StapleButter
I've been wanting to work out the DS's color precision. Incoming colors (palettes, vertex colors...) are all 15-bit, but the screens actually output 18-bit color. The 3D engine is well documented in this regard, we know that the colors are converted to 18-bit before being blended together and even how they're converted. However, details on the 2D engines are at a loss. GBAtek doesn't say much, other than that master brightness is applied to 18-bit color.
For reference, GBAtek's diagram of the whole video system.
I first tested master brightness. The idea is to configure it in "brightness decrease" mode with a factor of 8, such that incoming colors are halved. Then, fill two parts of the screen with colors 0,2,0 and 0,3,0 respectively. If the colors are processed as 15-bit, they will come out the same, but if they're converted to 18-bit beforehand, there will be a difference. Noting that I picked green colors to make the difference as visible as possible, as the human eye sees green best.
The test reveals that there is a difference, subtle but visible. Which confirms that master brightness is applied to 18-bit color.
To find out where the color conversion takes place, I did the test again, using the "brightness decrease" special effect instead of master brightness. It works the same, but it's applied earlier than master brightness. The test also showed a difference, meaning that color special effects are applied to 18-bit color.
So now we know that colors are converted as soon as possible, but there is one remaining detail to work out: how they're converted.
We know that the 3D engine converts color components the following way:
if (in == 0) out = 0;
else out = in*2 + 1;
From there, the idea is to display identical colors from the 2D and 3D engines, side by side, and compare them. By doing it for each possible intensity (from 0 to 31), we can work out where they differ and infer how the 2D engine converts colors.
The result of this test is a little surprising. For all the intensities above 0, the colors differ.
out = in*2;
So this is how the 2D engine converts colors. Which means that it's impossible to reach full intensity, for example, white (31,31,31) will come out as 62,62,62 instead of 63,63,63. This also means that colors output by the 2D and 3D engines will be subtly different, atleast unless special effects are used.
I also checked the VRAM/FIFO display modes of 2D engine A, and they convert colors the same way as the 2D engine. However, the white displayed by a disabled screen is 63,63,63.
|4 comments (last by Crono) | Post a comment|
A lot closer to a finished product
Feb 6th 2017, by StapleButter
Even if still very far.
After a while, melonDS finally boots commercial games. Among my small game library, the compatibility rate is encouraging, even. Here are a few screenshots, for example:
Getting there took a while of implementing new features, but also bashing my head against obscure bugs that often turn out to come from silly little things.
As an example, a bug that prevented games from booting.
On ARMv4/v5, the LDR opcode has the particularity that if you read from an address that isn't word-aligned, it aligns the address, and rotates the word it read so that the LSB is the byte pointed by the original address. In melonDS, it was implemented the following way:
u32 val = ROR(cpu->DataRead32(offset), ((offset&0x3)<<3));
At first glance, looks alright, doesn't it? Except ROR() is a macro, defined as follows:
#define ROR(x, n) (((x) >> (n)) | ((x) << (32-(n))))
This basically ends up calling cpu->DataRead32() twice. This isn't a big deal when reading from RAM, it only wastes some CPU time, but the bug goes unnoticed. However, it has nasty side-effects when reading from I/O registers like the IPC FIFO.
Next up, aside from GPU-related work, were features like touchscreen or save memory.
Touchscreen emulation isn't too hard. Saves are a little more involved. In itself, it's nothing big, the save memory is an EEPROM or Flash chip accessed over a dedicated SPI bus. The issue is how to determine the correct memory type. The ROM header doesn't contain that information, so we must guess it. The current implementation waits for the game to start writing to the save memory and tries to determine the memory type from the length of the longest write. The idea is to guess the memory page size, from which the memory type and size can be inferred. If a save file is already present, those variables are inferred from the file's size.
With that covered, games can do something more interesting than sitting on a "failed to erase data" screen.
So far, this is what I have tested:
New Super Mario Bros: non-3D minigames playable, freezes when going ingame
Super Mario 64 DS, Rayman Raving Rabbids 2, Meteos demo: "playable", but no 3D graphics
Mario Slam Basketball, Rayman DS: get stuck trying to do a GX FIFO DMA
Mario & Sonic at the Olympic Games, Mario Kart DS: freeze when trying to display 3D graphics
Super Princess Peach: playable, 3D effects missing
Worms 2 Open Warfare: seems to work, but menus invisible -- this game is all 3D
It appears that there are now two main immediate directions for melonDS: UI and 3D support.
The UI part will need some thinking to pick the best framework. The current UI is something I quickly threw together using the Win32 APIs so I could see graphics, but for the "final" product, I want something cross-platform. An idea is to provide a quick SDL-based interface and a more complete Qt interface. I don't like some aspects of Qt, but regardless, it's a possible candidate, and a powerful one.
A decent UI would also support things like selecting a ROM file instead of hardcoding the filename, choosing a save memory type should autodetection fail, choosing whether to boot from the BIOS or from the game directly, all those things.
3D support is going to be required to get further into emulating games at this point. Past the obvious reason that they can be unplayable without 3D graphics, some require the hardware support to get further.
The 3D GPU has a FIFO for sending commands to it, called GX FIFO. It can be set to trigger an IRQ when it gets empty or less than half-full. Some games wait for the IRQ before sending more commands, some others use DMA to send their command lists automatically. Without proper support, these games would just hang.
The most bizarre game is probably Super Mario 64 DS. It does enable the GX FIFO IRQ at times, but never waits for it -- instead polling the GXSTAT register with the following code:
0205A390 LDR R12, =0x4000600
0205A394 LDR R4, [R12]
0205A398 AND R4, R4, #0x7000000
0205A39C MOV R4, R4, LSR#24
0205A3A0 ANDS R4, R4, #2
0205A3A4 BEQ #0x0205A394
This is basically an inefficient way of checking whether bit 25 of GXSTAT (0x04000600) is set.
((GXSTAT & 0x0700000) >> 24) & 0x2
Could have as well been:
GXSTAT & 0x02000000
Why it doesn't just wait for the GX FIFO IRQ is a mystery (waiting for an IRQ lets the CPU go idle and saves power, unlike this kind of busy loop).
Stay tuned for more reports of the melonDS adventure!
|7 comments (last by Williamemoro) | Post a comment|