diff --git a/ewext/Cargo.lock b/ewext/Cargo.lock index 6c76b2e8..15abab14 100644 --- a/ewext/Cargo.lock +++ b/ewext/Cargo.lock @@ -40,14 +40,25 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ewext" -version = "0.3.0" +version = "0.4.0" dependencies = [ "backtrace", + "eyre", "iced-x86", "libloading", "noita_api_macro", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "gimli" version = "0.31.1" @@ -69,6 +80,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "itoa" version = "1.0.11" @@ -132,6 +149,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "proc-macro2" version = "1.0.89" diff --git a/ewext/Cargo.toml b/ewext/Cargo.toml index 93fc5769..a4651d81 100644 --- a/ewext/Cargo.toml +++ b/ewext/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ewext" -version = "0.3.0" +version = "0.4.0" edition = "2021" [lib] @@ -16,3 +16,4 @@ libloading = "0.8.5" backtrace = "0.3.74" iced-x86 = "1.21.0" noita_api_macro = {path = "noita_api_macro"} +eyre = "0.6.12" diff --git a/ewext/noita_api_macro/src/lib.rs b/ewext/noita_api_macro/src/lib.rs index 729f9c9e..b90e7f18 100644 --- a/ewext/noita_api_macro/src/lib.rs +++ b/ewext/noita_api_macro/src/lib.rs @@ -107,7 +107,9 @@ pub fn add_lua_fn(item: TokenStream) -> TokenStream { let fn_name_c = proc_macro2::Literal::c_string(CString::new(fn_name).unwrap().as_c_str()); quote! { unsafe extern "C" fn #bridge_fn_name(lua: *mut lua_State) -> c_int { - #fn_name_ident(LuaState::new(lua)) as c_int + let lua_state = LuaState::new(lua); + lua_state.make_current(); + crate::lua_state::LuaFnRet::do_return(#fn_name_ident(lua_state), lua_state) } LUA.lua_pushcclosure(lua, Some(#bridge_fn_name), 0); diff --git a/ewext/src/lib.rs b/ewext/src/lib.rs index 4f073d3f..f5404b2e 100644 --- a/ewext/src/lib.rs +++ b/ewext/src/lib.rs @@ -6,8 +6,9 @@ use std::{ }; use addr_grabber::{grab_addrs, grabbed_fns, grabbed_globals}; +use eyre::bail; use lua_bindings::{lua_State, Lua51}; -use lua_state::LuaState; +use lua_state::{LuaState, ValuesOnStack}; use noita::{ntypes::Entity, NoitaPixelRun, ParticleWorldState}; use noita_api_macro::add_lua_fn; @@ -35,7 +36,7 @@ struct ExtState { particle_world_state: Option, } -fn init_particle_world_state(lua: LuaState) -> c_int { +fn init_particle_world_state(lua: LuaState) { println!("\nInitializing particle world state"); let world_pointer = lua.to_integer(1); let chunk_map_pointer = lua.to_integer(2); @@ -50,10 +51,9 @@ fn init_particle_world_state(lua: LuaState) -> c_int { runner: Default::default(), }); }); - 0 } -fn encode_area(lua: LuaState) -> c_int { +fn encode_area(lua: LuaState) -> ValuesOnStack { let lua = lua.raw(); let start_x = unsafe { LUA.lua_tointeger(lua, 1) } as i32; let start_y = unsafe { LUA.lua_tointeger(lua, 2) } as i32; @@ -67,10 +67,10 @@ fn encode_area(lua: LuaState) -> c_int { let runs = unsafe { pws.encode_area(start_x, start_y, end_x, end_y, encoded_buffer) }; unsafe { LUA.lua_pushinteger(lua, runs as isize) }; }); - 1 + lua_state::ValuesOnStack(1) } -fn make_ephemerial(lua: LuaState) -> c_int { +fn make_ephemerial(lua: LuaState) -> eyre::Result<()> { unsafe { let entity_id = lua.to_integer(1) as u32; @@ -87,16 +87,16 @@ fn make_ephemerial(lua: LuaState) -> c_int { out("ecx") _, out("eax") entity, ); - if !entity.is_null() { - entity.cast::().offset(0x8).cast::().write(0); + if entity.is_null() { + bail!("Entity {} not found", entity_id); } + entity.cast::().offset(0x8).cast::().write(0); } - 0 + Ok(()) } -fn on_world_initialized(lua: LuaState) -> i32 { +fn on_world_initialized(lua: LuaState) { grab_addrs(lua); - 0 } /// # Safety diff --git a/ewext/src/lua_state.rs b/ewext/src/lua_state.rs index 6c84c6e5..f59a1fd6 100644 --- a/ewext/src/lua_state.rs +++ b/ewext/src/lua_state.rs @@ -1,10 +1,20 @@ -use std::ffi::CStr; +use std::{ + cell::Cell, + ffi::{c_char, c_int, CStr}, + mem, +}; + +use eyre::OptionExt; use crate::{ lua_bindings::{lua_CFunction, lua_State, LUA_GLOBALSINDEX}, LUA, }; +thread_local! { + static CURRENT_LUA_STATE: Cell> = Cell::default(); +} + #[derive(Clone, Copy)] pub(crate) struct LuaState { lua: *mut lua_State, @@ -15,6 +25,17 @@ impl LuaState { Self { lua } } + /// Returns a lua state that is considered "current". Usually set when we get called from noita. + pub(crate) fn current() -> eyre::Result { + CURRENT_LUA_STATE + .get() + .ok_or_eyre("No current lua state available") + } + + pub(crate) fn make_current(self) { + CURRENT_LUA_STATE.set(Some(self)); + } + pub(crate) fn raw(&self) -> *mut lua_State { self.lua } @@ -27,6 +48,12 @@ impl LuaState { unsafe { LUA.lua_tocfunction(self.lua, index) } } + pub(crate) fn push_string(&self, s: &str) { + unsafe { + LUA.lua_pushstring(self.lua, s.as_bytes().as_ptr() as *const c_char); + } + } + pub(crate) fn get_global(&self, name: &CStr) { unsafe { LUA.lua_getfield(self.lua, LUA_GLOBALSINDEX, name.as_ptr()) }; } @@ -34,4 +61,45 @@ impl LuaState { pub(crate) fn pop_last(&self) { unsafe { LUA.lua_settop(self.lua, -2) }; } + + /// Raise an error with message `s` + /// + /// This takes String so that it gets deallocated properly, as this functions doesn't return. + unsafe fn raise_error(&self, s: String) -> ! { + self.push_string(&s); + mem::drop(s); + unsafe { LUA.lua_error(self.lua) }; + // lua_error does not return. + unreachable!() + } +} + +pub(crate) trait LuaFnRet { + fn do_return(self, lua: LuaState) -> c_int; +} + +/// Function intends to return several values that it has on stack. +pub(crate) struct ValuesOnStack(pub(crate) c_int); + +impl LuaFnRet for ValuesOnStack { + fn do_return(self, _lua: LuaState) -> c_int { + self.0 + } +} + +impl LuaFnRet for () { + fn do_return(self, _lua: LuaState) -> c_int { + 0 + } +} + +impl LuaFnRet for eyre::Result { + fn do_return(self, lua: LuaState) -> c_int { + match self { + Ok(ok) => ok.do_return(lua), + Err(err) => unsafe { + lua.raise_error(format!("Error in ewext call: {:?}", err)); + }, + } + } }