From 5934aad39baa5202942349c2d4125d232e0896e5 Mon Sep 17 00:00:00 2001 From: Jim <0xJ1M@users.noreply.github.com> Date: Wed, 20 May 2026 19:15:27 +0100 Subject: [PATCH] RD-1: Added an inital port of d_main.c/d_main.h. Added some other basic files --- .gitignore | 9 +- .vscode/launch.json | 44 ++++ Cargo.lock | 7 + Cargo.toml | 6 + configs.txt | 1 + src/d_main/mod.rs | 554 ++++++++++++++++++++++++++++++++++++++++++++ src/doomdef.rs | 147 ++++++++++++ src/m_argv.rs | 101 ++++++++ src/main.rs | 16 ++ src/math/m_fixed.rs | 47 ++++ src/math/mod.rs | 3 + 11 files changed, 934 insertions(+), 1 deletion(-) create mode 100644 .vscode/launch.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 configs.txt create mode 100644 src/d_main/mod.rs create mode 100644 src/doomdef.rs create mode 100644 src/m_argv.rs create mode 100644 src/main.rs create mode 100644 src/math/m_fixed.rs create mode 100644 src/math/mod.rs diff --git a/.gitignore b/.gitignore index 8c2b884..3636ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # ---> VisualStudioCode -.vscode/* +.vscode/ !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json @@ -12,3 +12,10 @@ # Built Visual Studio Code Extensions *.vsix + + +# Added by cargo + +/target + +/WADS/* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9e833a1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,44 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug Executable", + "cargo": { + "args": [ + "build", + "--bin=RustyDoom" + ], + "filter": { + "name": "RustyDoom", + "kind": "bin" + } + }, + "args": [ + "./WADS/DOOM.WAD", "-dev", "@configs.txt", "-nomonsters", "-file", "test.WAD", "test2.WAD", "-skill", "3" + ], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Release Executable", + "cargo": { + "args": [ + "build", + "--release", + "--bin=RustyDoom" + ], + "filter": { + "name": "RustyDoom", + "kind": "bin" + } + }, + "args": [ + "./WADS/DOOM.WAD", "arg2", "-fast" + ], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..161433b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "RustyDoom" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a33c878 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "RustyDoom" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/configs.txt b/configs.txt new file mode 100644 index 0000000..addd7ed --- /dev/null +++ b/configs.txt @@ -0,0 +1 @@ +-warp 1 5 \ No newline at end of file diff --git a/src/d_main/mod.rs b/src/d_main/mod.rs new file mode 100644 index 0000000..d77fa75 --- /dev/null +++ b/src/d_main/mod.rs @@ -0,0 +1,554 @@ +use std::env; +use std::fs; +use std::io::{self, Write}; +use std::path::{self}; +use std::process; + +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::doomdef::{ + VERSION, + // D_DEVSTR, + GameMode, + Skill, + DOOMGLOBALS +}; + +fn flush_and_wait() { + let _ = io::stdout().flush(); + + // Replaces C's getchar() by waiting for the Enter key + let mut buffer = String::new(); + let _ = io::stdin().read_line(&mut buffer); +} + +#[allow(non_snake_case)] +pub fn D_AddFile(file: &str) { + DOOMGLOBALS::with_mut(|g| { + g.wadfiles.push(file.to_string()); + }); +} + +#[allow(non_snake_case)] +pub fn D_ProcessFileParameters(p_file: i32) { + let args = m_argv::M_GetAll(); + let pos = p_file as usize; + // Find the index of "-file" + // Look at everything after the index + for &arg in args.iter().skip(pos + 1) { + if arg.starts_with('-') { + break; // Stop if we hit another flag like -debug + } + D_AddFile(arg); + } +} + +/// Reads a Response file for additional command line arguments +/// A response file is a text file that may store additional command line parameters. +/// The file may have any name that is valid to the system, optionally with an extension. +/// The parameters are typed as in the command line (-episode 2, for example), +/// but one per line, where up to 100 lines may be used. +/// The additional parameters may be disabled for later use by placing a vertical bar +/// (the | character) between the prefixing dash (-) and the rest of the parameter name. +#[allow(non_snake_case)] +fn FindResponseFile(args: &mut Vec) -> Result<(), String> { + const MAX_ARGVS: usize = 100; + + let mut i = 0; + while i < args.len() { + let arg = &args[i]; + if arg.len() > 0 && arg.as_bytes()[0] == b'@' { + + let f_name = &arg[1..]; // remove @ + let f_content: String; + + match fs::read_to_string(f_name) { + Ok(content) => f_content = content, + Err(e) => { + eprintln!("Found response file {}! (Error reading: {})", f_name, e); + std::process::exit(1); + } + } + eprintln!("Found response file {}!", f_name); + + let f_args: Vec = f_content + .split_whitespace() + .map(|s| s.to_string()) + .collect(); + + args.remove(i); + + for (offset, new_arg) in f_args.into_iter().enumerate() { + args.insert(i+offset, new_arg); + } + + if args.len() > MAX_ARGVS { + eprintln!("Warning: Recieved {} total arguments, exceeding original MAX_ARGSV limit {}", args.len(), MAX_ARGVS) + } + + println!("{} command-line args:", args.len()); + for arg in args { + println!("\t{}", arg); + } + break; + } + i +=1; + } + + Ok(()) +} + +#[allow(non_snake_case)] +fn IdentifyVersion() -> Result<(), String> { + + if M_CheckParm("-shdev") != PARM_NOT_FOUND { + eprintln!("-shddev param not implemented in this port"); + eprintln!("Usage: RustyDoom "); + process::exit(1); + } + if M_CheckParm("-regdev") != PARM_NOT_FOUND { + eprintln!("-regdev param not implemented in this port"); + eprintln!("Usage: RustyDoom "); + process::exit(1); + } + if M_CheckParm("-comdev") != PARM_NOT_FOUND { + eprintln!("-comdev param not implemented in this port"); + eprintln!("Usage: RustyDoom "); + process::exit(1); + } + + let iwad_arg = m_argv::M_GetPositionalArgv() + .unwrap_or_else(|| { + eprintln!("Usage: RustyDoom "); + process::exit(1); + }); + + let Ok(iwad_path) = path::absolute(std::path::PathBuf::from(iwad_arg)) else { + eprintln!("FATAL: Could not resolve the IWAD path: {}", iwad_arg); + process::exit(1); + }; + if !iwad_path.is_file() { + eprintln!("FATAL: IWAD file not found: {}", iwad_path.display()); + process::exit(1); + } + + let filename = iwad_path + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or_else(|| { + eprintln!("FATAL: Unable to get WAD filename from path: {:?}", iwad_path); + std::process::exit(1); + }) + .to_ascii_lowercase(); + + DOOMGLOBALS::with_mut(|g| { + g.language = 0; + + g.gamemode = match filename.as_str() { + + "doom1.wad" => GameMode::Shareware, + + "doom.wad" => GameMode::Registered, + + "doomu.wad" | "ultimate.wad" => { + GameMode::Retail + } + + "doom2.wad" => GameMode::Commercial, + + "doom2f.wad" => { + g.language = 1; + GameMode::Commercial + } + + "tnt.wad" => GameMode::Commercial, + + "plutonia.wad" => GameMode::Commercial, + + _ => GameMode::Indetermined, + }; + }); + D_AddFile(&iwad_arg); + + Ok(()) +} + +// Converts the given string into a valid [u8; 128] array +fn to_fixed_bytes(input: &str) -> [u8; 128] { + let mut b = [0u8; 128]; + let src = input.as_bytes(); + let len = src.len().min(128); + b[..len].copy_from_slice(&src[..len]); + b +} + +#[allow(non_snake_case)] +pub fn D_DoomMain() { + // get cli args from env + // Original doom uses a static module argc, argv + // Collects arguments into a Vector of Strings + // Process args + let mut args: Vec = env::args().collect(); + let _ = FindResponseFile(&mut args); + + m_argv::init(args); + let _ = IdentifyVersion(); + + // setbuf (stdout, NULL); // TODO: Investigate this + DOOMGLOBALS::with_mut(|g| { + g.modifiedgame = false; + g.nomonsters = M_CheckParm("-nomonsters") != 0; + g.respawnparm = M_CheckParm("-respawn") != 0; + g.fastparm = M_CheckParm("-fastparm") != 0; + g.devparm = M_CheckParm("-devparam") != 0; + g.deathmatch = if M_CheckParm("-altdeath") != 0 { + 2 + } else if M_CheckParm("-deathmatch") != 0 { + 1 + } else { + 0 + }; + // TODO: fix the original issue to handle TnT + Plutonia + g.title = match g.gamemode { + GameMode::Retail => to_fixed_bytes(&format!(" The Ultimate DOOM Startup v{}.{} ", VERSION / 100, VERSION % 100)), + GameMode::Shareware => to_fixed_bytes(&format!(" DOOM Shareware Startup v{}.{} ", VERSION / 100, VERSION % 100)), + GameMode::Registered => to_fixed_bytes(&format!(" DOOM Registered Startup v{}.{} ", VERSION / 100, VERSION % 100)), + GameMode::Commercial => to_fixed_bytes(&format!(" DOOM 2: Hell on Earth v{}.{} ", VERSION / 100, VERSION % 100)), + GameMode::Indetermined => to_fixed_bytes(&format!(" Public DOOM - v{}.{} ", VERSION / 100, VERSION % 100)), + }; + + println!("{}", String::from_utf8_lossy(&g.title)); + + if g.devparm { + eprintln!("devparm not implemented in this port"); + eprintln!("Usage: RustyDoom "); + process::exit(1); + } + }); + + // Check and parse turbo options if applicable + if M_CheckParm("-turbo") != PARM_NOT_FOUND { + let mut scale :i32 = 200; + // TODO: Resolve externs (int should be fixed_t) or FixedPoint in our case + // extern int forwardmove[2]; + // extern int sidemove[2]; + if let Some(arg_str) = M_GetOptionalArgumentValueByArgument("-turbo") { + if let Ok(parsed_val) = arg_str.parse() { + scale = parsed_val; + } + else { + + } + } + scale = scale.clamp(10, 400); + println!("turbo scale: {}%", scale); + // TODO: Resolve externs + //forwardmove[0] = forwardmove[0]*scale/100; + //forwardmove[1] = forwardmove[1]*scale/100; + //sidemove[0] = sidemove[0]*scale/100; + //sidemove[1] = sidemove[1]*scale/100; + } + + if M_CheckParm ("-wart") != PARM_NOT_FOUND { + eprintln!("-wart is not implemented in this port"); + eprintln!("Usage: RustyDoom "); + process::exit(1); + } + + let mut p = M_CheckParm("-file");{ + if p != PARM_NOT_FOUND { + // Params after -file are wadfile/lump names + // Continue collecting until end of params or another `-` preceded param + DOOMGLOBALS::with_mut(|g|{ + g.modifiedgame = true; + }); + D_ProcessFileParameters(p); + } + } + + // Playing a dmeo? + p = M_CheckParm("-playdemo"); + if p != PARM_NOT_FOUND { + p = M_CheckParm("-timedemo"); + } + + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + if let Some(demo_file) = m_argv::M_GetOptionalArgumentValueByIndex(p+1) { + D_AddFile(demo_file); + println!("Playing demo {}.lmp.\n", demo_file); + } + } + + // get the skill, episode and map from the parms if given + + DOOMGLOBALS::with_mut(|g| { + g.startskill = Skill::sk_medium as i32; + g.startepisode = 1; + g.startmap = 1; + g.autostart = false; + }); + + p = M_CheckParm("-skill"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + if let Some(skill_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) { + if let Some(first_char) = skill_val.chars().next() { + let start_skill = (first_char as i32).wrapping_sub('1' as i32); + + DOOMGLOBALS::with_mut(|g| { + g.startskill = start_skill; + }) + } + } + } + + p = M_CheckParm("-episode"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + if let Some(episode_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) { + if let Some(first_char) = episode_val.chars().next() { + let start_episode = (first_char as i32).wrapping_sub('0' as i32); + + DOOMGLOBALS::with_mut(|g| { + g.startepisode = start_episode; + g.startmap = 1; + }) + } + } + } + + p = M_CheckParm("-timer"); + let is_deathmath = DOOMGLOBALS::with_ref(|g| g.deathmatch); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 && is_deathmath > 0 { + if let Some(timer_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) { + match timer_val.parse::() { + Ok(time) => { + if time > 1 { + println!("Levels will end after {} minutes", time) + } else { + + println!("Levels will end after {} minute", time) + } + }, + Err(_) => { + eprintln!("value for -timer is not a valid number of minutes"); + eprintln!("Usage: RustyDoom "); + std::process::exit(1); + } + }; + } + } + + p = M_CheckParm("-avg"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 && is_deathmath > 0 { + println!("Austin Virtual Gaming: Levels will end after 20 minutes\n"); + } + + p = M_CheckParm("-warp"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + let g_mode = DOOMGLOBALS::with_ref(|g| g.gamemode); + if g_mode == GameMode::Commercial { + if let Some(gamemode_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) { + match gamemode_val.parse::() { + Ok(mapstart) => { + DOOMGLOBALS::with_mut(|g| { + g.startmap = mapstart; + g.autostart = true; + }) + }, + Err(_) => { + eprintln!("value for -warp is not a valid map number"); + eprintln!("Usage: RustyDoom "); + std::process::exit(1); + } + }; + } + } else { + if let Some(episode_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 1) { + if let Some(map_val) = m_argv::M_GetOptionalArgumentValueByIndex(p + 2) { + match (episode_val.parse::(), map_val.parse::()) { + (Ok(startepisode), Ok(startmap)) => { + DOOMGLOBALS::with_mut(|g| { + g.startepisode = startepisode; + g.startmap = startmap; + g.autostart = true; + } + ); + } + (Ok(_), Err(_)) => { + eprintln!("value for starting map in -warp is not a valid number"); + eprintln!("Usage: RustyDoom "); + std::process::exit(1); + } + (Err(_), Ok(_)) => { + eprintln!("value for starting episode in -warp is not a valid number"); + eprintln!("Usage: RustyDoom "); + std::process::exit(1); + } + (Err(_), Err(_)) => { + eprintln!("Values for starting episode and map in -warp are not a valid numbers"); + eprintln!("Usage: RustyDoom "); + std::process::exit(1) + } + } + }; + } + } + } + + // TODO: Implement subsytem inits + /* + println!("V_Init: allocate screens."); + V_Init(); + + println!("M_LoadDefaults: Load system defaults.") + M_LoadDefaults(); + + println!("Z_init: Init zone memory allocation daemon."); + Z_Init(); + + println!("W_Init: Init WADfiles"); + W_InitMultipleFiles(wadfiles); + */ + + let is_modified = DOOMGLOBALS::with_ref(|g| g.modifiedgame); + + if is_modified { + let iwads_lumps_to_check: Vec<&str> = vec!["e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9", + "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9", + "dphoof","bfgga0","heada1","cybra1","spida1d1"]; + + let g_mode = DOOMGLOBALS::with_ref(|g| g.gamemode); + + if g_mode == GameMode::Shareware { + // I_ERROR? + } + + if g_mode == GameMode::Registered { + for lump in iwads_lumps_to_check.iter().take(24) { + // TODO: Implement check + // for (i = 0;i < 23; i++) + // if (W_CheckNumForName(name[i])<0) + // I_Error("\nThis is not the registered version."); + } + } + } + + // Display the modifed game header + + if is_modified { + print!("===========================================================================\n\ + ATTENTION: This version of DOOM has been modified. If you would like to\n\ + get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n\ + You will not receive technical support for modified games.\n\ + press enter to continue\n\ + ===========================================================================\n" + ); + flush_and_wait(); + } + + match DOOMGLOBALS::with_ref(|g| g.gamemode) { + GameMode::Shareware | GameMode::Indetermined => { + print!("===========================================================================\n\ + Shareware!\n\ + ===========================================================================\n") + } + GameMode::Registered | GameMode::Retail | GameMode::Commercial => { + print!("===========================================================================\n\ + Commercial product - do not distribute!\n\ + Please report software piracy to the SPA: 1-800-388-PIR8\n\ + ===========================================================================\n") + } + } + + // TODO: Implement other inits + /* + println!("M_Init: Init miscellaneous info."); + M_Init(); + + println!("R_Init: Init DOOM refresh daemon - ") + R_Init(); + + println!("P_Init: Init Playloop state."); + P_Init(); + + println!("I_Init: Setting up machine state."); + I_Init() + + println!("D_CheckNetGame: Checking network game status."); + D_CheckNetGame() + + println!("S_Init: Setting up sound."); + S_Init (snd_SfxVolume /* *8 */, snd_MusicVolume /* *8*/ ); + + printf ("HU_Init: Setting up heads up display.\n"); + HU_Init (); + + printf ("ST_Init: Init status bar.\n"); + ST_Init (); + */ + + // Check for a driver that wants intermission stats + + + p = M_CheckParm("-statcopy"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + // TODO: implement for statistics driver + /*extern void* statcopy; + + statcopy = (void*)atoi(myargv[p+1]); + printf ("External statistics registered.\n");*/ + } + + // Start the approiate gaem based on parms + p = M_CheckParm("-record"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + // TODO: Implement G_RecordDemo + //G_RecordDemo(m_argv::M_GetOptionalArgumentValueByIndex(p+1)); + DOOMGLOBALS::with_mut(|g| g.autostart = true); + } + + p = M_CheckParm("-playdemo"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + // TODO: Implement G_DeferedPlayDemo + // G_DeferedPlayDemo (myargv[p+1]); + // D_DoomLoop (); // never returns + } + + p = M_CheckParm("-timedemo"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + // TODO: Implement G_TimeDemo + // G_TimeDemo (myargv[p+1]); + // D_DoomLoop (); // never returns + } + + p = M_CheckParm("-loadgame"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + // TODO: Implement loadgame param G_LoadGame + // G_TimeDemo (myargv[p+1]); + // D_DoomLoop (); // never returns + if M_CheckParm("-cdrom") != PARM_NOT_FOUND { + // sprintf(file, "c:\\doomdata\\"SAVEGAMENAME"%c.dsg",myargv[p+1][0]); + } else { + // sprintf(file, SAVEGAMENAME"%c.dsg",myargv[p+1][0]); + } + // G_LoagGame(file) + } + + p = M_CheckParm("-ga_loadgame"); + if p != PARM_NOT_FOUND && p < m_argv::M_GetArgC() - 1 { + + } + + let g_action = DOOMGLOBALS::with_ref(|g| g.gameaction); + + // TODO: Impement Game actions + // if g_action != ga_loadgame { + // let autostart = DOOMGLOBALS::with_ref(|g| g.autostart); + // let netgame = DOOMGLOBALS::with_ref(|G| G.) + // } + + // D_DoomLoop() + +} diff --git a/src/doomdef.rs b/src/doomdef.rs new file mode 100644 index 0000000..5374cc9 --- /dev/null +++ b/src/doomdef.rs @@ -0,0 +1,147 @@ +use std::cell::RefCell; +use std::sync::{Mutex, MutexGuard, OnceLock}; + + +pub const VERSION:i32 = 110; +pub const D_DEVSTR: &str = "Development mode ON."; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum GameMode { + Shareware, + Registered, + Retail, + Commercial, + Indetermined +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum GameState { + Level, + Intermission, + Finale, + Demoscreen, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(i32)] +#[allow(non_camel_case_types)] +pub enum Skill { + sk_baby = 0, + sk_easy = 1, + sk_medium = 2, + sk_hard = 3, + sk_nightmare = 4 +} + +#[derive(Debug)] +pub struct DoomGlobalState { + pub devparm: bool, + pub nomonsters: bool, + pub respawnparm: bool, + pub fastparm: bool, + pub drone: bool, + pub singletics: bool, + pub advancedemo: bool, + pub automapactive: bool, + pub scaledviewwidth: usize, + pub viewheight: usize, + pub inhelpscreens: bool, + pub paused: bool, + pub viewactive: bool, + pub menuactive: bool, + pub gameaction: bool, + pub usergame: bool, + pub autostart: bool, + pub demorecording: bool, + pub modifiedgame: bool, + pub deathmatch: u32, + pub language: u32, + pub singledemo: bool, + pub consoleplayer: u32, + pub maketic: u32, + + // Buffers + pub wadfile: [u8; 1024], + pub mapdir: [u8; 1024], + pub basedefault: [u8; 1024], + pub title: [u8; 128], + + // System State + pub gamemode: GameMode, + pub startskill: i32, + pub startepisode: i32, + pub startmap: i32, + pub wipegamestate: GameState, + pub gametic: u32, + + // Lists + pub wadfiles: Vec, +} + +impl Default for DoomGlobalState { + fn default () -> Self { + Self { + devparm: false, + nomonsters: false, + respawnparm: false, + fastparm: false, + drone: false, + singletics: false, + advancedemo: false, + automapactive: false, + scaledviewwidth: 0, + viewheight: 0, + inhelpscreens: false, + paused: false, + viewactive: true, + menuactive: false, + gameaction: false, // ga_nothing + usergame: true, + autostart: false, + demorecording: false, + modifiedgame: false, + deathmatch: 0, + language: 0, // english + singledemo: false, + consoleplayer: 0, + maketic: 0, + wadfile: [0; 1024], + mapdir: [0; 1024], + basedefault: [0; 1024], + title: [0; 128], + gamemode: GameMode::Indetermined, + startskill: Skill::sk_medium as i32, + startepisode: 1, + startmap: 1, + wipegamestate: GameState::Demoscreen, + gametic: 0, + wadfiles: Vec::new(), + } + } +} + +thread_local! { + // We promise not to be naughty and access this from another thread :) + static GLOBALS: RefCell = RefCell::new(DoomGlobalState::default()); +} + + +pub struct DOOMGLOBALS; + +impl DOOMGLOBALS { + /// Provides mutable access to the globals + pub fn with_mut(f: F) -> R + where + F: FnOnce(&mut DoomGlobalState) -> R + { + GLOBALS.with(|g| f(&mut g.borrow_mut())) + } + + /// Provides reference only access to the globals + pub fn with_ref(f: F) -> R + where + F: FnOnce(&DoomGlobalState) -> R + { + GLOBALS.with(|g| f(&g.borrow())) + } +} \ No newline at end of file diff --git a/src/m_argv.rs b/src/m_argv.rs new file mode 100644 index 0000000..d008f60 --- /dev/null +++ b/src/m_argv.rs @@ -0,0 +1,101 @@ +// Stores argc and argv from the original Doom +// M_CheckParm as well as a helper to get a param value (e.g -file MyWad.wad) + +use std::sync::OnceLock; + +static MYARGC: OnceLock = OnceLock::new(); + +static MYARGV: OnceLock> = OnceLock::new(); + +pub const PARM_NOT_FOUND: i32 = 0; + +pub fn init(args: Vec) { + MYARGC.set(args.len()).ok(); + MYARGV.set(args).ok(); +} + +/// Returns the value of the pass CLI arguments at the position specified by `index` +#[allow(non_snake_case)] +pub fn M_GetPositionalArgv() -> Option<&'static str> { + let args = MYARGV.get()?; + + args.iter() + .skip(1) + .find(|arg| { + !arg.starts_with('-') + }) + .map(|s| s.as_str()) +} + +/// Returns the value of a optional argument specified by the argument +/// +#[allow(non_snake_case)] +pub fn M_GetOptionalArgumentValueByArgument(arg: &str) -> Option<&'static str> { + + let idx: usize = M_CheckParm(arg) as usize; + + if idx == 0 { + return None; + } + + let argv: &Vec = MYARGV.get()?; + let idx_val = idx + 1; + if idx_val > argv.len() - 1 { + return None + } + Some(argv[idx_val].as_str()) + +} + +/// Returns the value of a optional argument specified by the argument +/// +#[allow(non_snake_case)] +pub fn M_GetOptionalArgumentValueByIndex(index: i32) -> Option<&'static str> { + + let idx = index as usize; + if idx == 0 { + return None; + } + + let argv: &Vec = MYARGV.get()?; + let idx_val = idx + 1; + if idx_val > argv.len() - 1 { + return None + } + Some(argv[idx_val].as_str()) + +} + +/// Checks for the given paramter in the program command line arugments +/// Returns the argument number or zero if not present +#[allow(non_snake_case)] +pub fn M_CheckParm(c_str: &str) -> i32 { + + let args = match MYARGV.get() { + Some(a) => a, + None => return 0, // Not initialized yet + }; + + for i in 1..args.len() { + if args[i] == c_str { + return i as i32; + } + } + + PARM_NOT_FOUND +} + +/// Gets all the parameters of myargv +#[allow(non_snake_case)] +pub fn M_GetAll() -> Vec<&'static str> { + match MYARGV.get() { + Some(args) => args.iter().map(|s| s.as_str()).collect(), + None => Vec::new(), + } +} + +/// Returns the number of arguments passed to the program +#[allow(non_snake_case)] +pub fn M_GetArgC() -> i32 { + *MYARGC.get().unwrap() as i32 +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..903d705 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,16 @@ +mod d_main; +mod m_argv; +mod doomdef; +mod math; + + +/// Main entry pointz +/// +fn main() { + + if let Err(e) = std::panic::catch_unwind(|| { + d_main::D_DoomMain(); + }) { + eprintln!("Panic in Doom Main: {:?}", e); + } +} diff --git a/src/math/m_fixed.rs b/src/math/m_fixed.rs new file mode 100644 index 0000000..695e477 --- /dev/null +++ b/src/math/m_fixed.rs @@ -0,0 +1,47 @@ +/* Implemnetation of *m_fixed.c/h + +This provides fixed-point arithmetic as 32-bit 16.16 +*/ + +pub const FRACBITS: i32 = 16; +pub const FRACUNIT: i32 = 1 << FRACBITS; + +pub const MININT: i32 = 0x80000000; +pub const MAXINT: i32 = 0x7fffffff; +/// Struct representing a fixed point 32-bit value +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct FixedPoint { + pub value: i32, +} + +impl FixedPoint { + + #[allow(non_snake_case)] + pub fn FixedMul(a: FixedPoint, b: FixedPoint) -> FixedPoint { + let value = ((a.value as u64) * (b.value as u64) >> FRACBITS) as i32; + FixedPoint{value} + } + + #[allow(non_snake_case)] + pub fn FixedDiv(a: FixedPoint, b: FixedPoint) -> FixedPoint { + if (i32::abs(a.value) >> 14) >= i32::abs(b.value) { + if a.value^b.value == 0 { + FixedPoint{value: MININT}; + } + else { + FixedPoint{value: MAXINT}; + } + } + FixedPoint::FixedDiv2(a,b) + } + + #[allow(non_snake_case)] + pub fn FixedDiv2(a: FixedPoint, b: FixedPoint) -> FixedPoint { + let c: f64 = ((a.value as f64) / (b.value as f64)) * FRACUNIT as f64; + if c >= 2147483648.0 || c < -2147483648.0 { + panic!("FixedDiv: divide by zero"); + } + FixedPoint { value: c as i32 } + } + +} \ No newline at end of file diff --git a/src/math/mod.rs b/src/math/mod.rs new file mode 100644 index 0000000..6724737 --- /dev/null +++ b/src/math/mod.rs @@ -0,0 +1,3 @@ +// pub mod m_fixed; + +// pub use m_fixed::FixedPoint; \ No newline at end of file