pub use larian_formats;
use anyhow::{Result, anyhow, bail};
use clap::ValueEnum;
use larian_formats::{
lsf::LsfData,
lspk::{self, Lspk, ModuleInfo, is_override},
};
use std::{
collections::VecDeque,
ffi::OsStr,
fs::{File, read_dir},
path::{Path, PathBuf},
};
#[derive(Debug, ValueEnum, Clone, Copy)]
pub enum DataFileType {
Lsf,
Lsx,
}
impl DataFileType {
fn from_path(path: &Path) -> Option<Self> {
match path.extension().map(|s| s.to_string_lossy()).as_deref() {
Some("lsf") => Some(Self::Lsf),
Some("lsx") => Some(Self::Lsx),
_ => None,
}
}
const fn extension(self) -> &'static str {
match self {
Self::Lsf => "lsf",
Self::Lsx => "lsx",
}
}
}
macro_rules! convert_inner {
(
$from: expr,
$to: expr,
$read_fn: expr,
$write_fn: expr $(,)?
) => {
let mut input = File::open($from)?;
let data = $read_fn(&mut input)
.map_err(|e| anyhow!("Failed to parse {}: {e}", $from.display()))?;
let mut out = File::options()
.create(true)
.write(true)
.truncate(true)
.open($to)?;
$write_fn(&data, &mut out).map_err(|e| {
anyhow!("Failed to serialize data from {} into {}: {e}", $from.display(), $to.display())
})?;
eprintln!("Converted {} to {}", $from.display(), $to.display());
};
}
#[cfg(unix)]
pub fn contents(path: &Path) -> Result<()> {
use std::os::unix::ffi::OsStrExt;
let paths = larian_formats::lspk::list_mod_pack_files(path)?;
for bytes in paths.iter() {
println!("{}", OsStr::from_bytes(bytes.as_ref()).display());
}
Ok(())
}
#[cfg(unix)]
pub fn extract_file(from: &Path, internal: &Path, to: &Path) -> Result<()> {
use anyhow::Context;
let Some(bytes) = larian_formats::raw::extract_file_from_pak(internal, from)? else {
bail!("{} not found in {}", internal.display(), from.display());
};
std::fs::write(to, bytes).with_context(|| {
format!(
"could not write {} from {} to {}",
internal.display(),
from.display(),
to.display()
)
})
}
pub fn convert_all(path: PathBuf, from: DataFileType, to: DataFileType) -> Result<()> {
let mut dirs = VecDeque::from_iter([path]);
while let Some(dir) = dirs.pop_front() {
for entry in read_dir(dir)? {
let e = entry?;
let file_type = e.file_type()?;
if file_type.is_dir() {
dirs.push_back(e.path());
} else if file_type.is_file() &&
e.path().extension() == Some(OsStr::new(from.extension()))
{
let mut dest = e.path().clone();
dest.set_extension(to.extension());
convert(&e.path(), &dest)?;
}
}
}
Ok(())
}
pub fn convert(from: &Path, to: &Path) -> Result<()> {
let Some(from_type) = DataFileType::from_path(from) else {
bail!("`{}` does not have either `.lsf` or `.lsx` extension", from.display());
};
let Some(to_type) = DataFileType::from_path(to) else {
bail!("`{}` does not have either `.lsf` or `.lsx` extension", to.display());
};
match (from_type, to_type) {
(DataFileType::Lsf, DataFileType::Lsx) => {
convert_inner!(from, to, LsfData::read_lsf, LsfData::write_lsx);
}
(DataFileType::Lsx, DataFileType::Lsf) => {
convert_inner!(from, to, LsfData::read_lsx, LsfData::write_lsf);
}
(..) => {
std::fs::copy(from, to)?;
}
}
Ok(())
}
pub fn pack(mod_files_root: PathBuf, destination: Option<PathBuf>) -> Result<()> {
lspk::write(mod_files_root, destination)?;
Ok(())
}
pub fn unpack(mod_file_path: &Path, destination: Option<PathBuf>) -> Result<()> {
let data = Lspk::from_file(mod_file_path)?;
let prefix_dir = match destination {
Some(unpack_dir) => {
std::fs::create_dir_all(&unpack_dir)?;
unpack_dir
}
None => "./".into(),
};
for file in data.files {
let path = prefix_dir.join(file.path);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, file.contents)?;
}
Ok(())
}
pub fn parse(paths: impl IntoIterator<Item = PathBuf>, verbose: bool) -> Result<()> {
for path in paths {
let displayed_path = path.display().to_string();
let data = Lspk::from_file(&path)?;
print!("{displayed_path}");
if is_override(path) {
print!(" (override)");
}
println!();
for _ in displayed_path.chars() {
print!("-");
}
println!();
print_lspk(&data, verbose)?;
println!();
}
Ok(())
}
fn print_lspk(data: &Lspk, verbose: bool) -> Result<()> {
let meta_lsx = data.deserialize_meta_lsx()?;
let ModuleInfo {
author,
description,
folder,
md5,
name,
num_players,
module_type,
uuid,
version,
..
} = meta_lsx.module_info;
println!("Name : {name}");
println!("Folder : {folder}");
println!("Version64 : {version}");
println!("UUID : {uuid}");
if !verbose {
return Ok(());
}
println!("Author : {author}");
println!("Description : {description}");
println!("MD5 : {md5}");
println!("NumPlayers : {num_players}");
println!("Type : {module_type}");
Ok(())
}