Darktide Mod Manager #39

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

View file

@ -58,14 +58,14 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
Bundle::from_binary(&ctx, name, binary).wrap_err("Failed to open bundle file")? Bundle::from_binary(&ctx, name, binary).wrap_err("Failed to open bundle file")?
}; };
if let Some(_name) = matches.get_one::<String>("replace") { if let Some(name) = matches.get_one::<String>("replace") {
let mut file = File::open(&file_path) let mut file = File::open(&file_path)
.await .await
.wrap_err_with(|| format!("failed to open '{}'", file_path.display()))?; .wrap_err_with(|| format!("failed to open '{}'", file_path.display()))?;
if let Some(variant) = bundle if let Some(variant) = bundle
.files_mut() .files_mut()
.filter(|file| file.matches_name(_name)) .filter(|file| file.matches_name(name.clone()))
// TODO: Handle file variants // TODO: Handle file variants
.find_map(|file| file.variants_mut().next()) .find_map(|file| file.variants_mut().next())
{ {
@ -75,7 +75,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
.wrap_err("failed to read input file")?; .wrap_err("failed to read input file")?;
variant.set_data(data); variant.set_data(data);
} else { } else {
let err = eyre::eyre!("No file '{}' in this bundle.", _name) let err = eyre::eyre!("No file '{}' in this bundle.", name)
.with_suggestion(|| { .with_suggestion(|| {
format!( format!(
"Run '{} bundle list {}' to list the files in this bundle.", "Run '{} bundle list {}' to list the files in this bundle.",
@ -87,7 +87,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
format!( format!(
"Use '{} bundle inject --add {} {} {}' to add it as a new file", "Use '{} bundle inject --add {} {} {}' to add it as a new file",
clap::crate_name!(), clap::crate_name!(),
_name, name,
bundle_path.display(), bundle_path.display(),
file_path.display() file_path.display()
) )

View file

@ -64,7 +64,7 @@ where
let v = &f.variants()[0]; let v = &f.variants()[0];
println!( println!(
"\t{}.{}: {} bytes", "\t{}.{}: {} bytes",
f.base_name(), f.base_name().display(),
f.file_type().ext_name(), f.file_type().ext_name(),
v.size() v.size()
); );

View file

@ -104,17 +104,23 @@ pub(crate) fn command_definition() -> Command {
pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
match matches.subcommand() { match matches.subcommand() {
Some(("lookup", sub_matches)) => { Some(("lookup", sub_matches)) => {
let hash = sub_matches let hash = {
.get_one::<u64>("hash") let s = sub_matches
.expect("required argument not found"); .get_one::<String>("hash")
.expect("required argument not found");
u64::from_str_radix(s, 16)
.wrap_err("failed to parse argument as hexadecimal string")?
};
let groups = sub_matches let groups = sub_matches
.get_many::<HashGroup>("group") .get_many::<HashGroup>("group")
.unwrap_or_default(); .unwrap_or_default();
for group in groups { for group in groups {
let value = ctx.lookup_hash(*hash, (*group).into()); if let IdString64::String(value) = ctx.lookup_hash(hash, (*group).into()) {
println!("{value}"); println!("{group}: {value}");
}
} }
Ok(()) Ok(())

View file

@ -9,7 +9,7 @@ use serde::Serialize;
use crate::binary::sync::*; use crate::binary::sync::*;
use crate::filetype::*; use crate::filetype::*;
use crate::murmur::{HashGroup, Murmur64}; use crate::murmur::{HashGroup, IdString64, Murmur64};
use super::EntryHeader; use super::EntryHeader;
@ -499,7 +499,7 @@ bitflags! {
pub struct BundleFile { pub struct BundleFile {
file_type: BundleFileType, file_type: BundleFileType,
name: String, name: IdString64,
variants: Vec<BundleFileVariant>, variants: Vec<BundleFileVariant>,
props: Properties, props: Properties,
} }
@ -508,7 +508,7 @@ impl BundleFile {
pub fn new(name: String, file_type: BundleFileType) -> Self { pub fn new(name: String, file_type: BundleFileType) -> Self {
Self { Self {
file_type, file_type,
name, name: name.into(),
variants: Vec::new(), variants: Vec::new(),
props: Properties::empty(), props: Properties::empty(),
} }
@ -576,7 +576,7 @@ impl BundleFile {
let mut w = Cursor::new(Vec::new()); let mut w = Cursor::new(Vec::new());
w.write_u64(self.file_type.hash().into())?; w.write_u64(self.file_type.hash().into())?;
w.write_u64(Murmur64::hash(self.name.as_bytes()).into())?; w.write_u64(self.name.to_murmur64().into())?;
w.write_u32(self.variants.len() as u32)?; w.write_u32(self.variants.len() as u32)?;
// TODO: Figure out what this is // TODO: Figure out what this is
@ -628,12 +628,12 @@ impl BundleFile {
self.props self.props
} }
pub fn base_name(&self) -> &String { pub fn base_name(&self) -> &IdString64 {
&self.name &self.name
} }
pub fn name(&self, decompiled: bool, variant: Option<u32>) -> String { pub fn name(&self, decompiled: bool, variant: Option<u32>) -> String {
let mut s = self.name.clone(); let mut s = self.name.display().to_string();
s.push('.'); s.push('.');
if let Some(variant) = variant { if let Some(variant) = variant {
@ -652,10 +652,18 @@ impl BundleFile {
pub fn matches_name<S>(&self, name: S) -> bool pub fn matches_name<S>(&self, name: S) -> bool
where where
S: AsRef<str>, S: Into<IdString64>,
{ {
let name = name.as_ref(); let name = name.into();
self.name == name || self.name(false, None) == name || self.name(true, None) == name if self.name == name {
return true;
}
if let IdString64::String(name) = name {
self.name(false, None) == name || self.name(true, None) == name
} else {
false
}
} }
pub fn file_type(&self) -> BundleFileType { pub fn file_type(&self) -> BundleFileType {

View file

@ -67,7 +67,11 @@ impl Bundle {
path.file_name() path.file_name()
.and_then(|name| name.to_str()) .and_then(|name| name.to_str())
.and_then(|name| Murmur64::try_from(name).ok()) .and_then(|name| Murmur64::try_from(name).ok())
.map(|hash| ctx.lookup_hash(hash, HashGroup::Filename)) .map(|hash| {
ctx.lookup_hash(hash, HashGroup::Filename)
.display()
.to_string()
})
.unwrap_or_else(|| path.display().to_string()) .unwrap_or_else(|| path.display().to_string())
} }
@ -220,7 +224,7 @@ impl Bundle {
for file in self.files.iter() { for file in self.files.iter() {
w.write_u64(file.file_type().into())?; w.write_u64(file.file_type().into())?;
w.write_u64(Murmur64::hash(file.base_name().as_bytes()).into())?; w.write_u64(file.base_name().to_murmur64().into())?;
w.write_u32(file.props().bits())?; w.write_u32(file.props().bits())?;
} }

View file

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::murmur::{Dictionary, HashGroup, Murmur32, Murmur64}; use crate::murmur::{Dictionary, HashGroup, IdString64, Murmur32, Murmur64};
pub struct Context { pub struct Context {
pub lookup: Dictionary, pub lookup: Dictionary,
@ -21,17 +21,17 @@ impl Context {
} }
} }
pub fn lookup_hash<M>(&self, hash: M, group: HashGroup) -> String pub fn lookup_hash<M>(&self, hash: M, group: HashGroup) -> IdString64
where where
M: Into<Murmur64>, M: Into<Murmur64>,
{ {
let hash = hash.into(); let hash = hash.into();
if let Some(s) = self.lookup.lookup(hash, group) { if let Some(s) = self.lookup.lookup(hash, group) {
tracing::debug!(%hash, string = s, "Murmur64 lookup successful"); tracing::debug!(%hash, string = s, "Murmur64 lookup successful");
s.to_owned() s.to_string().into()
} else { } else {
tracing::debug!(%hash, "Murmur64 lookup failed"); tracing::debug!(%hash, "Murmur64 lookup failed");
format!("{hash:016X}") hash.into()
} }
} }

View file

@ -201,7 +201,10 @@ impl Package {
let t = BundleFileType::from(r.read_u64()?); let t = BundleFileType::from(r.read_u64()?);
let hash = Murmur64::from(r.read_u64()?); let hash = Murmur64::from(r.read_u64()?);
let path = ctx.lookup_hash(hash, HashGroup::Filename); let path = ctx.lookup_hash(hash, HashGroup::Filename);
inner.entry(t).or_default().insert(PathBuf::from(path)); inner
.entry(t)
.or_default()
.insert(PathBuf::from(path.display().to_string()));
} }
let pkg = Self { let pkg = Self {

View file

@ -236,3 +236,93 @@ impl<'de> Deserialize<'de> for Murmur32 {
deserializer.deserialize_any(Self(0)) deserializer.deserialize_any(Self(0))
} }
} }
// This type encodes the fact that when reading in a bundle, we don't always have a dictionary
// entry for every hash in there. So we do want to have the real string available when needed,
// but at the same time retain the original hash information for when we don't.
// This is especially important when wanting to write back the read bundle, as the hashes need to
// stay the same.
// The previous system of always turning hashes into strings worked well for the purpose of
// displaying hashes, but would have made it very hard to turn a stringyfied hash back into
// an actual hash.
#[derive(Clone, Debug, Eq)]
pub enum IdString64 {
Hash(Murmur64),
String(String),
}
impl IdString64 {
pub fn to_murmur64(&self) -> Murmur64 {
match self {
Self::Hash(hash) => *hash,
Self::String(s) => Murmur64::hash(s.as_bytes()),
}
}
pub fn display(&self) -> IdString64Display {
let s = match self {
IdString64::Hash(hash) => hash.to_string(),
IdString64::String(s) => s.clone(),
};
IdString64Display(s)
}
pub fn is_string(&self) -> bool {
match self {
IdString64::Hash(_) => false,
IdString64::String(_) => true,
}
}
pub fn is_hash(&self) -> bool {
match self {
IdString64::Hash(_) => true,
IdString64::String(_) => false,
}
}
}
impl From<String> for IdString64 {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<Murmur64> for IdString64 {
fn from(value: Murmur64) -> Self {
Self::Hash(value)
}
}
impl From<IdString64> for Murmur64 {
fn from(value: IdString64) -> Self {
value.to_murmur64()
}
}
impl PartialEq for IdString64 {
fn eq(&self, other: &Self) -> bool {
self.to_murmur64() == other.to_murmur64()
}
}
pub struct IdString64Display(String);
impl std::fmt::Display for IdString64Display {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::UpperHex for IdString64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::UpperHex::fmt(&self.to_murmur64(), f)
}
}
impl std::fmt::LowerHex for IdString64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::LowerHex::fmt(&self.to_murmur64(), f)
}
}