All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux
For most of the game files, we don't know the actual name, only the hash of that name. To still allow building bundles that contain files with that name (e.g. to override a game file with a custom one), there needs to be a way to tell DTMT to name a file such that its hash is the same as the one in the game. The initial idea was to just expect the file name on disk to be the hash, but that wouldn't allow for arbitrary folder structures anymore. So instead, there is now a new, optional setting in `dtmt.cfg`, where the modder can map a file path to an override name.
102 lines
2.6 KiB
Rust
102 lines
2.6 KiB
Rust
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
|
|
use color_eyre::eyre::{OptionExt as _, WrapErr as _};
|
|
use color_eyre::Result;
|
|
use serde::{Deserialize, Serialize};
|
|
use steamlocate::SteamDir;
|
|
use time::OffsetDateTime;
|
|
|
|
pub use log::*;
|
|
|
|
mod log;
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
pub struct ModConfigResources {
|
|
pub init: PathBuf,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub data: Option<PathBuf>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub localization: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ModOrder {
|
|
Before,
|
|
After,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
#[serde(untagged)]
|
|
pub enum ModDependency {
|
|
ID(String),
|
|
Config { id: String, order: ModOrder },
|
|
}
|
|
|
|
// A bit dumb, but serde doesn't support literal values with the
|
|
// `default` attribute, only paths.
|
|
fn default_true() -> bool {
|
|
true
|
|
}
|
|
|
|
// Similarly dumb, as the `skip_serializing_if` attribute needs a function
|
|
fn is_true(val: &bool) -> bool {
|
|
*val
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
pub struct ModConfig {
|
|
#[serde(skip)]
|
|
pub dir: PathBuf,
|
|
pub id: String,
|
|
pub name: String,
|
|
pub summary: String,
|
|
pub version: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub author: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub image: Option<PathBuf>,
|
|
#[serde(default)]
|
|
pub categories: Vec<String>,
|
|
#[serde(default)]
|
|
pub packages: Vec<PathBuf>,
|
|
pub resources: ModConfigResources,
|
|
#[serde(default)]
|
|
pub depends: Vec<ModDependency>,
|
|
#[serde(default = "default_true", skip_serializing_if = "is_true")]
|
|
pub bundled: bool,
|
|
#[serde(default)]
|
|
pub name_overrides: HashMap<String, String>,
|
|
}
|
|
|
|
pub const STEAMAPP_ID: u32 = 1361210;
|
|
|
|
#[derive(Debug)]
|
|
pub struct GameInfo {
|
|
pub path: PathBuf,
|
|
pub last_updated: OffsetDateTime,
|
|
}
|
|
|
|
pub fn collect_game_info() -> Result<Option<GameInfo>> {
|
|
let dir = SteamDir::locate().wrap_err("Failed to locate Steam installation")?;
|
|
|
|
let found = dir
|
|
.find_app(STEAMAPP_ID)
|
|
.wrap_err("Failed to look up game by Steam app ID")?;
|
|
|
|
let Some((app, _)) = found else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let last_updated = app
|
|
.last_updated
|
|
.ok_or_eyre("Missing field 'last_updated'")?;
|
|
|
|
Ok(Some(GameInfo {
|
|
path: app.install_dir.into(),
|
|
last_updated: last_updated.into(),
|
|
}))
|
|
}
|