首页
论坛
课程
招聘
[原创] 某梦想音游的逆向分析,以及一些心得总结
2021-2-14 21:10 7187

[原创] 某梦想音游的逆向分析,以及一些心得总结

2021-2-14 21:10
7187

作为一个又菜又爱玩的音游手残玩家,ap 一个曲子总是一个遥不可及的梦想,每天对着 1good 1miss 等等发呆,这样下去也不是办法,于是想调整一下判定,使得某梦想手游能够打到 phigros 的手感,提高一下游戏体验,满足一下联机 ap 的虚荣心。

 

另外,这是刚开始接触逆向的菜鸡的第一次发帖,肯定会有很多疏漏与不合适的地方,希望各位大大能指点指点。

前言

对于音游来说,过度修改会丧失游戏的乐趣,因此并不是很建议大改,折中方案在文末有所提及。

正片

首先拆一下包,发现是 unity 游戏的 il2cpp 方式,看一下 libil2cpp.so 发现没有加密,在 assets\bin\Data\Managed\Metadata 中取出 global-metadata.dat 尝试用 il2cppdumper 直接 dump 下来

 

image-20210214195704860

 

比较意外,没有出什么岔子。拿到了 dump.csscript.json ,那接下来就好办了。

dump.cs 分析

搜索关键词 Perfect,分析关联项,得到 Note 的判定信息。

1
2
3
4
5
6
7
8
9
10
11
12
// Namespace:
public enum NoteResultType // TypeDefIndex: 7514
{
    // Fields
    public int value__; // 0x0
    public const NoteResultType None = -1;
    public const NoteResultType Miss = 0;
    public const NoteResultType Bad = 1;
    public const NoteResultType Good = 2;
    public const NoteResultType Great = 3;
    public const NoteResultType Perfect = 4;
}

那么对于判定函数来说,返回值应该是一种 NoteResultType 的类型。搜索具有相关返回值的函数,得到:

1
2
// RVA: 0xCEA5E4 Offset: 0xCEA5E4 VA: 0xCEA5E4
public static NoteResultType GetResult(float diffSecond, int sweetFrame = 0) { }

用 ida 打开 libil2cpp.so ,执行脚本导入函数信息。按 G 跳转到 0xCEA5E4,得到:

 

image-20210214201335876

 

那么 v5 所存的应该就是判定结果,根据前面的 NoteResultType 枚举类型,一般思路是改为 return 4 ,但是在音游里这样改,反而会出现一个降低游戏体验的问题。

 

在此游戏中,由于 public const NoteResultType None = -1 的设定,你在距离待判定 Note 具有很长一段距离时,如果继续返回 Perfect(4),那么在此 Note 之前的 Note 都会被 miss 掉。

 

image-20210214202204832

 

那么我们只需要将 0x0CEA66C0x0CEA69C 处的 MOV R1, #X 改为 MOV R1, #4 即可。

 

image-20210214202610253

 

AndroidKiller 重新打包,进入游戏后体验直线上升。但还存在一些小问题,如下。

绿条判定修复

在游戏过程中,普通 Note 碰到都是 perfect,但是绿条的起点经常爆 Great,这让强迫症的我有些无法忍受,重新分析 dump.cs,查找有关 slideNote 的信息,终于功夫不负有心人,在 SlideNoteManager 找到了对应的判定函数。

1
2
// RVA: 0xD14E10 Offset: 0xD14E10 VA: 0xD14E10
public NoteResultType Judge(float posY, out int outCursor) { }

ida 跳过去看了一下,是个极其复杂的函数(我改了些变量名以便分析)

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
int __fastcall SlideNoteManager__Judge(int a1, float posY, int *outCursor)
{
    int32_t bpm;                                                                        // r0
    System_Collections_Generic_Dictionary_int__TerrainUtility_TerrainMap__o *music_map; // r6
    float v8;                                                                           // s16
    UnityEngine_Experimental_TerrainAPI_TerrainUtility_TerrainMap_o *v9;                // r5
    int32_t init_cont;                                                                  // r6
    int v11;                                                                            // r9
    int counter;                                                                        // r7
    System_TypeSpec_o *v13;                                                             // r0
    float v14;                                                                          // s18
    System_TypeSpec_o *v15;                                                             // r0
    float v16;                                                                          // s0
    __int64 v17;                                                                        // r0
    int32_t v18;                                                                        // r0
    System_TypeSpec_o *v19;                                                             // r0
    float v20;                                                                          // s18
    int32_t v21;                                                                        // r0
    System_TypeSpec_o *v22;                                                             // r0
    int NoteResult;                                                                     // r6
    System_TypeSpec_o *v24;                                                             // r6
    System_TypeSpec_o *v25;                                                             // r0
    int32_t v26;                                                                        // r0
    int32_t v27;                                                                        // kr04_4
    int v28;                                                                            // r0
 
    if (!byte_2ECE7FD)
    {
        sub_659480(35551);
        byte_2ECE7FD = 1;
    }
    if (!*(_DWORD *)(a1 + 16))
        sub_684BE8(0);
    bpm = NoteManager__get_CurrentBpm(*(_DWORD *)(a1 + 16), 0);
    music_map = *(System_Collections_Generic_Dictionary_int__TerrainUtility_TerrainMap__o **)(a1 + 24);
    if (!music_map)
        sub_684BE8(0);
    v8 = posY;
    v9 = System_Collections_Generic_Dictionary_int__TerrainUtility_TerrainMap___get_Item(
        music_map,
        bpm,
        (const MethodInfo_1431 *)Method_System_Collections_Generic_Dictionary_int__List_SlideNoteManager_SlideResultData___get_Item__);
    init_cont = 1;
    v11 = -2;
    while (1)
    {
        counter = init_cont - 1;
        if (!v9)
            sub_684BE8(0);
        if (counter >= System_Collections_Generic_List________get_Count(
                           (System_Collections_Generic_List_______o *)v9,
                           (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Count__) /
                           2)
        {
            NoteResult = -1;
            counter = 0;
            goto LABEL_34;
        }
        v13 = System_Collections_Generic_List_TypeSpec___get_Item(
            (System_Collections_Generic_List_TypeSpec__o *)v9,
            init_cont - 1,
            (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Item__);
        if (!v13)
            sub_684BE8(0);
        v14 = *(float *)&v13->fields.name;
        v15 = System_Collections_Generic_List_TypeSpec___get_Item(
            (System_Collections_Generic_List_TypeSpec__o *)v9,
            init_cont,
            (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Item__);
        if (!v15)
            sub_684BE8(0);
        v16 = *(float *)&v15->fields.name;
        v17 = 0LL;
        if (v14 < v8)
            LODWORD(v17) = 1;
        if (v16 >= v8)
            HIDWORD(v17) = 1;
        if (!v17)
            break;
        v18 = System_Collections_Generic_List________get_Count(
            (System_Collections_Generic_List_______o *)v9,
            (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Count__);
        v19 = System_Collections_Generic_List_TypeSpec___get_Item(
            (System_Collections_Generic_List_TypeSpec__o *)v9,
            v18 + v11 + 1,
            (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Item__);
        if (!v19)
            sub_684BE8(0);
        v20 = *(float *)&v19->fields.name;
        v21 = System_Collections_Generic_List________get_Count(
            (System_Collections_Generic_List_______o *)v9,
            (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Count__);
        v22 = System_Collections_Generic_List_TypeSpec___get_Item(
            (System_Collections_Generic_List_TypeSpec__o *)v9,
            v21 + v11,
            (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Item__);
        if (!v22)
            sub_684BE8(0);
        --v11;
        ++init_cont;
        if (v20 <= v8 && *(float *)&v22->fields.name > v8)
        {
            counter = System_Collections_Generic_List________get_Count(
                          (System_Collections_Generic_List_______o *)v9,
                          (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Count__) +
                      v11 + 2;
            v25 = System_Collections_Generic_List_TypeSpec___get_Item(
                (System_Collections_Generic_List_TypeSpec__o *)v9,
                counter,
                (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Item__);
            v24 = v25;
            if (!v25)
                sub_684BE8(0);
            goto LABEL_33;
        }
    }
    v24 = System_Collections_Generic_List_TypeSpec___get_Item(
        (System_Collections_Generic_List_TypeSpec__o *)v9,
        init_cont - 1,
        (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Item__);
    if (!v24)
        sub_684BE8(0);
LABEL_33:
    NoteResult = (int)v24->fields.assembly_name;
LABEL_34:
    v26 = System_Collections_Generic_List________get_Count(
        (System_Collections_Generic_List_______o *)v9,
        (const MethodInfo_1470 *)Method_System_Collections_Generic_List_SlideNoteManager_SlideResultData__get_Count__);
    if (NoteResult == -1)
    {
        v28 = 0;
    }
    else
    {
        v27 = v26;
        v28 = v26 / 2 - counter;
        if (v27 / 2 <= counter)
            --v28;
    }
    *outCursor = v28;
    return NoteResult;
}

同样的,如果对上述函数直接返回 Perfect(4),会出现同上文提到的同样的尴尬局面,滑条会提前判定,体验很难受,因此我们需要对这个函数进行分析。

 

阅读完代码后,我们发现,该函数的前半部分有对绿条是否结束进行判定,这个是不应该修改的,此时 NoteResult 为 -1,应该是标志着一个滑条的结束。

 

之后的代码猜测是对手指是否在滑条处的一个判定,结果保存在 NoteResult 中,这也是我们需要修改的地方。

1
2
LABEL_33:
    NoteResult = (int)v24->fields.assembly_name;

转到汇编,这里 R6 保存了 NoteResult。

 

image-20210214204551210

 

那么很显然,我们只需要把这条语句改为一般的赋值即可

 

image-20210214205048439

 

将 0C6096E5 改为 0460A0E3 后,可以看到修改成功。

 

image-20210214204948023

 

打包后进入游戏,喝!大功告成!(虽然手残还是手残,26 级以上还是按不完所有的 Note 呜呜呜)

 

(为了游戏体验,可以适当的更改难度,比如只将前面的 3 改成 4,这样的话,只有 Great 会变成 Perfect,good bad 依旧,相当于永久判卡)