UE2 - UT2kX Linux based server crash when using FTP based code

  • Two Factor Authentication is now available on BeyondUnreal Forums. To configure it, visit your Profile and look for the "Two Step Verification" option on the left side. We can send codes via email (may be slower) or you can set up any TOTP Authenticator app on your phone (Authy, Google Authenticator, etc) to deliver codes. It is highly recommended that you configure this to keep your account safe.

forrestmark9

New Member
Aug 6, 2009
56
0
0
Yes and few people have been trying to correct a problem with a perk database for Killing Floor that connects to a FTP server and gets perk stats, but anyway the problem is on any Linux based OS the FTP code will cause a crash on every map change, spam the chat with FTP connection timed-out, and does not save the stats as it does not attempt a upload.

Here is the code of the FTP classes, if you need more code I can provide is

Code:
Class FTPTcpLink extends TcpLink;

var array<ServerStStats> PendingLoaders;
var array<StatsObject> ToSave;
var ServerPerksMut Mut;
var IpAddr SiteAddress;
var FTPDataConnection DataConnection;
var transient float WelcomeTimer;
var array<string> TotalList;
var MessagingSpectator WebAdminController;
var bool bConnectionBroken,bFullVerbose,bUploadAllStats,bTotalUpload,bFileInProgress,bLogAllCommands,bCheckedWeb;

function BeginEvent()
{
	Mut.SaveAllStats = SaveAllStats;
	Mut.RequestStats = RequestStats;
	if( Mut.bDebugDatabase )
	{
		bLogAllCommands = true;
		bFullVerbose = true;
	}

	LinkMode = MODE_Line;
	ReceiveMode = RMODE_Event;
	Resolve(Mut.RemoteDatabaseURL);
}
final function ReportError( string InEr )
{
	if( !bConnectionBroken )
	{
		Level.Game.Broadcast(Self,"FTP Error: "$InEr);
		Log("FTP Error: "$InEr,Class.Name);
	}
	bConnectionBroken = true;
	GoToState('ErrorState');
}
event Resolved( IpAddr Addr )
{
	SiteAddress = Addr;
	SiteAddress.Port = Mut.RemotePort;
	GoToState('Idle');
}
event ResolveFailed()
{
	ReportError("Couldn't resolve address, aborting...");
}
event Closed()
{
	ReportError("Connection was closed by FTP server!");
}
final function DebugLog( string Str )
{
	if( !bCheckedWeb )
	{
		bCheckedWeb = true;
		foreach AllActors(class'MessagingSpectator',WebAdminController)
			break;
	}
	if( WebAdminController!=None )
		WebAdminController.ClientMessage(Str,'FTP');
	Log(Str,'FTP');
}
event ReceivedLine( string Text )
{
	if( bLogAllCommands )
		DebugLog("ReceiveFTP "$GetStateName()$":"@Text);
	ProcessResponse(int(Left(Text,3)),Mid(Text,4));
}
final function SendFTPLine( string Text )
{
	if( bLogAllCommands )
		DebugLog("SendFTP "$GetStateName()$":"@Text);
	SendText(Text);
}

function SaveAllStats()
{
	local int i;

	if( bTotalUpload )
		return;
	ToSave = Mut.ActiveStats;
	for( i=0; i<ToSave.Length; ++i )
	{
		if( !ToSave[i].bStatsChanged )
			ToSave.Remove(i--,1);
	}
	if( ToSave.Length>0 )
		bUploadAllStats = true;
}
function RequestStats( ServerStStats Other )
{
	local int i;
	
	if( bTotalUpload )
		return;
	for( i=0; i<PendingLoaders.Length; ++i )
	{
		if( PendingLoaders[i]==None )
			PendingLoaders.Remove(i--,1);
		else if( PendingLoaders[i]==Other )
			return;
	}
	PendingLoaders[PendingLoaders.Length] = Other;
}
final function FullUpload()
{
	TotalList = GetPerObjectNames("ServerPerksStat","StatsObject",9999999);
	bTotalUpload = true;
	bUploadAllStats = true;
	bFullVerbose = true;
	HasMoreStats();
	SaveAllStats();
}
final function bool HasMoreStats()
{
	local byte i;
	local int j;
	
	if( TotalList.Length==0 )
		return false;
	j = ToSave.Length;
	for( i=0; i<Min(20,TotalList.Length); ++i )
	{
		ToSave.Length = j+1;
		ToSave[j] = new(None,TotalList[i]) Class'StatsObject';
		++j;
	}
	TotalList.Remove(0,20);
	return true;
}
final function CheckNextCommand()
{
	while( PendingLoaders.Length>0 && PendingLoaders[0]==None )
		PendingLoaders.Remove(0,1);

	if( bUploadAllStats || (bTotalUpload && HasMoreStats()) )
		GoToState('UploadStats','Begin');
	else if( PendingLoaders.Length>0 )
		GoToState('DownloadStats','Begin');
	else
	{
		if( bFullVerbose )
			Level.Game.Broadcast(Self,"FTP: All done!");
		if( Mut.FTPKeepAliveSec>0 && !Level.Game.bGameEnded )
			GoToState('KeepAlive');
		else GoToState('EndConnection');
	}
}
function ProcessResponse( int Code, string Line )
{
	switch( Code )
	{
	case 220: // Welcome
		if( WelcomeTimer<Level.TimeSeconds )
		{
			SendFTPLine("USER "$Mut.RemoteFTPUser);
			WelcomeTimer = Level.TimeSeconds+0.2;
		}
		break;
	case 331: // Password required
		SendFTPLine("PASS "$Mut.RemotePassword);
		break;
	case 230: // User logged in.
		if( Mut.RemoteFTPDir!="" )
			SendFTPLine("CWD "$Mut.RemoteFTPDir);
		else SendFTPLine("TYPE A");
		break;
	case 250: // CWD command successful.
		SendFTPLine("TYPE A");
		break;
	case 200: // Type set to A
		CheckNextCommand();
		break;
	case 226: // File successfully transferred
	case 150: // Opening ASCII mode data connection
		break;
	case 421: // No transfer timeout: closing control connection
		if( bFullVerbose )
			Level.Game.Broadcast(Self,"FTP: Connection timed out, reconnecting!");
		GoToState('EndConnection');
		break;
	default:
		if( bFullVerbose )
			Level.Game.Broadcast(Self,"FTP: Unknown FTP code '"$Code$"': "$Line);
		Log("Unknown FTP code '"$Code$"': "$Line,Class.Name);
	}
}
function DataReceived();

final function bool OpenDataConnection( string S, bool bUpload )
{
	local int i,j;
	local IpAddr A;

	A = SiteAddress;
	
	// Get destination port
	S = Mid(S,InStr(S,"(")+1);
	for( i=0; i<4; ++i ) // Skip IP address
		S = Mid(S,InStr(S,",")+1);
	i = InStr(S,",");
	A.Port = int(Left(S,i))*256 + int(Mid(S,i+1));
	
	// Now attempt to bind port and open connection.
	for( j=0; j<20; ++j )
	{
		if( DataConnection!=None )
			DataConnection.Destroy();
		DataConnection = Spawn(Class'FTPDataConnection',Self);
		DataConnection.bUpload = bUpload;
		DataConnection.BindPort(500+Rand(5000),true);
		if( DataConnection.OpenNoSteam(A) )
			return true;
	}
	DataConnection.Destroy();
	DataConnection = None;
	ReportError("Couldn't bind port for upload data connection!");
	return false;
}

function Timer()
{
	ReportError("FTP connection timed out!");
}

state Idle
{
Ignores Timer;

	final function StartConnection()
	{
		local int i;
		
		for( i=0; i<40; ++i )
		{
			BindPort(500+Rand(5000),true);
			if( OpenNoSteam(SiteAddress) )
			{
				GoToState('InitConnection');
				return;
			}
		}
		ReportError("Port couldn't be bound or connection failed to open!");
	}
	function SaveAllStats()
	{
		Global.SaveAllStats();
		if( bUploadAllStats )
			StartConnection();
	}
	function RequestStats( ServerStStats Other )
	{
		Global.RequestStats(Other);
		StartConnection();
	}
Begin:
	Sleep(0.1f);
	if( bUploadAllStats || PendingLoaders.Length>0 )
		StartConnection();
}
state InitConnection
{
	function BeginState()
	{
		SetTimer(10,false);
	}
	event Closed()
	{
		ReportError("Connection was closed by FTP server!");
	}
}
state ConnectionBase
{
	event Closed()
	{
		GoToState('Idle');
	}
Begin:
	while( true )
	{
		if( bUploadAllStats && Level.bLevelChange ) // Delay mapchange until all stats are uploaded.
			Level.NextSwitchCountdown = FMax(Level.NextSwitchCountdown,1.f);
		Sleep(0.5);
	}
}
state EndConnection extends ConnectionBase
{
	function BeginState()
	{
		SendFTPLine("QUIT");
		Close();
		SetTimer(4,false);
	}
}
state KeepAlive extends ConnectionBase
{
Ignores Timer;

	function SaveAllStats()
	{
		Global.SaveAllStats();
		if( bUploadAllStats )
			StartConnection();
	}
	function RequestStats( ServerStStats Other )
	{
		Global.RequestStats(Other);
		StartConnection();
	}
	final function StartConnection()
	{
		CheckNextCommand();
	}
Begin:
	while( true )
	{
		if( bUploadAllStats || PendingLoaders.Length>0 )
			StartConnection();
		Sleep(Mut.FTPKeepAliveSec);
		SendFTPLine("NOOP");
	}
}
state UploadStats extends ConnectionBase
{
	function BeginState()
	{
		bUploadAllStats = false;
		SetTimer(10,false);
	}
	function SaveAllStats();
	
	final function InitDataConnection( string S )
	{
		if( bFullVerbose )
			Level.Game.Broadcast(Self,"FTP: Upload stats for "$ToSave[0].PlayerName$" ("$(ToSave.Length-1+TotalList.Length)$" remains)");
		if( OpenDataConnection(S,true) )
		{
			DataConnection.Data = ToSave[0].GetSaveData();
			SendFTPLine("STOR "$ToSave[0].Name$".txt");
			bFileInProgress = true;
		}
	}
	final function NextPackage()
	{
		ToSave.Remove(0,1);
		if( ToSave.Length==0 )
			CheckNextCommand();
		else SendFTPLine("PASV");
	}
	function ProcessResponse( int Code, string Line )
	{
		switch( Code )
		{
		case 200: // Type set to A
			// SendFTPLine("PASV");
			break;
		case 227: // Entering passive mode
			if( !bFileInProgress )
				InitDataConnection(Line);
			break;
		case 150: // Opening ASCII mode data connection for file
			SetTimer(60,false);
			if( DataConnection!=None )
				DataConnection.BeginUpload();
			break;
		case 226: // File transfer completed.
			if( bFileInProgress )
			{
				SetTimer(10,false);
				bFileInProgress = false;
				NextPackage();
			}
			break;
		default:
			Global.ProcessResponse(Code,Line);
		}
	}
Begin:
	SendFTPLine("PASV");
	while( true )
	{
		if( Level.bLevelChange ) // Delay mapchange until all stats are uploaded.
		{
			bFullVerbose = true;
			Level.NextSwitchCountdown = FMax(Level.NextSwitchCountdown,1.f);
		}
		Sleep(0.5);
	}
}
state DownloadStats extends ConnectionBase
{
	function BeginState()
	{
		SetTimer(10,false);
	}
	final function InitDataConnection( string S )
	{
		while( PendingLoaders.Length>0 && PendingLoaders[0]==None )
			PendingLoaders.Remove(0,1);
		if( PendingLoaders.Length==0 )
		{
			CheckNextCommand();
			return;
		}

		if( bFullVerbose )
			Level.Game.Broadcast(Self,"FTP: Download stats for "$PendingLoaders[0].MyStatsObject.PlayerName$" ("$(PendingLoaders.Length-1)$" remains)");

		if( OpenDataConnection(S,false) )
		{
			DataConnection.OnCompleted = DataReceived;
			SendFTPLine("RETR "$PendingLoaders[0].MyStatsObject.Name$".txt");
			bFileInProgress = true;
		}
	}
	function DataReceived()
	{
		bFileInProgress = false;
		if( PendingLoaders[0]!=None )
		{
			if( DataConnection!=None )
				PendingLoaders[0].GetData(DataConnection.Data);
			else PendingLoaders[0].GetData("");
		}
		PendingLoaders.Remove(0,1);
		while( PendingLoaders.Length>0 && PendingLoaders[0]==None )
			PendingLoaders.Remove(0,1);

		if( bUploadAllStats ) // Saving has higher priority.
			GoToState('UploadStats');
		else if( PendingLoaders.Length>0 )
			SendFTPLine("PASV");
		else CheckNextCommand();
	}
	function ProcessResponse( int Code, string Line )
	{
		switch( Code )
		{
		case 200: // Type set to A
			// SendFTPLine("PASV");
			break;
		case 227: // Entering passive mode
			if( !bFileInProgress )
				InitDataConnection(Line);
			break;
		case 150: // Opening ASCII mode data connection for file
			SetTimer(60,false);
			break;
		case 550: // No such file or directory
			SetTimer(10,false);
			if( bFileInProgress )
			{
				if( DataConnection!=None )
					DataConnection.Destroy();
				DataReceived();
			}
			break;
		default:
			Global.ProcessResponse(Code,Line);
		}
	}
Begin:
	SendFTPLine("PASV");
	while( true )
	{
		if( bUploadAllStats && Level.bLevelChange ) // Delay mapchange until all stats are uploaded.
		{
			bFullVerbose = true;
			Level.NextSwitchCountdown = FMax(Level.NextSwitchCountdown,1.f);
		}
		Sleep(0.5);
	}
}
state ErrorState
{
Ignores SaveAllStats,RequestStats;
Begin:
	Sleep(1.f);
	Mut.RespawnNetworkLink();
}

defaultproperties
{
}

Code:
Class FTPDataConnection extends TcpLink;

var bool bUpload,bWasOpened;
var string Data;

delegate OnCompleted();

function PostBeginPlay()
{
	LinkMode = MODE_Text;
}
event Opened()
{
	BeginUpload();
}
event Closed()
{
	OnCompleted();
	Destroy();
}
event ReceivedText( string Text )
{
	if( bUpload )
		Log(Text,'FTPD');
	else Data $= Text;
}
function BeginUpload()
{
	if( bWasOpened )
		GoToState('Uploading');
	else bWasOpened = true;
}

state Uploading
{
	function BeginState()
	{
		Tick(0.f);
	}
	function Tick( float Delta )
	{
		if( Data!="" )
		{
			SendText(Left(Data,250));
			Data = Mid(Data,250);
		}
		else Close();
	}
}
 

Wormbo

Administrator
Staff member
Jun 4, 2001
5,913
36
48
Germany
www.koehler-homepage.de
What WGH wrote. You might want to try elmuerte's LibHTTP4 instead and send the data via POST request.

As for the crash: "Logs or it didn't happen." ;)

BTW: Ports below 1024 are not available to non-root processes on Linux. Those various BindPort() calls not only ignore that, but also disregard the fact that passing true as second parameter already will try a range of available ports in case the one specified isn't available. But in any case, you should not even specify any port at all for a clientside of a connection, because the Unreal engine will do exactly what your code does a poor job of trying - pick a random port.
 
Last edited:

forrestmark9

New Member
Aug 6, 2009
56
0
0
What WGH wrote. You might want to try elmuerte's LibHTTP4 instead and send the data via POST request.

As for the crash: "Logs or it didn't happen." ;)

BTW: Ports below 1024 are not available to non-root processes on Linux. Those various BindPort() calls not only ignore that, but also disregard the fact that passing true as second parameter already will try a range of available ports in case the one specified isn't available. But in any case, you should not even specify any port at all for a clientside of a connection, because the Unreal engine will do exactly what your code does a poor job of trying - pick a random port.

Hmm I see Marco over at TWI forums was the one who made this code, he made two options this FTP code here or a custom .exe database (Which uses UDPLink and sends 3 or 4 1-byte packets containing passwords and several other things) both of them have some bad problems such as the .exe overwriting peoples with anothers or blanking them if the server crashes on a map change, and the problems with FTP I listed.

Also I can get a log as soon as TWI finishes updating there forums so I can get to the thread I posted about this
 
Last edited by a moderator:

forrestmark9

New Member
Aug 6, 2009
56
0
0
Here is the crash log from TWI forums I posted about, not much is shown but it always crashes when it says "Unknown FTP code '221': Goodbye."

Unknown FTP code '221': Goodbye.
ZombieFastClot_XMas KF-Abandoned-Moonbase.ZombieFastClot_XMas (Function KFMod.KFMonster.Tick:0528) Accessed None 'LastDamagedBy'
ProcessServerTravel: KF-WestLondon?Game=TwistedSPerks.ForrestGametype?Mutator=MutLoader.MutLoader?Difficulty=5
PreClientTravel
Server switch level: KF-WestLondon?Game=TwistedSPerks.ForrestGametype?Mutator=MutLoader.MutLoader?Difficulty=5
Browse: KF-WestLondon?Name=KFPlayer?Class=Engine.Pawn?Character=Corporal_Lewis?team=1?Sex=M?Game=TwistedSPerks.ForrestGametype?VACSecured=true?Mutator=MutLoader.MutLoader?CM=1?Difficulty=5
Close TcpipConnection 173.44.70.70:61405 Wed Dec 11 00:26:44 2013
Game LOST
Socket shut down
Collecting garbage
Purging garbage
(Karma): Level Karma Terminated.
Garbage: objects: 113029->67235; refs: 861830
InPos>Size in FFileManagerWindows. This error is probably due to either your computer overheating, or a corrupted installation of your game. If this error persists, please try re-installing the game.
Executing UObject::StaticShutdownAfterError
InPos>Size in FFileManagerWindows. This error is probably due to either your computer overheating, or a corrupted installation of your game. If this error persists, please try re-installing the game.



Exiting due to error
Exiting.
FileManager: Reading 1 GByte 638 MByte 211 KByte 914 Bytes from HD took 4.109187 seconds (1.914268 reading, 2.194919 seeking).
FileManager: 0.000000 seconds spent with misc. duties
Name subsystem shut down
 

forrestmark9

New Member
Aug 6, 2009
56
0
0
The crash is not related to the FTP protocol. It's caused by a corrupted package file, either the KF-WestLondon map file or any of the packages it depends on.

That's odd cause this crash only happened when the FTP database was enabled
 

forrestmark9

New Member
Aug 6, 2009
56
0
0
What WGH wrote. You might want to try elmuerte's LibHTTP4 instead and send the data via POST request.

As for the crash: "Logs or it didn't happen." ;)

BTW: Ports below 1024 are not available to non-root processes on Linux. Those various BindPort() calls not only ignore that, but also disregard the fact that passing true as second parameter already will try a range of available ports in case the one specified isn't available. But in any case, you should not even specify any port at all for a clientside of a connection, because the Unreal engine will do exactly what your code does a poor job of trying - pick a random port.

Marco doesn't seem to want to change things to use libHTTP4 and says the port issue will most likely not fix things. He thinks that Tripwire may have broken something in IpDrv.

I've decided to make my own custom method using libHTTP4 but sadly I do not know where to start except I need to spawn HttpSock. I've never had experience using UDPLink or TCPLink
 
Last edited by a moderator:

Wormbo

Administrator
Staff member
Jun 4, 2001
5,913
36
48
Germany
www.koehler-homepage.de
LibHTTP4 is quite easy to use for basic stuff. To make an HTTP request, you simply call the get(), head(), post() or postex() function on the HttpSock instance you spawned. The get() and head() functions make GET and HEAD requests, respectively. The difference is that a HEAD request expects only HTTP headers to be returned, but is otherwise identical to the GET request.

LibHTTP comes complete with support for authentication, cookies and proxies. It can also evaluate HTTP redirects via 3xx status codes. The content interpretation for returned data is left entirely up to you, though. For example for the UTAN Ban Manager v104 I created a ban database update protocol that allows the server to send ban data in a custom line-based format that contains instructions which bans to add or remove. Of course you could also use standard formats like JSON or XML to wrap your response or reply data. You will have to implement appropriate parsing, though.

If you want to send data, you usually pick from post() and postex(), although it is also possible to send data with the other request types. The difference between post() and postex() is that the former takes an optional string parameter for POST content, while the latter takes an optional string array parameter for the same purpose.

If you want to use the HTTP response, you should assign a callback function to the HttpSock's OnComplete delegate like in this example. The function will be called when the HTTP request completes successfully.

If the returned data is too large for single-pass processing, you might consider processing it as it comes in via the OnResponseBody delegate, which is called for each line of received response body (not headers) as they come in. If you need even more control, you could even extend from the HttpSock class. You will have to do that e.g. if you want to modify the HTTP UserAgent string.
 

forrestmark9

New Member
Aug 6, 2009
56
0
0
LibHTTP4 is quite easy to use for basic stuff. To make an HTTP request, you simply call the get(), head(), post() or postex() function on the HttpSock instance you spawned. The get() and head() functions make GET and HEAD requests, respectively. The difference is that a HEAD request expects only HTTP headers to be returned, but is otherwise identical to the GET request.

LibHTTP comes complete with support for authentication, cookies and proxies. It can also evaluate HTTP redirects via 3xx status codes. The content interpretation for returned data is left entirely up to you, though. For example for the UTAN Ban Manager v104 I created a ban database update protocol that allows the server to send ban data in a custom line-based format that contains instructions which bans to add or remove. Of course you could also use standard formats like JSON or XML to wrap your response or reply data. You will have to implement appropriate parsing, though.

If you want to send data, you usually pick from post() and postex(), although it is also possible to send data with the other request types. The difference between post() and postex() is that the former takes an optional string parameter for POST content, while the latter takes an optional string array parameter for the same purpose.

If you want to use the HTTP response, you should assign a callback function to the HttpSock's OnComplete delegate like in this example. The function will be called when the HTTP request completes successfully.

If the returned data is too large for single-pass processing, you might consider processing it as it comes in via the OnResponseBody delegate, which is called for each line of received response body (not headers) as they come in. If you need even more control, you could even extend from the HttpSock class. You will have to do that e.g. if you want to modify the HTTP UserAgent string.

Hmm I see that is quite interesting but sadly I would not know to setup things such as authentication and file types as I've never really worked with HTTP or XML

Such as an example here from the .ini from the .exe Database
Code:
[76561197997881512]
[WPC]_Forrest_Mark_X=SRVetScientist,10072068,10145656,56081791,10003592,10000033,10267987,10563821,91783779,10001152,10000020,10972567,10000002,9999999,10000185,10000647,10029994,10000637,10000830,10047318,30143945,10047147,10412619,10000092,10000166,'ForrestCharacters.Tyrael',((N="FMXAch",V="DF"),(N="AchMaps",V="00000300000F3000000F01000000000000F000000000"),(N="VIPInfo",V="0201"),(N="BenelliDamageProgress",V="13227489"),(N="AutoShotgunDamageProgress",V="10521200"),(N="KatanaDamageProgress",V="10117652"),(N="MachineGunDamageProgress",V="95792892"),(N="MedicGunDamageProgress",V="995230981"),(N="VirusKillsProgress",V="10001849"),(N="ScrnPistolDamageProgress",V="90388926"),(N="LAWDamageProgress",V="15470964"),(N="ScrnPistolKillProgress",V="9999999"),(N="TurretDamageProgress",V="91019897"),(N="Ach",V="F2DBEDF6813C19193801B02D1057840080076091F033005166F8BD80C43E00147D80BD0F78FC3E5C57E43980C40C72007001"),(N="D3Ach",V="9A2ADE1B1F1CFE8007F40F"))

The first is the playername, his current selected perk, the rest before ForrestCharacters.Tyrael is stats value for default KF perks, the name parameter is the players selected character, everything after that are custom-stat values added via a special function

I'd need to find the player by there SteamID then load there stats after that send the loaded stats to the StatsObject or the Mutator. I'm not sure how Marco has everything setup

The FTP database does something similiar but it saves seperate .txt files the name of the file is the players SteamID and the contents are the rest. Marco set it up in a way that I would need as it saves the file in a .tmp to keep players stats from being overwritten if say the server crashed

So far this is all I got
Code:
function RespawnNetworkLink()
{
	if( Link!=None )
		Link.Destroy();
	if( HttpLink!=None )
		HttpLink.Destroy();
	if( bUseHTTPLink )
	{
		HttpLink = Spawn(Class'SRHttpSock');
		SRHttpSock(HttpLink).Mut = Self;
	}
	else if( !bUseFTPLink )
	{
		Link = Spawn(Class'DatabaseUdpLink');
		DatabaseUdpLink(Link).Mut = Self;
	}
	else
	{
		Link = Spawn(Class'FMXFTPTcpLink');
		FMXFTPTcpLink(Link).Mut = Self;
	}
	if( SRHttpSock(HttpLink) != none )
		SRHttpSock(HttpLink).BeginEvent();
	else
		Link.BeginEvent();
}

Code:
class SRHttpSock extends HttpSock;

var ServerPerksMut Mut;

function BeginEvent()
{
	Get(Mut.RemoteDatabaseURL);
}

defaultproperties
{
}

Well looking at Marcos code I need to add a SaveAllStats() function as this is called by the mutator when stats are saved, I would also seem to need to add a array of "current stats" (ToSave)
I do know that if I need to get someones stats I should do something like Get("http://"$Mut.RemoteDatabaseURL$FMXServerPerksMut(Mut).HTTPFolder$ToSave[0].Name$".txt")

There also seems to be a function I have to call called GetSaveData() which is in StatsObject, this returns the stat values of the person perks. If I'm correct I would use POST for this? like this? Post("http://"$Mut.RemoteDatabaseURL$FMXServerPerksMut(Mut).HTTPFolder$ToSave[0].Name$".txt.tmp", ToSave[0].GetSaveData());

Here is the code
Code:
final function string GetSaveData()
{
	local string Result;

	Result = SelectedVeterancy$","$DamageHealedStat$","$WeldingPointsStat$","$ShotgunDamageStat$","$HeadshotKillsStat$","$StalkerKillsStat;
	Result = Result$","$BullpupDamageStat$","$MeleeDamageStat$","$FlameThrowerDamageStat$","$SelfHealsStat$","$SoleSurvivorWavesStat;
	Result = Result$","$CashDonatedStat$","$FeedingKillsStat$","$BurningCrossbowKillsStat$","$GibbedFleshpoundsStat$","$StalkersKilledWithExplosivesStat;
	Result = Result$","$GibbedEnemiesStat$","$BloatKillsStat$","$SirenKillsStat$","$KillsStat$","$ExplosivesDamageStat;
	Result = Result$","$int(TotalZedTimeStat)$","$TotalPlayTime$","$WinsCount$","$LostsCount$",'"$SelectedChar$"'";
	Result = Result$","$GetPropertyText("CC");
	return Result;
}
 
Last edited by a moderator:

forrestmark9

New Member
Aug 6, 2009
56
0
0
Apparently this is how the FTP does everything as said by someone on the TWI forums

--FTP Initialization--
1) Login
Server sends a 220 username request
Client sends FTP server Username
Server sends 331 username successful, password request
Client sends FTP server Password
Server sends a 230 user logged in

2) Initialization
Client sends a CWD command to change working directory
Server sends 250 CWD successful
Client sends a TYPE A command to set file type as ASCII (note: TYPE I = binary, TYPE A = ascii, textfiles)
Server sends 200 Type change successful
Client sends PASV command to enter passive mode

3) File Request
On serverperks, you have the PendingLoaders array. After the Type Change above, it will start to iterate. For each PendingLoaders object, GetData(function of ServerStStats) is called with the downloaded data passed into it, via FTPDataConnection class.

FTPDataConnection class is populated this way:
In FTPTcpLink (DownloadStats event):
While still passive, FTPDataConnection is initialized
Client sends "RETR "$PendingLoaders[0].MyStatsObject.Name$".txt"
Server sends 150 open data connection
Server sends data
**NOTE**
At this point, the textfile is being downloaded into FTPDataConnection object as a string variable
Server sends 226 file successfully transferred