2020-08-28 19:39:31 +00:00
|
|
|
use std::fmt::Debug;
|
|
|
|
use std::io::Cursor;
|
|
|
|
use std::io::ErrorKind;
|
|
|
|
use std::io::SeekFrom;
|
|
|
|
use std::path::{Component, Path, PathBuf};
|
2022-03-15 01:54:03 +00:00
|
|
|
use std::{fmt, io};
|
2020-08-28 19:39:31 +00:00
|
|
|
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::error::GameError::FilesystemError;
|
|
|
|
use crate::framework::error::GameResult;
|
|
|
|
use crate::framework::vfs::{OpenOptions, VFile, VMetadata, VFS};
|
2020-08-28 19:39:31 +00:00
|
|
|
|
|
|
|
#[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 {
|
2020-12-25 22:39:41 +00:00
|
|
|
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
|
2020-08-28 19:39:31 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-27 18:20:47 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-08-28 19:39:31 +00:00
|
|
|
enum FSNode {
|
|
|
|
File(&'static str, &'static [u8]),
|
|
|
|
Directory(&'static str, Vec<FSNode>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FSNode {
|
|
|
|
fn get_name(&self) -> &'static str {
|
|
|
|
match self {
|
2022-03-15 01:54:03 +00:00
|
|
|
FSNode::File(name, _) => name,
|
|
|
|
FSNode::Directory(name, _) => name,
|
2020-08-28 19:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_file(&self) -> GameResult<Box<dyn VFile>> {
|
|
|
|
match self {
|
2022-03-15 01:54:03 +00:00
|
|
|
FSNode::File(_, buf) => Ok(BuiltinFile::from(buf)),
|
|
|
|
FSNode::Directory(name, _) => Err(FilesystemError(format!("{} is a directory.", name))),
|
2020-08-28 19:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_metadata(&self) -> Box<dyn VMetadata> {
|
|
|
|
match self {
|
2022-03-15 01:54:03 +00:00
|
|
|
FSNode::File(_, buf) => Box::new(BuiltinMetadata { is_dir: false, size: buf.len() as u64 }),
|
|
|
|
FSNode::Directory(_, _) => Box::new(BuiltinMetadata { is_dir: true, size: 0 }),
|
2020-08-28 19:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct BuiltinFS {
|
|
|
|
root: Vec<FSNode>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BuiltinFS {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
2022-03-15 01:54:03 +00:00
|
|
|
root: vec![FSNode::Directory(
|
|
|
|
"builtin",
|
|
|
|
vec![
|
2020-08-28 19:39:31 +00:00
|
|
|
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")),
|
2022-08-01 00:18:43 +00:00
|
|
|
FSNode::File("gamecontrollerdb.txt", include_bytes!("builtin/gamecontrollerdb.txt")),
|
2022-03-15 01:54:03 +00:00
|
|
|
FSNode::File(
|
|
|
|
"organya-wavetable-doukutsu.bin",
|
|
|
|
include_bytes!("builtin/organya-wavetable-doukutsu.bin"),
|
|
|
|
),
|
2020-12-20 20:57:17 +00:00
|
|
|
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
|
2022-07-23 15:45:08 +00:00
|
|
|
FSNode::Directory(
|
|
|
|
"builtin_data",
|
2022-07-30 12:01:22 +00:00
|
|
|
vec![
|
|
|
|
FSNode::File("buttons.png", include_bytes!("builtin/builtin_data/buttons.png")),
|
|
|
|
FSNode::File("triangles.png", include_bytes!("builtin/builtin_data/triangles.png")),
|
2022-08-20 19:08:15 +00:00
|
|
|
FSNode::Directory(
|
|
|
|
"headband",
|
|
|
|
vec![
|
|
|
|
FSNode::Directory(
|
|
|
|
"ogph",
|
|
|
|
vec![
|
|
|
|
FSNode::File(
|
|
|
|
"Casts.png",
|
|
|
|
include_bytes!("builtin/builtin_data/headband/ogph/Casts.png"),
|
|
|
|
),
|
|
|
|
FSNode::Directory(
|
|
|
|
"Npc",
|
|
|
|
vec![
|
|
|
|
FSNode::File(
|
|
|
|
"NpcGuest.png",
|
|
|
|
include_bytes!(
|
|
|
|
"builtin/builtin_data/headband/ogph/Npc/NpcGuest.png"
|
|
|
|
),
|
|
|
|
),
|
|
|
|
FSNode::File(
|
|
|
|
"NpcMiza.png",
|
|
|
|
include_bytes!(
|
|
|
|
"builtin/builtin_data/headband/ogph/Npc/NpcMiza.png"
|
|
|
|
),
|
|
|
|
),
|
|
|
|
FSNode::File(
|
|
|
|
"NpcRegu.png",
|
|
|
|
include_bytes!(
|
|
|
|
"builtin/builtin_data/headband/ogph/Npc/NpcRegu.png"
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
FSNode::Directory(
|
|
|
|
"plus",
|
|
|
|
vec![
|
|
|
|
FSNode::File(
|
|
|
|
"Casts.png",
|
|
|
|
include_bytes!("builtin/builtin_data/headband/plus/casts.png"),
|
|
|
|
),
|
|
|
|
FSNode::Directory(
|
|
|
|
"Npc",
|
|
|
|
vec![
|
|
|
|
FSNode::File(
|
|
|
|
"NpcGuest.png",
|
|
|
|
include_bytes!(
|
|
|
|
"builtin/builtin_data/headband/plus/npc/npcguest.png"
|
|
|
|
),
|
|
|
|
),
|
|
|
|
FSNode::File(
|
|
|
|
"NpcMiza.png",
|
|
|
|
include_bytes!(
|
|
|
|
"builtin/builtin_data/headband/plus/npc/npcmiza.png"
|
|
|
|
),
|
|
|
|
),
|
|
|
|
FSNode::File(
|
|
|
|
"NpcRegu.png",
|
|
|
|
include_bytes!(
|
|
|
|
"builtin/builtin_data/headband/plus/npc/npcregu.png"
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
2022-08-26 00:17:45 +00:00
|
|
|
FSNode::Directory(
|
|
|
|
"locale",
|
|
|
|
vec![
|
|
|
|
FSNode::File("en.json", include_bytes!("builtin/builtin_data/locale/en.json")),
|
|
|
|
FSNode::File("jp.json", include_bytes!("builtin/builtin_data/locale/jp.json")),
|
|
|
|
],
|
|
|
|
),
|
2022-07-30 12:01:22 +00:00
|
|
|
],
|
2022-07-23 15:45:08 +00:00
|
|
|
),
|
2022-03-15 01:54:03 +00:00
|
|
|
FSNode::Directory(
|
|
|
|
"shaders",
|
|
|
|
vec![
|
|
|
|
// FSNode::File("basic_150.vert.glsl", include_bytes!("builtin/shaders/basic_150.vert.glsl")),
|
|
|
|
// FSNode::File("water_150.frag.glsl", include_bytes!("builtin/shaders/water_150.frag.glsl")),
|
|
|
|
// FSNode::File("basic_es300.vert.glsl", include_bytes!("builtin/shaders/basic_es300.vert.glsl")),
|
|
|
|
// FSNode::File("water_es300.frag.glsl", include_bytes!("builtin/shaders/water_es300.frag.glsl")),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
FSNode::Directory(
|
|
|
|
"lightmap",
|
|
|
|
vec![FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png"))],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)],
|
2020-08-28 19:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2022-03-15 01:54:03 +00:00
|
|
|
let msg = format!("Cannot alter file {:?} in root {:?}, filesystem read-only", path, self);
|
2020-08-28 19:39:31 +00:00
|
|
|
return Err(FilesystemError(msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2022-03-15 01:54:03 +00:00
|
|
|
fn read_dir(&self, path: &Path) -> GameResult<Box<dyn Iterator<Item = GameResult<PathBuf>>>> {
|
2020-08-28 19:39:31 +00:00
|
|
|
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()))
|
|
|
|
}
|
2022-03-15 01:54:03 +00:00
|
|
|
Ok(FSNode::File(_, _)) => Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path))),
|
|
|
|
Err(e) => Err(e),
|
2020-08-28 19:39:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn to_path_buf(&self) -> Option<PathBuf> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-27 18:20:47 +00:00
|
|
|
#[test]
|
2020-08-28 19:39:31 +00:00
|
|
|
fn test_builtin_fs() {
|
|
|
|
let fs = BuiltinFS {
|
|
|
|
root: vec![
|
|
|
|
FSNode::File("test.txt", &[]),
|
2022-03-15 01:54:03 +00:00
|
|
|
FSNode::Directory(
|
|
|
|
"memes",
|
|
|
|
vec![
|
|
|
|
FSNode::File("nothing.txt", &[]),
|
|
|
|
FSNode::Directory("secret stuff", vec![FSNode::File("passwords.txt", b"12345678")]),
|
|
|
|
],
|
|
|
|
),
|
2020-08-28 19:39:31 +00:00
|
|
|
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());
|
2021-01-27 18:20:47 +00:00
|
|
|
}
|