56d1814489
SDL2_mixer changed how it upsamples sounds, and some users complained
about the difference. Add an internal resampler that emulates how
SDL_mixer upsamples sounds. Since all Rebirth upsampling is an integer
upsample (11Khz -> 44KHz, 22KHz -> 44Khz), this internal emulation is
considerably simpler than a general purpose resampler.
With this commit, the builder can choose which resamplers to enable.
The available resamplers are chosen by preprocessor directive, and
presently do not have an SConstruct flag. For each resampler, if no
choice is made, then the resampler will be enabled if it is reasonable.
At least one of the resamplers must be enabled, or the build will fail
with a `#error` message.
The user may choose at program start time which of the available
resamplers to use for that execution of the program, through passing one
of the command line arguments:
- `-sdlmixer-resampler=sdl-native`
- `-sdlmixer-resampler=emulate-sdl1`
- `-sdlmixer-resampler=emulate-soundblaster16`
Runtime switching is not supported. If the user does not choose, then
the first enabled resampler from the list below will be used. The
available resamplers are:
- sdl_native (DXX_FEATURE_EXTERNAL_RESAMPLER_SDL_NATIVE) - delegates to
SDL_mixer / SDL2_mixer, the way Rebirth has historically done.
- emulate_sdl1 (DXX_FEATURE_INTERNAL_RESAMPLER_EMULATE_SDL1) - an
internal resampler that emulates how SDL_mixer worked. This should be
equivalent to sdl_native when using SDL_mixer, so by default it is
enabled when Rebirth is built to use SDL2_mixer and disabled when
Rebirth is built to use SDL_mixer. It can still be enabled manually
even when building for SDL_mixer, but this does not seem likely to be
useful.
- emulate_soundblaster16
(DXX_FEATURE_INTERNAL_RESAMPLER_EMULATE_SOUNDBLASTER16) - an internal
resampler submitted by @raptor in
5165efbc46
. Some users reported audio
quality issues with this resampler, so it is not presently the
default.
538 lines
14 KiB
C++
538 lines
14 KiB
C++
/*
|
|
* This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
|
|
* It is copyright by its individual contributors, as recorded in the
|
|
* project's Git history. See COPYING.txt at the top level for license
|
|
* terms and a link to the Git history.
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* Functions for accessing arguments.
|
|
*
|
|
*/
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <SDL_stdinc.h>
|
|
#include "physfsx.h"
|
|
#include "args.h"
|
|
#include "u_mem.h"
|
|
#include "strutil.h"
|
|
#include "digi.h"
|
|
#include "game.h"
|
|
#include "console.h"
|
|
#include "mission.h"
|
|
#if DXX_USE_SDLMIXER
|
|
#include "digi_mixer.h"
|
|
#endif
|
|
#if DXX_USE_UDP
|
|
#include "net_udp.h"
|
|
#endif
|
|
|
|
#include "compiler-range_for.h"
|
|
#include "partial_range.h"
|
|
|
|
namespace dcx {
|
|
CArg CGameArg;
|
|
|
|
namespace {
|
|
|
|
constexpr std::integral_constant<std::size_t, 1000> MAX_ARGS{};
|
|
typedef std::vector<std::string> Arglist;
|
|
|
|
class ini_entry
|
|
{
|
|
public:
|
|
const std::string filename;
|
|
ini_entry(std::string &&f) :
|
|
filename(std::move(f))
|
|
{
|
|
}
|
|
};
|
|
|
|
typedef std::vector<ini_entry> Inilist;
|
|
|
|
class argument_exception
|
|
{
|
|
public:
|
|
const std::string arg;
|
|
argument_exception(std::string &&a) :
|
|
arg(std::move(a))
|
|
{
|
|
}
|
|
};
|
|
|
|
class missing_parameter : public argument_exception
|
|
{
|
|
public:
|
|
using argument_exception::argument_exception;
|
|
};
|
|
|
|
class unhandled_argument : public argument_exception
|
|
{
|
|
public:
|
|
using argument_exception::argument_exception;
|
|
};
|
|
|
|
class conversion_failure : public argument_exception
|
|
{
|
|
public:
|
|
const std::string value;
|
|
conversion_failure(std::string &&a, std::string &&v) :
|
|
argument_exception(std::move(a)), value(std::move(v))
|
|
{
|
|
}
|
|
};
|
|
|
|
class nesting_depth_exceeded
|
|
{
|
|
};
|
|
|
|
static void AppendIniArgs(const char *filename, Arglist &Args)
|
|
{
|
|
if (auto f = PHYSFSX_openReadBuffered(filename).first)
|
|
{
|
|
PHYSFSX_gets_line_t<1024> line;
|
|
while (Args.size() < MAX_ARGS && PHYSFSX_fgets(line, f))
|
|
{
|
|
const auto separator = " \t";
|
|
for(char *token = strtok(line, separator); token != NULL; token = strtok(NULL, separator))
|
|
{
|
|
if (*token == ';')
|
|
break;
|
|
Args.push_back(token);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::string &&arg_string(Arglist::iterator &pp, Arglist::const_iterator end)
|
|
{
|
|
auto arg = pp;
|
|
if (++pp == end)
|
|
throw missing_parameter(std::move(*arg));
|
|
return std::move(*pp);
|
|
}
|
|
|
|
static long arg_integer(Arglist::iterator &pp, Arglist::const_iterator end)
|
|
{
|
|
auto arg = pp;
|
|
auto &&value = arg_string(pp, end);
|
|
char *p;
|
|
auto i = strtol(value.c_str(), &p, 10);
|
|
if (*p)
|
|
throw conversion_failure(std::move(*arg), std::move(value));
|
|
return i;
|
|
}
|
|
|
|
template<typename E> E arg_enum(Arglist::iterator &pp, Arglist::const_iterator end)
|
|
{
|
|
return static_cast<E>(arg_integer(pp, end));
|
|
}
|
|
|
|
#if DXX_USE_UDP
|
|
static void arg_port_number(Arglist::iterator &pp, Arglist::const_iterator end, uint16_t &out, bool allow_privileged)
|
|
{
|
|
auto port = arg_integer(pp, end);
|
|
if (static_cast<uint16_t>(port) == port && (allow_privileged || port >= 1024))
|
|
out = port;
|
|
}
|
|
#endif
|
|
|
|
static void InitGameArg()
|
|
{
|
|
CGameArg.SysMaxFPS = MAXIMUM_FPS;
|
|
#if DXX_USE_UDP
|
|
CGameArg.MplUdpHostAddr = UDP_MANUAL_ADDR_DEFAULT;
|
|
#if DXX_USE_TRACKER
|
|
CGameArg.MplTrackerAddr = TRACKER_ADDR_DEFAULT;
|
|
CGameArg.MplTrackerPort = TRACKER_PORT_DEFAULT;
|
|
#endif
|
|
#endif
|
|
CGameArg.DbgVerbose = CON_NORMAL;
|
|
CGameArg.DbgBpp = 32;
|
|
#if DXX_USE_OGL
|
|
CGameArg.OglSyncMethod = OGL_SYNC_METHOD_DEFAULT;
|
|
CGameArg.OglSyncWait = OGL_SYNC_WAIT_DEFAULT;
|
|
#if DXX_USE_STEREOSCOPIC_RENDER
|
|
CGameArg.OglStereo = false;
|
|
#endif
|
|
CGameArg.DbgGlIntensity4Ok = true;
|
|
CGameArg.DbgGlLuminance4Alpha4Ok = true;
|
|
CGameArg.DbgGlRGBA2Ok = true;
|
|
CGameArg.DbgGlReadPixelsOk = true;
|
|
CGameArg.DbgGlGetTexLevelParamOk = true;
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
|
|
Arg GameArg;
|
|
|
|
namespace {
|
|
|
|
static void InitGameArg()
|
|
{
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
GameArg.SndDigiSampleRate = sound_sample_rate::_22k;
|
|
#endif
|
|
::dcx::InitGameArg();
|
|
}
|
|
|
|
static void ReadCmdArgs(Inilist &ini, Arglist &Args);
|
|
|
|
static void ReadIniArgs(Inilist &ini)
|
|
{
|
|
Arglist Args;
|
|
AppendIniArgs(ini.back().filename.c_str(), Args);
|
|
ReadCmdArgs(ini, Args);
|
|
ini.pop_back();
|
|
}
|
|
|
|
static void ReadCmdArgs(Inilist &ini, Arglist &Args)
|
|
{
|
|
for (Arglist::iterator pp = Args.begin(), end = Args.end(); pp != end; ++pp)
|
|
{
|
|
const char *p = pp->c_str();
|
|
// System Options
|
|
|
|
if (!d_stricmp(p, "-help") || !d_stricmp(p, "-h") || !d_stricmp(p, "-?") || !d_stricmp(p, "?"))
|
|
CGameArg.SysShowCmdHelp = true;
|
|
else if (!d_stricmp(p, "-nonicefps"))
|
|
CGameArg.SysNoNiceFPS = true;
|
|
else if (!d_stricmp(p, "-maxfps"))
|
|
CGameArg.SysMaxFPS = arg_integer(pp, end);
|
|
else if (!d_stricmp(p, "-hogdir"))
|
|
CGameArg.SysHogDir = arg_string(pp, end);
|
|
#if PHYSFS_VER_MAJOR >= 2
|
|
else if (!d_stricmp(p, "-add-missions-dir"))
|
|
CGameArg.SysMissionDir = arg_string(pp, end);
|
|
#endif
|
|
else if (!d_stricmp(p, "-nohogdir"))
|
|
{
|
|
/* No effect if no DXX_SHAREPATH. Ignore it so that players can
|
|
* pass it via a cross-platform ini.
|
|
*/
|
|
#if DXX_USE_SHAREPATH
|
|
CGameArg.SysNoHogDir = true;
|
|
#endif
|
|
}
|
|
else if (!d_stricmp(p, "-use_players_dir"))
|
|
CGameArg.SysUsePlayersDir = static_cast<int8_t>(- (sizeof(PLAYER_DIRECTORY_TEXT) - 1));
|
|
else if (!d_stricmp(p, "-lowmem"))
|
|
CGameArg.SysLowMem = true;
|
|
else if (!d_stricmp(p, "-pilot"))
|
|
CGameArg.SysPilot = arg_string(pp, end);
|
|
else if (!d_stricmp(p, "-record-demo-format"))
|
|
CGameArg.SysRecordDemoNameTemplate = arg_string(pp, end);
|
|
else if (!d_stricmp(p, "-auto-record-demo"))
|
|
CGameArg.SysAutoRecordDemo = true;
|
|
else if (!d_stricmp(p, "-window"))
|
|
CGameArg.SysWindow = true;
|
|
else if (!d_stricmp(p, "-noborders"))
|
|
CGameArg.SysNoBorders = true;
|
|
else if (!d_stricmp(p, "-notitles"))
|
|
CGameArg.SysNoTitles = true;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
else if (!d_stricmp(p, "-nomovies"))
|
|
GameArg.SysNoMovies = 1;
|
|
#endif
|
|
else if (!d_stricmp(p, "-autodemo"))
|
|
CGameArg.SysAutoDemo = true;
|
|
|
|
// Control Options
|
|
|
|
else if (!d_stricmp(p, "-nocursor"))
|
|
CGameArg.CtlNoCursor = true;
|
|
else if (!d_stricmp(p, "-nomouse"))
|
|
CGameArg.CtlNoMouse = true;
|
|
else if (!d_stricmp(p, "-nojoystick"))
|
|
{
|
|
#if DXX_MAX_JOYSTICKS
|
|
CGameArg.CtlNoJoystick = 1;
|
|
#endif
|
|
}
|
|
else if (!d_stricmp(p, "-nostickykeys"))
|
|
CGameArg.CtlNoStickyKeys = true;
|
|
|
|
// Sound Options
|
|
|
|
else if (!d_stricmp(p, "-nosound"))
|
|
CGameArg.SndNoSound = 1;
|
|
else if (!d_stricmp(p, "-nomusic"))
|
|
CGameArg.SndNoMusic = true;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
else if (!d_stricmp(p, "-sound11k"))
|
|
GameArg.SndDigiSampleRate = sound_sample_rate::_11k;
|
|
#endif
|
|
else if (!d_stricmp(p, "-nosdlmixer"))
|
|
{
|
|
#if DXX_USE_SDLMIXER
|
|
CGameArg.SndDisableSdlMixer = true;
|
|
#endif
|
|
}
|
|
else if (!strncmp(p, "-sdlmixer-resampler=", 20))
|
|
{
|
|
#if DXX_USE_SDLMIXER
|
|
#if DXX_FEATURE_EXTERNAL_RESAMPLER_SDL_NATIVE
|
|
if (!strcmp(&p[20], "sdl-native"))
|
|
CGameArg.SndMixerMethod = digi_mixer_method::sdl_native;
|
|
else
|
|
#endif
|
|
#if DXX_FEATURE_INTERNAL_RESAMPLER_EMULATE_SDL1
|
|
if (!strcmp(&p[20], "emulate-sdl1"))
|
|
CGameArg.SndMixerMethod = digi_mixer_method::emulate_sdl1;
|
|
else
|
|
#endif
|
|
#if DXX_FEATURE_INTERNAL_RESAMPLER_EMULATE_SOUNDBLASTER16
|
|
if (!strcmp(&p[20], "emulate-soundblaster16"))
|
|
CGameArg.SndMixerMethod = digi_mixer_method::emulate_soundblaster16;
|
|
else
|
|
#endif
|
|
throw unhandled_argument(std::move(*pp));
|
|
#endif
|
|
}
|
|
|
|
// Graphics Options
|
|
|
|
else if (!d_stricmp(p, "-lowresfont"))
|
|
CGameArg.GfxSkipHiresFNT = true;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
else if (!d_stricmp(p, "-lowresgraphics"))
|
|
GameArg.GfxSkipHiresGFX = 1;
|
|
else if (!d_stricmp(p, "-lowresmovies"))
|
|
GameArg.GfxSkipHiresMovie = 1;
|
|
#endif
|
|
#if DXX_USE_OGL
|
|
// OpenGL Options
|
|
|
|
else if (!d_stricmp(p, "-gl_fixedfont"))
|
|
CGameArg.OglFixedFont = true;
|
|
else if (!d_stricmp(p, "-gl_syncmethod"))
|
|
CGameArg.OglSyncMethod = arg_enum<SyncGLMethod>(pp, end);
|
|
else if (!d_stricmp(p, "-gl_syncwait"))
|
|
CGameArg.OglSyncWait = arg_integer(pp, end);
|
|
else if (!d_stricmp(p, "-gl_darkedges"))
|
|
CGameArg.OglDarkEdges = true;
|
|
#if DXX_USE_STEREOSCOPIC_RENDER
|
|
else if (!d_stricmp(p, "-gl_stereo"))
|
|
CGameArg.OglStereo = true;
|
|
else if (!d_stricmp(p, "-gl_stereoview"))
|
|
CGameArg.OglStereoView = arg_integer(pp, end);
|
|
#endif
|
|
#endif
|
|
|
|
// Multiplayer Options
|
|
|
|
#if DXX_USE_UDP
|
|
else if (!d_stricmp(p, "-udp_hostaddr"))
|
|
CGameArg.MplUdpHostAddr = arg_string(pp, end);
|
|
else if (!d_stricmp(p, "-udp_hostport"))
|
|
/* Peers use -udp_myport to change, so peer cannot set a
|
|
* privileged port.
|
|
*/
|
|
arg_port_number(pp, end, CGameArg.MplUdpHostPort, false);
|
|
else if (!d_stricmp(p, "-udp_myport"))
|
|
{
|
|
arg_port_number(pp, end, CGameArg.MplUdpMyPort, false);
|
|
}
|
|
else if (!d_stricmp(p, "-no-tracker"))
|
|
{
|
|
/* Always recognized. No-op if tracker support compiled
|
|
* out. */
|
|
#if DXX_USE_TRACKER
|
|
CGameArg.MplTrackerAddr.clear();
|
|
#endif
|
|
}
|
|
#if DXX_USE_TRACKER
|
|
else if (!d_stricmp(p, "-tracker_hostaddr"))
|
|
{
|
|
CGameArg.MplTrackerAddr = arg_string(pp, end);
|
|
}
|
|
else if (!d_stricmp(p, "-tracker_hostport"))
|
|
arg_port_number(pp, end, CGameArg.MplTrackerPort, true);
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
else if (!d_stricmp(p, "-nobm"))
|
|
GameArg.EdiNoBm = 1;
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
#if DXX_USE_EDITOR
|
|
// Editor Options
|
|
|
|
else if (!d_stricmp(p, "-autoload"))
|
|
GameArg.EdiAutoLoad = arg_string(pp, end);
|
|
else if (!d_stricmp(p, "-macdata"))
|
|
GameArg.EdiMacData = 1;
|
|
else if (!d_stricmp(p, "-hoarddata"))
|
|
GameArg.EdiSaveHoardData = 1;
|
|
#endif
|
|
#endif
|
|
|
|
// Debug Options
|
|
|
|
else if (!d_stricmp(p, "-debug"))
|
|
{
|
|
if (CGameArg.DbgVerbose < CON_DEBUG)
|
|
CGameArg.DbgVerbose = CON_DEBUG;
|
|
}
|
|
else if (!d_stricmp(p, "-verbose"))
|
|
{
|
|
if (CGameArg.DbgVerbose < CON_VERBOSE)
|
|
CGameArg.DbgVerbose = CON_VERBOSE;
|
|
}
|
|
|
|
else if (!d_stricmp(p, "-no-grab"))
|
|
CGameArg.DbgForbidConsoleGrab = true;
|
|
else if (!d_stricmp(p, "-safelog"))
|
|
CGameArg.DbgSafelog = true;
|
|
else if (!d_stricmp(p, "-norun"))
|
|
CGameArg.DbgNoRun = true;
|
|
else if (!d_stricmp(p, "-renderstats"))
|
|
CGameArg.DbgRenderStats = true;
|
|
else if (!d_stricmp(p, "-text"))
|
|
CGameArg.DbgAltTex = arg_string(pp, end);
|
|
else if (!d_stricmp(p, "-showmeminfo"))
|
|
CGameArg.DbgShowMemInfo = 1;
|
|
else if (!d_stricmp(p, "-nodoublebuffer"))
|
|
CGameArg.DbgNoDoubleBuffer = true;
|
|
else if (!d_stricmp(p, "-bigpig"))
|
|
CGameArg.DbgNoCompressPigBitmap = true;
|
|
else if (!d_stricmp(p, "-16bpp"))
|
|
CGameArg.DbgBpp = 16;
|
|
|
|
#if DXX_USE_OGL
|
|
else if (!d_stricmp(p, "-gl_oldtexmerge"))
|
|
CGameArg.DbgUseOldTextureMerge = true;
|
|
else if (!d_stricmp(p, "-gl_intensity4_ok"))
|
|
CGameArg.DbgGlIntensity4Ok = arg_integer(pp, end);
|
|
else if (!d_stricmp(p, "-gl_luminance4_alpha4_ok"))
|
|
CGameArg.DbgGlLuminance4Alpha4Ok = arg_integer(pp, end);
|
|
else if (!d_stricmp(p, "-gl_rgba2_ok"))
|
|
CGameArg.DbgGlRGBA2Ok = arg_integer(pp, end);
|
|
else if (!d_stricmp(p, "-gl_readpixels_ok"))
|
|
CGameArg.DbgGlReadPixelsOk = arg_integer(pp, end);
|
|
else if (!d_stricmp(p, "-gl_gettexlevelparam_ok"))
|
|
CGameArg.DbgGlGetTexLevelParamOk = arg_integer(pp, end);
|
|
#else
|
|
else if (!d_stricmp(p, "-tmap"))
|
|
CGameArg.DbgTexMap = arg_string(pp, end);
|
|
else if (!d_stricmp(p, "-hwsurface"))
|
|
CGameArg.DbgSdlHWSurface = true;
|
|
else if (!d_stricmp(p, "-asyncblit"))
|
|
CGameArg.DbgSdlASyncBlit = true;
|
|
#endif
|
|
else if (!d_stricmp(p, "-ini"))
|
|
{
|
|
ini.emplace_back(arg_string(pp, end));
|
|
if (ini.size() > 10)
|
|
throw nesting_depth_exceeded();
|
|
ReadIniArgs(ini);
|
|
}
|
|
#if defined(__APPLE__) && defined(__MACH__)
|
|
else if (!strncmp(p, "-psn", 4))
|
|
{
|
|
//do nothing/gobble it up
|
|
}
|
|
#endif
|
|
else
|
|
throw unhandled_argument(std::move(*pp));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace dcx {
|
|
|
|
namespace {
|
|
|
|
static void PostProcessGameArg()
|
|
{
|
|
if (CGameArg.SysMaxFPS < MINIMUM_FPS)
|
|
CGameArg.SysMaxFPS = MINIMUM_FPS;
|
|
else if (CGameArg.SysMaxFPS > MAXIMUM_FPS)
|
|
CGameArg.SysMaxFPS = MAXIMUM_FPS;
|
|
#if PHYSFS_VER_MAJOR >= 2
|
|
if (!CGameArg.SysMissionDir.empty())
|
|
PHYSFS_mount(CGameArg.SysMissionDir.c_str(), MISSION_DIR, 1);
|
|
#endif
|
|
|
|
#if SDL_MAJOR_VERSION == 1
|
|
static char sdl_disable_lock_keys[] = "SDL_DISABLE_LOCK_KEYS=0";
|
|
if (CGameArg.CtlNoStickyKeys) // Must happen before SDL_Init!
|
|
sdl_disable_lock_keys[sizeof(sdl_disable_lock_keys) - 2] = '1';
|
|
SDL_putenv(sdl_disable_lock_keys);
|
|
#endif
|
|
}
|
|
|
|
static std::string ConstructIniStackExplanation(const Inilist &ini)
|
|
{
|
|
Inilist::const_reverse_iterator i = ini.rbegin(), e = ini.rend();
|
|
if (i == e)
|
|
return " while processing <command line>";
|
|
std::string result;
|
|
result.reserve(ini.size() * 128);
|
|
result += " while processing \"";
|
|
for (;;)
|
|
{
|
|
result += i->filename;
|
|
if (++ i == e)
|
|
return result += "\"";
|
|
result += "\"\n included from \"";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace dsx {
|
|
|
|
bool InitArgs( int argc,char **argv )
|
|
{
|
|
InitGameArg();
|
|
|
|
Inilist ini;
|
|
try {
|
|
{
|
|
assert(ini.empty());
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
const auto INI_FILENAME = "d1x.ini";
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
const auto INI_FILENAME = "d2x.ini";
|
|
#endif
|
|
ini.emplace_back(INI_FILENAME);
|
|
ReadIniArgs(ini);
|
|
}
|
|
{
|
|
Arglist Args;
|
|
Args.reserve(argc);
|
|
range_for (auto &i, unchecked_partial_range(argv, 1u, static_cast<unsigned>(argc)))
|
|
Args.push_back(i);
|
|
ReadCmdArgs(ini, Args);
|
|
}
|
|
PostProcessGameArg();
|
|
return true;
|
|
} catch(const missing_parameter& e) {
|
|
UserError("Missing parameter for argument \"%s\"%s", e.arg.c_str(), ConstructIniStackExplanation(ini).c_str());
|
|
} catch(const unhandled_argument& e) {
|
|
UserError("Unhandled argument \"%s\"%s", e.arg.c_str(), ConstructIniStackExplanation(ini).c_str());
|
|
} catch(const conversion_failure& e) {
|
|
UserError("Failed to convert argument \"%s\" parameter \"%s\"%s", e.arg.c_str(), e.value.c_str(), ConstructIniStackExplanation(ini).c_str());
|
|
} catch(const nesting_depth_exceeded &) {
|
|
UserError("Nesting depth exceeded%s", ConstructIniStackExplanation(ini).c_str());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|