Skip to main content

firefly_rust/
fs.rs

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