Polished and documented the manifest support a little. Feels a bit more robust now.
Try to avoid certain prediction issues with positions truncated into solid areas, that appears to happen on q3bsp and thus might happen elsewhere too. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4780 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
parent
661d64056a
commit
3998821865
9 changed files with 325 additions and 136 deletions
|
@ -947,7 +947,6 @@ void CL_BeginServerConnect(int port);
|
||||||
char *CL_TryingToConnect(void);
|
char *CL_TryingToConnect(void);
|
||||||
|
|
||||||
void CL_ExecInitialConfigs(char *defaultexec);
|
void CL_ExecInitialConfigs(char *defaultexec);
|
||||||
ftemanifest_t *CL_Manifest_Parse(vfsfile_t *file, const char *defaultsourceurl);
|
|
||||||
|
|
||||||
extern int cl_numvisedicts;
|
extern int cl_numvisedicts;
|
||||||
extern int cl_maxvisedicts;
|
extern int cl_maxvisedicts;
|
||||||
|
|
|
@ -1309,19 +1309,6 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n"));
|
||||||
cl.model_precache[i] = Mod_ForName (cl.model_name[i], MLV_SILENT);
|
cl.model_precache[i] = Mod_ForName (cl.model_name[i], MLV_SILENT);
|
||||||
else
|
else
|
||||||
cl.model_precache[i] = Mod_FindName (Mod_FixName(cl.model_name[i], cl.model_name[1]));
|
cl.model_precache[i] = Mod_FindName (Mod_FixName(cl.model_name[i], cl.model_name[1]));
|
||||||
|
|
||||||
if ((!cl.model_precache[i] || cl.model_precache[i]->type == mod_dummy) && i == 1)
|
|
||||||
{
|
|
||||||
Con_Printf ("\nThe required model file '%s' could not be found.\n\n"
|
|
||||||
, cl.model_name[i]);
|
|
||||||
Con_Printf ("You may need to download or purchase a client "
|
|
||||||
"pack in order to play on this server.\n\n");
|
|
||||||
CL_Disconnect ();
|
|
||||||
#ifdef VM_UI
|
|
||||||
UI_Reset();
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i=0; i < MAX_VWEP_MODELS; i++)
|
for (i=0; i < MAX_VWEP_MODELS; i++)
|
||||||
|
@ -1341,22 +1328,10 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n"));
|
||||||
cl.model_csqcprecache[i] = NULL;
|
cl.model_csqcprecache[i] = NULL;
|
||||||
TRACE(("dbg: R_ApplyRenderer: reloading csqc model %s\n", cl.model_csqcname[i]));
|
TRACE(("dbg: R_ApplyRenderer: reloading csqc model %s\n", cl.model_csqcname[i]));
|
||||||
cl.model_csqcprecache[i] = Mod_ForName (Mod_FixName(cl.model_csqcname[i], cl.model_name[1]), MLV_SILENT);
|
cl.model_csqcprecache[i] = Mod_ForName (Mod_FixName(cl.model_csqcname[i], cl.model_name[1]), MLV_SILENT);
|
||||||
|
|
||||||
if (!cl.model_csqcprecache[i])
|
|
||||||
{
|
|
||||||
Con_Printf ("\nThe required model file '%s' could not be found.\n\n"
|
|
||||||
, cl.model_csqcname[i]);
|
|
||||||
Con_Printf ("You may need to download or purchase a client "
|
|
||||||
"pack in order to play on this server.\n\n");
|
|
||||||
CL_Disconnect ();
|
|
||||||
#ifdef VM_UI
|
|
||||||
UI_Reset();
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//fixme: worldmodel could be ssqc or csqc.
|
||||||
cl.worldmodel = cl.model_precache[1];
|
cl.worldmodel = cl.model_precache[1];
|
||||||
|
|
||||||
if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADING)
|
if (cl.worldmodel && cl.worldmodel->loadstate == MLS_LOADING)
|
||||||
|
@ -1365,6 +1340,9 @@ TRACE(("dbg: R_ApplyRenderer: reloading ALL models\n"));
|
||||||
TRACE(("dbg: R_ApplyRenderer: done the models\n"));
|
TRACE(("dbg: R_ApplyRenderer: done the models\n"));
|
||||||
if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED)
|
if (!cl.worldmodel || cl.worldmodel->loadstate != MLS_LOADED)
|
||||||
{
|
{
|
||||||
|
// Con_Printf ("\nThe required model file '%s' could not be found.\n\n", cl.model_name[i]);
|
||||||
|
// Con_Printf ("You may need to download or purchase a client pack in order to play on this server.\n\n");
|
||||||
|
|
||||||
CL_Disconnect ();
|
CL_Disconnect ();
|
||||||
#ifdef VM_UI
|
#ifdef VM_UI
|
||||||
UI_Reset();
|
UI_Reset();
|
||||||
|
|
|
@ -339,8 +339,6 @@ void Skin_WorkerLoad(void *skinptr, void *data, size_t a, size_t b)
|
||||||
}
|
}
|
||||||
|
|
||||||
Skin_WorkerDone(skin, out, srcw, srch);
|
Skin_WorkerDone(skin, out, srcw, srch);
|
||||||
skin->loadstate = SKIN_LOADED;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -529,8 +527,12 @@ void Skin_FlushAll(void)
|
||||||
{ //wipe the skin info
|
{ //wipe the skin info
|
||||||
int i;
|
int i;
|
||||||
for (i=0 ; i<numskins ; i++)
|
for (i=0 ; i<numskins ; i++)
|
||||||
|
{
|
||||||
|
if (skins[i].loadstate==SKIN_LOADING)
|
||||||
|
COM_WorkerPartialSync(&skins[i], &skins[i].loadstate, SKIN_LOADING);
|
||||||
if (skins[i].skindata)
|
if (skins[i].skindata)
|
||||||
BZ_Free(skins[i].skindata);
|
BZ_Free(skins[i].skindata);
|
||||||
|
}
|
||||||
numskins = 0;
|
numskins = 0;
|
||||||
for (i = 0; i < MAX_CLIENTS; i++)
|
for (i = 0; i < MAX_CLIENTS; i++)
|
||||||
cl.players[i].lastskin = NULL;
|
cl.players[i].lastskin = NULL;
|
||||||
|
@ -557,8 +559,12 @@ void Skin_Skins_f (void)
|
||||||
|
|
||||||
R_GAliasFlushSkinCache(false);
|
R_GAliasFlushSkinCache(false);
|
||||||
for (i=0 ; i<numskins ; i++)
|
for (i=0 ; i<numskins ; i++)
|
||||||
|
{
|
||||||
|
if (skins[i].loadstate==SKIN_LOADING)
|
||||||
|
COM_WorkerPartialSync(&skins[i], &skins[i].loadstate, SKIN_LOADING);
|
||||||
if (skins[i].skindata)
|
if (skins[i].skindata)
|
||||||
BZ_Free(skins[i].skindata);
|
BZ_Free(skins[i].skindata);
|
||||||
|
}
|
||||||
numskins = 0;
|
numskins = 0;
|
||||||
for (i = 0; i < MAX_CLIENTS; i++)
|
for (i = 0; i < MAX_CLIENTS; i++)
|
||||||
cl.players[i].lastskin = NULL;
|
cl.players[i].lastskin = NULL;
|
||||||
|
|
|
@ -114,12 +114,12 @@ cvar_t snd_mixerthread = CVARAD( "s_mixerthread", "1",
|
||||||
"snd_mixerthread", "When enabled sound mixing will be run on a separate thread. Currently supported only by directsound. Other drivers may unconditionally thread audio. Set to 0 only if you have issues.");
|
"snd_mixerthread", "When enabled sound mixing will be run on a separate thread. Currently supported only by directsound. Other drivers may unconditionally thread audio. Set to 0 only if you have issues.");
|
||||||
cvar_t snd_device = CVARAF( "s_device", "",
|
cvar_t snd_device = CVARAF( "s_device", "",
|
||||||
"snd_device", CVAR_ARCHIVE);
|
"snd_device", CVAR_ARCHIVE);
|
||||||
cvar_t snd_device_opts = CVARFD( "_s_device_opts", "", CVAR_NOSET, "The possible audio output devices, in \"value\" \"description\" pairs.");
|
cvar_t snd_device_opts = CVARFD( "_s_device_opts", "", CVAR_NOSET, "The possible audio output devices, in \"value\" \"description\" pairs, for gamecode to read.");
|
||||||
|
|
||||||
#ifdef VOICECHAT
|
#ifdef VOICECHAT
|
||||||
static void S_Voip_Play_Callback(cvar_t *var, char *oldval);
|
static void S_Voip_Play_Callback(cvar_t *var, char *oldval);
|
||||||
cvar_t snd_voip_capturedevice = CVARF("cl_voip_capturedevice", "", CVAR_ARCHIVE);
|
cvar_t snd_voip_capturedevice = CVARF("cl_voip_capturedevice", "", CVAR_ARCHIVE);
|
||||||
cvar_t snd_voip_capturedevice_opts = CVARFD("_cl_voip_capturedevice_opts", "", CVAR_NOSET, "The possible audio capture devices, in \"value\" \"description\" pairs.");
|
cvar_t snd_voip_capturedevice_opts = CVARFD("_cl_voip_capturedevice_opts", "", CVAR_NOSET, "The possible audio capture devices, in \"value\" \"description\" pairs, for gamecode to read.");
|
||||||
int voipbutton; //+voip, no longer part of cl_voip_send to avoid it getting saved
|
int voipbutton; //+voip, no longer part of cl_voip_send to avoid it getting saved
|
||||||
cvar_t snd_voip_send = CVARFD("cl_voip_send", "0", CVAR_ARCHIVE, "Sends voice-over-ip data to the server whenever it is set.\n0: only send voice if +voip is pressed.\n1: voice activation.\n2: constantly send.\n+4: Do not send to game, only to rtp sessions.");
|
cvar_t snd_voip_send = CVARFD("cl_voip_send", "0", CVAR_ARCHIVE, "Sends voice-over-ip data to the server whenever it is set.\n0: only send voice if +voip is pressed.\n1: voice activation.\n2: constantly send.\n+4: Do not send to game, only to rtp sessions.");
|
||||||
cvar_t snd_voip_test = CVARD("cl_voip_test", "0", "If 1, enables you to hear your own voice directly, bypassing the server and thus without networking latency, but is fine for checking audio levels. Note that sv_voip_echo can be set if you want to include latency and packetloss considerations, but setting that cvar requires server admin access and is thus much harder to use.");
|
cvar_t snd_voip_test = CVARD("cl_voip_test", "0", "If 1, enables you to hear your own voice directly, bypassing the server and thus without networking latency, but is fine for checking audio levels. Note that sv_voip_echo can be set if you want to include latency and packetloss considerations, but setting that cvar requires server admin access and is thus much harder to use.");
|
||||||
|
|
|
@ -499,7 +499,8 @@ typedef struct
|
||||||
char *path; //the 'pure' name
|
char *path; //the 'pure' name
|
||||||
qboolean crcknown; //if the crc was specified
|
qboolean crcknown; //if the crc was specified
|
||||||
unsigned int crc; //the public crc
|
unsigned int crc; //the public crc
|
||||||
char *mirrors[8]; //a randomized (prioritized) list of http mirrors to use.
|
char *extractname; //if specified, this is the filename that should be extracted.
|
||||||
|
char *mirrors[8]; //a randomized (prioritized-on-load) list of http mirrors to use.
|
||||||
int mirrornum; //the index we last tried to download from, so we still work even if mirrors are down.
|
int mirrornum; //the index we last tried to download from, so we still work even if mirrors are down.
|
||||||
} package[64];
|
} package[64];
|
||||||
} ftemanifest_t;
|
} ftemanifest_t;
|
||||||
|
|
|
@ -179,6 +179,7 @@ int fs_hash_files;
|
||||||
static const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen);
|
static const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen);
|
||||||
void FS_RegisterDefaultFileSystems(void);
|
void FS_RegisterDefaultFileSystems(void);
|
||||||
static void COM_CreatePath (char *path);
|
static void COM_CreatePath (char *path);
|
||||||
|
ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, qboolean fixedbasedir);
|
||||||
|
|
||||||
#define ENFORCEFOPENMODE(mode) {if (strcmp(mode, "r") && strcmp(mode, "w")/* && strcmp(mode, "rw")*/)Sys_Error("fs mode %s is not permitted here\n");}
|
#define ENFORCEFOPENMODE(mode) {if (strcmp(mode, "r") && strcmp(mode, "w")/* && strcmp(mode, "rw")*/)Sys_Error("fs mode %s is not permitted here\n");}
|
||||||
|
|
||||||
|
@ -206,6 +207,7 @@ void FS_Manifest_Free(ftemanifest_t *man)
|
||||||
for (i = 0; i < sizeof(man->package) / sizeof(man->package[0]); i++)
|
for (i = 0; i < sizeof(man->package) / sizeof(man->package[0]); i++)
|
||||||
{
|
{
|
||||||
Z_Free(man->package[i].path);
|
Z_Free(man->package[i].path);
|
||||||
|
Z_Free(man->package[i].extractname);
|
||||||
for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++)
|
for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++)
|
||||||
Z_Free(man->package[i].mirrors[j]);
|
Z_Free(man->package[i].mirrors[j]);
|
||||||
}
|
}
|
||||||
|
@ -240,6 +242,8 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm)
|
||||||
{
|
{
|
||||||
if (oldm->package[i].path)
|
if (oldm->package[i].path)
|
||||||
newm->package[i].path = Z_StrDup(oldm->package[i].path);
|
newm->package[i].path = Z_StrDup(oldm->package[i].path);
|
||||||
|
if (oldm->package[i].extractname)
|
||||||
|
newm->package[i].extractname = Z_StrDup(oldm->package[i].extractname);
|
||||||
for (j = 0; j < sizeof(newm->package[i].mirrors) / sizeof(newm->package[i].mirrors[0]); j++)
|
for (j = 0; j < sizeof(newm->package[i].mirrors) / sizeof(newm->package[i].mirrors[0]); j++)
|
||||||
if (oldm->package[i].mirrors[j])
|
if (oldm->package[i].mirrors[j])
|
||||||
newm->package[i].mirrors[j] = Z_StrDup(oldm->package[i].mirrors[j]);
|
newm->package[i].mirrors[j] = Z_StrDup(oldm->package[i].mirrors[j]);
|
||||||
|
@ -278,13 +282,17 @@ void FS_Manifest_Print(ftemanifest_t *man)
|
||||||
{
|
{
|
||||||
if (man->package[i].path)
|
if (man->package[i].path)
|
||||||
{
|
{
|
||||||
|
if (man->package[i].extractname)
|
||||||
|
Con_Printf("achived");
|
||||||
if (man->package[i].crcknown)
|
if (man->package[i].crcknown)
|
||||||
Con_Printf("package %s 0x%x", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer)), man->package[i].crc);
|
Con_Printf("package %s 0x%x", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer)), man->package[i].crc);
|
||||||
else
|
else
|
||||||
Con_Printf("package %s -", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer)));
|
Con_Printf("package %s -", COM_QuotedString(man->package[i].path, buffer, sizeof(buffer)));
|
||||||
|
if (man->package[i].extractname)
|
||||||
|
Con_Printf(" %s", COM_QuotedString(man->package[i].extractname, buffer, sizeof(buffer)));
|
||||||
for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++)
|
for (j = 0; j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++)
|
||||||
if (man->package[i].mirrors[j])
|
if (man->package[i].mirrors[j])
|
||||||
Con_Printf(" \"%s\"", COM_QuotedString(man->package[i].mirrors[j], buffer, sizeof(buffer)));
|
Con_Printf(" %s", COM_QuotedString(man->package[i].mirrors[j], buffer, sizeof(buffer)));
|
||||||
Con_Printf("\n");
|
Con_Printf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,6 +308,8 @@ static void FS_Manifest_PurgeGamedirs(ftemanifest_t *man)
|
||||||
{
|
{
|
||||||
Z_Free(man->gamepath[i].path);
|
Z_Free(man->gamepath[i].path);
|
||||||
man->gamepath[i].path = NULL;
|
man->gamepath[i].path = NULL;
|
||||||
|
|
||||||
|
//FIXME: remove packages from the removed paths.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,16 +404,17 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!Q_strcasecmp(fname, "package"))
|
else if (!Q_strcasecmp(fname, "package") || !Q_strcasecmp(fname, "archivedpackage"))
|
||||||
{
|
{
|
||||||
qboolean crcknown;
|
qboolean crcknown;
|
||||||
int crc;
|
int crc;
|
||||||
int i, j;
|
int i, j;
|
||||||
|
int arg = 1;
|
||||||
|
|
||||||
fname = Cmd_Argv(1);
|
fname = Cmd_Argv(arg++);
|
||||||
|
|
||||||
crcknown = (strcmp(Cmd_Argv(2), "-") && *Cmd_Argv(2));
|
crcknown = (strcmp(Cmd_Argv(arg), "-") && *Cmd_Argv(arg));
|
||||||
crc = strtoul(Cmd_Argv(2), NULL, 0);
|
crc = strtoul(Cmd_Argv(arg++), NULL, 0);
|
||||||
|
|
||||||
for (i = 0; i < sizeof(man->package) / sizeof(man->package[0]); i++)
|
for (i = 0; i < sizeof(man->package) / sizeof(man->package[0]); i++)
|
||||||
{
|
{
|
||||||
|
@ -412,9 +423,13 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man)
|
||||||
man->package[i].path = Z_StrDup(fname);
|
man->package[i].path = Z_StrDup(fname);
|
||||||
man->package[i].crcknown = crcknown;
|
man->package[i].crcknown = crcknown;
|
||||||
man->package[i].crc = crc;
|
man->package[i].crc = crc;
|
||||||
for (j = 0; j < Cmd_Argc()-3 && j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++)
|
if (!Q_strcasecmp(fname, "archivedpackage"))
|
||||||
|
man->package[i].extractname = Z_StrDup(Cmd_Argv(arg++));
|
||||||
|
else
|
||||||
|
man->package[i].extractname = NULL;
|
||||||
|
for (j = 0; arg+j < Cmd_Argc() && j < sizeof(man->package[i].mirrors) / sizeof(man->package[i].mirrors[0]); j++)
|
||||||
{
|
{
|
||||||
man->package[i].mirrors[j] = Z_StrDup(Cmd_Argv(3+j));
|
man->package[i].mirrors[j] = Z_StrDup(Cmd_Argv(arg+j));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2215,6 +2230,35 @@ void COM_Gamedir (const char *dir)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
man = NULL;
|
||||||
|
if (!man)
|
||||||
|
{
|
||||||
|
vfsfile_t *f = VFSOS_Open(va("%s%s.fmf", com_gamepath, dir), "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
size_t len = VFS_GETLEN(f);
|
||||||
|
char *fdata = BZ_Malloc(len+1);
|
||||||
|
if (fdata)
|
||||||
|
{
|
||||||
|
VFS_READ(f, fdata, len);
|
||||||
|
fdata[len] = 0;
|
||||||
|
man = FS_Manifest_Parse(NULL, fdata);
|
||||||
|
BZ_Free(fdata);
|
||||||
|
}
|
||||||
|
VFS_CLOSE(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!man)
|
||||||
|
{
|
||||||
|
//generate a new manifest based upon the current one.
|
||||||
|
man = FS_ReadDefaultManifest(com_gamepath, sizeof(com_gamepath), true);
|
||||||
|
if (man && strcmp(man->installation, fs_manifest->installation))
|
||||||
|
{
|
||||||
|
FS_Manifest_Free(man);
|
||||||
|
man = NULL;
|
||||||
|
}
|
||||||
|
if (!man)
|
||||||
man = FS_Manifest_Clone(fs_manifest);
|
man = FS_Manifest_Clone(fs_manifest);
|
||||||
FS_Manifest_PurgeGamedirs(man);
|
FS_Manifest_PurgeGamedirs(man);
|
||||||
if (*dir)
|
if (*dir)
|
||||||
|
@ -2234,6 +2278,7 @@ void COM_Gamedir (const char *dir)
|
||||||
}
|
}
|
||||||
Z_Free(dup);
|
Z_Free(dup);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
FS_ChangeGame(man, cfg_reload_on_gamedir.ival);
|
FS_ChangeGame(man, cfg_reload_on_gamedir.ival);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2250,7 +2295,7 @@ void COM_Gamedir (const char *dir)
|
||||||
#define Q2CFG "com_nogamedirnativecode 0\n"
|
#define Q2CFG "com_nogamedirnativecode 0\n"
|
||||||
/*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/
|
/*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/
|
||||||
#define Q3CFG "gl_overbright 2\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_nogamedirnativecode 0\n"
|
#define Q3CFG "gl_overbright 2\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_nogamedirnativecode 0\n"
|
||||||
#define RMQCFG "sv_bigcoords 1\n"
|
//#define RMQCFG "sv_bigcoords 1\n"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *argname; //used if this was used as a parameter.
|
const char *argname; //used if this was used as a parameter.
|
||||||
|
@ -2282,13 +2327,13 @@ const gamemode_info_t gamemode_info[] = {
|
||||||
//various quake-based mods.
|
//various quake-based mods.
|
||||||
{"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"}, "Nexuiz"},
|
{"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"}, "Nexuiz"},
|
||||||
{"-xonotic", "xonotic", "Xonotic", {"xonotic.exe"}, NEXCFG, {"data", "*ftedata"}, "Xonotic"},
|
{"-xonotic", "xonotic", "Xonotic", {"xonotic.exe"}, NEXCFG, {"data", "*ftedata"}, "Xonotic"},
|
||||||
{"-spark", "spark", "Spark", {"base/src/progs.src",
|
// {"-spark", "spark", "Spark", {"base/src/progs.src",
|
||||||
"base/qwprogs.dat",
|
// "base/qwprogs.dat",
|
||||||
"base/pak0.pak"}, DMFCFG, {"base", }, "Spark"},
|
// "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"},
|
||||||
{"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src",
|
// {"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src",
|
||||||
"basesj/progs.dat",
|
// "basesj/progs.dat",
|
||||||
"basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"},
|
// "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"},
|
||||||
{"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte"}, "Remake Quake"},
|
// {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte"}, "Remake Quake"},
|
||||||
|
|
||||||
#ifdef HEXEN2
|
#ifdef HEXEN2
|
||||||
//hexen2's mission pack generally takes precedence if both are installed.
|
//hexen2's mission pack generally takes precedence if both are installed.
|
||||||
|
@ -2305,21 +2350,22 @@ const gamemode_info_t gamemode_info[] = {
|
||||||
#if defined(Q3CLIENT) || defined(Q3SERVER)
|
#if defined(Q3CLIENT) || defined(Q3SERVER)
|
||||||
{"-quake3", "q3", "FTE-Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena"},
|
{"-quake3", "q3", "FTE-Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena"},
|
||||||
//the rest are not supported in any real way. maps-only mostly, if that
|
//the rest are not supported in any real way. maps-only mostly, if that
|
||||||
{"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"},
|
// {"-quake4", "q4", "FTE-Quake4", {"q4base/pak00.pk4"}, NULL, {"q4base", "*fteq4"}, "Quake 4"},
|
||||||
{"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"},
|
// {"-et", NULL, "FTE-EnemyTerritory", {"etmain/pak0.pk3"}, NULL, {"etmain", "*fteet"}, "Wolfenstein - Enemy Territory"},
|
||||||
|
|
||||||
{"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"},
|
// {"-jk2", "jk2", "FTE-JK2", {"base/assets0.pk3"}, NULL, {"base", "*ftejk2"}, "Jedi Knight II: Jedi Outcast"},
|
||||||
{"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"},
|
// {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"},
|
||||||
#endif
|
#endif
|
||||||
#if !defined(QUAKETC) && !defined(MINIMAL)
|
#if !defined(QUAKETC) && !defined(MINIMAL)
|
||||||
{"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"}, "Doom"},
|
// {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"}, "Doom"},
|
||||||
{"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"}, "Doom2"},
|
// {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"}, "Doom2"},
|
||||||
{"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"},
|
// {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"},
|
||||||
|
|
||||||
//for the luls
|
//for the luls
|
||||||
{"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"},
|
// {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"},
|
||||||
|
#endif
|
||||||
//can run in windows, needs hl gamecode enabled.
|
#if defined(HLSERVER) || defined(HLCLIENT)
|
||||||
|
//can run in windows, needs hl gamecode enabled. maps can always be viewed, but meh.
|
||||||
{"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, NULL, {"valve", "*ftehl"}, "Half-Life"},
|
{"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, NULL, {"valve", "*ftehl"}, "Half-Life"},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -3316,10 +3362,8 @@ static int FS_IdentifyDefaultGame(char *newbase, int sizeof_newbase, qboolean fi
|
||||||
return gamenum;
|
return gamenum;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
|
||||||
|
|
||||||
//allowed to modify newbasedir if fixedbasedir isn't set
|
//allowed to modify newbasedir if fixedbasedir isn't set
|
||||||
ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir, qboolean fixedbasedir, int game)
|
static ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir, qboolean fixedbasedir, int game)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
ftemanifest_t *man;
|
ftemanifest_t *man;
|
||||||
|
@ -3383,13 +3427,13 @@ ftemanifest_t *FS_GenerateLegacyManifest(char *newbasedir, int sizeof_newbasedir
|
||||||
static char *FS_RelativeURL(char *base, char *file, char *buffer, int bufferlen)
|
static char *FS_RelativeURL(char *base, char *file, char *buffer, int bufferlen)
|
||||||
{
|
{
|
||||||
//fixme: cope with windows paths
|
//fixme: cope with windows paths
|
||||||
qboolean baseisurl = !!strchr(base, ':');
|
qboolean baseisurl = base?!!strchr(base, ':'):false;
|
||||||
qboolean fileisurl = !!strchr(file, ':');
|
qboolean fileisurl = !!strchr(file, ':');
|
||||||
//qboolean baseisabsolute = (*base == '/' || *base == '\\');
|
//qboolean baseisabsolute = (*base == '/' || *base == '\\');
|
||||||
qboolean fileisabsolute = (*file == '/' || *file == '\\');
|
qboolean fileisabsolute = (*file == '/' || *file == '\\');
|
||||||
char *ebase;
|
char *ebase;
|
||||||
|
|
||||||
if (fileisurl)
|
if (fileisurl || !base)
|
||||||
return file;
|
return file;
|
||||||
if (fileisabsolute)
|
if (fileisabsolute)
|
||||||
{
|
{
|
||||||
|
@ -3415,6 +3459,7 @@ static char *FS_RelativeURL(char *base, char *file, char *buffer, int bufferlen)
|
||||||
|
|
||||||
#ifdef WEBCLIENT
|
#ifdef WEBCLIENT
|
||||||
static struct dl_download *curpackagedownload;
|
static struct dl_download *curpackagedownload;
|
||||||
|
static char fspdl_internalname[MAX_QPATH];
|
||||||
static char fspdl_temppath[MAX_OSPATH];
|
static char fspdl_temppath[MAX_OSPATH];
|
||||||
static char fspdl_finalpath[MAX_OSPATH];
|
static char fspdl_finalpath[MAX_OSPATH];
|
||||||
static void FS_BeginNextPackageDownload(void);
|
static void FS_BeginNextPackageDownload(void);
|
||||||
|
@ -3431,11 +3476,62 @@ static void FS_PackageDownloaded(struct dl_download *dl)
|
||||||
{
|
{
|
||||||
//rename the file as needed.
|
//rename the file as needed.
|
||||||
COM_CreatePath(fspdl_finalpath);
|
COM_CreatePath(fspdl_finalpath);
|
||||||
|
|
||||||
|
if (*fspdl_internalname) //if zip...
|
||||||
|
{ //archive
|
||||||
|
searchpathfuncs_t *archive = FSZIP_LoadArchive(VFSOS_Open(fspdl_temppath, "rb"), dl->url);
|
||||||
|
if (archive)
|
||||||
|
{
|
||||||
|
flocation_t loc;
|
||||||
|
int status = FF_NOTFOUND;
|
||||||
|
//FIXME: loop through all packages to extract all of them as appropriate
|
||||||
|
if (status == FF_NOTFOUND)
|
||||||
|
status = archive->FindFile(archive, &loc, fspdl_internalname, NULL);
|
||||||
|
if (status == FF_NOTFOUND)
|
||||||
|
status = archive->FindFile(archive, &loc, COM_SkipPath(fspdl_internalname), NULL);
|
||||||
|
|
||||||
|
if (status == FF_FOUND)
|
||||||
|
{
|
||||||
|
vfsfile_t *in = archive->OpenVFS(archive, &loc, "rb");
|
||||||
|
if (in)
|
||||||
|
{
|
||||||
|
vfsfile_t *out = VFSOS_Open(fspdl_finalpath, "wb");
|
||||||
|
qofs_t remaining = VFS_GETLEN(in);
|
||||||
|
size_t count;
|
||||||
|
if (out)
|
||||||
|
{
|
||||||
|
char buf[65536];
|
||||||
|
while (remaining)
|
||||||
|
{
|
||||||
|
if (remaining < sizeof(buf))
|
||||||
|
count = remaining;
|
||||||
|
else
|
||||||
|
count = sizeof(buf);
|
||||||
|
VFS_READ(in, buf, count);
|
||||||
|
VFS_WRITE(out, buf, count);
|
||||||
|
remaining -= count;
|
||||||
|
}
|
||||||
|
VFS_CLOSE(out);
|
||||||
|
}
|
||||||
|
VFS_CLOSE(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Con_Printf("Unable to find %s in %s\n", fspdl_internalname, fspdl_temppath);
|
||||||
|
}
|
||||||
|
archive->ClosePath(archive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//direct file
|
||||||
if (!Sys_Rename(fspdl_temppath, fspdl_finalpath))
|
if (!Sys_Rename(fspdl_temppath, fspdl_finalpath))
|
||||||
{
|
{
|
||||||
Con_Printf("Unable to rename \"%s\" to \"%s\"\n", fspdl_temppath, fspdl_finalpath);
|
Con_Printf("Unable to rename \"%s\" to \"%s\"\n", fspdl_temppath, fspdl_finalpath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Sys_remove (fspdl_temppath);
|
Sys_remove (fspdl_temppath);
|
||||||
|
|
||||||
fs_restarts++;
|
fs_restarts++;
|
||||||
|
@ -3481,6 +3577,11 @@ static void FS_BeginNextPackageDownload(void)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (man->package[j].extractname)
|
||||||
|
Q_strncpyz(fspdl_internalname, man->package[j].extractname, sizeof(fspdl_internalname));
|
||||||
|
else
|
||||||
|
Q_strncpyz(fspdl_internalname, "", sizeof(fspdl_internalname));
|
||||||
|
|
||||||
//figure out a temp name and figure out where we're going to get it from.
|
//figure out a temp name and figure out where we're going to get it from.
|
||||||
FS_NativePath(buffer, FS_ROOT, fspdl_finalpath, sizeof(fspdl_finalpath));
|
FS_NativePath(buffer, FS_ROOT, fspdl_finalpath, sizeof(fspdl_finalpath));
|
||||||
if (!FS_GenCachedPakName(va("%s.tmp", man->package[j].path), crcstr, buffer, sizeof(buffer)))
|
if (!FS_GenCachedPakName(va("%s.tmp", man->package[j].path), crcstr, buffer, sizeof(buffer)))
|
||||||
|
@ -3615,6 +3716,42 @@ qboolean FS_FoundManifest(void *usr, ftemanifest_t *man)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//reads the default manifest based upon the basedir, the commandline arguments, the name of the exe, etc.
|
||||||
|
//may still fail if no game was identified.
|
||||||
|
//if fixedbasedir is true, stuff like -quake won't override/change the active basedir (ie: -basedir or gamedir switching without breaking gamedir)
|
||||||
|
ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedirsize, qboolean fixedbasedir)
|
||||||
|
{
|
||||||
|
ftemanifest_t *man = NULL;
|
||||||
|
|
||||||
|
vfsfile_t *f;
|
||||||
|
#ifdef BRANDING_NAME
|
||||||
|
f = VFSOS_Open(va("%s"STRINGIFY(BRANDING_NAME)".fmf", newbasedir), "rb");
|
||||||
|
if (!f)
|
||||||
|
#endif
|
||||||
|
f = VFSOS_Open(va("%sdefault.fmf", newbasedir), "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
size_t len = VFS_GETLEN(f);
|
||||||
|
char *fdata = BZ_Malloc(len+1);
|
||||||
|
if (fdata)
|
||||||
|
{
|
||||||
|
VFS_READ(f, fdata, len);
|
||||||
|
fdata[len] = 0;
|
||||||
|
man = FS_Manifest_Parse(NULL, fdata);
|
||||||
|
BZ_Free(fdata);
|
||||||
|
}
|
||||||
|
VFS_CLOSE(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!man)
|
||||||
|
{
|
||||||
|
int game = FS_IdentifyDefaultGame(newbasedir, newbasedirsize, fixedbasedir);
|
||||||
|
if (game != -1)
|
||||||
|
man = FS_GenerateLegacyManifest(newbasedir, newbasedirsize, fixedbasedir, game);
|
||||||
|
}
|
||||||
|
return man;
|
||||||
|
}
|
||||||
|
|
||||||
//this is potentially unsafe. needs lots of testing.
|
//this is potentially unsafe. needs lots of testing.
|
||||||
qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs)
|
qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs)
|
||||||
{
|
{
|
||||||
|
@ -3649,35 +3786,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs)
|
||||||
if (fs_manifest)
|
if (fs_manifest)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!man)
|
man = FS_ReadDefaultManifest(newbasedir, sizeof(newbasedir), fixedbasedir);
|
||||||
{
|
|
||||||
vfsfile_t *f;
|
|
||||||
#ifdef BRANDING_NAME
|
|
||||||
f = VFSOS_Open(va("%s"STRINGIFY(BRANDING_NAME)".fmf", newbasedir), "rb");
|
|
||||||
if (!f)
|
|
||||||
#endif
|
|
||||||
f = VFSOS_Open(va("%sdefault.fmf", newbasedir), "rb");
|
|
||||||
if (f)
|
|
||||||
{
|
|
||||||
size_t len = VFS_GETLEN(f);
|
|
||||||
char *fdata = BZ_Malloc(len+1);
|
|
||||||
if (fdata)
|
|
||||||
{
|
|
||||||
VFS_READ(f, fdata, len);
|
|
||||||
fdata[len] = 0;
|
|
||||||
man = FS_Manifest_Parse(NULL, fdata);
|
|
||||||
BZ_Free(fdata);
|
|
||||||
}
|
|
||||||
VFS_CLOSE(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!man)
|
|
||||||
{
|
|
||||||
int game = FS_IdentifyDefaultGame(newbasedir, sizeof(newbasedir), fixedbasedir);
|
|
||||||
if (game != -1)
|
|
||||||
man = FS_GenerateLegacyManifest(newbasedir, sizeof(newbasedir), fixedbasedir, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!man && isDedicated)
|
if (!man && isDedicated)
|
||||||
{ //dedicated servers have no menu code, so just pick the first fmf we could find.
|
{ //dedicated servers have no menu code, so just pick the first fmf we could find.
|
||||||
|
@ -4093,33 +4202,6 @@ void FS_ShowManifest_f(void)
|
||||||
else
|
else
|
||||||
Con_Printf("no manifest loaded...\n");
|
Con_Printf("no manifest loaded...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int FS_ThreadTest(void*arg)
|
|
||||||
{
|
|
||||||
vfsfile_t *f = NULL;
|
|
||||||
char startdata[256];
|
|
||||||
char lastdata[256];
|
|
||||||
while(!f)
|
|
||||||
{
|
|
||||||
if (Sys_LockMutex(fs_thread_mutex))
|
|
||||||
{
|
|
||||||
f = FS_OpenVFS("gfx/pop.lmp", "rb", FS_GAME);
|
|
||||||
Sys_UnlockMutex(fs_thread_mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VFS_READ(f, startdata, sizeof(startdata));
|
|
||||||
VFS_CLOSE(f);
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
Sys_LockMutex(fs_thread_mutex);
|
|
||||||
f = FS_OpenVFS("gfx/pop.lmp", "rb", FS_GAME);
|
|
||||||
Sys_UnlockMutex(fs_thread_mutex);
|
|
||||||
VFS_READ(f, lastdata, sizeof(lastdata));
|
|
||||||
VFS_CLOSE(f);
|
|
||||||
if (memcmp(startdata, lastdata, sizeof(lastdata)))
|
|
||||||
Con_Printf("FS_ThreadTest failed\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
================
|
================
|
||||||
COM_InitFilesystem
|
COM_InitFilesystem
|
||||||
|
|
|
@ -1274,6 +1274,9 @@ searchpathfuncs_t *QDECL FSZIP_LoadArchive (vfsfile_t *packhandle, const char *d
|
||||||
zipfile_t *zip;
|
zipfile_t *zip;
|
||||||
struct zipinfo info;
|
struct zipinfo info;
|
||||||
|
|
||||||
|
if (!packhandle)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
zip = Z_Malloc(sizeof(zipfile_t));
|
zip = Z_Malloc(sizeof(zipfile_t));
|
||||||
Q_strncpyz(zip->filename, desc, sizeof(zip->filename));
|
Q_strncpyz(zip->filename, desc, sizeof(zip->filename));
|
||||||
zip->raw = packhandle;
|
zip->raw = packhandle;
|
||||||
|
|
|
@ -863,7 +863,7 @@ void PM_CategorizePosition (void)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trace = PM_PlayerTracePortals (pmove.origin, point, MASK_PLAYERSOLID, NULL);
|
trace = PM_PlayerTracePortals (pmove.origin, point, MASK_PLAYERSOLID, NULL);
|
||||||
if (trace.fraction == 1 || -DotProduct(pmove.gravitydir, trace.plane.normal) < MIN_STEP_NORMAL)
|
if (!trace.startsolid && (trace.fraction == 1 || -DotProduct(pmove.gravitydir, trace.plane.normal) < MIN_STEP_NORMAL))
|
||||||
pmove.onground = false;
|
pmove.onground = false;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
120
specs/fte_manifests.txt
Normal file
120
specs/fte_manifests.txt
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
Manifest files are small files that describe various attributes of the game, primarily branding and filesystem configuration for mods or standalone games.
|
||||||
|
Manifest files are useful for standalone games that need to distance themselves from other games using the same engine. They are also useful for auto-updating mods or for mod compilations where mods should not be downloaded up-front.
|
||||||
|
|
||||||
|
FTE Manifest Files (.fmf) can either be invoked by opening them with FTE (set up a file association), or automatically via a default.fmf file in the path specified by the -basedir argument, or in the working directory (settable via shortcuts). A default.fmf file will override the engine's game determination routines completely.
|
||||||
|
These files are also supported by the Android port, where opening one will give an option to open with FTEDroid automatically (much like on a desktop OS), or in the webgl port with the url specified after a # in the url.
|
||||||
|
The gamedir command (eg: 'gamedir foo', or '-game foo' on the commandline) will attempt to load an fmf file with the specified name (ie: foo.fmf) from the current basedir. If the new manifest has a different 'game' value, the engine may ignore.
|
||||||
|
|
||||||
|
Each line of a manifest file forms a single command. New lines denote new commands. Comments are C++-style // double forward slashes.
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
//example manifest file for Sock's 'In The Shadows' stealth mod. Because why not?
|
||||||
|
FTEMANIFEST 1
|
||||||
|
GAME quake
|
||||||
|
NAME "In The Shadows"
|
||||||
|
PROTOCOLNAME ITShadows
|
||||||
|
DEFAULTEXEC ""
|
||||||
|
//UPDATEURL "http://example.com/mods/its.fmf"
|
||||||
|
BASEGAME id1
|
||||||
|
BASEGAME qw
|
||||||
|
BASEGAME *fte
|
||||||
|
GAMEDIR shadows
|
||||||
|
//it would be nice if we could specify pak0.pak and pak1.pak here, alas doing so would violate id's various copyrights. instead, hope the user has a copy already installed.
|
||||||
|
//this is a major pain for systems where there is no official installer (read: fte's webgl port, or android... or win64).
|
||||||
|
//its pak0 can be downloaded from sock's public server
|
||||||
|
ARCHIVEDPACKAGE shadows/pak0.pak "0xb1768147" "shadows/pak0.pak" "http://simonoc.com/files/maps/sp/its_demo_v1_1.zip"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
FTEMANIFEST 1
|
||||||
|
Specifies the actual version of the manifest file, and what the file actually is. Only valid as the first line. The version number is included for potential future use. Version 1 is the current version.
|
||||||
|
|
||||||
|
GAME quake
|
||||||
|
This specifies the code-name of the game upon which your mod is based.
|
||||||
|
Standalone games should pick their own string to use here.
|
||||||
|
Mods for one of the games that FTE intrinsically understands may be automatically located via the system registry for an existing installation. This is useful when the fmf file has been downloaded and opened via a web browser.
|
||||||
|
Known game name (namely: quake, quake2, quake3, hexen2) may imply other settings if they were not otherwise specified.
|
||||||
|
It is NOT recommended to omit this, but if you do, 'quake' will be assumed.
|
||||||
|
|
||||||
|
NAME "Example Quake Mod"
|
||||||
|
This specifies what the game should be referred to by various console prints etc. This should be the formal name of your mod.
|
||||||
|
|
||||||
|
PROTOCOLNAME "foo"
|
||||||
|
This is how the game/mod should be identified to master servers. A unique value here helps prevent servers from other games being listed, as well as preventing other games from seeing this game/mod.
|
||||||
|
FIXME: engine support for this should be improved.
|
||||||
|
|
||||||
|
DEFAULTEXEC ""
|
||||||
|
If specified, this is inserted before the game's normal default.cfg file, and can be used to override the engine's default cvar settings for mods where the default.cfg was originally written for a different engine (and thus omits certain settings). This should only be used for games where the default.cfg itself is not replacable. Standalone mods should not need to specify this, and should instead just put everything in the mod's default.cfg file.
|
||||||
|
For recognised games, the engine will automatically assume usable defaults, but its possible that a mod depends upon a setting that I overlooked.
|
||||||
|
|
||||||
|
BASEGAME "id1"
|
||||||
|
Multiple basegame lines can be specified. These are the core subdirectories that should always be loaded, even if the gamedir command was used to load a different mod.
|
||||||
|
Standadlone games will typically specify only a single value.
|
||||||
|
Each additional basegame will be favoured in preference to the prior ones.
|
||||||
|
(note: 'basedir' refers to the path from which all game subdirs are found in, thus manifests use basegame to avoid ambiguity at the cost of consistancy)
|
||||||
|
A basedir with a leading * is a private directory that will not be reported to clients nor appear in server browsers. This is typically used on fte-specific game directories, so that other-engine clients will not get confused.
|
||||||
|
|
||||||
|
GAMEDIR "mymod"
|
||||||
|
These act almost exactly like basegame, except they flushed and replaced if the gamedir command is used. Additionally these are never implied by the 'game' command.
|
||||||
|
These will always have a higher preference than any BASEGAME subdir.
|
||||||
|
There are arguments both for and against a mod specifying its subdir as a basegame or as just a gamedir.
|
||||||
|
|
||||||
|
DISABLEHOMEDIR 1
|
||||||
|
If non-zero, the game/mod will not attempt to use any directory within the user's homedir by default. This may cause issues if the base game was installed in some read-only system location like C:\Program Files\, and so should only be used for manually installed games (ones that contain just the exe+fmf file).
|
||||||
|
|
||||||
|
UPDATEURL "http://example.com/foo.fmf"
|
||||||
|
This command gives the url from which to update the manifest file from. The updated manifest *MUST* contain the same url for the update to be accepted. Because of this, you should ensure that it is has a url that will be valid for as long as your mod will be relevent.
|
||||||
|
This replaces just the manifest, but because the manifest file specifies a list of packages that should be used, a new set of packages can be downloaded automatically, and this is the true power of the update url.
|
||||||
|
|
||||||
|
PACKAGE "id1/pak0.pak" 0x4f069cac "http://mirror1.example.com/qsw106_pak0.pak" "http://mirror2.example.com/qsw106_pak0.pak"
|
||||||
|
WARNING: be sure to respect third-party copyrights.
|
||||||
|
This command, of which multiple can be specified, lists the packages which should automatically be downloaded.
|
||||||
|
The first name (in this case 'id1/pak0.pak') is the logical name of the package, including the gamedir in which it is to be found. The second argument is the CRC of the package's table of contents, and any additional arguments are alternative mirrors from which to download updated versions. The engine will randomize the order on load (for load balancing) and will try other mirrors if the any fails to provide a file.
|
||||||
|
Multiple versions of the same package may be installed. This is handled via the crc. If a mirror provides a file who's crc does not match, the download will be ignored and another mirror will be tried.
|
||||||
|
A CRC of '-' can be used if you really do not want to provide crcs. This suppresses all crc checks, and so is not recommended (if you instead use a different filename for each version, this is not always an issue). The CRC can be determined by using the wrong crc and fixing it up to the correct value that the engine claims that it should be.
|
||||||
|
If the user already has an actual id1/pak0.pak installed (ie: a prior install of quake), then an updated version will not be downloaded even if their crc does not match. Otherwise the downloaded file will be placed inside a 'dlcache' directory, with a crc postfix, allowing multiple versions.
|
||||||
|
If you are directly serving pk3 files, it is recommended to just compress the pk3.
|
||||||
|
If you are directly serving pak files, you will likely wish to ensure that gzip transfer compression is enabled on your mirrors for the files in question.
|
||||||
|
Its recommended to place pk3 files directly on your mirrors as they are already compressed, which keeps things consistant and avoids stalls. However, if you use a single installation zip that you wish to reuse with manifest files, you can use ARCHIVEDPACKAGE instead.
|
||||||
|
|
||||||
|
ARCHIVEDPACKAGE "id1/pak0.pak" 0x4f069cac "id1/pak0.pak" "http://mirror1.example.com/qsw106.zip" "http://mirror2.example.com/qsw106.zip"
|
||||||
|
Mostly identical to the PACKAGE command, but with an extra argument before the mirror list.
|
||||||
|
The difference is that if the file is downloaded, it is assumed to be a zip archive from which to extract the package. The extra/third argument is the filename that is read from the zip.
|
||||||
|
This command is provided for compatibility with existing installation media from third parties who do not themselves support manifest files.
|
||||||
|
|
||||||
|
MINVER FTE 4778
|
||||||
|
The manifest should not be used if the engine parsing it is a lower version than this. There is no default limit.
|
||||||
|
The first arg (ie: 'fte' in this case) specifies that the limit is specific to a specific fork or reimplementation of the file format.
|
||||||
|
The second arg should be an svn revision number (visible from the 'version' console command).
|
||||||
|
When updating a manifest, the engine will ignore the new manifest if its minver is too high. This can be used to avoid breaking installations if the user tries running the mod with too old an engine.
|
||||||
|
|
||||||
|
MAXVER FTE 4778
|
||||||
|
The manifest should not be used if the engine parsing it is a higher version than this. See minver for argument info.
|
||||||
|
I'm not sure how useful this will actually be, but it is included for completeness.
|
||||||
|
|
||||||
|
|
||||||
|
Console Commands:
|
||||||
|
/gamedir foo
|
||||||
|
This console command will read foo.fmf from the current basedir and load that instead. The basedir will not be changed to match the installation path of the specified game.
|
||||||
|
If there was no fmf file with that name, it will load the default.fmf file from that basedir instead, or try to guess which game is installed in the current basedir instead. From this default.fmf file, the gamedir attributes will be stripped and 'GAMEDIR foo' will be inserted internally.
|
||||||
|
/fs_showmanifest
|
||||||
|
This console command displays the effective/active manifest file that is currently in effect.
|
||||||
|
/fs_changegame
|
||||||
|
This command recognises various game names and will switch the basedir in order to try loading it (the game may be found via a prompt for the game's basedir, via a cached result of that prompt, or via known registry keys from that game's installer).
|
||||||
|
If the specified game is not recognised, the argument will be assumed to be a manifest named via a url or a system path.
|
||||||
|
|
||||||
|
Possible Future Direction:
|
||||||
|
ARCHIVEDPACKAGE should support multiple files without redownloading.
|
||||||
|
ARCHIVEDPACKAGE needs some way to not care about crcs/filenames, and extract+use all included files. May be better to just decompress the zip and directly use it with a path prefix.
|
||||||
|
Uninstallation/auto-pruning.
|
||||||
|
Extra polish.
|
||||||
|
Multiple mods specified within a single fmf.
|
||||||
|
Individual files (ie: maps) that can be downloaded on demand?
|
||||||
|
Other stuff.
|
||||||
|
Scan for a new manifest instead of merely switching gamedirs.
|
||||||
|
Readme/license/credits info is needed.
|
Loading…
Reference in a new issue