Skip to content

Commit

Permalink
Added potential VEH that is disabled for now
Browse files Browse the repository at this point in the history
Fixed a missing call for the Steam's launcher handling
Transpile Game's Main instead of prefixing
  • Loading branch information
Aragas committed May 4, 2024
1 parent 221e0ea commit 38b44e3
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 40 deletions.
26 changes: 17 additions & 9 deletions src/Bannerlord.BLSE.Shared/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
using Bannerlord.BLSE.Features.Interceptor;
using Bannerlord.BLSE.Features.Xbox;
using Bannerlord.BLSE.Shared.Utils;
using Bannerlord.LauncherEx;

using HarmonyLib;
using HarmonyLib.BUTR.Extensions;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

using Windows.Win32;
Expand All @@ -34,15 +35,24 @@ public static void Launch(string[] args)
XboxFeature.Enable(_featureHarmony);

ModuleInitializer.Disable();

GameOriginalEntrypointHandler.Initialize();

_featureHarmony.TryPatch(
AccessTools2.DeclaredMethod("TaleWorlds.Starter.Library.Program:Main"),
prefix: SymbolExtensions2.GetMethodInfo(static (string[] x) => MainPrefix(ref x)));
SymbolExtensions2.GetMethodInfo((string[] args_) => TaleWorlds.Starter.Library.Program.Main(args_)),
transpiler: SymbolExtensions2.GetMethodInfo(static () => MainTranspiler(null!)));

TaleWorlds.MountAndBlade.Launcher.Library.Program.Main(args);
GameLauncherEntrypointHandler.Entrypoint(args);
}

private static IEnumerable<CodeInstruction> MainTranspiler(IEnumerable<CodeInstruction> codeInstructions) => new []
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Call, SymbolExtensions2.GetMethodInfo((string[] args) => Entrypoint(args))),
new CodeInstruction(OpCodes.Ret),
};

private static void MainPrefix(ref string[] args)
private static int Entrypoint(string[] args)
{
ExceptionInterceptorFeature.Enable();
ExceptionInterceptorFeature.EnableAutoGens();
Expand All @@ -55,9 +65,7 @@ private static void MainPrefix(ref string[] args)

SpecialKILoader.LoadSpecialKIfNeeded();
ReShadeLoader.LoadReShadeIfNeeded();

_featureHarmony.Unpatch(
AccessTools2.DeclaredMethod("TaleWorlds.Starter.Library.Program:Main"),
SymbolExtensions2.GetMethodInfo(static (string[] x) => MainPrefix(ref x)));

return GameEntrypointHandler.Entrypoint(args);
}
}
25 changes: 17 additions & 8 deletions src/Bannerlord.BLSE.Shared/LauncherEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
using HarmonyLib.BUTR.Extensions;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

using Windows.Win32;
Expand Down Expand Up @@ -41,21 +43,30 @@ public static void Launch(string[] args)

ModuleInitializer.Disable();

GameOriginalEntrypointHandler.Initialize();

_featureHarmony.TryPatch(
AccessTools2.DeclaredMethod("TaleWorlds.Starter.Library.Program:Main"),
prefix: SymbolExtensions2.GetMethodInfo(static (string[] x) => MainPrefix(ref x)));
SymbolExtensions2.GetMethodInfo((string[] args_) => TaleWorlds.Starter.Library.Program.Main(args_)),
transpiler: SymbolExtensions2.GetMethodInfo(static () => MainTranspiler(null!)));

if (args.Contains("/noexceptions"))
{
ProgramEx.Main(args);
}
else
{
TaleWorlds.MountAndBlade.Launcher.Library.Program.Main(args);
GameLauncherEntrypointHandler.Entrypoint(args);
}
}

private static void MainPrefix(ref string[] args)
private static IEnumerable<CodeInstruction> MainTranspiler(IEnumerable<CodeInstruction> codeInstructions) => new []
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Call, SymbolExtensions2.GetMethodInfo((string[] args) => Entrypoint(args))),
new CodeInstruction(OpCodes.Ret),
};

private static int Entrypoint(string[] args)
{
var disableCrashHandler = LauncherSettings.DisableCrashHandlerWhenDebuggerIsAttached && DebuggerUtils.IsDebuggerAttached();
if (!disableCrashHandler)
Expand All @@ -72,9 +83,7 @@ private static void MainPrefix(ref string[] args)

SpecialKILoader.LoadSpecialKIfNeeded();
ReShadeLoader.LoadReShadeIfNeeded();

_featureHarmony.Unpatch(
AccessTools2.DeclaredMethod("TaleWorlds.Starter.Library.Program:Main"),
SymbolExtensions2.GetMethodInfo(static (string[] x) => MainPrefix(ref x)));

return GameEntrypointHandler.Entrypoint(args);
}
}
3 changes: 3 additions & 0 deletions src/Bannerlord.BLSE.Shared/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ GetTokenInformation
NtQueryInformationProcess
LoadLibrary
VirtualProtect
AddVectoredExceptionHandler
RemoveVectoredExceptionHandler
SetThreadStackGuarantee
22 changes: 17 additions & 5 deletions src/Bannerlord.BLSE.Shared/NoExceptions/ProgramEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
using System;
using System.Linq;
using System.Reflection;

using Bannerlord.BLSE.Shared.Utils;
using TaleWorlds.Library;
using TaleWorlds.ModuleManager;
using TaleWorlds.MountAndBlade.Launcher.Library;
using TaleWorlds.TwoDimension.Standalone;
using TaleWorlds.TwoDimension.Standalone.Native.Windows;
Expand All @@ -17,6 +18,14 @@ namespace Bannerlord.BLSE.Shared.NoExceptions;

public sealed class ProgramEx
{
private delegate void SetLauncherModeDelegate(bool isLauncherModeActive);
private static readonly SetLauncherModeDelegate? SetLauncherMode =
AccessTools2.GetDelegate<SetLauncherModeDelegate>(typeof(LauncherPlatform), "SetLauncherMode");

private delegate void SetInvariantCultureDelegate();
private static readonly SetInvariantCultureDelegate? SetInvariantCulture =
AccessTools2.GetDelegate<SetInvariantCultureDelegate>(typeof(Common), "SetInvariantCulture");

private record LauncherExContext(GraphicsForm GraphicsForm, StandaloneUIDomain StandaloneUIDomain, WindowsFrameworkEx WindowsFramework)
{
public static LauncherExContext Create()
Expand Down Expand Up @@ -59,9 +68,8 @@ private record StartGameData

public static void Main(string[] args)
{
#if v110 || v111
TaleWorlds.Library.Common.SetInvariantCulture();
#endif
SetInvariantCulture?.Invoke();

Common.PlatformFileHelper = new PlatformFileHelperPC("Mount and Blade II Bannerlord");
Debug.DebugManager = new LauncherDebugManager();
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
Expand All @@ -73,14 +81,18 @@ public static void Main(string[] args)
_gameData = _gameData with { Args = args.ToArray() };

LauncherPlatform.Initialize();
SetLauncherMode?.Invoke(true);

_context = LauncherExContext.Create();
_context.Initialize();

SetLauncherMode?.Invoke(false);
LauncherPlatform.Destroy();

AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;

if (_gameData.ShouldStart)
TaleWorlds.Starter.Library.Program.Main(_gameData.Args.ToArray());
GameEntrypointHandler.Entrypoint(_gameData.Args.ToArray());
}

public static bool StartGamePrefix()
Expand Down
27 changes: 18 additions & 9 deletions src/Bannerlord.BLSE.Shared/Standalone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
using Bannerlord.ModuleManager;

using HarmonyLib;
using HarmonyLib.BUTR.Extensions;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

using TaleWorlds.Core;
Expand All @@ -21,7 +22,7 @@
using TaleWorlds.SaveSystem;

using Windows.Win32;

using HarmonyLib.BUTR.Extensions;
using MessageBoxButtons = Bannerlord.BLSE.Shared.Utils.MessageBoxButtons;
using MessageBoxDefaultButton = Bannerlord.BLSE.Shared.Utils.MessageBoxDefaultButton;
using MessageBoxIcon = Bannerlord.BLSE.Shared.Utils.MessageBoxIcon;
Expand Down Expand Up @@ -112,14 +113,24 @@ public static void Launch(string[] args)

ModuleInitializer.Disable();

GameOriginalEntrypointHandler.Initialize();

_featureHarmony.TryPatch(
AccessTools2.DeclaredMethod("TaleWorlds.Starter.Library.Program:Main"),
prefix: SymbolExtensions2.GetMethodInfo(static (string[] x) => MainPrefix(ref x)));
SymbolExtensions2.GetMethodInfo((string[] args_) => TaleWorlds.Starter.Library.Program.Main(args_)),
transpiler: SymbolExtensions2.GetMethodInfo(static () => MainTranspiler(null!)));

// Just to keep the original method call in the stacktrace
TaleWorlds.Starter.Library.Program.Main(args);
}

private static void MainPrefix(ref string[] args)
private static IEnumerable<CodeInstruction> MainTranspiler(IEnumerable<CodeInstruction> codeInstructions) => new []
{
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Call, SymbolExtensions2.GetMethodInfo((string[] args) => Entrypoint(args))),
new CodeInstruction(OpCodes.Ret),
};

private static int Entrypoint(string[] args)
{
var disableCrashHandler = !args.Contains("/enablecrashhandlerwhendebuggerisattached") && DebuggerUtils.IsDebuggerAttached();
if (!disableCrashHandler)
Expand All @@ -133,9 +144,7 @@ private static void MainPrefix(ref string[] args)
Array.Resize(ref args, args.Length + 1);
args[args.Length - 1] = "no_watchdog";
}

_featureHarmony.Unpatch(
AccessTools2.DeclaredMethod("TaleWorlds.Starter.Library.Program:Main"),
SymbolExtensions2.GetMethodInfo(static (string[] x) => MainPrefix(ref x)));

return GameEntrypointHandler.Entrypoint(args);
}
}
136 changes: 136 additions & 0 deletions src/Bannerlord.BLSE.Shared/Utils/GameEntrypointHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Diagnostics.Debug;
using Bannerlord.BLSE.Features.ExceptionInterceptor;

namespace Bannerlord.BLSE.Shared.Utils;

file enum CustomExceptionType : uint
{
None = 0,
StackOverflow = 1,
AccessViolation = 2,
Unknown = 3,
}

public static class GameEntrypointHandler
{
private const int EXCEPTION_CONTINUE_SEARCH = 0;
private const int EXCEPTION_EXECUTE_HANDLER = 1;
private const int EXCEPTION_CONTINUE_EXECUTION = -1;

private const uint STATUS_STACK_OVERFLOW = 0xC00000FD;
private const uint STATUS_ACCESS_VIOLATION = 0xC0000005;

private const uint STATUS_CUSTOM_STACK_OVERFLOW = 0xF00000FD;
private const uint STATUS_CUSTOM_ACCESS_VIOLATION = 0xF00000FF;

private static readonly PVECTORED_EXCEPTION_HANDLER HandlerDelegate;

static unsafe GameEntrypointHandler()
{
HandlerDelegate = Handler;
GC.KeepAlive(HandlerDelegate);
}

private static unsafe int Handler(EXCEPTION_POINTERS* ExceptionInfo)
{
if (ExceptionInfo == null)
return EXCEPTION_CONTINUE_SEARCH;

if (ExceptionInfo->ExceptionRecord == null)
return EXCEPTION_CONTINUE_SEARCH;

var record = ExceptionInfo->ExceptionRecord;
Console.WriteLine(record->ExceptionCode);

if (record->ExceptionCode == STATUS_STACK_OVERFLOW)
{
record->ExceptionCode = (NTSTATUS) STATUS_CUSTOM_STACK_OVERFLOW;
return EXCEPTION_EXECUTE_HANDLER;
}

if (record->ExceptionCode == STATUS_ACCESS_VIOLATION)
{
record->ExceptionCode = (NTSTATUS) STATUS_CUSTOM_ACCESS_VIOLATION;
return EXCEPTION_EXECUTE_HANDLER;
}

return EXCEPTION_CONTINUE_SEARCH;
}


public static unsafe int Entrypoint(string[] args)
{
// This doesn't work as intended, so let's not overcomplicate for now
return GameOriginalEntrypointHandler.Entrypoint(args);


// Setting the vectored exception handler to 'first' will cause
// an ExecutionEngineException if the dotnet debugger is attached.
// Note that hooking will not work properly if the handler is not
// first because any of other application exception handlers may
// change state in unpredictable ways.
if (DebuggerUtils.IsDebuggerAttached())
return GameOriginalEntrypointHandler.Entrypoint(args);

var handler = PInvoke.AddVectoredExceptionHandler(1, HandlerDelegate);
if (handler == null)
{
ExceptionInterceptorFeature.HandleException(new Win32Exception("AddVectoredExceptionHandler failed"));
return 1;
}

var size = 32768U;
if (!PInvoke.SetThreadStackGuarantee(&size))
{
ExceptionInterceptorFeature.HandleException(new InsufficientExecutionStackException("SetThreadStackGuarantee failed", new Win32Exception()));
return 1;
}

var val = default(int);
var exType = CustomExceptionType.None;
var exCatched = default(Exception);
try
{
val = GameOriginalEntrypointHandler.Entrypoint(args);
}
catch (Exception ex)
{
exCatched = ex;
exType = CustomExceptionType.Unknown;

if (ex is SEHException seh)
{
exType = (uint) seh.HResult switch
{
STATUS_CUSTOM_STACK_OVERFLOW => CustomExceptionType.StackOverflow,
STATUS_CUSTOM_ACCESS_VIOLATION => CustomExceptionType.AccessViolation,
_ => CustomExceptionType.Unknown,
};
}
}

PInvoke.RemoveVectoredExceptionHandler(handler);

switch (exType)
{
case CustomExceptionType.None:
return val;
case CustomExceptionType.StackOverflow:
ExceptionInterceptorFeature.HandleException(new StackOverflowException("Stack Overflow", exCatched));
throw exCatched!;
case CustomExceptionType.AccessViolation:
ExceptionInterceptorFeature.HandleException(new AccessViolationException("Access Violation", exCatched));
throw exCatched!;
case CustomExceptionType.Unknown:
ExceptionInterceptorFeature.HandleException(new Exception("Unhandled", exCatched));
throw exCatched!;
default:
return 1;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Bannerlord.BLSE.Shared.Utils;

public class GameLauncherEntrypointHandler
{
public static void Entrypoint(string[] args)
{
TaleWorlds.MountAndBlade.Launcher.Library.Program.Main(args);
}
}
Loading

0 comments on commit 38b44e3

Please sign in to comment.