// SPDX-FileCopyrightText: 2023 Joshua Goins // SPDX-License-Identifier: GPL-3.0-or-later #![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; #[binrw] #[brw(magic = b"EXHF")] #[brw(big)] #[allow(dead_code)] #[derive(Debug)] pub struct EXHHeader { pub(crate) version: u16, pub data_offset: u16, pub(crate) column_count: u16, pub(crate) page_count: u16, pub(crate) language_count: u16, pub unk1: u16, #[br(temp)] #[bw(calc = 0x010000)] // always this value?? pub unk2: u32, #[brw(pad_after = 8)] // padding pub row_count: u32, } #[binrw] #[brw(repr(u16))] #[repr(u16)] #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum ColumnDataType { String = 0x0, Bool = 0x1, Int8 = 0x2, UInt8 = 0x3, Int16 = 0x4, UInt16 = 0x5, Int32 = 0x6, UInt32 = 0x7, Float32 = 0x9, Int64 = 0xA, UInt64 = 0xB, PackedBool0 = 0x19, PackedBool1 = 0x1A, PackedBool2 = 0x1B, PackedBool3 = 0x1C, PackedBool4 = 0x1D, PackedBool5 = 0x1E, PackedBool6 = 0x1F, PackedBool7 = 0x20, } #[binrw] #[brw(big)] #[derive(Debug, Copy, Clone)] pub struct ExcelColumnDefinition { pub data_type: ColumnDataType, pub offset: u16, } #[binrw] #[brw(big)] #[allow(dead_code)] #[derive(Debug)] pub struct ExcelDataPagination { pub start_id: u32, pub row_count: u32, } #[binrw] #[brw(big)] #[allow(dead_code)] #[derive(Debug)] pub struct EXH { pub header: EXHHeader, #[br(count = header.column_count)] pub column_definitions: Vec, #[br(count = header.page_count)] pub pages: Vec, #[br(count = header.language_count)] #[brw(pad_after = 1)] // \0 pub languages: Vec, } 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)] mod tests { use std::fs::read; use std::path::PathBuf; use super::*; #[test] fn test_invalid() { let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); d.push("resources/tests"); d.push("random"); // 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); } }