-
-
Notifications
You must be signed in to change notification settings - Fork 73
Creating a new mod
Each mod is a single C++ file which is compiled into a dynamic library. After it's compiled, the mod is loaded by the Windhawk engine which calls the mod's callback functions and provides API functions that the mod can use.
The mod is loaded in the context of the processes that the mod targets. For example, if the mod targets Notepad, it will be loaded by the Windhawk engine in the context of each notepad.exe process.
In addition to the C++ code itself, the mod must provide information such as the mod id and name. Optionally, the mod may include a readme with information and settings to configure the mod.
To submit a mod to the official collection of Windhawk mods, refer to the readme in the windhawk-mods repository.
Each mod starts with a metadata block which contains information such as the mod id and name. The general format is:
// ==WindhawkMod==
// @key value
// ==/WindhawkMod==
Some keys may have multiple values, and some keys can be localized.
Metadata block example:
// ==WindhawkMod==
// @id new-mod
// @name Your Awesome Mod
// @description The best mod ever that does great things
// @version 0.1
// @author You
// @github https://github.com/nat
// @twitter https://twitter.com/jack
// @homepage https://your-personal-homepage.example.com/
// @include notepad.exe
// ==/WindhawkMod==
// @id mod-id-1
Each mod must have a unique mod id. The mod id must only contain the following characters: 0-9, a-z, and a hyphen (-).
// @name The mod name
The mod name. Can be localized (see below).
// @description The mod description
A short description of the mod. Can be localized (see below).
// @version 1.0
The mod version number. Must increase with each newly published mod version.
// @author John Doe
The mod author name or nickname. Can be localized (see below).
// @github https://github.com/nat
The link to the GitHub profile of the mod author.
// @twitter https://twitter.com/jack
The link to the Twitter profile of the mod author.
// @homepage https://your-personal-homepage.example.com/
The link to the website of the mod author.
// @include notepad.exe
// @include program-1.*.exe
// @include C:\programs\*.exe
// @include C:\Windows\explorer.exe
A list of executable file names/paths that the mod targets. For each process, the mod is loaded if the executable file path matches one of the @include
entries and doesn't match any @exclude
entry (see blow).
A wildcard can be used, where the *
symbol matches any sequence of characters and ?
matches any single character.
There can be any number of @include
entries.
// @exclude notepad.exe
// @exclude program-1.*.exe
// @exclude C:\programs\*.exe
// @exclude C:\Windows\explorer.exe
A list of executable file names/paths to be excluded from targeting. For each process, the mod is loaded if the executable file path matches one of the @include
entries (see above) and doesn't match any @exclude
entry.
A wildcard can be used, where the *
symbol matches any sequence of characters and ?
matches any single character.
There can be any number of @exclude
entries.
// @architecture x86
// @architecture x86-64
A list of supported architectures. If no @architecture
entry is specified, all architectures which are supported by Windhawk are regarded as supported by the mod.
There can be any number of @architecture
entries.
// @compilerOptions -lcomctl32 -lgdi32 -luxtheme
Extra command line parameters that are passed to the compiler when compiling the mod.
Some of the metadata entries can be localized for multiple languages, for example:
// @name Example mod
// @name:uk-UA Приклад мод
// @name:fr-FR Exemple de mod
A mod may contain a readme block with information for the users. The general format is:
// ==WindhawkModReadme==
/*
content
*/
// ==/WindhawkModReadme==
Markdown can be used to add links and formatting to the readme. Only images from the following domains can be embedded in the readme: https://i.imgur.com
, https://raw.githubusercontent.com
.
Readme example:
// ==WindhawkModReadme==
/*
# Your Awesome Mod
This is a place for useful information about your mod. Use it to describe
the mod, explain why it's useful, and add any other relevant details.
You can use [Markdown](https://en.wikipedia.org/wiki/Markdown) to add links
and **formatting** to the readme.
*/
// ==/WindhawkModReadme==
A mod may define settings that the mod users will be able to configure. The settings are defined in YAML format as following:
// ==WindhawkModSettings==
/*
- SettingName: Default Value
*/
// ==/WindhawkModSettings==
Each setting is defined by a name and a default value. The default value is set when the mod is installed, and can be changed later by the user.
Possible setting types are: boolean, number, string, array of numbers, array of strings.
Options can be nested as can be seen in the example below.
In addition to the setting name and default value, several metadata values can be used to make it easier for users to edit the options:
-
$name
: The name of the option. -
$description
: A short description of the option. -
$options
: Possible values for a string option, displayed to the user in a combobox.
The metadata values can be localized for multiple languages.
Settings example:
// ==WindhawkModSettings==
/*
- BooleanOption: true
$name: Example Boolean
$description: An example boolean setting
$description:uk-UA: Приклад логічного налаштування
- NumberOption: 1234
- StringOption: Default string value
- StringCombobox: option1
$options:
- option1: First Option Description
- option2: Second Option Description
$options:uk-UA:
- option1: Опис першого варіанту
- option2: Опис другого варіанту
- NestedOptions:
- NumberNestedOption: 2345
- StringNestedOption: Nested option text
- ArrayOfNumbers: [1, 2, 3]
- ArrayOfStrings: [a, b, c]
- ArrayOfStringComboboxes: [a, b, c]
$options:
- a: First Option Description
- b: Second Option Description
- c: Third Option Description
- ArrayOfNestedOptions:
- - NumberNestedOptionInArray: 3456
- StringNestedOptionInArray: Nested option in array text
*/
// ==/WindhawkModSettings==
There are several callback functions that the mod can implement. Those callback functions are called by the Windhawk engine when specific events occur.
See also: Mod lifetime.
BOOL Wh_ModInit(void)
The first callback function that is called by the Windhawk engine. Called before the target process starts executing, unless the process is already running when the mod is loading. Allows the mod to initialize and to set up hooks using the Wh_SetFunctionHook API function (see below).
The mod must return TRUE
if initialization is successful. If FALSE
is returned, no further callbacks are called and the mod is unloaded.
void Wh_ModAfterInit(void)
Called after Wh_ModInit
and after the Windhawk engine completes setting up hooks.
void Wh_ModBeforeUninit(void)
Called when the mod is about to be unloaded, before the Windhawk engine removes hooks.
void Wh_ModUninit(void)
Called when the mod is about to be unloaded, after the Windhawk engine removes hooks.
// Variant 1:
void Wh_ModSettingsChanged(void)
// Variant 2:
BOOL Wh_ModSettingsChanged(BOOL* bReload)
Called when the mod settings are changed. Allows the mod to load and apply the new settings.
Variant 2 of the callback allows to unload or reload the mod after settings are changed. If the callback returns FALSE
, the mod will be unloaded for the target process, and will stay unloaded until settings are changed again. If the callback returns TRUE
and *bReload
is set to TRUE
, the mod will be reloaded after the callback returns.
There are several API functions that the mod can use.
#define Wh_Log(message, ...) /*...*/
Logs a message. If logging is enabled, the message can be viewed in the editor log output window. The arguments are only evaluated if logging is enabled.
-
message
: The message to be logged. It can optionally contain embedded printf-style format specifiers that are replaced by the values specified in subsequent additional arguments and formatted as requested.
int Wh_GetIntValue(PCWSTR valueName, int defaultValue);
Retrieves an integer value from the mod's local storage.
-
valueName
: The name of the value to retrieve. -
defaultValue
: The default value to be returned as a fallback.
Returns: The retrieved integer value. If the value doesn't exist or in case of an error, the provided default value is returned.
BOOL Wh_SetIntValue(PCWSTR valueName, int value);
Stores an integer value in the mod's local storage.
-
valueName
: The name of the value to store. -
value
: The value to store.
Returns: A boolean value indicating whether the function succeeded.
size_t Wh_GetStringValue(PCWSTR valueName, PWSTR stringBuffer, size_t bufferChars);
Retrieves a string value from the mod's local storage.
-
valueName
: The name of the value to retrieve. -
stringBuffer
: The buffer that will receive the text, terminated with a null character. -
bufferChars
: The length ofstringBuffer
, in characters. The buffer must be large enough to include the terminating null character.
Returns: The number of characters copied to the buffer, not including the terminating null character. If the value doesn't exist, if the buffer is not large enough, or in case of an error, an empty string is returned.
BOOL Wh_SetStringValue(PCWSTR valueName, PCWSTR value);
Stores a string value in the mod's local storage.
-
valueName
: The name of the value to store. -
value
: A null-terminated string containing the value to store.
Returns: A boolean value indicating whether the function succeeded.
size_t Wh_GetBinaryValue(PCWSTR valueName, void* buffer, size_t bufferSize);
Retrieves a binary value (raw bytes) from the mod's local storage.
-
valueName
: The name of the value to retrieve. -
buffer
: The buffer that will receive the value. -
bufferSize
: The length of the buffer, in bytes.
Returns: The number of bytes copied to the buffer. If the value doesn't exist, if the buffer is not large enough, or in case of an error, no data is copied and the return value is zero.
BOOL Wh_SetBinaryValue(PCWSTR valueName, const void* buffer, size_t bufferSize);
Stores a binary value (raw bytes) in the mod's local storage.
-
valueName
: The name of the value to store. -
buffer
: An array of bytes containing the value to store. -
bufferSize
: The size of the array of bytes.
Returns: A boolean value indicating whether the function succeeded.
BOOL Wh_DeleteValue(PCWSTR valueName);
Deletes a value from the mod's local storage.
-
valueName
: The name of the value to delete.
Returns: A boolean value indicating whether the function succeeded.
int Wh_GetIntSetting(PCWSTR valueName, ...);
Retrieves an integer value from the mod's user settings.
-
valueName
: The name of the value to retrieve. It can optionally contain embedded printf-style format specifiers that are replaced by the values specified in subsequent additional arguments and formatted as requested.
Returns: The retrieved integer value. If the value doesn't exist or in case of an error, the return value is zero.
PCWSTR Wh_GetStringSetting(PCWSTR valueName, ...);
Retrieves a string value from the mod's user settings. When no longer
needed, free the memory with Wh_FreeStringSetting
.
-
valueName
: The name of the value to retrieve. It can optionally contain embedded printf-style format specifiers that are replaced by the values specified in subsequent additional arguments and formatted as requested.
Returns: The retrieved string value. If the value doesn't exist or in case of an error, an empty string is returned.
void Wh_FreeStringSetting(PCWSTR string);
Frees a string returned by Wh_GetStringSetting
.
-
string
: The string to free.
BOOL Wh_SetFunctionHook(void* targetFunction, void* hookFunction, void** originalFunction);
Registers a hook for the specified target function. Can't be called
after Wh_ModBeforeUninit
returns. Registered hook operations can be
applied with Wh_ApplyHookOperations
.
-
targetFunction
: A pointer to the target function, which will be overridden by the detour function. -
hookFunction
: A pointer to the detour function, which will override the target function. -
originalFunction
: A pointer to the trampoline function, which will be used to call the original target function.
Returns: A boolean value indicating whether the function succeeded.
BOOL Wh_RemoveFunctionHook(void* targetFunction);
Registers a hook to be removed for the specified target function.
Can't be called before Wh_ModInit
returns or after Wh_ModBeforeUninit
returns. Registered hook operations can be applied with
Wh_ApplyHookOperations
.
-
targetFunction
: A pointer to the target function, for which the hook will be removed.
Returns: A boolean value indicating whether the function succeeded.
BOOL Wh_ApplyHookOperations();
Applies hook operations registered by Wh_SetFunctionHook
and
Wh_RemoveFunctionHook
. Called automatically by Windhawk after
Wh_ModInit
. Can't be called before Wh_ModInit
returns or after
Wh_ModBeforeUninit
returns. Note: This function is very slow, avoid
using it if possible. Ideally, all hooks should be set in Wh_ModInit
and this function should never be used.
Returns: A boolean value indicating whether the function succeeded.
typedef struct tagWH_FIND_SYMBOL_OPTIONS {
// Must be set to `sizeof(WH_FIND_SYMBOL_OPTIONS)`.
size_t optionsSize;
// The symbol server to query. Set to `NULL` to query the Microsoft public
// symbol server.
PCWSTR symbolServer;
// Set to `TRUE` to only retrieve decorated symbols, making the enumeration
// faster. Can be especially useful for very large modules such as Chrome or
// Firefox.
BOOL noUndecoratedSymbols;
} WH_FIND_SYMBOL_OPTIONS;
typedef struct tagWH_FIND_SYMBOL {
void* address;
PCWSTR symbol;
PCWSTR symbolDecorated;
} WH_FIND_SYMBOL;
HANDLE Wh_FindFirstSymbol(HMODULE hModule, const WH_FIND_SYMBOL_OPTIONS* options, WH_FIND_SYMBOL* findData);
Returns information about the first symbol for the specified module handle.
-
hModule
: A handle to the loaded module whose information is being requested. If this parameter isNULL
, the module of the current process (.exe file) is used. -
options
: Can be used to customize the symbol enumeration. PassNULL
to use the default options. -
findData
: A pointer to a structure to receive the symbol information.
Returns: A search handle used in a subsequent call to Wh_FindNextSymbol
or
Wh_FindCloseSymbol
. If no symbols are found or in case of an error, the
return value is NULL
.
typedef struct tagWH_FIND_SYMBOL {
void* address;
PCWSTR symbol;
PCWSTR symbolDecorated;
} WH_FIND_SYMBOL;
BOOL Wh_FindNextSymbol(HANDLE symSearch, WH_FIND_SYMBOL* findData);
Returns information about the next symbol for the specified search
handle, continuing an enumeration from a previous call to
Wh_FindFirstSymbol
.
-
symSearch
: A search handle returned by a previous call toWh_FindFirstSymbol
. -
findData
: A pointer to a structure to receive the symbol information.
Returns: A boolean value indicating whether symbol information was retrieved.
If no more symbols are found or in case of an error, the return value is
FALSE
.
void Wh_FindCloseSymbol(HANDLE symSearch);
Closes a file search handle opened by Wh_FindFirstSymbol
.
-
symSearch
: The search handle. If symSearch isNULL
, the function does nothing.
typedef struct tagWH_DISASM_RESULT {
// The length of the decoded instruction.
size_t length;
// The textual, human-readable representation of the instruction.
char text[96];
} WH_DISASM_RESULT;
BOOL Wh_Disasm(void* address, WH_DISASM_RESULT* result);
Disassembles an instruction and formats it to human-readable text.
-
address
: The address of the instruction to disassemble. -
result
: A pointer to a structure to receive the disassembly information.
Returns: A boolean value indicating whether the function succeeded.
typedef struct tagWH_URL_CONTENT {
const char* data;
size_t length;
int statusCode;
} WH_URL_CONTENT;
const WH_URL_CONTENT* Wh_GetUrlContent(PCWSTR url, void* reserved);
Retrieves the content of a URL. When no longer needed, call
Wh_FreeUrlContent
to free the content.
-
url
: The URL to retrieve. -
reserved
: Reserved for future use, must beNULL
.
Returns: The retrieved content. In case of an error, NULL
is returned.
void Wh_FreeUrlContent(const WH_URL_CONTENT* content);
Frees the content of a URL returned by Wh_GetUrlContent
.
-
content
: The content to free. IfNULL
, the function does nothing.
There are several defined constants that can be used in the code.
The mod id. Example: L"my-mod"
The mod version. Example: L"1.0"
As of version 1.5, Windhawk uses Clang 18 (mingw-w64 toolchain) and compiles the mods in C++23 mode. The full command line parameters can be seen in editing mode by clicking Ctrl+P and selecting compile_flags.txt
.