我正在使用Unity3D for Mobile的免费版本,它不允许我在移动设备上使用System.Net.Sockets
命名空间。问题是我正在使用引用.dll
的编译System.Net.Sockets
库(即IKVM)。我实际上并没有使用引用System.Net.Sockets
的IKVM中的类,因此我没有购买$ 3000 Unity Pro移动许可证,而是创建了一个名为Sockets
的{{1}}命名空间的存根库这只是用存根替换所有类和方法(我使用Mono源代码完成了这一操作)。
我需要将我的dll中的所有dudeprgm.Net.Sockets
引用替换为System.Net.Sockets.*
。 我知道这样的事情是可能的并且由其他人完成 (参见下面的 EDIT ,页面底部)。我想知道如何自己做。
我能够使用Mono.Cecil提出以下代码
它遍历所有IL指令,检查操作数是否为dudeprgm.Net.Sockets.*
,然后检查内联类型是否属于InlineType
,然后将其重命名为System.Net.Sockets
并写入。 **我不确定这是否是在Mono.Cecil中进行“查找和替换”的正确方法。问题是,这并没有抓住所有dudeprgm.Net.Sockets
用法(见下文)。
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));
}
}
}
}
}
反编译,我可以看到它取代了这里的类型:
ildasm
但它没有抓住这些“复杂”的指示:
box ['Socket Stubs'/*23000009*/]dudeprgm.Net.Sockets.SocketOptionLevel/*01000058*/
现在,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 */
混淆了.dll
和dudeprgm.Net.Sockets
个引用。
我很确定这种情况正在发生,因为我只是在改变System.Net.Sockets
,但我不确定如何做到这一点。我试过四处寻找,但在我看来,Mono.Cecil似乎无法将操作数设置为OperandType.InlineType
,似乎必须使用Cecil API完成所有操作(https://stackoverflow.com/a/7215711/837703)
(对不起,如果我使用的是不正确的条款,我对IL一般都是新手。)
如何替换 所有 在Mono.Cecil中出现string
的地方,而不仅仅是操作数为System.Net.Sockets
的地方?我真的不想查看Cecil中的每个InlineType
,我只是在Cecil中寻找一些 find-and-replace 方法,我不需要使用它简单的我自己。
编辑:(也是不必要的,令人困惑的,只有好奇的才能)
这个人能够以25美元的价格做同样的事情:http://www.reddit.com/r/Unity3D/comments/1xq516/good_ol_sockets_net_sockets_for_mobile_without/。
自动修补程序工具,用于检测和修复脚本和.dll中的套接字使用情况。
... 的
“DLL使用Mono.Cecil修补。 ...
您可以查看https://www.assetstore.unity3d.com/en/#!/content/13166处的第二个屏幕截图,看看它是否可以替换名称空间。
该库不符合我的需求,因为1)它不会重命名为我想要的命名空间(OperandType
),2)它重命名的库不支持所有{{1 IKVM需要的类,因为IKVM几乎使用每个Sockets类而且3)它的成本是25美元而且我真的不想购买我不会使用的东西。我只想表明在Mono.Cecil中替换名称空间/类型引用是可能的。
答案 0 :(得分:9)
用另一个dll(以及其中的类型)替换对dll(及其中的类型)的引用的问题在技术上类似于称为
的问题Google:&#34; c#为第三方程序集添加强名称&#34;
在这个问题中,您希望您的应用程序通过强名称签名并可能安装到GAC或Ngen-ed中,但您的应用程序依赖于在编译时未添加强名称的旧版第三方库,要求说强大的命名程序集只能使用强名称程序集。您没有第三方库的源代码,只有二进制文件,因此您无法重新编译它(==&#34;简化说明&#34;)
有几种可能的解决方案,最常见的是:
您可以使用ildasm
/ ilasm
round trip
,将所有二进制文件转换为文本格式,将所有引用更改为强名称等效(递归)并将文本转换回代码。示例:http://buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/和https://stackoverflow.com/a/6546134/2626313
您可以使用已编写的工具来解决此问题,例如:http://brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer
您可以创建精心设计的工具以满足您的确切需求。有可能,我已经完成了,花了几个星期,代码重达几千行代码。对于最肮脏的工作,我重复使用(稍作修改)主要来自(无序)源代码:
虽然您所描述的问题看起来只是我上面描述的问题的一部分,但如果您想使用GAC安装,则可能会出现同样的问题,而GAC安装又需要强名称签名。< / p>
我对你的建议是
提供最简单的解决方案 [02] 尝试使用Mono软件包中的ilasm / ildasm工具,而不是Microsoft的.NET Framework提供的工具(Microsoft&#39) ; .NET Framework 4.5中的Resgen已损坏且无法往返resx格式,Ildasm输出无法正确处理非ASCII字符等。虽然您无法修复Microsoft已损坏的封闭源,但您可以修复单声道的开源,但我没有必要。)
如果 [06] 不起作用,那么请研究(调试)→ILSpy←并研究各种命令行工具的Mono文档,了解您需要的内容及其来源 - 您和# 39;将看看他们如何使用Mono.Cecil库
如果您需要验证强名称或甚至签名的程序集(篡改它们将使签名无效)或删除签名等。您将深入研究代码的时间超过简单的Stack Overflow应答可以描述的内容。
潜伏在ILMerge所做的事情以及如何为您提供更简单的解决方案
另一个更简单的解决方案可能是(如果IKVM支持它)挂钩AssemblyResolve
事件,您可以将dll名称重新映射到物理dll,例如,来自完全不同的文件或资源流等。如旧Stack Overflow问题Embedding DLLs in a compiled executable的几个答案所示
(编辑#1:评论后)
如果您或多或少的一般性问题实际上归结为&#34;我如何使IKVM.dll使用我的套接字类而不是命名空间System.Net.Sockets&#34;那么非常直接的解决方案可能是:
使用http://www.ikvm.net/download.html提供的源代码编译和部署您自己的IKVM.dll自定义版本 - 无需使用二进制Mono.Cecil魔法。
由于所有代码都已打开,因此应该可以通过
查找并将指向名称空间System.Net
的所有引用重定向到dudeprgm.Net
dudeprgm.Net.cs
项目添加到解决方案using System.Net
System.Net
dudeprgm.Net
的所有内容
(编辑#2:评论后)
如果您选择跟踪 [04] 并使用文本文件和ilasm/ildasm
工具(样式 [02] )似乎效率不高,则下面是我的自动强名称签名者的关键相关部分,它使用Mono.Cecil将程序集引用更改为其他引用。代码按原样粘贴(不含代码行,之前,之后和所有代码),以适合我的形式。读取密钥:a is Mono.Cecil.AssemblyDefinition
,b implements Mono.Cecil.IAssemblyResolver
,b
实例中的密钥方法是方法AssemblyDefinition Resolve(AssemblyNameReference name)
,它将所需的DLL名称转换为对AssemblyDefinition.ReadAssembly(..)
的调用。我不需要解析指令流,重新映射汇编引用就足够了(如果需要,我可以在这里粘贴我的代码中的其他几个部分)
/// <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;
}
答案 1 :(得分:2)
这实际上是@ xmojmer答案的延伸。
我写了一个小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"
如果您的计算机是32位,或者在不同位置拥有ILASM_PATH
和ILDASM_PATH
,请修改ilasm
和ildasm
个变量。此脚本假定您在Windows上使用Cygwin。
修改发生在awk
命令中。它添加了对我的存根库(称为socketstubs.dll
)的引用,并存储在同一目录中。)通过添加
.assembly extern sockectstubs { }
在反汇编的IL代码的开头。然后,对于每一行,它会查找[System]System.Net.Sockets
并将其替换为[socketstubs]dudeprgm.Net.Sockets
。它在IL代码的末尾添加了换行符,否则ilasm
将不会根据docs重新汇编它:
如果.il源文件中的最后一行代码没有尾随空格或行尾字符,编译可能会失败。
最终修补的.dll将放在一个名为“Sockets_Patched”的新目录中。
您可以修改此脚本以交换任何dll中的任意两个名称空间:
System
中的[System[^\]]
替换为包含旧命名空间的任何程序集System\.Net\.Sockets
替换为旧命名空间,并在每个句点前加上反斜杠socketstubs
替换为包含新命名空间的库dudeprgm.Net.Sockets
替换为新的命名空间答案 2 :(得分:0)
您可以将套接字对象包装在一个接口中,然后依赖注入您真正想要的实现。代码可能有点乏味,但你可以在不直接引用System.Net.Sockets
的情况下将套接字交换到你想要的任何东西。