MSCO 2.9.0: standalone without MSCLoader (Unity Doorstop)
- Bootstrap: Doorstop entry point, loads Harmony/Newtonsoft from the MSCO folder, hooks PlayMakerArrayListProxy.Awake and starts MSCO in the main menu; skips the news screen - No MSCLoader reference anymore; key bindings in PlayerPrefs, bind console command - Harmony 1.2 and Newtonsoft.Json in lib/ (MIT notices); build copies to <game>\MSCO - README/CHANGELOG updated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+143
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Harmony;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Doorstop
|
||||
{
|
||||
/// <summary>
|
||||
/// Einstieg ohne MSCLoader: Unity Doorstop (winhttp.dll im Spielordner) ruft beim Spielstart diese Methode auf - der
|
||||
/// MSCO Launcher startet das Spiel mit "--doorstop-target-assembly ...\MSCO\MSCO.dll", MSCLoader wird dann gar nicht
|
||||
/// geladen. Hier läuft noch nichts von Unity: nur vorbereiten, gestartet wird im Hauptmenü (siehe MSCO.Bootstrap).
|
||||
/// </summary>
|
||||
public static class Entrypoint
|
||||
{
|
||||
public static void Start()
|
||||
{
|
||||
MSCO.Bootstrap.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MSCO
|
||||
{
|
||||
/// <summary>
|
||||
/// Startet MSCO eigenständig: Abhängigkeiten (Harmony, Newtonsoft.Json) aus dem MSCO-Ordner laden, warten bis das
|
||||
/// Spiel seinen Code geladen hat, dann beim ersten PlayMakerArrayListProxy.Awake (Hauptmenü, Hauptthread) den
|
||||
/// MultiplayerManager anlegen. Protokoll: <Spiel>\MSCO\MSCO_Loader.txt.
|
||||
/// </summary>
|
||||
public static class Bootstrap
|
||||
{
|
||||
static string folder;
|
||||
static bool patched, started;
|
||||
static StreamWriter log;
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
log = new StreamWriter(Path.Combine(folder, "MSCO_Loader.txt"), false) { AutoFlush = true };
|
||||
Log("MSCO " + MSCO.ModVersion + " loader started (standalone, without MSCLoader)");
|
||||
Log("Command line: " + string.Join(" ", Environment.GetCommandLineArgs()));
|
||||
AppDomain.CurrentDomain.AssemblyResolve += Resolve;
|
||||
AppDomain.CurrentDomain.AssemblyLoad += (_, e) => OnAssemblyLoaded(e.LoadedAssembly);
|
||||
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies()) OnAssemblyLoaded(a);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log("Loader failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Log(string line)
|
||||
{
|
||||
try { if (log != null) log.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " " + line); } catch { }
|
||||
}
|
||||
|
||||
/// <summary>Unsere Abhängigkeiten liegen neben MSCO.dll (das Spiel sucht nur in Managed).</summary>
|
||||
static Assembly Resolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string name = new AssemblyName(args.Name).Name;
|
||||
string file = Path.Combine(folder, name + ".dll");
|
||||
if (File.Exists(file)) return Assembly.LoadFrom(file);
|
||||
}
|
||||
catch (Exception e) { Log("Resolve " + args.Name + " failed: " + e.Message); }
|
||||
return null;
|
||||
}
|
||||
|
||||
static void OnAssemblyLoaded(Assembly assembly)
|
||||
{
|
||||
if (patched) return;
|
||||
string name;
|
||||
try { name = assembly.GetName().Name; } catch { return; }
|
||||
if (name != "Assembly-CSharp") return;
|
||||
patched = true;
|
||||
try { Patch(assembly); }
|
||||
catch (Exception e) { Log("Could not hook into the game: " + e); }
|
||||
}
|
||||
|
||||
/// <summary>Haken in PlayMakerArrayListProxy.Awake (läuft im Hauptmenü früh auf dem Hauptthread).</summary>
|
||||
static void Patch(Assembly game)
|
||||
{
|
||||
Type proxy = game.GetType("PlayMakerArrayListProxy");
|
||||
MethodInfo awake = proxy != null ? proxy.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) : null;
|
||||
if (awake == null)
|
||||
{
|
||||
Log("PlayMakerArrayListProxy.Awake not found - MSCO cannot start");
|
||||
return;
|
||||
}
|
||||
HarmonyInstance.Create("mbm.msco.bootstrap").Patch(awake,
|
||||
new HarmonyMethod(typeof(Bootstrap).GetMethod("BeforeAwake", BindingFlags.Static | BindingFlags.NonPublic)), null);
|
||||
Log("Hooked into the game, waiting for the main menu");
|
||||
}
|
||||
|
||||
static void BeforeAwake()
|
||||
{
|
||||
if (started) return;
|
||||
started = true;
|
||||
try
|
||||
{
|
||||
var go = new GameObject("MSCO_Host");
|
||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||
go.AddComponent<MscoHost>();
|
||||
Log("MSCO started");
|
||||
}
|
||||
catch (Exception e) { Log("Start failed: " + e); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Lebt über alle Szenen: legt im Hauptmenü den MultiplayerManager an (früher MSCLoaders OnMenuLoad).</summary>
|
||||
public class MscoHost : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
MscoLog.Info("Welcome to MSCO by mbm.wtf");
|
||||
// Den Nachrichten-Bildschirm des Spiels ("Latest news ... press any key") überspringen - direkt ins Hauptmenü
|
||||
string scene = Application.loadedLevelName;
|
||||
Bootstrap.Log("First scene: " + scene);
|
||||
if (scene != "MainMenu" && scene != "GAME")
|
||||
{
|
||||
Bootstrap.Log("Skipping the intro/news screen");
|
||||
Application.LoadLevel("MainMenu");
|
||||
return;
|
||||
}
|
||||
MultiplayerManager.Ensure();
|
||||
}
|
||||
|
||||
void OnLevelWasLoaded(int level)
|
||||
{
|
||||
Bootstrap.Log("Level loaded: " + Application.loadedLevelName);
|
||||
MultiplayerManager.Ensure();
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
// Wer beendet das Spiel? (Diagnose)
|
||||
Bootstrap.Log("Game is quitting (scene " + Application.loadedLevelName + "): " + System.Environment.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user