firefly_rust/
fs.rs

1//! Access file system: the game ROM files and the data dir.
2
3use crate::graphics::*;
4#[cfg(feature = "alloc")]
5use alloc::boxed::Box;
6#[cfg(feature = "alloc")]
7use alloc::vec;
8
9/// Like [File] but owns the buffer.
10///
11/// Returned by [`rom::load_buf`] and [`data::load_buf`]. Requires a global allocator.
12/// For a file of statically-known size, you might want to use [`rom::load`]
13/// and [`data::load`] instead.
14#[cfg(feature = "alloc")]
15pub struct FileBuf {
16    pub(crate) raw: Box<[u8]>,
17}
18
19#[cfg(feature = "alloc")]
20impl FileBuf {
21    /// Access the raw data in the file.
22    #[must_use]
23    pub fn data(&self) -> &[u8] {
24        &self.raw
25    }
26
27    /// Alias for [`FileBuf::data`].
28    #[must_use]
29    pub fn as_bytes(&self) -> &[u8] {
30        &self.raw
31    }
32
33    /// Interpret the file as a font.
34    #[must_use]
35    pub fn as_font(&'_ self) -> Font<'_> {
36        Font { raw: &self.raw }
37    }
38
39    /// Interpret the file as an image.
40    #[must_use]
41    pub fn as_image(&'_ self) -> Image<'_> {
42        Image { raw: &self.raw }
43    }
44
45    #[must_use]
46    pub fn into_vec(self) -> alloc::vec::Vec<u8> {
47        self.raw.into_vec()
48    }
49}
50
51#[cfg(feature = "alloc")]
52impl From<FileBuf> for Box<[u8]> {
53    fn from(value: FileBuf) -> Self {
54        value.raw
55    }
56}
57
58#[cfg(feature = "alloc")]
59impl From<FileBuf> for alloc::vec::Vec<u8> {
60    fn from(value: FileBuf) -> Self {
61        value.into_vec()
62    }
63}
64
65#[cfg(feature = "alloc")]
66impl TryFrom<FileBuf> for alloc::string::String {
67    type Error = alloc::string::FromUtf8Error;
68
69    fn try_from(value: FileBuf) -> Result<Self, Self::Error> {
70        let v = value.into_vec();
71        alloc::string::String::from_utf8(v)
72    }
73}
74
75/// A file loaded from ROM or data dir into the memory.
76///
77/// Returned by [`rom::load`] and [`data::load`] which requires a pre-allocated buffer
78/// of the right size. If the file size is deterimed dynamically,
79/// you might want to use [`rom::load_buf`] and [`data::load_buf`] instead
80/// (which will take care of the dynamic allocation).
81pub struct File<'a> {
82    pub(crate) raw: &'a [u8],
83}
84
85impl<'a> File<'a> {
86    /// Construct [`File`] from raw bytes.
87    ///
88    /// The main purpose of this function is to support convering [`File`]
89    /// to and from "basic" types, which might be required for implementing
90    /// some language interpreters (Lua, Python, etc) for Firefly in Rust.
91    ///
92    /// ## Safety
93    ///
94    /// This function allows bypassing type safety and constructing [`Image`]
95    /// and [`Font`] in runtime. Don't do that. Relying on internal representation
96    /// of file formats might make your app incompatible with future Firefly runtimes.
97    /// If you need to modify an in-memory image, use [`Canvas`] instead.
98    #[must_use]
99    pub unsafe fn from_bytes(b: &'a [u8]) -> Self {
100        Self { raw: b }
101    }
102
103    /// Access the raw data in the file.
104    #[must_use]
105    pub const fn data(&self) -> &[u8] {
106        self.raw
107    }
108
109    /// Alias for [`File::data`].
110    #[must_use]
111    pub fn as_bytes(&self) -> &[u8] {
112        self.raw
113    }
114
115    /// Interpret the file as a font.
116    #[must_use]
117    pub const fn as_font(&'_ self) -> Font<'_> {
118        Font { raw: self.raw }
119    }
120
121    /// Interpret the file as an image.
122    #[must_use]
123    pub const fn as_image(&'_ self) -> Image<'_> {
124        Image { raw: self.raw }
125    }
126}
127
128/// Get a file size in the data dir.
129///
130/// If the file does not exist, 0 is returned.
131#[must_use]
132pub fn get_file_size(name: &str) -> usize {
133    let path_ptr = name.as_ptr();
134    let size = unsafe { bindings::get_file_size(path_ptr as u32, name.len() as u32) };
135    size as usize
136}
137
138/// Read the whole file with the given name into the given buffer.
139///
140/// If the file size is not known in advance (and so the buffer has to be allocated
141/// dynamically), consider using [`load_file_buf`] instead.
142pub fn load_file<'a>(name: &str, buf: &'a mut [u8]) -> File<'a> {
143    let path_ptr = name.as_ptr();
144    let buf_ptr = buf.as_mut_ptr();
145    unsafe {
146        bindings::load_file(
147            path_ptr as u32,
148            name.len() as u32,
149            buf_ptr as u32,
150            buf.len() as u32,
151        );
152    }
153    File { raw: buf }
154}
155
156/// Read the whole file with the given name.
157///
158/// If you have a pre-allocated buffer of the right size, use [`load_file`] instead.
159///
160/// `None` is returned if the file does not exist.
161#[cfg(feature = "alloc")]
162#[must_use]
163pub fn load_file_buf(name: &str) -> Option<FileBuf> {
164    let size = get_file_size(name);
165    if size == 0 {
166        return None;
167    }
168    let mut buf = vec![0; size];
169    load_file(name, &mut buf);
170    Some(FileBuf {
171        raw: buf.into_boxed_slice(),
172    })
173}
174
175/// Write the buffer into the given file in the data dir.
176///
177/// If the file exists, it will be overwritten.
178/// If it doesn't exist, it will be created.
179pub fn dump_file(name: &str, buf: &[u8]) {
180    let path_ptr = name.as_ptr();
181    let buf_ptr = buf.as_ptr();
182    unsafe {
183        bindings::dump_file(
184            path_ptr as u32,
185            name.len() as u32,
186            buf_ptr as u32,
187            buf.len() as u32,
188        );
189    }
190}
191
192/// Remove file (if exists) with the given name from the data dir.
193pub fn remove_file(name: &str) {
194    let path_ptr = name.as_ptr();
195    unsafe {
196        bindings::remove_file(path_ptr as u32, name.len() as u32);
197    }
198}
199
200mod bindings {
201    #[link(wasm_import_module = "fs")]
202    unsafe extern "C" {
203        pub(crate) fn get_file_size(path_ptr: u32, path_len: u32) -> u32;
204        pub(crate) fn load_file(path_ptr: u32, path_len: u32, buf_ptr: u32, buf_len: u32) -> u32;
205        pub(crate) fn dump_file(path_ptr: u32, path_len: u32, buf_ptr: u32, buf_len: u32) -> u32;
206        pub(crate) fn remove_file(path_ptr: u32, path_len: u32);
207    }
208}