use jomini::{binary::BinaryFlavor, BinaryTape, BinaryToken, Encoding, Utf8Encoding};
pub(crate) fn reencode_float(f: f64) -> f64 {
let f = f * 1000.0;
let eps = f64::from(f32::EPSILON);
let num = (f / 32768.0 * 100_000.0 + (eps * f.signum())).trunc();
num / 100_000.0
}
pub(crate) trait Ck3BinaryFlavor: BinaryFlavor + jomini::Encoding {
fn float_reencoding(&self) -> bool;
fn unquote_token(&self, token: &str) -> bool;
}
impl<T: Ck3BinaryFlavor + ?Sized> Ck3BinaryFlavor for Box<T> {
fn float_reencoding(&self) -> bool {
(**self).float_reencoding()
}
fn unquote_token(&self, token: &str) -> bool {
(**self).unquote_token(token)
}
}
pub(crate) fn flavor_from_tape(tape: &BinaryTape) -> Box<dyn Ck3BinaryFlavor> {
match tape.tokens() {
[_, _, BinaryToken::Token(1423), BinaryToken::I32(x), ..] if *x > 5 => {
Box::new(Ck3Flavor15::new())
}
_ => Box::new(Ck3Flavor10::new()),
}
}
#[derive(Debug, Default)]
pub struct Ck3Flavor15(Utf8Encoding);
impl Ck3Flavor15 {
pub fn new() -> Self {
Ck3Flavor15(Utf8Encoding::new())
}
}
impl Encoding for Ck3Flavor15 {
fn decode<'a>(&self, data: &'a [u8]) -> std::borrow::Cow<'a, str> {
self.0.decode(data)
}
}
impl Ck3BinaryFlavor for Ck3Flavor15 {
fn float_reencoding(&self) -> bool {
false
}
fn unquote_token(&self, token: &str) -> bool {
matches!(
token,
"save_game_version"
| "portraits_version"
| "meta_date"
| "color1"
| "color2"
| "color3"
| "color4"
| "color5"
| "traits_lookup"
| "features"
| "modifiers"
| "traditions"
| "name_list"
| "localization_key"
)
}
}
impl BinaryFlavor for Ck3Flavor15 {
fn visit_f32(&self, data: [u8; 4]) -> f32 {
f32::from_bits(u32::from_le_bytes(data))
}
fn visit_f64(&self, data: [u8; 8]) -> f64 {
let x = i64::from_le_bytes(data) as f64;
let eps = f64::from(f32::EPSILON);
(x + (eps * x.signum())).trunc() / 100_000.0
}
}
#[derive(Debug, Default)]
pub struct Ck3Flavor10(Utf8Encoding);
impl Ck3Flavor10 {
pub fn new() -> Self {
Ck3Flavor10(Utf8Encoding::new())
}
}
impl Encoding for Ck3Flavor10 {
fn decode<'a>(&self, data: &'a [u8]) -> std::borrow::Cow<'a, str> {
self.0.decode(data)
}
}
impl BinaryFlavor for Ck3Flavor10 {
fn visit_f32(&self, data: [u8; 4]) -> f32 {
f32::from_bits(u32::from_le_bytes(data))
}
fn visit_f64(&self, data: [u8; 8]) -> f64 {
i64::from_le_bytes(data) as f64 / 1000.0
}
}
impl Ck3BinaryFlavor for Ck3Flavor10 {
fn float_reencoding(&self) -> bool {
true
}
fn unquote_token(&self, token: &str) -> bool {
matches!(
token,
"save_game_version"
| "portraits_version"
| "meta_date"
| "color1"
| "color2"
| "color3"
| "color4"
| "color5"
| "traits_lookup"
| "features"
| "modifiers"
| "traditions"
| "name_list"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reencode_accuracy() {
let data: [u8; 8] = [0, 160, 125, 0, 0, 0, 0, 0];
let flavor = Ck3Flavor10::default();
let f = flavor.visit_f64(data);
let newf = reencode_float(f);
assert_eq!(newf, 251.25000);
}
#[test]
fn reencode_accuracy_2() {
let data: [u8; 8] = [6, 193, 0, 0, 0, 0, 0, 0];
let flavor = Ck3Flavor10::default();
let f = flavor.visit_f64(data);
let newf = reencode_float(f);
assert_eq!(newf, 1.50799);
}
#[test]
fn reencode_accuracy_3() {
let data: [u8; 8] = [0, 0, 81, 255, 255, 255, 255, 255];
let flavor = Ck3Flavor10::default();
let f = flavor.visit_f64(data);
let newf = reencode_float(f);
assert_eq!(newf, -350.0);
}
}