From 38427e80d3f3daa10800d88b0f3422011d19f7ee Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Wed, 11 Oct 2023 19:01:07 +0100 Subject: [PATCH] Added compatibility mode to `mkrlen`. Fixed null terminator not being written by `mkrlen`. The compatibility mode is necessary because many rlens are formatted weirdly, and I couldn't find any algorithm to follow that would result in their format. Also only figured out it has a null terminator because without it there's a map part that's a multiple of `0x800` and it was missing a sector. --- tools/Cargo.lock | 7 ++ tools/Cargo.toml | 1 + tools/ddw3-mkrlen/Cargo.toml | 1 + tools/ddw3-mkrlen/src/args.rs | 7 ++ tools/ddw3-mkrlen/src/main.rs | 190 ++++++++++++++++++++++++++-------- tools/repack-rlen.sh | 24 +++++ 6 files changed, 185 insertions(+), 45 deletions(-) create mode 100755 tools/repack-rlen.sh diff --git a/tools/Cargo.lock b/tools/Cargo.lock index f5c7a2128..f40fd508e 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -448,6 +448,7 @@ dependencies = [ "clap", "ddw3-logger", "ddw3-util", + "hex-literal", "itertools 0.11.0", "pathdiff", "serde", @@ -808,6 +809,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "iana-time-zone" version = "0.1.57" diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 1f84b7b36..e3094b684 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -67,6 +67,7 @@ thiserror = "1.0.47" tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } xml-rs = "0.8.16" +hex-literal = "0.4.1" # Workspace ddw3-bytes = { path = "ddw3-bytes" } diff --git a/tools/ddw3-mkrlen/Cargo.toml b/tools/ddw3-mkrlen/Cargo.toml index 110bd3d81..71db51dd0 100644 --- a/tools/ddw3-mkrlen/Cargo.toml +++ b/tools/ddw3-mkrlen/Cargo.toml @@ -15,3 +15,4 @@ pathdiff = { workspace = true } serde = { workspace = true } serde_yaml = { workspace = true } tracing = { workspace = true } +hex-literal = { workspace = true } diff --git a/tools/ddw3-mkrlen/src/args.rs b/tools/ddw3-mkrlen/src/args.rs index 35dbc76fc..98a476885 100644 --- a/tools/ddw3-mkrlen/src/args.rs +++ b/tools/ddw3-mkrlen/src/args.rs @@ -14,4 +14,11 @@ pub struct Args { /// The output file #[clap(long = "output", short = 'o')] pub output: PathBuf, + + /// Run in compatibility mode for file. + /// + /// Expects a string of format `.`, + /// for example, `S232.12` + #[clap(long = "compatibility")] + pub compatibility: Option, } diff --git a/tools/ddw3-mkrlen/src/main.rs b/tools/ddw3-mkrlen/src/main.rs index 1d7f12ccb..b10a9332b 100644 --- a/tools/ddw3-mkrlen/src/main.rs +++ b/tools/ddw3-mkrlen/src/main.rs @@ -1,7 +1,15 @@ //! `RLEN` creator // Features -#![feature(array_chunks, array_windows, seek_stream_len, let_chains)] +#![feature( + array_chunks, + array_windows, + seek_stream_len, + let_chains, + generic_arg_infer, + lint_reasons, + decl_macro +)] // Modules mod args; @@ -12,6 +20,7 @@ use { anyhow::Context, byteorder::{LittleEndian, WriteBytesExt}, clap::Parser, + hex_literal::hex, std::{ fs, io::{self, BufWriter, Seek, Write}, @@ -29,6 +38,7 @@ fn main() -> Result<(), anyhow::Error> { // 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")?; + let input_len = input.len(); // Create the output file let output = fs::File::create(&args.output).context("Unable to open output file")?; @@ -39,65 +49,155 @@ fn main() -> Result<(), anyhow::Error> { .seek(io::SeekFrom::Start(8)) .context("Unable to seek past header")?; - let mut total_size = 0; let mut input = &*input; loop { + // If we're running in compatibility mode, manually output some parts + if let Some(compatibility) = &args.compatibility { + macro if_eq_then($cmp:expr => $res:expr) { + (input == hex![$cmp]).then_some(&hex![$res] as &[u8]) + } + + let bytes = match compatibility.as_str() { + "S205.43" => if_eq_then!("6b5aff" => "026b5a81ff"), + "S210.16" | "S212.12" => if_eq_then!("1b18" => "011b8118"), + "S225.12" => if_eq_then!("03050a0c142a44454d251f4c231625211623481b2149201a48282348303f432f2c182230" => + "2303050A0C142A44454D251F4C231625211623481B2149201A48282348303F432F2C18228130" + ), + "S232.12" => if_eq_then!("2f" => "812f"), + "S237.0" | "S238.0" | "S245.0" | "S406.15" | "S540.161" | "S641.42" | "S735.36" | "S736.36" => + if_eq_then!("00" => "8100"), + "S286.53" => if_eq_then!("0007" => "01008107"), + "S300.14" => if_eq_then!("19" => "8119"), + "S315.51" => if_eq_then!("bbb7c4bcb7b2bbc0ac" => "08bbb7c4bcb7b2bbc081ac"), + "S346.79" => if_eq_then!("9c93372ab8e78dbdc74dae6d" => "0b9c93372ab8e78dbdc74dae816d"), + "S360.9" => if_eq_then!("030016" => "0203008116"), + "S381.33" => if_eq_then!("ccd1c8b1cbccc2c3" => "07ccd1c8b1cbccc281c3"), + "S405.15" => + if_eq_then!("e74f1f161d140e0c0a0b0a0e0f0e0b11120806040200" => "15e74f1f161d140e0c0a0b0a0e0f0e0b1112080604028100"), + "S406.109" => if_eq_then!("6d72642f" => "036d7264812f"), + "S490.79" => if_eq_then!("47" => "8147"), + "S545.45" => if_eq_then!("efc30469f48f00" => "06efc30469f48f8100"), + "S550.6" => if_eq_then!("1d00" => "011d8100"), + "S650.22" => if_eq_then!("101e" => "0110811e"), + "S760.57" => if_eq_then!("050b050703070e" => "06050b05070307810e"), + "S860.4" => + if_eq_then!("6d5e474e63536e8765453c393c51768489dbd9a9f2ecf2f8d5deb789809ba78c91afc2d1781b00" => "266d5e474e63536e8765453c393c51768489dbd9a9f2ecf2f8d5deb789809ba78c91afc2d1781b8100"), + "S875.7" => if_eq_then!("696c6f7e8574642b080400" => "0a696c6f7e8574642b08048100"), + "S880.17" => if_eq_then!("0612242f2b241913417e927a300300" => "0e0612242f2b241913417e927a30038100"), + "S890.38" => if_eq_then!("093393a27e494440311d0a0100" => "0c093393a27e494440311d0a018100"), + "S895.10" => if_eq_then!("1d0b0200" => "031d0b028100"), + "S895.48" => + if_eq_then!("4027140400010918344d5b4a38250d0200" => "104027140400010918344d5b4a38250d028100"), + "S895.50" => + if_eq_then!("492a150501020c1f3c54665a412a100300" => "10492a150501020c1f3c54665a412a10038100"), + + _ => { + tracing::debug!(?compatibility, "Ignoring unknown compatibility"); + None + }, + }; + + if let Some(bytes) = bytes { + output.write_all(bytes).context("Unable to write output")?; + break; + } + + /* + //#[expect(clippy::type_complexity, reason = "This is the only mention of the type")] + const CASES: &[(&[&str], &[u8], &[u8]); 3] = &[ + (&["S205"], &hex!["6b5aff"], &hex!["026b5a81ff"]), + (&["S210", "S212"], &hex!["1b18"], &hex!["011b8118"]), + ( + &["S225"], + & + ), + ]; + + for &(filenames, needle, bytes) in CASES { + // If `filename` isn't part of the input file name, quit + if !args + .input. + .map_or(false, |input_filename| { + filenames.iter().any(|filename| input_filename.contains(filename)) + }) { + continue; + } + + // If we found it, write the output and break all the way out. + if input == needle { + output.write_all(bytes).context("Unable to write output")?; + break 'main; + } + } + */ + } + + // Get the current byte 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}"); + // And the next one + // Note: If there is no next one, just write the current and leave + let Some(&next) = input.get(1) else { + if args.compatibility.as_deref() == Some("S641.42") { + println!("Got {cur:x}"); + } - 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; - } + output.write_u8(0x01).context("Unable to write opcode")?; + output.write_u8(cur).context("Unable to write data")?; + 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")?; + 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; } - 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(), - }; + 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")?; + } - 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; - }, + input = &input[len..]; }, - None => { - output.write_u8(0x01).context("Unable to write opcode")?; - output.write_u8(cur).context("Unable to write data")?; - total_size += 1; - break; + + false => { + let len = match input[1..].array_windows().position(|&[b0, b1]| b0 == b1) { + Some(idx) => idx + 1, + None => match args.compatibility.as_deref() { + Some("S641.42") => input.len() - 1, + _ => 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..]; }, } } + // Finally write the null terminator + output.write_u8(0x00).context("Unable to write null terminator")?; + // Then go back and write the header output .seek(io::SeekFrom::Start(0)) @@ -105,7 +205,7 @@ fn main() -> Result<(), anyhow::Error> { 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`")?; + let total_size = u32::try_from(input_len).context("Total size didn't fit into `u32`")?; output .write_u32::(total_size) .context("Unable to write total size")?; diff --git a/tools/repack-rlen.sh b/tools/repack-rlen.sh new file mode 100755 index 000000000..ffbfc8737 --- /dev/null +++ b/tools/repack-rlen.sh @@ -0,0 +1,24 @@ +#!/bin/env bash + +set -e + +DIR="$(basename "$(dirname "$1")")" +FILE="$(basename "$1")" + +COMPATIBILITY="${DIR%PACK}.${FILE%.bin}" +OUTPUT="${1%.bin}.out.bin" + +#cargo build \ +# --manifest-path=tools/Cargo.toml \ +# --release \ +# -p ddw3-unrlen \ +# -p ddw3-mkrlen + +./tools/target/release/ddw3-unrlen "$1" \ + --output "$OUTPUT" + +./tools/target/release/ddw3-mkrlen "$OUTPUT" \ + --compatibility "$COMPATIBILITY" \ + --output "$1" + +#rm "$OUTPUT"