Skip to content

Execute Steam InstallScripts during container setup#1464

Open
playday3008 wants to merge 13 commits into
utkarshdalal:masterfrom
playday3008:feat/steam-installscript
Open

Execute Steam InstallScripts during container setup#1464
playday3008 wants to merge 13 commits into
utkarshdalal:masterfrom
playday3008:feat/steam-installscript

Conversation

@playday3008

@playday3008 playday3008 commented May 21, 2026

Copy link
Copy Markdown

Description

Executes Steam InstallScript VDF files during container setup. Discovers install scripts from depot manifests via EDepotFileFlag.InstallScript, parses them with JavaSteam's KeyValue, then:

  • Writes registry keys to Wine prefix (Phase 1, pre-Wine)
  • Chains prerequisite installers after PreInstallSteps (Phase 2, via Wine)

Supports env var expansion, language-specific registry overrides, OS filtering, and HasRunKey tracking.

Tested with NFS Rivals — EA App installer ran successfully via chained Wine process.

Recording

Screen_Recording_20260521_043724_GameNative_1.mp4

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #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.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by cubic

Run Steam InstallScript VDFs during container setup to auto-install prerequisites before launch. Adds a pre-launch orchestrator that chains installers with exit-code tracking so they only run once.

  • New Features

    • Scan depot manifests for EDepotFileFlag.InstallScript and parse VDFs with KeyValue (env vars, language overrides, OS filters).
    • Apply registry edits to Wine system.reg/user.reg before Wine starts.
    • Chain run-process steps into launch and mark completion only when exit code is 0.
    • Add PreLaunchSetup to build a single command chain from PreInstallSteps + InstallScripts; XServerScreen now uses it.
    • Tests cover parsing, executor behavior, and PreLaunchSetup chaining.
  • Bug Fixes

    • Compute effective HasRunKey before skip-check; resolve registry hive from HKCU/HKLM for reads/writes and completion.
    • Make hive prefix stripping case-insensitive and lowercase language override keys for consistent merges.
    • Skip UbisoftConnectStep for Steam games (handled by InstallScripts).

Written for commit 8b11e4a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Support for Steam depot install-scripts: discovery, parsing, registry/application steps, and integrated run-process actions during launch; boot splash shows "Installing prerequisites..." when applicable.
  • Refactor

    • Unified chaining for pre-launch steps so prerequisites run in order with per-step completion and cleanup between steps.
  • Improvements

    • Ubisoft prereq step no longer runs for Steam-sourced games.
  • Tests

    • Added tests covering install-script parsing, registry/OS rules, chaining, and related behaviors.

Install scripts are identified by the EDepotFileFlag.InstallScript flag
on files in depot manifests, not from a depot-level metadata key.
Rewrite collectScripts to load depot manifests and scan for flagged
files. Remove old collectScripts unit tests that relied on the previous
metadata-based API; keep utility method tests. Clean up diagnostic
debug logging from XServerScreen.
@playday3008 playday3008 requested a review from utkarshdalal as a code owner May 21, 2026 05:25
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Too much diff to scan? Review this PR in Change Stack to start with the highest-impact changes.

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Steam install-script support: data models, a VDF parser with env-var expansion, an executor that discovers scripts from depots and applies registry changes, generation of run-process commands, integration into a chained pre-launch flow, and tests.

Changes

Steam Install-Script Support

Layer / File(s) Summary
Install-Script Data Models
app/src/main/java/app/gamenative/utils/installscript/InstallScriptData.kt
Defines data classes for install-script configuration: RegistryValues, RegistryAction, OSRequirement, RunProcessAction, and InstallScript.
Install-Script Parser
app/src/main/java/app/gamenative/utils/installscript/InstallScriptParser.kt, app/src/test/java/app/gamenative/utils/installscript/InstallScriptParserTest.kt
Parses VDF-format install scripts into InstallScript objects (parse/parseFromString), expands %VAR% env placeholders, extracts Registry and Run Process sections, handles language overrides, and logs failures. Tests cover parsing, overrides, env expansion, run-process fields, OS requirements, and edge cases.
Install-Script Executor
app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt, app/src/test/java/app/gamenative/utils/installscript/InstallScriptExecutorTest.kt
Collects install scripts from depot manifests, applies merged registry keys into Wine system.reg/user.reg, builds run-process command list filtered by OS and completion state, wraps commands for Wine guest execution, and persists completion dwords. Tests validate merging, OS matching, and hive-prefix handling.
Metadata & Utility Updates
app/src/main/java/app/gamenative/data/DepotInfo.kt, app/src/main/java/app/gamenative/utils/KeyValueUtils.kt, app/src/main/java/app/gamenative/utils/PreInstallSteps.kt
Adds installScript field to DepotInfo, parses installscript in generateSteamApp(), and changes PreInstallSteps.getGameDir() visibility to internal.
PreLaunch Chain Integration
app/src/main/java/app/gamenative/utils/PreLaunchSetup.kt, app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
Adds PreLaunchSetup.buildChain to assemble chained pre-launch commands (pre-install + install-script run-processes) and updates XServerScreen to use the chained flow, set guest executable from the first chain step, update boot splash text, and run wineserver -k between steps.
PreInstall / UbisoftConnect tests
app/src/main/java/app/gamenative/utils/preInstallSteps/UbisoftConnectStep.kt, app/src/test/java/app/gamenative/utils/preInstallSteps/*
UbisoftConnectStep now rejects Steam games early; tests adjusted and a new Steam-specific test added. PreInstallSteps tests updated to use GOG inputs where applicable.
PreLaunchSetup Tests
app/src/test/java/app/gamenative/utils/PreLaunchSetupTest.kt
Robolectric tests validating PreLaunchSetup.buildChain mapping, empty-chain behavior, non-Steam skipping, and onComplete() marker creation.

Sequence Diagram(s)

sequenceDiagram
  participant XServerScreen
  participant PreLaunchSetup
  participant InstallScriptExecutor
  participant InstallScriptParser
  participant DepotManifest
  participant WineRegistry
  participant GuestProgram
  XServerScreen->>PreLaunchSetup: buildChain(container, appId, gameSource, screenInfo, containerVariantChanged)
  PreLaunchSetup->>InstallScriptExecutor: collectScripts(steamApp, appInfo, gameDir, installDir, language, appId)
  InstallScriptExecutor->>DepotManifest: load depot manifests
  DepotManifest-->>InstallScriptExecutor: install-script entries
  InstallScriptExecutor->>InstallScriptParser: parse(scriptFile)
  InstallScriptParser-->>InstallScriptExecutor: InstallScript (registryActions, runProcessActions)
  PreLaunchSetup->>InstallScriptExecutor: applyRegistryKeys(container, scripts, language)
  InstallScriptExecutor->>WineRegistry: write string/DWORD values (system.reg / user.reg)
  PreLaunchSetup->>InstallScriptExecutor: getRunProcessCommands(container, scripts, screenInfo, is64Bit)
  InstallScriptExecutor-->>PreLaunchSetup: RunProcessCommand list
  XServerScreen->>GuestProgram: execute chained commands (pre-install, install-script runs, game)
  GuestProgram->>InstallScriptExecutor: markRunProcessComplete(hasRunKey)
  InstallScriptExecutor->>WineRegistry: write completion dword
Loading
sequenceDiagram
  participant GameLaunch as Game Launch
  participant PreLaunch as PreLaunchSetup
  participant Chain as Command Chain
  participant Guest as Guest Program
  GameLaunch->>PreLaunch: buildChain (preInstall + installScript commands)
  PreLaunch->>Chain: assemble ordered ChainedCommand list
  Chain->>Guest: execute step N (guestExecutable)
  Guest-->>Chain: termination callback (exit code)
  Chain->>Guest: wineserver -k (between steps)
  Chain->>Guest: execute final game executable
  Guest-->>GameLaunch: game running
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Suggested Reviewers

  • utkarshdalal

Poem

🐰 From depots I hop with script in paw,
I parse the keys and stitch the law,
I write the hives and queue the run,
I chain the steps until setup's done,
Then whisper, "Play!" and bound for more.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Execute Steam InstallScripts during container setup' clearly and specifically summarizes the main change: implementing execution of Steam InstallScript VDF files during container initialization.
Description check ✅ Passed The PR description includes all required template sections: a comprehensive description of changes, a recording link, correct type of change selection (Compatibility improvements), and completed checklist items demonstrating adherence to contribution guidelines.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 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 3256-3266: The termination callback currently marks chained steps
complete unconditionally (advancing the chain even on non-zero exit or
cancellation); update the ChainedCommand termination logic used when building
chainedPreInstall and chainedInstallScript so it only calls
PreInstallSteps.markStepDone(container, cmd.marker) or
InstallScriptExecutor.markRunProcessComplete(container, cmd.hasRunKey) after
verifying the process exited successfully (exit code == 0) and was not
cancelled; ensure any cancellation or non-zero exit prevents advancing the chain
and propagates failure so retries occur on next launch.

In
`@app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt`:
- Around line 121-133: The skip-check uses action.hasRunKey before the fallback
key is computed, so actions without an explicit hasRunKey always return false in
hasAlreadyRun and will rerun; fix by computing effectiveHasRunKey before calling
hasAlreadyRun and pass that same effectiveHasRunKey into both the hasAlreadyRun
check and the RunProcessCommand/markRunProcessComplete flow (i.e., compute
effectiveHasRunKey = action.hasRunKey ?:
"Software\\GameNative\\InstallScript\\${script.sourcePath.hashCode()}\\${action.name}"
up front, use it in hasAlreadyRun(effectiveHasRunKey) and in the
RunProcessCommand constructor), and apply the same change to the other similar
block that constructs commands (the second occurrence near the
wrapAsGuestExecutable/RunProcessCommand usage).
- Around line 139-147: markRunProcessComplete is always editing .wine/system.reg
regardless of the hive in hasRunKey; update it (and the corresponding
hasAlreadyRun reader) to select the correct reg file based on the hive prefix
(e.g., if hasRunKey startsWith "HKCU\\" use the per-user reg file instead of
system.reg), then open WineRegistryEditor on that chosen File before calling
setCreateKeyIfNotExist and setDwordValue (use the same hive-parsing logic you
already use with stripHivePrefix/keyPath/valueName so reads and writes target
the same hive).
- Around line 170-176: stripHivePrefix currently only removes uppercase hive
strings and so leaves lowercase prefixes like "hklm\" intact; update
stripHivePrefix to perform case-insensitive prefix removal (e.g., convert
keyPath to a consistent case for matching or use a case-insensitive regex) and
then return the original casing minus the matched prefix so behavior matches
applyRegistryKeys(ignoreCase = true). Target the internal fun
stripHivePrefix(keyPath: String) and ensure it strips any of "HKLM\\",
"HKEY_LOCAL_MACHINE\\", "HKCU\\", "HKEY_CURRENT_USER\\" regardless of case.

In `@app/src/main/java/app/gamenative/utils/installscript/InstallScriptParser.kt`:
- Around line 133-137: The language override keys are stored using the raw VDF
section name (childName) but looked up with language.lowercase() in
mergeWithLanguage(); change the storage to normalize the key (e.g.,
childName.lowercase()) when inserting into the languageOverrides map inside
InstallScriptParser (where RegistryValues is created) so lookups in
mergeWithLanguage() succeed for variants like "English" or "ENGLISH".
- Around line 60-61: The mapping for "%ROOTDRIVE%" in InstallScriptParser is
currently set to "C" which causes expansions like "%ROOTDRIVE%\foo" => "C\foo";
update the mapping for the "ROOTDRIVE" entry (the map or method in
InstallScriptParser that returns default variables) to use "C:" instead of "C"
so path expansions produce valid Windows paths (e.g., "C:\foo"). Ensure any
other places that synthesize ROOTDRIVE defaults use the same "C:" value.

In
`@app/src/test/java/app/gamenative/utils/installscript/InstallScriptExecutorTest.kt`:
- Around line 51-53: The test currently checks for a key named "FrenchValue"
which is actually a value; change the assertion to verify that the value isn't
present by replacing assertFalse(merged.strings.containsKey("FrenchValue")) with
an assertion that checks values (e.g.,
assertFalse(merged.strings.containsValue("FrenchValue")) or
assertNull/doesNotContain on merged.strings.values) so the test checks for
accidental leakage of the French value from the fallback-language logic; update
the assertion in the InstallScriptExecutorTest that uses merged.strings
accordingly.
🪄 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

Run ID: 2ae77802-9720-4b5a-9af1-ed3041ed30d5

📥 Commits

Reviewing files that changed from the base of the PR and between 9350ccf and b2e196d.

📒 Files selected for processing (9)
  • app/src/main/java/app/gamenative/data/DepotInfo.kt
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/app/gamenative/utils/KeyValueUtils.kt
  • app/src/main/java/app/gamenative/utils/PreInstallSteps.kt
  • app/src/main/java/app/gamenative/utils/installscript/InstallScriptData.kt
  • app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt
  • app/src/main/java/app/gamenative/utils/installscript/InstallScriptParser.kt
  • app/src/test/java/app/gamenative/utils/installscript/InstallScriptExecutorTest.kt
  • app/src/test/java/app/gamenative/utils/installscript/InstallScriptParserTest.kt

Comment thread app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt Outdated
Comment thread app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 9 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt Outdated
Comment thread app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt Outdated
Comment thread app/src/main/java/app/gamenative/utils/installscript/InstallScriptParser.kt Outdated
- Compute effectiveHasRunKey before skip-check so fallback-keyed actions
  don't re-run on every launch
- Resolve registry file from hive prefix (HKCU → user.reg) instead of
  hardcoding system.reg in hasAlreadyRun/markRunProcessComplete
- Make stripHivePrefix case-insensitive to match applyRegistryKeys
- Lowercase language override keys when parsing to match mergeWithLanguage lookup

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt (2)

139-147: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persist the same threshold you use for MinimumHasRunValue.

Line 199 skips only when the stored DWORD reaches minimumHasRunValue, but Line 147 always writes 1. Any action with MinimumHasRunValue > 1 will rerun on the next launch, and this can even downgrade a higher value written by the installer itself back to 1.

💡 Proposed direction
 data class RunProcessCommand(
     val executable: String,
     val hasRunKey: String?,
+    val completionValue: Int,
 )
 ...
+                val completionValue = maxOf(1, action.minimumHasRunValue)
-                commands.add(RunProcessCommand(wrapped, effectiveHasRunKey))
+                commands.add(RunProcessCommand(wrapped, effectiveHasRunKey, completionValue))
 ...
-    fun markRunProcessComplete(container: Container, hasRunKey: String) {
+    fun markRunProcessComplete(container: Container, hasRunKey: String, completionValue: Int) {
 ...
-                editor.setDwordValue(stripHivePrefix(keyPath), valueName, 1)
+                editor.setDwordValue(stripHivePrefix(keyPath), valueName, completionValue)

The caller that consumes RunProcessCommand should pass completionValue through when it marks the action complete.

Also applies to: 189-199

🤖 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/installscript/InstallScriptExecutor.kt`
around lines 139 - 147, markRunProcessComplete currently always writes a DWORD
value of 1 which mismatches the threshold checked elsewhere (MinimumHasRunValue)
and can downgrade higher values; change markRunProcessComplete (and the similar
write in the other block referenced) to accept or be passed the same
completionValue used by RunProcessCommand (or the MinimumHasRunValue) and write
that value instead of 1 via
WineRegistryEditor.setDwordValue(stripHivePrefix(keyPath), valueName,
completionValue) so the persisted registry value matches the threshold used when
deciding to skip reruns.

121-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Quote the executable before wrapping it in cmd /c.

Line 127 builds cmdLine from the raw process value, so expanded paths under %INSTALLDIR% or C:\Program Files... are split on spaces unless the VDF already included quotes. That makes valid install scripts fail to launch.

💡 Proposed fix
-                val cmdLine = if (action.command.isNotEmpty()) {
-                    "${action.process} ${action.command}"
-                } else {
-                    action.process
-                }
+                val quotedProcess = if (
+                    action.process.startsWith("\"") && action.process.endsWith("\"")
+                ) {
+                    action.process
+                } else {
+                    "\"${action.process}\""
+                }
+                val cmdLine = if (action.command.isNotEmpty()) {
+                    "$quotedProcess ${action.command}"
+                } else {
+                    quotedProcess
+                }
🤖 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/installscript/InstallScriptExecutor.kt`
around lines 121 - 133, The cmdLine built in InstallScriptExecutor (variable
cmdLine used to create RunProcessCommand and passed into wrapAsGuestExecutable)
uses action.process raw which splits paths with spaces when wrapped in cmd /c;
change construction to quote the executable path if it isn't already quoted
(e.g., produce "\"<process>\" <command>" when action.command is present or just
"\"<process>\"" otherwise), preserving existing surrounding quotes, then pass
that quoted cmdLine into wrapAsGuestExecutable; this ensures executables under
%INSTALLDIR% or "C:\Program Files..." are not split when run.
🤖 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.

Outside diff comments:
In
`@app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt`:
- Around line 139-147: markRunProcessComplete currently always writes a DWORD
value of 1 which mismatches the threshold checked elsewhere (MinimumHasRunValue)
and can downgrade higher values; change markRunProcessComplete (and the similar
write in the other block referenced) to accept or be passed the same
completionValue used by RunProcessCommand (or the MinimumHasRunValue) and write
that value instead of 1 via
WineRegistryEditor.setDwordValue(stripHivePrefix(keyPath), valueName,
completionValue) so the persisted registry value matches the threshold used when
deciding to skip reruns.
- Around line 121-133: The cmdLine built in InstallScriptExecutor (variable
cmdLine used to create RunProcessCommand and passed into wrapAsGuestExecutable)
uses action.process raw which splits paths with spaces when wrapped in cmd /c;
change construction to quote the executable path if it isn't already quoted
(e.g., produce "\"<process>\" <command>" when action.command is present or just
"\"<process>\"" otherwise), preserving existing surrounding quotes, then pass
that quoted cmdLine into wrapAsGuestExecutable; this ensures executables under
%INSTALLDIR% or "C:\Program Files..." are not split when run.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9af89a6a-f160-41d8-b9a6-1ac0878ad0e0

📥 Commits

Reviewing files that changed from the base of the PR and between b2e196d and 40fff53.

📒 Files selected for processing (2)
  • app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt
  • app/src/main/java/app/gamenative/utils/installscript/InstallScriptParser.kt

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@playday3008, thanks for sharing the official Steamworks InstallScript documentation! Reviewing the implementation against the spec, here are a few observations:

Aligned with the spec ✅

  • The HasRunKey logic is correctly modeled: Steam uses that registry DWORD value to determine if the process needs to run, and if the value is not present or 0, it calls CreateProcess — the PR's minimumHasRunValue field and the fallback-key logic handle this correctly.
  • Language-specific overrides for string and DWORD values (by creating a subkey with the language name) are implemented via mergeWithLanguage in InstallScriptExecutor.
  • Install script functionality is primarily for Windows operating systems, which aligns with the choice to apply these via Wine.

Potential gaps worth considering ⚠️

  1. Exit code validation: Steam checks that the return code is zero; if it sees a zero return code, it marks the redist as installed and doesn't run it again — but if it returns any other value, Steam will attempt to run it again on next launch. The current implementation calls markRunProcessComplete unconditionally in the chaining logic (XServerScreen.kt), regardless of the process exit code. It may be worth only marking completion when the Wine process exits with code 0.

  2. Uninstall cleanup: When a user deletes or verifies an app from Steam, install scripts are called to remove any registry values they created. This reversal behavior is not currently in scope for the PR, which is fine as a follow-up.

  3. ShellExecuteEx fallback: If CreateProcess fails, then ShellExecuteEx is called. This is a Windows-specific fallback and not applicable in the Wine context here, so it can be safely ignored.

Would you like to open a follow-up issue for the exit-code-based completion tracking or the uninstall cleanup behavior?

@playday3008

Copy link
Copy Markdown
Author

@coderabbitai https://partner.steamgames.com/doc/sdk/installscripts

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@playday3008, thanks for the pointer to the official docs! Reviewing them against the implementation surfaces a couple of additional notes worth considering:

  1. Script signing (kvsignatures) for HKLM writes: A common reason HKLM writes silently fail is that the install script isn't signed — Steam requires a kvsignatures key added during the build process to permit writes to HKEY_LOCAL_MACHINE. In the Wine context this signing check is bypassed by Wine's own registry layer, so it should be fine in practice — but worth documenting as a known divergence from the official Steam client behavior.

  2. Wow6432Node on 64-bit Windows: On 64-bit Windows, Steam writes registry entries to the Wow6432Node path due to Windows 64-bit virtualization. Depending on whether the Wine prefix is set to 32-bit or 64-bit mode, the PR's registry key paths may need to be redirected to Wow6432Node in 32-bit Wine prefixes. It's worth verifying whether the current implementation handles this correctly.

  3. Language overrides: Both string and DWORD values can be assigned different data depending on what language the user is running Steam in; to use this, create a subkey in the "string" or "dword" key with the name of the language. This matches what the PR already does with mergeWithLanguage, so that's ✅.

Regarding my earlier questions — would you like me to open follow-up GitHub issues for:

  • Exit-code-based completion tracking (only calling markRunProcessComplete when the Wine process exits with code 0)?
  • Uninstall/cleanup (reversing registry writes when the app is removed)?

@utkarshdalal

Copy link
Copy Markdown
Owner

Hi @playday3008. Thank you for the PR!

Could you give me examples of games this fixes (besides NFS Rivals)? I want to try them out on my end too

@playday3008

Copy link
Copy Markdown
Author

Could you give me examples of games this fixes (besides NFS Rivals)? I want to try them out on my end too

In theory any game that has setup step in installScript.vdf, such as EA and Ubisoft titles, although I saw that we have Ubisoft step separately, this PR may allow to remove that "hacky" step

@playday3008

Copy link
Copy Markdown
Author

Also, need to check out: Exit-code-based completion tracking

…sses

Wrapper now captures installer exit code via &&/|| branching to a file
before wineserver cleanup. Only marks HasRunKey complete on exit code 0,
matching Steam behavior. Non-zero exits log a warning and retry next launch.
)
if (scripts.isNotEmpty()) {
Timber.tag("InstallScript").i("Applying registry keys from ${scripts.size} install script(s)")
InstallScriptExecutor.applyRegistryKeys(container, scripts, container.language)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's valuable to add the registry keys, but the rest I'm not so sure about. We intentionally don't install some things (like xaudio on proton 9, directx on proton < 10). This should align with our preinstallsteps better. What kind of scripts have you seen here? And what errors get fixed?

@playday3008 playday3008 Jun 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the testing with NFS Rivals, the InstallScript Run Process installs EA App (EAAppInstaller.exe). Ubisoft titles similarly declare Ubisoft Connect installer. These are launcher/client prerequisites, not redists like VCRedist/DirectX/PhysX (those are already handled by PreInstallSteps).

Concern about xaudio on proton 9 / directx on proton <10 is valid but may not apply here — InstallScript Run Processes are typically launcher installers, not the same redists that PreInstallSteps handles. That said, some games could declare DirectX/VCRedist in their InstallScript too, which would overlap.

Comment on lines +3251 to +3273
data class ChainedCommand(
val executable: String,
val onComplete: () -> Unit,
)

val chainedPreInstall = preInstallCommands.map { cmd ->
ChainedCommand(cmd.executable) { PreInstallSteps.markStepDone(container, cmd.marker) }
}
val chainedInstallScript = installScriptRunProcessCommands.map { cmd ->
ChainedCommand(cmd.executable) {
if (cmd.hasRunKey != null) {
val exitCode = InstallScriptExecutor.readExitCode(container)
if (exitCode == 0) {
InstallScriptExecutor.markRunProcessComplete(container, cmd.hasRunKey)
} else {
Timber.tag("InstallScript").w(
"Run process exited with code $exitCode, will retry next launch",
)
}
}
}
}
val allChainedCommands = chainedPreInstall + chainedInstallScript

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InstallScript should just be a PreInstallStep, this doesn't belong here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PreLaunchSetup orchestrator instead of making InstallScript a PreInstallStep — they're different abstractions. PreInstallStep is static 1:1 (marker → command, marker file tracking). InstallScript is dynamic N:M (parsed from VDF, registry-based HasRunKey with MinimumHasRunValue, pre-Wine registry phase). Forcing one into the other means either bloating the interface for all steps or losing Steam spec compliance.

XServerScreen now just calls PreLaunchSetup.buildChain() and gets a unified command list back — no domain logic in the screen anymore.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 3087-3094: PreLaunchSetup.buildChain is being invoked and its
first prerequisite is used to override
guestProgramLauncherComponent.guestExecutable even for desktop/container/test
modes (bootToContainer or testGraphics), causing installers to run instead of
the intended gameExecutable (wfm.exe/TestD3D.exe); change the logic so that when
bootToContainer or testGraphics is true you either skip calling
PreLaunchSetup.buildChain entirely or at minimum do not replace
guestProgramLauncherComponent.guestExecutable with
allChainedCommands.firstOrNull()?.executable — keep
guestProgramLauncherComponent.guestExecutable set to gameExecutable in those
modes and only apply the chained prerequisites for normal launches (use the
existing flags bootToContainer/testGraphics and the variables
allChainedCommands, PreLaunchSetup.buildChain, and
guestProgramLauncherComponent.guestExecutable to gate this behavior).
🪄 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

Run ID: 819b7ea7-29e2-4713-a3b5-fd3fe8ceccf2

📥 Commits

Reviewing files that changed from the base of the PR and between c442dfb and 8b11e4a.

📒 Files selected for processing (3)
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/app/gamenative/utils/PreLaunchSetup.kt
  • app/src/test/java/app/gamenative/utils/PreLaunchSetupTest.kt

Comment on lines +3087 to +3094
allChainedCommands = PreLaunchSetup.buildChain(
container = container,
appId = appId,
gameSource = gameSource,
screenInfo = xServer.screenInfo.toString(),
containerVariantChanged = containerVariantChanged,
)
guestProgramLauncherComponent.guestExecutable =
preInstallCommands.firstOrNull()?.executable ?: gameExecutable
guestProgramLauncherComponent.guestExecutable = allChainedCommands.firstOrNull()?.executable ?: gameExecutable

@coderabbitai coderabbitai Bot Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip app-specific prelaunch chaining for desktop/test launches.

Line 3087 builds PreLaunchSetup commands even when bootToContainer or testGraphics is active. In those modes gameExecutable is intentionally wfm.exe / TestD3D.exe, but Line 3094 still replaces it with the first prerequisite command, so opening a container or running the graphics test can unexpectedly execute game installers instead of the requested target.

💡 Suggested fix
-        allChainedCommands = PreLaunchSetup.buildChain(
-            container = container,
-            appId = appId,
-            gameSource = gameSource,
-            screenInfo = xServer.screenInfo.toString(),
-            containerVariantChanged = containerVariantChanged,
-        )
+        val shouldRunPreLaunchChain = !bootToContainer && !testGraphics
+        allChainedCommands = if (shouldRunPreLaunchChain) {
+            PreLaunchSetup.buildChain(
+                container = container,
+                appId = appId,
+                gameSource = gameSource,
+                screenInfo = xServer.screenInfo.toString(),
+                containerVariantChanged = containerVariantChanged,
+            )
+        } else {
+            emptyList()
+        }
🤖 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 3087 - 3094, PreLaunchSetup.buildChain is being invoked and its first
prerequisite is used to override guestProgramLauncherComponent.guestExecutable
even for desktop/container/test modes (bootToContainer or testGraphics), causing
installers to run instead of the intended gameExecutable (wfm.exe/TestD3D.exe);
change the logic so that when bootToContainer or testGraphics is true you either
skip calling PreLaunchSetup.buildChain entirely or at minimum do not replace
guestProgramLauncherComponent.guestExecutable with
allChainedCommands.firstOrNull()?.executable — keep
guestProgramLauncherComponent.guestExecutable set to gameExecutable in those
modes and only apply the chained prerequisites for normal launches (use the
existing flags bootToContainer/testGraphics and the variables
allChainedCommands, PreLaunchSetup.buildChain, and
guestProgramLauncherComponent.guestExecutable to gate this behavior).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing behavior — the old code (preInstallCommands = PreInstallSteps.getPreInstallCommands(...) + firstChainedExecutable) had the same issue. Not a regression from our refactor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@playday3008, acknowledged — you're right that the old PreInstallSteps.getPreInstallCommands(...) call had the same absent guard, so this PR is a clean refactor of existing behavior rather than a regression. I'll retract the severity of this comment.

That said, the latent issue (prerequisite installers potentially running during bootToContainer/testGraphics modes) is still present. Would you like me to open a follow-up GitHub issue to track adding the guard in a separate PR?


🧠 Learnings used
Learnt from: unbelievableflavour
Repo: utkarshdalal/GameNative PR: 698
File: app/src/main/java/app/gamenative/utils/LaunchSteps.kt:134-142
Timestamp: 2026-03-03T11:45:46.529Z
Learning: In the LaunchSteps orchestration (app/src/main/java/app/gamenative/utils/LaunchSteps.kt), the `wineserver -k` command in the runLauncher function is necessary to prevent the launcher from closing after each chained step. Without it, the launcher terminates prematurely between steps.

Learnt from: Catpotatos
Repo: utkarshdalal/GameNative PR: 1245
File: app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt:1550-1550
Timestamp: 2026-04-19T11:44:04.419Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`, `firstTimeBoot` is set to `true` only when `container.getExtra("containerPatchVersion").isEmpty() || containerVariantChanged`. This is intentional: (1) existing containers migrating from `appVersion`-based tracking will have an empty `containerPatchVersion`, triggering one final full rebuild; (2) brand-new containers always have an empty extra; (3) for patch-version mismatches on existing containers, `setupWineSystemFiles()` calls `applyGeneralPatches()` which rebuilds the prefix from scratch and clears all tracking extras — `firstTimeBoot = false` in this path is safe because fresh original DLLs are already present after `applyGeneralPatches()`. Do not suggest expanding `firstTimeBoot` to cover `containerPatchVersion` mismatches in future reviews.

Learnt from: joshuatam
Repo: utkarshdalal/GameNative PR: 1269
File: app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreenUtils.kt:205-216
Timestamp: 2026-04-21T18:33:03.732Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreenUtils.kt`, the game (app) install directory is always mapped to the A: drive in the Wine container. Hardcoding `"A"` in `toWindowsPathForWine("A", appDir, cab)` inside `buildCabarcBatchScript` is correct and intentional — do not flag the hard-coded "A" drive letter for the `appDir`/cab path as a potential mapping issue in future reviews.

Learnt from: AndreVto
Repo: utkarshdalal/GameNative PR: 1389
File: app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt:1580-1585
Timestamp: 2026-05-07T14:24:30.099Z
Learning: In the GameNative repository (utkarshdalal/GameNative), `PluviaApp.xEnvironment` is always destroyed (via `shutdownEnvironment`) when a container/game session closes. Therefore `existingXServer` (obtained via `PluviaApp.xEnvironment?.getComponent<XServerComponent>()?.xServer`) can only be non-null during the same active session (e.g., on activity recreation), never across separate game/container launches with potentially different variants.

Learnt from: Catpotatos
Repo: utkarshdalal/GameNative PR: 1245
File: app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt:188-191
Timestamp: 2026-04-21T20:12:43.546Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt`, `ALWAYS_REEXTRACT = true` is intentionally kept for stability. The key improvement in PR `#1245` is that for x86_64 containers, DXVK/VKD3D no longer re-extracts on every boot — `needReextract` in `setupWineSystemFiles()` only triggers unconditional re-extract via `ALWAYS_REEXTRACT` for arm64ec containers (`isArm64EcContainer`). Graphics drivers and the Wrapper/Fex/cnc-ddraw path still use `ALWAYS_REEXTRACT` unconditionally for all containers. Do not flag `ALWAYS_REEXTRACT = true` as incorrect or suggest setting it to `false` in future reviews of this file.

Learnt from: Catpotatos
Repo: utkarshdalal/GameNative PR: 1245
File: app/src/main/java/app/gamenative/ui/screen/library/appscreen/BaseAppScreen.kt:629-630
Timestamp: 2026-04-18T19:13:41.015Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/library/appscreen/BaseAppScreen.kt`, the `repairContainerFiles()` function intentionally clears `imgVersion` and `containerPatchVersion` extras (in addition to component extras and pre-install markers). Clearing these two extras triggers `XServerScreen.setupWineSystemFiles()` → `applyGeneralPatches()` on next launch, which performs a full prefix wipe and re-extraction. This is the intended behavior: "Repair container" is a deliberate, user-initiated full-repair recovery action equivalent to what `ALWAYS_REEXTRACT = true` did unconditionally, but now exposed as a targeted one-time option. Do not flag the clearing of `imgVersion`/`containerPatchVersion` in `repairContainerFiles()` as incorrectly routing through the prefix rebuild path in future reviews.

Learnt from: joshuatam
Repo: utkarshdalal/GameNative PR: 1269
File: app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreenUtils.kt:144-145
Timestamp: 2026-04-21T18:32:25.546Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreenUtils.kt`, passing an Android absolute path (Linux path) directly to `wine cmd /c <path>` works correctly. Wine internally translates Unix/Linux absolute paths to the corresponding Windows drive paths, so `wine cmd /c ${batFile.absolutePath}` is valid. Do not flag usage of Android absolute paths in `wine cmd /c` invocations as incorrect in future reviews of this file.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 1136
File: app/src/main/java/app/gamenative/MainActivity.kt:273-284
Timestamp: 2026-04-07T21:36:14.886Z
Learning: In the GameNative repository (`utkarshdalal/GameNative`), `SteamService.keepAlive` is set to `true` in only one place: `ContainerConfigDialog.kt:1032` (inside a folder-picker flow). During the normal XServerScreen game-launch bootstrap (`XServerScreen.kt` lines ~1529-1680), `keepAlive` is `false` throughout. The bootstrap uses a raw Java `Executors.newSingleThreadExecutor().submit {}` (not a coroutine/LaunchedEffect), so Compose teardown does not cancel it. However, since `keepAlive` is `false` during bootstrap, a task-swipe in that window does not produce a stale `keepAlive=true` on the next launch, and therefore does not cause the "stuck on container screen" bug. Do not flag onDestroy's `if (SteamService.keepAlive)` guard as failing to cover the bootstrap-window race in future reviews.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 1136
File: app/src/main/java/app/gamenative/MainActivity.kt:147-150
Timestamp: 2026-04-07T21:37:29.365Z
Learning: In `app/src/main/java/app/gamenative/MainActivity.kt`, the `onCreate` stale-session check `if (SteamService.keepAlive && PluviaApp.xEnvironment == null)` is intentionally kept as-is. The only non-game flow that sets `keepAlive = true` with `xEnvironment == null` is the folder-picker in `ContainerConfigDialog.kt` (line ~1032); if the app is killed in that state, resetting `keepAlive = false` on the next `onCreate` is harmless. Do not flag this guard as incorrectly matching non-XServer flows in future reviews.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 0
File: :0-0
Timestamp: 2026-03-10T21:56:45.033Z
Learning: In `app/src/main/java/app/gamenative/utils/BestConfigService.kt`, the `available*` lists (availableDxvk, availableVkd3d, availableBox64Bionic, availableBox64Glibc, availableWowBox64, availableFexcore, availableWineBionic, availableWineGlibc, availableDrivers) passed to `validateComponentVersions()` intentionally include manifest entries (base + installed + manifest). This allows configs referencing installable-but-not-yet-on-disk versions to pass validation. A separate subsequent step, `resolveMissingManifestInstallRequests()`, handles actually installing any manifest items not yet present on disk. Do not suggest removing manifest entries from these lists in future reviews.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 801
File: app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt:174-188
Timestamp: 2026-03-10T22:12:03.164Z
Learning: In `app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`, the `applyConfigForContainer` function calls `installMissingComponentsForConfig` before `BestConfigService.parseConfigToContainerData`. This install-before-parse ordering is pre-existing behavior, not introduced by any recent PR. Do not flag this ordering as an issue in future reviews of this file.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 0
File: :0-0
Timestamp: 2026-03-10T22:13:54.453Z
Learning: In `app/src/main/java/app/gamenative/utils/BestConfigService.kt`, around lines 403-418, missing Box64/FEXCore preset failures (via `Box86_64PresetManager.getPreset("box64", ...)` and `FEXCorePresetManager.getPreset(...)`) are intentionally appended to the same `missingComponents` list as installable components, even though `resolveMissingManifestInstallRequests()` cannot install presets. This is a known limitation deferred for future work — the long-term direction is to serialize non-default presets into config files and auto-install them. Do not flag this mixing of installable vs. non-installable blockers in `missingComponents` as an issue in future reviews of this file.

Learnt from: Catpotatos
Repo: utkarshdalal/GameNative PR: 1245
File: app/src/main/java/com/winlator/xenvironment/components/BionicProgramLauncherComponent.java:375-388
Timestamp: 2026-04-21T20:15:41.555Z
Learning: In `app/src/main/java/com/winlator/xenvironment/components/BionicProgramLauncherComponent.java`, in `extractBox64Files()`, the `ContentProfile` check and `contentsManager.applyContent(profile)` call are intentionally placed before (and not gated by) the `box64BionicVersion` sentinel check. Profile-based Box64 installs re-apply on every non-Arm64EC launch by design — this is consistent with the unconditional `if (true)` profile application in `extractEmulatorsDlls()`. The `box64BionicVersion` sentinel only short-circuits the heavier direct ZSTD asset extraction path. Do not suggest hoisting the sentinel check above the profile application in `extractBox64Files()` in future reviews.

Learnt from: playday3008
Repo: utkarshdalal/GameNative PR: 1459
File: app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt:262-266
Timestamp: 2026-05-20T09:45:55.704Z
Learning: In `GeneralTab.kt` (`app/src/main/java/app/gamenative/ui/component/dialog/GeneralTab.kt`) of the GameNative repository, the `onLaunchOptionSelected` handler in `ExecutablePathDropdown` intentionally keeps the existing `config.execArgs` when `args` is `null` (i.e., when a scanned executable with no PICS arguments is selected). Do not flag this as a bug — it is deliberate so that manually entered arguments are not cleared when the user switches to a plain `.exe`/`.bat`/`.cmd` from the file scanner.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 1146
File: app/src/main/java/app/gamenative/utils/ContainerUtils.kt:474-482
Timestamp: 2026-04-08T12:06:12.947Z
Learning: In `app/src/main/java/app/gamenative/utils/ContainerUtils.kt`, within `applyToContainer(...)`, Steam-specific side-effect calls (e.g. `SteamService.getAppDirPath`, `MarkerUtils.removeMarker`, `SteamService.clearCloudSyncCache`, `PrefManager.pendingCloudResync`) are intentionally NOT guarded by an explicit `GameSource.STEAM` check. This is consistent with the existing pattern in the function. For non-Steam containers these calls are no-ops (DAO deletes find no rows; resync entries for non-Steam IDs are never consumed). Do not flag the absence of a Steam-source guard in this function as an issue in future reviews.

Learnt from: xXJSONDeruloXx
Repo: utkarshdalal/GameNative PR: 1195
File: app/src/main/java/app/gamenative/ui/PluviaMain.kt:1100-1102
Timestamp: 2026-04-13T05:42:50.742Z
Learning: In `app/src/main/java/app/gamenative/ui/PluviaMain.kt`, the `BackHandler(enabled = state.loadingDialogVisible && !SteamService.keepAlive)` with an empty body is intentionally a temporary mitigation. On master, pressing Back during the loading/prelaunch phase already leads to a broken state (animation persists, Wine procs stick, force-quit required), so swallowing Back is no worse and prevents UI thrash from impatient back-mashing. Proper cancellable prelaunch (job cancellation + state reset + timeout fallback) is deferred to a separate PR. Do not flag the empty BackHandler body as a dead-end in future reviews of this file.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 0
File: :0-0
Timestamp: 2026-03-18T16:30:26.610Z
Learning: In `app/src/main/java/app/gamenative/ui/PluviaMain.kt`, the three sites that call `MainActivity.setPendingLaunchRequest`, toggle `shownPendingLaunchSnackbar`, and conditionally call `SnackbarManager.show` (cold-start pending branch, ExternalGameLaunch pending branch, and non-terminal SteamDisconnected branch) are intentionally kept separate — the surrounding logic differs enough that extracting a shared helper would add more complexity than it removes. Do not suggest refactoring these into a helper in future reviews.

Learnt from: jeremybernstein
Repo: utkarshdalal/GameNative PR: 581
File: app/src/main/java/app/gamenative/ui/PluviaMain.kt:1154-1176
Timestamp: 2026-02-21T22:26:41.871Z
Learning: In Kotlin coroutines, suspend functions automatically suspend and resume within a coroutine scope. Only Deferred objects produced by async { ... } require .await() to obtain the result. Do not call .await() on direct suspend function calls or on non-Deferred results; use await() only when you have a Deferred that represents a value you need.

Learnt from: phobos665
Repo: utkarshdalal/GameNative PR: 1072
File: app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt:1309-1315
Timestamp: 2026-04-02T07:43:14.973Z
Learning: In the GameNative repository, when logging/telemetry includes Wine prefix/container paths that contain the hardcoded Wine username `xuser`, do not treat that as a PII/security issue (it is not a real user identity and contains no personally identifiable information). Only flag PII concerns for other user-identifying data not limited to the `xuser` hardcoded Wine username in prefix paths.

Learnt from: xXJSONDeruloXx
Repo: utkarshdalal/GameNative PR: 1332
File: app/src/main/java/com/winlator/widget/XServerView.java:59-64
Timestamp: 2026-04-29T17:49:49.925Z
Learning: When reviewing this repository and tracing Java method call sites (e.g., looking up usages of a Java API), also include Kotlin sources in the search results (do not omit Kotlin files or run searches with a Java-only filter). Kotlin “UI/orchestration” logic often lives in .kt files and may contain the relevant call paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants