Skip to content

Conversation

@joshuatam
Copy link
Contributor

@joshuatam joshuatam commented Jan 24, 2026

This is useful when some games will create another directory for saves if the game is not start from steam client.

Implements a system to create symbolic links for game save locations, addressing compatibility issues with games that store saves in non-standard locations.

Adds a registry to define these workarounds, allowing for automatic creation of symlinks based on the game's app ID.

The system supports placeholders for Steam ID and Account ID to accommodate user-specific save paths.

Supported game now:
The First Berserker: Khazan (2680010)

Related steam discussion with this issue:
https://steamcommunity.com/app/2680010/discussions/0/595144651000835275/#c595144799751775858


Summary by cubic

Adds game-specific save location symlink support to fix save path mismatches when games aren’t launched from the Steam client. Automatically applies per app ID; initial support for The First Berserker: Khazan (2680010).

  • New Features
    • Registry defines per-game source/target paths and base PathType.
    • Supports {64BitSteamID} and {Steam3AccountID} placeholders for user paths.
    • Creates symlink only if source exists; logs when target exists or isn’t a symlink.
    • Runs during Steam setup flows to apply workarounds automatically.

Written for commit 5ff6fee. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added game-specific save-location support, including automatic symlink handling and a registry entry for "The First Berserker Khazan".
  • Bug Fixes

    • Improved save location management during Steam operations and restoration flows to prevent missing or misplaced saves.

✏️ Tip: You can customize this high-level summary in your review settings.

Implements a system to create symbolic links for game save locations,
addressing compatibility issues with games that store saves in
non-standard locations.

Adds a registry to define these workarounds, allowing for automatic
creation of symlinks based on the game's app ID.

The system supports placeholders for Steam ID and Account ID to
accommodate user-specific save paths.
@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a new SpecialGameSaveMapping data class and registry, plus a new SteamUtils.public function ensureSaveLocationsForGames(context, steamAppId) that resolves game-specific save mappings, computes Steam-ID placeholders, and creates symlinks from target to source with logging and error handling.

Changes

Cohort / File(s) Summary
Game Save Mapping Configuration
app/src/main/java/app/gamenative/enums/SpecialGameSaveMapping.kt
New data class SpecialGameSaveMapping with fields (appId, pathType, sourceRelativePath, targetRelativePath, description) and a companion registry containing an entry for appId 2680010 ("The First Berserker Khazan").
Save Location Symlink Creation & Call Sites
app/src/main/java/app/gamenative/utils/SteamUtils.kt, app/src/main/java/app/gamenative/...
Adds public function ensureSaveLocationsForGames(context: Context, steamAppId: Int) that finds mappings, substitutes {64BitSteamID} and {Steam3AccountID}, checks source existence, and creates a symlink from target → source with logs for missing source, non-symlink targets, and failures. Invoked at multiple Steam-related call sites (DLL replacement, cold client flow, restore flows).

Sequence Diagram(s)

sequenceDiagram
  participant App as App Flow
  participant SteamUtils as SteamUtils
  participant Mapping as SpecialGameSaveMapping
  participant FS as FileSystem

  App->>SteamUtils: ensureSaveLocationsForGames(context, steamAppId)
  SteamUtils->>Mapping: lookup mapping by appId
  Mapping-->>SteamUtils: mapping (sourceRelativePath, targetRelativePath, pathType)
  SteamUtils->>App: resolve user Steam IDs ({64BitSteamID}, {Steam3AccountID})
  SteamUtils->>FS: check if source path exists
  alt source exists
    SteamUtils->>FS: check if target exists and is symlink
    alt target missing or is symlink
      SteamUtils->>FS: create symlink target -> source
      FS-->>SteamUtils: success / failure
    else target exists and not symlink
      FS-->>SteamUtils: report existing non-symlink
    end
  else source missing
    FS-->>SteamUtils: report missing source
  end
  SteamUtils-->>App: return / log result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I dug a little tunnel, neat and snug,
Linking Khazan saves with a happy hug,
IDs tucked in placeholders, snug as can be,
Symlinks sprout where they ought to be,
Hoppy fixes shipped—hooray for me!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: adding game-specific save location symlink support, which directly matches the PR's core objective.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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
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 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="app/src/main/java/app/gamenative/utils/SteamUtils.kt">

<violation number="1" location="app/src/main/java/app/gamenative/utils/SteamUtils.kt:1238">
P2: Missing `PrefManager` fallback for Steam ID retrieval. Other functions in this file (e.g., `ensureSteamSettings`) check `PrefManager.steamUserSteamId64` and `PrefManager.steamUserAccountId` as fallbacks before defaulting to "0". Without this fallback, symlinks could be created with "0" in the path when the Steam ID is temporarily unavailable from `SteamService`.

(Based on your team's feedback about adding fallbacks when reading optional data.) [FEEDBACK_USED]</violation>
</file>

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

Comment on lines +1238 to +1239
val accountId = SteamService.userSteamId?.accountID?.toLong() ?: 0L
val steamId64 = SteamService.userSteamId?.convertToUInt64()?.toString() ?: "0"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 24, 2026

Choose a reason for hiding this comment

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

P2: Missing PrefManager fallback for Steam ID retrieval. Other functions in this file (e.g., ensureSteamSettings) check PrefManager.steamUserSteamId64 and PrefManager.steamUserAccountId as fallbacks before defaulting to "0". Without this fallback, symlinks could be created with "0" in the path when the Steam ID is temporarily unavailable from SteamService.

(Based on your team's feedback about adding fallbacks when reading optional data.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/utils/SteamUtils.kt, line 1238:

<comment>Missing `PrefManager` fallback for Steam ID retrieval. Other functions in this file (e.g., `ensureSteamSettings`) check `PrefManager.steamUserSteamId64` and `PrefManager.steamUserAccountId` as fallbacks before defaulting to "0". Without this fallback, symlinks could be created with "0" in the path when the Steam ID is temporarily unavailable from `SteamService`.

(Based on your team's feedback about adding fallbacks when reading optional data.) </comment>

<file context>
@@ -1209,5 +1221,59 @@ object SteamUtils {
+        val workaround = SaveLocationWorkaround.registry.find { it.appId == steamAppId } ?: return
+
+        try {
+            val accountId = SteamService.userSteamId?.accountID?.toLong() ?: 0L
+            val steamId64 = SteamService.userSteamId?.convertToUInt64()?.toString() ?: "0"
+            val steam3AccountId = accountId.toString()
</file context>
Suggested change
val accountId = SteamService.userSteamId?.accountID?.toLong() ?: 0L
val steamId64 = SteamService.userSteamId?.convertToUInt64()?.toString() ?: "0"
val accountId = SteamService.userSteamId?.accountID?.toLong()
?: PrefManager.steamUserAccountId.takeIf { it != 0 }?.toLong()
?: 0L
val steamId64 = SteamService.userSteamId?.convertToUInt64()?.toString()
?: PrefManager.steamUserSteamId64.takeIf { it != 0L }?.toString()
?: "0"
Fix with Cubic

* - {64BitSteamID} - Replaced with the user's 64-bit Steam ID
* - {Steam3AccountID} - Replaced with the user's Steam3 account ID
*/
data class SaveLocationWorkaround(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd recommend giving this a more approprate name like "bindedSaveLocation" or something. Workaround always makes it sound hacky :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, updated see if you agree this name

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