mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-10-01 05:55:15 +00:00
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:
parent
46c286719f
commit
b9d6d38b17
@ -40,6 +40,7 @@ public class Apple2Activity extends Activity {
|
|||||||
|
|
||||||
private final static String TAG = "Apple2Activity";
|
private final static String TAG = "Apple2Activity";
|
||||||
private final static int MAX_FINGERS = 32;// HACK ...
|
private final static int MAX_FINGERS = 32;// HACK ...
|
||||||
|
private final static String SAVE_FILE = "emulator.state";
|
||||||
private static volatile boolean DEBUG_STRICT = false;
|
private static volatile boolean DEBUG_STRICT = false;
|
||||||
|
|
||||||
private Apple2View mView = null;
|
private Apple2View mView = null;
|
||||||
@ -94,6 +95,10 @@ public class Apple2Activity extends Activity {
|
|||||||
|
|
||||||
private native void nativeOnKeyUp(int keyCode, int metaState);
|
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 nativeEmulationResume();
|
||||||
|
|
||||||
public native void nativeEmulationPause();
|
public native void nativeEmulationPause();
|
||||||
@ -605,4 +610,26 @@ public class Apple2Activity extends Activity {
|
|||||||
}).setNegativeButton(R.string.no, null).create();
|
}).setNegativeButton(R.string.no, null).create();
|
||||||
registerAndShowDialog(rebootDialog);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,17 @@ public class Apple2MainMenu {
|
|||||||
mainMenu.showDisksMenu();
|
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 {
|
REBOOT_EMULATOR {
|
||||||
@Override public String getTitle(Context ctx) {
|
@Override public String getTitle(Context ctx) {
|
||||||
return ctx.getResources().getString(R.string.reboot);
|
return ctx.getResources().getString(R.string.reboot);
|
||||||
|
@ -160,6 +160,11 @@
|
|||||||
<string name="reboot_really">Reboot emulator?</string>
|
<string name="reboot_really">Reboot emulator?</string>
|
||||||
<string name="reboot_summary"></string>
|
<string name="reboot_summary"></string>
|
||||||
<string name="reboot_warning">You will lose unsaved progress</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 & 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→</string>
|
<string name="skip">Skip→</string>
|
||||||
<string name="spacer"></string>
|
<string name="spacer"></string>
|
||||||
<string name="speaker_disabled_title">Speaker disabled</string>
|
<string name="speaker_disabled_title">Speaker disabled</string>
|
||||||
|
@ -341,6 +341,32 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEjectDisk(JNIEnv *env, jobj
|
|||||||
disk6_eject(!driveA);
|
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
|
// Constructor
|
||||||
|
|
||||||
|
@ -654,6 +654,105 @@ void cpu65_reboot(void) {
|
|||||||
cpu65_interrupt(ResetSig);
|
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
|
#if CPU_TRACING
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
|
@ -51,6 +51,9 @@ extern void cpu65_uninterrupt(int reason);
|
|||||||
extern void cpu65_run(void);
|
extern void cpu65_run(void);
|
||||||
extern void cpu65_reboot(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_direct_write(int ea,int data);
|
||||||
|
|
||||||
extern void *cpu65_vmem_r[65536];
|
extern void *cpu65_vmem_r[65536];
|
||||||
|
219
src/disk.c
219
src/disk.c
@ -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
|
#if DISK_TRACING
|
||||||
void c_begin_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) {
|
||||||
if (read_file) {
|
if (read_file) {
|
||||||
|
@ -81,7 +81,7 @@ extern drive_t disk6;
|
|||||||
extern void disk6_init(void);
|
extern void disk6_init(void);
|
||||||
|
|
||||||
// insert 5.25 disk image file
|
// 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
|
// eject 5.25 disk image file
|
||||||
extern const char *disk6_eject(int drive);
|
extern const char *disk6_eject(int drive);
|
||||||
@ -89,6 +89,9 @@ extern const char *disk6_eject(int drive);
|
|||||||
// flush all I/O
|
// flush all I/O
|
||||||
extern void disk6_flush(int drive);
|
extern void disk6_flush(int drive);
|
||||||
|
|
||||||
|
extern bool disk6_saveState(StateHelper_s *helper);
|
||||||
|
extern bool disk6_loadState(StateHelper_s *helper);
|
||||||
|
|
||||||
#if DISK_TRACING
|
#if DISK_TRACING
|
||||||
void c_toggle_disk_trace_6(const char *read_file, const char *write_file);
|
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);
|
void c_begin_disk_trace_6(const char *read_file, const char *write_file);
|
||||||
|
@ -1094,6 +1094,46 @@ void video_clear(void) {
|
|||||||
video_setDirty();
|
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) {
|
void video_redraw(void) {
|
||||||
|
|
||||||
// temporarily reset softswitches
|
// temporarily reset softswitches
|
||||||
|
152
src/misc.c
152
src/misc.c
@ -15,6 +15,9 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#define SAVE_MAGICK "A2VM"
|
||||||
|
#define SAVE_MAGICK_LEN sizeof(SAVE_MAGICK)
|
||||||
|
|
||||||
bool do_logging = true; // also controlled by NDEBUG
|
bool do_logging = true; // also controlled by NDEBUG
|
||||||
FILE *error_log = NULL;
|
FILE *error_log = NULL;
|
||||||
int sound_volume = 2;
|
int sound_volume = 2;
|
||||||
@ -35,6 +38,155 @@ static void _init_common(void) {
|
|||||||
#endif
|
#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) {
|
static void _shutdown_threads(void) {
|
||||||
#if !TESTING
|
#if !TESTING
|
||||||
# if defined(__linux__) && !defined(ANDROID)
|
# if defined(__linux__) && !defined(ANDROID)
|
||||||
|
16
src/misc.h
16
src/misc.h
@ -29,6 +29,22 @@ void emulator_start(void);
|
|||||||
// shutdown emulator in preparation for app exit
|
// shutdown emulator in preparation for app exit
|
||||||
void emulator_shutdown(void);
|
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 ...
|
// Crash handling ...
|
||||||
//
|
//
|
||||||
|
@ -157,6 +157,9 @@ static inline unsigned long video_clearDirty(void) {
|
|||||||
return __sync_fetch_and_and(&_backend_vid_dirty, 0UL);
|
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
381
src/vm.c
@ -1224,6 +1224,387 @@ void vm_reinitializeAudio(void) {
|
|||||||
#warning TODO FIXME ... should unset MB/Phasor hooks if volume is zero ...
|
#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) {
|
void debug_print_softwitches(void) {
|
||||||
// useful from GDB ...
|
// useful from GDB ...
|
||||||
|
|
||||||
|
3
src/vm.h
3
src/vm.h
@ -147,6 +147,9 @@ void vm_initialize(void);
|
|||||||
|
|
||||||
void vm_reinitializeAudio(void);
|
void vm_reinitializeAudio(void);
|
||||||
|
|
||||||
|
extern bool vm_saveState(StateHelper_s *helper);
|
||||||
|
extern bool vm_loadState(StateHelper_s *helper);
|
||||||
|
|
||||||
#if VM_TRACING
|
#if VM_TRACING
|
||||||
void vm_trace_begin(const char *vm_file);
|
void vm_trace_begin(const char *vm_file);
|
||||||
void vm_trace_end(void);
|
void vm_trace_end(void);
|
||||||
|
Loading…
Reference in New Issue
Block a user