h7 library¶
The global h7 table holds the engine's API. It is shared by every mod and by exec scripts.
Parameters called class take a class name ("H7AdventureHero" or "MMH7Game.H7AdventureHero") or a class
object; func takes a function path ("MMH7Game.H7Calendar.NextDay", optionally prefixed with "Function ")
or a function object.
Engine¶
h7.version¶
The engine version, e.g. "0.1.0". Use it to check for features your mod needs.
Logging¶
All logging goes to <Game>\H7ModEngine\logs\engine.log (the previous run is kept in engine.prev.log). Each
line starts with a timestamp and the name of the mod that wrote it, or [lua] for code that runs outside any mod
(exec scripts).
h7.log¶
Writes its arguments, converted with tostring and separated by tabs, as one line:
h7.log("Hero", hero, "has", 12, "movement points")
-- [19:32:14.962] [MyMod] Hero H7AdventureHero sce_sephinroth.TheWorld.PersistentLevel.H7AdventureHero_0 has 12 movement points
print is the same function.
h7.warn¶
Like h7.log, with the line prefixed WARNING.
h7.error¶
Like h7.log, with the line prefixed ERROR. It only logs; it does not raise an error or stop
your code. To stop with an error, use Lua's error("message").
h7.log_lines¶
The most recent engine log lines, for showing the log in the game (the DebugConsole mod uses it). Lines are numbered from 1 since the game started; the engine keeps the last 2000 in memory.
| Parameter | |
|---|---|
after |
Return only lines numbered above this (default 0: every kept line) |
Returns lines, a sequence of strings as written to the log but without the timestamp (e.g.
[MyMod] message; one entry may contain newlines, such as an error with its stack trace), and newest, the
number of the newest line. Pass newest back as after to get only the lines added since.
local last = 0
h7.every(1, function()
local new
new, last = h7.log_lines(last)
for _, line in ipairs(new) do
if line:find("ERROR", 1, true) then --[[ ... ]] end
end
end)
Finding objects¶
h7.first¶
The first live instance of class or one of its subclasses, or nil if there is none. Class default objects
(Default__...) and archetypes (templates stored in content packages) are skipped.
| Parameter | |
|---|---|
class |
Class name or class object |
Errors: unknown class '<name>' if a class name doesn't exist.
local controller = h7.first("H7AdventureController")
if controller then h7.log(controller:GetLocalPlayer(true):GetName()) end
"First" means lowest in the engine's object table, which usually but not always is the one created first. When
there can be several instances (heroes, players, armies), use h7.find_all and pick the one you
want.
h7.find_all¶
Every live instance of class or its subclasses, as a sequence (empty when there are none).
| Parameter | |
|---|---|
class |
Class name or class object |
include_templates |
true to include class default objects and archetypes. Default false |
Errors: unknown class '<name>'.
for _, hero in ipairs(h7.find_all("H7AdventureHero")) do
h7.log(hero:GetName(), hero:GetLevel())
end
-- every creature archetype the game has loaded
local creatures = h7.find_all("H7Creature", true)
find_all scans the whole engine object table. Call it when you need it, not every frame,
and cache the result while it stays valid (until the next map_loaded, for objects in a map).
h7.find¶
The object with the given path name, or nil. The path may be prefixed with the object's class to tell apart
objects with the same path.
| Parameter | |
|---|---|
path |
"Package.Outer.Name", or "ClassName Package.Outer.Name" |
local calendar_class = h7.find("Class MMH7Game.H7Calendar")
local next_day = h7.find("Function MMH7Game.H7Calendar.NextDay")
local pixie = h7.find("Creature_Pixie.A_CreaturePixie") -- an archetype in a content package
dumps\objects.txt (written at startup) lists every object's path.
h7.find_class¶
The class called name ("Package.Class" or the bare "Class"), or nil.
local hero_class = h7.find_class("H7AdventureHero")
h7.log(hero_class:get_super()) -- Class MMH7Game.H7EditorHero
h7.enum¶
A table mapping each name of the UnrealScript enum name to its numeric value (0, 1, ...). Enum values read
from properties and function results are plain integers; use this table to compare them or pass them.
| Parameter | |
|---|---|
name |
The enum's name ("EUnitType") or path ("MMH7Game.H7StructsAndEnumsNative.EUnitType") |
Errors: unknown enum '<name>'.
Reflection¶
h7.properties¶
Every property of a class or struct, inherited ones included.
| Parameter | |
|---|---|
x |
A class or struct name; an object (lists its class); a class, struct or function object; or a struct reference |
| Field | |
|---|---|
name |
Property name, as used from Lua |
type |
The engine's property type, e.g. IntProperty, ObjectProperty, ArrayProperty, StructProperty |
type_name |
The value's type as UnrealScript writes it (as in h7.signature): int, H7Creature, Vector, array<H7BaseCreatureStack> |
owner |
The class or struct that declares it |
offset |
Byte offset inside the object or struct |
Passing a function object lists its parameters and local variables. Properties of types the engine doesn't support are left out.
Errors: unknown class/struct '<name>'.
h7.functions¶
Every function of a class (or struct), inherited ones included. x takes the same values as in
h7.properties.
| Field | |
|---|---|
name |
Function name |
type |
The function object's class ("Function") |
owner |
The class that declares it. A function overridden in a subclass appears once per declaring class |
flags |
The engine's function flags (bit field: 0x400 native, 0x800 event, 0x2000 static, ...) |
Errors: unknown class/struct '<name>'.
h7.signature¶
The function's declaration in UnrealScript syntax: static / native / event qualifiers, return type, full
path and parameters with out and optional.
Errors: function '<path>' not found (use Package.Class.Function); expected a Function object.
h7.log(h7.signature("MMH7Game.H7GameProcessor.CalculateDamage"))
-- function void MMH7Game.H7GameProcessor.CalculateDamage(out H7CombatResult Result, int resultIdx, bool forecast)
Check the signature before hooking a function: native functions can be hooked but give the hook no arguments
and can't be blocked (see h7.hook).
h7.to_table¶
A plain Lua copy of an engine value, independent of the engine:
value |
Result |
|---|---|
| Struct reference | Table of its fields |
| Array reference | Sequence of its elements |
| Object reference | Table of the object's properties |
| Anything else | value itself |
Structs and arrays inside are copied recursively; objects inside stay object references (they are not expanded, since object graphs loop). Use it to log a value, or to keep hook arguments after the hook returns.
h7.hook_post("MMH7Game.H7GameProcessor.CalculateDamage", function(proc, args)
last_result = h7.to_table(args.Result) -- args.Result itself is invalid after the hook
end)
Hooks¶
A hook runs a Lua function whenever a game function is called, from UnrealScript or from the engine's C++ code. See Hooks in the guide for what hooks see and how to keep them fast.
h7.hook¶
Adds a hook on func and returns its id (an integer, for h7.unhook).
| Parameter | |
|---|---|
func |
Function path or function object |
fn |
The hook function, see below |
when |
"pre" (default): before the function runs; "post": after it, like h7.hook_post |
A pre-hook is called as fn(self, args):
| Argument | |
|---|---|
self |
The object the function was called on |
args |
The function's parameters by name, readable and writable (a struct reference). nil for native functions |
Return true (the boolean) to block the call: the original function doesn't run and its caller gets a zero
return value (0, false, "", None). Post-hooks still run. Any other return value lets the call proceed.
Changing args changes what the function receives.
Errors: the function isn't found; could not hook <path>.
-- No hero may visit obelisks
h7.hook("MMH7Game.H7Obelisk.OnVisit", function(obelisk, args)
return true
end)
Errors inside fn are logged with a stack trace and the call continues as if the hook had returned nothing;
the hook stays installed.
Hooks on native functions (native in h7.signature) receive args = nil and can't
block; the engine logs a note when you add a pre-hook to one. Latent and iterator functions only report calls
made from C++.
h7.hook_post¶
Adds a hook that runs after func has run. Same as h7.hook(func, fn, "post"). fn is called as
fn(self, args, result):
| Argument | |
|---|---|
self |
The object the function was called on |
args |
The parameters after the call, out parameters included (nil for native functions) |
result |
The return value. Not passed for functions that return nothing |
Return a non-nil value to replace the return value; return nothing to leave it. A replacement of the wrong
type is logged as an error and the original value is kept.
h7.hook_post("MMH7Game.H7CombatArmy.GetKilledCreaturesXP", function(army, args, xp)
if xp > 0 then return math.floor(xp * 1.5 + 0.5) end
end)
Several hooks on one function all run in the order they were added: every pre-hook, then (unless blocked) the
function, then every post-hook. When two post-hooks replace the result, the later one wins and receives the
earlier one's value as result.
h7.unhook¶
Removes the hook id. Unknown ids are ignored. Hooks are removed automatically when their mod unloads.
Events and timers¶
See Events and timers for the list of events and how timers are scheduled.
h7.on¶
Calls fn(...) every time event fires, with the event's arguments. Returns the handler's
id. Handlers run in the order they were added. Any event name is accepted, so a misspelt name simply never fires.
h7.off¶
Removes the event handler id. Unknown ids are ignored.
h7.after¶
Calls fn() once, seconds of game time from now (on the first frame at or after that time). Returns the
timer's id.
h7.every¶
Calls fn() every seconds of game time until cancelled. The first call is seconds from now. At most one call
per frame: a timer shorter than a frame runs once per frame. seconds of 0 or less makes a one-shot timer,
like h7.after.
h7.cancel¶
Cancels the timer id. Unknown ids (including timers that already fired) are ignored. A timer may cancel
itself.
Creating objects¶
See Creating objects in the guide for how copies interact with the garbage collector, lazily loaded references, localisation and save games.
h7.clone¶
Creates a copy of source: an object of the same class with every property copied from it.
| Parameter | |
|---|---|
source |
The object to copy, usually an archetype (a template such as Creature_Pixie.A_CreaturePixie) |
name |
Name of the new object |
outer |
The object or package that contains it. Default: the transient package. Use h7.package for stable paths |
flags |
Extra object flags to add, e.g. h7.flags.RootSet |
| Returns | |
|---|---|
object |
The copy |
created |
true if it was created now; false if outer already held an object called name of the same class, which is returned instead |
The copy keeps the source's Public, Standalone and Archetype flags, so a copy of an archetype is an
archetype too. Returning an existing copy makes mod code safe to run again after a hot reload.
Errors: <path> already exists with another class; could not create <class> '<name>': <reason> (for
example when h7.can_create() is false).
local pkg = h7.package("MyMod")
local sprite = h7.clone(h7.find("Creature_Pixie.A_CreaturePixie"), "A_CreatureFrostSprite", pkg, h7.flags.RootSet)
sprite.mHitpoints = 20
h7.new_object¶
Creates a new object of class. options is a table with any of:
| Field | |
|---|---|
name |
Object name. Default: a generated unique name |
outer |
Containing object or package. Default: the transient package |
template |
Object to copy property values from. Default: the class defaults |
flags |
Object flags (replaces the default Public + Standalone) |
archetype |
true to add the Archetype flag |
replace |
true to reconstruct an existing object with the same name in place (same address, properties reset) instead of failing |
Errors: unknown class '<name>'; <path> already exists (pass replace = true to reconstruct it);
could not create <class> '<name>': <reason>.
local copy = h7.new_object("H7Creature", { name = "A_MyCreature", outer = h7.package("MyMod"),
template = h7.find("Creature_Pixie.A_CreaturePixie"), archetype = true })
h7.package¶
The top-level package called name, created if it doesn't exist. Objects inside it have stable paths
(MyMod.A_CreatureFrostSprite), which is what save games store.
Errors: could not create package '<name>': <reason>.
h7.name¶
Adds str to the engine's name table (if it isn't there yet) and returns it. Writing a name property does this
for you; call it yourself when a name must exist before the game looks it up.
Errors: could not create name '<str>'.
h7.can_create¶
true when the engine found and verified the game functions object creation needs. When false, the reason is
in the log and h7.clone, h7.new_object, h7.package (for a new
package) and h7.name (for a new name) fail.
h7.flags¶
Object flags for h7.clone and h7.new_object. Combine them with |.
| Flag | Value | Meaning |
|---|---|---|
Public |
0x0000000400000000 |
Visible outside its package |
Standalone |
0x0008000000000000 |
Kept while its package is loaded, even when unreferenced |
Transient |
0x0000400000000000 |
Never saved |
Archetype |
0x0000000000000400 |
A template other objects are created from |
RootSet |
0x0000000000004000 |
Never garbage collected |
Mods¶
h7.mods¶
Every installed mod, in load order.
| Field | |
|---|---|
name, version, author, description, category |
From the manifest (empty strings when not set) |
enabled |
Whether the mod is turned on (manifest enabled, overridden by the player's choice in H7ModEngine.ini) |
loaded |
Whether it is loaded. enabled but not loaded: it has no main.lua, or is about to load on the next frame. A mod whose main.lua raised an error still counts as loaded (see the log) |
hidden |
Manifest hidden: not listed in the Mods menu |
settings |
How many settings it declares |
h7.set_mod_enabled¶
Turns the mod name on (on truthy) or off. The choice is saved in H7ModEngine.ini and the mod loads or
unloads at the start of the next frame, after which mod_toggled fires. A mod may
disable itself.
Errors: unknown mod '<name>'.
h7.mod_settings¶
The settings the mod name declares, in manifest order, each with its current value:
| Field | |
|---|---|
key, label, description |
As declared (label defaults to key) |
type |
"bool", "number", "choice" or "text" |
value |
Current value |
default |
Declared default |
min, max, step |
Numbers only, when declared |
options |
Choices only: the allowed values |
reload |
Whether changing it restarts the mod |
control |
Numbers only, when declared: "slider", "input" or "arrows" |
Errors: unknown mod '<name>'.
h7.set_mod_setting¶
Changes a setting of mod name, exactly like the Mods menu does: the value is validated (numbers are clamped
to min..max and snapped to step), saved in H7ModEngine.ini, and
setting_changed fires if it changed. Returns the value as stored. A mod changes
its own settings with mod.settings.key = value.
Errors: unknown mod '<name>'; mod <name> has no setting '<key>'; invalid value for <name>.<key> (<type>)
(e.g. a string for a number, or a value not in options).
h7.reload¶
At the start of the next frame, unloads every mod, scans the mods folder again (picking up new mod folders) and
loads every enabled mod; then mods_loaded fires.
Console¶
h7.exec¶
Runs an Unreal console command through the local player controller (PlayerController.ConsoleCommand) and
returns its output. Which commands work depends on the game's (shipping) build.
Errors: no PlayerController exists yet (very early during startup).
h7.clipboard¶
Without an argument, returns the text on the Windows clipboard ("" when it holds no text). With text, puts it
on the clipboard and returns whether that worked. The debug console uses it for Ctrl+V / Ctrl+C.
Networking¶
Network functions return at once (except h7.net.responder_*, h7.net.local_ip and h7.crypto.*, which are
synchronous); the work runs on background threads and fn is called with the result on the
game thread on a later frame, as part of your mod (its log lines and errors carry your mod's name). When your mod
unloads before the result arrives, fn is not called. Verified in game (JSON, h7.net.local_ip, HTTPS GET)
and outside it (build.bat nettest online, which also covers POST, errors and UPnP); UPnP from inside the game
is not verified yet.
h7.http.request¶
Sends an HTTP or HTTPS request. options:
| Field | |
|---|---|
url |
Required; http:// or https:// (anything else raises an error) |
method |
"GET" (default), "POST", "PUT", "DELETE", ... |
headers |
Table of header name → value |
body |
A string, or a table that is sent as JSON (with Content-Type: application/json unless you set one) |
timeout |
Seconds for each phase (resolve, connect, send, receive); default 10 |
res fields: ok (a response arrived), status (e.g. 200; 0 when ok is false), body (string, up to 16 MB),
headers (lower-case names; repeated headers joined with ", ") and error (a message when ok is false). HTTP
errors such as 404 are ok = true. Uses Windows' own HTTP stack (system proxy and certificates).
h7.http.request({ url = "https://example.com/games/42", method = "DELETE", headers = { Authorization = "Bearer " .. token } },
function(res) h7.log("deleted: " .. res.status) end)
h7.http.get¶
Shorthand for a GET h7.http.request.
h7.http.post¶
h7.http.post(url: string, body: string | table, fn?: function(res: table), headers?: table) → id: int
Shorthand for a POST h7.http.request; a table body is sent as JSON.
h7.http.cancel¶
Forgets request id: its fn won't be called. The request itself still completes in the background. Unknown
ids are ignored. Works for h7.net.upnp_* ids too.
h7.json.encode¶
Encodes nil, booleans, numbers, strings, h7.json.null and tables. A table whose keys are exactly 1..n is an
array, as is one marked with h7.json.array; others are objects with keys in sorted order (number
keys become strings), so the same data always gives the same text. pretty indents by two spaces. Raises an
error for functions, userdata, NaN, infinity and nesting deeper than 100 (cyclic tables).
h7.json.decode¶
Parses JSON. Objects become tables, arrays become sequences marked as arrays, null becomes h7.json.null,
whole numbers become integers. Invalid JSON returns nil and a message with the position.
local data, err = h7.json.decode(res.body)
if not data then return h7.warn("bad reply: " .. err) end
h7.json.array¶
Marks t (or a new empty table) so it encodes as a JSON array even when empty, and returns it.
h7.json.null¶
The value JSON null decodes to, and encodes from. Compare with ==.
h7.net.local_ip¶
This PC's LAN address (the one Windows uses to reach the internet), e.g. "192.168.1.20"; nil when offline. No
packets are sent.
h7.net.upnp_add¶
h7.net.upnp_add(port: int, protocol: string, fn?: function(res: table), description?: string) → id: int
Asks the router (UPnP IGD) to forward port (protocol "UDP" or "TCP") to this PC's same port. The engine
talks to the router itself (SSDP discovery from this PC's LAN address, then SOAP), not through Windows' UPnP
component, which can miss the router on PCs with virtual network adapters. If the port is already forwarded to
this PC (the game's own networking maps UDP 27015 as "Heroes VII ..."), nothing is changed and res.existing is
true: don't remove such a mapping later. res: ok, existing, external_ip (the router's public address),
internal_ip, error (e.g. the port is forwarded to another PC, or no router answered). Usually takes about a
second, up to ~8 s when no router answers. description is shown in the router's list (default
"Heroes VII (H7ModEngine)"). Verified in game on the user's router (2026-11-05). The game itself only listens on
UDP (27015); a TCP mapping of the same port is for h7.net.responder_start, which the
OnlineLobby mod maps alongside.
h7.net.upnp_remove¶
Removes the router's mapping of port/protocol. res: ok, error.
h7.net.set_lan_host¶
Makes a server-browser entry point at host ip:port the way a LAN search result does, so the game's own join
(JoinOnlineGame → GetResolvedConnectString → start ip:port) connects there. result is an element of an
array<OnlineGameSearchResult>, typically H7MultiplayerGameManager.SearchResults after :add(). The engine
builds the game's native session record (0x38 bytes on v2.2.1) and stores it in PlatformData; the game deletes
it when the session ends. Raises an error on game builds whose record layout doesn't match. Used by the
OnlineLobby mod. Verified in game: a join attempt to an unreachable host times out cleanly (2026-11-05).
h7.net.responder_start¶
Answers the online lobby's TCP challenge on port from a background thread (H7Lobby host verification).
The listener accepts connections on every interface and keeps answering while the game window is in the
background and the game thread is paused (protocol and threat model: docs/ONLINE.md in the repository). One
connection at a time, each answered at most once:
| Bytes | ||
|---|---|---|
| in | 43 | "H7LOBBY 1 " + 32 lowercase hex characters (the nonce) + "\n" |
| out | 65 | h7.crypto.hmac_sha256(key, nonce) + "\n" |
Anything else, or nothing within 2 s, is closed without a reply. Synchronous; returns true, or false and the
reason ("TCP port 27015 is already in use", ...). Calling it again while it runs on the same port only swaps the
key; another port moves it. The responder belongs to the calling mod and stops when that mod unloads (hot
reload never leaves the port held). Use it with the lobby's protocol only: key is the challenge key derived
from your registration secret (hmac_sha256(token, "H7LOBBY challenge key")), never the secret itself. Windows
may ask the player once whether the game may accept connections. Verified offline (build.bat nettest); not yet
in game.
h7.net.responder_stop¶
Stops the challenge responder and frees its port (waits at most ~2 s for a connection being answered). Harmless when it isn't running.
h7.net.udp_ports¶
The IPv4 UDP sockets the game process has open (ip is "0.0.0.0" for all addresses). A hosting game's listen
socket is among them once the lobby map has loaded; it is on 27015 unless another program holds that port, in
which case UE3 takes the next free one. Synchronous.
h7.net.firewall_check¶
Whether Windows Firewall lets inbound protocol ("UDP" or "TCP") traffic on port reach the game. It
evaluates the enabled inbound rules of the active network profiles like Windows does (a matching block rule
wins; without a rule the profile's default inbound action decides) and calls fn on a later frame with:
| Field | |
|---|---|
ok |
The check ran (false: error says why) |
enabled |
Windows Firewall is on for an active profile |
verdict |
"allowed", "blocked", "no rule" (blocked by default) or "off" |
rule |
The deciding rule's name, when there is one |
profiles |
Active profiles, e.g. "private" |
Third-party firewalls are not seen. Also logged as Firewall: inbound ....
h7.crypto.sha256¶
SHA-256 of data (any bytes) as 64 lowercase hex characters. Windows' own cryptography (CNG); synchronous.
h7.crypto.hmac_sha256¶
HMAC-SHA256 of message under key (both any bytes; a hex string is used as its characters, not decoded), as
64 lowercase hex characters.
h7.crypto.random_hex¶
bytes (1..1024) cryptographically secure random bytes as 2 * bytes lowercase hex characters, for secrets
and tokens (math.random is not suitable).
h7.game_content¶
Describes the community content the game loads from the player's
Documents\My Games\Might & Magic Heroes VII\MMH7Game\CookedPC folder (*.upk, *.umap). Scanned once on a
background thread (about 70 ms for ~600 packages) and cached; fn gets:
| Field | |
|---|---|
modded |
true when any package is there |
packs |
{ {name, files, version}, ... } sorted by name. Each entry of CookedPC\mods is one pack ("mods/Conflux"); every other folder is one pack ("Data", "Visuals"); loose top-level packages are their own pack. version is 8 hex digits hashed from the pack's file names, sizes and package headers: identical copies match, another release of the same mod doesn't (packages carry no version string) |
fingerprint |
16 hex digits over all packs; "" when unmodded |
root |
The scanned folder |
error |
Set if the Documents folder couldn't be found |
LocalShaderCache* packages are machine-generated and skipped.
h7.game_content(function(c)
h7.log(("%s, %d pack(s)"):format(c.modded and "modded" or "base", #c.packs))
end)
Configuration¶
h7.config¶
Reads key from [section] of H7ModEngine.ini. The file is read on every call, so a value the player changes
while the game runs is seen at once. Values are strings; convert them yourself (tonumber).
Returns default (nil if not given) when the key is missing or empty.
Mod settings are better declared in the manifest (settings, shown in the Mods menu); use h7.config for
options that belong in the ini itself.