Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,22 @@ For more detailed instructions visit the [wiki](https://fexty12573.github.io/Sha
To create release packages you can use the `make-package.py` script. It will build everything and package it into a ready to use zip.

```
make-package.py [-h] [-c {Release,Debug}] [-m] sln_dir tag
make-package.py [-h] [-s] [-c {Release,Debug}] [tag] [sln_dir]

Create a release of the plugin loader

positional arguments:
sln_dir The directory of the solution
tag The tag to use for the release in the format x.x.x[.x]
sln_dir The directory of the solution

options:
-h, --help show this help message and exit
-c {Release,Debug}, --config {Release,Debug}
-h, --help Show this message
-s, --skip-build Skip building the solution
-c, --config {Release,Debug}
The configuration to build
-m, --msbuild-in-path
Set if msbuild is in the PATH environment variable
```

Example invocation: `python make-package.py -c Release -m ./ 0.1.0`
Example invocation: `python make-package.py -c Release 0.1.0 ./`

## **Enabling C# Debugging**

Expand Down
20 changes: 14 additions & 6 deletions docs/Install/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@ Extract the contents of the archive into the game's root directory (where `Monst

If you installed everything correctly you should now find `winmm.dll` in the same directory as `MonsterHunterWorld.exe`, and a `CSharp` directory in `nativePC\plugins`.

If you're running the game _without_ Steam and it's immediately closing on startup, you should try adding `MonsterHunterWorld.exe` as a Non-Steam Game and launching it through Steam. Steam affects the order in which `winmm.dll` gets loaded by injecting `GameOverlayRenderer64.dll` into the process. This happens whether you have the Steam Overlay enabled or not.

## Linux (Proton/Wine)
As of version 0.0.7.2, SPL officially supports Linux through Proton/Wine. Below are the steps to install and run SPL on Linux.

1. Install .NET Desktop Runtime 8.0 and Direct 3D Shader Compiler using [protontricks](https://github.com/Matoking/protontricks):
```bash
protontricks 582010 dotnetdesktop8 d3dcompiler_47
```
2. Download the latest linux release of SPL (`SharpPluginLoader-<version>-linux.zip`) from the [Releases Page](https://github.com/Fexty12573/SharpPluginLoader/releases) and extract it into the game's root directory. After doing so you should have a `msvcrt.dll` file in the same directory as `MonsterHunterWorld.exe`.
3. Set the steam launch options for MHW as follows:
2. Download the latest linux release of SPL (`SharpPluginLoader-<version>-linux.zip`) from the [Releases Page](https://github.com/Fexty12573/SharpPluginLoader/releases) and extract it into the game's root directory. After doing so you should have a `ucrtbase.dll` file in the same directory as `MonsterHunterWorld.exe`.
3. Set the Steam launch options for MHW as follows:
```bash
# Use this for SPL only
WINEDLLOVERRIDES="msvcrt=n,b" %command%
WINEDLLOVERRIDES="ucrtbase=n,b" %command%

# Or this for SPL together with Stracker's Loader
WINEDLLOVERRIDES="msvcrt,dinput8=n,b" %command%
WINEDLLOVERRIDES="ucrtbase,dinput8=n,b" %command%
```

If the game fails to start, even with the dependencies above correctly installed, check your environment for `DOTNET_ROOT`. It may be set by your package manager. For example, on Gentoo with `eselect dotnet`. You can unset it in your Steam launch options like so:
```bash
# Unset DOTNET_ROOT to avoid conflict with native dotnet and dotnet installed in the Wine prefix.
DOTNET_ROOT= WINEDLLOVERRIDES="ucrtbase,dinput8=n,b" %command%
```

## Usage
Expand All @@ -46,7 +54,7 @@ Depending on the plugins you have installed you might also see an overlay/UI app
### Directory Structure Examples
```
<Root game directory>
└── winmm.dll/msvcrt.dll
└── winmm.dll/ucrtbase.dll
└── nativePC
└── plugins
└── CSharp
Expand All @@ -59,7 +67,7 @@ Depending on the plugins you have installed you might also see an overlay/UI app
Conversely, the following is **not** valid, as `Plugin1.dll` is inside the `Loader` directory. The plugin loader will not load it.
```
<Root game directory>
└── winmm.dll/msvcrt.dll
└── winmm.dll/ucrtbase.dll
└── nativePC
└── plugins
└── CSharp
Expand Down
56 changes: 56 additions & 0 deletions generate_export_forwarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sys
import os
import shutil
from pathlib import Path
import subprocess
import hashlib

# Use a Developer Command Prompt/Powershell for VS 20XX to get dumpbin.exe in your PATH.

if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} [path_to_dll] (optional_path_to_dumpbin.exe)')
exit(1)

if len(sys.argv) == 2:
dumpbin = shutil.which('dumpbin.exe')
else:
dumpbin = sys.argv[2]
if not dumpbin or not os.path.isfile(dumpbin):
print(f'Error: dumpbin.exe not found' + (' in PATH' if dumpbin is None else f' at \'{dumpbin}\''))
exit(1)

dll = Path(sys.argv[1])
if not dll.exists():
print(f'Error: dll not found at \'{dll}\'')
exit(1)
dllbase = dll.parts[-1]

if os.name == 'nt': # Windows.
version_cmd = ('wmic', 'datafile', 'where', 'name = "{:s}"'.format(str(dll.absolute()).replace('\\', '\\\\')), 'get', 'Version', '/value')
output = subprocess.run(version_cmd, stdout=subprocess.PIPE, shell=True)
dll_version = output.stdout.decode('utf-8').strip().replace('=', ' ')
output = subprocess.run([dumpbin, '/EXPORTS', '/NOLOGO', dll], stdout=subprocess.PIPE)
else: # Try to use Wine.
dll_version = 'unknown'
os.environ['WINEDEBUG'] = '-all'
output = subprocess.run(['wine', dumpbin, '/EXPORTS', '/NOLOGO', dll], stdout=subprocess.PIPE)

m = hashlib.sha256()
with open(dll, 'rb') as f:
m.update(f.read())

print(f'// Exports for {dllbase} ({dll_version}, sha256-{m.hexdigest()})')
print(f'#pragma region {dllbase} export forwarding')
lines = output.stdout.decode('utf-8').splitlines()
while not lines[0].startswith(' ordinal hint'):
lines.pop(0)
for l in lines[2:]:
if len(l) == 0:
break
last_space = l.rfind(' ')
if last_space >= 0:
l = l[last_space + 1:]
if l == '[NONAME]':
break
print(f'#pragma comment(linker, "/export:{l}=\\\"C:\\\\Windows\\\\System32\\\\{dll.stem}.{l}\\\"")')
print('#pragma endregion')
59 changes: 36 additions & 23 deletions make-package.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,29 @@
import argparse
import os

def main(sln_dir, config, tag, msbuild_in_path):
# build the solution
print(f'Building solution in {sln_dir} with configuration {config}...')

if msbuild_in_path:
msbuild = "msbuild"
else:
msbuild = f"{os.environ['ProgramFiles']}\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe"
sln = os.path.join(sln_dir, "mhw-cs-plugin-loader.slnx")
subprocess.run([msbuild, sln, f"/p:Configuration={config}"], check=True)
def main(sln_dir, config, tag, skip_build):
if not os.path.isdir(sln_dir):
print(f"Error: {sln_dir} is not a directory")
return False

if not skip_build:
# build the solution
sln = os.path.join(sln_dir, "mhw-cs-plugin-loader.slnx")
if not os.path.isfile(sln):
print(f"Error: {sln} not found")
return False
print(f"Building solution {sln} with configuration {config}...")
# check PATH for MSBuild
msbuild = shutil.which("MSBuild.exe")
if not msbuild and "ProgramFiles" in os.environ:
# search for MSBuild in the default VS 2026 then VS 2022 install directories
msbuild = f"{os.environ["ProgramFiles"]}\\Microsoft Visual Studio\\18\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe"
if not os.path.isfile(msbuild):
msbuild = f"{os.environ["ProgramFiles"]}\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe"
if not msbuild or not os.path.isfile(msbuild):
print("Error: MSBuild.exe not found")
return False
subprocess.run([msbuild, sln, f"/p:Configuration={config}"], check=True)

# create the release directory
release_dir = os.path.join(sln_dir, "release-files")
Expand All @@ -38,7 +51,7 @@ def main(sln_dir, config, tag, msbuild_in_path):

native_src = os.path.join(sln_dir, "x64", config, "mhw-cs-plugin-loader.dll")
native_winmm_dst = os.path.join(root_dir_win32, "winmm.dll")
native_msvcrt_dst = os.path.join(root_dir_linux, "msvcrt.dll")
native_ucrtbase_dst = os.path.join(root_dir_linux, "ucrtbase.dll")

runtimeconfig_src = os.path.join(sln_dir, "mhw-cs-plugin-loader/SharpPluginLoader.runtimeconfig.json")
runtimeconfig_dst = os.path.join(loader_dir, "SharpPluginLoader.runtimeconfig.json")
Expand All @@ -50,9 +63,9 @@ def main(sln_dir, config, tag, msbuild_in_path):
shutil.copyfile(native_src, native_winmm_dst)
shutil.copyfile(runtimeconfig_src, runtimeconfig_dst)

# copy the files for linux (msvcrt.dll)
# copy the files for linux (ucrtbase.dll)
shutil.copytree(root_dir_win32, root_dir_linux, dirs_exist_ok=True)
shutil.copyfile(native_src, native_msvcrt_dst)
shutil.copyfile(native_src, native_ucrtbase_dst)
os.remove(os.path.join(root_dir_linux, "winmm.dll"))

# create the zip file
Expand All @@ -61,26 +74,26 @@ def main(sln_dir, config, tag, msbuild_in_path):
zip_path_win32 = os.path.join(tag_dir, zip_name_win32)
zip_path_linux = os.path.join(tag_dir, zip_name_linux)

shutil.make_archive(zip_path_win32, 'zip', root_dir_win32)
shutil.make_archive(zip_path_linux, 'zip', root_dir_linux)
shutil.make_archive(zip_path_win32, "zip", root_dir_win32)
shutil.make_archive(zip_path_linux, "zip", root_dir_linux)

print(f"Created release: {zip_path_win32}.zip")
print(f"Created release: {zip_path_linux}.zip")

return True

def usage(parser: argparse.ArgumentParser):
parser.print_help()
exit(1)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Create a release of the plugin loader")
parser.add_argument("sln_dir", help="The directory of the solution")
parser = argparse.ArgumentParser(description="Create a release of the plugin loader", add_help=False)
parser.add_argument("-h", "--help", action="help", help="Show this message")
parser.add_argument("-s", "--skip-build", help="Skip building the solution", action="store_true")
parser.add_argument("-c", "--config", help="The configuration to build", default="Release", choices=["Release", "Debug"], type=str.capitalize)
parser.add_argument("-m", "--msbuild-in-path", help="Set if msbuild is in the PATH environment variable", action="store_true")
Comment thread
Fexty12573 marked this conversation as resolved.
parser.add_argument("tag", help="The tag to use for the release in the format x.x.x[.x]", default="latest")
parser.add_argument("tag", help="The tag to use for the release in the format x.x.x[.x]", nargs="?", default="latest")
parser.add_argument("sln_dir", help="The directory of the solution", nargs="?", default=".")
args = parser.parse_args()

if not os.path.isdir(args.sln_dir):
print(f"Error: {args.sln_dir} is not a directory")
if not main(args.sln_dir, args.config, args.tag, args.skip_build):
usage(parser)

main(args.sln_dir, args.config, args.tag, args.msbuild_in_path)
4 changes: 2 additions & 2 deletions mhw-cs-plugin-loader/Preloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ void initialize_preloader() {
}

// Reset the processes' security cookie to the default value to make the
// MSVC startup code to attempt to initalize it to a new value, which will
// MSVC startup code to attempt to initalize it to a new value, which will
// cause our hooked GetSystemTimeAsFileTime to be called pre-CRT init.
*security_cookie = MSVC_DEFAULT_SECURITY_COOKIE_VALUE;

g_get_system_time_as_file_time_hook = safetyhook::create_inline(
reinterpret_cast<void*>(GetSystemTimeAsFileTime),
reinterpret_cast<void*>(hooked_get_system_time_as_file_time)
);
}
}
Loading
Loading