rvz 0.2.0

RVZ compression library.
Documentation
// SPDX-License-Identifier: LGPL-2.1-or-later OR GPL-2.0-or-later OR MPL-2.0
// SPDX-FileCopyrightText: 2024 Gabriel Marcano <gabemarcano@yahoo.com>

use rvz::Error;
use rvz::HeaderRead;
use rvz::Rvz;

use std::fs::File;
use std::path::PathBuf;

use clap::Parser;

#[derive(Parser)]
struct Cli {
    #[arg(required = true)]
    files: Vec<PathBuf>,
}

pub fn main() -> Result<(), Error> {
    let args = Cli::parse();

    println!("[");
    for (i, path) in args.files.iter().enumerate() {
        let mut f = File::open(path)?;

        let io = if f.has_rvz_magic() {
            Rvz::new(f)
        } else {
            eprintln!(
                "Error: \"{}\" does not appear to be an RVZ file",
                path.display()
            );
            continue;
        };

        if let Err(err) = io {
            eprintln!("Error: {err}");
            continue;
        }

        let mut io = io?;
        let hashes = io.compute_hashes()?.clone();

        let header = &io.metadata.header;
        println!("\t{{");
        println!("\t\t\"header\": {{");
        println!(
            "\t\t\t\"magic\": \"0x{:02X}{:02X}{:02X}{:02X}\"",
            header.magic[0], header.magic[1], header.magic[2], header.magic[3]
        );

        println!("\t\t\t\"version\": \"0x{:08X}\"", header.version);
        println!(
            "\t\t\t\"version compatible\": \"0x{:08X}\"",
            header.version_compatible
        );
        println!(
            "\t\t\t\"disc structure size\": \"0x{:08X}\"",
            header.disc_size
        );
        println!("\t\t\t\"disc hash\": \"{}\"", hex::encode(header.disc_hash));
        println!("\t\t\t\"iso file size\": \"0x{:X}\"", header.iso_file_size);
        println!("\t\t\t\"RVZ file size\": \"0x{:X}\"", header.rvz_file_size);
        println!(
            "\t\t\t\"file head hash\": \"{}\"",
            hex::encode(header.file_head_hash)
        );
        println!("\t\t}},");

        let disc = &io.metadata.disc;
        println!("\t\t\"disc\": {{");
        println!("\t\t\t\"disc type\": \"{}\",", disc.disc_type);
        println!("\t\t\t\"compression\": \"{}\",", disc.compression);
        println!("\t\t\t\"compression level\": {},", disc.compression_level);
        println!("\t\t\t\"chunk size\": \"0x{:X}\",", disc.chunk_size);
        // disc.disc_head
        println!("\t\t\t\"partition count\": {},", disc.partition_count);
        println!("\t\t\t\"partition struct size\": {},", disc.partition_size);
        println!(
            "\t\t\t\"partitions offset\": \"0x{:X}\",",
            disc.partitions_offset
        );
        println!(
            "\t\t\t\"partitions hash\": \"{}\",",
            hex::encode(disc.partitions_hash)
        );
        println!("\t\t\t\"raw data count\": {},", disc.raw_data_count);
        println!(
            "\t\t\t\"raw data offset\": \"0x{:X}\",",
            disc.raw_data_offset
        );
        println!(
            "\t\t\t\"raw data structs size\": \"0x{:X}\",",
            disc.raw_data_size
        );
        println!("\t\t\t\"group count\": {},", disc.group_count);
        println!("\t\t\t\"group offset\": \"0x{:X}\",", disc.group_offset);
        println!("\t\t\t\"group structs size\": \"0x{:X}\",", disc.group_size);
        println!(
            "\t\t\t\"compressor data length\": {}",
            disc.compressor_data_length
        );
        println!("\t\t}},");

        let partitions = &io.metadata.partitions;
        if !partitions.is_empty() {
            println!("\t\t\"partitions\": [");
            let last_partition = partitions.len() - 1;
            for (i, partition) in partitions.iter().enumerate() {
                println!("\t\t\t\"partition {i}\": {{");

                println!(
                    "\t\t\t\t\"partition key\": \"{}\",",
                    hex::encode(partition.partition_key)
                );
                for (j, data) in partition.partition_data.iter().enumerate() {
                    println!("\t\t\t\t\"partition data {j}\": {{");
                    println!("\t\t\t\t\t\"first sector\": \"0x{:X}\",", data.first_sector);
                    println!("\t\t\t\t\t\"sector count\": \"0x{:X}\",", data.sector_count);
                    println!("\t\t\t\t\t\"group index\": \"0x{:X}\",", data.group_index);
                    println!("\t\t\t\t\t\"group count\": \"0x{:X}\",", data.group_count);
                    if j == 1 {
                        println!("\t\t\t\t}}");
                    } else {
                        println!("\t\t\t\t}},");
                    }
                }
                if i == last_partition {
                    println!("\t\t\t}}");
                } else {
                    println!("\t\t\t}},");
                }
            }
            println!("\t\t],");
        }

        let raw_data = &io.metadata.raw_data;
        if !raw_data.is_empty() {
            println!("\t\t\"raw data\": [");
            let last_raw_data = raw_data.len() - 1;
            for (i, raw) in raw_data.iter().enumerate() {
                println!("\t\t\t\"raw data {i}\": {{");

                println!(
                    "\t\t\t\t\"raw data offset\": \"0x{:X}\",",
                    raw.raw_data_offset
                );
                println!("\t\t\t\t\"raw data size\": \"0x{:X}\",", raw.raw_data_size);
                println!("\t\t\t\t\"group index\": \"0x{:X}\",", raw.group_index);
                println!("\t\t\t\t\"group count\": \"0x{:X}\",", raw.group_count);
                if i == last_raw_data {
                    println!("\t\t\t}}");
                } else {
                    println!("\t\t\t}},");
                }
            }
            println!("\t\t]");
        }

        if i == (args.files.len() - 1) {
            println!("\t}}");
        } else {
            println!("\t}},");
        }

        let mut h0_count = 0;
        let mut h1_count = 0;
        let mut h2_count = 0;
        for hash in hashes {
            for sector in &hash.h0 {
                for entry in sector {
                    print!("H0 {h0_count} ");
                    for byte in entry {
                        print!("{byte:02X}");
                    }
                    h0_count += 1;
                    println!();
                }
            }
            for subcluster in &hash.h1 {
                for sector in subcluster {
                    print!("H1 {h1_count} ");
                    for byte in sector {
                        print!("{byte:02X}");
                    }
                    h1_count += 1;
                    println!();
                }
            }
            for cluster in &hash.h2 {
                for subgroup in cluster {
                    print!("H2 {h2_count} ");
                    for byte in subgroup {
                        print!("{byte:02X}");
                    }
                    h2_count += 1;
                    println!();
                }
            }
            for (i, hash) in hash.h3.iter().enumerate() {
                print!("H3 {i} ");
                for byte in hash {
                    print!("{byte:02X}");
                }
                println!();
            }
        }
    }
    println!("]");
    Ok(())
}