mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-06-10 08:33:32 +00:00
Revamps support for container paths and centralizes logic into a VFS (virtual file system) module. The new VFS architecture supports disc images and any layer of nesting. For example, the following command works: `dtk dol info 'Interactive Multi-Game Demo Disc - July 2002 (USA).rvz:files/zz_StarFox051702_e3.tgc:files/default.dol'` This opens a TGC file inside an RVZ disc image, then reads `default.dol` in the FST. Another example: `dtk rel info 'Legend of Zelda, The - The Wind Waker (USA).rvz:files/RELS.arc:mmem/f_pc_profile_lst.rel'` This opens a RARC archive inside an RVZ disc image, loads the Yaz0-compressed REL and decompresses it on the fly. This all operates in memory with minimal overhead, with no need to extract temporary files. Supported container formats: - Disc images (ISO/GCM, WIA/RVZ, WBFS, CISO, NFS, GCZ, TGC) - RARC/SZS and U8 (.arc) Supported compression formats: - Yaz0 (SZS) - Yay0 (SZP) - NLZSS (.lz) Additionally, projects can utilize a new configuration key `object_base`: ``` object: orig/GZLE01/sys/main.dol modules: - object: orig/GZLE01/files/RELS.arc:rels/mmem/f_pc_profile_lst.rel ``` becomes ``` object_base: orig/GZLE01 object: sys/main.dol modules: - object: files/RELS.arc:mmem/f_pc_profile_lst.rel ``` When loading the objects, decomp-toolkit will automatically check the `object_base` directory for any disc images. (They can be named anything, but must be in the folder root) If one is found, all objects will be fetched from the disc image itself, rather than having to extract the files manually. While still a work in progress, two new `vfs` commands were added: `vfs ls` and `vfs cp`. These commands are very barebones currently, but allow listing directory contents and extracting files from decomp-toolkit's vfs representation: ``` ❯ dtk vfs ls disc.rvz: files sys ❯ dtk vfs ls disc.rvz:sys boot.bin bi2.bin apploader.bin fst.bin main.dol ❯ dtk vfs cp disc.rvz:sys/main.dol . ```
131 lines
3.5 KiB
Rust
131 lines
3.5 KiB
Rust
use std::{
|
|
io,
|
|
io::{BufRead, Cursor, Read, Seek, SeekFrom},
|
|
sync::Arc,
|
|
};
|
|
|
|
use filetime::FileTime;
|
|
|
|
use super::{DiscStream, VfsFileType, VfsMetadata};
|
|
use crate::vfs::VfsFile;
|
|
|
|
#[derive(Clone)]
|
|
pub struct StaticFile {
|
|
inner: Cursor<Arc<[u8]>>,
|
|
mtime: Option<FileTime>,
|
|
}
|
|
|
|
impl StaticFile {
|
|
pub fn new(data: Arc<[u8]>, mtime: Option<FileTime>) -> Self {
|
|
Self { inner: Cursor::new(data), mtime }
|
|
}
|
|
}
|
|
|
|
impl BufRead for StaticFile {
|
|
fn fill_buf(&mut self) -> io::Result<&[u8]> { self.inner.fill_buf() }
|
|
|
|
fn consume(&mut self, amt: usize) { self.inner.consume(amt) }
|
|
}
|
|
|
|
impl Read for StaticFile {
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.inner.read(buf) }
|
|
}
|
|
|
|
impl Seek for StaticFile {
|
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { self.inner.seek(pos) }
|
|
}
|
|
|
|
impl VfsFile for StaticFile {
|
|
fn map(&mut self) -> io::Result<&[u8]> { Ok(self.inner.get_ref()) }
|
|
|
|
fn metadata(&mut self) -> io::Result<VfsMetadata> {
|
|
Ok(VfsMetadata {
|
|
file_type: VfsFileType::File,
|
|
len: self.inner.get_ref().len() as u64,
|
|
mtime: self.mtime,
|
|
})
|
|
}
|
|
|
|
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream> { self }
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct WindowedFile {
|
|
base: Box<dyn VfsFile>,
|
|
pos: u64,
|
|
begin: u64,
|
|
end: u64,
|
|
}
|
|
|
|
impl WindowedFile {
|
|
pub fn new(mut base: Box<dyn VfsFile>, offset: u64, size: u64) -> io::Result<Self> {
|
|
base.seek(SeekFrom::Start(offset))?;
|
|
Ok(Self { base, pos: offset, begin: offset, end: offset + size })
|
|
}
|
|
|
|
#[inline]
|
|
pub fn len(&self) -> u64 { self.end - self.begin }
|
|
}
|
|
|
|
impl BufRead for WindowedFile {
|
|
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
|
let buf = self.base.fill_buf()?;
|
|
let remaining = self.end.saturating_sub(self.pos);
|
|
Ok(&buf[..buf.len().min(remaining as usize)])
|
|
}
|
|
|
|
fn consume(&mut self, amt: usize) {
|
|
let remaining = self.end.saturating_sub(self.pos);
|
|
let amt = amt.min(remaining as usize);
|
|
self.base.consume(amt);
|
|
self.pos += amt as u64;
|
|
}
|
|
}
|
|
|
|
impl Read for WindowedFile {
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
let remaining = self.end.saturating_sub(self.pos);
|
|
if remaining == 0 {
|
|
return Ok(0);
|
|
}
|
|
let len = buf.len().min(remaining as usize);
|
|
self.base.read(&mut buf[..len])
|
|
}
|
|
}
|
|
|
|
impl Seek for WindowedFile {
|
|
#[inline]
|
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
|
let mut pos = match pos {
|
|
SeekFrom::Start(p) => self.begin + p,
|
|
SeekFrom::End(p) => self.end.saturating_add_signed(p),
|
|
SeekFrom::Current(p) => self.pos.saturating_add_signed(p),
|
|
};
|
|
if pos < self.begin {
|
|
pos = self.begin;
|
|
} else if pos > self.end {
|
|
pos = self.end;
|
|
}
|
|
let result = self.base.seek(SeekFrom::Start(pos))?;
|
|
self.pos = result;
|
|
Ok(result - self.begin)
|
|
}
|
|
|
|
#[inline]
|
|
fn stream_position(&mut self) -> io::Result<u64> { Ok(self.pos) }
|
|
}
|
|
|
|
impl VfsFile for WindowedFile {
|
|
fn map(&mut self) -> io::Result<&[u8]> {
|
|
let buf = self.base.map()?;
|
|
Ok(&buf[self.pos as usize..self.end as usize])
|
|
}
|
|
|
|
fn metadata(&mut self) -> io::Result<VfsMetadata> {
|
|
let metadata = self.base.metadata()?;
|
|
Ok(VfsMetadata { file_type: VfsFileType::File, len: self.len(), mtime: metadata.mtime })
|
|
}
|
|
|
|
fn into_disc_stream(self: Box<Self>) -> Box<dyn DiscStream> { self }
|
|
}
|