diff --git a/Cargo.lock b/Cargo.lock index 52cdbfd..f3ca04b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "as_derive_utils" version = "0.11.0" @@ -160,6 +166,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -271,6 +288,15 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f3b219d28b6e3b4ac87bc1fc522e0803ab22e055da177bff0068c4150c61a6" +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.13" @@ -468,6 +494,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -680,6 +712,29 @@ dependencies = [ "typewit", ] +[[package]] +name = "lazy-regex" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576c8060ecfdf2e56995cf3274b4f2d71fa5e4fa3607c1c0b63c10180ee58741" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9efb9e65d4503df81c615dc33ff07042a9408ac7f26b45abee25566f7fbfd12c" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.74", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -712,6 +767,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "libudis86-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -744,6 +809,15 @@ dependencies = [ "crc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.4" @@ -759,6 +833,16 @@ dependencies = [ "adler", ] +[[package]] +name = "mmap-fixed-fixed" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "mod_util" version = "0.4.1" @@ -783,6 +867,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "ntapi" version = "0.4.1" @@ -954,6 +1044,17 @@ dependencies = [ "hmac", ] +[[package]] +name = "pdb" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863" +dependencies = [ + "fallible-iterator", + "scroll", + "uuid", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -1083,6 +1184,18 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "region" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach2", + "windows-sys 0.52.0", +] + [[package]] name = "repr_offset" version = "0.2.2" @@ -1092,6 +1205,32 @@ dependencies = [ "tstr", ] +[[package]] +name = "retour" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9af44d40e2400b44d491bfaf8eae111b09f23ac4de6e92728e79d93e699c527" +dependencies = [ + "cfg-if", + "generic-array", + "libc", + "libudis86-sys", + "mmap-fixed-fixed", + "once_cell", + "region", + "slice-pool2", +] + +[[package]] +name = "rivets" +version = "0.1.0" +dependencies = [ + "abi_stable", + "retour", + "rivets-macros", + "rivets-shared", +] + [[package]] name = "rivets-injector" version = "1.1.0" @@ -1103,10 +1242,38 @@ dependencies = [ "dll-syringe", "libloading 0.8.5", "mod_util", + "rivets", "thiserror", "windows", ] +[[package]] +name = "rivets-macros" +version = "0.1.0" +dependencies = [ + "anyhow", + "darling", + "lazy-regex", + "proc-macro2", + "quote", + "rivets-shared", + "syn 2.0.74", +] + +[[package]] +name = "rivets-shared" +version = "0.1.0" +dependencies = [ + "abi_stable", + "anyhow", + "cpp_demangle", + "pdb", + "serde", + "syn 2.0.74", + "undname", + "windows", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1264,6 +1431,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "slice-pool2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8" + [[package]] name = "smallvec" version = "1.13.2" @@ -1449,12 +1622,33 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" +[[package]] +name = "undname" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f76889af53deca091b038f6ebb04efa45fcba08908e69c0049e8b8c281f16f9" +dependencies = [ + "arrayvec", + "bitflags 2.6.0", + "bstr", + "bumpalo", + "nonmax", + "smallvec", + "thiserror", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + [[package]] name = "version_check" version = "0.9.5" @@ -1550,7 +1744,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1632,6 +1826,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index dd5c73e..d94ef26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,14 @@ mod_util = { git = "https://github.com/fgardt/factorio-scanner" } [target.'cfg(windows)'.dependencies] anyhow = "1.0" -dll-syringe = "0.15.2" +dll-syringe = "0.15" windows = { version = "0.58.0", features = [ "Win32", "Win32_System_Threading", "Win32_System_Pipes", "Win32_Security", ] } +rivets = { git = "https://github.com/factorio-rivets/rivets-rs" } [target.'cfg(unix)'.dependencies] abi_stable = "0.11.3" @@ -35,4 +36,4 @@ crate-type = ["cdylib"] [profile.release] strip = true -lto = true +lto = true \ No newline at end of file diff --git a/src/windows.rs b/src/windows.rs index efe1c8b..d1f81d8 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,9 +1,12 @@ //! A small windows application to inject the Rivets DLL into Factorio. use crate::common; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use dll_syringe::process::{BorrowedProcess, ProcessModule}; use dll_syringe::{process::OwnedProcess, Syringe}; +use mod_util::mod_list::ModList; +use mod_util::mod_loader::ModError; +use rivets::SymbolCache; use std::ffi::CString; use std::io; use std::os::windows::io::FromRawHandle; @@ -39,20 +42,19 @@ fn inject_dll( fn rpc( syringe: &Syringe, module: ProcessModule, - read_path: impl AsRef, - write_path: impl AsRef, + symbol_cache: &SymbolCache, ) -> Result<()> { - let rpc = unsafe { - syringe.get_payload_procedure:: Option>( - module, - "rivetslib_setup", - ) - }? - .ok_or_else(|| anyhow!("Failed to get RPC procedure"))?; - match rpc.call( - &read_path.as_ref().to_path_buf(), - &write_path.as_ref().to_path_buf(), - )? { + type Rpc = fn(symbol_cache: SymbolCache) -> Option; + + let rpc = unsafe { syringe.get_payload_procedure::(module, "rivets_finalize") } + .context("Failed to get RPC procedure")? + .ok_or_else(|| { + anyhow!("Failed to get RPC procedure. rivets_finalize does not exist in rivets.dll") + })?; + + println!("RPC procedure address located."); + + match rpc.call(symbol_cache).context("RPC paniced")? { Some(err) => bail!(format!("Failed to preform RPC: {err}")), None => Ok(()), } @@ -102,42 +104,116 @@ fn start_factorio(factorio_path: PCSTR) -> Result { Ok(factorio_process_information) } -pub fn run() -> Result<()> { - let bin_path = common::get_bin_folder()?; - let (read_path, write_path) = common::get_data_dirs(&bin_path)?; +fn extract_all_mods_libs( + read_data: impl AsRef, + write_data: impl AsRef, +) -> Result> { + #[cfg(target_os = "linux")] + static DYNAMIC_LIBRARY_SUFFIX: &str = ".so"; + #[cfg(target_os = "linux")] + static RIVETS_LIB: &str = "rivets.so"; + #[cfg(target_os = "windows")] + static DYNAMIC_LIBRARY_SUFFIX: &str = ".dll"; + #[cfg(target_os = "windows")] + static RIVETS_LIB: &str = "rivets.dll"; + + let mut result = vec![]; + let mut mod_list = ModList::generate_custom(&read_data, &write_data) + .context("Failed to find your Factorio mods directory.")?; + mod_list.load().context( + "Failed to parse mods-list.json file. Please ensure this file exists and is well-formated.", + )?; + + let (all_active_mods, mod_load_order) = mod_list.active_with_order(); + for mod_name in mod_load_order { + let current_mod = all_active_mods + .get(&mod_name) + .expect("The list of active mods contains all mods in the load order"); + + let lib = match current_mod.get_file(RIVETS_LIB) { + Err(ModError::PathDoesNotExist(_)) => continue, + Err(ModError::ZipError(e)) + if e.to_string() == "specified file not found in archive" => + { + continue + } + Ok(lib) => lib, + Err(e) => return Err(anyhow!(e).context(format!("Detected corrupted mod file! {mod_name} does not have proper zip encodings. Please try to reinstall this mod."))), + }; + + std::fs::create_dir_all(write_data.as_ref().join("temp/rivets")).context( + "Could not find the factorio/temp directory. Please manually create this directory.", + )?; + + let extracted_lib_name = format!("{mod_name}{DYNAMIC_LIBRARY_SUFFIX}"); + let lib_path = write_data + .as_ref() + .join("temp/rivets") + .join(extracted_lib_name); + std::fs::write(&lib_path, lib) + .context("Failed to write the extracted rivets .dll library to the temp directory.")?; + + result.push((mod_name, lib_path)); + } + + Ok(result) +} - let (stdout_read, _) = create_pipe()?; +pub fn run() -> Result<()> { + println!("Starting Rivets injector..."); + let bin_path = common::get_bin_folder().context("Failed to find factorio.exe. You must run this program from the Factorio installation directory.")?; + let (read_path, write_path) = common::get_data_dirs(&bin_path).context("Failed to parse Factorio's config-path.cfg and config.ini files. Please ensure these files actually exist. If not, reinstall Factorio.")?; + + println!("Searching for Rivets mod .dll libraries..."); + let all_mods = extract_all_mods_libs(read_path, write_path)?; + println!( + "Mods list loaded successfully. Found {} enabled rivets mods.", + all_mods.len() + ); + + let (stdout_read, _) = create_pipe().context( + "Failed to create operating system pipes. Try running rivets with elevated permissions.", + )?; let mut reader = unsafe { std::fs::File::from_raw_handle(stdout_read.0) }; let factorio_path = bin_path.join("factorio.exe"); + let pdb_path = bin_path.join("factorio.pdb"); - let factorio_path = CString::new(factorio_path.as_os_str().to_string_lossy().into_owned())?; + println!("Creating symbol cache..."); + let symbol_cache = SymbolCache::new(pdb_path, "factorio.exe"); + let symbol_cache = symbol_cache.context("Failed to create symbol cache.")?; + + let factorio_path = CString::new(factorio_path.as_os_str().to_string_lossy().into_owned()).context("Failed to convert Factorio path to CString. Do you have any invalid chars in your Factorio path? Please report this on the Rivets Github.")?; println!("Factorio path: {factorio_path:?}"); let factorio_path = PCSTR(factorio_path.as_ptr().cast()); - let dll_path = common::extract_rivets_lib(&read_path, &write_path)?; - let factorio_process_information: PROCESS_INFORMATION = start_factorio(factorio_path)?; println!("Factorio process started."); let syringe = get_syringe().inspect_err(|_| { attempt_kill_factorio(factorio_process_information); })?; - println!("Injecting DLL into Factorio process..."); - println!("\t{}", dll_path.display()); + for (mod_name, dll_path) in all_mods { + println!("Discovered rivets mod: {mod_name}"); + println!("Injecting DLL into Factorio process..."); - let module = inject_dll(&syringe, &dll_path).inspect_err(|_| { - attempt_kill_factorio(factorio_process_information); - })?; + let module = inject_dll(&syringe, &dll_path) + .inspect_err(|_| { + attempt_kill_factorio(factorio_process_information); + }) + .context(format!("DLL injection failed for {mod_name}"))?; - println!("DLL injected successfully."); - println!("Performing DLL initialization RPC..."); + println!("DLL injected successfully. Performing DLL initialization RPC..."); - rpc(&syringe, module, read_path, write_path).inspect_err(|_| { - attempt_kill_factorio(factorio_process_information); - })?; + rpc(&syringe, module, &symbol_cache) + .inspect_err(|_| { + attempt_kill_factorio(factorio_process_information); + }) + .context(format!("Function detouring failed for {mod_name}"))?; + + println!("RPC completed successfully."); + } - println!("RPC completed successfully."); println!("Returning control back into Factorio process..."); unsafe { @@ -153,8 +229,5 @@ pub fn run() -> Result<()> { } fn attempt_kill_factorio(factorio_process_information: PROCESS_INFORMATION) { - if let Err(e) = unsafe { TerminateProcess(factorio_process_information.hProcess, 0) } { - eprintln!("Failed to terminate Factorio process after experiencing a rivets error: {e}"); - eprintln!("You likely have a ghost Factorio process running. Please kill it manually via task manager."); - } + let _ = unsafe { TerminateProcess(factorio_process_information.hProcess, 0) }; }