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 DataSectionHeader {
48 pub(crate) size: u32,
50 pub(crate) row_count: u16,
52}
53
54#[binrw]
55#[brw(big)]
56#[allow(dead_code)]
57#[derive(Debug)]
58pub(crate) struct SubRowHeader {
59 pub(crate) subrow_id: u16,
60}
61
62#[binrw]
63#[brw(big)]
64#[allow(dead_code)]
65#[derive(Debug)]
66pub(crate) struct DataSection {
67 pub(crate) header: DataSectionHeader,
69 #[br(temp, count = header.size)]
71 #[bw(ignore)]
72 data: Vec<u8>,
73}
74
75#[binrw]
77#[brw(big)]
78#[allow(dead_code)]
79#[derive(Debug)]
80#[brw(import(exh: &EXH))]
81pub struct EXD {
82 header: EXDHeader,
83
84 #[br(count = header.data_offset_size / core::mem::size_of::<ExcelDataOffset>() as u32)]
85 #[bw(ignore)] data_offsets: Vec<ExcelDataOffset>,
87
88 #[br(parse_with = read_data_sections, args(&header))]
89 #[bw(ignore)] data: Vec<DataSection>,
91
92 #[br(parse_with = parse_rows, args(exh, &data_offsets))]
94 #[bw(write_with = write_rows, args(exh))]
95 pub rows: Vec<ExcelRow>,
96}
97
98#[derive(Debug, Clone, PartialEq)]
99pub enum ColumnData {
100 String(String),
101 Bool(bool),
102 Int8(i8),
103 UInt8(u8),
104 Int16(i16),
105 UInt16(u16),
106 Int32(i32),
107 UInt32(u32),
108 Float32(f32),
109 Int64(i64),
110 UInt64(u64),
111}
112
113impl ColumnData {
114 pub fn into_string(&self) -> Option<&String> {
116 if let ColumnData::String(value) = self {
117 return Some(value);
118 }
119 None
120 }
121
122 pub fn into_bool(&self) -> Option<&bool> {
124 if let ColumnData::Bool(value) = self {
125 return Some(value);
126 }
127 None
128 }
129
130 pub fn into_i8(&self) -> Option<&i8> {
132 if let ColumnData::Int8(value) = self {
133 return Some(value);
134 }
135 None
136 }
137
138 pub fn into_u8(&self) -> Option<&u8> {
140 if let ColumnData::UInt8(value) = self {
141 return Some(value);
142 }
143 None
144 }
145
146 pub fn into_i16(&self) -> Option<&i16> {
148 if let ColumnData::Int16(value) = self {
149 return Some(value);
150 }
151 None
152 }
153
154 pub fn into_u16(&self) -> Option<&u16> {
156 if let ColumnData::UInt16(value) = self {
157 return Some(value);
158 }
159 None
160 }
161
162 pub fn into_i32(&self) -> Option<&i32> {
164 if let ColumnData::Int32(value) = self {
165 return Some(value);
166 }
167 None
168 }
169
170 pub fn into_u32(&self) -> Option<&u32> {
172 if let ColumnData::UInt32(value) = self {
173 return Some(value);
174 }
175 None
176 }
177
178 pub fn into_f32(&self) -> Option<&f32> {
180 if let ColumnData::Float32(value) = self {
181 return Some(value);
182 }
183 None
184 }
185
186 pub fn into_i64(&self) -> Option<&i64> {
188 if let ColumnData::Int64(value) = self {
189 return Some(value);
190 }
191 None
192 }
193
194 pub fn into_u64(&self) -> Option<&u64> {
196 if let ColumnData::UInt64(value) = self {
197 return Some(value);
198 }
199 None
200 }
201}
202
203#[derive(Debug, Clone, PartialEq)]
205pub struct ExcelSingleRow {
206 pub columns: Vec<ColumnData>,
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub enum ExcelRowKind {
211 SingleRow(ExcelSingleRow),
212 SubRows(Vec<(u16, ExcelSingleRow)>),
213}
214
215#[derive(Debug)]
217pub struct ExcelRow {
218 pub row_id: u32,
220 pub kind: ExcelRowKind,
222}
223
224impl EXD {
225 pub fn from_existing(exh: &EXH, buffer: ByteSpan) -> Option<EXD> {
227 EXD::read_args(&mut Cursor::new(&buffer), (exh,)).ok()
228 }
229
230 pub fn get_row(&self, row_id: u32) -> Option<ExcelRowKind> {
232 for row in &self.rows {
233 if row.row_id == row_id {
234 return Some(row.kind.clone());
235 }
236 }
237
238 None
239 }
240
241 pub fn get_subrow(&self, row_id: u32, subrow_id: u16) -> Option<ExcelSingleRow> {
243 for row in &self.rows {
244 if row.row_id == row_id {
245 match &row.kind {
246 ExcelRowKind::SingleRow(_) => {}
247 ExcelRowKind::SubRows(subrows) => {
248 dbg!(subrows);
249 if let Some(subrow) =
250 subrows.iter().filter(|(id, _)| *id == subrow_id).next()
251 {
252 return Some(subrow.1.clone());
253 }
254 }
255 }
256 }
257 }
258
259 None
260 }
261
262 pub fn calculate_filename(
264 name: &str,
265 language: Language,
266 page: &ExcelDataPagination,
267 ) -> String {
268 use crate::common::get_language_code;
269
270 match language {
271 Language::None => {
272 format!("{name}_{}.exd", page.start_id)
273 }
274 lang => {
275 format!("{name}_{}_{}.exd", page.start_id, get_language_code(&lang))
276 }
277 }
278 }
279
280 pub fn write_to_buffer(&self, exh: &EXH) -> Option<ByteBuffer> {
282 let mut buffer = ByteBuffer::new();
283
284 {
285 let cursor = Cursor::new(&mut buffer);
286 let mut writer = BufWriter::new(cursor);
287
288 self.write_args(&mut writer, (exh,)).unwrap();
289 }
290
291 Some(buffer)
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use crate::exh::{EXHHeader, SheetRowKind};
298 use std::fs::read;
299 use std::path::PathBuf;
300
301 use super::*;
302
303 #[test]
304 fn test_invalid() {
305 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
306 d.push("resources/tests");
307 d.push("random");
308
309 let exh = EXH {
310 header: EXHHeader {
311 version: 0,
312 row_size: 0,
313 column_count: 0,
314 page_count: 0,
315 language_count: 0,
316 row_count: 0,
317 unk1: 0,
318 row_kind: SheetRowKind::SingleRow,
319 unk2: 0,
320 unk3: 0,
321 },
322 column_definitions: vec![],
323 pages: vec![],
324 languages: vec![],
325 };
326
327 EXD::from_existing(&exh, &read(d).unwrap());
329 }
330
331 #[test]
333 fn test_read() {
334 let exh;
336 {
337 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
338 d.push("resources/tests");
339 d.push("gcshop.exh");
340
341 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
342 }
343
344 let exd;
346 {
347 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
348 d.push("resources/tests");
349 d.push("gcshop_1441792.exd");
350
351 exd = EXD::from_existing(&exh, &read(d).unwrap()).unwrap();
352 }
353
354 assert_eq!(exd.rows.len(), 4);
355
356 assert_eq!(exd.rows[0].row_id, 1441792);
358 assert_eq!(
359 exd.rows[0].kind,
360 ExcelRowKind::SingleRow(ExcelSingleRow {
361 columns: vec![ColumnData::Int8(0)]
362 })
363 );
364
365 assert_eq!(exd.rows[1].row_id, 1441793);
367 assert_eq!(
368 exd.rows[1].kind,
369 ExcelRowKind::SingleRow(ExcelSingleRow {
370 columns: vec![ColumnData::Int8(1)]
371 })
372 );
373
374 assert_eq!(exd.rows[2].row_id, 1441794);
376 assert_eq!(
377 exd.rows[2].kind,
378 ExcelRowKind::SingleRow(ExcelSingleRow {
379 columns: vec![ColumnData::Int8(2)]
380 })
381 );
382
383 assert_eq!(exd.rows[3].row_id, 1441795);
385 assert_eq!(
386 exd.rows[3].kind,
387 ExcelRowKind::SingleRow(ExcelSingleRow {
388 columns: vec![ColumnData::Int8(3)]
389 })
390 );
391 }
392
393 #[test]
395 fn test_write() {
396 let exh;
398 {
399 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
400 d.push("resources/tests");
401 d.push("gcshop.exh");
402
403 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
404 }
405
406 let expected_exd_bytes;
408 let expected_exd;
409 {
410 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
411 d.push("resources/tests");
412 d.push("gcshop_1441792.exd");
413
414 expected_exd_bytes = read(d).unwrap();
415 expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap();
416 }
417
418 let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
419 assert_eq!(actual_exd_bytes, expected_exd_bytes);
420 }
421
422 #[test]
424 fn test_read_strings() {
425 let exh;
427 {
428 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
429 d.push("resources/tests");
430 d.push("openingsystemdefine.exh");
431
432 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
433 }
434
435 let exd;
437 {
438 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
439 d.push("resources/tests");
440 d.push("openingsystemdefine_0.exd");
441
442 exd = EXD::from_existing(&exh, &read(d).unwrap()).unwrap();
443 }
444
445 assert_eq!(exd.rows.len(), 8);
446
447 assert_eq!(exd.rows[0].row_id, 0);
449 assert_eq!(
450 exd.rows[0].kind,
451 ExcelRowKind::SingleRow(ExcelSingleRow {
452 columns: vec![
453 ColumnData::String("HOWTO_MOVE_AND_CAMERA".to_string()),
454 ColumnData::UInt32(1)
455 ]
456 })
457 );
458
459 assert_eq!(exd.rows[1].row_id, 1);
461 assert_eq!(
462 exd.rows[1].kind,
463 ExcelRowKind::SingleRow(ExcelSingleRow {
464 columns: vec![
465 ColumnData::String("HOWTO_ANNOUNCE_AND_QUEST".to_string()),
466 ColumnData::UInt32(2)
467 ]
468 })
469 );
470
471 assert_eq!(exd.rows[2].row_id, 2);
473 assert_eq!(
474 exd.rows[2].kind,
475 ExcelRowKind::SingleRow(ExcelSingleRow {
476 columns: vec![
477 ColumnData::String("HOWTO_QUEST_REWARD".to_string()),
478 ColumnData::UInt32(11)
479 ]
480 })
481 );
482
483 assert_eq!(exd.rows[3].row_id, 3);
485 assert_eq!(
486 exd.rows[3].kind,
487 ExcelRowKind::SingleRow(ExcelSingleRow {
488 columns: vec![
489 ColumnData::String("BGM_MUSIC_NO_MUSIC".to_string()),
490 ColumnData::UInt32(1001)
491 ]
492 })
493 );
494
495 assert_eq!(exd.rows[4].row_id, 4);
497 assert_eq!(
498 exd.rows[4].kind,
499 ExcelRowKind::SingleRow(ExcelSingleRow {
500 columns: vec![
501 ColumnData::String("ITEM_INITIAL_RING_A".to_string()),
502 ColumnData::UInt32(4423)
503 ]
504 })
505 );
506
507 assert_eq!(exd.rows[5].row_id, 5);
509 assert_eq!(
510 exd.rows[5].kind,
511 ExcelRowKind::SingleRow(ExcelSingleRow {
512 columns: vec![
513 ColumnData::String("ITEM_INITIAL_RING_B".to_string()),
514 ColumnData::UInt32(4424)
515 ]
516 })
517 );
518
519 assert_eq!(exd.rows[6].row_id, 6);
521 assert_eq!(
522 exd.rows[6].kind,
523 ExcelRowKind::SingleRow(ExcelSingleRow {
524 columns: vec![
525 ColumnData::String("ITEM_INITIAL_RING_C".to_string()),
526 ColumnData::UInt32(4425)
527 ]
528 })
529 );
530
531 assert_eq!(exd.rows[7].row_id, 7);
533 assert_eq!(
534 exd.rows[7].kind,
535 ExcelRowKind::SingleRow(ExcelSingleRow {
536 columns: vec![
537 ColumnData::String("ITEM_INITIAL_RING_D".to_string()),
538 ColumnData::UInt32(4426)
539 ]
540 })
541 );
542 }
543
544 #[test]
546 fn test_write_strings() {
547 let exh;
549 {
550 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
551 d.push("resources/tests");
552 d.push("openingsystemdefine.exh");
553
554 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
555 }
556
557 let expected_exd_bytes;
559 let expected_exd;
560 {
561 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
562 d.push("resources/tests");
563 d.push("openingsystemdefine_0.exd");
564
565 expected_exd_bytes = read(d).unwrap();
566 expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap();
567 }
568
569 let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
570 assert_eq!(actual_exd_bytes, expected_exd_bytes);
571 }
572
573 #[test]
575 fn test_write_many_columns() {
576 let exh;
578 {
579 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
580 d.push("resources/tests");
581 d.push("physicsgroup.exh");
582
583 exh = EXH::from_existing(&read(d).unwrap()).unwrap();
584 }
585
586 let expected_exd_bytes;
588 let expected_exd;
589 {
590 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
591 d.push("resources/tests");
592 d.push("physicsgroup_1.exd");
593
594 expected_exd_bytes = read(d).unwrap();
595 expected_exd = EXD::from_existing(&exh, &expected_exd_bytes).unwrap();
596 }
597
598 let actual_exd_bytes = expected_exd.write_to_buffer(&exh).unwrap();
599 assert_eq!(actual_exd_bytes, expected_exd_bytes);
600 }
601}