大江湖-逆向篇-源码修复

逆向篇-源码修复

摘要
​反编译后的代码存在许多问题,这个章节我们将对出现的问题进行修复,让其能够顺利编译通过

导入 Assembly-CSharp

Assets\Scripts\Assembly-CSharp\Assembly-CSharp.csproj​覆盖到根目录

编辑 csproj​文件,所有源文件的路径是错误的,对所有字符串进行统一替换,<Compile Include="​替换为 <Compile Include="Assets\Scripts\Assemble-CSharp\

1
2
3
4
5
6
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Global.cs" />
<Compile Include="C2TDemo.cs" />
<Compile Include="SampleTable.cs" />
<Compile Include="CsvParser.cs" />
<Compile Include="CsvParser2.cs" />

替换后路径如下

1
2
3
4
5
<Compile Include="Assets\Scripts\Assemble-CSharp\AssemblyInfo.cs" />
<Compile Include="Assets\Scripts\Assemble-CSharp\Global.cs" />
<Compile Include="Assets\Scripts\Assemble-CSharp\C2TDemo.cs" />
<Compile Include="Assets\Scripts\Assemble-CSharp\SampleTable.cs" />
<Compile Include="Assets\Scripts\Assemble-CSharp\CsvParser.cs" />

重新打开 Visual Studio​,发现已经可以正确识别 Assembly-CSharp​内的源码了

第三方库修复

第三方引用是以源码的形式写入的

此处我们将 Assembly-CSharp-firstpass​和 Unity​系列的换成工程换成 dll​形式

打开游戏目录,将 TWOKFDEMO\TheWorldOfKongFu_Data\Managed​目录下的 Unity.Analytics.DataPrivacy.dll​和 Unity.TextMeshPro.dll​拷贝到工程 Library\ScriptAssemblies​目录

编辑 Assembly-CSharp.csproj​文件,将以上三个目录下的 cs 引用删除

1
2
3
4
5
6
<Compile Include="Assets\Scripts\Unity.TextMeshPro\TMPro\ColorMode.cs" />
<Compile Include="Assets\Scripts\Unity.TextMeshPro\TMPro\FaceInfo.cs" />
...
<Compile Include="Assets\Scripts\Assembly-CSharp-firstpass\Steamworks\CSteamID.cs" />
<Compile Include="Assets\Scripts\Assembly-CSharp-firstpass\Steamworks\CallbackDispatcher.cs" />
...

Assembly-CSharp-firstpass.dll​是 steam​的 api​库,我们可以将其移除

同时,修改 Assets\Scripts\Assembly-CSharp\TitleController.cs​文件,将初始化逻辑删除

1
2
3
4
5
6
7
8
private void Start()
{
    // 注释steam初始化代码
    //if (SteamManager.Initialized)
    //{
    //    Debug.Log(SteamFriends.GetPersonaName());
    //}
}

导入 DOTween

DOTween 是开源在 Github​的一个强大的动画插件,我们可以直接从 Github 上下载源码覆盖进去

首先需要确认 DOTween​的版本号,打开 Reflector​反编译工具,将 DOTween.dll​文件拖进去,版本号记录在 DG.Tweening.DOTween.Version​中

可以看到版本号为 1.2.280,接下来就可以将 DOTWeen1.2.280 整个工程下载下来,里面目录较多,我们只需要将 _DOTween.Assembly\DOTween\​下的所有文件覆盖到 Assets\Scripts\DOTween\DG\Tweening​目录即可

导入 spine-unity

spine​是一个强大的骨骼动画工具,官方提供的插件源码在 Spine-Runtime,只需要下载对应版本代码覆盖进去即可

确定版本号可以通过 Reflect​工具对代码进行反编译,或者与 Github​上的源码进行比对,最终确认版本为 spine-runtimes-spine-libgdx-3.8.55.1

下载地址为 spine-libgdx-3.8.55.1,下载成功后,搜索对应的类名,将文件覆盖到 Assets\Scripts\Assembly-CSharp\Spine\​和 Assets\Scripts\spine-unity​目录

导入 SuperTiled2Unity

SuperTiled2Unity 是一个开源的转换工具,支持将 Tiled Map Editor 创建的地图导入到 Unity​中使用,与上面的方式处理相同,将代码下载后,覆盖到同名文件

注意
.meta​文件不能覆盖,里面存放着 cs 文件的 id 信息,替换后 id 变化会导致资源引用失败

源码修复

yield 修复

C#​中使用 yield​关键字的源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
IEnumerator AutoActionWait_CallBack(Vector3Int _attackPos) {
    yield return new WaitForSeconds(0.25f);

    if (current.race == "player")
    {
        m_Flow = BattleController.Flow.PlayerAction;
    }
    else if (current.race == "enemy")
    {
        m_Flow = BattleController.Flow.EnemyAction;
    }
    current.Attack(_attackPos, true);
}

编译后会变成如下代码,使用了状态机进行条件转换

 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
[IteratorStateMachine((Type) typeof(<AutoActionWait_CallBack>d__54))]
    private IEnumerator AutoActionWait_CallBack(Vector3Int _attackPos) => 
    new <AutoActionWait_CallBack>d__54(0) { 
        <>4__this = this,
        _attackPos = _attackPos
    };

[CompilerGenerated]
private sealed class <AutoActionWait_CallBack>d__54 : IEnumerator<object>, IEnumerator, IDisposable
{
    private int <>1__state;
    private object <>2__current;
    public BattleController <>4__this;
    public Vector3Int _attackPos;

    [DebuggerHidden]
    public <AutoActionWait_CallBack>d__54(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }

    private bool MoveNext()
    {
        int num = this.<>1__state;
        BattleController controller = this.<>4__this;
        switch (num)
        {
            case 0:
                this.<>1__state = -1;
                this.<>2__current = new WaitForSeconds(0.25f);
                this.<>1__state = 1;
                return true;

            case 1:
                this.<>1__state = -1;
                if (controller.current.race == "player")
                {
                    controller.m_Flow = BattleController.Flow.PlayerAction;
                }
                else if (controller.current.race == "enemy")
                {
                    controller.m_Flow = BattleController.Flow.EnemyAction;
                }
                controller.current.Attack(this._attackPos, true);
                break;
        }
        return false;
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    [DebuggerHidden]
    void IDisposable.Dispose()
    {
    }

    object IEnumerator<object>.Current =>
        this.<>2__current;

    object IEnumerator.Current =>
        this.<>2__current;
}

此处只能手动将代码中所有的 yield​逻辑进行替换

Object 不明确引用

提示 Object​不明确引用

C#标准库​和 Unity​中都有定义 Ojbect​类,需要指定为 UnityEngine.Object​​

还有

  • Random​ 修改为 UnityEngine.Random

bool 修复

所有 bool​类型编译后都被转换成了 0 和 1,需要手动转换成 false​ 和 true

get/set

DateTime.get_UtcNow​提示无法显示调用运算符或访问器

修改为

同理,将以下 get 函数还原成直接读取属性

  • DateTime.get_Now()​修改为 DateTime.Now
  • componentInChildren.get_color();​修改为 componentInChildren.color;
  • componentInChildren.set_color(color);​修改为 componentInChildren.color = color;
  • go.GetComponent<Image>().set_sprite(this.SelectedSprite);​修改为 go.GetComponent<Image>().sprite = this.SelectedSprite;

还有一种形式,编译后的 set​如下,多了一个()

1
2
3
4
5
6
7
public float MoveThreshold
{
    get => 
        this.moveThreshold;
    set => 
        (this.moveThreshold = Mathf.Abs(value));
}

需要修改为

1
2
3
4
5
6
7
public float MoveThreshold
    {
        get => 
            this.moveThreshold;
        set =>  
            this.moveThreshold = Mathf.Abs(value);
    }

switch

源码 switch​的结构如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
switch (type)
{
    case "debuffATK":
    {
        original = SharedData.Instance(false).Buff_debuffATK_Prefab;
        break;
    }

    case  "burn":
    {
        original = SharedData.Instance(false).Buff_burn_Prefab;
        break;
    }
}

编译后 C#​为了提高性能,会优化成判断字符串的 hash​值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
switch (<PrivateImplementationDetails>.ComputeStringHash(type))
{
    case 0x312d5c4b:
        if (type == "debuffATK")
        {
            original = SharedData.Instance(false).Buff_debuffATK_Prefab;
            break;
        }
        break;

    case 0x51f91098:
        if (type == "burn")
        {
            original = SharedData.Instance(false).Buff_burn_Prefab;
            break;
        }
        break;
}

需要将所有涉及 switch​语句的代码进行还原

int/float/string 转换

编译后的代码,部分赋值不符合函数参数格式,比如 int​和 string​需要进行转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public Row Find_LV(string find) => 
    this.rowList.Find(delegate (Row x) {
    return x.LV == find;
});

public class CharaData
{
    public int m_Level = 1;
}

SharedData.Instance(false).a05.Find_LV( ( (int) this.charadata.m_Level) ).EXP 

base.transform.Find("Panel/Status/Power/Text").GetComponent<Text>().text = ((float) num);

修改为

1
2
3
SharedData.Instance(false).a05.Find_LV( this.charadata.m_Level.ToString() ).EXP 

base.transform.Find("Panel/Status/Power/Text").GetComponent<Text>().text =  num.ToString();

goto

for​循环中,会使用 goto​进行跳转

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
foreach (char ch in str)
{
    if (ch != '"')
    {
        if (ch != ',')
        {
            goto Label_003F;
        }
        state = state.Comma(context);
    }
    else
    {
        state = state.Quote(context);
    }
    continue;
Label_003F:
    state = state.AnyChar(ch, context);
}

可以修复为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
foreach (char ch in str)
{
    if (ch != '"')
    {
        if (ch != ',')
        {
            state = state.AnyChar(ch, context);
            continue;
        }
        state = state.Comma(context);
    }
    else
    {
        state = state.Quote(context);
    }
}

修复完成

以上所有代码修改完成后,代码可以正常编译,没有报错信息了

要修复的种类并不多,但是代码量比较大,需要一些耐心逐个进行修复

0%