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

ParadoxGameConverters / ImperatorToCK3 / 18805871990

25 Oct 2025 04:53PM UTC coverage: 49.049%. First build
18805871990

Pull #2794

github

web-flow
Merge 9e1258b19 into a57a7c966
Pull Request #2794: Bump PGCG.commonItems from 17.0.1 to 18.0.0

2411 of 5777 branches covered (41.73%)

Branch coverage included in aggregate %.

2 of 18 new or added lines in 3 files covered. (11.11%)

8622 of 16717 relevant lines covered (51.58%)

4161.96 hits per line

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

38.23
/ImperatorToCK3/Configuration.cs
1
using commonItems;
2
using commonItems.Collections;
3
using commonItems.Exceptions;
4
using commonItems.Mods;
5
using System;
6
using System.Collections.Generic;
7
using System.Globalization;
8
using System.IO;
9
using System.Linq;
10

11
namespace ImperatorToCK3;
12

13
internal enum LegionConversion { No, SpecialTroops, MenAtArms }
14
internal sealed class Configuration {
15
        public string SaveGamePath { get; set; } = "";
148✔
16
        public string ImperatorPath { get; set; } = "";
162✔
17
        public string ImperatorDocPath { get; set; } = "";
148✔
18
        public string CK3Path { get; set; } = "";
191✔
19
        public string CK3ModsPath { get; set; } = "";
156✔
20
        public OrderedSet<string> SelectedCK3Mods { get; } = new();
148✔
21
        public string OutputModName { get; set; } = "";
148✔
22
        public bool HeresiesInHistoricalAreas { get; set; } = false;
156✔
23
        public bool StaticDeJure { get; set; } = false;
150✔
24
        public bool FillerDukes { get; set; } = true;
148✔
25
        public bool UseCK3Flags { get; set; } = true;
149✔
26
        public float ImperatorCurrencyRate { get; set; } = 1.0f;
151✔
27
        public double ImperatorCivilizationWorth { get; set; } = 0.4;
172✔
28
        public LegionConversion LegionConversion { get; set; } = LegionConversion.MenAtArms;
148✔
29
        public Date CK3BookmarkDate { get; set; } = new(0, 1, 1);
206✔
30
        public bool SkipDynamicCoAExtraction { get; set; } = false;
157✔
31
        public bool SkipHoldingOwnersImport { get; set; } = true;
148✔
32
        public GameVersion IRVersion { get; private set; } = new();
148✔
33
        public GameVersion CK3Version { get; private set; } = new();
148✔
34
        public bool FallenEagleEnabled { get; private set; }
19✔
35
        public bool WhenTheWorldStoppedMakingSenseEnabled { get; private set; }
7✔
36
        public bool RajasOfAsiaEnabled { get; private set; }
10✔
37
        public bool AsiaExpansionProjectEnabled { get; private set; }
8✔
38

39
        public bool OutputCCUParameters => WhenTheWorldStoppedMakingSenseEnabled || FallenEagleEnabled || RajasOfAsiaEnabled;
×
40

41
        public Configuration() { }
438✔
42
        public Configuration(ConverterVersion converterVersion) {
4✔
43
                Logger.Info("Reading configuration file...");
2✔
44
                var parser = new Parser();
2✔
45
                RegisterKeys(parser);
2✔
46
                const string configurationPath = "configuration.txt";
47
                if (!File.Exists(configurationPath)) {
4!
48
                        throw new ConverterException($"{configurationPath} not found! Run ConverterFrontend to generate it.");
2✔
49
                }
50
                parser.ParseFile(configurationPath);
×
51

52
                SetOutputName();
×
53
                VerifyImperatorPath();
×
54
                VerifyImperatorVersion(converterVersion);
×
55
                VerifyCK3Path();
×
56
                VerifyCK3Version(converterVersion);
×
57
                VerifyImperatorDocPath();
×
58
                VerifyCK3ModsPath();
×
59

60
                Logger.IncrementProgress();
×
61
        }
×
62

63
        private void RegisterKeys(Parser parser) {
2✔
64
                parser.RegisterKeyword("SaveGame", reader => {
2✔
65
                        SaveGamePath = reader.GetString();
×
66
                        Logger.Info($"Save game set to: {SaveGamePath}");
×
67
                });
2✔
68
                parser.RegisterKeyword("ImperatorDirectory", reader => ImperatorPath = reader.GetString());
2✔
69
                parser.RegisterKeyword("ImperatorDocDirectory", reader => ImperatorDocPath = reader.GetString());
2✔
70
                parser.RegisterKeyword("CK3directory", reader => CK3Path = reader.GetString());
2✔
71
                parser.RegisterKeyword("targetGameModPath", reader => CK3ModsPath = reader.GetString());
2✔
72
                parser.RegisterKeyword("selectedMods", reader => {
2✔
73
                        SelectedCK3Mods.UnionWith(reader.GetStrings());
×
74
                        Logger.Info($"{SelectedCK3Mods.Count} mods selected by configuration.");
×
75
                });
2✔
76
                parser.RegisterKeyword("output_name", reader => {
2✔
77
                        OutputModName = reader.GetString();
×
78
                        Logger.Info($"Output name set to: {OutputModName}");
×
79
                });
2✔
80
                parser.RegisterKeyword("HeresiesInHistoricalAreas", reader => {
2✔
81
                        var valueString = reader.GetString();
×
82
                        try {
×
83
                                HeresiesInHistoricalAreas = Convert.ToInt32(valueString, CultureInfo.InvariantCulture) == 1;
×
84
                                Logger.Info($"{nameof(HeresiesInHistoricalAreas)} set to: {HeresiesInHistoricalAreas}");
×
85
                        } catch (Exception e) {
×
86
                                Logger.Error($"Undefined error, {nameof(HeresiesInHistoricalAreas)} value was: {valueString}; Error message: {e}");
×
87
                        }
×
88
                });
2✔
89
                parser.RegisterKeyword("StaticDeJure", reader => {
2✔
90
                        var valueString = reader.GetString();
×
91
                        try {
×
92
                                StaticDeJure = Convert.ToInt32(valueString, CultureInfo.InvariantCulture) == 2;
×
93
                                Logger.Info($"{nameof(StaticDeJure)} set to: {StaticDeJure}");
×
94
                        } catch (Exception e) {
×
95
                                Logger.Error($"Undefined error, {nameof(StaticDeJure)} value was: {valueString}; Error message: {e}");
×
96
                        }
×
97
                });
2✔
98
                parser.RegisterKeyword("FillerDukes", reader => {
2✔
99
                        var valueString = reader.GetString();
×
100
                        try {
×
101
                                FillerDukes = Convert.ToInt32(valueString, CultureInfo.InvariantCulture) == 1;
×
102
                                Logger.Info($"{nameof(FillerDukes)} set to: {FillerDukes}");
×
103
                        } catch (Exception e) {
×
104
                                Logger.Error($"Undefined error, {nameof(FillerDukes)} value was: {valueString}; Error message: {e}");
×
105
                        }
×
106
                });
2✔
107
                parser.RegisterKeyword("UseCK3Flags", reader => {
2✔
108
                        var valueString = reader.GetString();
×
109
                        try {
×
110
                                UseCK3Flags = Convert.ToInt32(valueString, CultureInfo.InvariantCulture) == 1;
×
111
                                Logger.Info($"{nameof(UseCK3Flags)} set to: {UseCK3Flags}");
×
112
                        } catch (Exception e) {
×
113
                                Logger.Error($"Undefined error, {nameof(UseCK3Flags)} value was: {valueString}; Error message: {e}");
×
114
                        }
×
115
                });
2✔
116
                parser.RegisterKeyword("ImperatorCurrencyRate", reader => {
2✔
117
                        ImperatorCurrencyRate = reader.GetFloat();
×
118
                        Logger.Info($"{nameof(ImperatorCurrencyRate)} set to: {ImperatorCurrencyRate}");
×
119
                });
2✔
120
                parser.RegisterKeyword("ImperatorCivilizationWorth", reader => {
2✔
121
                        ImperatorCivilizationWorth = reader.GetDouble();
×
122
                        Logger.Info($"{nameof(ImperatorCivilizationWorth)} set to: {ImperatorCivilizationWorth}");
×
123
                });
2✔
124
                parser.RegisterKeyword("LegionConversion", reader => {
2✔
125
                        var valueString = reader.GetString();
×
126
                        var success = Enum.TryParse(valueString, out LegionConversion selection);
×
127
                        if (success) {
×
128
                                LegionConversion = selection;
×
129
                                Logger.Info($"{nameof(LegionConversion)} set to {LegionConversion}.");
×
130
                        } else {
×
131
                                Logger.Warn($"Failed to parse {valueString} as value for {nameof(LegionConversion)}.");
×
132
                        }
×
133
                });
2✔
134
                parser.RegisterKeyword("bookmark_date", reader => {
2✔
135
                        var dateStr = reader.GetString();
×
136
                        if (string.IsNullOrEmpty(dateStr)) {
×
137
                                return;
×
138
                        }
2✔
139

2✔
140
                        Logger.Info($"Entered CK3 bookmark date: {dateStr}");
×
141
                        CK3BookmarkDate = new Date(dateStr);
×
142
                        var earliestAllowedDate = new Date(2,1,1);
×
143
                        if (CK3BookmarkDate < earliestAllowedDate) {
×
144
                                Logger.Warn($"CK3 bookmark date cannot be earlier than {earliestAllowedDate} AD (Y.M.D format), you should fix your configuration. Setting to earliest allowed date...");
×
145
                                CK3BookmarkDate = earliestAllowedDate;
×
146
                                Logger.Info($"Changed CK3 bookmark date to {earliestAllowedDate}");
×
147
                        }
×
148
                        Logger.Info($"CK3 bookmark date set to: {CK3BookmarkDate}");
×
149
                });
2✔
150
                parser.RegisterKeyword("SkipDynamicCoAExtraction", reader => {
2✔
151
                        var valueString = reader.GetString();
×
152
                        try {
×
153
                                SkipDynamicCoAExtraction = Convert.ToInt32(valueString, CultureInfo.InvariantCulture) == 1;
×
154
                                Logger.Info($"{nameof(SkipDynamicCoAExtraction)} set to: {SkipDynamicCoAExtraction}");
×
155
                        } catch (Exception e) {
×
156
                                Logger.Error($"Undefined error, {nameof(SkipDynamicCoAExtraction)} value was: {valueString}; Error message: {e}");
×
157
                        }
×
158
                });
2✔
159
                parser.RegisterKeyword("SkipHoldingOwnersImport", reader => {
2✔
160
                        var valueString = reader.GetString();
×
161
                        try {
×
162
                                SkipHoldingOwnersImport = Convert.ToInt32(valueString, CultureInfo.InvariantCulture) == 1;
×
163
                                Logger.Info($"{nameof(SkipHoldingOwnersImport)} set to: {SkipHoldingOwnersImport}");
×
164
                        } catch (Exception e) {
×
165
                                Logger.Error($"Undefined error, {nameof(SkipHoldingOwnersImport)} value was: {valueString}; Error message: {e}");
×
166
                        }
×
167
                });
2✔
168
                parser.RegisterRegex(CommonRegexes.Catchall, ParserHelpers.IgnoreAndLogItem);
2✔
169
        }
2✔
170

171
        private void VerifyImperatorPath() {
×
172
                if (!Directory.Exists(ImperatorPath)) {
×
173
                        throw new UserErrorException($"{ImperatorPath} does not exist!");
×
174
                }
175

176
                var binariesPath = Path.Combine(ImperatorPath, "binaries");
×
177
                var imperatorExePath = Path.Combine(binariesPath, "imperator");
×
178
                if (OperatingSystem.IsWindows()) {
×
179
                        imperatorExePath += ".exe";
×
180
                }
×
181

182
                bool installVerified = File.Exists(imperatorExePath);
×
183
                if (!installVerified) {
×
184
                        try {
×
185
                                var appIdPath = Path.Combine(binariesPath, "steam_appid.txt");
×
186
                                var appId = File.ReadAllText(appIdPath).Trim();
×
187
                                if (appId == "859580") {
×
188
                                        installVerified = true;
×
189
                                }
×
190
                        } catch(Exception e) {
×
191
                                Logger.Debug($"Exception was raised when checking I:R steam_appid: {e.Message}");
×
192
                        }
×
193
                }
×
194

195
                if (installVerified) {
×
196
                        Logger.Info($"\tI:R install path is {ImperatorPath}");
×
197
                } else {
×
198
                        throw new UserErrorException($"{ImperatorPath} does not contain Imperator: Rome!");
×
199
                }
200
        }
×
201

202
        private void VerifyCK3Path() {
×
203
                if (!Directory.Exists(CK3Path)) {
×
204
                        throw new UserErrorException($"{CK3Path} does not exist!");
×
205
                }
206

207
                var binariesPath = Path.Combine(CK3Path, "binaries");
×
208
                var ck3ExePath = Path.Combine(binariesPath, "ck3");
×
209
                if (OperatingSystem.IsWindows()) {
×
210
                        ck3ExePath += ".exe";
×
211
                }
×
212

213
                bool installVerified = File.Exists(ck3ExePath);
×
214
                if (!installVerified) {
×
215
                        try {
×
216
                                var appIdPath = Path.Combine(binariesPath, "steam_appid.txt");
×
217
                                var appId = File.ReadAllText(appIdPath).Trim();
×
218
                                if (appId == "1158310") {
×
219
                                        installVerified = true;
×
220
                                }
×
221
                        } catch(Exception e) {
×
222
                                Logger.Debug($"Exception was raised when checking CK3 steam_appid: {e.Message}");
×
223
                        }
×
224
                }
×
225

226
                if (installVerified) {
×
227
                        Logger.Info($"\tCK3 install path is {CK3Path}");
×
228
                } else{
×
229
                        throw new UserErrorException($"{CK3Path} does not contain Crusader Kings III!");
×
230
                }
231
        }
×
232

233
        private void VerifyImperatorDocPath() {
×
234
                if (!Directory.Exists(ImperatorDocPath)) {
×
235
                        throw new UserErrorException($"{ImperatorDocPath} does not exist!");
×
236
                }
237

238
                string[] dirsInDocFolder = ["mod/", "logs/", "save_games/", "cache/"];
×
239
                string[] filesInDocFolder = [
×
240
                        "continue_game.json", "dlc_load.json", "dlc_signature", "game_data.json", "pdx_settings.txt"
×
241
                ];
×
242
                // If at least one of the paths exists, we consider the folder to be valid.
243
                bool docFolderVerified = dirsInDocFolder.Any(dir => Directory.Exists(Path.Combine(ImperatorDocPath, dir)));
×
244
                if (!docFolderVerified) {
×
245
                        docFolderVerified = filesInDocFolder.Any(file => File.Exists(Path.Combine(ImperatorDocPath, file)));
×
246
                }
×
247

248
                if (!docFolderVerified) {
×
249
                        throw new UserErrorException($"{ImperatorDocPath} is not a valid I:R documents path!\n" +
×
250
                                                     "It should contain one of the following files: " +
×
251
                                                     $"{string.Join(", ", filesInDocFolder)}");
×
252
                }
253
                
254
                Logger.Debug($"I:R documents path {ImperatorDocPath} is valid.");
×
255
        }
×
256
        
257
        private void VerifyCK3ModsPath() {
2✔
258
                if (!Directory.Exists(CK3ModsPath)) {
2!
259
                        throw new UserErrorException($"{CK3ModsPath} does not exist!");
×
260
                }
261

262
                var normalizedCK3ModsPath = Path.TrimEndingDirectorySeparator(
2✔
263
                        CK3ModsPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
2✔
264
                );
2✔
265
                var expectedSuffix = Path.Combine("Paradox Interactive", "Crusader Kings III", "mod");
2✔
266
                var comparison = OperatingSystem.IsWindows() || OperatingSystem.IsMacOS()
2!
267
                        ? StringComparison.OrdinalIgnoreCase
2✔
268
                        : StringComparison.Ordinal;
2✔
269
                if (!normalizedCK3ModsPath.EndsWith(expectedSuffix, comparison)) {
3✔
270
                        throw new UserErrorException($"{CK3ModsPath} is not a valid CK3 mods directory! It should end with {expectedSuffix}.");
1✔
271
                }
272

273
                // If the mods folder contains any files, at least one on them should have a .mod extension.
274
                var filesInFolder = Directory.GetFiles(CK3ModsPath);
1✔
275
                if (filesInFolder.Length > 0) {
1!
276
                        var modFiles = filesInFolder.Where(f => f.EndsWith(".mod", StringComparison.OrdinalIgnoreCase));
×
277
                        if (!modFiles.Any()) {
×
278
                                throw new UserErrorException($"{CK3ModsPath} does not contain any .mod files!");
×
279
                        }
280
                }
×
281
        }
1✔
282

283
        private void SetOutputName() {
×
284
                if (string.IsNullOrWhiteSpace(OutputModName)) {
×
285
                        OutputModName = CommonFunctions.TrimExtension(CommonFunctions.TrimPath(SaveGamePath));
×
286
                }
×
287
                OutputModName = OutputModName.Replace('-', '_');
×
288
                OutputModName = OutputModName.Replace(' ', '_');
×
289

290
                OutputModName = CommonFunctions.NormalizeUTF8Path(OutputModName);
×
291
                Logger.Info($"Using output name {OutputModName}");
×
292
        }
×
293

294
        private void VerifyImperatorVersion(ConverterVersion converterVersion) {
×
295
                var path = Path.Combine(ImperatorPath, "launcher/launcher-settings.json");
×
NEW
296
                IRVersion = GameVersion.ExtractVersionFromLauncher(path) ??
×
NEW
297
                                   throw new ConverterException("Imperator version could not be determined.");
×
298

NEW
299
                Logger.Info($"Imperator version: {IRVersion.ToShortString()}");
×
300

NEW
301
                if (converterVersion.MinSource > IRVersion) {
×
NEW
302
                        Logger.Error($"Imperator version is v{IRVersion.ToShortString()}, converter requires minimum v{converterVersion.MinSource.ToShortString()}!");
×
303
                        throw new UserErrorException("Converter vs Imperator installation mismatch!");
×
304
                }
NEW
305
                if (!converterVersion.MaxSource.IsLargerishThan(IRVersion)) {
×
NEW
306
                        Logger.Error($"Imperator version is v{IRVersion.ToShortString()}, converter requires maximum v{converterVersion.MaxSource.ToShortString()}!");
×
307
                        throw new UserErrorException("Converter vs Imperator installation mismatch!");
×
308
                }
309
        }
×
310

311
        private void VerifyCK3Version(ConverterVersion converterVersion) {
×
312
                var path = Path.Combine(CK3Path, "launcher/launcher-settings.json");
×
NEW
313
                CK3Version = GameVersion.ExtractVersionFromLauncher(path) ??
×
NEW
314
                             throw new ConverterException("CK3 version could not be determined.");
×
315

NEW
316
                Logger.Info($"CK3 version: {CK3Version.ToShortString()}");
×
317

NEW
318
                if (converterVersion.MinTarget > CK3Version) {
×
NEW
319
                        Logger.Error($"CK3 version is v{CK3Version.ToShortString()}, converter requires minimum v{converterVersion.MinTarget.ToShortString()}!");
×
320
                        throw new UserErrorException("Converter vs CK3 installation mismatch!");
×
321
                }
NEW
322
                if (!converterVersion.MaxTarget.IsLargerishThan(CK3Version)) {
×
NEW
323
                        Logger.Error($"CK3 version is v{CK3Version.ToShortString()}, converter requires maximum v{converterVersion.MaxTarget.ToShortString()}!");
×
324
                        throw new UserErrorException("Converter vs CK3 installation mismatch!");
×
325
                }
326
        }
×
327

328
        public void DetectSpecificCK3Mods(ICollection<Mod> loadedMods) {
4✔
329
                var tfeMod = loadedMods.FirstOrDefault(m => m.Name.StartsWith("The Fallen Eagle", StringComparison.Ordinal));
9✔
330
                if (tfeMod is not null) {
7✔
331
                        FallenEagleEnabled = true;
3✔
332
                        Logger.Info($"TFE detected: {tfeMod.Name}");
3✔
333
                }
3✔
334
                
335
                var wtwsmsMod = loadedMods.FirstOrDefault(m => m.Name.StartsWith("When the World Stopped Making Sense", StringComparison.Ordinal));
12✔
336
                if (wtwsmsMod is not null) {
5✔
337
                        WhenTheWorldStoppedMakingSenseEnabled = true;
1✔
338
                        Logger.Info($"WtWSMS detected: {wtwsmsMod.Name}");
1✔
339
                }
1✔
340
                
341
                var roaMod = loadedMods.FirstOrDefault(m => m.Name.StartsWith("Rajas of Asia", StringComparison.Ordinal));
11✔
342
                if (roaMod is not null) {
6✔
343
                        RajasOfAsiaEnabled = true;
2✔
344
                        Logger.Info($"RoA detected: {roaMod.Name}");
2✔
345
                }
2✔
346
                
347
                var aepMod = loadedMods.FirstOrDefault(m => m.Name.StartsWith("Asia Expansion Project", StringComparison.Ordinal));
12✔
348
                if (aepMod is not null) {
6✔
349
                        AsiaExpansionProjectEnabled = true;
2✔
350
                        Logger.Info($"AEP detected: {aepMod.Name}");
2✔
351
                }
2✔
352

353
                ThrowUserErrorExceptionForUnsupportedModCombinations();
4✔
354
        }
×
355

356
        private void ThrowUserErrorExceptionForUnsupportedModCombinations() {
4✔
357
                if (FallenEagleEnabled && WhenTheWorldStoppedMakingSenseEnabled) {
5✔
358
                        throw new UserErrorException("The converter doesn't support combining The Fallen Eagle with When the World Stopped Making Sense!");
1✔
359
                }
360
                if (RajasOfAsiaEnabled && AsiaExpansionProjectEnabled) {
4✔
361
                        throw new UserErrorException("The converter doesn't support combining Rajas of Asia with Asia Expansion Project!");
1✔
362
                }
363
                if (FallenEagleEnabled && RajasOfAsiaEnabled) {
3!
364
                        throw new UserErrorException("The converter doesn't support combining The Fallen Eagle with Rajas of Asia!");
1✔
365
                }
366
                if (FallenEagleEnabled && AsiaExpansionProjectEnabled) {
2!
367
                        throw new UserErrorException("The converter doesn't support combining The Fallen Eagle with Asia Expansion Project!");
1✔
368
                }
369
        }
×
370

371
        /// <summary>Returns a collection of CK3 mod flags with values based on the enabled mods. "vanilla" flag is set to true if no other flags are set.</summary>
372
        public OrderedDictionary<string, bool> GetCK3ModFlags() {
3✔
373
                var flags = new OrderedDictionary<string, bool> {
3✔
374
                        ["tfe"] = FallenEagleEnabled,
3✔
375
                        ["wtwsms"] = WhenTheWorldStoppedMakingSenseEnabled,
3✔
376
                        ["roa"] = RajasOfAsiaEnabled,
3✔
377
                        ["aep"] = AsiaExpansionProjectEnabled,
3✔
378
                };
3✔
379

380
                flags["vanilla"] = !flags.Any(f => f.Value);
15✔
381
                return flags;
3✔
382
        }
3✔
383
        
384
        public IEnumerable<string> GetActiveCK3ModFlags() {
3✔
385
                return GetCK3ModFlags().Where(f => f.Value).Select(f => f.Key);
3✔
386
        }
3✔
387
}
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

© 2025 Coveralls, Inc