Skip to content

Commit 5566d3d

Browse files
committed
Support new binary i64 data type from HOI4 1.13
This new data type can be seen in HOI4 1.13 saves as ``` manpower = { ratio = 81900 } ``` This datatype technically conflicts with some detected tokens in Imperator and Vic3 (`playbackrate`), but I believe this token to be unused and shouldn't pose too much of a problem. I determined that it was signed 64bit by editing the binary to turn all bits on and I saw the output as -1. This is a breaking change as `BinaryToken` is receiving an addition.
1 parent 8d2e048 commit 5566d3d

File tree

4 files changed

+88
-2
lines changed

4 files changed

+88
-2
lines changed

src/bin/stats.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ struct Stats {
1818
f64: u32,
1919
token: u32,
2020
rgb: u32,
21+
i64: u32,
2122
}
2223

2324
impl Stats {
@@ -31,6 +32,7 @@ impl Stats {
3132
BinaryToken::Bool(_) => self.bool += 1,
3233
BinaryToken::U32(_) => self.u32 += 1,
3334
BinaryToken::U64(_) => self.u64 += 1,
35+
BinaryToken::I64(_) => self.i64 += 1,
3436
BinaryToken::I32(_) => self.i32 += 1,
3537
BinaryToken::Quoted(_) => self.quoted += 1,
3638
BinaryToken::Unquoted(_) => self.unquoted += 1,
@@ -56,7 +58,8 @@ impl std::fmt::Display for Stats {
5658
+ self.f32
5759
+ self.f64
5860
+ self.token
59-
+ self.rgb;
61+
+ self.rgb
62+
+ self.i64;
6063

6164
let total = total as f64;
6265

@@ -168,6 +171,15 @@ impl std::fmt::Display for Stats {
168171
)?;
169172
}
170173

174+
if self.i64 != 0 {
175+
writeln!(
176+
f,
177+
"i64:\t\t{:<8}({:.2}%)",
178+
self.i64,
179+
(self.i64 as f64) / total * 100.0
180+
)?;
181+
}
182+
171183
writeln!(f, "total:\t\t{:<8}", total)?;
172184

173185
Ok(())

src/binary/de.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ impl<'data> OndemandParser<'data> {
6969
Ok(u64::from_le_bytes(head))
7070
}
7171

72+
#[inline]
73+
fn read_i64(&mut self) -> Result<i64, Error> {
74+
let (head, rest) = get_split::<8>(self.data).ok_or_else(Error::eof)?;
75+
self.data = rest;
76+
Ok(i64::from_le_bytes(head))
77+
}
78+
7279
#[inline]
7380
fn read_i32(&mut self) -> Result<i32, Error> {
7481
let (head, rest) = get_split::<4>(self.data).ok_or_else(Error::eof)?;
@@ -109,6 +116,10 @@ impl<'data> OndemandParser<'data> {
109116
self.read_u64()?;
110117
Ok(())
111118
}
119+
I64 => {
120+
self.read_i64()?;
121+
Ok(())
122+
}
112123
BOOL => {
113124
self.read_bool()?;
114125
Ok(())
@@ -143,6 +154,9 @@ impl<'data> OndemandParser<'data> {
143154
U64 => {
144155
self.read_u64()?;
145156
}
157+
I64 => {
158+
self.read_i64()?;
159+
}
146160
BOOL => {
147161
self.read_bool()?;
148162
}
@@ -397,6 +411,7 @@ where
397411
U32 => visitor.visit_u32(self.de.parser.read_u32()?),
398412
I32 => visitor.visit_i32(self.de.parser.read_i32()?),
399413
U64 => visitor.visit_u64(self.de.parser.read_u64()?),
414+
I64 => visitor.visit_i64(self.de.parser.read_i64()?),
400415
BOOL => visitor.visit_bool(self.de.parser.read_bool()?),
401416
F32 => visitor.visit_f32(self.de.config.flavor.visit_f32(self.de.parser.read_f32()?)),
402417
F64 => visitor.visit_f64(self.de.config.flavor.visit_f64(self.de.parser.read_f64()?)),
@@ -440,7 +455,6 @@ impl<'a, 'de: 'a, 'res: 'de, RES: TokenResolver, F: BinaryFlavor> de::Deserializ
440455
deserialize_scalar!(deserialize_any);
441456
deserialize_scalar!(deserialize_i8);
442457
deserialize_scalar!(deserialize_i16);
443-
deserialize_scalar!(deserialize_i64);
444458
deserialize_scalar!(deserialize_u8);
445459
deserialize_scalar!(deserialize_u16);
446460
deserialize_scalar!(deserialize_char);
@@ -492,6 +506,17 @@ impl<'a, 'de: 'a, 'res: 'de, RES: TokenResolver, F: BinaryFlavor> de::Deserializ
492506
}
493507
}
494508

509+
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
510+
where
511+
V: Visitor<'de>,
512+
{
513+
if self.token == I64 {
514+
visitor.visit_i64(self.de.parser.read_i64()?)
515+
} else {
516+
Ok(self.deser(visitor)?)
517+
}
518+
}
519+
495520
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
496521
where
497522
V: Visitor<'de>,
@@ -1104,6 +1129,7 @@ fn visit_key<'b, 'de: 'b, 'res: 'de, RES: TokenResolver, F: BinaryFlavor, V: Vis
11041129
BinaryToken::Bool(x) => visitor.visit_bool(x),
11051130
BinaryToken::U32(x) => visitor.visit_u32(x),
11061131
BinaryToken::U64(x) => visitor.visit_u64(x),
1132+
BinaryToken::I64(x) => visitor.visit_i64(x),
11071133
BinaryToken::I32(x) => visitor.visit_i32(x),
11081134
BinaryToken::Quoted(x) | BinaryToken::Unquoted(x) => {
11091135
match config.flavor.decode(x.as_bytes()) {
@@ -1643,6 +1669,24 @@ mod tests {
16431669
assert_eq!(actual, MyStruct { field1: 128 });
16441670
}
16451671

1672+
#[test]
1673+
fn test_i64_event() {
1674+
let data = [
1675+
0x6b, 0x32, 0x01, 0x00, 0x17, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1676+
];
1677+
1678+
#[derive(Deserialize, PartialEq, Eq, Debug)]
1679+
struct MyStruct {
1680+
field1: i64,
1681+
}
1682+
1683+
let mut map = HashMap::new();
1684+
map.insert(0x326b, String::from("field1"));
1685+
1686+
let actual: MyStruct = from_slice(&data[..], &map).unwrap();
1687+
assert_eq!(actual, MyStruct { field1: -1 });
1688+
}
1689+
16461690
#[test]
16471691
fn test_f32_event() {
16481692
let data = [0x82, 0x2d, 0x01, 0x00, 0x0d, 0x00, 0x17, 0x00, 0x00, 0x00];

src/binary/tape.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub enum BinaryToken<'a> {
3636
/// Represents a binary unsigned 64bit integer
3737
U64(u64),
3838

39+
/// Represents a binary signed 64bit integer
40+
I64(i64),
41+
3942
/// Represents a binary signed 32bit integer
4043
I32(i32),
4144

@@ -173,6 +176,14 @@ impl<'a, 'b> ParserState<'a, 'b> {
173176
Ok(rest)
174177
}
175178

179+
#[inline]
180+
fn parse_i64(&mut self, data: &'a [u8]) -> Result<&'a [u8], Error> {
181+
let (head, rest) = get_split::<8>(data).ok_or_else(Error::eof)?;
182+
let val = i64::from_le_bytes(head);
183+
self.token_tape.alloc().init(BinaryToken::I64(val));
184+
Ok(rest)
185+
}
186+
176187
#[inline]
177188
fn parse_i32(&mut self, data: &'a [u8]) -> Result<&'a [u8], Error> {
178189
let (head, rest) = get_split::<4>(data).ok_or_else(Error::eof)?;
@@ -613,6 +624,9 @@ impl<'a, 'b> ParserState<'a, 'b> {
613624
state = ParseState::ObjectValue;
614625
} else {
615626
let len = self.token_tape.len();
627+
if len == 0 {
628+
panic!("EEEK");
629+
}
616630
unsafe { ptr.add(len).write(BinaryToken::MixedContainer) };
617631
unsafe { ptr.add(len + 1).write(last) };
618632
unsafe { ptr.add(len + 2).write(BinaryToken::Equal) };
@@ -627,6 +641,10 @@ impl<'a, 'b> ParserState<'a, 'b> {
627641
data = self.parse_rgb(d)?;
628642
state = ParseState::Key;
629643
}
644+
I64 => {
645+
data = self.parse_i64(d)?;
646+
state = Self::next_state(state);
647+
}
630648
x => {
631649
data = d;
632650
self.token_tape.alloc().init(BinaryToken::Token(x));
@@ -1210,6 +1228,17 @@ mod tests {
12101228
);
12111229
}
12121230

1231+
#[test]
1232+
fn test_i64() {
1233+
let data = [
1234+
0x6b, 0x32, 0x01, 0x00, 0x17, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1235+
];
1236+
assert_eq!(
1237+
parse(&data).unwrap().token_tape,
1238+
vec![BinaryToken::Token(0x326b), BinaryToken::I64(-1),]
1239+
);
1240+
}
1241+
12131242
#[test]
12141243
fn test_nested_arrays() {
12151244
let data = [

src/binary/tokens.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pub(crate) const UNQUOTED_STRING: u16 = 0x0017;
1010
pub(crate) const F32: u16 = 0x000d;
1111
pub(crate) const F64: u16 = 0x0167;
1212
pub(crate) const RGB: u16 = 0x0243;
13+
pub(crate) const I64: u16 = 0x0317;

0 commit comments

Comments
 (0)