add mem reader

This commit is contained in:
bgkillas 2025-11-27 15:14:42 -05:00
parent f1e4166173
commit f2350690b0
6 changed files with 2805 additions and 13 deletions

View file

@ -36,7 +36,7 @@ rayon = "1.11.0"
windows = { version = "0.62.2", features = ["Win32", "Win32_System", "Win32_System_Memory"] , optional=true}
[features]
default=["debug"]
default=[]
debug=["dep:windows"]
#enables cross-compilation on older systems (for example, when compiling on ubuntu 20.04)
#due to unresolved bug in rust toolchain

View file

@ -4,22 +4,23 @@ use std::{
};
use windows::Win32::System::Memory::{PAGE_PROTECTION_FLAGS, PAGE_READWRITE, VirtualProtect};
unsafe extern "cdecl" fn operator_new(size: usize) -> *mut c_void {
let buffer =
unsafe { alloc::alloc(Layout::from_size_align_unchecked(size + 4, 4)).cast::<usize>() };
unsafe { buffer.write(size) };
unsafe { buffer.offset(1).cast() }
}
unsafe extern "cdecl" fn operator_delete(pointer: *mut c_void) {
let ptr = unsafe { pointer.cast::<usize>().offset(-1) };
let size = unsafe { ptr.read() };
unsafe {
alloc::dealloc(ptr.cast(), Layout::from_size_align_unchecked(size + 4, 4));
let buffer = alloc::alloc(Layout::from_size_align_unchecked(size + 16, 4)).cast::<usize>();
buffer.write(buffer.offset(4) as usize);
buffer.offset(1).write(1);
buffer.offset(2).write(usize::MAX - 1);
buffer.offset(3).write(size);
buffer.offset(4).cast()
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn dummy() {}
unsafe extern "cdecl" fn operator_delete(pointer: *mut c_void) {
unsafe {
let size_ptr = pointer.cast::<usize>().offset(-1);
let size = size_ptr.read();
alloc::dealloc(pointer.offset(-4).cast(), Layout::from_size_align_unchecked(size + 16, 4));
}
}
unsafe fn make_writable(addr: *mut *const usize) {
let mut old = PAGE_PROTECTION_FLAGS(0);

7
extra/mem_reader/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "mem_reader"
version = "0.1.0"

View file

@ -0,0 +1,7 @@
[package]
name = "mem_reader"
version = "0.1.0"
edition = "2024"
[dependencies]
#noita_api = {path = "../../noita_api"}

View file

@ -0,0 +1,277 @@
use std::collections::HashMap;
use std::env::args;
use std::fs::File;
use std::os::unix::fs::FileExt;
fn get_size(mem: &File, addr: u32) -> Option<u32> {
if addr < 16
|| read_byte(mem, addr - 16)? != addr
|| read_byte(mem, addr - 12)? != 1
|| read_byte(mem, addr - 8)? != u32::MAX - 1
{
return None;
}
read_byte(mem, addr - 4)
}
#[allow(unused)]
fn read_unsized(mem: &File, addr: u32) -> Option<Vec<u32>> {
let size = get_size(mem, addr)?;
read(mem, addr, size as usize)
}
fn read_byte(mem: &File, addr: u32) -> Option<u32> {
let mut buf = [0; 4];
mem.read_exact_at(&mut buf, addr as u64).ok()?;
Some(u32::from_le_bytes(buf))
}
#[allow(unused)]
fn read(mem: &File, addr: u32, size: usize) -> Option<Vec<u32>> {
let size = (size + 0x11) & !0x11;
let mut buf = vec![0; size];
mem.read_exact_at(&mut buf, addr as u64).ok()?;
Some(
buf.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect(),
)
}
fn get_map() -> HashMap<u32, (String, usize)> {
let vftables = include_str!("../vftables.txt");
let mut name_map = HashMap::new();
let mut size_map = HashMap::new();
for line in vftables.lines() {
let mut split = line.split(' ');
let Some(name) = split.next() else {
continue;
};
let name = name.to_string();
let Some(addr) = split.next() else {
continue;
};
let addr = u32::from_str_radix(addr, 16).unwrap();
let Some(size) = split.next() else {
continue;
};
let size = size.parse::<usize>().unwrap_or(0);
name_map.insert(addr, name);
size_map.insert(addr, size);
}
let mut map = HashMap::new();
for (addr, size) in size_map.into_iter() {
let name = name_map.remove(&addr).unwrap();
map.insert(addr, (name, size));
}
map
}
//use noita_api::types::*;
fn main() {
let map = get_map();
let mut args = args();
let Some(pid) = args.nth(1) else {
2!("no pid");
return;
};
let pid = pid.parse::<usize>().unwrap();
let path = format!("/proc/{pid}/mem");
let mem = File::open(path).unwrap();
#[allow(unused)]
let print = |addr: u32| {
check_global(addr, &mem, &map, &mut Vec::new(), None).print(0, 0, 0, None);
};
#[allow(unused)]
let print_sized = |addr: u32| {
let elem = Elem::from_addr(
addr,
&mem,
&map,
&mut Vec::new(),
"Unk",
get_size(&mem, addr).unwrap() as usize,
false,
);
elem.print(0, 0, 0, None);
};
print_sized(read_byte(&mem, 0x0122374c).unwrap());
}
#[derive(Default, PartialEq, Debug)]
pub struct Struct {
name: String,
size: usize,
fields: Vec<Elem>,
}
#[derive(Debug, PartialEq)]
pub enum Elem {
Ref(Box<Elem>),
Struct(Struct),
VFTable,
Array(Box<Elem>, usize),
#[allow(unused)]
Usize,
#[allow(unused)]
Recursive(usize),
}
impl Elem {
pub fn array_eq(&self, other: &Self) -> bool {
match self {
Elem::Array(e, _) => e.as_ref() == other,
e => e == other,
}
}
pub fn size(&self) -> usize {
match self {
Elem::Ref(_) | Elem::VFTable | Elem::Usize | Elem::Recursive(_) => 4,
Elem::Struct(e) => e.size,
Elem::Array(e, n) => e.size() * n,
}
}
#[allow(clippy::too_many_arguments)]
pub fn from_addr(
mut reference: u32,
mem: &File,
map: &HashMap<u32, (String, usize)>,
addrs: &mut Vec<u32>,
name: &str,
size: usize,
skip: bool,
) -> Self {
let mut s = Struct::new(name, size);
if skip {
s.fields.push(Elem::VFTable);
reference += 4;
}
let mut i = 0;
while i < if skip {
(size / 4).saturating_sub(1)
} else {
size / 4
} {
let len = addrs.len();
let e = check_global(reference + 4 * i as u32, mem, map, addrs, Some(name));
i += e.size() / 4;
if let Some(last) = s.fields.last_mut()
&& last.array_eq(&e)
{
if let Elem::Array(_, n) = last {
*n += 1;
} else {
s.fields.pop();
s.fields.push(Elem::Array(Box::new(e), 2));
};
} else {
s.fields.push(e);
}
while len < addrs.len() {
addrs.pop();
}
}
Elem::Struct(s)
}
}
fn check_global(
reference: u32,
mem: &File,
map: &HashMap<u32, (String, usize)>,
addrs: &mut Vec<u32>,
parent: Option<&str>,
) -> Elem {
if let Some(n) = addrs.iter().position(|n| *n == reference) {
return Elem::Recursive(addrs.len() - n);
}
addrs.push(reference);
let addr_size = get_size(mem, reference).unwrap_or(u32::MAX);
let Some(table) = read_byte(mem, reference) else {
return Elem::Usize;
};
if let Some((name, size)) = map.get(&table) {
if Some(name.as_ref()) == parent {
Elem::VFTable
} else {
Elem::from_addr(
reference,
mem,
map,
addrs,
name,
if addr_size != u32::MAX {
addr_size as usize
} else {
*size
},
true,
)
}
} else if let Some(size) = get_size(mem, table)
&& let Some(inner) = read_byte(mem, table)
{
Elem::Ref(if size == 4 {
Box::new(check_global(inner, mem, map, addrs, None))
} else {
Box::new(Elem::from_addr(
inner,
mem,
map,
addrs,
"Unk",
size as usize,
false,
))
})
} else {
Elem::Usize
}
}
impl Struct {
pub fn new(name: &str, size: usize) -> Self {
Self {
name: name.to_string(),
size,
..Default::default()
}
}
fn print(&self, n: usize, count: usize, entry: usize, array: Option<usize>) {
println!(
"{}[{entry}]{}{}<{}>{}",
" ".repeat(n),
"&".repeat(count),
self.name,
self.size,
array.map(|a| format!("[{a}]")).unwrap_or_default()
);
let mut e = 0;
for f in self.fields.iter() {
f.print(n + 1, 0, e / 4, None);
e += f.size();
}
}
}
impl Elem {
fn print(&self, n: usize, count: usize, e: usize, array: Option<usize>) {
match self {
Elem::Ref(r) => r.print(n, count + 1, e, array),
Elem::Array(r, k) => r.print(n, count, e, Some(*k)),
Elem::Struct(s) => s.print(n, count, e, array),
Elem::Usize => {
println!(
"{}[{e}]{}usize{}",
" ".repeat(n),
"&".repeat(count),
array.map(|a| format!("[{a}]")).unwrap_or_default()
)
}
Elem::Recursive(k) => {
println!(
"{}[{e}]{}recursive<{k}>{}",
" ".repeat(n),
"&".repeat(count),
array.map(|a| format!("[{a}]")).unwrap_or_default()
)
}
Elem::VFTable => {
println!(
"{}[{e}]{}VFTable{}",
" ".repeat(n),
"&".repeat(count),
array.map(|a| format!("[{a}]")).unwrap_or_default()
)
}
}
}
}

File diff suppressed because it is too large Load diff