555 lines
19 KiB
Rust
555 lines
19 KiB
Rust
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()
|
|
|
|
}
|