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

1
ewext/Cargo.lock generated
View file

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

View file

@ -25,6 +25,7 @@ libloading = "0.8.6"
rand = "0.9.0" rand = "0.9.0"
rustc-hash = "2.0.0" rustc-hash = "2.0.0"
bimap = "0.6.3" bimap = "0.6.3"
rayon = "1.10.0"
[features] [features]
#enables cross-compilation on older systems (for example, when compiling on ubuntu 20.04) #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::WorldSync;
use crate::modules::{Module, ModuleCtx}; 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 noita_api::noita::world::ParticleWorldState;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use shared::NoitaOutbound; 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 { impl Module for WorldSync {
fn on_world_update(&mut self, ctx: &mut ModuleCtx) -> eyre::Result<()> { fn on_world_update(&mut self, ctx: &mut ModuleCtx) -> eyre::Result<()> {
let mut update = NoitaWorldUpdate { let update = NoitaWorldUpdate {
coord: ChunkCoord(0, 0), 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 self.particle_world_state
.assume_init_ref() .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])); let msg = NoitaOutbound::WorldSyncToProxy(WorldSyncToProxy::Updates(vec![update]));
ctx.net.send(&msg)?; ctx.net.send(&msg)?;
Ok(()) Ok(())
@ -24,22 +49,25 @@ impl WorldSync {
match msg { match msg {
ProxyToWorldSync::Updates(updates) => { ProxyToWorldSync::Updates(updates) => {
for chunk in updates { for chunk in updates {
let time = std::time::Instant::now();
unsafe { unsafe {
self.particle_world_state self.particle_world_state
.assume_init_ref() .assume_init_ref()
.decode_world(chunk)? .decode_world(chunk)?
} }
noita_api::print!("de {}", time.elapsed().as_micros());
} }
} }
} }
Ok(()) Ok(())
} }
} }
pub const SCALE: isize = (512 / CHUNK_SIZE as isize).ilog2() as isize;
trait WorldData { trait WorldData {
unsafe fn encode_world( unsafe fn encode_world(
&self, &self,
coord: ChunkCoord, coord: ChunkCoord,
chunk: &mut NoitaWorldUpdate, chunk: &mut [RawPixel; CHUNK_SIZE * CHUNK_SIZE],
) -> eyre::Result<()>; ) -> eyre::Result<()>;
unsafe fn decode_world(&self, chunk: NoitaWorldUpdate) -> eyre::Result<()>; unsafe fn decode_world(&self, chunk: NoitaWorldUpdate) -> eyre::Result<()>;
} }
@ -47,16 +75,87 @@ impl WorldData for ParticleWorldState {
unsafe fn encode_world( unsafe fn encode_world(
&self, &self,
coord: ChunkCoord, coord: ChunkCoord,
chunk: &mut NoitaWorldUpdate, chunk: &mut [RawPixel; CHUNK_SIZE * CHUNK_SIZE],
) -> eyre::Result<()> { ) -> eyre::Result<()> {
chunk.coord = coord; let (cx, cy) = (coord.0 as isize, coord.1 as isize);
let runs = &mut chunk.runs; let Some(pixel_array) = unsafe { self.world_ptr.as_mut() }
runs.clear(); .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(()) Ok(())
} }
unsafe fn decode_world(&self, chunk: NoitaWorldUpdate) -> eyre::Result<()> { unsafe fn decode_world(&self, chunk: NoitaWorldUpdate) -> eyre::Result<()> {
std::hint::black_box(chunk); let chunk_coord = chunk.coord;
//TODO 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(()) Ok(())
} }
} }

View file

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

View file

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

View file

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

View file

@ -1,99 +1,10 @@
use std::num::NonZeroU16;
use super::{ChunkData, encoding::PixelRunner}; use super::{ChunkData, encoding::PixelRunner};
use bitcode::{Decode, Encode}; use shared::world_sync::{CHUNK_SIZE, CompactPixel, RawPixel};
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())
}
}
pub struct Chunk { pub struct Chunk {
pixels: [u16; CHUNK_SQUARE], pixels: [u16; CHUNK_SQUARE],
changed: Changed<bool, CHUNK_SQUARE>, changed: Changed<bool, CHUNK_SQUARE>,
any_changed: bool, any_changed: bool,
crc: AtomicCell<Option<u64>>,
} }
struct Changed<T: Default, const N: usize>([T; N]); 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) 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; const CHUNK_SQUARE: usize = CHUNK_SIZE * CHUNK_SIZE;
impl Changed<bool, CHUNK_SQUARE> { impl Changed<bool, CHUNK_SQUARE> {
fn get(&self, n: usize) -> bool { fn get(&self, n: usize) -> bool {
@ -147,22 +56,21 @@ impl Default for Chunk {
pixels: [4095; CHUNK_SQUARE], pixels: [4095; CHUNK_SQUARE],
changed: Changed([false; CHUNK_SQUARE]), changed: Changed([false; CHUNK_SQUARE]),
any_changed: false, any_changed: false,
crc: None.into(),
} }
} }
} }
/// Chunk of pixels. Stores pixels and tracks if they were changed. /// Chunk of pixels. Stores pixels and tracks if they were changed.
impl Chunk { impl Chunk {
pub fn pixel(&self, offset: usize) -> Pixel { pub fn pixel(&self, offset: usize) -> RawPixel {
Pixel::from_compact(CompactPixel::from_raw(self.pixels[offset])) RawPixel::from_compact(CompactPixel::from_raw(self.pixels[offset]))
} }
pub fn compact_pixel(&self, offset: usize) -> CompactPixel { pub fn compact_pixel(&self, offset: usize) -> CompactPixel {
CompactPixel::from_raw(self.pixels[offset]) 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(); let px = pixel.to_compact().raw();
if self.pixels[offset] != px { if self.pixels[offset] != px {
self.pixels[offset] = px; self.pixels[offset] = px;
@ -184,7 +92,6 @@ impl Chunk {
pub fn mark_changed(&mut self, offset: usize) { pub fn mark_changed(&mut self, offset: usize) {
self.changed.set(offset); self.changed.set(offset);
self.any_changed = true; self.any_changed = true;
self.crc.store(None);
} }
pub fn clear_changed(&mut self) { pub fn clear_changed(&mut self) {

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
use bitcode::{Decode, Encode}; use bitcode::{Decode, Encode};
use std::num::NonZeroU16;
/// Stores a run of pixels. /// Stores a run of pixels.
/// Not specific to Noita side - length is an actual length /// Not specific to Noita side - length is an actual length
#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
@ -18,10 +19,74 @@ pub struct NoitaWorldUpdate {
pub runs: Vec<PixelRun<RawPixel>>, 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)] #[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy)]
pub struct RawPixel { pub struct RawPixel {
pub material: u16, 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)] #[derive(Debug, Encode, Decode, Clone)]