Speed up external - #1773
Conversation
📝 WalkthroughWalkthroughChangesExternal storage path migration
Runtime asset updates
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`:
- Around line 3662-3665: Update the FFP_ENABLE path logic near ffpGameDir to
resolve the container’s established A: drive mapping through ContainerUtils
instead of calling SteamService.getAppDirPath. Use the resolved game directory
for all supported game sources, including GOG, Epic, Amazon, and custom games,
while preserving the existing /storage/ prefix check and environment-variable
behavior.
In `@app/src/main/java/app/gamenative/utils/StorageUtils.kt`:
- Around line 135-139: Update the legacy-path resolution logic around src and
dst so that when dst already exists as a directory, it returns dst.absolutePath
even if src no longer exists. Preserve the existing fallback to path for
non-directory destinations and migration failures, making repeated calls
idempotently resolve to the migrated directory.
In `@app/src/main/java/com/winlator/xenvironment/ImageFsInstaller.java`:
- Line 46: The installFromAssetsFuture flow must not persist LATEST_VERSION when
guest-library deployment fails. Update installGuestLibs() to return deployment
success, including extraction outcomes, and make installFromAssetsFuture write
the imagefs version only when that result indicates success so
installIfNeededFuture can retry failed deployments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 25922ec3-14db-4839-b247-13db11c20db2
⛔ Files ignored due to path filters (1)
app/src/modern/assets/libredirect-bionic-wx.sois excluded by!**/*.so
📒 Files selected for processing (7)
app/src/main/assets/redirect.tzstapp/src/main/java/app/gamenative/service/DownloadService.ktapp/src/main/java/app/gamenative/ui/screen/settings/SettingsGroupInterface.ktapp/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.ktapp/src/main/java/app/gamenative/utils/ContainerUtils.ktapp/src/main/java/app/gamenative/utils/StorageUtils.ktapp/src/main/java/com/winlator/xenvironment/ImageFsInstaller.java
| val ffpGameDir = runCatching { | ||
| File(SteamService.getAppDirPath(ContainerUtils.extractGameIdFromContainerId(appId))).canonicalFile.path | ||
| }.getOrDefault("") | ||
| if (ffpGameDir.startsWith("/storage/")) envVars.put("FFP_ENABLE", "1") |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
Use the container’s resolved game drive, not a Steam-only lookup.
This executes for GOG, Epic, Amazon, and custom games too, but always queries SteamService. Resolve the A: mapping established by ContainerUtils so FFP_ENABLE reflects the launched game’s actual directory.
Proposed fix
-val ffpGameDir = runCatching {
- File(SteamService.getAppDirPath(ContainerUtils.extractGameIdFromContainerId(appId))).canonicalFile.path
-}.getOrDefault("")
+val ffpGameDir = ContainerUtils.getADrivePath(container.drives)
+ ?.let { runCatching { File(it).canonicalFile.path }.getOrNull() }
+ .orEmpty()
if (ffpGameDir.startsWith("/storage/")) envVars.put("FFP_ENABLE", "1")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val ffpGameDir = runCatching { | |
| File(SteamService.getAppDirPath(ContainerUtils.extractGameIdFromContainerId(appId))).canonicalFile.path | |
| }.getOrDefault("") | |
| if (ffpGameDir.startsWith("/storage/")) envVars.put("FFP_ENABLE", "1") | |
| val ffpGameDir = ContainerUtils.getADrivePath(container.drives) | |
| ?.let { runCatching { File(it).canonicalFile.path }.getOrNull() } | |
| .orEmpty() | |
| if (ffpGameDir.startsWith("/storage/")) envVars.put("FFP_ENABLE", "1") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` around
lines 3662 - 3665, Update the FFP_ENABLE path logic near ffpGameDir to resolve
the container’s established A: drive mapping through ContainerUtils instead of
calling SteamService.getAppDirPath. Use the resolved game directory for all
supported game sources, including GOG, Epic, Amazon, and custom games, while
preserving the existing /storage/ prefix check and environment-variable
behavior.
| val src = File(path) | ||
| if (!src.isDirectory) return path | ||
| val publicRoot = publicInstallRoot(legacyRoot) ?: return path | ||
| val dst = File(publicRoot, rel) | ||
| if (dst.exists() || !ensureInstallRoot(publicRoot)) return path |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make legacy-path resolution idempotent.
After a successful first migration, src no longer exists, so a later call with the still-stored legacy path returns it at Line 136 instead of the already-migrated directory. The next container update can therefore remap A: back to a nonexistent legacy path. Return dst.absolutePath when the destination is already a directory.
Proposed fix
val src = File(path)
-if (!src.isDirectory) return path
val publicRoot = publicInstallRoot(legacyRoot) ?: return path
val dst = File(publicRoot, rel)
-if (dst.exists() || !ensureInstallRoot(publicRoot)) return path
+if (dst.isDirectory) return dst.absolutePath
+if (!src.isDirectory || dst.exists() || !ensureInstallRoot(publicRoot)) return path📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val src = File(path) | |
| if (!src.isDirectory) return path | |
| val publicRoot = publicInstallRoot(legacyRoot) ?: return path | |
| val dst = File(publicRoot, rel) | |
| if (dst.exists() || !ensureInstallRoot(publicRoot)) return path | |
| val src = File(path) | |
| val publicRoot = publicInstallRoot(legacyRoot) ?: return path | |
| val dst = File(publicRoot, rel) | |
| if (dst.isDirectory) return dst.absolutePath | |
| if (!src.isDirectory || dst.exists() || !ensureInstallRoot(publicRoot)) return path |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/app/gamenative/utils/StorageUtils.kt` around lines 135 -
139, Update the legacy-path resolution logic around src and dst so that when dst
already exists as a directory, it returns dst.absolutePath even if src no longer
exists. Preserve the existing fallback to path for non-directory destinations
and migration failures, making repeated calls idempotently resolve to the
migrated directory.
|
|
||
| public abstract class ImageFsInstaller { | ||
| public static final byte LATEST_VERSION = 28; | ||
| public static final byte LATEST_VERSION = 29; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not mark the imagefs latest when guest-library deployment fails.
With LATEST_VERSION set to 29, installFromAssetsFuture() can call installGuestLibs() at Line [170], receive an early failure return, and still persist version 29 at Line [171]. Since installIfNeededFuture() skips installation when the stored version is at least 29, subsequent launches will not retry the missing or stale redirect libraries.
Make installGuestLibs() return success/failure—including extraction results—and write the imagefs version only after successful deployment.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/com/winlator/xenvironment/ImageFsInstaller.java` at line
46, The installFromAssetsFuture flow must not persist LATEST_VERSION when
guest-library deployment fails. Update installGuestLibs() to return deployment
success, including extraction outcomes, and make installFromAssetsFuture write
the imagefs version only when that result indicates success so
installIfNeededFuture can retry failed deployments.
There was a problem hiding this comment.
7 issues found across 8 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt">
<violation number="1" location="app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt:3662">
P2: This FFP_ENABLE logic only resolves the game directory through `SteamService.getAppDirPath()`, which means it will silently fail (caught by `runCatching`) for non-Steam games (GOG, Epic, Amazon, custom). As a result, FFP_ENABLE will never be set for those game types even when they're installed on external storage. Consider resolving the `A:` drive mapping from `container.drives` instead, which would work regardless of game source.</violation>
<violation number="2" location="app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt:3663">
P1: The `canonicalFile` call on line 3663 resolves symbolic links in the game directory path. On Android, `/storage/emulated/0` is commonly a symlink to a different mount point (e.g., `/mnt/user/0/primary` on pre-FUSE devices, or `/mnt/shell/emulated/0`). After `canonicalFile` resolves these symlinks, the resulting path will typically **not** start with `/storage/`, so the `startsWith("/storage/")` check on the next line silently evaluates to `false` and `FFP_ENABLE` is never set — even for games on external storage. This defeats the purpose of the optimization for the primary external storage volume.
Since `getAppDirPath` already returns a clean constructed path (without `..` or `.` components), `canonicalFile` is unnecessary here. Remove it so the check runs against the logical `/storage/...` path that Android presents to apps.</violation>
</file>
<file name="app/src/main/java/app/gamenative/utils/StorageUtils.kt">
<violation number="1" location="app/src/main/java/app/gamenative/utils/StorageUtils.kt:112">
P1: Modern builds select a root-level shared-storage directory they cannot create or write on Android 11+, so external installs silently fall back here or later download operations fail. Keep modern installs app-scoped, or gate this public-root path behind the existing legacy all-files-access flow.</violation>
<violation number="2" location="app/src/main/java/app/gamenative/utils/StorageUtils.kt:136">
P1: `migrateLegacyGameDir` is not idempotent: after a successful migration, `src` no longer exists, so `!src.isDirectory` is true and the function returns the original (now non-existent) legacy `path`. On a subsequent container update, `A:` can be remapped to this stale path. The fix is to check whether `dst` already exists as a directory *before* the `src.isDirectory` guard, and return `dst.absolutePath` in that case.</violation>
<violation number="3" location="app/src/main/java/app/gamenative/utils/StorageUtils.kt:141">
P1: Migrated Epic, GOG, and Amazon games work for the migration launch but revert to their deleted legacy path on the next launch because the source-specific persisted install path is never updated. Persist the destination path with the game metadata as part of a successful migration, or resolve legacy paths to the destination when the source no longer exists.</violation>
</file>
<file name="app/src/main/java/app/gamenative/service/DownloadService.kt">
<violation number="1" location="app/src/main/java/app/gamenative/service/DownloadService.kt:60">
P1: Interrupted GOG, Epic, and Amazon downloads on existing external installs stop being resumable after startup. This repoints the sole external root without migrating legacy partial directories or retaining the old root in each service’s scan set; migrate those directories before updating the preference, or preserve the legacy root for discovery.</violation>
</file>
<file name="app/src/main/java/com/winlator/xenvironment/ImageFsInstaller.java">
<violation number="1" location="app/src/main/java/com/winlator/xenvironment/ImageFsInstaller.java:46">
P1: Bumping `LATEST_VERSION` to 29 means that if `installGuestLibs()` fails during `installFromAssetsFuture()`, version 29 is still persisted. Since `installIfNeededFuture()` skips installation when the stored version meets `LATEST_VERSION`, subsequent launches will never retry the failed redirect library deployment. Consider making the version write conditional on successful guest library installation.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if (!envVars.has("WINEESYNC")) envVars.put("WINEESYNC", "1") | ||
|
|
||
| val ffpGameDir = runCatching { | ||
| File(SteamService.getAppDirPath(ContainerUtils.extractGameIdFromContainerId(appId))).canonicalFile.path |
There was a problem hiding this comment.
P1: The canonicalFile call on line 3663 resolves symbolic links in the game directory path. On Android, /storage/emulated/0 is commonly a symlink to a different mount point (e.g., /mnt/user/0/primary on pre-FUSE devices, or /mnt/shell/emulated/0). After canonicalFile resolves these symlinks, the resulting path will typically not start with /storage/, so the startsWith("/storage/") check on the next line silently evaluates to false and FFP_ENABLE is never set — even for games on external storage. This defeats the purpose of the optimization for the primary external storage volume.
Since getAppDirPath already returns a clean constructed path (without .. or . components), canonicalFile is unnecessary here. Remove it so the check runs against the logical /storage/... path that Android presents to apps.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt, line 3663:
<comment>The `canonicalFile` call on line 3663 resolves symbolic links in the game directory path. On Android, `/storage/emulated/0` is commonly a symlink to a different mount point (e.g., `/mnt/user/0/primary` on pre-FUSE devices, or `/mnt/shell/emulated/0`). After `canonicalFile` resolves these symlinks, the resulting path will typically **not** start with `/storage/`, so the `startsWith("/storage/")` check on the next line silently evaluates to `false` and `FFP_ENABLE` is never set — even for games on external storage. This defeats the purpose of the optimization for the primary external storage volume.
Since `getAppDirPath` already returns a clean constructed path (without `..` or `.` components), `canonicalFile` is unnecessary here. Remove it so the check runs against the logical `/storage/...` path that Android presents to apps.</comment>
<file context>
@@ -3658,6 +3658,12 @@ private fun setupXEnvironment(
if (!envVars.has("WINEESYNC")) envVars.put("WINEESYNC", "1")
+
+ val ffpGameDir = runCatching {
+ File(SteamService.getAppDirPath(ContainerUtils.extractGameIdFromContainerId(appId))).canonicalFile.path
+ }.getOrDefault("")
+ if (ffpGameDir.startsWith("/storage/")) envVars.put("FFP_ENABLE", "1")
</file context>
| if (!src.isDirectory) return path | ||
| val publicRoot = publicInstallRoot(legacyRoot) ?: return path | ||
| val dst = File(publicRoot, rel) | ||
| if (dst.exists() || !ensureInstallRoot(publicRoot)) return path |
There was a problem hiding this comment.
P1: migrateLegacyGameDir is not idempotent: after a successful migration, src no longer exists, so !src.isDirectory is true and the function returns the original (now non-existent) legacy path. On a subsequent container update, A: can be remapped to this stale path. The fix is to check whether dst already exists as a directory before the src.isDirectory guard, and return dst.absolutePath in that case.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/utils/StorageUtils.kt, line 136:
<comment>`migrateLegacyGameDir` is not idempotent: after a successful migration, `src` no longer exists, so `!src.isDirectory` is true and the function returns the original (now non-existent) legacy `path`. On a subsequent container update, `A:` can be remapped to this stale path. The fix is to check whether `dst` already exists as a directory *before* the `src.isDirectory` guard, and return `dst.absolutePath` in that case.</comment>
<file context>
@@ -98,6 +98,55 @@ object StorageUtils {
+ val legacyRoot = File(path.substring(0, filesIdx + "/files".length))
+ val rel = path.substring(filesIdx + "/files/".length)
+ val src = File(path)
+ if (!src.isDirectory) return path
+ val publicRoot = publicInstallRoot(legacyRoot) ?: return path
+ val dst = File(publicRoot, rel)
</file context>
| if (!src.isDirectory) return path | |
| val publicRoot = publicInstallRoot(legacyRoot) ?: return path | |
| val dst = File(publicRoot, rel) | |
| if (dst.exists() || !ensureInstallRoot(publicRoot)) return path | |
| val publicRoot = publicInstallRoot(legacyRoot) ?: return path | |
| val dst = File(publicRoot, rel) | |
| if (dst.isDirectory) return dst.absolutePath | |
| if (!src.isDirectory || dst.exists() || !ensureInstallRoot(publicRoot)) return path |
| val path = appFilesDir.absolutePath | ||
| val idx = path.indexOf("/Android/data/") | ||
| if (idx <= 0) return null | ||
| return File(path.substring(0, idx), PUBLIC_INSTALL_DIR_NAME) |
There was a problem hiding this comment.
P1: Modern builds select a root-level shared-storage directory they cannot create or write on Android 11+, so external installs silently fall back here or later download operations fail. Keep modern installs app-scoped, or gate this public-root path behind the existing legacy all-files-access flow.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/utils/StorageUtils.kt, line 112:
<comment>Modern builds select a root-level shared-storage directory they cannot create or write on Android 11+, so external installs silently fall back here or later download operations fail. Keep modern installs app-scoped, or gate this public-root path behind the existing legacy all-files-access flow.</comment>
<file context>
@@ -98,6 +98,55 @@ object StorageUtils {
+ val path = appFilesDir.absolutePath
+ val idx = path.indexOf("/Android/data/")
+ if (idx <= 0) return null
+ return File(path.substring(0, idx), PUBLIC_INSTALL_DIR_NAME)
+ }
+
</file context>
| val dst = File(publicRoot, rel) | ||
| if (dst.exists() || !ensureInstallRoot(publicRoot)) return path | ||
| dst.parentFile?.mkdirs() | ||
| return if (src.renameTo(dst)) { |
There was a problem hiding this comment.
P1: Migrated Epic, GOG, and Amazon games work for the migration launch but revert to their deleted legacy path on the next launch because the source-specific persisted install path is never updated. Persist the destination path with the game metadata as part of a successful migration, or resolve legacy paths to the destination when the source no longer exists.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/utils/StorageUtils.kt, line 141:
<comment>Migrated Epic, GOG, and Amazon games work for the migration launch but revert to their deleted legacy path on the next launch because the source-specific persisted install path is never updated. Persist the destination path with the game metadata as part of a successful migration, or resolve legacy paths to the destination when the source no longer exists.</comment>
<file context>
@@ -98,6 +98,55 @@ object StorageUtils {
+ val dst = File(publicRoot, rel)
+ if (dst.exists() || !ensureInstallRoot(publicRoot)) return path
+ dst.parentFile?.mkdirs()
+ return if (src.renameTo(dst)) {
+ Timber.i("Migrated game dir $path to ${dst.absolutePath}")
+ dst.absolutePath
</file context>
| val public = StorageUtils.publicInstallRoot(File(pref)) ?: return | ||
| if (StorageUtils.ensureInstallRoot(public)) { | ||
| Timber.i("Migrating external install root from $pref to ${public.absolutePath}") | ||
| PrefManager.externalStoragePath = public.absolutePath |
There was a problem hiding this comment.
P1: Interrupted GOG, Epic, and Amazon downloads on existing external installs stop being resumable after startup. This repoints the sole external root without migrating legacy partial directories or retaining the old root in each service’s scan set; migrate those directories before updating the preference, or preserve the legacy root for discovery.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/service/DownloadService.kt, line 60:
<comment>Interrupted GOG, Epic, and Amazon downloads on existing external installs stop being resumable after startup. This repoints the sole external root without migrating legacy partial directories or retaining the old root in each service’s scan set; migrate those directories before updating the preference, or preserve the legacy root for discovery.</comment>
<file context>
@@ -37,10 +38,27 @@ object DownloadService {
+ val public = StorageUtils.publicInstallRoot(File(pref)) ?: return
+ if (StorageUtils.ensureInstallRoot(public)) {
+ Timber.i("Migrating external install root from $pref to ${public.absolutePath}")
+ PrefManager.externalStoragePath = public.absolutePath
+ }
}
</file context>
|
|
||
| public abstract class ImageFsInstaller { | ||
| public static final byte LATEST_VERSION = 28; | ||
| public static final byte LATEST_VERSION = 29; |
There was a problem hiding this comment.
P1: Bumping LATEST_VERSION to 29 means that if installGuestLibs() fails during installFromAssetsFuture(), version 29 is still persisted. Since installIfNeededFuture() skips installation when the stored version meets LATEST_VERSION, subsequent launches will never retry the failed redirect library deployment. Consider making the version write conditional on successful guest library installation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/com/winlator/xenvironment/ImageFsInstaller.java, line 46:
<comment>Bumping `LATEST_VERSION` to 29 means that if `installGuestLibs()` fails during `installFromAssetsFuture()`, version 29 is still persisted. Since `installIfNeededFuture()` skips installation when the stored version meets `LATEST_VERSION`, subsequent launches will never retry the failed redirect library deployment. Consider making the version write conditional on successful guest library installation.</comment>
<file context>
@@ -43,7 +43,7 @@
public abstract class ImageFsInstaller {
- public static final byte LATEST_VERSION = 28;
+ public static final byte LATEST_VERSION = 29;
private static void resetContainerImgVersions(Context context) {
</file context>
| envVars.remove("VKD3D_FRAME_RATE") | ||
| if (!envVars.has("WINEESYNC")) envVars.put("WINEESYNC", "1") | ||
|
|
||
| val ffpGameDir = runCatching { |
There was a problem hiding this comment.
P2: This FFP_ENABLE logic only resolves the game directory through SteamService.getAppDirPath(), which means it will silently fail (caught by runCatching) for non-Steam games (GOG, Epic, Amazon, custom). As a result, FFP_ENABLE will never be set for those game types even when they're installed on external storage. Consider resolving the A: drive mapping from container.drives instead, which would work regardless of game source.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt, line 3662:
<comment>This FFP_ENABLE logic only resolves the game directory through `SteamService.getAppDirPath()`, which means it will silently fail (caught by `runCatching`) for non-Steam games (GOG, Epic, Amazon, custom). As a result, FFP_ENABLE will never be set for those game types even when they're installed on external storage. Consider resolving the `A:` drive mapping from `container.drives` instead, which would work regardless of game source.</comment>
<file context>
@@ -3658,6 +3658,12 @@ private fun setupXEnvironment(
envVars.remove("VKD3D_FRAME_RATE")
if (!envVars.has("WINEESYNC")) envVars.put("WINEESYNC", "1")
+
+ val ffpGameDir = runCatching {
+ File(SteamService.getAppDirPath(ContainerUtils.extractGameIdFromContainerId(appId))).canonicalFile.path
+ }.getOrDefault("")
</file context>
This reverts commit a5d0c28.
Description
migrate external games to public external storage to speed up, and update redirect shims to load external storage games faster
Recording
Type of Change
Checklist
#code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.CONTRIBUTING.md.Summary by cubic
Move external installs from app-specific Android/data to a public GameNative folder on each external volume to remove FUSE overhead and speed up launches and IO. Updated redirect shims and env handling so external games start faster.
New Features
Migration
Written for commit 8a78fd7. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes