Wip stuff

This commit is contained in:
IQuant 2024-11-24 18:46:38 +03:00
parent c781187e57
commit a830e0f33c
6 changed files with 227 additions and 83 deletions

View file

@ -37,6 +37,14 @@ impl Typ {
}
}
#[derive(Deserialize)]
enum Typ2 {
#[serde(rename = "int")]
Int,
#[serde(other)]
Other,
}
#[derive(Deserialize)]
struct Field {
field: String,
@ -49,6 +57,21 @@ struct Component {
name: String,
fields: Vec<Field>,
}
#[derive(Deserialize)]
struct FnArg {
name: String,
typ: Typ2, // TODO
default: Option<String>,
}
#[derive(Deserialize)]
struct ApiFn {
fn_name: String,
desc: String,
args: Vec<FnArg>,
}
#[proc_macro]
pub fn generate_components(_item: TokenStream) -> TokenStream {
let components: Vec<Component> = serde_json::from_str(include_str!("components.json")).unwrap();
@ -97,6 +120,23 @@ fn generate_code_for_component(com: Component) -> proc_macro2::TokenStream {
}
}
fn generate_code_for_api_fn(api_fn: ApiFn) -> proc_macro2::TokenStream {
let fn_name = format_ident!("{}", api_fn.fn_name.to_snek_case());
quote! {
pub(crate) fn #fn_name() {
}
}
}
#[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();

File diff suppressed because one or more lines are too long

View file

@ -9,7 +9,7 @@ use addr_grabber::{grab_addrs, grabbed_fns, grabbed_globals};
use eyre::bail;
use lua_bindings::{lua_State, Lua51};
use lua_state::{LuaState, ValuesOnStack};
use noita::{ntypes::Entity, NoitaPixelRun, ParticleWorldState};
use noita::{ntypes::Entity, pixel::NoitaPixelRun, ParticleWorldState};
use noita_api_macro::add_lua_fn;
mod lua_bindings;

View file

@ -1,95 +1,18 @@
use std::{ffi::c_void, mem};
pub(crate) mod ntypes;
pub(crate) mod pixel;
mod api {
noita_api_macro::generate_components!();
}
#[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) _world_ptr: *mut c_void,
pub(crate) chunk_map_ptr: *mut c_void,
pub(crate) material_list_ptr: *const c_void,
pub(crate) runner: PixelRunner<RawPixel>,
pub(crate) runner: pixel::PixelRunner<pixel::RawPixel>,
}
impl ParticleWorldState {
@ -130,7 +53,7 @@ impl ParticleWorldState {
start_y: i32,
end_x: i32,
end_y: i32,
mut pixel_runs: *mut NoitaPixelRun,
mut pixel_runs: *mut pixel::NoitaPixelRun,
) -> usize {
// Allow compiler to generate better code.
assert_eq!(start_x % 128, 0);
@ -140,7 +63,7 @@ impl ParticleWorldState {
for y in start_y..end_y {
for x in start_x..end_x {
let mut raw_pixel = RawPixel {
let mut raw_pixel = pixel::RawPixel {
material: 0,
flags: 0,
};

78
ewext/src/noita/pixel.rs Normal file
View 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();
}
}

102
scripts/parse_lua_api.py Normal file
View file

@ -0,0 +1,102 @@
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 parse_arg(arg_s):
if "|" in arg_s:
raise ValueError("multiple argument types not supported")
other, *default = arg_s.split(" = ", maxsplit=1)
if default:
default = default[0]
else:
default = None
name, typ = other.split(":", maxsplit=1)
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 "{" in ret_s:
raise ValueError("tables in returns not supported")
typ = other
name = None
if ":" in other:
name, typ = other.split(":", maxsplit=1)
if name == "entity_id":
typ = "entity_id"
return {
"name": name,
"typ": typ,
"optional": optional
}
ignore = {
}
skipped = 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
ret = ret_line.removeprefix('<th><span class="field_name">').removesuffix('</span></th></th>').strip()
try:
ret = parse_ret(ret)
except ValueError as e:
print(f"Skipping {fn_name}: {e}")
skipped += 1
continue
desc = desc_line.removeprefix('<th><span class="description">').removesuffix('</span></th></th>').strip().replace("</br>", "\n")
if not desc:
desc = "Nolla forgot to include a description :("
#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,
})
except StopIteration:
pass
print("Total skipped:", skipped)
json.dump(parsed, open("ewext/noita_api_macro/src/lua_api.json", "w"), indent=None)