Disk chooser can now also choose .a2state files

- Internal save/restore API now uses file descriptors (supports restrictive app environments)
This commit is contained in:
Aaron Culliney 2017-06-29 22:42:28 -07:00
parent d98c4afa84
commit 7411a987fa
9 changed files with 268 additions and 180 deletions

View File

@ -74,9 +74,9 @@ public class Apple2Activity extends Activity implements Apple2DiskChooserActivit
private static native void nativeOnKeyUp(int keyCode, int metaState);
private static native void nativeSaveState(String path);
private static native void nativeSaveState(String saveStateJson);
private static native String nativeStateExtractDiskPaths(String path);
private static native String nativeStateExtractDiskPaths(String extractStateJson);
private static native String nativeLoadState(String loadStateJson);
@ -192,18 +192,10 @@ public class Apple2Activity extends Activity implements Apple2DiskChooserActivit
@Override
public void onDisksChosen(DiskArgs args) {
if (Apple2DisksMenu.hasDiskExtension(args.name)) {
final String name = args.name;
if (Apple2DisksMenu.hasDiskExtension(name) || Apple2DisksMenu.hasStateExtension(name)) {
sDisksChosen = args;
} else {
if (args.name.equals("")) {
return;
}
if (Apple2DisksMenu.hasStateExtension(args.name)) {
////mMainMenu.restoreEmulatorState(args); FIXME TODO ...
return;
}
} else if (!name.equals("")) {
Toast.makeText(this, R.string.disk_insert_toast_cannot, Toast.LENGTH_SHORT).show();
}
}
@ -232,40 +224,63 @@ public class Apple2Activity extends Activity implements Apple2DiskChooserActivit
@Override
protected void onResume() {
super.onResume();
if (sNativeBarfed) {
Apple2CrashHandler.getInstance().abandonAllHope(this, sNativeBarfedThrowable);
return;
}
Log.d(TAG, "onResume()");
showSplashScreen(/*dismissable:*/true);
if (!mSwitchingToPortrait.get()) {
Apple2CrashHandler.getInstance().checkForCrashes(this); // NOTE : needs to be called again to clean-up
}
if (sDisksChosen != null && mDisksMenu != null) {
if (sDisksChosen.pfd != null) {
String name = sDisksChosen.name;
final String[] prefices = {"content://com.android.externalstorage.documents/document", "content://com.android.externalstorage.documents", "content://com.android.externalstorage.documents", "content://"};
for (String prefix : prefices) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
break;
}
}
// strip out URL-encoded '/' directory separators
String nameLower = name.toLowerCase();
int idx = nameLower.lastIndexOf("%2f", /*fromIndex:*/name.length()-3);
if (idx >= 0) {
name = name.substring(idx + 3);
}
mDisksMenu.showDiskInsertionAlertDialog(name, sDisksChosen);
do {
if (sNativeBarfed) {
Apple2CrashHandler.getInstance().abandonAllHope(this, sNativeBarfedThrowable);
break;
}
Log.d(TAG, "onResume()");
showSplashScreen(/*dismissable:*/true);
if (!mSwitchingToPortrait.get()) {
Apple2CrashHandler.getInstance().checkForCrashes(this); // NOTE : needs to be called again to clean-up
}
if (mDisksMenu == null) {
break;
}
if (sDisksChosen == null) {
break;
}
DiskArgs args = sDisksChosen;
sDisksChosen = null;
}
if (args.pfd == null) {
break;
}
String name = args.name;
if (Apple2DisksMenu.hasStateExtension(name)) {
boolean restored = Apple2MainMenu.restoreEmulatorState(this, args);
dismissAllMenus();
if (!restored) {
Toast.makeText(this, R.string.state_not_restored, Toast.LENGTH_SHORT).show();
}
break;
}
final String[] prefices = {"content://com.android.externalstorage.documents/document", "content://com.android.externalstorage.documents", "content://com.android.externalstorage.documents", "content://"};
for (String prefix : prefices) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
break;
}
}
// strip out URL-encoded '/' directory separators
String nameLower = name.toLowerCase();
int idx = nameLower.lastIndexOf("%2f", /*fromIndex:*/name.length() - 3);
if (idx >= 0) {
name = name.substring(idx + 3);
}
mDisksMenu.showDiskInsertionAlertDialog(name, args);
} while (false);
}
@Override
@ -488,10 +503,7 @@ public class Apple2Activity extends Activity implements Apple2DiskChooserActivit
public void maybeResumeEmulation() {
if (mMenuStack.size() == 0 && !mPausing.get()) {
Apple2Preferences.sync(this, null);
boolean previouslyPaused = nativeEmulationResume();
if (BuildConfig.DEBUG && !previouslyPaused) {
throw new RuntimeException("expecting native CPU thread to have been paused");
}
nativeEmulationResume();
}
}
@ -506,12 +518,12 @@ public class Apple2Activity extends Activity implements Apple2DiskChooserActivit
nativeReboot(resetState);
}
public void saveState(String stateFile) {
nativeSaveState(stateFile);
public void saveState(String saveStateJson) {
nativeSaveState(saveStateJson);
}
public String stateExtractDiskPaths(String stateFile) {
return nativeStateExtractDiskPaths(stateFile);
public String stateExtractDiskPaths(String extractStateJson) {
return nativeStateExtractDiskPaths(extractStateJson);
}
public String loadState(String loadStateJson) {

View File

@ -683,7 +683,7 @@ public class Apple2DisksMenu implements Apple2MenuView {
return false;
}
File file = new File(dir, name);
return file.isDirectory() || hasDiskExtension(name);
return file.isDirectory() || hasDiskExtension(name) || hasStateExtension(name);
}
});
@ -824,6 +824,12 @@ public class Apple2DisksMenu implements Apple2MenuView {
return;
}
if (hasStateExtension(imageName)) {
final String jsonString = "{ \"stateFile\" : \"" + imageName + "\" }";
Apple2MainMenu.restoreEmulatorState(mActivity, jsonString);
return;
}
showDiskInsertionAlertDialog(/*title:*/fileNames[position], /*diskPath:*/new DiskArgs(imageName));
}
});

View File

@ -30,8 +30,10 @@ import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import org.deadc0de.apple2ix.basic.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
@ -298,17 +300,69 @@ public class Apple2MainMenu {
mActivity.registerAndShowDialog(rebootQuitDialog);
}
public void restoreEmulatorState(String quickSavePath) {
public static boolean restoreEmulatorState(Apple2Activity activity, DiskArgs diskArgs) {
boolean restored = false;
try {
String stateFile = diskArgs.path;
if (stateFile == null) {
stateFile = Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL + diskArgs.uri.toString();
}
JSONObject map = new JSONObject();
map.put("stateFile", stateFile);
if (stateFile.startsWith(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL)) {
if (!Apple2Utils.isExternalStorageAccessible(activity)) {
// disallow access if we cannot access external storage
throw new Exception("External storage not accessible for state load");
}
if (diskArgs.pfd == null) {
if (diskArgs.uri == null) {
String uriString = stateFile.substring(Apple2DisksMenu.EXTERNAL_CHOOSER_SENTINEL.length());
diskArgs.uri = Uri.parse(uriString);
}
diskArgs.pfd = Apple2DiskChooserActivity.openFileDescriptorFromUri(activity, diskArgs.uri);
}
int fd = diskArgs.pfd.getFd(); // NPE thrown if diskArgs.pfd is null
map.put("fdState", fd);
} else {
File file = new File(stateFile);
if (!file.exists()) {
throw new RuntimeException("cannot insert state file : " + stateFile);
}
}
restored = restoreEmulatorState(activity, map.toString());
try {
diskArgs.pfd.close(); // at this point diskArgs.pfd !null
} catch (IOException ioe) {
Log.e(TAG, "Error attempting to close PFD : " + ioe);
}
diskArgs.pfd = null;
} catch (Exception e) {
Log.e(TAG, "OOPS: " + e);
}
return restored;
}
public static boolean restoreEmulatorState(Apple2Activity activity, String jsonString) {
boolean restored = false;
Apple2DisksMenu.ejectDisk(/*isDriveA:*/true);
Apple2DisksMenu.ejectDisk(/*isDriveA:*/false);
// First we extract and open the emulator.a2state disk paths (which could be in a restricted location)
String jsonString = mActivity.stateExtractDiskPaths(quickSavePath);
jsonString = activity.stateExtractDiskPaths(jsonString);
try {
JSONObject map = new JSONObject(jsonString);
map.put("stateFile", quickSavePath);
final String[] diskPathKeys = new String[]{"diskA", "diskB"};
final String[] readOnlyKeys = new String[]{"readOnlyA", "readOnlyB"};
@ -333,7 +387,7 @@ public class Apple2MainMenu {
Uri uri = Uri.parse(uriString);
pfds[i] = Apple2DiskChooserActivity.openFileDescriptorFromUri(mActivity, uri);
pfds[i] = Apple2DiskChooserActivity.openFileDescriptorFromUri(activity, uri);
if (pfds[i] == null) {
Log.e(TAG, "Did not find URI for drive #" + i + " specified in " + SAVE_FILE + " file : " + diskPath);
} else {
@ -348,7 +402,7 @@ public class Apple2MainMenu {
}
}
jsonString = mActivity.loadState(map.toString());
jsonString = activity.loadState(map.toString());
for (int i = 0; i < 2; i++) {
try {
@ -370,11 +424,13 @@ public class Apple2MainMenu {
Apple2Preferences.setJSONPref(Apple2DisksMenu.SETTINGS.CURRENT_DISK_PATH_B_GZ, wasGzippedB);
}
// FIXME TODO : what to do if state load failed?
restored = map.getBoolean("loadStateSuccess");
} catch (Throwable t) {
Log.v(TAG, "OOPS : " + t);
}
return restored;
}
private void maybeSaveRestore() {
@ -398,7 +454,9 @@ public class Apple2MainMenu {
Log.v(TAG, "OMG, avoiding nasty UI race in sync/restore onClick()");
return;
}
mActivity.saveState(quickSavePath);
String jsonString = "{ \"stateFile\" : \"" + quickSavePath + "\" }";
mActivity.saveState(jsonString);
Apple2MainMenu.this.dismiss();
}
}).setNeutralButton(R.string.restore, new DialogInterface.OnClickListener() {
@ -410,7 +468,11 @@ public class Apple2MainMenu {
return;
}
restoreEmulatorState(quickSavePath);
final String jsonString = "{ \"stateFile\" : \"" + quickSavePath + "\" }";
boolean restored = restoreEmulatorState(mActivity, jsonString);
if (!restored) {
Toast.makeText(mActivity, R.string.state_not_restored, Toast.LENGTH_SHORT).show();
}
Apple2MainMenu.this.dismiss();
}
}).setNegativeButton(R.string.cancel, null).create();

View File

@ -174,6 +174,7 @@
<string name="settings_advanced_joystick">Advanced joystick/keypad settings</string>
<string name="settings_advanced_joystick_summary">Advanced settings and performance tuning</string>
<string name="storage">/storage/</string>
<string name="state_not_restored">Error restoring state…</string>
<string name="touch_menu_enable">Enable touch menus</string>
<string name="touch_menu_enable_summary">Enables soft menu buttons in top screen corners</string>
<string name="video_configure">Configure video…</string>

View File

@ -385,13 +385,16 @@ jstring Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeChooseDisk(JNIEnv *env,
json_mapParseBoolValue(jsonData, "readOnly", &readOnly);
long fd = -1;
bool createdFd = false;
if (!json_mapParseLongValue(jsonData, "fd", &fd, 10)) {
createdFd = true;
TEMP_FAILURE_RETRY(fd = open(path, readOnly ? O_RDONLY : O_RDWR));
if (fd == -1) {
LOG("OOPS could not open disk path : %s", path);
}
} else {
fd = dup(fd);
if (fd == -1) {
ERRLOG("OOPS could not dup file descriptor!");
}
}
long drive = -1;
@ -419,7 +422,7 @@ jstring Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeChooseDisk(JNIEnv *env,
json_mapSetBoolValue(jsonData, "wasGzipped", disk6.disk[drive].was_gzipped);
json_mapSetBoolValue(jsonData, "inserted", inserted);
if (createdFd) {
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
fd = -1;
}
@ -447,17 +450,67 @@ void Java_org_deadc0de_apple2ix_Apple2DisksMenu_nativeEjectDisk(JNIEnv *env, jcl
disk6_eject(driveA ? 0 : 1);
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSaveState(JNIEnv *env, jclass cls, jstring jPath) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
static int _openFdFromJson(OUTPARM int *fdOut, JSON_ref jsonData, const char * const fdKey, const char * const pathKey, int flags, int mode) {
long fd = -1;
char *path = NULL;
do {
if (!json_mapParseLongValue(jsonData, fdKey, &fd, 10)) {
json_mapCopyStringValue(jsonData, pathKey, &path);
assert(path != NULL);
json_unescapeSlashes(&path);
if (strlen(path) <= 0) {
break;
}
if (mode == 0) {
TEMP_FAILURE_RETRY(fd = open(path, flags));
} else {
TEMP_FAILURE_RETRY(fd = open(path, flags, mode));
}
if (fd == -1) {
LOG("OOPS could not open state file path %s", path);
}
} else {
fd = dup(fd);
if (fd == -1) {
ERRLOG("OOPS could not dup file descriptor!");
}
}
} while (0);
FREE(path);
*fdOut = (int)fd;
}
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeSaveState(JNIEnv *env, jclass cls, jstring jJsonString) {
assert(cpu_isPaused() && "considered dangerous to save state when CPU thread is running");
LOG(": (%s)", path);
if (!emulator_saveState(path)) {
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
LOG(": (%s)", jsonString);
JSON_ref jsonData = NULL;
bool ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
int fdState = -1;
_openFdFromJson(&fdState, jsonData, /*fdKey:*/"fdState", /*pathKey:*/"stateFile", O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
if (!emulator_saveState(fdState)) {
LOG("OOPS, could not save emulator state");
}
(*env)->ReleaseStringUTFChars(env, jPath, path);
if (fdState >= 0) {
TEMP_FAILURE_RETRY(close(fdState));
fdState = -1;
}
json_destroy(&jsonData);
}
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, jclass cls, jstring jJsonString) {
@ -472,50 +525,25 @@ jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, j
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
char *path = NULL;
json_mapCopyStringValue(jsonData, "stateFile", &path);
json_unescapeSlashes(&path);
int fdState = -1;
_openFdFromJson(&fdState, jsonData, /*fdKey:*/"fdState", /*pathKey:*/"stateFile", O_RDONLY, 0);
bool readOnlyA = true;
json_mapParseBoolValue(jsonData, "readOnlyA", &readOnlyA);
bool createdFdA = false;
long fdA = -1;
if (!json_mapParseLongValue(jsonData, "fdA", &fdA, 10)) {
char *pathA = NULL;
json_mapCopyStringValue(jsonData, "diskA", &pathA);
json_unescapeSlashes(&pathA);
TEMP_FAILURE_RETRY(fdA = open(pathA, readOnlyA ? O_RDONLY : O_RDWR));
if (fdA == -1) {
LOG("OOPS could not open disk path %s", pathA);
}
createdFdA = fdA > 0;
FREE(pathA);
int fdA = -1;
{
bool readOnlyA = true;
json_mapParseBoolValue(jsonData, "readOnlyA", &readOnlyA);
_openFdFromJson(&fdA, jsonData, /*fdKey:*/"fdA", /*pathKey:*/"diskA", readOnlyA ? O_RDONLY : O_RDWR, 0);
}
bool readOnlyB = true;
json_mapParseBoolValue(jsonData, "readOnlyB", &readOnlyB);
bool createdFdB = false;
long fdB = -1;
if (!json_mapParseLongValue(jsonData, "fdB", &fdB, 10)) {
char *pathB = NULL;
json_mapCopyStringValue(jsonData, "diskB", &pathB);
json_unescapeSlashes(&pathB);
TEMP_FAILURE_RETRY(fdB = open(pathB, readOnlyB ? O_RDONLY : O_RDWR));
if (fdB == -1) {
LOG("OOPS could not open disk path %s", pathB);
}
createdFdB = fdB > 0;
FREE(pathB);
int fdB = -1;
{
bool readOnlyB = true;
json_mapParseBoolValue(jsonData, "readOnlyB", &readOnlyB);
_openFdFromJson(&fdB, jsonData, /*fdKey:*/"fdB", /*pathKey:*/"diskB", readOnlyB ? O_RDONLY : O_RDWR, 0);
}
bool loadStateSuccess = true;
if (emulator_loadState(path, (int)fdA, (int)fdB)) {
if (emulator_loadState(fdState, (int)fdA, (int)fdB)) {
json_mapSetBoolValue(jsonData, "wasGzippedA", disk6.disk[0].was_gzipped);
json_mapSetBoolValue(jsonData, "wasGzippedB", disk6.disk[1].was_gzipped);
} else {
@ -524,11 +552,17 @@ jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, j
// FIXME TODO : should show invalid state animation here ...
}
if (createdFdA) {
TEMP_FAILURE_RETRY(close(fdA));
if (fdState >= 0) {
TEMP_FAILURE_RETRY(close(fdState));
fdState = -1;
}
if (createdFdB) {
if (fdA >= 0) {
TEMP_FAILURE_RETRY(close(fdA));
fdA = -1;
}
if (fdB >= 0) {
TEMP_FAILURE_RETRY(close(fdB));
fdB = -1;
}
json_mapSetBoolValue(jsonData, "loadStateSuccess", loadStateSuccess);
@ -536,35 +570,47 @@ jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeLoadState(JNIEnv *env, j
jsonString = ((JSON_s *)jsonData)->jsonString;
jstring jstr = (*env)->NewStringUTF(env, jsonString);
FREE(path);
json_destroy(&jsonData);
return jstr;
}
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeStateExtractDiskPaths(JNIEnv *env, jclass cls, jstring jPath) {
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
jstring Java_org_deadc0de_apple2ix_Apple2Activity_nativeStateExtractDiskPaths(JNIEnv *env, jclass cls, jstring jJsonString) {
assert(cpu_isPaused() && "considered dangerous to save state when CPU thread is running");
LOG(": (%s)", path);
const char *jsonString = (*env)->GetStringUTFChars(env, jJsonString, NULL);
LOG(": (%s)", jsonString);
JSON_ref jsonData;
bool ret = json_createFromString("{}", &jsonData);
assert(ret >= 0 && "should be able to create JSON");
JSON_ref jsonData = NULL;
bool ret = json_createFromString(jsonString, &jsonData);
assert(ret > 0);
if (!emulator_stateExtractDiskPaths(path, &jsonData)) {
(*env)->ReleaseStringUTFChars(env, jJsonString, jsonString); jsonString = NULL;
int fdState = -1;
_openFdFromJson(&fdState, jsonData, /*fdKey:*/"fdState", /*pathKey:*/"stateFile", O_RDONLY, 0);
if (!emulator_stateExtractDiskPaths(fdState, jsonData)) {
LOG("OOPS, could not extract disk paths from emulator state file");
}
char *jsonString = ((JSON_s *)jsonData)->jsonString;
jsonString = ((JSON_s *)jsonData)->jsonString;
jstring jstr = (*env)->NewStringUTF(env, jsonString);
json_destroy(&jsonData);
(*env)->ReleaseStringUTFChars(env, jPath, path);
if (fdState >= 0) {
// 2017/06/30 HACK NOTE : if we dup()'d an Android ParcelFileDescriptor ... seek back to beginning here to reset for
// subsequent call to emulator_loadState(). Apparently there is no way to do that via the ParcelFileDescriptor
// API because ... rockstarz and ninjaz
off_t ret = lseek(fdState, 0, SEEK_SET);
if (ret != 0) {
ERRLOG("OOPS : state file lseek() failed!");
}
TEMP_FAILURE_RETRY(close(fdState));
fdState = -1;
}
return jstr;
}

View File

@ -1120,16 +1120,16 @@ bool disk6_saveState(StateHelper_s *helper) {
return saved;
}
static bool _disk6_loadState(StateHelper_s *helper, JSON_ref *json) {
static bool _disk6_loadState(StateHelper_s *helper, JSON_ref json) {
bool loaded = false;
int fd = helper->fd;
do {
if (json != NULL) {
json_mapSetStringValue(*json, "diskA", "");
json_mapSetStringValue(*json, "diskB", "");
json_mapSetBoolValue(*json, "readOnlyA", "true");
json_mapSetBoolValue(*json, "readOnlyB", "true");
json_mapSetStringValue(json, "diskA", "");
json_mapSetStringValue(json, "diskB", "");
json_mapSetBoolValue(json, "readOnlyA", "true");
json_mapSetBoolValue(json, "readOnlyB", "true");
}
const bool changeState = (json == NULL);
@ -1209,11 +1209,11 @@ static bool _disk6_loadState(StateHelper_s *helper, JSON_ref *json) {
}
} else {
if (i == 0) {
json_mapSetStringValue(*json, "diskA", namebuf);
json_mapSetBoolValue(*json, "readOnlyA", is_protected);
json_mapSetStringValue(json, "diskA", namebuf);
json_mapSetBoolValue(json, "readOnlyA", is_protected);
} else {
json_mapSetStringValue(*json, "diskB", namebuf);
json_mapSetBoolValue(*json, "readOnlyB", is_protected);
json_mapSetStringValue(json, "diskB", namebuf);
json_mapSetBoolValue(json, "readOnlyB", is_protected);
}
}
@ -1270,7 +1270,7 @@ bool disk6_loadState(StateHelper_s *helper) {
return _disk6_loadState(helper, NULL);
}
bool disk6_stateExtractDiskPaths(StateHelper_s *helper, JSON_ref *json) {
bool disk6_stateExtractDiskPaths(StateHelper_s *helper, JSON_ref json) {
return _disk6_loadState(helper, json);
}

View File

@ -91,7 +91,7 @@ extern void disk6_flush(int drive);
extern bool disk6_saveState(StateHelper_s *helper);
extern bool disk6_loadState(StateHelper_s *helper);
extern bool disk6_stateExtractDiskPaths(StateHelper_s *helper, JSON_ref *json);
extern bool disk6_stateExtractDiskPaths(StateHelper_s *helper, JSON_ref json);
#if DISK_TRACING
void disk6_traceToggle(const char *read_file, const char *write_file);

View File

@ -125,8 +125,7 @@ static int _load_magick(int fd) {
return -1;
}
bool emulator_saveState(const char * const path) {
int fd = -1;
bool emulator_saveState(int fd) {
bool saved = false;
#if !TESTING
@ -134,12 +133,6 @@ bool emulator_saveState(const char * const path) {
#endif
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, (const uint8_t *)SAVE_MAGICK3, SAVE_MAGICK_LEN)) {
break;
@ -180,20 +173,10 @@ bool emulator_saveState(const char * const path) {
saved = true;
} while (0);
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
}
if (!saved) {
ERRLOG("OOPS, could not write to the emulator save-state file");
unlink(path);
}
return saved;
}
bool emulator_loadState(const char * const path, int fdA, int fdB) {
int fd = -1;
bool emulator_loadState(int fd, int fdA, int fdB) {
bool loaded = false;
#if !TESTING
@ -203,12 +186,6 @@ bool emulator_loadState(const char * const path, int fdA, int fdB) {
video_setDirty(A2_DIRTY_FLAG);
do {
TEMP_FAILURE_RETRY(fd = open(path, O_RDONLY));
if (fd < 0) {
break;
}
assert(fd != 0 && "crazy platform");
int version = _load_magick(fd);
if (version < 0) {
break;
@ -270,10 +247,6 @@ bool emulator_loadState(const char * const path, int fdA, int fdB) {
loaded = true;
} while (0);
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
}
if (!loaded) {
LOG("OOPS, problem(s) encountered loading emulator save-state file");
}
@ -281,18 +254,10 @@ bool emulator_loadState(const char * const path, int fdA, int fdB) {
return loaded;
}
bool emulator_stateExtractDiskPaths(const char * const path, JSON_ref *json) {
int fd = -1;
bool emulator_stateExtractDiskPaths(int fd, JSON_ref json) {
bool loaded = false;
do {
TEMP_FAILURE_RETRY(fd = open(path, O_RDONLY));
if (fd < 0) {
ERRLOG("OOPS, cannot open path %s", path);
break;
}
assert(fd != 0 && "crazy platform");
int version = _load_magick(fd);
if (version < 0) {
break;
@ -314,10 +279,6 @@ bool emulator_stateExtractDiskPaths(const char * const path, JSON_ref *json) {
loaded = true;
} while (0);
if (fd >= 0) {
TEMP_FAILURE_RETRY(close(fd));
}
if (!loaded) {
LOG("OOPS, problem(s) encountered loading emulator save-state file");
}

View File

@ -67,13 +67,13 @@ typedef struct StateHelper_s {
} StateHelper_s;
// save current emulator state
bool emulator_saveState(const char * const path);
bool emulator_saveState(int fdState);
// load emulator state from save path
bool emulator_loadState(const char * const path, int fdA, int fdB);
// load emulator state from save file descriptor
bool emulator_loadState(int fdState, int fdA, int fdB);
// extract paths from save state file into freshly allocated JSON_ref
bool emulator_stateExtractDiskPaths(const char * const path, JSON_ref *json);
// extract path(s) and readonly status(es) from save state file into json
bool emulator_stateExtractDiskPaths(int fdState, JSON_ref json);
//
// Crash handling ...