Compare commits

...

3 Commits

Author SHA1 Message Date
1eb9341d93 added basic unpacker 2026-03-11 23:50:00 +01:00
1199d40b31 ModConfig add ID 2026-03-11 23:49:48 +01:00
6a60e29fd7 add save function to root_config 2026-03-11 23:49:08 +01:00
8 changed files with 351 additions and 15 deletions

260
Cargo.lock generated
View File

@@ -97,6 +97,36 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit-set"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.1" version = "1.12.1"
@@ -107,6 +137,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.11.1"
@@ -129,6 +171,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.60" version = "4.5.60"
@@ -224,6 +275,30 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.0" version = "1.5.0"
@@ -264,6 +339,16 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.8" version = "0.5.8"
@@ -273,6 +358,16 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "6.0.0" version = "6.0.0"
@@ -379,6 +474,28 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "filetime"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
dependencies = [
"cfg-if",
"libc",
"libredox",
]
[[package]]
name = "filetime_creation"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c25b5d475550e559de5b0c0084761c65325444e3b6c9e298af9cefe7a9ef3a5f"
dependencies = [
"cfg-if",
"filetime",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.9"
@@ -425,6 +542,7 @@ dependencies = [
"log", "log",
"quick-xml", "quick-xml",
"serde", "serde",
"sevenz-rust",
"thiserror", "thiserror",
"toml", "toml",
"ureq", "ureq",
@@ -441,6 +559,16 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -670,6 +798,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "js-sys"
version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]] [[package]]
name = "keyvalues-parser" name = "keyvalues-parser"
version = "0.2.3" version = "0.2.3"
@@ -728,7 +866,10 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
dependencies = [ dependencies = [
"bitflags",
"libc", "libc",
"plain",
"redox_syscall",
] ]
[[package]] [[package]]
@@ -762,6 +903,15 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "lzma-rust"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baab2bbbd7d75a144d671e9ff79270e903957d92fb7386fd39034c709bd2661"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@@ -787,6 +937,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "nt-time"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2de419e64947cd8830e66beb584acc3fb42ed411d103e3c794dda355d1b374b5"
dependencies = [
"chrono",
"time",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.0" version = "0.2.0"
@@ -867,6 +1027,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.1" version = "1.13.1"
@@ -945,6 +1111,15 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "redox_syscall"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.5.2" version = "0.5.2"
@@ -1061,6 +1236,12 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
@@ -1145,6 +1326,34 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "sevenz-rust"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26482cf1ecce4540dc782fc70019eba89ffc4d87b3717eb5ec524b5db6fdefef"
dependencies = [
"bit-set",
"byteorder",
"crc",
"filetime_creation",
"js-sys",
"lzma-rust",
"nt-time",
"sha2",
"wasm-bindgen",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@@ -1312,6 +1521,12 @@ version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
version = "0.1.7" version = "0.1.7"
@@ -1420,6 +1635,51 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [
"unicode-ident",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "1.0.6" version = "1.0.6"

View File

@@ -12,6 +12,7 @@ libloot = "0.29.0"
log = "0.4.29" log = "0.4.29"
quick-xml = { version = "0.39.2", features = ["serde-types", "serialize"] } quick-xml = { version = "0.39.2", features = ["serde-types", "serialize"] }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
sevenz-rust = { version = "0.6.1", default-features = false }
thiserror = "2.0.18" thiserror = "2.0.18"
toml = "1.0.3" toml = "1.0.3"
ureq = { version = "3.2.0", features = ["json"] } ureq = { version = "3.2.0", features = ["json"] }

View File

@@ -19,4 +19,5 @@ pub enum Commands {
LoadOrder { instance: String }, LoadOrder { instance: String },
ApiCheck, ApiCheck,
Download { url: String }, Download { url: String },
Unpack { id: String, path: String },
} }

View File

@@ -9,6 +9,7 @@ use crate::{
instance::{files_to_install_mod, insert_mod_to_instance}, instance::{files_to_install_mod, insert_mod_to_instance},
nexus::{NexusAPI, download_nxm}, nexus::{NexusAPI, download_nxm},
types::RootConfig, types::RootConfig,
unpacker::unpack,
}; };
mod activator; mod activator;
@@ -21,6 +22,7 @@ mod load_order;
mod mod_config_installer; mod mod_config_installer;
mod nexus; mod nexus;
mod types; mod types;
mod unpacker;
mod utils; mod utils;
fn command_activate( fn command_activate(
@@ -39,9 +41,9 @@ fn command_add(root_config: &RootConfig, instance_id: &str, mod_id: &str) -> any
.mod_by_id(mod_id) .mod_by_id(mod_id)
.ok_or(anyhow!("Can't find mod in config"))?; .ok_or(anyhow!("Can't find mod in config"))?;
let files = files_to_install_mod(root_config, &instance, mod_to_install)?; let files = files_to_install_mod(root_config, &instance, &mod_to_install)?;
match insert_mod_to_instance(&mut instance, mod_to_install, &files, 0) { match insert_mod_to_instance(&mut instance, &mod_to_install, &files, 0) {
Ok(_) => { Ok(_) => {
instance.save_to_file()?; instance.save_to_file()?;
Ok(()) Ok(())
@@ -104,6 +106,25 @@ fn command_download(root_config: &RootConfig, nxm_url: &str) -> anyhow::Result<(
Ok(()) Ok(())
} }
fn command_unpack(
root_config: &mut RootConfig,
id: &str,
file: impl AsRef<Path>,
) -> anyhow::Result<()> {
if root_config.game_by_id(id).is_some() {
error!("Mod already present");
return Err(anyhow!("Mod already exists"));
}
let new_mod = unpack(root_config, id, file)?;
root_config.add_mod(&new_mod);
root_config.save_to_file()?;
Ok(())
}
fn setup_logger() { fn setup_logger() {
env_logger::builder() env_logger::builder()
.filter_level(log::LevelFilter::max()) .filter_level(log::LevelFilter::max())
@@ -118,7 +139,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse(); let args = Args::parse();
debug!("Loading config from {:?}", args.config); debug!("Loading config from {:?}", args.config);
let root_config = RootConfig::load_from_file(args.config)?; let mut root_config = RootConfig::load_from_file(args.config)?;
match args.command { match args.command {
cli::Commands::Activate { instance, target } => { cli::Commands::Activate { instance, target } => {
@@ -137,6 +158,9 @@ fn main() -> Result<(), Box<dyn Error>> {
cli::Commands::Download { url } => { cli::Commands::Download { url } => {
command_download(&root_config, &url)?; command_download(&root_config, &url)?;
} }
cli::Commands::Unpack { id, path } => {
command_unpack(&mut root_config, &id, path)?;
}
} }
Ok(()) Ok(())

View File

@@ -3,12 +3,12 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use serde::Deserialize; use serde::{Deserialize, Serialize};
use crate::{types::link::Link, utils::walk_all_files}; use crate::{types::link::Link, utils::walk_all_files};
/// Available game /// Available game
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Game { pub struct Game {
path: PathBuf, path: PathBuf,
} }

View File

@@ -1,11 +1,12 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use serde::Deserialize; use serde::{Deserialize, Serialize};
/// Config for an available mod /// Config for an available mod
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ModConfig { pub struct ModConfig {
/// ID of the mod /// ID of the mod
#[serde(skip)]
id: String, id: String,
/// Relative to the mod_location from root config /// Relative to the mod_location from root config
@@ -13,10 +14,12 @@ pub struct ModConfig {
/// If the files should be included on the root /// If the files should be included on the root
#[serde(default)] #[serde(default)]
#[serde(skip_serializing_if = "is_false")]
root_mod: bool, root_mod: bool,
/// Globs of what files to ignore /// Globs of what files to ignore
#[serde(default)] #[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
ignore: Vec<String>, ignore: Vec<String>,
} }
@@ -30,6 +33,11 @@ impl ModConfig {
} }
} }
pub fn add_id(mut self, id: &str) -> Self {
self.id = id.to_owned();
self
}
pub fn id(&self) -> &str { pub fn id(&self) -> &str {
&self.id &self.id
} }
@@ -47,3 +55,7 @@ impl ModConfig {
&self.ignore &self.ignore
} }
} }
fn is_false(b: &bool) -> bool {
!b
}

View File

@@ -1,21 +1,20 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::{self, read_to_string}, fs::{self, read_to_string},
io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use log::debug; use log::debug;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use crate::types::{ConfigReadWriteError, ModConfig, game::Game, modded_instance::ModdedInstance}; use crate::types::{ConfigReadWriteError, ModConfig, game::Game, modded_instance::ModdedInstance};
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RootConfig { pub struct RootConfig {
/// Available games
#[serde(default)] #[serde(default)]
games: HashMap<String, Game>, games: HashMap<String, Game>,
/// Where all mods are stored
mod_location: PathBuf, mod_location: PathBuf,
download_location: Option<PathBuf>, download_location: Option<PathBuf>,
@@ -27,7 +26,7 @@ pub struct RootConfig {
/// All available mods /// All available mods
#[serde(default)] #[serde(default)]
mods: Vec<ModConfig>, mods: HashMap<String, ModConfig>,
#[serde(skip)] #[serde(skip)]
self_path: PathBuf, self_path: PathBuf,
@@ -75,12 +74,23 @@ impl RootConfig {
Ok(config) Ok(config)
} }
pub fn save_to_file(&self) -> Result<(), ConfigReadWriteError> {
let content = toml::to_string_pretty(self)?;
let mut file = fs::File::create(&self.self_path)?;
write!(file, "{}", content)?;
Ok(())
}
pub fn game_by_id(&self, id: &str) -> Option<&Game> { pub fn game_by_id(&self, id: &str) -> Option<&Game> {
self.games.get(id) self.games.get(id)
} }
pub fn mod_by_id(&self, id: &str) -> Option<&ModConfig> { pub fn mod_by_id(&self, id: &str) -> Option<ModConfig> {
self.mods.iter().find(|e| e.id() == id) self.mods.get(id).map(|e| e.clone().add_id(id))
}
pub fn add_mod(&mut self, new_mod: &ModConfig) {
self.mods.insert(new_mod.id().to_owned(), new_mod.clone());
} }
pub fn load_instance_by_id(&self, id: &str) -> Result<ModdedInstance, ConfigReadWriteError> { pub fn load_instance_by_id(&self, id: &str) -> Result<ModdedInstance, ConfigReadWriteError> {
@@ -109,7 +119,7 @@ impl RootConfig {
} }
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
struct InstancePointer { struct InstancePointer {
path: PathBuf, path: PathBuf,
} }

28
src/unpacker.rs Normal file
View File

@@ -0,0 +1,28 @@
use std::{fs, path::Path};
use anyhow::anyhow;
use crate::types::{ModConfig, RootConfig};
pub fn unpack(
root_config: &RootConfig,
id: &str,
path: impl AsRef<Path>,
) -> anyhow::Result<ModConfig> {
let extract_to = root_config.mod_location().join(id);
if fs::exists(&extract_to)? {
return Err(anyhow!("File already exists"));
}
unpack_7z_file(path, &extract_to)?;
let new_mod = ModConfig::new(id, id);
Ok(new_mod)
}
fn unpack_7z_file(path: impl AsRef<Path>, to: impl AsRef<Path>) -> anyhow::Result<()> {
sevenz_rust::decompress_file(path, to)?;
Ok(())
}