OBR Mod Creation Wiki
A complete guide from first texture swap to full custom armor with cloth physics, scripted spells, and new rooms - every step in order, every tool linked, honest about what works in mid-2026.
The One Thing You Need to Know
Oblivion Remastered is two game engines stacked together. Once you understand this, everything else clicks.
Unreal Engine 5.3.2 - visual
Models · textures · lighting · materials · animations · UI · cloth physics · audio (Wwise)
Gamebryo 2006 - logic
Stats · quests · scripts · AI · die rolls · item records · cells · saves
What This Covers
- Retextured & reskinned items
- Brand-new weapons & armor (full 3D pipeline)
- Capes & cloth physics workarounds
- Custom animations & retargeting from any source
- Scripted abilities via OBScript + Lua bridge
- New interior rooms & player homes
- Custom creatures via blueprint duplication
- Facial animation with MetaHuman / DNA files
- Wwise audio integration & External Sources
- UE4SS Lua scripting - hooks, snippets, recipes
- Blueprint behavior mods (custom UI, spawning)
- Debugging, crash logs & rapid testing workflow
- Mod distribution, compatibility patching & save safety
- NPCs, custom races, hair modding
- Dialogue, voice lines, lip sync workarounds
Known Limits (mid-2026)
- UE-side terrain editing (no clean workflow yet)
- New exterior worldspace cells (unsolved)
- Custom Chaos Cloth Assets (crash on load)
- Brand-new standalone sounds (experimental only)
- NPC pathfinding in new cells (no navmesh)
- Multiple enchantments (backend blocks it)
- Fully custom voiced dialogue (BNK generation)
- Game Pass / Epic stores (no OBSE64)
- Classic .nif meshes (dead format for rendering)
- Arbitrary body slot expansion (Altar enums fixed)
Your Learning Path
These steps are designed to be followed in order - each builds on the last.
How Modding Works
The dual-engine model. Ten minutes that make everything else make sense.
Set Up Your Tools
Every tool, in order, with links. FModel, xEdit, UE4SS, UE 5.3.2, Blender - all of it.
Make Mods: Basics
Retexture, swap sounds, edit stats, add new gear with existing models. Easiest first.
Make Mods: Advanced
Custom 3D armor, cloth physics, animations, MetaHuman faces, Wwise audio, new rooms.
Scripting & Custom Code
OBScript, OBSE64, UE4SS Lua - the deepest layer, where "impossible" mods live.
Reference - Always Here
CTD fixes, what-works status board, full glossary, console commands.
All Links & Resources
Every tool, every Nexus mod, every guide - every link in one place.
Step 1 of 5
How Modding This Game Works
Before installing a single tool, understand the one idea that reframes every decision you will ever make about Oblivion Remastered modding.
The Dual-Engine Architecture
When you look at Oblivion Remastered you see Unreal Engine 5 graphics. But underneath, the original 2006 Gamebryo engine is still running the actual game - quests, stats, scripts, AI, die rolls. UE5 handles rendering only.
The original Oblivion.esm is essentially byte-identical to the classic game. The remaster’s changes are layered on top through the Altar ESPs rather than rewriting the master file.
The Face - Unreal Engine 5.3.2 visual
Models · textures · materials · lighting · menus · animations · cloth physics · audio (Wwise)
The Brain - Gamebryo 2006 logic
Logic · data · rules · OBScript · AI packages · inventory · quests · cells · saves
Two Kinds of Mod Files
brain ESP / ESM - Logic
Classic Bethesda plugin files. Item names and stats, creature health, quest steps, AI packages, script behavior. Same format as the 2006 game.
Location: ...\\Content\\Dev\\ObvData\\Data\\
visual PAK - Visuals
Unreal Engine content packages (.utoc + .ucas + .pak) containing all 3D models, textures, materials, and animations. All three files must be present together.
Location: ...\\Content\\Paks\\~mods\\
.nif files and textures were BSA-packed. None of that applies to the visuals here. UE5 reads all models and textures from PAK files. The old NIF format is dead for rendering. Loose BSA assets are simply ignored. This invalidates a huge amount of 15-year-old modding knowledge - exactly why this game-specific codex exists.The Altar ESPs (never disable)
The remaster ships three special plugins that must stay enabled and correctly ordered at all times:
AltarESPMain.esp- applies the remaster’s record changes over the vanilla originals. Crucially, it rewrites all item names to UE5-managed localization keys (LOC_FN_IronSword, etc.). These keys are resolved at runtime from the UE localization system in the PAK files.AltarDeluxe.esp- Deluxe Edition content (present in all editions).AltarESPLocal.esp- referenced in Plugins.txt; may not be physically present on disk.
AltarESPMain rewrites every vanilla item name to a localization key, if your mod edits a vanilla record without forwarding that change, the display name reverts to the raw Gamebryo string and shows up tagged [nl]Something in-game. The NL-Tag Remover mod (mods/473) fixes this artifact on your new items.The FormID Bridge - How the Engines Talk
Every item, NPC, and cell with a visual representation has a corresponding UE5 asset (Blueprint / form file) in the PAK files. This is how they connect:
- Gamebryo processes game logic (equipping a sword, placing an NPC) using internal FormIDs.
- Altar receives the FormID, converts it from hex to decimal integer, and looks it up in a pre-loaded map called the SyncMap.
- The SyncMap maps FormID → UE asset path.
- UE loads and renders the corresponding Blueprint/mesh/material.
0x0003AA82. UE form files use the decimal equivalent: 240258. The first two hex digits (e.g. 03) are the load order index - always zero these out when referencing from the UE side. Formula: int(hexFormID, 16) with the first byte zeroed.| Adding a new sword requires | Engine | File type |
|---|---|---|
| The sword’s name, damage, weight, value, FormID | brain | ESP record |
| The actual 3D model and texture the player sees | visual | PAK asset |
| Wiring the ESP record to the PAK model | bridge | SyncMap .ini |
.ini. A genuinely new model still requires the full UE5 asset pipeline.The PAK Container Format
Oblivion Remastered uses UE5’s IO Store format, not traditional loose PAK files. This means every visual mod is three files that must all be present together:
.pak- loose files (config, fonts), packed with standard UnrealPak.ucas- Content Addressable Store, contains all cooked.uassetdata.utoc- Table of Contents, the index for the.ucasfile
The global.ucas / global.utoc pair provides dependency resolution for the whole game.
No Official Toolkit
- Bethesda does not officially support mods for Oblivion Remastered and there is no Construction Kit built for the remaster.
- The entire toolchain is community-built and partly reverse-engineered since April 2025. Capabilities change month to month.
- The Xbox Game Pass version shipped with extra debug data which significantly accelerated community reverse engineering.
- There is an estimated ~30% chance an official CK will be released - no announcement as of mid-2026.
- The Altar plugin code is proprietary (licensed third-party components from Virtuous) and is extremely unlikely to ever be publicly released.
Step 2 of 5
Set Up Your Tools
From zero to a fully working modding workbench. Follow these steps in order - each is the foundation for the next.
Confirm Your Install & Back Up
- Steam build only. OBSE64 supports the Steam version only - not Game Pass, Microsoft Store, or Epic. If you own a non-Steam version, most of this codex still applies but scripting tools will not.
- Note your exact game version (build number visible in-game) - several tools are matched to specific builds and will break on updates.
- Back up your save files and make a clean copy of
Plugins.txtbefore installing anything.
Mod Manager - pick one
- pick oneMod Organizer 2 - profiles + virtual file system. Recommended for creators - keeps your game folder clean. Install the MO2 OR plugin (mods/366) for automatic argument passing.
- altVortex - Nexus’s official manager, beginner-friendly. nexusmods.com/site/mods/1
- altNORMM - dedicated Oblivion Remastered manager built specifically for this game.
Loaders & Bridge bridge
The foundation everything else runs on. Install in this exact order.
- requiredOBSE64 - script extender / DLL plugin loader. Drop
.dll+.exeinBinaries\Win64. Currently only a plugin loader - not a full script extender yet. Steam only. - requiredAddress Library for OBSE Plugins (mods/4475) - makes OBSE DLL plugins version-independent. Required by most plugins.
- requiredUE4SS (mods/32, OR build v3.0.1) - Blueprint mod loader and Lua scripting runtime for UE5. Required by TesSyncMapInjector. Install to
Binaries\Win64\. Use the Nexus OR build, not the GitHub experimental build - the generic build doesn’t hook into UE 5.3 properly. - requiredTesSyncMapInjector (mods/1272) - the bridge that links new ESP items to UE5 meshes at runtime. Runtime UE4SS Lua mod.
- when neededMagicLoader + MagicPatcher (mods/1966) - enables new interior cells. Install when you reach Lesson 14.
- handyOBRConsole (mods/2205) - lets UE4SS Lua mods run Oblivion console commands. ⚠ Crashes if you tab out of the game while active.
- altSML / Simple BP Mod Loader (mods/1172) - alternative Blueprint loader with better input action recognition and documentation.
UE4SS Configuration visual
After installing UE4SS to Binaries\Win64\, configure it for development work:
UE4SS-settings.ini - enable the GUI console
[Debug] ConsoleEnabled = 1 GuiConsoleEnabled = 1 GuiConsoleVisible = 1 GraphicsAPI = d3d11
With this enabled, UE4SS opens a GUI window alongside the game showing logs, a live object viewer, and function hooks. Press Ctrl+Numpad 6 in the GUI console to dump an up-to-date .usmap mappings file.
.usmap file after any game patch - asset structures change between updates. The Nexus mappings file may be outdated. UE4SS generates your own from the live game.Hot Reloading Lua Mods
Enable hot reloading in ue4ss-settings.ini and press Ctrl+R to reload Lua mods without restarting the game. Note: mods that hook into game-spawned actors may not hot-reload cleanly.
ESP / Data Tools brain
- requiredxEdit 4.1.5n+ (TES4R / OR build) - primary ESP authoring and conflict detection. Get the OR-compatible build from the official xEdit Discord
#xedit-buildschannel - not the standard release. Launch with:TES4R64.exe -D:"...\ObvData\Data" -I:"...\ObvData\Oblivion.ini" - requiredNL-Tag Remover (mods/473) - fixes the
[nl]artifact on new item names caused by Altar localization. - requiredLOOT - automatic load-order sorting. Sort with LOOT, then hand-fix the special cases below.
- handyRuntime EditorIDs (mods/1331) - surfaces EditorIDs in the in-game console for quick testing.
- situationalConstruction Set (2006 original) + CSE - visual editor for cells and containers. Setup details below. Never load the Altar ESPs in the CS - they crash it.
Construction Set Extender (CSE) Full Setup
- Download: OBSE for original Oblivion · CSE (mods/36370) · Official Construction Set · vcredist_x86.exe
- Extract all into your
ObvDatadirectory. - Run
TES4_Construction_Set_1.2.404.exeand install to yourObvDatafolder. Ignore the “Oblivion not installed” error. - Edit
Launch CSE.batand replace its contents with:obse_loader.exe -editor -notimeout - Right-click
Launch CSE.bat→ Run as Administrator.
- Your saved
.espappears inObvData\Data\Data- move it up one level toObvData\Dataand add it toplugins.txt. - For protected directories: create a file named
BGSEE_DirectoryCheckOverride(no extension) in ObvData. - For MO2 without elevation: create
BGSEE_ElevatedPrivilegesCheckOverride(no extension) in ObvData. - Missing DLL error: install
vcredist_x86.exe- the x64 version alone is insufficient. - Use the CSE beta from the Nexus Mirrors tab for fixes to script editor crashes and dialog size issues.
UE5 Asset Pipeline visual
Large downloads - install now so the workbench is complete before you need them.
- requiredBlender + PSK/PSA plugin - model and animation authoring. If the current PSK plugin breaks on your imports, try the older Befzz plugin.
- requiredUnreal Engine 5.3.2 - cook art assets. Use 5.3.2 specifically - not the latest version. Install via the Epic Games Launcher to an unprotected path like
C:/, not Program Files or Desktop. - requiredFModel + mappings file (.usmap, mods/47) - browse and extract game assets. Set Archive Directory to game root (not a subfolder), UE Version to
GAME_UE5_3. - requiredOR Mod Tools bundle (mods/3918) - retoc + UAssetGUI + oo2core in one package. Use the Nexus version of UAssetGUI, not the GitHub release (it has a critical PackageName bug fix).
- materialsJsonAsAsset (C0bra5+Tectors fork recommended) + j0.dev - import vanilla materials from FModel JSON exports into your UE project.
- armor/formsC.A.F.E. (mods/4891) - CosmicBoogaloo’s tool for setting asset paths in armor and weapon forms quickly.
- texturesNNRM Merge/Split Tool (mods/3051) - split and recombine the NNRM channel-packed textures that OBR uses.
- blueprintsOR SDK (Kein/Altar) + Visual Studio 2022 - the Altar stub project for custom blueprints and cloth physics. No full Unreal Engine source build required.
- nanitesimple-nanite-parser (c0bra5) - extract full-detail Nanite meshes that FModel only exports the fallback version of.
- audiosound2wem - convert audio files to Wwise
.wemformat for sound replacement. - audiowwiser (github.com/bnnm/wwiser) - browse Wwise
.bnksoundbank files to locate specific.wemaudio file IDs.
Build the Altar SDK Project (for Blueprints & Cloth Physics)
Required only if you’re doing custom blueprints, cloth physics, or C++ plugins. Skip if you’re only doing texture/sound/ESP work.
- Download and extract github.com/Kein/Altar.
- Install Visual Studio 2022 with the workload: Game Development with C++ + individual component:
MSVC v143 - VS 2022 C++ x64/x86 build tools (v14.38-17.8). - Right-click
OblivionRemastered.uproject→ Generate Visual Studio project files. - Open
OblivionRemastered.slnin VS2022 → right-click the project in Solution Explorer → Build. Wait for it to finish, then close VS. - Open the
.uprojectwith UE 5.3.2. - On first launch: if it warns about a missing water collision channel, click “Add to Engine.ini” and continue.
C:\Users\YOU\AppData\Roaming\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml:
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
<WindowsPlatform>
<CompilerVersion>14.38.33130</CompilerVersion>
<ToolchainVersion>14.38.33130</ToolchainVersion>
</WindowsPlatform>
</Configuration>Enable Chunking in UE (Required for Packaging)
Edit → Project Settings → Packaging: enable Generate Chunks and Use IO Store. Disable Share Material Shader Code.Edit → Editor Preferences: search “chunk” → enable Allow ChunkID Assignments.
Creating a Chunk Assignment
- In the Content Browser, create a Data Asset → Primary Asset Label.
- Set: Priority = 1 · Chunk ID = any number 1–300 · uncheck Apply Recursively · Cook Rule = Always Cook · check Label Assets in My Directory.
- Name it
ChunkYOURNUMBERfor easy reference.
First Launch & Verify
- Launch the game once WITHOUT OBSE so UE4SS can configure itself properly.
- Then launch through OBSE.
- Verify OBSE is working (in-game version check command:
GetObseVersionin console). - Verify UE4SS is working (its GUI console window should appear).
- Check load order in LOOT - Altar/base ESPs should be present and correctly ordered.
Plugins.txt backup is still safe before doing anything else.Community Starter Packs
The community has assembled pre-packaged tool bundles to get you started faster:
Step 3 of 5
Make Mods: The Basics
Your first real mods - easiest first. Retexture, swap sounds, edit stats, add new gear without any 3D work required.
Retexture or Recolor Something visual
Changing how an existing item, surface, or character looks by editing its texture. This is where everyone should start.
- Open FModel. Set Archive Directory to your game root (the folder containing
OblivionRemastered.exe), UE Version toGAME_UE5_3, and load your.usmapmappings file. - Browse the Folders tab to find your texture. Right-click → Save Texture. Always use TGA format, not PNG - TGA preserves all channels including alpha, which PNG discards.
- Edit the image in Photoshop, GIMP, or Substance. The game uses DirectX normal map format - if you’re working with normal maps, the Y channel is not inverted (unlike OpenGL).
- Import into your UE project at the exact same folder path as the original. Check FModel to match the original’s Texture Group and Compression Settings.
- Assign to a chunk. Package. Rename output files with
_Psuffix. Drop all three files (.pak/.ucas/.utoc) in~mods.
OBR packs multiple maps into one image in the NNRM format (Normal/Normal/Roughness/Metallic):
| Channel | Contains | Notes |
|---|---|---|
| R | Normal X | DirectX format |
| G | Normal Y | DirectX format (NOT inverted like OpenGL) |
| B | Roughness | Black = shiny, White = matte |
| A (alpha) | Metallic | Black = non-metal, White = full metal |
Variants: NNRS (specular instead of metallic) · NNRE (emissive) · NNR (no metallic) · NNRAO (ambient occlusion).
Import settings in UE: use BC7 compression and untick sRGB. Do not use the Normal compression preset for NNRMs - it breaks them.
In Blender: when working with OBR assets, invert the Green channel of the NNRM (UE uses DirectX -Y, Blender uses OpenGL Y). Set color space to Non-Color.
Use the free NNRM Merge/Split Tool (mods/3051) to split channels and recombine them.
ffmpeg command - preserve RGB, set alpha=255
ffmpeg -i input.png -filter_complex "[0]geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='255'" out.png
Making an NNRM in Photoshop
- Open your Normal, Roughness, and Metallic maps.
- In the Channels panel of a new document: paste Roughness → Blue · Metallic → Alpha · Normal R → Red · Normal G → Green.
- Save as .TGA 32-bit - PNG will discard the alpha channel.
NNRM ffmpeg Split Scripts (c0bra5)
Split an NNRM into component images
# Normal XY channels (produces a blueish image - that's correct) ffmpeg -hide_banner -v error -i "%1" -filter_complex "[0]geq=r='r(X,Y)':g='g(X,Y)':b='255':a='255'" "%~n1_n.png" # Roughness (from B channel) ffmpeg -hide_banner -v error -i "%1" -filter_complex "[0]geq=r='b(X,Y)':g='b(X,Y)':b='b(X,Y)':a='255'" "%~n1_r.png" # Metallic (from Alpha channel) ffmpeg -hide_banner -v error -i "%1" -filter_complex "[0]geq=r='255':g='255':b='255':a='p(X,Y)'" "%~n1_m.png"
For a model with no normal map detail, create a flat NNRM: set R=128, G=128 (neutral normal), fill B with your roughness value, fill Alpha with your metallic value.
Skin materials use SubsurfaceProfile (SP_SkinBase). Diffuse/BaseColor format: RGB = color, A = emissive mask. Hair textures use the _RAUD suffix multi-channel format - always export TGA from FModel, the alpha channel contains the opacity/strand mask.
Swap or Replace a Sound visual
The game uses Audiokinetic Wwise for all audio. Sounds are stored as .wem files (Wwise Encoded Media) inside .bnk soundbank containers.
- Use FModel + retoc to unpack the
.wemaudio file you want to replace. - Convert your replacement audio to
.wemformat using sound2wem. Alternatively, use older Wwise installer tools; see the RE Modding forum guide. - Swap in your audio keeping the exact same filename. Repack with retoc into a
_Ppak. Drop in~mods.
Finding a Specific Voice Line
- In FModel, browse to
Localization/String Tables. Find the line you want and copy its event name number (looks like00047660). - Browse to
WWise/Event/English. Find the.bnkbank file matching the NPC race and dialogue category. - Open that bank in wwiser and navigate the tree to find your number in a
CAkSoundnode. That node’s value is the SOURCE ID - the number matching the corresponding.wemfile. - Extract that
.wem, replace with your converted audio, repack.
PlaySound commands only work for a subset of sounds explicitly wired through Altar.Dialogue Timing Without Voice
Placing .mp3 files in the legacy Sound/Voice/ folder matching the vanilla dialogue filename convention controls subtitle display duration, even though no audio plays. The mp3 file duration equals the subtitle display time - useful for localization subtitle syncing.
Unpack & Repack Game Files visual
The game’s files are sealed in the IO Store container format. Pull one out, change it, seal it back. This underlies almost every visual mod.
Extracting with retoc
retoc.exe to-legacy --filter "OblivionRemastered/Content/YourPath/YourFile" ^ "[GameDir]\Content\Paks" "C:\Output"
Use forward slashes in the filter path. To extract multiple files at once, add multiple --filter arguments.
.uasset/.uexp blueprint files (forms, BDPs), use retoc to extract, not FModel - FModel doesn’t export .uexp files from IO Store archives, giving you only half the file.Editing with UAssetGUI
UAssetGUI needs both the .uasset and .uexp files in the same folder. Set the version dropdown to 5.3. Load your .usmap mappings file via Utils → Import Mappings. If you get “Failed to Parse X Exports”, extract the referenced dependency files alongside the main asset in the same directory structure.
Repacking with retoc
retoc.exe to-zen --version=UE5_3 "OblivionRemastered" "MyMod_P.utoc"
Input directory must be the OblivionRemastered folder (not deeper). This produces all three files (.pak, .ucas, .utoc) at once.
.ubulk files. If retoc doesn’t pack these correctly, textures appear blurry in-game. Fix: set “Never Stream” on the texture in UE Editor (the old ini-based setting is from UE4 and doesn’t work in UE5). Update retoc to a recent version - GitHub issue #22 was resolved. Check: if the .ubulk is larger than .uexp → fix not applied. If .uexp is larger → fix worked.You can combine art assets and form assets in the same retoc folder before packing to produce one single pak for your entire mod.
Change an Item’s Stats or Enchantment brain
Making a sword hit harder, armor weigh less, or adding enchantments. Uses xEdit to edit game data records on the Gamebryo side.
Launch xEdit with the correct flags
TES4R64.exe -D:"[GameDir]\Content\Dev\ObvData\Data" -I:"[GameDir]\Content\Dev\ObvData\Oblivion.ini"
- Let xEdit fully load
Oblivion.esmand the DLC files. - Find your item inside
AltarESPMain.esp. Right-click it → “Copy as new record into…” a new mod file. Never edit the originals directly. - Give it a fresh EditorID so it can’t conflict with anything, then edit the numbers in the DATA section - damage, weight, value, enchantments.
- Add your new
.esptoPlugins.txtand test.
[nl]Something in-game. Use the NL-Tag Remover mod (mods/473) to fix the broken tag, or use xEdit’s new-string workflow for properly localized names.Finding Your FormIDs In-Game
Your mod’s load order index determines the first two hex digits of all FormIDs it creates. Formula: (line number in plugins.txt minus the comment lines) → convert to hex. Example: your mod is the 23rd line → index 21 → hex 15 → your FormIDs start with 15XXXXXX. Install the Runtime EditorIDs mod (mods/1331) to surface EditorIDs directly in the console.
Game Settings (GMSTs) via GameSettings Loader
Use the Game Settings Loader (mods/833) to override game settings from a config file without touching ESPs:
[GameSettings] fJumpHeightMin = 64 fJumpHeightMax = 128 fHealthRegenDelay = 999999
Add a New Item Using an Existing Model bridge
Creating a genuinely new item - your own named, custom-stat weapon or armor - while reusing a model the game already has. No Blender or UE required thanks to TesSyncMapInjector.
- In xEdit, create your new item record (new EditorID, set your stats). In the model/mesh field, point it at an existing in-game model path you found in FModel.
- Run the Smart Mapper xEdit script. It reads your ESP and automatically writes a
.inilink file toxEdit\SyncMap\. - Copy that
.inifile to[GameDir]\Content\Dev\ObvData\Data\SyncMap\. - Put your
.espinObvData\Dataand add it toPlugins.txt. - Critical load order rule: your plugin must appear above
AltarDeluxe.espin Plugins.txt. Below it, new items go invisible or crash on cell entry.
If you’re writing the SyncMap .ini manually:
[Meshes] ; DecimalFormID = /Game/path/to/UEasset.UEasset 002350=/Game/Forms/items/armor/BP_DaedricCuirass.BP_DaedricCuirass
Where the decimal FormID is converted from the hex FormID in xEdit (with load order bytes zeroed). Multiple mods coexist fine with separate INI files.
Add a New Container or World Object bridge
- In the Construction Set, load MagicLoader’s example file (
IntTestMod.esp) and click Set As Active File. - Duplicate an existing chest or container, give it a new EditorID, say “Yes” to creating a new object. Place it in the world, save, close.
- Open the mod in xEdit (with all Altar files loaded) and run Haphestia’s Fix and Port Script (
fixmod). This fixes the two missing parameters that the Remaster requires but the CS doesn’t add. - Run the Smart Mapper xEdit script. Copy the resulting
.initoData\SyncMap. - Run MagicLoader → “Do Magic!” once to register the new cell entries.
- Enable your esp in
Plugins.txt- aboveAltarDeluxe.esp.
CellsToMapPath DataTable to link Gamebryo cells to UE map files.Step 4 of 5
Make Mods: Advanced
Custom 3D armor, cloth physics, animations, MetaHuman facial rigging, Wwise audio, world editing, new rooms. Requires Blender and Unreal Engine 5.3.2.
Build Custom 3D Armor from Scratch bridge
Asset Structure - What You Need to Create
Each armor piece in OBR consists of 7 components. For a mesh replacer you only need to swap the skeletal mesh. For a standalone new item you need all of them:
| # | Component | Path pattern | Required for |
|---|---|---|---|
| 1 | ESP Form (Gamebryo side) | ObvData/Data/MyMod.esp | All new items |
| 2 | Form Blueprint | /Game/Forms/items/armor/ArmName.uasset | New items |
| 3 | BDP Blueprint | /Game/Forms/items/armor/BP_BDP_ArmName.uasset | New items - the key file |
| 4 | Skeletal Mesh | /Game/Art/Equipment/armor/type/SK_Piece.uasset | Replacers + new items |
| 5 | Material Instance (MIC) | /Game/Materials/MIC_Piece.uasset | Custom textures |
| 6 | GND mesh (ground model) | /Game/Art/armor/SM_Piece_gnd.uasset | Dropped item appearance |
| 7 | Icon texture | /Game/Art/UI/Icons/.../T_MyPiece | Inventory icon |
A - Blender Setup
- Import SK_HumanoidFull (extracted from FModel via Save Model as PSK) as your body reference mesh. Color body yellow and head red for visual clarity.
- Import your armor mesh PSK. On PSK import in Blender: set Linear Color, scale 3m, scale factor 0.01.
- Rename the armature object to
Armaturein Blender’s Outliner. PSK meshes from FModel carry an extra root bone; this naming tells UE to skip that extra bone on import. Exception: do NOT rename NBO (New Body Options) skeletons - they are already set up correctly. - For static meshes (
SM_prefix, like ground models): remove the armature entirely before exporting.
PSK issues: causes seam splitting and blendshape/morph artifacts; adds extra joints that cause Joint Count Mismatch errors.
UEFormat advantages/disadvantages: avoids morph issues. Problem: exports vertex colors as sRGB instead of linear, causing incorrect body part hiding in-game. Also doesn’t export sockets. Plugin: github.com/h4lfheart/UEFormat
Workaround: use UEFormat to get correct morphs, use PSK to get correct vertex colors, then copy the vertex color data from the PSK import to the UEFormat import in Blender.
B - Material Slots (Control First-Person Visibility)
| Slot | Purpose | Notes |
|---|---|---|
0 | Body main material | Hidden in first-person view |
1 | Sleeves / arms | Remains visible in first-person |
2 | Skirt / physics proxy | Should have no material assigned - disable in LOD 0 sections |
MIC_Piece_TEMP) before exporting FBX. In UE, open the skeletal mesh editor and rename it back to the correct name, then assign the correct MIC to both slots.C - Weight Painting
- Parent your clothing mesh to the armature: select mesh, Shift-click armature, Ctrl+P → Object. For NBO armors: first un-parent with Alt+P → Keep Transformations.
- Transfer weights using the Data Transfer modifier: Source = Humanoid/NBO mesh, Mapping = “Nearest Face Interpolated” → click Generate Data Layers → Apply.
- Add an Armature modifier - do NOT apply this one.
- Delete unused vertex groups (use a Blender plugin for bulk cleanup - vanilla body meshes ship with 407 vertex groups). Hand-paint any problem areas in Pose mode.
Blender Sculpt Mode Tips
- Use Sculpt Mode instead of Edit Mode for mesh fitting adjustments - prevents accidental backface selection.
- Grab and Smooth tools are most useful for fine-tuned fitting to the body.
- Enable Backface Culling in Viewport Shading to catch normal direction issues early.
Fixing Blade Mesh Shadow Issues
- Don’t use Merge by Distance on blade edges - it breaks the sharp normals that give blades their edge.
- Fix: in Edit Mode, select the sharp edges → Mark as Sharp (turns blue).
- Or: Mesh → Normals → Average Vectors → Face Area.
- To reset normals entirely: Mesh → Normals → Reset Vectors.
D - FBX Export Settings from Blender
Name skeletal meshes with SK_SetName_PieceName_m convention (_m for male, _f for female variants).
E - Import into UE 5.3.2
- Replicate the game’s exact directory structure in your UE Content folder. This is non-negotiable - the game resolves paths at runtime.
- For Skeletal Meshes: assign the correct skeleton on import:
- Regular clothing/armor →
SKEL_HumanoidSkeleton - NBO-specific female armor →
SKEL_HumanoidFemaleAdd - Helmets → usually
SKEL_HumanoidSkeletonorHumanoidHeadRig
- Regular clothing/armor →
- Set Import Normals and Tangents to preserve your custom normals (not Compute Normals).
- Enable Import Morph Targets if you’re editing head/face meshes.
- For Static Ground Meshes: set Collision preset to Custom. Enable Allow CPU Access (required for enchantment Niagara VFX on weapons). Set Collision Complexity to “Simple As Complex” and check Customized Collision.
F - Vertex Colors & Body Part Hiding System
Vertex colors on the body mesh control which parts disappear when armor is worn, via material function MF_HideByVertexColorInt3. They must be linear - the game crashes if exported as sRGB.
| Body Part | Channel | Value | Body Part | Channel | Value |
|---|---|---|---|---|---|
| pecs + mid-back | R | 128 | shoulder | G | 2 |
| sternum | R | 64 | upper-back | G | 1 |
| front-abs | R | 32 | bicep | G | 4 |
| side-abs + lumbar | R | 16 | forearm | G | 8 |
| thigh | R | 8 | hand | G | 32 |
| knee | R | 4 | underwear | G | 128 |
| low-calf | R | 2 | bra | G | 64 |
| foot-top | R | 1 | low-ankle | G | 16 |
| high-ankle | B | 64 | toes + foot-bottom | B | 128 |
Altar_enums.hpp dump under EBodyPartSlot. Note: Ears are NOT in this vertex color hiding system - hair/ear hiding is separate, handled by the head slot / biped slot. Merged armor meshes don’t support morphs. Use Blender’s Vertex Color Controls addon (BlenderMarket) to accurately sample linear RGBA values from imported meshes.
G - Bitmask Calculator (BP_BDP Body Section Hidden)
- Extract the item’s
BP_BDPblueprint with retoc. Open in UAssetGUI. View → Expand All. FindMaleBodySectionHiddenandFemaleBodySectionHidden. Add them if not present (add as 0 first, save, then change). - Copy the current number into Windows Calculator → Programmer mode → Bit Toggling Keyboard.
- Using the body part color table above, set each bit to 1 (hidden) or 0 (visible).
- Copy the resulting number back into UAssetGUI. Save. Repack. Test.
0 means inherit hidden parts from parent blueprint - NOT “show all body parts.” To explicitly keep parts visible (e.g. sandals showing feet), set a non-zero value with all relevant bits cleared. The one exception: when editing directly in UAssetGUI, literal 0 works as true zero and doesn’t inherit.Tails are separate - add or remove the
HideTail property independently. Setting an item to the Amulet biped slot preserves hair and ears. Setting armor on the Tail biped slot makes it not render at all regardless of BDP settings.Known working values:
4294967040 = hides everything except hair · 4294918400 = Mythic Dawn armor value · 10240 = hides head only · 0 = inherit from parent.H - Materials in UE 5.3.2
You cannot create fully custom materials from scratch in UE - they crash on load. You can only inherit from vanilla base materials. The authoritative workflow from c0bra5:
- Pick a MIC from the game (inspect it in FModel → Export Properties JSON to see its parent chain).
- Create a new material in your UE project at the exact same path as the vanilla parent (e.g.
/Game/Art/Character/Imperial/MIC_HumanUnderwear_M). Make it inherit fromM_Base_Char. - Create a new MIC (the location doesn’t matter) inheriting from that stub in step 2. Set up your textures only in this new MIC.
- Files to include in your mod pak: your own MICs + the skeletal mesh + your textures. Everything else (stubs, vanilla materials) stays out of the chunk - it’s reference-only.
2. Never pak the base material stub - packing it overwrites the real game material and greys out everything that shares it (potentially all weapons or all armor of one type).
3. Materials can un-assign themselves from chunks when re-opening the project. Fix: confirm Allow ChunkID Assignments is enabled in Editor Preferences and set Cook Rule to Always Cook.
- FPSClippingFix - the correct parent for any first-person-visible gear (weapons, gloves, cuirass sleeves). Prevents clipping in first-person view.
- TWO-SIDED - good parent for ground drop meshes (SM_ prefix).
I - BP_BDP Blueprint Parent Chains
Your armor blueprint must inherit from the correct parent chain. Create dummy parent blueprints in Dev/clothing/GenericChild/ - they just need to exist with no content. Weapons do NOT require BDP blueprints.
| Piece Type | Required Parent Chain |
|---|---|
| Cuirass | VUpperBodyModularPart → BP_Generic_BDP_UpperBody → BP_Generic_BDP_UB_Cuirass |
| Full Armor | VUpperBodyModularPart → BP_Generic_BDP_UpperBody → BP_Generic_BDP_UB_FullArmor |
| Greaves | BP_Generic_BDP_LowerBody → BP_Generic_BDP_LB_Greaves |
| Skeletal Helmet / Hood | BP_Generic_BDP_SkeletalHelmet |
| Amulet | VAmuletModularBodyPart |
J - Ground Models (GND Assets)
When armor is dropped on the ground, it uses a separate static mesh (SM_ prefix, _gnd suffix convention). Where the GND reference is declared varies - Virtuos was inconsistent. Check both the main form asset (e.g. EbonyCuirass.uasset) and the BP_ form. To add a GND reference via UAssetGUI, add a NewWorldModels ArrayProperty of SoftObjectProperty to the BP form, then View → Recalculate Nodes. GND meshes must have proper collision or they fall through the floor. Object Channels must all be set; Complex Collision set to Default.
K - Forms with C.A.F.E., ESP, SyncMap & Packaging
- Use C.A.F.E. (mods/4891) to create armor forms. The form name must match the name in the asset path exactly.
- Create your ESP in xEdit with item records. Run Smart Mapper to generate
Data\SyncMap\YourMod.ini. - Place your ESP above
AltarDeluxe.espin Plugins.txt.
| What | Where it goes |
|---|---|
| Body blueprints (LogicMods) | ...\\Content\\Paks\\LogicMods\\YourModName\\ |
| Art assets + form blueprints | ...\\Content\\Paks\\~mods\\YourModName\\ |
| ESP + SyncMap .ini | Content\\Dev\\ObvData\\Data\ and \Data\SyncMap\ |
Additional Field Notes
- Eye color editing is possible (discovered by _trungus) - method still being fully documented as of mid-2025. Check the OBR modding Discord
#research-modelingfor the current workflow. - Amulet biped slot preserves hair and ears - useful for items like head accessories or circlets that shouldn’t hide hair. Note the mesh will be positioned for the neck, so clipping adjustments may be needed.
- Tail biped slot makes armor invisible regardless of BDP settings - don’t use it for regular armor.
- Weapons do NOT require BDP blueprints - they are not assigned to body parts. Only armor/clothing needs them.
- Vertex weight limits: too many bones deforming the same area produces warped/jagged edges. Delete unused vertex groups. Body meshes ship with 407 vertex groups - use a Blender plugin for bulk cleanup.
Beast Race Armor Compatibility
- Import all skeletal meshes with SKEL_HumanoidSkeleton. Never include the skeleton in your pak chunk.
- Never include physics assets - delete them from your project before packaging.
- Khajiit material slot order must be preserved - procedural overlays depend on it.
- Wrong skeleton assignment = “origami” deformed character model in-game.
Materials, Blueprints & Custom Icons visual
Override Material via Blueprint (No Mesh Edit)
- Extract the item’s
BP_BDPwith retoc. Open in UAssetGUI → Import Data tab. - In the bottom empty row, add: ClassPackage =
Script/CoreUObject· ClassName =Package· OuterIndex = 0 · ObjectName = full game path of your material · BImportOptional = False. Click away and back to make it stick. A new empty row appears below. - Fill the new row: ClassPackage =
/Script/Engine· ClassName =MaterialInstanceConstant· OuterIndex = negative row number from step 2 · ObjectName = material name only. - Go to Export Data tab → find the ChaosClothComponent (or appropriate mesh component) → look for or add
OverrideMaterialsArrayProperty of ObjectProperty. Inside it, map slot numbers to your import rows. - Save → repack with retoc → test.
JsonAsAsset (Import Vanilla Materials)
- Install the JAA plugin into
Plugins/JsonAsAssetin your Altar project. Enable, restart, configure the export directory and mappings file path in plugin settings. Launchj0.dev.exe. - In FModel, right-click a material → Export Properties (.json). Keep the export path short with no spaces, close to the root drive (e.g.
C:\FModelExports). - In UE, click the JAA plugin button → select the JSON → wait. Enable the Stubs checkbox (new in 1.4.1) for crash-free import of any material without a pre-made material store.
- MIC parents are not automatically assigned in 1.4.1 - set the parent manually after import. If the imported material is missing layers (the MIC Layers tab), edit that material in UAssetGUI instead - JAA doesn’t import the Layers tab.
- If JAA isn’t working: close FModel before running JAA; restart UE and re-check plugin settings.
Transparent / Masked Materials
| Method | Result | Notes |
|---|---|---|
| Blend Mode: Masked + Opacity Mask | Binary cutout (no gradient) | Works reliably for tattered cloth, hair cards |
MIC_Necromancer_Amulet_Gem | True translucency | Path: Content/Art/Clothes/Amulet/ |
/Engine/EngineDebugMaterials/M_SimpleUnlitTranslucent | Unlit translucency | Color only - can’t add textures or roughness |
Custom Inventory Icons
- In UE: create folder
Content/Art/UI/Icons/Dynamic_Icons/menus/Icons/armor/YourFolderName/ - Import your 256×256 PNG. In UE, prefix it with
T_(e.g.T_MyCuirass). Without the prefix, the game won’t detect the asset. - In xEdit, set the Icon Image path to:
Art/UI/Icons/.../YourFolderName/MyCuirass.dds- with.ddsat the end even though there’s no .dds file. Do NOT include theT_prefix in the xEdit path.
Blueprint Behavior Mods (ModActor Entry Point)
- Create
Content\Mods\YourModName_P\in your UE project. The folder name must match your final pak filenames exactly. - Create a Blueprint of type Actor and name it exactly
ModActor. UE4SS BPModLoaderMod auto-discovers and injects this on game load. - Add a Widget Blueprint (
WBP_ModHud) as your mod’s UI layer. In ModActor’s Event Graph, onEvent BeginPlay: Create Widget → Add to Viewport. This persists across level changes. - Use
Event Tick+Was Input Key Just Pressedon the ModActor (not on a hidden widget - hidden widgets don’t tick) for keypress detection. - Save mod data using
SaveGame to slot "Mods/YourModName"(not the root, to prevent crashes when the mod is removed). - Package and place files in
Paks\LogicMods\YourModName_P\.
ScrollBox widget crashes on some setups when included in a packaged pak. Workaround: create the ScrollBox at runtime using a Create Widget node rather than placing it in the widget designer. VModernScrollBox and VAltarNavigableScrollBox are custom Altar variants that partially address this but don’t solve all cases.Add Cloth Physics to Clothing bridge
SkeletalVariants property in generic armor BPs which forces CCA files to be mandatory. Hair and amulets work because they don’t have SkeletalVariants.UE Editor Steps (Apply Cloth Physics)
- Import your cloth mesh separately from the armor body. They’ll be re-attached via blueprints.
- Open the skeletal mesh → right-click the section to simulate → Create Clothing Data from Section. Name the asset, leave Physics Asset as None for now.
- Right-click again → Apply Clothing Data.
- Click Activate Cloth Paint: white = simulated (moves freely), black = anchored (fixed to skeleton). Top edge = black, bottom hangs free.
- Tune ClothConfigs for physics behavior: stiffness, damping, gravity scale. Reference: UE cloth painting tutorial.
- Right-click mesh → Create Physics Asset. Name it
PA_YourMod_Cloak. - Open the Physics Asset → replace auto-generated capsules with hand-placed ones on the bones that matter. Set all capsule Physics Type to Kinematic.
- In the clothing config, assign your physics asset.
Physics Proxy Mesh
A low-poly duplicate mesh called “PhysicsProxy” is often included in OBR meshes. Physics is calculated on the low-poly version and then interpolated to the actual mesh. If your armor doesn’t need cloth physics: right-click the PhysicsProxy section in LOD 0 → Disable. Or delete the slot entirely. A material assigned to the proxy causes z-fighting and clipping artifacts.
Blueprint Setup for Cuirass + Cape
- Create
Content/Mods/YourName/. Folder name must match pak filenames exactly. - Create a blueprint named exactly
ModActor(parent: Actor). UE4SS looks for this specific name. - Create dummy parent blueprints for your armor type (see Lesson 7 parent chain table).
- Create your main blueprint (parent = deepest appropriate dummy). Set Male Mesh and Female Mesh to the armor body mesh (without cloth). Configure Material Slots Hidden In First Person.
- Add two Skeletal Mesh components as children of Root (one male cape, one female cape).
- Event graph:
Event BeginPlay → Branch → Get Female Mesh → Is Valid Soft Object Reference → Set Leader Pose Component → Set Hidden In Game (male cape, check Hidden). Compile. - Assign blueprint + ModActor to a different chunk number from your mesh assets. Package. Drop into
Paks\LogicMods\YourName\. - In UAssetGUI, update the
BP_BDPpath in your biped model form to point to your new blueprint.
VAmuletModularBodyPart behavior changed - you must now add the cape as a child to the Root Skeletal Mesh Component, not assign it directly. For the root, create an empty skeletal mesh (duplicate any SK → disable LOD0). Also: gender swapping via Get Female/Male Mesh nodes in the event graph no longer works as of patch 1.2.Standalone Capes (Amulet Slot)
Route standalone capes through the amulet slot only - amulets are the only item type that accepts physics meshes directly without the CCA issue. Blueprint parent: VAmuletModularBodyPart. In xEdit, ensure the item uses the Amulet biped flag. Use the Hair mesh slot as an alternative if you don’t mind the workaround. Performance: cloth simulation is CPU-bound and varies significantly between systems. Use a proxy mesh (simplified low-poly version) for high-poly capes: proxy mesh guide.
Custom Animations & Replacers visual
Step 1 - Import SKEL_HumanoidSkeleton via JsonAsAsset (Critical First Step)
Use JAA 1.3.7 or newer. This imports the skeleton with virtual bones (IK targets) intact. Virtual bones cannot be recovered from a PSK export - JAA is the only reliable method. The correct skeleton has 378–379 bones (without sockets exported as bones).
Step 2 - Export SK_HumanoidFull from FModel Correctly
In FModel: Settings → Models → Socket Format → “Don’t Export Bone Sockets”. This prevents sockets from being exported as bones, which adds ~28 extra bones and breaks all IK at runtime.
Step 3 - Fix the Extra Root Bone in Blender
The PSK importer adds a root bone named after the mesh file (e.g. SK_HumanoidFull). Fix: rename the armature object to Armature in Blender’s Outliner. This makes UE skip the extra root, and the bone index order matches correctly.
Correct workflow:
1. Set Blender timeline to 30fps before importing PSA/PSK.
2. Edit or create your animation at 30fps.
3. Export as FBX.
4. Import into UE with custom sample rate of 60fps.
Common mistake: Blender defaults to 24fps. Importing at 24fps makes the total animation length longer than the vanilla version, shifting all notifies out of sync. Verify by exporting the vanilla animation’s JSON from FModel and comparing FrameCount and Duration.
Retargeting Animations from Other Games
Any animation from any source (Elden Ring, The Witcher 3, Skyrim, Paragon, stock UE Mannequin, Mixamo, CMU mocap library, Fab marketplace free tier) can be retargeted to the OBR skeleton.
Recommended UE 5.4 bridge method (krasuepisac):
- In your 5.3.2 modding project, migrate the OBR skeleton to a UE 5.4 project via Content → Migrate.
- In 5.4: create an IK Rig and IK Retargeter for the OBR skeleton. UE 5.4’s retargeting tools are 1–3 clicks vs. building an entire IK rig manually in 5.3.2.
- Retarget your source animations onto the OBR skeleton in 5.4. Export the retargeted animations as FBX.
- Re-import FBX into your 5.3.2 modding project. (Assets can’t be migrated back from 5.4 to 5.3.2, but FBX as an intermediate works perfectly.)
For Skyrim animations specifically: import NIF animations into Blender using the NIF importer plugin, export as FBX, then use the retargeter method above.
Animation Notifies - Combat Combo Chaining
Simply importing notifies via JAA is often insufficient - they may need to be added manually. For combat combos to chain correctly, both of the following notifies are required:
BP_ActionNotifyState_ChainWindow Begin Action Event Tag: ActionEvent.Attack.ChainingWindow.Enter End Action Event Tag: ActionEvent.Attack.ChainingWindow.Exit VAnimNotify_ActionNotifyState Begin Action Event Tag: ActionEvent.Attack.InputWindow.Enter (no End tag)
Removing either one breaks chaining. Confirmed by kei7855 on their shortsword animation replacer mod (mods/2489).
For block to interrupt an attack mid-swing, the start time of this notify must fall within both VAnim_ActionMeleeHitWindow and VAnimNotifyState_ImpactSystem:
BP_ActionNotifyState_TagContainer Tags To Transmit: [Input.Action.Combat.Block] BeginActionEventTag: ActionEvent.Attack.CancelTags.Add EndActionEventTag: ActionEvent.Attack.CancelTags.Remove
Replacing Locomotion Blendspaces Persistently
Injecting a blendspace via AnimSet gets reset when the player equips or unequips a weapon, because the game stores all valid blendspaces in a TMap keyed by GameplayTags. Solution (krasuepisac’s discovery):
- Find the TMap that maps GameplayTags to blendspaces inside the AnimBP.
- Swap the entry for the relevant tag directly. Tag the actor after modification to avoid re-applying every frame.
- This persists through cell transitions - the player actor reference survives cell loads (unusual for UE games but intentional here).
Velocity reference values: Walk ~155 · Run (blendspace activates) ~509. Sprint uses a plain AnimSequence, not a blendspace. Root motion appears fully disabled - everything is root-locked; set root lock to zero on import.
Playing Animations at Runtime (Lua/Blueprint)
Triggering PlayAnimation from Lua works for the first play, but after the animation completes the character stops playing all normal animations permanently (frozen on last frame or T-pose). Use PlayMontage instead - montages blend in and out automatically via UE’s montage system and return the character to ABP-controlled state after finishing. The FullBody animation slot overwrites all other blending when active - use it for full-character override animations like emotes or cutscene poses.
ABP Architecture - Template & Linked Layers
The game uses a template ABP with linked layer instances. Each animation category (combat, idle, locomotion) is a separate linked layer instance within the template. Target the correct layer for the animation type you are replacing - this is why some replacements work and others break unexpectedly.
StrideWarping (standard UE plugin node) handles foot placement based on LocomotionSpeed. Foot lock positions only update when the character starts or stops moving.
Actor Persistence Across Cell Loads
- The player actor reference persists through cell transitions (unusual for UE games). GameplayTags on the player also persist.
- NPC actor references are completely destroyed on any cell load. Store persistent NPC mod data in a widget or custom UObject with a reference loop.
Mesh Merging & Morph Targets
UE’s Merge Meshes system combines all equipped armor pieces into one SkeletalMesh at runtime. This process destroys morph targets. NBO avoids this by using bone scaling instead - bone scale survives merging.
Gamebryo Animation (Legacy)
PlayGroup and PickIdle execute on the Gamebryo side but have no visual effect since rendering moved to UE5. AI Package idles still work. NPCs can get stuck in triggered idles - cast a spell on the NPC to break the animation lock.
Reference Mods & Resources
- miken1ke Blender rig and guide (mods/2069) - community standard rig, includes articles
- kei7855 shortsword animation mod (mods/2489) - reference for notify setup
- Leaning disable mod (mods/2570) - disables forward-lean locomotion behavior
- Alpakit for UE 5.3.2 - OBR-compatible fork; enables one-click auto-deploy of BP mods
- Jake Alaimo live mocap - live mocap modded into OBR, reference implementation
Facial Animation & the MetaHuman System visual
Architecture
- Every voice line has its own AnimSequence stored in
Content/Art/Animation/Humanoid/Facial. Warning: the sheer number of files in this folder will freeze FModel on browse - navigate by known path instead. - Audio and face animation play independently - they start at the same time with no synchronization tracking. This means replacing either leaves the other running unchanged.
- Mouth/speech sync uses a UE function called speech2face (procedural, no per-frame data).
TABP_FacialPoseis the facial pose AnimBP.
Setup for Custom Head Facial Animation
- Enable the RigLogic, MetaHuman, and MetaHuman Identity plugins in your Altar UE project.
- Attach the appropriate
.dnafile to your face SkeletalMesh asset. DNA files are NOT standard.uassetfiles - they require CUE4Parse to extract from the game:
foreach (var kv in provider.Files) {
if (kv.Key.EndsWith(".dna")) {
var outPath = Path.Combine("extracted", Path.GetFileName(kv.Key));
await using var stream = provider.SaveAsset(kv.Value,
new FileStream(outPath, FileMode.Create));
}
}
- Create and reference a dummy ABP_HeadPostProcess blueprint. This is required for the DNA to drive the facial bones.
Community DNA Files
WSDog extracted and publicly shared DNA files for all base races and named NPCs:
- Community DNA files (mods/2592) - base races and named NPCs
- Guide: Nexus mods/625
Generating New Facial Animations from Audio (Audio2Face Workflow)
- Use NVIDIA Audio2Face to generate blendshape/shape key animation from your audio file.
- Pipe through MetaHuman Live Link into UE 5.3.2. Record the result as a UE AnimSequence.
- OBR face curves share the exact same names as MetaHuman curves - they map directly with no translation needed.
- Replace the existing AnimSequence at the matching path in
Content/Art/Animation/Humanoid/Facial. - MetaHuman plugin supports batch processing for automating generation from multiple audio files at once.
Audio-Anim Sync for Localization
Since audio and face animation play independently, to fix out-of-sync dubbed audio (where the new audio is longer than the original): replace the AnimSequence at the correct path with a new one timed to match your audio duration.
Wwise Audio Integration visual
The game uses Audiokinetic Wwise 2023.1.8.8601.3258 for all audio. UE’s built-in audio system has only two dummy assets (DummySoundClass, DummySoundCue).
What Works Without Full Wwise Integration
- Sound replacement: swap existing
.wemfiles inside a pak (see Lesson 2) - Posting vanilla Wwise events from Blueprints: use the
VAudioHandlerssubsystem andBPF_PostEvent(orPostEventAtLocationfor spatial audio) - Turning vanilla audio events on/off from Blueprints using vanilla
AkAudioEventclass defaults
Full Wwise Integration Setup
- Install Wwise from the Audiokinetic Launcher - select exactly the version above.
- In your Altar project folder, delete the existing
WwiseandWwiseNiagaraplugin folders. - Use the Launcher: Unreal Engine → Integrate Wwise into Project.
- Fix the compile error in
Source/Altar/Public/VAltarAkPortalComponent.h: update the include to"AkAcousticPortal.h"and parent class topublic UAkPortalComponent(the Altar project stubs reference the outdated nameAkPortalComponent). - Build via Visual Studio. Name the Wwise project
Altarand place it atC:\Altar-main\WwiseAltar\to match paths in the game’s DefaultEngine.ini (not strictly required but simplifies cross-referencing).
External Sources - Custom .wem Files Without Rebuilding Soundbanks
External Sources allow specifying which .wem file Wwise loads at runtime without rebuilding soundbanks. This enables one framework mod to handle the Wwise plumbing while other mods simply ship .wem files.
Enable External Sources by overriding the game’s DefaultEngine.ini from your mod pak:
[Audio] WwiseFileHandlerModuleName=WwiseSimpleExternalSource
Required Setup for External Sources
- Create a DataTable using the External Source cookie struct (provided by Wwise).
- Populate with: cookie value, media ID, codec ID, media name (filename of your
.wem). - Override
DefaultEngine.inito enableWwiseSimpleExternalSource. - Include your
.wemfile in the pak at the correct staging path. - Ensure your mod actor has a packaged static mesh component with a mesh assigned.
Getting a Free Non-Commercial Wwise License
Creating new Wwise audio events (new sound forms in the ESP with new UE audio) requires Wwise’s authoring tools to generate new .bnk soundbank files. Getting a free non-commercial license requires a manual review by Audiokinetic staff - fill out the form at audiokinetic.com/en/pricing honestly.
World & Mapping - What’s Actually Possible visual
.umap files. The CK terrain editor exists in name but has no visible effect at runtime - Unreal’s terrain takes over.Current Status Table (mid-2025 community research)
| Feature | Status | Notes |
|---|---|---|
| New interior cells | Working | Via MagicLoader (Haphestia’s tool) |
| New exterior cells | Not working | nafnaf_95 confirmed - as of mid-2025 |
| New worldspaces | Not working | As of mid-2025 |
| CK terrain editing | No effect | UE terrain takes over; CK data unused |
| UE heightmap editing | No clean workflow | No polished mod tools yet |
| Grass type modification via CS | No effect | Grass is UE-side foliage paint |
| Navmesh editing | Not possible | Baked into UE umaps |
| LOD configuration | Not needed | Nanite handles it automatically |
| .umap direct editing | Works via UAssetGUI | Version-sensitive; breaks on updates |
| Map borders | Can be disabled | Terrain ends at square boundary |
| Custom map loading | Working | Must use MagicLoader for Gamebryo sync |
Grass & Foliage
All visible grass is part of generated UE maps - painted onto landscape materials during UE map generation. TESGrass records in the CS have no in-game effect. Adding custom trees and grass:
- TESSyncMapInjector approach (most ergonomic): link a UE static mesh to a TES mesh record in the CS and place it using standard CS workflows. Demonstrated for water meshes (mods/4874) and trees (mods/4740).
- Blueprint/Lua spawn approach: a custom Blueprint spawns a mesh at dynamic coordinates from Lua. Requires manual coordinate input.
- Mad’s placement system (minuteready): spray mesh instances organically by running around in-game using Mad’s “rapid spell” + “add instance on X” system, writing positions to a text file, then importing them. Labor-intensive but gives natural placement feel.
- Removing existing UE grass: edit each
.umapvia UAssetGUI to remove grass foliage actors (tedious, version-sensitive), or hook the grass Blueprint creation in Lua and destroy instances on spawn (theoretical).
Heightmap
The old Gamebryo heightmap still exists in game files but does NOT drive the visible terrain. The UE side has its own heightmap. No clean extraction or re-injection workflow exists yet.
Using surf / blenderumap3: importing .umap files via surf/blenderumap3 confirms the layer-per-category structure - exporting only the umap for a specific area returns a single tree mesh, not the full scene. The layers need to be loaded together to reconstruct the full environment. Tools: search for BlenderUMap2 and surf in the UE modding community.
The “All of Tamriel” heightmap from Nexus (Skyrim mods/52675) has been discussed as a source for adding adjacent provinces. Key note from grimlock_arts: TES game heightmap data is deceptively low detail - the games rely heavily on placed rock models pushed into the ground to create the impression of complex terrain. The raw heightmap is just a flat base layer.
Heightmap tools referenced by the community:
- TESAnnwyn - CK heightmap import/export for the Gamebryo side (legacy, won’t affect UE terrain)
- Aerialod - heightmap viewer; recommended settings: scale 2.5, adjust lighting for shadow detail
- ue4parse / UnrealStuff landscape tools - for reading UE landscape data
Map Borders & Open Space
Map borders can be disabled. A large open stretch of land runs from roughly 1/3 to 1/2 the size of the main game map, fully walkable and tree-free, extending all the way to the ocean. No water in the way. This is the most viable space for exterior expansion content as of mid-2025.
Navmesh
Navmesh editing is not currently possible from the modder side. NPCs cannot be directed reliably in new areas without navmesh. Workaround: add a small gatehouse cell or confined space to prevent NPCs from wandering beyond intended areas.
Nanite & LODs
OBR uses Nanite for static mesh geometry, which handles LODs automatically. You do not need to create LOD meshes or configure LOD settings. There is no DynDOLOD equivalent. FModel exports only the fallback mesh (low-poly version for Lumen), not the full Nanite detail. For full extraction use c0bra5’s simple-nanite-parser (Python-based, exports to GLTF with vertex colors).
CK–UE Terrain Height Mismatch Workaround
When building outside the vanilla map border, CK terrain height and UE terrain height diverge. Workaround (nafnaf_95): work one cell at a time, build at CK-default height, then select all objects and shift them vertically until they sit correctly on the UE terrain surface. Tedious but workable for exterior static object placement.
Trees & SpeedTree
Trees are handled via SpeedTree assets with built-in wind animation, baked into per-cell UE umap files with foliage instancers. Map file names are hashed making systematic modification very difficult. Adding new trees as individual Blueprint actors works but incurs significant performance cost. The SpeedTree SDK (required for full authoring) has enterprise-only commercial licensing (~$100k+).
Build a New Interior Room or Player Home bridge
New interior cells work via MagicLoader. The tool modifies the CellsToMapPath DataTable with new entries that map Gamebryo cells to UE map files, making the game load the correct UE level when a cell is entered.
- In the Construction Set, load MagicLoader’s example file (
IntTestMod.esp) → Set As Active File. - In Cell view, create a new interior cell. Build the room using existing tileset pieces. Place furniture, containers, lights. Add a door and use Door Teleport to connect it to an exterior location.
- Save the esp. Open in xEdit (all Altar files loaded) and run Haphestia’s Fix and Port Script (
fixmod). - Run the Smart Mapper xEdit script. Copy the resulting
.initoData\SyncMap. - Run MagicPatcher to build the string table patch
.jsonfor room names and door labels. - Run MagicLoader → “Do Magic!” once.
- Enable your esp in
Plugins.txt- aboveAltarDeluxe.esp.
MagicLoader Known Bug (Fixed in v1.1)
An early version of the Fix and Port Script overwrote FULL name fields on existing records, causing custom items and locations to lose their names. This was fixed in v1.1: “Fixes clearing IDs on newly added records by mistake.”
Custom Creatures via Blueprint Duplication bridge
- In FModel, find the base creature blueprint you want to modify. Note which blueprints it references.
- Trace references upward - those BPs reference their own parents. You’ll find a hierarchy of AI, animation, skeleton, etc.
- Duplicate only as many levels as you need. Stop at the level where you want to diverge. To replace mesh and textures while keeping vanilla AI and animations: duplicate only 2–3 levels.
- In your duplicated top-level blueprint, swap in your new mesh, textures, or behavior values.
- Create the ESP form for your new creature in xEdit. SyncMap it via TesSyncMapInjector. Package blueprints and form.
NPCs, Custom Races & Hair Modding bridge
Adding New NPCs
- Create the NPC record in CS or xEdit. Use an existing vanilla race for reliable results - non-vanilla races default to Imperial appearance at runtime if the UE form can’t be found.
- For the UE side: clone an NPC form file from FModel that matches the intended race/gender.
- Edit the cloned form in UAssetGUI to change name references.
- Register in TesSyncMapInjector.
- Place the NPC in a cell via CS.
0000_. At runtime, if UE can’t find the race form, it silently defaults to Imperial.Custom Races
Custom races require:
- An ESP with the new race record.
- A UE race form blueprint at
/Game/Forms/actors/race/CustomRace.uasset. - Entries in the AllRaceModifications data table (edit via JsonAsAsset or retoc + UAssetGUI).
- Phenotype and hair tables updated to include the race.
Adding fully custom playable races with all CharGen sliders is still in research as of mid-2026. Non-playable custom races (for NPCs) work when given a vanilla voice type. The race menu UI reads from UE-side data tables.
Hair Modding
Hair phenotype files live at /Game/Dev/Phenotypes/Hair/ as data tables listing available hairstyles per race.
- Extract the existing
HP_[Race]_HR_[StyleName].uassetfile using retoc. - Inspect with UAssetGUI or JsonAsAsset.
- Create your new hair mesh in UE project.
- Add an entry to the race’s phenotype hair data table.
- Use RMU (Race Menu Utility) for runtime loading:
rmu set hair /Game/Dev/Phenotypes/Hair/HP_RaceName_HR_HairName.HP_RaceName_HR_HairName
Hair texture format: use the _RAUD suffix multi-channel format. Always export as TGA from FModel - the alpha channel contains the opacity/strand mask, critical for proper hair card rendering.
Facial animation compatibility: modifying head meshes (including head-attached hair) can break facial lip-sync if the mesh topology changes significantly. Blinking and face morphs may continue to work even when lip-sync breaks.
Strand hair: true strand (groom) hair via UE5’s Groom system has been partially achieved but the full binding pipeline is not documented. Hair card replacement is the stable workflow.
Dialogue, Voice Lines & Lip Sync bridge
Text / Subtitle Changes
ESP-side text: changing dialogue text in CS/xEdit works for most dialogue. Display names use the LOC_FN prefix system - forward AltarESPMain changes or names break. Localization files: some UI strings are in UE localization tables inside the PAK and require editing the localization .uasset files. Note: only one mod’s localization table wins in case of PAK conflict.
Reusing Existing Voice Lines for New NPCs
- Create the dialogue topic in your ESP.
- Use TesSyncMapInjector to map your new dialogue FormID to an existing dialogue form blueprint path. Format:
TOPICTYPE_espname_FormID. Example:GREETING_MyMod_00000ED6=/Game/Forms/miscellaneous/dialog/GREETING_Someactor_000479e1.GREETING_Someactor_000479e1
- The NPC’s voice type (set in the race ESP record) must match the target dialogue form’s voice set. Using a non-vanilla race? Set the race to a vanilla voice type to prevent crashes.
Subtitle Display Duration Without Voice
Placing .mp3 files in the legacy Sound/Voice/ folder matching the vanilla dialogue filename convention controls subtitle display duration, even though no actual audio plays. The mp3 file’s duration equals the subtitle display time - useful for subtitle-only localization mods.
Lip Sync
Lip sync is handled by UE animation assets linked to dialogue forms. Current workaround: use an existing dialogue form that has entries for multiple races - the game will attempt to use the matching race’s lip sync animation. Fully custom voiced dialogue (new audio + new lip sync) is not yet solved end-to-end - the WEM format is figured out but BNK generation is the blocker.
Step 5 of 5
Scripting & Custom Code
Three scripting surfaces mirror the two-engine architecture. The real craft is making them communicate with each other.
The Three Scripting Surfaces
brain 1 · OBScript (Construction Set)
The classic Oblivion scripting language - bread and butter for enchantments, spell script effects, object/quest scripts, and AI packages. The UESP Construction Set wiki is ~20 years of OBScript documentation and is the essential reference. Any function marked as OBSE on that wiki will not work in the Remaster.
brain 2 · OBSE64 (C++ DLL Plugins)
OBSE64 is currently only a DLL/plugin loader - it does NOT extend OBScript with new functions, despite the misleading name. To write C++ DLL plugins, use commonlibob64 for reverse-engineered class headers. OBSE64 is version-locked: any game binary update breaks it until offsets are manually updated. See a working plugin example at cnf13/DeleteSpells.
visual 3 · UE4SS Lua + Blueprint Loaders
Some capabilities exist only on the Unreal side: hiding armor pieces, driving animation, Niagara VFX, spawning actors. Use the Nexus OR-specific UE4SS build (v3.0.1-394-g437a8ff) - the generic GitHub experimental build doesn’t hook into UE 5.3 properly and may not recognize Execute Console Command nodes from Blueprints.
The Notification Bridge - OBScript → Lua
Worked out by MadAborModding on top of a notification-parsing method by Dicene. This is the breakthrough that unlocks “impossible” mods like levitation, custom physics casts, and transformations.
OBScript can send on-screen notifications, and Lua can read and instantly hide them. A notification becomes a silent message channel from the Gamebryo brain to the UE face.
Brain side - OBScript attached to a spell, quest, or item
ScriptName madLevitationScript
begin ScriptEffectStart
message "madLevitationScriptStart"
end
begin ScriptEffectFinish
message "madLevitationScriptEnd"
end
Face side - Lua hook that reads, hides, and reacts
RegisterHook("Function /Script/Altar.VHUDSubtitleViewModel:ConsumeNotification",
function(hudVM)
local hudVM = hudVM:get()
local text = hudVM.Notification.Text:ToString()
if text:match("madLevitationScriptStart") then
hudVM.Notification.ShowSeconds = 0.0001 -- hide from player instantly
ToggleFly()
return
end
if text:match("madLevitationScriptEnd") then
hudVM.Notification.ShowSeconds = 0.0001
DispelFly()
return
end
end)
Setting ShowSeconds = 0.0001 effectively hides the notification before it can appear on screen. Source: VHUDSubtitleViewModel.cpp. Live reference implementation: Levitation – UE4SS (mods/3334) version “A”.
console.ExecuteConsole() now requires a fourth true parameter to work correctly. If your console commands silently stopped working after a patch, add true as the fourth argument: Kismet:ExecuteConsoleCommand(pc.player, command, pc, true).Lua → OBScript (The Reverse Direction)
Both OBScript and UE4SS have access to Oblivion global variables. This is the primary reverse channel:
- In Lua, on your event, call:
Kismet:ExecuteConsoleCommand(pc.player, "ObvConsole set MyGlobalFlag to 1", pc, true) - An Oblivion quest script (Begin GameMode) polls that global; when it sees 1, does its thing (can fire a “silent” notification back to Lua), then resets the global to 0.
- Lua reads the return notification and continues.
UE4SS Lua - API, Patterns & Gotchas
Essential Functions
RegisterKeyBind(Key.N, fn)- bind a hotkeyRegisterHook("Function /Script/Altar.Class:Method", fn)- run on an engine function callNotifyOnNewObject(class, fn)- react when a new object of a class spawns; the standard way to grab aVPairedPawnreference instead of pollingFindAllOf("ClassName")- get all live instances of a classStaticFindObject(path)/LoadAsset(path)- find or load an assetrequire("UEHelpers")- player, controller, world, math helpersExecuteInGameThread(fn)- run on the game thread (required for spawning)LoopAsync(ms, fn)- repeating timer; returntrueto stop; save the handle to cancel laterFName.new("E")- construct FNames correctly. Passing a raw string where an FName is expected hard-crashes.
- TArray iteration: use
items:Get(i)and#items, NOTitems[i]. Usevalue:get()inside loops to access the real object. tostring()on a UE object can crash the game.- Post-hook doesn’t see changes a pre-hook made to local values - post-hook sees original values only.
- Debug UE4SS build needs the right
UE4SS-settings.ini- the end-user ini can crash on launch with “no debugging symbols.” - Many UE4SS mods together → CTD: a mod stable alone can crash when many UE4SS mods load together. Problem is timing/order via mods.txt/mods.json. Deferred hook system is the solution (see dotaxis’s snippet in #ue4ss-snippets).
- RefreshAppearance crashes when multiple mods call it simultaneously. Limit to only the aspect you’re changing; call it twice within your own mod.
StaticFindObject('/Script/Engine.Default__KismetSystemLibrary')is the correct way to get KismetSystemLibrary -UClass.Load(...)is not valid in this context.- LiveView can crash if left focused on an Actor too long (confirmed crash on
FakeRootDistanceInterpSpeedFactorCurve). Dump to file instead.
Deferred Hook System
Solves hooks failing when their target function isn’t loaded at Lua startup - waits until FindFirstOf succeeds. Source: #ue4ss-snippets Discord (dotaxis, May 2025). For TypeScript fans: TypeScript-to-Lua compiles TypeScript to UE4SS Lua with better type checking.
Spawning Actors from Oblivion Forms (Blueprint)
Use OblivionActorFactory → Spawn Actor from Form at Location. Pass a live TESForm reference. Spawned actor may not have its pairing set correctly but can receive calls like Jump(). Example: blueprintue.com/blueprint/fo7y5owq/.
Widget & UI Blueprint Tips
- Widget Switchers can replace Scroll Boxes if Scroll Boxes crash in packaged paks.
- Hidden widgets do not tick - put tick/keypress logic on the mod actor instead. Or use opacity 0 +
Not Hit-Testable (Self & All Children)visibility. - To get a ViewModel reference from Blueprints: find it from Lua and pass via console command or custom event.
- References: Custom Logger (Dmgvol) · Config Variables & Key Remapping
V (e.g. VInventoryMenuViewModel, VMisc, VHUDSubtitleViewModel). The central actor class for player and NPCs is AVPairedPawn / AVPairedCharacter - most character data hangs off its components. Standard UE conventions: A = Actor · U = non-actor UObject · F = struct · T = container/template · leading lowercase b = boolean.Finding Hooks
The Kein/Altar SDK repo is the hook directory. Search it on GitHub for a class/function name (e.g. repo:Kein/Altar UVActorBehaviorBase) to find the exact /Script/Altar.Class:Function path and its C++ signature. Also use the UE4SS GUI / LiveView. Use regex search: ^Function.+Cast to find hookable functions efficiently.
Console Commands from Lua (Robust Method)
local UEHelpers = require("UEHelpers")
local pc = UEHelpers.GetPlayerController()
local Kismet = StaticFindObject('/Script/Engine.Default__KismetSystemLibrary')
ExecuteInGameThread(function()
if pc:IsValid() then
Kismet:ExecuteConsoleCommand(pc.player, command, pc, true) -- true required post-June 2025
end
end)
The UE console connects to the Gamebryo console only after the game starts. Console commands work after load, not at startup. get commands via console do NOT return values to Lua - use FindFirstOf("ATMSubsystem"):GetTime() patterns for reading state.
Detecting Player vs NPC
actor:IsPlayerCharacter()→ returns true for the player. Alternative: NPCs have.AVPairedPawnAIController; the player doesn’t.- No clean “player death” hook exists. Pattern: hook
DoRagdoll(fires for any ragdoll, including paralyze/KO) and filter withIsPlayerCharacter()plus a real “is dead” check so KO and paralyze don’t false-trigger.
Inventory from Lua - Traps
FindFirstOf("VInventoryMenuViewModel") returns a fake/empty instance. The real one is hidden inside WBPModernPlayer. Reading the fake one gives an empty list and items with .form = 0000. Fetch inventory right after the player loads and BEFORE opening the inventory menu - opening it wipes/reshuffles the exposed list. An AddItem mod can cause an inventory wipe that breaks other mods reading the viewmodel. Struct fields: Name, Price, Weight, WeaponDamage, ArmorRating, Health, Count, Icon, Type, bIsEquiped, bIsFavorite, InventoryIndex, form, StatusFlags.Time & Fast Travel
Get current time: FindFirstOf("ATMSubsystem"):GetTime(). Enum: EATMTimeUpdateSource {FROM_INIT, FROM_TICK, FROM_GAMEPLAY, FROM_DEBUG}. To freeze time across fast travel: store the hour on OnFadeToBlackBeginEventReceived, restore on OnFadeToGameBeginEventReceived. Setting time via the property directly doesn’t persist - use set gamehour to N via console.
Magic / Enchantment Internals (Advanced)
TESSync.DynamicFormsis a TMap (~246 entries) mapping runtime FormIDs → forms (e.g.0xFF005EC7 → TESObjectCLOT /Game/Forms/items/clothing/TESObjectCLOT_2147479875). This is the way to reach dynamically-added forms at runtime.- Enchantment form chain:
UTESEnchantment → UTESMagicItemForm → UTESForm.UTESEffectSettingholdsEnchantEffect,EffectShader,CastingBlueprintClass,ProjectileBlueprintClass,AreaEffectBlueprintClass,HitEffectBlueprintClass, socket names, andGetEnchantEffectID/EffectShaderID/AssociatedItemID(). - Magic VFX bounce: you can get the magic effect that shaped a projectile (e.g.
FIDGfor fire) but not the actual spell that was cast. EVMusicTypeenum contains all music state data. No hooks have been found yet to intercept individual music state transitions reliably.
Inventory Struct Fields (Reference)
The full FOriginalInventoryMenuItemProperties struct: Name, Price, Weight, WeaponDamage, ArmorRating, Health, Count, Icon, Type, bIsEquiped, bIsFavorite, InventoryIndex, form (UTESForm*), StatusFlags, bIsInventoryItem, bIsInContainerMenu.
Built-in Debug Widgets
WBPPairedPawnDebugInfo_C- built-in AI debug widget that ships with the game. UseFindFirstOf("WBPPairedPawnDebugInfo_C")from Lua to get a live reference to NPC AI state without building your own debug overlay.- KismetDebugger - UE4SS includes this tool which works on your own custom Blueprints. You can step through your Blueprint mod logic in the UE4SS GUI while the game is running.
ESL Support
ESL (Light Plugin) format is not yet available in Oblivion Remastered. The 512 plugin slot limit from classic Oblivion applies. Plan your mod’s FormID space accordingly.
GitHub Actions for Lua Mod Releases
A GitHub Actions template for auto-releasing UE4SS Lua mods on tag push circulates in the OBR modding Discord. This enables CI/CD: push a git tag, and your .zip is automatically built and attached to a GitHub Release. Ask in #resources for the current template.
Known Unsolved Frontiers
- frontier Spawning Niagara systems from pure Lua - not figured out.
- frontier Adding new audio for shout/Fus-Ro-Dah-style effects - effect works, custom audio doesn’t.
- frontier Chameleon visual bug - can force-remove it but it returns ~1s later 99% of the time. Effect ID:
1280133187. - frontier Multi-effect armor enchantment - backend validation blocks it despite UI hook research.
- frontier Setting weapon enchantment charges -
UVItemDetailsViewModelcan read them but setters are UI-only.
Confirmed Hook Catalog
Hooks people actually got working. Format: /Script/Altar.Class:Function unless noted. Source: community research via Kein/Altar SDK.
| Hook Path | Fires when / use for |
|---|---|
VLevelChangeData:OnFadeToGameBeginEventReceived | World finished loading - best “after load screen” hook |
VLevelChangeData:OnFadeToBlackBeginEventReceived | A fade-out starts (entering load / fast travel) |
VLevelChangeData:OnFadeToBlackOverBeforeFastTravel | Just before a fast travel resolves |
Engine.PlayerController:ClientRestart | Player (re)possessed / load finished - good init hook |
VAltarTelemetrySubsystem:OnSaveStarted | A save is starting |
VPairedPawn:OnCombatHitTaken | The “something got damaged” hook |
VPairedPawn:OnCombatHitDealt | An actor deals a hit |
VPairedPawn:DoRagdoll | ANY ragdoll (death, paralyze, knockdown). Filter with IsPlayerCharacter() |
VPairedPawn:OnWeaponChanged | Equipped weapon changed |
VPairedPawn:OnChangeActionState | Action state changes (⚠ gives a pointer, not a name - can’t filter by string) |
VHitBoxComponent:OnOverlapTriggered / StartHit | Melee hit/impact detection |
VAltarPlayerController:OnJumpPressed | Jump pressed |
VEnhancedAltarPlayerController:ToggleSneak | Sneak toggled (hookable, but calling it to drive sneak does nothing) |
VEnhancedAltarPlayerController:OnAttackRequestPressed | Attack button pressed |
VEnhancedAltarPlayerController:OnLoadFinished | Player controller load finished |
VHUDSubtitleViewModel:ConsumeNotification | The notification bridge (OBScript → Lua) |
VMagicSpellVFX:OnSpellProjectileBounce | Spell projectile bounced |
VAmmunition:OnBounce | Arrow bounced |
VActorValuesPairingComponent:OnAllActorValueChanged | A stat/actor-value changed (⚠ delegate, not plain UFunction - needs different binding method) |
VDoor:OnBeginOverlapPreLoadBox | Door pre-load trigger |
VOblivionPlayerCharacter:RequestPowerAttack | Power attack requested |
BP_OblivionPlayerCharacter_C:ReceiveTick | Per-frame tick on the player (⚠ two variants exist with/without underscore - check your build) |
BP_OblivionPlayerCharacter_C:OnEnterUnderwater | Player enters water |
WBP_LegacyMenu_Main_C:OnConfirmNewGame | New game confirmed |
WBP_Modern_MapWidget_C:OnIconHovered | Map icon hovered |
WBPModernMenuEnchantmentMenu_C:OnEffectClicked | Enchant menu effect clicked (used in multi-enchant research) |
WBP_LockPick_C:OnFocus / BreakLockpick | Lockpicking UI events |
AIModule.AIPerceptionComponent:GetPerceivedHostileActors | AI perception - who an NPC sees as hostile |
VPhysicsControllerComponent:HandleCollisionSoundOnBeginOverlap | Physics collision sounds |
VPairedPawn:OnHitReaction / OnCapsuleHit / OnDeathVFX / SendJump | Hit reactions, capsule hits, death VFX, jump events |
BPCI_StatusEffect_Light_C:OnStartPlayStatic | Light status effect start (used for spawn-light recipe) |
Engine.Actor:K2_GetActorRotation / K2_GetActorLocation / DisableInput | Common actor utilities. Rotation returns Yaw/Pitch/Roll. |
OBScript Reference
Script Block Types
| Block | When it fires |
|---|---|
Begin GameMode | Every quest tick (~5 seconds default). Set fQuestDelayTime to change frequency. |
Begin MenuMode [id] | While a specific menu is open. |
Begin OnEquip [actorID] | When item is equipped. Runs even on broken items; does NOT run on unequip if item breaks. |
Begin OnUnequip [actorID] | When item is unequipped. |
Begin ScriptEffectStart | Once when spell effect begins. |
Begin ScriptEffectUpdate | Every frame while spell effect is active. Buggy in Remaster - use GetSecondsPassed workaround. |
Begin ScriptEffectFinish | Once when spell effect ends. |
Begin OnDeath [actorID] | When scripted actor is killed. Parameter is the killer, not the target. |
Begin OnHit | When scripted actor is hit. Only works on NPCs and Creatures. |
Begin OnActivate [actorID] | When specified actor activates this object. |
Begin OnLoad | When the cell containing the object loads. |
OBScript Bugs in the Remaster
Messageformatting (%g,%.0f, etc.) does not work - the Altar interpretation layer does not support dynamic string formatting.ScriptEffectUpdateruns inconsistently without a timer. Fix:set elapsed to elapsed + GetSecondsPassed, checkif elapsed >= 0.016.SetActorsAI 0called on a ref in another cell crashes the game as of update 1.1.PlayMagicEffectVisualsdoes not apply visual effects on actors.SetPos zon actors is unreliable depending on terrain height.- All OBSE functions (
HasSpell,IsKeyPressed,CloseAllMenus, etc.) do not work - OBSE64 is only a plugin loader. returninside a flatif/endifdoes not work as expected - always nest logic properly.- Quest scripts may randomly lose their attached script in the CS - re-attach if behavior suddenly breaks.
- ESPs created in the old CS are missing 2 parameters the Remaster requires. Fix with xEdit fix scripts before use.
- HTML-style text formatting in books/messages (e.g.
<br>) no longer works.
Useful OBScript Patterns
Toggle a spell without OBSE
If ( Player.IsSpellTarget MySpell == 0 )
Player.AddSpell MySpell
Else
Player.RemoveSpell MySpell
EndIf
Run code once on game start
scn MyOneTimeScript
Begin GameMode
player.AddItem MyCustomItem 1
StopQuest MyStartupQuest
End
Attach to a quest with Start Game Enabled checked. Quest runs once and stops itself.
ScriptEffectUpdate timer workaround
Begin ScriptEffectUpdate
set elapsed to elapsed + GetSecondsPassed
if elapsed >= 0.016
; your logic here
set elapsed to 0
endif
End
OBScript Pattern: Item Equip with Infamy Check
scn GrayFoxCuirassScript
begin OnEquip player
if ( GetPCInfamy >= 10 ) && ( GetPCFame < GetPCInfamy )
if ( GetPCInfamy < 20 )
player.addspell GrayFoxArmorEnchant01
elseif ( GetPCInfamy >= 20 )
player.addspell GrayFoxArmorEnchant02
endif
endif
end
begin OnUnequip player
player.removespell GrayFoxArmorEnchant01
end
Pattern: Menu After Inventory Closes
Opening ShowEnchantment while inventory is open freezes controls. Use a quest script waiting for GameMode:
; Item script fires on equip:
Begin OnEquip player
StartQuest MySpellMenuQuest
End
; Quest script (only runs when no menus are open):
Begin GameMode
ShowEnchantment
StopQuest MySpellMenuQuest
End
Quest & AI Notes
- Quest stages can only go forward. Enable Allow Repeated Stages for stages that need to fire multiple times.
- Quest scripts die at stage 100 /
StopQuest. Don’t mix stages into existing vanilla quests - breaks saves. FollowPlayerAI packages may not work as expected - buggy in the Remaster.PushActorAwaywith negative values may push instead of pull. Results are inconsistent.- NPCs activate furniture to use it; they don’t just play animations near it.
Console Commands for Debugging
ObvConsole set QuestName.VarName to 1 ; set a quest variable ObvConsole show QuestName.VarName ; read a single variable sqv QuestName ; dump all variables for a quest GetGlobalValue GlobalVarName ; read a global variable ToggleDebugCamera ; free camera (replaces old tfc) Altar.StandOutOblivionAsset 1 ; turn legacy assets pink Altar.Cheat.AllowSetStage 1 ; enable SetStage in console SloMo 0 ; freeze time
Ready-to-Use Lua Snippets
Detect Weapon Type & Power Attack
-- pawn is AVPairedPawn reference
local function GetWeaponType(pawn)
local wa = pawn.WeaponsPairingComponent.WeaponActor
if wa and wa:IsValid() then
return wa.WeaponTypeTag.TagName:ToString()
end
return "HandToHand"
end
-- Example output: "Weapon.Type.Sword.One.Hand", "Weapon.Type.Bow", "HandToHand"
local powerTag = { TagName = FName("Actor.Action.Combat.Attacking.Power") }
local function IsPowerAttack(pawn) return pawn:HasGameplayTag(powerTag) end
Detect Spell Cast Type
-- Returns: 0=Self, 1=Touch, 2=Target, 3=Unknown, 4=MAX, -1=Error
local function GetSpellCastType(pawn)
local s = pawn.OblivionActorStatePairingComponent
if not s or not s.SpellCastType then return -1 end
return tonumber(s.SpellCastType)
end
Efficient Proximity Detection (SphereOverlapActors)
-- ~100x faster than iterating all actors and manually calculating distance
local playerLocation = playerPawn:K2_GetActorLocation()
local actorList = {}
UEHelpers.GetKismetSystemLibrary():SphereOverlapActors(
UEHelpers.GetWorldContextObject(), playerLocation,
500, { someClass }, someClass, { }, actorList)
for i = 1, #actorList do
local actor = actorList[i]:get()
-- do something with actor
end
Spawn a Blueprint Actor at Location
local UEHelpers = require("UEHelpers")
local function SpawnBP(path, location, rotation)
local bp_path = path
if bp_path:match("^OblivionRemastered/Content/.+%.uasset$") then
bp_path = bp_path:gsub("OblivionRemastered/Content/", "/Game/")
local fn = bp_path:match("/([a-zA-Z0-9_]+).uasset")
local dir = bp_path:match("^(.+/)[a-zA-Z0-9_]+.uasset")
bp_path = dir .. fn .. "." .. fn .. "_C"
end
LoadAsset(bp_path)
local bp_class = StaticFindObject(bp_path) or CreateInvalidObject()
if not bp_class:IsValid() then return end
local loc = location or UEHelpers.GetPlayer():K2_GetActorLocation()
local rot = rotation or {Pitch=0, Roll=0, Yaw=0}
local spawned = UEHelpers.GetWorld():SpawnActor(bp_class, loc, rot) or CreateInvalidObject()
if not spawned:IsValid() then return end
spawned.Tags[#spawned.Tags + 1] = FName("ManuallySpawned")
spawned:K2_GetRootComponent():SetMobility(2) -- Movable
spawned:SetActorEnableCollision(false)
return spawned
end
RegisterKeyBind(Key.N, function()
ExecuteInGameThread(function()
SpawnBP("/Game/Dev/Creatures/BP_Generic_Flameatronach.BP_Generic_Flameatronach_C")
end)
end)
Dump Player Inventory
-- IMPORTANT: fetch BEFORE opening the inventory menu, not after
local vm = FindAllOf("VInventoryMenuViewModel")[1]
for i, item in ipairs(vm:GetInventory()) do
local d = item.DisplayName
print(d.Name:ToString(), d.Weight, d.Price, d.Count, d.bisEquiped, d.form)
end
Get All Skeletal Mesh Sockets on a Pawn
-- pawn is a valid AVPairedPawn reference
local socketCount = pawn.MainSkeletalMeshComponent.SkeletalMeshAsset:NumSockets()
print("Number of sockets: " .. socketCount)
for i = 0, socketCount - 1 do
local sock = pawn.MainSkeletalMeshComponent.SkeletalMeshAsset:GetSocketByIndex(i)
print("Socket: " .. sock.SocketName:ToString() .. " | Bone: " .. sock.BoneName:ToString())
end
Smooth Tween / Interpolation Helper
Smoothly transition a numeric value over time using an easing function. Requires the easing_functions module. Visual reference: easings.net.
local Math = require("easing_functions") -- provides Math.EasingOptions table
local CurrentTweenTask = nil
local function TweenToValue(GivenVariable, TotalDuration, StartingValue, EndingValue, EasingFunc)
local startTime = os.clock()
GivenVariable = StartingValue
CurrentTweenTask = LoopAsync(1, function()
local progress = (os.clock() - startTime) / TotalDuration
GivenVariable = EasingFunc(StartingValue, EndingValue, progress)
if GivenVariable >= EndingValue then
GivenVariable = EndingValue
return true -- stops the loop
end
return false
end)
end
-- Usage: TweenToValue(myVar, 2.0, 0.0, 100.0, Math.EasingOptions.EaseLinear)
Full Notification Bridge (Complete Pattern)
-- OBScript side: message "MY_SIGNAL" from any script block
-- Lua side: intercept, hide, react
RegisterHook("Function /Script/Altar.VHUDSubtitleViewModel:ConsumeNotification",
function(hudVM)
local vm = hudVM:get()
local text = vm.Notification.Text:ToString()
if text:match("MY_UNIQUE_SIGNAL") then
vm.Notification.ShowSeconds = 0.0001 -- hide from player
-- do whatever you want here
return
end
end)
-- Lua -> OBScript reverse:
-- Kismet:ExecuteConsoleCommand(pc.player, "ObvConsole set MyFlag to 1", pc, true)
-- Quest script checks: if (MyFlag == 1) ... set MyFlag to 0 ... endif
Useful Classes & Structs Reference
| Class | Purpose / Key Methods |
|---|---|
VPairedPawn | Base pawn for all characters. WeaponsPairingComponent, OnCombatHitTaken, DoRagdoll, IsPlayerCharacter() |
VPairedCharacter | Character subclass. Has VHumanoidHeadComponent, VHumanoidHeadAnimBP |
VActorValuesPairingComponent | Attribute values (Strength, Health, etc.). OnAllActorValueChanged delegate. |
VHumanoidHeadComponent | Facial expressions/emotion. Search “Emotion” in class dump. |
VEnhancedAltarPlayerController | Player controller. OnLoadStarted, OnLoadFinished, OnAttackRequestPressed, ToggleSneak. |
VLevelChangeData | Level transitions. OnFadeToGameBeginEventReceived - the primary “load screen done” hook. |
VAltarUISubSystem | UI subsystem. GetInventoryHoveredActor, menu access. |
VInventoryMenuViewModel | Inventory menu data. Contains item arrays. See gotchas for fake-instance trap. |
VHUDSubtitleViewModel | HUD message notifications. The notification bridge key hook. |
UTESMagicItemForm | Magic item / spell form. FullName, EffectSettings TArray. |
VOblivionGameInstanceSubSystem | Game instance subsystem. Persistent across level loads. |
UVTESObjectRefComponent | Object reference component. FormIDInstance (uint32), TESForm*, GetHexFormRefID(). |
Object Browsers & Reference Resources
Reference
Fixing Problems & Glossary
CTD index, status board, field notes, resource directory, and full glossary. Always open in another tab when something breaks.
Debugging Workflow
Opening the In-Game Console
The in-game console key is ` (backtick/tilde). This opens the Oblivion console. UE console commands are also available. The UE4SS GUI console window is separate - it opens alongside the game window when UE4SS is active with GuiConsoleVisible = 1 in UE4SS-settings.ini.
Rapid Testing Commands
tgm ; god mode for safe testing tdetect ; disable NPC detection so you can work undisturbed set timescale to 0 ; freeze game time coc "TestingHall" ; jump to a vanilla test cell (or any cell EditorID) coc "ICMarketDistrict" ; jump to a known working exterior player.moveto 00000014 ; teleport to player start ToggleDebugCamera ; free-fly camera for scene inspection
Reading UE4SS Crash Logs
UE4SS logs are at: [GameDir]\Binaries\Win64\ue4ss\Logs\UE4SS.log
Key things to look for in crash logs:
Unhandled exception+ stack trace - the actual crash locationLogScript: Errorentries - OBScript evaluation errorsLogBlueprintUserMessages- Blueprint print nodes output hereModuleManagererrors - a DLL plugin failed to load
UE crash reports go to: C:\Users\YOU\AppData\Local\CrashReportClient\
Mod Compatibility & Conflicts
ESP Record Conflicts
- When two mods modify the same record, the last one in load order wins. Neither mod’s change is truly merged - one overwrites the other.
- To merge changes: open both ESPs in xEdit, copy the conflicting record into a new patch ESP, and manually combine both sets of changes. This is a compatibility patch.
- xEdit shows conflicts in the right-click context menu and flags records with colored backgrounds. Red = conflict you must resolve.
PAK Conflicts
- In
~mods, PAK files load in alphabetical order by filename with later entries overwriting earlier ones. - For your mod to win over another: prefix with a lower letter (
000_) or instruct users to rename. - PAK conflicts are silent - no error, the later file just wins entirely for each asset.
- SyncMap
.inifiles from multiple TSMI mods do NOT conflict - they are additive. Multiple INI files coexist fine.
Dirty Edits & ITM Records
When you open a record in xEdit and accidentally touch it without changing anything, xEdit may mark it as modified. These “Identical To Master” (ITM) records bloat your ESP and can cause unnecessary conflicts. Before releasing any mod: in xEdit, right-click your plugin → Other → Check for Errors and Other → Apply Filter for Cleaning. Remove ITM records.
Save Safety
| Mod Type | Safe to Add Mid-Playthrough? | Safe to Remove? |
|---|---|---|
| Texture/sound replacers (PAK only) | Yes | Yes - reverts to vanilla on removal |
| ESP stat edits (existing records) | Yes | Mostly yes - existing items revert to vanilla stats |
| ESP new items (new FormIDs) | Yes | Risky - missing FormIDs in save cause issues |
| New interior cells (MagicLoader) | Yes | No - removing breaks saves that visited the cell |
| OBScript changes | Careful - running scripts may have already modified save state | Depends - permanent ModAV changes stay in save |
| UE4SS Lua mods | Yes | Yes - no save state touched |
ModActorValue in OBScript makes permanent changes to actor values stored in the save file. A player’s Health modified with ModAV cannot be “healed back” by normal means. Use SetActorValue for temporary changes or enchantment Fortify effects instead.Distribution Checklist
Before Releasing a Mod
- Clean your ESP in xEdit (remove ITM records, check for errors).
- Test on a clean install without your development tools installed - users don’t have your UE project or xEdit scripts.
- Document what files go where in your mod description: pak in ~mods, ESP in Data, SyncMap ini in Data\SyncMap, etc.
- State your dependencies: OBSE64, UE4SS, TesSyncMapInjector, Address Library - any loader your mod needs.
- State game version: OBSE64 and compiled Blueprint paks are version-locked. Mention which game build you tested against.
- Include a folder structure diagram for anything beyond a simple pak replacement. Users get confused by nested install paths.
- Test removal: verify removing your mod on an existing save doesn’t corrupt it, or warn users clearly if it does.
What Files Users Actually Need
| If your mod is | Users need |
|---|---|
| Texture/sound replacer | ModName_P.pak + .ucas + .utoc in ~mods |
| New item (existing model) | ESP in Data + SyncMap .ini in Data\SyncMap |
| New item (custom model) | ESP + SyncMap .ini + PAK in ~mods |
| New interior cell | ESP + SyncMap .ini + MagicLoader must be installed |
| Blueprint behavior mod | PAK in LogicMods\ModName + UE4SS installed |
| Lua scripting mod | Lua mod folder in ue4ss\Mods\ + UE4SS installed |
Performance Considerations
- Draw calls matter more than polygon count. One complex merged mesh is better than 50 simple separate meshes in the same cell.
- Cloth simulation is CPU-bound and scales with complexity. Use PhysicsProxy low-poly meshes to drive simulation and interpolate to the high-poly result.
- Blueprint Tick cost:
Event Tickin a Blueprint runs every frame. Use event-driven patterns (RegisterHook,NotifyOnNewObject) wherever possible instead of polling on tick. - ESP record count: practically unlimited - the vanilla game has thousands. Not a real bottleneck.
- Lua mod load timing: many UE4SS mods loading simultaneously can cause CTDs due to race conditions. Use the deferred hook pattern and
mods.txt/mods.jsonload ordering to sequence initialization. - PAK file size: no hard limit but very large PAKs (>1GB) can increase load time noticeably. Split by content type if needed.
Game Version & Patch Stability
- Keep a backup of your working game version before any game update. Steam can be set to not auto-update under Properties → Updates.
- OBSE64 breaks on every game binary update until offsets are manually updated by the OBSE team. Check the Nexus page changelog after patches.
- Compiled Blueprint paks may become incompatible after patches that change class layouts. The game will usually crash silently on load or produce broken behavior rather than a useful error.
- UAssetGUI edits are binary-specific - changes made to assets from one game version may not work on another. Always extract fresh assets from the current game version.
- Check the Nexus early modding overview and Discord
#announcementsafter every patch for community-reported breakage.
CTD & Issue Index
| Symptom | Likely Cause | Fix |
|---|---|---|
| New object invisible / CTD entering new interior | Missing SyncMap entry or wrong load order | Run Smart Mapper; place plugin above AltarDeluxe.esp |
| New item named [nl]Something | Altar localization artifact | Install NL-Tag Remover (mods/473) |
| Won’t launch after adding a mod | Scrambled load order or bad DLL | Restore clean Plugins.txt; remove last-added mod; reinstall one at a time |
| OBSE “version not detected” | Wrong build / wrong folder / base exe launched | Match build; dll+exe in Binaries\Win64; always launch via OBSE loader |
| UE4SS not loading / startup crash | OBSE conflict on first launch | Launch once WITHOUT OBSE first so UE4SS configures itself |
| Cooked assets won’t load in-game | Wrong UE version used to cook | Cook in UE 5.3.2 specifically - not 5.5 or any other version |
| UAssetGUI “Failed to Parse X Exports” | Referenced dependency files not extracted alongside | Extract all referenced assets too in the same directory tree, then retry |
| .pak ignored by game | Missing _P suffix or wrong load priority | Add _P in ~mods; or prefix with 000_ for Paks folder alphabetical priority |
| BODY SETUP 0: BAD NAME INDEX crash | Collision Presets = Block All, or wrong material slot names | Set Collision Presets to Custom; match slot names from vanilla FModel JSON |
| Whole world renders as UE grey grid | Static switch changed, or base material packed | Never edit static switches; pak only the material instance, never the parent |
| Unversioned properties crash (hard to diagnose) | Mod cooked with Shipping instead of Development | Always cook in Development configuration |
| BP_ form files crash after patch 1.2 | Old blueprint forms incompatible with new game version | Extract fresh vanilla BP_ forms from 1.2 PAKs; rebuild. Gauntlets and greaves most affected. |
| NPCs / creatures frozen in new cell | No navmesh data in new interiors | Known limitation; no fix. Design rooms to not need NPC movement. |
| First-person weapon / glove / sleeve clipping | Wrong material parent for first-person gear | Use FPSClippingFix material parent |
| Boots hide feet (bitmask = 0) | 0 means inherit from parent, not “show all” | Use a non-zero value with relevant bits cleared |
| Inventory icon missing | Missing T_ prefix in UE, or wrong xEdit path | Prefix PNG with T_ in UE only; keep .dds extension in xEdit Icon Image path |
| Textures blurry in-game | ubulk split issue | Set “Never Stream” on texture in UE; update retoc (issue #22 fixed) |
| Cloth mesh doubled (static + dynamic) | Cloth mesh is the Root Skeletal Mesh Component | Cloth mesh must be a child of root, never root itself |
| Custom armor invisible when enchanted | Dynamic FormID not in TSMI SyncMap | Known bug; don’t change load order after enchanting; re-enchant if needed |
| Lua ExecuteConsole silently broken after patch | API signature changed | Add fourth true parameter |
| JAA imports missing MIC layers | JAA doesn’t import the MIC Layers tab | Edit that material in UAssetGUI instead |
| Material un-assigns from chunk on project reopen | Allow ChunkID Assignments not enabled | Enable in Editor Preferences + set Cook Rule to Always Cook |
| Animation looks fine in editor, broken in-game | Wrong bone index order from PSK skeleton | Import SKEL_HumanoidSkeleton via JAA, not from PSK export |
| Notifies fire at wrong time in-game | Wrong framerate (Blender default 24fps) | Set Blender to 30fps; import UE at custom sample rate 60fps |
| RefreshAppearance crash with multiple mods | Multiple mods calling it simultaneously | Limit calls to only the aspect you’re changing |
| Enchantment VFX missing on custom weapon mesh | bAllowCPUAccess not set on the static mesh | Set Allow CPU Access = True in UAssetGUI on your SM_ asset |
What Works - Status Board (mid-2026)
Field Notes
1.2 Update Mesh Fix Checklist
- Set Collision Presets to Custom on all static meshes (primary crash source: BODY SETUP 0: BAD NAME INDEX).
- Match Material Slot Names to vanilla values. Extract vanilla mesh properties JSON from FModel, search
"MaterialSlotName". Mismatch = BAD NAME INDEX. - Uncheck left checkbox on all Static Switch Parameters in every MIC - checked switches cause grey world or crash.
- For blueprint/form crashes after 1.2: extract fresh vanilla BP_ forms from 1.2 PAKs and rebuild. Gauntlets and greaves are most commonly affected; boots often fine; helmets sometimes affected.
- Use the 1.2 Updater Tool (mods/5281) to automate collision and slot name fixing.
Material Rules That Save Hours
- Never change a static switch - even to the same value. Causes grey world-grid rendering.
- Don’t pak the base material. Make a material instance of the base and pak only the instance.
- All non-static parameters are fair game to override.
- FPSClippingFix is the correct parent for any first-person-visible gear (weapons, gloves, cuirass sleeves).
Debug Console Quick Reference
ToggleDebugCamera ; free camera (replaces old tfc) SloMo 0 ; freeze time completely Altar.StandOutOblivionAsset 1 ; turn legacy assets pink (dev tool) Altar.Cheat.AllowSetStage 1 ; enable SetStage in console player.moveto 00000014 ; teleport to player start ATM.SetWeatherSetup clear ; set clear weather set timescale to 1 ; restore normal time tgm ; toggle god mode tai ; toggle AI tdetect ; toggle enemy detection
Engine.ini Blueprint Variable Overrides
Set Blueprint variables without Lua by editing: C:\Users\YOU\Documents\My Games\Oblivion Remastered\Saved\Config\Windows\Engine.ini
[/Game/PathToBlueprint/BP_Name.BP_Name_C] MyVariable=Value MyStruct=(Prop1=Val1,Prop2=Val2)
Never include Content in the path. Use /Game/ or /PluginName/. [/Script/ is reserved for C++ classes.
Glossary
Reference
Every Tool & Folder
The complete modding workbench: where every file goes, how load order works, and every tool with its purpose.
The File Buckets
Every file you install or create belongs to exactly one destination. Learn to answer “which bucket?” by hand even when a mod manager handles it automatically.
| Bucket | File Types | Location | Layer |
|---|---|---|---|
| Data | .esp .esm | ...\Content\Dev\ObvData\Data\ | brain |
| SyncMap | .ini (TSMI mappings) | ...\ObvData\Data\SyncMap\ | bridge |
| Paks ~mods | .pak + .ucas + .utoc | ...\Content\Paks\~mods\ | visual |
| LogicMods | Blueprint paks | ...\Content\Paks\LogicMods\<ModName>\ | visual |
| UE4SS Mods (Lua) | main.lua in Scripts\ subfolder | ...\Binaries\Win64\ue4ss\Mods\<ModName>\ | visual |
| UE4SS BPModLoaderMod | Blueprint paks (alternative path) | ...\Binaries\Win64\ue4ss\Mods\LogicMods\ | visual |
| OBSE / ASI Plugins | .dll / .asi | ...\Binaries\Win64\ | extension |
| GameSettings | .ini GMST overrides | ...\Binaries\Win64\GameSettings\ | brain |
PAK Naming & Load Rules
_Psuffix in~mods: all three files must end in_P(e.g.MyMod_P.pak,MyMod_P.ucas,MyMod_P.utoc).- Alphabetical priority in
Paksfolder: placed directly in Paks, a mod sorts alphabetically and must sort belowOblivion.pakto win conflicts - use a prefix like000_MyMod.pak. - Blueprint/logic mods go in the
LogicModssubfolder.
ESP Load Order - Canonical Template
Oblivion.esm DLCBattlehornCastle.esp · DLCFrostcrag.esp · DLCHorseArmor.esp DLCMehrunesRazor.esp · DLCOrrery.esp · DLCShiveringIsles.esp DLCSpellTomes.esp · DLCThievesDen.esp · DLCVileLair.esp Knights.esp AltarESPMain.esp ← YOUR NEW-OBJECT / NEW-CELL PLUGINS GO HERE AltarDeluxe.esp AltarESPLocal.esp
AltarDeluxe.esp. Below it, new objects render invisible and entering a new interior crashes - a TesSyncMapInjector ordering requirement.Key Content Paths in the Game Files
/Game/Art/Character/{Race}/ ← Race textures and meshes
/Game/Art/Equipment/armor/{type}/ ← Armor skeletal meshes
/Game/Art/Equipment/weapons/ ← Weapon static meshes
/Game/Forms/items/armor/ ← Armor form blueprints (BP_BDP_*)
/Game/Forms/items/weapons/ ← Weapon form blueprints
/Game/Forms/actors/npc/ ← NPC blueprints
/Game/Forms/actors/race/ ← Race blueprints
/Game/Dev/Phenotypes/Hair/ ← Hair phenotype data tables
/Game/Materials/ ← Master materials
/Game/Art/Animation/Humanoid/ ← Character animations
/Game/Art/Animation/Humanoid/Facial ← Per-voice-line face animations
/Game/Art/UI/Icons/Dynamic_Icons/ ← Inventory icons
Loaders & Bridge Tools
| Tool | Role | Notes |
|---|---|---|
| OBSE64 | DLL plugin loader (not script extender yet) | Steam only; match game build; dll+exe to Binaries\Win64 |
| Address Library (mods/4475) | Version-independent OBSE plugins | Required by almost all OBSE plugins |
| UE4SS (mods/32, OR build v3.0.1) | Lua scripting + Blueprint loader for UE5 side | Launch once without OBSE to configure; use OR build not GitHub generic |
| TesSyncMapInjector (mods/1272) | Links ESP FormIDs to UE5 assets at runtime | INIs in ObvData\Data\SyncMap; unzip to game root |
| Smart Mapper | xEdit script that auto-generates TSMI INI files | Output lands in xEdit\SyncMap; copy to game Data\SyncMap |
| MagicLoader (mods/1966) | Enables new interior cells via CellsToMapPath DataTable | Also requires MagicPatcher for string table entries |
| OBRConsole (mods/2205) | Lets UE4SS Lua run Oblivion console commands | ⚠ Crashes if you tab out of the game while active |
| SML / Simple BP Mod Loader (mods/1172) | Alternative Blueprint loader; better input action docs | Also provides SML Dev Resources for console output, UObject storage |
ESP / Data Authoring
| Tool | Role | Notes |
|---|---|---|
| xEdit 4.1.5n+ (TES4R build) | Primary ESP authoring and conflict detection | Get from xEdit Discord #xedit-builds; launch with -TES4R flag |
| Construction Set (2006) + CSE | Visual editor for cells, containers, world objects | Never load Altar ESPs; saved esp in ObvData\Data\Data (move up one level) |
| LOOT | Automatic load-order sorting | Sort, then hand-fix the special cases above |
| NL-Tag Remover (mods/473) | Strips [nl] artifact from new item names | Required for any mod that adds new named items |
| Haphestia’s Fix and Port Script (fixmod) | Fixes the 2 missing parameters CS doesn’t add for Remaster | Run in xEdit with Altar files loaded |
| Game Settings Loader (mods/833) | Load GMST overrides from config file | Alternative to setGS console command each session |
| Runtime EditorIDs (mods/1331) | Surfaces EditorIDs in the in-game console | QoL for testing; lets you spawn items by EditorID |
UE5 Asset Pipeline
| Tool | Role | Notes |
|---|---|---|
| Blender + PSK/PSA plugin | Model and animation authoring | Rename armature to “Armature”; if PSK breaks try Befzz plugin |
| Unreal Engine 5.3.2 | Cook art assets into paks | Use 5.3.2 specifically. Install to C:/ not Program Files. |
| FModel + .usmap (mods/47) | Browse and extract game assets | Set to GAME_UE5_3; regenerate .usmap after patches via UE4SS Ctrl+Numpad6 |
| OR Mod Tools (mods/3918) | retoc + UAssetGUI + oo2core bundle | Use Nexus UAssetGUI version (PackageName bug fix) |
| retoc (github.com/trumank/retoc) | Extract and repack IO Store paks | to-legacy to extract; to-zen to repack; update for ubulk fix |
| UAssetGUI (Nexus version) | Edit cooked .uasset/.uexp blueprint and form files | Set version to 5.3; needs both .uasset and .uexp in same folder |
| JsonAsAsset + j0.dev | Import materials from FModel JSON into UE project | Use C0bra5+Tectors fork; enable Stubs checkbox; MICs need manual parenting |
| C.A.F.E. (mods/4891) | CosmicBoogaloo’s armor/weapon form path tool | Form name must match asset path name exactly |
| NNRM Merge/Split Tool (mods/3051) | Split and recombine NNRM channel-packed textures | Also use c0bra5’s ffmpeg scripts for channel-precise splitting |
| OR SDK (Kein/Altar) | UE project stub with Oblivion superclasses | Compile with VS2022 MSVC v143 v14.38.33130; no engine-from-source needed |
| simple-nanite-parser (c0bra5) | Extract full-detail Nanite meshes | FModel only exports fallback mesh; this gets the real thing. Python, exports to GLTF. |
| Alpakit for UE 5.3.2 | One-click auto-deploy of Blueprint mods | OBR-compatible fork by Wikt0r1us |
| sound2wem | Convert audio files to Wwise .wem format | Required for any audio replacement workflow |
| wwiser | Browse Wwise .bnk soundbanks to find .wem IDs | Find CAkSound node SOURCE IDs for specific voice lines |
| Visual Studio 2022 (direct link) | Compile Altar SDK project and OBSE64 C++ plugins | Game Dev C++ workload + MSVC v143 v14.38.33130 component |
| 1.2 Updater Tool (mods/5281) | Automates collision + material slot name fixing for 1.2 | Run on any mesh mod made before patch 1.2 |
| Body Part Chart (mods/2583) | nyyxn’s authoritative body-part bitmask reference | Required reference for MaleBodySectionHidden values |
| Community DNA Files (mods/2592) | MetaHuman DNA files for all base races and named NPCs | Required for custom head facial animation |
Reference
Every Link & Resource
Every tool, Nexus mod, GitHub repo, guide, and community resource referenced in this codex, organized in one place.
Essential Tools (Install These First)
Loaders & Bridge (Required for Modding)
Mod Managers
ESP / Data Authoring
UE5 Asset Pipeline
Audio Tools
Scripting & Code References
Community Hubs
This codex synthesizes work from hundreds of community researchers. Key contributors by area:
Materials & Modeling: c0bra5 (nanite parser, material reverse-engineering), deathwrench (practical workflow, collision, standalone items), jack_la (morphs, vertex colors), qunai (bitmasks, BP form structure), .astralus (texture pipeline), exicide (beast race compatibility), dotaxis (packaging workflow)
Cloth Physics: tenebrisequitem (cloth physics deep-dive), izedev_55749 (MetaHuman DNA research)
Animation: ryanhank, krasuepisac, michaelpstanich, lunemods, strikshaw, kei7855, jakealaimo, .yeah.nah.yeah.
Mapping: nafnaf_95, khameli0n, narm_, minuteready, dicene, cnnrduncan, .yeah.nah.yeah., wxmichael, agentlefox (heightmap tools research), grimlock_arts (heightmap artist)
Scripting: MadAborModding (levitation / notification bridge), dicene (ConsumeNotification method), dotaxis, zarrastro, randombombombom, veter_, ubawesome, faeriemushroom, crimson, PuddlePumpkin
Reference mods: tommn (Bloodlust, Custom Summoning), yerawizardharreh (Shield on Back), kei7855 (Shortsword animations)
Animation & Modding Examples
Texture & Heightmap Tools
Community Starter Packs (Google Drive)
Unreal Engine Documentation
Video Tutorials
Additional 3D & Rigging Tools
Community
Credits & Contributors
This wiki exists because of hundreds of people who spent countless hours reverse-engineering a game with no official documentation, sharing everything they found for free, and building tools for everyone to use. This page is for them.
The Community Hub
Every discovery in this wiki came from the OBR Modding Discord. If you’re making mods, you need to be here. The #research-general, #research-modeling, #research-scripting-ue4ss, #research-animation, and #research-mapping channels are where the real work happens.
Research Contributors
These are the people who actually figured this stuff out. Every name in these lists did something real - wrote a tool, reversed a system, tested a workflow, documented a finding, or shared a crucial insight that saved everyone else hours of work.
Modeling, Textures & Asset Pipeline
Animation, Audio & ABP System
World Editing & Mapping
Scripting, Lua & Cross-System
Tools & Frameworks
The Compiler & Curator
CosmicBoogaloo - DreamEater
The author of this wiki. I didn’t discover most of what’s on these pages - this community did. My job was to read every Discord export, every research channel, every tool readme, and every pinned message, then organize it into something a new modder can actually use without drowning in 9,655 messages of raw Discord history.
I also made C.A.F.E. (the armor/weapon form path tool), published 60+ mods with 15,000+ downloads, got covered by PC Gamer, GameSpot, and The Gamer, and received a personal compliment from Markus Persson. I’m building a full indie game engine and studio under the DreamEater name. Oblivion Remastered was the spark that started all of it.
Get In Touch
Found an error? Have new research to add? Want to be credited or update your info? Use the form below.