Skip to content

Instantly share code, notes, and snippets.

@nkrapivin
Last active July 19, 2021 16:58
Show Gist options
  • Save nkrapivin/29fbf113a7dc9126cb6f687b308ccb5d to your computer and use it in GitHub Desktop.
Save nkrapivin/29fbf113a7dc9126cb6f687b308ccb5d to your computer and use it in GitHub Desktop.
Zeus Linux IDE patcher
/*! 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