文件名:rxdata_library.php
</pre><pre name="code" class="php"><?php define('MARSHAL_MAJOR', 4); define('MARSHAL_MINOR', 8); define('RBT_FALSE', 'F'); define('RBT_FIXNUM', 'i'); define('RBT_EXTENDED', 'e'); define('RBT_UCLASS', 'C'); define('RBT_OBJECT', 'o'); 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_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_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_LONG', 4); // 在C语言中,int和long都是4个字节... define('SIZEOF_LONGLONG', 8); class RubyModule extends RubyClass {} public function __get($property) { if (is_numeric($this->_length)) { return $this->_data[$property]; if (isset($this->_data['extension'][$property])) { return $this->_data['extension'][$property]; } else if (isset($this->data['base'][$property])) { return $this->_data['base'][$property]; public function getMember(RubyObject $obj) { $member = $this->getMemberName(); return $obj->_data[$member]; public function getMemberName() { return substr($this->name, 1); 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'); $data = rxdata_load_one($file); /* 根据文件名打开文件并读取全部项目,通过数组返回后关闭文件 */ function rxdata_load_all($filename) { $file = fopen($filename, 'rb'); while ($obj = rxdata_load_one($file)) { function rxdata_load_one($file) { if (rxdata_read_header($file)) { rxdata_reset(); // 移除之前的符号表 $data = rxdata_read($file); /* 读取文件中接下来的符号对象或符号链接所指向的符号对象,以该符号的名称作为对象名,创建并注册对象,返回所创建的对象 */ function rxdata_prepare_object($file, $has_length = true) { // 在Ruby中,类名和变量名都是符号,且同名符号只能在内存中存在一次 $sym = rxdata_read_symbol_block($file); $obj->_name = $sym->name; $obj->_length = rxdata_read_long($file); rxdata_register_object($obj); // 创建对象后立即注册对象(的引用),以便于之后能够链接到该对象 function rxdata_read($file) { return rxdata_read_array($file); return rxdata_read_bignum($file); return rxdata_read_class($file); return rxdata_read_extended($file); return rxdata_read_fixnum($file); return rxdata_read_float($file); return rxdata_read_hash($file); return rxdata_read_hash($file, true); return rxdata_read_ivar($file); return rxdata_read_link($file); return rxdata_read_module($file); return rxdata_read_object($file); return rxdata_read_regexp($file); return rxdata_read_string($file, true); return rxdata_read_symbol($file); return rxdata_read_symlink($file); return rxdata_read_uclass($file); return rxdata_read_userdef($file); return rxdata_read_usrmarshal($file); trigger_error(sprintf('文件中0x%x处含有无法识别的Ruby数据类型: \'%s\' (0x%02X)', ftell($file), $type, ord($type)), E_USER_WARNING); function rxdata_read_array($file) { rxdata_register_object($arr); $len = rxdata_read_long($file); for ($i = 0; $i < $len; $i++) { $arr[$i] = rxdata_read($file); /* 此函数读出来的字节一定是无符号十六进制整数 */ function rxdata_read_byte($file) { function rxdata_read_bignum($file) { $n = rxdata_read_long($file); $num = rxdata_read_ulong($file, 2 * $n); rxdata_register_object($num); function rxdata_read_class($file) { $class = new RubyClass(); rxdata_register_object($class); $class->name = rxdata_read_string($file); function rxdata_read_extended($file) { $ex = rxdata_read_symbol_block($file); $obj = rxdata_read($file); $obj->_extension = $ex->name; $package = new RubyObject(); $package->_length = count($obj); $package->_extension = $ex->name; rxdata_registry_replace($obj, $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'))) { rxdata_register_object($data); function rxdata_read_hash($file, $hasDefaultValue = false) { $len = rxdata_read_long($file); rxdata_register_object($obj); for ($i = 0; $i < $len; $i++) { $key = rxdata_read($file); $hash[$key] = rxdata_read($file); $obj[1] = rxdata_read($file); function rxdata_read_header($file) { $a = rxdata_read_byte($file); $b = rxdata_read_byte($file); if ($a == MARSHAL_MAJOR && $b == MARSHAL_MINOR) { return true; // 如果读取成功则返回true,且指针移动到数据头的后面 fseek($file, $oldpos); // 如果读取失败,则退回到读取前的位置,并返回false function rxdata_read_ivar($file) { $obj = rxdata_read($file); $base_len = $obj->_length; $base_data = $obj->_data; $obj->_length = rxdata_read_long($file); rxdata_read_object($file, $obj); $obj->_length = array('base' => $base_len, 'extension' => $obj->_length); $obj->_data = array('base' => $base_data, 'extension' => $obj->_data); 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 > 4 && $ch < 128) { for ($i = 0; $i < $ch; $i++) { $x |= to_signed(rxdata_read_byte($file), SIZEOF_LONG) << (8 * $i); if ($ch > -129 && $ch < -4) { for ($i = 0; $i < $ch; $i++) { $x &= ~(0xff << (8 * $i)); $x |= to_signed(rxdata_read_byte($file), SIZEOF_LONG) << (8 * $i); function rxdata_read_module($file) { $module = new RubyModule(); rxdata_register_object($module); $module->name = rxdata_read_string($file); // 如果指定了参数$obj,则此函数只读取成员变量 function rxdata_read_object($file, $obj = NULL) { $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)); function rxdata_read_regexp($file) { $reg = '/' . rxdata_read_string($file) . '/'; $options = rxdata_read_byte($file); if ($options & ONIG_OPTION_IGNORECASE) { if ($options & ONIG_OPTION_MULTILINE) { if ($options & ONIG_OPTION_EXTEND) { rxdata_register_object($reg); function rxdata_read_string($file, $register = false) { $len = rxdata_read_long($file); $str = fread($file, $len); rxdata_register_object($str); function rxdata_read_symbol($file) { $symbol = new RubySymbol(); $symbol->name = rxdata_read_string($file); rxdata_register_symbol($symbol); // 注册该符号,以便于之后链接回来。因为在内存中,同名符号对象只允许存在一次 function rxdata_read_symbol_block($file) { if ($type == RBT_SYMBOL) { return rxdata_read_symbol($file); } else if ($type == RBT_SYMLINK) { return rxdata_read_symlink($file); 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); /* 获取一个由固定n个字节(小端序)表示的无符号整数 */ function rxdata_read_ulong($file, $n = SIZEOF_LONG) { for ($i = 0; $i < $n; $i++) { $v = rxdata_read_byte($file); $num += ($v << ($i * 8)); // 注意:由于$obj已经注册,所以返回时必须返回$obj,不能返回其他变量 function rxdata_read_userdef($file) { $obj = rxdata_prepare_object($file); // 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); for ($i = 0; $i < $total; $i++) { $value = to_signed(rxdata_read_ulong($file, SIZEOF_INT), SIZEOF_INT); $y = (int)($i % ($xsize * $ysize) / $xsize); $z = (int)($i / ($xsize * $ysize)); if (!isset($arr[$x][$y])) { $arr[$x][$y][$z] = $value; $format = 'dred/dgreen/dblue/d'; if ($obj->_name == 'Color') { $data = fread($file, 4 * SIZEOF_DOUBLE); $obj->_data = unpack($format, $data); ** 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); for ($i = 0; $i < $obj->_length; $i++) { $obj->_data .= sprintf("%02X ", ord($data{$i})); $obj->_data = rtrim($obj->_data); function rxdata_read_usrmarshal($file) { $obj = rxdata_prepare_object($file, false); $obj->_data = rxdata_read($file); $_RBOBJLIST = array(); // 当前字节流中出现的所有非符号对象 $_RBSYMLIST = array(); // 当前字节流中出现的所有符号对象 function rxdata_fetch_object($id) { if (isset($_RBOBJLIST[$id])) { trigger_error(sprintf('引用未注册的非符号对象ID: %d,已注册的最大ID为: %d', $id, count($_RBOBJLIST) - 1), E_USER_WARNING); function rxdata_fetch_symbol($id) { if (isset($_RBSYMLIST[$id])) { trigger_error(sprintf('引用未注册的符号对象ID: %d,已注册的最大ID为: %d', $id, count($_RBSYMLIST) - 1), E_USER_WARNING); function rxdata_register_object(&$obj) { $id = count($_RBOBJLIST); $_RBOBJLIST[$id] = &$obj; function rxdata_register_symbol(RubySymbol $sym) { $id = count($_RBSYMLIST); $_RBSYMLIST[$id] = $sym; // $sym本身就是一个php对象,所以这里无需加上引用符号 function rxdata_registry_replace(&$old, &$new) { foreach ($_RBOBJLIST as $i => $v) { function rxdata_reset() { $GLOBALS['_RBOBJLIST'] = array(); $GLOBALS['_RBSYMLIST'] = array(); function rxdata_unregister_object($id = NULL) { $_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文件查看:
|