From ce715686a138d7b93c6475e87895e0373c0a37b7 Mon Sep 17 00:00:00 2001 From: MR-1124 Date: Thu, 10 Sep 2026 11:09:39 +0530 Subject: [PATCH] Add one-time backup + Revert-to-vanilla-Steam feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before the first mode install writes to the Steam root, snapshot the pre-existing files (dwmapi.dll, xinput1_4.dll, opensteamtool.toml) into %AppData%\LuaToolsGui\backups with a SHA-256 manifest; the snapshot is never overwritten by later installs. A "Revert to vanilla Steam" card on the Mode page then undoes every mode artifact: closes Steam, restores backed-up files (hash-verified, corrupt copies are discarded rather than propagated), removes mode payloads (OpenSteamTool.dll, cloud_redirect.dll, opensteamtool.toml), optionally cleans game luas and pinned manifests, clears the selected mode, and restarts Steam. Installs made before backups existed (no snapshot available) revert by deleting the mode-placed proxy DLLs โ€” steam.exe falls back to the real system DLLs, so removal is the correct revert there. Revert is offered whenever mode artifacts are present, backup or not. ๐Ÿค– Generated with Codebuff Co-Authored-By: Codebuff --- src/LuaToolsGui/App.xaml.cs | 1 + src/LuaToolsGui/Resources/Strings.Designer.cs | 11 + src/LuaToolsGui/Resources/Strings.resx | 12 + src/LuaToolsGui/Services/BackupService.cs | 250 ++++++++++++++++++ src/LuaToolsGui/Services/UnlockerService.cs | 153 ++++++++++- src/LuaToolsGui/ViewModels/ModeViewModel.cs | 78 ++++++ src/LuaToolsGui/Views/ModeView.xaml | 111 ++++++++ 7 files changed, 613 insertions(+), 3 deletions(-) create mode 100644 src/LuaToolsGui/Services/BackupService.cs diff --git a/src/LuaToolsGui/App.xaml.cs b/src/LuaToolsGui/App.xaml.cs index f719494..4804525 100644 --- a/src/LuaToolsGui/App.xaml.cs +++ b/src/LuaToolsGui/App.xaml.cs @@ -45,6 +45,7 @@ public App() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // revert-to-vanilla-Steam feature (local) services.AddSingleton(); services.AddSingleton(); services.AddTransient(); // one per page (Home, Add) diff --git a/src/LuaToolsGui/Resources/Strings.Designer.cs b/src/LuaToolsGui/Resources/Strings.Designer.cs index 51974f0..b1823ef 100644 --- a/src/LuaToolsGui/Resources/Strings.Designer.cs +++ b/src/LuaToolsGui/Resources/Strings.Designer.cs @@ -165,6 +165,17 @@ public static class Strings public static string Mode_Btn_Update => Get(nameof(Mode_Btn_Update)); public static string Mode_Btn_Install => Get(nameof(Mode_Btn_Install)); public static string Mode_Btn_Switch => Get(nameof(Mode_Btn_Switch)); + public static string Mode_Revert_Title => Get(nameof(Mode_Revert_Title)); + public static string Mode_Revert_Desc => Get(nameof(Mode_Revert_Desc)); + public static string Mode_Revert_Button => Get(nameof(Mode_Revert_Button)); + public static string Mode_Revert_Confirm_Body => Get(nameof(Mode_Revert_Confirm_Body)); + public static string Mode_Revert_Opt_Lua => Get(nameof(Mode_Revert_Opt_Lua)); + public static string Mode_Revert_Opt_Manifests => Get(nameof(Mode_Revert_Opt_Manifests)); + public static string Mode_Revert_Toast_Done => Get(nameof(Mode_Revert_Toast_Done)); + public static string Mode_Revert_Toast_Done_NoStart => Get(nameof(Mode_Revert_Toast_Done_NoStart)); + public static string Mode_Revert_Failed_Body => Get(nameof(Mode_Revert_Failed_Body)); + public static string Mode_Backup_Summary => Get(nameof(Mode_Backup_Summary)); + public static string Mode_Backup_Summary_None => Get(nameof(Mode_Backup_Summary_None)); public static string Mode_Confirm_Reinstall => Get(nameof(Mode_Confirm_Reinstall)); public static string Mode_Confirm_Switch => Get(nameof(Mode_Confirm_Switch)); public static string Mode_Toast_Updated => Get(nameof(Mode_Toast_Updated)); diff --git a/src/LuaToolsGui/Resources/Strings.resx b/src/LuaToolsGui/Resources/Strings.resx index fe4c107..06c2a27 100644 --- a/src/LuaToolsGui/Resources/Strings.resx +++ b/src/LuaToolsGui/Resources/Strings.resx @@ -162,6 +162,18 @@ RECOMMENDED Steam will be closed so its files can be changed, then restarted automatically. Any running games or downloads will stop. Cancel + + Revert to vanilla Steam + Remove the unlocker and restore Steam to how it was before LuaTools touched it. A backup of the original files was taken before the first install. + Revert + Steam will be closed, the unlocker files removed, the original files restored, and Steam restarted. Your games and saves are not affected. + Also remove game lua files (removes added games) + Also remove pinned manifest files + Steam reverted to vanilla and restarted. + Steam reverted to vanilla. Start it manually when ready. + Revert failed. + Original files backed up + No backup โ€” the unlocker files will be removed instead of restored (safe: Steam uses its own system DLLs when these are absent) Close Steam & continue An open source fork of OpenSteamTools actively maintained by the LuaTools team. Introducing fixes and new features! diff --git a/src/LuaToolsGui/Services/BackupService.cs b/src/LuaToolsGui/Services/BackupService.cs new file mode 100644 index 0000000..ccaba77 --- /dev/null +++ b/src/LuaToolsGui/Services/BackupService.cs @@ -0,0 +1,250 @@ +using System.IO; +using System.Security.Cryptography; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace LuaToolsGui.Services; + +/// One backed-up file entry in the backup manifest. +public sealed record BackupEntry( + [property: JsonPropertyName("file")] string File, + [property: JsonPropertyName("sha256")] string Sha256); + +/// manifest.json describing a backup set. Written into the backup folder itself. +public sealed record BackupManifest( + [property: JsonPropertyName("createdAtUtc")] DateTimeOffset CreatedAtUtc, + [property: JsonPropertyName("entries")] List Entries); + +/// Summary of the current backup state for UI display. +public sealed record BackupInfo(bool HasBackup, string? CreatedText, int FileCount); + +/// +/// One-time safety backup of the Steam root before a managed mode ever writes to it, plus the +/// restore half of "revert to vanilla Steam". +/// +/// +/// Design notes: +/// โ€ข The backup captures the PRE-modification state of the loader DLLs + opensteamtool.toml, the +/// first time a mode install touches the Steam root. If those files were already a mode install +/// (not vanilla), restoring just returns the machine to that earlier state โ€” which is exactly +/// what a backup means. If they were vanilla (the common case), restore is a true revert. +/// โ€ข dwmapi.dll/xinput1_4.dll in the Steam root are NOT the Windows system DLLs โ€” they are proxy +/// DLLs a mode places there (or files that were already present before us). Nothing outside the +/// Steam folder is ever touched; "restore originals from System32" is deliberately NOT done +/// because System32 files are irrelevant to the Steam root. +/// โ€ข Hashes (sha256) are recorded at backup time so a restore can verify the copies are intact. +/// โ€ข The backup is created once and then left alone by later installs, so it always represents +/// the oldest state this app observed. +/// +/// +public class BackupService(SteamService steam) +{ + private static readonly JsonSerializerOptions JsonOpts = new() + { + PropertyNameCaseInsensitive = true, + }; + + /// Files worth backing up from the Steam root. Only ones WE (a mode install) would + /// overwrite or that describe our configuration. Never touches Steam's own files. + private static readonly string[] RootFilesToBackup = + [ + "dwmapi.dll", + "xinput1_4.dll", + "opensteamtool.toml", + ]; + + /// Mode payload file (removed on revert; recreated by a future install). + public const string OpenSteamToolDll = "OpenSteamTool.dll"; + + /// CloudRedirect add-on payload (removed on revert when present). + public const string CloudRedirectDll = "cloud_redirect.dll"; + + private static string Dir => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "LuaToolsGui", "backups"); + + private static string ManifestPath => Path.Combine(Dir, "manifest.json"); + + /// True when a backup set exists on disk (manifest present with entries). + public bool HasBackup + { + get + { + try + { + if (!File.Exists(ManifestPath)) return false; + var manifest = ReadManifest(); + return manifest is { Entries.Count: > 0 }; + } + catch { return false; } + } + } + + /// Backup summary for the Mode page UI (exists + when + how many files). + public BackupInfo GetInfo() + { + try + { + var manifest = ReadManifest(); + if (manifest is null || manifest.Entries.Count == 0) + return new BackupInfo(false, null, 0); + + // Local time, short format โ€” e.g. "9/6/2026 6:46 PM". + string when = manifest.CreatedAtUtc.LocalDateTime.ToString("g"); + return new BackupInfo(true, when, manifest.Entries.Count); + } + catch + { + return new BackupInfo(false, null, 0); + } + } + + /// + /// Back up the Steam-root files we would touch โ€” but ONLY once, and only what actually exists. + /// Called by before the first managed install. Later + /// installs keep the original backup so it always preserves the oldest observed state. + /// Returns true if a backup exists after this call (new or previous). + /// + public bool BackupIfNeeded() + { + if (HasBackup) return true; // one-time: never overwrite the original backup + + string? root = steam.EffectivePath; + if (root is null) return false; + + Directory.CreateDirectory(Dir); + + var entries = new List(); + try + { + foreach (string file in RootFilesToBackup) + { + string src = Path.Combine(root, file); + if (!File.Exists(src)) continue; + + string dst = Path.Combine(Dir, file); + File.Copy(src, dst, overwrite: true); + entries.Add(new BackupEntry(file, AssetHash.OfFile(dst))); + } + + if (entries.Count == 0) + { + // Nothing to back up (fresh Steam, no loader DLLs yet). Don't leave an empty backup + // dir masquerading as one: remove it so BackupIfNeeded can try again later if needed. + try { Directory.Delete(Dir, recursive: true); } catch { /* best effort */ } + return false; + } + + WriteManifest(new BackupManifest(DateTimeOffset.UtcNow, entries)); + return true; + } + catch + { + // Failed backup must NOT block install silently-as-success: report no backup. The caller + // decides whether to proceed (it does โ€” a backup is a nicety, not a hard requirement). + return false; + } + } + + /// + /// Restore the backed-up files into the Steam root (overwriting whatever a mode left there), + /// verify each restored copy's hash, and remove any restored file that fails verification. + /// Returns per-file failures; empty = full success. + /// + public List RestoreBackedUpFiles() + { + var failures = new List(); + + var manifest = TryReadManifestOrNull(); + if (manifest is null || manifest.Entries.Count == 0) + { + failures.Add("no backup manifest"); + return failures; + } + + string? root = steam.EffectivePath; + if (root is null) + { + failures.Add("steam not found"); + return failures; + } + + foreach (var entry in manifest.Entries) + { + try + { + string src = Path.Combine(Dir, entry.File); + if (!File.Exists(src)) + { + failures.Add(entry.File); + continue; + } + + // Verify the backup copy is intact BEFORE it lands in the Steam root. + if (!AssetHash.OfFile(src).Equals(entry.Sha256, StringComparison.OrdinalIgnoreCase)) + { + failures.Add(entry.File); // corrupted backup copy โ†’ don't propagate it + continue; + } + + string dst = Path.Combine(root, entry.File); + File.Copy(src, dst, overwrite: true); + + // Double-check what actually landed on disk. + if (!AssetHash.OfFile(dst).Equals(entry.Sha256, StringComparison.OrdinalIgnoreCase)) + { + try { File.Delete(dst); } catch { /* best effort */ } + failures.Add(entry.File); + } + } + catch + { + failures.Add(entry.File); // locked (Steam running?) or IO error + } + } + + return failures; + } + + /// The backed-up file names, or empty if no backup. For UI text ("restores 3 files"). + public IReadOnlyList BackedUpFiles() + { + try + { + return TryReadManifestOrNull()?.Entries.Select(e => e.File).ToList() ?? []; + } + catch { return []; } + } + + /// Delete the backup set entirely (opt-in "forget the backup" action). + public void DeleteBackup() + { + try { if (Directory.Exists(Dir)) Directory.Delete(Dir, recursive: true); } + catch { /* best effort */ } + } + + // โ”€โ”€ plumbing โ”€โ”€ + + private BackupManifest? TryReadManifestOrNull() + { + try { return ReadManifest(); } + catch { return null; } + } + + private BackupManifest? ReadManifest() + { + if (!File.Exists(ManifestPath)) return null; + string json = File.ReadAllText(ManifestPath); + return JsonSerializer.Deserialize(json, JsonOpts); + } + + private static void WriteManifest(BackupManifest manifest) + { + string json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + WriteIndented = true, + }); + File.WriteAllText(ManifestPath, json); + } +} diff --git a/src/LuaToolsGui/Services/UnlockerService.cs b/src/LuaToolsGui/Services/UnlockerService.cs index 18234ae..5341fcb 100644 --- a/src/LuaToolsGui/Services/UnlockerService.cs +++ b/src/LuaToolsGui/Services/UnlockerService.cs @@ -14,7 +14,7 @@ namespace LuaToolsGui.Services; /// files. Switching overwrites shared files but doesn't delete the previous mode's leftovers. The /// active mode persists in settings. /// -public class UnlockerService(SteamService steam, SettingsService settings, CacheService cache, GithubProxy gh) +public class UnlockerService(SteamService steam, SettingsService settings, CacheService cache, GithubProxy gh, BackupService backup) { private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true }; @@ -202,6 +202,148 @@ private static ModeStatus ManifestStatus(UpdateManifest manifest, string root) return (ModeStatus.UpdateAvailable, latest.TagName); } + // โ”€โ”€ Revert to vanilla โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + /// True when a revert has something to undo: any mode-managed artifact is present in + /// the Steam root. Works with OR without a backup โ€” without one (installs made before backups + /// existed), the mode-placed files are removed instead of restored. Cheap, disk-only. + public bool CanRevert => ModeArtifactsPresent().Count > 0; + + /// Human-readable backup summary for the Mode page. With a backup: when it was taken + + /// how many files. Without one: a note that the files will be removed rather than restored. + public string? BackupSummary + { + get + { + var info = backup.GetInfo(); + return info.HasBackup + ? $"{Resources.Strings.Mode_Backup_Summary} ยท {info.CreatedText} ยท {info.FileCount}" + : Resources.Strings.Mode_Backup_Summary_None; + } + } + + /// Mode-managed artifacts currently present in the Steam root: the loader DLLs, the + /// payload DLL, CloudRedirect and opensteamtool.toml. Used to decide if a revert has work to do. + private List ModeArtifactsPresent() + { + var present = new List(); + string? root = steam.EffectivePath; + if (root is null) return present; + + foreach (string f in new[] + { + BackupService.OpenSteamToolDll, + BackupService.CloudRedirectDll, + "dwmapi.dll", "xinput1_4.dll", "opensteamtool.toml", + }) + if (File.Exists(Path.Combine(root, f))) present.Add(f); + return present; + } + + /// + /// Revert Steam to its pre-LuaTools state: Steam must already be stopped (the caller owns the + /// close/relaunch choreography). Restores every backed-up file (verified), removes mode payload + /// DLLs and opensteamtool.toml (restored by the backup when it existed before us), clears the + /// selected mode, and optionally cleans game luas + pinned manifests. + /// + /// Also delete <Steam>/config/stplug-in/*.lua (game unlocks). + /// Also delete <Steam>/config/depotcache/*.manifest (pins). + public ModeInstallResult Revert(bool cleanLua = false, bool cleanManifests = false) + { + string? root = steam.EffectivePath; + if (root is null || !steam.IsValid) + return ModeInstallResult.Fail(Resources.Strings.Err_SteamNotFound); + + var failed = new List(); + + // 1. Restore backed-up files (loader DLLs + opensteamtool.toml as they were before us). + // No backup (e.g. the mode was installed before backups existed) โ†’ the loader DLLs were + // placed by the mode, so deleting them IS the revert: absent proxies, steam.exe loads the + // real system DLLs from System32. (opensteamtool.toml is handled by step 3 below.) + if (backup.HasBackup) + failed.AddRange(backup.RestoreBackedUpFiles()); + else + foreach (string f in new[] { "dwmapi.dll", "xinput1_4.dll" }) + { + try { string p = Path.Combine(root, f); if (File.Exists(p)) File.Delete(p); } + catch { failed.Add(f); } + } + + // 2. Remove mode payload files that have no backup to restore over them. + foreach (string f in new[] { BackupService.OpenSteamToolDll, BackupService.CloudRedirectDll }) + { + try + { + string p = Path.Combine(root, f); + // Only delete when it's NOT covered by the backup (the restore already overwrote it). + if (backup.BackedUpFiles().Any(b => b.Equals(f, StringComparison.OrdinalIgnoreCase))) continue; + if (File.Exists(p)) File.Delete(p); + } + catch { failed.Add(f); } + } + + // 3. opensteamtool.toml: if the backup has no copy, the file was created by us โ†’ delete it. + try + { + string toml = Path.Combine(root, "opensteamtool.toml"); + if (!backup.BackedUpFiles().Any(b => b.Equals("opensteamtool.toml", StringComparison.OrdinalIgnoreCase)) + && File.Exists(toml)) + File.Delete(toml); + } + catch { failed.Add("opensteamtool.toml"); } + + // 4. Optional: remove game luas (the user's unlocks). + if (cleanLua) + { + try + { + string? dir = steam.StPlugInDir; + if (dir is not null && Directory.Exists(dir)) + foreach (string f in Directory.GetFiles(dir, "*.lua")) + File.Delete(f); + } + catch { failed.Add("*.lua"); } + } + + // 5. Optional: remove pinned manifests. + if (cleanManifests) + { + try + { + string? dir = steam.DepotCacheDir; + if (dir is not null && Directory.Exists(dir)) + foreach (string f in Directory.GetFiles(dir, "*.manifest")) + File.Delete(f); + } + catch { failed.Add("*.manifest"); } + } + + // 6. Clear the active mode so the app reflects vanilla state (onboarding re-opens). + settings.SelectedMode = null; + cache.OpenSteamToolsInstalledVersion = null; + cache.OpenSteamToolsInstalledZipDigest = null; + + return failed.Count > 0 + ? new ModeInstallResult(false, + string.Format(Resources.Strings.Err_WriteFailedCount, failed.Count), failed) + : ModeInstallResult.Ok(); + } + + /// Delete stale lua files from stplug-in. Returns count removed (for UI), -1 on failure. + /// Used by the revert flow when the user opts into lua cleanup. + public int CleanLuaFiles() + { + try + { + string? dir = steam.StPlugInDir; + if (dir is null || !Directory.Exists(dir)) return 0; + int n = 0; + foreach (string f in Directory.GetFiles(dir, "*.lua")) { File.Delete(f); n++; } + return n; + } + catch { return -1; } + } + // โ”€โ”€ Install / switch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ /// Download + verify a mode's files, place them in the Steam root, remove the other mode's @@ -290,7 +432,12 @@ public async Task InstallAsync( return ModeInstallResult.Fail(string.Format(Resources.Strings.Err_VerifyFailedFile, manifest.File)); } - // 2. Copy verified files into the Steam root (overwrite). Locked files โ†’ Failed (Steam running). + // 2. SAFETY BACKUP (one-time): before the first managed install ever writes to the Steam + // root, snapshot the current loader DLLs + opensteamtool.toml so a later revert can + // restore this exact state. Never blocks install on failure. + try { backup.BackupIfNeeded(); } catch { /* best-effort safety net */ } + + // 3. Copy verified files into the Steam root (overwrite). Locked files โ†’ Failed (Steam running). var failed = new List(); foreach (string file in def.PlaceFiles) { @@ -306,7 +453,7 @@ public async Task InstallAsync( } } - // 3. This mode is now the active one. (No cleanup of other modes' files. Just overwrite.) + // 4. This mode is now the active one. (No cleanup of other modes' files. Just overwrite.) settings.SelectedMode = mode.ToString(); // Record the installed zip digest/version for reference (the up-to-date check uses per-DLL diff --git a/src/LuaToolsGui/ViewModels/ModeViewModel.cs b/src/LuaToolsGui/ViewModels/ModeViewModel.cs index e480668..7f95031 100644 --- a/src/LuaToolsGui/ViewModels/ModeViewModel.cs +++ b/src/LuaToolsGui/ViewModels/ModeViewModel.cs @@ -66,6 +66,21 @@ public partial class ModeViewModel : ObservableObject [ObservableProperty] private string _confirmTitle = ""; private ModeCardViewModel? _pendingCard; + // Revert-to-vanilla confirmation overlay state. + [ObservableProperty] private bool _isConfirmingRevert; + + /// "Also remove game luas" checkbox on the revert confirmation. + [ObservableProperty] private bool _revertCleanLua; + + /// "Also remove pinned manifests" checkbox on the revert confirmation. + [ObservableProperty] private bool _revertCleanManifests; + + /// True when a backup exists AND mode artifacts are present โ†’ the Revert card is offered. + [ObservableProperty] private bool _canRevert; + + /// Backup status line for the revert card, or null. + [ObservableProperty] private string? _backupSummary; + public ModeViewModel(UnlockerService unlocker, ToastService toast, SteamService steam, CloudRedirectService cloudRedirect) { @@ -289,6 +304,69 @@ public async Task LoadAsync(bool forceRefresh = false) // Bottom CloudRedirect add-on panel (locked unless Nightly BST is the active mode). await RefreshCloudRedirectAsync(forceRefresh); + + // Revert card: visible only when a backup exists AND there are mode artifacts to undo. + CanRevert = _unlocker.CanRevert; + BackupSummary = _unlocker.BackupSummary; + } + + // โ”€โ”€ Revert to vanilla Steam โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + /// "Revert to vanilla" button โ†’ show the confirmation overlay. + [RelayCommand] + private void StartRevert() + { + if (IsBusy || !_unlocker.CanRevert) return; + RevertCleanLua = false; + RevertCleanManifests = false; + IsConfirmingRevert = true; + } + + [RelayCommand] + private void CancelRevert() + { + IsConfirmingRevert = false; + } + + /// Confirmed revert: close Steam, restore/delete mode files, restart Steam, report. + [RelayCommand] + private async Task ConfirmRevert() + { + IsConfirmingRevert = false; + if (IsBusy) return; + IsBusy = true; + IsProgressIndeterminate = true; + Progress = 0; + try + { + // Files are locked while Steam runs; same choreography as install. + await Task.Run(_steam.StopSteam); + + var result = await Task.Run(() => + _unlocker.Revert(RevertCleanLua, RevertCleanManifests)); + + if (result.Success) + { + bool started = await Task.Run(_steam.StartSteam); + _toast.Show(Resources.Strings.Mode_Revert_Title, started + ? Resources.Strings.Mode_Revert_Toast_Done + : Resources.Strings.Mode_Revert_Toast_Done_NoStart); + } + else + { + // Revert (partially) failed: still bring Steam back up. + await Task.Run(_steam.StartSteam); + _toast.Show(Resources.Strings.Mode_Revert_Title, + result.Error ?? Resources.Strings.Mode_Revert_Failed_Body, error: true); + } + + await LoadAsync(); + } + finally + { + IsBusy = false; + IsProgressIndeterminate = false; + } } private DateTime _lastCheck; diff --git a/src/LuaToolsGui/Views/ModeView.xaml b/src/LuaToolsGui/Views/ModeView.xaml index cb5f118..e7f45c1 100644 --- a/src/LuaToolsGui/Views/ModeView.xaml +++ b/src/LuaToolsGui/Views/ModeView.xaml @@ -199,6 +199,61 @@ + + + + + + + + + + + + + + + + + + + + + @@ -298,6 +353,62 @@ + + + + + + + + + + + + + + + +