mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-11 15:21:51 +00:00
Try to sanitize the exec+pullin mess
- Old mess: * Execute autoexec files right away. * Execute -exec files right away. * Execute command line commands right away. - If, during any of the above, an unknown command or a set of an unknown variable is encountered, store it for later. - Pullin commands are directly executed and add to the list of files to load. * Do a little setup, including parsing CVARINFOs. * Retry saved commands in case CVARINFO added a cvar they refer to. - New, less messy, mess: * Parse autoexec files into an array. * Parse -exec files. * Parse command line commands. - During all of the above, exec commands are also parsed into the array immediately rather than being saved for execution later. - Pullin commands are parsed into a different array. The pullin command doesn't actually do anything directly anymore. * Add all the pullin files to the list of files to load. * Do a little setup, including parsing CVARINFOs. * Execute every command that was parsed in the preceding steps.
This commit is contained in:
parent
e4cadc90a5
commit
6428b399d6
4 changed files with 178 additions and 165 deletions
|
@ -450,11 +450,10 @@ CCMD (exec)
|
||||||
|
|
||||||
for (int i = 1; i < argv.argc(); ++i)
|
for (int i = 1; i < argv.argc(); ++i)
|
||||||
{
|
{
|
||||||
switch (C_ExecFile (argv[i], gamestate == GS_STARTUP))
|
if (!C_ExecFile(argv[i]))
|
||||||
{
|
{
|
||||||
case 1: Printf ("Could not open \"%s\"\n", argv[1]); break;
|
Printf ("Could not exec \"%s\"\n", argv[i]);
|
||||||
case 2: Printf ("Error parsing \"%s\"\n", argv[1]); break;
|
break;
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,9 +185,6 @@ static const char *KeyConfCommands[] =
|
||||||
"clearplayerclasses"
|
"clearplayerclasses"
|
||||||
};
|
};
|
||||||
|
|
||||||
static TArray<FString> StoredStartupSets;
|
|
||||||
static bool RunningStoredStartups;
|
|
||||||
|
|
||||||
// CODE --------------------------------------------------------------------
|
// CODE --------------------------------------------------------------------
|
||||||
|
|
||||||
IMPLEMENT_CLASS (DWaitingCommand)
|
IMPLEMENT_CLASS (DWaitingCommand)
|
||||||
|
@ -540,18 +537,6 @@ void ResetButtonStates ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void C_ExecStoredSets()
|
|
||||||
{
|
|
||||||
assert(!RunningStoredStartups);
|
|
||||||
RunningStoredStartups = true;
|
|
||||||
for (unsigned i = 0; i < StoredStartupSets.Size(); ++i)
|
|
||||||
{
|
|
||||||
C_DoCommand(StoredStartupSets[i]);
|
|
||||||
}
|
|
||||||
StoredStartupSets.Clear();
|
|
||||||
RunningStoredStartups = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void C_DoCommand (const char *cmd, int keynum)
|
void C_DoCommand (const char *cmd, int keynum)
|
||||||
{
|
{
|
||||||
FConsoleCommand *com;
|
FConsoleCommand *com;
|
||||||
|
@ -627,22 +612,7 @@ void C_DoCommand (const char *cmd, int keynum)
|
||||||
|
|
||||||
if ( (com = FindNameInHashTable (Commands, beg, len)) )
|
if ( (com = FindNameInHashTable (Commands, beg, len)) )
|
||||||
{
|
{
|
||||||
if (gamestate == GS_STARTUP && !RunningStoredStartups &&
|
if (gamestate != GS_STARTUP || ParsingKeyConf ||
|
||||||
len == 3 && strnicmp(beg, "set", 3) == 0)
|
|
||||||
{
|
|
||||||
// Save setting of unknown cvars for later, in case a loaded wad has a
|
|
||||||
// CVARINFO that defines it.
|
|
||||||
FCommandLine args(beg);
|
|
||||||
if (args.argc() > 1 && FindCVar(args[1], NULL) == NULL)
|
|
||||||
{
|
|
||||||
StoredStartupSets.Push(beg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
com->Run(args, players[consoleplayer].mo, keynum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (gamestate != GS_STARTUP || ParsingKeyConf ||
|
|
||||||
(len == 3 && strnicmp (beg, "set", 3) == 0) ||
|
(len == 3 && strnicmp (beg, "set", 3) == 0) ||
|
||||||
(len == 7 && strnicmp (beg, "logfile", 7) == 0) ||
|
(len == 7 && strnicmp (beg, "logfile", 7) == 0) ||
|
||||||
(len == 9 && strnicmp (beg, "unbindall", 9) == 0) ||
|
(len == 9 && strnicmp (beg, "unbindall", 9) == 0) ||
|
||||||
|
@ -687,15 +657,7 @@ void C_DoCommand (const char *cmd, int keynum)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // We don't know how to handle this command
|
{ // We don't know how to handle this command
|
||||||
if (gamestate == GS_STARTUP && !RunningStoredStartups)
|
Printf ("Unknown command \"%.*s\"\n", (int)len, beg);
|
||||||
{
|
|
||||||
// Save it for later, in case a CVARINFO defines it.
|
|
||||||
StoredStartupSets.Push(beg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Printf ("Unknown command \"%.*s\"\n", (int)len, beg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1368,7 +1330,7 @@ CCMD (key)
|
||||||
|
|
||||||
// Execute any console commands specified on the command line.
|
// Execute any console commands specified on the command line.
|
||||||
// These all begin with '+' as opposed to '-'.
|
// These all begin with '+' as opposed to '-'.
|
||||||
void C_ExecCmdLineParams ()
|
FExecList *C_ParseCmdLineParams(FExecList *exec)
|
||||||
{
|
{
|
||||||
for (int currArg = 1; currArg < Args->NumArgs(); )
|
for (int currArg = 1; currArg < Args->NumArgs(); )
|
||||||
{
|
{
|
||||||
|
@ -1389,10 +1351,15 @@ void C_ExecCmdLineParams ()
|
||||||
cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
|
cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
|
||||||
if (!cmdString.IsEmpty())
|
if (!cmdString.IsEmpty())
|
||||||
{
|
{
|
||||||
C_DoCommand (&cmdString[1]);
|
if (exec == NULL)
|
||||||
|
{
|
||||||
|
exec = new FExecList;
|
||||||
|
}
|
||||||
|
exec->AddCommand(&cmdString[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return exec;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FConsoleCommand::IsAlias ()
|
bool FConsoleCommand::IsAlias ()
|
||||||
|
@ -1469,28 +1436,60 @@ void FConsoleAlias::SafeDelete ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static BYTE PullinBad = 2;
|
void FExecList::AddCommand(const char *cmd, const char *file)
|
||||||
static const char *PullinFile;
|
{
|
||||||
extern TArray<FString> allwads;
|
// Pullins are special and need to be separated from general commands.
|
||||||
|
// They also turned out to be a really bad idea, since they make things
|
||||||
|
// more complicated. :(
|
||||||
|
if (file != NULL && strnicmp(cmd, "pullin", 6) == 0 && isspace(cmd[6]))
|
||||||
|
{
|
||||||
|
FCommandLine line(cmd);
|
||||||
|
C_SearchForPullins(this, file, line);
|
||||||
|
}
|
||||||
|
// Recursive exec: Parse this file now.
|
||||||
|
else if (strnicmp(cmd, "exec", 4) == 0 && isspace(cmd[4]))
|
||||||
|
{
|
||||||
|
FCommandLine argv(cmd);
|
||||||
|
for (int i = 1; i < argv.argc(); ++i)
|
||||||
|
{
|
||||||
|
C_ParseExecFile(argv[i], this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Commands.Push(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int C_ExecFile (const char *file, bool usePullin)
|
void FExecList::ExecCommands() const
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Commands.Size(); ++i)
|
||||||
|
{
|
||||||
|
AddCommandString(Commands[i].LockBuffer());
|
||||||
|
Commands[i].UnlockBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FExecList::AddPullins(TArray<FString> &wads) const
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < Pullins.Size(); ++i)
|
||||||
|
{
|
||||||
|
D_AddFile(wads, Pullins[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FExecList *C_ParseExecFile(const char *file, FExecList *exec)
|
||||||
{
|
{
|
||||||
FILE *f;
|
FILE *f;
|
||||||
char cmd[4096];
|
char cmd[4096];
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
BYTE pullinSaved = PullinBad;
|
|
||||||
const char *fileSaved = PullinFile;
|
|
||||||
|
|
||||||
if ( (f = fopen (file, "r")) )
|
if ( (f = fopen (file, "r")) )
|
||||||
{
|
{
|
||||||
PullinBad = 1-usePullin;
|
while (fgets(cmd, countof(cmd)-1, f))
|
||||||
PullinFile = file;
|
|
||||||
|
|
||||||
while (fgets (cmd, 4095, f))
|
|
||||||
{
|
{
|
||||||
// Comments begin with //
|
// Comments begin with //
|
||||||
char *stop = cmd + strlen (cmd) - 1;
|
char *stop = cmd + strlen(cmd) - 1;
|
||||||
char *comment = cmd;
|
char *comment = cmd;
|
||||||
int inQuote = 0;
|
int inQuote = 0;
|
||||||
|
|
||||||
|
@ -1517,88 +1516,78 @@ int C_ExecFile (const char *file, bool usePullin)
|
||||||
{ // Comment in middle of line
|
{ // Comment in middle of line
|
||||||
*comment = 0;
|
*comment = 0;
|
||||||
}
|
}
|
||||||
|
if (exec == NULL)
|
||||||
AddCommandString (cmd);
|
{
|
||||||
|
exec = new FExecList;
|
||||||
|
}
|
||||||
|
exec->AddCommand(cmd, file);
|
||||||
}
|
}
|
||||||
if (!feof (f))
|
if (!feof(f))
|
||||||
{
|
{
|
||||||
retval = 2;
|
Printf("Error parsing \"%s\"\n", file);
|
||||||
}
|
}
|
||||||
fclose (f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
retval = 1;
|
Printf ("Could not open \"%s\"\n", file);
|
||||||
|
}
|
||||||
|
return exec;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool C_ExecFile (const char *file)
|
||||||
|
{
|
||||||
|
FExecList *exec = C_ParseExecFile(file, NULL);
|
||||||
|
if (exec != NULL)
|
||||||
|
{
|
||||||
|
exec->ExecCommands();
|
||||||
|
if (exec->Pullins.Size() > 0)
|
||||||
|
{
|
||||||
|
Printf(TEXTCOLOR_BOLD "Notice: Pullin files were ignored.\n");
|
||||||
|
}
|
||||||
|
delete exec;
|
||||||
|
}
|
||||||
|
return exec != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void C_SearchForPullins(FExecList *exec, const char *file, FCommandLine &argv)
|
||||||
|
{
|
||||||
|
const char *lastSlash;
|
||||||
|
|
||||||
|
assert(exec != NULL);
|
||||||
|
assert(file != NULL);
|
||||||
|
#ifdef __unix__
|
||||||
|
lastSlash = strrchr(file, '/');
|
||||||
|
#else
|
||||||
|
const char *lastSlash1, *lastSlash2;
|
||||||
|
|
||||||
|
lastSlash1 = strrchr(file, '/');
|
||||||
|
lastSlash2 = strrchr(file, '\\');
|
||||||
|
lastSlash = MAX(lastSlash1, lastSlash2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (int i = 1; i < argv.argc(); ++i)
|
||||||
|
{
|
||||||
|
// Try looking for the wad in the same directory as the .cfg
|
||||||
|
// before looking for it in the current directory.
|
||||||
|
if (lastSlash != NULL)
|
||||||
|
{
|
||||||
|
FString path(file, (lastSlash - file) + 1);
|
||||||
|
path += argv[i];
|
||||||
|
if (FileExists(path))
|
||||||
|
{
|
||||||
|
exec->Pullins.Push(path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exec->Pullins.Push(argv[i]);
|
||||||
}
|
}
|
||||||
PullinBad = pullinSaved;
|
|
||||||
PullinFile = fileSaved;
|
|
||||||
return retval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CCMD (pullin)
|
CCMD (pullin)
|
||||||
{
|
{
|
||||||
if (PullinBad == 2)
|
// Actual handling for pullin is now completely special-cased above
|
||||||
{
|
Printf (TEXTCOLOR_BOLD "Pullin" TEXTCOLOR_NORMAL " is only valid from .cfg\n"
|
||||||
Printf ("This command is only valid from .cfg\n"
|
"files and only when used at startup.\n");
|
||||||
"files and only when used at startup.\n");
|
|
||||||
}
|
|
||||||
else if (argv.argc() > 1)
|
|
||||||
{
|
|
||||||
const char *lastSlash;
|
|
||||||
|
|
||||||
#ifdef __unix__
|
|
||||||
lastSlash = strrchr (PullinFile, '/');
|
|
||||||
#else
|
|
||||||
const char *lastSlash1, *lastSlash2;
|
|
||||||
|
|
||||||
lastSlash1 = strrchr (PullinFile, '/');
|
|
||||||
lastSlash2 = strrchr (PullinFile, '\\');
|
|
||||||
lastSlash = MAX (lastSlash1, lastSlash2);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (PullinBad)
|
|
||||||
{
|
|
||||||
Printf ("Not loading:");
|
|
||||||
}
|
|
||||||
for (int i = 1; i < argv.argc(); ++i)
|
|
||||||
{
|
|
||||||
if (PullinBad)
|
|
||||||
{
|
|
||||||
Printf (" %s", argv[i]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Try looking for the wad in the same directory as the .cfg
|
|
||||||
// before looking for it in the current directory.
|
|
||||||
char *path = argv[i];
|
|
||||||
|
|
||||||
if (lastSlash != NULL)
|
|
||||||
{
|
|
||||||
size_t pathlen = lastSlash - PullinFile + strlen (argv[i]) + 2;
|
|
||||||
path = new char[pathlen];
|
|
||||||
strncpy (path, PullinFile, (lastSlash - PullinFile) + 1);
|
|
||||||
strcpy (path + (lastSlash - PullinFile) + 1, argv[i]);
|
|
||||||
if (!FileExists (path))
|
|
||||||
{
|
|
||||||
delete[] path;
|
|
||||||
path = argv[i];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FixPathSeperator (path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
D_AddFile (allwads, path);
|
|
||||||
if (path != argv[i])
|
|
||||||
{
|
|
||||||
delete[] path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (PullinBad)
|
|
||||||
{
|
|
||||||
Printf ("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,31 +39,6 @@
|
||||||
class FConfigFile;
|
class FConfigFile;
|
||||||
class APlayerPawn;
|
class APlayerPawn;
|
||||||
|
|
||||||
extern bool CheckCheatmode (bool printmsg = true);
|
|
||||||
|
|
||||||
void C_ExecCmdLineParams ();
|
|
||||||
void C_ExecStoredSets();
|
|
||||||
|
|
||||||
// Add commands to the console as if they were typed in. Can handle wait
|
|
||||||
// and semicolon-separated commands. This function may modify the source
|
|
||||||
// string, but the string will be restored to its original state before
|
|
||||||
// returning. Therefore, commands passed must not be in read-only memory.
|
|
||||||
void AddCommandString (char *text, int keynum=0);
|
|
||||||
|
|
||||||
// Process a single console command. Does not handle wait.
|
|
||||||
void C_DoCommand (const char *cmd, int keynum=0);
|
|
||||||
|
|
||||||
int C_ExecFile (const char *cmd, bool usePullin);
|
|
||||||
|
|
||||||
// Write out alias commands to a file for all current aliases.
|
|
||||||
void C_ArchiveAliases (FConfigFile *f);
|
|
||||||
|
|
||||||
void C_SetAlias (const char *name, const char *cmd);
|
|
||||||
void C_ClearAliases ();
|
|
||||||
|
|
||||||
// build a single string out of multiple strings
|
|
||||||
FString BuildString (int argc, FString *argv);
|
|
||||||
|
|
||||||
// Class that can parse command lines
|
// Class that can parse command lines
|
||||||
class FCommandLine
|
class FCommandLine
|
||||||
{
|
{
|
||||||
|
@ -83,6 +58,44 @@ private:
|
||||||
bool noescapes;
|
bool noescapes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Contains the contents of an exec'ed file
|
||||||
|
struct FExecList
|
||||||
|
{
|
||||||
|
TArray<FString> Commands;
|
||||||
|
TArray<FString> Pullins;
|
||||||
|
|
||||||
|
void AddCommand(const char *cmd, const char *file = NULL);
|
||||||
|
void ExecCommands() const;
|
||||||
|
void AddPullins(TArray<FString> &wads) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern bool CheckCheatmode (bool printmsg = true);
|
||||||
|
|
||||||
|
FExecList *C_ParseCmdLineParams(FExecList *exec);
|
||||||
|
|
||||||
|
// Add commands to the console as if they were typed in. Can handle wait
|
||||||
|
// and semicolon-separated commands. This function may modify the source
|
||||||
|
// string, but the string will be restored to its original state before
|
||||||
|
// returning. Therefore, commands passed must not be in read-only memory.
|
||||||
|
void AddCommandString (char *text, int keynum=0);
|
||||||
|
|
||||||
|
// Process a single console command. Does not handle wait.
|
||||||
|
void C_DoCommand (const char *cmd, int keynum=0);
|
||||||
|
|
||||||
|
FExecList *C_ParseExecFile(const char *file, FExecList *source);
|
||||||
|
void C_SearchForPullins(FExecList *exec, const char *file, class FCommandLine &args);
|
||||||
|
bool C_ExecFile(const char *file);
|
||||||
|
|
||||||
|
// Write out alias commands to a file for all current aliases.
|
||||||
|
void C_ArchiveAliases (FConfigFile *f);
|
||||||
|
|
||||||
|
void C_SetAlias (const char *name, const char *cmd);
|
||||||
|
void C_ClearAliases ();
|
||||||
|
|
||||||
|
// build a single string out of multiple strings
|
||||||
|
FString BuildString (int argc, FString *argv);
|
||||||
|
|
||||||
typedef void (*CCmdRun) (FCommandLine &argv, APlayerPawn *instigator, int key);
|
typedef void (*CCmdRun) (FCommandLine &argv, APlayerPawn *instigator, int key);
|
||||||
|
|
||||||
class FConsoleCommand
|
class FConsoleCommand
|
||||||
|
|
|
@ -1723,12 +1723,13 @@ bool ConsiderPatches (const char *arg)
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
void D_MultiExec (DArgs *list, bool usePullin)
|
FExecList *D_MultiExec (DArgs *list, FExecList *exec)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < list->NumArgs(); ++i)
|
for (int i = 0; i < list->NumArgs(); ++i)
|
||||||
{
|
{
|
||||||
C_ExecFile (list->GetArg (i), usePullin);
|
exec = C_ParseExecFile(list->GetArg(i), exec);
|
||||||
}
|
}
|
||||||
|
return exec;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void GetCmdLineFiles(TArray<FString> &wadfiles)
|
static void GetCmdLineFiles(TArray<FString> &wadfiles)
|
||||||
|
@ -2291,18 +2292,24 @@ void D_DoomMain (void)
|
||||||
|
|
||||||
AddAutoloadFiles(iwad_info->Autoname);
|
AddAutoloadFiles(iwad_info->Autoname);
|
||||||
|
|
||||||
// Run automatically executed files
|
// Process automatically executed files
|
||||||
|
FExecList *exec;
|
||||||
execFiles = new DArgs;
|
execFiles = new DArgs;
|
||||||
GameConfig->AddAutoexec (execFiles, gameinfo.ConfigName);
|
GameConfig->AddAutoexec(execFiles, gameinfo.ConfigName);
|
||||||
D_MultiExec (execFiles, true);
|
exec = D_MultiExec(execFiles, NULL);
|
||||||
|
|
||||||
// Run .cfg files at the start of the command line.
|
// Process .cfg files at the start of the command line.
|
||||||
execFiles = Args->GatherFiles ("-exec");
|
execFiles = Args->GatherFiles ("-exec");
|
||||||
D_MultiExec (execFiles, true);
|
exec = D_MultiExec(execFiles, exec);
|
||||||
|
|
||||||
C_ExecCmdLineParams (); // [RH] do all +set commands on the command line
|
// [RH] process all + commands on the command line
|
||||||
|
exec = C_ParseCmdLineParams(exec);
|
||||||
|
|
||||||
CopyFiles(allwads, pwads);
|
CopyFiles(allwads, pwads);
|
||||||
|
if (exec != NULL)
|
||||||
|
{
|
||||||
|
exec->AddPullins(allwads);
|
||||||
|
}
|
||||||
|
|
||||||
// Since this function will never leave we must delete this array here manually.
|
// Since this function will never leave we must delete this array here manually.
|
||||||
pwads.Clear();
|
pwads.Clear();
|
||||||
|
@ -2324,8 +2331,13 @@ void D_DoomMain (void)
|
||||||
// Now that wads are loaded, define mod-specific cvars.
|
// Now that wads are loaded, define mod-specific cvars.
|
||||||
ParseCVarInfo();
|
ParseCVarInfo();
|
||||||
|
|
||||||
// Try setting previously unknown cvars again, as a CVARINFO may have made them known.
|
// Actually exec command line commands and exec files.
|
||||||
C_ExecStoredSets();
|
if (exec != NULL)
|
||||||
|
{
|
||||||
|
exec->ExecCommands();
|
||||||
|
delete exec;
|
||||||
|
exec = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// [RH] Initialize localizable strings.
|
// [RH] Initialize localizable strings.
|
||||||
GStrings.LoadStrings (false);
|
GStrings.LoadStrings (false);
|
||||||
|
|
Loading…
Reference in a new issue