From 9ff5e85476ae76d80b487480b9f12c33c43158a7 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 14:33:11 +0200 Subject: [PATCH 01/11] chore: add nom package --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + 2 files changed, 18 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8c45940..771737d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,7 @@ dependencies = [ "assert_fs", "clap", "hex-literal", + "nom", "predicates", "thiserror", "walkdir", @@ -322,6 +323,22 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 23e22ca..b9682a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ edition = "2021" clap = { version = "4.5.4", features = ["derive"] } thiserror = "1.0.59" walkdir = "2.5.0" +nom = "7.1.3" [dev-dependencies] assert_cmd = "2.0.12" From 36ad9d4fc15c1b68edb6cfb9220d9a9441e728b6 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou <5919707+tangtang95@users.noreply.github.com> Date: Sat, 18 May 2024 12:39:25 +0200 Subject: [PATCH 02/11] feat: wip implementation of unpack iro --- src/iro_entry.rs | 23 +++++++++++--- src/iro_header.rs | 31 ++++++++++++++----- src/iro_parser.rs | 42 ++++++++++++++++++++++++++ src/main.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 src/iro_parser.rs diff --git a/src/iro_entry.rs b/src/iro_entry.rs index 7824783..47d7f04 100644 --- a/src/iro_entry.rs +++ b/src/iro_entry.rs @@ -1,12 +1,16 @@ +use crate::Error; + pub const INDEX_FIXED_BYTE_SIZE: usize = 20; +#[derive(Debug)] pub struct IroEntry { - path: Vec, - flags: FileFlags, - offset: u64, - data_len: u32, + pub path: Vec, + pub flags: FileFlags, + pub offset: u64, + pub data_len: u32, } +#[derive(Debug)] pub enum FileFlags { Uncompressed = 0, } @@ -34,3 +38,14 @@ impl From for Vec { bytes } } + +impl TryFrom for FileFlags { + type Error = Error; + + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(FileFlags::Uncompressed), + _ => Err(Error::InvalidFileFlags(value)) + } + } +} diff --git a/src/iro_header.rs b/src/iro_header.rs index d79b70e..c7a90cd 100644 --- a/src/iro_header.rs +++ b/src/iro_header.rs @@ -1,21 +1,23 @@ -const IRO_SIG: i32 = 0x534f5249; // represents IROS text +use crate::Error; -#[derive(Clone)] +pub const IRO_SIG: i32 = 0x534f5249; // represents IROS text + +#[derive(Clone, Debug)] pub struct IroHeader { - version: IroVersion, - flags: IroFlags, - size: i32, - num_files: u32, + pub version: IroVersion, + pub flags: IroFlags, + pub size: i32, + pub num_files: u32, } -#[derive(Clone)] +#[derive(Clone, Debug)] #[allow(dead_code)] pub enum IroFlags { None = 0, Patch = 1, } -#[derive(Clone)] +#[derive(Clone, Debug)] #[allow(dead_code)] pub enum IroVersion { Zero = 0x10000, @@ -45,3 +47,16 @@ impl From for Vec { .concat() } } + +impl TryFrom for IroFlags { + type Error = Error; + + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(IroFlags::None), + 1 => Ok(IroFlags::Patch), + _ => Err(Error::InvalidIroFlags(value)) + } + } +} + diff --git a/src/iro_parser.rs b/src/iro_parser.rs new file mode 100644 index 0000000..213032f --- /dev/null +++ b/src/iro_parser.rs @@ -0,0 +1,42 @@ +use nom::{ + bytes::streaming::{tag, take}, + number::streaming::{le_i32, le_u16, le_u32, le_u64}, +}; + +use crate::{ + iro_entry::{FileFlags, IroEntry}, + iro_header::{IroFlags, IroHeader, IroVersion, IRO_SIG}, + Error, +}; + +pub fn parse_iro_header_v2(bytes: &[u8]) -> Result<(&[u8], IroHeader), Error> { + let (bytes, _) = tag(&IRO_SIG.to_le_bytes())(bytes)?; + let (bytes, _) = tag((IroVersion::Two as i32).to_le_bytes())(bytes)?; + let (bytes, flags) = le_i32(bytes)?; + let (bytes, _) = tag(16i32.to_le_bytes())(bytes)?; + let (bytes, num_files) = le_u32(bytes)?; + + Ok(( + bytes, + IroHeader::new(IroVersion::Two, IroFlags::try_from(flags)?, 16, num_files), + )) +} + +pub fn parse_iro_entry_v2(bytes: &[u8]) -> Result<(&[u8], IroEntry), Error> { + let (bytes, _) = le_u16(bytes)?; + let (bytes, filepath_len) = le_u16(bytes)?; + let (bytes, filepath) = take(filepath_len)(bytes)?; + let (bytes, file_flags) = le_i32(bytes)?; + let (bytes, offset) = le_u64(bytes)?; + let (bytes, data_len) = le_u32(bytes)?; + + Ok(( + bytes, + IroEntry::new( + filepath.to_vec(), + FileFlags::try_from(file_flags)?, + offset, + data_len, + ), + )) +} diff --git a/src/main.rs b/src/main.rs index ab7691a..b6c2267 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod iro_entry; mod iro_header; +mod iro_parser; use std::{ io::{BufRead, BufReader, Seek, Write}, @@ -11,6 +12,7 @@ use std::{ use clap::{Args, Parser, Subcommand}; use iro_entry::{FileFlags, IroEntry, INDEX_FIXED_BYTE_SIZE}; use iro_header::{IroFlags, IroHeader, IroVersion}; +use iro_parser::{parse_iro_entry_v2, parse_iro_header_v2}; use thiserror::Error; use walkdir::{DirEntry, WalkDir}; @@ -26,6 +28,7 @@ struct Cli { enum Commands { /// Pack a single directory into an IRO archive Pack(PackArgs), + Unpack(UnpackArgs), } #[derive(Args)] @@ -39,6 +42,17 @@ struct PackArgs { output: Option, } +#[derive(Args)] +struct UnpackArgs { + /// IRO file to unpack + #[arg()] + iro_path: PathBuf, + + /// Output directory path (default is the name of the IRO to unpack) + #[arg(short, long)] + output: Option, +} + #[derive(Error, Debug)] pub enum Error { #[error(transparent)] @@ -53,6 +67,18 @@ pub enum Error { InvalidUnicode(PathBuf), #[error("could not find default name from {0}")] CannotDetectDefaultName(PathBuf), + #[error("parsing error due to invalid iro flags {0}")] + InvalidIroFlags(i32), + #[error(transparent)] + NomParseError(nom::Err<::nom::error::Error>>), + #[error("parsing error due to invalid file flags {0}")] + InvalidFileFlags(i32), +} + +impl From>> for Error { + fn from(err: nom::Err>) -> Self { + Self::NomParseError(err.map_input(|input| input.into())) + } } fn main() { @@ -72,6 +98,17 @@ fn main() { process::exit(1); } }, + Commands::Unpack(args) => match unpack_archive(args.iro_path, args.output) { + Ok(output_dir) => { + println!("iro unpacked into \"{}\" directory", output_dir.display()); + process::exit(0); + }, + Err(err) => { + let stderr = std::io::stderr(); + writeln!(stderr.lock(), "[iroga error]: {}", err).ok(); + process::exit(1); + } + }, } } @@ -154,6 +191,46 @@ fn pack_archive(dir_to_pack: PathBuf, output_path: Option) -> Result) -> Result { + // compute output filepath: either default generated name or given output_path + let output_path = match output_path { + Some(path) => path, + None => { + let filename = iro_path + .file_name() + .ok_or(Error::CannotDetectDefaultName(iro_path.clone()))? + .to_str() + .ok_or(Error::CannotDetectDefaultName(iro_path.clone()))? + .trim_end_matches(".iro"); + Path::new(filename).to_owned() + } + }; + std::fs::create_dir_all(&output_path)?; + + let iro_file = std::fs::File::open(&iro_path)?; + let mut buf_reader = BufReader::new(iro_file); + let bytes = buf_reader.fill_buf()?; + let (rem_bytes, iro_header) = parse_iro_header_v2(bytes)?; + let consumed_bytes_len = bytes.len() - rem_bytes.len(); + buf_reader.consume(consumed_bytes_len); + + println!("{:?}", iro_header); + + let mut iro_entries: Vec = Vec::new(); + for _ in 0..iro_header.num_files { + let bytes = buf_reader.fill_buf()?; + let (rem_bytes, iro_entry) = parse_iro_entry_v2(bytes)?; + println!("{:?}", iro_entry); + + iro_entries.push(iro_entry); + let consumed_bytes_len = bytes.len() - rem_bytes.len(); + buf_reader.consume(consumed_bytes_len); + } + + Ok(output_path) +} + + fn unicode_filepath_bytes(path: &Path, strip_prefix_str: &Path) -> Result, Error> { Ok(path .strip_prefix(strip_prefix_str)? From a3a47aec1858b0ed22403a9e7d6f9b498b65b7a8 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 17:40:39 +0200 Subject: [PATCH 03/11] feat: implement unpack write files --- src/main.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index b6c2267..3256209 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod iro_header; mod iro_parser; use std::{ - io::{BufRead, BufReader, Seek, Write}, + io::{BufRead, BufReader, Cursor, Read, Seek, Write}, path::{Path, PathBuf}, process, result::Result, @@ -73,6 +73,8 @@ pub enum Error { NomParseError(nom::Err<::nom::error::Error>>), #[error("parsing error due to invalid file flags {0}")] InvalidFileFlags(i32), + #[error("utf16 error {0}")] + Utf16Error(String), } impl From>> for Error { @@ -208,7 +210,7 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result) -> Result Result { + let bytes_u16 = path_bytes + .chunks(2) + .map(|e| e.try_into().map(u16::from_be_bytes)) + .collect::, _>>() + .map_err(|_| Error::Utf16Error("uneven bytes".to_owned()))?; + + String::from_utf16(&bytes_u16).map_err(|_| Error::Utf16Error("path_bytes in u16 cannot be converted to string".to_owned())) +} + fn unicode_filepath_bytes(path: &Path, strip_prefix_str: &Path) -> Result, Error> { Ok(path From 4ce0e210bdefaa163c60b1860df4e06d1a0fad56 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 17:55:04 +0200 Subject: [PATCH 04/11] feat: implement unpack write files --- src/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3256209..a5d5a1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,7 +61,7 @@ pub enum Error { StripPrefix(#[from] ::std::path::StripPrefixError), #[error("{0} is not a directory")] NotDir(PathBuf), - #[error("output file path already exists: {0}")] + #[error("output path already exists: {0}")] OutputPathExists(PathBuf), #[error("{0} has invalid unicode")] InvalidUnicode(PathBuf), @@ -75,6 +75,8 @@ pub enum Error { InvalidFileFlags(i32), #[error("utf16 error {0}")] Utf16Error(String), + #[error("parten file path does not exists: {0}")] + ParentPathDoesNotExist(PathBuf), } impl From>> for Error { @@ -207,6 +209,9 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result) -> Result) -> Result Result { let bytes_u16 = path_bytes .chunks(2) - .map(|e| e.try_into().map(u16::from_be_bytes)) + .map(|e| e.try_into().map(u16::from_le_bytes)) .collect::, _>>() .map_err(|_| Error::Utf16Error("uneven bytes".to_owned()))?; From 655b5c464442214dd3cc1092a345ca417b75db79 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 18:10:14 +0200 Subject: [PATCH 05/11] chore: refactor unpack feature --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index a5d5a1a..1457947 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,7 @@ pub enum Error { CannotDetectDefaultName(PathBuf), #[error("parsing error due to invalid iro flags {0}")] InvalidIroFlags(i32), - #[error(transparent)] + #[error("failed to parse binary data")] NomParseError(nom::Err<::nom::error::Error>>), #[error("parsing error due to invalid file flags {0}")] InvalidFileFlags(i32), @@ -212,7 +212,6 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result Date: Sat, 18 May 2024 18:10:53 +0200 Subject: [PATCH 06/11] chore: refactor unused and formatting --- src/main.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1457947..f729f57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod iro_header; mod iro_parser; use std::{ - io::{BufRead, BufReader, Cursor, Read, Seek, Write}, + io::{BufRead, BufReader, Read, Seek, Write}, path::{Path, PathBuf}, process, result::Result, @@ -103,10 +103,10 @@ fn main() { } }, Commands::Unpack(args) => match unpack_archive(args.iro_path, args.output) { - Ok(output_dir) => { + Ok(output_dir) => { println!("iro unpacked into \"{}\" directory", output_dir.display()); process::exit(0); - }, + } Err(err) => { let stderr = std::io::stderr(); writeln!(stderr.lock(), "[iroga error]: {}", err).ok(); @@ -236,7 +236,11 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result Result { .collect::, _>>() .map_err(|_| Error::Utf16Error("uneven bytes".to_owned()))?; - String::from_utf16(&bytes_u16).map_err(|_| Error::Utf16Error("path_bytes in u16 cannot be converted to string".to_owned())) + String::from_utf16(&bytes_u16).map_err(|_| { + Error::Utf16Error("path_bytes in u16 cannot be converted to string".to_owned()) + }) } - fn unicode_filepath_bytes(path: &Path, strip_prefix_str: &Path) -> Result, Error> { Ok(path .strip_prefix(strip_prefix_str)? From 68ed0e9dc7b4437a72d1198e36c1801686abb385 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 20:01:16 +0200 Subject: [PATCH 07/11] test: add tests for unpack subcommand --- src/main.rs | 2 +- tests/test.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index f729f57..d9d4f37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -234,7 +234,7 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result Command { Command::cargo_bin("iroga").unwrap() } From d007201a6d671c3615c33c803d7304d9ecfb9987 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 20:20:35 +0200 Subject: [PATCH 08/11] chore: refactor unpack stdout --- src/iro_header.rs | 21 ++++++++++++++++++++- src/main.rs | 28 ++++++++++++++++------------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/iro_header.rs b/src/iro_header.rs index c7a90cd..42345e5 100644 --- a/src/iro_header.rs +++ b/src/iro_header.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use crate::Error; pub const IRO_SIG: i32 = 0x534f5249; // represents IROS text @@ -55,8 +57,25 @@ impl TryFrom for IroFlags { match value { 0 => Ok(IroFlags::None), 1 => Ok(IroFlags::Patch), - _ => Err(Error::InvalidIroFlags(value)) + _ => Err(Error::InvalidIroFlags(value)), + } + } +} + +impl Display for IroFlags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IroFlags::None => f.write_str("Full IRO"), + IroFlags::Patch => f.write_str("Patch IRO"), } } } +impl Display for IroVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + IroVersion::Zero => f.write_str("0x10000"), + IroVersion::Two => f.write_str("0x10002"), + } + } +} diff --git a/src/main.rs b/src/main.rs index d9d4f37..130d43c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -220,13 +220,16 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result version: {}", iro_header.version); + println!("> type: {}", iro_header.flags); + println!("> number of files: {}", iro_header.num_files); + println!(); let mut iro_entries: Vec = Vec::new(); for _ in 0..iro_header.num_files { let bytes = buf_reader.fill_buf()?; let (rem_bytes, iro_entry) = parse_iro_entry_v2(bytes)?; - println!("{:?}", iro_entry); iro_entries.push(iro_entry); let consumed_bytes_len = bytes.len() - rem_bytes.len(); @@ -234,34 +237,35 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result Result { - let bytes_u16 = path_bytes +fn parse_utf16(bytes: &[u8]) -> Result { + let bytes_u16 = bytes .chunks(2) .map(|e| e.try_into().map(u16::from_le_bytes)) .collect::, _>>() .map_err(|_| Error::Utf16Error("uneven bytes".to_owned()))?; - String::from_utf16(&bytes_u16).map_err(|_| { - Error::Utf16Error("path_bytes in u16 cannot be converted to string".to_owned()) - }) + String::from_utf16(&bytes_u16) + .map_err(|_| Error::Utf16Error("bytes in u16 cannot be converted to string".to_owned())) } fn unicode_filepath_bytes(path: &Path, strip_prefix_str: &Path) -> Result, Error> { From 640c885d77c996200aac2685d8ebc4cabbd7da4e Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 21:38:22 +0200 Subject: [PATCH 09/11] chore: refactor error app --- src/error.rs | 36 ++++++++++++++++++++++++++++++++++++ src/main.rs | 47 ++++++++--------------------------------------- 2 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b8b30b0 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use thiserror::Error; + + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + Io(#[from] ::std::io::Error), + #[error(transparent)] + StripPrefix(#[from] ::std::path::StripPrefixError), + #[error("{0} is not a directory")] + NotDir(PathBuf), + #[error("output path already exists: {0}")] + OutputPathExists(PathBuf), + #[error("{0} has invalid unicode")] + InvalidUnicode(PathBuf), + #[error("could not find default name from {0}")] + CannotDetectDefaultName(PathBuf), + #[error("parsing error due to invalid iro flags {0}")] + InvalidIroFlags(i32), + #[error("failed to parse binary data")] + CannotParseBinary(nom::Err<::nom::error::Error>>), + #[error("parsing error due to invalid file flags {0}")] + InvalidFileFlags(i32), + #[error("invalid utf16 {0}")] + InvalidUtf16(String), + #[error("parten file path does not exists: {0}")] + ParentPathDoesNotExist(PathBuf), +} + +impl From>> for Error { + fn from(err: nom::Err>) -> Self { + Self::CannotParseBinary(err.map_input(|input| input.into())) + } +} diff --git a/src/main.rs b/src/main.rs index 130d43c..1345c40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod iro_entry; mod iro_header; mod iro_parser; +mod error; use std::{ io::{BufRead, BufReader, Read, Seek, Write}, @@ -10,10 +11,10 @@ use std::{ }; use clap::{Args, Parser, Subcommand}; +use error::Error; use iro_entry::{FileFlags, IroEntry, INDEX_FIXED_BYTE_SIZE}; use iro_header::{IroFlags, IroHeader, IroVersion}; use iro_parser::{parse_iro_entry_v2, parse_iro_header_v2}; -use thiserror::Error; use walkdir::{DirEntry, WalkDir}; /// Command line tool to pack a single directory into a single archive in IRO format @@ -53,38 +54,6 @@ struct UnpackArgs { output: Option, } -#[derive(Error, Debug)] -pub enum Error { - #[error(transparent)] - Io(#[from] ::std::io::Error), - #[error(transparent)] - StripPrefix(#[from] ::std::path::StripPrefixError), - #[error("{0} is not a directory")] - NotDir(PathBuf), - #[error("output path already exists: {0}")] - OutputPathExists(PathBuf), - #[error("{0} has invalid unicode")] - InvalidUnicode(PathBuf), - #[error("could not find default name from {0}")] - CannotDetectDefaultName(PathBuf), - #[error("parsing error due to invalid iro flags {0}")] - InvalidIroFlags(i32), - #[error("failed to parse binary data")] - NomParseError(nom::Err<::nom::error::Error>>), - #[error("parsing error due to invalid file flags {0}")] - InvalidFileFlags(i32), - #[error("utf16 error {0}")] - Utf16Error(String), - #[error("parten file path does not exists: {0}")] - ParentPathDoesNotExist(PathBuf), -} - -impl From>> for Error { - fn from(err: nom::Err>) -> Self { - Self::NomParseError(err.map_input(|input| input.into())) - } -} - fn main() { let cli = Cli::parse(); match cli.command { @@ -104,7 +73,7 @@ fn main() { }, Commands::Unpack(args) => match unpack_archive(args.iro_path, args.output) { Ok(output_dir) => { - println!("iro unpacked into \"{}\" directory", output_dir.display()); + println!("IRO unpacked into \"{}\" directory", output_dir.display()); process::exit(0); } Err(err) => { @@ -221,9 +190,9 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result version: {}", iro_header.version); - println!("> type: {}", iro_header.flags); - println!("> number of files: {}", iro_header.num_files); + println!("- version: {}", iro_header.version); + println!("- type: {}", iro_header.flags); + println!("- number of files: {}", iro_header.num_files); println!(); let mut iro_entries: Vec = Vec::new(); @@ -262,10 +231,10 @@ fn parse_utf16(bytes: &[u8]) -> Result { .chunks(2) .map(|e| e.try_into().map(u16::from_le_bytes)) .collect::, _>>() - .map_err(|_| Error::Utf16Error("uneven bytes".to_owned()))?; + .map_err(|_| Error::InvalidUtf16("uneven bytes".to_owned()))?; String::from_utf16(&bytes_u16) - .map_err(|_| Error::Utf16Error("bytes in u16 cannot be converted to string".to_owned())) + .map_err(|_| Error::InvalidUtf16("bytes in u16 cannot be converted to string".to_owned())) } fn unicode_filepath_bytes(path: &Path, strip_prefix_str: &Path) -> Result, Error> { From 979e447982d72d089149aaaf4c910b07cdbb222d Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 21:47:55 +0200 Subject: [PATCH 10/11] test: add unpack negative tests --- tests/test.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/test.rs b/tests/test.rs index d1561ea..a080f67 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -6,7 +6,7 @@ use assert_fs::{ use hex_literal::hex; #[test] -pub fn not_exists_file() { +pub fn pack_not_exists_file() { let dir = assert_fs::TempDir::new().unwrap(); iroga_cmd() .current_dir(dir.path()) @@ -19,7 +19,7 @@ pub fn not_exists_file() { } #[test] -pub fn not_dir() { +pub fn pack_not_dir() { let dir = assert_fs::TempDir::new().unwrap(); dir.child("not_dir").touch().unwrap(); iroga_cmd() @@ -32,7 +32,7 @@ pub fn not_dir() { } #[test] -pub fn output_file_already_exists() { +pub fn pack_output_file_already_exists() { let dir = assert_fs::TempDir::new().unwrap(); dir.child("dir/file.txt").touch().unwrap(); dir.child("dir.iro").touch().unwrap(); @@ -103,6 +103,47 @@ pub fn pack_multiple_files() { dir.close().unwrap(); } +#[test] +pub fn unpack_not_exists_file() { + let dir = assert_fs::TempDir::new().unwrap(); + iroga_cmd() + .current_dir(dir.path()) + .arg("unpack") + .arg(dir.path().join("not_exists_file.iro")) + .assert() + .failure() + .code(1); + assert!(!dir.child("not_exists_file.iro").exists()); +} + +#[test] +pub fn unpack_not_file() { + let dir = assert_fs::TempDir::new().unwrap(); + dir.child("not_file/is_file").touch().unwrap(); + iroga_cmd() + .arg("unpack") + .arg(dir.path().join("not_file")) + .assert() + .failure() + .code(1); + assert!(dir.child("not_file").is_dir()); +} + +#[test] +pub fn unpack_output_path_already_exists() { + let dir = assert_fs::TempDir::new().unwrap(); + dir.child("dir/file.txt").touch().unwrap(); + dir.child("dir.iro").touch().unwrap(); + iroga_cmd() + .current_dir(dir.path()) + .arg("unpack") + .arg("dir.iro") + .assert() + .failure() + .code(1) + .stderr(predicates::str::contains("output path already exists")); +} + #[test] pub fn unpack_single_file() { let iro_bytes: &[u8] = &hex!( From 881a89ddea16b6462a1bad4206d8c935d0128d54 Mon Sep 17 00:00:00 2001 From: Tangtang Zhou Date: Sat, 18 May 2024 23:15:45 +0200 Subject: [PATCH 11/11] fix: use exact reads rather than buf reader --- src/error.rs | 3 ++- src/iro_parser.rs | 6 +++--- src/main.rs | 24 ++++++++++++++---------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/error.rs b/src/error.rs index b8b30b0..61c61a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,7 +19,8 @@ pub enum Error { CannotDetectDefaultName(PathBuf), #[error("parsing error due to invalid iro flags {0}")] InvalidIroFlags(i32), - #[error("failed to parse binary data")] + // #[error("failed to parse binary data")] + #[error(transparent)] CannotParseBinary(nom::Err<::nom::error::Error>>), #[error("parsing error due to invalid file flags {0}")] InvalidFileFlags(i32), diff --git a/src/iro_parser.rs b/src/iro_parser.rs index 213032f..45fd8a5 100644 --- a/src/iro_parser.rs +++ b/src/iro_parser.rs @@ -1,6 +1,6 @@ use nom::{ - bytes::streaming::{tag, take}, - number::streaming::{le_i32, le_u16, le_u32, le_u64}, + bytes::complete::{tag, take}, + number::complete::{le_i32, le_u16, le_u32, le_u64}, }; use crate::{ @@ -22,8 +22,8 @@ pub fn parse_iro_header_v2(bytes: &[u8]) -> Result<(&[u8], IroHeader), Error> { )) } +/// Parse IroEntry without considering length of entire block pub fn parse_iro_entry_v2(bytes: &[u8]) -> Result<(&[u8], IroEntry), Error> { - let (bytes, _) = le_u16(bytes)?; let (bytes, filepath_len) = le_u16(bytes)?; let (bytes, filepath) = take(filepath_len)(bytes)?; let (bytes, file_flags) = le_i32(bytes)?; diff --git a/src/main.rs b/src/main.rs index 1345c40..6b5fab6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -182,12 +182,10 @@ fn unpack_archive(iro_path: PathBuf, output_path: Option) -> Result) -> Result = Vec::new(); for _ in 0..iro_header.num_files { - let bytes = buf_reader.fill_buf()?; - let (rem_bytes, iro_entry) = parse_iro_entry_v2(bytes)?; + let mut entry_len_bytes = [0u8; 2]; + iro_file.read_exact(&mut entry_len_bytes)?; + let entry_len = u16::from_le_bytes(entry_len_bytes); + println!("{}", entry_len); + + let mut entry_bytes = vec![0u8; entry_len as usize - 2]; + iro_file.read_exact(entry_bytes.as_mut())?; + println!("{:?}", entry_bytes); + + let (_, iro_entry) = parse_iro_entry_v2(&entry_bytes)?; iro_entries.push(iro_entry); - let consumed_bytes_len = bytes.len() - rem_bytes.len(); - buf_reader.consume(consumed_bytes_len); } for iro_entry in iro_entries {