#![no_std]
#![forbid(unsafe_code)]
#![allow(clippy::needless_return)]
mod error;
pub use error::*;
mod header;
use header::*;
use core::str;
use core::u32;
use arrayvec::{ArrayString, ArrayVec};
use byte_slice_cast::AsSliceOf;
pub const FILENAME_LEN: usize = 24;
const DIR_ENTRY_LEN: usize = 32;
const NUM_FS_ENTRIES: usize = 2048;
type Filename = ArrayString<FILENAME_LEN>;
#[derive(Debug, Copy, Clone)]
struct GBFSFileEntry {
name: [u8; FILENAME_LEN],
len: u32,
data_offset: u32,
}
impl GBFSFileEntry {
fn name_is_equal(&self, name: Filename) -> Result<bool, GBFSError> {
let no_nulls: ArrayVec<u8, { FILENAME_LEN }> =
self.name.iter().filter(|x| **x != 0).copied().collect();
let filename_str: &str = match str::from_utf8(no_nulls.as_ref()) {
Ok(s) => s,
Err(e) => return Err(GBFSError::Utf8Error(e)),
};
match Filename::from(filename_str) {
Err(_) => return Err(GBFSError::FilenameTooLong(FILENAME_LEN, filename_str.len())),
Ok(our_name) => return Ok(name == our_name),
}
}
}
#[repr(align(4))]
#[repr(C)]
#[derive(Clone)]
pub struct GBFSFilesystem<'a> {
data: &'a [u8],
hdr: GBFSHeader,
dir: [Option<GBFSFileEntry>; NUM_FS_ENTRIES],
}
impl<'a> GBFSFilesystem<'a> {
pub const fn from_slice(data: &'a [u8]) -> Result<GBFSFilesystem<'a>, GBFSError> {
let hdr: GBFSHeader;
if data.len() < header::GBFS_HEADER_LENGTH {
return Err(GBFSError::HeaderInvalid);
}
match GBFSHeader::from_slice(&[
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16],
data[17], data[18], data[19], data[20], data[21], data[22], data[23], data[24],
data[25], data[26], data[27], data[28], data[29], data[30], data[31],
]) {
Ok(val) => hdr = val,
Err(err) => return Err(err),
}
let mut dir_entries: [Option<GBFSFileEntry>; NUM_FS_ENTRIES] = [None; NUM_FS_ENTRIES];
let mut i = 0;
if (hdr.dir_num_members as usize) > NUM_FS_ENTRIES {
return Err(GBFSError::TooManyEntries(
NUM_FS_ENTRIES,
hdr.dir_num_members as usize,
));
}
while i < hdr.dir_num_members as usize {
let entry_start = hdr.dir_off as usize + ((i as usize) * DIR_ENTRY_LEN);
if data.len() < entry_start + FILENAME_LEN {
return Err(GBFSError::Truncated);
}
let filename: [u8; FILENAME_LEN] = [
data[entry_start],
data[entry_start + 1],
data[entry_start + 2],
data[entry_start + 3],
data[entry_start + 4],
data[entry_start + 5],
data[entry_start + 6],
data[entry_start + 7],
data[entry_start + 8],
data[entry_start + 9],
data[entry_start + 10],
data[entry_start + 11],
data[entry_start + 12],
data[entry_start + 13],
data[entry_start + 14],
data[entry_start + 15],
data[entry_start + 16],
data[entry_start + 17],
data[entry_start + 18],
data[entry_start + 19],
data[entry_start + 20],
data[entry_start + 21],
data[entry_start + 22],
data[entry_start + 23],
];
if data.len() < entry_start + FILENAME_LEN + 4 {
return Err(GBFSError::Truncated);
};
let len = u32::from_le_bytes([
data[(entry_start + FILENAME_LEN)],
data[entry_start + FILENAME_LEN + 1],
data[entry_start + FILENAME_LEN + 2],
data[entry_start + FILENAME_LEN + 3],
]);
if data.len() < entry_start + FILENAME_LEN + 8 {
return Err(GBFSError::Truncated);
};
let data_offset = u32::from_le_bytes([
data[(entry_start + FILENAME_LEN + 4)],
data[entry_start + FILENAME_LEN + 5],
data[entry_start + FILENAME_LEN + 6],
data[entry_start + FILENAME_LEN + 7],
]);
dir_entries[i] = Some(GBFSFileEntry {
name: filename,
len,
data_offset,
});
i += 1;
}
return Ok(GBFSFilesystem {
data,
hdr,
dir: dir_entries,
});
}
fn get_file_data_by_index(&self, index: usize) -> &'a [u8] {
let dir_entry_wrapped = self.dir[index];
let dir_entry = dir_entry_wrapped
.expect("Attempt to access file with nonexistent index. This is a bug in gbfs_rs.");
return &self.data[dir_entry.data_offset as usize
..(dir_entry.data_offset as usize + dir_entry.len as usize)];
}
pub fn get_file_data_by_name(&self, str_name: &str) -> Result<&'a [u8], GBFSError> {
let name: Filename;
match Filename::from(str_name) {
Ok(val) => name = val,
Err(_) => return Err(GBFSError::FilenameTooLong(FILENAME_LEN, str_name.len())),
}
for (i, entry) in self.dir.iter().enumerate() {
match entry {
Some(inner_entry) => {
if inner_entry.name_is_equal(name)? {
return Ok(self.get_file_data_by_index(i));
}
}
None => return Err(GBFSError::NoSuchFile(name)),
}
}
return Err(GBFSError::NoSuchFile(name));
}
pub fn get_file_data_by_name_as_u16_slice(&self, name: &str) -> Result<&'a [u16], GBFSError> {
return Ok(self.get_file_data_by_name(name)?.as_slice_of::<u16>()?);
}
pub fn get_file_data_by_name_as_u32_slice(&self, name: &str) -> Result<&'a [u32], GBFSError> {
return Ok(self.get_file_data_by_name(name)?.as_slice_of::<u32>()?);
}
}
impl<'a> IntoIterator for GBFSFilesystem<'a> {
type Item = &'a [u8];
type IntoIter = GBFSFilesystemIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
return GBFSFilesystemIterator {
fs: self,
next_file_index: 0,
};
}
}
pub struct GBFSFilesystemIterator<'a> {
fs: GBFSFilesystem<'a>,
next_file_index: usize,
}
impl<'a> Iterator for GBFSFilesystemIterator<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.next_file_index < self.fs.hdr.dir_num_members as usize {
let ret = Some(self.fs.get_file_data_by_index(self.next_file_index));
self.next_file_index += 1;
return ret;
} else {
return None;
}
}
}