dxx-rebirth/similar/misc/args.cpp

470 lines
12 KiB
C++
Raw Normal View History

2006-03-20 17:12:09 +00:00
/*
* This file is part of the DXX-Rebirth project <http://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.
*/
2006-03-20 17:12:09 +00:00
/*
*
* Functions for accessing arguments.
*
*/
2013-11-10 00:41:38 +00:00
#include <string>
#include <vector>
2006-03-20 17:12:09 +00:00
#include <stdlib.h>
#include <string.h>
2013-06-30 02:22:56 +00:00
#include <SDL_stdinc.h>
2006-03-20 17:12:09 +00:00
#include "physfsx.h"
#include "args.h"
#include "u_mem.h"
#include "strutil.h"
#include "digi.h"
#include "game.h"
#include "gauges.h"
#include "console.h"
#ifdef USE_UDP
#include "net_udp.h"
#endif
2015-03-22 18:49:20 +00:00
#include "compiler-range_for.h"
#include "partial_range.h"
dcx::CArg dcx::CGameArg;
2015-12-13 18:00:49 +00:00
namespace dsx {
#define MAX_ARGS 1000
#if defined(DXX_BUILD_DESCENT_I)
#define INI_FILENAME "d1x.ini"
#elif defined(DXX_BUILD_DESCENT_II)
#define INI_FILENAME "d2x.ini"
#endif
2013-11-10 00:41:38 +00:00
typedef std::vector<std::string> Arglist;
2006-03-20 17:12:09 +00:00
2015-03-22 18:49:21 +00:00
class ini_entry
{
std::string m_filename;
public:
ini_entry(std::string &&f) :
m_filename(std::move(f))
{
}
const std::string &filename() const
{
return m_filename;
}
};
typedef std::vector<ini_entry> Inilist;
2006-03-20 17:12:09 +00:00
2013-11-10 18:31:52 +00:00
class argument_exception
{
public:
2015-03-22 18:49:21 +00:00
const std::string arg;
argument_exception(std::string &&a) :
arg(std::move(a))
{
}
2013-11-10 18:31:52 +00:00
};
2013-11-10 18:31:52 +00:00
class missing_parameter : public argument_exception
2006-03-20 17:12:09 +00:00
{
2013-11-10 18:31:52 +00:00
public:
2015-03-22 18:49:21 +00:00
missing_parameter(std::string &&a) :
argument_exception(std::move(a))
{
}
2013-11-10 18:31:52 +00:00
};
2006-03-20 17:12:09 +00:00
2013-11-10 18:31:52 +00:00
class unhandled_argument : public argument_exception
{
public:
2015-03-22 18:49:21 +00:00
unhandled_argument(std::string &&a) :
argument_exception(std::move(a))
{
}
2013-11-10 18:31:52 +00:00
};
class conversion_failure : public argument_exception
{
public:
2015-03-22 18:49:21 +00:00
const std::string value;
conversion_failure(std::string &&a, std::string &&v) :
argument_exception(std::move(a)), value(std::move(v))
{
}
2013-11-10 18:31:52 +00:00
};
2015-03-22 18:49:21 +00:00
class nesting_depth_exceeded
{
2013-11-10 18:31:52 +00:00
};
Arg GameArg;
2006-03-20 17:12:09 +00:00
2015-03-22 18:49:21 +00:00
static void ReadCmdArgs(Inilist &ini, Arglist &Args);
static void AppendIniArgs(const char *filename, Arglist &Args)
{
2015-03-22 18:49:21 +00:00
if (auto f = PHYSFSX_openReadBuffered(filename))
{
2014-09-07 19:48:10 +00:00
PHYSFSX_gets_line_t<1024> line;
while (Args.size() < MAX_ARGS && PHYSFSX_fgets(line, f))
{
2013-08-03 15:50:56 +00:00
static const char separator[] = " ";
for(char *token = strtok(line, separator); token != NULL; token = strtok(NULL, separator))
2014-05-24 23:03:19 +00:00
{
if (*token == ';')
break;
2013-11-10 00:41:38 +00:00
Args.push_back(token);
2014-05-24 23:03:19 +00:00
}
}
}
}
2015-03-22 18:49:20 +00:00
static std::string &&arg_string(Arglist::iterator &pp, Arglist::const_iterator end)
2013-11-10 18:31:52 +00:00
{
2015-03-22 18:49:20 +00:00
auto arg = pp;
2013-11-10 18:31:52 +00:00
if (++pp == end)
2015-03-22 18:49:21 +00:00
throw missing_parameter(std::move(*arg));
2015-03-22 18:49:20 +00:00
return std::move(*pp);
}
2013-11-10 18:31:52 +00:00
2015-03-22 18:49:20 +00:00
static long arg_integer(Arglist::iterator &pp, Arglist::const_iterator end)
2013-11-10 18:31:52 +00:00
{
2015-03-22 18:49:20 +00:00
auto arg = pp;
auto &&value = arg_string(pp, end);
2013-11-10 18:31:52 +00:00
char *p;
2015-03-22 18:49:20 +00:00
auto i = strtol(value.c_str(), &p, 10);
2013-11-10 18:31:52 +00:00
if (*p)
2015-03-22 18:49:21 +00:00
throw conversion_failure(std::move(*arg), std::move(value));
2013-11-10 18:31:52 +00:00
return i;
}
template<typename E> E arg_enum(Arglist::iterator &pp, Arglist::const_iterator end)
{
return static_cast<E>(arg_integer(pp, end));
}
2015-03-22 18:49:20 +00:00
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;
}
2015-03-22 18:49:21 +00:00
static void InitGameArg()
{
CGameArg.SysMaxFPS = MAXIMUM_FPS;
2013-11-10 18:31:52 +00:00
#if defined(DXX_BUILD_DESCENT_II)
GameArg.SndDigiSampleRate = SAMPLE_RATE_22K;
#endif
#ifdef USE_UDP
2015-12-24 04:01:27 +00:00
CGameArg.MplUdpHostAddr = UDP_MANUAL_ADDR_DEFAULT;
2013-11-10 18:31:52 +00:00
#ifdef USE_TRACKER
GameArg.MplTrackerAddr = TRACKER_ADDR_DEFAULT;
2015-12-24 04:01:29 +00:00
CGameArg.MplTrackerPort = TRACKER_PORT_DEFAULT;
2013-11-10 18:31:52 +00:00
#endif
#endif
2015-10-18 21:01:21 +00:00
CGameArg.DbgVerbose = CON_NORMAL;
2015-12-18 04:08:23 +00:00
CGameArg.DbgBpp = 32;
2013-11-10 18:31:52 +00:00
#ifdef OGL
2015-12-24 04:01:27 +00:00
CGameArg.OglSyncMethod = OGL_SYNC_METHOD_DEFAULT;
2015-12-24 04:01:27 +00:00
CGameArg.OglSyncWait = OGL_SYNC_WAIT_DEFAULT;
2015-12-18 04:08:23 +00:00
CGameArg.DbgGlIntensity4Ok = true;
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlLuminance4Alpha4Ok = true;
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlRGBA2Ok = true;
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlReadPixelsOk = true;
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlGetTexLevelParamOk = true;
2013-11-10 18:31:52 +00:00
#endif
2015-03-22 18:49:21 +00:00
}
static void ReadIniArgs(Inilist &ini)
{
Arglist Args;
AppendIniArgs(ini.back().filename().c_str(), Args);
ReadCmdArgs(ini, Args);
}
static void ReadCmdArgs(Inilist &ini, Arglist &Args)
{
2015-03-22 18:49:20 +00:00
for (Arglist::iterator pp = Args.begin(), end = Args.end(); pp != end; ++pp)
2013-11-10 18:31:52 +00:00
{
const char *p = pp->c_str();
// System Options
2013-11-10 18:31:52 +00:00
if (!d_stricmp(p, "-help") || !d_stricmp(p, "-h") || !d_stricmp(p, "-?") || !d_stricmp(p, "?"))
CGameArg.SysShowCmdHelp = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nonicefps"))
2015-12-24 04:01:26 +00:00
CGameArg.SysNoNiceFPS = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-maxfps"))
CGameArg.SysMaxFPS = arg_integer(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-hogdir"))
2015-12-24 04:01:26 +00:00
CGameArg.SysHogDir = arg_string(pp, end);
2015-10-11 22:21:00 +00:00
#if PHYSFS_VER_MAJOR >= 2
else if (!d_stricmp(p, "-add-missions-dir"))
CGameArg.SysMissionDir = arg_string(pp, end);
2015-10-11 22:21:00 +00:00
#endif
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nohogdir"))
2015-09-22 02:28:38 +00:00
{
/* No effect on non-Unix. Ignore it so that players can
* pass it via a cross-platform ini.
*/
#if defined(__unix__)
CGameArg.SysNoHogDir = true;
2015-09-22 02:28:38 +00:00
#endif
}
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-use_players_dir"))
2015-12-24 04:01:26 +00:00
CGameArg.SysUsePlayersDir = 1;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-lowmem"))
2015-12-24 04:01:26 +00:00
CGameArg.SysLowMem = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-pilot"))
2015-12-24 04:01:26 +00:00
CGameArg.SysPilot = arg_string(pp, end);
else if (!d_stricmp(p, "-record-demo-format"))
2015-12-24 04:01:26 +00:00
CGameArg.SysRecordDemoNameTemplate = arg_string(pp, end);
else if (!d_stricmp(p, "-auto-record-demo"))
2015-12-24 04:01:27 +00:00
CGameArg.SysAutoRecordDemo = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-window"))
2015-12-24 04:01:27 +00:00
CGameArg.SysWindow = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-noborders"))
2015-12-24 04:01:27 +00:00
CGameArg.SysNoBorders = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-notitles"))
2015-12-24 04:01:27 +00:00
CGameArg.SysNoTitles = true;
2015-10-09 02:46:09 +00:00
#if defined(DXX_BUILD_DESCENT_II)
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nomovies"))
GameArg.SysNoMovies = 1;
#endif
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-autodemo"))
2015-12-24 04:01:27 +00:00
CGameArg.SysAutoDemo = true;
2007-10-01 20:42:35 +00:00
// Control Options
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nocursor"))
2015-07-18 21:01:56 +00:00
CGameArg.CtlNoCursor = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nomouse"))
2015-07-18 21:01:56 +00:00
CGameArg.CtlNoMouse = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nojoystick"))
2015-11-26 02:56:55 +00:00
{
#if MAX_JOYSTICKS
CGameArg.CtlNoJoystick = 1;
#endif
}
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nostickykeys"))
2015-07-18 21:01:56 +00:00
CGameArg.CtlNoStickyKeys = true;
// Sound Options
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nosound"))
2015-11-24 04:05:36 +00:00
CGameArg.SndNoSound = 1;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nomusic"))
2015-12-24 04:01:26 +00:00
CGameArg.SndNoMusic = true;
#if defined(DXX_BUILD_DESCENT_II)
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-sound11k"))
GameArg.SndDigiSampleRate = SAMPLE_RATE_11K;
#endif
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nosdlmixer"))
{
#ifdef USE_SDLMIXER
2015-11-24 04:05:36 +00:00
CGameArg.SndDisableSdlMixer = true;
#endif
}
// Graphics Options
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-lowresfont"))
2015-12-24 04:01:26 +00:00
CGameArg.GfxSkipHiresFNT = true;
#if defined(DXX_BUILD_DESCENT_II)
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-lowresgraphics"))
GameArg.GfxSkipHiresGFX = 1;
else if (!d_stricmp(p, "-lowresmovies"))
GameArg.GfxSkipHiresMovie = 1;
#endif
#ifdef OGL
// OpenGL Options
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_fixedfont"))
2015-12-24 04:01:27 +00:00
CGameArg.OglFixedFont = true;
else if (!d_stricmp(p, "-gl_syncmethod"))
2015-12-24 04:01:27 +00:00
CGameArg.OglSyncMethod = arg_enum<SyncGLMethod>(pp, end);
else if (!d_stricmp(p, "-gl_syncwait"))
2015-12-24 04:01:27 +00:00
CGameArg.OglSyncWait = arg_integer(pp, end);
#endif
// Multiplayer Options
#ifdef USE_UDP
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-udp_hostaddr"))
2015-12-24 04:01:27 +00:00
CGameArg.MplUdpHostAddr = arg_string(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-udp_hostport"))
2015-01-25 05:32:44 +00:00
/* Peers use -udp_myport to change, so peer cannot set a
* privileged port.
*/
2015-12-24 04:01:28 +00:00
arg_port_number(pp, end, CGameArg.MplUdpHostPort, false);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-udp_myport"))
{
2015-12-24 04:01:29 +00:00
arg_port_number(pp, end, CGameArg.MplUdpMyPort, false);
}
else if (!d_stricmp(p, "-no-tracker"))
{
/* Always recognized. No-op if tracker support compiled
* out. */
#ifdef USE_TRACKER
2015-03-22 18:49:21 +00:00
GameArg.MplTrackerAddr.clear();
#endif
}
#ifdef USE_TRACKER
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-tracker_hostaddr"))
{
2013-11-10 18:31:52 +00:00
GameArg.MplTrackerAddr = arg_string(pp, end);
}
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-tracker_hostport"))
2015-12-24 04:01:29 +00:00
arg_port_number(pp, end, CGameArg.MplTrackerPort, true);
#endif
#endif
#if defined(DXX_BUILD_DESCENT_I)
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nobm"))
GameArg.EdiNoBm = 1;
#elif defined(DXX_BUILD_DESCENT_II)
#ifdef EDITOR
// Editor Options
2013-11-10 18:31:52 +00:00
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
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-debug"))
2015-10-18 21:01:21 +00:00
CGameArg.DbgVerbose = CON_DEBUG;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-verbose"))
2015-10-18 21:01:21 +00:00
CGameArg.DbgVerbose = CON_VERBOSE;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-no-grab"))
2015-07-18 21:01:56 +00:00
CGameArg.DbgForbidConsoleGrab = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-safelog"))
2015-10-18 21:01:21 +00:00
CGameArg.DbgSafelog = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-norun"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgNoRun = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-renderstats"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgRenderStats = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-text"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgAltTex = arg_string(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-tmap"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgTexMap = arg_string(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-showmeminfo"))
CGameArg.DbgShowMemInfo = 1;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-nodoublebuffer"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgNoDoubleBuffer = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-bigpig"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgNoCompressPigBitmap = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-16bpp"))
2015-12-18 04:08:23 +00:00
CGameArg.DbgBpp = 16;
2007-07-22 20:40:39 +00:00
2007-07-22 01:34:00 +00:00
#ifdef OGL
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_oldtexmerge"))
2015-12-18 04:08:24 +00:00
CGameArg.DbgUseOldTextureMerge = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_intensity4_ok"))
2015-12-18 04:08:23 +00:00
CGameArg.DbgGlIntensity4Ok = arg_integer(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_luminance4_alpha4_ok"))
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlLuminance4Alpha4Ok = arg_integer(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_rgba2_ok"))
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlRGBA2Ok = arg_integer(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_readpixels_ok"))
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlReadPixelsOk = arg_integer(pp, end);
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-gl_gettexlevelparam_ok"))
2015-12-18 04:08:24 +00:00
CGameArg.DbgGlGetTexLevelParamOk = arg_integer(pp, end);
#else
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-hwsurface"))
2015-12-24 04:01:27 +00:00
CGameArg.DbgSdlHWSurface = true;
2013-11-10 18:31:52 +00:00
else if (!d_stricmp(p, "-asyncblit"))
2015-12-24 04:01:28 +00:00
CGameArg.DbgSdlASyncBlit = true;
2007-07-22 01:34:00 +00:00
#endif
2015-03-22 18:49:21 +00:00
else if (!d_stricmp(p, "-ini"))
{
ini.emplace_back(arg_string(pp, end));
if (ini.size() > 10)
throw nesting_depth_exceeded();
ReadIniArgs(ini);
ini.pop_back();
}
2013-11-10 18:31:52 +00:00
else
2015-03-22 18:49:21 +00:00
throw unhandled_argument(std::move(*pp));
2013-11-10 18:31:52 +00:00
}
2015-03-22 18:49:21 +00:00
}
2013-11-10 18:31:52 +00:00
2015-03-22 18:49:21 +00:00
static void PostProcessGameArg()
{
if (CGameArg.SysMaxFPS < MINIMUM_FPS)
CGameArg.SysMaxFPS = MINIMUM_FPS;
else if (CGameArg.SysMaxFPS > MAXIMUM_FPS)
CGameArg.SysMaxFPS = MAXIMUM_FPS;
2015-10-11 22:21:00 +00:00
#if PHYSFS_VER_MAJOR >= 2
if (!CGameArg.SysMissionDir.empty())
PHYSFS_mount(CGameArg.SysMissionDir.c_str(), MISSION_DIR, 1);
2015-10-11 22:21:00 +00:00
#endif
2013-11-10 18:31:52 +00:00
static char sdl_disable_lock_keys[] = "SDL_DISABLE_LOCK_KEYS=0";
2015-07-18 21:01:56 +00:00
if (CGameArg.CtlNoStickyKeys) // Must happen before SDL_Init!
2013-11-10 18:31:52 +00:00
sdl_disable_lock_keys[sizeof(sdl_disable_lock_keys) - 1] = '1';
SDL_putenv(sdl_disable_lock_keys);
}
2015-03-22 18:49:21 +00:00
static std::string ConstructIniStackExplanation(const Inilist &ini)
2006-03-20 17:12:09 +00:00
{
2015-03-22 18:49:21 +00:00
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 \"";
}
2006-03-20 17:12:09 +00:00
}
bool InitArgs( int argc,char **argv )
2006-03-20 17:12:09 +00:00
{
2015-03-22 18:49:21 +00:00
InitGameArg();
2006-03-20 17:12:09 +00:00
2015-03-22 18:49:21 +00:00
Inilist ini;
2013-11-10 18:31:52 +00:00
try {
2015-03-22 18:49:21 +00:00
{
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);
}
{
assert(ini.empty());
ini.emplace_back(INI_FILENAME);
ReadIniArgs(ini);
}
PostProcessGameArg();
return true;
2013-11-10 18:31:52 +00:00
} catch(const missing_parameter& e) {
UserError("Missing parameter for argument \"%s\"%s", e.arg.c_str(), ConstructIniStackExplanation(ini).c_str());
2013-11-10 18:31:52 +00:00
} catch(const unhandled_argument& e) {
UserError("Unhandled argument \"%s\"%s", e.arg.c_str(), ConstructIniStackExplanation(ini).c_str());
2013-11-10 18:31:52 +00:00
} 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());
2015-03-22 18:49:21 +00:00
} catch(const nesting_depth_exceeded &) {
UserError("Nesting depth exceeded%s", ConstructIniStackExplanation(ini).c_str());
2013-11-10 18:31:52 +00:00
}
return false;
2006-03-20 17:12:09 +00:00
}
}