吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4221|回复: 17
收起左侧

[其他原创] 使用BepInEx修改unity3d游戏(1)——初识

[复制链接]
艾莉希雅 发表于 2023-8-25 16:46
本帖最后由 艾莉希雅 于 2023-8-25 17:56 编辑

前言说明

本篇除判断游戏类型一节中其余内容演示均围绕以下脱敏名称
JTVCUkowMTAwMjk4OCU1RCV1MzBCQiV1MzBBNCV1MzBGMyV1MzBDOCV1MzBBRSV1MzBBMiV1MzBENSV1MzBBOSV1MzBGQyV1MzBCOSUyOCV1NEUyRCV1NjU4NyV1NTQwRCV1RkYxQSV1NTcyMyV1OUY3RiV1OEY2RSV1OTBFOCV1OTYxRiUyOSUyOCV1ODJGMSV1NjU4NyV1NTQwRCV1RkYxQVNhaW50R2VhckZvcmNlJTI5
本文建立在您已经分析清楚该游戏的修改位置的情况下,侧重点是BepInEx的使用
如果着重于分析游戏那这一系列文章可能就变成《如何修改S什么什么e》了
当前互联网中修改unity游戏很多都是直接修改Assembly-CSharp.dll或者是GameAssembly.dll
在游戏更新后重复工作量较大,如果使用BepInEx可以显著降低工作量
接下来将会以几个常见的修改方式,作为第一篇的演示例子
20230825161401.png

判断游戏类型

判断游戏架构

这个没什么好说的,拿工具扫一下就知道了
20230825141909.png
如图所示,很清楚的写明了是amd64

判断runtime

首先你要知道,你要动手的游戏究竟是什么runtime
目前unity主要有il2cpp及mono两种,其他wasm之类的用的比较少的不在BepInEx中介绍与演示
20230825140243.png
20230825140500.png
如图所示,以上图片有带有MonoBleedingEdge可以判断为mono运行时,而带有是il2cpp运行时
这个方法能大致判断目标应用的runtime,部分应用可能会隐藏相关特征
本篇我们以游戏SaintGearForce为例
除了MonoBleedingEdge 我们发现SaintGearForce_Data\Managed\Assembly-CSharp.dll因此这个想必就是mono

判断unity版本

其实这个很好判断的,用文本编辑器打开游戏Data目录下的globalgamemanagers
20230825141611.png
可以很清晰的看见版本2021.3.11

安装BepInEx

首先我们去github下载压缩包
20230825142012.png
如图所示,有几个不同的zip,我们本次练习的目标是amd64的,因此选择BepInEx_x64_5.4.21.0.zip下载
下载完成后解压里面的文件到游戏目录
20230825142710.png
然后,打开游戏后直接关闭游戏,让BepInEx自行生成相关文件
随后编辑BepInEx的配置,开启调试日志窗口
20230825142822.png
保存配置后,再次打开游戏就可以见到日志窗口了
20230825143434.png

编写Plugin

创建plugin

首先我们要加载模板

dotnet new -i BepInEx.Templates --nuget-source https://nuget.bepinex.dev/v3/index.json

然后使用模板创建插件,注意这里使用的是bepinex5,因为游戏不是il2cpp没必要冲pre-release

dotnet new bepinex5plugin -n SaintGearForcePlugin -T net472 -U 2021.3.11

之类的-T是插件将要使用的目标运行时,-U是游戏的unity版本
20230825143435.png
创建完后我们cd至创建的插件目录下,把使用如下命令把Harmony安装上

dotnet add package HarmonyX --version 2.10.1

添加游戏本体至依赖

如图所示,复制游戏中的Assembly-CSharp.dll至插件目录下,并添加至依赖
20230825145222.png
这个步骤我觉得应该不用细说吧,就复制个文件然后添加依赖

编译测试

20230825145510.png
正常编译插件即可,图中的代码是模板生成的,自带一个日志,可以很方便的让我们看出插件加载没加载。
20230825145553.png
把构建的插件,放到BepInEx\plugins目录下启动游戏
20230825145913.png
可以看见,插件被加载成功,说明我们的环境并没有什么问题。

日志

这里不得不说一句,日志是一个很重要的东西,它关系着你能不能舒适的编写插件。
此处我们演示新增日志tag及开启HarmonyFileLog
20230825150415.png
如图所示,我们新增并添加了一个叫glog的ManualLogSource,以及设置了HarmonyFileLog.Enabled
20230825150433.png
在使用自行添加的gLog后,日志窗口成功的输出了glog标签的日志

Harmony

为什么这一节叫Harmony,因为这一节已经是Harmony的部分了。

CreateAndPatchAll

Harmony支持多种创建patch的方法,这里图省事,先演示CreateAndPatchAll
20230825151034.png
创建一个class,然后使用Harmony.CreateAndPatchAll,该class就会被应用了

HarmonyPrefix

这个跟Xposed的beforeHookedMethod很相似,也基本上就是这样用的。
它的作用就是在方法执行前进行一些操作,包括但不限于修改入参。
本次以修改该游戏的SP及EP为例进行演示
20230825151035.png
20230825151036.png
从上图我们可以看出,消费SP及EP的method有一个叫consume_amount类型为int的入参
20230825151757.png
那么我们使用如上代码,将入参打印并设置为1
编译插件后,进入游戏进行测试
20230825152310.png
如图所示,使用SP及EP的技能均只花费了1点

HarmonyPostfix

这个跟Xposed的afterHookedMethod很相似,都是在method执行完后进行某些操作,包括但不限于修改返回值。
20230825152549.png
本次以修改游戏加点需要的点数为例。
20230825152550.png
20230825153021.png
如图所示,这里我们希望修改statusGrowUpData.consume_sp及growUpSkillData.amount
20230825153215.png
此处的__result是该框架的一个关键字,在执行完方法后,演示代码会把consume_sp以及amount修改为1
20230825153418.png

HarmonyTranspiler

这玩意不推荐使用。通用性极差,但某些场景下不得不使用它。
这里我们虚构一个场景,就是战斗时HP血量为0时不触发败北逻辑。
先上一段战斗伤害的逻辑
20230825153419.png
让图所示,当HP小于等于0时,败北逻辑触发
虽然我们可以直接把damage_amount直接归0免伤立于不败之地
但为了演示HarmonyTranspiler我们通过消除败北逻辑实现不败
20230825153420.png
打开该方法的IL,如图逻辑所示,我们只需在63处替换为ret即可
20230825154157.png
如上,演示代码会打印codes[63]的opcode,并替换为ret
20230825154548.png
如图所示,HP归零时不会触发败北逻辑,实现立于不败之地的需求。

结束语

本文使用BepInEx实现了几个修改Assembly-CSharp.dll实现的需求
但没有实现通过配置或者是插入UI等方式控制功能的开关,存在某些必败关卡会卡关的问题
想必看完之后你还会对

[HarmonyPatch(typeof(PlayerStatusManeger), nameof(PlayerStatusManeger.Damage_HP))]

之类的地方感到困惑
在后续文章中,将会演示通过配置及显式UI开关的方式控制patch的行为,以及进一步介绍BepInEx与Harmony



[C#] 纯文本查看 复制代码
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using HarmonyLib.Tools;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SaintGearForcePlugin
{
    [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
    public class Plugin : BaseUnityPlugin
    {
        static ManualLogSource gLog = new ManualLogSource("glog");
        private void Awake()
        {
            // Plugin startup logic
            Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
            HarmonyFileLog.Enabled = true;
            BepInEx.Logging.Logger.Sources.Add(gLog);
            gLog.LogInfo("i am g glog");
            Harmony _pluginTriggers = Harmony.CreateAndPatchAll(
                typeof(Triggers)
            );
        }
        private class Triggers
        {
            [HarmonyTranspiler]
            [HarmonyPatch(typeof(PlayerStatusManeger), nameof(PlayerStatusManeger.Damage_HP))]
            static IEnumerable<CodeInstruction> Damage_HP(IEnumerable<CodeInstruction> instructions)
            {
                var codes = new List<CodeInstruction>(instructions);
                gLog.LogInfo("codes[63].opcode " + codes[63].opcode);
                codes[63].opcode = System.Reflection.Emit.OpCodes.Ret;
                return codes.AsEnumerable();
            }
            [HarmonyPostfix]
            [HarmonyPatch(typeof(PlayerGrowUpSystem), "GetStatusGrowUpData")]
            static public void GetStatusGrowUpData_Hook(ref StatusGrowUpData __result)
            {
                __result.consume_sp = 1;
            }
            [HarmonyPostfix]
            [HarmonyPatch(typeof(PlayerGrowUpSystem), "GetGrowUpSkillData")]
            static public void GetGrowUpSkillData_Hook(ref GrowUpSkillData __result)
            {
                __result.amount = 1;
            }
            [HarmonyPrefix]
            [HarmonyPatch(typeof(PlayerStatusManeger), "Consume_EP")]
            [HarmonyPatch(typeof(PlayerStatusManeger), "Consume_SP")]
            static public void Consume_SP_EP(ref int consume_amount)
            {
                gLog.LogInfo("consume_amount" + consume_amount);
                consume_amount = 1;
            }
            //[HarmonyPrefix]
            //[HarmonyPatch(typeof(PlayerStatusManeger), nameof(PlayerStatusManeger.Damage_HP))]
            //static public void Damage_HP_Hook(ref int damage_amount, ref bool acme)
            //{
            //    damage_amount = 1;
            //}
        }
    }
}

免费评分

参与人数 5威望 +1 吾爱币 +24 热心值 +5 收起 理由
HundSimon + 1 + 1 谢谢@Thanks!
CIBao + 1 + 1 谢谢@Thanks!
苏紫方璇 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
XIE小谢 + 1 + 1 谢谢@Thanks!
momosys + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

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

HundSimon 发表于 2024-5-25 18:45
艾莉希雅 发表于 2024-5-24 18:59
看怎么混咯,一般来说dlsite那边的游戏也不会有人混
毕竟混完了出bug都不知道排查,怕不是被炎上

指的是见过其他游戏混淆
类似BeeByte(把方法名 类名和变量名都混淆成了随机字符)
这种游戏我一般都是手改的,偶然看到了这篇帖子,就想着有没有通用的解决方案
但现在看来貌似还是没有能通用的方案
 楼主| 艾莉希雅 发表于 2023-8-25 17:02
xscbelieve 发表于 2023-8-25 17:58
我还是喜欢cheatengine那种形式,或者再简洁一点的

主要是这东西图的就是个减少工作量
20230825170115.png
文中这示例游戏的更版本还真的挺勤快的
xscbelieve 发表于 2023-8-25 16:58
我还是喜欢cheatengine那种形式,或者再简洁一点的
官方萌物丶 发表于 2023-8-25 17:11
太好了  制作Mod 那些就是 用这个方法吧
头像被屏蔽
moruye 发表于 2023-8-25 21:08
提示: 作者被禁止或删除 内容自动屏蔽
头像被屏蔽
moruye 发表于 2023-8-25 21:09
提示: 作者被禁止或删除 内容自动屏蔽
dyc66666 发表于 2023-8-25 21:10
666,学习学习
 楼主| 艾莉希雅 发表于 2023-8-26 13:22
官方萌物丶 发表于 2023-8-25 18:11
太好了  制作Mod 那些就是 用这个方法吧

倒也不全是
zhaoduck 发表于 2023-8-28 16:31
感谢,学习一下
quanshouzhu 发表于 2023-9-18 23:03
这个帖子能更新一下吗?太需要了https://www.52pojie.cn/thread-615210-1-1.html
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-24 10:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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