boot games directly fast boot up

This commit is contained in:
izzy2lost
2026-03-07 03:20:03 -05:00
parent bfc1b2ac0c
commit cc051daba8
3 changed files with 197 additions and 27 deletions
+145 -4
View File
@@ -11,6 +11,7 @@
#include <jni.h>
#include <cctype>
#include <cstdint>
#include <climits>
#include <cstdio>
#include <cstdlib>
@@ -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<off_t>(-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");
}
}
}
@@ -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()
+44 -2
View File
@@ -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 {