From ab84ad75629b0a4124221023ca91411d2cd62a32 Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Tue, 25 Mar 2025 12:46:49 +1300 Subject: Restructure program This commit also includes changes to devices according to the latest devices specification, in particular the math and system devices. --- src/types/buffered_file.rs | 143 ++++++++++++++++++++ src/types/debug_symbols.rs | 59 +++++++++ src/types/directory_listing.rs | 120 +++++++++++++++++ src/types/entry_type.rs | 37 ++++++ src/types/file_path.rs | 288 +++++++++++++++++++++++++++++++++++++++++ src/types/metadata.rs | 125 ++++++++++++++++++ src/types/mod.rs | 19 +++ src/types/path_buffer.rs | 60 +++++++++ src/types/read_buffer.rs | 34 +++++ src/types/sprite_buffer.rs | 85 ++++++++++++ 10 files changed, 970 insertions(+) create mode 100644 src/types/buffered_file.rs create mode 100644 src/types/debug_symbols.rs create mode 100644 src/types/directory_listing.rs create mode 100644 src/types/entry_type.rs create mode 100644 src/types/file_path.rs create mode 100644 src/types/metadata.rs create mode 100644 src/types/mod.rs create mode 100644 src/types/path_buffer.rs create mode 100644 src/types/read_buffer.rs create mode 100644 src/types/sprite_buffer.rs (limited to 'src/types') diff --git a/src/types/buffered_file.rs b/src/types/buffered_file.rs new file mode 100644 index 0000000..5cdf0ea --- /dev/null +++ b/src/types/buffered_file.rs @@ -0,0 +1,143 @@ +use std::fs::File; +use std::io::{BufReader, BufWriter, Read, Write}; +use std::io::{ErrorKind, Seek, SeekFrom}; + + +pub struct BufferedFile { + file: AccessMode, +} + +impl BufferedFile { + pub fn new(file: File) -> Self { + Self { + file: AccessMode::Read(BufReader::new(file)), + } + } + + pub fn close(&mut self) { + self.file = AccessMode::None; + } + + pub fn read(&mut self) -> u8 { + let mut buffer = [0u8; 1]; + + let read_result = match &mut self.file { + AccessMode::Read(reader) => reader.read_exact(&mut buffer), + AccessMode::Write(writer) => { + let address = writer.stream_position().unwrap(); + let file = std::mem::take(&mut self.file).unwrap(); + let mut reader = BufReader::new(file); + reader.seek(SeekFrom::Start(address)).unwrap(); + let read_result = reader.read_exact(&mut buffer); + self.file = AccessMode::Read(reader); + read_result + } + AccessMode::None => unreachable!(), + }; + + match read_result { + Ok(_) => buffer[0], + Err(error) => match error.kind() { + ErrorKind::UnexpectedEof => 0, + _ => { log::error!("BufferedFile::read: {error:?}"); 0 }, + } + } + } + + pub fn write(&mut self, byte: u8) { + let mut buffer = [byte; 1]; + + let write_result = match &mut self.file { + AccessMode::Write(writer) => writer.write_all(&mut buffer), + AccessMode::Read(reader) => { + let address = reader.stream_position().unwrap(); + let file = std::mem::take(&mut self.file).unwrap(); + let mut writer = BufWriter::new(file); + writer.seek(SeekFrom::Start(address)).unwrap(); + let write_result = writer.write_all(&mut buffer); + self.file = AccessMode::Write(writer); + write_result + } + AccessMode::None => unreachable!(), + }; + + write_result.unwrap(); + } + + pub fn pointer(&mut self) -> u32 { + let position = match &mut self.file { + AccessMode::Read(reader) => reader.stream_position(), + AccessMode::Write(writer) => writer.stream_position(), + AccessMode::None => unreachable!(), + }; + u32::try_from(position.unwrap()).unwrap_or(u32::MAX) + } + + pub fn set_pointer(&mut self, pointer: u32) { + let position = SeekFrom::Start(pointer as u64); + match &mut self.file { + AccessMode::Read(reader) => reader.seek(position).unwrap(), + AccessMode::Write(writer) => writer.seek(position).unwrap(), + AccessMode::None => unreachable!(), + }; + } + + pub fn length(&mut self) -> u32 { + let length = match &mut self.file { + AccessMode::Read(reader) => reader.stream_len(), + AccessMode::Write(writer) => writer.stream_len(), + AccessMode::None => unreachable!(), + }; + u32::try_from(length.unwrap()).unwrap_or(u32::MAX) + } + + pub fn set_length(&mut self, length: u32) { + match &mut self.file { + AccessMode::Read(_) => { + let file = std::mem::take(&mut self.file).unwrap(); + file.set_len(length as u64).unwrap(); + self.file = AccessMode::Read(BufReader::new(file)); + } + AccessMode::Write(_) => { + let file = std::mem::take(&mut self.file).unwrap(); + file.set_len(length as u64).unwrap(); + self.file = AccessMode::Read(BufReader::new(file)); + } + AccessMode::None => unreachable!(), + }; + } + + pub fn flush(&mut self) { + if let AccessMode::Write(writer) = &mut self.file { + let _ = writer.flush(); + } + } +} + +impl Drop for BufferedFile { + fn drop(&mut self) { + self.flush() + } +} + +enum AccessMode { + Read(BufReader), + Write(BufWriter), + None, +} + +impl AccessMode { + pub fn unwrap(self) -> File { + match self { + Self::Read(reader) => reader.into_inner(), + Self::Write(writer) => writer.into_inner().unwrap(), + Self::None => unreachable!(), + } + } +} + +impl Default for AccessMode { + fn default() -> Self { + Self::None + } +} diff --git a/src/types/debug_symbols.rs b/src/types/debug_symbols.rs new file mode 100644 index 0000000..f4fc412 --- /dev/null +++ b/src/types/debug_symbols.rs @@ -0,0 +1,59 @@ +use crate::*; + + +pub struct DebugSymbols { + symbols: Vec +} + +impl DebugSymbols { + /// Load debug symbols from a symbols file. + pub fn from_path_opt>(path: Option

) -> Self { + let mut symbols = Vec::new(); + if let Some(path) = path { + if let Ok(string) = std::fs::read_to_string(path) { + for line in string.lines() { + if let Some(symbol) = DebugSymbol::from_line(line) { + symbols.push(symbol); + } + } + } + } + symbols.sort_by_key(|s| s.address); + Self { symbols } + } + + /// Return the symbol matching a given address. + pub fn for_address(&self, address: u16) -> Option<&DebugSymbol> { + if self.symbols.is_empty() { return None; } + let symbol = match self.symbols.binary_search_by_key(&address, |s| s.address) { + Ok(index) => self.symbols.get(index)?, + Err(index) => self.symbols.get(index.checked_sub(1)?)?, + }; + Some(&symbol) + } +} + + +pub struct DebugSymbol { + pub address: u16, + pub name: String, + pub location: Option, +} + +impl DebugSymbol { + pub fn from_line(line: &str) -> Option { + if let Some((address, line)) = line.split_once(' ') { + let address = u16::from_str_radix(address, 16).ok()?; + if let Some((name, location)) = line.split_once(' ') { + let name = name.to_string(); + let location = Some(location.to_string()); + Some( DebugSymbol { address, name, location } ) + } else { + let name = line.to_string(); + Some( DebugSymbol { address, name, location: None } ) + } + } else { + None + } + } +} diff --git a/src/types/directory_listing.rs b/src/types/directory_listing.rs new file mode 100644 index 0000000..f079217 --- /dev/null +++ b/src/types/directory_listing.rs @@ -0,0 +1,120 @@ +use crate::*; + + +pub struct DirectoryListing { + children: Vec, + length: u32, + selected: Option, + child_path_buffer: BedrockPathBuffer, +} + + +impl DirectoryListing { + pub fn from_path(path: &BedrockFilePath) -> Option { + macro_rules! unres { + ($result:expr) => { match $result { Ok(v) => v, Err(_) => continue} }; + } + macro_rules! unopt { + ($option:expr) => { match $option { Some(v) => v, None => continue} }; + } + + #[cfg(target_family = "windows")] { + if path.as_path().components().count() == 0 { + return Some(Self::construct_virtual_root()) + } + } + + let mut children = Vec::new(); + if let Ok(dir_listing) = std::fs::read_dir(path.as_path()) { + for (i, entry_result) in dir_listing.enumerate() { + // Firebreak to prevent emulator from consuming an absurd amount + // of memory when opening too large of a directory. + if i == (u16::MAX as usize) { + break; + } + + let entry = unres!(entry_result); + let entry_path = unopt!(BedrockFilePath::from_path(&entry.path(), path.base())); + if entry_path.is_hidden() { + continue; + } + + children.push(entry_path); + } + } + + children.sort(); + let length = u32::try_from(children.len()).ok()?; + let selected = None; + let child_path_buffer = BedrockPathBuffer::new(); + Some( Self { children, length, selected, child_path_buffer } ) + } + + /// Generate entries for a virtual root directory. + #[cfg(target_family = "windows")] + fn construct_virtual_root() -> Self { + let mut children = Vec::new(); + let base = PathBuf::from(""); + let drive_bits = unsafe { + windows::Win32::Storage::FileSystem::GetLogicalDrives() + }; + for i in 0..26 { + if drive_bits & (0x1 << i) != 0 { + let letter: char = (b'A' + i).into(); + let path = PathBuf::from(format!("{letter}:/")); + if let Some(drive) = BedrockFilePath::from_path(&path, &base) { + children.push(drive); + } + } + } + + let length = children.len() as u32; + let selected = None; + let child_path_buffer = BedrockPathBuffer::new(); + Self { children, length, selected, child_path_buffer } + } + + /// Attempts to return a directory child by index. + pub fn get(&self, index: u32) -> Option<&BedrockFilePath> { + self.children.get(index as usize) + } + + pub fn length(&self) -> u32 { + self.length + } + + /// Returns the index of the selected child, or zero if no child is selected. + pub fn selected(&self) -> u32 { + self.selected.unwrap_or(0) + } + + /// Attempts to select a child by index. + pub fn set_selected(&mut self, index: u32) { + if let Some(child) = self.get(index) { + let buffer = child.as_buffer(); + self.child_path_buffer.populate(buffer); + self.selected = Some(index); + } else { + self.child_path_buffer.clear(); + self.selected = None; + } + } + + pub fn deselect_child(&mut self) { + self.child_path_buffer.clear(); + self.selected = None; + } + + pub fn child_path_buffer(&mut self) -> &mut BedrockPathBuffer { + &mut self.child_path_buffer + } + + pub fn child_type(&self) -> Option { + self.selected.and_then(|s| self.get(s).and_then(|i| i.entry_type())) + } + + pub fn child_path(&self) -> Option { + self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.clone()))) + } +} + diff --git a/src/types/entry_type.rs b/src/types/entry_type.rs new file mode 100644 index 0000000..6a9ac2d --- /dev/null +++ b/src/types/entry_type.rs @@ -0,0 +1,37 @@ +use crate::*; + +use std::cmp::Ordering; + + +pub enum Entry { + File(BufferedFile), + Directory(DirectoryListing), +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum EntryType { + File, + Directory, +} + +impl PartialOrd for EntryType { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (EntryType::Directory, EntryType::Directory) => Some(Ordering::Equal ), + (EntryType::Directory, EntryType::File ) => Some(Ordering::Less ), + (EntryType::File, EntryType::Directory) => Some(Ordering::Greater), + (EntryType::File, EntryType::File ) => Some(Ordering::Equal ), + } + } +} + +impl Ord for EntryType { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (EntryType::Directory, EntryType::Directory) => Ordering::Equal , + (EntryType::Directory, EntryType::File ) => Ordering::Less , + (EntryType::File, EntryType::Directory) => Ordering::Greater, + (EntryType::File, EntryType::File ) => Ordering::Equal , + } + } +} diff --git a/src/types/file_path.rs b/src/types/file_path.rs new file mode 100644 index 0000000..7e6dbe8 --- /dev/null +++ b/src/types/file_path.rs @@ -0,0 +1,288 @@ +use crate::*; + +use std::cmp::Ordering; +use std::ffi::OsString; +use std::path::Component; + + +#[derive(Clone)] +pub struct BedrockFilePath { + /// Sandbox directory + base: PathBuf, + /// Path relative to sandbox directory + relative: PathBuf, + bytes: Vec, + entry_type: Option, +} + +impl BedrockFilePath { + pub fn from_buffer(buffer: [u8; 256], base: &Path) -> Option { + let base = base.to_path_buf(); + let relative = buffer_to_path(buffer)?; + let bytes = path_to_bytes(&relative)?; + let entry_type = get_entry_type(base.join(&relative)); + assert_path_is_safe(&relative, &base)?; + Some(Self { base, relative, bytes, entry_type }) + } + + /// Construct an instance from an absolute path and a prefix of that path. + pub fn from_path(path: &Path, base: &Path) -> Option { + let base = base.to_path_buf(); + let relative = path.strip_prefix(&base).ok()?.to_path_buf(); + let bytes = path_to_bytes(&relative)?; + let entry_type = get_entry_type(base.join(&relative)); + assert_path_is_safe(&relative, &base)?; + Some( Self { base, relative, bytes, entry_type } ) + } + + /// Get the base path used by this path. + pub fn base(&self) -> &Path { + &self.base + } + + /// Get this path as a Bedrock-style path, which can be passed to + /// a Bedrock program. + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + /// Get this path as a byte buffer, from which a CircularPathBuffer + /// can be populated. + pub fn as_buffer(&self) -> [u8; 256] { + let mut buffer: [u8; 256] = [0; 256]; + buffer[..self.bytes.len()].copy_from_slice(&self.bytes); + return buffer; + } + + /// Get this path as an absolute operating-system path, which can + /// be used to open a file or directory. + pub fn as_path(&self) -> PathBuf { + self.base.join(&self.relative) + } + + /// Get the entry type of this path. + pub fn entry_type(&self) -> Option { + self.entry_type + } + + /// Get a path which represents the parent of this path. + pub fn parent(&self) -> Option { + #[cfg(target_family = "unix")] { + Self::from_path(self.as_path().parent()?, &self.base) + } + #[cfg(target_family = "windows")] { + if self.base.components().count() != 0 { + // Sandboxed path, cannot ascend to a virtual root directory. + Self::from_path(self.as_path().parent()?, &self.base) + } else { + // Unsandboxed path, we can ascend to a virtual root directory. + match self.as_path().parent() { + // Ascend to concrete parent directory. + Some(parent) => Self::from_path(parent, &self.base), + // Ascend into a virtual root directory. + None => { + if self.relative.components().count() != 0 { + // Ascend from concrete path to virtual root. + let blank = PathBuf::from(""); + BedrockFilePath::from_path(&blank, &blank) + } else { + // Cannot ascend above the virtual root. + None + } + }, + } + } + } + } + + /// Returns true if the file would be hidden by the default file browser. + pub fn is_hidden(&self) -> bool { + #[cfg(target_family = "unix")] { + if let Some(stem) = self.relative.file_stem() { + if let Some(string) = stem.to_str() { + return string.starts_with('.'); + } + } + } + #[cfg(target_family = "windows")] { + use std::os::windows::fs::MetadataExt; + // See https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants + // const FILE_ATTRIBUTE_HIDDEN: u32 = 0x00000002; + // const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x00002000; + if let Ok(metadata) = std::fs::metadata(self.as_path()) { + return metadata.file_attributes() & 0x2002 != 0; + } + } + return false; + } +} + + +/// Converts the contents of a CircularPathBuffer to a relative path. +fn buffer_to_path(bytes: [u8; 256]) -> Option { + // The buffer must be non-empty and slash-prefixed. + if bytes[0] != ('/' as u8) { + return None; + } + + // Find the index of the first null byte. + let mut null_i = None; + for (i, b) in bytes.iter().enumerate() { + if *b == 0x00 { + null_i = Some(i); + break; + } + } + // Take a slice, excluding the leading slash, up to the trailing null. + let slice = &bytes[1..null_i?]; + + #[cfg(target_family = "unix")] { + use std::os::unix::ffi::OsStringExt; + let vec = Vec::from(slice); + return Some(OsString::from_vec(vec).into()) + } + #[cfg(target_family = "windows")] { + use std::os::windows::ffi::OsStringExt; + let mut string = String::from_utf8_lossy(slice).to_string(); + // Convert drive-current-directory paths to drive-root paths. This is + // needed because the paths C: and C:/ point to separate directories, + // but trailing forward-slashes are optional in Bedrock. + if string.ends_with(':') { + string.push('/'); + } + let utf16: Vec = string.replace(r"/", r"\").encode_utf16().collect(); + return Some(OsString::from_wide(&utf16).into()) + } +} + +/// Convert an operating system path to a Bedrock-style byte path. +/// +/// A byte path contains at most 255 bytes, and is not null-terminated. +fn path_to_bytes(path: &Path) -> Option> { + #[cfg(target_family = "unix")] + let string = path.as_os_str().to_str()?.to_string(); + #[cfg(target_family = "windows")] + let string = path.as_os_str().to_str()?.replace(r"\", r"/"); + + // Remove any trailing forward-slash and add a leading forward-slash. + let mut prefixed_string = String::from("/"); + prefixed_string.push_str(string.trim_end_matches('/')); + let slice = prefixed_string.as_bytes(); + + // Error if bytes does not fit into a CircularPathBuffer. + if slice.len() > 255 { return None; } + + Some(Vec::from(slice)) +} + +/// Returns true if a relative path can be safely attached to a base without +/// breaking out of the sandbox. +fn assert_path_is_safe(relative: &Path, _base: &Path) -> Option<()> { + #[cfg(target_family = "unix")] { + // Error if path contains special components. + for component in relative.components() { + match component { + Component::Normal(_) => continue, + _ => return None, + } + } + } + #[cfg(target_family = "windows")] { + // If the base path is empty, the relative path needs to be able to + // contain the prefix and root element. If the base path is not + // empty, the relative path must not contain these elements else + // they will override the base path when joined. + if _base.components().count() != 0 { + for component in relative.components() { + match component { + Component::Normal(_) => continue, + _ => return None, + } + } + } + } + return Some(()); +} + +fn get_entry_type(absolute: PathBuf) -> Option { + #[cfg(target_family = "windows")] { + // If path is empty, this is a virtual root directory. + if absolute.components().count() == 0 { + return Some(EntryType::Directory) + } + } + let metadata = std::fs::metadata(absolute).ok()?; + if metadata.is_file() { + Some(EntryType::File) + } else if metadata.is_dir() { + Some(EntryType::Directory) + } else { + None + } +} + +impl std::fmt::Debug for BedrockFilePath { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + self.as_path().fmt(f) + } +} + +// --------------------------------------------------------------------------- + +impl PartialEq for BedrockFilePath { + fn eq(&self, other: &Self) -> bool { + self.bytes == other.bytes && self.entry_type == other.entry_type + } +} + +impl Eq for BedrockFilePath {} + +impl PartialOrd for BedrockFilePath { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for BedrockFilePath { + fn cmp(&self, other: &Self) -> Ordering { + match self.entry_type.cmp(&other.entry_type) { + Ordering::Equal => compare_ascii_slices(&self.bytes, &other.bytes), + ordering => ordering, + } + } +} + +/// Compare two ASCII byte-slices in case-agnostic alphabetic order. +fn compare_ascii_slices(left: &[u8], right: &[u8]) -> Ordering { + let l = std::cmp::min(left.len(), right.len()); + let lhs = &left[..l]; + let rhs = &right[..l]; + + for i in 0..l { + let a = remap_ascii(lhs[i]); + let b = remap_ascii(rhs[i]); + match a.cmp(&b) { + Ordering::Equal => (), + non_eq => return non_eq, + } + } + + left.len().cmp(&right.len()) +} + +/// Remap ASCII values so that they sort in case-agnostic alphabetic order: +/// +/// ```text +/// !"#$%&'()*+,-./0123456789:;<=>? +/// @`AaBbCcDdEeFfGgHhIiJjKkLlMmNnOo +/// PpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_ +/// ``` +fn remap_ascii(c: u8) -> u8 { + if 0x40 <= c && c <= 0x5F { + (c - 0x40) * 2 + 0x40 + } else if 0x60 <= c && c <= 0x7F { + (c - 0x60) * 2 + 0x41 + } else { + c + } +} diff --git a/src/types/metadata.rs b/src/types/metadata.rs new file mode 100644 index 0000000..dde86fa --- /dev/null +++ b/src/types/metadata.rs @@ -0,0 +1,125 @@ +use crate::*; + + +const SMALL_ICON_LEN: usize = 3*3*8; +const LARGE_ICON_LEN: usize = 8*8*8; + +pub fn parse_metadata(bytecode: &[u8]) -> Option { + MetadataParser::from_bytecode(bytecode).parse() +} + + +pub struct ProgramMetadata { + pub name: Option, + pub version: Option, + pub authors: Option>, + pub description: Option, + pub bg_colour: Option, + pub fg_colour: Option, + pub small_icon: Option<[u8; SMALL_ICON_LEN]>, + pub large_icon: Option<[u8; LARGE_ICON_LEN]>, +} + + +struct MetadataParser<'a> { + bytecode: &'a [u8], +} + +impl<'a> MetadataParser<'a> { + pub fn from_bytecode(bytecode: &'a [u8]) -> Self { + Self { bytecode } + } + + pub fn parse(self) -> Option { + // Verify metadata identifier. + let identifier = self.vec(0x00, 10); + if identifier != &[0x41,0x00,0x18,0x42,0x45,0x44,0x52,0x4F,0x43,0x4B] { + return None; + } + + let (name, version) = if let Some(pointer) = self.pointer(0x0a) { + let string = self.string(pointer); + if let Some((name, version)) = string.split_once('/') { + (Some(name.trim().to_string()), Some(version.trim().to_string())) + } else { + (Some(string.trim().to_string()), None) + } + } else { + (None, None) + }; + + let authors = self.pointer(0x0c).map(|p| { + self.string(p).lines().map(|s| s.trim().to_string()).collect() + }); + + let description = self.pointer(0x0e).map(|p| self.string(p)); + let bg_colour = self.pointer(0x10).map(|p| self.colour(p)); + let fg_colour = self.pointer(0x12).map(|p| self.colour(p)); + + let small_icon = if let Some(pointer) = self.pointer(0x14) { + let vec = self.vec(pointer, SMALL_ICON_LEN); + Some(vec.try_into().unwrap()) + } else { + None + }; + + let large_icon = if let Some(pointer) = self.pointer(0x16) { + let vec = self.vec(pointer, LARGE_ICON_LEN); + Some(vec.try_into().unwrap()) + } else { + None + }; + + let metadata = ProgramMetadata { + name, version, authors, description, + bg_colour, fg_colour, small_icon, large_icon, + }; + return Some(metadata); + } + + fn byte(&self, address: usize) -> u8 { + match self.bytecode.get(address) { + Some(byte) => *byte, + None => 0, + } + } + + fn double(&self, address: usize) -> u16 { + u16::from_be_bytes([ + self.byte(address), + self.byte(address + 1), + ]) + } + + fn pointer(&self, address: usize) -> Option { + match self.double(address) { + 0 => None, + v => Some(v as usize), + } + } + + fn vec(&self, address: usize, length: usize) -> Vec { + let mut vec = Vec::new(); + for i in 0..length { + vec.push(self.byte(address + i)); + } + return vec; + } + + fn string(&self, address: usize) -> String { + let mut i = address; + while self.byte(i) != 0 { + i += 1; + } + let slice = &self.bytecode[address..i]; + String::from_utf8_lossy(slice).to_string() + } + + fn colour(&self, address: usize) -> Colour { + let double = self.double(address); + let r = (double >> 8 & 0xf) as u8 * 17; + let g = (double >> 4 & 0xf) as u8 * 17; + let b = (double & 0xf) as u8 * 17; + Colour::from_rgb(r, g, b) + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..730f053 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,19 @@ +mod buffered_file; +mod debug_symbols; +mod directory_listing; +mod entry_type; +mod file_path; +mod metadata; +mod path_buffer; +mod read_buffer; +mod sprite_buffer; + +pub use buffered_file::*; +pub use debug_symbols::*; +pub use directory_listing::*; +pub use entry_type::*; +pub use file_path::*; +pub use metadata::*; +pub use path_buffer::*; +pub use read_buffer::*; +pub use sprite_buffer::*; diff --git a/src/types/path_buffer.rs b/src/types/path_buffer.rs new file mode 100644 index 0000000..d6a0861 --- /dev/null +++ b/src/types/path_buffer.rs @@ -0,0 +1,60 @@ +pub struct BedrockPathBuffer { + buffer: [u8; 256], + pointer: u8, +} + +impl BedrockPathBuffer { + pub fn new() -> Self { + Self { buffer: [0; 256] , pointer: 0 } + } + + /// Clear the buffer, returning the previous buffer contents. + pub fn clear(&mut self) -> [u8; 256] { + self.pointer = 0; + std::mem::replace(&mut self.buffer, [0; 256]) + } + + /// Reset the pointer and hot-swap the byte buffer. + pub fn populate(&mut self, buffer: [u8; 256]) { + self.pointer = 0; + self.buffer = buffer; + } + + /// Move internal pointer to the start of the path or file name. + /// + /// If value is non-zero, the pointer will be moved to the byte + /// directly following the final forward-slash. + pub fn set_pointer(&mut self, value: u8) { + self.pointer = 0; + // Set the pointer to the start of the filename if value is truthy. + if value != 0x00 { + for (i, c) in self.buffer.iter().enumerate() { + match c { + b'/' => self.pointer = (i as u8).saturating_add(1), + 0x00 => break, + _ => continue, + } + } + } + } + + /// Read a single byte from the buffer. + pub fn read(&mut self) -> u8 { + let pointer = self.pointer as usize; + self.pointer = self.pointer.wrapping_add(1); + self.buffer[pointer] + } + + /// Write a single byte to the buffer. + /// + /// If a null-byte is written, the buffer will be cleared and returned. + pub fn write(&mut self, byte: u8) -> Option<[u8; 256]> { + if byte == 0x00 { + Some(self.clear()) + } else { + self.buffer[self.pointer as usize] = byte; + self.pointer = self.pointer.saturating_add(1); + None + } + } +} diff --git a/src/types/read_buffer.rs b/src/types/read_buffer.rs new file mode 100644 index 0000000..7128048 --- /dev/null +++ b/src/types/read_buffer.rs @@ -0,0 +1,34 @@ +pub struct ReadBuffer { + pub bytes: Vec, + pub pointer: usize, +} + +impl ReadBuffer { + pub fn new() -> Self { + Self { + bytes: Vec::new(), + pointer: 0, + } + } + + pub fn from_str(text: &str) -> Self { + Self { + bytes: text.bytes().collect(), + pointer: 0, + } + } + + pub fn set_str(&mut self, text: &str) { + self.bytes = text.bytes().collect(); + self.pointer = 0; + } + + pub fn read(&mut self) -> u8 { + let pointer = self.pointer; + self.pointer += 1; + match self.bytes.get(pointer) { + Some(byte) => *byte, + None => 0, + } + } +} diff --git a/src/types/sprite_buffer.rs b/src/types/sprite_buffer.rs new file mode 100644 index 0000000..74c7b55 --- /dev/null +++ b/src/types/sprite_buffer.rs @@ -0,0 +1,85 @@ +use crate::*; + + +pub struct SpriteBuffer { + pub mem: [u8; 16], + pub pointer: usize, + pub cached: Option<(Sprite, u8)>, +} + +impl SpriteBuffer { + pub fn new() -> Self { + Self { + mem: [0; 16], + pointer: 0, + cached: None, + } + } + + pub fn push_byte(&mut self, byte: u8) { + self.mem[self.pointer] = byte; + self.pointer = (self.pointer + 1) % 16; + self.cached = None; + } + + pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite { + if let Some((sprite, transform)) = self.cached { + if transform == (draw & 0x77) { + return sprite; + } + } + macro_rules! c { + ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; + ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; + } + let mut sprite = [[0; 8]; 8]; + let mut p = match draw & 0x02 != 0 { + true => self.pointer, + false => self.pointer + 8, + }; + match draw & 0x07 { + 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, + 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, + 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, + 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, + 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, + 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, + 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, + 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, + _ => unreachable!(), + } + self.cached = Some((sprite, draw & 0x77)); + return sprite; + } + + pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite { + if let Some((sprite, transform)) = self.cached { + if transform == (draw & 0x77) { + return sprite; + } + } + macro_rules! c { + ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; + ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; + } + let mut sprite = [[0; 8]; 8]; + let mut p = match draw & 0x02 != 0 { + true => self.pointer, + false => self.pointer + 8, + }; + let mut s = p + 8; + match draw & 0x07 { + 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, + _ => unreachable!(), + } + self.cached = Some((sprite, draw & 0x77)); + return sprite; + } +} -- cgit v1.2.3-70-g09d2