Sunday, 28 December 2014

Implementing MBC3 timers in supergameherm

I've mentioned supergameherm in my initial post and how I would be talking about it. As promised, here is some discussion on the topic.

As anyone who develops for the GameBoy knows, all but the most simple games require an MBC (Memory Bank Controller), due to address space constraints. Tetris, notably, did not need an MBC (being one of the simplest games ever made for the GameBoy); however, most did.

If you're ever wondering how games such as Pokémon Gold and Silver kept what day of the week and what time it was (within semi-reasonable accuracy), it used a timer inside the cartridge, which was powered by a battery. Its ticking was controlled by a quartz oscillator. Pretty simple stuff; it might even remind you of a watch.

The RTC has 5 registers: seconds, minutes, hours, the lower 8 bits of a day counter, and another register containing the following values:
  • Bit 0: Extra bit for day (day counter was unsigned 9 bits, allowing values from 0-512)
  • Bits 1-5: Unused, values unknown (I don't have real hardware anymore to test with)
  • Bit 6: Clock halt (timer stops when this bit is set - to avoid race conditions, only write to the RTC registers when this is set)
  • Bit 7: Day counter overflow flag (when day > 512, and is NOT reset by the cartridge when it overflows again - only the game resets it)
In addition, there are another 5 "shadow" registers that you don't normally see - they are the latch registers. When a bit is set in the cart, the present time is latched into these shadow registers, and this is what is presented as the current time. The time only appears frozen; it really isn't. You can modify the registers all day long, and the real time will keep on ticking and pretend nothing happened. Unlatch the time, and you get the real time back - but the old value is retained in the latched registers until you relatch the RTC (inaccessible of course, in theory).

All this is in the pandocs for the most part, but the relevant bits are in Denglisch, which can be hard to read at times.

The easiest way to emulate the counter ticking is to intercept all reads to the RTC registers, and then compute a time delta. You could of course implement a simple counter, but that requires checking for overflow of seconds/minutes/hours/days/setting day carry - things you have to do if you are computing a delta anyway! Besides all that, you need a function to compute a delta if you have any hope of loading a save file and keeping accurate time (assuming the ROM is opened every 512 days of course, to ensure the day overflow bit is checked :p). It's best to only adjust the clock as-needed, because modulus and division are very slow operations on x86.

When saving, just keep track of the Unix time stamp (see the time() function in C). In the save format used by VBA and modern BGB, the clock data is appended to the end of the save file, including the Unix time stamp. Using this information, a delta can be computed, and the time brought up to speed in the game. As far as the game is concerned, nothing even happened.

If you're curious about the code to compute time deltas, it's all on GitHub. If you want to make your own MBC3 implementation, feel free to use that code - it's BSD licensed. I wanted WTFPL, but Anna wanted BSD, and I wasn't going to argue the point.

There seems to be a dearth of emulators which do RTC emulation. It's a real pity, because the code isn't that difficult. The only tricky part is to remember to compute the delta before latching.

Optimisations I want to make to the code are to replace my MBC3 time structure with a structure containing pointers. We use memory-mapped I/O for save files to emulate .sav files accurately without obliterating performance. Writing to disk for every RAM operation would have been costly, and isn't feasible for games that actually use the RAM for purposes besides save files (a common thing seems to be holding sprites). This avoids an unnecessary function call to write back all the values periodically to the memory region.

No comments :

Post a Comment