Skip to content

Commit 202419f

Browse files
authored
Handle corrupted I:R character birth date in the save (#3083) #patch
Sentry event ID: 823a305e66244323be7ac77e6d7cf657
1 parent 4f66593 commit 202419f

2 files changed

Lines changed: 83 additions & 35 deletions

File tree

ImperatorToCK3.UnitTests/Imperator/Characters/CharacterTests.cs

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ public void FieldsCanBeSet() {
6262
"\tprisoner_home=68" +
6363
"}"
6464
);
65-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
65+
var character = Character.Parse(reader, "42", genesDB);
6666
var spouse1Reader = new BufferedReader(string.Empty);
6767
var spouse2Reader = new BufferedReader(string.Empty);
68-
character.Spouses = new Dictionary<ulong, ImperatorToCK3.Imperator.Characters.Character> {
69-
{ 3, ImperatorToCK3.Imperator.Characters.Character.Parse(spouse1Reader, "3", genesDB) },
70-
{ 4, ImperatorToCK3.Imperator.Characters.Character.Parse(spouse2Reader, "4", genesDB) }
68+
character.Spouses = new Dictionary<ulong, Character> {
69+
{ 3, Character.Parse(spouse1Reader, "3", genesDB) },
70+
{ 4, Character.Parse(spouse2Reader, "4", genesDB) }
7171
};
7272

7373
Assert.Equal((ulong)42, character.Id);
7474

7575
Assert.Null(character.Country); // we have a country id, but no linked country yet
7676
var countriesReader = new BufferedReader("={ 69={} 68={} }");
77-
var countries = new ImperatorToCK3.Imperator.Countries.CountryCollection();
77+
var countries = new CountryCollection();
7878
countries.LoadCountries(countriesReader);
7979
character.LinkCountry(countries);
8080
Assert.NotNull(character.Country);
@@ -113,10 +113,10 @@ public void FieldsCanBeSet() {
113113
);
114114

115115
Assert.Empty(character.Children); // children not linked yet
116-
var characters = new ImperatorToCK3.Imperator.Characters.CharacterCollection();
116+
var characters = new CharacterCollection();
117117
characters.Add(character);
118-
var child1 = ImperatorToCK3.Imperator.Characters.Character.Parse(new BufferedReader("={ mother=42 }"), "69", null);
119-
var child2 = ImperatorToCK3.Imperator.Characters.Character.Parse(new BufferedReader("={ mother=42 }"), "420", null);
118+
var child1 = Character.Parse(new BufferedReader("={ mother=42 }"), "69", null);
119+
var child2 = Character.Parse(new BufferedReader("={ mother=42 }"), "420", null);
120120
characters.Add(child1);
121121
characters.Add(child2);
122122
child1.LinkMother(characters);
@@ -133,8 +133,8 @@ public void FieldsCanBeSet() {
133133

134134
Assert.Null(character.Mother); // mother not linked yet
135135
Assert.Null(character.Father); // father not linked yet
136-
var mother = new ImperatorToCK3.Imperator.Characters.Character(123);
137-
var father = new ImperatorToCK3.Imperator.Characters.Character(124);
136+
var mother = new Character(123);
137+
var father = new Character(124);
138138
characters.Add(mother);
139139
characters.Add(father);
140140
character.LinkMother(characters);
@@ -171,7 +171,7 @@ public void FieldsCanBeSet() {
171171
[Fact]
172172
public void FieldsDefaultToCorrectValues() {
173173
var reader = new BufferedReader(string.Empty);
174-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
174+
var character = Character.Parse(reader, "42", genesDB);
175175
Assert.Null(character.Country);
176176
Assert.Equal(string.Empty, character.Culture);
177177
Assert.Equal(string.Empty, character.Religion);
@@ -198,6 +198,45 @@ public void FieldsDefaultToCorrectValues() {
198198
Assert.Null(character.ProvinceId);
199199
}
200200

201+
[Fact]
202+
public void NegativeAgeIsClampedAndInvalidBirthDateKeepsDefault() {
203+
var reader = new BufferedReader("""
204+
23={
205+
first_name_loc={
206+
name="Lagos"
207+
}
208+
country=272
209+
home_country=272
210+
province=5544
211+
age=-17819
212+
birth_date=-1518605856
213+
attributes={
214+
martial=7
215+
finesse=8
216+
charisma=2
217+
zeal=4
218+
}
219+
family=6
220+
dna="Wc1ZzbN0s3TDhcOFApACkAJvAm8CfwJ/AncCdwJ0AnQCdgJ2AoACgAKMAowCmAKYAnECcQJ3AncCfgJ+ApcClwJ3AncClQKVAm4CbgJzAnMCkQKRA6sDqwJtAm0CeQJ5AokCiQJ/An8CgwKDAmsCawJ6AnoBUgFSAnUCdQKSApICagJqAoUChQKEAoQCkwKTA5kDmQKDAoMCcwJzAmsCawJtAm0EzQTNAn8CfwOjA6MCaAJoApMCkwKAAoABWwFbApgCmAKRApECZwJnAo8CjwKQApAChwKHAoQChAJpAmkCiAKIAo4CjgI7AjsBPAE8AOEA4QBwAHABcwFzACMAIwFbAVsAvAC8AQgBCAFDAUMBBgEGAUQBRAB/AH8AoQChAK8ArwBLAEsAHAAcAMEAwQDLAMsBVgFWATIBMgB/AH8AswCzADoAOgEqASoBfQF9AKwArAFPAU8C6wLrA3MDcwC0ALQBQAFAAIUAhQNYA1gH7wfvAH8AfwSNBI0FcQVxAOsA6wViBWICYwJjAioCKgAAAAAAAAAAAAAAAA=="
221+
traits={
222+
"lagids" "prominent"
223+
}
224+
children={
225+
42 47
226+
}
227+
culture="macedonian"
228+
ethnicity="greek"
229+
religion="roman_pantheon"
230+
death_date=433.6.7
231+
}
232+
""");
233+
234+
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "23", genesDB);
235+
236+
character.Age.Should().Be(0);
237+
character.BirthDate.Should().Be(new Date("1.1.1"));
238+
}
239+
201240
[Fact]
202241
public void CultureCanBeInheritedFromFamily() {
203242
var familyReader = new BufferedReader(
@@ -206,8 +245,8 @@ public void CultureCanBeInheritedFromFamily() {
206245
var characterReader = new BufferedReader(
207246
"= { family = 42 }"
208247
);
209-
var family = ImperatorToCK3.Imperator.Families.Family.Parse(familyReader, 42);
210-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(characterReader, "69", genesDB);
248+
var family = Family.Parse(familyReader, 42);
249+
var character = Character.Parse(characterReader, "69", genesDB);
211250
character.Family = family;
212251
Assert.Equal("paradoxian", character.Culture);
213252
}
@@ -218,7 +257,7 @@ public void PortraitDataIsNotExtractedFromEmptyDNAString() {
218257
// ReSharper disable once StringLiteralTypo
219258
"={dna=\"\"}"
220259
);
221-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
260+
var character = Character.Parse(reader, "42", genesDB);
222261
Assert.Null(character.PortraitData);
223262
}
224263
[Fact]
@@ -227,7 +266,7 @@ public void ColorPaletteCoordinatesCanBeExtractedFromDNA() {
227266
// ReSharper disable once StringLiteralTypo
228267
"={dna=\"AAAAAAAAAAAAAAAAAH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AfwB/AH8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\"}"
229268
);
230-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
269+
var character = Character.Parse(reader, "42", genesDB);
231270

232271
Assert.Equal(0, character.PortraitData!.HairColorPaletteCoordinates.X);
233272
Assert.Equal(0, character.PortraitData.HairColorPaletteCoordinates.Y);
@@ -261,10 +300,10 @@ public void AgeSexReturnsCorrectString() {
261300
"\tage=8\n" +
262301
"}"
263302
);
264-
var character1 = ImperatorToCK3.Imperator.Characters.Character.Parse(reader1, "42", genesDB);
265-
var character2 = ImperatorToCK3.Imperator.Characters.Character.Parse(reader2, "43", genesDB);
266-
var character3 = ImperatorToCK3.Imperator.Characters.Character.Parse(reader3, "44", genesDB);
267-
var character4 = ImperatorToCK3.Imperator.Characters.Character.Parse(reader4, "45", genesDB);
303+
var character1 = Character.Parse(reader1, "42", genesDB);
304+
var character2 = Character.Parse(reader2, "43", genesDB);
305+
var character3 = Character.Parse(reader3, "44", genesDB);
306+
var character4 = Character.Parse(reader4, "45", genesDB);
268307
Assert.Equal("female", character1.AgeSex);
269308
Assert.Equal("male", character2.AgeSex);
270309
Assert.Equal("girl", character3.AgeSex);
@@ -276,7 +315,7 @@ public void AUC0ConvertsTo754BC() {
276315
var reader = new BufferedReader(
277316
"= { birth_date = 0.1.1 }"
278317
);
279-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
318+
var character = Character.Parse(reader, "42", genesDB);
280319

281320
Assert.Equal("-754.1.1", character.BirthDate.ToString());
282321
}
@@ -286,7 +325,7 @@ public void AUC753ConvertsTo1BC() {
286325
var reader = new BufferedReader(
287326
"= { birth_date = 753.1.1 }"
288327
);
289-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
328+
var character = Character.Parse(reader, "42", genesDB);
290329

291330
Assert.Equal("-1.1.1", character.BirthDate.ToString());
292331
}
@@ -296,28 +335,31 @@ public void AUC754ConvertsTo1AD() {
296335
var reader = new BufferedReader(
297336
"= { birth_date = 754.1.1 }"
298337
);
299-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(reader, "42", genesDB);
338+
var character = Character.Parse(reader, "42", genesDB);
300339

301340
Assert.Equal("1.1.1", character.BirthDate.ToString());
302341
}
303342

304343
[Fact]
305344
public void IgnoredTokensAreSaved() {
345+
// Ensure that other tests don't interfere with this one by resetting the ignored tokens before we start.
346+
Character.IgnoredTokens.Clear();
347+
306348
var reader1 = new BufferedReader("= { culture=paradoxian ignoredKeyword1=something ignoredKeyword2={} }");
307349
var reader2 = new BufferedReader("= { ignoredKeyword1=stuff ignoredKeyword3=stuff }");
308-
_ = ImperatorToCK3.Imperator.Characters.Character.Parse(reader1, "1", null);
309-
_ = ImperatorToCK3.Imperator.Characters.Character.Parse(reader2, "2", null);
350+
_ = Character.Parse(reader1, "1", null);
351+
_ = Character.Parse(reader2, "2", null);
310352

311353
var expectedIgnoredTokens = new HashSet<string> {
312354
"ignoredKeyword1", "ignoredKeyword2", "ignoredKeyword3"
313355
};
314-
Assert.True(ImperatorToCK3.Imperator.Characters.Character.IgnoredTokens.SetEquals(expectedIgnoredTokens));
356+
Assert.True(Character.IgnoredTokens.SetEquals(expectedIgnoredTokens));
315357
}
316358

317359
[Fact]
318360
public void CountryIsNotLinkedWithoutParsedId() {
319-
var character = new ImperatorToCK3.Imperator.Characters.Character(1);
320-
var countries = new ImperatorToCK3.Imperator.Countries.CountryCollection();
361+
var character = new Character(1);
362+
var countries = new CountryCollection();
321363
character.LinkCountry(countries);
322364
character.LinkHomeCountry(countries);
323365
character.LinkPrisonerHome(countries);
@@ -331,8 +373,8 @@ [Fact] public void LinkingCountryWithNoDefinitionIsLogged() {
331373
Console.SetOut(output);
332374

333375
var characterReader = new BufferedReader("= { country=69 }");
334-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(characterReader, "1", null);
335-
var countries = new ImperatorToCK3.Imperator.Countries.CountryCollection();
376+
var character = Character.Parse(characterReader, "1", null);
377+
var countries = new CountryCollection();
336378
character.LinkCountry(countries);
337379

338380
Assert.Contains("[WARN] Country with ID 69 has no definition!", output.ToString());
@@ -344,8 +386,8 @@ public void LinkingHomeCountryWithNoDefinitionIsLogged() {
344386
Console.SetOut(output);
345387

346388
var characterReader = new BufferedReader("= { home_country=69 }");
347-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(characterReader, "1", null);
348-
var countries = new ImperatorToCK3.Imperator.Countries.CountryCollection();
389+
var character = Character.Parse(characterReader, "1", null);
390+
var countries = new CountryCollection();
349391
character.LinkHomeCountry(countries);
350392

351393
Assert.Contains("[WARN] Country with ID 69 has no definition!", output.ToString());
@@ -357,8 +399,8 @@ public void LinkingPrisonerHomeWithNoDefinitionIsLogged() {
357399
Console.SetOut(output);
358400

359401
var characterReader = new BufferedReader("= { prisoner_home=69 }");
360-
var character = ImperatorToCK3.Imperator.Characters.Character.Parse(characterReader, "1", null);
361-
var countries = new ImperatorToCK3.Imperator.Countries.CountryCollection();
402+
var character = Character.Parse(characterReader, "1", null);
403+
var countries = new CountryCollection();
362404
character.LinkPrisonerHome(countries);
363405

364406
Assert.Contains("[WARN] Country with ID 69 has no definition!", output.ToString());

ImperatorToCK3/Imperator/Characters/Character.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using ImperatorToCK3.CommonUtils.Genes;
77
using ImperatorToCK3.CommonUtils.Map;
88
using Open.Collections;
9+
using System;
910
using System.Collections.Generic;
1011
using System.Collections.Immutable;
1112

@@ -128,7 +129,7 @@ private static void RegisterCharacterKeywords(Parser parser, Character character
128129
parser.RegisterKeyword("spouse", reader => character.parsedSpouseIds = [.. reader.GetULongs()]);
129130
parser.RegisterKeyword("friends", r => SetFriendIds(character, r));
130131
parser.RegisterKeyword("rivals", r => SetRivalIds(character, r));
131-
parser.RegisterKeyword("age", reader => character.Age = (uint)reader.GetInt());
132+
parser.RegisterKeyword("age", reader => character.Age = (uint)Math.Max(0, reader.GetInt()));
132133
parser.RegisterKeyword("birth_date", r => SetBirthDate(character, r));
133134
parser.RegisterKeyword("death_date", r => SetDeathDate(character, r));
134135
parser.RegisterKeyword("death", reader => character.DeathReason = string.Intern(reader.GetString()));
@@ -169,7 +170,12 @@ private static void SetCharacterName(Character character, BufferedReader reader)
169170
}
170171

171172
private static void SetDeathDate(Character character, BufferedReader reader) {
172-
character.DeathDate = new Date(reader.GetString(), AUC: true); // converted to AD
173+
var dateStr = reader.GetString();
174+
try {
175+
character.DeathDate = new Date(dateStr, AUC: true); // converted to AD
176+
} catch (ArgumentOutOfRangeException e) {
177+
Logger.Warn($"Failed to parse death date \"{dateStr}\" for character {character.Id}: {e.Message}");
178+
}
173179
}
174180

175181
private static void SetBirthDate(Character character, BufferedReader reader) {

0 commit comments

Comments
 (0)