Skip to content

Commit

Permalink
Merge pull request #24 from BUTR/dev
Browse files Browse the repository at this point in the history
v1.2.2
  • Loading branch information
Aragas authored Mar 25, 2023
2 parents 8319977 + 2d013e9 commit 58d88e9
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 2 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,41 @@ Can be opted-out via passing **/nounblock** in command-line args.
Can opted-in by passing **/unblock** in command-line args.
* **Continue Save File** - Allows to specify the save file to load when launching the game.
Can be used by passing **/continuesave _mysavegame_** in command-line args.
* **Game Pass PC** - Support of modding on the Xbox platform. BLSE disabled Xbox integration, replacing Cloud Saves with saves stored like on Steam/GOG/Epic
* **Assembly Resolver** - Changes the game's assembly loading priority.
If an assembly is available in one of the loaded modules, it will be loaded from there instead, even if the assembly is available in the main **/bin** folder.
* **Interceptor** - BLSE checks if the is a class with a custom attribute named ***BLSEInterceptorAttribute***. If it's found it checks if there are the following signatures:
* **static void OnInitializeSubModulesPrefix()** - will execute just before the game starts to initialize the SubModules. This gives us the ability to add SubModules declared in other programming languages like [Python](https://github.com/BUTR/Bannerlord.Python) and [Lua](https://github.com/BUTR/Bannerlord.Lua)
* **static void OnLoadSubModulesPostfix()** - will execute just after all SubModules were initialized

## FAQ
* I have issues with the installation!
* <details>
<summary>Xbox Game Pass PC</summary>
<p>You need to copy content of '/bin/Gaming.Desktop.x64_Shipping_Client' from BLSE to 'Mount & Blade II- Bannerlord/Content/bin/Gaming.Desktop.x64_Shipping_Client'</p>
<img src="https://media.discordapp.net/attachments/422092475163869201/1088721252702765126/image.png" alt="BLSE Installation Path" width="500">
<p>You need to copy content of 'Modules/Bannerlord.Harmony' from BLSE to 'Mount & Blade II- Bannerlord/Content/Modules/Bannerlord.Harmony'</p>
<img src="https://media.discordapp.net/attachments/422092475163869201/1088721253692616775/image.png" alt="Bannerlord.Harmony Installation Path" width="500">
</details>
* <details>
<summary>Steam</summary>
<p>You need to copy content of '/bin/Win64_Shipping_Client' from BLSE to 'Mount & Blade II Bannerlord/bin/Win64_Shipping_Client'</p>
<img src="https://media.discordapp.net/attachments/422092475163869201/1088721252962807818/image.png" alt="BLSE Installation Path" width="500">
<p>You need to copy content of 'Modules/Bannerlord.Harmony' from BLSE to 'Mount & Blade II Bannerlord/Modules/Bannerlord.Harmony'</p>
<img src="https://media.discordapp.net/attachments/422092475163869201/1088721253478711407/image.png" alt="Bannerlord.Harmony Installation Path" width="500">
</details>
* <details>
<summary>GOG</summary>
<p>You need to copy content of '/bin/Win64_Shipping_Client' from BLSE to 'Mount & Blade II Bannerlord/bin/Win64_Shipping_Client'</p>
<img src="https://media.discordapp.net/attachments/422092475163869201/1088721253185097758/image.png" alt="BLSE Installation Path" width="500">
<p>You need to copy content of 'Modules/Bannerlord.Harmony' from BLSE to 'Mount & Blade II Bannerlord/Modules/Bannerlord.Harmony'</p>
<img src="https://media.discordapp.net/attachments/422092475163869201/1088725020458614794/image.png" alt="Bannerlord.Harmony Installation Path" width="500">
</details>
* Do I need to include both `Win64_Shipping_Client` and `Gaming.Desktop.x64_Shipping_Client` directories?
No!
For Xbox Game Pass PC you need only `Gaming.Desktop.x64_Shipping_Client`
For Steam/GOG/Epic you need only `Win64_Shipping_Client`
* I don't see my old saves on Xbox Game Pass PC!
BLSE uses a storage that Steam/GOG/Epic versions of the game use. We do not support Xbox's Cloud Saves!
* BLSE is not shown in Vortex's Tools!
You need to add it manually for now!
2 changes: 1 addition & 1 deletion build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<!--Development Variables-->
<PropertyGroup>
<Version>1.2.1</Version>
<Version>1.2.2</Version>
<HarmonyVersion>2.10.1</HarmonyVersion>
<BUTRSharedVersion>3.0.0.135</BUTRSharedVersion>
<BUTRModuleManagerVersion>5.0.198</BUTRModuleManagerVersion>
Expand Down
4 changes: 4 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
---------------------------------------------------------------------------------------------------
Version: 1.2.2
Game Versions: v1.0.0,v1.0.1,v1.0.2,v1.0.3,v1.1.0,v1.1.1
* Added Steam UAC check
---------------------------------------------------------------------------------------------------
Version: 1.2.1
Game Versions: v1.0.0,v1.0.1,v1.0.2,v1.0.3,v1.1.0,v1.1.1
* Fixed possible GamePass crash
Expand Down
4 changes: 3 additions & 1 deletion src/Bannerlord.BLSE.Shared/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ MessageBox
DeleteFile
GetConsoleWindow
ShowWindow
SetProcessDPIAware
SetProcessDPIAware
OpenProcessToken
GetTokenInformation
3 changes: 3 additions & 0 deletions src/Bannerlord.BLSE.Shared/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public static void Main(string[] args)
{
//PInvoke.ShowWindow(PInvoke.GetConsoleWindow(), SHOW_WINDOW_CMD.SW_HIDE);

if (PlatformHelper.IsSteam())
UacHelper.CheckSteam();

LauncherExceptionHandler.Watch();

switch (args[0])
Expand Down
16 changes: 16 additions & 0 deletions src/Bannerlord.BLSE.Shared/Utils/PlatformHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Bannerlord.BUTR.Shared.Helpers;

using System.IO;

namespace Bannerlord.BLSE.Shared.Utils;

internal static class PlatformHelper
{
private static string ConfigName = Path.GetFileName(Directory.GetCurrentDirectory());
private static string GameBasePath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "../", "../"));

public static bool IsSteam() => ConfigName == "Win64_Shipping_Client" && File.Exists(Path.Combine(GameBasePath, ModuleInfoHelper.ModulesFolder, "Native", "steam.target"));
public static bool IsGog() => ConfigName == "Win64_Shipping_Client" && File.Exists(Path.Combine(GameBasePath, ModuleInfoHelper.ModulesFolder, "Native", "gog.target"));
public static bool IsGdk() => ConfigName == "Gaming.Desktop.x64_Shipping_Client" && File.Exists(Path.Combine(GameBasePath, "appxmanifest.xml"));
public static bool IsEpic() => ConfigName == "Win64_Shipping_Client" && File.Exists(Path.Combine(GameBasePath, ModuleInfoHelper.ModulesFolder, "Native", "epic.target"));
}
134 changes: 134 additions & 0 deletions src/Bannerlord.BLSE.Shared/Utils/RegistryHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using Microsoft.Win32.SafeHandles;

using System;
using System.Runtime.InteropServices;

namespace Bannerlord.BLSE.Shared.Utils;

file static class SafeNativeMethods
{
internal const int ERROR_SUCCESS = 0;

internal const int KEY_QUERY_VALUE = 1;
internal const int KEY_SET_VALUE = 2;
internal const int KEY_CREATE_SUB_KEY = 4;
internal const int KEY_ENUMERATE_SUB_KEYS = 8;
internal const int KEY_NOTIFY = 16;
internal const int KEY_CREATE_LINK = 32;
internal const int KEY_READ = 131097;
internal const int KEY_WRITE = 131078;

internal const int REG_NONE = 0;
internal const int REG_SZ = 1;
internal const int REG_EXPAND_SZ = 2;
internal const int REG_BINARY = 3;
internal const int REG_DWORD = 4;
internal const int REG_DWORD_LITTLE_ENDIAN = 4;
internal const int REG_DWORD_BIG_ENDIAN = 5;
internal const int REG_LINK = 6;
internal const int REG_MULTI_SZ = 7;
internal const int REG_RESOURCE_LIST = 8;
internal const int REG_FULL_RESOURCE_DESCRIPTOR = 9;
internal const int REG_RESOURCE_REQUIREMENTS_LIST = 10;
internal const int REG_QWORD = 11;

internal const string ADVAPI32 = "advapi32.dll";

[DllImport(ADVAPI32, BestFitMapping = false, CharSet = CharSet.Unicode)]
internal static extern int RegOpenKeyEx(RegistryHandle hKey, string lpSubKey, int ulOptions, int samDesired, out RegistryHandle? hkResult);

/*
[DllImport(ADVAPI32, BestFitMapping = false, CharSet = CharSet.Unicode)]
internal static extern int RegSetValueEx(RegistryHandle hKey, string lpValueName, int Reserved, int dwType, string val, int cbData);
[DllImport(ADVAPI32, BestFitMapping = false, CharSet = CharSet.Unicode)]
internal static extern int RegCreateKeyEx(RegistryHandle hKey, string lpSubKey, int Reserved, string? lpClass, int dwOptions, int samDesigner, IntPtr lpSecurityAttributes, out RegistryHandle hkResult, out int lpdwDisposition);
*/

[DllImport(ADVAPI32)]
internal static extern int RegCloseKey(IntPtr handle);

[DllImport(ADVAPI32, BestFitMapping = false, CharSet = CharSet.Unicode)]
internal static extern int RegQueryValueEx(RegistryHandle hKey, string lpValueName, int lpReserved, ref int lpType, [Out] byte[]? lpData, ref int lpcbData);

/*
[DllImport(ADVAPI32, BestFitMapping = false, CharSet = CharSet.Unicode)]
internal static extern int RegDeleteKey(RegistryHandle hKey, string lpValueName);
*/
}

internal class RegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public static readonly RegistryHandle HKEY_CLASSES_ROOT = new(new IntPtr(int.MinValue), ownHandle: false);
public static readonly RegistryHandle HKEY_CURRENT_USER = new(new IntPtr(-2147483647), ownHandle: false);
public static readonly RegistryHandle HKEY_LOCAL_MACHINE = new(new IntPtr(-2147483646), ownHandle: false);
public static readonly RegistryHandle HKEY_USERS = new(new IntPtr(-2147483645), ownHandle: false);
public static readonly RegistryHandle HKEY_PERFORMANCE_DATA = new(new IntPtr(-2147483644), ownHandle: false);
public static readonly RegistryHandle HKEY_CURRENT_CONFIG = new(new IntPtr(-2147483643), ownHandle: false);
public static readonly RegistryHandle HKEY_DYN_DATA = new(new IntPtr(-2147483642), ownHandle: false);

public static int TryGetHKLMSubkey(string key, out RegistryHandle? regHandle)
{
return SafeNativeMethods.RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, SafeNativeMethods.KEY_READ, out regHandle);
}

public static RegistryHandle? GetHKLMSubkey(string key)
{
if (SafeNativeMethods.RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, SafeNativeMethods.KEY_READ, out var hkResult) != SafeNativeMethods.ERROR_SUCCESS || hkResult == null || hkResult.IsInvalid)
return null;
return hkResult;
}

public RegistryHandle(IntPtr hKey, bool ownHandle) : base(ownHandle) => handle = hKey;
public RegistryHandle() : base(ownsHandle: true) { }

/*
public bool DeleteKey(string key)
{
if (SafeNativeMethods.RegDeleteKey(this, key) == SafeNativeMethods.ERROR_SUCCESS)
{
return true;
}
return false;
}
public RegistryHandle? CreateSubKey(string subKey)
{
if (SafeNativeMethods.RegCreateKeyEx(this, subKey, 0, null, 0, SafeNativeMethods.KEY_CREATE_SUB_KEY, IntPtr.Zero, out var hkResult, out _) != SafeNativeMethods.ERROR_SUCCESS || hkResult == null || hkResult.IsInvalid)
return null;
return hkResult;
}
public bool SetValue(string valName, string value)
{
if (SafeNativeMethods.RegSetValueEx(this, valName, 0, SafeNativeMethods.REG_SZ, value, value.Length * 2 + 2) != SafeNativeMethods.ERROR_SUCCESS)
return false;
return true;
}
public string? GetStringValue(string valName)
{
var lpType = 0;
var lpcbData = 0;
if (SafeNativeMethods.RegQueryValueEx(this, valName, 0, ref lpType, null, ref lpcbData) == SafeNativeMethods.ERROR_SUCCESS && lpType == 1)
{
var array = new byte[lpcbData];
var num = SafeNativeMethods.RegQueryValueEx(this, valName, 0, ref lpType, array, ref lpcbData);
return new UnicodeEncoding().GetString(array);
}
return null;
}
*/

public int? GetDwordValue(string valName)
{
var lpType = 4;
var arraySize = 4;
var array = new byte[arraySize];
if (SafeNativeMethods.RegQueryValueEx(this, valName, 0, ref lpType, array, ref arraySize) == SafeNativeMethods.ERROR_SUCCESS && lpType == 4)
return BitConverter.ToInt32(array, 0);
return null;
}

protected override bool ReleaseHandle() => SafeNativeMethods.RegCloseKey(handle) == SafeNativeMethods.ERROR_SUCCESS;
}
88 changes: 88 additions & 0 deletions src/Bannerlord.BLSE.Shared/Utils/UacHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Microsoft.Win32.SafeHandles;

using System;
using System.Diagnostics;
using System.Linq;

using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Security;

namespace Bannerlord.BLSE.Shared.Utils;

public static class UacHelper
{
private const string uacRegistryKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
private const string uacRegistryValue = "EnableLUA";

private static uint STANDARD_RIGHTS_READ = 0x00020000;
private static uint TOKEN_QUERY = 0x0008;

public enum TOKEN_ELEVATION_TYPE
{
TokenElevationTypeDefault = 1,
TokenElevationTypeFull,
TokenElevationTypeLimited
}

public static bool IsUacEnabled
{
get
{
using var key = RegistryHandle.GetHKLMSubkey(uacRegistryKey);
return key?.GetDwordValue(uacRegistryValue)?.Equals(1) == true;
}
}

public static unsafe bool? IsProcessElevated(SafeProcessHandle processHandle)
{
try
{
if (IsUacEnabled)
{
if (!PInvoke.OpenProcessToken(processHandle, TOKEN_ACCESS_MASK.TOKEN_READ, out var tokenHandle) || tokenHandle.IsClosed || tokenHandle.IsInvalid)
return null;
using var __ = tokenHandle;

var elevationResult = TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault;
const uint elevationResultSize = sizeof(TOKEN_ELEVATION_TYPE);
uint result = 0;
if (PInvoke.GetTokenInformation((HANDLE) tokenHandle.DangerousGetHandle(), TOKEN_INFORMATION_CLASS.TokenElevationType, &elevationResult, elevationResultSize, &result))
return elevationResult == TOKEN_ELEVATION_TYPE.TokenElevationTypeFull;

return null;
}
}
catch (Exception)
{
return null;
}

return false;
}

public static void CheckSteam()
{
var thisProcess = Process.GetCurrentProcess();

var steamProcesses = Process.GetProcessesByName("steam");
if (steamProcesses.Length != 1) return;
var steamProcess = steamProcesses.First();

using var steamProcessHandle = steamProcess.SafeHandle;
var steamElevated = IsProcessElevated(steamProcessHandle);
if (steamElevated is null) return;

using var thisProcessHandle = thisProcess.SafeHandle;
var thisElevated = IsProcessElevated(thisProcessHandle);
if (thisElevated is null) return;

if (steamElevated == true && thisElevated != true)
{
MessageBoxDialog.Show(@"Steam is launched as Admin, but BLSE is not!
The game won't work if Steam has higher privileges than the game!
Please run Steam as a user or run the game as Admin!", "Error from BLSE!", MessageBoxButtons.Ok, MessageBoxIcon.Error);
Environment.Exit(1);
}
}
}

0 comments on commit 58d88e9

Please sign in to comment.