[英]Replace references to a type/namespace using Mono.Cecil
I'm using the free version of Unity3D for Mobile and it doesn't allow me to use the System.Net.Sockets
namespace on mobile devices. 我正在使用Unity3D for Mobile的免费版本,它不允许我在移动设备上使用System.Net.Sockets
命名空间。 The problem is that I'm using a compiled .dll
library (namely, IKVM) that references the System.Net.Sockets
. 问题是我正在使用引用System.Net.Sockets
已编译的.dll
库(即IKVM)。 I'm not actually using the classes in IKVM that references that references System.Net.Sockets
, so instead of buying the $3000 Unity Pro mobile licenses, I made a stub library of the Sockets
namespace called dudeprgm.Net.Sockets
that just replaces all the classes and methods with stubs (I did this using the Mono source code). 我实际上并没有使用引用System.Net.Sockets
IKVM中的类,因此我没有购买3000美元的Unity Pro移动许可证,而是创建了一个名为dudeprgm.Net.Sockets
的Sockets
命名空间的存根库, dudeprgm.Net.Sockets
替换了所有存根的类和方法(我使用Mono源代码完成此操作)。
I need to replace all System.Net.Sockets.*
references in my dlls to dudeprgm.Net.Sockets.*
. 我需要将dll中的所有System.Net.Sockets.*
引用替换为dudeprgm.Net.Sockets.*
。 I know that something like this is possible and done by other people (See EDIT below, at the bottom of the page) . 我知道这样的事情是可能的并且由其他人完成 (参见下面的编辑 ,在页面底部) 。 I would like to know how to do it myself. 我想知道自己该怎么做。
I was able to come up with the following code using Mono.Cecil. 我能够使用Mono.Cecil提出以下代码。
It goes through all the IL instructions, checks if the operand is an InlineType
, then checks if the inline type is part of System.Net.Sockets
, then renames it to dudeprgm.Net.Sockets
and writes it. 它遍历所有IL指令,检查操作数是否为InlineType
,然后检查内联类型是否是System.Net.Sockets
一部分,然后将其重命名为dudeprgm.Net.Sockets
并写入。 **I'm not sure if this is the right way to go about "finding-and-replacing" in Mono.Cecil. **我不确定这是否是在Mono.Cecil中进行“查找和替换”的正确方法。 Problem is, this doesn't catch all Sockets
usages (see below). 问题是,这并没有捕获所有Sockets
使用(见下文)。
private static AssemblyDefinition stubsAssembly;
static void Main(string[] args) {
AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(args[0]);
stubsAssembly = AssemblyDefinition.ReadAssembly("Socket Stubs.dll");
// ...
// Call ProcessSockets on everything
// ...
asm.Write(args[1]);
}
/*
* This will be run on every property, constructor and method in the entire dll given
*/
private static void ProcessSockets(MethodDefinition method) {
if (method.HasBody) {
Mono.Collections.Generic.Collection<Instruction> instructions = method.Body.Instructions;
for (int i = 0; i < instructions.Count; i++) {
Instruction instruction = instructions[i];
if (instruction.OpCode.OperandType == OperandType.InlineType) {
string operand = instruction.Operand.ToString();
if (operand.StartsWith("System.Net.Sockets")) {
Console.WriteLine(method.DeclaringType + "." + method.Name + "(...) uses type " + operand);
Console.WriteLine("\t(Instruction: " + instruction.OpCode.ToString() + " " + instruction.Operand.ToString() + ")");
instruction.Operand = method.Module.Import(stubsAssembly.MainModule.GetType("dudeprgm.Net.Sockets", operand.Substring(19)));
Console.WriteLine("\tReplaced with type " + "dudeprgm.Net.Sockets" + operand.Substring(18));
}
}
}
}
}
It works fine, but only catches "simple" instructions. 它工作正常,但只捕获“简单”指令。 Decompiled with ildasm
, I can see where it replaced the types like here: 用ildasm
反编译,我可以看到它取代了这里的类型:
box ['Socket Stubs'/*23000009*/]dudeprgm.Net.Sockets.SocketOptionLevel/*01000058*/
But it didn't catch these "complex" instructions: 但它没有抓住这些“复杂”的指示:
callvirt instance void [System/*23000003*/]System.Net.Sockets.Socket/*0100003F*/::SetSocketOption(valuetype [System/*23000003*/]System.Net.Sockets.SocketOptionLevel/*01000055*/,
valuetype [System/*23000003*/]System.Net.Sockets.SocketOptionName/*01000056*/,
int32) /* 0A000094 */
Now the .dll
s are a jumble of dudeprgm.Net.Sockets
and System.Net.Sockets
references. 现在.dll
是dudeprgm.Net.Sockets
和System.Net.Sockets
引用的混乱。
I'm pretty sure that this is happening because I'm only changing OperandType.InlineType
s, but I'm not sure on how else to do this. 我很确定这种情况正在发生,因为我只是在改变OperandType.InlineType
,但我不确定如何做到这一点。 I've tried looking around everywhere, but it seems to me like Mono.Cecil has no way to set operands to a string
, everything seems to have to be done using the Cecil API only ( https://stackoverflow.com/a/7215711/837703 ). 我试过到处寻找,但在我看来,像Mono.Cecil无法将操作数设置为string
,似乎只需要使用Cecil API完成所有操作( https://stackoverflow.com/a/ 7215711/837703 )。
(Sorry if I'm using incorrect terms, I'm pretty new to IL in general.) (对不起,如果我使用的是不正确的条款,我对IL一般都是新手。)
How can I replace all places where System.Net.Sockets
appear in Mono.Cecil, rather than just where the operand is an InlineType
? 如何替换System.Net.Sockets
出现在Mono.Cecil中的所有位置,而不仅仅是操作数是InlineType
? I don't really want to go through every OperandType
there is in Cecil, I was just looking for some find-and-replace method in Cecil where I wouldn't have to work with plain IL myself. 我真的不想查看Cecil中的每一个OperandType
,我只是在Cecil中寻找一些查找和替换方法,我不必自己使用普通的IL。
EDIT: (also unnecessary, confusing, and only for the curious) 编辑:(也没必要,令人困惑,只为好奇)
This person was able to do something similar for $25: http://www.reddit.com/r/Unity3D/comments/1xq516/good_ol_sockets_net_sockets_for_mobile_without/ . 这个人能够以25美元的价格做类似的事情: http : //www.reddit.com/r/Unity3D/comments/1xq516/good_ol_sockets_net_sockets_for_mobile_without/ 。
Automatic patcher tool that detects and fixes socket usage in scripts and .dll. 自动修补程序工具,用于检测和修复脚本和.dll中的套接字使用情况。
... ...
"DLL's are patched using Mono.Cecil. ... “DLL使用Mono.Cecil进行修补。...
You can go look at the second screenshot at https://www.assetstore.unity3d.com/en/#!/content/13166 and see that it says that it can replace namespaces. 您可以查看https://www.assetstore.unity3d.com/en/#!/content/13166上的第二个屏幕截图,看看它是否可以替换命名空间。
That library doesn't fit my needs, because 1) it doesn't rename to the namespace I want ( dudeprgm.Net.Sockets
), 2) the library that it is renaming to does not support all the System.Net.Sockets
classes that IKVM needs, because IKVM uses pretty much every Sockets class and 3) it costs $25 and I don't really want to buy something that I'm not going to use. 该库不符合我的需求,因为1)它不会重命名为我想要的命名空间( dudeprgm.Net.Sockets
),2)它重命名的库不支持所有的System.Net.Sockets
类IKVM需要,因为IKVM几乎使用每个Sockets类而且3)它的成本是25美元而且我真的不想购买我不会使用的东西。 I just wanted to show that replacing namespace/type references in Mono.Cecil is possible. 我只想表明在Mono.Cecil中替换名称空间/类型引用是可能的。
Your problem with replacing references to a dll (and types within) with another dll (and types within) is technically similar to problem known as 用另一个dll(和其中的类型)替换对dll(和其中的类型)的引用的问题在技术上类似于已知的问题
Google: "c# add strong name to 3rd party assembly" 谷歌:“c#为第三方组装添加强名”
In this problem you want to have your application signed by strong name and possibly installed into GAC or Ngen-ed, but your application depends on a legacy 3rd party library which does not have a strong name added at compile time, which breaks the requirement saying that a strong named assembly can use only strong named assemblies. 在这个问题中,您希望您的应用程序使用强名称进行签名,并且可能已安装到GAC或Ngen-ed中,但您的应用程序依赖于在编译时未添加强名称的旧版第三方库,这违反了要求强命名程序集只能使用强命名程序集。 You don't have source code for the 3rd party library, only binaries, so you can't recompile it (== "simplified description") 您没有第三方库的源代码,只有二进制文件,因此您无法重新编译它(==“简化描述”)
There are several solutions possible, 3 most typical being: 有几种可能的解决方案,最常见的是3种:
You can use ildasm
/ ilasm
round trip
, convert all binaries into text form, change all references into their strong name equivalents (recursively) and turn text back into code. 您可以使用ildasm
/ ilasm
round trip
,将所有二进制文件转换为文本形式,将所有引用更改为强名称等效(递归)并将文本转换回代码。 Examples: http://buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/ and https://stackoverflow.com/a/6546134/2626313 示例: http : //buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/和https://stackoverflow.com/a/6546134/2626313
You can use tools already written to solve exactly this problem, example: http://brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer 您可以使用已编写的工具来解决此问题,例如: http : //brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer
You can create a tool crafted to match your exact needs. 您可以创建一个精心设计的工具来满足您的确切需求。 It is possible, I have done it, it took several weeks and the code weighs several thousand lines of code. 有可能,我已经完成了,花了几个星期,代码重达几千行代码。 For the most dirty work I have reused (with some slight modifications) mainly source code from (unordered): 对于我已经重复使用的最脏的工作(稍作修改)主要来自(无序)源代码:
Although the problem you have described looks like just a subset of what I'm describing above, it may well turn out to be the same problem if you want to use GAC installation which in turn requires strong name signing. 虽然您所描述的问题看起来只是我上面描述的问题的一部分,但如果您想使用GAC安装,则可能会出现同样的问题,而GAC安装又需要强名称签名。
My recommendation for you is 我给你的建议是
Give the easiest solution [02] a tryand to get into least trouble use ilasm/ildasm tools from the Mono package not the ones provided by Microsoft's .NET Framework (Microsoft's Resgen in .NET Framework 4.5 is broken and cannot round-trip resx format, Ildasm output does not handle non-ASCII characters correctly etc. While you can't fix Microsoft's broken closed source, you can fix Mono's open source but I did not have to.) 提供最简单的解决方案[02]尝试使用Mono软件包中的ilasm / ildasm工具,而不是微软.NET Framework提供的工具(.NET Framework 4.5中的Microsoft Resgen已损坏且不能往返resx格式, Ildasm输出不能正确处理非ASCII字符等。虽然你无法修复微软破解的闭源,你可以修复Mono的开源,但我不需要。)
If [06] does not work for you then study (debug) → ILSpy ← and study Mono documentation for various command line tools doing what you need and their sources - you'll see how exactly they use the Mono.Cecil library 如果[06]不适合你,那么学习(调试)→ ILSpy ←并研究各种命令行工具的Mono文档,做你需要的东西及其来源 - 你会看到他们如何使用Mono.Cecil库
If you face the need to validate strong named or even signed assemblies (tampering them will invalidate the signatures) or remove the signatures etc. You are going to dive into code longer than a simple Stack Overflow answer can describe. 如果您需要验证强名称或甚至签名的程序集(篡改它们将使签名无效)或删除签名等。您将深入研究代码的时间超过简单的Stack Overflow答案可以描述的内容。
Lurking around what ILMerge does and how can point you to an easier solution 潜伏在ILMerge的周围,以及如何为您提供更简单的解决方案
Another easier solution might be (if IKVM supports it) hooking the AssemblyResolve
event where you can remap dll name into physical dll, loaded eg From totally different file or from a resource stream etc. As shown in several answers of older Stack Overflow question Embedding DLLs in a compiled executable 另一个更简单的解决方案可能是(如果IKVM支持它)挂钩AssemblyResolve
事件,您可以将dll名称重新映射到物理dll,例如从完全不同的文件或资源流等加载。如旧堆栈溢出问题嵌入DLL的几个答案中所示在已编译的可执行文件
(EDIT #1: after comments) (编辑#1:评论后)
If your more or less general question actually boils down into "How can I make IKVM.dll to use my socket classes instead of those from namespace System.Net.Sockets" then quite straightforward solution might be: 如果您或多或少的一般性问题实际上归结为“如何使IKVM.dll使用我的套接字类而不是命名空间System.Net.Sockets中的那些”,那么非常简单的解决方案可能是:
Compile and deploy your own customized version of IKVM.dll using source code available at http://www.ikvm.net/download.html - no binary Mono.Cecil magic needed. 使用http://www.ikvm.net/download.html上提供的源代码编译和部署您自己的自定义版本的IKVM.dll - 不需要二进制Mono.Cecil魔术。
As all code is open it should be possible to find and redirect all references pointing to namespace System.Net
into dudeprgm.Net
by 由于所有代码都是打开的,因此应该可以找到并将指向命名空间System.Net
所有引用重定向到dudeprgm.Net
dudeprgm.Net.cs
project to the solution [10.2]将dudeprgm.Net.cs
项目添加到解决方案中 using System.Net
[10.3]在所有源文件中查找并删除所有using System.Net
System.Net
with dudeprgm.Net
[10.4]在所有源文件中,全文通过dudeprgm.Net
查找并替换看起来像System.Net
的dudeprgm.Net
(EDIT #2: after comments) (编辑#2:评论后)
If you choose track [04] and working with text files and ilasm/ildasm
tools (style [02] ) would not seem productive then below is the key relevant part of my automatic strong name signer which changes assembly references into other references using Mono.Cecil. 如果您选择track [04]并使用文本文件和ilasm/ildasm
工具(样式[02] )似乎效率不高,那么下面是我的自动强名称签名者的关键相关部分,它使用Mono将程序集引用更改为其他引用。塞西尔。 The code is pasted as is (without lines of code before, after and all around) in a form that works for me. 代码按原样粘贴(不含代码行,之前,之后和所有代码),以适合我的形式。 Reading keys: a is Mono.Cecil.AssemblyDefinition
, b implements Mono.Cecil.IAssemblyResolver
, key method in b
instance is the method AssemblyDefinition Resolve(AssemblyNameReference name)
which translates required DLL name into call to AssemblyDefinition.ReadAssembly(..)
. 读取键: a is Mono.Cecil.AssemblyDefinition
, b implements Mono.Cecil.IAssemblyResolver
, b
实例中的键方法是AssemblyDefinition Resolve(AssemblyNameReference name)
,它将所需的DLL名称转换为对AssemblyDefinition.ReadAssembly(..)
调用。 I did not need to parse the instruction stream, remapping assembly references was enough (I can paste here few other pieces from my code if needed) 我不需要解析指令流,重新映射汇编引用就足够了(如果需要,我可以在这里粘贴我的代码中的其他几个部分)
/// <summary>
/// Fixes references in assembly pointing to other assemblies to make their PublicKeyToken-s compatible. Returns true if some changes were made.
/// <para>Inspiration comes from https://github.com/brutaldev/StrongNameSigner/blob/master/src/Brutal.Dev.StrongNameSigner.UI/MainForm.cs
/// see call to SigningHelper.FixAssemblyReference
/// </para>
/// </summary>
public static bool FixStrongNameReferences(IEngine engine, string assemblyFile, string keyFile, string password)
{
var modified = false;
assemblyFile = Path.GetFullPath(assemblyFile);
var assemblyHasStrongName = GetAssemblyInfo(assemblyFile, AssemblyInfoFlags.Read_StrongNameStatus)
.StrongNameStatus == StrongNameStatus.Present;
using (var handle = new AssemblyHandle(engine, assemblyFile))
{
AssemblyDefinition a;
var resolver = handle.GetAssemblyResolver();
a = handle.AssemblyDefinition;
foreach (var reference in a.MainModule.AssemblyReferences)
{
var b = resolver.Resolve(reference);
if (b != null)
{
// Found a matching reference, let's set the public key token.
if (BitConverter.ToString(reference.PublicKeyToken) != BitConverter.ToString(b.Name.PublicKeyToken))
{
reference.PublicKeyToken = b.Name.PublicKeyToken ?? new byte[0];
modified = true;
}
}
}
foreach (var resource in a.MainModule.Resources.ToList())
{
var er = resource as EmbeddedResource;
if (er != null && er.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
{
using (var targetStream = new MemoryStream())
{
bool resourceModified = false;
using (var sourceStream = er.GetResourceStream())
{
using (System.Resources.IResourceReader reader = new System.Resources.ResourceReader(sourceStream))
{
using (var writer = new System.Resources.ResourceWriter(targetStream))
{
foreach (DictionaryEntry entry in reader)
{
var key = (string)entry.Key;
if (entry.Value is string)
{
writer.AddResource(key, (string)entry.Value);
}
else
{
if (key.EndsWith(".baml", StringComparison.OrdinalIgnoreCase) && entry.Value is Stream)
{
Stream newBamlStream = null;
if (FixStrongNameReferences(handle, (Stream)entry.Value, ref newBamlStream))
{
writer.AddResource(key, newBamlStream, closeAfterWrite: true);
resourceModified = true;
}
else
{
writer.AddResource(key, entry.Value);
}
}
else
{
writer.AddResource(key, entry.Value);
}
}
}
}
}
if (resourceModified)
{
targetStream.Flush();
// I'll swap new resource instead of the old one
a.MainModule.Resources.Remove(resource);
a.MainModule.Resources.Add(new EmbeddedResource(er.Name, resource.Attributes, targetStream.ToArray()));
modified = true;
}
}
}
}
}
if (modified)
{
string backupFile = SigningHelper.GetTemporaryFile(assemblyFile, 1);
// Make a backup before overwriting.
File.Copy(assemblyFile, backupFile, true);
try
{
try
{
AssemblyResolver.RunDefaultAssemblyResolver(Path.GetDirectoryName(assemblyFile), () => {
// remove previous strong name https://groups.google.com/forum/#!topic/mono-cecil/5If6OnZCpWo
a.Name.HasPublicKey = false;
a.Name.PublicKey = new byte[0];
a.MainModule.Attributes &= ~ModuleAttributes.StrongNameSigned;
a.Write(assemblyFile);
});
if (assemblyHasStrongName)
{
SigningHelper.SignAssembly(assemblyFile, keyFile, null, password);
}
}
catch (Exception)
{
// Restore the backup if something goes wrong.
File.Copy(backupFile, assemblyFile, true);
throw;
}
}
finally
{
File.Delete(backupFile);
}
}
}
return modified;
}
This is actually meant to be an extension to @xmojmer's answer. 这实际上是@ xmojmer答案的延伸。
I wrote a small bash
script to automate xmojmer's option [02] : 我写了一个小的bash
脚本来自动化xmojmer的选项[02] :
# vsilscript.sh
# Usage:
# Open Cygwin
# . vsilscript.sh <original .dll to patch>
# output in "Sockets_Patched" subdirectory
nodosfilewarning=true # just in case cygwin complains
ILASM_PATH="/cygdrive/c/Windows/Microsoft.NET/Framework64/" # modify for your needs
ILASM_PATH="$ILASM_PATH"$(ls "$ILASM_PATH" -v | tail -n 1)
ILDASM_PATH="/cygdrive/c/Program Files (x86)/Microsoft SDKs/Windows/v8.1A/bin/NETFX 4.5.1 Tools" # modify for your needs
PATH="$ILDASM_PATH:$ILASM_PATH:$PATH"
base=$(echo $1 | sed "s/\.dll//g")
ildasm /out=$base".il" /all /typelist $base".dll"
cp $base".il" "tempdisassembled.il"
cat "tempdisassembled.il" | awk '
BEGIN { print ".assembly extern socketstubs { }"; }
{ gsub(/\[System[^\]]*\]System\.Net\.Sockets/, "[socketstubs]dudeprgm.Net.Sockets", $0); print $0; }
END { print "\n"; }
' 1> $base".il" #change the awk program to swap out different types
rm "tempdisassembled.il"
mkdir "Sockets_Patched"
# read -p "Press Enter to assemble..."
ilasm /output:"Sockets_Patched/"$base".dll" /dll /resource:$base".res" $base".il"
Modify the ILASM_PATH
and ILDASM_PATH
variables if your computer is 32bit or have ilasm
and ildasm
on different locations. 如果您的计算机是32位或在不同位置具有ilasm
和ildasm
,请修改ILASM_PATH
和ILDASM_PATH
变量。 This script assumes you are using Cygwin on Windows. 此脚本假定您在Windows上使用Cygwin 。
The modification happens in the awk
command. 修改发生在awk
命令中。 It adds a reference to my library of stubs (called socketstubs.dll
, and is stored in the same directory.) by adding 它通过添加添加对我的存根库(称为socketstubs.dll
,并存储在同一目录中)的引用。
.assembly extern sockectstubs { }
at the beginning of the disassembled IL code. 在反汇编的IL代码的开头。 Then, for each line, it looks for [System]System.Net.Sockets
and replaces them with [socketstubs]dudeprgm.Net.Sockets
. 然后,对于每一行,它查找[System]System.Net.Sockets
并用[socketstubs]dudeprgm.Net.Sockets
替换它们。 It adds a newline at the end of the IL code, or else ilasm
won't re-assemble it, as per the docs : 它在IL代码的末尾添加了换行符,否则ilasm
将不会根据文档重新组合它:
Compilation might fail if the last line of code in the .il source file does not have either trailing white space or an end-of-line character. 如果.il源文件中的最后一行代码没有尾随空格或行尾字符,则编译可能会失败。
The final patched .dll will be placed in a new directory called "Sockets_Patched". 最终修补的.dll将放在一个名为“Sockets_Patched”的新目录中。
You can modify this script to swap any two namespaces in any dll: 您可以修改此脚本以交换任何dll中的任何两个名称空间:
System
in [System[^\\]]
to whatever assembly contains your old namespace 将[System[^\\]]
的System
替换为包含旧命名空间的任何程序集 System\\.Net\\.Sockets
with your old namespace and put a backslash in front of every period 用您的旧命名空间替换System\\.Net\\.Sockets
并在每个句点前放置一个反斜杠 socketstubs
in the script with your library containing the new namespace 将脚本中的所有socketstubs
替换为包含新命名空间的库 dudeprgm.Net.Sockets
with your new namespace 用您的新命名空间替换dudeprgm.Net.Sockets
You could wrap the sockets objects in an interface and then dependency inject the implementation you really want. 您可以将套接字对象包装在一个接口中,然后依赖注入您真正想要的实现。 The code might be a little tedious, but you can swap out the sockets to anything you want after that without directly referencing System.Net.Sockets 代码可能有点乏味,但您可以在不直接引用System.Net.Sockets的情况下将套接字交换到您想要的任何内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.