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    /// Interpret the file as a font.
28    #[must_use]
29    pub fn as_font(&'_ self) -> Font<'_> {
30        Font { raw: &self.raw }
31    }
32
33    /// Interpret the file as an image.
34    #[must_use]
35    pub fn as_image(&'_ self) -> Image<'_> {
36        Image { raw: &self.raw }
37    }
38
39    #[must_use]
40    pub fn into_vec(self) -> alloc::vec::Vec<u8> {
41        self.raw.into_vec()
42    }
43}
44
45#[cfg(feature = "alloc")]
46impl From<FileBuf> for Box<[u8]> {
47    fn from(value: FileBuf) -> Self {
48        value.raw
49    }
50}
51
52#[cfg(feature = "alloc")]
53impl From<FileBuf> for alloc::vec::Vec<u8> {
54    fn from(value: FileBuf) -> Self {
55        value.into_vec()
56    }
57}
58
59#[cfg(feature = "alloc")]
60impl TryFrom<FileBuf> for alloc::string::String {
61    type Error = alloc::string::FromUtf8Error;
62
63    fn try_from(value: FileBuf) -> Result<Self, Self::Error> {
64        let v = value.into_vec();
65        alloc::string::String::from_utf8(v)
66    }
67}
68
69/// A file loaded from ROM or data dir into the memory.
70///
71/// Returned by [`rom::load`] and [`data::load`] which requires a pre-allocated buffer
72/// of the right size. If the file size is deterimed dynamically,
73/// you might want to use [`rom::load_buf`] and [`data::load_buf`] instead
74/// (which will take care of the dynamic allocation).
75pub struct File<'a> {
76    pub(crate) raw: &'a [u8],
77}
78
79impl File<'_> {
80    #[must_use]
81    pub const fn data(&self) -> &[u8] {
82        self.raw
83    }
84
85    #[must_use]
86    pub const fn as_font(&'_ self) -> Font<'_> {
87        Font { raw: self.raw }
88    }
89
90    #[must_use]
91    pub const fn as_image(&'_ self) -> Image<'_> {
92        Image { raw: self.raw }
93    }
94}
95
96/// Get a file size in the data dir.
97///
98/// If the file does not exist, 0 is returned.
99#[must_use]
100pub fn get_file_size(name: &str) -> usize {
101    let path_ptr = name.as_ptr();
102    let size = unsafe { bindings::get_file_size(path_ptr as u32, name.len() as u32) };
103    size as usize
104}
105
106/// Read the whole file with the given name into the given buffer.
107///
108/// If the file size is not known in advance (and so the buffer has to be allocated
109/// dynamically), consider using [`load_file_buf`] instead.
110pub fn load_file<'a>(name: &str, buf: &'a mut [u8]) -> File<'a> {
111    let path_ptr = name.as_ptr();
112    let buf_ptr = buf.as_mut_ptr();
113    unsafe {
114        bindings::load_file(
115            path_ptr as u32,
116            name.len() as u32,
117            buf_ptr as u32,
118            buf.len() as u32,
119        );
120    }
121    File { raw: buf }
122}
123
124/// Read the whole file with the given name.
125///
126/// If you have a pre-allocated buffer of the right size, use [`load_file`] instead.
127///
128/// `None` is returned if the file does not exist.
129#[cfg(feature = "alloc")]
130#[must_use]
131pub fn load_file_buf(name: &str) -> Option<FileBuf> {
132    let size = get_file_size(name);
133    if size == 0 {
134        return None;
135    }
136    let mut buf = vec![0; size];
137    load_file(name, &mut buf);
138    Some(FileBuf {
139        raw: buf.into_boxed_slice(),
140    })
141}
142
143/// Write the buffer into the given file in the data dir.
144///
145/// If the file exists, it will be overwritten.
146/// If it doesn't exist, it will be created.
147pub fn dump_file(name: &str, buf: &[u8]) {
148    let path_ptr = name.as_ptr();
149    let buf_ptr = buf.as_ptr();
150    unsafe {
151        bindings::dump_file(
152            path_ptr as u32,
153            name.len() as u32,
154            buf_ptr as u32,
155            buf.len() as u32,
156        );
157    }
158}
159
160/// Remove file (if exists) with the given name from the data dir.
161pub fn remove_file(name: &str) {
162    let path_ptr = name.as_ptr();
163    unsafe {
164        bindings::remove_file(path_ptr as u32, name.len() as u32);
165    }
166}
167
168/// A loaded font file.
169///
170/// Can be loaded as [`FileBuf`] from ROM with [`rom::load_buf`]
171/// and then cast using [Into].
172pub struct Font<'a> {
173    pub(crate) raw: &'a [u8],
174}
175
176impl<'a> From<File<'a>> for Font<'a> {
177    fn from(value: File<'a>) -> Self {
178        Self { raw: value.raw }
179    }
180}
181
182#[cfg(feature = "alloc")]
183impl<'a> From<&'a FileBuf> for Font<'a> {
184    fn from(value: &'a FileBuf) -> Self {
185        Self { raw: &value.raw }
186    }
187}
188
189mod bindings {
190    #[link(wasm_import_module = "fs")]
191    extern "C" {
192        pub(crate) fn get_file_size(path_ptr: u32, path_len: u32) -> u32;
193        pub(crate) fn load_file(path_ptr: u32, path_len: u32, buf_ptr: u32, buf_len: u32) -> u32;
194        pub(crate) fn dump_file(path_ptr: u32, path_len: u32, buf_ptr: u32, buf_len: u32) -> u32;
195        pub(crate) fn remove_file(path_ptr: u32, path_len: u32);
196    }
197}