First cut at save/restore emulator state feature

- This adds to overall UX ... (you can finally finish some very difficult arcade games by strategically leveraging
      this feature)
    - Currently enabled for Android, but not Android-specific
This commit is contained in:
Aaron Culliney 2015-11-22 17:04:46 -10:00
parent 46c286719f
commit b9d6d38b17
14 changed files with 989 additions and 1 deletions

View File

@ -40,6 +40,7 @@ public class Apple2Activity extends Activity {
private final static String TAG = "Apple2Activity";
private final static int MAX_FINGERS = 32;// HACK ...
private final static String SAVE_FILE = "emulator.state";
private static volatile boolean DEBUG_STRICT = false;
private Apple2View mView = null;
@ -94,6 +95,10 @@ public class Apple2Activity extends Activity {
private native void nativeOnKeyUp(int keyCode, int metaState);
private native void nativeSaveState(String path);
private native void nativeLoadState(String path);
public native void nativeEmulationResume();
public native void nativeEmulationPause();
@ -605,4 +610,26 @@ public class Apple2Activity extends Activity {
}).setNegativeButton(R.string.no, null).create();
registerAndShowDialog(rebootDialog);
}
public void maybeSaveRestore() {
nativeEmulationPause();
final String quickSavePath = Apple2DisksMenu.getDataDir(this) + File.separator + SAVE_FILE;
AlertDialog saveRestoreDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.saverestore).setMessage(R.string.saverestore_choice).setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Apple2Activity.this.nativeSaveState(quickSavePath);
Apple2Activity.this.mMainMenu.dismiss();
}
}).setNeutralButton(R.string.restore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Apple2Activity.this.nativeLoadState(quickSavePath);
Apple2Activity.this.mMainMenu.dismiss();
}
}).setNegativeButton(R.string.no, null).create();
registerAndShowDialog(saveRestoreDialog);
}
}

View File

@ -66,6 +66,17 @@ public class Apple2MainMenu {
mainMenu.showDisksMenu();
}
},
SAVE_RESTORE {
@Override public String getTitle(Context ctx) {
return ctx.getResources().getString(R.string.saverestore);
}
@Override public String getSummary(Context ctx) {
return ctx.getResources().getString(R.string.saverestore_summary);
}
@Override public void handleSelection(Apple2MainMenu mainMenu) {
mainMenu.mActivity.maybeSaveRestore();
}
},
REBOOT_EMULATOR {
@Override public String getTitle(Context ctx) {
return ctx.getResources().getString(R.string.reboot);

View File

@ -160,6 +160,11 @@
<string name="reboot_really">Reboot emulator?</string>
<string name="reboot_summary"></string>
<string name="reboot_warning">You will lose unsaved progress</string>
<string name="restore">Quick restore</string>
<string name="save">Quick save</string>
<string name="saverestore">Save &amp; restore…</string>
<string name="saverestore_choice">Save current state, restore previous, or cancel?</string>
<string name="saverestore_summary">Quick save and restore</string>
<string name="skip">Skip&#8594;</string>
<string name="spacer"></string>
<string name="speaker_disabled_title">Speaker disabled</string>

View File

@ -341,6 +341,32 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEjectDisk(JNIEnv *env, jobj
disk6_eject(!driveA);
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSaveState(JNIEnv *env, jobject obj, jstring jPath) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
assert(cpu_isPaused() && "considered dangerous to save state CPU thread is running");
LOG(": (%s)", path);
if (!emulator_saveState(path)) {
LOG("OOPS, could not save emulator state");
}
(*env)->ReleaseStringUTFChars(env, jPath, path);
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, jobject obj, jstring jPath) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
assert(cpu_isPaused() && "considered dangerous to save state CPU thread is running");
LOG(": (%s)", path);
if (!emulator_loadState(path)) {
LOG("OOPS, could not load emulator state");
}
(*env)->ReleaseStringUTFChars(env, jPath, path);
}
// ----------------------------------------------------------------------------
// Constructor

View File

@ -654,6 +654,105 @@ void cpu65_reboot(void) {
cpu65_interrupt(ResetSig);
}
bool cpu65_saveState(StateHelper_s *helper) {
bool saved = false;
int fd = helper->fd;
do {
uint8_t serialized[4] = { 0 };
// save CPU state
serialized[0] = ((cpu65_pc & 0xFF00) >> 8);
serialized[1] = ((cpu65_pc & 0xFF ) >> 0);
if (!helper->save(fd, serialized, sizeof(cpu65_pc))) {
break;
}
LOG("SAVE cpu65_pc = %04x", cpu65_pc);
serialized[0] = ((cpu65_ea & 0xFF00) >> 8);
serialized[1] = ((cpu65_ea & 0xFF ) >> 0);
if (!helper->save(fd, serialized, sizeof(cpu65_ea))) {
break;
}
LOG("SAVE cpu65_ea = %04x", cpu65_ea);
if (!helper->save(fd, &cpu65_a, sizeof(cpu65_a))) {
break;
}
LOG("SAVE cpu65_a = %02x", cpu65_a);
if (!helper->save(fd, &cpu65_f, sizeof(cpu65_f))) {
break;
}
LOG("SAVE cpu65_f = %02x", cpu65_f);
if (!helper->save(fd, &cpu65_x, sizeof(cpu65_x))) {
break;
}
LOG("SAVE cpu65_x = %02x", cpu65_x);
if (!helper->save(fd, &cpu65_y, sizeof(cpu65_y))) {
break;
}
LOG("SAVE cpu65_y = %02x", cpu65_y);
if (!helper->save(fd, &cpu65_sp, sizeof(cpu65_sp))) {
break;
}
LOG("SAVE cpu65_sp = %02x", cpu65_sp);
saved = true;
} while (0);
return saved;
}
bool cpu65_loadState(StateHelper_s *helper) {
bool loaded = false;
int fd = helper->fd;
do {
uint8_t serialized[4] = { 0 };
// load CPU state
if (!helper->load(fd, serialized, sizeof(uint16_t))) {
break;
}
cpu65_pc = (serialized[0] << 8);
cpu65_pc |= serialized[1];
LOG("LOAD cpu65_pc = %04x", cpu65_pc);
if (!helper->load(fd, serialized, sizeof(uint16_t))) {
break;
}
cpu65_ea = (serialized[0] << 8);
cpu65_ea |= serialized[1];
LOG("LOAD cpu65_ea = %04x", cpu65_ea);
if (!helper->load(fd, &cpu65_a, sizeof(cpu65_a))) {
break;
}
LOG("LOAD cpu65_a = %02x", cpu65_a);
if (!helper->load(fd, &cpu65_f, sizeof(cpu65_f))) {
break;
}
LOG("LOAD cpu65_f = %02x", cpu65_f);
if (!helper->load(fd, &cpu65_x, sizeof(cpu65_x))) {
break;
}
LOG("LOAD cpu65_x = %02x", cpu65_x);
if (!helper->load(fd, &cpu65_y, sizeof(cpu65_y))) {
break;
}
LOG("LOAD cpu65_y = %02x", cpu65_y);
if (!helper->load(fd, &cpu65_sp, sizeof(cpu65_sp))) {
break;
}
LOG("LOAD cpu65_sp = %02x", cpu65_sp);
loaded = true;
} while (0);
return loaded;
}
#if CPU_TRACING
/* -------------------------------------------------------------------------

View File

@ -51,6 +51,9 @@ extern void cpu65_uninterrupt(int reason);
extern void cpu65_run(void);
extern void cpu65_reboot(void);
extern bool cpu65_saveState(StateHelper_s *helper);
extern bool cpu65_loadState(StateHelper_s *helper);
extern void cpu65_direct_write(int ea,int data);
extern void *cpu65_vmem_r[65536];

View File

@ -906,6 +906,225 @@ void disk6_flush(int drive) {
}
}
bool disk6_saveState(StateHelper_s *helper) {
bool saved = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
state = (uint8_t)disk6.motor_off;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE motor_off = %02x", state);
state = (uint8_t)disk6.drive;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE drive = %02x", state);
state = (uint8_t)disk6.ddrw;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE ddrw = %02x", state);
state = (uint8_t)disk6.disk_byte;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE disk_byte = %02x", state);
// Drive A/B
bool saved_drives = false;
for (unsigned long i=0; i<3; i++) {
if (i >= 2) {
saved_drives = true;
break;
}
state = (uint8_t)disk6.disk[i].is_protected;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE is_protected[%lu] = %02x", i, state);
uint8_t serialized[4] = { 0 };
if (disk6.disk[i].file_name != NULL) {
uint32_t namelen = strlen(disk6.disk[i].file_name);
serialized[0] = (uint8_t)((namelen & 0xFF000000) >> 24);
serialized[1] = (uint8_t)((namelen & 0xFF0000 ) >> 16);
serialized[2] = (uint8_t)((namelen & 0xFF00 ) >> 8);
serialized[3] = (uint8_t)((namelen & 0xFF ) >> 0);
if (!helper->save(fd, serialized, 4)) {
break;
}
if (!helper->save(fd, disk6.disk[i].file_name, namelen)) {
break;
}
LOG("SAVE disk[%lu] : (%u) %s", i, namelen, disk6.disk[i].file_name);
} else {
memset(serialized, 0x0, sizeof(serialized));
if (!helper->save(fd, serialized, 4)) {
break;
}
LOG("SAVE disk[%lu] (0) <NULL>", i);
}
state = (uint8_t)disk6.disk[i].track_valid;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE track_valid[%lu] = %02x", i, state);
state = (uint8_t)disk6.disk[i].track_dirty;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE track_dirty[%lu] = %02x", i, state);
state = (uint8_t)disk6.disk[i].phase;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE phase[%lu] = %02x", i, state);
serialized[0] = (uint8_t)((disk6.disk[i].run_byte & 0xFF00) >> 8);
serialized[1] = (uint8_t)((disk6.disk[i].run_byte & 0xFF ) >> 0);
if (!helper->save(fd, serialized, 2)) {
break;
}
LOG("SAVE run_byte[%lu] = %04x", i, disk6.disk[i].run_byte);
}
if (!saved_drives) {
break;
}
saved = true;
} while (0);
return saved;
}
bool disk6_loadState(StateHelper_s *helper) {
bool loaded = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.motor_off = state;
LOG("LOAD motor_off = %02x", disk6.motor_off);
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.drive = state;
LOG("LOAD drive = %02x", disk6.drive);
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.ddrw = state;
LOG("LOAD ddrw = %02x", disk6.ddrw);
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.disk_byte = state;
LOG("LOAD disk_byte = %02x", disk6.disk_byte);
// Drive A/B
bool loaded_drives = false;
for (unsigned long i=0; i<3; i++) {
if (i >= 2) {
loaded_drives = true;
break;
}
uint8_t serialized[4] = { 0 };
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.disk[i].is_protected = state;
LOG("LOAD is_protected[%lu] = %02x", i, disk6.disk[i].is_protected);
if (!helper->load(fd, serialized, 4)) {
break;
}
uint32_t namelen = 0x0;
namelen = (uint32_t)(serialized[0] << 24);
namelen |= (uint32_t)(serialized[1] << 16);
namelen |= (uint32_t)(serialized[2] << 8);
namelen |= (uint32_t)(serialized[3] << 0);
disk6_eject(i);
if (namelen) {
unsigned long gzlen = (_GZLEN+1);
char *namebuf = malloc(namelen+gzlen+1);
if (!helper->load(fd, namebuf, namelen)) {
FREE(namebuf);
break;
}
snprintf(namebuf+namelen, gzlen, "%s", DISK_EXT_GZ);
namebuf[namelen+gzlen] = '\0';
LOG("LOAD disk[%lu] : (%u) %s", i, namelen, namebuf);
disk6_insert(i, namebuf, disk6.disk[i].is_protected);
FREE(namebuf);
}
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.disk[i].track_valid = state;
LOG("LOAD track_valid[%lu] = %02x", i, disk6.disk[i].track_valid);
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.disk[i].track_dirty = state;
LOG("LOAD track_dirty[%lu] = %02x", i, disk6.disk[i].track_dirty);
if (!helper->load(fd, &state, 1)) {
break;
}
disk6.disk[i].phase = state;
LOG("LOAD phase[%lu] = %02x", i, disk6.disk[i].phase);
if (!helper->load(fd, serialized, 2)) {
break;
}
disk6.disk[i].run_byte = (uint32_t)(serialized[0] << 8);
disk6.disk[i].run_byte |= (uint32_t)(serialized[1] << 0);
LOG("LOAD run_byte[%lu] = %04x", i, disk6.disk[i].run_byte);
}
if (!loaded_drives) {
break;
}
loaded = true;
} while (0);
return loaded;
}
#if DISK_TRACING
void c_begin_disk_trace_6(const char *read_file, const char *write_file) {
if (read_file) {

View File

@ -81,7 +81,7 @@ extern drive_t disk6;
extern void disk6_init(void);
// insert 5.25 disk image file
extern const char *disk6_insert(int drive, const char * const file_name, int force);
extern const char *disk6_insert(int drive, const char * const file_name, int readonly);
// eject 5.25 disk image file
extern const char *disk6_eject(int drive);
@ -89,6 +89,9 @@ extern const char *disk6_eject(int drive);
// flush all I/O
extern void disk6_flush(int drive);
extern bool disk6_saveState(StateHelper_s *helper);
extern bool disk6_loadState(StateHelper_s *helper);
#if DISK_TRACING
void c_toggle_disk_trace_6(const char *read_file, const char *write_file);
void c_begin_disk_trace_6(const char *read_file, const char *write_file);

View File

@ -1094,6 +1094,46 @@ void video_clear(void) {
video_setDirty();
}
bool video_saveState(StateHelper_s *helper) {
bool saved = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
state = (uint8_t)video__current_page;
if (!helper->save(fd, &state, 1)) {
break;
}
LOG("SAVE video__current_page = %02x", state);
saved = true;
} while (0);
return saved;
}
bool video_loadState(StateHelper_s *helper) {
bool loaded = false;
int fd = helper->fd;
do {
uint8_t state = 0x0;
if (!helper->load(fd, &state, 1)) {
break;
}
video__current_page = state;
LOG("LOAD video__current_page = %02x", video__current_page);
loaded = true;
} while (0);
video_redraw();
return loaded;
}
void video_redraw(void) {
// temporarily reset softswitches

View File

@ -15,6 +15,9 @@
#include "common.h"
#define SAVE_MAGICK "A2VM"
#define SAVE_MAGICK_LEN sizeof(SAVE_MAGICK)
bool do_logging = true; // also controlled by NDEBUG
FILE *error_log = NULL;
int sound_volume = 2;
@ -35,6 +38,155 @@ static void _init_common(void) {
#endif
}
static bool _save_state(int fd, const uint8_t * outbuf, ssize_t outmax) {
ssize_t outlen = 0;
do {
if (TEMP_FAILURE_RETRY(outlen = write(fd, outbuf, outmax)) == -1) {
ERRLOG("error writing emulator save state file");
break;
}
outbuf += outlen;
outmax -= outlen;
} while (outmax > 0);
return outmax == 0;
}
static bool _load_state(int fd, uint8_t * inbuf, ssize_t inmax) {
ssize_t inlen = 0;
do {
if (TEMP_FAILURE_RETRY(inlen = read(fd, inbuf, inmax)) == -1) {
ERRLOG("error reading emulator save state file");
break;
}
if (inlen == 0) {
ERRLOG("error reading emulator save state file (truncated)");
break;
}
inbuf += inlen;
inmax -= inlen;
} while (inmax > 0);
return inmax == 0;
}
bool emulator_saveState(const char * const path) {
int fd = -1;
bool saved = false;
assert(cpu_isPaused() && "should be paused to save state");
do {
TEMP_FAILURE_RETRY(fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR));
if (fd < 0) {
break;
}
assert(fd != 0 && "crazy platform");
// save header
if (!_save_state(fd, SAVE_MAGICK, SAVE_MAGICK_LEN)) {
break;
}
StateHelper_s helper = {
.fd = fd,
.save = &_save_state,
.load = &_load_state,
};
if (!disk6_saveState(&helper)) {
break;
}
if (!vm_saveState(&helper)) {
break;
}
if (!cpu65_saveState(&helper)) {
break;
}
if (!video_saveState(&helper)) {
break;
}
TEMP_FAILURE_RETRY(fsync(fd));
saved = true;
} while (0);
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
}
if (!saved) {
ERRLOG("could not write to the emulator save state file");
unlink(path);
}
return saved;
}
bool emulator_loadState(const char * const path) {
int fd = -1;
bool loaded = false;
assert(cpu_isPaused() && "should be paused to load state");
do {
TEMP_FAILURE_RETRY(fd = open(path, O_RDONLY));
if (fd < 0) {
break;
}
assert(fd != 0 && "crazy platform");
// load header
uint8_t magick[SAVE_MAGICK_LEN] = { 0 };
if (!_load_state(fd, magick, SAVE_MAGICK_LEN)) {
break;
}
// check header
if (memcmp(magick, SAVE_MAGICK, SAVE_MAGICK_LEN) != 0) {
ERRLOG("bad header magick in emulator save state file");
break;
}
StateHelper_s helper = {
.fd = fd,
.save = &_save_state,
.load = &_load_state,
};
if (!disk6_loadState(&helper)) {
break;
}
if (!vm_loadState(&helper)) {
break;
}
if (!cpu65_loadState(&helper)) {
break;
}
if (!video_loadState(&helper)) {
break;
}
loaded = true;
} while (0);
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
}
if (!loaded) {
ERRLOG("could not load emulator save state file");
}
return loaded;
}
static void _shutdown_threads(void) {
#if !TESTING
# if defined(__linux__) && !defined(ANDROID)

View File

@ -29,6 +29,22 @@ void emulator_start(void);
// shutdown emulator in preparation for app exit
void emulator_shutdown(void);
//
// Emulator state save/restore
//
typedef struct StateHelper_s {
int fd;
bool (*save)(int fd, const uint8_t * outbuf, ssize_t outmax);
bool (*load)(int fd, uint8_t * inbuf, ssize_t inmax);
} StateHelper_s;
// save current emulator state
bool emulator_saveState(const char * const path);
// load emulator state from save path
bool emulator_loadState(const char * const path);
//
// Crash handling ...
//

View File

@ -157,6 +157,9 @@ static inline unsigned long video_clearDirty(void) {
return __sync_fetch_and_and(&_backend_vid_dirty, 0UL);
}
extern bool video_saveState(StateHelper_s *helper);
extern bool video_loadState(StateHelper_s *helper);
// ----------------------------------------------------------------------------
/*

381
src/vm.c
View File

@ -1224,6 +1224,387 @@ void vm_reinitializeAudio(void) {
#warning TODO FIXME ... should unset MB/Phasor hooks if volume is zero ...
}
bool vm_saveState(StateHelper_s *helper) {
bool saved = false;
int fd = helper->fd;
do {
uint8_t serialized[8] = { 0 };
serialized[0] = (uint8_t)((softswitches & 0xFF000000) >> 24);
serialized[1] = (uint8_t)((softswitches & 0xFF0000 ) >> 16);
serialized[2] = (uint8_t)((softswitches & 0xFF00 ) >> 8);
serialized[3] = (uint8_t)((softswitches & 0xFF ) >> 0);
LOG("SAVE softswitches = %08x", softswitches);
if (!helper->save(fd, serialized, sizeof(softswitches))) {
break;
}
// save main/aux memory state
if (!helper->save(fd, apple_ii_64k[0], sizeof(apple_ii_64k))) {
break;
}
// save language card
if (!helper->save(fd, language_card[0], sizeof(language_card))) {
break;
}
// save language banks
if (!helper->save(fd, language_banks[0], sizeof(language_banks))) {
break;
}
// save offsets
serialized[0] = 0x0;
serialized[1] = 0x1;
serialized[2] = 0x2;
serialized[3] = 0x3;
serialized[4] = 0x4;
serialized[5] = 0x5;
LOG("SAVE base_ramrd = %d", (base_ramrd == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_ramrd == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_ramwrt = %d", (base_ramwrt == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_ramwrt == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_textrd = %d", (base_textrd == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_textrd == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_textwrt = %d", (base_textwrt == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_textwrt == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_hgrrd = %d", (base_hgrrd == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_hgrrd == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_hgrwrt = %d", (base_hgrwrt == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_hgrwrt == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_stackzp = %d", (base_stackzp == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_stackzp == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_c3rom = %d", (base_c3rom == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_c3rom == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
LOG("SAVE base_cxrom = %d", (base_cxrom == apple_ii_64k[0]) ? serialized[0] : serialized[1]);
if (!helper->save(fd, (base_cxrom == apple_ii_64k[0]) ? &serialized[0] : &serialized[1], 1)) {
break;
}
if (base_d000_rd == apple_ii_64k[0]) {
LOG("SAVE base_d000_rd = %d", serialized[0]);
if (!helper->save(fd, &serialized[0], 1)) { // base_d000_rd --> //e ROM
break;
}
} else if (base_d000_rd == language_banks[0] - 0xD000) {
LOG("SAVE base_d000_rd = %d", serialized[2]);
if (!helper->save(fd, &serialized[2], 1)) { // base_d000_rd --> main LC mem
break;
}
} else if (base_d000_rd == language_banks[0] - 0xC000) {
LOG("SAVE base_d000_rd = %d", serialized[3]);
if (!helper->save(fd, &serialized[3], 1)) { // base_d000_rd --> main LC mem
break;
}
} else if (base_d000_rd == language_banks[1] - 0xD000) {
LOG("SAVE base_d000_rd = %d", serialized[4]);
if (!helper->save(fd, &serialized[4], 1)) { // base_d000_rd --> aux LC mem
break;
}
} else if (base_d000_rd == language_banks[1] - 0xC000) {
LOG("SAVE base_d000_rd = %d", serialized[5]);
if (!helper->save(fd, &serialized[5], 1)) { // base_d000_rd --> aux LC mem
break;
}
} else {
LOG("OOPS ... language_banks[0] == %p base_d000_rd == %p", language_banks[0], base_d000_rd);
RELEASE_BREAK();
}
if (base_d000_wrt == 0) {
LOG("SAVE base_d000_wrt = %d", serialized[0]);
if (!helper->save(fd, &serialized[0], 1)) { // base_d000_wrt --> no write
break;
}
} else if (base_d000_wrt == language_banks[0] - 0xD000) {
LOG("SAVE base_d000_wrt = %d", serialized[2]);
if (!helper->save(fd, &serialized[2], 1)) { // base_d000_wrt --> main LC mem
break;
}
} else if (base_d000_wrt == language_banks[0] - 0xC000) {
LOG("SAVE base_d000_wrt = %d", serialized[3]);
if (!helper->save(fd, &serialized[3], 1)) { // base_d000_wrt --> main LC mem
break;
}
} else if (base_d000_wrt == language_banks[1] - 0xD000) {
LOG("SAVE base_d000_wrt = %d", serialized[4]);
if (!helper->save(fd, &serialized[4], 1)) { // base_d000_wrt --> aux LC mem
break;
}
} else if (base_d000_wrt == language_banks[1] - 0xC000) {
LOG("SAVE base_d000_wrt = %d", serialized[5]);
if (!helper->save(fd, &serialized[5], 1)) { // base_d000_wrt --> aux LC mem
break;
}
} else {
LOG("OOPS ... language_banks[0] == %p base_d000_wrt == %p", language_banks[0], base_d000_wrt);
RELEASE_BREAK();
}
if (base_e000_rd == apple_ii_64k[0]) {
LOG("SAVE base_e000_rd = %d", serialized[0]);
if (!helper->save(fd, &serialized[0], 1)) { // base_e000_rd --> //e ROM
break;
}
} else if (base_e000_rd == language_card[0] - 0xE000) {
LOG("SAVE base_e000_rd = %d", serialized[2]);
if (!helper->save(fd, &serialized[2], 1)) { // base_e000_rd --> main LC mem
break;
}
} else if (base_e000_rd == language_card[0] - 0xC000) {
LOG("SAVE base_e000_rd = %d", serialized[3]);
if (!helper->save(fd, &serialized[3], 1)) { // base_e000_rd --> aux LC mem
break;
}
} else {
LOG("OOPS ... language_card[0] == %p base_e000_rd == %p", language_card[0], base_e000_rd);
RELEASE_BREAK();
}
if (base_e000_wrt == 0) {
LOG("SAVE base_e000_wrt = %d", serialized[0]);
if (!helper->save(fd, &serialized[0], 1)) { // base_e000_wrt --> no write
break;
}
} else if (base_e000_wrt == language_card[0] - 0xE000) {
LOG("SAVE base_e000_wrt = %d", serialized[2]);
if (!helper->save(fd, &serialized[2], 1)) { // base_e000_wrt --> main LC mem
break;
}
} else if (base_e000_wrt == language_card[0] - 0xC000) {
LOG("SAVE base_e000_wrt = %d", serialized[3]);
if (!helper->save(fd, &serialized[3], 1)) { // base_e000_wrt --> aux LC mem
break;
}
} else {
LOG("OOPS ... language_card[0] == %p base_e000_wrt == %p", language_card[0], base_e000_wrt);
RELEASE_BREAK();
}
saved = true;
} while (0);
return saved;
}
bool vm_loadState(StateHelper_s *helper) {
bool loaded = false;
int fd = helper->fd;
do {
uint8_t serialized[4] = { 0 };
if (!helper->load(fd, serialized, sizeof(uint32_t))) {
break;
}
softswitches = (uint32_t)(serialized[0] << 24);
softswitches |= (uint32_t)(serialized[1] << 16);
softswitches |= (uint32_t)(serialized[2] << 8);
softswitches |= (uint32_t)(serialized[3] << 0);
LOG("LOAD softswitches = %08x", softswitches);
// load main/aux memory state
if (!helper->load(fd, apple_ii_64k[0], sizeof(apple_ii_64k))) {
break;
}
// load language card
if (!helper->load(fd, language_card[0], sizeof(language_card))) {
break;
}
// load language banks
if (!helper->load(fd, language_banks[0], sizeof(language_banks))) {
break;
}
// load offsets
uint8_t state = 0x0;
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_ramrd = %d", state);
base_ramrd = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_ramwrt = %d", state);
base_ramwrt = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_textrd = %d", state);
base_textrd = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_textwrt = %d", state);
base_textwrt = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_hgrrd = %d", state);
base_hgrrd = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_hgrwrt = %d", state);
base_hgrwrt = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_stackzp = %d", state);
base_stackzp = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_c3rom = %d", state);
base_c3rom = state == 0x0 ? apple_ii_64k[0] : apple_ii_64k[1];
if (!helper->load(fd, &state, 1)) {
break;
}
LOG("LOAD base_cxrom = %d", state);
if (state == 0) {
base_cxrom = apple_ii_64k[0];
#ifdef AUDIO_ENABLED
extern VMFunc MB_Read;
base_c4rom = (void *)MB_Read;
base_c5rom = (void *)MB_Read;
#else
base_c4rom = (void *)ram_nop;
base_c5rom = (void *)ram_nop;
#endif
} else {
base_cxrom = apple_ii_64k[1];
base_c4rom = apple_ii_64k[1];
base_c5rom = apple_ii_64k[1];
}
if (!helper->load(fd, &state, 1)) {
break;
}
switch (state) {
case 0:
base_d000_rd = apple_ii_64k[0];
break;
case 2:
base_d000_rd = language_banks[0] - 0xD000;
break;
case 3:
base_d000_rd = language_banks[0] - 0xC000;
break;
case 4:
base_d000_rd = language_banks[1] - 0xD000;
break;
case 5:
base_d000_rd = language_banks[1] - 0xC000;
break;
default:
LOG("Unknown state base_d000_rd %02x", state);
RELEASE_BREAK();
break;
}
LOG("LOAD base_d000_rd = %d", state);
if (!helper->load(fd, &state, 1)) {
break;
}
switch (state) {
case 0:
base_d000_wrt = 0;
break;
case 2:
base_d000_wrt = language_banks[0] - 0xD000;
break;
case 3:
base_d000_wrt = language_banks[0] - 0xC000;
break;
case 4:
base_d000_wrt = language_banks[1] - 0xD000;
break;
case 5:
base_d000_wrt = language_banks[1] - 0xC000;
break;
default:
LOG("Unknown state base_d000_wrt %02x", state);
RELEASE_BREAK();
break;
}
LOG("LOAD base_d000_wrt = %d", state);
if (!helper->load(fd, &state, 1)) {
break;
}
switch (state) {
case 0:
base_e000_rd = apple_ii_64k[0];
break;
case 2:
base_e000_rd = language_card[0] - 0xE000;
break;
case 3:
base_e000_rd = language_card[0] - 0xC000;
break;
default:
LOG("Unknown state base_e000_rd %02x", state);
RELEASE_BREAK();
break;
}
LOG("LOAD base_e000_rd = %d", state);
if (!helper->load(fd, &state, 1)) {
break;
}
switch (state) {
case 0:
base_e000_wrt = 0;
break;
case 2:
base_e000_wrt = language_card[0] - 0xE000;
break;
case 3:
base_e000_wrt = language_card[0] - 0xC000;
break;
default:
LOG("Unknown state base_e000_wrt %02x", state);
RELEASE_BREAK();
break;
}
LOG("LOAD base_e000_wrt = %d", state);
loaded = true;
} while (0);
return loaded;
}
void debug_print_softwitches(void) {
// useful from GDB ...

View File

@ -147,6 +147,9 @@ void vm_initialize(void);
void vm_reinitializeAudio(void);
extern bool vm_saveState(StateHelper_s *helper);
extern bool vm_loadState(StateHelper_s *helper);
#if VM_TRACING
void vm_trace_begin(const char *vm_file);
void vm_trace_end(void);