Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: serialization issues #45

Merged
merged 2 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/vpx/biff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ impl<'a> BiffReader<'a> {
}

pub fn get_bool(&mut self) -> bool {
let all = &self.data[self.pos..self.pos + 4];
// Any other value is suspicious as it is not a boolean
if all != [0, 0, 0, 0] && all != [1, 0, 0, 0] {
panic!("Unexpected bytes for bool: {:?}", all);
}
let b = self.data[self.pos] != 0;
self.pos += 4;
self.bytes_in_record_remaining -= 4;
Expand Down Expand Up @@ -144,10 +149,16 @@ impl<'a> BiffReader<'a> {
}

pub fn get_f32(&mut self) -> f32 {
let i: Result<(&[u8], f32), nom::Err<()>> = le_f32(&self.data[self.pos..]);
let data = &self.data[self.pos..self.pos + 4];
let i: Result<(&[u8], f32), nom::Err<()>> = le_f32(data);
self.pos += 4;
self.bytes_in_record_remaining -= 4;
i.unwrap().1

let res = i.unwrap().1;
if res.is_nan() {
eprintln!("NaN value found in f32 for tag {}: {:?}", self.tag, data);
}
res
}

pub fn get_str(&mut self, count: usize) -> String {
Expand Down
39 changes: 24 additions & 15 deletions src/vpx/gamedata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::{
};
use crate::vpx::biff::{BiffRead, BiffWrite};
use crate::vpx::color::{Color, ColorJson};
use crate::vpx::json::F32WithNanInf;
use crate::vpx::material::{Material, SaveMaterial, SavePhysicsMaterial};
use crate::vpx::math::{dequantize_u8, quantize_u8};
use crate::vpx::renderprobe::RenderProbeWithGarbage;
Expand Down Expand Up @@ -221,7 +222,11 @@ pub struct GameData {
pub custom_colors: [Color; 16], //[Color; 16], // CCUS 113
pub protection_data: Option<Vec<u8>>, // SECB (removed in ?)
pub code: StringWithEncoding, // CODE 114
pub is_locked: Option<bool>, // TLCK (added in 10.8 for tournament mode?)
/// TLCK (added in 10.8 for tournament mode?)
/// Flag that disables all table edition. Lock toggles are counted to identify
/// version changes in a table (for example to guarantee untouched table for tournament)
/// Used to be a boolean for a while in the 10.8 dev cycle but now is a lock counter.
pub locked: Option<u32>, // TLCK (added in 10.8 for tournament mode?)
// This is a bit of a hack because we want reproducible builds.
// 10.8.0 beta 1-4 had EFSS at the old location, but it was moved to the new location in beta 5
// Some tables were released with these old betas, so we need to support both locations to be 100% reproducing the orignal table
Expand Down Expand Up @@ -281,10 +286,10 @@ pub(crate) struct GameDataJson {
pub bg_layback_full_single_screen: Option<f32>,
pub bg_fov_full_single_screen: Option<f32>,
pub bg_offset_x_full_single_screen: Option<f32>,
pub bg_offset_y_full_single_screen: Option<f32>,
pub bg_offset_y_full_single_screen: Option<F32WithNanInf>,
pub bg_offset_z_full_single_screen: Option<f32>,
pub bg_scale_x_full_single_screen: Option<f32>,
pub bg_scale_y_full_single_screen: Option<f32>,
pub bg_scale_y_full_single_screen: Option<F32WithNanInf>,
pub bg_scale_z_full_single_screen: Option<f32>,
pub bg_view_horizontal_offset_full_single_screen: Option<f32>,
pub bg_view_vertical_offset_full_single_screen: Option<f32>,
Expand Down Expand Up @@ -371,7 +376,7 @@ pub(crate) struct GameDataJson {
pub custom_colors: [ColorJson; 16],
pub protection_data: Option<Vec<u8>>,
//pub code: StringWithEncoding,
pub is_locked: Option<bool>,
pub locked: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_10_8_0_beta1_to_beta4: Option<bool>,
}
Expand Down Expand Up @@ -437,10 +442,10 @@ impl GameDataJson {
bg_layback_full_single_screen: self.bg_layback_full_single_screen,
bg_fov_full_single_screen: self.bg_fov_full_single_screen,
bg_offset_x_full_single_screen: self.bg_offset_x_full_single_screen,
bg_offset_y_full_single_screen: self.bg_offset_y_full_single_screen,
bg_offset_y_full_single_screen: self.bg_offset_y_full_single_screen.map(f32::from),
bg_offset_z_full_single_screen: self.bg_offset_z_full_single_screen,
bg_scale_x_full_single_screen: self.bg_scale_x_full_single_screen,
bg_scale_y_full_single_screen: self.bg_scale_y_full_single_screen,
bg_scale_y_full_single_screen: self.bg_scale_y_full_single_screen.map(f32::from),
bg_scale_z_full_single_screen: self.bg_scale_z_full_single_screen,
bg_view_horizontal_offset_full_single_screen: self
.bg_view_horizontal_offset_full_single_screen,
Expand Down Expand Up @@ -555,7 +560,7 @@ impl GameDataJson {
custom_colors,
protection_data: self.protection_data.clone(),
code: StringWithEncoding::empty(),
is_locked: self.is_locked,
locked: self.locked,
is_10_8_0_beta1_to_beta4: self.is_10_8_0_beta1_to_beta4.unwrap_or(false),
}
}
Expand Down Expand Up @@ -617,10 +622,14 @@ impl GameDataJson {
bg_layback_full_single_screen: game_data.bg_layback_full_single_screen,
bg_fov_full_single_screen: game_data.bg_fov_full_single_screen,
bg_offset_x_full_single_screen: game_data.bg_offset_x_full_single_screen,
bg_offset_y_full_single_screen: game_data.bg_offset_y_full_single_screen,
bg_offset_y_full_single_screen: game_data
.bg_offset_y_full_single_screen
.map(F32WithNanInf::from),
bg_offset_z_full_single_screen: game_data.bg_offset_z_full_single_screen,
bg_scale_x_full_single_screen: game_data.bg_scale_x_full_single_screen,
bg_scale_y_full_single_screen: game_data.bg_scale_y_full_single_screen,
bg_scale_y_full_single_screen: game_data
.bg_scale_y_full_single_screen
.map(F32WithNanInf::from),
bg_scale_z_full_single_screen: game_data.bg_scale_z_full_single_screen,
bg_view_horizontal_offset_full_single_screen: game_data
.bg_view_horizontal_offset_full_single_screen,
Expand Down Expand Up @@ -717,7 +726,7 @@ impl GameDataJson {
custom_colors,
protection_data: game_data.protection_data.clone(),
// code: game_data.code.clone(),
is_locked: game_data.is_locked,
locked: game_data.locked,
is_10_8_0_beta1_to_beta4: Some(game_data.is_10_8_0_beta1_to_beta4)
.filter(|x| x == &true),
}
Expand Down Expand Up @@ -892,7 +901,7 @@ impl Default for GameData {
bg_window_bottom_x_offset_full_single_screen: None,
bg_window_bottom_y_offset_full_single_screen: None,
bg_window_bottom_z_offset_full_single_screen: None,
is_locked: None,
locked: None,
}
}
}
Expand Down Expand Up @@ -1226,8 +1235,8 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec
writer.write_tagged_data("SECB", protection_data);
}
writer.write_tagged_string_with_encoding_no_size("CODE", &gamedata.code);
if let Some(is_locked) = gamedata.is_locked {
writer.write_tagged_bool("TLCK", is_locked);
if let Some(is_locked) = gamedata.locked {
writer.write_tagged_u32("TLCK", is_locked);
}

writer.close(true);
Expand Down Expand Up @@ -1458,7 +1467,7 @@ pub fn read_all_gamedata_records(input: &[u8], version: &Version) -> GameData {
// at least a the time of 1060, some code was still encoded in latin1
gamedata.code = reader.get_str_with_encoding_no_remaining_update(len as usize);
}
"TLCK" => gamedata.is_locked = Some(reader.get_bool()),
"TLCK" => gamedata.locked = Some(reader.get_u32()),
other => {
let data = reader.get_record_data(false);
println!("unhandled tag {} {} bytes", other, data.len());
Expand Down Expand Up @@ -1658,7 +1667,7 @@ mod tests {
bg_window_bottom_x_offset_full_single_screen: None,
bg_window_bottom_y_offset_full_single_screen: None,
bg_window_bottom_z_offset_full_single_screen: None,
is_locked: Some(true),
locked: Faker.fake(),
is_10_8_0_beta1_to_beta4: false,
};
let version = Version::new(1074);
Expand Down
2 changes: 1 addition & 1 deletion src/vpx/gameitem/gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl Default for Gate {
name: Default::default(),
two_way: false,
is_reflection_enabled: None, //true,
gate_type: Some(GateType::Plate),
gate_type: None, //Some(GateType::Plate),
is_locked: false,
editor_layer: Default::default(),
editor_layer_name: None,
Expand Down
12 changes: 11 additions & 1 deletion src/vpx/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ pub(crate) fn read(file_version: &Version, reader: &mut BiffReader) -> SoundData
10
};

// We have seen below case for a 1040 file:
// Legacy behavior, where the BG selection was encoded into the strings directly
// path = "* Backglass Output *" or name contains "bgout_"
// This is still seen as a wav file, even if the path does not end with .wav!

for i in 0..num_values {
match i {
0 => {
Expand Down Expand Up @@ -303,8 +308,13 @@ pub(crate) fn read(file_version: &Version, reader: &mut BiffReader) -> SoundData
}
}

/// Check if the path is a wav file.
/// If the path does not have an extension, it is also considered a wav file!
fn is_wav(path: &str) -> bool {
path.to_lowercase().ends_with(".wav")
match path.rfind('.') {
Some(pos) => path[(pos + 1)..].eq_ignore_ascii_case("wav"),
None => true,
}
}

pub(crate) fn write(file_version: &Version, sound: &SoundData, writer: &mut BiffWriter) {
Expand Down
35 changes: 19 additions & 16 deletions tests/vpx_read_extract_assemble_write_compare_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,35 @@ mod test {
let paths = find_files(&folder, "vpx")?;
// testdir can not be used in non-main threads
let dir: PathBuf = testdir!();

// Example tables caused problems in the past:
//
// * Inhabiting Mars RC 4 - for animation frame
// * DieHard_272.vpx - primitive "BM_pAirDuctGate" has a NaN value for nx
// * Johnny Mnemonic (Williams 1995) VPW v1.0.2.vpx - animated frames that overlap with primitive names

// TODO why is par_iter() not faster but just consuming all cpu cores?
paths
// * Future Spa (Bally 1979) v4.3.vpx - NaN in table setup values
let filtered: Vec<&PathBuf> = paths
.iter()
// .filter(|path| {
// let name = path.file_name().unwrap().to_str().unwrap();
// name.contains("Bob")
// })
.try_for_each(|path| {
println!("testing: {:?}", path);
let ReadAndWriteResult {
extracted,
test_vpx,
} = read_and_write_vpx(&dir, &path)?;
assert_equal_vpx(path, test_vpx.clone());
// panic!("stop");
// if all is good we remove the test file and the extracted dir
std::fs::remove_file(&test_vpx)?;
std::fs::remove_dir_all(&extracted)?;
Ok(())
})
.collect();

// TODO why is par_iter() not faster but just consuming all cpu cores?
filtered.iter().enumerate().try_for_each(|(n, path)| {
println!("testing {}/{}: {:?}", n + 1, filtered.len(), path);
let ReadAndWriteResult {
extracted,
test_vpx,
} = read_and_write_vpx(&dir, &path)?;
assert_equal_vpx(path, test_vpx.clone());
// panic!("stop");
// if all is good we remove the test file and the extracted dir
std::fs::remove_file(&test_vpx)?;
std::fs::remove_dir_all(&extracted)?;
Ok(())
})
}

struct ReadAndWriteResult {
Expand Down