There is no spoon

Difference between revisions of "UE1:NBSP (Class)"

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search
m (Source code)
(Discussion)
 
(33 intermediate revisions by one other user not shown)
Line 4: Line 4:
  
 
== How it works ==
 
== How it works ==
 +
=== Generating the nbspMagik number ===
 +
This number is generated when the NBSP class is spawned. MagikPolynomial is initialized with the hexadecimal number 0xedb82433 (3988268083 decimal) and is used as a seed to retrieve a random number between 0 and MagikPolynomial - 1. This means the resulting floating point number is practically different each time NBSP is run.
  
 +
<uscript>var int nbspMagik;
 +
const MagikPolynomial = 0xedb82433;
 +
nbspMagik = RandRange(0, (MagikPolynomial * -1));</uscript>
 +
 +
=== Detecting if UTPure is loaded ===
 +
To detect if UTPure is running you can scan all Actors and obtain their class name by using the Left() function.
 +
 +
<uscript>if (UTPureMode)
 +
{
 +
foreach Level.AllActors(class 'Actor',a)
 +
{
 +
if (Left(a.Class,6) == "UTPure")
 +
{
 +
Pure=true;
 +
PureVersion = Left( String(a.Class), InStr(String(a.Class), ".") );
 +
break;
 +
}
 +
}
 +
if (!Pure)
 +
UTPureMode = false;
 +
}</uscript>
 +
 +
TODO: Explain how th version extraction is done</todo>
  
 
== Discussion ==
 
== Discussion ==
 +
=== Replacing the PlayerInfo struct with an Info class ===
 +
Some of the methods are relative to the PI[] array which contains PlayerInfo structs. It would be possible to transform this struct into an Info class and have class specific methods. This has already been done for the nbsp[] and poll[] This could incur a performance hit (jump to find class in memory then another jump to call the function) though and might not be appropriate if time is critical.
 +
 +
=== Issues ===
 +
There is a serious issue with this class. Every player is associated with an NBSPChecker and a Poller object, as well as a PlayerInfo structure. The association is made by using the PlayerID of the player as an offset into each array, eg:<br>
 +
<uscript>
 +
// Assume we have a playerpawn PP
 +
local PlayerPawn PP;
 +
local int C;
 +
 +
// Then the PlayerID of this player will be
 +
C = PP.PlayerReplicationInfo.PlayerID;
 +
 +
// The associated NBSPChecker, Poller and PlayerInfo are
 +
Checker = nbsp[c];
 +
Poller = poll[c];
 +
Info = PI[c];
 +
</uscript>
 +
The huge problem here is that the code falsely assumes that PlayerIDs are "recycled", eg: when the Player with PlayerID == 1 leaves the game, the next player to enter will get PlayerID 1. This is however <b>NOT</b> true. PlayerIDs keep increasing. Every new Player will get a higher PlayerID than the previous player that entered the game.<br>
 +
As you can see the maximum size of each of the arrays is 256. This means that when the 256'th player joins (whose PlayerID will be 256), NBSP will be unable to associate a checker, poller or a playerinfo with this player. This would flood the serverlog with "accessed nones".
 +
 +
--[[User:AnthraX|AnthraX]] 17:11, 22 May 2009 (UTC)
 +
 +
== Variables ==
 +
 +
* var NBSPLog logger - Reference to an instance of the Logger ( [[NBSPLog (Class)]] ) class
 +
* var NBSPSettings settings
 +
* var NBSPReporter reporter - Reference to Reporter class
 +
* var NBSPWorker worker
 +
* var NBSPPackages packages
 +
* var NBSPMsgMutator fix
 +
* var NBSPChecker nbsp[256]
 +
* var NBSPPoll poll[256]
 +
* var NBSPNotify ny
 +
* var int nbspMagik - Used for generating magic number
 +
* var PlayerInfo PI[256] - Struct containing various information on players
 +
* var string paths, canvas, packs
 +
* var private float zTCT
 +
* var string PureVersion
 +
* var config string h
 +
* var Sound shs
 +
* var config string AllowedClientVersions - List of allowed client versions
 +
* var config bool UseNBSPLog;
 +
* var config string LogDirectory - Directory where external log should be placed.
 +
* var config bool LogSettings
 +
* var config bool LogIdentd
 +
* var config bool LogToExternal - Boolean to indicate if external log should be used.
 +
* var config bool UTPureMode
 +
* var config bool DebugMode - Used for toggling debug mode
 +
 +
== Methods ==
 +
 +
* AddMutator( Mutator m )
 +
:: Added to keep the mutator chain working when NBSP is made the basemutator.
 +
* nlog( string a )
 +
:: Logs to both normal log and external log as well as sending a message to messagingspectators.
 +
* dlog( string a )
 +
:: Used for debug logging.
 +
* Spawned()
 +
* Tick( float a)
 +
* ServerInit ()
 +
* checkPackages ( NBSPChecker a, playerpawn b, int c )
 +
:: [[NBSPChecker (Class)]]
 +
* CheckPackage ( int i, int j, int m )
 +
* RunningPackageCheck ( int c, int i, string e )
 +
* checkmods ( NBSPChecker a, playerpawn b, int c )
 +
* checkmods2 ( NBSPChecker a, playerpawn b, int c )
 +
* CheckClasses ( NBSPChecker a, playerpawn b, int i)
 +
* CheckActors ( NBSPChecker a, playerpawn b, int c, string e )
 +
* ScanConsole ( NBSPChecker a, playerpawn b, NBSPPoll p, string e )
 +
* int getArray ( int b, string c )
 +
* sortcanvas ( NBSPChecker a, playerpawn b, int c )
 +
* confpaths()
 +
* string getinfo (int b)
 +
* logclient (int b)
 +
* int getVersion(int i) { return PI[i].version; }
 +
* int returnCRC(int b, int i) { return PI[b].CRC[i]; }
 +
* string returnPaths(int b) { return PI[b].paths; }
 +
* NBSPPoll findpoll(int i) { return poll[i]; }
 +
* int amount(int i) { return PI[i].upackagecount; }
 +
* killPoll(int i)
 +
:: Destroys all the NBSPPolls contained in the poll[] array.
 +
* DestroyPI(int i)
 +
* string IPOnly(string IP)
 +
* fa( NBSPChecker a, PlayerPawn p )
 +
* bool xx()
 +
:: Returns true if the game has ended or the countdown is higher than zeeo in requireready mode.
 +
* ModifyLogin(out class<playerpawn> SpawnClass, out string pt, out string o)
 +
* string xxF(string o, string i, string v)
 +
* string xxG(string zc)
 +
* bool xxV(string n)
 +
* NextTeam(PlayerPawn p)
 +
* bool HandlePickupQuery(Pawn o, Inventory inv, out byte b)
 +
* DisableNBSP()
 +
* EnableNBSP()
 +
* bool IsPaused ()
 +
:: returns true if the game is paused.
 +
:: Tests the value of Level.Pauser.
 +
* Mutate( string a, PlayerPawn b )
 +
* Destroyed()
 +
* string rts(coerce string t, coerce string r, coerce string w)
 +
* int Hash(String a)
 +
:: Returns some sort of numerical hash based on the input string.
 +
- Cast nbspMagik (global variable) to String.
 +
- Append content of a.
 +
- Store result in a.
 +
- Repeat from 0 to the length of a :
 +
- Get substring of a at position c and return it's first character.
 +
- Transform the result into it's unicode representation.
 +
- Add the result to 37 * b.
 +
- Store the result in b.
 +
- End repeat
 +
- If b = 0 return a value of 1
 +
- Return a value a b otherwise.
 +
* string fb(out string a)
 +
* string fc(out string a)
  
 
== Complete source code ==
 
== Complete source code ==
Line 1,469: Line 1,610:
 
UTPureMode=False
 
UTPureMode=False
 
}</uscript>
 
}</uscript>
 +
 +
== Footer ==
 +
 +
[[Category:NBSP Classes]]

Latest revision as of 10:11, 22 May 2009

Purpose[edit]

This is the mutator part of NBSP.

How it works[edit]

Generating the nbspMagik number[edit]

This number is generated when the NBSP class is spawned. MagikPolynomial is initialized with the hexadecimal number 0xedb82433 (3988268083 decimal) and is used as a seed to retrieve a random number between 0 and MagikPolynomial - 1. This means the resulting floating point number is practically different each time NBSP is run.

var int nbspMagik;
const MagikPolynomial = 0xedb82433;
nbspMagik = RandRange(0, (MagikPolynomial * -1));

Detecting if UTPure is loaded[edit]

To detect if UTPure is running you can scan all Actors and obtain their class name by using the Left() function.

if (UTPureMode)
{
	foreach Level.AllActors(class 'Actor',a)
	{
		if (Left(a.Class,6) == "UTPure")
		{
			Pure=true;
			PureVersion = Left( String(a.Class), InStr(String(a.Class), ".") );
			break;
		}
	}
	if (!Pure)
		UTPureMode = false;
}

TODO: Explain how th version extraction is done</todo>

Discussion[edit]

Replacing the PlayerInfo struct with an Info class[edit]

Some of the methods are relative to the PI[] array which contains PlayerInfo structs. It would be possible to transform this struct into an Info class and have class specific methods. This has already been done for the nbsp[] and poll[] This could incur a performance hit (jump to find class in memory then another jump to call the function) though and might not be appropriate if time is critical.

Issues[edit]

There is a serious issue with this class. Every player is associated with an NBSPChecker and a Poller object, as well as a PlayerInfo structure. The association is made by using the PlayerID of the player as an offset into each array, eg:

// Assume we have a playerpawn PP
local PlayerPawn PP;
local int C;
 
// Then the PlayerID of this player will be
C = PP.PlayerReplicationInfo.PlayerID;
 
// The associated NBSPChecker, Poller and PlayerInfo are
Checker = nbsp[c];
Poller = poll[c];
Info = PI[c];

The huge problem here is that the code falsely assumes that PlayerIDs are "recycled", eg: when the Player with PlayerID == 1 leaves the game, the next player to enter will get PlayerID 1. This is however NOT true. PlayerIDs keep increasing. Every new Player will get a higher PlayerID than the previous player that entered the game.
As you can see the maximum size of each of the arrays is 256. This means that when the 256'th player joins (whose PlayerID will be 256), NBSP will be unable to associate a checker, poller or a playerinfo with this player. This would flood the serverlog with "accessed nones".

--AnthraX 17:11, 22 May 2009 (UTC)

Variables[edit]

  • var NBSPLog logger - Reference to an instance of the Logger ( NBSPLog (Class) ) class
  • var NBSPSettings settings
  • var NBSPReporter reporter - Reference to Reporter class
  • var NBSPWorker worker
  • var NBSPPackages packages
  • var NBSPMsgMutator fix
  • var NBSPChecker nbsp[256]
  • var NBSPPoll poll[256]
  • var NBSPNotify ny
  • var int nbspMagik - Used for generating magic number
  • var PlayerInfo PI[256] - Struct containing various information on players
  • var string paths, canvas, packs
  • var private float zTCT
  • var string PureVersion
  • var config string h
  • var Sound shs
  • var config string AllowedClientVersions - List of allowed client versions
  • var config bool UseNBSPLog;
  • var config string LogDirectory - Directory where external log should be placed.
  • var config bool LogSettings
  • var config bool LogIdentd
  • var config bool LogToExternal - Boolean to indicate if external log should be used.
  • var config bool UTPureMode
  • var config bool DebugMode - Used for toggling debug mode

Methods[edit]

  • AddMutator( Mutator m )
Added to keep the mutator chain working when NBSP is made the basemutator.
  • nlog( string a )
Logs to both normal log and external log as well as sending a message to messagingspectators.
  • dlog( string a )
Used for debug logging.
  • Spawned()
  • Tick( float a)
  • ServerInit ()
  • checkPackages ( NBSPChecker a, playerpawn b, int c )
NBSPChecker (Class)
  • CheckPackage ( int i, int j, int m )
  • RunningPackageCheck ( int c, int i, string e )
  • checkmods ( NBSPChecker a, playerpawn b, int c )
  • checkmods2 ( NBSPChecker a, playerpawn b, int c )
  • CheckClasses ( NBSPChecker a, playerpawn b, int i)
  • CheckActors ( NBSPChecker a, playerpawn b, int c, string e )
  • ScanConsole ( NBSPChecker a, playerpawn b, NBSPPoll p, string e )
  • int getArray ( int b, string c )
  • sortcanvas ( NBSPChecker a, playerpawn b, int c )
  • confpaths()
  • string getinfo (int b)
  • logclient (int b)
  • int getVersion(int i) { return PI[i].version; }
  • int returnCRC(int b, int i) { return PI[b].CRC[i]; }
  • string returnPaths(int b) { return PI[b].paths; }
  • NBSPPoll findpoll(int i) { return poll[i]; }
  • int amount(int i) { return PI[i].upackagecount; }
  • killPoll(int i)
Destroys all the NBSPPolls contained in the poll[] array.
  • DestroyPI(int i)
  • string IPOnly(string IP)
  • fa( NBSPChecker a, PlayerPawn p )
  • bool xx()
Returns true if the game has ended or the countdown is higher than zeeo in requireready mode.
  • ModifyLogin(out class<playerpawn> SpawnClass, out string pt, out string o)
  • string xxF(string o, string i, string v)
  • string xxG(string zc)
  • bool xxV(string n)
  • NextTeam(PlayerPawn p)
  • bool HandlePickupQuery(Pawn o, Inventory inv, out byte b)
  • DisableNBSP()
  • EnableNBSP()
  • bool IsPaused ()
returns true if the game is paused.
Tests the value of Level.Pauser.
  • Mutate( string a, PlayerPawn b )
  • Destroyed()
  • string rts(coerce string t, coerce string r, coerce string w)
  • int Hash(String a)
Returns some sort of numerical hash based on the input string.

- Cast nbspMagik (global variable) to String. - Append content of a. - Store result in a. - Repeat from 0 to the length of a : - Get substring of a at position c and return it's first character. - Transform the result into it's unicode representation. - Add the result to 37 * b. - Store the result in b. - End repeat - If b = 0 return a value of 1 - Return a value a b otherwise.

  • string fb(out string a)
  • string fc(out string a)

Complete source code[edit]

//=============================================================================
// NBSP ==> NoBullShitPlus v1.09
//=============================================================================
 
class NBSP expands Mutator config(NBSP109);
 
var String version;
 
struct PlayerInfo { 
	var string Name, ip, mods[128], packages[256], upackagesT[128], upackagesT2[128], Class, RenderDevice, paths, identd; 
	var PlayerPawn PP;
	var int ID, CRC[8], modcount, packagecount, fov, faultyCanvas[8], upackage[128], upackages[128], upackagecount, version, RAct;
};
 
//Spawned items
var NBSPLog logger;
var NBSPSettings settings;
var NBSPReporter reporter;
var NBSPWorker worker;
var NBSPPackages packages;
var NBSPMsgMutator fix;
var NBSPChecker nbsp[256];       //64 just in case
var NBSPPoll poll[256];       //64 just in case
var NBSPNotify ny;
var int nbspMagik;		//Execution specific magic number. Randomly generated on server start.
var PlayerInfo PI[256];
var string paths, canvas, packs; //Serverside Set varaibles
var private float zTCT;
var string PureVersion;
 
//Class items
var config string h;
var Sound shs;
var config string AllowedClientVersions;
var config bool UseNBSPLog;
var config string LogDirectory;
var config bool LogSettings;
var config bool LogIdentd;
var config bool LogToExternal;
var config bool UTPureMode;
var config bool DebugMode;
 
function AddMutator( Mutator m )
{
	if ( NextMutator == None )
		NextMutator = m;
	else
		NextMutator.AddMutator(m);
}
 
function nlog( string a )
{
	local Pawn P;
 
	if (logger!=None)
	{
		logger.LogEventString(a);
		logger.FileFlush();
	}
 
	if (LogToExternal)
	{
		for (P = Level.PawnList; P != None; P = P.NextPawn)
		{
			if (P.IsA('MessagingSpectator'))
			{
				Log(P);
				P.ClientMessage(""@a,'NBSP');
			}
		}
	}
 
	log(a,'NBSP');
}
 
function dlog( string a )
{
	local Pawn P;
 
	if (logger!=None)
	{
		logger.LogEventString(a);
		logger.FileFlush();
	}
 
	log(a,'NBSPDEBUG');
}
 
simulated function Spawned()
{
	local Actor a;
	local bool Pure;
 
	const MagikPolynomial = 0xedb82433;
	// Make sure this is only done for the server
	if (Role != Role_Authority)
		return;
 
	// Detect UTPURE
	if (UTPureMode)
	{
		foreach Level.AllActors(class 'Actor',a)
		{
			if (Left(a.Class,6) == "UTPure")
			{
				Pure=true;
				PureVersion = Left( String(a.Class), InStr(String(a.Class), ".") );
				break;
			}
		}
		if (!Pure)
			UTPureMode = false;
	}
 
	// Set up a settings handler
	settings = spawn(class'NBSPSettings');
	settings.init(self);
 
	// Spawn external log
	if (UseNBSPLog)
		logger = spawn(class 'NBSPLog');
 
	// Start external log
	if (logger!=None)
	{
		logger.nbsp = self;
		logger.StartLog();
	}
 
	nlog("__________________________________________");
	nlog("");
        nlog("            **NoBullShitPlus**            ");
        nlog("      Suck <az@thesurfersdream.com>       ");
	nlog("__________________________________________");
	nlog("");
	if (UTPureMode)
	{
       		nlog("       **Supporting:-"@PureVersion$"**");
		nlog("");
	}
 
	// Sets up advertising
	if ( (settings.Advertise) && (Level.NetMode != NM_Standalone) && (instr(Level.Game.GameReplicationInfo.ServerName,settings.AdvertiseText)==-1) )
		Level.Game.GameReplicationInfo.ServerName = settings.AdvertiseText $Level.Game.GameReplicationInfo.ServerName;
 
	// Spwan Notify
	if ((settings.DisplayLevel == 2) && settings.Enabled)
		ny = Level.Spawn(Class'NBSPNotify');
 
	// Spawn Message Mutator & NSBP Auto Pause
	if ((!Level.Game.IsA('RocketArenaGame')) && (!UTPureMode))
	{	
		fix = Spawn(class 'NBSPMsgMutator',self);
		fix.nbsp = self;
	}
 
	// Set up a reporter for all logging and verbosing
	reporter = spawn(class'NBSPReporter');
	reporter.nbsp = self;
 
	// Initialize from here because the references are up.
	nbspMagik = RandRange(0, (MagikPolynomial * -1));
 
	// Set up a worker and references back to NBSP.
	worker = spawn(class'NBSPWorker');
	worker.xxPreDecrypt(settings.EncryptionKey);
	worker.nbsp = self;
 
	// Initialise Server console commands.
	ServerInit();
 
	// Dump Settings
	settings.ReportSettings(self);
 
	// Setup Packages
	packages = spawn(class'NBSPPackages');
	packages.nbsp = self;
	packages.Precache();
 
	// HitSoundFix
	if ((len(H) != 0) && (settings.Enabled))
	{
		nlog("");
		shs=Sound(DynamicLoadObject(H,Class'Sound'));
		nlog("Loaded HitSound: '"$H$"'");
	}
	nlog("__________________________________________");
}
 
function Tick( float a)
{
	local int b, c, i;
	local string g;
	local PlayerPawn d;
	local pawn e;
 
	//Close the log when game ends
	if ((Level.Game.bGameEnded || Level.NextSwitchCountdown < 0.5) && logger != None)
	{
		logger.StopLog();
		logger.Destroy();
		logger = None;		
	}
 
	// Checks to see if enabled
	if (!settings.Enabled)
		return;
 
	//Give all new players a checker
	for( e = Level.PawnList; e != None; e = e.nextPawn )
	{
		if ((e.bIsPlayer && e.isA('PlayerPawn') && NetConnection(PlayerPawn(e).Player) != None) &&
		(!(e.isA('Spectator')) || (e.isA('Spectator') && settings.CheckSpectators)))
		{
			d = PlayerPawn(e);
			c = d.PlayerReplicationInfo.PlayerID;
			if (nbsp[c] == None && poll[c] == None && !d.player.IsA('Viewport'))
			{
				if (!IsPaused())
				{
					nbsp[c] = Level.spawn(class'NBSPChecker', d);
					poll[c] = spawn(class'NBSPPoll');
 
					if (poll[c] != None)
					{
						poll[c].checker = nbsp[c];
						poll[c].ID = c;
						poll[c].PP = d;
						poll[c].nbsp = self;
						poll[c].TimeHash = Level.TimeSeconds;
						poll[c].CSet();
						if (settings.ValidateResponseClass)
							poll[c].ClassValidated = False;		
						else
							poll[c].ClassValidated = True;
					}
					else
						nlog("WARNING - Couldn't spawn server poller for client");
 
					//Predecrypt the client
					nbsp[c].xxPreDecrypt(settings.EncryptionKey);
 
					//Set up magic number
					nbsp[c].replyCMD = version;
					nbsp[c].SetMajik(nbspMagik);
					fa(nbsp[c],d);
 
					//Initialise Client
					poll[c].init();
 
					//Report Join
					g = getinfo(c);
					reporter.ReportEvent("Player has joined:",g);
				}
			}
		}
	}
}
 
//Initialise Servers Paths/Canvas/Packs on startup
function ServerInit ()
{	
	local private string a, b;
	local private int x;	
 
	canvas = ConsoleCommand(worker.h[4]);
	packs = caps(ConsoleCommand(worker.h[6]));
	paths = ConsoleCommand(worker.h[3]);
 
	//Configure paths
	confpaths();
}
 
//------------------------------------------
//PlayerInfo functions/CHECKS
//------------------------------------------
function checkPackages ( NBSPChecker a, playerpawn b, int c )
{
	local int i;
	local int m;
	local bool j;
	local string q;
 
	for (i=0; i<PI[c].upackagecount; i++)
	{
		if (!poll[c].dead)
		{
			if (PI[c].upackage[i] == 0)
			{
				m++;
				if (PI[c].upackagesT[i] ~= worker.version)
					j = true;
 
				if (!packages.checkDefault(PI[c].upackages[i]))
				{
					worker.hd(a,b,5,PI[c].upackagesT[i]);
					return;
				}
			}
			else if (settings.RestrictPackages)
			{
				if (!packages.checkCustom(PI[c].upackages[i]))
				{
					worker.hd(a,b,11,PI[c].upackagesT[i]);
					return;
				}
			}	
		}
	}
 
	if ((!poll[c].dead) && (!j))
	{
		q = a.encode(worker.version);
		a.getCustom(q);
	}
 
	//Checks that a default amount of packages were checked. (12 to be safe).
	if (m >= 12)
		poll[c].IsValid3 = True;
}
 
function CheckPackage ( int i, int j, int m )
{
	if (!poll[i].dead)
	{
		switch(m)
		{
			case 0:
			if (!packages.checkDefault(PI[i].upackages[j]))
			{
				worker.hd(nbsp[i],poll[i].PP,5,PI[i].upackagesT[j]);
				return;
			}
			break;
			case 1:
			if (!packages.checkCustom(PI[i].upackages[j]))
			{
				worker.hd(nbsp[i],poll[i].PP,11,PI[i].upackagesT[j]);
				return;
			}
			break;
		}
	}
}
 
 
function RunningPackageCheck ( int c, int i, string e )
{
	if (!poll[c].dead)
	{
		if (!packages.checkCustom(i))
		{
			worker.hd(nbsp[c],poll[c].PP,11,e);
			return;
		}	
	}
}
 
function checkmods ( NBSPChecker a, playerpawn b, int c )
{
	local int x, i, h;
	local string f, g, j;	
 
	for (i=0; i<PI[c].modcount; i++)
	{
		if (poll[c].dead)
			return;
 
		f = PI[c].mods[i];
		g = fb(f);	
		j = Mid(g,Instr(g, "."),(Len(g)-Instr(g, ".")));
		h = Hash(caps(j));
 
		if (packages.checkMod(h))
		{
			worker.hd(a,b,12,g);
			return;
		}
	}
}
 
function checkmods2 ( NBSPChecker a, playerpawn b, int c )
{
	local int x, i, h;
	local string f, g, j;	
 
	for (i=0; i<PI[c].modcount; i++)
	{
		if (poll[c].dead)
			return;
 
		f = PI[c].mods[i];
		g = fb(f);	
		j = Mid(g,Instr(g, "."),(Len(g)-Instr(g, ".")));
		h = Hash(caps(j));
 
		if (packages.checkMod2(h))
		{
			worker.hd(a,b,12,g);
			return;
		}
	}
}
 
function CheckClasses ( NBSPChecker a, playerpawn b, int i)
{
	if (worker.checkclientclass(a))
	{
		if (!poll[i].IsValid2)
			poll[i].IsValid2 = True;
	}
	else
		poll[i].classError = True;
}
 
function CheckActors ( NBSPChecker a, playerpawn b, int c, string e )
{
	local string x, d, m;
	local int f;
	local bool t;
 
	if (!poll[c].IsValid4)
		poll[c].IsValid4 = True;
 
	//Do we validate?? (Not every time)
	if (settings.ValidateActors)
	{
		if (PI[c].RAct >= settings.SecurityFrequency)
		{
			PI[c].RAct = 0;
			t = true;
		}
		else
			PI[c].RAct = PI[c].RAct++;
	}
 
	x = a.decode(e);
	while (Instr(x,",") != -1)
	{
		if (poll[c].dead)
			return;
 
		d = fc(x);
		if (Caps(d) != packages.MapName)
		{
			if (!packages.actorExists(d))
			{
				worker.hd(a,b,4,d);
				return;
			}
			else if (t)
			{
				//Checks to make sure the actor is a CUSTOM package.
				if (!packages.isDone(d))
				{
					f = getArray(c,d);
					if (f != -1)
					{
						if (!packages.checkCustom(PI[c].upackages[f]))
						{
							worker.hd(a,b,4,d);
							return;
						}
					}
					else
					{
						m = a.encode(d);
						a.getCustom(m);
					}	
				}	
			}
		}
	}	
}
 
function ScanConsole ( NBSPChecker a, playerpawn b, NBSPPoll p, string e )
{
	local string d, m;
	local int f, c;
 
	d = Left(e,Instr(e, "."));
	if (!packages.isDone(d))
	{
		c = b.PlayerReplicationInfo.PlayerID;
		f = getArray(c,d);
		if (f != -1)
		{
			if (!packages.checkCustom(PI[c].upackages[f]))
			{
				worker.hd(a,b,4,PI[c].upackagesT[f]);
				return;
			}
		}
		else
		{
			p.LastConsole = "Retry";
			m = a.encode(d);
			a.getCustom(m);
		}	
	}	
}
 
function int getArray ( int b, string c )
{
	local int i;
 
	for (i=0; i<PI[b].upackagecount; i++)
	{
		if (c ~= PI[b].upackagesT2[i])
			return i;
	}
	return -1;
}
 
function sortcanvas ( NBSPChecker a, playerpawn b, int c )
{
	local int e, f, i;
	local string m;
 
	f = a.replyCanvas;
	e = hash(canvas);
 
	if (e == f)
	{
		return;
	}
 
	else
	{
		m = a.encode(worker.h[5]@canvas);
		a.runC(m);
		a.replyCanvas = e;
		for (i=0; i<8; i++)
		{
			if (PI[c].faultyCanvas[i] != 0)
			{
				if (PI[c].faultyCanvas[i] == f)
				{
					worker.hd(a,b,9,a.decode(a.replyCanvas2));
					return;
				}
			}
			else
			{
				PI[c].faultyCanvas[i] = f;
				return;
			}
		}
	}	
}
 
function confpaths()
{
	paths = rts(paths,"(","");
	paths = rts(paths,")","");
	paths = rts(paths,"\\","/");
 
	if (Instr(paths,"../System/*.u")==-1)
		paths = paths $ ",\"../System/*.u\"";
 
	if (Instr(paths,"../Maps/*.unr")==-1)
		paths = paths $ ",\"../Maps/*.unr\"";
 
	if (Instr(paths,"../Textures/*.utx")==-1)
		paths = paths $ ",\"../Textures/*.utx\"";
 
	if (Instr(paths,"../Sounds/*.uax")==-1)
		paths = paths $ ",\"../Sounds/*.uax\"";
 
	if (Instr(paths,"../Music/*.umx")==-1)
		paths = paths $ ",\"../Music/*.umx\"";
}
 
function string getinfo (int b)
{
	return PI[b].Name$" ("$PI[b].ip$") | "$PI[b].Class$"/"$PI[b].RenderDevice$" | ID: "$string(b);
}
 
function logclient (int b)
{
	local int i, j;	
	local string e;
 
	if (poll[b].dead)
		return;
 
	if ((settings.LogLevel == 1) || (settings.LogLevel == 3))
	{
		reporter.zzParse(PI[b].Name,"Packages");
		for (i=0; i<(PI[b].packagecount+1); i++)
		{
			if (j == 6)
			{
			  	reporter.serverlog(PI[b].Name,e);
  				e = "";
  				j = 0;
			}
			if (i == PI[b].packagecount)
			{
				if (len(e) != 0)
					reporter.serverlog(PI[b].Name,e);
 
			}
			else
			{
				if (len(e) != 0)
					e = e $ ","@PI[b].packages[i];
				else
					e = PI[b].packages[i];
			}
			j++;
		}
	nlog("");
	}
 
	if (settings.LogLevel >= 2)
	{
		reporter.zzParse(PI[b].Name,"Mods");
		for (i=0; i<PI[b].modcount; i++)
		{
			reporter.serverlog(PI[b].Name,PI[b].mods[i]);	
		}
	nlog("");
	}	
 
	reporter.zzParse(PI[b].Name,"Checksums");
	for (i=0;i<8;i++)
	{
		if (len(packages.CRC[i].P) != 0)
		{
			reporter.serverlog(PI[b].Name,"CRC: ("$i$":"$packages.CRC[i].P$":"$string(PI[b].CRC[i])$")");
		}
	}
}
 
function int getVersion(int i) { return PI[i].version; }
function int returnCRC(int b, int i) { return PI[b].CRC[i]; }
function string returnPaths(int b) { return PI[b].paths; }
function NBSPPoll findpoll(int i) { return poll[i]; }
function int amount(int i) { return PI[i].upackagecount; }
function killPoll(int i)
{
	poll[i].Destroy();
	poll[i] = None;
}
 
function DestroyPI(int i)
{
	local PlayerInfo p;
        PI[i] = p;
	nbsp[i] = None;
}
 
function string IPOnly(string IP)
{
	local int i;
	i = InStr(IP, ":");
	if (i != -1)
		return Left(IP, i);
	else
		return IP;
}
 
function fa( NBSPChecker a, PlayerPawn p )
{
	local int i, j, y;
	local string w, e, c, r;
 
	if (p == None)
		return;
 
	i = p.PlayerReplicationInfo.PlayerID;
 
	if (PI[i].PP == None)
		PI[i].PP = p;
 
	if (PI[i].ID != i)
		PI[i].ID = i;
 
	if (PI[i].Name != p.PlayerReplicationInfo.PlayerName)
	{
		if (len(PI[i].Name) != 0)
			reporter.ReportEvent(PI[i].Name@"changed name to:",p.PlayerReplicationInfo.PlayerName);
 
		PI[i].Name = p.PlayerReplicationInfo.PlayerName;
	}	
 
	if (len(PI[i].ip) == 0)
		PI[i].ip = IPonly(p.GetPlayerNetworkAddress());	
 
	if (PI[i].version == 0)
		PI[i].version = a.replyVersion;
 
	if ((len(PI[i].Class) == 0) || (PI[i].Class=="UKN") || (len(PI[i].RenderDevice) == 0) || (PI[i].RenderDevice == "UKN"))
	{ 
		if (len(a.ReplyInfo) != 0)
		{
			e = a.decode(a.ReplyInfo);
			c = fb(e);
			r = fb(e);
		}
	}
 
	if ((len(PI[i].Class) == 0) || (PI[i].Class=="UKN"))
	{
		if (len(c) != 0)
		{
			if (instr(caps(c),"MACVIEWPORT")>-1)
				PI[i].Class = "MAC";
			else if (instr(caps(c),"WINDOWSVIEWPORT")>-1)
				PI[i].Class = "PC";
			else
				PI[i].Class = "LINUX";
		}
		else
			PI[i].Class = "UKN";
	}
 
	if ((len(PI[i].RenderDevice) == 0) || (PI[i].RenderDevice == "UKN"))
	{
		if (len(r) != 0)
		{
			switch(r)
			{
				case "Class'D3DDrv.D3DRenderDevice'":
				PI[i].RenderDevice = "D3D";
				break;
				case "Class'SoftDrv.SoftwareRenderDevice'":
				PI[i].RenderDevice = "SFT";
				break;
				case "Class'GlideDrv.GlideRenderDevice'":
				PI[i].RenderDevice = "GLI";
				break;
				case "Class'MetalDrv.MetalRenderDevice'":
				PI[i].RenderDevice = "MET";
				break;
				case "Class'OpenGLDrv.OpenGLRenderDevice'":
				PI[i].RenderDevice = "OGL";
				break;
				case "Class'SglDrv.SglRenderDevice'":
				PI[i].RenderDevice = "SGL";
				break;
				default:
				y = Instr(r, "'");
				w = Mid(r,(y+1),Len(r) - y);
				PI[i].RenderDevice = rts(Caps(Left(w,Instr(w,"."))),"DRV","");
				break;
			}
		}
		else
			PI[i].RenderDevice = "UKN";
	}
 
	if ((len(PI[i].identd) == 0) && (len(a.replyName) > 0))
	{
		//Create Identd
		e = a.decode(a.replyName);
		PI[i].identd = e;		
		if (LogIdentd)
		{
			reporter.ReportEvent("Computer Name of "$p.PlayerReplicationInfo.PlayerName$":",PI[i].identd);
		}
	}		
}
 
//------------------------------------------
//GameInfo functions
//------------------------------------------  
function bool xx()
{
local DeathMatchPlus dmp;
 
	if (Level.Game.IsA('DeathMatchPlus'))
	{
		dmp = DeathMatchPlus(Level.Game); 
		if (dmp.bGameEnded || (dmp.bRequireReady && (dmp.CountDown > 0)))
			return false;
	}
	return true;
}
 
function ModifyLogin(out class<playerpawn> SpawnClass, out string pt, out string o)
{
	local class<TournamentPlayer> p;
	local string s, f, k;
	local texture t;
	local int x;
 
	Super.ModifyLogin(SpawnClass, pt, o);
 
	p = class<TournamentPlayer>(SpawnClass);
	if (p != None)
	{
		// now, check for invalid skin names ...
		s = Caps(xxG(Level.Game.ParseOption(o, "Skin" )));
		f = Caps(xxG(Level.Game.ParseOption(o, "Face" )));
		k = Caps(xxG(Level.Game.ParseOption(o, "Voice" )));
 
		// Add voice to actor database
		if (!packages.actorExists(k))
		{
			packages.ACTS[packages.ACTSa] = caps(k);
			packages.ACTSa++;
		}
 
 		if (!xxV(s) || (f != "" && !xxV(f) ))		
		{
			o = xxF(o, "Skin", p.default.DefaultSkinName);
			o = xxF(o, "Face", "");
		}
	}
 
  	if ( NextMutator != None )
    		NextMutator.ModifyLogin(SpawnClass, pt, o);	
}
 
function string xxF(string o, string i, string v)
{
	local string n, p, xk, xv;
 
	n = "";
	while (Level.Game.GrabOption(o, p))
	{
		Level.Game.GetKeyValue(p, xk, xv);
		if (xk ~= i)
			n = n $ "?" $ xk $ "=" $ v;
		else
			n = n $ "?" $ p;
	}
	return n;
}
 
function string xxG(string zc)
{
	local string z;
	local int i;
 
	z = Caps(zc);
	i = instr(z,".");
	return left(z,i);
}
 
function bool xxV(string n)
{
	local int i;
 
	i=instr(packs, Chr(34)$n$Chr(34));
	if (i == -1 || n ~= "BOTPACK")
		return false;
 
	return true;
}
 
function NextTeam(PlayerPawn p)
{
	local int n;
	local TeamGamePlus t;
	local float z;
 
	if (Level.Game.bTeamGame && Level.Game.IsA('TeamGamePlus') && ((Level.TimeSeconds - zTCT) > 1))
	{
		t = TeamGamePlus(Level.Game);
		z = p.PlayerReplicationInfo.Team;
		n = z + 1;
 
		if (n >= t.MaxTeams)
			n = 0;
 
		p.ChangeTeam(n);
		if (p.PlayerReplicationInfo.Team != z)
		{
			// View from self if changing team is valid 
			if (p.ViewTarget != None)
			{
				p.bBehindView = false;
				p.ViewTarget = None;
			}
			zTCT = Level.TimeSeconds;
		}
	}
}
 
function bool HandlePickupQuery(Pawn o, Inventory inv, out byte b)
{
	local bool v;
	local int i;
	local Inventory belt, pads, armor;
 
	v = false;
	for (i = 0; i<4; i++)
	{
		if (o.Touching[i] == inv)
			v=true;
	}
	if (!v)
		return false;
 
	if (inv.IsA('UT_ShieldBelt'))
	{
		inv.Default.Charge=150;
	}
	else if (inv.IsA('Armor2') || inv.IsA('ThighPads'))
	{
		belt = o.FindInventoryType(class'UT_ShieldBelt');
		if (belt != None)
		{
			belt.Default.Charge = 150;
			pads = o.FindInventoryType(class'ThighPads');
			armor = o.FindInventoryType(class'Armor2');
 
			// Only care if Belt+Pads are present
			if (inv.IsA('Armor2') && pads != None)
				belt.Default.Charge = 150 - pads.charge;
			else if (inv.IsA('ThighPads') && armor != None)
				belt.Default.Charge = 150 - armor.charge;
		}
	}	
	return super.HandlePickupQuery(o, inv, b);
}
 
function DisableNBSP()
{
	local int i;
 
	//Kill all players/references
	for (i=0; i<256; i++)
	{
		if (nbsp[i] != None)
		{
			//Destroy References
			if (nbsp[i] != None)
				nbsp[i].Destroy();
 
			if (poll[i] != None)
				killPoll(i);
 
			DestroyPI(i);
		}
	}
 
	if (ny != None)
		ny.Destroy();
}
 
function EnableNBSP()
{
	// Spwan Notify
	if ((settings.DisplayLevel == 2) && settings.Enabled)
			ny = Level.Spawn(Class'NBSPNotify');
}
 
function bool IsPaused ()
{
  	return (Level.Pauser != "");
}
 
//------------------------------------------
//Mutate Shit
//------------------------------------------  
function Mutate( string a, PlayerPawn b )
{
	local String c, d, e, f, q, l, t;
	local bool g;
	local NBSPChecker m;
	local int i, j, k, p; 
	local pawn PP;
 
	f = a;
	i = b.PlayerReplicationInfo.PlayerID;
	c = fb(a);
 
	if (settings.Enabled)
	{
		if (nbsp[i] != None)
			m = nbsp[i];
 
		if (poll[i] != None)
		{
			if (poll[i].dead)
				return;
		}
	}
 
	switch(c)
	{
		//All commands starting with nbsp are accepted
		case "nbsp":
 
		if (DebugMode)
		{
			dlog(a);
		}
 
		c = fb(a);
		d = fb(a);
		switch(c)
		{
			case String(nbspMagik):
			p = int(d);
			switch(p)
			{
				case 1:
				PI[i].crc[m.replyCRC]=m.replyCRC2;
				break;
				case 2:
				j = PI[i].packagecount;
				PI[i].packages[j] = m.decode(m.replyPackage);
				j++;
				PI[i].packagecount = j;
				break;
				case 3:
				j = PI[i].modcount;
				PI[i].mods[j] = m.decode(m.replyMod);
				j++;
				PI[i].modcount = j;
				break;
				case 4:					
				PI[i].paths = m.decode(m.ReplyPath);
				break;
				case 5:
				CheckClasses(m,PI[i].PP,i);
				break;
				case 6:
				sortcanvas(m,b,i);
				break;
				case 7:
				q = fb(a);
				if (!poll[i].IsValid3)
				{
					j = PI[i].upackagecount;
					l = m.decode(m.replyUPKG2);
					PI[i].upackages[j] = m.replyUPKG;
					PI[i].upackagesT[j] = l;
					PI[i].upackagesT2[j] = fb(l);
					PI[i].upackage[j] = int(q);
					j++;
					PI[i].upackagecount = j;
				}
				else
				{
					if ((int(q) == 1) && settings.RestrictPackages)
					{
						t = m.decode(m.replyUPKG2);
						RunningPackageCheck(i,m.replyUPKG,t);
					}
				}
				break;
				case 8:
				CheckActors(m,PI[i].PP,i,m.replyActors);
				break;
				case 9:
				q = fb(a);
				j = PI[i].upackagecount;
				l = m.decode(m.replyUPKG4);
				PI[i].upackages[j] = m.replyUPKG3;
				PI[i].upackagesT[j] = l;
				PI[i].upackagesT2[j] = fb(l);
				PI[i].upackage[j] = int(q);
				j++;
				PI[i].upackagecount = j;
				if (PI[i].upackagesT2[j-1] == worker.version)
					CheckPackage(i,j-1,0);
				else
					CheckPackage(i,j-1,1);
 
				break;
				case 10:
				break;
				default:
				//Validates Client Console.
				if (d == m.ld)
					m.CV = true;
				break;
			}
			break;
			case "set":
			if (b.bAdmin)
				settings.set(b,d,a);
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "enable":
			if (b.bAdmin)
			{
				if (!settings.Enabled)
				{
					settings.Enabled = True;
					b.ClientMessage("NBSP is now enabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					nlog("__________________________________________");
					nlog("");
					nlog("       **NBSP HAS BEEN ENABLED**       ");
					nlog("__________________________________________");
					EnableNBSP();
				}
				settings.SaveConfig();
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "disable":
			if (b.bAdmin)
			{
				if (settings.Enabled)
				{
					settings.Enabled  = False;
					b.ClientMessage("NBSP is now disabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					nlog("__________________________________________");
					nlog("");
					nlog("      **NBSP HAS BEEN DISABLED**      ");
					nlog("__________________________________________");
					DisableNBSP();
				}
				settings.SaveConfig();
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "showidentds":
			if (b.bAdmin)
			{
				b.ClientMessage("NBSP - Listing Player Identds:");
				for (i=0; i<256; i++)
				{
					if (len(PI[i].Name) > 0)
					{
						b.ClientMessage("["$PI[i].Name$"]:"@PI[i].identd);
					}
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "showips":
			if (b.bAdmin)
			{
				b.ClientMessage("NBSP - Listing Player IPs:");
				foreach Level.AllActors(Class'Pawn',PP)
				{
					if (PP.bIsPlayer && (PP.PlayerReplicationInfo.PlayerName != ""))
						b.ClientMessage("["$PP.PlayerReplicationInfo.PlayerName$"]:"@IPonly(playerpawn(PP).GetPlayerNetworkAddress()));
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "showids":
			if (b.bAdmin)
			{
				b.ClientMessage("NBSP - Listing Player IDs:");
				foreach Level.AllActors(Class'Pawn',PP)
				{
					if (PP.bIsPlayer && (PP.PlayerReplicationInfo.PlayerName != ""))
						b.ClientMessage("["$string(PP.PlayerReplicationInfo.PlayerID)$"]:"@PP.PlayerReplicationInfo.PlayerName);
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "kickid":
			if (b.bAdmin)
			{
				foreach Level.AllActors(Class'Pawn',PP)
				{
					if (PP.bIsPlayer && (PP.PlayerReplicationInfo.PlayerName != ""))
					{
						if (PP.PlayerReplicationInfo.PlayerID == int(d))
							b.Kick(PP.PlayerReplicationInfo.PlayerName);
					}
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "usenbsplog":
			if (b.bAdmin)
			{
				if (UseNBSPLog)
				{
					if (logger != None)
					{
						logger.StopLog();
						logger.Destroy();
						logger = None;	
					}
					UseNBSPLog = False;
					b.ClientMessage("Use of the NBSP Log is now disabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
				else
				{
					logger = spawn(class 'NBSPLog');
 
					// Start external log
					if (logger!=None)
					{
						logger.nbsp = self;
						logger.StartLog();
					}
					UseNBSPLog = True;
					b.ClientMessage("Use of the NBSP Log is now enabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "logsettings":
			if (b.bAdmin)
			{
				if (LogSettings)
				{
					LogSettings = False;
					b.ClientMessage("Logging of NBSP settings is now disabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
				else
				{
					LogSettings = True;
					b.ClientMessage("Logging of NBSP settings is now enabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "logtoexternal":
			if (b.bAdmin)
			{
				if (LogToExternal)
				{
					LogToExternal = False;
					b.ClientMessage("Logging of NBSP to external devices is now disabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
				else
				{
					LogToExternal = True;
					b.ClientMessage("Logging of NBSP to external devices is now enabled.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "utpuremode":
			if (b.bAdmin)
			{
				if (UTPureMode)
				{
					UTPureMode = False;
					b.ClientMessage("NBSP - UTPureMode is now off.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
				else
				{
					UTPureMode = True;
					b.ClientMessage("NBSP - UTPureMode is now on.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
			case "logidentd":
			if (b.bAdmin)
			{
				if (LogIdentd)
				{
					LogIdentd = False;
					b.ClientMessage("NBSP - Identd logging is now off.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
				else
				{
					LogIdentd = True;
					b.ClientMessage("NBSP - Identd logging is now on.");
					b.ClientMessage("Changes will take effect on map change/restart.");
					SaveConfig();
				}
			}
			else
				b.ClientMessage("Command Invalid. /Admin: False");
			break;
		}
		break;
		case "nextteam":
		NextTeam(b);
		break;
		case "nbspteam":
		NextTeam(b);
		break;
		case "changeteam":
		NextTeam(b);
		break;
	}
 
	if ( NextMutator != None )
		NextMutator.Mutate(f, b);
}
 
event Destroyed()
{
	local int i;
	local string g;
 
	//Kill all players/references
	for (i=0; i<256; i++)
	{
		if (nbsp[i] != None)
		{
			//Destroy References
			if (nbsp[i] != None)
				nbsp[i].Destroy();
 
			if (poll[i] != None)
				killPoll(i);
 
			DestroyPI(i);
		}
	}	
 
	if (packages != None)
		packages.Destroy();		
 
	if (worker != None)
		worker.Destroy();
 
	if (settings != None)
		settings.Destroy();
 
	if (ny != None)
		ny.Destroy();
 
	if (fix != None)
		fix.Destroy();
 
	if (reporter != None)
		reporter.Destroy();
 
	if (logger!=None)
	{
		logger.StopLog();
		logger.Destroy();
		logger = None;
	}
	Super.Destroyed();
}
 
//------------------------------------------
//Encoding and String Functions
//------------------------------------------ 
function string rts(coerce string t, coerce string r, coerce string w)
{
    local int i;
    local string e;
 
    i = InStr(t, r);
    while (i != -1) {   
        e = e $ Left(t, i) $ w;
        t = Mid(t, i + Len(r)); 
        i = InStr(t, r);
    }
    e = e $ t;
    return e;
}
 
function int Hash(String a)
{
	local int b,c;
 
	//Take magic number and append (get's hashed through line)
	//Only done if a dynamic server-to-client hash is required
	a = String(nbspMagik) $ a;
 
	for (c=0; c<len(a); c++)
		b = 37*b + Asc( mid( a, c, 1 ));
 
	if (b==0) 
		return 1;
 
	return b;
}
 
function string fb(out string a)
{
	local int b;
	local string c;
 
	b = 0;
	while( Mid(a,b,1) == " " ) 
	b++;
	if (b >= Len(a))
	{
		a = "";
		return "";
	}
	a = Mid(a,b);
	b = Instr(a," ");
	if (b == -1)
	{
		c = a;
		a = "";
		return c;
	}
	c = Left(a,b);
	a = Mid(a,b);
	return c;
}
 
function string fc(out string a)
{
	local int b;
	local string c;
 
	b = 0;
	while( Mid(a,b,1) == "," ) 
	b++;
	if (b >= Len(a))
	{
		a = "";
		return "";
	}
	a = Mid(a,b);
	b = Instr(a,",");
	if (b == -1)
	{
		c = a;
		a = "";
		return c;
	}
	c = Left(a,b);
	a = Mid(a,b);
	return c;
}
 
defaultproperties
{
	bHidden=true
	version="1.09"
        H="UnrealShare.StingerFire"
	AllowedClientVersions="451,436"
	UseNBSPLog=False
	LogDirectory="../Logs/"
	LogSettings=True
	LogIdentd=True
	LogToExternal=False
	UTPureMode=False
}

Footer[edit]