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.
#2
02/02/2010 (6:15 pm)
function tbgSAXformatstring($in){
return $in;
}This is a wrapper function. Cemetech's version of SAX doesn't use it anymore. The original function can be copied from SAXformatstring() in a vanilla installation of SAX.
#3
... function is split and continues ...
02/02/2010 (6:17 pm)
function tbgSaxAddItem($type,$who,$what,$where) {
global $phpbb_root_path,$phpEx,$db,$sql,$result,$userdata,$lang;
if ($type==0)
{
$out=tbgSAXformatstring('<span class="saxgray">***Server <a class="saxgray" href="'.$where.'">'.stripslashes($who).'</a> is now running a '. $what[0] .' game in '.$what[1] .'.<span>');
} else if ($type==1) {
$what = preg_replace("/[url]([A-Za-z0-9/~-+%&#@=_:.^*?]+)[/url]/","<a href="" target="_blank"></A>",$what);
$what = preg_replace("/[url=([A-Za-z0-9/~-+%&#@=_:.^*)]([wsd]+)[/url]/","<a href="" target="_blank"></A>",$what);
$what = preg_replace("/@([wd-'_]+)/","<span class="saxred">@</span>",$what);
$whats = split(' ',strtolower($what));
$out = '';
$realtxt = true;
if ($whats[0] == "/me")
{
$out.=tbgSAXformatstring('<span class="saxgray">*'.$who.' '.substr($what,4,strlen($what)-4).'</span>');
$realtxt = false;
}
if (substr($what,0,6) == "!calc ")
{
$what2 = substr($what,6,strlen($what)-6);
$out.=tbgSAXformatstring('<span class="saxmaroon">saxjax:</span> <span class="saxblack">'.$who.': '.$what2.'='.matheval($what2).'</span>');
$realtxt = false;
}
if ($whats[0] == "where's")
{
$findwho = preg_replace("/[^wd]/","",strtolower($whats[1]));
$length = strlen($findwho);
$sql = "SELECT username FROM ".USERS_TABLE." WHERE LCASE(SUBSTR(username,1,$length)) = '$findwho'";
if( !($result = $db->sql_query($sql)) )
{
message_die(CRITICAL_ERROR, "Could not query config information for $sql");
}
$totalfound = $db->sql_numrows($result);
if ($totalfound == 1)
{
$row = $db->sql_fetchrow($result);
$sql2 = "SELECT session_user_id FROM ".SESSIONS_TABLE." WHERE session_user_id = '" . $row['user_id'] . "'";
if( !($result2 = $db->sql_query($sql2)) )
{
message_die(CRITICAL_ERROR, "Could not query session information for $sql2");
}
$online = false;
while ($row2 = $db->sql_fetchrow($result2))
{
if ($row2['session_time'] > time()-300){
$online = true;
}
}... function is split and continues ...
#4
02/02/2010 (6:18 pm)
$outstring = $row['username'] . ' was last seen at ' . date('H:i:s',$row['user_lastvisit']);
if (date('m d Y',$row['user_lastvisit']) == date('m d Y'))
{
$outstring .= ' today.';
} else {
$outstring .= ' on ' . date('m/d/y',$row['user_lastvisit']) . '.';
}
$out .= tbgSAXformatstring('<span class="saxmaroon">'.$lang['SAXSAX'].':</span> <span class="saxblack">'.$outstring.'</span>');
} else {
if ($totalfound == 0)
{ //no users found
$out .= tbgSAXformatstring('<span class="saxmaroon">saxjax:</span> <span class="saxblack">I could not find a user with that name, '.$who.'.</span>');
} else { //too many users found
$out .= tbgSAXformatstring('<span class="saxmaroon">saxjax:</span> <span class="saxblack">I found more than one user. Could you be more specific, '.$who.'?</span>');
}
}
}
if (
($whats[0] == "what" && $whats[1] == "is") ||
($whats[0] == "where" && $whats[1] == "is") ||
($whats[0] == "wheres")
)
{
if (($whats[0] == "what" && $whats[1] == "is") || ($whats[0] == "where" && "is")) $uname = 2;
if ($whats[0] == "wheres") $uname = 1;
$findwho = preg_replace("/[^wd]/","",strtolower($whats[$uname]));
$length = strlen($findwho);
$sql = "SELECT * FROM ".USERS_TABLE." WHERE LCASE(SUBSTR(username,1,$length)) = '$findwho'";
if( !($result = $db->sql_query($sql)) )
{
message_die(CRITICAL_ERROR, "Could not query user information for $sql");
}
$totalfound = $db->sql_numrows($result);
if ($totalfound == 1)
{
$row = $db->sql_fetchrow($result);
$sql2 = "SELECT * FROM ".SESSIONS_TABLE." WHERE session_user_id = '" . $row['user_id'] . "'";
$result2 = $db->sql_query($sql2);
$online = false;
while ($row2 = $db->sql_fetchrow($result2))
{
if ($row2['session_time'] > time()-300) $online = true;
}
$outstring = '';... function is split and continues ...
#5
02/02/2010 (6:20 pm)
if ($online)
{
$outstring .= $row['username'] . ' is currently online. ';
}
$outstring .= $row['username'] . ' was last online at ' . date('H:i:s',$row['user_lastvisit']);
if (date('m d Y',$row['user_lastvisit']) == date('m d Y'))
{
$outstring .= ' today.';
} else {
$outstring .= ' on ' . date('m/d/y',$row['user_lastvisit']) . '.';
}
$out .= tbgSAXformatstring('<span class="saxmaroon">saxjax:</span> <span class="saxblack">'.$outstring.'</span>');
} else {
if ($totalfound == 0)
{ //no users found
$out .= tbgSAXformatstring('<span class="saxmaroon">saxjax:</span> <span class="saxblack">I don't know who you mean, '.$who.'.</span>');
} else { //too many users found
$out .= tbgSAXformatstring('<span class="saxmaroon">saxjax:</span> <span class="saxblack">I found more than one user. Could you be more specific, '.$who.'?</span>');
}
}
}
if ($realtxt == true){
$innertextchunk = ($who[0] != "") ? '<span class="saxuser"><span class="saxserver">[<a class="saxgray" href="'.$where.'">'.$who[0].'</a>]: </span><span class="saxinneruser">' . $who[1] . ': </span></span>' : $who[1] . ': ';
$out.=tbgSAXformatstring('<span class="saxmaroon">' . $innertextchunk . '</span> <span class="saxblack">'.$what.'</span>');
}
} else if ($type==2) {
$out=tbgSAXformatstring('<span class="saxgray">***'.$who.' has logged in</span>');
} else if ($type==3) {
$out=tbgSAXformatstring('<span class="saxgray">***'.$who.' has logged out</span>');
} else if ($type==4) {
$out=tbgSAXformatstring('<span class="saxgray">***'.$who.' has entered the room</span>');
} else if ($type==5) {
$out=tbgSAXformatstring('<span class="saxgray">***'.$who.' has left the room</span>');
} else { $out = tbgSAXformatstring("Umm, wtf was that?");}
$saxkey = '***** PRIVATE KEY REDACTED ******';
$URI = '/scripts/saxsay.php?key='.$saxkey.'&message='.urlencode(str_replace("+",'_-_',$out));
$Host = 'www.cemetech.net';
$ReqHeader = "GET $URI HTTP/1.1n".
"Host: $Hostn".
"User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.0.1) Gecko/2008071222 Firefox/3.0.1 n".
"accept: */*nrnrn";
// Open the connection to the host
$socket = fsockopen($Host, 80, &$errno, &$errstr);
if (!$socket)
{
$Result["errno"] = $errno;
$Result["errstr"] = $errstr;
return $Result;
}
$idx = 0;
fputs($socket, $ReqHeader);
while (!feof($socket))
$Result[$idx++] = fgets($socket, 128);
}You can safely ignore this if you aren't using SAX, but you'll need to comment out any calls tbgSaxAddItem() elsewhere in the code. Vanilla SAX also doesn't need the $saxkey stuff at the end (which only applies to websites which are part of the Cemetech network), and you should base that part of your function on the saxAddItem(). in your installation.
#6
02/02/2010 (6:21 pm)
// session id check
if (!empty($HTTP_POST_VARS['sid']) || !empty($HTTP_GET_VARS['sid']))
{
$sid = (!empty($HTTP_POST_VARS['sid'])) ? $HTTP_POST_VARS['sid'] : $HTTP_GET_VARS['sid'];
} else {
$sid = '';
}
$sql = "DELETE FROM Servers_Online WHERE LastSeen < " . (time() - 180) .";";
//for once we don't care enough if this fails to disrupt the script
$result = $db->sql_query($sql);More phpBB initialization stuff + removal of expired servers.if(isset($HTTP_GET_VARS['showserver'])) //Show INFO for a single server, by ID.
{
$ServerID = tbgServerClean($_GET['id']);
if($ServerID != $_GET['id']){
die("ERROR: GTFO HACKER N00B");
}
if(strlen($ServerID) != 32){
die("ERROR: INVALID ServerID");
}
$sql = "SELECT NeedsPass, ServerName, ConnectedPlayers, MaxPlayers, ServerIP, ServerMission, ServerInfo, GameType FROM Servers_Online WHERE ServerID='" . $ServerID ."';";
if ( !($result = $db->sql_query($sql)) )
{
die("ERROR: COULDN'T RETRIEVE SERVER LIST");
}
if($row = $db->sql_fetchrow($result)){
die(serverLine($row));
} else {
die("ERROR: NO SUCH SERVER");
}
} else if (isset($HTTP_GET_VARS['listservers'])) { //Create Server Listing!
$sql = "SELECT NeedsPass, ServerName, ConnectedPlayers, MaxPlayers, ServerIP, ServerMission, ServerInfo, GameType, LastSeen FROM Servers_Online WHERE LastSeen >= " . (time() - 180) . " ORDER BY LastSeen DESC"; //WHERE LastSeen >= " . (time() - 180) . ";";
if ( !($result = $db->sql_query($sql)) )
{
die("ERROR: COULDN'T RETRIEVE SERVER LIST");
}
while($row = $db->sql_fetchrow($result)){
print(serverLine($row));
}
die("END LIST");
} else if (isset($HTTP_GET_VARS['regserver'])) { //Register a new server
if(!isset($HTTP_POST_VARS['name']))
{
die("ERROR: SERVER NAME REQUIRED");
}
if(!isset($HTTP_POST_VARS['type']))
{
die("ERROR: GAMETYPE REQUIRED");
}
if(!isset($HTTP_POST_VARS['maxp']) || !isset($HTTP_POST_VARS['connp']))
{
die("ERROR: PLAYER INFORMATION REQUIRED");
}
if(!isset($HTTP_POST_VARS['mission']))
{
die("ERROR: MISSION NAME REQUIRED");
}
if(!isset($HTTP_POST_VARS['port']) || intval($_POST['port']) <= 0 || intval($_POST['port']) > 65535)
{
$port = '28000';
} else {
$port = intval($_POST['port']);
}
$ServerID = md5(time() . ":" . rand());
$ServerKEY = md5(rand());
$ServerIP = $_SERVER['REMOTE_ADDR'] . ':' . $port;
$LastSeen = time();
$ServerName = tbgServerClean($_POST['name']);
$ServerMission = tbgServerClean($_POST['mission']);
$ServerInfo = tbgServerClean($_POST['info']);
$GameType = tbgServerClean($_POST['type']);
$ConnectedPlayers = tbgServerClean($_POST['connp']);
$MaxPlayers = tbgServerClean($_POST['maxp']);
$NeedsPass = isset($HTTP_GET_VARS['needspass']) ? 1 : 0;
$sql = "INSERT INTO Servers_Online (ServerID, ServerKEY, NeedsPass, ServerName, GameType, ConnectedPlayers, MaxPlayers, ServerIP, ServerMission, ServerInfo, LastSeen) VALUES ('$ServerID', '$ServerKEY', $NeedsPass, '$ServerName', '$GameType', $ConnectedPlayers, $MaxPlayers, '$ServerIP', '$ServerMission', '$ServerInfo', $LastSeen)";
if ( !($result = $db->sql_query($sql)) )
{
die("ERROR: COULDN'T INSERT SERVER");
}
tbgSaxAddItem(0,$ServerName,Array($GameType, $ServerMission),"http://www.cemetech.net/scripts/torqueauth.php?showserver&id=" . $ServerID);
die("ONLINE: $ServerID $ServerKEY");
}
#7
02/02/2010 (6:22 pm)
else if (isset($HTTP_GET_VARS['updserver'])) { //Register a new server
$ServerID = tbgServerClean($_GET['id']);
$ServerKEY = tbgServerClean($_GET['key']);
if(!isset($HTTP_POST_VARS['port']) || intval($_POST['port']) <= 0 || intval($_POST['port']) > 65535)
{
$port = '28000';
} else {
$port = intval($_POST['port']);
}
$ServerIP = $_SERVER['REMOTE_ADDR'] . ':' . $port;
if(!checkServer($ServerID, $ServerKEY, $ServerIP)){
die("ERROR: UNREGISTERED SERVER");
}
$LastSeen = time();
$ServerName = tbgServerClean($_POST['name']);
$ServerMission = tbgServerClean($_POST['mission']);
$ServerInfo = tbgServerClean($_POST['info']);
$GameType = tbgServerClean($_POST['type']);
$ConnectedPlayers = tbgServerClean($_POST['connp']);
$MaxPlayers = tbgServerClean($_POST['maxp']);
$NeedsPass = isset($HTTP_GET_VARS['needspass']) ? 1 : 0;
$sql = "UPDATE Servers_Online SET NeedsPass=$NeedsPass, ServerName='$ServerName', GameType='$GameType', ConnectedPlayers=$ConnectedPlayers, MaxPlayers=$MaxPlayers, ServerMission='$ServerMission', ServerInfo='$ServerInfo', LastSeen=$LastSeen WHERE ServerID='" . $ServerID . "' AND ServerKEY='" . $ServerKey . "' AND ServerIP='" . $ServerIP . "';";
if ( !($result = $db->sql_query($sql)) )
{
die("ERROR: COULDN'T UPDATE SERVER");
}
die("ONLINE: $ServerID $ServerKEY");
} else if (isset($HTTP_GET_VARS['saxsay'])) {
if($userdata['session_logged_in'])
{
$username = $userdata['username'];
$ServerID = ""; //temporarily
$tempsay = stripslashes(strip_tags(urldecode($_GET['what'])));
$tempsay = str_replace("_-_",'+',$tempsay);
tbgSaxAddItem(1,Array(getServerName($ServerID),$username),$tempsay,"http://www.cemetech.net/scripts/torqueauth.php?showserver&id=" . $ServerID);
die("OK");
} else {
die("ERROR: NOT LOGGED IN");
}
} else if( isset($HTTP_GET_VARS['paircheck']) ){
if(!isset($HTTP_POST_VARS['port']) || intval($_POST['port']) <= 0 || intval($_POST['port']) > 65535)
{
$port = '28000';
} else {
$port = intval($_POST['port']);
}
$ServerID = tbgServerClean($_GET['id']);
$ServerKEY = tbgServerClean($_GET['key']);
$ServerIP = $_SERVER['REMOTE_ADDR'] . ':' . $port;
$username = tbgServerClean($_GET['username']);
$sidhash = tbgServerClean($_GET['sidhash']);
if( !checkServer($ServerID, $ServerKEY, $ServerIP) ){
die("ERROR: UNREGISTERED SERVER");
}
$sql = "SELECT forum_users.username AS username FROM forum_sessions LEFT JOIN forum_users ON forum_sessions.session_user_id = forum_users.user_id WHERE MD5(session_id) = '" . strtolower($sidhash) . "' AND session_logged_in = 1";
if ( !($result = $db->sql_query($sql)) )
{
die("ERROR: COULDN'T FETCH USER DATA");
}
if(mysql_num_rows($result) != 1){
die("ERROR: INVALID SESSION");
}
$row = mysql_fetch_row($result);
die("EXPECTED: " . $row[0]);
}
#8
Here we have the changes that handle the saxsay + login/logout functionality described in previous blog, as well as cases to handle username/hashedSID pair checking, server registration, server heartbeats, and querying the whole server list, and querying info about a single server. I'll make one more post in this comment thread with the client side details of querying the PHP generated server list after I get back from dinner.
02/02/2010 (6:23 pm)
else if( isset($HTTP_POST_VARS['login']) || isset($HTTP_GET_VARS['login']) || isset($HTTP_POST_VARS['logout']) || isset($HTTP_GET_VARS['logout']) ) {
if( ( isset($HTTP_POST_VARS['login']) || isset($HTTP_GET_VARS['login']) ) && (!$userdata['session_logged_in'] || isset($HTTP_POST_VARS['admin'])) )
{
$username = isset($HTTP_POST_VARS['username']) ? phpbb_clean_username($HTTP_POST_VARS['username']) : '';
$password = isset($HTTP_POST_VARS['password']) ? $HTTP_POST_VARS['password'] : '';
$sql = "SELECT user_id, username, user_password, user_active, user_level FROM " . USERS_TABLE . " WHERE username = '" . str_replace("'", "''", $username) . "'";
if ( !($result = $db->sql_query($sql)) )
{
die("ERROR: COULDN'T FETCH USER DATA");
}
if( $row = $db->sql_fetchrow($result) )
{
if( md5($password) == $row['user_password'] && $row['user_active'] )
{
$autologin = ( isset($HTTP_POST_VARS['autologin']) ) ? TRUE : 0;
//BEGIN SAX MOD changes
//tbgSaxAddItem(2,$row['username'],null,null);
//END SAX MOD CHANGES
$admin = (isset($HTTP_POST_VARS['admin'])) ? 1 : 0;
$session_id = session_begin($row['user_id'], $user_ip, PAGE_INDEX, FALSE, $autologin, $admin);
if( $session_id )
{
tbgSaxAddItem(2,$row['username'],null,null);
die("LOGIN: " . $session_id['session_id'] . " " . $row['username']);
} else {
die("ERROR: COULDN'T START A NEW SESSION");
}
}
die("ERROR: BAD PASSWORD");
} else {
die("ERROR: NONEXISTENT USER");
}
} else if( ( isset($HTTP_GET_VARS['logout']) || isset($HTTP_POST_VARS['logout']) ) && $userdata['session_logged_in'] ) {
// session id check
if ($sid == '' || $sid != $userdata['session_id'])
{
die("ERROR: BAD SESSION");
}
if( $userdata['session_logged_in'] )
{
//BEGIN SAX MOD changes
$olduname = $userdata['username'];
//END SAX MOD changes
session_end($userdata['session_id'], $userdata['user_id']);
//BEGIN SAX MOD changes
tbgSaxAddItem(3,$olduname,null,null);
//END SAX MOD changes
}
die("LOGOUT: SUCCESSFUL");
} else {
die("ERROR: NOTHING ATTEMPTED");
}
} else if(isset($HTTP_POST_VARS['check_session'])){
if($userdata['session_logged_in']){
die("LOGIN: " . $userdata['session_id'] . " " . $userdata['username']);
} else{
die("ERROR: BAD SESSION");
}
}
die("ERROR: NOTHING ATTEMPTED");
?>Here we have the changes that handle the saxsay + login/logout functionality described in previous blog, as well as cases to handle username/hashedSID pair checking, server registration, server heartbeats, and querying the whole server list, and querying info about a single server. I'll make one more post in this comment thread with the client side details of querying the PHP generated server list after I get back from dinner.
#9
02/02/2010 (7:24 pm)
The first thing to do to be able to build your own server list, is to follow Tom Bampton's first tip here and expose queryFavoriteServers()to the console. In my code I changed $Pref::Client::ServerFavorite and $Pref::Client::ServerFavoriteCount to $Client::ServerFavorite and $Client::ServerFavoriteCount, respectively, because I didn't want my server list to be persistent and written to file between sessions. This has to be done in both engine code and in script. I stripped the favorite server list gui from the engine, because we weren't using it anyway (though if you needed it, it would be pretty easy to duplicate the functions in engine and have both). Then I rewrote JoinServerGui.cs to try querying torqueauth.php and building a favorite server list from that, instead of using queryMasterServer(). I did however choose to leave queryMasterServer code present as a fallback, in case Cemetech was temporarily unavailable. My JoinServerGui.cs code follows.
#10
02/02/2010 (7:25 pm)
function resetInternalServerList(){
deleteVariables("$Client::ServerFavorite*");
}
function addServerToInternalList(%name, %address)
{
$Client::ServerFavorite[$Client::ServerFavoriteCount] = %name TAB %address;
echo("Serv:" SPC $Client::ServerFavorite[$Client::ServerFavoriteCount]);
$Client::ServerFavoriteCount++;
}
//----------------------------------------
function JoinServerGui::onWake()
{
// Double check the status. Tried setting this the control
// inactive to start with, but that didn't seem to work.
JS_joinServer.setActive(JS_serverList.rowCount() > 0);
clearServerInfo();
}
function JoinServerGui::onSleep()
{
//Nothing here at the moment
}
//----------------------------------------
function JoinServerGui::query()
{
resetInternalServerList();
if($Pref::Player::Isloggedin == 0){
canvas.popdialog(joinservergui);
canvas.pushdialog(joinbyip);
messageboxok("You are not logged in!","Sorry, but you are not logged in to Cemetech! Without being logged in you cannot join online servers!nYou can join servers given you know their IP.");
return;
}
%cemetechQuery = new SimCurl();
%cemetechQuery.setUrl("www.cemetech.net/scripts/torqueauth.php?listservers");
%cemetechQuery.setFinishCallback("handleCemPollResponse");
%cemetechQuery.setFailedCallback("handleCemPollFailure");
%cemetechQuery.setProgressCallback("handleCemPollProgress");
%cemetechQuery.start();
clearServerInfo();
JS_joinServer.setActive(false);
JS_queryMaster.setActive(false);
JS_statusText.setText(%msg);
JS_statusBar.setValue(0);
JS_serverList.clear();
JS_serverList.setActive(false);
}
function handleCemPollProgress(%cemetechQuery){
JS_serverList.setActive(false);
if (!JS_queryStatus.isVisible()) JS_queryStatus.setVisible(1);
%total = %cemetechQuery.getSizeTotal();
%now = %cemetechQuery.getSizeNow();
if(%total){
JS_statusText.setText("Polling for Servers:" @ ((%now/%total)*100) @ "%");
JS_statusBar.setValue(mFloor((%now/%total)*100));
}
else{
JS_statusText.setText("Polling for Servers: progress unknown");
JS_statusBar.setValue(0);
}
}
function handleCemPollResponse(%cemetechQuery){
JS_queryMaster.setActive(true);
JS_queryLAN.setActive(true);
JS_queryStatus.setVisible(0);
JS_serverList.setActive(true);
JS_serverList.clear();
%i = 0;
%line = "";
while(!%cemetechQuery.isEOF() && %line !$= "END LIST"){
%line = %cemetechQuery.readLine();
if(%line !$= "END LIST" && %line !$= ""){
//%needsP = getField(%line, 0);
%name = getField(%line, 1);
//
//GET PING NOW THAT IS ALL
//
//%ping = 0;
//%playercount = getField(%line, 3);
//%maxplayers = getField(%line, 4);
%ipaddr = getField(%line, 5);
//%missionname = getField(%line, 6);
//%info = getField(%line, 7);
//%missiontype = getField(%line, 8);
addServerToInternalList(%name, %ipaddr);
queryFavoriteServers(0);
}
else {
}
}
echo("Query Complete");
%cemetechQuery.delete();
}
#11
02/02/2010 (7:27 pm)
function handleCemPollFailure(%cemetechQuery){
//oh noes, fallback quick
%cemetechQuery.delete();
error("Polling Cemetech failed, falling back to Master Server");
queryMasterServer(
0, // Query flags
$Client::GameTypeQuery, // gameTypes
$Client::MissionTypeQuery, // missionType
0, // minPlayers
100, // maxPlayers
0, // maxBots
2, // regionMask
0, // maxPing
100, // minCPU
0 // filterFlags
);
}
//----------------------------------------
function JoinServerGui::queryLan()
{
queryLANServers(
28000, // lanPort for local queries
0, // Query flags
$Client::GameTypeQuery, // gameTypes
$Client::MissionTypeQuery, // missionType
0, // minPlayers
100, // maxPlayers
0, // maxBots
2, // regionMask
0, // maxPing
0, // minCPU - Should be 100, but at the moment CPU stuff isn't working.
0 // filterFlags
);
clearServerInfo();
}
//----------------------------------------
function JoinServerGui::cancel()
{
cancelServerQuery();
JS_queryStatus.setVisible(false);
clearServerInfo();
}
//----------------------------------------
function checkjoinserver()
{
if($ServerInfo::Password){
canvas.pushdialog(serverpass);
}
else
joinservergui.join();
}
//----------------------------------------
function JoinServerGui::join()
{
disconnect();
cancelServerQuery();
%id = JS_serverList.getSelectedId();
// The server info index is stored in the row along with the
// rest of displayed info.
%index = getField(JS_serverList.getRowTextById(%id),3);
if (setServerInfo(%index)) {
%conn = new GameConnection(ServerConnection);
%conn.setConnectArgs($pref::Player::Name, getStringMD5($pref::Player::SID));
%conn.setJoinPassword($Client::Password);
%conn.connect($ServerInfo::Address);
}
else
MessageBoxOk("No Server Selected","You need to select a server. Press either of the Query buttons to list available servers or go "<<Back" to the main menu and press "Start Server" to create your own.");
echo(getField(JS_serverList.getRowTextById(%id),5));
clearServerInfo();
}
#12
02/02/2010 (7:28 pm)
//----------------------------------------
function JoinServerGui::joinwithIP()
{
disconnect();
cancelServerQuery();
// The server info index is stored in the row along with the
// rest of displayed info.
%conn = new GameConnection(ServerConnection);
%conn.setConnectArgs($pref::Player::Name, getStringMD5($pref::Player::SID));
%conn.setJoinPassword($Client::Password);
$Client::JoinIPFinal = $Client::JoinIP @ ":" @ $Client::JoinIPPort;
%conn.connect($Client::JoinIPFinal);
clearServerInfo();
}
//-----adds more server info things
function JoinServerGui::serverinfo()
{
%id = JS_serverList.getSelectedId();
ServerInfo.setText(" ");
ServerInfo.setText(getField(JS_serverList.getRowTextById(%id),6));
ServerIP.setText(getField(JS_serverList.getRowTextById(%id),5));
ServerName.setText(getField(JS_serverList.getRowTextById(%id),1));
//ServerPCspeed.setText(getField(JS_serverList.getRowTextById(%id),7));
}
//----------------------------------------
function JoinServerGui::exit()
{
cancelServerQuery();
//queryIRCend();
Canvas.setContent(mainMenuGui);
clearServerInfo();
}
//----------------------------------------
function JoinServerGui::update()
{
// Copy the servers into the server list.
//JS_queryStatus.setVisible(false);
JS_serverList.clear();
%sc = getServerCount();
for (%i = 0; %i < %sc; %i++) {
setServerInfo(%i);
echo("ping" SPC $ServerInfo::Ping);
//if(getword($ServerInfo::MissionType,0) $= "Sandbox" || $Pref::Filter == 0)
JS_serverList.addRow(%i,
($ServerInfo::Password? "Yes": "No") TAB
$ServerInfo::Name TAB
$ServerInfo::Ping TAB
$ServerInfo::MissionName TAB
$ServerInfo::PlayerCount @ "/" @ $ServerInfo::MaxPlayers TAB
$ServerInfo::Address TAB
$ServerInfo::Info TAB
$ServerInfo::MissionType TAB
$ServerInfo::CPUSpeed TAB
$ServerInfo::Status);
}
JS_serverList.sort(0);
JS_serverList.setSelectedRow(0);
JS_serverList.scrollVisible(0);
JS_joinServer.setActive(JS_serverList.rowCount() > 0);
}
//----------------------------------------
function onServerQueryStatus(%status, %msg, %value)
{
// Update query status
// States: start, update, ping, query, done
// value = % (0-1) done for ping and query states
if (!JS_queryStatus.isVisible())
JS_queryStatus.setVisible(1);
JS_serverList.setActive(false);
JoinServerGui.update();
switch$ (%status) {
case "start":
JS_joinServer.setActive(false);
JS_queryMaster.setActive(false);
JS_statusText.setText(%msg);
JS_statusBar.setValue(0);
JS_serverList.clear();
echo("Starting Query");
case "ping":
JS_statusText.setText("Ping Servers: " @ mFloor(%value*100) @"%");
JS_statusBar.setValue(%value);
case "query":
JS_statusText.setText("Query Servers: " @ mFloor(%value*100) @"%");
JS_statusBar.setValue(%value);
case "done":
JS_queryMaster.setActive(true);
//JS_queryIRC.setActive(true);
JS_queryStatus.setVisible(0);
JS_serverList.setActive(true);
}
}
function querysort(%a){JS_serverList.sort(%a);}
function querysort2(%a) {JS_serverlist.invsort(%a);}
function clearServerInfo() {
ServerInfo.setText(" ");
ServerIP.setText(" ");
ServerName.setText(" ");
}
function JS_ServerList::invsort(%this,%a) {
JS_ServerList.sort(%a);
%rowcount = JS_ServerList.rowcount();
for(%i=1;%i<%rowcount;0)
$JSArray[%i++] = JS_ServerList.getRowTextbyId(%i);
//JS_ServerList.clear();
%row = %rowcount+1;
for(%i=1;%i<%rowcount;%i++)
JS_ServerList.setRowbyid(%i,$JSArray[%row--]);
}
$Joinserverguitog = 0; //Noob protect
function togglejoinservergui (%val)
{
if(%val && $Joinserverguitog == 0)
canvas.pushDialog(joinservergui);
else if(%val && $Joinserverguitog == 1)
canvas.popDialog(joinservergui);
}
function joinservergui::OnWake() {
$Joinserverguitog = 1;
JoinServerGui.query();
}
function joinservergui::OnSleep() {
$Joinserverguitog = 0;
}
#13
02/02/2010 (7:36 pm)
OMG this is great! this is gonna be a definite bookmark! thank you very much
#14
02/02/2010 (7:44 pm)
Hey Scooby, I thought you'd enjoy that. Feel free to let me know if you have any question.
#15
02/02/2010 (11:16 pm)
Quick update, fixing some issues in serverauth.cs$Server::Key = getword(%line, 2);
echo("Server registered with Cemetech");
$Server::Registered = 1;
- if($Server::UnprocessedUserpairs !$= "") $Server::UnprocessedSchedule = schedule(1000, 0, beginProcessing);
+ if($Server::UnprocessedUserpairs !$= ""){
+ $Server::UnprocessedSchedule = schedule(1000, 0, beginProcessing);
+ }
$Server::HeartbeatSchedule = schedule(90000, 0, heartbeat);
}if($UnprocessUserPairs !$=""){
$Server::UnprocessedSchedule = schedule(1000, 0, beginProcessing);
- } else $Server::UnprocessedUserpairs = 0;
+ } else $Server::UnprocessedSchedule = 0;
}
#16
02/03/2010 (12:47 am)
and, one more quick fix, in torqueauth.php.-$sql = "UPDATE Servers_Online SET NeedsPass=$NeedsPass, ServerName='$ServerName', GameType='$GameType', ConnectedPlayers=$ConnectedPlayers, MaxPlayers=$MaxPlayers, ServerMission='$ServerMission', ServerInfo='$ServerInfo', LastSeen=$LastSeen WHERE ServerID='" . $ServerID . "' AND ServerKEY='" . $ServerKey . "' AND ServerIP='" . $ServerIP . "';"; +$sql = "UPDATE Servers_Online SET NeedsPass=$NeedsPass, ServerName='$ServerName', GameType='$GameType', ConnectedPlayers=$ConnectedPlayers, MaxPlayers=$MaxPlayers, ServerMission='$ServerMission', ServerInfo='$ServerInfo', LastSeen=$LastSeen WHERE ServerID='$ServerID' AND ServerKEY='$ServerKEY' AND ServerIP='$ServerIP'";
#17
phpBB3 uses a database abstraction layer called "DBAL" and you'd need to make sure that all the my $db->function() code was replaced with the DBAL equivalent. You'll also need to replace the $userdata array with the data member of the user class. The session related stuff will likely need to be rewritten completely. The only other caveat I'm aware of is that I'm not quite sure what the phpBB3 equivalent to phpbb_clean_username() is. You can get more help on porting the code over at the phpBB community forum, or by digging around in your code and investigating the login mechanism.
These should all be useful resources for you:
http://wiki.phpbb.com/Database_Abstraction_Layer
http://wiki.phpbb.com/User_class
http://wiki.phpbb.com/Olympus_for_phpBB2_users
The SAX mod, should you choose to install it, is also phpBB2 only, but it only took me about 15 minutes to do the same porting process with it.
02/03/2010 (11:46 am)
Marc, It's been a long time since I shut down my personal phpBB3 site, which was mostly a development testbed, but all the TorqueScript stuff is the same. On the php side there are several changes you'd need to make (which are pretty easy--bordering on find and replace).phpBB3 uses a database abstraction layer called "DBAL" and you'd need to make sure that all the my $db->function() code was replaced with the DBAL equivalent. You'll also need to replace the $userdata array with the data member of the user class. The session related stuff will likely need to be rewritten completely. The only other caveat I'm aware of is that I'm not quite sure what the phpBB3 equivalent to phpbb_clean_username() is. You can get more help on porting the code over at the phpBB community forum, or by digging around in your code and investigating the login mechanism.
These should all be useful resources for you:
http://wiki.phpbb.com/Database_Abstraction_Layer
http://wiki.phpbb.com/User_class
http://wiki.phpbb.com/Olympus_for_phpBB2_users
The SAX mod, should you choose to install it, is also phpBB2 only, but it only took me about 15 minutes to do the same porting process with it.
#18
02/03/2010 (12:34 pm)
Awesome blog Thomas! You should definitely submit this as resource.
#19
02/03/2010 (12:38 pm)
I'll see what I can do about packaging it up as a resource during some inter-class time today. 
Torque 3D Owner Thomas -elfprince13- Dickerson
<?php define('IN_PHPBB', true); $phpbb_root_path = '../forum/'; include($phpbb_root_path . 'extension.inc'); include($phpbb_root_path . 'common.'.$phpEx); // // Set page ID for session management // $userdata = session_pagestart($user_ip, PAGE_TBG); init_userprefs($userdata); // // End session management //Standard phpBB script initialization. You'll need to change PAGE_TBG to a PAGE_* constant of your own choosing.function serverLine($row){ $line = $row['NeedsPass']; $line .= "t" . $row['ServerName']; //no PING from us, leave it blank $line .= "tt" . $row['ConnectedPlayers'] . "t" . $row['MaxPlayers']; $line .= "t" . $row['ServerIP']; $line .= "t" . $row['ServerMission']; $line .= "t" . $row['ServerInfo']; $line .= "t" . $row['GameType'] . "n"; return $line; }This function is used to print a row of information about a single server.function tbgServerClean($text){ return mysql_real_escape_string(str_replace("t", " ", str_replace("<", "", str_replace(">", "", stripslashes(strip_tags(urldecode($text))))))); }Input cleaning function.function getServerName($id){ global $db; $id = tbgServerClean($id); $sql = "SELECT ServerName FROM Servers_Online WHERE ServerID='" . $id . "';"; if( !($result = $db->sql_query($sql)) ) { die("ERROR: COULDN'T RETRIEVE SERVER LIST"); } if($row = $db->sql_fetchrow($result)){ return $row['ServerName']; } else{ return false; } }Returns a server's name based on it's ID.function checkServer($id, $key, $ip){ global $db; $id = tbgServerClean($id); $key = tbgServerClean($key); $sql = "SELECT LastSeen FROM Servers_Online WHERE ServerIP='" . $ip . "' AND ServerID='" . $id . "' AND ServerKEY='" . $key . "';"; if( !($result = $db->sql_query($sql)) ) { die("ERROR: COULDN'T RETRIEVE SERVER LIST"); } $totalfound = $db->sql_numrows($result); return ($totalfound == 1); }Checks to make sure that a given server id/key/ip triplet represent a valid server.