草庐IT

记录使用 Lib.Harmony 时注入过程中遇到的一个坑

KangKangLoveS 2023-04-10 原文

1. 从这里开始

在一个无聊的下午,我偶然看到某个 Terraria 群里在谈论着一个叫 TerrariaHelper(自动钓鱼) 的软件,顿时有了兴致。我之前粗略研究过 FruitY(某个直接修改 Terraria 程序集代码而做出来的魔改端(作弊端),功能丰富) 的实现代码,但是今天看到的是一个独立的 Helper 程序,没有修改 Terraria 的源文件,也包含了自瞄,自动回血等等功能,不禁让人好奇这些功能是如何实现的。

我一开始猜想这可能类似于 CE(Cheat Engine) 那样,从内存的层面上去修改一些东西(感觉很复杂,没有研究过)。直到我偷偷地把它拖进了 dnSpy(一个反编译软件),短暂地分析代码后,得出的结果让我大吃一惊——居然使用了 Lib.Harmony(我接触过 Lib.Harmony 但是我只知道它是用来制作 Unity 游戏的 Mod 的),然后我才意识到 Lib.Harmony 功能强大,不仅仅是做 Unity 游戏 Mod 的工具。

2. 阅读 Lib.Harmony 的文档

因为 Lib.Harmony 没有多少中文资料(宵夜97制作过一系列教程),我们还是从看它的官网文档开始!

Harmony 2

Introduction

Harmony gives you an elegant(优雅的) and high level way to alter functionality(实用地更改) in applications written in C#. It does this at runtime by monkey patching methods unlike other solutions that change the content of dll files.

It supports Mono and .NET environments on Windows, Unix and macOS except when Unity uses the stripped down NetStandard profile (.NET 4.x profile works fine). Harmony is used in mainstream Unity games and many other applications.(不仅仅用于 Unity 游戏,还有其他的应用程序)

Designed to be used by multiple users (usually called Mods) that would otherwise override each others hooks, it was originally created for the game RimWorld and its large modding community by Andreas Pardeike.

Enjoy!
/Andreas Pardeike

Introduction

Harmony - a library for patching, replacing and decorating .NET methods during runtime.

上面的介绍提到了 Lib.Harmony 是一个跨平台的适用于 Mono 和 .NET 运行时环境的在程序运行时对方法进行 修补(patch),替换(replace) 和 装饰(decorate) 的类库。同时,尽管 Lib.Harmony 最初是为 RimWorld 而创造 且现在大多用于 Unity 游戏的 Mod 创作,但是它仍然适用于其他的符合要求的 .NET 应用程序。

Bootstrapping and Injection

Harmony does not provide you with a way to run your own code within an application that is not designed to execute foreign code. You need a way to inject at least the few lines that start the Harmony patching and this is usually done with a loader. Here are some common examples of loaders (incomplete):

You need to find your own injection method or choose a game that supports user dll loading (usually called Mods) like for example RimWorld (Wiki).

上面提到,Lib.Harmony 不是用被设计来在应用程序里面执行外来的代码,所以它没有给你在别的程序里面运行你的代码的方法。而且你需要把代码通过加载器(注入器)注入目标程序才能让它们开始工作。

上面列举了几个加载器,但是很遗憾,他们都是针对 Mono 运行时的(就是 Unity 程序使用的运行时环境)。所以如果要在其他的应用程序里面使用 Lib.Harmony,需要寻找或者自己做一个注入器。( 注入这个步骤我遇到了第一个坑 )

Adding using nuget

To add Harmony manually to your Visual Studio project, you right-click on References in your solution explorer and choose Manage NuGet Packages, then search for “Harmony Library” and install it.

Import

Once you reference Harmony correctly, you should be able to import it by adding Harmony to your imports. That gives you code completion so you can discover the API:

using HarmonyLib;

Creating a Harmony instance

Most patch operations require a Harmony instance. To instantiate Harmony, you simply call

var harmony = new Harmony("com.company.project.product");

The id should be in reverse domain notation and must be unique. In order to understand and react on existing patches of others, all patches in Harmony are bound to that id. This allows other authors to execute their patches before or after a specific patch by referring to this id.

Patching using annotations

If you prefer annotations to organize your patches, you instruct Harmony to search for them by using PatchAll():

var assembly = Assembly.GetExecutingAssembly();
harmony.PatchAll(assembly);

// or implying current assembly:
harmony.PatchAll();

which will search the given assembly for all classes that are decorated with Harmony annotations. All patches are registered automatically and Harmony will do the rest.

通过上面的教程(或者宵夜97的教程)可以写出 Lib.Harmony 的"初始化"代码

using System;
using System.Windows.Froms; // 我们可以用 MessageBox 来看看有没有成功载入
using HarmonyLib;

namespace MyPatch {
    public static class EndPoint {
        public static void Start() {
            var harmony = new Harmony("my.patch");
            harmony.PatchAll();
            MessageBox.Show("成功注入!");
        }
    }
}

似乎一切都没问题,现在寻找注入的方法。我还是参照了 TerrariaHelper 的方式,引用 FastWin32 包,调用里面的 InjectManaged 方法来进行注入。

bool Injector.InjectManaged(uint processId, string assemblyPath, string typeName, string methodName, string argument)

参数说明:

  • processId:目标进程的进程id ->pid
  • assemblyPath:核心Hook 注入的dll 绝对路径
  • typeName:Hook 初始化方法的命名空间,一般注入一个模块dll后需要执行的入口初始化方法,这里是Hook 核心dll 中的HookService.Start 方法的命名空间(Jlion.Process.HookCore.HookService)
  • methodName : 注入后执行的方法名称
  • argument : 方法所需要的参数

当我生成好要注入的 dll 和简易的注入器后,打开了一个测试的程序进行注入,Injector.InjectManaged 方法返回了 true。意思是注入成功了,但是却没有弹出“成功注入”的消息框。我起初以为是注入器的问题(因为 FastWin32 这个库比较久远,是在 .NET Framework 上面开发的,甚至显示不兼容 .NET Framework 4.8,而我的简易注入器是创建的 .NET 6 的项目),于是我又新建了一个 .NET Framework 的项目,但是问题没有解决。

我又感觉可能是 FastWin32 太久远了,或许 Windows11 平台上面不兼容它。所以我在 GitHub 上面找了的一款现成的注入器

ManagedInjector (正是这个注入器帮我解决了问题)。

当我用这个注入器选择我生成的 dll 时,列表里竟空无一物,无法选择我的 Start 方法进行注入。

我这才意识到可能是我的 dll 出了问题。

[经过了漫长的尝试]

我发现了一个规律,只有当方法的返回值为 int 类型,且参数有且只有一个 string 类型变量时,才能成功被注入。

using System;
using System.Windows.Froms; // 我们可以用 MessageBox 来看看有没有成功载入
using HarmonyLib;

namespace MyPatch {
    public static class EndPoint {
        public static int Start(string str) { // void -> int, () -> (string str)
            var harmony = new Harmony("my.patch");
            harmony.PatchAll();
            MessageBox.Show("成功注入!");
            return 0;
        }
    }
}

将代码改成上面这样后,终于注入成功,弹出了"成功注入"的消息框!

有关记录使用 Lib.Harmony 时注入过程中遇到的一个坑的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐