Releases: jbowensii/MoriaAdvancedBuilder
v6.4.0 — Critical fix for Steam users (Unicode path)
Khazad-dum Advanced Builder Pack v6.4.0
Critical fix: Steam users — ALL file I/O was broken
Fixed: The mod was completely non-functional for Steam installations.
Steam installs Return to Moria to a folder containing a trademark symbol:
C:\Program Files (x86)\Steam\steamapps\common\The Lord of the Rings Return to Moria™\
The mod's working directory path conversion used static_cast<char>() to
convert each wide character to a narrow character. The ™ symbol (Unicode
U+2122) was truncated to 0x22, which is the double-quote character ".
This produced a corrupted path with an illegal character, causing every
single file operation to silently fail:
- Configuration would not load (keybinds, preferences, verbose flag)
- Quick-build slots would not save or restore
- HISM environment removals would not persist
- Definition packs (Game Mods) would not load — F12 Game Mods tab was blank
- GameMods.ini would not read or write
- Icon cache would not save or load
- Removed instances file would not persist
Epic Games Store users were unaffected because their path
(C:\Program Files\Epic Games\ReturnToMoria\) contains only ASCII characters.
Fix: Replaced the per-character static_cast<char> truncation with
WideCharToMultiByte(CP_UTF8), which properly converts the full Unicode
path to a UTF-8 string that Windows file APIs handle correctly. The ™
character is preserved as its UTF-8 byte sequence (0xE2 0x84 0xA2) rather
than being destroyed.
Files changed
dllmain.cpp—on_unreal_init()working directory conversion
Upgrade notes
- Steam users: This is a critical update. All mod features that depend on
file I/O (which is nearly all of them) were silently broken. - Epic users: No functional change — the fix only affects paths containing
non-ASCII characters. - Drop-in replacement. No config migration needed.
v6.3.9 — Installer fix for non-C: drive installs
Khazad-dum Advanced Builder Pack v6.3.9
Installer: custom paths on non-C: drives now work
Fixed: the installer would fail or silently fall back to C: when the game
was installed on drives other than C: (e.g. D:\Games\ReturnToMoria).
Root cause: DefaultDirName was hardcoded to
C:\Program Files\Epic Games\ReturnToMoria\Moria\Binaries\Win64. Inno Setup
uses this value to initialize {app} at setup startup — before any wizard
pages run. The custom path wizard overrides WizardForm.DirEdit.Text in
NextButtonClick, but some Inno Setup internal state was already committed
to C: by the time the override happened, causing path issues on other drives.
Fix: DefaultDirName is now {code:GetDefaultDir}, a dynamic function
that runs at setup init and returns the first valid game path found (Epic,
then Steam, falling back to Epic's path as a placeholder if neither exists).
This ensures {app} initializes on whichever drive actually has the game,
and the custom path page works correctly for any drive.
Additionally audited all mod code and config files:
- C++ mod uses
modPath()for all I/O (dynamic from UE4SS working directory) - No INI, txt, json, def, or Lua file contains hardcoded drive paths
- Server Lua mods (CheatManagerEnabler, ConsoleEnabler) are drive-agnostic
Files changed
installer/KhazadDumAdvancedBuilderPack.iss—DefaultDirNamenow dynamic viaGetDefaultDir()
Upgrade notes
- No mod code changed — the compiled DLL is functionally identical to v6.3.8
- Version bumped in all tracked locations for consistency
- Users on D:, E:, or any non-C: drive can now run the installer and select their custom path
v6.3.8 — Dedicated server fly + pitch/roll offset fix
Khazad-dum Advanced Builder Pack v6.3.8
Dedicated server fly fix
Fixed: flying did not work on dedicated servers.
The server-fly sweep (which sets bIgnoreClientMovementErrorChecksAndCorrection
and bServerAcceptClientAuthoritativePosition on all authoritative dwarf pawns)
was placed after a if (!m_characterLoaded) { ... return; } block in the game
tick. On a dedicated server there is no local pawn, so m_characterLoaded is
never true — the tick returned early and the sweep was never reached.
Fix: Moved the sweep before the character-loaded gate. It now runs
immediately after the m_replayActive check, gated on
(m_characterLoaded || m_isDedicatedServer). On a dedicated server the sweep
runs every 2 seconds and sets the auth flags on all dwarf pawns with
Role == ROLE_Authority.
Verified against UE4.27 engine source: with both flags set,
ServerCheckClientError skips all error checks (including movement mode
mismatch at ServerExceedsAllowablePositionError), and
ServerShouldUseAuthoritativePosition returns true — the server accepts the
client's reported position directly.
Pitch/roll placement position fix
Fixed: pieces placed with pitch/roll were offset from where the ghost showed.
The onBuildNewConstruction hook patched the FTransform quaternion for
pitch/roll but did not adjust the translation. When the rotation pivot (snap
component) is not at the FTransform origin, rotating the piece shifts the
snap point's world position. The ghost preview handled this correctly via
rotateReticleVisual (which rotates all children around the pivot), but the
placed piece used the un-adjusted position.
Fix: After patching the quaternion, the hook now compensates the FTransform
translation: T_new = T + Q_orig * pivot - Q_new * pivot. This keeps the
snap/pivot point at the same world position after rotation, matching the ghost.
Skipped when pivot is at origin (no adjustment needed).
Files changed
dllmain.cpp— server-fly sweep moved before character-loaded gatemoria_placement.inl— FTransform translation compensation in onBuildNewConstruction
Upgrade notes
- Drop-in replacement. No config or INI changes needed.
- Dedicated server operators: deploy the new
main.dllto both the server AND all clients.
v6.3.7 — Fix rotation step
Khazad-dum Advanced Builder Pack v6.3.7
Rotation step fix (root cause found)
Fixed: custom rotation step (e.g. 15°, 45°) only worked for the first piece, then reverted to 90° on all subsequent rotations.
Root cause: The v6.1.0 multiplayer stability pass added an isLocalContext(context)
guard to the RotatePressed/RotateCcwPressed pre-hook. But the context for
these functions is the BuildHUD — a UMG widget whose Outer chain goes to
WidgetTree → GameInstance, never reaching m_localPC or m_localPawn. So
isLocalContext() returned false and the entire rotation logic was silently
skipped on every rotation press.
The first piece worked because onGhostAppeared() (called from blockSelectedEvent,
which correctly has no isLocalContext guard) applied the step when selecting the
recipe. After that, every RotatePressed was blocked.
Three other UMG widget hooks (OnAfterShow, OnAfterHide, blockSelectedEvent)
had already had this same guard removed with comments explaining why. The
RotatePressed hook was missed during the v6.1.0 pass.
Fix: Removed the isLocalContext guard from the RotatePressed/RotateCcwPressed
hook. The m_isDedicatedServer check remains (correct — dedicated servers have
no local UI). On a client, only the local player's BuildHUD exists, so there is
no cross-player bleed risk.
Also reverted the v6.3.0 m_lastGATA tracking inside resolveGATA() back to
the clean v6.0.0 pass-through pattern. The tracking was unnecessary — the
rotation step is applied unconditionally in the RotatePressed hook and in
onGhostAppeared(), so auto-detecting new GATAs added complexity without benefit.
Files changed
dllmain.cpp— removedisLocalContextguard from RotatePressed/RotateCcwPressed, reverted RotatePressed to v6.0.0 patternmoria_placement.inl— revertedresolveGATA()to clean pass-through (removedm_lastGATAtracking + member variable)
Upgrade notes
- Drop-in replacement. No config or INI changes needed.
- Server operators: replace
main.dllin<server>/ue4ss/Mods/MoriaCppMod/dlls/.
v6.3.6 — Rotation step fix + replenish MP fix
Khazad-dum Advanced Builder Pack v6.3.6
Rotation step fix
Fixed: custom rotation step (e.g. 15°, 45°) reverted to 90° on each rotation.
The game's GATA (ghost actor) resets SnapRotateIncrement and
FreePlaceRotateIncrement to 90° internally. The mod's RotatePressed
pre-hook applied the custom step via resolveGATA(), but that path went
through a cached BuildHUD reference that could miss a freshly spawned GATA.
Fix: The RotatePressed/RotateCcwPressed pre-hook now reads the
TargetActor weak pointer directly from the BuildHUD context object
(which is always the live, current BuildHUD). This guarantees the custom
rotation step is applied to the correct GATA on every single rotation,
regardless of whether the GATA was just spawned or has been alive for a while.
Falls back to the cached resolveGATA() path if the direct read fails.
Replenish item multiplayer fix
Fixed: replenish item did not work reliably when hosting a game or on a server.
RequestAddItem is a local-only function on the inventory component. On a
non-host client, calling it via ProcessEvent executed locally but never reached
the server — items either never appeared or vanished immediately.
Fix: Switched to ServerDebugSetItem(Item, Count), which is a proper
Server RPC (UE4 Server-prefixed UFUNCTION). The engine automatically routes
it to the authoritative machine. Works identically for host, remote client,
and standalone.
Note: ServerDebugSetItem sets the item count to an absolute value rather
than adding a delta. The mod passes MaxStackSize directly, which always
fills the stack to maximum.
Files changed
dllmain.cpp— RotatePressed/RotateCcwPressed pre-hook reads TargetActor directly from contextmoria_inventory.inl—ServerDebugSetItemreplacesRequestAddItemin replenish flow
Upgrade notes
- Drop-in replacement. No config or INI changes needed.
- Server operators: replace
main.dllin<server>/ue4ss/Mods/MoriaCppMod/dlls/.
v6.3.5 — Multiplayer fly fix
Khazad-dum Advanced Builder Pack v6.3.5
Multiplayer fly fix
Fixed: only the first player to join could fly.
Two underlying bugs worked together to break fly for every player after the first:
-
Wrong player controller lookup. In multiplayer, the engine keeps replicated
proxies of every remote player'sPlayerControlleralongside the local one.
The mod'sfindPlayerController()returned the first PC it found, which for
the second/third player was usually the host's replicated proxy — sogetPawn()
returned the host's pawn and the fly command went to the wrong character.Fix:
findPlayerController()now walks the PC list and filters by the
bIsLocalPlayerControllerreflection property, guaranteeing it returns the
actually-local PC on every machine. Every downstream caller (getPawn(),
m_localPC/m_localPawncaching, all hook guards, character-load detection)
inherits the fix. -
Server-auth movement flags only set on the host's own pawn. The v6.1.0
multiplayer stability pass restricted the client-authoritative movement flags
(bIgnoreClientMovementErrorChecksAndCorrection,
bServerAcceptClientAuthoritativePosition) to the local pawn. On a listen
server that meant only the host's server-side pawn had the flags — every
joining client would be snapped back to the ground by movement validation,
even after the client-side fly toggle succeeded.Fix: The authoritative machine (listen-server host or standalone) now
sweeps allBP_FGKDwarf_Cpawns every 2 seconds and sets the client-auth
flags on each pawn whoseRole == ROLE_Authority. Remote clients don't
run the sweep (their local pawn isAutonomousProxy, notAuthority),
so no cross-player bleed.
Combined result: fly works for every player in multiplayer, regardless of join
order. New joiners get server-auth movement within ~2 seconds of spawn.
Controller UI removal
The Controller enable checkbox and Xbox/PS5 profile selector in F12 → Game Options
have been removed. Controller input is now permanently disabled.
- Removed the Controller row widget from the Game Options tab
- Removed the two click handlers and the row's y-offset slot
- Removed
Controller=andControllerProfile=persistence from
MoriaCppMod.ini(stale values in existing INIs are ignored) - The underlying gamepad/DualSense code is still compiled in but permanently
gated off — no functional change for keyboard+mouse users
Files changed
- moria_common.inl —
findPlayerController()filters bybIsLocalPlayerController - dllmain.cpp — server-fly sweep, F12 Controller row removal, y-offset shift
- moria_widgets.inl — Controller row widget deleted
- moria_quickbuild.inl — Controller INI load/save deleted
Upgrade notes
- No config migration needed. Existing
MoriaCppMod.inifiles are forward-compatible;
theController=andControllerProfile=lines are silently ignored. - Server operators: drop the new
main.dllinto
<server>/ue4ss/Mods/MoriaCppMod/dlls/main.dll. No INI changes required.
v6.3.0 — Controller Profiles, DirectInput, Bug Fixes
v6.3.0 — Controller Profiles (Xbox/PS5), DirectInput, Rotation Fix
Gamepad Controller Support
F12 → Game Options now has a Controller toggle + Xbox/PS5 profile selector at the top.
How It Works
- D-pad Left = Toggle mod toolbar mode ON/OFF (both profiles)
- When ON: gold highlight appears on first mod slot, game input blocked on pawn
- When OFF: all game input restored normally
- Double-click D-pad Left = leaves the callout menu visible (normal game function)
Controller Button Mapping (while mod toolbar is ON)
| Xbox | PS5 | Action |
|---|---|---|
| RB | R1 | Next mod slot (wraps around) |
| LB | L1 | Previous mod slot (wraps around) |
| X | Square | Select / activate highlighted slot |
| A | Cross | Modifier action (SHIFT) or Cancel Build (ESC on QB) |
| B | Circle | Exit mod toolbar mode |
| D-pad Left | D-pad Left | Exit mod toolbar mode |
Mod Toolbar Slot Order
AB(1 slot) → MC(9 slots) → QB(8 slots) = 18 navigable slots
Mod Controller Toolbar (9 Slots)
| Slot | Name | X/Square | A/Cross (Modifier) |
|---|---|---|---|
| 0 | Rotation | Increase step (5°→90°) | Decrease step (90°→5°) |
| 1 | Snap Toggle | Toggle snap on/off | — |
| 2 | Stability Check | Run integrity audit | — |
| 3 | Super Dwarf | Toggle character visibility | Toggle fly mode |
| 4 | Target / Inspect | Crosshair + inspect | Build From Target |
| 5 | Reserved | — | — |
| 6 | Remove Single | Remove aimed object | — |
| 7 | Undo Last | Undo last removal | — |
| 8 | Remove All | Remove all of type | — |
Technical Details
- Xbox profile: XInput polling (reads hardware directly, unaffected by UE4 input blocking)
- PS5 profile: DirectInput polling via IDirectInput8 (shared access, works on Epic Games Store)
- DualSense raw HID reader included as fallback (moria_dualsense.h)
- Game input blocked via
bBlockInputon pawn while mod toolbar ON - ProcessEvent pre-hook suppresses action bar + input state functions during mod mode
- Delayed ESC (3 frames) dismisses callout triggered by D-pad Left; cancelled on double-click
Bug Fixes
Rotation Step Not Applied to New Pieces — FIXED
- Each new build piece previously reset to 90° rotation, requiring F9 press before each placement
resolveGATA()now auto-detects new GATA objects and applies the current rotation step immediately- Set rotation step once with F9, it persists across all subsequent piece selections
F12 Tab Switching Crash — FIXED (from v6.1.0)
- Tab content panels now toggled via visibility instead of ClearChildren/AddChild
- Prevents MovieScene animation crash (UUMGSequenceTickManager dangling pointer)
SHIFT+F Key Quickbuild Assignment — FIXED
bBlockInputremoved from PlayerController (was blocking keyboard input)- Only pawn gets bBlockInput; keyboard shortcuts work normally
Changes Since v6.2.0
- New:
moria_dualsense.h— DualSense raw HID reader with overlapped I/O + BT simple/extended support - New:
DIGamepadReaderclass — DirectInput gamepad polling (shared access, all controller types) - New: Controller enabled/profile state persisted to INI
- New: XInput linked for Xbox, DirectInput + HID for PS5
- New:
m_lastGATAtracking in resolveGATA() for rotation step auto-application - New: Diagnostic logging for replay mesh IDs, saved removal details, replenish DataTable status
- Fixed: Rotation step lost on new build piece selection
- Fixed: Keyboard shortcuts blocked while controller mod toolbar ON
Full installer (code-signed): KhazadDumAdvancedBuilderPack_v6.3.0_Setup.exe
v6.2.0 — Gamepad Controller Support
v6.2.0 — Gamepad Controller Support + Stability Fixes
🎮 Gamepad Controller Support (NEW)
Full gamepad navigation for all mod toolbars via XInput. Navigate seamlessly from the game's built-in action bar to all mod toolbars using LB/RB.
Navigation: LB/RB moves slot-by-slot through a continuous 27-slot strip:
- RB → Game slots 0–8 → NUM+ → MC 0–8 → QB 0–7 → back to Game slot 0
- LB ← Game slots 8–0 → QB 7–0 → MC 8–0 → NUM+ → back to Game slot 8 (Epic Item)
The currently selected mod slot is highlighted in gold. The game's action bar is disabled while browsing mod toolbars to prevent double-activation.
Quick Build Toolbar (8 Slots — F1 through F8)
| Button | Action |
|---|---|
| X | Activate the highlighted recipe |
| A | Cancel current build placement (ESC) |
Mod Controller Toolbar (9 Slots)
| Slot | Name | X Button | A Button (Modifier) |
|---|---|---|---|
| 0 | Rotation | Increase step (5°→90°) | Decrease step (90°→5°) |
| 1 | Snap Toggle | Toggle snap on/off | — |
| 2 | Stability Check | Run integrity audit | — |
| 3 | Super Dwarf | Toggle character visibility | Toggle fly mode |
| 4 | Target / Inspect | Crosshair + inspect | Build From Target |
| 5 | Reserved | — | — |
| 6 | Remove Single | Remove aimed object | — |
| 7 | Undo Last | Undo last removal | — |
| 8 | Remove All | Remove all of type | — |
NUM+ Toolbar (1 Slot)
| Button | Action |
|---|---|
| X | Toggle visibility of QB + MC toolbars |
Button Summary
| Button | Action |
|---|---|
| RB | Next slot (cycles through all toolbars) |
| LB | Previous slot (cycles through all toolbars) |
| X | Select / Activate highlighted slot |
| A | Modifier (SHIFT) or Cancel Build on QB |
Tip: EasySMX X20 back buttons (M1–M4) can be remapped on the controller to mirror X or A.
Stability Fixes (since v6.1.0)
- Startup crash fix: UButton IsPressed() resolved via StaticFindObject instead of per-object GetFunctionByNameInChain (prevents crash on GC'd buttons)
- UButton polling guarded: Only polls when parent toolbar widget exists
- SetFocus removed from creation: Was stealing focus from game's player selection screen
- Character detection: Requires BP_FGKDwarf class name match (prevents toolbar appearing during lobby)
- Game action bar suppression: Set to HitTestInvisible in mod toolbar mode, ProcessEvent pre-hook blocks HUD Focus From Controller / HotBarActionRequest / Navigate To Epic Item
- Game bar focus restore: Returns to correct slot on exit (RB→slot 0, LB→slot 8 Epic Item)
Changes from v6.1.0
- All toolbar slots (QB 8, MC 9, AB 1) wrapped in transparent UButton widgets
- XInput.h linked for direct gamepad state polling
- HUD Focus From Controller hook tracks game action bar index
- 9-slot game hotbar (0-7 + Epic Item at index 8)
- Diagnostic logging for button mapping and action bar function names
Full installer (code-signed): KhazadDumAdvancedBuilderPack_v6.2.0_Setup.exe
v6.1.0 — Multiplayer Stability + Gamepad Support
v6.1.0 — Multiplayer Stability Fixes + Gamepad UButton Prototype
Multiplayer Fixes
- Rotation bleed fix: One player rotating a building piece no longer affects other players' pieces
- Fly mode fix: toggleFlyMode now targets only the local player's pawn (was flying ALL players)
- Hide character fix: toggleHideCharacter now targets only local pawn (was hiding ALL dwarves)
- Server fly setup: Client-authoritative movement flags only set on local pawn at character load
- ProcessEvent hook guards: All hooks now check isLocalContext() to prevent cross-player state contamination
- Dedicated server early-return: All post-callback hooks skip on dedicated server
- Listen server protection: isLocalContext() uses cached local PC/Pawn with Outer chain walk
- Character detection: All FindAllOf("BP_FGKDwarf_C") replaced with getPawn()
- Save game and inventory audit: Use getPawn() instead of dwarves[0]
F12 Config Menu Fixes
- Tab switching crash fix: Visibility toggle instead of ClearChildren/AddChild (MovieScene crash)
- Blank tab pane fix: All tab content panels stay in widget tree permanently
Gamepad Support (Prototype)
- All toolbar slots wrapped in transparent UButton widgets (MC 9, QB 8, AB 1)
- D-pad/stick navigation between buttons via native Slate hit test grid
- Gamepad A/Cross fires click on focused button via IsPressed() polling
- Initial focus on AB button (always visible, toggles other toolbars)
README
- Comprehensive non-technical feature documentation
- Validated against source code
Full installer (code-signed): KhazadDumAdvancedBuilderPack_v6.1.0_Setup.exe
v6.0.0 — Server Build + Multipart Ghost + 3D Rotation
What's New in v6.0.0
Server Installation Support
The installer now includes an optional checkbox to create a Server Installation folder on your desktop (or any location you choose). This folder contains everything needed to deploy the mod on a Return to Moria dedicated server:
- All client mod files (UE4SS framework, MoriaCppMod, definitions)
- CheatManagerEnablerMod (Lua) — enables admin/cheat commands: fly, walk, god, ghost, teleport, slomo
- README_SERVER.txt — comprehensive, non-technical guide covering step-by-step server installation, every configuration option explained in plain English, game mod pack descriptions, and cheat command reference
The server folder is self-contained — just copy its Win64/ contents to your server's game directory.
Multipart Ghost Pitch/Roll — SOLVED (from v5.5.2)
Building pieces with multiple mesh components (walls, battlements, etc.) now rotate correctly as one solid assembly in both the ghost preview and when placed.
Root cause: Quaternion helper functions had X,Y components negated vs UE4's conventions, causing position and orientation transforms to be inconsistent. All quaternion math now matches UE4's FRotator::Quaternion() / FQuat::Rotator() exactly.
Full 3D Building Rotation
- Pitch (comma key): tilt forward/backward
- Roll (period key): tilt sideways
- Both work with any rotation step size
- Ghost preview matches placed result exactly
- Placement uses piece-local frame (existingQ * pitchRollQ)
Crash Fixes
- SEH in isObjectAlive(): Catches access violations on fully-freed UObject pointers
- Slate PaintFastPath: Collapse containers before ClearChildren() to prevent stale SWidget crashes
- F12 menu: Keybind click + scrollbar crashes fixed
- showInfoMessage: Stale errorBox widget auto-recreated
- 25+ isObjectAlive guards added across all UMG widget helpers
Game Thread Migration
All mod logic runs on the game thread via EngineTick callback. on_update() is empty.
Build Info
- UE4SS v4.0.0-rc1 (custom-built, commit 0bfec09e)
- VS2026 (v18), MSVC v14.50, CMake 4.1.2
- 286 tests passing
- Debug mode OFF
Installer
Signed installer: KhazadDumAdvancedBuilderPack_v6.0.0_Setup.exe (6.75 MB, code-signed)