Skip to content

Fix steam client#1

Merged
TideGear merged 51 commits into
masterfrom
Fix-Steam-Client
Apr 21, 2026
Merged

Fix steam client#1
TideGear merged 51 commits into
masterfrom
Fix-Steam-Client

Conversation

@TideGear
Copy link
Copy Markdown
Owner

Description

Recording

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.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

xXJSONDeruloXx and others added 30 commits April 12, 2026 22:34
Co-authored-by: Phobos665 <5970062+phobos665@users.noreply.github.com>
…hdalal#1133)

* feat: update wine-mono version to 11.0.0 in installer scripts

* fix: bump_version in ImageFsInstaller.java,
will trigger extraction for all existing containers that are on Mono 9.0.0 and install 11.0.0.
* feat: add fake HDR screen effect

* fix: remove quick menu darkening scrim

* chore: shorten HDR effect description

* refactor: rename HDR effect to vivid mode

* i18n: add vivid mode translations
DownloadService caches directory listings for 5s. After deleteApp,
the cache still holds the deleted directory, so the subsequent
LibraryInstallStatusChanged refresh sees stale data. Invalidate
the cache after deletion so the next scan picks up the change.
…tkarshdalal#1191)

* fix: correct steam game dlc licensing logic and enhance dlc display in content

Cross-references resolved depots with owned DLC package information to ensure depots are attributed to the correct DLC app ID. This ensures accurate DLC identification for titles like Don't Starve, Halo MCC, and Cyberpunk 2077.

* refactor getMainAppDepots to calculate the logic to be used in getDownloadableDepots
…dalal#918)

Also use state.isSteamConnected (Compose-observable StateFlow) instead
of SteamService.isConnected (static boolean invisible to recomposition)
for banner visibility.
* fix: case-insensitive .exe filter in getWindowsLaunchInfos

* removed bug around appLaunchInfo null opening wfm.exe

* fixed build

* addressed coderabbit

* more coderabbit

---------

Co-authored-by: Dan Brooke <mail@danbrooke.net>
Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
…s to steamcloud (utkarshdalal#1100)

* migrate GSE Saves to steam userdata, always upload userdata files to steam cloud

fix tests

* move migrateGSESavesToSteamUserdata just before beginLaunchApp

* also migrateGSESavesToSteamUserdata just before forceSyncUserFiles

* also migrateGSESavesToSteamUserdata in SteamUtils ensureSteamSettings

* use Files.move for migrating files

* check dir empty to exit earlier, update logging

* preserve file attributes like timestamp and permission during migration
* Add reusable ini game fix for Imperivm

* Avoid rereading ini fixes after migration

* Remove ini migration marker tracking
…ior (utkarshdalal#1014)

* feat: disable IME extract UI and centralize singleLine keyboard behavior

* fix: convert remaining OutlinedTextField to NoExtractOutlinedTextField
…al#1199)

Maintains the original modification time from Steam Cloud for downloaded files. This ensures that games which rely on file timestamps for save loading and ordering, such as Skyrim, function correctly.
* feat: added multi-controller support

- physical controller handler services now acommodate multi controller
state management via deviceId
- new autoassign func
- windhandler accomodated MAX_PLAYERS = 4 suppot, multi-controller state
management
- new MultiControllerTest.kt test suite

fix: address multi-controller rumble and device identifier bugs

- prevent missed rumble on secondary slots when controller is adopted
    after a rumble command arrives by deferring the "delivered" marker
  - remove unreachable vendor/product fallback in device identifier
    lookup since getDescriptor() is available on all supported API
levels

revert to original controller manager handling legacy cases and added test case

* handled respect disabled slots when reusing an existing assignment

* reset rumble state when a slot adopts a new controller

* magic number removed using dedicated WinHandler.MAX_PLAYERS const

* removed more magic numbers - using dedicated MAX_PLAYERS const and remove null controller phone vibrate case with some docs on why

---------

Co-authored-by: = <=>
)

size estimate used PrefManager.containerLanguage (global default) but
download uses container.language (per-container). mismatch causes wrong
depot count and size when container language differs from default.
* feat: add quick menu fsr and sharpness controls

* refactor: port fsr passes from official gpuopen source

* feat: add true fsr render-scale upscaling

* chore: remove fsr toggle description

* refactor: derive fsr input from container resolution

* feat: add live scaling modes

* chore: move scaling controls to top of screen effects

* fix: match default fsr rcas denoise behavior

* fix: save quick menu scrim removal before master sync

* fix(renderer): only override render target size for scaled scenes
…shdalal#1136)

* fix: clear stale container state on task swipe and app restart

when the user swipes the app from the task switcher while a container
is running, keepAlive stays true but xEnvironment is gone. on next
launch the app is stuck thinking a container is running.

extract shutdownEnvironment() from XServerScreen.exit() so the same
full teardown runs in both the normal exit path and the crash recovery
path. each step is wrapped in runCatching so one failure doesn't
prevent the rest.

- onCreate: if keepAlive is set but xEnvironment is null, run
  shutdownEnvironment() to clear stale state
- onDestroy: emit ActivityDestroyed before super (so exit() listeners
  still fire), then force shutdownEnvironment() if keepAlive persists
- exit(): delegates teardown to shutdownEnvironment(), keeps only
  winHandler.stop() and trash cleanup (container-specific)

* fix: stop all foreground services on task swipe when idle

Steam/GOG/Epic services had no onTaskRemoved — foreground notification
persisted after swipe because nothing told the service to stop itself.
…edge cases (utkarshdalal#1157)

* fix: recognize WindowsHome UFS root as PathType.Root for cloud save sync

Steam PICS can specify `root: WindowsHome` in save file patterns (e.g.
Stellar Blade, app 3489700). PathType.from() did not handle this token,
causing it to fall through to PathType.None. None.isWindows is false, so
the pattern was silently dropped in getLocalUserFilesAsPrefixMap and the
saves were never scanned or synced.

WindowsHome is the Windows user home directory (C:\users\xuser\ in Wine),
which is exactly what PathType.Root maps to. Fix by recognising
"windowshome", "%windowshome%", and "root" in PathType.from() as Root,
and adding Root to the isWindows set so it passes the save pattern filter.

* fix: recognize SteamCloudDocuments UFS root as WinMyDocuments for cloud save sync

Steam PICS can specify `root: SteamCloudDocuments` in save file patterns
(e.g. Sonic Mania, app 584400). PathType.from() did not handle this token,
causing it to fall through to PathType.None and be silently dropped during
save pattern filtering.

SteamCloudDocuments is Steam's name for the user's Documents folder, which
maps to WinMyDocuments (C:\users\xuser\Documents\) in Wine. Fix by
recognising "steamclouddocuments" and "%steamclouddocuments%" in
PathType.from() as WinMyDocuments.

* fix: normalize '.' save path to empty string to prevent broken cloud keys

Steam PICS manifests sometimes use `path: .` to mean "root of this path
type, no subdirectory" (common in Unity games). When a Windows rootoverride
also has a non-empty addpath, the dot was appended literally — producing
paths like "Thunder Lotus Games/Spiritfarer/." and uploadPath = "." —
which caused cloudPrefixToLocalPath to build a key like "%GameInstall%."
that never matches the bare "%GameInstall%" prefix the cloud API returns,
so downloaded files landed in the wrong directory.

Fix by normalising "." to "" at parse time in KeyValueUtils, consistent
with how UserFileInfo.prefix already treats cloudPath == ".". Affected
games: Spiritfarer and CrossCode.

* fix: recognize WinProgramData and SteamUserBaseStorage UFS path types, handle oslist in rootoverrides

- Add WinProgramData PathType mapping to drive_c/ProgramData/
- Alias SteamUserBaseStorage to SteamUserData in PathType.from()
- Check oslist field alongside os when filtering Windows rootoverrides

* fix: bump CURRENT_UFS_PARSE_VERSION to 2 to force re-parse of cached UFS data

Ensures existing cached SteamApp rows are re-parsed to pick up the path
normalization and oslist rootoverride fixes from this branch.

* fix: lowercase Root/ROOT_MOD aliases and add wrapped %root% form in PathType.from()

* fix: add %steamuserbasestorage% as suggested by coderabbit
* fix(hud): measure fps from render frames

* fix(hud): track fps against topmost app window
…lal#1169)

* fix: show conflict dialog when cloud sync cache is missing, regardless of change number

the old gate (localAppChangeNumber >= 0) skipped the conflict dialog
for first-time offline players whose change number is -1. this meant
local saves were silently overwritten by cloud on reconnect. remove
the gate: if cache is absent and local files exist, always treat as
conflict.

* test: cloud sync decision matrix covering all cache/CN/cloud states

12 scenarios covering: cache present/absent, local changes/none,
cloud ahead/same, preferred save location, first-time offline play.
also fixes existing download test (cloud filenames, mock params,
deprecated API).

* fix: split cache-absent conflict into upgrade vs first-offline cases

* test: revert modifications to existing tests, keep only new additions
utkarshdalal and others added 21 commits April 14, 2026 18:43
* feat: streaming download+assembly for Epic/GOG to reduce disk usage

replace two-phase (download all chunks → assemble all files) with a
unified loop that assembles files front-to-back as their chunks land
and deletes consumed chunks immediately. peak disk usage drops from
~2x install size to ~1x.

- StreamingAssembly: shared pure logic for chunk ordering, last-file
  tracking, readiness checks, and safe deletion decisions
- EpicDownloadManager: downloadAndAssembleEpicChunks replaces separate
  download+assembly phases for both base game and DLC
- GOGDownloadManager: downloadAndAssembleChunks with secure link
  refresh, used by both main game and dependency downloads. removed
  dead downloadChunksSimple and assembleFiles.
- 14 unit tests covering ordering, deduplication, shared chunks,
  cleanup safety, and full batch-loop simulations

* fix: run final assembly pass for zero-chunk Epic files

mirrors the GOG fix — when all files have zero chunks, the chunk loop
never executes and assembly never runs without a trailing pass.

* fix: allow all-zero-chunk Epic manifests to reach assembly

* Resolved conflicts, sped up GOG, made resuming downloads for GOG work better, made downloading UI progress smoother

* handle retries correctly for assembly for GOG

* addressed coderabbit comments

* More AI fixes

---------

Co-authored-by: Jeremy Bernstein <jeremy.d.bernstein@googlemail.com>
Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
…hdalal#1220)

* Aligned epic downloads to GOG to make it faster from eg India

* coderabbit comments, removed xserverscreen mistake changes

---------

Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
…#1143)

* Added launch dependency tests

* Added game fix registry tests

* Added preinstall step tests

* Some AI improvements + moved gamefixes tests to new types folder
* Added recommendations to game page + library, added toggle to hide recommendations,

* Added review scores to recommended games

* Added date to recommended app screen like the others

* coderabit comments

---------

Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
…hdalal#1228)

destructive db migration wipes file_change_lists cache, making every
steam game trigger conflict on first launch post-update. check if
local state is byte-identical to remote manifest (by filename + SHA)
before declaring conflict; if so, rehydrate cache and report UpToDate
silently. also populates real timestamps on the genuine-divergence
path (was showing epoch).

test: dbCleared_localMatchesRemote_rehydratesSilently_noConflict
* chore(): openApi specs for Gog, Epic & Amazon

* chore(): Update the epic token to look more fake.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* added toggle to disable posthog tracking

* Updated readme to include analytics info

---------

Co-authored-by: Utkarsh Dalal <utkarsh.dalal@toptal.com>
…ng original behaviour + tests) (utkarshdalal#1052)

* Reapply "Refactored default Proton downloads to launch deps" (utkarshdalal#1050)

This reverts commit 1cd84b4.

* Moved proton download to original location by moving launch deps call

* Removed deletion since previously we didnt do that either.

* Added test for new launch dep
## Summary

Stabilizes the "real Steam client" launch path (`steam.exe -applaunch`) so
games like Darkest Dungeon and 868-HACK boot cleanly without the multi-minute
"gray Play" reconcile loop, and adds a per-container toggle to disable the
Steam overlay. Also hardens cloud sync and GSE→userdata migration against
mode switches between emu and real-Steam launches.

## Changes

### Steamworks Common Redistributables (228980) manifest

- `SteamUtils.writeSteamworksCommonManifest` now writes only the depots whose
  `_CommonRedist/<folder>/installscript.vdf` is actually present on disk
  (typically 228985 vcredist 2013 + 228989 vcredist 2022) instead of the full
  24 PICS-declared depots. The SKIP check compares against the same
  present-depot baseline, so a correct manifest survives across launches.
- `SteamUtils.createAppManifest` emits a `SharedDepots` block in the child
  game's acf for any depot whose parent app id is known (via
  `DepotInfo.depotFromApp`). Declaring shared ownership up front stops Steam
  from reparenting depots and cascading `Update Queued` onto the child on
  every launch.
- Removed the now-unused `canonical228980SizeOnDisk`,
  `canonical228980BytesToDownload`, and `canonical228980BytesToStage`
  constants; size-on-disk is summed from the present depots and
  bytes-to-download is `0`.

Why: writing all 24 depots made Steam prune 22 phantoms every boot (~3 min of
gray Play). Writing an empty manifest made Steam try to download ~52 MB,
hang `Suspended`, and permanently cascade `Update Queued`. The 2-depot +
`SharedDepots` shape is the only combination that survives Steam's reconcile.

### Disable Steam Overlay toggle

- New per-container `disableSteamOverlay` flag (`Container.java`,
  `ContainerData.kt`, `PrefManager.kt`, `strings.xml`).
- Toggle surfaced in the container dialog's General tab, visible only when
  "Launch Real Steam" is enabled (`GeneralTab.kt`).
- When enabled, `XServerScreen` exports `DISABLE_VK_LAYER_VALVE_steam_overlay_1=1`
  and `SteamNoOverlayUIDrawing=1` into the Wine env so the overlay DLL never
  injects.

### Real-Steam-mode aware sync & migration

- `SteamService.beginLaunchApp` / `syncUserFiles` / `closeApp` now take an
  `isLaunchRealSteam` flag. In real-Steam mode the Goldberg achievement sync
  and the GSE→Steam userdata migration are skipped so the real client owns
  its own save state.
- `MainViewModel` tracks the last launch mode (`lastSteamMode` extra) and,
  on real→emu transitions, cleans up artifacts from the previous mode to
  avoid mixing GSE and real-client save layouts.
- `PluviaMain` threads the launch-mode flag through to `syncUserFiles`.

### Cloud conflict handling

- `SteamAutoCloud` refactors the byte-identical detection into a shared
  lambda and adds a "cache lost but local == remote" branch that silently
  rehydrates the cache instead of surfacing a spurious conflict prompt.

### Launch diagnostics

- `MainViewModel` adds `SteamFixDiagnostics` (last mapped window class,
  game-window-mapped flag, last unmatched window class) and a helper
  window-class filter (`STEAM_HELPER_WINDOW_CLASSES`) so stall reports
  distinguish the real game window from Steam's own helper UI.
- `XServerScreen` integrates a 60 s stall watchdog and logs window-mapping
  events for post-mortem analysis of gray-Play hangs.

### Supporting changes

- `SteamTokenLogin`, `PreInstallSteps`, `preInstallSteps/VcRedistStep`,
  `ContainerUtils`, and `ImageFsInstaller` receive small wiring changes to
  support the real-Steam-client launch path (vcredist gating, container
  defaults, ImageFs layout).

## Test plan

- [ ] Launch Darkest Dungeon (262060) three times in a row — Play button
      should auto-dismiss; no multi-minute gray Play.
- [ ] Launch 868-HACK — clean boot via real Steam client.
- [ ] Switch a container between emu and real-Steam mode; verify saves from
      the prior mode are not blended into the new one.
- [ ] Toggle "Disable Steam Overlay" on a real-Steam container and confirm
      the overlay DLL does not inject at runtime.
- [ ] With a stale cloud cache matching remote, confirm no conflict dialog
      appears.
Three fixes to real-Steam-client launch path, all in SteamUtils.kt:

1. applySteamInstallScriptShim (new): writes HKLM\Software\Valve\Steam\Apps
   and \InstallScripts entries for appId, 228980, and all 24 canonical
   228980 depots with Installed=1 / Run=1. This stops Steam from re-running
   bundled vc_redist.x86.exe / vc_redist.x64.exe / DXSETUP.exe on every
   launch, which was racing against the game's own MSVC loader and (on
   Unity titles) triggering UnityCrashHandler. Called from restoreSteamApi
   after createAppManifest.

2. verifyRestoredState: no longer requires a .orig sibling next to
   steam_api*.dll. Games with useLegacyDRM=false never go through
   replaceSteamApi and so never produce a .orig, which caused the old
   check to log "DLL marker desync" on every launch and needlessly
   re-copy pipe DLLs. Now compares the on-disk DLL hash against the pipe
   asset hash: match = still pipe (bad), mismatch = restored (good).

3. createAppManifest regularDepots-empty path: downgraded from W to I when
   the existing appmanifest already has a valid buildId + depots +
   UpdateResult=0. The PICS "regularDepots empty" result is a transient
   refresh flake and not actionable when the acf is otherwise healthy.

Validated on Shiren (2178480) and Darkest Dungeon (262060): shim writes
300 reg entries, vcredist windows no longer appear on 2nd+ launches, DLL
hash check logs "DLL marker + hash ok, skipping DLL copy" instead of
desync warnings.
The real-Steam launch path is stable across DD / 868-HACK / Shotgun King /
Baba Is You — fold in the remaining structural fixes and strip the
investigation-era diagnostic scaffolding that was left behind.

SteamUtils.kt:
- Replace hardcoded canonical228980Depots table with a runtime PICS resolver
  driven by the child acf's SharedDepots. Fixes permanent "gray Play" on
  games whose shared-redist depot set diverges from DD's (was silently
  writing InstalledDepots{} + BytesToDownload=52MB on those).
- Add SHA-256 verify-and-reheal to STEAM_DLL_REPLACED / STEAM_DLL_RESTORED
  markers in replaceSteamApi/restoreSteamApi so a stale marker can no
  longer lie about on-disk state (2nd-launch black-screen repro).
- Add validateAcfShape() self-check: after writing any child or 228980
  manifest, log Timber.e on shapes known to cause gray Play (Update
  Required bit set, BytesToDownload > 0, InstalledDepots empty when it
  shouldn't be). Catches future regressions loudly instead of silently.

Diagnostic cleanup:
- Drop SteamFixDiagnostics object, STEAM_HELPER_WINDOW_CLASSES,
  REAL_STEAM_STALL_WATCHDOG_MS, and all Timber.tag("SteamFix") log lines
  across SteamUtils, SteamService, SteamAutoCloud, SteamTokenLogin,
  MainViewModel, XServerScreen, ImageFsInstaller.
- Rewrite the comments that referenced the investigation's numbered
  SteamFix list (utkarshdalal#8, utkarshdalal#9, utkarshdalal#11, utkarshdalal#16utkarshdalal#24) so they stand on their own.

UX default:
- Default disableSteamOverlay to true in Container.java, ContainerData.kt,
  and PrefManager.kt. Overlay-on is an opt-in now.

Housekeeping:
- .gitignore: exclude .claude/ session state.
- Drop tracked .claude/scheduled_tasks.lock.
Addresses three related issues seen when launching games in real-Steam mode.

1. Suppress Wine-hosted Steam client's cloud sync (Option B)
   The Wine-hosted Steam client was racing GameNative's SteamKit cloud
   sync on every launch/exit. Per-app gates now disable the Wine
   client's cloud path in userdata/<steamID>/config/localconfig.vdf
   (cloud_enabled=0, cce=0) for both the existing-file and new-file
   branches, with a setOrReplaceKey helper to avoid duplicates.

2. Graceful red-exit with 5s grace window
   The quick-menu EXIT_GAME action previously went straight to a SIGKILL
   of the Wine process tree, giving games no chance to flush saves. It
   now sends WM_CLOSE to the game exe (and to steam.exe when real-Steam
   mode is on), waits 5s, then proceeds with the existing exit path. A
   second tap within the grace window cancels the wait and force-quits.

3. Filter localhost pending-operations in real-Steam mode
   Steam's server reports the Wine-hosted client's self-registered
   session as machineName="localhost" with UploadPending, causing a
   spurious "Pending Upload" dialog before every real-Steam launch.
   beginLaunchApp now filters pendingRemoteOperations by
   machineName="localhost" when isLaunchRealSteam is true. Genuine
   entries from other devices still surface the dialog, and the
   kickPlayingSession path is unaffected.

4. Fix inverted uploadsRequired flag on exit sync
   signalAppExitSyncDone was passing uploadsRequired = (... == false),
   producing impossible states like "upload succeeded but wasn't
   required". Flipped to == true so Steam's server gets a truthful
   signal and stops holding stale pending markers.

Also: ignore .claude/ session state directory.
SysVSharedMemory.delete(int) mutated the shmemories SparseArray without
synchronizing on it, while every other mutator (get, attach, detach,
deleteAll) held synchronized(shmemories). delete is invoked from the
SysV SHM connector thread (SysVSHMRequestHandler), and detach is invoked
from the X11 connector thread (MITSHMExtension), so the two can run
concurrently on the same array.

When delete ran mid-iteration in detach, SparseArray's internal DELETED
sentinel (a bare Object) could surface at valueAt(i), producing:

  java.lang.ClassCastException: java.lang.Object cannot be cast to
    com.winlator.sysvshm.SysVSharedMemory$SHMemory
      at SysVSharedMemory.detach(SysVSharedMemory.java:117)
      at SHMSegmentManager.detach(SHMSegmentManager.java:26)
      at MITSHMExtension.detach(MITSHMExtension.java:74)

Fix: wrap the body of delete(int) in synchronized(shmemories). The lock
is reentrant, so deleteAll (which already holds it while calling delete
per-id) is unaffected.

Files:
  app/src/main/java/com/winlator/sysvshm/SysVSharedMemory.java
Three audit findings from the Fix-Steam-Client branch review:

- XServerScreen: cancel gracefulExitJob in DisposableEffect.onDispose.
  If the screen disposed during the 5s force-quit grace window, the
  coroutine leaked (only exitWatchJob was cancelled).

- SteamUtils.createAppManifest: filter shared depots whose depotFromApp
  is 0 before writing the SharedDepots block. Writing `"<id>" "0"` told
  Steam the depot belonged to a nonexistent app 0, which could
  re-trigger the PICS reconcile / gray-Play cascade this branch was
  built to prevent. Unowned shared depots are now logged and omitted.

- SteamUtils.applySteamInstallScriptShim: narrow the blanket
  `catch (Exception)` to IOException + SecurityException so programming
  errors (NPE, ISE) surface instead of being silently swallowed.
@TideGear TideGear merged commit 4d14726 into master Apr 21, 2026
1 check failed
TideGear added a commit that referenced this pull request May 2, 2026
DRI3Extension.pixmapFromFd
- Move drawable.setOnDestroyListener(onDestroyDrawableListener) so it only
  fires after createPixmap succeeds. Registering it earlier caused a
  double-unmap on the failure path: removeDrawable() invoked the listener
  (unmap #1), then the finally block unmapped again because
  handedOffToDrawable was still false. (Bug introduced by my earlier
  SHM-leak fix.)

GeneralTab SdkCloudSaveSubdirField
- Drop the wasBlank/meaningful/confirm-dialog gate from the typing path
  entirely. The original wasBlank-on-first-keystroke check fired the
  confirm dialog on the first character and disrupted typing; my length>1
  patch made it never fire because by char 2 wasBlank was already false.
  Manual typing is intentional — commit each keystroke directly. The
  first-activation confirmation is reserved for auto-filled values from
  the Recommend / Detect buttons below, which the user didn't type.

SteamService phantom dismiss
- Filter probed pending ops by machineName before auto-dismissing. Only
  entries from our machineName (or "localhost") are treated as phantoms;
  any cross-device entry blocks the auto-dismiss path so legitimate
  cloud conflicts surface to the SYNC_CONFLICT dialog instead of getting
  silently wiped by ignorePendingOperations=true.

SteamService beginLaunchApp offline gate
- Move the isOffline/!isConnected early-return below the GSE→userdata
  migrate, inside the existing try block. Local prep should run even
  offline (one-shot file moves, not cloud-dependent). The cloud RPCs
  below the gate continue to short-circuit when offline.

PluviaMain SDK-cloud-bridge prompt
- Don't fire the prompt for non-game launches: bootToContainer (Open
  Container), useTemporaryOverride, or skipCloudSync flows. The post-
  prompt `relaunch` lambda doesn't carry those flags forward, so an
  Open-Container that triggered the prompt would silently turn into a
  game launch on dismiss. Easier to gate the prompt out than thread
  every flag through the relaunch.

SteamUtils verifyReplacedState / verifyRestoredState
- Treat a missing pipe-asset hash as a verification FAILURE rather than
  a silent skip. The previous `?: return@forEach` would skip the file
  and leave the function returning true — meaning a future build that
  drops a pipe DLL asset would silently let the marker check pass and
  suppress DLL repair. Fail closed instead.

LudusaviRegistry.ensureLoaded
- Wrap the fetch + disk-write + memoryCache-populate path in a
  kotlinx.coroutines Mutex with a double-check on entry. Without it,
  concurrent primeCache() + lookup() callers could both pass the null
  check and both fetch the 5 MB manifest. Now the second waiter sees
  the populated cache after the first finishes.

Findings flagged but not changed:
- SteamUtils SDK cloud mirror nested directories (third time): mirror
  is by design for Dead Cells flat saves; recursive expansion remains
  scope creep.
TideGear added a commit that referenced this pull request May 3, 2026
DRI3Extension.pixmapFromFd
- Move drawable.setOnDestroyListener(onDestroyDrawableListener) so it only
  fires after createPixmap succeeds. Registering it earlier caused a
  double-unmap on the failure path: removeDrawable() invoked the listener
  (unmap #1), then the finally block unmapped again because
  handedOffToDrawable was still false. (Bug introduced by my earlier
  SHM-leak fix.)

GeneralTab SdkCloudSaveSubdirField
- Drop the wasBlank/meaningful/confirm-dialog gate from the typing path
  entirely. The original wasBlank-on-first-keystroke check fired the
  confirm dialog on the first character and disrupted typing; my length>1
  patch made it never fire because by char 2 wasBlank was already false.
  Manual typing is intentional — commit each keystroke directly. The
  first-activation confirmation is reserved for auto-filled values from
  the Recommend / Detect buttons below, which the user didn't type.

SteamService phantom dismiss
- Filter probed pending ops by machineName before auto-dismissing. Only
  entries from our machineName (or "localhost") are treated as phantoms;
  any cross-device entry blocks the auto-dismiss path so legitimate
  cloud conflicts surface to the SYNC_CONFLICT dialog instead of getting
  silently wiped by ignorePendingOperations=true.

SteamService beginLaunchApp offline gate
- Move the isOffline/!isConnected early-return below the GSE→userdata
  migrate, inside the existing try block. Local prep should run even
  offline (one-shot file moves, not cloud-dependent). The cloud RPCs
  below the gate continue to short-circuit when offline.

PluviaMain SDK-cloud-bridge prompt
- Don't fire the prompt for non-game launches: bootToContainer (Open
  Container), useTemporaryOverride, or skipCloudSync flows. The post-
  prompt `relaunch` lambda doesn't carry those flags forward, so an
  Open-Container that triggered the prompt would silently turn into a
  game launch on dismiss. Easier to gate the prompt out than thread
  every flag through the relaunch.

SteamUtils verifyReplacedState / verifyRestoredState
- Treat a missing pipe-asset hash as a verification FAILURE rather than
  a silent skip. The previous `?: return@forEach` would skip the file
  and leave the function returning true — meaning a future build that
  drops a pipe DLL asset would silently let the marker check pass and
  suppress DLL repair. Fail closed instead.

LudusaviRegistry.ensureLoaded
- Wrap the fetch + disk-write + memoryCache-populate path in a
  kotlinx.coroutines Mutex with a double-check on entry. Without it,
  concurrent primeCache() + lookup() callers could both pass the null
  check and both fetch the 5 MB manifest. Now the second waiter sees
  the populated cache after the first finishes.

Findings flagged but not changed:
- SteamUtils SDK cloud mirror nested directories (third time): mirror
  is by design for Dead Cells flat saves; recursive expansion remains
  scope creep.
TideGear added a commit that referenced this pull request May 4, 2026
DRI3Extension.pixmapFromFd
- Move drawable.setOnDestroyListener(onDestroyDrawableListener) so it only
  fires after createPixmap succeeds. Registering it earlier caused a
  double-unmap on the failure path: removeDrawable() invoked the listener
  (unmap #1), then the finally block unmapped again because
  handedOffToDrawable was still false. (Bug introduced by my earlier
  SHM-leak fix.)

GeneralTab SdkCloudSaveSubdirField
- Drop the wasBlank/meaningful/confirm-dialog gate from the typing path
  entirely. The original wasBlank-on-first-keystroke check fired the
  confirm dialog on the first character and disrupted typing; my length>1
  patch made it never fire because by char 2 wasBlank was already false.
  Manual typing is intentional — commit each keystroke directly. The
  first-activation confirmation is reserved for auto-filled values from
  the Recommend / Detect buttons below, which the user didn't type.

SteamService phantom dismiss
- Filter probed pending ops by machineName before auto-dismissing. Only
  entries from our machineName (or "localhost") are treated as phantoms;
  any cross-device entry blocks the auto-dismiss path so legitimate
  cloud conflicts surface to the SYNC_CONFLICT dialog instead of getting
  silently wiped by ignorePendingOperations=true.

SteamService beginLaunchApp offline gate
- Move the isOffline/!isConnected early-return below the GSE→userdata
  migrate, inside the existing try block. Local prep should run even
  offline (one-shot file moves, not cloud-dependent). The cloud RPCs
  below the gate continue to short-circuit when offline.

PluviaMain SDK-cloud-bridge prompt
- Don't fire the prompt for non-game launches: bootToContainer (Open
  Container), useTemporaryOverride, or skipCloudSync flows. The post-
  prompt `relaunch` lambda doesn't carry those flags forward, so an
  Open-Container that triggered the prompt would silently turn into a
  game launch on dismiss. Easier to gate the prompt out than thread
  every flag through the relaunch.

SteamUtils verifyReplacedState / verifyRestoredState
- Treat a missing pipe-asset hash as a verification FAILURE rather than
  a silent skip. The previous `?: return@forEach` would skip the file
  and leave the function returning true — meaning a future build that
  drops a pipe DLL asset would silently let the marker check pass and
  suppress DLL repair. Fail closed instead.

LudusaviRegistry.ensureLoaded
- Wrap the fetch + disk-write + memoryCache-populate path in a
  kotlinx.coroutines Mutex with a double-check on entry. Without it,
  concurrent primeCache() + lookup() callers could both pass the null
  check and both fetch the 5 MB manifest. Now the second waiter sees
  the populated cache after the first finishes.

Findings flagged but not changed:
- SteamUtils SDK cloud mirror nested directories (third time): mirror
  is by design for Dead Cells flat saves; recursive expansion remains
  scope creep.
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.