• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ParadoxGameConverters / Fronter.NET / 25877866674

14 May 2026 06:27PM UTC coverage: 30.933% (-0.2%) from 31.099%
25877866674

push

github

web-flow
Bump dependencies (#982)

* Bump dependencies

* Use OrderedDictionary from standard library

* Update ModCopierTests.cs

220 of 814 branches covered (27.03%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

67 existing lines in 2 files now uncovered.

910 of 2839 relevant lines covered (32.05%)

5.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.24
/Fronter.NET/Models/Configuration/Config.cs
1
using Avalonia.Controls.ApplicationLifetimes;
2
using commonItems;
3
using Fronter.Models.Configuration.Options;
4
using Fronter.Models.Database;
5
using Fronter.Services;
6
using Fronter.ViewModels;
7
using log4net;
8
using Microsoft.EntityFrameworkCore;
9
using System;
10
using System.Collections.Generic;
11
using System.Collections.ObjectModel;
12
using System.Globalization;
13
using System.IO;
14
using System.Linq;
15

16
namespace Fronter.Models.Configuration;
17

18
internal sealed class Config {
19
        public string Name { get; private set; } = string.Empty;
20
        public string ConverterFolder { get; private set; } = string.Empty;
21
        public string BackendExePath { get; private set; } = string.Empty; // relative to ConverterFolder
22
        public string DisplayName { get; private set; } = string.Empty;
23
        public string SourceGame { get; private set; } = string.Empty;
24
        public string TargetGame { get; private set; } = string.Empty;
25
        public string? SentryDsn { get; private set; }
26
        public bool TargetPlaysetSelectionEnabled { get; private set; } = false;
27
        public ObservableCollection<Playset> AutoLocatedPlaysets { get; } = [];
28
        public Playset? SelectedPlayset { get; set; }
29
        public bool CopyToTargetGameModDirectory { get; set; } = true;
30
        public ushort ProgressOnCopyingComplete { get; set; } = 109;
31
        public bool UpdateCheckerEnabled { get; private set; } = false;
32
        public bool CheckForUpdatesOnStartup { get; private set; } = false;
33
        public string ConverterReleaseForumThread { get; private set; } = string.Empty;
34
        public string LatestGitHubConverterReleaseUrl { get; private set; } = string.Empty;
35
        public string PagesCommitIdUrl { get; private set; } = string.Empty;
36
        public List<RequiredFile> RequiredFiles { get; } = [];
37
        public List<RequiredFolder> RequiredFolders { get; } = [];
38
        public List<Option> Options { get; } = [];
39
        private int optionCounter;
40
        private readonly string baseDirectory;
41
        private string? deferredSelectedPlaysetId;
42
        private bool autoLocateOnTargetPathChangeEnabled;
43

44
        private static readonly ILog logger = LogManager.GetLogger("Configuration");
1✔
45

46
        public Config(): this(AppContext.BaseDirectory) {
24✔
47
        }
12✔
48

49
        internal Config(string baseDirectory) {
24✔
50
                this.baseDirectory = baseDirectory;
12✔
51
                var parser = new Parser();
12✔
52
                RegisterKeys(parser);
12✔
53
                var fronterConfigurationPath = Path.Combine(baseDirectory, "Configuration/fronter-configuration.txt");
12✔
54
                if (File.Exists(fronterConfigurationPath)) {
24!
55
                        parser.ParseFile(fronterConfigurationPath);
12✔
56
                        logger.Info("Frontend configuration loaded.");
12✔
57
                } else {
12✔
UNCOV
58
                        logger.Warn($"{fronterConfigurationPath} not found!");
×
59
                }
×
60

61
                var fronterOptionsPath = Path.Combine(baseDirectory, "Configuration/fronter-options.txt");
12✔
62
                if (File.Exists(fronterOptionsPath)) {
24!
63
                        parser.ParseFile(fronterOptionsPath);
12✔
64
                        logger.Info("Frontend options loaded.");
12✔
65
                } else {
12✔
UNCOV
66
                        logger.Warn($"{fronterOptionsPath} not found!");
×
UNCOV
67
                }
×
68

69
                InitializePaths();
12✔
70

71
                LoadExistingConfiguration();
12✔
72
        }
12✔
73

74
        private void RegisterKeys(Parser parser) {
12✔
75
                parser.RegisterKeyword("name", reader => Name = reader.GetString());
12✔
76
                parser.RegisterKeyword("sentryDsn", reader => SentryDsn = reader.GetString());
12✔
77
                parser.RegisterKeyword("converterFolder", reader => {
12✔
78
                        ConverterFolder = Path.Combine(baseDirectory, reader.GetString());
12✔
79
                });
12✔
80
                parser.RegisterKeyword("backendExePath", reader => BackendExePath = reader.GetString());
12✔
81
                parser.RegisterKeyword("requiredFolder", reader => {
12✔
82
                        var newFolder = new RequiredFolder(reader, this);
12✔
83
                        if (!string.IsNullOrEmpty(newFolder.Name)) {
12✔
84
                                RequiredFolders.Add(newFolder);
12✔
85
                        } else {
12✔
86
                                logger.Error("Required Folder has no mandatory field: name!");
12✔
87
                        }
12✔
88
                });
12✔
89
                parser.RegisterKeyword("requiredFile", reader => {
12✔
90
                        var newFile = new RequiredFile(reader);
12✔
91
                        if (!string.IsNullOrEmpty(newFile.Name)) {
12✔
92
                                RequiredFiles.Add(newFile);
12✔
93
                        } else {
12✔
94
                                logger.Error("Required File has no mandatory field: name!");
12✔
95
                        }
12✔
96
                });
12✔
97
                parser.RegisterKeyword("option", reader => {
12✔
98
                        var newOption = new Option(reader, ++optionCounter);
12✔
99
                        Options.Add(newOption);
12✔
100
                });
12✔
101
                parser.RegisterKeyword("displayName", reader => DisplayName = reader.GetString());
12✔
102
                parser.RegisterKeyword("sourceGame", reader => SourceGame = reader.GetString());
12✔
103
                parser.RegisterKeyword("targetGame", reader => TargetGame = reader.GetString());
12✔
104
                parser.RegisterKeyword("targetPlaysetSelectionEnabled", reader => {
12✔
105
                        TargetPlaysetSelectionEnabled = reader.GetString().Equals("true", StringComparison.OrdinalIgnoreCase);
12✔
106
                });
12✔
107
                parser.RegisterKeyword("copyToTargetGameModDirectory", reader => {
12✔
108
                        CopyToTargetGameModDirectory = reader.GetString().Equals("true", StringComparison.OrdinalIgnoreCase);
12✔
109
                });
12✔
110
                parser.RegisterKeyword("progressOnCopyingComplete", reader => {
12✔
111
                        ProgressOnCopyingComplete = (ushort)reader.GetInt();
12✔
112
                });
12✔
113
                parser.RegisterKeyword("enableUpdateChecker", reader => {
12✔
114
                        UpdateCheckerEnabled = reader.GetString().Equals("true", StringComparison.OrdinalIgnoreCase);
12✔
115
                });
12✔
116
                parser.RegisterKeyword("checkForUpdatesOnStartup", reader => {
12✔
117
                        CheckForUpdatesOnStartup = reader.GetString().Equals("true", StringComparison.OrdinalIgnoreCase);
12✔
118
                });
12✔
119
                parser.RegisterKeyword("converterReleaseForumThread", reader => {
12✔
120
                        ConverterReleaseForumThread = reader.GetString();
12✔
121
                });
12✔
122
                parser.RegisterKeyword("latestGitHubConverterReleaseUrl", reader => {
12✔
123
                        LatestGitHubConverterReleaseUrl = reader.GetString();
12✔
124
                });
12✔
125
                parser.RegisterKeyword("pagesCommitIdUrl", reader => PagesCommitIdUrl = reader.GetString());
12✔
126
                parser.IgnoreAndLogUnregisteredItems();
12✔
127
        }
12✔
128

129
        private void RegisterPreloadKeys(Parser parser) {
14✔
130
                parser.RegisterRegex(CommonRegexes.String, (reader, incomingKey) => {
14✔
131
                        StringOfItem valueStringOfItem = reader.GetStringOfItem();
14✔
132
                        string valueStr = valueStringOfItem.ToString().RemQuotes();
14✔
133
                        var valueReader = new BufferedReader(valueStr);
14✔
134

14✔
135
                        foreach (var folder in RequiredFolders) {
14✔
136
                                if (folder.Name.Equals(incomingKey) && Directory.Exists(valueStr)) {
14✔
137
                                        folder.Value = valueStr;
14✔
138
                                }
14✔
139
                        }
14✔
140

14✔
141
                        foreach (var file in RequiredFiles) {
14✔
142
                                if (file.Name.Equals(incomingKey) && File.Exists(valueStr)) {
14✔
143
                                        file.Value = valueStr;
14✔
144
                                }
14✔
145
                        }
14✔
146
                        foreach (var option in Options) {
14✔
147
                                if (option.Name.Equals(incomingKey) && option.CheckBoxSelector is null) {
14✔
148
                                        option.SetValue(valueStr);
14✔
149
                                } else if (option.Name.Equals(incomingKey) && option.CheckBoxSelector is not null) {
14✔
150
                                        var selections = valueReader.GetStrings();
14✔
151
                                        var values = selections.ToHashSet(StringComparer.Ordinal);
14✔
152
                                        option.SetValue(values);
14✔
153
                                        option.SetCheckBoxSelectorPreloaded();
14✔
154
                                }
14✔
155
                        }
14✔
156
                        if (incomingKey.Equals("selectedPlayset", StringComparison.OrdinalIgnoreCase)) {
14✔
157
                                deferredSelectedPlaysetId = valueStr;
14✔
158
                        }
14✔
159
                });
14✔
160
                parser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem);
14✔
161
        }
14✔
162

163
        private void InitializePaths() {
12✔
164
                if (!OperatingSystem.IsWindows()) {
24!
165
                        return;
12✔
166
                }
167

168
                string documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
×
UNCOV
169
                InitializeFolders(documentsDir);
×
UNCOV
170
                InitializeFiles(documentsDir);
×
171
        }
12✔
172

173
        private void InitializeFolders(string documentsDir) {
×
UNCOV
174
                foreach (var folder in RequiredFolders.Where(f => string.IsNullOrEmpty(f.Value))) {
×
175
                        string? initialValue = null;
×
176

177
                        if (folder.SearchPathType.Equals("windowsUsersFolder")) {
×
178
                                initialValue = Path.Combine(documentsDir, folder.SearchPath);
×
179
                        } else if (folder.SearchPathType.Equals("storeFolder")) {
×
180
                                string? possiblePath = null;
×
181
                                if (uint.TryParse(folder.SteamGameId, out uint steamId)) {
×
182
                                        possiblePath = CommonFunctions.GetSteamInstallPath(steamId);
×
183
                                }
×
184
                                if (possiblePath is null && long.TryParse(folder.GOGGameId, CultureInfo.InvariantCulture, out long gogId)) {
×
UNCOV
185
                                        possiblePath = CommonFunctions.GetGOGInstallPath(gogId);
×
186
                                }
×
187

UNCOV
188
                                if (possiblePath is null) {
×
UNCOV
189
                                        continue;
×
190
                                }
191

192
                                initialValue = possiblePath;
×
193
                                if (!string.IsNullOrEmpty(folder.SearchPath)) {
×
194
                                        initialValue = Path.Combine(initialValue, folder.SearchPath);
×
195
                                }
×
196
                        } else if (folder.SearchPathType.Equals("direct")) {
×
UNCOV
197
                                initialValue = folder.SearchPath;
×
198
                        }
×
199

200
                        if (Directory.Exists(initialValue)) {
×
201
                                folder.Value = initialValue;
×
202
                        }
×
UNCOV
203
                }
×
204
        }
×
205

206
        private void InitializeFiles(string documentsDir) {
×
207
                foreach (var file in RequiredFiles) {
×
UNCOV
208
                        string? initialDirectory = null;
×
209
                        string? initialValue = null;
×
210

211
                        if (!string.IsNullOrEmpty(file.Value)) {
×
212
                                initialDirectory = CommonFunctions.GetPath(file.Value);
×
213
                        } else if (file.SearchPathType.Equals("windowsUsersFolder")) {
×
214
                                initialDirectory = Path.Combine(documentsDir, file.SearchPath);
×
215
                                if (!string.IsNullOrEmpty(file.FileName)) {
×
216
                                        initialValue = Path.Combine(initialDirectory, file.FileName);
×
217
                                }
×
218
                        } else if (file.SearchPathType.Equals("converterFolder")) {
×
219
                                initialDirectory = Path.Combine(baseDirectory, file.SearchPath);
×
220
                                if (!string.IsNullOrEmpty(file.FileName)) {
×
221
                                        initialValue = Path.Combine(initialDirectory, file.FileName);
×
222
                                }
×
UNCOV
223
                        }
×
224

225
                        if (string.IsNullOrEmpty(file.Value) && File.Exists(initialValue)) {
×
226
                                file.Value = initialValue;
×
UNCOV
227
                        }
×
228

229
                        if (Directory.Exists(initialDirectory)) {
×
230
                                file.InitialDirectory = initialDirectory;
×
231
                        }
×
232
                }
×
UNCOV
233
        }
×
234

235
        public void LoadExistingConfiguration() {
14✔
236
                var parser = new Parser();
14✔
237
                RegisterPreloadKeys(parser);
14✔
238
                var converterConfigurationPath = Path.Combine(ConverterFolder, "configuration.txt");
14✔
239
                if (string.IsNullOrEmpty(ConverterFolder) || !File.Exists(converterConfigurationPath)) {
14!
UNCOV
240
                        return;
×
241
                }
242

243
                logger.Info("Previous configuration located, preloading selections...");
14✔
244
                parser.ParseFile(converterConfigurationPath);
14✔
245
        }
14✔
246

247
        public void ApplyDeferredPlaysetAutoLocation() {
×
UNCOV
248
                if (!TargetPlaysetSelectionEnabled) {
×
249
                        return;
×
250
                }
251

252
                AutoLocatePlaysets();
×
UNCOV
253
                autoLocateOnTargetPathChangeEnabled = true;
×
254
        }
×
255

256
        public void HandleTargetGameModPathChanged() {
×
257
                if (!TargetPlaysetSelectionEnabled || !autoLocateOnTargetPathChangeEnabled) {
×
UNCOV
258
                        return;
×
259
                }
260

261
                AutoLocatePlaysets();
×
262
        }
×
263

264
        public bool ExportConfiguration() {
×
265
                SetSavingStatus("CONVERTSTATUSIN");
×
266

267
                if (string.IsNullOrEmpty(ConverterFolder)) {
×
268
                        logger.Error("Converter folder is not set!");
×
269
                        SetSavingStatus("CONVERTSTATUSPOSTFAIL");
×
UNCOV
270
                        return false;
×
271
                }
UNCOV
272
                if (!Directory.Exists(ConverterFolder)) {
×
273
                        logger.Error("Could not find converter folder!");
×
274
                        SetSavingStatus("CONVERTSTATUSPOSTFAIL");
×
275
                        return false;
×
276
                }
277

278
                var outConfPath = Path.Combine(ConverterFolder, "configuration.txt");
×
UNCOV
279
                try {
×
280
                        using var writer = new StreamWriter(outConfPath);
×
281

282
                        WriteRequiredFolders(writer);
×
283
                        WriteRequiredFiles(writer);
×
284
                        if (SelectedPlayset is not null) {
×
285
                                writer.WriteLine($"selectedPlayset = {SelectedPlayset.Id}");
×
286
                                WriteSelectedMods(writer, SelectedPlayset);
×
287
                        }
×
288

UNCOV
289
                        WriteOptions(writer);
×
290

291
                        SetSavingStatus("CONVERTSTATUSPOSTSUCCESS");
×
292
                        return true;
×
293
                } catch (Exception ex) {
×
294
                        logger.Error($"Could not open configuration.txt! Error: {ex}");
×
295
                        SetSavingStatus("CONVERTSTATUSPOSTFAIL");
×
UNCOV
296
                        return false;
×
297
                }
298
        }
×
299

300
        private void WriteOptions(StreamWriter writer) {
×
UNCOV
301
                foreach (var option in Options) {
×
UNCOV
302
                        if (option.CheckBoxSelector is not null) {
×
UNCOV
303
                                writer.Write($"{option.Name} = {{ ");
×
304
                                foreach (var value in option.GetValues()) {
×
305
                                        writer.Write($"\"{value}\" ");
×
306
                                }
×
307

UNCOV
308
                                writer.WriteLine("}");
×
309
                        } else {
×
310
                                writer.WriteLine($"{option.Name} = \"{option.GetValue()}\"");
×
UNCOV
311
                        }
×
312
                }
×
313
        }
×
314

315
        private void WriteRequiredFiles(StreamWriter writer) {
×
UNCOV
316
                foreach (var file in RequiredFiles) {
×
317
                        if (!file.Outputtable) {
×
318
                                continue;
×
319
                        }
320

321
                        // In the file path, replace backslashes with forward slashes.
322
                        string pathToWrite = file.Value.Replace('\\', '/');
×
323
                        writer.WriteLine($"{file.Name} = \"{pathToWrite}\"");
×
324
                }
×
325
        }
×
326

327
        private void WriteRequiredFolders(StreamWriter writer) {
×
328
                foreach (var folder in RequiredFolders) {
×
329
                        // In the folder path, replace backslashes with forward slashes.
330
                        string pathToWrite = folder.Value.Replace('\\', '/');
×
331
                        writer.WriteLine($"{folder.Name} = \"{pathToWrite}\"");
×
332
                }
×
UNCOV
333
        }
×
334

335
        private static void SetSavingStatus(string locKey) {
×
336
                if (Avalonia.Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) {
×
337
                        return;
×
338
                }
339

UNCOV
340
                if (desktop.MainWindow?.DataContext is MainWindowViewModel mainWindowDataContext) {
×
341
                        mainWindowDataContext.SaveStatus = locKey;
×
342
                }
×
343
        }
×
344

UNCOV
345
        private void WriteSelectedMods(StreamWriter writer, Playset selectedPlayset) {
×
346
                writer.WriteLine("selectedMods = {");
×
347
                using var dbContext = TargetDbManager.GetLauncherDbContext(this);
×
348
                if (dbContext is null) {
×
UNCOV
349
                        writer.WriteLine("}");
×
UNCOV
350
                        return;
×
351
                }
352

353
                var playsetMods = dbContext.PlaysetsMods
×
UNCOV
354
                        .Include(playsetMod => playsetMod.Mod)
×
355
                        .Where(playsetMod => playsetMod.PlaysetId == selectedPlayset.Id)
×
356
                        .OrderBy(playsetMod => playsetMod.Position ?? long.MaxValue)
×
UNCOV
357
                        .ToList();
×
358

359
                foreach (var playsetMod in playsetMods) {
×
360
                        if (!IsPlaysetModEnabled(playsetMod)) {
×
361
                                continue;
×
362
                        }
363

364
                        var modPath = GetModPath(playsetMod.Mod);
×
365
                        if (string.IsNullOrWhiteSpace(modPath)) {
×
UNCOV
366
                                continue;
×
367
                        }
368

369
                        modPath = modPath.Replace('\\', '/');
×
UNCOV
370
                        writer.WriteLine($"\t\"{modPath}\"");
×
UNCOV
371
                }
×
372

373
                writer.WriteLine("}");
×
374
        }
×
375

UNCOV
376
        private static bool IsPlaysetModEnabled(PlaysetsMod playsetMod) {
×
377
                var enabled = playsetMod.Enabled;
×
378
                if (enabled is null || enabled.Length == 0) {
×
379
                        return true;
×
380
                }
381

UNCOV
382
                return enabled.Any(b => b != 0);
×
UNCOV
383
        }
×
384

385
        private static string? GetModPath(Mod mod) {
×
386
                if (!string.IsNullOrWhiteSpace(mod.GameRegistryId)) {
×
387
                        return mod.GameRegistryId;
×
388
                }
389

390
                Logger.Warn($"Mod {mod.Name} has no GameRegistryId set in the launcher's DB, cannot determine its path!");
×
391
                return null;
×
392
        }
×
393

394
        public string? TargetGameModsPath {
395
                get {
×
396
                        var targetGameModPath = RequiredFolders
×
397
                                .FirstOrDefault(f => string.Equals(f?.Name, "targetGameModPath", StringComparison.OrdinalIgnoreCase), defaultValue: null);
×
UNCOV
398
                        return targetGameModPath?.Value;
×
399
                }
×
400
        }
401

402
        public void AutoLocatePlaysets() {
×
403
                logger.Debug("Clearing previously located playsets...");
×
UNCOV
404
                AutoLocatedPlaysets.Clear();
×
UNCOV
405
                logger.Debug("Autolocating playsets...");
×
406

UNCOV
407
                using var dbContext = TargetDbManager.GetLauncherDbContext(this);
×
UNCOV
408
                if (dbContext is not null) {
×
UNCOV
409
                        foreach (var playset in dbContext.Playsets.Where(p => p.IsRemoved == null || p.IsRemoved == false )) {
×
UNCOV
410
                                AutoLocatedPlaysets.Add(playset);
×
UNCOV
411
                        }
×
UNCOV
412
                }
×
413

UNCOV
414
                var locatedPlaysetsCount = AutoLocatedPlaysets.Count;
×
415

UNCOV
416
                logger.Debug($"Autolocated {locatedPlaysetsCount} playsets.");
×
417

UNCOV
418
                if (!string.IsNullOrWhiteSpace(deferredSelectedPlaysetId)) {
×
UNCOV
419
                        SelectedPlayset = AutoLocatedPlaysets.FirstOrDefault(p =>
×
UNCOV
420
                                string.Equals(p.Id, deferredSelectedPlaysetId, StringComparison.Ordinal));
×
UNCOV
421
                        deferredSelectedPlaysetId = null;
×
UNCOV
422
                }
×
UNCOV
423
        }
×
424
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc