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