21 Commits

Author SHA1 Message Date
neru 9f5abefb34 docs: add images and sources
Build / build (push) Successful in 9m28s
2026-06-20 09:18:59 -03:00
neru f6ac12962d fix: remove unneeded def 2026-06-20 09:18:29 -03:00
neru 6d176c2020 build: fix weird compile bug
Build / build (push) Successful in 9m23s
2026-06-20 08:25:13 -03:00
neru 83c1fba265 build: bring back minsizerel
Build / build (push) Failing after 6m7s
2026-06-20 08:17:03 -03:00
neru e46a60f95f feat: randomize console title
Build / build (push) Successful in 8m20s
2026-06-20 08:06:34 -03:00
neru 6c086b5307 feat: add additional logging and write to file 2026-06-20 08:05:23 -03:00
neru 064f1701ee chore: rename log-sink to log-sink-cout 2026-06-20 08:04:55 -03:00
neru df9ad4fc70 feat: add file log sink 2026-06-20 08:04:36 -03:00
neru c0b710d6f7 fix: misc building warnings
Build / build (push) Successful in 8m29s
2026-06-20 07:33:18 -03:00
neru e27fb6d73a docs: add readme
Build / build (push) Failing after 5m23s
2026-06-20 07:24:17 -03:00
neru e95308605b build: attempt to fix wolfssl build error 2026-06-20 07:15:44 -03:00
neru 205ee06c27 build: remove zip 2026-06-20 07:11:18 -03:00
neru d390304577 build: misc cmakelist fixes
Build / build (push) Failing after 4m55s
2026-06-20 07:03:01 -03:00
neru 037260acd5 style: run format 2026-06-20 06:59:39 -03:00
neru f30a76fe18 feat: add icon 2026-06-20 06:59:06 -03:00
neru 579e0d1352 feat: add mutex to prevent double runs 2026-06-20 06:53:20 -03:00
neru 26a4406c88 build: link libs for tray icon, change executable type 2026-06-20 06:53:15 -03:00
neru 385cfe147d feat: add tray icon 2026-06-20 06:53:02 -03:00
neru 98be88b346 fix: sort includes 2026-06-19 14:26:52 -03:00
neru bf83ae571d fix: undef error, include minwindef instead of windows 2026-06-19 14:26:34 -03:00
neru c1bc1023eb build: rollback cache version 2026-06-19 13:28:36 -03:00
18 changed files with 439 additions and 47 deletions
+5 -6
View File
@@ -15,7 +15,7 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
pacman -Syu --noconfirm pacman -Syu --noconfirm
pacman -S --noconfirm nodejs mingw-w64-gcc cmake ninja ccache zip git base-devel pacman -S --noconfirm nodejs mingw-w64-gcc cmake ninja ccache git base-devel
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -56,7 +56,7 @@ jobs:
} >> $GITHUB_OUTPUT } >> $GITHUB_OUTPUT
- name: Cache FetchContent & ccache - name: Cache FetchContent & ccache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: | path: |
build/_deps build/_deps
@@ -74,6 +74,7 @@ jobs:
- name: Configure CMake - name: Configure CMake
run: | run: |
cmake -B build -S . -G Ninja \ cmake -B build -S . -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_SYSTEM_NAME=Windows \ -DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \ -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \ -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \
@@ -90,14 +91,12 @@ jobs:
echo "Found EXE: $EXE" echo "Found EXE: $EXE"
mkdir -p release mkdir -p release
cp "$EXE" release/ cp "$EXE" release/
if [ -d "res" ]; then cp res/*.json release/; fi
cd release && zip -j ../hex-unlocked.zip *
- name: Upload Artifact - name: Upload Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: runner-build name: runner-build
path: hex-unlocked.zip path: release/
- name: Finalize Version and Push Tag - name: Finalize Version and Push Tag
if: ${{ github.event_name == 'push' && success() }} if: ${{ github.event_name == 'push' && success() }}
@@ -109,7 +108,7 @@ jobs:
if: ${{ github.event_name == 'push' && success() }} if: ${{ github.event_name == 'push' && success() }}
uses: akkuman/gitea-release-action@v1 uses: akkuman/gitea-release-action@v1
with: with:
files: hex-unlocked.zip files: release/*
tag_name: ${{ steps.calculate-version.outputs.new-tag }} tag_name: ${{ steps.calculate-version.outputs.new-tag }}
name: Release ${{ steps.calculate-version.outputs.new-tag }} name: Release ${{ steps.calculate-version.outputs.new-tag }}
body: ${{ steps.calculate-version.outputs.changelog }} body: ${{ steps.calculate-version.outputs.changelog }}
+15 -13
View File
@@ -6,20 +6,19 @@ cmake_minimum_required(VERSION 3.25.0)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
add_compile_definitions(WIN32_LEAN_AND_MEAN NOMINMAX)
if(MSVC)
add_compile_options(/EHsc /utf-8 /Zc:char8_t)
if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64")
add_compile_definitions(_AMD64_)
endif()
else()
add_compile_options(-m64)
endif()
project(hex-unlocked LANGUAGES CXX) project(hex-unlocked LANGUAGES CXX)
set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY USE_FOLDERS ON)
enable_language(RC)
if(CMAKE_GENERATOR MATCHES "Visual Studio")
add_compile_options(/EHsc /utf-8 /Zc:char8_t)
else()
add_compile_options(-m64)
add_link_options(-static -static-libgcc -static-libstdc++)
endif()
# ------------------------------ # ------------------------------
# options # options
# ------------------------------ # ------------------------------
@@ -37,7 +36,6 @@ endif()
# msvc stuff # msvc stuff
# ------------------------------ # ------------------------------
if(CMAKE_GENERATOR MATCHES "Visual Studio") if(CMAKE_GENERATOR MATCHES "Visual Studio")
string(APPEND CMAKE_CXX_FLAGS " /EHsc /DWIN32_LEAN_AND_MEAN /utf-8 /Zc:char8_t")
if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64") if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64")
string(APPEND CMAKE_CXX_FLAGS " /D_AMD64_") string(APPEND CMAKE_CXX_FLAGS " /D_AMD64_")
elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") elseif(CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
@@ -138,8 +136,9 @@ endif()
# ------------------------------ # ------------------------------
file(GLOB_RECURSE UNLOCKER_SOURCES CONFIGURE_DEPENDS "src/unlocker/*.cpp" "src/unlocker/*.h") file(GLOB_RECURSE UNLOCKER_SOURCES CONFIGURE_DEPENDS "src/unlocker/*.cpp" "src/unlocker/*.h")
add_executable(hex-unlocked ${UNLOCKER_SOURCES}) add_executable(hex-unlocked WIN32 ${UNLOCKER_SOURCES} "res/resources.rc")
target_link_libraries(hex-unlocked PRIVATE hex-warned seallib glaze::glaze tinymitm wininet ixwebsocket crypt32) target_compile_definitions(hex-unlocked PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_link_libraries(hex-unlocked PRIVATE hex-warned seallib glaze::glaze tinymitm wininet ixwebsocket crypt32 user32 shell32)
# rnd exe name # rnd exe name
string(RANDOM LENGTH 12 ALPHABET "abcdefghijklmnopqrstuvwxyz0123456789" RANDOM_EXE_NAME) string(RANDOM LENGTH 12 ALPHABET "abcdefghijklmnopqrstuvwxyz0123456789" RANDOM_EXE_NAME)
@@ -148,6 +147,9 @@ set_target_properties(hex-unlocked PROPERTIES OUTPUT_NAME "${RANDOM_EXE_NAME}")
# compiler / IDE config # compiler / IDE config
if(NOT MSVC) if(NOT MSVC)
target_link_options(hex-unlocked PRIVATE -static -static-libgcc -static-libstdc++) target_link_options(hex-unlocked PRIVATE -static -static-libgcc -static-libstdc++)
if(CMAKE_BUILD_TYPE MATCHES "Release|MinSizeRel")
target_link_options(hex-unlocked PRIVATE -s)
endif()
else() else()
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT hex-unlocked) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT hex-unlocked)
endif() endif()
+56
View File
@@ -0,0 +1,56 @@
<h1 align="center">
<b>Hex: Unlocked</b>
</h1>
<p align="center">
A Dead By Daylight inventory manager <b> for educational purposes</b>.
</p>
---
## Table of Contents
* [Installation & usage](#installation--usage)
* [Building](#building)
## Installation & usage
> [!WARNING]
> This goes against the Behaviour's Terms of Service. Although unlikely, there is a definite risk of ban.
### 1. Requirements
* **Dead By Daylight**: Xbox PC version or *Epic Games version **(untested)***
* **Operating System**: Windows 10 or 11 (64-bit)
### 2. Usage
1. Download the latest `hex-unlocked.exe` from the [Releases](../../releases) tab.
2. Run the exe before the game is running.
3. Configure your profile using the [site](https://dbd.neru.rip/).
4. Run the game.
> [!NOTE]
> If you're missing items, open the Bloodweb, wait for it to load, and the inventory should be applied once again.
## Building
This project uses CMake and requires a C++20 compatible compiler.
### Prerequisites
* CMake 3.25.0 or higher
* Visual Studio 2022 or higher (with C++ desktop development) OR MinGW-w64
* .NET SDK (if building with the optional Dumper enabled)
### Visual Studio (Recommended for Windows)
The easiest way to get started on Windows is to run the included PowerShell script. It will generate the Visual Studio solution and open it automatically:
```powershell
.\create-project.ps1
```
### MinGW (Linux Cross-Compile)
If you want to cross-compile using MinGW on Linux, you can configure and build using Ninja:
```bash
cmake -B proj -S . -G Ninja \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \
-DCMAKE_RC_COMPILER=x86_64-w64-mingw32-windres
cmake --build proj --target hex-unlocked
```
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+1
View File
@@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "icon.ico"
+1
View File
@@ -1,4 +1,5 @@
#include "cache-cleaner.h" #include "cache-cleaner.h"
#include "win-platform.h" #include "win-platform.h"
#include <filesystem> #include <filesystem>
@@ -11,6 +11,9 @@ class ConOutSink : public ILogSink
public: public:
virtual void receiveLog(LogType type, std::string_view loggerName, std::string_view msg) override virtual void receiveLog(LogType type, std::string_view loggerName, std::string_view msg) override
{ {
#ifndef _DEBUG
if (type == LogType::VERBOSE) return;
#endif
std::cout << "[" << loggerName << "] " << seallib::getLogTypeColor(type) << "[" std::cout << "[" << loggerName << "] " << seallib::getLogTypeColor(type) << "["
<< seallib::getLogTypeName(type) << "]" << seallib::getLogTypeName(type) << "]"
<< "\x1b[0m " << msg << std::endl; << "\x1b[0m " << msg << std::endl;
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <seallib/log.h>
#include <fstream>
#include <string>
#include <mutex>
#include <memory>
#include "utils.h"
using namespace seallib;
class FileSink : public ILogSink
{
public:
FileSink(const std::string& filename)
{
_file.open(utils::getExePath() + filename, std::ios::out | std::ios::app);
}
~FileSink()
{
if (_file.is_open())
_file.close();
}
virtual void receiveLog(LogType type, std::string_view loggerName, std::string_view msg) override
{
std::lock_guard<std::mutex> lock(_mutex);
if (_file.is_open())
{
_file << "[" << loggerName << "] ["
<< seallib::getLogTypeName(type) << "] "
<< msg << std::endl;
}
}
static std::shared_ptr<FileSink> getSharedInstance()
{
static std::shared_ptr<FileSink> instance = std::make_shared<FileSink>("hex-unlocked.log");
return instance;
}
private:
std::ofstream _file;
std::mutex _mutex;
};
+79 -16
View File
@@ -1,26 +1,22 @@
#include "win-platform.h"
#include "tray-icon.h"
#include "utils.h"
#include "spoofer.h"
#include "log-sink-cout.h"
#include "log-sink-file.h"
#include "proxy-configurator.h"
#include "cache-cleaner.h"
#include <seallib/log.h> #include <seallib/log.h>
#include <tinymitm/proxy.h> #include <tinymitm/proxy.h>
#include <memory> #include <memory>
#include "utils.h"
#include "spoofer.h"
#include "log-sink.h"
#include "proxy-configurator.h"
#include "cache-cleaner.h"
#include "win-platform.h"
#include <processenv.h> #include <processenv.h>
#include <cassert> #include <cassert>
#include <processthreadsapi.h> #include <processthreadsapi.h>
#include <handleapi.h> #include <handleapi.h>
#ifndef CREATE_NO_WINDOW
// from: WinBase.h
#define CREATE_NO_WINDOW 0x08000000
#endif
bool running = true; bool running = true;
TinyMITMProxy* proxy = nullptr; TinyMITMProxy* proxy = nullptr;
ProxyConfigurator* conf = nullptr; ProxyConfigurator* conf = nullptr;
@@ -71,8 +67,24 @@ void StartWatchdog()
bool run() bool run()
{ {
/*
mutex check before running
*/
HANDLE mtx = CreateMutexA(NULL, TRUE, "Global\\HexUnlocked");
if (mtx == NULL) return false;
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBoxA(NULL, "Program is already running", "Hex: Unlocked", MB_OK | MB_ICONERROR | MB_ICONINFORMATION);
CloseHandle(mtx);
return false;
}
/*
init
*/
seallib::Logger mainLog("Main"); seallib::Logger mainLog("Main");
mainLog.addSink(std::make_shared<ConOutSink>()); mainLog.addSink(std::make_shared<ConOutSink>());
mainLog.addSink(FileSink::getSharedInstance());
mainLog.info("Init"); mainLog.info("Init");
@@ -104,11 +116,37 @@ bool run()
return false; return false;
} }
mainLog.info("Proxy running, Ctrl+C to stop."); mainLog.info("Setting up system tray icon");
TrayIcon tray;
tray.setExitCallback([&]() {
running = false;
mainLog.info("Exit requested from system tray.");
});
if (!tray.init())
{
mainLog.error("Failed to initialize system tray icon.");
return false;
}
HWND consoleWnd = GetConsoleWindow();
if (consoleWnd)
{
HMENU menuHandle = GetSystemMenu(consoleWnd, FALSE);
if (menuHandle) RemoveMenu(menuHandle, SC_CLOSE, MF_BYCOMMAND);
}
mainLog.info("Proxy running, Ctrl+C to stop. Check system tray for options.");
mainLog.info("Go to https://dbd.neru.rip/ for settings.");
while (running) while (running)
std::this_thread::sleep_for(std::chrono::milliseconds(100)); {
tray.processMessages();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
mainLog.info("Shutting down..."); mainLog.info("Shutting down...");
tray.shutdown();
proxy->shutdown(); proxy->shutdown();
cleaner->shutdown(); cleaner->shutdown();
@@ -117,11 +155,36 @@ bool run()
delete conf; delete conf;
conf = nullptr; conf = nullptr;
ReleaseMutex(mtx);
CloseHandle(mtx);
return true; return true;
} }
int main() int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nShowCmd*/)
{ {
AllocConsole();
SetConsoleTitleA(utils::randomizeString(32).c_str());
/*
ansi seequences
*/
HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
if (outHandle == INVALID_HANDLE_VALUE) return 1;
DWORD conMode = 0;
if (!GetConsoleMode(outHandle, &conMode)) return 1;
conMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(outHandle, conMode);
/*
io
*/
FILE* dummy;
freopen_s(&dummy, "CONIN$", "r", stdin);
freopen_s(&dummy, "CONOUT$", "w", stdout);
freopen_s(&dummy, "CONOUT$", "w", stderr);
/* /*
handlers for cleaning proxy conf on exit / crash / taskkill / whatever handlers for cleaning proxy conf on exit / crash / taskkill / whatever
+5 -2
View File
@@ -1,17 +1,20 @@
#include "proxy-configurator.h" #include "proxy-configurator.h"
#include "log-sink.h"
#include "win-platform.h" #include "win-platform.h"
#include "log-sink-cout.h"
#include "log-sink-file.h"
#include <minwinbase.h> #include <minwinbase.h>
#include <wininet.h> #include <wininet.h>
#include <errhandlingapi.h> #include <errhandlingapi.h>
#include <seallib/log.h>
ProxyConfigurator::ProxyConfigurator() ProxyConfigurator::ProxyConfigurator()
{ {
_log = new seallib::Logger("ProxyConfigurator"); _log = new seallib::Logger("ProxyConfigurator");
_log->addSink(std::make_shared<ConOutSink>()); _log->addSink(std::make_shared<ConOutSink>());
_log->addSink(FileSink::getSharedInstance());
_log->verbose("ProxyConfigurator instantiated"); _log->verbose("ProxyConfigurator instantiated");
} }
+15 -8
View File
@@ -1,6 +1,7 @@
#include "spoofer.h" #include "spoofer.h"
#include "utils.h" #include "utils.h"
#include "log-sink.h" #include "log-sink-cout.h"
#include "log-sink-file.h"
#include <regex> #include <regex>
#include <map> #include <map>
@@ -35,18 +36,19 @@ Spoofer::Spoofer()
{ {
_log = new seallib::Logger("Spoofer"); _log = new seallib::Logger("Spoofer");
_log->addSink(std::make_shared<ConOutSink>()); _log->addSink(std::make_shared<ConOutSink>());
_log->addSink(FileSink::getSharedInstance());
_log->info("Spoofer init"); _log->verbose("Spoofer init");
loadConfig(); loadConfig();
_log->info("Starting WebSocket server"); _log->verbose("Starting WebSocket server");
initServer(); initServer();
} }
Spoofer::~Spoofer() Spoofer::~Spoofer()
{ {
_log->info("Stopping WebSocket server"); _log->verbose("Stopping WebSocket server");
stopServer(); stopServer();
delete _log; delete _log;
@@ -133,6 +135,7 @@ void Spoofer::saveConfig()
std::string buffer; std::string buffer;
auto errCtx = glz::write_file_json(conf, configPath, buffer); auto errCtx = glz::write_file_json(conf, configPath, buffer);
if (errCtx.ec != glz::error_code::none) _log->error("Failed to save config to {}", configPath); if (errCtx.ec != glz::error_code::none) _log->error("Failed to save config to {}", configPath);
_log->verbose("Saved config @ config.json");
} }
/* /*
@@ -175,7 +178,7 @@ void Spoofer::wsMessageCallback(std::shared_ptr<ix::ConnectionState> /*connectio
{ {
case ix::WebSocketMessageType::Open: case ix::WebSocketMessageType::Open:
{ {
_log->verbose("Websocket connection open, URI: {}", msg->openInfo.uri); _log->verbose("WebSocket connection open, URI: {}", msg->openInfo.uri);
WSMessages::Init initMsg; WSMessages::Init initMsg;
initMsg.profile.camperItems = _camperItems; initMsg.profile.camperItems = _camperItems;
initMsg.profile.camperAddons = _camperAddons; initMsg.profile.camperAddons = _camperAddons;
@@ -203,7 +206,7 @@ void Spoofer::wsMessageCallback(std::shared_ptr<ix::ConnectionState> /*connectio
break; break;
} }
case ix::WebSocketMessageType::Close: case ix::WebSocketMessageType::Close:
_log->verbose("Websocket connection close"); _log->verbose("WebSocket connection close");
break; break;
case ix::WebSocketMessageType::Message: case ix::WebSocketMessageType::Message:
{ {
@@ -211,7 +214,7 @@ void Spoofer::wsMessageCallback(std::shared_ptr<ix::ConnectionState> /*connectio
auto err = glz::read_json(req, msg->str); auto err = glz::read_json(req, msg->str);
if (err.ec != glz::error_code::none) if (err.ec != glz::error_code::none)
{ {
_log->error("Failed to parse websocket message"); _log->error("Failed to parse WebSocket message");
break; break;
} }
switch (req.action) switch (req.action)
@@ -330,13 +333,15 @@ void Spoofer::modifyCharacterInventory(glz::generic& js)
appendPerks(_slasherPerks); appendPerks(_slasherPerks);
} }
appendItems(_globalOfferings); appendItems(_globalOfferings);
_log->verbose("Modified inventory for character {}", js["characterName"].get_string());
} }
void Spoofer::modifyCharacterData(glz::generic& js) void Spoofer::modifyCharacterData(glz::generic& js)
{ {
if (!js.contains("characterName") || !js["characterName"].is_string()) if (!js.contains("characterName") || !js["characterName"].is_string())
{ {
_log->verbose("attempted to modify invalid char"); _log->warning("Attempted to modify invalid character");
return; return;
} }
@@ -371,6 +376,8 @@ void Spoofer::modifyCharacterData(glz::generic& js)
} }
if (_spoofItems || _spoofPerks) modifyCharacterInventory(js); if (_spoofItems || _spoofPerks) modifyCharacterInventory(js);
_log->verbose("Modified data for character {}", js["characterName"].get_string());
} }
void Spoofer::generateBloodweb(glz::generic& js) void Spoofer::generateBloodweb(glz::generic& js)
+175
View File
@@ -0,0 +1,175 @@
#include "tray-icon.h"
#include "win-platform.h"
#include <shellapi.h>
#include <iostream>
#define WM_TRAYICON (WM_USER + 1)
#define ID_TRAY_APP_ICON 1001
#define ID_TRAY_EXIT 1002
#define ID_TRAY_CONTROL_PANEL 1003
TrayIcon* TrayIcon::instance = nullptr;
TrayIcon::TrayIcon() : _hwnd(nullptr), _isConsoleVisible(true)
{
instance = this;
}
TrayIcon::~TrayIcon()
{
shutdown();
instance = nullptr;
}
void* TrayIcon::windowProc(void* h, unsigned int msg, unsigned long long wParam, long long lParam)
{
HWND hwnd = reinterpret_cast<HWND>(h);
if (msg == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
if (msg == WM_TRAYICON)
{
if (lParam == WM_LBUTTONUP && instance)
instance->onToggleConsole();
else if (lParam == WM_RBUTTONUP)
{
POINT curPoint;
GetCursorPos(&curPoint);
HMENU popupHandle = CreatePopupMenu();
InsertMenuA(popupHandle, 0, MF_BYPOSITION | MF_STRING, ID_TRAY_CONTROL_PANEL, "Open Control Panel");
InsertMenuA(popupHandle, 1, MF_BYPOSITION | MF_STRING, ID_TRAY_EXIT, "Exit");
SetForegroundWindow(hwnd);
UINT clicked =
TrackPopupMenu(popupHandle, TPM_RETURNCMD | TPM_NONOTIFY, curPoint.x, curPoint.y, 0, hwnd, NULL);
if (clicked == ID_TRAY_CONTROL_PANEL && instance)
instance->onOpenControlPanel();
else if (clicked == ID_TRAY_EXIT && instance)
instance->onExit();
DestroyMenu(popupHandle);
}
}
return reinterpret_cast<void*>(DefWindowProcA(hwnd, msg, wParam, lParam));
}
bool TrayIcon::init()
{
WNDCLASSEXA wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = reinterpret_cast<WNDPROC>(reinterpret_cast<void*>(windowProc));
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "HexUnlockedTrayIconClass";
if (!RegisterClassExA(&wc)) return false;
HWND window = CreateWindowExA(0, "HexUnlockedTrayIconClass", "HexUnlocked", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL,
wc.hInstance, NULL);
if (!window) return false;
this->_hwnd = window;
NOTIFYICONDATAA nid = {};
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = window;
nid.uID = ID_TRAY_APP_ICON;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid.uCallbackMessage = WM_TRAYICON;
nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
strncpy_s(nid.szTip, "HexUnlocked", sizeof(nid.szTip) - 1);
nid.szTip[sizeof(nid.szTip) - 1] = '\0';
Shell_NotifyIconA(NIM_ADD, &nid);
return true;
}
void TrayIcon::shutdown()
{
if (_hwnd)
{
NOTIFYICONDATAA nid = {};
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = reinterpret_cast<HWND>(_hwnd);
nid.uID = ID_TRAY_APP_ICON;
Shell_NotifyIconA(NIM_DELETE, &nid);
DestroyWindow(reinterpret_cast<HWND>(_hwnd));
UnregisterClassA("HexUnlockedTrayIconClass", GetModuleHandle(NULL));
_hwnd = nullptr;
}
}
void TrayIcon::processMessages()
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
HWND consoleWnd = GetConsoleWindow();
if (consoleWnd && IsIconic(consoleWnd))
{
ShowWindow(consoleWnd, SW_HIDE);
_isConsoleVisible = false;
}
}
void TrayIcon::onToggleConsole()
{
HWND consoleWnd = GetConsoleWindow();
if (!consoleWnd) return;
if (_isConsoleVisible)
{
ShowWindow(consoleWnd, SW_HIDE);
_isConsoleVisible = false;
}
else
{
ShowWindow(consoleWnd, SW_RESTORE);
HWND hCurWnd = GetForegroundWindow();
DWORD dwMyID = GetCurrentThreadId();
DWORD dwCurID = GetWindowThreadProcessId(hCurWnd, NULL);
AttachThreadInput(dwCurID, dwMyID, TRUE);
SetWindowPos(consoleWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
SetWindowPos(consoleWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
SetForegroundWindow(consoleWnd);
SetFocus(consoleWnd);
SetActiveWindow(consoleWnd);
AttachThreadInput(dwCurID, dwMyID, FALSE);
_isConsoleVisible = true;
}
}
void TrayIcon::onOpenControlPanel()
{
ShellExecuteA(NULL, "open", "https://dbd.neru.rip", NULL, NULL, SW_SHOWNORMAL);
}
void TrayIcon::onExit()
{
if (_exitCallback) _exitCallback();
}
void TrayIcon::setExitCallback(std::function<void()> callback)
{
_exitCallback = callback;
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <functional>
class TrayIcon
{
public:
TrayIcon();
~TrayIcon();
bool init();
void shutdown();
void processMessages();
void onToggleConsole();
void onOpenControlPanel();
void onExit();
void setExitCallback(std::function<void()> callback);
private:
static void* windowProc(void* hwnd, unsigned int msg, unsigned long long wParam, long long lParam);
static TrayIcon* instance;
void* _hwnd;
bool _isConsoleVisible;
std::function<void()> _exitCallback;
};
+1 -1
View File
@@ -6,4 +6,4 @@ namespace utils
{ {
std::string getExePath(); std::string getExePath();
std::string randomizeString(size_t length); std::string randomizeString(size_t length);
} } // namespace utils
+7 -1
View File
@@ -3,10 +3,16 @@
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
#ifndef NOMINMAX #ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
#endif #endif
#if defined(_WIN32) #if defined(_WIN32)
#include <windows.h> #include <minwindef.h>
#include <wtypes.h>
#endif
#ifdef ERROR
#undef ERROR
#endif #endif