1#![allow(clippy::unnecessary_fallible_conversions)] #![allow(unused_variables)] use std::io::BufWriter;
8use std::io::Cursor;
9
10use binrw::BinRead;
11use binrw::BinWrite;
12use binrw::binrw;
13
14use crate::ByteBuffer;
15use crate::ByteSpan;
16use crate::common::Language;
17
18#[binrw]
19#[brw(magic = b"EXHF")]
20#[brw(big)]
21#[derive(Debug)]
22pub struct EXHHeader {
23 pub(crate) version: u16,
24
25 pub data_offset: u16, pub(crate) column_count: u16,
27 pub(crate) page_count: u16,
28 pub(crate) language_count: u16,
29
30 pub unk1: u16,
32
33 #[br(temp)]
34 #[bw(calc = 0x010000)] pub unk2: u32,
36
37 #[brw(pad_after = 8)] pub row_count: u32,
39}
40
41#[binrw]
42#[brw(repr(u16))]
43#[repr(u16)]
44#[derive(PartialEq, Eq, Clone, Copy, Debug)]
45pub enum ColumnDataType {
46 String = 0x0,
47 Bool = 0x1,
48 Int8 = 0x2,
49 UInt8 = 0x3,
50 Int16 = 0x4,
51 UInt16 = 0x5,
52 Int32 = 0x6,
53 UInt32 = 0x7,
54 Float32 = 0x9,
55 Int64 = 0xA,
56 UInt64 = 0xB,
57
58 PackedBool0 = 0x19,
59 PackedBool1 = 0x1A,
60 PackedBool2 = 0x1B,
61 PackedBool3 = 0x1C,
62 PackedBool4 = 0x1D,
63 PackedBool5 = 0x1E,
64 PackedBool6 = 0x1F,
65 PackedBool7 = 0x20,
66}
67
68#[binrw]
69#[brw(big)]
70#[derive(Debug, Copy, Clone)]
71pub struct ExcelColumnDefinition {
72 pub data_type: ColumnDataType,
73 pub offset: u16,
74}
75
76#[binrw]
77#[brw(big)]
78#[allow(dead_code)]
79#[derive(Debug)]
80pub struct ExcelDataPagination {
81 pub start_id: u32,
82 pub row_count: u32,
83}
84
85#[binrw]
86#[brw(big)]
87#[allow(dead_code)]
88#[derive(Debug)]
89pub struct EXH {
90 pub header: EXHHeader,
91
92 #[br(count = header.column_count)]
93 pub column_definitions: Vec<ExcelColumnDefinition>,
94
95 #[br(count = header.page_count)]
96 pub pages: Vec<ExcelDataPagination>,
97
98 #[br(count = header.language_count)]
99 #[brw(pad_after = 1)] pub languages: Vec<Language>,
101}
102
103impl EXH {
104 pub fn from_existing(buffer: ByteSpan) -> Option<EXH> {
105 EXH::read(&mut Cursor::new(&buffer)).ok()
106 }
107
108 pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
109 let mut buffer = ByteBuffer::new();
110
111 {
112 let cursor = Cursor::new(&mut buffer);
113 let mut writer = BufWriter::new(cursor);
114
115 self.write_args(&mut writer, ()).unwrap();
116 }
117
118 Some(buffer)
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use std::fs::read;
125 use std::path::PathBuf;
126
127 use super::*;
128
129 #[test]
130 fn test_invalid() {
131 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
132 d.push("resources/tests");
133 d.push("random");
134
135 EXH::from_existing(&read(d).unwrap());
137 }
138
139 #[test]
141 fn test_read() {
142 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
143 d.push("resources/tests");
144 d.push("gcshop.exh");
145
146 let exh = EXH::from_existing(&read(d).unwrap()).unwrap();
147
148 assert_eq!(exh.header.version, 3);
150 assert_eq!(exh.header.data_offset, 4);
151 assert_eq!(exh.header.column_count, 1);
152 assert_eq!(exh.header.page_count, 1);
153 assert_eq!(exh.header.language_count, 1);
154 assert_eq!(exh.header.row_count, 4);
155
156 assert_eq!(exh.column_definitions.len(), 1);
158 assert_eq!(exh.column_definitions[0].data_type, ColumnDataType::Int8);
159 assert_eq!(exh.column_definitions[0].offset, 0);
160
161 assert_eq!(exh.pages.len(), 1);
163 assert_eq!(exh.pages[0].start_id, 1441792);
164 assert_eq!(exh.pages[0].row_count, 4);
165
166 assert_eq!(exh.languages.len(), 1);
168 assert_eq!(exh.languages[0], Language::None);
169 }
170
171 #[test]
173 fn test_write() {
174 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
175 d.push("resources/tests");
176 d.push("gcshop.exh");
177
178 let expected_exh_bytes = read(d).unwrap();
179 let expected_exh = EXH::from_existing(&expected_exh_bytes).unwrap();
180
181 let actual_exh_bytes = expected_exh.write_to_buffer().unwrap();
182
183 assert_eq!(actual_exh_bytes, expected_exh_bytes);
184 }
185}