Implement non-bundled mods #125
1 changed files with 58 additions and 48 deletions
|
@ -31,7 +31,7 @@ fn find_archive_file<R: Read + Seek>(
|
||||||
// from legacy mods.
|
// from legacy mods.
|
||||||
// 1. Create a global function `new_mod` that stores
|
// 1. Create a global function `new_mod` that stores
|
||||||
// the relevant bits in global variables.
|
// the relevant bits in global variables.
|
||||||
// 2. Run the `.mod` file, which will merely return a table.
|
// 2. Run the `.mod` file, which will return a table.
|
||||||
// 3. Run the `run` function from that table.
|
// 3. Run the `run` function from that table.
|
||||||
// 4. Access the global variables from #1.
|
// 4. Access the global variables from #1.
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
@ -203,6 +203,37 @@ end
|
||||||
// still end up creating tarbombs and Nexus does its own re-packaging.
|
// still end up creating tarbombs and Nexus does its own re-packaging.
|
||||||
#[tracing::instrument(skip(archive))]
|
#[tracing::instrument(skip(archive))]
|
||||||
fn extract_mod_config<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<(ModConfig, String)> {
|
fn extract_mod_config<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<(ModConfig, String)> {
|
||||||
|
let legacy_mod_data = if let Some(name) = find_archive_file(archive, ".mod") {
|
||||||
|
let (mod_id, resources) = {
|
||||||
|
let mut f = archive
|
||||||
|
.by_name(&name)
|
||||||
|
.wrap_err("Failed to read `.mod` file from archive")?;
|
||||||
|
|
||||||
|
let mut buf = Vec::with_capacity(f.size() as usize);
|
||||||
|
f.read_to_end(&mut buf)
|
||||||
|
.wrap_err("Failed to read `.mod` file from archive")?;
|
||||||
|
|
||||||
|
let data = String::from_utf8(buf).wrap_err("`.mod` file is not valid UTF-8")?;
|
||||||
|
parse_mod_id_file(&data)
|
||||||
|
.wrap_err("Invalid `.mod` file")
|
||||||
|
.note(
|
||||||
|
"The `.mod` file's `run` function may not contain any additional logic \
|
||||||
|
besides the default.",
|
||||||
|
)
|
||||||
|
.suggestion("Contact the mod author to fix this.")?
|
||||||
|
};
|
||||||
|
|
||||||
|
let root = if let Some(index) = name.rfind('/') {
|
||||||
|
name[..index].to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((mod_id, resources, root))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(name) = find_archive_file(archive, "dtmt.cfg") {
|
if let Some(name) = find_archive_file(archive, "dtmt.cfg") {
|
||||||
let mut f = archive
|
let mut f = archive
|
||||||
.by_name(&name)
|
.by_name(&name)
|
||||||
|
@ -214,52 +245,30 @@ fn extract_mod_config<R: Read + Seek>(archive: &mut ZipArchive<R>) -> Result<(Mo
|
||||||
|
|
||||||
let data = String::from_utf8(buf).wrap_err("Mod config is not valid UTF-8")?;
|
let data = String::from_utf8(buf).wrap_err("Mod config is not valid UTF-8")?;
|
||||||
|
|
||||||
let cfg = serde_sjson::from_str(&data).wrap_err("Failed to deserialize mod config")?;
|
let mut cfg: ModConfig = serde_sjson::from_str(&data)
|
||||||
|
.wrap_err("Failed to deserialize mod config")
|
||||||
|
.suggestion("Contact the mod author to fix this.")?;
|
||||||
|
|
||||||
|
if let Some((mod_id, resources, root)) = legacy_mod_data {
|
||||||
|
if cfg.id != mod_id {
|
||||||
|
let err = eyre::eyre!("Mod ID in `dtmt.cfg` does not match mod ID in `.mod` file");
|
||||||
|
return Err(err).suggestion("Contact the mod author to fix this.");
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.resources = resources;
|
||||||
|
|
||||||
|
Ok((cfg, root))
|
||||||
|
} else {
|
||||||
let root = name
|
let root = name
|
||||||
.strip_suffix("dtmt.cfg")
|
.strip_suffix("dtmt.cfg")
|
||||||
.expect("String must end with that suffix")
|
.expect("String must end with that suffix")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
Ok((cfg, root))
|
Ok((cfg, root))
|
||||||
} else if let Some(name) = find_archive_file(archive, ".mod") {
|
}
|
||||||
let (mod_id, resources) = {
|
|
||||||
let mut f = archive
|
|
||||||
.by_name(&name)
|
|
||||||
.wrap_err("Failed to read `.mod` file from archive")?;
|
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(f.size() as usize);
|
|
||||||
f.read_to_end(&mut buf)
|
|
||||||
.wrap_err("Failed to read `.mod` file from archive")?;
|
|
||||||
|
|
||||||
let data = String::from_utf8(buf).wrap_err("`.mod` file is not valid UTF-8")?;
|
|
||||||
parse_mod_id_file(&data).wrap_err("Invalid `.mod` file")?
|
|
||||||
};
|
|
||||||
|
|
||||||
let cfg = ModConfig {
|
|
||||||
bundled: false,
|
|
||||||
dir: PathBuf::new(),
|
|
||||||
id: mod_id.clone(),
|
|
||||||
name: mod_id,
|
|
||||||
summary: String::new(),
|
|
||||||
version: String::new(),
|
|
||||||
description: None,
|
|
||||||
author: None,
|
|
||||||
image: None,
|
|
||||||
categories: Vec::new(),
|
|
||||||
packages: Vec::new(),
|
|
||||||
resources,
|
|
||||||
depends: Vec::new(),
|
|
||||||
};
|
|
||||||
let root = if let Some(index) = name.rfind('/') {
|
|
||||||
name[..index].to_string()
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((cfg, root))
|
|
||||||
} else {
|
} else {
|
||||||
eyre::bail!(
|
eyre::bail!(
|
||||||
"Mod needs either a config file or `.mod` file. \
|
"Mod needs a config file or `.mod` file. \
|
||||||
Please get in touch with the author to provide a properly packaged mod."
|
Please get in touch with the author to provide a properly packaged mod."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -322,11 +331,11 @@ fn extract_legacy_mod<R: Read + Seek>(
|
||||||
.wrap_err_with(|| format!("Failed to get file at index {}", i))?;
|
.wrap_err_with(|| format!("Failed to get file at index {}", i))?;
|
||||||
|
|
||||||
let Some(name) = f.enclosed_name().map(|p| p.to_path_buf()) else {
|
let Some(name) = f.enclosed_name().map(|p| p.to_path_buf()) else {
|
||||||
let err = eyre::eyre!("File name in archive is not a safe path value.");
|
let err = eyre::eyre!("File name in archive is not a safe path value.").suggestion(
|
||||||
return Err(err).with_suggestion(|| {
|
|
||||||
"Only use well-known applications to create the ZIP archive, \
|
"Only use well-known applications to create the ZIP archive, \
|
||||||
and don't create paths that point outside the archive directory."
|
and don't create paths that point outside the archive directory.",
|
||||||
});
|
);
|
||||||
|
return Err(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(suffix) = name.strip_prefix(&root) else {
|
let Ok(suffix) = name.strip_prefix(&root) else {
|
||||||
|
@ -430,10 +439,11 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result<Mod
|
||||||
let img = match ImageBuf::from_data(&buf) {
|
let img = match ImageBuf::from_data(&buf) {
|
||||||
Ok(img) => img,
|
Ok(img) => img,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let err = Report::msg(err.to_string()).wrap_err("Invalid image data");
|
let err = Report::msg(err.to_string())
|
||||||
return Err(err).with_suggestion(|| {
|
.wrap_err("Invalid image data")
|
||||||
"Supported formats are: PNG, JPEG, Bitmap and WebP".to_string()
|
.note("Supported formats are: PNG, JPEG, Bitmap and WebP")
|
||||||
});
|
.suggestion("Contact the mod author to fix this");
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue