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}