作者:赖勇浩(http://blog.csdn.net/lanphaday)
《OGame》中文名《银河帝国》,是最早的 webgame 实现之一,也是国内众多三国题材的 webgame 的抄袭之原型。它是一个宇宙星际为题材的 webgame,据说现在已经有数千万玩家在玩这个游戏。
我使用 Source Navigator 来浏览这些代码,这是一个很好的工具,在这里推荐一下。这个笔记用 MS Word 进行排版,在文章结构上是以功能为章,以源码文件或关键函数为节进行组织的,行文的顺序为程序执行的顺序。以上,请读者记住,不然读起来难免会感觉怪异。
注:需要读懂这些笔记,你大概需要拥有:1)XNova/OGame的源码,我读的是网上流传甚广的 0.8TC3 版本(真的有这个版本吗,我在官网看到最大的版本号是 0.77);2)懂得 PHP 4 的基本知识,比如语法和常用的库函数之类;3)懂得 PHP 程序是怎么样运行的,或者说对 CGI 原理有所了解,最好知道如何用 PHP 或其它编程语言编写简单的 CGI 程序。
这个文章是游戏整站的入口。只有几行代码,功能一目了然:判断 ogame/config.php 的文件大小是否为 0,如果是,就重定向到 ogame/install/ 目录执行安装操作,否则就重定向到 ogame/login.php 显示登陆页面。我们假定游戏已经安装好,接下来看看 ogame/login.php。
顾名思义,login.php 当然就是显示登陆页面。一读代码,可以看到它先定义了两个常量:
define('INSIDE' , true);
define('INSTALL' , false);
因为这两个常量在接下来的代码中经常用到,所以在这里先提一下。其中 INSIDE 是用来防止攻击的,而 INSTALL 呢是标识是否处于安装程序的进程中。
接下来就可以发现这个文件 include 了两个文件:分别是 ogame/extension.inc 和 ogame/common.php 。既然 login.php 依赖于这两个文件,我们不妨先来看看这两个文件。而 login.php 的实现我们可以回头再看。
其实这个文件虽然是 .inc 后缀,其实却是一个名副其实的 .php 文件。它的作用是防止非法程序包含此文件,从而提高整站的安全性。
这个文件有点大,值得好好分析一下。里面定义了几个全局变量:
$game_config = array();
$user = array();
$lang = array();
$IsUserChecked = false;
这几个变量以后用得到。后面还包含了几个 includes/ 目录下的脚本和 language/ 目录下的相应的本地语言的 lang_info.cfg 配置文件。
common.php 是安装和游戏两个程序所通用的,虽然关于安装的那部分很简单,但我们的焦点不在这方面,所以我们只关注 INSTALL != true 成立时执行的代码段。
代码段开始处,又 include 了 includes/vars.php、includes/db.php、includes/strings.php 等三个文件。其中 vars.php 定义了游戏的一些依赖关系和数值;db.php 引用了 ogame/db/mysql.php,定义了一些常用的数据库操作函数,比如马上做会用到的 doquery();strings.php 则是定义了几个简单的数字输出美化的函数。其中 vars.php 和 ogame/db/mysql.php 将会另文详述,现在你只需要知道这么多就可以了。
好,接着再跟踪下去可以发现程序从数据库的 config 表中读取游戏的配置,这些配置会填充在 $game_config 变量中,这是一个 array 对象(非 PHP 出身的程序员注意啦,PHP 的 array 除了能够利用 0…n 的下标访问外,更可以自定义键值,换句话说,它的 array 其实是一个“映射表”,类似于 Python 里的 dict 或 C++ STL 中的 Map 和 HashMap)。
因为 login.php 定义了 $InLogin 的值为 true,所以接下来跟 $InLogin 有关的逻辑都不会执行,所以现在我们先放过它们,等执行到他们的时候再来分析。接着往下看,是:
includeLang ("system");
includeLang ('tech');
这里出现了让人感到颇为突兀的函数 includeLang(),从字面上来看,应该是分语言(语种)进行 include 操作的函数,大概是用来实现 i18n 的工具。具体如何,大家跟我先按下 common.php 不表,去看看 includeLang(),弄清它的来龙去脉,再回来研究 common.php。
includeLang() 定义在 includes/unlocalised.php 文件中,这个文件定义了许多有用的小函数,但现在我们独独来关注 includeLang() 的实现:
function includeLang ($filename, $ext = '.mo') {
global $ugamela_root_path, $lang, $user;
if ($user['lang'] != '') {
$SelLanguage = $user['lang'];
} else {
$SelLanguage = DEFAULT_LANG;
}
include ($ugamela_root_path . "language/". $SelLanguage ."/". $filename.$ext);
}
哦!原来是个根据用户的所用的语种 include 相应的代码的“代理”!这下心里就完全悉然了。
了解了 includeLang() 的意义,现在我们对 includeLang(“system”) 就非常清楚(在默认语言的情况下)它其实就是 include(“./language/de/system.mo”),includeLang(“tech”) 就是 include(“./language/de/tech.mo”)。其实这两个 .mo 文件也是货真价实的 php 脚本文件,其中 system.mo 定义了系统相关的术语,而 tech.mo 定义的是游戏中科技系统相关的术语。而 include() 它们的意义在于填充 $lang 变量。
因为这时我们的账号还从来没有登陆过游戏,所以接下来执行 isset($user) 返回假值,对 common.php 的执行就结束了,现在让我们回到 login.php 中去吧~
include(./common.php) 之后是 includeLang(‘login’),根据前面的知识,我们知道这里 include 本地化的 login.mo 文件,它也是用来填充 $lang 变量的,里面定义了与登陆页面相关的术语,比如“密码错误,请重新收入”之类。
因为我们是从 index.php 重定义过来的,所以 $_POST 的真值测试为假,我们执行的是 login.php 代码中的最后一个 else 子句对应的代码段。其中查询了两次数据库,分别是为了获取最新注册的用户和当前在线人数。接下来最重要的两句代码就是:
$page = parsetemplate(gettemplate('login_body'), $parse);
display($page, $lang['Login']);
从代码中很容易读懂其中第一行的意义是根据已经写好的 login_body 模板生成登陆页,而第二行则是将页面内容返回给客户端(在这里的意义就是浏览器)。这两行代码中的 gettemplate()、parsetemplate() 和 display() 三个函数值得进一步深入,且看下文。
这个函数在 ogame/includes/unlocalised.php 中定义,接受一个 $templatename 参数。其中最重要的一句代码是:
$filename = $ugamela_root_path . TEMPLATE_DIR . TEMPLATE_NAME . '/' . $templatename . ".tpl";
根据 ogame/common.php 中的定义,TEMPLATE_DIR 的值是 ‘templates/’,TEMPLATE_NAME 的值是 ‘OpenGame’,那么可以知道 gettempalte(‘login_body’) 即是读取 ogame/templates/OpenGame/login_body.tpl 文件的内容。
这个函数也是定义在 ogame/includes/unlocalised.php 中,接受 $template 和 $array 两个参数,代码只有一行:利用正则表达式对 $template 中的特定字符串(通常来而言就是由 {} 括起来的内容)用 $array 的值进行替换,从而实现多语言的本地化。
这个函数定义在 ogame/includes/functions.php 中,原型是:function display ($page, $title = '', $topnav = true, $metatags = '', $AdminPage = false)。实现上包括了普通用户页眉、管理员页眉的区分,是否显示顶部的导航条、分级授权和调试都提供了支持,所以这个函数虽然短小,却值得仔细体味。其中调用的 StdUserHeader()、AdminUserHeader()、ShowTopNavigationBar()和 StdFooter() 并不复杂,其原理都是 parsetempalte(gettemplate(), …),大家完全可以自己去看一下。
最后,执行一句:echo $DisplayPage;,页面就显示到玩家的浏览器上了;这时我的源码也先告一段落,明天再来看看“用户验证”