diff --git a/.github/workflows/steam-isolation.yml b/.github/workflows/steam-isolation.yml new file mode 100644 index 0000000..3238436 --- /dev/null +++ b/.github/workflows/steam-isolation.yml @@ -0,0 +1,37 @@ +name: Steam isolation native checks + +on: + pull_request: + paths: + - 'src/MultiSeat.SteamIsolation/**' + - '.github/workflows/steam-isolation.yml' + push: + paths: + - 'src/MultiSeat.SteamIsolation/**' + - '.github/workflows/steam-isolation.yml' + +permissions: + contents: read + +jobs: + native-checks: + runs-on: windows-2022 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - name: Build x64 and x86 launchers, hooks, and safe fixtures + shell: pwsh + run: ./src/MultiSeat.SteamIsolation/build.ps1 -BuildFixture + - name: Check native isolation policies + shell: pwsh + run: | + foreach ($architecture in @('x64', 'Win32')) { + ctest --test-dir "src/MultiSeat.SteamIsolation/build-$architecture" -C Release --output-on-failure + if ($LASTEXITCODE -ne 0) { throw "Policy checks failed for $architecture" } + } + - name: Check launch, restart, readiness, and cleanup + shell: pwsh + run: ./src/MultiSeat.SteamIsolation/tests/LauncherSmoke.ps1 + - name: Check direct game environment and working directory + shell: pwsh + run: ./src/MultiSeat.SteamIsolation/tests/GameLauncherSmoke.ps1 diff --git a/.gitignore b/.gitignore index 904748b..aa9431f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ PktMon.* # C++ InputHook DLL build output src/MultiSeat.InputHook/build/ +# Steam isolation native build products +src/MultiSeat.SteamIsolation/build-*/ +src/MultiSeat.SteamIsolation/artifacts/ + # TypeScript incremental build info *.tsbuildinfo diff --git a/README.md b/README.md index 6b0db8c..c547fcc 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,8 @@ Edit `appsettings.json` in `C:\Program Files\MultiSeat\` (restart the service af | `EnableKeyboardMouseIsolation` | `false` | Keyboard/mouse session isolation (no-op as architected — see Known Constraints in CLAUDE.md) | | `EnableSharedGameLibrary` | `true` | Create a shared games/ROMs folder all seats can use | | `SharedGameLibraryDir` | `C:\MultiSeatGames` | Root of the shared library (`\SteamLibrary` + `\ROMs`) | +| `SteamIsolationEnabled` | `false` | Wrap explicit `steam.exe` seat launches with the experimental native isolation helper. See [Steam isolation setup](docs/steam-isolation.md) for binaries, Apollo commands and limitations. | +| `SteamIsolationLauncherPath` | `C:\Program Files\MultiSeat\SteamIsolation\MultiSeat.SteamLauncher.exe` | Supervising x64 launcher; the matching x86 launcher and hooks live beside it. | | `EnableEmulatorNetplay` | `true` | Assign + open a per-seat RetroArch netplay port (seats connect over `127.0.0.1`) | | `SeedRetroArchNetplayConfig` | `false` | Auto-write each seat's `retroarch.cfg` (netplay port + shared ROM dir) | | `UpdateCheckEnabled` | `false` | Ask GitHub's public releases API whether newer MultiSeat, ApolloVibe and MoonlightVibe releases exist, and show the answer on the dashboard. **Off by default**: it is the only internet connection the service would make. It sends an unauthenticated GET with a generic User-Agent and nothing about your host, and never downloads or installs anything. Set it in `appsettings.local.json` or from the dashboard's System page; no restart needed. Details: `docs/security-posture.md`, "Outbound connections". | diff --git a/docs/steam-isolation.md b/docs/steam-isolation.md new file mode 100644 index 0000000..f4624b5 --- /dev/null +++ b/docs/steam-isolation.md @@ -0,0 +1,67 @@ +# Steam isolation (experimental, opt in) + +MultiSeat can launch Steam through its own native isolation launcher inside a seat's existing Windows user and session. This feature does not change Steam installations, replace system DLLs, or affect unrelated applications. + +Build and place the native launcher and hooks in `C:\Program Files\MultiSeat\SteamIsolation`. The folder must contain the x64 `MultiSeat.SteamLauncher.exe` and `MultiSeat.SteamHook.dll`, and for 32-bit Steam also `MultiSeat.SteamLauncher32.exe` and `MultiSeat.SteamHook32.dll`. Keep these binaries in a directory standard seat accounts cannot modify, while granting them read/execute access. + +Enable it in the deployed `appsettings.local.json`, then restart MultiSeat: + +```json +{ + "MultiSeat": { + "SteamIsolationEnabled": true, + "SteamIsolationLauncherPath": "C:\\Program Files\\MultiSeat\\SteamIsolation\\MultiSeat.SteamLauncher.exe" + } +} +``` + +The dashboard's app launch and `MultiSeat:LaunchOnConnect` automatically wrap an explicit, absolute path whose filename is exactly `steam.exe` (case insensitive). Other executables keep their existing behavior. Relative Steam paths are refused when isolation is enabled. Steam's original arguments and the launch working directory are preserved. Missing binaries, invalid PE files, and mismatched hook architecture prevent the isolated launch; there is no fallback that silently starts unisolated Steam. + +For example, configure a launch-on-connect entry with an absolute installed Steam path and `-bigpicture`. The launcher supervises the whole process tree, including replacement Steam processes, and returns the initial Steam process's exit code after the tree exits. MultiSeat tracks the supervisor PID, so reconnect duplicate detection and disconnect process-tree cleanup continue to cover the isolated Steam process. + +The service waits for an explicit native initialization signal, rather than assuming a running launcher means isolation succeeded. It creates a unique protected `Global\MultiSeatSteamReady-` event and grants only SYSTEM, administrators and the exact seat account access (the seat can signal/wait only). The launcher receives `--ready-event ` and signals it after successful hook initialization. Startup exit before the signal, cancellation, or a 180-second initialization timeout fails the launch and terminates the supervisor, whose job cleans up Steam and its children. There is no unisolated retry. Failures after readiness are visible in the native launcher's output; the service does not continuously monitor its exit code. + +Apollo launches its Moonlight app entries directly, so those commands bypass the managed service's wrapping. To opt in for an Apollo Steam app, use an explicit command in the source Apollo app list, substituting the installed Steam path: + +```text +"C:\Program Files\MultiSeat\SteamIsolation\MultiSeat.SteamLauncher.exe" --steam "C:\Program Files (x86)\Steam\steam.exe" -- -bigpicture +``` + +MultiSeat copies the source app list into the seat when it is provisioned again. Do not put this command in a detached/preparation command: the supervisor needs to remain the app process. A `steam://open/bigpicture` command invokes Windows' protocol handler and is not automatically rewritten. Replace that entry with the explicit launcher command when isolation is desired. Invoking the native launcher directly is itself an opt in and works independently of the service setting. + +Steam launched manually from a shortcut, Explorer, a Startup entry, or another program does not pass through this feature. Close Steam in the target seat before starting the isolated instance, and use the wrapper for subsequent Steam launches. Separate Windows users already isolate user files; native hooks address the Steam-specific machine/global state and require multi-session runtime validation. The feature does not grant game licenses or change Steam account access rules. + +## Building and checking + +Build the managed service using the normal MultiSeat build process. Building the native component requires Visual Studio 2022 C++ tools and CMake: + +```powershell +.\src\MultiSeat.SteamIsolation\build.ps1 +``` + +The four release binaries are written to `src\MultiSeat.SteamIsolation\artifacts`. MinHook v1.3.4 is vendored with its license in `third_party\minhook`. To build safe test fixtures and run the launcher smoke checks without launching installed Steam: + +```powershell +.\src\MultiSeat.SteamIsolation\build.ps1 -BuildFixture +.\src\MultiSeat.SteamIsolation\tests\LauncherSmoke.ps1 -Artifacts .\src\MultiSeat.SteamIsolation\artifacts +.\src\MultiSeat.SteamIsolation\tests\GameLauncherSmoke.ps1 -Artifacts .\src\MultiSeat.SteamIsolation\artifacts +dotnet test src\MultiSeat.Tests\MultiSeat.Tests.csproj --filter FullyQualifiedName~SteamIsolation +``` + +## Isolation behavior and limits + +The hook creates an IPC name from the Windows session and user SID, replaces Steam's `-master_ipc_name_override` argument and `steam_master_ipc_name_override` environment entry, and separates browser `htmlcache` and `cef_log` paths. It redirects `TempAppPath` and `TempAppCmdLine` registry operations to the seat user's `HKCU\Software\Valve\Steam`, and returns zero for successful DWORD `SteamPID` reads. + +Child command lines and explicit environments are adjusted before process creation. Same-architecture replacement `steam.exe` launches through `CreateProcessW/A` receive the hook before their main thread runs. Browser and game processes receive no injected hook DLL. An architecture-changing Steam update requires relaunching through the supervisor; the current hook cannot inject a DLL of the other architecture. Native-only replacement launches that bypass the Win32 creation functions have not been validated. + +The feature defaults to disabled. Fixture checks cover both architectures, restart propagation, quoting, registry types, explicit child environments, readiness, and job cleanup. Automated tests do not validate actual Steam login, updates, games, anti-cheat behavior, or simultaneous real Windows seat sessions. Limited manual reports are described below. This source does not establish compatibility with every Steam release. + +## Launching a game directly + +A game launched from Explorer does not inherit the isolated Steam client's IPC environment. Launch it from that Steam client's library, or use `Launch-Game.cmd` and `Launch-IsolatedGame.ps1` together from this component's source folder. The command opens an EXE picker and launches the selected game with the same session/user-derived Steam connection name and its own installation folder as the working directory. Run it as the normal seat account, after starting Steam with the native supervisor. + +The picker changes only the new game's environment. It does not inject a DLL into the game, modify saves, or create a second copy. This directly launched game is outside the Steam supervisor's job, so stopping the supervisor does not guarantee that the selected game exits. Close the game separately. The helper checks for a Steam process in the session; it does not verify that process was launched with isolation. + +Manual user testing reported simultaneous Steam clients on the host and one Windows seat and successful Valheim launches via this helper. The game installation included a third-party Steamworks compatibility component; unmodified retail Steamworks games have not been validated. These reports supplement the fixture checks rather than establishing general game compatibility. + +The IPC override technique predates this implementation; see [steam-multibox-easysetup](https://github.com/dreamware-media/steam-multibox-easysetup). This implementation adds targeted hooks, startup readiness, restart propagation, and process-tree supervision. No claim is made that those additions outperform simpler launch scripts across all games. diff --git a/src/MultiSeat.Service/Configuration/MultiSeatOptions.cs b/src/MultiSeat.Service/Configuration/MultiSeatOptions.cs index 462b7c6..ff8ca48 100644 --- a/src/MultiSeat.Service/Configuration/MultiSeatOptions.cs +++ b/src/MultiSeat.Service/Configuration/MultiSeatOptions.cs @@ -428,6 +428,13 @@ public sealed class MultiSeatOptions // at login run before any stream and never see the per-stream virtual pad. public LaunchOnConnectApp[] LaunchOnConnect { get; set; } = []; + /// Opt in to wrapping explicit steam.exe seat launches with the native isolation helper. + public bool SteamIsolationEnabled { get; set; } = false; + + /// Absolute path to the supervising x64 launcher; matching hooks live beside it. + public string SteamIsolationLauncherPath { get; set; } = + @"C:\Program Files\MultiSeat\SteamIsolation\MultiSeat.SteamLauncher.exe"; + // Delay after the client-connect event before launching the apps, giving Apollo // a moment to create the virtual controller so the apps enumerate it at startup. public int LaunchOnConnectDelayMs { get; set; } = 4_000; diff --git a/src/MultiSeat.Service/Sessions/ProcessInjector.cs b/src/MultiSeat.Service/Sessions/ProcessInjector.cs index a95056f..f5a320a 100644 --- a/src/MultiSeat.Service/Sessions/ProcessInjector.cs +++ b/src/MultiSeat.Service/Sessions/ProcessInjector.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Security.Principal; using Microsoft.Extensions.Options; using MultiSeat.Service.Configuration; using MultiSeat.Service.Interop; @@ -66,6 +67,11 @@ public async Task LaunchInSessionAsync( var consoleSessionId = (int)Kernel32.WTSGetActiveConsoleSessionId(); EnsureNotConsoleSession(sessionId, consoleSessionId, exePath, allowConsoleSession); + var launchPlan = SteamIsolationLaunchPlan.Create(exePath, arguments, _options); + if (launchPlan.Supervised) + _logger.LogInformation("Launching isolated Steam through '{Launcher}' in session {Sid}", + launchPlan.ExecutablePath, sessionId); + // Step 1: Get a primary token for the target session using var token = _sessionLauncher.GetSessionToken(sessionId, accountName); @@ -101,6 +107,12 @@ public async Task LaunchInSessionAsync( Marshal.GetLastWin32Error(), sessionId, exePath); } + using var tokenIdentity = launchPlan.Supervised ? new WindowsIdentity(token.DangerousGetHandle()) : null; + using var readiness = launchPlan.Supervised + ? new SteamIsolationReadiness(tokenIdentity!.User ?? throw new InvalidOperationException("Seat token has no user SID.")) + : null; + if (readiness is not null) launchPlan = launchPlan.WithReadyEvent(readiness.Name); + // Step 2: Create the environment block for the user if (!UserEnv.CreateEnvironmentBlock(out var envBlock, token, false)) { @@ -112,7 +124,7 @@ public async Task LaunchInSessionAsync( try { // Step 3: Build the command line - var cmdLine = FormatCommandLine(exePath, arguments); + var cmdLine = FormatCommandLine(launchPlan.ExecutablePath, launchPlan.Arguments); var si = new Kernel32.StartupInfo { @@ -154,12 +166,25 @@ public async Task LaunchInSessionAsync( // session we ASKED for, which is a claim about our intent rather than about the world — // so a process running in the wrong session produced a perfectly reassuring log. Seven // rounds of issue #18 went past exactly that line. - VerifyLandedInSession(pi, exePath, sessionId, consoleSessionId); - try { + VerifyLandedInSession(pi, exePath, sessionId, consoleSessionId); // Step 5: Wait briefly for the process to initialize - await WaitForProcessStartAsync(pi, ct); + if (readiness is not null) + await readiness.WaitAsync(pi.hProcess, TimeSpan.FromSeconds(180), ct); + else + await WaitForProcessStartAsync(pi, ct); + } + catch + { + // The supervisor owns a kill-on-close job containing Steam and its children. + // Terminating it also cleans up an incomplete or cancelled isolated launch. + if (readiness is not null && + Kernel32.WaitForSingleObject(pi.hProcess, 0) == Kernel32.WAIT_TIMEOUT && + !Kernel32.TerminateProcess(pi.hProcess, 1)) + _logger.LogError("Failed to terminate Steam isolation supervisor PID {Pid}: {Error}", + pi.dwProcessId, Marshal.GetLastWin32Error()); + throw; } finally { diff --git a/src/MultiSeat.Service/Sessions/SteamIsolationLaunchPlan.cs b/src/MultiSeat.Service/Sessions/SteamIsolationLaunchPlan.cs new file mode 100644 index 0000000..3a923b1 --- /dev/null +++ b/src/MultiSeat.Service/Sessions/SteamIsolationLaunchPlan.cs @@ -0,0 +1,94 @@ +using System.Reflection.PortableExecutable; +using MultiSeat.Service.Configuration; + +namespace MultiSeat.Service.Sessions; + +/// Wraps explicit Steam launches without changing unrelated seat processes. +internal sealed record SteamIsolationLaunchPlan(string ExecutablePath, string? Arguments, bool Supervised) +{ + private string? SteamPath { get; init; } + private string? SteamArguments { get; init; } + + internal SteamIsolationLaunchPlan WithReadyEvent(string eventName) + { + if (!Supervised) throw new InvalidOperationException("Only isolated Steam launches use a readiness event."); + if (!eventName.StartsWith(@"Global\MultiSeatSteamReady-", StringComparison.Ordinal) || + !Guid.TryParseExact(eventName[@"Global\MultiSeatSteamReady-".Length..], "N", out _)) + throw new ArgumentException("Invalid Steam readiness event name.", nameof(eventName)); + return this with { Arguments = BuildArguments(SteamPath!, SteamArguments, eventName) }; + } + + internal static SteamIsolationLaunchPlan Create(string executablePath, string? arguments, + MultiSeatOptions options) + { + if (!options.SteamIsolationEnabled || + !string.Equals(Path.GetFileName(executablePath), "steam.exe", StringComparison.OrdinalIgnoreCase)) + return new(executablePath, arguments, false); + + RequireFile(executablePath, "Steam executable"); + var launcher = options.SteamIsolationLauncherPath; + RequireFile(launcher, "Steam isolation launcher"); + var directory = Path.GetDirectoryName(launcher)!; + var machine = ReadMachine(executablePath); + if (machine is not (Machine.I386 or Machine.Amd64)) + throw new InvalidOperationException("Steam isolation supports only x86 and x64 Steam executables."); + + RequireMachine(launcher, Machine.Amd64, "Steam isolation launcher"); + var hook = Path.Combine(directory, machine == Machine.I386 + ? "MultiSeat.SteamHook32.dll" : "MultiSeat.SteamHook.dll"); + RequireFile(hook, "Steam isolation hook"); + RequireMachine(hook, machine, "Steam isolation hook"); + if (machine == Machine.I386) + { + var launcher32 = Path.Combine(directory, "MultiSeat.SteamLauncher32.exe"); + RequireFile(launcher32, "32-bit Steam isolation launcher"); + RequireMachine(launcher32, Machine.I386, "32-bit Steam isolation launcher"); + } + + // Steam arguments are already a Windows command-line tail. Do not split/requote them. + return new(launcher, BuildArguments(executablePath, arguments, null), true) + { SteamPath = executablePath, SteamArguments = arguments }; + } + + private static string BuildArguments(string steam, string? arguments, string? readyEvent) + { + var wrapped = $"--steam {QuotePath(steam)}"; + if (readyEvent is not null) wrapped += $" --ready-event {readyEvent}"; + wrapped += " --"; + if (!string.IsNullOrWhiteSpace(arguments)) wrapped += " " + arguments; + return wrapped; + } + + private static void RequireFile(string path, string description) + { + if (string.IsNullOrWhiteSpace(path) || path.IndexOfAny(['"', '\0']) >= 0 || + !Path.IsPathFullyQualified(path) || !File.Exists(path)) + throw new InvalidOperationException( + $"{description} must be an existing absolute file path: '{path}'. " + + "Steam was not launched without isolation."); + } + + private static Machine ReadMachine(string path) + { + try + { + using var stream = File.OpenRead(path); + using var pe = new PEReader(stream); + if (pe.PEHeaders.PEHeader is null) throw new BadImageFormatException(); + return pe.PEHeaders.CoffHeader.Machine; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or BadImageFormatException) + { + throw new InvalidOperationException($"Cannot read Steam isolation executable architecture: '{path}'.", ex); + } + } + + private static void RequireMachine(string path, Machine expected, string description) + { + if (ReadMachine(path) != expected) + throw new InvalidOperationException($"{description} has the wrong architecture: '{path}' (expected {expected})."); + } + + // Validated file paths cannot contain a quote and cannot end in a directory separator. + private static string QuotePath(string path) => $"\"{path}\""; +} diff --git a/src/MultiSeat.Service/Sessions/SteamIsolationReadiness.cs b/src/MultiSeat.Service/Sessions/SteamIsolationReadiness.cs new file mode 100644 index 0000000..6874751 --- /dev/null +++ b/src/MultiSeat.Service/Sessions/SteamIsolationReadiness.cs @@ -0,0 +1,117 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Microsoft.Win32.SafeHandles; +using MultiSeat.Service.Interop; + +namespace MultiSeat.Service.Sessions; + +/// A service-owned event shared only with the exact seat account and host administrators. +internal sealed class SteamIsolationReadiness : IDisposable +{ + private readonly EventWaitHandle _event; + internal string Name { get; } + + internal SteamIsolationReadiness(SecurityIdentifier seatSid) + { + Name = @"Global\MultiSeatSteamReady-" + Guid.NewGuid().ToString("N"); + if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(BuildSddl(seatSid), 1, + out var descriptor, out _)) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Cannot create Steam readiness event security."); + using (descriptor) + { + var attributes = new SecurityAttributes + { + Length = Marshal.SizeOf(), + SecurityDescriptor = descriptor.DangerousGetHandle(), + InheritHandle = false + }; + var handle = CreateEventW(ref attributes, true, false, Name); + var error = Marshal.GetLastWin32Error(); + if (handle.IsInvalid) + { + handle.Dispose(); + throw new Win32Exception(error, "Cannot create Steam readiness event."); + } + if (error == 183) // A fresh GUID must never reuse an existing object. + { + handle.Dispose(); + throw new InvalidOperationException("Steam readiness event name collision."); + } + try + { + _event = new EventWaitHandle(false, EventResetMode.ManualReset); + var originalHandle = _event.SafeWaitHandle; + _event.SafeWaitHandle = handle; + originalHandle.Dispose(); + } + catch + { + handle.Dispose(); + throw; + } + } + } + + internal static string BuildSddl(SecurityIdentifier sid) => + $"D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;0x00100002;;;{sid.Value})"; + + internal Task WaitAsync(IntPtr processHandle, TimeSpan timeout, CancellationToken ct) => + WaitCoreAsync(() => _event.WaitOne(0), () => + { + var status = Kernel32.WaitForSingleObject(processHandle, 0); + if (status == Kernel32.WAIT_OBJECT_0) + { + if (!Kernel32.GetExitCodeProcess(processHandle, out var code)) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Cannot read Steam launcher exit code."); + return code; + } + if (status != Kernel32.WAIT_TIMEOUT) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Cannot wait for Steam launcher."); + return null; + }, timeout, ct); + + internal static async Task WaitCoreAsync(Func isReady, Func exitCode, + TimeSpan timeout, CancellationToken ct) + { + var elapsed = System.Diagnostics.Stopwatch.StartNew(); + while (true) + { + ct.ThrowIfCancellationRequested(); + if (isReady()) return; + if (exitCode() is { } code) + throw new InvalidOperationException($"Steam isolation launcher exited before initialization succeeded (code {code})."); + if (elapsed.Elapsed >= timeout) + throw new TimeoutException("Steam isolation initialization did not signal readiness before the timeout."); + await Task.Delay(TimeSpan.FromMilliseconds(50), ct); + } + } + + public void Dispose() => _event.Dispose(); + + [StructLayout(LayoutKind.Sequential)] + private struct SecurityAttributes + { + public int Length; + public IntPtr SecurityDescriptor; + [MarshalAs(UnmanagedType.Bool)] public bool InheritHandle; + } + + private sealed class SafeLocalMemoryHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeLocalMemoryHandle() : base(true) { } + protected override bool ReleaseHandle() => LocalFree(handle) == IntPtr.Zero; + } + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptorW( + string descriptor, uint revision, out SafeLocalMemoryHandle result, out uint size); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern SafeWaitHandle CreateEventW(ref SecurityAttributes attributes, + [MarshalAs(UnmanagedType.Bool)] bool manualReset, [MarshalAs(UnmanagedType.Bool)] bool initialState, string name); + + [DllImport("kernel32.dll")] + private static extern IntPtr LocalFree(IntPtr memory); +} diff --git a/src/MultiSeat.Service/appsettings.json b/src/MultiSeat.Service/appsettings.json index 0b28beb..5bc0831 100644 --- a/src/MultiSeat.Service/appsettings.json +++ b/src/MultiSeat.Service/appsettings.json @@ -43,6 +43,8 @@ "ProcessLaunchTimeoutMs": 10000, "HealthCheckIntervalMs": 5000, "LaunchOnConnectDelayMs": 4000, + "SteamIsolationEnabled": false, + "SteamIsolationLauncherPath": "C:\\Program Files\\MultiSeat\\SteamIsolation\\MultiSeat.SteamLauncher.exe", "KillLaunchOnConnectAppsOnDisconnect": false, "LaunchOnConnect": [], "EnableSharedGameLibrary": true, diff --git a/src/MultiSeat.SteamIsolation/CMakeLists.txt b/src/MultiSeat.SteamIsolation/CMakeLists.txt new file mode 100644 index 0000000..bc3b461 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.20) +project(MultiSeatSteamIsolation LANGUAGES C CXX) +if(NOT WIN32) + message(FATAL_ERROR "Steam isolation requires Windows") +endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(BUILD_SHARED_LIBS OFF) +add_subdirectory(third_party/minhook) +add_library(MultiSeatSteamHook SHARED Hook.cpp) +target_link_libraries(MultiSeatSteamHook PRIVATE minhook advapi32 shell32 user32) +add_executable(MultiSeatSteamLauncher Launcher.cpp) +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set_target_properties(MultiSeatSteamHook PROPERTIES OUTPUT_NAME "MultiSeat.SteamHook" PREFIX "") + set_target_properties(MultiSeatSteamLauncher PROPERTIES OUTPUT_NAME "MultiSeat.SteamLauncher") +else() + set_target_properties(MultiSeatSteamHook PROPERTIES OUTPUT_NAME "MultiSeat.SteamHook32" PREFIX "") + set_target_properties(MultiSeatSteamLauncher PROPERTIES OUTPUT_NAME "MultiSeat.SteamLauncher32") +endif() +foreach(target MultiSeatSteamHook MultiSeatSteamLauncher) + target_compile_definitions(${target} PRIVATE UNICODE _UNICODE NOMINMAX) + if(MSVC) + target_compile_options(${target} PRIVATE /W4 /permissive-) + endif() +endforeach() +option(MULTISEAT_BUILD_STEAM_FIXTURE "Build safe native isolation test fixture (no Steam needed)" OFF) +if(MULTISEAT_BUILD_STEAM_FIXTURE) + enable_testing() + add_executable(SteamIsolationFixture tests/Fixture.cpp) + target_compile_definitions(SteamIsolationFixture PRIVATE UNICODE _UNICODE NOMINMAX) + target_link_libraries(SteamIsolationFixture PRIVATE advapi32) + add_executable(SteamIsolationPolicyTests tests/PolicyTests.cpp) + target_compile_definitions(SteamIsolationPolicyTests PRIVATE UNICODE _UNICODE NOMINMAX) + target_link_libraries(SteamIsolationPolicyTests PRIVATE shell32) + add_test(NAME SteamIsolationPolicyTests COMMAND SteamIsolationPolicyTests) +endif() diff --git a/src/MultiSeat.SteamIsolation/Hook.cpp b/src/MultiSeat.SteamIsolation/Hook.cpp new file mode 100644 index 0000000..960995f --- /dev/null +++ b/src/MultiSeat.SteamIsolation/Hook.cpp @@ -0,0 +1,240 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include "IsolationPolicy.h" +#include "Inject.h" + +namespace { +using namespace steam_isolation; +std::wstring ipcName, commandW; +std::string commandA; +bool isSteam = false; +HMODULE ownModule = nullptr; +std::wstring ownDllPath; +LONG initialization = 0; +thread_local bool creating = false; + +// Stable Windows process-parameter prefix, following winsiderss/phnt ntrtl.h. +// Only normalized parameters with the full prefix are admitted for rewriting. +struct CurrentDir { UNICODE_STRING path; HANDLE handle; }; +struct DriveDir { USHORT flags, length; ULONG timestamp; ANSI_STRING path; }; +struct Parameters { + ULONG maximumLength, length, flags, debugFlags; + HANDLE console; ULONG consoleFlags; + HANDLE input, output, error; + CurrentDir currentDirectory; + UNICODE_STRING dllPath, image, command; + PVOID environment; + ULONG startingX, startingY, countX, countY, countCharsX, countCharsY, fillAttribute; + ULONG windowFlags, showWindowFlags; + UNICODE_STRING windowTitle, desktop, shell, runtime; + DriveDir drives[32]; + ULONG_PTR environmentSize, environmentVersion; +}; +#ifdef _WIN64 +static_assert(offsetof(Parameters, command) == 0x70 && offsetof(Parameters, environmentSize) == 0x3f0); +#else +static_assert(offsetof(Parameters, command) == 0x40 && offsetof(Parameters, environmentSize) == 0x290); +#endif +using CreateUser = NTSTATUS (NTAPI*)(PHANDLE, PHANDLE, ACCESS_MASK, ACCESS_MASK, + POBJECT_ATTRIBUTES, POBJECT_ATTRIBUTES, ULONG, ULONG, PVOID, PVOID, PVOID); +CreateUser originalCreate = nullptr; +decltype(&CreateProcessW) createW = nullptr; +decltype(&CreateProcessA) createA = nullptr; +decltype(&RegQueryValueExW) queryW = nullptr; +decltype(&RegQueryValueExA) queryA = nullptr; +decltype(&RegSetValueExW) setW = nullptr; +decltype(&RegSetValueExA) setA = nullptr; +decltype(&RegDeleteValueW) deleteW = nullptr; +decltype(&RegDeleteValueA) deleteA = nullptr; + +bool Temporary(const wchar_t* value) { + return isSteam && value && (!_wcsicmp(value, L"TempAppPath") || !_wcsicmp(value, L"TempAppCmdLine")); +} +bool Temporary(const char* value) { + return isSteam && value && (!_stricmp(value, "TempAppPath") || !_stricmp(value, "TempAppCmdLine")); +} +struct UserSteamKey { + HKEY value = nullptr; + LSTATUS status = ERROR_INVALID_HANDLE; + explicit UserSteamKey(REGSAM access, bool create = false) { + if (!create) status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, access, &value); + else status = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, nullptr, 0, access, nullptr, &value, nullptr); + } + ~UserSteamKey() { if (value) RegCloseKey(value); } +}; +LSTATUS WINAPI QueryW(HKEY key, LPCWSTR value, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD size) { + LSTATUS result; + DWORD actualType = 0; auto returnedType = type ? type : &actualType; + if (Temporary(value)) { UserSteamKey user(KEY_QUERY_VALUE); if (!user.value) return user.status; result = queryW(user.value, value, reserved, returnedType, data, size); } + else result = queryW(key, value, reserved, returnedType, data, size); + if (result == ERROR_SUCCESS && value && !_wcsicmp(value, L"SteamPID") && data && size && *size == sizeof(DWORD) && *returnedType == REG_DWORD) { + DWORD zero = 0; memcpy(data, &zero, sizeof(zero)); + } + return result; +} +LSTATUS WINAPI QueryA(HKEY key, LPCSTR value, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD size) { + LSTATUS result; + DWORD actualType = 0; auto returnedType = type ? type : &actualType; + if (Temporary(value)) { UserSteamKey user(KEY_QUERY_VALUE); if (!user.value) return user.status; result = queryA(user.value, value, reserved, returnedType, data, size); } + else result = queryA(key, value, reserved, returnedType, data, size); + if (result == ERROR_SUCCESS && value && !_stricmp(value, "SteamPID") && data && size && *size == sizeof(DWORD) && *returnedType == REG_DWORD) { + DWORD zero = 0; memcpy(data, &zero, sizeof(zero)); + } + return result; +} +LSTATUS WINAPI SetW(HKEY key, LPCWSTR value, DWORD reserved, DWORD type, const BYTE* data, DWORD size) { + if (!Temporary(value)) return setW(key, value, reserved, type, data, size); + UserSteamKey user(KEY_SET_VALUE, true); return user.value ? setW(user.value, value, reserved, type, data, size) : user.status; +} +LSTATUS WINAPI SetA(HKEY key, LPCSTR value, DWORD reserved, DWORD type, const BYTE* data, DWORD size) { + if (!Temporary(value)) return setA(key, value, reserved, type, data, size); + UserSteamKey user(KEY_SET_VALUE, true); return user.value ? setA(user.value, value, reserved, type, data, size) : user.status; +} +LSTATUS WINAPI DeleteW(HKEY key, LPCWSTR value) { + if (!Temporary(value)) return deleteW(key, value); + UserSteamKey user(KEY_SET_VALUE); return user.value ? deleteW(user.value, value) : user.status; +} +LSTATUS WINAPI DeleteA(HKEY key, LPCSTR value) { + if (!Temporary(value)) return deleteA(key, value); + UserSteamKey user(KEY_SET_VALUE); return user.value ? deleteA(user.value, value) : user.status; +} +LPWSTR WINAPI CommandW() { return commandW.data(); } +LPSTR WINAPI CommandA() { return commandA.data(); } +bool SteamLaunch(LPCWSTR application, LPCWSTR command) { + if (application) return SteamImage(application); + if (!command) return false; + int count = 0; LPWSTR* args = CommandLineToArgvW(command, &count); + bool result = args && count && SteamImage(args[0]); if (args) LocalFree(args); return result; +} +std::wstring Wide(LPCSTR value) { + if (!value) return {}; + int size = MultiByteToWideChar(CP_ACP, 0, value, -1, nullptr, 0); + if (!size) return {}; + std::wstring result(size, 0); MultiByteToWideChar(CP_ACP, 0, value, -1, result.data(), size); + result.resize(size - 1); return result; +} +BOOL FinishRestart(PROCESS_INFORMATION* pi, DWORD flags) { + std::wstring error; + try { + if (Inject(pi->hProcess, ownDllPath, error) && + ((flags & CREATE_SUSPENDED) || ResumeThread(pi->hThread) != static_cast(-1))) return TRUE; + } catch (...) { error = L"Resource allocation failed"; } + OutputDebugStringW((L"MultiSeat Steam restart isolation failed: " + error).c_str()); + TerminateProcess(pi->hProcess, 1); CloseHandle(pi->hThread); CloseHandle(pi->hProcess); *pi = {}; + SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; +} +BOOL WINAPI CreateW(LPCWSTR app, LPWSTR command, LPSECURITY_ATTRIBUTES processAttributes, + LPSECURITY_ATTRIBUTES threadAttributes, BOOL inherit, DWORD flags, LPVOID environment, + LPCWSTR directory, LPSTARTUPINFOW startup, LPPROCESS_INFORMATION pi) { + bool restart = SteamLaunch(app, command); + BOOL result = createW(app, command, processAttributes, threadAttributes, inherit, + restart ? flags | CREATE_SUSPENDED : flags, environment, directory, startup, pi); + return result && restart ? FinishRestart(pi, flags) : result; +} +BOOL WINAPI CreateA(LPCSTR app, LPSTR command, LPSECURITY_ATTRIBUTES processAttributes, + LPSECURITY_ATTRIBUTES threadAttributes, BOOL inherit, DWORD flags, LPVOID environment, + LPCSTR directory, LPSTARTUPINFOA startup, LPPROCESS_INFORMATION pi) { + auto wideApp = Wide(app), wideCommand = Wide(command); + bool restart = SteamLaunch(app ? wideApp.c_str() : nullptr, command ? wideCommand.c_str() : nullptr); + BOOL result = createA(app, command, processAttributes, threadAttributes, inherit, + restart ? flags | CREATE_SUSPENDED : flags, environment, directory, startup, pi); + return result && restart ? FinishRestart(pi, flags) : result; +} +NTSTATUS NTAPI CreateUserHook(PHANDLE process, PHANDLE thread, ACCESS_MASK pa, ACCESS_MASK ta, + POBJECT_ATTRIBUTES po, POBJECT_ATTRIBUTES to, ULONG pf, ULONG tf, PVOID parameters, PVOID info, PVOID attributes) { + auto invoke = [&]() { return originalCreate(process, thread, pa, ta, po, to, pf, tf, parameters, info, attributes); }; + if (creating || !parameters) return invoke(); + auto* p = static_cast(parameters); + if (!(p->flags & 1) || p->length < offsetof(Parameters, environmentVersion) + sizeof(ULONG_PTR)) return invoke(); + creating = true; + struct Reset { ~Reset() { creating = false; } } reset; + UNICODE_STRING saved = p->command; + void* oldEnvironment = p->environment; + ULONG_PTR oldSize = p->environmentSize; + struct Restore { + Parameters* p; UNICODE_STRING command; PVOID environment; ULONG_PTR size; + ~Restore() { p->command = command; p->environment = environment; p->environmentSize = size; } + } restore{p, saved, oldEnvironment, oldSize}; + try { + std::wstring changed; + if (saved.Buffer && !(saved.Length % sizeof(wchar_t))) { + changed = RewriteCommand(std::wstring(saved.Buffer, saved.Length / sizeof(wchar_t)), ipcName); + if (changed.size() > 32766) return static_cast(0xc000000dL); + p->command.Buffer = changed.data(); + p->command.Length = static_cast(changed.size() * sizeof(wchar_t)); + p->command.MaximumLength = static_cast((changed.size() + 1) * sizeof(wchar_t)); + } + std::vector environment; + if (oldEnvironment && oldSize >= 4 && oldSize <= 16 * 1024 * 1024 && !(oldSize % sizeof(wchar_t))) { + environment = RewriteEnvironment(static_cast(oldEnvironment), oldSize / sizeof(wchar_t), ipcName); + if (environment.empty()) return static_cast(0xc000000dL); + p->environment = environment.data(); p->environmentSize = environment.size() * sizeof(wchar_t); + } + return invoke(); + } catch (...) { return static_cast(0xc0000017L); } +} +template bool Hook(const wchar_t* module, const char* function, void* replacement, T* original) { + HMODULE handle = GetModuleHandleW(module); + auto address = handle ? GetProcAddress(handle, function) : nullptr; + return address && MH_CreateHook(reinterpret_cast(address), replacement, reinterpret_cast(original)) == MH_OK; +} +} + +#if defined(_MSC_VER) && !defined(_WIN64) +#pragma comment(linker, "/EXPORT:MultiSeatInitialize=_MultiSeatInitialize@4") +#endif +extern "C" __declspec(dllexport) DWORD WINAPI MultiSeatInitialize(void*) { + if (InterlockedCompareExchange(&initialization, 1, 0) != 0) return initialization == 2 ? 1 : 0; + try { + DWORD session = 0; + if (!ProcessIdToSessionId(GetCurrentProcessId(), &session)) return 0; + HANDLE token = nullptr; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) return 0; + DWORD bytes = 0; GetTokenInformation(token, TokenUser, nullptr, 0, &bytes); + std::vector data(bytes); + bool got = GetTokenInformation(token, TokenUser, data.data(), bytes, &bytes) != FALSE; + CloseHandle(token); if (!got) return 0; + LPWSTR sid = nullptr; if (!ConvertSidToStringSidW(reinterpret_cast(data.data())->User.Sid, &sid)) return 0; + uint64_t hash = 14695981039346656037ULL; + for (const wchar_t* c = sid; *c; ++c) { hash ^= static_cast(*c); hash *= 1099511628211ULL; } + LocalFree(sid); + ipcName = L"MultiSeat_" + std::to_wstring(session) + L"_" + std::to_wstring(hash); + wchar_t image[32768]; DWORD length = GetModuleFileNameW(nullptr, image, 32768); + if (!length || length >= 32768 || !SteamImage(std::wstring(image, length))) return 0; + isSteam = true; + wchar_t dllPath[32768]; DWORD dllLength = GetModuleFileNameW(ownModule, dllPath, 32768); + if (!dllLength || dllLength >= 32768) return 0; + ownDllPath.assign(dllPath, dllLength); + commandW = RewriteCommand(GetCommandLineW(), ipcName); + int narrowSize = WideCharToMultiByte(CP_ACP, 0, commandW.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (!narrowSize) return 0; + commandA.resize(narrowSize); WideCharToMultiByte(CP_ACP, 0, commandW.c_str(), -1, commandA.data(), narrowSize, nullptr, nullptr); + if (!SetEnvironmentVariableW(L"steam_master_ipc_name_override", ipcName.c_str())) return 0; + if (MH_Initialize() != MH_OK) return 0; + LPWSTR (WINAPI* originalCommandW)() = nullptr; LPSTR (WINAPI* originalCommandA)() = nullptr; + bool ok = Hook(L"ntdll.dll", "NtCreateUserProcess", reinterpret_cast(CreateUserHook), &originalCreate) + // Win32 completes subsystem registration before restart injection; doing this + // inside NtCreateUserProcess is too early for remote loader initialization. + && Hook(L"kernelbase.dll", "CreateProcessW", reinterpret_cast(CreateW), &createW) + && Hook(L"kernelbase.dll", "CreateProcessA", reinterpret_cast(CreateA), &createA) + && Hook(L"kernelbase.dll", "GetCommandLineW", reinterpret_cast(CommandW), &originalCommandW) + && Hook(L"kernelbase.dll", "GetCommandLineA", reinterpret_cast(CommandA), &originalCommandA) + && Hook(L"kernelbase.dll", "RegQueryValueExW", reinterpret_cast(QueryW), &queryW) + && Hook(L"kernelbase.dll", "RegQueryValueExA", reinterpret_cast(QueryA), &queryA) + && Hook(L"kernelbase.dll", "RegSetValueExW", reinterpret_cast(SetW), &setW) + && Hook(L"kernelbase.dll", "RegSetValueExA", reinterpret_cast(SetA), &setA) + && Hook(L"kernelbase.dll", "RegDeleteValueW", reinterpret_cast(DeleteW), &deleteW) + && Hook(L"kernelbase.dll", "RegDeleteValueA", reinterpret_cast(DeleteA), &deleteA); + if (!ok || MH_EnableHook(MH_ALL_HOOKS) != MH_OK) { MH_Uninitialize(); return 0; } + InterlockedExchange(&initialization, 2); return 1; + } catch (...) { return 0; } +} +BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) { + if (reason == DLL_PROCESS_ATTACH) { ownModule = module; DisableThreadLibraryCalls(module); } + return TRUE; +} diff --git a/src/MultiSeat.SteamIsolation/Inject.h b/src/MultiSeat.SteamIsolation/Inject.h new file mode 100644 index 0000000..3a49dd7 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/Inject.h @@ -0,0 +1,156 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +namespace steam_isolation { +struct Handle { + HANDLE value = nullptr; + explicit Handle(HANDLE h = nullptr) : value(h) {} + ~Handle() { if (value && value != INVALID_HANDLE_VALUE) CloseHandle(value); } + Handle(const Handle&) = delete; + Handle& operator=(const Handle&) = delete; +}; +inline std::wstring ExecutableDirectory() { + std::vector path(32768); + DWORD n = GetModuleFileNameW(nullptr, path.data(), static_cast(path.size())); + if (!n || n >= path.size()) return {}; + std::wstring result(path.data(), n); + return result.substr(0, result.find_last_of(L"\\/")); +} +inline WORD TargetMachine(const std::wstring& path) { + Handle file(CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + if (file.value == INVALID_HANDLE_VALUE) return 0; + IMAGE_DOS_HEADER dos{}; DWORD read = 0; + if (!ReadFile(file.value, &dos, sizeof(dos), &read, nullptr) || read != sizeof(dos) || + dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < sizeof(dos)) return 0; + LARGE_INTEGER offset{}; offset.QuadPart = dos.e_lfanew; + if (!SetFilePointerEx(file.value, offset, nullptr, FILE_BEGIN)) return 0; + DWORD signature = 0; IMAGE_FILE_HEADER header{}; + if (!ReadFile(file.value, &signature, sizeof(signature), &read, nullptr) || + read != sizeof(signature) || signature != IMAGE_NT_SIGNATURE || + !ReadFile(file.value, &header, sizeof(header), &read, nullptr) || read != sizeof(header)) return 0; + return header.Machine; +} +inline uintptr_t FindModule(DWORD pid, const std::wstring& name, bool fullPath) { + HANDLE snapshot = INVALID_HANDLE_VALUE; + for (int attempt = 0; attempt < 8; ++attempt) { + snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); + if (snapshot != INVALID_HANDLE_VALUE || GetLastError() != ERROR_BAD_LENGTH) break; + } + Handle owner(snapshot); + if (snapshot == INVALID_HANDLE_VALUE) return 0; + MODULEENTRY32W module{}; module.dwSize = sizeof(module); + if (Module32FirstW(snapshot, &module)) do { + if (_wcsicmp(fullPath ? module.szExePath : module.szModule, name.c_str()) == 0) + return reinterpret_cast(module.modBaseAddr); + } while (Module32NextW(snapshot, &module)); + return 0; +} +inline bool Inject(HANDLE process, const std::wstring& dllPath, std::wstring& error) { + auto fail = [&](const wchar_t* stage) { + error = std::wstring(stage) + L" (Windows error " + std::to_wstring(GetLastError()) + L")"; + return false; + }; + // Call only for a suspended process with the same architecture as this caller. + BOOL targetWow = FALSE, selfWow = FALSE; + if (!IsWow64Process(process, &targetWow) || !IsWow64Process(GetCurrentProcess(), &selfWow)) + return fail(L"Cannot inspect process architecture"); + if (targetWow != selfWow) { error = L"Injection requires matching process architecture"; return false; } + wchar_t absolute[32768]; + DWORD n = GetFullPathNameW(dllPath.c_str(), 32768, absolute, nullptr); + if (!n || n >= 32768) return fail(L"Cannot resolve isolation DLL path"); + std::wstring resolved(absolute, n); + HMODULE localDll = LoadLibraryExW(resolved.c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES); + if (!localDll) return fail(L"Cannot inspect isolation DLL"); + auto init = GetProcAddress(localDll, "MultiSeatInitialize"); + if (!init) { FreeLibrary(localDll); error = L"Isolation DLL is missing MultiSeatInitialize"; return false; } + uintptr_t initOffset = reinterpret_cast(init) - reinterpret_cast(localDll); + FreeLibrary(localDll); + auto load = GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"); + HMODULE loadModule = nullptr; + if (!load || !GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(load), &loadModule)) + return fail(L"Cannot locate loader"); + wchar_t modulePath[32768]; + if (!GetModuleFileNameW(loadModule, modulePath, 32768)) return fail(L"Cannot locate loader module"); + std::wstring moduleName(modulePath); + moduleName = moduleName.substr(moduleName.find_last_of(L"\\/") + 1); + DWORD pid = GetProcessId(process); + uintptr_t remoteModule = FindModule(pid, moduleName, false); + if (!remoteModule) { + // A newly suspended process may have only ntdll mapped. Creating its first + // remote thread lets Windows initialize imports; the application's primary + // thread remains suspended. Exit immediately once that loader work completes. + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + auto exitThread = GetProcAddress(ntdll, "RtlExitUserThread"); + uintptr_t remoteNtdll = FindModule(pid, L"ntdll.dll", false); + if (!remoteNtdll) { + // Toolhelp's loader list is empty before initialization. Query mapped + // images directly instead; unlike a guessed address this handles ASLR. + uintptr_t address = 0; + MEMORY_BASIC_INFORMATION region{}; + while (VirtualQueryEx(process, reinterpret_cast(address), ®ion, sizeof(region))) { + if (region.Type == MEM_IMAGE && region.BaseAddress == region.AllocationBase) { + wchar_t mapped[32768]; + DWORD count = K32GetMappedFileNameW(process, region.AllocationBase, mapped, 32768); + if (count && count < 32768) { + std::wstring image(mapped, count); + auto name = image.substr(image.find_last_of(L"\\/") + 1); + if (_wcsicmp(name.c_str(), L"ntdll.dll") == 0) { + remoteNtdll = reinterpret_cast(region.AllocationBase); break; + } + } + } + uintptr_t next = reinterpret_cast(region.BaseAddress) + region.RegionSize; + if (next <= address) break; + address = next; + } + } + if (!exitThread || !remoteNtdll) { error = L"Target bootstrap module unavailable"; return false; } + uintptr_t remoteExit = remoteNtdll + reinterpret_cast(exitThread) - reinterpret_cast(ntdll); + Handle bootstrap(CreateRemoteThread(process, nullptr, 0, + reinterpret_cast(remoteExit), nullptr, 0, nullptr)); + if (!bootstrap.value) return fail(L"Cannot initialize target loader"); + if (WaitForSingleObject(bootstrap.value, 60000) != WAIT_OBJECT_0) { + error = L"Target loader initialization failed or timed out"; return false; + } + remoteModule = FindModule(pid, moduleName, false); + } + if (!remoteModule) { error = L"Target loader module unavailable"; return false; } + uintptr_t remoteLoad = remoteModule + reinterpret_cast(load) - reinterpret_cast(loadModule); + SIZE_T bytes = (resolved.size() + 1) * sizeof(wchar_t); + void* remotePath = VirtualAllocEx(process, nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!remotePath) return fail(L"Cannot allocate remote path"); + SIZE_T written = 0; + if (!WriteProcessMemory(process, remotePath, resolved.c_str(), bytes, &written) || written != bytes) { + auto result = fail(L"Cannot write remote path"); VirtualFreeEx(process, remotePath, 0, MEM_RELEASE); return result; + } + Handle loader(CreateRemoteThread(process, nullptr, 0, reinterpret_cast(remoteLoad), remotePath, 0, nullptr)); + if (!loader.value) { auto result = fail(L"Cannot start remote loader"); VirtualFreeEx(process, remotePath, 0, MEM_RELEASE); return result; } + DWORD wait = WaitForSingleObject(loader.value, 60000); + if (wait != WAIT_OBJECT_0) { + // Leave the allocation alive until the caller terminates the failed suspended child. + error = L"Remote loader failed or timed out"; return false; + } + VirtualFreeEx(process, remotePath, 0, MEM_RELEASE); + uintptr_t remoteDll = FindModule(pid, resolved, true); + if (!remoteDll) { error = L"Target did not load isolation DLL"; return false; } + Handle initializer(CreateRemoteThread(process, nullptr, 0, + reinterpret_cast(remoteDll + initOffset), nullptr, 0, nullptr)); + if (!initializer.value) return fail(L"Cannot start isolation initialization"); + if (WaitForSingleObject(initializer.value, 60000) != WAIT_OBJECT_0) { + error = L"Isolation initialization failed or timed out"; return false; + } + DWORD result = 0; + if (!GetExitCodeThread(initializer.value, &result) || result != 1) { + error = L"Isolation initialization refused to activate"; return false; + } + return true; +} +} diff --git a/src/MultiSeat.SteamIsolation/IsolationPolicy.h b/src/MultiSeat.SteamIsolation/IsolationPolicy.h new file mode 100644 index 0000000..3af7154 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/IsolationPolicy.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace steam_isolation { +inline std::wstring BaseName(const std::wstring& path) { + return path.substr(path.find_last_of(L"\\/") + 1); +} +inline bool Equal(const std::wstring& a, const std::wstring& b) { return _wcsicmp(a.c_str(), b.c_str()) == 0; } +inline bool SteamImage(const std::wstring& image) { return Equal(BaseName(image), L"steam.exe"); } +inline bool BrowserImage(const std::wstring& image) { return Equal(BaseName(image), L"steamwebhelper.exe"); } +inline std::wstring Quote(const std::wstring& value) { + std::wstring result = L"\""; size_t slashes = 0; + for (wchar_t c : value) { + if (c == L'\\') { ++slashes; continue; } + if (c == L'"') result.append(slashes * 2 + 1, L'\\'); + else result.append(slashes, L'\\'); + slashes = 0; result += c; + } + result.append(slashes * 2, L'\\'); return result + L"\""; +} +inline std::wstring RewriteCommand(const std::wstring& raw, const std::wstring& name) { + int count = 0; LPWSTR* parsed = CommandLineToArgvW(raw.c_str(), &count); + if (!parsed || count == 0) { if (parsed) LocalFree(parsed); return raw; } + std::vector args(parsed, parsed + count); LocalFree(parsed); + if (!SteamImage(args[0]) && !BrowserImage(args[0])) return raw; + std::vector output{args[0]}; bool changed = false; + for (size_t i = 1; i < args.size(); ++i) { + auto arg = args[i]; + if (SteamImage(args[0]) && Equal(arg, L"-master_ipc_name_override")) { + if (i + 1 < args.size()) ++i; + changed = true; continue; + } + if (SteamImage(args[0]) && arg.size() >= 26 && Equal(arg.substr(0, 26), L"-master_ipc_name_override=")) { + changed = true; continue; + } + std::wstring lowerArg = arg; std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), towlower); + bool cachePath = lowerArg.rfind(L"-cachedir=", 0) == 0 || lowerArg.rfind(L"--log-file=", 0) == 0 || + lowerArg.rfind(L"-cef-log-file=", 0) == 0 || (arg.size() >= 3 && arg[1] == L':' && (arg[2] == L'\\' || arg[2] == L'/')) || + arg.rfind(L"\\\\", 0) == 0; + // Limit rewriting to path arguments; URLs and arbitrary labels keep their values. + for (const auto& marker : {std::wstring(L"htmlcache"), std::wstring(L"cef_log")}) { + if (!cachePath) break; + std::wstring lower = arg; std::transform(lower.begin(), lower.end(), lower.begin(), towlower); + size_t position = 0; + while ((position = lower.find(marker, position)) != std::wstring::npos) { + size_t end = position + marker.size(); + bool left = position == 0 || arg[position - 1] == L'\\' || arg[position - 1] == L'/' || arg[position - 1] == L'='; + bool right = end == arg.size() || arg[end] == L'.' || arg[end] == L'\\' || arg[end] == L'/'; + const std::wstring suffix = L"." + name; + if (left && right && arg.compare(end, suffix.size(), suffix) != 0) { + arg.insert(end, suffix); lower.insert(end, suffix); changed = true; end += suffix.size(); + } + position = end; + } + } + output.push_back(std::move(arg)); + } + if (SteamImage(args[0])) { output.push_back(L"-master_ipc_name_override"); output.push_back(name); changed = true; } + if (!changed) return raw; + std::wstring result; + for (const auto& arg : output) { if (!result.empty()) result += L' '; result += Quote(arg); } + return result; +} +inline std::vector RewriteEnvironment(const wchar_t* data, size_t characters, const std::wstring& name) { + if (!data || characters < 2 || data[characters - 1] != 0 || data[characters - 2] != 0) return {}; + std::vector entries; + size_t pos = 0; + while (pos < characters && data[pos]) { + size_t end = pos; while (end < characters && data[end]) ++end; + if (end == characters) return {}; + std::wstring value(data + pos, end - pos); + const std::wstring prefix = L"steam_master_ipc_name_override="; + if (value.size() < prefix.size() || !Equal(value.substr(0, prefix.size()), prefix)) entries.push_back(value); + pos = end + 1; + } + entries.push_back(L"steam_master_ipc_name_override=" + name); + std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { return _wcsicmp(a.c_str(), b.c_str()) < 0; }); + std::vector result; + for (const auto& value : entries) { result.insert(result.end(), value.begin(), value.end()); result.push_back(0); } + result.push_back(0); return result; +} +} diff --git a/src/MultiSeat.SteamIsolation/Launch-Game.cmd b/src/MultiSeat.SteamIsolation/Launch-Game.cmd new file mode 100644 index 0000000..e518685 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/Launch-Game.cmd @@ -0,0 +1,2 @@ +@echo off +start "" "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -STA -WindowStyle Hidden -ExecutionPolicy Bypass -File "%~dp0Launch-IsolatedGame.ps1" diff --git a/src/MultiSeat.SteamIsolation/Launch-IsolatedGame.ps1 b/src/MultiSeat.SteamIsolation/Launch-IsolatedGame.ps1 new file mode 100644 index 0000000..f7783e8 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/Launch-IsolatedGame.ps1 @@ -0,0 +1,67 @@ +param( + [string]$Executable = '', + [string]$Arguments = '' +) +$ErrorActionPreference = 'Stop' +$taskInteractive = [string]::IsNullOrWhiteSpace($Executable) +try { +if ($taskInteractive) { + Add-Type -AssemblyName System.Windows.Forms + [Windows.Forms.Application]::EnableVisualStyles() + $taskPicker = New-Object Windows.Forms.OpenFileDialog + try { + $taskPicker.Title = 'Select a game to launch with isolated Steam' + $taskPicker.Filter = 'Game executable (*.exe)|*.exe' + $taskPicker.CheckFileExists = $true + $taskPicker.Multiselect = $false + $taskPicker.RestoreDirectory = $true + if ($taskPicker.ShowDialog() -ne [Windows.Forms.DialogResult]::OK) { return } + $Executable = $taskPicker.FileName + } finally { $taskPicker.Dispose() } +} +if (-not (Test-Path -LiteralPath $Executable -PathType Leaf)) { + throw "Game executable not found: $Executable" +} +if ([IO.Path]::GetExtension($Executable) -ine '.exe') { + throw 'Select an EXE file.' +} +$taskSession = [Diagnostics.Process]::GetCurrentProcess().SessionId +if (-not @(Get-Process steam -ErrorAction SilentlyContinue | Where-Object { $_.SessionId -eq $taskSession }).Count) { + throw 'Start Steam through MultiSeat.SteamLauncher in this Windows session first.' +} +# Match the session/SID-derived name used by MultiSeat.SteamHook.cpp. +if (-not ('MultiSeatGameIPC' -as [type])) { + Add-Type -TypeDefinition @' +using System; +public static class MultiSeatGameIPC { + public static string Name(int session, string sid) { + ulong hash = 14695981039346656037UL; + unchecked { + foreach (char character in sid) { hash ^= (ulong)character; hash *= 1099511628211UL; } + } + return "MultiSeat_" + session + "_" + hash; + } +} +'@ +} +$taskIdentity = [Security.Principal.WindowsIdentity]::GetCurrent() +try { $taskSid = $taskIdentity.User.Value } finally { $taskIdentity.Dispose() } +$taskIPC = [MultiSeatGameIPC]::Name($taskSession, $taskSid) +$taskStart = New-Object Diagnostics.ProcessStartInfo +$taskStart.FileName = [IO.Path]::GetFullPath($Executable) +$taskStart.WorkingDirectory = [IO.Path]::GetDirectoryName($taskStart.FileName) +$taskStart.Arguments = $Arguments +$taskStart.UseShellExecute = $false +$taskStart.EnvironmentVariables['steam_master_ipc_name_override'] = $taskIPC +Write-Host "Launching game for isolated Steam connection: $taskIPC" +$taskGame = [Diagnostics.Process]::Start($taskStart) +try { Write-Host "Game process started: $($taskGame.Id)" } finally { $taskGame.Dispose() } +} catch { + if ($taskInteractive) { + Add-Type -AssemblyName System.Windows.Forms + [void][Windows.Forms.MessageBox]::Show($_.Exception.Message, 'Steam game launcher', + [Windows.Forms.MessageBoxButtons]::OK, [Windows.Forms.MessageBoxIcon]::Error) + exit 1 + } + throw +} diff --git a/src/MultiSeat.SteamIsolation/Launcher.cpp b/src/MultiSeat.SteamIsolation/Launcher.cpp new file mode 100644 index 0000000..dc08580 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/Launcher.cpp @@ -0,0 +1,121 @@ +#include "Inject.h" +#include + +using namespace steam_isolation; + +// Decode only the launcher prefix. The application's argument tail remains byte-for-byte intact. +static bool NextToken(const std::wstring& raw, size_t& cursor, std::wstring& token) { + token.clear(); + while (cursor < raw.size() && (raw[cursor] == L' ' || raw[cursor] == L'\t')) ++cursor; + if (cursor == raw.size()) return false; + bool quoted = false; + while (cursor < raw.size()) { + if (!quoted && (raw[cursor] == L' ' || raw[cursor] == L'\t')) break; + size_t slashes = 0; + while (cursor < raw.size() && raw[cursor] == L'\\') { ++slashes; ++cursor; } + if (cursor < raw.size() && raw[cursor] == L'"') { + token.append(slashes / 2, L'\\'); + if (slashes % 2) { token.push_back(L'"'); ++cursor; } + else { + ++cursor; + if (quoted && cursor < raw.size() && raw[cursor] == L'"') { token.push_back(L'"'); ++cursor; } + else quoted = !quoted; + } + } else { + token.append(slashes, L'\\'); + if (cursor < raw.size() && (quoted || (raw[cursor] != L' ' && raw[cursor] != L'\t'))) + token.push_back(raw[cursor++]); + } + } + return !quoted; +} +static DWORD Run(const std::wstring& executable, std::wstring command, const std::wstring& hook, + const std::wstring& workingDirectory, HANDLE readyEvent = nullptr) { + Handle job(CreateJobObjectW(nullptr, nullptr)); + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{}; + limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (!job.value || !SetInformationJobObject(job.value, JobObjectExtendedLimitInformation, &limits, sizeof(limits))) { + std::wcerr << L"Cannot create isolation process supervisor: " << GetLastError() << L"\n"; return 1; + } + STARTUPINFOW startup{}; startup.cb = sizeof(startup); + PROCESS_INFORMATION info{}; + if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, + nullptr, workingDirectory.c_str(), &startup, &info)) { + std::wcerr << L"Cannot start Steam isolation process: " << GetLastError() << L"\n"; return 1; + } + Handle process(info.hProcess), thread(info.hThread); + auto stop = [&]() { TerminateProcess(process.value, 1); WaitForSingleObject(process.value, 5000); return DWORD{1}; }; + if (!AssignProcessToJobObject(job.value, process.value)) { + std::wcerr << L"Cannot supervise isolation process: " << GetLastError() << L"\n"; return stop(); + } + std::wstring error; + if (!hook.empty() && !Inject(process.value, hook, error)) { + std::wcerr << L"Steam isolation failed: " << error << L"\n"; return stop(); + } + if (ResumeThread(thread.value) == static_cast(-1)) { + std::wcerr << L"Cannot resume isolated process: " << GetLastError() << L"\n"; return stop(); + } + if (readyEvent && !SetEvent(readyEvent)) { + std::wcerr << L"Cannot signal Steam isolation readiness: " << GetLastError() << L"\n"; return stop(); + } + if (WaitForSingleObject(process.value, INFINITE) != WAIT_OBJECT_0) return stop(); + DWORD exitCode = 1; GetExitCodeProcess(process.value, &exitCode); + // Steam's updater can exit after creating its replacement. Keep owning the + // job until the entire descendant tree exits, rather than killing that replacement. + for (;;) { + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION accounting{}; + if (!QueryInformationJobObject(job.value, JobObjectBasicAccountingInformation, + &accounting, sizeof(accounting), nullptr)) { + std::wcerr << L"Cannot monitor isolation process tree: " << GetLastError() << L"\n"; return 1; + } + if (!accounting.ActiveProcesses) return exitCode; + Sleep(250); + } +} +int wmain() { + std::wstring raw(GetCommandLineW()), token, steam, readyEventName; + size_t cursor = 0; + bool valid = NextToken(raw, cursor, token); + [[maybe_unused]] size_t prefixStart = cursor; + valid = valid && NextToken(raw, cursor, token) && token == L"--steam"; + valid = valid && NextToken(raw, cursor, steam) && !steam.empty(); + valid = valid && NextToken(raw, cursor, token); + if (valid && token == L"--ready-event") { + valid = NextToken(raw, cursor, readyEventName) && !readyEventName.empty() && NextToken(raw, cursor, token); + } + valid = valid && token == L"--"; + const bool absolutePath = (steam.size() > 2 && steam[1] == L':' && (steam[2] == L'\\' || steam[2] == L'/')) || + (steam.size() > 2 && steam[0] == L'\\' && steam[1] == L'\\'); + const auto basename = steam.substr(steam.find_last_of(L"\\/") + 1); + if (!valid || !absolutePath || steam.find(L'"') != std::wstring::npos || _wcsicmp(basename.c_str(), L"steam.exe") != 0) { + std::wcerr << L"Usage: MultiSeat.SteamLauncher.exe --steam [--ready-event ] -- \n"; return 2; + } + Handle readyEvent(readyEventName.empty() ? nullptr : OpenEventW(EVENT_MODIFY_STATE, FALSE, readyEventName.c_str())); + if (!readyEventName.empty() && !readyEvent.value) { + std::wcerr << L"Cannot open Steam isolation readiness event: " << GetLastError() << L"\n"; return 2; + } + std::wstring directory = ExecutableDirectory(); + WORD machine = TargetMachine(steam); + if (machine != IMAGE_FILE_MACHINE_I386 && machine != IMAGE_FILE_MACHINE_AMD64) { + std::wcerr << L"Steam executable is missing, invalid, or uses an unsupported architecture\n"; return 2; + } + std::vector currentDirectory(32768); + DWORD cwdLength = GetCurrentDirectoryW(static_cast(currentDirectory.size()), currentDirectory.data()); + if (!cwdLength || cwdLength >= currentDirectory.size()) { + std::wcerr << L"Cannot resolve launch working directory\n"; return 2; + } + std::wstring workingDirectory(currentDirectory.data(), cwdLength); +#ifdef _WIN64 + if (machine == IMAGE_FILE_MACHINE_I386) { + std::wstring helper = directory + L"\\MultiSeat.SteamLauncher32.exe"; + return static_cast(Run(helper, L"\"" + helper + L"\"" + raw.substr(prefixStart), L"", workingDirectory)); + } + std::wstring hook = directory + L"\\MultiSeat.SteamHook.dll"; +#else + if (machine != IMAGE_FILE_MACHINE_I386) { + std::wcerr << L"Use the 64-bit isolation launcher for 64-bit Steam\n"; return 2; + } + std::wstring hook = directory + L"\\MultiSeat.SteamHook32.dll"; +#endif + return static_cast(Run(steam, L"\"" + steam + L"\"" + raw.substr(cursor), hook, workingDirectory, readyEvent.value)); +} diff --git a/src/MultiSeat.SteamIsolation/build.ps1 b/src/MultiSeat.SteamIsolation/build.ps1 new file mode 100644 index 0000000..4ded230 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/build.ps1 @@ -0,0 +1,30 @@ +param( + [string]$OutputDirectory = (Join-Path $PSScriptRoot 'artifacts'), + [switch]$BuildFixture +) +$ErrorActionPreference = 'Stop' +$cmakeCommand = Get-Command cmake -ErrorAction SilentlyContinue +$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' +if (-not (Test-Path -LiteralPath $vswhere)) { throw 'Visual Studio 2022 C++ build tools are required.' } +$installation = & $vswhere -latest -version '[17.0,18.0)' -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath +if (-not $installation) { throw 'Visual Studio 2022 C++ build tools are required.' } +$cmake = if ($cmakeCommand) { $cmakeCommand.Source } else { Join-Path $installation 'Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe' } +if (-not (Test-Path -LiteralPath $cmake)) { throw 'CMake is required (install the Visual Studio CMake tools component).' } +$fixtureFlag = if ($BuildFixture) { 'ON' } else { 'OFF' } +$destination = [IO.Path]::GetFullPath($OutputDirectory) +New-Item -ItemType Directory -Force -Path $destination | Out-Null +foreach ($architecture in @('x64', 'Win32')) { + $buildDirectory = Join-Path $PSScriptRoot "build-$architecture" + & $cmake -S $PSScriptRoot -B $buildDirectory -G 'Visual Studio 17 2022' -A $architecture "-DMULTISEAT_BUILD_STEAM_FIXTURE=$fixtureFlag" + if ($LASTEXITCODE -ne 0) { throw "CMake configuration failed for $architecture" } + & $cmake --build $buildDirectory --config Release --parallel + if ($LASTEXITCODE -ne 0) { throw "Build failed for $architecture" } + $suffix = if ($architecture -eq 'Win32') { '32' } else { '' } + foreach ($filename in @("MultiSeat.SteamLauncher$suffix.exe", "MultiSeat.SteamHook$suffix.dll")) { + Copy-Item -LiteralPath (Join-Path $buildDirectory "Release\$filename") -Destination $destination -Force + } + if ($BuildFixture) { + Copy-Item -LiteralPath (Join-Path $buildDirectory 'Release\SteamIsolationFixture.exe') -Destination (Join-Path $destination "SteamIsolationFixture$suffix.exe") -Force + } +} +Write-Host "Steam isolation binaries written to $destination" diff --git a/src/MultiSeat.SteamIsolation/tests/Fixture.cpp b/src/MultiSeat.SteamIsolation/tests/Fixture.cpp new file mode 100644 index 0000000..a977dfa --- /dev/null +++ b/src/MultiSeat.SteamIsolation/tests/Fixture.cpp @@ -0,0 +1,115 @@ +// Harmless stand-in for Steam: records arguments/environment and optionally holds a test mutex. +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +int wmain(int argc, wchar_t** argv) { + std::wstring report, mutexName, childReport, restartReport; + DWORD holdMs = 0; + DWORD childHoldMs = 0; + bool detachChild = false; + for (int i = 1; i < argc; ++i) { + std::wstring arg(argv[i]); + if (arg == L"--report" && i + 1 < argc) report = argv[++i]; + else if (arg == L"--child-report" && i + 1 < argc) childReport = argv[++i]; + else if (arg == L"--restart-report" && i + 1 < argc) restartReport = argv[++i]; + else if (arg == L"--mutex" && i + 1 < argc) mutexName = argv[++i]; + else if (arg == L"--hold-ms" && i + 1 < argc) holdMs = wcstoul(argv[++i], nullptr, 10); + else if (arg == L"--child-hold-ms" && i + 1 < argc) childHoldMs = wcstoul(argv[++i], nullptr, 10); + else if (arg == L"--detach-child") detachChild = true; + } + HANDLE mutex = nullptr; + DWORD mutexError = 0; + if (!mutexName.empty()) { + SetLastError(ERROR_SUCCESS); + mutex = CreateMutexW(nullptr, FALSE, mutexName.c_str()); + mutexError = GetLastError(); + if (!mutex) return 3; + } + if (!report.empty()) { + std::wofstream output{std::filesystem::path(report)}; + if (!output) { if (mutex) CloseHandle(mutex); return 4; } + output << L"command=" << GetCommandLineW() << L"\n"; + const char* narrow = GetCommandLineA(); + output << L"commandA="; + for (; *narrow; ++narrow) output << static_cast(static_cast(*narrow)); + output << L"\n"; + for (int i = 0; i < argc; ++i) output << L"arg" << i << L"=" << argv[i] << L"\n"; + DWORD session = 0; ProcessIdToSessionId(GetCurrentProcessId(), &session); + output << L"session=" << session << L"\nmutexError=" << mutexError << L"\n"; + const wchar_t* variables[] = {L"steam_master_ipc_name_override", L"LOCALAPPDATA", L"MULTISEAT_STEAM_ISOLATION"}; + for (auto variable : variables) { + DWORD needed = GetEnvironmentVariableW(variable, nullptr, 0); + std::vector value(needed ? needed : 1); + if (needed) GetEnvironmentVariableW(variable, value.data(), needed); + output << variable << L"=" << value.data() << L"\n"; + } + // A dedicated fixture-owned key avoids changing real Steam settings. + std::wstring keyPath = L"Software\\MultiSeat\\SteamIsolationTests\\" + std::to_wstring(GetCurrentProcessId()); + HKEY key = nullptr; + LONG registryStatus = RegCreateKeyExW(HKEY_CURRENT_USER, keyPath.c_str(), 0, nullptr, 0, + KEY_SET_VALUE | KEY_QUERY_VALUE, nullptr, &key, nullptr); + DWORD steamPid = 123456, readPid = 0, size = sizeof(readPid), type = 0; + DWORD nullTypePid = 0; + wchar_t stringPid[64]{}; + if (registryStatus == ERROR_SUCCESS) { + registryStatus = RegSetValueExW(key, L"SteamPID", 0, REG_DWORD, + reinterpret_cast(&steamPid), sizeof(steamPid)); + if (registryStatus == ERROR_SUCCESS) registryStatus = RegQueryValueExW(key, L"SteamPID", nullptr, + &type, reinterpret_cast(&readPid), &size); + size = sizeof(nullTypePid); + if (registryStatus == ERROR_SUCCESS) registryStatus = RegQueryValueExW(key, L"SteamPID", nullptr, + nullptr, reinterpret_cast(&nullTypePid), &size); + const wchar_t stringValue[] = L"fixture-nonDWORD"; + if (registryStatus == ERROR_SUCCESS) registryStatus = RegSetValueExW(key, L"SteamPID", 0, REG_SZ, + reinterpret_cast(stringValue), sizeof(stringValue)); + size = sizeof(stringPid); + if (registryStatus == ERROR_SUCCESS) registryStatus = RegQueryValueExW(key, L"SteamPID", nullptr, + nullptr, reinterpret_cast(stringPid), &size); + RegCloseKey(key); + RegDeleteKeyW(HKEY_CURRENT_USER, keyPath.c_str()); + } + output << L"registryStatus=" << registryStatus << L"\nsteamPid=" << readPid + << L"\nsteamPidNullType=" << nullTypePid << L"\nsteamPidString=" << stringPid << L"\n"; + } + if (!childReport.empty() || !restartReport.empty()) { + std::vector path(32768); + DWORD n = GetModuleFileNameW(nullptr, path.data(), static_cast(path.size())); + if (!n || n >= path.size()) { if (mutex) CloseHandle(mutex); return 6; } + std::wstring helper(path.data(), n); + helper = helper.substr(0, helper.find_last_of(L"\\/")) + + (restartReport.empty() ? L"\\steamwebhelper.exe" : L"\\steam.exe"); + std::wstring command; + if (!restartReport.empty()) { + if (childReport.empty()) childReport = restartReport + L".browser"; + command = L"\"" + helper + L"\" --report \"" + restartReport + L"\" --child-report \"" + childReport + + L"\" --child-hold-ms " + std::to_wstring(childHoldMs) + L" --detach-child"; + } else { + command = L"\"" + helper + L"\" --report \"" + childReport + + L"\" -cachedir=C:\\fixture\\htmlcache --log-file=C:\\fixture\\cef_log.txt --hold-ms " + std::to_wstring(childHoldMs); + } + // Explicit environment exercises propagation even when the caller doesn't inherit it. + wchar_t environment[] = L"FIXTURE_EXPLICIT=1\0\0"; + STARTUPINFOW startup{}; startup.cb = sizeof(startup); + PROCESS_INFORMATION child{}; + if (!CreateProcessW(helper.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, + environment, nullptr, &startup, &child)) { + std::wcerr << L"Fixture child creation failed: " << GetLastError() << L"\n"; + if (mutex) CloseHandle(mutex); return 7; + } + CloseHandle(child.hThread); + if (detachChild || !restartReport.empty()) { CloseHandle(child.hProcess); if (mutex) CloseHandle(mutex); return 0; } + DWORD wait = WaitForSingleObject(child.hProcess, 10000), exitCode = 0; + if (wait != WAIT_OBJECT_0) TerminateProcess(child.hProcess, 8); + else GetExitCodeProcess(child.hProcess, &exitCode); + CloseHandle(child.hProcess); + if (wait != WAIT_OBJECT_0 || exitCode) { if (mutex) CloseHandle(mutex); return 8; } + } + if (holdMs) Sleep(holdMs); + if (mutex) CloseHandle(mutex); + return mutexError == ERROR_ALREADY_EXISTS ? 5 : 0; +} diff --git a/src/MultiSeat.SteamIsolation/tests/GameLauncherSmoke.ps1 b/src/MultiSeat.SteamIsolation/tests/GameLauncherSmoke.ps1 new file mode 100644 index 0000000..e8a3556 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/tests/GameLauncherSmoke.ps1 @@ -0,0 +1,41 @@ +param( + [string]$Artifacts = (Join-Path $PSScriptRoot '..\artifacts'), + [string]$TestRoot = (Join-Path $PSScriptRoot '..\build-game-smoke') +) +$ErrorActionPreference = 'Stop' +$taskSource = [IO.Path]::GetFullPath($Artifacts) +$taskRun = Join-Path ([IO.Path]::GetFullPath($TestRoot)) ([guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $taskRun -Force | Out-Null +$taskFixture = Join-Path $taskSource 'SteamIsolationFixture.exe' +foreach ($taskName in @('MultiSeat.SteamLauncher.exe', 'MultiSeat.SteamHook.dll')) { + Copy-Item -LiteralPath (Join-Path $taskSource $taskName) -Destination $taskRun +} +Copy-Item -LiteralPath $taskFixture -Destination (Join-Path $taskRun 'steam.exe') +Copy-Item -LiteralPath $taskFixture -Destination (Join-Path $taskRun 'game.exe') +$taskLauncher = $null +try { + $taskSteamReport = Join-Path $taskRun 'steam-report.txt' + $taskSteamArgs = '--steam "' + (Join-Path $taskRun 'steam.exe') + '" -- --report "' + $taskSteamReport + '" --hold-ms 30000' + $taskLauncher = Start-Process -FilePath (Join-Path $taskRun 'MultiSeat.SteamLauncher.exe') -ArgumentList $taskSteamArgs -WorkingDirectory $taskRun -WindowStyle Hidden -PassThru + $taskDeadline = [DateTime]::UtcNow.AddSeconds(10) + while (-not (Test-Path -LiteralPath $taskSteamReport) -and [DateTime]::UtcNow -lt $taskDeadline) { Start-Sleep -Milliseconds 100 } + if (-not (Test-Path -LiteralPath $taskSteamReport)) { throw 'Isolated Steam fixture did not start.' } + $taskOriginal = $env:steam_master_ipc_name_override + & (Join-Path $PSScriptRoot '..\Launch-IsolatedGame.ps1') -Executable (Join-Path $taskRun 'game.exe') -Arguments '--report game-report.txt' + if ($env:steam_master_ipc_name_override -cne $taskOriginal) { throw 'Game launcher changed the caller environment.' } + $taskGameReport = Join-Path $taskRun 'game-report.txt' + $taskDeadline = [DateTime]::UtcNow.AddSeconds(10) + while (-not (Test-Path -LiteralPath $taskGameReport) -and [DateTime]::UtcNow -lt $taskDeadline) { Start-Sleep -Milliseconds 100 } + if (-not (Test-Path -LiteralPath $taskGameReport)) { throw 'Game did not write its report in its installation folder.' } + $taskSteamIPC = @(Get-Content -LiteralPath $taskSteamReport | Where-Object { $_ -like 'steam_master_ipc_name_override=*' }) + $taskGameIPC = @(Get-Content -LiteralPath $taskGameReport | Where-Object { $_ -like 'steam_master_ipc_name_override=*' }) + if ($taskSteamIPC.Count -ne 1 -or $taskGameIPC.Count -ne 1 -or $taskSteamIPC[0] -cne $taskGameIPC[0]) { throw 'Game and isolated Steam used different IPC names.' } + if (-not (Select-String -LiteralPath $taskGameReport -Pattern '^SteamPID=123456$' -Quiet)) { throw 'Direct game received an unexpected registry hook.' } + Write-Host 'Game launcher environment and working-directory checks passed.' +} finally { + if ($taskLauncher -and -not $taskLauncher.HasExited) { + Stop-Process -Id $taskLauncher.Id + [void]$taskLauncher.WaitForExit(5000) + } + if ($taskLauncher) { $taskLauncher.Dispose() } +} diff --git a/src/MultiSeat.SteamIsolation/tests/LauncherSmoke.ps1 b/src/MultiSeat.SteamIsolation/tests/LauncherSmoke.ps1 new file mode 100644 index 0000000..5890d3f --- /dev/null +++ b/src/MultiSeat.SteamIsolation/tests/LauncherSmoke.ps1 @@ -0,0 +1,74 @@ +param( + [string]$Artifacts = (Join-Path $PSScriptRoot '..\artifacts'), + [string]$TestRoot = (Join-Path $PSScriptRoot '..\build-smoke') +) +$ErrorActionPreference = 'Stop' +$source = [IO.Path]::GetFullPath($Artifacts) +$runRoot = Join-Path ([IO.Path]::GetFullPath($TestRoot)) ([guid]::NewGuid().ToString('N')) +function Assert-True($condition, [string]$message) { if (-not $condition) { throw $message } } +function Invoke-Fixture([string]$directory, [string]$tail, [bool]$expectReady, [int]$expectExit) { + $name = 'Global\MultiSeatSteamReady-' + [guid]::NewGuid().ToString('N') + $ready = [Threading.EventWaitHandle]::new($false, [Threading.EventResetMode]::ManualReset, $name) + $launcher = $null + try { + $args = '--steam "' + (Join-Path $directory 'steam.exe') + '" --ready-event "' + $name + '" -- ' + $tail + $launcher = Start-Process -FilePath (Join-Path $directory 'MultiSeat.SteamLauncher.exe') -ArgumentList $args ` + -WorkingDirectory $directory -WindowStyle Hidden -RedirectStandardError (Join-Path $directory 'stderr.txt') -PassThru + $signaled = $ready.WaitOne(5000) + Assert-True ($signaled -eq $expectReady) "Readiness was $signaled, expected $expectReady in $directory" + Assert-True ($launcher.WaitForExit(5000)) "Fixture process tree did not finish in $directory" + Assert-True ($launcher.ExitCode -eq $expectExit) "Unexpected launcher exit $($launcher.ExitCode) in $directory" + } finally { + if ($launcher -and -not $launcher.HasExited) { Stop-Process -Id $launcher.Id } + $ready.Dispose() + } +} +foreach ($suffix in @('', '32')) { + $directory = Join-Path $runRoot $(if ($suffix) { 'x86' } else { 'x64' }) + New-Item -ItemType Directory -Force -Path $directory | Out-Null + foreach ($name in @('MultiSeat.SteamLauncher.exe', 'MultiSeat.SteamLauncher32.exe', "MultiSeat.SteamHook$suffix.dll")) { + Copy-Item -LiteralPath (Join-Path $source $name) -Destination $directory + } + # Only the compiled harmless fixture is named steam.exe; installed Steam is never used. + Copy-Item -LiteralPath (Join-Path $source "SteamIsolationFixture$suffix.exe") -Destination (Join-Path $directory 'steam.exe') + Copy-Item -LiteralPath (Join-Path $source "SteamIsolationFixture$suffix.exe") -Destination (Join-Path $directory 'steamwebhelper.exe') + Invoke-Fixture $directory '--report ready.txt --hold-ms 100 "two words" "quote\"inside"' $true 0 + $report = Get-Content -LiteralPath (Join-Path $directory 'ready.txt') -Raw + Assert-True ($report -match '(?m)^steamPid=0\r?$') 'SteamPID DWORD was not isolated' + Assert-True ($report -match '(?m)^steamPidNullType=0\r?$') 'SteamPID DWORD with null type was not isolated' + Assert-True ($report -match '(?m)^steamPidString=fixture-nonDWORD\r?$') 'SteamPID string was corrupted' + Assert-True ($report -match '(?m)^arg5=two words\r?$') 'Quoted argument was not preserved' + Invoke-Fixture $directory '--report first.txt --restart-report restarted.txt --child-report browser.txt --child-hold-ms 200' $true 0 + $replacement = Get-Content -LiteralPath (Join-Path $directory 'restarted.txt') -Raw + $browser = Get-Content -LiteralPath (Join-Path $directory 'browser.txt') -Raw + Assert-True ($replacement -match '(?m)^steamPid=0\r?$') 'Replacement Steam was not hooked' + Assert-True ($browser -match 'htmlcache\.MultiSeat_') 'Replacement browser cache was not isolated' + $supervisor = $null + try { + $args = '--steam "' + (Join-Path $directory 'steam.exe') + '" -- --restart-report live-restarted.txt --child-report live-browser.txt --child-hold-ms 30000' + $supervisor = Start-Process -FilePath (Join-Path $directory 'MultiSeat.SteamLauncher.exe') -ArgumentList $args ` + -WorkingDirectory $directory -WindowStyle Hidden -PassThru + $childInfo = $null + for ($attempt = 0; $attempt -lt 30; $attempt++) { + $childInfo = Get-CimInstance Win32_Process -Filter "Name='steamwebhelper.exe'" | + Where-Object ExecutablePath -eq (Join-Path $directory 'steamwebhelper.exe') + if ($childInfo -and (Test-Path -LiteralPath (Join-Path $directory 'live-browser.txt'))) { break } + Start-Sleep -Milliseconds 100 + } + Assert-True ([bool]$childInfo) 'Replacement browser fixture did not start' + $childProcess = Get-Process -Id $childInfo.ProcessId + Start-Sleep -Milliseconds 300 + Assert-True (-not $supervisor.HasExited) 'Supervisor stopped while replacement descendants were running' + Stop-Process -Id $supervisor.Id + Assert-True ($childProcess.WaitForExit(3000)) 'Supervisor termination left a browser descendant running' + } finally { + if ($supervisor -and -not $supervisor.HasExited) { Stop-Process -Id $supervisor.Id } + } +} +$failureDirectory = Join-Path $runRoot 'missing-hook' +New-Item -ItemType Directory -Force -Path $failureDirectory | Out-Null +Copy-Item -LiteralPath (Join-Path $source 'MultiSeat.SteamLauncher.exe') -Destination $failureDirectory +Copy-Item -LiteralPath (Join-Path $source 'SteamIsolationFixture.exe') -Destination (Join-Path $failureDirectory 'steam.exe') +Invoke-Fixture $failureDirectory '--report unexpected.txt' $false 1 +Assert-True (-not (Test-Path -LiteralPath (Join-Path $failureDirectory 'unexpected.txt'))) 'Unisolated child ran after a hook failure' +Write-Host "Native launcher smoke tests passed. Fixture reports: $runRoot" diff --git a/src/MultiSeat.SteamIsolation/tests/PolicyTests.cpp b/src/MultiSeat.SteamIsolation/tests/PolicyTests.cpp new file mode 100644 index 0000000..c04f5c2 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/tests/PolicyTests.cpp @@ -0,0 +1,47 @@ +#include "../IsolationPolicy.h" +#include +#include +using namespace steam_isolation; +static void Expect(bool result, const char* message) { if (!result) throw std::runtime_error(message); } +static std::vector Args(const std::wstring& command) { + int count = 0; LPWSTR* parsed = CommandLineToArgvW(command.c_str(), &count); + if (!parsed) throw std::runtime_error("Cannot parse command"); + std::vector result(parsed, parsed + count); LocalFree(parsed); return result; +} +int main() { + try { + const std::wstring key = L"MultiSeat_17_123"; + auto input = L"\"C:\\Program Files (x86)\\Steam\\steam.exe\" -bigpicture \"a\\\"b\" \"trailing\\\\\""; + auto before = Args(input), after = Args(RewriteCommand(input, key)); + Expect(after.size() == before.size() + 2, "IPC option missing"); + for (size_t i = 0; i < before.size(); ++i) Expect(before[i] == after[i], "Argument semantics changed"); + Expect(after.back() == key, "Wrong namespace"); + after = Args(RewriteCommand(L"steam.exe -master_ipc_name_override wrong -bigpicture", key)); + Expect(after.size() == 4 && after[1] == L"-bigpicture" && after[3] == key, "IPC duplicate not replaced"); + after = Args(RewriteCommand(L"steam.exe -master_ipc_name_override=wrong", key)); + Expect(after.size() == 3 && after[2] == key, "Inline IPC duplicate not replaced"); + const std::wstring browser = L"steamwebhelper.exe -cachedir=\"C:\\cache folder\\htmlcache\" --log-file=C:\\logs\\cef_log.txt --other=x"; + auto changed = RewriteCommand(browser, key); after = Args(changed); + Expect(after[1] == L"-cachedir=C:\\cache folder\\htmlcache." + key, "Browser cache not separated"); + Expect(after[2] == L"--log-file=C:\\logs\\cef_log." + key + L".txt", "Browser log not separated"); + Expect(RewriteCommand(changed, key) == changed, "Browser suffix added twice"); + Expect(RewriteCommand(L"mySteam.exe -cachedir=C:\\htmlcache", key) == L"mySteam.exe -cachedir=C:\\htmlcache", "Unrelated program changed"); + Expect(RewriteCommand(L"steamwebhelper.exe -cachedir=C:\\myhtmlcache", key) == L"steamwebhelper.exe -cachedir=C:\\myhtmlcache", "Substring changed"); + after = Args(RewriteCommand(L"steam.exe https://example.test/htmlcache --label=cef_log", key)); + Expect(after[1] == L"https://example.test/htmlcache" && after[2] == L"--label=cef_log", "URL or label changed"); + const wchar_t environment[] = L"=C:=C:\\work\0HOME=C:\\profile\0StEaM_MaStEr_IpC_NaMe_OvErRiDe=old\0TEST=ok\0"; + auto block = RewriteEnvironment(environment, sizeof(environment) / sizeof(wchar_t), key); + Expect(!block.empty() && block.back() == 0 && block[block.size() - 2] == 0, "Environment terminator missing"); + int found = 0; bool drive = false, test = false; + for (const wchar_t* p = block.data(); *p; p += wcslen(p) + 1) { + std::wstring value(p); + if (value == L"steam_master_ipc_name_override=" + key) ++found; + if (value == L"=C:=C:\\work") drive = true; + if (value == L"TEST=ok") test = true; + Expect(value.find(L"=old") == std::wstring::npos, "Old IPC retained"); + } + Expect(found == 1 && drive && test, "Environment unrelated entries lost"); + Expect(RewriteEnvironment(L"broken", 6, key).empty(), "Malformed block accepted"); + std::cout << "Steam command-line and environment policy tests passed\n"; return 0; + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } +} diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/.editorconfig b/src/MultiSeat.SteamIsolation/third_party/minhook/.editorconfig new file mode 100644 index 0000000..735e983 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/.editorconfig @@ -0,0 +1,26 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Windows-style newlines with a newline ending every file +[*] +end_of_line = crlf +insert_final_newline = true + +# 4 space indentation +[*.{c,h,def}] +indent_style = space +indent_size = 4 + +# Trim trailing whitespaces +[*.{c,h,def,txt}] +trim_trailing_whitespace = true + +# UTF-8 with BOM +[*.{c,h,def,txt}] +charset=utf-8-bom + +# C/C++ code formatting +[*.{c,h}] +cpp_space_pointer_reference_alignment = right diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/.github/workflows/msbuild.yml b/src/MultiSeat.SteamIsolation/third_party/minhook/.github/workflows/msbuild.yml new file mode 100644 index 0000000..5575ff2 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/.github/workflows/msbuild.yml @@ -0,0 +1,59 @@ +name: Build + +on: + workflow_dispatch: + push: + branches: [ "master" ] + +env: + # Path to the solution file relative to the root of the project. + SOLUTION_FILE_PATH: build/VC17/MinHookVC17.sln + + # Configuration type to build. + # You can convert this to a build matrix if you need coverage of multiple configuration types. + # https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + BUILD_CONFIGURATION: Release + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2 + + - name: Build Win32 + # Add additional options to the MSBuild command line here (like platform or verbosity level). + # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference + run: msbuild /m /p:Configuration=${{ env.BUILD_CONFIGURATION }} /p:Platform="Win32" ${{ env.SOLUTION_FILE_PATH }} + + - name: Build x64 + # Add additional options to the MSBuild command line here (like platform or verbosity level). + # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference + run: msbuild /m /p:Configuration=${{ env.BUILD_CONFIGURATION }} /p:Platform="x64" ${{ env.SOLUTION_FILE_PATH }} + + - name: Collect artifacts + shell: bash + run: | + mkdir "${{ runner.temp }}/artifacts" + mv "build/VC17/bin/${{ env.BUILD_CONFIGURATION }}" "${{ runner.temp }}/artifacts/bin" + mv "build/VC17/lib/${{ env.BUILD_CONFIGURATION }}" "${{ runner.temp }}/artifacts/lib" + cp -r include "${{ runner.temp }}/artifacts/include" + + - name: Package bin + uses: actions/upload-artifact@v4 + with: + name: MinHook_bin + path: | + ${{ runner.temp }}/artifacts/bin/ + ${{ runner.temp }}/artifacts/include/ + + - name: Package lib + uses: actions/upload-artifact@v4 + with: + name: MinHook_lib + path: | + ${{ runner.temp }}/artifacts/lib/ + ${{ runner.temp }}/artifacts/include/ diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/.gitignore b/src/MultiSeat.SteamIsolation/third_party/minhook/.gitignore new file mode 100644 index 0000000..ad165d5 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/.gitignore @@ -0,0 +1,44 @@ +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.sbr +*.sdf +*.opensdf +*.unsuccessfulbuild +ipch/ +obj/ +[Ll]ib +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad +*.VC.db +.vs/ + +#GCC files +*.o +*.d +*.res +*.dll +*.a + +#Visual Studio Code files +.vscode/ diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/AUTHORS.txt b/src/MultiSeat.SteamIsolation/third_party/minhook/AUTHORS.txt new file mode 100644 index 0000000..ebef1a6 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/AUTHORS.txt @@ -0,0 +1,8 @@ +Tsuda Kageyu + Creator, maintainer + +Michael Maltsev + Added "Queue" functions. A lot of bug fixes. + +Andrey Unis + Rewrote the hook engine in plain C. diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/CMakeLists.txt b/src/MultiSeat.SteamIsolation/third_party/minhook/CMakeLists.txt new file mode 100644 index 0000000..845e3c7 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/CMakeLists.txt @@ -0,0 +1,141 @@ +# MinHook - The Minimalistic API Hooking Library for x64/x86 +# Copyright (C) 2009-2017 Tsuda Kageyu. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +cmake_minimum_required(VERSION 3.0...3.5) + +project(minhook LANGUAGES C) + +include(CMakePackageConfigHelpers) + +set(MINHOOK_MAJOR_VERSION 1) +set(MINHOOK_MINOR_VERSION 3) +set(MINHOOK_PATCH_VERSION 3) +set(MINHOOK_VERSION ${MINHOOK_MAJOR_VERSION}.${MINHOOK_MINOR_VERSION}.${MINHOOK_PATCH_VERSION}) + +################ +# BUILD # +################ + +option(BUILD_SHARED_LIBS "build shared version" OFF) + +set(SOURCES_MINHOOK + "src/buffer.c" + "src/hook.c" + "src/trampoline.c" +) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(SOURCES_HDE "src/hde/hde64.c") +else() + set(SOURCES_HDE "src/hde/hde32.c") +endif() + +if(BUILD_SHARED_LIBS) + set(RESOURCES + "dll_resources/MinHook.rc" + "dll_resources/MinHook.def" + ) +endif() + +add_library(minhook ${SOURCES_MINHOOK} ${SOURCES_HDE} ${RESOURCES}) + +target_include_directories(minhook PUBLIC + $ + $ +) + +target_include_directories(minhook PRIVATE "src/") +target_include_directories(minhook PRIVATE "src/hde/") + +if(WIN32) + set_target_properties(minhook PROPERTIES PREFIX "") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set_target_properties(minhook PROPERTIES DEBUG_POSTFIX ".x64d") + set_target_properties(minhook PROPERTIES RELEASE_POSTFIX ".x64") + set_target_properties(minhook PROPERTIES RELWITHDEBINFO_POSTFIX ".x64") + set_target_properties(minhook PROPERTIES MINSIZEREL_POSTFIX ".x64") + else() + set_target_properties(minhook PROPERTIES DEBUG_POSTFIX ".x32d") + set_target_properties(minhook PROPERTIES RELEASE_POSTFIX ".x32") + set_target_properties(minhook PROPERTIES RELWITHDEBINFO_POSTFIX ".x32") + set_target_properties(minhook PROPERTIES MINSIZEREL_POSTFIX ".x32") + endif() +else() + set_target_properties(minhook PROPERTIES PREFIX "lib") + set_target_properties(minhook PROPERTIES POSTFIX "") + set_target_properties(minhook PROPERTIES DEBUG_POSTFIX "d") +endif() + +################ +# CMAKE CONFIG # +################ + +configure_package_config_file( + "cmake/minhook-config.cmake.in" + "minhook-config.cmake" + INSTALL_DESTINATION + "lib/minhook" +) + +write_basic_package_version_file( + "minhook-config-version.cmake" +VERSION + ${MINHOOK_VERSION} +COMPATIBILITY + AnyNewerVersion +) + +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/minhook-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/minhook-config-version.cmake" + DESTINATION + "lib/minhook" +) + +################### +# INSTALL # +################### + +install(TARGETS minhook + EXPORT minhook-targets + RUNTIME DESTINATION "bin" + ARCHIVE DESTINATION "lib" + LIBRARY DESTINATION "lib" +) + +install( + EXPORT + minhook-targets + NAMESPACE + minhook:: + DESTINATION + "lib/minhook" +) + +install( + DIRECTORY include DESTINATION . +) diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/LICENSE.txt b/src/MultiSeat.SteamIsolation/third_party/minhook/LICENSE.txt new file mode 100644 index 0000000..74dea27 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/LICENSE.txt @@ -0,0 +1,81 @@ +MinHook - The Minimalistic API Hooking Library for x64/x86 +Copyright (C) 2009-2017 Tsuda Kageyu. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +================================================================================ +Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov. +================================================================================ +Hacker Disassembler Engine 32 C +Copyright (c) 2008-2009, Vyacheslav Patkov. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +Hacker Disassembler Engine 64 C +Copyright (c) 2008-2009, Vyacheslav Patkov. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/README.md b/src/MultiSeat.SteamIsolation/third_party/minhook/README.md new file mode 100644 index 0000000..b77edb5 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/README.md @@ -0,0 +1,94 @@ +# MinHook + +[![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause) + +The Minimalistic x86/x64 API Hooking Library for Windows + +http://www.codeproject.com/KB/winsdk/LibMinHook.aspx + +### Version history + +- **v1.3.4 - 28 Mar 2025** + * Improved error handling for enumerating and suspending threads. + * Visual Studio VS2022 support. + * CMake support. + * Fixed compilation with Clang. + * Fixed compilation as C++ code. + +- **v1.3.3 - 8 Jan 2017** + * Added a helper function ```MH_CreateHookApiEx```. (Thanks to asm256) + * Support Visual Studio 2017 RC. + +- **v1.3.2.1 - 9 Nov 2015** (Nuget package only) + * Fixed an insufficient support for Visual Studio 2015. + +- **v1.3.2 - 1 Nov 2015** + * Support Visual Studio 2015. + * Support MinGW. + +- **v1.3.2-beta3 - 21 Jul 2015** (Nuget package only) + * Support MinGW. (Experimental) + +- **v1.3.2-beta2 - 18 May 2015** + * Fixed some subtle bugs. (Thanks to RaMMicHaeL) + * Added a helper function ```MH_StatusToString```. (Thanks to Jan Klass) + +- **v1.3.2-beta - 12 May 2015** + * Fixed a possible thread deadlock in x64 mode. (Thanks to Aleh Kazakevich) + * Reduced the footprint a little more. + * Support Visual Studio 2015 RC. (Experimental) + +- **v1.3.1.1 - 7 Apr 2015** (Nuget package only) + * Support for WDK8.0 and 8.1. + +- **v1.3.1 - 19 Mar 2015** + * No major changes from v1.3.1-beta. + +- **v1.3.1-beta - 11 Mar 2015** + * Added a helper function ```MH_CreateHookApi```. (Thanks to uniskz). + * Fixed a false memory leak reported by some tools. + * Fixed a degradated compatibility issue. + +- **v1.3 - 13 Sep 2014** + * No major changes from v1.3-beta3. + +- **v1.3-beta3 - 31 Jul 2014** + * Fixed some small bugs. + * Improved the memory management. + +- **v1.3-beta2 - 21 Jul 2014** + * Changed the parameters to Windows-friendly types. (void* to LPVOID) + * Fixed some small bugs. + * Reorganized the source files. + * Reduced the footprint a little more. + +- **v1.3-beta - 17 Jul 2014** + * Rewrote in plain C to reduce the footprint and memory usage. (suggested by Andrey Unis) + * Simplified the overall code base to make it more readable and maintainable. + * Changed the license from 3-clause to 2-clause BSD License. + +- **v1.2 - 28 Sep 2013** + * Removed boost dependency ([jarredholman](https://github.com/jarredholman/minhook)). + * Fixed a small bug in the GetRelativeBranchDestination function ([pillbug99](http://www.codeproject.com/Messages/4058892/Small-Bug-Found.aspx)). + * Added the ```MH_RemoveHook``` function, which removes a hook created with the ```MH_CreateHook``` function. + * Added the following functions to enable or disable multiple hooks in one go: ```MH_QueueEnableHook```, ```MH_QueueDisableHook```, ```MH_ApplyQueued```. This is the preferred way of handling multiple hooks as every call to `MH_EnableHook` or `MH_DisableHook` suspends and resumes all threads. + * Made the functions ```MH_EnableHook``` and ```MH_DisableHook``` enable/disable all created hooks when the ```MH_ALL_HOOKS``` parameter is passed. This, too, is an efficient way of handling multiple hooks. + * If the target function is too small to be patched with a jump, MinHook tries to place the jump above the function. If that fails as well, the ```MH_CreateHook``` function returns ```MH_ERROR_UNSUPPORTED_FUNCTION```. This fixes an issue of hooking the LoadLibraryExW function on Windows 7 x64 ([reported by Obble](http://www.codeproject.com/Messages/4578613/Re-Bug-LoadLibraryExW-hook-fails-on-windows-2008-r.aspx)). + +- **v1.1 - 26 Nov 2009** + * Changed the interface to create a hook and a trampoline function in one go to prevent the detour function from being called before the trampoline function is created. ([reported by xliqz](http://www.codeproject.com/Messages/3280374/Unsafe.aspx)) + * Shortened the function names from ```MinHook_*``` to ```MH_*``` to make them handier. + +- **v1.0 - 22 Nov 2009** + * Initial release. + +### Building MinHook - Using vcpkg + +You can download and install MinHook using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + + git clone https://github.com/microsoft/vcpkg + .\vcpkg\bootstrap-vcpkg.bat + .\vcpkg\vcpkg integrate install + .\vcpkg\vcpkg install minhook + +The MinHook port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/Makefile b/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/Makefile new file mode 100644 index 0000000..9784745 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/Makefile @@ -0,0 +1,33 @@ +WINDRES:=$(CROSS_PREFIX)windres +DLLTOOL:=$(CROSS_PREFIX)dlltool +AR:=$(CROSS_PREFIX)ar +CC:=$(CROSS_PREFIX)gcc +CCLD:=$(CC) +SRCS:=$(wildcard src/*.c src/hde/*.c) +OBJS:=$(SRCS:%.c=%.o) +DEPS:=$(SRCS:%.c=%.d) +INCS:=-Isrc -Iinclude +CFLAGS:=-masm=intel -Wall -Werror -std=c11 +LDFLAGS:=-Wl,-enable-stdcall-fixup -s -static-libgcc + +all: MinHook.dll libMinHook.dll.a libMinHook.a + +-include $(DEPS) + +libMinHook.a: $(OBJS) + $(AR) r $@ $^ +libMinHook.dll.a: MinHook.dll dll_resources/MinHook.def + $(DLLTOOL) --dllname MinHook.dll --input-def dll_resources/MinHook.def --output-lib $@ +MinHook.dll: $(OBJS) dll_resources/MinHook.res dll_resources/MinHook.def + $(CCLD) -o $@ -shared $(LDFLAGS) $^ + +.rc.res: + $(WINDRES) -o $@ --input-format=rc --output-format=coff $< +.c.o: + $(CC) -o $@ -c -MMD -MP $(INCS) $(CFLAGS) $< + +clean: + rm -f $(OBJS) $(DEPS) MinHook.dll libMinHook.dll.a libMinHook.a dll_resources/MinHook.res + +.PHONY: clean +.SUFFIXES: .rc .res diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/make.bat b/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/make.bat new file mode 100644 index 0000000..7671878 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/make.bat @@ -0,0 +1 @@ +windres -i ../../dll_resources/MinHook.rc -o MinHook_rc.o && dllwrap --driver-name g++ -o MinHook.dll -masm=intel --def ../../dll_resources/MinHook.def -Wl,-enable-stdcall-fixup -Wall MinHook_rc.o ../../src/*.c ../../src/HDE/*.c -I../../include -I../../src -Werror -std=c++11 -s -static-libgcc -static-libstdc++|| pause \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/make.sh b/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/make.sh new file mode 100644 index 0000000..17eddbc --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/MinGW/make.sh @@ -0,0 +1,2 @@ +x86_64-w64-mingw32-windres -i ../../dll_resources/MinHook.rc -o MinHook_rc.o && +x86_64-w64-mingw32-dllwrap -o MinHook.dll -masm=intel --def ../../dll_resources/MinHook.def -Wl,-enable-stdcall-fixup -Wall MinHook_rc.o ../../src/*.c ../../src/hde/*.c -I../../include -I../../src -Werror -s -static-libgcc -static-libstdc++ diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/MinHook.vcxproj new file mode 100644 index 0000000..3944d80 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/MinHook.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + + + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + + + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + + + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + + + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/MinHookVC10.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/MinHookVC10.sln new file mode 100644 index 0000000..dcc1d5c --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/MinHookVC10.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/libMinHook.vcxproj new file mode 100644 index 0000000..589ff9a --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/libMinHook.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + + + false + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + + + false + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + + + true + AnySuitable + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + + + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC10/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/MinHook.vcxproj new file mode 100644 index 0000000..4c0e212 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/MinHook.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + + + + DynamicLibrary + Unicode + true + v110_xp + + + DynamicLibrary + Unicode + v110_xp + + + DynamicLibrary + Unicode + true + v110_xp + + + DynamicLibrary + Unicode + v110_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/MinHookVC11.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/MinHookVC11.sln new file mode 100644 index 0000000..5b56553 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/MinHookVC11.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/libMinHook.vcxproj new file mode 100644 index 0000000..1a65499 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/libMinHook.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + + + + StaticLibrary + Unicode + true + v110_xp + + + StaticLibrary + Unicode + v110_xp + + + StaticLibrary + Unicode + true + v110_xp + + + StaticLibrary + Unicode + v110_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NoExtensions + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + NoExtensions + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC11/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/MinHook.vcxproj new file mode 100644 index 0000000..40ec836 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/MinHook.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + + + + DynamicLibrary + Unicode + true + v120_xp + + + DynamicLibrary + Unicode + v120_xp + + + DynamicLibrary + Unicode + true + v120_xp + + + DynamicLibrary + Unicode + v120_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/MinHookVC12.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/MinHookVC12.sln new file mode 100644 index 0000000..cfd928b --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/MinHookVC12.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/libMinHook.vcxproj new file mode 100644 index 0000000..7e7fe8e --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/libMinHook.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + + + + StaticLibrary + Unicode + true + v120_xp + + + StaticLibrary + Unicode + v120_xp + + + StaticLibrary + Unicode + true + v120_xp + + + StaticLibrary + Unicode + v120_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NoExtensions + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NotSet + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + AnySuitable + true + NoExtensions + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC12/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/MinHook.vcxproj new file mode 100644 index 0000000..d5ecda2 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/MinHook.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + + + + DynamicLibrary + Unicode + true + v140_xp + + + DynamicLibrary + Unicode + v140_xp + + + DynamicLibrary + Unicode + true + v140_xp + + + DynamicLibrary + Unicode + v140_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/MinHookVC14.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/MinHookVC14.sln new file mode 100644 index 0000000..258c192 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/MinHookVC14.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.22823.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/libMinHook.vcxproj new file mode 100644 index 0000000..a429e69 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/libMinHook.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + + + + StaticLibrary + Unicode + true + v140_xp + + + StaticLibrary + Unicode + v140_xp + + + StaticLibrary + Unicode + true + v140_xp + + + StaticLibrary + Unicode + v140_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NoExtensions + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NotSet + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + AnySuitable + true + NoExtensions + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC14/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/MinHook.vcxproj new file mode 100644 index 0000000..1d51833 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/MinHook.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + + + + DynamicLibrary + Unicode + true + v141_xp + + + DynamicLibrary + Unicode + v141_xp + + + DynamicLibrary + Unicode + true + v141_xp + + + DynamicLibrary + Unicode + v141_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/MinHookVC15.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/MinHookVC15.sln new file mode 100644 index 0000000..946dc70 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/MinHookVC15.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/libMinHook.vcxproj new file mode 100644 index 0000000..829ac34 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/libMinHook.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + + + + StaticLibrary + Unicode + true + v141_xp + + + StaticLibrary + Unicode + v141_xp + + + StaticLibrary + Unicode + true + v141_xp + + + StaticLibrary + Unicode + v141_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NoExtensions + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NotSet + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + AnySuitable + true + NoExtensions + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC15/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/MinHook.vcxproj new file mode 100644 index 0000000..23ddafd --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/MinHook.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + + + + DynamicLibrary + Unicode + true + v142 + + + DynamicLibrary + Unicode + v142 + + + DynamicLibrary + Unicode + true + v142 + + + DynamicLibrary + Unicode + v142 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/MinHookVC16.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/MinHookVC16.sln new file mode 100644 index 0000000..191b75c --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/MinHookVC16.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/libMinHook.vcxproj new file mode 100644 index 0000000..605871b --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/libMinHook.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + + + + StaticLibrary + Unicode + true + v142 + + + StaticLibrary + Unicode + v142 + + + StaticLibrary + Unicode + true + v142 + + + StaticLibrary + Unicode + v142 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NoExtensions + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NotSet + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + AnySuitable + true + NoExtensions + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC16/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/MinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/MinHook.vcxproj new file mode 100644 index 0000000..1a7aae5 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/MinHook.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {027FAC75-3FDB-4044-8DD0-BC297BD4C461} + MinHook + Win32Proj + 10.0 + + + + DynamicLibrary + Unicode + true + v143 + + + DynamicLibrary + Unicode + v143 + + + DynamicLibrary + Unicode + true + v143 + + + DynamicLibrary + Unicode + v143 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + true + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(SolutionDir)bin\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + false + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + + + Level3 + None + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + + + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX86 + $(SolutionDir)lib\$(Configuration)\libMinHook.x86.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + X64 + + + MinSpace + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;MINHOOK_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + None + false + + + $(SolutionDir)..\..\dll_resources\MinHook.def + false + Windows + true + true + MachineX64 + $(SolutionDir)lib\$(Configuration)\libMinHook.x64.lib;%(AdditionalDependencies) + true + .CRT=.text + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/MinHookVC17.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/MinHookVC17.sln new file mode 100644 index 0000000..3c40ce3 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/MinHookVC17.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcxproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcxproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/libMinHook.vcxproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/libMinHook.vcxproj new file mode 100644 index 0000000..21197df --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/libMinHook.vcxproj @@ -0,0 +1,174 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + libMinHook + Win32Proj + 10.0 + + + + StaticLibrary + Unicode + true + v143 + + + StaticLibrary + Unicode + v143 + + + StaticLibrary + Unicode + true + v143 + + + StaticLibrary + Unicode + v143 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.40219.1 + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(SolutionDir)lib\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(ProjectName).x86 + $(ProjectName).x86 + $(ProjectName).x64 + $(ProjectName).x64 + + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NoExtensions + + + + + + X64 + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebug + Level3 + None + NotSet + + + + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + AnySuitable + true + NoExtensions + + + + + + X64 + + + MinSpace + true + %(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;STRICT;%(PreprocessorDefinitions) + false + MultiThreaded + true + Level3 + None + true + AnySuitable + + + + + + + true + true + + + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/libMinHook.vcxproj.filters b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/libMinHook.vcxproj.filters new file mode 100644 index 0000000..f2d1d97 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC17/libMinHook.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + HDE + + + HDE + + + + + Header Files + + + Header Files + + + + HDE + + + HDE + + + HDE + + + HDE + + + HDE + + + + + {9d24b740-be2e-4cfd-b9a4-340a50946ee9} + + + {76381bc7-2863-4cc5-aede-926ec2c506e4} + + + {56ddb326-6179-430d-ae19-e13bfd767bfa} + + + \ No newline at end of file diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/MinHook.vcproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/MinHook.vcproj new file mode 100644 index 0000000..4bad257 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/MinHook.vcproj @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/MinHookVC9.sln b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/MinHookVC9.sln new file mode 100644 index 0000000..869f5b6 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/MinHookVC9.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libMinHook", "libMinHook.vcproj", "{F142A341-5EE0-442D-A15F-98AE9B48DBAE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MinHook", "MinHook.vcproj", "{027FAC75-3FDB-4044-8DD0-BC297BD4C461}" + ProjectSection(ProjectDependencies) = postProject + {F142A341-5EE0-442D-A15F-98AE9B48DBAE} = {F142A341-5EE0-442D-A15F-98AE9B48DBAE} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.ActiveCfg = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|Win32.Build.0 = Debug|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.ActiveCfg = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Debug|x64.Build.0 = Debug|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.ActiveCfg = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|Win32.Build.0 = Release|Win32 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.ActiveCfg = Release|x64 + {F142A341-5EE0-442D-A15F-98AE9B48DBAE}.Release|x64.Build.0 = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.ActiveCfg = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|Win32.Build.0 = Debug|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.ActiveCfg = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Debug|x64.Build.0 = Debug|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.ActiveCfg = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|Win32.Build.0 = Release|Win32 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.ActiveCfg = Release|x64 + {027FAC75-3FDB-4044-8DD0-BC297BD4C461}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/libMinHook.vcproj b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/libMinHook.vcproj new file mode 100644 index 0000000..68b0c05 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/build/VC9/libMinHook.vcproj @@ -0,0 +1,410 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/cmake/minhook-config.cmake.in b/src/MultiSeat.SteamIsolation/third_party/minhook/cmake/minhook-config.cmake.in new file mode 100644 index 0000000..14e6463 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/cmake/minhook-config.cmake.in @@ -0,0 +1,39 @@ +# MinHook - The Minimalistic API Hooking Library for x64/x86 +# Copyright (C) 2009-2017 Tsuda Kageyu. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(MINHOOK_MAJOR_VERSION "@MINHOOK_MAJOR_VERSION@") +set(MINHOOK_MINOR_VERSION "@MINHOOK_MINOR_VERSION@") +set(MINHOOK_PATCH_VERSION "@MINHOOK_PATCH_VERSION@") +set(MINHOOK_VERSION "@MINHOOK_VERSION@") + +@PACKAGE_INIT@ + +set(MINHOOK_FOUND ON) + +set_and_check(MINHOOK_INCLUDE_DIRS "${PACKAGE_PREFIX_DIR}/include/") +set_and_check(MINHOOK_LIBRARY_DIRS "${PACKAGE_PREFIX_DIR}/lib") + +include("${PACKAGE_PREFIX_DIR}/lib/minhook/minhook-targets.cmake") diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/dll_resources/MinHook.def b/src/MultiSeat.SteamIsolation/third_party/minhook/dll_resources/MinHook.def new file mode 100644 index 0000000..c6af698 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/dll_resources/MinHook.def @@ -0,0 +1,14 @@ +EXPORTS + MH_Initialize + MH_Uninitialize + + MH_CreateHook + MH_CreateHookApi + MH_CreateHookApiEx + MH_RemoveHook + MH_EnableHook + MH_DisableHook + MH_QueueEnableHook + MH_QueueDisableHook + MH_ApplyQueued + MH_StatusToString diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/dll_resources/MinHook.rc b/src/MultiSeat.SteamIsolation/third_party/minhook/dll_resources/MinHook.rc new file mode 100644 index 0000000..d59db98 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/dll_resources/MinHook.rc @@ -0,0 +1,32 @@ +1 VERSIONINFO + FILEVERSION 1,3,4,0 + PRODUCTVERSION 1,3,4,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Tsuda Kageyu" + VALUE "FileDescription", "MinHook - The Minimalistic API Hook Library for x64/x86" + VALUE "FileVersion", "1.3.4.0" + VALUE "InternalName", "MinHookD" + VALUE "LegalCopyright", "Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved." + VALUE "LegalTrademarks", "Tsuda Kageyu" + VALUE "ProductName", "MinHook DLL" + VALUE "ProductVersion", "1.3.4.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/include/MinHook.h b/src/MultiSeat.SteamIsolation/third_party/minhook/include/MinHook.h new file mode 100644 index 0000000..492d83f --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/include/MinHook.h @@ -0,0 +1,185 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) + #error MinHook supports only x86 and x64 systems. +#endif + +#include + +// MinHook Error Codes. +typedef enum MH_STATUS +{ + // Unknown error. Should not be returned. + MH_UNKNOWN = -1, + + // Successful. + MH_OK = 0, + + // MinHook is already initialized. + MH_ERROR_ALREADY_INITIALIZED, + + // MinHook is not initialized yet, or already uninitialized. + MH_ERROR_NOT_INITIALIZED, + + // The hook for the specified target function is already created. + MH_ERROR_ALREADY_CREATED, + + // The hook for the specified target function is not created yet. + MH_ERROR_NOT_CREATED, + + // The hook for the specified target function is already enabled. + MH_ERROR_ENABLED, + + // The hook for the specified target function is not enabled yet, or already + // disabled. + MH_ERROR_DISABLED, + + // The specified pointer is invalid. It points the address of non-allocated + // and/or non-executable region. + MH_ERROR_NOT_EXECUTABLE, + + // The specified target function cannot be hooked. + MH_ERROR_UNSUPPORTED_FUNCTION, + + // Failed to allocate memory. + MH_ERROR_MEMORY_ALLOC, + + // Failed to change the memory protection. + MH_ERROR_MEMORY_PROTECT, + + // The specified module is not loaded. + MH_ERROR_MODULE_NOT_FOUND, + + // The specified function is not found. + MH_ERROR_FUNCTION_NOT_FOUND +} +MH_STATUS; + +// Can be passed as a parameter to MH_EnableHook, MH_DisableHook, +// MH_QueueEnableHook or MH_QueueDisableHook. +#define MH_ALL_HOOKS NULL + +#ifdef __cplusplus +extern "C" { +#endif + + // Initialize the MinHook library. You must call this function EXACTLY ONCE + // at the beginning of your program. + MH_STATUS WINAPI MH_Initialize(VOID); + + // Uninitialize the MinHook library. You must call this function EXACTLY + // ONCE at the end of your program. + MH_STATUS WINAPI MH_Uninitialize(VOID); + + // Creates a hook for the specified target function, in disabled state. + // Parameters: + // pTarget [in] A pointer to the target function, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszProcName [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszProcName [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + // ppTarget [out] A pointer to the target function, which will be used + // with other functions. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); + + // Removes an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); + + // Enables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // enabled in one go. + MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); + + // Disables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // disabled in one go. + MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); + + // Queues to enable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be enabled. + MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); + + // Queues to disable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be disabled. + MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); + + // Applies all queued changes in one go. + MH_STATUS WINAPI MH_ApplyQueued(VOID); + + // Translates the MH_STATUS to its name as a string. + const char * WINAPI MH_StatusToString(MH_STATUS status); + +#ifdef __cplusplus +} +#endif diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/buffer.c b/src/MultiSeat.SteamIsolation/third_party/minhook/src/buffer.c new file mode 100644 index 0000000..55412b0 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/buffer.c @@ -0,0 +1,312 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "buffer.h" + +// Size of each memory block. (= page size of VirtualAlloc) +#define MEMORY_BLOCK_SIZE 0x1000 + +// Max range for seeking a memory block. (= 1024MB) +#define MAX_MEMORY_RANGE 0x40000000 + +// Memory protection flags to check the executable address. +#define PAGE_EXECUTE_FLAGS \ + (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) + +// Memory slot. +typedef struct _MEMORY_SLOT +{ + union + { + struct _MEMORY_SLOT *pNext; + UINT8 buffer[MEMORY_SLOT_SIZE]; + }; +} MEMORY_SLOT, *PMEMORY_SLOT; + +// Memory block info. Placed at the head of each block. +typedef struct _MEMORY_BLOCK +{ + struct _MEMORY_BLOCK *pNext; + PMEMORY_SLOT pFree; // First element of the free slot list. + UINT usedCount; +} MEMORY_BLOCK, *PMEMORY_BLOCK; + +//------------------------------------------------------------------------- +// Global Variables: +//------------------------------------------------------------------------- + +// First element of the memory block list. +PMEMORY_BLOCK g_pMemoryBlocks; + +//------------------------------------------------------------------------- +VOID InitializeBuffer(VOID) +{ + // Nothing to do for now. +} + +//------------------------------------------------------------------------- +VOID UninitializeBuffer(VOID) +{ + PMEMORY_BLOCK pBlock = g_pMemoryBlocks; + g_pMemoryBlocks = NULL; + + while (pBlock) + { + PMEMORY_BLOCK pNext = pBlock->pNext; + VirtualFree(pBlock, 0, MEM_RELEASE); + pBlock = pNext; + } +} + +//------------------------------------------------------------------------- +#if defined(_M_X64) || defined(__x86_64__) +static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity) +{ + ULONG_PTR tryAddr = (ULONG_PTR)pAddress; + + // Round down to the allocation granularity. + tryAddr -= tryAddr % dwAllocationGranularity; + + // Start from the previous allocation granularity multiply. + tryAddr -= dwAllocationGranularity; + + while (tryAddr >= (ULONG_PTR)pMinAddr) + { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) + break; + + if (mbi.State == MEM_FREE) + return (LPVOID)tryAddr; + + if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity) + break; + + tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity; + } + + return NULL; +} +#endif + +//------------------------------------------------------------------------- +#if defined(_M_X64) || defined(__x86_64__) +static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity) +{ + ULONG_PTR tryAddr = (ULONG_PTR)pAddress; + + // Round down to the allocation granularity. + tryAddr -= tryAddr % dwAllocationGranularity; + + // Start from the next allocation granularity multiply. + tryAddr += dwAllocationGranularity; + + while (tryAddr <= (ULONG_PTR)pMaxAddr) + { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) + break; + + if (mbi.State == MEM_FREE) + return (LPVOID)tryAddr; + + tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize; + + // Round up to the next allocation granularity. + tryAddr += dwAllocationGranularity - 1; + tryAddr -= tryAddr % dwAllocationGranularity; + } + + return NULL; +} +#endif + +//------------------------------------------------------------------------- +static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin) +{ + PMEMORY_BLOCK pBlock; +#if defined(_M_X64) || defined(__x86_64__) + ULONG_PTR minAddr; + ULONG_PTR maxAddr; + + SYSTEM_INFO si; + GetSystemInfo(&si); + minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress; + maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress; + + // pOrigin ± 512MB + if ((ULONG_PTR)pOrigin > MAX_MEMORY_RANGE && minAddr < (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE) + minAddr = (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE; + + if (maxAddr > (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE) + maxAddr = (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE; + + // Make room for MEMORY_BLOCK_SIZE bytes. + maxAddr -= MEMORY_BLOCK_SIZE - 1; +#endif + + // Look the registered blocks for a reachable one. + for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext) + { +#if defined(_M_X64) || defined(__x86_64__) + // Ignore the blocks too far. + if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr) + continue; +#endif + // The block has at least one unused slot. + if (pBlock->pFree != NULL) + return pBlock; + } + +#if defined(_M_X64) || defined(__x86_64__) + // Alloc a new block above if not found. + { + LPVOID pAlloc = pOrigin; + while ((ULONG_PTR)pAlloc >= minAddr) + { + pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity); + if (pAlloc == NULL) + break; + + pBlock = (PMEMORY_BLOCK)VirtualAlloc( + pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (pBlock != NULL) + break; + } + } + + // Alloc a new block below if not found. + if (pBlock == NULL) + { + LPVOID pAlloc = pOrigin; + while ((ULONG_PTR)pAlloc <= maxAddr) + { + pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity); + if (pAlloc == NULL) + break; + + pBlock = (PMEMORY_BLOCK)VirtualAlloc( + pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (pBlock != NULL) + break; + } + } +#else + // In x86 mode, a memory block can be placed anywhere. + pBlock = (PMEMORY_BLOCK)VirtualAlloc( + NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); +#endif + + if (pBlock != NULL) + { + // Build a linked list of all the slots. + PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1; + pBlock->pFree = NULL; + pBlock->usedCount = 0; + do + { + pSlot->pNext = pBlock->pFree; + pBlock->pFree = pSlot; + pSlot++; + } while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE); + + pBlock->pNext = g_pMemoryBlocks; + g_pMemoryBlocks = pBlock; + } + + return pBlock; +} + +//------------------------------------------------------------------------- +LPVOID AllocateBuffer(LPVOID pOrigin) +{ + PMEMORY_SLOT pSlot; + PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin); + if (pBlock == NULL) + return NULL; + + // Remove an unused slot from the list. + pSlot = pBlock->pFree; + pBlock->pFree = pSlot->pNext; + pBlock->usedCount++; +#ifdef _DEBUG + // Fill the slot with INT3 for debugging. + memset(pSlot, 0xCC, sizeof(MEMORY_SLOT)); +#endif + return pSlot; +} + +//------------------------------------------------------------------------- +VOID FreeBuffer(LPVOID pBuffer) +{ + PMEMORY_BLOCK pBlock = g_pMemoryBlocks; + PMEMORY_BLOCK pPrev = NULL; + ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / MEMORY_BLOCK_SIZE) * MEMORY_BLOCK_SIZE; + + while (pBlock != NULL) + { + if ((ULONG_PTR)pBlock == pTargetBlock) + { + PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer; +#ifdef _DEBUG + // Clear the released slot for debugging. + memset(pSlot, 0x00, sizeof(MEMORY_SLOT)); +#endif + // Restore the released slot to the list. + pSlot->pNext = pBlock->pFree; + pBlock->pFree = pSlot; + pBlock->usedCount--; + + // Free if unused. + if (pBlock->usedCount == 0) + { + if (pPrev) + pPrev->pNext = pBlock->pNext; + else + g_pMemoryBlocks = pBlock->pNext; + + VirtualFree(pBlock, 0, MEM_RELEASE); + } + + break; + } + + pPrev = pBlock; + pBlock = pBlock->pNext; + } +} + +//------------------------------------------------------------------------- +BOOL IsExecutableAddress(LPVOID pAddress) +{ + MEMORY_BASIC_INFORMATION mi; + VirtualQuery(pAddress, &mi, sizeof(mi)); + + return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE_FLAGS)); +} diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/buffer.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/buffer.h new file mode 100644 index 0000000..204d551 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/buffer.h @@ -0,0 +1,42 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +// Size of each memory slot. +#if defined(_M_X64) || defined(__x86_64__) + #define MEMORY_SLOT_SIZE 64 +#else + #define MEMORY_SLOT_SIZE 32 +#endif + +VOID InitializeBuffer(VOID); +VOID UninitializeBuffer(VOID); +LPVOID AllocateBuffer(LPVOID pOrigin); +VOID FreeBuffer(LPVOID pBuffer); +BOOL IsExecutableAddress(LPVOID pAddress); diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde32.c b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde32.c new file mode 100644 index 0000000..eb6af9b --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde32.c @@ -0,0 +1,324 @@ +/* + * Hacker Disassembler Engine 32 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#if defined(_M_IX86) || defined(__i386__) + +#include +#include "hde32.h" +#include "table32.h" + +unsigned int hde32_disasm(const void *code, hde32s *hs) +{ + uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; + uint8_t *ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0; + + memset(hs, 0, sizeof(hde32s)); + + for (x = 16; x; x--) + switch (c = *p++) { + case 0xf3: + hs->p_rep = c; + pref |= PRE_F3; + break; + case 0xf2: + hs->p_rep = c; + pref |= PRE_F2; + break; + case 0xf0: + hs->p_lock = c; + pref |= PRE_LOCK; + break; + case 0x26: case 0x2e: case 0x36: + case 0x3e: case 0x64: case 0x65: + hs->p_seg = c; + pref |= PRE_SEG; + break; + case 0x66: + hs->p_66 = c; + pref |= PRE_66; + break; + case 0x67: + hs->p_67 = c; + pref |= PRE_67; + break; + default: + goto pref_done; + } + pref_done: + + hs->flags = (uint32_t)pref << 23; + + if (!pref) + pref |= PRE_NONE; + + if ((hs->opcode = c) == 0x0f) { + hs->opcode2 = c = *p++; + ht += DELTA_OPCODES; + } else if (c >= 0xa0 && c <= 0xa3) { + if (pref & PRE_67) + pref |= PRE_66; + else + pref &= ~PRE_66; + } + + opcode = c; + cflags = ht[ht[opcode / 4] + (opcode % 4)]; + + if (cflags == C_ERROR) { + hs->flags |= F_ERROR | F_ERROR_OPCODE; + cflags = 0; + if ((opcode & -3) == 0x24) + cflags++; + } + + x = 0; + if (cflags & C_GROUP) { + uint16_t t; + t = *(uint16_t *)(ht + (cflags & 0x7f)); + cflags = (uint8_t)t; + x = (uint8_t)(t >> 8); + } + + if (hs->opcode2) { + ht = hde32_table + DELTA_PREFIXES; + if (ht[ht[opcode / 4] + (opcode % 4)] & pref) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (cflags & C_MODRM) { + hs->flags |= F_MODRM; + hs->modrm = c = *p++; + hs->modrm_mod = m_mod = c >> 6; + hs->modrm_rm = m_rm = c & 7; + hs->modrm_reg = m_reg = (c & 0x3f) >> 3; + + if (x && ((x << m_reg) & 0x80)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + + if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { + uint8_t t = opcode - 0xd9; + if (m_mod == 3) { + ht = hde32_table + DELTA_FPU_MODRM + t*8; + t = ht[m_reg] << m_rm; + } else { + ht = hde32_table + DELTA_FPU_REG; + t = ht[t] << m_reg; + } + if (t & 0x80) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (pref & PRE_LOCK) { + if (m_mod == 3) { + hs->flags |= F_ERROR | F_ERROR_LOCK; + } else { + uint8_t *table_end, op = opcode; + if (hs->opcode2) { + ht = hde32_table + DELTA_OP2_LOCK_OK; + table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; + } else { + ht = hde32_table + DELTA_OP_LOCK_OK; + table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; + op &= -2; + } + for (; ht != table_end; ht++) + if (*ht++ == op) { + if (!((*ht << m_reg) & 0x80)) + goto no_lock_error; + else + break; + } + hs->flags |= F_ERROR | F_ERROR_LOCK; + no_lock_error: + ; + } + } + + if (hs->opcode2) { + switch (opcode) { + case 0x20: case 0x22: + m_mod = 3; + if (m_reg > 4 || m_reg == 1) + goto error_operand; + else + goto no_error_operand; + case 0x21: case 0x23: + m_mod = 3; + if (m_reg == 4 || m_reg == 5) + goto error_operand; + else + goto no_error_operand; + } + } else { + switch (opcode) { + case 0x8c: + if (m_reg > 5) + goto error_operand; + else + goto no_error_operand; + case 0x8e: + if (m_reg == 1 || m_reg > 5) + goto error_operand; + else + goto no_error_operand; + } + } + + if (m_mod == 3) { + uint8_t *table_end; + if (hs->opcode2) { + ht = hde32_table + DELTA_OP2_ONLY_MEM; + table_end = ht + sizeof(hde32_table) - DELTA_OP2_ONLY_MEM; + } else { + ht = hde32_table + DELTA_OP_ONLY_MEM; + table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; + } + for (; ht != table_end; ht += 2) + if (*ht++ == opcode) { + if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) + goto error_operand; + else + break; + } + goto no_error_operand; + } else if (hs->opcode2) { + switch (opcode) { + case 0x50: case 0xd7: case 0xf7: + if (pref & (PRE_NONE | PRE_66)) + goto error_operand; + break; + case 0xd6: + if (pref & (PRE_F2 | PRE_F3)) + goto error_operand; + break; + case 0xc5: + goto error_operand; + } + goto no_error_operand; + } else + goto no_error_operand; + + error_operand: + hs->flags |= F_ERROR | F_ERROR_OPERAND; + no_error_operand: + + c = *p++; + if (m_reg <= 1) { + if (opcode == 0xf6) + cflags |= C_IMM8; + else if (opcode == 0xf7) + cflags |= C_IMM_P66; + } + + switch (m_mod) { + case 0: + if (pref & PRE_67) { + if (m_rm == 6) + disp_size = 2; + } else + if (m_rm == 5) + disp_size = 4; + break; + case 1: + disp_size = 1; + break; + case 2: + disp_size = 2; + if (!(pref & PRE_67)) + disp_size <<= 1; + break; + } + + if (m_mod != 3 && m_rm == 4 && !(pref & PRE_67)) { + hs->flags |= F_SIB; + p++; + hs->sib = c; + hs->sib_scale = c >> 6; + hs->sib_index = (c & 0x3f) >> 3; + if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) + disp_size = 4; + } + + p--; + switch (disp_size) { + case 1: + hs->flags |= F_DISP8; + hs->disp.disp8 = *p; + break; + case 2: + hs->flags |= F_DISP16; + hs->disp.disp16 = *(uint16_t *)p; + break; + case 4: + hs->flags |= F_DISP32; + hs->disp.disp32 = *(uint32_t *)p; + break; + } + p += disp_size; + } else if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + + if (cflags & C_IMM_P66) { + if (cflags & C_REL32) { + if (pref & PRE_66) { + hs->flags |= F_IMM16 | F_RELATIVE; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + goto disasm_done; + } + goto rel32_ok; + } + if (pref & PRE_66) { + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + } else { + hs->flags |= F_IMM32; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } + } + + if (cflags & C_IMM16) { + if (hs->flags & F_IMM32) { + hs->flags |= F_IMM16; + hs->disp.disp16 = *(uint16_t *)p; + } else if (hs->flags & F_IMM16) { + hs->flags |= F_2IMM16; + hs->disp.disp16 = *(uint16_t *)p; + } else { + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t *)p; + } + p += 2; + } + if (cflags & C_IMM8) { + hs->flags |= F_IMM8; + hs->imm.imm8 = *p++; + } + + if (cflags & C_REL32) { + rel32_ok: + hs->flags |= F_IMM32 | F_RELATIVE; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } else if (cflags & C_REL8) { + hs->flags |= F_IMM8 | F_RELATIVE; + hs->imm.imm8 = *p++; + } + + disasm_done: + + if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) { + hs->flags |= F_ERROR | F_ERROR_LENGTH; + hs->len = 15; + } + + return (unsigned int)hs->len; +} + +#endif // defined(_M_IX86) || defined(__i386__) diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde32.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde32.h new file mode 100644 index 0000000..1112450 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde32.h @@ -0,0 +1,105 @@ +/* + * Hacker Disassembler Engine 32 + * Copyright (c) 2006-2009, Vyacheslav Patkov. + * All rights reserved. + * + * hde32.h: C/C++ header file + * + */ + +#ifndef _HDE32_H_ +#define _HDE32_H_ + +/* stdint.h - C99 standard header + * http://en.wikipedia.org/wiki/stdint.h + * + * if your compiler doesn't contain "stdint.h" header (for + * example, Microsoft Visual C++), you can download file: + * http://www.azillionmonkeys.com/qed/pstdint.h + * and change next line to: + * #include "pstdint.h" + */ +#include "pstdint.h" + +#define F_MODRM 0x00000001 +#define F_SIB 0x00000002 +#define F_IMM8 0x00000004 +#define F_IMM16 0x00000008 +#define F_IMM32 0x00000010 +#define F_DISP8 0x00000020 +#define F_DISP16 0x00000040 +#define F_DISP32 0x00000080 +#define F_RELATIVE 0x00000100 +#define F_2IMM16 0x00000800 +#define F_ERROR 0x00001000 +#define F_ERROR_OPCODE 0x00002000 +#define F_ERROR_LENGTH 0x00004000 +#define F_ERROR_LOCK 0x00008000 +#define F_ERROR_OPERAND 0x00010000 +#define F_PREFIX_REPNZ 0x01000000 +#define F_PREFIX_REPX 0x02000000 +#define F_PREFIX_REP 0x03000000 +#define F_PREFIX_66 0x04000000 +#define F_PREFIX_67 0x08000000 +#define F_PREFIX_LOCK 0x10000000 +#define F_PREFIX_SEG 0x20000000 +#define F_PREFIX_ANY 0x3f000000 + +#define PREFIX_SEGMENT_CS 0x2e +#define PREFIX_SEGMENT_SS 0x36 +#define PREFIX_SEGMENT_DS 0x3e +#define PREFIX_SEGMENT_ES 0x26 +#define PREFIX_SEGMENT_FS 0x64 +#define PREFIX_SEGMENT_GS 0x65 +#define PREFIX_LOCK 0xf0 +#define PREFIX_REPNZ 0xf2 +#define PREFIX_REPX 0xf3 +#define PREFIX_OPERAND_SIZE 0x66 +#define PREFIX_ADDRESS_SIZE 0x67 + +#pragma pack(push,1) + +typedef struct { + uint8_t len; + uint8_t p_rep; + uint8_t p_lock; + uint8_t p_seg; + uint8_t p_66; + uint8_t p_67; + uint8_t opcode; + uint8_t opcode2; + uint8_t modrm; + uint8_t modrm_mod; + uint8_t modrm_reg; + uint8_t modrm_rm; + uint8_t sib; + uint8_t sib_scale; + uint8_t sib_index; + uint8_t sib_base; + union { + uint8_t imm8; + uint16_t imm16; + uint32_t imm32; + } imm; + union { + uint8_t disp8; + uint16_t disp16; + uint32_t disp32; + } disp; + uint32_t flags; +} hde32s; + +#pragma pack(pop) + +#ifdef __cplusplus +extern "C" { +#endif + +/* __cdecl */ +unsigned int hde32_disasm(const void *code, hde32s *hs); + +#ifdef __cplusplus +} +#endif + +#endif /* _HDE32_H_ */ diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde64.c b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde64.c new file mode 100644 index 0000000..ce773b1 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde64.c @@ -0,0 +1,335 @@ +/* + * Hacker Disassembler Engine 64 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#if defined(_M_X64) || defined(__x86_64__) + +#include +#include "hde64.h" +#include "table64.h" + +unsigned int hde64_disasm(const void *code, hde64s *hs) +{ + uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; + uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; + uint8_t op64 = 0; + + memset(hs, 0, sizeof(hde64s)); + + for (x = 16; x; x--) + switch (c = *p++) { + case 0xf3: + hs->p_rep = c; + pref |= PRE_F3; + break; + case 0xf2: + hs->p_rep = c; + pref |= PRE_F2; + break; + case 0xf0: + hs->p_lock = c; + pref |= PRE_LOCK; + break; + case 0x26: case 0x2e: case 0x36: + case 0x3e: case 0x64: case 0x65: + hs->p_seg = c; + pref |= PRE_SEG; + break; + case 0x66: + hs->p_66 = c; + pref |= PRE_66; + break; + case 0x67: + hs->p_67 = c; + pref |= PRE_67; + break; + default: + goto pref_done; + } + pref_done: + + hs->flags = (uint32_t)pref << 23; + + if (!pref) + pref |= PRE_NONE; + + if ((c & 0xf0) == 0x40) { + hs->flags |= F_PREFIX_REX; + if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8) + op64++; + hs->rex_r = (c & 7) >> 2; + hs->rex_x = (c & 3) >> 1; + hs->rex_b = c & 1; + if (((c = *p++) & 0xf0) == 0x40) { + opcode = c; + goto error_opcode; + } + } + + if ((hs->opcode = c) == 0x0f) { + hs->opcode2 = c = *p++; + ht += DELTA_OPCODES; + } else if (c >= 0xa0 && c <= 0xa3) { + op64++; + if (pref & PRE_67) + pref |= PRE_66; + else + pref &= ~PRE_66; + } + + opcode = c; + cflags = ht[ht[opcode / 4] + (opcode % 4)]; + + if (cflags == C_ERROR) { + error_opcode: + hs->flags |= F_ERROR | F_ERROR_OPCODE; + cflags = 0; + if ((opcode & -3) == 0x24) + cflags++; + } + + x = 0; + if (cflags & C_GROUP) { + uint16_t t; + t = *(uint16_t *)(ht + (cflags & 0x7f)); + cflags = (uint8_t)t; + x = (uint8_t)(t >> 8); + } + + if (hs->opcode2) { + ht = hde64_table + DELTA_PREFIXES; + if (ht[ht[opcode / 4] + (opcode % 4)] & pref) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (cflags & C_MODRM) { + hs->flags |= F_MODRM; + hs->modrm = c = *p++; + hs->modrm_mod = m_mod = c >> 6; + hs->modrm_rm = m_rm = c & 7; + hs->modrm_reg = m_reg = (c & 0x3f) >> 3; + + if (x && ((x << m_reg) & 0x80)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + + if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { + uint8_t t = opcode - 0xd9; + if (m_mod == 3) { + ht = hde64_table + DELTA_FPU_MODRM + t*8; + t = ht[m_reg] << m_rm; + } else { + ht = hde64_table + DELTA_FPU_REG; + t = ht[t] << m_reg; + } + if (t & 0x80) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (pref & PRE_LOCK) { + if (m_mod == 3) { + hs->flags |= F_ERROR | F_ERROR_LOCK; + } else { + uint8_t *table_end, op = opcode; + if (hs->opcode2) { + ht = hde64_table + DELTA_OP2_LOCK_OK; + table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; + } else { + ht = hde64_table + DELTA_OP_LOCK_OK; + table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; + op &= -2; + } + for (; ht != table_end; ht++) + if (*ht++ == op) { + if (!((*ht << m_reg) & 0x80)) + goto no_lock_error; + else + break; + } + hs->flags |= F_ERROR | F_ERROR_LOCK; + no_lock_error: + ; + } + } + + if (hs->opcode2) { + switch (opcode) { + case 0x20: case 0x22: + m_mod = 3; + if (m_reg > 4 || m_reg == 1) + goto error_operand; + else + goto no_error_operand; + case 0x21: case 0x23: + m_mod = 3; + if (m_reg == 4 || m_reg == 5) + goto error_operand; + else + goto no_error_operand; + } + } else { + switch (opcode) { + case 0x8c: + if (m_reg > 5) + goto error_operand; + else + goto no_error_operand; + case 0x8e: + if (m_reg == 1 || m_reg > 5) + goto error_operand; + else + goto no_error_operand; + } + } + + if (m_mod == 3) { + uint8_t *table_end; + if (hs->opcode2) { + ht = hde64_table + DELTA_OP2_ONLY_MEM; + table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; + } else { + ht = hde64_table + DELTA_OP_ONLY_MEM; + table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; + } + for (; ht != table_end; ht += 2) + if (*ht++ == opcode) { + if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) + goto error_operand; + else + break; + } + goto no_error_operand; + } else if (hs->opcode2) { + switch (opcode) { + case 0x50: case 0xd7: case 0xf7: + if (pref & (PRE_NONE | PRE_66)) + goto error_operand; + break; + case 0xd6: + if (pref & (PRE_F2 | PRE_F3)) + goto error_operand; + break; + case 0xc5: + goto error_operand; + } + goto no_error_operand; + } else + goto no_error_operand; + + error_operand: + hs->flags |= F_ERROR | F_ERROR_OPERAND; + no_error_operand: + + c = *p++; + if (m_reg <= 1) { + if (opcode == 0xf6) + cflags |= C_IMM8; + else if (opcode == 0xf7) + cflags |= C_IMM_P66; + } + + switch (m_mod) { + case 0: + if (pref & PRE_67) { + if (m_rm == 6) + disp_size = 2; + } else + if (m_rm == 5) + disp_size = 4; + break; + case 1: + disp_size = 1; + break; + case 2: + disp_size = 2; + if (!(pref & PRE_67)) + disp_size <<= 1; + break; + } + + if (m_mod != 3 && m_rm == 4) { + hs->flags |= F_SIB; + p++; + hs->sib = c; + hs->sib_scale = c >> 6; + hs->sib_index = (c & 0x3f) >> 3; + if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) + disp_size = 4; + } + + p--; + switch (disp_size) { + case 1: + hs->flags |= F_DISP8; + hs->disp.disp8 = *p; + break; + case 2: + hs->flags |= F_DISP16; + hs->disp.disp16 = *(uint16_t *)p; + break; + case 4: + hs->flags |= F_DISP32; + hs->disp.disp32 = *(uint32_t *)p; + break; + } + p += disp_size; + } else if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + + if (cflags & C_IMM_P66) { + if (cflags & C_REL32) { + if (pref & PRE_66) { + hs->flags |= F_IMM16 | F_RELATIVE; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + goto disasm_done; + } + goto rel32_ok; + } + if (op64) { + hs->flags |= F_IMM64; + hs->imm.imm64 = *(uint64_t *)p; + p += 8; + } else if (!(pref & PRE_66)) { + hs->flags |= F_IMM32; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } else + goto imm16_ok; + } + + + if (cflags & C_IMM16) { + imm16_ok: + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + } + if (cflags & C_IMM8) { + hs->flags |= F_IMM8; + hs->imm.imm8 = *p++; + } + + if (cflags & C_REL32) { + rel32_ok: + hs->flags |= F_IMM32 | F_RELATIVE; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } else if (cflags & C_REL8) { + hs->flags |= F_IMM8 | F_RELATIVE; + hs->imm.imm8 = *p++; + } + + disasm_done: + + if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) { + hs->flags |= F_ERROR | F_ERROR_LENGTH; + hs->len = 15; + } + + return (unsigned int)hs->len; +} + +#endif // defined(_M_X64) || defined(__x86_64__) diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde64.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde64.h new file mode 100644 index 0000000..ecbf4df --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/hde64.h @@ -0,0 +1,112 @@ +/* + * Hacker Disassembler Engine 64 + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + * hde64.h: C/C++ header file + * + */ + +#ifndef _HDE64_H_ +#define _HDE64_H_ + +/* stdint.h - C99 standard header + * http://en.wikipedia.org/wiki/stdint.h + * + * if your compiler doesn't contain "stdint.h" header (for + * example, Microsoft Visual C++), you can download file: + * http://www.azillionmonkeys.com/qed/pstdint.h + * and change next line to: + * #include "pstdint.h" + */ +#include "pstdint.h" + +#define F_MODRM 0x00000001 +#define F_SIB 0x00000002 +#define F_IMM8 0x00000004 +#define F_IMM16 0x00000008 +#define F_IMM32 0x00000010 +#define F_IMM64 0x00000020 +#define F_DISP8 0x00000040 +#define F_DISP16 0x00000080 +#define F_DISP32 0x00000100 +#define F_RELATIVE 0x00000200 +#define F_ERROR 0x00001000 +#define F_ERROR_OPCODE 0x00002000 +#define F_ERROR_LENGTH 0x00004000 +#define F_ERROR_LOCK 0x00008000 +#define F_ERROR_OPERAND 0x00010000 +#define F_PREFIX_REPNZ 0x01000000 +#define F_PREFIX_REPX 0x02000000 +#define F_PREFIX_REP 0x03000000 +#define F_PREFIX_66 0x04000000 +#define F_PREFIX_67 0x08000000 +#define F_PREFIX_LOCK 0x10000000 +#define F_PREFIX_SEG 0x20000000 +#define F_PREFIX_REX 0x40000000 +#define F_PREFIX_ANY 0x7f000000 + +#define PREFIX_SEGMENT_CS 0x2e +#define PREFIX_SEGMENT_SS 0x36 +#define PREFIX_SEGMENT_DS 0x3e +#define PREFIX_SEGMENT_ES 0x26 +#define PREFIX_SEGMENT_FS 0x64 +#define PREFIX_SEGMENT_GS 0x65 +#define PREFIX_LOCK 0xf0 +#define PREFIX_REPNZ 0xf2 +#define PREFIX_REPX 0xf3 +#define PREFIX_OPERAND_SIZE 0x66 +#define PREFIX_ADDRESS_SIZE 0x67 + +#pragma pack(push,1) + +typedef struct { + uint8_t len; + uint8_t p_rep; + uint8_t p_lock; + uint8_t p_seg; + uint8_t p_66; + uint8_t p_67; + uint8_t rex; + uint8_t rex_w; + uint8_t rex_r; + uint8_t rex_x; + uint8_t rex_b; + uint8_t opcode; + uint8_t opcode2; + uint8_t modrm; + uint8_t modrm_mod; + uint8_t modrm_reg; + uint8_t modrm_rm; + uint8_t sib; + uint8_t sib_scale; + uint8_t sib_index; + uint8_t sib_base; + union { + uint8_t imm8; + uint16_t imm16; + uint32_t imm32; + uint64_t imm64; + } imm; + union { + uint8_t disp8; + uint16_t disp16; + uint32_t disp32; + } disp; + uint32_t flags; +} hde64s; + +#pragma pack(pop) + +#ifdef __cplusplus +extern "C" { +#endif + +/* __cdecl */ +unsigned int hde64_disasm(const void *code, hde64s *hs); + +#ifdef __cplusplus +} +#endif + +#endif /* _HDE64_H_ */ diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/pstdint.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/pstdint.h new file mode 100644 index 0000000..84d82a0 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/pstdint.h @@ -0,0 +1,39 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +// Integer types for HDE. +typedef INT8 int8_t; +typedef INT16 int16_t; +typedef INT32 int32_t; +typedef INT64 int64_t; +typedef UINT8 uint8_t; +typedef UINT16 uint16_t; +typedef UINT32 uint32_t; +typedef UINT64 uint64_t; diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/table32.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/table32.h new file mode 100644 index 0000000..7b3e12e --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/table32.h @@ -0,0 +1,73 @@ +/* + * Hacker Disassembler Engine 32 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#define C_NONE 0x00 +#define C_MODRM 0x01 +#define C_IMM8 0x02 +#define C_IMM16 0x04 +#define C_IMM_P66 0x10 +#define C_REL8 0x20 +#define C_REL32 0x40 +#define C_GROUP 0x80 +#define C_ERROR 0xff + +#define PRE_ANY 0x00 +#define PRE_NONE 0x01 +#define PRE_F2 0x02 +#define PRE_F3 0x04 +#define PRE_66 0x08 +#define PRE_67 0x10 +#define PRE_LOCK 0x20 +#define PRE_SEG 0x40 +#define PRE_ALL 0xff + +#define DELTA_OPCODES 0x4a +#define DELTA_FPU_REG 0xf1 +#define DELTA_FPU_MODRM 0xf8 +#define DELTA_PREFIXES 0x130 +#define DELTA_OP_LOCK_OK 0x1a1 +#define DELTA_OP2_LOCK_OK 0x1b9 +#define DELTA_OP_ONLY_MEM 0x1cb +#define DELTA_OP2_ONLY_MEM 0x1da + +unsigned char hde32_table[] = { + 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3, + 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f, + 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3, + 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa, + 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90, + 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f, + 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d, + 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59, + 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, + 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0, + 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01, + 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11, + 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8, + 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca, + 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff, + 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03, + 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00, + 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f, + 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a, + 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a, + 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a, + 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06, + 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06, + 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, + 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08, + 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01, + 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba, + 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00, + 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00, + 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07, + 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf, + 0xe7,0x08,0x00,0xf0,0x02,0x00 +}; diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/table64.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/table64.h new file mode 100644 index 0000000..01d4541 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hde/table64.h @@ -0,0 +1,74 @@ +/* + * Hacker Disassembler Engine 64 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#define C_NONE 0x00 +#define C_MODRM 0x01 +#define C_IMM8 0x02 +#define C_IMM16 0x04 +#define C_IMM_P66 0x10 +#define C_REL8 0x20 +#define C_REL32 0x40 +#define C_GROUP 0x80 +#define C_ERROR 0xff + +#define PRE_ANY 0x00 +#define PRE_NONE 0x01 +#define PRE_F2 0x02 +#define PRE_F3 0x04 +#define PRE_66 0x08 +#define PRE_67 0x10 +#define PRE_LOCK 0x20 +#define PRE_SEG 0x40 +#define PRE_ALL 0xff + +#define DELTA_OPCODES 0x4a +#define DELTA_FPU_REG 0xfd +#define DELTA_FPU_MODRM 0x104 +#define DELTA_PREFIXES 0x13c +#define DELTA_OP_LOCK_OK 0x1ae +#define DELTA_OP2_LOCK_OK 0x1c6 +#define DELTA_OP_ONLY_MEM 0x1d8 +#define DELTA_OP2_ONLY_MEM 0x1e7 + +unsigned char hde64_table[] = { + 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, + 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, + 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, + 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, + 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, + 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, + 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, + 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, + 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, + 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, + 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, + 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, + 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, + 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, + 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, + 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, + 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, + 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, + 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, + 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, + 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, + 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, + 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, + 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, + 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, + 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, + 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, + 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, + 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, + 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, + 0x00,0xf0,0x02,0x00 +}; diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/hook.c b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hook.c new file mode 100644 index 0000000..68a006a --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/hook.c @@ -0,0 +1,939 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "../include/MinHook.h" +#include "buffer.h" +#include "trampoline.h" + +#ifndef ARRAYSIZE + #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +// Initial capacity of the HOOK_ENTRY buffer. +#define INITIAL_HOOK_CAPACITY 32 + +// Initial capacity of the thread IDs buffer. +#define INITIAL_THREAD_CAPACITY 128 + +// Special hook position values. +#define INVALID_HOOK_POS UINT_MAX +#define ALL_HOOKS_POS UINT_MAX + +// Freeze() action argument defines. +#define ACTION_DISABLE 0 +#define ACTION_ENABLE 1 +#define ACTION_APPLY_QUEUED 2 + +// Thread access rights for suspending/resuming threads. +#define THREAD_ACCESS \ + (THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT) + +// Hook information. +typedef struct _HOOK_ENTRY +{ + LPVOID pTarget; // Address of the target function. + LPVOID pDetour; // Address of the detour or relay function. + LPVOID pTrampoline; // Address of the trampoline function. + UINT8 backup[8]; // Original prologue of the target function. + + UINT8 patchAbove : 1; // Uses the hot patch area. + UINT8 isEnabled : 1; // Enabled. + UINT8 queueEnable : 1; // Queued for enabling/disabling when != isEnabled. + + UINT nIP : 4; // Count of the instruction boundaries. + UINT8 oldIPs[8]; // Instruction boundaries of the target function. + UINT8 newIPs[8]; // Instruction boundaries of the trampoline function. +} HOOK_ENTRY, *PHOOK_ENTRY; + +// Suspended threads for Freeze()/Unfreeze(). +typedef struct _FROZEN_THREADS +{ + LPDWORD pItems; // Data heap + UINT capacity; // Size of allocated data heap, items + UINT size; // Actual number of data items +} FROZEN_THREADS, *PFROZEN_THREADS; + +//------------------------------------------------------------------------- +// Global Variables: +//------------------------------------------------------------------------- + +// Spin lock flag for EnterSpinLock()/LeaveSpinLock(). +volatile LONG g_isLocked = FALSE; + +// Private heap handle. If not NULL, this library is initialized. +HANDLE g_hHeap = NULL; + +// Hook entries. +struct +{ + PHOOK_ENTRY pItems; // Data heap + UINT capacity; // Size of allocated data heap, items + UINT size; // Actual number of data items +} g_hooks; + +//------------------------------------------------------------------------- +// Returns INVALID_HOOK_POS if not found. +static UINT FindHookEntry(LPVOID pTarget) +{ + UINT i; + for (i = 0; i < g_hooks.size; ++i) + { + if ((ULONG_PTR)pTarget == (ULONG_PTR)g_hooks.pItems[i].pTarget) + return i; + } + + return INVALID_HOOK_POS; +} + +//------------------------------------------------------------------------- +static PHOOK_ENTRY AddHookEntry() +{ + if (g_hooks.pItems == NULL) + { + g_hooks.capacity = INITIAL_HOOK_CAPACITY; + g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc( + g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY)); + if (g_hooks.pItems == NULL) + return NULL; + } + else if (g_hooks.size >= g_hooks.capacity) + { + PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( + g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY)); + if (p == NULL) + return NULL; + + g_hooks.capacity *= 2; + g_hooks.pItems = p; + } + + return &g_hooks.pItems[g_hooks.size++]; +} + +//------------------------------------------------------------------------- +static VOID DeleteHookEntry(UINT pos) +{ + if (pos < g_hooks.size - 1) + g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1]; + + g_hooks.size--; + + if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size) + { + PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( + g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY)); + if (p == NULL) + return; + + g_hooks.capacity /= 2; + g_hooks.pItems = p; + } +} + +//------------------------------------------------------------------------- +static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip) +{ + UINT i; + + if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL))) + return (DWORD_PTR)pHook->pTarget; + + for (i = 0; i < pHook->nIP; ++i) + { + if (ip == ((DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i])) + return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]; + } + +#if defined(_M_X64) || defined(__x86_64__) + // Check relay function. + if (ip == (DWORD_PTR)pHook->pDetour) + return (DWORD_PTR)pHook->pTarget; +#endif + + return 0; +} + +//------------------------------------------------------------------------- +static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip) +{ + UINT i; + for (i = 0; i < pHook->nIP; ++i) + { + if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i])) + return (DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i]; + } + + return 0; +} + +//------------------------------------------------------------------------- +static VOID ProcessThreadIPs(HANDLE hThread, UINT pos, UINT action) +{ + // If the thread suspended in the overwritten area, + // move IP to the proper address. + + CONTEXT c; +#if defined(_M_X64) || defined(__x86_64__) + DWORD64 *pIP = &c.Rip; +#else + DWORD *pIP = &c.Eip; +#endif + UINT count; + + c.ContextFlags = CONTEXT_CONTROL; + if (!GetThreadContext(hThread, &c)) + return; + + if (pos == ALL_HOOKS_POS) + { + pos = 0; + count = g_hooks.size; + } + else + { + count = pos + 1; + } + + for (; pos < count; ++pos) + { + PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; + BOOL enable; + DWORD_PTR ip; + + switch (action) + { + case ACTION_DISABLE: + enable = FALSE; + break; + + case ACTION_ENABLE: + enable = TRUE; + break; + + default: // ACTION_APPLY_QUEUED + enable = pHook->queueEnable; + break; + } + if (pHook->isEnabled == enable) + continue; + + if (enable) + ip = FindNewIP(pHook, *pIP); + else + ip = FindOldIP(pHook, *pIP); + + if (ip != 0) + { + *pIP = ip; + SetThreadContext(hThread, &c); + } + } +} + +//------------------------------------------------------------------------- +static BOOL EnumerateThreads(PFROZEN_THREADS pThreads) +{ + BOOL succeeded = FALSE; + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hSnapshot != INVALID_HANDLE_VALUE) + { + THREADENTRY32 te; + te.dwSize = sizeof(THREADENTRY32); + if (Thread32First(hSnapshot, &te)) + { + succeeded = TRUE; + do + { + if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD)) + && te.th32OwnerProcessID == GetCurrentProcessId() + && te.th32ThreadID != GetCurrentThreadId()) + { + if (pThreads->pItems == NULL) + { + pThreads->capacity = INITIAL_THREAD_CAPACITY; + pThreads->pItems + = (LPDWORD)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(DWORD)); + if (pThreads->pItems == NULL) + { + succeeded = FALSE; + break; + } + } + else if (pThreads->size >= pThreads->capacity) + { + LPDWORD p; + pThreads->capacity *= 2; + p = (LPDWORD)HeapReAlloc( + g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(DWORD)); + if (p == NULL) + { + succeeded = FALSE; + break; + } + + pThreads->pItems = p; + } + pThreads->pItems[pThreads->size++] = te.th32ThreadID; + } + + te.dwSize = sizeof(THREADENTRY32); + } while (Thread32Next(hSnapshot, &te)); + + if (succeeded && GetLastError() != ERROR_NO_MORE_FILES) + succeeded = FALSE; + + if (!succeeded && pThreads->pItems != NULL) + { + HeapFree(g_hHeap, 0, pThreads->pItems); + pThreads->pItems = NULL; + } + } + CloseHandle(hSnapshot); + } + + return succeeded; +} + +//------------------------------------------------------------------------- +static MH_STATUS Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action) +{ + MH_STATUS status = MH_OK; + + pThreads->pItems = NULL; + pThreads->capacity = 0; + pThreads->size = 0; + if (!EnumerateThreads(pThreads)) + { + status = MH_ERROR_MEMORY_ALLOC; + } + else if (pThreads->pItems != NULL) + { + UINT i; + for (i = 0; i < pThreads->size; ++i) + { + HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]); + BOOL suspended = FALSE; + if (hThread != NULL) + { + DWORD result = SuspendThread(hThread); + if (result != 0xFFFFFFFF) + { + suspended = TRUE; + ProcessThreadIPs(hThread, pos, action); + } + CloseHandle(hThread); + } + + if (!suspended) + { + // Mark thread as not suspended, so it's not resumed later on. + pThreads->pItems[i] = 0; + } + } + } + + return status; +} + +//------------------------------------------------------------------------- +static VOID Unfreeze(PFROZEN_THREADS pThreads) +{ + if (pThreads->pItems != NULL) + { + UINT i; + for (i = 0; i < pThreads->size; ++i) + { + DWORD threadId = pThreads->pItems[i]; + if (threadId != 0) + { + HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, threadId); + if (hThread != NULL) + { + ResumeThread(hThread); + CloseHandle(hThread); + } + } + } + + HeapFree(g_hHeap, 0, pThreads->pItems); + } +} + +//------------------------------------------------------------------------- +static MH_STATUS EnableHookLL(UINT pos, BOOL enable) +{ + PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; + DWORD oldProtect; + SIZE_T patchSize = sizeof(JMP_REL); + LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget; + + if (pHook->patchAbove) + { + pPatchTarget -= sizeof(JMP_REL); + patchSize += sizeof(JMP_REL_SHORT); + } + + if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect)) + return MH_ERROR_MEMORY_PROTECT; + + if (enable) + { + PJMP_REL pJmp = (PJMP_REL)pPatchTarget; + pJmp->opcode = 0xE9; + pJmp->operand = (UINT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL))); + + if (pHook->patchAbove) + { + PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget; + pShortJmp->opcode = 0xEB; + pShortJmp->operand = (UINT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL))); + } + } + else + { + if (pHook->patchAbove) + memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); + else + memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL)); + } + + VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect); + + // Just-in-case measure. + FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize); + + pHook->isEnabled = enable; + pHook->queueEnable = enable; + + return MH_OK; +} + +//------------------------------------------------------------------------- +static MH_STATUS EnableAllHooksLL(BOOL enable) +{ + MH_STATUS status = MH_OK; + UINT i, first = INVALID_HOOK_POS; + + for (i = 0; i < g_hooks.size; ++i) + { + if (g_hooks.pItems[i].isEnabled != enable) + { + first = i; + break; + } + } + + if (first != INVALID_HOOK_POS) + { + FROZEN_THREADS threads; + status = Freeze(&threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE); + if (status == MH_OK) + { + for (i = first; i < g_hooks.size; ++i) + { + if (g_hooks.pItems[i].isEnabled != enable) + { + status = EnableHookLL(i, enable); + if (status != MH_OK) + break; + } + } + + Unfreeze(&threads); + } + } + + return status; +} + +//------------------------------------------------------------------------- +static VOID EnterSpinLock(VOID) +{ + SIZE_T spinCount = 0; + + // Wait until the flag is FALSE. + while (InterlockedCompareExchange(&g_isLocked, TRUE, FALSE) != FALSE) + { + // No need to generate a memory barrier here, since InterlockedCompareExchange() + // generates a full memory barrier itself. + + // Prevent the loop from being too busy. + if (spinCount < 32) + Sleep(0); + else + Sleep(1); + + spinCount++; + } +} + +//------------------------------------------------------------------------- +static VOID LeaveSpinLock(VOID) +{ + // No need to generate a memory barrier here, since InterlockedExchange() + // generates a full memory barrier itself. + + InterlockedExchange(&g_isLocked, FALSE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_Initialize(VOID) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap == NULL) + { + g_hHeap = HeapCreate(0, 0, 0); + if (g_hHeap != NULL) + { + // Initialize the internal function buffer. + InitializeBuffer(); + } + else + { + status = MH_ERROR_MEMORY_ALLOC; + } + } + else + { + status = MH_ERROR_ALREADY_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_Uninitialize(VOID) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + status = EnableAllHooksLL(FALSE); + if (status == MH_OK) + { + // Free the internal function buffer. + + // HeapFree is actually not required, but some tools detect a false + // memory leak without HeapFree. + + UninitializeBuffer(); + + HeapFree(g_hHeap, 0, g_hooks.pItems); + HeapDestroy(g_hHeap); + + g_hHeap = NULL; + + g_hooks.pItems = NULL; + g_hooks.capacity = 0; + g_hooks.size = 0; + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour)) + { + UINT pos = FindHookEntry(pTarget); + if (pos == INVALID_HOOK_POS) + { + LPVOID pBuffer = AllocateBuffer(pTarget); + if (pBuffer != NULL) + { + TRAMPOLINE ct; + + ct.pTarget = pTarget; + ct.pDetour = pDetour; + ct.pTrampoline = pBuffer; + if (CreateTrampolineFunction(&ct)) + { + PHOOK_ENTRY pHook = AddHookEntry(); + if (pHook != NULL) + { + pHook->pTarget = ct.pTarget; +#if defined(_M_X64) || defined(__x86_64__) + pHook->pDetour = ct.pRelay; +#else + pHook->pDetour = ct.pDetour; +#endif + pHook->pTrampoline = ct.pTrampoline; + pHook->patchAbove = ct.patchAbove; + pHook->isEnabled = FALSE; + pHook->queueEnable = FALSE; + pHook->nIP = ct.nIP; + memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs)); + memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs)); + + // Back up the target function. + + if (ct.patchAbove) + { + memcpy( + pHook->backup, + (LPBYTE)pTarget - sizeof(JMP_REL), + sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); + } + else + { + memcpy(pHook->backup, pTarget, sizeof(JMP_REL)); + } + + if (ppOriginal != NULL) + *ppOriginal = pHook->pTrampoline; + } + else + { + status = MH_ERROR_MEMORY_ALLOC; + } + } + else + { + status = MH_ERROR_UNSUPPORTED_FUNCTION; + } + + if (status != MH_OK) + { + FreeBuffer(pBuffer); + } + } + else + { + status = MH_ERROR_MEMORY_ALLOC; + } + } + else + { + status = MH_ERROR_ALREADY_CREATED; + } + } + else + { + status = MH_ERROR_NOT_EXECUTABLE; + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + UINT pos = FindHookEntry(pTarget); + if (pos != INVALID_HOOK_POS) + { + if (g_hooks.pItems[pos].isEnabled) + { + FROZEN_THREADS threads; + status = Freeze(&threads, pos, ACTION_DISABLE); + if (status == MH_OK) + { + status = EnableHookLL(pos, FALSE); + + Unfreeze(&threads); + } + } + + if (status == MH_OK) + { + FreeBuffer(g_hooks.pItems[pos].pTrampoline); + DeleteHookEntry(pos); + } + } + else + { + status = MH_ERROR_NOT_CREATED; + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +static MH_STATUS EnableHook(LPVOID pTarget, BOOL enable) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + if (pTarget == MH_ALL_HOOKS) + { + status = EnableAllHooksLL(enable); + } + else + { + UINT pos = FindHookEntry(pTarget); + if (pos != INVALID_HOOK_POS) + { + if (g_hooks.pItems[pos].isEnabled != enable) + { + FROZEN_THREADS threads; + status = Freeze(&threads, pos, ACTION_ENABLE); + if (status == MH_OK) + { + status = EnableHookLL(pos, enable); + + Unfreeze(&threads); + } + } + else + { + status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED; + } + } + else + { + status = MH_ERROR_NOT_CREATED; + } + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget) +{ + return EnableHook(pTarget, TRUE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget) +{ + return EnableHook(pTarget, FALSE); +} + +//------------------------------------------------------------------------- +static MH_STATUS QueueHook(LPVOID pTarget, BOOL queueEnable) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + if (pTarget == MH_ALL_HOOKS) + { + UINT i; + for (i = 0; i < g_hooks.size; ++i) + g_hooks.pItems[i].queueEnable = queueEnable; + } + else + { + UINT pos = FindHookEntry(pTarget); + if (pos != INVALID_HOOK_POS) + { + g_hooks.pItems[pos].queueEnable = queueEnable; + } + else + { + status = MH_ERROR_NOT_CREATED; + } + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget) +{ + return QueueHook(pTarget, TRUE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget) +{ + return QueueHook(pTarget, FALSE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_ApplyQueued(VOID) +{ + MH_STATUS status = MH_OK; + UINT i, first = INVALID_HOOK_POS; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + for (i = 0; i < g_hooks.size; ++i) + { + if (g_hooks.pItems[i].isEnabled != g_hooks.pItems[i].queueEnable) + { + first = i; + break; + } + } + + if (first != INVALID_HOOK_POS) + { + FROZEN_THREADS threads; + status = Freeze(&threads, ALL_HOOKS_POS, ACTION_APPLY_QUEUED); + if (status == MH_OK) + { + for (i = first; i < g_hooks.size; ++i) + { + PHOOK_ENTRY pHook = &g_hooks.pItems[i]; + if (pHook->isEnabled != pHook->queueEnable) + { + status = EnableHookLL(i, pHook->queueEnable); + if (status != MH_OK) + break; + } + } + + Unfreeze(&threads); + } + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, + LPVOID *ppOriginal, LPVOID *ppTarget) +{ + HMODULE hModule; + LPVOID pTarget; + + hModule = GetModuleHandleW(pszModule); + if (hModule == NULL) + return MH_ERROR_MODULE_NOT_FOUND; + + pTarget = (LPVOID)GetProcAddress(hModule, pszProcName); + if (pTarget == NULL) + return MH_ERROR_FUNCTION_NOT_FOUND; + + if (ppTarget != NULL) + *ppTarget = pTarget; + + return MH_CreateHook(pTarget, pDetour, ppOriginal); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal) +{ + return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL); +} + +//------------------------------------------------------------------------- +const char *WINAPI MH_StatusToString(MH_STATUS status) +{ +#define MH_ST2STR(x) \ + case x: \ + return #x; + + switch (status) { + MH_ST2STR(MH_UNKNOWN) + MH_ST2STR(MH_OK) + MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED) + MH_ST2STR(MH_ERROR_NOT_INITIALIZED) + MH_ST2STR(MH_ERROR_ALREADY_CREATED) + MH_ST2STR(MH_ERROR_NOT_CREATED) + MH_ST2STR(MH_ERROR_ENABLED) + MH_ST2STR(MH_ERROR_DISABLED) + MH_ST2STR(MH_ERROR_NOT_EXECUTABLE) + MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION) + MH_ST2STR(MH_ERROR_MEMORY_ALLOC) + MH_ST2STR(MH_ERROR_MEMORY_PROTECT) + MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND) + MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND) + } + +#undef MH_ST2STR + + return "(unknown)"; +} diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/trampoline.c b/src/MultiSeat.SteamIsolation/third_party/minhook/src/trampoline.c new file mode 100644 index 0000000..d4d6f99 --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/trampoline.c @@ -0,0 +1,320 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS) + #define ALLOW_INTRINSICS + #include +#endif + +#ifndef ARRAYSIZE + #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#if defined(_M_X64) || defined(__x86_64__) + #include "./hde/hde64.h" + typedef hde64s HDE; + #define HDE_DISASM(code, hs) hde64_disasm(code, hs) +#else + #include "./hde/hde32.h" + typedef hde32s HDE; + #define HDE_DISASM(code, hs) hde32_disasm(code, hs) +#endif + +#include "trampoline.h" +#include "buffer.h" + +// Maximum size of a trampoline function. +#if defined(_M_X64) || defined(__x86_64__) + #define TRAMPOLINE_MAX_SIZE (MEMORY_SLOT_SIZE - sizeof(JMP_ABS)) +#else + #define TRAMPOLINE_MAX_SIZE MEMORY_SLOT_SIZE +#endif + +//------------------------------------------------------------------------- +static BOOL IsCodePadding(LPBYTE pInst, UINT size) +{ + UINT i; + + if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC) + return FALSE; + + for (i = 1; i < size; ++i) + { + if (pInst[i] != pInst[0]) + return FALSE; + } + return TRUE; +} + +//------------------------------------------------------------------------- +BOOL CreateTrampolineFunction(PTRAMPOLINE ct) +{ +#if defined(_M_X64) || defined(__x86_64__) + CALL_ABS call = { + 0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8] + 0xEB, 0x08, // EB 08: JMP +10 + 0x0000000000000000ULL // Absolute destination address + }; + JMP_ABS jmp = { + 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] + 0x0000000000000000ULL // Absolute destination address + }; + JCC_ABS jcc = { + 0x70, 0x0E, // 7* 0E: J** +16 + 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] + 0x0000000000000000ULL // Absolute destination address + }; +#else + CALL_REL call = { + 0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx + 0x00000000 // Relative destination address + }; + JMP_REL jmp = { + 0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx + 0x00000000 // Relative destination address + }; + JCC_REL jcc = { + 0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx + 0x00000000 // Relative destination address + }; +#endif + + UINT8 oldPos = 0; + UINT8 newPos = 0; + ULONG_PTR jmpDest = 0; // Destination address of an internal jump. + BOOL finished = FALSE; // Is the function completed? +#if defined(_M_X64) || defined(__x86_64__) + UINT8 instBuf[16]; +#endif + + ct->patchAbove = FALSE; + ct->nIP = 0; + + do + { + HDE hs; + UINT copySize; + LPVOID pCopySrc; + ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos; + ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos; + + copySize = HDE_DISASM((LPVOID)pOldInst, &hs); + if (hs.flags & F_ERROR) + return FALSE; + + pCopySrc = (LPVOID)pOldInst; + if (oldPos >= sizeof(JMP_REL)) + { + // The trampoline function is long enough. + // Complete the function with the jump to the target function. +#if defined(_M_X64) || defined(__x86_64__) + jmp.address = pOldInst; +#else + jmp.operand = (UINT32)(pOldInst - (pNewInst + sizeof(jmp))); +#endif + pCopySrc = &jmp; + copySize = sizeof(jmp); + + finished = TRUE; + } +#if defined(_M_X64) || defined(__x86_64__) + else if ((hs.modrm & 0xC7) == 0x05) + { + // Instructions using RIP relative addressing. (ModR/M = 00???101B) + + // Modify the RIP relative address. + PUINT32 pRelAddr; + + // Avoid using memcpy to reduce the footprint. +#ifndef ALLOW_INTRINSICS + memcpy(instBuf, (LPBYTE)pOldInst, copySize); +#else + __movsb(instBuf, (LPBYTE)pOldInst, copySize); +#endif + pCopySrc = instBuf; + + // Relative address is stored at (instruction length - immediate value length - 4). + pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4); + *pRelAddr + = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len)); + + // Complete the function if JMP (FF /4). + if (hs.opcode == 0xFF && hs.modrm_reg == 4) + finished = TRUE; + } +#endif + else if (hs.opcode == 0xE8) + { + // Direct relative CALL + ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32; +#if defined(_M_X64) || defined(__x86_64__) + call.address = dest; +#else + call.operand = (UINT32)(dest - (pNewInst + sizeof(call))); +#endif + pCopySrc = &call; + copySize = sizeof(call); + } + else if ((hs.opcode & 0xFD) == 0xE9) + { + // Direct relative JMP (EB or E9) + ULONG_PTR dest = pOldInst + hs.len; + + if (hs.opcode == 0xEB) // isShort jmp + dest += (INT8)hs.imm.imm8; + else + dest += (INT32)hs.imm.imm32; + + // Simply copy an internal jump. + if ((ULONG_PTR)ct->pTarget <= dest + && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) + { + if (jmpDest < dest) + jmpDest = dest; + } + else + { +#if defined(_M_X64) || defined(__x86_64__) + jmp.address = dest; +#else + jmp.operand = (UINT32)(dest - (pNewInst + sizeof(jmp))); +#endif + pCopySrc = &jmp; + copySize = sizeof(jmp); + + // Exit the function if it is not in the branch. + finished = (pOldInst >= jmpDest); + } + } + else if ((hs.opcode & 0xF0) == 0x70 + || (hs.opcode & 0xFC) == 0xE0 + || (hs.opcode2 & 0xF0) == 0x80) + { + // Direct relative Jcc + ULONG_PTR dest = pOldInst + hs.len; + + if ((hs.opcode & 0xF0) == 0x70 // Jcc + || (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ + dest += (INT8)hs.imm.imm8; + else + dest += (INT32)hs.imm.imm32; + + // Simply copy an internal jump. + if ((ULONG_PTR)ct->pTarget <= dest + && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) + { + if (jmpDest < dest) + jmpDest = dest; + } + else if ((hs.opcode & 0xFC) == 0xE0) + { + // LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported. + return FALSE; + } + else + { + UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F); +#if defined(_M_X64) || defined(__x86_64__) + // Invert the condition in x64 mode to simplify the conditional jump logic. + jcc.opcode = 0x71 ^ cond; + jcc.address = dest; +#else + jcc.opcode1 = 0x80 | cond; + jcc.operand = (UINT32)(dest - (pNewInst + sizeof(jcc))); +#endif + pCopySrc = &jcc; + copySize = sizeof(jcc); + } + } + else if ((hs.opcode & 0xFE) == 0xC2) + { + // RET (C2 or C3) + + // Complete the function if not in a branch. + finished = (pOldInst >= jmpDest); + } + + // Can't alter the instruction length in a branch. + if (pOldInst < jmpDest && copySize != hs.len) + return FALSE; + + // Trampoline function is too large. + if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE) + return FALSE; + + // Trampoline function has too many instructions. + if (ct->nIP >= ARRAYSIZE(ct->oldIPs)) + return FALSE; + + ct->oldIPs[ct->nIP] = oldPos; + ct->newIPs[ct->nIP] = newPos; + ct->nIP++; + + // Avoid using memcpy to reduce the footprint. +#ifndef ALLOW_INTRINSICS + memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize); +#else + __movsb((LPBYTE)ct->pTrampoline + newPos, (LPBYTE)pCopySrc, copySize); +#endif + newPos += copySize; + oldPos += hs.len; + } while (!finished); + + // Is there enough place for a long jump? + if (oldPos < sizeof(JMP_REL) + && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos)) + { + // Is there enough place for a short jump? + if (oldPos < sizeof(JMP_REL_SHORT) + && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos)) + { + return FALSE; + } + + // Can we place the long jump above the function? + if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL))) + return FALSE; + + if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL))) + return FALSE; + + ct->patchAbove = TRUE; + } + +#if defined(_M_X64) || defined(__x86_64__) + // Create a relay function. + jmp.address = (ULONG_PTR)ct->pDetour; + + ct->pRelay = (LPBYTE)ct->pTrampoline + newPos; + memcpy(ct->pRelay, &jmp, sizeof(jmp)); +#endif + + return TRUE; +} diff --git a/src/MultiSeat.SteamIsolation/third_party/minhook/src/trampoline.h b/src/MultiSeat.SteamIsolation/third_party/minhook/src/trampoline.h new file mode 100644 index 0000000..bdffdac --- /dev/null +++ b/src/MultiSeat.SteamIsolation/third_party/minhook/src/trampoline.h @@ -0,0 +1,105 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#pragma pack(push, 1) + +// Structs for writing x86/x64 instructions. + +// 8-bit relative jump. +typedef struct _JMP_REL_SHORT +{ + UINT8 opcode; // EB xx: JMP +2+xx + UINT8 operand; +} JMP_REL_SHORT, *PJMP_REL_SHORT; + +// 32-bit direct relative jump/call. +typedef struct _JMP_REL +{ + UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx + UINT32 operand; // Relative destination address +} JMP_REL, *PJMP_REL, CALL_REL; + +// 64-bit indirect absolute jump. +typedef struct _JMP_ABS +{ + UINT8 opcode0; // FF25 00000000: JMP [+6] + UINT8 opcode1; + UINT32 dummy; + UINT64 address; // Absolute destination address +} JMP_ABS, *PJMP_ABS; + +// 64-bit indirect absolute call. +typedef struct _CALL_ABS +{ + UINT8 opcode0; // FF15 00000002: CALL [+6] + UINT8 opcode1; + UINT32 dummy0; + UINT8 dummy1; // EB 08: JMP +10 + UINT8 dummy2; + UINT64 address; // Absolute destination address +} CALL_ABS; + +// 32-bit direct relative conditional jumps. +typedef struct _JCC_REL +{ + UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx + UINT8 opcode1; + UINT32 operand; // Relative destination address +} JCC_REL; + +// 64bit indirect absolute conditional jumps that x64 lacks. +typedef struct _JCC_ABS +{ + UINT8 opcode; // 7* 0E: J** +16 + UINT8 dummy0; + UINT8 dummy1; // FF25 00000000: JMP [+6] + UINT8 dummy2; + UINT32 dummy3; + UINT64 address; // Absolute destination address +} JCC_ABS; + +#pragma pack(pop) + +typedef struct _TRAMPOLINE +{ + LPVOID pTarget; // [In] Address of the target function. + LPVOID pDetour; // [In] Address of the detour function. + LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function. + +#if defined(_M_X64) || defined(__x86_64__) + LPVOID pRelay; // [Out] Address of the relay function. +#endif + BOOL patchAbove; // [Out] Should use the hot patch area? + UINT nIP; // [Out] Number of the instruction boundaries. + UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function. + UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. +} TRAMPOLINE, *PTRAMPOLINE; + +BOOL CreateTrampolineFunction(PTRAMPOLINE ct); diff --git a/src/MultiSeat.Tests/Sessions/SteamIsolationLaunchPlanTests.cs b/src/MultiSeat.Tests/Sessions/SteamIsolationLaunchPlanTests.cs new file mode 100644 index 0000000..f95f454 --- /dev/null +++ b/src/MultiSeat.Tests/Sessions/SteamIsolationLaunchPlanTests.cs @@ -0,0 +1,176 @@ +using System.Reflection.PortableExecutable; +using MultiSeat.Service.Configuration; +using MultiSeat.Service.Sessions; +using Xunit; + +namespace MultiSeat.Tests.Sessions; + +public sealed class SteamIsolationLaunchPlanTests : IDisposable +{ + private readonly string _directory = Path.Combine(Path.GetTempPath(), "MultiSeat Steam " + Guid.NewGuid()); + + public SteamIsolationLaunchPlanTests() => Directory.CreateDirectory(_directory); + public void Dispose() => Directory.Delete(_directory, recursive: true); + + [Fact] + public void DisabledPreservesSteamCommandWithoutRequiringComponents() + { + var plan = SteamIsolationLaunchPlan.Create("steam.exe", "-bigpicture", new()); + Assert.Equal("steam.exe", plan.ExecutablePath); + Assert.Equal("-bigpicture", plan.Arguments); + Assert.False(plan.Supervised); + } + + [Theory] + [InlineData("steamwebhelper.exe")] + [InlineData("my-steam.exe")] + [InlineData("sunshine.exe")] + public void EnabledDoesNotWrapOtherExecutables(string executable) + { + var plan = SteamIsolationLaunchPlan.Create(executable, "--unchanged", Enabled()); + Assert.Equal(executable, plan.ExecutablePath); + Assert.Equal("--unchanged", plan.Arguments); + Assert.False(plan.Supervised); + } + + [Theory] + [InlineData("steam.exe")] + [InlineData(".\\steam.exe")] + public void EnabledRefusesRelativeSteamPath(string executable) => + Assert.Throws(() => SteamIsolationLaunchPlan.Create(executable, null, Enabled())); + + [Theory] + [InlineData(Machine.Amd64)] + [InlineData(Machine.I386)] + public void WrapsBothArchitecturesAndPreservesArgumentTail(Machine machine) + { + var steam = Fixture("StEaM.exe", machine); + var options = CompleteComponents(machine); + const string arguments = "-bigpicture -applaunch 123 \"save game\""; + var plan = SteamIsolationLaunchPlan.Create(steam, arguments, options); + Assert.Equal(options.SteamIsolationLauncherPath, plan.ExecutablePath); + Assert.Equal($"--steam \"{steam}\" -- {arguments}", plan.Arguments); + Assert.True(plan.Supervised); + } + + [Fact] + public void MissingHelperFailsBeforeLaunchingSteam() + { + var steam = Fixture("steam.exe", Machine.Amd64); + var error = Assert.Throws(() => SteamIsolationLaunchPlan.Create(steam, null, Enabled())); + Assert.Contains("launcher", error.Message); + } + + [Fact] + public void MissingHookFailsBeforeLaunchingSteam() + { + var steam = Fixture("steam.exe", Machine.Amd64); + Fixture("MultiSeat.SteamLauncher.exe", Machine.Amd64); + var error = Assert.Throws(() => SteamIsolationLaunchPlan.Create(steam, null, Enabled())); + Assert.Contains("hook", error.Message); + } + + [Fact] + public void X86SteamRequiresX86Helper() + { + var steam = Fixture("steam.exe", Machine.I386); + Fixture("MultiSeat.SteamLauncher.exe", Machine.Amd64); + Fixture("MultiSeat.SteamHook32.dll", Machine.I386); + var error = Assert.Throws(() => SteamIsolationLaunchPlan.Create(steam, null, Enabled())); + Assert.Contains("32-bit", error.Message); + } + + [Fact] + public void WrongHookArchitectureFails() + { + var steam = Fixture("steam.exe", Machine.Amd64); + CompleteComponents(Machine.Amd64); + Fixture("MultiSeat.SteamHook.dll", Machine.I386); + var error = Assert.Throws(() => SteamIsolationLaunchPlan.Create(steam, null, Enabled())); + Assert.Contains("wrong architecture", error.Message); + } + + [Fact] + public void CorruptSteamExecutableFails() + { + var steam = Path.Combine(_directory, "steam.exe"); + File.WriteAllText(steam, "not an executable"); + Fixture("MultiSeat.SteamLauncher.exe", Machine.Amd64); + Assert.Throws(() => SteamIsolationLaunchPlan.Create(steam, null, Enabled())); + } + + [Fact] + public void WrongPrimaryLauncherArchitectureFails() + { + var steam = Fixture("steam.exe", Machine.I386); + CompleteComponents(Machine.I386); + Fixture("MultiSeat.SteamLauncher.exe", Machine.I386); + var error = Assert.Throws(() => SteamIsolationLaunchPlan.Create(steam, null, Enabled())); + Assert.Contains("wrong architecture", error.Message); + } + + [Fact] + public void NoSteamArgumentsStillHasSeparator() + { + var steam = Fixture("steam.exe", Machine.Amd64); + var plan = SteamIsolationLaunchPlan.Create(steam, null, CompleteComponents(Machine.Amd64)); + Assert.Equal($"--steam \"{steam}\" --", plan.Arguments); + } + + [Fact] + public void ReadyEventPrecedesSeparatorAndPreservesSteamArgumentTail() + { + var steam = Fixture("steam.exe", Machine.Amd64); + const string arguments = "-bigpicture --literal \"hello -- world\""; + var eventName = @"Global\MultiSeatSteamReady-" + Guid.NewGuid().ToString("N"); + var plan = SteamIsolationLaunchPlan.Create(steam, arguments, CompleteComponents(Machine.Amd64)) + .WithReadyEvent(eventName); + Assert.Equal($"--steam \"{steam}\" --ready-event {eventName} -- {arguments}", plan.Arguments); + } + + [Theory] + [InlineData("arbitrary-event")] + [InlineData("Global\\MultiSeatSteamReady-x --evil")] + public void ReadyEventRejectsInvalidNames(string eventName) + { + var steam = Fixture("steam.exe", Machine.Amd64); + var plan = SteamIsolationLaunchPlan.Create(steam, null, CompleteComponents(Machine.Amd64)); + Assert.Throws(() => plan.WithReadyEvent(eventName)); + } + + private MultiSeatOptions Enabled() => new() + { + SteamIsolationEnabled = true, + SteamIsolationLauncherPath = Path.Combine(_directory, "MultiSeat.SteamLauncher.exe") + }; + + private MultiSeatOptions CompleteComponents(Machine steamMachine) + { + Fixture("MultiSeat.SteamLauncher.exe", Machine.Amd64); + Fixture(steamMachine == Machine.I386 ? "MultiSeat.SteamHook32.dll" : "MultiSeat.SteamHook.dll", steamMachine); + if (steamMachine == Machine.I386) Fixture("MultiSeat.SteamLauncher32.exe", Machine.I386); + return Enabled(); + } + + // Minimal PE fixtures are never executed. The architecture preflight parses their headers. + private string Fixture(string name, Machine machine) + { + var path = Path.Combine(_directory, name); + var bytes = new byte[1024]; + using (var writer = new BinaryWriter(new MemoryStream(bytes))) + { + writer.Write((ushort)0x5a4d); + writer.BaseStream.Position = 0x3c; + writer.Write(0x80); + writer.BaseStream.Position = 0x80; + writer.Write(0x00004550); + writer.Write((ushort)machine); + writer.BaseStream.Position = 0x94; + writer.Write((ushort)(machine == Machine.Amd64 ? 240 : 224)); + writer.Write((ushort)0x0002); + writer.Write((ushort)(machine == Machine.Amd64 ? 0x20b : 0x10b)); + } + File.WriteAllBytes(path, bytes); + return path; + } +} diff --git a/src/MultiSeat.Tests/Sessions/SteamIsolationReadinessTests.cs b/src/MultiSeat.Tests/Sessions/SteamIsolationReadinessTests.cs new file mode 100644 index 0000000..2be51eb --- /dev/null +++ b/src/MultiSeat.Tests/Sessions/SteamIsolationReadinessTests.cs @@ -0,0 +1,67 @@ +using System.Security.AccessControl; +using System.Security.Principal; +using MultiSeat.Service.Interop; +using MultiSeat.Service.Sessions; +using Xunit; + +namespace MultiSeat.Tests.Sessions; + +public sealed class SteamIsolationReadinessTests +{ + [Fact] + public void SecurityGrantsOnlyHostAdminsAndExactSeatMinimalRights() + { + var seat = new SecurityIdentifier("S-1-5-21-100-200-300-1001"); + var security = new RawSecurityDescriptor(SteamIsolationReadiness.BuildSddl(seat)); + Assert.True(security.ControlFlags.HasFlag(ControlFlags.DiscretionaryAclProtected)); + Assert.Equal(3, security.DiscretionaryAcl!.Count); + var rule = Assert.IsType(security.DiscretionaryAcl[2]); + Assert.Equal(seat, rule.SecurityIdentifier); + Assert.Equal(0x00100002, rule.AccessMask); + Assert.Equal(AceQualifier.AccessAllowed, rule.AceQualifier); + } + + [Fact] + public async Task RealNamedEventCanBeSignaledByItsIntendedAccount() + { + using var identity = WindowsIdentity.GetCurrent(); + using var ready = new SteamIsolationReadiness(identity.User!); + using var opened = EventWaitHandle.OpenExisting(ready.Name); + Assert.False(opened.WaitOne(0)); + opened.Set(); + await ready.WaitAsync(Kernel32.GetCurrentProcess(), TimeSpan.FromSeconds(1), default); + Assert.True(opened.WaitOne(0)); // manual reset + } + + [Fact] + public async Task WaitsForInitializationInsteadOfAssumingRunningMeansReady() + { + var checks = 0; + await SteamIsolationReadiness.WaitCoreAsync(() => ++checks >= 3, () => null, + TimeSpan.FromSeconds(2), default); + Assert.True(checks >= 3); + } + + [Theory] + [InlineData(0u)] + [InlineData(9u)] + public async Task ExitWithoutReadinessFailsEvenWithZeroExitCode(uint code) + { + var error = await Assert.ThrowsAsync(() => + SteamIsolationReadiness.WaitCoreAsync(() => false, () => code, TimeSpan.FromSeconds(1), default)); + Assert.Contains($"code {code}", error.Message); + } + + [Fact] + public async Task MissingReadinessTimesOut() => + await Assert.ThrowsAsync(() => SteamIsolationReadiness.WaitCoreAsync( + () => false, () => null, TimeSpan.Zero, default)); + + [Fact] + public async Task CancellationInterruptsReadinessWait() + { + using var cancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(80)); + await Assert.ThrowsAnyAsync(() => SteamIsolationReadiness.WaitCoreAsync( + () => false, () => null, TimeSpan.FromSeconds(30), cancellation.Token)); + } +}