PHP MasterServer for Torque
by Thomas Huehn · 06/11/2008 (6:11 am) · 5 comments
Just another Master Server prototype Masterserver written in PHP. It's not possible to use this on an webserver like most people use php - it must run standalone and the PHP binary must be compiled with --enable-sockets. You can execute it like this: php -q MasterServer.php.
Update: 09/14/08 Added config flag for enhancedfilter and fixed "Out of range read" on GameMasterInfoRequest
Update: 04/28/09 Query check changed to accept case insensitive values.
MasterServer.php
Update: 09/14/08 Added config flag for enhancedfilter and fixed "Out of range read" on GameMasterInfoRequest
Update: 04/28/09 Query check changed to accept case insensitive values.
MasterServer.php
<?php
/***
*
* MasterServer for Torque Game Engine
*
* tested with PHP 4.3.10
* PHP binary must be compiled with --enable-sockets
*
* Prototype Master Server by T.Huehn.
* Needs to be more tested and moved into a class.
*
* With help from reading serverquery.cc, c3masterserver.pl from Thomas Lund and Python MasterServer by Andy Rollins
*
*
* 09/14/08 Added config flag for enhancedfilter and fixed "Out of range read" on GameMasterInfoRequest
* 04/28/09 Query check changed to accept case insensitive values.
**/
//------------------------------------------------------------------------------
// Config
//------------------------------------------------------------------------------
$_config["port"] = 28002;
$_config["bindaddr"] = "0.0.0.0";
$_config["buflen"] = 1024;
$_config["timeout"] = 180;
$_config["enhancedfilter"] = false; //if true check query for: regionmask,bots and cpu
$_config["debugmode"] = 5; //console output: 0=disabled, 1=low, 2=recommended, 5=high
$_config["useSignals"] = false; //unix only
$_config["useSyslog"] = false;
//------------------------------------------------------------------------------
// Init
//------------------------------------------------------------------------------
set_time_limit(0); //MAXIMUM LIFETIME Unlimited
error_reporting(E_ERROR | E_PARSE);
//------------------------------------------------------------------------------
// Helper
//------------------------------------------------------------------------------
function svar_dump($data,$mode) {
if ($mode > $_config["debugmode"])
return;
ob_start();
var_dump($data);
$ret_val = ob_get_contents();
ob_end_clean();
conmsg($ret_val,$mode);
}
function conmsg($str,$mode) {
global $_config;
if ($mode > $_config["debugmode"])
return;
if ($_config["useSyslog"]) {
syslog(LOG_NOTICE,$str);
} else {
echo($str."n");
flush();
ob_flush();
}
}
//------------------------------------------------------------------------------
// Const
//------------------------------------------------------------------------------
define("Status_Dedicated", 1 << 0);
define("Status_Passworded", 1 << 1);
define("Status_Linux", 1 << 2);
//------------------------------------------------------------------------------
// Signals and syslog
//------------------------------------------------------------------------------
function sig_handler($signo)
{
conMsg("SIGNAL $signo",2);
switch ($signo) {
case SIGINT:
// interrupt same as sigterm here
case SIGTERM:
// default on kill
$GLOBALS["serverrunning"] = false;
break;
case SIGHUP:
//used to flush the serverlist
unset($GLOBALS["servers"]);
conmsg("Server cache flushed!",0);
break;
case SIGUSR1:
// Dump all servers
conMsg("Serverlist >>",0);
foreach($GLOBALS["servers"] as $s) {
conMsg(sPrintf("%-25s %-30s Mission:%s,Player:%s,Reg:%s,Ver:%s,Bots:%s,CPU:%s",
$s["IP"]."::".$s["PORT"],$s["gametype"],$s["missiontype"],
$s["playerCount"],$s["regionMask"],$s["versionNumber"],$s["botCount"], $s["cpuMhz"]
),0);
}
conMsg("<< Serverlist",0);
break;
default:
// for future use
break;
}
}
if ($_config["useSignals"]) {
conMsg("Setting up Signals",2);
declare(ticks = 1);
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGINT, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");
}
if ($_config["useSyslog"]) {
define_syslog_variables();
openlog("TorqueMaster", LOG_ODELAY, LOG_DAEMON);
}
//------------------------------------------------------------------------------
// Main
//------------------------------------------------------------------------------
conmsg("MasterServer starting.... ".$_config["bindaddr"],"::".$_config["port"],1);
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if (!socket_bind($socket, $_config["bindaddr"], $_config["port"])) {
@socket_close($socket);
die("Cant bind Server to $bindaddr");
};
socket_set_nonblock($socket);
conmsg("MasterServer up.",2);
$serverrunning = true; //
$servers=array();
while($serverrunning) {
$scnt=socket_recvfrom($socket,$buf,$_config["buflen"],0,$clientIP,$clientPort);
if ($scnt<1) {
usleep(2500);
continue;
}
$clientId=$clientIP."::".$clientPort;
$tmpdata = unpack("C",$buf);
$cmdtype = $tmpdata[1];
unset($tmpdata);
switch ($cmdtype) {
case 6:
conmsg("RCV MasterServerListRequest from $clientId",2);
$req=array();
$data = unpack("Ctype/CqueryFlags/VpingSessionId/Cdummy/Cgtlen", $buf);
$needle = 8;
$req["queryFlags"] = $data["queryFlags"];
$req["gametype"] = substr($buf,$needle,$data["gtlen"]);
$req["pingSessionId"] = $data["pingSessionId"];
$needle += $data["gtlen"];
unset($data);
$tmpdata = unpack("C",substr($buf,$needle,1));
$needle++;
$req["missiontype"]=substr($buf,$needle,$tmpdata[1]);
$needle+=$tmpdata[1];
unset($tmpdata);
$data = unpack("CminPlayers/CmaxPlayers/VregionMask/VversionNumber/CfilterFlags/CmaxbotCount/vmincpuMhz",
substr($buf,$needle,14));
$needle+=14;
//Note: rest is followed up with buddy stuff.. i ignore this here
$req=array_merge($req,$data);
unset($data);
$foundServer = array();
//hack for empty request strings:
if ($req["missiontype"] == "" ) $req["missiontype"]="Any";
conmsg("Query:: GameType:".$req["gametype"]." MissionType:".$req["missiontype"]
." min/max players:".$req["minPlayers"]."/".$req["minPlayers"]
." Region:".$req["regionMask"]
." Version:".$req["versionNumber"]
." MinBotcnt:".$req["maxbotCount"]
." MinCPU:".$req["mincpuMhz"]
,5);
foreach(array_keys($servers) as $k) {
if ($servers[$k]["expire"] < time()) {
unset($servers[$k]);
} else {
if (
(strcasecmp($servers[$k]["gametype"],$req["gametype"])==0)
&& (strcasecmp($req["missiontype"],"Any")==0 || strcasecmp($servers[$k]["missiontype"],$req["missiontype"]) == 0)
&& $servers[$k]["playerCount"] >= $req["minPlayers"]
&& $servers[$k]["maxPlayers"] <= $req["maxPlayers"]
&& ($req["versionNumber"] == 0 || strcasecmp($servers[$k]["versionNumber"] , $req["versionNumber"]) == 0)
&& ( !$_config["enhancedfilter"]
|| (
($req["regionMask"] == 0 || $servers[$k]["regionMask"] == $req["regionMask"])
&& $servers[$k]["botCount"] <= $req["maxbotCount"]
&& $servers[$k]["cpuMhz"] > $req["mincpuMhz"]
)
)
) {
//ok lets answer ;)
$foundServer[]=$servers[$k];
}
}
}
$packettype = 8; //MasterServerListResponse
$flag = 0;
$key = $req["pingSessionId"];
$packetindex = 0;
$servercount = count($foundServer);
svar_dump($req,5);
conmsg("PROC found Servers: $servercount",5);
if ($servercount == 0) {
$packettotal = 1;
$packet = pack("CCVCCvCCCCv",
$packettype,
$flag,
$key,
$packetindex,
$packettotal,
$servercount,
0,0,0,0,
0
);
socket_sendto($socket,$packet,strlen($packet),0,$clientIP,$clientPort);
conmsg("SND MasterServerListResponse empty.",2);
} else {
$packettotal = $servercount;
foreach($foundServer as $s) {
$ipv4 = explode(".",$s["IP"]);
$packet = pack("CCVCCvCCCCv",
$packettype,
$flag,
$key,
$packetindex,
$packettotal,
$servercount,
$ipv4[0],$ipv4[1],$ipv4[2],$ipv4[3],
$s["PORT"]
);
socket_sendto($socket,$packet,strlen($packet),0,$clientIP,$clientPort);
conmsg("SND MasterServerListResponse to $clientId SERVER:".$s["IP"]."::".$s["PORT"],2);
$packetindex ++;
}
}
unset($foundServer);
break;
case 12:
conmsg("RCV GameMasterInfoResponse from $clientId",2);
$servers[$clientId]["expire"]=time()+$_config["timeout"];
$servers[$clientId]["IP"]=$clientIP;
$servers[$clientId]["PORT"]=$clientPort;
$data = unpack("Ctype/Cflags/Vkeys/Cgtlen", $buf);
$needle = 7;
$data[keys]=sprintf("%u", $data[keys]); //force unsigned
$servers[$clientId]["gametype"] = substr($buf,7,$data["gtlen"]);
$needle += $data["gtlen"];
$tmpdata = unpack("C",substr($buf,$needle,1));
$needle++;
$servers[$clientId]["missiontype"]=substr($buf,$needle,$tmpdata[1]);
$needle+=$tmpdata[1];
unset($tmpdata);
$data = unpack("CmaxPlayers/VregionMask/VversionNumber/CfilterFlags/CbotCount/VcpuMhz/CplayerCount",
substr($buf,$needle,16));
$needle+=16;
//Note: rest is followed up with guidList stuff.. i ignore this here
$servers[$clientId]=array_merge($servers[$clientId],$data);
unset($data);
svar_dump($servers,5);
break;
case 22:
conmsg("RCV GameHeartbeat from $clientId",2);
$servers[$clientId]["expire"]=time()+$_config["timeout"];
$servers[$clientId]["IP"]=$clientIP;
$servers[$clientId]["PORT"]=$clientPort;
$packet = pack("CCV", 10,0,0); //flag and key to zero added
conmsg("SND GameMasterInfoRequest to $clientId",5);
socket_sendto($socket,$packet,strlen($packet),0,$clientIP,$clientPort);
break;
default:
conmsg("RCV unknown commandtype = $cmdtype from $clientId",1);
break;
}
} //while true
conmsg("MasterServer shutting down.",2);
@socket_close($socket);
?>About the author
Contact: torque [AT] ohmtal [DOT] com
#2
running TGE 1.5.2
09/12/2008 (7:08 pm)
I getting an "Out of range read" warning when my server receives a GameMasterInfoRequest from this MasterServer.php script I have setup as a master server.running TGE 1.5.2
#3
I tried it out in debug mode and got the same warning. The Problem was I did only send the packettype but the engine expected also flags and key. Resource updated ;)
09/14/2008 (5:11 am)
@John I tried it out in debug mode and got the same warning. The Problem was I did only send the packettype but the engine expected also flags and key. Resource updated ;)
#4
02/26/2009 (1:26 pm)
Does it do the NAT-handshake thing?
#5
1. Download the PHP files from here: (I downloaded the .zip for Windows)
http://www.php.net/downloads.php
2. Extract the files into a directory.
3. Open up the file "php.ini-recommended" in notepad and "save as" php.ini
4. Now edit this php.ini:
Search for "sockets" and remove the ";" so the line looks like this:
extension=php_sockets.dll
Search for "extension_dir" and change it to:
extension_dir = "./ext/"
This is because the php_sockets.dll is located in this directory and php needs to fins it.
4. Create a new text document in the root of the PHP directory and rename it to MasterServer.php. Copy the above code from like 1 to 298 and past it in this document. Save and close
5. Create a new text document in the PHP rood directory and rename it to "startMasterServer.bat"
6. Add this command to start the server to this file:
php -q MasterServer.php
Save it.
You may now need to edit the MasterServer.php to change these settings but it works as is:
# $_config["port"] = 28002;
# $_config["bindaddr"] = "0.0.0.0";
7. In your Torque project directory edit this file:
YourTorqueProject\common\preferences\defaultPrefs.cs
and change the Master Server settings under /// Server:
$pref::Master0 = "2:master.garagegames.com:28002";
to
$pref::Master0 = "2:0.0.0.0:28002";
Save that file
Now back to the PHP directory double click the .BAT you created earlier and the server will run. Now you can start your game and it will use this server.
06/27/2009 (1:47 am)
Ok Just some more info on setting up.1. Download the PHP files from here: (I downloaded the .zip for Windows)
http://www.php.net/downloads.php
2. Extract the files into a directory.
3. Open up the file "php.ini-recommended" in notepad and "save as" php.ini
4. Now edit this php.ini:
Search for "sockets" and remove the ";" so the line looks like this:
extension=php_sockets.dll
Search for "extension_dir" and change it to:
extension_dir = "./ext/"
This is because the php_sockets.dll is located in this directory and php needs to fins it.
4. Create a new text document in the root of the PHP directory and rename it to MasterServer.php. Copy the above code from like 1 to 298 and past it in this document. Save and close
5. Create a new text document in the PHP rood directory and rename it to "startMasterServer.bat"
6. Add this command to start the server to this file:
php -q MasterServer.php
Save it.
You may now need to edit the MasterServer.php to change these settings but it works as is:
# $_config["port"] = 28002;
# $_config["bindaddr"] = "0.0.0.0";
7. In your Torque project directory edit this file:
YourTorqueProject\common\preferences\defaultPrefs.cs
and change the Master Server settings under /// Server:
$pref::Master0 = "2:master.garagegames.com:28002";
to
$pref::Master0 = "2:0.0.0.0:28002";
Save that file
Now back to the PHP directory double click the .BAT you created earlier and the server will run. Now you can start your game and it will use this server.

Torque Owner Dmitry Serdukov