From cc051daba89e6372325e55cf8af95574399bb7c5 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Sat, 7 Mar 2026 03:20:03 -0500 Subject: [PATCH] boot games directly fast boot up --- android/app/src/main/cpp/xemu_android.cpp | 149 +++++++++++++++++- .../izzy2lost/x1box/GameLibraryActivity.kt | 29 +--- block/file-posix.c | 46 +++++- 3 files changed, 197 insertions(+), 27 deletions(-) diff --git a/android/app/src/main/cpp/xemu_android.cpp b/android/app/src/main/cpp/xemu_android.cpp index c2da5d723b..d83e8bbe02 100644 --- a/android/app/src/main/cpp/xemu_android.cpp +++ b/android/app/src/main/cpp/xemu_android.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,13 @@ #include "xemu-settings.h" +struct Error; +struct AddfdInfo; +extern "C" AddfdInfo* monitor_fdset_add_fd(int fd, bool has_fdset_id, + int64_t fdset_id, + const char* opaque, + Error** errp); + namespace { constexpr const char* kLogTag = "xemu-android"; constexpr const char* kPrefsName = "x1box_prefs"; @@ -56,6 +64,8 @@ static void LogErrorFmt(const char* fmt, const char* detail) { __android_log_print(ANDROID_LOG_ERROR, kLogTag, fmt, detail); } +static int g_next_dvd_fdset_id = 9000; + static bool EnsureDirExists(const std::string& path) { if (path.empty()) return false; if (mkdir(path.c_str(), 0755) == 0) return true; @@ -417,6 +427,11 @@ static int GetPrefInt(JNIEnv* env, jobject activity, const char* key, int defVal return out; } +static bool IsSeekableFd(int fd) { + errno = 0; + return lseek(fd, 0, SEEK_CUR) != static_cast(-1); +} + static bool CopyUriToPath(JNIEnv* env, jobject activity, const std::string& uriString, const std::string& path) { if (!env || !activity || uriString.empty() || path.empty()) return false; @@ -576,6 +591,127 @@ cleanup: return success; } +static std::string OpenUriAsReadOnlyFdPath(JNIEnv* env, + jobject activity, + const std::string& uriString) { + if (!env || !activity || uriString.empty()) { + return {}; + } + + std::string out; + jclass activityClass = nullptr; + jobject resolver = nullptr; + jclass resolverClass = nullptr; + jclass uriClass = nullptr; + jobject uri = nullptr; + jobject parcelFd = nullptr; + jclass parcelFdClass = nullptr; + + activityClass = env->GetObjectClass(activity); + if (!activityClass) { + goto cleanup; + } + + { + jmethodID getContentResolver = env->GetMethodID( + activityClass, "getContentResolver", + "()Landroid/content/ContentResolver;"); + if (!getContentResolver) { + goto cleanup; + } + resolver = env->CallObjectMethod(activity, getContentResolver); + if (HasException(env, "getContentResolver") || !resolver) { + goto cleanup; + } + } + + uriClass = env->FindClass("android/net/Uri"); + if (!uriClass) { + goto cleanup; + } + { + jmethodID parse = env->GetStaticMethodID( + uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); + if (!parse) { + goto cleanup; + } + jstring juri = env->NewStringUTF(uriString.c_str()); + if (!juri) { + goto cleanup; + } + uri = env->CallStaticObjectMethod(uriClass, parse, juri); + env->DeleteLocalRef(juri); + if (HasException(env, "Uri.parse") || !uri) { + goto cleanup; + } + } + + resolverClass = env->GetObjectClass(resolver); + if (!resolverClass) { + goto cleanup; + } + { + jmethodID openFileDescriptor = env->GetMethodID( + resolverClass, "openFileDescriptor", + "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;"); + if (!openFileDescriptor) { + goto cleanup; + } + + jstring readMode = env->NewStringUTF("r"); + if (!readMode) { + goto cleanup; + } + parcelFd = env->CallObjectMethod(resolver, openFileDescriptor, uri, readMode); + env->DeleteLocalRef(readMode); + if (HasException(env, "openFileDescriptor") || !parcelFd) { + goto cleanup; + } + } + + parcelFdClass = env->GetObjectClass(parcelFd); + if (!parcelFdClass) { + goto cleanup; + } + { + jmethodID detachFd = env->GetMethodID(parcelFdClass, "detachFd", "()I"); + if (!detachFd) { + goto cleanup; + } + jint fd = env->CallIntMethod(parcelFd, detachFd); + if (HasException(env, "ParcelFileDescriptor.detachFd") || fd < 0) { + goto cleanup; + } + if (!IsSeekableFd(fd)) { + LogInfo("DVD descriptor is not seekable; falling back to staged copy"); + close(fd); + goto cleanup; + } + + { + const int fdsetId = g_next_dvd_fdset_id++; + AddfdInfo* fdinfo = + monitor_fdset_add_fd(fd, true, fdsetId, "android-dvd", nullptr); + if (!fdinfo) { + LogError("Failed to register DVD fd with QEMU fdset"); + close(fd); + goto cleanup; + } + out = "/dev/fdset/" + std::to_string(fdsetId); + } + } + +cleanup: + if (parcelFdClass) env->DeleteLocalRef(parcelFdClass); + if (parcelFd) env->DeleteLocalRef(parcelFd); + if (uri) env->DeleteLocalRef(uri); + if (uriClass) env->DeleteLocalRef(uriClass); + if (resolverClass) env->DeleteLocalRef(resolverClass); + if (resolver) env->DeleteLocalRef(resolver); + if (activityClass) env->DeleteLocalRef(activityClass); + return out; +} + struct EmulatorSettings { int surface_scale = 1; // 1, 2, or 3 int frame_rate_limit = 60; // 30 or 60 @@ -796,11 +932,16 @@ static SetupFiles SyncSetupFiles() { out.dvd = dvdPath; } if (out.dvd.empty() && !dvdUri.empty()) { - out.dvd = base + "/dvd.iso"; - if (CopyUriToPath(env, activity, dvdUri, out.dvd)) { - LogInfo("DVD image synced to app storage"); + out.dvd = OpenUriAsReadOnlyFdPath(env, activity, dvdUri); + if (!out.dvd.empty()) { + LogInfoFmt("DVD image opened directly from SAF fd: %s", out.dvd.c_str()); } else { - LogError("Failed to sync DVD image"); + out.dvd = base + "/dvd.iso"; + if (CopyUriToPath(env, activity, dvdUri, out.dvd)) { + LogInfo("DVD image synced to app storage"); + } else { + LogError("Failed to sync DVD image"); + } } } diff --git a/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt index 731a6b259a..287204e7c0 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/GameLibraryActivity.kt @@ -208,15 +208,19 @@ class GameLibraryActivity : AppCompatActivity() { return false } - val internalDvdIso = resolveInternalDvdIsoFile() - if (internalDvdIso == null || !internalDvdIso.isFile) { + val dvdUri = prefs.getString("dvdUri", null)?.let(Uri::parse) + val dvdPath = prefs.getString("dvdPath", null) + val hasDvd = when { + dvdUri != null -> hasPersistedReadPermission(dvdUri) + dvdPath != null -> File(dvdPath).isFile + else -> false + } + if (!hasDvd) { Toast.makeText(this, getString(R.string.library_restart_failed), Toast.LENGTH_SHORT).show() return false } prefs.edit() - .putString("dvdPath", internalDvdIso.absolutePath) - .remove("dvdUri") .putBoolean("skip_game_picker", false) .apply() @@ -224,23 +228,6 @@ class GameLibraryActivity : AppCompatActivity() { return true } - private fun resolveInternalDvdIsoFile(): File? { - val external = getExternalFilesDir(null) - if (external != null) { - val externalIso = File(external, "x1box/dvd.iso") - if (externalIso.isFile) { - return externalIso - } - } - - val internalIso = File(filesDir, "x1box/dvd.iso") - if (internalIso.isFile) { - return internalIso - } - - return null - } - private fun launchMainActivityForRestart() { startActivity(Intent(this, MainActivity::class.java)) finish() diff --git a/block/file-posix.c b/block/file-posix.c index 45c457cff1..2f2f61ecf6 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -208,6 +208,39 @@ typedef struct BDRVRawReopenState { bool check_cache_dropped; } BDRVRawReopenState; +#ifdef __ANDROID__ +static bool raw_android_fd_path(const char *filename) +{ + return filename && + (g_str_has_prefix(filename, "/proc/self/fd/") || + g_str_has_prefix(filename, "/dev/fdset/")); +} + +static bool raw_android_seekable_fd(BlockDriverState *bs, int fd) +{ + off_t current; + + if (!raw_android_fd_path(bs->filename)) { + return false; + } + + current = lseek(fd, 0, SEEK_CUR); + if (current < 0) { + current = 0; + } + + if (lseek(fd, 0, SEEK_END) < 0) { + return false; + } + + if (lseek(fd, current, SEEK_SET) < 0) { + lseek(fd, 0, SEEK_SET); + } + + return true; +} +#endif + static int fd_open(BlockDriverState *bs) { BDRVRawState *s = bs->opaque; @@ -785,13 +818,22 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, goto fail; } +#ifdef __ANDROID__ + bool android_seekable_fd = !device && + !S_ISREG(st.st_mode) && + (s->open_flags & O_ACCMODE) == O_RDONLY && + raw_android_seekable_fd(bs, s->fd); +#else + bool android_seekable_fd = false; +#endif + if (!device) { - if (!S_ISREG(st.st_mode)) { + if (!S_ISREG(st.st_mode) && !android_seekable_fd) { error_setg(errp, "'%s' driver requires '%s' to be a regular file", bs->drv->format_name, bs->filename); ret = -EINVAL; goto fail; - } else { + } else if (S_ISREG(st.st_mode)) { s->has_fallocate = true; } } else {