QoL batch: sealed-key migration, friendly crypto errors, compare profiles, group launch, maFile import, friends tab, backup/restore, live title, keyboard shortcuts#6
Merged
Conversation
…arsing Introduces a real encryption-key story so that rebuilding SAM no longer breaks every user's info.dat: - New Core/EncryptionKey.cs: a per-user DPAPI-sealed key stored as base64 in samsettings.ini [System] SealedKey. Falls back to the legacy "PRIVATE_KEY" constant when no sealed key is set, so existing installs keep working unchanged. - AccountsWindow.eKey is now a property reading EncryptionKey.Get(), not a build-time constant. All existing encrypt/decrypt call sites continue to work, but switching to a sealed key only requires a single Settings action (migration UI lands in a later commit). - New Core/CryptoHelper.cs + StringCipher.DecryptOrThrow: distinguish "wrong key" / "not encrypted" / "corrupted" failures so call sites can show actionable text instead of the raw "Padding is invalid". - StringCipher.Decrypt: replaces the cryptic CryptographicException message box with a clear "key does not match — open Settings → Migrate Encryption Key" prompt. - Account.LastLoggedIn: optional DateTime, persisted in info.dat, consumed by the upcoming sort/filter UI. - AccountUtils.RotateBackups + BackupCount setting: every Serialize / PasswordSerialize call now writes info.dat.bak.yyyyMMdd-HHmmss and keeps the most recent N (default 5). Old info.dat.bak is migrated in place so nothing is lost. - AccountUtils.ValidateApiKeyAsync: round-trips through ISteamWebAPIUtil/GetSupportedAPIList so the Settings dialog can prove a pasted key is real before saving. - AccountUtils.FindDuplicates: surfaces same-username and same-SteamID groupings; consumed by the upcoming "Find Duplicates" menu. - New Core/MaFileImporter.cs: parses Steam Desktop Authenticator .maFile JSON for the upcoming bulk import flow. - SAM.csproj: adds System.Security reference (DPAPI ProtectedData) and registers the new files. No UI changes in this commit; just the plumbing every later feature relies on.
…aunch Adds five new Edit-menu actions plus one new Import option, each backed by a dedicated MahApps dialog: - Migrate Encryption Key (MigrateKeyWindow): "Test" against a sample account first, generate a random new key or supply one, optionally seal it via DPAPI. Re-encrypts every Password / SharedSecret in info.dat, skipping fields that don't decrypt with the old key instead of corrupting them. - Find Duplicates (FindDuplicatesWindow): groups accounts by same username and same SteamID; per-row Delete button so you can clear the "paris12345678910"-style accidental double-imports. - Find Game in Library (FindGameWindow): scans every account with a SteamID via GetOwnedGames, surfaces matches with playtime, shows per-account progress live as it runs. - Group Launch (GroupLaunchWindow): multi-select list with a filter, configurable inter-login delay (default 8s), kicks off Login() on each selected account in sequence so Steam has time to shut down between switches. - Test API Key (Edit menu): round-trips through ISteamWebAPIUtil/GetSupportedAPIList and reports success/failure with actionable error text — no more guessing whether a freshly pasted key is right. - Import → Steam Desktop Authenticator (.maFile) / Folder: parses shared_secret, account_name and Session.SteamID from one or many maFiles, then either fills in the SharedSecret on a matching account or adds a new entry. Encrypts with the current eKey. Account model gains an optional LastLoggedIn DateTime. Login() stamps it on every login and SerializeAccounts is invoked on the finally branch so it survives restarts. Sort menu gets a new "Last Logged In" option that orders most-recent first. No existing handler signatures changed; all new entry points are additive and dialogs decline gracefully when no API key is set or no SteamID is associated with an account.
- ProfileInfoWindow gains a Friends tab populated via ISteamUser/ GetFriendList plus a bulk GetPlayerSummaries enrichment pass to fill in persona name, avatar, online status, and friend-since date. The fetch joins the existing parallel Task.WhenAll in GetProfileInfoAsync so the new tab fills in alongside everything else, sharing the 15-minute cache. - AccountUtils.SerializeBackup / DeserializeBackup: portable ".sambackup" format. Decrypts every per-account password and shared_secret with the *current* eKey, repackages the XML, then re-encrypts the whole payload under a user-supplied passphrase (PKCS7-AES-256-CBC via StringCipher.Encrypt). Restoring on a different machine re-encrypts each field with that machine's eKey, so backups survive migrations between sealed-eKey installations. - File menu gains Backup → Create Encrypted Backup... and Restore from Backup..., both reusing the existing PasswordWindow for the passphrase. Restore offers Replace / Merge / Cancel; merge skips same-username duplicates so re-importing the same backup is idempotent.
AccountUtils.GetCurrentSteamUser reads loginusers.vdf and returns the
user marked MostRecent=1 (falling back to the highest Timestamp when
nothing is flagged most-recent). SetWindowTitle calls it when no
status banner is set, so the idle title becomes
"SAM — PersonaName (accountname)" instead of plain "SAM". Activity
banners ("Loading", "Working", etc.) still take priority, and any
error reading the vdf silently falls back to the plain title.
…Profiles
- FileSystemWatcher on loginusers.vdf: when Steam writes the file (on
login / logout / account switch), AccountsWindow re-reads MostRecent
and re-renders the title automatically. 500ms debounce stops Steam's
bursty save pattern from spamming title updates. Falls back silently
if the steam path isn't configured yet.
- Window-level keyboard shortcuts via InputBindings:
Ctrl+R Reload all accounts
Ctrl+D Find duplicates
Ctrl+F Find game in library
Ctrl+N New account
Ctrl+B Backup (create encrypted)
Ctrl+L Group launch
Bound via a tiny RelayCommand wrapper exposing ICommand properties
on the window; uses RelativeSource AncestorType=Window so the
existing DataContext isn't touched.
- Compare Profiles (Edit menu, CompareProfilesWindow): two
GetProfileInfoAsync calls (cache-shared with the right-click Profile
Info window), side-by-side level / hours / games / top-20-by-hours,
with delta indicators and an overlap count of shared AppIds. Empty
state if no SteamID on either side or no API key.
…in CompareProfilesWindow
5 tasks
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
Big QoL batch built in one branch for a single phone-merge.
Encryption painless suite
Password/SharedSecretwith a new key. Includes a "Test old key" button so you don't run a destructive migration with the wrong input. Can generate a random key or take a custom one; can optionally seal the new key via DPAPI (locked to your Windows user) so future rebuilds of SAM never breakinfo.datagain.Core/EncryptionKey.cs): ifsamsettings.inihas a[System] SealedKey, that's what every encrypt/decrypt uses. Otherwise falls back to the existing"PRIVATE_KEY"literal so old installs still work unchanged.StringCipher.Decryptnow replaces "Padding is invalid and cannot be removed" with "Decryption failed — the encryption key does not match… open Settings → Migrate Encryption Key". NewCryptoHelperdistinguishes wrong-key / not-encrypted / corrupted for callers that want to branch on it..sambackupfile (passphrase-encrypted, key-independent) on one machine, restore on another. Restore offers Replace / Merge.info.dat.bak.{timestamp}kept on every save (default 5, configurable via[Settings] BackupCount). Pre-existinginfo.dat.bakis preserved on first run.ISteamWebAPIUtil/GetSupportedAPIList, shows pass/fail with actionable text.Account management
paris12345678910double-entry problem.Login()s each.Account.LastLoggedInis stamped every login (and persisted via the existingSerializeAccountspath), sortable most-recent-first..maFiles. For each, fillsSharedSecreton the matchingNameaccount or creates a new one. Encrypts with the currenteKey.Profile Info window v2
ISteamUser/GetFriendList+ a batch summaries enrichment pass (persona name, avatar, online status, friend-since).GetProfileInfoAsynccalls (cache-shared with Profile Info), side-by-side level / hours / games / top-20-by-hours with delta indicators and library overlap count.UX polish
FileSystemWatcheronloginusers.vdfre-reads MostRecent whenever Steam writes the file, so the idle title (SAM — PersonaName (accountname)) updates within ms of a Steam account switch. 500ms debounce + silent fall back if no Steam path is set.InputBindings(bound through a tinyRelayCommandwrapper):Ctrl+RCtrl+DCtrl+FCtrl+NCtrl+BCtrl+LFiles
Core/EncryptionKey.csCore/CryptoHelper.csCore/MaFileImporter.cs.maFileparsingCore/RelayCommand.csViews/MigrateKeyWindow.{xaml,xaml.cs}Views/FindDuplicatesWindow.{xaml,xaml.cs}Views/FindGameWindow.{xaml,xaml.cs}Views/GroupLaunchWindow.{xaml,xaml.cs}Views/CompareProfilesWindow.{xaml,xaml.cs}Core/AccountUtils.csRotateBackups, +ValidateApiKeyAsync, +FindDuplicates, +GetFriendListAsync, +SerializeBackup/DeserializeBackup, +GetCurrentSteamUserCore/ProfileInfo.csFriendInfo+ friends slot in orchestratorCore/Account.csLastLoggedInCore/StringCipher.csDecryptOrThrow, friendly error inDecryptCore/SortType.csLastLoggedInCore/SAMSettings.cs,Core/UserSettings.csSealedKey, +BackupCountViews/ProfileInfoWindow.{xaml,xaml.cs}Views/AccountsWindow.{xaml,xaml.cs}SAM.csprojSystem.Securityref + new file entriesSkipped (high WPF runtime risk without a Windows build/test loop)
Test plan
paris12345678910group appears, Delete works, info.dat savesinfo.dat.bak.YYYYMMDD-HHMMSSfiles appear, oldest pruned beyondBackupCount(default 5)