Last active
July 19, 2021 16:58
-
-
Save nkrapivin/29fbf113a7dc9126cb6f687b308ccb5d to your computer and use it in GitHub Desktop.
Zeus Linux IDE patcher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! how to compile: `csc Program.cs -r:Mono.Cecil.dll` | |
* how to use: `mono Program.exe /opt/GameMakerStudio2/IDE.dll` | |
* OR, FOR RED: `mono Program.exe /opt/GameMakerStudio2-Dev/IDE.dll` | |
* | |
* the Cecil dll can be obtained manually from NuGet. (which is what I did) | |
* thanks to YoYo not obfuscating Linux builds, we can easily do our funky stuff. | |
* | |
* also hi to all YoYo employees reading this, please take care of yourself, hydrate, | |
* go outside (in a mask), take breaks from work, and don't patch this program, or I will become vewy sad :< | |
*/ | |
// usings go here: | |
using Mono.Cecil; // core mono | |
using Mono.Cecil.Cil; // extension stuff | |
using System; // exceptions | |
using System.IO; // file io | |
/// <summary> | |
/// Linux IDE build patcher, allows regular GMS 2 users to login into Linux IDE builds, be it Red or Beta-Internal channel. | |
/// </summary> | |
public class Program | |
{ | |
public static void PatchMoveNextMethod(MethodDefinition mt) | |
{ | |
Console.WriteLine("PatchMoveNextMethod(): begin"); | |
var processor = mt.Body.GetILProcessor(); | |
var instructions = mt.Body.Instructions; | |
var patches = 0; // how many patches we applied... | |
for (var i = 0; i < instructions.Count; ++i) | |
{ | |
if (patches == 2) | |
{ | |
Console.WriteLine("All patches are applied for this class..."); | |
break; | |
} | |
var instr = instructions[i]; | |
if (instr.OpCode.Code == Code.Call) | |
{ | |
if (instr.Operand is MethodReference mref) | |
{ | |
if (mref.Name == "get_OSType") | |
{ | |
processor.Replace(i, processor.Create(OpCodes.Ldstr, "win")); | |
Console.WriteLine("Patched OSType getter to ldstr('win');"); | |
++patches; | |
} | |
else if (mref.Name == "GetEntryAssembly") | |
{ | |
processor.Replace(i, processor.Create(OpCodes.Ldstr, /* change this later */ "2.3.3.570")); | |
processor.Replace(i + 1, processor.Create(OpCodes.Nop)); // GetName(). | |
processor.Replace(i + 2, processor.Create(OpCodes.Nop)); // GetVersion(). | |
// ^ the exact reason why I'm not using foreach() here | |
// (also foreach() seems to corrupt the object's state? wtf?!) | |
// next instruction is .ToString(), we're fine with it. | |
// "a string".ToString(); will return the same string, less instruction pokery. | |
Console.WriteLine("Patched Assembly Version getter to a constant string..."); | |
++patches; | |
} | |
} | |
} | |
} | |
} | |
public static void PatchAsyncMachineClass(TypeDefinition t) | |
{ | |
for (var i = 0; i < t.Methods.Count; ++i) | |
{ | |
// internal async state machine method. | |
if (t.Methods[i].Name == "MoveNext") | |
{ | |
PatchMoveNextMethod(t.Methods[i]); | |
} | |
} | |
} | |
public static void PatchFile(string path) | |
{ | |
var resolver = new DefaultAssemblyResolver(); | |
resolver.AddSearchDirectory(Path.GetDirectoryName(path)); | |
var readerParams = new ReaderParameters(ReadingMode.Immediate); | |
readerParams.AssemblyResolver = resolver; | |
readerParams.InMemory = true; | |
var module = ModuleDefinition.ReadModule(path, readerParams); | |
var UserManMethods = module.GetType("YoYoStudio.User.UserManager"); | |
for (var i = 0; i < UserManMethods.NestedTypes.Count; ++i) | |
{ | |
var ntype = UserManMethods.NestedTypes[i]; | |
var n = ntype.Name; | |
if (n.Contains("<ActualDoBackgroundLogin>") || | |
n.Contains("<DoLogin>") || | |
n.Contains("<DoTwoStepAuthentication>")) | |
{ | |
Console.WriteLine($"Patching class {n}"); | |
PatchAsyncMachineClass(ntype); | |
} | |
} | |
var bakpath = Path.Combine(Path.GetDirectoryName(path), "IDE.Original.bak"); | |
Console.WriteLine($"Backing up to {bakpath}"); | |
if (File.Exists(bakpath)) | |
{ | |
Console.WriteLine("Backup already exists, deleting it..."); | |
File.Delete(bakpath); | |
} | |
if (File.Exists(path)) | |
{ | |
File.Copy(path, bakpath); | |
File.Delete(path); | |
} | |
try | |
{ | |
module.Write(path); | |
} | |
catch // an [unknown] made me do this. | |
{ | |
// [directory of executable]/IDE.CopyMe.dll | |
var altpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "IDE.CopyMe.dll"); | |
Console.WriteLine($"Unable to write the file. Writing as {altpath}"); | |
module.Write(altpath); | |
Console.WriteLine("Please copy the IDE.CopyMe.dll OVER(!) original IDE.dll yourself, thanks."); | |
} | |
module.Dispose(); | |
} | |
public static void TryPatchFile(string path) | |
{ | |
Console.WriteLine($"Poking file {path}"); | |
// try to read the file, see if we have access rights. | |
File.ReadAllBytes(path); // will throw an exception if can't even read the file | |
PatchFile(path); // will throw an exception too | |
Console.WriteLine("All is fine. Thank you for using software by nik the incompetent C#ist."); | |
} | |
/// <summary> | |
/// the fun begins HERE | |
/// </summary> | |
/// <param name="args">only first argument (optional) is read, ability to override IDE.dll path</param> | |
/// <returns>EXIT_SUCCESS or EXIT_FAILURE</returns> | |
public static int Main(string[] args) | |
{ | |
// regular C exit codes: seem to match Linux headers. | |
const int EXIT_SUCCESS = 0; | |
const int EXIT_FAILURE = 1; | |
try | |
{ | |
var IDEpath = Path.GetFullPath("IDE.dll"); | |
if (args.Length < 1) | |
{ | |
IDEpath = "/opt/GameMakerStudio2/IDE.dll"; // or GameMakerStudio2-Dev | |
} | |
else | |
{ | |
IDEpath = args[0]; | |
} | |
TryPatchFile(IDEpath); | |
return EXIT_SUCCESS; | |
} | |
catch (Exception exc) | |
{ | |
Console.WriteLine("Patch failed, exception:"); | |
Console.WriteLine("{"); | |
Console.WriteLine(exc.ToString()); | |
Console.WriteLine("}"); | |
Console.WriteLine("Try running the mono runtime as sudo, or override the path to IDE.dll via the first argument:"); | |
Console.WriteLine("sudo mono Program.exe /full/path/to/the/IDE.dll"); | |
Console.WriteLine(); | |
Console.Write("Press any key to continue . . . "); | |
Console.ReadKey(true); | |
return EXIT_FAILURE; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment