static OsuPatcher() { FancyOutput = new ConcurrentQueue <string>(); // ReSharper disable once FunctionNeverReturns ThreadPool.QueueUserWorkItem(state => { while (true) { if (!FancyOutput.IsEmpty && FancyOutput.TryDequeue(out string msg)) { XConsole.Write(msg); } Thread.Sleep(1); } }); #if !DEBUG AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => { Exit(XConsole.Fatal("Unhandled exception! This shouldn't occur. Details:\n" + eventArgs.ExceptionObject)); // exit because ReadKey is needed if configuration is debug Environment.Exit(1); }; #endif #if LIVE_DEBUG Environment.CurrentDirectory = @"C:\osu!"; var executingAssLocation = @"C:\osu!\osu!patch"; var proc = new Process { StartInfo = new ProcessStartInfo { FileName = @"OsuVersionDownloader\OsuVersionDownloader.exe", Arguments = "Stable40 osu!.exe", WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, RedirectStandardOutput = true, UseShellExecute = false } }; proc.Start(); proc.WaitForExit(); #else var executingAssLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); #endif PluginsFolderLocation = Path.Combine(executingAssLocation, "plugins"); CacheFolderLocation = Path.Combine(executingAssLocation, "cache"); ConfigFileLocation = Path.Combine(executingAssLocation, "patcher.cfg"); }
public static int Main(string[] args) { // Console.ReadKey(true); if (args.Length < 2) { return(Exit("osu!patch - osu! assembly patcher based on NameMapper\n" + "by exys, 2019 - 2020\n" + "modded by Aoba Suzukaze, 2020\n" + "\n" + "Usage:\n" + "osu!patch [clean module] [obfuscated module]")); } if (!File.Exists(_cleanOsuPath = Path.GetFullPath(args[0]))) { return(XConsole.PrintFatal("Specified clean module path does not exist!\n")); } if (!File.Exists(_obfOsuPath = Path.GetFullPath(args[1]))) { return(XConsole.PrintFatal("Specified obfuscated module path does not exist!\n")); } try { _obfOsuModule = ModuleDefMD.Load(_obfOsuPath); _cleanOsuModule = ModuleDefMD.Load(_cleanOsuPath); _obfOsuAssembly = Assembly.LoadFile(_obfOsuPath); } catch (Exception ex) { return(XConsole.PrintFatal("Unable to load one of the modules! Details:\n" + ex)); } ObfOsuHash = MD5Helper.Compute(_obfOsuPath); // ORIGINAL!!!!!!! hash, PLEASE PASS UNMODIFIED PEPPY-SIGNED ASSEMBLY AS _obfOsuModule!@!!32R1234 (refer to "Patch on update" patch) XConsole.PrintInfo($"Loaded assemblies: {_cleanOsuModule.Assembly.FullName} (clean); {_obfOsuModule.Assembly.FullName} (obfuscated)."); XConsole.PrintInfo("MD5 hash of obfuscated assembly: " + ObfOsuHash); try { LoadPlugins(); // Loading plugins } catch (Exception ex) { return(XConsole.PrintFatal("Something really bad happened while trying to process plugins! Details:\n" + ex)); } try { XConsole.PrintInfo("Cleaning control flow of obfuscated assembly"); CleanControlFlow(); } catch (Exception ex) { return(XConsole.PrintFatal("Unable to deobfuscate control flow of obfuscated assembly! Details:\n" + ex)); } try { XConsole.PrintInfo("Fixing strings in obfuscated assembly."); StringFixer.Fix(_obfOsuModule, _obfOsuAssembly); // Fixing strings } catch (Exception ex) { return(XConsole.PrintFatal("Unable to fix strings of obfuscated assembly! Details:\n" + ex)); } if (!Directory.Exists(CacheFolderLocation)) { XConsole.PrintInfo("Creating cache folder..."); Directory.CreateDirectory(CacheFolderLocation); } try { var nameProvider = InitializeNameProvider(); // Fixing names (SimpleNameProvider/MapperNameProvider) _obfOsuExplorer = new ModuleExplorer(_obfOsuModule, nameProvider); } catch (Exception ex) { return(XConsole.PrintFatal("Unable to get clean names for obfuscated assembly! Details:\n" + ex)); } #if DEBUG _obfOsuModule.Write(Path.Combine(Path.GetDirectoryName(_obfOsuPath), "OsuObfModule-cflow-string-nmapped.exe"), new ModuleWriterOptions(_obfOsuModule) { MetadataOptions = { Flags = DEFAULT_METADATA_FLAGS } }); #endif XConsole.PrintInfo("Done! Now patching."); bool overallSuccess = true; var failedDetails = new List <PatchResult>(); void ExecutePatchCli(Patch patch) { XConsole.PrintInfo($"{patch.Name}: ", false); if (File.Exists(ConfigFileLocation)) // incase if config file not initialized { patch.Enabled = GetConfigEnabled(patch.Name); } PatchResult res = patch.Execute(_obfOsuExplorer); switch (res.Result) { case PatchStatus.Disabled: Console.ForegroundColor = ConsoleColor.Gray; XConsole.WriteLine("DISABLED"); break; case PatchStatus.Exception: case PatchStatus.Failure: Console.ForegroundColor = ConsoleColor.Red; XConsole.WriteLine("FAIL"); failedDetails.Add(res); overallSuccess = false; break; case PatchStatus.Success: Console.ForegroundColor = ConsoleColor.Green; XConsole.WriteLine("DONE"); break; default: Console.ForegroundColor = ConsoleColor.DarkGray; XConsole.WriteLine("[???]"); break; } Console.ResetColor(); } // Executing local patches. foreach (var patch in LocalPatches.PatchList) { ExecutePatchCli(patch); } XConsole.PrintInfo("Done processing all local patches! Now processing patches from loaded add-ons..."); #if !CORE_PATCHES_ONLY // Executing all patches from all loaded plugins from all loaded assemblies (what a hierarchy) foreach (var plugin in _loadedPlugins) { XConsole.PrintInfo($"{plugin.AssemblyName}: Processing plugin: {plugin.TypeName}."); foreach (var patch in plugin.Type.GetPatches()) { ExecutePatchCli(patch); } XConsole.PrintInfo($"{plugin.AssemblyName}: Done processing: {plugin.TypeName}."); } #endif if (!File.Exists(ConfigFileLocation)) { XConsole.PrintInfo("Creating config file..."); var configLines = string.Empty; foreach (var patch in Patches) { configLines += patch.Name + " = " + "Enabled\n"; // probably bad implementation of config but simplest i can imagine } File.WriteAllText(ConfigFileLocation, configLines); XConsole.PrintInfo("Config file created!"); } XConsole.PrintInfo("Done processing all plugins."); if (failedDetails.Any()) { XConsole.PrintInfo("There's some details about failed patches."); foreach (var details in failedDetails) { details.PrintDetails(Console.Out); XConsole.WriteLine(); } } if (!overallSuccess) { XConsole.PrintInfo("There are some failed patches. Do you want to continue?"); XConsole.PrintWarn("In case of self-update pressing 'N' will leave stock version of osu! without patching it!"); XConsole.Write(XConsole.PAD + "Continue? (y/n) "); var exit = false; while (true) { var key = Console.ReadKey(true).Key; if (key == ConsoleKey.Y) { break; } if (key == ConsoleKey.N) { exit = true; break; } } XConsole.WriteLine(); if (exit) { return(Exit(XConsole.Info("Aborted by user."))); } } //string filename = Path.GetFileNameWithoutExtension(_obfOsuPath) + "-osupatch" + Path.GetExtension(_obfOsuPath); string filename = "ainu.exe"; XConsole.PrintInfo($"Saving assembly as {filename}"); try { _obfOsuModule.Write(Path.Combine(Path.GetDirectoryName(_obfOsuPath), filename), new ModuleWriterOptions(_obfOsuModule) { MetadataOptions = { Flags = DEFAULT_METADATA_FLAGS } }); } catch (Exception ex) { return(Exit(XConsole.Fatal("Unable to save patched assembly! Details:\n" + ex))); } _cleanOsuModule.Dispose(); _obfOsuModule.Dispose(); #if DEBUG Console.ReadKey(true); #endif #if LIVE_DEBUG Process.Start(new ProcessStartInfo { FileName = "cmd", Arguments = "/c timeout /T 1 /NOBREAK & move /Y \"osu!-osupatch.exe\" \"osu!.exe\"", WorkingDirectory = Environment.CurrentDirectory, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false }); #endif return(overallSuccess ? 0 : 1); }