7Gyl7kZLOWwAAAAAElFTkSuQmCC.png

跑跑卡丁车kartriderrush.apk(跑跑卡丁车2008年韩文单机版)是nexon公司在2008年出品的手机单机版类似PC版的赛车游戏,可谓是那时的一代经典,曾经在iOS平台和我那800×480屏幕的小破安卓手机上完美运行。十年时间过去了,如今的手机屏幕分辨率已经达到1920×1080甚至2K、4K,高分屏的安卓手机上运行这个古老的apk,会出现游戏分辨率不能自适应的问题,触摸按钮位置和图标也出现漂移。原因自然是当时这个游戏只适配了800×480屏幕的手机。

经过3个多月不懈的努力,我利用unity3D游戏逆向的技术,终于成功让这个古老的游戏换发了生机,完美自适应适配各种屏幕的手机。同时顺便破解了付费跑道、人物和赛车。

最后总结一下,这三个月的经验与教训。

1.利用apktool或者androidkiller解包apk

eyF0+gRYj04AAAAASUVORK5CYII=.png

这是用androidkiller解包后的文件结构

assets文件夹:unity3d的东西基本都在里面

android文件夹:所有的跑道assetbundle在里面

bin->data文件夹:unity3d的所有关卡(场景)资源和序列化的资源(就是乱码的那些文件)都在里面,可以使用disunity来把资源解包成贴图和shade文件等。

bin->data->Managed文件夹:里面都是c#编写的dll动态链接库(或者说是assembly文件),核心文件是Assembly-CSharp.dll,我们主要修改这个文件

2.回编译一下apk文件,看有没有签名验证

回编译后安装运行游戏,结果游戏闪退,证明存在签名验证,估计在java代码里面,利用jd-gui查看java源码,搜索“sign”,发现在com\nexon\kartriderrush\android\olleh\KartRiderRushActivity.class类中存在判断代码

8KwHgAAAABJRU5ErkJggg==.png

然后我们只需要在smali文件中注释掉这个判断就可以不闪退了。

3.研究c#源码

打开Assembly-CSharp.dll,发现有500多个类,真是要命了~
我们主要修改GUI开头的类,下面我来分析一下

GUIAccomplishPopup.cs,跑完成弹出的一个什么东西,只要是带Popup都是关于弹出的窗口
GUIAtlas.cs这个atlas真没搞懂,好像是负责图片资源最初的加载的
GUIAtlasCreator.cs,顾名思义,atlas创建器
GUIAtlasManager.cs,atlas管理器
GUIBackground.cs,显示普通gui界面背景的类:

4fkg92iqGVd6QAAAAASUVORK5CYII=.png

GUIBackgroundHost.cs,显示当为蓝牙房主时的gui界面背景的类:

HxOppwztdN79AAAAAElFTkSuQmCC.png

还有好多分析,太长了我就不贴了

4.使图片全屏显示

注意到显示图片的地方就有这个函数,实际上这个函数就是将tex贴图中我们需要的那部分图片截取出来(缩放)并且显示在屏幕上的:

GUIPanelFactory.Instance.CreateByWindowSpace(type, f, tex, 5, GUIFontCalculator.DEFAULT_GAP);
参数解释:
type:类型

f:float[6]或者float[8],
如果长度为6则没有缩放,则f[0],f[1]为图片左上角在屏幕上显示的坐标(横屏以左上角为坐标系原点,水平右方向为x正方向,垂直向下为y正方向)。(f[2],f[3])-(f[4],f[5])为在贴图tex文件中截取的区域,就是左上角到右下角的对角线。用ps打开,用添加参考线的功能就可以看出实际截图区域。

(Dds格式的贴图可使用ps-dds插件打开,2345看图王也可以查看,但是按照坐标截取查图片时要将dds格式的图片垂直翻转过来,否则一眼看出就是反的)

Float[8]的情况就是增加了缩放,
(float[0],float[1])-(float[2],float[3])是在手机屏幕上显示的区域的对角线,相对于float[6]增加了2位,可以实现放大缩小贴图。

(float[4],float[5])-(float[6],float[7])同样是贴图截取位置

?有人想既然这个方法是负责显示所有图片的,那么只要改一个方法 让其适应屏幕宽高,不就一劳永逸了吗?结果是悲观的,还存在很多地方有问题,例如在批量显示时,每个图片之间的高度和宽度,还有有些水平3部分或垂直3部分的控件图片,就比较复杂。所以还是得慢慢查看源码,慢慢改。

举几个例子,例如GUILoading类
abl2ZmtsSRmr0iA93jP2kpqC2nSD7RfBnspfhIyOsAlitfsjlst8S82njFgIzDDECHiK0msLarh731PnDn0xGrEZTNYIujGfW9FQ1TZEQ2xw9L65fO0+MrRGMj7p9Y01whpBzIUEkIBMIKafbv8HjfXjvr7l2PoAAAAASUVORK5CYII=.png

Guiloading类是负责显示刚打开游戏那个界面的,这里它获取了mainTex,这个贴图就是
nKYkKU5f8fKflaieAoWB4AAAAASUVORK5CYII=.png

这张图片基本不需要截取,直接用就可以了,所以前4位是显示图片到全屏的意思,后4位微微偏了2个像素截取图片。

上图是我改后的代码,添加了自适应屏幕缩放,原来的代码是长度为6数组,没有f[2]和f[3]。

又例如:

5EJnsQhCWmQAAAAASUVORK5CYII=.png

这张图片Ingame_ui_android.dds有左右和漂移按键,是初始化操纵界面时调用的资源。我们就找到对应的类。
Ingame_ui_android.dds贴图 对应的类是GUIIPad类(这个类不是专门给iPad准备的,误导我好久),至于怎么找到的,只能告诉你,猜的。

要是我们把修改前的贴图控件在屏幕上显示的位置标出来,可以看出:

f8sIEz5SHd4AAAAASUVORK5CYII=.jpg

图上用白线圈出,用红笔标记出的位置就是操控按钮显示的位置。
由于操纵方式有3中,所以这三个位置的按键功能是不固定的,所以就采取了先定位,在动态切换功能的方式。要使这5个位置缩放到全屏,只需改一下代码就可以了。

所以根据这个思路,代码修改如下:(原来的代码没有*Screen.width/800f 类似的缩放代码)

ByzKWNtmULcNAAAAAElFTkSuQmCC.png

不过,这只是贴图的位置全屏了,触摸的位置还是没有变。
接下来我们来改触摸位置。

5.使触摸区域自适应屏幕

一般来说,触摸区域和贴图位置都是绑定的,只有很特殊的情况才需要专门分析修改。但是这个左右漂移减速按键是特殊的。它们的触控区域代码在:IOSControler类(设置漂移和方向按键触控区域的类)中的一个字段的get和set方法里(真。大神才敢这么写,活久见)

x8N0YaxRFTVigAAAABJRU5ErkJggg==.png

把这么关键的代码放在一个set方法里面,真的让我瑟瑟发抖,工程师厉害了!

判断操控方式type的值,有三种,对应三种操控方式,同样我给它加上了自动适应屏幕缩放。

6.顺便破解付费跑道

(1)首先尝试对GUITrackListInSingle类的InitializeListctrl()方法修改

在this.visibleAssets_.Add(item);语句附近删除if判断是否是付费的跑道,结果跑道全部显示,但是要钱的跑道为阴影且无法选择

(2)继续修改GUITrackListInSingle类InitializeTrackList()方法

if (definition.Lock)
    {
     nONE = (GUITrackListItem.enItemLockType) definition.LockType;//把这句改成nONE = GUITrackListItem.enItemLockType.NONE
}

修改的意思是在加载跑道列表时,不去判断是否跑道锁定或要钱,结果跑道没有阴影了但还是无法选择

到这一步,可以看见跑道,但是付费的就是无法选中,真的很急人。

我们分析一下这个跑道类结构。
GUITrackListInSingle类是一个list类,list中的item类对应GUITrackListItem类,item被选中,最后发消息给list类,由list负责处理。
所以,关键验证核心代码在GUITrackListInSingle类的ReceiveMessage()方法中,注释掉就可以了。

付费的车辆和人物都是同样的list-item结构,破解的方法也是类似的,就不废话了。

7.拾遗

(1)GUITrackListInSingle类的方法:

protected override Rect GetAvailableRegion()
{
    return GUIBase.ConvertWSToUS(12f, 78f, 458f, 474f);将474f改为(float) Screen.height
}

(2)GUIControls类是管理在进行比赛过程中打开菜单选择gui操纵方式控件的,例如左右方向键和漂移按键,减速按键等

(3)对于CreateByWindowSpace(Int32, Single[], FiaTexture, Int32, Vector3) : GUIPanelEx
函数,在single数组为6的情况下,single[0] single[1]代表图片左上角在手机屏幕上的起始位置点坐标(以屏幕左上角为坐标系原点,确定),(single[2] ,single[3])和(single[4],single[5])代表在贴图资源上截图的矩形的对角线(似乎都要统一减2才准确,可能是系统偏移修正)。
在single数组长度为8的情况下,前四位数代表屏幕上显示的区域对角线两点的坐标(可以比图片实际大,会自动缩放填满),后四位仍然是贴图资源截取位置

(4)修改GUIIPad类的srtart()方法的this.panels_[24].SetTouchRegionByWindowPos((702f Screen.width) / 800f, 0f, (float) Screen.width, (82f Screen.height) / 480f);
使暂停触摸区域适应屏幕

(5)修改数组layoutArray1[0-23]的前两个浮点数,使右上角计时面板位置自适应屏幕

(6)GUIIPad类中的awake()方法给this.controlInfo赋值,这个this.controlInfo[5]就是屏幕下方最多5个按键的图片显示位置坐标,前4位是左上角和右下角的坐标,我修改成根据屏幕大小缩放图标

(7)修改GUIIPad类的awake()和start()方法,使操纵按钮图标显示自适应屏幕缩放,修改iOSController类中的type字段的set_Type方法,使操纵按钮触摸位置自适应屏幕缩放。

(8)class MinimapCameraControl类中修改小地图的位置

(9)GUIMode类是控制最上方按钮栏的,不管在哪个GUI界面都控制

(10)GameLoadingBack和GameLoadingStage类是管理选择跑道点击开始后的载入界面的。

最后的最后,想下载破解版的:
链接:https://pan.baidu.com/s/1geWmnPT 密码:1ms1
这个版本对android7.0以上和arm64位的CPU支持不好,原因在于mono的二进制文件太老了,能运行就可以玩,否则闪退就是不兼容了,我以后再继续研究一下兼容性的问题,毕竟10年前的老游戏了!

标签: none

赞赏排名 赞赏支持

添加新评论