diff --git a/ewext/Cargo.lock b/ewext/Cargo.lock index c9c95123..3da2ebb5 100644 --- a/ewext/Cargo.lock +++ b/ewext/Cargo.lock @@ -43,6 +43,7 @@ name = "ewext" version = "0.1.0" dependencies = [ "backtrace", + "iced-x86", "libloading", ] @@ -52,6 +53,21 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "iced-x86" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.162" diff --git a/ewext/Cargo.toml b/ewext/Cargo.toml index 15d428d5..e102a815 100644 --- a/ewext/Cargo.toml +++ b/ewext/Cargo.toml @@ -14,3 +14,4 @@ strip = true [dependencies] libloading = "0.8.5" backtrace = "0.3.74" +iced-x86 = "1.21.0" diff --git a/ewext/src/addr_grabber.rs b/ewext/src/addr_grabber.rs new file mode 100644 index 00000000..903bff80 --- /dev/null +++ b/ewext/src/addr_grabber.rs @@ -0,0 +1,27 @@ +use std::{os::raw::c_void, ptr}; + +use iced_x86::{Decoder, DecoderOptions, Mnemonic}; + +pub(crate) unsafe fn grab_addr_from_instruction( + func: *const c_void, + offset: isize, + expected_mnemonic: Mnemonic, +) -> *mut c_void { + let instruction_addr = func.wrapping_offset(offset); + // We don't really have an idea of how many bytes the instruction takes, so just take *enough* bytes for most cases. + let instruction_bytes = ptr::read_unaligned(instruction_addr.cast::<[u8; 16]>()); + let mut decoder = Decoder::with_ip( + 32, + &instruction_bytes, + instruction_addr as u64, + DecoderOptions::NONE, + ); + let instruction = decoder.decode(); + + if instruction.mnemonic() != expected_mnemonic { + println!("Encountered unexpected mnemonic: {}", instruction); + } + assert_eq!(instruction.mnemonic(), expected_mnemonic); + + instruction.memory_displacement32() as *mut c_void +} diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index e49c5cba..4ec0bf9d 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -4,13 +4,16 @@ use std::{ sync::LazyLock, }; -use lua_bindings::{lua_State, Lua51}; +use iced_x86::Mnemonic; +use lua_bindings::{lua_State, Lua51, LUA_GLOBALSINDEX}; use noita::{NoitaPixelRun, ParticleWorldState}; mod lua_bindings; mod noita; +mod addr_grabber; + static LUA: LazyLock = LazyLock::new(|| unsafe { let lib = libloading::Library::new("./lua51.dll").expect("library to exist"); Lua51::from_library(lib).expect("library to be lua") @@ -20,9 +23,15 @@ thread_local! { static STATE: LazyCell> = LazyCell::new(|| ExtState::default().into()); } +struct GrabbedGlobals { + game_global: *const c_void, + world_state_entity: *const c_void, +} + #[derive(Default)] struct ExtState { particle_world_state: Option, + globals: Option, } // const EWEXT: [(&'static str, Function); 1] = [("testfn", None)]; @@ -61,6 +70,40 @@ unsafe extern "C" fn encode_area(lua: *mut lua_State) -> c_int { 1 } +unsafe fn grab_addrs(lua: *mut lua_State) { + LUA.lua_getfield(lua, LUA_GLOBALSINDEX, c"GameGetWorldStateEntity".as_ptr()); + let base = LUA.lua_tocfunction(lua, -1).unwrap() as *const c_void; + let world_state_entity = + addr_grabber::grab_addr_from_instruction(base, 0x007aa7ce - 0x007aa540, Mnemonic::Mov); + println!( + "World state entity addr: 0x{:x}", + world_state_entity as usize + ); + // Pop the last element. + LUA.lua_settop(lua, -2); + + LUA.lua_getfield(lua, LUA_GLOBALSINDEX, c"GameGetFrameNum".as_ptr()); + let base = LUA.lua_tocfunction(lua, -1).unwrap() as *const c_void; + let load_game_global = + addr_grabber::grab_addr_from_instruction(base, 0x007bf3c9 - 0x007bf140, Mnemonic::Call); // CALL load_game_global + println!("Load game global addr: 0x{:x}", load_game_global as usize); + let game_global = addr_grabber::grab_addr_from_instruction( + load_game_global, + 0x00439c17 - 0x00439bb0, + Mnemonic::Mov, + ); + println!("Game global addr: 0x{:x}", game_global as usize); + // Pop the last element. + LUA.lua_settop(lua, -2); + + STATE.with(|state| { + state.borrow_mut().globals = Some(GrabbedGlobals { + game_global, + world_state_entity, + }); + }); +} + /// # Safety /// /// Only gets called by lua when loading a module. @@ -68,6 +111,8 @@ unsafe extern "C" fn encode_area(lua: *mut lua_State) -> c_int { pub unsafe extern "C" fn luaopen_ewext0(lua: *mut lua_State) -> c_int { println!("Initializing ewext"); unsafe { + grab_addrs(lua); + LUA.lua_createtable(lua, 0, 0); LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0);