Add 'schemes' value to fmf files, for games to list (multiple) uri schemes with which to easily start the engine and connect to a specified server. Also add cl_verify_urischeme, a bit like ezquake has.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6021 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2021-08-19 06:01:42 +00:00
parent f0aea54e5d
commit cf31dcccad
12 changed files with 488 additions and 147 deletions

View File

@ -123,6 +123,7 @@ cvar_t cl_demospeed = CVARF("cl_demospeed", "1", 0);
cvar_t cl_demoreel = CVARFD("cl_demoreel", "0", CVAR_SAVE, "When enabled, the engine will begin playing a demo loop on startup.");
cvar_t cl_loopbackprotocol = CVARD("cl_loopbackprotocol", "qw", "Which protocol to use for single-player/the internal client. Should be one of: qw, qwid, nqid, nq, fitz, bjp3, dp6, dp7, auto. If 'auto', will use qw protocols for qw mods, and nq protocols for nq mods.");
static cvar_t cl_verify_urischeme = CVARAFD("cl_verify_urischeme", "0", "cl_verify_qwprotocol"/*ezquake, inappropriate for misc schemes*/, CVAR_NOSAVE/*checked at startup, so its only really default.cfg that sets it*/, "0: Do nothing.\n1: Check whether our protocol scheme is registered and prompt the user to register associations.\n2: Always re-register on every startup, without prompting. Sledgehammer style.");
cvar_t cl_threadedphysics = CVARD("cl_threadedphysics", "0", "When set, client input frames are generated and sent on a worker thread");
@ -5006,6 +5007,8 @@ void CL_Init (void)
#ifndef SERVERONLY
Cvar_Register (&cl_loopbackprotocol, cl_controlgroup);
#endif
Cvar_Register (&cl_verify_urischeme, cl_controlgroup);
Cvar_Register (&cl_countpendingpl, cl_controlgroup);
Cvar_Register (&cl_threadedphysics, cl_controlgroup);
hud_tracking_show = Cvar_Get("hud_tracking_show", "1", 0, "statusbar");
@ -6003,18 +6006,62 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file)
else
#endif
{
if (nlen >= 5 && !strncmp(fname, "qw://", 5))
const char *schemeend = strstr(fname, "://");
if (schemeend)
{ //this is also implemented by ezquake, so be careful here...
//"qw://[stream@]host[:port]/COMMAND" join, spectate, qtvplay
//examples:
// "quake2://broker:port"
// "quake2:rtc://broker:port/game"
// "qw://[stream@]host[:port]/COMMAND" join, spectate, qtvplay
//we'll chop off any non-auth prefix, its just so we can handle multiple protocols via a single uri scheme.
char *t, *cmd;
const char *url;
char buffer[8192];
t = Z_Malloc(nlen+1);
memcpy(t, fname, nlen);
t[nlen] = 0;
url = t+5;
const char *schemestart = strchr(fname, ':');
int schemelen, urilen;
for (cmd = t+5; *cmd; cmd++)
//if its one of our explicit protocols then use the url as-is
const char *netschemes[] = {"udp", "udp4", "udp6", "ipx", "tcp", "tcp4", "tcp6", "spx", "ws", "wss", "tls", "dtls", "ice", "rtc", "ices", "rtcs", "irc", "udg", "unix"};
int i;
size_t slen;
if (!schemestart || schemestart==schemeend)
schemestart = fname;
else
schemestart++;
schemelen = schemeend-schemestart;
urilen = nlen-(schemestart-fname);
for (i = 0; i < countof(netschemes); i++)
{
slen = strlen(netschemes[i]);
if (schemelen == slen && !strncmp(schemestart, netschemes[i], slen))
{
char quoted[8192];
char *t = Z_Malloc(urilen+1);
memcpy(t, schemestart, urilen);
t[urilen] = 0;
Cbuf_AddText(va("connect %s\n", COM_QuotedString(t, quoted, sizeof(quoted), false)), RESTRICT_LOCAL);
if(file)
VFS_CLOSE(file);
Z_Free(t);
return true;
}
}
schemelen++;
if (!strncmp(schemestart+schemelen, "//", schemelen))
schemelen+=2;
t = Z_Malloc(urilen+1);
memcpy(t, schemestart, urilen);
t[urilen] = 0;
url = t+schemelen;
for (cmd = t+schemelen; *cmd; cmd++)
{
if (*cmd == '/')
{
@ -6043,25 +6090,6 @@ qboolean Host_RunFile(const char *fname, int nlen, vfsfile_t *file)
Z_Free(t);
return true;
}
{
const char *netschemes[] = {"udp://", "udp4//", "udp6//", "ipx://", "tcp://", "tcp4//", "tcp6//", "spx://", "ws://", "wss://", "tls://", "dtls://", "ice://", "rtc://", "ices://", "rtcs://", "irc://", "udg://", "unix://"};
int i;
size_t slen;
for (i = 0; i < countof(netschemes); i++)
{
slen = strlen(netschemes[i]);
if (nlen >= slen && !strncmp(fname, netschemes[i], slen))
{
char quoted[8192];
char *t = Z_Malloc(nlen+1);
memcpy(t, fname, nlen);
t[nlen] = 0;
Cbuf_AddText(va("connect %s\n", COM_QuotedString(t, quoted, sizeof(quoted), false)), RESTRICT_LOCAL);
Z_Free(t);
}
}
}
}
f = Z_Malloc(sizeof(*f) + nlen);
@ -6762,7 +6790,11 @@ void CL_ExecInitialConfigs(char *resetcommand)
Ruleset_Scan();
}
static void Host_URIPrompt(void *ctx, promptbutton_t btn)
{
if (btn == PROMPT_YES)
Cbuf_AddText ("\nsys_register_file_associations\n", RESTRICT_LOCAL);
}
void Host_FinishLoading(void)
{
@ -6846,6 +6878,22 @@ void Host_FinishLoading(void)
}
else //3 flags for a renderer restart
Renderer_Start();
if (fs_manifest->schemes && Cmd_IsCommand("sys_register_file_associations"))
{
if (cl_verify_urischeme.ival >= 2)
Cbuf_AddText ("\nsys_register_file_associations\n", RESTRICT_LOCAL);
else if (cl_verify_urischeme.ival)
{
char *scheme = Sys_URIScheme_NeedsRegistering();
if (scheme)
{
Menu_Prompt(Host_URIPrompt, NULL, va("The URI scheme %s:// is not configured.\nRegister now?", scheme), "Register", NULL, "No");
Z_Free(scheme);
}
}
}
}
/*

View File

@ -1018,6 +1018,10 @@ qboolean Sys_GetFreeDiskSpace(const char *path, quint64_t *freespace)
void Sys_SendKeyEvents(void)
{
}
char *Sys_URIScheme_NeedsRegistering(void)
{ //android does its mime/etc registrations via android xml junk. dynamically registering stuff isn't supported, so pretend that its already registered to avoid annoying prompts.
return NULL;
}
void Sys_Init(void)
{
Cvar_Register(&sys_keepscreenon, "android stuff");

View File

@ -289,11 +289,85 @@ void Sys_Quit (void)
exit(0);
}
char *Sys_URIScheme_NeedsRegistering(void)
{
qboolean found;
qofs_t insize;
char *in;
char confbase[MAX_OSPATH];
char scheme[64];
const char *schemes = fs_manifest->schemes;
schemes=COM_ParseOut(schemes, scheme, sizeof(scheme));
if (!schemes)
return NULL;
{ //user
const char *config = getenv("XDG_CONFIG_HOME");
const char *home = getenv("HOME");
if (config && *config)
Q_strncpyz(confbase, config, sizeof(confbase));
else
{
if (home && *home)
Q_snprintfz(confbase, sizeof(confbase), "%s/.config", home);
else
return NULL; //can't register anyway, just pretend its registered.
}
}
//check if the scheme is registered or not.
in = FS_MallocFile(va("%s/mimeapps.list", confbase), FS_SYSTEM, &insize);
if (in)
{
do
{
qboolean inadded = false;
char *l = in;
const char *schemeline = va("x-scheme-handler/%s=fte-%s.desktop;", scheme, fs_manifest->installation);
size_t schemelinelen = strlen(schemeline);
found = false;
while(*l)
{
char *le;
while(*l == ' ' || *l == '\n')
l++;
le = strchr(l, '\n');
if (le)
le = le+1;
else
le = l + strlen(l);
if (!strncmp(l, "[Added Associations]", 20))
inadded = true;
else if (!strncmp(l, "[", 1))
inadded = false;
else if (inadded && !strncmp(l, schemeline, schemelinelen))
{
found = true;
break;
}
l = le;
}
} while(found && (schemes=COM_ParseOut(schemes, scheme, sizeof(scheme))));
Z_Free(in);
}
else
found = false;
if (found)
return NULL;
return Z_StrDup(scheme);
}
static void Sys_Register_File_Associations_f(void)
{
const char *s;
char xdgbase[MAX_OSPATH];
char confbase[MAX_OSPATH];
char scheme[MAX_OSPATH];
const char *schemes = fs_manifest->schemes;
if (1)
{ //user
const char *data = getenv("XDG_DATA_HOME");
@ -357,41 +431,66 @@ static void Sys_Register_File_Associations_f(void)
//we need to create some .desktop file first, so stuff knows how to start us up.
{
vfsfile_t *f;
char iconsyspath[MAX_OSPATH];
char *exe = realpath(host_parms.argv[0], NULL);
char *basedir = realpath(com_gamepath, NULL);
const char *iconname = fs_manifest->installation;
const char *desktopfile =
"[Desktop Entry]\n"
"Type=Application\n"
"Encoding=UTF-8\n"
"Name=%s\n"
"Comment=Awesome First Person Shooter\n" //again should be a manicfest item
"Exec=\"%s\" %%u\n" //FIXME: FS_GetManifestArgs! etc!
"Path=%s\n"
"Icon=%s\n"
"Terminal=false\n"
"Categories=Game;\n"
"MimeType=" "application/x-quakeworlddemo;" "x-scheme-handler/qw;\n"
;
if (!exe)
{
int i;
if (strchr(host_parms.argv[0], '/') && (i=readlink("/proc/self/exe", iconsyspath, sizeof(iconsyspath)-1))>0)
{ //if they used a relative path to invoke the binary, replace it with an absolute one.
iconsyspath[i] = 0;
exe = strdup(iconsyspath);
}
else //no absolute path. assume it was loaded from the (default) path.
exe = strdup(host_parms.argv[0]);
}
if (!strcmp(iconname, "afterquake") || !strcmp(iconname, "nq")) //hacks so that we don't need to create icons.
iconname = "quake";
if (FS_NativePath("icon.png", FS_PUBBASEGAMEONLY, iconsyspath, sizeof(iconsyspath)))
iconname = iconsyspath;
desktopfile = va(desktopfile,
s = va("%s/applications/fte-%s.desktop", xdgbase, fs_manifest->installation);
FS_CreatePath(s, FS_SYSTEM);
f = FS_OpenVFS(s, "wb", FS_SYSTEM);
if (f)
{
VFS_PRINTF(f,
"[Desktop Entry]\n"
"Type=Application\n"
"Encoding=UTF-8\n"
"Name=%s\n"
"Comment=Awesome First Person Shooter\n" //again should be a manicfest item
"Exec=\"%s\" %%u %s\n"
"Path=%s\n"
"Icon=%s\n"
"Terminal=false\n"
"Categories=Game;\n"
"MimeType=" "application/x-quakeworlddemo;",
fs_manifest->formalname?fs_manifest->formalname:fs_manifest->installation,
exe, basedir, iconname);
exe, FS_GetManifestArgs(), basedir, iconname);
for (s = schemes; (s=COM_ParseOut(s,scheme,sizeof(scheme)));)
VFS_PRINTF(f, "x-scheme-handler/%s;", scheme);
VFS_PRINTF(f, "\n");
VFS_CLOSE(f);
}
free(exe);
free(basedir);
FS_WriteFile(va("%s/applications/fte-%s.desktop", xdgbase, fs_manifest->installation), desktopfile, strlen(desktopfile), FS_SYSTEM);
//FIXME: read icon.png and write it to ~/.local/share/icons/hicolor/WxH/apps/foo.png
}
//we need to set some default applications.
//write out a new file and rename the new over the top of the old
for (s = schemes; (s=COM_ParseOut(s,scheme,sizeof(scheme)));)
{
char *foundassoc = NULL;
vfsfile_t *out = FS_OpenVFS(va("%s/.mimeapps.list.new", confbase), "wb", FS_SYSTEM);
@ -403,6 +502,8 @@ static void Sys_Register_File_Associations_f(void)
{
qboolean inadded = false;
char *l = in;
const char *schemeline = va("x-scheme-handler/%s=", scheme);
size_t schemelinelen = strlen(schemeline);
while(*l)
{
char *le;
@ -421,10 +522,10 @@ static void Sys_Register_File_Associations_f(void)
}
else if (!strncmp(l, "[", 1))
inadded = false;
else if (inadded && !strncmp(l, "x-scheme-handler/qw=", 20))
else if (inadded && !strncmp(l, schemeline, schemelinelen))
{
foundassoc = l;
insize -= strlen(le);
insize -= le-l;
memmove(l, le, strlen(le)); //remove the line
}
l = le;
@ -432,7 +533,7 @@ static void Sys_Register_File_Associations_f(void)
if (foundassoc)
{ //if we found it, or somewhere to insert it, then insert it.
VFS_WRITE(out, in, foundassoc-in);
VFS_PRINTF(out, "x-scheme-handler/qw=fte-%s.desktop;\n", fs_manifest->installation);
VFS_PRINTF(out, "x-scheme-handler/%s=fte-%s.desktop;\n", scheme, fs_manifest->installation);
VFS_WRITE(out, foundassoc, insize - (foundassoc-in));
}
else
@ -442,7 +543,7 @@ static void Sys_Register_File_Associations_f(void)
if (!foundassoc)
{ //if file not found, or no appropriate section, just concat it on the end.
VFS_PRINTF(out, "[Added Associations]\n");
VFS_PRINTF(out, "x-scheme-handler/qw=fte-%s.desktop;\n", fs_manifest->installation);
VFS_PRINTF(out, "x-scheme-handler/%s=fte-%s.desktop;\n", scheme, fs_manifest->installation);
}
VFS_FLUSH(out);
VFS_CLOSE(out);

View File

@ -371,6 +371,10 @@ int main(int argc, char **argv)
}
}
char *Sys_URIScheme_NeedsRegistering(void)
{ //no support, report something that'll disable annoying prompts.
return NULL;
}
void Sys_Init()
{
}

View File

@ -725,6 +725,11 @@ int Sys_FileTime (char *path)
return -1;
}
char *Sys_URIScheme_NeedsRegistering(void)
{ //no support.
return NULL;
}
void Sys_Init(void)
{
SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);

View File

@ -1511,10 +1511,13 @@ void Sys_MakeCodeWriteable (void *startaddr, unsigned long length)
}
#endif
void Sys_DoFileAssociations(int elevated);
void Sys_DoFileAssociations(int elevated, const char *scheme);
void Sys_Register_File_Associations_f(void)
{
Sys_DoFileAssociations(0);
if (!Q_strcasecmp(Cmd_Argv(1), "quiet"))
Sys_DoFileAssociations(2, fs_manifest->schemes); //current user only.
else
Sys_DoFileAssociations(0, fs_manifest->schemes); //user+machine(with elevation on failure)
}
static void QDECL Sys_Priority_Changed(cvar_t *var, char *oldval)
@ -1559,7 +1562,7 @@ void Sys_Init (void)
#ifndef SERVERONLY
Cvar_Register(&sys_disableWinKeys, "System vars");
Cvar_Register(&sys_disableTaskSwitch, "System vars");
Cmd_AddCommandD("sys_register_file_associations", Sys_Register_File_Associations_f, "Register FTE as the system handler for .bsp .mvd .qwd .dem files. Also register the qw:// URL protocol. This command will probably trigger a UAC prompt in Windows Vista and up. Deny it for current-user-only asociations (will also prevent listing in windows' 'default programs' ui due to microsoft bugs/limitations).");
Cmd_AddCommandD("sys_register_file_associations", Sys_Register_File_Associations_f, "Register FTE as the system handler for .bsp .mvd .qwd .dem files. Also register the URL protocol. This command will probably trigger a UAC prompt in Windows Vista and up. Deny it for current-user-only asociations (will also prevent listing in windows' 'default programs' ui due to microsoft bugs/limitations).");
#ifdef QUAKESPYAPI
#ifndef CLIENTONLY
@ -2884,6 +2887,47 @@ void Win7_TaskListInit(void)
}
#endif
//using this like posix' access function, but with much more code, microsoftisms, and no errno codes/info
//no, I don't really have a clue why it needs to be so long.
//#include <svrapi.h>
#ifndef ACCESS_READ
#define ACCESS_READ 0x1
#define ACCESS_WRITE 0x2
#endif
static BOOL microsoft_accessW(LPWSTR pszFolder, DWORD dwAccessDesired)
{
HANDLE hToken;
PRIVILEGE_SET PrivilegeSet;
DWORD dwPrivSetSize;
DWORD dwAccessGranted;
BOOL fAccessGranted = FALSE;
GENERIC_MAPPING GenericMapping;
SECURITY_INFORMATION si = (SECURITY_INFORMATION)( OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION);
PSECURITY_DESCRIPTOR psdSD = NULL;
DWORD dwNeeded;
GetFileSecurityW(pszFolder,si,NULL,0,&dwNeeded);
psdSD = malloc(dwNeeded);
GetFileSecurityW(pszFolder,si,psdSD,dwNeeded,&dwNeeded);
ImpersonateSelf(SecurityImpersonation);
OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken);
memset(&GenericMapping, 0xff, sizeof(GENERIC_MAPPING));
GenericMapping.GenericRead = ACCESS_READ;
GenericMapping.GenericWrite = ACCESS_WRITE;
GenericMapping.GenericExecute = 0;
GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE;
MapGenericMask(&dwAccessDesired, &GenericMapping);
dwPrivSetSize = sizeof(PRIVILEGE_SET);
AccessCheck(psdSD, hToken, dwAccessDesired, &GenericMapping, &PrivilegeSet, &dwPrivSetSize, &dwAccessGranted, &fAccessGranted);
free(psdSD);
return fAccessGranted;
}
static BOOL microsoft_accessU(LPCSTR pszFolder, DWORD dwAccessDesired)
{
wchar_t wpath[MAX_OSPATH];
return microsoft_accessW(widen(wpath, sizeof(wpath), pszFolder), dwAccessDesired);
}
#ifndef SVNREVISION
#if 0 //1 to debug engine update in msvc.
#define SVNREVISION 1
@ -3085,10 +3129,68 @@ typedef struct qIApplicationAssociationRegistrationUI
} *lpVtbl;
} qIApplicationAssociationRegistrationUI;
void Sys_DoFileAssociations(int elevated)
char *Sys_URIScheme_NeedsRegistering(void)
{ //just disables the prompts.
HKEY root;
char buffer[2048];
char scheme[64];
const char *s, *schemes = fs_manifest->schemes;
char *exec, *me;
size_t i;
wchar_t enginebinaryw[MAX_OSPATH];
char enginebinary[MAX_OSPATH*4];
for (s = schemes; (s=COM_ParseOut(s, scheme, sizeof(scheme))); )
{
root = HKEY_CURRENT_USER;
if (!MyRegGetStringValue(root, va("Software\\Classes\\%s", scheme), "", buffer, sizeof(buffer)))
{
root = HKEY_LOCAL_MACHINE;
if (!MyRegGetStringValue(root, va("Software\\Classes\\%s", scheme), "", buffer, sizeof(buffer)))
break;
}
//the scheme exists at least...
if (!MyRegGetStringValue(root, va("Software\\Classes\\%s\\shell\\open\\command", scheme), "", buffer, sizeof(buffer)))
break; //erk, missing.
COM_Parse(buffer);
if (!microsoft_accessU(com_token, ACCESS_READ))
break; //can't read it? doesn't exist?
exec = COM_SkipPath(com_token);
for (i = 0; exec[i]; i++)
if (exec[i] == '_' || exec[i] == '.' || (exec[i] >= '0' && exec[i] <= '9'))
{ //anything that looks like a revision number
exec[i] = 0;
break;
}
GetModuleFileNameW(NULL, enginebinaryw, countof(enginebinaryw)-1);
narrowen(enginebinary, sizeof(enginebinary), enginebinaryw);
me = COM_SkipPath(enginebinary);
for (i = 0; me[i]; i++)
if (me[i] == '_' || me[i] == '.' || (me[i] >= '0' && me[i] <= '9'))
{ //anything that looks like a revision number
me[i] = 0;
break;
}
if (Q_strcasecmp(exec, me))
break; //looks like its set to something else.
}
if (s)
return Z_StrDup(scheme);
return NULL;
}
void Sys_DoFileAssociations(int elevated, const char *schemes)
{
//elevated:
// 0: console command
// 1: running as an elevated/admin process
// 2: register as current user only (do not show associations prompt).
char command[1024];
qboolean ok = true;
char scheme[64];
const char *s;
qboolean ok = true;
HKEY root;
//I'd do everything in current_user if I could, but windows sucks too much for that.
@ -3101,12 +3203,13 @@ void Sys_DoFileAssociations(int elevated)
//on xp, we use ONLY current user. no 'registered applications' means no 'registered applications bug', which means no need to use hklm at all.
//in vista/7, we have to create stuff in local_machine. in which case we might as well put ALL associations in there. the ui stuff will allow user-specific settings, so this is not an issue other than the fact that it triggers uac.
//in 8, we cannot programatically force ownership of our associations, so we might as well just use the ui method even for vista+7 instead of the ruder version.
//in win10, the 'ui' stuff is just a quick popup to tell the user to configure defaults themselves. hopefully we can fall back on the regular associations for when the user didn't override anyting.
if (qwinvermaj < 6)
elevated = 2;
root = elevated == 2?HKEY_CURRENT_USER:HKEY_LOCAL_MACHINE;
root = (elevated>=2)?HKEY_CURRENT_USER:HKEY_LOCAL_MACHINE;
#define ASSOC_VERSION 2
// #define ASSOC_VERSION 2
#define ASSOCV "1"
//register the basic demo class
@ -3116,6 +3219,13 @@ void Sys_DoFileAssociations(int elevated)
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_DemoFile."ASSOCV"\\DefaultIcon", "", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), "\"%s\" \"%%1\"", com_argv[0]);
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_DemoFile."ASSOCV"\\shell\\open\\command", "", REG_SZ, command, strlen(command));
if (ok)
{ //and now the extensions themselves...
MyRegSetValue(root, "Software\\Classes\\.qtv", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile."ASSOCV));
MyRegSetValue(root, "Software\\Classes\\.mvd", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile."ASSOCV));
MyRegSetValue(root, "Software\\Classes\\.qwd", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile."ASSOCV));
MyRegSetValue(root, "Software\\Classes\\.dem", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile."ASSOCV));
}
//register the basic map class. yeah, the command is the same as for demos. but the description is different!
Q_snprintfz(command, sizeof(command), "Quake Map");
@ -3124,8 +3234,13 @@ void Sys_DoFileAssociations(int elevated)
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_BSPFile."ASSOCV"\\DefaultIcon", "", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), "\"%s\" \"%%1\"", com_argv[0]);
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_BSPFile."ASSOCV"\\shell\\open\\command", "", REG_SZ, command, strlen(command));
if (ok)
{ //and now the extensions themselves...
MyRegSetValue(root, "Software\\Classes\\.bsp", "", REG_SZ, DISTRIBUTION"_BSPFile."ASSOCV, strlen(DISTRIBUTION"_BSPFile."ASSOCV));
MyRegSetValue(root, "Software\\Classes\\.map", "", REG_SZ, DISTRIBUTION"_BSPFile."ASSOCV, strlen(DISTRIBUTION"_BSPFile."ASSOCV));
}
//register the basic protocol class
//register the basic uri scheme class
Q_snprintfz(command, sizeof(command), "QuakeWorld Server");
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_Server."ASSOCV"", "", REG_SZ, command, strlen(command));
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_Server."ASSOCV"", "URL Protocol", REG_SZ, "", strlen(""));
@ -3133,8 +3248,22 @@ void Sys_DoFileAssociations(int elevated)
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_Server."ASSOCV"\\DefaultIcon", "", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), "\"%s\" \"%%1\"", com_argv[0]);
ok = ok & MyRegSetValue(root, "Software\\Classes\\"DISTRIBUTION"_Server."ASSOCV"\\shell\\open\\command", "", REG_SZ, command, strlen(command));
if (ok)
{ //and now the schemes themselves... (doesn't really use the same scheme stuff)
for (s = schemes; (s=COM_ParseOut(s, scheme, sizeof(scheme))); )
{
Q_snprintfz(command, sizeof(command), "QuakeWorld Server");
MyRegSetValue(root, va("Software\\Classes\\%s", scheme), "", REG_SZ, command, strlen(command));
MyRegSetValue(root, va("Software\\Classes\\%s", scheme), "URL Protocol", REG_SZ, "", strlen(""));
Q_snprintfz(command, sizeof(command), "\"%s\",0", com_argv[0]);
MyRegSetValue(root, va("Software\\Classes\\%s\\DefaultIcon", scheme), "", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), "\"%s\" \"%%1\"", com_argv[0]);
MyRegSetValue(root, va("Software\\Classes\\%s\\shell\\open\\command", scheme), "", REG_SZ, command, strlen(command));
}
}
//try to get ourselves listed in windows' 'default programs' ui.
//now try to get ourselves listed in windows' 'default programs' ui.
Q_snprintfz(command, sizeof(command), "%s", FULLENGINENAME);
ok = ok & MyRegSetValue(root, "Software\\"FULLENGINENAME"\\Capabilities", "ApplicationName", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), "%s", FULLENGINENAME" is an awesome hybrid game engine able to run multiple Quake-compatible/derived games.");
@ -3154,53 +3283,34 @@ void Sys_DoFileAssociations(int elevated)
// ok = ok & MyRegSetValue(root, "Software\\"FULLENGINENAME"\\Capabilities\\FileAssociations", ".fmf", REG_SZ, DISTRIBUTION"_ManifestFile", strlen(DISTRIBUTION"_ManifestFile"));
// ok = ok & MyRegSetValue(root, "Software\\"FULLENGINENAME"\\Capabilities\\MIMEAssociations", "application/x-ftemanifest", REG_SZ, DISTRIBUTION"_ManifestFile", strlen(DISTRIBUTION"_ManifestFile"));
Q_snprintfz(command, sizeof(command), DISTRIBUTION"_Server.1");
ok = ok & MyRegSetValue(root, "Software\\"FULLENGINENAME"\\Capabilities\\UrlAssociations", "qw", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), DISTRIBUTION"_Server."ASSOCV);
for (s = schemes; (s=COM_ParseOut(s, scheme, sizeof(scheme))); )
{
ok = ok & MyRegSetValue(root, "Software\\"FULLENGINENAME "\\Capabilities\\UrlAssociations", scheme, REG_SZ, command, strlen(command));
}
Q_snprintfz(command, sizeof(command), "Software\\"FULLENGINENAME"\\Capabilities");
ok = ok & MyRegSetValue(root, "Software\\RegisteredApplications", FULLENGINENAME, REG_SZ, command, strlen(command));
//also try to add it to current user.
if (root==HKEY_LOCAL_MACHINE)
Sys_DoFileAssociations(2, schemes);
//let the shell know that file associations changed (otherwise we might have to wait for a reboot)
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
if (!ok && elevated < 2)
if (!ok && root==HKEY_LOCAL_MACHINE)
{
HINSTANCE ch = ShellExecute(mainwindow, "runas", com_argv[0], va("-register_types %i", elevated+1), NULL, SW_SHOWNORMAL);
if ((qintptr_t)ch <= 32)
Sys_DoFileAssociations(2);
ShellExecute(mainwindow, "runas", com_argv[0], va("-register_types \"%s\"", schemes), NULL, SW_SHOWNORMAL);
return;
}
if (ok)
if (ok && root==HKEY_LOCAL_MACHINE)
{
// char buf[1];
//attempt to display the vista+ prompt (only way possible in win8, apparently)
//note that in win10 this will supposedly just show a notification popup with the user required to configure it manually via control panel.
qIApplicationAssociationRegistrationUI *aarui = NULL;
//needs to be done anyway to ensure that its listed, and so that we get the association if nothing else has it.
//however, the popup for when you start new programs is very annoying, so lets try to avoid that. our file associations are somewhat explicit anyway.
//note that you'll probably still get the clumsy prompt if you try to run fte as a different user. really depends if you gave it local machine write access.
// if (!aarui || elevated==2 || !MyRegGetStringValue(root, "Software\\Classes\\.qtv", "", buf, sizeof(buf)))
MyRegSetValue(root, "Software\\Classes\\.qtv", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile.1"));
// if (!aarui || elevated==2 || !MyRegGetStringValue(root, "Software\\Classes\\.mvd", "", buf, sizeof(buf)))
MyRegSetValue(root, "Software\\Classes\\.mvd", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile.1"));
// if (!aarui || elevated==2 || !MyRegGetStringValue(root, "Software\\Classes\\.qwd", "", buf, sizeof(buf)))
MyRegSetValue(root, "Software\\Classes\\.qwd", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile.1"));
// if (!aarui || elevated==2 || !MyRegGetStringValue(root, "Software\\Classes\\.dem", "", buf, sizeof(buf)))
MyRegSetValue(root, "Software\\Classes\\.dem", "", REG_SZ, DISTRIBUTION"_DemoFile."ASSOCV, strlen(DISTRIBUTION"_DemoFile.1"));
// if (!aarui || elevated==2 || !MyRegGetStringValue(root, "Software\\Classes\\.bsp", "", buf, sizeof(buf)))
MyRegSetValue(root, "Software\\Classes\\.bsp", "", REG_SZ, DISTRIBUTION"_BSPFile."ASSOCV, strlen(DISTRIBUTION"_BSPFile.1"));
//legacy url associations are a bit more explicit
// if (!aarui || elevated==2 || !MyRegGetStringValue(HKEY_CURRENT_USER, "Software\\Classes\\qw", "", buf, sizeof(buf)))
{
Q_snprintfz(command, sizeof(command), "QuakeWorld Server");
MyRegSetValue(root, "Software\\Classes\\qw", "", REG_SZ, command, strlen(command));
MyRegSetValue(root, "Software\\Classes\\qw", "URL Protocol", REG_SZ, "", strlen(""));
Q_snprintfz(command, sizeof(command), "\"%s\",0", com_argv[0]);
MyRegSetValue(root, "Software\\Classes\\qw\\DefaultIcon", "", REG_SZ, command, strlen(command));
Q_snprintfz(command, sizeof(command), "\"%s\" \"%%1\"", com_argv[0]);
MyRegSetValue(root, "Software\\Classes\\qw\\shell\\open\\command", "", REG_SZ, command, strlen(command));
}
CoInitialize(NULL);
if (FAILED(CoCreateInstance(&qCLSID_ApplicationAssociationRegistrationUI, 0, CLSCTX_INPROC_SERVER, &qIID_IApplicationAssociationRegistrationUI, (LPVOID*)&aarui)))
aarui = NULL;
@ -3308,46 +3418,6 @@ int MessageBoxU(HWND hWnd, char *lpText, char *lpCaption, UINT uType)
}
#ifdef WEBCLIENT
//using this like posix' access function, but with much more code, microsoftisms, and no errno codes/info
//no, I don't really have a clue why it needs to be so long.
//#include <svrapi.h>
#ifndef ACCESS_READ
#define ACCESS_READ 0x1
#define ACCESS_WRITE 0x2
#endif
static BOOL microsoft_accessW(LPWSTR pszFolder, DWORD dwAccessDesired)
{
HANDLE hToken;
PRIVILEGE_SET PrivilegeSet;
DWORD dwPrivSetSize;
DWORD dwAccessGranted;
BOOL fAccessGranted = FALSE;
GENERIC_MAPPING GenericMapping;
SECURITY_INFORMATION si = (SECURITY_INFORMATION)( OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION);
PSECURITY_DESCRIPTOR psdSD = NULL;
DWORD dwNeeded;
GetFileSecurityW(pszFolder,si,NULL,0,&dwNeeded);
psdSD = malloc(dwNeeded);
GetFileSecurityW(pszFolder,si,psdSD,dwNeeded,&dwNeeded);
ImpersonateSelf(SecurityImpersonation);
OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken);
memset(&GenericMapping, 0xff, sizeof(GENERIC_MAPPING));
GenericMapping.GenericRead = ACCESS_READ;
GenericMapping.GenericWrite = ACCESS_WRITE;
GenericMapping.GenericExecute = 0;
GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE;
MapGenericMask(&dwAccessDesired, &GenericMapping);
dwPrivSetSize = sizeof(PRIVILEGE_SET);
AccessCheck(psdSD, hToken, dwAccessDesired, &GenericMapping, &PrivilegeSet, &dwPrivSetSize, &dwAccessGranted, &fAccessGranted);
free(psdSD);
return fAccessGranted;
}
static BOOL microsoft_accessU(LPCSTR pszFolder, DWORD dwAccessDesired)
{
wchar_t wpath[MAX_OSPATH];
return microsoft_accessW(widen(wpath, sizeof(wpath), pszFolder), dwAccessDesired);
}
#ifndef GWLP_WNDPROC
#define GWLP_WNDPROC GWL_WNDPROC
#define SetWindowLongPtr SetWindowLong
@ -4112,9 +4182,10 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin
isPlugin = 0;
}
if (COM_CheckParm("-register_types"))
c = COM_CheckParm("-register_types");
if (c)
{
Sys_DoFileAssociations(1);
Sys_DoFileAssociations(1, (c+1 < com_argc)?com_argv[c+1]:NULL);
return true;
}
/*

View File

@ -7746,7 +7746,7 @@ void Info_SetValueForKey (char *s, const char *key, const char *value, int maxsi
Info_SetValueForStarKey (s, key, value, maxsize);
}
static void Info_Enumerate (const char *s, void *ctx, void(*cb)(void *ctx, const char *key, const char *value))
void Info_Enumerate (const char *s, void *ctx, void(*cb)(void *ctx, const char *key, const char *value))
{
char key[1024];
char value[1024];

View File

@ -764,6 +764,8 @@ typedef struct
char *rtcbroker; //the broker to use for webrtc connections.
char *basedir; //this is where we expect to find the data.
char *iconname; //path we can find the icon (relative to the fmf's location)
char *schemes; //protocol scheme used to connect to a server running this game, use com_parse.
struct
{
enum
@ -854,9 +856,9 @@ void Info_RemovePrefixedKeys (char *start, char prefix);
void Info_RemoveKey (char *s, const char *key);
char *Info_KeyForNumber (const char *s, int num);
void Info_Print (const char *s, const char *lineprefix);
void Info_Enumerate (const char *s, void *ctx, void(*cb)(void *ctx, const char *key, const char *value));
/*
void Info_RemoveNonStarKeys (char *start);
void Info_Enumerate (const char *s, void *ctx, void(*cb)(void *ctx, const char *key, const char *value));
void Info_WriteToFile(vfsfile_t *f, char *info, char *commandname, int cvarflags);
*/

View File

@ -219,6 +219,7 @@ void FS_Manifest_Free(ftemanifest_t *man)
Z_Free(man->downloadsurl);
Z_Free(man->installupd);
#endif
Z_Free(man->schemes);
Z_Free(man->protocolname);
Z_Free(man->eula);
Z_Free(man->defaultexec);
@ -259,6 +260,8 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm)
if (oldm->installupd)
newm->installupd = Z_StrDup(oldm->installupd);
#endif
if (oldm->schemes)
newm->schemes = Z_StrDup(oldm->schemes);
if (oldm->protocolname)
newm->protocolname = Z_StrDup(oldm->protocolname);
if (oldm->eula)
@ -319,6 +322,8 @@ static void FS_Manifest_Print(ftemanifest_t *man)
if (man->installupd)
Con_Printf("install %s\n", COM_QuotedString(man->installupd, buffer, sizeof(buffer), false));
#endif
if (man->schemes)
Con_Printf("schemes %s\n", COM_QuotedString(man->schemes, buffer, sizeof(buffer), false));
if (man->protocolname)
Con_Printf("protocolname %s\n", COM_QuotedString(man->protocolname, buffer, sizeof(buffer), false));
if (man->defaultexec)
@ -660,6 +665,14 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man)
man->installupd = Z_StrDup(Cmd_Argv(1));
}
#endif
else if (!Q_strcasecmp(cmd, "schemes"))
{
int i;
Z_Free(man->schemes);
man->schemes = Z_StrDup(Cmd_Argv(1));
for (i = 2; i < Cmd_Argc(); i++)
Z_StrCat(&man->schemes, va(" %s", Cmd_Argv(i)));
}
else if (!Q_strcasecmp(cmd, "protocolname"))
{
Z_Free(man->protocolname);
@ -3807,9 +3820,9 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths)
/*quake requires a few settings for compatibility*/
#define QRPCOMPAT "set cl_cursor_scale 0.2\nset cl_cursor_bias_x 7.5\nset cl_cursor_bias_y 0.8\n"
#define QUAKESPASMSUCKS "set mod_h2holey_bugged 1\n"
#define QCFG "set v_gammainverted 1\nset con_stayhidden 0\nset com_parseutf8 0\nset allow_download_pakcontents 1\nset allow_download_refpackages 0\nset r_meshpitch -1\nr_sprite_backfacing 1\nset sv_bigcoords \"\"\nmap_autoopenportals 1\n" "sv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" ZFIXHACK EZQUAKECOMPETITIVE QRPCOMPAT QUAKESPASMSUCKS
#define QCFG "//schemes quake qw\n" "set v_gammainverted 1\nset con_stayhidden 0\nset com_parseutf8 0\nset allow_download_pakcontents 1\nset allow_download_refpackages 0\nset r_meshpitch -1\nr_sprite_backfacing 1\nset sv_bigcoords \"\"\nmap_autoopenportals 1\n" "sv_port "STRINGIFY(PORT_QWSERVER)" "STRINGIFY(PORT_NQSERVER)"\n" ZFIXHACK EZQUAKECOMPETITIVE QRPCOMPAT QUAKESPASMSUCKS
/*NetQuake reconfiguration, to make certain people feel more at home...*/
#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\ncfg_save_auto 1\n" QCFG "set sv_nqplayerphysics 1\nset cl_loopbackprotocol auto\ncl_sbar 1\nset plug_sbar 0\nset sv_port "STRINGIFY(PORT_NQSERVER)"\ncl_defaultport "STRINGIFY(PORT_NQSERVER)"\nset m_preset_chosen 1\nset vid_wait 1\nset cl_demoreel 1\n"
#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\n" QCFG "cfg_save_auto 1\nset sv_nqplayerphysics 1\nset cl_loopbackprotocol auto\ncl_sbar 1\nset plug_sbar 0\nset sv_port "STRINGIFY(PORT_NQSERVER)"\ncl_defaultport "STRINGIFY(PORT_NQSERVER)"\nset m_preset_chosen 1\nset vid_wait 1\nset cl_demoreel 1\n"
#define SPASMCFG NQCFG "fps_preset builtin_spasm\nset cl_demoreel 0\ncl_sbar 2\nset gl_load24bit 1\n"
#define FITZCFG NQCFG "fps_preset builtin_spasm\ncl_sbar 2\nset gl_load24bit 1\n"
#define TENEBRAECFG NQCFG "fps_preset builtin_tenebrae\n"
@ -3823,11 +3836,11 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths)
/*some modern non-compat settings*/
#define DMFCFG "set com_parseutf8 1\npm_airstep 1\nsv_demoExtensions 1\n"
/*set some stuff so our regular qw client appears more like hexen2. sv_mintic is required to 'fix' the ravenstaff so that its projectiles don't impact upon each other*/
#define HEX2CFG "set v_gammainverted 1\nset com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset cl_forwardspeed 200\nset cl_backspeed 200\ncl_sidespeed 225\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset r_meshpitch -1\nset r_meshroll -1\nr_sprite_backfacing 1\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\nset sv_walkpitch 0\n"
#define HEX2CFG "//schemes hexen2\n" "set v_gammainverted 1\nset com_parseutf8 -1\nset gl_font gfx/hexen2\nset in_builtinkeymap 0\nset_calc cl_playerclass int (random * 5) + 1\nset cl_forwardspeed 200\nset cl_backspeed 200\ncl_sidespeed 225\nset sv_maxspeed 640\ncl_run 0\nset watervis 1\nset r_lavaalpha 1\nset r_lavastyle -2\nset r_wateralpha 0.5\nset sv_pupglow 1\ngl_shaftlight 0.5\nsv_mintic 0.015\nset r_meshpitch -1\nset r_meshroll -1\nr_sprite_backfacing 1\nset mod_warnmodels 0\nset cl_model_bobbing 1\nsv_sound_watersplash \"misc/hith2o.wav\"\nsv_sound_land \"fx/thngland.wav\"\nset sv_walkpitch 0\n"
/*yay q2!*/
#define Q2CFG "set v_gammainverted 1\nset com_parseutf8 0\ncom_nogamedirnativecode 0\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\n"
#define Q2CFG "//schemes quake2\n" "set v_gammainverted 1\nset com_parseutf8 0\ncom_nogamedirnativecode 0\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\n"
/*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/
#define Q3CFG "set v_gammainverted 0\nset snd_ignorecueloops 1\nsetfl g_gametype 0 s\nset gl_clear 8\nset com_parseutf8 0\ngl_overbright 2\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_nogamedirnativecode 0\nsv_port "STRINGIFY(PORT_Q3SERVER)"\n"
#define Q3CFG "//schemes quake3\n" "set v_gammainverted 0\nset snd_ignorecueloops 1\nsetfl g_gametype 0 s\nset gl_clear 8\nset com_parseutf8 0\ngl_overbright 2\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_nogamedirnativecode 0\nsv_port "STRINGIFY(PORT_Q3SERVER)"\n"
//#define RMQCFG "sv_bigcoords 1\n"
#ifdef HAVE_SSL
@ -5900,6 +5913,12 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean
}
}
if (!man->schemes)
{
Cmd_TokenizeString(va("schemes \"%s\"", gamemode_info[i].argname+1), false, false);
FS_Manifest_ParseTokens(man);
}
#ifdef PACKAGEMANAGER
if (!man->downloadsurl && gamemode_info[i].downloadsurl)
{
@ -5932,7 +5951,17 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean
}
if (!man->defaultexec && gamemode_info[i].customexec)
{
man->defaultexec = Z_StrDup(gamemode_info[i].customexec);
const char *e = gamemode_info[i].customexec;
while (e[0] == '/' && e[1] == '/')
{
e+=2;
while(*e)
{
if (*e++ == '\n')
break;
}
}
man->defaultexec = Z_StrDup(e);
}
builtingame = true;

View File

@ -49,6 +49,7 @@ void VARGS Sys_Printf (char *fmt, ...) LIKEPRINTF(1);
void Sys_Warn (char *fmt, ...) LIKEPRINTF(1);
//like Sys_Printf. dunno why there needs to be two of em.
char *Sys_URIScheme_NeedsRegistering(void); //returns the name of one of the current manifests uri schemes that isn't registered (but should be registerable).
void Sys_Quit (void);
void Sys_RecentServer(char *command, char *target, char *title, char *desc);
qboolean Sys_RunInstaller(void);

View File

@ -57,6 +57,7 @@ typedef struct svm_server_s {
bucket_t bucket; //for faster address lookups.
struct svm_game_s *game;
struct svm_server_s *next;
char rules[1024];
} svm_server_t;
typedef struct svm_game_s {
@ -66,6 +67,7 @@ typedef struct svm_game_s {
size_t numservers;
qboolean persistent;
char *aliases; //list of terminated names, terminated with a double-null
char *scheme;
char name[1]; //eg: Quake
} svm_game_t;
@ -604,6 +606,31 @@ vfsfile_t *SVM_Generate_Gamelist(const char **mimetype, const char *query)
*mimetype = "text/html";
return f;
}
struct rulelist_s
{
unsigned int lines;
unsigned int blobofs;
char *line[64];
char blob[8192];
};
static void SVM_GatherServerRule(void *ctx, const char *key, const char *val)
{
struct rulelist_s *rules = ctx;
char niceval[256];
if (rules->lines == countof(rules->line))
return; //overflow
QuakeCharsToHTML(niceval, sizeof(niceval), val, false);
if (!Q_snprintfz(rules->blob+rules->blobofs, sizeof(rules->blob)-rules->blobofs, "<tr><td>%s</td><td>%s</td></tr>\n", key, niceval))
{
rules->line[rules->lines++] = rules->blob+rules->blobofs;
rules->blobofs += strlen(rules->blob+rules->blobofs)+1;
}
}
static int QDECL SVM_SortServerRule(const void *r1, const void *r2)
{
return Q_strcasecmp(*(char*const*const)r1, *(char*const*const)r2);
}
vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr, const char *query)
{
vfsfile_t *f = VFSPIPE_Open(1, false);
@ -611,13 +638,12 @@ vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr
char hostname[1024];
svm_server_t *server;
netadr_t adr[64];
int count;
size_t count, u;
const char *url;
VFS_PRINTF(f, "%s", master_css);
VFS_PRINTF(f, "<h1>Single Server Info</h1>\n");
VFS_PRINTF(f, "<table border=1>\n");
VFS_PRINTF(f, "<tr><th>Game</th><th>Address</th><th>Hostname</th><th>Mod dir</th><th>Mapname</th><th>Players</th></tr>\n");
//FIXME: block dns lookups here?
count = NET_StringToAdr2(serveraddr, 0, adr, countof(adr), NULL);
while(count-->0)
@ -625,13 +651,45 @@ vfsfile_t *SVM_Generate_Serverinfo(const char **mimetype, const char *serveraddr
server = SVM_GetServer(&adr[count]);
if (server)
{
VFS_PRINTF(f, "<table border=1>\n");
VFS_PRINTF(f, "<tr><th>Game</th><th>Address</th><th>Hostname</th><th>Mod dir</th><th>Mapname</th><th>Players</th></tr>\n");
QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false);
VFS_PRINTF(f, "<tr><td>%s</td><td>%s</td><td>%s%s</td><td>%s</td><td>%s</td><td>%u/%u</td></tr>\n", server->game?server->game->name:"Unknown", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), (server->needpass&1)?"&#x1F512;":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients);
url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr);
if (server->game->scheme && !server->brokerid)
url = va("<a href=\"%s://%s\">%s</a>", server->game->scheme, url, url);
VFS_PRINTF(f, "<tr><td>%s</td><td>%s</td><td>%s%s</td><td>%s</td><td>%s</td><td>%u/%u</td></tr>\n", server->game?server->game->name:"Unknown", url, (server->needpass&1)?"&#x1F512;":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients);
VFS_PRINTF(f, "</table>\n");
VFS_PRINTF(f, "<br/>\n");
if (*server->rules)
{
struct rulelist_s rules;
rules.lines = rules.blobofs = 0;
Info_Enumerate(server->rules, &rules, SVM_GatherServerRule);
qsort(rules.line, rules.lines, sizeof(rules.line[0]), SVM_SortServerRule);
//VFS_PRINTF(f, "<table border=0>\n");
// VFS_PRINTF(f, "<td></td><td>");
VFS_PRINTF(f, "<table border=1>\n");
VFS_PRINTF(f, "</th><th>Rule</th><th>Value</th></tr>\n");
for (u = 0; u < rules.lines; u++)
VFS_PUTS(f, rules.line[u]);
VFS_PRINTF(f, "</table>");
// VFS_PRINTF(f, "</td>");
//VFS_PRINTF(f, "</table>\n");
VFS_PRINTF(f, "<br/>\n");
}
}
else
{
VFS_PRINTF(f, "<table border=1>\n");
VFS_PRINTF(f, "<tr><th>Game</th><th>Address</th><th>Hostname</th><th>Mod dir</th><th>Mapname</th><th>Players</th></tr>\n");
VFS_PRINTF(f, "<tr><td>?</td><td>%s</td><td>?</td><td>?</td><td>?</td><td>?/?</td></tr>\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &adr[count]));
VFS_PRINTF(f, "</table>\n");
}
}
VFS_PRINTF(f, "</table>\n");
*mimetype = "text/html";
return f;
@ -642,7 +700,7 @@ vfsfile_t *SVM_Generate_Serverlist(const char **mimetype, const char *masteraddr
vfsfile_t *f = VFSPIPE_Open(1, false);
char tmpbuf[256];
char hostname[1024];
const char *url;
const char *url, *infourl;
svm_game_t *game;
svm_server_t *server;
unsigned clients=0,bots=0,specs=0;
@ -684,11 +742,14 @@ vfsfile_t *SVM_Generate_Serverlist(const char **mimetype, const char *masteraddr
{
url = tmpbuf;
Q_snprintfz(tmpbuf, sizeof(tmpbuf), "rtc://%s/%s", masteraddr, server->brokerid);
infourl = tmpbuf;
}
else
url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr);
{
infourl = url = NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr);
}
QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false);
VFS_PRINTF(f, "<tr><td>%s</td><td>%s%s%s</td><td>%s</td><td>%s</td><td>%u", url, (server->needpass&1)?"&#x1F512;":"", (server->coop&1)?"&#x1F6B8;":"", hostname, server->gamedir, server->mapname, server->clients);
VFS_PRINTF(f, "<tr><td><a href=\"/server/%s\">%s</a></td><td>%s%s%s</td><td>%s</td><td>%s</td><td>%u", infourl, url, (server->needpass&1)?"&#x1F512;":"", (server->coop&1)?"&#x1F6B8;":"", hostname, server->gamedir, server->mapname, server->clients);
if (server->bots)
VFS_PRINTF(f, "+%ub", server->bots);
VFS_PRINTF(f, "/%u", server->maxclients);
@ -1192,6 +1253,7 @@ static void SVM_ProcessUDPPacket(void)
SVM_GenChallenge(ourchallenge, sizeof(ourchallenge), &net_from);
if (!strcmp(chal, ourchallenge))
{
bots = atoi(Info_ValueForKey(s, "bots"));
clients = atoi(Info_ValueForKey(s, "clients"));
clients = max(0, clients-bots);
@ -1204,6 +1266,7 @@ static void SVM_ProcessUDPPacket(void)
srv = SVM_Heartbeat(game, &net_from, clients,bots,specs, svm.time + sv_heartbeattimeout.ival);
if (srv)
{
Q_strncpyz(srv->rules, s, sizeof(srv->rules));
if (developer.ival)
Info_Print(s, "\t");
if (game)
@ -1380,6 +1443,7 @@ static void SVM_ProcessUDPPacket(void)
srv = SVM_Heartbeat(game, &net_from, clients,bots,specs, svm.time + sv_heartbeattimeout.ival);
if (srv)
{
Q_strncpyz(srv->rules, s, sizeof(srv->rules));
if (developer.ival)
Info_Print(s, "\t");
srv->protover = 3;//atoi(Info_ValueForKey(s, "protocol"));
@ -1606,6 +1670,13 @@ static qboolean SVM_FoundManifest(void *usr, ftemanifest_t *man)
g = COM_Parse(g);
game = SVM_FindGame(com_token, 2);
#endif
if (!game)
return false;
if (man->schemes && !game->scheme)
{
COM_Parse(man->schemes);
game->scheme = Z_StrDup(com_token);
}
while (*g)
{
g = COM_Parse(g);

View File

@ -155,6 +155,11 @@ void Sys_BrowserRedirect_f(void)
emscriptenfte_window_location(Cmd_Argv(1));
}
char *Sys_URIScheme_NeedsRegistering(void)
{ //just disables the prompts that we can't honour anyway.
return NULL;
}
void Sys_Init(void)
{
extern cvar_t vid_width, vid_height, vid_fullscreen;