2 Commits

Author SHA1 Message Date
Jim
dad59e261d RD-6: Implemeneted M_Init 2026-05-28 20:41:59 +01:00
Jim
93a3c8548e RD-5: Implemented Rust equivalent zone allocator 2026-05-27 21:14:43 +01:00
5 changed files with 501 additions and 8 deletions

View File

@@ -1,7 +1,12 @@
# RustyDoom
A Rust version of the original DOOM from the offically released source code found at [https://github.com/id-software/doom](https://github.com/id-software/doom)
A Rust version of the original DOOM base on the offically released source code found at [https://github.com/id-software/doom](https://github.com/id-software/doom)
This is more of a learning exercise than something good!
This is more of a learning exercise than something good! For example, the original `z_zone.c` and `z_zone.h` will be more "Rust-like" compared to the original version.
This repository will only contain the SHAREWARE verison of the original DOOM1, but will be able to run both IWAD and PWADS.
This repository will be done in two parts before a "release"
1. An initial port keeping as close to the original C code as possible, which some minor modifications where it is clear on how to convert the code to Rust
2. Rust-ify any (hopefully all) parts of the code, that is removing `unsafe` and other such things. This will also include breaking down some of the `uber` sized funtions of the original source into more managable bits

View File

@@ -8,10 +8,12 @@ use crate::m_argv;
use crate::m_argv::M_CheckParm;
use crate::m_argv::M_GetOptionalArgumentValueByArgument;
use crate::m_argv::PARM_NOT_FOUND;
use crate::m_menu::M_Init;
use crate::m_misc::M_LoadDefaults;
use crate::v_video::V_Init;
use crate::w_wad::{W_CheckNumForName, W_InitMultipleFiles};
use crate::z_zone::Z_Init;
use crate::doomdef::{
VERSION,
@@ -408,11 +410,9 @@ pub fn D_DoomMain() {
println!("M_LoadDefaults: Load system defaults.");
M_LoadDefaults();
/* TODO: Implement subsytem inits
println!("Z_init: Init zone memory allocation daemon.");
Z_Init();
*/
Z_Init();
println!("W_Init: Init WADfiles");
W_InitMultipleFiles(DOOMGLOBALS::with_ref(|g| g.wadfiles.clone())); // we are loading so how cares about a borrow
@@ -467,11 +467,12 @@ pub fn D_DoomMain() {
}
}
// TODO: Implement other inits
/*
println!("M_Init: Init miscellaneous info.");
M_Init();
// TODO: Implement other inits
/*
println!("R_Init: Init DOOM refresh daemon - ")
R_Init();

320
src/m_menu/mod.rs Normal file
View File

@@ -0,0 +1,320 @@
// Menu Widget stuff i.e episode selection etc.
// TODO: Implement d_event.handle
use std::ptr::addr_of_mut;
use crate::doomdef::{DOOMGLOBALS, GameMode};
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_snake_case)]
struct menuitem_t {
status: i32,
name: [u8; 10],
routine: Option<fn(choice: i32)>,
// hotkey in menu
alpha_key: u8,
}
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_snake_case)]
struct menu_t {
/// Number of items in the menu
numitems: i32,
/// Pointer to the previous menu
prevMenu: *mut menu_t,
/// The menu items
menuItems: *mut menuitem_t,
/// draw routine
routine: Option<fn()>,
/// X of the menu
x: i32,
/// y of the menu
y: i32,
/// The last item the user was on in the menu
lastOn: i32
}
/// TEMP function
#[allow(non_snake_case)]
fn VoidRoutine() {
}
#[allow(non_snake_case)]
fn VoidRoutineWithChoice(choice: i32) {
let _ = choice;
}
// Main Menu
#[allow(non_snake_case)]
mod MainMenu {
use super::*;
/// Enumeration of the main menu options
#[allow(non_camel_case_types)]
pub(super) enum main_e {
newgame = 0,
options,
loadgame,
savegame,
readthis,
quitdoom,
main_end
}
/// Main Menu items
#[allow(non_upper_case_globals)]
pub(super) static mut MainMenu: [menuitem_t; 6] = [
menuitem_t {
status: 1,
name: *b"M_NGAME\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'n'
},
menuitem_t {
status: 1,
name: *b"M_OPTION\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'o'
},
menuitem_t {
status: 1,
name: *b"M_LOADG\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'l'
},
menuitem_t {
status: 1,
name: *b"M_SAVEG\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b's'
},
menuitem_t {
status: 1,
name: *b"M_RDTHIS\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'r'
},
menuitem_t {
status: 1,
name: *b"M_QUITG\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'q'
}
];
/// Default MainMenu
#[allow(non_upper_case_globals)]
pub(super) static mut MainDef: menu_t = menu_t {
numitems: main_e::main_end as i32,
prevMenu: std::ptr::null_mut(),
menuItems: &raw mut MainMenu as *mut menuitem_t, // Use &raw mut instead of .as_mut_ptr() to avoid illegal static mut references (Feels dirty)
routine: Some(VoidRoutine),
x: 97,
y: 64,
lastOn: 0,
};
}
// New Game menu
#[allow(non_snake_case)]
mod NewGameMenu {
use super::*;
/// Enumeration of the New Game options
#[allow(non_camel_case_types)]
pub(super) enum newgame_e {
killthings,
toorough,
hurtme,
violence,
nightmare,
newg_end
}
/// New Game Menu items
#[allow(non_upper_case_globals)]
pub(super) static mut NewGameMenu: [menuitem_t; 5] = [
menuitem_t {
status: 1,
name: *b"M_JKILL\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'i'
},
menuitem_t {
status: 1,
name: *b"M_ROUGH\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'h'
},
menuitem_t {
status: 1,
name: *b"M_HURT\0\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'h'
},
menuitem_t {
status: 1,
name: *b"M_ULTRA\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'u'
},
menuitem_t {
status: 1,
name: *b"M_NMARE\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'n'
}
];
#[allow(non_upper_case_globals)]
pub(super) static mut NewDef: menu_t = menu_t {
numitems: newgame_e::newg_end as i32,
prevMenu: std::ptr::null_mut(), // TODO: Add EpiDef
menuItems: &raw mut NewGameMenu as *mut menuitem_t, // Use &raw mut instead of .as_mut_ptr() to avoid illegal static mut references (Feels dirty)
routine: Some(VoidRoutine),
x: 48,
y: 63,
lastOn: newgame_e::hurtme as i32,
};
}
// TODO: add OptionsMenu
#[allow(non_snake_case)]
mod ReadThis1 {
use super::*;
/// Enumeration of the main menu options
#[allow(non_camel_case_types)]
pub(super) enum read_e {
rdthisempty1 = 0,
read1_end,
}
/// Read1 Menu items
#[allow(non_upper_case_globals)]
pub(super) static mut ReadMenu1: [menuitem_t; 1] = [
menuitem_t {
status: 1,
name: *b"\0\0\0\0\0\0\0\0\0\0",
routine: Some(VoidRoutineWithChoice),
alpha_key: b'0'
}
];
/// Default MainMenu
#[allow(non_upper_case_globals)]
pub(super) static mut Read1Def: menu_t = menu_t {
numitems: read_e::rdthisempty1 as i32,
prevMenu: addr_of_mut!(MainMenu::MainDef),
menuItems: &raw mut ReadMenu1 as *mut menuitem_t, // Use &raw mut instead of .as_mut_ptr() to avoid illegal static mut references (Feels dirty)
routine: Some(VoidRoutine),
x: 280,
y: 185,
lastOn: 0,
};
}
// TODO: The rest of this hell!
#[allow(non_upper_case_globals)]
static mut currentMenu: *mut menu_t = std::ptr::null_mut();
#[allow(non_upper_case_globals)]
static mut menuactive: bool = false;
#[allow(non_upper_case_globals)]
static mut itemOn: i32 = 0;
#[allow(non_upper_case_globals)]
static mut whichSkull: i32 = 0;
#[allow(non_upper_case_globals)]
static mut skullAnimCounter: i32 = 0;
#[allow(non_upper_case_globals)]
static mut screenSize: i32 = 0;
#[allow(non_upper_case_globals)]
static mut screenBlocks: i32 = 0;
#[allow(non_upper_case_globals)]
static mut messageToPrint: i32 = 0;
#[allow(non_upper_case_globals)]
static mut messageString: *mut u8 = std::ptr::null_mut();
#[allow(non_upper_case_globals)]
static mut messageLastMenuActive: i32 = 0;
#[allow(non_upper_case_globals)]
static mut quickSaveSlot: i32 = -1;
#[allow(non_snake_case)]
pub fn M_Ticket() {
}
#[allow(non_snake_case)]
pub fn M_Drawer() {
}
#[allow(non_snake_case)]
pub fn M_Init() {
unsafe {
currentMenu = addr_of_mut!(MainMenu::MainDef);
menuactive = false;
itemOn = (*currentMenu).lastOn;
whichSkull = 0;
skullAnimCounter = 10;
screenSize = screenBlocks - 3;
messageToPrint = 0;
messageString = std::ptr::null_mut();
messageLastMenuActive = menuactive as i32;
quickSaveSlot = -1;
match DOOMGLOBALS::with_ref(|g| g.gamemode) {
GameMode::Commercial => {
/*
Original source code comment:
// This is used because DOOM 2 had only one HELP
// page. I use CREDIT as second page now, but
// kept this hack for educational purposes.
*/
MainMenu::MainMenu[MainMenu::main_e::readthis as usize] = MainMenu::MainMenu[MainMenu::main_e::quitdoom as usize];
MainMenu::MainDef.numitems -=1;
MainMenu::MainDef.y += 8;
NewGameMenu::NewDef.prevMenu = addr_of_mut!(MainMenu::MainDef);
ReadThis1::Read1Def.routine = Some(VoidRoutine);
ReadThis1::Read1Def.x = 330;
ReadThis1::Read1Def.y = 165;
ReadThis1::ReadMenu1[ReadThis1::read_e::rdthisempty1 as usize].routine = Some(VoidRoutineWithChoice);
},
GameMode::Shareware | GameMode::Registered => {
// TODO EpiDef.numitems--
}
_ => {
// do nothing :)
}
}
}
}
#[allow(non_snake_case)]
pub fn M_StartControlPanel() {
}

View File

@@ -4,9 +4,11 @@ mod doomdef;
mod doomtype;
mod hu_stuff;
mod m_argv;
mod m_menu;
mod m_misc;
mod v_video;
mod w_wad;
mod z_zone;
mod math;

165
src/z_zone/mod.rs Normal file
View File

@@ -0,0 +1,165 @@
/// Memory allocator stuff
/// Compared to the original source, this will be some what "lean n' mean"
use std::cell::RefCell;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
/// The PurgeTags (PUs)
/// Tags < 100 are not overwritten untill freed
pub enum PurgeTag {
/// Static the entire execution time
Static = 1,
/// Static while playing
Sound = 2,
/// static while playing
Music = 3,
/// Original source comment: anything else dave wants static
Dave = 4,
/// static until level exit
Level = 50,
/// static for special thinkers in a level
LevSpec = 51,
// Tags >= 100 are "purgable" whenever needed
PurgeLevel = 100,
Cache = 101,
}
/// Provides metadata on
struct AllocMeta {
/// The size of the allocation
size: usize,
/// The PU tag associated with the allocation
tag: i32,
/// A pointer to the caller's tracking pointer, cleared automatically upon freeing
user: *mut *mut u8,
}
thread_local! {
// We promise not to be naughty and access this from another thread :)
/// Mapping from pointers (as usize) to the correspond AllocationMeta
static REGISTRY: RefCell<HashMap<usize, AllocMeta>> = RefCell::new(HashMap::new());
}
// Helper method that we can use to access the registry
fn with_registry<F, R>(f: F) -> R
where
F: FnOnce(&mut HashMap<usize, AllocMeta>) -> R
{
REGISTRY.with(|registry| f(&mut *registry.borrow_mut()))
}
#[allow(non_snake_case)]
/// Initalized the Zone memory allocator sub-system
pub fn Z_Init() {
// do nothing, we get lazy init of the registry.
}
#[allow(non_snake_case)]
/// Allocate a contiguous chunk of zeroed memory from the zone allocator
/// # Safety
/// This function returns a raw pointer that bypasses Rust's automatic memory
/// tracking. The returned memory must be manually reclaimed using `Z_Free`.
pub unsafe fn Z_Malloc(size: usize, tag: i32, user: *mut *mut u8) -> *mut u8 {
let mut buffer = vec![0u8; size];
let ptr = buffer.as_mut_ptr();
// ensures Rust does not drop the buffer at the end of this block
std::mem::forget(buffer);
// Update the engine's tracking pointer if one was provided
if !user.is_null() {
unsafe {
*user = ptr;
}
}
// Save allocation details using the pointer address as the key
with_registry(|map| {
map.insert(ptr as usize, AllocMeta { size, tag, user });
});
ptr
}
#[allow(non_snake_case)]
/// Reclaims and frees a allocation from Z_Malloc
/// # Safety
/// The provided pointer must point to a valid allocation tracked by the registry.
/// Passing an invalid or already freed pointer can result in undefined behavior
pub unsafe fn Z_Free(ptr: *mut u8) {
if ptr.is_null() {
return;
}
let removed = with_registry(|map| map.remove(&(ptr as usize)));
if let Some(meta) = removed {
// Clear the caller's tracking pointer to prevent use-after-free
if !meta.user.is_null() {
unsafe {
*meta.user = std::ptr::null_mut();
}
}
// re-build the vector so Rust can drop it
unsafe {
let _dropped_vec = Vec::from_raw_parts(ptr, meta.size, meta.size);
}
}
}
#[allow(non_snake_case)]
/// Frees all memory allocations between the specific lifetime PU tags.
/// # Safety
/// All pointers tracked within this tag boundary are invalidated and deallocate
pub unsafe fn Z_FreeTags(low_tag: i32, high_tag: i32) {
// get all of the allocations to free
let tags_to_free: Vec<usize> = with_registry(|map| {
map.iter()
.filter(|(_, meta)| meta.tag >= low_tag && meta.tag <= high_tag)
.map(|(ptr_addr,_)| *ptr_addr)
.collect()
});
// remove each pointer returned from the map and drop the memory
for ptr_addr in tags_to_free {
let removed_ptr = with_registry(|map| map.remove(&ptr_addr));
if let Some(meta) = removed_ptr {
let p = ptr_addr as *mut u8;
unsafe {
// clear caller tracking pointer to preven use after free
if !meta.user.is_null() {
*meta.user = std::ptr::null_mut();
}
let _dropped_vec = Vec::from_raw_parts(p, meta.size, meta.size);
}
}
}
}
// Internal ChangeTag function
#[allow(non_snake_case)]
fn Z_ChangeTagInternal(ptr: *mut u8, tag: i32) {
if ptr.is_null() {
return;
}
with_registry(|map| {
if let Some(meta) = map.get_mut(&(ptr as usize)) {
meta.tag = tag;
}
})
}
/// Modifies the lifetime tag of an active allocation.
///
/// # Safety
/// The provided pointer must point to a valid allocation tracked by the registry
#[allow(non_snake_case)]
pub unsafe fn Z_ChangeTag(ptr: *mut u8, tag: i32) {
// removes the original helper macro from the original C, we don't need withcraft here
Z_ChangeTagInternal(ptr, tag);
}