diff --git a/CMakeLists.txt b/CMakeLists.txt index d35a8c391..b5ab69905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,8 +61,30 @@ IF (EXISTS ${CMAKE_SOURCE_DIR}/.git) OUTPUT_VARIABLE FTE_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) - MESSAGE(STATUS "FTE GIT ${FTE_BRANCH} Revision git-${FTE_REVISON_GIT}, ${FTE_DATE}") - SET(SVNREVISION git-${FTE_REVISON_GIT}) + EXECUTE_PROCESS(COMMAND + git rev-parse --is-shallow-repository + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE FTE_GIT_IS_SHALLOW + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + IF(FTE_GIT_IS_SHALLOW STREQUAL true) + MESSAGE(STATUS "shallow clone prevents calculation of revision number.") + SET(SVNREVISION "git-${FTE_REVISON_GIT}") #if its a shallow clone then we can't count commits properly so don't know what revision we actually are. + ELSE() + EXECUTE_PROCESS(COMMAND + git rev-list HEAD --count + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE SVNREVISION + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE + ) + MATH(EXPR SVNREVISION "${SVNREVISION} + 29") #not all svn commits managed to appear im the git repo, so we have a small bias to keep things consistent. + IF (FTE_BRANCH STREQUAL "master") + SET(SVNREVISION "${SVNREVISION}-git-${FTE_REVISON_GIT}") + ELSE() + SET(SVNREVISION "${FTE_BRANCH}-${SVNREVISION}-git-${FTE_REVISON_GIT}") #weird branches get a different form of revision, to reduce confusion. + ENDIF() + ENDIF() + MESSAGE(STATUS "FTE GIT ${FTE_BRANCH} Revision ${SVNREVISION}, ${FTE_DATE}") SET(FTE_REVISON SVNREVISION=${SVNREVISION} SVNDATE=${FTE_DATE} FTE_BRANCH=${FTE_BRANCH}) ENDIF() @@ -225,7 +247,7 @@ IF(CMAKE_BUILD_TYPE MATCHES "Debug") ENDIF() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FILE_OFFSET_BITS=64") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DFTE_LIBRARY_PATH=${CMAKE_INSTALL_FULL_LIBDIR}/${FTE_INSTALL_LIBDIR}") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DFTE_LIBRARY_PATH=${CMAKE_INSTALL_FULL_LIBDIR}/${FTE_INSTALL_LIBDIR} -DFTE_DATA_DIR=${CMAKE_INSTALL_FULL_DATAROOTDIR}") FUNCTION(EMBED_PLUGIN_META PLUGNAME PLUGTITLE PLUGDESC) SET_TARGET_PROPERTIES(plug_${PLUGNAME} PROPERTIES OUTPUT_NAME "${PLUGNAME}") @@ -235,10 +257,10 @@ FUNCTION(EMBED_PLUGIN_META PLUGNAME PLUGTITLE PLUGDESC) #sadly we need to use a temp zip file, because otherwise zip insists on using zip64 extensions which breaks zip -A (as well as any attempts to read any files). ADD_CUSTOM_COMMAND( TARGET plug_${PLUGNAME} POST_BUILD - COMMAND echo "{\\n package fteplug_${PLUGNAME}\\n ver \"${SVNREVISION}\"\\n category Plugins\\n title \"${PLUGTITLE}\"\\n gamedir \"\"\\n desc \"${PLUGDESC}\"\\n}" | zip -q -9 -fz- $.zip - - COMMAND cat $.zip >> "$" + COMMAND /bin/echo -e "{\\n package fteplug_${PLUGNAME}\\n ver \"${SVNREVISION}\"\\n category Plugins\\n title \"${PLUGTITLE}\"\\n gamedir \"\"\\n desc \"${PLUGDESC}\"\\n}" | zip -q -9 -fz- $.zip - + COMMAND cmake -E cat $.zip >> "$" COMMAND zip -A "$" - COMMAND rm $.zip + COMMAND cmake -E rm $.zip VERBATIM) ENDFUNCTION() @@ -1585,6 +1607,14 @@ IF(FTE_MENU_SYS) quakec/menusys/menu/options_video.qc quakec/menusys/menu/quit.qc ) + + ADD_CUSTOM_COMMAND( + TARGET menusys POST_BUILD + COMMAND /bin/echo -e "{\\n package fte_menusys\\n ver \"${SVNREVISION}\"\\n category Plugins\\n title \"Replacement Menus\"\\n gamedir \"id1\"\\n desc \"Modern menus to replace the ancient quake ones\"\\n}" | zip -q -9 -fz- menusys.pk3 - menu.dat + VERBATIM) + INSTALL(FILES + menusys.pk3 + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/games/quake/id1/") ENDIF() SET(FTE_CSADDON true CACHE BOOL "CS Addon.") @@ -1616,4 +1646,12 @@ IF(FTE_CSADDON) quakec/csaddon/src/cam.qc quakec/csaddon/src/csaddon.qc ) + + ADD_CUSTOM_COMMAND( + TARGET csaddon POST_BUILD + COMMAND /bin/echo -e "{\\n package fte_csaddon\\n ver \"${SVNREVISION}\"\\n category Plugins\\n title \"${PLUGTITLE}\"\\n gamedir \"id1\"\\n desc \"${PLUGDESC}\"\\n}" | zip -q -9 -fz- csaddon.pk3 - csaddon.dat + VERBATIM) + INSTALL(FILES + csaddon.pk3 + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/games/quake/id1/") ENDIF() diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 6b42613bb..84556aa18 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -7006,7 +7006,7 @@ void CL_ArgumentOverrides(void) } //note that this does NOT include commandline. -void CL_ExecInitialConfigs(char *resetcommand) +void CL_ExecInitialConfigs(char *resetcommand, qboolean fullvidrestart) { #ifndef QUAKETC int qrc, hrc; @@ -7085,7 +7085,9 @@ void CL_ExecInitialConfigs(char *resetcommand) com_parseutf8.ival = com_parseutf8.value; //if the renderer is already up and running, be prepared to reload content to match the new conback/font/etc - if (qrenderer != QR_NONE) + if (fullvidrestart) + Cbuf_AddText ("vid_restart\n", RESTRICT_LOCAL); + else if (qrenderer != QR_NONE) Cbuf_AddText ("vid_reload\n", RESTRICT_LOCAL); // if (Key_Dest_Has(kdm_menu)) // Cbuf_AddText ("closemenu\ntogglemenu\n", RESTRICT_LOCAL); //make sure the menu has the right content loaded. @@ -7175,7 +7177,7 @@ void Host_FinishLoading(void) #endif } - if (PM_IsApplying(true)) + if (PM_IsApplying() == 1) { #ifdef MULTITHREAD Sys_Sleep(0.1); diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index 15d968925..4c3aca94a 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -26,6 +26,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #include "shader.h" #include "gl_draw.h" +#include "fs.h" //name of the current backdrop for the loading screen char levelshotname[MAX_QPATH]; @@ -2462,7 +2463,7 @@ void SCR_SetUpToDrawConsole (void) { if (CL_TryingToConnect()) //if we're trying to connect, make sure there's a loading/connecting screen showing instead of forcing the menu visible SCR_SetLoadingStage(LS_CONNECTION); - else if (!Key_Dest_Has(kdm_menu) && !startuppending) //don't force anything until the startup stuff has been done + else if (!Key_Dest_Has(kdm_menu) && !Key_Dest_Has(kdm_prompt) && !PM_IsApplying() && !startuppending) //don't force anything until the startup stuff has been done M_ToggleMenu_f(); } } diff --git a/engine/client/client.h b/engine/client/client.h index 13e0150b7..f80fee5bc 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -1149,7 +1149,7 @@ void CL_SetInfoBlob (int pnum, const char *key, const char *value, size_t values char *CL_TryingToConnect(void); -void CL_ExecInitialConfigs(char *defaultexec); +void CL_ExecInitialConfigs(char *defaultexec, qboolean fullvidrestart); extern int cl_framecount; //number of times the entity lists have been cleared+reset. extern int cl_numvisedicts; diff --git a/engine/client/m_download.c b/engine/client/m_download.c index ac76c1d3d..ac21bd209 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -74,7 +74,7 @@ cvar_t pkg_autoupdate = CVARFD("pkg_autoupdate", "-1", CVAR_NOTFROMSERVER|CVAR_N #define DPF_ENGINE (1u<<14) //engine update. replaces old autoupdate mechanism #define DPF_PLUGIN (1u<<15) //this is a plugin package, with a dll -//#define DPF_TRUSTED (1u<<16) //flag used when parsing package lists. if not set then packages will be ignored if they are anything but paks/pk3s +#define DPF_TRUSTED (1u<<16) //package was trusted when installed. any pk3s can be flagged appropriately. #define DPF_SIGNATUREREJECTED (1u<<17) //signature is bad #define DPF_SIGNATUREACCEPTED (1u<<18) //signature is good (required for dll/so/exe files) #define DPF_SIGNATUREUNKNOWN (1u<<19) //signature is unknown @@ -137,9 +137,9 @@ typedef struct package_s { char *packprefix; //extra weirdness to skip embedded gamedirs or force extra maps/ nesting quint64_t filesize; //in bytes, as part of verifying the hash. - char *filesha1; - char *filesha512; - char *signature; + char *filesha1; //this is the hash of the _download_, not the individual files + char *filesha512; //this is the hash of the _download_, not the individual files + char *signature; //signature of the [prefix:]sha512 char *title; char *description; @@ -194,13 +194,7 @@ typedef struct package_s { static qboolean loadedinstalled; static package_t *availablepackages; static int numpackages; -static char *manifestpackages; //metapackage named by the manicfest. static char *declinedpackages; //metapackage named by the manicfest. -static int domanifestinstall; //SECURITY_MANIFEST_* - -#ifdef PLUGINS -static int pluginsadded; //so we only show prompts for new externally-installed plugins once, instead of every time the file is reloaded. -#endif #ifdef WEBCLIENT static struct @@ -210,7 +204,8 @@ static struct } pm_onload; static int allowphonehome = -1; //if autoupdates are disabled, make sure we get (temporary) permission before phoning home for available updates. (-1=unknown, 0=no, 1=yes) -static qboolean doautoupdate; //updates will be marked (but not applied without the user's actions) +static int doautoupdate; //updates will be marked (but not applied without the user's actions) +static int pm_pendingprompts; //number of prompts that are pending. don't show apply prompts until these are all cleared. static qboolean pkg_updating; //when flagged, further changes are blocked until completion. #else static const qboolean pkg_updating = false; @@ -252,10 +247,16 @@ static struct pm_source_s void *module; //plugins plugupdatesourcefuncs_t *funcs; } *pm_source/*[pm_maxsources]*/; -static int downloadablessequence; //bumped any time any package is purged +static int pm_sequence; //bumped any time any package is purged static void PM_WriteInstalledPackages(void); static void PM_PreparePackageList(void); +static void PM_UpdatePackageList(qboolean autoupdate); +static void PM_PromptApplyChanges(void); +qboolean PM_AreSourcesNew(qboolean doprompt); //prompts to enable sources, or just queries(to do the flashing thing) +#ifdef DOWNLOADMENU +static qboolean PM_DeclinedPackages(char *out, size_t outsize); +#endif #ifdef WEBCLIENT static qboolean PM_SignatureOkay(package_t *p); #endif @@ -390,7 +391,7 @@ static qboolean PM_PurgeOnDisable(package_t *p) static void PM_ValidateAuthenticity(package_t *p, enum hashvalidation_e validated) { - qbyte hashdata[512]; + qbyte hashdata[512+MAX_QPATH]; size_t hashsize = 0; qbyte signdata[1024]; size_t signsize = 0; @@ -452,7 +453,23 @@ static void PM_ValidateAuthenticity(package_t *p, enum hashvalidation_e validate strcpy(authority, "Spike"); //legacy bollocks sig = p->signature; } + + //to validate it, we need a single blob that composits all the parts that might need to be validated. mostly just prefix and file hash. hashsize = Base16_DecodeBlock(p->filesha512, hashdata, sizeof(hashdata)); + + if (p->packprefix && *p->packprefix) + { //need to a bit of extra hashing when we have extra data, to get it to fit. + hashfunc_t *h = &hash_sha2_512; + void *ctx = alloca(h->contextsize); + h->init(ctx); + h->process(ctx, p->packprefix, strlen(p->packprefix)); + h->process(ctx, "\0", 1); + h->process(ctx, hashdata, hashsize); + h->terminate(hashdata, ctx); + hashsize = h->digestsize; + } + + //and the proof... signsize = Base64_DecodeBlock(sig, NULL, signdata, sizeof(signdata)); r = VH_UNSUPPORTED;//preliminary } @@ -472,7 +489,10 @@ static void PM_ValidateAuthenticity(package_t *p, enum hashvalidation_e validate if (r == VH_CORRECT) p->flags |= DPF_SIGNATUREACCEPTED; else if (r == VH_INCORRECT) + { + Con_Printf("Signature verification failed\n"); p->flags |= DPF_SIGNATUREREJECTED; + } else if (validated == VH_CORRECT && p->filesize && (p->filesha1||p->filesha512)) p->flags |= DPF_SIGNATUREACCEPTED; //parent validation was okay, expand that to individual packages too. else if (p->signature) @@ -611,7 +631,7 @@ static void PM_ValidatePackage(package_t *p) } else if (p->qhash) { - searchpathfuncs_t *archive = FS_OpenPackByExtension(pf, NULL, n, n); + searchpathfuncs_t *archive = FS_OpenPackByExtension(pf, NULL, n, n, p->packprefix); if (archive) { @@ -746,7 +766,7 @@ static qboolean PM_MergePackage(package_t *oldp, package_t *newp) } } //these flags should only remain set if set in both. - oldp->flags &= ~(DPF_FORGETONUNINSTALL|DPF_TESTING|DPF_MANIFEST) | (newp->flags & (DPF_FORGETONUNINSTALL|DPF_TESTING|DPF_MANIFEST)); + oldp->flags &= ~(DPF_FORGETONUNINSTALL|DPF_TESTING|DPF_MANIFEST|DPF_GUESSED) | (newp->flags & (DPF_FORGETONUNINSTALL|DPF_TESTING|DPF_MANIFEST|DPF_GUESSED)); for (nd = newp->deps; nd ; nd = nd->next) { @@ -1016,7 +1036,7 @@ static void PM_AddSubListModule(void *module, plugupdatesourcefuncs_t *funcs, co pm_source[i].prefix = BZ_Malloc(strlen(prefix)+1); strcpy(pm_source[i].prefix, prefix); - downloadablessequence++; + pm_sequence++; } if (pm_source[i].funcs && (pm_source[i].status == SRCSTAT_UNTRIED) && (pm_source[i].flags&SRCFL_ENABLED)) //cache only! @@ -1041,7 +1061,7 @@ static void PM_RemSubList(const char *url) } //don't actually remove it, that'd mess up the indexes which could break stuff like PM_ListDownloaded callbacks. :( pm_source[i].flags = SRCFL_HISTORIC; //forget enablement state etc. we won't bother writing it. - downloadablessequence++; //make sure any menus hide it. + pm_sequence++; //make sure any menus hide it. break; } else @@ -1238,6 +1258,8 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha flags |= DPF_TESTING; else if (!strcmp(key, "guessed")) flags |= DPF_GUESSED; + else if (!strcmp(key, "trusted") && (source->parseflags&DPF_ENABLED)) + flags |= DPF_TRUSTED; else if (!strcmp(key, "stale") && source->version==2) flags &= ~DPF_ENABLED; //known about, (probably) cached, but not actually enabled. else if (!strcmp(key, "enabled") && source->version>2) @@ -1248,6 +1270,8 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha { if (!Q_strcasecmp(val, "bin")) p->fsroot = FS_BINARYPATH; + else if (!Q_strcasecmp(val, "lib")) + p->fsroot = FS_LIBRARYPATH; else p->fsroot = FS_ROOT; } @@ -1350,7 +1374,7 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha } else if (!Q_strcasecmp(p->arch, THISARCH)) { - if ((p->fsroot == FS_ROOT || p->fsroot == FS_BINARYPATH) && !*p->gamedir && p->priority == PM_DEFAULTPRIORITY) + if ((p->fsroot == FS_ROOT || p->fsroot == FS_BINARYPATH || p->fsroot == FS_LIBRARYPATH) && !*p->gamedir && p->priority == PM_DEFAULTPRIORITY) p->flags |= DPF_PLUGIN; } else @@ -1462,7 +1486,7 @@ static qboolean PM_ParsePackageList(const char *f, unsigned int parseflags, cons return forcewrite; //it's not the right version. } - downloadablessequence++; + pm_sequence++; while(*f) { @@ -1520,7 +1544,10 @@ static qboolean PM_ParsePackageList(const char *f, unsigned int parseflags, cons pubkey = Auth_GetKnownCertificate(authority, &pubkeysize); if (!pubkey) + { r = VH_AUTHORITY_UNKNOWN; + Con_Printf(CON_ERROR"%s: Unknown signature authority %s\n", url, authority); + } //try and get one of our providers to verify it... for (i = 0; r==VH_UNSUPPORTED && i < cryptolib_count; i++) @@ -1677,7 +1704,7 @@ void PM_EnumeratePlugins(void (*callback)(const char *name, qboolean blocked)) { if (!Q_strncasecmp(d->name, PLUGINPREFIX, strlen(PLUGINPREFIX))) { - qboolean blocked = PM_NameIsInStrings(manifestpackages, va("!%s", p->name)); + qboolean blocked = PM_NameIsInStrings(fs_manifest?fs_manifest->installupd:NULL, va("!%s", p->name)); callback(d->name, blocked); } } @@ -1735,7 +1762,7 @@ static char *PM_GetMetaTextFromFile(vfsfile_t *file, const char *filename, char *qhash = 0; file->Close = Host_StubClose; //so it doesn't go away without our say - archive = FS_OpenPackByExtension(file, NULL, filename, filename); + archive = FS_OpenPackByExtension(file, NULL, filename, filename, ""); if (archive) { flocation_t loc; @@ -1771,6 +1798,8 @@ static char *PM_GetMetaTextFromFile(vfsfile_t *file, const char *filename, char archive->ClosePath(archive); } + else + Con_Printf("No archive in %s\n", filename); file->Close = OriginalClose; return ret; } @@ -1923,6 +1952,7 @@ static package_t *PM_FindExactPackage(const char *packagename, const char *arch, static package_t *PM_FindPackage(const char *packagename); static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath) { + enum fs_relative fsroot = (qintptr_t)param; static const char *knownarch[] = { "x32", "x64", "amd64", "x86", //various x86 ABIs @@ -1972,6 +2002,8 @@ static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtim { if (!(p->flags & DPF_PLUGIN)) continue; + if (p->fsroot != fsroot) + continue; for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype != DEP_FILE) @@ -1990,7 +2022,7 @@ static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtim //FIXME: should be checking whether there's a package that provides the file... - f = FS_OpenVFS(name, "rb", FS_BINARYPATH); + f = FS_OpenVFS(name, "rb", fsroot); if (f) { char qhash[16]; @@ -1998,7 +2030,7 @@ static int QDECL PM_EnumeratedPlugin (const char *name, qofs_t size, time_t mtim VFS_CLOSE(f); } - return PM_FileInstalled_Internal(pkgname, "Plugins/", vmname, name, FS_BINARYPATH, DPF_PLUGIN|DPF_SIGNATUREACCEPTED, metainfo, + return PM_FileInstalled_Internal(pkgname, "Plugins/", vmname, name, fsroot, DPF_PLUGIN|DPF_TRUSTED, metainfo, #ifdef ENABLEPLUGINSBYDEFAULT true #else @@ -2065,6 +2097,7 @@ static void PM_PreparePackageList(void) { unsigned int fl = SRCFL_MANIFEST; char *s = fs_manifest->downloadsurl; + if (fs_manifest->security==MANIFEST_SECURITY_NOT) fl |= SRCFL_DISABLED; //don't trust it, don't even prompt. @@ -2078,18 +2111,29 @@ static void PM_PreparePackageList(void) if (FS_NativePath("", FS_BINARYPATH, nat, sizeof(nat))) { Con_DPrintf("Loading plugins from \"%s\"\n", nat); - Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &pluginsadded, NULL); + Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, (void*)FS_BINARYPATH, NULL); } if (FS_NativePath("", FS_LIBRARYPATH, nat, sizeof(nat))) { Con_DPrintf("Loading plugins from \"%s\"\n", nat); - Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, &pluginsadded, NULL); + Sys_EnumerateFiles(nat, PLUGINPREFIX"*" ARCH_DL_POSTFIX, PM_EnumeratedPlugin, (void*)FS_LIBRARYPATH, NULL); } } #endif } } +void PM_ManifestChanged(ftemanifest_t *man) +{ //gamedir or something changed. reset the package manager stuff. + if (!man) + { //shutting down... don't reload anything. + PM_Shutdown(false); + return; + } + PM_Shutdown(true); + PM_UpdatePackageList(false); +} + void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const char *parent_logical, searchpath_t *search, unsigned int loadstuff, int minpri, int maxpri) { package_t *p; @@ -2097,9 +2141,6 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha char temp[MAX_OSPATH]; int pri; - //figure out what we've previously installed. - PM_PreparePackageList(); - do { //find the lowest used priority above the previous @@ -2117,7 +2158,9 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha { char *qhash = (p->qhash&&*p->qhash)?p->qhash:NULL; unsigned int fsfl = SPF_COPYPROTECTED; - if (!qhash || !(p->flags & DPF_SIGNATUREACCEPTED)) + if (qhash && (p->flags&DPF_TRUSTED)) + ; + else fsfl |= SPF_UNTRUSTED; //never trust it if we can't provide it for (d = p->deps; d; d = d->next) @@ -2143,7 +2186,7 @@ void PM_Shutdown(qboolean soft) size_t i, pm_numoldsources = pm_numsources; //free everything... - downloadablessequence++; + pm_sequence++; pm_numsources = 0; for (i = 0; i < pm_numoldsources; i++) @@ -2491,9 +2534,12 @@ static qboolean PM_MarkPackage(package_t *package, unsigned int markflag) } //just flag stuff as needing updating +#define UPDAVAIL_UPDATE 1 //there's regular updates available. show the updates menu. +#define UPDAVAIL_REQUIRED 2 //important updates that require the user to confirm +#define UPDAVAIL_FORCED 4 //trusted updates that are insisted on by the manifest. unsigned int PM_MarkUpdates (void) { - unsigned int changecount = 0; + unsigned int ret = 0; package_t *p, *o, *b; #ifdef WEBCLIENT package_t *e = NULL; @@ -2501,10 +2547,12 @@ unsigned int PM_MarkUpdates (void) int them; #endif - if (manifestpackages) + doautoupdate = 0; + + if (fs_manifest && fs_manifest->installupd) { char tok[1024]; - char *strings = manifestpackages; + char *strings = fs_manifest->installupd; while (strings && *strings) { qboolean isunwanted = (*tok=='!'); @@ -2519,17 +2567,20 @@ unsigned int PM_MarkUpdates (void) if (p) { if (PM_MarkPackage(p, DPF_AUTOMARKED)) - changecount++; + ret |= UPDAVAIL_UPDATE; } } else if (isunwanted) { PM_UnmarkPackage(p, DPF_AUTOMARKED); //try and unmark it. - changecount++; + ret |= UPDAVAIL_UPDATE; } else if (!(p->flags & DPF_ENABLED)) - changecount++; + ret |= UPDAVAIL_UPDATE; } + + if (ret) + ret = (fs_manifest->security==MANIFEST_SECURITY_INSTALLER)?UPDAVAIL_FORCED:UPDAVAIL_REQUIRED; } for (p = availablepackages; p; p = p->next) @@ -2565,7 +2616,7 @@ unsigned int PM_MarkUpdates (void) { if (PM_MarkPackage(b, p->flags&DPF_MARKED)) { - changecount++; + ret |= UPDAVAIL_UPDATE; PM_UnmarkPackage(p, DPF_MARKED); } } @@ -2577,12 +2628,12 @@ unsigned int PM_MarkUpdates (void) if (pkg_autoupdate.ival >= UPD_STABLE) { if (PM_MarkPackage(e, DPF_AUTOMARKED)) - changecount++; + ret |= UPDAVAIL_UPDATE; } } #endif - return changecount; + return ret; } #if defined(M_Menu_Prompt) || defined(SERVERONLY) @@ -2708,7 +2759,7 @@ static void PM_ListDownloaded(struct dl_download *dl) { if (dl->replycode != 100) pm_source[listidx].status = SRCSTAT_OBTAINED; - downloadablessequence++; + pm_sequence++; if (pm_source[listidx].flags & SRCFL_UNSAFE) PM_ParsePackageList(f, DPF_SIGNATUREACCEPTED, dl->url, pm_source[listidx].prefix); else @@ -2731,54 +2782,24 @@ static void PM_ListDownloaded(struct dl_download *dl) pm_source[listidx].status = SRCSTAT_FAILED_EOF; BZ_Free(f); - if (!doautoupdate && !domanifestinstall) + if (!doautoupdate) return; //don't spam this. - + if (pm_pendingprompts) + return; //check if we're still waiting for (listidx = 0; listidx < pm_numsources; listidx++) { if (pm_source[listidx].status == SRCSTAT_PENDING) - break; + return; } -/* - if (domanifestinstall == MANIFEST_SECURITY_INSTALLER && manifestpackages) - { - package_t *meta; - meta = PM_MarkedPackage(manifestpackages); - if (!meta) - { - meta = PM_FindPackage(manifestpackages); - if (meta) - { - PM_RevertChanges(); - PM_MarkPackage(meta); - PM_ApplyChanges(); - -#ifdef DOWNLOADMENU - if (!isDedicated) - { - if (Key_Dest_Has(kdm_emenu)) - { - Key_Dest_Remove(kdm_emenu); - } -#ifdef MENU_DAT - if (Key_Dest_Has(kdm_gmenu)) - MP_Toggle(0); -#endif - Cmd_ExecuteString("menu_download\n", RESTRICT_LOCAL); - - } -#endif - return; - } - } - } -*/ //if our downloads finished and we want to shove it in the user's face then do so now. - if ((doautoupdate || domanifestinstall == MANIFEST_SECURITY_DEFAULT) && listidx == pm_numsources) + if (doautoupdate && listidx == pm_numsources) { - if (PM_MarkUpdates()) + int updates = PM_MarkUpdates(); + if (updates == UPDAVAIL_FORCED) + PM_PromptApplyChanges(); + else if (updates) { #ifdef DOWNLOADMENU if (!isDedicated) @@ -2836,7 +2857,6 @@ static void PM_Plugin_Source_CacheFinished(void *ctx, vfsfile_t *f) } #endif #if defined(HAVE_CLIENT) && defined(WEBCLIENT) -static void PM_UpdatePackageList(qboolean autoupdate, int retry); static void PM_AllowPackageListQuery_Callback(void *ctx, promptbutton_t opt) { unsigned int i; @@ -2851,17 +2871,14 @@ static void PM_AllowPackageListQuery_Callback(void *ctx, promptbutton_t opt) pm_source[i].flags |= SRCFL_ONCE; } } - PM_UpdatePackageList(false, 0); + PM_UpdatePackageList(false); } #endif //retry 1== -static void PM_UpdatePackageList(qboolean autoupdate, int retry) +static void PM_UpdatePackageList(qboolean autoupdate) { unsigned int i; - if (retry>1) - PM_Shutdown(true); - PM_PreparePackageList(); #ifndef WEBCLIENT @@ -2873,20 +2890,27 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) #else doautoupdate |= autoupdate; + if (COM_CheckParm("-noupdate") || COM_CheckParm("-noupdates")) + allowphonehome = false; #ifdef HAVE_CLIENT - if (pkg_autoupdate.ival >= 1) - allowphonehome = true; - else if (allowphonehome == -1) - { - if (retry) - Menu_Prompt(PM_AllowPackageListQuery_Callback, NULL, "Query updates list?\n", "Okay", NULL, "Nope", true); - return; - } + else if (pkg_autoupdate.ival >= 1) + allowphonehome = true; + else if (allowphonehome == -1) + { + if (doautoupdate) + Menu_Prompt(PM_AllowPackageListQuery_Callback, NULL, "Query updates list?\n", "Okay", NULL, "Nope", true); + return; + } + + if (allowphonehome && PM_AreSourcesNew(true)) + return; #else allowphonehome = true; //erk. #endif - if (COM_CheckParm("-noupdate") || COM_CheckParm("-noupdates")) - allowphonehome = false; + + if (pm_pendingprompts) + return; + autoupdate = doautoupdate; //kick off the initial tier of list-downloads. for (i = 0; i < pm_numsources; i++) @@ -2905,7 +2929,7 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) continue; } if (pm_source[i].status == SRCSTAT_OBTAINED) - return; //already successful once. no need to do it again. + continue; //already successful once. no need to do it again. pm_source[i].flags &= ~SRCFL_ONCE; if (pm_source[i].funcs) @@ -2934,9 +2958,6 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry) if (autoupdate) { -#ifdef WEBCLIENT - doautoupdate = 0; -#endif if (PM_MarkUpdates()) { #ifdef DOWNLOADMENU @@ -3025,6 +3046,8 @@ static void PM_WriteInstalledPackage_v3(package_t *p, char *buf, size_t bufsize) COM_QuotedKeyVal("enabled", "1", buf, bufsize); if (p->flags & DPF_GUESSED) COM_QuotedKeyVal("guessed", "1", buf, bufsize); + if (p->flags & DPF_TRUSTED) + COM_QuotedKeyVal("trusted", "1", buf, bufsize); if (*p->title && strcmp(p->title, p->name)) COM_QuotedKeyVal("title", p->title, buf, bufsize); if (*p->version) @@ -3052,6 +3075,8 @@ static void PM_WriteInstalledPackage_v3(package_t *p, char *buf, size_t bufsize) if (p->fsroot == FS_BINARYPATH) COM_QuotedKeyVal("root", "bin", buf, bufsize); + else if (p->fsroot == FS_LIBRARYPATH) + COM_QuotedKeyVal("root", "lib", buf, bufsize); if (p->packprefix) COM_QuotedKeyVal("packprefix", p->packprefix, buf, bufsize); @@ -3101,6 +3126,11 @@ static void PM_WriteInstalledPackage_v2(package_t *p, char *buf, size_t bufsize) Q_strncatz(buf, " ", bufsize); COM_QuotedConcat(va("stale=1"), buf, bufsize); } + if (p->flags & DPF_TRUSTED) + { //v3+. was signed when installed. + Q_strncatz(buf, " ", bufsize); + COM_QuotedConcat(va("trusted=1"), buf, bufsize); + } if (p->flags & DPF_GUESSED) { Q_strncatz(buf, " ", bufsize); @@ -3253,7 +3283,10 @@ static void PM_WriteInstalledPackages(void) qboolean v3 = false; if (!f) { - Con_Printf("package manager: Can't update installed list\n"); + if (FS_NativePath(INSTALLEDFILES, FS_ROOT, buf, sizeof(buf))) + Con_Printf("package manager: Can't write %s\n", buf); + else + Con_Printf("package manager: Can't update installed list\n"); return; } @@ -3317,6 +3350,7 @@ static void PM_PackageEnabled(package_t *p) if (dep->dtype != DEP_FILE && dep->dtype != DEP_CACHEFILE) continue; COM_FileExtension(dep->name, ext, sizeof(ext)); +/*this is now done after all downloads completed, so we don't keep re-execing configs nor forgetting that they changed. if (!pm_packagesinstalled) if (!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip")) { @@ -3327,7 +3361,7 @@ static void PM_PackageEnabled(package_t *p) } else FS_ReloadPackFiles(); - } + }*/ #ifdef PLUGINS if ((p->flags & DPF_PLUGIN) && !Q_strncasecmp(dep->name, PLUGINPREFIX, strlen(PLUGINPREFIX))) Cmd_ExecuteString(va("plug_load %s\n", dep->name), RESTRICT_LOCAL); @@ -3877,6 +3911,9 @@ static qboolean PM_SignatureOkay(package_t *p) if (p->flags & DPF_SIGNATUREACCEPTED) //sign value is present and correct return true; //go for it. + if (p->flags & DPF_TRUSTED) + return true; + //packages without a signature are only allowed under some limited conditions. //basically we only allow meta packages, pk3s, and paks. @@ -3928,24 +3965,6 @@ static qboolean PM_SignatureOkay(package_t *p) p->previewimage = NULL; }*/ -int PM_IsApplying(qboolean listsonly) -{ - int count = 0; -#ifdef WEBCLIENT - package_t *p; -// int i; - if (!listsonly) - { - for (p = availablepackages; p ; p=p->next) - { - if (p->download) - count++; - } - } -#endif - return count; -} - #ifdef WEBCLIENT static size_t PM_AddFilePackage(const char *packagename, struct gamepacks *gp, size_t numgp) { @@ -4008,6 +4027,17 @@ static void PM_DownloadsCompleted(int iarg, void *data) } } +static unsigned int PM_DownloadingCount(void) +{ + package_t *p; + unsigned int count = 0; + for (p = availablepackages; p ; p=p->next) + { + if (p->download) + count++; + } + return count; +} //looks for the next package that needs downloading, and grabs it static void PM_StartADownload(void) @@ -4016,11 +4046,11 @@ static void PM_StartADownload(void) char *temp; enum fs_relative temproot; package_t *p; - const int simultaneous = PM_IsApplying(true)?1:2; + const int simultaneous = 2; int i; qboolean downloading = false; - for (p = availablepackages; p && simultaneous > PM_IsApplying(false); p=p->next) + for (p = availablepackages; p && simultaneous > PM_DownloadingCount(); p=p->next) { if (p->download) downloading = true; @@ -4139,8 +4169,7 @@ static void PM_StartADownload(void) else { char syspath[MAX_OSPATH]; - FS_NativePath(temp, temproot, syspath, sizeof(syspath)); - Con_Printf("Unable to write %s. Fix permissions before trying to download %s\n", syspath, p->name); + Con_Printf("Unable to write %s. Fix permissions before trying to download %s\n", FS_NativePath(temp, temproot, syspath, sizeof(syspath))?syspath:p->name, p->name); p->trymirrors = 0; //don't bother trying other mirrors if we can't write the file or understand its type. } if (p->download) @@ -4207,6 +4236,24 @@ void PM_LoadMap(const char *package, const char *map) { //not supported, which is a shame because it might have been downloaded via other means. } #endif +unsigned int PM_IsApplying(void) +{ + int ret = 0; +#ifdef WEBCLIENT + size_t i; + if (PM_DownloadingCount()) + ret |= 1; + for (i = 0; i < pm_numsources; i++) + if (pm_source[i].curdl) + ret |= 1; //some source is downloading. +#endif + if (pm_pendingprompts) + ret |= 2; //waiting for user action to complete. + if (doautoupdate) + ret |= 4; //will want to trigger a prompt... + + return ret; +} //'just' starts doing all the things needed to remove/install selected packages void PM_ApplyChanges(void) { @@ -4317,9 +4364,9 @@ void PM_ApplyChanges(void) FS_ReloadPackFiles(); if ((p->flags & DPF_FORGETONUNINSTALL) && !(p->flags & DPF_PRESENT)) - { + { //packages that have no source to redownload #if 1 - downloadablessequence++; + pm_sequence++; PM_FreePackage(p); #else if (p->alternative) @@ -4358,6 +4405,8 @@ void PM_ApplyChanges(void) ((p->flags&DPF_MARKED) && !(p->flags&DPF_ENABLED))) //actually enabled stuff requires actual enablement { p->trymirrors = ~0u; + if (p->flags & DPF_SIGNATUREACCEPTED) + p->flags |= DPF_TRUSTED; //user confirmed it, engine trusts it, we're all okay with any exploits it may have... } } PM_StartADownload(); //and try to do those downloads. @@ -4401,22 +4450,16 @@ void PM_ApplyChanges(void) #endif } -#if defined(M_Menu_Prompt) || defined(SERVERONLY) -//if M_Menu_Prompt is a define, then its a stub... -static void PM_PromptApplyChanges(void) -{ - PM_ApplyChanges(); -} -#else +#ifdef DOWNLOADMENU static qboolean PM_DeclinedPackages(char *out, size_t outsize) { size_t ofs = 0; package_t *p; qboolean ret = false; - if (manifestpackages) + if (fs_manifest) { char tok[1024]; - char *strings = manifestpackages; + char *strings = fs_manifest->installupd; while (strings && *strings) { strings = COM_ParseStringSetSep(strings, ';', tok, sizeof(tok)); @@ -4470,6 +4513,14 @@ static qboolean PM_DeclinedPackages(char *out, size_t outsize) PM_WriteInstalledPackages(); return ret; } +#endif +#if defined(M_Menu_Prompt) || defined(SERVERONLY) +//if M_Menu_Prompt is a define, then its a stub... +static void PM_PromptApplyChanges(void) +{ + PM_ApplyChanges(); +} +#else static void PM_PromptApplyChanges_Callback(void *ctx, promptbutton_t opt) { #ifdef WEBCLIENT @@ -4478,7 +4529,6 @@ static void PM_PromptApplyChanges_Callback(void *ctx, promptbutton_t opt) if (opt == PROMPT_YES) PM_ApplyChanges(); } -static void PM_PromptApplyChanges(void); static void PM_PromptApplyDecline_Callback(void *ctx, promptbutton_t opt) { #ifdef WEBCLIENT @@ -4529,27 +4579,12 @@ static void PM_AddSubList_Callback(void *ctx, promptbutton_t opt) { PM_AddSubList(ctx, "", SRCFL_USER|SRCFL_ENABLED); PM_WriteInstalledPackages(); - PM_UpdatePackageList(false, 0); + PM_UpdatePackageList(false); } Z_Free(ctx); } #endif -//names packages that were listed from the manifest. -//if 'mark' is true, then this is an initial install. -void PM_ManifestPackage(const char *metaname, int security) -{ - domanifestinstall = security; - Z_Free(manifestpackages); - if (metaname) - { - manifestpackages = Z_StrDup(metaname); -// PM_UpdatePackageList(false, false); - } - else - manifestpackages = NULL; -} - qboolean PM_CanInstall(const char *packagename) { int i; @@ -4635,7 +4670,7 @@ void PM_Command_f(void) { if (!loadedinstalled) - PM_UpdatePackageList(false, false); + PM_UpdatePackageList(false); if (!strcmp(act, "list")) { @@ -4882,7 +4917,7 @@ void PM_Command_f(void) //FIXME: regrab if more than an hour ago? if (!allowphonehome) allowphonehome = -1; //trigger a prompt, instead of ignoring it. - PM_UpdatePackageList(false, 0); + PM_UpdatePackageList(false); } else if (!strcmp(act, "refresh")) { //flush package cache, make a new request even if we already got a response from the server. @@ -4895,7 +4930,7 @@ void PM_Command_f(void) } if (!allowphonehome) allowphonehome = -1; //trigger a prompt, instead of ignoring it. - PM_UpdatePackageList(false, 0); + PM_UpdatePackageList(false); } else if (!strcmp(act, "upgrade")) { //auto-mark any updated packages. @@ -5135,14 +5170,19 @@ void PM_AddManifestPackages(ftemanifest_t *man) p->qhash = pack->crcknown?Z_StrDupf("%#x", pack->crc):NULL; dtype = DEP_FILE; + p->packprefix = pack->prefix?Z_StrDup(pack->prefix):NULL; + +Con_Printf("Adding package %s, prefix %s\n", p->name, p->packprefix); //note that this signs the hash(validated with size) with an separately trusted authority and is thus not dependant upon trusting the manifest itself... //that said, we can't necessarily trust any overrides the manifest might include - those parts do not form part of the signature. - if (!pack->prefix && pack->crcknown && strchr(p->name, '/')) + if (pack->crcknown && strchr(p->name, '/')) { p->signature = pack->signature?Z_StrDup(pack->signature):NULL; - p->filesha512 = pack->sha512?Z_StrDup(pack->sha512):NULL; - p->filesize = pack->filesize; } + else if (pack->signature) + Con_Printf(CON_WARNING"Ignoring signature for %s\n", p->name); + p->filesha512 = pack->sha512?Z_StrDup(pack->sha512):NULL; + p->filesize = pack->filesize; { char *c = p->name; @@ -5210,6 +5250,8 @@ void PM_AddManifestPackages(ftemanifest_t *man) PM_AddDep(p, dtype, path); PM_ValidateAuthenticity(p, VH_UNSUPPORTED); + if (p->flags & DPF_SIGNATUREACCEPTED) + p->flags |= DPF_TRUSTED; //user confirmed it, engine trusts it, we're all okay with any exploits it may have... m = PM_InsertPackage(p); if (!m) @@ -5286,7 +5328,7 @@ void QCBUILTIN PF_cl_getpackagemanagerinfo(pubprogfuncs_t *prinst, struct global break; case GPMI_AVAILABLE: #ifdef WEBCLIENT - if (PM_SignatureOkay(p)) + if (PM_SignatureOkay(p) && !(p->flags&DPF_FORGETONUNINSTALL)) RETURN_TSTRING("1"); #endif break; @@ -5346,9 +5388,6 @@ qboolean PM_CanInstall(const char *packagename) void PM_EnumeratePlugins(void (*callback)(const char *name, qboolean blocked)) { } -void PM_ManifestPackage(const char *metaname, int security) -{ -} int PM_IsApplying(qboolean listsonly) { return false; @@ -5382,7 +5421,7 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct emenu_s *m) if (y + 8 < 0 || y >= vid.height) //small optimisation. return; - if (c->dint != downloadablessequence) + if (c->dint != pm_sequence) return; //probably stale #ifdef WEBCLIENT @@ -5539,7 +5578,7 @@ static qboolean MD_Key (struct menucustom_s *c, struct emenu_s *m, int key, unsi package_t *p, *p2; struct packagedep_s *dep, *dep2; dlmenu_t *info = m->data; - if (c->dint != downloadablessequence) + if (c->dint != pm_sequence) return false; //probably stale p = c->dptr; if (key == 'c' && ctrl) @@ -5559,7 +5598,7 @@ static qboolean MD_Key (struct menucustom_s *c, struct emenu_s *m, int key, unsi { //close this submenu thing... info->expandedpackage = NULL; //remove the following map items. - downloadablessequence++; + pm_sequence++; return true; } @@ -5568,7 +5607,7 @@ static qboolean MD_Key (struct menucustom_s *c, struct emenu_s *m, int key, unsi if (dep->dtype == DEP_MAP) { info->expandedpackage = p; - downloadablessequence++; + pm_sequence++; //add the map items after (and shift everything) return true; } @@ -5686,7 +5725,7 @@ static void MD_MapDraw (int x, int y, struct menucustom_s *c, struct emenu_s *m) if (y + 8 < 0 || y >= vid.height) //small optimisation. return; - if (c->dint != downloadablessequence) + if (c->dint != pm_sequence) return; //probably stale #if defined(HAVE_SERVER) @@ -5718,7 +5757,7 @@ static qboolean MD_MapKey (struct menucustom_s *c, struct emenu_s *m, int key, u struct packagedep_s *map = c->dptr2; struct packagedep_s *dep; - if (c->dint != downloadablessequence) + if (c->dint != pm_sequence) return false; //probably stale if (key == K_ENTER || key == K_KP_ENTER || key == K_GP_START || key == K_MOUSE1 || key == K_TOUCH || key == K_GP_DIAMOND_CONFIRM) @@ -5808,7 +5847,9 @@ static qboolean MD_Source_Key (struct menucustom_s *c, struct emenu_s *m, int ke pm_source[c->dint].status = SRCSTAT_PENDING; } PM_WriteInstalledPackages(); - PM_UpdatePackageList(true, 2); + if (pm_source[c->dint].flags & SRCFL_DISABLED) + PM_Shutdown(true); + PM_UpdatePackageList(true); return true; } if (key == K_DEL || key == K_BACKSPACE || key == K_GP_DIAMOND_ALTCONFIRM) @@ -5826,7 +5867,9 @@ static qboolean MD_Source_Key (struct menucustom_s *c, struct emenu_s *m, int ke else return false; //will just be re-added anyway... :( PM_WriteInstalledPackages(); - PM_UpdatePackageList(true, 2); + if (pm_source[c->dint].flags & SRCFL_DISABLED) + PM_Shutdown(true); + PM_UpdatePackageList(true); return true; } return false; @@ -5862,7 +5905,7 @@ static qboolean MD_AutoUpdate_Key (struct menucustom_s *c, struct emenu_s *m, in Cvar_ForceSet(&pkg_autoupdate, nv); PM_WriteInstalledPackages(); - PM_UpdatePackageList(true, 0); + PM_UpdatePackageList(true); } return false; } @@ -5919,7 +5962,7 @@ static int MD_AddMapItems(emenu_t *m, package_t *p, int y, const char *filter) if (filter) if (!strstr(dep->name, filter)) continue; - c = MC_AddCustom(m, 0, y, p, downloadablessequence, NULL); + c = MC_AddCustom(m, 0, y, p, pm_sequence, NULL); c->dptr2 = dep; c->draw = MD_MapDraw; c->key = MD_MapKey; @@ -6035,7 +6078,38 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix, else if (head) desc = va(U8("%s"), head); - c = MC_AddCustom(m, 0, y, p, downloadablessequence, desc); + if (developer.ival) + { + const char *root = "?/"; + switch(p->fsroot) + { + //valid roots + case FS_ROOT: root = "$BASEDIR/"; break; + case FS_BINARYPATH: root = "$BINDIR/"; break; + case FS_LIBRARYPATH: root = "$LIBDIR/"; break; + + //invalid roots... (we don't use fs_game etc for packages because that breaks when switching mods within the same basedir) + case FS_GAME: root = "$FS_GAME/"; break; + case FS_GAMEONLY: root = "$FS_GAMEONLY/"; break; + case FS_BASEGAMEONLY: root = "$FS_BASEGAMEONLY/"; break; + case FS_PUBGAMEONLY: root = "$FS_PUBGAMEONLY/"; break; + case FS_PUBBASEGAMEONLY:root = "FS_PUBBASEGAMEONLY://"; break; + case FS_SYSTEM: root = "file://"; break; + default: root = "?/"; break; + } + if (!desc) + desc = ""; + for (dep = p->deps; dep; dep = dep->next) + if (dep->dtype == DEP_FILE) + { + if (p->qhash || p->packprefix) + desc = va("%s\n%s%s%s%s.%s%s", desc, root, p->gamedir, *p->gamedir?"/":"", dep->name, p->qhash, p->packprefix); + else + desc = va("%s\n%s%s%s%s", desc, root, p->gamedir, *p->gamedir?"/":"", dep->name); + } + } + + c = MC_AddCustom(m, 0, y, p, pm_sequence, desc); c->draw = MD_Draw; c->key = MD_Key; c->common.width = 320-16; @@ -6104,7 +6178,7 @@ static void MD_Download_UpdateStatus(struct emenu_s *m) float framefrac = 0; void *oldpackage = NULL; - if (info->downloadablessequence != downloadablessequence || !info->populated) + if (info->downloadablessequence != pm_sequence || !info->populated) { while(m->options) { @@ -6118,7 +6192,7 @@ static void MD_Download_UpdateStatus(struct emenu_s *m) Z_Free(op); } m->cursoritem = m->selecteditem = m->mouseitem = NULL; - info->downloadablessequence = downloadablessequence; + info->downloadablessequence = pm_sequence; info->populated = false; MC_AddWhiteText(m, 24, 320, 8, "Downloads", false)->text = info->titletext; @@ -6342,20 +6416,21 @@ void Menu_DownloadStuff_f (void) menu->menu.persist = true; menu->predraw = MD_Download_UpdateStatus; menu->key = MD_Download_Key; - info->downloadablessequence = downloadablessequence; + info->downloadablessequence = pm_sequence; Q_strncpyz(info->pathprefix, Cmd_Argv(1), sizeof(info->pathprefix)); if (!*info->pathprefix || !loadedinstalled) - PM_UpdatePackageList(false, true); + PM_UpdatePackageList(false); info->populated = false; //will add any headers as needed } #ifdef WEBCLIENT static void PM_ConfirmSource(void *ctx, promptbutton_t button) -{ +{ //yes='Enable', no='Configure', cancel='Later'... size_t i; + pm_pendingprompts--; if (button == PROMPT_YES) { for (i = 0; i < pm_numsources; i++) @@ -6364,8 +6439,8 @@ static void PM_ConfirmSource(void *ctx, promptbutton_t button) { pm_source[i].flags |= (button == PROMPT_YES)?SRCFL_ENABLED:SRCFL_DISABLED; PM_WriteInstalledPackages(); - Menu_Download_Update(); - return; + PM_UpdatePackageList(true); + break; } } } @@ -6376,6 +6451,8 @@ static void PM_ConfirmSource(void *ctx, promptbutton_t button) if (button == PROMPT_NO) Cmd_ExecuteString("menu_download\n", RESTRICT_LOCAL); + else + doautoupdate = false; //try don't want updates... don't give them any. } } @@ -6418,30 +6495,24 @@ qboolean PM_AreSourcesNew(qboolean doprompt) { qboolean ret = false; #ifdef WEBCLIENT - if (pkg_autoupdate.ival > 0) - { //only prompt if autoupdate is actually enabled. - int i; - for (i = 0; i < pm_numsources; i++) + //only prompt if autoupdate is actually enabled. + int i; + for (i = 0; i < pm_numsources; i++) + { + if (pm_source[i].flags & SRCFL_HISTORIC) + continue; //hidden anyway + if (!(pm_source[i].flags & (SRCFL_ENABLED|SRCFL_DISABLED|SRCFL_PROMPTED)) && (pkg_autoupdate.ival > 0 || (pm_source[i].flags&SRCFL_MANIFEST))) { - if (pm_source[i].flags & SRCFL_HISTORIC) - continue; //hidden anyway - if (!(pm_source[i].flags & (SRCFL_ENABLED|SRCFL_DISABLED|SRCFL_PROMPTED))) + ret = true; //something is dirty. + if (doprompt) { - ret = true; //something is dirty. - if (doprompt) - { - const char *msg = va("Enable update source\n\n^x66F%s", (pm_source[i].flags&SRCFL_MANIFEST)?PrettyHostFromURL(pm_source[i].url):pm_source[i].url); - Menu_Prompt(PM_ConfirmSource, Z_StrDup(pm_source[i].url), msg, "Enable", "Configure", "Later", true); - pm_source[i].flags |= SRCFL_PROMPTED; - } - break; + const char *msg = va("Enable update source\n\n^x66F%s", (pm_source[i].flags&SRCFL_MANIFEST)?PrettyHostFromURL(pm_source[i].url):pm_source[i].url); + pm_pendingprompts++; + Menu_Prompt(PM_ConfirmSource, Z_StrDup(pm_source[i].url), msg, "Enable", "Configure", "Later", true); + pm_source[i].flags |= SRCFL_PROMPTED; } + break; } - /*if (!pluginpromptshown && i < pm_numsources) - { - pluginpromptshown = true; - Menu_Prompt(PM_AutoUpdateQuery, NULL, "Configure update sources now?", "View", NULL, "Later", true); - }*/ } #endif return ret; @@ -6453,13 +6524,13 @@ void Menu_Download_Update(void) if (pkg_autoupdate.ival <= 0) return; - PM_UpdatePackageList(true, 2); + PM_UpdatePackageList(true); } #else void Menu_Download_Update(void) { #ifdef PACKAGEMANAGER - PM_UpdatePackageList(true, 2); + PM_UpdatePackageList(true); #endif } void Menu_DownloadStuff_f (void) diff --git a/engine/client/m_items.c b/engine/client/m_items.c index bcdeb8e4d..ad34becec 100644 --- a/engine/client/m_items.c +++ b/engine/client/m_items.c @@ -2426,6 +2426,16 @@ void M_Menu_Main_f (void) */ SCR_EndLoadingPlaque(); //just in case... + if (!FS_GameIsInitialised()) + { //if you canceled the mods menu, quit instead. + if (!Key_Dest_Has(kdm_prompt) && !Key_Dest_Has(kdm_menu)) + { + M_Menu_Mods_f(); //bring back the mods menu... THERE'S NO ESCAPE!!! (no basedir, so options etc is ponitless) + M_Menu_Quit_f(); //and a quit prompt, cos they probably hit escape or something. + } + return; + } + /* if (0) { diff --git a/engine/client/m_single.c b/engine/client/m_single.c index 665e33323..670e2a4f3 100644 --- a/engine/client/m_single.c +++ b/engine/client/m_single.c @@ -599,6 +599,9 @@ typedef struct { demoloc_t *fs; int pathlen; + //for the basedir picker... + ftemanifest_t *man; + char *command[64]; //these let the menu be used for nearly any sort of file browser. char *ext[64]; int numext; @@ -646,41 +649,48 @@ static void M_DemoDraw(int x, int y, menucustom_t *control, emenu_t *menu) if (!item) info->firstitem = info->items; - if (!info->dragscroll && (keydown[K_MOUSE1] || keydown[K_TOUCH])) + if (keydown[K_MOUSE1] || keydown[K_TOUCHSLIDE]) { - info->dragscroll = 1; - info->mousedownpos = mousecursor_y; - } - if (info->dragscroll && (keydown[K_MOUSE1] || keydown[K_TOUCH])) - { - if (info->mousedownpos >= mousecursor_y+8) + if (!info->dragscroll) { - info->dragscroll = 2; - info->mousedownpos -= 8; - if (info->firstitem->next) - { - if (info->firstitem == info->selected) - info->selected = info->firstitem->next; - info->firstitem = info->firstitem->next; - } + info->dragscroll = 1; + info->mousedownpos = mousecursor_y-y; } - if (info->mousedownpos+8 <= mousecursor_y) + if (info->dragscroll) { - info->dragscroll = 2; - info->mousedownpos += 8; - if (info->firstitem->prev) + if (info->mousedownpos >= mousecursor_y-y+8) { - if (ty <= 24) - info->selected = info->selected->prev; - info->firstitem = info->firstitem->prev; + info->dragscroll = 2; + info->mousedownpos -= 8; + if (info->firstitem->next) + { + if (info->firstitem == info->selected) + info->selected = info->firstitem->next; + info->firstitem = info->firstitem->next; + } + } + if (info->mousedownpos+8 <= mousecursor_y-y) + { + info->dragscroll = 2; + info->mousedownpos += 8; + if (info->firstitem->prev) + { + if (ty <= 24) + info->selected = info->selected->prev; + info->firstitem = info->firstitem->prev; + } } } } + else + info->dragscroll = 0; + + control->common.height = vid.height-y; item = info->firstitem; while(item) { - if (y >= vid.height) + if (y >= y+control->common.height) return; if (!item->isdir) text = va("%-32.32s%6iKB", item->name+info->pathlen, item->size/1024); @@ -739,13 +749,10 @@ static qboolean M_DemoKey(menucustom_t *control, emenu_t *menu, int key, unsigne info->selected = info->selected->next; } break; - case K_TOUCHTAP: - case K_MOUSE1: + case K_MOUSE1: //this is on release if (info->dragscroll == 2) - { - info->dragscroll = 0; break; - } + case K_TOUCHTAP: it = info->firstitem; i = (mousecursor_y - control->common.posy) / 8; while(i > 0 && it && it->next) @@ -767,7 +774,7 @@ static qboolean M_DemoKey(menucustom_t *control, emenu_t *menu, int key, unsigne { if (info->selected->isdir) ShowDemoMenu(menu, info->selected->name); - else + else if (info->numext) { extern int shift_down; int extnum; @@ -945,6 +952,34 @@ static void M_Demo_Remove (emenu_t *menu) { demomenu_t *info = menu->data; M_Demo_Flush(info); + + FS_Manifest_Free(info->man); + info->man = NULL; +} + +static void FS_GameDirPrompted(void *ctx, promptbutton_t btn) +{ + emenu_t *menu = ctx; + if (Menu_IsLinked(&menu->menu)) + { + demomenu_t *info = menu->data; + ftemanifest_t *man = info->man; + if (!man || info->fs->fsroot != FS_SYSTEM) + return; //erk? no exploits! + + switch(btn) + { + case PROMPT_CANCEL: + return; + case PROMPT_YES: + info->man = NULL; + Menu_Unlink(&menu->menu, true); //try to kill the dialog menu. + FS_ChangeGame(man, true, true); //switch to that new gamedir + break; + case PROMPT_NO: + return; + } + } } static void ShowDemoMenu (emenu_t *menu, const char *path) @@ -1042,6 +1077,15 @@ static void ShowDemoMenu (emenu_t *menu, const char *path) // COM_EnumerateFiles(match, DemoAddItem, info); } M_Demo_Flatten(info); + + if (info->man && FS_DirHasAPackage(info->fs->path, info->man)) + { + if (promptmenu) + return //wut? don't confuse basedirs here... + Z_Free(info->man->basedir); + info->man->basedir = Z_StrDup(info->fs->path); + Menu_Prompt(FS_GameDirPrompted, &menu->menu, va("Use this directory?%s", info->fs->path), "Yes!", NULL, "No", true); + } } void M_Demo_Reselect(demomenu_t *info, const char *name) { @@ -1206,4 +1250,55 @@ void M_Menu_MediaFiles_f (void) M_Demo_Reselect(info, info->fs->selname); } #endif + +#include +void M_Menu_BasedirPrompt(ftemanifest_t *man) +{ + demomenu_t *info; + emenu_t *menu; + char *start = getenv("HOME"); + size_t l; + + Key_Dest_Remove(kdm_console); + + menu = M_CreateMenu(sizeof(demomenu_t) + sizeof(demoloc_t)); + menu->remove = M_Demo_Remove; + info = menu->data; + + info->man = man; + + info->fs = (demoloc_t*)(info+1); + info->fs->fsroot = FS_SYSTEM; + if (!start || !*start || (l = strlen(info->fs->path))>=sizeof(info->fs->path)) + strcpy(info->fs->path, "/"); + else + strcpy(info->fs->path, start); + //make sure it has a trailing slash. + l = strlen(info->fs->path); +#ifdef _WIN32 + if (info->fs->path[l-1] == '\\') + info->fs->path[l-1] = '/'; +#endif + if (info->fs->path[l-1] != '/') + { + info->fs->path[l] = '/'; + info->fs->path[l+1] = 0; + } + + info->numext = 0; + + MC_AddWhiteText(menu, 24, 170, 8, va("Where is %s installed?", man->formalname), false); + MC_AddWhiteText(menu, 16, 170, 24, "^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f", false); + + info->list = MC_AddCustom(menu, 0, 32, NULL, 0, NULL); + info->list->common.width = 320; + info->list->draw = M_DemoDraw; + info->list->key = M_DemoKey; + + menu->selecteditem = (menuoption_t*)info->list; + + ShowDemoMenu(menu, info->fs->path); + M_Demo_Reselect(info, info->fs->selname); +} + #endif diff --git a/engine/client/menu.h b/engine/client/menu.h index 1a95e537b..21e99547f 100644 --- a/engine/client/menu.h +++ b/engine/client/menu.h @@ -428,6 +428,8 @@ void M_Complex_Key(emenu_t *currentmenu, int key, int unicode); void M_Script_Init(void); void M_Serverlist_Init(void); +void M_Menu_BasedirPrompt(ftemanifest_t *man); + const char *M_ChooseAutoSave(void); void M_Menu_Main_f (void); void M_Menu_SinglePlayer_f (void); diff --git a/engine/client/sys_linux.c b/engine/client/sys_linux.c index 1f4d6ca52..bb800ae4c 100644 --- a/engine/client/sys_linux.c +++ b/engine/client/sys_linux.c @@ -1195,20 +1195,24 @@ static int Crypto_GenerateSignature(qbyte *hashdata, size_t hashsize, qbyte *sig } static void DoSign(const char *fname, int signtype) { - qbyte digest[1024]; + qbyte digest[1024], digest2[1024]; qbyte signature[2048]; qbyte base64[2048*4]; int sigsize; vfsfile_t *f; const char *auth = "Unknown"; + const char *prefix = ""; int i = COM_CheckParm("-certhost"); if (i) auth = com_argv[i+1]; + i = COM_CheckParm("-prefix"); + if (i) + prefix = com_argv[i+1]; f = FS_OpenVFS(fname, "rb", FS_SYSTEM); if (f && signtype == -1) { //just report the qhash - searchpathfuncs_t *search = FS_OpenPackByExtension(f, NULL, fname, fname); + searchpathfuncs_t *search = FS_OpenPackByExtension(f, NULL, fname, fname, prefix); if (search) { printf("%#08x", search->GeneratePureCRC(search, 0, 0)); @@ -1232,27 +1236,53 @@ static void DoSign(const char *fname, int signtype) h->terminate(digest, ctx); VFS_CLOSE(f); - if (signtype == 0) + //prefix it by the prefix + if (*prefix) + { + h->init(ctx); + h->process(ctx, prefix, strlen(prefix)); + h->process(ctx, "\0", 1); + h->process(ctx, digest, h->digestsize); + h->terminate(digest2, ctx); + } + else + memcpy(digest2, digest, h->digestsize); + + if (signtype == 3) + { //the stupid package crap in fmf files which is different just to be an absolute pain + printf(" prefix \"%s\"", prefix); + printf(" filesize %zu", ts); + + base64[Base16_EncodeBlock(digest, h->digestsize, base64+2048, sizeof(base64)-2048-1)] = 0; + printf(" sha512 \"%s\"", base64+2048); + + sigsize = Crypto_GenerateSignature(digest2, h->digestsize, signature, sizeof(signature)); + Base64_EncodeBlock(signature, sigsize, base64, sizeof(base64)); + printf(" signature \"%s:%s\"\n", auth, base64); + } + else if (signtype == 0) { //old junk + if (*prefix) + printf(" \\\"prefix=%s\\\"", prefix); printf(" \\\"dlsize=%zu\\\"", ts); - Base16_EncodeBlock(digest, h->digestsize, base64, sizeof(base64)); + base64[Base16_EncodeBlock(digest, h->digestsize, base64, sizeof(base64)-1)] = 0; printf(" \\\"sha512=%s\\\"", base64); - sigsize = Crypto_GenerateSignature(digest, h->digestsize, signature, sizeof(signature)); + sigsize = Crypto_GenerateSignature(digest2, h->digestsize, signature, sizeof(signature)); Base64_EncodeBlock(signature, sigsize, base64, sizeof(base64)); printf(" \\\"sign=%s:%s\\\"\n", auth, base64); } else if (signtype == 2) { //spits out the raw signature. - sigsize = Crypto_GenerateSignature(digest, h->digestsize, signature, sizeof(signature)); + sigsize = Crypto_GenerateSignature(digest2, h->digestsize, signature, sizeof(signature)); Base64_EncodeBlock(signature, sigsize, base64, sizeof(base64)); printf("%s", base64); } else { //just spits out the hash - Base16_EncodeBlock(digest, h->digestsize, base64, sizeof(base64)); + base64[Base16_EncodeBlock(digest, h->digestsize, base64, sizeof(base64))] = 0; printf("%s", base64); } } @@ -1461,7 +1491,7 @@ int main (int c, const char **v) nostdout = 1; //begin meta generation helpers - //fteqw -privcert privcert.key -pubcert pubcert.key -sign binaryfile.pk3 + //fteqw -privkey privcert.key -pubkey pubcert.key -certhost Spike -prefix foo -sign binaryfile.pk3 { static struct { @@ -1471,6 +1501,8 @@ int main (int c, const char **v) { {"-sign", 0}, {"-sign2", 2}, + {"-signraw", 2}, + {"-signfmfpkg", 3}, {"-qhash", -1}, {"-sha1", 1}, {"-sha256", 256}, diff --git a/engine/common/cmd.c b/engine/common/cmd.c index d97cdad0d..ee5e7f3bf 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -689,7 +689,7 @@ static const char *replacementq1binds = "bind LSHIFT +speed\n" "bind RSHIFT +speed\n" - "bind + sizeup\n" + "bind = sizeup\n" "bind - sizedown\n" "bind 1 impulse 1\n" diff --git a/engine/common/common.c b/engine/common/common.c index a4bbdb799..1da765f34 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -5548,8 +5548,8 @@ static void COM_Version_f (void) #ifdef FTE_BRANCH Con_Printf("Branch: "STRINGIFY(FTE_BRANCH)"\n"); -#endif -#if defined(SVNREVISION) && defined(SVNDATE) + Con_Printf("Revision: %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); +#elif defined(SVNREVISION) && defined(SVNDATE) if (!strncmp(STRINGIFY(SVNREVISION), "git-", 4)) Con_Printf("GIT Revision: %s - %s\n",STRINGIFY(SVNREVISION), STRINGIFY(SVNDATE)); else @@ -8511,10 +8511,15 @@ char *version_string(void) #ifdef OFFICIAL_RELEASE Q_snprintfz(s, sizeof(s), "%s v%i.%02i", DISTRIBUTION, FTE_VER_MAJOR, FTE_VER_MINOR); #elif defined(SVNREVISION) && defined(SVNDATE) + #ifdef FTE_BRANCH + //something like 'FTE master 6410M-HASH' + Q_snprintfz(s, sizeof(s), "%s %s %s", DISTRIBUTION, STRINGIFY(FTE_BRANCH), STRINGIFY(SVNREVISION)); + #else if (!strncmp(STRINGIFY(SVNREVISION), "git-", 4)) Q_snprintfz(s, sizeof(s), "%s %s", DISTRIBUTION, STRINGIFY(SVNREVISION)); //if both are defined then its a known unmodified svn revision. else Q_snprintfz(s, sizeof(s), "%s SVN %s", DISTRIBUTION, STRINGIFY(SVNREVISION)); //if both are defined then its a known unmodified svn revision. + #endif #else #if defined(SVNREVISION) if (!strncmp(STRINGIFY(SVNREVISION), "git-", 4)) @@ -8569,9 +8574,17 @@ int parse_revision_number(const char *s, qboolean strict) } else { - //[lower-]upper[M] + //svn: [lower-]upper[M] + //git: revision-git-hash[-dirty] + //git: branch-revision-git-hash[-dirty] rev = strtoul(s, &e, 10); - if (*e && strict) + if (!strncmp(e, "-git", 4)) + { //if there's a -dirty in there then its bad. + //we can't validate that the commit id matches the same branch as this build. we'll just have to live with it. + if (strict && strstr(s, "-dirty")) + return false; + } + else if (*e && strict) return false; //something odd. } return rev; diff --git a/engine/common/common.h b/engine/common/common.h index 397b08f22..3aa5aae13 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -739,7 +739,7 @@ typedef struct { MANIFEST_SECURITY_NOT, //don't trust it, don't even allow downloadsurl. MANIFEST_SECURITY_DEFAULT, //the default.fmf file may suggest packages+force sources - MANIFEST_SECURITY_INSTALLER //built-in fmf files can force packages+sources + MANIFEST_SECURITY_INSTALLER //built-in fmf files can 'force' packages+sources } security; //manifest was embedded in the engine. don't assume its already installed, but ask to install it (also, enable some extra permissions for writing dlls) enum @@ -763,6 +763,7 @@ typedef struct #ifdef PACKAGEMANAGER char *downloadsurl; //optional installable files (menu) char *installupd; //which download/updated package to install. + qboolean installable; //(expected) available packages give a playable experience, even if just a basic/demo version. #endif char *protocolname; //the name used for purposes of dpmaster char *defaultexec; //execed after cvars are reset, to give game-specific engine-defaults. @@ -814,6 +815,7 @@ qboolean PM_CanInstall(const char *packagename); void COM_InitFilesystem (void); //does not set up any gamedirs. qboolean FS_DownloadingPackage(void); void FS_CreateBasedir(const char *path); +qboolean FS_DirHasAPackage(char *basedir, ftemanifest_t *man); qboolean FS_ChangeGame(ftemanifest_t *newgame, qboolean allowreloadconfigs, qboolean allowbasedirchange); qboolean FS_GameIsInitialised(void); void FS_Shutdown(void); @@ -844,7 +846,8 @@ void FS_CloseMapPackFile (searchpathfuncs_t *archive); void COM_FlushTempoaryPacks(void); void COM_EnumerateFiles (const char *match, int (QDECL *func)(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath), void *parm); -searchpathfuncs_t *FS_OpenPackByExtension(vfsfile_t *f, searchpathfuncs_t *parent, const char *filename, const char *pakname); +void COM_EnumerateFilesReverse (const char *match, int (QDECL *func)(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath), void *parm); +searchpathfuncs_t *FS_OpenPackByExtension(vfsfile_t *f, searchpathfuncs_t *parent, const char *filename, const char *pakname, const char *pakpathprefix); extern qboolean com_installer; //says that the engine is running in an 'installer' mode, and that the correct basedir is not yet known. extern struct cvar_s registered; diff --git a/engine/common/fs.c b/engine/common/fs.c index cb5bb0a73..ab24873ca 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -12,6 +12,193 @@ #include "winquake.h" #endif + + +#ifdef FTE_TARGET_WEB //for stuff that doesn't work right... +#define FORWEB(a,b) a +#else +#define FORWEB(a,b) b +#endif + + + + +#if !defined(HAVE_LEGACY) || !defined(HAVE_CLIENT) + #define ZFIXHACK +#elif defined(ANDROID) //on android, these numbers seem to be generating major weirdness, so disable these. + #define ZFIXHACK +#elif defined(FTE_TARGET_WEB) //on firefox (but not chrome or ie), these numbers seem to be generating major weirdness, so tone them down significantly by default. + #define ZFIXHACK "set r_polygonoffset_submodel_offset 1\nset r_polygonoffset_submodel_factor 0.05\n" +#else //many quake maps have hideous z-fighting. this provides a way to work around it, although the exact numbers are gpu and bitdepth dependant, and trying to fix it can actually break other things. + #define ZFIXHACK "set r_polygonoffset_submodel_offset 25\nset r_polygonoffset_submodel_factor 0.05\n" +#endif + +/*ezquake cheats and compat*/ +#define EZQUAKECOMPETITIVE "set ruleset_allow_fbmodels 1\nset sv_demoExtensions \"\"\n" +/*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 QUAKEOVERRIDES "set sv_listen_nq 2\n set v_gammainverted 1\nset cl_download_mapsrc \"https://maps.quakeworld.nu/all/\"\nset con_stayhidden 0\nset allow_download_pakcontents 2\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 QUAKESPASMSUCKS +#define QCFG "//schemes quake qw\n" QUAKEOVERRIDES "set com_parseutf8 0\n" QRPCOMPAT +#define KEXCFG "//schemes quake_r2\n" QUAKEOVERRIDES "set com_parseutf8 1\nset campaign 0\nset net_enable_dtls 1\nset sv_mintic 0.016666667\nset sv_maxtic $sv_mintic\nset cl_netfps 60\n" +/*NetQuake reconfiguration, to make certain people feel more at home...*/ +#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\n" QCFG "cfg_save_auto 1\nset pm_bunnyfriction 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" +//nehahra has to be weird with its extra cvars, and buggy fullbrights. +#define NEHCFG QCFG "set nospr32 0\nset cutscene 1\nalias startmap_sp \"map nehstart\"\nr_fb_bmodels 0\nr_fb_models 0\n" +/*stuff that makes dp-only mods work a bit better*/ +#define DPCOMPAT QCFG "gl_specular 1\nset _cl_playermodel \"\"\n set dpcompat_set 1\ndpcompat_console 1\nset dpcompat_corruptglobals 1\nset vid_pixelheight 1\nset dpcompat_set 1\nset dpcompat_console 1\nset r_particlesdesc effectinfo\n" +/*nexuiz/xonotic has a few quirks/annoyances...*/ +#define NEXCFG DPCOMPAT "cl_loopbackprotocol dpp7\nset sv_listen_dp 1\nset sv_listen_qw 0\nset sv_listen_nq 0\nset dpcompat_nopreparse 1\nset sv_bigcoords 1\nset sv_maxairspeed \"30\"\nset sv_jumpvelocity 270\nset sv_mintic \"0.01\"\ncl_nolerp 0\n" +#define XONCFG NEXCFG "set qport $qport_\ncom_parseutf8 1\npr_fixbrokenqccarrays 2\nset pr_csqc_memsize 64m\nset pr_ssqc_memsize 96m\n" +/*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 "//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 "//schemes quake2\n" "set v_gammainverted 1\nset com_parseutf8 0\ncom_gamedirnativecode 1\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q2SERVER)"\n" +/*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/ +#define Q3CFG "//schemes quake3\n" "set v_gammainverted 0\nset snd_ignorecueloops 1\nsetfl g_gametype 0 s\nset gl_clear 1\nset r_clearcolour 0 0 0\nset com_parseutf8 0\ngl_overbright "FORWEB("0","2")"\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_gamedirnativecode 1\nsv_port "STRINGIFY(PORT_Q3SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q3SERVER)"\ncom_protocolversion 68\n" +//#define RMQCFG "sv_bigcoords 1\n" + +#define HLCFG NULL + +#ifndef UPDATEURL + #ifdef HAVE_SSL + #define UPDATEURL(g) "/downloadables.php?game=" #g + #else + #define UPDATEURL(g) NULL + #endif +#endif + +#define QUAKEPROT "FTE-Quake DarkPlaces-Quake" + +typedef struct { + const char *argname; //used if this was used as a parameter. + const char *exename; //used if the exe name contains this + const char *protocolname; //sent to the master server when this is the current gamemode (Typically set for DP compat). + const char *auniquefile[4]; //used if this file is relative from the gamedir. needs just one file + + const char *customexec; + + const char *dir[4]; + const char *poshname; //Full name for the game. + const char *downloadsurl; //url to check for updates. + const char *needpackages; //package name(s) that are considered mandatory for this game to work. + const char *manifestfile; //contents of manifest file to use. +} gamemode_info_t; +static const gamemode_info_t gamemode_info[] = { +#ifdef GAME_SHORTNAME + #ifndef GAME_PROTOCOL + #define GAME_PROTOCOL DISTRIBUTION + #endif + #ifndef GAME_IDENTIFYINGFILES + #define GAME_IDENTIFYINGFILES NULL // + #endif + #ifndef GAME_DEFAULTCMDS + #define GAME_DEFAULTCMDS NULL //doesn't need anything + #endif + #ifndef GAME_BASEGAMES + #define GAME_BASEGAMES "data" + #endif + #ifndef GAME_FULLNAME + #define GAME_FULLNAME FULLENGINENAME + #endif + #ifndef GAME_MANIFESTUPDATE + #define GAME_MANIFESTUPDATE NULL + #endif + + {"-"GAME_SHORTNAME, GAME_SHORTNAME, GAME_PROTOCOL, {GAME_IDENTIFYINGFILES}, GAME_DEFAULTCMDS, {GAME_BASEGAMES}, GAME_FULLNAME, NULL/*updateurl*/, NULL/*needpackages*/, GAME_MANIFESTUPDATE}, +#endif +//note that there is no basic 'fte' gamemode, this is because we aim for network compatability. Darkplaces-Quake is the closest we get. +//this is to avoid having too many gamemodes anyway. + +//mission packs should generally come after the main game to avoid prefering the main game. we violate this for hexen2 as the mission pack is mostly a superset. +//whereas the quake mission packs replace start.bsp making the original episodes unreachable. +//for quake, we also allow extracting all files from paks. some people think it loads faster that way or something. +#ifdef HAVE_LEGACY + //cmdline switch exename protocol name(dpmaster) identifying file exec dir1 dir2 dir3 dir(fte) full name + //use rerelease behaviours if we seem to be running from that dir. + {"-quake_rerel",NULL, "FTE-QuakeRerelease", {"QuakeEX.kpf"}, KEXCFG, {"id1", "*fte"}, "Quake Re-Release", UPDATEURL(Q1)}, + //standard quake + {"-quake", "q1", QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "Quake", UPDATEURL(Q1)}, + //alternative name, because fmf file install names are messy when a single name is used for registry install path. + {"-afterquake", NULL, "FTE-Quake", {"id1/pak0.pak", "id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "AfterQuake", UPDATEURL(Q1), NULL}, + //netquake-specific quake that avoids qw/ with its nquake fuckups, and disables nqisms + {"-netquake", NULL, QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},NQCFG, {"id1"}, "NetQuake", UPDATEURL(Q1)}, + //common variant of fitzquake that includes its own special pak file in the basedir + {"-spasm", NULL, QUAKEPROT, {"quakespasm.pak"}, SPASMCFG,{"/id1"}, "FauxSpasm", UPDATEURL(Q1)}, + //because we can. 'fps_preset spasm' is hopefully close enough... + {"-fitz", "nq", QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},FITZCFG,{"id1"}, "FauxFitz", UPDATEURL(Q1)}, + //because we can + {"-tenebrae", NULL, QUAKEPROT, {"tenebrae/Pak0.pak","id1/quake.rc"},TENEBRAECFG,{"id1", "tenebrae"}, "FauxTenebrae", UPDATEURL(Q1)}, + + //quake's mission packs should not be favoured over the base game nor autodetected + //third part mods also tend to depend upon the mission packs for their huds, even if they don't use any other content. + //and q2 also has a rogue/pak0.pak file that we don't want to find and cause quake2 to look like dissolution of eternity + //so just make these require the same files as good ol' quake. + {"-hipnotic", "hipnotic", "FTE-Hipnotic", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "hipnotic", "*fte"}, "Quake: Scourge of Armagon", UPDATEURL(Q1)}, + {"-rogue", "rogue", "FTE-Rogue", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "rogue", "*fte"}, "Quake: Dissolution of Eternity", UPDATEURL(Q1)}, + + //various quake-dependant non-standalone mods that require hacks + //quoth needed an extra arg just to enable hipnotic hud drawing, it doesn't actually do anything weird, but most engines have a -quoth arg, so lets have one too. + {"-quoth", "quoth", "FTE-Quake", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "quoth", "*fte"}, "Quake: Quoth", UPDATEURL(Q1)}, + {"-nehahra", "nehahra", "FTE-Quake", {"id1/pak0.pak","id1/quake.rc"},NEHCFG, {"id1", "qw", "nehahra", "*fte"}, "Quake: Seal Of Nehahra", UPDATEURL(Q1)}, + //various quake-based standalone mods. + {"-librequake", "librequake","LibreQuake", {"lq1/pak0.pak","lq1/gfx.pk3","lq1/quake.rc"},QCFG, {"lq1"}, "LibreQuake", UPDATEURL(LQ)}, +// {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"},"Nexuiz"}, +// {"-xonotic", "xonotic", "Xonotic", {"data/xonotic-data.pk3dir", +// "data/xonotic-*data*.pk3"}, XONCFG, {"data", "*ftedata"},"Xonotic", UPDATEURL(Xonotic)}, +// {"-spark", "spark", "Spark", {"base/src/progs.src", +// "base/qwprogs.dat", +// "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"}, +// {"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src", +// "basesj/progs.dat", +// "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"}, +// {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte" }, "Remake Quake"}, + +#ifdef HEXEN2 + //hexen2's mission pack generally takes precedence if both are installed. + {"-portals", "h2mp", "FTE-H2MP", {"portals/hexen.rc", + "portals/pak3.pak"}, HEX2CFG,{"data1", "portals", "*fteh2"}, "Hexen II MP", UPDATEURL(H2)}, + {"-hexen2", "hexen2", "FTE-Hexen2", {"data1/pak0.pak"}, HEX2CFG,{"data1", "*fteh2"}, "Hexen II", UPDATEURL(H2)}, +#endif +#if defined(Q2CLIENT) || defined(Q2SERVER) + {"-quake2", "q2", "Quake2", {"baseq2/pak0.pak"}, Q2CFG, {"baseq2", "*fteq2"}, "Quake II", UPDATEURL(Q2)}, + //mods of the above that should generally work. + {"-dday", "dday", "Quake2", {"dday/pak0.pak"}, Q2CFG, {"baseq2", "dday", "*fteq2"}, "D-Day: Normandy"}, +#endif + +#if defined(Q3CLIENT) || defined(Q3SERVER) + {"-quake3", "q3", "Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena", UPDATEURL(Q3), "fteplug_quake3"}, + {"-quake3demo", "q3demo", "Quake3Demo", {"demoq3/pak0.pk3"}, Q3CFG, {"demoq3", "*fteq3"}, "Quake III Arena Demo", NULL, "fteplug_quake3"}, + //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"}, +// {"-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"}, +// {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, +#endif +#if !defined(QUAKETC) && !defined(MINIMAL) +// {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"},"Doom"}, +// {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"},"Doom2"}, +// {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"}, + + //for the luls +// {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, +#endif + /* maintained by FreeHL ~eukara */ + {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, HLCFG, {"logos", "valve"}, "Half-Life", NULL, "fteplug_ffmpeg"}, +#endif + + {NULL} +}; + + + + void FS_BeginManifestUpdates(void); static void QDECL fs_game_callback(cvar_t *var, char *oldvalue); static void COM_InitHomedir(ftemanifest_t *man); @@ -45,7 +232,6 @@ void COM_CheckRegistered (void); void Mods_FlushModList(void); static void FS_ReloadPackFilesFlags(unsigned int reloadflags); static qboolean Sys_SteamHasFile(char *basepath, int basepathlen, char *steamdir, char *fname); -searchpathfuncs_t *FS_OpenPackByExtension(vfsfile_t *f, searchpathfuncs_t *parent, const char *filename, const char *pakname); static void QDECL fs_game_callback(cvar_t *var, char *oldvalue) { @@ -395,6 +581,8 @@ static ftemanifest_t *FS_Manifest_Clone(ftemanifest_t *oldm) newm->package[i].mirrors[j] = Z_StrDup(oldm->package[i].mirrors[j]); } + newm->security = oldm->security; + return newm; } @@ -537,7 +725,13 @@ static ftemanifest_t *FS_Manifest_Create(const char *syspath, const char *basedi { ftemanifest_t *man = Z_Malloc(sizeof(*man)); - man->formalname = Z_StrDup(FULLENGINENAME); + if (syspath) + { + char base[MAX_QPATH]; + COM_FileBase(syspath, base, sizeof(base)); + if (*base && !Q_strcasecmp(base, "default")) + man->formalname = Z_StrDup(base); + } #ifdef _DEBUG //FOR TEMPORARY TESTING ONLY. // man->doinstall = true; @@ -558,6 +752,7 @@ static ftemanifest_t *FS_Manifest_Create(const char *syspath, const char *basedi static qboolean FS_Manifest_ParsePackage(ftemanifest_t *man, int packagetype) { + //CMD [deparch] packagename qhash [archivedfilename] [prefix skip/this/] [mirror url] [[filesize foo] [sha512 hash] [signature "base64"]] char *path = ""; unsigned int crc = 0; qboolean crcknown = false; @@ -924,7 +1119,7 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) FS_Manifest_ParsePackage(man, mdt_singlepackage); else if (!Q_strcasecmp(cmd, "basedir")) { //allow explicit basedirs when this is an actual file on the user's system, and we don't have an explicit one. - //this should only happen from parsing /etc/fte/*.fmf + //this should only happen from parsing /etc/xdg/games/*.fmf if (!man->basedir && man->filename) man->basedir = Z_StrDup(Cmd_Argv(1)); } @@ -935,9 +1130,95 @@ static qboolean FS_Manifest_ParseTokens(ftemanifest_t *man) } return result; } +//if the manifest omits some expected stuff, give it some defaults to match known game names (so fmf files can defer to the engine instead of having to be maintained separately). +static void FS_Manifest_SetDefaultSettings(ftemanifest_t *man, const gamemode_info_t *game) +{ + int j; + + if (game) + { + //if there's no base dirs, edit the manifest to give it its default ones. + for (j = 0; j < sizeof(man->gamepath) / sizeof(man->gamepath[0]); j++) + { + if (man->gamepath[j].path && (man->gamepath[j].flags&GAMEDIR_BASEGAME)) + break; + } + if (j == sizeof(man->gamepath) / sizeof(man->gamepath[0])) + { + for (j = 0; j < 4; j++) + if (game->dir[j]) + { + Cmd_TokenizeString(va("basegame \"%s\"", game->dir[j]), false, false); + FS_Manifest_ParseTokens(man); + } + } + + if (!man->schemes) + { + Cmd_TokenizeString(va("schemes \"%s\"", game->argname+1), false, false); + FS_Manifest_ParseTokens(man); + } + +#ifdef PACKAGEMANAGER + if (!man->downloadsurl && game->downloadsurl) + { +#ifndef FTE_TARGET_WEB + if (*game->downloadsurl == '/') + { + conchar_t musite[256], *e; + char site[256]; + char *oldprefix = "http://fte."; + char *newprefix = "https://updates."; + e = COM_ParseFunString(CON_WHITEMASK, ENGINEWEBSITE, musite, sizeof(musite), false); + COM_DeFunString(musite, e, site, sizeof(site)-1, true, true); + if (!strncmp(site, oldprefix, strlen(oldprefix))) + { + memmove(site+strlen(newprefix), site+strlen(oldprefix), strlen(site)-strlen(oldprefix)+1); + memcpy(site, newprefix, strlen(newprefix)); + } + man->downloadsurl = Z_StrDupf("%s%s", site, game->downloadsurl); + } + else +#endif + man->downloadsurl = Z_StrDup(game->downloadsurl); + FS_Manifest_ParseTokens(man); + } + if (!man->installupd && game->needpackages) + man->installupd = Z_StrDup(game->needpackages); +#endif + + if (!man->protocolname) + man->protocolname = Z_StrDup(game->protocolname); + + if (!man->defaultexec && game->customexec) + { + const char *e = game->customexec; + while (e[0] == '/' && e[1] == '/') + { + e+=2; + while(*e) + { + if (*e++ == '\n') + break; + } + } + man->defaultexec = Z_StrDup(e); + } + + if (!man->formalname) + man->formalname = Z_StrDup(game->poshname); + } + + + if (!man->formalname && man->installation && *man->installation) + man->formalname = Z_StrDup(man->installation); + else if (!man->formalname) + man->formalname = Z_StrDup(FULLENGINENAME); +} //read a manifest file ftemanifest_t *FS_Manifest_ReadMem(const char *fname, const char *basedir, const char *data) { + int i; ftemanifest_t *man; if (!data) return NULL; @@ -968,6 +1249,17 @@ ftemanifest_t *FS_Manifest_ReadMem(const char *fname, const char *basedir, const #endif FS_Manifest_ParseTokens(man); } + if (man->installation) + { //if we know about it, fill in some defaults... + for (i = 0; gamemode_info[i].argname; i++) + { + if (!strcmp(man->installation, gamemode_info[i].argname+1)) + { + FS_Manifest_SetDefaultSettings(man, &gamemode_info[i]); + break; + } + } + } #ifdef SVNREVISION //svnrevision is often '-', which means we can't just use it as a constant. @@ -2619,6 +2911,7 @@ vfsfile_t *QDECL FS_OpenVFS(const char *filename, const char *mode, enum fs_rela if (!FS_NativePath(filename, relativeto, fullname, sizeof(fullname))) return NULL; break; + case FS_LIBRARYPATH: case FS_BINARYPATH: if (!FS_NativePath(filename, relativeto, fullname, sizeof(fullname))) return NULL; @@ -2632,12 +2925,16 @@ vfsfile_t *QDECL FS_OpenVFS(const char *filename, const char *mode, enum fs_rela { if (!try_snprintf(fullname, sizeof(fullname), "%s%s", com_homepath, filename)) return NULL; + if (*mode == 'w') + COM_CreatePath(fullname); vfs = VFSOS_Open(fullname, mode); if (vfs) return vfs; } if (!try_snprintf(fullname, sizeof(fullname), "%s%s", com_gamepath, filename)) return NULL; + if (*mode == 'w') + COM_CreatePath(fullname); return VFSOS_Open(fullname, mode); default: Sys_Error("FS_OpenVFS: Bad relative path (%i)", relativeto); @@ -3106,14 +3403,14 @@ searchpathfuncs_t *COM_EnumerateFilesPackage (char *matches, const char *package if (com_homepathenabled) { //try the homedir Q_snprintfz(syspath, sizeof(syspath), "%s%s", com_homepath, package); - handle = FS_OpenPackByExtension(VFSOS_Open(syspath, "rb"), NULL, package, package); + handle = FS_OpenPackByExtension(VFSOS_Open(syspath, "rb"), NULL, package, package, ""); } else handle = NULL; if (!handle) { //now go for the basedir to see if ther. Q_snprintfz(syspath, sizeof(syspath), "%s%s", com_gamepath, package); - handle = FS_OpenPackByExtension(VFSOS_Open(syspath, "rb"), NULL, package, package); + handle = FS_OpenPackByExtension(VFSOS_Open(syspath, "rb"), NULL, package, package, ""); } if (handle) @@ -3427,7 +3724,7 @@ static void FS_LoadWildDataFiles (filelist_t *list, wildpaks_t *wp) list->maxfiles = list->maxnames = 0; } -searchpathfuncs_t *FS_OpenPackByExtension(vfsfile_t *f, searchpathfuncs_t *parent, const char *filename, const char *pakname) +searchpathfuncs_t *FS_OpenPackByExtension(vfsfile_t *f, searchpathfuncs_t *parent, const char *filename, const char *pakname, const char *pakpathprefix) { searchpathfuncs_t *pak; int j; @@ -3441,7 +3738,7 @@ searchpathfuncs_t *FS_OpenPackByExtension(vfsfile_t *f, searchpathfuncs_t *paren continue; if (!strcmp(ext, searchpathformats[j].extension)) { - pak = searchpathformats[j].OpenNew(f, parent, filename, pakname, ""); + pak = searchpathformats[j].OpenNew(f, parent, filename, pakname, pakpathprefix); if (pak) return pak; Con_Printf("Unable to open %s - corrupt?\n", pakname); @@ -3548,7 +3845,10 @@ void FS_AddHashedPackage(searchpath_t **oldpaths, const char *parentpath, const int truecrc = handle->GeneratePureCRC(handle, 0, false); if (truecrc != (int)strtoul(qhash, NULL, 0)) { - Con_Printf(CON_ERROR "File \"%s\" has hash %#x (required: %s). Please delete it or move it away\n", lname, truecrc, qhash); + if (pakprefix && *pakprefix) + Con_Printf(CON_ERROR "File \"%s\" [prefix %s] has hash %#x (required: %s). Please delete it or move it away\n", lname, pakprefix, truecrc, qhash); + else + Con_Printf(CON_ERROR "File \"%s\" has hash %#x (required: %s). Please delete it or move it away\n", lname, truecrc, qhash); handle->ClosePath(handle); handle = NULL; } @@ -3711,7 +4011,7 @@ static void FS_AddDataFiles(searchpath_t **oldpaths, const char *purepath, const snprintf (pakfile, sizeof(pakfile), "quakespasm.%s", extension); handle = FS_GetOldPath(oldpaths, logicalfile, &keptflags); if (!handle) - handle = FS_OpenPackByExtension(VFSOS_Open(pakfile, "rb"), NULL, pakfile, pakfile); + handle = FS_OpenPackByExtension(VFSOS_Open(pakfile, "rb"), NULL, pakfile, pakfile, ""); if (handle) //logically should have SPF_EXPLICIT set, but that would give it a worse gamedir depth FS_AddPathHandle(oldpaths, "", pakfile, handle, "", SPF_COPYPROTECTED|SPF_PRIVATE, (unsigned int)-1); } @@ -3765,12 +4065,15 @@ static searchpath_t *FS_AddPathHandle(searchpath_t **oldpaths, const char *purep } search = (searchpath_t*)Z_Malloc (sizeof(searchpath_t)); - search->flags = flags; search->handle = handle; Q_strncpyz(search->purepath, purepath, sizeof(search->purepath)); Q_strncpyz(search->logicalpath, logicalpath, sizeof(search->logicalpath)); - if (prefix) + if (prefix && *prefix) + { Q_strncpyz(search->prefix, prefix, sizeof(search->prefix)); + flags |= SPF_COPYPROTECTED; //don't do downloading weirdness when there's weird prefix shenanegans going on. + } + search->flags = flags; flags &= ~SPF_WRITABLE; @@ -4209,185 +4512,6 @@ void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths) #endif } -#if !defined(HAVE_LEGACY) || !defined(HAVE_CLIENT) - #define ZFIXHACK -#elif defined(ANDROID) //on android, these numbers seem to be generating major weirdness, so disable these. - #define ZFIXHACK -#elif defined(FTE_TARGET_WEB) //on firefox (but not chrome or ie), these numbers seem to be generating major weirdness, so tone them down significantly by default. - #define ZFIXHACK "set r_polygonoffset_submodel_offset 1\nset r_polygonoffset_submodel_factor 0.05\n" -#else //many quake maps have hideous z-fighting. this provides a way to work around it, although the exact numbers are gpu and bitdepth dependant, and trying to fix it can actually break other things. - #define ZFIXHACK "set r_polygonoffset_submodel_offset 25\nset r_polygonoffset_submodel_factor 0.05\n" -#endif - -#ifdef FTE_TARGET_WEB //for stuff that doesn't work right... -#define FORWEB(a,b) a -#else -#define FORWEB(a,b) b -#endif - -/*ezquake cheats and compat*/ -#define EZQUAKECOMPETITIVE "set ruleset_allow_fbmodels 1\nset sv_demoExtensions \"\"\n" -/*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 QUAKEOVERRIDES "set sv_listen_nq 2\n set v_gammainverted 1\nset cl_download_mapsrc \"https://maps.quakeworld.nu/all/\"\nset con_stayhidden 0\nset allow_download_pakcontents 2\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 QUAKESPASMSUCKS -#define QCFG "//schemes quake qw\n" QUAKEOVERRIDES "set com_parseutf8 0\n" QRPCOMPAT -#define KEXCFG "//schemes quake_r2\n" QUAKEOVERRIDES "set com_parseutf8 1\nset campaign 0\nset net_enable_dtls 1\nset sv_mintic 0.016666667\nset sv_maxtic $sv_mintic\nset cl_netfps 60\n" -/*NetQuake reconfiguration, to make certain people feel more at home...*/ -#define NQCFG "//disablehomedir 1\n//mainconfig ftenq\n" QCFG "cfg_save_auto 1\nset pm_bunnyfriction 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" -//nehahra has to be weird with its extra cvars, and buggy fullbrights. -#define NEHCFG QCFG "set nospr32 0\nset cutscene 1\nalias startmap_sp \"map nehstart\"\nr_fb_bmodels 0\nr_fb_models 0\n" -/*stuff that makes dp-only mods work a bit better*/ -#define DPCOMPAT QCFG "gl_specular 1\nset _cl_playermodel \"\"\n set dpcompat_set 1\ndpcompat_console 1\nset dpcompat_corruptglobals 1\nset vid_pixelheight 1\nset dpcompat_set 1\nset dpcompat_console 1\nset r_particlesdesc effectinfo\n" -/*nexuiz/xonotic has a few quirks/annoyances...*/ -#define NEXCFG DPCOMPAT "cl_loopbackprotocol dpp7\nset sv_listen_dp 1\nset sv_listen_qw 0\nset sv_listen_nq 0\nset dpcompat_nopreparse 1\nset sv_bigcoords 1\nset sv_maxairspeed \"30\"\nset sv_jumpvelocity 270\nset sv_mintic \"0.01\"\ncl_nolerp 0\n" -#define XONCFG NEXCFG "set qport $qport_\ncom_parseutf8 1\npr_fixbrokenqccarrays 2\nset pr_csqc_memsize 64m\nset pr_ssqc_memsize 96m\n" -/*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 "//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 "//schemes quake2\n" "set v_gammainverted 1\nset com_parseutf8 0\ncom_gamedirnativecode 1\nset sv_bigcoords 0\nsv_port "STRINGIFY(PORT_Q2SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q2SERVER)"\n" -/*Q3's ui doesn't like empty model/headmodel/handicap cvars, even if the gamecode copes*/ -#define Q3CFG "//schemes quake3\n" "set v_gammainverted 0\nset snd_ignorecueloops 1\nsetfl g_gametype 0 s\nset gl_clear 1\nset r_clearcolour 0 0 0\nset com_parseutf8 0\ngl_overbright "FORWEB("0","2")"\nseta model sarge\nseta headmodel sarge\nseta handicap 100\ncom_gamedirnativecode 1\nsv_port "STRINGIFY(PORT_Q3SERVER)"\ncl_defaultport "STRINGIFY(PORT_Q3SERVER)"\ncom_protocolversion 68\n" -//#define RMQCFG "sv_bigcoords 1\n" - -#define HLCFG "plug_load ffmpeg\n" - -#ifndef UPDATEURL - #ifdef HAVE_SSL - #define UPDATEURL(g) "/downloadables.php?game=" #g - #else - #define UPDATEURL(g) NULL - #endif -#endif - -#define QUAKEPROT "FTE-Quake DarkPlaces-Quake" - -typedef struct { - const char *argname; //used if this was used as a parameter. - const char *exename; //used if the exe name contains this - const char *protocolname; //sent to the master server when this is the current gamemode (Typically set for DP compat). - const char *auniquefile[4]; //used if this file is relative from the gamedir. needs just one file - - const char *customexec; - - const char *dir[4]; - const char *poshname; //Full name for the game. - const char *downloadsurl; //url to check for updates. - const char *needpackages; //package name(s) that are considered mandatory for this game to work. - const char *manifestfile; //contents of manifest file to use. -} gamemode_info_t; -static const gamemode_info_t gamemode_info[] = { -#ifdef GAME_SHORTNAME - #ifndef GAME_PROTOCOL - #define GAME_PROTOCOL DISTRIBUTION - #endif - #ifndef GAME_IDENTIFYINGFILES - #define GAME_IDENTIFYINGFILES NULL // - #endif - #ifndef GAME_DEFAULTCMDS - #define GAME_DEFAULTCMDS NULL //doesn't need anything - #endif - #ifndef GAME_BASEGAMES - #define GAME_BASEGAMES "data" - #endif - #ifndef GAME_FULLNAME - #define GAME_FULLNAME FULLENGINENAME - #endif - #ifndef GAME_MANIFESTUPDATE - #define GAME_MANIFESTUPDATE NULL - #endif - - {"-"GAME_SHORTNAME, GAME_SHORTNAME, GAME_PROTOCOL, {GAME_IDENTIFYINGFILES}, GAME_DEFAULTCMDS, {GAME_BASEGAMES}, GAME_FULLNAME, NULL/*updateurl*/, NULL/*needpackages*/, GAME_MANIFESTUPDATE}, -#endif -//note that there is no basic 'fte' gamemode, this is because we aim for network compatability. Darkplaces-Quake is the closest we get. -//this is to avoid having too many gamemodes anyway. - -//mission packs should generally come after the main game to avoid prefering the main game. we violate this for hexen2 as the mission pack is mostly a superset. -//whereas the quake mission packs replace start.bsp making the original episodes unreachable. -//for quake, we also allow extracting all files from paks. some people think it loads faster that way or something. -#ifdef HAVE_LEGACY - //cmdline switch exename protocol name(dpmaster) identifying file exec dir1 dir2 dir3 dir(fte) full name - //use rerelease behaviours if we seem to be running from that dir. - {"-quake_rerel",NULL, "FTE-QuakeRerelease", {"QuakeEX.kpf"}, KEXCFG, {"id1", "*fte"}, "Quake Re-Release", UPDATEURL(Q1)}, - //standard quake - {"-quake", "q1", QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "Quake", UPDATEURL(Q1)}, - //alternative name, because fmf file install names are messy when a single name is used for registry install path. - {"-afterquake", NULL, "FTE-Quake", {"id1/pak0.pak", "id1/quake.rc"},QCFG, {"id1", "qw", "*fte"}, "AfterQuake", UPDATEURL(Q1)}, - //netquake-specific quake that avoids qw/ with its nquake fuckups, and disables nqisms - {"-netquake", NULL, QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},NQCFG, {"id1"}, "NetQuake", UPDATEURL(Q1)}, - //common variant of fitzquake that includes its own special pak file in the basedir - {"-spasm", NULL, QUAKEPROT, {"quakespasm.pak"}, SPASMCFG,{"/id1"}, "FauxSpasm", UPDATEURL(Q1)}, - //because we can. 'fps_preset spasm' is hopefully close enough... - {"-fitz", "nq", QUAKEPROT, {"id1/pak0.pak","id1/quake.rc"},FITZCFG,{"id1"}, "FauxFitz", UPDATEURL(Q1)}, - //because we can - {"-tenebrae", NULL, QUAKEPROT, {"tenebrae/Pak0.pak","id1/quake.rc"},TENEBRAECFG,{"id1", "tenebrae"}, "FauxTenebrae", UPDATEURL(Q1)}, - - //quake's mission packs should not be favoured over the base game nor autodetected - //third part mods also tend to depend upon the mission packs for their huds, even if they don't use any other content. - //and q2 also has a rogue/pak0.pak file that we don't want to find and cause quake2 to look like dissolution of eternity - //so just make these require the same files as good ol' quake. - {"-hipnotic", "hipnotic", "FTE-Hipnotic", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "hipnotic", "*fte"}, "Quake: Scourge of Armagon", UPDATEURL(Q1)}, - {"-rogue", "rogue", "FTE-Rogue", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "rogue", "*fte"}, "Quake: Dissolution of Eternity", UPDATEURL(Q1)}, - - //various quake-dependant non-standalone mods that require hacks - //quoth needed an extra arg just to enable hipnotic hud drawing, it doesn't actually do anything weird, but most engines have a -quoth arg, so lets have one too. - {"-quoth", "quoth", "FTE-Quake", {"id1/pak0.pak","id1/quake.rc"},QCFG, {"id1", "qw", "quoth", "*fte"}, "Quake: Quoth", UPDATEURL(Q1)}, - {"-nehahra", "nehahra", "FTE-Quake", {"id1/pak0.pak","id1/quake.rc"},NEHCFG, {"id1", "qw", "nehahra", "*fte"}, "Quake: Seal Of Nehahra", UPDATEURL(Q1)}, - //various quake-based standalone mods. - {"-librequake", "librequake","LibreQuake", {"lq1/pak0.pak","lq1/gfx.pk3","lq1/quake.rc"},QCFG, {"lq1"}, "LibreQuake", UPDATEURL(LQ)}, -// {"-nexuiz", "nexuiz", "Nexuiz", {"nexuiz.exe"}, NEXCFG, {"data", "*ftedata"},"Nexuiz"}, -// {"-xonotic", "xonotic", "Xonotic", {"data/xonotic-data.pk3dir", -// "data/xonotic-*data*.pk3"}, XONCFG, {"data", "*ftedata"},"Xonotic", UPDATEURL(Xonotic)}, -// {"-spark", "spark", "Spark", {"base/src/progs.src", -// "base/qwprogs.dat", -// "base/pak0.pak"}, DMFCFG, {"base", }, "Spark"}, -// {"-scouts", "scouts", "FTE-SJ", {"basesj/src/progs.src", -// "basesj/progs.dat", -// "basesj/pak0.pak"}, NULL, {"basesj", }, "Scouts Journey"}, -// {"-rmq", "rmq", "RMQ", {NULL}, RMQCFG, {"id1", "qw", "rmq", "*fte" }, "Remake Quake"}, - -#ifdef HEXEN2 - //hexen2's mission pack generally takes precedence if both are installed. - {"-portals", "h2mp", "FTE-H2MP", {"portals/hexen.rc", - "portals/pak3.pak"}, HEX2CFG,{"data1", "portals", "*fteh2"}, "Hexen II MP", UPDATEURL(H2)}, - {"-hexen2", "hexen2", "FTE-Hexen2", {"data1/pak0.pak"}, HEX2CFG,{"data1", "*fteh2"}, "Hexen II", UPDATEURL(H2)}, -#endif -#if defined(Q2CLIENT) || defined(Q2SERVER) - {"-quake2", "q2", "Quake2", {"baseq2/pak0.pak"}, Q2CFG, {"baseq2", "*fteq2"}, "Quake II", UPDATEURL(Q2)}, - //mods of the above that should generally work. - {"-dday", "dday", "Quake2", {"dday/pak0.pak"}, Q2CFG, {"baseq2", "dday", "*fteq2"}, "D-Day: Normandy"}, -#endif - -#if defined(Q3CLIENT) || defined(Q3SERVER) - {"-quake3", "q3", "Quake3", {"baseq3/pak0.pk3"}, Q3CFG, {"baseq3", "*fteq3"}, "Quake III Arena", UPDATEURL(Q3), "quake3"}, - {"-quake3demo", "q3demo", "Quake3Demo", {"demoq3/pak0.pk3"}, Q3CFG, {"demoq3", "*fteq3"}, "Quake III Arena Demo", NULL, "quake3"}, - //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"}, -// {"-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"}, -// {"-warsow", "warsow", "FTE-Warsow", {"basewsw/pak0.pk3"}, NULL, {"basewsw", "*ftewsw"}, "Warsow"}, -#endif -#if !defined(QUAKETC) && !defined(MINIMAL) -// {"-doom", "doom", "FTE-Doom", {"doom.wad"}, NULL, {"*", "*ftedoom"},"Doom"}, -// {"-doom2", "doom2", "FTE-Doom2", {"doom2.wad"}, NULL, {"*", "*ftedoom"},"Doom2"}, -// {"-doom3", "doom3", "FTE-Doom3", {"doom3.wad"}, NULL, {"based3", "*ftedoom3"},"Doom3"}, - - //for the luls -// {"-diablo2", NULL, "FTE-Diablo2", {"d2music.mpq"}, NULL, {"*", "*fted2"}, "Diablo 2"}, -#endif - /* maintained by FreeHL ~eukara */ - {"-halflife", "halflife", "FTE-HalfLife", {"valve/liblist.gam"}, HLCFG, {"logos", "valve"}, "Half-Life"}, -#endif - - {NULL} -}; - static void QDECL Q_strnlowercatz(char *d, const char *s, int n) { int c = strlen(d); @@ -4911,7 +5035,7 @@ static void FS_ReloadPackFilesFlags(unsigned int reloadflags) const char *pakname = com_argv[i+1]; searchpathfuncs_t *pak; vfsfile_t *vfs = VFSOS_Open(pakname, "rb"); - pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname); + pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname, ""); if (pak) //logically should have SPF_EXPLICIT set, but that would give it a worse gamedir depth FS_AddPathHandle(&oldpaths, "", pakname, pak, "", SPF_COPYPROTECTED, reloadflags); i = COM_CheckNextParm ("-basepack", i); @@ -5630,11 +5754,37 @@ qboolean Sys_DoDirectoryPrompt(char *basepath, size_t basepathsize, const char * } //#define Sys_DoDirectoryPrompt(bp,bps,game,savename) false +#if defined(__linux__) || defined(__unix__) || defined(__apple__) +static qboolean Sys_XDGHasDirectory(char *basepath, int basepathlen, const char *pregame, const char *gamename, const char *postgame) +{ //returns true if the gamedir can be found, and fills in the basepath. + struct stat sb; + const char *dirs = getenv("XDG_DATA_DIRS"), *s; + char dir[MAX_OSPATH]; + if (!dirs||!*dirs) + dirs = "/usr/local/share:/usr/share"; + + while (dirs && *dirs) + { + dirs = COM_ParseStringSetSep(dirs, ':', dir, sizeof(dir)); + + s = va("%s/%s%s%s/", dir, pregame, gamename, postgame); + if (stat(s, &sb) == 0) + { + if (S_ISDIR(sb.st_mode)) + { + Q_strncpyz(basepath, s, basepathlen); + return true; + } + } + } + return false; +} +#endif + +//FIXME: replace with a callback version, for multiple results. qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *basepath, int basepathlen, qboolean allowprompts) { #if defined(__linux__) || defined(__unix__) || defined(__apple__) - struct stat sb; - char *s; if (!*gamename) gamename = "quake"; //just a paranoia fallback, shouldn't be needed. if (!strcmp(gamename, "quake_rerel")) @@ -5649,14 +5799,8 @@ qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *base if (Sys_SteamHasFile(basepath, basepathlen, "Quake", "id1/pak0.pak")) //people may have tried to sanitise it already. return true; - if (stat("/usr/share/quake/", &sb) == 0) - { - if (S_ISDIR(sb.st_mode)) - { - Q_strncpyz(basepath, "/usr/share/quake/", basepathlen); - return true; - } - } + if (Sys_XDGHasDirectory(basepath, basepathlen, "", gamename, "")) + return true; } else if (!strcmp(gamename, "quake2")) { @@ -5670,24 +5814,14 @@ qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *base gamename = "hexen2"; } - s = va("/usr/share/games/%s/", gamename); - if (stat(s, &sb) == 0) - { - if (S_ISDIR(sb.st_mode)) - { - Q_strncpyz(basepath, s, basepathlen); - return true; - } - } - s = va("/usr/share/games/%s-demo/", gamename); - if (stat(s, &sb) == 0) - { - if (S_ISDIR(sb.st_mode)) - { - Q_strncpyz(basepath, s, basepathlen); - return true; - } - } + if (Sys_XDGHasDirectory(basepath, basepathlen, "games/", gamename, "")) + return true; + if (Sys_XDGHasDirectory(basepath, basepathlen, "", gamename, "")) + return true; + if (Sys_XDGHasDirectory(basepath, basepathlen, "games/", gamename, "-demo")) + return true; + if (Sys_XDGHasDirectory(basepath, basepathlen, "", gamename, "-demo")) + return true; #if defined(HAVE_CLIENT) //this is *really* unfortunate, but doing this crashes the browser if (allowprompts && poshname && *gamename && !COM_CheckParm("-manifest")) @@ -5739,7 +5873,7 @@ void FS_Shutdown(void) Mods_FlushModList(); #ifdef PACKAGEMANAGER - PM_ManifestPackage(NULL, false); + PM_ManifestChanged(NULL); #endif FS_FreePaths(); Sys_DestroyMutex(fs_thread_mutex); @@ -5751,7 +5885,7 @@ void FS_Shutdown(void) //returns false if the directory is not suitable. //returns true if it contains a known package. if we don't actually know of any packages that it should have, we just have to assume that its okay. -static qboolean FS_DirHasAPackage(char *basedir, ftemanifest_t *man) +qboolean FS_DirHasAPackage(char *basedir, ftemanifest_t *man) { qboolean defaultret = true; int j; @@ -5875,7 +6009,6 @@ static int FS_IdentifyDefaultGame(char *newbase, int sizeof_newbase, qboolean fi static ftemanifest_t *FS_GenerateLegacyManifest(int game, const char *basedir) { ftemanifest_t *man; - size_t j; const char *cexec; if (gamemode_info[game].manifestfile) @@ -5884,6 +6017,9 @@ static ftemanifest_t *FS_GenerateLegacyManifest(int game, const char *basedir) { man = FS_Manifest_Create(NULL, basedir); + Cmd_TokenizeString(va("game \"%s\"", gamemode_info[game].argname+1), false, false); + FS_Manifest_ParseTokens(man); + for (cexec = gamemode_info[game].customexec; cexec && cexec[0] == '/' && cexec[1] == '/'; ) { char line[256]; @@ -5896,29 +6032,7 @@ static ftemanifest_t *FS_GenerateLegacyManifest(int game, const char *basedir) FS_Manifest_ParseTokens(man); } - Cmd_TokenizeString(va("game \"%s\"", gamemode_info[game].argname+1), false, false); - FS_Manifest_ParseTokens(man); - if (gamemode_info[game].poshname) - { - Cmd_TokenizeString(va("name \"%s\"", gamemode_info[game].poshname), false, false); - FS_Manifest_ParseTokens(man); - } - - //if there's no base dirs, edit the manifest to give it its default ones. - for (j = 0; j < sizeof(man->gamepath) / sizeof(man->gamepath[0]); j++) - { - if (man->gamepath[j].path && (man->gamepath[j].flags&GAMEDIR_BASEGAME)) - break; - } - if (j == sizeof(man->gamepath) / sizeof(man->gamepath[0])) - { - for (j = 0; j < countof(gamemode_info[game].dir); j++) - if (gamemode_info[game].dir[j]) - { - Cmd_TokenizeString(va("basegame \"%s\"", gamemode_info[game].dir[j]), false, false); - FS_Manifest_ParseTokens(man); - } - } + FS_Manifest_SetDefaultSettings(man, &gamemode_info[game]); } man->security = MANIFEST_SECURITY_INSTALLER; return man; @@ -5984,7 +6098,7 @@ static void FS_AppendManifestGameArguments(ftemanifest_t *man) static struct dl_download *curpackagedownload; qboolean FS_DownloadingPackage(void) { - if (PM_IsApplying(false)) + if (PM_IsApplying() & 3) return true; return !fs_manifest || !!curpackagedownload; } @@ -6089,7 +6203,6 @@ static void FS_ManifestUpdated(struct dl_download *dl) void FS_BeginManifestUpdates(void) { ftemanifest_t *man = fs_manifest; - PM_ManifestPackage(man->installupd, man->security); if (curpackagedownload || !man) return; @@ -6172,7 +6285,7 @@ static ftemanifest_t *FS_ReadDefaultManifest(char *newbasedir, size_t newbasedir const char *pakname = com_argv[i+1]; searchpathfuncs_t *pak; vfsfile_t *vfs = VFSOS_Open(pakname, "rb"); - pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname); + pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname, ""); if (pak) { flocation_t loc; @@ -6379,36 +6492,6 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean olddownloadsurl = NULL; #endif - if (man == fs_manifest) - { - //don't close anything. theoretically nothing is changing, and we don't want to load new defaults either. - } - else if (!fs_manifest || !strcmp(fs_manifest->installation?fs_manifest->installation:"", man->installation?man->installation:"")) - { - if (!fs_manifest) - reloadconfigs = true; - FS_Manifest_Free(fs_manifest); - } - else - { - FS_FreePaths(); - - reloadconfigs = true; - } - fs_manifest = man; - -#ifdef PACKAGEMANAGER - PM_Shutdown(true); - - if (man->security == MANIFEST_SECURITY_NOT && strcmp(man->downloadsurl?man->downloadsurl:"", olddownloadsurl?olddownloadsurl:"")) - { //make sure we only fuck over the user if this is a 'secure' manifest, and not hacked in some way. - Z_Free(man->downloadsurl); - man->downloadsurl = olddownloadsurl; - } - else - Z_Free(olddownloadsurl); -#endif - if (man->installation && *man->installation) { for (i = 0; gamemode_info[i].argname; i++) @@ -6508,6 +6591,18 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean { if (Sys_FindGameData(man->formalname, man->installation, realpath, sizeof(realpath), man->security != MANIFEST_SECURITY_INSTALLER) && FS_FixPath(realpath, sizeof(realpath)) && FS_DirHasAPackage(realpath, man)) Q_strncpyz (newbasedir, realpath, sizeof(newbasedir)); + else if (man->basedir) + Q_strncpyz (newbasedir, man->basedir, sizeof(newbasedir)); +#if !defined(NOBUILTINMENUS) && defined(HAVE_CLIENT) + else if (man != fs_manifest) + { +#ifdef PACKAGEMANAGER + Z_Free(olddownloadsurl); +#endif + M_Menu_BasedirPrompt(man); + return false; + } +#endif #ifdef HAVE_CLIENT else { //no basedir known... switch to installer mode and ask the user where they want it (at least on windows) @@ -6521,14 +6616,42 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean if (!fixedbasedir && !com_installer) { if (strcmp(com_gamepath, newbasedir)) - { -#ifdef PACKAGEMANAGER - PM_Shutdown(false); -#endif Q_strncpyz (com_gamepath, newbasedir, sizeof(com_gamepath)); - } } + //now that we know what we're running and where we're running it, we can switch to it. + if (man == fs_manifest) + { + //don't close anything. theoretically nothing is changing, and we don't want to load new defaults either. + } + else if (!fs_manifest || !strcmp(fs_manifest->installation?fs_manifest->installation:"", man->installation?man->installation:"")) + { + if (!fs_manifest) + reloadconfigs = true; + FS_Manifest_Free(fs_manifest); + } + else + { + FS_FreePaths(); + + reloadconfigs = true; + } + fs_manifest = man; + + + +#ifdef PACKAGEMANAGER + PM_ManifestChanged(man); + + if (man->security == MANIFEST_SECURITY_NOT && strcmp(man->downloadsurl?man->downloadsurl:"", olddownloadsurl?olddownloadsurl:"")) + { //make sure we only fuck over the user if this is a 'secure' manifest, and not hacked in some way. + Z_Free(man->downloadsurl); + man->downloadsurl = olddownloadsurl; + } + else + Z_Free(olddownloadsurl); +#endif + //make sure it has a trailing slash, or is empty. woo. FS_CleanDir(com_gamepath, sizeof(com_gamepath)); @@ -6577,7 +6700,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean if (Sys_LockMutex(fs_thread_mutex)) { #ifdef HAVE_CLIENT - qboolean vidrestart = false; + int vidrestart = FS_GameIsInitialised()?false:2; #endif FS_ReloadPackFilesFlags(~0); @@ -6594,7 +6717,7 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean COM_CheckRegistered(); #ifdef HAVE_CLIENT - if (qrenderer != QR_NONE && allowvidrestart) + if (qrenderer != QR_NONE && allowvidrestart && !vidrestart) { for (i = 0; i < countof(vidfile); i++) { @@ -6629,9 +6752,6 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean //FIXME: flag this instead and do it after a delay? Cvar_ForceSet(&fs_gamename, fs_gamename.enginevalue); Cvar_ForceSet(&com_protocolname, com_protocolname.enginevalue); -#ifdef HAVE_CLIENT - vidrestart = false; -#endif #ifdef HAVE_SERVER if (isDedicated) @@ -6640,7 +6760,10 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean #endif #ifdef HAVE_CLIENT if (1) - CL_ExecInitialConfigs(man->defaultexec?man->defaultexec:""); + { + CL_ExecInitialConfigs(man->defaultexec?man->defaultexec:"", vidrestart==2); + vidrestart = false; + } else #endif { @@ -6649,11 +6772,6 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } } #ifdef HAVE_CLIENT - else if (vidrestart) - { - Cbuf_AddText ("vid_reload\n", RESTRICT_LOCAL); - vidrestart = false; - } if (Cmd_Exists("ui_restart")) //if we're running a q3 ui, restart it now... Cbuf_InsertText("ui_restart\n", RESTRICT_LOCAL, false); @@ -6667,7 +6785,12 @@ qboolean FS_ChangeGame(ftemanifest_t *man, qboolean allowreloadconfigs, qboolean } } #ifdef HAVE_CLIENT - if (vidrestart) + if (vidrestart == 2) + { //done when we picked an initial mod to run (so managed to actually read user settings instead of being forced windowed) + Cbuf_AddText ("vid_restart\n", RESTRICT_LOCAL); + vidrestart = false; + } + else if (vidrestart) { Cbuf_AddText ("vid_reload\n", RESTRICT_LOCAL); vidrestart = false; @@ -6769,10 +6892,23 @@ static int QDECL FS_EnumeratedFMF(const char *fname, qofs_t fsize, time_t mtime, } } } - else if (e->basedir) + else if (e->basedir == com_gamepath) man = FS_Manifest_ReadMod(fname); else - man = FS_Manifest_ReadSystem(fname, NULL); + { + man = FS_Manifest_ReadSystem(fname, e->basedir); + + if (man && !man->basedir && man->installation && *man->installation) + { //try and give it a proper gamedir... + char basedir[MAX_OSPATH]; + + //enables sources etc. + man->security = MANIFEST_SECURITY_INSTALLER; + + if (Sys_FindGameData(NULL, man->installation, basedir, sizeof(basedir), false)) + man->basedir = Z_StrDup(basedir); + } + } if (man) { @@ -6819,9 +6955,18 @@ int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), if (e.anygamedir) { -#ifdef __linux__ +#ifdef __unix__ + const char *dirs = getenv("XDG_CONFIG_DIRS"); + if (!dirs || !*dirs) + dirs = "/etc/xdg"; + e.basedir = NULL; - Sys_EnumerateFiles(NULL, "/etc/fte/*.fmf", FS_EnumeratedFMF, &e, NULL); + while (dirs && *dirs) + { + dirs = COM_ParseStringSetSep(dirs, ':', basedir, sizeof(basedir)); + Q_strncatz(basedir, "/games/*.fmf", sizeof(basedir)); + Sys_EnumerateFiles(NULL, basedir, FS_EnumeratedFMF, &e, NULL); + } #endif } @@ -6834,7 +6979,7 @@ int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), const char *pakname = com_argv[i+1]; searchpathfuncs_t *pak; vfsfile_t *vfs = VFSOS_Open(pakname, "rb"); - pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname); + pak = FS_OpenPackByExtension(vfs, NULL, pakname, pakname, ""); if (pak) { pak->EnumerateFiles(pak, "*.fmf", FS_EnumeratedFMF, &e); @@ -7262,7 +7407,7 @@ static void FS_ChangeGame_f(void) else COM_Gamedir(mod->gamedir, NULL); #ifdef HAVE_CLIENT - Cbuf_AddText("menu_restart\n", RESTRICT_LOCAL); +// Cbuf_AddText("menu_restart\n", RESTRICT_LOCAL); #endif } return; diff --git a/engine/common/fs.h b/engine/common/fs.h index 065332457..a86a7a53b 100644 --- a/engine/common/fs.h +++ b/engine/common/fs.h @@ -73,16 +73,16 @@ void FS_UnRegisterFileSystemModule(void *module); void FS_AddHashedPackage(searchpath_t **oldpaths, const char *parent_pure, const char *parent_logical, searchpath_t *search, unsigned int loadstuff, const char *pakpath, const char *qhash, const char *pakprefix, unsigned int packageflags); void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const char *parent_logical, searchpath_t *search, unsigned int loadstuff, int minpri, int maxpri); +void PM_ManifestChanged(ftemanifest_t *man); void *PM_GeneratePackageFromMeta(vfsfile_t *file, char *fname, size_t fnamesize, enum fs_relative *fsroot); void PM_FileInstalled(const char *filename, enum fs_relative fsroot, void *metainfo, qboolean enable); //we finished installing a file via some other mechanism (drag+drop or from server. insert it into the updates menu. void PM_EnumeratePlugins(void (*callback)(const char *name, qboolean blocked)); struct xcommandargcompletioncb_s; void PM_EnumerateMaps(const char *partial, struct xcommandargcompletioncb_s *ctx); void PM_LoadMap(const char *package, const char *map); -int PM_IsApplying(qboolean listsonly); +unsigned int PM_IsApplying(void); unsigned int PM_MarkUpdates (void); //mark new/updated packages as needing install. void PM_ApplyChanges(void); //for -install/-doinstall args -void PM_ManifestPackage(const char *name, int security); qboolean PM_AreSourcesNew(qboolean doprompt); qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize); //names the engine we should be running void PM_AddManifestPackages(ftemanifest_t *man); diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index 760bb101a..41bb718d9 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -2066,13 +2066,13 @@ static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo * nlen += cl; } f->name[nlen] = 0; - } + ofs += entry.cesize; if (prefix && *prefix) { if (!strcmp(prefix, "..")) - { + { //strip leading directories blindly char *c; for (c = f->name; *c; ) { @@ -2081,8 +2081,20 @@ static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo * } memmove(f->name, c, strlen(c)+1); } + else if (*prefix == '/') + { //move any files with the specified prefix to the root + size_t prelen = strlen(prefix+1); + if (!strncmp(prefix+1, f->name, prelen)) + { + if (f->name[prelen] == '/') + prelen++; + memmove(f->name, f->name+prelen, strlen(f->name+prelen)+1); + } + else + continue; + } else - { + { //add the specified text to the start of each file name (handy for 'maps') size_t prelen = strlen(prefix); size_t oldlen = strlen(f->name); if (prelen+1+oldlen+1 > sizeof(f->name)) @@ -2102,7 +2114,6 @@ static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo * f->flags = entry.flags; f->mtime = entry.mtime; - ofs += entry.cesize; f++; } @@ -2113,6 +2124,7 @@ static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo * zip->files = NULL; zip->numfiles = 0; } + zip->numfiles = f - zip->files; } } diff --git a/engine/gl/gl_vidlinuxglx.c b/engine/gl/gl_vidlinuxglx.c index 321e87213..94b462ef9 100644 --- a/engine/gl/gl_vidlinuxglx.c +++ b/engine/gl/gl_vidlinuxglx.c @@ -4010,6 +4010,7 @@ static Window X_CreateWindow(rendererstate_t *info, qboolean override, XVisualIn XSizeHints szhints; unsigned int mask; Atom prots[2]; + extern cvar_t vid_minsize; /* window attributes */ attr.background_pixel = 0; @@ -4029,8 +4030,8 @@ static Window X_CreateWindow(rendererstate_t *info, qboolean override, XVisualIn memset(&szhints, 0, sizeof(szhints)); szhints.flags = PMinSize|PPosition|PSize; - szhints.min_width = 320; - szhints.min_height = 200; + szhints.min_width = max(vid_minsize.vec4[0], 320); + szhints.min_height = max(vid_minsize.vec4[1], 200); if (!fullscreen) { diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 76693478f..dea0d313e 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -689,6 +689,8 @@ void SVNQ_New_f (void) #ifdef OFFICIAL_RELEASE Q_snprintfz(build, sizeof(build), "v%i.%02i", FTE_VER_MAJOR, FTE_VER_MINOR); +#elif defined(FTE_BRANCH) + Q_snprintfz(build, sizeof(build), "Rev %s", STRINGIFY(SVNREVISION)); #elif defined(SVNREVISION) Q_snprintfz(build, sizeof(build), "SVN %s", STRINGIFY(SVNREVISION)); #else diff --git a/flatpak.json b/flatpak.json index 028cbc42e..4cf644f80 100644 --- a/flatpak.json +++ b/flatpak.json @@ -1,7 +1,7 @@ { "//":"To build:", - "//":" flatpak-builder --user --install --from-git=~/quake/fteqw-code-git build-dir flatpak.json", - "//":" (swap the git url for a web-based one if you're not gonna make any local changes first - note that it'll only take committed changes)", + "//":" flatpak-builder --user --install --from-git=https://github.com/fte-team/fteqw.git build-dir flatpak.json", + "//":" (drop the git arg if you're making local changes to this file - note that it'll only take committed changes, annoyingly there's no way to have the git sources pick up the proper url)", "//":"To then run:", "//":" cd ~/quake && flatpak run info.triptohell.fteqw", @@ -23,8 +23,10 @@ "//":"dri needed for gl", "//":"ipc supposedly recommended for x11's shm stuff", "//":"flatpak doesn't seem to support alsa. anyone not using pipewire is thus fucked, nothing I can do about that", + "//":"flatpak doesn't seem to support gamepads, at least not on the steamdeck.", "//":"filesystem /usr/share/quake seems b0rked, we can't find standard gamedata there. still using 'host' because steam might have your game library on some other partition/device other than home.", - "//":"--device=all fixes gamepad so we have usable inputs on steamdeck (apparently there's no proper way around that)", + "//":"we request network... cos we ARE a game...", + "//":"--filesystem=host", "finish-args": [ "--share=network", @@ -35,12 +37,6 @@ "--socket=wayland", - "--filesystem=host", - "--filesystem=/run/udev:ro", - - "--device=all", - - "--device=snd", "--socket=pulseaudio" ], "modules": [ @@ -48,7 +44,7 @@ "name": "fteqw", "buildsystem": "cmake", - "//":"Using sdl to ensure game controller support eg for steamdeck etc. This may result in some clipboard issues as flatpak's sdl is a little too old (and sdl sucked in delaying proper support).", + "//":"Using sdl to provide game controller support eg for steamdeck etc (FIXME: see above complaint about gamepads being unusable in flatpak...). This may result in some clipboard issues as flatpak's sdl is a little too old (and sdl sucked in delaying proper support).", "//":"Server stuff disabled, flatpak is not a good match. commandline tools also disabled for the most part, no .desktop files for those", "//":"install to /app/bin instead of /app/games, flatpak just prefers it that way and the distinction isn't useful.", "config-opts": ["-DCMAKE_BUILD_TYPE=Release", @@ -72,7 +68,19 @@ "path": "/home/spike/quake/fteqw-code-git", "//url": "https://github.com/fte-team/fteqw.git" } - ] + ], + + "//":"Add in our manifest files for TCs.", + "post-install": [ + "install -d /app/etc/xdg/fte/", + "install games/*.fmf /app/etc/xdg/fte/", + + "install -D games/quake-demo.fmf /app/share/games/quake/default.fmf", + "install -D games/hexen2-demo.fmf /app/share/games/hexen2-demo/default.fmf" + ], + "build-options" : { + "no-debuginfo": true + } } ] } diff --git a/games/fortressone.fmf b/games/fortressone.fmf new file mode 100644 index 000000000..0c4189b29 --- /dev/null +++ b/games/fortressone.fmf @@ -0,0 +1,17 @@ +FTEMANIFEST 1 +game quake +name "FortressOne" +basegame id1 +gamedir fortress +protocolname FortressOne + +//mixing fortressone and other fortress mods in the same gamedir gets messy (especially when protocolnames are being overriden inside configs) +mainconfig "fo.cfg" + +//homedir usage is used only where required (or previously created). things like flatpak require its use. + +//see quake-demo.fmf +archivedpackage id1/pak0.pak 0x4f069cac id1/pak0.pak https://updates.triptohell.info/moodles/live/QUAKE_SW.zip +//this is made complicated because we can't just use the pk3 as-is etc. +package fortress/fortressone.pk3 mirror "https://github.com/FortressOne/fte-config/releases/download/1.1.1-beta.1/fortressone-fte-linux-1.1.1-beta.1-portable.zip" crc 0x7d74337f prefix "/FortressOne/fortress" filesize 197849205 sha512 "42a0deaa571f30c56dac08603d94d968a603a6c2d64db29473cb25f7e1db29fe63260cc8cda2e41dbbb3decd5e1c5440aa6bbceeae45c3225dad359fe0559021" signature "Spike:OrVRb1AuVmPGSmPlMS37DmEs1UIOeAZpSFj8s9jweUDbSqbkBnY6+5tBI43MxqDtIRMhK1+zDoaRxyvR2HErks2hf1wVoDnwpVACi893tvhSKQ0yfKUfkdpqm8aQM7AU/22ZGj5zav6RtxoX+np/7rzfET0fHzCSaQS1d6/TeJaQ5rMPX13Bgu0CenuOD1rvVNXXPMD5d887Kd+y/kz4OVUH0/xkjua5LHsWwDroC5AwzQI/EsWbcJx+xppihKNdMboAr51dALDuWLeSYzswPoJZWppv9D0WbjLohiZU04gmys7JdLn1cclz5MvIinLNFK//adQHrISqDFJqBygni8k90A0BNwSjdVzf9f4bYtzwS5qFRNgYtONyb380cpFZtMMKLdLRne1K8Y4FljzaJztcbm5qBQCLjAaK+C405tUDddDToCyyGsO6Zxf7yzhlSXNTWv/qRgKx91w5pJNyACgYnspxr+xOX+6ewchZZi07uPOPwVrxoAkuIrkiJtUe" + diff --git a/games/hexen2-demo.fmf b/games/hexen2-demo.fmf new file mode 100644 index 000000000..aa53c5c9d --- /dev/null +++ b/games/hexen2-demo.fmf @@ -0,0 +1,7 @@ +//Note - the hexen2 demo is an unmaintained variation of an earlier build, incompatible with the later game. Any basedirs must be entirely separate. + +game hexen2 +name "Hexen2 Demo" + +package "data/pak0.pak" crc 0x3bbcb56c mirror "unzip:H2Demo/Install/Hexen2/data1/pak0.pak,https://www.quaddicted.com/files/idgames2/idstuff/hexen2/H2Demo.exe" + diff --git a/games/quake-demo.fmf b/games/quake-demo.fmf new file mode 100644 index 000000000..79e168195 --- /dev/null +++ b/games/quake-demo.fmf @@ -0,0 +1,11 @@ +//This can skip most of its details as the important stuff is baked into the engine and its best to avoid dupes. +//View some other file for a real example. + +//Registered Quake is a strict superset of the shareware version, so we don't really need to care about whether its a demo or not. + +FTEManifestVer 1 +game quake + +//the shareware license requires distribution in whole... so we'll just download the whole shareware zip and extract only what we need - assuming the user does not already have a copy. +archivedpackage id1/pak0.pak 0x4f069cac id1/pak0.pak https://fte.triptohell.info/moodles/live/QUAKE_SW.zip + diff --git a/games/xonotic_85.fmf b/games/xonotic_85.fmf new file mode 100644 index 000000000..f9acb100f --- /dev/null +++ b/games/xonotic_85.fmf @@ -0,0 +1,235 @@ +//-prefixed lines are effectively inserted before the default.cfg +//+prefixed lines are inserted AFTER default.cfg and will thus conflict/override the mod's own settings + +game xonotic +name "FTE Xonotic (0.8.5)" +protocolname "Xonotic" +basegame data +basegame *ftedata //so stuff gets written here instead. + +//xonotic 0.8.5 packages. +package "data/font-unifont-20220627.pk3" crc 0xa39ce3ad mirror "unzip:Xonotic/data/font-unifont-20220627.pk3,https://github.com/garymoon/xonotic/releases/download/xonotic-v0.8.5/xonotic-0.8.5.zip" +package "data/font-xolonium-20220627.pk3" crc 0x9553d8a4 mirror "unzip:Xonotic/data/font-xolonium-20220627.pk3,https://github.com/garymoon/xonotic/releases/download/xonotic-v0.8.5/xonotic-0.8.5.zip" +package "data/xonotic-20220627-data.pk3" crc 0x57a1ba9c mirror "unzip:Xonotic/data/xonotic-20220627-data.pk3,https://github.com/garymoon/xonotic/releases/download/xonotic-v0.8.5/xonotic-0.8.5.zip" +package "data/xonotic-20220627-maps.pk3" crc 0x1d3d7cf1 mirror "unzip:Xonotic/data/xonotic-20220627-maps.pk3,https://github.com/garymoon/xonotic/releases/download/xonotic-v0.8.5/xonotic-0.8.5.zip" +package "data/xonotic-20220627-music.pk3" crc 0x5d1dd373 mirror "unzip:Xonotic/data/xonotic-20220627-music.pk3,https://github.com/garymoon/xonotic/releases/download/xonotic-v0.8.5/xonotic-0.8.5.zip" +package "data/xonotic-20220627-nexcompat.pk3" crc 0x83f613b9 mirror "unzip:Xonotic/data/xonotic-20220627-nexcompat.pk3,https://github.com/garymoon/xonotic/releases/download/xonotic-v0.8.5/xonotic-0.8.5.zip" + +//This sucks. overrides the *.dat files to work around gmqcc bugs (also smaller dat files at runtime, but still stupid and annoying and means compat hits the fan when using the standard csprogs on dp servers) +package "data/xonotic-fixups-0.8.5.zip" crc 0xe27b8ad3 mirror "xonotic-fixups-0.8.5.pk3" +//-set pr_fixbrokenqccarrays 2 //this can be used instead, but can cause its own problems. + + + +-set dpcompat_set 1 //gah +-set dpcompat_console 1 //DP's $ stuff works differently from quakeworld. definitely more annoying, but xonotic's configs expect dp behaviour +-set dpcompat_smallerfonts 1 //in case its needed. +-set dpcompat_strcat_limit 16383 //work around xonotic network compatibility issue. + +-set v_gammainverted 1 +-set con_stayhidden 0 +-set allow_download_pakcontents 1 +-set allow_download_refpackages 0 +-set sv_bigcoords "" +-set map_autoopenportals 1 +-set sv_port 26000 +-set cl_defaultport 26000 +-set r_particlesdesc effectinfo + +-if ($dedicated < 1) then set qport 654 //xonotic expects this cvar to exist only in clients (and uses it to detect client vs dedicated server). this entirely ignores 'setrenderer sv' of course. good luck with that one. + +-set gl_info_extensions "GL_EXT_texture_compression_s3tc GL_ARB_texture_compression" //fte doesn't have a cvar that contains opengl extensions, in part because they don't apply to other renderers, thus making it kinda useless. and xonotic likes spamming warning messages, even though they're invalid half the time, and impossible for a user to work around the rest of the time. + +-set cl_movement 1 //xonotic's physics are inconsistent with itself, but it judders even without prediction so we might as well enable it +-set cl_movement_replay "" //just to silence spam. actual value doesn't change anything in fte, and xonotic keeps changing it randomly anyway. +-set sv_nqplayerphysics 0 //xonotic runs async player physics. we've no need to force anything. +-set pr_enable_uriget 0 //enabling this causes xonotic's menu to get pissy about updates, which probably won't work very well with fte, so best to block all that crap. sadly this also breaks the stats stuff. +-set cl_lerp_smooth 0 //don't run in the past. DP has nothing like that the whole servertime/serverprevtime stuff will just get confused like hell. +-set r_shadow_realtime_nonworld_lightmaps 2 //DP's q3bsp lighting is doubled relative to q3, for some reason. + +-set con_chatsize 8 //doesn't exist in FTE, resulting in qc division by 0 if not set. +-set con_textsize 12 //keep the console sized at a fixed 12pt point, regardless of actual res. +//con_stayhidden 1 //don't pop up the console randomly. +-set dpcompat_makeshitup 2 //ignore most of what the shaders are saying and just make shit up, adding deluxe+specular+fullbrights+reflectcube etc. +-set dpcompat_findradiusarealinks 1 //matches dp. should help performance. +-set sv_gameplayfix_spawnbeforethinks 1 //some nq mods actually break without this glitch. DP forces a fix, so match that behaviour because xonotic needs it. + +//I copied this list out of the dp sourcecode. I don't know if xonotic needs them, but it normally has them anyway. +//fte doesn't even implement them all either, so that's fun. still, if it ever does then they'll at least get their expected values. +-set sv_gameplayfix_blowupfallenzombies 1 +-set sv_gameplayfix_findradiusdistancetobox 1 +-set sv_gameplayfix_grenadebouncedownslopes 1 +-set sv_gameplayfix_slidemoveprojectiles 1 +-set sv_gameplayfix_upwardvelocityclearsongroundflag 1 +-set sv_gameplayfix_setmodelrealbox 1 +-set sv_gameplayfix_droptofloorstartsolid 1 +-set sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1 +-set sv_gameplayfix_noairborncorpse 1 +-set sv_gameplayfix_noairborncorpse_allowsuspendeditems 1 +-set sv_gameplayfix_easierwaterjump 1 +-set sv_gameplayfix_delayprojectiles 1 +-set sv_gameplayfix_multiplethinksperframe 1 +-set sv_gameplayfix_fixedcheckwatertransition 1 +-set sv_gameplayfix_q1bsptracelinereportstexture 1 +-set sv_gameplayfix_swiminbmodels 1 +-set sv_gameplayfix_downtracesupportsongroundflag 1 +-set sv_gameplayfix_q2airaccelerate 0 + +//most of these cvars are not defined by FTE, but xonotic expects them anyway. so make sure they're there for the pmove code. +-set sv_jumpvelocity 270 +-set sv_maxairspeed 30 +-set sv_nostep 0 +-set sv_jumpstep 1 +-set sv_wateraccelerate -1 +-set sv_waterfriction -1 +-set sv_airaccel_sideways_friction 0 +-set sv_airaccel_qw 1 +-set sv_airaccel_qw_stretchfactor 0 +-set sv_airstopaccelerate 0 +-set sv_airstrafeaccelerate 0 +-set sv_maxairstrafespeed 0 +-set sv_airstrafeaccel_qw 0 +-set sv_aircontrol 0 +-set sv_aircontrol_penalty 0 +-set sv_aircontrol_power 2 +-set sv_aircontrol_backwards 0 +-set sv_airspeedlimit_nonqw 0 +-set sv_warsowbunny_turnaccel 0 +-set sv_warsowbunny_accel 0.1585 +-set sv_warsowbunny_topspeed 925 +-set sv_warsowbunny_backtosideratio 0.8 +-set sv_airaccelerate -1 //dp's default... to mean defer to sv_accelerate. + +//all these cvars are implemented using a custom conback shader. +-set scr_conalphafactor 1 +-set scr_conalpha2factor 0 +-set scr_conalpha3factor 0 +-set scr_conbrightness 1 +-set scr_conscroll_x 0 +-set scr_conscroll_y 0 +-set scr_conscroll2_x 0 +-set scr_conscroll2_y 0 +-set scr_conscroll3_x 0 +-set scr_conscroll3_y 0 +-set r_textcontrast 1 //fixes colourpicker widget + +-set com_parseutf8 1 //fte's name. interpret text as utf-8 when printing. +-set utf8_enable 1 //not fte's cvar name. have the qc builtins operate on codepoints rather than bytes (slower, still unaware of emoji, accents, etc). + +-alias playermodel model //FTE uses userinfo stuff for this stuff +-alias playerskin skin //FTE uses userinfo stuff for this stuff + +-set pr_csqc_memsize "64m" //xonotic is shit and inefficient. it really does need FAR too much memory. +-set pr_ssqc_memsize "96m" //xonotic is shit and inefficient. it really does need FAR too much memory. + +-set r_deluxemapping 1 //load deluxemaps, cos they're a little prettier +-set dpcompat_nopremulpics 1 //0 has problems with dds files etc, which is frankly a shame. + +//this won't force the menu to use a less laggy cursor, but should at least give the console a little more personality. +-set cl_cursor "gfx/menu/luma/cursor.jpg" +-set cl_cursor_scale 0.333 +-set cl_cursor_bias_x 10 +-set cl_cursor_bias_y 3.33 + +-set vid_gl20 1 //make sure various menu options are not greyed out + +-set gl_specular 1 //DP's default, that makes things too shiny (because if you're going to make a texture then sadly most people want it to be seen) +-set dpcompat_corruptglobals 1 //stomp on random qc globals that were meant to be cachable from one frame to the next. +-set vid_pixelheight 1 //DP mods have a nasty tendancy to get confused when this cvar doesn't exist. + +-set s_al_disable 1 //xonotic seems to clog ALL the openal audio buffers with useless sounds, so disable openal to prevent that from happening. + +-set dpcompat_nopreparse 1 //No unicasts and stuff split over packets mean lengths of custom stuff cannot be determined, resulting in translation of other things failing to translate. +-set cl_loopbackprotocol dpp7 //needs to be some sort of nq protocol due to nopreparse. might as well match dp and network player velocity entirely separately from its position... +-set sv_listen_dp 1 //listen for dp-protocol client connections +-set sv_listen_qw 0 //ignore standard QW clients (they'll just get dp responses which they'll ignore) +-set sv_listen_nq 0 //nq-protocol clients will just be ignored. +-set sv_bigcoords 1 //kinda required for dpp7 to function correctly. + +-set r_particlesdesc effectinfo +-set sv_mintic 0.0333 //should match dp's rate. +-set sv_maxtic 0 //fixed tick rates +-set cl_nolerp 0 + +-set sv_cullentities_trace 0 //still needs work. fte still has performance issues with tracing through patches + + +-set sv_curl_serverpackages "" //not in FTE, but xonotic warns when missing. + + +-set _cl_name "Stalking is illegal" +-set pr_autocreatecvars 0 //so we're more likely to notice unknown cvars. + +//misc cvars that the gamecode checks for for unknowable reasons +//set r_texture_dds_load 1 +//set vid_desktopfullscreen ${vid_fullscreen==2} +//set hud_panel_notify_print +//set con_chattime +//set con_chatsound +//set net_slist_pause +//set r_viewfbo ${r_hdr_framebuffer} +//set r_depthfirst +//set gl_vbo //removed from dp too +//set v_glslgamma +//set r_glsl_saturation +//set r_hdr_scenebrightness +//set sys_memsize_virtual +//set sys_memsize_physical +//set mod_q3bsp_nolightmaps +//set r_shadow_gloss ${gl_specular} +//set r_water ${r_portalrecursion>0} +//set r_water_resolutionmultiplier ${r_reflectrefract_scale} +//set cl_decals +//set cl_decals_models +//set r_drawdecals_drawdistance +//set cl_decals_fadetime +//set r_shadow_usenormalmap +//set r_coronas_occlusionquery +//set r_motionblur +//set cl_particles +//set r_drawparticles_drawdistance +//set snd_staticvolume +//set snd_channel0volume +//set snd_channel3volume +//set snd_channel6volume +//set snd_channel7volume +//set snd_channel4volume +//set snd_channel2volume +//set snd_channel1volume +//set snd_mutewhenidle ${snd_inactive==0} +//set snd_speed ${snd_khz} +//set snd_channels ${snd_numspeakers} +//set snd_swapstereo ${snd_leftisright} +//set snd_spatialization_control +//set m_accelerate +//set con_closeontoggleconsole +//set cl_movement_track_canjump +//set _cl_rate ${rate} +//set cl_curl_maxdownloads +//set cl_curl_maxspeed +//set shownetgraph ${r_netgraph} +//set cl_minfps +//set cl_maxidlefps ${cl_idlefps} +//set showtime +//set cl_capturevideo +//set g_campaignxonoticbeta_won +//set r_glsl_postprocess +//set joy_active +//set con_chatrect_x +//set con_chatrect_y +//set con_chatwidth +//set net_slist_favorites +//set cl_bobfall +//set cl_smoothviewheight + +-maxplayers 16 //xonotic doesn't like empty to mean ((deathmatch||coop)?32:1), so be explicit. +-deathmatch 1 //normally gets set to 1 in dp when maxplayers is forced (coop be damned). + +-pr_precachepic_slow 1 //xonotic sucks for this. its abuse of drawgetimagesize slows stuff down too. +-set mod_precache 2 //disabling this slashes ram usage. there might be some stutter from loading textures, but no outright stalls at least. the prediction misses are worse. +-set mod_precache 0 //FIXME: override sv_precacheplayermodels instead. +//FIXME: -set _q3bsp_bihtraces 1 //work around q3map2 being so defective and throwing 5000 brushes into a single leaf. +-set sv_gameplayfix_setmodelrealbox 0 //setting this to 0 prevents the server from stalling waiting for the thing to load. ++set sv_precacheplayermodels 0 //fte has loader threads, and the extra upfront load time is just annoying (override the mod's default). +-set s_precache 0 //mneh, sounds are overrated anyway. +