pub mod collision;
pub mod manifest;
pub mod redmod;
pub mod saves;
pub mod scanner;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use smallvec::SmallVec;
use crate::policies::{BareLayoutPolicy, ContentPolicy, DllOverridePolicy, StagingDllSearch};
use crate::traits::{ContentCategory, GamePlugin, ModSafety};
pub struct Cyberpunk2077;
pub static CYBERPUNK2077: Cyberpunk2077 = Cyberpunk2077;
const CYBERPUNK_SAVE_BREAKING_EXT: &[&str] = &["reds", "lua", "tweak", "xl", "yaml", "yls"];
const CYBERPUNK_SAVE_BREAKING_DIRS: &[&str] = &[
"r6/scripts",
"r6/tweaks",
"bin/x64/plugins/cyber_engine_tweaks/mods",
];
const CYBERPUNK_COSMETIC_EXT: &[&str] = &["archive", "xl", "png", "jpg", "dds", "tga", "ini"];
const CYBERPUNK_CONTENT_CATEGORIES: &[(&str, ContentCategory)] = &[
("archive", ContentCategory::Archive),
("dll", ContentCategory::Binary),
("so", ContentCategory::Binary),
("reds", ContentCategory::Script),
("lua", ContentCategory::Script),
("tweak", ContentCategory::Script),
("xl", ContentCategory::Script),
("yaml", ContentCategory::Config),
("yls", ContentCategory::Config),
("yml", ContentCategory::Config),
("ini", ContentCategory::Config),
("json", ContentCategory::Config),
("toml", ContentCategory::Config),
("xml", ContentCategory::Config),
("dds", ContentCategory::Texture),
("png", ContentCategory::Texture),
("tga", ContentCategory::Texture),
("jpg", ContentCategory::Texture),
];
const CYBERPUNK_CONTENT_POLICY: ContentPolicy = ContentPolicy {
save_breaking_ext: CYBERPUNK_SAVE_BREAKING_EXT,
cosmetic_ext: CYBERPUNK_COSMETIC_EXT,
save_breaking_dirs: CYBERPUNK_SAVE_BREAKING_DIRS,
categories: CYBERPUNK_CONTENT_CATEGORIES,
};
const KNOWN_PROXY_DLLS: &[&str] = &[
"version", "winmm", "dinput8", "d3d11", "dxgi", "winhttp", "xinput1_3", ];
const CYBERPUNK_DLL_POLICY: DllOverridePolicy = DllOverridePolicy {
proxy_dlls: KNOWN_PROXY_DLLS,
staging_search: StagingDllSearch::NestedModsBinX64,
};
const CYBERPUNK_BARE_LAYOUT_POLICY: BareLayoutPolicy = BareLayoutPolicy {
root_dirs: &[
"r6", "archive", "archives", "bin", "engine", "mods", "red4ext",
],
root_file_exts: &[],
case_insensitive_dirs: false,
};
impl GamePlugin for Cyberpunk2077 {
fn game_id(&self) -> &'static str {
"cyberpunk2077"
}
fn display_name(&self) -> &'static str {
"Cyberpunk 2077"
}
fn mod_directory(&self, install: &Path) -> PathBuf {
install.join("mods")
}
fn deploy(&self, staging: &Path, target: &Path) -> Result<()> {
if !target.exists() {
std::fs::create_dir_all(target)
.with_context(|| format!("failed to create {}", target.display()))?;
}
for entry in std::fs::read_dir(staging)
.with_context(|| format!("failed to read directory: {}", staging.display()))?
{
let entry = entry?;
let dst = target.join(entry.file_name());
if dst.exists() || dst.symlink_metadata().is_ok() {
if dst.is_dir() {
std::fs::remove_dir_all(&dst)
.with_context(|| format!("failed to remove {}", dst.display()))?;
} else {
std::fs::remove_file(&dst)
.with_context(|| format!("failed to remove {}", dst.display()))?;
}
}
modde_core::fs::symlink(&entry.path(), &dst)?;
}
Ok(())
}
fn post_deploy(&self, install: &Path) -> Result<()> {
redmod::deploy_if_available(install)
}
fn save_directory(&self) -> Option<PathBuf> {
let save_suffix = "pfx/drive_c/users/steamuser/Saved Games/CD Projekt Red/Cyberpunk 2077";
let heroic_prefixes =
modde_core::paths::home_dir().join("Games/Heroic/Prefixes/default/Cyberpunk 2077");
let heroic_path = heroic_prefixes.join(save_suffix);
if heroic_path.exists() {
return Some(heroic_path);
}
let compat = modde_core::paths::steam_common()
.parent()? .join("compatdata/1091500")
.join(save_suffix);
if compat.exists() {
return Some(compat);
}
None
}
fn supports_save_profiles(&self) -> bool {
true
}
fn classify_mod(&self, mod_dir: &Path) -> ModSafety {
CYBERPUNK_CONTENT_POLICY.classify_mod(mod_dir)
}
fn classify_extension(&self, ext: &str) -> ContentCategory {
CYBERPUNK_CONTENT_POLICY.classify_extension(ext)
}
fn wine_dll_overrides(&self, game_dir: &Path) -> SmallVec<[String; 4]> {
CYBERPUNK_DLL_POLICY.from_executable_dir(&self.executable_dir(game_dir))
}
fn wine_dll_overrides_from_staging(&self, staging: &Path) -> SmallVec<[String; 4]> {
CYBERPUNK_DLL_POLICY.from_staging(staging)
}
fn executable_dir(&self, install: &Path) -> PathBuf {
install.join("bin").join("x64")
}
fn archive_extensions(&self) -> &[&str] {
&["archive"]
}
fn steam_app_id_u32(&self) -> Option<u32> {
Some(1091500)
}
fn nexus_game_domain(&self) -> Option<&str> {
Some("cyberpunk2077")
}
fn nexus_game_id_u32(&self) -> Option<u32> {
Some(3333)
}
fn analyze_mod_archive(
&self,
extracted_dir: &Path,
) -> Option<modde_core::installer::InstallMethod> {
let info_json = extracted_dir.join("info.json");
if !info_json.is_file() {
return None;
}
let archives = extracted_dir.join("archives");
let archive = extracted_dir.join("archive");
if archives.is_dir() || archive.is_dir() {
return Some(modde_core::installer::InstallMethod::REDmod {
manifest: PathBuf::from("info.json"),
});
}
None
}
fn recognizes_bare_layout(&self, extracted_dir: &Path) -> bool {
CYBERPUNK_BARE_LAYOUT_POLICY.recognizes(extracted_dir)
}
}