Fixed: #1378 Imported nimetu's WebIG reference implementation.

This commit is contained in:
sfb 2012-03-22 15:48:13 -05:00
parent ae10726b98
commit 548740bafc
7 changed files with 796 additions and 0 deletions

View file

@ -0,0 +1,29 @@
<?php
require_once 'lib/functions.php';
$user = app_authenticate();
if(empty($user)){
// redirect to login page
$url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
header('Location: index.php?redirect='.urlencode($url));
exit;
}
// get more info about character - race, civlization, cult, guild, etc
$character = webig_load_character($user['cid']);
?>
<html>
<head>
<title>App Hello World!</title>
</head>
<body>
<h1>APP Hello World!</h1>
<a href="index.php">index</a><?=(!isWEBIG ? '| <a href="?logout">logout</a>' : '')?>
<h2>Character</h2>
<pre><?php print_r($character);?></pre>
</body>
</html>

View file

@ -0,0 +1,133 @@
<?php
$__start = microtime(true);
require_once 'lib/functions.php';
session_start();
// do user login
$user = app_authenticate();
if(empty($user)){
// user not verified
//
$error = is($_SESSION['login']['error'], '');
if(!empty($error)){
$error = '<p style="color: red;">login error</p>';
}
echo '
<html>
<head>
<title>WebIG - Login</title>
</head>
<body
<h1>Login</h1>
<form method="post" action="">
<input type="hidden" name="login[shardid]" value="302" />
Char name <input type="text" name="login[name]" value="" /><br />
Password <input type="password" name="login[passwd]" value="" /><br />
<input type="submit" name="login[submit]" value="Login" />
</form>
</body>
</html>';
exit;
}
// if this was login request from app, then redirect back there
$redirect = is($_GET['redirect'], '');
if(!empty($redirect)){
header('Location: '.$redirect);
exit;
}
// check user privileges
$is_admin = webig_is_admin($user['cid']>>4);
// get more info about character - race, civlization, cult, guild, etc
$character = webig_load_character($user['cid']);
// user is verified
?>
<html>
<head>
<title>App Index</title>
</head>
<body>
<h1>Hello "<?=h($user['name'])?>"!</h1>
<a href="index.php">index</a> | <a href="app_hello.php">Hello APP</a><?=(!isWEBIG ? '| <a href="?logout">logout</a>' : '')?>
<?php
if($is_admin){
if(isWEBIG){
display_teleport_list();
}
}
echo '<h2>User info</h2>';
echo 'USER:'.dump_array($user);
echo 'CHARACTER:'.dump_array($character);
$__end = microtime(true);
echo "<pre>\n---\npage created ".sprintf("%.5fsec", $__end - $__start).'</pre>';
?>
</body>
</html>
<?php
function dump_array($array){
ob_start();
echo '
<table cellspacing="2" cellpadding="0" bgcolor="#4f4f4f">
<tr><td>
<table cellspacing="0" cellpadding="0">
';
$c=0;
foreach($array as $k => $v){
if(is_array($v)){
$v = dump_array($v);
}else{
// make value safe for html
$v = h($v);
}
echo '
<tr valign="middle" bgcolor="'.($c%2 ? '#9f9f9f' : '#909090').'">
<td height="20">'.h($k).'</td><td>'.$v.'</td>
</tr>
';
$c++;
}
echo '
</table>
</td></tr>
</table>
';
return ob_get_clean();
}
function display_teleport_list(){
$places = array(
'Ranger Camp' => array(10314,-11734),
'Shining Lake' => array(9056, -10822),
);
?>
<h2>Teleport destinations</h2>
<table cellspacing="2" cellpadding="0" bgcolor="#4f4f4f">
<tr><td>
<table cellspacing="0" cellpadding="0">
<?php $c=0; foreach($places as $txt => $xyz){
echo '
<tr valign="middle" bgcolor="'.($c%2 ? '#3f3f3f' : '#303030').'">
<td height="20">'.h($txt).'</td><td><a href="ah:command&a&Position&'.join(',', $xyz).'">'.join(',', $xyz).'</a></td>
</tr>
';
$c++;
} ?>
</table>
</td></tr>
</table>
<?php
}

View file

@ -0,0 +1,23 @@
<?php
// ----------------------------------------------------------------------------------------
// Variables for PdrUtil class
// ----------------------------------------------------------------------------------------
define('CMD_PDR_UTIL', 'C:/RyzomCore/ryzom_core_server/ryzom/tools/pdr_util');
define('SHARD_SAVE', 'C:/RyzomCore/ryzom_core_server/ryzom/server/save_shard');
define('SHEETID_DIR', 'C:/RyzomCore/ryzom_core_server/ryzom/common/data_leveldesign/leveldesign/Game_elem');
define('TEMP_UNPACK', 'c:/temp');
// ----------------------------------------------------------------------------------------
// Variables for nel database access
// ----------------------------------------------------------------------------------------
// where we can find the mysql database
$DBHost = 'localhost';
$DBUserName = 'shard';
$DBPassword = '';
$DBName = 'nel';
//
$RingDBUserName = 'shard';
$RingDBName = 'ring_open';
$RingDBPassword = '';

View file

@ -0,0 +1,394 @@
<?php
if(strstr($_SERVER['HTTP_USER_AGENT'], 'Ryzom')){
define('isWEBIG', true);
}else{
define('isWEBIG', false);
}
require_once 'config.php';
require_once 'pdr_util.php';
require_once 'pdr_util_character.php';
require_once 'pdr_util_guild.php';
/**
* Singleton wrapper class for PDO database connection
*/
class DB {
private $_pdo = null;
private function __construct(){
$this->_pdo = new PDO('mysql:host='.$GLOBALS['DBHost'].';dbname='.$GLOBALS['DBName'].';charset=utf-8', $GLOBALS['DBUserName'], $GLOBALS['DBPassword'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8'));
$this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function getInstance(){
static $instance = null;
if($instance === null){
$instance = new DB();
}
return $instance;
}
/**
* @param string $sql
* @param array $params (optional)
* @return PDOStatement
*/
function query($sql, $params=array()){
if(empty($params)){
$stmt = $this->_pdo->query($sql);
}else{
$stmt = $this->_pdo->prepare($sql);
$stmt->execute($params);
}
return $stmt;
}
}
/**
* Verify and log-in user
*
* @return mixed user info array or boolean FALSE when user was not verified
*/
function app_authenticate(){
// mask possible double session_start() warning
@session_start();
if(isWEBIG){
// ingame login
// gather user from $_GET or $_POST variables
$user = webig_user();
// verify it against database
$user = webig_auth($user);
}else{
// outgame login
if(isset($_POST['login'])){
// login request
$shardid = is($_POST['login']['shardid'], '');
$name = is($_POST['login']['name'], '');
$passwd = is($_POST['login']['passwd'], '');
// verify character and password against database and populate $_GET with user info
$user = login_auth($shardid, $name, $passwd);
$_SESSION['login']['error'] = ($user === false);
}elseif(isset($_GET['logout'])){
// logout request
unset($_SESSION['user']);
unset($_SESSION['authkey']);
// redirect to self without URL parameters
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}else{
// continue session
$user = is($_SESSION['user'], false);
// verify user in session against database (e.g. user might be deleted)
$user = load_user($user['shardid'], null, $user['cid']);
}
}
// auth failed?
if(empty($user)){
return false;
}
// remove values we do not need to keep in session
unset($user['password']);
unset($user['cookie']);
// return user info array on success
$_SESSION['user'] = $user;
return $user;
}
// get user info that WebIG sends us
function webig_user(){
$user = array();
// shard id (302)
$user['shardid'] = ryzom_get_param('shardid');
// character name (User)
$user['name'] = ryzom_get_param('name');
// character id (16), user id is calculated as 'uid = cid >> 4';
$user['cid'] = ryzom_get_param('cid');
// language
$user['lang'] = ryzom_get_param('lang');
$user['authkey'] = ryzom_get_param('authkey');
return $user;
}
/**
* Verify character using info from ig browser
*
* @param array $user
* @return bool return user info array on success and FALSE on error
*/
function webig_auth($user){
// find user by shard and character id (name might be temporarily changed in game)
$result = load_user($user['shardid'], null, $user['cid']);
if(empty($result)){
// should not happen, but user was not found
return false;
}
// Create auth key by using cookie from DB and user info from user
$authkey = webig_create_authkey($user, $result['cookie']);
if($user['authkey'] !== $authkey){
// something is out of sync - either user info or cookie
return false;
}
// return result from DB
return $result;
}
/**
* Verify character
*
* @param int $shardid character shard id
* @param string $name character name
* @param string $passwd plain text password
* @return mixed return user info array on success or boolean false on error
*/
function login_auth($shardid, $name, $passwd){
// get character from db
$user = load_user($shardid, $name);
if(empty($user)){
// user was not found
return false;
}
$passwd = crypt($passwd, substr($user['password'], 0, 2));
if($passwd !== $user['password']){
// password failed
return false;
}
return $user;
}
/**
* Fetch user info from db
*
* If name is NULL, then $cid is used
*
* @param int $shardid
* @param string $name
* @param int $cid
* @return array
*/
function load_user($shardid, $name, $cid = null){
// `nel`.`user` has password
// `ring_open`.`ring_users` has cookie
// `ring_open`.`characters` has char_id, char_name, home_mainland_session_id(==shardid)
$sql = 'SELECT c.`char_id` cid, c.`char_name` name, c.`home_mainland_session_id` shardid, n.`password`, u.`cookie`
FROM `ring_open`.`characters` c
JOIN `ring_open`.`ring_users` u on u.`user_id` = c.`user_id`
JOIN `nel`.`user` n on n.`uid` = c.`user_id`
WHERE c.`home_mainland_session_id` = :shardid';
$params = array('shardid' => $shardid);
if($name !== null){
$sql .= ' AND c.`char_name` = :name';
$params['name'] = $name;
}elseif($cid !== null){
$sql .= ' AND c.`char_id` = :cid';
$params['cid'] = $cid;
}else{
// $name or $cid both empty
return false;
}
$result = DB::getInstance()->query($sql, $params)->fetch(PDO::FETCH_ASSOC);
return $result;
}
/**
* Verify user info that ig browser sent us using cookie from database
*
* @param array $user user info array
* @param string $cookie User login cookie from database
* @return string md5 hash
*/
function webig_create_authkey($user, $cookie){
return md5($user['shardid'].$user['name'].$user['cid'].'\''.$cookie.'\'');
}
/**
* Return user privileges from DB
*
* @param int $uid user id (uid = cid >> 4)
* @return mixed array of user privileges or boolean FALSE when user was not found
*/
function webig_get_privileges($uid){
$sql = 'select `privilege` from `nel`.`user` where `uid` = :id';
$params = array('id' => $uid);
$result = DB::getInstance()->query($sql, $params)->fetchColumn(0);
if($result !== false){
$result = explode(':', $result);
$ret = array();
foreach($result as $k=>$v){
if($v != ''){
$ret[]=$v;
}
}
$result = $ret;
}
return $result;
}
/**
* Test user privileges
*
* @param int $uid user id
* @param array $priv array of privileges, like array('DEV', 'GM')
* @return bool
*/
function webig_has_privileges($uid, $priv){
$userpriv = webig_get_privileges($uid);
$result = array_intersect($priv, $userpriv);
return !empty($result);
}
/**
* Test user privileges against (DEV, SGM, GM)
*
* @param int $uid user id
* @return bool
*/
function webig_is_admin($uid){
// entities_game_service/player_manager/player_manager.cpp defines order
// DEV > SGM > EM > GM > EG > VG > SG > G > OBSERVER > PR
return webig_has_privileges($uid, array('DEV', 'SGM', 'EM', 'GM'));
}
/**
* Load character from shard save binary file
*
* @param int $cid
* @return mixed array with character info or boolean FALSE on error
*/
function webig_load_character($cid){
$pdr = CharacterPdr::createDefault();
$char = $pdr->load($cid);
if(empty($char)){
return false;
}
$result = array(
'id' => (int) $cid,
'name' => (string) $char->EntityBase->_Name['value'],
'title' => (string) $char->_Title['value'],
'race' => (string) $char->EntityBase->_Race['value'],
'gender' => (int) $char->EntityBase->_Gender['value'] == '0' ? 'male' : 'female',
'cult' => (string) $char->DeclaredCult['value'],
'civ' => (string) $char->DeclaredCiv['value'],
'guild' => false,
);
$guild_id = (int) $char->_GuildId['value'];
if($guild_id>0){
// if char is in guild, then also get guild info
$result['guild'] = webig_load_guild($guild_id);
// get guild rank (also from guild file)
$result['guild_membership'] = webig_load_guild_membership($guild_id, $cid);
}
unset($char);
return $result;
}
/**
* Load basic guild info (name, description, motd, culv, civ)
*
* @param int $guild_id
* @return mixed array with guild info or boolean FALSE on error
*/
function webig_load_guild($guild_id){
$pdr = GuildPdr::createDefault();
$guild = $pdr->load($guild_id);
if(empty($guild)){
return false;
}
$result = array(
'id' => (int) $guild_id,
'icon' => (string) $guild->Icon['value'],
'name' => (string) $guild->_Name['value'],
'description' => (string) $guild->_Description['value'],
'motd' => (string) $guild->_MessageOfTheDay['value'],
'cult' => (string) $guild->DeclaredCult['value'],
'civ' => (string) $guild->DeclaredCiv['value'],
);
unset($guild);
return $result;
}
/**
* Load guild member info
*
* @param int $guild_id
* @param int $char_id
* @return mixed array with guild member info or boolean FALSE if guild or character not found
*/
function webig_load_guild_membership($guild_id, $char_id){
$pdr = GuildPdr::createDefault();
$guild = $pdr->load($guild_id);
if(empty($guild)){
return false;
}
$result = false;
// test for 'id' and type (CHAR == 0), ignore creator (should be 0x00) and dynamic
// 0x0000000013:00:00:87
$eid = sprintf('0x%010x:00:', $char_id);
$i = 0;
while(isset($guild->Members->__Key__[$i])){
$key = $guild->Members->__Key__[$i];
$pos = strpos((string)$key['value'], $eid);
if($pos === 1){
$val = $guild->Members->__Val__[$i];
$result = array(
'grade' => (string) $val->Members->Grade['value'],
'joined' => (int) $val->Members->EnterTime['value'],
);
break;
}
$i++;
}
unset($guild);
return $result;
}
// shortcut for 'isset() ? .. : ..'
function is(&$var, $default = null){
return isset($var) ? $var : $default;
}
// escape string so it's safe for HTML
function h($str){
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
// return $_GET[var] or $_POST[var] or $default
function ryzom_get_param($var, $default=''){
return is($_GET[$var], is($_POST[$var], $default));
}

View file

@ -0,0 +1,124 @@
<?php
class PdrUtil {
/**
* Shard seems to be saving guild file every few seconds.
* Increase cache valid timer a bit.
*
* @const int cache expire in seconds
*/
const CACHE_MIN_TIME = 15;
/**
* Where does shard keep it's character, guild files
*
* @var string
*/
private $_shard_save;
/**
* Directory where sheet_id.bin is
*
* @var string
*/
private $_sheetid_dir;
/**
* Directory for extracted character, guild xml files
*
* @var string
*/
private $_unpack_dir;
/**
* Public Constructor for PdrUtil
*
* @param string $save_path
* @param string $unpack temp unpack directory, default is /tmp/
*/
function __construct($shard_save, $sheetid_dir, $unpack = '/tmp/'){
$this->_shard_save = $shard_save;
$this->_sheetid_dir = $sheetid_dir;
$this->_unpack_dir = $unpack;
}
function setShardSaveDirectory($dir){
$this->_shard_save = $dir;
}
function getShardSaveDirectory(){
return $this->_shard_save;
}
function setSheetIdDirectory($dir){
$this->_sheetid_dir = $dir;
}
function getSheetIdDirectory(){
return $this->_sheetid_dir;
}
function setUnpackDirectory($dir){
$this->_unpack_dir = $dir;
}
function getUnpackDirectory(){
return $this->_unpack_dir;
}
/**
* Extract $pdr file to $xml file
*
* @param string $pdr
* @param string $xml
* @return bool
*/
function extract($pdr, $xml){
if(!file_exists($pdr)){
return false;
}
$pdr_mtime = filemtime($pdr);
if(file_exists($xml)){
$xml_mtime = filemtime($xml);
}else{
$xml_mtime = 0;
}
$diff = $pdr_mtime - $xml_mtime;
if($diff > self::CACHE_MIN_TIME){
// remove expired xml file
@unlink($xml);
// change working directory to unpack directory to keep pdr_util log file in one place
$pwd = getcwd();
chdir($this->_unpack_dir);
// run pdr_util
$cmd = sprintf(' -s%s -x -o%s %s', $this->_sheetid_dir, $xml, $pdr);
exec(CMD_PDR_UTIL.' '.$cmd);
// change working directory back what it was before
chdir($pwd);
}
// if pdr_util failed, then there is no xml file
return file_exists($xml);
}
/**
* @param string $fname
* @return string ShardSaveDirectory + fname
*/
function getSaveFileName($fname){
return $this->getShardSaveDirectory().'/'.$fname;
}
/**
* @param string $fname
* return string TempDirectory + $fname
*/
function getXmlFileName($fname){
return $this->getUnpackDirectory().'/'.$fname;
}
}

View file

@ -0,0 +1,46 @@
<?php
class CharacterPdr extends PdrUtil {
/**
* Factory method to create new instance using default path values from config.php
*/
static public function createDefault(){
return new self(SHARD_SAVE, SHEETID_DIR, TEMP_UNPACK);
}
/**
* Load character from shard binary file
*
* @param int $char_id
* @return SimpleXML object or boolean FALSE on error
*/
function load($char_id){
$char_save = $this->getSaveFileName($char_id >> 4, $char_id & 0x0F);
$xml_file = $this->getXmlFileName($char_id);
if($this->extract($char_save, $xml_file)){
return simplexml_load_file($xml_file);
}
// extract failed
return false;
}
/**
* @param int $uid user id
* @param int $slot character slot, starting from 0
* @return string character save path + filename
*/
function getSaveFileName($uid, $slot){
return parent::getSaveFileName(sprintf('characters/%03d/account_%d_%d_pdr.bin', $uid, $uid, $slot));
}
/**
* @param $char_id
* return string character xml file in unpack directory
*/
function getXmlFileName($char_id){
return parent::getXmlFileName(sprintf('character_%d.xml', $char_id));
}
}

View file

@ -0,0 +1,47 @@
<?php
class GuildPdr extends PdrUtil {
/**
* Factory method to create new instance using default path values from config.php
*/
static public function createDefault(){
return new self(SHARD_SAVE, SHEETID_DIR, TEMP_UNPACK);
}
/**
* Load guild info from shard binary file
*
* @param int $guild_id
* @return SimpleXML object or boolean FALSE on error
*/
function load($guild_id){
$guild_save = $this->getSaveFileName($guild_id);
$xml_file = $this->getXmlFileName($guild_id);
if($this->extract($guild_save, $xml_file)){
return simplexml_load_file($xml_file);
}
// extract failed
return false;
}
/**
* @param int $guild_id
* @return string full path to guild binary file
*/
function getSaveFileName($guild_id){
// chop off shard component from guild id
return parent::getSaveFileName(sprintf('guilds/guild_%05d.bin', $guild_id & 0xFFFFF));
}
/**
* @param $guild_id
* return string full path to extracted guild xml file
*/
function getXmlFileName($guild_id){
return parent::getXmlFileName(sprintf('guild_%d.xml', $guild_id));
}
}