Skip to content

Execute Steam InstallScripts during container setup#1464

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

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

Conversation

@playday3008
Copy link
Copy Markdown

@playday3008 playday3008 commented May 21, 2026

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 game launch. Writes registry keys pre-Wine and chains installers via Wine, using HasRunKey and installer exit codes to avoid re-running.

  • New Features

    • Discover scripts by scanning depot manifests for EDepotFileFlag.InstallScript.
    • Parse VDFs with KeyValue: env var expansion, language overrides, OS filters; default HasRunKey when missing.
    • Apply registry edits to Wine system.reg/user.reg before starting Wine.
    • Build run-process commands and chain them into the launch; mark completion only on installer exit code 0.
    • Integrate with XServerScreen alongside PreInstallSteps.
    • Add tests for InstallScriptParser and InstallScriptExecutor.
  • Bug Fixes

    • Compute effective HasRunKey before skip-check so fallback-keyed actions don’t re-run.
    • Resolve registry hive from key prefix (HKCU → user.reg, HKLM → system.reg) for reads/writes and completion marks.
    • 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 c442dfb. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

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

    • Unified chaining of pre-install and install-script steps with per-step completion gating and cleanup between steps.
  • Improvements

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

    • Added unit tests for install-script parsing, registry/OS requirements, and command-generation.

Review Change Stack

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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c479c51d-f8e2-4404-9d92-91e422a3e9f2

📥 Commits

Reviewing files that changed from the base of the PR and between 88a7d6d and c442dfb.

📒 Files selected for processing (3)
  • app/src/main/java/app/gamenative/utils/preInstallSteps/UbisoftConnectStep.kt
  • app/src/test/java/app/gamenative/utils/PreInstallStepsTest.kt
  • app/src/test/java/app/gamenative/utils/preInstallSteps/UbisoftConnectStepTest.kt

📝 Walkthrough

Walkthrough

Adds Steam install-script support: data models, a VDF parser with env-var expansion, an executor that discovers scripts from downloaded depots, applies registry changes, generates run-process commands, integrates command chaining into Steam launches, and includes parser/executor 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.
Steam Launch Integration
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
Integrates install-script collection/application into Steam launch flow, populates installScriptRunProcessCommands, updates boot splash selection, and replaces pre-install-only chaining with a unified chained-command mechanism that runs wineserver -k between steps and schedules guest execution for the final step.
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 test inputs switched to GOG where appropriate.

Sequence Diagram(s)

sequenceDiagram
  participant XServerScreen
  participant InstallScriptExecutor
  participant DepotManifest
  participant InstallScriptParser
  participant WineRegistry
  participant GuestProgram
  XServerScreen->>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)
  XServerScreen->>InstallScriptExecutor: applyRegistryKeys(container, scripts, language)
  InstallScriptExecutor->>WineRegistry: write string/DWORD values (system.reg / user.reg)
  XServerScreen->>InstallScriptExecutor: getRunProcessCommands(container, scripts, screenInfo, is64Bit)
  InstallScriptExecutor-->>XServerScreen: 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 InstallScript as InstallScript Executor
  participant Chain as Command Chain
  participant Guest as Guest Program
  GameLaunch->>InstallScript: collectScripts & applyRegistryKeys
  GameLaunch->>InstallScript: getRunProcessCommands
  GameLaunch->>Chain: build unified command chain (preInstall + installScript)
  Chain->>Guest: execute step N
  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

  • utkarshdalal/GameNative#921: Related UbisoftConnectStep and pre-install marker logic adjustments which overlap with the UbisoftConnect appliesTo changes here.

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,
Chain the steps till setup's done —
Then wink, "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 14.29% 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 accurately and concisely describes the main change: executing Steam InstallScripts during container setup, which is the primary objective of the entire changeset.
Description check ✅ Passed The description covers the main changes, type of change (marked), and includes a recording link. However, the checklist item about discussing in #code-changes is unchecked, which may indicate incomplete preparation per repository 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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
Comment thread app/src/main/java/app/gamenative/utils/installscript/InstallScriptExecutor.kt Outdated
Copy link
Copy Markdown
Contributor

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

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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

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.
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