get all vtables, more ewext world data setup

This commit is contained in:
bgkillas 2025-07-15 08:19:09 -04:00
parent 29abf3f26f
commit 3d530ca648
12 changed files with 327 additions and 195 deletions

View file

@ -203,7 +203,6 @@ impl ChunkOps for ParticleWorldState {
if !chunk.modified {
return Ok(());
}
let (shift_x, shift_y) = self.get_shift::<CHUNK_SIZE>(cx, cy);
let Some(pixel_array) = unsafe { self.world_ptr.as_mut() }
.wrap_err("no world")?
.chunk_map
@ -212,12 +211,13 @@ impl ChunkOps for ParticleWorldState {
else {
return Err(eyre!("chunk not loaded"));
};
let (shift_x, shift_y) = self.get_shift::<CHUNK_SIZE>(cx, cy);
let x = cx * CHUNK_SIZE as isize;
let y = cy * CHUNK_SIZE as isize;
let blob_cell = unsafe {
Box::new(types::LiquidCell::create(
&self.material_list[blob as usize],
self.cell_vtable,
self.cell_vtables.liquid(),
self.world_ptr,
))
};

1
ewext/Cargo.lock generated
View file

@ -142,6 +142,7 @@ dependencies = [
"libloading",
"noita_api",
"rand",
"rayon",
"rustc-hash",
"shared",
]

View file

@ -25,6 +25,7 @@ libloading = "0.8.6"
rand = "0.9.0"
rustc-hash = "2.0.0"
bimap = "0.6.3"
rayon = "1.10.0"
[features]
#enables cross-compilation on older systems (for example, when compiling on ubuntu 20.04)

View file

@ -1,19 +1,44 @@
use crate::WorldSync;
use crate::modules::{Module, ModuleCtx};
use eyre::{ContextCompat, eyre};
use noita_api::noita::types::{CellType, FireCell, GasCell, LiquidCell};
use noita_api::noita::world::ParticleWorldState;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use shared::NoitaOutbound;
use shared::world_sync::{ChunkCoord, NoitaWorldUpdate, ProxyToWorldSync, WorldSyncToProxy};
use shared::world_sync::{
CHUNK_SIZE, ChunkCoord, NoitaWorldUpdate, PixelFlags, ProxyToWorldSync, RawPixel,
WorldSyncToProxy,
};
use std::ptr;
impl Module for WorldSync {
fn on_world_update(&mut self, ctx: &mut ModuleCtx) -> eyre::Result<()> {
let mut update = NoitaWorldUpdate {
let update = NoitaWorldUpdate {
coord: ChunkCoord(0, 0),
runs: vec![],
runs: Vec::with_capacity(16384),
};
unsafe {
let upd0 = std::array::from_fn(|_| RawPixel {
material: 0,
flags: PixelFlags::Unknown,
});
let upd1 = std::array::from_fn(|_| RawPixel {
material: 0,
flags: PixelFlags::Unknown,
});
let upd2 = std::array::from_fn(|_| RawPixel {
material: 0,
flags: PixelFlags::Unknown,
});
let upd3 = std::array::from_fn(|_| RawPixel {
material: 0,
flags: PixelFlags::Unknown,
});
let mut arr = [upd0, upd1, upd2, upd3];
arr.par_iter_mut().try_for_each(|upd| unsafe {
self.particle_world_state
.assume_init_ref()
.encode_world(ChunkCoord(0, 0), &mut update)?
};
.encode_world(ChunkCoord(-2, -7), upd)
})?;
std::hint::black_box(arr);
let msg = NoitaOutbound::WorldSyncToProxy(WorldSyncToProxy::Updates(vec![update]));
ctx.net.send(&msg)?;
Ok(())
@ -24,22 +49,25 @@ impl WorldSync {
match msg {
ProxyToWorldSync::Updates(updates) => {
for chunk in updates {
let time = std::time::Instant::now();
unsafe {
self.particle_world_state
.assume_init_ref()
.decode_world(chunk)?
}
noita_api::print!("de {}", time.elapsed().as_micros());
}
}
}
Ok(())
}
}
pub const SCALE: isize = (512 / CHUNK_SIZE as isize).ilog2() as isize;
trait WorldData {
unsafe fn encode_world(
&self,
coord: ChunkCoord,
chunk: &mut NoitaWorldUpdate,
chunk: &mut [RawPixel; CHUNK_SIZE * CHUNK_SIZE],
) -> eyre::Result<()>;
unsafe fn decode_world(&self, chunk: NoitaWorldUpdate) -> eyre::Result<()>;
}
@ -47,16 +75,87 @@ impl WorldData for ParticleWorldState {
unsafe fn encode_world(
&self,
coord: ChunkCoord,
chunk: &mut NoitaWorldUpdate,
chunk: &mut [RawPixel; CHUNK_SIZE * CHUNK_SIZE],
) -> eyre::Result<()> {
chunk.coord = coord;
let runs = &mut chunk.runs;
runs.clear();
let (cx, cy) = (coord.0 as isize, coord.1 as isize);
let Some(pixel_array) = unsafe { self.world_ptr.as_mut() }
.wrap_err("no world")?
.chunk_map
.chunk_array
.get(cx >> SCALE, cy >> SCALE)
else {
return Err(eyre!("chunk not loaded"));
};
let (shift_x, shift_y) = self.get_shift::<CHUNK_SIZE>(cx, cy);
for ((i, j), p) in (shift_x..shift_x + CHUNK_SIZE as isize)
.flat_map(|i| (shift_y..shift_y + CHUNK_SIZE as isize).map(move |j| (i, j)))
.zip(chunk.iter_mut())
{
*p = pixel_array.get_raw_pixel(i, j);
}
Ok(())
}
unsafe fn decode_world(&self, chunk: NoitaWorldUpdate) -> eyre::Result<()> {
std::hint::black_box(chunk);
//TODO
let chunk_coord = chunk.coord;
let (cx, cy) = (chunk_coord.0 as isize, chunk_coord.1 as isize);
let Some(pixel_array) = unsafe { self.world_ptr.as_mut() }
.wrap_err("no world")?
.chunk_map
.chunk_array
.get_mut(cx >> SCALE, cy >> SCALE)
else {
return Err(eyre!("chunk not loaded"));
};
let (shift_x, shift_y) = self.get_shift::<CHUNK_SIZE>(cx, cy);
let start_x = cx * CHUNK_SIZE as isize;
let start_y = cy * CHUNK_SIZE as isize;
let mut x = 0;
let mut y = 0;
for run in chunk.runs {
for _ in 0..run.length {
if let Some(cell) = pixel_array.get_mut(shift_x + x, shift_y + y) {
let xs = start_x + x;
let ys = start_y + y;
let mat = &self.material_list[run.data.material as usize];
match mat.cell_type {
CellType::None => {
cell.0 = ptr::null_mut();
}
CellType::Liquid => {
let liquid = Box::leak(Box::new(unsafe {
LiquidCell::create(mat, self.cell_vtables.liquid(), self.world_ptr)
}));
liquid.x = xs;
liquid.y = ys;
cell.0 = (liquid as *mut LiquidCell).cast();
}
CellType::Gas => {
let gas = Box::leak(Box::new(unsafe {
GasCell::create(mat, self.cell_vtables.gas(), self.world_ptr)
}));
gas.x = xs;
gas.y = ys;
cell.0 = (gas as *mut GasCell).cast();
}
CellType::Solid => {}
CellType::Fire => {
let fire = Box::leak(Box::new(unsafe {
FireCell::create(mat, self.cell_vtables.fire(), self.world_ptr)
}));
fire.x = xs;
fire.y = ys;
cell.0 = (fire as *mut FireCell).cast();
}
}
}
if x == CHUNK_SIZE as isize {
x = 0;
y += 1;
} else {
x += 1;
}
}
}
Ok(())
}
}

View file

@ -30,7 +30,6 @@ use world::WorldManager;
use crate::lobby_code::LobbyKind;
use crate::mod_manager::{ModmanagerSettings, get_mods};
use crate::net::world::world_model::ChunkData;
use crate::net::world::world_model::chunk::{Pixel, PixelFlags};
use crate::player_cosmetics::{PlayerPngDesc, create_player_png, get_player_skin};
use crate::steam_helper::LobbyExtraData;
use crate::{
@ -38,7 +37,7 @@ use crate::{
bookkeeping::save_state::{SaveState, SaveStateEntry},
};
use shared::des::ProxyToDes;
use shared::world_sync::{ChunkCoord, ProxyToWorldSync};
use shared::world_sync::{ChunkCoord, PixelFlags, ProxyToWorldSync, RawPixel};
use tangled::Reliability;
use tracing::{error, info, warn};
mod audio;
@ -1486,7 +1485,7 @@ pub struct ExplosionData {
ray: u64,
hole: bool,
liquid: bool,
mat: Pixel,
mat: RawPixel,
prob: u8,
}
impl ExplosionData {
@ -1510,7 +1509,7 @@ impl ExplosionData {
ray,
hole,
liquid,
mat: Pixel {
mat: RawPixel {
flags: PixelFlags::Normal,
material: mat,
},

View file

@ -12,10 +12,7 @@ use std::time::Duration;
use std::{cmp, mem, thread};
use tracing::{debug, info, warn};
use wide::f32x8;
use world_model::{
ChunkData, ChunkDelta, WorldModel,
chunk::{Chunk, Pixel},
};
use world_model::{ChunkData, ChunkDelta, WorldModel, chunk::Chunk};
use crate::bookkeeping::save_state::{SaveState, SaveStateEntry};
@ -1185,7 +1182,7 @@ impl WorldManager {
let start = x - radius;
let end = x + radius;
let air_pixel = Pixel {
let air_pixel = RawPixel {
flags: PixelFlags::Normal,
material: 0,
};
@ -1270,7 +1267,7 @@ impl WorldManager {
let dm2 = ((dmx.unsigned_abs() as u64 * dmx.unsigned_abs() as u64
+ dmy.unsigned_abs() as u64 * dmy.unsigned_abs() as u64) as f64)
.recip();
let air_pixel = Pixel {
let air_pixel = RawPixel {
flags: PixelFlags::Normal,
material: 0,
};
@ -1421,7 +1418,7 @@ impl WorldManager {
(y - r).div_euclid(CHUNK_SIZE as i32),
(y + r).div_euclid(CHUNK_SIZE as i32),
);
let air_pixel = Pixel {
let air_pixel = RawPixel {
flags: PixelFlags::Normal,
material: mat.unwrap_or(0),
};
@ -1727,7 +1724,7 @@ impl WorldManager {
list: Vec<(u64, u64, Option<ChunkCoord>)>,
hole: bool,
liquid: bool,
mat: Pixel,
mat: RawPixel,
prob: u8,
r: u64,
) -> Vec<ExRet> {
@ -1744,7 +1741,7 @@ impl WorldManager {
(y - r as i32).div_euclid(CHUNK_SIZE as i32),
(y + r as i32).div_euclid(CHUNK_SIZE as i32),
);
let air_pixel = Pixel {
let air_pixel = RawPixel {
flags: PixelFlags::Normal,
material: 0,
};
@ -2050,7 +2047,7 @@ impl WorldManager {
grouped.entry(key).or_default().push((a, b));
}
let data: Vec<(usize, Vec<(usize, u64)>)> = grouped.into_iter().collect();
let air_pixel = Pixel {
let air_pixel = RawPixel {
flags: PixelFlags::Normal,
material: 0,
};
@ -3075,12 +3072,13 @@ fn test_explosion_img_big_many() {
}*/
#[cfg(test)]
use crate::net::LiquidType;
use crate::net::world::world_model::chunk::PixelFlags;
#[cfg(test)]
use rand::seq::SliceRandom;
#[cfg(test)]
use serial_test::serial;
use shared::world_sync::{CHUNK_SIZE, ChunkCoord, NoitaWorldUpdate, WorldSyncToProxy};
use shared::world_sync::{
CHUNK_SIZE, ChunkCoord, NoitaWorldUpdate, PixelFlags, RawPixel, WorldSyncToProxy,
};
#[cfg(test)]
#[test]
#[serial]
@ -3398,4 +3396,4 @@ impl WorldManager {
}
}
}
}
}

View file

@ -2,10 +2,12 @@ use std::num::NonZeroU16;
use std::sync::Arc;
use bitcode::{Decode, Encode};
use chunk::{Chunk, CompactPixel, Pixel, PixelFlags};
use chunk::Chunk;
use encoding::PixelRunner;
use rustc_hash::{FxHashMap, FxHashSet};
use shared::world_sync::{CHUNK_SIZE, ChunkCoord, NoitaWorldUpdate, PixelRun};
use shared::world_sync::{
CHUNK_SIZE, ChunkCoord, CompactPixel, NoitaWorldUpdate, PixelRun, RawPixel,
};
use tracing::info;
pub(crate) mod chunk;
pub mod encoding;
@ -53,8 +55,8 @@ impl ChunkData {
let mut runner = PixelRunner::new();
for _ in 0..CHUNK_SIZE * CHUNK_SIZE {
runner.put_pixel(
Pixel {
flags: PixelFlags::Normal,
RawPixel {
flags: shared::world_sync::PixelFlags::Normal,
material: mat,
}
.to_compact(),
@ -131,7 +133,7 @@ impl WorldModel {
update: NoitaWorldUpdate,
changed: &mut FxHashSet<ChunkCoord>,
) {
fn set_pixel(pixel: Pixel, chunk: &mut Chunk, offset: usize) -> bool {
fn set_pixel(pixel: RawPixel, chunk: &mut Chunk, offset: usize) -> bool {
let current = chunk.pixel(offset);
if current != pixel {
chunk.set_pixel(offset, pixel);
@ -149,11 +151,6 @@ impl WorldModel {
let mut chunk_coord = update.coord;
let mut chunk = self.chunks.entry(update.coord).or_default();
for run in update.runs {
let flags = if run.data.flags > 0 {
PixelFlags::Fluid
} else {
PixelFlags::Normal
};
for _ in 0..run.length {
let xs = start_x + x;
let ys = start_y + y;
@ -163,9 +160,9 @@ impl WorldModel {
chunk = self.chunks.entry(chunk_coord).or_default();
}
if set_pixel(
Pixel {
RawPixel {
material: run.data.material,
flags,
flags: run.data.flags,
},
chunk,
offset,
@ -193,7 +190,7 @@ impl WorldModel {
let mut runner = PixelRunner::new();
for j in 0..CHUNK_SIZE {
for i in 0..CHUNK_SIZE {
runner.put_pixel(chunk.pixel(i + j * CHUNK_SIZE).to_raw())
runner.put_pixel(chunk.pixel(i + j * CHUNK_SIZE))
}
}
updates.push(NoitaWorldUpdate {

View file

@ -1,99 +1,10 @@
use std::num::NonZeroU16;
use super::{ChunkData, encoding::PixelRunner};
use bitcode::{Decode, Encode};
use crossbeam::atomic::AtomicCell;
use shared::world_sync::{CHUNK_SIZE, RawPixel};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Encode, Decode)]
pub enum PixelFlags {
/// Actual material isn't known yet.
#[default]
Unknown,
Normal,
Fluid,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Encode, Decode)]
pub struct Pixel {
pub flags: PixelFlags,
pub material: u16,
}
impl Pixel {
pub fn to_raw(self) -> RawPixel {
RawPixel {
material: if self.flags != PixelFlags::Unknown {
self.material
} else {
u16::MAX
},
flags: if self.flags == PixelFlags::Normal {
0
} else {
1
},
}
}
pub fn to_compact(self) -> CompactPixel {
let flag_bit = if self.flags == PixelFlags::Normal {
0
} else {
1
};
let material = (self.material + 1) & 2047; // 11 bits for material
let raw = if self.flags == PixelFlags::Unknown {
CompactPixel::UNKNOWN_RAW
} else {
(material << 1) | flag_bit
};
CompactPixel(NonZeroU16::new(raw).unwrap())
}
fn from_compact(compact: CompactPixel) -> Self {
let raw = u16::from(compact.0);
let material = (raw >> 1) - 1;
let flags = if raw & 1 == 1 {
PixelFlags::Fluid
} else {
PixelFlags::Normal
};
if raw == CompactPixel::UNKNOWN_RAW {
Pixel {
flags: PixelFlags::Unknown,
material: 0,
}
} else {
Pixel { flags, material }
}
}
}
/// An entire pixel packed into 12 bits.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[repr(transparent)]
pub struct CompactPixel(pub NonZeroU16);
impl CompactPixel {
const UNKNOWN_RAW: u16 = 4095;
fn from_raw(val: u16) -> Self {
CompactPixel(NonZeroU16::new(val).unwrap())
}
fn raw(self) -> u16 {
u16::from(self.0)
}
}
impl Default for CompactPixel {
fn default() -> Self {
Self(NonZeroU16::new(CompactPixel::UNKNOWN_RAW).unwrap())
}
}
use shared::world_sync::{CHUNK_SIZE, CompactPixel, RawPixel};
pub struct Chunk {
pixels: [u16; CHUNK_SQUARE],
changed: Changed<bool, CHUNK_SQUARE>,
any_changed: bool,
crc: AtomicCell<Option<u64>>,
}
struct Changed<T: Default, const N: usize>([T; N]);
@ -106,8 +17,6 @@ impl Changed<u128, CHUNK_SIZE> {
self.0[n / CHUNK_SIZE] |= 1 << (n % CHUNK_SIZE)
}
}
#[cfg(test)]
const _: () = assert!(u128::BITS as usize == CHUNK_SIZE);
const CHUNK_SQUARE: usize = CHUNK_SIZE * CHUNK_SIZE;
impl Changed<bool, CHUNK_SQUARE> {
fn get(&self, n: usize) -> bool {
@ -147,22 +56,21 @@ impl Default for Chunk {
pixels: [4095; CHUNK_SQUARE],
changed: Changed([false; CHUNK_SQUARE]),
any_changed: false,
crc: None.into(),
}
}
}
/// Chunk of pixels. Stores pixels and tracks if they were changed.
impl Chunk {
pub fn pixel(&self, offset: usize) -> Pixel {
Pixel::from_compact(CompactPixel::from_raw(self.pixels[offset]))
pub fn pixel(&self, offset: usize) -> RawPixel {
RawPixel::from_compact(CompactPixel::from_raw(self.pixels[offset]))
}
pub fn compact_pixel(&self, offset: usize) -> CompactPixel {
CompactPixel::from_raw(self.pixels[offset])
}
pub fn set_pixel(&mut self, offset: usize, pixel: Pixel) {
pub fn set_pixel(&mut self, offset: usize, pixel: RawPixel) {
let px = pixel.to_compact().raw();
if self.pixels[offset] != px {
self.pixels[offset] = px;
@ -184,7 +92,6 @@ impl Chunk {
pub fn mark_changed(&mut self, offset: usize) {
self.changed.set(offset);
self.any_changed = true;
self.crc.store(None);
}
pub fn clear_changed(&mut self) {

View file

@ -1,11 +1,15 @@
use crate::noita::types;
use crate::noita::types::{
CellVTable, CellVTables, FireCellVTable, GasCellVTable, LiquidCellVTable, NoneCellVTable,
SolidCellVTable,
};
use eyre::ContextCompat;
use object::{Object, ObjectSection};
use std::arch::asm;
use std::ffi::c_void;
use std::fs::File;
use std::io::Read;
pub fn get_functions() -> eyre::Result<(&'static types::CellVTable, *mut types::GameGlobal)> {
pub fn get_functions() -> eyre::Result<(types::CellVTables, *mut types::GameGlobal)> {
let exe = std::env::current_exe()?;
let mut file = File::open(exe)?;
let mut vec = Vec::with_capacity(15460864);
@ -20,29 +24,25 @@ pub fn get_functions() -> eyre::Result<(&'static types::CellVTable, *mut types::
let ptr = unsafe { start.add(game_global) };
let game_global_ptr = get_rela_call(ptr, offset);
let game_global_ptr = get_global(game_global_ptr);
let rdata = obj.section_by_name(".rdata").wrap_err("obj err")?;
let data = rdata.data()?;
let cellvtable: &[u8] = &[
0x20, 0xaf, 0x70, 0x00, 0xa0, 0x01, 0x5b, 0x00, 0x50, 0xb0, 0x70, 0x00, 0x60, 0xb0, 0x70,
0x00, 0xc0, 0x01, 0x5b, 0x00, 0xd0, 0x01, 0x5b, 0x00, 0x90, 0xd0, 0x70, 0x00, 0xe0, 0x01,
0x5b, 0x00, 0x00, 0x02, 0x5b, 0x00, 0xf0, 0x01, 0x5b, 0x00, 0x70, 0xb0, 0x70, 0x00, 0xb0,
0xb0, 0x70, 0x00, 0xd0, 0xc0, 0x4a, 0x00, 0xb0, 0xd0, 0x70, 0x00, 0x60, 0xbf, 0x4a, 0x00,
0xa0, 0xd1, 0x70, 0x00, 0xe0, 0xd1, 0x70, 0x00, 0x80, 0xd1, 0x70, 0x00, 0x40, 0xcb, 0x70,
0x00, 0x80, 0xcd, 0x70, 0x00, 0xd0, 0xcd, 0x70, 0x00, 0xe0, 0xc6, 0x70, 0x00, 0xb0, 0x01,
0x5b, 0x00, 0x90, 0xbf, 0x4a, 0x00, 0xa0, 0xbf, 0x4a, 0x00, 0x10, 0xb1, 0x70, 0x00, 0x20,
0xb1, 0x70, 0x00, 0x60, 0xb1, 0x70, 0x00, 0xb0, 0xf5, 0x70, 0x00, 0xd0, 0xf5, 0x70, 0x00,
0xf0, 0xcd, 0x70, 0x00, 0x50, 0xf7, 0x70, 0x00, 0xe0, 0xc0, 0x4a, 0x00, 0xf0, 0xf7, 0x70,
0x00, 0x20, 0xc0, 0x4a, 0x00, 0x60, 0xf1, 0x70, 0x00, 0xf0, 0xea, 0x70, 0x00, 0x90, 0xef,
0x70, 0x00, 0x60, 0xf3, 0x70, 0x00, 0x50, 0xaf, 0x70, 0x00, 0xd0, 0xb1, 0x70,
0x00,
//TODO i should search for a function in the vtable then find the vtable prob
];
let start = rdata.address() as *const c_void;
let cellvtable_ptr = unsafe {
(start.add(find_pattern(data, cellvtable)?) as *const types::CellVTable).as_ref()
}
.wrap_err("cell data err")?;
Ok((cellvtable_ptr, game_global_ptr))
let cellvtables = unsafe {
let none = CellVTable {
none: (0xff2040 as *const NoneCellVTable).as_ref().unwrap(),
};
let liquid = CellVTable {
liquid: (0x100bb90 as *const LiquidCellVTable).as_ref().unwrap(),
};
let gas = CellVTable {
gas: (0x1007bcc as *const GasCellVTable).as_ref().unwrap(),
};
let solid = CellVTable {
solid: (0xff8a6c as *const SolidCellVTable).as_ref().unwrap(),
};
let fire = CellVTable {
fire: (0x10096e0 as *const FireCellVTable).as_ref().unwrap(),
};
CellVTables([none, liquid, gas, solid, fire])
};
Ok((cellvtables, game_global_ptr))
}
fn get_global(global: *const c_void) -> *mut types::GameGlobal {
unsafe {
@ -56,11 +56,11 @@ fn get_global(global: *const c_void) -> *mut types::GameGlobal {
ptr
}
}
fn find_pattern(data: &[u8], pattern: &[u8]) -> eyre::Result<usize> {
/*fn find_pattern(data: &[u8], pattern: &[u8]) -> eyre::Result<usize> {
data.windows(pattern.len())
.position(|window| window == pattern)
.wrap_err("match err")
}
}*/
fn find_pattern_global(data: &[u8], pattern: &[u8], other: &[u8]) -> eyre::Result<(usize, isize)> {
let r = data
.windows(pattern.len() + 4 + other.len())

View file

@ -2,6 +2,7 @@
use std::ffi::c_void;
use shared::world_sync::{PixelFlags, RawPixel};
use std::fmt::{Debug, Display, Formatter};
#[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
@ -28,20 +29,49 @@ unsafe impl Sync for ChunkPtr {}
unsafe impl Send for ChunkPtr {}
impl ChunkPtr {
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &CellPtr> {
unsafe { std::slice::from_raw_parts(self.0, 512 * 512) }.iter()
}
#[inline]
pub fn get(&self, x: isize, y: isize) -> Option<&Cell> {
let index = (y << 9) | x;
unsafe { self.0.offset(index).as_ref().and_then(|c| c.0.as_ref()) }
}
#[inline]
pub fn get_mut(&mut self, x: isize, y: isize) -> Option<&mut CellPtr> {
unsafe { self.get_mut_raw(x, y).as_mut() }
}
#[inline]
pub fn get_mut_raw(&mut self, x: isize, y: isize) -> *mut CellPtr {
let index = (y << 9) | x;
unsafe { self.0.offset(index) }
}
#[inline]
pub fn get_raw_pixel(&self, x: isize, y: isize) -> RawPixel {
if let Some(cell) = self.get(x, y) {
if cell.material.cell_type == CellType::Liquid {
RawPixel {
material: cell.material.material_type as u16,
flags: if cell.get_liquid().is_static == cell.material.liquid_static {
PixelFlags::Normal
} else {
PixelFlags::Abnormal
},
}
} else {
RawPixel {
material: cell.material.material_type as u16,
flags: PixelFlags::Normal,
}
}
} else {
RawPixel {
material: 0,
flags: PixelFlags::Normal,
}
}
}
}
#[repr(C)]
@ -61,16 +91,20 @@ pub struct ChunkArrayPtr(pub *mut ChunkPtrPtr);
unsafe impl Sync for ChunkArrayPtr {}
unsafe impl Send for ChunkArrayPtr {}
impl ChunkArrayPtr {
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &ChunkPtrPtr> {
unsafe { std::slice::from_raw_parts(self.0, 512 * 512) }.iter()
}
#[inline]
pub fn slice(&self) -> &'static [ChunkPtrPtr] {
unsafe { std::slice::from_raw_parts(self.0, 512 * 512) }
}
#[inline]
pub fn get(&self, x: isize, y: isize) -> Option<&ChunkPtr> {
let index = (((y - 256) & 511) << 9) | ((x - 256) & 511);
unsafe { self.0.offset(index).as_ref().and_then(|c| c.0.as_ref()) }
}
#[inline]
pub fn get_mut(&mut self, x: isize, y: isize) -> Option<&mut ChunkPtr> {
let index = (((y - 256) & 511) << 9) | ((x - 256) & 511);
unsafe { self.0.offset(index).as_mut().and_then(|c| c.0.as_mut()) }
@ -118,14 +152,15 @@ struct AABB {
#[repr(C)]
#[derive(Debug)]
//ptr is 0x17f83e30, seems not constant
pub struct GridWorldThreadedVTable {
//TODO find some data maybe
}
#[repr(C)]
#[derive(Debug)]
struct GridWorldThreaded {
grid_world_threaded_vtable: &'static GridWorldThreadedVTable,
pub struct GridWorldThreaded {
pub grid_world_threaded_vtable: &'static GridWorldThreadedVTable,
unknown: [isize; 287],
update_region: AABB,
}
@ -139,7 +174,7 @@ pub struct GridWorld {
pub world_update_count: isize,
pub chunk_map: ChunkMap,
unknown2: [isize; 41],
m_thread_impl: *mut GridWorldThreaded,
pub m_thread_impl: *mut GridWorldThreaded,
}
#[repr(C)]
@ -462,19 +497,40 @@ impl Default for CellData {
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CellVTables(pub [CellVTable; 5]);
impl CellVTables {
pub fn none(&self) -> &'static NoneCellVTable {
unsafe { self.0[0].none }
}
pub fn liquid(&self) -> &'static LiquidCellVTable {
unsafe { self.0[1].liquid }
}
pub fn gas(&self) -> &'static GasCellVTable {
unsafe { self.0[2].gas }
}
pub fn solid(&self) -> &'static SolidCellVTable {
unsafe { self.0[3].solid }
}
pub fn fire(&self) -> &'static FireCellVTable {
unsafe { self.0[4].fire }
}
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union CellVTable {
//ptr is 0xff2040
none: NoneCellVTable,
pub none: &'static NoneCellVTable,
//ptr is 0x100bb90
liquid: LiquidCellVTable,
gas: GasCellVTable,
pub liquid: &'static LiquidCellVTable,
//ptr is 0x1007bcc
pub gas: &'static GasCellVTable,
//ptr is 0xff8a6c
solid: SolidCellVTable,
pub solid: &'static SolidCellVTable,
//ptr is 0x10096e0
fire: FireCellVTable,
pub fire: &'static FireCellVTable,
}
impl Debug for CellVTable {
@ -645,7 +701,7 @@ impl FireCell {
///# Safety
pub unsafe fn create(
mat: &'static CellData,
vtable: &'static CellVTable,
vtable: &'static FireCellVTable,
world: *mut GridWorld,
) -> Self {
let lifetime = if let Some(world) = unsafe { world.as_mut() } {
@ -655,7 +711,12 @@ impl FireCell {
} else {
-1
};
let mut cell = Cell::create(mat, vtable);
let mut cell = Cell::create(mat, unsafe {
(vtable as *const FireCellVTable)
.cast::<CellVTable>()
.as_ref()
.unwrap()
});
cell.is_burning = true;
Self {
cell,
@ -689,7 +750,7 @@ impl GasCell {
///# Safety
pub unsafe fn create(
mat: &'static CellData,
vtable: &'static CellVTable,
vtable: &'static GasCellVTable,
world: *mut GridWorld,
) -> Self {
let (bool, lifetime) = if let Some(world) = unsafe { world.as_mut() } {
@ -703,7 +764,12 @@ impl GasCell {
} else {
(false, -1)
};
let mut cell = Cell::create(mat, vtable);
let mut cell = Cell::create(mat, unsafe {
(vtable as *const GasCellVTable)
.cast::<CellVTable>()
.as_ref()
.unwrap()
});
cell.is_burning = true;
Self {
cell,
@ -746,7 +812,7 @@ impl LiquidCell {
/// # Safety
pub unsafe fn create(
mat: &'static CellData,
vtable: &'static CellVTable,
vtable: &'static LiquidCellVTable,
world: *mut GridWorld,
) -> Self {
let lifetime = if mat.lifetime > 0
@ -760,7 +826,12 @@ impl LiquidCell {
-1
};
Self {
cell: Cell::create(mat, vtable),
cell: Cell::create(mat, unsafe {
(vtable as *const LiquidCellVTable)
.cast::<CellVTable>()
.as_ref()
.unwrap()
}),
x: 0,
y: 0,
unknown1: 3,
@ -806,7 +877,7 @@ pub struct CellFactory {
unknown1: [isize; 5],
pub cell_data_len: usize,
pub cell_data_ptr: *const CellData,
//likely more data
//TODO likely more data
}
#[repr(C)]
@ -831,12 +902,12 @@ pub struct GameGlobal {
pub struct Entity {
_unknown0: [u8; 8],
pub filename_index: u32,
// More stuff, not that relevant currently.
//TODO More stuff, not that relevant currently.
}
#[repr(C)]
pub struct EntityManager {
_fld: c_void,
// Unknown
//TODO Unknown
}
#[repr(C)]
pub struct ThiscallFn(c_void);

View file

@ -5,7 +5,7 @@ use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIter
pub struct ParticleWorldState {
pub world_ptr: *mut types::GridWorld,
pub material_list: &'static [types::CellData],
pub cell_vtable: &'static types::CellVTable,
pub cell_vtables: types::CellVTables,
}
unsafe impl Sync for ParticleWorldState {}
unsafe impl Send for ParticleWorldState {}
@ -15,12 +15,6 @@ impl ParticleWorldState {
let shift_y = (y * CHUNK_SIZE as isize).rem_euclid(512);
(shift_x, shift_y)
}
pub fn get_cell_material_id(&self, cell: &mut types::Cell) -> u16 {
let offset = unsafe {
(cell.material as *const types::CellData).offset_from(self.material_list.as_ptr())
};
offset as u16
}
pub fn exists<const SCALE: isize>(&self, cx: isize, cy: isize) -> bool {
let Some(world) = (unsafe { self.world_ptr.as_mut() }) else {
return false;
@ -82,7 +76,7 @@ impl ParticleWorldState {
Ok(())
}
pub fn new() -> eyre::Result<Self> {
let (cell_vtable, global_ptr) = crate::noita::init_data::get_functions()?;
let (cell_vtables, global_ptr) = crate::noita::init_data::get_functions()?;
let global = unsafe { global_ptr.as_mut() }.wrap_err("no global?")?;
let cell_factory =
unsafe { global.m_cell_factory.as_mut() }.wrap_err("no cell factory?")?;
@ -93,7 +87,7 @@ impl ParticleWorldState {
Ok(ParticleWorldState {
world_ptr,
material_list,
cell_vtable,
cell_vtables,
})
}
}

View file

@ -1,4 +1,5 @@
use bitcode::{Decode, Encode};
use std::num::NonZeroU16;
/// Stores a run of pixels.
/// Not specific to Noita side - length is an actual length
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
@ -18,10 +19,74 @@ pub struct NoitaWorldUpdate {
pub runs: Vec<PixelRun<RawPixel>>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Encode, Decode)]
pub enum PixelFlags {
/// Actual material isn't known yet.
#[default]
Unknown,
Normal,
Abnormal,
}
#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy)]
pub struct RawPixel {
pub material: u16,
pub flags: u8,
pub flags: PixelFlags,
}
impl RawPixel {
pub fn to_compact(self) -> CompactPixel {
let flag_bit = if self.flags == PixelFlags::Normal {
0
} else {
1
};
let material = (self.material + 1) & 2047; // 11 bits for material
let raw = if self.flags == PixelFlags::Unknown {
CompactPixel::UNKNOWN_RAW
} else {
(material << 1) | flag_bit
};
CompactPixel(NonZeroU16::new(raw).unwrap())
}
pub fn from_compact(compact: CompactPixel) -> Self {
let raw = u16::from(compact.0);
let material = (raw >> 1) - 1;
let flags = if raw & 1 == 1 {
PixelFlags::Abnormal
} else {
PixelFlags::Normal
};
if raw == CompactPixel::UNKNOWN_RAW {
RawPixel {
flags: PixelFlags::Unknown,
material: 0,
}
} else {
RawPixel { flags, material }
}
}
}
/// An entire pixel packed into 12 bits.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[repr(transparent)]
pub struct CompactPixel(pub NonZeroU16);
impl CompactPixel {
const UNKNOWN_RAW: u16 = 4095;
pub fn from_raw(val: u16) -> Self {
CompactPixel(NonZeroU16::new(val).unwrap())
}
pub fn raw(self) -> u16 {
u16::from(self.0)
}
}
impl Default for CompactPixel {
fn default() -> Self {
Self(NonZeroU16::new(CompactPixel::UNKNOWN_RAW).unwrap())
}
}
#[derive(Debug, Encode, Decode, Clone)]