- Rust 100%
|
|
||
|---|---|---|
| src | ||
| .gitignore | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
RCSP — Rin Cat's Simple Pack
RCSP is a small, dependency-free container for bundling files into a Rust binary and decoding them at runtime.
Requires Rust 1.85 or newer.
Not a virtual filesystem
RCSP stores paths and file data. It does not provide mounting, lookup,
mutation, directory objects, or filesystem metadata. For a virtual filesystem,
see vfs,
virtual-filesystem, or
vfs-kit.
Input safety
RCSP does not enforce resource limits. Declared file counts, path lengths, and file sizes determine allocations, so untrusted input requires a validation layer with application-specific limits. Limiting the source reader alone does not limit the size of an attempted allocation.
Pack
Pack a directory to a file:
rcsp::pack_dir_to_file("assets", "assets.rcsp")?;
Pack generated data into memory:
let encoded = rcsp::pack([
("config.json", br#"{"enabled":true}"#.as_slice()),
("message.txt", b"hello".as_slice()),
])?;
Pack selected filesystem files:
let encoded = rcsp::pack_paths([
("config.json", "assets/config.json"),
("images/icon.png", "assets/icon.png"),
])?;
The corresponding output functions are:
pack,pack_to_filepack_paths,pack_paths_to_file,pack_paths_to_writerpack_dir,pack_dir_to_file,pack_dir_to_writer
Writer provides direct control over streaming output:
use std::fs::File;
let source = File::open("assets/data.bin")?;
let mut writer = rcsp::Writer::new(File::create("assets.rcsp")?, 1)?;
writer.write_file("data.bin", source.metadata()?.len(), source)?;
writer.finish()?;
After any write_file I/O error, discard the Writer and its output; a partial
entry may already have been written.
The *_to_file functions write through a temporary file in the destination
directory. Replacement behavior follows std::fs::rename; Windows may reject
replacing an existing file.
Read
Embed and decode the complete bundle:
static BUNDLE: &[u8] = include_bytes!("../assets.rcsp");
let pack = rcsp::Pack::from_bytes(BUNDLE)?;
for file in pack.files {
println!("{}: {} bytes", file.path, file.size());
}
Or read one complete file at a time:
static BUNDLE: &[u8] = include_bytes!("../assets.rcsp");
let mut reader = rcsp::Reader::new(BUNDLE)?;
while let Some(file) = reader.next_file()? {
println!("{}: {} bytes", file.path, file.size());
}
Trailing bytes after the declared files are ignored.
Compression
Compression wraps the complete RCSP stream and is not part of the format.
Pack a directory directly into Zstandard:
use std::fs::File;
let encoder = zstd::stream::write::Encoder::new(File::create("assets.rcsp.zst")?, 3)?;
let encoder = rcsp::pack_dir_to_writer("assets", encoder)?;
encoder.finish()?;
Decode an embedded compressed bundle with any Read decoder:
static ZSTD: &[u8] = include_bytes!("../assets.rcsp.zst");
let decoder = zstd::stream::read::Decoder::new(ZSTD)?;
let pack = rcsp::Pack::read_from(decoder)?;
static XZ: &[u8] = include_bytes!("../assets.rcsp.xz");
let decoder = xz2::read::XzDecoder::new(XZ);
let pack = rcsp::Pack::read_from(decoder)?;
static GZIP: &[u8] = include_bytes!("../assets.rcsp.gz");
let decoder = flate2::read::GzDecoder::new(GZIP);
let pack = rcsp::Pack::read_from(decoder)?;
lzma-rs decodes into a buffer:
static LZMA: &[u8] = include_bytes!("../assets.rcsp.lzma");
let mut input = LZMA;
let mut decoded = Vec::new();
lzma_rs::lzma_decompress(&mut input, &mut decoded)?;
let pack = rcsp::Pack::from_bytes(&decoded)?;
Format
All integers are little-endian.
"RCSP" | version:u8 | file_count:u32
repeated file_count times:
path_length:u32 | file_size:u64 | path:UTF-8 | data
Paths are unique, non-empty UTF-8 relative paths using /. Empty, ., and
.. components, backslashes, and NUL are rejected.
Directory packing is deterministic. File and directory symlinks are followed, including targets outside the source directory. Cycles, broken links, and non-regular entries are skipped. RCSP does not store compression, checksums, timestamps, permissions, directory entries, or symlink metadata.