mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-01 13:41:28 +00:00
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:
parent
d98c4afa84
commit
7411a987fa
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
20
src/disk.c
20
src/disk.c
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
45
src/misc.c
45
src/misc.c
|
@ -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");
|
||||
}
|
||||
|
|
10
src/misc.h
10
src/misc.h
|
@ -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 ...
|
||||
|
|
Loading…
Reference in New Issue
Block a user