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_TEKWAR;
27 int     g_addonNum = 0;
28 
29 int r_showfps;
30 
31 // g_gameNamePtr can point to one of: grpfiles[].name (string literal), string
32 // literal, malloc'd block (XXX: possible leak)
33 const char* g_gameNamePtr = NULL;
34 
35 // grp/con handling
36 
37 static const char* defaultgamegrp = "tekwar.pk3";
38 static const char* defaultdeffilename = "tekwar.def";
39 
40 // g_grpNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
41 char* g_grpNamePtr = NULL;
42 // g_scriptNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
43 char* g_scriptNamePtr = NULL;
44 // g_rtsNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
45 char* g_rtsNamePtr = NULL;
46 
clearGrpNamePtr(void)47 void clearGrpNamePtr(void)
48 {
49     Xfree(g_grpNamePtr);
50     // g_grpNamePtr assumed to be assigned to right after
51 }
52 
clearScriptNamePtr(void)53 void clearScriptNamePtr(void)
54 {
55     Xfree(g_scriptNamePtr);
56     // g_scriptNamePtr assumed to be assigned to right after
57 }
58 
G_DefaultGrpFile(void)59 const char* G_DefaultGrpFile(void)
60 {
61     return defaultgamegrp;
62 }
G_DefaultDefFile(void)63 const char* G_DefaultDefFile(void)
64 {
65     return defaultdeffilename;
66 }
67 
G_GrpFile(void)68 const char* G_GrpFile(void)
69 {
70     return (g_grpNamePtr == NULL) ? G_DefaultGrpFile() : g_grpNamePtr;
71 }
72 
G_DefFile(void)73 const char* G_DefFile(void)
74 {
75     return (g_defNamePtr == NULL) ? G_DefaultDefFile() : g_defNamePtr;
76 }
77 
78 //////////
79 
80 // Set up new-style multi-psky handling.
G_InitMultiPsky(int CLOUDYOCEAN__DYN,int MOONSKY1__DYN,int BIGORBIT1__DYN,int LA__DYN)81 void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN)
82 {
83     // When adding other multi-skies, take care that the tileofs[] values are
84     // <= PSKYOFF_MAX. (It can be increased up to MAXPSKYTILES, but should be
85     // set as tight as possible.)
86 
87     // The default sky properties (all others are implicitly zero):
88     psky_t* sky = tileSetupSky(DEFAULTPSKY);
89     sky->lognumtiles = 3;
90     sky->horizfrac = 32768;
91 
92     // CLOUDYOCEAN
93     // Aligns with the drawn scene horizon because it has one itself.
94     sky = tileSetupSky(CLOUDYOCEAN__DYN);
95     sky->lognumtiles = 3;
96     sky->horizfrac = 65536;
97 
98     // MOONSKY1
99     //        earth          mountain   mountain         sun
100     sky = tileSetupSky(MOONSKY1__DYN);
101     sky->lognumtiles = 3;
102     sky->horizfrac = 32768;
103     sky->tileofs[6] = 1;
104     sky->tileofs[1] = 2;
105     sky->tileofs[4] = 2;
106     sky->tileofs[2] = 3;
107 
108     // BIGORBIT1   // orbit
109     //       earth1         2           3           moon/sun
110     sky = tileSetupSky(BIGORBIT1__DYN);
111     sky->lognumtiles = 3;
112     sky->horizfrac = 32768;
113     sky->tileofs[5] = 1;
114     sky->tileofs[6] = 2;
115     sky->tileofs[7] = 3;
116     sky->tileofs[2] = 4;
117 
118     // LA // la city
119     //       earth1         2           3           moon/sun
120     sky = tileSetupSky(LA__DYN);
121     sky->lognumtiles = 3;
122     sky->horizfrac = 16384 + 1024;
123     sky->tileofs[0] = 1;
124     sky->tileofs[1] = 2;
125     sky->tileofs[2] = 1;
126     sky->tileofs[3] = 3;
127     sky->tileofs[4] = 4;
128     sky->tileofs[5] = 0;
129     sky->tileofs[6] = 2;
130     sky->tileofs[7] = 3;
131 
132 #if 0
133     // This assertion should hold. See note above.
134     for (bssize_t i = 0; i < pskynummultis; ++i)
135         for (bssize_t j = 0; j < (1 << multipsky[i].lognumtiles); ++j)
136             Bassert(multipsky[i].tileofs[j] <= PSKYOFF_MAX);
137 #endif
138 }
139 
G_SetupGlobalPsky(void)140 void G_SetupGlobalPsky(void)
141 {
142     int skyIdx = 0;
143 
144     // NOTE: Loop must be running backwards for the same behavior as the game
145     // (greatest sector index with matching parallaxed sky takes precedence).
146     for (int i = numsectors - 1; i >= 0; i--)
147     {
148         if (sector[i].ceilingstat & 1)
149         {
150             skyIdx = getpskyidx(sector[i].ceilingpicnum);
151             if (skyIdx > 0)
152                 break;
153         }
154     }
155 
156     g_pskyidx = skyIdx;
157 }
158 
159 //////////
160 
161 static char g_rootDir[BMAX_PATH];
162 
163 int g_useCwd;
164 int32_t g_groupFileHandle;
165 
166 static struct strllist* CommandPaths, * CommandGrps;
167 
G_ExtPreInit(int32_t argc,char const * const * argv)168 void G_ExtPreInit(int32_t argc, char const* const* argv)
169 {
170     g_useCwd = G_CheckCmdSwitch(argc, argv, "-usecwd");
171 
172 #ifdef _WIN32
173     GetModuleFileName(NULL, g_rootDir, BMAX_PATH);
174     Bcorrectfilename(g_rootDir, 1);
175     //buildvfs_chdir(g_rootDir);
176 #else
177     buildvfs_getcwd(g_rootDir, BMAX_PATH);
178     strcat(g_rootDir, "/");
179 #endif
180 }
181 
G_ExtInit(void)182 void G_ExtInit(void)
183 {
184     char cwd[BMAX_PATH];
185 
186 #ifdef EDUKE32_OSX
187     char* appdir = Bgetappdir();
188     addsearchpath(appdir);
189     Xfree(appdir);
190 #endif
191 
192 #ifdef USE_PHYSFS
193     strncpy(cwd, PHYSFS_getBaseDir(), ARRAY_SIZE(cwd));
194     cwd[ARRAY_SIZE(cwd) - 1] = '\0';
195 #else
196     if (buildvfs_getcwd(cwd, ARRAY_SIZE(cwd)) && Bstrcmp(cwd, "/") != 0)
197 #endif
198         addsearchpath(cwd);
199 
200     // TODO:
201     if (CommandPaths)
202     {
203         int32_t i;
204         struct strllist* s;
205         while (CommandPaths)
206         {
207             s = CommandPaths->next;
208             i = addsearchpath(CommandPaths->str);
209             if (i < 0)
210             {
211                 initprintf("Failed adding %s for game data: %s\n", CommandPaths->str,
212                     i == -1 ? "not a directory" : "no such directory");
213             }
214 
215             Xfree(CommandPaths->str);
216             Xfree(CommandPaths);
217             CommandPaths = s;
218         }
219     }
220 
221 #if defined(_WIN32) && !defined(EDUKE32_STANDALONE)
222     if (buildvfs_exists("user_profiles_enabled"))
223 #else
224     if (g_useCwd == 0 && !buildvfs_exists("user_profiles_disabled"))
225 #endif
226     {
227         char* homedir;
228         int32_t asperr;
229 
230         if ((homedir = Bgethomedir()))
231         {
232             Bsnprintf(cwd, ARRAY_SIZE(cwd), "%s/"
233 #if defined(_WIN32)
234                 APPNAME
235 #elif defined(GEKKO)
236                 "apps/" APPBASENAME
237 #else
238                 ".config/" APPBASENAME
239 #endif
240                 , homedir);
241             asperr = addsearchpath(cwd);
242             if (asperr == -2)
243             {
244                 if (buildvfs_mkdir(cwd, S_IRWXU) == 0) asperr = addsearchpath(cwd);
245                 else asperr = -1;
246             }
247             if (asperr == 0)
248                 buildvfs_chdir(cwd);
249             Xfree(homedir);
250         }
251     }
252 }
253 
G_ScanGroups(void)254 void G_ScanGroups(void)
255 {
256     ScanGroups();
257 
258     g_selectedGrp = NULL;
259 
260     char const* const currentGrp = G_GrpFile();
261 
262     for (grpfile_t const* fg = foundgrps; fg; fg = fg->next)
263     {
264         if (!Bstrcasecmp(fg->filename, currentGrp))
265         {
266             g_selectedGrp = fg;
267             break;
268         }
269     }
270 
271     if (g_selectedGrp == NULL)
272         g_selectedGrp = foundgrps;
273 }
274 
G_TryLoadingGrp(char const * const grpfile)275 static int32_t G_TryLoadingGrp(char const* const grpfile)
276 {
277     int32_t i;
278 
279     if ((i = initgroupfile(grpfile)) == -1)
280         initprintf("Warning: could not find main data file \"%s\"!\n", grpfile);
281     else
282         initprintf("Using \"%s\" as main game data file.\n", grpfile);
283 
284     return i;
285 }
286 
G_LoadGrpDependencyChain(grpfile_t const * const grp)287 static int32_t G_LoadGrpDependencyChain(grpfile_t const* const grp)
288 {
289     if (!grp)
290         return -1;
291 
292     if (grp->type->dependency && grp->type->dependency != grp->type->crcval)
293         G_LoadGrpDependencyChain(FindGroup(grp->type->dependency));
294 
295     int32_t const i = G_TryLoadingGrp(grp->filename);
296 
297     return i;
298 }
299 
G_LoadGroups(int32_t autoload)300 void G_LoadGroups(int32_t autoload)
301 {
302     if (g_modDir[0] != '/')
303     {
304         char cwd[BMAX_PATH];
305 
306         Bstrcat(g_rootDir, g_modDir);
307         addsearchpath(g_rootDir);
308         //        addsearchpath(mod_dir);
309 
310         char path[BMAX_PATH];
311 
312         if (buildvfs_getcwd(cwd, BMAX_PATH))
313         {
314             Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir);
315             if (!Bstrcmp(g_rootDir, path))
316             {
317                 if (addsearchpath(path) == -2)
318                     if (buildvfs_mkdir(path, S_IRWXU) == 0)
319                         addsearchpath(path);
320             }
321         }
322 
323 #ifdef USE_OPENGL
324         Bsnprintf(path, sizeof(path), "%s/%s", g_modDir, TEXCACHEFILE);
325         Bstrcpy(TEXCACHEFILE, path);
326 #endif
327     }
328 
329     const char* grpfile;
330     int32_t i;
331 
332     if ((i = G_LoadGrpDependencyChain(g_selectedGrp)) != -1)
333     {
334         grpfile = g_selectedGrp->filename;
335 
336         clearGrpNamePtr();
337         g_grpNamePtr = dup_filename(grpfile);
338 
339         grpinfo_t const* const type = g_selectedGrp->type;
340 
341         g_gameType = type->game;
342         g_gameNamePtr = type->name;
343 
344         if (type->defname && g_defNamePtr == NULL)
345             g_defNamePtr = dup_filename(type->defname);
346     }
347     else
348     {
349         grpfile = G_GrpFile();
350         i = G_TryLoadingGrp(grpfile);
351     }
352 
353     if (autoload)
354     {
355         G_LoadGroupsInDir("autoload");
356 
357         if (i != -1)
358             G_DoAutoload(grpfile);
359     }
360 
361     if (g_modDir[0] != '/')
362         G_LoadGroupsInDir(g_modDir);
363 
364     loaddefinitions_game(G_DefFile(), TRUE);
365 
366     struct strllist* s;
367 
368     int const bakpathsearchmode = pathsearchmode;
369     pathsearchmode = 1;
370 
371     while (CommandGrps)
372     {
373         int32_t j;
374 
375         s = CommandGrps->next;
376 
377         if ((j = initgroupfile(CommandGrps->str)) == -1)
378             initprintf("Could not find file \"%s\".\n", CommandGrps->str);
379         else
380         {
381             g_groupFileHandle = j;
382             initprintf("Using file \"%s\" as game data.\n", CommandGrps->str);
383             if (autoload)
384                 G_DoAutoload(CommandGrps->str);
385         }
386 
387         Xfree(CommandGrps->str);
388         Xfree(CommandGrps);
389         CommandGrps = s;
390     }
391     pathsearchmode = bakpathsearchmode;
392 }
393 
G_CleanupSearchPaths(void)394 void G_CleanupSearchPaths(void)
395 {
396     removesearchpaths_withuser(SEARCHPATH_REMOVE);
397 }
398 
399 //////////
400 
401 GrowArray<char*> g_scriptModules;
402 
G_AddGroup(const char * buffer)403 void G_AddGroup(const char* buffer)
404 {
405     char buf[BMAX_PATH];
406 
407     struct strllist* s = (struct strllist*)Xcalloc(1, sizeof(struct strllist));
408 
409     Bstrcpy(buf, buffer);
410 
411     if (Bstrchr(buf, '.') == 0)
412         Bstrcat(buf, ".grp");
413 
414     s->str = Xstrdup(buf);
415 
416     if (CommandGrps)
417     {
418         struct strllist* t;
419         for (t = CommandGrps; t->next; t = t->next);
420         t->next = s;
421         return;
422     }
423     CommandGrps = s;
424 }
425 
G_AddPath(const char * buffer)426 void G_AddPath(const char* buffer)
427 {
428     struct strllist* s = (struct strllist*)Xcalloc(1, sizeof(struct strllist));
429     s->str = Xstrdup(buffer);
430 
431     if (CommandPaths)
432     {
433         struct strllist* t;
434         for (t = CommandPaths; t->next; t = t->next);
435         t->next = s;
436         return;
437     }
438     CommandPaths = s;
439 }
440 
G_AddCon(const char * buffer)441 void G_AddCon(const char* buffer)
442 {
443     clearScriptNamePtr();
444     g_scriptNamePtr = dup_filename(buffer);
445     initprintf("Using CON file \"%s\".\n", g_scriptNamePtr);
446 }
447 
G_AddConModule(const char * buffer)448 void G_AddConModule(const char* buffer)
449 {
450     g_scriptModules.append(Xstrdup(buffer));
451 }
452 
453 //////////
454 
455 // loads all group (grp, zip, pk3/4) files in the given directory
G_LoadGroupsInDir(const char * dirname)456 void G_LoadGroupsInDir(const char* dirname)
457 {
458     static const char* extensions[] = { "*.grp", "*.zip", "*.ssi", "*.pk3", "*.pk4" };
459     char buf[BMAX_PATH];
460     fnlist_t fnlist = FNLIST_INITIALIZER;
461 
462     for (auto& extension : extensions)
463     {
464         BUILDVFS_FIND_REC* rec;
465 
466         fnlist_getnames(&fnlist, dirname, extension, -1, 0);
467 
468         for (rec = fnlist.findfiles; rec; rec = rec->next)
469         {
470             Bsnprintf(buf, sizeof(buf), "%s/%s", dirname, rec->name);
471             initprintf("Using group file \"%s\".\n", buf);
472             initgroupfile(buf);
473         }
474 
475         fnlist_clearnames(&fnlist);
476     }
477 }
478 
G_DoAutoload(const char * dirname)479 void G_DoAutoload(const char* dirname)
480 {
481     char buf[BMAX_PATH];
482 
483     Bsnprintf(buf, sizeof(buf), "autoload/%s", dirname);
484     G_LoadGroupsInDir(buf);
485 }
486 
487 //////////
488 
G_LoadLookups(void)489 void G_LoadLookups(void)
490 {
491     int32_t j;
492     buildvfs_kfd fp;
493 
494     if ((fp = kopen4loadfrommod("lookup.dat", 0)) == buildvfs_kfd_invalid)
495         if ((fp = kopen4loadfrommod("lookup.dat", 1)) == buildvfs_kfd_invalid)
496             return;
497 
498     j = paletteLoadLookupTable(fp);
499 
500     if (j < 0)
501     {
502         if (j == -1)
503             initprintf("ERROR loading \"lookup.dat\": failed reading enough data.\n");
504 
505         return kclose(fp);
506     }
507 
508     uint8_t paldata[768];
509 
510     for (j = 1; j <= 5; j++)
511     {
512         // Account for TITLE and REALMS swap between basepal number and on-disk order.
513         int32_t basepalnum = (j == 3 || j == 4) ? 4 + 3 - j : j;
514 
515         if (kread_and_test(fp, paldata, 768))
516             return kclose(fp);
517 
518         for (unsigned char& k : paldata)
519             k <<= 2;
520 
521         paletteSetColorTable(basepalnum, paldata);
522     }
523 
524     kclose(fp);
525 }
526 
527 //////////
528 
529 #ifdef FORMAT_UPGRADE_ELIGIBLE
530 int g_maybeUpgradeSoundFormats = 1;
531 
S_TryFormats(char * const testfn,char * const fn_suffix,char const searchfirst)532 static buildvfs_kfd S_TryFormats(char* const testfn, char* const fn_suffix, char const searchfirst)
533 {
534     if (!g_maybeUpgradeSoundFormats)
535         return buildvfs_kfd_invalid;
536 
537     static char const* extensions[] =
538     {
539 #ifdef HAVE_FLAC
540         ".flac",
541 #endif
542 #ifdef HAVE_VORBIS
543         ".ogg",
544 #endif
545     };
546 
547     for (char const* ext : extensions)
548     {
549         Bstrcpy(fn_suffix, ext);
550         buildvfs_kfd const fp = kopen4loadfrommod(testfn, searchfirst);
551         if (fp != buildvfs_kfd_invalid)
552             return fp;
553     }
554 
555     return buildvfs_kfd_invalid;
556 }
557 
S_TryExtensionReplacements(char * const testfn,char const searchfirst,uint8_t const ismusic)558 static buildvfs_kfd S_TryExtensionReplacements(char* const testfn, char const searchfirst, uint8_t const ismusic)
559 {
560     char* extension = Bstrrchr(testfn, '.');
561     char* const fn_end = Bstrchr(testfn, '\0');
562 
563     // ex: grabbag.voc --> grabbag_voc.*
564     if (extension != NULL)
565     {
566         *extension = '_';
567 
568         buildvfs_kfd const fp = S_TryFormats(testfn, fn_end, searchfirst);
569         if (fp != buildvfs_kfd_invalid)
570             return fp;
571     }
572     else
573     {
574         extension = fn_end;
575     }
576 
577     // ex: grabbag.mid --> grabbag.*
578     if (ismusic)
579     {
580         buildvfs_kfd const fp = S_TryFormats(testfn, extension, searchfirst);
581         if (fp != buildvfs_kfd_invalid)
582             return fp;
583     }
584 
585     return buildvfs_kfd_invalid;
586 }
587 
S_OpenAudio(const char * fn,char searchfirst,uint8_t const ismusic)588 buildvfs_kfd S_OpenAudio(const char* fn, char searchfirst, uint8_t const ismusic)
589 {
590     buildvfs_kfd const origfp = kopen4loadfrommod(fn, searchfirst);
591 #ifndef USE_PHYSFS
592     char const* const origparent = origfp != buildvfs_kfd_invalid ? kfileparent(origfp) : NULL;
593     uint32_t const    parentlength = origparent != NULL ? Bstrlen(origparent) : 0;
594 
595     auto testfn = (char*)Xmalloc(Bstrlen(fn) + 12 + parentlength); // "music/" + overestimation of parent minus extension + ".flac" + '\0'
596 #else
597     auto testfn = (char*)Xmalloc(Bstrlen(fn) + 12);
598 #endif
599 
600     // look in ./
601     // ex: ./grabbag.mid
602     Bstrcpy(testfn, fn);
603     buildvfs_kfd fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
604     if (fp != buildvfs_kfd_invalid)
605         goto success;
606 
607 #ifndef USE_PHYSFS
608     // look in ./music/<file's parent GRP name>/
609     // ex: ./music/duke3d/grabbag.mid
610     // ex: ./music/nwinter/grabbag.mid
611     if (origparent != NULL)
612     {
613         char const* const parentextension = Bstrrchr(origparent, '.');
614         uint32_t const namelength = parentextension != NULL ? (unsigned)(parentextension - origparent) : parentlength;
615 
616         Bsprintf(testfn, "music/%.*s/%s", namelength, origparent, fn);
617         fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
618         if (fp != buildvfs_kfd_invalid)
619             goto success;
620     }
621 
622     // look in ./music/
623     // ex: ./music/grabbag.mid
624     Bsprintf(testfn, "music/%s", fn);
625     fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
626     if (fp != buildvfs_kfd_invalid)
627         goto success;
628 #endif
629 
630     Xfree(testfn);
631     return origfp;
632 
633 success:
634     Xfree(testfn);
635     kclose(origfp);
636     return fp;
637 }
638 
G_Polymer_UnInit(void)639 void G_Polymer_UnInit(void) { }
640 
calc_smoothratio(ClockTicks totalclk,ClockTicks ototalclk)641 static inline int32_t calc_smoothratio(ClockTicks totalclk, ClockTicks ototalclk)
642 {
643     // if (!((ud.show_help == 0 && (!g_netServer && ud.multimode < 2) && ((g_player[myconnectindex].ps->gm & MODE_MENU) == 0)) ||
644     //       (g_netServer || ud.multimode > 1) ||
645     //       ud.recstat == 2) ||
646     //     ud.pause_on)
647     // {
648     //     return 65536;
649     // }
650 
651 /* TODO
652     if (bRecord || bPlayback || nFreeze != 0 || bCamera || bPause)
653         return 65536;
654 */
655 
656     int32_t rfreq = (refreshfreq != -1 ? refreshfreq : 60);
657     uint64_t elapsedFrames = tabledivide64(((uint64_t)(totalclk - ototalclk).toScale16()) * rfreq, 65536 * 120);
658 #if 0
659     //POGO: additional debug info for testing purposes
660     OSD_Printf("Elapsed frames: %" PRIu64 ", smoothratio: %" PRIu64 "\n", elapsedFrames, tabledivide64(65536 * elapsedFrames * 30, rfreq));
661 #endif
662     return clamp(tabledivide64(65536 * elapsedFrames * 30, rfreq), 0, 65536);
663 }
664 
665 #endif
666