c# - Reemplazar referencias a un tipo/espacio de nombres usando Mono.Cecil
replace types (3)
Podría envolver los objetos de los sockets en una interfaz y luego insertar la implementación que realmente desea. El código puede ser un poco tedioso, pero puede cambiar los sockets a cualquier cosa que desee después de eso sin hacer referencia directa a System.Net.Sockets
Fondo (innecesario, confuso, solo para los curiosos)
Estoy usando la versión gratuita de Unity3D para dispositivos móviles y no me permite usar el espacio de nombres System.Net.Sockets
en dispositivos móviles. El problema es que estoy usando una biblioteca compilada .dll
(es decir, IKVM) que hace referencia a System.Net.Sockets
. En realidad, no estoy usando las clases en IKVM que hace referencia a las referencias System.Net.Sockets
, así que en lugar de comprar las licencias móviles Unity Pro de $ 3000, hice una biblioteca de código auxiliar del espacio de nombres Sockets
llamado dudeprgm.Net.Sockets
que simplemente reemplaza a todas las clases y métodos con stubs (lo hice usando el código fuente Mono).
Mi problema
Necesito reemplazar todas las referencias de System.Net.Sockets.*
En my dlls a dudeprgm.Net.Sockets.*
. Sé que algo como esto es posible y hecho por otras personas (ver EDITAR abajo, en la parte inferior de la página) . Me gustaría saber cómo hacerlo yo mismo.
Pude encontrar el siguiente código usando Mono.Cecil.
Pasa por todas las instrucciones de IL, verifica si el operando es un InlineType
, luego verifica si el tipo en línea es parte de System.Net.Sockets
, luego lo renombra a dudeprgm.Net.Sockets
y lo escribe. ** No estoy seguro de si esta es la forma correcta de "buscar y reemplazar" en Mono.Cecil. El problema es que esto no captura todos los usos de Sockets
(ver a continuación).
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));
}
}
}
}
}
Funciona bien, pero solo capta instrucciones "simples". ildasm
con ildasm
, puedo ver dónde reemplazó los tipos como aquí:
box [''Socket Stubs''/*23000009*/]dudeprgm.Net.Sockets.SocketOptionLevel/*01000058*/
Pero no captó estas instrucciones "complejas":
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 */
Ahora los .dll
s son un revoltijo de dudeprgm.Net.Sockets
de dudeprgm.Net.Sockets
y System.Net.Sockets
.
Estoy bastante seguro de que esto está sucediendo porque solo estoy cambiando OperandType.InlineType
s, pero no estoy seguro de cómo hacerlo. He intentado buscar en todas partes, pero me parece que es Mono. Cecil no tiene forma de establecer operandos en una string
, todo parece tener que hacerse solo con la API de Cecil ( https://stackoverflow.com/a/ 7215711/837703 ).
(Lo siento si estoy usando términos incorrectos, soy bastante nuevo para IL en general).
Pregunta
¿Cómo puedo reemplazar todos los lugares donde System.Net.Sockets
aparece en Mono.Cecil, en lugar de solo donde el operando es un InlineType
? Realmente no quiero pasar por todos los OperandType
hay en Cecil, solo estaba buscando algún método de búsqueda y reemplazo en Cecil donde no tuviera que trabajar con IL.
EDITAR: (también es innecesario, confuso y solo para los curiosos)
Esta persona pudo hacer algo similar por $ 25: http://www.reddit.com/r/Unity3D/comments/1xq516/good_ol_sockets_net_sockets_for_mobile_without/ .
Herramienta de herramienta automática que detecta y corrige el uso del socket en scripts y .dll.
...
"Las DLL están parcheadas usando Mono.Cecil ...
Puede ir a la segunda captura de pantalla en https://www.assetstore.unity3d.com/en/#!/content/13166 y ver que dice que puede reemplazar espacios de nombres.
Esa biblioteca no se ajusta a mis necesidades, porque 1) no cambia el nombre al espacio de nombres que quiero ( dudeprgm.Net.Sockets
), 2) la biblioteca a la que renombra no admite todas las clases de System.Net.Sockets
que IKVM necesita, porque IKVM usa prácticamente todas las clases de Sockets y 3) cuesta $ 25 y realmente no quiero comprar algo que no voy a usar. Solo quería mostrar que es posible reemplazar las referencias de espacios de nombres / tipos en Mono.Cecil.
En realidad, esto pretende ser una extensión de la respuesta de @xmojmer.
Escribí un pequeño script bash
para automatizar la opción de 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"
Modifique las variables ILASM_PATH
e ILDASM_PATH
si su computadora tiene 32 bits o tiene ilasm
e ildasm
en diferentes ubicaciones. Este script asume que estás usando Cygwin en Windows.
La modificación ocurre en el comando awk
. Agrega una referencia a mi biblioteca de stubs (llamada socketstubs.dll
, y se almacena en el mismo directorio) al agregar
.assembly extern sockectstubs { }
al comienzo del código IL desensamblado. Luego, para cada línea, busca [System]System.Net.Sockets
y los reemplaza por [socketstubs]dudeprgm.Net.Sockets
. Agrega una nueva línea al final del código IL, o de lo contrario ilasm
no lo volverá a ensamblar, según los documentos :
La compilación puede fallar si la última línea de código en el archivo fuente .il no tiene un espacio en blanco al final o un carácter de fin de línea.
El .dll parcheado final se colocará en un nuevo directorio llamado "Sockets_Patched".
Puede modificar esta secuencia de comandos para intercambiar dos espacios de nombres en cualquier dll:
- Reemplace el
System
en[System[^/]]
a cualquier ensamblaje que contenga su antiguo espacio de nombres - Reemplace
System/.Net/.Sockets
con su antiguo espacio de nombres y ponga una barra invertida delante de cada período - Reemplace todos los
socketstubs
en la secuencia de comandos con su biblioteca que contiene el nuevo espacio de nombres - Reemplace
dudeprgm.Net.Sockets
con su nuevo espacio de nombres
[01] Problema similar
Su problema al reemplazar referencias a un dll (y tipos dentro) con otro dll (y tipos dentro) es técnicamente similar al problema conocido como
Google: "c # agrega un nombre fuerte al ensamblaje de terceros"
En este problema, desea que su aplicación se firme por nombre seguro y posiblemente se instale en GAC o Ngen-ed, pero su aplicación depende de una biblioteca heredada de terceros que no tiene un nombre fuerte agregado en tiempo de compilación, lo que rompe el requisito que dice que un ensamblado con nombre fuerte solo puede usar ensamblajes con nombres fuertes. No tiene un código fuente para la biblioteca de terceros, solo binarios, por lo que no puede recompilarlo (== "descripción simplificada")
Hay varias soluciones posibles, siendo las 3 más típicas:
[02] Solución de problema similar # 1
Puede usar ildasm
/ ilasm
round trip
, convertir todos los binarios en forma de texto, cambiar todas las referencias en sus equivalentes de nombre fuerte (recursivamente) y convertir el texto nuevamente en código. Ejemplos: http://buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/ y https://.com/a/6546134/2626313
[03] Solución de problema similar n. ° 2
Puede usar las herramientas ya escritas para resolver exactamente este problema, por ejemplo: http://brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer
[04] Solución de problema similar n. ° 3
Puede crear una herramienta diseñada para satisfacer sus necesidades exactas. Es posible, lo he hecho, tomó varias semanas y el código pesa varios miles de líneas de código. Para el trabajo más sucio que he reutilizado (con ligeras modificaciones), principalmente el código fuente de (desordenado):
- ApiChange.Api.Introspection.CorFlagsReader.cs
- GACManagerApi.Fusion
- brutaldev / StrongNameSigner
- icsharpcode / ILSpy
- Mono.Cecil.Binary
- Mono.Cecil.Metadata
- Mono.ResGen
- Ricciolo.StylesExplorer.MarkupReflection
- y leyendo http://referencesource.microsoft.com/
[05] Tu problema
Aunque el problema que ha descrito parece solo un subconjunto de lo que describo arriba, bien podría ser el mismo problema si desea usar la instalación de GAC, que a su vez requiere una fuerte firma de nombres.
Mi recomendación para ti es
[06] La solución de tu problema n. ° 1
Ofrezca la solución más fácil [02] y evite problemas al usar las herramientas ilasm / ildasm del paquete Mono, no las proporcionadas por .NET Framework de Microsoft (Resgen de Microsoft en .NET Framework 4.5 está roto y no puede hacer el formato de resx de ida y vuelta, La salida de Ildasm no maneja los caracteres que no son ASCII correctamente, etc. Si bien no puede reparar el código cerrado de Microsoft, puede arreglar el código abierto de Mono, pero no fue necesario.
[07] La solución de su problema n. ° 2
Si [06] no funciona para usted, estudie ( depure ) → ILSpy ← y estudie la documentación de Mono para varias herramientas de línea de comandos haciendo lo que necesita y sus fuentes: verá exactamente cómo usan la biblioteca Mono.Cecil
Si enfrenta la necesidad de validar ensamblajes fuertes con nombre o incluso firmados (alterarlos invalidará las firmas) o eliminar las firmas, etc. Va a sumergirse en el código por más tiempo de lo que una simple respuesta de desbordamiento de pila puede describir.
[08] La solución de tu problema n. ° 3
Al acecho de lo que ILMerge hace y cómo puede apuntar a una solución más fácil
[09] Solución de su problema n. ° 4
Otra solución más fácil podría ser (si IKVM lo admite) conectar el evento AssemblyResolve
donde puede reasignar el nombre de dll a dll físico, cargado, por ejemplo, de un archivo totalmente diferente o de un flujo de recursos, etc. Como se muestra en varias respuestas de DLL anterior. en un ejecutable compilado
(EDIT # 1: después de los comentarios)
[10] La solución de tu problema n. ° 5
Si su pregunta más o menos general se reduce a "¿Cómo puedo hacer que IKVM.dll use mis clases de socket en lugar de las del espacio de nombres System.Net.Sockets?", Entonces una solución bastante sencilla podría ser:
Compile e implemente su propia versión personalizada de IKVM.dll utilizando el código fuente disponible en http://www.ikvm.net/download.html , sin magia binaria Mono.Cecil necesaria.
Como todo el código está abierto, debería ser posible encontrar y redirigir todas las referencias que apuntan al espacio de nombres System.Net
a dudeprgm.Net
por
- [10.1] obtenga el código fuente IKVM y todos los demás requisitos previos y asegúrese de que puede compilar IKVM.dll de trabajo
- [10.2] agregue el proyecto
dudeprgm.Net.cs
a la solución - [10.3] en todos los archivos fuente encuentre y elimine todo lo que parezca
using System.Net
- [10.4] en todos los archivos fuente, texto completo, busque y reemplace todo lo que se parece a
System.Net
condudeprgm.Net
- [10.5] compilar Cuando el compilador se queja de un símbolo que falta (que estaba antes en el espacio de nombres System.Net), entonces agréguelo a su archivo de código auxiliar. Ir a [10.5]
- [10.6] si el paso anterior no se establece como "compilar bien" después de 2 horas, entonces piensa en otra solución (o descansa un poco)
- [10.7] revise la licencia IKVM ( http://sourceforge.net/p/ikvm/wiki/License/ ) si hay algo que debe cambiar / reclamar / acusar conforme se modificó el código fuente original
(EDIT # 2: después de los comentarios)
[11] La solución de tu problema n. ° 6
Si elije track [04] y trabaja con archivos de texto y herramientas ilasm/ildasm
(style [02] ) no parecería productivo, a continuación se muestra la parte clave clave de mi firmante de nombre automático fuerte que cambia las referencias de ensamblaje a otras referencias usando Mono. Cecilio. El código se pega tal cual (sin líneas de código antes, después y todo) en una forma que funcione para mí. Lectura de claves: a is Mono.Cecil.AssemblyDefinition
, b implements Mono.Cecil.IAssemblyResolver
, método clave en la instancia b
es el método AssemblyDefinition Resolve(AssemblyNameReference name)
que traduce el nombre requerido de DLL en llamar a AssemblyDefinition.ReadAssembly(..)
. No necesité analizar el flujo de instrucciones, reasignar las referencias de ensamblado fue suficiente (puedo pegar aquí algunas otras piezas de mi código si es necesario)
/// <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;
}