When your innocuous little code goes kaboom
So until we get something amazing to release, we're going to post some technical shito again.


As said in the previous post, I've been coding savestates. The base idea seems simple enough, just throw all the emulator's state into a file (or read it from that file if you were loading a state), make extra sure that you're not missing anything important, regenerating things that need it when loading, etc...


So I first designed the bases of the implementation:

• A savestate file is divided in several sections. Those can be ordered arbitrarily. The file format itself is very simple.

• To reduce complexity, we only handle loading/saving between frames.

• Some questions are still not clear, and I still need a way to avoid loading a savestate over the wrong ROM. (or I could allow it as a fun Easter egg, even if it generally results in a crash)


Then, the gruelling work, storing all the emulator state.


Including, you guess, the event scheduler, which is an array of event structures with an entry per possible event source. Just write that to the savestate file, right?

Think again.

typedef struct
{
   void (*Func)(u32 param);
   s32 WaitCycles;
   u32 Param;

} SchedEvent;

This is the event structure. We have a handler for when the event fires, how many cycles remain before it fires, and a parameter that is passed to the handler. The idea is that we can use different handlers for one event source, acting sort of like a state machine, which is what is done in display emulation for example (HBlank, next scanline, frame end).

The handler is a function pointer. Which means that storing it as-is in a file is a bad idea. For various reasons, there is absolutely no guarantee that the functions those pointers refer to will be at the same addresses between two runs. In fact, it even seems completely impossible, because modern-day OSes will randomize the address at which the melonDS process is loaded. Then you have things like using savestates across different versions or even different builds...


Well, good thing I thought about that before trying to run that code.


(now I also realized that there are several other instances of dumping structure arrays straight to the file, namely in the 3D pipeline, which might cause compatibility issues between builds as compilers might decide to order the structures differently; that will have to be dealt with too)


So, what do we do now?


As I originally wanted to keep the evnt system fairly flexible, an idea I had was to store relative function pointers, ie basing them off of main()'s address or something like that. While this would work for making savestates reusable between runs, it wouldn't fix any of the other issues. For example, run a different melonDS build and the event functions may be at different places, meaning the relative pointers wouldn't point to them anymore.


So instead, the way I went with was to store the handlers as indexes.

The idea is to keep a table of event handlers. When saving, we lookup the table to convert each event entry's handler to an index. When loading, we do the conversion backwards. Et voila, no problem. There are a couple safety checks around it, for example, to prevent haxing via a bogus savestate file containing an event handler ID that is out of range.

The main disadvantage of this method is that any function that is to be used as an event handler needs to be added to the event handler table.

But atleast our savestates are portable. I guess we win.


That being said, if any of you folks has a better idea for this problem, I'd like to hear about it.
poudink says:
Oct 19th 2018
Well glad you're still doing things. I was afraid nothing happened since the DSi post since the only sing of life was the savestates commits on github.
Anonymous says:
Apr 15th 2019
Wouldn't it be possible to dump the RAM into a file and then just reload it again?
Post a comment
Name:
DO NOT TOUCH