mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
boot games directly fast boot up
This commit is contained in:
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user