mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
Merge pull request #233 from IntQuant/rust-api
Implement rust api to allow writing modules in rust.
This commit is contained in:
commit
5628b78ae2
19 changed files with 9535 additions and 260 deletions
132
ewext/Cargo.lock
generated
132
ewext/Cargo.lock
generated
|
@ -40,11 +40,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ewext"
|
name = "ewext"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
"eyre",
|
||||||
"iced-x86",
|
"iced-x86",
|
||||||
"libloading",
|
"libloading",
|
||||||
|
"noita_api",
|
||||||
|
"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]]
|
[[package]]
|
||||||
|
@ -53,6 +66,12 @@ version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced-x86"
|
name = "iced-x86"
|
||||||
version = "1.21.0"
|
version = "1.21.0"
|
||||||
|
@ -62,6 +81,18 @@ dependencies = [
|
||||||
"lazy_static",
|
"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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -99,6 +130,26 @@ dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noita_api"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eyre",
|
||||||
|
"libloading",
|
||||||
|
"noita_api_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noita_api_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.5"
|
version = "0.36.5"
|
||||||
|
@ -108,12 +159,91 @@ dependencies = [
|
||||||
"memchr",
|
"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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.214"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.214"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.132"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ewext"
|
name = "ewext"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -15,3 +15,6 @@ strip = true
|
||||||
libloading = "0.8.5"
|
libloading = "0.8.5"
|
||||||
backtrace = "0.3.74"
|
backtrace = "0.3.74"
|
||||||
iced-x86 = "1.21.0"
|
iced-x86 = "1.21.0"
|
||||||
|
noita_api_macro = {path = "noita_api_macro"}
|
||||||
|
eyre = "0.6.12"
|
||||||
|
noita_api = {path = "noita_api"}
|
9
ewext/noita_api/Cargo.toml
Normal file
9
ewext/noita_api/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "noita_api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eyre = "0.6.12"
|
||||||
|
libloading = "0.8.5"
|
||||||
|
noita_api_macro = {path = "../noita_api_macro"}
|
81
ewext/noita_api/src/lib.rs
Normal file
81
ewext/noita_api/src/lib.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use std::{borrow::Cow, num::NonZero};
|
||||||
|
|
||||||
|
use eyre::{eyre, Context};
|
||||||
|
|
||||||
|
pub mod lua;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct EntityID(pub NonZero<isize>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ComponentID(pub NonZero<isize>);
|
||||||
|
|
||||||
|
pub struct Obj(pub usize);
|
||||||
|
|
||||||
|
pub struct Color(pub u32);
|
||||||
|
|
||||||
|
pub trait Component: From<ComponentID> {
|
||||||
|
const NAME_STR: &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
noita_api_macro::generate_components!();
|
||||||
|
|
||||||
|
impl EntityID {
|
||||||
|
pub fn try_get_first_component<C: Component>(
|
||||||
|
self,
|
||||||
|
tag: Option<Cow<'_, str>>,
|
||||||
|
) -> eyre::Result<Option<C>> {
|
||||||
|
raw::entity_get_first_component(self, C::NAME_STR.into(), tag)
|
||||||
|
.map(|x| x.flatten().map(Into::into))
|
||||||
|
.wrap_err_with(|| eyre!("Failed to get first component {} for {self:?}", C::NAME_STR))
|
||||||
|
}
|
||||||
|
pub fn get_first_component<C: Component>(self, tag: Option<Cow<'_, str>>) -> eyre::Result<C> {
|
||||||
|
self.try_get_first_component(tag)?
|
||||||
|
.ok_or_else(|| eyre!("Entity {self:?} has no component {}", C::NAME_STR))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod raw {
|
||||||
|
use eyre::eyre;
|
||||||
|
use eyre::Context;
|
||||||
|
|
||||||
|
use super::{Color, ComponentID, EntityID, Obj};
|
||||||
|
use crate::lua::LuaGetValue;
|
||||||
|
use crate::lua::LuaPutValue;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::lua::LuaState;
|
||||||
|
|
||||||
|
noita_api_macro::generate_api!();
|
||||||
|
|
||||||
|
pub(crate) fn component_get_value<T>(component: ComponentID, field: &str) -> eyre::Result<T>
|
||||||
|
where
|
||||||
|
T: LuaGetValue,
|
||||||
|
{
|
||||||
|
let lua = LuaState::current()?;
|
||||||
|
lua.get_global(c"ComponentGetValue2");
|
||||||
|
lua.push_integer(component.0.into());
|
||||||
|
lua.push_string(field);
|
||||||
|
lua.call(2, T::size_on_stack());
|
||||||
|
let ret = T::get(lua, -1);
|
||||||
|
lua.pop_last_n(T::size_on_stack());
|
||||||
|
ret.wrap_err_with(|| eyre!("Getting {field} for {component:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn component_set_value<T>(
|
||||||
|
component: ComponentID,
|
||||||
|
field: &str,
|
||||||
|
value: T,
|
||||||
|
) -> eyre::Result<()>
|
||||||
|
where
|
||||||
|
T: LuaPutValue,
|
||||||
|
{
|
||||||
|
let lua = LuaState::current()?;
|
||||||
|
lua.get_global(c"ComponentSetValue2");
|
||||||
|
lua.push_integer(component.0.into());
|
||||||
|
lua.push_string(field);
|
||||||
|
value.put(lua);
|
||||||
|
lua.call(2 + T::size_on_stack(), 0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
507
ewext/noita_api/src/lua.rs
Normal file
507
ewext/noita_api/src/lua.rs
Normal file
|
@ -0,0 +1,507 @@
|
||||||
|
pub mod lua_bindings;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
cell::Cell,
|
||||||
|
ffi::{c_char, c_int, CStr},
|
||||||
|
mem, slice,
|
||||||
|
sync::LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use eyre::{bail, Context, OptionExt};
|
||||||
|
use lua_bindings::{lua_CFunction, lua_State, Lua51, LUA_GLOBALSINDEX};
|
||||||
|
|
||||||
|
use crate::{Color, ComponentID, EntityID, Obj};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static CURRENT_LUA_STATE: Cell<Option<LuaState>> = Cell::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static LUA: LazyLock<Lua51> = LazyLock::new(|| unsafe {
|
||||||
|
let lib = libloading::Library::new("./lua51.dll").expect("library to exist");
|
||||||
|
Lua51::from_library(lib).expect("library to be lua")
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct LuaState {
|
||||||
|
lua: *mut lua_State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaState {
|
||||||
|
pub fn new(lua: *mut lua_State) -> Self {
|
||||||
|
Self { lua }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a lua state that is considered "current". Usually set when we get called from noita.
|
||||||
|
pub fn current() -> eyre::Result<Self> {
|
||||||
|
CURRENT_LUA_STATE
|
||||||
|
.get()
|
||||||
|
.ok_or_eyre("No current lua state available")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_current(self) {
|
||||||
|
CURRENT_LUA_STATE.set(Some(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw(&self) -> *mut lua_State {
|
||||||
|
self.lua
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_integer(&self, index: i32) -> isize {
|
||||||
|
unsafe { LUA.lua_tointeger(self.lua, index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_number(&self, index: i32) -> f64 {
|
||||||
|
unsafe { LUA.lua_tonumber(self.lua, index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bool(&self, index: i32) -> bool {
|
||||||
|
unsafe { LUA.lua_toboolean(self.lua, index) > 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_string(&self, index: i32) -> eyre::Result<String> {
|
||||||
|
let mut size = 0;
|
||||||
|
let buf = unsafe { LUA.lua_tolstring(self.lua, index, &mut size) };
|
||||||
|
if buf.is_null() {
|
||||||
|
bail!("Expected a string, but got a null pointer");
|
||||||
|
}
|
||||||
|
let slice = unsafe { slice::from_raw_parts(buf as *const u8, size) };
|
||||||
|
|
||||||
|
Ok(String::from_utf8(slice.to_owned())
|
||||||
|
.wrap_err("Attempting to get lua string, expecting it to be utf-8")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_cfunction(&self, index: i32) -> lua_CFunction {
|
||||||
|
unsafe { LUA.lua_tocfunction(self.lua, index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_number(&self, val: f64) {
|
||||||
|
unsafe { LUA.lua_pushnumber(self.lua, val) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_integer(&self, val: isize) {
|
||||||
|
unsafe { LUA.lua_pushinteger(self.lua, val) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_bool(&self, val: bool) {
|
||||||
|
unsafe { LUA.lua_pushboolean(self.lua, val as i32) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_string(&self, s: &str) {
|
||||||
|
unsafe {
|
||||||
|
LUA.lua_pushlstring(self.lua, s.as_bytes().as_ptr() as *const c_char, s.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_nil(&self) {
|
||||||
|
unsafe { LUA.lua_pushnil(self.lua) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, nargs: i32, nresults: i32) {
|
||||||
|
unsafe { LUA.lua_call(self.lua, nargs, nresults) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_global(&self, name: &CStr) {
|
||||||
|
unsafe { LUA.lua_getfield(self.lua, LUA_GLOBALSINDEX, name.as_ptr()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn objlen(&self, index: i32) -> usize {
|
||||||
|
unsafe { LUA.lua_objlen(self.lua, index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_table(&self, table_index: i32, index_in_table: usize) {
|
||||||
|
self.push_integer(index_in_table as isize);
|
||||||
|
if table_index < 0 {
|
||||||
|
unsafe { LUA.lua_gettable(self.lua, table_index - 1) };
|
||||||
|
} else {
|
||||||
|
unsafe { LUA.lua_gettable(self.lua, table_index) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_last(&self) {
|
||||||
|
unsafe { LUA.lua_settop(self.lua, -2) };
|
||||||
|
}
|
||||||
|
pub fn pop_last_n(&self, n: i32) {
|
||||||
|
unsafe { LUA.lua_settop(self.lua, -1 - (n)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_nil_or_none(&self, index: i32) -> bool {
|
||||||
|
(unsafe { LUA.lua_type(self.lua, index) }) <= 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used for types that can be returned from functions that were defined in rust to lua.
|
||||||
|
pub trait LuaFnRet {
|
||||||
|
fn do_return(self, lua: LuaState) -> c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function intends to return several values that it has on stack.
|
||||||
|
pub struct ValuesOnStack(pub 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<R: LuaFnRet> LuaFnRet for eyre::Result<R> {
|
||||||
|
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 rust call: {:?}", err));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for arguments that can be put on lua stack.
|
||||||
|
pub(crate) trait LuaPutValue {
|
||||||
|
fn put(&self, lua: LuaState);
|
||||||
|
fn is_non_empty(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for i32 {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_integer(*self as isize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for isize {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_integer(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for u32 {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_integer(unsafe { mem::transmute::<_, i32>(*self) as isize });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for f32 {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_number(*self as f64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for f64 {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_number(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for bool {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_bool(*self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for Cow<'_, str> {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_string(self.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for str {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
lua.push_string(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for EntityID {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
isize::from(self.0).put(lua);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for ComponentID {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
isize::from(self.0).put(lua);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for Color {
|
||||||
|
fn put(&self, _lua: LuaState) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaPutValue for Obj {
|
||||||
|
fn put(&self, _lua: LuaState) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: LuaPutValue> LuaPutValue for Option<T> {
|
||||||
|
fn put(&self, lua: LuaState) {
|
||||||
|
match self {
|
||||||
|
Some(val) => val.put(lua),
|
||||||
|
None => lua.push_nil(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_non_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Some(val) => val.is_non_empty(),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for arguments that can be retrieved from the lua stack.
|
||||||
|
pub(crate) trait LuaGetValue {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for i32 {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(lua.to_integer(index) as Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for isize {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(lua.to_integer(index) as Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for u32 {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(unsafe { mem::transmute(lua.to_integer(index) as i32) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for f32 {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(lua.to_number(index) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for f64 {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(lua.to_number(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for bool {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(lua.to_bool(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for Option<EntityID> {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
let ent = lua.to_integer(index);
|
||||||
|
Ok(if ent == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(EntityID(ent.try_into().unwrap()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for Option<ComponentID> {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
let com = lua.to_integer(index);
|
||||||
|
Ok(if com == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ComponentID(com.try_into().unwrap()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for Cow<'static, str> {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(lua.to_string(index)?.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for () {
|
||||||
|
fn get(_lua: LuaState, _index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for Obj {
|
||||||
|
fn get(_lua: LuaState, _index: i32) -> eyre::Result<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for Color {
|
||||||
|
fn get(_lua: LuaState, _index: i32) -> eyre::Result<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: LuaGetValue> LuaGetValue for Option<T> {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok(if lua.is_nil_or_none(index) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(T::get(lua, index)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: LuaGetValue> LuaGetValue for Vec<T> {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
if T::size_on_stack() != 1 {
|
||||||
|
bail!("Encountered Vec<T> where T needs more than 1 slot on the stack. This isn't supported");
|
||||||
|
}
|
||||||
|
let len = lua.objlen(index);
|
||||||
|
let mut res = Vec::with_capacity(len);
|
||||||
|
for i in 1..=len {
|
||||||
|
lua.index_table(index, dbg!(i));
|
||||||
|
let get = T::get(lua, -1);
|
||||||
|
lua.pop_last();
|
||||||
|
res.push(get?);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T0: LuaGetValue, T1: LuaGetValue> LuaGetValue for (T0, T1) {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
T0::get(lua, index - T1::size_on_stack())?,
|
||||||
|
T1::get(lua, index)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
T0::size_on_stack() + T1::size_on_stack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T0: LuaGetValue, T1: LuaGetValue, T2: LuaGetValue> LuaGetValue for (T0, T1, T2) {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
T0::get(lua, index - T1::size_on_stack() - T2::size_on_stack())?,
|
||||||
|
T1::get(lua, index - T2::size_on_stack())?,
|
||||||
|
T2::get(lua, index)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
T0::size_on_stack() + T1::size_on_stack() + T2::size_on_stack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T0: LuaGetValue, T1: LuaGetValue, T2: LuaGetValue, T3: LuaGetValue> LuaGetValue
|
||||||
|
for (T0, T1, T2, T3)
|
||||||
|
{
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
T0::get(
|
||||||
|
lua,
|
||||||
|
index - T1::size_on_stack() - T2::size_on_stack() - T3::size_on_stack(),
|
||||||
|
)?,
|
||||||
|
T1::get(lua, index - T2::size_on_stack() - T3::size_on_stack())?,
|
||||||
|
T2::get(lua, index - T3::size_on_stack())?,
|
||||||
|
T3::get(lua, index)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
T0::size_on_stack() + T1::size_on_stack() + T2::size_on_stack() + T3::size_on_stack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T0: LuaGetValue, T1: LuaGetValue, T2: LuaGetValue, T3: LuaGetValue, T4: LuaGetValue>
|
||||||
|
LuaGetValue for (T0, T1, T2, T3, T4)
|
||||||
|
{
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let prev = <(T0, T1, T2, T3)>::get(lua, index - T4::size_on_stack())?;
|
||||||
|
Ok((prev.0, prev.1, prev.2, prev.3, T4::get(lua, index)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
<(T0, T1, T2, T3)>::size_on_stack() + T4::size_on_stack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
T0: LuaGetValue,
|
||||||
|
T1: LuaGetValue,
|
||||||
|
T2: LuaGetValue,
|
||||||
|
T3: LuaGetValue,
|
||||||
|
T4: LuaGetValue,
|
||||||
|
T5: LuaGetValue,
|
||||||
|
> LuaGetValue for (T0, T1, T2, T3, T4, T5)
|
||||||
|
{
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let prev = <(T0, T1, T2, T3, T4)>::get(lua, index - T5::size_on_stack())?;
|
||||||
|
Ok((prev.0, prev.1, prev.2, prev.3, prev.4, T5::get(lua, index)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
<(T0, T1, T2, T3, T4)>::size_on_stack() + T5::size_on_stack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaGetValue for (bool, bool, bool, f64, f64, f64, f64, f64, f64, f64, f64) {
|
||||||
|
fn get(lua: LuaState, index: i32) -> eyre::Result<Self> {
|
||||||
|
Ok((
|
||||||
|
bool::get(lua, index - 10)?,
|
||||||
|
bool::get(lua, index - 9)?,
|
||||||
|
bool::get(lua, index - 8)?,
|
||||||
|
f64::get(lua, index - 7)?,
|
||||||
|
f64::get(lua, index - 6)?,
|
||||||
|
f64::get(lua, index - 5)?,
|
||||||
|
f64::get(lua, index - 4)?,
|
||||||
|
f64::get(lua, index - 3)?,
|
||||||
|
f64::get(lua, index - 2)?,
|
||||||
|
f64::get(lua, index - 1)?,
|
||||||
|
f64::get(lua, index)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_on_stack() -> i32 {
|
||||||
|
11
|
||||||
|
}
|
||||||
|
}
|
105
ewext/noita_api_macro/Cargo.lock
generated
Normal file
105
ewext/noita_api_macro/Cargo.lock
generated
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noita_api_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.214"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.214"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.132"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
18
ewext/noita_api_macro/Cargo.toml
Normal file
18
ewext/noita_api_macro/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = ["noita_api", "noita_api_macro"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "noita_api_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
heck = "0.5.0"
|
||||||
|
proc-macro2 = "1.0.89"
|
||||||
|
quote = "1.0.37"
|
||||||
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
|
serde_json = "1.0.132"
|
1
ewext/noita_api_macro/src/components.json
Normal file
1
ewext/noita_api_macro/src/components.json
Normal file
File diff suppressed because one or more lines are too long
312
ewext/noita_api_macro/src/lib.rs
Normal file
312
ewext/noita_api_macro/src/lib.rs
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use heck::ToSnekCase;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
enum Typ {
|
||||||
|
#[serde(rename = "int")]
|
||||||
|
Int,
|
||||||
|
#[serde(rename = "uint32")]
|
||||||
|
UInt,
|
||||||
|
#[serde(rename = "float")]
|
||||||
|
Float,
|
||||||
|
#[serde(rename = "double")]
|
||||||
|
Double,
|
||||||
|
#[serde(rename = "bool")]
|
||||||
|
Bool,
|
||||||
|
#[serde(rename = "std::string")]
|
||||||
|
StdString,
|
||||||
|
#[serde(rename = "vec2")]
|
||||||
|
Vec2,
|
||||||
|
#[serde(other)]
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Typ {
|
||||||
|
fn as_rust_type(&self) -> proc_macro2::TokenStream {
|
||||||
|
match self {
|
||||||
|
Typ::Int => quote!(i32),
|
||||||
|
Typ::UInt => quote!(u32),
|
||||||
|
Typ::Float => quote!(f32),
|
||||||
|
Typ::Double => quote!(f64),
|
||||||
|
Typ::Bool => quote!(bool),
|
||||||
|
Typ::StdString => quote!(Cow<'_, str>),
|
||||||
|
Typ::Vec2 => todo!(),
|
||||||
|
Typ::Other => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
enum Typ2 {
|
||||||
|
#[serde(rename = "int")]
|
||||||
|
Int,
|
||||||
|
#[serde(rename = "number")]
|
||||||
|
Number,
|
||||||
|
#[serde(rename = "string")]
|
||||||
|
String,
|
||||||
|
#[serde(rename = "bool")]
|
||||||
|
Bool,
|
||||||
|
#[serde(rename = "entity_id")]
|
||||||
|
EntityID,
|
||||||
|
#[serde(rename = "component_id")]
|
||||||
|
ComponentID,
|
||||||
|
#[serde(rename = "obj")]
|
||||||
|
Obj,
|
||||||
|
#[serde(rename = "color")]
|
||||||
|
Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Typ2 {
|
||||||
|
fn as_rust_type(&self) -> proc_macro2::TokenStream {
|
||||||
|
match self {
|
||||||
|
Typ2::Int => quote! {i32},
|
||||||
|
Typ2::Number => quote! {f64},
|
||||||
|
Typ2::String => quote! {Cow<str>},
|
||||||
|
Typ2::Bool => quote! {bool},
|
||||||
|
Typ2::EntityID => quote! {EntityID},
|
||||||
|
Typ2::ComponentID => quote!(ComponentID),
|
||||||
|
Typ2::Obj => quote! {Obj},
|
||||||
|
Typ2::Color => quote!(Color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_rust_type_return(&self) -> proc_macro2::TokenStream {
|
||||||
|
match self {
|
||||||
|
Typ2::String => quote! {Cow<'static, str>},
|
||||||
|
Typ2::EntityID => quote! {Option<EntityID>},
|
||||||
|
Typ2::ComponentID => quote!(Option<ComponentID>),
|
||||||
|
_ => self.as_rust_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Field {
|
||||||
|
field: String,
|
||||||
|
typ: Typ,
|
||||||
|
desc: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Component {
|
||||||
|
name: String,
|
||||||
|
fields: Vec<Field>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FnArg {
|
||||||
|
name: String,
|
||||||
|
typ: Typ2,
|
||||||
|
default: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct FnRet {
|
||||||
|
// name: String,
|
||||||
|
typ: Typ2,
|
||||||
|
optional: bool,
|
||||||
|
is_vec: bool,
|
||||||
|
}
|
||||||
|
impl FnRet {
|
||||||
|
fn as_rust_type_return(&self) -> proc_macro2::TokenStream {
|
||||||
|
let mut ret = self.typ.as_rust_type_return();
|
||||||
|
if self.is_vec {
|
||||||
|
ret = quote! {
|
||||||
|
Vec<#ret>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if self.optional {
|
||||||
|
ret = quote! {
|
||||||
|
Option<#ret>
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ApiFn {
|
||||||
|
fn_name: String,
|
||||||
|
desc: String,
|
||||||
|
args: Vec<FnArg>,
|
||||||
|
rets: Vec<FnRet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn generate_components(_item: TokenStream) -> TokenStream {
|
||||||
|
let components: Vec<Component> = serde_json::from_str(include_str!("components.json")).unwrap();
|
||||||
|
|
||||||
|
let res = components.into_iter().map(generate_code_for_component);
|
||||||
|
quote! {#(#res)*}.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_field_name(field_name: &str) -> String {
|
||||||
|
if field_name == "type" {
|
||||||
|
return "type_fld".to_owned();
|
||||||
|
}
|
||||||
|
if field_name == "loop" {
|
||||||
|
return "loop_fld".to_owned();
|
||||||
|
}
|
||||||
|
field_name.to_snek_case()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_code_for_component(com: Component) -> proc_macro2::TokenStream {
|
||||||
|
let component_name = format_ident!("{}", com.name);
|
||||||
|
|
||||||
|
let impls = com.fields.iter().filter_map(|field| {
|
||||||
|
let field_name_s = convert_field_name(&field.field);
|
||||||
|
let field_name = format_ident!("{}", field_name_s);
|
||||||
|
let field_doc = &field.desc;
|
||||||
|
let set_method_name = format_ident!("set_{}", field_name);
|
||||||
|
match field.typ {
|
||||||
|
Typ::Int | Typ::UInt | Typ::Float | Typ::Double | Typ::Bool => {
|
||||||
|
let field_type = field.typ.as_rust_type();
|
||||||
|
Some(quote! {
|
||||||
|
#[doc = #field_doc]
|
||||||
|
pub fn #field_name(self) -> eyre::Result<#field_type> {
|
||||||
|
// This trasmute is used to reinterpret i32 as u32 in one case.
|
||||||
|
raw::component_get_value(self.0, #field_name_s)
|
||||||
|
}
|
||||||
|
#[doc = #field_doc]
|
||||||
|
pub fn #set_method_name(self, value: #field_type) -> eyre::Result<()> {
|
||||||
|
raw::component_set_value(self.0, #field_name_s, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let com_name = com.name;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct #component_name(pub ComponentID);
|
||||||
|
|
||||||
|
impl Component for #component_name {
|
||||||
|
const NAME_STR: &'static str = #com_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ComponentID> for #component_name {
|
||||||
|
fn from(com: ComponentID) -> Self {
|
||||||
|
#component_name(com)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #component_name {
|
||||||
|
#(#impls)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_code_for_api_fn(api_fn: ApiFn) -> proc_macro2::TokenStream {
|
||||||
|
let fn_name = format_ident!("{}", api_fn.fn_name.to_snek_case());
|
||||||
|
let fn_doc = api_fn.desc;
|
||||||
|
|
||||||
|
let args = api_fn.args.iter().map(|arg| {
|
||||||
|
let arg_name = format_ident!("{}", arg.name);
|
||||||
|
let arg_type = arg.typ.as_rust_type();
|
||||||
|
let optional = arg.default.is_some();
|
||||||
|
if optional {
|
||||||
|
quote! {
|
||||||
|
#arg_name: Option<#arg_type>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#arg_name: #arg_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let put_args_pre = api_fn.args.iter().enumerate().map(|(i, arg)| {
|
||||||
|
let arg_name = format_ident!("{}", arg.name);
|
||||||
|
let i = i as i32;
|
||||||
|
quote! {
|
||||||
|
if LuaPutValue::is_non_empty(&#arg_name) {
|
||||||
|
last_non_empty = #i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let put_args = api_fn.args.iter().enumerate().map(|(i, arg)| {
|
||||||
|
let arg_name = format_ident!("{}", arg.name);
|
||||||
|
let i = i as i32;
|
||||||
|
quote! {
|
||||||
|
if #i <= last_non_empty {
|
||||||
|
LuaPutValue::put(&#arg_name, lua);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let ret_type = if api_fn.rets.is_empty() {
|
||||||
|
quote! { () }
|
||||||
|
} else {
|
||||||
|
if api_fn.rets.len() == 1 {
|
||||||
|
let ret = api_fn.rets.first().unwrap();
|
||||||
|
ret.as_rust_type_return()
|
||||||
|
} else {
|
||||||
|
let ret_types = api_fn.rets.iter().map(|ret| ret.as_rust_type_return());
|
||||||
|
quote! { ( #(#ret_types),* ) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let fn_name_c = name_to_c_literal(api_fn.fn_name);
|
||||||
|
|
||||||
|
let ret_count = api_fn.rets.len() as i32;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[doc = #fn_doc]
|
||||||
|
pub fn #fn_name(#(#args,)*) -> eyre::Result<#ret_type> {
|
||||||
|
let lua = LuaState::current()?;
|
||||||
|
|
||||||
|
lua.get_global(#fn_name_c);
|
||||||
|
|
||||||
|
let mut last_non_empty: i32 = -1;
|
||||||
|
#(#put_args_pre)*
|
||||||
|
#(#put_args)*
|
||||||
|
|
||||||
|
lua.call(last_non_empty+1, #ret_count);
|
||||||
|
|
||||||
|
let ret = LuaGetValue::get(lua, -1);
|
||||||
|
lua.pop_last_n(#ret_count);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn generate_api(_item: TokenStream) -> TokenStream {
|
||||||
|
let api_fns: Vec<ApiFn> = serde_json::from_str(include_str!("lua_api.json")).unwrap();
|
||||||
|
|
||||||
|
let res = api_fns.into_iter().map(generate_code_for_api_fn);
|
||||||
|
quote! {#(#res)*}.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn add_lua_fn(item: TokenStream) -> TokenStream {
|
||||||
|
let mut tokens = item.into_iter();
|
||||||
|
|
||||||
|
let fn_name = tokens.next().unwrap().to_string();
|
||||||
|
let fn_name_ident = format_ident!("{fn_name}");
|
||||||
|
let bridge_fn_name = format_ident!("{fn_name}_lua_bridge");
|
||||||
|
let fn_name_c = name_to_c_literal(fn_name);
|
||||||
|
quote! {
|
||||||
|
unsafe extern "C" fn #bridge_fn_name(lua: *mut lua_State) -> c_int {
|
||||||
|
let lua_state = noita_api::lua::LuaState::new(lua);
|
||||||
|
lua_state.make_current();
|
||||||
|
noita_api::lua::LuaFnRet::do_return(#fn_name_ident(lua_state), lua_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
LUA.lua_pushcclosure(lua, Some(#bridge_fn_name), 0);
|
||||||
|
LUA.lua_setfield(lua, -2, #fn_name_c.as_ptr());
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_to_c_literal(name: String) -> proc_macro2::Literal {
|
||||||
|
proc_macro2::Literal::c_string(CString::new(name).unwrap().as_c_str())
|
||||||
|
}
|
7889
ewext/noita_api_macro/src/lua_api.json
Normal file
7889
ewext/noita_api_macro/src/lua_api.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,11 @@
|
||||||
use std::{os::raw::c_void, ptr};
|
use std::{mem, os::raw::c_void, ptr, sync::OnceLock};
|
||||||
|
|
||||||
use iced_x86::{Decoder, DecoderOptions, Mnemonic};
|
use iced_x86::{Decoder, DecoderOptions, Mnemonic};
|
||||||
|
use noita_api::lua::LuaState;
|
||||||
|
|
||||||
|
use crate::noita::ntypes::{EntityManager, ThiscallFn};
|
||||||
|
|
||||||
|
static GRABBED: OnceLock<Grabbed> = OnceLock::new();
|
||||||
|
|
||||||
pub(crate) unsafe fn grab_addr_from_instruction(
|
pub(crate) unsafe fn grab_addr_from_instruction(
|
||||||
func: *const c_void,
|
func: *const c_void,
|
||||||
|
@ -25,3 +30,80 @@ pub(crate) unsafe fn grab_addr_from_instruction(
|
||||||
|
|
||||||
instruction.memory_displacement32() as *mut c_void
|
instruction.memory_displacement32() as *mut c_void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Grabbed {
|
||||||
|
globals: GrabbedGlobals,
|
||||||
|
fns: GrabbedFns,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This only stores pointers that are constant, so should be safe to share between threads.
|
||||||
|
unsafe impl Sync for Grabbed {}
|
||||||
|
unsafe impl Send for Grabbed {}
|
||||||
|
|
||||||
|
pub(crate) struct GrabbedGlobals {
|
||||||
|
// These 3 actually point to a pointer.
|
||||||
|
pub(crate) _game_global: *mut usize,
|
||||||
|
pub(crate) _world_state_entity: *mut usize,
|
||||||
|
pub(crate) entity_manager: *const *mut EntityManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct GrabbedFns {
|
||||||
|
pub(crate) get_entity: *const ThiscallFn, //unsafe extern "C" fn(*const EntityManager, u32) -> *mut Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn grab_addrs(lua: LuaState) {
|
||||||
|
lua.get_global(c"GameGetWorldStateEntity");
|
||||||
|
let base = lua.to_cfunction(-1).unwrap() as *const c_void;
|
||||||
|
let world_state_entity =
|
||||||
|
unsafe { grab_addr_from_instruction(base, 0x007aa7ce - 0x007aa540, Mnemonic::Mov).cast() };
|
||||||
|
println!(
|
||||||
|
"World state entity addr: 0x{:x}",
|
||||||
|
world_state_entity as usize
|
||||||
|
);
|
||||||
|
lua.pop_last();
|
||||||
|
|
||||||
|
lua.get_global(c"GameGetFrameNum");
|
||||||
|
let base = lua.to_cfunction(-1).unwrap() as *const c_void;
|
||||||
|
let load_game_global =
|
||||||
|
unsafe { 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 = unsafe {
|
||||||
|
grab_addr_from_instruction(load_game_global, 0x00439c17 - 0x00439bb0, Mnemonic::Mov).cast()
|
||||||
|
};
|
||||||
|
println!("Game global addr: 0x{:x}", game_global as usize);
|
||||||
|
lua.pop_last();
|
||||||
|
|
||||||
|
lua.get_global(c"EntityGetFilename");
|
||||||
|
let base = lua.to_cfunction(-1).unwrap() as *const c_void;
|
||||||
|
let get_entity = unsafe {
|
||||||
|
mem::transmute_copy(&grab_addr_from_instruction(
|
||||||
|
base,
|
||||||
|
0x0079782b - 0x00797570,
|
||||||
|
Mnemonic::Call,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
println!("get_entity addr: 0x{:x}", get_entity as usize);
|
||||||
|
let entity_manager =
|
||||||
|
unsafe { grab_addr_from_instruction(base, 0x00797821 - 0x00797570, Mnemonic::Mov).cast() };
|
||||||
|
println!("entity_manager addr: 0x{:x}", entity_manager as usize);
|
||||||
|
lua.pop_last();
|
||||||
|
|
||||||
|
GRABBED
|
||||||
|
.set(Grabbed {
|
||||||
|
globals: GrabbedGlobals {
|
||||||
|
_game_global: game_global,
|
||||||
|
_world_state_entity: world_state_entity,
|
||||||
|
entity_manager,
|
||||||
|
},
|
||||||
|
fns: GrabbedFns { get_entity },
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn grabbed_fns() -> &'static GrabbedFns {
|
||||||
|
&GRABBED.get().expect("to be initialized early").fns
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn grabbed_globals() -> &'static GrabbedGlobals {
|
||||||
|
&GRABBED.get().expect("to be initialized early").globals
|
||||||
|
}
|
||||||
|
|
236
ewext/src/lib.rs
236
ewext/src/lib.rs
|
@ -2,28 +2,23 @@ use std::{
|
||||||
arch::asm,
|
arch::asm,
|
||||||
cell::{LazyCell, RefCell},
|
cell::{LazyCell, RefCell},
|
||||||
ffi::{c_int, c_void},
|
ffi::{c_int, c_void},
|
||||||
mem,
|
time::Instant,
|
||||||
sync::LazyLock,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced_x86::Mnemonic;
|
use addr_grabber::{grab_addrs, grabbed_fns, grabbed_globals};
|
||||||
use lua_bindings::{lua_State, Lua51, LUA_GLOBALSINDEX};
|
use eyre::{bail, OptionExt};
|
||||||
use noita::{
|
|
||||||
ntypes::{Entity, EntityManager, ThiscallFn},
|
use noita::{ntypes::Entity, pixel::NoitaPixelRun, ParticleWorldState};
|
||||||
NoitaPixelRun, ParticleWorldState,
|
use noita_api::{
|
||||||
|
lua::{lua_bindings::lua_State, LuaState, ValuesOnStack, LUA},
|
||||||
|
DamageModelComponent,
|
||||||
};
|
};
|
||||||
|
use noita_api_macro::add_lua_fn;
|
||||||
|
|
||||||
mod lua_bindings;
|
pub mod noita;
|
||||||
|
|
||||||
mod noita;
|
|
||||||
|
|
||||||
mod addr_grabber;
|
mod addr_grabber;
|
||||||
|
|
||||||
static LUA: LazyLock<Lua51> = LazyLock::new(|| unsafe {
|
|
||||||
let lib = libloading::Library::new("./lua51.dll").expect("library to exist");
|
|
||||||
Lua51::from_library(lib).expect("library to be lua")
|
|
||||||
});
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static STATE: LazyCell<RefCell<ExtState>> = LazyCell::new(|| {
|
static STATE: LazyCell<RefCell<ExtState>> = LazyCell::new(|| {
|
||||||
println!("Initializing ExtState");
|
println!("Initializing ExtState");
|
||||||
|
@ -31,37 +26,16 @@ thread_local! {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SavedWorldState {
|
|
||||||
game_global: usize,
|
|
||||||
world_state_entity: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GrabbedGlobals {
|
|
||||||
// These 3 actually point to a pointer.
|
|
||||||
game_global: *mut usize,
|
|
||||||
world_state_entity: *mut usize,
|
|
||||||
entity_manager: *const *mut EntityManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GrabbedFns {
|
|
||||||
get_entity: *const ThiscallFn, //unsafe extern "C" fn(*const EntityManager, u32) -> *mut Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ExtState {
|
struct ExtState {
|
||||||
particle_world_state: Option<ParticleWorldState>,
|
particle_world_state: Option<ParticleWorldState>,
|
||||||
globals: Option<GrabbedGlobals>,
|
|
||||||
saved_world_state: Option<SavedWorldState>,
|
|
||||||
fns: Option<GrabbedFns>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// const EWEXT: [(&'static str, Function); 1] = [("testfn", None)];
|
fn init_particle_world_state(lua: LuaState) {
|
||||||
|
|
||||||
unsafe extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int {
|
|
||||||
println!("\nInitializing particle world state");
|
println!("\nInitializing particle world state");
|
||||||
let world_pointer = unsafe { LUA.lua_tointeger(lua, 1) };
|
let world_pointer = lua.to_integer(1);
|
||||||
let chunk_map_pointer = unsafe { LUA.lua_tointeger(lua, 2) };
|
let chunk_map_pointer = lua.to_integer(2);
|
||||||
let material_list_pointer = unsafe { LUA.lua_tointeger(lua, 3) };
|
let material_list_pointer = lua.to_integer(3);
|
||||||
println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}");
|
println!("pws stuff: {world_pointer:?} {chunk_map_pointer:?}");
|
||||||
|
|
||||||
STATE.with(|state| {
|
STATE.with(|state| {
|
||||||
|
@ -72,10 +46,10 @@ unsafe extern "C" fn init_particle_world_state(lua: *mut lua_State) -> c_int {
|
||||||
runner: Default::default(),
|
runner: Default::default(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn encode_area(lua: *mut lua_State) -> 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_x = unsafe { LUA.lua_tointeger(lua, 1) } as i32;
|
||||||
let start_y = unsafe { LUA.lua_tointeger(lua, 2) } as i32;
|
let start_y = unsafe { LUA.lua_tointeger(lua, 2) } as i32;
|
||||||
let end_x = unsafe { LUA.lua_tointeger(lua, 3) } as i32;
|
let end_x = unsafe { LUA.lua_tointeger(lua, 3) } as i32;
|
||||||
|
@ -88,125 +62,79 @@ unsafe extern "C" fn encode_area(lua: *mut lua_State) -> c_int {
|
||||||
let runs = unsafe { pws.encode_area(start_x, start_y, end_x, end_y, encoded_buffer) };
|
let runs = unsafe { pws.encode_area(start_x, start_y, end_x, end_y, encoded_buffer) };
|
||||||
unsafe { LUA.lua_pushinteger(lua, runs as isize) };
|
unsafe { LUA.lua_pushinteger(lua, runs as isize) };
|
||||||
});
|
});
|
||||||
1
|
ValuesOnStack(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn save_world_state() {
|
fn make_ephemerial(lua: LuaState) -> eyre::Result<()> {
|
||||||
STATE.with(|state| {
|
|
||||||
let mut state = state.borrow_mut();
|
|
||||||
let game_global = state.globals.as_ref().unwrap().game_global.read();
|
|
||||||
let world_state_entity = state.globals.as_ref().unwrap().world_state_entity.read();
|
|
||||||
|
|
||||||
state.saved_world_state = Some(SavedWorldState {
|
|
||||||
game_global,
|
|
||||||
world_state_entity,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn load_world_state() {
|
|
||||||
println!("Loading world state");
|
|
||||||
STATE.with(|state| {
|
|
||||||
let state = state.borrow_mut();
|
|
||||||
let saved_ws = state.saved_world_state.as_ref().unwrap();
|
|
||||||
let globals = state.globals.as_ref().unwrap();
|
|
||||||
globals.game_global.write(saved_ws.game_global);
|
|
||||||
globals
|
|
||||||
.world_state_entity
|
|
||||||
.write(saved_ws.world_state_entity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn save_world_state_lua(lua: *mut lua_State) -> i32 {
|
|
||||||
if STATE.with(|state| state.borrow().globals.is_none()) {
|
|
||||||
grab_addrs(lua);
|
|
||||||
}
|
|
||||||
|
|
||||||
save_world_state();
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn load_world_state_lua(_lua: *mut lua_State) -> i32 {
|
|
||||||
load_world_state();
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
.cast();
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
.cast();
|
|
||||||
println!("Game global addr: 0x{:x}", game_global as usize);
|
|
||||||
// Pop the last element.
|
|
||||||
LUA.lua_settop(lua, -2);
|
|
||||||
|
|
||||||
LUA.lua_getfield(lua, LUA_GLOBALSINDEX, c"EntityGetFilename".as_ptr());
|
|
||||||
let base = LUA.lua_tocfunction(lua, -1).unwrap() as *const c_void;
|
|
||||||
let get_entity = mem::transmute_copy(&addr_grabber::grab_addr_from_instruction(
|
|
||||||
base,
|
|
||||||
0x0079782b - 0x00797570,
|
|
||||||
Mnemonic::Call,
|
|
||||||
));
|
|
||||||
println!("get_entity addr: 0x{:x}", get_entity as usize);
|
|
||||||
let entity_manager =
|
|
||||||
addr_grabber::grab_addr_from_instruction(base, 0x00797821 - 0x00797570, Mnemonic::Mov)
|
|
||||||
.cast();
|
|
||||||
println!("entity_manager addr: 0x{:x}", entity_manager 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,
|
|
||||||
entity_manager,
|
|
||||||
});
|
|
||||||
state.borrow_mut().fns = Some(GrabbedFns { get_entity })
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn make_ephemerial(lua: *mut lua_State) -> c_int {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let entity_id = LUA.lua_tointeger(lua, 1) as u32;
|
let entity_id = lua.to_integer(1) as u32;
|
||||||
STATE.with(|state| {
|
|
||||||
let state = state.borrow();
|
let entity_manager = grabbed_globals().entity_manager.read();
|
||||||
let entity_manager = state.globals.as_ref().unwrap().entity_manager.read();
|
|
||||||
let mut entity: *mut Entity;
|
let mut entity: *mut Entity;
|
||||||
asm!(
|
asm!(
|
||||||
"mov ecx, {entity_manager}",
|
"mov ecx, {entity_manager}",
|
||||||
"push {entity_id:e}",
|
"push {entity_id:e}",
|
||||||
"call {get_entity}",
|
"call {get_entity}",
|
||||||
entity_manager = in(reg) entity_manager,
|
entity_manager = in(reg) entity_manager,
|
||||||
get_entity = in(reg) state.fns.as_ref().unwrap().get_entity,
|
get_entity = in(reg) grabbed_fns().get_entity,
|
||||||
entity_id = in(reg) entity_id,
|
entity_id = in(reg) entity_id,
|
||||||
clobber_abi("C"),
|
clobber_abi("C"),
|
||||||
out("ecx") _,
|
out("ecx") _,
|
||||||
out("eax") entity,
|
out("eax") entity,
|
||||||
);
|
);
|
||||||
// let entity = (state.fns.as_ref().unwrap().get_entity)(entity_manager, entity_id);
|
if entity.is_null() {
|
||||||
entity.cast::<c_void>().offset(0x8).cast::<u32>().write(0);
|
bail!("Entity {} not found", entity_id);
|
||||||
})
|
|
||||||
}
|
}
|
||||||
0
|
entity.cast::<c_void>().offset(0x8).cast::<u32>().write(0);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_world_initialized(lua: LuaState) {
|
||||||
|
grab_addrs(lua);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_fn(_lua: LuaState) -> eyre::Result<()> {
|
||||||
|
let start = Instant::now();
|
||||||
|
let iters = 10000;
|
||||||
|
for _ in 0..iters {
|
||||||
|
let player = noita_api::raw::entity_get_closest_with_tag(0.0, 0.0, "player_unit".into())?
|
||||||
|
.ok_or_eyre("Entity not found")?;
|
||||||
|
noita_api::raw::entity_set_transform(player, 0.0, Some(0.0), None, None, None)?;
|
||||||
|
}
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
noita_api::raw::game_print(
|
||||||
|
format!(
|
||||||
|
"Took {}us to test, {}ns per call",
|
||||||
|
elapsed.as_micros(),
|
||||||
|
elapsed.as_nanos() / iters
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_fn(_lua: LuaState) -> eyre::Result<()> {
|
||||||
|
let player = noita_api::raw::entity_get_closest_with_tag(0.0, 0.0, "player_unit".into())?
|
||||||
|
.ok_or_eyre("Entity not found")?;
|
||||||
|
let damage_model: DamageModelComponent = player.get_first_component(None)?;
|
||||||
|
let hp = damage_model.hp()?;
|
||||||
|
damage_model.set_hp(hp - 1.0)?;
|
||||||
|
|
||||||
|
let (x, y, _, _, _) = noita_api::raw::entity_get_transform(player)?;
|
||||||
|
|
||||||
|
noita_api::raw::game_print(
|
||||||
|
format!("Component: {:?}, Hp: {}", damage_model.0, hp * 25.0,).into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let entities = noita_api::raw::entity_get_in_radius_with_tag(x, y, 300.0, "enemy".into())?;
|
||||||
|
noita_api::raw::game_print(format!("{:?}", entities).into())?;
|
||||||
|
|
||||||
|
// noita::api::raw::entity_set_transform(player, 0.0, 0.0, 0.0, 1.0, 1.0)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -218,16 +146,12 @@ pub unsafe extern "C" fn luaopen_ewext0(lua: *mut lua_State) -> c_int {
|
||||||
unsafe {
|
unsafe {
|
||||||
LUA.lua_createtable(lua, 0, 0);
|
LUA.lua_createtable(lua, 0, 0);
|
||||||
|
|
||||||
LUA.lua_pushcclosure(lua, Some(init_particle_world_state), 0);
|
add_lua_fn!(init_particle_world_state);
|
||||||
LUA.lua_setfield(lua, -2, c"init_particle_world_state".as_ptr());
|
add_lua_fn!(encode_area);
|
||||||
LUA.lua_pushcclosure(lua, Some(encode_area), 0);
|
add_lua_fn!(make_ephemerial);
|
||||||
LUA.lua_setfield(lua, -2, c"encode_area".as_ptr());
|
add_lua_fn!(on_world_initialized);
|
||||||
LUA.lua_pushcclosure(lua, Some(load_world_state_lua), 0);
|
add_lua_fn!(test_fn);
|
||||||
LUA.lua_setfield(lua, -2, c"load_world_state".as_ptr());
|
add_lua_fn!(bench_fn);
|
||||||
LUA.lua_pushcclosure(lua, Some(save_world_state_lua), 0);
|
|
||||||
LUA.lua_setfield(lua, -2, c"save_world_state".as_ptr());
|
|
||||||
LUA.lua_pushcclosure(lua, Some(make_ephemerial), 0);
|
|
||||||
LUA.lua_setfield(lua, -2, c"make_ephemerial".as_ptr());
|
|
||||||
}
|
}
|
||||||
println!("Initializing ewext - Ok");
|
println!("Initializing ewext - Ok");
|
||||||
1
|
1
|
||||||
|
|
|
@ -1,92 +1,14 @@
|
||||||
use std::{ffi::c_void, mem};
|
use std::{ffi::c_void, mem};
|
||||||
|
|
||||||
pub(crate) mod ntypes;
|
pub(crate) mod ntypes;
|
||||||
|
pub(crate) mod pixel;
|
||||||
#[repr(packed)]
|
|
||||||
pub(crate) struct NoitaPixelRun {
|
|
||||||
length: u16,
|
|
||||||
material: u16,
|
|
||||||
flags: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from proxy.
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
|
||||||
pub(crate) struct RawPixel {
|
|
||||||
pub material: u16,
|
|
||||||
pub flags: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from proxy.
|
|
||||||
/// Stores a run of pixels.
|
|
||||||
/// Not specific to Noita side - length is an actual length
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct PixelRun<Pixel> {
|
|
||||||
pub length: u32,
|
|
||||||
pub data: Pixel,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copied from proxy.
|
|
||||||
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
|
||||||
pub(crate) struct PixelRunner<Pixel> {
|
|
||||||
current_pixel: Option<Pixel>,
|
|
||||||
current_run_len: u32,
|
|
||||||
runs: Vec<PixelRun<Pixel>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Pixel: Eq + Copy> Default for PixelRunner<Pixel> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
current_pixel: None,
|
|
||||||
current_run_len: 0,
|
|
||||||
runs: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn put_pixel(&mut self, pixel: Pixel) {
|
|
||||||
if let Some(current) = self.current_pixel {
|
|
||||||
if pixel != current {
|
|
||||||
self.runs.push(PixelRun {
|
|
||||||
length: self.current_run_len,
|
|
||||||
data: current,
|
|
||||||
});
|
|
||||||
self.current_pixel = Some(pixel);
|
|
||||||
self.current_run_len = 1;
|
|
||||||
} else {
|
|
||||||
self.current_run_len += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.current_pixel = Some(pixel);
|
|
||||||
self.current_run_len = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn build(&mut self) -> &[PixelRun<Pixel>] {
|
|
||||||
if self.current_run_len > 0 {
|
|
||||||
self.runs.push(PixelRun {
|
|
||||||
length: self.current_run_len,
|
|
||||||
data: self.current_pixel.expect("has current pixel"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
&mut self.runs
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.current_pixel = None;
|
|
||||||
self.current_run_len = 0;
|
|
||||||
self.runs.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct ParticleWorldState {
|
pub(crate) struct ParticleWorldState {
|
||||||
pub(crate) _world_ptr: *mut c_void,
|
pub(crate) _world_ptr: *mut c_void,
|
||||||
pub(crate) chunk_map_ptr: *mut c_void,
|
pub(crate) chunk_map_ptr: *mut c_void,
|
||||||
pub(crate) material_list_ptr: *const c_void,
|
pub(crate) material_list_ptr: *const c_void,
|
||||||
|
|
||||||
pub(crate) runner: PixelRunner<RawPixel>,
|
pub(crate) runner: pixel::PixelRunner<pixel::RawPixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticleWorldState {
|
impl ParticleWorldState {
|
||||||
|
@ -127,7 +49,7 @@ impl ParticleWorldState {
|
||||||
start_y: i32,
|
start_y: i32,
|
||||||
end_x: i32,
|
end_x: i32,
|
||||||
end_y: i32,
|
end_y: i32,
|
||||||
mut pixel_runs: *mut NoitaPixelRun,
|
mut pixel_runs: *mut pixel::NoitaPixelRun,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// Allow compiler to generate better code.
|
// Allow compiler to generate better code.
|
||||||
assert_eq!(start_x % 128, 0);
|
assert_eq!(start_x % 128, 0);
|
||||||
|
@ -137,7 +59,7 @@ impl ParticleWorldState {
|
||||||
|
|
||||||
for y in start_y..end_y {
|
for y in start_y..end_y {
|
||||||
for x in start_x..end_x {
|
for x in start_x..end_x {
|
||||||
let mut raw_pixel = RawPixel {
|
let mut raw_pixel = pixel::RawPixel {
|
||||||
material: 0,
|
material: 0,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
};
|
};
|
||||||
|
|
78
ewext/src/noita/pixel.rs
Normal file
78
ewext/src/noita/pixel.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#[repr(packed)]
|
||||||
|
pub(crate) struct NoitaPixelRun {
|
||||||
|
pub(crate) length: u16,
|
||||||
|
pub(crate) material: u16,
|
||||||
|
pub(crate) flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct RawPixel {
|
||||||
|
pub material: u16,
|
||||||
|
pub flags: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
/// Stores a run of pixels.
|
||||||
|
/// Not specific to Noita side - length is an actual length
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PixelRun<Pixel> {
|
||||||
|
pub length: u32,
|
||||||
|
pub data: Pixel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copied from proxy.
|
||||||
|
/// Converts a normal sequence of pixels to a run-length-encoded one.
|
||||||
|
pub(crate) struct PixelRunner<Pixel> {
|
||||||
|
pub(crate) current_pixel: Option<Pixel>,
|
||||||
|
pub(crate) current_run_len: u32,
|
||||||
|
pub(crate) runs: Vec<PixelRun<Pixel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pixel: Eq + Copy> Default for PixelRunner<Pixel> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Pixel: Eq + Copy> PixelRunner<Pixel> {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current_pixel: None,
|
||||||
|
current_run_len: 0,
|
||||||
|
runs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn put_pixel(&mut self, pixel: Pixel) {
|
||||||
|
if let Some(current) = self.current_pixel {
|
||||||
|
if pixel != current {
|
||||||
|
self.runs.push(PixelRun {
|
||||||
|
length: self.current_run_len,
|
||||||
|
data: current,
|
||||||
|
});
|
||||||
|
self.current_pixel = Some(pixel);
|
||||||
|
self.current_run_len = 1;
|
||||||
|
} else {
|
||||||
|
self.current_run_len += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.current_pixel = Some(pixel);
|
||||||
|
self.current_run_len = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn build(&mut self) -> &[PixelRun<Pixel>] {
|
||||||
|
if self.current_run_len > 0 {
|
||||||
|
self.runs.push(PixelRun {
|
||||||
|
length: self.current_run_len,
|
||||||
|
data: self.current_pixel.expect("has current pixel"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
&mut self.runs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear(&mut self) {
|
||||||
|
self.current_pixel = None;
|
||||||
|
self.current_run_len = 0;
|
||||||
|
self.runs.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ local module = {}
|
||||||
|
|
||||||
function module.on_world_initialized()
|
function module.on_world_initialized()
|
||||||
initial_world_state_entity = GameGetWorldStateEntity()
|
initial_world_state_entity = GameGetWorldStateEntity()
|
||||||
ewext.save_world_state()
|
ewext.on_world_initialized()
|
||||||
local grid_world = world_ffi.get_grid_world()
|
local grid_world = world_ffi.get_grid_world()
|
||||||
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
local chunk_map = grid_world.vtable.get_chunk_map(grid_world)
|
||||||
grid_world = tonumber(ffi.cast("intptr_t", grid_world))
|
grid_world = tonumber(ffi.cast("intptr_t", grid_world))
|
||||||
|
@ -35,10 +35,34 @@ function module.on_local_player_spawn()
|
||||||
EntitySetTransform(GameGetWorldStateEntity(), 0, 0)
|
EntitySetTransform(GameGetWorldStateEntity(), 0, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function fw_button(label)
|
||||||
|
return imgui.Button(label, imgui.GetWindowWidth() - 15, 20)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bench_fn_lua()
|
||||||
|
local start = GameGetRealWorldTimeSinceStarted()
|
||||||
|
for i=1,10000 do
|
||||||
|
local player = EntityGetClosestWithTag(0, 0, "player_unit")
|
||||||
|
EntitySetTransform(player, 0, 0, 0, 1, 1)
|
||||||
|
end
|
||||||
|
local elapsed = GameGetRealWorldTimeSinceStarted() - start
|
||||||
|
GamePrint(elapsed*1000000)
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.on_draw_debug_window(imgui)
|
||||||
|
if imgui.CollapsingHeader("ewext") then
|
||||||
|
if fw_button("test_fn") then
|
||||||
|
ewext.test_fn()
|
||||||
|
end
|
||||||
|
if fw_button("bench") then
|
||||||
|
ewext.bench_fn()
|
||||||
|
bench_fn_lua()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function module.on_world_update()
|
function module.on_world_update()
|
||||||
if GameGetWorldStateEntity() ~= initial_world_state_entity then
|
if GameGetWorldStateEntity() ~= initial_world_state_entity then
|
||||||
-- -- EntityKill(GameGetWorldStateEntity())
|
|
||||||
-- ewext.load_world_state()
|
|
||||||
oh_another_world_state(GameGetWorldStateEntity())
|
oh_another_world_state(GameGetWorldStateEntity())
|
||||||
initial_world_state_entity = GameGetWorldStateEntity()
|
initial_world_state_entity = GameGetWorldStateEntity()
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,23 +1,40 @@
|
||||||
import shlex
|
import shlex
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
all_types = set()
|
||||||
|
|
||||||
|
renames = {
|
||||||
|
"std_string": "std::string",
|
||||||
|
}
|
||||||
|
|
||||||
def parse_component(component):
|
def parse_component(component):
|
||||||
it = iter(component)
|
it = iter(component)
|
||||||
name = next(it)
|
c_name = next(it)
|
||||||
|
c_name = c_name.strip("\n")
|
||||||
|
if "-" in c_name or "\n" in c_name:
|
||||||
|
print(component)
|
||||||
|
exit(-1)
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
for line in it:
|
for line in it:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith("-"):
|
if line.startswith("-"):
|
||||||
continue
|
continue
|
||||||
typ, name, *range_info, desc = shlex.split(line)
|
typ, name, *range_info, desc = shlex.split(line)
|
||||||
|
name = name.strip("\n")
|
||||||
|
if name == "-":
|
||||||
|
print(f"Field of type {typ} skipped")
|
||||||
|
continue
|
||||||
|
typ = renames.get(typ, typ)
|
||||||
fields.append({
|
fields.append({
|
||||||
"field": name,
|
"field": name,
|
||||||
"typ": typ,
|
"typ": typ,
|
||||||
"desc": desc,
|
"desc": desc,
|
||||||
})
|
})
|
||||||
|
all_types.add(typ)
|
||||||
#print(name, typ, desc, range_info)
|
#print(name, typ, desc, range_info)
|
||||||
return {
|
return {
|
||||||
"name": name,
|
"name": c_name,
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +43,8 @@ path = "/home/quant/.local/share/Steam/steamapps/common/Noita/tools_modding/comp
|
||||||
|
|
||||||
components = []
|
components = []
|
||||||
current = []
|
current = []
|
||||||
for line in open(path):
|
|
||||||
|
for i, line in enumerate(open(path)):
|
||||||
if line == "\n":
|
if line == "\n":
|
||||||
if current:
|
if current:
|
||||||
components.append(current)
|
components.append(current)
|
||||||
|
@ -37,5 +55,7 @@ for line in open(path):
|
||||||
assert not current
|
assert not current
|
||||||
|
|
||||||
parsed = [parse_component(component) for component in components]
|
parsed = [parse_component(component) for component in components]
|
||||||
json.dump(parsed, open("components.json", "w"), indent=None)
|
json.dump(parsed, open("ewext/noita_api_macro/src/components.json", "w"), indent=None)
|
||||||
|
|
||||||
|
#print(*all_types, sep="\n")
|
||||||
|
|
||||||
|
|
170
scripts/parse_lua_api.py
Normal file
170
scripts/parse_lua_api.py
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
path = "/home/quant/.local/share/Steam/steamapps/common/Noita/tools_modding/lua_api_documentation.html"
|
||||||
|
|
||||||
|
lines = open(path).readlines()
|
||||||
|
|
||||||
|
lines_iter = iter(lines)
|
||||||
|
|
||||||
|
parsed = []
|
||||||
|
|
||||||
|
def maybe_map_types(name, typ):
|
||||||
|
if typ == "multiple types":
|
||||||
|
raise ValueError("no 'multiple types' either")
|
||||||
|
if name == "entity_id":
|
||||||
|
typ = "entity_id"
|
||||||
|
if name == "component_id":
|
||||||
|
typ = "component_id"
|
||||||
|
if typ == "float":
|
||||||
|
typ = "number"
|
||||||
|
if typ == "uint":
|
||||||
|
typ = "color"
|
||||||
|
if typ == "uint32":
|
||||||
|
typ = "color"
|
||||||
|
if typ == "name":
|
||||||
|
typ = "string"
|
||||||
|
if typ == "bool_is_new":
|
||||||
|
typ = "bool"
|
||||||
|
if typ == "boolean":
|
||||||
|
typ = "bool"
|
||||||
|
if typ == "item_entity_id":
|
||||||
|
typ = "entity_id"
|
||||||
|
if typ == "physics_body_id":
|
||||||
|
raise ValueError(f"{typ} not supported")
|
||||||
|
return typ
|
||||||
|
|
||||||
|
def parse_arg(arg_s):
|
||||||
|
if "|" in arg_s:
|
||||||
|
raise ValueError("multiple argument types not supported")
|
||||||
|
if "{" in arg_s:
|
||||||
|
raise ValueError("no table support for now")
|
||||||
|
if "multiple_types" in arg_s:
|
||||||
|
raise ValueError("no 'multiple_types' either")
|
||||||
|
other, *default = arg_s.split("=", maxsplit=1)
|
||||||
|
other = other.strip()
|
||||||
|
if default:
|
||||||
|
default = default[0].strip()
|
||||||
|
else:
|
||||||
|
default = None
|
||||||
|
name, typ = other.split(":", maxsplit=1)
|
||||||
|
|
||||||
|
typ = maybe_map_types(name, typ)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"typ": typ,
|
||||||
|
"default": default,
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse_ret(ret_s):
|
||||||
|
if not ret_s:
|
||||||
|
return None
|
||||||
|
|
||||||
|
optional = ret_s.endswith("|nil")
|
||||||
|
ret_s = ret_s.removesuffix("|nil")
|
||||||
|
|
||||||
|
if "|" in ret_s:
|
||||||
|
raise ValueError("multiple return types not supported")
|
||||||
|
if "multiple_types" in ret_s:
|
||||||
|
raise ValueError("no 'multiple_types' either")
|
||||||
|
|
||||||
|
returns_vec = False
|
||||||
|
if ret_s.startswith("{"):
|
||||||
|
ret_s = ret_s.removeprefix("{").removesuffix("}")
|
||||||
|
returns_vec = True
|
||||||
|
if "-" in ret_s:
|
||||||
|
raise ValueError("No support for key-value tables in returns")
|
||||||
|
|
||||||
|
typ = ret_s
|
||||||
|
name = None
|
||||||
|
if ":" in ret_s:
|
||||||
|
name, typ = ret_s.split(":", maxsplit=1)
|
||||||
|
|
||||||
|
if typ.endswith(" -"):
|
||||||
|
optional = True
|
||||||
|
typ = typ.removesuffix(" -")
|
||||||
|
|
||||||
|
typ = maybe_map_types(name, typ)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"typ": typ,
|
||||||
|
"optional": optional,
|
||||||
|
"is_vec": returns_vec,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ignore = {
|
||||||
|
# Those have some specifics that make generic way of handling things not work on them
|
||||||
|
"PhysicsApplyForceOnArea",
|
||||||
|
"GetRandomActionWithType",
|
||||||
|
"GetParallelWorldPosition",
|
||||||
|
"EntityGetFirstHitboxCenter",
|
||||||
|
"InputGetJoystickAnalogStick",
|
||||||
|
"PhysicsAddBodyImage",
|
||||||
|
"PhysicsBodyIDGetBodyAABB",
|
||||||
|
"GuiTextInput",
|
||||||
|
}
|
||||||
|
skipped = 0
|
||||||
|
deprecated = 0
|
||||||
|
|
||||||
|
# 2 lazy 2 parse xml properly
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
line = next(lines_iter)
|
||||||
|
if line.startswith('<th><span class="function">'):
|
||||||
|
fn_line = line.strip()
|
||||||
|
ret_line = next(lines_iter).strip()
|
||||||
|
desc_line = next(lines_iter).strip()
|
||||||
|
|
||||||
|
fn_name, other = fn_line.removeprefix('<th><span class="function">').split('</span>(<span class="field_name">', maxsplit=1)
|
||||||
|
args = other.removesuffix('</span><span class="func">)</span></th>').strip().split(", ")
|
||||||
|
try:
|
||||||
|
args = [parse_arg(arg) for arg in args if ":" in arg]
|
||||||
|
except ValueError as e:
|
||||||
|
skipped += 1
|
||||||
|
print(f"Skipping {fn_name}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
rets = ret_line.removeprefix('<th><span class="field_name">').removesuffix('</span></th></th>').strip()
|
||||||
|
desc = desc_line.removeprefix('<th><span class="description">').removesuffix('</span></th></th>').strip().replace("</br>", "\n")
|
||||||
|
|
||||||
|
if "Debugish" in rets:
|
||||||
|
rets, desc = rets.split(" (", maxsplit=1)
|
||||||
|
desc = desc.removesuffix(")")
|
||||||
|
rets = rets.split(", ")
|
||||||
|
try:
|
||||||
|
rets = [parse_ret(ret) for ret in rets if ret]
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Skipping {fn_name}: {e}")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
if not desc:
|
||||||
|
desc = "Nolla forgot to include a description :("
|
||||||
|
|
||||||
|
if "Deprecated" in desc_line:
|
||||||
|
deprecated += 1
|
||||||
|
print(f"Skipping {fn_name}: deprecated")
|
||||||
|
continue
|
||||||
|
|
||||||
|
#print(fn_line, ret_line, desc_line)
|
||||||
|
|
||||||
|
if fn_name not in ignore:
|
||||||
|
#print(fn_name, args, "->", ret)
|
||||||
|
parsed.append({
|
||||||
|
"fn_name": fn_name,
|
||||||
|
"args": args,
|
||||||
|
"desc": desc,
|
||||||
|
"rets": rets
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
skipped += 1
|
||||||
|
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
print("Total skipped:", skipped, deprecated)
|
||||||
|
print("Total parsed:", len(parsed))
|
||||||
|
json.dump(parsed, open("ewext/noita_api_macro/src/lua_api.json", "w"), indent=2)
|
Loading…
Add table
Add a link
Reference in a new issue