RD-1: Added an inital port of d_main.c/d_main.h. Added some other basic files

This commit is contained in:
Jim
2026-05-20 19:08:56 +01:00
parent 21aedec4d4
commit c7f7d9c324
11 changed files with 934 additions and 1 deletions

9
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# ---> VisualStudioCode # ---> VisualStudioCode
.vscode/* .vscode/
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
@@ -12,3 +12,10 @@
# Built Visual Studio Code Extensions # Built Visual Studio Code Extensions
*.vsix *.vsix
# Added by cargo
/target
/WADS/*

44
.vscode/launch.json vendored Normal file
View File

@@ -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}"
}
]
}

7
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 = "RustyDoom"
version = "0.1.0"

6
Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "RustyDoom"
version = "0.1.0"
edition = "2024"
[dependencies]

1
configs.txt Normal file
View File

@@ -0,0 +1 @@
-warp 1 5

554
src/d_main/mod.rs Normal file
View File

@@ -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<String>) -> 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<String> = 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 <iwad-file> <Options>");
process::exit(1);
}
if M_CheckParm("-regdev") != PARM_NOT_FOUND {
eprintln!("-regdev param not implemented in this port");
eprintln!("Usage: RustyDoom <iwad-file> <Options>");
process::exit(1);
}
if M_CheckParm("-comdev") != PARM_NOT_FOUND {
eprintln!("-comdev param not implemented in this port");
eprintln!("Usage: RustyDoom <iwad-file> <Options>");
process::exit(1);
}
let iwad_arg = m_argv::M_GetPositionalArgv()
.unwrap_or_else(|| {
eprintln!("Usage: RustyDoom <iwad-file> <Options>");
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<String> = 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 <iwad-file> <Options>");
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 <iwad-file> <Options>");
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::<u32>() {
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 <iwad-file> <Options>");
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::<i32>() {
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 <iwad-file> <Options>");
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::<i32>(), map_val.parse::<i32>()) {
(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 <iwad-file> <Options>");
std::process::exit(1);
}
(Err(_), Ok(_)) => {
eprintln!("value for starting episode in -warp is not a valid number");
eprintln!("Usage: RustyDoom <iwad-file> <Options>");
std::process::exit(1);
}
(Err(_), Err(_)) => {
eprintln!("Values for starting episode and map in -warp are not a valid numbers");
eprintln!("Usage: RustyDoom <iwad-file> <Options>");
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()
}

147
src/doomdef.rs Normal file
View File

@@ -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<String>,
}
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<DoomGlobalState> = RefCell::new(DoomGlobalState::default());
}
pub struct DOOMGLOBALS;
impl DOOMGLOBALS {
/// Provides mutable access to the globals
pub fn with_mut<F, R>(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, R>(f: F) -> R
where
F: FnOnce(&DoomGlobalState) -> R
{
GLOBALS.with(|g| f(&g.borrow()))
}
}

101
src/m_argv.rs Normal file
View File

@@ -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<usize> = OnceLock::new();
static MYARGV: OnceLock<Vec<String>> = OnceLock::new();
pub const PARM_NOT_FOUND: i32 = 0;
pub fn init(args: Vec<String>) {
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<String> = 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<String> = 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
}

16
src/main.rs Normal file
View File

@@ -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);
}
}

47
src/math/m_fixed.rs Normal file
View File

@@ -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 }
}
}

3
src/math/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
// pub mod m_fixed;
// pub use m_fixed::FixedPoint;