1 //
2 // Common non-engine code/data for EDuke32 and Mapster32
3 //
4 
5 #include "compat.h"
6 #include "build.h"
7 #include "baselayer.h"
8 #include "palette.h"
9 
10 #include "grpscan.h"
11 
12 #include "vfs.h"
13 
14 #ifdef _WIN32
15 # include "windows_inc.h"
16 # include "winbits.h"
17 #elif defined __APPLE__
18 # include "osxbits.h"
19 #endif
20 
21 #include "common.h"
22 #include "common_game.h"
23 
24 struct grpfile_t const *g_selectedGrp;
25 
26 int32_t g_gameType = GAMEFLAG_DUKE;
27 int     g_addonNum = 0;
28 
29 // g_gameNamePtr can point to one of: grpfiles[].name (string literal), string
30 // literal, malloc'd block (XXX: possible leak)
31 const char *g_gameNamePtr = NULL;
32 
33 // grp/con handling
34 
35 static const char *defaultconfilename                = "GAME.CON";
36 #ifndef EDUKE32_STANDALONE
37 static const char *defaultgamegrp[GAMECOUNT]         = { "DUKE3D.GRP", "NAM.GRP", "NAPALM.GRP", "WW2GI.GRP" };
38 static const char *defaultdeffilename[GAMECOUNT]     = { "duke3d.def", "nam.def", "napalm.def", "ww2gi.def" };
39 static const char *defaultgameconfilename[GAMECOUNT] = { "EDUKE.CON", "NAM.CON", "NAPALM.CON", "WW2GI.CON" };
40 #endif
41 
42 // g_grpNamePtr can ONLY point to a Bmalloc'd block (length BMAX_PATH)
43 char *g_grpNamePtr = NULL;
44 // g_scriptNamePtr can ONLY point to a Bmalloc'd block (length BMAX_PATH)
45 char *g_scriptNamePtr = NULL;
46 // g_rtsNamePtr can ONLY point to a Bmalloc'd block (length BMAX_PATH)
47 char *g_rtsNamePtr = NULL;
48 
clearGrpNamePtr(void)49 void clearGrpNamePtr(void)
50 {
51     Xfree(g_grpNamePtr);
52     // g_grpNamePtr assumed to be assigned to right after
53 }
54 
clearScriptNamePtr(void)55 void clearScriptNamePtr(void)
56 {
57     Xfree(g_scriptNamePtr);
58     // g_scriptNamePtr assumed to be assigned to right after
59 }
60 
G_DefaultGrpFile(void)61 const char *G_DefaultGrpFile(void)
62 {
63 #ifndef EDUKE32_STANDALONE
64     if (DUKE)
65         return defaultgamegrp[GAME_DUKE];
66     else if (NAPALM)
67         return defaultgamegrp[GAME_NAPALM];
68     else if (WW2GI)
69         return defaultgamegrp[GAME_WW2GI];
70     else if (NAM)
71         return defaultgamegrp[GAME_NAM];
72 
73     return defaultgamegrp[0];
74 #else
75     return "(none)";
76 #endif
77 }
G_DefaultDefFile(void)78 const char *G_DefaultDefFile(void)
79 {
80 #ifndef EDUKE32_STANDALONE
81     if (DUKE)
82         return defaultdeffilename[GAME_DUKE];
83     else if (WW2GI)
84         return defaultdeffilename[GAME_WW2GI];
85     else if (NAPALM)
86     {
87         if (!testkopen(defaultdeffilename[GAME_NAPALM],0) && testkopen(defaultdeffilename[GAME_NAM],0))
88             return defaultdeffilename[GAME_NAM]; // NAM/NAPALM Sharing
89         else
90             return defaultdeffilename[GAME_NAPALM];
91     }
92     else if (NAM)
93     {
94         if (!testkopen(defaultdeffilename[GAME_NAM],0) && testkopen(defaultdeffilename[GAME_NAPALM],0))
95             return defaultdeffilename[GAME_NAPALM]; // NAM/NAPALM Sharing
96         else
97             return defaultdeffilename[GAME_NAM];
98     }
99 
100     return defaultdeffilename[0];
101 #else
102     return "(none)";
103 #endif
104 }
G_DefaultConFile(void)105 const char *G_DefaultConFile(void)
106 {
107 #ifndef EDUKE32_STANDALONE
108     if (DUKE && testkopen(defaultgameconfilename[GAME_DUKE],0))
109         return defaultgameconfilename[GAME_DUKE];
110     else if (WW2GI && testkopen(defaultgameconfilename[GAME_WW2GI],0))
111         return defaultgameconfilename[GAME_WW2GI];
112     else if (NAPALM)
113     {
114         if (!testkopen(defaultgameconfilename[GAME_NAPALM],0))
115         {
116             if (testkopen(defaultgameconfilename[GAME_NAM],0))
117                 return defaultgameconfilename[GAME_NAM]; // NAM/NAPALM Sharing
118         }
119         else
120             return defaultgameconfilename[GAME_NAPALM];
121     }
122     else if (NAM)
123     {
124         if (!testkopen(defaultgameconfilename[GAME_NAM],0))
125         {
126             if (testkopen(defaultgameconfilename[GAME_NAPALM],0))
127                 return defaultgameconfilename[GAME_NAPALM]; // NAM/NAPALM Sharing
128         }
129         else
130             return defaultgameconfilename[GAME_NAM];
131     }
132 #endif
133     return defaultconfilename;
134 }
135 
G_GrpFile(void)136 const char *G_GrpFile(void)
137 {
138     return (g_grpNamePtr == NULL) ? G_DefaultGrpFile() : g_grpNamePtr;
139 }
140 
G_DefFile(void)141 const char *G_DefFile(void)
142 {
143     return (g_defNamePtr == NULL) ? G_DefaultDefFile() : g_defNamePtr;
144 }
145 
G_ConFile(void)146 const char *G_ConFile(void)
147 {
148     return (g_scriptNamePtr == NULL) ? G_DefaultConFile() : g_scriptNamePtr;
149 }
150 
151 //////////
152 
153 // Set up new-style multi-psky handling.
G_InitMultiPsky(int CLOUDYOCEAN__DYN,int MOONSKY1__DYN,int BIGORBIT1__DYN,int LA__DYN)154 void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN)
155 {
156     // When adding other multi-skies, take care that the tileofs[] values are
157     // <= PSKYOFF_MAX. (It can be increased up to MAXPSKYTILES, but should be
158     // set as tight as possible.)
159 
160     // The default sky properties (all others are implicitly zero):
161     psky_t *sky      = tileSetupSky(DEFAULTPSKY);
162     sky->lognumtiles = 3;
163     sky->horizfrac   = 32768;
164 
165     // CLOUDYOCEAN
166     // Aligns with the drawn scene horizon because it has one itself.
167     sky              = tileSetupSky(CLOUDYOCEAN__DYN);
168     sky->lognumtiles = 3;
169     sky->horizfrac   = 65536;
170 
171     // MOONSKY1
172     //        earth          mountain   mountain         sun
173     sky              = tileSetupSky(MOONSKY1__DYN);
174     sky->lognumtiles = 3;
175     sky->horizfrac   = 32768;
176     sky->tileofs[6]  = 1;
177     sky->tileofs[1]  = 2;
178     sky->tileofs[4]  = 2;
179     sky->tileofs[2]  = 3;
180 
181     // BIGORBIT1   // orbit
182     //       earth1         2           3           moon/sun
183     sky              = tileSetupSky(BIGORBIT1__DYN);
184     sky->lognumtiles = 3;
185     sky->horizfrac   = 32768;
186     sky->tileofs[5]  = 1;
187     sky->tileofs[6]  = 2;
188     sky->tileofs[7]  = 3;
189     sky->tileofs[2]  = 4;
190 
191     // LA // la city
192     //       earth1         2           3           moon/sun
193     sky              = tileSetupSky(LA__DYN);
194     sky->lognumtiles = 3;
195     sky->horizfrac   = 16384 + 1024;
196     sky->tileofs[0]  = 1;
197     sky->tileofs[1]  = 2;
198     sky->tileofs[2]  = 1;
199     sky->tileofs[3]  = 3;
200     sky->tileofs[4]  = 4;
201     sky->tileofs[5]  = 0;
202     sky->tileofs[6]  = 2;
203     sky->tileofs[7]  = 3;
204 
205 #if 0
206     // This assertion should hold. See note above.
207     for (bssize_t i=0; i<pskynummultis; ++i)
208         for (bssize_t j=0; j<(1<<multipsky[i].lognumtiles); ++j)
209             Bassert(multipsky[i].tileofs[j] <= PSKYOFF_MAX);
210 #endif
211 }
212 
G_SetupGlobalPsky(void)213 void G_SetupGlobalPsky(void)
214 {
215     int skyIdx = 0;
216 
217     // NOTE: Loop must be running backwards for the same behavior as the game
218     // (greatest sector index with matching parallaxed sky takes precedence).
219     for (int i = numsectors - 1; i >= 0; i--)
220     {
221         if (sector[i].ceilingstat & 1)
222         {
223             skyIdx = getpskyidx(sector[i].ceilingpicnum);
224             if (skyIdx > 0)
225                 break;
226         }
227     }
228 
229     g_pskyidx = skyIdx;
230 }
231 
232 //////////
233 
234 static char g_rootDir[BMAX_PATH];
235 
236 int g_useCwd;
237 static void G_LoadAddon(void);
238 int32_t g_groupFileHandle;
239 
240 static struct strllist *CommandPaths, *CommandGrps;
241 
G_ExtPreInit(int32_t argc,char const * const * argv)242 void G_ExtPreInit(int32_t argc,char const * const * argv)
243 {
244     g_useCwd = G_CheckCmdSwitch(argc, argv, "-usecwd");
245 
246 #ifdef _WIN32
247     GetModuleFileName(NULL,g_rootDir,BMAX_PATH);
248     Bcorrectfilename(g_rootDir,1);
249     //buildvfs_chdir(g_rootDir);
250 #else
251     buildvfs_getcwd(g_rootDir,BMAX_PATH);
252     strcat(g_rootDir,"/");
253 #endif
254 }
255 
G_ExtInit(void)256 void G_ExtInit(void)
257 {
258 #ifdef EDUKE32_OSX
259     char *appdir = Bgetappdir();
260     addsearchpath(appdir);
261     Xfree(appdir);
262 #endif
263 
264     char cwd[BMAX_PATH];
265 #ifdef USE_PHYSFS
266     strncpy(cwd, PHYSFS_getBaseDir(), ARRAY_SIZE(cwd));
267     cwd[ARRAY_SIZE(cwd)-1] = '\0';
268 #else
269     if (buildvfs_getcwd(cwd, ARRAY_SIZE(cwd)) && Bstrcmp(cwd, "/") != 0)
270 #endif
271         addsearchpath(cwd);
272 
273     if (CommandPaths)
274     {
275         int32_t i;
276         struct strllist *s;
277         while (CommandPaths)
278         {
279             s = CommandPaths->next;
280             i = addsearchpath(CommandPaths->str);
281             if (i < 0)
282             {
283                 initprintf("Failed adding %s for game data: %s\n", CommandPaths->str,
284                            i==-1 ? "not a directory" : "no such directory");
285             }
286 
287             Xfree(CommandPaths->str);
288             Xfree(CommandPaths);
289             CommandPaths = s;
290         }
291     }
292 
293 #if defined(_WIN32) && !defined(EDUKE32_STANDALONE)
294     if (buildvfs_exists("user_profiles_enabled"))
295 #else
296     if (g_useCwd == 0 && !buildvfs_exists("user_profiles_disabled"))
297 #endif
298     {
299         char *homedir;
300         int32_t asperr;
301 
302         if ((homedir = Bgethomedir()))
303         {
304             Bsnprintf(cwd, ARRAY_SIZE(cwd), "%s/"
305 #if defined(_WIN32)
306                       APPNAME
307 #elif defined(GEKKO)
308                       "apps/" APPBASENAME
309 #else
310                       ".config/" APPBASENAME
311 #endif
312                       ,homedir);
313             asperr = addsearchpath(cwd);
314             if (asperr == -2)
315             {
316                 if (buildvfs_mkdir(cwd,S_IRWXU) == 0) asperr = addsearchpath(cwd);
317                 else asperr = -1;
318             }
319             if (asperr == 0)
320                 buildvfs_chdir(cwd);
321             Xfree(homedir);
322         }
323     }
324 
325     // JBF 20031220: Because it's annoying renaming GRP files whenever I want to test different game data
326 #ifndef EDUKE32_STANDALONE
327     if (g_grpNamePtr == NULL)
328     {
329         const char *cp = getenv("DUKE3DGRP");
330         if (cp)
331         {
332             clearGrpNamePtr();
333             g_grpNamePtr = dup_filename(cp);
334             initprintf("Using \"%s\" as main GRP file\n", g_grpNamePtr);
335         }
336     }
337 #endif
338 }
339 
G_ScanGroups(void)340 void G_ScanGroups(void)
341 {
342     ScanGroups();
343 
344     g_selectedGrp = NULL;
345 
346     char const * const currentGrp = G_GrpFile();
347 
348     for (grpfile_t const *fg = foundgrps; fg; fg=fg->next)
349     {
350         if (!Bstrcasecmp(fg->filename, currentGrp))
351         {
352             g_selectedGrp = fg;
353             break;
354         }
355     }
356 
357     if (g_selectedGrp == NULL)
358         g_selectedGrp = foundgrps;
359 }
360 
G_TryLoadingGrp(char const * const grpfile)361 static int32_t G_TryLoadingGrp(char const * const grpfile)
362 {
363     int32_t i;
364 
365     if ((i = initgroupfile(grpfile)) == -1)
366         initprintf("Warning: could not find main data file \"%s\"!\n", grpfile);
367     else
368         initprintf("Using \"%s\" as main game data file.\n", grpfile);
369 
370     return i;
371 }
372 
G_LoadGrpDependencyChain(grpfile_t const * const grp)373 static int32_t G_LoadGrpDependencyChain(grpfile_t const * const grp)
374 {
375     if (!grp)
376         return -1;
377 
378     if (grp->type->dependency && grp->type->dependency != grp->type->crcval)
379         G_LoadGrpDependencyChain(FindGroup(grp->type->dependency));
380 
381     int32_t const i = G_TryLoadingGrp(grp->filename);
382 
383     if (grp->type->postprocessing)
384         grp->type->postprocessing(i);
385 
386     return i;
387 }
388 
G_LoadGroups(int32_t autoload)389 void G_LoadGroups(int32_t autoload)
390 {
391     if (g_modDir[0] != '/')
392     {
393         char cwd[BMAX_PATH];
394 
395         Bstrcat(g_rootDir, g_modDir);
396         addsearchpath(g_rootDir);
397         //        addsearchpath(mod_dir);
398 
399         char path[BMAX_PATH];
400 
401         if (buildvfs_getcwd(cwd, BMAX_PATH))
402         {
403             Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir);
404             if (!Bstrcmp(g_rootDir, path))
405             {
406                 if (addsearchpath(path) == -2)
407                     if (buildvfs_mkdir(path, S_IRWXU) == 0)
408                         addsearchpath(path);
409             }
410         }
411 
412 #ifdef USE_OPENGL
413         Bsnprintf(path, sizeof(path), "%s/%s", g_modDir, TEXCACHEFILE);
414         Bstrcpy(TEXCACHEFILE, path);
415 #endif
416     }
417 
418     if (g_addonNum)
419         G_LoadAddon();
420 
421     const char *grpfile;
422     int32_t i;
423 
424     if ((i = G_LoadGrpDependencyChain(g_selectedGrp)) != -1)
425     {
426         grpfile = g_selectedGrp->filename;
427 
428         clearGrpNamePtr();
429         g_grpNamePtr = dup_filename(grpfile);
430 
431         grpinfo_t const * const type = g_selectedGrp->type;
432 
433         g_gameType = type->game;
434         g_gameNamePtr = type->name;
435 
436         if (type->scriptname && g_scriptNamePtr == NULL)
437             g_scriptNamePtr = dup_filename(type->scriptname);
438 
439         if (type->defname && g_defNamePtr == NULL)
440             g_defNamePtr = dup_filename(type->defname);
441 
442         if (type->rtsname && g_rtsNamePtr == NULL)
443             g_rtsNamePtr = dup_filename(type->rtsname);
444     }
445     else
446     {
447         grpfile = G_GrpFile();
448         i = G_TryLoadingGrp(grpfile);
449     }
450 
451     if (autoload)
452     {
453         G_LoadGroupsInDir("autoload");
454 
455         if (i != -1)
456             G_DoAutoload(grpfile);
457     }
458 
459     if (g_modDir[0] != '/')
460         G_LoadGroupsInDir(g_modDir);
461 
462 #ifndef EDUKE32_STANDALONE
463     if (g_defNamePtr == NULL)
464     {
465         const char *tmpptr = getenv("DUKE3DDEF");
466         if (tmpptr)
467         {
468             clearDefNamePtr();
469             g_defNamePtr = dup_filename(tmpptr);
470             initprintf("Using \"%s\" as definitions file\n", g_defNamePtr);
471         }
472     }
473 #endif
474 
475     loaddefinitions_game(G_DefFile(), TRUE);
476 
477     struct strllist *s;
478 
479     int const bakpathsearchmode = pathsearchmode;
480     pathsearchmode = 1;
481 
482     while (CommandGrps)
483     {
484         int32_t j;
485 
486         s = CommandGrps->next;
487 
488         if ((j = initgroupfile(CommandGrps->str)) == -1)
489             initprintf("Could not find file \"%s\".\n", CommandGrps->str);
490         else
491         {
492             g_groupFileHandle = j;
493             initprintf("Using file \"%s\" as game data.\n", CommandGrps->str);
494             if (autoload)
495                 G_DoAutoload(CommandGrps->str);
496         }
497 
498         Xfree(CommandGrps->str);
499         Xfree(CommandGrps);
500         CommandGrps = s;
501     }
502     pathsearchmode = bakpathsearchmode;
503 }
504 
G_LoadAddon(void)505 static void G_LoadAddon(void)
506 {
507 #ifndef EDUKE32_STANDALONE
508     uint32_t crc;
509 
510     switch (g_addonNum)
511     {
512     case ADDON_DUKEDC:
513         crc = DUKEDC_CRC;
514         break;
515     case ADDON_NWINTER:
516         crc = DUKENW_CRC;
517         break;
518     case ADDON_CARIBBEAN:
519         crc = DUKECB_CRC;
520         break;
521     default:
522         return;
523     }
524 
525     grpfile_t const * const grp = FindGroup(crc);
526 
527     if (grp)
528         g_selectedGrp = grp;
529 #endif
530 }
531 
532 #ifndef EDUKE32_STANDALONE
533 #ifndef EDUKE32_TOUCH_DEVICES
534 
535 #if defined __linux__ || defined EDUKE32_BSD
Duke_Add_GOG_Atomic_Linux(const char * path)536 static void Duke_Add_GOG_Atomic_Linux(const char * path)
537 {
538     char buf[BMAX_PATH];
539 
540     Bsnprintf(buf, sizeof(buf), "%s/data", path);
541     addsearchpath_user(buf, SEARCHPATH_REMOVE);
542 }
Fury_Add_GOG_Linux(const char * path)543 static void Fury_Add_GOG_Linux(const char * path)
544 {
545     char buf[BMAX_PATH];
546 
547     Bsnprintf(buf, sizeof(buf), "%s/game", path);
548     addsearchpath(buf);
549 }
550 #endif
551 
552 #if defined EDUKE32_OSX || defined __linux__ || defined EDUKE32_BSD
Duke_AddSteamPaths(const char * basepath)553 static void Duke_AddSteamPaths(const char *basepath)
554 {
555     char buf[BMAX_PATH];
556 
557     // Duke Nukem 3D: Megaton Edition - Steam
558     static char const s_Megaton_Steam[] = "steamapps/common/Duke Nukem 3D/gameroot";
559     Bsnprintf(buf, sizeof(buf), "%s/%s", basepath, s_Megaton_Steam);
560     addsearchpath(buf);
561     Bsnprintf(buf, sizeof(buf), "%s/%s/addons/dc", basepath, s_Megaton_Steam);
562     addsearchpath_user(buf, SEARCHPATH_REMOVE);
563     Bsnprintf(buf, sizeof(buf), "%s/%s/addons/nw", basepath, s_Megaton_Steam);
564     addsearchpath_user(buf, SEARCHPATH_REMOVE);
565     Bsnprintf(buf, sizeof(buf), "%s/%s/addons/vacation", basepath, s_Megaton_Steam);
566     addsearchpath_user(buf, SEARCHPATH_REMOVE);
567 
568     // Duke Nukem 3D - 3D Realms Anthology / Kill-A-Ton Collection 2015 - Steam
569 #if defined EDUKE32_OSX
570     Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/Duke Nukem 3D.app/drive_c/Program Files/Duke Nukem 3D", basepath);
571     addsearchpath_user(buf, SEARCHPATH_REMOVE);
572 #endif
573 
574     // NAM - Steam
575 #if defined EDUKE32_OSX
576     Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Nam/Nam.app/Contents/Resources/Nam.boxer/C.harddisk/NAM", basepath);
577 #else
578     Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Nam/NAM", basepath);
579 #endif
580     addsearchpath_user(buf, SEARCHPATH_NAM);
581 
582     // WWII GI - Steam
583     Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/World War II GI/WW2GI", basepath);
584     addsearchpath_user(buf, SEARCHPATH_WW2GI);
585 
586     // Ion Fury - Steam
587     Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Ion Fury", basepath);
588     addsearchpath_user(buf, SEARCHPATH_FURY);
589 }
590 #endif
591 #endif
592 #endif
593 
G_AddSearchPaths(void)594 void G_AddSearchPaths(void)
595 {
596 #ifndef EDUKE32_STANDALONE
597 #ifndef EDUKE32_TOUCH_DEVICES
598 #if defined __linux__ || defined EDUKE32_BSD
599     char buf[BMAX_PATH];
600     char *homepath = Bgethomedir();
601 
602     Bsnprintf(buf, sizeof(buf), "%s/.steam/steam", homepath);
603     Duke_AddSteamPaths(buf);
604 
605     Bsnprintf(buf, sizeof(buf), "%s/.steam/steam/steamapps/libraryfolders.vdf", homepath);
606     Paths_ParseSteamLibraryVDF(buf, Duke_AddSteamPaths);
607 
608     // Duke Nukem 3D: Atomic Edition - GOG.com
609     Bsnprintf(buf, sizeof(buf), "%s/GOG Games/Duke Nukem 3D Atomic Edition", homepath);
610     Duke_Add_GOG_Atomic_Linux(buf);
611     Paths_ParseXDGDesktopFilesFromGOG(homepath, "Duke_Nukem_3D_Atomic_Edition", Duke_Add_GOG_Atomic_Linux);
612 
613     // Ion Fury - GOG.com
614     Bsnprintf(buf, sizeof(buf), "%s/GOG Games/ION Fury", homepath);
615     Fury_Add_GOG_Linux(buf);
616     Paths_ParseXDGDesktopFilesFromGOG(homepath, "ION_Fury", Fury_Add_GOG_Linux);
617 
618     Xfree(homepath);
619 
620     addsearchpath("/usr/share/games/jfduke3d");
621     addsearchpath("/usr/local/share/games/jfduke3d");
622     addsearchpath("/usr/share/games/eduke32");
623     addsearchpath("/usr/local/share/games/eduke32");
624 #elif defined EDUKE32_OSX
625     char buf[BMAX_PATH];
626     int32_t i;
627     char *applications[] = { osx_getapplicationsdir(0), osx_getapplicationsdir(1) };
628     char *support[] = { osx_getsupportdir(0), osx_getsupportdir(1) };
629     char *documents[] = { osx_getdocumentsdir(0), osx_getdocumentsdir(1) };
630 
631     for (i = 0; i < 2; i++)
632     {
633         Bsnprintf(buf, sizeof(buf), "%s/Steam", support[i]);
634         Duke_AddSteamPaths(buf);
635 
636         Bsnprintf(buf, sizeof(buf), "%s/Steam/steamapps/libraryfolders.vdf", support[i]);
637         Paths_ParseSteamLibraryVDF(buf, Duke_AddSteamPaths);
638 
639         // Duke Nukem 3D: Atomic Edition - GOG.com
640         Bsnprintf(buf, sizeof(buf), "%s/Duke Nukem 3D.app/Contents/Resources/Duke Nukem 3D.boxer/C.harddisk", applications[i]);
641         addsearchpath_user(buf, SEARCHPATH_REMOVE);
642         Bsnprintf(buf, sizeof(buf), "%s/Duke Nukem 3D.app/Contents/Resources/game/Duke Nukem 3D.app/Contents/Resources/Duke Nukem 3D.boxer/C.harddisk", applications[i]);
643         addsearchpath_user(buf, SEARCHPATH_REMOVE);
644 
645         // Duke Nukem 3D: Atomic Edition - ZOOM Platform
646         Bsnprintf(buf, sizeof(buf), "%s/Duke Nukem 3D - Atomic Edition.app/Contents/MacOS/Duke3D - Atomic Edition", applications[i]);
647         addsearchpath_user(buf, SEARCHPATH_REMOVE);
648 
649         // NAM - GOG.com
650         Bsnprintf(buf, sizeof(buf), "%s/NAM.app/Contents/Resources/game", applications[i]);
651         addsearchpath_user(buf, SEARCHPATH_NAM);
652         Bsnprintf(buf, sizeof(buf), "%s/NAM.app/Contents/Resources/game/NAM.app/Contents/Resources/game", applications[i]);
653         addsearchpath_user(buf, SEARCHPATH_NAM);
654         Bsnprintf(buf, sizeof(buf), "%s/NAM.app/Contents/Resources/game", documents[i]);
655         addsearchpath_user(buf, SEARCHPATH_NAM);
656     }
657 
658     for (i = 0; i < 2; i++)
659     {
660         Bsnprintf(buf, sizeof(buf), "%s/JFDuke3D", support[i]);
661         addsearchpath(buf);
662         Bsnprintf(buf, sizeof(buf), "%s/EDuke32", support[i]);
663         addsearchpath(buf);
664     }
665 
666     for (i = 0; i < 2; i++)
667     {
668         Xfree(applications[i]);
669         Xfree(support[i]);
670         Xfree(documents[i]);
671     }
672 #elif defined (_WIN32)
673     char buf[BMAX_PATH] = {0};
674     DWORD bufsize;
675 
676     // Duke Nukem 3D: 20th Anniversary World Tour - Steam
677     bufsize = sizeof(buf);
678     if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 434050)", "InstallLocation", buf, &bufsize))
679     {
680         addsearchpath_user(buf, SEARCHPATH_REMOVE);
681     }
682 
683     // Duke Nukem 3D: Megaton Edition - Steam
684     bufsize = sizeof(buf);
685     if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 225140)", "InstallLocation", buf, &bufsize))
686     {
687         char * const suffix = buf + bufsize - 1;
688         DWORD const remaining = sizeof(buf) - bufsize;
689 
690         Bstrncpy(suffix, "/gameroot", remaining);
691         addsearchpath(buf);
692         Bstrncpy(suffix, "/gameroot/addons/dc", remaining);
693         addsearchpath_user(buf, SEARCHPATH_REMOVE);
694         Bstrncpy(suffix, "/gameroot/addons/nw", remaining);
695         addsearchpath_user(buf, SEARCHPATH_REMOVE);
696         Bstrncpy(suffix, "/gameroot/addons/vacation", remaining);
697         addsearchpath_user(buf, SEARCHPATH_REMOVE);
698     }
699 
700     // Duke Nukem 3D - 3D Realms Anthology / Kill-A-Ton Collection 2015 - Steam
701     bufsize = sizeof(buf);
702     if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 359850)", "InstallLocation", buf, &bufsize))
703     {
704         char * const suffix = buf + bufsize - 1;
705         DWORD const remaining = sizeof(buf) - bufsize;
706 
707         Bstrncpy(suffix, "/Duke Nukem 3D", remaining);
708         addsearchpath_user(buf, SEARCHPATH_REMOVE);
709     }
710 
711     // Duke Nukem 3D: Atomic Edition - GOG.com
712     bufsize = sizeof(buf);
713     if (Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1207658730)", "path", buf, &bufsize))
714     {
715         addsearchpath_user(buf, SEARCHPATH_REMOVE);
716     }
717     bufsize = sizeof(buf);
718     if (Paths_ReadRegistryValue("SOFTWARE\\GOG.com\\GOGDUKE3D", "PATH", buf, &bufsize))
719     {
720         addsearchpath_user(buf, SEARCHPATH_REMOVE);
721     }
722 
723     // Duke Nukem 3D: Atomic Edition - ZOOM Platform
724     bufsize = sizeof(buf);
725     if (Paths_ReadRegistryValue(R"(SOFTWARE\ZOOM PLATFORM\Duke Nukem 3D - Atomic Edition)", "InstallPath", buf, &bufsize))
726     {
727         char * const suffix = buf + bufsize - 1;
728         DWORD const remaining = sizeof(buf) - bufsize;
729 
730         addsearchpath_user(buf, SEARCHPATH_REMOVE);
731 
732         Bstrncpy(suffix, "/AddOns", remaining);
733         addsearchpath_user(buf, SEARCHPATH_REMOVE);
734     }
735 
736     // Duke Nukem 3D - 3D Realms Anthology
737     bufsize = sizeof(buf);
738     if (Paths_ReadRegistryValue("SOFTWARE\\3DRealms\\Duke Nukem 3D", NULL, buf, &bufsize))
739     {
740         char * const suffix = buf + bufsize - 1;
741         DWORD const remaining = sizeof(buf) - bufsize;
742 
743         Bstrncpy(suffix, "/Duke Nukem 3D", remaining);
744         addsearchpath_user(buf, SEARCHPATH_REMOVE);
745     }
746 
747     // 3D Realms Anthology
748     bufsize = sizeof(buf);
749     if (Paths_ReadRegistryValue("SOFTWARE\\3DRealms\\Anthology", NULL, buf, &bufsize))
750     {
751         char * const suffix = buf + bufsize - 1;
752         DWORD const remaining = sizeof(buf) - bufsize;
753 
754         Bstrncpy(suffix, "/Duke Nukem 3D", remaining);
755         addsearchpath_user(buf, SEARCHPATH_REMOVE);
756     }
757 
758     // NAM - Steam
759     bufsize = sizeof(buf);
760     if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 329650)", "InstallLocation", buf, &bufsize))
761     {
762         char * const suffix = buf + bufsize - 1;
763         DWORD const remaining = sizeof(buf) - bufsize;
764 
765         Bstrncpy(suffix, "/NAM", remaining);
766         addsearchpath_user(buf, SEARCHPATH_NAM);
767     }
768 
769     // NAM - GOG.com
770     bufsize = sizeof(buf);
771     if (Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1575726518)", "path", buf, &bufsize))
772     {
773         addsearchpath_user(buf, SEARCHPATH_NAM);
774     }
775 
776     // WWII GI - Steam
777     bufsize = sizeof(buf);
778     if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 376750)", "InstallLocation", buf, &bufsize))
779     {
780         char * const suffix = buf + bufsize - 1;
781         DWORD const remaining = sizeof(buf) - bufsize;
782 
783         Bstrncpy(suffix, "/WW2GI", remaining);
784         addsearchpath_user(buf, SEARCHPATH_WW2GI);
785     }
786 
787     // Ion Fury - Steam
788     bufsize = sizeof(buf);
789     if (Paths_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 562860)", "InstallLocation", buf, &bufsize))
790     {
791         addsearchpath_user(buf, SEARCHPATH_FURY);
792     }
793 
794     // Ion Fury - GOG.com
795     bufsize = sizeof(buf);
796     if (Paths_ReadRegistryValue(R"(SOFTWARE\GOG.com\Games\1740836875)", "path", buf, &bufsize))
797     {
798         addsearchpath_user(buf, SEARCHPATH_FURY);
799     }
800 #endif
801 #endif
802 #endif
803 }
804 
G_CleanupSearchPaths(void)805 void G_CleanupSearchPaths(void)
806 {
807     removesearchpaths_withuser(SEARCHPATH_REMOVE);
808 
809     if (!NAM)
810         removesearchpaths_withuser(SEARCHPATH_NAM);
811 
812     if (!WW2GI)
813         removesearchpaths_withuser(SEARCHPATH_WW2GI);
814 
815     if (!FURY)
816         removesearchpaths_withuser(SEARCHPATH_FURY);
817 }
818 
819 //////////
820 
821 GrowArray<char *> g_scriptModules;
822 
G_AddGroup(const char * buffer)823 void G_AddGroup(const char *buffer)
824 {
825     char buf[BMAX_PATH];
826 
827     struct strllist *s = (struct strllist *)Xcalloc(1,sizeof(struct strllist));
828 
829     Bstrcpy(buf, buffer);
830 
831     if (Bstrchr(buf,'.') == 0)
832         Bstrcat(buf,".grp");
833 
834     s->str = Xstrdup(buf);
835 
836     if (CommandGrps)
837     {
838         struct strllist *t;
839         for (t = CommandGrps; t->next; t=t->next) ;
840         t->next = s;
841         return;
842     }
843     CommandGrps = s;
844 }
845 
G_AddPath(const char * buffer)846 void G_AddPath(const char *buffer)
847 {
848     struct strllist *s = (struct strllist *)Xcalloc(1,sizeof(struct strllist));
849     s->str = Xstrdup(buffer);
850 
851     if (CommandPaths)
852     {
853         struct strllist *t;
854         for (t = CommandPaths; t->next; t=t->next) ;
855         t->next = s;
856         return;
857     }
858     CommandPaths = s;
859 }
860 
G_AddCon(const char * buffer)861 void G_AddCon(const char *buffer)
862 {
863     clearScriptNamePtr();
864     g_scriptNamePtr = dup_filename(buffer);
865     initprintf("Using CON file \"%s\".\n",g_scriptNamePtr);
866 }
867 
G_AddConModule(const char * buffer)868 void G_AddConModule(const char *buffer)
869 {
870     g_scriptModules.append(Xstrdup(buffer));
871 }
872 
873 //////////
874 
875 // loads all group (grp, zip, pk3/4) files in the given directory
G_LoadGroupsInDir(const char * dirname)876 void G_LoadGroupsInDir(const char *dirname)
877 {
878     static const char *extensions[] = { "*.grp", "*.zip", "*.ssi", "*.pk3", "*.pk4" };
879     char buf[BMAX_PATH];
880     fnlist_t fnlist = FNLIST_INITIALIZER;
881 
882     for (auto & extension : extensions)
883     {
884         BUILDVFS_FIND_REC *rec;
885 
886         fnlist_getnames(&fnlist, dirname, extension, -1, 0);
887 
888         for (rec=fnlist.findfiles; rec; rec=rec->next)
889         {
890             Bsnprintf(buf, sizeof(buf), "%s/%s", dirname, rec->name);
891             initprintf("Using group file \"%s\".\n", buf);
892             initgroupfile(buf);
893         }
894 
895         fnlist_clearnames(&fnlist);
896     }
897 }
898 
G_DoAutoload(const char * dirname)899 void G_DoAutoload(const char *dirname)
900 {
901     char buf[BMAX_PATH];
902 
903     Bsnprintf(buf, sizeof(buf), "autoload/%s", dirname);
904     G_LoadGroupsInDir(buf);
905 }
906 
907 //////////
908 
G_LoadLookups(void)909 void G_LoadLookups(void)
910 {
911     int32_t j;
912     buildvfs_kfd fp;
913 
914     if ((fp=kopen4loadfrommod("lookup.dat",0)) == buildvfs_kfd_invalid)
915         if ((fp=kopen4loadfrommod("lookup.dat",1)) == buildvfs_kfd_invalid)
916             return;
917 
918     j = paletteLoadLookupTable(fp);
919 
920     if (j < 0)
921     {
922         if (j == -1)
923             initprintf("ERROR loading \"lookup.dat\": failed reading enough data.\n");
924 
925         return kclose(fp);
926     }
927 
928     uint8_t paldata[768];
929 
930     for (j=1; j<=5; j++)
931     {
932         // Account for TITLE and REALMS swap between basepal number and on-disk order.
933         int32_t basepalnum = (j == 3 || j == 4) ? 4+3-j : j;
934 
935         if (kread_and_test(fp, paldata, 768))
936             return kclose(fp);
937 
938         for (unsigned char & k : paldata)
939             k <<= 2;
940 
941         paletteSetColorTable(basepalnum, paldata);
942     }
943 
944     kclose(fp);
945 }
946 
947 //////////
948 
949 #ifdef FORMAT_UPGRADE_ELIGIBLE
950 int g_maybeUpgradeSoundFormats = 1;
951 
S_TryFormats(char * const testfn,char * const fn_suffix,char const searchfirst)952 static buildvfs_kfd S_TryFormats(char * const testfn, char * const fn_suffix, char const searchfirst)
953 {
954     if (!g_maybeUpgradeSoundFormats)
955         return buildvfs_kfd_invalid;
956 
957     static char const * extensions[] =
958     {
959 #ifdef HAVE_FLAC
960         ".flac",
961 #endif
962 #ifdef HAVE_VORBIS
963         ".ogg",
964 #endif
965     };
966 
967     for (char const * ext : extensions)
968     {
969         Bstrcpy(fn_suffix, ext);
970         buildvfs_kfd const fp = kopen4loadfrommod(testfn, searchfirst);
971         if (fp != buildvfs_kfd_invalid)
972             return fp;
973     }
974 
975     return buildvfs_kfd_invalid;
976 }
977 
S_TryExtensionReplacements(char * const testfn,char const searchfirst,uint8_t const ismusic)978 static buildvfs_kfd S_TryExtensionReplacements(char * const testfn, char const searchfirst, uint8_t const ismusic)
979 {
980     char * extension = Bstrrchr(testfn, '.');
981     char * const fn_end = Bstrchr(testfn, '\0');
982 
983     // ex: grabbag.voc --> grabbag_voc.*
984     if (extension != NULL)
985     {
986         *extension = '_';
987 
988         buildvfs_kfd const fp = S_TryFormats(testfn, fn_end, searchfirst);
989         if (fp != buildvfs_kfd_invalid)
990             return fp;
991     }
992     else
993     {
994         extension = fn_end;
995     }
996 
997     // ex: grabbag.mid --> grabbag.*
998     if (ismusic)
999     {
1000         buildvfs_kfd const fp = S_TryFormats(testfn, extension, searchfirst);
1001         if (fp != buildvfs_kfd_invalid)
1002             return fp;
1003     }
1004 
1005     return buildvfs_kfd_invalid;
1006 }
1007 
S_OpenAudio(const char * fn,char searchfirst,uint8_t const ismusic)1008 buildvfs_kfd S_OpenAudio(const char *fn, char searchfirst, uint8_t const ismusic)
1009 {
1010     buildvfs_kfd const origfp      = kopen4loadfrommod(fn, searchfirst);
1011 #ifndef USE_PHYSFS
1012     char const * const origparent  = origfp != buildvfs_kfd_invalid ? kfileparent(origfp) : NULL;
1013     uint32_t const    parentlength = origparent != NULL ? Bstrlen(origparent) : 0;
1014 
1015     auto testfn = (char *)Xmalloc(Bstrlen(fn) + 12 + parentlength); // "music/" + overestimation of parent minus extension + ".flac" + '\0'
1016 #else
1017     auto testfn = (char *)Xmalloc(Bstrlen(fn) + 12);
1018 #endif
1019 
1020     // look in ./
1021     // ex: ./grabbag.mid
1022     Bstrcpy(testfn, fn);
1023     buildvfs_kfd fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
1024     if (fp != buildvfs_kfd_invalid)
1025         goto success;
1026 
1027 #ifndef USE_PHYSFS
1028     // look in ./music/<file's parent GRP name>/
1029     // ex: ./music/duke3d/grabbag.mid
1030     // ex: ./music/nwinter/grabbag.mid
1031     if (origparent != NULL)
1032     {
1033         char const * const parentextension = Bstrrchr(origparent, '.');
1034         uint32_t const namelength = parentextension != NULL ? (unsigned)(parentextension - origparent) : parentlength;
1035 
1036         Bsprintf(testfn, "music/%.*s/%s", namelength, origparent, fn);
1037         fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
1038         if (fp != buildvfs_kfd_invalid)
1039             goto success;
1040     }
1041 
1042     // look in ./music/
1043     // ex: ./music/grabbag.mid
1044     Bsprintf(testfn, "music/%s", fn);
1045     fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
1046     if (fp != buildvfs_kfd_invalid)
1047         goto success;
1048 #endif
1049 
1050     Xfree(testfn);
1051     return origfp;
1052 
1053 success:
1054     Xfree(testfn);
1055     kclose(origfp);
1056     return fp;
1057 }
1058 
1059 #endif
1060 
Duke_CommonCleanup(void)1061 void Duke_CommonCleanup(void)
1062 {
1063     DO_FREE_AND_NULL(g_grpNamePtr);
1064     DO_FREE_AND_NULL(g_scriptNamePtr);
1065     DO_FREE_AND_NULL(g_rtsNamePtr);
1066 }
1067