这是一篇投稿文章,试图混一点 rank

https://bbs.pediy.com/thread-266027.htm

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

前言

对于音游来说,过度修改会丧失游戏的乐趣,折中方案在文末有所提及。

正片

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

image-20210214195704860

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

dump.cs 分析

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

// 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 的类型。搜索具有相关返回值的函数,得到:

// 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 找到了对应的判定函数。

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

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

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 中,这也是我们需要修改的地方。

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 依旧,相当于永久判卡)

参考资料

Last modification:February 17th, 2021 at 03:00 pm