- using
System;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
namespace SharpPatcher
{
class Patcher
{
private bool m_bValid = false;
private string m_sFile1;
private string m_sFile2;
private FileStream m_File1;
private FileStream m_File2;
private
System.Collections.Hashtable m_OffsetByteTable;
Patcher(string File1, string
File2)
{
m_sFile1 =
File1;
m_sFile2 =
File2;
if(!File.Exists(File1) || !File.Exists(File2))
Console.Out.WriteLine("Could not open files.");
else
{
try
{
m_File1 = new FileStream(File1,
FileMode.Open);
m_File2 = new FileStream(File2,
FileMode.Open);
}
catch(System.IO.FileNotFoundException e)
{
Console.WriteLine("Could not open
{0}", new object[] {(object)e.FileName});
return;
}
if(m_File1.Length != m_File2.Length)
Console.WriteLine("Files must be
of the same size!");
else
{
m_OffsetByteTable = new
System.Collections.Hashtable();
m_bValid = true;
Console.WriteLine("Input file
successfully opened.");
}
}
}
public bool Valid
{
get
{
return m_bValid;
}
}
public bool GetDifferentBytes()
{
long fileSize
= m_File1.Length;
byte b1 = 0;
byte b2 = 0;
long step =
(long)(fileSize / 100);
bool found =
false;
Console.WriteLine("Reading different bytes...");
m_File1.Lock(0, m_File1.Length);
m_File2.Lock(0, m_File2.Length);
for(long i =
0; i < fileSize; i++)
{
b1 = (byte)m_File1.ReadByte();
b2 = (byte)m_File2.ReadByte();
if(b1 != b2)
{
found = true;
m_OffsetByteTable.Add((object)(m_File1.Position-1), (object)b2);
}
if((i % step) == 0)
Console.Write("#");
}
m_File2.Unlock(0, m_File2.Length);
m_File1.Unlock(0, m_File1.Length);
m_File2.Close();
m_File1.Close();
Console.WriteLine("\nDone.");
return found;
}
public void PrintDifferentBytes()
{
foreach(long
position in m_OffsetByteTable.Keys)
{
Console.WriteLine("{0} - {1}", new object[]
{(object)position, (object)m_OffsetByteTable[position]});
}
}
public bool BuildPatch(string
OutputFileName, string FileToPatch)
{
AssemblyName
an = new AssemblyName();
an.Version =
new Version(1, 0, 0, 0);
an.Name =
OutputFileName;
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an,
AssemblyBuilderAccess.Save);
ModuleBuilder
mb = ab.DefineDynamicModule(OutputFileName, OutputFileName + ".exe");
TypeBuilder tb
= mb.DefineType("Patcher.Patch", TypeAttributes.Class |
TypeAttributes.Public);
ConstructorBuilder cb =
tb.DefineDefaultConstructor(MethodAttributes.Public);
-
//ecco la prima
novità, stavolta siccome vogliamo fare un exe, dobbiamo definire
una funzione
-
//che abbia gli attributi public e static, il nome non è
necessario che sia Main, ma l'ho
-
//messo tanto per chiarezza :)
MethodBuilder
mainMethod = tb.DefineMethod("Main", MethodAttributes.Public |
MethodAttributes.Static,
CallingConventions.Standard, typeof(void), new
Type[] {typeof(string[])});
ILGenerator
ilgen = mainMethod.GetILGenerator();
-
//definiamo una
variabile locale di tipo System.IO.FileStream, che sarà il nostro
-
//file da patchare
ilgen.DeclareLocal(typeof(System.IO.FileStream));
-
//ecco la seconda
novità, siccome dobbiamo gestire le eccezioni, definiamo una
label
-
//che servirà per segnare l'uscita dalla funzione
Label exit =
ilgen.DefineLabel();
ilgen.EmitWriteLine("Sharp Patcher by Quake2");
ilgen.EmitWriteLine("Opening input file...");
-
//come annunciato,
gestiamo l'eccezione che verrebbe lanciata in caso il file
-
//non venisse trovato
ilgen.BeginExceptionBlock();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Ldstr, FileToPatch);
ilgen.Emit(OpCodes.Ldc_I4, (int)System.IO.FileMode.Open);
-
//una novità
importante, ovvero la creazione degli oggetti, si usa l'istruzione
Newobj,
-
//a cui va passato il costruttore da chiamare, che viene selezionato in
base
-
//al numero e al tipo dei suoi parametri, diciamo che in c# corrisponde
a fare
-
//System.IO.FileStream fs = new System.IO.FileStream("file.dat",
System.IO.FileMode.Open) ,
-
//il nome ovviamente cambia eh :)
ilgen.Emit(OpCodes.Newobj,
typeof(System.IO.FileStream).GetConstructor(new Type[2]
{typeof(string), typeof(FileMode)}));
ilgen.Emit(OpCodes.Stloc_0);
-
//essendo un blocco
try { ...; } catch(...) { ...; } definiamo l'inizio del catch appunto
ilgen.BeginCatchBlock(typeof(System.IO.FileNotFoundException));
ilgen.EmitWriteLine("Could not find the specified file.");
-
//per uscire da un
eccezione, bisogna usare l'opcode Leave, che sistema lo stack
ilgen.Emit(OpCodes.Leave, exit);
-
//siccome abbiamo
finito di gestire l'eccezione, chiudiamo il blocco con EndExceptionBlock
ilgen.EndExceptionBlock();
foreach(long
offset in m_OffsetByteTable.Keys)
{
ilgen.Emit(OpCodes.Ldloc_0);
-
//ecco un nuovo
opcode, l'opcode Ldc.i8 non fa altro che caricare un intero
-
//a 64bit nello stack, l'intero glie lo passiamo noi
-
//(l'offset appunto in cui vogliamo scrivere il byte)
ilgen.Emit(OpCodes.Ldc_I8, offset);
-
//un altro opcode
nuovo, ldc.i4.s carica nello stack un valore a 8bit
-
//e lo estende a 32, in questo modo si risparmiano tre byte
nella codifica
-
//dell'opcode (ldc.i4.s è la versione
"short" di ldc.i4, accetta quindi interi a 8bit
ilgen.Emit(OpCodes.Ldc_I4_S, (byte)SeekOrigin.Begin);
ilgen.Emit(OpCodes.Callvirt,
typeof(System.IO.FileStream).GetMethod("Seek"));
-
//novità
importante, il metodo Seek della classe System.IO.FileStream, ritorna
un valore,
-
//precisamente un intero a 64bit, a cui però noi non
siamo interessati,
-
//fatto sta che questo valore è comunque sullo stack, e noi lo
stack lo dobbiamo
-
//mantenere pulito, quindi se non vogliamo usare questo valore,
-
//semplicemente facciamo pop, che non farà altro che eliminare
l'ultima cosa
<>
//presente sullo stack, in modo da averlo di
nuovo "pulito"
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ldloc_0);
ilgen.Emit(OpCodes.Ldc_I4_S,
(byte)m_OffsetByteTable[offset]);
ilgen.Emit(OpCodes.Callvirt,
typeof(System.IO.FileStream).GetMethod("WriteByte"));
}
ilgen.Emit(OpCodes.Ldloc_0);
ilgen.Emit(OpCodes.Callvirt,
typeof(System.IO.FileStream).GetMethod("Close"));
ilgen.EmitWriteLine("File successfully patched.");
//questo è il punto in cui
vogliamo riprenda l'esecuzione del programma in caso di eccezione,
//quindi non facciamo altro che assegnare la label "exit" a questo
punto, in modo da potervi
//fare riferimento dal codice
ilgen.MarkLabel(exit);
ilgen.Emit(OpCodes.Ret);
tb.CreateType();
//fondamentale, un exe al contrario di
una dll (che ripeto, in .NET sono la stessa
//identica cosa), ha un entry point, ovvero il punto da cui parte
l'esecuzione,
//in questo caso la funzione Main che abbiamo appena creato
ab.SetEntryPoint(tb.GetMethod("Main"));
ab.Save(OutputFileName + ".exe");
return true;
}
[STAThread]
public static void Main(string[]
args)
{
Patcher p =
new Patcher(args[0], args[1]);
if(p.Valid)
{
if(p.GetDifferentBytes())
{
Console.WriteLine("Building patch
file.");
if(!p.BuildPatch(args[2],
"Civilization3.exe"))
Console.WriteLine("Could not create patch file.");
else
Console.WriteLine("Patch file created.");
}
else
Console.WriteLine("No different
bytes found.");
}
else
Console.WriteLine("Error");
}
}
}
>
|