《星界之梯》查询网页开发记录

banner

之前玩一款港游 《星界之梯》 就一直想把里面的立绘找出来做桌面,所以一边玩一遍寻思解包的方法。

游戏分析

要拿到资源,首先要确定游戏的资源在哪。我玩游戏的时候发现一个现象,就是在游戏副本里面,断开网络,并不影响副本里面的操作,只有通关把网络打开,就能 connect 到服务器领取奖励。

也就是说,游戏大部分的运行逻辑都在手机端完成的,服务端只是对手机端的充值,副本胜利判定,和账户中关卡开放程度进行管理。

那手机端的游戏数据会放在哪里呢?不应该在 apk 里面,因为图片,音效,运行逻辑数据太大,只能是需要的时候读取。而为了 apk 不卡顿,这些数据不应该存放在外置 SD 卡。而系统的 app 的 root 路径是需要管理员权限的,游戏如果把数据放那里,那没有 Root 用户怎么安装游戏呢?所以,游戏数据包应该在手机内存的Android/data/文件夹下。

果然,我在Android/data/文件夹下的com.firedog里面找到了数据。以前要是更新,还会看到*.obb文件在Android/obb/文件夹下。

游戏包解析

上面说到的*.obb文件,其实是 Android 游戏数据包。以前玩过手机 QQ 美化,所以知道 Android 上的特殊格式一般可以解压打开。而*.obb解压得到的就是Android/data/下的数据。

接下来看解压后的数据,里面有 json 文件,unity3d 的文件。json 文件是一种对象数据文件,所以立绘是不会在这里的。而且我用 sublime 搜索没发现有任何 png 或者 jpg 的文件引用。先放一边,我们要的立绘不在这。接下来动手拆 unity3d 数据了。

拆包

没做过游戏,不太清楚 unity3d 的包怎么打开,上百度查了一圈,找到了 disunityUnityStudiodisunity 是一个命令行反编译 unity3d 工具,而 UnityStudio 是 win 上使用的反编译 unity3d 工具。我用的是 Linux,所以毫不犹豫用了 disunity。说起来这款工具的作者在 2016 年 1 月 18 号停更了 Java 版本,2017 年 08 月 13 日我翻了一下发现有个 Python 版本的昨天刚刚更新。把里面的版本试了一圈,发现 3.x 系列才能正确的反编译出东西,4.x 貌似还是有点小问题,5.0 根本不能用好吗。

disunity 3.x 使用方法:

1
2
3
disunity extract Web.unity3d
# 或者
java -jar disunity.jar extract Web.unity3d

图片格式批量转换

得到了很多文件夹,里面有音频、纯文本数据和后缀为tga的文件,我要的 png 呢?难道是这些tga后缀文件?不知道就百度呗,果然这个 tga 也是图片资源,开始我电脑不识别呀,还是转换成大众点的格式比较好,上 github 找轮子,果然还是有人写过的,tga2png 有例子,可是我需要批量呀,既然是 js 模块,我用 node 写个批量处理好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var fs = require('fs');
let glob = require("glob")//文件模式匹配
var filename = 'CutInNew'
let Afiles = `./files/${filename}/**/**/Texture2D/*.tga`
var tga2png = require('tga2png');//tga 转 png 工具
var async = require('async')//异步工具

fs.exists(filename, function(exists) {
if (exists)
console.log(filename + '文件夹存在');
else {
fs.mkdir(filename, function(err) {
if (err)
console.error(err);
console.log(`创建${filename}文件夹成功`);
})
}

});
glob(Afiles, 'buffer',
function(err, afiles) {
if (err) throw err;
/**
* [async 遍历文件名,并转换格式]
*/
async.mapSeries(afiles, function(item, callback) {
var arrUrl = item.split("/");
var name = arrUrl[arrUrl.length - 1].replace(".tga", "");
var fname = arrUrl[arrUrl.length - 3]
setTimeout(function() {
tga2png(item, fname == 'TC' ? `./${filename}/${fname}-${name}.png` : `./${filename}/${name}.png`)
.then(() => {
console.log('the png buffer is ', fname + '-' + name);
}, err => {
console.log('error', err);
});
callback(null, item + '!!!');
}, 0);
}, function(err, results) {
console.log(err)
});
}
)

这样就拿到立绘了。

不过有个问题,obb 里面有些资源不全呀,游戏没出错。应该是有部分资源在 apk 里面。动手反编译 apk。

反编译 apk

这里用到了一个工具 apktool,官网有说明的。

1
2
3
4
# 反编译
$ apktool d test.apk
# 打包
$ apktool b test

还有一种方法比较暴力,直接解压,用 dex2jar 转换成 jar 用 jd-gui 看源码。

用纯 React 写一个 AS 查询网页

这个游戏的行使有 1000 多个,有时候搭配起来还是很麻烦的,所以就有了写个查询的网页。

星界之梯的数据其实不符合我操作的预期的,例如:

  1. 有些有名字的行使,并没有发布出来,也就是和立绘没有一一对应。
  2. 行使的数据分散到不同的 json 文件,而文件对应关系也不理想。

还有些是前端的问题:

  1. React 没有双向绑定,要点击一个头像,在侧边栏显示详情,组件关系怎么解决?
  2. 图片太多,全部加载完的话会需要很长时间,有什么好的方案?

目前这四个问题需要我一步一步解决。

json 数据处理

数据如果没法对应起来,前端做的事就很麻烦了。为了解决这个问题,我决定把所有需要的数据写到一个 json 文件中,这样子处理起来,就容易许多了。

整合完 json,接下来就是让立绘和数据一一对应。头像的文件和立绘文件是一一对应,所以只有检测头像是否存在,也就能知道立绘是否存在了。做法是读取头像文件夹存在的文件,把文件名转换成数值,保存到数组中,需要的时候拼接字符串即可。但是这个数组不能和整合好的 json 文件对应,所以 json 文件不能引用这个数组。怎么处理?

其实可以这么做,把数组作为中间数据,用来规范命名资源文件,到时候 json 文件只需要数值拼接成字符串就能访问到准确的资源文件。

React 通信问题

React 是单向数据的,常用的有propsstateprops是父组件给子组件传值用的信使,state是组件内部的状态。没有子组件向父组件传值的方法呀。官方给出的答案是状态提升。

状态提升,就是把state统一放在父组价中管理,父组件通过props把子组件需要的参数传给子组件。JavaScript 有个神技能,就是一等公民,所以我们可以把一个函数传递给子组件,子组件拿到参数,里面有需要的值和一个父组件中定义的函数,子组件把参数拿来给自己用,当有事触发,可以把触发的信息具体是什么塞到父组件给的函数里返回给父组件。这样就能实现子组件向父组件通信了。

而我想在左边栏显示详情,右边是全部行使的头像,点击头像,左边栏显示对应的行使详情。状态提升就能做到。

首先定义一个函数传递给右边栏组件,右边栏里面的的头像都要自己唯一的 ID,同时有个点击触发 handler 传递 id 给父组件的函数返回给父组件,父组件拿到 id 就传递给左边栏显示。左右边栏是兄弟关系。

图片太多的问题

行使头像还好,可是行使立绘就呵呵哒了呀。怎么样才能让行使立绘需要的时候才加载呢?问了慕课网的鞭挞师,答案是 CSS 引入图片,就这么简单。

界面美化

虽然东西是写出来了,可是我感觉左边栏的滚动条好丑呀,可是隐藏滚动调,要是左边的内容太多,那就看不到了。….. 关键时刻找度娘,找到了个可行方案,就是写两个 div,上面的 div 比下面的 div 宽度小,就把下面的 div 的滚动条给盖住了,看不到的。而下面的 div 还是能滚动。


《星界之梯》查询网页开发记录
https://bubao.github.io/posts/cef2f6bc.html
作者
一念
发布于
2017年8月13日
许可协议