feature: first
All checks were successful
Build / Compute Version (push) Successful in 4s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 2m47s
Build / Build Nuget package (CringePlugins) (push) Successful in 5m31s
Build / Build Nuget package (NuGet) (push) Successful in 6m2s
Build / Build Nuget package (SharedCringe) (push) Successful in 7m25s
Build / Build Launcher (push) Successful in 9m11s
All checks were successful
Build / Compute Version (push) Successful in 4s
Build / Build Nuget package (CringeBootstrap.Abstractions) (push) Successful in 2m47s
Build / Build Nuget package (CringePlugins) (push) Successful in 5m31s
Build / Build Nuget package (NuGet) (push) Successful in 6m2s
Build / Build Nuget package (SharedCringe) (push) Successful in 7m25s
Build / Build Launcher (push) Successful in 9m11s
This commit is contained in:
19
CringeLauncher/Patches/DarkTardMissingNamespacePatch.cs
Normal file
19
CringeLauncher/Patches/DarkTardMissingNamespacePatch.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.World;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptManager), nameof(MyScriptManager.Init))]
|
||||
public static class DarkTardMissingNamespacePatch
|
||||
{
|
||||
private static void Prefix(Dictionary<string, string> ___m_compatibilityChanges)
|
||||
{
|
||||
___m_compatibilityChanges["using System.Runtime.Remoting.Metadata.W3cXsd2001;"] = "";
|
||||
___m_compatibilityChanges["using System.IO.Ports;"] = "";
|
||||
___m_compatibilityChanges["using System.Runtime.Remoting;"] = "";
|
||||
___m_compatibilityChanges["using System.Runtime.Remoting.Messaging;"] = "";
|
||||
___m_compatibilityChanges["using System.Numerics;"] = "";
|
||||
___m_compatibilityChanges["using System.Runtime.Remoting.Lifetime;"] = "";
|
||||
___m_compatibilityChanges["using System.Net.Configuration;"] = "";
|
||||
}
|
||||
}
|
30
CringeLauncher/Patches/EosInitPatch.cs
Normal file
30
CringeLauncher/Patches/EosInitPatch.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
// eos is disabled
|
||||
// [HarmonyPatch]
|
||||
public class EosInitPatch
|
||||
{
|
||||
private static MethodInfo TargetMethod() =>
|
||||
AccessTools.Method(Type.GetType("VRage.EOS.MyEOSNetworking, VRage.EOS", true), "Init");
|
||||
|
||||
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
var ins = instructions.ToList();
|
||||
|
||||
var stIndex = ins.FindIndex(b => b.opcode == OpCodes.Stloc_1);
|
||||
|
||||
ins.InsertRange(stIndex, new []
|
||||
{
|
||||
new CodeInstruction(OpCodes.Dup),
|
||||
new(OpCodes.Ldc_I4_2), // PlatformFlags.DisableOverlay
|
||||
new(OpCodes.Conv_I8),
|
||||
CodeInstruction.Call("Epic.OnlineServices.Platform.Options:set_Flags"),
|
||||
});
|
||||
|
||||
return ins;
|
||||
}
|
||||
}
|
122
CringeLauncher/Patches/IntrospectionPatches.cs
Normal file
122
CringeLauncher/Patches/IntrospectionPatches.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Xml.Serialization;
|
||||
using CringeBootstrap.Abstractions;
|
||||
using CringePlugins.Utils;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.World;
|
||||
using VRage;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Game;
|
||||
using VRage.Game.Common;
|
||||
using VRage.Game.Components;
|
||||
using VRage.Game.Definitions;
|
||||
using VRage.Game.Entity.UseObject;
|
||||
using VRage.ObjectBuilders;
|
||||
using VRage.ObjectBuilders.Private;
|
||||
using VRage.Plugins;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class IntrospectionPatches
|
||||
{
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(Assembly), nameof(Assembly.GetTypes))]
|
||||
private static bool GetTypesPrefix(Assembly __instance, ref Type[] __result)
|
||||
{
|
||||
if (AssemblyLoadContext.GetLoadContext(__instance) is ICoreLoadContext || __instance.FullName?.StartsWith("System.") == true)
|
||||
return true;
|
||||
|
||||
Debug.WriteLine($"Blocking GetTypes for {__instance.FullName}");
|
||||
|
||||
__result = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(AccessTools), nameof(AccessTools.GetTypesFromAssembly))]
|
||||
private static bool GetTypesHarmonyPrefix(Assembly assembly, ref Type[] __result)
|
||||
{
|
||||
if (AssemblyLoadContext.GetLoadContext(assembly) is ICoreLoadContext)
|
||||
return true;
|
||||
|
||||
__result = IntrospectionContext.Global.CollectAttributedTypes<HarmonyAttribute>(assembly.GetMainModule())
|
||||
.ToArray();
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(MySession), "PrepareBaseSession", typeof(MyObjectBuilder_Checkpoint), typeof(MyObjectBuilder_Sector))]
|
||||
private static void PrepareSessionPrefix()
|
||||
{
|
||||
// i hate keen for that in MyUseObjectFactory..cctor
|
||||
// MyUseObjectFactory.RegisterAssemblyTypes(Assembly.LoadFrom(Path.Combine(MyFileSystem.ExePath, "Sandbox.Game.dll")));
|
||||
|
||||
MyUseObjectFactory.RegisterAssemblyTypes(MyPlugins.SandboxGameAssembly);
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(MyPlugins), nameof(MyPlugins.LoadPlugins))]
|
||||
private static bool LoadPluginsPrefix(List<Assembly> assemblies, List<IPlugin> ___m_plugins, List<IHandleInputPlugin> ___m_handleInputPlugins)
|
||||
{
|
||||
foreach (var type in assemblies.SelectMany(b => IntrospectionContext.Global.CollectDerivedTypes<IPlugin>(b.GetMainModule())))
|
||||
{
|
||||
var instance = Activator.CreateInstance(type);
|
||||
|
||||
if (instance is IPlugin plugin)
|
||||
___m_plugins.Add(plugin);
|
||||
|
||||
if (instance is IHandleInputPlugin handleInputPlugin)
|
||||
___m_handleInputPlugins.Add(handleInputPlugin);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(MyXmlSerializerManager), "TryLoadSerializerFrom")]
|
||||
private static bool LoadSerializerPrefix(string assemblyName, string typeName, ref XmlSerializer? __result)
|
||||
{
|
||||
if (AssemblyLoadContext.GetLoadContext(typeof(IntrospectionPatches).Assembly) is not ICoreLoadContext context)
|
||||
return false;
|
||||
|
||||
var assembly = context.ResolveFromAssemblyName(new(assemblyName));
|
||||
|
||||
if (assembly?.GetType($"Microsoft.Xml.Serialization.GeneratedAssembly.{typeName}Serializer") is not { } type)
|
||||
return false;
|
||||
|
||||
__result = Activator.CreateInstance(type) as XmlSerializer;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(MyXmlSerializerManager), nameof(MyXmlSerializerManager.RegisterFromAssembly))]
|
||||
private static bool XmlManagerRegisterPrefix(Assembly assembly) => AssemblyLoadContext.GetLoadContext(assembly) is ICoreLoadContext;
|
||||
|
||||
[HarmonyPatch]
|
||||
private static class GameAssembliesPatch
|
||||
{
|
||||
private static IEnumerable<MethodInfo> TargetMethods()
|
||||
{
|
||||
return AccessTools.GetDeclaredMethods(typeof(MyPlugins))
|
||||
.Where(b => b.Name.StartsWith("Register") &&
|
||||
b.GetParameters() is [var param] && param.ParameterType == typeof(string));
|
||||
}
|
||||
|
||||
private static bool Prefix([HarmonyArgument(0)] string file)
|
||||
{
|
||||
var name = AssemblyName.GetAssemblyName(Path.Join(MyFileSystem.ExePath, file));
|
||||
|
||||
ref var staticAssembly = ref AccessTools.StaticFieldRefAccess<Assembly>(typeof(MyPlugins), name.Name switch
|
||||
{
|
||||
"Sandbox.Common" => "m_sandboxAssembly",
|
||||
"Sandbox.Game" => "m_sandboxGameAssembly",
|
||||
"SpaceEngineers.Game" => "m_gamePluginAssembly",
|
||||
"SpaceEngineers.ObjectBuilders" => "m_gameObjBuildersPlugin",
|
||||
_ => throw new ArgumentOutOfRangeException(null, $"Unknown assembly name: {name}")
|
||||
});
|
||||
|
||||
staticAssembly = AssemblyLoadContext.GetLoadContext(typeof(GameAssembliesPatch).Assembly)!
|
||||
.LoadFromAssemblyName(name);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
70
CringeLauncher/Patches/ModAssemblyLoadContextPatches.cs
Normal file
70
CringeLauncher/Patches/ModAssemblyLoadContextPatches.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.Loader;
|
||||
using CringeBootstrap.Abstractions;
|
||||
using CringeLauncher.Loader;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.World;
|
||||
using VRage.Scripting;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class ModAssemblyLoadContextPatches
|
||||
{
|
||||
private static ModAssemblyLoadContext? _currentSessionContext;
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptCompiler), nameof(MyScriptCompiler.Compile), MethodType.Async)]
|
||||
[HarmonyTranspiler]
|
||||
private static IEnumerable<CodeInstruction> CompilerTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
|
||||
{
|
||||
var matcher = new CodeMatcher(instructions);
|
||||
|
||||
var load1Method = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[]), typeof(byte[])]);
|
||||
var load2Method = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.Load), [typeof(byte[])]);
|
||||
|
||||
matcher.SearchForward(i => i.Calls(load1Method))
|
||||
.InsertAndAdvance(new(OpCodes.Ldarg_0), CodeInstruction.LoadField(original.DeclaringType, "target"))
|
||||
.SetInstruction(CodeInstruction.CallClosure((byte[] assembly, byte[] symbols, MyApiTarget target) =>
|
||||
{
|
||||
if (target is not MyApiTarget.Mod) return Assembly.Load(assembly, symbols);
|
||||
ArgumentNullException.ThrowIfNull(_currentSessionContext, "No session context");
|
||||
return _currentSessionContext.LoadFromStream(new MemoryStream(assembly), new MemoryStream(symbols));
|
||||
}));
|
||||
|
||||
matcher.SearchForward(i => i.Calls(load2Method))
|
||||
.InsertAndAdvance(new(OpCodes.Ldarg_0), CodeInstruction.LoadField(original.DeclaringType, "target"))
|
||||
.SetInstruction(CodeInstruction.CallClosure((byte[] assembly, MyApiTarget target) =>
|
||||
{
|
||||
if (target is not MyApiTarget.Mod) return Assembly.Load(assembly);
|
||||
ArgumentNullException.ThrowIfNull(_currentSessionContext, "No session context");
|
||||
return _currentSessionContext.LoadFromStream(new MemoryStream(assembly));
|
||||
}));
|
||||
|
||||
return matcher.Instructions();
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MySession), nameof(MySession.Load))]
|
||||
[HarmonyPatch(typeof(MySession), "LoadMultiplayer")]
|
||||
[HarmonyPrefix]
|
||||
private static void LoadPrefix()
|
||||
{
|
||||
if (_currentSessionContext is not null)
|
||||
throw new InvalidOperationException("Previous session context was not disposed");
|
||||
|
||||
if (AssemblyLoadContext.GetLoadContext(typeof(MySession).Assembly) is not ICoreLoadContext coreContext)
|
||||
throw new NotSupportedException("Mod loading is not supported in this context");
|
||||
|
||||
_currentSessionContext = new ModAssemblyLoadContext(coreContext);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MySession), nameof(MySession.Unload))]
|
||||
[HarmonyPostfix]
|
||||
private static void UnloadPostfix()
|
||||
{
|
||||
if (_currentSessionContext is null) return;
|
||||
|
||||
_currentSessionContext.Unload();
|
||||
_currentSessionContext = null;
|
||||
}
|
||||
}
|
309
CringeLauncher/Patches/ModScriptCompilerPatch.cs
Normal file
309
CringeLauncher/Patches/ModScriptCompilerPatch.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
#if false
|
||||
[HarmonyPatch]
|
||||
public static class ModScriptCompilerPatch
|
||||
{
|
||||
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||
private static AssemblyLoadContext _modContext = new(null, true);
|
||||
private static readonly HashSet<string> LoadedModAssemblyNames = new();
|
||||
|
||||
private static readonly ConditionalWeakTable<MyProgrammableBlock, AssemblyLoadContext> LoadContexts = new();
|
||||
|
||||
private static readonly FieldInfo InstanceField = AccessTools.Field(typeof(MyProgrammableBlock), "m_instance");
|
||||
private static readonly FieldInfo AssemblyField = AccessTools.Field(typeof(MyProgrammableBlock), "m_assembly");
|
||||
private static readonly FieldInfo CompilerErrorsField = AccessTools.Field(typeof(MyProgrammableBlock), "m_compilerErrors");
|
||||
|
||||
static ModScriptCompilerPatch()
|
||||
{
|
||||
MySession.OnUnloaded += OnUnloaded;
|
||||
|
||||
ModWhitelistAnalyzer =
|
||||
AccessTools.FieldRefAccess<MyScriptCompiler, DiagnosticAnalyzer>(
|
||||
MyScriptCompiler.Static, "m_modApiWhitelistDiagnosticAnalyzer");
|
||||
ScriptWhitelistAnalyzer =
|
||||
AccessTools.FieldRefAccess<MyScriptCompiler, DiagnosticAnalyzer>(
|
||||
MyScriptCompiler.Static, "m_ingameWhitelistDiagnosticAnalyzer");
|
||||
|
||||
MetadataReferences =
|
||||
AccessTools.FieldRefAccess<MyScriptCompiler, List<MetadataReference>>(
|
||||
MyScriptCompiler.Static, "m_metadataReferences");
|
||||
|
||||
InjectMod = AccessTools.MethodDelegate<Func<CSharpCompilation, SyntaxTree, int, SyntaxTree>>(
|
||||
AccessTools.Method(typeof(MyScriptCompiler), "InjectMod"), MyScriptCompiler.Static);
|
||||
|
||||
InjectInstructionCounter = AccessTools.MethodDelegate<Func<CSharpCompilation, SyntaxTree, SyntaxTree>>(
|
||||
AccessTools.Method(typeof(MyScriptCompiler), "InjectInstructionCounter"), MyScriptCompiler.Static);
|
||||
|
||||
EmitDiagnostics = AccessTools.MethodDelegate<Func<CompilationWithAnalyzers, EmitResult, List<Message>, bool, Task<bool>>>(
|
||||
AccessTools.Method(typeof(MyScriptCompiler), "EmitDiagnostics"), MyScriptCompiler.Static);
|
||||
|
||||
MakeAssemblyName =
|
||||
AccessTools.MethodDelegate<Func<string, string>>(AccessTools.Method(typeof(MyScriptCompiler),
|
||||
"MakeAssemblyName"));
|
||||
|
||||
CreateInstanceMethod = AccessTools.Method(typeof(MyProgrammableBlock), "CreateInstance");
|
||||
SetDetailedInfoMethod = AccessTools.Method(typeof(MyProgrammableBlock), "SetDetailedInfo");
|
||||
}
|
||||
|
||||
private static void OnUnloaded()
|
||||
{
|
||||
LoadedModAssemblyNames.Clear();
|
||||
if (!_modContext.Assemblies.Any())
|
||||
return;
|
||||
|
||||
_modContext.Unload();
|
||||
_modContext = new(null, true);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MyProgrammableBlock), "Compile")]
|
||||
[HarmonyPrefix]
|
||||
private static bool CompilePrefix(MyProgrammableBlock __instance, string program, string storage, bool instantiate,
|
||||
ref MyProgrammableBlock.ScriptTerminationReason ___m_terminationReason,
|
||||
MyIngameScriptComponent ___ScriptComponent)
|
||||
{
|
||||
if (!MySession.Static.EnableIngameScripts || __instance.CubeGrid is {IsPreview: true} or {CreatePhysics: false})
|
||||
return false;
|
||||
|
||||
___m_terminationReason = MyProgrammableBlock.ScriptTerminationReason.None;
|
||||
CompileAsync(__instance, program, storage, instantiate, ___ScriptComponent);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MyGuiScreenEditor), "CheckCodeButtonClicked")]
|
||||
[HarmonyPrefix]
|
||||
private static bool GuiCompilePrefix(List<string> ___m_compilerErrors, MyGuiScreenEditor __instance)
|
||||
{
|
||||
___m_compilerErrors.Clear();
|
||||
|
||||
var progress = new MyGuiScreenProgress(MyTexts.Get(MySpaceTexts.ProgrammableBlock_Editor_CheckingCode));
|
||||
MyScreenManager.AddScreen(progress);
|
||||
|
||||
if (__instance.Description.Text.Length > 0)
|
||||
CompileAsync(__instance, ___m_compilerErrors, __instance.Description.Text.ToString(), progress).Wait();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptCompiler), nameof(MyScriptCompiler.Compile))]
|
||||
[HarmonyPrefix]
|
||||
private static bool Prefix(ref Task<Assembly?> __result, MyApiTarget target, string assemblyName, IEnumerable<Script> scripts,
|
||||
List<Message> messages, string friendlyName, bool enableDebugInformation = false)
|
||||
{
|
||||
__result = CompileAsync(_modContext, target, assemblyName, scripts, messages, friendlyName,
|
||||
enableDebugInformation);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task CompileAsync(MyGuiScreenEditor editor, List<string> errors, string program, MyGuiScreenProgress progress)
|
||||
{
|
||||
var context = new AssemblyLoadContext(null, true);
|
||||
var messages = new List<Message>();
|
||||
var script = MyVRage.Platform.Scripting.GetIngameScript(program, "Program", nameof(MyGridProgram));
|
||||
await CompileAsync(context, MyApiTarget.Ingame, "check", new[] { script }, messages,
|
||||
"PB Code Editor");
|
||||
|
||||
errors.AddRange(messages.OrderBy(b => b.IsError ? 0 : 1).Select(b => b.Text));
|
||||
context.Unload();
|
||||
|
||||
progress.CloseScreen();
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
var sb = new StringBuilder(errors.Sum(b => b.Length + Environment.NewLine.Length));
|
||||
foreach (var error in errors)
|
||||
{
|
||||
sb.AppendLine(error);
|
||||
}
|
||||
|
||||
MyScreenManager.AddScreen(new MyGuiScreenEditorError(sb.ToString()));
|
||||
return;
|
||||
}
|
||||
|
||||
var messageBox = MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Info, MyMessageBoxButtonsType.OK,
|
||||
MyTexts.Get(MySpaceTexts.ProgrammableBlock_Editor_CompilationOk),
|
||||
MyTexts.Get(MySpaceTexts.ProgrammableBlock_CodeEditor_Title));
|
||||
MyGuiSandbox.AddScreen(messageBox);
|
||||
}
|
||||
|
||||
private static async void CompileAsync(MyProgrammableBlock block,
|
||||
string program,
|
||||
string storage,
|
||||
bool instantiate, MyIngameScriptComponent scriptComponent)
|
||||
{
|
||||
scriptComponent.NextUpdate = UpdateType.None;
|
||||
scriptComponent.NeedsUpdate = MyEntityUpdateEnum.NONE;
|
||||
|
||||
SetDetailedInfoMethod.Invoke(block, new object?[] { "Compiling..." });
|
||||
|
||||
try
|
||||
{
|
||||
if (LoadContexts.TryGetValue(block, out var context))
|
||||
{
|
||||
AccessTools.FieldRefAccess<MyProgrammableBlock, IMyGridProgram?>(block, InstanceField) = null;
|
||||
AccessTools.FieldRefAccess<MyProgrammableBlock, Assembly?>(block, AssemblyField) = null;
|
||||
context.Unload();
|
||||
}
|
||||
|
||||
LoadContexts.AddOrUpdate(block, context = new(null, true));
|
||||
|
||||
var messages = new List<Message>();
|
||||
var assembly = await CompileAsync(context, MyApiTarget.Ingame,
|
||||
$"pb_{block.EntityId}_{Random.Shared.NextInt64()}",
|
||||
new[]
|
||||
{
|
||||
MyVRage.Platform.Scripting.GetIngameScript(
|
||||
program, "Program", nameof(MyGridProgram))
|
||||
}, messages, $"PB: {block.DisplayName} ({block.EntityId})");
|
||||
|
||||
AccessTools.FieldRefAccess<MyProgrammableBlock, Assembly?>(block, AssemblyField) = assembly;
|
||||
|
||||
var errors = AccessTools.FieldRefAccess<MyProgrammableBlock, List<string>>(block, CompilerErrorsField);
|
||||
|
||||
errors.Clear();
|
||||
errors.AddRange(messages.Select(b => b.Text));
|
||||
|
||||
if (instantiate)
|
||||
MySandboxGame.Static.Invoke(
|
||||
() => CreateInstanceMethod.Invoke(block, new object?[] { assembly, errors, storage }),
|
||||
nameof(CompileAsync));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SetDetailedInfoMethod.Invoke(block, new object?[] { e.ToString() });
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Assembly?> CompileAsync(AssemblyLoadContext context, MyApiTarget target,
|
||||
string assemblyName, IEnumerable<Script> scripts,
|
||||
List<Message> messages, string? friendlyName,
|
||||
bool enableDebugInformation = false)
|
||||
{
|
||||
friendlyName ??= "<No Name>";
|
||||
var assemblyFileName = MakeAssemblyName(assemblyName);
|
||||
Func<CSharpCompilation, SyntaxTree, SyntaxTree>? syntaxTreeInjector;
|
||||
DiagnosticAnalyzer? whitelistAnalyzer;
|
||||
switch (target)
|
||||
{
|
||||
case MyApiTarget.None:
|
||||
whitelistAnalyzer = null;
|
||||
syntaxTreeInjector = null;
|
||||
break;
|
||||
case MyApiTarget.Mod:
|
||||
{
|
||||
var modId = MyModWatchdog.AllocateModId(friendlyName);
|
||||
whitelistAnalyzer = ModWhitelistAnalyzer;
|
||||
syntaxTreeInjector = (c, st) => InjectMod(c, st, modId);
|
||||
|
||||
//skip if name exists already
|
||||
if (!LoadedModAssemblyNames.Add(assemblyFileName))
|
||||
{
|
||||
Console.WriteLine($"{assemblyFileName} is already loaded, skipping");
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MyApiTarget.Ingame:
|
||||
syntaxTreeInjector = InjectInstructionCounter;
|
||||
whitelistAnalyzer = ScriptWhitelistAnalyzer;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(target), target, "Invalid compilation target");
|
||||
}
|
||||
var compilation = CreateCompilation(assemblyFileName, scripts);
|
||||
var compilationWithoutInjection = compilation;
|
||||
var injectionFailed = false;
|
||||
|
||||
if (syntaxTreeInjector != null)
|
||||
{
|
||||
SyntaxTree[]? newSyntaxTrees = null;
|
||||
try
|
||||
{
|
||||
var syntaxTrees = compilation.SyntaxTrees;
|
||||
if (syntaxTrees.Length == 1)
|
||||
{
|
||||
newSyntaxTrees = new[] { syntaxTreeInjector(compilation, syntaxTrees[0]) };
|
||||
}
|
||||
else
|
||||
{
|
||||
var compilation1 = compilation;
|
||||
newSyntaxTrees = await Task
|
||||
.WhenAll(syntaxTrees.Select(
|
||||
x => Task.Run(() => syntaxTreeInjector(compilation1, x)))).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Warn(e);
|
||||
injectionFailed = true;
|
||||
}
|
||||
|
||||
if (newSyntaxTrees is not null)
|
||||
compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSyntaxTrees);
|
||||
|
||||
}
|
||||
CompilationWithAnalyzers? analyticCompilation = null;
|
||||
if (whitelistAnalyzer != null)
|
||||
{
|
||||
analyticCompilation = compilation.WithAnalyzers(ImmutableArray.Create(whitelistAnalyzer));
|
||||
compilation = (CSharpCompilation)analyticCompilation.Compilation;
|
||||
}
|
||||
|
||||
using var assemblyStream = new MemoryStream();
|
||||
|
||||
var emitResult = compilation.Emit(assemblyStream);
|
||||
var success = emitResult.Success;
|
||||
var myBlacklistSyntaxVisitor = new MyBlacklistSyntaxVisitor();
|
||||
foreach (var syntaxTree in compilation.SyntaxTrees)
|
||||
{
|
||||
myBlacklistSyntaxVisitor.SetSemanticModel(compilation.GetSemanticModel(syntaxTree, false));
|
||||
myBlacklistSyntaxVisitor.Visit(await syntaxTree.GetRootAsync());
|
||||
}
|
||||
if (myBlacklistSyntaxVisitor.HasAnyResult())
|
||||
{
|
||||
myBlacklistSyntaxVisitor.GetResultMessages(messages);
|
||||
}
|
||||
else
|
||||
{
|
||||
success = await EmitDiagnostics(analyticCompilation, emitResult, messages, success).ConfigureAwait(false);
|
||||
assemblyStream.Seek(0, SeekOrigin.Begin);
|
||||
if (injectionFailed) return null;
|
||||
if (success)
|
||||
return context.LoadFromStream(assemblyStream);
|
||||
|
||||
await EmitDiagnostics(analyticCompilation, compilationWithoutInjection.Emit(assemblyStream), messages,
|
||||
false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static readonly CSharpCompilationOptions CompilationOptions =
|
||||
new(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, platform: Platform.X64);
|
||||
private static readonly CSharpParseOptions ParseOptions = new(LanguageVersion.CSharp11, DocumentationMode.None);
|
||||
private static readonly DiagnosticAnalyzer ModWhitelistAnalyzer;
|
||||
private static readonly DiagnosticAnalyzer ScriptWhitelistAnalyzer;
|
||||
private static readonly List<MetadataReference> MetadataReferences;
|
||||
private static readonly Func<CSharpCompilation, SyntaxTree, int, SyntaxTree> InjectMod;
|
||||
private static readonly Func<CSharpCompilation, SyntaxTree, SyntaxTree> InjectInstructionCounter;
|
||||
private static readonly Func<CompilationWithAnalyzers, EmitResult, List<Message>, bool, Task<bool>> EmitDiagnostics;
|
||||
private static readonly Func<string, string> MakeAssemblyName;
|
||||
private static readonly MethodInfo CreateInstanceMethod;
|
||||
private static readonly MethodInfo SetDetailedInfoMethod;
|
||||
|
||||
|
||||
private static CSharpCompilation CreateCompilation(string assemblyFile, IEnumerable<Script>? scripts)
|
||||
{
|
||||
if (scripts == null)
|
||||
return CSharpCompilation.Create(assemblyFile, null, MetadataReferences,
|
||||
CompilationOptions);
|
||||
|
||||
|
||||
var parseOptions = ParseOptions.WithPreprocessorSymbols(MyScriptCompiler.Static.ConditionalCompilationSymbols);
|
||||
var enumerable = scripts.Select(s => CSharpSyntaxTree.ParseText(s.Code, parseOptions, s.Name, Encoding.UTF8));
|
||||
|
||||
return CSharpCompilation.Create(assemblyFile, enumerable, MetadataReferences, CompilationOptions);
|
||||
}
|
||||
}
|
||||
#endif
|
22
CringeLauncher/Patches/RenderHookPatch.cs
Normal file
22
CringeLauncher/Patches/RenderHookPatch.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Windows.Win32.Foundation;
|
||||
using HarmonyLib;
|
||||
using SharpDX.DXGI;
|
||||
using VRage.Platform.Windows.Forms;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public class RenderHookPatch
|
||||
{
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(SwapChain), nameof(SwapChain.Present))]
|
||||
private static void PresentPrefix()
|
||||
{
|
||||
ImGuiHandler.Instance?.DoRender();
|
||||
}
|
||||
|
||||
[HarmonyPostfix, HarmonyPatch(typeof(MyGameForm), "OnLoad")]
|
||||
private static void LoadPostfix(MyGameForm __instance)
|
||||
{
|
||||
ImGuiHandler.Instance?.HookWindow((HWND)__instance.Handle);
|
||||
}
|
||||
}
|
10
CringeLauncher/Patches/RenderInitPatch.cs
Normal file
10
CringeLauncher/Patches/RenderInitPatch.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using HarmonyLib;
|
||||
using SpaceEngineers.Game;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(SpaceEngineersGame), "InitializeRender")]
|
||||
public static class RenderInitPatch
|
||||
{
|
||||
private static bool Prefix() => false;
|
||||
}
|
30
CringeLauncher/Patches/ScriptCompilerInitializationPatch.cs
Normal file
30
CringeLauncher/Patches/ScriptCompilerInitializationPatch.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using VRage.Scripting;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class ScriptCompilerInitializationPatch
|
||||
{
|
||||
private static MethodInfo TargetMethod()
|
||||
{
|
||||
return AccessTools.Method(Type.GetType("VRage.Scripting.MyVRageScriptingInternal, VRage.Scripting", true),
|
||||
"Initialize");
|
||||
}
|
||||
|
||||
private static bool Prefix(Thread updateThread, Type[] referencedTypes, string[] symbols)
|
||||
{
|
||||
MyModWatchdog.Init(updateThread);
|
||||
MyScriptCompiler.Static.AddImplicitIngameNamespacesFromTypes(referencedTypes);
|
||||
MyScriptCompiler.Static.AddConditionalCompilationSymbols(symbols);
|
||||
|
||||
using var batch = MyScriptCompiler.Static.Whitelist.OpenBatch();
|
||||
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(ConcurrentQueue<>));
|
||||
batch.AllowNamespaceOfTypes(MyWhitelistTarget.Both, typeof(ImmutableArray));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
85
CringeLauncher/Patches/SwapChainPatch.cs
Normal file
85
CringeLauncher/Patches/SwapChainPatch.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using HarmonyLib;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
using VRage.Platform.Windows.Render;
|
||||
using VRage.Render11.Resources;
|
||||
using VRageRender;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class SwapChainPatch
|
||||
{
|
||||
internal static nint WindowHandle;
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(MyPlatformRender), nameof(MyPlatformRender.CreateSwapChain))]
|
||||
private static bool SwapChainPrefix(nint windowHandle)
|
||||
{
|
||||
WindowHandle = windowHandle;
|
||||
MyPlatformRender.DisposeSwapChain();
|
||||
MyPlatformRender.Log.WriteLine("CreateDeviceInternal create swapchain");
|
||||
|
||||
if (MyPlatformRender.m_swapchain != null)
|
||||
return false;
|
||||
|
||||
var chainDescription = new SwapChainDescription
|
||||
{
|
||||
BufferCount = 2,
|
||||
Flags = SwapChainFlags.AllowModeSwitch,
|
||||
IsWindowed = true,
|
||||
ModeDescription = MyPlatformRender.GetCurrentModeDescriptor(MyPlatformRender.m_settings) with
|
||||
{
|
||||
Format = Format.R8G8B8A8_UNorm
|
||||
},
|
||||
SampleDescription = {
|
||||
Count = 1,
|
||||
Quality = 0
|
||||
},
|
||||
OutputHandle = windowHandle,
|
||||
Usage = Usage.ShaderInput | Usage.RenderTargetOutput,
|
||||
SwapEffect = SwapEffect.Discard
|
||||
};
|
||||
|
||||
var factory = MyPlatformRender.GetFactory();
|
||||
try
|
||||
{
|
||||
MyPlatformRender.m_swapchain = new SwapChain(factory, MyPlatformRender.DeviceInstance, chainDescription);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
MyPlatformRender.Log.WriteLine("SwapChain factory = " + factory);
|
||||
MyPlatformRender.Log.WriteLine("SwapChain Device = " + MyPlatformRender.DeviceInstance);
|
||||
MyPlatformRender.PrintSwapChainDescriptionToLog(chainDescription);
|
||||
throw;
|
||||
}
|
||||
factory.MakeWindowAssociation(windowHandle, WindowAssociationFlags.IgnoreAll);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPostfix, HarmonyPatch(typeof(MyRender11), nameof(MyRender11.CreateDeviceInternal))]
|
||||
private static void CreateDevicePostfix()
|
||||
{
|
||||
ImGuiHandler.Instance ??= new ImGuiHandler(WindowHandle, MyRender11.DeviceInstance, MyRender11.RC.DeviceContext);
|
||||
}
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(MyBackbuffer), MethodType.Constructor, typeof(SharpDX.Direct3D11.Resource))]
|
||||
private static bool SwapChainBBPrefix(MyBackbuffer __instance, SharpDX.Direct3D11.Resource swapChainBB)
|
||||
{
|
||||
__instance.m_resource = swapChainBB;
|
||||
__instance.m_rtv = new RenderTargetView(MyRender11.DeviceInstance, swapChainBB, new()
|
||||
{
|
||||
Format = Format.R8G8B8A8_UNorm_SRgb,
|
||||
Dimension = RenderTargetViewDimension.Texture2D,
|
||||
});
|
||||
__instance.m_srv = new ShaderResourceView(MyRender11.DeviceInstance, swapChainBB);
|
||||
|
||||
ImGuiHandler.Rtv = new RenderTargetView(MyRender11.DeviceInstance, swapChainBB, new()
|
||||
{
|
||||
Format = Format.R8G8B8A8_UNorm,
|
||||
Dimension = RenderTargetViewDimension.Texture2D,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
14
CringeLauncher/Patches/TypeTableRegisterPatch.cs
Normal file
14
CringeLauncher/Patches/TypeTableRegisterPatch.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using HarmonyLib;
|
||||
using VRage.Network;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(MyTypeTable), "IsSerializableClass")]
|
||||
public static class TypeTableRegisterPatch
|
||||
{
|
||||
private static void Postfix(Type type, ref bool __result)
|
||||
{
|
||||
if (type == typeof(Delegate) || type == typeof(MulticastDelegate))
|
||||
__result = true;
|
||||
}
|
||||
}
|
20
CringeLauncher/Patches/WhitelistAllowPatch.cs
Normal file
20
CringeLauncher/Patches/WhitelistAllowPatch.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using VRage.Scripting;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class WhitelistAllowPatch
|
||||
{
|
||||
private static MethodInfo TargetMethod()
|
||||
{
|
||||
return AccessTools.Method(AccessTools.Inner(typeof(MyScriptWhitelist), "MyWhitelistBatch"), "AllowMembers");
|
||||
}
|
||||
|
||||
private static void Prefix(ref MemberInfo[] members)
|
||||
{
|
||||
if (members.Any(b => b is null))
|
||||
members = members.Where(b => b is { }).ToArray();
|
||||
}
|
||||
}
|
46
CringeLauncher/Patches/WhitelistPatch.cs
Normal file
46
CringeLauncher/Patches/WhitelistPatch.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using HarmonyLib;
|
||||
using VRage.FileSystem;
|
||||
using VRage.Scripting;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(MyScriptWhitelist), MethodType.Constructor, typeof(MyScriptCompiler))]
|
||||
public static class WhitelistPatch
|
||||
{
|
||||
private static void Prefix(MyScriptCompiler scriptCompiler)
|
||||
{
|
||||
var baseDir = new FileInfo(typeof(Type).Assembly.Location).DirectoryName!;
|
||||
|
||||
scriptCompiler.AddReferencedAssemblies(
|
||||
typeof(Type).Assembly.Location,
|
||||
typeof(LinkedList<>).Assembly.Location,
|
||||
typeof(Regex).Assembly.Location,
|
||||
typeof(Enumerable).Assembly.Location,
|
||||
typeof(ConcurrentBag<>).Assembly.Location,
|
||||
typeof(ImmutableArray).Assembly.Location,
|
||||
typeof(PropertyChangedEventArgs).Assembly.Location,
|
||||
typeof(TypeConverter).Assembly.Location,
|
||||
typeof(System.Diagnostics.TraceSource).Assembly.Location,
|
||||
typeof(System.Security.Policy.Evidence).Assembly.Location,
|
||||
Path.Combine(baseDir, "System.Xml.ReaderWriter.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "ProtoBuf.Net.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "ProtoBuf.Net.Core.dll"),
|
||||
Path.Combine(baseDir, "netstandard.dll"),
|
||||
Path.Combine(baseDir, "System.Runtime.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "Sandbox.Game.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "Sandbox.Common.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "Sandbox.Graphics.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "VRage.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "VRage.Library.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "VRage.Math.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "VRage.Game.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "VRage.Render.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "VRage.Input.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "SpaceEngineers.ObjectBuilders.dll"),
|
||||
Path.Combine(MyFileSystem.ExePath, "SpaceEngineers.Game.dll"));
|
||||
}
|
||||
}
|
37
CringeLauncher/Patches/WhitelistRegistrationPatch.cs
Normal file
37
CringeLauncher/Patches/WhitelistRegistrationPatch.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using VRage.Scripting;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class WhitelistRegistrationPatch
|
||||
{
|
||||
private static IEnumerable<MethodInfo> TargetMethods()
|
||||
{
|
||||
yield return AccessTools.Method(typeof(MyScriptWhitelist), "Register",
|
||||
new[] { typeof(MyWhitelistTarget), typeof(INamespaceSymbol), typeof(Type) });
|
||||
yield return AccessTools.Method(typeof(MyScriptWhitelist), "Register",
|
||||
new[] { typeof(MyWhitelistTarget), typeof(ITypeSymbol), typeof(Type) });
|
||||
}
|
||||
|
||||
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
var ins = instructions.ToList();
|
||||
var throwIns = ins.FindAll(b => b.opcode == OpCodes.Throw).Select(b => ins.IndexOf(b));
|
||||
foreach (var index in throwIns)
|
||||
{
|
||||
var i = index;
|
||||
do
|
||||
{
|
||||
ins[i] = new(OpCodes.Nop);
|
||||
} while (ins[--i].opcode.FlowControl != FlowControl.Cond_Branch);
|
||||
|
||||
ins[index] = new(OpCodes.Ret);
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
}
|
36
CringeLauncher/Patches/XmlRootWriterPatch.cs
Normal file
36
CringeLauncher/Patches/XmlRootWriterPatch.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection.Emit;
|
||||
using System.Xml;
|
||||
using HarmonyLib;
|
||||
using VRage;
|
||||
|
||||
namespace CringeLauncher.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(CustomRootWriter), "Init")]
|
||||
public class XmlRootWriterPatch
|
||||
{
|
||||
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
var ins = instructions.ToList();
|
||||
|
||||
var index = ins.FindIndex(b =>
|
||||
b.opcode == OpCodes.Ldstr && b.operand is "xsi:type");
|
||||
ins[index].operand = "xsi";
|
||||
|
||||
ins.InsertRange(index + 1, new[]
|
||||
{
|
||||
new CodeInstruction(OpCodes.Ldstr, "type"),
|
||||
new CodeInstruction(OpCodes.Ldstr, "http://www.w3.org/2001/XMLSchema-instance")
|
||||
});
|
||||
|
||||
var instruction = ins[ins.FindIndex(b => b.opcode == OpCodes.Callvirt)];
|
||||
instruction.operand = AccessTools.Method(typeof(XmlWriter), "WriteAttributeString", new[]
|
||||
{
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string),
|
||||
typeof(string)
|
||||
});
|
||||
|
||||
return ins;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user