oliver 0.1.8

Lightweight CLI mod manager for Baldur's Gate 3 on Linux
Documentation
use crate::settings::Settings;
use anyhow::{bail, Result};
use larian_formats::{
    bg3::{raw::Version, ModPakLsx, ModuleInfo},
    lsf,
    lspk::{self, DecompressedLspk},
};
use std::{fs::File, path::PathBuf};

impl Settings {
    pub(crate) fn dump_helper(path: PathBuf) -> Result<()> {
        parse_inner(std::iter::once(path), Style::Dump)
    }

    pub(crate) fn parse_helper(
        paths: impl IntoIterator<Item = PathBuf>,
        verbose: bool,
    ) -> Result<()> {
        let style = if verbose {
            Style::Verbose
        } else {
            Style::Basic
        };

        parse_inner(paths, style)
    }
}

#[derive(Debug, Clone, Copy)]
enum Style {
    Basic,
    Dump,
    Verbose,
}

impl Style {
    const fn is_print(self) -> bool {
        matches!(self, Self::Basic | Self::Verbose)
    }
}

fn parse_inner(paths: impl IntoIterator<Item = PathBuf>, style: Style) -> Result<()> {
    for path in paths {
        if style.is_print() {
            let displayed_path = path.display().to_string();

            println!("{displayed_path}");

            for _ in displayed_path.chars() {
                print!("-");
            }

            println!();
        }

        let file = File::open(&path)?;

        match path.extension().map(|s| s.to_string_lossy()).as_deref() {
            Some("zip") => {
                let data = lspk::entry_from_zipfile(&file)?;

                print_lspk(data.1, style)?;
            }
            Some("lsf") => {
                let data = lsf::Reader::new(file)?.read()?;

                if style.is_print() {
                    data.print_summary();
                }
            }
            Some("pak") => {
                let data = lspk::Reader::new(file)?.read()?;
                print_lspk(data, style)?;
            }
            _other => {
                if style.is_print() {
                    bail!("expected .lsf or .pak file");
                }

                bail!("expected .pak file");
            }
        }

        if style.is_print() {
            println!();
        }
    }

    Ok(())
}

fn print_lspk(data: DecompressedLspk, style: Style) -> Result<()> {
    let meta_lsx = data.extract_meta_lsx()?;
    let raw = String::from_utf8_lossy(&meta_lsx.decompressed_bytes);

    if !style.is_print() {
        println!("{raw}");
        return Ok(());
    }

    let ModPakLsx {
        version,
        module_info,
    } = meta_lsx.deserialize_as_mod_pak()?;
    let Version {
        major,
        minor,
        revision,
        build,
    } = version;

    println!("Game version : {major}.{minor}.{revision}.{build}");

    let ModuleInfo {
        author,
        description,
        folder,
        md5,
        name,
        num_players,
        module_type,
        uuid,
        version,
    } = module_info;

    println!("Name         : {name}");
    println!("Folder       : {folder}");
    println!("Version64    : {version}");
    println!("UUID         : {uuid}");

    if !matches!(style, Style::Verbose) {
        return Ok(());
    }

    if let Some(author) = author {
        println!("Author       : {author}");
    }

    if let Some(description) = description {
        println!("Description  : {description}");
    }

    if let Some(md5) = md5 {
        println!("MD5          : {md5}");
    }

    if let Some(num_players) = num_players {
        println!("NumPlayers    : {num_players}");
    }

    if let Some(module_type) = module_type {
        println!("Type         : {module_type}");
    }

    Ok(())
}