alandtse / alandtse/vr_address_tools
12 mapped addresses for navigation
- Dominant language
- Python
- Stars
- 17
- Forks
- 5
- PR merge metrics
- No merged PRs in 30d
Description
I made a personal port of Compass Navigation Overall for VR and found some 12 entries to contribute to the csv. I used Claude Code with these tools and a LOT of trial and error. I hope this helps. Since I am on 0.206, I decided not to open a PR.
# VR Address Library — New Findings for Compass Navigation Overhaul VR Port
**Date:** 2026-04-15 → 2026-04-16
**Author:** Ported using Skyrim Claude Code Modding Toolkit and Spookys Automod Toolkit
**Game:** Skyrim VR 1.4.15.0
**Base VR Address Library version:** 0.206.0 (13,861 entries)
**Upstream repo:** [alandtse/skyrim_vr_address_library](https://github.com/alandtse/skyrim_vr_address_library)
**Status:** Port is functional — quest distances, location marker distances, and quest objective labels all working in VR.
---
## Summary
While porting [Compass Navigation Overhaul](https://www.nexusmods.com/skyrimspecialedition/mods/74484) (CNO) to SkyrimVR, we identified **12 SE address-library IDs** that were missing from the published VR Address Library 0.206.0. All 12 are required by CNO's CommonLibSSE-NG codebase (which uses `alandtse/CommonLibVR` with `RELOCATION_ID(se, ae)` macros that resolve SE IDs at runtime via the VR CSV).
We recovered the VR offsets from two analysis CSVs in the `skyrim_vr_address_library` repo that contain auto-generated (but unreleased) mappings:
| Source file | Author / tool | Rows | Notes |
|---|---|---|---|
| `addrlib.csv` | bakou / IDA Pro auto-match | 139,817 | Maps `vr_va, sse_va, id` — partially automated |
| `sse_vr.csv` | meh321 / IDADiffCalculator | 833,647 | Maps `sse_va, vr_va` — partially automated |
---
## New Entries (12 IDs)
### Code function IDs (10)
| SE ID | VR RVA | Cross-validated | Function | Used by |
|---|---|---|---|---|
| **50718** | `0x52a9a0` | addrlib.csv only | `HUDMenu::ProcessMessage` | CNO enemy-marker hook (disabled in VR port) |
| **50773** | *(unreliable)* | addrlib.csv only — **WRONG** | `Compass::Update` | CNO vtable override; bypassed via vtable capture instead |
| **50775** | `0x8afd30` | addrlib.csv + sse_vr.csv ✓ | `Compass::SetMarkers` | CNO player-set-marker hook (disabled in VR port) |
| **50826** | `0x8b2bd0` | addrlib.csv only | `HUDMarkerManager::UpdateQuests` | CNO quest-marker hook (disabled in VR port) |
| **50851** | `0x8b3430` | addrlib.csv + sse_vr.csv ✓ | `HUDMarkerManager::AddMarker` | CNO marker capture (attempted prologue hook — failed, see below) |
| **50870** | `0x8b4170` | addrlib.csv + sse_vr.csv ✓ | `HUDMarkerManager::UpdateLocations` | CNO location-marker hook (disabled in VR port) |
| **80229** | `0xf27030` | addrlib.csv + sse_vr.csv ✓ | `GFxValue::ObjectInterface::?` | CNO Scaleform accessor |
| **80274** | `0xf29ab0` | addrlib.csv + sse_vr.csv ✓ | `GFxValue::ObjectInterface::?` | CNO Scaleform accessor |
| **80275** | `0xf29ca0` | addrlib.csv + sse_vr.csv ✓ | `GFxValue::ObjectInterface::?` | CNO Scaleform accessor |
| **80278** | `0xf29f10` | addrlib.csv + sse_vr.csv ✓ | `GFxValue::ObjectInterface::?` | CNO Scaleform accessor |
### Data singleton IDs (2)
| SE ID | VR RVA | Cross-validated | What | Used by |
|---|---|---|---|---|
| **519587** | `0x3011d18` | sse_vr.csv ✓ | `RE::HUDMarker::FrameOffsets` singleton pointer | CNO marker icon-frame classification |
| **519611** | `0x3011e20` | sse_vr.csv ✓ | `RE::HUDMarkerManager` singleton pointer | CNO compass marker state access |
---
## Confidence Assessment
### High confidence (cross-validated by both sources)
IDs **50775, 50851, 50870, 80229, 80274, 80275, 80278** — present in both `addrlib.csv` (bakou/IDA) and `sse_vr.csv` (meh321/IDADiffCalculator) with matching VR addresses. The two tools use independent matching algorithms, so agreement is strong evidence.
IDs **519587, 519611** — present in `sse_vr.csv` only, but meh321's IDADiffCalculator is specifically designed for data-pointer matching (not code functions), so these are reliable for data singletons. **519611 (HUDMarkerManager singleton) confirmed functional in VR** — `currentMarkerIndex` at struct offset 0x14BC reads correctly.
### Medium confidence (single source only)
IDs **50718, 50826** — present only in `addrlib.csv` (bakou/IDA). Not independently verified but geographically consistent with the HUD-function cluster (VR RVAs in the `0x8Axxxx–0x8Bxxxx` range alongside cross-validated neighbors). **50826 confirmed via crash-log stack analysis** — frame annotations using this offset match the expected function context.
### Known bad
ID **50773** (`Compass::Update`) — `addrlib.csv` maps it to VR `0x12212a0`, which is **9.6 MB away** from the adjacent `Compass::SetMarkers` at `0x8afd30`. In SE, these two functions are 896 bytes apart. The VR mapping is clearly an automated-matcher false positive. **Do not use this offset.**
The VR port bypasses this ID entirely by capturing the original function pointer from the `RE::Compass` vtable at runtime (`write_vfunc` returns the old slot value), so no address-library lookup is needed.
---
## CSV Patch Format
The following rows can be appended to `version-1-4-15-0.csv` (the VR Address Library release CSV). The meta row's address count must be incremented by 12.
```csv
50718,52a9a0
50773,12212a0
50775,8afd30
50826,8b2bd0
50851,8b3430
50870,8b4170
80229,f27030
80274,f29ab0
80275,f29ca0
80278,f29f10
519587,3011d18
519611,3011e20
```
> **Note:** ID 50773 is included for completeness (the game won't crash if it's present — it's only used at runtime if a mod explicitly calls `Compass::Update` via `REL::Relocation`). However, the offset is **known incorrect** and should be flagged as unverified/low-confidence in the database.
### Proposed `database.csv` rows (for upstream PR)
```csv
50718,0x14087d580,0x14052a9a0,3,HUDMenu::ProcessMessage
50773,0x140882070,0x1412212a0,1,Compass::Update (UNVERIFIED - likely wrong VR offset)
50775,0x1408823f0,0x1408afd30,4,Compass::SetMarkers
50826,0x1408856f0,0x1408b2bd0,3,HUDMarkerManager::UpdateQuests
50851,0x140885fe0,0x1408b3430,4,HUDMarkerManager::AddMarker
50870,0x140886c60,0x1408b4170,4,HUDMarkerManager::UpdateLocations
80229,0x140eca4e0,0x140f27030,4,GFxValue::ObjectInterface::? (accessor)
80274,0x140eccf60,0x140f29ab0,4,GFxValue::ObjectInterface::? (accessor)
80275,0x140ecd150,0x140f29ca0,4,GFxValue::ObjectInterface::? (accessor)
80278,0x140ecd3c0,0x140f29f10,4,GFxValue::ObjectInterface::? (accessor)
519587,0x142f4c950,0x143011d18,4,HUDMarker::FrameOffsets singleton
519611,0x142f4ca00,0x143011e20,4,HUDMarkerManager singleton
```
Status codes: `1` = unverified, `3` = auto-matched single source, `4` = cross-validated.
---
## VR-Specific Findings (CommonLibSSE-NG / Engine Differences)
### HUDMarkerManager VR struct layout
The `HUDMarkerManager` singleton (via SE ID 519611) IS accessible in VR. However, the struct's internal field population differs from SE:
| Field | SE offset | VR status |
|---|---|---|
| `scaleformMarkerData[49]` | 0x0000 | **Partially works** — GFxValue type tags are present (0x03 at +0x08 within each GFxValue) but `GFxValue::IsNumber()` returns false for valid-looking entries. The VR GFxValue type enum may differ from CommonLib's expectation. |
| `position[48]` | 0x1260 | **NOT populated in VR.** Always zeros. VR's 3D compass rendering uses a different mechanism for marker positions (possibly attached to the `skyvr_hmd_compass.nif` BSFadeNode). |
| `locationRefs` (BSTArray) | 0x14A0 | **Has a valid heap pointer + count**, but `LookupByHandle()` on the stored values returns null. The element size or handle format may differ from SE's `RefHandle` (uint32). |
| `sqRadiusToAddLocation` | 0x14B8 | **Correct** — reads `400000000.0` = `20000²` (float). |
| `currentMarkerIndex` | 0x14BC | **Correct** — reads accurate marker count (0–10 range, matches visible compass markers). |
**Workaround used:** Instead of reading from HUDMarkerManager, iterate the worldspace's **persistent cell** (`TESWorldSpace::persistentCell`) for refs with `ExtraMapMarker` data. This is reliable, gives display names, and works identically in SE and VR.
### PlayerCharacter::VR_PLAYER_RUNTIME_DATA struct layout mismatch
- **Accessor:** `REL::RelocateMember(this, 0, 0x3E0)` — places the struct at `PlayerCharacter + 0x3E0`.
- **Struct comments** (e.g., `objectives` at `/* B70 */`, `playerMapMarker` at `/* 1020 */`) appear to be **SE absolute offsets**, not correctly adjusted for VR.
- **Symptom:** `player->GetVRPlayerRuntimeData().objectives.size()` returned `3780437280` (garbage). The BSTArray's `_size` field is at the wrong offset.
- **Workaround:** Quest objectives iterated via `TESDataHandler::GetFormArray()` with `IsActive() && !IsCompleted() && kDisplayedInHUD` filter instead of player runtime data.
### TESQuest::IsRunning() semantic mismatch
CommonLibSSE-NG defines:
```cpp
bool TESQuest::IsRunning() const {
return !IsStopping() && !promoteTask;
}
```
Returns `true` for **every quest form that was never started** (4380 out of 4380 in test). The correct filter for "quest is currently tracked in the journal":
```cpp
quest->IsActive() // kActive flag (bit 11)
&& !quest->IsCompleted() // not completed
&& quest->data.flags.any(RE::QuestFlag::kDisplayedInHUD) // CK "Show in HUD" checkbox (bit 5)
```
### SKSE::Trampoline::write_branch<5> — NOT a function-entry hook
Reads 4 bytes at `a_src+1` as a signed `rel32` displacement and returns the decoded target. **Assumes `a_src` points at an existing `call/jmp rel32` instruction.** Using it at a function entry point produces a garbage "original function pointer." Correct use: call-site replacement only. Function-entry hooks require MinHook, Detours, or manual Xbyak+hde64.
### VR HUD architecture
- **`HUDMenu.swf` is NOT loaded in VR.** The HUD is rendered via `Interface/VR/VR_HMD_info.swf`.
- `VR_HMD_info.swf` internally contains the same MovieClip hierarchy as SE's `HUDMenu.swf` (`_level0.HUDMovieBaseInstance.CompassShoutMeterHolder.Compass`).
- InfinityUI patches go at `Interface/InfinityUI/VR_HMD_info/...` (filename stem, not subpath `VR/VR_HMD_info/`).
- CNO's `InfinityUIMessageListener` must accept movie URLs containing `VR_HMD_info` (the SE code filters on `HUDMenu` only).
### Interior cell coordinate spaces
Interior cells use cell-local coordinate systems near `(0, 0, 0)`. Cross-cell comparisons (player in one interior, target in another) produce meaningless distances. The game resolves interior quest targets to their **exterior entry door** via `ExtraTeleport` linked-door chains. The VR port replicates this by iterating the target cell's refs for `TESObjectDOOR` with `GetTeleportLinkedDoor()`. When the player is in an interior, location-marker and player-marker passes are disabled entirely.
### Map marker ref iteration
Map markers are **persistent references** stored in `TESWorldSpace::persistentCell`, NOT in individual grid cells. `TESObjectCELL::ForEachReference` on a grid cell won't find them. The correct iteration:
```cpp
auto* worldspace = player->GetWorldspace();
if (worldspace && worldspace->persistentCell) {
worldspace->persistentCell->ForEachReferenceInRange(
player->GetPosition(), radius,
[](TESObjectREFR* ref) {
auto* marker = ref->extraList.GetByType();
if (marker && marker->mapData) {
// marker->mapData->locationName.GetFullName() = display name
}
});
}
```
---
## Tools and environment
| Tool | Version | Purpose |
|---|---|---|
| CommonLibSSE-NG | 3.6.0 (alandtse/CommonLibVR fork) | SE+AE+VR multi-target SKSE library |
| vcpkg | 2026-04-08 (HEAD) | C++ package manager |
| MSVC | 14.44.35207 (VS2022 Community) | Compiler |
| CMake | 4.3.1 | Build system |
| VR Address Library | 0.206.0 | Base CSV (13,861 entries) |
| alandtse/skyrim_vr_address_library | GitHub HEAD (cloned 2026-04-15) | Source of analysis CSVs |
Thanks for all you do!
Contributor guide
Assessment
This issue has not been assessed yet.