1#![allow(unused_variables)] use std::io::{BufWriter, Cursor};
7
8use binrw::BinRead;
9use binrw::{BinWrite, binrw};
10
11use crate::common::Language;
12use crate::exd_file_operations::{parse_rows, read_data_sections, write_rows};
13use crate::exh::{EXH, ExcelDataPagination};
14use crate::{ByteBuffer, ByteSpan};
15
16#[binrw]
17#[brw(magic = b"EXDF")]
18#[brw(big)]
19#[allow(dead_code)]
20#[derive(Debug)]
21pub(crate) struct EXDHeader {
22 pub(crate) version: u16,
24 pub(crate) unk1: u16,
26 pub(crate) data_offset_size: u32,
28 #[brw(pad_after = 16)] pub(crate) data_section_size: u32,
31}
32
33#[binrw]
34#[brw(big)]
35#[derive(Debug)]
36pub(crate) struct ExcelDataOffset {
37 pub(crate) row_id: u32,
39 pub(crate) offset: u32,
41}
42
43#[binrw]
44#[brw(big)]
45#[allow(dead_code)]
46#[derive(Debug)]
47pub(crate) struct DataSection {
48 pub(crate) size: u32,
50 pub(crate) row_count: u16,
52 #[br(temp, count = size)]
55 #[bw(ignore)]
56 data: Vec<u8>,
57}
58
59#[binrw]
61#[brw(big)]
62#[allow(dead_code)]
63#[derive(Debug)]
64#[brw(import(exh: &EXH))]
65pub struct EXD {
66 header: EXDHeader,
67
68 #[br(count = header.data_offset_size / core::mem::size_of::<ExcelDataOffset>() as u32)]
69 #[bw(ignore)] data_offsets: Vec<ExcelDataOffset>,
71
72 #[br(parse_with = read_data_sections, args(&header))]
73 #[bw(ignore)] data: Vec<DataSection>,
75
76 #[br(parse_with = parse_rows, args(exh, &data_offsets))]
78 #[bw(write_with = write_rows, args(exh))]
79 pub rows: Vec<ExcelRow>,
80}
81
82#[derive(Debug, Clone, PartialEq)]
83pub enum ColumnData {
84 String(String),
85 Bool(bool),
86 Int8(i8),
87 UInt8(u8),
88 Int16(i16),
89 UInt16(u16),
90 Int32(i32),
91 UInt32(u32),
92 Float32(f32),
93 Int64(i64),
94 UInt64(u64),
95}
96
97impl ColumnData {
98 pub fn into_string(&self) -> Option<&String> {
100 if let ColumnData::String(value) = self {
101 return Some(value);
102 }
103 None
104 }
105
106 pub fn into_bool(&self) -> Option<&bool> {
108 if let ColumnData::Bool(value) = self {
109 return Some(value);
110 }
111 None
112 }
113
114 pub fn into_i8(&self) -> Option<&i8> {
116 if let ColumnData::Int8(value) = self {
117 return Some(value);
118 }
119 None
120 }
121
122 pub fn into_u8(&self) -> Option<&u8> {
124 if let ColumnData::UInt8(value) = self {
125 return Some(value);
126 }
127 None
128 }
129
130 pub fn into_i16(&self) -> Option<&i16> {
132 if let ColumnData::Int16(value) = self {
133 return Some(value);
134 }
135 None
136 }
137
138 pub fn into_u16(&self) -> Option<&u16> {
140 if let ColumnData::UInt16(value) = self {
141 return Some(value);
142 }
143 None
144 }
145
146 pub fn into_i32(&self) -> Option<&i32> {
148 if let ColumnData::Int32(value) = self {
149 return Some(value);
150 }
151 None
152 }
153
154 pub fn into_u32(&self) -> Option<&u32> {
156 if let ColumnData::UInt32(value) = self {
157 return Some(value);
158 }
159 None
160 }
161
162 pub fn into_f32(&self) -> Option<&f32> {
164 if let ColumnData::Float32(value) = self {
165 return Some(value);
166 }
167 None
168 }
169
170 pub fn into_i64(&self) -> Option<&i64> {
172 if let ColumnData::Int64(value) = self {
173 return Some(value);
174 }
175 None
176 }
177
178 pub fn into_u64(&self) -> Option<&u64> {
180 if let ColumnData::UInt64(value) = self {
181 return Some(value);
182 }
183 None
184 }
185}
186
187#[derive(Debug, Clone, PartialEq)]
188pub struct ExcelSingleRow {
189 pub columns: Vec<ColumnData>,
190}
191
192#[derive(Debug, Clone, PartialEq)]
193pub enum ExcelRowKind {
194 SingleRow(ExcelSingleRow),
195 SubRows(Vec<ExcelSingleRow>),
196}
197
198#[derive(Debug)]
200pub struct ExcelRow {
201 pub row_id: u32,
203 pub kind: ExcelRowKind,
205}
206
207impl EXD {
208 pub fn from_existing(exh: &EXH, buffer: ByteSpan) -> Option<EXD> {
210 EXD::read_args(&mut Cursor::new(&buffer), (exh,)).ok()
211 }
212
213 pub fn get_row(&self, row_id: u32) -> Option<ExcelRowKind> {
215 for row in &self.rows {
216 if row.row_id == row_id {
217 return Some(row.kind.clone());
218 }
219 }
220
221 None
222 }
223
224 pub fn calculate_filename(
226 name: &str,
227 language: Language,
228 page: &ExcelDataPagination,
229 ) -> String {
230 use crate::common::get_language_code;
231
232 match language {
233 Language::None => {
234 format!("{name}_{}.exd", page.start_id)
235 }
236 lang => {
237 format!("{name}_{}_{}.exd", page.start_id, get_language_code(&lang))
238 }
239 }
240 }
241
242 pub fn write_to_buffer(&self, exh: &EXH) -> Option<ByteBuffer> {
244 let mut buffer = ByteBuffer::new();
245
246 {
247 let cursor = Cursor::new(&mut buffer);
248 let mut writer = BufWriter::new(cursor);
249
250 self.write_args(&mut writer, (exh,)).unwrap();
251 }
252
253 Some(buffer)
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use crate::exh::EXHHeader;
260 use std::fs::read;
261 use std::path::PathBuf;
262
263 use super::*;
264
265 #[test]
266 fn test_invalid() {
267 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
268 d.push("resources/tests");
269 d.push("random");
270
271 let exh = EXH {
272 header: EXHHeader {
273 version: 0,
274 data_offset: 0,
275 column_count: 0,
276 page_count: 0,
277 language_count: 0,
278 row_count: 0,
279 unk1: 0,
280 },
281 column_definitions: vec![],
282 pages: vec![],
283 languages: vec![],
284 };
285
286 EXD::from_existing(&exh, &read(d).unwrap());
288 }
289
290 #[test]
292 fn test_read() {
293 let exh;
295 {
296 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
297 d.push("resources/tests");
298 d.push("gcshop.exh");
299
300 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
301 }
302
303 let exd;
305 {
306 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
307 d.push("resources/tests");
308 d.push("gcshop_1441792.exd");
309
310 exd = EXD::from_existing(&exh, &read(d).unwrap()).unwrap();
311 }
312
313 assert_eq!(exd.rows.len(), 4);
314
315 assert_eq!(exd.rows[0].row_id, 1441792);
317 assert_eq!(
318 exd.rows[0].kind,
319 ExcelRowKind::SingleRow(ExcelSingleRow {
320 columns: vec![ColumnData::Int8(0)]
321 })
322 );
323
324 assert_eq!(exd.rows[1].row_id, 1441793);
326 assert_eq!(
327 exd.rows[1].kind,
328 ExcelRowKind::SingleRow(ExcelSingleRow {
329 columns: vec![ColumnData::Int8(1)]
330 })
331 );
332
333 assert_eq!(exd.rows[2].row_id, 1441794);
335 assert_eq!(
336 exd.rows[2].kind,
337 ExcelRowKind::SingleRow(ExcelSingleRow {
338 columns: vec![ColumnData::Int8(2)]
339 })
340 );
341
342 assert_eq!(exd.rows[3].row_id, 1441795);
344 assert_eq!(
345 exd.rows[3].kind,
346 ExcelRowKind::SingleRow(ExcelSingleRow {
347 columns: vec![ColumnData::Int8(3)]
348 })
349 );
350 }
351
352 #[test]
354 fn test_write() {
355 let exh;
357 {
358 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
359 d.push("resources/tests");
360 d.push("gcshop.exh");
361
362 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
363 }
364
365 let expected_exd_bytes;
367 let expected_exd;
368 {
369 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
370 d.push("resources/tests");
371 d.push("gcshop_1441792.exd");
372
373 expected_exd_bytes = read(d).unwrap();
374 expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap();
375 }
376
377 let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
378 assert_eq!(actual_exd_bytes, expected_exd_bytes);
379 }
380
381 #[test]
383 fn test_read_strings() {
384 let exh;
386 {
387 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
388 d.push("resources/tests");
389 d.push("openingsystemdefine.exh");
390
391 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
392 }
393
394 let exd;
396 {
397 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
398 d.push("resources/tests");
399 d.push("openingsystemdefine_0.exd");
400
401 exd = EXD::from_existing(&exh, &read(d).unwrap()).unwrap();
402 }
403
404 assert_eq!(exd.rows.len(), 8);
405
406 assert_eq!(exd.rows[0].row_id, 0);
408 assert_eq!(
409 exd.rows[0].kind,
410 ExcelRowKind::SingleRow(ExcelSingleRow {
411 columns: vec![
412 ColumnData::String("HOWTO_MOVE_AND_CAMERA".to_string()),
413 ColumnData::UInt32(1)
414 ]
415 })
416 );
417
418 assert_eq!(exd.rows[1].row_id, 1);
420 assert_eq!(
421 exd.rows[1].kind,
422 ExcelRowKind::SingleRow(ExcelSingleRow {
423 columns: vec![
424 ColumnData::String("HOWTO_ANNOUNCE_AND_QUEST".to_string()),
425 ColumnData::UInt32(2)
426 ]
427 })
428 );
429
430 assert_eq!(exd.rows[2].row_id, 2);
432 assert_eq!(
433 exd.rows[2].kind,
434 ExcelRowKind::SingleRow(ExcelSingleRow {
435 columns: vec![
436 ColumnData::String("HOWTO_QUEST_REWARD".to_string()),
437 ColumnData::UInt32(11)
438 ]
439 })
440 );
441
442 assert_eq!(exd.rows[3].row_id, 3);
444 assert_eq!(
445 exd.rows[3].kind,
446 ExcelRowKind::SingleRow(ExcelSingleRow {
447 columns: vec![
448 ColumnData::String("BGM_MUSIC_NO_MUSIC".to_string()),
449 ColumnData::UInt32(1001)
450 ]
451 })
452 );
453
454 assert_eq!(exd.rows[4].row_id, 4);
456 assert_eq!(
457 exd.rows[4].kind,
458 ExcelRowKind::SingleRow(ExcelSingleRow {
459 columns: vec![
460 ColumnData::String("ITEM_INITIAL_RING_A".to_string()),
461 ColumnData::UInt32(4423)
462 ]
463 })
464 );
465
466 assert_eq!(exd.rows[5].row_id, 5);
468 assert_eq!(
469 exd.rows[5].kind,
470 ExcelRowKind::SingleRow(ExcelSingleRow {
471 columns: vec![
472 ColumnData::String("ITEM_INITIAL_RING_B".to_string()),
473 ColumnData::UInt32(4424)
474 ]
475 })
476 );
477
478 assert_eq!(exd.rows[6].row_id, 6);
480 assert_eq!(
481 exd.rows[6].kind,
482 ExcelRowKind::SingleRow(ExcelSingleRow {
483 columns: vec![
484 ColumnData::String("ITEM_INITIAL_RING_C".to_string()),
485 ColumnData::UInt32(4425)
486 ]
487 })
488 );
489
490 assert_eq!(exd.rows[7].row_id, 7);
492 assert_eq!(
493 exd.rows[7].kind,
494 ExcelRowKind::SingleRow(ExcelSingleRow {
495 columns: vec![
496 ColumnData::String("ITEM_INITIAL_RING_D".to_string()),
497 ColumnData::UInt32(4426)
498 ]
499 })
500 );
501 }
502
503 #[test]
505 fn test_write_strings() {
506 let exh;
508 {
509 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
510 d.push("resources/tests");
511 d.push("openingsystemdefine.exh");
512
513 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
514 }
515
516 let expected_exd_bytes;
518 let expected_exd;
519 {
520 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
521 d.push("resources/tests");
522 d.push("openingsystemdefine_0.exd");
523
524 expected_exd_bytes = read(d).unwrap();
525 expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap();
526 }
527
528 let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
529 assert_eq!(actual_exd_bytes, expected_exd_bytes);
530 }
531
532 #[test]
534 fn test_write_many_columns() {
535 let exh;
537 {
538 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
539 d.push("resources/tests");
540 d.push("physicsgroup.exh");
541
542 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
543 }
544
545 let expected_exd_bytes;
547 let expected_exd;
548 {
549 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
550 d.push("resources/tests");
551 d.push("physicsgroup_1.exd");
552
553 expected_exd_bytes = read(d).unwrap();
554 expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap();
555 }
556
557 let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
558 assert_eq!(actual_exd_bytes, expected_exd_bytes);
559 }
560}