mirror of
https://github.com/redstrate/Physis.git
synced 2025-05-16 23:17:45 +00:00
Extend EXD writing to support strings, add new test cases
Now we can re-create OpeningSystemDefine, and is added as a new test case to test reading/writing string columns. This was actually quite fun, it turns out that they like aligning the data sections to a 4-byte boundary for some reason.
This commit is contained in:
parent
36304a2e57
commit
dae35ecfb8
3 changed files with 192 additions and 22 deletions
BIN
resources/tests/openingsystemdefine.exh
Normal file
BIN
resources/tests/openingsystemdefine.exh
Normal file
Binary file not shown.
BIN
resources/tests/openingsystemdefine_0.exd
Normal file
BIN
resources/tests/openingsystemdefine_0.exd
Normal file
Binary file not shown.
212
src/exd.rs
212
src/exd.rs
|
@ -16,12 +16,14 @@ use crate::{ByteBuffer, ByteSpan};
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct EXDHeader {
|
struct EXDHeader {
|
||||||
|
/// Usually 2, I don't think I've seen any other version
|
||||||
version: u16,
|
version: u16,
|
||||||
|
/// Seems to be 0?
|
||||||
unk1: u16,
|
unk1: u16,
|
||||||
/// Size of the ExcelDataOffset array
|
/// Size of the data offsets in bytes
|
||||||
index_size: u32,
|
data_offset_size: u32,
|
||||||
#[brw(pad_after = 16)] // padding
|
#[brw(pad_after = 16)] // padding
|
||||||
/// Total size of the string data?
|
/// Size of the data sections in bytes
|
||||||
data_section_size: u32,
|
data_section_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,18 +125,23 @@ fn write_rows(rows: &Vec<ExcelRow>, exh: &EXH) -> BinResult<()> {
|
||||||
offset: writer.stream_position().unwrap() as u32,
|
offset: writer.stream_position().unwrap() as u32,
|
||||||
});
|
});
|
||||||
|
|
||||||
let row_header = DataSection {
|
// skip row header for now, because we don't know the size yet!
|
||||||
size: 6, // TODO: hardcoded
|
let row_header_pos = writer.stream_position().unwrap();
|
||||||
row_count: 1, // TODO: hardcoded
|
|
||||||
data: Vec::new(), // TOOD: not used here
|
writer.seek(SeekFrom::Current(6)).unwrap(); // u32 + u16
|
||||||
};
|
|
||||||
row_header.write(writer).unwrap();
|
let old_pos = writer.stream_position().unwrap();
|
||||||
|
|
||||||
// write column data
|
// write column data
|
||||||
{
|
{
|
||||||
let mut write_row = |row: &ExcelSingleRow| {
|
let mut write_row = |row: &ExcelSingleRow| {
|
||||||
for (i, column) in row.columns.iter().enumerate() {
|
for (i, column) in row.columns.iter().enumerate() {
|
||||||
EXD::write_column(writer, &column, &exh.column_definitions[i]);
|
EXD::write_column(writer, &column, &exh.column_definitions[i]);
|
||||||
|
|
||||||
|
// TODO: temporary workaround until i can figure out why it has 4 extra bytes
|
||||||
|
if exh.column_definitions[i].data_type == ColumnDataType::Int8 {
|
||||||
|
0u32.write_le(writer).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,11 +153,6 @@ fn write_rows(rows: &Vec<ExcelRow>, exh: &EXH) -> BinResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: temporary padding for now
|
|
||||||
for _ in 0..4 {
|
|
||||||
0u8.write_le(writer).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write strings at the end of column data
|
// write strings at the end of column data
|
||||||
|
@ -161,6 +163,9 @@ fn write_rows(rows: &Vec<ExcelRow>, exh: &EXH) -> BinResult<()> {
|
||||||
ColumnData::String(val) => {
|
ColumnData::String(val) => {
|
||||||
let bytes = val.as_bytes();
|
let bytes = val.as_bytes();
|
||||||
bytes.write(writer).unwrap();
|
bytes.write(writer).unwrap();
|
||||||
|
|
||||||
|
// nul terminator
|
||||||
|
0u8.write_le(writer).unwrap();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -177,10 +182,29 @@ fn write_rows(rows: &Vec<ExcelRow>, exh: &EXH) -> BinResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// There's an empty byte between each row... for some reason
|
// aligned to the next 4 byte boundary
|
||||||
|
let boundary_pos = writer.stream_position().unwrap();
|
||||||
|
let remainder = (boundary_pos + 4 - 1) / 4 * 4;
|
||||||
|
for _ in 0..remainder - boundary_pos {
|
||||||
0u8.write_le(writer).unwrap();
|
0u8.write_le(writer).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let new_pos = writer.stream_position().unwrap();
|
||||||
|
|
||||||
|
// write row header
|
||||||
|
writer.seek(SeekFrom::Start(row_header_pos)).unwrap();
|
||||||
|
|
||||||
|
let row_header = DataSection {
|
||||||
|
size: (new_pos - old_pos) as u32,
|
||||||
|
row_count: 1, // TODO: hardcoded
|
||||||
|
data: Vec::new(), // NOTE: not used here
|
||||||
|
};
|
||||||
|
row_header.write(writer).unwrap();
|
||||||
|
|
||||||
|
// restore pos
|
||||||
|
writer.seek(SeekFrom::Start(new_pos)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// now write the data offsets
|
// now write the data offsets
|
||||||
writer.seek(SeekFrom::Start(data_offsets_pos)).unwrap();
|
writer.seek(SeekFrom::Start(data_offsets_pos)).unwrap();
|
||||||
data_offsets.write(writer).unwrap();
|
data_offsets.write(writer).unwrap();
|
||||||
|
@ -193,7 +217,6 @@ fn write_rows(rows: &Vec<ExcelRow>, exh: &EXH) -> BinResult<()> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DataSection {
|
pub struct DataSection {
|
||||||
#[br(dbg)]
|
|
||||||
size: u32,
|
size: u32,
|
||||||
row_count: u16,
|
row_count: u16,
|
||||||
#[br(count = size)]
|
#[br(count = size)]
|
||||||
|
@ -207,15 +230,12 @@ pub struct DataSection {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[brw(import(exh: &EXH))]
|
#[brw(import(exh: &EXH))]
|
||||||
pub struct EXD {
|
pub struct EXD {
|
||||||
#[br(dbg)]
|
|
||||||
header: EXDHeader,
|
header: EXDHeader,
|
||||||
|
|
||||||
#[br(count = header.index_size / core::mem::size_of::<ExcelDataOffset>() as u32)]
|
#[br(count = header.data_offset_size / core::mem::size_of::<ExcelDataOffset>() as u32)]
|
||||||
#[bw(ignore)] // write_rows handles writing this
|
#[bw(ignore)] // write_rows handles writing this
|
||||||
#[br(dbg)]
|
|
||||||
data_offsets: Vec<ExcelDataOffset>,
|
data_offsets: Vec<ExcelDataOffset>,
|
||||||
|
|
||||||
#[br(dbg)]
|
|
||||||
#[br(parse_with = read_data_sections, args(&header))]
|
#[br(parse_with = read_data_sections, args(&header))]
|
||||||
#[bw(ignore)] // write_rows handles writing this
|
#[bw(ignore)] // write_rows handles writing this
|
||||||
data: Vec<DataSection>,
|
data: Vec<DataSection>,
|
||||||
|
@ -575,7 +595,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// row 1
|
// row 1
|
||||||
assert_eq!(exd.rows[1].row_id, 1441793);
|
assert_eq!(exd.rows[1].row_id, 1441693);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
exd.rows[1].kind,
|
exd.rows[1].kind,
|
||||||
ExcelRowKind::SingleRow(ExcelSingleRow {
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
@ -628,7 +648,157 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
|
let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
|
||||||
|
assert_eq!(actual_exd_bytes, expected_exd_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slightly more complex to read, because it has STRINGS
|
||||||
|
#[test]
|
||||||
|
fn test_read_strings() {
|
||||||
|
// exh
|
||||||
|
let exh;
|
||||||
|
{
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests");
|
||||||
|
d.push("openingsystemdefine.exh");
|
||||||
|
|
||||||
|
exh = EXH::from_existing(&read(d).unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// exd
|
||||||
|
let exd;
|
||||||
|
{
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests");
|
||||||
|
d.push("openingsystemdefine_0.exd");
|
||||||
|
|
||||||
|
exd = EXD::from_existing(&exh, &read(d).unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(exd.rows.len(), 8);
|
||||||
|
|
||||||
|
// row 0
|
||||||
|
assert_eq!(exd.rows[0].row_id, 0);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[0].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("HOWTO_MOVE_AND_CAMERA".to_string()),
|
||||||
|
ColumnData::UInt32(1)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 1
|
||||||
|
assert_eq!(exd.rows[1].row_id, 1);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[1].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("HOWTO_ANNOUNCE_AND_QUEST".to_string()),
|
||||||
|
ColumnData::UInt32(2)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 2
|
||||||
|
assert_eq!(exd.rows[2].row_id, 2);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[2].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("HOWTO_QUEST_REWARD".to_string()),
|
||||||
|
ColumnData::UInt32(11)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 3
|
||||||
|
assert_eq!(exd.rows[3].row_id, 3);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[3].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("BGM_MUSIC_NO_MUSIC".to_string()),
|
||||||
|
ColumnData::UInt32(1001)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 4
|
||||||
|
assert_eq!(exd.rows[4].row_id, 4);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[4].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("ITEM_INITIAL_RING_A".to_string()),
|
||||||
|
ColumnData::UInt32(4423)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 5
|
||||||
|
assert_eq!(exd.rows[5].row_id, 5);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[5].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("ITEM_INITIAL_RING_B".to_string()),
|
||||||
|
ColumnData::UInt32(4424)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 6
|
||||||
|
assert_eq!(exd.rows[6].row_id, 6);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[6].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("ITEM_INITIAL_RING_C".to_string()),
|
||||||
|
ColumnData::UInt32(4425)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// row 7
|
||||||
|
assert_eq!(exd.rows[7].row_id, 7);
|
||||||
|
assert_eq!(
|
||||||
|
exd.rows[7].kind,
|
||||||
|
ExcelRowKind::SingleRow(ExcelSingleRow {
|
||||||
|
columns: vec![
|
||||||
|
ColumnData::String("ITEM_INITIAL_RING_D".to_string()),
|
||||||
|
ColumnData::UInt32(4426)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// slightly more complex to write, because it has STRINGS
|
||||||
|
#[test]
|
||||||
|
fn test_write_strings() {
|
||||||
|
// exh
|
||||||
|
let exh;
|
||||||
|
{
|
||||||
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
d.push("resources/tests");
|
||||||
|
d.push("openingsystemdefine.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("openingsystemdefine_0.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);
|
assert_eq!(actual_exd_bytes, expected_exd_bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue