diff options
Diffstat (limited to 'src/types')
-rw-r--r-- | src/types/buffered_file.rs | 143 | ||||
-rw-r--r-- | src/types/controller.rs | 160 | ||||
-rw-r--r-- | src/types/directory_listing.rs | 120 | ||||
-rw-r--r-- | src/types/entry_type.rs | 37 | ||||
-rw-r--r-- | src/types/file_path.rs | 288 | ||||
-rw-r--r-- | src/types/mod.rs | 19 | ||||
-rw-r--r-- | src/types/path_buffer.rs | 60 | ||||
-rw-r--r-- | src/types/sprite_buffer.rs | 85 | ||||
-rw-r--r-- | src/types/string_buffer.rs | 37 | ||||
-rw-r--r-- | src/types/wake_queue.rs | 51 |
10 files changed, 1000 insertions, 0 deletions
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<File>), + Write(BufWriter<File>), + 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/controller.rs b/src/types/controller.rs new file mode 100644 index 0000000..76b77e3 --- /dev/null +++ b/src/types/controller.rs @@ -0,0 +1,160 @@ +use crate::*; + +pub use gilrs::{Gilrs, GamepadId}; + + +pub struct OwnedGamepad { + tag: usize, + id: Option<GamepadId>, + gamepad: Gamepad, +} + +impl OwnedGamepad { + pub fn new(tag: usize) -> Self { + Self { tag, id: None, gamepad: Gamepad::new() } + } + + /// Returns Some if the ID owns this gamepad. + pub fn register(&mut self, new_id: GamepadId) -> Option<&mut Gamepad> { + if let Some(id) = self.id { + match id == new_id { + true => Some(&mut self.gamepad), + false => None, + } + } else { + self.id = Some(new_id); + info!("Registered gamepad {}", self.tag); + Some(&mut self.gamepad) + } + } + + pub fn state(&self) -> u8 { + self.gamepad.state + } + + pub fn reset(&mut self) { + self.gamepad.reset(); + } +} + + +pub struct Gamepad { + pub state: u8, + l_up: bool, + l_down: bool, + l_left: bool, + l_right: bool, + r_up: bool, + r_down: bool, + r_left: bool, + r_right: bool, + d_up: bool, + d_down: bool, + d_left: bool, + d_right: bool, + a: bool, + b: bool, + x: bool, + y: bool, +} + +impl Gamepad { + pub fn new() -> Self { + Self { + state: 0, + l_up: false, + l_down: false, + l_left: false, + l_right: false, + r_up: false, + r_down: false, + r_left: false, + r_right: false, + d_up: false, + d_down: false, + d_left: false, + d_right: false, + a: false, + b: false, + x: false, + y: false, + } + } + + pub fn reset(&mut self) { + self.state = 0; + self.l_up = false; + self.l_down = false; + self.l_left = false; + self.l_right = false; + self.r_up = false; + self.r_down = false; + self.r_left = false; + self.r_right = false; + self.d_up = false; + self.d_down = false; + self.d_left = false; + self.d_right = false; + self.a = false; + self.b = false; + self.x = false; + self.y = false; + } + + // Returns true if the state changed. + pub fn process_event(&mut self, event: &gilrs::Event) -> bool { + macro_rules! schmitt { + ($name_neg:ident, $name_pos:ident, $v:expr) => {{ + if self.$name_neg { if $v > -0.40 { self.$name_neg = false; } } + else { if $v < -0.50 { self.$name_neg = true; } } + if self.$name_pos { if $v < 0.40 { self.$name_pos = false; } } + else { if $v > 0.50 { self.$name_pos = true; } } + }}; + } + + match event.event { + gilrs::EventType::ButtonPressed(button, _) => match button { + gilrs::Button::South => self.a = true, + gilrs::Button::East => self.b = true, + gilrs::Button::West => self.x = true, + gilrs::Button::North => self.y = true, + gilrs::Button::DPadUp => self.d_up = true, + gilrs::Button::DPadDown => self.d_down = true, + gilrs::Button::DPadLeft => self.d_left = true, + gilrs::Button::DPadRight => self.d_right = true, + _ => (), + } + gilrs::EventType::ButtonReleased(button, _) => match button { + gilrs::Button::South => self.a = false, + gilrs::Button::East => self.b = false, + gilrs::Button::West => self.x = false, + gilrs::Button::North => self.y = false, + gilrs::Button::DPadUp => self.d_up = false, + gilrs::Button::DPadDown => self.d_down = false, + gilrs::Button::DPadLeft => self.d_left = false, + gilrs::Button::DPadRight => self.d_right = false, + _ => (), + } + gilrs::EventType::AxisChanged(axis, v, _) => match axis { + gilrs::Axis::LeftStickX => schmitt!(l_left, l_right, v), + gilrs::Axis::LeftStickY => schmitt!(l_down, l_up, v), + gilrs::Axis::RightStickX => schmitt!(r_left, r_right, v), + gilrs::Axis::RightStickY => schmitt!(r_down, r_up, v), + _ => (), + } + _ => (), + } + + let old_state = self.state; + self.state = 0; + if self.l_up | self.r_up | self.d_up { self.state |= 0x80; } + if self.l_down | self.r_down | self.d_down { self.state |= 0x40; } + if self.l_left | self.r_left | self.d_left { self.state |= 0x20; } + if self.l_right | self.r_right | self.d_right { self.state |= 0x10; } + if self.a { self.state |= 0x08; } + if self.b { self.state |= 0x04; } + if self.x { self.state |= 0x02; } + if self.y { self.state |= 0x01; } + old_state != self.state + } +} 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<BedrockFilePath>, + length: u32, + selected: Option<u32>, + child_path_buffer: BedrockPathBuffer, +} + + +impl DirectoryListing { + pub fn from_path(path: &BedrockFilePath) -> Option<Self> { + 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<EntryType> { + self.selected.and_then(|s| self.get(s).and_then(|i| i.entry_type())) + } + + pub fn child_path(&self) -> Option<BedrockFilePath> { + 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<Ordering> { + 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<u8>, + entry_type: Option<EntryType>, +} + +impl BedrockFilePath { + pub fn from_buffer(buffer: [u8; 256], base: &Path) -> Option<Self> { + 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<Self> { + 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<EntryType> { + self.entry_type + } + + /// Get a path which represents the parent of this path. + pub fn parent(&self) -> Option<Self> { + #[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<PathBuf> { + // 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<u16> = 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<Vec<u8>> { + #[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<EntryType> { + #[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<Ordering> { + 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/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000..1cc90d3 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,19 @@ +mod buffered_file; +mod controller; +mod directory_listing; +mod entry_type; +mod file_path; +mod path_buffer; +mod sprite_buffer; +mod string_buffer; +mod wake_queue; + +pub use buffered_file::*; +pub use controller::*; +pub use directory_listing::*; +pub use entry_type::*; +pub use file_path::*; +pub use path_buffer::*; +pub use sprite_buffer::*; +pub use string_buffer::*; +pub use wake_queue::*; 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/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; + } +} diff --git a/src/types/string_buffer.rs b/src/types/string_buffer.rs new file mode 100644 index 0000000..7751d9f --- /dev/null +++ b/src/types/string_buffer.rs @@ -0,0 +1,37 @@ +pub struct StringBuffer { + pub bytes: Vec<u8>, + pub pointer: usize, +} + +impl StringBuffer { + pub fn new() -> Self { + Self { + bytes: Vec::new(), + pointer: 0, + } + } + + pub fn from_str(text: &str) -> Self { + let mut new = Self::new(); + new.set_str(text); + new + } + + pub fn set_str(&mut self, text: &str) { + self.bytes = text.bytes().collect(); + self.pointer = 0; + } + + pub fn read(&mut self) -> u8 { + if let Some(byte) = self.bytes.get(self.pointer) { + self.pointer += 1; + *byte + } else { + 0 + } + } + + pub fn restart(&mut self) { + self.pointer = 0; + } +} diff --git a/src/types/wake_queue.rs b/src/types/wake_queue.rs new file mode 100644 index 0000000..41d815b --- /dev/null +++ b/src/types/wake_queue.rs @@ -0,0 +1,51 @@ +pub struct WakeQueue { + queue: Vec<u8>, +} + + +impl WakeQueue { + pub fn new() -> Self { + Self { + queue: [0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF].into(), + } + } + + /// Iterate over all masked devices in last-woken order. + pub fn iter(&self, mask: u16) -> WakeIterator { + let mut queue = Vec::new(); + for i in &self.queue { + if mask & (0x8000 >> i) != 0 { + queue.push(*i); + } + } + // Add system device last. + if mask & 0x8000 != 0 { + queue.push(0); + } + WakeIterator { queue, pointer: 0 } + } + + /// Push a device to the back of the queue. + pub fn wake(&mut self, id: u8) { + if let Some(index) = self.queue.iter().position(|e| *e == id) { + self.queue.remove(index); + self.queue.push(id); + } + } +} + + +pub struct WakeIterator { + queue: Vec<u8>, + pointer: usize, +} + +impl Iterator for WakeIterator { + type Item = u8; + + fn next(&mut self) -> Option<u8> { + let pointer = self.pointer; + self.pointer += 1; + self.queue.get(pointer).copied() + } +} |