Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-verification #124

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion hook/src/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ pub unsafe fn initialize() -> Result<()> {
)?;
HookUFunctionBind.enable()?;

if let Ok(server_name) = &globals().resolution.server_name {
if let Ok(server_name) = &globals().resolution.server_name
&& globals().meta.mods.iter().any(|m| m.gameplay_affecting)
{
GetServerName
.initialize(
std::mem::transmute(server_name.get_server_name.0),
Expand Down
2 changes: 2 additions & 0 deletions hook/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![feature(let_chains)]

mod hooks;
mod ue;

Expand Down
5 changes: 4 additions & 1 deletion mint_lib/src/mod_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub enum ApprovalStatus {
}

/// Whether a mod can be resolved by clients or not
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd, Hash, Serialize, Deserialize)]
pub enum ResolvableStatus {
Unresolvable(String),
Resolvable,
Expand Down Expand Up @@ -134,6 +134,7 @@ pub struct Meta {
pub struct MetaConfig {
pub disable_fix_exploding_gas: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SemverVersion {
pub major: u32,
Expand All @@ -153,7 +154,9 @@ pub struct MetaMod {
pub author: String,
pub approval: ApprovalStatus,
pub required: bool,
pub gameplay_affecting: bool,
}

impl Meta {
pub fn to_server_list_string(&self) -> String {
use itertools::Itertools;
Expand Down
1 change: 1 addition & 0 deletions src/gui/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ async fn integrate_async(
crate::integrate::integrate(
fsd_pak,
config,
store,
to_integrate.into_iter().zip(paths).collect(),
)
})
Expand Down
263 changes: 186 additions & 77 deletions src/gui/mod.rs

Large diffs are not rendered by default.

159 changes: 156 additions & 3 deletions src/integrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ use std::ffi::{OsStr, OsString};
use std::fs::OpenOptions;
use std::io::{self, BufReader, BufWriter, Cursor, ErrorKind, Read, Seek};
use std::path::{Path, PathBuf};
use std::sync::Arc;

use anyhow::{Context, Result};
use mint_lib::mod_info::{ApprovalStatus, Meta, MetaConfig, MetaMod, SemverVersion};
use mint_lib::mod_info::{ApprovalStatus, Meta, MetaConfig, MetaMod, ModioTags, SemverVersion};
use mint_lib::DRGInstallation;
use repak::PakWriter;
use serde::Deserialize;
use tracing::info;
use tracing::{debug, info};
use uasset_utils::splice::{
extract_tracked_statements, inject_tracked_statements, walk, AssetVersion, TrackedStatement,
};
use unreal_asset::reader::ArchiveTrait;

use crate::providers::ModInfo;
use crate::providers::{ModInfo, ModStore};
use crate::{get_pak_from_data, open_file};

use unreal_asset::{
Expand Down Expand Up @@ -162,6 +164,7 @@ static INTEGRATION_DIR: include_dir::Dir<'_> =
pub fn integrate<P: AsRef<Path>>(
path_pak: P,
config: MetaConfig,
store: Arc<ModStore>,
mods: Vec<(ModInfo, PathBuf)>,
) -> Result<(), IntegrationErr> {
let installation = DRGInstallation::from_pak_path(&path_pak).map_err(|e| IntegrationErr {
Expand Down Expand Up @@ -193,6 +196,13 @@ pub fn integrate<P: AsRef<Path>>(
.into_iter()
.map(PathBuf::from)
.collect::<Vec<_>>();

let fsd_lowercase_path_map = fsd_pak
.files()
.into_iter()
.map(|p| (p.to_ascii_lowercase(), p))
.collect::<HashMap<_, _>>();

let mut directories: HashMap<OsString, Dir> = HashMap::new();
for f in &paths {
let mut dir = &mut directories;
Expand Down Expand Up @@ -369,24 +379,52 @@ pub fn integrate<P: AsRef<Path>>(

let mut added_paths = HashSet::new();

let mut gameplay_affecting_results = HashMap::new();

for (mod_info, path) in &mods {
let raw_mod_file = open_file(path).map_err(|e| IntegrationErr {
mod_ctxt: Some(mod_info.clone()),
kind: IntegrationErrKind::Generic(e),
})?;

let mut buf = get_pak_from_data(Box::new(BufReader::new(raw_mod_file))).map_err(|e| {
IntegrationErr {
mod_ctxt: Some(mod_info.clone()),
kind: IntegrationErrKind::Generic(e),
}
})?;

let pak = repak::PakBuilder::new()
.reader(&mut buf)
.map_err(|e| IntegrationErr {
mod_ctxt: Some(mod_info.clone()),
kind: IntegrationErrKind::Repak(e),
})?;

let gameplay_affecting = if let Some(ModioTags {
approval_status, ..
}) = mod_info.modio_tags
{
approval_status != ApprovalStatus::Verified
} else {
check_gameplay_affecting(
&fsd_lowercase_path_map,
&mut fsd_pak_reader,
&fsd_pak,
&mut buf,
&pak,
)
.map_err(|e| IntegrationErr {
mod_ctxt: Some(mod_info.clone()),
kind: IntegrationErrKind::Generic(e),
})?
};

debug!(?mod_info, ?gameplay_affecting);

gameplay_affecting_results.insert(mod_info.resolution.url.clone(), gameplay_affecting);
store.update_gameplay_affecting_status(mod_info.resolution.url.clone(), gameplay_affecting);

let mount = Path::new(pak.mount_point());

for p in pak.files() {
Expand Down Expand Up @@ -588,6 +626,13 @@ pub fn integrate<P: AsRef<Path>>(
.as_ref()
.map(|t| t.approval_status)
.unwrap_or(ApprovalStatus::Sandbox),
gameplay_affecting: match info.modio_tags.as_ref().map(|t| t.approval_status) {
Some(ApprovalStatus::Verified) => false,
Some(_) => true,
None => *gameplay_affecting_results
.get(&info.resolution.url)
.unwrap_or(&true),
},
})
.collect(),
};
Expand All @@ -613,6 +658,114 @@ pub fn integrate<P: AsRef<Path>>(
Ok(())
}

pub fn check_gameplay_affecting<F, M>(
fsd_lowercase_path_map: &HashMap<String, String>,
fsd_pak: &mut F,
fsd_pak_reader: &repak::PakReader,
mod_pak: &mut M,
mod_pak_reader: &repak::PakReader,
) -> Result<bool>
where
F: Read + Seek,
M: Read + Seek,
{
debug!("check_gameplay_affecting");

let mount = Path::new(mod_pak_reader.mount_point());

let whitelist = [
"SoundWave",
"SoundCue",
"SoundClass",
"SoundMix",
"MaterialInstanceConstant",
"Material",
"SkeletalMesh",
"StaticMesh",
"Texture2D",
"AnimSequence",
"Skeleton",
"StringTable",
]
.into_iter()
.collect::<HashSet<_>>();

let check_asset = |data: Vec<u8>| -> Result<bool> {
debug!("check_asset");
let asset = unreal_asset::AssetBuilder::new(
Cursor::new(data),
unreal_asset::engine_version::EngineVersion::VER_UE4_27,
)
.skip_data(true)
.build()?;

for export in &asset.asset_data.exports {
let base = export.get_base_export();
// don't care about exported classes in this case
if base.outer_index.index == 0
&& base.class_index.is_import()
&& !asset
.get_import(base.class_index)
.map(|import| import.object_name.get_content(|c| whitelist.contains(c)))
.unwrap_or(false)
{
// invalid import or import name is not whitelisted, unknown
return Ok(true);
};
}

Ok(false)
};

let mod_lowercase_path_map = mod_pak_reader
.files()
.into_iter()
.map(|p| -> Result<(String, String)> {
let j = mount.join(&p);
let new_path = j
.strip_prefix("../../../")
.context("prefix does not match")?;
let new_path_str = &new_path.to_string_lossy().replace('\\', "/");

Ok((new_path_str.to_ascii_lowercase(), p))
})
.collect::<Result<HashMap<_, _>>>()?;

for lower in mod_lowercase_path_map.keys() {
if let Some((base, ext)) = lower.rsplit_once('.') {
if ["uasset", "uexp", "umap", "ubulk", "ufont"].contains(&ext) {
let key_uasset = format!("{base}.uasset");
let key_umap = format!("{base}.umap");
// check mod pak for uasset or umap
// if not found, check fsd pak for uasset or umap
let asset = if let Some(path) = mod_lowercase_path_map.get(&key_uasset) {
mod_pak_reader.get(path, mod_pak)?
} else if let Some(path) = mod_lowercase_path_map.get(&key_umap) {
mod_pak_reader.get(path, mod_pak)?
} else if let Some(path) = fsd_lowercase_path_map.get(&key_uasset) {
fsd_pak_reader.get(path, fsd_pak)?
} else if let Some(path) = fsd_lowercase_path_map.get(&key_umap) {
fsd_pak_reader.get(path, fsd_pak)?
} else {
// not found, unknown
return Ok(true);
};

let asset_result = check_asset(asset.clone())?;
debug!(?asset_result);

if asset_result {
debug!("GameplayAffecting: true");
debug!("{:#?}", asset.clone());
return Ok(true);
}
}
}
}

Ok(false)
}

type ImportChain<'a> = Vec<Import<'a>>;

struct Import<'a> {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub async fn resolve_unordered_and_integrate<P: AsRef<Path>>(
integrate::integrate(
game_path,
state.config.deref().into(),
state.store.clone(),
to_integrate.into_iter().zip(paths).collect(),
)
}
Expand Down
Loading
Loading