Skip to the text
[Devlog] Kingdom Come: Deliverance II - Finding the Third-Person View Toggle Flag

[Devlog] Kingdom Come: Deliverance II - Finding the Third-Person View Toggle Flag

Enabling Kingdom Come: Deliverance II's hidden third-person view: a walk through memory, vftables, and pointer chains.

Third Person Camera · Instalment 1 of 3
  1. I.Finding the Third-Person View Toggle FlagMar 29, 2025
  2. II.Customizing the View: TPV Offsets, Input, and What's Under the HoodMay 07, 2025
  3. III.Building a Proper Third Person CameraJun 06, 2026

Alright folks, it's time for another dive under the hood, this time with Kingdom Come: Deliverance II (KCD2). This post has now been updated to reflect the memory addresses and details for game version 1.3.1. My initial explorations were with an earlier build, but with the latest patch out, it was time to re-verify. My immediate impressions of KCD2 remain the same: the sheer scale and detail Warhorse has achieved is remarkable. Naturally, the engine itself beckoned further investigation with these new addresses. KCD2 is, through and through, a deeply immersive first-person RPG. However, a third-person perspective (TPV) offers distinct advantages: a better view of character equipment, broader environmental awareness, a different tactical feel, and for some players, a more comfortable viewing experience less prone to motion sickness.

My experience with KCD1 and general CryEngine knowledge suggested that a TPV system might already be lurking within the game's code, perhaps used by the developers for debugging, crafting cutscenes, or just as an unexposed feature. This devlog is the chronicle of my reverse engineering adventure into WHGame.DLL for the current version to locate and activate this TPV functionality. It turns out, while a TPV system is indeed present, it definitely feels like it was primarily a development or debug tool – functional, but with its own charming set of quirks!

For those eager to try it out, you can find the TPV Toggle mod based on these findings on NexusMods: Third Person View (TPV Camera) Enabler

The Starting Point: ISystem and the Path to Camera Data

In large game engines like CryEngine (and its KCD2 fork), a common entry point to various global systems is through an interface like ISystem. From my prior reverse engineering work and observations of community tools (e.g., Cheat Engine tables and the KCD2ModLoader often point to g_ISystem, which for this version can be found at WHGame.DLL+3E281F8, corresponding to the CSystem::vftable located at 183e281f8), it's clear that the path to the camera often involves a chain of pointers originating from, or accessible via, ISystem or a "Global Context" object.

The CSystem::vftable (at 183e281f8) is a goldmine. Specifically, the 134th entry (index 133) is a function that yields a pointer to a camera data block. This address is 183e281f8 + 133*8 = 183e281f8 + 0x428 = 183e28620. The function at this vftable entry (FUN_180792250 in my disassembly) simply performs lea rax,[rcx+00000288]. Here, rcx is the this pointer (the ISystem instance), so it's returning a pointer to a member at ISystem + 0x288. This member is our systemCameraMatrixPtr – likely the final storage for the view matrix data.

Log confirming the address
[2025-05-16 15:34:43] [INFO   ] :: GameInterface: systemCameraMatrixPtr at 0x2229856C208

The code that writes to this systemCameraMatrixPtr (FUN_18085cd20) appears to be a large memcpy-style operation, copying a complete camera state into our target memory block.

While having the final matrix is useful, to toggle views, we need to intervene earlier. We need to find the flag or object that dictates which camera controller (First-Person, Third-Person, Dialog, etc.) is active and populates this final matrix structure. This objective led me back to hunting for the wh::game::C_CameraManager.

The AOB Hunt for the Camera Manager's Gateway

Finding the wh::game::C_CameraManager (and from there, the TPV object and its control flag) typically starts with locating a reliable pointer to the game's "Global Context" – a central data structure.

For KCD2, the function FUN_180a0a080 in WHGame.DLL returns a pointer that appears to be the base of this global context. The key instruction loading this is:

Key instructions
WHGame.DLL+A0A0A9 - 48 8B 05 0083AA04     - mov rax,[WHGame.DLL+54B23B0]
; This MOV loads the address stored at WHGame.DLL+54B23B0 into RAX.
; This address, WHGame.DLL+54B23B0, is where the actual Global Context instance pointer is kept.

To find this instruction robustly across minor patches, an AOB (Array of Bytes) signature is essential. Based on the instruction and its neighbors:

AOB to find the global context pointer storage address
// AOB for locating the instruction that loads the global context pointer storage address
constexpr const char *GLOBAL_CONTEXT_PTR_STORAGE_LOAD_AOB = "7F ?? 48 8B 05 ?? ?? ?? ?? 48 83 C4 20 5B C3";

My initializeGameInterface function in the mod uses this AOB:

  1. Scans WHGame.DLL for this byte pattern.
  2. The mov rax,[rip+offset] part (AOB bytes 48 8B 05 ?? ?? ?? ??) starts 2 bytes into my pattern.
  3. It reads the 4-byte relative offset from within the instruction (3 bytes from the start of 48 8B 05).
  4. It calculates the RIP (address of the instruction after the mov).
  5. g_global_context_ptr_address = RIP + relative_offset;. This points to WHGame.DLL+54B23B0, which is the address that stores the pointer to the actual global context instance.

Pointer Chasing: Global Context -> CameraManager -> TPV Object -> Flag

With g_global_context_ptr_address (pointing to WHGame.DLL+54B23B0) identified, the crucial pointer chase to our TPV flag begins. This involves a series of dereferences and offset additions:

  1. Dereference to get the Global Context Instance: First, we read the value at g_global_context_ptr_address to get the actual memory address of the global context object instance.

    global_ctx_instance_ptr = *reinterpret_cast<uintptr_t*>(g_global_context_ptr_address);

    This global_ctx_instance_ptr (e.g., 0x26A07867760 based on earlier traces) is our starting point for navigating the object structure.

  2. From Global Context to Camera Manager Instance: An offset of 0x38 (OFFSET_ManagerPtrStorage) from the global_ctx_instance_ptr leads to where the pointer to the wh::game::C_CameraManager instance is stored. We dereference that to get the manager's instance address.

    cam_manager_instance_ptr = *(uintptr_t*)(global_ctx_instance_ptr + OFFSET_ManagerPtrStorage);

    This cam_manager_instance_ptr (e.g., 0x26C24C1EA20) points to the C_CameraManager object. We can confirm this by inspecting its virtual function table (vftable): *cam_manager_instance_ptr should resolve to wh::game::C_CameraManager::vftable (located at 18406adb8 in version 1.3.1).

  3. From Camera Manager to Third-Person View (TPV) Object Instance: Next, an offset of 0x28 (OFFSET_TpvObjPtrStorage) from the cam_manager_instance_ptr gives us the location of the pointer to the wh::game::C_CameraThirdPerson instance.

    tpv_object_instance_ptr = *(uintptr_t*)(cam_manager_instance_ptr + OFFSET_TpvObjPtrStorage);

    This tpv_object_instance_ptr is the address of the specific TPV camera controller object. Its vftable should match wh::game::C_CameraThirdPerson::vftable (at 183a85220). The C_CameraManager constructor (FUN_180c989b8) further confirms that the manager initializes and holds references or embedded instances of various camera types, including the TPV one.

  4. Locating the TPV Flag within the TPV Object: Finally, an offset of 0x38 (OFFSET_TpvFlag) within the C_CameraThirdPerson object instance points to the 1-byte flag that controls whether TPV is active.

    flag_address = tpv_object_instance_ptr + OFFSET_TpvFlag;

    This flag_address is our ultimate target for the toggle.

The vftables from the disassembly are indispensable for this process. Key vftables include:

  • wh::game::C_CameraManager::vftable: at 18406adb8. (Its constructor FUN_180c989b8 sets up different camera objects).
  • wh::game::C_Camera::vftable: at 18406ab78 (The base class for all cameras).
  • wh::game::C_CameraFirstPerson::vftable: at 183a85098 (Identified by its vftable index 6, FUN_181a57310, which returns the string "FIRST PERSON").
  • wh::game::C_CameraThirdPerson::vftable: at 183a85220 (Identified by its vftable index 6, FUN_181a574d0, returning "THIRD PERSON").
  • Others like wh::game::C_CameraDialog::vftable (183a85140), wh::game::C_CameraRider::vftable (183a85290), and wh::game::C_CameraUI::vftable (183a851b0) further paint the picture of a polymorphic camera system where our TPV flag likely instructs the C_CameraManager which controller's logic to employ.
TPV Flag Discovery PathAdd 0x38 Offset_ManagerPtrStorageVerify vftableAdd 0x28 Offset_TpvObjPtrStorageVerify vftableAdd 0x38Offset_TpvFlagInput: Mod Hotkey('Toggle View')Mod LogicCallgetResolvedTpvFlagAddress()Find Global Context Storage Addr(AOB Scan for MOV nearWHGame.DLL+A0A0A9)Global Context Storage Address(e.g., WHGame.DLL+54B23B0)Dereference to get:Global Context Instance Ptr(e.g., 0x26A07867760)Addr of wh::game::C_CameraManagerInstance Ptr StorageDereference to get:wh::game::C_CameraManagerInstance Ptr(e.g., 0x26C24C1EA20)wh::game::C_CameraManager::vftable @18406adb8Addr of wh::game::C_CameraThirdPersonInstance Ptr StorageDereference to get:wh::game::C_CameraThirdPersonInstance Ptrwh::game::C_CameraThirdPerson::vftable @183a85220Address of TPV Flag Byte(within TPV Object)Mod Reads/WritesFlag Value (0 <-> 1)Game Calls Active CameraController's Update(e.g., FUN_1839ad780 for TPV)

Implementing the Toggle

With the volatile BYTE* tpvFlagAddress reliably resolved, the actual toggle becomes simple:

Implementing the Toggle
void toggleThirdPersonView() {
    Logger &logger = Logger::getInstance();
 
    volatile BYTE* flagAddr = getResolvedTpvFlagAddress(); // Uses the chain described
    if (flagAddr) {
        // 0 for First-Person, 1 for Third-Person
        *flagAddr = (*flagAddr == 0) ? 1 : 0;
        logger.log(LOG_INFO, "TPV Flag toggled to: " + std::to_string(*flagAddr));
    } else {
        logger.log(LOG_WARNING, "TPV Flag Address not resolved, cannot toggle.");
    }
}

The game's camera system, likely coordinated by C_CameraManager or a higher-level update function, reads this flag. It then delegates rendering tasks to the appropriate active camera controller (e.g., wh::game::C_CameraFirstPerson or wh::game::C_CameraThirdPerson). Their respective "update" methods (like FUN_180b96198 for FPV or FUN_1839ad780 for TPV, which perform complex quaternion math) then compute the view parameters for that frame.

Limitations of the Built-in TPV Camera

It's crucial to remember that KCD2 is primarily a first-person experience. This built-in TPV, while functional via this flag, exhibits characteristics of a debug or developer tool rather than a fully polished player-facing feature:

  • Clipping Issues: The TPV camera often lacks proper collision detection, meaning it can clip through walls, objects, and terrain.
  • UI/Event Glitches: Some game events, menus (like the main map or dialog screens), or interactions might behave unexpectedly or have minor visual bugs when in TPV. The game often expects an FPV context for these UI elements.
  • Unexpected Camera Shifts: The camera distance or angle in TPV might shift erratically in certain game situations (e.g., tight corridors, specific animations). This behavior seems inherent to this unpolished TPV mode.
  • Horse Riding Tilt: When riding a horse, the TPV camera can sometimes appear slightly tilted or off-center.
  • Aiming/Interactions: Precise aiming (especially with bows) and context-sensitive interactions are finely tuned for FPV and will likely feel awkward or misaligned in TPV.
  • No Y-Axis Mouse Look on Model: Character head tracking based on Y-axis mouse input (looking up/down) is typically not implemented for the third-person player model; the camera moves, but Henry's head might not follow vertically in a natural way.

Despite these limitations, having the TPV option is fantastic for screenshots, general exploration, or just a different way to enjoy Bohemia!

Finally, KCD2 in Third-Person View!

Future Aspirations: A Custom TPV?

While toggling this built-in TPV flag is a great achievement, a more robust, player-friendly solution would likely involve crafting a completely custom third-person camera system from scratch within the mod. This is a much larger endeavor and would involve:

  • Properly handling Henry's player pose and ensuring animations look correct from all TPV angles.
  • Implementing smooth camera controls with configurable distances, offsets, and potentially shoulder-switching.
  • Adding robust camera collision detection and obstacle avoidance logic.
  • Modifying or creating new aiming reticles and ensuring interaction prompts work intuitively from TPV.
  • Extensive use of raycasting for ideal camera positioning, targeting, and line-of-sight checks.

This would essentially mean creating a new camera controller. It might involve intercepting the FPV camera's final matrix data and then calculating custom offsets and rotations based on player input and world geometry, or perhaps even attempting to inject a new custom camera controller into the game's existing polymorphic camera management system if a suitable hook point can be found. For now, unlocking the game's own (though quirky) TPV offers a very welcome alternative perspective.

The geometry is easier to understand when you can move it. This little camera rig lets you explore the pivot, the arm behind it, and the shoulder offset. Move the camera to one shoulder, then switch Look at the pivot off. With it on, the camera turns to keep the pivot centred; with it off, the camera keeps the rig's heading, so the subject shifts across the frame. The reading links lead into the motion and collision problems in the rest of this series.

A study in motion · Interactive plate

The camera has a body, too.

A subject, a spring arm, and one inconvenient wall. Change the rig; watch what the camera sees.

pivot / subjectcameraboomcollider

Four experiments. Start still, then play the path.

Generic teaching model; fading is an experiment here.The working mod and video live on Nexus / YouTube. This plate only teaches the motion.

This whole process, from identifying target functions via disassembly, crafting AOBs, meticulously tracing pointers through multiple object instances, and confirming object types using vftables, is pretty standard fare for this kind of in-depth game modding. Each game update from Warhorse might necessitate re-validating these AOBs and memory offsets, but the underlying architecture (like how ISystem connects to C_CameraManager which then holds different camera types) often remains a stable pattern derived from CryEngine.


The satisfaction of finally flipping that view after hours immersed in hex and assembly listings is what keeps this hobby so engaging. Now, if you'll excuse me, Bohemia looks quite different from a few feet back!

For those interested in the code, you can check out the tools used for this and other KCD2 experiments on GitHub: KCD2Tools

KCD2 Modding: Because Henry deserves to see his own magnificent armor. CryEngine, CryMore!

Set down 29 March 2025 · 1,722 words · 9 min read

Cross-references

Technical · Third Person Camera
Next entryMay 07, 2025

[Devlog] Kingdom Come: Deliverance II - Customizing the View: TPV Offsets, Input, and What's Under the Hood

Alright folks, our exploration of modding Kingdom …


Aleks

Hi, I'm Aleks, a software engineer into open source and micro startups. This blog is where I write up the things I take apart: game engines, the web platform, and the ideas that survived contact with production.