-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added instruction pattern matching, refactored some
- Loading branch information
Showing
5 changed files
with
255 additions
and
138 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,34 @@ | ||
[package] | ||
name = "eldenring-alt-saves" | ||
version = "1.3.2" | ||
version = "1.4.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[profile.release] | ||
strip = true | ||
lto = true | ||
codegen-units = 1 | ||
opt-level = "z" | ||
|
||
[dependencies] | ||
log = "0.4.1" | ||
serde = { version = "1.0.160", features = ["derive"]} | ||
toml = "0.7.2" | ||
broadsword = { git = "https://github.com/vswarte/broadsword.git" } | ||
detour = { git = "https://github.com/veeenu/detour-rs.git", branch = "master" } | ||
|
||
[dependencies.windows] | ||
version = "0.48.0" | ||
features = [ | ||
"Win32_Foundation", | ||
] | ||
|
||
[profile.release] | ||
strip = true | ||
lto = true | ||
codegen-units = 1 | ||
opt-level = "z" | ||
[dependencies.retour] | ||
version = "0.3" | ||
features = [ | ||
"static-detour", | ||
] | ||
|
||
[patch.crates-io] | ||
libudis86-sys = { git = 'https://github.com/vars1ty/libudis86-sys.git' } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use crate::config::{get_rewrite_extension, get_seamless_rewrite_extension}; | ||
|
||
use std::mem::transmute; | ||
|
||
use broadsword::runtime; | ||
use retour::static_detour; | ||
use windows::{core::{HSTRING, PCWSTR}, Win32::Foundation::HANDLE}; | ||
|
||
const SAVEGAME_EXTENSION: &str = ".sl2"; | ||
const SAVEGAME_BACKUP_EXTENSION: &str = ".sl2.bak"; | ||
const SC_SAVEGAME_EXTENSION: &str = ".co2"; | ||
const SC_SAVEGAME_BACKUP_EXTENSION: &str = ".co2.bak"; | ||
|
||
static_detour! { | ||
static CREATE_FILE_W_HOOK: unsafe extern "C" fn(PCWSTR, u32, u32, u64, u32, u32, HANDLE) -> u64; | ||
} | ||
|
||
pub fn hook() { | ||
// Hook Kernel32's CreateFileW since that is responsible for opening file | ||
// handles, and it's nice and documented. | ||
let create_file_w = runtime::get_module_symbol("kernel32", "CreateFileW") | ||
.expect("Could not find CreateFileW"); | ||
|
||
unsafe { | ||
CREATE_FILE_W_HOOK | ||
.initialize( | ||
transmute(create_file_w), | ||
create_file_hook, | ||
) | ||
.unwrap(); | ||
|
||
CREATE_FILE_W_HOOK.enable().unwrap(); | ||
} | ||
} | ||
|
||
/// Handle an actual invoke of CreateFileW and do necessary rewrites. | ||
fn create_file_hook( | ||
path: PCWSTR, | ||
desired_access: u32, | ||
share_mode: u32, | ||
security_attributes: u64, | ||
creation_disposition: u32, | ||
flags_and_attributes: u32, | ||
template_file: HANDLE, | ||
) -> u64 { | ||
let patched_path = unsafe { transform_path(path) } | ||
.map(HSTRING::from); | ||
|
||
let result_path = match patched_path { | ||
None => path, | ||
Some(s) => PCWSTR::from_raw(s.as_ptr()), | ||
}; | ||
|
||
unsafe { | ||
CREATE_FILE_W_HOOK.call( | ||
result_path, | ||
desired_access, | ||
share_mode, | ||
security_attributes, | ||
creation_disposition, | ||
flags_and_attributes, | ||
template_file, | ||
) | ||
} | ||
} | ||
|
||
/// Transforms the input path for CreateFileW, yields Some() with a new path or | ||
/// None if no rewriting was necessary. | ||
unsafe fn transform_path(path: PCWSTR) -> Option<String> { | ||
// TODO: this logic can be broken up for readability | ||
let path_string = path.to_string() | ||
.expect("Could not convert PCWSTR into string"); | ||
|
||
if path_string.ends_with(SAVEGAME_EXTENSION) || | ||
path_string.ends_with(SAVEGAME_BACKUP_EXTENSION) { | ||
|
||
Some(path_string.replace(SAVEGAME_EXTENSION, get_rewrite_extension())) | ||
} else if path_string.ends_with(SC_SAVEGAME_EXTENSION) || | ||
path_string.ends_with(SC_SAVEGAME_BACKUP_EXTENSION) { | ||
|
||
let extension = get_seamless_rewrite_extension() | ||
.map(|f| f.as_str()) | ||
.unwrap_or(get_rewrite_extension()); | ||
|
||
Some(path_string.replace(SC_SAVEGAME_EXTENSION, extension)) | ||
} else { | ||
None | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,58 @@ | ||
#![feature(absolute_path)] | ||
|
||
use std::mem; | ||
|
||
mod config; | ||
mod file; | ||
mod regulation; | ||
|
||
use broadsword::dll; | ||
use broadsword::runtime; | ||
use detour::static_detour; | ||
use broadsword::runtime::get_module_handle; | ||
use windows::core::{HSTRING, PCWSTR}; | ||
use windows::Win32::Foundation::HANDLE; | ||
|
||
use crate::config::{get_rewrite_extension, get_seamless_rewrite_extension}; | ||
|
||
const SAVEGAME_EXTENSION: &str = ".sl2"; | ||
const SAVEGAME_BACKUP_EXTENSION: &str = ".sl2.bak"; | ||
const SC_SAVEGAME_EXTENSION: &str = ".co2"; | ||
const SC_SAVEGAME_BACKUP_EXTENSION: &str = ".co2.bak"; | ||
|
||
const REGBIN_CHECK_FLAG_IBO: usize = 0x3b40052; | ||
const REGULATIONMANAGER_CONSTRUCTOR_IBO: usize = 0xe076c0; | ||
use std::slice; | ||
|
||
static_detour! { | ||
static CREATE_FILE_W_HOOK: unsafe extern "system" fn(PCWSTR, u32, u32, u64, u32, u32, HANDLE) -> u64; | ||
static REGULATIONMANAGER_CONSTRUCTOR: unsafe extern "system" fn(u64, u64) -> u64; | ||
} | ||
use broadsword::{dll, runtime, scanner}; | ||
|
||
#[dll::entrypoint] | ||
pub fn entry(_: usize) -> bool { | ||
apply_file_hook(); | ||
apply_regulation_hook(); | ||
file::hook(); | ||
regulation::hook(); | ||
true | ||
} | ||
|
||
fn apply_regulation_hook() { | ||
let regulationmanager_constructor = get_main_module() + REGULATIONMANAGER_CONSTRUCTOR_IBO; | ||
|
||
unsafe { | ||
REGULATIONMANAGER_CONSTRUCTOR | ||
.initialize( | ||
mem::transmute(regulationmanager_constructor), | ||
|allocated_space: u64, param_2: u64| { | ||
let result = REGULATIONMANAGER_CONSTRUCTOR.call(allocated_space, param_2); | ||
patch_regbin_check(); | ||
result | ||
} | ||
) | ||
.unwrap(); | ||
|
||
REGULATIONMANAGER_CONSTRUCTOR.enable().unwrap(); | ||
} | ||
} | ||
|
||
// Overwrites the flag that seems to determine if the regulation bin file should be checked against | ||
// a particular hash. This check causes new save files to throw errors when the regbin has been | ||
// changed. | ||
fn patch_regbin_check() { | ||
let ptr = get_main_module() + REGBIN_CHECK_FLAG_IBO; | ||
unsafe { *(ptr as *mut u8) = 0x0 }; | ||
/// Takes an instruction pattern and looks for its location | ||
pub fn match_instruction_pattern(pattern: &str) -> Option<PatternResult> { | ||
// Find .text section details since that's where the code lives | ||
let text_section = runtime::get_module_section_range("eldenring.exe", ".text") | ||
.or_else(|_| runtime::get_module_section_range("start_protected_game.exe", ".text")) | ||
.unwrap(); | ||
|
||
// Represent search area as a slice | ||
let scan_slice = unsafe { | ||
slice::from_raw_parts( | ||
text_section.start as *const u8, | ||
text_section.end - text_section.start, | ||
) | ||
}; | ||
|
||
let pattern = scanner::Pattern::from_bit_pattern(pattern).unwrap(); | ||
|
||
scanner::simple::scan(scan_slice, &pattern) | ||
// TODO: this kinda of rebasing can be done in broadsword probably | ||
.map(|result| PatternResult { | ||
location: text_section.start + result.location, | ||
captures: result.captures.into_iter() | ||
.map(|capture| { | ||
PatternCapture { | ||
location: text_section.start + capture.location, | ||
bytes: capture.bytes, | ||
} | ||
}) | ||
.collect() | ||
}) | ||
} | ||
|
||
fn apply_file_hook() { | ||
let create_file_w = runtime::get_module_symbol("kernel32", "CreateFileW") | ||
.expect("Could not find CreateFileW"); | ||
|
||
unsafe { | ||
CREATE_FILE_W_HOOK | ||
.initialize( | ||
mem::transmute(create_file_w), | ||
move |path: PCWSTR, | ||
desired_access: u32, | ||
share_mode: u32, | ||
security_attributes: u64, | ||
creation_disposition: u32, | ||
flags_and_attributes: u32, | ||
template_file: HANDLE| { | ||
|
||
// Doing this here to ensure the string isn't dropped until after the fn call | ||
// otherwise the string's source is dropped before the pointer is consumed. | ||
let patched_path = transform_path(path) | ||
.map(HSTRING::from); | ||
|
||
let effective_path = match patched_path { | ||
None => path, | ||
Some(s) => PCWSTR::from_raw(s.as_ptr()), | ||
}; | ||
|
||
CREATE_FILE_W_HOOK.call( | ||
effective_path, | ||
desired_access, | ||
share_mode, | ||
security_attributes, | ||
creation_disposition, | ||
flags_and_attributes, | ||
template_file, | ||
) | ||
}, | ||
) | ||
.unwrap(); | ||
|
||
CREATE_FILE_W_HOOK.enable().unwrap(); | ||
} | ||
} | ||
|
||
// TODO: Rewrites can be cached but is it worth the performance gain with how little it's called? | ||
unsafe fn transform_path(path: PCWSTR) -> Option<String> { | ||
let path_string = path.to_string() | ||
.expect("Could not convert PCWSTR into string"); | ||
|
||
if path_string.ends_with(SAVEGAME_EXTENSION) || path_string.ends_with(SAVEGAME_BACKUP_EXTENSION) { | ||
Some(path_string.clone().replace( | ||
SAVEGAME_EXTENSION, | ||
get_rewrite_extension().as_str(), | ||
)) | ||
} else if path_string.ends_with(SC_SAVEGAME_EXTENSION) || path_string.ends_with(SC_SAVEGAME_BACKUP_EXTENSION) { | ||
let extension = get_seamless_rewrite_extension() | ||
.unwrap_or(get_rewrite_extension()); | ||
|
||
Some(path_string.clone().replace( | ||
SC_SAVEGAME_EXTENSION, | ||
extension.as_str(), | ||
)) | ||
} else { | ||
None | ||
} | ||
#[derive(Debug)] | ||
pub struct PatternResult { | ||
location: usize, | ||
captures: Vec<PatternCapture>, | ||
} | ||
|
||
/// Attempts to retrieve the main module of the game | ||
pub fn get_main_module() -> usize { | ||
get_module_handle("eldenring.exe") | ||
.or_else(|_| get_module_handle("start_protected_game.exe")) | ||
.expect("Could not locate main module") | ||
#[derive(Debug)] | ||
pub struct PatternCapture { | ||
location: usize, | ||
bytes: Vec<u8>, | ||
} |
Oops, something went wrong.