diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-28 20:25:01 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-28 20:29:12 +1300 |
commit | 1a830a3d1b9d99653322d5ae49ea8165de7ed9d0 (patch) | |
tree | 798e77b6fcf2438b1c2538a67efe856a2f7cb979 /src/devices/file_device.rs | |
parent | 03c4b069e1806af256730639cefdae115b24401a (diff) | |
download | bedrock-pc-1a830a3d1b9d99653322d5ae49ea8165de7ed9d0.zip |
Rewrite emulatorv1.0.0-alpha1
This is a complete rewrite and restructure of the entire emulator
project, as part of the effort in locking down the Bedrock specification
and in creating much better tooling for creating and using Bedrock
programs.
This commit adds a command-line argument scheme, an embedded assembler,
a headless emulator for use in non-graphical environments, deferred
window creation for programs that do not access the screen device,
and new versions of phosphor and bedrock-core. The new version of
phosphor supports multi-window programs, which will make it possible to
implement program forking in the system device later on, and the new
version of bedrock-core implements the final core specification.
Diffstat (limited to 'src/devices/file_device.rs')
-rw-r--r-- | src/devices/file_device.rs | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs new file mode 100644 index 0000000..4859053 --- /dev/null +++ b/src/devices/file_device.rs @@ -0,0 +1,335 @@ +mod bedrock_file_path; +mod bedrock_path_buffer; +mod buffered_file; +mod directory_listing; +mod entry; +mod operations; + +use buffered_file::BufferedFile; +use bedrock_file_path::BedrockFilePath; +use bedrock_path_buffer::BedrockPathBuffer; +use directory_listing::DirectoryListing; +use entry::{Entry, EntryType}; +use operations::{create_file, move_entry, delete_entry}; + +use bedrock_core::*; + +use std::path::{Component, Path, PathBuf}; + + +pub struct FileDevice { + pub base_path: PathBuf, + pub default_path: PathBuf, + + pub entry_buffer: BedrockPathBuffer, + pub action_buffer: BedrockPathBuffer, + pub path_buffer: BedrockPathBuffer, + + pub entry: Option<(Entry, BedrockFilePath)>, + pub cached_dir: Option<(Entry, BedrockFilePath)>, + + pub success: bool, + pub pointer_write: u32, + pub length_write: u32, + + pub enable_read: bool, + pub enable_write: bool, + pub enable_create: bool, + pub enable_move: bool, + pub enable_delete: bool, +} + +impl FileDevice { + pub fn new() -> Self { + #[cfg(target_family = "unix")] + let default_base: PathBuf = PathBuf::from("/"); + #[cfg(target_family = "windows")] + let default_base: PathBuf = PathBuf::from(""); + + // TODO: I'm not at all confident that the default path is correct + // when not being set as the current directory. + Self { + base_path: default_base, + default_path: match std::env::current_dir() { + Ok(dir) => PathBuf::from(dir), + Err(_) => PathBuf::from(""), + }, + + entry_buffer: BedrockPathBuffer::new(), + action_buffer: BedrockPathBuffer::new(), + path_buffer: BedrockPathBuffer::new(), + + entry: None, + cached_dir: None, + + success: false, + pointer_write: 0, + length_write: 0, + + enable_read: true, + enable_write: true, + enable_create: true, + enable_move: true, + enable_delete: false, + } + } + + /// Safely close the current entry, cleaning up entry variables. + pub fn close(&mut self) { + self.entry_buffer.clear(); + self.action_buffer.clear(); + self.path_buffer.clear(); + self.flush(); + + if let Some((Entry::Directory(dir), path)) = std::mem::take(&mut self.entry) { + self.cached_dir = Some((Entry::Directory(dir), path)); + } + } + + /// Open the entry at the given Bedrock path. + pub fn open(&mut self, path: BedrockFilePath) -> Result<(), ()> { + match path.entry_type() { + Some(EntryType::File) => { + let open_result = std::fs::OpenOptions::new() + .read(self.enable_read) + .write(self.enable_write) + .open(path.as_path()); + // Keep the current entry open if we can't open the new path. + if let Ok(file) = open_result { + self.close(); + self.path_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::File(BufferedFile::new(file)), path)); + return Ok(()); + }; + } + Some(EntryType::Directory) => { + // Attempt to use the cached directory. + if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) { + if cached_path == path { + self.close(); + self.path_buffer.populate(cached_path.as_buffer()); + self.entry = Some((dir, cached_path)); + return Ok(()); + } + } + // Keep the current entry open if we can't open the new path. + if let Some(listing) = DirectoryListing::from_path(&path) { + self.close(); + self.path_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::Directory(listing), path)); + return Ok(()); + }; + } + // The entry either doesn't exist or is not a file or directory. + None => (), + } + return Err(()); + } + + /// Process a byte received from the entry port. + pub fn write_to_entry_port(&mut self, byte: u8) { + if let Some(buffer) = self.entry_buffer.write(byte) { + self.close(); + match BedrockFilePath::from_buffer(buffer, &self.base_path) { + Some(path) => self.success = self.open(path).is_ok(), + None => self.success = false, + }; + } + } + + /// Process a byte received from the action port. + pub fn write_to_action_port(&mut self, byte: u8) { + if let Some(buffer) = self.action_buffer.write(byte) { + let destination_blank = buffer[0] == 0x00; + let destination = BedrockFilePath::from_buffer(buffer, &self.base_path); + self.success = false; + + if let Some((_, source)) = &self.entry { + if destination_blank { + if self.enable_delete { + self.success = delete_entry(&source.as_path()); + } + } else if let Some(dest) = destination { + if self.enable_move { + self.success = move_entry(&source.as_path(), &dest.as_path()); + } + } + } else if let Some(dest) = destination { + if self.enable_create { + self.success = create_file(&dest.as_path()); + } + } + self.close(); + } + } + + /// Attempt to open the parent directory of the current entry. + pub fn ascend_to_parent(&mut self) { + if let Some((_, path)) = &self.entry { + match path.parent() { + Some(parent) => self.success = self.open(parent).is_ok(), + None => self.success = false, + }; + } else { + match BedrockFilePath::from_path(&self.default_path, &self.base_path) { + Some(default) => self.success = self.open(default).is_ok(), + None => self.success = false, + }; + } + } + + /// Attempt to open the selected child of the current directory. + pub fn descend_to_child(&mut self) { + if let Some((Entry::Directory(dir), _)) = &self.entry { + match dir.child_path() { + Some(child) => self.success = self.open(child).is_ok(), + None => self.success = false, + }; + } else { + self.success = false; + } + } + + /// Return true if the current entry is a directory. + pub fn entry_type(&self) -> bool { + match self.entry { + Some((Entry::Directory(_), _)) => true, + _ => false, + } + } + + /// Read a byte from the path buffer of the selected child. + pub fn read_child_path(&mut self) -> u8 { + match &mut self.entry { + Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(), + _ => 0, + } + } + + pub fn set_child_path(&mut self, byte: u8) { + if let Some((Entry::Directory(dir), _)) = &mut self.entry { + dir.child_path_buffer().set_pointer(byte); + } + } + + /// Return true if the selected child is a directory. + pub fn child_type(&self) -> bool { + match &self.entry { + Some((Entry::Directory(dir), _)) => match dir.child_type() { + Some(EntryType::Directory) => true, + _ => false, + } + _ => false, + } + } + + /// Read a byte from the current file. + pub fn read_byte(&mut self) -> u8 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.read(), + _ => 0, + } + } + + /// Writes a byte to the currently-open file. + pub fn write_byte(&mut self, byte: u8) { + match &mut self.entry { + Some((Entry::File(file), _)) => file.write(byte), + _ => (), + } + } + + pub fn pointer(&mut self) -> u32 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.pointer(), + Some((Entry::Directory(dir), _)) => dir.selected(), + _ => 0, + } + } + + pub fn commit_pointer(&mut self) { + match &mut self.entry { + Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write), + Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write), + _ => (), + } + } + + pub fn length(&mut self) -> u32 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.length(), + Some((Entry::Directory(dir), _)) => dir.length(), + _ => 0, + } + } + + pub fn commit_length(&mut self) { + match &mut self.entry { + Some((Entry::File(file), _)) => file.set_length(self.length_write), + _ => (), + } + } + + pub fn flush(&mut self) { + if let Some((Entry::File(buffered_file), _)) = &mut self.entry { + buffered_file.flush(); + } + } +} + +impl Drop for FileDevice { + fn drop(&mut self) { + self.flush(); + } +} + +impl Device for FileDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_b!(self.entry.is_some()), + 0x1 => read_b!(self.success), + 0x2 => self.path_buffer.read(), + 0x3 => read_b!(self.entry_type()), + 0x4 => self.read_byte(), + 0x5 => self.read_byte(), + 0x6 => self.read_child_path(), + 0x7 => read_b!(self.child_type()), + 0x8 => read_hh!(self.pointer()), + 0x9 => read_hl!(self.pointer()), + 0xa => read_lh!(self.pointer()), + 0xb => read_ll!(self.pointer()), + 0xc => read_hh!(self.length()), + 0xd => read_hl!(self.length()), + 0xe => read_lh!(self.length()), + 0xf => read_ll!(self.length()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => self.write_to_entry_port(value), + 0x1 => self.write_to_action_port(value), + 0x2 => self.path_buffer.set_pointer(value), + 0x3 => self.ascend_to_parent(), + 0x4 => self.write_byte(value), + 0x5 => self.write_byte(value), + 0x6 => self.set_child_path(value), + 0x7 => self.descend_to_child(), + 0x8 => write_hh!(self.pointer_write, value), + 0x9 => write_hl!(self.pointer_write, value), + 0xa => write_lh!(self.pointer_write, value), + 0xb => write_ll!(self.pointer_write, value), + 0xc => write_hh!(self.length_write, value), + 0xd => write_hl!(self.length_write, value), + 0xe => write_lh!(self.length_write, value), + 0xf => write_ll!(self.length_write, value), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } +} |