diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index a920289f8..beb76494e 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -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); + } + } + } } /* diff --git a/engine/client/sys_droid.c b/engine/client/sys_droid.c index 3ef41df06..888d57f2b 100644 --- a/engine/client/sys_droid.c +++ b/engine/client/sys_droid.c @@ -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"); diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index be47a0732..f23dd0883 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -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); diff --git a/engine/client/sys_morphos.c b/engine/client/sys_morphos.c index 936c8f25c..0af374e96 100755 --- a/engine/client/sys_morphos.c +++ b/engine/client/sys_morphos.c @@ -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() { } diff --git a/engine/client/sys_sdl.c b/engine/client/sys_sdl.c index e53108a06..eb7d8ecfb 100644 --- a/engine/client/sys_sdl.c +++ b/engine/client/sys_sdl.c @@ -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); diff --git a/engine/client/sys_win.c b/engine/client/sys_win.c index 7905e64e7..5ecdef513 100644 --- a/engine/client/sys_win.c +++ b/engine/client/sys_win.c @@ -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 +#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 -#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; } /* diff --git a/engine/common/common.c b/engine/common/common.c index e44281fba..9db00eb13 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -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]; diff --git a/engine/common/common.h b/engine/common/common.h index c294898c3..f42edcd3c 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -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); */ diff --git a/engine/common/fs.c b/engine/common/fs.c index 0dc3b25ac..121f8794f 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -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; diff --git a/engine/common/sys.h b/engine/common/sys.h index 050edc72b..383583936 100644 --- a/engine/common/sys.h +++ b/engine/common/sys.h @@ -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); diff --git a/engine/server/sv_master.c b/engine/server/sv_master.c index 54945c542..25d212321 100644 --- a/engine/server/sv_master.c +++ b/engine/server/sv_master.c @@ -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, "%s%s\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, "

Single Server Info

\n"); - VFS_PRINTF(f, "\n"); - VFS_PRINTF(f, "\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, "
GameAddressHostnameMod dirMapnamePlayers
\n"); + VFS_PRINTF(f, "\n"); QuakeCharsToHTML(hostname, sizeof(hostname), server->hostname, false); - VFS_PRINTF(f, "\n", server->game?server->game->name:"Unknown", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &server->adr), (server->needpass&1)?"🔒":"", 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("%s", server->game->scheme, url, url); + + VFS_PRINTF(f, "\n", server->game?server->game->name:"Unknown", url, (server->needpass&1)?"🔒":"", hostname, server->gamedir, server->mapname, server->clients, server->maxclients); + VFS_PRINTF(f, "
GameAddressHostnameMod dirMapnamePlayers
%s%s%s%s%s%s%u/%u
%s%s%s%s%s%s%u/%u
\n"); + VFS_PRINTF(f, "
\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, "\n"); + // VFS_PRINTF(f, ""); + //VFS_PRINTF(f, "
"); + VFS_PRINTF(f, "\n"); + VFS_PRINTF(f, "\n"); + for (u = 0; u < rules.lines; u++) + VFS_PUTS(f, rules.line[u]); + VFS_PRINTF(f, "
RuleValue
"); + // VFS_PRINTF(f, "
\n"); + VFS_PRINTF(f, "
\n"); + } } else + { + VFS_PRINTF(f, "\n"); + VFS_PRINTF(f, "\n"); VFS_PRINTF(f, "\n", NET_AdrToString(tmpbuf, sizeof(tmpbuf), &adr[count])); + VFS_PRINTF(f, "
GameAddressHostnameMod dirMapnamePlayers
?%s????/?
\n"); + } } - VFS_PRINTF(f, "\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, "%s%s%s%s%s%s%u", url, (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", hostname, server->gamedir, server->mapname, server->clients); + VFS_PRINTF(f, "%s%s%s%s%s%s%u", infourl, url, (server->needpass&1)?"🔒":"", (server->coop&1)?"🚸":"", 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); diff --git a/engine/web/sys_web.c b/engine/web/sys_web.c index c799afd76..b801ba7d9 100644 --- a/engine/web/sys_web.c +++ b/engine/web/sys_web.c @@ -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;