chore: reorganize files for easier Docker integration

This commit is contained in:
Emmanuel BENOîT 2025-01-02 14:02:30 +01:00
parent ab00e5ee08
commit 99511fe11f
Signed by: Emmanuel BENOîT
SSH key fingerprint: SHA256:l7PFUUF5TCDsvYeQC9OnTNz08dFY7Fvf4Hv3neIqYpg
1222 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,13 @@
<?php
/****************************************************************************
* ACCOUNT MANAGEMENT FUNCTIONS
****************************************************************************/
function accountLog($what, $uid) {
$game = config::getGame('main');
$lib = $game->getLib('main/account');
$lib->call('log', $uid, $what);
}
?>

View file

@ -0,0 +1,93 @@
<?php
/****************************************************************************
* GAME ACTIONS
****************************************************************************/
/** The game_action class should be used as a base class for all classes
* implementing specific game actions.
*
* It initializes the object by storing the game instance and loading the
* required libraries.
*/
abstract class game_action {
/** A reference to the game instance the action is called on.
*/
protected $game;
/** Libraries loaded and instanciated. These libraries will
* be accessed to the __get() method.
*/
private $__libraries;
/** The constructor initialises the instance by storing the
* associated game instance and loading libraries.
*/
protected function __construct($game, $libraries = null) {
$this->game = $game;
if (is_null($libraries)) {
$libraries = array();
}
$this->__initLibraries($libraries);
}
/** This method loads libraries from the game instance and
* stores them inside the $__libraries array.
*/
private function __initLibraries($libraries) {
$libs = array();
foreach ($libraries as $key => $name) {
$libs[$key] = $this->game->getLib($name);
}
$this->__libraries = $libs;
}
/** The __get() overload method is useful to access the
* libraries associated with this game action.
*/
public function __get($var) {
if (array_key_exists($var, $this->__libraries)) {
return $this->__libraries[$var];
}
return null;
}
}
/** This function allows the user to call a game action function; it must
* have at least one argument, which may be either a string (the name of the
* action function to call for the current game version) or an array
* containing two strings (the version identifier and the name of the action).
* Other arguments may follow; they will be passed to the action function.
*/
function gameAction() {
$sitePath = input::$path;
$n = func_num_args();
if ($n == 0) {
l::fatal(22, "Requested game was '$sitePath'");
}
$args = func_get_args();
$gas = array_shift($args);
if (is_array($gas)) {
list($v,$a) = $gas;
} elseif (is_string($gas)) {
$v = $sitePath;
$a = $gas;
} else {
l::fatal(23, "Requested game was '$sitePath'");
}
array_unshift($args, $a);
$g = config::getGame($v);
l::deprecated("gameAction($v, $a, ...)");
return call_user_func_array(array($g, 'deprecatedAction'), $args);
}
?>

84
game/scripts/lib/ajax.inc Normal file
View file

@ -0,0 +1,84 @@
<?php
class ajax {
static $method = array();
static $fHandler = array();
static $fTheme = array();
static $init = "";
private static function getTheme() {
$f = getLayoutDirectory(input::$game->version->id) . "/ajax.inc";
if (!file_exists($f)) {
return array();
}
return include($f);
}
static function init() {
$handler = handler::$h;
// Get AJAX functions from handler
$a1 = is_array($handler->ajax) ? $handler->ajax : array();
if (isset($a1['func'])) {
ajax::$fHandler = $a1['func'];
$ml = is_array($a1['method'] ?? null) ? $a1['method'] : array();
foreach ($a1['func'] as $f) {
$m = ($ml[$f] ?? "" == "") ? ($handler->ajaxPOST ?? false ? "POST" : "GET") : $ml[$f];
ajax::$method[$f] = $m;
}
}
// Get theme-specific AJAX functions
$a2 = ajax::getTheme();
if (isset($a2['func'])) {
ajax::$fTheme = $a2['func'];
$ml = is_array($a2['method'] ?? null) ? $a2['method'] : array();
foreach ($a2['func'] as $f) {
$m = ($ml[$f] ?? "" == "") ? ($handler->ajaxPOST ?? false ? "POST" : "GET") : $ml[$f];
ajax::$method[$f] = $m;
}
}
// Create init string
ajax::$init = ($a2['init'] ?? '') . ($a1['init'] ?? '');
}
static function canCall($function) {
return in_array($function, ajax::$fTheme) || in_array($function, ajax::$fHandler);
}
static function call($function, $args) {
// We don't want an error in that part of the code to make the
// client-side JS script go insane
ob_start();
// Call the function
if (in_array($function, ajax::$fTheme)) {
$res = call_user_func_array("thm_{$function}", $args);
} else if (in_array($function, ajax::$fHandler)) {
$res = call_user_func_array(array(handler::$h, $function), $args);
} else {
$res = null;
}
// Log any error / warning / whatever
$buffer = ob_get_contents();
if ($buffer != '') {
$b = explode("\n", $buffer);
foreach ($b as $line) {
if ($line != '') {
l::warning("AJAX ($function): $line");
}
}
}
ob_end_clean();
return $res;
}
}
?>

View file

@ -0,0 +1,25 @@
<?php
class loader {
private static $loadedClasses = array();
static function load($file, $className) {
if (in_array($className, loader::$loadedClasses)) {
return;
}
if (!(file_exists($file) && is_readable($file) && is_file($file))) {
l::fatal(19, "File '$file' (for class $className) not found");
}
if (!include_once($file)) {
l::fatal(20, "File '$file' (for class $className) returned false");
}
if (!class_exists($className)) {
l::fatal(21, "Class '$className' not found in file '$file'");
}
array_push(loader::$loadedClasses, $className);
}
}
?>

136
game/scripts/lib/config.inc Normal file
View file

@ -0,0 +1,136 @@
<?php
/****************************************************************************
* MAIN CONFIGURATION MANAGEMENT
****************************************************************************/
class config {
static $config = null;
static $main = null;
function __construct($versions, $games, $defGame) {
$this->versions = $versions;
$this->games = $games;
$this->defGame = $defGame;
}
private static function parseXML($xmlData) {
require_once('xml_config.inc');
return xml_config::parse($xmlData);
}
private static function tryLoadSerialized() {
$config = &config::$main;
if (!file_exists("{$config['cachedir']}/config.ser")) {
return false;
}
$f = @file_get_contents("{$config['cachedir']}/config.ser");
if ($f === false) {
return false;
}
return @unserialize($f);
}
private static function writeCache($cObj) {
$config = &config::$main;
umask(0007);
if (!is_dir(config::$main['cachedir']) && !@mkdir(config::$main['cachedir'], 0770)) {
l::warn("CONFIG: failed to create cache directory");
return;
}
$text = serialize($cObj);
$mask = umask(0002);
$w = @file_put_contents("{$config['cachedir']}/config.ser", $text, LOCK_EX);
umask($mask);
if ($w === false) {
l::warn("CONFIG: failed to cache configuration");
}
}
private static function checkUpdate($checksum = null) {
$config = &config::$main;
$f = @file_get_contents("{$config['scriptdir']}/legacyworlds.xml");
if ($f === false) {
l::error("CONFIG: could not open file '{$config['scriptdir']}/legacyworlds.xml'");
return false;
}
$md5 = md5($f);
if (!is_null($checksum) && $checksum == $md5) {
return false;
}
l::notice("CONFIG: XML configuration file modified, updating");
$data = config::parseXML($f);
if (is_object($data)) {
$data->checksum = $md5;
config::writeCache($data);
}
return $data;
}
static function load() {
global $config;
if (!is_null($config) && is_null(config::$main)) {
config::$main = $config;
}
$sData = config::tryLoadSerialized();
if (!is_object($sData)) {
$sData = config::checkUpdate();
} else {
$nData = config::checkUpdate($sData->checksum);
if (is_object($nData)) {
$sData = $nData;
}
}
if (!is_object($sData)) {
l::fatal(0, "The XML configuration could not be parsed and serialized data could not be read");
}
config::$config = $sData;
}
static function reload() {
$nData = config::checkUpdate(config::$config->checksum);
if (is_object($nData)) {
config::$config = $nData;
l::info("CONFIG: configuration updated");
}
return is_object($nData);
}
static function getGames() {
return config::$config->games;
}
static function getGame($name) {
return config::$config->games[$name];
}
static function getDefaultGame() {
return config::$config->games[config::$config->defGame];
}
static function hasGame($name) {
return is_object(config::$config->games[$name]);
}
static function getParam($name) {
return config::$config->games['main']->params[$name];
}
}
config::load();
?>

View file

@ -0,0 +1,130 @@
<?php
abstract class data_gen {
private $name;
private $attributes;
function __construct($name) {
$this->name = $name;
$this->attributes = array();
}
function getName() {
return $name;
}
abstract function getContents();
abstract function addContents($contents);
function getAttribute($name) {
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
}
function setAttribute($name, $value) {
$this->attributes[$name] = $value;
}
function toXML($document) {
$element = $document->createElement($this->name);
foreach ($this->attributes as $k => $v) {
$element->setAttribute($k, $v);
}
return $element;
}
function toLWData($document) {
$root = $document->createElement("Node");
$root->setAttribute("name", $this->name);
foreach ($this->attributes as $k => $v) {
$node = $document->createElement("Attr");
$root->appendChild($node);
$node->setAttribute("name", $k);
$text = $document->createTextNode($v);
$node->appendChild($text);
}
return $root;
}
}
class data_leaf extends data_gen {
private $contents;
function __construct($name, $contents = "") {
parent::__construct($name);
$this->contents = $contents;
}
function getContents() {
return $this->contents;
}
function addContents($contents) {
$this->contents .= $contents;
}
function toXML($document) {
$element = parent::toXML($document);
$value = $document->createTextNode($this->contents);
$element->appendChild($value);
return $element;
}
function toLWData($document) {
$element = parent::toLWData($document);
$element->setAttribute("node", 0);
if ($this->contents != '') {
$node = $document->createElement("Text");
$value = $document->createTextNode($this->contents);
$node->appendChild($value);
$element->appendChild($node);
}
return $element;
}
}
class data_node extends data_gen {
private $contents;
function __construct($name) {
parent::__construct($name);
$this->contents = array();
}
function getContents() {
return $this->contents;
}
function addContents($data) {
if ($data instanceof data_gen) {
array_push($this->contents, $data);
}
}
function toXML($document) {
$element = parent::toXML($document);
foreach ($this->contents as $sub) {
$node = $sub->toXML($document);
$element->appendChild($node);
}
return $element;
}
function toLWData($document) {
$element = parent::toLWData($document);
$element->setAttribute("node", 1);
foreach ($this->contents as $sub) {
$node = $sub->toLWData($document);
$element->appendChild($node);
}
return $element;
}
}
?>

74
game/scripts/lib/db.inc Normal file
View file

@ -0,0 +1,74 @@
<?php
/****************************************************************************
* DATABASE FUNCTIONS
****************************************************************************/
function dbConnect($fatal = true) {
if (is_null(db::$database)) {
try {
new db();
$rv = true;
} catch (Exception $e) {
$rv = false;
}
} else {
$rv = db::$database->open();
}
if ($fatal && !$rv) {
try {
$lastError = pg_last_error();
} catch (Error) {
$lastError = "(no connection)";
}
l::fatal(1, array("SQL: Database connection failed", $lastError));
}
return $rv;
}
function dbClose() {
db::$database->close();
}
function dbQuery($q) {
return db::$database->query($q);
}
function dbBegin() {
db::$database->begin();
}
function dbEnd() {
db::$database->end();
}
/** This function returns the results count for the $qr database query
* result
*/
function dbCount($qr) {
return @pg_num_rows($qr);
}
/** This function returns the next row from the $qr query result as a hash
* table
*/
function dbFetchHash($qr) {
return @pg_fetch_assoc($qr);
}
/** This function returns the next row from the $qr query result as an array */
function dbFetchArray($qr) {
return @pg_fetch_row($qr);
}
/** This function turns a value into a DB-specific boolean value. */
function dbBool($v) {
return $v ? "true" : "false";
}
?>

View file

@ -0,0 +1,127 @@
<?php
class db_accessor {
private static $lastNamespace = null;
private $db;
private $namespace;
public function __construct($db, $namespace) {
$this->db = $db;
$this->namespace = $namespace;
}
public function setNamespace() {
if (self::$lastNamespace === $this->namespace) {
return;
}
if (is_null($this->namespace) || $this->namespace == '') {
$this->db->query('SET search_path=public,main');
} else {
$this->db->query('SET search_path=public,main,' . $this->namespace);
}
self::$lastNamespace = $this->namespace;
}
public function enableExceptions() {
$this->db->enableExceptions();
}
public function disableExceptions() {
$this->db->disableExceptions();
}
public function query($q) {
$this->setNamespace();
return $this->db->query($q);
}
public function begin() {
$this->db->begin();
}
public function end($rb = false) {
return $this->db->end($rb);
}
public function needsReset() {
return $this->db->needsReset();
}
public function reset($attempts = 5, $delay = 1) {
if ($rv = $this->db->reset($attempts, $delay)) {
self::$lastNamespace = false;
}
return $rv;
}
/** This method starts a transaction and runs in safely, trying to reset the connection
* if it fails.
*/
public function safeTransaction($callback, $args = null, $maxResets = 1,
$attempts = 5, $delay = 1, $maxDeadlocks = 10) {
if (is_null($args)) {
$args = array();
} elseif (!is_array($args)) {
$args = array($args);
}
$resets = $deadlocks = 0;
$this->enableExceptions();
try {
do {
$ok = false;
try {
$this->begin();
$returnValue = call_user_func_array($callback, $args);
$this->end();
$ok = true;
} catch (db_deadlock_exception $e) {
/* $this->end(false);
self::$lastNamespace = false;
if ($deadlocks == $maxDeadlocks) { */
throw $e;
// }
// $deadlocks ++;
} catch (db_sql_exception $e) {
$this->end();
throw $e;
} catch (db_srv_exception $e) {
if ($resets == $maxResets) {
throw $e;
}
$this->reset($attempts, $delay);
$resets ++;
}
} while (!$ok);
} catch (Exception $e) {
$this->disableExceptions();
throw $e;
}
$this->disableExceptions();
return $returnValue;
}
/** This method copies raw data into a table.
*/
public function copyTo($tableName, $data) {
$this->setNamespace();
return $this->db->copyTo($tableName, $data);
}
/** This method copies raw data from a table.
*/
public function copyFrom($tableName) {
$this->setNamespace();
return $this->db->copyFrom($tableName);
}
}
?>

View file

@ -0,0 +1,446 @@
<?php
class db_srv_exception extends Exception {
public function __construct($message, $code = 0) {
parent::__construct("Database server error ($message)", $code);
}
}
class db_sql_exception extends Exception {
private $query;
public function __construct($query, $message = "") {
parent::__construct("SQL query exception" . ($message != '' ? ": $message" : ""));
$this->query = $query;
}
public function getQuery() {
return $this->query;
}
}
class db_deadlock_exception extends Exception {
public function __construct($error) {
parent::__construct("Deadlock detected ($error)");
}
}
class db {
static $database = null;
private $accessors = array();
private $isOpen = false;
private $cString = '';
private $conn = null;
private $inTrans = false;
private $queries = 0;
private $__needsReset = false;
private $useExceptions = 0;
private $trace = false;
static function connect() {
if (self::$database && ! self::$database->open()) {
throw new db_srv_exception("unable to connect");
} else {
new db();
}
return self::$database;
}
public function __construct() {
if (!$this->open()) {
throw new db_srv_exception("unable to connect");
}
self::$database = $this;
}
public function open() {
if ($this->isOpen) {
return true;
}
// Get access to the 'main' game
$main = config::getGame('main');
if (!$main) {
if ($this->useExceptions) {
throw new db_srv_exception("failed to fetch 'main' pseudo-game");
}
return false;
}
// Generate the connection string
if ($this->cString == '') {
$cString = "dbname='{$main->params['dbname']}'";
if ($main->params['dbhost'] != '') {
$cString .= " host='{$main->params['dbhost']}' sslmode='disable'";
}
if ($main->params['dbuser'] != '') {
$cString .= " user='{$main->params['dbuser']}'";
}
if ($main->params['dbpass'] != '') {
$cString .= " password='{$main->params['dbpass']}'";
}
$this->cString = $cString;
}
// Connect to the database
ob_start();
$this->conn = pg_connect($this->cString);
$error = ob_get_contents();
ob_end_clean();
// Check status
if (!$this->conn || pg_connection_status($this->conn) == PGSQL_CONNECTION_BAD) {
$this->conn = null;
if ($this->useExceptions) {
throw new db_srv_exception("database connection failed: $error");
}
return false;
}
$this->isOpen = true;
$this->inTrans = false;
$this->queries = 1;
if (!@pg_query($this->conn, 'SET search_path=public,main')) {
$error = pg_last_error();
@pg_close($this->conn);
$this->conn = null;
if ($this->useExceptions) {
throw new db_sql_exception('SET search_path=public,main',
"failed to initialise search path: $error");
}
return false;
}
$this->begin();
return true;
}
public function close() {
if (! $this->isOpen) {
return;
}
$this->end();
@pg_close($this->conn);
$this->isOpen = false;
$this->accessors = array();
if ($this->queries >= 20) {
l::debug("SQL: connection closed after {$this->queries} queries");
}
}
public function enableExceptions() {
$this->useExceptions ++;
}
public function disableExceptions() {
if ($this->useExceptions > 0) {
$this->useExceptions --;
}
}
private function fail($logTxt, $query) {
if ($this->error) {
if ($this->useExceptions) {
throw new db_sql_exception($query, $logText);
}
return;
}
l::error("SQL: $logTxt");
l::info("QUERY: $query");
l::backtrace();
$this->error = true;
if ($this->useExceptions) {
throw new db_sql_exception($query, $logTxt);
}
}
public function begin() {
if ($this->inTrans || $this->__needsReset) {
return;
}
if (@pg_query($this->conn, "BEGIN TRANSACTION")) {
$this->queries ++;
$this->inTrans = true;
$this->error = false;
} else {
l::error("SQL: could not start transaction");
l::info("SQL: error was " . ($error = pg_last_error($this->conn)));
if (pg_connection_status($this->conn) == PGSQL_CONNECTION_BAD) {
$this->__needsReset = true;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
} else {
if ($this->useExceptions) {
throw new db_sql_exception("BEGIN TRANSACTION", $error);
}
}
}
}
public function end($rollback = false) {
if (!$this->inTrans) {
return true;
}
$r = ($rollback || $this->error);
$query = $r ? "ROLLBACK" : "COMMIT";
if (@pg_query($this->conn, $query)) {
$this->queries ++;
$this->inTrans = false;
} else {
$cStat = pg_connection_status($this->conn);
$error = pg_last_error($this->conn);
if ($cStat == PGSQL_CONNECTION_BAD) {
l::notice("SQL: could not end transaction, connection is in an invalid status");
$this->__needsReset = true;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
} else {
l::error("SQL: could not end transaction while " . ($r ? "rolling back" : "comitting"));
l::info("SQL ERROR: $error");
l::backtrace();
if ($this->useExceptions) {
throw new db_sql_exception($query, $error);
}
}
return false;
}
return true;
}
public function query($query) {
if (!$this->inTrans) {
$this->fail("query executed outside of transaction", $query);
return null;
}
$this->queries ++;
if ($this->trace) {
l::trace("EXECUTE: $query");
}
$r = @pg_query($this->conn, $query);
if (!$r) {
$cStat = pg_connection_status($this->conn);
$error = pg_last_error($this->conn);
if ($cStat == PGSQL_CONNECTION_BAD) {
l::notice("SQL: connection is gone while executing query");
l::debug("QUERY: $query");
$this->__needsReset = true;
$this->error = true;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
return;
} elseif (preg_match('/deadlock/i', $error)) {
l::error("SQL: deadlock detected");
if ($this->useExceptions) {
throw new db_deadlock_exception($error);
}
$this->error = true;
return;
} else {
$this->fail("an error has occured: $error", $query);
}
} elseif (preg_match('/^\s*insert\s+into ("?\w+"?)/i', $query, $match)) {
pg_free_result($r);
$tn = $match[1];
if ($tn[0] == '"') {
$tn = str_replace('"', '', $tn);
} else {
$tn = strtolower($tn);
}
$r2 = @pg_query("SELECT last_inserted('$tn')");
if ($r2) {
$rv = pg_fetch_row($r2);
if (is_null($rv[0])) {
$r = true;
} else {
$r = $rv[0];
}
pg_free_result($r2);
} elseif (preg_match('/deadlock/i', $error)) {
l::error("SQL: deadlock detected");
if ($this->useExceptions) {
throw new db_deadlock_exception($error);
}
$this->error = true;
return;
} else {
$cStat = pg_connection_status($this->conn);
$error = pg_last_error($this->conn);
if ($cStat == PGSQL_CONNECTION_BAD) {
l::notice("SQL: connection is gone while fetching last ID");
$this->__needsReset = true;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
} else {
$this->fail("failed to fetch last ID: $error", "SELECT last_inserted('$tn')");
}
$r = null;
}
}
return $r;
}
public function getGameAccess($prefix) {
if (!isset($this->accessors[$prefix])) {
$this->accessors[$prefix] = new db_accessor($this, $prefix);
}
return $this->accessors[$prefix];
}
public function needsReset() {
return $this->__needsReset;
}
public function reset($attempts = 5, $delay = 1) {
l::notice("SQL: resetting connection after {$this->queries} queries");
$this->__needsReset = false;
$i = 0;
do {
$rv = @pg_connection_reset($this->conn);
if (!$rv) {
$error = pg_last_error($this->conn);
$i ++;
l::warn("Reconnection attempt #$i failed" . ($i < $attempts ? ", retrying" : ""));
sleep($delay);
}
} while ($i < $attempts && !$rv);
if ($rv) {
$this->error = false;
$this->inTrans = false;
$this->queries = 0;
$this->trace = true;
} else {
$this->isOpen = false;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
}
return $rv;
}
public function copyTo($table, $data) {
$actualData = array();
foreach ($data as $row) {
$actualRow = array();
foreach ($row as $column) {
if (is_null($column)) {
$output = "\\N";
} elseif (is_string($column)) {
$output = preg_replace(
array('/\\\\/', '/\x08/', '/\f/', '/\r/', '/\\n/', '/\t/', '/\v/'),
array('\\\\\\', '\\b', '\\f', '\\r', '\\n', '\\t', '\\v'),
$column
);
} else {
$output = $column;
}
array_push($actualRow, $output);
}
array_push($actualData, join("\t", $actualRow));
}
$result = @pg_copy_from($this->conn, $table, $actualData);
if (!$result) {
$cStat = pg_connection_status($this->conn);
$error = pg_last_error($this->conn);
if ($cStat == PGSQL_CONNECTION_BAD) {
l::notice("SQL: connection is gone while copying into table '$table'");
$this->__needsReset = true;
$this->error = true;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
return false;
} elseif (preg_match('/deadlock/i', $error)) {
l::error("SQL: deadlock detected while copying into table '$table'");
if ($this->useExceptions) {
throw new db_deadlock_exception($error);
}
$this->error = true;
return false;
} else {
$this->fail("an error has occured: $error", "COPY \"$table\" FROM STDIN");
}
}
return true;
}
public function copyFrom($table) {
$result = pg_copy_to($this->conn, $table);
if ($result === FALSE) {
$cStat = pg_connection_status($this->conn);
$error = pg_last_error($this->conn);
if ($cStat == PGSQL_CONNECTION_BAD) {
l::notice("SQL: connection is gone while copying from table '$table'");
$this->__needsReset = true;
$this->error = true;
if ($this->useExceptions) {
throw new db_srv_exception($error);
}
return false;
} elseif (preg_match('/deadlock/i', $error)) {
l::error("SQL: deadlock detected while copying from table '$table'");
if ($this->useExceptions) {
throw new db_deadlock_exception($error);
}
$this->error = true;
return false;
} else {
$this->fail("an error has occured: $error", "COPY \"$table\" TO STDOUT");
}
}
$actualData = array();
foreach ($result as $row) {
$actualRow = array();
$splitRow = explode("\t", $row);
foreach ($splitRow as $column) {
if ($column == "\\N") {
$output = null;
} else {
$output = preg_replace(
array('/\\\\b/', '/\\\\f/',
'/\\\\r/', '/\\\\n/', '/\\\\t/', '/\\\\v/', '/\\\\\\\\/'),
array("\x08", "\x0c", "\r", "\n", "\t", "\x0b", "\\"),
$column
);
}
array_push($actualRow, $output);
}
array_push($actualData, $actualRow);
}
return $actualData;
}
}
?>

View file

@ -0,0 +1,210 @@
<?php
//-----------------------------------------------------------------------
// LegacyWorlds Beta 5
// Game libraries
//
// lib/db_copy.inc
//
// This library adds support for mass insertions into the database.
//
// Copyright(C) 2004-2008, DeepClone Development
//-----------------------------------------------------------------------
class db_copy {
/* COPY MODES */
const copyTo = 0; // Mass-insert data into the table
const copyToClean = 1; // Similar to copyTo, but delete the table's contents first
const copyFrom = 2; // Mass-reads the table's contents
/**************/
private $table;
private $mode;
private $db;
private $data;
private $colNames;
/** Initialises the instance by specifying a table name and an optional copy mode.
* If no mode is specified, it defaults to copyFrom (mass read).
* The constructor can also be called with another db_copy instance as its first
* argument; in that case, it copies this instance's data; the mode must be either
* copyTo or copyToClean.
*/
public function __construct($table, $mode = self::copyFrom) {
if (is_string($table)) {
$this->table = $table;
$this->mode = $mode;
$this->db = $this->data = $this->colNames = null;
} elseif (is_object($table) && $table instanceof db_copy) {
$this->table = $table->table;
$this->mode = ($mode != self::copyFrom ? $mode : self::copyTo);
$this->db = $table->db;
$this->data = $table->data;
$this->colNames = $table->colNames;
} else {
throw new Exception("Invalid parameters to the db_copy constructor");
}
}
/** This method sets the database accessor to use when performing the copy
* operation.
*/
public function setAccessor($db) {
if (!is_object($db) || !(($db instanceof db) || ($db instanceof db_accessor))) {
throw new Exception("Invalid database accessor: " . gettype($db)
. (is_object($db) ? (" (" . get_class($db) . ")") : "") );
}
$this->db = $db;
}
/** This method executes the operation. It returns true on success or false
* on error. However, if database exceptions are enabled, SQL failure will
* cause an exception to be thrown.
*/
public function execute() {
if (is_null($this->db)) {
l::debug("db_copy::execute() called but no accessor set");
return false;
}
if ($this->mode == self::copyFrom) {
if (!is_null($this->data)) {
return true;
}
$this->data = $this->db->copyFrom($this->table);
return !is_null($this->data);
}
if (is_null($this->data)) {
return false;
}
if ($this->mode == self::copyToClean) {
$this->db->query("DELETE FROM \"{$this->table}\"");
}
return $this->db->copyTo($this->table, $this->data);
}
/** This method is called with an arbitrary number of arguments. It sets
* the names for the columns, which is required for some of the operations.
*/
public function setColumnNames() {
$n = func_num_args();
if ($n == 0) {
return;
}
$args = func_get_args();
$names = array();
for ($i = 0; $i < $n; $i ++) {
if (!is_string($args[$i])) {
return;
}
$names[$args[$i]] = $i;
}
$this->colNames = $names;
}
/** This method returns the amount of rows in the object.
*/
public function rows() {
return is_null($this->data) ? 0 : count($this->data);
}
/** This method appends a row to the current data. The row must be passed
* as an array of values. This method will fail and return FALSE if the
* current mode is copyFrom or if the data is not an array.
*/
public function appendRow($data) {
if (!is_array($data) || $this->mode == self::copyFrom) {
return false;
}
if (!is_array($this->data)) {
$this->data = array();
}
array_push($this->data, $data);
}
/** This method removes a row using its index. It will return FALSE if the
* index is invalid or if the current mode is copyFrom. In any other case
* it will return the row that has been removed.
*/
public function removeRow($index) {
if ($this->mode == self::copyFrom || !is_int($index) || $index < 0
|| is_null($this->data) || $index >= count($this->data)) {
return false;
}
return array_splice($this->data, $index, 1);
}
/** This method returns a row from the current data. It will return FALSE
* if the index is invalid, or the row on success.
*/
public function getRow($index) {
if (!is_int($index) || $index < 0 || is_null($this->data) || $index >= count($this->data)) {
return false;
}
return $this->data[$index];
}
/** This method searches for rows using a column identifier (either an
* integer or, if the column names have been specified, a column name)
* and a value to look for. It returns FALSE if the column identifier
* is invalid, or an array of row indices if rows are found. The
* additional $unique parameter can specify that the value will be
* unique to prevent the method from going through all rows in that
* case; it is false by default.
*/
public function lookupRow($column, $value, $unique = false) {
if (is_null($this->data)) {
return false;
}
if (is_int($column)) {
if ($column < 0) {
return false;
}
} elseif (is_string($column)) {
if (is_null($this->colNames) || !array_key_exists($column, $this->colNames)) {
return false;
}
$column = $this->colNames[$column];
} else {
return false;
}
$results = array();
for ($i = 0; $i < count($this->data); $i ++) {
if (count($this->data[$i]) < $column) {
return false;
}
if ($this->data[$i][$column] != $value) {
continue;
}
array_push($results, $i);
if ($unique) {
break;
}
}
return $results;
}
}
?>

View file

@ -0,0 +1,57 @@
<?php
/****************************************************************************
* DISPLAY ENGINE MANAGEMENT
****************************************************************************/
class engine {
static $e = null;
/** This function loads a display engine. It then checks the configuration for
* the maintenance mode data; if such data is found, the display engine's
* maintenance() method is called.
* A fatal error is displayed if the engine isn't found in the engines/
* subdirectory of the script directory.
*/
public static function load() {
$eType = input::$eType;
// Check for a valid type
if (preg_match('/[^A-Za-z0-9]/', $eType)) {
l::fatal(26);
}
$eType = strtolower($eType);
// Get the path
$ePath = config::$main['scriptdir'] . "/lib/engines/$eType.inc";
// Load the engine
if (!file_exists($ePath)) {
l::fatal(27, "Unhandled engine type: $eType");
} elseif (!include($ePath)) {
l::fatal(28, "Could not open engine file '$ePath'");
} elseif (!class_exists('display_engine')) {
l::fatal(29, "Invalid engine file for $eType");
} else {
$engine = new display_engine();
}
// Check for maintenance mode
if (is_array(config::$main['maintenance'])) {
$engine->maintenance(config::$main['maintenance']);
exit(0);
}
engine::$e = $engine;
l::setFatalHandler('engine::fatalError');
}
static function fatalError($errno, $errorText, $information) {
die(engine::$e->displayFatalError($errno, $errorText, $information));
}
}
?>

View file

@ -0,0 +1,71 @@
<?php
class display_engine {
function maintenance($maintenance) {
echo "/* Server is under maintenance: " . $maintenance['reason'] . " */";
}
function displayFatalError($code, $i, $i2) {
echo "/* Fatal error $code */";
}
function initSession() {
}
function checkAuth() {
}
function handleInput() {
$this->id = (int) input::$input['id'];
}
function initRPC() {
}
function outputData() {
header("Content-Type: text/css");
if (!is_array(tracking::$data['cssId'])) {
tracking::$data['cssId'] = array();
} else {
$now = time();
foreach (tracking::$data['cssId'] as $ck => $t) {
if ($now - $t > 345600) {
unset(tracking::$data['cssId'][$ck]);
}
}
}
$cssId = input::$path . "/" . input::$page . "/{$this->id}";
if (!isset(tracking::$data['cssId'][$cssId])) {
tracking::$data['cssId'][$cssId] = time();
}
$time = tracking::$data['cssId'][$cssId];
$lastModified = substr(date('r', $time), 0, -5).'GMT';
$etag = '"'.md5($lastModified).'"';
header("Last-Modified: $lastModified GMT");
header("ETag: $etag");
if (!input::$IE) {
header("Cache-Control: no-cache, must-revalidate");
}
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;
if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) {
header('HTTP/1.0 304 Not Modified');
return;
}
displayResource($this->id, "css");
l::fatal(30, "CSS resource {$this->id}");
}
}

View file

@ -0,0 +1,71 @@
<?php
class display_engine {
function maintenance($maintenance) {
echo "/* Server is under maintenance: " . $maintenance['reason'] . " */";
}
function displayFatalError($code, $i, $i2) {
echo "/* Fatal error $code */";
}
function initSession() {
}
function checkAuth() {
}
function handleInput() {
$this->id = (int) input::$input['id'];
}
function initRPC() {
}
function outputData() {
header("Content-Type: text/javascript");
if (!is_array(tracking::$data['jsId'])) {
tracking::$data['jsId'] = array();
} else {
$now = time();
foreach (tracking::$data['jsId'] as $ck => $t) {
if ($now - $t > 345600) {
unset(tracking::$data['jsId'][$ck]);
}
}
}
$jsId = input::$path . "/" . input::$page . "/{$this->id}";
if (!isset(tracking::$data['jsId'][$jsId])) {
tracking::$data['jsId'][$jsId] = time();
}
$time = tracking::$data['jsId'][$jsId];
$lastModified = substr(date('r', $time), 0, -5).'GMT';
$etag = '"'.md5($lastModified).'"';
header("Last-Modified: $lastModified GMT");
header("ETag: $etag");
if (!input::$IE) {
header("Cache-Control: no-cache, must-revalidate");
}
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;
if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) {
header('HTTP/1.0 304 Not Modified');
return;
}
displayResource($this->id, "js");
l::fatal(30, "JS resource {$this->id}");
}
}

View file

@ -0,0 +1,452 @@
<?php
// Standard page output engine
class display_engine {
public static $version;
function maintenance($maintenance) {
include(config::$main['scriptdir'] . "/site/main/maintenance.inc");
}
function displayFatalError($errno, $errorText, $information) {
ob_end_clean();
ob_start();
?>
<html>
<head>
<title>Legacy Worlds</title>
</head>
<body>
<p>
<b>The LegacyWorlds game engine encountered a fatal error:</b><br/>
<?=$errorText?><br/>
Please report this error by sending an e-mail to the staff at
<a href='mailto:webmaster@legacyworlds.com'>webmaster@legacyworlds.com</a>
explaining what happened. Please make sure you include information such
as the name of the Web browser you're using, the address of the page on
which you got this message, etc ... The more information you give us, the
better we'll be able to fix the problem.
</p>
</body>
</html>
<?php
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
function displayLogin($default = "", $error = false) {
input::$path = 'main';
input::$page = 'login';
$this->outputPage('main', 'login', array('login' => $default, 'error' => $error));
endRequest();
}
function displayConfirm($error = false) {
input::$path = 'main';
input::$page = 'confirm';
$this->outputPage('main', 'confirm', array('error' => $error));
endRequest();
}
function displayKicked() {
input::$path = 'main';
input::$page = 'kicked';
$this->outputPage('main', 'kicked', null);
endRequest();
}
function initSession() {
$create = handler::$h->needsAuth || input::$input['userlogin'] == 1 || input::$input['authcode'] != '' || input::$input['ac_restart'] != "";
$r = session::handle($create);
if ($r == 1 && $create) {
$this->displayLogin();
} elseif ($r == 2 && $create) {
l::trace("tracknew? " . (tracking::$new ? "yes" : "no"));
l::fatal(12);
}
}
function checkGameRegistration() {
if (input::$path == "main") {
return;
}
$game = input::$game;
if ($game->status() == 'PRE' || $game->status() == 'FINISHED') {
input::$path = 'main';
input::$page = 'notfound';
$this->outputPage('main', 'notfound', array());
endRequest();
}
$lib = $game->getLib();
$pid = $lib->call("doesUserPlay", $_SESSION['userid']);
if (is_null($pid)) {
input::$path = 'main';
input::$page = 'notregistered';
$this->outputPage('main', 'notregistered', array(
"id" => $game->name,
"name" => $game->text
));
endRequest();
}
if (!is_array($_SESSION["{$game->name}_data"])) {
$_SESSION["{$game->name}_data"] = array("player" => $pid);
}
dbQuery("UPDATE main.credits SET resources_used = resources_used + 1 WHERE account = {$_SESSION['userid']}");
}
function checkCredits() {
if (! is_null($_SESSION['annoy_until'])) {
if (time() >= $_SESSION['annoy_until']) {
$_SESSION['annoy_until'] = null;
return;
}
$this->outputPage('main', 'annoy', array(
"time" => $_SESSION['annoy_until'] - time()
));
endRequest();
}
$q = dbQuery("SELECT resources_used, credits_obtained FROM credits WHERE account = {$_SESSION['userid']}");
list($used, $cred) = dbFetchArray($q);
if ($used < $cred) {
return;
}
$time = min(60, round(5 + ($used - $cred) / 1000));
$_SESSION['annoy_until'] = time() + $time;
$this->outputPage('main', 'annoy', array(
"time" => $time + 1
));
endRequest();
}
function checkConfirmationCode($code) {
$q = "SELECT status,conf_code FROM account WHERE id=" . $_SESSION['userid'];
$qr = dbQuery($q);
if (!$qr || dbCount($qr) != 1) {
l::warn("Reading the {$_SESSION['userid']} account's confirmation code failed");
killSession();
$this->displayLogin();
}
list($status, $cc) = dbFetchArray($qr);
if ($cc != strtolower($code)) {
l::notice("Account {$_SESSION['userid']} entered an invalid confirmation code");
$this->displayConfirm(true);
}
// Validate account
dbQuery("UPDATE account SET conf_code=NULL,status='STD' WHERE id={$_SESSION['userid']}");
$_SESSION['authok'] = true;
accountLog('v', $_SESSION['userid']);
if ($status == 'NEW') {
// New accounts -> register to default game
$main = config::getGame('main');
$game = $main->getLib()->call('preJoin', $_SESSION['userid']);
input::$path = 'main';
input::$page = 'play';
if (is_null($game)) {
$this->outputPage('main', 'play', 2);
} else {
$this->outputPage('main', 'play', array('registered' => $game));
}
endRequest();
} else {
$input = $_SESSION['original_request'];
$this->checkGameRegistration();
}
}
function doRestart($uid) {
$conf = substr(md5(uniqid(rand())), 0, 16);
$q = dbQuery("SELECT name,email FROM account WHERE id='$uid' AND conf_code IS NULL");
if (!($q && dbCount($q) == 1)) {
return false;
}
list($u, $e) = dbFetchArray($q);
if (!dbQuery("UPDATE account SET conf_code='$conf' WHERE id='$uid'")) {
return false;
}
dbQuery(
"INSERT INTO account_log(tracking,account,ip_addr,action) VALUES("
. tracking::$dbId . ",$uid,'".$_SERVER['REMOTE_ADDR']."','CREATE')"
);
$mail = @file_get_contents(config::$main['scriptdir'] . "/game/main/mail/mail-restart.en.txt");
if (is_bool($mail)) {
return false;
}
$tmp = explode("\n", $mail);
$sub = array_shift($tmp);
$mail = preg_replace(
array('/_USER_/', '/_CCODE_/'),
array($u, $conf), join("\n", $tmp)
);
$hdr = "From: webmaster@legacyworlds.com\r\n"
. "Reply-To: webmaster@legacyworlds.com\r\n"
. "X-Mailer: LegacyWorlds\r\n"
. "Mime-Version: 1.0\r\n"
. "Content-Type: text/plain; charset=utf-8";
if (!mail($e, $sub, $mail, $hdr)) {
return false;
}
return true;
}
function restartAccount($mail) {
if (is_null(input::$input['ac_restart'])) {
input::$path = 'main';
input::$page = 'restart';
$this->outputPage('main', 'restart', array("email"=>$mail));
endRequest();
} else {
$this->doRestart($_SESSION['userid']);
$this->displayLogin();
}
}
function accountValidation($loggedIn = false) {
$q = dbQuery("SELECT status,reason,email,conf_code FROM account WHERE id={$_SESSION['userid']}");
if (!$q || dbCount($q) != 1) {
$engine->displayLogin($_SESSION['login'], true);
}
list($status,$reason,$mail,$code) = dbFetchArray($q);
if ($status == 'KICKED') {
l::notice("Got a login attempt from kicked user {$_SESSION['userid']}; setting banned attempt flag");
dbQuery("INSERT INTO banned_attempt (ip_addr) VALUES ('{$_SERVER['REMOTE_ADDR']}')");
tracking::$data['bat'] = true;
tracking::$data['uid'] = $_SESSION['userid'];
session::kill();
$this->displayKicked();
}
if ($status == 'STD' || $status == 'VAC') {
if (! $_SESSION['authok']) {
$_SESSION['authok'] = true;
$this->checkCredits();
} elseif (! is_null($_SESSION['annoy_until'])) {
$this->checkCredits();
}
$this->checkGameRegistration();
$_SESSION['last_page'] = time();
return;
}
if (is_null(input::$input['authcode']) && !is_null($code)) {
$_SESSION['original_request'] = input::$input;
$this->displayConfirm(false);
} elseif ($loggedIn) {
session::kill();
$this->displayLogin();
} elseif (!is_null(input::$input['authcode']) && !is_null($code)) {
$this->checkConfirmationCode(input::$input['authcode']);
} else {
$this->restartAccount($mail);
}
}
function checkAccountAuth($l, $p) {
$l1 = addslashes($l); $p1 = addslashes($p);
$q = "SELECT id,name,password FROM account WHERE lower(name) = lower('$l1')";
$qr = dbQuery($q);
if (!$qr || dbCount($qr) != 1) {
l::info("Account authentication failed, login '".urlencode($l)."' not found");
if ($qr) {
l::debug("Query returned " . dbCount($qr) . " row(s)", LOG_DEBUG);
}
return false;
}
list($uid,$l2,$pw) = dbFetchArray($qr);
if ($p != $pw) {
l::info("Account authentication failed, login '".urlencode($l)."' used invalid password");
l::debug("User ID: $uid ; real user name: $l2", LOG_DEBUG);
return false;
}
// Close existing sessions for the same user
$q = dbQuery("SELECT id,tracking FROM web_session WHERE account=$uid");
if ($q && dbCount($q)) {
list($sid, $tid) = dbFetchArray($q);
dbQuery(
"INSERT INTO account_log(tracking,account,ip_addr,action) "
. "VALUES ($tid,$uid,'AUTO','OUT')"
);
dbQuery("DELETE FROM web_session WHERE id=$sid");
}
$_SESSION['login'] = $l2;
$_SESSION['userid'] = $uid;
l::info("Player '$l2' (#$uid) logged in");
tracking::$data['previous_login'] = $l;
session::setAccount($uid);
accountLog('i', $uid);
$q = dbQuery("UPDATE account SET last_login=UNIX_TIMESTAMP(NOW()) WHERE id=$uid");
return true;
}
function checkAuth() {
$create = handler::$h->needsAuth || input::$input['userlogin'] == 1 || input::$input['authcode'] != '' || input::$input['ac_restart'] != "";
if (!$create) {
if ($_SESSION['authok']) {
$this->accountValidation(true);
}
return;
}
if (tracking::$data['bat'] ?? false) {
// User tried to log in after being kicked
session::kill();
$this->displayKicked();
} elseif ($_SESSION['login'] != "") {
$this->accountValidation();
} elseif (input::$input['userlogin'] == 1) {
if ($this->checkAccountAuth(input::$input['login'], input::$input['password'])) {
input::$input = $_SESSION['original_request'];
$this->accountValidation();
} else {
sleep(5);
$this->displayLogin(input::$input['login'], true);
}
} else {
$_SESSION['original_request'] = input::$input;
$this->displayLogin();
}
}
function handleInput() {
handler::$h->handle(input::$input);
if (is_null(handler::$h->output)) {
l::fatal(18, "In handler for ".input::$path." / " . input::$page);
}
}
function initRPC() {
ajax::init();
}
function outputData() {
$this->outputPage(input::$game->version->id, handler::$h->output, handler::$h->data);
}
function outputPage($version, $page, $args) {
$lang = getLanguage();
$f = $this->checkOutput($version, $page, $lang);
self::$version = $version;
$layoutFile = config::$main['scriptdir'] . "/site/$version/page.inc";
loader::load($layoutFile, "page_layout");
$this->layout = new page_layout();
ob_start();
$this->layout->header($page, $lang);
$this->layout->includeFile($f, $args);
$this->layout->footer($page, $lang);
$data = ob_get_contents();
ob_end_clean();
$this->sendHTTP($data);
}
/** This function checks for the presence of a file containing the specified
* page in the specified language. It dies with an internal error if the file
* isn't found, and returns the file's path otherwise.
*/
function checkOutput($version, $page, $lg) {
$file = config::$main['scriptdir']."/site/$version/output/$page.$lg.inc";
if (file_exists($file) && is_readable($file) && is_file($file)) {
return $file;
}
$file = config::$main['scriptdir']."/site/$version/output/$page.inc";
if (!(file_exists($file) && is_readable($file) && is_file($file))) {
l::fatal(16, "Page was '$page', language was '$lg'");
}
return $file;
}
function sendHTTP($data) {
header("Content-Type: text/html; charset=utf-8");
if (input::$IE || input::$safari) {
// Don't even bother with the cache; in fact, make sure the cache doesn't interfere
header("Expires: Thu, 19 Feb 1981 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
// Send the data directly
endRequest(false);
echo $data;
exit(0);
}
// Generate a HTML resource from the data
addRawResource('html', $data);
$resId = storeResource("html", 21600, false);
$htmlId = md5($_SERVER['REQUEST_URI']) . ":$resId";
// Check the tracking cookie for the required contents
if (!is_array(tracking::$data['htmlId'])) {
tracking::$data['htmlId'] = array();
} else {
$now = time();
foreach (tracking::$data['htmlId'] as $ck => $t) {
if ($now - $t > 21600) {
unset(tracking::$data['htmlId'][$ck]);
}
}
}
if (!isset(tracking::$data['htmlId'][$htmlId])) {
tracking::$data['htmlId'][$htmlId] = time();
}
// Send the page's header
$time = tracking::$data['htmlId'][$htmlId];
$lastModified = substr(date('r', $time), 0, -5).'GMT';
$etag = '"'.md5($lastModified).'"';
header("Last-Modified: $lastModified GMT");
header("ETag: $etag");
header("Cache-Control: no-cache, must-revalidate");
// Sends either a 304 reply or the data depending on the browser's will
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;
if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) {
header('HTTP/1.0 304 Not Modified');
} else {
endRequest(false);
echo $data;
exit(0);
}
}
}
?>

View file

@ -0,0 +1,34 @@
<?php
class display_engine {
function maintenance($maintenance) {
$this->redirect = makeLink('main', 'index');
$this->outputData();
}
function displayFatalError($code, $i, $i2) {
header("Location: http://www.legacyworlds.com");
}
function handleInput() {
$this->redirect = handler::$h->redirect(input::$input);
}
function outputData() {
header("Location: {$this->redirect}");
}
function initSession() {
session::handle(false);
}
function checkAuth() { }
function initRPC() { }
}

View file

@ -0,0 +1,167 @@
<?php
class display_engine {
function rpcHeader() {
header("Expires: Thu, 19 Feb 1981 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
header("Content-Type: text/plain; charset=utf-8");
}
function maintenance($maintenance) {
$this->rpcHeader();
echo "-:m";
}
function displayFatalError($code, $i, $i2) {
$this->rpcHeader();
echo "-:f:$code";
}
function initSession() {
$create = handler::$h->needsAuth;
$r = session::handle($create);
if ($r == 1 && $create) {
$this->needsLogin();
} elseif ($r == 2 && $create) {
l::debug("(RPC engine) tracknew? " . (tracking::$new ? "yes" : "no"));
l::fatal(12);
}
}
function sendKick($reason) {
$this->rpcHeader();
echo "-:k:$reason";
endRequest();
}
function needsLogin() {
$this->rpcHeader();
echo "-:a";
endRequest();
}
function checkAuth() {
if (!handler::$h->needsAuth) {
return;
}
if (isset(tracking::$data['bat'])) {
// User tried to log in after being kicked
session::kill();
$q = dbQuery("SELECT reason FROM account WHERE id=" . tracking::$data['uid'] . " AND status='KICKED'");
if ($q && dbCount($q) == 1) {
list($reason) = dbFetchArray($q);
} else {
$reason = "";
}
$this->sendKick($reason);
} elseif ($_SESSION['login'] != "") {
$q = dbQuery("SELECT status,reason FROM account WHERE id={$_SESSION['userid']}");
if (!($q && dbCount($q) == 1)) {
$this->needsLogin();
}
list($status,$reason) = dbFetchArray($q);
if ($status == "STD" || $status == "VAC") {
$this->checkGameRegistration();
return;
}
if ($status == 'KICKED') {
session::kill();
$this->sendKick($reason);
}
$this->rpcHeader();
echo "-:";
endRequest();
} else {
$this->needsLogin();
}
}
private function checkGameRegistration() {
if (input::$path == "main") {
return;
}
$game = input::$game;
if (!($game->status() == 'PRE' || $game->status() == 'FINISHED')) {
$lib = $game->getLib();
$pid = $lib->call("doesUserPlay", $_SESSION['userid']);
if (!is_null($pid)) {
if (!is_array($_SESSION["{$game->name}_data"])) {
$_SESSION["{$game->name}_data"] = array("player" => $pid);
}
dbQuery("UPDATE main.credits SET resources_used = resources_used + 1 WHERE account = {$_SESSION['userid']}");
return;
}
}
$this->rpcHeader();
echo "-:";
endRequest();
}
function handleInput() {
// Get arguments and function name
$this->func = preg_replace('/[^A-Za-z0-9_]/', '', input::$input['rs']);
$this->args = empty(input::$input['rsargs']) ? array() : input::$input['rsargs'];
}
function initRPC() {
ajax::init();
}
function outputData() {
if (is_array($_SESSION) && $_SESSION['authok']) {
if (is_null($_SESSION['last_page'])) {
$_SESSION['last_page'] = time();
} else if (time() - $_SESSION['last_page'] >= 7200) {
l::notice("Player '{$_SESSION['login']}' (#{$_SESSION['userid']}) is idling - forcing log out");
session::kill();
$this->needsLogin();
}
}
$this->rpcHeader();
$page = input::$page;
if (ajax::canCall($this->func)) {
$res = ajax::call($this->func, $this->args);
// Trace activity
/* FIXME: Disabled for now, should be somewhere else.
$spyOn = array();
if (in_array($_SESSION['userid'], $spyOn)) {
logText("*** TRACE ({$_SESSION['userid']}:{$_SESSION['login']}) RPC: {$page}::{$this->func} called, arguments:");
foreach ($this->args as $arg) {
logText("*** TRACE ({$_SESSION['userid']}:{$_SESSION['login']}) RPC: -> $arg");
}
logText("*** TRACE ({$_SESSION['userid']}:{$_SESSION['login']}) RPC: returning $res");
}
*/
echo "+:$res";
} else {
l::notice("RPC: unknown function call {$page}::{$this->func}");
l::debug("Referer: " . $_SERVER['HTTP_REFERER']);
echo "-:c:{$this->func}";
}
}
}

View file

@ -0,0 +1,33 @@
<?php
class display_engine {
function maintenance($maintenance) {
}
function displayFatalError($code, $text, $info) {
}
function initSession() {
}
function checkAuth() {
}
function handleInput() {
}
function initRPC() {
}
function outputData() {
}
}

View file

@ -0,0 +1,247 @@
<?php
class display_engine {
private $document;
private $outputMode;
private $data;
function __construct() {
$this->document = new DomDocument('1.0', 'utf-8');
$this->outputMode = "xml";
}
function maintenance($maintenance) {
$this->data = new data_node('Maintenance');
$this->data->addContents(new data_leaf('Until', strftime("%Y-%m-%d %H:%M:%S", $maintenance['until'])));
$this->data->addContents(new data_leaf('Current', gmstrftime("%Y-%m-%d %H:%M:%S")));
$this->data->addContents(new data_leaf('Reason', $maintenance['reason']));
$this->rawOutput($this->genOutput('xml'));
}
function displayFatalError($code, $i, $i2) {
$this->data = new data_node('FatalError');
$this->data->setAttribute('code', $code);
$this->data->addContents(new data_leaf('Text', $i));
return $this->genOutput('xml');
}
private function loginFailed($code) {
$this->data = new data_node('Failed');
$this->data->setAttribute('code', $code);
$this->rawOutput($this->genOutput('xml'));
}
private function kicked($reason) {
$this->data = new data_node('Kicked');
$this->data->addContents(new data_leaf('Reason', $reason));
$this->rawOutput($this->genOutput('xml'));
}
function initSession() {
// Check for "single-shot" request
if (input::$input['__s'] == 1) {
$_SESSION = array();
return;
}
// FIXME: handle session, but why bother doing it when the whole thing will be rewritten?
}
function checkAuth() {
// Check authentication directly in "single-shot" mode
if (input::$input['__s'] == 1) {
$this->singleShotAuth(input::$input['__l'], input::$input['__p']);
$this->outputMode = "lw";
return;
}
// FIXME: handle authentication, but why bother doing it when the whole thing will be rewritten?
}
private function singleShotAuth($login, $password) {
// Check the account's login and password
if (!$this->checkAccountAuth($login, $password)) {
sleep(5);
$this->loginFailed(0);
}
// Make sure the account is in a valid state
$q = dbQuery("SELECT status,reason FROM account WHERE id={$_SESSION['userid']}");
if (!$q || dbCount($q) != 1) {
$this->loginFailed(1);
}
list($status,$reason) = dbFetchArray($q);
if ($status == 'KICKED') {
$this->kicked($reason);
} elseif ($status == 'QUIT' || $status == 'INAC') {
$this->loginFailed(2);
} elseif ($status == 'NEW') {
$this->loginFailed(3);
}
$_SESSION['authok'] = true;
if (!$this->checkVersionRegistration()) {
$this->loginFailed(4);
}
$this->singleShotLog();
}
private function checkAccountAuth($l, $p) {
$l1 = addslashes($l); $p1 = addslashes($p);
$q = "SELECT id,name,password FROM account WHERE lower(name)=lower('$l1')";
$qr = dbQuery($q);
if (!$qr || dbCount($qr) != 1) {
l::info("Single-shot account authentication failed: login '".urlencode($l)."' not found");
if ($qr) {
l::debug("Query returned " . dbCount($qr) . " row(s)", LOG_DEBUG);
}
return false;
}
list($uid,$l2,$pw) = dbFetchArray($qr);
if ($p != $pw) {
l::info("Single-shot account authentication failed: login '"
. urlencode($l) . "' used invalid password");
l::debug("User ID: $uid ; real user name: $l2");
return false;
}
$_SESSION['login'] = $l2;
$_SESSION['userid'] = $uid;
return true;
}
private function checkVersionRegistration() {
if (input::$path == "main") {
return true;
}
$game = config::getGame(input::$path);
$lib = $game->getLib();
$pid = $lib->call("doesUserPlay", $_SESSION['userid']);
if (is_null($pid)) {
return false;
}
if (!is_array($_SESSION["{$game->name}_data"])) {
$_SESSION["{$game->name}_data"] = array("player" => $pid);
}
return true;
}
private function singleShotLog() {
$uid = $_SESSION['userid'];
$q = dbQuery("SELECT id FROM web_session WHERE account = $uid");
if (!($q && dbCount($q))) {
$in = time();
$out = $in + 1;
$q = dbQuery("UPDATE account SET last_login=$in,last_logout=$out WHERE id=$uid");
}
dbQuery("INSERT INTO account_log(account,ip_addr,action,t) VALUES ($uid,'AUTO','IN',unix_timestamp(now()) - 1)");
dbQuery("INSERT INTO account_log(account,ip_addr,action) VALUES ($uid,'AUTO','OUT')");
}
function handleInput() {
$data = handler::$h->xml(input::$input);
if (!($data instanceof data_gen)) {
l::fatal(18, "In XML handler for " . input::$path . "/" . input::$page);
}
$this->data = $data;
}
function initRPC() { /*NO RPC*/ }
function outputData() {
$data = $this->genOutput($this->outputMode);
if (input::$IE || input::$safari || tracking::$disabled) {
$this->rawOutput($data);
} else {
$this->cachedOutput($data);
}
}
private function cachedOutput($data) {
// Generate an XML resource from the data
addRawResource('xml', $data);
$resId = storeResource("xml", 21600, false);
$xmlId = md5($_SERVER['REQUEST_URI']) . ":$resId";
// Check the tracking cookie for the required contents
if (!is_array(tracking::$data['xmlId'])) {
tracking::$data['xmlId'] = array();
}
if (!isset(tracking::$data['xmlId'][$xmlId])) {
tracking::$data['xmlId'][$xmlId] = time();
}
// Send the page's header
$time = tracking::$data['xmlId'][$xmlId];
$lastModified = substr(date('r', $time), 0, -5).'GMT';
$etag = '"'.md5($lastModified).'"';
header("Content-Type: text/xml; charset=utf-8");
header("Last-Modified: $lastModified GMT");
header("ETag: $etag");
header("Cache-Control: no-cache, must-revalidate");
// Sends either a 304 reply or the data depending on the browser's will
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;
if (($ifNoneMatch || $ifModifiedSince) && (!$ifNoneMatch || $ifNoneMatch == $etag) && (!$ifModifiedSince || $ifModifiedSince != $lastModified)) {
header('HTTP/1.0 304 Not Modified');
} else {
endRequest(false);
echo $data;
exit(0);
}
}
private function rawOutput($data) {
header("Content-Type: text/xml; charset=utf-8");
header("Expires: Thu, 19 Feb 1981 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
// Send the data directly
endRequest(false);
echo $data;
exit(0);
}
private function genOutput($mode) {
if ($mode == 'xml') {
$data = $this->data->toXML($this->document);
} else {
$data = $this->data->toLWData($this->document);
}
$this->document->appendChild($data);
return $this->document->saveXML();
}
}
?>

244
game/scripts/lib/game.inc Normal file
View file

@ -0,0 +1,244 @@
<?php
class game {
private $__status = null;
private $__firstTick = null;
private $__lastTick = null;
public $db = null;
function __construct($version, $name, $namespace, $public, $canJoin, $text) {
$this->version = $version;
$this->name = $name;
$this->namespace = $namespace;
$this->public = $public;
$this->canJoin = $canJoin;
$this->text = $text;
$this->ticks = array();
$this->params = array();
$this->descriptions = array();
$this->initExternal();
}
private function initExternal() {
$this->dir = $this->version->getDirectory();
$this->siteDir = $this->version->getSiteDirectory();
}
function getDBAccess() {
if ($this->db) {
return $this->db;
}
if (!db::$database) {
return null;
}
return ($this->db = db::$database->getGameAccess($this->namespace));
}
function addTick($tick) {
$td = $tick->definition->script;
if (!isset($this->ticks[$td])) {
$this->ticks[$td] = $tick;
}
}
private function __loadActionObject() {
if (isset($this->actionObj)) {
return;
}
$acn = $this->version->loadActionsClass();
$this->actionObj = new $acn($this);
foreach (get_class_methods($acn) as $method) {
$this->actions[strtolower($method)] = array(false, $method);
}
if (is_array($this->actionObj->index)) {
foreach ($this->actionObj->index as $action) {
if (strtolower($action) == strtolower($acn)) {
continue;
}
$this->actions[strtolower($action)] = array(true, $action, null);
}
}
}
private function __doAction($action, $args, $log = false) {
// Load actions object
$this->__loadActionObject();
// Check action
if (!is_array($this->actions[$action])) {
l::fatal(23, "Unknown action was '$action' on game '{$this->name}'");
}
if ($this->actions[$action][0]) {
// Load separate action class
if (!is_object($this->actions[$action][2])) {
$acn = $this->version->loadAction($this->actions[$action][1]);
$this->actions[$action][2] = new $acn($this);
}
$rv = call_user_func_array(array($this->actions[$action][2], 'run'), $args);
} else {
// Call the action instance's method
$rv = call_user_func_array(array($this->actionObj, $this->actions[$action][1]), $args);
}
return $rv;
}
public function action() {
$n = func_num_args();
if ($n == 0) {
l::fatal(22, "Empty action call on game '{$this->name}'");
}
// Get arguments
$args = func_get_args();
$action = strtolower(array_shift($args));
return $this->__doAction($action, $args);
}
public function deprecatedAction() {
$n = func_num_args();
if ($n == 0) {
l::fatal(22, "Empty action call on game '{$this->name}'");
}
// Get arguments
$args = func_get_args();
$action = strtolower(array_shift($args));
return $this->__doAction($action, $args);
}
function getLib($lib = null) {
if (is_null($lib)) {
$lib = $this->version->id;
}
if (!isset($this->libraries[$lib])) {
$this->libraries[$lib] = new library($lib, $this);
}
$this->getDBAccess();
return $this->libraries[$lib];
}
function runTick($name, $okNotFound = false) {
if (!is_null($this->ticks[$name])) {
$this->ticks[$name]->run();
} elseif ($okNotFound) {
$tick = $this->getLib($this->version->id . "/ticks/$name");
if (is_null($tick)) {
l::error("TICK: tick library '$libName' not found");
} else {
$tick->call('runTick');
}
}
}
private function earliestTick() {
$then = time() + 10 * 365 * 24 * 60 * 60;
foreach ($this->ticks as $td => $tick) {
if ($tick->first < $then && $tick->definition->public) {
$then = $tick->first;
}
}
return $then;
}
private function latestTick() {
$then = 0;
foreach ($this->ticks as $td => $tick) {
if (is_null($tick->last)) {
continue;
}
if ($tick->last > $then) {
$then = $tick->last;
}
}
return $then;
}
private function computeStatus() {
$visible = $this->public && $this->canJoin;
$this->__firstTick = $this->earliestTick($this);
$this->__lastTick = $this->latestTick($this);
$now = time();
if ($this->__lastTick && $this->__lastTick < $now) {
$this->__status = 'FINISHED';
} elseif (!$visible) {
$this->__status = 'PRE';
} else {
$running = ($this->__firstTick <= $now);
if ($running) {
if ($this->__lastTick) {
$this->__status = "ENDING";
} else {
$gLib = $this->getLib();
$this->__status = $gLib->call('isFinished') ? 'VICTORY' : 'RUNNING';
}
} else {
$this->__status = 'READY';
}
}
}
function status() {
if (is_null($this->__status)) {
$this->computeStatus();
}
return $this->__status;
}
function firstTick() {
if (is_null($this->__firstTick)) {
$this->computeStatus();
}
return $this->__firstTick;
}
function lastTick() {
if (is_null($this->__lastTick)) {
$this->computeStatus();
}
return $this->__lastTick;
}
function __sleep() {
return array('version', 'name', 'namespace', 'public', 'canJoin', 'text', 'descriptions', 'ticks', 'params');
}
function __wakeup() {
$this->initExternal();
}
public static function sessName() {
if (class_exists('input')) {
return input::$game->name . "_data";
}
return "";
}
}
?>

View file

@ -0,0 +1,77 @@
<?php
/****************************************************************************
* PAGE HANDLER FUNCTIONS
****************************************************************************/
class handler {
static $h = null;
/** This function looks for a handler for the requested combination of game
* version and page. If such a handler is found, the file is loaded and the
* handler is initialized.
*/
public static function load() {
$path = input::$path;
$page = input::$page;
$game = input::$game;
if (!file_exists("{$game->siteDir}/handlers") || !is_dir($game->siteDir."/handlers")) {
l::fatal(8, array("Handlers directory for path '$path' not found (page '$page')"));
}
$ifile = "{$game->siteDir}/handlers/$page.inc";
if (!(file_exists($ifile) && is_readable($ifile) && is_file($ifile))) {
l::notice("Requested path '" . input::$path . "' doesn't match any handler");
l::debug("Referer was '{$_SERVER['HTTP_REFERER']}' ; requested page was '" . input::$page . "'");
$path = input::$path = 'main';
$page = input::$page = 'notfound';
input::$eType = null;
$game = input::$game = config::getGame('main');
$ifile = "{$game->siteDir}/handlers/$page.inc";
}
if (!include_once($ifile)) {
l::fatal(10, "File inclusion failed: '$ifile'");
}
if (!class_exists('page_handler')) {
l::fatal(11, "File '$ifile' did not define a page_handler class");
}
handler::$h = $handler = new page_handler();
$handler->game = $game;
/* The handler is loaded, check the engine type vs. the handler's
* supported and default engines.
*/
// Get the list of supported engines
if (isset($handler->engines) && is_array($handler->engines)) {
$engines = $handler->engines;
} else {
$engines = array('page', 'css', 'js', 'rpc');
}
// Get the default engine
if (isset($handler->defaultEngine)) {
$dEngine = $handler->defaultEngine;
} else {
$dEngine = $engines[0];
}
// Set the engine type to default if it isn't set
if (is_null(input::$eType)) {
input::$eType = $dEngine;
}
// Check the engine's type against the list of supported engines
if (!in_array(input::$eType, $engines)) {
l::fatal(31, "$path/$page: trying to use unsupported engine type '" . input::$eType . "'");
}
return $handler;
}
}
?>

108
game/scripts/lib/input.inc Normal file
View file

@ -0,0 +1,108 @@
<?php
/****************************************************************************
* INPUT DATA GATHERING FUNCTIONS
****************************************************************************/
class input {
static $input = array();
static $path = null;
static $page = null;
static $eType = null;
static $game = null;
static $IE = null;
static $safari = null;
/** This function analyzes the request to find out which "sub-version" is being
* accessed, and which page should be displayed.
*/
static function identify() {
$p = $_SERVER["PATH_INFO"] ?? "";
if (preg_match('/\.[a-z0-9]{2,10}$/', $p)) {
input::$eType = preg_replace('/^.*\.([a-z0-9]{2,10})$/', '\1', $p);
$p = preg_replace('/\.[a-z0-9]{2,10}$/', '', $p);
} else {
input::$eType = null;
}
if ($p == "") {
input::$path = 'main';
input::$page = 'index';
} elseif (preg_match('/[^A-Za-z0-9\/]/', $p)) {
l::notice("Invalid path requested: '$p'");
l::debug("Referer was '{$_SERVER['HTTP_REFERER']}'");
input::$path = 'main';
input::$page = 'notfound';
input::$eType = null;
input::$game = config::getGame('main');
} else {
$p = preg_replace(
array('/\/+/', '/\/$/', '/^\//'),
array('/', '', ''),
$p
);
$tmp = explode('/', $p);
if (count($tmp) > 2) {
l::notice("Invalid path requested: '$p'");
l::debug("Referer was '{$_SERVER['HTTP_REFERER']}'");
input::$path = 'main';
input::$page = 'notfound';
input::$eType = null;
input::$game = config::getGame('main');
} else {
if (count($tmp) == 1) {
input::$path = 'main';
} else {
input::$path = array_shift($tmp);
}
input::$page = $tmp[0];
}
}
// Find the game instance for this game
if (!config::hasGame(input::$path)) {
l::notice("Requested path '" . input::$path . "' doesn't match any game");
l::debug("Referer was '{$_SERVER['HTTP_REFERER']}' ; requested page was '" . input::$page . "'");
input::$path = 'main';
input::$page = 'notfound';
input::$eType = null;
input::$game = config::getGame('main');
} else {
input::$game = config::getGame(input::$path);
}
l::setPrefix(input::$path . "/" . input::$page . (is_null(input::$eType) ? "" : ( "." . input::$eType)));
return array(input::$path, input::$page, input::$eType);
}
/** This function reads the arguments in the $_POST variable, then in the $_GET
* variable, and stores them inside an hash table. If PHP's crappy "magic
* quotes" are enabled, remove them.
* NOTE: This behaviour should be reversed in a production version.
*/
static function read() {
input::$IE = preg_match('/MSIE/', $_SERVER['HTTP_USER_AGENT'])
&& !preg_match('/Opera/', $_SERVER['HTTP_USER_AGENT']);
input::$safari = preg_match('/AppleWebKit/', $_SERVER['HTTP_USER_AGENT']);
$p = array();
foreach ($_POST as $k => $v) {
$p[$k] = $v;
}
foreach ($_GET as $k => $v) {
$p[$k] = $v;
}
input::$input = $p;
return $p;
}
}
?>

View file

@ -0,0 +1,79 @@
<?php
class library {
var $name;
var $game;
var $mainClass = null;
var $functions = array();
function __construct($name, $game) {
$this->name = $name;
$this->game = $game;
}
private function loadClass($name = null) {
// Get the path to the class to be loaded
$path = config::$main['scriptdir'] . "/game/{$this->name}/library";
if (!is_null($name)) {
$path .= "/$name";
}
$path .= ".inc";
// Get the class' name
$cn = preg_replace('#/#', '_', strtolower($this->name));
$cn .= is_null($name) ? "_library" : "_$name";
// Load it
loader::load($path, $cn);
return $cn;
}
public function call() {
$n = func_num_args();
if ($n == 0) {
l::fatal(22, "Empty library call for library '{$this->name}' on game '{$this->game->game['site_path']}'");
}
// Load the main class if that is needed
if (!$this->mainClass) {
$lcn = $this->loadClass();
$this->mainClass = new $lcn($this);
foreach (get_class_methods($lcn) as $method) {
$this->functions[strtolower($method)] = array(false, $method);
}
if (is_array($this->mainClass->index)) {
foreach ($this->mainClass->index as $function) {
if (strtolower($function) == strtolower($lcn)) {
continue;
}
$this->functions[strtolower($function)] = array(true, $function, null);
}
}
}
// Check function
$args = func_get_args();
$function = strtolower(array_shift($args));
if (!is_array($this->functions[$function])) {
l::fatal(23, "Unknown function call '$function' in library '{$this->name}' on game '{$this->game->game['site_path']}'");
}
if ($this->functions[$function][0]) {
// Load separate class
if (!is_object($this->functions[$function][2])) {
$fcn = $this->loadClass($this->functions[$function][1]);
$this->functions[$function][2] = new $fcn($this);
}
$rv = call_user_func_array(array($this->functions[$function][2], 'run'), $args);
} else {
// Call the function instance's method
$rv = call_user_func_array(array($this->mainClass, $this->functions[$function][1]), $args);
}
return $rv;
}
}
?>

295
game/scripts/lib/log.inc Normal file
View file

@ -0,0 +1,295 @@
<?php
/****************************************************************************
* LOG AND FATAL ERRORS
****************************************************************************/
class l {
/** This variable should contain the name of the function to call when
* handling fatal errors.
*/
private static $fatalErrorCallback = "l::defaultFatalError";
/** This array lists the errors the engine can encounter and handle
* through the fatalError() function.
*/
private static $engineErrors = array(
0 => "Could not open configuration file",
1 => "Could not connect to database",
2 => "Failed to set up tracking data",
3 => "Failed to set up tracking data",
4 => "Failed to set up tracking data",
5 => "Failed to set up tracking data",
6 => 'Invalid request',
7 => 'Invalid request',
8 => 'Page not found',
9 => 'Page not found',
10 => 'Internal error',
11 => 'Internal error',
12 => "Failed to set up session data",
13 => "Failed to set up session data",
14 => "Failed to set up session data",
15 => "Failed to set up session data",
16 => 'Internal error',
17 => 'Internal error',
18 => "Internal error",
19 => 'Internal error',
20 => 'Internal error',
21 => 'Internal error',
22 => 'Internal error',
23 => 'Internal error',
24 => 'Internal error',
25 => 'Internal error',
26 => 'Invalid extension',
27 => 'Unhandled extension',
28 => 'Internal error',
29 => 'Internal error',
30 => 'Resource not found',
31 => 'Unhandler extension',
);
/** This variable indicates whether the logging system has
* been initialised.
*/
private static $initialised = false;
/** This variable contains the prefix to use when writing
* to syslog.
*/
private static $syslogPrefix = "lwEngine";
/** This variable defines a text that is used as a prefix
* when logging strings.
*/
private static $prefix = "";
/** This variable prevents multiple "deprecated" entries from
* being logged.
*/
private static $deprecatedLogged = false;
/** This variable prevents multiple "FIXME" entries from
* being logged.
*/
private static $fixmeLogged = false;
/** This function is the default fatal error display function. */
private static function defaultFatalError($errno, $error, $info) {
ob_start();
?>
<html>
<head>
<title>Legacy Worlds</title>
</head>
<body>
<p>
<b>The LegacyWorlds game engine encountered a fatal error:</b><br/>
<?=$error?><br/>
Please report this error by sending an e-mail to the staff at
<a href='mailto:webmaster@legacyworlds.com'>webmaster@legacyworlds.com</a>
explaining what happened.
</p>
</body>
</html>
<?php
$html = ob_get_contents();
ob_end_clean();
die($html);
}
/** This method initialises communications with syslog.
*/
private static function init() {
if (self::$initialised) {
return;
}
openlog(self::$syslogPrefix, LOG_PID, LOG_USER);
self::$initialised = true;
}
/** This method writes a string to the system log.
*/
private static function __write($txt, $level) {
syslog($level, self::$prefix . $txt);
}
/** This method writes a string to the system log;
* it is used by the external "logText" function and
* should disappear in time.
*/
public static function write($txt, $level) {
self::init();
if (is_null($level)) {
$level = LOG_INFO;
}
self::__write($txt, $level);
}
/** This method displays one of the game engine's
* fatal errors and adds log entries accordingly.
*/
public static function fatal($errno, $information = null) {
// Log the error
$errorText = self::$engineErrors[$errno] . " [" . sprintf("%03d", $errno) . "]";
self::critical($errorText);
// Log additional information
if (is_null($information)) {
$information = array();
} elseif (!is_array($information)) {
$information = array($information);
}
foreach ($information as $it) {
self::info($it);
}
// Log additional details
if (class_exists('input') && !is_null(input::$page)) {
self::debug("Request: " . input::$path . "/" . input::$page . "." . input::$eType);
if (tracking::$id != '') {
$txt = "tracking: " . tracking::$id . " (" . tracking::$dbId . ")";
if (session::$id != '') {
$txt .= "; session: " . session::$id . " (" . session::$dbId . ")";
if (!is_null($_SESSION['userid'])) {
$txt .= "; user {$_SESSION['login']} ({$_SESSION['userid']})";
}
}
$txt .= "; user agent: {$_SERVER['HTTP_USER_AGENT']}";
self::debug($txt);
}
}
self::backtrace();
eval(self::$fatalErrorCallback . '($errno, $errorText, $information);');
}
/** This method writes a backtrace to the log, removing all
* entries from the logging class.
*/
public static function backtrace() {
$bt = debug_backtrace();
while (is_array($bt[1]) && isset($bt[1]['class']) && $bt[1]['class'] == 'l') {
array_shift($bt);
}
self::info("Backtrace to the error:");
$base = dirname(config::$main['scriptdir']);
foreach ($bt as $data) {
$class = $data['class'] ?? '';
$cnLength = strlen($class);
$str = "... " . str_repeat(' ', $cnLength > 30 ? 1 : (31 - $cnLength)) . $class
. " :: " . $data['function'];
if (!is_null($data['file'])) {
$cnLength = strlen($data['function']);
$fn = preg_replace("#^$base/#", "", $data['file']);
$str .= str_repeat(' ', $cnLength > 25 ? 1 : (26 - $cnLength))
. " (line {$data['line']}, file '$fn')";
}
self::info($str);
}
}
/** This method changes the syslog prefix.
*/
public static function setSyslogPrefix($prefix) {
self::$syslogPrefix = $prefix;
if (self::$initialised) {
closelog();
self::$initialised = false;
}
}
/** This method changes the string prefix.
*/
public static function setPrefix($prefix) {
self::$prefix = "$prefix ";
}
/** This method changes the function to call
* for fatal errors.
*/
public static function setFatalHandler($function) {
self::$fatalErrorCallback = $function;
}
/** This function logs the use of a deprecated
* function call and prevents further logging of
* similar occurences.
*/
public static function deprecated($function) {
if (config::$main['debug'] == 2 && !self::$deprecatedLogged) {
l::trace("DEPRECATED: $function");
l::backtrace();
self::$deprecatedLogged = true;
}
}
/** This function logs FIXME's. */
public static function FIXME($text) {
if (config::$main['debug'] >= 1 && !self::$fixmeLogged) {
l::debug("FIXME: $text");
if (config::$main['debug'] == 2) {
l::backtrace();
}
self::$fixmeLogged = true;
}
}
/******************* LOGGING METHODS ************************/
/* These methods should replace logText() wherever possible */
/************************************************************/
public static function crit($txt) { self::init(); self::__write($txt, LOG_CRIT); }
public static function critical($txt) { self::init(); self::__write($txt, LOG_CRIT); }
public static function error($txt) { self::init(); self::__write($txt, LOG_ERR); }
public static function warn($txt) { self::init(); self::__write($txt, LOG_WARNING); }
public static function warning($txt) { self::init(); self::__write($txt, LOG_WARNING); }
public static function notice($txt) { self::init(); self::__write($txt, LOG_NOTICE); }
public static function info($txt) { self::init(); self::__write($txt, LOG_INFO); }
public static function debug($txt) {
if (config::$main['debug'] >= 1) {
self::init(); self::__write($txt, LOG_DEBUG);
}
}
public static function trace($txt) {
if (config::$main['debug'] == 2) {
self::init(); self::__write($txt, LOG_DEBUG);
}
}
}
/** This function writes an entry to the system log. */
function logText($txt, $level = null) {
l::deprecated("logText()");
l::write($txt, $level);
}
/** This function displays one of the game engine's fatal errors and adds
* log entries accordingly.
*/
function fatalError($errno, $information = null) {
l::deprecated("fatalError($errno)");
l::fatal($errno, $information);
}
?>

141
game/scripts/lib/output.inc Normal file
View file

@ -0,0 +1,141 @@
<?php
/****************************************************************************
* HELPER FUNCTIONS
****************************************************************************/
function tooltip($n) {
return " onmouseover=\"return escape('".utf8entities($n,ENT_QUOTES)."')\"";
}
/** This function checks the static elements directory for a specified path.
* If the path is found to exist, an URL to this path is returned, otherwise
* a null value is returned.
*/
function getStatic($path) {
$f = config::$main['staticdir'] . "/$path";
if (!file_exists($f)) {
return null;
}
return config::$main['staticurl'] . "/$path";
}
/** This function generates a link to a script page. It accepts either one or
* two arguments: a page name and an optional version identifier. If the
* version identifier isn't specified, the current version is used.
*/
function makeLink($p, $v = null, $e = null) {
if (is_null($v)) {
$v = display_engine::$version;
}
$es = is_null($e) ? "" : ".$e";
return $_SERVER['SCRIPT_NAME'] . "/$v/$p$es";
}
/** This function terminates request processing by storing session and
* tracking information. It then exits the script.
*/
function endRequest($quit = true) {
session::store();
tracking::store();
dbEnd();
if ($quit) {
exit(0);
}
}
/** This function identifies the directory containing the lay-out scripts for
* the current version+theme combination, checks for its existence (the script
* bails out if it doesn't exist) and returns it.
*/
function getLayoutDirectory($version) {
$d = config::$main['scriptdir'] . "/site/$version/layout";
if ($version != 'main') {
$t = prefs::get("$version/theme");
if ($t == "") {
$t = "default";
}
$d .= "/$t";
}
if (!file_exists($d)||!is_dir($d)) {
l::fatal(17, "Directory '$d' for version '$version'");
}
return $d;
}
/** This function determines the current language from either:
* 1) the user's preferences it they are available,
* 2) tracking data if a user has already logged in,
* 3) the default value, "en".
* If user preferences are currently available, tracking data is modified
* accordingly.
*/
function getLanguage() {
$l = prefs::get('main/language');
if ($l != "") {
if ($_SESSION['authok']) {
tracking::$data['language'] = $l;
}
return $l;
} elseif ($_SESSION['authok']) {
return "en";
}
$l = tracking::$data['language'];
if ($l != "") {
return $l;
}
return "en";
}
/** This function acts like utf8entities, except for the fact that it uses
* UTF-8 by default.
*/
function utf8entities($string, $mode = ENT_COMPAT) {
return htmlentities($string, $mode, 'UTF-8');
}
/****************************************************************************
* PAGE OUTPUT FUNCTIONS
****************************************************************************/
function handleInput() {
engine::$e->initSession();
engine::$e->checkAuth();
if (isset($_SESSION) && is_array(config::$main['trace'])
&& in_array($_SESSION['userid'], config::$main['trace'])) {
$path = input::$path;
$page = input::$page;
ob_start();
print "REQUEST AT $path/$page." . input::$eType . "\n";
print "==========================================\n";
print "Time: " . gmstrftime("%Y-%m-%d %H:%M:%S", time()) . "\n\n";
print "SESSION DATA\n";
print_r($_SESSION);
print "\n\nINPUT:";
print_r(input::$input);
print "\n\n";
$log = ob_get_contents();
$logFile = fopen("/tmp/trace-{$_SESSION['userid']}" . gmstrftime("-%Y-%m-%d", time()) . ".log", "a");
fwrite($logFile, $log);
fclose($logFile);
ob_end_clean();
}
engine::$e->handleInput();
engine::$e->initRPC();
engine::$e->outputData();
endRequest();
}
?>

112
game/scripts/lib/prefs.inc Normal file
View file

@ -0,0 +1,112 @@
<?php
/****************************************************************************
* PREFERENCES MANAGEMENT FUNCTIONS
****************************************************************************/
class prefs {
private static $prefs = null;
/** This function transforms user_preferences database records into values
* stored inside $prefs.
*/
private static function fromBase($qr, $toTrack = false) {
while ($r = dbFetchHash($qr)) {
$v = $r['version'];
$i = $r['id'];
if (!isset(prefs::$prefs[$v])) {
prefs::$prefs[$v] = array();
}
prefs::$prefs[$v][$i] = $r['value'];
if ($toTrack && $v == "main") {
if (!is_array(tracking::$data['pref_override'])) {
tracking::$data['pref_override'] = array();
}
tracking::$data['pref_override'][$i] = $r['value'];
}
}
}
/** This function initializes $prefs to an empty array
* then reads default preferences from the user_preferences table.
*/
private static function getDefaults() {
prefs::$prefs = array();
$qr = dbQuery("SELECT id,version,value FROM user_preferences WHERE account=0");
if (!$qr || !pg_num_rows($qr)) {
return;
}
prefs::fromBase($qr);
}
/** This function reads the current user's preferences from the base. */
private static function loadUser() {
$qr = dbQuery("SELECT id,version,value FROM user_preferences WHERE account={$_SESSION['userid']}");
if (!$qr || !pg_num_rows($qr)) {
return;
}
prefs::fromBase($qr, true);
}
/** This function loads preferences by loading default values and then, if
* an user is authenticated, his own preferences.
*/
private static function load() {
prefs::getDefaults();
if ($_SESSION['authok']) {
prefs::loadUser();
} elseif (is_array(tracking::$data['pref_override'])) {
foreach (tracking::$data['pref_override'] as $pref => $value) {
prefs::$prefs['main'][$pref] = $value;
}
}
}
/** This function sets user preferences. */
static function set($path, $val) {
if (!$_SESSION['authok']) {
return false;
}
if (!is_array(prefs::$prefs)) {
prefs::load();
}
list($ver, $pref) = explode('/', $path);
$q = dbQuery("DELETE FROM user_preferences WHERE account={$_SESSION['userid']} AND id='$pref' AND version='$ver'");
$q = dbQuery("INSERT INTO user_preferences VALUES('$pref','$ver',{$_SESSION['userid']},'".addslashes($val)."')");
if ($q) {
prefs::$prefs[$ver][$pref] = $val;
return true;
}
return false;
}
/** This function returns a value from the preferences using a version/pref_name path.
*/
static function get($path, $default = null) {
if (!is_array(prefs::$prefs)) {
prefs::load();
}
list($version, $name) = explode('/', $path);
if (isset(prefs::$prefs[$version])) {
$v = prefs::$prefs[$version][$name];
} else {
$v = null;
}
if (is_null($v) && !is_null($default)) {
$v = $default;
}
return $v;
}
}
?>

View file

@ -0,0 +1,161 @@
<?php
/****************************************************************************
* STATIC RESOURCES MANAGEMENT
****************************************************************************/
global $resources;
/** This variable contains the list of resources to be loaded.
*/
$resources = array();
/** This function appends a resource of some type to the list of resources
* to be loaded.
*/
function addFileResource($type, $path) {
if (!file_exists($path)) {
//logText("Resource type '$type': '$path' not found", LOG_DEBUG);
return false;
}
global $resources;
if (!isset($resources[$type])) {
$resources[$type] = array();
}
$contents = @file_get_contents($path);
if ($contents === false) {
l::notice("Resource type '$type': unable to read '$path'");
return false;
}
$md5 = md5($contents);
array_push($resources[$type], array($path, $md5, $contents));
return true;
}
/** This function appends a chunk of raw text to a resource output.
*/
function addRawResource($type, $text) {
global $resources;
if (!isset($resources[$type])) {
$resources[$type] = array();
}
$md5 = md5($text);
array_push($resources[$type], array(null, $md5, $text));
return true;
}
/** This function generates the cached output for a set of resources.
*/
function generateResourceCache($type, $md5) {
umask(0007);
if (!is_dir(config::$main['cachedir']) && !@mkdir(config::$main['cachedir'], 0770)) {
l::warn("Could not create cache dir " . config::$main['cachedir']);
return false;
}
$path = config::$main['cachedir'] . "/$md5.$type";
$f = @fopen($path, "w");
if (!$f) {
l::warn("Could not create file " . config::$main['cachedir']);
return false;
}
global $resources;
$c = count($resources[$type]);
for ($i=0;$i<$c;$i++) {
fwrite($f, preg_replace('/__STATICURL__/', config::$main['staticurl'], $resources[$type][$i][2]) . "\n");
}
fclose($f);
return true;
}
/** This function stores a ressource into the database so that it may be used
* from other scripts.
*/
function storeResource($type, $deleteOld = 0, $write = true) {
global $resources;
if (!isset($resources[$type])) {
// l::notice("No resource of type $type");
return null;
}
// Delete old entries
if ($deleteOld > 0) {
if ($write) {
$q = dbQuery("SELECT md5 FROM web_cache WHERE unix_timestamp(now())-last_used>$deleteOld AND rtype='$type'");
while ($r = dbFetchArray($q)) {
@unlink(config::$main['cachedir'] . "/{$r[0]}.$type");
}
}
dbQuery("DELETE FROM web_cache WHERE unix_timestamp(now())-last_used>$deleteOld AND rtype='$type'");
}
// Check for an existing entry
$md5 = md5(serialize($resources[$type]));
$q = dbQuery("SELECT id FROM web_cache WHERE rtype='$type' AND md5='$md5' FOR SHARE");
if ($q && dbCount($q)) {
list($id) = dbFetchArray($q);
$path = config::$main['cachedir'] . "/$md5.$type";
if (!$write || file_exists($path)) {
return $id;
}
l::debug("Resource $id no longer exists on disk ($path), re-generating");
dbQuery("DELETE FROM web_cache WHERE id = $id");
}
// Generate the output file
if ($write && !generateResourceCache($type, $md5)) {
l::warn("Resource file generation failed (type $type)");
return null;
}
// Add the database entry
return dbQuery("INSERT INTO web_cache(rtype,md5,last_used) VALUES('$type','$md5'," . time() . ")");
}
/** This function reads a resource, identified by its DB identifier, from the
* database. If the resource is found in the base, it then tries to send the
* file's contents.
*/
function displayResource($id, $rtype) {
$q = dbQuery("SELECT rtype,md5 FROM web_cache WHERE id=$id FOR UPDATE");
if (!($q && dbCount($q) == 1)) {
l::warn("Resource ID '$id' not in the database");
return false;
}
list($dbtype,$md5) = dbFetchArray($q);
if ($rtype != $dbtype) {
l::warn("Resource ID '$id' has wrong type $dbtype (expected $rtype)");
return false;
}
$path = config::$main['cachedir'] . "/$md5.$rtype";
if (readfile($path) === false) {
dbQuery("DELETE FROM web_cache WHERE id=$id");
l::warn("File not found for resource '$id': $path");
endRequest(false);
return false;
}
dbQuery("UPDATE web_cache SET last_used=" . time() . " WHERE id=$id");
endRequest(false);
exit(0);
}
?>

View file

@ -0,0 +1,185 @@
<?php
/****************************************************************************
* SESSION MANAGEMENT FUNCTIONS
****************************************************************************/
class session {
static $cName = "";
static $id = null;
static $dbId = null;
static $new = null;
static $dataMD5 = null;
/** This function generates a new session identifier. */
private static function generateId() {
// Create a new tracking cookie if none exists
do {
$v = md5(uniqid(rand()));
$q = "SELECT id FROM web_session WHERE cookie = '$v'";
$qr = dbQuery($q);
} while ($qr && dbCount($qr));
// If $qr is null, something went wrong
if (!$qr) {
l::warning('No query result in session::generateId()');
return null;
}
return $v;
}
/** This function reads the session cookie from the user's browser or generates
* a value for the session cookie if none is found.
*/
private static function readId($create) {
// Get session identifier from cookie if it exists
if (isset($_COOKIE[self::$cName]) && ctype_alnum($_COOKIE[self::$cName]) && strlen($_COOKIE[self::$cName]) == 32) {
$v = pg_escape_string($_COOKIE[self::$cName]);
$q = "SELECT id FROM web_session WHERE cookie='$v' AND tracking=" . tracking::$dbId;
$qr = dbQuery($q);
if ($qr && dbCount($qr) == 1) {
return array($v, false);
} else {
l::notice("Session '$v' not found");
$create = true;
}
}
return $create ? array(self::generateId(), true) : array(null, false);
}
/** This function inserts session data into the database's web_session table. */
static private function createData() {
$q = "INSERT INTO web_session(cookie,ip_addr,tracking,stored_data) VALUES ('"
. self::$id . "','" . $_SERVER['REMOTE_ADDR'] . "'," . tracking::$dbId . ",'a:0:{}')";
return dbQuery($q);
}
/** This function updates a session's last access timestamp. */
static private function updateAccess() {
$q = "UPDATE web_session SET last_used=unix_timestamp(now()) WHERE cookie='" . self::$id . "'";
return dbQuery($q);
}
/** This function reads tracking data from the web_tracking table and stores it
* in the $_SESSION autoglobal variable.
*/
static private function readData() {
$q = "SELECT stored_data,id FROM web_session WHERE cookie = '" . self::$id . "'";
$qr = dbQuery($q);
if (!$qr || dbCount($qr) != 1) {
return false;
}
$tmp = dbFetchArray($qr);
$_SESSION = unserialize($tmp[0]);
self::$dataMD5 = md5($tmp[0]);
self::$dbId = $tmp[1];
return is_array($_SESSION);
}
/** This function updates the session record in the web_session table using
* the serialized contents of $_SESSION.
*/
static function store() {
if (is_null(self::$dbId)) {
return true;
}
$ser = serialize($_SESSION);
if ($ser != self::$dataMD5) {
$txt = pg_escape_string($ser);
$q = "UPDATE web_session SET stored_data='$txt' WHERE id=" . self::$dbId;
return dbQuery($q);
}
return true;
}
/** This function terminates the current session and removes the record from
* the web_session table. If an user was authenticated, accountLog() is
* called to account for the fact that the user has "logged out".
*/
static function kill() {
if (is_null(self::$dbId)) {
return;
}
if ($_SESSION['userid'] != "") {
dbQuery("UPDATE account SET last_logout=".time()." WHERE id={$_SESSION['userid']}");
accountLog('o', $_SESSION['userid']);
}
$_SESSION = array();
setcookie(self::$cName, false, 0, dirname($_SERVER['SCRIPT_NAME']));
dbQuery("DELETE FROM web_session WHERE id=".self::$dbId);
self::$dbId = self::$id = null;
}
/** This method sets the account identifier associated with a session. */
static function setAccount($account) {
if (is_null($account)) {
$account = "NULL";
}
dbQuery("UPDATE web_session SET account=$account WHERE id=" . self::$dbId);
}
/** This function manages the session, creating it if it is required or
* reading it from the base.
*/
static function handle($create) {
self::$cName = config::getParam('sessname');
// If tracking is disabled, return
if (tracking::$disabled) {
return 1;
}
// Get or generate session identifier
if (!tracking::$new) {
list($sessId, $sessNew) = self::readId($create);
} elseif ($create) {
list($sessId, $sessNew) = array(self::generateId(), true);
}
// If no session identifier has been returned, end.
if (is_null($sessId)) {
return 2;
}
self::$id = $sessId;
self::$new = $sessNew;
// Create or update session record
if ($sessNew && !self::createData()) {
l::fatal(13, "Session ID was $sessId");
} elseif (!$sessNew && !self::updateAccess()) {
l::fatal(14, "Session ID was $sessId");
}
// Read session data
if (!self::readData()) {
l::fatal(15, "Session ID was $sessId");
}
// If no user is currently authenticated and no
// authentication is required, terminate the session
if (!($create || $_SESSION['authok'])) {
self::kill();
return 0;
}
// Sets session cookie
setcookie(self::$cName, $sessId, 0, dirname($_SERVER['SCRIPT_NAME']));
return 0;
}
}
?>

94
game/scripts/lib/tick.inc Normal file
View file

@ -0,0 +1,94 @@
<?php
/****************************************************************************
* TICK MANAGEMENT CLASSES
****************************************************************************/
class tick_definition {
function __construct($version, $script, $public) {
$this->version = $version;
$this->script = $script;
$this->public = $public;
$this->text = array();
$this->version->addTickDefinition($this);
}
function addText($lang, $name, $description) {
$this->text[$lang] = array($name, $description);
}
function getName($lang) {
return isset($this->text[$lang]) ? $this->text[$lang][0] : "";
}
function getDescription($lang) {
return isset($this->text[$lang]) ? $this->text[$lang][1] : "";
}
function getPath() {
return "{$this->version->id}/ticks/{$this->script}";
}
}
class tick_instance {
function __construct($game, $script, $first, $interval, $last) {
$this->game = $game;
$this->definition = $game->version->getTickDefinition($script);
$this->first = $first;
$this->interval = $interval;
$this->last = $last;
$this->game->addTick($this);
$this->computeNext();
}
function computeNext() {
$now = time();
if (!is_null($this->last) && $now >= $this->last) {
$this->next = null;
return;
}
if ($now < $this->first) {
$this->next = $this->first;
return;
}
$diff = $now - $this->first;
$mod = $diff % $this->interval;
$mul = ($diff - $mod) / $this->interval;
$this->next = $this->first + ($mul + 1) * $this->interval;
}
function run() {
$libName = $this->definition->getPath();
$tick = $this->game->getLib($libName);
l::setPrefix("{$this->game->name}::{$this->definition->script}");
if (is_null($tick)) {
l::error("tick library '$libName' not found");
} else {
try {
$tick->call('runTick');
} catch (Exception $e) {
l::error("tick failed with exception " . get_class($e));
l::info("exception message: " . $e->getMessage());
}
}
l::setPrefix("");
}
function __sleep() {
return array('game', 'definition', 'first', 'interval', 'last');
}
function __wakeup() {
$this->computeNext();
}
}
?>

View file

@ -0,0 +1,258 @@
<?php
declare(ticks = 1); // PHP stupidity
class tick_manager {
var $threads = array();
var $byId = array();
static function fatalError($errno, $errorText, $information) {
exit(1);
}
function __construct($debug) {
$this->debug = $debug;
if (!$this->debug) {
// Forks to the background
$pid = pcntl_fork();
if ($pid == -1) {
die("The tick manager failed to launch: fork() failed.\n");
} elseif ($pid) {
exit(0);
}
// We're in the child, detach from parent as much as possible
$this->pid = posix_getpid();
posix_setsid();
// Tell the system control script that we are running
if (file_exists(config::$main['cs_fifo'])) {
$pipe = fopen(config::$main['cs_fifo'], "w");
fwrite($pipe, "TMPID {$this->pid}\n");
fclose($pipe);
}
}
l::debug("cache dir : " . config::$main['cachedir']);
if (file_exists(config::$main['cachedir'] . "/stop_ticks")) {
unlink(config::$main['cachedir'] . "/stop_ticks");
$this->ticksStopped = true;
touch(config::$main['cachedir'] . "/ticks_stopped");
} elseif (file_exists(config::$main['cachedir'] . "/ticks_stopped")) {
$this->ticksStopped = true;
} else {
$this->ticksStopped = false;
}
if ($this->ticksStopped) {
l::warn("Ticks manager starting but ticks are stopped");
} else {
l::notice("Ticks manager started");
}
l::setFatalHandler('tick_manager::fatalError');
$this->run();
}
function run() {
$this->setSignals(false);
$this->setAlarm();
while (1) {
// Sleeps for a while
sleep(10);
// Check for configuration update
$this->update();
// Check last tick
if (time() - $this->lastTick >= 6) {
l::error("No ticks for 6+ seconds! Trying to re-schedule!");
$this->setSignals(false);
$this->setAlarm();
$this->lastTick = time();
}
}
}
function setSignals($mask) {
$myHandler = array($this, "signalHandler");
$sigs = array(SIGTERM, SIGINT, SIGCHLD);
foreach ($sigs as $s) {
if ($mask) {
pcntl_signal($s, SIG_IGN);
} else {
pcntl_signal($s, $myHandler);
}
}
if ($mask) {
pcntl_signal(SIGALRM, SIG_IGN);
} else {
pcntl_signal(SIGALRM, array($this, "alarm"));
}
}
function signalHandler($signo) {
switch ($signo) :
case SIGTERM:
case SIGINT:
l::notice("Main thread terminating on SIG" . ($signo == SIGTERM ? "TERM" : "INT"));
exit(0);
case SIGCHLD:
do {
$wr = pcntl_waitpid(-1, $status, WNOHANG);
} while ($wr > 0);
break;
endswitch;
}
function update() {
// Mask signals
$this->setSignals(true);
// Reload configuration
if (config::reload()) {
l::notice("Configuration changed, re-scheduling");
$this->setAlarm();
}
// Stop/start ticks
if ($this->ticksStopped && file_exists(config::$main['cachedir'] . "/start_ticks")) {
l::notice("Ticks restarted");
$this->ticksStopped = false;
unlink(config::$main['cachedir'] . "/ticks_stopped");
} elseif (file_exists(config::$main['cachedir'] . "/stop_ticks")) {
l::notice("Ticks stopped");
$this->ticksStopped = true;
touch(config::$main['cachedir'] . "/ticks_stopped");
}
@unlink(config::$main['cachedir'] . "/stop_ticks");
@unlink(config::$main['cachedir'] . "/start_ticks");
// Tell the system control script that we are still running
if (!$this->debug && file_exists(config::$main['cs_fifo'])) {
$pipe = fopen(config::$main['cs_fifo'], "w");
fwrite($pipe, "TMPID {$this->pid}\n");
fclose($pipe);
}
// Unmask child signals
$this->setSignals(false);
}
function setAlarm() {
$min = null;
foreach (config::$config->games as $name => $game) {
foreach ($game->ticks as $tName => $tick) {
if (!is_null($tick->next) && (is_null($min) || $tick->next < $min)) {
$min = $tick->next;
}
}
}
if (is_null($min)) {
l::warn("No ticks left to execute");
return;
}
$delay = $min - time();
$rdelay = $delay <= 1 ? 2 : $delay;
pcntl_alarm($rdelay);
}
function alarm($sig) {
$this->setSignals(true);
$exec = array();
$now = time();
foreach (config::$config->games as $name => $game) {
foreach ($game->ticks as $tName => $tick) {
if (!is_null($tick->next) && $tick->next <= $now) {
array_push($exec, $tick);
$oNext = $tick->next;
$tick->computeNext();
if ($tick->next !== $oNext && !$this->ticksStopped) {
if (is_null($tick->next)) {
l::info("Not rescheduling {$name}::{$tName}");
}
}
}
}
}
if (!count($exec)) {
l::warning("No ticks scheduled for execution!");
}
$this->setAlarm();
foreach ($exec as $tick) {
$this->startThread($tick);
}
$this->setSignals(false);
}
function startThread($tick) {
// Don't even try if ticks are stopped
if ($this->ticksStopped) {
$this->lastTick = time();
return;
}
// Fork
$pid = pcntl_fork();
if ($pid == -1) {
l::error("Fork failed for {$tick->game->name}::{$tick->definition->script}!");
return false;
} elseif ($pid) {
$this->lastTick = time();
return $pid;
}
// We're in the child, detach from parent as much as possible
if (!$this->debug) {
posix_setsid();
}
// Connect to the DB and check if the game's running
if (!dbConnect(false)) {
exit(0);
}
$tick->game->getDBAccess();
if ($tick->game->version->id != 'main') {
$status = $tick->game->status();
if ($status == 'FINISHED'
|| ($status == 'PRE' || $status == 'READY') && $tick->definition->public) {
exit(0);
}
}
// Execute the tick
l::info("TICK: {$tick->game->name}::{$tick->definition->script}");
ob_start();
$tick->run($t);
$text = ob_get_contents();
ob_end_clean();
if ($text != '') {
$text = explode("\n", $text);
foreach ($text as $line) {
l::notice("{$tick->game->name}::{$tick->definition->script} OUTPUT: $line");
}
}
dbClose();
exit(0);
}
}
?>

View file

@ -0,0 +1,187 @@
<?php
/****************************************************************************
* TRACKING FUNCTIONS
****************************************************************************/
class tracking {
static $cName = "";
static $disabled = false;
static $id = null;
static $dbId = null;
static $data = array();
static $new = false;
static $dataMD5;
/** This function reads the tracking cookie from the user's browser or generates
* a value for the tracking cookie if none is found.
*/
private static function readId() {
// Get tracking identifier from cookie if it exists
if (isset($_COOKIE[tracking::$cName]) && ctype_alnum($_COOKIE[tracking::$cName]) && strlen($_COOKIE[tracking::$cName]) == 32) {
$v = $_COOKIE[tracking::$cName];
$q = "SELECT id FROM web_tracking WHERE cookie = '$v'";
$qr = dbQuery($q);
if (!$qr) {
return array(null, false);
}
if (dbCount($qr) == 1) {
return array($v, false);
}
}
// Create a new tracking cookie if none exists
do {
$v = md5(uniqid(rand()));
$q = "SELECT id FROM web_tracking WHERE cookie = '$v'";
$qr = dbQuery($q);
} while ($qr && dbCount($qr));
// If $qr is null, something went wrong
if (!$qr) {
l::debug("tracking: query failed for ID '$v'");
return array(null, false);
}
return array($v, true);
}
/** This function inserts tracking data into the database's web_tracking table. */
private static function createData() {
// Look for tracking records from the same IP/agent combination in the last
// 10 minutes that have been used for less than 10 seconds
$q = "SELECT COUNT(*) FROM web_tracking WHERE ip_addr = '" . $_SERVER['REMOTE_ADDR']
. "' AND browser = '".addslashes($_SERVER['HTTP_USER_AGENT'])."'"
. " AND UNIX_TIMESTAMP(now()) - created < 600"
. " AND last_used - created < 10";
$r = dbQuery($q);
list($c) = dbFetchArray($r);
// If more than 5 recent unused records are found, tracking is disabled.
if ($c > 5) {
tracking::$disabled = true;
return true;
}
$q = "INSERT INTO web_tracking(cookie,created,last_used,ip_addr,"
. "browser,stored_data) VALUES ('" . tracking::$id
. "',unix_timestamp(now()),unix_timestamp(now()),'".$_SERVER['REMOTE_ADDR']."','"
. addslashes($_SERVER['HTTP_USER_AGENT']) . "',"
. "'a:0:{}')";
return dbQuery($q);
}
/** This function updates a tracking entry's last access timestamp. */
private static function updateAccess() {
$q = "UPDATE web_tracking SET last_used=unix_timestamp(now()) WHERE cookie='" . tracking::$id . "'";
return dbQuery($q);
}
/** This function reads tracking data from the web_tracking table and stores it
* in the tracking::$data variable.
*/
private static function readData() {
$trackId = tracking::$id;
$q = "SELECT stored_data,id FROM web_tracking WHERE cookie = '$trackId' FOR UPDATE";
$qr = dbQuery($q);
if (!$qr || dbCount($qr) != 1) {
l::notice("Tracking data not found for cookie '$trackId'");
return false;
}
$tmp = dbFetchArray($qr);
$trackData = unserialize($tmp[0]);
$trackDBId = $tmp[1];
if (!is_array($trackData)) {
// Make sure we delete the tracking data that caused the problem
l::notice("Invalid tracking data for '$trackId'");
l::debug("DB id= $trackDBId, data type= '" . gettype($trackData) . "'");
l::info("Moving entry out of the way");
//dbQuery("DELETE FROM web_tracking WHERE id=$trackDBId");
dbQuery("UPDATE web_tracking SET cookie='DISABLED $trackDBId' WHERE id=$trackDBId");
dbEnd();
} else {
tracking::$dbId = $trackDBId;
tracking::$data = $trackData;
tracking::$dataMD5 = md5($tmp[0]);
}
return is_array($trackData);
}
/** This function initializes the tracking system */
static function init() {
tracking::$cName = config::getParam('trackname');
list($trackId,$trackNew) = tracking::readId();
if (handler::$h->noTracking ?? false && (is_null($trackId) || $trackNew)) {
tracking::$disabled = true;
return;
}
if (is_null($trackId)) {
l::fatal(2);
}
tracking::$id = $trackId;
tracking::$new = $trackNew;
if ($trackNew && !tracking::createData()) {
l::fatal(3);
} elseif (!$trackNew && !tracking::updateAccess()) {
l::fatal(4);
}
if (tracking::$disabled) {
return;
}
if (tracking::readData()) {
setcookie(
tracking::$cName,
$trackId,
[
'expires' => time() + 31536000,
'path' => dirname($_SERVER['SCRIPT_NAME']),
'samesite' => 'strict',
]
);
} else {
$trackDBId = tracking::$dbId;
l::fatal(5, "Tracking data: ID='$trackId',DB ID=$trackDBId" . ($trackNew ? ",new" : ""));
}
}
/** This function updates the web_tracking table using the serialized contents
* of tracking::$data
*/
static function store() {
if (is_null(tracking::$dbId)) {
if (tracking::$disabled) {
return 1;
}
l::warn("storeTrackingData: database identifier is null");
return 1;
}
$serialized = serialize(tracking::$data);
if (self::$dataMD5 != md5($serialized)) {
$txt = pg_escape_string(serialize(tracking::$data));
$q = "UPDATE web_tracking SET last_used=unix_timestamp(now()),stored_data='$txt' WHERE id='" . tracking::$dbId . "'";
} else {
$q = "UPDATE web_tracking SET last_used=unix_timestamp(now()) WHERE id='" . tracking::$dbId . "'";
}
return dbQuery($q);
}
}
?>

View file

@ -0,0 +1,48 @@
<?php
/****************************************************************************
* VERSION MANAGEMENT
****************************************************************************/
class version {
function __construct($id, $playable, $text) {
$this->id = $id;
$this->playable = $playable;
$this->text = $text;
$this->tickDefs = array();
}
function addTickDefinition($def) {
if (!isset($this->tickDefs[$def->script])) {
$this->tickDefs[$def->script] = $def;
}
}
function getTickDefinition($id) {
return $this->tickDefs[$id] ?? null;
}
function getDirectory() {
return config::$main['scriptdir'] . "/game/{$this->id}";
}
function getSiteDirectory() {
return config::$main['scriptdir'] . "/site/{$this->id}";
}
function loadActionsClass() {
$cn = "actions_{$this->id}";
loader::load($this->getDirectory() . "/actions.inc", $cn);
return $cn;
}
function loadAction($action) {
$cn = "{$this->id}_{$action}";
loader::load($this->getDirectory() . "/actions/$action.inc", $cn);
return $cn;
}
}
?>

View file

@ -0,0 +1,376 @@
<?php
/****************************************************************************
* XML CONFIGURATION PARSER
****************************************************************************/
class xml_config {
private static $mVersion = null;
private static $mGame = null;
private static $versions = null;
private static $games = null;
private static $defGame = null;
private static function parseMainParams($root) {
$node = $root->firstChild;
while ($node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Param') {
$aName = $node->getAttribute('name');
$aValue = $node->getAttribute('value');
if ($aName == "") {
l::warn("CONFIG: a main parameter is missing a 'name' attribute");
} elseif (!isset(xml_config::$mGame->params[$aName])) {
xml_config::$mGame->params[$aName] = $aValue;
} else {
l::notice("CONFIG: duplicate main parameter '$aName'");
}
} elseif ($node->nodeType == XML_ELEMENT_NODE) {
l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in MainParams section");
}
$node = $node->nextSibling;
}
}
private static function parseMainTicks($root) {
$node = $root->firstChild;
while ($node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Tick') {
$tScript = $node->getAttribute('script');
$tFirst = $node->getAttribute('first');
$tInterval = $node->getAttribute('interval');
$tLast = $node->getAttribute('last');
if ($tScript == "" || $tFirst == "" || $tInterval == "") {
l::warn("CONFIG: a main tick declaration is missing a mandatory attribute");
} elseif ((int)$tInterval == 0) {
l::warn("CONFIG: invalid interval for main tick '$tScript'");
} elseif (is_null(xml_config::$mVersion->getTickDefinition($tScript))) {
new tick_definition(xml_config::$mVersion, $tScript, false);
new tick_instance(xml_config::$mGame, $tScript, (int)$tFirst,
(int)$tInterval, $tLast ? (int)$tLast : null);
} else {
l::notice("CONFIG: duplicate main tick '$tScript'");
}
} elseif ($node->nodeType == XML_ELEMENT_NODE) {
l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in MainTicks section");
}
$node = $node->nextSibling;
}
}
private static function parseTickDefinition($version, $root) {
$script = $root->getAttribute('script');
$public = ($root->getAttribute('public') == 1);
if ($script == "") {
l::warn("CONFIG: invalid tick definition for version {$version->id}");
return;
}
if (!is_null($version->getTickDefinition($script))) {
l::notice("CONFIG: duplicate tick definition '$script' for version {$version->id}");
return;
}
$def = new tick_definition($version, $script, $public);
if (!$public) {
return;
}
$names = array();
$descs = array();
$node = $root->firstChild;
while ($node) {
if ($node->nodeType != XML_ELEMENT_NODE) {
$node = $node->nextSibling;
continue;
}
if ($node->nodeName != "Name" && $node->nodeName != 'Description') {
l::warn("CONFIG: unexpected tag '{$node->nodeName}' found in tick definition "
. "'{$script}' for version '{$version->id}'");
$node = $node->nextSibling;
continue;
}
$lang = $node->getAttribute('lang');
if ($lang == '') {
l::warn("CONFIG: missing language for {$node->nodeName} in tick definition "
. "'{$script}' for version '{$version->id}'");
$node = $node->nextSibling;
continue;
}
$contents = trim($node->textContent);
if ($contents == '') {
l::warn("CONFIG: missing contents for {$node->nodeName} in tick definition "
. "'{$script}' for version '{$version->id}'");
$node = $node->nextSibling;
continue;
}
if ($node->nodeName == 'Name') {
$names[$lang] = $contents;
} else {
$descs[$lang] = $contents;
}
$node = $node->nextSibling;
}
foreach ($names as $lang => $name) {
$def->addText($lang, $name, $descs[$lang]);
}
}
private static function parseVersion($root) {
$id = $root->getAttribute("id");
$cp = ($root->getAttribute("playable") == 1);
$tx = $root->getAttribute("text");
if ($id == "" || $tx == "") {
l::warn("CONFIG: invalid version definition (missing identifier or text)");
return null;
} elseif ($id == 'main') {
l::warn("CONFIG: invalid version definition (using 'main' identifier)");
return null;
}
$version = new version($id, $cp, $tx);
$node = $root->firstChild;
while ($node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Tick') {
xml_config::parseTickDefinition($version, $node);
} elseif ($node->nodeType == XML_ELEMENT_NODE) {
l::warn("CONFIG: found unexpected tag '{$node->nodeName}' in MainTicks section");
}
$node = $node->nextSibling;
}
return $version;
}
private static function parseVersions($root) {
$node = $root->firstChild;
$versions = array();
while ($node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Version') {
$v = xml_config::parseVersion($node);
if (!is_null($v)) {
if (isset($versions[$v->id])) {
l::notice("CONFIG: found duplicate definition for version '{$v->id}'");
} else {
$versions[$v->id] = $v;
}
}
} elseif ($node->nodeType == XML_ELEMENT_NODE) {
l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in Versions section");
}
$node = $node->nextSibling;
}
return $versions;
}
private static function parseGame($root) {
$id = $root->getAttribute('id');
if ($id == '') {
l::warn("CONFIG: game definition is missing an identifier");
return null;
}
if ($id == 'main') {
l::warn("CONFIG: game definition with 'main' identifier ignored");
return null;
}
$version = $root->getAttribute('version');
if (is_null(xml_config::$versions[$version])) {
l::warn("CONFIG: game '$id' has unknown version '$version'");
return null;
}
$namespace = $root->getAttribute('namespace');
if ($namespace == '') {
l::warn("CONFIG: no namespace defined for game '$id'");
return null;
}
$text = $root->getAttribute('text');
if ($namespace == '') {
l::warn("CONFIG: no text for game '$id'");
return null;
}
$public = ($root->getAttribute('public') == 1);
$canJoin = ($root->getAttribute('canjoin') == 1);
$game = new game(xml_config::$versions[$version], $id, $namespace, $public, $canJoin, $text);
$node = $root->firstChild;
while ($node) {
if ($node->nodeType != XML_ELEMENT_NODE) {
$node = $node->nextSibling;
continue;
}
if ($node->nodeName == 'Param') {
$aName = $node->getAttribute('name');
$aValue = $node->getAttribute('value');
if ($aName == "") {
l::warn("CONFIG: a parameter is missing a 'name' attribute for game '$id'");
} elseif (isset($game->params[$aName])) {
l::notice("CONFIG: duplicate parameter '$aName'");
} else {
$game->params[$aName] = $aValue;
}
} elseif ($node->nodeName == 'Description') {
$lang = $node->getAttribute('lang');
$contents = trim($node->textContent);
if ($lang == "") {
l::warn("CONFIG: a description is missing the 'lang' attribute for game '$id'");
} elseif ($contents == "") {
l::warn("CONFIG: description in language '$lang' has no contents for game '$id'");
} elseif (isset($game->descriptions[$lang])) {
l::notice("CONFIG: description in language '$lang' appears twice for game '$id'");
} else {
$game->descriptions[$lang] = $contents;
}
} elseif ($node->nodeName == 'Tick') {
$tScript = $node->getAttribute('script');
$tFirst = $node->getAttribute('first');
$tInterval = $node->getAttribute('interval');
$tLast = $node->getAttribute('last');
if ($tScript == "" || $tFirst == "" || $tInterval == "") {
l::warn("CONFIG: a tick declaration is missing a mandatory attribute "
. "for game '$id'");
} elseif ((int)$tInterval == 0) {
l::warn("CONFIG: invalid interval for tick '$tScript' in game '$id'");
} elseif (is_null($game->version->getTickDefinition($tScript))) {
l::warn("CONFIG: no definition for tick '$tScript' in game '$id' "
. "(version '$version')");
} elseif (isset($game->ticks[$tScript])) {
l::notice("CONFIG: duplicate tick initialiser for tick '$tScript' in game '$id'");
} else {
new tick_instance($game, $tScript, (int)$tFirst, (int)$tInterval,
$tLast ? (int)$tLast : null);
}
} else {
l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in game '$id' definition");
}
$node = $node->nextSibling;
}
return $game;
}
private static function parseGames($root) {
$defaultId = $root->getAttribute('default');
$games = array();
$node = $root->firstChild;
while ($node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'Game') {
$g = xml_config::parseGame($node);
if (!is_null($g)) {
if (isset($games[$g->name])) {
l::notice("CONFIG: found duplicate definition for game '{$v->name}'");
} else {
$games[$g->name] = $g;
}
}
} elseif ($node->nodeType == XML_ELEMENT_NODE) {
l::notice("CONFIG: found unexpected tag '{$node->nodeName}' in Games section");
}
$node = $node->nextSibling;
}
if (count($games) && ($defaultId == '' || is_null($games[$defaultId]))) {
$defaultId = $games[0]->name;
l::notice("CONFIG: no default game, using '$defaultId'");
}
xml_config::$defGame = $defaultId;
return $games;
}
public static function parse($xmlData) {
$doc = new DOMDocument();
if (!$doc->loadXML($xmlData)) {
l::error("CONFIG: error while parsing XML configuration");
return false;
}
xml_config::$mVersion = new version('main', false, 'Legacy Worlds');
xml_config::$mGame = new game(xml_config::$mVersion, 'main', '', false, false, 'Legacy Worlds');
xml_config::$versions = null;
xml_config::$games = null;
xml_config::$defGame = null;
$root = $doc->documentElement;
$node = $root->firstChild;
while ($node) {
if ($node->nodeType == XML_ELEMENT_NODE) {
switch ($node->nodeName) :
case 'MainParams':
xml_config::parseMainParams($node);
break;
case 'MainTicks':
xml_config::parseMainTicks($node);
break;
case 'Versions':
if (is_array(xml_config::$versions)) {
l::notice("CONFIG: found duplicate set of version definitions");
} else {
xml_config::$versions = xml_config::parseVersions($node);
}
break;
case 'Games':
if (is_array(xml_config::$games)) {
l::notice("CONFIG: found duplicate set of game definitions");
} elseif (is_array(xml_config::$versions)) {
xml_config::$games = xml_config::parseGames($node);
} else {
l::notice("CONFIG: game definitions found before version "
. "definitions, ignored");
}
break;
default:
l::notice("CONFIG: found unknown tag '{$node->nodeName}' at toplevel");
break;
endswitch;
}
$node = $node->nextSibling;
}
if (!is_array(xml_config::$versions) || count(xml_config::$versions) == 0) {
l::error("CONFIG: no versions have been defined!");
return false;
}
if (!is_array(xml_config::$games) || count(xml_config::$games) == 0) {
l::error("CONFIG: no games have been defined!");
return false;
}
xml_config::$versions['main'] = xml_config::$mVersion;
xml_config::$games['main'] = xml_config::$mGame;
return new config(xml_config::$versions, xml_config::$games, xml_config::$defGame);
}
}
?>