Skip to content

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
kg committed Apr 17, 2012
0 parents commit 0e6c5d5
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

*.suo
*.user
bin
obj
_Resharper.*
xextool.exe
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "Upstream/Cecil"]
path = Upstream/Cecil
url = https://github.com/jbevain/cecil.git
203 changes: 203 additions & 0 deletions AssemblyRewriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace XexDecryptor {
public class AssemblyRewriter {
public const string AssemblyNameMscorlib = "mscorlib, Version=3.5.0.0, Culture=neutral, PublicKeyToken=1c9e259686f921e0";
public const string AssemblyNameXnaFramework = "Microsoft.Xna.Framework, Version=3.1.0.0, Culture=neutral, PublicKeyToken=51c3bfb2db46012c";

// The XBox 360 versions of MS assemblies have different versions and public key tokens.
// We need to find any references to them and fix them to point to the Win32 versions.
public static readonly Dictionary<string, string> AssemblyNameReplacements = new Dictionary<string, string> {
// XNA 3.1 references

{AssemblyNameMscorlib,
"mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"},
{"System, Version=3.5.0.0, Culture=neutral, PublicKeyToken=1c9e259686f921e0",
"System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"},
{"System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=1c9e259686f921e0",
"System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"},
{"System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=1c9e259686f921e0",
"System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"},
{AssemblyNameXnaFramework,
"Microsoft.Xna.Framework, Version=3.1.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d"},
{"Microsoft.Xna.Framework.Game, Version=3.1.0.0, Culture=neutral, PublicKeyToken=51c3bfb2db46012c",
"Microsoft.Xna.Framework.Game, Version=3.1.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d"},

// TODO: Add XNA 4.0 references
};

private static void KillCallInstruction (MethodBody body, MethodReference invokeTarget, ref int i, ref int c, bool replaceReturnValue) {
// Patch out the call instruction.
int stackEntriesToPop = invokeTarget.Parameters.Count + (invokeTarget.HasThis ? 1 : 0);
body.Instructions.RemoveAt(i);

c += stackEntriesToPop;

int insertionPosition = i;
while (stackEntriesToPop > 0) {
body.Instructions.Insert(insertionPosition, Instruction.Create(OpCodes.Pop));
stackEntriesToPop--;
insertionPosition += 1;
}

if (!replaceReturnValue || (invokeTarget.ReturnType.FullName == "System.Void")) {
c -= 1;
i = insertionPosition;
} else {
body.Instructions.Insert(insertionPosition, Instruction.Create(OpCodes.Ldnull));
i = insertionPosition + 1;
}
}

public static void Rewrite (string executablePath) {
var resolver = new RewriterAssemblyResolver();
var readerParameters = new ReaderParameters {
AssemblyResolver = resolver,
ReadingMode = ReadingMode.Immediate,
ReadSymbols = false
};
AssemblyDefinition asm;
try {
asm = Mono.Cecil.AssemblyDefinition.ReadAssembly(
executablePath, readerParameters
);
} catch (Exception) {
Console.WriteLine("{0} isn't a managed assembly. Not rewriting.", executablePath);
return;
}

var asmCorlib = resolver.Resolve(AssemblyNameMscorlib, readerParameters);
var asmFramework = resolver.Resolve(AssemblyNameXnaFramework, readerParameters);

foreach (var module in asm.Modules) {
// Force 32-bit x86
module.Architecture = TargetArchitecture.I386;
module.Attributes = ((module.Attributes & ~ModuleAttributes.Preferred32Bit) & ~ModuleAttributes.StrongNameSigned)
| ModuleAttributes.Required32Bit;

// The main module will be console, switch it to GUI
if (module.Kind == ModuleKind.Console)
module.Kind = ModuleKind.Windows;

for (var i = 0; i < module.AssemblyReferences.Count; i++) {
var ar = module.AssemblyReferences[i];
Debug.WriteLine(ar.FullName);

string newFullName;
if (AssemblyNameReplacements.TryGetValue(ar.FullName, out newFullName)) {
var newReference = AssemblyNameReference.Parse(newFullName);
module.AssemblyReferences[i] = newReference;
Console.WriteLine("{0} -> {1}", ar.Name, newFullName);
} else {
Console.WriteLine("Ignoring {0}", ar.Name);
}
}

for (var i = 0; i < module.Resources.Count; i++) {
var rsrc = module.Resources[i];

switch (rsrc.Name) {
case "Microsoft.Xna.Framework.RuntimeProfile":
// FIXME: Detect version of executable and pick correct windows profile
module.Resources[i] = new EmbeddedResource(
rsrc.Name, rsrc.Attributes,
Encoding.ASCII.GetBytes("Windows.v3.1")
);

break;
default:
break;
}
}

foreach (var type in module.GetTypes()) {
string qualifiedName;

foreach (var method in type.Methods) {
if (!method.HasBody)
continue;

var body = method.Body;

// Patch out particular method calls.
for (int i = 0, c = body.Instructions.Count; i < c; i++) {
var instruction = body.Instructions[i];
MethodReference invokeTarget = null;

switch (instruction.OpCode.Code) {
case Code.Callvirt:
case Code.Call:
invokeTarget = instruction.Operand as MethodReference;
break;
default:
continue;
}

if (invokeTarget == null)
continue;

qualifiedName = invokeTarget.DeclaringType.FullName + "::" + invokeTarget.Name;

switch (qualifiedName) {

// XBox 360 only.
case "System.Threading.Thread::SetProcessorAffinity":
case "Microsoft.Xna.Framework.GamerServices.GamerPresence::set_PresenceMode":
KillCallInstruction(body, invokeTarget, ref i, ref c, true);
break;

// Force windowed mode.
case "Microsoft.Xna.Framework.GraphicsDeviceManager::set_IsFullScreen":
var ldc = body.Instructions[i - 1];
if (ldc.OpCode.Code == Code.Ldc_I4_1) {
body.Instructions[i - 1] = Instruction.Create(
OpCodes.Ldc_I4_0
);
}

break;
default:
continue;
}
}

}
}
}

var writerParameters = new WriterParameters {
WriteSymbols = false
};
asm.Write(executablePath, writerParameters);
}
}

public class RewriterAssemblyResolver : BaseAssemblyResolver {
public readonly Dictionary<string, AssemblyDefinition> Cache = new Dictionary<string, AssemblyDefinition>();

public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters) {
var key = name.FullName;
AssemblyDefinition result;

string newKey;
if (AssemblyRewriter.AssemblyNameReplacements.TryGetValue(key, out newKey))
key = newKey;
else
Debugger.Break();

if (Cache.TryGetValue(key, out result))
return result;

Cache[key] = result = base.Resolve(
AssemblyNameReference.Parse(key), parameters
);
return result;
}
}
}
137 changes: 137 additions & 0 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace XexDecryptor {
public static class Program {
public static void Abort (string format, params object[] args) {
Console.Error.WriteLine(format, args);
Environment.Exit(1);
}

public static int? FindByteString (byte[] bytes, int startOffset, int? limit, byte[] searchString) {
int i = startOffset;
int end = limit.GetValueOrDefault(bytes.Length - i) + i;

int matchStart = 0, matchPos = 0;

while (i < end) {
var current = bytes[i];

if (current != searchString[matchPos]) {
if (matchPos != 0) {
matchPos = 0;
continue;
}
} else {
if (matchPos == 0)
matchStart = i;

matchPos += 1;
if (matchPos == searchString.Length)
return matchStart;
}

i++;
}

return 0;
}

public static int? FindExecutableHeader (byte[] bytes, int startOffset) {
int? offset = FindByteString(bytes, startOffset, null, new byte[] { 0x4D, 0x5A });
if (!offset.HasValue)
return null;

int? offset2 = FindByteString(bytes, offset.Value + 2, 512, new byte[] { 0x50, 0x45, 0x00, 0x00 });
if (!offset2.HasValue)
return null;

return offset;
}

public static void Main (string[] args) {
if (args.Length < 1) {
Abort("Usage: XexDecryptor [xex filenames]");
}

var filenames = new List<string>();
foreach (var filename in args) {
if (filename.IndexOfAny(new char[] { '*', '?' }) < 0) {
filenames.Add(filename);
} else {
var dirname = Path.GetDirectoryName(filename);
if (String.IsNullOrWhiteSpace(dirname))
dirname = Environment.CurrentDirectory;

filenames.AddRange(Directory.GetFiles(dirname, Path.GetFileName(filename)));
}
}

foreach (var sourceFile in filenames) {
Console.WriteLine("Decrypting {0}...", sourceFile);

if (!File.Exists(sourceFile)) {
Abort("File not found: {0}", sourceFile);
}

string outputFile = Path.GetFullPath(sourceFile);
if (outputFile.ToLower().EndsWith(".xex")) {
outputFile = Path.Combine(
Path.GetDirectoryName(outputFile),
Path.GetFileNameWithoutExtension(outputFile)
);
} else {
outputFile += ".decrypted";
}

var tempFilePath = Path.GetTempFileName();
File.Delete(tempFilePath);

string stdError;
byte[] stdOut;

Util.RunProcess(
"xextool.exe",
String.Format(
"-c u -e u -o \"{0}\" \"{1}\"",
tempFilePath,
sourceFile
),
null, out stdError, out stdOut
);

if (!String.IsNullOrWhiteSpace(stdError)) {
File.Delete(tempFilePath);
Abort("XexTool reported error: {0}", stdError);
}

var xexBytes = File.ReadAllBytes(tempFilePath);
File.Delete(tempFilePath);

var firstHeader = FindExecutableHeader(xexBytes, 0);
if (!firstHeader.HasValue) {
Abort("File is not a valid executable.");
}

var secondHeader = FindExecutableHeader(xexBytes, firstHeader.Value + 128);
if (!secondHeader.HasValue) {
Abort("File does not contain an embedded executable.");
}

Console.Write("Extracting to '{0}'... ", outputFile);
using (var fs = File.OpenWrite(outputFile)) {
fs.Write(xexBytes, secondHeader.Value, xexBytes.Length - secondHeader.Value);
}
Console.WriteLine("done.");

Console.WriteLine("Rewriting assembly... ");
AssemblyRewriter.Rewrite(outputFile);
}

Console.WriteLine("{0} assemblies processed.", filenames.Count);
}
}
}
36 changes: 36 additions & 0 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("XexDecryptor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("XexDecryptor")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("11fd52e2-8757-4f48-9f38-55d250a9fdf7")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
1 change: 1 addition & 0 deletions Upstream/Cecil
Submodule Cecil added at ec8248
Loading

0 comments on commit 0e6c5d5

Please sign in to comment.