Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Commit

Permalink
No commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
Aetopia committed May 13, 2024
0 parents commit 9497d58
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Build.cmd
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
171 changes: 171 additions & 0 deletions DllMain.cpp
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;
}
21 changes: 21 additions & 0 deletions LICENSE
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.
167 changes: 167 additions & 0 deletions README.md
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` &rarr; Halo Infinite's Window Mode</li><li>`1` &rarr; 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).

0 comments on commit 9497d58

Please sign in to comment.