diff --git a/.gitignore b/.gitignore
index b34a2808..1bc5b749 100644
--- a/.gitignore
+++ b/.gitignore
@@ -330,3 +330,4 @@ ASALocalRun/
.mfractor/
Assemblies/
+Referenced/
diff --git a/Source/Multiplayer_Compat.sln b/Multiplayer_Compat.sln
similarity index 54%
rename from Source/Multiplayer_Compat.sln
rename to Multiplayer_Compat.sln
index 621e893d..8ae06925 100644
--- a/Source/Multiplayer_Compat.sln
+++ b/Multiplayer_Compat.sln
@@ -1,7 +1,9 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multiplayer_Compat", "Multiplayer_Compat.csproj", "{163F72FB-89D2-4FA7-B392-D31513C473BE}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multiplayer_Compat", "Source\Multiplayer_Compat.csproj", "{163F72FB-89D2-4FA7-B392-D31513C473BE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Multiplayer_Compat_Referenced", "Source_Referenced\Multiplayer_Compat_Referenced.csproj", "{65FF869B-3A3E-418A-8734-4D70FE2D0B1F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -13,5 +15,9 @@ Global
{163F72FB-89D2-4FA7-B392-D31513C473BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{163F72FB-89D2-4FA7-B392-D31513C473BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{163F72FB-89D2-4FA7-B392-D31513C473BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {65FF869B-3A3E-418A-8734-4D70FE2D0B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {65FF869B-3A3E-418A-8734-4D70FE2D0B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {65FF869B-3A3E-418A-8734-4D70FE2D0B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {65FF869B-3A3E-418A-8734-4D70FE2D0B1F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Source/MpCompat.cs b/Source/MpCompat.cs
index 0d304d84..76c582d2 100644
--- a/Source/MpCompat.cs
+++ b/Source/MpCompat.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
using HarmonyLib;
@@ -10,11 +11,17 @@ namespace Multiplayer.Compat
{
public class MpCompat : Mod
{
+ const string REFERENCES_FOLDER = "References";
internal static readonly Harmony harmony = new Harmony("rimworld.multiplayer.compat");
public MpCompat(ModContentPack content) : base(content)
{
- if (!MP.enabled) return;
+ if (!MP.enabled) {
+ Log.Warning($"MPCompat :: Multiplayer is disabled. Running in Reference Building mode.\nPut any reference building requests under {REFERENCES_FOLDER} as {{DLLNAME}}.txt");
+ ReferenceBuilder.Restore(Path.Combine(content.RootDir, REFERENCES_FOLDER));
+ Log.Warning($"MPCompat :: Done Rebuilding. Bailing out...");
+ return;
+ }
MpCompatLoader.Load(content);
harmony.PatchAll();
diff --git a/Source/MpCompatLoader.cs b/Source/MpCompatLoader.cs
index 08b6fd47..ddebc904 100644
--- a/Source/MpCompatLoader.cs
+++ b/Source/MpCompatLoader.cs
@@ -1,6 +1,8 @@
using System;
+using System.IO;
using System.Linq;
using System.Reflection;
+using Mono.Cecil;
using Verse;
namespace Multiplayer.Compat
@@ -9,9 +11,43 @@ public static class MpCompatLoader
{
internal static void Load(ModContentPack content)
{
+ LoadConditional(content);
+
foreach (var asm in content.assemblies.loadedAssemblies)
InitCompatInAsm(asm);
}
+
+ static void LoadConditional(ModContentPack content)
+ {
+ var asmPath = ModContentPack
+ .GetAllFilesForModPreserveOrder(content, "Referenced/", f => f.ToLower() == ".dll")
+ .FirstOrDefault(f => f.Item2.Name == "Multiplayer_Compat_Referenced.dll")?.Item2;
+
+ if (asmPath == null)
+ {
+ return;
+ }
+
+ var asm = AssemblyDefinition.ReadAssembly(asmPath.FullName);
+
+ foreach (var t in asm.MainModule.GetTypes().ToArray())
+ {
+ var attr = t.CustomAttributes
+ .FirstOrDefault(a => a.Constructor.DeclaringType.Name == nameof(MpCompatForAttribute));
+ if (attr == null) continue;
+
+ var modId = (string)attr.ConstructorArguments.First().Value;
+ var mod = LoadedModManager.RunningMods.FirstOrDefault(m => m.PackageId.NoModIdSuffix() == modId);
+ if (mod == null)
+ asm.MainModule.Types.Remove(t);
+ }
+
+ var stream = new MemoryStream();
+ asm.Write(stream);
+
+ var loadedAsm = AppDomain.CurrentDomain.Load(stream.ToArray());
+ InitCompatInAsm(loadedAsm);
+ }
static void InitCompatInAsm(Assembly asm)
{
diff --git a/Source/Multiplayer_Compat.csproj b/Source/Multiplayer_Compat.csproj
index 681b9eb3..4495294b 100644
--- a/Source/Multiplayer_Compat.csproj
+++ b/Source/Multiplayer_Compat.csproj
@@ -10,6 +10,7 @@
false
None
+
2.1.*
diff --git a/Source/ReferenceBuilder.cs b/Source/ReferenceBuilder.cs
new file mode 100644
index 00000000..7d0e3196
--- /dev/null
+++ b/Source/ReferenceBuilder.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using Mono.Cecil;
+using Verse;
+
+namespace Multiplayer.Compat
+{
+ static class ReferenceBuilder
+ {
+ public static void Restore(string refsFolder)
+ {
+ var requestedFiles = Directory.CreateDirectory(refsFolder).GetFiles("*.txt");
+
+ foreach(var request in requestedFiles)
+ {
+ BuildReference(refsFolder, request);
+ }
+ }
+
+ static void BuildReference(string refsFolder, FileInfo request)
+ {
+ var asmId = Path.GetFileNameWithoutExtension(request.Name);
+
+ var assembly = LoadedModManager.RunningModsListForReading
+ .SelectMany(m => ModContentPack.GetAllFilesForModPreserveOrder(m, "Assemblies/", f => f.ToLower() == ".dll"))
+ .FirstOrDefault(f => f.Item2.Name == asmId + ".dll")?.Item2;
+
+ var hash = ComputeHash(assembly.FullName);
+ var hashFile = Path.Combine(refsFolder, asmId + ".txt");
+
+ if (File.Exists(hashFile) && File.ReadAllText(hashFile) == hash)
+ return;
+
+ Console.WriteLine($"MpCompat References: Writing {asmId}.dll");
+
+ var outFile = Path.Combine(refsFolder, asmId + ".dll");
+ var asmDef = AssemblyDefinition.ReadAssembly(assembly.FullName);
+
+ foreach (var t in asmDef.MainModule.GetTypes())
+ {
+ if (t.IsNested)
+ t.IsNestedPublic = true;
+ else
+ t.IsPublic = true;
+
+ foreach (var m in t.Methods)
+ {
+ m.IsPublic = true;
+ m.Body = new Mono.Cecil.Cil.MethodBody(m);
+ }
+
+ foreach (var f in t.Fields)
+ {
+ f.IsInitOnly = false;
+ f.IsPublic = true;
+ }
+ }
+
+ asmDef.Write(outFile);
+ File.WriteAllText(hashFile, hash);
+ }
+
+ static string ComputeHash(string assemblyPath)
+ {
+ var res = new StringBuilder();
+
+ using var hash = SHA1.Create();
+ using FileStream file = File.Open(assemblyPath, FileMode.Open, FileAccess.Read);
+
+ hash.ComputeHash(file);
+
+ foreach (byte b in hash.Hash)
+ res.Append(b.ToString("X2"));
+
+ return res.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source_Referenced/Multiplayer_Compat_Referenced.csproj b/Source_Referenced/Multiplayer_Compat_Referenced.csproj
new file mode 100644
index 00000000..6ed87e40
--- /dev/null
+++ b/Source_Referenced/Multiplayer_Compat_Referenced.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net472
+ 9.0
+ false
+ false
+ ..\Referenced\
+ false
+ false
+ None
+
+
+
+ false
+
+
+ 2.1.*
+ runtime
+
+
+ 0.3.0
+ runtime
+
+
+ 1.3.*
+ runtime
+
+
+ false
+
+
+