mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-11-27 14:46:26 +00:00
game: add executable built-in virtual filesystem
This commit is contained in:
parent
0ace1831e2
commit
ff1dca747c
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
245
src/builtin_fs.rs
Normal file
245
src/builtin_fs.rs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
use std::{fmt, io};
|
||||
use std::fmt::Debug;
|
||||
use std::io::Cursor;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use crate::ggez::{GameError, GameResult};
|
||||
use crate::ggez::GameError::FilesystemError;
|
||||
use crate::ggez::vfs::{OpenOptions, VFile, VFS, VMetadata};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuiltinFile(Cursor<&'static [u8]>);
|
||||
|
||||
impl BuiltinFile {
|
||||
pub fn from(buf: &'static [u8]) -> Box<dyn VFile> {
|
||||
Box::new(BuiltinFile(Cursor::new(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for BuiltinFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for BuiltinFile {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
self.0.seek(pos)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for BuiltinFile {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only."))
|
||||
}
|
||||
}
|
||||
|
||||
struct BuiltinMetadata {
|
||||
is_dir: bool,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl VMetadata for BuiltinMetadata {
|
||||
fn is_dir(&self) -> bool {
|
||||
self.is_dir
|
||||
}
|
||||
|
||||
fn is_file(&self) -> bool {
|
||||
!self.is_dir
|
||||
}
|
||||
|
||||
fn len(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum FSNode {
|
||||
File(&'static str, &'static [u8]),
|
||||
Directory(&'static str, Vec<FSNode>),
|
||||
}
|
||||
|
||||
impl FSNode {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
FSNode::File(name, _) => { name }
|
||||
FSNode::Directory(name, _) => { name }
|
||||
}
|
||||
}
|
||||
|
||||
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => { Ok(BuiltinFile::from(buf)) }
|
||||
FSNode::Directory(name, _) => { Err(FilesystemError(format!("{} is a directory.", name))) }
|
||||
}
|
||||
}
|
||||
|
||||
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
||||
match self {
|
||||
FSNode::File(_, buf) => {
|
||||
Box::new(BuiltinMetadata {
|
||||
is_dir: false,
|
||||
size: buf.len() as u64,
|
||||
})
|
||||
}
|
||||
FSNode::Directory(_, _) => {
|
||||
Box::new(BuiltinMetadata {
|
||||
is_dir: true,
|
||||
size: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuiltinFS {
|
||||
root: Vec<FSNode>,
|
||||
}
|
||||
|
||||
impl BuiltinFS {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
root: vec![
|
||||
FSNode::Directory("builtin", vec![
|
||||
FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
||||
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
|
||||
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")),
|
||||
])
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node(&self, path: &Path) -> GameResult<FSNode> {
|
||||
let mut iter = path.components().peekable();
|
||||
|
||||
if let Some(Component::RootDir) = iter.next() {
|
||||
let mut curr_dir = &self.root;
|
||||
|
||||
if iter.peek().is_none() {
|
||||
return Ok(FSNode::Directory("", self.root.clone()));
|
||||
}
|
||||
|
||||
while let Some(comp) = iter.next() {
|
||||
let comp_name = comp.as_os_str().to_string_lossy();
|
||||
|
||||
for file in curr_dir {
|
||||
match file {
|
||||
FSNode::File(name, _) if comp_name.eq(name) => {
|
||||
return if iter.peek().is_some() {
|
||||
Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path)))
|
||||
} else {
|
||||
Ok(file.clone())
|
||||
};
|
||||
}
|
||||
FSNode::Directory(name, contents) if comp_name.eq(name) => {
|
||||
if iter.peek().is_some() {
|
||||
curr_dir = contents;
|
||||
break;
|
||||
} else {
|
||||
return Ok(file.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(FilesystemError("Path must be absolute.".to_string()));
|
||||
}
|
||||
|
||||
Err(FilesystemError("File not found.".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BuiltinFS {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "<BuiltinFS>")
|
||||
}
|
||||
}
|
||||
|
||||
impl VFS for BuiltinFS {
|
||||
fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult<Box<dyn VFile>> {
|
||||
if open_options.write || open_options.create || open_options.append || open_options.truncate {
|
||||
let msg = format!(
|
||||
"Cannot alter file {:?} in root {:?}, filesystem read-only",
|
||||
path, self
|
||||
);
|
||||
return Err(FilesystemError(msg));
|
||||
}
|
||||
|
||||
log::info!("open: {:?}", path);
|
||||
|
||||
self.get_node(path)?.to_file()
|
||||
}
|
||||
|
||||
fn mkdir(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to make directory {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn rm(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to remove file {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn rmrf(&self, _path: &Path) -> GameResult<()> {
|
||||
Err(FilesystemError("Tried to remove file/dir {} but FS is read-only".to_string()))
|
||||
}
|
||||
|
||||
fn exists(&self, path: &Path) -> bool {
|
||||
self.get_node(path).is_ok()
|
||||
}
|
||||
|
||||
fn metadata(&self, path: &Path) -> GameResult<Box<dyn VMetadata>> {
|
||||
self.get_node(path).map(|v| v.to_metadata())
|
||||
}
|
||||
|
||||
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item=GameResult<PathBuf>>>> {
|
||||
match self.get_node(path) {
|
||||
Ok(FSNode::Directory(_, contents)) => {
|
||||
let mut vec = Vec::new();
|
||||
for node in contents {
|
||||
vec.push(Ok(PathBuf::from(node.get_name())))
|
||||
}
|
||||
|
||||
Ok(Box::new(vec.into_iter()))
|
||||
}
|
||||
Ok(FSNode::File(_, _)) => {
|
||||
Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path)))
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_path_buf(&self) -> Option<PathBuf> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/*#[test]
|
||||
fn test_builtin_fs() {
|
||||
let fs = BuiltinFS {
|
||||
root: vec![
|
||||
FSNode::File("test.txt", &[]),
|
||||
FSNode::Directory("memes", vec![
|
||||
FSNode::File("nothing.txt", &[]),
|
||||
FSNode::Directory("secret stuff", vec![
|
||||
FSNode::File("passwords.txt", &[b'1', b'2', b'3', b'4', b'5', b'6',]),
|
||||
]),
|
||||
]),
|
||||
FSNode::File("test2.txt", &[]),
|
||||
],
|
||||
};
|
||||
|
||||
println!("{:?}", fs.get_node(Path::new("/")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/test.txt")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap());
|
||||
println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap());
|
||||
}*/
|
||||
|
|
@ -307,6 +307,10 @@ impl Filesystem {
|
|||
self.vfs.push_back(Box::new(physfs));
|
||||
}
|
||||
|
||||
pub(crate) fn mount_vfs(&mut self, vfs: Box<dyn vfs::VFS>) {
|
||||
self.vfs.push_back(vfs);
|
||||
}
|
||||
|
||||
/// Looks for a file named `/conf.toml` in any resource directory and
|
||||
/// loads it if it finds it.
|
||||
/// If it can't read it for some reason, returns an error.
|
||||
|
|
@ -434,6 +438,10 @@ pub fn mount(ctx: &mut Context, path: &path::Path, readonly: bool) {
|
|||
ctx.filesystem.mount(path, readonly)
|
||||
}
|
||||
|
||||
pub fn mount_vfs(ctx: &mut Context, vfs: Box<dyn vfs::VFS>) {
|
||||
ctx.filesystem.mount_vfs(vfs)
|
||||
}
|
||||
|
||||
/// Looks for a file named `/conf.toml` in any resource directory and
|
||||
/// loads it if it finds it.
|
||||
/// If it can't read it for some reason, returns an error.
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ pub mod filesystem;
|
|||
pub mod graphics;
|
||||
pub mod input;
|
||||
pub mod timer;
|
||||
mod vfs;
|
||||
pub mod vfs;
|
||||
|
||||
pub use crate::ggez::context::{Context, ContextBuilder};
|
||||
pub use crate::ggez::error::*;
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ impl<T> VFile for T where T: Read + Write + Seek + Debug {}
|
|||
#[must_use]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
pub struct OpenOptions {
|
||||
read: bool,
|
||||
write: bool,
|
||||
create: bool,
|
||||
append: bool,
|
||||
truncate: bool,
|
||||
pub(crate) read: bool,
|
||||
pub(crate) write: bool,
|
||||
pub(crate) create: bool,
|
||||
pub(crate) append: bool,
|
||||
pub(crate) truncate: bool,
|
||||
}
|
||||
|
||||
impl OpenOptions {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ use crate::stage::StageData;
|
|||
use crate::text_script::TextScriptVM;
|
||||
use crate::texture_set::TextureSet;
|
||||
use crate::ui::UI;
|
||||
use crate::builtin_fs::BuiltinFS;
|
||||
|
||||
mod builtin_fs;
|
||||
mod caret;
|
||||
mod common;
|
||||
mod engine_constants;
|
||||
|
|
@ -265,6 +267,8 @@ pub fn main() -> GameResult {
|
|||
.add_resource_path(resource_dir);
|
||||
|
||||
let (ctx, event_loop) = &mut cb.build()?;
|
||||
ctx.filesystem.mount_vfs(Box::new(BuiltinFS::new()));
|
||||
|
||||
let game = &mut Game::new(ctx)?;
|
||||
game.state.next_scene = Some(Box::new(LoadingScene::new()));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue