Skip to content

Commit

Permalink
Handle command-line mods/maps/demos/savefiles
Browse files Browse the repository at this point in the history
If a single file or directory is passed on the command line
we try to infer the basedir, mod, and quake flavor (original
or remastered) as needed, and take the appropriate action
based on the type of the argument.

This enables associating the executable with .bsp/.dem/.sav
files and dropping files/mod dirs over the exe in order to
play them.
  • Loading branch information
andrei-drexler committed Nov 12, 2023
1 parent ebfdbbf commit cb34a1a
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 10 deletions.
277 changes: 272 additions & 5 deletions Quake/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "miniz.h"
#include "unicode_translit.h"

static char *largv[MAX_NUM_ARGVS + 1];
static const char *largv[MAX_NUM_ARGVS + 1];
static char argvdummy[] = " ";

int safemode;
Expand All @@ -55,8 +55,8 @@ static void COM_Path_f (void);
#define PAK0_CRC_V091 28804 /* id1/pak0.pak - v0.91/0.92, not supported */

THREAD_LOCAL char com_token[1024];
int com_argc;
char **com_argv;
int com_argc;
const char **com_argv;

#define CMDLINE_LENGTH 256 /* johnfitz -- mirrored in cmd.c */
char com_cmdline[CMDLINE_LENGTH];
Expand Down Expand Up @@ -1043,6 +1043,37 @@ void SZ_Print (sizebuf_t *buf, const char *data)

//============================================================================

/*
============
COM_FirstPathSep
Returns a pointer to the first path separator, if any, or the end of the string
============
*/
const char *COM_FirstPathSep (const char *path)
{
while (*path && !Sys_IsPathSep (*path))
path++;
return path;
}

/*
============
COM_NormalizePath
Replaces all path separators with forward slashes
============
*/
void COM_NormalizePath (char *path)
{
while (*path)
{
if (Sys_IsPathSep (*path))
*path = '/';
path++;
}
}

/*
============
COM_SkipPath
Expand Down Expand Up @@ -1500,6 +1531,22 @@ void COM_InitArgv (int argc, char **argv)
}
}

/*
================
COM_AddArg
Returns the index of the new argument or -1 on overflow
================
*/
int COM_AddArg (const char *arg)
{
if (com_argc >= MAX_NUM_ARGVS)
return -1;
com_argv[com_argc] = arg;
com_argv[com_argc + 1] = argvdummy;
return com_argc++;
}

/*
================
COM_Init
Expand Down Expand Up @@ -2649,6 +2696,175 @@ static void COM_MigrateNightdiveUserFiles (void)
VEC_FREE (subdirs);
}

/*
=================
COM_SetBaseDirRec
Looks for a valid basedir in all the ancestors of the supplied path
Returns the path relative to that basedir if successful, NULL otherwise
=================
*/
static const char *COM_SetBaseDirRec (const char *start)
{
char buf[MAX_OSPATH];
size_t i, len;

q_strlcpy (buf, start, sizeof (buf));
len = strlen (start);

for (i = len - 1; i > 1; i--)
{
if (Sys_IsPathSep (buf[i]))
{
buf[i] = '\0';
if (COM_SetBaseDir (buf))
return start + i + 1;
}
}

return NULL;
}

/*
=================
COM_MakeRelative
=================
*/
static const char *COM_MakeRelative (const char *basepath, const char *fullpath)
{
for (; *basepath && *fullpath; ++basepath, ++fullpath)
{
if (Sys_IsPathSep (*basepath) != Sys_IsPathSep (*fullpath))
return NULL;
if (*basepath != *fullpath)
return NULL;
}

while (Sys_IsPathSep (*fullpath))
++fullpath;

return fullpath;
}

/*
=================
COM_PatchCmdLine
Tries to initialize basedir from a single command-line argument
(either a mod dir or a map/save/demo file)
Returns true if successful, false otherwise
=================
*/
static qboolean COM_PatchCmdLine (const char *fullpath)
{
static char game[MAX_QPATH];
char qpath[MAX_QPATH];
char printpath[MAX_OSPATH];
const char *relpath;
const char *sep;
int type;
int i;

// The path (file or directory) must exist
type = Sys_FileType (fullpath);
if (type == FS_ENT_NONE)
{
UTF8_ToQuake (printpath, sizeof (printpath), fullpath);
Con_SafePrintf ("\"%s\" does not exist\n", printpath);
return false;
}

// Look for the corresponding basedir
relpath = NULL;
for (i = 0; i < com_numbasedirs; i++)
{
relpath = COM_MakeRelative (com_basedirs[i], fullpath);
if (relpath)
break;
}
if (!relpath)
{
UTF8_ToQuake (printpath, sizeof (printpath), fullpath);
Con_SafePrintf ("\"%s\" does not belong to an existing Quake installation\n", printpath);
return false;
}

// Game dir is the first component of the relative path
sep = COM_FirstPathSep (relpath);
if ((uintptr_t)(sep - relpath) >= sizeof (game))
{
UTF8_ToQuake (printpath, sizeof (printpath), relpath);
Con_SafePrintf ("\"%s\" is too long\n", printpath);
return false;
}

// Apply game dir
Q_strncpy (game, relpath, (int)(sep - relpath));
COM_AddArg ("-game");
COM_AddArg (game);
if (*sep)
relpath = sep + 1;
q_strlcpy (qpath, relpath, sizeof (qpath));
COM_NormalizePath (qpath);

// Check argument type
switch (type)
{
case FS_ENT_DIRECTORY:
if (qpath[0])
{
if (q_strcasecmp (qpath, "maps") == 0)
{
Cbuf_AddText ("menu_maps\n");
return true;
}
UTF8_ToQuake (printpath, sizeof (printpath), qpath);
Con_SafePrintf ("\x02subdir \"%s\" ignored\n", printpath);
}
return true;

case FS_ENT_FILE:
{
const char *ext = COM_FileGetExtension (qpath);

// Map file
if (q_strcasecmp (ext, "bsp") == 0)
{
if (q_strncasecmp (qpath, "maps/", 5) != 0)
return false;
memmove (qpath, qpath + 5, strlen (qpath + 5) + 1);
Cbuf_AddText (va ("menu_maps \"%s\"\n", qpath));
return true;
}

// Save file
if (q_strcasecmp (ext, "sav") == 0)
{
Cbuf_AddText (va ("load \"%s\"\n", qpath));
return true;
}

// Demo file
if (q_strcasecmp (ext, "dem") == 0)
{
Cbuf_AddText (va ("playdemo \"%s\"\n", qpath));
return true;
}

break;
}

default:
break;
}

UTF8_ToQuake (printpath, sizeof (printpath), relpath);
Con_SafePrintf ("Unsupported file type \"%s\"\n", printpath);

return false;
}

/*
=================
COM_InitBaseDir
Expand Down Expand Up @@ -2832,6 +3048,49 @@ static void COM_InitBaseDir (void)
);
}

/*
=================
COM_ChooseStartArgFlavor
Checks if the supplied path belongs to the user dir
of a specific Quake flavor (original/remastered)
and adds the corresponding command-line argument if needed
=================
*/
static void COM_ChooseStartArgFlavor (const char *startarg)
{
steamgame_t steamquake;
char steampath[MAX_OSPATH];
char userdir[MAX_OSPATH];

if (Sys_GetAltUserPrefDir (true, userdir, sizeof (userdir)) && COM_MakeRelative (userdir, startarg))
{
COM_AddArg ("-prefremaster");
return;
}

if (Sys_GetAltUserPrefDir (false, userdir, sizeof (userdir)) && COM_MakeRelative (userdir, startarg))
{
COM_AddArg ("-preforiginal");
return;
}

if (Steam_FindGame (&steamquake, QUAKE_STEAM_APPID) &&
Steam_ResolvePath (steampath, sizeof (steampath), &steamquake) &&
Sys_GetSteamQuakeUserDir (userdir, sizeof (userdir), steamquake.library) &&
COM_MakeRelative (userdir, startarg))
{
COM_AddArg ("-prefremaster");
return;
}

if (Sys_GetGOGQuakeEnhancedUserDir (userdir, sizeof (userdir)) && COM_MakeRelative (userdir, startarg))
{
COM_AddArg ("-prefremaster");
return;
}
}

/*
=================
COM_InitFilesystem
Expand All @@ -2840,18 +3099,26 @@ COM_InitFilesystem
void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial
{
int i;
const char *p;
const char *p, *startarg;

Cvar_RegisterVariable (&registered);
Cvar_RegisterVariable (&cmdline);
Cmd_AddCommand ("path", COM_Path_f);
Cmd_AddCommand ("game", COM_Game_f); //johnfitz

COM_InitBaseDir ();
startarg = (com_argc == 2 && Sys_FileType (com_argv[1]) != FS_ENT_NONE) ? com_argv[1] : NULL;
if (startarg)
COM_ChooseStartArgFlavor (startarg);

if (!startarg || !COM_SetBaseDirRec (startarg))
COM_InitBaseDir ();

if (host_parms->userdir != host_parms->basedir)
COM_AddBaseDir (host_parms->userdir);

if (startarg)
COM_PatchCmdLine (startarg);

i = COM_CheckParm ("-basegame");
if (i)
{ //-basegame:
Expand Down
4 changes: 2 additions & 2 deletions Quake/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ const char *COM_Parse (const char *data);
const char *COM_ParseEx (const char *data, cpe_mode mode);


extern int com_argc;
extern char **com_argv;
extern int com_argc;
extern const char **com_argv;

extern int safemode;
/* safe mode: in true, the engine will behave as if one
Expand Down
3 changes: 0 additions & 3 deletions Quake/host.c
Original file line number Diff line number Diff line change
Expand Up @@ -1333,9 +1333,6 @@ void Host_Init (void)
if (host_parms->memsize < minimum_memory)
Sys_Error ("Only %4.1f megs of memory available, can't execute game", host_parms->memsize / (float)0x100000);

com_argc = host_parms->argc;
com_argv = host_parms->argv;

Memory_Init (host_parms->membase, host_parms->memsize);
Cbuf_Init ();
Cmd_Init ();
Expand Down
23 changes: 23 additions & 0 deletions Quake/menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,29 @@ void M_Menu_Maps_f (void)
m_state = m_maps;
m_entersound = true;
M_Maps_Init ();

// Handle optional map argument
if (Cmd_Argc() >= 2)
{
char mapname[MAX_QPATH];
size_t i;

COM_StripExtension (Cmd_Argv (1), mapname, sizeof (mapname));

for (i = 0; i < VEC_SIZE (mapsmenu.items); i++)
if (q_strcasecmp (mapname, mapsmenu.items[i].name) == 0)
break;

if (i == VEC_SIZE (mapsmenu.items))
{
Con_SafePrintf ("Couldn't find map \"%s\".\n", mapname);
return;
}

mapsmenu.list.cursor = i;
M_SetSkillMenuMap (mapname);
M_Menu_Skill_f ();
}
}

void M_Maps_Draw (void)
Expand Down
Loading

0 comments on commit cb34a1a

Please sign in to comment.