This repository has been archived by the owner on May 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9497d58
Showing
4 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
@echo off | ||
cd "%~dp0" | ||
g++ -std=c++20 -static -Os -s -shared -municode DllMain.cpp -lOleAut32 -lRuntimeObject -o dpapi.dll | ||
upx --best --ultra-brute dpapi.dll>nul 2>&1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
#include <windows.h> | ||
#include <winrt/Windows.Data.Json.h> | ||
using namespace winrt::Windows::Data::Json; | ||
|
||
static BOOL fFullscreen = FALSE; | ||
static DWORD dmPelsWidth = 0, dmPelsHeight = 0, dmDisplayFrequency = 0; | ||
static WNDPROC lpPrevWndFunc = NULL; | ||
|
||
static LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) | ||
{ | ||
switch (uMsg) | ||
{ | ||
case WM_MOUSEACTIVATE: | ||
return MA_NOACTIVATEANDEAT; | ||
|
||
case WM_CLOSE: | ||
return TerminateProcess(GetCurrentProcess(), 0); | ||
|
||
case WM_WINDOWPOSCHANGED: | ||
return lpPrevWndFunc(NULL, WM_SIZE, SIZE_RESTORED, 0); | ||
|
||
case WM_STYLECHANGING: | ||
((LPSTYLESTRUCT)lParam)->styleNew = wParam == GWL_STYLE | ||
? IsWindowVisible(hWnd) ? WS_VISIBLE | WS_POPUP : WS_POPUP | ||
: WS_EX_APPWINDOW | WS_EX_NOACTIVATE; | ||
break; | ||
|
||
case WM_NCACTIVATE: | ||
case WM_WINDOWPOSCHANGING: | ||
wParam = hWnd == GetForegroundWindow(); | ||
DEVMODEW dm = {.dmSize = sizeof(DEVMODEW), | ||
.dmFields = DM_DISPLAYORIENTATION | DM_DISPLAYFIXEDOUTPUT | DM_PELSWIDTH | DM_PELSHEIGHT | | ||
DM_DISPLAYFREQUENCY, | ||
.dmDisplayOrientation = DMDO_DEFAULT, | ||
.dmDisplayFixedOutput = DMDFO_DEFAULT, | ||
.dmPelsWidth = dmPelsWidth, | ||
.dmPelsHeight = dmPelsHeight, | ||
.dmDisplayFrequency = dmDisplayFrequency}; | ||
|
||
if (ChangeDisplaySettingsW(&dm, CDS_TEST)) | ||
{ | ||
EnumDisplaySettingsW(NULL, ENUM_REGISTRY_SETTINGS, &dm); | ||
if (dm.dmDisplayOrientation == DMDO_90 || dm.dmDisplayOrientation == DMDO_270) | ||
dm = {.dmSize = sizeof(DEVMODEW), .dmPelsWidth = dm.dmPelsHeight, .dmPelsHeight = dm.dmPelsWidth}; | ||
dm.dmDisplayOrientation = DMDO_DEFAULT; | ||
dm.dmDisplayFixedOutput = DMDFO_DEFAULT; | ||
dm.dmFields = | ||
DM_DISPLAYORIENTATION | DM_DISPLAYFIXEDOUTPUT | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; | ||
} | ||
|
||
if (uMsg == WM_NCACTIVATE) | ||
{ | ||
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, 0); | ||
ChangeDisplaySettingsW(wParam ? &dm : NULL, CDS_FULLSCREEN); | ||
} | ||
else | ||
*((PWINDOWPOS)lParam) = {.hwnd = hWnd, | ||
.hwndInsertAfter = wParam ? HWND_TOPMOST : HWND_BOTTOM, | ||
.x = 0, | ||
.y = 0, | ||
.cx = (int)dm.dmPelsWidth, | ||
.cy = (int)dm.dmPelsHeight, | ||
.flags = SWP_ASYNCWINDOWPOS | SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_SHOWWINDOW}; | ||
} | ||
return DefWindowProcW(hWnd, uMsg, wParam, lParam); | ||
} | ||
|
||
static VOID WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, | ||
DWORD dwEventThread, DWORD dwmsEventTime) | ||
{ | ||
WCHAR szClassName[256] = {}; | ||
GetClassNameW(hwnd, szClassName, 256); | ||
|
||
if (CompareStringOrdinal(szClassName, -1, L"Halo Infinite", -1, FALSE) == CSTR_EQUAL || | ||
CompareStringOrdinal(szClassName, -1, L"CampaignS1", -1, FALSE) == CSTR_EQUAL) | ||
{ | ||
if (fFullscreen) | ||
{ | ||
(lpPrevWndFunc = (WNDPROC)SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)WndProc))(NULL, WM_ACTIVATEAPP, | ||
TRUE, 0); | ||
SetWindowLongPtrW(hwnd, GWL_STYLE, WS_OVERLAPPED); | ||
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, WS_EX_LEFT | WS_EX_LTRREADING); | ||
SetClassLongPtrW(hwnd, GCLP_HCURSOR, (LONG_PTR)LoadCursorW(NULL, IDC_ARROW)); | ||
SetForegroundWindow(hwnd); | ||
} | ||
|
||
HANDLE hThread = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, dwEventThread); | ||
SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST); | ||
CloseHandle(hThread); | ||
|
||
PostQuitMessage(0); | ||
} | ||
} | ||
|
||
static DWORD ThreadProc(LPVOID lpParameter) | ||
{ | ||
MSG msg = {}; | ||
SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, NULL, WinEventProc, GetCurrentProcessId(), 0, | ||
WINEVENT_OUTOFCONTEXT); | ||
while (GetMessageW(&msg, NULL, 0, 0)) | ||
; | ||
return 0; | ||
} | ||
|
||
BOOL DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) | ||
{ | ||
if (fdwReason == DLL_PROCESS_ATTACH) | ||
{ | ||
WCHAR szFileName[MAX_PATH] = {}; | ||
ExpandEnvironmentStringsW(L"%LOCALAPPDATA%\\HaloInfinite\\Settings\\ZetaLoader.ini", szFileName, MAX_PATH); | ||
|
||
if ((fFullscreen = GetPrivateProfileIntW(L"Settings", L"Fullscreen", FALSE, szFileName) == TRUE)) | ||
{ | ||
if (!(dmPelsWidth = GetPrivateProfileIntW(L"Settings", L"Width", -1, szFileName)) || | ||
!(dmPelsHeight = GetPrivateProfileIntW(L"Settings", L"Height", -1, szFileName)) || | ||
!(dmDisplayFrequency = GetPrivateProfileIntW(L"Settings", L"Frequency", -1, szFileName))) | ||
dmDisplayFrequency = -1; | ||
|
||
ExpandEnvironmentStringsW(L"%LOCALAPPDATA%\\HaloInfinite\\Settings\\SpecControlSettings.json", szFileName, | ||
MAX_PATH); | ||
HANDLE hFile = CreateFile2(szFileName, FILE_READ_DATA | FILE_WRITE_DATA, 0, OPEN_ALWAYS, NULL); | ||
|
||
if (hFile != INVALID_HANDLE_VALUE) | ||
{ | ||
JsonObject pSpecControlSettings = NULL, pSpecControlWindowedDisplayResolutionX = JsonObject(), | ||
pSpecControlWindowedDisplayResolutionY = JsonObject(), pSpecControlWindowMode = JsonObject(); | ||
JsonValue pJsonValue = JsonValue::CreateNumberValue(0); | ||
|
||
pSpecControlWindowedDisplayResolutionX.SetNamedValue(L"version", pJsonValue); | ||
pSpecControlWindowedDisplayResolutionX.SetNamedValue(L"value", JsonValue::CreateNumberValue(1024)); | ||
pSpecControlWindowedDisplayResolutionY.SetNamedValue(L"version", pJsonValue); | ||
pSpecControlWindowedDisplayResolutionY.SetNamedValue(L"value", JsonValue::CreateNumberValue(768)); | ||
pSpecControlWindowMode.SetNamedValue(L"version", pJsonValue); | ||
pSpecControlWindowMode.SetNamedValue(L"value", pJsonValue); | ||
|
||
INT cbMultiByte = GetFileSize(hFile, NULL); | ||
LPSTR lpMultiByteStr = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMultiByte + 1); | ||
|
||
ReadFile(hFile, lpMultiByteStr, cbMultiByte, NULL, NULL); | ||
if (!JsonObject::TryParse(winrt::to_hstring(lpMultiByteStr), pSpecControlSettings)) | ||
pSpecControlSettings = JsonObject(); | ||
|
||
pSpecControlSettings.TryRemove(L"spec_control_window_size"); | ||
pSpecControlSettings.TryRemove(L"spec_control_resolution_scale"); | ||
|
||
pSpecControlSettings.SetNamedValue(L"spec_control_windowed_display_resolution_x", | ||
pSpecControlWindowedDisplayResolutionX); | ||
pSpecControlSettings.SetNamedValue(L"spec_control_windowed_display_resolution_y", | ||
pSpecControlWindowedDisplayResolutionY); | ||
pSpecControlSettings.SetNamedValue(L"spec_control_window_mode", pSpecControlWindowMode); | ||
|
||
winrt::hstring sWideCharStr = pSpecControlSettings.Stringify(); | ||
cbMultiByte = WideCharToMultiByte(CP_UTF8, 0, sWideCharStr.c_str(), -1, NULL, 0, NULL, NULL); | ||
WideCharToMultiByte(CP_UTF8, 0, sWideCharStr.c_str(), -1, | ||
lpMultiByteStr = (LPSTR)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, | ||
lpMultiByteStr, cbMultiByte), | ||
cbMultiByte, NULL, NULL); | ||
|
||
SetFilePointer(hFile, 0, NULL, FILE_BEGIN); | ||
SetEndOfFile(hFile); | ||
WriteFile(hFile, lpMultiByteStr, cbMultiByte - 1, NULL, NULL); | ||
HeapFree(GetProcessHeap(), 0, lpMultiByteStr); | ||
CloseHandle(hFile); | ||
} | ||
} | ||
|
||
DisableThreadLibraryCalls(hinstDLL); | ||
CloseHandle(CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL)); | ||
} | ||
return TRUE; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Aetopia | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
> [!WARNING] | ||
> ZetaLoader will be archived since [CU31](https://support.halowaypoint.com/hc/en-us/articles/24540901669780-Halo-Infinite-Content-Update-31-Patch-Notes) introduces Easy Anti-Cheat which blocks the modification from loading. | ||
# ZetaLoader | ||
|
||
A modification to fix technical issues with Halo Infinite on PC. | ||
|
||
## Features | ||
#### Borderless Fullscreen | ||
|
||
> [!WARNING] | ||
> ZetaLoader's Borderless Fullscreen renders the following options unusable:<br> | ||
> - Display Monitor | ||
> - Limit Inactive Framerate | ||
> - Inactive Mute | ||
> - V-Sync | ||
> - Maximum Framerate | ||
> | ||
> Because of this consider using alternatives for the following: | ||
> |Option|Alternative| | ||
> |-|-| | ||
> |V-Sync|Driver Based V-Sync| | ||
> |Maximum Framerate|External Framerate Limiter| | ||
> |Limit Inactive Framerate|Background Application Max Frame Rate (**NVIDIA Only!**)| | ||
> [!IMPORTANT] | ||
> - ZetaLoader's Borderless Fullscreen forces the game's window to be always on the primary monitor.<br> | ||
> - To activate the game's window either click on its taskbar button or use <kbd>Alt</kbd> + <kbd>Tab</kbd>. | ||
ZetaLoader overrides Halo Infinite's Borderless Fullscreen/window style, if its Borderless Fullscreen implementation is enabled. | ||
Halo Infinite uses the following styles `WS_VISIBLE | WS_OVERLAPPED | WS_CLIPSIBLINGS` for its Borderless Fullscreen implementation. | ||
|
||
The window style `WS_OVERLAPPED` doesn't fill the screen area correctly if the display mode is below `1440x810` resulting in the window to extend its client area beyond the screen area itself. | ||
|
||
This is simply fixed by using the following attributes: | ||
- **Window Styles - `WS_VISIBLE | WS_POPUP`:** | ||
- Fixes incorrect window sizing caused by `WS_OVERLAPPED`. | ||
|
||
- **Extended Window Styles - `WS_EX_APPWINDOW | WS_EX_NOACTIVATE`**: | ||
- Force the window to appear onto the taskbar. | ||
- Prevent window activation due to a mouse click or a window being minimized or closed. | ||
|
||
- **Z-Order**:<br> | ||
- **`HWND_TOPMOST`**: The game's window becomes always on top when it is in the foreground. | ||
- **`HWND_BOTTOM`**: The game's window drops to the bottom if it is not in the foreground. | ||
|
||
|
||
|
||
#### User Specified Display Mode | ||
> [!NOTE] | ||
> User Specified Display Mode Demonstration: https://www.youtube.com/watch?v=FnzN4xTO6UA | ||
> [!TIP] | ||
> - ZetaLoader's Borderless Fullscreen must be enabled to use this feature. | ||
> - Adjust Halo Infinite's UI's **`Text Size`**. | ||
> - In your GPU's Control Panel set the Scaling Mode to **`Fullscreen`**│**`Stretched`**. | ||
> - User Specified Display Mode handles display modes as follows:<br> | ||
> - Only Landscape orientation based display modes can be used. | ||
> - If no display mode is specified, the display mode stored in the Windows Registry will be used. | ||
User Specified Display Mode provides Halo Infinite with the facility to have the game's window run at any arbitrary display mode of the user's choice as long as it is valid. | ||
|
||
|
||
|
||
#### Jittery Mouse Input Fix | ||
> [!IMPORTANT] | ||
> Setting the game's process priority to `High` will negate this fix. | ||
> Reference: https://learn.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities | ||
> Issue + Fix Demonstration: https://www.youtube.com/watch?v=4pJd-dKW7WY | ||
Setting the game's window thread priority to `THREAD_PRIORITY_HIGHEST` resolves jittery mouse input when an external framerate limiter is being used by giving the game's window thread enough of a timeslice. <br> | ||
You can verify if this works by using a lower thread priority & setting it using the function `SetThreadPriority` in the source code. | ||
|
||
## Installation | ||
### Automated | ||
Run the following script in PowerShell to install ZetaLoader for Halo Infinite's Campaign and Multiplayer:<br> | ||
|
||
```powershell | ||
$ProgressPreference = $ErrorActionPreference = "SilentlyContinue" | ||
Get-Content "$(Split-Path $(([string]((Get-ItemPropertyValue ` | ||
-Path "Registry::HKEY_CLASSES_ROOT\steam\Shell\Open\Command" ` | ||
-Name "(Default)") ` | ||
-Split "-", 2, "SimpleMatch")[0]).Trim().Trim('"')))\config\libraryfolders.vdf" | | ||
ForEach-Object { | ||
if ($_ -like '*"path"*') { | ||
[string]$Path = "$(([string]$_).Trim().Trim('"path"').Trim().Trim('"').Replace("\\", "\"))\steamapps\common\Halo Infinite" | ||
if (Test-Path $Path) { | ||
Write-Host "ZetaLoader Installation Status:" -ForegroundColor Yellow | ||
try { | ||
Invoke-RestMethod ` | ||
-Uri "$((Invoke-RestMethod "https://api.github.com/repos/Aetopia/ZetaLoader/releases/latest").assets[0].browser_download_url)" ` | ||
-OutFile "$Path\game\dpapi.dll" | ||
Write-Host "`tMultiplayer: Success" -ForegroundColor Green | ||
} | ||
catch { Write-Host "`tMultiplayer: Failed" -ForegroundColor Red } | ||
try { | ||
Copy-Item "$Path\game\dpapi.dll" "$Path\subgames\CampaignS1\dpapi.dll" | ||
Write-Host "`tCampaign: Success" -ForegroundColor Green | ||
} | ||
catch { Write-Host "`tCampaign: Failed" -ForegroundColor Red } | ||
} | ||
} | ||
} | ||
$ProgressPreference = $ErrorActionPreference = "Continue" | ||
``` | ||
|
||
### Manual | ||
1. Download the latest version of ZetaLoader from [GitHub Releases](https://github.com/Aetopia/ZetaLoader/releases/latest). | ||
2. Open Halo Infinite's installation directory. | ||
3. Place the dynamic link library in the following folders:<br> | ||
- Multiplayer: `<Installation Directory>\game` | ||
- Campaign: `<Installation Directory>\subgames\CampaignS1` | ||
|
||
### Uninstallation | ||
Simply remove any instances of `dpapi.dll` from Halo Infinite's installation directory. | ||
|
||
## Configuration | ||
|
||
> [!IMPORTANT] | ||
> You must restart the game for any configuration file changes to reflect. | ||
To configure ZetaLoader, do the following: | ||
1. Go to the following directory: | ||
- `%LOCALAPPDATA%\HaloInfinite\Settings` | ||
2. Create a new file called `ZetaLoader.ini`, this is ZetaLoader's configuration file. | ||
3. Add the following contents into the file: | ||
```ini | ||
[Settings] | ||
Fullscreen = 0 | ||
Width = 0 | ||
Height = 0 | ||
Frequency = 0 | ||
``` | ||
|
||
|Key|Value| | ||
|-|-| | ||
|`Fullscreen`|<ul><li>`0` → Halo Infinite's Window Mode</li><li>`1` → ZetaLoader's Borderless Fullscreen</li></ol>| | ||
|`Width`|Display Resolution Width| | ||
|`Height`|Display Resolution Height| | ||
|`Frequency`|Display Refresh Rate| | ||
|
||
This will make Halo Infinite run `1360`x`768` @ `60` Hz with ZetaLoader's Borderless Fullscreen:<br> | ||
```ini | ||
[Settings] | ||
Fullscreen = 1 | ||
Width = 1360 | ||
Height = 768 | ||
Frequency = 60 | ||
``` | ||
## Building | ||
1. Install [MSYS2](https://www.msys2.org/) & [UPX](https://upx.github.io/) for optional compression. | ||
2. Update the MSYS2 Environment until there are no pending updates using: | ||
```bash | ||
pacman -Syu --noconfirm | ||
``` | ||
3. Install GCC & C++/WinRT using: | ||
```bash | ||
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cppwinrt --noconfirm | ||
``` | ||
3. Make sure `<MSYS2 Installation Directory>\ucrt64\bin` is added to the Windows `PATH` environment variable. | ||
4. Run [`Build.cmd`](Build.cmd). |