1 /** @file dd_main.cpp  Engine core.
2  *
3  * @todo Much of this should be refactored and merged into the App classes.
4  * @todo The rest should be split into smaller, perhaps domain-specific files.
5  *
6  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
7  * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
8  * @authors Copyright © 2006-2007 Jamie Jones <jamie_jones_au@yahoo.com.au>
9  *
10  * @par License
11  * GPL: http://www.gnu.org/licenses/gpl.html
12  *
13  * <small>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 the
15  * Free Software Foundation; either version 2 of the License, or (at your
16  * option) any later version. This program is distributed in the hope that it
17  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
18  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19  * Public License for more details. You should have received a copy of the GNU
20  * General Public License along with this program; if not, write to the Free
21  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA</small>
23  */
24 
25 #define DENG_NO_API_MACROS_BASE // functions defined here
26 
27 #include "de_platform.h"
28 #include "dd_main.h"
29 
30 #ifdef WIN32
31 #  define _WIN32_DCOM
32 #  include <objbase.h>
33 #endif
34 #include <cstring>
35 #ifdef UNIX
36 #  include <ctype.h>
37 #endif
38 
39 #include <QStringList>
40 #include <de/charsymbols.h>
41 #include <de/concurrency.h>
42 #include <de/findfile.h>
43 #include <de/memoryzone.h>
44 #include <de/memory.h>
45 #include <de/timer.h>
46 #include <de/ArrayValue>
47 #include <de/CommandLine>
48 #include <de/Garbage>
49 #include <de/NativeFile>
50 #include <de/PackageLoader>
51 #include <de/LinkFile>
52 #include <de/LogBuffer>
53 #include <de/DictionaryValue>
54 #include <de/Log>
55 #include <de/EscapeParser>
56 #include <de/NativePath>
57 #ifdef __CLIENT__
58 #  include <de/texgamma.h>
59 #  include <de/DisplayMode>
60 #endif
61 #include <doomsday/AbstractSession>
62 #include <doomsday/console/alias.h>
63 #include <doomsday/console/cmd.h>
64 #include <doomsday/console/exec.h>
65 #include <doomsday/console/var.h>
66 #include <doomsday/filesys/fs_main.h>
67 #include <doomsday/filesys/fs_util.h>
68 #include <doomsday/filesys/virtualmappings.h>
69 #include <doomsday/filesys/wad.h>
70 #include <doomsday/filesys/zip.h>
71 #include <doomsday/resource/databundle.h>
72 #include <doomsday/resource/manifest.h>
73 #include <doomsday/resource/resources.h>
74 #include <doomsday/res/Bundles>
75 #include <doomsday/res/DoomsdayPackage>
76 #include <doomsday/res/MapManifests>
77 #include <doomsday/res/Sprites>
78 #include <doomsday/res/Textures>
79 #include <doomsday/world/Materials>
80 #include <doomsday/help.h>
81 #include <doomsday/library.h>
82 #include <doomsday/world/entitydef.h>
83 
84 #ifdef __SERVER__
85 #  include "serverapp.h"
86 #endif
87 
88 #include "dd_loop.h"
89 #include "def_main.h"
90 #include "busyrunner.h"
91 #include "con_config.h"
92 #include "sys_system.h"
93 //#include "ui/editors/edit_bias.h"
94 #include "gl/svg.h"
95 
96 #include "world/clientserverworld.h"
97 #include "world/map.h"
98 #include "world/p_players.h"
99 
100 #include "ui/infine/infinesystem.h"
101 #include "ui/nativeui.h"
102 #include "ui/progress.h"
103 
104 #ifdef __CLIENT__
105 #  include "clientapp.h"
106 #  include "ui/clientwindowsystem.h"
107 
108 #  include "client/cledgeloop.h"
109 #  include "client/clientsubsector.h"
110 #  include "client/cl_def.h"
111 #  include "client/cl_infine.h"
112 
113 #  include "gl/gl_main.h"
114 #  include "gl/gl_defer.h"
115 #  include "gl/gl_texmanager.h"
116 
117 #  include "network/net_main.h"
118 #  include "network/net_demo.h"
119 
120 #  include "render/rendersystem.h"
121 #  include "render/cameralensfx.h"
122 #  include "render/r_draw.h" // R_InitViewWindow
123 #  include "render/r_main.h" // pspOffset
124 #  include "render/rend_main.h"
125 #  include "render/rend_font.h"
126 #  include "render/rend_particle.h" // Rend_ParticleLoadSystemTextures
127 #  include "render/vr.h"
128 
129 #  include "Contact"
130 #  include "MaterialAnimator"
131 #  include "Sector"
132 
133 #  include "ui/ui_main.h"
134 #  include "ui/inputsystem.h"
135 #  include "ui/busyvisual.h"
136 #  include "ui/sys_input.h"
137 #  include "ui/widgets/sidebarwidget.h"
138 #  include "ui/widgets/taskbarwidget.h"
139 #  include "ui/home/homewidget.h"
140 
141 #  include "updater.h"
142 #  include "updater/updatedownloaddialog.h"
143 #endif
144 #ifdef __SERVER__
145 #  include "network/net_main.h"
146 
147 #  include "server/sv_def.h"
148 #endif
149 
150 using namespace de;
151 
152 class ZipFileType : public de::NativeFileType
153 {
154 public:
ZipFileType()155     ZipFileType() : NativeFileType("FT_ZIP", RC_PACKAGE)
156     {
157         addKnownExtension(".pk3");
158         addKnownExtension(".zip");
159     }
160 
interpret(FileHandle & hndl,String path,FileInfo const & info) const161     File1 *interpret(FileHandle &hndl, String path, FileInfo const &info) const
162     {
163         if (Zip::recognise(hndl))
164         {
165             LOG_AS("ZipFileType");
166             LOG_RES_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\"");
167             return new Zip(hndl, path, info);
168         }
169         return nullptr;
170     }
171 };
172 
173 class WadFileType : public de::NativeFileType
174 {
175 public:
WadFileType()176     WadFileType() : NativeFileType("FT_WAD", RC_PACKAGE)
177     {
178         addKnownExtension(".wad");
179     }
180 
interpret(FileHandle & hndl,String path,FileInfo const & info) const181     File1 *interpret(FileHandle &hndl, String path, FileInfo const &info) const
182     {
183         if (Wad::recognise(hndl))
184         {
185             LOG_AS("WadFileType");
186             LOG_RES_VERBOSE("Interpreted \"" + NativePath(path).pretty() + "\"");
187             return new Wad(hndl, path, info);
188         }
189         return nullptr;
190     }
191 };
192 
193 static dint DD_StartupWorker(void *context);
194 static dint DD_DummyWorker(void *context);
195 
196 dint isDedicated;
197 dint verbose;                      ///< For debug messages (-verbose).
198 #ifdef __CLIENT__
199 dint symbolicEchoMode = false;     ///< @note Mutable via public API.
200 #endif
201 
202 static char *startupFiles = (char *) "";  ///< List of file names, whitespace seperating (written to .cfg).
203 
registerResourceFileTypes()204 static void registerResourceFileTypes()
205 {
206     FileType *ftype;
207 
208     //
209     // Packages types:
210     //
211     ResourceClass &packageClass = App_ResourceClass("RC_PACKAGE");
212 
213     ftype = new ZipFileType();
214     packageClass.addFileType(ftype);
215     DD_AddFileType(*ftype);
216 
217     ftype = new WadFileType();
218     packageClass.addFileType(ftype);
219     DD_AddFileType(*ftype);
220 
221     ftype = new FileType("FT_LMP", RC_PACKAGE);  ///< Treat lumps as packages so they are mapped to $App.DataPath.
222     ftype->addKnownExtension(".lmp");
223     DD_AddFileType(*ftype);
224     /// @todo ftype leaks. -jk
225 
226     //
227     // Definition fileTypes:
228     //
229     ftype = new FileType("FT_DED", RC_DEFINITION);
230     ftype->addKnownExtension(".ded");
231     App_ResourceClass("RC_DEFINITION").addFileType(ftype);
232     DD_AddFileType(*ftype);
233 
234     //
235     // Graphic fileTypes:
236     //
237     ResourceClass &graphicClass = App_ResourceClass("RC_GRAPHIC");
238 
239     ftype = new FileType("FT_PNG", RC_GRAPHIC);
240     ftype->addKnownExtension(".png");
241     graphicClass.addFileType(ftype);
242     DD_AddFileType(*ftype);
243 
244     ftype = new FileType("FT_TGA", RC_GRAPHIC);
245     ftype->addKnownExtension(".tga");
246     graphicClass.addFileType(ftype);
247     DD_AddFileType(*ftype);
248 
249     ftype = new FileType("FT_JPG", RC_GRAPHIC);
250     ftype->addKnownExtension(".jpg");
251     graphicClass.addFileType(ftype);
252     DD_AddFileType(*ftype);
253 
254     ftype = new FileType("FT_PCX", RC_GRAPHIC);
255     ftype->addKnownExtension(".pcx");
256     graphicClass.addFileType(ftype);
257     DD_AddFileType(*ftype);
258 
259     //
260     // Model fileTypes:
261     //
262     ResourceClass &modelClass = App_ResourceClass("RC_MODEL");
263 
264     ftype = new FileType("FT_DMD", RC_MODEL);
265     ftype->addKnownExtension(".dmd");
266     modelClass.addFileType(ftype);
267     DD_AddFileType(*ftype);
268 
269     ftype = new FileType("FT_MD2", RC_MODEL);
270     ftype->addKnownExtension(".md2");
271     modelClass.addFileType(ftype);
272     DD_AddFileType(*ftype);
273 
274     //
275     // Sound fileTypes:
276     //
277     ftype = new FileType("FT_WAV", RC_SOUND);
278     ftype->addKnownExtension(".wav");
279     App_ResourceClass("RC_SOUND").addFileType(ftype);
280     DD_AddFileType(*ftype);
281 
282     //
283     // Music fileTypes:
284     //
285     ResourceClass &musicClass = App_ResourceClass("RC_MUSIC");
286 
287     ftype = new FileType("FT_OGG", RC_MUSIC);
288     ftype->addKnownExtension(".ogg");
289     musicClass.addFileType(ftype);
290     DD_AddFileType(*ftype);
291 
292     ftype = new FileType("FT_MP3", RC_MUSIC);
293     ftype->addKnownExtension(".mp3");
294     musicClass.addFileType(ftype);
295     DD_AddFileType(*ftype);
296 
297     ftype = new FileType("FT_MOD", RC_MUSIC);
298     ftype->addKnownExtension(".mod");
299     musicClass.addFileType(ftype);
300     DD_AddFileType(*ftype);
301 
302     ftype = new FileType("FT_MID", RC_MUSIC);
303     ftype->addKnownExtension(".mid");
304     musicClass.addFileType(ftype);
305     DD_AddFileType(*ftype);
306 
307     //
308     // Font fileTypes:
309     //
310     ftype = new FileType("FT_DFN", RC_FONT);
311     ftype->addKnownExtension(".dfn");
312     App_ResourceClass("RC_FONT").addFileType(ftype);
313     DD_AddFileType(*ftype);
314 
315     //
316     // Misc fileTypes:
317     //
318     ftype = new FileType("FT_DEH", RC_PACKAGE);  ///< Treat DeHackEd patches as packages so they are mapped to $App.DataPath.
319     ftype->addKnownExtension(".deh");
320     DD_AddFileType(*ftype);
321     /// @todo ftype leaks. -jk
322 }
323 
324 #if 0
325 static void createPackagesScheme()
326 {
327     FS1::Scheme &scheme = App_FileSystem().createScheme("Packages");
328 
329     //
330     // Add default search paths.
331     //
332     // Note that the order here defines the order in which these paths are searched
333     // thus paths must be added in priority order (newer paths have priority).
334     //
335 
336 #ifdef UNIX
337     // There may be an iwaddir specified in a system-level config file.
338     if (char *fn = UnixInfo_GetConfigValue("paths", "iwaddir"))
339     {
340         NativePath path = de::App::commandLine().startupPath() / fn;
341         scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), SearchPath::NoDescend));
342         LOG_RES_NOTE("Using paths.iwaddir: %s") << path.pretty();
343         free(fn);
344     }
345 #endif
346 
347     // Add paths to games bought with/using Steam.
348     if (!CommandLine_Check("-nosteamapps"))
349     {
350         NativePath steamBase = DoomsdayApp::steamBasePath();
351         if (!steamBase.isEmpty())
352         {
353             NativePath steamPath = steamBase / "SteamApps/common/";
354             LOG_RES_NOTE("Using SteamApps path: %s") << steamPath.pretty();
355 
356             static String const appDirs[] =
357             {
358                 "doom 2/base",
359                 "final doom/base",
360                 "heretic shadow of the serpent riders/base",
361                 "hexen/base",
362                 "hexen deathkings of the dark citadel/base",
363                 "ultimate doom/base",
364                 "DOOM 3 BFG Edition/base/wads",
365                 ""
366             };
367             for (dint i = 0; !appDirs[i].isEmpty(); ++i)
368             {
369                 scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(steamPath / appDirs[i]),
370                                                 SearchPath::NoDescend));
371             }
372         }
373     }
374 
375 #ifdef UNIX
376     NativePath systemWads("/usr/share/games/doom");
377     if (systemWads.exists())
378     {
379         scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(systemWads),
380                                         SearchPath::NoDescend));
381     }
382 #endif
383 
384     // Add the path from the DOOMWADDIR environment variable.
385     if (!CommandLine_Check("-nodoomwaddir") && getenv("DOOMWADDIR"))
386     {
387         NativePath path = App::commandLine().startupPath() / getenv("DOOMWADDIR");
388         scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), SearchPath::NoDescend));
389         LOG_RES_NOTE("Using DOOMWADDIR: %s") << path.pretty();
390     }
391 
392     // Add any paths from the DOOMWADPATH environment variable.
393     if (!CommandLine_Check("-nodoomwadpath") && getenv("DOOMWADPATH"))
394     {
395 #if WIN32
396 #  define SEP_CHAR      ';'
397 #else
398 #  define SEP_CHAR      ':'
399 #endif
400 
401         QStringList allPaths = String(getenv("DOOMWADPATH")).split(SEP_CHAR, String::SkipEmptyParts);
402         for (dint i = allPaths.count(); i--> 0; )
403         {
404             NativePath path = App::commandLine().startupPath() / allPaths[i];
405             scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), SearchPath::NoDescend));
406             LOG_RES_NOTE("Using DOOMWADPATH: %s") << path.pretty();
407         }
408 
409 #undef SEP_CHAR
410     }
411 
412     scheme.addSearchPath(SearchPath(de::makeUri("$(App.DataPath)/"), SearchPath::NoDescend));
413     scheme.addSearchPath(SearchPath(de::makeUri("$(App.DataPath)/$(GamePlugin.Name)/"), SearchPath::NoDescend));
414 }
415 #endif
416 
DD_CreateFileSystemSchemes()417 void DD_CreateFileSystemSchemes()
418 {
419     dint const schemedef_max_searchpaths = 5;
420     struct schemedef_s {
421         char const *name;
422         char const *optOverridePath;
423         char const *optFallbackPath;
424         FS1::Scheme::Flags flags;
425         SearchPath::Flags searchPathFlags;
426         /// Priority is right to left.
427         char const *searchPaths[schemedef_max_searchpaths];
428     } defs[] = {
429         { "Defs",         nullptr,           nullptr,     FS1::Scheme::Flag(0), 0,
430             { "$(App.DefsPath)/", "$(App.DefsPath)/$(GamePlugin.Name)/", "$(App.DefsPath)/$(GamePlugin.Name)/$(Game.IdentityKey)/" }
431         },
432         { "Graphics",     "-gfxdir2",     "-gfxdir",      FS1::Scheme::Flag(0), 0,
433             { "$(App.DataPath)/graphics/" }
434         },
435         { "Models",       "-modeldir2",   "-modeldir",    FS1::Scheme::MappedInPackages, 0,
436             { "$(App.DataPath)/$(GamePlugin.Name)/models/", "$(App.DataPath)/$(GamePlugin.Name)/models/$(Game.IdentityKey)/" }
437         },
438         { "Sfx",          "-sfxdir2",     "-sfxdir",      FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
439             { "$(App.DataPath)/$(GamePlugin.Name)/sfx/", "$(App.DataPath)/$(GamePlugin.Name)/sfx/$(Game.IdentityKey)/" }
440         },
441         { "Music",        "-musdir2",     "-musdir",      FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
442             { "$(App.DataPath)/$(GamePlugin.Name)/music/", "$(App.DataPath)/$(GamePlugin.Name)/music/$(Game.IdentityKey)/" }
443         },
444         { "Textures",     "-texdir2",     "-texdir",      FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
445             { "$(App.DataPath)/$(GamePlugin.Name)/textures/", "$(App.DataPath)/$(GamePlugin.Name)/textures/$(Game.IdentityKey)/" }
446         },
447         { "Flats",        "-flatdir2",    "-flatdir",     FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
448             { "$(App.DataPath)/$(GamePlugin.Name)/flats/", "$(App.DataPath)/$(GamePlugin.Name)/flats/$(Game.IdentityKey)/" }
449         },
450         { "Patches",      "-patdir2",     "-patdir",      FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
451             { "$(App.DataPath)/$(GamePlugin.Name)/patches/", "$(App.DataPath)/$(GamePlugin.Name)/patches/$(Game.IdentityKey)/" }
452         },
453         { "LightMaps",    "-lmdir2",      "-lmdir",       FS1::Scheme::MappedInPackages, 0,
454             { "$(App.DataPath)/$(GamePlugin.Name)/lightmaps/" }
455         },
456         { "Fonts",        "-fontdir2",    "-fontdir",     FS1::Scheme::MappedInPackages, SearchPath::NoDescend,
457             { "$(App.DataPath)/fonts/", "$(App.DataPath)/$(GamePlugin.Name)/fonts/", "$(App.DataPath)/$(GamePlugin.Name)/fonts/$(Game.IdentityKey)/" }
458         }
459     };
460 
461     //createPackagesScheme();
462 
463     // Setup the rest...
464     for (schemedef_s const &def : defs)
465     {
466         FS1::Scheme &scheme = App_FileSystem().createScheme(def.name, def.flags);
467 
468         dint searchPathCount = 0;
469         while (def.searchPaths[searchPathCount] && ++searchPathCount < schemedef_max_searchpaths)
470         {}
471 
472         for (dint i = 0; i < searchPathCount; ++i)
473         {
474             scheme.addSearchPath(SearchPath(de::makeUri(def.searchPaths[i]), def.searchPathFlags));
475         }
476 
477         if (def.optOverridePath && CommandLine_CheckWith(def.optOverridePath, 1))
478         {
479             NativePath path = NativePath(CommandLine_NextAsPath());
480             scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), def.searchPathFlags), FS1::OverridePaths);
481             path = path / "$(Game.IdentityKey)";
482             scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), def.searchPathFlags), FS1::OverridePaths);
483         }
484 
485         if (def.optFallbackPath && CommandLine_CheckWith(def.optFallbackPath, 1))
486         {
487             NativePath path = NativePath(CommandLine_NextAsPath());
488             scheme.addSearchPath(SearchPath(de::Uri::fromNativeDirPath(path), def.searchPathFlags), FS1::FallbackPaths);
489         }
490     }
491 }
492 
App_Error(char const * error,...)493 void App_Error(char const *error, ...)
494 {
495     static bool errorInProgress = false;
496 
497     LogBuffer_Flush();
498 
499     char buff[2048], err[256];
500     va_list argptr;
501 
502 #ifdef __CLIENT__
503     ClientWindow::main().eventHandler().trapMouse(false);
504 #endif
505 
506     // Already in an error?
507     if (errorInProgress)
508     {
509 #ifdef __CLIENT__
510         DisplayMode_Shutdown();
511 #endif
512 
513         va_start(argptr, error);
514         dd_vsnprintf(buff, sizeof(buff), error, argptr);
515         va_end(argptr);
516 
517 #if defined (__CLIENT__) && defined (DENG_HAVE_BUSYRUNNER)
518         if (!ClientApp::busyRunner().inWorkerThread())
519         {
520             Sys_MessageBox(MBT_ERROR, DOOMSDAY_NICENAME, buff, 0);
521         }
522 #endif
523 
524         // Exit immediately, lest we go into an infinite loop.
525         exit(1);
526     }
527 
528     // We've experienced a fatal error; program will be shut down.
529     errorInProgress = true;
530 
531     // Get back to the directory we started from.
532     //Dir_SetCurrent(DD_RuntimePath());
533 
534     va_start(argptr, error);
535     dd_vsnprintf(err, sizeof(err), error, argptr);
536     va_end(argptr);
537 
538     LOG_CRITICAL("") << err;
539     LogBuffer_Flush();
540 
541     strcpy(buff, "");
542     strcat(buff, "\n");
543     strcat(buff, err);
544 
545     if (BusyMode_Active())
546     {
547         DoomsdayApp::app().busyMode().abort(buff);
548 
549 #if defined (__CLIENT__) && defined (DENG_HAVE_BUSYRUNNER)
550         if (ClientApp::busyRunner().inWorkerThread())
551         {
552             // We should not continue to execute the worker any more.
553             // The thread will be terminated imminently.
554             forever Thread_Sleep(10000);
555         }
556 #endif
557     }
558     else
559     {
560         App_AbnormalShutdown(buff);
561     }
562     exit(-1);
563 }
564 
App_AbnormalShutdown(char const * message)565 void App_AbnormalShutdown(char const *message)
566 {
567     DENG2_ASSERT_IN_MAIN_THREAD();
568 
569 #ifdef __CLIENT__
570     // This is a crash landing, better be safe than sorry.
571     DoomsdayApp::app().busyMode().setTaskRunner(nullptr);
572 #endif
573 
574     Sys_Shutdown();
575 
576 #ifdef __CLIENT__
577     DisplayMode_Shutdown();
578     DENG2_GUI_APP->loop().pause();
579 
580     // This is an abnormal shutdown, we cannot continue drawing any of the
581     // windows. (Alternatively could hide/disable drawing of the windows.) Note
582     // that the app's event loop is running normally while we show the native
583     // message box below -- if the app windows are not hidden/closed, they might
584     // receive draw events.
585     ClientApp::windowSystem().forAll([] (BaseWindow *win) {
586         win->hide();
587         return LoopContinue;
588     });
589 #endif
590 
591     if (message) // Only show if a message given.
592     {
593         // Make sure all the buffered stuff goes into the file.
594         LogBuffer_Flush();
595 
596         /// @todo Get the actual output filename (might be a custom one).
597         Sys_MessageBoxWithDetailsFromFile(MBT_ERROR, DOOMSDAY_NICENAME, message,
598                                           "See Details for complete message log contents.",
599                                           LogBuffer::get().outputFile().toUtf8());
600     }
601 
602     //Sys_Shutdown();
603     DD_Shutdown();
604 
605     Garbage_ForgetAndLeak(); // At this point, it's too late.
606 
607     // Get outta here.
608     exit(1);
609 }
610 
App_AudioSystem()611 AudioSystem &App_AudioSystem()
612 {
613     if (App::appExists())
614     {
615 #ifdef __CLIENT__
616         if (ClientApp::hasAudioSystem())
617         {
618             return ClientApp::audioSystem();
619         }
620 #endif
621 #ifdef __SERVER__
622         return ServerApp::audioSystem();
623 #endif
624     }
625     throw Error("App_AudioSystem", "App not yet initialized");
626 }
627 
628 #ifdef __CLIENT__
App_Resources()629 ClientResources &App_Resources()
630 {
631     return ClientResources::get();
632 }
633 #else
App_Resources()634 Resources &App_Resources()
635 {
636     return Resources::get();
637 }
638 #endif
639 
App_World()640 ClientServerWorld &App_World()
641 {
642     return static_cast<ClientServerWorld &>(World::get());
643 }
644 
App_InFineSystem()645 InFineSystem &App_InFineSystem()
646 {
647     if (App::appExists())
648     {
649 #ifdef __CLIENT__
650         return ClientApp::infineSystem();
651 #endif
652 #ifdef __SERVER__
653         return ServerApp::infineSystem();
654 #endif
655     }
656     throw Error("App_InFineSystem", "App not yet initialized");
657 }
658 
659 #undef Con_Open
Con_Open(dint yes)660 void Con_Open(dint yes)
661 {
662 #ifdef __CLIENT__
663     if (yes)
664     {
665         ClientWindow &win = ClientWindow::main();
666         win.taskBar().open();
667         win.root().setFocus(&win.console().commandLine());
668     }
669     else
670     {
671         ClientWindow::main().console().closeLog();
672     }
673 #endif
674 
675 #ifdef __SERVER__
676     DENG2_UNUSED(yes);
677 #endif
678 }
679 
680 #ifdef __CLIENT__
681 
682 /**
683  * Console command to open/close the console prompt.
684  */
D_CMD(OpenClose)685 D_CMD(OpenClose)
686 {
687     DENG2_UNUSED2(src, argc);
688 
689     if (!stricmp(argv[0], "conopen"))
690     {
691         Con_Open(true);
692     }
693     else if (!stricmp(argv[0], "conclose"))
694     {
695         Con_Open(false);
696     }
697     else
698     {
699         Con_Open(!ClientWindow::main().console().isLogOpen());
700     }
701     return true;
702 }
703 
D_CMD(TaskBar)704 D_CMD(TaskBar)
705 {
706     DENG2_UNUSED3(src, argc, argv);
707 
708     ClientWindow &win = ClientWindow::main();
709     if (!win.taskBar().isOpen() || !win.console().commandLine().hasFocus())
710     {
711         win.taskBar().open();
712         win.console().focusOnCommandLine();
713     }
714     else
715     {
716         win.taskBar().close();
717     }
718     return true;
719 }
720 
D_CMD(PackagesSidebar)721 D_CMD(PackagesSidebar)
722 {
723     DENG2_UNUSED3(src, argc, argv);
724 
725     if (!DoomsdayApp::isGameLoaded()) return false;
726 
727     ClientWindow &win = ClientWindow::main();
728     if (!win.hasSidebar())
729     {
730         win.taskBar().openPackagesSidebar();
731     }
732     else
733     {
734         win.sidebar().as<SidebarWidget>().close();
735     }
736     return true;
737 }
738 
D_CMD(Tutorial)739 D_CMD(Tutorial)
740 {
741     DENG2_UNUSED3(src, argc, argv);
742     ClientWindow::main().taskBar().showTutorial();
743     return true;
744 }
745 
746 #endif // __CLIENT__
747 
DD_ActivateGameWorker(void * context)748 int DD_ActivateGameWorker(void *context)
749 {
750     DoomsdayApp::GameChangeParameters &parms = *(DoomsdayApp::GameChangeParameters *) context;
751 
752     auto &plugins = DoomsdayApp::plugins();
753     auto &resSys = App_Resources();
754 
755     // Some resources types are located prior to initializing the game.
756     auto &textures = res::Textures::get();
757     textures.initTextures();
758     textures.textureScheme("Lightmaps").clear();
759     textures.textureScheme("Flaremaps").clear();
760     resSys.mapManifests().initMapManifests();
761 
762     if (parms.initiatedBusyMode)
763     {
764         Con_SetProgress(50);
765     }
766 
767     // Now that resources have been located we can begin to initialize the game.
768     if (App_GameLoaded())
769     {
770         // Any game initialization hooks?
771         plugins.callAllHooks(HOOK_GAME_INIT);
772 
773         if (gx.PreInit)
774         {
775             DENG2_ASSERT(App_CurrentGame().pluginId() != 0);
776 
777             plugins.setActivePluginId(App_CurrentGame().pluginId());
778             gx.PreInit(App_CurrentGame().id().toUtf8());
779             plugins.setActivePluginId(0);
780         }
781     }
782 
783     if (parms.initiatedBusyMode)
784     {
785         Con_SetProgress(100);
786     }
787 
788     if (App_GameLoaded())
789     {
790         File const *configFile;
791 
792         // Parse the game's main config file.
793         // If a custom top-level config is specified; let it override.
794         if (CommandLine_CheckWith("-config", 1))
795         {
796             Con_ParseCommands(NativePath(CommandLine_NextAsPath()));
797         }
798         else
799         {
800             configFile = FS::tryLocate<const File>(App_CurrentGame().mainConfig());
801             Con_SetDefaultPath(App_CurrentGame().mainConfig());
802 
803             // This will be missing on the first launch.
804             if (configFile)
805             {
806                 LOG_SCR_NOTE("Parsing primary config %s...") << configFile->description();
807                 Con_ParseCommands(*configFile);
808             }
809         }
810         Con_SetAllowed(CPCF_ALLOW_SAVE_STATE);
811 
812 #ifdef __CLIENT__
813         // Apply default control bindings for this game.
814         ClientApp::inputSystem().bindGameDefaults();
815 
816         // Read bindings for this game and merge with the working set.
817         if ((configFile = FS::tryLocate<const File>(App_CurrentGame().bindingConfig()))
818                 != nullptr)
819         {
820             Con_ParseCommands(*configFile);
821         }
822         Con_SetAllowed(CPCF_ALLOW_SAVE_BINDINGS);
823 #endif
824     }
825 
826     if (parms.initiatedBusyMode)
827     {
828         Con_SetProgress(120);
829     }
830 
831     Def_Read();
832 
833     if (parms.initiatedBusyMode)
834     {
835         Con_SetProgress(130);
836     }
837 
838     resSys.sprites().initSprites(); // Fully initialize sprites.
839 #ifdef __CLIENT__
840     resSys.initModels();
841 #endif
842 
843     Def_PostInit();
844 
845     DD_ReadGameHelp();
846 
847     // Reset the tictimer so than any fractional accumulation is not added to
848     // the tic/game timer of the newly-loaded game.
849     gameTime = 0;
850     DD_ResetTimer();
851 
852 #ifdef __CLIENT__
853     // Make sure that the next frame does not use a filtered viewer.
854     R_ResetViewer();
855 #endif
856 
857     // Init player values.
858     DoomsdayApp::players().forAll([] (Player &plr)
859     {
860         plr.extraLight        = 0;
861         plr.targetExtraLight  = 0;
862         plr.extraLightCounter = 0;
863         return LoopContinue;
864     });
865 
866     if (gx.PostInit)
867     {
868         plugins.setActivePluginId(App_CurrentGame().pluginId());
869         gx.PostInit();
870         plugins.setActivePluginId(0);
871     }
872 
873     if (parms.initiatedBusyMode)
874     {
875         Con_SetProgress(200);
876     }
877 
878     return 0;
879 }
880 
App_Games()881 Games &App_Games()
882 {
883     if (App::appExists())
884     {
885 #ifdef __CLIENT__
886         return ClientApp::games();
887 #endif
888 #ifdef __SERVER__
889         return ServerApp::games();
890 #endif
891     }
892     throw Error("App_Games", "App not yet initialized");
893 }
894 
App_ClearGames()895 void App_ClearGames()
896 {
897     App_Games().clear();
898     DoomsdayApp::setGame(App_Games().nullGame());
899 }
900 
populateGameInfo(GameInfo & info,Game const & game)901 static void populateGameInfo(GameInfo &info, Game const &game)
902 {
903     info.identityKey = AutoStr_FromTextStd(game.id().toUtf8().constData());
904     info.title       = AutoStr_FromTextStd(game.title().toUtf8().constData());
905     info.author      = AutoStr_FromTextStd(game.author().toUtf8().constData());
906 }
907 
908 /// @note Part of the Doomsday public API.
909 #undef DD_GameInfo
DD_GameInfo(GameInfo * info)910 dd_bool DD_GameInfo(GameInfo *info)
911 {
912     LOG_AS("DD_GameInfo");
913     if (!info) return false;
914 
915     zapPtr(info);
916 
917     if (App_GameLoaded())
918     {
919         populateGameInfo(*info, App_CurrentGame());
920         return true;
921     }
922 
923     LOGDEV_WARNING("No game currently loaded");
924     return false;
925 }
926 
App_CurrentGame()927 Game const &App_CurrentGame()
928 {
929     return DoomsdayApp::game();
930 }
931 
932 static GameProfile automaticProfile;
933 
autoselectGameProfile()934 static GameProfile const *autoselectGameProfile()
935 {
936     if (auto arg = CommandLine::get().check("-game", 1))
937     {
938         String const param = arg.params.first();
939         Games &games = DoomsdayApp::games();
940 
941         // The argument can be a game ID or a profile name.
942         if (games.contains(param))
943         {
944             auto &prof = DoomsdayApp::gameProfiles().find(games[param].title()).as<GameProfile>();
945             prof.setLastPlayedAt();
946             automaticProfile = prof;
947         }
948         else if (auto *prof = maybeAs<GameProfile>(DoomsdayApp::gameProfiles().tryFind(param)))
949         {
950             prof->setLastPlayedAt();
951             automaticProfile = *prof;
952         }
953 
954         // Packages from the command line.
955         foreach (String packageId, PackageLoader::get().loadedFromCommandLine())
956         {
957             StringList pkgs = automaticProfile.packages();
958             pkgs << packageId;
959             automaticProfile.setPackages(pkgs);
960         }
961 
962         // Also append the packages specified as files on the command line.
963         foreach (File *f, DoomsdayApp::app().filesFromCommandLine())
964         {
965             String packageId;
966             if (auto const *bundle = maybeAs<DataBundle>(f))
967             {
968                 packageId = bundle->packageId();
969             }
970             else if (f->extension() == ".pack")
971             {
972                 packageId = Package::identifierForFile(*f);
973             }
974             else
975             {
976                 LOG_RES_WARNING("Unknown file %s will not be loaded")
977                         << f->description();
978             }
979 
980             if (!packageId.isEmpty())
981             {
982                 StringList pkgs = automaticProfile.packages();
983                 pkgs << packageId;
984                 automaticProfile.setPackages(pkgs);
985             }
986         }
987 
988         if (automaticProfile.isPlayable())
989         {
990             return &automaticProfile;
991         }
992     }
993 
994     // We don't know what to do.
995     return nullptr;
996 }
997 
DD_EarlyInit()998 dint DD_EarlyInit()
999 {
1000     // Determine the requested degree of verbosity.
1001     ::verbose = CommandLine_Exists("-verbose");
1002 
1003 #ifdef __SERVER__
1004     ::isDedicated = true;
1005 #else
1006     ::isDedicated = false;
1007 #endif
1008 
1009     // Bring the console online as soon as we can.
1010     DD_ConsoleInit();
1011     Con_InitDatabases();
1012 
1013     // Register the engine's console commands and variables.
1014     DD_ConsoleRegister();
1015 
1016     return true;
1017 }
1018 
1019 // Perform basic runtime type size checks.
1020 #ifdef DENG2_DEBUG
assertTypeSizes()1021 static void assertTypeSizes()
1022 {
1023     void *ptr = 0;
1024     int32_t int32 = 0;
1025     int16_t int16 = 0;
1026     dfloat float32 = 0;
1027 
1028     DENG2_UNUSED(ptr);
1029     DENG2_UNUSED(int32);
1030     DENG2_UNUSED(int16);
1031     DENG2_UNUSED(float32);
1032 
1033     ASSERT_32BIT(int32);
1034     ASSERT_16BIT(int16);
1035     ASSERT_32BIT(float32);
1036 #ifdef __64BIT__
1037     ASSERT_64BIT(ptr);
1038     ASSERT_64BIT(int64_t);
1039 #else
1040     ASSERT_NOT_64BIT(ptr);
1041 #endif
1042 }
1043 #endif
1044 
1045 /**
1046  * Engine initialization. Once completed the game loop is ready to be started.
1047  * Called from the app entrypoint function.
1048  */
initializeWithWindowReady()1049 static void initializeWithWindowReady()
1050 {
1051     DENG2_DEBUG_ONLY( assertTypeSizes(); )
1052 
1053     static char const *AUTOEXEC_NAME = "autoexec.cfg";
1054 
1055 #ifdef __CLIENT__
1056     GL_EarlyInit();
1057 #endif
1058 
1059     // Initialize the subsystems needed prior to entering busy mode for the first time.
1060     Sys_Init();
1061     ResourceClass::setResourceClassCallback(App_ResourceClass);
1062     registerResourceFileTypes();
1063     F_Init();
1064     DD_CreateFileSystemSchemes();
1065 
1066 #ifdef __CLIENT__
1067     FR_Init();
1068 
1069     // Enter busy mode until startup complete.
1070     //Con_InitProgress(200);
1071 #endif
1072 
1073     BusyMode_RunNewTaskWithName(BUSYF_NO_UPLOADS | BUSYF_STARTUP | (verbose? BUSYF_CONSOLE_OUTPUT : 0),
1074                                 DD_StartupWorker, 0, "Starting up...");
1075 
1076     // Engine initialization is complete. Now finish up with the GL.
1077 #ifdef __CLIENT__
1078     GL_Init();
1079     GL_InitRefresh();
1080     App_Resources().clearAllTextureSpecs();
1081     LensFx_Init();
1082     R_InitViewWindow();
1083     UI_LoadFonts();
1084 #endif
1085     App_Resources().initSystemTextures();
1086 
1087 //#ifdef __CLIENT__
1088 //    // Do deferred uploads.
1089 //    Con_SetProgress(100);
1090 //#endif
1091     BusyMode_RunNewTaskWithName(BUSYF_STARTUP | (verbose? BUSYF_CONSOLE_OUTPUT : 0),
1092                                 DD_DummyWorker, 0, "Buffering...");
1093 
1094     //
1095     // Try to locate all required data files for all registered games.
1096     //
1097 //#ifdef __CLIENT__
1098 //    Con_SetProgress(200);
1099 //#endif
1100 
1101     App_Games().checkReadiness();
1102 
1103     // Attempt automatic game selection.
1104     if (!CommandLine_Exists("-noautoselect") || isDedicated)
1105     {
1106         if (GameProfile const *game = autoselectGameProfile())
1107         {
1108 #ifdef __CLIENT__
1109             ClientWindow::main().home().moveOffscreen(0.0);
1110 #endif
1111             // Begin the game session.
1112             DoomsdayApp::app().changeGame(*game, DD_ActivateGameWorker);
1113         }
1114 #ifdef __SERVER__
1115         else
1116         {
1117             // A server is presently useless without a game, as shell
1118             // connections can only be made after a game is loaded and the
1119             // server mode started.
1120             /// @todo Allow shell connections in Home, too.
1121             String msg = "Could not determine which game to start. "
1122                          "Please specify one with the " _E(b) "-game" _E(.) " option. ";
1123             auto const playable = DoomsdayApp::gameProfiles().allPlayableProfiles();
1124             if (playable.isEmpty())
1125             {
1126                 msg += "However, it seems all games are missing required data files. "
1127                        "Check that the " _E(b) "-iwad" _E(.) " option specifies a "
1128                        "folder with game WAD files.";
1129             }
1130             else
1131             {
1132                 StringList ids;
1133                 foreach (GameProfile const *prof, playable) ids << prof->gameId();
1134                 msg += "The following games are playable: " + String::join(ids, ", ");
1135             }
1136             App_Error(msg.toLatin1());
1137         }
1138 #endif
1139     }
1140 
1141     FS_InitPathLumpMappings();
1142 
1143     // Re-initialize the filesystem subspace schemes as there are now new
1144     // resources to be found on existing search paths (probably that is).
1145     App_FileSystem().resetAllSchemes();
1146 
1147     //
1148     // One-time execution of various command line features available during startup.
1149     //
1150     if (CommandLine_CheckWith("-dumplump", 1))
1151     {
1152         String name = CommandLine_Next();
1153         lumpnum_t lumpNum = App_FileSystem().lumpNumForName(name);
1154         if (lumpNum >= 0)
1155         {
1156             F_DumpFile(App_FileSystem().lump(lumpNum), 0);
1157         }
1158         else
1159         {
1160             LOG_RES_WARNING("Cannot dump unknown lump \"%s\"") << name;
1161         }
1162     }
1163 
1164     if (CommandLine_Check("-dumpwaddir"))
1165     {
1166         Con_Executef(CMDS_CMDLINE, false, "listlumps");
1167     }
1168 
1169     // Try to load the autoexec file. This is done here to make sure everything is
1170     // initialized: the user can do here anything that s/he'd be able to do in-game
1171     // provided a game was loaded during startup.
1172     Con_ParseCommands(App::app().nativeHomePath() / AUTOEXEC_NAME);
1173 
1174     // Read additional config files that should be processed post engine init.
1175     if (CommandLine_CheckWith("-parse", 1))
1176     {
1177         LOG_AS("-parse");
1178         Time begunAt;
1179         forever
1180         {
1181             char const *arg = CommandLine_NextAsPath();
1182             if (!arg || arg[0] == '-') break;
1183 
1184             LOG_NOTE("Additional pre-init config file \"%s\"") << NativePath(arg).pretty();
1185             Con_ParseCommands(NativePath(arg));
1186         }
1187         LOGDEV_SCR_VERBOSE("Completed in %.2f seconds") << begunAt.since();
1188     }
1189 
1190     // A console command on the command line?
1191     for (dint p = 1; p < CommandLine_Count() - 1; p++)
1192     {
1193         if (stricmp(CommandLine_At(p), "-command") &&
1194             stricmp(CommandLine_At(p), "-cmd"))
1195         {
1196             continue;
1197         }
1198 
1199         for (++p; p < CommandLine_Count(); p++)
1200         {
1201             char const *arg = CommandLine_At(p);
1202 
1203             if (arg[0] == '-')
1204             {
1205                 p--;
1206                 break;
1207             }
1208             Con_Execute(CMDS_CMDLINE, arg, false, false);
1209         }
1210     }
1211 
1212     //
1213     // One-time execution of network commands on the command line.
1214     // Commands are only executed if we have loaded a game during startup.
1215     //
1216     if (App_GameLoaded())
1217     {
1218         // Client connection command.
1219         if (CommandLine_CheckWith("-connect", 1))
1220         {
1221             Con_Executef(CMDS_CMDLINE, false, "connect %s", CommandLine_Next());
1222         }
1223 
1224         // Incoming TCP port.
1225         if (CommandLine_CheckWith("-port", 1))
1226         {
1227             Con_Executef(CMDS_CMDLINE, false, "net-ip-port %s", CommandLine_Next());
1228         }
1229 
1230 #ifdef __SERVER__
1231         // Automatically start the server.
1232         N_ServerOpen();
1233 #endif
1234     }
1235     else
1236     {
1237         // No game loaded.
1238         // Lets get most of everything else initialized.
1239         // Reset file IDs so previously seen files can be processed again.
1240         App_FileSystem().resetFileIds();
1241         FS_InitPathLumpMappings();
1242         FS_InitVirtualPathMappings();
1243         App_FileSystem().resetAllSchemes();
1244 
1245         auto &textures = res::Textures::get();
1246         textures.initTextures();
1247         textures.textureScheme("Lightmaps").clear();
1248         textures.textureScheme("Flaremaps").clear();
1249         App_Resources().mapManifests().initMapManifests();
1250 
1251         Def_Read();
1252 
1253         App_Resources().sprites().initSprites();
1254 #ifdef __CLIENT__
1255         App_Resources().initModels();
1256 #endif
1257 
1258         Def_PostInit();
1259 
1260         if (!CommandLine_Exists("-noautoselect"))
1261         {
1262             LOG_NOTE("Game could not be selected automatically");
1263         }
1264     }
1265 }
1266 
1267 /**
1268  * This gets called when the main window is ready for GL init. The application
1269  * event loop is already running.
1270  */
DD_FinishInitializationAfterWindowReady()1271 void DD_FinishInitializationAfterWindowReady()
1272 {
1273     LOGDEV_MSG("Window is ready, finishing initialization");
1274 
1275 #ifdef __CLIENT__
1276 # ifdef WIN32
1277     // Now we can get the color transfer table as the window is available.
1278     DisplayMode_SaveOriginalColorTransfer();
1279 # endif
1280     if (!Sys_GLInitialize())
1281     {
1282         App_Error("Error initializing OpenGL.\n");
1283     }
1284     else
1285     {
1286         ClientWindow::main().setTitle(DD_ComposeMainWindowTitle());
1287     }
1288 #endif // __CLIENT__
1289 
1290     // Initialize engine subsystems and initial state.
1291     try
1292     {
1293         initializeWithWindowReady();
1294         // Let everyone know we're up and running (via another timer callback).
1295         Loop::get().timer(0.1, [] () { App::app().notifyStartupComplete(); });
1296         return;
1297     }
1298     catch (Error const &er)
1299     {
1300         EscapeParser esc;
1301         esc.parse(er.asText());
1302         Sys_CriticalMessage(esc.plainText().toUtf8().constData());
1303     }
1304     catch (...)
1305     {}
1306     exit(2);  // Cannot continue...
1307 }
1308 
DD_StartupWorker(void *)1309 static dint DD_StartupWorker(void * /*context*/)
1310 {
1311 #ifdef WIN32
1312     // Initialize COM for this thread (needed for DirectInput).
1313     CoInitialize(nullptr);
1314 #endif
1315     //Con_SetProgress(10);
1316 
1317     // Any startup hooks?
1318     DoomsdayApp::plugins().callAllHooks(HOOK_STARTUP);
1319     //Con_SetProgress(20);
1320 
1321     // Was the change to userdir OK?
1322     /*if (CommandLine_CheckWith("-userdir", 1) && !DoomsdayApp::app().isUsingUserDir())
1323     {
1324         LOG_WARNING("User directory not found (check -userdir)");
1325     }*/
1326 
1327     FS_InitVirtualPathMappings();
1328     App_FileSystem().resetAllSchemes();
1329 
1330     //Con_SetProgress(40);
1331 
1332     Net_Init();
1333     Sys_HideMouseCursor();
1334 
1335     // Read config files that should be read BEFORE engine init.
1336     if (CommandLine_CheckWith("-cparse", 1))
1337     {
1338         Time begunAt;
1339         LOG_AS("-cparse")
1340 
1341         forever
1342         {
1343             char const *arg = CommandLine_NextAsPath();
1344             if (!arg || arg[0] == '-') break;
1345 
1346             LOG_MSG("Additional (pre-init) config file \"%s\"") << NativePath(arg).pretty();
1347             Con_ParseCommands(arg);
1348         }
1349         LOGDEV_SCR_VERBOSE("Completed in %.2f seconds") << begunAt.since();
1350     }
1351 
1352     //
1353     // Add required engine resource files.
1354     //
1355 
1356     // Make sure all files have been found so we can determine which games are playable.
1357     /*Folder::waitForPopulation(
1358             #if defined (__SERVER__)
1359                 Folder::BlockingMainThread
1360             #endif
1361                 );
1362     DoomsdayApp::bundles().waitForEverythingIdentified();*/
1363     FS::waitForIdle();
1364 
1365     /*String foundPath = App_FileSystem().findPath(de::Uri("doomsday.pk3", RC_PACKAGE),
1366                                                  RLF_DEFAULT, App_ResourceClass(RC_PACKAGE));
1367     foundPath = App_BasePath() / foundPath;  // Ensure the path is absolute.
1368     File1 *loadedFile = File1::tryLoad(de::makeUri(foundPath));
1369     DENG2_ASSERT(loadedFile);
1370     DENG2_UNUSED(loadedFile);*/
1371 
1372     // It is assumed that doomsday.pk3 is currently stored in a native file.
1373     if (File const *basePack = App::packageLoader().select("net.dengine.legacy.base"))
1374     {
1375         // The returned file is a symlink to the actual data file.
1376         // Since we're loading with FS1, we need to look up the native path.
1377         // The data file is an interpreter in /local/wads, whose source is the native file.
1378         File1::tryLoad(File1::LoadAsVanillaFile,
1379                        res::DoomsdayPackage::loadableUri(*basePack));
1380     }
1381     else
1382     {
1383         throw Error("DD_StartupWorker", "Failed to find \"net.dengine.legacy.base\" package");
1384     }
1385 
1386     // No more files or packages will be loaded in "startup mode" after this point.
1387     App_FileSystem().endStartup();
1388 
1389     // Load engine help resources.
1390     DD_InitHelp();
1391     //Con_SetProgress(60);
1392 
1393     // Execute the startup script (Startup.cfg).
1394     char const *startupConfig = "startup.cfg";
1395     if (F_FileExists(startupConfig))
1396     {
1397         Con_ParseCommands(startupConfig);
1398     }
1399     //Con_SetProgress(90);
1400 
1401 #ifdef __CLIENT__
1402     R_BuildTexGammaLut(texGamma);
1403     R_InitSvgs();
1404     R_ResetFrameCount();
1405 #endif
1406     //Con_SetProgress(165);
1407 
1408     Net_InitGame();
1409 #ifdef __CLIENT__
1410     Demo_Init();
1411 #endif
1412     //Con_SetProgress(190);
1413 
1414     // In dedicated mode the console must be opened, so all input events
1415     // will be handled by it.
1416     if (isDedicated)
1417     {
1418         Con_Open(true);
1419     }
1420     //Con_SetProgress(199);
1421 
1422     // Any initialization hooks?
1423     DoomsdayApp::plugins().callAllHooks(HOOK_INIT);
1424     //Con_SetProgress(200);
1425 
1426     // Release all cached uncompressed entries. If the contents of the compressed
1427     // files are needed, they will be decompressed and cached again.
1428     DoomsdayApp::app().uncacheFilesFromMemory();
1429 
1430 #ifdef WIN32
1431     // This thread has finished using COM.
1432     CoUninitialize();
1433 #endif
1434 
1435     return 0;
1436 }
1437 
1438 #if 1
1439 /**
1440  * This only exists so we have something to call while the deferred uploads of the
1441  * startup are processed.
1442  */
DD_DummyWorker(void *)1443 static dint DD_DummyWorker(void * /*context*/)
1444 {
1445     Con_SetProgress(200);
1446     return 0;
1447 }
1448 #endif
1449 
DD_CheckTimeDemo()1450 void DD_CheckTimeDemo()
1451 {
1452     static bool checked = false;
1453 
1454     if (!checked)
1455     {
1456         checked = true;
1457         if (CommandLine_CheckWith("-timedemo", 1) || // Timedemo mode.
1458             CommandLine_CheckWith("-playdemo", 1))   // Play-once mode.
1459         {
1460             Block cmd = String("playdemo %1").arg(CommandLine_Next()).toUtf8();
1461             Con_Execute(CMDS_CMDLINE, cmd.constData(), false, false);
1462         }
1463     }
1464 }
1465 
DD_UpdateEngineStateWorker(void * context)1466 static dint DD_UpdateEngineStateWorker(void *context)
1467 {
1468     DENG2_ASSERT(context);
1469     auto const initiatedBusyMode = *static_cast<bool *>(context);
1470 
1471 #ifdef __CLIENT__
1472     if (!novideo)
1473     {
1474         GL_InitRefresh();
1475         App_Resources().clearAllTextureSpecs();
1476     }
1477 #endif
1478     App_Resources().initSystemTextures();
1479 
1480     if (initiatedBusyMode)
1481     {
1482         Con_SetProgress(50);
1483     }
1484 
1485     // Allow previously seen files to be processed again.
1486     App_FileSystem().resetFileIds();
1487 
1488     // Re-read definitions.
1489     Def_Read();
1490 
1491     //
1492     // Rebuild resource data models (defs might've changed).
1493     //
1494     App_Resources().sprites().initSprites();
1495 #ifdef __CLIENT__
1496     App_Resources().clearAllRawTextures();
1497     App_Resources().initModels();
1498 #endif
1499     Def_PostInit();
1500 
1501     //
1502     // Update misc subsystems.
1503     //
1504     App_World().update();
1505 
1506 #ifdef __CLIENT__
1507     // Recalculate the light range mod matrix.
1508     Rend_UpdateLightModMatrix();
1509     // The rendering lists have persistent data that has changed during the
1510     // re-initialization.
1511     ClientApp::renderSystem().clearDrawLists();
1512 #endif
1513 
1514     /// @todo fixme: Update the game title and the status.
1515 
1516 #ifdef DENG2_DEBUG
1517     Z_CheckHeap();
1518 #endif
1519 
1520     if (initiatedBusyMode)
1521     {
1522         Con_SetProgress(200);
1523     }
1524     return 0;
1525 }
1526 
DD_UpdateEngineState()1527 void DD_UpdateEngineState()
1528 {
1529     LOG_MSG("Updating engine state...");
1530 
1531 #ifdef __CLIENT__
1532     // Stop playing sounds and music.
1533     App_AudioSystem().reset();
1534 
1535     BusyMode_FreezeGameForBusyMode();
1536     GL_SetFilter(false);
1537     Demo_StopPlayback();
1538     Rend_ResetLookups();
1539 #endif
1540 
1541     //App_FileSystem().resetFileIds();
1542 
1543     // Update the dir/WAD translations.
1544     FS_InitPathLumpMappings();
1545     FS_InitVirtualPathMappings();
1546     // Re-build the filesystem subspace schemes as there may be new resources to be found.
1547     App_FileSystem().resetAllSchemes();
1548 
1549     res::Textures::get().initTextures();
1550     Resources::get().mapManifests().initMapManifests();
1551 
1552     if (App_GameLoaded() && gx.UpdateState)
1553     {
1554         gx.UpdateState(DD_PRE);
1555     }
1556 
1557 #ifdef __CLIENT__
1558     dd_bool hadFog = fogParams.usingFog;
1559 
1560     GL_TotalReset();
1561     GL_TotalRestore(); // Bring GL back online.
1562 
1563     // Make sure the fog is enabled, if necessary.
1564     if (hadFog)
1565     {
1566         GL_UseFog(true);
1567     }
1568 #endif
1569 
1570     // The bulk of this we can do in busy mode unless we are already busy
1571     // (which can happen during a runtime game change).
1572     bool initiatedBusyMode = !BusyMode_Active();
1573     if (initiatedBusyMode)
1574     {
1575 #ifdef __CLIENT__
1576         Con_InitProgress(200);
1577 #endif
1578         BusyMode_RunNewTaskWithName(BUSYF_ACTIVITY | BUSYF_PROGRESS_BAR | (verbose? BUSYF_CONSOLE_OUTPUT : 0),
1579                                     DD_UpdateEngineStateWorker, &initiatedBusyMode,
1580                                     "Updating engine state...");
1581     }
1582     else
1583     {
1584         /// @todo Update the current task name and push progress.
1585         DD_UpdateEngineStateWorker(&initiatedBusyMode);
1586     }
1587 
1588     if (App_GameLoaded() && gx.UpdateState)
1589     {
1590         gx.UpdateState(DD_POST);
1591     }
1592 
1593 #ifdef __CLIENT__
1594     world::Materials::get().forAllMaterials([] (world::Material &material)
1595     {
1596         return static_cast<ClientMaterial &>(material).forAllAnimators([] (MaterialAnimator &animator)
1597         {
1598             animator.rewind();
1599             return LoopContinue;
1600         });
1601     });
1602 #endif
1603 }
1604 
1605 struct ddvalue_t
1606 {
1607     dint *readPtr;
1608     dint *writePtr;
1609 };
1610 
1611 static ddvalue_t ddValues[DD_LAST_VALUE - DD_FIRST_VALUE] = {
1612     {&novideo, 0},
1613     {&netGame, 0},
1614     {&isServer, 0}, // An *open* server?
1615     {&isClient, 0},
1616     {&consolePlayer, &consolePlayer},
1617     {&displayPlayer, 0}, // use R_SetViewPortPlayer() to change
1618     {&gotFrame, 0},
1619     {0, 0}, // pointer updated when queried (DED sound def count)
1620 
1621 #ifdef __SERVER__
1622     {&allowFrames, &allowFrames},
1623 #else
1624     {0, 0},
1625 #endif
1626 
1627 #ifdef __CLIENT__
1628     {&levelFullBright, &levelFullBright},
1629     {&gameReady, &gameReady},
1630     {&playback, 0},
1631     {&clientPaused, &clientPaused},
1632     {&weaponOffsetScaleY, &weaponOffsetScaleY},
1633     {&gameDrawHUD, 0},
1634     {&symbolicEchoMode, &symbolicEchoMode},
1635     {&rendLightAttenuateFixedColormap, &rendLightAttenuateFixedColormap},
1636 #else
1637     {0, 0},
1638     {0, 0},
1639     {0, 0},
1640     {0, 0},
1641     {0, 0},
1642     {0, 0},
1643     {0, 0},
1644     {0, 0},
1645 #endif
1646 };
1647 
1648 #undef DD_GetInteger
1649 /**
1650  * Get a 32-bit signed integer value.
1651  */
DD_GetInteger(dint ddvalue)1652 dint DD_GetInteger(dint ddvalue)
1653 {
1654     switch (ddvalue)
1655     {
1656 #ifdef __CLIENT__
1657     case DD_SHIFT_DOWN:
1658         return dint( ClientApp::inputSystem().shiftDown() );
1659 
1660     case DD_WINDOW_WIDTH:
1661         return DENG_GAMEVIEW_WIDTH;
1662 
1663     case DD_WINDOW_HEIGHT:
1664         return DENG_GAMEVIEW_HEIGHT;
1665 
1666     case DD_CURRENT_CLIENT_FINALE_ID:
1667         return Cl_CurrentFinale();
1668 
1669     case DD_DYNLIGHT_TEXTURE:
1670         return dint( GL_PrepareLSTexture(LST_DYNAMIC) );
1671 
1672     case DD_USING_HEAD_TRACKING:
1673         return vrCfg().mode() == VRConfig::OculusRift && vrCfg().oculusRift().isReady();
1674 #endif
1675 
1676     case DD_NUMMOBJTYPES:
1677         return DED_Definitions()->things.size();
1678 
1679     case DD_MAP_MUSIC:
1680         if (App_World().hasMap())
1681         {
1682             Record const &mapInfo = App_World().map().mapInfo();
1683             return DED_Definitions()->getMusicNum(mapInfo.gets("music").toUtf8().constData());
1684         }
1685         return -1;
1686 
1687     default: break;
1688     }
1689 
1690     if (ddvalue >= DD_LAST_VALUE || ddvalue < DD_FIRST_VALUE)
1691     {
1692         return 0;
1693     }
1694 
1695     // Update pointers.
1696     if (ddvalue == DD_NUMSOUNDS)
1697     {
1698         ddValues[ddvalue].readPtr = &DED_Definitions()->sounds.count.num;
1699     }
1700 
1701     if (ddValues[ddvalue].readPtr == 0)
1702     {
1703         return 0;
1704     }
1705     return *ddValues[ddvalue].readPtr;
1706 }
1707 
1708 #undef DD_SetInteger
1709 /**
1710  * Set a 32-bit signed integer value.
1711  */
DD_SetInteger(dint ddvalue,dint parm)1712 void DD_SetInteger(dint ddvalue, dint parm)
1713 {
1714     if (ddvalue < DD_FIRST_VALUE || ddvalue >= DD_LAST_VALUE)
1715     {
1716         return;
1717     }
1718 
1719     if (ddValues[ddvalue].writePtr)
1720     {
1721         *ddValues[ddvalue].writePtr = parm;
1722     }
1723 }
1724 
1725 #undef DD_GetVariable
1726 /**
1727  * Get a pointer to the value of a variable. Not all variables support
1728  * this. Added for 64-bit support.
1729  */
DD_GetVariable(dint ddvalue)1730 void *DD_GetVariable(dint ddvalue)
1731 {
1732     static dint value;
1733     static ddouble valueD;
1734     static timespan_t valueT;
1735     static AABoxd valueBox;
1736 
1737     switch (ddvalue)
1738     {
1739     case DD_GAME_EXPORTS:
1740         return &gx;
1741 
1742     case DD_MAP_POLYOBJ_COUNT:
1743         value = App_World().hasMap()? App_World().map().polyobjCount() : 0;
1744         return &value;
1745 
1746     case DD_MAP_BOUNDING_BOX:
1747         if (App_World().hasMap())
1748         {
1749             valueBox = App_World().map().bounds();
1750         }
1751         else
1752         {
1753             valueBox = AABoxd(0.0, 0.0, 0.0, 0.0);
1754         }
1755         return &valueBox;
1756 
1757     case DD_MAP_MIN_X:
1758         valueD = App_World().hasMap()? App_World().map().bounds().minX : 0;
1759         return &valueD;
1760 
1761     case DD_MAP_MIN_Y:
1762         valueD = App_World().hasMap()? App_World().map().bounds().minY : 0;
1763         return &valueD;
1764 
1765     case DD_MAP_MAX_X:
1766         valueD = App_World().hasMap()? App_World().map().bounds().maxX : 0;
1767         return &valueD;
1768 
1769     case DD_MAP_MAX_Y:
1770         valueD = App_World().hasMap()? App_World().map().bounds().maxY : 0;
1771         return &valueD;
1772 
1773     /*case DD_CPLAYER_THRUST_MUL:
1774         return &cplrThrustMul;*/
1775 
1776     case DD_MAP_GRAVITY:
1777         valueD = App_World().hasMap()? App_World().map().gravity() : 0;
1778         return &valueD;
1779 
1780 #ifdef __CLIENT__
1781     case DD_PSPRITE_OFFSET_X:
1782         return &pspOffset[0];
1783 
1784     case DD_PSPRITE_OFFSET_Y:
1785         return &pspOffset[1];
1786 
1787     case DD_PSPRITE_LIGHTLEVEL_MULTIPLIER:
1788         return &pspLightLevelMultiplier;
1789 
1790     case DD_TORCH_RED:
1791         return &torchColor.x;
1792 
1793     case DD_TORCH_GREEN:
1794         return &torchColor.y;
1795 
1796     case DD_TORCH_BLUE:
1797         return &torchColor.z;
1798 
1799     //case DD_TORCH_ADDITIVE:
1800     //    return &torchAdditive;
1801 
1802 # ifdef WIN32
1803     case DD_WINDOW_HANDLE:
1804         return ClientWindow::main().nativeHandle();
1805 # endif
1806 #endif
1807 
1808     // We have to separately calculate the 35 Hz ticks.
1809     case DD_GAMETIC:
1810         valueT = gameTime * TICSPERSEC;
1811         return &valueT;
1812 
1813     case DD_DEFS:
1814         return DED_Definitions();
1815 
1816     default: break;
1817     }
1818 
1819     if (ddvalue >= DD_LAST_VALUE || ddvalue < DD_FIRST_VALUE)
1820     {
1821         return 0;
1822     }
1823 
1824     // Other values not supported.
1825     return ddValues[ddvalue].writePtr;
1826 }
1827 
1828 /**
1829  * Set the value of a variable. The pointer can point to any data, its
1830  * interpretation depends on the variable. Added for 64-bit support.
1831  */
1832 #undef DD_SetVariable
DD_SetVariable(dint ddvalue,void * parm)1833 void DD_SetVariable(dint ddvalue, void *parm)
1834 {
1835     if (ddvalue < DD_FIRST_VALUE || ddvalue >= DD_LAST_VALUE)
1836     {
1837         switch (ddvalue)
1838         {
1839         /*case DD_CPLAYER_THRUST_MUL:
1840             cplrThrustMul = *(dfloat*) parm;
1841             return;*/
1842 
1843         case DD_MAP_GRAVITY:
1844             if (App_World().hasMap())
1845                 App_World().map().setGravity(*(coord_t*) parm);
1846             return;
1847 
1848 #ifdef __CLIENT__
1849         case DD_PSPRITE_OFFSET_X:
1850             pspOffset[0] = *(dfloat *) parm;
1851             return;
1852 
1853         case DD_PSPRITE_OFFSET_Y:
1854             pspOffset[1] = *(dfloat *) parm;
1855             return;
1856 
1857         case DD_PSPRITE_LIGHTLEVEL_MULTIPLIER:
1858             pspLightLevelMultiplier = *(dfloat *) parm;
1859             return;
1860 
1861         case DD_TORCH_RED:
1862             torchColor.x = de::clamp(0.f, *((dfloat*) parm), 1.f);
1863             return;
1864 
1865         case DD_TORCH_GREEN:
1866             torchColor.y = de::clamp(0.f, *((dfloat*) parm), 1.f);
1867             return;
1868 
1869         case DD_TORCH_BLUE:
1870             torchColor.z = de::clamp(0.f, *((dfloat*) parm), 1.f);
1871             return;
1872 
1873         //case DD_TORCH_ADDITIVE:
1874         //    torchAdditive = (*(dint*) parm)? true : false;
1875         //    break;
1876 #endif
1877 
1878         default:
1879             break;
1880         }
1881     }
1882 }
1883 
DD_ReadGameHelp()1884 void DD_ReadGameHelp()
1885 {
1886     using namespace de;
1887     LOG_AS("DD_ReadGameHelp");
1888     try
1889     {
1890         if (App_GameLoaded())
1891         {
1892             const de::Uri uri(Path("$(App.DataPath)/$(GamePlugin.Name)/conhelp.txt"));
1893             FS::FoundFiles found;
1894             FS::get().findAll(uri.resolved(), found);
1895             if (found.empty())
1896             {
1897                 throw Error("DD_ReadGameHelp", "conhelp.txt not found");
1898             }
1899             Help_ReadStrings(*found.front());
1900         }
1901     }
1902     catch (Error const &er)
1903     {
1904         LOG_RES_WARNING("") << er.asText();
1905     }
1906 }
1907 
1908 /// @note Part of the Doomsday public API.
DD_ParseFontSchemeName(char const * str)1909 fontschemeid_t DD_ParseFontSchemeName(char const *str)
1910 {
1911 #ifdef __CLIENT__
1912     try
1913     {
1914         FontScheme &scheme = App_Resources().fontScheme(str);
1915         if (!scheme.name().compareWithoutCase("System"))
1916         {
1917             return FS_SYSTEM;
1918         }
1919         if (!scheme.name().compareWithoutCase("Game"))
1920         {
1921             return FS_GAME;
1922         }
1923     }
1924     catch (Resources::UnknownSchemeError const &)
1925     {}
1926 #endif
1927     qDebug() << "Unknown font scheme:" << String(str) << ", returning 'FS_INVALID'";
1928     return FS_INVALID;
1929 }
1930 
DD_MaterialSchemeNameForTextureScheme(String textureSchemeName)1931 String DD_MaterialSchemeNameForTextureScheme(String textureSchemeName)
1932 {
1933     if (!textureSchemeName.compareWithoutCase("Textures"))
1934     {
1935         return "Textures";
1936     }
1937     if (!textureSchemeName.compareWithoutCase("Flats"))
1938     {
1939         return "Flats";
1940     }
1941     if (!textureSchemeName.compareWithoutCase("Sprites"))
1942     {
1943         return "Sprites";
1944     }
1945     if (!textureSchemeName.compareWithoutCase("System"))
1946     {
1947         return "System";
1948     }
1949     return "";
1950 }
1951 
DD_MaterialSchemeNameForTextureScheme(ddstring_t const * textureSchemeName)1952 AutoStr *DD_MaterialSchemeNameForTextureScheme(ddstring_t const *textureSchemeName)
1953 {
1954     if (!textureSchemeName)
1955     {
1956         return AutoStr_FromTextStd("");
1957     }
1958     else
1959     {
1960         QByteArray schemeNameUtf8 = DD_MaterialSchemeNameForTextureScheme(String(Str_Text(textureSchemeName))).toUtf8();
1961         return AutoStr_FromTextStd(schemeNameUtf8.constData());
1962     }
1963 }
1964 
D_CMD(Load)1965 D_CMD(Load)
1966 {
1967     DENG2_UNUSED(src);
1968     BusyMode_FreezeGameForBusyMode();
1969     auto &loader = PackageLoader::get();
1970 
1971     for (int arg = 1; arg < argc; ++arg)
1972     {
1973         String searchTerm = String(argv[arg]).trimmed();
1974         if (!searchTerm) continue;
1975 
1976         // Are we loading a game?
1977         if (DoomsdayApp::games().contains(searchTerm))
1978         {
1979             Game const &game = DoomsdayApp::games()[searchTerm];
1980             if (!game.isPlayable())
1981             {
1982                 LOG_SCR_ERROR("Game \"%s\" is missing one or more required packages: %s")
1983                         << game.id()
1984                         << String::join(game.profile().unavailablePackages(), ", ");
1985                 return true;
1986             }
1987             if (DoomsdayApp::app().changeGame(game.profile(), DD_ActivateGameWorker))
1988             {
1989                 game.profile().setLastPlayedAt();
1990                 continue;
1991             }
1992             return false;
1993         }
1994 
1995         // It could also be a game profile.
1996         if (auto *profile = DoomsdayApp::gameProfiles().tryFind(searchTerm))
1997         {
1998             auto &gameProf = profile->as<GameProfile>();
1999             if (!gameProf.isPlayable())
2000             {
2001                 LOG_SCR_ERROR("Profile \"%s\" is missing one or more required packages: ")
2002                         << String::join(gameProf.unavailablePackages(), ", ");
2003                 return true;
2004             }
2005             if (DoomsdayApp::app().changeGame(gameProf, DD_ActivateGameWorker))
2006             {
2007                 gameProf.setLastPlayedAt();
2008                 continue;
2009             }
2010             return false;
2011         }
2012 
2013         try
2014         {
2015             // Check packages with a matching name.
2016             if (loader.isAvailable(searchTerm))
2017             {
2018                 if (loader.isLoaded(searchTerm))
2019                 {
2020                     LOG_SCR_MSG("Package \"%s\" is already loaded") << searchTerm;
2021                     continue;
2022                 }
2023                 loader.load(searchTerm);
2024                 continue;
2025             }
2026 
2027             // Check data bundles with a matching name. We assume the search term
2028             // is a native path.
2029             if (!DoomsdayApp::isGameLoaded())
2030             {
2031                 LOG_SCR_ERROR("Cannot load data files when a game isn't loaded");
2032                 return false;
2033             }
2034             auto files = DataBundle::findAllNative(searchTerm);
2035             if (files.size() == 1)
2036             {
2037                 if (!files.first()->isLinkedAsPackage())
2038                 {
2039                     LOG_SCR_ERROR("%s cannot be loaded (could be ignored due to "
2040                                   "being unsupported or invalid") << files.first()->description();
2041                     return false;
2042                 }
2043                 loader.load(files.first()->packageId());
2044                 continue;
2045             }
2046             else if (files.size() > 1)
2047             {
2048                 LOG_SCR_MSG("There are %i possible matches for the name \"%s\"")
2049                         << files.size()
2050                         << searchTerm;
2051                 if (files.size() <= 10)
2052                 {
2053                     LOG_SCR_MSG("Maybe you meant:");
2054                     for (auto const *f : files)
2055                     {
2056                         LOG_SCR_MSG("- " _E(>) "%s") << f->description();
2057                     }
2058                 }
2059                 return false;
2060             }
2061             else
2062             {
2063                 LOG_SCR_ERROR("No files found matching the name \"%s\"") << searchTerm;
2064                 return false;
2065             }
2066         }
2067         catch (Error const &er)
2068         {
2069             LOG_SCR_ERROR("Failed to load package \"%s\": %s") << searchTerm << er.asText();
2070             return false;
2071         }
2072     }
2073     return true;
2074 }
2075 
D_CMD(Unload)2076 D_CMD(Unload)
2077 {
2078     DENG2_UNUSED(src);
2079     BusyMode_FreezeGameForBusyMode();
2080 
2081     DoomsdayApp &app = DoomsdayApp::app();
2082 
2083     try
2084     {
2085         // No arguments; unload the current game if loaded.
2086         if (argc == 1)
2087         {
2088             if (!app.isGameLoaded())
2089             {
2090                 LOG_SCR_MSG("No game is currently loaded");
2091                 return true;
2092             }
2093             return app.changeGame(GameProfiles::null(), DD_ActivateGameWorker);
2094         }
2095 
2096         auto &loader = PackageLoader::get();
2097         auto const loadedPackages = loader.loadedPackages();
2098         auto loadedBundles = DataBundle::loadedBundles();
2099 
2100         for (int arg = 1; arg < argc; ++arg)
2101         {
2102             String searchTerm = String(argv[arg]).trimmed();
2103             if (!searchTerm) continue;
2104 
2105             if (app.isGameLoaded() && searchTerm == DoomsdayApp::game().id())
2106             {
2107                 if (!app.changeGame(GameProfiles::null(), DD_ActivateGameWorker))
2108                 {
2109                     return false;
2110                 }
2111                 continue;
2112             }
2113 
2114             // Is this one of the loaded packages?
2115             if (loadedPackages.contains(searchTerm) && loader.isAvailable(searchTerm))
2116             {
2117                 loader.unload(searchTerm);
2118                 continue;
2119             }
2120             for (DataBundle const *bundle : loadedBundles)
2121             {
2122                 if (!bundle->sourceFile().name().compareWithoutCase(searchTerm))
2123                 {
2124                     loadedBundles.removeOne(bundle);
2125                     loader.unload(bundle->packageId());
2126                     break;
2127                 }
2128             }
2129         }
2130     }
2131     catch (Error const &er)
2132     {
2133         LOG_SCR_ERROR("Problem while unloading: %s") << er.asText();
2134         return false;
2135     }
2136 
2137     /*AutoStr *searchPath = AutoStr_NewStd();
2138     Str_Set(searchPath, argv[1]);
2139     Str_Strip(searchPath);
2140     if (Str_IsEmpty(searchPath)) return false;
2141 
2142     F_FixSlashes(searchPath, searchPath);
2143 
2144     // Ignore attempts to unload directories.
2145     if (Str_RAt(searchPath, 0) == '/')
2146     {
2147         LOG_MSG("Directories cannot be \"unloaded\" (only files and/or known games).");
2148         return true;
2149     }
2150 
2151     // Unload the current game if specified.
2152     if (argc == 2)
2153     {
2154         try
2155         {
2156             Game &game = App_Games()[Str_Text(searchPath)];
2157             if (App_GameLoaded())
2158             {
2159                 return DoomsdayApp::app().changeGame(GameProfiles::null(),
2160                                                      DD_ActivateGameWorker);
2161             }
2162 
2163             LOG_MSG("%s is not currently loaded.") << game.id();
2164             return true;
2165         }
2166         catch (Games::NotFoundError const &)
2167         {} // Ignore the error.
2168     }
2169 
2170     // Try the resource locator.
2171     bool didUnloadFiles = false;
2172     for (dint i = 1; i < argc; ++i)
2173     {
2174         try
2175         {
2176             String foundPath = App_FileSystem().findPath(de::Uri::fromNativePath(argv[1], RC_PACKAGE),
2177                                                          RLF_MATCH_EXTENSION, App_ResourceClass(RC_PACKAGE));
2178             foundPath = App_BasePath() / foundPath; // Ensure the path is absolute.
2179 
2180             if (File1::tryUnload(de::makeUri(foundPath)))
2181             {
2182                 didUnloadFiles = true;
2183             }
2184         }
2185         catch (FS1::NotFoundError const&)
2186         {} // Ignore this error.
2187     }
2188 
2189     if (didUnloadFiles)
2190     {
2191         // A changed file list may alter the main lump directory.
2192         DD_UpdateEngineState();
2193     }
2194 
2195     return didUnloadFiles;*/
2196 
2197     return true;
2198 }
2199 
D_CMD(Reset)2200 D_CMD(Reset)
2201 {
2202     DENG2_UNUSED3(src, argc, argv);
2203 
2204     DD_UpdateEngineState();
2205     return true;
2206 }
2207 
D_CMD(ReloadGame)2208 D_CMD(ReloadGame)
2209 {
2210     DENG2_UNUSED3(src, argc, argv);
2211 
2212     if (!DoomsdayApp::currentGameProfile())
2213     {
2214         LOG_MSG("No game is presently loaded.");
2215         return true;
2216     }
2217     DoomsdayApp::app().changeGame(*DoomsdayApp::currentGameProfile(),
2218                                   DD_ActivateGameWorker,
2219                                   DoomsdayApp::AllowReload);
2220     return true;
2221 }
2222 
2223 #if defined (DENG_HAVE_UPDATER)
2224 
D_CMD(CheckForUpdates)2225 D_CMD(CheckForUpdates)
2226 {
2227     DENG2_UNUSED3(src, argc, argv);
2228 
2229     LOG_MSG("Checking for available updates...");
2230     ClientApp::updater().checkNow(Updater::OnlyShowResultIfUpdateAvailable);
2231     return true;
2232 }
2233 
D_CMD(CheckForUpdatesAndNotify)2234 D_CMD(CheckForUpdatesAndNotify)
2235 {
2236     DENG2_UNUSED3(src, argc, argv);
2237 
2238     LOG_MSG("Checking for available updates...");
2239     ClientApp::updater().checkNow(Updater::AlwaysShowResult);
2240     return true;
2241 }
2242 
D_CMD(LastUpdated)2243 D_CMD(LastUpdated)
2244 {
2245     DENG2_UNUSED3(src, argc, argv);
2246 
2247     ClientApp::updater().printLastUpdated();
2248     return true;
2249 }
2250 
D_CMD(ShowUpdateSettings)2251 D_CMD(ShowUpdateSettings)
2252 {
2253     DENG2_UNUSED3(src, argc, argv);
2254 
2255     ClientApp::updater().showSettings();
2256     return true;
2257 }
2258 
2259 #endif // DENG_HAVE_UPDATER
2260 
D_CMD(Version)2261 D_CMD(Version)
2262 {
2263     DENG2_UNUSED3(src, argc, argv);
2264 
2265     LOG_SCR_NOTE(_E(D) DOOMSDAY_NICENAME " %s") << Version::currentBuild().asHumanReadableText();
2266     LOG_SCR_MSG(_E(l) "Homepage: " _E(.) _E(i) DOOMSDAY_HOMEURL _E(.)
2267             "\n" _E(l) "Project: " _E(.) _E(i) DENGPROJECT_HOMEURL);
2268 
2269     // Print the version info of the current game if loaded.
2270     if (App_GameLoaded())
2271     {
2272         LOG_SCR_MSG(_E(l) "Game: " _E(.) "%s") << (char const *) gx.GetPointer(DD_PLUGIN_VERSION_LONG);
2273     }
2274 
2275     // Additional information for developers.
2276     Version const ver;
2277     if (!ver.gitDescription.isEmpty())
2278     {
2279         LOGDEV_SCR_MSG(_E(l) "Git revision: " _E(.) "%s") << ver.gitDescription;
2280     }
2281     return true;
2282 }
2283 
D_CMD(Quit)2284 D_CMD(Quit)
2285 {
2286     DENG2_UNUSED2(src, argc);
2287 
2288 #if defined (DENG_HAVE_UPDATER)
2289     if (UpdateDownloadDialog::isDownloadInProgress())
2290     {
2291         LOG_WARNING("Cannot quit while downloading an update");
2292         ClientWindow::main().taskBar().openAndPauseGame();
2293         UpdateDownloadDialog::currentDownload().open();
2294         return false;
2295     }
2296 #endif
2297 
2298     if (argv[0][4] == '!' || isDedicated || !App_GameLoaded() ||
2299        gx.TryShutdown == 0)
2300     {
2301         // No questions asked.
2302         Sys_Quit();
2303         return true; // Never reached.
2304     }
2305 
2306 #ifdef __CLIENT__
2307     // Dismiss the taskbar if it happens to be open, we are expecting
2308     // the game to handle this from now on.
2309     ClientWindow::main().taskBar().close();
2310 #endif
2311 
2312     // Defer this decision to the loaded game.
2313     return gx.TryShutdown();
2314 }
2315 
2316 #ifdef _DEBUG
D_CMD(DebugError)2317 D_CMD(DebugError)
2318 {
2319     DENG2_UNUSED3(src, argv, argc);
2320 
2321     App_Error("Fatal error!\n");
2322     return true;
2323 }
2324 #endif
2325 
D_CMD(Help)2326 D_CMD(Help)
2327 {
2328     DENG2_UNUSED3(src, argc, argv);
2329 
2330     /*
2331 #ifdef __CLIENT__
2332     char actKeyName[40];
2333     strcpy(actKeyName, B_ShortNameForKey(consoleActiveKey));
2334     actKeyName[0] = toupper(actKeyName[0]);
2335 #endif
2336 */
2337 
2338     LOG_SCR_NOTE(_E(b) DOOMSDAY_NICENAME " %s Console") << Version::currentBuild().compactNumber();
2339 
2340 #define TABBED(A, B) "\n" _E(Ta) _E(b) "  " << A << " " _E(.) _E(Tb) << B
2341 
2342 #ifdef __CLIENT__
2343     LOG_SCR_MSG(_E(D) "Keys:" _E(.))
2344             << TABBED(DENG2_CHAR_SHIFT_KEY "Esc", "Open the taskbar and console")
2345             << TABBED("Tab", "Autocomplete the word at the cursor")
2346             << TABBED(DENG2_CHAR_UP_DOWN_ARROW, "Move backwards/forwards through the input command history, or up/down one line inside a multi-line command")
2347             << TABBED("PgUp/Dn", "Scroll up/down in the history, or expand the history to full height")
2348             << TABBED(DENG2_CHAR_SHIFT_KEY "PgUp/Dn", "Jump to the top/bottom of the history")
2349             << TABBED("Home", "Move the cursor to the start of the command line")
2350             << TABBED("End", "Move the cursor to the end of the command line")
2351             << TABBED(DENG2_CHAR_CONTROL_KEY "K", "Clear everything on the line right of the cursor position")
2352             << TABBED("F5", "Clear the console message history");
2353 #endif
2354     LOG_SCR_MSG(_E(D) "Getting started:");
2355     LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "help (what)" _E(.) " for information about " _E(l) "(what)");
2356     LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "listcmds" _E(.) " to list available commands");
2357     LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "listgames" _E(.) " to list installed games and their status");
2358     LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "listvars" _E(.) " to list available variables");
2359 
2360 #undef TABBED
2361 
2362     return true;
2363 }
2364 
printHelpAbout(char const * query)2365 static void printHelpAbout(char const *query)
2366 {
2367     // Try the console commands first.
2368     if (ccmd_t *ccmd = Con_FindCommand(query))
2369     {
2370         LOG_SCR_MSG(_E(b) "%s" _E(.) " (Command)") << ccmd->name;
2371 
2372         HelpId help = DH_Find(ccmd->name);
2373         if (char const *description = DH_GetString(help, HST_DESCRIPTION))
2374         {
2375             LOG_SCR_MSG("") << description;
2376         }
2377 
2378         Con_PrintCommandUsage(ccmd);  // For all overloaded variants.
2379 
2380         // Any extra info?
2381         if (char const *info = DH_GetString(help, HST_INFO))
2382         {
2383             LOG_SCR_MSG("  " _E(>) _E(l)) << info;
2384         }
2385         return;
2386     }
2387 
2388     if (cvar_t *var = Con_FindVariable(query))
2389     {
2390         AutoStr *path = CVar_ComposePath(var);
2391         LOG_SCR_MSG(_E(b) "%s" _E(.) " (Variable)") << Str_Text(path);
2392 
2393         HelpId help = DH_Find(Str_Text(path));
2394         if (char const *description = DH_GetString(help, HST_DESCRIPTION))
2395         {
2396             LOG_SCR_MSG("") << description;
2397         }
2398         return;
2399     }
2400 
2401     if (calias_t *calias = Con_FindAlias(query))
2402     {
2403         LOG_SCR_MSG(_E(b) "%s" _E(.) " alias of:\n")
2404                 << calias->name << calias->command;
2405         return;
2406     }
2407 
2408     // Perhaps a game?
2409     try
2410     {
2411         Game &game = App_Games()[query];
2412         LOG_SCR_MSG(_E(b) "%s" _E(.) " (IdentityKey)") << game.id();
2413 
2414         LOG_SCR_MSG("Unique identifier of the " _E(b) "%s" _E(.) " game mode.") << game.title();
2415         LOG_SCR_MSG("An 'IdentityKey' is used when referencing a game unambiguously from the console and on the command line.");
2416         LOG_SCR_MSG(_E(D) "Related commands:");
2417         LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "inspectgame %s" _E(.) " for information and status of this game") << game.id();
2418         LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "listgames" _E(.) " to list all installed games and their status");
2419         LOG_SCR_MSG("  " _E(>) "Enter " _E(b) "load %s" _E(.) " to load the " _E(l) "%s" _E(.) " game mode") << game.id() << game.title();
2420         return;
2421     }
2422     catch (Games::NotFoundError const &)
2423     {}  // Ignore this error.
2424 
2425     LOG_SCR_NOTE("There is no help about '%s'") << query;
2426 }
2427 
D_CMD(HelpWhat)2428 D_CMD(HelpWhat)
2429 {
2430     DENG2_UNUSED2(argc, src);
2431 
2432     if (!String(argv[1]).compareWithoutCase("(what)"))
2433     {
2434         LOG_SCR_MSG("You've got to be kidding!");
2435         return true;
2436     }
2437 
2438     printHelpAbout(argv[1]);
2439     return true;
2440 }
2441 
2442 #ifdef __CLIENT__
D_CMD(Clear)2443 D_CMD(Clear)
2444 {
2445     DENG2_UNUSED3(src, argc, argv);
2446 
2447     ClientWindow::main().console().clearLog();
2448     return true;
2449 }
2450 #endif
2451 
DD_ConsoleRegister()2452 void DD_ConsoleRegister()
2453 {
2454     C_VAR_CHARPTR("file-startup", &startupFiles, 0, 0, 0);
2455 
2456     C_CMD("help",           "",     Help);
2457     C_CMD("help",           "s",    HelpWhat);
2458     C_CMD("version",        "",     Version);
2459     C_CMD("quit",           "",     Quit);
2460     C_CMD("quit!",          "",     Quit);
2461     C_CMD("load",           "s*",   Load);
2462     C_CMD("reset",          "",     Reset);
2463     C_CMD("reload",         "",     ReloadGame);
2464     C_CMD("unload",         "*",    Unload);
2465     C_CMD("write",          "s",    WriteConsole);
2466 
2467 #ifdef DENG2_DEBUG
2468     C_CMD("fatalerror",     nullptr,   DebugError);
2469 #endif
2470 
2471     DD_RegisterLoop();
2472     Def_ConsoleRegister();
2473     FS1::consoleRegister();
2474     Con_Register();
2475     Games::consoleRegister();
2476     DH_Register();
2477     AudioSystem::consoleRegister();
2478 
2479 #ifdef __CLIENT__
2480     C_CMD("clear",           "", Clear);
2481 
2482 #if defined (DENG_HAVE_UPDATER)
2483     C_CMD("update",          "", CheckForUpdates);
2484     C_CMD("updateandnotify", "", CheckForUpdatesAndNotify);
2485     C_CMD("updatesettings",  "", ShowUpdateSettings);
2486     C_CMD("lastupdated",     "", LastUpdated);
2487 #endif
2488 
2489     C_CMD_FLAGS("conclose",       "",     OpenClose,    CMDF_NO_DEDICATED);
2490     C_CMD_FLAGS("conopen",        "",     OpenClose,    CMDF_NO_DEDICATED);
2491     C_CMD_FLAGS("contoggle",      "",     OpenClose,    CMDF_NO_DEDICATED);
2492     C_CMD      ("taskbar",        "",     TaskBar);
2493     C_CMD      ("tutorial",       "",     Tutorial);
2494     C_CMD      ("packages",       "",     PackagesSidebar);
2495 
2496     /// @todo Move to UI module.
2497     Con_TransitionRegister();
2498 
2499     InputSystem::consoleRegister();
2500 #if 0
2501     SBE_Register();  // for bias editor
2502 #endif
2503     RenderSystem::consoleRegister();
2504     GL_Register();
2505     //UI_Register();
2506     Demo_Register();
2507     P_ConsoleRegister();
2508     I_Register();
2509     ClientResources::consoleRegister();
2510 #endif
2511 
2512 #ifdef __SERVER__
2513     Resources::consoleRegister();
2514 #endif
2515 
2516     Net_Register();
2517     ClientServerWorld::consoleRegister();
2518     InFineSystem::consoleRegister();
2519 }
2520 
2521 // dd_loop.c
2522 DENG_EXTERN_C dd_bool DD_IsSharpTick(void);
2523 
2524 // net_main.c
2525 DENG_EXTERN_C void Net_SendPacket(dint to_player, dint type, const void* data, size_t length);
2526 
2527 #undef R_SetupMap
R_SetupMap(dint mode,dint flags)2528 DENG_EXTERN_C void R_SetupMap(dint mode, dint flags)
2529 {
2530     DENG2_UNUSED2(mode, flags);
2531 
2532     if (!App_World().hasMap()) return; // Huh?
2533 
2534     // Perform map setup again. Its possible that after loading we now
2535     // have more HOMs to fix, etc..
2536     world::Map &map = App_World().map();
2537 
2538 #ifdef __CLIENT__
2539     map.initSkyFix();
2540 #endif
2541 
2542 #ifdef __CLIENT__
2543     /*
2544     // Update all sectors.
2545     /// @todo Refactor away.
2546     map.forAllSectors([] (Sector &sector)
2547     {
2548         return sector.forAllSubsectors([] (world::Subsector &subsec)
2549         {
2550             return subsec.as<world::ClientSubsector>().forAllEdgeLoops([] (world::ClEdgeLoop &loop)
2551             {
2552                 loop.fixSurfacesMissingMaterials();
2553                 return LoopContinue;
2554             });
2555         });
2556     });
2557     */
2558 #endif
2559 
2560     // Re-initialize polyobjs.
2561     /// @todo Still necessary?
2562     map.initPolyobjs();
2563 
2564     // Reset the timer so that it will appear that no time has passed.
2565     DD_ResetTimer();
2566 }
2567 
2568 // sys_system.c
2569 DENG_EXTERN_C void Sys_Quit();
2570 
2571 DENG_DECLARE_API(Base) =
2572 {
2573     { DE_API_BASE },
2574 
2575     Sys_Quit,
2576     DD_GetInteger,
2577     DD_SetInteger,
2578     DD_GetVariable,
2579     DD_SetVariable,
2580     DD_GameInfo,
2581     DD_IsSharpTick,
2582     Net_SendPacket,
2583     R_SetupMap
2584 };
2585