Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
  3.  * It is copyright by its individual contributors, as recorded in the
  4.  * project's Git history.  See COPYING.txt at the top level for license
  5.  * terms and a link to the Git history.
  6.  */
  7.  
  8. /*
  9.  *
  10.  * Functions for accessing arguments.
  11.  *
  12.  */
  13.  
  14. #include <string>
  15. #include <vector>
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include <SDL_stdinc.h>
  19. #include "physfsx.h"
  20. #include "args.h"
  21. #include "u_mem.h"
  22. #include "strutil.h"
  23. #include "digi.h"
  24. #include "game.h"
  25. #include "gauges.h"
  26. #include "console.h"
  27. #include "mission.h"
  28. #if DXX_USE_UDP
  29. #include "net_udp.h"
  30. #endif
  31.  
  32. #include "compiler-range_for.h"
  33. #include "partial_range.h"
  34.  
  35. namespace dcx {
  36. CArg CGameArg;
  37. constexpr std::integral_constant<std::size_t, 1000> MAX_ARGS{};
  38. typedef std::vector<std::string> Arglist;
  39.  
  40. namespace {
  41.  
  42. class ini_entry
  43. {
  44. public:
  45.         const std::string filename;
  46.         ini_entry(std::string &&f) :
  47.                 filename(std::move(f))
  48.         {
  49.         }
  50. };
  51.  
  52. typedef std::vector<ini_entry> Inilist;
  53.  
  54. class argument_exception
  55. {
  56. public:
  57.         const std::string arg;
  58.         argument_exception(std::string &&a) :
  59.                 arg(std::move(a))
  60.         {
  61.         }
  62. };
  63.  
  64. class missing_parameter : public argument_exception
  65. {
  66. public:
  67.         using argument_exception::argument_exception;
  68. };
  69.  
  70. class unhandled_argument : public argument_exception
  71. {
  72. public:
  73.         using argument_exception::argument_exception;
  74. };
  75.  
  76. class conversion_failure : public argument_exception
  77. {
  78. public:
  79.         const std::string value;
  80.         conversion_failure(std::string &&a, std::string &&v) :
  81.                 argument_exception(std::move(a)), value(std::move(v))
  82.         {
  83.         }
  84. };
  85.  
  86. class nesting_depth_exceeded
  87. {
  88. };
  89.  
  90. }
  91.  
  92. static void AppendIniArgs(const char *filename, Arglist &Args)
  93. {
  94.         if (auto f = PHYSFSX_openReadBuffered(filename))
  95.         {
  96.                 PHYSFSX_gets_line_t<1024> line;
  97.                 while (Args.size() < MAX_ARGS && PHYSFSX_fgets(line, f))
  98.                 {
  99.                         const auto separator = " \t";
  100.                         for(char *token = strtok(line, separator); token != NULL; token = strtok(NULL, separator))
  101.                         {
  102.                                 if (*token == ';')
  103.                                         break;
  104.                                 Args.push_back(token);
  105.                         }
  106.                 }
  107.         }
  108. }
  109.  
  110. static std::string &&arg_string(Arglist::iterator &pp, Arglist::const_iterator end)
  111. {
  112.         auto arg = pp;
  113.         if (++pp == end)
  114.                 throw missing_parameter(std::move(*arg));
  115.         return std::move(*pp);
  116. }
  117.  
  118. static long arg_integer(Arglist::iterator &pp, Arglist::const_iterator end)
  119. {
  120.         auto arg = pp;
  121.         auto &&value = arg_string(pp, end);
  122.         char *p;
  123.         auto i = strtol(value.c_str(), &p, 10);
  124.         if (*p)
  125.                 throw conversion_failure(std::move(*arg), std::move(value));
  126.         return i;
  127. }
  128.  
  129. template<typename E> E arg_enum(Arglist::iterator &pp, Arglist::const_iterator end)
  130. {
  131.         return static_cast<E>(arg_integer(pp, end));
  132. }
  133.  
  134. #if DXX_USE_UDP
  135. static void arg_port_number(Arglist::iterator &pp, Arglist::const_iterator end, uint16_t &out, bool allow_privileged)
  136. {
  137.         auto port = arg_integer(pp, end);
  138.         if (static_cast<uint16_t>(port) == port && (allow_privileged || port >= 1024))
  139.                 out = port;
  140. }
  141. #endif
  142.  
  143. static void InitGameArg()
  144. {
  145.         CGameArg.SysMaxFPS = MAXIMUM_FPS;
  146. #if defined(DXX_BUILD_DESCENT_II)
  147.         GameArg.SndDigiSampleRate = SAMPLE_RATE_22K;
  148. #endif
  149. #if DXX_USE_UDP
  150.         CGameArg.MplUdpHostAddr = UDP_MANUAL_ADDR_DEFAULT;
  151. #if DXX_USE_TRACKER
  152.         CGameArg.MplTrackerAddr = TRACKER_ADDR_DEFAULT;
  153.         CGameArg.MplTrackerPort = TRACKER_PORT_DEFAULT;
  154. #endif
  155. #endif
  156.         CGameArg.DbgVerbose = CON_NORMAL;
  157.         CGameArg.DbgBpp = 32;
  158. #if DXX_USE_OGL
  159.         CGameArg.OglSyncMethod = OGL_SYNC_METHOD_DEFAULT;
  160.         CGameArg.OglSyncWait = OGL_SYNC_WAIT_DEFAULT;
  161.         CGameArg.DbgGlIntensity4Ok      = true;
  162.         CGameArg.DbgGlLuminance4Alpha4Ok = true;
  163.         CGameArg.DbgGlRGBA2Ok = true;
  164.         CGameArg.DbgGlReadPixelsOk = true;
  165.         CGameArg.DbgGlGetTexLevelParamOk = true;
  166. #endif
  167. }
  168.  
  169. }
  170.  
  171. namespace dsx {
  172.  
  173. Arg GameArg;
  174.  
  175. static void ReadCmdArgs(Inilist &ini, Arglist &Args);
  176.  
  177. static void ReadIniArgs(Inilist &ini)
  178. {
  179.         Arglist Args;
  180.         AppendIniArgs(ini.back().filename.c_str(), Args);
  181.         ReadCmdArgs(ini, Args);
  182.         ini.pop_back();
  183. }
  184.  
  185. static void ReadCmdArgs(Inilist &ini, Arglist &Args)
  186. {
  187.         for (Arglist::iterator pp = Args.begin(), end = Args.end(); pp != end; ++pp)
  188.         {
  189.                 const char *p = pp->c_str();
  190.         // System Options
  191.  
  192.                 if (!d_stricmp(p, "-help") || !d_stricmp(p, "-h") || !d_stricmp(p, "-?") || !d_stricmp(p, "?"))
  193.                         CGameArg.SysShowCmdHelp = true;
  194.                 else if (!d_stricmp(p, "-nonicefps"))
  195.                         CGameArg.SysNoNiceFPS = true;
  196.                 else if (!d_stricmp(p, "-maxfps"))
  197.                         CGameArg.SysMaxFPS = arg_integer(pp, end);
  198.                 else if (!d_stricmp(p, "-hogdir"))
  199.                         CGameArg.SysHogDir = arg_string(pp, end);
  200. #if PHYSFS_VER_MAJOR >= 2
  201.                 else if (!d_stricmp(p, "-add-missions-dir"))
  202.                         CGameArg.SysMissionDir = arg_string(pp, end);
  203. #endif
  204.                 else if (!d_stricmp(p, "-nohogdir"))
  205.                 {
  206.                         /* No effect if no DXX_SHAREPATH.  Ignore it so that players can
  207.                          * pass it via a cross-platform ini.
  208.                          */
  209. #if DXX_USE_SHAREPATH
  210.                         GameArg.SysNoHogDir = true;
  211. #endif
  212.                 }
  213.                 else if (!d_stricmp(p, "-use_players_dir"))
  214.                         CGameArg.SysUsePlayersDir = static_cast<int8_t>(- (sizeof(PLAYER_DIRECTORY_TEXT) - 1));
  215.                 else if (!d_stricmp(p, "-lowmem"))
  216.                         CGameArg.SysLowMem = true;
  217.                 else if (!d_stricmp(p, "-pilot"))
  218.                         CGameArg.SysPilot = arg_string(pp, end);
  219.                 else if (!d_stricmp(p, "-record-demo-format"))
  220.                         CGameArg.SysRecordDemoNameTemplate = arg_string(pp, end);
  221.                 else if (!d_stricmp(p, "-auto-record-demo"))
  222.                         CGameArg.SysAutoRecordDemo = true;
  223.                 else if (!d_stricmp(p, "-window"))
  224.                         CGameArg.SysWindow = true;
  225.                 else if (!d_stricmp(p, "-noborders"))
  226.                         CGameArg.SysNoBorders = true;
  227.                 else if (!d_stricmp(p, "-notitles"))
  228.                         CGameArg.SysNoTitles = true;
  229. #if defined(DXX_BUILD_DESCENT_II)
  230.                 else if (!d_stricmp(p, "-nomovies"))
  231.                         GameArg.SysNoMovies             = 1;
  232. #endif
  233.                 else if (!d_stricmp(p, "-autodemo"))
  234.                         CGameArg.SysAutoDemo = true;
  235.  
  236.         // Control Options
  237.  
  238.                 else if (!d_stricmp(p, "-nocursor"))
  239.                         CGameArg.CtlNoCursor            = true;
  240.                 else if (!d_stricmp(p, "-nomouse"))
  241.                         CGameArg.CtlNoMouse             = true;
  242.                 else if (!d_stricmp(p, "-nojoystick"))
  243.                 {
  244. #if DXX_MAX_JOYSTICKS
  245.                         CGameArg.CtlNoJoystick          = 1;
  246. #endif
  247.                 }
  248.                 else if (!d_stricmp(p, "-nostickykeys"))
  249.                         CGameArg.CtlNoStickyKeys        = true;
  250.  
  251.         // Sound Options
  252.  
  253.                 else if (!d_stricmp(p, "-nosound"))
  254.                         CGameArg.SndNoSound             = 1;
  255.                 else if (!d_stricmp(p, "-nomusic"))
  256.                         CGameArg.SndNoMusic = true;
  257. #if defined(DXX_BUILD_DESCENT_II)
  258.                 else if (!d_stricmp(p, "-sound11k"))
  259.                         GameArg.SndDigiSampleRate               = SAMPLE_RATE_11K;
  260. #endif
  261.                 else if (!d_stricmp(p, "-nosdlmixer"))
  262.                 {
  263. #if DXX_USE_SDLMIXER
  264.                         CGameArg.SndDisableSdlMixer = true;
  265. #endif
  266.                 }
  267.  
  268.         // Graphics Options
  269.  
  270.                 else if (!d_stricmp(p, "-lowresfont"))
  271.                         CGameArg.GfxSkipHiresFNT = true;
  272. #if defined(DXX_BUILD_DESCENT_II)
  273.                 else if (!d_stricmp(p, "-lowresgraphics"))
  274.                         GameArg.GfxSkipHiresGFX = 1;
  275.                 else if (!d_stricmp(p, "-lowresmovies"))
  276.                         GameArg.GfxSkipHiresMovie               = 1;
  277. #endif
  278. #if DXX_USE_OGL
  279.         // OpenGL Options
  280.  
  281.                 else if (!d_stricmp(p, "-gl_fixedfont"))
  282.                         CGameArg.OglFixedFont = true;
  283.                 else if (!d_stricmp(p, "-gl_syncmethod"))
  284.                         CGameArg.OglSyncMethod = arg_enum<SyncGLMethod>(pp, end);
  285.                 else if (!d_stricmp(p, "-gl_syncwait"))
  286.                         CGameArg.OglSyncWait = arg_integer(pp, end);
  287.                 else if (!d_stricmp(p, "-gl_darkedges"))
  288.                         CGameArg.OglDarkEdges = true;
  289. #endif
  290.  
  291.         // Multiplayer Options
  292.  
  293. #if DXX_USE_UDP
  294.                 else if (!d_stricmp(p, "-udp_hostaddr"))
  295.                         CGameArg.MplUdpHostAddr = arg_string(pp, end);
  296.                 else if (!d_stricmp(p, "-udp_hostport"))
  297.                         /* Peers use -udp_myport to change, so peer cannot set a
  298.                          * privileged port.
  299.                          */
  300.                         arg_port_number(pp, end, CGameArg.MplUdpHostPort, false);
  301.                 else if (!d_stricmp(p, "-udp_myport"))
  302.                 {
  303.                         arg_port_number(pp, end, CGameArg.MplUdpMyPort, false);
  304.                 }
  305.                 else if (!d_stricmp(p, "-no-tracker"))
  306.                 {
  307.                         /* Always recognized.  No-op if tracker support compiled
  308.                          * out. */
  309. #if DXX_USE_TRACKER
  310.                         CGameArg.MplTrackerAddr.clear();
  311. #endif
  312.                 }
  313. #if DXX_USE_TRACKER
  314.                 else if (!d_stricmp(p, "-tracker_hostaddr"))
  315.                 {
  316.                         CGameArg.MplTrackerAddr = arg_string(pp, end);
  317.                 }
  318.                 else if (!d_stricmp(p, "-tracker_hostport"))
  319.                         arg_port_number(pp, end, CGameArg.MplTrackerPort, true);
  320. #endif
  321. #endif
  322.  
  323. #if defined(DXX_BUILD_DESCENT_I)
  324.                 else if (!d_stricmp(p, "-nobm"))
  325.                         GameArg.EdiNoBm                 = 1;
  326. #elif defined(DXX_BUILD_DESCENT_II)
  327. #if DXX_USE_EDITOR
  328.         // Editor Options
  329.  
  330.                 else if (!d_stricmp(p, "-autoload"))
  331.                         GameArg.EdiAutoLoad = arg_string(pp, end);
  332.                 else if (!d_stricmp(p, "-macdata"))
  333.                         GameArg.EdiMacData              = 1;
  334.                 else if (!d_stricmp(p, "-hoarddata"))
  335.                         GameArg.EdiSaveHoardData        = 1;
  336. #endif
  337. #endif
  338.  
  339.         // Debug Options
  340.  
  341.                 else if (!d_stricmp(p, "-debug"))
  342.                         CGameArg.DbgVerbose     = CON_DEBUG;
  343.                 else if (!d_stricmp(p, "-verbose"))
  344.                         CGameArg.DbgVerbose     = CON_VERBOSE;
  345.  
  346.                 else if (!d_stricmp(p, "-no-grab"))
  347.                         CGameArg.DbgForbidConsoleGrab = true;
  348.                 else if (!d_stricmp(p, "-safelog"))
  349.                         CGameArg.DbgSafelog = true;
  350.                 else if (!d_stricmp(p, "-norun"))
  351.                         CGameArg.DbgNoRun = true;
  352.                 else if (!d_stricmp(p, "-renderstats"))
  353.                         CGameArg.DbgRenderStats = true;
  354.                 else if (!d_stricmp(p, "-text"))
  355.                         CGameArg.DbgAltTex = arg_string(pp, end);
  356.                 else if (!d_stricmp(p, "-showmeminfo"))
  357.                         CGameArg.DbgShowMemInfo                 = 1;
  358.                 else if (!d_stricmp(p, "-nodoublebuffer"))
  359.                         CGameArg.DbgNoDoubleBuffer = true;
  360.                 else if (!d_stricmp(p, "-bigpig"))
  361.                         CGameArg.DbgNoCompressPigBitmap = true;
  362.                 else if (!d_stricmp(p, "-16bpp"))
  363.                         CGameArg.DbgBpp         = 16;
  364.  
  365. #if DXX_USE_OGL
  366.                 else if (!d_stricmp(p, "-gl_oldtexmerge"))
  367.                         CGameArg.DbgUseOldTextureMerge = true;
  368.                 else if (!d_stricmp(p, "-gl_intensity4_ok"))
  369.                         CGameArg.DbgGlIntensity4Ok = arg_integer(pp, end);
  370.                 else if (!d_stricmp(p, "-gl_luminance4_alpha4_ok"))
  371.                         CGameArg.DbgGlLuminance4Alpha4Ok = arg_integer(pp, end);
  372.                 else if (!d_stricmp(p, "-gl_rgba2_ok"))
  373.                         CGameArg.DbgGlRGBA2Ok = arg_integer(pp, end);
  374.                 else if (!d_stricmp(p, "-gl_readpixels_ok"))
  375.                         CGameArg.DbgGlReadPixelsOk = arg_integer(pp, end);
  376.                 else if (!d_stricmp(p, "-gl_gettexlevelparam_ok"))
  377.                         CGameArg.DbgGlGetTexLevelParamOk = arg_integer(pp, end);
  378. #else
  379.                 else if (!d_stricmp(p, "-tmap"))
  380.                         CGameArg.DbgTexMap = arg_string(pp, end);
  381.                 else if (!d_stricmp(p, "-hwsurface"))
  382.                         CGameArg.DbgSdlHWSurface = true;
  383.                 else if (!d_stricmp(p, "-asyncblit"))
  384.                         CGameArg.DbgSdlASyncBlit = true;
  385. #endif
  386.                 else if (!d_stricmp(p, "-ini"))
  387.                 {
  388.                         ini.emplace_back(arg_string(pp, end));
  389.                         if (ini.size() > 10)
  390.                                 throw nesting_depth_exceeded();
  391.                         ReadIniArgs(ini);
  392.                 }
  393. #if defined(__APPLE__) && defined(__MACH__)
  394.                 else if (!strncmp(p, "-psn", 4))
  395.                 {
  396.                         //do nothing/gobble it up
  397.                 }
  398. #endif
  399.                 else
  400.                         throw unhandled_argument(std::move(*pp));
  401.         }
  402. }
  403.  
  404. }
  405.  
  406. namespace dcx {
  407.  
  408. static void PostProcessGameArg()
  409. {
  410.         if (CGameArg.SysMaxFPS < MINIMUM_FPS)
  411.                 CGameArg.SysMaxFPS = MINIMUM_FPS;
  412.         else if (CGameArg.SysMaxFPS > MAXIMUM_FPS)
  413.                 CGameArg.SysMaxFPS = MAXIMUM_FPS;
  414. #if PHYSFS_VER_MAJOR >= 2
  415.         if (!CGameArg.SysMissionDir.empty())
  416.                 PHYSFS_mount(CGameArg.SysMissionDir.c_str(), MISSION_DIR, 1);
  417. #endif
  418.  
  419. #if SDL_MAJOR_VERSION == 1
  420.         static char sdl_disable_lock_keys[] = "SDL_DISABLE_LOCK_KEYS=0";
  421.         if (CGameArg.CtlNoStickyKeys) // Must happen before SDL_Init!
  422.                 sdl_disable_lock_keys[sizeof(sdl_disable_lock_keys) - 2] = '1';
  423.         SDL_putenv(sdl_disable_lock_keys);
  424. #endif
  425. }
  426.  
  427. static std::string ConstructIniStackExplanation(const Inilist &ini)
  428. {
  429.         Inilist::const_reverse_iterator i = ini.rbegin(), e = ini.rend();
  430.         if (i == e)
  431.                 return " while processing <command line>";
  432.         std::string result;
  433.         result.reserve(ini.size() * 128);
  434.         result += " while processing \"";
  435.         for (;;)
  436.         {
  437.                 result += i->filename;
  438.                 if (++ i == e)
  439.                         return result += "\"";
  440.                 result += "\"\n    included from \"";
  441.         }
  442. }
  443.  
  444. }
  445.  
  446. namespace dsx {
  447.  
  448. bool InitArgs( int argc,char **argv )
  449. {
  450.         InitGameArg();
  451.  
  452.         Inilist ini;
  453.         try {
  454.                 {
  455.                         assert(ini.empty());
  456. #if defined(DXX_BUILD_DESCENT_I)
  457.                         const auto INI_FILENAME = "d1x.ini";
  458. #elif defined(DXX_BUILD_DESCENT_II)
  459.                         const auto INI_FILENAME = "d2x.ini";
  460. #endif
  461.                         ini.emplace_back(INI_FILENAME);
  462.                         ReadIniArgs(ini);
  463.                 }
  464.                 {
  465.                         Arglist Args;
  466.                         Args.reserve(argc);
  467.                         range_for (auto &i, unchecked_partial_range(argv, 1u, static_cast<unsigned>(argc)))
  468.                                 Args.push_back(i);
  469.                         ReadCmdArgs(ini, Args);
  470.                 }
  471.                 PostProcessGameArg();
  472.                 return true;
  473.         } catch(const missing_parameter& e) {
  474.                 UserError("Missing parameter for argument \"%s\"%s", e.arg.c_str(), ConstructIniStackExplanation(ini).c_str());
  475.         } catch(const unhandled_argument& e) {
  476.                 UserError("Unhandled argument \"%s\"%s", e.arg.c_str(), ConstructIniStackExplanation(ini).c_str());
  477.         } catch(const conversion_failure& e) {
  478.                 UserError("Failed to convert argument \"%s\" parameter \"%s\"%s", e.arg.c_str(), e.value.c_str(), ConstructIniStackExplanation(ini).c_str());
  479.         } catch(const nesting_depth_exceeded &) {
  480.                 UserError("Nesting depth exceeded%s", ConstructIniStackExplanation(ini).c_str());
  481.         }
  482.         return false;
  483. }
  484.  
  485. }
  486.