|
Several MAME developers and others have recently started
writing code to save/restore the state of emulated games.
In this article I shall attempt to describe how this process works
in HiVE and how the same concepts can be applied to other emulators
(including MAME).
General Overview
While an emulator is running a game, it must orchestrate various
processes occuring with various objects (both dynamic and static)
which represent (emulate) the hardware of the original game.
Typically these objects will include the memory controlled by the
microprocessor(s), the microprocessor register values, state machines
representing various hardware such as sound or video and any user-settable
options specific to the emulator. The current 'state' of
a game is contained in these objects and has the potential to be
saved and loaded if these objects can be saved and restored in a
reasonable fashion. In the case of HiVE, these objects consist
of the memory maps of the microprocessors (64K each for the 8-bit
micros supported by HiVE), structures containing the register values,
and a few assorted variables with state-machine info to emulate
various bits of hardware. Since each emulated game (which
doesn't run on hardware common to other games) uses it's own set
of variables and memory maps, then it logically follows that each
different game will require a different procedure to save/restore
its unique variables. This is the stumbling block preventing
MAME from supporting save/restore in all games - since each game
driver is different (and often written by different authors), it
will require custom code to save/restore its state. In an
emulator with 1400+ supported games, this becomes a daunting task
for a single person to do and a difficult task to coordinate for
a multitude of authors.
The Specifics
Shown below is my function to save/load the game state for Mappy
and the other games which run on that platform (e.g. Super Pac-Man).
I have created 2 functions (not shown - these functions are left
as an exercise for the reader :) ) which handle the dirtywork of
loading and saving the game state. The parameters passed are
the game number to save (1-5), the CPU memory map (including memory
usage flags) and 2 optional buffers containing anything else which
needs to be saved. For this particular hardware platform,
the memory of CPU #2 does not need to be saved since all of its
RAM is represented in CPU #1's memory map as shared areas.
The only info of CPU #2 which needs to be saved is the current CPU
state (register values). The load/save function looks at the
flag map of the memory map and only saves sections containing RAM
and custom functions since the ROM areas do not change. It
then compresses the RAM and areas containing custom functions by
combining repeating sequences of zeros. This form of compression
may seem very simplistic, but it is able to compress the 3 64K memory
maps of Galaga & its registers to a file size of about 5K.
As shown below, Mappy has just a few variables which
need to be saved. The sound chip info is also saved, but probably
does not need to be. When a game is restored, the screen is
forced to repaint itself and the hiscore flag is set indicating
that the emulator should not attempt to reload the hiscore values
since they have already been loaded in the process of restoring
the machine state. I have left extra space in the "temp"
buffer for future expansion. This way, if there are other
games which have similar hardware, but require other variables to
be added, I can modify this function without invalidating the existing
saved-game files.
Portability Issues
As mentioned by Brad Oliver, making the code portable across multiple
platforms complicates things a bit. Besides endian issues,
there is also the problem of variations in compiler behavior with
respect to structure packing/padding. A convention of using
either big or little endian and specific code to extract variables
from structures becomes necessary to ensure compatibility across
various platforms.
The save/load functions can be called at any moment
during gameplay because they save/restore the entire machine state;
it is not necessary to wait for the self-test to finish. In
fact, the save/load state can be used as a convenient way to avoid
the load self-test of certain games. The information I've
provided in this article should be sufficient for any emulator developer
to add this feature to his emulator. If there is anything
which needs clarification, write me a note and I will add it.
void MappyLoadSave(int iGame, BOOL bLoad)
{
char cTemp[256];
if (bLoad)
{
EMULoadState(iGame, &mem_map1[MEM_ROMRAM],
(char *)®s1, sizeof(regs1), (char *)&cTemp, 256);
cIRQEnable1 = cTemp[0];
cIRQEnable2 = cTemp[1];
cCPU2Enable = cTemp[2];
cMappyScroll = cTemp[3];
memcpy(cVolume, &cTemp[4], 8);
memcpy(iFreq, &cTemp[12], 32);
memcpy(pWave, &cTemp[44], 32);
memcpy(iPosition, &cTemp[76],
32);
memcpy(®s2, &cTemp[108],
sizeof(regs2));
memset(cDirtyChar, 1, 2047);
lDirtyRect = -1;
bHiLoaded = TRUE;
}
else
{
cTemp[0] = cIRQEnable1;
cTemp[1] = cIRQEnable2;
cTemp[2] = cCPU2Enable;
cTemp[3] = cMappyScroll;
memcpy(&cTemp[4], cVolume, 8);
memcpy(&cTemp[12], iFreq, 32);
memcpy(&cTemp[44], pWave, 32);
memcpy(&cTemp[76], iPosition,
32);
memcpy(&cTemp[108], ®s2,
sizeof(regs2));
EMUSaveState(iGame, &mem_map1[MEM_ROMRAM],
(char *)®s1, sizeof(regs1), (char *)&cTemp, 256);
}
}
Webdesign
by Deep Magic Studios
- HanaHo Games, Inc. Copyright © 2002 |