</pre><pre name="code" class="php"><?php
/**
** PHP RXData 解析库 V1.0
**
** 本函数库是巨大八爪鱼编写的
** 使用和转载时请保留此信息
**/
define('MARSHAL_MAJOR', 4);
define('MARSHAL_MINOR', 8);
define('RBT_NIL', '0');
define('RBT_TRUE', 'T');
define('RBT_FALSE', 'F');
define('RBT_FIXNUM', 'i');
define('RBT_EXTENDED', 'e');
define('RBT_UCLASS', 'C');
define('RBT_OBJECT', 'o');
define('RBT_DATA', 'd');
define('RBT_USERDEF', 'u');
define('RBT_USRMARSHAL', 'U');
define('RBT_FLOAT', 'f');
define('RBT_BIGNUM', 'l');
define('RBT_STRING', '"');
define('RBT_REGEXP', '/');
define('RBT_ARRAY', '[');
define('RBT_HASH', '{');
define('RBT_HASH_DEF', '}');
define('RBT_STRUCT', 'S');
define('RBT_MODULE_OLD', 'M'); // 该类型为老版格式,已弃用
define('RBT_CLASS', 'c');
define('RBT_MODULE', 'm');
define('RBT_SYMBOL', ':'); // 符号对象
define('RBT_SYMLINK', ';'); // 链接到符号对象
define('RBT_IVAR', 'I');
define('RBT_LINK', '@'); // 链接到非符号对象
define('ONIG_OPTION_IGNORECASE', 0x01);
define('ONIG_OPTION_EXTEND', 0x02);
define('ONIG_OPTION_MULTILINE', 0x04);
define('SIZEOF_CHAR', 1);
define('SIZEOF_DOUBLE', 8);
define('SIZEOF_INT', 2);
define('SIZEOF_LONG', 4); // 在C语言中,int和long都是4个字节...
define('SIZEOF_LONGLONG', 8);
class RubyClass {
public $name;
}
class RubyModule extends RubyClass {}
class RubyNil {}
class RubyObject {
public $_name = '';
public $_length = 0;
public $_extension = '';
public $_data = array();
public function __get($property) {
if (is_numeric($this->_length)) {
return $this->_data[$property];
} else {
if (isset($this->_data['extension'][$property])) {
return $this->_data['extension'][$property];
} else if (isset($this->data['base'][$property])) {
return $this->_data['base'][$property];
} else {
return NULL;
}
}
}
}
class RubySymbol {
public $name;
public function getMember(RubyObject $obj) {
$member = $this->getMemberName();
return $obj->_data[$member];
}
public function getMemberName() {
if ($this->isMember()) {
return substr($this->name, 1);
} else {
return $this->name;
}
}
// 判断该符号对象是不是一个类的属性名称
public function isMember() {
return ($this->name{0} == '@');
}
public function setMember(RubyObject $obj, $value) {
$member = $this->getMemberName();
$obj->_data[$member] = $value;
}
}
/* 根据文件名打开文件并读取一个项目,返回读取的对象并关闭文件 */
function rxdata_load($filename) {
$file = fopen($filename, 'rb');
if ($file === false) {
return NULL;
}
$data = rxdata_load_one($file);
fclose($file);
return $data;
}
/* 根据文件名打开文件并读取全部项目,通过数组返回后关闭文件 */
function rxdata_load_all($filename) {
$file = fopen($filename, 'rb');
if ($file === false) {
return NULL;
}
$data = array();
while ($obj = rxdata_load_one($file)) {
array_push($data, $obj);
}
return $data;
}
/* 从打开的文件中读取一个项目 */
function rxdata_load_one($file) {
$data = NULL;
if (rxdata_read_header($file)) {
rxdata_reset(); // 移除之前的符号表
$data = rxdata_read($file);
}
return $data;
}
/* 读取文件中接下来的符号对象或符号链接所指向的符号对象,以该符号的名称作为对象名,创建并注册对象,返回所创建的对象 */
function rxdata_prepare_object($file, $has_length = true) {
// 在Ruby中,类名和变量名都是符号,且同名符号只能在内存中存在一次
$sym = rxdata_read_symbol_block($file);
$obj = new RubyObject();
$obj->_name = $sym->name;
if ($has_length) {
$obj->_length = rxdata_read_long($file);
}
rxdata_register_object($obj); // 创建对象后立即注册对象(的引用),以便于之后能够链接到该对象
return $obj;
}
function rxdata_read($file) {
$type = fgetc($file);
switch ($type) {
case RBT_ARRAY:
return rxdata_read_array($file);
case RBT_BIGNUM:
return rxdata_read_bignum($file);
case RBT_CLASS:
return rxdata_read_class($file);
case RBT_EXTENDED:
return rxdata_read_extended($file);
case RBT_FALSE:
return false;
case RBT_FIXNUM:
return rxdata_read_fixnum($file);
case RBT_FLOAT:
return rxdata_read_float($file);
case RBT_HASH:
return rxdata_read_hash($file);
case RBT_HASH_DEF:
return rxdata_read_hash($file, true);
case RBT_IVAR:
return rxdata_read_ivar($file);
case RBT_LINK:
return rxdata_read_link($file);
case RBT_MODULE:
return rxdata_read_module($file);
case RBT_NIL:
return new RubyNil();
case RBT_OBJECT:
case RBT_STRUCT:
return rxdata_read_object($file);
case RBT_REGEXP:
return rxdata_read_regexp($file);
case RBT_TRUE:
return true;
case RBT_STRING:
return rxdata_read_string($file, true);
case RBT_SYMBOL:
return rxdata_read_symbol($file);
case RBT_SYMLINK:
return rxdata_read_symlink($file);
case RBT_UCLASS:
return rxdata_read_uclass($file);
case RBT_USERDEF:
return rxdata_read_userdef($file);
case RBT_USRMARSHAL:
return rxdata_read_usrmarshal($file);
default:
trigger_error(sprintf('文件中0x%x处含有无法识别的Ruby数据类型: \'%s\' (0x%02X)', ftell($file), $type, ord($type)), E_USER_WARNING);
return NULL;
}
}
function rxdata_read_array($file) {
$arr = array();
rxdata_register_object($arr);
$len = rxdata_read_long($file);
for ($i = 0; $i < $len; $i++) {
$arr[$i] = rxdata_read($file);
}
return $arr;
}
/* 从文件中读取一个字节 */
/* 此函数读出来的字节一定是无符号十六进制整数 */
function rxdata_read_byte($file) {
$ch = ord(fgetc($file));
return $ch;
}
function rxdata_read_bignum($file) {
$sign = fgetc($file);
$n = rxdata_read_long($file);
$num = rxdata_read_ulong($file, 2 * $n);
if ($sign == '-') {
$num = -$num;
}
rxdata_register_object($num);
return $num;
}
function rxdata_read_class($file) {
$class = new RubyClass();
rxdata_register_object($class);
$class->name = rxdata_read_string($file);
return $class;
}
function rxdata_read_extended($file) {
$ex = rxdata_read_symbol_block($file);
$obj = rxdata_read($file);
if (is_object($obj)) {
$obj->_extension = $ex->name;
return $obj;
} else {
$package = new RubyObject();
$package->_length = count($obj);
$package->_extension = $ex->name;
$package->_data = $obj;
rxdata_registry_replace($obj, $package); // 重定向连接表中的注册信息
return $package;
}
}
/*
注意:
使用rxdata_read_fixnum读取一个Fixnum对象
使用rxdata_read_long读取任意对象原始二进制数据中的长整数值(例如String对象中表示字符串长度的长整数)
*/
function rxdata_read_fixnum($file) {
// 在Ruby Marshal中,Fixnum类型的数据无需注册
return rxdata_read_long($file);
}
function rxdata_read_float($file) {
$str = rxdata_read_string($file);
if (in_array($str, array('nan', 'inf', '-inf'))) {
$data = $str;
} else {
$data = (float)$str;
}
rxdata_register_object($data);
return $data;
}
function rxdata_read_hash($file, $hasDefaultValue = false) {
$len = rxdata_read_long($file);
$hash = array();
if ($hasDefaultValue) {
$obj = array(&$hash);
} else {
$obj = &$hash;
}
rxdata_register_object($obj);
for ($i = 0; $i < $len; $i++) {
$key = rxdata_read($file);
$hash[$key] = rxdata_read($file);
}
if ($hasDefaultValue) {
$obj[1] = rxdata_read($file);
}
return $obj;
}
/* 读取Marshal数据头 */
function rxdata_read_header($file) {
$oldpos = ftell($file);
$a = rxdata_read_byte($file);
$b = rxdata_read_byte($file);
if ($a == MARSHAL_MAJOR && $b == MARSHAL_MINOR) {
return true; // 如果读取成功则返回true,且指针移动到数据头的后面
} else {
fseek($file, $oldpos); // 如果读取失败,则退回到读取前的位置,并返回false
return false;
}
}
function rxdata_read_ivar($file) {
$obj = rxdata_read($file);
$base_len = $obj->_length;
$base_data = $obj->_data;
$obj->_length = rxdata_read_long($file);
$obj->_data = array();
rxdata_read_object($file, $obj);
$obj->_length = array('base' => $base_len, 'extension' => $obj->_length);
$obj->_data = array('base' => $base_data, 'extension' => $obj->_data);
return $obj;
}
/* 链接到字节流中的非符号对象 */
function rxdata_read_link($file) {
$id = rxdata_read_long($file);
return rxdata_fetch_object($id);
}
/* 读取一个经过Ruby Marshal特殊处理的有符号长整数(不含类型标记) */
function rxdata_read_long($file) {
$ch = to_signed(rxdata_read_byte($file));
if ($ch == 0) {
return 0;
}
if ($ch > 0) {
if ($ch > 4 && $ch < 128) {
return $ch - 5;
}
$x = 0;
for ($i = 0; $i < $ch; $i++) {
$x |= to_signed(rxdata_read_byte($file), SIZEOF_LONG) << (8 * $i);
}
} else {
if ($ch > -129 && $ch < -4) {
return $ch + 5;
}
$ch = -$ch;
$x = -1;
for ($i = 0; $i < $ch; $i++) {
$x &= ~(0xff << (8 * $i));
$x |= to_signed(rxdata_read_byte($file), SIZEOF_LONG) << (8 * $i);
}
}
return $x;
}
function rxdata_read_module($file) {
$module = new RubyModule();
rxdata_register_object($module);
$module->name = rxdata_read_string($file);
return $module;
}
// 如果指定了参数$obj,则此函数只读取成员变量
function rxdata_read_object($file, $obj = NULL) {
if (is_null($obj)) {
$obj = rxdata_prepare_object($file);
}
for ($i = 0; $i < $obj->_length; $i++) {
$memsym = rxdata_read_symbol_block($file);
$memsym->setMember($obj, rxdata_read($file, $obj));
}
return $obj;
}
function rxdata_read_regexp($file) {
$reg = '/' . rxdata_read_string($file) . '/';
$options = rxdata_read_byte($file);
if ($options & ONIG_OPTION_IGNORECASE) {
$reg .= 'i';
}
if ($options & ONIG_OPTION_MULTILINE) {
$reg .= 'm';
}
if ($options & ONIG_OPTION_EXTEND) {
$reg .= 'x';
}
rxdata_register_object($reg);
return $reg;
}
function rxdata_read_string($file, $register = false) {
$len = rxdata_read_long($file);
if ($len > 0) {
$str = fread($file, $len);
} else {
$str = '';
}
if ($register) {
rxdata_register_object($str);
}
return $str;
}
function rxdata_read_symbol($file) {
$symbol = new RubySymbol();
$symbol->name = rxdata_read_string($file);
rxdata_register_symbol($symbol); // 注册该符号,以便于之后链接回来。因为在内存中,同名符号对象只允许存在一次
return $symbol;
}
function rxdata_read_symbol_block($file) {
$type = fgetc($file);
if ($type == RBT_SYMBOL) {
return rxdata_read_symbol($file);
} else if ($type == RBT_SYMLINK) {
return rxdata_read_symlink($file);
} else {
return NULL;
}
}
function rxdata_read_symlink($file) {
$id = rxdata_read_long($file);
return rxdata_fetch_symbol($id);
}
function rxdata_read_uclass($file) {
$obj = rxdata_prepare_object($file, false);
$next_registry = count($GLOBALS['_RBOBJLIST']);
$obj->_data = rxdata_read($file);
rxdata_unregister_object($next_registry); // 由于$obj已经注册,所以需要删除重复注册的$obj->_data
$obj->_length = count($obj->_data);
return $obj;
}
/* 获取一个由固定n个字节(小端序)表示的无符号整数 */
function rxdata_read_ulong($file, $n = SIZEOF_LONG) {
$num = 0;
for ($i = 0; $i < $n; $i++) {
$v = rxdata_read_byte($file);
$num += ($v << ($i * 8));
}
return $num;
}
// 注意:由于$obj已经注册,所以返回时必须返回$obj,不能返回其他变量
function rxdata_read_userdef($file) {
$obj = rxdata_prepare_object($file);
switch ($obj->_name) {
case 'Table':
// Table是RGSS自带的内部类,不是Ruby自带的类
// 地图图层的三维数组的结构是: data[x坐标, y坐标, 地图id]
$dim = rxdata_read_ulong($file); // 数组的维数
$xsize = rxdata_read_ulong($file);
$ysize = rxdata_read_ulong($file);
$zsize = rxdata_read_ulong($file);
$total = rxdata_read_ulong($file);
$arr = array();
for ($i = 0; $i < $total; $i++) {
$value = to_signed(rxdata_read_ulong($file, SIZEOF_INT), SIZEOF_INT);
if ($dim == 1) {
$arr[$i] = $value;
} else if ($dim == 2) {
$x = $i % $xsize;
$y = (int)($i / $xsize);
if (!isset($arr[$x])) {
$arr[$x] = array();
}
$arr[$x][$y] = $value;
} else if ($dim == 3) {
$x = $i % $xsize;
$y = (int)($i % ($xsize * $ysize) / $xsize);
$z = (int)($i / ($xsize * $ysize));
if (!isset($arr[$x])) {
$arr[$x] = array();
}
if (!isset($arr[$x][$y])) {
$arr[$x][$y] = array();
}
$arr[$x][$y][$z] = $value;
}
}
$obj->_data = $arr;
break;
case 'Color':
case 'Tone':
$format = 'dred/dgreen/dblue/d';
if ($obj->_name == 'Color') {
$format .= 'alpha';
} else {
$format .= 'gray';
}
$data = fread($file, 4 * SIZEOF_DOUBLE);
$obj->_data = unpack($format, $data);
break;
default:
/* 输出范例:
** Array ( [0] => RubyObject Object ( [_name] => Color [_length] => 32 [_data] => Array ( ) [data] => 00 00 00 00 00 C0 58 40 00 00 00 00 00 00 59 40 00 00 00 00 00 C0 62 40 00 00 00 00 00 E0 6F 40 ) )
**/
$data = fread($file, $obj->_length);
trigger_error("无法解析的自定义对象类型\"{$obj->_name}\"", E_USER_NOTICE);
$obj->_data = '';
for ($i = 0; $i < $obj->_length; $i++) {
$obj->_data .= sprintf("%02X ", ord($data{$i}));
}
$obj->_data = rtrim($obj->_data);
}
return $obj;
}
function rxdata_read_usrmarshal($file) {
$obj = rxdata_prepare_object($file, false);
$obj->_length = 1;
$obj->_data = rxdata_read($file);
return $obj;
}
/* Ruby链接列表相关函数 */
$_RBOBJLIST = array(); // 当前字节流中出现的所有非符号对象
$_RBSYMLIST = array(); // 当前字节流中出现的所有符号对象
// 获取已注册的非符号对象
function rxdata_fetch_object($id) {
global $_RBOBJLIST;
if (isset($_RBOBJLIST[$id])) {
return $_RBOBJLIST[$id];
} else {
trigger_error(sprintf('引用未注册的非符号对象ID: %d,已注册的最大ID为: %d', $id, count($_RBOBJLIST) - 1), E_USER_WARNING);
return NULL;
}
}
// 获取已注册的符号对象
function rxdata_fetch_symbol($id) {
global $_RBSYMLIST;
if (isset($_RBSYMLIST[$id])) {
return $_RBSYMLIST[$id];
} else {
trigger_error(sprintf('引用未注册的符号对象ID: %d,已注册的最大ID为: %d', $id, count($_RBSYMLIST) - 1), E_USER_WARNING);
return NULL;
}
}
// 注册非符号对象
function rxdata_register_object(&$obj) {
global $_RBOBJLIST;
$id = count($_RBOBJLIST);
$_RBOBJLIST[$id] = &$obj;
return $id;
}
// 注册符号对象
function rxdata_register_symbol(RubySymbol $sym) {
global $_RBSYMLIST;
$id = count($_RBSYMLIST);
$_RBSYMLIST[$id] = $sym; // $sym本身就是一个php对象,所以这里无需加上引用符号
return $id;
}
// 替换非符号对象注册信息
function rxdata_registry_replace(&$old, &$new) {
global $_RBOBJLIST;
foreach ($_RBOBJLIST as $i => $v) {
if ($v === $old) {
$_RBOBJLIST[$i] = &$new;
}
}
}
// 清空上一个字节流的所有链接列表
function rxdata_reset() {
$GLOBALS['_RBOBJLIST'] = array();
$GLOBALS['_RBSYMLIST'] = array();
}
function rxdata_unregister_object($id = NULL) {
global $_RBOBJLIST;
if (is_null($id)) {
// 取消注册最近注册的对象
array_pop($_RBOBJLIST);
} else {
// 取消注册指定序号的对象
unset($_RBOBJLIST[$id]);
$_RBOBJLIST = array_values($_RBOBJLIST);
}
}
/* 将无符号十六进制数转换为有符号十进制整数, 参数$bytes规定了有符号整数类型的字节数 */
/* 在marshal.c中,宏SIGN_EXTEND_CHAR(n)就相当于这里的to_signed(n) */
function to_signed($hex, $bytes = SIZEOF_CHAR) {
$mask = 1 << ($bytes * 8 - 1);
return ($hex ^ $mask) - $mask;
}
/* 将有符号十进制整数转换为无符号十六进制整数 */
function to_unsigned($dex, $bytes = SIZEOF_CHAR) {
$mask = 1 << ($bytes * 8 - 1);
return ($dex + $mask) ^ $mask;
}
// 在64位php中,整数1的十六进制是0x01,而整数-1的十六进制数是0xffffffffffffffff
// 因此,如果想要char a = -1的十六进制值,只需在php中写上-1 & 0xff即可
// echo '0x', dechex(-1 & 0xff); // 输出0xff
// 使用to_unsigned函数也能达到同样的目的: echo to_unsigned(-1, SIZEOF_CHAR);
// 此外,php还提供了unpack函数读取二进制字符串表示的无符号整数
?>
函数库中的三个重要函数:
rxdata_load($filename)
根据文件名打开文件并读取一个项目,返回读取的对象并关闭文件。
rxdata_load_all($filename)
根据文件名打开文件并读取全部项目,通过数组返回后关闭文件。
如果文件打开失败,返回NULL。如果文件打开成功但读取失败,则返回空数组。
rxdata_load_one($file)
从打开的文件中读取一个项目,$file为fopen打开的文件。
注意:
如果读到的是Ruby的nil对象,函数返回PHP的RubyNil对象。
如果读取失败或遇到文件结束,则返回NULL。
一般情况下,RMXP工程中的Data文件夹中的每个rxdata文件都只含有一个项目,而Save存档的rxdata文件中则含有多个项目。
读取Scripts.rxdata时,如果需要获取其中的Ruby脚本内容,只需执行php的gzuncompress函数即可完成解码。
【示例代码1:读取一个存档文件的内容】
<?php
include_once('rxdata_library.php');
$maps = rxdata_load_all('project/Save1.rxdata');
print_r($maps);
?>
【运行结果】
【示例代码2:读取Data文件夹下一个普通的rxdata文件】
<?php
include_once('rxdata_library.php');
$maps = rxdata_load('project/Data/MapInfos.rxdata');
print_r($maps);
?>
【运行结果】
【示例代码3:打开MapInfos.rxdata文件并输出其中存储的RPGXP工程中所有地图的名称】
<?php
include_once('rxdata_library.php');
$maps = rxdata_load('project/Data/MapInfos.rxdata');
foreach ($maps as $map) {
echo $map->name, '<br>';
}
?>
【运行结果】
【示例代码4:输出所有地图的名称和大小等信息】
<?php
include_once('rxdata_library.php');
$maps = rxdata_load('project/Data/MapInfos.rxdata');
foreach ($maps as $map_id => $map) {
$map_info = rxdata_load(sprintf('project/Data/Map%03d.rxdata', $map_id));
$events_num = count($map_info->events);
if (isset($map_info->events[1])) {
$first_event_name = $map_info->events[1]->name;
} else {
$first_event_name = '无';
}
printf('地图: %s, 尺寸: %dx%d, 事件数: %d, 第一个事件的名称: %s<br>', $map->name, $map_info->width, $map_info->height, $events_num, $first_event_name);
}
?>
【运行结果】
【示例代码5:以表格的形式输出工程中的所有地图】
<?php
include_once('rxdata_library.php');
$maps = rxdata_load('Data/MapInfos.rxdata');
?>
<!doctype html>
<html>
<head>
<title>地图列表</title>
<style>
body {
font-family: Arial, Helvetica;
font-size: 14px;
}
</style>
</head>
<body>
<table border="1">
<tr>
<td>地图ID</td>
<td>地图名称</td>
<td>地图尺寸</td>
<td>事件数量</td>
</tr>
<?php
$i = 0;
foreach ($maps as $id => $map) {
$mapfile = rxdata_load(sprintf("Data/Map%03d.rxdata", $id));
?>
<tr>
<td><?=$id?></td>
<td><?=$map->name?></td>
<td><?=$mapfile->width?>x<?=$mapfile->height?></td>
<td><?=count($mapfile->events)?></td>
</tr>
<?php } ?>
<tr>
<td colspan="4">共有<span style="color:red"><?=count($maps)?></span>张地图。</td>
</tr>
</table>
</body>
</html>
【运行结果】
【示例代码6:读取脚本内容】
<?php
include_once('rxdata_library.php');
$scripts = rxdata_load('project/Data/Scripts.rxdata');
$txt = gzuncompress($scripts[1][2]);
echo nl2br($txt); // 把\n转换为<br>
?>
【运行结果】
【示例7:把工程中的所有脚本全部保存为txt文件】
<?php
include_once('rxdata_library.php');
if (!is_writable(getcwd())) {
die('当前文件夹下无写入权限!');
}
$folder_name = 'scripts';
if (is_dir($folder_name)) {
// 如果目录存在,则删除目录下所有文件
$dir = opendir($folder_name);
while ($file = readdir($dir)) {
$file = $folder_name . '/' . $file;
if (!is_dir($file)) {
unlink($file);
}
}
closedir($dir);
} else {
mkdir($folder_name);
}
$scripts = rxdata_load('project/Data/Scripts.rxdata');
foreach ($scripts as $script) {
if ($script[1] == '')
$script[1] = 'empty';
$filename = $folder_name . '/' . $script[1] . '.txt';
$fp = fopen($filename, 'w');
fwrite($fp, gzuncompress($script[2]));
fclose($fp);
}
printf("共保存了%d个文件", count($scripts));
?>
【运行结果】
保存的txt文件:
打开其中一个txt文件查看: