fix(steam): restore achievement loading for real-world games#22
Open
MotherSphere wants to merge 1 commit into
Open
fix(steam): restore achievement loading for real-world games#22MotherSphere wants to merge 1 commit into
MotherSphere wants to merge 1 commit into
Conversation
Three independent bugs that combine to make the tool return empty achievement lists for most non-trivial games. All happen to be hidden by happy-path luck on small/simple games, which is why upstream tests pass. 1. CallbackHandle leak in start_client. client.register_callback(...) returns a CallbackHandle whose Drop impl synchronously unregisters the callback (see steamworks-rs 0.12.2/src/callback.rs:143). Upstream discards the return value, so the UserStatsReceived handler is removed before Steam can ever fire it. The 1-second wait loop then always times out, and load_achievements proceeds against unready stats. This is fixed by binding the handle to a named local for the duration of the wait loop. Also reorder request_user_stats() to fire AFTER the callback is registered, otherwise a fast Steam pipe can race and we'd miss the event even with the handle held. 2. break; after first ACHIEVEMENTS block in load_achievement_icons. Steam represents achievements as 32-bit bitfields. Games with more than 32 achievements (Elden Ring: 42, Skyrim: 75, etc.) have multiple ACHIEVEMENTS entries in the schema. The current loop reads the first block, then `break;`s, silently dropping every icon past index 31. Removing the break iterates them all. 3. .unwrap() panics in load_achievements. Three call sites unwrap Result<&str, ()> from get_achievement_display_attribute and Result<bool, ()> from get(). Either fails for any achievement whose status hasn't loaded yet (which, before fix jsnli#1, was every achievement). The panic is caught by catch_unwind and converted to Err, which main.rs maps to Vec::new(). So a single transient hiccup gives the UI "0/0" with no error surfacing. Replace with unwrap_or_default / unwrap_or(false), and short-circuit when get_num_achievements() returns 0 (steamworks-rs panics inside get_achievement_names for zero-achievement games). Tested locally against Elden Ring (42), Counter-Strike 2 (1), and a game with no achievements - all now load correctly. Before the fix, Elden Ring loaded 0/42 with no error; with only fix 1, 21/42 (half the icons missing); with all three, 42/42 correctly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three independent bugs in
src-tauri/src/steam.rsthat combine to make Samira return empty achievement lists for most non-trivial games. All happen to be hidden by happy-path luck on small/simple games, which is why upstream testing didn't catch them.I hit these while building a downstream fork (Project-Colony/SAM-Colony-Edition) — sending the bug-fix portion upstream because it benefits everyone using Samira on AAA titles.
The bugs
1.
CallbackHandleleak instart_clientregister_callbackreturns aCallbackHandlewhoseDropimpl synchronously unregisters the callback (steamworks 0.12.2/src/callback.rs#L143). Discarding the return value drops the handle right away, removing the callback before Steam can ever fire it. The 1-second wait loop then always times out, andload_achievementsproceeds against unready stats.Fix: bind the handle to a named local for the duration of the wait loop (
let _stats_cb = ...). Also reorderrequest_user_stats()to fire after the callback is registered (otherwise a fast pipe can race and we miss the event even with the handle held).2.
break;after first ACHIEVEMENTS block inload_achievement_iconsSteam represents achievements as 32-bit bitfields. Games with more than 32 achievements (Elden Ring: 42, Skyrim: 75, RDR2: 50+, etc.) have multiple ACHIEVEMENTS entries in the schema. The current loop reads the first block, then
break;s, silently dropping every icon past index 31.Fix: remove the
break;, iterate all ACHIEVEMENTS blocks.3.
.unwrap()panics inload_achievementsThree call sites unwrap
Result<&str, ()>fromget_achievement_display_attributeandResult<bool, ()>fromget(). Either fails for any achievement whose status hasn't loaded yet (which, before fix #1, was every achievement on every game). The panic is caught bycatch_unwindand converted toErr, whichmain.rsmaps toVec::new(). So a single transient hiccup gives the UI "0/0" with no error surfacing.Also:
user_stats.get_achievement_names()panics inside steamworks-rs when the game has zero achievements (it calls.expect()onget_num_achievements). This crashes Samira when the user picks a no-achievement game.Fix: replace
.unwrap()with.unwrap_or_default()/.unwrap_or(false), and short-circuitload_achievementswhenget_num_achievements()returns 0.Test plan
cargo check+ fulltauri build)panic::catch_unwindboundary preserved, so any unexpected residual panic still surfaces asErr(String)instead of crashing the process