Fix Batman: Arkham Asylum GOTY relaunch black screen#950
Fix Batman: Arkham Asylum GOTY relaunch black screen#950Misazam wants to merge 4 commits intoutkarshdalal:masterfrom
Conversation
This change addresses a reproducible launch-state regression for Batman: Arkham Asylum GOTY (Steam app id 35140) in GameNative, and also adds DirectX preinstallation support required by this title's redistributables. Observed behaviour: - Fresh install/download could launch successfully on first run. - Exiting the game normally and relaunching it later could leave GameNative on a black screen. - The black screen was not an Android app crash. The actual game process (ShippingPC-BmGame.exe) was starting and then terminating, while Steam/coldclient container state could remain alive. Reproduction used during investigation: - Device: OnePlus OPD2415 - Android: 16 - Game: Batman: Arkham Asylum GOTY - Store: Steam - App id: 35140 Root cause: Batman was sensitive to stale state surviving between launches. Cleaning only the Steam coldclient marker improved the situation but was not sufficient by itself. Reliable recovery required clearing the stale launch artifacts that persisted across sessions: - ColdClientLoader.ini in the Steam prefix - STEAM_COLDCLIENT_USED marker in the game install directory - BmEngine.ini and BmGame.ini under the game's Documents config path - ShippingPC-BmGame.dxvk-cache in the container cache Changes included in this commit: - Add a Batman-specific Steam game fix for app id 35140 that clears the stale launch/config/cache files above before launch. - Register that fix in GameFixesRegistry so it is applied automatically for Batman. - Regenerate ColdClientLoader.ini more defensively by deleting stale coldclient launch state before writing a new ini. - Add DirectX preinstallation detection/execution through DXSETUP.exe and track it with a dedicated marker, because Batman's redistributables include legacy DirectX components that need to be installed for a clean first run. Why this matters: Without this cleanup, Batman can appear to fail randomly after a successful first boot, when the issue is actually deterministic stale launch state reuse. This makes the regression highly visible and user-facing because the game works once, then later relaunches degrade into a black screen. Validation: - assembleDebug completed successfully. - Verified on-device on the OnePlus OPD2415 / Android 16 test setup. - Confirmed successful cycle: launch game -> exit normally -> close app -> reopen app -> launch game again.
📝 WalkthroughWalkthroughAdds a new Steam game fix for Batman Arkham Asylum GOTY (Steam ID 35140) and registers it in the game fixes registry; also a non-functional whitespace change in Steam utils. Changes
Sequence DiagramsequenceDiagram
participant Caller
participant STEAM_Fix_35140
participant Container
participant FileSystem
participant Logger
Caller->>STEAM_Fix_35140: apply(context, gameId, installPath, installPathWindows, container)
STEAM_Fix_35140->>Container: inspect installPath for STEAM_COLDCLIENT_USED marker
alt marker present
STEAM_Fix_35140->>FileSystem: delete marker file
FileSystem-->>STEAM_Fix_35140: success/failure
STEAM_Fix_35140->>Logger: log marker removal result
else marker absent
STEAM_Fix_35140->>Logger: note marker not present
end
loop each stale path
STEAM_Fix_35140->>FileSystem: attempt delete stale path (INI, DXVK cache, ...)
FileSystem-->>STEAM_Fix_35140: success/failure
STEAM_Fix_35140->>Logger: log deletion result
end
STEAM_Fix_35140-->>Caller: return changed (true/false)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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.
3 issues found across 6 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/utils/preInstallSteps/DirectXStep.kt">
<violation number="1" location="app/src/main/java/app/gamenative/utils/preInstallSteps/DirectXStep.kt:55">
P2: DirectX installer command uses an unquoted executable path in a `cmd /c` chain, so paths with spaces can be misparsed and fail to execute.</violation>
</file>
<file name="app/src/main/java/app/gamenative/gamefixes/STEAM_35140.kt">
<violation number="1" location="app/src/main/java/app/gamenative/gamefixes/STEAM_35140.kt:41">
P1: The game fix unconditionally deletes user config INI files and DXVK cache on every apply() run, causing repeated settings loss and cache rebuild overhead.</violation>
</file>
<file name="app/src/main/java/app/gamenative/utils/SteamUtils.kt">
<violation number="1" location="app/src/main/java/app/gamenative/utils/SteamUtils.kt:350">
P2: Clearing `STEAM_COLDCLIENT_USED` in `writeColdClientIni` conflicts with its guard role in `replaceSteamclientDll`, enabling repeated replacement and `.orig` backup overwrite across launches.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| Timber.tag("GameFixes").i("Cleared stale coldclient marker for Batman Arkham Asylum GOTY") | ||
| } | ||
|
|
||
| staleFiles.forEach { file -> |
There was a problem hiding this comment.
P1: The game fix unconditionally deletes user config INI files and DXVK cache on every apply() run, causing repeated settings loss and cache rebuild overhead.
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/gamefixes/STEAM_35140.kt, line 41:
<comment>The game fix unconditionally deletes user config INI files and DXVK cache on every apply() run, causing repeated settings loss and cache rebuild overhead.</comment>
<file context>
@@ -0,0 +1,52 @@
+ Timber.tag("GameFixes").i("Cleared stale coldclient marker for Batman Arkham Asylum GOTY")
+ }
+
+ staleFiles.forEach { file ->
+ if (file.exists() && file.delete()) {
+ changed = true
</file context>
app/src/main/java/app/gamenative/utils/preInstallSteps/DirectXStep.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/main/java/app/gamenative/utils/preInstallSteps/DirectXStep.kt`:
- Around line 48-56: The queued DirectX installer commands are not quoted and
can break on paths with spaces, and duplicate installers may be added when scan
roots overlap; update the logic in DirectXStep (the block that builds
relativePath, winePath and adds to parts) to wrap the winePath in quotes when
composing the command (e.g., ensure the executable path portion is quoted before
appending "/silent") and prevent duplicates by checking the composed command (or
maintaining a Set of added commands) before calling parts.add(...), so only
unique quoted commands are pushed.
In `@app/src/main/java/app/gamenative/utils/SteamUtils.kt`:
- Around line 339-355: The current writeColdClientIni() unconditionally calls
MarkerUtils.removeMarker(Marker.STEAM_COLDCLIENT_USED) which globally clears the
skip guard used by replaceSteamclientDll() and causes unnecessary DLL
replacements for unrelated apps; change this to scoped clearing by either (A)
adding a small whitelist/Set of known affected app IDs (e.g., AFFECTED_APP_IDS)
and only calling MarkerUtils.removeMarker(...) when steamAppId is in that set,
or (B) gating the removal with explicit stale-state detection (for example,
check iniFile.exists() and its lastModified() is older than a configurable
threshold) before calling MarkerUtils.removeMarker; update writeColdClientIni()
to implement one of these checks and keep references to writeColdClientIni(),
replaceSteamclientDll(), MarkerUtils.removeMarker, and
Marker.STEAM_COLDCLIENT_USED so reviewers can locate the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ef360b67-ee33-4216-a103-cce4332c19d9
📒 Files selected for processing (6)
app/src/main/java/app/gamenative/enums/Marker.ktapp/src/main/java/app/gamenative/gamefixes/GameFixesRegistry.ktapp/src/main/java/app/gamenative/gamefixes/STEAM_35140.ktapp/src/main/java/app/gamenative/utils/PreInstallSteps.ktapp/src/main/java/app/gamenative/utils/SteamUtils.ktapp/src/main/java/app/gamenative/utils/preInstallSteps/DirectXStep.kt
| val relativePath = exeFile | ||
| .relativeTo(gameDir) | ||
| .path | ||
| .replace('/', '\\') | ||
| val winePath = "A:\\$relativePath" | ||
| Timber.tag("DirectXStep").i("Queued DirectX installer: $winePath") | ||
|
|
||
| parts.add("$winePath /silent") | ||
| } |
There was a problem hiding this comment.
Quote DXSETUP paths and dedupe queued commands.
The command string is built without quoting the executable path, which can fail for redistributable paths containing spaces. Also, deduping avoids running the same installer twice when scan roots overlap.
Proposed hardening
- val parts = mutableListOf<String>()
+ val parts = linkedSetOf<String>()
@@
- parts.add("$winePath /silent")
+ parts.add("\"$winePath\" /silent")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/main/java/app/gamenative/utils/preInstallSteps/DirectXStep.kt` around
lines 48 - 56, The queued DirectX installer commands are not quoted and can
break on paths with spaces, and duplicate installers may be added when scan
roots overlap; update the logic in DirectXStep (the block that builds
relativePath, winePath and adds to parts) to wrap the winePath in quotes when
composing the command (e.g., ensure the executable path portion is quoted before
appending "/silent") and prevent duplicates by checking the composed command (or
maintaining a Set of added commands) before calling parts.add(...), so only
unique quoted commands are pushed.
|
I believe trying to install directx will fail on many proton versions and basically cause a stall. Have you tested the different proton versions? |
DirectX preinstallation was only observed as a possible factor in one failed first-run attempt and was not validated across a matrix of Proton versions. The relaunch black screen fix that was actually reproduced and verified on-device is the stale Batman/Steam launch-state cleanup, so this commit removes the broader DirectX preinstall change and keeps the PR narrowly scoped to the confirmed fix.
|
Good point. I had only validated that part on my device with proton-10.0-arm64ec-2, and I had not tested a proper matrix across different Proton versions. To be precise, the DirectX preinstall part only came up after one failed first-run attempt, and after that I had to redownload the game from scratch, so I do not consider that part properly verified. The part that was actually reproduced and confirmed on-device was the relaunch black screen fix itself, caused by stale Batman/Steam launch state between sessions. I’ve removed the DirectX preinstall part from the PR and kept only the narrower relaunch-state cleanup that was consistently reproducible and tested. Thank you for your reply : ) |
Remove the global stale coldclient reset from SteamUtils.writeColdClientIni(). The relaunch regression we reproduced and validated was specific to Batman: Arkham Asylum GOTY, and the PR already has a dedicated STEAM_35140 fix that clears the stale marker, stale ColdClientLoader.ini, and Batman-specific config/cache files before launch. Keeping the reset global would broaden the behavior for unrelated Steam titles without equivalent validation.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/main/java/app/gamenative/gamefixes/STEAM_35140.kt`:
- Around line 23-30: The DXVK cache staleFiles entry incorrectly targets
File(container.rootDir, ".cache/ShippingPC-BmGame.dxvk-cache") which points to
<rootDir>/.cache; change it to point under the container user's home cache by
creating the File with the path
"home/${ImageFs.USER}/.cache/ShippingPC-BmGame.dxvk-cache" so the entry in the
staleFiles list uses File(container.rootDir,
"home/${ImageFs.USER}/.cache/ShippingPC-BmGame.dxvk-cache"); update the
staleFiles list alongside the other entries (e.g., where staleFiles is defined)
to use ImageFs.USER in the path.
- Around line 34-39: The code checks MarkerUtils.hasMarker(installPath,
Marker.STEAM_COLDCLIENT_USED) then calls MarkerUtils.removeMarker(...) but lacks
a warning when removeMarker returns false; update the conditional to detect the
case where hasMarker is true and removeMarker returns false and log a warning
via Timber.tag("GameFixes").w(...) (include installPath and
Marker.STEAM_COLDCLIENT_USED in the message) so failed deletions are visible,
while keeping the existing path that sets changed = true and the info log when
removal succeeds.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c7e3cc92-a897-42bc-84d8-73aff00b573a
📒 Files selected for processing (2)
app/src/main/java/app/gamenative/gamefixes/GameFixesRegistry.ktapp/src/main/java/app/gamenative/gamefixes/STEAM_35140.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/java/app/gamenative/gamefixes/GameFixesRegistry.kt
| val userDocumentsConfigPath = | ||
| ".wine/drive_c/users/${ImageFs.USER}/Documents/Square Enix/Batman Arkham Asylum GOTY/BmGame/Config" | ||
| val staleFiles = listOf( | ||
| File(container.rootDir, ".wine/drive_c/Program Files (x86)/Steam/ColdClientLoader.ini"), | ||
| File(container.rootDir, "$userDocumentsConfigPath/BmEngine.ini"), | ||
| File(container.rootDir, "$userDocumentsConfigPath/BmGame.ini"), | ||
| File(container.rootDir, ".cache/ShippingPC-BmGame.dxvk-cache"), | ||
| ) |
There was a problem hiding this comment.
Fix DXVK cache target path under container.rootDir.
Line 29 currently targets <rootDir>/.cache/..., but container user cache is under /home/${ImageFs.USER}/.cache. This misses the intended stale cache file.
💡 Proposed fix
val staleFiles = listOf(
File(container.rootDir, ".wine/drive_c/Program Files (x86)/Steam/ColdClientLoader.ini"),
File(container.rootDir, "$userDocumentsConfigPath/BmEngine.ini"),
File(container.rootDir, "$userDocumentsConfigPath/BmGame.ini"),
- File(container.rootDir, ".cache/ShippingPC-BmGame.dxvk-cache"),
+ File(container.rootDir, "home/${ImageFs.USER}/.cache/ShippingPC-BmGame.dxvk-cache"),
)📝 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 userDocumentsConfigPath = | |
| ".wine/drive_c/users/${ImageFs.USER}/Documents/Square Enix/Batman Arkham Asylum GOTY/BmGame/Config" | |
| val staleFiles = listOf( | |
| File(container.rootDir, ".wine/drive_c/Program Files (x86)/Steam/ColdClientLoader.ini"), | |
| File(container.rootDir, "$userDocumentsConfigPath/BmEngine.ini"), | |
| File(container.rootDir, "$userDocumentsConfigPath/BmGame.ini"), | |
| File(container.rootDir, ".cache/ShippingPC-BmGame.dxvk-cache"), | |
| ) | |
| val userDocumentsConfigPath = | |
| ".wine/drive_c/users/${ImageFs.USER}/Documents/Square Enix/Batman Arkham Asylum GOTY/BmGame/Config" | |
| val staleFiles = listOf( | |
| File(container.rootDir, ".wine/drive_c/Program Files (x86)/Steam/ColdClientLoader.ini"), | |
| File(container.rootDir, "$userDocumentsConfigPath/BmEngine.ini"), | |
| File(container.rootDir, "$userDocumentsConfigPath/BmGame.ini"), | |
| File(container.rootDir, "home/${ImageFs.USER}/.cache/ShippingPC-BmGame.dxvk-cache"), | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/main/java/app/gamenative/gamefixes/STEAM_35140.kt` around lines 23 -
30, The DXVK cache staleFiles entry incorrectly targets File(container.rootDir,
".cache/ShippingPC-BmGame.dxvk-cache") which points to <rootDir>/.cache; change
it to point under the container user's home cache by creating the File with the
path "home/${ImageFs.USER}/.cache/ShippingPC-BmGame.dxvk-cache" so the entry in
the staleFiles list uses File(container.rootDir,
"home/${ImageFs.USER}/.cache/ShippingPC-BmGame.dxvk-cache"); update the
staleFiles list alongside the other entries (e.g., where staleFiles is defined)
to use ImageFs.USER in the path.
| if (MarkerUtils.hasMarker(installPath, Marker.STEAM_COLDCLIENT_USED) && | ||
| MarkerUtils.removeMarker(installPath, Marker.STEAM_COLDCLIENT_USED) | ||
| ) { | ||
| changed = true | ||
| Timber.tag("GameFixes").i("Cleared stale coldclient marker for Batman Arkham Asylum GOTY") | ||
| } |
There was a problem hiding this comment.
Add explicit logging when marker deletion fails.
If the marker exists and removeMarker(...) returns false, the code currently has no warning path, which makes cleanup failures harder to debug.
💡 Proposed fix
- if (MarkerUtils.hasMarker(installPath, Marker.STEAM_COLDCLIENT_USED) &&
- MarkerUtils.removeMarker(installPath, Marker.STEAM_COLDCLIENT_USED)
- ) {
- changed = true
- Timber.tag("GameFixes").i("Cleared stale coldclient marker for Batman Arkham Asylum GOTY")
+ if (MarkerUtils.hasMarker(installPath, Marker.STEAM_COLDCLIENT_USED)) {
+ if (MarkerUtils.removeMarker(installPath, Marker.STEAM_COLDCLIENT_USED)) {
+ changed = true
+ Timber.tag("GameFixes").i("Cleared stale coldclient marker for Batman Arkham Asylum GOTY")
+ } else {
+ Timber.tag("GameFixes").w("Failed to clear stale coldclient marker for Batman Arkham Asylum GOTY")
+ }
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/main/java/app/gamenative/gamefixes/STEAM_35140.kt` around lines 34 -
39, The code checks MarkerUtils.hasMarker(installPath,
Marker.STEAM_COLDCLIENT_USED) then calls MarkerUtils.removeMarker(...) but lacks
a warning when removeMarker returns false; update the conditional to detect the
case where hasMarker is true and removeMarker returns false and log a warning
via Timber.tag("GameFixes").w(...) (include installPath and
Marker.STEAM_COLDCLIENT_USED in the message) so failed deletions are visible,
while keeping the existing path that sets changed = true and the info log when
removal succeeds.
|
I scoped the stale coldclient cleanup down to the Batman-specific fix as well. The global reset in That keeps the PR focused on the confirmed Batman relaunch regression without changing launch-state handling for unrelated Steam titles. |
PR utkarshdalal#775 introduced ExeRunDir to fix save loading in New Star GP, whose exe lives in a subdirectory (release/NSGP.exe) and opens saves via a relative path from the game install root. Without ExeRunDir, ColdClient- Loader defaults the working directory to the exe's parent, so the save file was never found. However, hardcoding ExeRunDir to the game root broke games like Batman: Arkham Asylum GOTY (reverted in PR utkarshdalal#950), which declares workingdir: Binaries in its appinfo. For these games, the correct CWD is the subdirectory, not the root — and the previous empty ExeRunDir happened to work because ColdClientLoader defaulted to the exe's parent. The fix reads workingDir from the Steam launch config (LaunchInfo) and appends it to the game root for ExeRunDir when present, falling back to the game root when absent. This matches Steam's own behaviour across all cases observed in the wild. The INI content is extracted into generateColdClientIni() for testability, with unit tests covering the Batman and New Star GP cases and edge cases such as trailing slashes and forward-slash normalisation. Fixes: utkarshdalal#775 Fixes: utkarshdalal#950
|
Hi @Misazam, I don't think the coldclient.ini deletion is really relevant here. It has nothing to do with the game. I think what most likely needs to be cleared is the cache file only - clearing the other inis will clear user settings. Please double check that just deleting the cache on every launch is sufficient. |
PR utkarshdalal#775 introduced ExeRunDir to fix save loading in New Star GP, whose exe lives in a subdirectory (release/NSGP.exe) and opens saves via a relative path from the game install root. Without ExeRunDir, ColdClient- Loader defaults the working directory to the exe's parent, so the save file was never found. However, hardcoding ExeRunDir to the game root broke games like Batman: Arkham Asylum GOTY (reverted in PR utkarshdalal#950), which declares workingdir: Binaries in its appinfo. For these games, the correct CWD is the subdirectory, not the root — and the previous empty ExeRunDir happened to work because ColdClientLoader defaulted to the exe's parent. The fix reads workingDir from the Steam launch config (LaunchInfo) and appends it to the game root for ExeRunDir when present, falling back to the game root when absent. This matches Steam's own behaviour across all cases observed in the wild. The INI content is extracted into generateColdClientIni() for testability, with unit tests covering the Batman and New Star GP cases and edge cases such as trailing slashes and forward-slash normalisation. Fixes: utkarshdalal#775 Fixes: utkarshdalal#950
This change addresses a reproducible launch-state regression for Batman: Arkham Asylum GOTY (Steam app id 35140) in GameNative.
Observed behaviour:
Reproduction used during investigation:
Root cause:
Batman was sensitive to stale state surviving between launches. Cleaning only the Steam coldclient marker improved the situation but was not sufficient by itself. Reliable recovery required clearing the stale launch artifacts that persisted across sessions:
Changes in this PR:
Why this matters:
Without this cleanup, Batman can appear to fail randomly after a successful first boot, when the issue is actually deterministic stale launch state reuse. This makes the regression highly visible and user-facing because the game works once, then later relaunches degrade into a black screen.
Validation:
Note:
A broader DirectX preinstall change was initially included during investigation, but it was not validated across multiple Proton versions and has been removed from this PR.
Summary by cubic
Fixes the relaunch black screen for Batman: Arkham Asylum GOTY (Steam app 35140) by clearing stale Steam coldclient and Batman config/cache state.
ColdClientLoader.ini, theSTEAM_COLDCLIENT_USEDmarker,BmEngine.ini,BmGame.ini, andShippingPC-BmGame.dxvk-cachebefore launch.GameFixesRegistryso it runs automatically for this title.SteamUtils.writeColdClientIni.Written for commit 7b486ad. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes