From 311efdf4ca97ef543aeacfc6536db1229ac8c1da Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Wed, 11 Oct 2023 11:35:26 +0100 Subject: [PATCH] Added tools to extract and create `rlen` encoded files. --- .vscode/settings.json | 2 + tools/Cargo.lock | 31 +++++++++ tools/Cargo.toml | 6 ++ tools/ddw3-mkrlen/Cargo.toml | 17 +++++ tools/ddw3-mkrlen/src/args.rs | 17 +++++ tools/ddw3-mkrlen/src/main.rs | 114 ++++++++++++++++++++++++++++++++++ tools/ddw3-unrlen/Cargo.toml | 16 +++++ tools/ddw3-unrlen/src/args.rs | 17 +++++ tools/ddw3-unrlen/src/main.rs | 90 +++++++++++++++++++++++++++ 9 files changed, 310 insertions(+) create mode 100644 tools/ddw3-mkrlen/Cargo.toml create mode 100644 tools/ddw3-mkrlen/src/args.rs create mode 100644 tools/ddw3-mkrlen/src/main.rs create mode 100644 tools/ddw3-unrlen/Cargo.toml create mode 100644 tools/ddw3-unrlen/src/args.rs create mode 100644 tools/ddw3-unrlen/src/main.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 51d59a394..1db888876 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,12 +73,14 @@ "Paildramon", "Patamon", "pathdiff", + "peekable", "Phoenixmon", "pngs", "psexe", "REGS", "Renamon", "reqs", + "RLEN", "Rosemon", "Sakuyamon", "seeked", diff --git a/tools/Cargo.lock b/tools/Cargo.lock index ef3b73300..f5c7a2128 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -439,6 +439,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "ddw3-mkrlen" +version = "0.1.0" +dependencies = [ + "anyhow", + "byteorder", + "clap", + "ddw3-logger", + "ddw3-util", + "itertools 0.11.0", + "pathdiff", + "serde", + "serde_yaml", + "tracing", +] + [[package]] name = "ddw3-mktim" version = "0.1.0" @@ -562,6 +578,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "ddw3-unrlen" +version = "0.1.0" +dependencies = [ + "anyhow", + "byteorder", + "clap", + "ddw3-logger", + "itertools 0.11.0", + "pathdiff", + "serde", + "serde_yaml", + "tracing", +] + [[package]] name = "ddw3-untim" version = "0.1.0" diff --git a/tools/Cargo.toml b/tools/Cargo.toml index eaab322d1..1f84b7b36 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -33,6 +33,10 @@ members = [ "ddw3-unmap", "ddw3-mkmap", + # RLen + "ddw3-unrlen", + "ddw3-mkrlen", + # Util "ddw3-bytes", "ddw3-util", @@ -72,6 +76,7 @@ ddw3-lang-file = { path = "ddw3-lang-file" } ddw3-logger = { path = "ddw3-logger" } ddw3-mklang-file = { path = "ddw3-mklang-file" } ddw3-mkpsexe = { path = "ddw3-mkpsexe" } +ddw3-mkrlen = { path = "ddw3-mkrlen" } ddw3-mkmap = { path = "ddw3-mkmap" } ddw3-mkpack = { path = "ddw3-mkpack" } ddw3-mktim = { path = "ddw3-untim" } @@ -82,5 +87,6 @@ ddw3-uniso = { path = "ddw3-uniso" } ddw3-unlang-file = { path = "ddw3-unlang-file" } ddw3-unmap = { path = "ddw3-unmap" } ddw3-unpack = { path = "ddw3-unpack" } +ddw3-unrlen = { path = "ddw3-unrlen" } ddw3-untim = { path = "ddw3-untim" } ddw3-util = { path = "ddw3-util" } diff --git a/tools/ddw3-mkrlen/Cargo.toml b/tools/ddw3-mkrlen/Cargo.toml new file mode 100644 index 000000000..110bd3d81 --- /dev/null +++ b/tools/ddw3-mkrlen/Cargo.toml @@ -0,0 +1,17 @@ +[package] +edition = "2021" +name = "ddw3-mkrlen" +version = "0.1.0" + +[dependencies] + +anyhow = { workspace = true } +byteorder = { workspace = true } +clap = { workspace = true } +ddw3-logger = { workspace = true } +ddw3-util = { workspace = true } +itertools = { workspace = true } +pathdiff = { workspace = true } +serde = { workspace = true } +serde_yaml = { workspace = true } +tracing = { workspace = true } diff --git a/tools/ddw3-mkrlen/src/args.rs b/tools/ddw3-mkrlen/src/args.rs new file mode 100644 index 000000000..35dbc76fc --- /dev/null +++ b/tools/ddw3-mkrlen/src/args.rs @@ -0,0 +1,17 @@ +//! Cli manager + +// Imports +use std::path::PathBuf; + +/// Data from the command line +#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(clap::Parser)] +#[clap(author, version, about)] +pub struct Args { + /// Input file + pub input: PathBuf, + + /// The output file + #[clap(long = "output", short = 'o')] + pub output: PathBuf, +} diff --git a/tools/ddw3-mkrlen/src/main.rs b/tools/ddw3-mkrlen/src/main.rs new file mode 100644 index 000000000..1d7f12ccb --- /dev/null +++ b/tools/ddw3-mkrlen/src/main.rs @@ -0,0 +1,114 @@ +//! `RLEN` creator + +// Features +#![feature(array_chunks, array_windows, seek_stream_len, let_chains)] + +// Modules +mod args; + +// Imports +use { + self::args::Args, + anyhow::Context, + byteorder::{LittleEndian, WriteBytesExt}, + clap::Parser, + std::{ + fs, + io::{self, BufWriter, Seek, Write}, + }, +}; + +fn main() -> Result<(), anyhow::Error> { + // Initialize the logger + ddw3_logger::init().context("Unable to initialize logger")?; + + // Get all args + let args = Args::parse(); + tracing::debug!(?args, "Arguments"); + + // Read the input file + // TODO: Stream the input instead of reading it all? + let input = fs::read(&args.input).context("Unable to read input file")?; + + // Create the output file + let output = fs::File::create(&args.output).context("Unable to open output file")?; + let mut output = BufWriter::new(output); + + // Skip over the header + output + .seek(io::SeekFrom::Start(8)) + .context("Unable to seek past header")?; + + let mut total_size = 0; + let mut input = &*input; + loop { + let Some(&cur) = input.first() else { + break; + }; + + match input.get(1) { + Some(&next) => match cur == next { + true => { + let mut len = match input[1..].iter().position(|&b| b != cur) { + Some(idx) => idx + 1, + None => input.len(), + }; + assert!(len >= 2, "Tried to use repeat opcode on length less than 2: {len}"); + + for bytes in input[..len].chunks(0x7f) { + // If we'd emit less than a `0x82`, instead quit + // Note: This implies that `bytes.len()` is 1, as it will + // never be 0, and we've just checked it's `< 2` + if bytes.len() < 2 { + len -= 1; + break; + } + + let opcode = u8::try_from(0x80 + bytes.len()).expect("Chunk size was greater than 0x80"); + output.write_u8(opcode).context("Unable to write opcode")?; + output.write_u8(cur).context("Unable to write data")?; + } + + input = &input[len..]; + total_size += len; + }, + false => { + let len = match input[1..].array_windows().position(|&[b0, b1]| b0 == b1) { + Some(idx) => idx + 1, + None => input.len(), + }; + + for bytes in input[..len].chunks(0x7f) { + let opcode = u8::try_from(bytes.len()).expect("Chunk size was greater than 0x80"); + assert!(opcode < 0x80, "Chunk size was greater than 0x80"); + output.write_u8(opcode).context("Unable to write opcode")?; + output.write_all(bytes).context("Unable to write data")?; + } + + input = &input[len..]; + total_size += len; + }, + }, + None => { + output.write_u8(0x01).context("Unable to write opcode")?; + output.write_u8(cur).context("Unable to write data")?; + total_size += 1; + break; + }, + } + } + + // Then go back and write the header + output + .seek(io::SeekFrom::Start(0)) + .context("Unable to seek output to start")?; + output + .write_u32::(u32::from_le_bytes(*b"RLEN")) + .context("Unable to write magic")?; + let total_size = u32::try_from(total_size).context("Total size didn't fit into `u32`")?; + output + .write_u32::(total_size) + .context("Unable to write total size")?; + + Ok(()) +} diff --git a/tools/ddw3-unrlen/Cargo.toml b/tools/ddw3-unrlen/Cargo.toml new file mode 100644 index 000000000..5cf1355b3 --- /dev/null +++ b/tools/ddw3-unrlen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +edition = "2021" +name = "ddw3-unrlen" +version = "0.1.0" + +[dependencies] + +anyhow = { workspace = true } +byteorder = { workspace = true } +clap = { workspace = true } +ddw3-logger = { workspace = true } +itertools = { workspace = true } +pathdiff = { workspace = true } +serde = { workspace = true } +serde_yaml = { workspace = true } +tracing = { workspace = true } diff --git a/tools/ddw3-unrlen/src/args.rs b/tools/ddw3-unrlen/src/args.rs new file mode 100644 index 000000000..50900ae87 --- /dev/null +++ b/tools/ddw3-unrlen/src/args.rs @@ -0,0 +1,17 @@ +//! Cli manager + +// Imports +use std::path::PathBuf; + +/// Data from the command line +#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(clap::Parser)] +#[clap(author, version, about)] +pub struct Args { + /// Input file + pub input: PathBuf, + + /// The output path + #[clap(long = "output", short = 'o')] + pub output: PathBuf, +} diff --git a/tools/ddw3-unrlen/src/main.rs b/tools/ddw3-unrlen/src/main.rs new file mode 100644 index 000000000..99eeebd8c --- /dev/null +++ b/tools/ddw3-unrlen/src/main.rs @@ -0,0 +1,90 @@ +//! `RLEN` extractor + +// Features +#![feature(array_chunks, array_windows, seek_stream_len, exclusive_range_pattern)] + +// Modules +mod args; + +// Imports +use { + self::args::Args, + anyhow::Context, + byteorder::{LittleEndian, ReadBytesExt}, + clap::Parser, + std::{ + fs, + io::{self, BufReader, BufWriter, Read, Write}, + path::PathBuf, + }, +}; + +fn main() -> Result<(), anyhow::Error> { + // Initialize the logger + ddw3_logger::init().context("Unable to initialize logger")?; + + // Get all args + let args = Args::parse(); + tracing::debug!(?args, "Arguments"); + + // Open the file + let reader = fs::File::open(&args.input).context("Unable to open input file")?; + let mut reader = BufReader::new(reader); + + let magic = reader + .read_u32::() + .context("Unable to read magic")? + .to_le_bytes(); + const MAGIC: [u8; 4] = *b"RLEN"; + anyhow::ensure!(magic == MAGIC, "Found wrong magic: {magic:x?}, expected {MAGIC:x?}"); + + let total_size = reader.read_u32::().context("Unable to read total size")?; + + // Open the output file + let output = fs::File::create(&args.output).context("Unable to create output file")?; + let mut output = BufWriter::new(output); + + let mut cur_pos = 0; + while cur_pos < total_size { + let opcode = reader.read_u8().context("Unable to read next opcode")?; + + match opcode { + 0x00..0x80 => { + let len = opcode; + io::copy(&mut reader.by_ref().take(u64::from(len)), &mut output) + .context("Unable to write to output")?; + cur_pos += u32::from(opcode); + }, + 0x80..=0xff => { + let byte = reader.read_u8().context("Unable to read byte")?; + let len = opcode - 0x80; + let bytes = vec![byte; usize::from(len)]; + output.write_all(&bytes).context("Unable to write to output")?; + cur_pos += u32::from(len); + }, + } + } + + + Ok(()) +} + +/// Output +#[derive(serde::Serialize)] +pub struct Output { + /// Width + width: u32, + + /// Height + height: u32, + + /// Entries + entries: Vec, + + /// Override number of entries in header + #[serde(skip_serializing_if = "Option::is_none")] + override_entries_len: Option, + + /// Extra padding + extra_padding: u64, +}