use std::fmt::Display;
use std::fs::File;
use std::path::PathBuf;
use mkups::{parse, create, apply, data::{Trailer, Hunk}};
use clap::{Parser, Subcommand};
use nom::Finish;
mod mman;
#[derive(Parser)]
#[command(version, about)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Create {
input: PathBuf,
patched: PathBuf,
},
Apply {
input: PathBuf,
patch: PathBuf,
#[arg(long, short)]
forward: bool,
#[arg(long, short)]
reverse: bool,
},
Show {
patch: PathBuf,
},
}
#[derive(Debug)]
pub enum CliError {
Usage(String),
BadPatch(String),
Io(std::io::Error),
}
impl From<std::io::Error> for CliError {
fn from(err: std::io::Error) -> Self {
CliError::Io(err)
}
}
impl From<mman::MapError> for CliError {
fn from(err: mman::MapError) -> Self {
match err {
mman::MapError::ZeroLength => CliError::BadPatch("Zero-length patch file".to_owned()),
mman::MapError::Io(io) => io.into(),
}
}
}
impl From<apply::ApplyError> for CliError {
fn from(err: apply::ApplyError) -> Self {
match err {
apply::ApplyError::Io(io) => io.into(),
apply::ApplyError::Parse(msg) => CliError::BadPatch(msg),
apply::ApplyError::DirectionMustBeSpecified => CliError::Usage("Direction must be specified".to_owned()),
}
}
}
impl From<parse::Error<'_>> for CliError {
fn from(err: parse::Error) -> Self {
CliError::BadPatch(parse::format_error(err))
}
}
impl Display for CliError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CliError::Usage(u) => write!(f, "Error: {}", u),
CliError::BadPatch(b) => write!(f, "Malformed patch:\n{}", b),
CliError::Io(io) => write!(f, "IO error {:?}", io),
}
}
}
fn main() {
match main1() {
Ok(_) => (),
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
},
}
}
fn main1() -> Result<(), CliError> {
let cli = Cli::parse();
match &cli.command {
Commands::Create { input, patched } => {
let input_file = File::open(input)?;
let input_buf = mman::MappedFile::new(&input_file)?;
let patched_file = File::open(patched)?;
let patched_buf = mman::MappedFile::new(&patched_file)?;
create::create(input_buf.data(), patched_buf.data(), &mut std::io::stdout())?;
Ok(())
},
Commands::Apply { input, patch, forward, reverse } => {
let direction = match (forward, reverse) {
(false, false) => apply::Direction::Auto,
(true, false) => apply::Direction::Forward,
(false, true) => apply::Direction::Reverse,
_ => return Err(CliError::Usage("Only one of --forward or --reverse may be specified".to_owned())),
};
let mut input_file = File::open(input)?;
let patch_file = File::open(patch)?;
let patch_buf = mman::MappedFile::new(&patch_file)?;
apply::patch_file(&mut input_file, &mut std::io::stdout(), patch_buf.data(), direction)?;
Ok(())
},
Commands::Show { patch } => show(patch),
}
}
fn show(patch: &PathBuf) -> Result<(), CliError> {
println!("UPS Patch {}:", patch.as_path().to_str().unwrap());
let f = File::open(patch)?;
let patch_buf = mman::MappedFile::new(&f)?;
let input = patch_buf.data();
let (input, header) = parse::header(input).finish()?;
println!(" Source length: {}", header.src_len);
println!(" Destination length: {}", header.src_len);
println!();
let input = show_hunks(input)?;
let (_, trailer) = parse::trailer(input).finish()?;
println!("CRCs: Source=0x{:08x} Destination=0x{:08x} Patch=0x{:08x}", trailer.src_crc, trailer.dst_crc, trailer.ups_crc);
Ok(())
}
fn show_hunks(input: &[u8]) -> Result<&[u8], parse::Error> {
let mut input = input;
let mut n = 1;
let mut pos = 0usize;
while input.len() > Trailer::LENGTH {
let hunk: Hunk;
(input, hunk) = parse::hunk(input).finish()?;
pos += hunk.skip;
println!("Hunk #{}: Patch 0x{:x} bytes at 0x{:08x}", n, hunk.xor.len(), pos);
show_hexdump(hunk.xor);
println!();
pos += hunk.xor.len() + 1;
n += 1;
}
Ok(input)
}
fn show_hexdump(input: &[u8]) {
for (n, b) in input.iter().enumerate() {
if n % 16 == 0 {
print!(" {:08x} ", n);
}
print!("{:02x}", b);
if n % 2 == 1 {
print!(" ");
}
if n % 8 == 7 {
print!(" ");
}
if n % 16 == 15 {
println!();
}
}
println!();
}