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

ParadoxGameConverters / Fronter.NET / 21723328854

05 Feb 2026 06:20PM UTC coverage: 23.081% (+0.03%) from 23.05%
21723328854

push

github

web-flow
Fix playsets not being found when targetGameModsPath ends with a slash (#937)

* Fix playsets not being found when targetGameModsPath ends with a slash

* Update Fronter.NET/Services/TargetDbManager.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Fronter.NET/Services/ModCopier.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

144 of 774 branches covered (18.6%)

Branch coverage included in aggregate %.

0 of 8 new or added lines in 2 files covered. (0.0%)

2 existing lines in 1 file now uncovered.

728 of 3004 relevant lines covered (24.23%)

8.85 hits per line

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

0.0
/Fronter.NET/Services/ModCopier.cs
1
using commonItems;
2
using Fronter.Models.Configuration;
3
using Fronter.Models.Database;
4
using log4net;
5
using System;
6
using System.Globalization;
7
using System.IO;
8
using System.Linq;
9
using Mod = Fronter.Models.Database.Mod;
10

11
namespace Fronter.Services;
12

NEW
13
internal sealed class ModCopier(Config config) {
×
UNCOV
14
        private readonly ILog logger = LogManager.GetLogger("Mod copier");
×
15

16
        public bool CopyMod() {
×
17
                logger.Notice("Mod Copying Started.");
×
18
                var converterFolder = config.ConverterFolder;
×
19
                if (!Directory.Exists(converterFolder)) {
×
20
                        logger.Error("Copy failed - where is the converter?");
×
21
                        return false;
×
22
                }
23

24
                var outputFolder = Path.Combine(converterFolder, "output");
×
25
                if (!Directory.Exists(outputFolder)) {
×
26
                        logger.Error("Copy failed - where is the converter's output folder?");
×
27
                        return false;
×
28
                }
29

30
                string? destModsFolder = config.TargetGameModsPath;
×
31
                if (destModsFolder is null) {
×
32
                        logger.Error("Copy failed - Target Folder isn't loaded!");
×
33
                        return false;
×
34
                }
35
                if (!Directory.Exists(destModsFolder)) {
×
36
                        logger.Error("Copy failed - Target Folder does not exist!");
×
37
                        return false;
×
38
                }
39
                var options = config.Options;
×
40
                string? targetName = null;
×
41
                foreach (var option in options) {
×
42
                        var value = option.GetValue();
×
43
                        if (option.Name.Equals("output_name") && !string.IsNullOrEmpty(value)) {
×
44
                                targetName = value;
×
45
                        }
×
46
                }
×
47
                var requiredFiles = config.RequiredFiles;
×
48
                if (string.IsNullOrEmpty(targetName)) {
×
49
                        var saveGame = requiredFiles.FirstOrDefault(f => string.Equals(f?.Name, "SaveGame", StringComparison.Ordinal), defaultValue: null);
×
50
                        if (saveGame is null) {
×
51
                                logger.Error("Copy failed - SaveGame is does not exist!");
×
52
                                return false;
×
53
                        }
54
                        var saveGamePath = saveGame.Value;
×
55
                        if (string.IsNullOrEmpty(saveGamePath)) {
×
56
                                logger.Error("Copy Failed - save game path is empty, did we even convert anything?");
×
57
                                return false;
×
58
                        }
59
                        if (!File.Exists(saveGamePath)) {
×
60
                                logger.Error("Copy Failed - save game does not exist, did we even convert anything?");
×
61
                                return false;
×
62
                        }
63
                        if (Directory.Exists(saveGamePath)) {
×
64
                                logger.Error("Copy Failed - Save game is a directory...");
×
65
                                return false;
×
66
                        }
67
                        saveGamePath = CommonFunctions.TrimPath(saveGamePath);
×
68
                        saveGamePath = CommonFunctions.NormalizeStringPath(saveGamePath);
×
69
                        var pos = saveGamePath.LastIndexOf('.');
×
70
                        if (pos != -1) {
×
71
                                saveGamePath = saveGamePath[..pos];
×
72
                        }
×
73
                        targetName = saveGamePath;
×
74
                }
×
75

76
                targetName = CommonFunctions.ReplaceCharacter(targetName, '-');
×
77
                targetName = CommonFunctions.ReplaceCharacter(targetName, ' ');
×
78
                targetName = CommonFunctions.NormalizeUTF8Path(targetName);
×
79

80
                var modFolderPath = Path.Combine(outputFolder, targetName);
×
81
                if (!Directory.Exists(modFolderPath)) {
×
82
                        logger.Error($"Copy Failed - Could not find mod folder: {modFolderPath}");
×
83
                        return false;
×
84
                }
85

86
                // For games using mods with .metadata folders we need to skip .mod file requirement.
87
                bool skipModFile = false;
×
88
                var metadataPath = Path.Combine(outputFolder, $"{targetName}/.metadata");
×
89
                if (Directory.Exists(metadataPath)) {
×
90
                        skipModFile = true;
×
91
                }
×
92

93
                var modFilePath = Path.Combine(outputFolder, $"{targetName}.mod");
×
94
                if (!skipModFile && !File.Exists(modFilePath)) {
×
95
                        logger.Error($"Copy Failed - Could not find mod: {modFilePath}");
×
96
                        return false;
×
97
                }
98

99
                var destModFilePath = Path.Combine(destModsFolder, $"{targetName}.mod");
×
100
                if (!skipModFile && File.Exists(destModFilePath)) {
×
101
                        logger.Info("Previous mod file found, deleting...");
×
102
                        File.Delete(destModFilePath);
×
103
                }
×
104

105
                var destModFolderPath = Path.Combine(destModsFolder, targetName);
×
106
                if (Directory.Exists(destModFolderPath)) {
×
107
                        logger.Info("Previous mod directory found, deleting...");
×
108
                        if (!SystemUtils.TryDeleteFolder(destModFolderPath)) {
×
109
                                logger.Error($"Could not delete directory: {destModFolderPath}");
×
110
                                return false;
×
111
                        }
112
                }
×
113
                try {
×
114
                        logger.Info("Copying mod to target location...");
×
115
                        if (!skipModFile) {
×
116
                                if (!SystemUtils.TryCopyFile(modFilePath, destModFilePath)) {
×
117
                                        logger.Error($"Could not copy file: {modFilePath}\nto {destModFilePath}");
×
118
                                }
×
119
                        }
×
120
                        if (!SystemUtils.TryCopyFolder(modFolderPath, destModFolderPath)) {
×
121
                                logger.Error($"Could not copy folder: {modFolderPath}\nto {destModFolderPath}");
×
122
                        }
×
123
                } catch (Exception e) {
×
124
                        logger.Error(e.ToString());
×
125
                        return false;
×
126
                }
127
                logger.Notice($"Mod successfully copied to: {destModFolderPath}");
×
128

129
                CreatePlayset(destModsFolder, targetName, destModFolderPath);
×
130

131
                return true;
×
132
        }
×
133

134
        private void CreatePlayset(string targetModsDirectory, string modName, string destModFolder) {
×
135
                var gameDocsDirectory = Directory.GetParent(targetModsDirectory)?.FullName;
×
136
                if (gameDocsDirectory is null) {
×
137
                        logger.Warn($"Couldn't get parent directory of \"{targetModsDirectory}\".");
×
138
                        return;
×
139
                }
140
                try {
×
NEW
141
                        var dbContext = TargetDbManager.GetLauncherDbContext(config);
×
NEW
142
                        if (dbContext is null) {
×
NEW
143
                                logger.Debug("Launcher's database not found.");
×
NEW
144
                                return;
×
145
                        }
146
                        logger.Debug("Connecting to launcher's DB...");
×
147

UNCOV
148
                        var playsetName = $"{config.Name}: {modName}";
×
149
                        var dateTimeOffset = new DateTimeOffset(DateTime.UtcNow);
×
150
                        string unixTimeMilliSeconds = dateTimeOffset.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
×
151

152
                        DeactivateCurrentPlayset(dbContext);
×
153

154
                        // Check if a playset with the same name already exists.
155
                        var playset = dbContext.Playsets.FirstOrDefault(p => p.Name == playsetName);
×
156
                        if (playset is not null) {
×
157
                                logger.Debug("Removing mods from existing playset...");
×
158
                                dbContext.PlaysetsMods.RemoveRange(dbContext.PlaysetsMods.Where(pm => pm.PlaysetId == playset.Id));
×
159
                                dbContext.SaveChanges();
×
160

161
                                logger.Debug("Re-activating existing playset...");
×
162
                                // Set isActive to true and updatedOn to current time.
163
                                playset.IsActive = true;
×
164
                                playset.UpdatedOn = unixTimeMilliSeconds;
×
165
                                dbContext.SaveChanges();
×
166

167
                                logger.Notice("Updated existing playset.");
×
168
                        } else {
×
169
                                logger.Debug("Creating new playset...");
×
170
                                playset = new Playset {
×
171
                                        Id = Guid.NewGuid().ToString(),
×
172
                                        Name = playsetName,
×
173
                                        IsActive = true,
×
174
                                        IsRemoved = false,
×
175
                                        HasNotApprovedChanges = false,
×
176
                                        CreatedOn = unixTimeMilliSeconds
×
177
                                };
×
178
                                dbContext.Playsets.Add(playset);
×
179
                                dbContext.SaveChanges();
×
180
                        }
×
181

182
                        Logger.Debug("Adding mods to playset...");
×
183
                        var playsetInfo = LoadPlaysetInfo();
×
184
                        if (playsetInfo.Count == 0) {
×
185
                                var gameRegistryId = $"mod/{modName}.mod";
×
186
                                var mod = AddModToDb(dbContext, modName, gameRegistryId, destModFolder);
×
187
                                AddModToPlayset(dbContext, mod, playset);
×
188
                        }
×
189
                        foreach (var (playsetModName, playsetModPath) in playsetInfo) {
×
190
                                string playsetModPathWithBackSlashes = playsetModPath.Replace('/', '\\');
×
191

192
                                // Try to get an ID of existing matching mod.
193
                                var mod = dbContext.Mods.FirstOrDefault(m => m.Name == playsetModName ||
×
194
                                                                                                                          m.DirPath == playsetModPath ||
×
195
                                                                                                                          m.DirPath == playsetModPathWithBackSlashes);
×
196
                                if (mod is not null) {
×
197
                                        AddModToPlayset(dbContext, mod, playset);
×
198
                                } else {
×
199
                                        var gameRegistryId = playsetModPath;
×
200
                                        if (!gameRegistryId.StartsWith("mod/", StringComparison.Ordinal)) {
×
201
                                                gameRegistryId = $"mod/{gameRegistryId}";
×
202
                                        }
×
203
                                        if (!gameRegistryId.EndsWith(".mod", StringComparison.Ordinal)) {
×
204
                                                gameRegistryId = $"{gameRegistryId}.mod";
×
205
                                        }
×
206

207
                                        string dirPath;
208
                                        if (Path.IsPathRooted(playsetModPath)) {
×
209
                                                dirPath = playsetModPath;
×
210
                                        } else {
×
211
                                                dirPath = Path.Combine(gameDocsDirectory, gameRegistryId);
×
212
                                        }
×
213

214
                                        mod = AddModToDb(dbContext, modName, gameRegistryId, dirPath);
×
215
                                        AddModToPlayset(dbContext, mod, playset);
×
216
                                }
×
217
                        }
×
218

219
                        logger.Notice("Successfully set up playset.");
×
220
                } catch (Exception e) {
×
221
                        logger.Error(e);
×
222
                }
×
223
        }
×
224

225
        // Returns saved mod.
226
        private Mod AddModToDb(LauncherDbContext dbContext, string modName, string gameRegistryId, string dirPath) {
×
227
                logger.Debug($"Saving mod \"{modName}\" to DB...");
×
228

229
                var mod = new Mod {
×
230
                        Id = Guid.NewGuid().ToString(),
×
231
                        Status = "ready_to_play",
×
232
                        Source = "local",
×
233
                        Version = "1",
×
234
                        GameRegistryId = gameRegistryId,
×
235
                        Name = modName,
×
236
                        DirPath = dirPath,
×
237
                };
×
238
                dbContext.Mods.Add(mod);
×
239
                dbContext.SaveChanges();
×
240

241
                return mod;
×
242
        }
×
243

244
        private static void AddModToPlayset(LauncherDbContext dbContext, Mod mod, Playset playset) {
×
245
                var playsetMod = new PlaysetsMod {
×
246
                        Playset = playset,
×
247
                        Mod = mod,
×
248
                };
×
249
                dbContext.PlaysetsMods.Add(playsetMod);
×
250
                dbContext.SaveChanges();
×
251
        }
×
252

253
        // Loads playset info generated by converter backend.
254
        private Open.Collections.OrderedDictionary<string, string> LoadPlaysetInfo() {
×
255
                logger.Debug("Loading playset info from converter backend...");
×
256
                var toReturn = new Open.Collections.OrderedDictionary<string, string>();
×
257

258
                var filePath = Path.Combine(config.ConverterFolder, "playset_info.txt");
×
259
                if (!File.Exists(filePath)) {
×
260
                        return toReturn;
×
261
                }
262

263
                var parser = new Parser();
×
264
                parser.RegisterRegex(CommonRegexes.QuotedString, (reader, modName) => {
×
265
                        toReturn.Add(modName, reader.GetString().RemQuotes());
×
266
                });
×
267
                parser.ParseFile(filePath);
×
268
                return toReturn;
×
269
        }
×
270

271
        private void DeactivateCurrentPlayset(LauncherDbContext dbContext) {
×
272
                logger.Debug("Deactivating currently active playset...");
×
273
                dbContext.Playsets
×
274
                        .Where(p => p.IsActive == true)
×
275
                        .ToList()
×
276
                        .ForEach(p => p.IsActive = false);
×
277
                dbContext.SaveChanges();
×
278
        }
×
279
}
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