1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
26 #include "../gamedefs.h"
28 #include "../../libs/core/fsys/fsys_local.h"
29 #include "../decorate/vc_decorate.h"
33 // from corelib, sorry!
34 extern TArray
<VSearchPath
*> fsysSearchPaths
;
36 extern VCvarB game_release_mode
;
37 //extern VCvarI game_override_mode;
38 extern int cli_NoZMapinfo
; // from mapinfo.cpp
39 int cli_NoExternalDeh
= 0;
41 //extern VCvarB k8gore_enabled;
42 extern VCvarI k8gore_enabled_override
;
43 extern VCvarI k8gore_enabled_override_decal
;
46 static VCvarB
dbg_dump_gameinfo("dbg_dump_gameinfo", false, "Dump parsed game.txt?", CVAR_PreInit
|CVAR_NoShadow
);
47 static VCvarB
gz_skip_menudef("gz_skip_menudef", false, "Skip gzdoom menudef parsing?", CVAR_PreInit
|CVAR_Hidden
|CVAR_NoShadow
);
49 static VCvarB
__dbg_debug_preinit("dbg_debug_preinit", false, "Dump preinits?", CVAR_PreInit
|CVAR_Hidden
|CVAR_NoShadow
);
51 VCvarS
game_name("game_name", "unknown", "The Name Of The Game.", CVAR_Rom
|CVAR_NoShadow
);
55 bool fsys_hasPwads
= false; // or paks
56 bool fsys_hasMapPwads
= false; // or paks
57 bool fsys_DisableBloodReplacement
= false; // for custom modes
58 VStr flForcePlayerClass
= VStr::EmptyString
;
61 int fsys_warp_n0
= -1;
62 int fsys_warp_n1
= -1;
63 VStr fsys_warp_cmd
= VStr::EmptyString
;
65 static bool fsys_onlyOneBaseFile
= false;
67 static VStr cliGameMode
;
68 static const char *cliGameCStr
= nullptr;
69 static TArray
<VStr
> autoloadDirList
;
71 GameOptions game_options
;
73 extern VCvarB respawnparm
;
74 extern VCvarI fastparm
;
75 extern VCvarB NoMonsters
;
78 int cli_NoMonsters
= -1; // not specified
79 int cli_CompileAndExit
= 0;
80 static int cli_FastMonsters
= -1; // not specified
81 static int cli_Respawn
= -1; // not specified
82 static int cli_NoMenuDef
= -1; // not specified
83 /*static*/ int cli_GoreMod
= -1; // not specified
84 static int cli_GoreModForce
= 0;
85 static int cli_BDWMod
= -1; // not specified
86 static int cli_LudoGibsMod
= -1; // not specified
87 static int cli_SkeeHUD
= -1; // not specified
89 static const char *cli_BaseDir
= nullptr;
90 static const char *cli_IWadName
= nullptr;
91 static const char *cli_IWadDir
= nullptr;
92 static const char *cli_SaveDir
= nullptr;
93 static const char *cli_ConfigDir
= nullptr;
95 static int reportWads
= 1;
96 static int cli_NakedBase
= -1;
101 TArray
<VStr
> aux
; // list of additinal files (if fname starts with "?", the file is optional)
106 struct GameDefinition
{
107 VStr description
; // game description (may be empty)
108 VStr gamename
; // cannot be empty
109 TArray
<MainWadFiles
> mainWads
; // list of wads (ony one is required)
110 TArray
<VStr
> params
; // list of CLI params (always contains at least one item)
111 TArray
<VStr
> defines
; // VavoomC defines to add
112 VStr GameDir
; // main game dir
113 TArray
<VStr
> BaseDirs
; // common base dirs
116 VStr warp
; // warp template
117 TArray
<VStr
> filters
;
127 static TArray
<VStr
> IWadDirs
;
128 static int IWadIndex
;
130 static TArray
<ArgVarValue
> preinitCV
;
131 static ArgVarValue emptyAV
;
133 static TArray
<VStr
> cliModesList
;
134 static int cli_oldSprites
= 0;
136 static TArray
<GameDefinition
> knownGames
;
139 //==========================================================================
143 //==========================================================================
144 static VStr
getNetPath (VSearchPath
*sp
) {
145 if (!sp
) return VStr::EmptyString
;
146 if (sp
->cosmetic
) return VStr::EmptyString
;
147 if (sp
->IsNonPak()) return VStr::EmptyString
;
148 VStr fname
= sp
->GetPrefix();
149 VStr bname
= fname
.extractFileName();
150 int colidx
= bname
.lastIndexOf(':');
151 if (colidx
>= 0) bname
= bname
.mid(colidx
+1, bname
.length());
152 if (bname
.isEmpty()) return VStr::EmptyString
;
153 if (bname
.strEquCI("basepak.pk3") || bname
.strEquCI("basepak.vwad")) {
154 while (!fname
.isEmpty() && fname
[fname
.length()-1] != '/' && fname
[fname
.length()-1] != '\\') fname
.chopRight(1);
156 VStr xname
= fname
.extractFileName();
157 if (!xname
.isEmpty()) bname
= xname
+"/"+bname
;
163 // ////////////////////////////////////////////////////////////////////////// //
165 int cliHelpSorter (const void *aa
, const void *bb
, void *) {
166 if (aa
== bb
) return 0;
167 const VParsedArgs::ArgHelp
*a
= (const VParsedArgs::ArgHelp
*)aa
;
168 const VParsedArgs::ArgHelp
*b
= (const VParsedArgs::ArgHelp
*)bb
;
169 // if any of it doesn't have help...
170 if (!!(a
->arghelp
) != !!(b
->arghelp
)) {
172 vassert(!b
->arghelp
);
176 vassert(!a
->arghelp
);
181 return VStr::ICmp(a
->argname
, b
->argname
);
186 void FL_CollectPreinits () {
187 if (GArgs
.CheckParm("-help") || GArgs
.CheckParm("--help")) {
188 TArray
<VParsedArgs::ArgHelp
> list
;
189 VParsedArgs::GetArgList(list
);
191 smsort_r(list
.ptr(), list
.length(), sizeof(VParsedArgs::ArgHelp
), &cliHelpSorter
, nullptr);
193 for (auto &&ainfo
: list
) {
194 int len
= (int)strlen(ainfo
.argname
);
195 if (maxlen
< len
) maxlen
= len
;
197 for (auto &&ainfo
: list
) {
199 fprintf(stderr
, "%*s -- %s\n", -maxlen
, ainfo
.argname
, ainfo
.arghelp
);
201 GLog
.Logf("%*s -- %s", -maxlen
, ainfo
.argname
, ainfo
.arghelp
);
208 if (GArgs
.CheckParm("-help-developer") || GArgs
.CheckParm("--help-developer") ||
209 GArgs
.CheckParm("-help-dev") || GArgs
.CheckParm("--help-dev"))
211 TArray
<VParsedArgs::ArgHelp
> list
;
212 VParsedArgs::GetArgList(list
, true);
214 smsort_r(list
.ptr(), list
.length(), sizeof(VParsedArgs::ArgHelp
), &cliHelpSorter
, nullptr);
216 for (auto &&ainfo
: list
) {
217 int len
= (int)strlen(ainfo
.argname
);
218 if (maxlen
< len
) maxlen
= len
;
220 for (auto &&ainfo
: list
) {
222 fprintf(stderr
, "%*s -- %s\n", -maxlen
, ainfo
.argname
, (ainfo
.arghelp
? ainfo
.arghelp
: "undocumented"));
224 GLog
.Logf("%*s -- %s", -maxlen
, ainfo
.argname
, (ainfo
.arghelp
? ainfo
.arghelp
: "undocumented"));
232 bool inMinus
= false;
233 while (aidx
< GArgs
.Count()) {
234 const char *args
= GArgs
[aidx
];
235 //GLog.Logf("FL_CollectPreinits: argc=%d; aidx=%d; inminus=%d; args=<%s>", GArgs.Count(), aidx, (int)inMinus, args);
238 GArgs
.removeAt(aidx
);
244 if (args
[0] == '-') {
248 if (args
[0] != '+') {
257 // extract command name
259 while (args
[spos
] && (vuint8
)(args
[spos
]) > ' ') ++spos
;
260 VStr vname
= VStr(args
+1, spos
-1);
261 int flg
= VCvar::GetVarFlags(*vname
);
262 if (flg
< 0 || !(flg
&CVAR_PreInit
)) {
268 while (args
[spos
] && (vuint8
)(args
[spos
]) <= ' ') ++spos
;
272 if (aidx < GArgs.Count()) {
273 const char *a2 = GArgs[aidx];
274 if (a2[0] != '+' && a2[0] != '-') {
280 val
= VStr(args
+spos
);
283 GArgs
.removeAt(aidx
);
286 if (aidx
>= GArgs
.Count()) break;
288 if (aidx+1 < GArgs.Count()) {
289 const char *a2 = GArgs[aidx+1];
290 if (a2[0] != '+' && a2[0] != '-') {
296 val
= VStr(GArgs
[aidx
]);
299 GArgs
.removeAt(aidx
);
300 GArgs
.removeAt(aidx
);
303 ArgVarValue
&vv
= preinitCV
.alloc();
306 //GLog.Logf("FL_CollectPreinits: new var '%s' with value '%s'", *vname, *val);
308 if (vname
.ICmp("dbg_debug_preinit") == 0) VCvar::Set(*vname
, val
);
310 if (__dbg_debug_preinit
&& preinitCV
.length()) {
311 for (int f
= 0; f
< GArgs
.Count(); ++f
) GCon
->Logf("::: %d: <%s>", f
, GArgs
[f
]);
312 for (int f
= 0; f
< preinitCV
.length(); ++f
) {
313 const ArgVarValue
&vv
= preinitCV
[f
];
314 GCon
->Logf(":DOSET:%d: <%s> = <%s>", f
, *vv
.varname
.quote(), *vv
.value
.quote());
320 void FL_ProcessPreInits () {
321 if (__dbg_debug_preinit
) {
322 if (preinitCV
.length()) for (int f
= 0; f
< GArgs
.Count(); ++f
) GCon
->Logf("::: %d: <%s>", f
, GArgs
[f
]);
324 for (int f
= 0; f
< preinitCV
.length(); ++f
) {
325 const ArgVarValue
&vv
= preinitCV
[f
];
326 VCvar::Set(*vv
.varname
, vv
.value
);
327 if (__dbg_debug_preinit
) GCon
->Logf(":SET:%d: <%s> = <%s>", f
, *vv
.varname
.quote(), *vv
.value
.quote());
332 bool FL_HasPreInit (VStr varname
) {
333 if (varname
.length() == 0 || preinitCV
.length() == 0) return false;
334 for (int f
= 0; f
< preinitCV
.length(); ++f
) {
335 if (preinitCV
[f
].varname
.ICmp(varname
) == 0) return true;
341 void FL_ClearPreInits () {
346 // used to set "preinit" cvars
347 int FL_GetPreInitCount () {
348 return preinitCV
.length();
352 const ArgVarValue
&FL_GetPreInitAt (int idx
) {
353 if (idx
< 0 || idx
>= preinitCV
.length()) return emptyAV
;
354 return preinitCV
[idx
];
358 // ////////////////////////////////////////////////////////////////////////// //
363 bool isGlob () const { return (mask
.indexOf('*') >= 0 || mask
.indexOf('?') >= 0 || mask
.indexOf('[') >= 0); }
364 bool match (VStr s
) const { return s
.globmatch(mask
, false); }
368 struct GroupPwadInfo
{
369 VStr filename
; // with path, maybe
370 bool cosmetic
; // is this pwad a cosmetic one (i.e. doesn't affect saves?)
374 struct CustomModeInfo
{
376 TArray
<VStr
> aliases
;
377 TArray
<GroupPwadInfo
> pwads
;
378 TArray
<GroupPwadInfo
> postpwads
;
379 TArray
<GroupMask
> autoskips
;
380 bool disableBloodReplacement
;
383 VStr ForcePlayerClass
;
385 // has any sense only for modes loaded from "~/.k8vavoom/modes.rc"
386 TArray
<VStr
> basedirglob
;
389 CustomModeInfo () : name(), aliases(), pwads(), postpwads(), autoskips(), disableBloodReplacement(false), disableGoreMod(false), disableBDW(false), ForcePlayerClass(), basedir(), basedirglob(), reported(false) {}
391 CustomModeInfo (const CustomModeInfo
&src
) : name(), aliases(), pwads(), postpwads(), autoskips(), disableBloodReplacement(false), disableGoreMod(false), disableBDW(false), ForcePlayerClass(), basedir(), basedirglob(), reported(false) { copyFrom(src
); }
393 void operator = (const CustomModeInfo
&src
) { copyFrom(src
); }
401 disableBloodReplacement
= false;
402 disableGoreMod
= false;
404 ForcePlayerClass
.clear();
409 void appendPWad (VStr str
, bool asCosmetic
) {
410 if (str
.isEmpty()) return;
411 for (auto &&s
: pwads
) if (s
.filename
.strEqu(str
)) return;
412 GroupPwadInfo
&pi
= pwads
.alloc();
414 pi
.cosmetic
= asCosmetic
;
417 void appendPostPWad (VStr str
, bool asCosmetic
) {
418 if (str
.isEmpty()) return;
419 for (auto &&s
: postpwads
) if (s
.filename
.strEqu(str
)) return;
420 GroupPwadInfo
&pi
= postpwads
.alloc();
422 pi
.cosmetic
= asCosmetic
;
425 void copyFrom (const CustomModeInfo
&src
) {
426 if (&src
== this) return;
430 aliases
.setLength(src
.aliases
.length());
431 for (auto &&it
: src
.aliases
.itemsIdx()) aliases
[it
.index()] = it
.value();
433 pwads
.setLength(src
.pwads
.length());
434 for (auto &&it
: src
.pwads
.itemsIdx()) pwads
[it
.index()] = it
.value();
436 postpwads
.setLength(src
.postpwads
.length());
437 for (auto &&it
: src
.postpwads
.itemsIdx()) postpwads
[it
.index()] = it
.value();
439 autoskips
.setLength(src
.autoskips
.length());
440 for (auto &&it
: src
.autoskips
.itemsIdx()) autoskips
[it
.index()] = it
.value();
442 disableBloodReplacement
= src
.disableBloodReplacement
;
443 disableGoreMod
= src
.disableGoreMod
;
444 disableBDW
= src
.disableBDW
;
446 ForcePlayerClass
= src
.ForcePlayerClass
;
447 basedir
= src
.basedir
;
448 basedirglob
.setLength(src
.basedirglob
.length());
449 for (auto &&it
: src
.basedirglob
.itemsIdx()) basedirglob
[it
.index()] = it
.value();
450 reported
= src
.reported
;
453 // used to build final mode definition
454 void merge (const CustomModeInfo
&src
) {
455 if (&src
== this) return;
456 for (auto &&s
: src
.pwads
) appendPWad(s
.filename
, s
.cosmetic
);
457 for (auto &&s
: src
.postpwads
) appendPostPWad(s
.filename
, s
.cosmetic
);
458 for (auto &&s
: src
.autoskips
) autoskips
.append(s
);
459 if (src
.disableBloodReplacement
) disableBloodReplacement
= true;
460 if (src
.disableGoreMod
) disableGoreMod
= true;
461 if (src
.disableBDW
) disableBDW
= true;
462 if (!src
.ForcePlayerClass
.isEmpty()) ForcePlayerClass
= src
.ForcePlayerClass
;
466 GLog
.Logf(NAME_Debug
, "==== MODE: %s ====", *name
);
467 GLog
.Logf(NAME_Debug
, "basedir: <%s>", *basedir
);
468 GLog
.Logf(NAME_Debug
, "basedirglobs: %d", basedirglob
.length());
469 for (auto &&s
: basedirglob
) GLog
.Logf(NAME_Debug
, " <%s>", *s
);
470 GLog
.Logf(NAME_Debug
, "pwads: %d", pwads
.length());
471 for (auto &&s
: pwads
) GLog
.Logf(NAME_Debug
, " <%s>%s", *s
.filename
, (s
.cosmetic
? " (cosmetic)" : ""));
472 GLog
.Logf(NAME_Debug
, "postpwads: %d", postpwads
.length());
473 for (auto &&s
: postpwads
) GLog
.Logf(NAME_Debug
, " <%s>%s", *s
.filename
, (s
.cosmetic
? " (cosmetic)" : ""));
474 GLog
.Logf(NAME_Debug
, "autoskips: %d", autoskips
.length());
475 for (auto &&s
: autoskips
) GLog
.Logf(NAME_Debug
, " <%s> (%d)", *s
.mask
, (int)s
.enabled
);
476 GLog
.Logf(NAME_Debug
, "disableBloodReplacement: %s", (disableBloodReplacement
? "tan" : "ona"));
477 GLog
.Logf(NAME_Debug
, "disableGoreMod: %s", (disableGoreMod
? "tan" : "ona"));
478 GLog
.Logf(NAME_Debug
, "disableBDW: %s", (disableBDW
? "tan" : "ona"));
479 GLog
.Log(NAME_Debug
, "========");
482 bool isMyAlias (VStr s
) const {
484 if (s
.isEmpty()) return false;
485 for (auto &&a
: aliases
) if (a
.strEquCI(s
)) return true;
489 static VStr
stripBaseDirShit (VStr dirname
) {
490 while (!dirname
.isEmpty() && (dirname
[0] == '/' || dirname
[0] == '\\')) dirname
.chopLeft(1);
491 while (!dirname
.isEmpty() && (dirname
[dirname
.length()-1] == '/' || dirname
[dirname
.length()-1] == '\\')) dirname
.chopRight(1);
492 if (dirname
.startsWithCI("basev/")) dirname
.chopLeft(6);
496 bool isGoodBaseDir (VStr dirname
) const {
497 if (basedirglob
.length() == 0) return true;
498 dirname
= stripBaseDirShit(dirname
);
499 if (dirname
.isEmpty()) return true;
500 for (auto &&g
: basedirglob
) if (dirname
.globMatchCI(g
)) return true;
504 // returns "cosmetic" flag
505 static bool processPWadFlags (VStr
&path
) {
506 path
= path
.xstrip();
507 if (path
.isEmpty()) return false;
508 if (path
[0] == '!') {
510 path
= path
.xstrip();
513 if (path
[0] == '*') {
515 path
= path
.xstrip();
521 // `mode` keyword skipped, expects mode name
522 // mode must be cleared
523 void parse (VScriptParser
*sc
) {
528 while (!sc
->Check("}")) {
529 if (sc
->Check("pwad")) {
532 const bool cosmetic
= processPWadFlags(sc
->String
);
533 if (sc
->String
.isEmpty()) continue;
534 appendPWad(sc
->String
, cosmetic
);
535 if (!sc
->Check(",")) break;
537 } else if (sc
->Check("postpwad")) {
540 const bool cosmetic
= processPWadFlags(sc
->String
);
541 if (sc
->String
.isEmpty()) continue;
542 appendPostPWad(sc
->String
, cosmetic
);
543 if (!sc
->Check(",")) break;
545 } else if (sc
->Check("skipauto")) {
548 sc
->String
= sc
->String
.xstrip();
549 if (!sc
->String
.isEmpty()) {
550 GroupMask
&gi
= autoskips
.alloc();
551 gi
.mask
= sc
->String
;
554 if (!sc
->Check(",")) break;
556 } else if (sc
->Check("forceauto")) {
559 GroupMask
&gi
= autoskips
.alloc();
560 gi
.mask
= sc
->String
;
562 if (!sc
->Check(",")) break;
564 } else if (sc
->Check("DisableBloodReplacement")) {
565 disableBloodReplacement
= true;
566 } else if (sc
->Check("DisableGoreMod")) {
567 disableGoreMod
= true;
568 } else if (sc
->Check("DisableBDW")) {
570 } else if (sc
->Check("ForcePlayerClass")) {
572 ForcePlayerClass
= sc
->String
.xstrip();
573 } else if (sc
->Check("alias")) {
575 VStr k
= sc
->String
.xstrip();
576 if (!k
.isEmpty()) aliases
.append(k
);
577 } else if (sc
->Check("basedir")) {
579 VStr k
= stripBaseDirShit(sc
->String
.xstrip());
580 if (!k
.isEmpty()) basedirglob
.append(k
);
582 sc
->Error(va("unknown command '%s'", *sc
->String
));
589 static CustomModeInfo customMode
;
590 static TArray
<CustomModeInfo
> userModes
; // from "~/.k8vavoom/modes.rc"
591 static TArray
<GroupPwadInfo
> postPWads
; // from autoload
592 static bool modDetectorDisabledIWads
= false;
593 static bool modNoBaseSprOfs
= false;
594 static TArray
<VStr
> modAddMods
;
597 //==========================================================================
601 //==========================================================================
602 VVA_OKUNUSED
static void mdetect_AddMod (VStr s
) {
603 if (s
.isEmpty()) return;
604 for (auto &&mm
: modAddMods
) if (mm
.strEquCI(s
)) return;
605 modAddMods
.append(s
);
609 //==========================================================================
611 // mdetect_ClearAndBlockCustomModes
613 //==========================================================================
614 VVA_OKUNUSED
static void mdetect_ClearAndBlockCustomModes () {
618 cli_NakedBase
= 1; // ignore autoloads
619 fsys_onlyOneBaseFile
= true;
623 //==========================================================================
625 // mdetect_DisableBDW
627 //==========================================================================
628 VVA_OKUNUSED
static void mdetect_DisableBDW () {
629 if (!fsys_DisableBDW
) GCon
->Logf(NAME_Init
, "BDW mod disabled.");
630 fsys_DisableBDW
= true;
634 //==========================================================================
636 // mdetect_DisableGore
638 //==========================================================================
639 VVA_OKUNUSED
static void mdetect_DisableGore () {
640 if (cli_GoreMod
!= 0) GCon
->Logf(NAME_Init
, "Gore mod disabled.");
645 //==========================================================================
647 // mdetect_DisableIWads
649 //==========================================================================
650 VVA_OKUNUSED
static void mdetect_DisableIWads () {
651 if (!modDetectorDisabledIWads
) GCon
->Logf(NAME_Init
, "IWAD disabled.");
652 modDetectorDisabledIWads
= true;
656 //==========================================================================
658 // mdetect_SetGameName
660 //==========================================================================
661 VVA_OKUNUSED
static void mdetect_SetGameName (VStr gname
) {
666 #include "fsmoddetect.cpp"
669 //==========================================================================
671 // LoadModesFromStream
675 //==========================================================================
676 static void LoadModesFromStream (VStream
*rcstrm
, TArray
<CustomModeInfo
> &modes
, VStr basedir
=VStr::EmptyString
) {
678 VScriptParser
*sc
= new VScriptParser(rcstrm
->GetName(), rcstrm
);
679 while (!sc
->AtEnd()) {
680 if (sc
->Check("alias")) {
682 VStr k
= sc
->String
.xstrip();
685 VStr v
= sc
->String
.xstrip();
686 if (!k
.isEmpty() && !v
.isEmpty() && !k
.strEquCI(v
)) {
688 for (int f
= 0; f
< modes
.length(); ++f
) {
689 if (modes
[f
].name
.strEquCI(v
)) {
691 modes
[f
].aliases
.append(k
);
695 if (!found
) sc
->Message(va("cannot set alias '%s', because mode '%s' is unknown", *k
, *v
));
702 mode
.basedir
= basedir
;
706 for (int f
= 0; f
< modes
.length(); ++f
) {
707 if (modes
[f
].name
.strEquCI(mode
.name
)) {
714 if (!found
) modes
.append(mode
);
720 //==========================================================================
724 // "game_name" cvar must be set
726 //==========================================================================
727 static void ParseUserModes () {
728 if (game_name
.asStr().isEmpty()) return; // just in case
729 VStream
*rcstrm
= FL_OpenFileReadInCfgDir("modes.rc");
731 GCon
->Logf(NAME_Init
, "parsing user mode definitions from '%s'", *rcstrm
->GetName());
734 LoadModesFromStream(rcstrm
, userModes
);
738 //==========================================================================
742 //==========================================================================
743 static void ApplyUserModes (VStr basedir
) {
745 for (auto &&mname
: cliModesList
) {
746 CustomModeInfo
*nfo
= nullptr;
747 for (auto &&mode
: userModes
) {
748 if (mode
.isGoodBaseDir(basedir
) && mode
.name
.strEquCI(mname
)) {
754 for (auto &&mode
: userModes
) {
755 if (mode
.isGoodBaseDir(basedir
) && mode
.isMyAlias(mname
)) {
762 if (!nfo
->reported
) {
763 nfo
->reported
= true;
764 GCon
->Logf(NAME_Init
, "activating user mode '%s'", *nfo
->name
);
766 customMode
.merge(*nfo
);
772 //==========================================================================
776 //==========================================================================
777 static void SetupCustomMode (VStr basedir
) {
778 //customMode.clear();
780 if (!basedir
.isEmpty() && !basedir
.endsWith("/")) basedir
+= "/";
781 VStream
*rcstrm
= FL_OpenSysFileRead(basedir
+"modes.rc");
783 GCon
->Logf(NAME_Init
, "parsing mode definitions from '%s'", *rcstrm
->GetName());
786 TArray
<CustomModeInfo
> modes
;
787 LoadModesFromStream(rcstrm
, modes
, basedir
);
788 if (modes
.length() == 0) return; // nothing to do
791 customMode
.basedir
= basedir
;
792 for (auto &&mname
: cliModesList
) {
793 const CustomModeInfo
*nfo
= nullptr;
794 for (auto &&mode
: modes
) {
795 if (mode
.name
.strEquCI(mname
)) {
801 for (auto &&mode
: modes
) {
802 if (mode
.isMyAlias(mname
)) {
809 GCon
->Logf(NAME_Init
, "activating mode '%s'", *nfo
->name
);
810 customMode
.merge(*nfo
);
816 // ////////////////////////////////////////////////////////////////////////// //
829 , skipDehacked(false)
836 static TArray
<PWadFile
> pwadList
;
837 static int doStartMap
= 0;
840 static int pwflag_SkipSounds
= 0;
841 static int pwflag_SkipSprites
= 0;
842 static int pwflag_SkipDehacked
= 0;
843 static int pwflag_SkipSaveList
= 0;
846 //**************************************************************************
848 // pwad scanner (used to pre-scan pwads to extract various things)
850 //**************************************************************************
852 struct PWadScanInfo
{
854 VStr iwad
; // guessed from gameinfo; no path
855 VStr mapname
; // name of the arbitrary map from pwads
856 VStr maplump
; // ...for the above
857 int episode
; // 0: doom2
861 PWadScanInfo () noexcept
: processed(false), iwad(), mapname(), maplump(), episode(-1), mapnum(-1), hasMapinfo(false) {}
862 inline void clear () noexcept
{ processed
= false; iwad
.clear(); mapname
.clear(); maplump
.clear(); episode
= -1; mapnum
= -1; hasMapinfo
= false; }
864 inline bool isMapIndexValid () const noexcept
{ return (episode
>= 0 && mapnum
>= 0); }
866 inline int getMapIndex () const noexcept
{ return (episode
> 0 ? episode
*10+mapnum
: episode
== 0 ? mapnum
: 0); }
868 static inline int exmxToIndex (int e
, int m
) noexcept
{ return (e
*10+m
); }
872 static PWadScanInfo pwadScanInfo
;
873 TArray
<PWadMapLump
> fsys_PWadMaps
; // sorted
876 static int cmpPWadMapLump (const void *aa
, const void *bb
, void *) {
877 if (aa
== bb
) return 0;
878 const PWadMapLump
*a
= (const PWadMapLump
*)aa
;
879 const PWadMapLump
*b
= (const PWadMapLump
*)bb
;
880 if (a
->episode
&& b
->episode
) return a
->getMapIndex()-b
->getMapIndex();
881 if (!a
->episode
&& !b
->episode
) return a
->getMapIndex()-b
->getMapIndex();
882 if (a
->episode
) return -1; // b is D2 map
883 if (b
->episode
) return 1; // a is D2 map
885 return a
->getMapIndex()-b
->getMapIndex();
890 //==========================================================================
894 //==========================================================================
895 static void appendPWadMapLump (const PWadMapLump
&wlmp
) {
896 if (!wlmp
.isValid()) return;
897 for (auto &&l
: fsys_PWadMaps
) {
898 if (l
.isEqual(wlmp
)) {
903 fsys_PWadMaps
.append(wlmp
);
907 //==========================================================================
909 // PWadMapLump::parseMapName
911 //==========================================================================
912 bool PWadMapLump::parseMapName (const char *name
) noexcept
{
914 if (!name
|| !name
[0]) return false;
917 if ((name
[0] == 'e' || name
[0] == 'z') && name
[1] && name
[2] == 'm' && name
[3] && !name
[4]) {
918 int e
= VStr::digitInBase(name
[1], 10);
919 int m
= VStr::digitInBase(name
[3], 10);
920 if (e
< 0 || m
< 0) return false;
921 if (e
>= 1 && e
<= 9 && m
>= 1 && m
<= 9) {
922 mapname
= VStr(name
);
931 // try to detect things like "aaa<digit>"
933 while (name
[dpos
] && VStr::digitInBase(name
[dpos
], 10) < 0) ++dpos
;
934 if (dpos
== 0) return false; // must start from a lettter
935 if (VStr::digitInBase(name
[dpos
], 10) < 0) return false; // nope
936 for (const char *t
= name
+dpos
; *t
; ++t
) if (VStr::digitInBase(*t
, 10) < 0) return false;
937 if (!VStr::convertInt(name
+dpos
, &n
)) return false;
938 if (n
< 1 || n
> 99) return false;
940 mapname
= VStr(name
);
947 //==========================================================================
951 //==========================================================================
952 static void processMapName (const char *name
, int lump
) {
953 if (!name
|| !name
[0]) return;
954 //GCon->Logf(NAME_Debug, "*** checking map: name=<%s>", name);
956 //TODO: use `PWadMapLump::parseMapName()` here, and remove pasta
958 if (wlmp
.parseMapName(name
)) {
960 appendPWadMapLump(wlmp
);
963 // if we have maps for both D1 and D2 (Maps Of Chaos, for example), use D2 maps
966 if (pwadScanInfo
.episode
!= 0) {
967 if ((name
[0] == 'e' || name
[0] == 'z') && name
[1] && name
[2] == 'm' && name
[3] && !name
[4]) {
968 int e
= VStr::digitInBase(name
[1], 10);
969 int m
= VStr::digitInBase(name
[3], 10);
970 if (e
< 0 || m
< 0) return;
971 if (e
>= 1 && e
<= 9 && m
>= 1 && m
<= 9) {
972 //if (pwadScanInfo.episode == 0) return; // oops, mixed maps
973 const int newidx
= PWadScanInfo::exmxToIndex(e
, m
);
974 if (pwadScanInfo
.episode
< 0 || pwadScanInfo
.getMapIndex() > newidx
) {
975 pwadScanInfo
.episode
= e
;
976 pwadScanInfo
.mapnum
= m
;
977 pwadScanInfo
.mapname
= VStr(name
);
978 if (pwadScanInfo
.maplump
.isEmpty()) pwadScanInfo
.maplump
= W_FullLumpName(lump
);
979 //GCon->Logf(NAME_Debug, "*** D1 MAP; episode=%d; map=%d; index=%d", e, m, pwadScanInfo.getMapIndex());
987 /*if (pwadScanInfo.episode <= 0)*/ {
989 // try to detect things like "aaa<digit>"
991 while (name
[dpos
] && VStr::digitInBase(name
[dpos
], 10) < 0) ++dpos
;
992 if (dpos
== 0) return; // must start from a lettter
993 if (VStr::digitInBase(name
[dpos
], 10) < 0) return; // nope
994 for (const char *t
= name
+dpos
; *t
; ++t
) if (VStr::digitInBase(*t
, 10) < 0) return;
995 if (!VStr::convertInt(name
+dpos
, &n
)) return;
997 //GCon->Logf(NAME_Debug, " n=%d", n);
998 // if D1 map were found, perform some hackery
999 // only "MAPxx" is allowed to override it
1000 if (pwadScanInfo
.episode
> 0) {
1002 pfx
= pfx
.left(dpos
);
1003 //GCon->Logf(NAME_Debug, " pfx=<%s>; len=%d", *pfx, VStr::Length(name));
1004 if (!pfx
.startsWithCI("MAP") || VStr::Length(name
) != 5) return;
1005 pwadScanInfo
.clear();
1007 pwadScanInfo
.episode
= 0;
1008 if (pwadScanInfo
.mapnum
< 0 || pwadScanInfo
.mapnum
> n
) {
1009 pwadScanInfo
.mapnum
= n
;
1010 pwadScanInfo
.mapname
= VStr(name
);
1011 if (pwadScanInfo
.maplump
.isEmpty()) pwadScanInfo
.maplump
= W_FullLumpName(lump
);
1012 //GCon->Logf(NAME_Debug, "*** D2 MAP; index=%d", pwadScanInfo.getMapIndex());
1019 //==========================================================================
1023 //==========================================================================
1024 static void findMapChecker (int lump
) {
1025 VName lumpname
= W_LumpName(lump
);
1026 if (lumpname
== NAME_None
) return;
1027 processMapName(*lumpname
, lump
);
1031 //==========================================================================
1035 // it is safe to call this several times
1036 // results will be put into `pwadScanInfo`
1038 //==========================================================================
1039 static void performPWadScan () {
1040 if (pwadScanInfo
.processed
) return;
1042 pwadScanInfo
.clear();
1043 pwadScanInfo
.processed
= true;
1047 auto milump
= (cli_NoZMapinfo
>= 0 ? W_CheckNumForName("zmapinfo") : -1);
1048 if (milump
< 0 /*!!! || !W_IsAuxLump(milump)*/) milump
= W_CheckNumForName("mapinfo");
1049 pwadScanInfo
.hasMapinfo
= (milump
>= 0 /*!!! && W_IsAuxLump(milump)*/);
1051 auto xilump
= W_CheckNumForName("complvl");
1053 VScriptParser
*gsc
= VScriptParser::NewWithLump(xilump
);
1054 gsc
->SetCMode(true);
1056 if (gsc
->String
.strEquCI("mbf21")) {
1057 Sys_Error("COMPLVL with \"mbf21\" found. k8vavoom doesn't support MBF21.");
1062 //GCon->Log("**********************************");
1063 // try "GAMEINFO" first
1064 auto gilump
= W_CheckNumForName("gameinfo");
1065 if (gilump
>= 0/*!!! W_IsAuxLump(gilump)*/) {
1066 VScriptParser
*gsc
= VScriptParser::NewWithLump(gilump
);
1067 gsc
->SetCMode(true);
1068 while (gsc
->GetString()) {
1069 if (gsc
->QuotedString
) continue; // just in case
1070 if (gsc
->String
.strEquCI("iwad")) {
1072 if (gsc
->GetString()) pwadScanInfo
.iwad
= gsc
->String
;
1077 pwadScanInfo
.iwad
= pwadScanInfo
.iwad
.ExtractFileBaseName();
1078 if (!pwadScanInfo
.iwad
.isEmpty()) {
1079 if (!pwadScanInfo
.iwad
.extractFileExtension().strEquCI(".wad")) pwadScanInfo
.iwad
+= ".wad";
1080 GCon
->Logf(NAME_Init
, "gameinfo iwad: '%s'", *pwadScanInfo
.iwad
);
1081 giwad
= pwadScanInfo
.iwad
;
1085 // guess the name of the first map
1086 GCon
->Log(NAME_Init
, "detecting pwad maps");
1087 for (auto &&it
: WadMapIterator::FromWadFile(0)/*!!! FromFirstAuxFile()*/) findMapChecker(it
.lump
);
1088 // if no ordinary maps, try to find "maps/xxx.wad" files
1089 if (!pwadScanInfo
.isMapIndexValid()) {
1090 //for (auto &&it : WadFileIterator::FromFirstAuxFile()) {
1091 for (auto &&it
: WadNSIterator::FromWadFile(0/*!!! W_GetFirstAuxFile()*/, WADNS_AllFiles
)) {
1092 VStr fname
= it
.getRealName();
1093 // check for "maps/xxx.wad"
1094 //GCon->Logf(NAME_Debug, ":: <%s>", *fname);
1095 if (fname
.startsWithCI("maps/") &&
1096 (fname
.endsWithCI(".wad") || fname
.endsWithCI(".vwad")))
1098 bool fucked
= false;
1099 const char *s
= fname
.getCStr() + 5;
1100 while (!fucked
&& *s
) { fucked
= (*s
== '/'); s
+= 1; }
1103 if (fname
.endsWithCI(".wad")) fname
.chopRight(4); else fname
.chopRight(5);
1104 processMapName(*fname
, it
.lump
);
1109 // sort collected maps
1110 smsort_r(fsys_PWadMaps
.ptr(), fsys_PWadMaps
.length(), sizeof(PWadMapLump
), &cmpPWadMapLump
, nullptr);
1111 GCon
->Log(NAME_Init
, "pwad map detection complete.");
1113 fsys_hasMapPwads
= (fsys_PWadMaps
.length() > 0);
1114 pwadScanInfo
.iwad
= giwad
;
1118 // ////////////////////////////////////////////////////////////////////////// //
1119 static TArray
<VStr
> wpklist
; // everything is lowercased
1120 static TArray
<VStr
> wpklistSmall
; // everything is lowercased
1123 //==========================================================================
1127 //==========================================================================
1128 static VStr
fixWpkFileName (VStr fname
) {
1129 if (fname
.IsEmpty()) return VStr::EmptyString
;
1130 // yeah, assume that they are the same
1131 // (because i will prolly convert them)
1132 if (fname
== "player_zan.vwad") {
1140 //==========================================================================
1142 // appendWpkSmallName
1144 //==========================================================================
1145 static void appendWpkSmallName (VStr fname
) {
1146 if (fname
.IsEmpty()) return;
1147 fname
= fname
.toLowerCase();
1148 // yeah, assume that they are the same
1149 // (because i will prolly convert them)
1150 if (fname
.endsWith(".pk3") || fname
.endsWith(".zip") ||
1151 fname
.endsWith(".ipk3"))
1156 for (VStr ws
: wpklistSmall
) if (ws
== fname
) return; // i found her!
1157 wpklistSmall
.Append(fname
);
1161 //==========================================================================
1165 //==========================================================================
1166 const TArray
<VStr
> &FL_GetWadPk3List () {
1171 //==========================================================================
1173 // FL_GetWadPk3ListSmall
1175 //==========================================================================
1176 const TArray
<VStr
> &FL_GetWadPk3ListSmall () {
1177 return wpklistSmall
;
1181 //==========================================================================
1185 //==========================================================================
1186 static void wpkAppend (VStr fname
, bool asystem
) {
1187 if (fname
.length() == 0) return;
1188 VStr fn
= fname
.toLowerCase();
1190 fn
= fn
.extractFileName();
1191 if (fn
.length() == 0) fn
= fname
.toLowerCase();
1193 //if (fname.endsWithCI(".zip")) return; // ignore zip containers
1194 //GCon->Logf(NAME_Debug, "WPK: %s", *fn);
1195 appendWpkSmallName(fn
);
1196 // check for duplicates
1197 for (VStr ws
: wpklist
) if (ws
== fn
) return; // i found her!
1202 //==========================================================================
1206 // call this before adding archives
1208 //==========================================================================
1209 static int wpkMark (bool cosmetic
) {
1210 return (cosmetic
? -1 : fsysSearchPaths
.length());
1214 //==========================================================================
1218 //==========================================================================
1219 static void wpkAddMarked (int idx
) {
1220 if (idx
< 0) return;
1221 for (; idx
< fsysSearchPaths
.length(); ++idx
) {
1222 VSearchPath
*sp
= fsysSearchPaths
[idx
];
1224 if (sp->cosmetic) continue; // just in case
1225 if (sp->IsNonPak()) continue;
1226 VStr fn = sp->GetPrefix().extractFileName().toLowerCase();
1228 VStr fn
= getNetPath(sp
);
1229 if (fn
.isEmpty()) continue;
1230 //GCon->Logf(NAME_Debug, "*** <%s> (%s) ***", *fn, *sp->GetPrefix());
1231 wpklist
.append(fixWpkFileName(fn
));
1233 // lone files marked as paks too
1234 if (!sp
->IsLoneWadArchive() && sp
->IsAnyPak()) {
1235 appendWpkSmallName(fn
);
1241 // ////////////////////////////////////////////////////////////////////////// //
1243 VVA_OKUNUSED static int cmpfuncCI (const void *v1, const void *v2) {
1244 return ((VStr *)v1)->ICmp((*(VStr *)v2));
1248 VVA_OKUNUSED
static int cmpfuncCINoExt (const void *v1
, const void *v2
, void *) {
1249 return ((VStr
*)v1
)->StripExtension().ICmp(((VStr
*)v2
)->StripExtension());
1253 //==========================================================================
1257 //==========================================================================
1258 static bool AddAnyFile (VStr fname
, bool allowFail
, bool fixVoices
=false) {
1259 if (fname
.length() == 0) {
1260 if (!allowFail
) Sys_Error("cannot add empty file");
1263 if (!Sys_FileExists(fname
)) {
1264 if (!allowFail
) Sys_Error("cannot add file \"%s\"", *fname
);
1265 GCon
->Logf(NAME_Warning
, "cannot add file \"%s\"", *fname
);
1268 if (!W_AddDiskFileOptional(fname
, (allowFail
? false : fixVoices
))) {
1269 if (!allowFail
) Sys_Error("cannot add file \"%s\"", *fname
);
1270 GCon
->Logf(NAME_Warning
, "cannot add file \"%s\"", *fname
);
1277 //==========================================================================
1281 //==========================================================================
1282 static void AddAnyUserFile (VStr fname
, bool cosmetic
) {
1283 auto mark
= wpkMark(cosmetic
);
1284 if (!AddAnyFile(fname
, true/*allowFail*/)) return;
1285 //if (!cosmetic) wpkAppend(fname, false);
1290 // ////////////////////////////////////////////////////////////////////////// //
1291 enum { CM_PRE_PWADS
, CM_POST_PWADS
};
1293 //==========================================================================
1295 // CustomModeLoadPwads
1297 //==========================================================================
1298 static void CustomModeLoadPwads (int type
) {
1299 TArray
<GroupPwadInfo
> &list
= (type
== CM_PRE_PWADS
? customMode
.pwads
: customMode
.postpwads
);
1300 //GCon->Logf(NAME_Init, "CustomModeLoadPwads: type=%d; len=%d", type, list.length());
1302 // load post-file pwads from autoload here too
1303 if (type
== CM_POST_PWADS
&& postPWads
.length() > 0) {
1304 GCon
->Logf(NAME_Init
, "loading autoload post-pwads");
1305 for (auto &&wn
: postPWads
) {
1306 GCon
->Logf(NAME_Init
, "autoload %spost-pwad: %s", (wn
.cosmetic
? "cosmetic " : ""), *wn
.filename
);
1307 AddAnyUserFile(wn
.filename
, wn
.cosmetic
); // allow fail
1311 for (auto &&ww
: list
) {
1312 if (ww
.filename
.isEmpty()) continue;
1313 VStr fname
= ww
.filename
;
1314 if (!fname
.startsWith("/")) fname
= customMode
.basedir
+fname
;
1315 GCon
->Logf(NAME_Init
, "%smode pwad: %s", (ww
.cosmetic
? "cosmetic " : ""), *fname
);
1316 AddAnyUserFile(fname
, ww
.cosmetic
); // allow fail
1321 static TMap
<VStr
, bool> cliGroupMap
; // true: enabled; false: disabled
1322 static TArray
<GroupMask
> cliGroupMask
;
1325 //==========================================================================
1329 //==========================================================================
1330 void AddAutoloadRC (VStr aubasedir
) {
1331 VStream
*aurc
= FL_OpenSysFileRead(aubasedir
+"autoload.rc");
1334 // collect autoload groups to skip
1335 // add skips from custom mode
1336 for (int f
= 0; f
< customMode
.autoskips
.length(); ++f
) {
1337 GroupMask
&gi
= customMode
.autoskips
[f
];
1338 //GCon->Logf(NAME_Debug, "f=%d; %s=<%s>; enabled=%s", f, (gi.isGlob() ? "glob" : "name"), *gi.mask, (gi.enabled ? "tan" : "ona"));
1340 cliGroupMask
.append(gi
);
1342 cliGroupMap
.put(gi
.mask
.toLowerCase(), gi
.enabled
);
1346 GCon
->Logf(NAME_Init
, "parsing autoload groups from '%s'", *aurc
->GetName());
1347 VScriptParser
*sc
= new VScriptParser(aurc
->GetName(), aurc
);
1349 while (!sc
->AtEnd()) {
1350 sc
->Expect("group");
1352 VStr grpname
= sc
->String
;
1353 bool enabled
= !sc
->Check("disabled");
1355 // exact matches has precedence
1356 //GCon->Logf(" GROUP: <%s>", *grpname);
1357 auto gmp
= cliGroupMap
.get(grpname
.toLowerCase());
1361 // process masks; backwards, so latest mask has precedence
1362 for (int f
= cliGroupMask
.length()-1; f
>= 0; --f
) {
1363 if (grpname
.globmatch(cliGroupMask
[f
].mask
, false)) {
1364 enabled
= cliGroupMask
[f
].enabled
;
1370 GCon
->Logf(NAME_Init
, "skipping autoload group '%s'", *grpname
);
1371 sc
->SkipBracketed(true); // bracket eaten
1374 GCon
->Logf(NAME_Init
, "processing autoload group '%s'", *grpname
);
1376 while (!sc
->Check("}")) {
1377 if (sc
->Check(",")) continue;
1378 bool postPWad
= false;
1379 bool cosmetic
= true;
1380 if (sc
->Check("postpwad")) postPWad
= true;
1381 if (!sc
->GetString()) sc
->Error("wad/pk3 path expected");
1382 if (sc
->String
.isEmpty()) continue;
1383 if (sc
->String
[0] == '!') {
1384 sc
->String
.chopLeft(1);
1385 if (sc
->String
.isEmpty()) continue;
1388 VStr fname
= ((*sc
->String
)[0] == '/' ? sc
->String
: aubasedir
+sc
->String
);
1390 GroupPwadInfo
&wi
= postPWads
.alloc();
1391 wi
.filename
= fname
;
1392 wi
.cosmetic
= cosmetic
;
1394 AddAnyUserFile(fname
, cosmetic
);
1403 //==========================================================================
1407 //==========================================================================
1408 static void AddGameAutoloads (VStr basedir
, bool addAutoload
=true) {
1409 if (fsys_onlyOneBaseFile
) return;
1411 basedir
= basedir
.fixSlashes();
1412 if (basedir
.length() == 0 || basedir
== "/") return;
1413 if (addAutoload
) basedir
= basedir
.appendPath("autoload");
1414 //GCon->Logf(NAME_Debug, "::: <%s> :::", *basedir);
1416 TArray
<VStr
> WadFiles
;
1417 TArray
<VStr
> ZipFiles
;
1419 // find all .wad/.pk3 files in that directory
1420 auto dirit
= Sys_OpenDir(basedir
);
1422 for (VStr test
= Sys_ReadDir(dirit
); test
.IsNotEmpty(); test
= Sys_ReadDir(dirit
)) {
1423 //fprintf(stderr, " <%s>\n", *test);
1424 if (test
[0] == '_' || test
[0] == '.') continue; // skip it
1425 VStr ext
= test
.ExtractFileExtension();
1426 if (ext
.strEquCI(".wad")) WadFiles
.Append(test
);
1427 else if (ext
.strEquCI(".pk3")) ZipFiles
.Append(test
);
1428 else if (ext
.strEquCI(".vwad")) ZipFiles
.Append(test
);
1430 Sys_CloseDir(dirit
);
1431 smsort_r(WadFiles
.Ptr(), WadFiles
.length(), sizeof(VStr
), cmpfuncCINoExt
, nullptr);
1432 smsort_r(ZipFiles
.Ptr(), ZipFiles
.length(), sizeof(VStr
), cmpfuncCINoExt
, nullptr);
1435 basedir
= basedir
.appendTrailingSlash();
1437 if (WadFiles
.length() || ZipFiles
.length()) {
1438 GCon
->Logf(NAME_Init
, "adding game autoloads from '%s'", *basedir
);
1439 // now add wads, then pk3s
1440 for (auto &&fn
: WadFiles
) W_AddDiskFile(basedir
.appendPath(fn
), false);
1441 for (auto &&fn
: ZipFiles
) W_AddDiskFile(basedir
.appendPath(fn
));
1444 AddAutoloadRC(basedir
);
1448 //==========================================================================
1452 //==========================================================================
1453 static void AddGameDir (VStr basedir
, VStr dir
) {
1454 GCon
->Logf(NAME_Init
, "adding game dir '%s'", *dir
);
1457 if (bdx
.length() == 0) bdx
= "./";
1458 bdx
= bdx
.appendPath(dir
);
1460 //GCon->Logf(NAME_Debug, "*** adding game dir \"%s\"", *bdx.quote());
1462 if (!Sys_DirExists(bdx
)) return;
1464 fsys_hide_sprofs
= modNoBaseSprOfs
;
1466 TArray
<VStr
> WadFiles
;
1467 TArray
<VStr
> ZipFiles
;
1469 // find all .wad/.pk3 files in that directory
1470 auto dirit
= Sys_OpenDir(bdx
);
1472 for (VStr test
= Sys_ReadDir(dirit
); test
.IsNotEmpty(); test
= Sys_ReadDir(dirit
)) {
1473 //fprintf(stderr, " <%s>\n", *test);
1474 if (test
[0] == '_' || test
[0] == '.') continue; // skip it
1475 if (test
.extractFileName().strEquCI("basepak.pk3")) continue; // it will be explicitly added later
1476 if (test
.extractFileName().strEquCI("basepak.vwad")) continue; // it will be explicitly added later
1477 VStr ext
= test
.ExtractFileExtension();
1478 if (ext
.strEquCI(".wad")) WadFiles
.Append(test
);
1479 else if (ext
.strEquCI(".pk3")) ZipFiles
.Append(test
);
1480 else if (ext
.strEquCI(".vwad")) ZipFiles
.Append(test
);
1482 Sys_CloseDir(dirit
);
1483 smsort_r(WadFiles
.Ptr(), WadFiles
.length(), sizeof(VStr
), cmpfuncCINoExt
, nullptr);
1484 smsort_r(ZipFiles
.Ptr(), ZipFiles
.length(), sizeof(VStr
), cmpfuncCINoExt
, nullptr);
1487 // use `VStdFileStreamRead` so android port can override it
1488 auto bps
= FL_OpenSysFileRead(bdx
.appendPath("basepak.vwad"));
1491 ZipFiles
.insert(0, "basepak.vwad");
1492 GCon
->Logf(NAME_Init
, "found basepak at '%s'", *bdx
.appendPath("basepak.vwad"));
1493 bps
= FL_OpenSysFileRead(bdx
.appendPath("basepak.pk3"));
1496 Sys_Error("found both PK3 and VWAD basepacks");
1499 bps
= FL_OpenSysFileRead(bdx
.appendPath("basepak.pk3"));
1502 ZipFiles
.insert(0, "basepak.pk3");
1503 GCon
->Logf(NAME_Init
, "found basepak at '%s'", *bdx
.appendPath("basepak.pk3"));
1507 // add system dir, if it has any files
1508 if (ZipFiles
.length() || WadFiles
.length()) {
1509 wpkAppend(dir
+"/", true); // don't strip path
1512 // now add wads, then pk3s
1513 for (int i
= 0; i
< WadFiles
.length(); ++i
) {
1514 //if (i == 0 && ZipFiles.length() == 0) wpkAppend(dir+"/"+WadFiles[i], true); // system pak
1515 W_AddDiskFile(bdx
.appendPath(WadFiles
[i
]), false);
1518 for (int i
= 0; i
< ZipFiles
.length(); ++i
) {
1519 //if (i == 0) wpkAppend(dir+"/"+ZipFiles[i], true); // system pak
1520 bool isBPK
= ZipFiles
[i
].extractFileName().strEquCI("basepak.pk3") ||
1521 ZipFiles
[i
].extractFileName().strEquCI("basepak.vwad");
1522 int spl
= fsysSearchPaths
.length();
1523 W_AddDiskFile(bdx
.appendPath(ZipFiles
[i
]));
1525 // mark "basepak" flags
1526 for (int cc
= spl
; cc
< fsysSearchPaths
.length(); ++cc
) {
1527 fsysSearchPaths
[cc
]->basepak
= true;
1532 fsys_hide_sprofs
= false;
1535 SetupCustomMode(bdx
);
1536 ApplyUserModes(dir
);
1538 AddGameAutoloads(bdx
);
1539 VStr gdn
= dir
.extractFileName();
1541 if (!gdn
.isEmpty()) {
1543 const char *hdir
= getenv("HOME");
1544 if (hdir
&& hdir
[0] && hdir
[1]) {
1545 VStr hbd
= VStr(hdir
);
1546 if (!gdn
.isEmpty()) {
1547 hbd
= hbd
.appendPath(".k8vavoom");
1548 hbd
= hbd
.appendPath("autoload");
1549 hbd
= hbd
.appendPath(gdn
);
1550 AddGameAutoloads(hbd
, false);
1554 for (auto &&audir
: autoloadDirList
) {
1555 VStr hbd
= VStr(audir
);
1556 if (hbd
.isEmpty()) continue;
1557 if (hbd
[0] == '!') {
1559 hbd
= GParsedArgs
.getBinDir()+hbd
;
1560 } else if (hbd
[0] == '~') {
1562 const char *homdir
= getenv("HOME");
1563 if (homdir
&& homdir
[0]) {
1564 hbd
= VStr(homdir
)+hbd
;
1569 hbd
= hbd
.appendPath(gdn
);
1570 //GCon->Logf(NAME_Debug, "!!! <%s> : <%s> : <%s>", *audir, *gdn, *hbd);
1571 AddGameAutoloads(hbd
, false);
1575 // finally add directory itself
1578 VFilesDir *info = new VFilesDir(bdx);
1579 fsysSearchPaths.Append(info);
1584 //==========================================================================
1588 //==========================================================================
1589 static void AddGameDir (VStr dir
) {
1590 AddGameDir(fl_basedir
, dir
);
1592 //if (fl_savedir.IsNotEmpty()) AddGameDir(fl_savedir, dir);
1597 //==========================================================================
1601 //==========================================================================
1602 static VStr
FindMainWad (VStr MainWad
) {
1603 if (MainWad
.length() == 0) return VStr();
1605 if (Sys_FileExists(MainWad
)) return MainWad
;
1607 // if we have path separators, try relative path first
1609 const bool hasSep
= (strchr(*MainWad
, '\\') || strchr(*MainWad
, '/') || (MainWad
.length() >= 2 && MainWad
[1] == ':'));
1611 const bool hasSep
= !!strchr(*MainWad
, '/');
1614 //GLog.Logf(NAME_Debug, "trying to find iwad \"%s\" (hasSep=%d)", *MainWad.quote(), (int)hasSep);
1617 VStr nwadfname
= Sys_FindFileCI(MainWad
);
1618 if (nwadfname
.length() != 0) return nwadfname
;
1621 // first check in IWAD directories
1622 for (auto &&dir
: IWadDirs
) {
1623 //GLog.Logf(NAME_Debug, " looking for iwad '%s/%s'", *dir, *MainWad);
1624 VStr wadfname
= Sys_FindFileCI(dir
+"/"+MainWad
);
1625 if (wadfname
.length() != 0) {
1626 //GLog.Logf(NAME_Debug, " FOUND iwad \"%s/%s\"!", *dir, *MainWad);
1631 // then look in the save directory
1632 //if (fl_savedir.IsNotEmpty() && Sys_FileExists(fl_savedir+"/"+MainWad)) return fl_savedir+"/"+MainWad;
1634 // finally in base directory
1635 VStr bdwadfname
= Sys_FindFileCI(fl_basedir
+"/"+MainWad
);
1636 if (bdwadfname
.length() != 0) return bdwadfname
;
1638 // just in case, check it as-is
1639 bdwadfname
= Sys_FindFileCI(MainWad
);
1640 if (bdwadfname
.length() != 0) return bdwadfname
;
1646 //==========================================================================
1650 //==========================================================================
1651 static void SetupGameDir (VStr dirname
) {
1652 AddGameDir(dirname
);
1656 //==========================================================================
1658 // ParseStringValueOrList
1660 //==========================================================================
1661 static void ParseStringValueOrList (VScriptParser
*sc
, TArray
<VStr
> &list
, VStr
*dsc
=nullptr) {
1663 if (sc
->Check("{")) {
1665 while (!sc
->Check("}")) {
1667 list
.append(sc
->String
);
1668 if (!sc
->Check(",")) {
1673 if (dsc
&& sc
->Check(":")) {
1677 sc
->Check(";"); // optional
1681 list
.append(sc
->String
);
1682 if (dsc
&& sc
->Check(":")) {
1691 //==========================================================================
1695 //==========================================================================
1696 static VStr
ParseStringValue (VScriptParser
*sc
) {
1699 VStr res
= sc
->String
;
1705 //==========================================================================
1709 //==========================================================================
1710 static bool ParseBoolValue (VScriptParser
*sc
) {
1713 if (sc
->Check("true") || sc
->Check("tan")) res
= true;
1714 else if (sc
->Check("false") || sc
->Check("ona")) res
= false;
1715 else sc
->Error("boolean expected");
1721 //==========================================================================
1725 // "{" already eaten
1727 //==========================================================================
1728 static void ParseGameDef (VScriptParser
*sc
, GameDefinition
&game
) {
1729 while (!sc
->Check("}")) {
1731 if (sc
->Check("description")) { game
.description
= ParseStringValue(sc
); continue; }
1733 if (sc
->Check("game")) { game
.GameDir
= ParseStringValue(sc
); continue; }
1735 if (sc
->Check("base")) {
1736 game
.BaseDirs
.clear();
1737 ParseStringValueOrList(sc
, game
.BaseDirs
);
1741 if (sc
->Check("iwad")) {
1742 TArray
<VStr
> iwads
; // 2nd and next iwads will be added to addfiles
1744 ParseStringValueOrList(sc
, iwads
, &dsc
);
1745 if (iwads
.length() == 0) continue;
1746 if (iwads
[0].isEmpty()) sc
->Error(va("game '%s' has empty main wad", *game
.gamename
));
1747 MainWadFiles
&wf
= game
.mainWads
.alloc();
1749 wf
.description
= dsc
;
1750 for (int f
= 1; f
< iwads
.length(); ++f
) {
1752 if (s
.isEmpty()) continue;
1753 if (s
.length() == 1 && s
[0] == '?') continue;
1755 if (nfn
[0] == '?') nfn
.chopLeft(1);
1757 for (auto &&ks
: wf
.aux
) {
1759 if (ks
.mid(1, ks
.length()).strEquCI(nfn
)) { found
= true; break; }
1761 if (ks
.strEquCI(nfn
)) { found
= true; break; }
1764 if (found
) sc
->Error(va("game '%s' has duplicate additional wad \"%s\"", *game
.gamename
, *s
));
1770 if (sc
->Check("param")) {
1771 game
.params
.clear();
1772 ParseStringValueOrList(sc
, game
.params
);
1776 if (sc
->Check("fixvoices")) {
1777 game
.FixVoices
= ParseBoolValue(sc
);
1781 if (sc
->Check("warp")) {
1782 game
.warp
= ParseStringValue(sc
);
1786 if (sc
->Check("filter")) {
1787 TArray
<VStr
> filters
;
1788 ParseStringValueOrList(sc
, filters
);
1789 game
.filters
.clear();
1790 for (auto &&fs
: filters
) game
.filters
.append(VStr("filter/")+fs
.toLowerCase());
1794 if (sc
->Check("ashexen")) {
1795 game
.options
.hexenGame
= ParseBoolValue(sc
);
1799 if (sc
->Check("define")) {
1800 game
.defines
.clear();
1801 ParseStringValueOrList(sc
, game
.defines
);
1805 if (sc
->Check("warnings")) {
1808 while (!sc
->Check("}")) {
1810 VStr wname
= sc
->String
;
1811 const bool val
= ParseBoolValue(sc
);
1812 if (wname
.strEquCI("dehacked")) game
.options
.warnDeh
= val
;
1813 else if (wname
.strEquCI("pnames")) game
.options
.warnPNames
= val
;
1814 else if (wname
.strEquCI("animated")) game
.options
.warnAnimated
= val
;
1819 if (sc
->Check("nakedbase")) {
1820 game
.options
.nakedbase
= ParseBoolValue(sc
);
1823 if (sc
->Check("bdw")) {
1824 game
.options
.bdw
= (ParseBoolValue(sc
) ? 1 : 0);
1827 if (sc
->Check("gore")) {
1828 game
.options
.gore
= (ParseBoolValue(sc
) ? 1 : 0);
1831 if (sc
->Check("modblood")) {
1832 game
.options
.modblood
= (ParseBoolValue(sc
) ? 1 : 0);
1835 if (sc
->Check("ludogibs")) {
1836 game
.options
.ludogibs
= (ParseBoolValue(sc
) ? 1 : 0);
1839 if (sc
->Check("sprofslump")) {
1840 game
.options
.sprofslump
= ParseBoolValue(sc
);
1843 if (sc
->Check("acstype")) {
1845 if (sc
->Check("default")) game
.options
.acsType
= FL_ACS_Default
;
1846 else if (sc
->Check("zandronum") || sc
->Check("zandro")) game
.options
.acsType
= FL_ACS_Zandronum
;
1847 else if (sc
->Check("zdoom")) game
.options
.acsType
= FL_ACS_ZDoom
;
1848 else sc
->Error(va("unknown asc type '%s'", *sc
->String
));
1853 if (!sc
->GetString()) sc
->Error("unexpected end of file");
1854 sc
->Error(va("unknown command: '%s'", *sc
->String
));
1857 if (game
.params
.length() == 0) game
.params
.append(game
.gamename
);
1858 if (game
.mainWads
.length() == 0) sc
->Error(va("game '%s' has no iwads", *game
.gamename
));
1859 if (game
.GameDir
.isEmpty()) sc
->Error(va("game '%s' has no game dir", *game
.gamename
));
1861 // fix iwad descritions
1862 for (auto &&wfi
: game
.mainWads
) {
1863 if (wfi
.description
.isEmpty()) wfi
.description
= game
.description
;
1868 //==========================================================================
1870 // ParseGameDefinitions
1872 //==========================================================================
1873 static void ParseGameDefinitions (VScriptParser
*sc
, TArray
<GameDefinition
> &games
) {
1875 while (!sc
->AtEnd()) {
1876 if (sc
->Check(";")) continue;
1877 if (sc
->Check("game")) {
1879 VStr gname
= sc
->String
;
1880 if (gname
.isEmpty()) sc
->Error("game name is empty");
1881 for (int f
= 0; f
< games
.length(); ) {
1882 if (games
[f
].gamename
.strEqu(gname
)) {
1889 GameDefinition
&game
= games
.Alloc();
1890 game
.FixVoices
= false;
1891 game
.gamename
= gname
;
1892 ParseGameDef(sc
, game
);
1900 //==========================================================================
1902 // ParseBaseGameDefsFile
1904 //==========================================================================
1905 static void ParseBaseGameDefsFile (VStr name
, VStr
/*mainiwad*/, bool inHomeDir
) {
1908 if (name
.isAbsolutePath()) {
1909 if (!Sys_FileExists(name
)) return;
1911 } else if (inHomeDir
) {
1912 if (fl_configdir
.IsNotEmpty() && Sys_FileExists(fl_configdir
+"/"+name
)) {
1913 UseName
= fl_configdir
+"/"+name
;
1918 if (Sys_FileExists(fl_basedir
+"/"+name
)) {
1919 UseName
= fl_basedir
+"/"+name
;
1925 if (dbg_dump_gameinfo
|| inHomeDir
|| true) GCon
->Logf(NAME_Init
, "Parsing game definition file \"%s\"", *UseName
);
1926 VScriptParser
*sc
= new VScriptParser(UseName
, FL_OpenSysFileRead(UseName
));
1927 ParseGameDefinitions(sc
, knownGames
);
1928 if (dbg_dump_gameinfo
) GCon
->Logf(NAME_Init
, "Done parsing game definition file \"%s\"", *UseName
);
1932 //==========================================================================
1934 // ProcessBaseGameDefs
1936 //==========================================================================
1937 static void ProcessBaseGameDefs (VStr mainiwad
) {
1938 GameDefinition
*selectedGame
= nullptr;
1940 if (knownGames
.length() == 0) Sys_Error("No game definitions found!");
1942 for (auto &&game
: knownGames
) {
1943 for (auto &&arg
: game
.params
) {
1944 if (arg
.isEmpty()) continue;
1945 if (cliGameMode
.strEquCI(arg
)) {
1946 selectedGame
= &game
;
1950 if (selectedGame
) break;
1954 VStr gn
= selectedGame
->gamename
;
1955 if (dbg_dump_gameinfo
) GCon
->Logf(NAME_Init
, "SELECTED GAME: \"%s\"", *gn
);
1957 if (knownGames
.length() > 1) {
1958 // try to detect game by custom iwad
1959 if (!selectedGame
&& mainiwad
.length() > 0) {
1960 for (auto &&game
: knownGames
) {
1961 VStr mw
= mainiwad
.extractFileBaseName();
1962 for (auto &&mwi
: game
.mainWads
) {
1963 VStr gw
= mwi
.main
.extractFileBaseName();
1964 if (gw
.strEquCI(mw
)) {
1965 GCon
->Logf(NAME_Init
, "Detected game is '%s' (from iwad)", *mwi
.description
);
1966 selectedGame
= &game
;
1970 if (selectedGame
) break;
1974 // try to select DooM or DooM II automatically
1975 if (!selectedGame
) {
1976 //!performPWadScan();
1977 if (!pwadScanInfo
.iwad
.isEmpty()) {
1978 for (auto &&game
: knownGames
) {
1979 for (auto &&mwi
: game
.mainWads
) {
1980 VStr gw
= mwi
.main
.extractFileBaseName();
1981 if (gw
.strEquCI(pwadScanInfo
.iwad
)) {
1982 GCon
->Logf(NAME_Init
, "Detected game is '%s' (from gameinfo)", *mwi
.description
);
1983 selectedGame
= &game
;
1987 if (selectedGame
) break;
1991 // try to guess from map name
1992 if (!selectedGame
) {
1993 //!performPWadScan();
1994 //GCon->Logf(NAME_Debug, "*** hasMapinfo: %d; mapname=<%s>; episode=%d; map=%d; index=%d", (int)pwadScanInfo.hasMapinfo, *pwadScanInfo.mapname, pwadScanInfo.episode, pwadScanInfo.mapnum, pwadScanInfo.getMapIndex());
1995 if (pwadScanInfo
.getMapIndex() > 0) {
1996 //GCon->Logf("MNAME: <%s>", *mname);
1997 // found map, find DooM or DooM II game definition
1998 VStr gn1
= (pwadScanInfo
.episode
== 0 ? "doom2" : "doom");
1999 VStr gn2
= (pwadScanInfo
.episode
== 0 ? "freedoom2" : "freedoom");
2000 for (auto &&game
: knownGames
) {
2001 if (!gn1
.strEquCI(game
.gamename
) && !gn2
.strEquCI(game
.gamename
)) continue;
2002 // check if we have the corresponding iwad
2004 for (auto &&mwi
: game
.mainWads
) {
2005 mwp
= FindMainWad(mwi
.main
);
2006 if (!mwp
.isEmpty()) {
2007 if (!pwadScanInfo
.maplump
.isEmpty()) {
2008 GCon
->Logf(NAME_Init
, "Detected game is '%s' (from map lump '%s' at '%s')", *mwi
.description
, *pwadScanInfo
.mapname
, *pwadScanInfo
.maplump
);
2010 GCon
->Logf(NAME_Init
, "Detected game is '%s' (from map lump '%s')", *mwi
.description
, *pwadScanInfo
.mapname
);
2012 selectedGame
= &game
;
2016 if (selectedGame
) break;
2022 // try to find game iwad
2023 if (!selectedGame
) {
2024 for (auto &&game
: knownGames
) {
2026 for (auto &&mwi
: game
.mainWads
) {
2027 mainWadPath
= FindMainWad(mwi
.main
);
2028 if (!mainWadPath
.isEmpty()) {
2029 GCon
->Logf(NAME_Init
, "Detected game is '%s' (iwad search)", *mwi
.description
);
2030 selectedGame
= &game
;
2034 if (selectedGame
) break;
2039 if (selectedGame >= 0) {
2040 game_name = *knownGames[selectedGame].gamename;
2041 GCon->Logf(NAME_Init, "detected game: \"%s\"", *knownGames[selectedGame].gamename);
2045 selectedGame
= &knownGames
[0];
2047 if (!selectedGame
) {
2048 if (mainiwad
.isEmpty()) Sys_Error("Looks like I cannot find any IWADs. Did you forgot to specify -iwaddir?");
2049 selectedGame
= &knownGames
[0];
2052 // set game name variable
2053 game_name
= *selectedGame
->gamename
;
2055 vassert(selectedGame
);
2056 GameDefinition
&game
= *selectedGame
;
2057 game_options
= game
.options
;
2059 for (auto &&ds
: game
.defines
) {
2060 if (!ds
.isEmpty()) {
2061 VMemberBase::StaticAddDefine(*ds
);
2062 GCon
->Logf(NAME_Init
, "added define '%s' for game '%s'", *ds
, *game
.gamename
);
2066 // look for the main wad file
2071 if (!modDetectorDisabledIWads
) {
2072 // try user-specified iwad
2073 if (mainiwad
.length() > 0) {
2074 GCon
->Logf(NAME_Init
, "trying custom IWAD '%s'", *mainiwad
);
2075 mainWadPath
= FindMainWad(mainiwad
);
2076 if (mainWadPath
.isEmpty()) Sys_Error("custom IWAD '%s' not found!", *mainiwad
);
2077 GCon
->Logf(NAME_Init
, "found custom IWAD '%s'", *mainWadPath
);
2078 gameDsc
= game
.description
;
2080 // try default iwads
2081 for (iwadidx
= 0; iwadidx
< game
.mainWads
.length(); ++iwadidx
) {
2082 mainWadPath
= FindMainWad(game
.mainWads
[iwadidx
].main
);
2083 if (!mainWadPath
.isEmpty()) {
2084 gameDsc
= game
.mainWads
[iwadidx
].description
;
2088 if (mainWadPath
.isEmpty()) Sys_Error("Main wad file \"%s\" not found.", *game
.mainWads
[0].main
);
2089 vassert(iwadidx
>= 0 && iwadidx
< game
.mainWads
.length());
2092 gameDsc
= "custom TC";
2095 // process filters and warp template
2096 FL_ClearGameFilters();
2097 for (auto &&flt
: game
.filters
) {
2098 int res
= FL_AddGameFilter(flt
);
2100 case FL_ADDFILTER_OK
: break;
2101 case FL_ADDFILTER_INVALID
: GCon
->Logf(NAME_Error
, "Invalid game filter '%s', ignored", *flt
); break;
2102 case FL_ADDFILTER_DUPLICATE
: GCon
->Logf(NAME_Warning
, "Duplicate game filter '%s', ignored", *flt
); break;
2103 default: GCon
->Logf(NAME_Error
, "Unknown game filter error %d for filter '%s', ignored", res
, *flt
); break;
2106 warpTpl
= game
.warp
;
2109 if (game_options
.nakedbase
) { GCon
->Log(NAME_Init
, "iwad forced 'nakedbase'"); mdetect_ClearAndBlockCustomModes(); }
2110 if (game_options
.acsType
!= FL_ACS_Default
) { GCon
->Log(NAME_Init
, "iwad forced ACS type"); flACSType
= game_options
.acsType
; }
2111 if (game_options
.bdw
>= 0) { GCon
->Logf(NAME_Init
, "iwad forced BDW (%s)", (game_options
.bdw
== 0 ? "off" : "on")); fsys_DisableBDW
= (game_options
.bdw
== 0); cli_BDWMod
= game_options
.bdw
; }
2112 if (game_options
.gore
>= 0) {
2113 if (cli_GoreMod
!= game_options
.gore
) GCon
->Logf(NAME_Init
, "iwad forced gore mod (%s).", (game_options
.gore
? "on" : "off"));
2114 cli_GoreMod
= game_options
.gore
;
2116 if (game_options
.modblood
>= 0) {
2117 //if (game_options.modblood < 0) game_options.modblood = (cli_GoreMod == 0);
2118 fsys_DisableBloodReplacement
= (game_options
.modblood
== 0);
2120 if (game_options
.ludogibs
>= 0) { GCon
->Logf(NAME_Init
, "iwad forced Ludicruous Gibs (%s)", (game_options
.ludogibs
== 0 ? "off" : "on")); cli_LudoGibsMod
= game_options
.ludogibs
; }
2121 if (!game_options
.sprofslump
) modNoBaseSprOfs
= true;
2124 //GCon->Logf("MAIN WAD(1): '%s'", *MainWadPath);
2126 GCon
->Logf(NAME_Init
, "loading game %s", *gameDsc
);
2127 bool iwadAdded
= false;
2128 if (!modDetectorDisabledIWads
) {
2129 // if iwad is pk3, add it last
2130 if (mainWadPath
.endsWithCI("wad")) {
2131 GCon
->Logf(NAME_Init
, "adding iwad \"%s\"", *mainWadPath
);
2133 IWadIndex
= fsysSearchPaths
.length();
2134 wpkAppend(mainWadPath
, false); // mark iwad as "non-system" file, so path won't be stored in savegame
2135 AddAnyFile(mainWadPath
, false, game
.FixVoices
);
2137 GCon
->Logf(NAME_Init
, "using iwad \"%s\"", *mainWadPath
);
2141 // add optional files
2143 for (auto &&xfn
: game
.mainWads
[iwadidx
].aux
) {
2144 vassert(!xfn
.isEmpty());
2145 bool optional
= (xfn
[0] == '?');
2146 VStr fname
= FindMainWad(optional
? xfn
.mid(1, xfn
.length()) : xfn
);
2147 if (fname
.isEmpty()) {
2148 if (optional
) continue;
2149 Sys_Error("Required file \"%s\" not found", *xfn
);
2151 GCon
->Logf(NAME_Init
, "additing game file \"%s\"", *fname
);
2152 wpkAppend(fname
, false); // mark additional files as "non-system", so path won't be stored in savegame
2153 AddAnyFile(fname
, false);
2157 //GCon->Logf(NAME_Debug, "BDIRS: %d", game.BaseDirs.length());
2158 for (auto &&bdir
: game
.BaseDirs
) if (!bdir
.isEmpty()) AddGameDir(bdir
);
2160 SetupGameDir(game
.GameDir
);
2163 if (!modDetectorDisabledIWads
) {
2165 GCon
->Logf(NAME_Init
, "adding iwad \"%s\"", *mainWadPath
);
2166 IWadIndex
= fsysSearchPaths
.length();
2167 wpkAppend(mainWadPath
, false); // mark iwad as "non-system" file, so path won't be stored in savegame
2168 AddAnyFile(mainWadPath
, false, game
.FixVoices
);
2176 //==========================================================================
2180 //==========================================================================
2181 static void RenameSprites () {
2182 VStream
*Strm
= FL_OpenFileRead("sprite_rename.txt");
2185 VScriptParser
*sc
= new VScriptParser("sprite_rename.txt", Strm
);
2186 TArray
<VSpriteRename
> Renames
;
2187 TArray
<VSpriteRename
> AlwaysRenames
;
2188 TArray
<VLumpRename
> LumpRenames
;
2189 TArray
<VLumpRename
> AlwaysLumpRenames
;
2190 while (!sc
->AtEnd()) {
2191 bool Always
= sc
->Check("always");
2193 if (sc
->Check("lump")) {
2195 VStr Old
= sc
->String
.ToLower();
2197 VStr New
= sc
->String
.ToLower();
2198 VLumpRename
&R
= (Always
? AlwaysLumpRenames
.Alloc() : LumpRenames
.Alloc());
2205 if (sc
->String
.Length() != 4) sc
->Error("Sprite name must be 4 chars long");
2206 VStr Old
= sc
->String
.ToLower();
2209 if (sc
->String
.Length() != 4) sc
->Error("Sprite name must be 4 chars long");
2210 VStr New
= sc
->String
.ToLower();
2212 VSpriteRename
&R
= Always
? AlwaysRenames
.Alloc() : Renames
.Alloc();
2224 bool RenameAll
= !!cli_oldSprites
;
2225 for (int i
= 0; i
< fsysSearchPaths
.length(); ++i
) {
2226 if (RenameAll
|| i
== IWadIndex
) fsysSearchPaths
[i
]->RenameSprites(Renames
, LumpRenames
);
2227 fsysSearchPaths
[i
]->RenameSprites(AlwaysRenames
, AlwaysLumpRenames
);
2232 // ////////////////////////////////////////////////////////////////////////// //
2235 //==========================================================================
2239 //==========================================================================
2240 static int countFmtHash (VStr str
) {
2241 if (str
.length() == 0) return 0;
2243 bool inHash
= false;
2244 for (const char *s
= *str
; *s
; ++s
) {
2246 if (!inHash
) ++count
;
2256 //==========================================================================
2258 // cliFnameCollector
2260 //==========================================================================
2261 static int cliFnameCollector (VArgs
&args
, int idx
, bool first
) {
2262 //pwflag_SkipSaveList = 0;
2263 //bool first = true;
2264 //++idx; // done by the caller
2265 while (idx
< args
.Count()) {
2266 if (VStr::strEqu(args
[idx
], "-cosmetic")) {
2267 pwflag_SkipSaveList
= 1;
2271 if (VParsedArgs::IsArgBreaker(args
, idx
)) break;
2272 VStr fname
= args
[idx
++];
2273 const bool sts
= !pwflag_SkipSaveList
;
2274 pwflag_SkipSaveList
= 0; // autoreset
2275 //GCon->Logf(NAME_Debug, "idx=%d; fname=<%s>; sts=%d", idx-1, *fname, (int)sts);
2276 if (fname
.isEmpty()) { first
= false; continue; }
2280 pwf
.skipSounds
= !!pwflag_SkipSounds
;
2281 pwf
.skipSprites
= !!pwflag_SkipSprites
;
2282 pwf
.skipDehacked
= !!pwflag_SkipDehacked
;
2283 pwf
.storeInSave
= sts
;
2284 pwf
.asDirectory
= false;
2285 //GCon->Logf(NAME_Debug, "<%s>: sdh=%d", *fname, pwflag_SkipDehacked);
2287 if (Sys_DirExists(fname
)) {
2288 pwf
.asDirectory
= true;
2290 GCon
->Logf(NAME_Warning
, "To mount directory '%s' as emulated PK3 file, you should use \"-file\".", *fname
);
2292 pwadList
.append(pwf
);
2294 } else if (Sys_FileExists(fname
)) {
2295 pwadList
.append(pwf
);
2297 GCon
->Logf(NAME_Warning
, "File \"%s\" doesn't exist.", *fname
);
2305 //==========================================================================
2309 //==========================================================================
2310 void FL_InitOptions () {
2311 fsys_IgnoreZScript
= 0;
2313 GArgs
.AddFileOption("!1-game"); // '!' means "has args, and breaking" (number is argc)
2314 GArgs
.AddFileOption("!1-logfile"); // don't register log file in saves
2315 GArgs
.AddFileOption("!1-iwad");
2316 GArgs
.AddFileOption("!1-iwaddir");
2317 GArgs
.AddFileOption("!1-basedir");
2318 GArgs
.AddFileOption("!1-savedir");
2319 GArgs
.AddFileOption("!1-configdir");
2320 GArgs
.AddFileOption("!1-autoloaddir");
2321 GArgs
.AddFileOption("!1-deh");
2322 GArgs
.AddFileOption("!1-vc-decorate-ignore-file");
2323 GArgs
.AddFileOption("!1-audio-device");
2325 FSYS_InitOptions(GParsedArgs
);
2327 GParsedArgs
.RegisterFlagSet("-c", "compile VavoomC/decorate code and immediately exit", &cli_CompileAndExit
);
2329 GParsedArgs
.RegisterFlagSet("-cosmetic", "!do not store next pwad in save list", &pwflag_SkipSaveList
);
2331 GParsedArgs
.RegisterFlagSet("-skip-sounds", "skip sounds in the following pwads", &pwflag_SkipSounds
);
2332 GParsedArgs
.RegisterFlagReset("-allow-sounds", "allow sounds in the following pwads", &pwflag_SkipSounds
);
2334 GParsedArgs
.RegisterAlias("-skipsounds", "-skip-sounds");
2335 GParsedArgs
.RegisterAlias("-allowsounds", "-allow-sounds");
2337 GParsedArgs
.RegisterFlagSet("-skip-sprites", "skip sprites in the following pwads", &pwflag_SkipSprites
);
2338 GParsedArgs
.RegisterFlagReset("-allow-sprites", "allow sprites in the following pwads", &pwflag_SkipSprites
);
2340 GParsedArgs
.RegisterAlias("-skipsprites", "-skip-sprites");
2341 GParsedArgs
.RegisterAlias("-allowsprites", "-allow-sprites");
2343 GParsedArgs
.RegisterFlagSet("-skip-dehacked", "skip dehacked in the following pwads", &pwflag_SkipDehacked
);
2344 GParsedArgs
.RegisterFlagReset("-allow-dehacked", "allow dehacked in the following pwads", &pwflag_SkipDehacked
);
2346 GParsedArgs
.RegisterAlias("-skipdehacked", "-skip-dehacked");
2347 GParsedArgs
.RegisterAlias("-allowdehacked", "-allow-dehacked");
2349 GParsedArgs
.RegisterFlagSet("-oldsprites", "!some sprite renaming crap (do not bother)", &cli_oldSprites
);
2350 GParsedArgs
.RegisterAlias("-old-sprites", "-oldsprites");
2352 GParsedArgs
.RegisterFlagSet("-no-external-deh", "disable external (out-of-wad) .deh loading", &cli_NoExternalDeh
);
2354 GParsedArgs
.RegisterAlias("--no-extern-deh", "-no-external-deh");
2356 // filename collector
2357 GParsedArgs
.RegisterCallback(nullptr, nullptr, [] (VArgs
&args
, int idx
) -> int { return cliFnameCollector(args
, idx
, false); });
2358 GParsedArgs
.RegisterCallback("-file", "add the following arguments as pwads", [] (VArgs
&args
, int idx
) -> int { return cliFnameCollector(args
, ++idx
, true); });
2362 GParsedArgs
.RegisterCallback("-mode", "activate game mode from 'modes.rc'", [] (VArgs
&args
, int idx
) -> int {
2364 if (!VParsedArgs::IsArgBreaker(args
, idx
)) {
2365 VStr mn
= args
[idx
++];
2366 if (!mn
.isEmpty()) cliModesList
.append(mn
);
2372 // automatic groups collector
2373 GParsedArgs
.RegisterCallback("-autoload", "activate game mode from 'autoload.rc'", [] (VArgs
&args
, int idx
) -> int {
2375 if (!VParsedArgs::IsArgBreaker(args
, idx
)) {
2376 VStr sg
= args
[idx
++];
2377 if (!sg
.isEmpty()) {
2382 cliGroupMask
.append(gi
);
2384 cliGroupMap
.put(sg
.toLowerCase(), gi
.enabled
);
2390 GParsedArgs
.RegisterAlias("-auto", "-autoload");
2391 GParsedArgs
.RegisterAlias("-auto-load", "-autoload");
2393 GParsedArgs
.RegisterStringArrayOption("-autoloaddir", "add autoload directory to the list of autoload dirs", &autoloadDirList
);
2395 GParsedArgs
.RegisterCallback("-skip-autoload", "skip game mode from 'autoload.rc'", [] (VArgs
&args
, int idx
) -> int {
2397 if (!VParsedArgs::IsArgBreaker(args
, idx
)) {
2398 VStr sg
= args
[idx
++];
2399 if (!sg
.isEmpty()) {
2404 cliGroupMask
.append(gi
);
2406 cliGroupMap
.put(sg
.toLowerCase(), gi
.enabled
);
2412 GParsedArgs
.RegisterAlias("-skipauto", "-skip-autoload");
2413 GParsedArgs
.RegisterAlias("-skip-auto-load", "-skip-autoload");
2416 GParsedArgs
.RegisterStringOption("-game", "select game type", &cliGameCStr
);
2418 // add known game aliases
2419 //FIXME: unhardcode this!
2420 GParsedArgs
.RegisterCallback("-doom", "select Doom/Ultimate Doom game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "doom"; return 0; });
2421 GParsedArgs
.RegisterAlias("-doom1", "-doom");
2422 GParsedArgs
.RegisterCallback("-doom2", "select Doom II game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "doom2"; return 0; });
2423 GParsedArgs
.RegisterCallback("-tnt", "select TNT Evilution game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "tnt"; return 0; });
2424 GParsedArgs
.RegisterAlias("-evilution", "-tnt");
2425 GParsedArgs
.RegisterCallback("-plutonia", "select Plutonia game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "plutonia"; return 0; });
2426 GParsedArgs
.RegisterCallback("-nerve", "select Doom II + No Rest for the Living game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "nerve"; return 0; });
2428 GParsedArgs
.RegisterCallback("-freedoom", "select Freedoom Phase I game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "freedoom"; return 0; });
2429 GParsedArgs
.RegisterAlias("-freedoom1", "-freedoom");
2430 GParsedArgs
.RegisterCallback("-freedoom2", "select Freedoom Phase II game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "freedoom2"; return 0; });
2432 GParsedArgs
.RegisterCallback("-heretic", "select Heretic game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "heretic"; return 0; });
2433 GParsedArgs
.RegisterCallback("-hexen", "select Hexen game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "hexen"; return 0; });
2434 GParsedArgs
.RegisterCallback("-hexendd", "select Hexen:Deathkings game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "hexendd"; return 0; });
2436 GParsedArgs
.RegisterCallback("-strife", "select Strife game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "strife"; return 0; });
2437 GParsedArgs
.RegisterCallback("-strifeteaser", "select Strife Teaser game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "strifeteaser"; return 0; });
2440 GParsedArgs
.RegisterCallback("-complete", "!DooM Complete game (broken for now)", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "complete"; return 0; });
2442 GParsedArgs
.RegisterCallback("-chex", "Chex Quest game (semi-broken)", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "chex"; return 0; });
2443 GParsedArgs
.RegisterAlias("-chex1", "-chex");
2444 GParsedArgs
.RegisterCallback("-chex2", "Chex Quest 2 game (semi-broken)", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "chex2"; return 0; });
2445 GParsedArgs
.RegisterCallback("-chex3", "!Chex Quest 3 game (semi-broken)", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "chex3"; return 0; });
2447 GParsedArgs
.RegisterCallback("-hacx", "HacX (untested) game", [] (VArgs
&/*args*/, int /*idx*/) -> int { cliGameCStr
= nullptr; cliGameMode
= "hacx"; return 0; });
2449 GParsedArgs
.RegisterFlagSet("-k8runmap", "try to detect and run first pwad map automatically", &doStartMap
);
2451 GParsedArgs
.RegisterFlagSet("-fast", "fast monsters", &cli_FastMonsters
);
2452 GParsedArgs
.RegisterFlagReset("-slow", "slow monsters", &cli_FastMonsters
);
2454 GParsedArgs
.RegisterFlagSet("-respawn", "turn on respawning", &cli_Respawn
);
2455 GParsedArgs
.RegisterFlagReset("-no-respawn", "!reverse of \"-respawn\"", &cli_Respawn
);
2457 GParsedArgs
.RegisterFlagSet("-nomonsters", "disable monsters", &cli_NoMonsters
);
2458 GParsedArgs
.RegisterAlias("-no-monsters", "-nomonsters");
2459 GParsedArgs
.RegisterFlagReset("-monsters", "!reverse of \"monsters\"", &cli_NoMonsters
);
2461 GParsedArgs
.RegisterFlagSet("-nomenudef", "disable gzdoom MENUDEF parsing", &cli_NoMenuDef
);
2462 GParsedArgs
.RegisterAlias("-no-menudef", "-nomenudef");
2463 GParsedArgs
.RegisterFlagReset("-allow-menudef", "!reverse of \"-nomenudef\"", &cli_NoMenuDef
);
2465 GParsedArgs
.RegisterFlagSet("-gore", "enable gore mod", &cli_GoreMod
);
2466 GParsedArgs
.RegisterFlagReset("-nogore", "disable gore mod", &cli_GoreMod
);
2467 GParsedArgs
.RegisterAlias("-no-gore", "-nogore");
2469 GParsedArgs
.RegisterFlagSet("-force-gore", "force gore mod", &cli_GoreModForce
);
2471 GParsedArgs
.RegisterFlagSet("-bdw", "enable BDW (weapons) mod", &cli_BDWMod
);
2472 GParsedArgs
.RegisterFlagReset("-nobdw", "disable BDW (weapons) mod", &cli_BDWMod
);
2473 GParsedArgs
.RegisterAlias("-no-bdw", "-nobdw");
2475 GParsedArgs
.RegisterFlagSet("-gibs", "enable Ludicruous Gibs mod", &cli_LudoGibsMod
);
2476 GParsedArgs
.RegisterFlagReset("-nogibs", "disable Ludicruous Gibs mod", &cli_LudoGibsMod
);
2477 GParsedArgs
.RegisterAlias("-no-gibs", "-nogibs");
2480 GParsedArgs
.RegisterFlagSet("-skeehud", "!force SkullDash EE HUD", &cli_SkeeHUD
);
2483 GParsedArgs
.RegisterCallback("-skill", "select starting skill (3 is HMP; default is UV aka 4)", [] (VArgs
&args
, int idx
) -> int {
2485 if (!VParsedArgs::IsArgBreaker(args
, idx
)) {
2486 int skn
= M_SkillFromName(args
[idx
]);
2487 if (skn
< 0) skn
= 4-1; // default is UV
2491 GCon
->Log(NAME_Warning
, "skill name expected!");
2496 GParsedArgs
.RegisterStringOption("-iwad", "override iwad file name", &cli_IWadName
);
2497 GParsedArgs
.RegisterStringOption("-iwaddir", "set directory to look for iwads", &cli_IWadDir
);
2498 GParsedArgs
.RegisterStringOption("-basedir", "set directory to look for base k8vavoom pk3s", &cli_BaseDir
);
2499 GParsedArgs
.RegisterStringOption("-savedir", "set directory to store save files", &cli_SaveDir
);
2500 GParsedArgs
.RegisterStringOption("-configdir", "set directory to store config file (and save files)", &cli_ConfigDir
);
2503 GParsedArgs
.RegisterCallback("-warp", "warp to map number", [] (VArgs
&args
, int idx
) -> int {
2505 int wmap1
= -1, wmap2
= -1;
2506 bool mapok
= false, epiok
= false;
2507 if (!VParsedArgs::IsArgBreaker(args
, idx
)) {
2508 mapok
= VStr::convertInt(args
[idx
], &wmap1
);
2511 if (!VParsedArgs::IsArgBreaker(args
, idx
)) {
2512 epiok
= VStr::convertInt(args
[idx
], &wmap2
);
2516 if (!mapok
) wmap1
= -1;
2517 if (!epiok
) wmap2
= -1;
2519 if (wmap1
< 0) wmap1
= -1;
2520 if (wmap2
< 0) wmap2
= -1;
2521 fsys_warp_n0
= wmap1
;
2522 fsys_warp_n1
= wmap2
;
2526 GParsedArgs
.RegisterFlagReset("-silentwads", "do not report loaded wads (report by default)", &reportWads
);
2528 GParsedArgs
.RegisterFlagSet("-nakedbase", "skip all autoloads", &cli_NakedBase
);
2529 GParsedArgs
.RegisterAlias("-naked-base", "-nakedbase");
2532 GParsedArgs
.RegisterFlagSet("-Wall", "!turn on various useless warnings", &cli_WAll
);
2535 autoloadDirList
.append("!/autoload");
2540 //==========================================================================
2544 //==========================================================================
2547 VStr mainIWad
= VStr();
2549 // check for cmake dir
2550 #ifndef VAVOOM_K8_DEVELOPER
2552 VStr
crapmakedir(GParsedArgs
.getBinDir());
2553 if (Sys_DirExists(crapmakedir
.appendPath("CMakeFiles")) ||
2554 Sys_FileExists(crapmakedir
.appendPath("CMakeCache.txt")) ||
2555 Sys_FileExists(crapmakedir
.appendPath("cmake_install.cmake")))
2557 Sys_Error("Please, do not run k8vavoom from build dir! Without proper `make install` k8vavoom will not work!");
2562 FL_RegisterModDetectors();
2564 /* oops! they are already parsed here
2567 fsys_warp_cmd = VStr();
2570 // if it is set, than it was the latest
2571 if (cliGameCStr
) cliGameMode
= cliGameCStr
;
2573 if (cli_FastMonsters
== 1) fastparm
= 1;
2574 else if (cli_FastMonsters
== 0) fastparm
= -1;
2576 if (cli_Respawn
== 1) respawnparm
= true;
2577 if (cli_NoMonsters
== 1) NoMonsters
= true;
2578 if (cli_NoMenuDef
== 1) gz_skip_menudef
= true;
2581 bool isChex = false;
2582 if (cliGameMode.strEquCI("chex")) {
2583 cliGameMode = "doom2"; // arbitrary
2584 //game_override_mode = GAME_Chex;
2585 fsys_onlyOneBaseFile = true; // disable autoloads
2590 if (cli_IWadName
&& cli_IWadName
[0]) {
2591 mainIWad
= cli_IWadName
;
2592 if (mainIWad
.length()) {
2593 if (!Sys_FileExists(mainIWad
) && !mainIWad
.IsAbsolutePath()) {
2595 if (cli_IWadDir
&& cli_IWadDir
[0]) {
2596 VStr ipp
= VStr(cli_IWadDir
).appendPath(mainIWad
);
2597 if (Sys_FileExists(ipp
)) {
2599 GCon
->Logf(NAME_Init
, "found custom iwad at \"-iwaddir\"");
2600 vassert(Sys_FileExists(mainIWad
));
2604 if (!Sys_FileExists(mainIWad
)) Sys_Error("custom IWAD '%s' not found!", *mainIWad
);
2608 fsys_report_added_paks
= !!reportWads
;
2610 //if (!isChex) fsys_onlyOneBaseFile = (cli_NakedBase > 0);
2611 fsys_onlyOneBaseFile
= (cli_NakedBase
> 0);
2613 // set up base directory (main data files)
2618 if (fl_basedir
.isEmpty()) fl_basedir
= ".";
2620 /*static*/ const char *defaultBaseDirs
[] = {
2624 #elif !defined(_WIN32)
2625 "/opt/vavoom/share/k8vavoom",
2626 "/opt/k8vavoom/share/k8vavoom",
2627 "/usr/local/share/k8vavoom",
2628 "/usr/share/k8vavoom",
2629 "!/../share/k8vavoom",
2638 for (const char **tbd
= defaultBaseDirs
; *tbd
; ++tbd
) {
2639 VStr dir
= VStr(*tbd
);
2640 if (dir
[0] == '!') {
2642 dir
= GParsedArgs
.getBinDir()+dir
;
2643 } else if (dir
[0] == '~') {
2645 const char *hdir
= getenv("HOME");
2646 if (hdir
&& hdir
[0]) {
2647 dir
= VStr(hdir
)+dir
;
2652 if (Sys_DirExists(dir
)) {
2657 if (fl_basedir
.isEmpty()) Sys_Error("cannot find basedir; use \"-basedir dir\" to set it");
2660 // set up config directory (files written by engine)
2661 if (cli_ConfigDir
&& cli_ConfigDir
[0]) {
2662 fl_configdir
= VStr(cli_ConfigDir
).fixSlashes();
2665 #if !defined(_WIN32)
2666 const char *HomeDir
= getenv("HOME");
2667 if (HomeDir
&& HomeDir
[0]) fl_configdir
= VStr(HomeDir
)+"/.k8vavoom";
2669 //fl_configdir = ".";
2670 fl_configdir
= VStr(VArgs::GetBinaryDir());
2673 fl_configdir
= fl_configdir
.removeTrailingSlash();
2675 // set up save directory (files written by engine)
2680 fl_savedir
= fl_configdir
.appendPath("saves");
2682 fl_savedir
= fl_savedir
.removeTrailingSlash();
2684 // set up additional directories where to look for IWAD files
2687 if (p
[0]) IWadDirs
.Append(p
);
2689 /*static*/ const char *defaultIwadDirs
[] = {
2694 #elif !defined(_WIN32)
2695 "~/.k8vavoom/iwads",
2701 "/opt/vavoom/share/k8vavoom",
2702 "/opt/k8vavoom/share/k8vavoom",
2703 "/usr/local/share/k8vavoom",
2704 "/usr/share/k8vavoom",
2705 "!/../share/k8vavoom",
2713 for (const char **tbd
= defaultIwadDirs
; *tbd
; ++tbd
) {
2714 VStr dir
= VStr(*tbd
);
2715 if (dir
[0] == '!') {
2717 dir
= GParsedArgs
.getBinDir()+dir
;
2718 } else if (dir
[0] == '~') {
2720 const char *hdir
= getenv("HOME");
2721 if (hdir
&& hdir
[0]) {
2722 dir
= VStr(hdir
)+dir
;
2727 if (Sys_DirExists(dir
)) {
2728 //GLog.Logf(NAME_Debug, "found iwad dir '%s'", *dir);
2729 IWadDirs
.Append(dir
);
2733 // envvars: DOOMWADDIR
2735 const char *dwd
= getenv("DOOMWADDIR");
2736 if (dwd
&& dwd
[0]) IWadDirs
.Append(dwd
);
2738 // envvars: DOOMWADPATH ( https://doomwiki.org/wiki/Environment_variables )
2740 const char *dwp
= getenv("DOOMWADPATH");
2744 while (s
.length()) {
2745 int p0
= s
.indexOf(':');
2746 int p1
= s
.indexOf(';');
2748 if (p0
== 1 && VStr::isAlphaAscii(s
[0])) p0
= -1; // looks like 'a:'
2750 if (p0
>= 0 && p1
>= 0) p0
= min2(p0
, p1
);
2751 else if (p1
>= 0) { vassert(p0
< 0); p0
= p1
; }
2752 if (p0
< 0) p0
= s
.length();
2753 VStr pt
= s
.left(p0
).trimAll();
2757 //GLog.Logf(NAME_Debug, "DWP: <%.*s>", pt.length(), *pt);
2758 IWadDirs
.Append(pt
);
2764 // home dir (if any)
2767 const char *hd = getenv("HOME");
2768 if (hd && hd[0]) IWadDirs.Append(hd);
2773 //IWadDirs.Append(".");
2775 ParseDetectors("basev/detectors.txt", false);
2776 // parse detectors from home directory
2777 ParseDetectors("detectors.rc", true);
2779 ParseVWadKeys("basev/vwad_known_keys.txt", false);
2780 // parse keys from home directory
2781 ParseVWadKeys("vwad_known_keys.rc", true);
2786 const char *hdir = getenv("HOME");
2787 if (hdir && hdir[0]) {
2788 ParseDetectors(VStr(va("%s/.k8vavoom/detectors.rc", hdir)));
2796 bool mapinfoFound
= false;
2799 FL_StartUserWads(); // start marking
2800 for (int pwidx
= 0; pwidx
< pwadList
.length(); ++pwidx
) {
2801 PWadFile
&pwf
= pwadList
[pwidx
];
2802 fsys_skipSounds
= pwf
.skipSounds
;
2803 fsys_skipSprites
= pwf
.skipSprites
;
2804 fsys_skipDehacked
= pwf
.skipDehacked
;
2805 //!int nextfid = W_NextMountFileId();
2807 //GCon->Logf(NAME_Debug, "::: %d : <%s>", nextfid, *pwf.fname);
2808 int currFCount
= fsysSearchPaths
.length();
2810 auto mark
= wpkMark(!pwf
.storeInSave
);
2812 if (pwf
.asDirectory
) {
2813 //if (pwf.storeInSave) wpkAppend(pwf.fname, false); // non-system pak
2814 GCon
->Logf(NAME_Init
, "Mounting directory '%s' as emulated PK3 file.", *pwf
.fname
);
2815 //AddPakDir(pwf.fname);
2816 W_MountDiskDir(pwf
.fname
);
2818 //if (pwf.storeInSave) wpkAppend(pwf.fname, false); // non-system pak
2819 AddAnyFile(pwf
.fname
, true);
2822 // ignore cosmetic pwads
2823 if (!pwf
.storeInSave
) {
2824 for (int f
= currFCount
; f
< fsysSearchPaths
.length(); ++f
) fsysSearchPaths
[f
]->cosmetic
= true;
2829 fsys_hasPwads
= true;
2831 FL_EndUserWads(); // stop marking
2833 fsys_skipSounds
= false;
2834 fsys_skipSprites
= false;
2835 fsys_skipDehacked
= false;
2837 // scan for user maps
2839 if (pwadScanInfo
.hasMapinfo
) {
2840 mapinfoFound
= true;
2841 fsys_hasMapPwads
= true;
2842 } else if (!pwadScanInfo
.mapname
.isEmpty()) {
2843 mapnum
= pwadScanInfo
.getMapIndex();
2844 mapname
= pwadScanInfo
.mapname
;
2845 fsys_hasMapPwads
= true;
2848 // save pwads to be added later
2849 FSysSavedState pwadsSaved
;
2851 TArray
<VStr
> wpklistSaved
= wpklist
;
2852 TArray
<VStr
> wpklistSavedSmall
= wpklistSmall
;
2854 wpklistSmall
.clear();
2859 AddGameDir("basev/common");
2863 ParseBaseGameDefsFile("basev/games.txt", mainIWad
, false);
2864 ParseBaseGameDefsFile("games.rc", mainIWad
, true); // load rc from user home directory too
2865 ProcessBaseGameDefs(mainIWad
);
2867 // process "warp", do it here, so "+var" will be processed after "map nnn"
2868 // postpone, and use `P_TranslateMapEx()` in host initialization
2869 if (warpTpl
.length() > 0 && fsys_warp_n0
>= 0) {
2870 int fmtc
= countFmtHash(warpTpl
);
2871 if (fmtc
>= 1 && fmtc
<= 2) {
2872 if (fmtc
== 2 && fsys_warp_n1
== -1) { fsys_warp_n1
= fsys_warp_n0
; fsys_warp_n0
= 1; } // "-warp n" is "-warp 1 n" for ExMx
2876 while (spos
< warpTpl
.length()) {
2877 if (warpTpl
[spos
] == '#') {
2879 while (spos
< warpTpl
.length() && warpTpl
[spos
] == '#') { ++len
; ++spos
; }
2881 snprintf(tbuf
, sizeof(tbuf
), "%d", (numidx
== 0 ? fsys_warp_n0
: fsys_warp_n1
));
2882 VStr n
= VStr(tbuf
);
2883 while (n
.length() < len
) n
= VStr("0")+n
;
2887 cmd
+= warpTpl
[spos
++];
2891 //GCmdBuf.Insert(cmd);
2892 fsys_warp_cmd
= cmd
;
2898 //customMode.dump();
2900 if (cli_GoreModForce
!= 0) {
2901 GCon
->Logf(NAME_Init
, "Forcing gore mod.");
2902 //AddGameDir("basev/mods/gore"); // not disabled
2903 wpklist
.append("basev/mods/gore/"); // to not invalidate saves
2904 //wpklistSmall.append("basev/mods/gore/"); // to not invalidate saves
2905 cli_GoreMod
= cli_GoreModForce
;
2906 k8gore_enabled_override
= 1;
2907 k8gore_enabled_override_decal
= 1;
2908 } else if (!customMode
.disableGoreMod
) {
2910 if (/*game_release_mode ||*/ isChex
) {
2911 if (cli_GoreMod
== 1) AddGameDir("basev/mods/gore"); // explicitly enabled
2913 if (cli_GoreMod
!= 0) AddGameDir("basev/mods/gore"); // not disabled
2916 //if (cli_GoreMod != 0) AddGameDir("basev/mods/gore"); // not disabled
2917 if (cli_GoreMod
== 0) {
2918 k8gore_enabled_override
= -1;
2919 k8gore_enabled_override_decal
= -1;
2921 wpklist
.append("basev/mods/gore/"); // to not invalidate saves
2922 //wpklistSmall.append("basev/mods/gore/"); // to not invalidate saves
2926 GCon
->Logf(NAME_Init
, "Gore mod disabled.");
2927 k8gore_enabled_override
= -1;
2928 k8gore_enabled_override_decal
= -1;
2931 //if (isChex) AddGameDir("basev/mods/chex");
2933 // mark "iwad" flags
2934 for (int i
= 0; i
< fsysSearchPaths
.length(); ++i
) {
2935 fsysSearchPaths
[i
]->iwad
= true;
2938 // load custom mode pwads
2939 if (customMode
.disableBloodReplacement
) fsys_DisableBloodReplacement
= true;
2940 if (customMode
.disableBDW
) fsys_DisableBDW
= true;
2941 if (!customMode
.ForcePlayerClass
.isEmpty()) { GCon
->Logf(NAME_Init
, "user mode forces player class '%s'.", *customMode
.ForcePlayerClass
); flForcePlayerClass
= customMode
.ForcePlayerClass
; }
2943 fsys_report_added_paks
= !!reportWads
;
2944 //GCon->Logf(NAME_Debug, "!!!: %d", (fsys_report_added_paks ? 1 : 0));
2946 CustomModeLoadPwads(CM_PRE_PWADS
);
2948 // mount pwads (actually, add already mounted ones)
2949 if (pwadsSaved
.needReload()) {
2950 GCon
->Logf(NAME_Init
, "some user archives had filters, reloading...");
2951 pwadsSaved
.unload();
2954 FL_StartUserWads(); // start marking
2955 for (int pwidx
= 0; pwidx
< pwadList
.length(); ++pwidx
) {
2956 PWadFile
&pwf
= pwadList
[pwidx
];
2957 fsys_skipSounds
= pwf
.skipSounds
;
2958 fsys_skipSprites
= pwf
.skipSprites
;
2959 fsys_skipDehacked
= pwf
.skipDehacked
;
2960 //!int nextfid = W_NextMountFileId();
2962 //GCon->Logf(NAME_Debug, "::: %d : <%s>", nextfid, *pwf.fname);
2963 int currFCount
= fsysSearchPaths
.length();
2965 auto mark
= wpkMark(!pwf
.storeInSave
);
2967 if (pwf
.asDirectory
) {
2968 //if (pwf.storeInSave) wpkAppend(pwf.fname, false); // non-system pak
2969 GCon
->Logf(NAME_Init
, "Mounting directory '%s' as emulated PK3 file.", *pwf
.fname
);
2970 //AddPakDir(pwf.fname);
2971 W_MountDiskDir(pwf
.fname
);
2973 //if (pwf.storeInSave) wpkAppend(pwf.fname, false); // non-system pak
2974 AddAnyFile(pwf
.fname
, true);
2977 // ignore cosmetic pwads
2978 if (!pwf
.storeInSave
) {
2979 for (int f
= currFCount
; f
< fsysSearchPaths
.length(); ++f
) fsysSearchPaths
[f
]->cosmetic
= true;
2984 fsys_hasPwads
= true;
2986 FL_EndUserWads(); // stop marking
2988 fsys_skipSounds
= false;
2989 fsys_skipSprites
= false;
2990 fsys_skipDehacked
= false;
2992 pwadsSaved
.restore();
2993 for (auto &&it
: wpklistSaved
) wpklist
.append(it
);
2994 for (auto &&it
: wpklistSavedSmall
) wpklistSmall
.append(it
);
2997 // load custom mode pwads
2998 CustomModeLoadPwads(CM_POST_PWADS
);
3000 fsys_report_added_paks
= !!reportWads
;
3002 if (!fsys_DisableBDW
&& cli_BDWMod
> 0) AddGameDir("basev/mods/bdw");
3003 if (cli_LudoGibsMod
!= 0) AddGameDir("basev/mods/ludus_gibs");
3005 if (cli_SkeeHUD
> 0) mdetect_AddMod("skeehud");
3007 for (auto &&xmod
: modAddMods
) {
3008 GCon
->Logf(NAME_Init
, "adding special built-in mod '%s'", *xmod
);
3009 AddGameDir(va("basev/mods/%s", *xmod
));
3011 modAddMods
.clear(); // don't need it anymore
3013 fsys_report_added_paks
= !!reportWads
;
3017 if (doStartMap
&& fsys_warp_cmd
.isEmpty() && !mapinfoFound
&& mapnum
> 0 && mapname
.length()) {
3018 GCon
->Logf(NAME_Init
, "FOUND MAP: %s", *mapname
);
3019 mapname
= va("map \"%s\"\n", *mapname
.quote());
3020 //GCmdBuf.Insert(mapname);
3021 fsys_warp_cmd
= mapname
;
3022 } else if (doStartMap
&& fsys_warp_cmd
.isEmpty() && mapinfoFound
) {
3023 fsys_warp_cmd
= "__k8_run_first_map";
3027 if (fsys_warp_cmd
.length() == 0) {
3028 if (GArgs
.CheckParm("+map") != 0) Host_CLIMapStartFound();
3031 FreeDetectors(); // we don't need them
3035 //==========================================================================
3039 //==========================================================================
3040 void FL_Shutdown () {
3044 fl_configdir
.Clean();
3050 //==========================================================================
3054 //==========================================================================
3055 VStream
*FL_OpenFileWrite (VStr Name
, bool isFullName
) {
3060 if (fl_savedir
.IsNotEmpty()) {
3061 tmpName
= fl_savedir
+"/"+fl_gamedir
+"/"+Name
;
3063 tmpName
= fl_basedir
+"/"+fl_gamedir
+"/"+Name
;
3066 return FL_OpenSysFileWrite(tmpName
);
3070 //==========================================================================
3072 // FL_OpenFileReadInCfgDir
3074 //==========================================================================
3075 VStream
*FL_OpenFileReadInCfgDir (VStr Name
) {
3076 if (Name
.isEmpty()) return nullptr;
3077 VStr diskName
= FL_GetConfigDir().appendPath(Name
);
3078 VStream
*strm
= FL_OpenSysFileRead(diskName
);
3079 if (strm
) return strm
;
3080 return FL_OpenFileRead(Name
);
3084 //==========================================================================
3086 // FL_OpenFileWriteInCfgDir
3088 //==========================================================================
3089 VStream
*FL_OpenFileWriteInCfgDir (VStr Name
) {
3090 if (Name
.isEmpty()) return nullptr;
3091 VStr diskName
= FL_GetConfigDir().appendPath(Name
);
3092 return FL_OpenSysFileWrite(diskName
);
3096 //==========================================================================
3100 //==========================================================================
3101 VStr
FL_GetConfigDir () {
3102 VStr res
= fl_configdir
;
3103 if (res
.isEmpty()) res
= fl_savedir
;
3104 if (res
.isEmpty()) {
3105 #if !defined(_WIN32)
3106 const char *HomeDir
= getenv("HOME");
3107 res
= (HomeDir
&& HomeDir
[0] ? VStr(HomeDir
)+"/.k8vavoom" : VStr("."));
3110 res
= VStr(VArgs::GetBinaryDir());
3113 Sys_CreateDirectory(res
);
3118 //==========================================================================
3120 // FL_GetMapCacheDir
3122 //==========================================================================
3123 VStr
FL_GetMapCacheDir () {
3124 VStr res
= FL_GetConfigDir();
3125 if (res
.isEmpty()) return res
;
3126 res
+= "/.mapcache";
3127 Sys_CreateDirectory(res
);
3132 //==========================================================================
3134 // FL_GetVoxelCacheDir
3136 //==========================================================================
3137 VStr
FL_GetVoxelCacheDir () {
3138 VStr res
= FL_GetConfigDir();
3139 if (res
.isEmpty()) return res
;
3140 res
+= "/.voxcache";
3141 Sys_CreateDirectory(res
);
3146 //==========================================================================
3150 //==========================================================================
3151 VStr
FL_GetSavesDir () {
3152 VStr res
= fl_savedir
;
3153 if (res
.isEmpty()) {
3154 #if !defined(_WIN32)
3155 const char *HomeDir
= getenv("HOME");
3156 res
= (HomeDir
&& HomeDir
[0] ? VStr(HomeDir
)+"/.k8vavoom/saves" : VStr("./saves"));
3161 Sys_CreateDirectory(res
);
3166 //==========================================================================
3168 // FL_GetScreenshotsDir
3170 //==========================================================================
3171 VStr
FL_GetScreenshotsDir () {
3172 VStr res
= FL_GetConfigDir();
3173 if (res
.isEmpty()) return res
;
3175 Sys_CreateDirectory(res
);
3180 //==========================================================================
3182 // FL_GetUserDataDir
3184 //==========================================================================
3185 VStr
FL_GetUserDataDir (bool shouldCreate
) {
3186 VStr res
= FL_GetConfigDir();
3188 //res += '/'; res += game_name;
3189 if (shouldCreate
) Sys_CreateDirectory(res
);
3194 //==========================================================================
3196 // FL_GetNetWadsCount
3198 //==========================================================================
3199 int FL_GetNetWadsCount () {
3201 for (auto &&sp
: fsysSearchPaths
) {
3202 if (!getNetPath(sp
).isEmpty()) ++res
;
3208 //==========================================================================
3210 // FL_GetNetWadsHash
3212 //==========================================================================
3213 vuint32
FL_GetNetWadsHash () {
3216 for (auto &&sp
: fsysSearchPaths
) {
3217 VStr s
= getNetPath(sp
);
3218 if (s
.isEmpty()) continue;
3223 return XXH32(*modlist
, (vint32
)modlist
.length(), (vuint32
)count
);
3227 //==========================================================================
3231 //==========================================================================
3232 void FL_GetNetWads (TArray
<VStr
> &list
) {
3234 for (auto &&sp
: fsysSearchPaths
) {
3235 VStr s
= getNetPath(sp
);
3236 if (s
.isEmpty()) continue;
3237 //GCon->Logf(NAME_Debug, ":: <%s> (basepak=%d; iwad=%d; userwad=%d)", *s, (int)sp->basepak, (int)sp->iwad, (int)sp->userwad);
3243 //==========================================================================
3245 // FL_BuildRequiredWads
3247 //==========================================================================
3248 void FL_BuildRequiredWads () {
3250 // scan all textures, and mark any archive with textures as "required"
3251 GCon
->Logf(NAME_Debug
, "scanning %d textures", GTextureManager
.GetNumTextures());
3252 for (int f
= 0; f
< GTextureManager
.GetNumTextures(); ++f
) {
3253 VTexture
*tex
= GTextureManager
[f
];
3254 if (tex
&& tex
->SourceLump
>= 0) {
3255 int fl
= W_LumpFile(tex
->SourceLump
);
3256 if (fl
>= 0 && fl
< fsysSearchPaths
.length()) fsysSearchPaths
[fl
]->required
= true;
3260 // scan all archives, and mark any archives with important files as "required"
3261 GCon
->Log(NAME_Debug
, "scanning archives");
3262 for (auto &&arc
: fsysSearchPaths
) {
3263 if (arc
->required
) continue; // already marked
3264 for (int fn
= arc
->IterateNS(0, WADNS_Global
); fn
>= 0; fn
= arc
->IterateNS(fn
+1, WADNS_Global
)) {
3265 //GCon->Logf(NAME_Debug, " %d: %s : %s", fn, *arc->GetPrefix(), *arc->LumpName(fn));
3266 if (arc
->LumpName(fn
) == "decorate" || arc
->LumpName(fn
) == "dehacked") {
3267 arc
->required
= true;
3273 for (auto &&arc
: fsysSearchPaths
) if (arc
->required
) GCon
->Logf(NAME_Debug
, "rq: <%s>", *arc
->GetPrefix());