FreeBuild Progresses, phpBB2 Integration Part 2
by Thomas -elfprince13- Dickerson · 02/02/2010 (4:29 pm) · 21 comments
This blog is a followup to my previous blog here: http://www.torquepowered.com/community/blogs/view/18557/
Here are some screenshots of what our development team has been up to:






The first thing to do here is to modify your server-side onConnect function to expect an additional argument - %hashedSID and modify all client-side instances of setConnectArgs to include an md5 (or hash of your choice, though the php will also need to be modified accordingly) of the SID. I use Dave Bacher's resource for MD5, which can be found here.
The top of my onConnect looks like this:
The code will make more sense as I get further along in the discussion, but the gist of it is that if the server has already registered itself with your server database we go right ahead and process the username/hashedSID pair to make sure they represent a valid session. If not, we toss them in a stack to be dealt with later.
Your onServerCreated() routine will need to be modified in two ways.
First, you'll need to exec() serverauth.cs, and secondly, you'll need to add for check whether or not this is a public server, and decide based on that if you want to register the server. My check looks something like this:
Now for the last big chunk of TorqueScript before we get into the php, I have broken up serverauth.cs into a few chunks interspersed with comments.
Suggestions/bugfixes/improvements/cookies/exclamations-of-joy are always welcome.
Updates on FreeBuild
Before I get into any tech goodies I'd like to give an update on the status of the game. Development is progressing slowly, but surely. Our current top priorities are reworking the rendering engine to use Vertex Buffer Objects (if anyone has experience with using them in Torque, or some pro-tips, please give me a holler), finishing up with getting our flex/bison based LDraw parser to play nicely with Torque's ResourceManager, and integrating Bullet's collision library in place of the current mess.Here are some screenshots of what our development team has been up to:
Our entry in the screenshot competition

Dynamic Mod Loading (to understand why this is particularly interesting, all mods must be packaged in a zip file inside of the "mods" subdirectory)

Busting out of the sandbox to select the system LDraw directory:

Our first functional Linux build:

Another fun new map:

Improvements on the in-game color scheme:

phpBB2 Integration, Part 2
The last thing we looked at was client-side login. Now we get to the fun stuff. The important here is that the FreeBuild servers need to ensure that only registered users can connect to public servers (to help prevent impersonation and the drama that comes with it), BUT the servers themselves are user hosted, and the source code is publicly available, so we can't just give the servers access to the login database. Also, keep in mind that this code has a lot of components, because it needs to handle the vagaries of asynchronous network operations.The first thing to do here is to modify your server-side onConnect function to expect an additional argument - %hashedSID and modify all client-side instances of setConnectArgs to include an md5 (or hash of your choice, though the php will also need to be modified accordingly) of the SID. I use Dave Bacher's resource for MD5, which can be found here.
The top of my onConnect looks like this:
function GameConnection::onConnect( %client, %name, %hashedSID )
{
if($Server::Registered){
processUserKeyPair(%hashedSID, %name);
} else{
//The server hasn't heard back from Cemetech yet, so it any further transactions
//have to be put on hold. These will automagically be dealt with ASAP.
$Server::UnprocessedUserpairs = %hashedSID TAB %name NL $Server::UnprocessedUserpairs;
}
// ... and the rest of the original function goes down hereThe code will make more sense as I get further along in the discussion, but the gist of it is that if the server has already registered itself with your server database we go right ahead and process the username/hashedSID pair to make sure they represent a valid session. If not, we toss them in a stack to be dealt with later.
Your onServerCreated() routine will need to be modified in two ways.
First, you'll need to exec() serverauth.cs, and secondly, you'll need to add for check whether or not this is a public server, and decide based on that if you want to register the server. My check looks something like this:
if($pref::CemetechOn && $pref::Net::DisplayOnMaster $= "Yes"){
registerserver();
} else{
$Server::Registered = 0;
}Yours should be somewhat different, since you aren't going to be authenticating to anything involving Cemetech :pNow for the last big chunk of TorqueScript before we get into the php, I have broken up serverauth.cs into a few chunks interspersed with comments.
//Server authentication-y stuff //Table format //Name: Servers_Online //ServerID ServerKEY NeedsPass ServerName GameType ConnectedPlayers MaxPlayers ServerIP ServerMission ServerInfo LastSeen //varchar(32) varchar(32) boolean/tinyint varchar(256) varchar(64) mediumint mediumint varchar(21) varchar(255) varchar(255) whatever is needed to store a UNIX timestamp $Server::Registered = 0; $Server::ID = ""; $Server::Key = ""; $Server::HeartbeatSchedule = 0; $Server::UnprocessedUserpairs = ""; $Server::UnprocessedSchedule = 0;This just initializes all the relevant variables. $Server::Registered is just a flag value for use elsewhere. $Server::ID is the publicly available key used to identify the servers. $Server::Key is the private key used in communications between the server and your php scripts. $Server::UnprocessedUserpairs is our stack of username/hashedSID pairs that need to be worked with. $Server::HeartbeatSchedule and $Server::UnprocessedSchedule are used to store the current eventIDs for sending heartbeats to Cemetech and working through the username/hashedSID stack.
function heartbeat(){
if($pref::Net::DisplayOnMaster != 0 && $Server::Registered){
%ServerAuth = new SimCurl();
%ServerAuth.setUrl("www.cemetech.net/scripts/torqueauth.php?updserver" @ (($Pref::Server::Password !$= "") ? "&needspass" : "") @ "&key=" @ $Server::Key @ "&id=" @ $Server::ID);
%ServerAuth.addPostField("name",$Pref::Server::Name);
%ServerAuth.addPostField("mission", $Pref::Server::MissionName);
%ServerAuth.addPostField("info", $Pref::Server::Info);
%ServerAuth.addPostField("type", $Server::MissionType);
%ServerAuth.addPostField("connp", $Server::PlayerCount);
%ServerAuth.addPostField("maxp", $Pref::Server::MaxPlayers);
%ServerAuth.addPostField("port", $Pref::Server::Port);
%ServerAuth.addPostField("host", $Pref::Player::Name);
%ServerAuth.setFinishCallback("handleServerUpdResponse");
%ServerAuth.setFailedCallback("handleServerUpdFailure");
%ServerAuth.setProgressCallback("handleServerUpdProgress");
echo("Transmitting Server Heartbeat...");
%ServerAuth.start();
}
}
function handleServerUpdResponse(%reg){
while(!%reg.isEOF()){
%line = %reg.readLine();
if(%line $= ("ONLINE:" SPC $Server::ID SPC $Server::Key)){
$Server::HeartbeatSchedule = schedule(90000, 0, heartbeat);
} else if(%line $= "ERROR: UNREGISTERED SERVER"){
error("Server is no longer registered");
$Server::Registered = 0;
schedule(1000, 0, registerserver);
} else{
handleServerUpdFailure(%reg);
}
}
%reg.delete();
}
function handleServerUpdFailure(%reg){
warn("Could not update server. Status follows: " @ %reg.getStatus());
warn("Retry in 20 seconds");
$Server::HeartbeatSchedule = schedule(20000, 0, heartbeat);
%reg.delete();
}
function handleServerUpdProgress(%reg){
%total = %reg.getSizeTotal();
%now = %reg.getSizeNow();
//-^ save this for a gui
}This code handles keep up to date information on the servers. Information expires after 3 minutes, and update attempts happen every minute and half. If heartbeat doesn't go through, it retries every 20 seconds until one does go through.function registerserver(){
//check to see if the host even wants to register with cemetech, and if the server is already registered.
if($pref::Net::DisplayOnMaster != 0 && $Server::Registered != 1){
%ServerAuth = new SimCurl();
%ServerAuth.setUrl("www.cemetech.net/scripts/torqueauth.php?regserver" @ (($Pref::Server::Password !$= "") ? "&needspass" : ""));
%ServerAuth.addPostField("name",$Pref::Server::Name);
%ServerAuth.addPostField("mission", $Pref::Server::MissionName);
%ServerAuth.addPostField("info", $Pref::Server::Info);
%ServerAuth.addPostField("type", $Server::MissionType);
%ServerAuth.addPostField("connp", $Server::PlayerCount);
%ServerAuth.addPostField("maxp", $Pref::Server::MaxPlayers);
%ServerAuth.addPostField("port", $Pref::Server::Port);
%ServerAuth.addPostField("host", $Pref::Player::Name);
%ServerAuth.setFinishCallback("handleServerRegResponse");
%ServerAuth.setFailedCallback("handleServerRegFailure");
%ServerAuth.setProgressCallback("handleServerRegProgress");
echo("Registering Server...");
%ServerAuth.start();
}
}
function handleServerRegResponse(%reg){
while(!%reg.isEOF()){
%line = %reg.readLine();
echo(%line);
if(getword(%line, 0) $= "ONLINE:"){
$Server::ID = getword(%line, 1);
$Server::Key = getword(%line, 2);
echo("Server registered with Cemetech");
$Server::Registered = 1;
if($Server::UnprocessedUserpairs !$= "") $Server::UnprocessedSchedule = schedule(1000, 0, beginProcessing);
$Server::HeartbeatSchedule = schedule(90000, 0, heartbeat);
}
}
%reg.delete();
}
function handleServerRegFailure(%reg){
error("Could not register server. Status follows: " @ %reg.getStatus());
%reg.delete();
}
function handleServerRegProgress(%reg){
%total = %reg.getSizeTotal();
%now = %reg.getSizeNow();
if(%total) echo("Server Registration:" @ ((%now/%total)*100) @ "%");
else echo("Server Registration: progress unknown");
//-^ save this for a gui
}This is the preliminary registration process with Cemetech. If it succeeds it also sets up the heartbeats and begins checking through any username/hashedSID pairs that have stacked up while we were busy registering.function beginProcessing(){
if($Server::UnprocessedUserpairs !$=""){
%next = getRecord($Server::UnprocessedUserpairs, 0);
%key = getField(%next, 0);
%name = removeField(%next, 0);
$Server::UnprocessedUserpairs = removeRecord($Server::UnprocessedUserpairs, 0);
processUserKeyPair(%key, %name);
}
if($UnprocessUserPairs !$=""){
$Server::UnprocessedSchedule = schedule(1000, 0, beginProcessing);
} else $Server::UnprocessedUserpairs = 0;
}
function handlePairCheckProgress(%pc){
%total = %pc.getSizeTotal();
%now = %pc.getSizeNow();
if(%total) echo("Pairchecking:" @ ((%now/%total)*100) @ "%");
else echo("Pairchecking: progress unknown");
//-^ save this for a gui
}
function handlePairCheckResponse(%pc){
if($Pref::Net::Displayonmaster != 0){
%error = 0;
while(isObject(%pc) && !%pc.isEOF()){
%line = %pc.readLine();
echo(%line);
if(getword(%line, 0) $= "EXPECTED:"){
%uname = restwords(%line);
if(%uname !$= %pc.username){
%error = 1;
}
} else if(%line $= "ERROR: INVALID SESSION"){
%error = 1;
} else{
handlePairCheckFailure(%pc);
}
}
if(%error){
%cl = findClientByName(%pc.username);
if(isObject(%cl)){
%ip = %cl.getRawIP();
if(%ip $= "local"){
if (isObject(ServerConnection))
ServerConnection.delete();
disconnectedCleanup();
destroyServer();
messageboxok("Oops!","Your server is setup to register with Cemetech, but you aren't logged in!");
} else{
%cl.delete("This server is registered with Cemetech. You must use login in to Cemetech before you can join");
}
}
}
if(isObject(%pc))
%pc.delete();
}
}
function handlePairCheckFailure(%pc){
war ("Could not check userpair. Status follows: " @ %pc.getStatus());
//Probably should throw this userpair back in the batch to try later
$Server::UnprocessedUserpairs = %pc.sidhash TAB %pc.username NL $Server::UnprocessedUserpairs;
if(!$Server::UnprocessedUserpairs) schedule(1000, 0, beginProcessing);
%pc.delete();
}
function processUserKeyPair(%key, %name){
echo(%key SPC "wants to register as '" @ %name @ "'" );
%paircheck = new SimCurl();
%paircheck.username = %name;
%paircheck.sidhash = %key;
%paircheck.setUrl("http://www.cemetech.net/scripts/torqueauth.php?paircheck&sidhash=" @ %paircheck.sidhash @ "&username=" @ URLEncode(%paircheck.username) @ "&key=" @ $Server::Key @ "&id=" @ $Server::ID);
%paircheck.setFinishCallback("handlePairCheckResponse");
%paircheck.setFailedCallback("handlePairCheckFailure");
%paircheck.setProgressCallback("handlePairCheckProgress");
echo("Starting Paircheck");
%paircheck.start();
}This bit is a little more interesting since it has to handle booting players who don't have a valid session open. Note that using messageboxok() in a server side script is probably bad form, but we check first to make sure that that only pops up on the local client. We have code elsewhere that tries to prevent them from starting a publicly registered server at all if they aren't logged in. That's all for the TorqueScript side of things (until we get to checking the server list from the client), I'll add a look at the PHP side of things in a comment immediately following this.Suggestions/bugfixes/improvements/cookies/exclamations-of-joy are always welcome.
About the author
C.S. PhD student at Brown University. Project lead for FreeBuild. Administrator, Cemetech tech community. Webmaster for the Village2Village Projects and the Vermont Sustainable Heating Initiative.

Torque 3D Owner Thomas -elfprince13- Dickerson
[edit]
Here ya'll go.
www.torquepowered.com/community/resources/view/19281