phpBB2 + SAX Chat Compilation Tutorial
by Thomas -elfprince13- Dickerson · 02/04/2010 (2:12 am) · 6 comments
phpBB2 and SAX Walkthrough: Part 1
In the development of FreeBuild I was presented with an interesting dilemma. I wanted to allow users to have a central place to register their in-game nicknames, share builds, and chat as a cohesive community, but the game servers are user hosted, and not running on any sort of official dedicated box over which I would have any control. To this end I set up a 3-part system to handle the following tasks
- Registering servers with Cemetech (the central login database + chat location, serving as a stand-in Master Server).
- Allowing clients to log in to Cemetech for chatting, protecting their usernames
- Checking that clients connecting to servers are authenticated with Cemetech.
The first thing is the client login. I originally tried implementing this with TGE's built in HTTP and TCP objects, but it was way too slow, and fairly hard to work with. So I compiled in bank's libcurl integration, and modified it to the point that you see here.
Here are my base client-side login functions:
function trylogin(%username, %password, %autologin){
%auth = new SimCurl();
%auth.setUrl("http://www.cemetech.net/scripts/torqueauth.php?login");
%auth.setCookieJar("core/client/cookies/auth.txt");
%auth.addPostField("username",%username);
%auth.addPostField("password",%password);
if(%autologin) %auth.addPostField("autologin","1");
%auth.setFinishCallback("handleLoginResponse");
%auth.setFailedCallback("handleLoginFailure");
%auth.setProgressCallback("handleLoginProgress");
$Pref::Player::SID = "";
echo("Logging in...");
%auth.start();
}
function handleLoginResponse(%auth){
while(!%auth.isEOF()){
%line = %auth.readLine();
if(getword(%line, 0) $= "LOGIN:"){
%line = restwords(%line);
$pref::Player::SID = getword(%line, 0);
$pref::Player::Name = restwords(%line);
echo("Logged in successfully");
} else{
echo(%line);
}
}
%auth.delete();
}
function handleLoginFailure(%auth){
echo("Could not login. Status follows: " @ %auth.getStatus());
%auth.delete();
}
function handleLoginProgress(%auth){
%total = %auth.getSizeTotal();
%now = %auth.getSizeNow();
if(%total) echo("Logging in:" @ ((%now/%total)*100) @ "%");
else echo("Logging in: progress unknown");
//-^ save this for a gui
}
function checksession(){
%auth = new SimCurl();
%auth.setUrl("http://www.cemetech.net/scripts/torqueauth.php?sid="@$pref::player::sid);
%auth.loadCookieFile("core/client/cookies/auth.txt");
%auth.setCookieJar("core/client/cookies/auth.txt");
%auth.addPostField("check_session","1");
%auth.setFinishCallback("handleLoginResponse");
%auth.setFailedCallback("handleLoginFailure");
%auth.setProgressCallback("handleLoginProgress");
$Pref::Player::SID = "";
echo("Logging in...");
%auth.start();
}
function handleLogoutResponse(%auth){
while(!%auth.isEOF()){
%line = %auth.readLine();
if(getword(%line, 0) $= "LOGOUT:"){
$pref::Player::SID = "";
echo("Logged out successfully");
} else{
echo(%line);
}
}
%auth.delete();
}
function handleLogoutFailure(%auth){
echo("Could not logout. Status follows: " @ %auth.getStatus());
%auth.delete();
}
function handleLogoutProgress(%auth){
%total = %auth.getSizeTotal();
%now = %auth.getSizeNow();
if(%total) echo("Logging out:" @ ((%now/%total)*100) @ "%");
else echo("Logging out: progress unknown");
}
function logout(){
%auth = new SimCurl();
%auth.setUrl("http://www.cemetech.net/scripts/torqueauth.php?logout&sid="@$pref::Player::SID);
%auth.loadCookieFile("core/client/cookies/auth.txt");
%auth.setCookieJar("core/client/cookies/auth.txt");
%auth.setFinishCallback("handleLogoutResponse");
%auth.setFailedCallback("handleLogoutFailure");
%auth.setProgressCallback("handleLogoutProgress");
echo("Logging out...");
%auth.start();
}There are 3 critical functions here.
trylogin(), checksession() and logout(), and some important things to note. trylogin() sets the cookie jar for reading cookies, but doesn't try to send any. SID is stored in the cookie, but more importantly, this saves the data necessary for autologin to behave correctly under phpBB2. checksession() is used to refresh the SID upon opening FreeBuild, but is only called if the autologin $pref::* variable is set. You'll notice that checksession() uses the same callbacks as trylogin(), this is because we want it to have the same effect. If the stored SID is still valid, or there's any valid auto-login data in the stored cookies, Cemetech will return exactly the same info as if you were logging in the for the first time. If you don't have any valid session hanging around, the old session data is destroyed so that the engine knows you aren't logged in. Finally, it's important to note that if logging out fails (due to server downtime, temporary database issues, whatever), that the logout() callbacks do not destroy the session data. This is so that the users are aware that their session and autologin keys are most probably still valid in the database which in turn will hopefully encourage them to try to log out again (and protect their account from compromise) instead of leaving sessions open. This is the account-safety equivalent of protecting against memory leaks by calling delete on an object before you set pointers to it to null.
All this is well and good, but the system above is still entirely console based, and unhelpful to most endusers. The next step is adding an interface to allow the users to interact with this. I created a 2-tabbed interface in my main menu, pictured here.

In reality this is a hidden 3-tab system, where the contents of the Cemetech On tab are replaced depending on whether or not you are logged in. The Cemetech Off tab disables logging in (and server registration) for LAN play, or falling back to use of the GarageGames master server in case of an outage at Cemetech.
I also added a small dialog with a progress bar to show the progress of logging in + out operations. I next added a second code file that ties the login/checksession/logout functions and their callbacks to the gui system.
package loginOverrides{
function trylogin(%username, %password, %autologin){
canvas.pushDialog(LoginProgressGui);
LoginProgressBar.setValue(0);
Parent::trylogin(%username, %password, %autologin);
}
function checksession(){
canvas.pushDialog(LoginProgressGui);
LoginProgressBar.setValue(0);
Parent::checksession();
}
function handleLoginResponse(%auth){
%loginfail = 1;
%loginstatus = "";
while(!%auth.isEOF()){
%line = %auth.readLine();
if(getword(%line, 0) $= "LOGIN:"){
%line = restwords(%line);
$pref::Player::SID = getword(%line, 0);
$pref::Player::Name = restwords(%line);
LoginProgressText.setText("Logged in successfully");
LoginProgressBar.setValue(100);
%loginfail = 0;
} else if(getword(%line, 0) $= "ERROR:"){
%loginstatus = %line;
}
}
%auth.delete();
Canvas.popDialog(LoginProgressGui);
if(%loginfail){
$pref::player::sid = "";
messageboxok("Error Logging In...", %loginstatus);
} else{
toggleCemetech(1);
}
}
function handleLoginFailure(%auth){
%auth.delete();
Canvas.popDialog(LoginProgressGui);
$pref::player::sid = "";
messageboxok("Error Logging In...", %auth.getStatus());
}
function handleLoginProgress(%auth){
%total = %auth.getSizeTotal();
%now = %auth.getSizeNow();
if(%total){
LoginProgressText.setText("Logging In:" @ ((%now/%total)*100) @ "%");
LoginProgressBar.setValue(mFloor((%now/%total)*100));
}
else{
LoginProgressText.setText("Logging In: progress unknown");
LoginProgressBar.setValue(0);
}
}
function logout(){
canvas.pushDialog(LoginProgressGui);
LoginProgressBar.setValue(0);
Parent::logout();
}
function handleLogoutResponse(%auth){
%logoutfailure = 1;
%logoutstatus = "";
while(!%auth.isEOF()){
%line = %auth.readLine();
if(getword(%line, 0) $= "LOGOUT:"){
$pref::Player::SID = "";
LoginProgressBar.setValue(100);
%logoutfailure = 0;
} else if(getword(%line, 0) $= "ERROR:"){
%logoutstatus = %line;
}
}
%auth.delete();
Canvas.popDialog(LoginProgressGui);
if(%logoutfailure){
messageboxok("Error Logging Out...", %logoutstatus);
} else{
toggleCemetech($pref::CemetechOn);
}
}
function handleLogoutFailure(%auth){
LoginProgressText.setText("Could not logout.");
LoginProgressBar.setValue(100);
Canvas.popDialog(LoginProgressGui);
%auth.delete();
messageboxok("Could not logout", "Status follows: " @ %auth.getStatus());
}
function handleLogoutProgress(%auth){
%total = %auth.getSizeTotal();
%now = %auth.getSizeNow();
if(%total){
LoginProgressText.setText("Logging out: " @ ((%now/%total)*100) @ "%");
LoginProgressBar.setValue(mFloor((%now/%total)*100));
} else{
LoginProgressText.setText("Logging out: progress unknown");
LoginProgressBar.setValue(0);
}
}
};
activatepackage(loginOverrides);
if($pref::player::autologin && $pref::player::sid !$= ""){
checksession();
}This is fairly self-explanatory, as it mirrors the previous bit of code, but adds in the necessary elements for interacting with the GUI. The call to toggleCemetech() makes sure the tabs are in a consistent state. Now we'll take a look at how SAX is implemented. Note that this has not yet been tied in to the gui, because I still need to figure out some formatting issues.
package sax{
function saxsay(%what){
%saxsay = new SimCurl();
%saxsay.setUrl("http://www.cemetech.net/scripts/torqueauth.php?saxsay&sid=" @ $pref::player::sid @ "&what=" @ urlencode(%what));
%saxsay.loadCookieFile("core/client/cookies/auth.txt");
%saxsay.setCookieJar("core/client/cookies/auth.txt");
%saxsay.setFinishCallback("onSaxPostResponse");
%saxsay.setFailedCallback("onSaxPostFailed");
%saxsay.setProgressCallback("onSaxPostProgress");
%saxsay.start();
}
function onSaxPostProgress(%this)
{
}
function onSaxPostFailed(%this)
{
messageboxok("Sax Error!", %this.getStatus());
%this.delete();
}
function onSaxPostResponse(%this)
{
while(!%this.isEOF()){
%line = %this.readLine();
if(%line !$= "OK"){
messageboxok("Sax Error!", %line);
}
}
%this.delete();
}
function initSaxRead(){
echo("Initializing SAX...");
if(!$pref::sax::lastread){
$pref::sax::lastread = 1;
}
}
function onSaxReadProgress(%this){}
function onSaxReadFailed(%this){
messageboxok("Sax Error!", %this.getStatus());
%this.delete();
}
function onSaxReadResponse(%this){
//Cemetech sense in reverse chronological order
//this makes our job a pain in the butt.
%readin = "";
%lineid = "";
%who = "";
%what = "";
%timestamp = 0;
%linetype = 0;
%app = "";
while(!%this.isEOF()){
%line = %this.readLine();
if(%readin $= "") %readin = %line;
else %readin = %line NL %readin;
}
//split on x1b
for(%i = 0; %i < getRecordCount(%readin); %i++){
%line = getRecord(%readin, %i);
%lineid = getDelimitedItem(%line, "x1b", 0);
%who = getDelimitedItem(%line, "x1b", 1);
%what = getDelimitedItem(%line, "x1b", 2);
%timestamp = getDelimitedItem(%line, "x1b", 3);
%linetype = getDelimitedItem(%line, "x1b", 4);
%app = getDelimitedItem(%line, "x1b", 5);
if(%linetype != 2){
echo("(" @ %app @ ")" SPC %who SPC ":" SPC %what);
} else{
echo("(" @ %app @ ") ***" @ %who SPC %what );
}
if(%lineid > $pref::sax::lastread) $pref::sax::lastread = %lineid;
}
}
function saxUpdate(){
%saxread = new SimCurl();
%saxread.setUrl("http://www.cemetech.net/scripts/saxpop.php?i=" @ $pref::sax::lastread);
%saxread.loadCookieFile("core/client/cookies/auth.txt");
%saxread.setCookieJar("core/client/cookies/auth.txt");
%saxread.setFinishCallback("onSaxReadResponse");
%saxread.setFailedCallback("onSaxReadFailed");
%saxread.setProgressCallback("onSaxReadProgress");
%saxread.start();
}
};
activatePackage(sax);
initSaxRead();Posting to SAX is fairly straight forward, reading is a bit more interesting.
$pref::sax::lastread stores the row number of the last line read. initSaxRead() just makes sure that if it hasn't been set yet, it's set to 1. SAX either returns the most current 30 lines (if $pref::sax::lastread is less than the row number of the 30th most recent message), or if it's more recent, however many messages have happened since the last read. Since SAX publishes messages in reverse chronological order (like a Facebook newsfeed), my read method firsts reads them in and builds a correctly ordered list. Each row has 6 fields, which may or may not be empty, and are delimited with an "x1b" character. This presents a problem, since TGE's string manipulation functions are largely limited to dealing with spaces, tabs and new lines, or "tokens." At first glance tokens seem like a good match, since you can set custom delimiters, but the problem is that nextToken uses a greedy algorithm which reads as many delimiting characters as it can until it runs into something else. This means that any empty fields are skipped over, and values end up getting assigned in the wrong place. To work around this, I implemented new Console functions, based on the same internal getUnit() functions that are used by getWord(), getField(), and getRecord(). I've released this as a resource here, and you'll see calls to getDelimitedItem() in my source.
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::UnprocessedSchedule = 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), now I'll move on to the phpBB side of things. You'll need to add a table called "Servers_Online" with the following structure.ServerID - varchar(32) ServerKEY - varchar(32) NeedsPass - boolean/tinyint ServerName - varchar(256) GameType - varchar(64) // <- I actually use this to hold MissionType. Oops. ConnectedPlayers - mediumint MaxPlayers - mediumint ServerIP - varchar(21) ServerMission - varchar(255) ServerInfo - varchar(255) LastSeen - mediumint
I'm mostly sure that this code properly cleans input, but as with all database applications, check it carefully yourself, and of course I'd appreciate a heads up if you do find anything resembling an SQL injection. Also, if you're not running KermMartian's excellent SAX-chat modification, the SAX related code doesn't apply to you and should be removed. If you want it, it can be found here.
File: torqueauth.php
<?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, and $phpbb_root_path to match your phpBB installation root.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.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.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;
}
}
$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 = '';
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.// 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");
}
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]);
}
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. Now we get to the last bit of fun, which is convincing TGE to display your php generated server list in the Join Server Dialog.
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.
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();
}
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();
}
//----------------------------------------
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;
}I'm sure some of you will be running phpBB3, so here are some resources for porting my php code to run with phpBB3 instead of phpBB2
phpBB3 uses a database abstraction layer called "DBAL" and you'd need to make sure that all the $db->function() code is 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.
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/09/2010 (10:27 am)
Glad to be of assistance. :) let me know if you have any difficulties figuring it out.
#3
Al tho I think my troubles is not really understanding how to setup the compiler for this.
Thanks.
EDIT: I am also using VC2005.
02/12/2010 (12:13 pm)
Anyone happen to have source for this that works with TGEA / AFX 1.8.* ?Al tho I think my troubles is not really understanding how to setup the compiler for this.
Thanks.
EDIT: I am also using VC2005.
#4
02/12/2010 (12:27 pm)
I assume you're speaking of the sources for the libcurl integration? I had a pain in the butt time getting VC2008 to play nicely with it, but the updated resource which I posted speaks to some of those issues. I'm not sure if there are TGEA specific issues, but you may want to read through the comment thread on bank's original resource. I seem to remember TGEA being discussed there.
#5
02/12/2010 (12:42 pm)
Great Thanks.
#6
change instances of
to
02/15/2010 (11:33 pm)
There's a small problem that you may want to fix.change instances of
%ServerAuth.addPostField("mission", $Pref::Server::MissionName);to
%ServerAuth.addPostField("mission", $Server::MissionName); 
Torque Owner Michael Branin
Default Studio Name