1312 lines
30 KiB
C
1312 lines
30 KiB
C
/*
|
|
* UAE - The Un*x Amiga Emulator
|
|
*
|
|
* Save/restore emulator state
|
|
*
|
|
* (c) 1999-2001 Toni Wilen
|
|
*
|
|
* see below for ASF-structure
|
|
*/
|
|
|
|
/* Features:
|
|
*
|
|
* - full CPU state (68000/68010/68020)
|
|
* - FPU
|
|
* - full CIA-A and CIA-B state (with all internal registers)
|
|
* - saves all custom registers and audio internal state.
|
|
* - Chip, Bogo, Fast, Z3 and Picasso96 RAM supported
|
|
* - disk drive type, imagefile, track and motor state
|
|
* - Kickstart ROM version, address and size is saved. This data is not used during restore yet.
|
|
* - Action Replay state is saved
|
|
*/
|
|
|
|
/* Notes:
|
|
*
|
|
* - blitter state is not saved, blitter is forced to finish immediately if it
|
|
* was active
|
|
* - disk DMA state is completely saved
|
|
* - does not ask for statefile name and description. Currently uses DF0's disk
|
|
* image name (".adf" is replaced with ".asf")
|
|
* - only Amiga state is restored, harddisk support, autoconfig, expansion boards etc..
|
|
* are not saved/restored (and probably never will).
|
|
* - use this for saving games that can't be saved to disk
|
|
*/
|
|
|
|
/* Usage :
|
|
*
|
|
* save:
|
|
*
|
|
* set savestate_state = STATE_DOSAVE, savestate_filename = "..."
|
|
*
|
|
* restore:
|
|
*
|
|
* set savestate_state = STATE_DORESTORE, savestate_filename = "..."
|
|
*
|
|
*/
|
|
|
|
#include "sysconfig.h"
|
|
#include "sysdeps.h"
|
|
|
|
#include "options.h"
|
|
#include "memory.h"
|
|
#include "zfile.h"
|
|
#include "ar.h"
|
|
#include "filesys.h"
|
|
#include "custom.h"
|
|
#include "newcpu.h"
|
|
#include "savestate.h"
|
|
#include "uae.h"
|
|
#include "gui.h"
|
|
#include "audio.h"
|
|
#include "version.h"
|
|
|
|
#ifndef _WIN32
|
|
#define console_out printf
|
|
#endif
|
|
|
|
#ifdef SAVESTATE
|
|
|
|
int savestate_state = 0;
|
|
|
|
#define MAX_STATERECORDS 1024 /* must be power of 2 */
|
|
struct staterecord {
|
|
uae_u8 *start;
|
|
uae_u8 *end;
|
|
uae_u8 *next;
|
|
uae_u8 *cpu;
|
|
};
|
|
static int replaycounter;
|
|
static int replaylastreloaded = -1;
|
|
static int frameextra;
|
|
|
|
struct zfile *savestate_file;
|
|
static uae_u8 *replaybuffer, *replaybufferend;
|
|
static int savestate_docompress, savestate_specialdump;
|
|
static int replaybuffersize;
|
|
|
|
char savestate_fname[MAX_DPATH];
|
|
static struct staterecord staterecords[MAX_STATERECORDS];
|
|
|
|
#endif
|
|
|
|
/* functions for reading/writing bytes, shorts and longs in big-endian
|
|
* format independent of host machine's endianess */
|
|
|
|
void save_u32_func (uae_u8 **dstp, uae_u32 v)
|
|
{
|
|
uae_u8 *dst = *dstp;
|
|
*dst++ = (uae_u8)(v >> 24);
|
|
*dst++ = (uae_u8)(v >> 16);
|
|
*dst++ = (uae_u8)(v >> 8);
|
|
*dst++ = (uae_u8)(v >> 0);
|
|
*dstp = dst;
|
|
}
|
|
void save_u64_func (uae_u8 **dstp, uae_u64 v)
|
|
{
|
|
save_u32_func (dstp, (uae_u32)(v >> 32));
|
|
save_u32_func (dstp, (uae_u32)v);
|
|
}
|
|
void save_u16_func (uae_u8 **dstp, uae_u16 v)
|
|
{
|
|
uae_u8 *dst = *dstp;
|
|
*dst++ = (uae_u8)(v >> 8);
|
|
*dst++ = (uae_u8)(v >> 0);
|
|
*dstp = dst;
|
|
}
|
|
void save_u8_func (uae_u8 **dstp, uae_u8 v)
|
|
{
|
|
uae_u8 *dst = *dstp;
|
|
*dst++ = v;
|
|
*dstp = dst;
|
|
}
|
|
void save_string_func (uae_u8 **dstp, const char *from)
|
|
{
|
|
uae_u8 *dst = *dstp;
|
|
while (from && *from)
|
|
*dst++ = *from++;
|
|
*dst++ = 0;
|
|
*dstp = dst;
|
|
}
|
|
|
|
uae_u32 restore_u32_func (const uae_u8 **dstp)
|
|
{
|
|
uae_u32 v;
|
|
const uae_u8 *dst = *dstp;
|
|
v = (dst[0] << 24) | (dst[1] << 16) | (dst[2] << 8) | (dst[3]);
|
|
*dstp = dst + 4;
|
|
return v;
|
|
}
|
|
uae_u64 restore_u64_func (const uae_u8 **dstp)
|
|
{
|
|
uae_u64 v;
|
|
|
|
v = restore_u32_func (dstp);
|
|
v <<= 32;
|
|
v |= restore_u32_func (dstp);
|
|
return v;
|
|
}
|
|
uae_u16 restore_u16_func (const uae_u8 **dstp)
|
|
{
|
|
uae_u16 v;
|
|
const uae_u8 *dst = *dstp;
|
|
v=(dst[0] << 8) | (dst[1]);
|
|
*dstp = dst + 2;
|
|
return v;
|
|
}
|
|
uae_u8 restore_u8_func (const uae_u8 **dstp)
|
|
{
|
|
uae_u8 v;
|
|
const uae_u8 *dst = *dstp;
|
|
v = dst[0];
|
|
*dstp = dst + 1;
|
|
return v;
|
|
}
|
|
char *restore_string_func (const uae_u8 **dstp)
|
|
{
|
|
int len;
|
|
uae_u8 v;
|
|
const uae_u8 *dst = *dstp;
|
|
char *top, *to;
|
|
len = strlen ((const char *)dst) + 1;
|
|
top = to = malloc (len);
|
|
do {
|
|
v = *dst++;
|
|
*top++ = v;
|
|
} while(v);
|
|
*dstp = dst;
|
|
return to;
|
|
}
|
|
|
|
#ifdef SAVESTATE
|
|
|
|
/* read and write IFF-style hunks */
|
|
|
|
static void save_chunk (struct zfile *f, uae_u8 *chunk, size_t len, const char *name, int compress)
|
|
{
|
|
uae_u8 tmp[8], *dst;
|
|
uae_u8 zero[4]= { 0, 0, 0, 0 };
|
|
uae_u32 flags;
|
|
size_t pos;
|
|
size_t chunklen, len2;
|
|
|
|
if (!chunk)
|
|
return;
|
|
|
|
if (compress < 0) {
|
|
zfile_fwrite (chunk, 1, len, f);
|
|
return;
|
|
}
|
|
|
|
/* chunk name */
|
|
zfile_fwrite (name, 1, 4, f);
|
|
pos = zfile_ftell (f);
|
|
/* chunk size */
|
|
dst = &tmp[0];
|
|
chunklen = len + 4 + 4 + 4;
|
|
save_u32 (chunklen);
|
|
zfile_fwrite (&tmp[0], 1, 4, f);
|
|
/* chunk flags */
|
|
flags = 0;
|
|
dst = &tmp[0];
|
|
save_u32 (flags | compress);
|
|
zfile_fwrite (&tmp[0], 1, 4, f);
|
|
/* chunk data */
|
|
if (compress) {
|
|
int tmplen = len;
|
|
dst = &tmp[0];
|
|
save_u32 (len);
|
|
zfile_fwrite (&tmp[0], 1, 4, f);
|
|
len = zfile_zcompress (f, chunk, len);
|
|
if (len > 0) {
|
|
zfile_fseek (f, pos, SEEK_SET);
|
|
dst = &tmp[0];
|
|
save_u32 (len + 4 + 4 + 4 + 4);
|
|
zfile_fwrite (&tmp[0], 1, 4, f);
|
|
zfile_fseek (f, 0, SEEK_END);
|
|
} else {
|
|
len = tmplen;
|
|
compress = 0;
|
|
zfile_fseek (f, -8, SEEK_CUR);
|
|
dst = &tmp[0];
|
|
save_u32 (flags);
|
|
zfile_fwrite (&tmp[0], 1, 4, f);
|
|
}
|
|
}
|
|
if (!compress)
|
|
zfile_fwrite (chunk, 1, len, f);
|
|
/* alignment */
|
|
len2 = 4 - (len & 3);
|
|
if (len2)
|
|
zfile_fwrite (zero, 1, len2, f);
|
|
|
|
write_log ("Chunk '%s' chunk size %ld (%ld)\n", name, chunklen, len);
|
|
}
|
|
|
|
static uae_u8 *restore_chunk (struct zfile *f, char *name, size_t *len, size_t *totallen, size_t *filepos)
|
|
{
|
|
uae_u8 tmp[4], dummy[4], *mem;
|
|
const uae_u8 *src;
|
|
uae_u32 flags;
|
|
size_t len2;
|
|
|
|
*totallen = 0;
|
|
/* chunk name */
|
|
zfile_fread (name, 1, 4, f);
|
|
name[4] = 0;
|
|
/* chunk size */
|
|
zfile_fread (tmp, 1, 4, f);
|
|
src = tmp;
|
|
len2 = restore_u32 ();
|
|
if (len2 < 12) {
|
|
*len = 0;
|
|
return 0;
|
|
}
|
|
len2 -= 4 + 4 + 4;
|
|
*len = len2;
|
|
|
|
/* chunk flags */
|
|
zfile_fread (tmp, 1, 4, f);
|
|
src = tmp;
|
|
flags = restore_u32 ();
|
|
*totallen = *len;
|
|
if (flags & 1) {
|
|
zfile_fread (tmp, 1, 4, f);
|
|
src = tmp;
|
|
*totallen = restore_u32();
|
|
*filepos = zfile_ftell (f) - 4 - 4 - 4;
|
|
len2 -= 4;
|
|
} else {
|
|
*filepos = zfile_ftell (f) - 4 - 4;
|
|
}
|
|
/* chunk data. RAM contents will be loaded during the reset phase,
|
|
no need to malloc multiple megabytes here. */
|
|
if (strcmp (name, "CRAM") != 0
|
|
&& strcmp (name, "BRAM") != 0
|
|
&& strcmp (name, "FRAM") != 0
|
|
&& strcmp (name, "ZRAM") != 0
|
|
&& strcmp (name, "PRAM") != 0)
|
|
{
|
|
/* without zeros at the end old state files may not work */
|
|
mem = calloc (1, len2 + 32);
|
|
zfile_fread (mem, 1, len2, f);
|
|
} else {
|
|
mem = 0;
|
|
zfile_fseek (f, len2, SEEK_CUR);
|
|
}
|
|
|
|
/* alignment */
|
|
len2 = 4 - (len2 & 3);
|
|
if (len2)
|
|
zfile_fread (dummy, 1, len2, f);
|
|
return mem;
|
|
}
|
|
|
|
void restore_ram (size_t filepos, uae_u8 *memory)
|
|
{
|
|
uae_u8 tmp[8];
|
|
const uae_u8 *src = tmp;
|
|
unsigned int size, fullsize;
|
|
uae_u32 flags;
|
|
|
|
zfile_fseek (savestate_file, filepos, SEEK_SET);
|
|
zfile_fread (tmp, 1, sizeof (tmp), savestate_file);
|
|
size = restore_u32 ();
|
|
flags = restore_u32 ();
|
|
size -= 4 + 4 + 4;
|
|
if (flags & 1) {
|
|
zfile_fread (tmp, 1, 4, savestate_file);
|
|
src = tmp;
|
|
fullsize = restore_u32 ();
|
|
size -= 4;
|
|
zfile_zuncompress (memory, fullsize, savestate_file, size);
|
|
} else {
|
|
zfile_fread (memory, 1, size, savestate_file);
|
|
}
|
|
}
|
|
|
|
static void restore_header (const uae_u8 *src)
|
|
{
|
|
char *emuname, *emuversion, *description;
|
|
|
|
restore_u32();
|
|
emuname = restore_string ();
|
|
emuversion = restore_string ();
|
|
description = restore_string ();
|
|
write_log ("Saved with: '%s %s', description: '%s'\n",
|
|
emuname, emuversion, description);
|
|
free (description);
|
|
free (emuversion);
|
|
free (emuname);
|
|
}
|
|
|
|
/* restore all subsystems */
|
|
|
|
void restore_state (const char *filename)
|
|
{
|
|
struct zfile *f;
|
|
uae_u8 *chunk;
|
|
const uae_u8 *end;
|
|
char name[5], prevchunk[5];
|
|
size_t len, totallen;
|
|
size_t filepos;
|
|
|
|
chunk = 0;
|
|
f = zfile_fopen (filename, "rb");
|
|
if (!f)
|
|
goto error;
|
|
savestate_init ();
|
|
|
|
chunk = restore_chunk (f, name, &len, &totallen, &filepos);
|
|
if (!chunk || memcmp (name, "ASF ", 4)) {
|
|
gui_message ("Cannot restore state from '%s'.\n It is not an AmigaStateFile.\n", filename);
|
|
goto error;
|
|
}
|
|
savestate_file = f;
|
|
restore_header (chunk);
|
|
free (chunk);
|
|
changed_prefs.bogomem_size = 0;
|
|
changed_prefs.chipmem_size = 0;
|
|
changed_prefs.fastmem_size = 0;
|
|
savestate_state = STATE_RESTORE;
|
|
prevchunk[0] = 0;
|
|
for (;;) {
|
|
chunk = restore_chunk (f, name, &len, &totallen, &filepos);
|
|
end = chunk;
|
|
if (!strcmp (name, prevchunk))
|
|
break;
|
|
strcpy (prevchunk, name);
|
|
write_log ("Chunk '%s' size %ld (%ld)\n", name, len, totallen);
|
|
if (!strcmp (name, "END "))
|
|
break;
|
|
if (!strcmp (name, "CRAM")) {
|
|
restore_cram (totallen, filepos);
|
|
continue;
|
|
} else if (!strcmp (name, "BRAM")) {
|
|
restore_bram (totallen, filepos);
|
|
continue;
|
|
#ifdef AUTOCONFIG
|
|
} else if (!strcmp (name, "FRAM")) {
|
|
restore_fram (totallen, filepos);
|
|
continue;
|
|
} else if (!strcmp (name, "ZRAM")) {
|
|
restore_zram (totallen, filepos);
|
|
continue;
|
|
#endif
|
|
#ifdef PICASSO96
|
|
} else if (!strcmp (name, "PRAM")) {
|
|
restore_pram (totallen, filepos);
|
|
continue;
|
|
#endif
|
|
} else if (!strcmp (name, "CPU "))
|
|
end = restore_cpu (chunk);
|
|
#ifdef FPUEMU
|
|
else if (!strcmp (name, "FPU "))
|
|
end = restore_fpu (chunk);
|
|
#endif
|
|
else if (!strcmp (name, "AGAC"))
|
|
end = restore_custom_agacolors (chunk);
|
|
else if (!strcmp (name, "SPR0"))
|
|
end = restore_custom_sprite (0, chunk);
|
|
else if (!strcmp (name, "SPR1"))
|
|
end = restore_custom_sprite (1, chunk);
|
|
else if (!strcmp (name, "SPR2"))
|
|
end = restore_custom_sprite (2, chunk);
|
|
else if (!strcmp (name, "SPR3"))
|
|
end = restore_custom_sprite (3, chunk);
|
|
else if (!strcmp (name, "SPR4"))
|
|
end = restore_custom_sprite (4, chunk);
|
|
else if (!strcmp (name, "SPR5"))
|
|
end = restore_custom_sprite (5, chunk);
|
|
else if (!strcmp (name, "SPR6"))
|
|
end = restore_custom_sprite (6, chunk);
|
|
else if (!strcmp (name, "SPR7"))
|
|
end = restore_custom_sprite (7, chunk);
|
|
else if (!strcmp (name, "CIAA"))
|
|
end = restore_cia (0, chunk);
|
|
else if (!strcmp (name, "CIAB"))
|
|
end = restore_cia (1, chunk);
|
|
else if (!strcmp (name, "CHIP"))
|
|
end = restore_custom (chunk);
|
|
else if (!strcmp (name, "AUD0"))
|
|
end = restore_audio (0, chunk);
|
|
else if (!strcmp (name, "AUD1"))
|
|
end = restore_audio (1, chunk);
|
|
else if (!strcmp (name, "AUD2"))
|
|
end = restore_audio (2, chunk);
|
|
else if (!strcmp (name, "AUD3"))
|
|
end = restore_audio (3, chunk);
|
|
else if (!strcmp (name, "BLIT"))
|
|
end = restore_blitter (chunk);
|
|
else if (!strcmp (name, "DISK"))
|
|
end = restore_floppy (chunk);
|
|
else if (!strcmp (name, "DSK0"))
|
|
end = restore_disk (0, chunk);
|
|
else if (!strcmp (name, "DSK1"))
|
|
end = restore_disk (1, chunk);
|
|
else if (!strcmp (name, "DSK2"))
|
|
end = restore_disk (2, chunk);
|
|
else if (!strcmp (name, "DSK3"))
|
|
end = restore_disk (3, chunk);
|
|
else if (!strcmp (name, "KEYB"))
|
|
end = restore_keyboard (chunk);
|
|
#ifdef AUTOCONFIG
|
|
else if (!strcmp (name, "EXPA"))
|
|
end = restore_expansion (chunk);
|
|
#endif
|
|
else if (!strcmp (name, "ROM "))
|
|
end = restore_rom (chunk);
|
|
#ifdef PICASSO96
|
|
else if (!strcmp (name, "P96 "))
|
|
end = restore_p96 (chunk);
|
|
#endif
|
|
#ifdef ACTION_REPLAY
|
|
else if (!strcmp (name, "ACTR"))
|
|
end = restore_action_replay (chunk);
|
|
#endif
|
|
#if 0
|
|
#ifdef FILESYS
|
|
else if (!strcmp (name, "FSYS"))
|
|
end = restore_filesys (chunk);
|
|
#endif
|
|
#endif
|
|
else
|
|
write_log ("unknown chunk '%s' size %ld bytes\n", name, len);
|
|
if (len != (size_t)(end - chunk))
|
|
write_log ("Chunk '%s' total size %ld bytes but read %d bytes!\n",
|
|
name, len, end - chunk);
|
|
free (chunk);
|
|
}
|
|
restore_blitter_finish ();
|
|
return;
|
|
|
|
error:
|
|
savestate_state = 0;
|
|
savestate_file = 0;
|
|
if (chunk)
|
|
free (chunk);
|
|
if (f)
|
|
zfile_fclose (f);
|
|
}
|
|
|
|
void savestate_restore_finish (void)
|
|
{
|
|
if (savestate_state != STATE_RESTORE && savestate_state != STATE_REWIND)
|
|
return;
|
|
zfile_fclose (savestate_file);
|
|
savestate_file = 0;
|
|
savestate_state = 0;
|
|
restore_cpu_finish ();
|
|
}
|
|
|
|
/* 1=compressed,2=not compressed,3=ram dump,4=audio dump */
|
|
void savestate_initsave (const char *filename, int mode)
|
|
{
|
|
strcpy (savestate_fname, filename);
|
|
savestate_docompress = (mode == 1) ? 1 : 0;
|
|
savestate_specialdump = (mode == 3) ? 1 : (mode == 4) ? 2 : 0;
|
|
}
|
|
|
|
static void save_rams (struct zfile *f, int comp)
|
|
{
|
|
uae_u8 *dst;
|
|
uae_u32 len;
|
|
|
|
dst = save_cram (&len);
|
|
save_chunk (f, dst, len, "CRAM", comp);
|
|
dst = save_bram (&len);
|
|
save_chunk (f, dst, len, "BRAM", comp);
|
|
#ifdef AUTOCONFIG
|
|
dst = save_fram (&len);
|
|
save_chunk (f, dst, len, "FRAM", comp);
|
|
dst = save_zram (&len);
|
|
save_chunk (f, dst, len, "ZRAM", comp);
|
|
#endif
|
|
#ifdef PICASSO96
|
|
dst = save_pram (&len);
|
|
save_chunk (f, dst, len, "PRAM", comp);
|
|
dst = save_p96 (&len, 0);
|
|
save_chunk (f, dst, len, "P96 ", comp);
|
|
#endif
|
|
}
|
|
|
|
/* Save all subsystems */
|
|
|
|
void save_state (const char *filename, const char *description)
|
|
{
|
|
uae_u8 header[1000];
|
|
char tmp[100];
|
|
uae_u8 *dst;
|
|
struct zfile *f;
|
|
uae_u32 len;
|
|
unsigned int i;
|
|
char name[5];
|
|
int comp = savestate_docompress;
|
|
|
|
#ifdef FILESYS
|
|
if (nr_units (currprefs.mountinfo)) {
|
|
gui_message ("WARNING: State saves do not support HD emulation");
|
|
}
|
|
#endif
|
|
|
|
custom_prepare_savestate ();
|
|
|
|
f = zfile_fopen (filename, "w+b");
|
|
if (!f)
|
|
return;
|
|
#if 0
|
|
if (savestate_specialdump) {
|
|
size_t pos;
|
|
if (savestate_specialdump == 2)
|
|
write_wavheader (f, 0, 22050);
|
|
pos = zfile_ftell(f);
|
|
save_rams (f, -1);
|
|
if (savestate_specialdump == 2) {
|
|
int len, len2, i;
|
|
uae_u8 *tmp;
|
|
len = zfile_ftell(f) - pos;
|
|
tmp = malloc (len);
|
|
zfile_fseek(f, pos, SEEK_SET);
|
|
len2 = zfile_fread(tmp, 1, len, f);
|
|
for (i = 0; i < len2; i++)
|
|
tmp[i] += 0x80;
|
|
write_wavheader (f, len, 22050);
|
|
zfile_fwrite (tmp, len2, 1, f);
|
|
free (tmp);
|
|
}
|
|
zfile_fclose (f);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
dst = header;
|
|
save_u32 (0);
|
|
save_string ("UAE");
|
|
sprintf (tmp, "%d.%d.%d", UAEMAJOR, UAEMINOR, UAESUBREV);
|
|
save_string (tmp);
|
|
save_string (description);
|
|
save_chunk (f, header, dst-header, "ASF ", 0);
|
|
|
|
dst = save_cpu (&len, 0);
|
|
save_chunk (f, dst, len, "CPU ", 0);
|
|
free (dst);
|
|
|
|
#ifdef FPUEMU
|
|
dst = save_fpu (&len,0 );
|
|
save_chunk (f, dst, len, "FPU ", 0);
|
|
free (dst);
|
|
#endif
|
|
|
|
strcpy (name, "DSKx");
|
|
for (i = 0; i < 4; i++) {
|
|
dst = save_disk (i, &len, 0);
|
|
if (dst) {
|
|
name[3] = i + '0';
|
|
save_chunk (f, dst, len, name, 0);
|
|
free (dst);
|
|
}
|
|
}
|
|
dst = save_floppy (&len, 0);
|
|
save_chunk (f, dst, len, "DISK", 0);
|
|
free (dst);
|
|
|
|
dst = save_blitter (&len, 0);
|
|
save_chunk (f, dst, len, "BLIT", 0);
|
|
free (dst);
|
|
|
|
dst = save_custom (&len, 0, 0);
|
|
save_chunk (f, dst, len, "CHIP", 0);
|
|
free (dst);
|
|
|
|
dst = save_custom_agacolors (&len, 0);
|
|
save_chunk (f, dst, len, "AGAC", 0);
|
|
free (dst);
|
|
|
|
strcpy (name, "SPRx");
|
|
for (i = 0; i < 8; i++) {
|
|
dst = save_custom_sprite (i, &len, 0);
|
|
name[3] = i + '0';
|
|
save_chunk (f, dst, len, name, 0);
|
|
free (dst);
|
|
}
|
|
|
|
strcpy (name, "AUDx");
|
|
for (i = 0; i < 4; i++) {
|
|
dst = save_audio (i, &len, 0);
|
|
name[3] = i + '0';
|
|
save_chunk (f, dst, len, name, 0);
|
|
free (dst);
|
|
}
|
|
|
|
dst = save_cia (0, &len, 0);
|
|
save_chunk (f, dst, len, "CIAA", 0);
|
|
free (dst);
|
|
|
|
dst = save_cia (1, &len, 0);
|
|
save_chunk (f, dst, len, "CIAB", 0);
|
|
free (dst);
|
|
|
|
dst = save_keyboard (&len);
|
|
save_chunk (f, dst, len, "KEYB", 0);
|
|
free (dst);
|
|
|
|
#ifdef AUTOCONFIG
|
|
dst = save_expansion (&len, 0);
|
|
save_chunk (f, dst, len, "EXPA", 0);
|
|
#endif
|
|
save_rams (f, comp);
|
|
|
|
dst = save_rom (1, &len, 0);
|
|
do {
|
|
if (!dst)
|
|
break;
|
|
save_chunk (f, dst, len, "ROM ", 0);
|
|
free (dst);
|
|
} while ((dst = save_rom (0, &len, 0)));
|
|
|
|
#ifdef ACTION_REPLAY
|
|
dst = save_action_replay (&len, 0);
|
|
save_chunk (f, dst, len, "ACTR", 0);
|
|
#endif
|
|
#if 0
|
|
#ifdef FILESYS
|
|
for (i = 0; i < nr_units (currprefs.mountinfo); i++) {
|
|
dst = save_filesys (i, &len);
|
|
if (dst) {
|
|
save_chunk (f, dst, len, "FSYS", 0);
|
|
free (dst);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
zfile_fwrite ("END ", 1, 4, f);
|
|
zfile_fwrite ("\0\0\0\08", 1, 4, f);
|
|
write_log ("Save of '%s' complete\n", filename);
|
|
zfile_fclose (f);
|
|
savestate_state = 0;
|
|
}
|
|
|
|
void savestate_quick (int slot, int save)
|
|
{
|
|
int i, len = strlen (savestate_fname);
|
|
i = len - 1;
|
|
while (i >= 0 && savestate_fname[i] != '_')
|
|
i--;
|
|
if (i < len - 6 || i <= 0) { /* "_?.uss" */
|
|
i = len - 1;
|
|
while (i >= 0 && savestate_fname[i] != '.')
|
|
i--;
|
|
if (i <= 0) {
|
|
write_log ("savestate name skipped '%s'\n", savestate_fname);
|
|
return;
|
|
}
|
|
}
|
|
strcpy (savestate_fname + i, ".uss");
|
|
if (slot > 0)
|
|
sprintf (savestate_fname + i, "_%d.uss", slot);
|
|
if (save) {
|
|
write_log ("saving '%s'\n", savestate_fname);
|
|
savestate_docompress = 1;
|
|
save_state (savestate_fname, "");
|
|
} else {
|
|
if (!zfile_exists (savestate_fname)) {
|
|
write_log ("staterestore, file '%s' not found\n", savestate_fname);
|
|
return;
|
|
}
|
|
savestate_state = STATE_DORESTORE;
|
|
write_log ("staterestore starting '%s'\n", savestate_fname);
|
|
}
|
|
}
|
|
|
|
static struct staterecord *canrewind (int pos)
|
|
{
|
|
int i;
|
|
struct staterecord *st;
|
|
|
|
if (!replaybuffer)
|
|
return 0;
|
|
i = replaycounter;
|
|
st = &staterecords[i];
|
|
if (st->start)
|
|
return st;
|
|
return 0;
|
|
}
|
|
|
|
int savestate_dorewind (int pos)
|
|
{
|
|
if (canrewind (pos)) {
|
|
savestate_state = STATE_DOREWIND;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void savestate_listrewind (void)
|
|
{
|
|
int i = replaycounter;
|
|
int cnt;
|
|
const uae_u8 *p;
|
|
uae_u32 pc;
|
|
|
|
cnt = 1;
|
|
for (;;) {
|
|
struct staterecord *st;
|
|
st = &staterecords[i];
|
|
if (!st->start)
|
|
break;
|
|
p = st->cpu + 17 * 4;
|
|
pc = restore_u32_func (&p);
|
|
console_out ("%d: PC=%08X %c\n", cnt, pc, regs.pc == pc ? '*' : ' ');
|
|
cnt++;
|
|
i--;
|
|
if (i < 0)
|
|
i += MAX_STATERECORDS;
|
|
}
|
|
}
|
|
|
|
void savestate_rewind (void)
|
|
{
|
|
unsigned int len;
|
|
unsigned int i, dummy;
|
|
const uae_u8 *p, *p2;
|
|
struct staterecord *st;
|
|
|
|
st = canrewind (1);
|
|
if (!st)
|
|
return;
|
|
frameextra = timeframes % currprefs.statecapturerate;
|
|
p = st->start;
|
|
p2 = st->end;
|
|
write_log ("rewinding from %d\n", replaycounter);
|
|
p = restore_cpu (p);
|
|
#ifdef FPUEMU
|
|
if (restore_u32_func (&p))
|
|
p = restore_fpu (p);
|
|
#endif
|
|
for (i = 0; i < 4; i++) {
|
|
p = restore_disk (i, p);
|
|
}
|
|
p = restore_floppy (p);
|
|
p = restore_custom (p);
|
|
p = restore_blitter (p);
|
|
p = restore_custom_agacolors (p);
|
|
for (i = 0; i < 8; i++) {
|
|
p = restore_custom_sprite (i, p);
|
|
}
|
|
for (i = 0; i < 4; i++) {
|
|
p = restore_audio (i, p);
|
|
}
|
|
p = restore_cia (0, p);
|
|
p = restore_cia (1, p);
|
|
#ifdef AUTOCONFIG
|
|
p = restore_expansion (p);
|
|
#endif
|
|
len = restore_u32_func (&p);
|
|
memcpy (chipmemory, p, currprefs.chipmem_size > len ? len : currprefs.chipmem_size);
|
|
p += len;
|
|
len = restore_u32_func (&p);
|
|
memcpy (save_bram (&dummy), p, currprefs.bogomem_size > len ? len : currprefs.bogomem_size);
|
|
p += len;
|
|
#ifdef AUTOCONFIG
|
|
len = restore_u32_func (&p);
|
|
memcpy (save_fram (&dummy), p, currprefs.fastmem_size > len ? len : currprefs.fastmem_size);
|
|
p += len;
|
|
len = restore_u32_func (&p);
|
|
memcpy (save_zram (&dummy), p, currprefs.z3fastmem_size > len ? len : currprefs.z3fastmem_size);
|
|
p += len;
|
|
#endif
|
|
#ifdef ACTION_REPLAY
|
|
if (restore_u32_func (&p))
|
|
p = restore_action_replay (p);
|
|
#endif
|
|
p += 4;
|
|
if (p != p2) {
|
|
gui_message ("reload failure, address mismatch %p != %p", p, p2);
|
|
uae_reset (0);
|
|
return;
|
|
}
|
|
st->start = st->next = 0;
|
|
replaycounter--;
|
|
if (replaycounter < 0)
|
|
replaycounter += MAX_STATERECORDS;
|
|
}
|
|
|
|
#define BS 10000
|
|
|
|
static int bufcheck (uae_u8 **pp, int len)
|
|
{
|
|
uae_u8 *p = *pp;
|
|
if (p + len + BS >= replaybuffer + replaybuffersize) {
|
|
//write_log ("capture buffer wrap-around\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void savestate_capture (int force)
|
|
{
|
|
uae_u8 *p, *p2, *p3, *dst;
|
|
uae_u32 len;
|
|
int i, tlen, retrycnt;
|
|
struct staterecord *st, *stn;
|
|
|
|
#ifdef FILESYS
|
|
if (nr_units (currprefs.mountinfo))
|
|
return;
|
|
#endif
|
|
if (!replaybuffer)
|
|
return;
|
|
if (!force && (!currprefs.statecapture || !currprefs.statecapturerate || ((timeframes + frameextra) % currprefs.statecapturerate)))
|
|
return;
|
|
|
|
retrycnt = 0;
|
|
retry2:
|
|
st = &staterecords[replaycounter];
|
|
if (st->next == 0) {
|
|
replaycounter = 0;
|
|
st = &staterecords[replaycounter];
|
|
st->next = replaybuffer;
|
|
}
|
|
stn = &staterecords[(replaycounter + 1) & (MAX_STATERECORDS - 1)];
|
|
p = p2 = st->next;
|
|
tlen = 0;
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
stn->cpu = p;
|
|
save_cpu (&len, p);
|
|
tlen += len;
|
|
p += len;
|
|
#ifdef FPUEMU
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
p3 = p;
|
|
save_u32_func (&p, 0);
|
|
tlen += 4;
|
|
if (save_fpu (&len, p)) {
|
|
save_u32_func (&p3, 1);
|
|
tlen += len;
|
|
p += len;
|
|
}
|
|
#endif
|
|
for (i = 0; i < 4; i++) {
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_disk (i, &len, p);
|
|
tlen += len;
|
|
p += len;
|
|
}
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_floppy (&len, p);
|
|
tlen += len;
|
|
p += len;
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_custom (&len, p, 0);
|
|
tlen += len;
|
|
p += len;
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_blitter (&len, p);
|
|
tlen += len;
|
|
p += len;
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_custom_agacolors (&len, p);
|
|
tlen += len;
|
|
p += len;
|
|
for (i = 0; i < 8; i++) {
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_custom_sprite (i, &len, p);
|
|
tlen += len;
|
|
p += len;
|
|
}
|
|
for (i = 0; i < 4; i++) {
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_audio (i, &len, p);
|
|
tlen += len;
|
|
p += len;
|
|
}
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_cia (0, &len, p);
|
|
tlen += len;
|
|
p += len;
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_cia (1, &len, p);
|
|
tlen += len;
|
|
p += len;
|
|
#ifdef AUTOCONFIG
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
save_expansion (&len, p);
|
|
tlen += len;
|
|
p += len;
|
|
#endif
|
|
dst = save_cram (&len);
|
|
if (bufcheck (&p, len))
|
|
goto retry;
|
|
save_u32_func (&p, len);
|
|
memcpy (p, dst, len);
|
|
tlen += len + 4;
|
|
p += len;
|
|
dst = save_bram (&len);
|
|
if (bufcheck (&p, len))
|
|
goto retry;
|
|
save_u32_func (&p, len);
|
|
memcpy (p, dst, len);
|
|
tlen += len + 4;
|
|
p += len;
|
|
#ifdef AUTOCONFIG
|
|
dst = save_fram (&len);
|
|
if (bufcheck (&p, len))
|
|
goto retry;
|
|
save_u32_func (&p, len);
|
|
memcpy (p, dst, len);
|
|
tlen += len + 4;
|
|
p += len;
|
|
dst = save_zram (&len);
|
|
if (bufcheck (&p, len))
|
|
goto retry;
|
|
save_u32_func (&p, len);
|
|
memcpy (p, dst, len);
|
|
tlen += len + 4;
|
|
p += len;
|
|
#endif
|
|
#ifdef ACTION_REPLAY
|
|
if (bufcheck (&p, 0))
|
|
goto retry;
|
|
p3 = p;
|
|
save_u32_func (&p, 0);
|
|
tlen += 4;
|
|
if (save_action_replay (&len, p)) {
|
|
save_u32_func (&p3, 1);
|
|
tlen += len;
|
|
p += len;
|
|
}
|
|
#endif
|
|
save_u32_func (&p, tlen);
|
|
stn->next = p;
|
|
stn->start = p2;
|
|
stn->end = p;
|
|
replaylastreloaded = -1;
|
|
replaycounter++;
|
|
replaycounter &= (MAX_STATERECORDS - 1);
|
|
i = (replaycounter + 1) & (MAX_STATERECORDS - 1);
|
|
staterecords[i].next = staterecords[i].start = 0;
|
|
i = replaycounter - 1;
|
|
while (i != replaycounter) {
|
|
if (i < 0)
|
|
i += MAX_STATERECORDS;
|
|
st = &staterecords[i];
|
|
if (p2 <= st->start && p >= st->end) {
|
|
st->start = st->next = 0;
|
|
break;
|
|
}
|
|
i--;
|
|
}
|
|
//write_log ("state capture %d (%d bytes)\n", replaycounter, p - p2);
|
|
return;
|
|
retry:
|
|
staterecords[replaycounter].next = replaybuffer;
|
|
retrycnt++;
|
|
if (retrycnt > 1) {
|
|
write_log ("can't save, too small capture buffer\n");
|
|
return;
|
|
}
|
|
goto retry2;
|
|
}
|
|
|
|
void savestate_free (void)
|
|
{
|
|
free (replaybuffer);
|
|
replaybuffer = 0;
|
|
}
|
|
|
|
void savestate_init (void)
|
|
{
|
|
savestate_free ();
|
|
memset (staterecords, 0, sizeof (staterecords));
|
|
replaycounter = 0;
|
|
replaylastreloaded = -1;
|
|
frameextra = 0;
|
|
if (currprefs.statecapture && currprefs.statecapturebuffersize && currprefs.statecapturerate) {
|
|
replaybuffersize = currprefs.statecapturebuffersize;
|
|
replaybuffer = malloc (replaybuffersize);
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
My (Toni Wilen <twilen@arabuusimiehet.com>)
|
|
proposal for Amiga-emulators' state-save format
|
|
|
|
Feel free to comment...
|
|
|
|
This is very similar to IFF-fileformat
|
|
Every hunk must end to 4 byte boundary,
|
|
fill with zero bytes if needed
|
|
|
|
version 0.8
|
|
|
|
HUNK HEADER (beginning of every hunk)
|
|
|
|
hunk name (4 ascii-characters)
|
|
hunk size (including header)
|
|
hunk flags
|
|
|
|
bit 0 = chunk contents are compressed with zlib (maybe RAM chunks only?)
|
|
|
|
HEADER
|
|
|
|
"ASF " (AmigaStateFile)
|
|
|
|
statefile version
|
|
emulator name ("uae", "fellow" etc..)
|
|
emulator version string (example: "0.8.15")
|
|
free user writable comment string
|
|
|
|
CPU
|
|
|
|
"CPU "
|
|
|
|
CPU model 4 (68000,68010 etc..)
|
|
CPU typeflags bit 0=EC-model or not
|
|
D0-D7 8*4=32
|
|
A0-A6 7*4=32
|
|
PC 4
|
|
unused 4
|
|
68000 prefetch (IRC) 2
|
|
68000 prefetch (IR) 2
|
|
USP 4
|
|
ISP 4
|
|
SR/CCR 2
|
|
flags 4 (bit 0=CPU was HALTed)
|
|
|
|
CPU specific registers
|
|
|
|
68000: SR/CCR is last saved register
|
|
68010: save also DFC,SFC and VBR
|
|
68020: all 68010 registers and CAAR,CACR and MSP
|
|
etc..
|
|
|
|
DFC 4 (010+)
|
|
SFC 4 (010+)
|
|
VBR 4 (010+)
|
|
|
|
CAAR 4 (020-030)
|
|
CACR 4 (020+)
|
|
MSP 4 (020+)
|
|
|
|
FPU (only if used)
|
|
|
|
"FPU "
|
|
|
|
FPU model 4 (68881/68882/68040)
|
|
FPU typeflags 4 (keep zero)
|
|
|
|
FP0-FP7 4+4+2 (80 bits)
|
|
FPCR 4
|
|
FPSR 4
|
|
FPIAR 4
|
|
|
|
MMU (when and if MMU is supported in future..)
|
|
|
|
MMU model 4 (68851,68030,68040)
|
|
|
|
// 68040 fields
|
|
|
|
ITT0 4
|
|
ITT1 4
|
|
DTT0 4
|
|
DTT1 4
|
|
URP 4
|
|
SRP 4
|
|
MMUSR 4
|
|
TC 2
|
|
|
|
|
|
CUSTOM CHIPS
|
|
|
|
"CHIP"
|
|
|
|
chipset flags 4 OCS=0,ECSAGNUS=1,ECSDENISE=2,AGA=4
|
|
ECSAGNUS and ECSDENISE can be combined
|
|
|
|
DFF000-DFF1FF 352 (0x120 - 0x17f and 0x0a0 - 0xdf excluded)
|
|
|
|
sprite registers (0x120 - 0x17f) saved with SPRx chunks
|
|
audio registers (0x0a0 - 0xdf) saved with AUDx chunks
|
|
|
|
AGA COLORS
|
|
|
|
"AGAC"
|
|
|
|
AGA color 8 banks * 32 registers *
|
|
registers LONG (XRGB) = 1024
|
|
|
|
SPRITE
|
|
|
|
"SPR0" - "SPR7"
|
|
|
|
|
|
SPRxPT 4
|
|
SPRxPOS 2
|
|
SPRxCTL 2
|
|
SPRxDATA 2
|
|
SPRxDATB 2
|
|
AGA sprite DATA/DATB 3 * 2 * 2
|
|
sprite "armed" status 1
|
|
|
|
sprites maybe armed in non-DMA mode
|
|
use bit 0 only, other bits are reserved
|
|
|
|
|
|
AUDIO
|
|
"AUD0" "AUD1" "AUD2" "AUD3"
|
|
|
|
audio state 1
|
|
machine mode
|
|
AUDxVOL 1
|
|
irq? 1
|
|
data_written? 1
|
|
internal AUDxLEN 2
|
|
AUDxLEN 2
|
|
internal AUDxPER 2
|
|
AUDxPER 2
|
|
internal AUDxLC 4
|
|
AUDxLC 4
|
|
evtime? 4
|
|
|
|
BLITTER
|
|
|
|
"BLIT"
|
|
|
|
internal blitter state
|
|
|
|
flags 4
|
|
bit 0=blitter active
|
|
bit 1=fill carry bit
|
|
internal ahold 4
|
|
internal bhold 4
|
|
internal hsize 2
|
|
internal vsize 2
|
|
|
|
CIA
|
|
|
|
"CIAA" and "CIAB"
|
|
|
|
BFE001-BFEF01 16*1 (CIAA)
|
|
BFD000-BFDF00 16*1 (CIAB)
|
|
|
|
internal registers
|
|
|
|
IRQ mask (ICR) 1 BYTE
|
|
timer latches 2 timers * 2 BYTES (LO/HI)
|
|
latched tod 3 BYTES (LO/MED/HI)
|
|
alarm 3 BYTES (LO/MED/HI)
|
|
flags 1 BYTE
|
|
bit 0=tod latched (read)
|
|
bit 1=tod stopped (write)
|
|
div10 counter 1 BYTE
|
|
|
|
FLOPPY DRIVES
|
|
|
|
"DSK0" "DSK1" "DSK2" "DSK3"
|
|
|
|
drive state
|
|
|
|
drive ID-word 4
|
|
state 1 (bit 0: motor on, bit 1: drive disabled, bit 2: current id bit)
|
|
rw-head track 1
|
|
dskready 1
|
|
id-mode 1 (ID mode bit number 0-31)
|
|
floppy information
|
|
|
|
bits from 4
|
|
beginning of track
|
|
CRC of disk-image 4 (used during restore to check if image
|
|
is correct)
|
|
disk-image null-terminated
|
|
file name
|
|
|
|
INTERNAL FLOPPY CONTROLLER STATUS
|
|
|
|
"DISK"
|
|
|
|
current DMA word 2
|
|
DMA word bit offset 1
|
|
WORDSYNC found 1 (no=0,yes=1)
|
|
hpos of next bit 1
|
|
DSKLENGTH status 0=off,1=written once,2=written twice
|
|
unused 2
|
|
|
|
RAM SPACE
|
|
|
|
"xRAM" (CRAM = chip, BRAM = bogo, FRAM = fast, ZFRAM = Z3)
|
|
|
|
start address 4 ("bank"=chip/slow/fast etc..)
|
|
of RAM "bank"
|
|
RAM "bank" size 4
|
|
RAM flags 4 (bit 0 = zlib compressed)
|
|
RAM "bank" contents
|
|
|
|
ROM SPACE
|
|
|
|
"ROM "
|
|
|
|
ROM start 4
|
|
address
|
|
size of ROM 4
|
|
ROM type 4 KICK=0
|
|
ROM flags 4
|
|
ROM version 2
|
|
ROM revision 2
|
|
ROM CRC 4 see below
|
|
ROM-image ID-string null terminated, see below
|
|
path to rom image
|
|
ROM contents (Not mandatory, use hunk size to check if
|
|
this hunk contains ROM data or not)
|
|
|
|
Kickstart ROM:
|
|
ID-string is "Kickstart x.x"
|
|
ROM version: version in high word and revision in low word
|
|
Kickstart ROM version and revision can be found from ROM start
|
|
+ 12 (version) and +14 (revision)
|
|
|
|
ROM version and CRC is only meant for emulator to automatically
|
|
find correct image from its ROM-directory during state restore.
|
|
|
|
Usually saving ROM contents is not good idea.
|
|
|
|
ACTION REPLAY
|
|
|
|
"ACTR"
|
|
|
|
Model (1,2,3) 4
|
|
path to rom image
|
|
RAM space (depends on model)
|
|
ROM CRC 4
|
|
|
|
END
|
|
hunk "END " ends, remember hunk size 8!
|
|
|
|
|
|
EMULATOR SPECIFIC HUNKS
|
|
|
|
Read only if "emulator name" in header is same as used emulator.
|
|
Maybe useful for configuration?
|
|
|
|
misc:
|
|
|
|
- save only at position 0,0 before triggering VBLANK interrupt
|
|
- all data must be saved in bigendian format
|
|
- should we strip all paths from image file names?
|
|
|
|
*/
|
|
|
|
#endif
|