吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6512|回复: 15
收起左侧

[.NET逆向] 基于Mono注入 保存你画我猜历史房间数据

  [复制链接]
XhyEax 发表于 2021-8-2 16:24
本帖最后由 XhyEax 于 2021-8-2 16:28 编辑

概述

最近在Steam购买了你画我猜(Draw & Guess),游戏并不支持保存最近房间代码,以及玩家id。
由于众所周知的服务器原因(土豆做的),经常有人掉线或闪退(也可能是自己),重连后无法返回之前的房间。

于是考虑使用Mono注入技术,实现历史房间数据的保存。

逆向分析过程

文件结构分析

查看游戏目录结构,发现是使用Unity编写的Mono平台游戏,关键dll文件位于..\steamapps\common\Draw & Guess\Draw&Guess_Data\Managed,并且未加密。

房间代码生成

搜索RoomCode,定位到类RoomCodeEncoder,发现房间代码是由房主的SteamID进行base36编码生成,对应DecimalToArbitrarySystem函数。

ArbitraryToDecimalSystem函数用于将房间代码转换为SteamID,在MenuClicks.EnterRoomCode函数中被调用,通过LobbyManagerJoin函数加入指定玩家的房间(如下图)

因此,只需记录玩家id,即可计算出房间代码。

PS:可通过http://steamcommunity.com/profiles/+steamID,打开个人主页

记录玩家id

搜索PlayerList,找到LobbyPlayerListIngamePlayerList,分别对应匹配时玩家列表游戏时玩家列表

匹配时玩家列表
类结构:
public static LobbyPlayerList Instance;
public List<RectTransform> PlayerList = new List<RectTransform>();
public List<LobbyPlayerInfo> Players = new List<LobbyPlayerInfo>();
AddPlayer 函数:

可通过LobbyPlayerList.Instance.Players直接获取到玩家列表。

游戏时玩家列表
类结构:
private Dictionary<ulong, IngamePlayerListEntry> ContainedPlayers = new Dictionary<ulong, IngamePlayerListEntry>();
public static IngamePlayerList CurrentList;
AddPlayer 函数:

可使用IngamePlayerList.CurrentListContainedPlayers字段获取玩家列表,由于修饰符为private(非public),需要通过反射获取。

确定获取时机

查看AddPlayer调用栈:

由此可确定以下两个函数:
RoundManager.UserCode_RpcSetInfo(对应LobbyPlayerList
LobbyPlayerInfo.UserCode_RpcSetPlayerInfo(对应IngamePlayerList

注意到后者传入的是数组,推测该函数是每次按下Tab,显示玩家列表时调用,需要手动按键才能触发。

考虑到两者先后顺序(LobbyPlayerList先于IngamePlayerList),故选择使用LobbyPlayerList.Instance.Players获取玩家列表。

确定Hook函数

搜索字符串游戏将在,定位到SimplifiedChineseLocalisation.UIGameStartingIn变量,查看其调用栈:

双击UserCode_RpcGetCountdown函数,查看函数体:

功能是调用LobbyChat.Instance.ReceiveChat函数,更新聊天框内容

由此,选择在LobbyPlayerInfo.UserCode_RpcGetCountdown函数调用后(游戏即将开始时),保存房间代码及玩家列表。(保存上次调用时间,超过10秒则保存房间数据到本地)

同时,为了提示模块已经加载,选择在LobbyChat.Start函数调用后输出提示信息。

PS:由于LobbyChat实际上是更新一个TextMeshProUGUI(支持富文本标签),所以可以指定字体大小、颜色等属性。(在别人房间里发送,会被服务器断开连接)

TextMesh Pro支持的富文本标签见Rich Text

注入模块开发

基于MonoHook,开发一个注入dll

创建项目并导入依赖

Visual Studio中创建一个.Net 4.0类库项目,将必要的游戏dll添加为依赖。

编写注入代码

此处仅贴出关键代码,完整项目代码见github:DAGHistory

模块加载提示

hook LobbyChat.Start函数,在聊天界面创建后输出信息。

public static void StartReplace()
    {
        // 先调用原函数
        StartProxy();
        // 输出信息到聊天框
        LobbyChat.Instance.ReceiveChat("<color=#778899>[DAGHistory]: Loaded</color>");
    }

自定义类RoomData

使用该类保存房间数据。

public class Player
{
    ulong steamID;
    string name;

    public Player(string name, ulong steamID)
    {
        this.name = name;
        this.steamID = steamID;
    }
    public string Name { get => name; set => name = value; }
    public ulong SteamID { get => steamID; set => steamID = value; }
}

public class RoomData
{
    string roomCode;
    List<Player> playerList;

    public string RoomCode { get => roomCode; set => roomCode = value; }
    public List<Player> PlayerList { get => playerList; set => playerList = value; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

重载ToString函数,使用Json序列化该对象。

获取房间数据

遍历LobbyPlayerList.Instance.Players,并生成房间代码,保存到RoomData对象。

public static RoomData getRoomData()
{
    RoomData roomData = new RoomData();
    if (LobbyPlayerList.Instance != null && LobbyPlayerList.Instance.Players != null
        && LobbyPlayerList.Instance.Players.Count != 0)
    {
        List<LobbyPlayerInfo> players = LobbyPlayerList.Instance.Players;
        roomData.PlayerList = new List<Player>();
        for (int i = 0; i < players.Count; i++)
        {
            LobbyPlayerInfo lobbyPlayerInfo = players[i];
            string name = lobbyPlayerInfo.Name;
            ulong steamID = lobbyPlayerInfo.SteamID.m_SteamID;
            if (i == 0)
            {
                roomData.RoomCode = CodeEncoder.codeEncode(steamID);
            }
            roomData.PlayerList.Add(new Player(name, steamID));
        }
    }
    return roomData;
}
自动保存房间数据

hook LobbyPlayerInfo.UserCode_RpcGetCountdown函数,判断是否需要保存房间数据到文件。

public static void UserCode_RpcGetCountdownReplace(byte s)
    {
        // 调用原函数
        UserCode_RpcGetCountdownProxy(s);
        // 如果距离上一次调用该函数超过10秒,获取房间数据并保存到文件
        if (Time.realtimeSinceStartup - lastCallTime > 10)
        {
            RoomUtil.saveRoomData();
            LobbyChat.Instance.ReceiveChat("<color=#778899>[DAGHistory]: Saved</color>");
            lastCallTime = Time.realtimeSinceStartup;
        }
    }
加入房间

分析发现该功能最终调用的是LobbyManager.s_Singleton.Join函数,传入steamID,照搬即可。

public static void joinRoom(string code)
{
    if (code != null)
    {
        LobbyManager.s_Singleton.Join(CodeEncoder.codeDecode(code).ToString());
    }
}
获取日志路径

参考LogFileOpener.ReturnLogPath函数,编写以下代码(将默认值替换为临时文件夹)。

private static string ReturnLogPath()
{
    RuntimePlatform platform = Application.platform;
    switch (platform)
    {
        case RuntimePlatform.OSXEditor:
        case RuntimePlatform.OSXPlayer:
            return "~/Library/Logs/Unity/";
        case RuntimePlatform.LinuxPlayer:
        case RuntimePlatform.LinuxEditor:
            return Path.Combine("~/.config/unity3d", Application.companyName, "Draw_Guess");
        case RuntimePlatform.WindowsPlayer:
            return Path.Combine(Environment.GetEnvironmentVariable("AppData"), "..", "LocalLow",
                Application.companyName, "Draw_Guess");
        default:
            return Path.GetTempPath();
    }
}

点击左下角Logs打开该目录:

GUI

为方便使用,增加图形操作界面,提供手动保存、复制上局房间代码、加入上局房间功能。

并设置按后引号键(esc下方)隐藏该界面。

生成dll文件

Visual Studio中选择生成DAGHistory,得到bin\Debug\DAGHistory.dll

测试

使用SharpMonoInjector
提供的命令行注入工具SharpMonoInjector.Console进行注入

注入dll并调用Load函数

smi.exeSharpMonoInjector.dll、待注入dll放到同一目录下,在该目录执行以下命令:

.\smi.exe -p "Draw&Guess" -a "DAGHistory.dll" -n "DAGHistory" -c "Loader" -m "Load" inject

之后使用相关功能即可。

查看日志

点击游戏左下角Logs打开日志目录(Windows:C:\Users\用户名\AppData\LocalLow\Acureus\Draw_Guess
其中LastRoom.jsonDAGHistory.log即注入模块生成的日志文件。
前者保存上次游玩的房间数据(用于复制代码及快速加入),后者保存历史记录。

免费评分

参与人数 4吾爱币 +4 热心值 +4 收起 理由
weiye588 + 1 + 1 我很赞同!
小白来袭2 + 1 + 1 我很赞同!
呜呜啊哎 + 1 + 1 谢谢@Thanks!
ogli324 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

smallfiveya 发表于 2021-8-2 17:35
学习学习,语言也刚好会~
ogli324 发表于 2021-8-2 17:04
zhangchong5522 发表于 2021-8-2 17:17
我也是做Unity开发的。学习学习,语言也刚好会~
sun12345 发表于 2021-8-2 20:08
学到了,受益匪浅
靓仔小黄 发表于 2021-8-3 04:18
语言是Java的么?好厉害啊!大佬
我也想学习相关知识,菜鸟上可以学不?
darkfeast 发表于 2021-8-3 08:23
直接帮官方软件修好了, 补丁大佬~ +1     
大佬  有破解il2cpp方式的吗
apie 发表于 2021-8-3 11:17
官方:来来来,你来开发游戏,我把源码给你?……什么,不用源码直接修复好了?
apie 发表于 2021-8-3 11:18
靓仔小黄 发表于 2021-8-3 04:18
语言是Java的么?好厉害啊!大佬
我也想学习相关知识,菜鸟上可以学不?

语言是C#,需要学习Unity相关知识,C#和Java很像
头像被屏蔽
Wits 发表于 2021-8-3 15:43
我感觉官方得把你收进去,才更好,不然浪费人才了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 12:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表