This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Steam Achievement Manager X (SAM.X) is a fork of gibbed/SteamAchievementManager, upgraded to .NET 10 with x64 architecture. It allows users to unlock/lock Steam achievements and modify game statistics through direct Steam API integration.
Key Fork Features:
- Multi-language support for achievements
- Icon and game list caching
- Countdown timer for delayed achievement unlocking
- Advanced search/filter functionality
- Windows 11 theming with Mica effects
Build entire solution:
dotnet build SAM.sln -c Debug -p:Platform=x64
dotnet build SAM.sln -c Release -p:Platform=x64Build specific project:
dotnet build SAM.Picker/SAM.Picker.csproj -c Debug -p:Platform=x64
dotnet build SAM.Game/SAM.Game.csproj -c Debug -p:Platform=x64Run tests:
dotnet test SAM.Picker.Tests/SAM.Picker.Tests.csproj -p:Platform=x64
dotnet test SAM.Game.Tests/SAM.Game.Tests.csproj -p:Platform=x64Run single test:
dotnet test SAM.Picker.Tests/SAM.Picker.Tests.csproj --filter "FullyQualifiedName~TestName" -p:Platform=x64Output locations:
- Debug builds:
bin/directory - Release builds:
upload/directory
Note: Solution is x64-only. Always use -p:Platform=x64 when building.
The solution consists of 4 main projects + 2 test projects:
SAM.API - Steam API Wrapper Layer
- Provides managed C# wrappers around native Steam Client APIs
- Target framework:
net10.0-windows(Windows-only) - Loads
steamclient64.dlldynamically from Steam install directory - Security: Validates DLL signature against Valve Corporation's certificate
- Uses unsafe code for native interop with Steam interfaces
SAM.Picker - Game Selection Launcher (WinForms)
- Entry point: Displays list of owned Steam games
- Downloads and caches game list from
https://gib.me/sam/games.xml(30-minute cache) - Caches game logos in
appcache/directory - Launches
SAM.Game.exewith selected game's AppID as command-line argument - Output:
SAM.Picker.exeinbin/orupload/
SAM.Game - Achievement Manager (WinForms)
- Launched by SAM.Picker with AppID parameter
- Loads achievement schema from Steam's VDF cache:
Steam\appcache\stats\UserGameStatsSchema_{appId}.bin - Parses binary VDF (Valve Data Format) files using custom
KeyValue.csparser - Provides achievement unlock/lock, stat editing, countdown timers
- Caches achievement icons in
appcache/{gameId}/subdirectory - Output:
SAM.Game.exeinbin/orupload/
SAM.WinForms - Shared UI Library
- Common theming and styling components
ThemeHelper.cs: Sophisticated theme engine that applies light/dark themes based on Windows preferences- Windows 11 integration: Mica effects, rounded corners via DWM APIs
- Uses handler registration pattern for different control types
- Custom painting for ListView, TabControl, DataGridView
SAM.Picker.exe ──┬──> SAM.API (Steam wrapper)
└──> SAM.WinForms (theming)
│
└──> launches SAM.Game.exe
│
├──> SAM.API
└──> SAM.WinForms
Initialization:
- Loads
steamclient64.dllfrom registry-discovered Steam install path (seeSAM.API\Steam.cs) - Verifies DLL digital signature matches Valve Corporation
- Creates Steam pipe and connects via
SteamClient018interface - Sets
SteamAppIdenvironment variable to trick Steam into game context - For Picker: AppID = 0 (general Steam client)
- For Game: AppID = specific game passed via command-line
Key Steam Interfaces:
SteamClient018: Client initializationSteamUserStats013: Achievement get/set operations, stat modificationsSteamApps008: Game ownership checks, language queriesSteamUtils010: General utilities
Native Wrapper Pattern:
- Base class:
NativeWrapper<T>uses generics to wrap Steam interfaces - Function pointer caching via Dictionary for performance
- Uses
Marshal.GetDelegateForFunctionPointerfor P/Invoke delegates - Interface definitions in
Interfaces/, implementations inWrappers/
Callback Pattern:
- Steam callbacks implemented via
Callback<T>base class Client.RunCallbacks()called on timer to poll Steam events- Observer pattern: OnRun event handlers notify forms
- Important callbacks:
UserStatsReceived,AppDataChanged
-
Schema Loading (
SAM.Game\Manager.cs):- Load from
Steam\appcache\stats\UserGameStatsSchema_{appId}.bin - Parse with
KeyValue.csVDF binary reader - Schema contains: achievement definitions, stat definitions, localized strings
- Load from
-
Data Retrieval:
- Call
SteamUserStats013.RequestUserStats()callback - Receive
UserStatsReceivedcallback with user's current progress - Populate
_AchievementListViewwith AchievementInfo objects
- Call
-
Modification:
- In-memory changes via
SetAchievement(id, unlocked)orSetStatValue(id, value) - Changes not persisted until
StoreStats()called - Unlock times stored as Unix timestamps
- In-memory changes via
-
Commit:
- User clicks "Commit" or timer triggers auto-commit
- Call
SteamUserStats013.StoreStats() - Steam syncs to cloud and updates user profile
Implementation (SAM.Game\Manager.cs):
- Language obtained from
SteamApps008.GetCurrentGameLanguage() - Localization fallback chain in
GetLocalizedString():- Try requested language from VDF schema
- Fall back to English if not found
- Fall back to raw value if English missing
- User can select language via
_LanguageComboBoxdropdown - Applies to achievement text and game logo URLs
Supported in:
- Achievement names/descriptions
- Game title display
- Logo/capsule image URLs (uses
small_capsule/{language}path)
Picker Cache:
- Game list:
games.xmlrefreshed if > 30 minutes old - User games:
usergames.xmlcontains owned games from last session - Game logos:
appcache/{appId}.pngcached permanently - Concurrent download queue pattern (
ConcurrentQueue<GameInfo>) - Image validation: max 4MB, max 1024px dimension, proper content-type
- Fallback URL chain for logos (tries multiple CDN paths)
Game Cache:
- Achievement icons:
appcache/{gameId}/{achievementId}_{achieved|locked}.png - Image validation: max 512KB, max 1024px dimension
- Filename regex validation prevents path traversal attacks
Location: SAM.Game\Manager.cs:1275-1468
Components:
_submitAchievementsTimer: Main timer (1-second tick)_idleTimer: Prevents system sleep during countdown_achievementCounters: Dictionary<string, int> tracking countdown per achievement
Workflow:
- User selects achievements in ListView
- Sets countdown value in
_AddTimerTextBox(-1 to 999999 seconds) - Clicks
_AddTimerButtonto assign countdown _EnableTimerButtonstarts timer execution- Timer decrements counters each second, auto-unlocks when reaching 0
- Uses
SetThreadExecutionState()to prevent system idle - Optional auto-mouse-movement to prevent Steam "away" status
Core Architecture:
SAM.API\Client.cs- Steam client lifecycle managementSAM.API\Steam.cs- Native DLL loading with security validationSAM.API\NativeWrapper.cs- Base class for Steam API wrappersSAM.Game\KeyValue.cs- VDF binary parser (critical for schema reading)
Main Forms:
SAM.Picker\GamePicker.cs- Game selection UI (1200+ lines)SAM.Game\Manager.cs- Achievement manager UI (1500+ lines)
Data Models:
SAM.Picker\GameInfo.cs- Game metadataSAM.Game\Stats\AchievementInfo.cs- Runtime achievement stateSAM.Game\Stats\AchievementDefinition.cs- Schema definitionSAM.Game\Stats\StatDefinition.cs- Base stat schema (Int/Float subclasses)
Theme System:
SAM.WinForms\ThemeHelper.cs- Windows 11-aware theme engine
Security/Validation:
SAM.Picker\ImageUrlValidator.cs- URL sanitizationSAM.API\Steam.cs:LoadSteamClient()- DLL signature verification
SAM.Game parses binary VDF (Valve Data Format) files, NOT text VDF. The parser is in SAM.Game\KeyValue.cs:
- Binary format uses type markers (0x00=childObject, 0x01=string, 0x02=int32, etc.)
- Nested structure represented as tree of KeyValue objects
- Achievement schema structure:
AppID/{lang}/stats/{achievements,stats}
-
DLL Signature Validation (
SAM.API\Steam.cs):- Verifies steamclient64.dll is signed by "Valve Corporation"
- Checks certificate subject name matches exactly
- Prevents DLL hijacking attacks
-
Path Validation:
- Regex validation for cache filenames prevents path traversal
- URL validation in ImageUrlValidator prevents SSRF
-
Content Validation:
- Image size limits (4MB for logos, 512KB for achievement icons)
- Dimension limits (max 1024px for both)
- Content-Type header validation
SAM.WinForms\ThemeHelper.cs provides sophisticated theming:
- Detects Windows theme via registry:
HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize - Applies colors recursively to all controls
- Custom painting handlers for: ListView headers, TabControl tabs, DataGridView
- Windows 11 features: DWM Mica effect (
DwmSetWindowAttribute), rounded corners - Uses
ConditionalWeakTableto track color state without memory leaks
SAM.API uses unsafe code blocks for:
- Function pointer marshaling from native Steam interfaces
- Fast string conversion (UTF-8 ↔ UTF-16)
- Direct memory access for VTable traversal
Important: Always enable <AllowUnsafeBlocks>true</AllowUnsafeBlocks> in project files when working with SAM.API.
-
Making UI Changes:
- Forms use Windows Forms Designer (.Designer.cs files)
- Apply theme via
ThemeHelper.ApplyTheme(this)in form constructor - Use
DoubleBufferedListViewfor flicker-free lists
-
Adding Steam API Features:
- Add interface definition in
SAM.API\Interfaces\ - Create wrapper in
SAM.API\Wrappers\ - Inherit from
NativeWrapper<TInterface> - Use
Call<TDelegate>(functionIndex, args)pattern
- Add interface definition in
-
Working with VDF Files:
- Use
KeyValue.LoadAsBinary()for binary VDF - Navigate tree structure:
root.Childrencontains list of KeyValue nodes - Check
root.Validbefore accessing data
- Use
-
Testing:
- Tests use xUnit framework
- Mock Steam API interactions where possible
- Test projects have InternalsVisibleTo access for unit testing
-
Debugging Steam Integration:
- Enable debug logging in
SAM.API\Client.cs - Check Steam logs:
Steam\logs\directory - Verify schema files exist:
Steam\appcache\stats\UserGameStatsSchema_{appId}.bin
- Enable debug logging in
-
Platform Target: Always build with x64. Project does not support AnyCPU or x86.
-
Steam Must Be Running: Both SAM.Picker and SAM.Game require Steam to be running and logged in.
-
AppID Context: SAM.Game MUST be launched with AppID parameter. Don't run directly—use SAM.Picker.
-
VDF Binary vs Text: Steam's VDF files come in two formats. This codebase uses binary format for schemas.
-
Achievement Permissions: Some achievements are server-authoritative and cannot be unlocked via SAM.
-
Callback Timing: Steam callbacks are async. Always check
IsValidon callback data before use. -
Path Separators: Use
Path.Combine()for cross-platform compatibility, even though this is Windows-only (future-proofing).
✅ Completed Improvements:
-
x64 Architecture Enforcement (2025-12-31)
- Removed runtime architecture detection in
SAM.API\Steam.cs - Hardcoded to only load
steamclient64.dll(removed x86 fallback) - Hardcoded pointer format to X16 in
SAM.API\NativeWrapper.cs - Ensures pure x64 design from API to UI layer
- Removed runtime architecture detection in
-
Steamworks.NET Dependency Removal (2025-12-31)
- Removed cross-platform target framework from SAM.API (Windows-only now)
- Removed Steamworks.NET package dependency
- Eliminated all
#if WINDOWS / #elseconditional compilation - Deleted ~86 lines of cross-platform compatibility code
- Reduced binary size by 314 KB (Steamworks.NET.dll)
-
Test Project Dependencies Cleanup (2025-12-31)
- Removed 20+ unnecessary package references from SAM.Picker.Tests:
- Linux runtime packages (debian, fedora, opensuse)
- OpenSSL packages (not needed on Windows)
- Outdated packages (System.Net.Http 4.3.4, System.Text.RegularExpressions 4.3.1)
- Unused packages (Newtonsoft.Json, Microsoft.NETCore.Platforms)
- Changed target framework to Windows-only (
net10.0-windows) - Reduced package count from 26 to 4 essential packages only
- Removed 20+ unnecessary package references from SAM.Picker.Tests:
-
Exception Handling Improvements (2025-12-31)
- Added exception logging to empty catch blocks in:
SAM.Picker\GamePicker.cs:682-686(image validation failures)SAM.Game\Manager.cs:911-915(image validation failures)
- Now uses
DebugLogger.Log()for proper error tracking - Helps debugging by recording why cache files are deleted
- Added exception logging to empty catch blocks in:
-
Async/Sync Deadlock Prevention (2025-12-31)
- Fixed deadlock risk in
SAM.Picker\GameList.cs:42-57 - Fixed deadlock risk in
SAM.Picker\GamePicker.cs:726-729 - Wrapped async operations with
Task.Run()to prevent thread pool starvation - Added
ConfigureAwait(false)to avoid context capture - Eliminates UI freezing risk when downloading game lists and logos
- Fixed deadlock risk in
-
Memory Leak Prevention (2025-12-31)
- Added event handler cleanup in
SAM.Picker\GamePicker.Designer.cs:36-40 - Unsubscribes
_AppDataChangedCallback.OnRunin Dispose() method - Ensures proper form garbage collection
- Prevents memory accumulation when opening/closing multiple game pickers
- Added event handler cleanup in
-
Magic Numbers Extraction (2025-12-31)
- Extracted magic numbers to named constants in 3 files:
SAM.Picker\GameList.cs: CacheExpirationMinutes (30), StreamReadBufferSize (81920)SAM.Picker\GamePicker.cs: HttpClientTimeoutSeconds (30)SAM.Game\Manager.cs: HttpClientTimeoutSeconds (30), MaxTimerTextLength (6), MouseMoveDistance (15), MouseMoveDelayMs (12)
- Improves code readability and maintainability
- Makes configuration values easier to find and modify
- Extracted magic numbers to named constants in 3 files:
🔴 Known Remaining Issues (Requires Fixing):
-
God Classes (MEDIUM PRIORITY)
SAM.Game\Manager.cs(1749 lines) - handles UI, network, Steam API, file I/O, timersSAM.Picker\GamePicker.cs(1234 lines) - similar multiple responsibilities- Violates Single Responsibility Principle
- Should be refactored into separate layers (UI, business logic, data access, network)
-
HttpClient Instance Management (MEDIUM PRIORITY)
- Each form creates its own HttpClient instance
- Should use IHttpClientFactory or singleton pattern
- Current approach can lead to socket exhaustion
🟡 Recommended Next Steps:
When resuming work on this codebase, prioritize in this order:
- Consider refactoring God classes (long-term maintainability)
- Implement IHttpClientFactory for HTTP client management
📊 Metrics After All Improvements:
- Code reduced: ~86 lines of conditional compilation removed
- Binary size reduced: 314 KB (Steamworks.NET.dll removed)
- Package dependencies reduced: 22 packages removed from test project
- Build warnings: 0 (previously had 2 MSB3245/MSB3243 warnings)
- Test pass rate: 100% (27/27 tests passing)
- Critical issues fixed: 2/2 high-priority issues resolved (deadlock risk + memory leaks)
- Exception handling: 4 empty catch blocks now properly log errors
- Magic numbers extracted: 7 constants created across 3 files
- Code quality improvements: 7 major improvements completed