physis/resource/mod.rs
1// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4mod resolver;
5pub use resolver::ResourceResolver;
6
7mod sqpack;
8pub use sqpack::SqPackResource;
9
10mod unpacked;
11pub use unpacked::UnpackedResource;
12
13use crate::{ByteBuffer, common::Language, exd::EXD, exh::EXH, exl::EXL};
14
15/// Represents a source of files for reading.
16/// This abstracts away some of the nitty-gritty of where files come from. These could be coming from a compressed archive like SqPack, unpacked files on disk, or even a network.
17pub trait Resource {
18 /// Reads the file located at `path`. This is returned as an in-memory buffer, and will usually
19 /// have to be further parsed.
20 ///
21 /// # Example
22 ///
23 /// ```should_panic
24 /// # use physis::resource::{Resource, SqPackResource};
25 /// # use std::io::Write;
26 /// # use physis::common::Platform;
27 /// let mut game = SqPackResource::from_existing(Platform::Win32, "SquareEnix/Final Fantasy XIV - A Realm Reborn/game");
28 /// let data = game.read("exd/root.exl").unwrap();
29 ///
30 /// let mut file = std::fs::File::create("root.exl").unwrap();
31 /// file.write(data.as_slice()).unwrap();
32 /// ```
33 fn read(&mut self, path: &str) -> Option<ByteBuffer>;
34
35 /// Checks if a file exists
36 /// While you could abuse `read` to do this, in some Resources they can optimize this since it doesn't read data.
37 ///
38 /// # Example
39 ///
40 /// ```
41 /// # use physis::common::Platform;
42 /// # use physis::resource::{Resource, SqPackResource};
43 /// let mut game = SqPackResource::from_existing(Platform::Win32, "SquareEnix/Final Fantasy XIV - A Realm Reborn/game");
44 /// if game.exists("exd/cid.exl") {
45 /// println!("Cid really does exist!");
46 /// } else {
47 /// println!("Oh noes!");
48 /// }
49 /// ```
50 fn exists(&mut self, path: &str) -> bool;
51}
52
53/// Read an excel sheet by name (e.g. "Achievement")
54pub fn read_excel_sheet_header<T: Resource>(resource: &mut T, name: &str) -> Option<EXH> {
55 let root_exl_file = resource.read("exd/root.exl")?;
56
57 let root_exl = EXL::from_existing(&root_exl_file)?;
58
59 for (row, _) in root_exl.entries {
60 if row == name {
61 let new_filename = name.to_lowercase();
62
63 let path = format!("exd/{new_filename}.exh");
64
65 return EXH::from_existing(&resource.read(&path)?);
66 }
67 }
68
69 None
70}
71
72/// Returns all known sheet names listed in the root list
73pub fn get_all_sheet_names<T: Resource>(resource: &mut T) -> Option<Vec<String>> {
74 let root_exl_file = resource.read("exd/root.exl")?;
75
76 let root_exl = EXL::from_existing(&root_exl_file)?;
77
78 let mut names = vec![];
79 for (row, _) in root_exl.entries {
80 names.push(row);
81 }
82
83 Some(names)
84}
85
86/// Read an excel sheet
87pub fn read_excel_sheet<T: Resource>(
88 resource: &mut T,
89 name: &str,
90 exh: &EXH,
91 language: Language,
92 page: usize,
93) -> Option<EXD> {
94 let exd_path = format!(
95 "exd/{}",
96 EXD::calculate_filename(name, language, &exh.pages[page])
97 );
98
99 let exd_file = resource.read(&exd_path)?;
100
101 EXD::from_existing(exh, &exd_file)
102}