mirror of
https://github.com/IntQuant/noita_entangled_worlds.git
synced 2025-10-19 07:03:16 +00:00
get all vtables, more ewext world data setup
This commit is contained in:
parent
29abf3f26f
commit
3d530ca648
12 changed files with 327 additions and 195 deletions
|
@ -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
1
ewext/Cargo.lock
generated
|
@ -142,6 +142,7 @@ dependencies = [
|
|||
"libloading",
|
||||
"noita_api",
|
||||
"rand",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
"shared",
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue