diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..7a3a077 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,51 @@ +FROM ubuntu:24.04 + +# Install LLVM 19 repo (xwin's latest MSVC STL requires Clang 19+) +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl ca-certificates gnupg \ + && curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key \ + > /etc/apt/trusted.gpg.d/llvm.asc \ + && echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" \ + > /etc/apt/sources.list.d/llvm-19.list \ + && apt-get update && apt-get install -y --no-install-recommends \ + cmake \ + ninja-build \ + clang-19 \ + lld-19 \ + llvm-19 \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Create unversioned tool symlinks +# clang-cl is clang invoked by a different name (argv[0] detection) +RUN ln -sf /usr/bin/clang-19 /usr/bin/clang-cl \ + && for tool in clang clang++ lld-link llvm-lib llvm-rc llvm-mt llvm-ar; do \ + ln -sf "/usr/bin/${tool}-19" "/usr/bin/$tool"; \ + done + +# Install xwin to download Windows SDK and MSVC CRT +ARG XWIN_VERSION=0.6.5 +RUN curl -fsSL "https://github.com/Jake-Shadle/xwin/releases/download/${XWIN_VERSION}/xwin-${XWIN_VERSION}-x86_64-unknown-linux-musl.tar.gz" \ + | tar xz --strip-components=1 -C /usr/local/bin "xwin-${XWIN_VERSION}-x86_64-unknown-linux-musl/xwin" + +# Download Windows SDK and MSVC CRT (x64 only) +RUN xwin --accept-license --arch x86_64 splat --output /xwin \ + && rm -rf /root/.xwin-cache + +# Create case-variant symlinks for .lib files. +# Linux is case-sensitive but Windows SDK headers reference libs with varying casing +# (e.g., "Version.lib" vs "version.lib"). This ensures both forms resolve. +RUN find /xwin -name '*.lib' | while read -r lib; do \ + dir=$(dirname "$lib"); \ + base=$(basename "$lib"); \ + lower=$(echo "$base" | tr '[:upper:]' '[:lower:]'); \ + title=$(echo "$lower" | sed 's/^\(.\)/\U\1/'); \ + [ -e "$dir/$lower" ] || ln -sf "$base" "$dir/$lower"; \ + [ -e "$dir/$title" ] || ln -sf "$base" "$dir/$title"; \ + done; exit 0 + +# Create case-variant symlinks for commonly-mismatched CRT/SDK headers +# (e.g., #include "String.h" resolves to on case-insensitive Windows) +RUN [ ! -e /xwin/sdk/include/ucrt/String.h ] && ln -sf string.h /xwin/sdk/include/ucrt/String.h; exit 0 + +ENV XWIN_DIR=/xwin diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..0d70b47 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +{ + "name": "AlphaRing Cross-Compile (Windows x64)", + "build": { + "dockerfile": "Dockerfile", + "context": "." + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools" + ], + "settings": { + "cmake.configureArgs": [ + "-DCMAKE_TOOLCHAIN_FILE=${workspaceFolder}/.devcontainer/toolchain-cross-win64.cmake" + ], + "cmake.generator": "Ninja" + } + } + }, + "postCreateCommand": "sh .devcontainer/fix-case.sh && echo 'Container ready. Build: cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=.devcontainer/toolchain-cross-win64.cmake -DCMAKE_BUILD_TYPE=Release && cmake --build build'" +} diff --git a/.devcontainer/fix-case.sh b/.devcontainer/fix-case.sh new file mode 100644 index 0000000..45c2905 --- /dev/null +++ b/.devcontainer/fix-case.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# Fix case-sensitivity mismatches for cross-compiling on Linux. +# Windows (NTFS) is case-insensitive; these symlinks emulate that for known paths. +set -e + +# 1. lib build-type directories: Release -> release, Debug -> debug +for dir in lib/*/lib; do + [ -d "$dir/release" ] && [ ! -e "$dir/Release" ] && ln -sf release "$dir/Release" + [ -d "$dir/debug" ] && [ ! -e "$dir/Debug" ] && ln -sf debug "$dir/Debug" +done + +# 2. minhook lib filename casing (CMakeLists.txt says libMinhook, file is libMinHook) +for d in release Release debug Debug; do + p="lib/minhook/lib/$d" + [ -d "$p" ] && [ ! -e "$p/libMinhook.x64.lib" ] && ln -sf libMinHook.x64.lib "$p/libMinhook.x64.lib" +done + +# 3. Source directory casing: includes reference D3d11/ and Window/ but dirs are d3d11/ and window/ +[ -d "src/render/d3d11" ] && [ ! -e "src/render/D3d11" ] && ln -sf d3d11 "src/render/D3d11" +[ -d "src/render/window" ] && [ ! -e "src/render/Window" ] && ln -sf window "src/render/Window" + +echo "Case-sensitivity symlinks created." diff --git a/.devcontainer/toolchain-cross-win64.cmake b/.devcontainer/toolchain-cross-win64.cmake new file mode 100644 index 0000000..07fc185 --- /dev/null +++ b/.devcontainer/toolchain-cross-win64.cmake @@ -0,0 +1,48 @@ +# Cross-compilation toolchain: Linux -> Windows x64 using clang-cl + lld-link + xwin +# Used by the AlphaRing devcontainer to build WTSAPI32.dll + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +# --- Compilers and tools --- +set(CMAKE_C_COMPILER clang-cl) +set(CMAKE_CXX_COMPILER clang-cl) +set(CMAKE_LINKER lld-link) +set(CMAKE_AR llvm-lib) +set(CMAKE_MT llvm-mt) +set(CMAKE_RC_COMPILER llvm-rc) + +# Target triple +set(CMAKE_C_COMPILER_TARGET x86_64-pc-windows-msvc) +set(CMAKE_CXX_COMPILER_TARGET x86_64-pc-windows-msvc) + +# xwin only ships release CRT — force /MD for all build types +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "") + +# --- xwin SDK/CRT paths --- +if(DEFINED ENV{XWIN_DIR}) + set(XWIN_DIR "$ENV{XWIN_DIR}" CACHE PATH "Path to xwin output directory") +else() + set(XWIN_DIR "/xwin" CACHE PATH "Path to xwin output directory") +endif() + +# Include paths: MSVC CRT + Windows SDK (ucrt, um, shared) +string(CONCAT _XWIN_CL_FLAGS + "-imsvc ${XWIN_DIR}/crt/include " + "-imsvc ${XWIN_DIR}/sdk/include/ucrt " + "-imsvc ${XWIN_DIR}/sdk/include/um " + "-imsvc ${XWIN_DIR}/sdk/include/shared " + "/EHsc" +) +set(CMAKE_C_FLAGS_INIT "${_XWIN_CL_FLAGS}") +set(CMAKE_CXX_FLAGS_INIT "${_XWIN_CL_FLAGS}") + +# Library paths: MSVC CRT + Windows SDK +string(CONCAT _XWIN_LINK_FLAGS + "/LIBPATH:${XWIN_DIR}/crt/lib/x86_64 " + "/LIBPATH:${XWIN_DIR}/sdk/lib/um/x86_64 " + "/LIBPATH:${XWIN_DIR}/sdk/lib/ucrt/x86_64" +) +set(CMAKE_EXE_LINKER_FLAGS_INIT "${_XWIN_LINK_FLAGS}") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "${_XWIN_LINK_FLAGS}") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "${_XWIN_LINK_FLAGS}") diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..062b1a0 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,113 @@ +# Copilot Instructions for AlphaRing + +## Project Overview + +AlphaRing is a modding tool for Halo: The Master Chief Collection (MCC). It is compiled as a DLL (`WTSAPI32.dll`) that wraps the real Windows Terminal Services API, allowing it to be loaded by the game via DLL proxy injection. The mod provides splitscreen support for all games, a camera tool (Halo 3), and an object browser (Halo 3). + +## Build System + +- **CMake** (minimum 3.27), generator: Ninja +- **C++ Standard**: C++17 +- **Target architecture**: x64 only (Windows) +- **Toolchain**: MSVC (`msvc_x64_x64`) +- **Configurations**: Debug and Release +- **Output**: A shared library named `WTSAPI32.dll` +- **Current game version target**: `1.3528.0.0` (set via `VERSION` in root CMakeLists.txt) +- **Compile definitions**: `WRAPPER_DLL_NAME`, `GAME_VERSION`, `_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS`, `IMGUI_DEFINE_MATH_OPERATORS` +- **Install**: Copies the DLL to `${MCC_DIR}/mcc/binaries/win64` and resources to `${MCC_DIR}/alpha_ring` +- **Cross-compilation (Linux)**: Devcontainer in `.devcontainer/` using Clang 19 (`clang-cl`) + `lld-link` + `xwin` for Windows SDK/CRT. Toolchain file: `.devcontainer/toolchain-cross-win64.cmake`. Requires `fix-case.sh` for Linux case-sensitivity symlinks. + +## Dependencies (all pre-built as static/shared libs under `lib/`) + +| Library | Purpose | +|------------|--------------------------------------| +| MinHook | Function hooking (detour/trampoline) | +| ImGui | In-game overlay UI (DX11 backend) | +| Lua | Scripting engine | +| spdlog | Logging | +| tinyxml2 | XML parsing (patch.xml) | +| nlohmann/json | JSON parsing (header-only) | + +## Project Structure + +### `src/` — Core application code + +- **`main.cpp`** — DLL entry point (`DllMain`). Spawns initialization on `DLL_PROCESS_ATTACH`. +- **`common.h`** — Precompiled-style header: includes `utils.h`, `Hook.h`, `Log.h`, `Global.h`, `Filesystem.h`. +- **`hook/`** — MinHook wrapper. Provides `Detour()`, `Offset()`, `Patch()` helpers and struct types (`DetourFunction`, `DetourOffset`, `FunctionOffset`, `PatchMCC`, `Detour_t`). +- **`log/`** — spdlog-based logging. Macros: `LOG_INFO`, `LOG_ERROR`, `LOG_WARNING`, `LOG_DEBUG`. +- **`global/`** — Global state singletons using `DefGlobal`/`ImplGlobal` macros. Holds runtime flags (wireframe, imgui visibility, splitscreen config). +- **`filesystem/`** — File I/O helpers for the `alpha_ring/` resource directory. +- **`input/`** — XInput wrapper. Intercepts controller state for menu navigation. +- **`render/`** — Rendering subsystem: + - `d3d11/` — D3D11 hook (swapchain Present), Graphics state management. + - `imgui/` — ImGui integration, game-specific UI panels (`game/halo3/`, `game/mcc/`), curve editor. + - `window/` — Window management. +- **`wrapper/`** — DLL proxy implementation. `ModuleDefinition` class loads the real `WTSAPI32.dll` and forwards exported functions (`WTSRegisterSessionNotification`, etc.). +- **`mcc/`** — MCC engine interaction: + - `CGameEngine` — Virtual function table for the game engine (pause, restart, events). + - `CGameManager` — Player profiles, controller mapping, input, game state. + - `CGameGlobal` — Game identification enum (`Halo1`..`HaloReach`), game state/events. + - `CDeviceManager` — Device/controller management. + - `CUserProfile` / `CGamepadMapping` — Player settings. + - `module/` — Per-game module loading. `CModule` manages entry hooks and patches per game DLL. `entry/` has hook entry points per game. `patch/` has XML-driven patching (`CPatch`, `CPatchSet`). + - `network/` — Network features (currently commented out / WIP). + - `splitscreen/` — Splitscreen logic and ImGui UI. + +### `lib/game/` — Game-specific reverse-engineered code + +- **`inc/`** — Headers per game: `halo1.h`, `halo2.h`, `halo3.h`, `halo4.h`, `haloreach.h`, `halo3odst.h`, `groundhog.h` (Halo 2 Anniversary). +- **`inc//`** — Version-specific offset headers (e.g., `offset_halo3.h`, `offset_mcc.h`) for versions `1.3385.0.0`, `1.3495.0.0`, `1.3528.0.0`. +- **`src/`** — Native function wrappers per game (e.g., `halo3/` has subdirs for `ai`, `camera`, `game`, `objects`, `physics`, `render`, `simulation`, `units`, etc.). +- **`src/ICNative.h`** — Macros: `DefNative`, `DefPtr`, `DefPPtr` for defining native game function/pointer accessors using module base + offset. + +### `lib/utils/` — Shared utility code + +- `FileVersion` — Parse/compare game EXE version strings. +- `String` — String utilities. +- `ThreadLocalStorage` — TLS for per-module base address storage. + +### `res/` — Runtime resources (installed to `alpha_ring/`) + +- `init.lua` — Lua initialization script. +- `patch.xml` — XML-defined memory patches per module/version. + +## Coding Conventions + +- **Namespaces**: `AlphaRing::Hook`, `AlphaRing::Render`, `AlphaRing::Input`, `AlphaRing::Log`, `AlphaRing::Filesystem`, `AlphaRing::Global`, `MCC`, `MCC::Module`, `MCC::Splitscreen`, `MCC::Network`. +- **Naming**: PascalCase for classes/structs (`CGameEngine`, `CModule`), PascalCase for namespaces, camelCase/snake_case for functions and members. Prefix `C` for major classes. +- **Hook pattern**: Use `AlphaRing::Hook::Detour()` with initializer lists of `DetourOffset` or `Detour_t`. Use `AlphaRing::Hook::Offset()` for resolving function/data pointers. +- **Offsets**: Steam and Windows Store offsets are maintained in parallel (`offset_steam`, `offset_ws`). +- **Game detection**: Based on `CGameGlobal::eGame` enum. Module DLLs are loaded dynamically. +- **Assertions**: `assertm(expr, msg)` macro used extensively during initialization. +- **Globals**: Use `DefGlobal(Name)` / `ImplGlobal(Name)` pattern for singleton global state. +- **Native calls**: Use `DefNative`, `DefPtr`, `DefPPtr` macros from `ICNative.h` to access game memory at known offsets. + +## Supported Games + +| Enum Value | Game | Module DLL | +|-----------|-----------------|--------------| +| 0 | Halo 1 (CE) | halo1.dll | +| 1 | Halo 2 | halo2.dll | +| 2 | Halo 3 | halo3.dll | +| 3 | Halo 4 | halo4.dll | +| 4 | Groundhog (H2A) | groundhog.dll| +| 5 | Halo 3 ODST | halo3odst.dll| +| 6 | Halo Reach | haloreach.dll| + +## DLL Injection Mechanism + +The project builds as `WTSAPI32.dll`. When placed in the game directory, Windows loads it instead of the system DLL. The wrapper code (`src/wrapper/`) loads the real system `WTSAPI32.dll` and forwards all original exports while simultaneously running the mod's initialization code on `DLL_PROCESS_ATTACH`. + +## Key Technical Notes + +- All memory offsets are relative to the game executable's base address. +- Two sets of offsets exist: one for Steam, one for Windows Store. Only Steam is actively tested. +- The mod hooks D3D11 `Present` to inject ImGui rendering. +- XInput is intercepted to allow controller-based menu navigation. +- Patches can be defined in `patch.xml` and applied at runtime via the `CPatch`/`CPatchSet` system. +- Lua scripting support is available via the embedded Lua runtime. + +## Copilot Workflow Preferences + +- Always save memory/knowledge files within the repository (e.g., `.github/memories.md`) rather than only in Copilot's internal memory system. diff --git a/.github/memories.md b/.github/memories.md new file mode 100644 index 0000000..d558b62 --- /dev/null +++ b/.github/memories.md @@ -0,0 +1,112 @@ +# AlphaRing Repository Knowledge + +## What It Is +- Modding tool for Halo: Master Chief Collection (MCC) +- DLL proxy injection via `WTSAPI32.dll` — wraps real Windows Terminal Services API +- Provides: splitscreen (all games), camera tool (H3), object browser (H3) +- Created by WinterSquire, updated by xTrxplex + +## Build +- CMake 3.27+, Ninja generator, MSVC x64, C++17 +- Output: `WTSAPI32.dll` (shared library) +- Current target version: `1.3528.0.0` (set in root CMakeLists.txt `VERSION` var) +- Install dir: `${MCC_DIR}/mcc/binaries/win64` (DLL) + `${MCC_DIR}/alpha_ring` (resources) +- Two configs: Debug & Release — libs in `lib//lib/debug|release/` +- Definitions: `WRAPPER_DLL_NAME`, `GAME_VERSION`, `_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS`, `IMGUI_DEFINE_MATH_OPERATORS` + +## Dependencies (pre-built under lib/) +- MinHook: function hooking +- ImGui: in-game overlay UI (DX11) +- Lua: scripting engine +- spdlog: logging +- tinyxml2: XML parsing (patch.xml) +- nlohmann/json: JSON (header-only) + +## Key Architecture +- `src/main.cpp`: DllMain → DLL_PROCESS_ATTACH → CreateThread(Initialize) +- `src/common.h`: precompiled header — utils, Hook, Log, Global, Filesystem + assertm macro +- `src/hook/`: MinHook wrapper — Detour(), Offset(), Patch() with structs DetourOffset, Detour_t, FunctionOffset, PatchMCC +- `src/log/`: spdlog — LOG_INFO, LOG_ERROR, LOG_WARNING, LOG_DEBUG macros +- `src/global/`: DefGlobal/ImplGlobal pattern for singleton state (wireframe, imgui, splitscreen config) +- `src/filesystem/`: File I/O for alpha_ring/ resource dir +- `src/input/`: XInput wrapper for controller interception +- `src/render/`: D3D11 swapchain Present hook, ImGui integration, window management + - `d3d11/`: D3d11.h/cpp, Graphics.h/cpp, Hook.cpp + - `imgui/`: ImGui.h/cpp, ICContext.h, curve_editor/, game/ (halo3, mcc UI panels) + - `window/`: Window.h/cpp +- `src/wrapper/`: ModuleDefinition loads real WTSAPI32.dll, forwards 5 exports +- `src/mcc/`: Engine interaction + - CGameEngine: vtable for engine (pause/restart/events) + - CGameManager: player profiles, controllers, input, game state + - CGameGlobal: game enum (Halo1-HaloReach), state/events + - CDeviceManager, CUserProfile, CGamepadMapping + - `module/`: CModule — per-game DLL loading, entry hooks, XML patches + - `entry/`: per-game hook entry points (halo1-haloreach subdirs) + - `patch/`: CPatch, CPatchSet — XML-driven runtime patching + - `network/`: WIP, currently commented out + - `splitscreen/`: logic + ImGui UI + +## Game Library (lib/game/) +- `inc/`: Per-game headers (halo1.h-haloreach.h, groundhog.h) +- `inc//`: Offset headers per version (1.3385.0.0, 1.3495.0.0, 1.3528.0.0) + - Each version has: offset_halo1.h through offset_mcc.h +- `src/`: Native function wrappers per game + - halo3/ has deepest coverage: ai, camera, game, interface, main, networking, objects, physics, rasterizer, render, simulation, units +- `src/ICNative.h`: DefNative, DefPtr, DefPPtr macros for game memory access + +## Utils (lib/utils/) +- FileVersion: parse/compare game EXE version strings +- String: string utilities +- ThreadLocalStorage: TLS for per-module base address + +## Resources (res/ → installed to alpha_ring/) +- init.lua: Lua init script (sets alpha_ring['show_menu'] = true) +- patch.xml: XML memory patches per module/version (e.g., halo3.dll patches) + +## Coding Conventions +- Namespaces: AlphaRing::{Hook,Render,Input,Log,Filesystem,Global}, MCC, MCC::{Module,Splitscreen,Network} +- PascalCase classes/structs with C prefix (CGameEngine, CModule) +- PascalCase namespaces, camelCase/snake_case functions/members +- Hook pattern: AlphaRing::Hook::Detour({...}) with initializer lists +- Dual offsets: Steam + Windows Store (only Steam actively tested) +- Game detection: CGameGlobal::eGame enum +- assertm(expr, msg) for init-time assertions + +## Supported Games +| Enum | Game | DLL | +|------|-------------|---------------| +| 0 | Halo 1 (CE) | halo1.dll | +| 1 | Halo 2 | halo2.dll | +| 2 | Halo 3 | halo3.dll | +| 3 | Halo 4 | halo4.dll | +| 4 | H2A (Groundhog) | groundhog.dll | +| 5 | Halo 3 ODST | halo3odst.dll | +| 6 | Halo Reach | haloreach.dll | + +## Distribution Detection (Hook.cpp) +- Steam: MCC-Win64-Shipping.exe +- Windows Store: MCCWinStore-Win64-Shipping.exe +- Validates file version matches GAME_VERSION at startup + +## User Controls +- Toggle menu: F4 or Back+Start on controller +- Controller nav: Right Stick = mouse, RB = click +- Game input disabled when menu open + +## Steam Deck / Linux +- Launch option: `WINEDLLOVERRIDES="WTSAPI32=n,b" %command%` + +## Dev Container (Cross-Compilation from Linux) +- `.devcontainer/` provides a Docker-based cross-compilation environment +- Stack: Ubuntu 24.04 + Clang 19 (from LLVM apt repo) + lld-link + xwin 0.6.5 +- xwin downloads Windows SDK + MSVC CRT headers/libs into /xwin +- Toolchain file: `.devcontainer/toolchain-cross-win64.cmake` — uses clang-cl targeting x86_64-pc-windows-msvc +- xwin only ships release CRT — forces `CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL` (/MD) for all build types +- Case-sensitivity symlinks required on Linux (NTFS is case-insensitive): + - `lib/*/lib/Release` → `release`, `lib/*/lib/Debug` → `debug` + - `lib/minhook/lib/*/libMinhook.x64.lib` → `libMinHook.x64.lib` (CMakeLists.txt uses wrong casing) + - `src/render/D3d11` → `d3d11`, `src/render/Window` → `window` (includes use PascalCase dirs) + - `/xwin/sdk/include/ucrt/String.h` → `string.h` (game code includes `"String.h"` resolved case-insensitively) +- `.devcontainer/fix-case.sh` creates all workspace-level symlinks in postCreateCommand +- Build: `cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=.devcontainer/toolchain-cross-win64.cmake -DCMAKE_BUILD_TYPE=Release && cmake --build build` +- Produces valid PE32+ x86-64 DLL (~1.2 MB) diff --git a/.gitignore b/.gitignore index e1240f0..b2656d5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,22 @@ cmake-build-release/ cmake-build-debug/ .idea/ build/ +build-*/ +out/ /.vs + +# Case-sensitivity symlinks created by .devcontainer/fix-case.sh +src/render/D3d11 +src/render/Window +lib/imgui/lib/Release +lib/imgui/lib/Debug +lib/lua/lib/Release +lib/lua/lib/Debug +lib/minhook/lib/Release +lib/minhook/lib/Debug +lib/spdlog/lib/Release +lib/spdlog/lib/Debug +lib/tinyxml2/lib/Release +lib/tinyxml2/lib/Debug +lib/minhook/lib/release/libMinhook.x64.lib +lib/minhook/lib/debug/libMinhook.x64.lib diff --git a/README.md b/README.md index 2a2117c..ec9eae9 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,34 @@ A Modding Tool for MCC * Camera Tool (H3) * Object Browser (H3) +### Building from Source + +#### Windows (MSVC) +Requires CMake 3.27+, Ninja, and MSVC (x64). Visual Studio or the CMakeSettings.json configs work out of the box: +``` +cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build build +``` + +#### Linux / Dev Container (cross-compilation) +A devcontainer is provided in `.devcontainer/` for cross-compiling the Windows x64 DLL from Linux using Clang 19 + lld-link + xwin (Windows SDK/CRT). + +**VS Code**: Open the repo and select "Reopen in Container" when prompted. + +**CLI (Docker)**: +```bash +docker build -t alpharing-dev .devcontainer/ +docker run --rm -v "$(pwd)":/workspace -w /workspace alpharing-dev bash -c ' + sh .devcontainer/fix-case.sh + cmake -B build -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=.devcontainer/toolchain-cross-win64.cmake \ + -DCMAKE_BUILD_TYPE=Release + cmake --build build +' +``` + +Output: `build/WTSAPI32.dll` (PE32+ x86-64). + ### Installation Make sure you have the latest [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) installed. @@ -28,6 +56,21 @@ For Running on Steam Deck/Linux, add the following command in the Steam Game Lau WINEDLLOVERRIDES="WTSAPI32=n,b" %command% ``` +Tested with GE-Proton 10-34 + +### Logging +Logging is disabled by default. To enable it, set the `ALPHARING_LOG` environment variable to `1`. Logs are written to `AlphaRing.log` next to the DLL. + +**Windows** (Command Prompt, before launching the game): +``` +set ALPHARING_LOG=1 +``` + +**Linux/Steam Deck** (Steam Game Launch Options): +``` +WINEDLLOVERRIDES="WTSAPI32=n,b" ALPHARING_LOG=1 %command% +``` + ### Usage Toggle menu: `F4` or `Controller Back` + `Controller Start` @@ -35,6 +78,11 @@ To navigate using Controller use the `Right Stick` to move the mouse and `RB` to When the menu is open, game input is disabled. +#### Linux/Steam Deck Notes +On Linux (Proton), start each mission **without** enabling splitscreen or adding extra players first. Once you are loaded into the mission, open the menu and enable splitscreen/add players on the fly. + +> **Note:** This workaround does not apply to Halo 1 (CE), which does not support adding players mid-mission. + ### Bugs Report Submit it in the [Issues](https://github.com/WinterSquire/AlphaRing/issues) page. diff --git a/src/common.h b/src/common.h index 8c6b66a..9e598ad 100644 --- a/src/common.h +++ b/src/common.h @@ -12,3 +12,16 @@ #include #define assertm(exp, msg) assert(((void)msg, (exp))) + +// Drop-in replacement for std::mutex using Win32 CRITICAL_SECTION. +// Works reliably under Wine/Proton where std::mutex can deadlock +// when used from DLLs loaded via proxy injection. +struct WinMutex { + CRITICAL_SECTION cs; + WinMutex() { InitializeCriticalSection(&cs); } + ~WinMutex() { DeleteCriticalSection(&cs); } + void lock() { EnterCriticalSection(&cs); } + void unlock() { LeaveCriticalSection(&cs); } + WinMutex(const WinMutex&) = delete; + WinMutex& operator=(const WinMutex&) = delete; +}; diff --git a/src/hook/Hook.cpp b/src/hook/Hook.cpp index 58cc194..2e1f125 100644 --- a/src/hook/Hook.cpp +++ b/src/hook/Hook.cpp @@ -34,17 +34,24 @@ namespace AlphaRing::Hook { if ((hModule = (__int64)GetModuleHandleA("MCC-Win64-Shipping.exe")) != 0) { distro = Steam; + LOG_INFO("Detected Steam distro, module base: {:x}", hModule); } else if ((hModule = (__int64)GetModuleHandleA("MCCWinStore-Win64-Shipping.exe")) != 0) { distro = WindowsStore; + LOG_INFO("Detected Windows Store distro, module base: {:x}", hModule); } else { distro = None; + LOG_ERROR("Failed to find game executable module (neither Steam nor WinStore)"); } assertm(distro != None, "failed to get distro type"); LOG_INFO("Game Version[{}]: {}", IsWS() ? "Windows Store" : "Steam", GAME_VERSION); - if ((version = FileVersion(hModule)) != FileVersion::fromString(GAME_VERSION)) { + version = FileVersion(hModule); + LOG_INFO("Game file version: {}", version.toString()); + LOG_INFO("Expected version: {}", GAME_VERSION); + + if (version != FileVersion::fromString(GAME_VERSION)) { if (distro == WindowsStore) { if ((version = FileVersion(hModule)) == FileVersion::fromString("1.3498.0.0")) @@ -52,6 +59,7 @@ namespace AlphaRing::Hook { } sprintf(buffer, "Version mismatch [%s]:%s", GAME_VERSION, version.toString().c_str()); + LOG_ERROR("Version mismatch: expected {}, got {}", GAME_VERSION, version.toString()); MessageBoxA(nullptr, buffer, "Error", MB_OK); return false; } diff --git a/src/input/Input.cpp b/src/input/Input.cpp index d0e89a8..b113706 100644 --- a/src/input/Input.cpp +++ b/src/input/Input.cpp @@ -14,11 +14,15 @@ namespace AlphaRing::Input { if ((hModule = GetModuleHandleA("XINPUT1_3.dll")) || (hModule = GetModuleHandleA("XINPUT1_4.dll")) || (hModule = GetModuleHandleA("XINPUT9_1_0.dll"))) { + LOG_INFO("XInput module found: {:p}", (void*)hModule); g_pXInputGetState = (decltype(g_pXInputGetState))GetProcAddress(hModule, "XInputGetState"); g_pXInputSetState = (decltype(g_pXInputSetState))GetProcAddress(hModule, "XInputSetState"); } - assertm(hModule != nullptr, "failed to find xinput module"); + if (hModule == nullptr) { + LOG_ERROR("Failed to find any XInput module (1_3, 1_4, 9_1_0)"); + return false; + } return true; } diff --git a/src/log/Log.cpp b/src/log/Log.cpp index 9d50d3d..b3a0c04 100644 --- a/src/log/Log.cpp +++ b/src/log/Log.cpp @@ -1,35 +1,117 @@ #include "Log.h" -#include "spdlog.h" +namespace AlphaRing::Log { + static FILE* g_logFile = nullptr; + static CRITICAL_SECTION g_logCS; + static bool g_csInit = false; + static bool g_enabled = false; -#include "common.h" + static bool IsEnabled() { + static int cached = -1; + if (cached == -1) { + char buf[16] = {}; + DWORD len = GetEnvironmentVariableA("ALPHARING_LOG", buf, sizeof(buf)); + cached = (len > 0 && buf[0] != '0') ? 1 : 0; + } + return cached == 1; + } -namespace AlphaRing::Log { - std::shared_ptr default_logger; + // Returns path to "AlphaRing.log" next to the DLL + static const char* GetLogPath() { + static char path[MAX_PATH] = {}; + if (path[0] == '\0') { + HMODULE hSelf = nullptr; + GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)&GetLogPath, + &hSelf); + if (hSelf && GetModuleFileNameA(hSelf, path, MAX_PATH)) { + char* last = strrchr(path, '\\'); + if (!last) last = strrchr(path, '/'); + if (last) *(last + 1) = '\0'; + strcat(path, "AlphaRing.log"); + } else { + strcpy(path, "AlphaRing.log"); + } + } + return path; + } + + static void WriteTimestamp(FILE* f) { + time_t now = time(nullptr); + struct tm t; + localtime_s(&t, &now); + fprintf(f, "[%04d-%02d-%02d %02d:%02d:%02d] ", + t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, + t.tm_hour, t.tm_min, t.tm_sec); + } + + void Early(const char* fmt, ...) { + if (!IsEnabled()) return; + + FILE* f = fopen(GetLogPath(), "a"); + if (!f) return; + + WriteTimestamp(f); + + va_list args; + va_start(args, fmt); + vfprintf(f, fmt, args); + va_end(args); + + fprintf(f, "\n"); + fclose(f); + } + + void Write(const char* level, const char* msg) { + if (!IsEnabled()) return; + + if (!g_csInit) { + // Fallback before Init() + Early("[%s] %s", level, msg); + return; + } + + EnterCriticalSection(&g_logCS); + if (g_logFile) { + WriteTimestamp(g_logFile); + fprintf(g_logFile, "[%s] %s\n", level, msg); + fflush(g_logFile); + } + LeaveCriticalSection(&g_logCS); + } bool Init() { - bool result = AllocConsole(); - assertm(result, "failed to allocate console"); + g_enabled = IsEnabled(); + if (!g_enabled) return true; - freopen("CONIN$", "r", stdin); - freopen("CONOUT$", "w", stdout); - freopen("CONOUT$", "w", stderr); + Early("=== AlphaRing Log Start ==="); - default_logger = std::make_shared( - "default", - std::move(std::make_shared()) - ); + InitializeCriticalSection(&g_logCS); + g_csInit = true; - spdlog::register_logger(default_logger); + g_logFile = fopen(GetLogPath(), "a"); + if (!g_logFile) { + Early("ERROR: failed to open persistent log file"); + return false; + } return true; } bool Shutdown() { - fclose(stdin); - fclose(stdout); - fclose(stderr); - FreeConsole(); + Write("INFO", "Shutting down logger."); + + if (g_csInit) { + EnterCriticalSection(&g_logCS); + if (g_logFile) { + fclose(g_logFile); + g_logFile = nullptr; + } + LeaveCriticalSection(&g_logCS); + DeleteCriticalSection(&g_logCS); + g_csInit = false; + } return true; } diff --git a/src/log/Log.h b/src/log/Log.h index c4e4dfa..06caa73 100644 --- a/src/log/Log.h +++ b/src/log/Log.h @@ -1,15 +1,43 @@ #pragma once -#include +#include +#include +#include +#include +#include namespace AlphaRing::Log { - extern std::shared_ptr default_logger; + // Early file log - works before Init() (global constructors, DllMain) + void Early(const char* fmt, ...); + + // Formatted file log - works after Init() + void Write(const char* level, const char* msg); + + template + void Info(fmt::format_string fmt, Args&&... args) { + Write("INFO", fmt::format(fmt, std::forward(args)...).c_str()); + } + + template + void Error(fmt::format_string fmt, Args&&... args) { + Write("ERROR", fmt::format(fmt, std::forward(args)...).c_str()); + } + + template + void Warn(fmt::format_string fmt, Args&&... args) { + Write("WARN", fmt::format(fmt, std::forward(args)...).c_str()); + } + + template + void Debug(fmt::format_string fmt, Args&&... args) { + Write("DEBUG", fmt::format(fmt, std::forward(args)...).c_str()); + } bool Init(); bool Shutdown(); } -#define LOG_INFO(...) AlphaRing::Log::default_logger->info(__VA_ARGS__) -#define LOG_ERROR(...) AlphaRing::Log::default_logger->error(__VA_ARGS__) -#define LOG_WARNING(...) AlphaRing::Log::default_logger->warn(__VA_ARGS__) -#define LOG_DEBUG(...) AlphaRing::Log::default_logger->debug(__VA_ARGS__) \ No newline at end of file +#define LOG_INFO(...) AlphaRing::Log::Info(__VA_ARGS__) +#define LOG_ERROR(...) AlphaRing::Log::Error(__VA_ARGS__) +#define LOG_WARNING(...) AlphaRing::Log::Warn(__VA_ARGS__) +#define LOG_DEBUG(...) AlphaRing::Log::Debug(__VA_ARGS__) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e4082b0..4b237b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,41 +8,69 @@ static bool Initialize() { bool result; + AlphaRing::Log::Early("Initialize: starting..."); + result = AlphaRing::Log::Init(); - assertm(result, "failed to initialize log"); + if (!result) { + AlphaRing::Log::Early("Initialize: FAILED to init log"); + return false; + } + + AlphaRing::Log::Early("Initialize: log init done, testing LOG_INFO..."); + LOG_INFO("Log initialized."); + AlphaRing::Log::Early("Initialize: LOG_INFO works"); + AlphaRing::Log::Early("Initialize: calling Hook::Initialize..."); result = AlphaRing::Hook::Initialize(); - assertm(result, "failed to initialize hook"); + if (!result) { + AlphaRing::Log::Early("Initialize: FAILED Hook::Initialize"); + LOG_ERROR("Failed to initialize hook"); + return false; + } - //LOG_INFO("Initialized AlphaRing."); + AlphaRing::Log::Early("Initialize: Hook done"); result = AlphaRing::Filesystem::Init(); - assertm(result, "failed to initialize filesystem"); + if (!result) { + AlphaRing::Log::Early("Initialize: FAILED Filesystem::Init"); + return false; + } - //LOG_INFO("Initialized filesystem."); + AlphaRing::Log::Early("Initialize: Filesystem done"); result = AlphaRing::Input::Init(); - assertm(result, "failed to initialize input"); + if (!result) { + AlphaRing::Log::Early("Initialize: FAILED Input::Init"); + LOG_ERROR("Failed to initialize input"); + return false; + } - //LOG_INFO("Initialized input."); + AlphaRing::Log::Early("Initialize: Input done"); result = AlphaRing::Render::Initialize(); - assertm(result, "failed to initialize render"); + if (!result) { + AlphaRing::Log::Early("Initialize: FAILED Render::Initialize"); + LOG_ERROR("Failed to initialize render"); + return false; + } - //LOG_INFO("Initialized render."); + AlphaRing::Log::Early("Initialize: Render done"); result = MCC::Initialize(); - assertm(result, "failed to initialize mcc"); - - //LOG_INFO("Initialized mcc."); + if (!result) { + AlphaRing::Log::Early("Initialize: FAILED MCC::Initialize"); + LOG_ERROR("Failed to initialize MCC"); + return false; + } - //LOG_INFO("Game Version[{}]: {}", AlphaRing::Hook::IsWS() ? "Windows Store" : "Steam", GAME_VERSION); + AlphaRing::Log::Early("Initialize: ALL DONE"); + LOG_INFO("AlphaRing fully initialized."); return true; } @@ -60,8 +88,10 @@ static bool Shutdown() { BOOL APIENTRY DllMain(HANDLE handle, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) { + AlphaRing::Log::Early("DllMain: DLL_PROCESS_ATTACH"); CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)Initialize, nullptr, 0, nullptr); } else if (reason == DLL_PROCESS_DETACH) { + AlphaRing::Log::Early("DllMain: DLL_PROCESS_DETACH"); if (reserved == nullptr) return Shutdown(); } diff --git a/src/mcc/CGameManagerSplitscreen.cpp b/src/mcc/CGameManagerSplitscreen.cpp index 1912629..40dfdaa 100644 --- a/src/mcc/CGameManagerSplitscreen.cpp +++ b/src/mcc/CGameManagerSplitscreen.cpp @@ -31,6 +31,9 @@ bool CGameManager::get_xbox_user_id(CGameManager *self, __int64 *pId, wchar_t *p if (!p_setting->b_override || !index) return ppOriginal.get_xbox_user_id(self, pId, pName, size, index); + static bool logged = false; + if (!logged) { LOG_INFO("Splitscreen get_xbox_user_id: index={}, id={:x}", index, p_profile->id); logged = true; } + if (index >= p_setting->player_count) return false; @@ -65,6 +68,12 @@ bool CGameManager::get_key_state(CGameManager *self, DWORD index, input_data_t * if (!p_profile->b_override) return ppOriginal.get_key_state(self, index, p_input); + static bool logged_once = false; + if (!logged_once) { + LOG_INFO("Splitscreen get_key_state: player_count={}, index={}", p_profile->player_count, index); + logged_once = true; + } + if (index >= p_profile->player_count) return false; diff --git a/src/mcc/CUserProfile.cpp b/src/mcc/CUserProfile.cpp index cb4ca37..f2c85b7 100644 --- a/src/mcc/CUserProfile.cpp +++ b/src/mcc/CUserProfile.cpp @@ -3,6 +3,7 @@ #include "CGameEngine.h" #include "imgui.h" +#include #include void CUserProfile::ImGuiContext() { diff --git a/src/mcc/module/entry/groundhog/engine.cpp b/src/mcc/module/entry/groundhog/engine.cpp index b6fc14e..3d65d84 100644 --- a/src/mcc/module/entry/groundhog/engine.cpp +++ b/src/mcc/module/entry/groundhog/engine.cpp @@ -5,14 +5,13 @@ // todo: Scheduler namespace GroundHog::Entry::Engine { void Prologue() { - LOG_INFO("Engine Prologue"); // open access to main thread resources // main thread resources will be copied to the render thread - GroundHog::Native::s_nativeInfo.update("groundhog.dll"); + static __int64 hModule = (__int64)GetModuleHandleA("groundhog.dll"); + GroundHog::Native::s_nativeInfo.update(hModule); } void Epilogue() { - LOG_INFO("Engine Epilogue"); } GroundHogEntry(entry, OFFSET_GROUNDHOG_PF_ENGINE, void, detour) { diff --git a/src/mcc/module/entry/groundhog/world.cpp b/src/mcc/module/entry/groundhog/world.cpp index ee046f1..fff1ba8 100644 --- a/src/mcc/module/entry/groundhog/world.cpp +++ b/src/mcc/module/entry/groundhog/world.cpp @@ -5,20 +5,20 @@ #include namespace GroundHog::Entry::World { - std::mutex tasks_mutex; + WinMutex tasks_mutex; std::queue> tasks; void AddTask(const std::function& func) { - std::unique_lock lock(tasks_mutex); + std::lock_guard lock(tasks_mutex); tasks.push(func); } void ExecuteTask() { std::function func = nullptr; - std::unique_lock lock(tasks_mutex); + { std::lock_guard lock(tasks_mutex); if (!tasks.empty()) { func = tasks.front();tasks.pop();} - lock.unlock(); + } if (func != nullptr) func(); } diff --git a/src/mcc/module/entry/halo3/engine.cpp b/src/mcc/module/entry/halo3/engine.cpp index 07b4575..3d91465 100644 --- a/src/mcc/module/entry/halo3/engine.cpp +++ b/src/mcc/module/entry/halo3/engine.cpp @@ -4,7 +4,8 @@ namespace Halo3::Entry::Engine { Halo3Entry(entry, OFFSET_HALO3_PF_ENGINE, void, detour) { // open access to main thread resources // main thread resources will be copied to the render thread - Halo3::Native::s_nativeInfo.update("halo3.dll"); + static __int64 hModule = (__int64)GetModuleHandleA("halo3.dll"); + Halo3::Native::s_nativeInfo.update(hModule); ((detour_t)entry.m_pOriginal)(); } } \ No newline at end of file diff --git a/src/mcc/module/entry/halo3/simulation.cpp b/src/mcc/module/entry/halo3/simulation.cpp index 5506da5..831c642 100644 --- a/src/mcc/module/entry/halo3/simulation.cpp +++ b/src/mcc/module/entry/halo3/simulation.cpp @@ -6,12 +6,12 @@ #include #include -static std::mutex g_mutex1; +static WinMutex g_mutex1; static bool bTAS = false; static bool bRun = false; static int run_tick = 0; -static std::mutex g_mutex; +static WinMutex g_mutex; static bool bCapture = false; static bool bPlayback = false; @@ -28,7 +28,7 @@ static void SaveFileHandler(); namespace Halo3::Entry::Simulation { Halo3Entry(entry1, 0xEEFA8/*0xEEC5C*/, __int64, detour1, int tick, float* a2) { { - std::lock_guard g_lock(g_mutex1); + std::lock_guard g_lock(g_mutex1); if (bTAS) { tick = 0; if (bRun) { @@ -41,7 +41,7 @@ namespace Halo3::Entry::Simulation { } void ImGuiContext1() { - std::lock_guard g_lock(g_mutex1); + std::lock_guard g_lock(g_mutex1); ImGui::PushItemWidth(200.0f); ImGui::Checkbox("TAS", &bTAS); @@ -77,7 +77,7 @@ namespace Halo3::Entry::Simulation { // insert new node { - std::lock_guard g_lock(g_mutex); + std::lock_guard g_lock(g_mutex); if (head == nullptr) head = node; if (tail != nullptr) { tail->next = node; @@ -87,7 +87,7 @@ namespace Halo3::Entry::Simulation { ++total_tick; } } else if (bPlayback) { - std::lock_guard g_lock(g_mutex); + std::lock_guard g_lock(g_mutex); if (current != nullptr) { memcpy(control_data, ¤t->data, sizeof(unit_control_definition)); if (current->next != nullptr) { @@ -105,7 +105,7 @@ namespace Halo3::Entry::Simulation { void ImGuiContext() { if (ImGui::CollapsingHeader("Simulation") == false) return; - std::lock_guard g_lock(g_mutex); + std::lock_guard g_lock(g_mutex); auto status = "Idle"; if (bCapture) status = "Capture"; diff --git a/src/mcc/module/entry/halo3/world.cpp b/src/mcc/module/entry/halo3/world.cpp index db1e741..3d32553 100644 --- a/src/mcc/module/entry/halo3/world.cpp +++ b/src/mcc/module/entry/halo3/world.cpp @@ -6,20 +6,20 @@ #include namespace Halo3::Entry::World { - std::mutex tasks_mutex; + WinMutex tasks_mutex; std::queue> tasks; void AddTask(const std::function& func) { - std::unique_lock lock(tasks_mutex); + std::lock_guard lock(tasks_mutex); tasks.push(func); } void ExecuteTask() { std::function func = nullptr; - std::unique_lock lock(tasks_mutex); + { std::lock_guard lock(tasks_mutex); if (!tasks.empty()) { func = tasks.front();tasks.pop();} - lock.unlock(); + } if (func != nullptr) func(); } diff --git a/src/mcc/module/entry/halo3odst/engine.cpp b/src/mcc/module/entry/halo3odst/engine.cpp index 9dc2297..610e48d 100644 --- a/src/mcc/module/entry/halo3odst/engine.cpp +++ b/src/mcc/module/entry/halo3odst/engine.cpp @@ -6,14 +6,13 @@ namespace Halo3ODST::Entry::Engine { void Prologue() { - LOG_INFO("Engine Prologue"); // open access to main thread resources // main thread resources will be copied to the render thread - Halo3ODST::Native::s_nativeInfo.update("halo3odst.dll"); + static __int64 hModule = (__int64)GetModuleHandleA("halo3odst.dll"); + Halo3ODST::Native::s_nativeInfo.update(hModule); } void Epilogue() { - LOG_INFO("Engine Epilogue"); } Halo3ODSTEntry(entry, OFFSET_HALO3ODST_PF_ENGINE, void, detour) { diff --git a/src/mcc/module/entry/halo3odst/world.cpp b/src/mcc/module/entry/halo3odst/world.cpp index 9c079af..9fca8e6 100644 --- a/src/mcc/module/entry/halo3odst/world.cpp +++ b/src/mcc/module/entry/halo3odst/world.cpp @@ -5,21 +5,21 @@ #include namespace Halo3ODST::Entry::World { - std::mutex tasks_mutex; + WinMutex tasks_mutex; std::queue> tasks; void ExecuteTask() { std::function func = nullptr; - std::unique_lock lock(tasks_mutex); + { std::lock_guard lock(tasks_mutex); if (!tasks.empty()) { func = tasks.front();tasks.pop();} - lock.unlock(); + } if (func != nullptr) func(); } void AddTask(const std::function& func) { - std::unique_lock lock(tasks_mutex); + std::lock_guard lock(tasks_mutex); tasks.push(func); } diff --git a/src/mcc/module/entry/halo4/engine.cpp b/src/mcc/module/entry/halo4/engine.cpp index 2080e5c..74ad878 100644 --- a/src/mcc/module/entry/halo4/engine.cpp +++ b/src/mcc/module/entry/halo4/engine.cpp @@ -5,14 +5,13 @@ // todo: Scheduler namespace Halo4::Entry::Engine { void Prologue() { - LOG_INFO("Engine Prologue"); // open access to main thread resources // main thread resources will be copied to the render thread - Halo4::Native::s_nativeInfo.update("halo4.dll"); + static __int64 hModule = (__int64)GetModuleHandleA("halo4.dll"); + Halo4::Native::s_nativeInfo.update(hModule); } void Epilogue() { - LOG_INFO("Engine Epilogue"); } Halo4Entry(entry, OFFSET_HALO4_PF_ENGINE, void, detour) { diff --git a/src/mcc/module/entry/halo4/world.cpp b/src/mcc/module/entry/halo4/world.cpp index 5e59bac..4abde43 100644 --- a/src/mcc/module/entry/halo4/world.cpp +++ b/src/mcc/module/entry/halo4/world.cpp @@ -5,20 +5,20 @@ #include namespace Halo4::Entry::World { - std::mutex tasks_mutex; + WinMutex tasks_mutex; std::queue> tasks; void AddTask(const std::function& func) { - std::unique_lock lock(tasks_mutex); + std::lock_guard lock(tasks_mutex); tasks.push(func); } void ExecuteTask() { std::function func = nullptr; - std::unique_lock lock(tasks_mutex); + { std::lock_guard lock(tasks_mutex); if (!tasks.empty()) { func = tasks.front();tasks.pop();} - lock.unlock(); + } if (func != nullptr) func(); } diff --git a/src/mcc/module/entry/haloreach/engine.cpp b/src/mcc/module/entry/haloreach/engine.cpp index 656d0e0..400f335 100644 --- a/src/mcc/module/entry/haloreach/engine.cpp +++ b/src/mcc/module/entry/haloreach/engine.cpp @@ -5,13 +5,12 @@ // todo: Scheduler namespace HaloReach::Entry::Engine { void Prologue() { - LOG_INFO("Engine Prologue"); // open access to main thread resources // main thread resources will be copied to the render thread - HaloReach::Native::s_nativeInfo.update("haloreach.dll"); + static __int64 hModule = (__int64)GetModuleHandleA("haloreach.dll"); + HaloReach::Native::s_nativeInfo.update(hModule); } void Epilogue() { - LOG_INFO("Engine Epilogue"); } HaloReachEntry(entry, OFFSET_HALOREACH_PF_ENGINE, void, detour) { diff --git a/src/mcc/module/entry/haloreach/world.cpp b/src/mcc/module/entry/haloreach/world.cpp index 42d82fb..70ac307 100644 --- a/src/mcc/module/entry/haloreach/world.cpp +++ b/src/mcc/module/entry/haloreach/world.cpp @@ -5,20 +5,20 @@ #include namespace HaloReach::Entry::World { - std::mutex tasks_mutex; + WinMutex tasks_mutex; std::queue> tasks; void AddTask(const std::function& func) { - std::unique_lock lock(tasks_mutex); + std::lock_guard lock(tasks_mutex); tasks.push(func); } void ExecuteTask() { std::function func = nullptr; - std::unique_lock lock(tasks_mutex); + { std::lock_guard lock(tasks_mutex); if (!tasks.empty()) { func = tasks.front();tasks.pop();} - lock.unlock(); + } if (func != nullptr) func(); } diff --git a/src/mcc/network/Network.cpp b/src/mcc/network/Network.cpp index 8f5b528..72a7eb1 100644 --- a/src/mcc/network/Network.cpp +++ b/src/mcc/network/Network.cpp @@ -1,7 +1,7 @@ #include "Network.h" -#include #include +#include #include #include #include @@ -9,7 +9,7 @@ #include "common.h" namespace MCC::Network { - std::mutex mutex; + WinMutex mutex; bool b_enable_capture; std::vector request_list; std::unordered_map map_url; @@ -17,7 +17,7 @@ namespace MCC::Network { void set_url(HINTERNET hConnect, LPCWSTR url) { if (!b_enable_capture) return; - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); map_url[hConnect] = url; } @@ -26,7 +26,7 @@ namespace MCC::Network { if (!b_enable_capture) return false; - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); auto it = map_request.find(hRequest); if (it == map_request.end()) return false; @@ -41,7 +41,7 @@ namespace MCC::Network { if (!b_enable_capture) return false; - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); auto it = map_request.find(hRequest); if (it == map_request.end()) return false; @@ -57,7 +57,7 @@ namespace MCC::Network { if (!b_enable_capture) return false; - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); auto it = map_request.find(hRequest); if (it == map_request.end()) return false; @@ -69,7 +69,7 @@ namespace MCC::Network { } void clear() { - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); for (auto request: request_list) { delete request; @@ -85,7 +85,7 @@ namespace MCC::Network { if (!b_enable_capture) return; - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); sprintf(buffer, "%ls", method); @@ -110,7 +110,7 @@ namespace MCC::Network { } void close(HINTERNET hRequest) { - std::lock_guard lock(mutex); + std::lock_guard lock(mutex); auto it = map_request.find(hRequest); if (it == map_request.end()) return; diff --git a/src/mcc/splitscreen/Splitscreen.cpp b/src/mcc/splitscreen/Splitscreen.cpp index 54a0c6a..08f1240 100644 --- a/src/mcc/splitscreen/Splitscreen.cpp +++ b/src/mcc/splitscreen/Splitscreen.cpp @@ -15,6 +15,9 @@ namespace MCC::Splitscreen { if (!p_setting->b_override) return ppOriginal_get_index_by_xuid(a1, xuid); + static bool logged = false; + if (!logged) { LOG_INFO("Splitscreen: get_index_by_xuid override, xuid={:x}", xuid); logged = true; } + return CGameManager::get_index(xuid); } diff --git a/src/render/d3d11/D3d11.cpp b/src/render/d3d11/D3d11.cpp index 3c4d4c0..1814c4e 100644 --- a/src/render/d3d11/D3d11.cpp +++ b/src/render/d3d11/D3d11.cpp @@ -117,6 +117,9 @@ namespace AlphaRing::Render::D3d11 { hD3d11 = GetModuleHandle("d3d11.dll"); + if (hD3d11 == nullptr) { + LOG_ERROR("Failed to find module d3d11.dll (error {})", GetLastError()); + } assertm(hD3d11 != nullptr, "failed to find module \"d3d11.dll\""); p_fD3D11CreateDeviceAndSwapChain = (decltype(p_fD3D11CreateDeviceAndSwapChain)) GetProcAddress( diff --git a/src/wrapper/module_definition.cpp b/src/wrapper/module_definition.cpp index 4b2fe9b..6aa6bc9 100644 --- a/src/wrapper/module_definition.cpp +++ b/src/wrapper/module_definition.cpp @@ -1,11 +1,15 @@ #include "module_definition.h" #include +#include "log/Log.h" ModuleDefinition::ModuleDefinition(const char *moduleName, std::initializer_list funcs) { + AlphaRing::Log::Early("ModuleDefinition: loading wrapper for '%s'", moduleName); + // Get System Directory wchar_t systemPath[MAX_PATH]; if (!GetSystemDirectoryW(systemPath, MAX_PATH)) { + AlphaRing::Log::Early("ERROR: GetSystemDirectoryW failed (error %lu)", GetLastError()); MessageBoxA(0, "Unable to load system directory", "Error", 0); ExitProcess(0); @@ -14,23 +18,29 @@ ModuleDefinition::ModuleDefinition(const char *moduleName, std::initializer_list // Load DLL std::filesystem::path path(systemPath); path.append(moduleName); + AlphaRing::Log::Early("ModuleDefinition: loading real DLL from '%ls'", path.c_str()); if ((m_hModule = LoadLibraryW(path.c_str())) == nullptr) { + AlphaRing::Log::Early("ERROR: LoadLibraryW failed for '%s' (error %lu)", moduleName, GetLastError()); MessageBoxA(0, (std::string("Unable to load dll: ") + moduleName).c_str(), "Error", 0); ExitProcess(0); } + AlphaRing::Log::Early("ModuleDefinition: real DLL loaded at %p", (void*)m_hModule); // Find Function void *func_ptr; for (auto func: funcs) { if ((func_ptr = GetProcAddress(m_hModule, func)) == nullptr) { + AlphaRing::Log::Early("ERROR: GetProcAddress failed for '%s' (error %lu)", func, GetLastError()); MessageBoxA(0, (std::string("Unable to load function: ") + func).c_str(), "Error", 0); ExitProcess(0); } + AlphaRing::Log::Early("ModuleDefinition: resolved '%s' -> %p", func, func_ptr); m_funcs.push_back(func_ptr); } + AlphaRing::Log::Early("ModuleDefinition: wrapper ready (%zu functions)", m_funcs.size()); } void *ModuleDefinition::GetFunc(int index) {