├── RemnantSaveGuardian
├── Assets
│ ├── 1024.ico
│ ├── 256.ico
│ ├── applicationIcon-256.png
│ └── applicationIcon-1024.png
├── Models
│ ├── DataColor.cs
│ └── AppConfig.cs
├── ViewModels
│ ├── BackupsViewModel.cs
│ ├── LogViewModel.cs
│ ├── SettingsViewModel.cs
│ ├── WorldAnalyzerViewModel.cs
│ └── MainWindowViewModel.cs
├── EventTransfer.cs
├── AssemblyInfo.cs
├── Helpers
│ ├── CalculateConverter.cs
│ ├── EnumToBooleanConverter.cs
│ └── WindowDwmHelper.cs
├── Views
│ ├── Pages
│ │ ├── LogPage.xaml
│ │ ├── LogPage.xaml.cs
│ │ ├── BackupsPage.xaml
│ │ ├── SettingsPage.xaml
│ │ └── WorldAnalyzerPage.xaml
│ ├── UserControls
│ │ ├── TextBlockPlus.xaml
│ │ └── TextBlockPlus.xaml.cs
│ └── Windows
│ │ └── MainWindow.xaml
├── Services
│ ├── PageService.cs
│ └── ApplicationHostService.cs
├── WindowsSave.cs
├── SaveWatcher.cs
├── Properties
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ └── Settings.settings
├── Logger.cs
├── app.manifest
├── RemnantSaveGuardian.csproj
├── LocalizationProvider.cs
├── UpdateCheck.cs
├── SaveBackup.cs
├── App.xaml.cs
├── App.config
├── RemnantItem.cs
├── App.xaml
├── RemnantSave.cs
├── RemnantCharacter.cs
├── GameInfo.cs
└── locales
│ └── Strings.ko.resx
├── Directory.Build.props
├── .github
└── workflows
│ └── dotnet-desktop.yml
├── RemnantSaveGuardian.sln
├── README.md
└── .gitignore
/RemnantSaveGuardian/Assets/1024.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Razzmatazzz/RemnantSaveGuardian/HEAD/RemnantSaveGuardian/Assets/1024.ico
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Assets/256.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Razzmatazzz/RemnantSaveGuardian/HEAD/RemnantSaveGuardian/Assets/256.ico
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Assets/applicationIcon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Razzmatazzz/RemnantSaveGuardian/HEAD/RemnantSaveGuardian/Assets/applicationIcon-256.png
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Assets/applicationIcon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Razzmatazzz/RemnantSaveGuardian/HEAD/RemnantSaveGuardian/Assets/applicationIcon-1024.png
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(MSBuildProjectDirectory)=$(MSBuildProjectName)
4 |
5 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Models/DataColor.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Media;
2 |
3 | namespace RemnantSaveGuardian.Models
4 | {
5 | public struct DataColor
6 | {
7 | public Brush Color { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Models/AppConfig.cs:
--------------------------------------------------------------------------------
1 | namespace RemnantSaveGuardian.Models
2 | {
3 | public class AppConfig
4 | {
5 | public string ConfigurationsFolder { get; set; }
6 |
7 | public string AppPropertiesFileName { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/ViewModels/BackupsViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | using Wpf.Ui.Common.Interfaces;
4 |
5 | namespace RemnantSaveGuardian.ViewModels
6 | {
7 | public partial class BackupsViewModel : ObservableObject, INavigationAware
8 | {
9 | public void OnNavigatedTo()
10 | {
11 | }
12 |
13 | public void OnNavigatedFrom()
14 | {
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/EventTransfer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RemnantSaveGuardian
4 | {
5 | internal class EventTransfer
6 | {
7 | internal static event EventHandler? Event;
8 | internal class MessageArgs : EventArgs
9 | {
10 | internal MessageArgs(object message)
11 | {
12 | _message = message;
13 | }
14 | internal object _message { get; set; }
15 |
16 | }
17 | internal static void Transfer(object s)
18 | {
19 | Event?.Invoke(null, new MessageArgs(s));
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/ViewModels/LogViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | using Wpf.Ui.Common.Interfaces;
4 |
5 | namespace RemnantSaveGuardian.ViewModels
6 | {
7 | public partial class LogViewModel : ObservableObject, INavigationAware
8 | {
9 | private bool _isInitialized = false;
10 |
11 | public void OnNavigatedTo()
12 | {
13 | if (!_isInitialized)
14 | InitializeViewModel();
15 | }
16 |
17 | public void OnNavigatedFrom()
18 | {
19 | }
20 |
21 | private void InitializeViewModel()
22 | {
23 | _isInitialized = true;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/ViewModels/SettingsViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | using Wpf.Ui.Common.Interfaces;
4 |
5 | namespace RemnantSaveGuardian.ViewModels
6 | {
7 | public partial class SettingsViewModel : ObservableObject, INavigationAware
8 | {
9 | private bool _isInitialized = false;
10 |
11 | public void OnNavigatedTo()
12 | {
13 | if (!_isInitialized)
14 | InitializeViewModel();
15 | }
16 |
17 | public void OnNavigatedFrom()
18 | {
19 | }
20 |
21 | private void InitializeViewModel()
22 | {
23 | _isInitialized = true;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/ViewModels/WorldAnalyzerViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | using Wpf.Ui.Common.Interfaces;
4 |
5 | namespace RemnantSaveGuardian.ViewModels
6 | {
7 | public partial class WorldAnalyzerViewModel : ObservableObject, INavigationAware
8 | {
9 | private bool _isInitialized = false;
10 |
11 | public void OnNavigatedTo()
12 | {
13 | if (!_isInitialized)
14 | InitializeViewModel();
15 | }
16 |
17 | public void OnNavigatedFrom()
18 | {
19 | }
20 |
21 | private void InitializeViewModel()
22 | {
23 | _isInitialized = true;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Helpers/CalculateConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Data;
3 |
4 | namespace RemnantSaveGuardian.Helpers
5 | {
6 | internal class CalculateConverter : IValueConverter
7 | {
8 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
9 | {
10 | var intX = Math.Round((double)value);
11 | var intY = Int32.Parse((string)parameter);
12 | return (intX + intY);
13 | }
14 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
15 | {
16 | throw new NotSupportedException();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Views/Pages/LogPage.xaml:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Views/UserControls/TextBlockPlus.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Helpers/EnumToBooleanConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace RemnantSaveGuardian.Helpers
6 | {
7 | internal class EnumToBooleanConverter : IValueConverter
8 | {
9 | public EnumToBooleanConverter()
10 | {
11 | }
12 |
13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
14 | {
15 | if (parameter is not String enumString)
16 | throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
17 |
18 | if (!Enum.IsDefined(typeof(Wpf.Ui.Appearance.ThemeType), value))
19 | throw new ArgumentException("ExceptionEnumToBooleanConverterValueMustBeAnEnum");
20 |
21 | var enumValue = Enum.Parse(typeof(Wpf.Ui.Appearance.ThemeType), enumString);
22 |
23 | return enumValue.Equals(value);
24 | }
25 |
26 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
27 | {
28 | if (parameter is not String enumString)
29 | throw new ArgumentException("ExceptionEnumToBooleanConverterParameterMustBeAnEnumName");
30 |
31 | return Enum.Parse(typeof(Wpf.Ui.Appearance.ThemeType), enumString);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-desktop.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core Desktop
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: windows-latest
13 |
14 | env:
15 | Project: RemnantSaveGuardian/RemnantSaveGuardian.csproj
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 |
23 | - name: Install .NET Core
24 | uses: actions/setup-dotnet@v4
25 | with:
26 | dotnet-version: 8.0.x
27 |
28 | - name: Setup MSBuild.exe
29 | uses: microsoft/setup-msbuild@v2
30 |
31 | # Just in case there will be some unit tests in future
32 | - name: Execute unit tests
33 | run: dotnet test
34 |
35 | - name: Restore the application
36 | run: msbuild $env:Project /t:Restore /p:Configuration=Release
37 |
38 | - uses: kzrnm/get-net-sdk-project-versions-action@v2
39 | id: get-version
40 | with:
41 | proj-path: ${{ env.Project }}
42 |
43 | - name: publish
44 | run: dotnet publish RemnantSaveGuardian/RemnantSaveGuardian.csproj -o "./publish"
45 |
46 | - name: Upload build artifacts
47 | uses: actions/upload-artifact@v4
48 | with:
49 | name: 'RemnantSaveGuardian_${{steps.get-version.outputs.assembly-version}}'
50 | path: ./publish
51 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Services/PageService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using Wpf.Ui.Mvvm.Contracts;
4 |
5 | namespace RemnantSaveGuardian.Services
6 | {
7 | ///
8 | /// Service that provides pages for navigation.
9 | ///
10 | public class PageService : IPageService
11 | {
12 | ///
13 | /// Service which provides the instances of pages.
14 | ///
15 | private readonly IServiceProvider _serviceProvider;
16 |
17 | ///
18 | /// Creates new instance and attaches the .
19 | ///
20 | public PageService(IServiceProvider serviceProvider)
21 | {
22 | _serviceProvider = serviceProvider;
23 | }
24 |
25 | ///
26 | public T? GetPage() where T : class
27 | {
28 | if (!typeof(FrameworkElement).IsAssignableFrom(typeof(T)))
29 | throw new InvalidOperationException("The page should be a WPF control.");
30 |
31 | return (T?)_serviceProvider.GetService(typeof(T));
32 | }
33 |
34 | ///
35 | public FrameworkElement? GetPage(Type pageType)
36 | {
37 | if (!typeof(FrameworkElement).IsAssignableFrom(pageType))
38 | throw new InvalidOperationException("The page should be a WPF control.");
39 |
40 | return _serviceProvider.GetService(pageType) as FrameworkElement;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/WindowsSave.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 |
5 | namespace RemnantSaveGuardian
6 | {
7 | class WindowsSave
8 | {
9 | public string Container { get; set; }
10 | public string Profile { get; set; }
11 | public List Worlds { get; set; }
12 | private bool isValid;
13 | public bool Valid { get { return isValid; } }
14 |
15 | public WindowsSave(string containerPath)
16 | {
17 | Worlds = new List();
18 | Container = containerPath;
19 | var folderPath = new FileInfo(containerPath).Directory.FullName;
20 | var offset = 136;
21 | byte[] byteBuffer = File.ReadAllBytes(Container);
22 | var profileBytes = new byte[16];
23 | Array.Copy(byteBuffer, offset, profileBytes, 0, 16);
24 | var profileGuid = new Guid(profileBytes);
25 | Profile = profileGuid.ToString().ToUpper().Replace("-", "");
26 | isValid = File.Exists($@"{folderPath}\{Profile}");
27 | offset += 160;
28 | while (offset + 16 < byteBuffer.Length)
29 | {
30 | var worldBytes = new byte[16];
31 | Array.Copy(byteBuffer, offset, worldBytes, 0, 16);
32 | var worldGuid = new Guid(worldBytes);
33 | Worlds.Add(folderPath + "\\" + worldGuid.ToString().ToUpper().Replace("-", ""));
34 | offset += 160;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.6.33815.320
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemnantSaveGuardian", "RemnantSaveGuardian\RemnantSaveGuardian.csproj", "{61D7EC72-AB05-43AE-943E-066C4F1F1520}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D6BEAB10-FA9E-421D-A7E6-ED1F5203B96F}"
9 | ProjectSection(SolutionItems) = preProject
10 | Directory.Build.props = Directory.Build.props
11 | .github\workflows\dotnet-desktop.yml = .github\workflows\dotnet-desktop.yml
12 | LICENSE = LICENSE
13 | README.md = README.md
14 | EndProjectSection
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {61D7EC72-AB05-43AE-943E-066C4F1F1520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {61D7EC72-AB05-43AE-943E-066C4F1F1520}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {61D7EC72-AB05-43AE-943E-066C4F1F1520}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {61D7EC72-AB05-43AE-943E-066C4F1F1520}.Release|Any CPU.Build.0 = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | GlobalSection(ExtensibilityGlobals) = postSolution
31 | SolutionGuid = {E8790D64-B056-44FA-898D-43526F95A868}
32 | EndGlobalSection
33 | EndGlobal
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Remnant Save Guardian
2 | Back up your Remnant 2 saves and view your world rolls.
3 |
4 | **This project is a work in progress. Lots of features are broken or partially implemented.**
5 |
6 | ## Installation
7 | 1. Download and install [Microsoft .NET 8.0](https://dotnet.microsoft.com/en-us/download) or greater
8 | 2. Download the [latest release](https://github.com/Razzmatazzz/RemnantSaveGuardian/releases/latest/)
9 | 3. Unzip the latest release to a folder of your choosing (probably not the same folder where you have the game installed)
10 | 4. Run RemnantSaveGuardian.exe
11 |
12 | ## Screenshots
13 | 
14 | 
15 | 
16 |
17 | ## Known Issues
18 | - [Some items are missing from the world analyzer](https://github.com/Razzmatazzz/RemnantSaveGuardian/issues/43)
19 | - [Many events and items do not have well-formatted names](https://github.com/Razzmatazzz/RemnantSaveGuardian/issues/45)
20 | - [Some events not displaying or are erroneously displaying](https://github.com/Razzmatazzz/RemnantSaveGuardian/issues/44)
21 | - [If you are using Norton Antivirus, it may cause weirdness with your game saves and RemnantSaveGuardian](https://github.com/Razzmatazzz/RemnantSaveGuardian/issues/70)
22 |
23 | Thanks for [crackedmind](https://github.com/crackedmind) for the inflation code to convert saves into partial plaintext.
24 |
25 | Thanks to [AuriCrystal](https://github.com/Auricrystal) for event/item list.
26 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Services/ApplicationHostService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Hosting;
2 | using System;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using Wpf.Ui.Mvvm.Contracts;
8 |
9 | namespace RemnantSaveGuardian.Services
10 | {
11 | ///
12 | /// Managed host of the application.
13 | ///
14 | public class ApplicationHostService : IHostedService
15 | {
16 | private readonly IServiceProvider _serviceProvider;
17 | private INavigationWindow _navigationWindow;
18 |
19 | public ApplicationHostService(IServiceProvider serviceProvider)
20 | {
21 | _serviceProvider = serviceProvider;
22 | }
23 |
24 | ///
25 | /// Triggered when the application host is ready to start the service.
26 | ///
27 | /// Indicates that the start process has been aborted.
28 | public async Task StartAsync(CancellationToken cancellationToken)
29 | {
30 | await HandleActivationAsync();
31 | }
32 |
33 | ///
34 | /// Triggered when the application host is performing a graceful shutdown.
35 | ///
36 | /// Indicates that the shutdown process should no longer be graceful.
37 | public async Task StopAsync(CancellationToken cancellationToken)
38 | {
39 | await Task.CompletedTask;
40 | }
41 |
42 | ///
43 | /// Creates main window during activation.
44 | ///
45 | private async Task HandleActivationAsync()
46 | {
47 | await Task.CompletedTask;
48 |
49 | if (!Application.Current.Windows.OfType().Any())
50 | {
51 | _navigationWindow = (_serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!;
52 | _navigationWindow!.ShowWindow();
53 |
54 | _navigationWindow.Navigate(typeof(Views.Pages.BackupsPage));
55 | }
56 |
57 | await Task.CompletedTask;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Views/Pages/LogPage.xaml.cs:
--------------------------------------------------------------------------------
1 | //using System.Drawing;
2 | using System;
3 |
4 | using Wpf.Ui.Common.Interfaces;
5 | using Wpf.Ui.Controls;
6 |
7 | namespace RemnantSaveGuardian.Views.Pages
8 | {
9 | ///
10 | /// Interaction logic for LogView.xaml
11 | ///
12 | public partial class LogPage : INavigableView
13 | {
14 | public ViewModels.LogViewModel ViewModel
15 | {
16 | get;
17 | }
18 |
19 | public LogPage(ViewModels.LogViewModel viewModel)
20 | {
21 | ViewModel = viewModel;
22 |
23 | InitializeComponent();
24 | Logger.MessageLogged += Logger_MessageLogged;
25 | foreach (var logMessage in Logger.Messages)
26 | {
27 | addMessage(logMessage.Message, logMessage.LogType);
28 | }
29 | }
30 |
31 | private void Logger_MessageLogged(object? sender, MessageLoggedEventArgs e)
32 | {
33 | addMessage(e.Message, e.LogType);
34 | }
35 |
36 | private void addMessage(string message, LogType logType)
37 | {
38 | Dispatcher.Invoke(delegate {
39 | var infoBar = new InfoBar()
40 | {
41 | Message = message,
42 | IsOpen = true,
43 | Title = DateTime.Now.ToString(),
44 | };
45 | if (logType == LogType.Error)
46 | {
47 | infoBar.Severity = InfoBarSeverity.Error;
48 | }
49 | if (logType == LogType.Warning)
50 | {
51 | infoBar.Severity = InfoBarSeverity.Warning;
52 | }
53 | if (logType == LogType.Success)
54 | {
55 | infoBar.Severity = InfoBarSeverity.Success;
56 | }
57 | infoBar.ContextMenu = new System.Windows.Controls.ContextMenu();
58 | var menuCopyMessage = new Wpf.Ui.Controls.MenuItem();
59 | menuCopyMessage.Header = Loc.T("Copy");
60 | menuCopyMessage.SymbolIcon = Wpf.Ui.Common.SymbolRegular.Copy24;
61 | menuCopyMessage.Click += (s, e) =>
62 | {
63 | System.Windows.Clipboard.SetDataObject(message);
64 | };
65 | infoBar.ContextMenu.Items.Add(menuCopyMessage);
66 | stackLogs.Children.Insert(0, infoBar);
67 | });
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/SaveWatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace RemnantSaveGuardian
5 | {
6 | internal static class SaveWatcher
7 | {
8 | public static event EventHandler SaveUpdated;
9 | private static FileSystemWatcher watcher = new ()
10 | {
11 | //NotifyFilter = NotifyFilters.LastWrite,
12 | Filter = "profile.sav",
13 | };
14 | private static System.Timers.Timer saveTimer = new()
15 | {
16 | Interval = 2000,
17 | AutoReset = false,
18 | };
19 |
20 | static SaveWatcher()
21 | {
22 | watcher.Changed += OnSaveFileChanged;
23 | watcher.Created += OnSaveFileChanged;
24 | watcher.Deleted += OnSaveFileChanged;
25 |
26 | saveTimer.Elapsed += SaveTimer_Elapsed;
27 |
28 | Properties.Settings.Default.PropertyChanged += Default_PropertyChanged;
29 | }
30 |
31 | private static void Default_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
32 | {
33 | if (e.PropertyName != "SaveFolder")
34 | {
35 | return;
36 | }
37 | Watch(Properties.Settings.Default.SaveFolder);
38 | }
39 |
40 | private static void SaveTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
41 | {
42 | SaveUpdated?.Invoke(sender, e);
43 | }
44 |
45 | private static void OnSaveFileChanged(object sender, FileSystemEventArgs e)
46 | {
47 | try
48 | {
49 | //When the save files are modified, they are modified
50 | //multiple times in relatively rapid succession.
51 | //This timer is refreshed each time the save is modified,
52 | //and a backup only occurs after the timer expires.
53 | saveTimer.Stop();
54 | saveTimer.Start();
55 | }
56 | catch (Exception ex)
57 | {
58 | Logger.Error($"{ex.GetType()} {Loc.T("setting save file timer")}: {ex.Message} ({ex.StackTrace})");
59 | }
60 | }
61 |
62 | public static void Watch(string path)
63 | {
64 | if (Directory.Exists(path))
65 | {
66 | if (watcher.Path != path)
67 | {
68 | watcher.Path = path;
69 | }
70 | watcher.EnableRaisingEvents = true;
71 | }
72 | else
73 | {
74 | watcher.EnableRaisingEvents = false;
75 | }
76 | }
77 |
78 | public static void Pause()
79 | {
80 | watcher.EnableRaisingEvents = false;
81 | }
82 | public static void Resume()
83 | {
84 | watcher.EnableRaisingEvents = true;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace RemnantSaveGuardian.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RemnantSaveGuardian.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/RemnantSaveGuardian/ViewModels/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using CommunityToolkit.Mvvm.ComponentModel;
2 |
3 | using System;
4 | using System.Collections.ObjectModel;
5 |
6 | using Wpf.Ui.Common;
7 | using Wpf.Ui.Controls;
8 | using Wpf.Ui.Controls.Interfaces;
9 | using Wpf.Ui.Mvvm.Contracts;
10 |
11 | namespace RemnantSaveGuardian.ViewModels
12 | {
13 | public partial class MainWindowViewModel : ObservableObject
14 | {
15 | private bool _isInitialized = false;
16 |
17 | [ObservableProperty]
18 | private string _applicationTitle = String.Empty;
19 |
20 | [ObservableProperty]
21 | private ObservableCollection _navigationItems = new();
22 |
23 | [ObservableProperty]
24 | private ObservableCollection _navigationFooter = new();
25 |
26 | [ObservableProperty]
27 | private ObservableCollection