From c5613cd431c9e1a677e6158eff4c3755a95a4737 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 10 May 2025 19:06:06 -0400 Subject: [PATCH] Add support for writing EXH files This is an easy format to add write support for, and we can recreate the "GCShop" EXH which is super simple as it's just one page and one language. --- resources/tests/gcshop.exh | Bin 0 -> 46 bytes src/common.rs | 2 +- src/exd.rs | 1 + src/exh.rs | 73 ++++++++++++++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 resources/tests/gcshop.exh diff --git a/resources/tests/gcshop.exh b/resources/tests/gcshop.exh new file mode 100644 index 0000000000000000000000000000000000000000..287e6394f6f003b3be7945d74d21e29e46a5ba29 GIT binary patch literal 46 icmZ>b@Ni>bW?*4p1VaWO2?8u&5=1Zou^5;K6axS^69HEM literal 0 HcmV?d00001 diff --git a/src/common.rs b/src/common.rs index b49e6b5..bd1d591 100755 --- a/src/common.rs +++ b/src/common.rs @@ -9,7 +9,7 @@ use binrw::binrw; #[binrw] #[brw(repr(u8))] #[repr(u8)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// The language the game data is written for. Some of these languages are supported in the Global region. pub enum Language { /// Used for data that is language-agnostic. diff --git a/src/exd.rs b/src/exd.rs index ac90623..813c1a3 100644 --- a/src/exd.rs +++ b/src/exd.rs @@ -489,6 +489,7 @@ mod tests { page_count: 0, language_count: 0, row_count: 0, + unk1: 0, }, column_definitions: vec![], pages: vec![], diff --git a/src/exh.rs b/src/exh.rs index db80539..4d73742 100644 --- a/src/exh.rs +++ b/src/exh.rs @@ -3,11 +3,14 @@ #![allow(clippy::unnecessary_fallible_conversions)] // This wrongly trips on binrw code +use std::io::BufWriter; use std::io::Cursor; use binrw::BinRead; +use binrw::BinWrite; use binrw::binrw; +use crate::ByteBuffer; use crate::ByteSpan; use crate::common::Language; @@ -24,8 +27,13 @@ pub struct EXHHeader { pub(crate) page_count: u16, pub(crate) language_count: u16, - #[br(pad_before = 6)] - #[br(pad_after = 8)] + pub unk1: u16, + + #[br(temp)] + #[bw(calc = 0x010000)] // always this value?? + pub unk2: u32, + + #[brw(pad_after = 8)] // padding pub row_count: u32, } @@ -87,6 +95,7 @@ pub struct EXH { pub pages: Vec, #[br(count = header.language_count)] + #[brw(pad_after = 1)] // \0 pub languages: Vec, } @@ -94,6 +103,19 @@ impl EXH { pub fn from_existing(buffer: ByteSpan) -> Option { EXH::read(&mut Cursor::new(&buffer)).ok() } + + pub fn write_to_buffer(&self) -> Option { + let mut buffer = ByteBuffer::new(); + + { + let cursor = Cursor::new(&mut buffer); + let mut writer = BufWriter::new(cursor); + + self.write_args(&mut writer, ()).unwrap(); + } + + Some(buffer) + } } #[cfg(test)] @@ -112,4 +134,51 @@ mod tests { // Feeding it invalid data should not panic EXH::from_existing(&read(d).unwrap()); } + + // simple EXH to read, just one page + #[test] + fn test_read() { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/tests"); + d.push("gcshop.exh"); + + let exh = EXH::from_existing(&read(d).unwrap()).unwrap(); + + // header + assert_eq!(exh.header.version, 3); + assert_eq!(exh.header.data_offset, 4); + assert_eq!(exh.header.column_count, 1); + assert_eq!(exh.header.page_count, 1); + assert_eq!(exh.header.language_count, 1); + assert_eq!(exh.header.row_count, 4); + + // column definitions + assert_eq!(exh.column_definitions.len(), 1); + assert_eq!(exh.column_definitions[0].data_type, ColumnDataType::Int8); + assert_eq!(exh.column_definitions[0].offset, 0); + + // pages + assert_eq!(exh.pages.len(), 1); + assert_eq!(exh.pages[0].start_id, 1441792); + assert_eq!(exh.pages[0].row_count, 4); + + // languages + assert_eq!(exh.languages.len(), 1); + assert_eq!(exh.languages[0], Language::None); + } + + // simple EXH to write, only one page + #[test] + fn test_write() { + let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + d.push("resources/tests"); + d.push("gcshop.exh"); + + let expected_exh_bytes = read(d).unwrap(); + let expected_exh = EXH::from_existing(&expected_exh_bytes).unwrap(); + + let actual_exh_bytes = expected_exh.write_to_buffer().unwrap(); + + assert_eq!(actual_exh_bytes, expected_exh_bytes); + } }