From 0313f75abb4531e5fbbdcf1d0eae7edb36023df6 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 10 May 2025 23:31:53 -0400 Subject: [PATCH] Fix how we read/write packed booleans in EXD, add another write test Apparently we were reading packed booleans wrong the whole time (whoops!) They are packed into *bytes*, oops... That is fixed now, and I also expanded write support for packed booleans. It makes the assumption that the byte is always at the end of the row, but I think that's OK for now. I also added a good test-case for this, the PhysicsGroup EXD. It has a lot of rows, and some packed booleans thrown in. The columns also have out-of-order column definitions (as in, their offsets don't increase in order.) --- resources/tests/physicsgroup.exh | Bin 0 -> 118 bytes resources/tests/physicsgroup_1.exd | Bin 0 -> 2008 bytes src/exd.rs | 112 ++++++++++++++++++++++++----- src/exh.rs | 2 +- 4 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 resources/tests/physicsgroup.exh create mode 100644 resources/tests/physicsgroup_1.exd diff --git a/resources/tests/physicsgroup.exh b/resources/tests/physicsgroup.exh new file mode 100644 index 0000000000000000000000000000000000000000..21e9d1ac0aeb561bcd60e9bc3080a7b8946fcb1c GIT binary patch literal 118 zcmXBNy$(cR5QX8_Pq7k(gu;qKq1a*zg#w?JxCHlqC0@+r$(icRb{;zkDv2fHsZ^#u9= literal 0 HcmV?d00001 diff --git a/resources/tests/physicsgroup_1.exd b/resources/tests/physicsgroup_1.exd new file mode 100644 index 0000000000000000000000000000000000000000..49e2d1d7215a848c89d0013b242ad13735ff2a8d GIT binary patch literal 2008 zcmeH{yHUeH5Qa~ZdB5M7h!O}$k%2Kp1Ox;WKmh~<6hJ@$1egjafq()C%7bfr;R^T%n z%U=-;>+C1sEu4fIoFZfqPQx=e1MlH1e1mgHZNqtZ0T}i%YYgK(qL?;=+x5#9^gY)SWG-Nhka|!7+ zO5D>h*7l>&kXFBX=)HZ5)j>85v-`e8gYRh(+5PkyL-h7vqqDBQ9%lZ*J@pzT?y2sB X?uZS$AK(ANWCZ`hzt!;dNR-DXY}551 literal 0 HcmV?d00001 diff --git a/src/exd.rs b/src/exd.rs index 284a40f..55bbbf3 100644 --- a/src/exd.rs +++ b/src/exd.rs @@ -135,14 +135,63 @@ fn write_rows(rows: &Vec, exh: &EXH) -> BinResult<()> { // write column data { let mut write_row = |row: &ExcelSingleRow| { - for (i, column) in row.columns.iter().enumerate() { - EXD::write_column(writer, &column, &exh.column_definitions[i]); + let mut column_definitions: Vec<(ExcelColumnDefinition, ColumnData)> = exh + .column_definitions + .clone() + .into_iter() + .zip(row.columns.clone().into_iter()) + .collect::>(); - // TODO: temporary workaround until i can figure out why it has 4 extra bytes - if exh.column_definitions[i].data_type == ColumnDataType::Int8 { + // we need to sort them by offset + column_definitions.sort_by(|(a, _), (b, _)| a.offset.cmp(&b.offset)); + + for (definition, column) in &column_definitions { + EXD::write_column(writer, &column, &definition); + + // TODO: temporary workaround until i can figure out why it has 4 extra bytes in test_write's case + if definition.data_type == ColumnDataType::Int8 && column_definitions.len() == 1 + { 0u32.write_le(writer).unwrap(); } } + + // handle packed bools + let mut packed_byte = 0u8; + let mut byte_offset = 0; + + let mut write_packed_bool = + |definition: &ExcelColumnDefinition, shift: i32, boolean: &bool| { + byte_offset = definition.offset; // NOTE: it looks like there is only one byte for all of the packed booleans + + if *boolean { + let bit = 1 << shift; + packed_byte |= bit; + } + }; + + for (definition, column) in &column_definitions { + match &column { + ColumnData::Bool(val) => match definition.data_type { + ColumnDataType::PackedBool0 => write_packed_bool(definition, 0, val), + ColumnDataType::PackedBool1 => write_packed_bool(definition, 1, val), + ColumnDataType::PackedBool2 => write_packed_bool(definition, 2, val), + ColumnDataType::PackedBool3 => write_packed_bool(definition, 3, val), + ColumnDataType::PackedBool4 => write_packed_bool(definition, 4, val), + ColumnDataType::PackedBool5 => write_packed_bool(definition, 5, val), + ColumnDataType::PackedBool6 => write_packed_bool(definition, 6, val), + ColumnDataType::PackedBool7 => write_packed_bool(definition, 7, val), + _ => {} // not relevant + }, + _ => {} // not relevant + } + } + + // write the new packed boolean byte + // NOTE: This is a terrible way to check if there are packed booleans + // NOTE: Assumption: the packed boolean is always at the end of the row + if byte_offset != 0 { + packed_byte.write_le(writer).unwrap(); + } }; match &row.kind { @@ -394,7 +443,7 @@ impl EXD { ) -> Option { let mut read_packed_bool = |shift: i32| -> bool { let bit = 1 << shift; - let bool_data: i32 = Self::read_data_raw(cursor).unwrap_or(0); + let bool_data: u8 = Self::read_data_raw(cursor).unwrap_or(0); (bool_data & bit) == bit }; @@ -462,11 +511,6 @@ impl EXD { column: &ColumnData, column_definition: &ExcelColumnDefinition, ) { - let write_packed_bool = |cursor: &mut T, shift: i32, boolean: &bool| { - let val = 0i32; // TODO - Self::write_data_raw(cursor, &val); - }; - match column { ColumnData::String(_) => { let string_offset = 0u32; // TODO, but 0 is fine for single string column data @@ -474,14 +518,15 @@ impl EXD { } ColumnData::Bool(val) => match column_definition.data_type { ColumnDataType::Bool => todo!(), - ColumnDataType::PackedBool0 => write_packed_bool(cursor, 0, val), - ColumnDataType::PackedBool1 => write_packed_bool(cursor, 1, val), - ColumnDataType::PackedBool2 => write_packed_bool(cursor, 2, val), - ColumnDataType::PackedBool3 => write_packed_bool(cursor, 3, val), - ColumnDataType::PackedBool4 => write_packed_bool(cursor, 4, val), - ColumnDataType::PackedBool5 => write_packed_bool(cursor, 5, val), - ColumnDataType::PackedBool6 => write_packed_bool(cursor, 6, val), - ColumnDataType::PackedBool7 => write_packed_bool(cursor, 7, val), + // packed bools are handled in write_rows + ColumnDataType::PackedBool0 => {} + ColumnDataType::PackedBool1 => {} + ColumnDataType::PackedBool2 => {} + ColumnDataType::PackedBool3 => {} + ColumnDataType::PackedBool4 => {} + ColumnDataType::PackedBool5 => {} + ColumnDataType::PackedBool6 => {} + ColumnDataType::PackedBool7 => {} _ => panic!("This makes no sense!"), }, ColumnData::Int8(val) => Self::write_data_raw(cursor, val), @@ -595,7 +640,7 @@ mod tests { ); // row 1 - assert_eq!(exd.rows[1].row_id, 1441693); + assert_eq!(exd.rows[1].row_id, 1441793); assert_eq!( exd.rows[1].kind, ExcelRowKind::SingleRow(ExcelSingleRow { @@ -801,4 +846,33 @@ mod tests { let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap(); assert_eq!(actual_exd_bytes, expected_exd_bytes); } + + // this doesn't have any strings, but a LOT of columns and some packed booleans! + #[test] + fn test_write_many_columns() { + // exh + let exh; + { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/tests"); + d.push("physicsgroup.exh"); + + exh = EXH::from_existing(&read(d).unwrap()).unwrap(); + } + + // exd + let expected_exd_bytes; + let expected_exd; + { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/tests"); + d.push("physicsgroup_1.exd"); + + expected_exd_bytes = read(d).unwrap(); + expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap(); + } + + let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap(); + assert_eq!(actual_exd_bytes, expected_exd_bytes); + } } diff --git a/src/exh.rs b/src/exh.rs index a02756f..17846ed 100644 --- a/src/exh.rs +++ b/src/exh.rs @@ -27,7 +27,7 @@ pub struct EXHHeader { pub(crate) page_count: u16, pub(crate) language_count: u16, - #[br(dbg)] + /// Usually 0 pub unk1: u16, #[br(temp)]