Darktide Mod Manager #39

Merged
lucas merged 91 commits from feat/dtmm into master 2023-03-01 22:27:42 +01:00
6 changed files with 91 additions and 75 deletions
Showing only changes of commit ebcbdaeec0 - Show all commits

View file

@ -14,9 +14,9 @@ use sdk::filetype::lua;
use sdk::filetype::package::Package; use sdk::filetype::package::Package;
use sdk::murmur::Murmur64; use sdk::murmur::Murmur64;
use sdk::{ use sdk::{
Bundle, BundleDatabase, BundleFile, BundleFileType, BundleFileVariant, FromBinary, ToBinary, Bundle, BundleDatabase, BundleFile, BundleFileType, BundleFileVariant, FromBinary, ModConfig,
ToBinary,
}; };
use serde::Deserialize;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
use tokio::{fs, try_join}; use tokio::{fs, try_join};
use tracing::Instrument; use tracing::Instrument;
@ -176,7 +176,10 @@ async fn build_bundles(state: Arc<State>) -> Result<()> {
bundle.add_file(file); bundle.add_file(file);
let src = mod_dir.join(pkg_info.get_name()); let bundle_name = Murmur64::hash(pkg_info.get_name())
.to_string()
.to_ascii_lowercase();
let src = mod_dir.join(&bundle_name);
let dest = bundle_dir.clone(); let dest = bundle_dir.clone();
let pkg_name = pkg_info.get_name().clone(); let pkg_name = pkg_info.get_name().clone();
let mod_name = mod_info.get_name().clone(); let mod_name = mod_info.get_name().clone();
@ -200,7 +203,7 @@ async fn build_bundles(state: Arc<State>) -> Result<()> {
dest.display() dest.display()
); );
fs::hard_link(&src, dest.as_ref()).await.wrap_err_with(|| { fs::hard_link(&src, dest.as_ref()).await.wrap_err_with(|| {
format!("failed to hard link bundle {pkg_name} for mod {mod_name}") format!("failed to hard link bundle {pkg_name} for mod {mod_name}. src: {}, dest: {}", src.display(), dest.display())
}) })
} }
.instrument(span); .instrument(span);
@ -376,13 +379,6 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> {
Ok(()) Ok(())
} }
#[derive(Debug, Default, Deserialize)]
struct ModConfig {
name: String,
#[serde(default)]
description: String,
}
#[tracing::instrument(skip(state))] #[tracing::instrument(skip(state))]
pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> { pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> {
let data = fs::read(&info.path) let data = fs::read(&info.path)
@ -462,7 +458,7 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
.into_iter() .into_iter()
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect())) .map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
.collect(); .collect();
let info = ModInfo::new(mod_cfg.name, mod_cfg.description, packages); let info = ModInfo::new(mod_cfg, packages);
Ok(info) Ok(info)
} }

View file

@ -7,6 +7,7 @@ use druid::{
AppDelegate, Command, Data, DelegateCtx, Env, FileInfo, Handled, Lens, Selector, SingleUse, AppDelegate, Command, Data, DelegateCtx, Env, FileInfo, Handled, Lens, Selector, SingleUse,
Target, Target,
}; };
use sdk::ModConfig;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use crate::Config; use crate::Config;
@ -58,6 +59,27 @@ impl PackageInfo {
} }
} }
#[derive(Clone, Data, Debug)]
pub(crate) struct ModResourceInfo {
init: String,
data: String,
localization: String,
}
impl ModResourceInfo {
pub(crate) fn get_init(&self) -> &String {
&self.init
}
pub(crate) fn get_data(&self) -> &String {
&self.data
}
pub(crate) fn get_localization(&self) -> &String {
&self.localization
}
}
#[derive(Clone, Data, Debug, Lens)] #[derive(Clone, Data, Debug, Lens)]
pub(crate) struct ModInfo { pub(crate) struct ModInfo {
name: String, name: String,
@ -65,15 +87,22 @@ pub(crate) struct ModInfo {
enabled: bool, enabled: bool,
#[lens(ignore)] #[lens(ignore)]
packages: Vector<PackageInfo>, packages: Vector<PackageInfo>,
#[lens(ignore)]
resources: ModResourceInfo,
} }
impl ModInfo { impl ModInfo {
pub fn new(name: String, description: String, packages: Vector<PackageInfo>) -> Self { pub fn new(cfg: ModConfig, packages: Vector<PackageInfo>) -> Self {
Self { Self {
name, name: cfg.name,
description: Arc::new(description), description: Arc::new(cfg.description),
packages,
enabled: false, enabled: false,
packages,
resources: ModResourceInfo {
init: cfg.resources.init,
data: cfg.resources.data,
localization: cfg.resources.localization,
},
} }
} }
@ -84,6 +113,14 @@ impl ModInfo {
pub(crate) fn get_name(&self) -> &String { pub(crate) fn get_name(&self) -> &String {
&self.name &self.name
} }
pub(crate) fn get_enabled(&self) -> bool {
self.enabled
}
pub(crate) fn get_resources(&self) -> &ModResourceInfo {
&self.resources
}
} }
impl PartialEq for ModInfo { impl PartialEq for ModInfo {

View file

@ -7,8 +7,7 @@ use color_eyre::{Help, Report};
use futures::future::try_join_all; use futures::future::try_join_all;
use futures::StreamExt; use futures::StreamExt;
use sdk::filetype::package::Package; use sdk::filetype::package::Package;
use sdk::{Bundle, BundleFile}; use sdk::{Bundle, BundleFile, ModConfig};
use serde::Deserialize;
use tokio::fs::{self, File}; use tokio::fs::{self, File};
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
@ -36,16 +35,8 @@ pub(crate) fn command_definition() -> Command {
)) ))
} }
#[derive(Debug, Default, Deserialize)]
struct ProjectConfig {
#[serde(skip)]
dir: PathBuf,
name: String,
packages: Vec<PathBuf>,
}
#[tracing::instrument] #[tracing::instrument]
async fn find_project_config(dir: Option<PathBuf>) -> Result<ProjectConfig> { async fn find_project_config(dir: Option<PathBuf>) -> Result<ModConfig> {
let (path, mut file) = if let Some(path) = dir { let (path, mut file) = if let Some(path) = dir {
let file = File::open(&path.join(PROJECT_CONFIG_NAME)) let file = File::open(&path.join(PROJECT_CONFIG_NAME))
.await .await
@ -83,7 +74,7 @@ async fn find_project_config(dir: Option<PathBuf>) -> Result<ProjectConfig> {
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf).await?; file.read_to_string(&mut buf).await?;
let mut cfg: ProjectConfig = serde_sjson::from_str(&buf)?; let mut cfg: ModConfig = serde_sjson::from_str(&buf)?;
cfg.dir = path; cfg.dir = path;
Ok(cfg) Ok(cfg)
} }
@ -210,23 +201,23 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
}) })
}); });
let bundles = try_join_all(tasks).await?; let bundles = try_join_all(tasks)
.await
.wrap_err("failed to build mod bundles")?;
let mod_file = { let config_file = {
let mut path = cfg.dir.join(&cfg.name); let path = cfg.dir.join("dtmt.cfg");
path.set_extension("mod"); fs::read(&path)
fs::read(path).await? .await
.wrap_err_with(|| format!("failed to read mod config at {}", path.display()))?
}; };
let config_file = fs::read(cfg.dir.join("dtmt.cfg")).await?;
{ {
let dest = dest.clone(); let dest = dest.clone();
let name = cfg.name.clone(); let name = cfg.name.clone();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let mut archive = Archive::new(name); let mut archive = Archive::new(name);
archive.add_mod_file(mod_file);
archive.add_config(config_file); archive.add_config(config_file);
for bundle in bundles { for bundle in bundles {

View file

@ -8,13 +8,19 @@ use futures::{StreamExt, TryStreamExt};
use string_template::Template; use string_template::Template;
use tokio::fs::{self, DirBuilder}; use tokio::fs::{self, DirBuilder};
const TEMPLATES: [(&str, &str); 6] = [ const TEMPLATES: [(&str, &str); 5] = [
( (
"dtmt.cfg", "dtmt.cfg",
r#"name = "{{name}}" r#"name = "{{name}}"
description = "An elaborate description of my cool game mod!" description = "An elaborate description of my cool game mod!"
version = "0.1.0" version = "0.1.0"
resources = {
script = "scripts/mods/{{name}}/init"
data = "scripts/mods/{{name}}/data"
localization = "scripts/mods/{{name}}/locationzation"
}
packages = [ packages = [
"packages/{{name}}" "packages/{{name}}"
] ]
@ -23,21 +29,6 @@ depends = [
"dmf" "dmf"
] ]
"#, "#,
),
(
"{{name}}.mod",
r#"return {
run = function()
fassert(rawget(_G, "new_mod"), "`{{title}}` encountered an error loading the Darktide Mod Framework.")
new_mod("{{name}}", {
mod_script = "scripts/mods/{{name}}/{{name}}",
mod_data = "scripts/mods/{{name}}/{{name}}_data",
mod_localization = "scripts/mods/{{name}}/{{name}}_localization",
})
end,
packages = {},
}"#,
), ),
( (
"packages/{{name}}.package", "packages/{{name}}.package",
@ -47,7 +38,7 @@ depends = [
"#, "#,
), ),
( (
"scripts/mods/{{name}}/{{name}}.lua", "scripts/mods/{{name}}/init.lua",
r#"local mod = get_mod("{{name}}") r#"local mod = get_mod("{{name}}")
-- Your mod code goes here. -- Your mod code goes here.
@ -55,7 +46,7 @@ depends = [
"#, "#,
), ),
( (
"scripts/mods/{{name}}/{{name}}_data.lua", "scripts/mods/{{name}}/data.lua",
r#"local mod = get_mod("{{name}}") r#"local mod = get_mod("{{name}}")
return { return {
@ -65,7 +56,7 @@ return {
}"#, }"#,
), ),
( (
"scripts/mods/{{name}}/{{name}}_localization.lua", "scripts/mods/{{name}}/localization.lua",
r#"return { r#"return {
mod_description = { mod_description = {
en = "An elaborate description of my cool game mod!", en = "An elaborate description of my cool game mod!",
@ -127,8 +118,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
promptly::prompt_default("The mod identifier name", default)? promptly::prompt_default("The mod identifier name", default)?
}; };
tracing::debug!(root = %root.display()); tracing::debug!(root = %root.display(), title, name);
tracing::debug!(title, name);
let mut data = HashMap::new(); let mut data = HashMap::new();
data.insert("name", name.as_str()); data.insert("name", name.as_str());

View file

@ -12,7 +12,6 @@ use zip::ZipWriter;
pub struct Archive { pub struct Archive {
name: String, name: String,
bundles: Vec<Bundle>, bundles: Vec<Bundle>,
mod_file: Option<Vec<u8>>,
config_file: Option<Vec<u8>>, config_file: Option<Vec<u8>>,
} }
@ -21,7 +20,6 @@ impl Archive {
Self { Self {
name, name,
bundles: Vec::new(), bundles: Vec::new(),
mod_file: None,
config_file: None, config_file: None,
} }
} }
@ -30,10 +28,6 @@ impl Archive {
self.bundles.push(bundle) self.bundles.push(bundle)
} }
pub fn add_mod_file(&mut self, content: Vec<u8>) {
self.mod_file = Some(content);
}
pub fn add_config(&mut self, content: Vec<u8>) { pub fn add_config(&mut self, content: Vec<u8>) {
self.config_file = Some(content); self.config_file = Some(content);
} }
@ -42,11 +36,6 @@ impl Archive {
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let mod_file = self
.mod_file
.as_ref()
.ok_or_else(|| eyre::eyre!("Mod file is missing in mod archive"))?;
let config_file = self let config_file = self
.config_file .config_file
.as_ref() .as_ref()
@ -64,13 +53,6 @@ impl Archive {
let base_path = PathBuf::from(&self.name); let base_path = PathBuf::from(&self.name);
{
let mut name = base_path.join(&self.name);
name.set_extension("mod");
zip.start_file(name.to_string_lossy(), Default::default())?;
zip.write_all(mod_file)?;
}
{ {
let name = base_path.join("dtmt.cfg"); let name = base_path.join("dtmt.cfg");
zip.start_file(name.to_string_lossy(), Default::default())?; zip.start_file(name.to_string_lossy(), Default::default())?;

View file

@ -9,3 +9,23 @@ pub use bundle::database::BundleDatabase;
pub use bundle::decompress; pub use bundle::decompress;
pub use bundle::{Bundle, BundleFile, BundleFileType, BundleFileVariant}; pub use bundle::{Bundle, BundleFile, BundleFileType, BundleFileVariant};
pub use context::Context; pub use context::Context;
#[derive(Clone, Debug, Default, serde::Deserialize)]
pub struct ModConfigResources {
pub init: String,
pub data: String,
pub localization: String,
}
#[derive(Clone, Debug, Default, serde::Deserialize)]
pub struct ModConfig {
#[serde(skip)]
pub dir: std::path::PathBuf,
pub name: String,
pub description: String,
pub version: String,
pub packages: Vec<std::path::PathBuf>,
pub resources: ModConfigResources,
#[serde(default)]
pub depends: Vec<String>,
}