Compare commits

...

5 Commits

9 changed files with 137 additions and 43 deletions

View File

@@ -32,7 +32,8 @@ pub fn handle_nxm(root_config: &mut RootConfig, raw_url: &str) -> anyhow::Result
return Err(anyhow!("Mod with generated id already exists")); return Err(anyhow!("Mod with generated id already exists"));
} }
unpack(root_config, &mod_id, dl_file)?; let extract_to = root_config.mod_location().join(&mod_id);
unpack(dl_file, extract_to)?;
let file_id: u64 = nxm_url.file.parse()?; let file_id: u64 = nxm_url.file.parse()?;
let new_mod = ModConfig::from_mod_info(&mod_id, &mod_id, &mod_info, file_id); let new_mod = ModConfig::from_mod_info(&mod_id, &mod_id, &mod_info, file_id);

View File

@@ -160,6 +160,7 @@ fn should_be_included(path: impl AsRef<Path>) -> bool {
| "ilstrings" | "ilstrings"
| "dlstrings" | "dlstrings"
| "dll" | "dll"
| "swf"
) )
) )
} }

View File

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

View File

@@ -118,6 +118,7 @@ pub enum PluginTypeDescriptorEnum {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DependencyPluginType { pub struct DependencyPluginType {
#[serde(rename = "defaultType")]
pub default_type: PluginType, pub default_type: PluginType,
pub patterns: DependencyPatternList, pub patterns: DependencyPatternList,
} }
@@ -129,7 +130,7 @@ pub struct DependencyPatternList {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DependencyPattern { pub struct DependencyPattern {
pub dependencies: CompositeDependency, pub dependencies: Vec<CompositeDependency>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub typ: PluginType, pub typ: PluginType,
} }

View File

@@ -10,8 +10,7 @@ use fomod_manager::{
}, },
cli::{self, Args}, cli::{self, Args},
nexus::NexusAPI, nexus::NexusAPI,
types::{ModConfig, RootConfig}, types::RootConfig,
unpacker::unpack,
}; };
fn command_activate( fn command_activate(
@@ -70,27 +69,6 @@ fn command_download(root_config: &mut RootConfig, raw_url: &str) -> anyhow::Resu
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"));
}
unpack(root_config, id, file)?;
let new_mod = ModConfig::new(id, id);
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())
@@ -124,9 +102,6 @@ fn main() -> Result<(), Box<dyn Error>> {
cli::Commands::Download { url } => { cli::Commands::Download { url } => {
command_download(&mut root_config, &url)?; command_download(&mut root_config, &url)?;
} }
cli::Commands::Unpack { id, path } => {
command_unpack(&mut root_config, &id, path)?;
}
} }
Ok(()) Ok(())

View File

@@ -176,7 +176,11 @@ fn resolve_plugin_type(
PluginTypeDescriptorEnum::PluginType(plugin_type) => plugin_type.name, PluginTypeDescriptorEnum::PluginType(plugin_type) => plugin_type.name,
PluginTypeDescriptorEnum::DependencyType(dependency_plugin_type) => { PluginTypeDescriptorEnum::DependencyType(dependency_plugin_type) => {
for dep in &dependency_plugin_type.patterns.pattern { for dep in &dependency_plugin_type.patterns.pattern {
if evaluate_dependency(&dep.dependencies, state, installed_plugins) { if dep
.dependencies
.iter()
.all(|e| evaluate_dependency(e, state, installed_plugins))
{
return dep.typ.name; return dep.typ.name;
} }
} }

View File

@@ -1,25 +1,24 @@
use std::{fs, path::Path}; use std::{
fs,
path::{Path, PathBuf},
};
use anyhow::anyhow; use anyhow::{Ok, anyhow};
use log::error; use log::error;
use zip::ZipArchive; use zip::ZipArchive;
use crate::types::RootConfig; pub fn unpack(archive_path: impl AsRef<Path>, extract_to: impl AsRef<Path>) -> anyhow::Result<()> {
pub fn unpack(root_config: &RootConfig, id: &str, path: impl AsRef<Path>) -> anyhow::Result<()> {
let extract_to = root_config.mod_location().join(id);
if fs::exists(&extract_to)? { if fs::exists(&extract_to)? {
return Err(anyhow!( return Err(anyhow!(
"File already exists: {}", "File already exists: {}",
extract_to.to_string_lossy() extract_to.as_ref().to_string_lossy()
)); ));
} }
match path.as_ref().extension().and_then(|e| e.to_str()) { match archive_path.as_ref().extension().and_then(|e| e.to_str()) {
Some("7z") => unpack_7z_file(path, &extract_to), Some("7z") => unpack_7z_file(archive_path, &extract_to),
Some("zip") => unpack_zip_file(path, &extract_to), Some("zip") => unpack_zip_file(archive_path, &extract_to),
Some("rar") => unpack_rar(path, &extract_to), Some("rar") => unpack_rar(archive_path, &extract_to),
Some(ext) => { Some(ext) => {
error!("Unsupported archive format: {}", ext); error!("Unsupported archive format: {}", ext);
Err(anyhow!("Unsupported archive format: {}", ext)) Err(anyhow!("Unsupported archive format: {}", ext))
@@ -27,12 +26,14 @@ pub fn unpack(root_config: &RootConfig, id: &str, path: impl AsRef<Path>) -> any
None => { None => {
error!( error!(
"Failed to determine the file extension for {}", "Failed to determine the file extension for {}",
&path.as_ref().to_string_lossy() &archive_path.as_ref().to_string_lossy()
); );
Err(anyhow!("Failed to determine file extension")) Err(anyhow!("Failed to determine file extension"))
} }
}?; }?;
unnest_dir(extract_to)?;
Ok(()) Ok(())
} }
@@ -57,3 +58,47 @@ fn unpack_rar(path: impl AsRef<Path>, to: impl AsRef<Path>) -> anyhow::Result<()
Ok(()) Ok(())
} }
/// Moves a directorys content into the parent if it is the only dir
fn unnest_dir(path: impl AsRef<Path>) -> anyhow::Result<()> {
let path = path.as_ref();
let Some(nested_dir) = check_nested_dir(path) else {
return Ok(());
};
for entry in fs::read_dir(&nested_dir)? {
let entry = entry?;
let src = entry.path();
let dest = path.join(entry.file_name());
fs::rename(&src, &dest)?;
}
fs::remove_dir(&nested_dir)?;
Ok(())
}
/// Check if the extracted archive has a single directory in it which contains the mod files
fn check_nested_dir(path: impl AsRef<Path>) -> Option<PathBuf> {
let path = path.as_ref();
let entries: Vec<_> = fs::read_dir(path).ok()?.filter_map(|e| e.ok()).collect();
if entries.len() == 1 {
let entry = &entries[0];
let entry_path = entry.path();
if entry_path
.file_name()
.is_some_and(|e| e == "Data" || e == "data")
{
return None;
}
if entry_path.is_dir() {
return Some(entry_path);
}
}
None
}

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://qconsulting.ca/fo3/ModConfig5.0.xsd">
<moduleName>powerofthree's Tweaks</moduleName>
<requiredInstallFiles>
<folder source="Required" destination="" />
</requiredInstallFiles>
<installSteps order="Explicit">
<installStep name="Main">
<optionalFileGroups order="Explicit">
<group name="DLL" type="SelectExactlyOne">
<plugins order="Explicit">
<plugin name="SSE v1.6.629+ (&quot;Anniversary Edition&quot;)">
<description>Select this if you are using Skyrim Anniversary Edition v1.6.629 or higher.</description>
<files>
<folder source="AE/SKSE/Plugins" destination="SKSE/Plugins" priority="0" />
</files>
<typeDescriptor>
<dependencyType>
<defaultType name="Optional" />
<patterns>
<pattern>
<dependencies>
<gameDependency version="1.6" />
</dependencies>
<type name="Recommended" />
</pattern>
<pattern>
<dependencies>
<gameDependency version="1.5" />
</dependencies>
<type name="Optional" />
</pattern>
</patterns>
</dependencyType>
</typeDescriptor>
</plugin>
<plugin name="SSE v1.5.97 (&quot;Special Edition&quot;)">
<description>Select this if you are using Skyrim Special Edition v1.5.97.</description>
<files>
<folder source="SE/SKSE/Plugins" destination="SKSE/Plugins" priority="0" />
</files>
<typeDescriptor>
<dependencyType>
<defaultType name="Optional" />
<patterns>
<pattern>
<dependencies>
<gameDependency version="1.6" />
</dependencies>
<type name="Optional" />
</pattern>
<pattern>
<dependencies>
<gameDependency version="1.5" />
</dependencies>
<type name="Recommended" />
</pattern>
</patterns>
</dependencyType>
</typeDescriptor>
</plugin>
</plugins>
</group>
</optionalFileGroups>
</installStep>
</installSteps>
</config>

View File

@@ -34,6 +34,7 @@ fn parse() {
"example_04.xml", "example_04.xml",
"example_05.xml", "example_05.xml",
"banana.xml", "banana.xml",
"po3tweaks.xml"
] { ] {
fomod::Config::load_from_file(get_xml(xml)) fomod::Config::load_from_file(get_xml(xml))
.unwrap_or_else(|e| panic!("Parse for {xml} with {}", err_to_string(e))); .unwrap_or_else(|e| panic!("Parse for {xml} with {}", err_to_string(e)));