Skip to content

fix: set ColdClientLoader.ini ExeRunDir to exe directory when no workingDir in appinfo#1012

Merged
utkarshdalal merged 1 commit intoutkarshdalal:masterfrom
kiequoo:fix/cold-client-ini-workingdir
Mar 28, 2026
Merged

fix: set ColdClientLoader.ini ExeRunDir to exe directory when no workingDir in appinfo#1012
utkarshdalal merged 1 commit intoutkarshdalal:masterfrom
kiequoo:fix/cold-client-ini-workingdir

Conversation

@kiequoo
Copy link
Copy Markdown
Contributor

@kiequoo kiequoo commented Mar 25, 2026

PR #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 set correctly, ColdClientLoader 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 (PR #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.

This fix derives ExeRunDir from the exe path when workingDir is absent in appinfo, so New Star GP gets release/ as its CWD. When workingDir is present, ExeRunDir is left blank to preserve legacy behaviour (ColdClientLoader uses the loader's own CWD).

The INI content is extracted into generateColdClientIni() for testability, with unit tests covering the New Star GP case and the workingDir-set case.

Fixes: #775
Fixes: #950

Summary by CodeRabbit

  • New Features

    • Launcher now forwards extra launch context for non-legacy DRM containers to improve Steam start behavior.
    • INI/config generation now respects explicit working directories, derives executable run directories when absent, and conditionally includes DLL-injection settings.
  • Tests

    • Added tests validating INI output for working-dir variants, run-directory derivation, AppID output, and conditional DLL injection.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f71dc0f-6e00-4296-b141-176a2d49d0be

📥 Commits

Reviewing files that changed from the base of the PR and between 3ad5c87 and 2ac21e0.

📒 Files selected for processing (3)
  • app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
  • app/src/main/java/app/gamenative/utils/SteamUtils.kt
  • app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/app/gamenative/utils/SteamUtils.kt

📝 Walkthrough

Walkthrough

Passes LaunchInfo into ColdClientLoader.ini generation, refactors INI creation into a pure generator that computes ExeRunDir, normalizes paths, and conditionally includes DLL injection; adds unit tests for the generated INI behavior.

Changes

Cohort / File(s) Summary
X Server / launch plumbing
app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt
For non-legacy Steam launches, forwards appLaunchInfo into SteamUtils.writeColdClientIni(...) so INI generation can use launch-specific workingDir.
Steam INI utils
app/src/main/java/app/gamenative/utils/SteamUtils.kt
Added generateColdClientIni(...) returning INI text; writeColdClientIni(...) now accepts optional LaunchInfo? and delegates to the generator. ExeRunDir derivation and inclusion of the injection DllsToInjectFolder line are controlled by explicit parameters (workingDir, isUnpackFiles).
Unit tests
app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt
New tests covering generateColdClientIni output: ExeRunDir when workingDir is null/empty/set and conditional injection behavior based on isUnpackFiles.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #411: Alters XServerScreen.getWineStartCommand and Steam launch plumbing; directly touches the same call-site changed here.
  • #420: Modifies Steam INI generation and related Steam launch calls; overlaps with SteamUtils refactor.
  • #1008: Changes ExeRunDir behavior in ColdClientLoader.ini generation; strongly related to the ExeRunDir logic updated here.

Suggested reviewers

  • utkarshdalal

Poem

🐰 I hop through folders, tidy each slash,
ExeRunDir found without any clash.
LaunchInfo carried, the INI is neat,
DLLs appear only when flags meet.
Puff! The loader’s ready to greet.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: set ColdClientLoader.ini ExeRunDir to exe directory when no workingDir in appinfo' clearly and specifically describes the main change: it explains what is being fixed (ExeRunDir setting), where (ColdClientLoader.ini), what the new behavior is (set to exe directory), and the condition (when no workingDir in appinfo). This directly aligns with the changeset which refactors INI generation and adds logic to conditionally set ExeRunDir based on workingDir presence.

✏️ 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

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

1 issue found across 3 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/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt">

<violation number="1" location="app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt:28">
P2: Regression test for `ExeRunDir` is too permissive: prefix-based `contains` can pass even when an incorrect subdirectory is appended.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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: 1

🧹 Nitpick comments (1)
app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt (1)

24-73: Strengthen these tests to reduce brittleness and cover trailing-slash regression.

Line 34 depends on a formatting newline, and Line 71 uses assertTrue(!...); both can be made clearer. Also, add an explicit workingDir trailing-slash test to lock in the trim behavior.

🧪 Suggested test refinements
-import org.junit.Assert.assertTrue
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
@@
     fun `ExeRunDir is game root when workingDir is empty`() {
         val ini = generate(workingDir = "")
-        assertTrue(ini.contains("ExeRunDir=steamapps\\common\\Batman Arkham Asylum GOTY\n"))
+        assertTrue(ini.contains("ExeRunDir=steamapps\\common\\Batman Arkham Asylum GOTY"))
     }
@@
     fun `DllsToInjectFolder is absent when isUnpackFiles is false`() {
         val ini = generate(isUnpackFiles = false)
-        assertTrue(!ini.contains("DllsToInjectFolder"))
+        assertFalse(ini.contains("DllsToInjectFolder"))
     }
+
+    `@Test`
+    fun `workingDir trailing slash is trimmed`() {
+        val ini = generate(workingDir = "Binaries/")
+        assertTrue(ini.contains("ExeRunDir=steamapps\\common\\Batman Arkham Asylum GOTY\\Binaries"))
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt` around
lines 24 - 73, Tests in SteamUtilsColdClientIniTest.kt are brittle: remove the
dependency on a trailing newline in the "`ExeRunDir is game root when workingDir
is empty`" test by checking for the substring without "\n" (use generate(...)
and assert that ini.contains("ExeRunDir=steamapps\\common\\Batman Arkham Asylum
GOTY")), replace negated assertTrue(!...) in "`DllsToInjectFolder is absent when
isUnpackFiles is false`" with assertFalse(ini.contains("DllsToInjectFolder")),
and add a new test using generate(workingDir = "Binaries/") (or similar) to
verify that workingDir trailing slashes are normalized (assert
ini.contains("ExeRunDir=...\\Binaries") and not a trailing backslash) so
Generate (the generate(...) helper) and the existing test functions validate
trimming/normalization behavior consistently.
🤖 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/ui/screen/xserver/XServerScreen.kt`:
- Line 2874: The code is passing a possibly-mismatched LaunchInfo (appLaunchInfo
from firstOrNull()) into SteamUtils.writeColdClientIni which can set the wrong
ExeRunDir for multi-launch games; update the call to first locate the LaunchInfo
that corresponds to the selected executable (match container.executablePath
against the LaunchInfo's executablePath/launch entry in container.launches) and
use that matched LaunchInfo when calling SteamUtils.writeColdClientIni, falling
back to the existing firstOrNull() behavior only if no exact match is found.

---

Nitpick comments:
In `@app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt`:
- Around line 24-73: Tests in SteamUtilsColdClientIniTest.kt are brittle: remove
the dependency on a trailing newline in the "`ExeRunDir is game root when
workingDir is empty`" test by checking for the substring without "\n" (use
generate(...) and assert that ini.contains("ExeRunDir=steamapps\\common\\Batman
Arkham Asylum GOTY")), replace negated assertTrue(!...) in "`DllsToInjectFolder
is absent when isUnpackFiles is false`" with
assertFalse(ini.contains("DllsToInjectFolder")), and add a new test using
generate(workingDir = "Binaries/") (or similar) to verify that workingDir
trailing slashes are normalized (assert ini.contains("ExeRunDir=...\\Binaries")
and not a trailing backslash) so Generate (the generate(...) helper) and the
existing test functions validate trimming/normalization behavior consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2a2fbaf2-2ab3-499a-8e41-fe945bcdf2de

📥 Commits

Reviewing files that changed from the base of the PR and between c7da303 and de53810.

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

if (!container.isUseLegacyDRM){
// Create ColdClientLoader.ini file
SteamUtils.writeColdClientIni(gameId, container)
SteamUtils.writeColdClientIni(gameId, container, appLaunchInfo)
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

Match LaunchInfo to the selected executable before writing ColdClientLoader.ini.

On Line 2874, passing appLaunchInfo (currently derived via firstOrNull()) can apply the wrong workingDir for multi-launch games where container.executablePath is not the first launch entry. That can set an incorrect ExeRunDir and break runtime CWD-sensitive titles.

💡 Suggested fix
-            SteamUtils.writeColdClientIni(gameId, container, appLaunchInfo)
+            val normalizedSelectedExe = container.executablePath.replace('\\', '/')
+            val matchedLaunchInfo = SteamService.getWindowsLaunchInfos(gameId).firstOrNull { info ->
+                info.executable.replace('\\', '/').equals(normalizedSelectedExe, ignoreCase = true)
+            } ?: appLaunchInfo
+            SteamUtils.writeColdClientIni(gameId, container, matchedLaunchInfo)
📝 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.

Suggested change
SteamUtils.writeColdClientIni(gameId, container, appLaunchInfo)
val normalizedSelectedExe = container.executablePath.replace('\\', '/')
val matchedLaunchInfo = SteamService.getWindowsLaunchInfos(gameId).firstOrNull { info ->
info.executable.replace('\\', '/').equals(normalizedSelectedExe, ignoreCase = true)
} ?: appLaunchInfo
SteamUtils.writeColdClientIni(gameId, container, matchedLaunchInfo)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt` at line
2874, The code is passing a possibly-mismatched LaunchInfo (appLaunchInfo from
firstOrNull()) into SteamUtils.writeColdClientIni which can set the wrong
ExeRunDir for multi-launch games; update the call to first locate the LaunchInfo
that corresponds to the selected executable (match container.executablePath
against the LaunchInfo's executablePath/launch entry in container.launches) and
use that matched LaunchInfo when calling SteamUtils.writeColdClientIni, falling
back to the existing firstOrNull() behavior only if no exact match is found.

val exePath = "steamapps\\common\\$gameName\\${executablePath.replace("/", "\\")}"
val gameRoot = "steamapps\\common\\$gameName"
val normalizedWorkingDir = workingDir?.replace("/", "\\")?.trim('\\')
val exeRunDir = if (!normalizedWorkingDir.isNullOrEmpty()) "$gameRoot\\$normalizedWorkingDir" else gameRoot
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.

leave blank (legacy behaviour) instead of explicitly setting gameroot

@kiequoo kiequoo force-pushed the fix/cold-client-ini-workingdir branch from 6d8a6cc to 84e5fcf Compare March 27, 2026 07:56
@kiequoo kiequoo changed the title fix: respect appinfo workingdir in ColdClientLoader.ini ExeRunDir fix: set ColdClientLoader.ini ExeRunDir to exe directory when no workingDir in appinfo Mar 27, 2026
@kiequoo kiequoo requested a review from utkarshdalal March 27, 2026 08:04
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.

🧹 Nitpick comments (1)
app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt (1)

27-44: Consider adding test coverage for isUnpackFiles behavior.

The tests thoroughly cover the workingDir scenarios affecting ExeRunDir, which is the core fix in this PR. However, the PR description mentions that "only includes the injection section when unpackFiles is enabled" — this behavior isn't currently tested.

🧪 Suggested additional tests for injection section
`@Test`
fun `injection section excludes DllsToInjectFolder when unpackFiles is false`() {
    val ini = generate(isUnpackFiles = false)
    assert(!ini.contains("DllsToInjectFolder"))
}

`@Test`
fun `injection section includes DllsToInjectFolder when unpackFiles is true`() {
    val ini = generate(isUnpackFiles = true)
    assert(ini.contains("DllsToInjectFolder=extra_dlls"))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt` around
lines 27 - 44, Add tests to cover the isUnpackFiles behavior described in the
PR: call the existing generate(...) helper with isUnpackFiles = false and assert
the produced ini does not contain the "DllsToInjectFolder" key, and call
generate(...) with isUnpackFiles = true and assert the ini contains
"DllsToInjectFolder=extra_dlls" (or the expected value); use the same test
helpers (generate and iniValue / contains on the returned ini) as existing tests
so the new tests mirror the style of `ExeRunDir` tests and verify the injection
section is included only when isUnpackFiles is enabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt`:
- Around line 27-44: Add tests to cover the isUnpackFiles behavior described in
the PR: call the existing generate(...) helper with isUnpackFiles = false and
assert the produced ini does not contain the "DllsToInjectFolder" key, and call
generate(...) with isUnpackFiles = true and assert the ini contains
"DllsToInjectFolder=extra_dlls" (or the expected value); use the same test
helpers (generate and iniValue / contains on the returned ini) as existing tests
so the new tests mirror the style of `ExeRunDir` tests and verify the injection
section is included only when isUnpackFiles is enabled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3254de9f-92c5-4d29-b76e-25c366ba26d6

📥 Commits

Reviewing files that changed from the base of the PR and between 84e5fcf and 3ad5c87.

📒 Files selected for processing (1)
  • app/src/test/java/app/gamenative/utils/SteamUtilsColdClientIniTest.kt

…orkingDir in appinfo

Games like New Star GP whose exe lives in a subdirectory (e.g. release/NSGP.exe)
need ExeRunDir set to that subdirectory so CWD-relative asset and save paths
resolve correctly. When workingDir is present in appinfo, ExeRunDir is left blank
to preserve legacy ColdClientLoader behaviour.

Extracts generateColdClientIni() for testability and adds unit tests.
@kiequoo kiequoo force-pushed the fix/cold-client-ini-workingdir branch from 3ad5c87 to 2ac21e0 Compare March 27, 2026 08:13
@utkarshdalal utkarshdalal merged commit 17f8a98 into utkarshdalal:master Mar 28, 2026
2 checks passed
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