Here's a mild tech write-up of what I spent yesterday doing, to at least show that progress is being made.
There had been a few reports of the memory card emulation failing, normally involving the game giving some sort of error while saving and then stating that the device in slot A isn't a memory card. The second part is easy to explain: once a game decides the memory card is bad, it won't change its mind until it sees a new card inserted and that behaviour isn't part of the emulation (there's no point). But what was causing the original error?
The only thing that stood out from the logs is that the memcard.bin file (the 16MB file that stores the memory card content) was always fragmented. Fragmentation normally isn't much of a big deal, on average it only requires one extra read of the FAT area when you want to seek backwards in a file. Real memory cards are solid state devices meant to work pretty fast; the gamecube SDK actually sets up a 100ms timeout callback whenever it writes something to a card. If the timeout period expires before the write finishes, the game decides the card is bad and won't do anything more with it. 100ms is a fairly long time just for a single write, but there might also be other reads occuring simultaneously if the game is also reading from the DVD or using audio streaming. In these cases things get even worse when the ISO is fragmented because the FAT accesses will probably be to a different area.
The maximum size of an individual memory card write operation is 128 bytes. Since this is smaller than a single sector the target sector must first be read, then the new data is placed (overwritten) within it, then it gets written back to the disk. On top of all this there's the SCSI/USB overhead, which breaks each read/write into three seperate USB commands. Each USB command is an IPC command which requires costly cache flushing/invalidation and is accomplished via four separate operations between the PowerPC and IOS/Starlet (request->request ack->response->response ack).
Additionally, every time the game reads or writes to one of the EXI registers (which is how it tells the memory card what to do) the amount of time taken is hundreds of times longer than it would be on a real gamecube since it's being emulated. Unfortunately the timer is started before the writing process begins, so there's a fair bit of "emulation time" that also gets included.
Possible ways to fix:
- Cache the entire memory card contents in memory: there's enough room to do it, but it's a large waste since a particular game will only use a small portion of the card. Plus it would mean delayed flushing of the memory card contents to disc and that could mean data loss.
- Wind back the timebase values while a write is in progress: not a great idea since that timebase also drives lots of other things, particularly audio/video playback.
- Asynchronous writing: tell the game that each write command finishes immediately and then do the real write operation in the background. Again, not a great idea because the game might disable interrupts and they are needed to talk to starlet. There's also the problem of what to do if the game immediately attempts another write operation - if it is delayed until the real write is finished, we're back to where we started.
- Patch the timeout value: Simple and obvious. The only risk is making sure the opcodes that need patching are distinct enough to always be detected and avoid false positives. I compared a launch game (Rogue Leader) against the latest game I have (Geist) and the timeout setup was identical, so the patch went in to change the timeout period from 100ms to 1 second (which should be more than enough).