Introduction
This book explains the internal design of the Dark Ages 4.21 Stone client. It follows the program from process startup into its main loop, then separates the client into rendering, audio, input, user interface, file, event, and network systems.
Each subsystem is explained at two levels. The high-level section describes responsibilities, lifecycle, and data flow in approachable language. The code-level section follows function calls, decisions, addresses, arguments, and state. A function table closes each subsystem for readers who want a compact technical reference.
This is a living reverse engineering reference. Empty sections are intentional at the start of the project. They define where understood behavior will go without filling gaps with speculation.
Audience
The book is written primarily for people in the Dark Ages developer community. It assumes that many readers already understand the game, its terminology, and the broad shape of its clients and protocol. It does not assume that every reader is comfortable reading disassembly or following compiler-generated C++.
Ways to use this book
Understand a client system
Start with a subsystem’s high-level section to see its responsibilities, lifecycle, and connections to the rest of the client. Continue into the code-level section when you need the actual call path, arguments, decisions, and state.
Connect network behavior to client behavior
Protocol and networking pages connect message framing and fields to the client functions that parse, dispatch, and act on them. This path is intended for proxy and server developers who already know the wire side and want to understand what happens after a message reaches the client.
Investigate a quirk or version difference
Use subsystem headings and function tables to move from visible behavior to the responsible code. Where differences with another client version are established, the relevant page should state what changed and link the comparison to exact functions or data structures.
Implement a file format
Format pages provide field layouts, byte order, string rules, reader and writer behavior, validation, and parser quirks. They are intended to remain useful for formats that continue to appear in other clients and community tools.
Familiarity with C-like code is helpful for the technical sections. Platform-specific Windows, DirectX, compiler, and networking details are explained when they affect the client behavior being discussed.
Current state
Repository structure and documentation conventions are established. Subsystem behavior has not yet been mapped.
Scope and Method
Scope
The project covers behavior implemented by the analyzed Darkages.exe, including:
- Process initialization, configuration, main loop, state changes, and shutdown
- Rendering, surface management, palette behavior, sprites, text, and presentation
- Sound effects, music, resource lifetime, and playback control
- Keyboard, mouse, Win32 messages, and internal event delivery
- File lookup, archive or container access, decoding, parsing, and serialization
- Socket lifetime, connection states, packet framing, message dispatch, and field serialization
- Byte transformations such as XOR when their exact behavior and order are understood
- Compiler and runtime mechanisms when they materially explain application behavior
Compatibility wrappers such as cnc-ddraw are outside the client architecture unless their interaction reveals a client requirement worth documenting.
Clean-room policy
The repository contains original documentation, diagrams, tables, and limited explanatory pseudocode derived from analysis. It does not contain the executable, supporting DLLs, game assets, private runtime captures, an IDA database, or reconstructed source code.
Local copies may be used as analysis inputs. Proprietary materials must never be committed. The ignored .local/ directory may hold a private working IDA database.
Two levels of explanation
Every subsystem chapter has a high-level section and a code-level section.
The high-level section explains responsibilities, lifecycle, data flow, and interactions with other parts of the client. It should be understandable without following assembly or decompiler output.
The code-level section follows the real implementation. It records call trees, function addresses, prototypes, arguments, return values, branches, state changes, important data, and cleanup paths. The chapter ends with a function table for readers who need a compact technical reference.
What belongs in the book
The book contains behavior confirmed through direct code analysis or supported with high confidence when code patterns and observed behavior agree. Speculative leads remain in local notes or tentative IDA names until they are understood well enough to explain clearly.
The decompiler is a useful view of the program, but its types and reconstructed control flow are not automatically correct. Disassembly and call sites should be checked whenever signedness, ownership, object layout, calling convention, or optimizer behavior affects the explanation.
Address and naming conventions
A function reference uses the form Darkages.exe:0x00401234, current IDA name net_receive_packet. Unless a page says otherwise, addresses are virtual addresses from the analyzed image rather than file offsets or RVAs.
IDA names are descriptive research names. They are not presented as original source symbols. The function address remains the stable reference if a name improves later.
Legal Disclaimer
Ownership
Dark Ages and all original game software, code, artwork, music, sound, text, characters, names, logos, trademarks, and related materials are the property of their respective copyright and trademark owners. Nothing in this project transfers, limits, or challenges those rights.
The contributors claim authorship only over the original documentation, diagrams, research notes, and explanatory pseudocode created for this project. References to the game and its behavior are included only where needed to explain the research.
Independent project
This is an independent and unofficial research project. It is not affiliated with, sponsored by, approved by, or endorsed by the developers, publishers, operators, or rights holders of Dark Ages.
Purpose and fair use
This project is undertaken in good faith for noncommercial education, scholarship, preservation, commentary, and interoperability research. The maintainers believe that the limited analysis, factual descriptions, and necessary references presented here are consistent with fair-use principles under 17 U.S.C. Section 107.
Fair use is determined from the facts of each use. An educational or noncommercial purpose does not automatically make every use fair. The U.S. Copyright Office Fair Use Index provides general information and examples, but this project does not offer legal advice or guarantee that every possible use of its material will qualify as fair use.
What is not distributed
This repository does not distribute the game executable, supporting DLLs, game assets, music, artwork, private packet captures, IDA databases, license material, or reconstructed proprietary source code. Contributors must never force-add or commit such material. A local IDA database may remain in the ignored .local/ workspace described in the README.
The documentation is not a substitute for obtaining the game or any other copyrighted work through lawful means. Users are responsible for ensuring that their own research, implementations, and distribution choices comply with applicable law and any agreements that apply to them.
Rights-holder concerns
A rights holder who believes this repository contains material that should not be present may contact the repository maintainers through the GitHub project. Please identify the material and explain the concern so it can be reviewed promptly.
Client Overview
This chapter connects the major runtime systems and explains how control and data move through the client as a whole.
High-level architecture
This section will describe the top-level application state, major subsystem boundaries, screen or game states, shared services, thread ownership, and the order in which input, networking, game logic, audio, and rendering are processed.
The client architecture has not been mapped yet.
Code-level coordination
This section will begin at the application entry point and follow the functions that construct, connect, update, and destroy the major systems. It will record global state, top-level objects, dispatch points, main-loop decisions, and cross-subsystem calls.
No top-level call tree is documented yet.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
| No functions documented yet |
UI, Input, and Packet Flows
This page connects visible in-game UI behavior to the internal Event system and the client and server packet actions that drive it. The most important distinction is ownership. Some panes are persistent and are only shown or hidden. Some are allocated in response to a server packet. Bulletin and mail use a longer-lived session object that repeatedly replaces its active child dialog.
The packet action byte is the first byte of the decoded logical packet. Packet offsets in this page are relative to that action byte unless stated otherwise.
High-level operation
End-to-end architecture
flowchart LR
win["Win32 mouse and key messages"] --> queue["EventMan input queue"]
queue --> input["Pane Event types 0 through 8"]
input --> local["Pane mouse and key handlers"]
local --> transition["Create, show, hide, or remove pane"]
local --> builder["Client packet builder"]
builder --> send["net_c_queue_send"]
send --> encode["Sequence, XOR, and TCP frame"]
encode --> server["Game server"]
server --> tcp["TCP frame receive and XOR decode"]
tcp --> socket["Pane socket Event type 9"]
socket --> map["MapPane and registered child handlers"]
map --> parser["Server-action parser"]
parser --> transition
parser --> state["Persistent pane or dialog fields"]
state --> transition
timer["Pane timers"] --> delay["Spell delay controller"]
delay --> builder
Win32 messages do not call game panes directly. win_main_window_proc queues input in EventMan. The event worker converts it to internal Events, and the dispatcher calls the relevant pane virtual handler. Client packet builders then submit logical packets to net_c_queue_send.
The reverse path begins when the Socket worker completes and decodes a frame. EventMan creates Event type 9 and traverses the registered pane hierarchy. MapPane owns most in-game server actions, while persistent child panes such as the equipment, skill, and spell panes claim the actions that update their own state.
Pane transition models
| Model | Examples | What “show” means | What closes or replaces it |
|---|---|---|---|
| Persistent content selection | status, chat, equipment, skill inventory, spell inventory | Hide the previous content pane through vslot +0x18, show the selected pane through +0x14, then store it at GameButtons +0x124. | Selecting another content pane. Equipment can also hide itself when selected twice. |
| Persistent server-populated pane | equipment, users | The object survives between updates. A packet changes fields and invalidates controls; visibility is a separate operation. | Equipment is hidden locally. The users pane survives after it is first created and can be repopulated. |
| Dynamic dialog | options, paper, screen menu, pursuit message variants, skill and spell detail dialogs | Allocate, construct controls, add to the Screen tree, and register in the Event tree. | The dialog’s control or generic Dialog key path unregisters, removes, and deletes or defers deletion. |
| Session with replaceable child | bulletin and mail | Allocate one BulletinSession, then replace its current BoardList, ArticleList, Article, MailList, or Mail child as responses arrive. | Session destruction removes the active child and clears the static session root. |
| Dynamic slot children | individual skill and spell slots | Server add actions allocate a slot pane and add it to both registries. Parent inventory visibility is unchanged. | The matching server remove action unregisters, removes, deletes, and clears the indexed pointer. |
Server actions that affect UI
| Server action | UI target | Creation or visibility effect | State effect and handler |
|---|---|---|---|
SStatus 0x08 | persistent status and related game panes | Does not select or show the status pane. | Updates already registered status fields through several pane-local handlers, including Darkages.exe:0x0043F420 and Darkages.exe:0x00441850. |
SMessage 0x0A, subtype 8, 9, or 10 | generic message dialog | Allocates and registers a dynamic message dialog. Subtype 10 selects the alternate constructor mode. | A big-endian text length at packet +2 controls the copied message. Tabs are normalized to carriage returns before construction. |
SSelfSaveOk 0x21 | registered OptionPane | Allocates a local message dialog containing “Saved.” | ui_option_pane_handle_server_packet claims the action. It reads no payload fields. |
SAddSpell 0x17 | spell inventory slot | Allocates, adds, and registers one spell slot pane. It does not show the parent spell inventory. | ui_spell_inventory_handle_server_packet calls the add path after removing any old pane in the same slot. |
SRemoveSpell 0x18 | spell inventory slot | Unregisters, removes, and deletes one slot pane. | Clears the indexed slot pointer. |
SEnterEditingMode 0x1B | paper dialog | Allocates the same Paper dialog class in mode 0. | Parses editing-mode fields before building controls. |
SAddSkill 0x2C | skill inventory slot | Allocates, adds, and registers one skill slot pane. It does not show the parent skill inventory. | ui_skill_inventory_handle_server_packet replaces the indexed slot. |
SRemoveSkill 0x2D | skill inventory slot | Unregisters, removes, and deletes one slot pane. | Clears the indexed slot pointer. |
SScreenMenu 0x2F | full-screen menu pane | Allocates a 0x104-byte pane, adds it below BackgroundPane, and registers it. | ui_screen_menu_pane_ctor parses the packet and later menu responses can update the registered menu receiver. |
SPursuitMenu 0x30 | message or question dialog family | Allocates one of seven dynamic variants according to packet +1. | Subtype 2 constructs QuestionMessageDialog; subtype 6 constructs QuestionMessageFaceDialog. The other five concrete source class names are not yet established. |
SBulletin 0x31 | bulletin and mail session | Creates a session for an allowed unsolicited packet when no session exists, or replaces the active child in an existing session. | Subtypes 1 through 5 select board list, article list, article, mail list, and mail. Subtype 6 is accepted without creating a child. |
SShowPaper 0x35 | paper dialog | Allocates a 0x560-byte Paper dialog in mode 1 and registers it below BackgroundPane. | Mode 1 parses the server text and display flags before building controls. |
SShowUsers 0x36 | persistent Users Dialog Pane | Populates the existing pane and explicitly shows it. | Parses list metadata and variable-length user records, enables row controls, starts a 100 ms pane timer, and invalidates the region. |
SAddEquip 0x37 | persistent User Equip pane | Does not show the pane. | Stores a big-endian item identifier and length-prefixed name in the selected equipment slot, then redraws. |
SRemoveEquip 0x38 | persistent User Equip pane | Does not hide the pane. | Clears the selected slot identifier and name, then redraws. |
SSelfLook 0x39 | persistent User Equip pane and embedded legend/details | Does not show the pane. | Replaces character strings and appearance/detail fields, updates the embedded legend parser, and redraws. |
SCooldown 0x3F | skill or spell slot | Does not select the parent pane. | The skill and spell inventory handlers route the action to their respective slot cooldown update paths. |
SReconnect 0x4C, subtype 1 | exit-wait pane | Keeps the existing pane and replaces its warning with the safe-to-exit state. | Sends CQuit 0x0B, subtype 0, completing the orderly-exit exchange. This branch does not reconnect a socket. |
No established server action directly selects the persistent A/S/D/F/G content pane. Server status, skill, spell, and equipment actions update those panes even while another content pane is visible.
Client actions that affect UI or packets
| Input or control action | Immediate UI effect | Client traffic | Server-driven continuation |
|---|---|---|---|
A or inventory button | Selects equipment. If it is already current and visible, hides it. If it is current but hidden, shows it again. | Reopening the hidden current equipment pane first sends CSelfLook 0x2D, one byte with no payload. Switching from another content pane sends no self-look request. | SSelfLook 0x39 refreshes data but does not itself show the pane. |
S or skill button | Selects the persistent skill inventory and hides the previous content pane. | None merely for selection. | SAddSkill, SRemoveSkill, and SCooldown maintain its slot children. |
D or spell button | Selects the persistent spell inventory and hides the previous content pane. | None merely for selection. | SAddSpell, SRemoveSpell, and SCooldown maintain its slot children. |
F or chat button | Selects the persistent chat pane. | None merely for selection. | Chat controls emit their own message actions when text is submitted. |
G or status button | Selects the persistent status pane. | None. | SStatus updates fields independently of selection. |
Q or options button | Allocates and registers a new dynamic OptionPane below BackgroundPane. | The open action itself sends no packet. Option controls can send CUserSetting 0x1B with one setting-code byte. | SSelfSaveOk 0x21 creates the local “Saved.” message dialog while the pane is registered. |
X while OptionPane owns the shortcut | Allocates ui_exit_wait_pane over the options UI. | Its constructor sends CQuit 0x0B, subtype 1. | SReconnect 0x4C, subtype 1, sends the subtype 0 completion and changes the pane to safe-to-exit text. |
| Right down on a skill slot | Allocates and registers SkillBookDialog. | None. | None required. |
| Activate a skill slot | Runs the slot’s activation path. | Optional CSpellDelaySay 0x4E, followed by CUseSkill 0x3E with one slot byte. | Cooldown state can later arrive through action 0x3F. |
| Right up on a spell slot | Allocates and registers SpellBookDialog. | None. | None required. |
| Activate a spell slot | Chooses an immediate, targeting, or text-input path from the spell’s type byte. | The confirmed immediate path builds action 0x0F plus one slot byte. With a configured delay it first sends CSpellDelayRequest 0x4D and timed CSpellDelaySay 0x4E packets, then sends the held 0x0F packet. | Target selection or input completion supplies any additional cast context before the packet enters the delay controller. |
W or bulletin button | Allocates and registers BulletinSession. | Sends CBulletin 0x3B, subtype 1. | SBulletin subtype 1 creates the first BoardList dialog. |
| Bulletin or mail navigation controls | Keeps the session and changes the active child when a response arrives. | CBulletin subtype 2 requests lists, 3 requests an article or mail, 4 posts an article, 5 deletes, 6 posts mail, and 7 changes article highlight state. | SBulletin subtypes 1 through 5 replace the active child. |
E or users button | Lazily creates Users Dialog Pane, immediately hides it, and leaves it registered. Existing panes are reused. | Sends one-byte CWho 0x18 on every activation. | SShowUsers 0x36 parses the reply and shows the pane. |
| Screen-menu selection or text submission | Uses the currently registered SScreenMenu pane. | Sends CMenuCode 0x39. Every traced variant begins with a menu type byte, a big-endian object identifier, and a big-endian menu code, followed by variant-specific selection or text. | Later SScreenMenu or SPursuitMenu traffic can replace or update the interaction. |
| Question dialog answer | Leaves the response to the dialog’s answer virtual. | QuestionMessageDialog and its face variant send CMessage 0x3A, including the dialog type, object and pursuit identifiers, response marker 1, and selected answer byte. | Server pursuit traffic determines the next dialog. |
| Paper submit or editing completion | Normalizes carriage returns to tab bytes for the packet and closes through the Paper path. | Sends CExitEditingMode 0x23 with mode, a big-endian text length, and the text bytes. | Server behavior after submission is outside the local UI transition. |
Bulletin and mail child replacement
The session owns a ten-entry dialog history array beginning at session +0xF8, the current index at +0xF5, and the active child at +0x120. Each successful response constructs a new dialog, detaches or queues deletion of superseded children, stores the new child in the history, sets the child’s back-pointer to the session, and invokes the child’s class-specific build/register virtual.
SBulletin subtype | Child result | Matching client request family |
|---|---|---|
1 | BoardListDialog | CBulletin subtype 1, start session |
2 | ArticleListDialog | CBulletin subtype 2, list request |
3 | ArticleDialog | CBulletin subtype 3, article request |
4 | MailListDialog | CBulletin subtype 2, mail-list request |
5 | MailDialog | CBulletin subtype 3, mail request |
6 | No new child | Completion or acknowledgement for a prior write operation |
Article-list and mail-list responses are accepted only while the session’s request-wait flag at +0x124 is set. This prevents an unsolicited list response from replacing the active child.
Spell-delay sequence
The spell slot builds the final action before the delay begins. The delay controller copies that logical packet into its own buffer and records its length, so the final timer callback does not reconstruct the cast.
sequenceDiagram
participant Input as Spell slot input
participant Delay as Spell delay controller
participant Queue as Client send queue
participant Server as Server
Input->>Delay: Hold packet 0x0F, slot
alt delay is zero
Delay->>Queue: Queue held 0x0F packet
else delay is positive
Delay->>Queue: CSpellDelayRequest 0x4D, seconds
loop once per second
Delay->>Queue: CSpellDelaySay 0x4E, configured phrase
Delay->>Delay: Schedule next 1000 ms timer
end
Delay->>Queue: Queue held 0x0F packet
end
Queue->>Server: Sequence, XOR, and frame each logical packet
The phrases are loaded from SpellBook.cfg and matched against the current spell name. A runtime packet hook that only watches the initial mouse event will therefore miss the later timer-owned final send.
Runtime observation roots and useful fields
The Runtime UI Memory Map gives the complete read-only Screen and Event tree traversal, vtable discriminators, field layouts, and stable snapshot procedure.
The virtual addresses below assume image base 0x00400000. External tools should use loaded_module_base + RVA.
| VA | RVA | Current IDA name | Object and useful chain |
|---|---|---|---|
Darkages.exe:0x004E2D70 | 0x000E2D70 | ui_bulletin_session | Active heap BulletinSession, or null. Child history begins at +0xF8, current index is +0xF5, active child is +0x120, request-wait flag is +0x124. |
Darkages.exe:0x004E32A4 | 0x000E32A4 | ui_equip_pane | Persistent heap User Equip pane. Slot 1 through 13 identifiers are at +0xB22 + 2 * slot; names are 0x80-byte records at +0xB3E + 0x80 * (slot - 1); embedded legend/details ownership begins at +0x11CC. |
Darkages.exe:0x004E3564 | 0x000E3564 | ui_users_dialog_pane | Heap Users Dialog Pane, or null before first CWho. It is reused across SShowUsers replies. |
GameButtons owner field +0x124 | dynamic | current content pane | Points at the currently selected status, chat, equipment, skill, or spell content pane. Find the owner through the Screen or Event registry. |
GameButtons owner fields +0x130 through +0x140 | dynamic | content pane pointers | +0x130 chat, +0x134 status, +0x138 equipment, +0x13C skill inventory, +0x140 spell inventory. |
These pointers are observation roots, not synchronization primitives. Dialog removal may use deferred deletion, and the Screen and Event registries can change while another thread reads them. Validate the pane vtable and take a stable registry snapshot before following dynamic children. Writing a visible flag alone is unsafe because Screen composition, Event eligibility, capture, timers, and owner pointers must agree.
Code-level flow
Persistent content selection
ui_game_buttons_handle_key_event accepts Event type 8 and maps A/S/D/F/G to indices 0 through 4. ui_game_buttons_select_content dispatches to the relevant selector. A selector hides the previous pane through vslot +0x18, shows the new pane through +0x14, stores it at owner +0x124, updates the associated button layout, and redraws.
Equipment has an additional same-pane branch. ui_game_buttons_select_inventory calls ui_pane_is_visible. A visible current equipment pane is hidden. A hidden current pane causes net_c_send_self_look to queue action 0x2D, then the pane is shown. This order means the old equipment data can become visible before the asynchronous SSelfLook reply arrives.
Equipment and self-look
ui_equip_pane_ctor publishes ui_equip_pane, while its destructor clears the pointer. ui_equip_pane_handle_server_packet claims only actions 0x37, 0x38, and 0x39.
ui_equip_pane_add_item reads the slot at packet +1, a big-endian identifier at +2, the name length at +4, and name bytes at +5. ui_equip_pane_remove_item reads the slot at +1 and clears the corresponding fields. ui_equip_pane_apply_self_look replaces the larger appearance, identity, and legend state before invalidating the pane.
Skill and spell inventories
ui_skill_inventory_handle_server_packet dispatches 0x2C, 0x2D, and 0x3F. ui_spell_inventory_handle_server_packet dispatches 0x17, 0x18, and 0x3F. Both add handlers first remove an existing pane at the packet-selected index, then allocate a class-specific slot object and add it to both registries. Both remove handlers perform the inverse sequence and clear the slot pointer.
ui_skill_slot_handle_mouse_event constructs SkillBookDialog on right-button down inside the slot. The activation branch sends the optional action 0x4E phrase and then net_c_send_use_skill, which emits [0x3E, slot].
ui_spell_slot_handle_mouse_event constructs SpellBookDialog on right-button up inside the slot. Its activation dispatcher selects immediate cast, targeted cast, or an input dialog from the spell type at slot +0xF8. ui_spell_slot_begin_cast builds [0x0F, slot] and passes it to ui_spell_delay_begin. A nonzero configured delay sends action 0x4D, installs a 1000 ms callback, emits action 0x4E phrases, and releases the held cast in ui_spell_delay_handle_timer.
Bulletin and mail
ui_window_buttons_show_bulletin_session allocates 0x12C bytes and calls ui_bulletin_session_ctor in client-opened mode. The constructor publishes ui_bulletin_session, adds and registers the session below BackgroundPane, then calls net_c_send_bulletin_start.
An unsolicited SBulletin reaches ui_map_handle_bulletin. When no session exists and the packet flag allows creation, the map handler allocates the same object in server-opened mode and passes the original packet to the constructor. When a session already exists, its registered socket handler claims the packet directly.
ui_bulletin_session_process_packet reads packet +1 and dispatches to the five child processors. Each processor handles history cleanup, attaches the new child, sets the session back-pointer, and calls the child build virtual. Article and mail identifiers of zero produce a small “No More” dialog instead of an Article or Mail child.
Users, paper, and server menus
Window button Q calls ui_window_buttons_show_option_pane, which allocates 0x578 bytes and constructs OptionPane. ui_option_pane_ctor installs ui_option_pane_vtable, builds the controls, and adds and registers the pane below BackgroundPane. Its mouse, key, socket, and timer vslots are all class-specific. Option controls call net_c_send_user_setting; a registered OptionPane claims SSelfSaveOk and constructs the local confirmation dialog. Its X shortcut constructs the exit-wait pane, beginning the two-step CQuit and SReconnect orderly-exit exchange.
Window button E always calls net_c_send_who_request. On first use it also allocates Users Dialog Pane, publishes ui_users_dialog_pane, adds and registers it, and immediately calls vslot +0x18 to keep it hidden. ui_map_handle_show_users later obtains that static object, calls ui_users_dialog_parse_server_list, and then ui_users_dialog_show.
ui_map_handle_show_paper allocates Paper in mode 1. SEnterEditingMode uses the same constructor in mode 0. Both parsers eventually call ui_paper_dialog_build_content, which creates the controls, adds the dialog below BackgroundPane, and registers it.
ui_paper_dialog_handle_completion reads the text control, changes carriage returns to tab bytes, serializes CExitEditingMode, and closes the dialog when its completion conditions permit submission. net_c_send_exit_editing_mode uses the same packet layout without owning the close transition.
ui_map_handle_screen_menu allocates and constructs one full-screen menu pane for action 0x2F. Menu interaction functions serialize CMenuCode action 0x39. ui_map_handle_pursuit_menu reads packet +1 and allocates one of seven message classes. The two friendly question class names are proven by constructor diagnostics and vtable installation. Their answer virtuals serialize CMessage action 0x3A.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x0040EB10 | ui_bulletin_session_ctor | void *__thiscall(void *, int, const uint8_t *) | Construct and publish a bulletin/mail session. | Called for client-opened W flow and allowed unsolicited SBulletin. |
Darkages.exe:0x0040ECA4 | net_c_send_bulletin_start | void __thiscall(void *) | Send CBulletin subtype 1. | Called by the client-opened session constructor path. |
Darkages.exe:0x0040ED04 | ui_bulletin_session_process_packet | void __thiscall(void *, const uint8_t *) | Dispatch SBulletin subtype. | Calls the five child-specific processors. |
Darkages.exe:0x0040EE64 | ui_bulletin_session_process_board_list | void __thiscall(void *, const uint8_t *) | Construct and attach BoardListDialog. | SBulletin subtype 1. |
Darkages.exe:0x0040F8F4 | ui_bulletin_session_process_article_list | void __thiscall(void *, const uint8_t *) | Construct and attach ArticleListDialog. | SBulletin subtype 2, gated by request-wait state. |
Darkages.exe:0x00410A14 | ui_bulletin_session_process_article | void __thiscall(void *, const uint8_t *) | Construct ArticleDialog or a no-more dialog. | SBulletin subtype 3. |
Darkages.exe:0x004118F4 | ui_bulletin_session_process_mail_list | void __thiscall(void *, const uint8_t *) | Construct and attach MailListDialog. | SBulletin subtype 4, gated by request-wait state. |
Darkages.exe:0x00412974 | ui_bulletin_session_process_mail | void __thiscall(void *, const uint8_t *) | Construct MailDialog or a no-more dialog. | SBulletin subtype 5. |
Darkages.exe:0x0042EAF0 | ui_equip_pane_handle_server_packet | int __thiscall(void *, void *) | Claim equipment and self-look packets. | Calls add, remove, or full self-look parser. |
Darkages.exe:0x0042EBA4 | ui_equip_pane_apply_self_look | void __thiscall(void *, const uint8_t *) | Apply SSelfLook. | Updates the persistent pane without changing visibility. |
Darkages.exe:0x0043CF20 | ui_game_buttons_handle_key_event | int __thiscall(void *, void *) | Map A/S/D/F/G to content panes. | Calls ui_game_buttons_select_content. |
Darkages.exe:0x0043D164 | ui_game_buttons_select_content | void __thiscall(void *, int8_t) | Dispatch persistent pane selection. | Indices 0 through 4 are inventory, skills, spells, chat, status. |
Darkages.exe:0x0043D3D4 | ui_game_buttons_select_inventory | void __thiscall(void *) | Select or toggle equipment. | Hidden same-pane path sends CSelfLook. |
Darkages.exe:0x0043D4C4 | net_c_send_self_look | void __cdecl(void) | Send one-byte CSelfLook. | Queues action 0x2D. |
Darkages.exe:0x0043E1B4 | ui_window_buttons_select | void __thiscall(void *, int8_t) | Dispatch Q/W/E window buttons. | Bulletin is index 1; users is index 2. |
Darkages.exe:0x0043E2B4 | ui_window_buttons_show_option_pane | void __thiscall(void *) | Create a dynamic OptionPane. | Q or window-button index 0. |
Darkages.exe:0x0043E384 | net_c_send_who_request | void __cdecl(void) | Send one-byte CWho. | Users list request, action 0x18. |
Darkages.exe:0x00442990 | ui_skill_inventory_handle_server_packet | int __thiscall(void *, void *) | Maintain skill slot panes. | Handles 0x2C, 0x2D, and 0x3F. |
Darkages.exe:0x00443B10 | ui_spell_inventory_handle_server_packet | int __thiscall(void *, void *) | Maintain spell slot panes. | Handles 0x17, 0x18, and 0x3F. |
Darkages.exe:0x00445830 | ui_users_dialog_parse_server_list | void __thiscall(void *, const uint8_t *) | Populate Users Dialog Pane. | Called by ui_map_handle_show_users before showing. |
Darkages.exe:0x00454C64 | net_c_send_use_skill | void __thiscall(void *) | Send [0x3E, slot]. | Called by skill activation. |
Darkages.exe:0x00455684 | ui_spell_slot_begin_cast | void __thiscall(void *) | Build [0x0F, slot]. | Passes the logical packet to the delay controller. |
Darkages.exe:0x004556F4 | ui_spell_delay_begin | established __thiscall method | Hold or immediately send a spell packet. | Loads SpellBook.cfg, sends delay traffic, and installs timer callback 0. |
Darkages.exe:0x00455CA4 | net_c_send_spell_delay_request | void __thiscall(void *, uint8_t) | Send action 0x4D. | Payload is configured delay seconds. |
Darkages.exe:0x00456940 | ui_spell_delay_handle_timer | int __thiscall(void *, int, int, int) | Advance delayed casting. | Sends action 0x4E phrases and finally queues held 0x0F. |
Darkages.exe:0x00469474 | ui_map_handle_screen_menu | int __thiscall(void *, const uint8_t *) | Create an SScreenMenu pane. | Allocates and calls ui_screen_menu_pane_ctor. |
Darkages.exe:0x0046C004 | ui_map_handle_pursuit_menu | int __thiscall(void *, const uint8_t *) | Create an SPursuitMenu dialog variant. | Dispatches subtype at packet +1. |
Darkages.exe:0x0046C3F4 | ui_map_handle_show_paper | int __thiscall(void *, const uint8_t *) | Create server-show Paper dialog. | Uses constructor mode 1. |
Darkages.exe:0x0046C464 | ui_map_handle_show_users | int __thiscall(void *, const uint8_t *) | Populate and show Users Dialog Pane. | Requires the pane created by the CWho input path. |
Darkages.exe:0x0046CAB4 | ui_map_handle_bulletin | int __thiscall(void *, const uint8_t *) | Handle sessionless SBulletin. | Creates server-opened session when allowed. |
Darkages.exe:0x0047EFC0 | ui_question_message_dialog_ctor | void *__thiscall(void *, const uint8_t *) | Construct QuestionMessageDialog. | SPursuitMenu subtype 2. |
Darkages.exe:0x0047F9C0 | ui_question_message_dialog_send_answer | established __thiscall method | Send selected question answer. | Serializes 12 logical bytes beginning with CMessage 0x3A. |
Darkages.exe:0x0047FC80 | ui_question_message_face_dialog_ctor | void *__thiscall(void *, const uint8_t *) | Construct faced question dialog. | SPursuitMenu subtype 6. |
Darkages.exe:0x0048F600 | ui_option_pane_ctor | void *__thiscall(void *) | Build and register OptionPane. | Installs vtable 0x00520660. |
Darkages.exe:0x004909E0 | ui_option_pane_handle_server_packet | int __thiscall(void *, void *) | Handle SSelfSaveOk. | Creates the local “Saved.” message dialog. |
Darkages.exe:0x00490A80 | ui_option_pane_handle_mouse_event | int __thiscall(void *, void *) | Handle OptionPane mouse state. | Delegates common behavior to Dialog. |
Darkages.exe:0x00490D70 | ui_option_pane_handle_timer | int __thiscall(void *, int, int, int) | Advance OptionPane timer state. | Handles callback 100 separately. |
Darkages.exe:0x00490FE0 | ui_option_pane_handle_key_event | int __thiscall(void *, void *) | Handle OptionPane keyboard shortcuts. | Delegates unclaimed keys to Dialog. |
Darkages.exe:0x00491AA4 | net_c_send_user_setting | void __thiscall(void *, uint8_t) | Send [0x1B, setting_code]. | Called by OptionPane-family controls. |
Darkages.exe:0x004921F0 | ui_exit_wait_handle_server_packet | int __thiscall(void *, void *) | Complete orderly exit on action 0x4C, subtype 1. | Sends CQuit subtype 0 and changes the pane text. |
Darkages.exe:0x00492310 | ui_exit_wait_pane_ctor | established __thiscall constructor | Create exit-wait pane. | Sends CQuit subtype 1. |
Darkages.exe:0x00492C10 | ui_pane_is_visible | int __thiscall(void *) | Query pane visibility. | Equipment same-pane toggle uses it. |
Darkages.exe:0x00492C40 | ui_pane_show | void __thiscall(void *) | Set visible state and invalidate. | Common vslot +0x14. |
Darkages.exe:0x00492D50 | ui_pane_hide | void __thiscall(void *) | Clear visible state and invalidate. | Common vslot +0x18. |
Darkages.exe:0x004939C0 | ui_paper_dialog_ctor | void *__thiscall(void *, const uint8_t *, int) | Construct Paper in either packet mode. | Reaches ui_paper_dialog_build_content. |
Darkages.exe:0x00493B60 | ui_paper_dialog_handle_completion | int __thiscall(void *, int, int) | Submit current Paper content when allowed, then close. | Queues CExitEditingMode through the common send queue. |
Darkages.exe:0x00494100 | net_c_send_exit_editing_mode | void __thiscall(void *) | Submit current Paper content. | Uses the same action 0x23 payload without closing the dialog. |
Darkages.exe:0x004A9BC0 | ui_spell_book_dialog_ctor | established __thiscall constructor | Construct local spell details. | Called on right-button up over a spell slot. |
Darkages.exe:0x004AB1D0 | ui_skill_book_dialog_ctor | established __thiscall constructor | Construct local skill details. | Called on right-button down over a skill slot. |
Startup and Shutdown
The client uses a conventional Win32 process entry and a blocking GetMessageA loop, but its recurring game and timer work does not run in that loop. WinMain owns process checks, the main window, subsystem construction, and orderly teardown. A separate internal event worker supplies periodic callbacks. See Internal Event Routing for that worker.
High-level lifecycle
Process entry and window class
Windows enters the executable through the Microsoft Visual C++ runtime stub. The stub initializes the heap, multithreading runtime, standard I/O, command-line arguments, environment, and C initializers. It derives the command tail and the initial show command from STARTUPINFOA, obtains the module handle, and calls app_win_main. It always passes a null previous_instance, so the window-class registration path is taken in normal execution.
The client registers the ANSI window class DAClass with these established properties:
| Property | Value |
|---|---|
| Window procedure | win_main_window_proc |
| Class style | 0 |
| Extra class and window bytes | 0 |
| Icon | Resource identifier 126 |
| Cursor | IDC_ARROW |
| Background brush | BLACK_BRUSH |
| Menu | None |
If the first RegisterClassA call fails, the code calls RegisterClassA a second time while forming a diagnostic assertion, sets app_error_code to 1, and returns 0 from app_win_main. The second call does not recover startup.
Preflight checks and single-instance enforcement
app_win_main performs the following checks in order before it creates the window:
- It examines three values under
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices: the unnamed default value," .EXE"with a leading space, and"UMGR32.EXE". The key is requested withKEY_ALL_ACCESS. For the first value whose returned data begins with a nonzero byte, the client displays a message box with type0x44. SelectingIDNOdeletes that value and stops startup. Any other response permits startup. Failure to open the key permits startup. - It checks a process-name list and two files. A match displays a yes-or-no prompt. Selecting
IDNOreturns 0 and stops startup; the other response continues the scan. - It calls
FindWindowExA(NULL, NULL, "#32770", "Brothers Speeder"). Finding that top-level dialog stops startup without creating the client window. - It creates the initially owned mutex
Nexon.SingleInstance.ERROR_ALREADY_EXISTScauses an immediate_exit(0). A mutex creation failure with another error is not treated as fatal, so this check fails open.
The process scan dynamically resolves CreateToolhelp32Snapshot, Process32First, and Process32Next from Kernel32.DLL. It runs on Windows 9x, or on the NT platform when the major version is at least 5. Executable paths are compared case-insensitively by suffix against this exact list:
| Entry | Compared process suffix |
|---|---|
| 0 | Patch.exe |
| 1 | boserve.exe |
| 2 | winhwak.exe |
| 3 | grcframe.exe |
The implementation passes a local PROCESSENTRY32A to Process32First without first assigning its required dwSize field. It also does not close the process snapshot handle. Failure to load the library, resolve an entry point, create the snapshot, or begin enumeration skips to the file checks and therefore permits startup unless a later check rejects it.
The file checks use fopen(..., "rb") on <system-directory>\grcframe.exe and <windows-directory>\KeyHook.dll. Each existing file produces its own yes-or-no prompt.
These checks are documented as observed control flow. The runtime strings used for several prompts do not establish a broader intent for the checks.
Window creation
After the checks pass, app_win_main creates a topmost Dark Ages window of class DAClass. The extended style is WS_EX_TOPMOST. The style value 0x90880000 combines WS_POPUP, WS_VISIBLE, WS_BORDER, and WS_SYSMENU. Both coordinates are CW_USEDEFAULT, and the requested outer size is 642 by 482 pixels. The client later creates a 640 by 480 screen pane for presentation.
The return from CreateWindowExA is stored in app_main_window but is not validated. The code still calls SetForegroundWindow, ShowWindow, UpdateWindow, and app_initialize if the handle is null.
Subsystem initialization
app_initialize runs only after the window creation attempt. Its established setup order is:
- Fill a 1024-byte global buffer with ASCII spaces.
- Read physical-memory status and request a process working-set range from half of physical memory to all physical memory. Return values are ignored.
- Remove the IME context from the main window with
ImmAssociateContext(window, NULL). - Set the current directory from the executable path parsed out of the raw command line.
- Set the process priority class to
HIGH_PRIORITY_CLASS, create the client memory manager, and seedrandwithtime(NULL). - Open
Legend.dat,seo.dat, andkhan.dat. All three must open for that archive set to be accepted. Otherwise the client tries the single fallback archiveDarkAges.dat. - Create the rendering and media singletons, initialize rendering support, and load
Skill.tbl. - Construct the remaining service objects, the sound manager, and configuration. The sound path opened here is
.\Music\1.mp3. - If
English.nfoexists, construct the configuration fromDarkages.cfg; otherwise useLegend.cfg. - Create the deferred-deletion queue, the 640 by 480 8-bit screen pane using
Legend.pal, the internal event dispatcher, and the separateevent_manager_instance. Both workers are resumed during this phase. - Register the screen pane with the dispatcher, establish the 640 by 480 screen rectangle, load
Darkages.prforLegend.prfaccording to configuration byte+0x8D, and create the first terminal pane.
Most constructors communicate failure through the process-wide app_error_code. app_initialize contains staged cleanup branches for partial construction. When it returns, app_win_main checks the error code again and returns 0 instead of entering the message loop if it is nonzero.
The missing-archive path is different. It deletes the two early rendering or media singletons, shows two error boxes, posts WM_QUIT, and calls _exit(0). The first box labels GetCommandLineA output as the current directory, even though it did not call GetCurrentDirectoryA. The direct _exit bypasses app_shutdown.
Command-line directory parsing
app_set_working_directory_from_command_line does not use the command_line argument supplied to app_win_main. It calls GetCommandLineA again and applies this parser:
- If any double quote exists, copy from the second command-line byte through the byte before the last quote.
- Otherwise, copy up to the first space.
- If there is no space, copy the complete command line.
- Find the last backslash in the copied path, terminate the string there, and pass the directory to
SetCurrentDirectoryA.
The helper ignores the SetCurrentDirectoryA result. Its local path buffer is fixed at approximately 256 bytes and the copies have no length check. Because it uses the last quote anywhere in the raw command line and always starts the quoted case at byte 1, quoted arguments after the executable can confuse the result.
Message pump and activation
Immediately before the first GetMessageA, app_win_main marks the window active and calls ui_screen_pane_activate if the screen pane exists. The message loop is blocking and unfiltered. It does not render or advance timers directly:
for (;;) {
int get_message_result;
get_message_result = GetMessageA(&message, NULL, 0, 0);
if (get_message_result == 0) {
break;
}
if (message.message == WM_QUIT || app_shutdown_requested == 1) {
app_shutdown_requested = 1;
break;
}
TranslateMessage(&message);
DispatchMessageA(&message);
}
The explicit WM_QUIT comparison is normally redundant because GetMessageA returns zero for WM_QUIT. The code tests only for zero, so a GetMessageA error return of -1 is treated as a message and passed to TranslateMessage and DispatchMessageA. The shutdown flag is examined only after GetMessageA returns, so changing the flag alone does not wake a blocked loop.
win_main_window_proc connects the pump to client behavior. Relevant lifecycle cases include:
WM_DESTROYcallsPostQuitMessage(0)and returns 0.- An inactive
WM_ACTIVATEAPPpath minimizes the window, beeps, and clearsapp_window_active. - The active
WM_ACTIVATEAPPpath repeats theBrothers Speederwindow check. A match sets the shutdown flag and posts quit or close messages. Otherwise it restores the window, sets the active flag, and callsui_screen_pane_activate. WM_USER + 2routes asynchronous Winsock notifications, as described in Networking.- Keyboard, mouse, palette, and client-specific messages are dispatched in the same procedure. The complete message table and both application-defined messages are documented in Input and Windows Events. Unknown messages go to
DefWindowProcA.
Periodic timer processing is independent of this pump. The event worker waits with a 1 millisecond timeout and invokes the dispatcher tick. See Worker and timer lifecycle.
Normal and abnormal shutdown
Normal shutdown begins when GetMessageA returns zero or the loop observes app_shutdown_requested. app_win_main waits for a fixed 200 milliseconds, calls app_shutdown, closes and clears app_single_instance_mutex, and returns message.wParam. The CRT entry then passes that result to _exit.
For a normal WM_QUIT, message.wParam is the quit code. If the shutdown flag stops the loop while a different message is current, the function returns that message’s wParam instead.
app_shutdown is an ordered, null-safe deletion sweep. It closes the archive managers first, then destroys the constructed service singletons and global objects. Confirmed objects in the sweep include media and audio support, screen and UI state, configuration and preferences, the event dispatcher, the deferred-deletion queue, network-owned state, and the memory manager. Each owned global is set to null after deletion.
Deleting the event dispatcher releases its pane and timer containers, calls timeEndPeriod, and then invokes the worker base destructor. That base destructor uses TerminateThread, closes the worker and wait handles, and destroys queued synchronization state. It does not request a cooperative worker exit or join the thread. The full timing teardown is documented in Internal Event Routing.
Several exits do not run this sweep:
| Exit path | Behavior |
|---|---|
| Window-class or preflight failure | Return 0 from app_win_main; no subsystem graph was constructed. |
Existing Nexon.SingleInstance mutex | Call _exit(0) immediately. |
| Missing required data archives | Perform limited early cleanup, display errors, post WM_QUIT, then call _exit(0). |
| Nonzero initialization error | Use staged cleanup inside app_initialize, then return 0 from app_win_main without calling the full app_shutdown. |
| CRT exception path | Filter the exception and call the CRT exit path with the exception code. |
Code-level flow
CRT handoff
Darkages.exe:0x004C62A5app_crt_entryinstalls the runtime exception frame and initializes the heap, thread runtime, standard I/O, arguments, environment, and C initializers. Heap initialization failure exits with code0x1C; multithreading initialization failure exits with code0x10.- It obtains the command tail from the CRT helper, derives
show_commandfromSTARTUPINFOAwhenSTARTF_USESHOWWINDOWis present or usesSW_SHOWDEFAULT, and callsapp_win_main(GetModuleHandleA(NULL), NULL, command_tail, show_command). - A normal return calls
_exit(win_main_result). The exception branch calls__XcptFilterand then the CRT exception exit routine.
app_win_main
- Clear
app_main_window. - When
previous_instance == NULL, assemble and registerDAClass. A registration failure setsapp_error_code = 1and returns 0. - Save
instanceinapp_instance. - Call
app_check_runservices_entries. Return 0 fromapp_win_mainif it returns 1. - Call
app_check_conflicting_software. Return 0 if it returns 0. - Call
win_top_level_window_exists("#32770", "Brothers Speeder"). Return 0 on a match. - Create
Nexon.SingleInstance. Call_exit(0)whenGetLastError()isERROR_ALREADY_EXISTS. - Create and show the main window, set
app_window_active = 1, and callapp_initialize. - If
app_error_code != 0, emit the diagnostic and return 0. - Activate the screen pane and enter the
GetMessageAloop. - On loop exit, wait 200 milliseconds, call
app_shutdown, close the mutex, and return the currentMSG.wParam.
The command_line parameter is not read by this function. Directory setup re-reads the complete process command line.
Initialization and failure ownership
Darkages.exe:0x0045CCA0 app_initialize has no meaningful return value. Construction success is represented by nonnull globals and app_error_code == 0. Each major allocation is followed by an error check. Later failure labels delete the objects that were already created, proceeding backward through the partial dependency graph.
The successful path creates event_dispatcher with event_dispatcher_ctor, calls util_thread_queue_start to resume its suspended worker, then constructs event_manager_instance with event_manager_ctor and starts that worker. It registers ui_screen_pane with the dispatcher only after both exist.
The archive failure branch at Darkages.exe:0x0045CE6C does not join the common staged cleanup. It destroys the two early graphics or media objects, displays the archive errors, posts a quit message, and reaches _exit(0) at Darkages.exe:0x0045CF31.
Window messages and loop termination
Darkages.exe:0x0045BDC0 win_main_window_proc is the only window procedure installed for DAClass. DispatchMessageA reaches it for queued messages, while some client paths also post messages directly to app_main_window. Its WM_DESTROY case supplies the normal WM_QUIT. Other fatal paths set app_shutdown_requested and post a message so the blocking loop can observe the flag.
Darkages.exe:0x0049D790 ui_screen_pane_activate sets a screen-pane active field at +0x568, resets a presentation counter to 10, restores the pane’s render object through a virtual call, and invokes two screen update helpers. It is called once before the first GetMessageA and again after an active WM_ACTIVATEAPP transition.
Teardown
Darkages.exe:0x0045B8F0 app_shutdown checks each singleton or global before calling its deleting destructor. It then clears the corresponding global. The event dispatcher is deleted after the main screen, configuration, preference, and several UI service objects, but before the deferred-deletion queue and final media and memory singletons. No return code is propagated from teardown.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x0041D390 | app_check_conflicting_software | int __cdecl(void) | Prompt for four process suffixes and two files. | Called by app_win_main; 0 stops startup. Toolhelp failures are fail-open. The local PROCESSENTRY32A.dwSize is not initialized and the snapshot is not closed. |
Darkages.exe:0x0041D690 | win_top_level_window_exists | BOOL __cdecl(LPCSTR class_name, LPCSTR window_title) | Test for a matching top-level window. | Thin FindWindowExA(NULL, NULL, ...) wrapper. Used during initial startup and active-window restoration. |
Darkages.exe:0x0041D6C0 | app_check_runservices_entries | int __cdecl(void) | Examine and optionally delete three RunServices values. | Called first by app_win_main; 1 means a value was deleted after IDNO and startup must stop. |
Darkages.exe:0x0045B5F0 | app_win_main | int __stdcall(HINSTANCE instance, HINSTANCE previous_instance, LPSTR command_line, int show_command) | Own the Win32 lifecycle. | Called only by app_crt_entry; calls the checks, creates the mutex and window, initializes subsystems, pumps messages, and shuts down. |
Darkages.exe:0x0045B8F0 | app_shutdown | void __cdecl(void) | Delete the initialized subsystem graph in fixed order. | Called only on the normal message-loop exit path. Null-checks and clears owned globals. |
Darkages.exe:0x0045BDC0 | win_main_window_proc | LRESULT __stdcall(HWND window, UINT message, WPARAM wparam, LPARAM lparam) | Dispatch all DAClass window messages. | Registered by app_win_main; WM_DESTROY posts quit and WM_ACTIVATEAPP coordinates active state and screen restoration. See Input and Windows Events. |
Darkages.exe:0x0045C840 | app_set_working_directory_from_command_line | void __cdecl(void) | Set the current directory to the parsed executable directory. | Called near the start of app_initialize; re-reads the raw command line and ignores API failure. |
Darkages.exe:0x0045CCA0 | app_initialize | void __cdecl(void) | Construct resources, rendering, audio, configuration, event workers, screen state, and the initial pane. | Called after window creation. Uses app_error_code and staged cleanup; the missing-archive branch calls _exit(0). |
Darkages.exe:0x0049D790 | ui_screen_pane_activate | void __thiscall(void *screen_pane) | Restore active screen and presentation state. | Called before the message loop and by the active WM_ACTIVATEAPP path. |
Darkages.exe:0x004C62A5 | app_crt_entry | void __cdecl(void) | Initialize the Microsoft C runtime and hand off to app_win_main. | PE process entry; passes the result to _exit and contains the CRT exception path. |
Rendering
The rendering subsystem covers graphics initialization, drawing primitives, surface ownership, sprite composition, palette and color handling, text, frame assembly, and final presentation.
High-level operation
This section will explain how a frame moves from game and user interface state to the displayed image. It will be divided into graphics initialization, resource and surface management, scene composition, user interface drawing, presentation, lost-surface recovery, and shutdown.
Rendering behavior has not been mapped yet.
Code-level flow
This section will trace the graphics entry points and their call trees. It will record DirectDraw interfaces, object ownership, function arguments, coordinate systems, clipping decisions, layer order, palette behavior, frame timing, presentation calls, and error recovery.
No rendering call tree is documented yet.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
| No functions documented yet |
Audio
The audio subsystem covers sound initialization, music, sound effects, resource loading, playback, volume control, and shutdown.
High-level operation
This section will explain how audio resources enter the client, how music and sound effects are selected, what owns active playback, and which game or user interface events control sound.
Audio behavior has not been mapped yet.
Code-level flow
This section will trace audio initialization and playback call trees. It will record the Windows or DirectX interfaces in use, function arguments, resource lookup, caching, channel or buffer selection, volume calculations, stop behavior, failure handling, and cleanup.
No audio call tree is documented yet.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
| No functions documented yet |
Input and Windows Events
The DAClass window procedure is the boundary between the Win32 message queue and the client’s asynchronous event system. It handles a fixed set of 15 message values. Keyboard and mouse messages update immediate window or screen state, then enter the EventMan worker queue. Activation, palette, socket, virus-check, and destruction messages remain on the window thread.
High-level operation
Message-pump boundary
app_win_main blocks in GetMessageA, calls TranslateMessage, and sends each retrieved message through DispatchMessageA. The registered DAClass procedure is win_main_window_proc. This is an event-driven pump, not the rendering or game-tick loop. Timer work runs on the event-dispatcher worker described in Internal Event Routing.
The window procedure returns 0 for every message it recognizes. All other messages go to DefWindowProcA. This includes WM_CLOSE, so the default procedure destroys the window and the resulting WM_DESTROY case posts WM_QUIT. It also includes WM_CHAR, WM_PAINT, WM_SETFOCUS, WM_KILLFOCUS, WM_TIMER, and WM_QUERYNEWPALETTE. TranslateMessage can therefore produce a WM_CHAR, but the client has no explicit text-message branch here.
Complete handled-message table
All handler locations below are inside Darkages.exe:0x0045BDC0 win_main_window_proc.
| Value | Win32 name | Handler address | Payload used | Client action |
|---|---|---|---|---|
0x0002 | WM_DESTROY | Darkages.exe:0x0045BE77 | None | Call PostQuitMessage(0). |
0x001C | WM_ACTIVATEAPP | Darkages.exe:0x0045C2F8 | wParam is the process activation state. | Minimize and deactivate, or check for conflicting software and restore presentation state. |
0x0100 | WM_KEYDOWN | Darkages.exe:0x0045C21F | Scan and extended-key fields from lParam. | Queue physical-key work code 0x0A. |
0x0101 | WM_KEYUP | Darkages.exe:0x0045C17A | Scan and extended-key fields from lParam. | Queue key-release work code 0x0B. |
0x0104 | WM_SYSKEYDOWN | Darkages.exe:0x0045C295 | wParam is checked only for Alt+F4; the queued key comes from lParam. | Apply the conditional Alt+F4 remap, then use the key-down path. |
0x0105 | WM_SYSKEYUP | Darkages.exe:0x0045C1F0 | Same interpretation as WM_SYSKEYDOWN. | Apply the conditional Alt+F4 remap, then use the key-up path. |
0x0200 | WM_MOUSEMOVE | Darkages.exe:0x0045C112 | Low and high words of lParam. | Update the software cursor and queue work code 4. |
0x0201 | WM_LBUTTONDOWN | Darkages.exe:0x0045C0C4 | Coordinates from lParam; wParam modifier bits are ignored. | Update the cursor, set left-button state, and queue work code 5. |
0x0202 | WM_LBUTTONUP | Darkages.exe:0x0045C07D | Coordinates from lParam; wParam modifier bits are ignored. | Update the cursor, clear left-button state, and queue work code 6. |
0x0204 | WM_RBUTTONDOWN | Darkages.exe:0x0045C02A | Coordinates from lParam; wParam modifier bits are ignored. | Update the cursor, set right-button state, and queue work code 7. |
0x0205 | WM_RBUTTONUP | Darkages.exe:0x0045BF9E | Coordinates from lParam; wParam modifier bits are ignored. | Update the cursor, clear right-button state, and queue work code 8. |
0x020A | WM_MOUSEWHEEL | Darkages.exe:0x0045BFE8 | Signed high word of wParam is the wheel delta. | Update the cursor and queue work code 9. |
0x0311 | WM_PALETTECHANGED | Darkages.exe:0x0045C62E | wParam is the window that changed the palette. | Reload and apply legend.pal when another window changed the palette. |
0x0402 | WM_USER + 2 | Darkages.exe:0x0045C2C4 | Low word of lParam is a Winsock network event. | Queue socket receive work for FD_READ, or disconnect for FD_CLOSE. |
0x2446 | WM_USER + 0x2046 | Darkages.exe:0x0045C412 | Nonzero wParam points to a virus-check module-name string; zero selects the scan-failure text. lParam is ignored. | Route a virus-check warning to an active UI receiver or a modal LOD message box. |
The last two rows are the only application-defined messages recognized by this window procedure.
Mouse input and message time
Every handled mouse message first performs the same software-cursor update under the critical section at screen-pane offset +0x534. The procedure restores the old cursor area with sub_49D850, calls ui_screen_pane_set_cursor_position, then draws or presents the new cursor area through the entry at sub_49D120. This occurs before EventMan input blocking is checked.
Coordinate handling has several exact quirks:
- The low word of
lParamis zero-extended. Values at least 640 become 639. The apparent negative clamp cannot be reached after zero extension. - The high word is obtained with a logical shift and is neither sign-extended nor clamped.
WM_MOUSEWHEELgoes through the same coordinate path. Windows supplies wheel coordinates in screen space, but this procedure does not callScreenToClientbefore treating them like the other mouse coordinates.
Each message then calls GetMessageTime and places that 32-bit timestamp in the EventMan work record. Mouse movement allocates and transfers ownership of an eight-byte pair stored internally as (y, x), matching the screen-pane cursor calls. Button messages use the most recently stored coordinates. The worker divides a signed wheel delta by 120 before dispatching its mouse Event.
EventMan field +0x4B0 bit 0 blocks movement, wheel, button-down, and key-down queueing. Releases are deliberately different: left-button up, right-button up, and key up still queue while input is blocked. Button state is held at +0x4B4, with bit 0 for left and bit 1 for right.
The worker recognizes double-clicks itself rather than relying on WM_LBUTTONDBLCLK or WM_RBUTTONDBLCLK, neither of which is handled. It compares the new down timestamp with its stored timestamp and requires total horizontal plus vertical movement of at most two pixels. It dispatches mouse subtypes 1 and 2 for left single and double down, 3 for left up, 4 and 5 for right single and double down, 6 for right up, and 7 for wheel. Mouse movement uses subtype 0.
Keyboard input
The client queues physical scan codes rather than the wParam virtual-key value. Bits 16 through 23 of lParam supply the scan byte. If KF_EXTENDED bit 24 is set, the procedure adds 0x80 and keeps the low byte. A resulting zero is ignored. Repeat count and previous-key-state fields do not suppress repeated key-down messages.
uint8_t input_scan_code(uint32_t key_data)
{
uint8_t scan_code = (uint8_t)(key_data >> 16);
if ((key_data & 0x01000000U) != 0) {
scan_code = (uint8_t)(scan_code + 0x80U);
}
return scan_code;
}
WM_SYSKEYDOWN and WM_SYSKEYUP contain one special path. If wParam is VK_F4, the Alt-context bit 29 is set, and Darkages.exe:0x0043DE20 returns a nonnull UI object, the code rewrites the scan byte to 0x10, clears the extended and Alt-context bits, and preserves selected high transition bits. The down and up messages then continue through their ordinary paths. The code establishes the remap but does not establish its original design intent.
On the EventMan worker, event_dispatch_key_down marks the scan code pressed, applies the client’s scan-code mapping and modifier tables, and dispatches key subtype 8 when the mapped value is nonzero. event_update_key_up clears pressed and modifier state but does not dispatch a key-up Event to panes.
Activation, palette, and shutdown messages
The application-level activation message is WM_ACTIVATEAPP, not WM_ACTIVATE. A zero wParam invokes a render-object virtual method, minimizes the window with SW_MINIMIZE, emits a 2000 Hz beep for 100 ms, and clears app_window_active. A nonzero wParam repeats the Brothers Speeder top-level-window check. A match sets app_shutdown_requested, posts quit, and posts WM_CLOSE. Otherwise the client restores a 640 by 480, 8-bit presentation configuration, uses SW_RESTORE, sets app_window_active, and calls ui_screen_pane_activate.
WM_PALETTECHANGED is ignored when the sender in wParam is the main window or when no screen pane exists. Otherwise render_screen_pane_load_palette reads 256 RGB entries from legend.pal, preserves the system-reserved palette entries at both ends, applies the palette, and marks the screen palette dirty. The procedure then calls the small screen-pane apply entry at Darkages.exe:0x0049D0E0.
WM_DESTROY only posts WM_QUIT. It does not perform subsystem cleanup itself. The message loop returns to app_win_main, which calls app_shutdown as described in Startup and Shutdown.
Application-defined messages
WM_USER + 2 is registered by net_connect_configured_server through WSAAsyncSelect with FD_READ | FD_CLOSE. win_main_window_proc uses only LOWORD(lParam): FD_READ calls net_queue_receive, and FD_CLOSE calls net_handle_disconnect. Other network-event values are ignored. The procedure also ignores the socket in wParam and the WSAGETSELECTERROR value in the high word of lParam. The receive and framing path is documented in Networking.
WM_USER + 0x2046 belongs to the process/module virus-check worker. app_virus_check_module_callback copies the first reported module name into the 256-byte app_virus_report_module buffer and posts the message with that pointer in wParam. The window procedure formats it as a quoted name. A zero wParam instead inserts app_virus_check_failure_detail into a Korean format string whose meaning is, “Could not perform the virus scan. ( %s ) Would you like to continue?”
The report is sent to the first available target in this order: the object returned by sub_4AD130, dword_4F51AC, then receiver. The procedure allocates a 0x554-byte report object and initializes it with sub_498CE0. If no target exists, it calls MessageBoxA with caption LOD and type MB_OK. The subsequent IDCANCEL shutdown test is not reachable through an ordinary MB_OK message box, although the code contains it.
Code-level flow
Dispatch and default processing
app_win_main
-> GetMessageA
-> TranslateMessage
-> DispatchMessageA
-> win_main_window_proc
-> explicit handler for one of 15 message values
-> DefWindowProcA for every other value
Darkages.exe:0x0045BDC0 win_main_window_proc is a direct comparison chain rather than a jump table. Its four parameters have the normal WNDPROC contract. Every explicit branch returns zero.
Mouse queue and worker path
win_main_window_proc
-> sub_49D850
-> ui_screen_pane_set_cursor_position
-> sub_49D120
-> event_manager_get_instance
-> GetMessageTime
-> event_queue_mouse_* work code 4 through 9
-> util_thread_queue posting path
-> event_process_work_item
-> event_dispatch_mouse_* Event subtype 0 through 7
-> event dispatcher pane routing
| Windows input | Queue function | Work code | Worker handler | Result |
|---|---|---|---|---|
| Move | event_queue_mouse_move | 4 | event_dispatch_mouse_move | Mouse subtype 0 with copied coordinates. |
| Left down | event_queue_left_button_down | 5 | event_dispatch_left_button_down | Mouse subtype 1 or double-click subtype 2. |
| Left up | event_queue_left_button_up | 6 | event_dispatch_left_button_up | Mouse subtype 3. |
| Right down | event_queue_right_button_down | 7 | event_dispatch_right_button_down | Mouse subtype 4 or double-click subtype 5. |
| Right up | event_queue_right_button_up | 8 | event_dispatch_right_button_up | Mouse subtype 6. |
| Wheel | event_queue_mouse_wheel | 9 | event_dispatch_mouse_wheel | Mouse subtype 7 with wheel_delta / 120. |
Keyboard queue and worker path
WM_KEYDOWN or WM_SYSKEYDOWN
-> scan-code normalization
-> event_queue_key_down work code 0x0A
-> event_process_work_item
-> event_dispatch_key_down key subtype 8 when mapping succeeds
WM_KEYUP or WM_SYSKEYUP
-> scan-code normalization
-> event_queue_key_up work code 0x0B
-> event_process_work_item
-> event_update_key_up state update, no pane Event
event_dispatch_key_down stores pressed state in the per-scan-code table beginning at EventMan offset +0x390. It combines the active modifier byte at +0x490 with mapping tables at +0x90, +0x190, and +0x290. One internal mapped value, 0x88 with state bit 0 set, also sets app_shutdown_requested and posts WM_CLOSE before the key Event is dispatched.
Socket and virus-check messages
net_connect_configured_server
-> WSAAsyncSelect(..., WM_USER + 2, FD_READ | FD_CLOSE)
-> win_main_window_proc
-> net_queue_receive
-> net_handle_disconnect
app_virus_check_thread
-> module scanner callback
-> app_virus_check_module_callback
-> PostMessageA(..., WM_USER + 0x2046,
app_virus_report_module, 0)
-> win_main_window_proc
-> active UI report receiver
-> MessageBoxA fallback
The module callback posts only the first infected-module name because it first requires app_virus_report_module[0] == 0. It also clears the virus-check continuation flag after posting, which causes the worker’s loaded scanner callbacks to stop their traversal.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x00432BD0 | event_queue_mouse_move | void __thiscall(void *event_manager_object, int mouse_y, int mouse_x, unsigned int message_time) | Copy coordinates and queue mouse-move work. | Called only by win_main_window_proc; work code 4; blocked by +0x4B0 bit 0. |
Darkages.exe:0x00432C50 | event_queue_left_button_down | void __thiscall(void *event_manager_object, unsigned int message_time) | Set left-button state and queue button-down work. | Work code 5; blocked by +0x4B0 bit 0. |
Darkages.exe:0x00432C90 | event_queue_left_button_up | void __thiscall(void *event_manager_object, unsigned int message_time) | Clear left-button state and queue release work. | Work code 6; not suppressed by input blocking. |
Darkages.exe:0x00432CC0 | event_queue_right_button_down | void __thiscall(void *event_manager_object, unsigned int message_time) | Set right-button state and queue button-down work. | Work code 7; blocked by +0x4B0 bit 0. |
Darkages.exe:0x00432D00 | event_queue_right_button_up | void __thiscall(void *event_manager_object, unsigned int message_time) | Clear right-button state and queue release work. | Work code 8; not suppressed by input blocking. |
Darkages.exe:0x00432D30 | event_queue_mouse_wheel | void __thiscall(void *event_manager_object, int wheel_delta, unsigned int message_time) | Queue the signed wheel delta and message time. | Work code 9; blocked by +0x4B0 bit 0. |
Darkages.exe:0x00432D60 | event_queue_key_down | void __thiscall(void *event_manager_object, unsigned char scan_code, unsigned int message_time) | Queue physical-key-down work. | Work code 0x0A; blocked by +0x4B0 bit 0. |
Darkages.exe:0x00432D90 | event_queue_key_up | void __thiscall(void *event_manager_object, unsigned char scan_code, unsigned int message_time) | Queue physical-key-release work. | Work code 0x0B; not suppressed by input blocking. |
Darkages.exe:0x00433060 | event_manager_get_instance | void *__cdecl(void) | Return the EventMan singleton. | Asserts that event_manager_instance is nonnull; used for every queued input message. |
Darkages.exe:0x00433434 | event_dispatch_mouse_move | void __thiscall(void *event_manager_object, int mouse_y, int mouse_x, unsigned int message_time) | Dispatch mouse subtype 0. | Called by event_process_work_item for work code 4. |
Darkages.exe:0x00433504 | event_dispatch_left_button_down | void __thiscall(void *event_manager_object, unsigned int message_time) | Dispatch left single- or double-button-down Event. | Called for work code 5; subtypes 1 and 2. |
Darkages.exe:0x004336C4 | event_dispatch_left_button_up | void __thiscall(void *event_manager_object, unsigned int message_time) | Dispatch left-button release. | Called for work code 6; subtype 3. |
Darkages.exe:0x00433794 | event_dispatch_right_button_down | void __thiscall(void *event_manager_object, unsigned int message_time) | Dispatch right single- or double-button-down Event. | Called for work code 7; subtypes 4 and 5. |
Darkages.exe:0x00433944 | event_dispatch_right_button_up | void __thiscall(void *event_manager_object, unsigned int message_time) | Dispatch right-button release. | Called for work code 8; subtype 6. |
Darkages.exe:0x00433A14 | event_dispatch_mouse_wheel | void __thiscall(void *event_manager_object, int wheel_delta, unsigned int message_time) | Normalize and dispatch wheel movement. | Called for work code 9; divides delta by 120 and emits subtype 7. |
Darkages.exe:0x00433AB4 | event_dispatch_key_down | void __thiscall(void *event_manager_object, unsigned char scan_code, unsigned int message_time) | Update key state and dispatch a mapped key Event. | Called for work code 0x0A; can also request WM_CLOSE. |
Darkages.exe:0x00433C34 | event_update_key_up | void __thiscall(void *event_manager_object, unsigned char scan_code, unsigned int message_time) | Clear pressed and modifier state. | Called for work code 0x0B; does not dispatch a pane Event. |
Darkages.exe:0x0045BDC0 | win_main_window_proc | LRESULT __stdcall(HWND window, UINT message, WPARAM wparam, LPARAM lparam) | Route all explicit DAClass messages. | Registered by app_win_main; recognizes the 15 values in the table above and delegates all others. |
Darkages.exe:0x00498430 | app_virus_check_module_callback | int __cdecl(void *process_context, void *module_context, const char *module_name, FILE *log_stream) | Report the first infected module to the window thread. | Called through the loaded module scanner; posts WM_USER + 0x2046. |
Darkages.exe:0x00498780 | app_virus_check_thread | void __cdecl(char *log_filename) | Run virus checking on a worker thread. | Invokes loaded process and module enumerators, cleans up, then calls _endthread. |
Darkages.exe:0x0049DA30 | render_screen_pane_load_palette | void __thiscall(void *screen_pane, const char *palette_name) | Load and apply a 256-entry palette. | WM_PALETTECHANGED passes legend.pal; other UI transitions also call it. |
Darkages.exe:0x0049E820 | ui_screen_pane_set_cursor_position | void __thiscall(void *screen_pane, int mouse_y, int mouse_x) | Update the 32 by 32 software-cursor rectangle. | Called by the shared mouse-message path while holding the screen-pane critical section. |
UI Panes and Registries
The client represents screens, dialogs, controls, and many temporary overlays as Pane-derived objects. A pane participates in two independent hierarchies. The Screen composition tree records placement and parent-child drawing relationships. The event-dispatch tree records which panes receive mouse, keyboard, socket-packet, and timer callbacks. A pane may appear in both trees, but the trees have different nodes, payloads, and ownership.
The executable does not expose usable Microsoft C++ class RTTI for these pane types. Enumeration instead starts from the common Pane virtual layout. Every confirmed pane-compatible virtual table contains ui_pane_register_events at slot +0x30 and ui_pane_unregister_events at +0x34. Cross-references to the registration slot identify 173 compatible tables. These include controls and construction-state tables as well as full visible screens, so the count is not a count of 173 independently visible windows. The complete address and handler inventory is in Pane Virtual Table Inventory.
High-level operation
Two pane hierarchies
The Screen composition tree answers where a pane is placed and which panes are below it. ui_screen_registry is a static pointer to its heap registry. The root node holds ui_screen_pane, the persistent 640 by 480 surface pane. Adding a pane supplies a rectangle, an optional pane to place it in front of, and an optional parent pane. Removing a node also removes its child hierarchy.
The event-dispatch tree answers which panes are eligible for input and internal events. It is reached through event_dispatcher + 0x68. Mouse events use virtual slot +0x38, keyboard events use +0x3C, socket-packet events use +0x40, and due pane timers use +0x44. Event traversal visits a node’s children before the node itself. Mouse coordinates are adjusted into pane-relative coordinates during traversal.
The two registrations are deliberately separate. ui_pane_add_to_screen does not register event handling, and ui_pane_register_events does not add anything to the drawing tree. Constructors and transitions normally perform both operations explicitly.
Pane lifecycle
app_initialize first creates the Screen registry, constructs ui_screen_pane, and makes that pane the Screen root. It then creates and starts the event dispatcher and registers the screen pane as the initial event receiver.
The first substantial screen is TerminalPane. Its constructor publishes ui_terminal_pane, adds the pane below ui_screen_pane, registers it in the event hierarchy, and adds its controls. A successful terminal bootstrap packet causes ui_terminal_handle_server_packet to remove the terminal child, unregister and remove the terminal pane, create MainMenuPane, add and register the main menu, send the bootstrap/version traffic, and delete the terminal object. The terminal destructor clears its static pointer.
MainMenuPane publishes ui_main_menu_pane. Its server handler can create ServerSelectDialogPane as a registered child dialog. The dialog destructor explicitly unregisters the pane and removes it from Screen before destroying its dialog state.
The transition into the game reaches ui_main_menu_create_game_panes. It creates the persistent in-game BackgroundPane and MapPane, publishes ui_background_pane and ui_map_pane, and builds the larger child-pane graph below them. The map pane becomes the principal socket-packet receiver for in-game server actions.
Temporary controls follow the same rule. The game-buttons owner stores each allocated skill or spell pane in an indexed heap-pointer array, adds it below the owner in Screen, and registers it below the owner in the event tree. Removal deletes the node from both trees, deletes the object, and clears the pointer slot. TextEdit Escape and Enter paths also remove both registrations before queueing deferred deletion. Enter calls the class-specific virtual slot +0x4C before removal.
Visibility is separate from both hierarchies. Common Pane vslot +0x14 sets the visible flag at +0xB0 and invalidates the pane, while +0x18 clears the flag and invalidates the exposed parent region. Persistent content panes can therefore remain present in both registries while hidden. The exact packet and input transitions for equipment, status, skills, spells, users, paper, messages, and bulletin or mail are in UI, Input, and Packet Flows.
The base ui_pane_dtor only cancels timers and destroys base members. It does not unregister the object from either tree. Every owning path must therefore remove both registrations before immediate or deferred deletion. The observed order varies by owner, but both removals precede destruction.
Named persistent panes and handlers
The following panes have established construction, virtual-table, and handler evidence. M, K, S, and T mean mouse, keyboard, socket packet, and timer.
| Pane | Static root and object size | Vtable | M handler | K handler | S handler | T handler |
|---|---|---|---|---|---|---|
ScreenPane | ui_screen_pane at Darkages.exe:0x004F51C8, 0x580 bytes | ui_screen_pane_vtable at Darkages.exe:0x00524CE0 | ui_pane_default_mouse_handler | ui_screen_pane_handle_key_event | ui_pane_default_socket_handler | ui_screen_pane_handle_timer |
TerminalPane | ui_terminal_pane at Darkages.exe:0x004FD640, 0xF78 bytes | ui_terminal_pane_vtable at Darkages.exe:0x00529000 | ui_terminal_handle_mouse_event | ui_terminal_handle_key_event | ui_terminal_handle_server_packet | ui_pane_default_timer_handler |
MainMenuPane | ui_main_menu_pane at Darkages.exe:0x004F51AC, 0x124 bytes | ui_main_menu_pane_vtable at Darkages.exe:0x005177C0 | ui_main_menu_handle_mouse_event | ui_main_menu_handle_key_event | ui_main_menu_handle_server_packet | ui_pane_default_timer_handler |
BackgroundPane | ui_background_pane at Darkages.exe:0x004F51B4, 0x144 bytes | ui_background_pane_vtable at Darkages.exe:0x00500700 | ui_background_pane_handle_mouse_event | ui_pane_default_key_handler | ui_handle_server_request_portrait | ui_background_pane_handle_timer |
MapPane | ui_map_pane at Darkages.exe:0x004F51B0, 0xF38 bytes | ui_map_pane_vtable at Darkages.exe:0x00519280 | ui_map_handle_mouse_event | ui_map_handle_key_event | ui_map_dispatch_server_packet | ui_map_handle_timer |
ServerSelectDialogPane | Dynamic, 0x554 bytes | ui_server_select_dialog_pane_vtable at Darkages.exe:0x00526140 | ui_server_select_handle_mouse_event | ui_dialog_handle_key_event | ui_server_select_handle_server_packet | ui_dialog_default_timer_handler |
OptionPane | Dynamic, 0x578 bytes | ui_option_pane_vtable at Darkages.exe:0x00520660 | ui_option_pane_handle_mouse_event | ui_option_pane_handle_key_event | ui_option_pane_handle_server_packet | ui_option_pane_handle_timer |
| Exit-wait pane | Dynamic | ui_exit_wait_pane_vtable at Darkages.exe:0x005207E0 | ui_dialog_handle_mouse_event | ui_dialog_handle_key_event | ui_exit_wait_handle_server_packet | ui_dialog_default_timer_handler |
Friendly class names and concrete event coverage
The executable has no usable Microsoft RTTI for this hierarchy, but it retains many class and method diagnostics. A friendly class name is treated as confirmed only when the same constructor, destructor, or virtual method also installs or is stored in the candidate vtable. This establishes names such as BulletinDialogPane, ArticleListDialog, MailListPane, LegendDialogPane, MonsterPane2, EffectObjectPane, OptionPane, and WeatherPane without relying on nearby text alone. The complete confirmed name-to-vtable map is in Pane Virtual Table Inventory.
A vtable identifies the broad Event families a pane can receive. The handler body supplies the narrower filter. For example, ServerSelectDialogPane has mouse, keyboard, socket, and timer slots, but its mouse override only adds behavior for a left double-click and its socket override only claims server action 0x56. The inherited Dialog handlers still process their ordinary mouse and keyboard cases. This distinction is important for runtime hooks because a non-default slot does not imply that every subtype is consumed.
The client dispatches key-down Events only. Key-up updates EventMan state but does not enter the pane hierarchy. Timer callbacks are also separate from the numbered Event types: the timer worker calls pane slot +0x44 with a class-local callback identifier and two payload values.
The socket handler is not a Winsock callback. Socket bytes are decoded and converted to internal Event type 9 before the dispatcher calls pane slot +0x40. The packet path is documented in Networking, while Win32 WM_* handling remains in Input and Windows Events.
Runtime observation roots
The documented virtual addresses use image base 0x00400000. A runtime tool that resolves a loaded module should use loaded_module_base + RVA rather than assuming the preferred base.
| VA | RVA | Current IDA name | Runtime meaning | Lifetime and nullability |
|---|---|---|---|---|
Darkages.exe:0x004F51AC | 0x000F51AC | ui_main_menu_pane | Pointer to the heap MainMenuPane. | Non-null during the main-menu phase; cleared during transitions and teardown. |
Darkages.exe:0x004E2D70 | 0x000E2D70 | ui_bulletin_session | Pointer to the active heap BulletinSession. | Non-null only while a bulletin or mail session owns its child-dialog history. |
Darkages.exe:0x004E32A4 | 0x000E32A4 | ui_equip_pane | Pointer to the persistent heap User Equip pane. | Published during game-pane construction and cleared by its destructor. |
Darkages.exe:0x004E3564 | 0x000E3564 | ui_users_dialog_pane | Pointer to the heap Users Dialog Pane. | Null until the first CWho UI action creates it; reused for later SShowUsers replies. |
Darkages.exe:0x004F51B0 | 0x000F51B0 | ui_map_pane | Pointer to the heap MapPane. | Published after game-pane creation; cleared during game teardown. |
Darkages.exe:0x004F51B4 | 0x000F51B4 | ui_background_pane | Pointer to the heap BackgroundPane. | Published with the game UI; cleared during game teardown. |
Darkages.exe:0x004F51C8 | 0x000F51C8 | ui_screen_pane | Pointer to the persistent heap ScreenPane. | Non-null after successful initialization until application shutdown. |
Darkages.exe:0x004F51CC | 0x000F51CC | ui_screen_registry | Pointer to the heap Screen hierarchy list. | Non-null after Screen initialization until application shutdown. |
Darkages.exe:0x004F51D0 | 0x000F51D0 | event_dispatcher | Pointer to the dispatcher; +0x68 points to its pane hierarchy. | Non-null after event initialization until dispatcher shutdown. |
Darkages.exe:0x004FD640 | 0x000FD640 | ui_terminal_pane | Pointer to the heap TerminalPane. | Published by the constructor and cleared by the destructor. |
These roots are useful object discriminators as well as pointer chains. A live object begins with its current virtual-table pointer. Comparing that pointer against the vtable inventory identifies compatible panes without relying on absent RTTI.
The Runtime UI Memory Map turns these roots into concrete Screen and Event tree-walking recipes and records confirmed fields for GameButtonsPane, equipment and self-look state, skill and spell slots, BulletinSession, and Users Dialog Pane.
External readers can race Screen changes, event registration, and deferred deletion. The Screen add and remove paths do not acquire a visible lock around the packed list. A practical snapshot should read the list pointer, count, array pointer, and stride, copy the nodes, then re-read those four values and discard the snapshot if they changed. Each pane pointer should also be checked for readability and a known vtable before reading fields. Writes are more hazardous because pane fields, both registries, timers, and ownership must remain consistent.
Code-level flow
Common Pane layout and virtual slots
ui_pane_ctor constructs the common Pane base and installs ui_pane_vtable. The following fields and slots are directly established.
| Pane offset | Width | Established meaning |
|---|---|---|
+0x0000 | 4 | Current virtual-table pointer. |
+0x0008 | 4 | Last propagated Screen or dispatcher error. |
+0x0038 | 16 | Local graphic bound rectangle in client order: top, left, bottom, right. |
+0x00A8 | 8 | Relative origin point: signed top at +0xA8 and left at +0xAC. |
+0x00B0 | 1 | Visible or active state; set by ui_pane_show, cleared by ui_pane_hide, and queried by ui_pane_is_visible. |
+0x00B1 | 1 | Constructor flag copied from the first stack argument; exact meaning is not yet established. |
+0x00B2 | 1 | Constructor flag copied from the second stack argument; exact meaning is not yet established. |
+0x00B3 | 1 | Pane flag copied into the Screen node payload. |
+0x00F0 | 1 | Capture-related state checked before Screen removal. |
| Vtable offset | Role | Base implementation |
|---|---|---|
+0x14 | Show pane and invalidate its region | ui_pane_show |
+0x18 | Hide pane and invalidate the exposed parent region | ui_pane_hide |
+0x28 | Add pane to Screen composition | ui_pane_add_to_screen |
+0x2C | Remove pane from Screen composition | ui_pane_remove_from_screen |
+0x30 | Register event receiver | ui_pane_register_events |
+0x34 | Unregister event receiver | ui_pane_unregister_events |
+0x38 | Mouse Event handler, Event types 0 through 7 | ui_pane_default_mouse_handler |
+0x3C | Keyboard Event handler, Event type 8 | ui_pane_default_key_handler |
+0x40 | Socket-packet Event handler, Event type 9 | ui_pane_default_socket_handler |
+0x44 | Pane timer callback | ui_pane_default_timer_handler |
+0x48 | Additional class-specific handler | Base is nullsub_6; the generic role is not yet established. |
+0x4C | Optional class-specific handler | Absent in the base table; TextEdit calls its override on Enter. |
Event values and handler filters
The dispatcher reads the byte at Event offset +0x0C. Mouse subtype values and their Win32 production paths are established in Input and Windows Events.
| Event type | Meaning | Pane virtual slot and notes |
|---|---|---|
0 | Mouse move | +0x38 |
1 | Left single-button down | +0x38 |
2 | Left double-button down | +0x38; recognized by EventMan, not WM_LBUTTONDBLCLK |
3 | Left-button up | +0x38 |
4 | Right single-button down | +0x38 |
5 | Right double-button down | +0x38; recognized by EventMan, not WM_RBUTTONDBLCLK |
6 | Right-button up | +0x38 |
7 | Mouse wheel | +0x38; payload is the signed wheel delta divided by 120 |
8 | Mapped key down | +0x3C; there is no pane-dispatched key-up Event |
9 | Decoded socket packet or terminal bootstrap bytes | +0x40 |
The following filters are comparisons in the named handler bodies. A handler that delegates to ui_dialog_handle_mouse_event can still inherit the generic Dialog cases after its class-specific work.
| Class or shared handler | Accepted mouse subtype values | Established behavior |
|---|---|---|
ui_dialog_handle_mouse_event | 0, 1, 2, 3, 4, 7 | Generic Dialog hover, button, drag, and wheel processing. There is no explicit subtype 5 or 6 branch. |
ui_bulletin_dialog_handle_mouse_event | Adds 1 and 3, then delegates | Captures and releases Bulletin-family dialog drag state. |
LegendDialogPane | Adds 1 and 4, then delegates | Captures position on left down and invokes its class action on right down. |
BackPane | 1 | Starts its button animation or help-button timer. |
MainMenuPane | 0, 3 | Tracks menu hover and activates the released selection. |
QuestionMessageDialog and QuestionMessageFaceDialog | Adds 1 and 3, then delegates | Captures and releases dialog drag state. |
ServerSelectDialogPane | Adds 2, then delegates | Selects one of five server rows on left double-click. |
TerminalPane | 1, 3 | Presses and releases its three connection controls. |
Keyboard handlers receive Event type 8 and compare the mapped key byte at Event offset +0x10. Values below are recorded as the handler sees them. They are not assumed to be raw WM_* virtual-key values.
| Class or shared handler | Explicit mapped key values | Established behavior |
|---|---|---|
ui_dialog_handle_key_event | 0x09, 0x0D, 0x20, 0x1B, 0x90 | Tab, Enter, Space, Escape, screen action 0x90, plus focused-control forwarding. |
BulletinDialogPane and BoardListDialog | Q/q, W/w, E/e, 0x90 | Shared Bulletin-family navigation and focused-control forwarding. |
ArticleListDialog | V/v, N/n, 0x1B, 0x84 | View, new-article, close, and class-specific list actions. |
MailListDialog | V/v, N/n, R/r, 0x1B, 0x84 | View, new, reply, close, and class-specific list actions. |
NewArticleDialog and NewMailDialog | 0x09, 0x0D, 0x1B | Focus traversal, submit through the focused control, and cancel. |
Socket Event handlers filter on the first decoded byte or, where noted, a second subtype byte. The Map action set is kept in the protocol index because each branch has its own parser and side effects.
| Receiver | Claimed socket input | Notes |
|---|---|---|
| Bulletin dialog family | Server action 0x31 | Calls the receiver’s class-specific virtual at +0x64. |
MainMenuPane | Server actions 0x00, 0x02, 0x03, 0x0A | Bootstrap, version, and main-menu transitions. |
BackPane | Server action 0x49 | Portrait request. |
MapPane | 31 action bytes from 0x03 through 0x4B | Exact switch coverage is in Server Actions. |
| Exit-wait pane | Server action 0x4C, subtype 1 | Completes the orderly-exit exchange. |
ServerSelectDialogPane | Server action 0x56 | Parses the server table. |
TerminalPane | Bootstrap byte stream states 0 through 7 | Stateful terminal protocol, not an ordinary packet-action switch. |
Timer callback identifiers are local to the receiving class. BackPane uses IDs 0, 1, and 2; MapPane and ScreenPane each dispatch IDs 0 through 4. These values share no global enum with Event types 0 through 9.
Packed hierarchy representation
Both registries use ui_hierarchy_list_ctor, which adds an 11-byte hierarchy header to the requested payload size. The result is a packed, commonly unaligned node array.
| List offset | Width | Meaning |
|---|---|---|
+0x0C | 4 | Node stride. |
+0x14 | 4 | Node count. |
+0x18 | 4 | Pointer to the contiguous packed node array. |
+0x1C | 4 | Root or parent node pointer. Screen stores its separately allocated root here. |
| Node offset | Width | Meaning |
|---|---|---|
+0x00 | 4 | Parent node pointer. |
+0x04 | 4 | Child hierarchy-list pointer, or null. |
+0x08 | variable | Registry-specific payload. Both mapped registries begin the payload with the pane pointer. |
Screen requests a 0x34-byte payload, producing stride 0x3F. The pane pointer is at node +0x08, its RECT is at +0x0C, and copied pane flags begin at +0x1C. The root pointer stored at [ui_screen_registry + 0x1C] addresses a node whose child-list pointer is at root +0x04 and whose pane at root +0x08 is ui_screen_pane.
The event dispatcher requests a four-byte payload, producing stride 0x0F. Each event node therefore contains only its hierarchy links and the pane pointer at +0x08.
The exact external pointer chains are:
screen_list = read_process_u32(module_base + 0x000f51cc);
screen_root = read_process_u32(screen_list + 0x1c);
screen_children = read_process_u32(screen_root + 0x04);
dispatcher_object = read_process_u32(module_base + 0x000f51d0);
event_list = read_process_u32(dispatcher_object + 0x68);
Walk screen_children for ordinary Screen descendants. The separately allocated Screen root itself can be observed directly at screen_root. Walk event_list for registered event receivers.
A raw external walk is equivalent to:
static void walk_pane_list(uintptr_t list_address)
{
uint32_t node_stride;
uint32_t node_count;
uintptr_t node_array;
uint32_t node_index;
node_stride = read_process_u32(list_address + 0x0c);
node_count = read_process_u32(list_address + 0x14);
node_array = read_process_u32(list_address + 0x18);
for (node_index = 0; node_index < node_count; ++node_index) {
uintptr_t node_address;
uintptr_t child_list;
uintptr_t pane_address;
node_address = node_array + node_index * node_stride;
child_list = read_process_u32(node_address + 0x04);
pane_address = read_process_u32(node_address + 0x08);
observe_pane(pane_address);
if (child_list != 0) {
walk_pane_list(child_list);
}
}
}
The helper reads 32-bit values from arbitrary byte addresses because the strides 0x3F and 0x0F do not preserve four-byte alignment.
Creation and removal paths
The common add path is:
- A constructor installs a concrete pane vtable after
ui_pane_ctorinitializes the base. - The owner calls vslot
+0x28, reachingui_pane_add_to_screenand thenui_screen_add_pane. ui_screen_add_panevalidates bounds, resolves optional parent and front panes withui_screen_find_pane_node, copies the Screen payload, and inserts the packed node.- The owner calls vslot
+0x30, reachingui_pane_register_eventsandevent_dispatcher_register_pane.
Removal reverses both memberships before destruction. ui_pane_remove_from_screen releases capture-related state when necessary and calls ui_screen_remove_pane. Event removal reaches event_dispatcher_unregister_pane. Representative dynamic paths are ui_skill_inventory_remove_slot_pane, ui_spell_inventory_remove_slot_pane, the server-select destructor, and ui_text_edit_handle_key_event.
ui_screen_find_pane_node recursively returns the containing list and node index. ui_screen_get_pane_origin walks parent links to accumulate screen coordinates. These functions make the Screen hierarchy useful for both rendering and runtime inspection.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x0040A0A0 | ui_dialog_default_socket_handler | int __thiscall(void *, void *) | Decline a socket Event. | Default Dialog vslot +0x40; always returns zero. |
Darkages.exe:0x0040A0B0 | ui_dialog_default_timer_handler | int __thiscall(void *, int, int, int) | Decline a timer callback. | Default Dialog vslot +0x44; always returns zero. |
Darkages.exe:0x0040A5A0 | ui_pane_default_key_handler | established Pane virtual handler | Default keyboard Event handler. | Base vtable slot +0x3C; inherited by panes without a keyboard override. |
Darkages.exe:0x0040A5B0 | ui_background_pane_ctor | established __thiscall constructor | Construct the in-game background pane. | Called by ui_main_menu_create_game_panes; object size is 0x144. |
Darkages.exe:0x0040B460 | ui_pane_default_mouse_handler | established Pane virtual handler | Default mouse Event handler. | Base vtable slot +0x38. |
Darkages.exe:0x0040B470 | ui_pane_default_socket_handler | established Pane virtual handler | Default socket Event handler. | Base vtable slot +0x40. |
Darkages.exe:0x0040EA50 | ui_pane_default_timer_handler | established Pane timer handler | Default timer callback. | Base vtable slot +0x44. |
Darkages.exe:0x00414130 | ui_bulletin_dialog_handle_key_event | int __thiscall(void *, void *) | Process shared Bulletin-family key actions. | Vslot +0x3C for BulletinDialogPane and BoardListDialog. |
Darkages.exe:0x00414260 | ui_bulletin_dialog_handle_server_packet | int __thiscall(void *, void *) | Dispatch Bulletin-family server action 0x31. | Vslot +0x40; calls class vslot +0x64. |
Darkages.exe:0x004142E0 | ui_bulletin_dialog_handle_mouse_event | int __thiscall(void *, void *) | Add Bulletin-family drag tracking. | Vslot +0x38; handles subtypes 1 and 3, then delegates to Dialog. |
Darkages.exe:0x00415A10 | ui_article_list_dialog_handle_key_event | int __thiscall(void *, void *) | Process ArticleListDialog key actions. | Handles mapped V/v, N/n, Escape, and 0x84. |
Darkages.exe:0x004181A0 | ui_new_article_dialog_handle_key_event | int __thiscall(void *, void *) | Process NewArticleDialog editing keys. | Handles Tab, Enter, Escape, and focused controls. |
Darkages.exe:0x004184C0 | ui_mail_list_dialog_handle_key_event | int __thiscall(void *, void *) | Process MailListDialog key actions. | Adds mapped reply keys R/r to the list-dialog actions. |
Darkages.exe:0x0041AE70 | ui_new_mail_dialog_handle_key_event | int __thiscall(void *, void *) | Process NewMailDialog editing keys. | Handles Tab, Enter, Escape, and focused controls. |
Darkages.exe:0x0041C4D0 | ui_legend_dialog_pane_ctor | established __thiscall constructor | Construct the outer LegendDialogPane and its inner list control. | Installs ui_legend_dialog_pane_vtable; the inner 0x00506BE0 class remains unnamed. |
Darkages.exe:0x0041C770 | ui_legend_dialog_pane_handle_mouse_event | int __thiscall(void *, void *) | Process LegendDialogPane mouse input. | Adds subtypes 1 and 4, then delegates to Dialog. |
Darkages.exe:0x00431150 | event_dispatcher_register_pane | void __thiscall(void *, void *, int, int) | Insert a pane into the event hierarchy. | Reached directly for the root and through Pane vslot +0x30. |
Darkages.exe:0x004311B0 | event_dispatcher_unregister_pane | void __thiscall(void *, void *) | Remove a pane from the event hierarchy. | Reached through Pane vslot +0x34 and several explicit cleanup paths. |
Darkages.exe:0x00431D54 | event_dispatch_hierarchy | int __thiscall(void *, void *, void *) | Traverse child-first and call the event-specific pane slot. | Selects +0x38, +0x3C, or +0x40 for Event types 0 through 9. |
Darkages.exe:0x004325D0 | event_is_mouse | int __thiscall(void *event) | Test Event type 0 through 7. | Used by event_dispatch_hierarchy. |
Darkages.exe:0x004325F0 | event_is_keyboard | int __thiscall(void *event) | Test Event type 8. | Used by event_dispatch_hierarchy. |
Darkages.exe:0x0042A190 | ui_dialog_handle_mouse_event | int __thiscall(void *, void *) | Process generic Dialog mouse input. | Explicit cases are 0, 1, 2, 3, 4, and 7. |
Darkages.exe:0x0042AED0 | ui_dialog_handle_key_event | int __thiscall(void *, void *) | Process generic Dialog key input and forward to the focused control. | Handles Event type 8 only. |
Darkages.exe:0x00442C04 | ui_skill_inventory_remove_slot_pane | void __thiscall(void *, uint8_t) | Remove, unregister, delete, and clear an indexed skill pane. | Counterpart to ui_skill_inventory_create_slot_pane. |
Darkages.exe:0x00442D14 | ui_skill_inventory_create_slot_pane | void __thiscall(void *, uint8_t, uint16_t, const char *) | Allocate and register an indexed 0x294-byte skill pane. | Stores the heap pointer in its inventory parent. |
Darkages.exe:0x00443E14 | ui_spell_inventory_remove_slot_pane | void __thiscall(void *, uint8_t) | Remove, unregister, delete, and clear an indexed spell pane. | Counterpart to ui_spell_inventory_create_slot_pane. |
Darkages.exe:0x00443F24 | ui_spell_inventory_create_slot_pane | void __thiscall(void *, uint8_t, uint16_t, int8_t, const char *, const char *, uint8_t) | Allocate and register an indexed 0x214-byte spell pane. | Stores the heap pointer in its inventory parent. |
Darkages.exe:0x00446090 | ui_point_init | void __cdecl(void *, int, int) | Initialize an eight-byte client point. | Used for Pane relative origin. |
Darkages.exe:0x004460B0 | ui_rect_init | void __cdecl(void *, int, int, int, int) | Initialize a client rectangle from left, top, right, and bottom. | Stores the four fields in top, left, bottom, right memory order. |
Darkages.exe:0x0044E4D0 | ui_hierarchy_list_ctor | void *__thiscall(void *list, int payload_size) | Construct a packed hierarchy list. | Produces stride payload_size + 0x0B. |
Darkages.exe:0x0044F4F0 | ui_hierarchy_remove_node | established __thiscall removal method | Remove one packed node and its child list. | Shared by Screen composition and event dispatch. |
Darkages.exe:0x004594A0 | ui_hierarchy_get_node | void *__thiscall(void *list, int index) | Return a checked packed-node address. | Computes array + index * stride. |
Darkages.exe:0x0045D930 | ui_main_menu_pane_ctor | established __thiscall constructor | Construct and publish MainMenuPane. | Called by terminal completion and the in-game return path. |
Darkages.exe:0x0045FA44 | ui_main_menu_create_game_panes | established __thiscall transition method | Build and publish the in-game pane graph. | Constructs BackgroundPane, MapPane, and child panes. |
Darkages.exe:0x004658C0 | ui_map_pane_ctor | established __thiscall constructor | Construct MapPane. | Called for a 0xF38-byte allocation during game UI creation. |
Darkages.exe:0x0047DBA0 | ui_question_message_dialog_handle_mouse_event | int __thiscall(void *, void *) | Add drag tracking for the question-message dialog family. | Handles subtypes 1 and 3, then delegates to Dialog. |
Darkages.exe:0x0048D2E0 | ui_effect_object_pane_handle_timer | established Pane timer handler | Process EffectObjectPane timer callbacks. | Class name and method are confirmed by the vslot and diagnostic. |
Darkages.exe:0x0048F600 | ui_option_pane_ctor | void *__thiscall(void *) | Construct, add, and register dynamic OptionPane. | Installed by the Q window-button path. |
Darkages.exe:0x004909E0 | ui_option_pane_handle_server_packet | int __thiscall(void *, void *) | Claim SSelfSaveOk. | Creates a local confirmation dialog. |
Darkages.exe:0x00490A80 | ui_option_pane_handle_mouse_event | int __thiscall(void *, void *) | Process OptionPane mouse state. | Delegates common behavior to Dialog. |
Darkages.exe:0x00490D70 | ui_option_pane_handle_timer | int __thiscall(void *, int, int, int) | Advance OptionPane timer state. | Handles callback 100 separately. |
Darkages.exe:0x00490FE0 | ui_option_pane_handle_key_event | int __thiscall(void *, void *) | Process OptionPane shortcuts. | X constructs the exit-wait pane. |
Darkages.exe:0x00493240 | ui_pane_get_bound_rect | void __thiscall(void *, int32_t *) | Return the local graphic rectangle translated by Pane origin. | Reads +0x38 and +0xA8. |
Darkages.exe:0x004932B0 | ui_pane_set_bound_rect | void __thiscall(void *, const int32_t *) | Store relative origin and apply a translated local rectangle. | Vtable slot +0x24; Screen add calls it. |
Darkages.exe:0x00498F20 | ui_screen_registry_ctor | void *__thiscall(void *registry) | Construct the Screen composition registry. | Requests a 0x34-byte hierarchy payload. |
Darkages.exe:0x00498F40 | ui_screen_add_pane | established __thiscall add method | Validate and insert a Screen pane node. | Called by ui_pane_add_to_screen. |
Darkages.exe:0x004993C4 | ui_screen_find_pane_node | established recursive __thiscall search | Find a pane and optionally return its list and index. | Used for parent, sibling, removal, and coordinate queries. |
Darkages.exe:0x00499530 | ui_screen_remove_pane | established __thiscall removal method | Remove a pane node and child hierarchy. | Called by ui_pane_remove_from_screen. |
Darkages.exe:0x00499ED0 | ui_screen_get_pane_origin | established __thiscall query | Accumulate a pane’s screen-space origin. | Walks Screen hierarchy parent links. |
Darkages.exe:0x004A1680 | ui_scrollable_pane_handle_mouse_event | int __thiscall(void *, void *) | Route mouse input through scroll bars and the list body. | Base mouse path for ArticleListPane, MailListPane, and ItemListPane. |
Darkages.exe:0x004A1900 | ui_scrollable_pane_handle_key_event | int __thiscall(void *, void *) | Route key input through scroll bars and the list body. | Base key path for the same scrollable list panes. |
Darkages.exe:0x00492A90 | ui_pane_dtor | void __thiscall(void *pane) | Cancel timers and destroy the Pane base. | Does not remove either hierarchy registration. |
Darkages.exe:0x00492AD0 | ui_pane_ctor | void *__thiscall(void *, unsigned char, unsigned char) | Construct the common Pane base. | Installs ui_pane_vtable and initializes bounds and flags. |
Darkages.exe:0x00492C10 | ui_pane_is_visible | int __thiscall(void *pane) | Query common Pane visibility. | Used by the equipment same-pane toggle. |
Darkages.exe:0x00492C40 | ui_pane_show | void __thiscall(void *pane) | Set +0xB0, update Screen state, and invalidate. | Common Pane vslot +0x14. |
Darkages.exe:0x00492D50 | ui_pane_hide | void __thiscall(void *pane) | Clear +0xB0 and invalidate the exposed parent region. | Common Pane vslot +0x18; also handles capture state. |
Darkages.exe:0x004933C0 | ui_pane_add_to_screen | void __thiscall(void *, const int32_t *, void *, void *) | Add this pane to Screen composition using a client-order rectangle. | Pane vslot +0x28; delegates to ui_screen_add_pane. |
Darkages.exe:0x00493480 | ui_pane_remove_from_screen | void __thiscall(void *) | Remove this pane from Screen composition. | Pane vslot +0x2C; delegates to ui_screen_remove_pane. |
Darkages.exe:0x00493530 | ui_pane_register_events | void __thiscall(void *, void *, int) | Register this pane for event traversal. | Pane vslot +0x30; the signature xrefs enumerate compatible vtables. |
Darkages.exe:0x004935D0 | ui_pane_unregister_events | void __thiscall(void *) | Unregister this pane from event traversal. | Pane vslot +0x34. |
Darkages.exe:0x004A1B04 | ui_server_select_dialog_pane_dtor | established __thiscall destructor | Unregister and remove the server-select dialog. | Restores main-menu server-selection state before base destruction. |
Darkages.exe:0x004A2740 | ui_server_select_dialog_pane_ctor | established __thiscall constructor | Construct, add, and register the server-select dialog. | Called by ui_main_menu_handle_server_packet. |
Darkages.exe:0x004AC944 | ui_terminal_pane_dtor | established __thiscall destructor | Clear the terminal root and destroy its child editors. | Calls ui_pane_dtor after derived cleanup. |
Darkages.exe:0x004ACA00 | ui_terminal_pane_ctor | established __thiscall constructor | Construct, publish, add, and register TerminalPane. | Object size is 0xF78. |
Darkages.exe:0x004AD130 | ui_get_terminal_pane | void *__cdecl(void) | Return ui_terminal_pane. | Direct static-root accessor. |
Darkages.exe:0x004AD140 | ui_terminal_handle_server_packet | established Pane socket handler | Handle terminal packets and transition to MainMenuPane. | Terminal vtable slot +0x40. |
Darkages.exe:0x004BE6F4 | ui_text_edit_handle_key_event | established Pane keyboard handler | Complete or cancel a temporary text editor. | Removes both registrations and queues deferred deletion. |
Internal Event Routing
The internal event system owns two related paths. It routes typed events through registered panes, and it runs periodic timer callbacks on a worker thread independently of the blocking Windows message pump. Network packets enter the same pane hierarchy through a separate event-manager worker.
High-level operation
Dispatcher, event manager, and pane hierarchy
app_initialize creates the global event_dispatcher, starts its worker, then constructs and starts the separate event_manager_instance. The dispatcher owns the pane registry and the timer list. The event manager converts queued work records into Event objects, including socket-packet events, and submits those events to the dispatcher.
The established socket path is:
- The Socket receive worker posts copied decoded bytes through
event_post_socket_bytes. event_process_work_itemconverts the queued record to Event type 9.event_queue_socket_packettransfers the owned packet pointer and size into the Event queue.event_dispatchandevent_dispatch_hierarchywalk registered panes and invoke the socket-event virtual handler.
Packet ownership and the type 9 fields are documented in Networking. Pane registration and timer callbacks remain owned by this subsystem.
The dispatcher hierarchy is separate from the Screen composition hierarchy. The event tree stores packed 0x0F-byte nodes with a pane pointer at +0x08, while Screen stores placement and drawing relationships in 0x3F-byte nodes. Their raw layouts, pane virtual slots, creation and removal flows, and complete vtable inventory are documented in UI Panes and Registries.
Window input uses the same EventMan worker. The main window procedure posts work codes 4 through 0x0B for mouse movement, buttons, wheel, and physical scan codes. event_process_work_item converts those records into mouse and key state or Events before the normal pane hierarchy sees them. The complete mapping is documented in Input and Windows Events.
Event copying and queue ownership
event_dispatcher_queue_event_copy allocates and shallow-copies the complete 0x24-byte Event, then posts dispatcher work code 3. event_dispatcher_process_work_item calls event_dispatch for that code. After dispatch returns, it frees Event +0x14 when the object is a socket Event, then deletes the Event copy. This dispatcher-worker cleanup still runs when a hook or pane consumes the Event.
event_manager_queue_event_copy performs the same 0x24-byte shallow copy but posts EventMan work code 0x0F. event_process_work_item forwards that copy into the dispatcher queue. No native call site for the EventMan wrapper is present in this build, so its implementation and consumer are established but its original producer role is not.
Both wrappers ultimately call util_thread_queue_post_async, which appends the raw {code, data, value} record and releases the queue semaphore. The raw helper does not copy the data pointer. Ownership comes from the caller-specific wrapper, so proxy code should use a mapped wrapper instead of posting a stack pointer directly. See Event Proxy Hooks and Injection for the resulting interception and injection boundaries.
Bounded queue synchronization
The worker queue is safe for simultaneous producers. Its ring owns a monitor, a not-full condition, and a not-empty condition. util_ring_buffer_push_wait acquires the monitor, waits when element_count == capacity, copies one fixed-size record, signals not-empty, and releases the monitor. util_ring_buffer_pop_wait performs the symmetric operation and signals not-full after removing a record.
This establishes synchronization for the native client and an injected producer that calls a copied-input wrapper at the same time. It does not make admission nonblocking. util_thread_queue_post_async does not wait for worker completion, but its internal ring push can wait indefinitely for space. The Socket and EventMan rings each hold 128 records; the dispatcher ring holds 1,024.
That distinction matters during shutdown. EventMan destruction deletes its owned Socket, clears both static roots, and then tears down its worker base. The worker-base destructor uses TerminateThread and destroys the ring and its synchronization objects. A foreign thread must not still be waiting in that ring when teardown begins. The Event Proxy design therefore uses bounded proxy queues and worker-affine pumps, with app_shutdown entry as a drain barrier.
Worker and timer lifecycle
The Windows message loop is not a frame loop. The dispatcher derives from a cross-subsystem thread-queue base whose constructor creates a thread with _beginthreadex and CREATE_SUSPENDED. The same base is also used by the Socket and other worker-derived objects. The dispatcher configures that worker before util_thread_queue_start resumes it:
| Setting | Established value or rule |
|---|---|
| Work-queue capacity | 0x400 records |
WaitForMultipleObjects timeout | 1 millisecond |
| Requested multimedia timer period | 5 milliseconds clamped to TIMECAPS.wPeriodMin..wPeriodMax |
| Timer clock | 32-bit timeGetTime() milliseconds |
| Initial next deadline | UINT32_MAX |
| Timer-list capacity | 256 records |
| Timer-record size | 20 bytes |
| Worker priority | THREAD_PRIORITY_TIME_CRITICAL |
The dispatcher calls timeGetDevCaps but does not check its return. It calls timeBeginPeriod with the selected period, stores the first timeGetTime sample, creates the timer list, and raises the still-suspended worker’s priority. A 1 millisecond wait timeout is therefore a polling interval requested from the worker, not a promise that Windows will deliver callbacks every millisecond. The multimedia period is normally 5 milliseconds when the device range permits it.
util_thread_queue_start first calls virtual slot +0x08 so the derived object can add its wait object. It resumes the thread only when app_error_code remains zero.
The worker runs WaitForMultipleObjects(wait_count, wait_handles, FALSE, timeout_ms) forever. Wait handle index 0 is the queued-work notification. A work wake first checks the ring. It removes and handles one record when nonempty, but an empty ring is also valid and skips directly to the periodic virtual. Other registered handle indices call virtual slot +0x1C. After every handled wake, empty queue wake, secondary signal, or timeout, the loop calls virtual slot +0x10. In the dispatcher vtable, that periodic slot is event_dispatcher_tick; the Socket slot is net_poll_receive; the EventMan slot is event_manager_periodic_noop.
Timer records and scheduling
Timer insertion normally reaches event_dispatcher_process_work_item as work code 4. Code 5 cancels all timers for a pane. event_dispatcher_insert_timer converts the relative delay to an absolute 32-bit deadline by adding it to the dispatcher’s most recent timeGetTime sample.
Each sorted timer record has this layout:
| Offset | Width | Meaning |
|---|---|---|
+0x00 | 4 | Receiver pane pointer |
+0x04 | 4 | Callback identifier |
+0x08 | 4 | Absolute timeGetTime deadline |
+0x0C | 4 | First callback payload value |
+0x10 | 4 | Second callback payload value |
The insertion scan uses an unsigned comparison and places the record before the first later deadline. Equal deadlines remain in insertion order. If the new record becomes element 0, its deadline is copied to the dispatcher’s cached next-deadline field.
event_dispatcher_remove_pane_timers scans the complete list and removes every record whose receiver pointer matches the pane. It then sets the cached deadline from the new first record, or to UINT32_MAX when the list is empty.
Periodic tick and deferred deletion
Each event_dispatcher_tick first drains event_deferred_delete_queue. The drain walks queued object pointers from the last element to the first, calls each object’s deleting destructor with flag 1, and removes the entry. This gives client code a deferred-destruction path tied to the dispatcher worker rather than the Windows message thread.
The tick then samples timeGetTime, stores the result, and compares it to the cached next deadline as unsigned 32-bit values. If the current tick is earlier, the tick returns. Otherwise it removes only the first timer record, refreshes the cached deadline, verifies that the receiver is still an active object, and calls receiver virtual slot +0x44 with the callback identifier and the two payload values.
At most one due timer is dispatched per worker iteration. When several timers are overdue, they are removed across successive worker wakes or timeouts.
The code uses plain unsigned addition, sorting, and current_tick < deadline comparisons. It does not use a signed modular difference or another visible timeGetTime wraparound correction. Implementations that need byte-for-byte behavioral compatibility should preserve that distinction; implementations seeking a robust long-running clock should account for 32-bit wrap separately.
Shutdown and ownership
app_shutdown first deletes EventMan, whose destructor deletes its owned Socket and clears both static roots. It later deletes the event dispatcher before it deletes the global deferred-deletion queue. The dispatcher destructor performs these operations in order:
- Delete the pane registry.
- Delete the timer list.
- Call
timeEndPeriodwith the stored period. - Invoke the worker-base destructor.
The worker-base destructor calls TerminateThread(worker_handle, 0), closes the thread handle and every wait handle, and destroys the queued message and synchronization objects. There is no cooperative stop flag, worker acknowledgement, or thread join. The pane and timer containers are released before the worker is forcibly terminated, so shutdown relies on the fixed surrounding teardown sequence and timing rather than an explicit worker quiescence handshake.
Code-level flow
Construction and start
Darkages.exe:0x0045CCA0app_initializeallocates the 128-byte dispatcher object and callsevent_dispatcher_ctor.Darkages.exe:0x00430FE0callsutil_thread_queue_ctor(this, 0x400). The base constructor creates queue state and a suspended_beginthreadexworker stored at object offset+0x60.- The dispatcher installs
event_dispatcher_vtable, callsutil_thread_queue_set_wait_timeout(this, 1), and allocates its pane registry at+0x68. - It reads
TIMECAPS, clamps 5 milliseconds into the reported range, stores the result at+0x6C, and callstimeBeginPeriod. - It stores
timeGetTime()at+0x70, sets cached next deadline+0x74toUINT32_MAX, and creates a 256-element timer list at+0x78with 20-byte elements. - It sets the suspended thread to
THREAD_PRIORITY_TIME_CRITICAL. app_initializestores the object inevent_dispatcherand callsutil_thread_queue_start, which creates the derived wait object and resumes the worker.- After the screen pane exists,
event_dispatcher_register_pane(screen_pane, 0, 0)inserts it into the pane registry. app_initializeseparately allocates the EventMan object, callsevent_manager_ctor, and callsutil_thread_queue_start. The constructor uses a work-queue capacity of0x80, publishesevent_manager_instance, and sets its worker timeout from the constructed event-manager state.
Allocation failure sets app_error_code to 2 and records the same error in the dispatcher base. The initializer then follows its staged failure cleanup.
Worker loop
Darkages.exe:0x004BF250 util_thread_queue_worker_loop has no normal exit branch. Its repeating path is equivalent to:
for (;;) {
uint32_t wait_result;
wait_result = WaitForMultipleObjects(
worker->wait_count,
worker->wait_handles,
0,
worker->timeout_ms
);
if (wait_result == WAIT_OBJECT_0) {
if (!util_ring_buffer_is_empty(worker->work_queue)) {
process_one_queued_record(worker);
}
} else if (wait_result > WAIT_OBJECT_0 &&
wait_result < WAIT_OBJECT_0 + worker->wait_count) {
process_secondary_wait_handle(worker, wait_result);
}
run_periodic_worker_method(worker);
}
The real queue path supports both asynchronous records and synchronous records whose producer waits on an event for a returned result. After processing a synchronous record, the worker writes the result to the matching waiter entry and signals its event.
Timer insertion and callback
For work code 4, event_dispatcher_process_work_item extracts the receiver, callback identifier, delay, and two payload values and calls event_dispatcher_insert_timer. The insertion function builds the 20-byte record, performs a linear scan of the sorted list, inserts it, and updates +0x74 only when inserted at index 0.
The periodic vtable call reaches event_dispatcher_tick:
event_deferred_delete_queue_drain(event_deferred_delete_queue);
current_tick = timeGetTime();
dispatcher->current_tick = current_tick;
if (current_tick < dispatcher->next_deadline) {
return;
}
timer_record = remove_first_timer_record(dispatcher);
refresh_next_deadline(dispatcher);
if (receiver_is_active(timer_record.receiver)) {
invoke_timer_callback(
timer_record.receiver,
timer_record.callback_id,
timer_record.payload_0,
timer_record.payload_1
);
}
The implementation removes the timer before checking receiver activity. An inactive receiver therefore loses the due callback rather than retaining or rescheduling it.
Network event handoff
The separate event manager’s mapped packet route is documented in detail under receive and event routing. The important cross-thread ownership boundary is that event_post_socket_bytes copies the decoded packet into a work record. event_process_work_item later constructs the Event and queues it to the dispatcher. The dispatcher worker frees the owned packet and Event copy after pane dispatch.
Dispatcher object fields
Offsets below are from the event dispatcher object. The worker-base fields are shared with the event manager and other worker-derived objects.
| Offset | Width | Working field | Reads and writes |
|---|---|---|---|
+0x00 | 4 | vtable | Set to event_dispatcher_vtable. |
+0x0C | 4 | wait_timeout_ms | Set to 1 by util_thread_queue_set_wait_timeout; consumed by util_thread_queue_worker_loop. |
+0x10 | 1 | wait_handle_count | Passed to WaitForMultipleObjects. |
+0x14 | variable | wait_handles | Handle index 0 is the worker queue notification. |
+0x54 | 4 | work_queue | Bounded fixed-record ring; capacity is 0x400 for the dispatcher and 0x80 for EventMan and Socket. |
+0x58 | 4 | completion_monitor | Serializes synchronous completion bookkeeping. |
+0x5C | 4 | completion_waiters | Tracks synchronous callers waiting for a work result. |
+0x60 | 4 | worker_thread | Suspended handle created by the base constructor, resumed by util_thread_queue_start, forcibly terminated by the base destructor. |
+0x64 | 4 | worker_thread_id | Thread identifier returned by _beginthreadex. |
+0x68 | 4 | pane_registry | Points to a packed hierarchy with stride 0x0F; each node stores its child list at +0x04 and pane at +0x08. |
+0x6C | 4 | multimedia_period_ms | Period passed to timeBeginPeriod and later timeEndPeriod. |
+0x70 | 4 | current_tick | Latest timeGetTime sample; also the base for newly inserted relative delays. |
+0x74 | 4 | next_deadline | First timer deadline, or UINT32_MAX when empty. |
+0x78 | 4 | timer_list | Sorted list of 20-byte timer records. |
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x0040E4D0 | event_deferred_delete_queue_ctor | void *__thiscall(void *queue_object) | Construct the deferred-deletion queue. | Called by app_initialize; source diagnostics identify the original implementation unit as BlackHole.cpp. |
Darkages.exe:0x0040E5C0 | event_deferred_delete_queue_drain | void __thiscall(void *queue_object) | Delete and remove every deferred object. | Called first by each event_dispatcher_tick and during shutdown before queue deletion. |
Darkages.exe:0x00430F84 | event_dispatcher_dtor | void __thiscall(void *event_dispatcher_object) | Release dispatcher containers and multimedia timing state. | Calls timeEndPeriod, then util_thread_queue_dtor. |
Darkages.exe:0x00430FE0 | event_dispatcher_ctor | void *__thiscall(void *event_dispatcher_object) | Construct the dispatcher, timer list, and worker timing state. | Called by app_initialize; calls util_thread_queue_ctor, util_thread_queue_set_wait_timeout, timeBeginPeriod, and SetThreadPriority. |
Darkages.exe:0x00431150 | event_dispatcher_register_pane | void __thiscall(void *event_dispatcher_object, void *pane, int hierarchy, int position) | Add a pane and hierarchy metadata to the dispatcher registry. | Called by app_initialize for the screen pane and by later pane lifecycle paths. |
Darkages.exe:0x004311B0 | event_dispatcher_unregister_pane | void __thiscall(void *event_dispatcher_object, void *pane) | Remove a pane from the dispatcher registry. | Prevents later hierarchy dispatch to the removed pane. |
Darkages.exe:0x00431200 | event_dispatcher_queue_event_copy | void __thiscall(void *event_dispatcher_object, const void *event) | Copy one 0x24-byte Event into dispatcher work. | Allocates the copy and posts work code 3; the dispatcher worker owns final cleanup. |
Darkages.exe:0x00431320 | event_dispatcher_insert_timer | void __thiscall(void *event_dispatcher_object, void *receiver, int callback_id, unsigned int delay_ms, int payload_0, int payload_1) | Insert a relative timer into the absolute-deadline sorted list. | Reached from dispatcher work code 4; updates cached deadline when inserted first. |
Darkages.exe:0x00431470 | event_dispatcher_remove_pane_timers | void __thiscall(void *event_dispatcher_object, void *receiver) | Cancel every timer owned by one pane. | Reached from dispatcher work code 5; refreshes the cached deadline. |
Darkages.exe:0x004315B0 | event_dispatcher_tick | void __thiscall(void *event_dispatcher_object) | Drain deferred deletion and dispatch at most one due timer. | Dispatcher vtable slot +0x10; called by util_thread_queue_worker_loop after every wake or timeout. |
Darkages.exe:0x004316E0 | event_dispatcher_process_work_item | void __thiscall(void *event_dispatcher_object, int code, void *data, int value) | Execute dispatcher work codes 1 through 5. | Timer insertion is code 4; pane-timer cancellation is code 5. |
Darkages.exe:0x00431B84 | event_dispatch | int __thiscall(void *this, void *event) | Dispatch one internal Event. | Called by the event manager; delegates hierarchy traversal to event_dispatch_hierarchy. |
Darkages.exe:0x00431D54 | event_dispatch_hierarchy | int __thiscall(void *this, void *event, void *hierarchy) | Walk registered panes and call the type-specific virtual handler. | Socket Event type 9 reaches pane virtual slot +0x40. |
Darkages.exe:0x00432630 | event_manager_ctor | void *__thiscall(void *event_manager_object) | Construct and publish the EventMan singleton. | Calls util_thread_queue_ctor with capacity 0x80, initializes event-manager and Socket-facing state, and stores event_manager_instance. |
Darkages.exe:0x00432514 | event_manager_dtor | void __thiscall(void *event_manager_object) | Destroy EventMan and its owned Socket. | Clears net_socket_instance and event_manager_instance, then tears down the worker base. |
Darkages.exe:0x00432E50 | event_post_socket_bytes | void __thiscall(void *this, const uint8_t *packet, int length) | Copy decoded packet bytes to event-manager work code 0x0E. | Called by the Socket receive path. |
Darkages.exe:0x00432F10 | event_manager_queue_event_copy | void __thiscall(void *event_manager_object, const void *event) | Copy one 0x24-byte Event to EventMan work code 0x0F. | The consumer path is established, but this build has no native call sites for the wrapper. |
Darkages.exe:0x00433110 | event_process_work_item | void __thiscall(void *this, int code, void *data, int value) | Convert event-manager work records into input state or Event objects. | Work codes 4 through 0x0B handle window input; code 0x0E creates a socket-packet Event and transfers packet ownership. |
Darkages.exe:0x00433DC4 | event_queue_socket_packet | void __stdcall(uint8_t *packet, uint32_t size) | Queue Event type 9 with packet ownership. | Called by event_process_work_item; dispatcher work code 3 frees the packet after dispatch. |
Darkages.exe:0x00434080 | event_manager_periodic_noop | void __thiscall(void *event_manager_object) | Perform the native EventMan periodic callback. | Empty implementation at vtable slot +0x10; still reached after every EventMan wake or timeout. |
Darkages.exe:0x004BE9B0 | util_thread_queue_ctor | void *__thiscall(void *worker_object, int queue_capacity) | Construct queue state and a suspended worker thread. | Cross-subsystem base constructor used by the dispatcher, event manager, Socket, and other worker-derived objects. |
Darkages.exe:0x004BEC34 | util_thread_queue_add_wait_handle | void __thiscall(void *worker_object, void *wait_handle) | Append a Win32 handle to the worker wait set. | Constructor uses it to install the work semaphore as wait handle 0; maximum count is 16. |
Darkages.exe:0x004BECB0 | util_thread_queue_dtor | void __thiscall(void *worker_object) | Terminate the worker and close its handles and queue state. | Uses TerminateThread; called by derived destructors. |
Darkages.exe:0x004BEDF0 | util_thread_queue_set_wait_timeout | void __thiscall(void *worker_object, unsigned int timeout_ms) | Set the worker wait timeout. | Dispatcher constructor passes 1 millisecond. |
Darkages.exe:0x004BEE00 | util_thread_queue_start | void __thiscall(void *worker_object) | Create the derived wait object and resume the suspended thread. | Called by app_initialize for both event workers and by other worker-derived clients. |
Darkages.exe:0x004BF250 | util_thread_queue_worker_loop | void __thiscall(void *worker_object) | Wait for work, signals, or timeout and invoke derived virtual methods. | Empty work wakes still call periodic slot +0x10; targets are dispatcher tick, Socket poll, and EventMan no-op. |
Darkages.exe:0x00498080 | util_ring_buffer_push_wait | void __thiscall(void *ring_buffer, const void *element) | Append one fixed-size ring element under the queue monitor. | Waits on not-full when the bounded ring has no capacity. |
Darkages.exe:0x004981B0 | util_ring_buffer_pop_wait | void __thiscall(void *ring_buffer, void *element_out) | Remove one fixed-size ring element under the queue monitor. | Signals not-full after the copy and count update. |
Darkages.exe:0x004BF440 | util_thread_queue_post_async | void __thiscall(void *worker_object, int code, void *data, int value) | Append one asynchronous worker record and wake the worker. | Copies the record fields but not pointed-to data; can block for ring capacity even though it does not wait for completion. |
File System
The file subsystem covers path construction, lookup order, file access, containers, caching, and the shared readers and writers used by format-specific code.
High-level operation
This section will explain where the client looks for data, how resources are identified, which content is loaded eagerly or on demand, and how file data reaches rendering, audio, user interface, and game systems.
File system behavior has not been mapped yet. Specific layouts will be linked from the Format Catalog.
Code-level flow
This section will trace path construction, open and close operations, archive or container access, allocation, common byte and string readers, bounds handling, caching, decoding, and cleanup. Format-specific flows will link back to their reader and writer functions.
No file system call tree is documented yet.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
| No functions documented yet |
Networking
The networking subsystem owns the active transport, turns a byte stream into packet bodies, applies the protocol’s XOR transformations, and moves decoded server packets into the pane event hierarchy. Client packet builders use raw byte arrays and the same small big-endian field writers.
The directional action indexes are Client actions and Server actions. Each index links to one payload-relative page per action.
High-level operation
Connection and Windows notification
The normal game transport uses Winsock 1.1 and an IPv4 TCP stream. Connection setup creates an AF_INET, SOCK_STREAM socket, requests a 0x4000 byte Winsock receive buffer, resolves the configured host, and connects to the configured port. Once connected, WSAAsyncSelect associates the socket with the main window using message 0x0402 and event mask 0x21, which is FD_READ | FD_CLOSE.
The main window procedure handles the low word of lParam for message 0x0402:
| Winsock event | Value | Client action |
|---|---|---|
FD_READ | 0x0001 | Queue socket work code 4, which runs the receive framer. |
FD_CLOSE | 0x0020 | Close the socket and serial handle, clear connection state, and offer the reconnect UI. |
The executable also retains serial and printable-text transports. They eventually feed the same logical packet event path, but the standard Stone server path is the binary TCP path described here.
Binary frame
Every binary TCP frame begins with 0xAA. The two-byte size is unsigned and big-endian. It counts the body beginning at the action byte, not the marker or the size itself.
| Offset | Width | Type | Meaning |
|---|---|---|---|
0x00 | 1 | uint8_t | Marker, always 0xAA. |
0x01 | 2 | uint16_t, big-endian | Wire body length. |
0x03 | body_length | bytes | Action, transformation metadata where applicable, and payload. |
The total frame length is therefore 3 + body_length. The receive code requires a positive body length and stores at most 0x10000 body bytes. Since the wire field is 16 bits, the largest representable wire value is 0xFFFF.
The application-facing logical packet is:
[uint8 action] [payload bytes...] [zero sentinel]
net_c_queue_send receives the action and payload length from a packet builder, copies those bytes, and appends the zero sentinel. The sentinel is an implementation guard, not a checksum. The decoder does not validate its transformed value. After decoding it writes another zero immediately after the returned logical bytes.
Direction defines the action
Action values are not globally unique. A client action and a server action with the same byte can have unrelated meanings. For example, both directions support 0x0D, but the send builders and receive handlers are separate code paths. Protocol implementations must key dispatch by (direction, action), never by action alone.
The executable contains 52 established client action values and 59 server action values. Fifty-one client values occur directly at net_c_queue_send call sites. Action 0x0F is built by the spell-slot path and reaches the queue through the spell-delay controller. Thirty-five values occur in both directions. A server 0x4B packet can also ask the client to forward an embedded logical client packet, so that path is not restricted to one statically embedded client action constant.
Receive and event routing
The TCP reader refills a 0x18000 byte cache with recv and then supplies one byte at a time to the framer. This preserves a partial header or body across Windows notifications and permits multiple complete frames to be drained from one receive cache.
After a frame is complete, the client decodes it when required and posts a copy as internal work code 0x0E. The EventMan worker converts that work item into an Event with type 9, packet pointer at offset +0x14, and packet size at offset +0x18. It queues a copied Event to the dispatcher worker, which recursively walks the pane hierarchy and calls virtual slot +0x40 on each eligible pane. That virtual method is the pane’s server-packet handler.
Dispatch is therefore distributed rather than centralized. MapPane owns the largest switch, while MainMenuPane, ServerSelectDialogPane, exchange panes, bulletin panes, and other UI objects handle actions relevant to their current state. The server action index combines those separate handlers.
Send path
Packet builders assemble a logical packet in a local byte array and call net_c_queue_send. The Socket object copies the bytes, appends the sentinel, and queues work code 5. The socket worker then applies the client transformation rule, allocates body_length + 3 bytes, writes the frame header, and calls Winsock send once.
The send path treats SOCKET_ERROR as failure. It does not retry when send succeeds with a byte count smaller than the requested frame length. A compatible implementation should use a full-write loop even though this client does not.
During a server transfer state, net_c_queue_send drops every client action except 0x10, the client transfer-server action.
Queue concurrency and Socket affinity
The Socket work queue has capacity 0x80. Its ring is protected by a monitor and paired not-full and not-empty conditions. net_c_queue_send can therefore run concurrently on a native client thread and an injected producer without racing the head, tail, count, or copied packet ownership.
The enqueue is not nonblocking. util_thread_queue_post_async calls util_ring_buffer_push_wait, which waits indefinitely when the ring is full, then signals the worker semaphore after admission. Calling it from an IPC read thread would make pipe liveness depend on Socket progress. Calling it from the Socket consumer itself can deadlock if other producers refill the ring before the self-enqueue.
Sequence, XOR state, framing buffers, and the transport call are mutated only by the Socket worker after it consumes code 5. A production Event Proxy should preserve that affinity. The documented design wakes the Socket worker with an empty native-queue signal and drains a bounded proxy queue from the worker’s net_poll_receive periodic callback. A simpler dedicated producer thread may call net_c_queue_send, but it must be drained before EventMan destroys its owned Socket during app_shutdown.
The active Socket is reachable through net_socket_instance at Darkages.exe:0x004F51BC. EventMan owns the Socket at object offset +0x68, publishes the static root during construction, and clears it during destruction. This is the established runtime root for logical packet injection, but callers must still validate the live net_socket_vtable before using a cached pointer. See Event Proxy Hooks and Injection for the injection contract and ownership boundaries.
Packet representation and C++ class evidence
The traced send and receive paths do not instantiate a class per packet or a common ClientPacket or ServerPacket object. Client builders write directly into byte arrays. The receiver copies decoded bytes into an Event, and pane handlers switch on packet[0] and call field readers on later offsets.
The code does establish these surrounding C++ types through constructors, virtual tables, source assertions, and diagnostics:
| Established type | Evidence in the packet path |
|---|---|
Socket | Socket.cpp diagnostics, a constructor-installed virtual table, transport state fields, and virtual work-item processing. |
Event | Socket Event type 9 owns the packet pointer and size and frees the packet after dispatch. |
MainMenuPane | Named diagnostics and virtual table Darkages.exe:0x005177C0; handles initial version and key negotiation. |
MapPane | MapPane.cpp, named method diagnostics, and virtual table Darkages.exe:0x00519280; owns the main in-game action switch. |
ServerSelectDialogPane | Named constructor and deletion diagnostics and virtual table Darkages.exe:0x00526140; handles server action 0x56. |
Names such as kServerRequestPortrait and kClientTransferServer are enum-style action names embedded in diagnostics, not evidence of packet classes. The safest compatible-client model for the mapped path is a direction-tagged byte buffer plus per-action serializers and handlers.
Big-endian serialization
The packet builders and handlers share small integer helpers:
| Width | Writer | Reader | Byte order |
|---|---|---|---|
| 1 byte | net_write_u8 | net_read_u8 | Not applicable |
| 2 bytes | net_write_u16_be | net_read_u16_be | Most significant byte first |
| 4 bytes | net_write_u32_be | net_read_u32_be | Most significant byte first |
The writers place a zero byte immediately after the value. Packet builders subsequently overwrite that byte when adding the next field, or leave it as the packet sentinel at the final logical offset.
Sequence and XOR transformation
Ordinary bodies carry a sequence byte and three XOR passes over the payload and trailing zero. The message indexes label this branch Encrypted: Yes, but the transformation does not provide authentication or cryptographic security.
The default repeating key is the nine ASCII bytes NexonInc., the default table is generated by function 0, and the client send sequence starts at zero. Clear server action 0x00, subtype 0x00, can replace the nine-byte key and select any table function from 0 through 9. Only encrypted client packets increment the client send sequence.
The decoder does not verify that the transformed trailing byte becomes zero. It retains that byte and writes a second local zero guard after the returned logical packet.
See Sequence and XOR Transformation for the exact wire placement, pass order, negotiation payload, all ten table formulas, and implementation addresses.
Failure and bounds behavior
The standard receive path has these established edge behaviors:
- Outside a frame, the next consumed byte is asserted to be
0xAA; the code does not scan ahead for a later marker. - A body length of zero or greater than
0x10000fails an internal assertion. The two-byte wire field cannot encode a value greater than0xFFFF. recv == 0closes the socket, sets it toINVALID_SOCKET, clears connection state, and stops reading.recv == SOCKET_ERRORclears the receive cache and returns no byte. This path does not distinguish individualWSAGetLastErrorvalues.- Completed packet bytes are copied for the event worker. The Event owns the copy and frees it after pane dispatch.
- No checksum or message authentication value is validated by the mapped frame or XOR code.
Code-level flow
Startup and connection
Darkages.exe:0x004A32F0net_socket_ctorinstalls the Socket virtual table, initializes the transport buffers and state, and installsNexonInc.as the default repeating key.Darkages.exe:0x004A59F4net_connect_configured_serverstarts Winsock 1.1, checks that version 1.1 was returned, resolves the configured host, creates and connects the TCP socket, and registersWM_USER + 2withFD_READ | FD_CLOSE.Darkages.exe:0x0045BDC0win_main_window_procreceives message0x0402.FD_READcallsnet_queue_receive;FD_CLOSEcallsnet_handle_disconnect.
Receive, decode, and dispatch
win_main_window_proc
-> net_queue_receive work code 4
-> net_process_work_item
-> net_s_receive_frames
-> net_read_transport_byte
-> recv refill up to 0x18000 bytes
-> net_s_decode_packet_body ordinary server actions
-> event_post_socket_bytes work code 0x0E
-> event_process_work_item
-> event_queue_socket_packet Event type 9
-> event_dispatch
-> event_dispatch_hierarchy
-> pane vtable +0x40
net_s_receive_frames maintains three important pieces of persistent state: whether a frame is open, the number of header or body bytes collected, and the current body length. It combines the first two body-header bytes as (high << 8) | low. Completion occurs after exactly body_length bytes have been placed in the packet buffer.
event_dispatch_hierarchy tests Event type 9 with event_is_socket_packet before calling pane virtual slot +0x40. The return value indicates whether the event was consumed. After event_dispatch returns, event_dispatcher_process_work_item frees the packet pointer for a socket Event and deletes the copied Event.
Build, encode, and send
per-action packet builder
-> net_write_u8 / net_write_u16_be / net_write_u32_be
-> net_c_queue_send copy and append zero
-> net_process_work_item work code 5
-> net_c_send_packet_body
-> net_c_encode_packet_body ordinary client actions
-> net_write_u8(0xAA)
-> net_write_u16_be(body_length)
-> send one call
net_c_encode_packet_body returns one byte more than its input size because it inserts the sequence after the action. net_s_decode_packet_body returns one byte less than its input size because it removes that sequence. Bypass actions keep their original body size.
Representative server dispatch
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet is the largest action switch. It accepts 31 action values and rejects the other values in its 0x03 through 0x4B switch range. Important established branches include:
| Server action | Callee or effect |
|---|---|
0x03 | Transfer-server processing. |
0x0A | Secondary subtype dispatch within MapPane. |
0x0E | Delete an object from map state. |
0x11 | ui_map_handle_object_direction, which reads a big-endian object ID and a direction from 0 through 3. |
0x32 | Inline tile or object updates. |
0x4B | net_forward_embedded_client_packet, which reads a big-endian embedded length and sends the embedded logical client packet. |
The complete accepted set and all other pane handlers are in the server action index. The separate exit-wait pane handles SReconnect, server action 0x4C. Its subtype-one branch completes an orderly-exit exchange and does not call the Socket reconnect path.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x00431B84 | event_dispatch | int __thiscall(void *this, void *event) | Dispatch one internal Event. | Calls event_dispatch_hierarchy; participates in capture and consumed-event handling. |
Darkages.exe:0x00431D54 | event_dispatch_hierarchy | int __thiscall(void *this, void *event, void *hierarchy) | Recursively deliver an Event to panes. | Calls pane virtual slot +0x40 for socket Event type 9. |
Darkages.exe:0x00432610 | event_is_socket_packet | int __thiscall(void *event) | Test for socket Event type 9. | Reads the Event type byte at +0x0C. |
Darkages.exe:0x00432E50 | event_post_socket_bytes | void __thiscall(void *this, const uint8_t *packet, int length) | Copy decoded packet bytes to the worker queue. | Posts work code 0x0E. |
Darkages.exe:0x00433110 | event_process_work_item | void __thiscall(void *this, int code, void *data, int value) | Convert worker records to internal Events. | Code 0x0E calls event_queue_socket_packet. |
Darkages.exe:0x00433DC4 | event_queue_socket_packet | void __stdcall(uint8_t *packet, uint32_t size) | Create and queue a socket Event. | Sets Event type 9, pointer +0x14, and size +0x18. |
Darkages.exe:0x0045BDC0 | win_main_window_proc | LRESULT __stdcall(HWND, UINT, WPARAM, LPARAM) | Main window procedure and Winsock notification bridge. | Message 0x0402 handles FD_READ and FD_CLOSE. |
Darkages.exe:0x004A32F0 | net_socket_ctor | void *__thiscall(void *this, void *event_sink) | Construct and initialize the Socket object. | Installs default key and transport buffers. |
Darkages.exe:0x004A3294 | net_socket_dtor | void __thiscall(void *socket_object) | Close transport handles and tear down the Socket worker. | Called by the deleting destructor owned by EventMan; worker teardown uses TerminateThread. |
Darkages.exe:0x004A3560 | net_queue_receive | void __thiscall(void *this) | Queue receive processing. | Posts Socket work code 4. |
Darkages.exe:0x004A3570 | net_c_queue_send | void __thiscall(void *this, const uint8_t *packet, int16_t length) | Copy and queue a logical client packet. | Adds the zero sentinel and posts work code 5. |
Darkages.exe:0x004A36C0 | net_queue_xor_key | void __thiscall(void *this, unsigned int key_length, const uint8_t *key) | Queue a replacement XOR key. | Copies the key and posts Socket work code 10; only called by the clear action 0x00 handler. |
Darkages.exe:0x004A3700 | net_queue_xor_table_function | void __thiscall(void *this, unsigned int function_index) | Queue an XOR-table function selector. | Posts Socket work code 11; only called by the clear action 0x00 handler. |
Darkages.exe:0x004A3770 | net_write_u32_be | void __cdecl(uint32_t value, uint8_t *dest) | Write a big-endian 32-bit field. | Writes a zero byte at dest[4]. |
Darkages.exe:0x004A37D0 | net_read_u8 | uint32_t __cdecl(const uint8_t *src) | Read an unsigned byte. | Used by server action handlers. |
Darkages.exe:0x004A3810 | net_read_u16_be | uint32_t __cdecl(const uint8_t *src) | Read a big-endian 16-bit field. | Used by framing-related and packet handlers. |
Darkages.exe:0x004A38B0 | net_read_u32_be | uint32_t __cdecl(const uint8_t *src) | Read a big-endian 32-bit field. | Returns the combined 32-bit value. |
Darkages.exe:0x004A3910 | net_handle_disconnect | void __thiscall(void *this) | Close transports and handle connection loss. | Called for FD_CLOSE. |
Darkages.exe:0x004A39C0 | net_poll_receive | void __thiscall(void *this) | Poll transport readiness outside the async window path. | Uses select for TCP and calls net_s_receive_frames. |
Darkages.exe:0x004A3A74 | net_s_receive_frames | void __thiscall(void *this) | Extract, decode, and post complete packets. | Calls net_read_transport_byte, net_s_decode_packet_body, and event_post_socket_bytes. |
Darkages.exe:0x004A44D4 | net_s_decode_packet_body | int __stdcall(const uint8_t *src, int src_size, uint8_t *dest) | Reverse the ordinary server-body XOR transformation. | Returns src_size - 1. |
Darkages.exe:0x004A4D34 | net_read_transport_byte | int __thiscall(void *this, uint8_t *out_byte) | Supply one cached transport byte. | Refills with recv or ReadFile. |
Darkages.exe:0x004A54D0 | net_process_work_item | void __thiscall(void *this, int code, void *data, int value) | Execute queued Socket work. | Codes 4, 5, 10, and 11 drive receive, send, key, and table operations. |
Darkages.exe:0x004A59F4 | net_connect_configured_server | void __thiscall(void *this) | Establish the configured connection. | Uses Winsock startup, resolution, connect, and WSAAsyncSelect. |
Darkages.exe:0x004A72B4 | net_c_send_packet_body | void __thiscall(void *this, const uint8_t *packet, int16_t length) | Transform, frame, and send a client packet. | Calls send once and frees the temporary frame. |
Darkages.exe:0x004A7940 | net_write_u8 | void __cdecl(uint32_t value, uint8_t *dest) | Write one byte. | Writes a zero byte at dest[1]. |
Darkages.exe:0x004A7990 | net_write_u16_be | void __cdecl(uint32_t value, uint8_t *dest) | Write a big-endian 16-bit field. | Used for the frame length and packet fields. |
Darkages.exe:0x004A79E4 | net_c_encode_packet_body | int __stdcall(const uint8_t *src, int src_size, uint8_t *dest) | Apply the ordinary client-body sequence and XOR transformation. | Returns src_size + 1. |
Darkages.exe:0x00498080 | util_ring_buffer_push_wait | void __thiscall(void *ring_buffer, const void *element) | Append a synchronized bounded-ring record. | Waits indefinitely on the not-full condition when the Socket queue reaches 128 records. |
Darkages.exe:0x004A8414 | net_set_xor_key | void __stdcall(int length, const uint8_t *key) | Replace the runtime XOR key. | Requires length at most 9 and writes four repeated copies. |
Darkages.exe:0x004A8590 | net_build_xor_table | void __stdcall(int function_index) | Generate the 256-entry XOR table. | Accepts function indexes 0 through 9. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | int __thiscall(void *this, void *event) | Dispatch in-game server actions in MapPane. | Supports 31 fixed action values. |
Darkages.exe:0x0046B574 | ui_map_handle_object_direction | int __thiscall(void *this, const uint8_t *packet) | Apply a server direction update to a map object. | Called by the MapPane action 0x11 branch; reads a big-endian object ID at +0x01 and direction at +0x05. |
Darkages.exe:0x0046CA54 | net_forward_embedded_client_packet | established MapPane action handler | Forward the client packet embedded in server action 0x4B. | Reads a big-endian length and calls net_c_queue_send. |
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | int __thiscall(void *this, void *event) | Handle main-menu server actions. | Supports 0x00, 0x02, 0x03, and 0x0A. |
Darkages.exe:0x004921F0 | ui_exit_wait_handle_server_packet | int __thiscall(void *this, void *event) | Handle server action 0x4C in the exit-wait pane. | Subtype 0x01 sends CQuit subtype 0x00 and displays the safe-exit text; no reconnect call is made. |
Darkages.exe:0x00492310 | ui_exit_wait_pane_ctor | established __thiscall constructor | Create the exit-wait pane. | Displays the wait warning and sends CQuit subtype 0x01. |
Darkages.exe:0x004A2ED0 | ui_server_select_handle_server_packet | int __thiscall(void *this, void *event) | Deserialize the server table from action 0x56. | Owns and replaces the per-server entry allocation. |
Darkages.exe:0x0040B3A0 | ui_handle_server_request_portrait | int __thiscall(void *this, void *event) | Handle kServerRequestPortrait, action 0x49. | Calls net_c_build_portrait_response. |
Darkages.exe:0x0040A664 | net_c_build_portrait_response | established __thiscall builder | Build client action 0x4F from a local portrait. | Called after server action 0x49. |
Darkages.exe:0x004C33B2 | recv | Winsock import | Refill the TCP receive cache. | Called with a maximum length of 0x18000. |
Darkages.exe:0x004C33D6 | send | Winsock import | Send a complete allocated frame. | The caller does not handle a short successful write. |
Darkages.exe:0x004C33D0 | WSAAsyncSelect | Winsock import | Register socket notifications with the main window. | Uses message 0x0402 and mask 0x21. |
Event Proxy Architecture
An Event Proxy can run inside the client and expose logical input, internal Events, decoded server packets, logical client packets, and selected runtime state to an external controller. It can provide most of the useful behavior of a network proxy without redirecting the TCP connection and without asking the controller to reproduce sequence, XOR, framing, or socket state.
This section is a development design based on confirmed Dark Ages 4.21 client boundaries. The client functions, object roots, ownership rules, and worker paths are established by static analysis. The injected DLL, IPC protocol, rule engine, and version resolver described here are proposed components and have not yet been implemented or validated by a controlled runtime experiment.
High-level operation
Design goals
The injected module should satisfy these requirements:
| Requirement | Design response |
|---|---|
| Observe all logical traffic | Hook decoded server packet posting, central internal Event dispatch, and logical client packet queueing. |
| Inject client-recognized events | Admit commands into bounded proxy queues and execute the established packet, input, or pane action from its owning client worker. |
| Send any logical client packet | Queue an action and payload for the Socket worker pump; it appends the sentinel and enters net_c_send_packet_body, which adds sequence, XOR transformation, frame, and transport send. |
| Block without an IPC round trip | Evaluate an immutable local ruleset in the hook and make the pass or block decision before telemetry is sent. |
| Continue when no controller exists | The initial and fallback policy is pass-through. Hook execution never waits for the pipe. |
| Survive controller exit and reconnect | The DLL owns a persistent named-pipe accept loop. A controller connection is a replaceable session, not the lifetime of the injected module. |
| Supply state after late attach | Produce a stable typed snapshot from static roots and active pane trees, then replay events newer than the snapshot boundary. |
“All events” means every event category the mapped client recognizes. Mouse Event types are 0 through 7, keyboard is type 8, and decoded server packets are type 9. Pane timers are a separate callback path and are not Event objects. Unknown Event type values reach an assertion path instead of a generic extension handler, so the public IPC API should reject unknown types.
Component layout
flowchart LR
Controller["External controller"] <-->|"duplex named pipe"| IPC["IPC service thread"]
subgraph Proxy["Injected Event Proxy DLL"]
IPC --> Commands["Bounded proxy command queues"]
Hooks["Hook adapters"] --> Rules["Local immutable rules"]
Hooks --> Telemetry["Bounded telemetry ring"]
Telemetry --> IPC
Commands --> SocketPump["Socket worker pump"]
Commands --> EventPump["EventMan worker pump"]
Commands --> DispatchPump["Dispatcher worker pump"]
Snapshot["Typed state snapshotter"] --> IPC
end
Win32["Win32 messages"] --> EventMan["EventMan worker"]
Receive["Socket receive and decode"] --> Inbound["event_post_socket_bytes"]
Inbound --> EventMan
EventMan --> Dispatch["event_dispatch"]
Dispatch --> Panes["Pane hierarchy"]
Builders["Client packet builders"] --> Outbound["net_c_queue_send"]
Outbound --> SocketWorker["Socket worker: sequence, XOR, frame, send"]
Hooks -. "observe or block" .-> Inbound
Hooks -. "observe or block" .-> Dispatch
Hooks -. "observe or block" .-> Outbound
SocketPump -. "worker-affine send" .-> SocketWorker
EventPump -. "worker-affine inject" .-> EventMan
DispatchPump -. "UI and snapshot commands" .-> Dispatch
Snapshot -. "read validated roots" .-> Panes
The hooks remain synchronous only for the local decision. Telemetry is copied into a bounded ring and delivered later by the IPC thread. Commands travel in the other direction through bounded proxy-owned queues. The target client worker drains each queue so IPC code does not call a live client object from an unrelated thread.
Threading model and pump placement
The named-pipe pump should run on a proxy-owned thread, but that thread should stop at framing, validation, ruleset compilation, command admission, and response serialization. It should not call net_c_queue_send, event_post_socket_bytes, pane methods, or timer methods.
The strongest 4.21 design has one bounded command queue for each client ownership domain:
| Queue | Execution context | Wake and pump point | Allowed work |
|---|---|---|---|
| Socket commands | Socket worker | Signal wait handle 0 at Socket +0x14; drain from the net_socket_vtable +0x10 callback around net_poll_receive | Logical client packet sends and Socket-owned queries. |
| EventMan commands | EventMan worker | Signal wait handle 0 at EventMan +0x14; replace or wrap event_manager_vtable +0x10, whose native target is event_manager_periodic_noop | Decoded server packet and normalized input injection. |
| Dispatcher commands | Dispatcher worker | Signal wait handle 0 at dispatcher +0x14; drain from a bounded event_dispatcher_tick hook | Pane methods, timer operations, snapshot barriers, and validated UI writes. |
This wake mechanism is supported by the generic worker loop. A wait-handle-0 wake first checks whether the native ring is empty. If it is empty, the loop skips the native pop and still invokes virtual slot +0x10. A proxy can therefore enqueue into its own ring, release the existing work semaphore once, and let the correct client worker enter the class-specific pump without adding a fake native work record.
Command admission and the matching ReleaseSemaphore call must run inside a proxy lifecycle read guard. Admission rechecks the active state after acquiring the guard, validates the live root, vtable, and wait handle, copies the command, and checks the wake result before leaving. The shutdown hook changes the state to draining and waits for these guards before the original client can close any worker handle. This closes the race where an IPC thread captures Socket or EventMan +0x14 immediately before destruction.
Each pump should call the original periodic method and then process a small fixed number of commands. The Socket and EventMan pumps can normally process one packet per wake. The dispatcher pump needs both an item limit and a short elapsed-time budget because its thread runs at THREAD_PRIORITY_TIME_CRITICAL and also owns deferred deletion and timers. None of the pumps should wait for pipe I/O or a controller response.
The client native queues are synchronized, so a simpler first implementation can use a separate proxy command-executor thread that calls net_c_queue_send and event_post_socket_bytes. This does not race the native ring indices. It is not the preferred final design because both wrappers can wait indefinitely when their 128-record queues are full, and a blocked foreign producer complicates Socket and EventMan destruction. Never make those calls on the named-pipe accept or read thread.
Hook layers
Three primary hooks cover different semantics:
event_post_socket_bytessees one decoded server packet before Event creation. It is the cleanest inbound packet filter because skipping the original call prevents allocation and pane delivery.event_dispatchsees one logical Event before hierarchy fan-out. It covers input and packets at a common point and preserves the dispatcher’s normal cleanup when a hook reports the Event consumed.net_c_queue_sendsees one native logical client packet before the sequence byte, XOR transformation, frame header, and Winsock call. It is the outbound observation point and a synchronized fallback injection adapter. The preferred controller injection path runs on the Socket worker and entersnet_c_send_packet_bodydirectly.
Hooking event_dispatch_hierarchy alone is not a replacement for the central Event hook. It is recursive, visits children before parents, translates mouse coordinates during traversal, and may be called more than once for one Event. It is useful for pane-specific diagnostics, but it produces duplicate observations and is a poor global blocking boundary.
Injection lifecycle
The DLL entry point should do only loader-safe work. Windows holds the loader lock while it calls DllMain, so starting IPC, resolving imports, installing hooks, or waiting for client initialization there can deadlock with other module loads. A small entry point can disable thread notifications and arrange for initialization after LoadLibrary returns. Injector-assisted invocation of an exported proxy_start function is the cleanest option. A separately created bootstrap thread is a practical fallback.
The proxy lifecycle should be explicit:
| Proxy state | Allowed behavior |
|---|---|
loaded | Record module handle and reject IPC work. |
resolving | Fingerprint the image, resolve a known profile, and validate every required symbol. |
passive | Run IPC and state reads, but do not install mutation-capable hooks when validation is incomplete. |
active | Hooks, local rules, telemetry, packet injection, and typed snapshots are available. |
draining | Reject new mutations, use pass-through rules, stop command execution, and remove hooks. |
detached | Close proxy-owned handles and leave no client callback pointing into the DLL. |
Early injection does not require guessing how long initialization takes. The bootstrap can validate the image immediately, then wait until event_dispatcher, event_manager_instance, and net_socket_instance are nonnull and have their expected vtables. Late injection follows the same validation and can enter active as soon as those roots are stable.
app_shutdown is the critical detach boundary. It destroys EventMan, whose destructor destroys its owned Socket and clears net_socket_instance, before the final dispatcher destruction. The proxy should intercept shutdown entry, enter draining, stop accepting mutating commands, atomically select a pass-through ruleset, wait for in-flight hook calls to leave, and remove hooks in reverse order. It must not depend on a client worker shutdown notification because the shared worker destructor uses TerminateThread rather than a cooperative stop and join.
Failure isolation and pass-through
The injected module is part of the game process, so its failure policy must favor the unmodified client path:
- A missing or unknown build profile disables hooks that can mutate state.
- No controller connection means pass-through unless an explicitly persistent local ruleset is installed.
- A full telemetry ring drops telemetry, increments a loss counter, and continues the client call.
- A malformed IPC message is rejected before it reaches a client function.
- A controller timeout never blocks a client worker.
- A hook reentry guard prevents a rule-generated packet from recursively generating the same rule action without a limit.
- Shutdown invalidates all cached client pointers before closing the pipe.
Session rules should be removed automatically when their controller disconnects. Persistent rules should require an explicit flag and remain local to the DLL across reconnects. This distinction preserves seamless default pass-through while still allowing a block rule to operate without live IPC.
Code-level flow
Activation path
- Fingerprint the loaded
Darkages.exeimage and select a version profile. - Resolve every required RVA to
loaded_module_base + rva. - Decode and validate the expected instructions, call relationships, globals, and vtables before changing code.
- Start the pipe accept loop and publish passive build and capability information.
- Wait for
event_dispatcher,event_manager_instance, andnet_socket_instanceto be live. - Install the inbound, Event, and outbound hooks. Install the Socket and EventMan worker pumps for packet mutation capabilities, and the dispatcher pump for pane, timer, write, or snapshot capabilities.
- Atomically change the capability state to active.
Hook installation should use one transaction where the hooking library supports it. If any required hook fails, roll back the complete set and remain passive. A partial set can create misleading ordering, such as allowing injected packets without observing the resulting outbound queue.
Normal packet and Event flow
decoded server packet
-> inbound local rules
-> block: return without event posting
-> pass or copy-on-write modify
-> event_post_socket_bytes
-> EventMan work code 0x0E
-> Event type 9
-> central Event rules
-> block: report consumed; dispatcher still frees payload
-> pass: event_dispatch hierarchy fan-out
native client builder
-> outbound local rules
-> block: return without queue allocation
-> pass or copy-on-write modify
-> net_c_queue_send
-> Socket work code 5
-> sequence, XOR, frame, send
proxy client command
-> bounded Socket command queue and worker wake
-> Socket periodic pump
-> outbound local rules and telemetry
-> net_c_send_packet_body
-> sequence, XOR, frame, send
The inbound and Event hooks can both observe a server packet. Telemetry therefore includes a phase field. A default subscription should publish decoded ingress at the first hook and suppress the duplicate type 9 record at the central Event hook unless the controller explicitly requests both phases.
Worker-affine packet pumps
For a client packet, the Socket pump validates the live root and vtable, checks the same transfer gate used by net_c_queue_send, applies outbound rules, appends the zero sentinel in proxy-owned storage, and calls net_c_send_packet_body with logical_length + 1. Static analysis shows that this function reads the input synchronously, applies the normal sequence, XOR, framing, and send path, and neither retains nor frees the input pointer. The proxy pump must emit the controller or local_rule telemetry itself because this route intentionally does not re-enter the net_c_queue_send hook.
For a server packet, the EventMan pump allocates logical_length + 1 through util_memory_manager_alloc, copies the logical bytes, appends zero, and calls event_process_work_item(event_manager, 0x0E, owned_packet, logical_length) on the EventMan worker. Code 0x0E transfers the packet into Event type 9 and the dispatcher later releases it through util_memory_manager_free. A DLL-heap pointer must not be passed into this ownership path.
Input injection belongs on the same EventMan pump. It can invoke the established worker-side work-code behavior with the same normalized scan code, message time, coordinates, or wheel delta that the native wrappers would have queued. This avoids self-enqueueing into EventMan’s bounded ring from its own consumer thread.
Direct pane operations and timer-list operations remain dispatcher-only. event_dispatcher_insert_timer edits the timer list without a visible lock. A timer injection command must run through the dispatcher pump and should target only an established receiver and callback identifier. All commands carry a connection generation and cancellation token so a disconnected controller cannot leave stale work queued.
The Socket worker processes at most one native record and then calls its periodic method. A proxy send performed there is therefore serialized with sequence and transport state, but it is not in a single chronological FIFO with the remaining native Socket queue. If strict native-versus-controller admission order is required, use net_c_queue_send from one dedicated executor and accept its blocking full-queue behavior, or implement a separately validated unified admission hook.
Shutdown path
- Enter
drainingatapp_shutdownentry, before the original function deletes EventMan and its owned Socket. - Reject new mutations, stop worker wake signals, cancel queued commands, and atomically select pass-through rules.
- Disconnect the current controller and stop the accept loop. Use overlapped pipe I/O or explicit cancellation so this step cannot wait indefinitely on a blocked pipe read or connect.
- Wait for IPC admission and wake guards, the Socket, EventMan, and dispatcher pumps, and ordinary hook active-call counters to reach zero while all three client workers are still alive.
- Restore worker vtable slots and remove the dispatcher, outbound, Event, and inbound hooks in reverse order.
- Clear cached roots and call the original
app_shutdown. The EventMan destructor then deletes the Socket, clears both roots, and tears down their workers. - Close proxy-owned handles and mark the module detached.
The proxy should normally remain loaded until process exit. Calling FreeLibrary while another client worker might still execute a trampoline or callback into the module is more dangerous than retaining a small inert DLL.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x004315B0 | event_dispatcher_tick | void __thiscall(void *event_dispatcher_object) | Run deferred deletion and one due timer. | Optional bounded proxy command-pump location because it runs on the dispatcher worker. |
Darkages.exe:0x00431B84 | event_dispatch | int __thiscall(void *event_dispatcher_object, void *event) | Dispatch one logical Event. | Primary central Event observation and block boundary before hierarchy fan-out. |
Darkages.exe:0x00431D54 | event_dispatch_hierarchy | int __thiscall(void *event_dispatcher_object, void *event, void *hierarchy) | Recursively deliver an Event to panes. | Useful for targeted diagnostics, not for one-record-per-Event telemetry. |
Darkages.exe:0x00432E50 | event_post_socket_bytes | void __thiscall(void *event_manager_object, const uint8_t *packet, int length) | Copy a decoded server packet and post work code 0x0E. | Primary decoded inbound packet hook and synchronized fallback injection adapter. |
Darkages.exe:0x00432F10 | event_manager_queue_event_copy | void __thiscall(void *event_manager_object, const void *event) | Copy a 36-byte Event and post EventMan work code 0x0F. | No native call sites are present in this build; usable only with a validated Event layout. |
Darkages.exe:0x00434080 | event_manager_periodic_noop | void __thiscall(void *event_manager_object) | Native no-op EventMan periodic callback. | event_manager_vtable +0x10; practical vtable-slot pump for EventMan-affine commands. |
Darkages.exe:0x0045B8F0 | app_shutdown | void __cdecl(void) | Tear down application subsystems. | Proxy draining and hook-removal boundary. |
Darkages.exe:0x0045CCA0 | app_initialize | void __cdecl(void) | Construct the dispatcher, EventMan, Socket, screen, and other subsystems. | Early-injection readiness boundary. |
Darkages.exe:0x004A3570 | net_c_queue_send | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Copy and queue one logical client packet. | Primary native outbound hook and synchronized native-queue fallback adapter; admission can block. |
Darkages.exe:0x004A39C0 | net_poll_receive | void __thiscall(void *socket_object) | Poll receive state from the Socket periodic worker callback. | net_socket_vtable +0x10; recommended bounded Socket command-pump hook. |
Darkages.exe:0x004A72B4 | net_c_send_packet_body | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Apply client transform and framing, then call send. | Socket-worker-affine packet adapter; input is read synchronously and is not retained or freed. |
Darkages.exe:0x004BF440 | util_thread_queue_post_async | void __thiscall(void *worker_object, int code, void *data, int value) | Append an asynchronous worker record and release the queue semaphore. | Does not copy data or wait for completion, but can block while the bounded ring is full. |
Event Proxy Hooks and Injection
The 4.21 client exposes logical boundaries on both sides of the internal Event system. Decoded server packets can be intercepted before Event creation, all pane-bound Events can be intercepted once before recursive delivery, and client packets can be intercepted before transport transformation. These are internal functions, so an injected module needs x86 inline detours or equivalent call-site instrumentation rather than Winsock import hooks.
This page distinguishes confirmed client behavior from recommended proxy behavior. Hook return values, copied-buffer ownership, queue codes, Event fields, and static roots are confirmed. Rule evaluation, telemetry, and IPC commands are proposed.
High-level operation
Primary hook matrix
| Phase | Function | Data visible to the hook | Recommended use |
|---|---|---|---|
| Decoded ingress | event_post_socket_bytes | [server action][payload], length excludes the local zero sentinel | Observe, block, or copy-on-write modify server packets before Event creation. |
| Central Event | event_dispatch | One 36-byte Event before capture-aware hierarchy routing | Observe or block mouse, keyboard, and socket Events once per logical Event. |
| Pane traversal | event_dispatch_hierarchy | Event plus the current packed hierarchy list | Optional targeted diagnostics where child and parent delivery order matters. |
| Logical egress | net_c_queue_send | [client action][payload], length excludes the sentinel and all wire metadata | Observe, block, or modify native client packets; optional native-queue injection adapter. |
| Socket pump | net_poll_receive | Socket worker context after a native wake | Bounded worker-affine client packet injection. |
| EventMan pump | event_manager_periodic_noop vtable slot | EventMan worker context after a native wake | Bounded server packet and normalized input injection. |
| Dispatcher tick | event_dispatcher_tick | Dispatcher worker context before or after timer processing | Bounded command pump for direct pane, timer, write, and snapshot operations. |
The first and fourth hooks avoid the protocol transformation entirely. Inbound data has already been decoded. Outbound data has not yet received its sequence byte, XOR passes, frame marker, or big-endian frame length.
Packet blocking and mutation
Blocking is simplest at the copied-input boundaries:
- An inbound block skips
event_post_socket_bytes. The receive framer has already consumed the frame, and no EventMan-owned copy exists yet. - An outbound block skips
net_c_queue_send. The producer retains its stack or object buffer, and the Socket queue has not allocated a copy. - A central Event block skips the original
event_dispatch, returns 1 as the consumed result, and does not free the Event or packet.event_dispatcher_process_work_itemstill performs normal socket-payload cleanup and deletes the queued Event after the hook returns.
Packet mutation should be copy-on-write. The proxy copies the logical packet into proxy-owned scratch storage, applies validated edits, and passes that copy to the original client function. Both packet-boundary functions make their own owned copy before returning, so the proxy scratch buffer can be released immediately afterward.
Do not replace Event +0x14 with memory allocated by the DLL’s C runtime. The dispatcher releases an owned socket packet through the client’s memory manager. Inbound packet replacement belongs at event_post_socket_bytes, where the client performs the allocation itself.
Client packet injection
A controller command for an arbitrary logical client packet should contain exactly:
[uint8 action] [payload bytes...]
It must not contain 0xAA, the two-byte frame length, a sequence byte, transformed bytes, or the trailing zero sentinel.
The recommended stable path queues the command in a bounded proxy-owned Socket queue, signals the Socket worker’s existing wait handle 0, and executes it from the Socket periodic callback. The worker-affine adapter checks the transfer gate, appends the sentinel, and calls:
net_c_send_packet_body(
net_socket_instance,
packet_with_sentinel,
logical_length + 1
);
This is the same Socket-worker function reached by native work code 5. It updates sequence and transformation state and performs normal framing and transport output. It reads the supplied packet synchronously and does not retain or free it, so the pump can release its proxy-owned command after the call.
The native-queue alternative calls net_c_queue_send(net_socket_instance, logical_packet, logical_length) from a dedicated proxy executor. The confirmed wrapper contract has several consequences:
logical_lengthincludes the action byte and must be positive.- The parameter is signed 16-bit. The function adds one for the sentinel before placing the value in another signed 16-bit work field, so the practical maximum input is 32,766 bytes.
- The function copies the packet and appends the sentinel before it returns.
- Socket work code 5 performs the later transformation and send.
- When Socket
transfer_gateat+0x780D2is 1, the function drops every action except0x10. - Its 128-record ring is protected by a monitor and not-full condition, so simultaneous native and proxy producers do not corrupt queue state.
- The push waits indefinitely when the ring is full. The wrapper is asynchronous with respect to completion, not nonblocking with respect to admission.
- The function returns no queue or transport success value. An IPC acknowledgement can mean “validated and admitted,” not “sent on TCP.” A later Socket-worker or
sendobservation would be required for delivery-attempt telemetry.
Before either adapter runs, validate that net_socket_instance is nonnull and its first pointer is net_socket_vtable. A useful command response distinguishes not_ready, not_connected, blocked_by_rule, blocked_by_transfer_gate, admitted, executed, and invalid_length.
Server packet injection
To make a logical server packet enter the client as though it had just been decoded, queue the command for the EventMan worker. The pump allocates logical_length + 1 through the client’s util_memory_manager_alloc, copies the packet, appends zero, and invokes the established worker case:
void *memory_manager;
uint8_t *client_owned_packet;
memory_manager = util_memory_manager_get_instance();
client_owned_packet = util_memory_manager_alloc(
memory_manager,
logical_length + 1
);
memcpy(client_owned_packet, logical_packet, logical_length);
client_owned_packet[logical_length] = 0;
event_process_work_item(
event_manager_instance,
0x0e,
client_owned_packet,
logical_length
);
The input begins with the server action and excludes the sentinel. The pump must validate a live MemoryMan root before allocation and free the new buffer itself if command execution stops before work code 0x0E accepts it. Once called, work code 0x0E creates Event type 9, routes it through event_dispatch, and transfers the packet to dispatcher cleanup after pane delivery. Allocation through the executable is required because the dispatcher later calls util_memory_manager_free.
The native-queue alternative calls event_post_socket_bytes(event_manager_instance, logical_packet, logical_length) from a dedicated proxy executor. That wrapper performs the same client allocation and posts EventMan code 0x0E. Its 128-record ring is synchronized for multiple producers but can block for capacity, so it must not run on the named-pipe thread or on the EventMan consumer itself.
This path preserves normal pane filtering. A packet is acted on only if an active registered pane claims its action. For example, injecting an in-game action while only MainMenuPane is registered does not bypass the UI state machine.
Input injection
Two input modes are useful:
| Mode | Mechanism | Semantics |
|---|---|---|
| Window-faithful input | PostMessageA(app_main_window, ...) | Runs the original window procedure, including software-cursor updates, scan-code extraction, EventMan input blocking, and message-specific quirks. |
| Internal input | Call event_queue_mouse_* or event_queue_key_* on event_manager_instance | Enters the existing EventMan queue directly but does not perform the window procedure’s cursor drawing and coordinate preprocessing. |
The complete handled WM_*, WM_USER + 2 Winsock notification, and application-defined WM_USER + 0x2046 behavior is indexed in Input and Windows Events. Window-faithful injection should be limited to messages whose parameter layout is established there.
For an internal mouse click at a new position, queue a mouse move first, then the button transition. Button work uses the current EventMan mouse coordinates. Queue order preserves the move-before-click relationship. Releases should normally be paired even when a press was blocked because the client deliberately allows button-up and key-up work while input blocking is active.
For the worker-affine architecture, the proxy command queue preserves the same move-before-click ordering and the EventMan pump invokes the corresponding event_process_work_item cases directly. Calling an input queue wrapper from the EventMan consumer can self-block if concurrent producers refill the native ring before the wrapper pushes.
Direct keyboard injection uses the client’s physical scan-code representation. Extended keys have 0x80 added to the base scan byte. Passing a virtual-key code instead produces the wrong mapping. The controller should either supply the normalized scan byte or request a window-faithful WM_KEYDOWN or WM_KEYUP with a correctly formed lParam.
Thread-affinity matrix
| Operation | Required execution context | Reason |
|---|---|---|
| Pipe accept, read, write, handshake | Proxy IPC thread | May block on IPC and must not own client state. |
| Rule compilation and subscription changes | Proxy IPC thread | Publishes immutable state atomically; no client call is needed. |
| Client packet injection | Socket worker pump | Serializes sequence, XOR, framing, and transport state. |
| Server packet and normalized input injection | EventMan worker pump | Preserves input state and packet ownership before dispatcher handoff. |
| Pane method, timer, typed UI write, snapshot barrier | Dispatcher worker pump | These structures have no established external-thread lock. |
| Hook telemetry publication | Calling client thread | Bounded copy only; never writes the pipe or waits. |
The proxy can keep a dedicated executor that calls native queue wrappers as a simpler implementation, but it is an admission adapter rather than the IPC pump. Use one executor, one in-flight client call, bounded proxy input, and an app_shutdown drain gate. The worker-affine pumps remove that foreign-thread lifetime edge and are the recommended production layout.
Generic Event injection
The Event object is 36 bytes and uses event_vtable. The mapped variants share these fields:
| Offset | Width | Event context | Meaning |
|---|---|---|---|
+0x00 | 4 | All | event_vtable pointer. |
+0x04 | 4 | All | unknown_04 base-object marker initialized by the common constructor. |
+0x08 | 4 | All | unknown_08. |
+0x0C | 1 | All | Type: mouse subtype 0 through 7, keyboard 8, socket packet 9. |
+0x10 | 4 | Mouse | X coordinate. |
+0x14 | 4 | Mouse | Y coordinate. |
+0x18 | 1 | Mouse | Modifier and button state byte. |
+0x1C | 4 | Wheel | Wheel steps after signed delta division by 120. |
+0x20 | 4 | Mouse | Message timestamp. |
+0x10 | 1 | Keyboard | Mapped internal key value. |
+0x11 | 1 | Keyboard | Active modifier byte. |
+0x14 | 4 | Keyboard | Message timestamp. |
+0x14 | 4 | Socket | Owned decoded-packet pointer. |
+0x18 | 4 | Socket | Positive decoded-packet size. |
event_manager_queue_event_copy allocates and shallow-copies all 36 bytes, then posts EventMan work code 0x0F. The EventMan worker forwards another copy to the dispatcher and deletes its first copy. This function has no native call sites in the analyzed build, although its implementation and worker case are complete.
Use this generic path only for a fully mapped non-owning Event layout. Native input queue functions are safer for mouse and keyboard because they also update EventMan state. event_post_socket_bytes is safer for socket packets because it establishes correct packet allocation and ownership. An IPC INJECT_EVENT_RAW command should therefore be disabled by default and restricted to a version profile with explicit field validation.
Timers and pane-specific actions
Pane timers do not become Events. event_dispatcher_tick removes a timer record and calls receiver vtable slot +0x44 with a callback identifier and two payload values. event_dispatcher_insert_timer edits the dispatcher’s timer list directly and has no visible lock.
A proxy timer command must execute on the dispatcher worker, validate that the receiver is still registered, and use only a callback contract established for that pane class. An arbitrary receiver, callback identifier, or payload can violate pane state assumptions. Packet-driven UI should normally be exercised by injecting its server packet, and user-driven UI should normally be exercised by input injection, rather than calling class handlers directly.
Code-level flow
Inbound hook flow
hook_event_post_socket_bytes(event_manager, packet, length)
-> validate length and read action packet[0]
-> evaluate local ingress rules
-> enqueue telemetry copy without waiting
-> block: return
-> modify: call original with scratch packet
-> pass: call original with original packet
-> copy length + 1
-> append zero
-> util_thread_queue_post_async(code 0x0E)
The hook must not retain the caller’s packet pointer. Normal TCP receive uses a persistent Socket decode buffer that is reused for later frames.
Central Event hook flow
event_dispatcher_process_work_item(code 3, event)
-> hook_event_dispatch(dispatcher, event)
-> classify event[0x0C]
-> evaluate local Event rules
-> enqueue bounded telemetry
-> block: return consumed
-> pass: call original event_dispatch
-> if socket Event, free event[0x14]
-> delete Event
Because cleanup belongs to the caller, the hook must never delete the Event. A telemetry record must copy all needed scalar fields and packet bytes before returning.
Outbound hook flow
native client packet builder
-> hook_net_c_queue_send(socket, packet, length)
-> validate action and signed length
-> evaluate local egress rules
-> enqueue telemetry copy without waiting
-> block: return
-> modify: call original with scratch packet
-> pass: call original with original packet
-> enforce transfer gate
-> allocate and copy length + 1
-> append zero
-> util_thread_queue_post_async(code 5)
-> net_process_work_item
-> net_c_send_packet_body
The hook should tag origin as client, controller, or local_rule. A thread-local reentry counter and a maximum rule-action depth prevent an injected or rule-generated packet from triggering an unbounded emission cycle. The packet should still be observable unless a subscription explicitly excludes non-client origins.
Worker-affine injected packets do not pass through net_c_queue_send. The Socket pump runs the same immutable outbound rules, publishes a logical-egress record with the command origin, appends the sentinel, and calls net_c_send_packet_body. This avoids recursive hook entry while retaining one telemetry model.
proxy Socket command
-> wake Socket wait handle 0
-> worker calls original net_poll_receive
-> Socket pump validates root, vtable, generation, and transfer gate
-> evaluate outbound rules and publish telemetry
-> net_c_send_packet_body(packet_with_sentinel, logical_length + 1)
Detour ABI requirements
These functions are x86 __thiscall. The object pointer arrives in ECX, while the remaining arguments are on the stack. A detour wrapper must preserve the nonvolatile registers, stack cleanup convention, and original ECX. The trampoline must relocate complete overwritten instructions and any relative branches or calls.
Before patching a target, compare its bytes with the selected build profile and decode enough whole instructions for the jump stub. Do not install a detour from a raw address match alone. The Version Profiles page defines the required semantic validation.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x00431200 | event_dispatcher_queue_event_copy | void __thiscall(void *event_dispatcher_object, const void *event) | Allocate and shallow-copy a 36-byte Event, then post dispatcher work code 3. | Native mouse, key, and socket Event producers use this queue. |
Darkages.exe:0x004315B0 | event_dispatcher_tick | void __thiscall(void *event_dispatcher_object) | Run periodic dispatcher work. | Optional bounded dispatcher-thread proxy command pump. |
Darkages.exe:0x004316E0 | event_dispatcher_process_work_item | void __thiscall(void *event_dispatcher_object, int code, void *data, int value) | Process dispatcher queue records. | Code 3 dispatches, frees an owned socket payload, and deletes the Event. |
Darkages.exe:0x00431B84 | event_dispatch | int __thiscall(void *event_dispatcher_object, void *event) | Apply capture handling and dispatch one Event. | Primary one-record-per-Event hook. |
Darkages.exe:0x00431D54 | event_dispatch_hierarchy | int __thiscall(void *event_dispatcher_object, void *event, void *hierarchy) | Recursively call pane input or socket handlers. | Child lists are visited before their current pane. |
Darkages.exe:0x00432BD0 | event_queue_mouse_move | void __thiscall(void *event_manager_object, int mouse_y, int mouse_x, uint32_t message_time) | Queue internal mouse movement. | Work code 4; updates EventMan coordinates when processed. |
Darkages.exe:0x00432C50 | event_queue_left_button_down | void __thiscall(void *event_manager_object, uint32_t message_time) | Queue left-button press. | Work code 5; uses current EventMan coordinates. |
Darkages.exe:0x00432C90 | event_queue_left_button_up | void __thiscall(void *event_manager_object, uint32_t message_time) | Queue left-button release. | Work code 6; not suppressed by input blocking. |
Darkages.exe:0x00432CC0 | event_queue_right_button_down | void __thiscall(void *event_manager_object, uint32_t message_time) | Queue right-button press. | Work code 7. |
Darkages.exe:0x00432D00 | event_queue_right_button_up | void __thiscall(void *event_manager_object, uint32_t message_time) | Queue right-button release. | Work code 8; not suppressed by input blocking. |
Darkages.exe:0x00432D30 | event_queue_mouse_wheel | void __thiscall(void *event_manager_object, int wheel_delta, uint32_t message_time) | Queue signed wheel input. | Work code 9; worker divides by 120. |
Darkages.exe:0x00432D60 | event_queue_key_down | void __thiscall(void *event_manager_object, uint8_t scan_code, uint32_t message_time) | Queue normalized physical key press. | Work code 0x0A. |
Darkages.exe:0x00432D90 | event_queue_key_up | void __thiscall(void *event_manager_object, uint8_t scan_code, uint32_t message_time) | Queue normalized physical key release. | Work code 0x0B; updates state without a pane Event. |
Darkages.exe:0x00432E50 | event_post_socket_bytes | void __thiscall(void *event_manager_object, const uint8_t *packet, int length) | Copy and queue a decoded server packet. | Work code 0x0E; preferred native-ingress block boundary and synchronized fallback injection adapter. |
Darkages.exe:0x00432F10 | event_manager_queue_event_copy | void __thiscall(void *event_manager_object, const void *event) | Copy and queue a raw Event through EventMan. | Work code 0x0F; no native call sites in this build. |
Darkages.exe:0x00433110 | event_process_work_item | void __thiscall(void *event_manager_object, int code, void *data, int value) | Convert EventMan records into state updates or Events. | Codes 4 through 0x0B, 0x0E, and 0x0F are relevant to injection. |
Darkages.exe:0x00433DC4 | event_queue_socket_packet | void __stdcall(uint8_t *packet, uint32_t size) | Build Event type 9 and submit it to the dispatcher. | Transfers packet ownership to dispatcher cleanup. |
Darkages.exe:0x00434080 | event_manager_periodic_noop | void __thiscall(void *event_manager_object) | Native EventMan periodic callback. | Vtable slot +0x10; practical worker-affine command-pump slot. |
Darkages.exe:0x0045BDC0 | win_main_window_proc | LRESULT __stdcall(HWND, UINT, WPARAM, LPARAM) | Convert handled Win32 input and socket notifications. | Window-faithful input injection uses PostMessageA to this procedure’s window. |
Darkages.exe:0x00470F80 | util_memory_manager_get_instance | void *__cdecl(void) | Return the client MemoryMan singleton. | Required before allocating an owned server-injection buffer. |
Darkages.exe:0x00470FC0 | util_memory_manager_alloc | void *__thiscall(void *memory_manager, int size) | Allocate through the executable’s CRT wrapper. | Use for buffers that dispatcher cleanup will own. |
Darkages.exe:0x004A3570 | net_c_queue_send | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Copy and queue a logical client packet. | Work code 5; transfer gate permits only 0x10 during transfer. |
Darkages.exe:0x004A39C0 | net_poll_receive | void __thiscall(void *socket_object) | Poll Socket receive state from periodic worker execution. | Vtable slot +0x10; recommended Socket command-pump hook. |
Darkages.exe:0x004A54D0 | net_process_work_item | void __thiscall(void *socket_object, int code, void *data, int value) | Execute Socket queue records. | Code 5 calls net_c_send_packet_body and frees the queued copy. |
Darkages.exe:0x004A72B4 | net_c_send_packet_body | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Transform, frame, and call the configured transport. | Socket-worker adapter; reads but does not retain or free the input. |
Darkages.exe:0x004BF440 | util_thread_queue_post_async | void __thiscall(void *worker_object, int code, void *data, int value) | Post one raw asynchronous work record. | Does not copy data or wait for completion, but can block while the native ring is full. |
Event Proxy IPC, Rules, and State
The Event Proxy should keep client hooks independent from the external controller. A duplex IPC service carries commands and telemetry, an in-process ruleset makes immediate decisions, and a typed snapshot service supplies current state after late attach or reconnect.
Everything on this page is a proposed proxy protocol built around confirmed 4.21 client roots and object layouts. It does not describe an IPC facility already present in the game.
High-level operation
Pipe ownership and reconnect
The injected DLL should be the named-pipe server. A practical per-process name is:
\\.\pipe\darkages-event-proxy-{process_id}
The pipe should reject remote-machine clients and grant access only to the current interactive user unless the operator explicitly configures a broader security descriptor. One connection owns mutation privileges. Additional read-only monitor connections can be added later, but they complicate rule ownership and backpressure and are not required for the first implementation.
The accept loop belongs to the DLL and survives client-controller failure:
- Create a pipe instance and wait for a controller.
- Complete a versioned handshake and allocate a new connection generation.
- Apply subscriptions and session rules only after validation.
- On clean close, broken pipe, or controller process death, remove session rules and cancel that generation’s pending commands.
- Return immediately to the accept state. Do not unload the DLL or remove the pass-through hooks.
Persistent rules are separate from session rules. They survive pipe disconnect and must be installed with an explicit persistence flag. With no rules, no controller, or an IPC failure, the proxy passes client traffic unchanged.
Command execution and worker wakes
The pipe service and client command execution are separate concerns. The IPC thread parses and validates a command, copies it into a bounded proxy-owned queue, records the current connection generation, and signals the owning client worker. It never invokes a live client object directly.
| Proxy queue | Consumer | Wake mechanism | Typical commands |
|---|---|---|---|
| Socket | Socket worker periodic pump | ReleaseSemaphore(Socket->wait_handles[0], 1, NULL) | SEND_CLIENT_PACKET and Socket-owned queries. |
| EventMan | EventMan worker periodic pump | ReleaseSemaphore(EventMan->wait_handles[0], 1, NULL) | INJECT_SERVER_PACKET and normalized INJECT_INPUT. |
| Dispatcher | Dispatcher worker periodic pump | ReleaseSemaphore(Dispatcher->wait_handles[0], 1, NULL) | Snapshot barriers, pane actions, timers, and typed UI writes. |
The generic worker explicitly tolerates a work-semaphore wake while its native ring is empty and still calls virtual slot +0x10. This lets the DLL wake the correct worker without adding an unknown native work code. The Socket slot already calls net_poll_receive, the EventMan slot is a no-op, and the dispatcher slot calls event_dispatcher_tick.
Admission holds a proxy lifecycle read guard across the final state check, root and vtable validation, queue copy, and ReleaseSemaphore. A failed wake marks the command canceled and reports wake_failed; it must not be reported as admitted. At app_shutdown entry, the DLL changes to draining, rejects new guards, and waits for existing admission guards before the client closes worker handles.
All three proxy queues must be bounded. Admission fails immediately with proxy_queue_full; it must not wait for capacity while holding an IPC session lock. A successful command has two distinct milestones:
admittedmeans the command was copied into the proxy queue and the worker was signaled.executedmeans the target pump validated the generation and live root and invoked the client-side action.
Neither status means that TCP delivered a client packet or that a pane accepted an injected server packet. Completion records should carry the original request identifier and may arrive asynchronously after the admission response.
The 4.21 native rings are also synchronized for multiple producers, so a dedicated fallback executor may call the copied-input wrappers safely. Their full-queue wait is infinite, however. This fallback must use one executor, never the pipe thread, and must stop before EventMan and Socket teardown. The worker-affine design avoids a foreign producer being asleep inside a native queue when app_shutdown destroys it.
IPC framing
A small fixed header lets either side skip unknown message types and correlate requests with replies. All proposed IPC integers are little-endian because this is a local Win32 protocol, not the game’s network byte order.
| Offset | Width | Field | Meaning |
|---|---|---|---|
0x00 | 4 | magic | ASCII DAEP. |
0x04 | 2 | protocol_version | IPC schema version. |
0x06 | 2 | header_size | 0x28 for this header. |
0x08 | 2 | message_type | Command, response, telemetry, or control type. |
0x0A | 2 | flags | Type-specific flags. Unknown required flags reject the message. |
0x0C | 8 | connection_generation | Changes on every successful controller handshake. |
0x14 | 8 | request_id | Nonzero on a command and copied to its response. |
0x1C | 8 | sequence | Monotonic telemetry or replay sequence, zero when not applicable. |
0x24 | 4 | payload_length | Bytes after the header. |
The DLL must reject an unsupported version, a header smaller than the required fields, integer overflow in header_size + payload_length, and payloads above a configured limit before allocating memory.
Message families
| Message | Direction | Purpose |
|---|---|---|
HELLO / HELLO_REPLY | Both | Negotiate IPC version, build profile, capabilities, controller role, and connection generation. |
SUBSCRIBE | Controller to DLL | Select phases, directions, actions, Event types, origins, and payload inclusion. |
EVENT_RECORD | DLL to controller | Report decoded ingress, central Event dispatch, logical egress, rule decisions, or loss markers. |
RULESET_REPLACE | Controller to DLL | Compile and atomically replace session or persistent rules. |
SEND_CLIENT_PACKET | Controller to DLL | Admit [client action][payload] to the Socket proxy queue for worker-affine sending. |
INJECT_SERVER_PACKET | Controller to DLL | Admit [server action][payload] to the EventMan proxy queue for worker-affine Event creation. |
INJECT_INPUT | Controller to DLL | Post Win32-faithful input or use an established EventMan input queue. |
SNAPSHOT_REQUEST / SNAPSHOT_RESPONSE | Both | Return typed current state with a dispatch sequence boundary. |
PEEK_REQUEST / PEEK_RESPONSE | Both | Read a bounded validated memory region or pointer path. |
POKE_REQUEST / POKE_RESPONSE | Both | Perform an explicitly enabled, width-checked, compare-before-write mutation. |
PING / PONG | Both | Detect a dead peer without coupling hook execution to liveness. |
GAP | DLL to controller | Report telemetry loss or a replay request older than the retained ring. |
A packet command response reports local admission or execution only. The Socket and Winsock send paths do not provide delivery confirmation, so the IPC protocol must not describe either status as network delivery.
Nonblocking telemetry
Hooks must not write to a pipe, wait on a controller, perform name resolution, or take a lock that the IPC thread can hold. Each hook should create a compact record in a bounded multi-producer queue. The IPC thread drains records, applies subscription payload policy, and writes them to the pipe.
Every record should include:
- global sequence number;
- phase such as
decoded_ingress,event_pre_dispatch, orlogical_egress; - origin such as
client,controller, orlocal_rule; - process and thread identifier;
- high-resolution proxy timestamp;
- client message timestamp when the Event contains one;
- Event type or directional packet action;
- rule generation and matched rule identifier;
- original and modified lengths where applicable;
- copied logical payload when subscribed.
When the queue is full, increment a loss counter and continue the original client call. The next successfully delivered record includes the accumulated loss count, and the IPC thread also emits GAP. Blocking the game to preserve telemetry defeats the pass-through requirement.
Local rules
The controller compiles policy in the DLL once and the hooks evaluate it without an IPC round trip. A useful rule model contains:
| Rule field | Examples |
|---|---|
| Scope | Decoded ingress, central Event, logical egress. |
| Origin mask | Native client, controller injection, local-rule emission. |
| Primary discriminator | Event type, server action, or client action. |
| Payload predicate | Minimum length, byte equality, masked byte equality, bounded integer field. |
| State predicate | Active client phase or validated pane-class presence. Keep state reads small and profile-backed. |
| Action | Pass, block, mirror, bounded byte patch, or enqueue a validated packet template. |
| Lifetime | Session or persistent. |
| Priority and terminal flag | Deterministic ordering and whether later rules may run. |
The hot representation should be immutable. Compile a replacement table on the IPC thread, validate every offset and action, then swap one pointer atomically. The old table is retired only after all hooks that acquired it have exited. This can use a small active-reader counter or an epoch scheme.
Rules must not allocate, parse regular expressions, or perform unbounded string work inside a hook. Payload predicates need a proven minimum length before every read. A patch must preserve the configured maximum length and should create a scratch copy rather than edit the client’s source buffer.
Rule-generated packets go to a proxy command queue rather than calling back into a client function while a hook is midway through its own decision. The generated command carries origin local_rule, a parent sequence, and a depth. A fixed maximum depth prevents recursive rules from emitting forever.
Late-connect state synchronization
The controller needs a baseline before it can interpret live deltas. A useful reconnect protocol is:
- Establish the connection and begin retaining telemetry for the new generation.
- Request a dispatcher-bound snapshot barrier.
- At a bounded point between dispatcher work items, record
last_applied_event_sequenceand copy the small typed state roots. - Send the snapshot with its sequence and state epoch.
- Send retained records newer than that sequence.
- If the retention ring overflowed, send
GAPand require a new snapshot.
The central Event hook can update last_applied_event_sequence after the original dispatch returns. Most pane state changes caused by input and server packets occur during that call. A compact snapshot taken from the dispatcher worker can therefore describe state after a known Event. The capture must still validate pointers and vtables because rendering, window processing, and teardown involve other threads.
Large or not-yet-mapped world state should not be copied without a time budget on the time-critical dispatcher worker. A later implementation can maintain a proxy-owned shadow model from decoded packets or divide large snapshots into stable chunks. The first version should favor small typed UI and character-pane state.
Runtime state and memory access
Static roots
All addresses below are for the analyzed image base 0x00400000. A profile and external controller must use loaded_module_base + rva.
| VA | RVA | Current IDA name | State reached |
|---|---|---|---|
Darkages.exe:0x004E2D70 | 0x000E2D70 | ui_bulletin_session | Active bulletin or mail session, child history, active child, and request-wait state. |
Darkages.exe:0x004E32A4 | 0x000E32A4 | ui_equip_pane | Persistent equipment identifiers, names, self-look fields, and legend child. |
Darkages.exe:0x004E32C0 | 0x000E32C0 | event_manager_instance | EventMan input state and owned Socket pointer at object +0x68. |
Darkages.exe:0x004E3564 | 0x000E3564 | ui_users_dialog_pane | Reusable users dialog and its owned row/list objects. |
Darkages.exe:0x004F51AC | 0x000F51AC | ui_main_menu_pane | Current main-menu pane, or null outside that phase. |
Darkages.exe:0x004F51B0 | 0x000F51B0 | ui_map_pane | Current in-game MapPane root. World collections inside it are not yet mapped. |
Darkages.exe:0x004F51B4 | 0x000F51B4 | ui_background_pane | Current in-game background pane. |
Darkages.exe:0x004F51BC | 0x000F51BC | net_socket_instance | Active Socket object, transport state, framing buffers, connection flag, and transfer gate. |
Darkages.exe:0x004F51C8 | 0x000F51C8 | ui_screen_pane | Active screen pane and software cursor state. |
Darkages.exe:0x004F51CC | 0x000F51CC | ui_screen_registry | Packed Screen composition tree. |
Darkages.exe:0x004F51D0 | 0x000F51D0 | event_dispatcher | Packed Event pane tree, capture pane, current timer state, and dispatcher worker. |
Darkages.exe:0x004F51DC | 0x000F51DC | app_main_window | Main HWND used for window-faithful input. |
Darkages.exe:0x004FA160 | 0x000FA160 | util_memory_manager_instance | Client allocator root required when a worker-injected server packet transfers to dispatcher-owned cleanup. |
Darkages.exe:0x004FD640 | 0x000FD640 | ui_terminal_pane | Active terminal pane. |
The complete Screen and Event tree algorithms and current class fields are in Runtime UI Memory Map.
Current typed snapshot coverage
| State family | Current confidence and coverage |
|---|---|
| Active pane tree and UI state | High. Screen and Event trees, common bounds, relative origin, visibility, and known pane vtables are mapped. |
| Equipment and self-look | High for the documented fields, 13 equipment slots, item names, appearance bytes, and bounded strings. |
| Skills and spells | High for both 36-pointer parent arrays, populated slot objects, names, identifiers, type, slot, and cooldown state. |
| GameButtons content | High. Current content and persistent chat, status, equipment, skill, and spell pane pointers are mapped. |
| Bulletin and mail | High for session state and active child ownership. Child-class field schemas vary and are only partly mapped. |
| Users dialog | Partial. Pane visibility and owned list objects are mapped; row record internals are not. |
| Character statistics and ordinary item inventory | Not yet mapped to the publication threshold. The relevant panes can be found, but typed fields require another reversing pass. |
| Map tiles, characters, monsters, and world objects | Not yet mapped to the publication threshold beyond the ui_map_pane root and packet handlers. |
| Network transport | High for Socket root, connection state, frame state, buffers, transfer gate, sequence, key, and XOR table. |
The proxy protocol should expose capability bits per snapshot family. It must not return guessed fields just because an object can be reached. A raw peek can still return bytes from a validated address, but the response should label them untyped.
Pointer-path reads
IPC requests should avoid controller-supplied absolute addresses where a stable descriptor is possible. A pointer path can contain:
- a named profile root or module RVA;
- whether to dereference it;
- a bounded signed offset;
- an expected vtable or readable region class;
- another dereference step if required;
- final byte count and scalar or string interpretation.
For example, a typed path can start at ui_equip_pane, dereference the root, validate ui_equip_pane_vtable, then read the item identifier expression for a checked slot. GameButtons is reached by scanning a stable Screen or Event tree snapshot for its vtable, then following the mapped content fields.
Before every dereference, validate the memory range and overflow, and confirm class vtables where available. After copying class-specific state, re-read both the root that led to it and its vtable. Discard and retry if either changed. Packed Screen nodes use stride 0x3F and Event nodes use 0x0F; they must be parsed with unaligned-safe reads rather than native structure casts.
Peek and poke policy
PEEK_REQUEST can support a named pointer path and an explicitly enabled raw-address mode. Bound each request, validate every page as readable, and return the resolved path, bytes, and profile identity. A raw address is meaningful only for the current process and build.
POKE_REQUEST should be disabled by default. A safe typed write includes:
- named field and exact width;
- expected root and vtable;
- expected old bytes for compare-before-write;
- allowed value range or bit mask;
- execution context, normally the dispatcher command pump for pane state;
- a response containing whether the compare and write succeeded.
Do not permit ordinary writes to vtables, function pointers, packed-list metadata, owned pointers, executable pages, or object lifetime fields. Writing a Pane visibility byte alone is not a valid show or hide operation because Screen composition, Event eligibility, capture, timers, and owner pointers must remain consistent. Prefer an established UI method, input event, or server packet for stateful changes. An explicit unsafe developer mode may offer raw writes, but it should be clearly separate from the typed API and never enabled by a controller handshake alone.
Code-level flow
Hot-hook rule evaluation
hook entry
-> increment active hook count
-> acquire immutable rule pointer
-> classify fixed fields and validate payload bounds
-> evaluate rules in priority order
-> try to append telemetry record
-> release rule pointer
-> pass, block, or call original with a scratch copy
-> decrement active hook count
The IPC thread performs ruleset parsing and compilation. It does not hold the active rules pointer while waiting on pipe I/O. A ruleset replacement response is sent only after the new table has been validated and published.
Stable typed snapshot
controller snapshot request
-> proxy queues dispatcher barrier command
-> signal dispatcher wait handle 0
-> dispatcher tick pump drains bounded command
-> record last applied Event sequence
-> read roots and expected vtables
-> copy packed registry metadata and nodes
-> copy mapped class-specific fields
-> re-read roots, metadata, and vtables
-> retry or mark one family unstable
-> IPC thread serializes snapshot
-> replay retained telemetry newer than snapshot sequence
One unstable family should not invalidate unrelated stable families. The response can mark equipment stable while reporting that a bulletin child changed during capture.
Controller disconnect
pipe read or write reports disconnect
-> invalidate connection generation
-> cancel pending commands from that generation
-> atomically remove session rules
-> retain explicitly persistent rules
-> release queued controller responses
-> create the next pipe instance
-> continue client pass-through
No hook waits for this cleanup. A command executor checks the generation again immediately before any mutation so a queued command cannot run after its owner disconnected.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x004315B0 | event_dispatcher_tick | void __thiscall(void *event_dispatcher_object) | Periodic dispatcher-worker entry. | Proposed bounded snapshot barrier and pane-command pump location. |
Darkages.exe:0x00431B84 | event_dispatch | int __thiscall(void *event_dispatcher_object, void *event) | Deliver one logical Event. | Proxy can establish an applied Event sequence around the original call. |
Darkages.exe:0x00432E50 | event_post_socket_bytes | void __thiscall(void *event_manager_object, const uint8_t *packet, int length) | Copy decoded ingress into EventMan. | Native ingress hook and synchronized, blocking fallback for INJECT_SERVER_PACKET. |
Darkages.exe:0x00434080 | event_manager_periodic_noop | void __thiscall(void *event_manager_object) | EventMan periodic worker callback. | Native no-op vtable slot used by the proposed EventMan command pump. |
Darkages.exe:0x0043CF20 | ui_game_buttons_handle_key_event | int __thiscall(void *game_buttons_pane, void *event) | Select one persistent game content pane from keyboard input. | Evidence that input injection should prefer normal Event routing over pointer writes. |
Darkages.exe:0x0043D164 | ui_game_buttons_select_content | void __thiscall(void *game_buttons_pane, int8_t index) | Hide the old persistent content pane and show the selected one. | Candidate typed action only when the pane and thread context are validated. |
Darkages.exe:0x004594A0 | ui_hierarchy_get_node | void *__thiscall(void *hierarchy, int index) | Compute a packed node address from list stride and index. | Confirms unaligned Screen and Event node traversal. |
Darkages.exe:0x00492C40 | ui_pane_show | void __thiscall(void *pane) | Show and invalidate a Pane. | Prefer this mapped method over writing only the common visibility byte. |
Darkages.exe:0x00492D50 | ui_pane_hide | void __thiscall(void *pane) | Hide and invalidate a Pane. | Must still be coordinated with owning-pane selection state. |
Darkages.exe:0x004A3570 | net_c_queue_send | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Copy and queue logical client egress. | Native egress hook and synchronized, blocking fallback for SEND_CLIENT_PACKET; returns no send result. |
Darkages.exe:0x004A39C0 | net_poll_receive | void __thiscall(void *socket_object) | Socket periodic worker callback. | Recommended Socket command-pump detour; call the original before a bounded proxy drain. |
Darkages.exe:0x004BF440 | util_thread_queue_post_async | void __thiscall(void *worker_object, int code, void *data, int value) | Append one asynchronous worker record. | Native ring access is synchronized but can block for capacity; raw use does not establish data ownership. |
Event Proxy Version Profiles
An Event Proxy should treat every executable build as a profile, even when later clients descend from the same source tree. Function signatures can relocate well-understood symbols across nearby builds, but they cannot by themselves prove object field offsets, allocation sizes, ownership, or calling conventions.
The recommended strategy for 5.x and 7.x is to reverse one representative executable from each build family, establish the same lifecycle, Event, Socket, pane, and state roots, and generate signatures from that ground truth. Signature remapping can then handle close patch revisions. It should not replace the first semantic analysis of a major client family.
High-level operation
Exact profiles first
The resolver should fingerprint the loaded image before it installs any hook. The strongest identity is a hash of the file or its mapped immutable sections. PE timestamp, image size, section sizes, and version resources are useful secondary properties but are not unique enough alone.
The analyzed sample has SHA-256 36093dc1572521ea8c4c5f25548068c971a3d41c7722ef9fe0c05c94e24d6979. Its project identification is 4.21 Stone, while its Windows version resource reports 360, 0, 0, 0; the mismatch is documented in Analyzed Binary.
For an exact match, the profile supplies RVAs, expected instructions, vtables, globals, calling conventions, object fields, and capability-specific validators. The runtime address is always:
loaded_module_base + profile_rva
Do not store a process-specific absolute address in controller configuration.
Dark Ages 4.21 core profile
| Symbol | VA | RVA | Profile role |
|---|---|---|---|
app_shutdown | Darkages.exe:0x0045B8F0 | 0x0005B8F0 | Draining and detach boundary. |
app_initialize | Darkages.exe:0x0045CCA0 | 0x0005CCA0 | Early-injection readiness boundary. |
event_dispatcher_queue_event_copy | Darkages.exe:0x00431200 | 0x00031200 | Copy a 36-byte Event and post dispatcher work code 3. |
event_dispatcher_tick | Darkages.exe:0x004315B0 | 0x000315B0 | Dispatcher-thread command pump. |
event_dispatch | Darkages.exe:0x00431B84 | 0x00031B84 | Central Event hook. |
event_dispatch_hierarchy | Darkages.exe:0x00431D54 | 0x00031D54 | Recursive pane delivery validator. |
event_post_socket_bytes | Darkages.exe:0x00432E50 | 0x00032E50 | Decoded server packet hook and injection. |
event_manager_queue_event_copy | Darkages.exe:0x00432F10 | 0x00032F10 | Optional raw Event injection through EventMan. |
event_process_work_item | Darkages.exe:0x00433110 | 0x00033110 | EventMan worker-side packet and input adapter. |
event_manager_periodic_noop | Darkages.exe:0x00434080 | 0x00034080 | EventMan worker command-pump slot. |
util_memory_manager_alloc | Darkages.exe:0x00470FC0 | 0x00070FC0 | Client-owned inbound packet allocation. |
net_c_queue_send | Darkages.exe:0x004A3570 | 0x000A3570 | Logical client packet hook and native-queue fallback. |
net_poll_receive | Darkages.exe:0x004A39C0 | 0x000A39C0 | Socket periodic callback and command-pump hook. |
net_process_work_item | Darkages.exe:0x004A54D0 | 0x000A54D0 | Socket work-code validator. |
net_c_send_packet_body | Darkages.exe:0x004A72B4 | 0x000A72B4 | Transform, frame, and send validator. |
event_manager_instance | Darkages.exe:0x004E32C0 | 0x000E32C0 | EventMan static root. |
net_socket_instance | Darkages.exe:0x004F51BC | 0x000F51BC | Socket static root. |
ui_screen_registry | Darkages.exe:0x004F51CC | 0x000F51CC | Screen tree static root. |
event_dispatcher | Darkages.exe:0x004F51D0 | 0x000F51D0 | Dispatcher static root. |
app_main_window | Darkages.exe:0x004F51DC | 0x000F51DC | Main HWND root. |
util_memory_manager_instance | Darkages.exe:0x004FA160 | 0x000FA160 | Client allocator static root. |
event_dispatcher_vtable | Darkages.exe:0x0050D180 | 0x0010D180 | Dispatcher class validator. |
event_manager_vtable | Darkages.exe:0x0050D720 | 0x0010D720 | EventMan class validator. |
event_vtable | Darkages.exe:0x0050D740 | 0x0010D740 | Raw Event object validator. |
net_socket_vtable | Darkages.exe:0x00526940 | 0x00126940 | Socket class validator. |
This table is the core hook profile, not the complete memory profile. UI vtables, roots, and fields remain in Runtime UI Memory Map and Pane Virtual Table Inventory.
What a profile must contain
A useful profile contains more than symbol addresses:
| Profile section | Required information |
|---|---|
| Image identity | Hash, section layout, preferred base, version metadata, and immutable-section hashes. |
| Functions | RVA, prototype, calling convention, minimum detour length, expected normalized instructions, and semantic validator identifier. |
| Globals | RVA, type, lifecycle, nullable phases, expected publisher and clearer functions. |
| Vtables | RVA, slot count used by the proxy, required slot targets, and class name confidence. |
| Object layouts | Size where established, field offsets and widths, ownership, discriminator, valid ranges, worker wait handle 0, and native ring pointer. |
| Packet and Event layouts | Direction, type or action field, payload representation, owned pointers, and cleanup function. |
| Capabilities | Required symbols and validators for each hook, command, snapshot family, peek path, or typed poke. |
A controller should receive the active profile identifier during HELLO_REPLY and repeat it in diagnostic logs and captured metadata. This prevents a memory path from one build being silently applied to another.
Signature scanning
A function signature is useful only when it survives relocations and still has enough structure to be unique. Generate signatures from decoded instructions, not by copying a long unexamined byte range.
The scanner should:
- Restrict the search to the expected PE section and executable or read-only data class.
- Wildcard absolute image addresses, import pointers, relative-call displacements, jump-table addresses, and other relocation-sensitive operands.
- Preserve stable opcodes, constants, stack cleanup, field displacements, queue codes, and branch relationships when they are part of the established contract.
- Require a unique candidate before semantic validation.
- Decode the candidate and verify its control flow, callees, globals, object fields, and ownership behavior.
- Resolve related symbols as a group and verify their cross-references.
A generic function prologue such as register saves and stack allocation is not a signature. A source filename or nearby assertion string is a useful anchor but is not proof. The final candidate must perform the mapped data flow.
Semantic validators for the core hooks
| Symbol | Required behavior to validate in an unknown build |
|---|---|
event_post_socket_bytes | Reject nonpositive length, allocate length + 1, copy bytes, append zero, and post EventMan work code 0x0E. |
event_dispatch | Read the dispatcher’s pane hierarchy, honor capture state, classify mouse, keyboard, and socket Events, and call the recursive hierarchy function. |
event_dispatcher_queue_event_copy | Allocate the established Event size, shallow-copy the complete Event, and post dispatcher work code 3. |
net_c_queue_send | Accept a pointer and signed length, enforce the transfer exception for action 0x10, allocate length + 1, append zero, and post Socket work code 5. |
net_process_work_item | Route code 5 to the logical body sender and free the queued packet copy afterward. |
net_poll_receive and Socket vtable | Occupy periodic slot +0x10, run on the Socket worker after every wake, and poll the configured receive path. |
event_manager_periodic_noop and EventMan vtable | Occupy periodic slot +0x10 and return without native work. |
util_thread_queue_worker_loop | Treat an empty wait-handle-0 wake as valid and still call periodic slot +0x10. |
util_ring_buffer_push_wait | Lock the queue monitor, wait on not-full at capacity, copy one fixed-size element, signal not-empty, and unlock. |
event_manager_ctor and roots | Allocate or construct the Socket at EventMan +0x68, publish EventMan and Socket globals, and clear them during destruction. |
app_shutdown | Delete EventMan before final dispatcher destruction; EventMan’s destructor deletes its Socket and clears both static roots. |
Validation should also confirm the expected vtable at each live root. A matching function body with a mismatched owner layout is not enough to enable mutation.
Capability gating
Resolve capabilities independently, then expose only the ones whose complete dependency set passed:
| Capability | Minimum validated dependencies |
|---|---|
| Decoded ingress observation and block | event_post_socket_bytes, EventMan root and vtable, safe detour bytes. |
| Central Event observation and block | event_dispatch, Event layout, dispatcher root and vtable, caller cleanup. |
| Client packet observation | net_c_queue_send, Socket root and vtable, signed length and transfer-gate fields. |
| Worker-affine client packet send | Socket root and vtable, net_poll_receive, net_c_send_packet_body, wait handle 0, signed length, and transfer-gate fields. |
| Worker-affine server packet injection | EventMan root and vtable, periodic slot, event_process_work_item, memory-manager root and allocator, packet ownership, and wait handle 0. |
| Window-faithful input | Main HWND, window procedure behavior, message schema. |
| Internal input injection | EventMan root plus each requested input queue function and normalized scan-code contract. |
| Typed UI snapshot | Root or tree walker, vtable, every requested field offset, stable-copy rules. |
| Typed poke | All snapshot dependencies plus an established mutation method or safe scalar field contract. |
If a required mutation dependency fails, disable that capability. Do not fall back to an unvalidated nearest address. Passive build diagnostics can remain available without client hooks.
Porting to 5.x and 7.x
Why one-time reversing is still required
Shared source lineage is valuable because queue codes, class roles, packet actions, assertion text, vtable shapes, and ownership patterns may remain recognizable. It does not guarantee stable machine code or memory layouts. These changes can invalidate a 4.21 profile while leaving the game visibly similar:
- compiler or optimization changes;
- inserted class fields that shift every later offset;
- new virtual methods that shift vtable slots;
- inlining or outlining of queue wrappers;
- different static initialization and linker ordering;
- larger packet, map, UI, or transport buffers;
- changed ownership or thread-affinity rules;
- security additions around packet processing.
Function scanning also does not find heap fields automatically. Character statistics, inventory, and world-object walking require profile-specific owner roots, object discriminators, container layouts, and deletion rules. These should be established once for each family and then compared structurally with 4.21.
Recommended porting order
- Identify the exact executable and record its hash without adding it to the repository.
- Map WinMain,
app_initialize, the message procedure, andapp_shutdown. - Locate the generic worker queue and distinguish the dispatcher, EventMan, and Socket objects.
- Trace decoded receive bytes into pane delivery and packet builders into the logical send queue.
- Establish Event type layout, Event cleanup, pane handler vslots, and timer delivery.
- Find EventMan, Socket, dispatcher, Screen, MapPane, and persistent character-pane roots.
- Map state fields from packet handlers and multiple callers, not from string proximity.
- Update IDA names, types, comments, and vtable slots.
- Produce an exact profile and instruction-aware signatures from the established functions.
- Run passive telemetry first. Enable blocking, injection, and writes only after runtime observations agree with the static model.
Start with one representative 5.x build and one representative 7.x build. If several patch revisions differ only slightly, signatures and semantic validators can likely cover them. If allocation sizes, vtable layouts, or packet ownership change, split them into separate profiles even when the major version is the same.
Comparing profiles
Use semantic identifiers across profiles while storing version-specific details beneath them. For example, all profiles can export logical_client_send, but each entry supplies its own RVA, ABI, transfer-gate offset, sentinel rule, and validators. The IPC command stays stable while the injected adapter changes by profile.
For memory, compare paths rather than absolute offsets alone:
semantic root -> owner relationship -> container kind -> element discriminator -> field
If EventMan -> owned Socket remains true but the owner offset changes, the profile records the new offset. If a later client moves the Socket to another singleton, the semantic path changes and must be re-established rather than forced into the old shape.
Code-level flow
Resolver flow
load image metadata
-> exact immutable-section hash match
-> load exact profile
-> otherwise scan instruction-aware signatures
-> collect unique candidates
-> run per-symbol semantic validators
-> validate roots, vtables, and cross-references
-> build a temporary resolved profile
-> evaluate capability dependency sets
-> install only fully validated capabilities
The temporary resolved profile should be exportable as diagnostic data, but it should not be promoted to a maintained profile until the same addresses and layouts are confirmed in IDA and, where needed, by controlled runtime observation.
Normalized instruction matching
For matching purposes, represent each instruction by opcode class and stable operand properties. Relative targets can be expressed as “call to candidate with validator X” rather than a fixed displacement. Absolute globals can be expressed as “writable image address published by constructor Y.” This is more durable than a byte mask alone and permits cross-symbol validation.
A byte mask can remain the fast first stage. The instruction model is the proof stage before hook installation.
Field-offset recovery
Do not copy 4.21 offsets into an unknown profile. Recover offsets from established functions and require agreement across independent uses. Examples include:
- Event type reads in all three classifier functions;
- packet pointer and size writes in socket Event construction and reads in dispatcher cleanup;
- Socket transfer-gate reads in logical send queueing;
- Socket connection flag writes in connect, receive-zero, and disconnect paths;
- Pane visibility reads, show writes, and hide writes;
- parent slot-array reads in add, remove, destructor, and activation paths.
Only publish a field after its owner, width, signedness, and lifecycle agree across the relevant sites.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x00431200 | event_dispatcher_queue_event_copy | void __thiscall(void *event_dispatcher_object, const void *event) | Copy one established Event into dispatcher work. | Signature validator includes 36-byte copy and work code 3. |
Darkages.exe:0x00431B84 | event_dispatch | int __thiscall(void *event_dispatcher_object, void *event) | Central logical Event routing. | Signature validator includes capture, hierarchy, and three Event category tests. |
Darkages.exe:0x00432E50 | event_post_socket_bytes | void __thiscall(void *event_manager_object, const uint8_t *packet, int length) | Copy decoded server bytes into EventMan work. | Signature validator includes length + 1, zero sentinel, and code 0x0E. |
Darkages.exe:0x00432630 | event_manager_ctor | void *__thiscall(void *event_manager_object) | Construct EventMan and its owned Socket. | Allocates the 0x780D4-byte Socket, stores it at +0x68, and publishes both roots in 4.21. |
Darkages.exe:0x00433110 | event_process_work_item | void __thiscall(void *event_manager_object, int code, void *data, int value) | Execute EventMan worker records. | Code 0x0E transfers a client-allocated decoded packet into Event type 9. |
Darkages.exe:0x00434080 | event_manager_periodic_noop | void __thiscall(void *event_manager_object) | Native EventMan periodic callback. | Confirms a no-op +0x10 worker slot for a profile-gated command pump. |
Darkages.exe:0x0045B8F0 | app_shutdown | void __cdecl(void) | Destroy global subsystems. | Cross-profile lifecycle and detach anchor. |
Darkages.exe:0x0045CCA0 | app_initialize | void __cdecl(void) | Build the main client subsystem graph. | Cross-profile allocation and publication anchor. |
Darkages.exe:0x00470FC0 | util_memory_manager_alloc | void *__thiscall(void *memory_manager, int size) | Allocate client-owned storage. | Required for EventMan worker injection whose payload reaches dispatcher cleanup. |
Darkages.exe:0x004A32F0 | net_socket_ctor | void *__thiscall(void *socket_object, void *event_sink) | Construct Socket state and worker base. | Vtable, buffer-size, event-sink, and default-key anchor. |
Darkages.exe:0x004A3570 | net_c_queue_send | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Copy logical client egress. | Signature validator includes transfer gate, 0x10 exception, sentinel, and work code 5. |
Darkages.exe:0x004A39C0 | net_poll_receive | void __thiscall(void *socket_object) | Run Socket periodic receive polling. | Socket vtable slot +0x10; profile-gated Socket command-pump hook. |
Darkages.exe:0x004A54D0 | net_process_work_item | void __thiscall(void *socket_object, int code, void *data, int value) | Dispatch Socket worker records. | Validates queue code relationships and queued-buffer cleanup. |
Darkages.exe:0x004A72B4 | net_c_send_packet_body | void __thiscall(void *socket_object, const uint8_t *packet, int16_t length) | Transform, frame, and send on the Socket worker. | Worker-affine injection adapter; validate that it does not retain or free its input. |
Darkages.exe:0x00498080 | util_ring_buffer_push_wait | void __thiscall(void *ring_buffer, const void *element) | Synchronize bounded-ring admission. | Cross-family proof that native multi-producer admission is race-safe but can block. |
Darkages.exe:0x004BF440 | util_thread_queue_post_async | void __thiscall(void *worker_object, int code, void *data, int value) | Append a raw async record and signal the worker. | Cross-family worker-queue anchor; can block for capacity and caller wrappers establish ownership. |
Format Catalog
This catalog will link to one page per understood file or container format.
Each format page must explain its purpose and loading flow, then record byte order, complete field layout, string rules, validation, reader and writer call paths, and relevant function addresses. Unknown fields must retain their exact offset and width.
Catalog
| Format | Purpose | Reader | Writer | Detailed page |
|---|---|---|---|---|
| None documented |
Protocol Catalog
The Stone client uses a direction-sensitive action byte inside an 0xAA binary frame. This catalog separates client actions from server actions because equal numeric values can have unrelated behavior.
Transport and framing
The standard network transport is an IPv4 TCP byte stream. Frames have this wire layout:
AA [uint16 body_length, big-endian] [body bytes...]
| Offset | Width | Meaning |
|---|---|---|
0x00 | 1 | 0xAA frame marker. |
0x01 | 2 | Unsigned big-endian body length. |
0x03 | 1 | Direction-specific action. |
0x04 | variable | Transformation metadata or payload, depending on action. |
body_length includes the action and every remaining wire body byte. It excludes the marker and its own two bytes. Total frame length is body_length + 3.
Ordinary transformed bodies have this form:
[action] [sequence] [XOR-transformed payload and zero sentinel]
The logical packet delivered to or accepted from application code is:
[action] [payload] [zero sentinel]
The sequence byte and XOR passes are omitted for client actions 0x00, 0x10, and 0x48, and for server actions 0x00, 0x03, and 0x40. Only transformed client packets consume and increment the client send sequence. See Sequence and XOR Transformation for the exact pass order, negotiated key, and generated-table formulas.
Directional catalogs
| Direction | Fixed action values | Index |
|---|---|---|
| Client to server | 51 | Client actions, with one linked page per action |
| Server to client | 59 | Server actions, with one linked page per action |
Thirty-five action values occur in both lists. Direction is part of a packet’s identity.
The server action 0x4B contains a big-endian length followed by a logical client packet. The client forwards that embedded packet through its normal send queue. This permits a server-selected action in addition to the 51 constants present in client packet builders.
Current interpretation level
The indexes distinguish established action support from payload details that still require tracing:
- Direction-prefixed
C...andS...class names identify messages in the 4.21 protocol catalog. - Payload fields and behavioral meanings are documented only when instructions, data flow, diagnostics, or visible state changes establish them.
- Builder and handler addresses are included even when individual payload fields remain to be analyzed.
The indexes are exhaustive for fixed action constants accepted or emitted by the traced Stone packet path. They are not claims that every payload schema is already understood.
Direction-specific network-owned IDA names use net_c_... for client-to-server code and net_s_... for server-to-client code. Direction-neutral transport code retains net_.... Methods owned primarily by another subsystem retain that subsystem prefix, such as ui_... for pane packet dispatch.
Sequence and XOR Transformation
Most binary packets carry a sequence byte and use three XOR passes over the payload plus a trailing zero. The message indexes call this Encrypted: Yes for protocol familiarity. It is obfuscation rather than authenticated encryption: there is no integrity tag, message authentication code, or cryptographic security boundary.
Wire placement
The transformation applies inside the standard 0xAA frame:
Encrypted: AA [uint16 body_length, big-endian] [action] [sequence] [XOR(payload || 00)]
Not encrypted: AA [uint16 body_length, big-endian] [action] [payload] [00]
body_length counts the action and every subsequent body byte. It excludes the marker and the two-byte length. The action remains visible in both forms. Only the payload and trailing zero enter the XOR passes.
These actions bypass the sequence and XOR code:
| Direction | Encrypted: No |
|---|---|
| Client to server | 0x00, 0x10, 0x48 |
| Server to client | 0x00, 0x03, 0x40 |
All other fixed actions in the current indexes use the encrypted branch.
Runtime state and defaults
| State | Default | Behavior |
|---|---|---|
| Repeating key | Nine ASCII bytes NexonInc. | Repeats across the transformed data. |
| Table function | 0 | Produces the identity table, where the generated byte equals input_value. |
| Client send sequence | 0x00 | Inserted after the action and incremented modulo 256 after each encrypted client packet. |
Clear client packets do not consume the client send sequence. On receive, the decoder uses the sequence carried by the server packet. No independent receive counter or monotonic-sequence check appears in the decoded server path.
Server-controlled key and table
The server can replace both transformation inputs during the clear SVersionCheck action 0x00. Subtype 0x00 has this payload prefix:
| Payload offset | Width | Field | Behavior |
|---|---|---|---|
0x00 | 1 | subtype | Constant 0x00 for transformation negotiation. |
0x01 | 1 | server_version | Stored and compared with the active server version. |
0x02 | 1 | table_function | Queues regeneration with function index 0 through 9. |
0x03 | 1 | key_length | The main-menu handler requires exactly 9. |
0x04 | key_length | key | Copied and queued as the new repeating key. |
ui_main_menu_handle_server_packet queues the table update through net_queue_xor_table_function and the key update through net_queue_xor_key. Socket work codes 11 and 10 apply them in net_process_work_item. The general setter accepts a key length no greater than nine, but this server-negotiation path asserts that the supplied length equals nine.
Because action 0x00 is not encrypted, these values can establish new state without depending on the previous key or table.
Client encoding
The encoder produces:
[action] [packet_sequence] [encoded payload and trailing zero]
It copies packet_sequence to the output, increments the stored counter modulo 256, and transforms payload_and_zero in this order:
void encode_packet_data(
const uint8_t *payload_and_zero,
size_t data_length,
const uint8_t *repeating_key,
size_t key_length,
const uint8_t *xor_table,
uint8_t packet_sequence,
uint8_t *encoded_data)
{
size_t byte_index;
size_t block_index;
size_t key_index;
size_t data_index;
uint8_t block_number;
for (byte_index = 0; byte_index < data_length; ++byte_index) {
encoded_data[byte_index] =
payload_and_zero[byte_index] ^
repeating_key[byte_index % key_length];
}
for (block_index = 0;
block_index * key_length < data_length;
++block_index) {
block_number = (uint8_t)block_index;
if (block_number == packet_sequence)
continue;
for (key_index = 0; key_index < key_length; ++key_index) {
data_index = block_index * key_length + key_index;
if (data_index >= data_length)
break;
encoded_data[data_index] ^=
xor_table[block_number + key_index];
}
}
for (byte_index = 0; byte_index < data_length; ++byte_index) {
encoded_data[byte_index] ^=
xor_table[packet_sequence + byte_index];
}
}
The first pass uses the repeating nine-byte key. The second applies table bytes by key-sized block, except when the block number equals the packet sequence. The third applies table bytes indexed from the sequence.
Server decoding
net_s_decode_packet_body leaves the action in place, reads the following sequence byte, and reverses the XOR passes in the opposite order:
- XOR with table bytes indexed from the received sequence.
- XOR each key-sized block with its table bytes, skipping the block whose byte index equals the received sequence.
- XOR with the repeating key.
- Remove the sequence byte from the application-facing packet.
The decoder returns one fewer byte than the wire body because the sequence is removed. It also writes an additional zero immediately after the returned bytes.
Trailing zero behavior
net_c_queue_send appends a zero before client encoding, so the trailing zero is transformed with the payload. The server is expected to provide the corresponding byte on server packets.
The 4.21 decoder does not verify that this byte becomes zero. It neither rejects the packet nor reports a transformation failure when the decoded value is nonzero. It keeps that byte in the returned logical packet and writes another local zero guard after it. A proxy or compatible server can use the decoded value as a diagnostic, but treating it as a required integrity check would be stricter than this client.
Table generators
net_build_xor_table accepts function indexes 0 through 9. For input_value from 0 through 255, it generates the following byte values. Function 9 first calculates centered_value as ((int32_t)input_value - 128) / 8, using signed C integer division toward zero.
| Function | C expression for the generated byte |
|---|---|
| 0 | input_value |
| 1 | 128 + (((input_value & 1) == 0) ? ((input_value + 1) / 2) : -((input_value + 1) / 2)) |
| 2 | 255 - input_value |
| 3 | 128 + (((input_value & 1) == 0) ? ((255 - input_value) / 2) : -((255 - input_value) / 2)) |
| 4 | (input_value / 16) * (input_value / 16) |
| 5 | (2 * input_value) & 0xFF |
| 6 | 255 - ((2 * input_value) & 0xFF) |
| 7 | (input_value <= 127) ? (255 - 2 * input_value) : (2 * input_value - 256) |
| 8 | (input_value <= 127) ? (2 * input_value) : (511 - 2 * input_value) |
| 9 | 255 - ((centered_value * centered_value) & 0xFF) |
The image starts with function 0. Each generated byte is stored in all four bytes of one dword, allowing the optimized paths to XOR four data bytes at once.
The additive table indexes in the encoder and decoder do not have an explicit modulo operation or bounds check after addition. Compatible implementations should reproduce the observed indexing before adding stricter validation.
Function reference
| Address | Current IDA name | Prototype | Purpose |
|---|---|---|---|
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | int __thiscall(void *this, void *event) | Parse clear action 0x00, including the table selector and negotiated key. |
Darkages.exe:0x004A36C0 | net_queue_xor_key | void __thiscall(void *this, unsigned int key_length, const uint8_t *key) | Copy and queue a key update as Socket work code 10. |
Darkages.exe:0x004A3700 | net_queue_xor_table_function | void __thiscall(void *this, unsigned int function_index) | Queue table regeneration as Socket work code 11. |
Darkages.exe:0x004A44D4 | net_s_decode_packet_body | int __stdcall(const uint8_t *src, int src_size, uint8_t *dest) | Decode a sequence-bearing server body. |
Darkages.exe:0x004A54D0 | net_process_work_item | void __thiscall(void *this, int code, void *data, int value) | Apply queued key and table updates on work codes 10 and 11. |
Darkages.exe:0x004A79E4 | net_c_encode_packet_body | int __stdcall(const uint8_t *src, int src_size, uint8_t *dest) | Insert the client sequence and encode the payload and trailing zero. |
Darkages.exe:0x004A8414 | net_set_xor_key | void __stdcall(int length, const uint8_t *key) | Install and repeat the active key. |
Darkages.exe:0x004A8590 | net_build_xor_table | void __stdcall(int function_index) | Generate the table with one of ten functions. |
Client Actions
This index records every fixed action byte placed in a logical client packet before net_c_queue_send. The send-site addresses are the calls that submit the completed byte array.
Each class name in the index links to a dedicated page containing its payload format, queue call sites, and current schema status. Message-page offsets are payload-relative and exclude framing, action, sequence, and the trailing zero.
The C... names identify 52 fixed client-to-server actions found in the traced 4.21 send path. Fifty-one appear directly at net_c_queue_send call sites. CUseSpell action 0x0F is additionally established through the spell-delay controller, which stores the already-built logical packet and submits it immediately or on a later timer callback. The spelling CNewUserApperance is the 4.21 class name.
All entries use the client-to-server direction. The send queue appends a zero sentinel. Except for actions 0x00, 0x48, and 0x10, net_c_encode_packet_body also inserts a sequence byte, applies the XOR transformation, and increments the send sequence before framing. The three cleartext bypass actions neither carry nor consume a sequence value.
Fixed action index
| Action | Class name | Encrypted | 4.21 send sites and notes |
|---|---|---|---|
0x00 | CVersion | No | Darkages.exe:0x004AD6A3. The four-byte payload is a big-endian data-version uint16_t followed by ASCII LK. XOR bypass. Used during login or lobby. |
0x02 | CNewUser | Yes | Darkages.exe:0x00462730. Used during login or lobby; the payload semantics are not yet established. |
0x03 | CLogin | Yes | Darkages.exe:0x00462D2F. Used during login or lobby; the payload semantics are not yet established. |
0x04 | CNewUserApperance | Yes | Darkages.exe:0x00461725. Used during login or lobby; the payload semantics are not yet established. |
0x05 | CMapRequest | Yes | Darkages.exe:0x00465D7A. Payload semantics are not yet established. |
0x06 | CMove | Yes | Darkages.exe:0x0048853A. Payload semantics are not yet established. |
0x07 | CGet | Yes | Darkages.exe:0x00454213. Payload semantics are not yet established. |
0x08 | CDrop | Yes | Darkages.exe:0x004530AE. Payload semantics are not yet established. |
0x0B | CQuit | Yes | Darkages.exe:0x0046471C, Darkages.exe:0x004889DC, Darkages.exe:0x00488E2E, Darkages.exe:0x004922B1, Darkages.exe:0x0049236B, Darkages.exe:0x0049544D. |
0x0C | CPutGround | Yes | Darkages.exe:0x0046B1C2. Payload semantics are not yet established. |
0x0D | CBlockListen | Yes | Darkages.exe:0x004C1691, Darkages.exe:0x004C1956, Darkages.exe:0x004C1A66. |
0x0E | CSay | Yes | Darkages.exe:0x004811F1, Darkages.exe:0x00482DA9, Darkages.exe:0x0048865F, Darkages.exe:0x004C0987. |
0x0F | CUseSpell | Yes | ui_spell_slot_begin_cast builds the confirmed two-byte immediate form. ui_spell_delay_begin queues it directly or ui_spell_delay_handle_timer queues the held packet after delay traffic. |
0x10 | CTransferServer | No | Darkages.exe:0x00460617, Darkages.exe:0x00463119, Darkages.exe:0x0046C901. A MapPane::ProcessTransferServer diagnostic names kClientTransferServer; this is the only action allowed while the Socket transfer gate is active. XOR bypass. |
0x11 | CChangeDirection | Yes | Darkages.exe:0x0046822F, Darkages.exe:0x004884D4. |
0x13 | CAttack | Yes | Darkages.exe:0x0048895C. Payload semantics are not yet established. |
0x18 | CWho | Yes | net_c_send_who_request submits one action byte. The E window button requests the list; SShowUsers populates and shows the pane. |
0x19 | CWhisper | Yes | Darkages.exe:0x004C1182. Payload semantics are not yet established. |
0x1B | CUserSetting | Yes | net_c_send_user_setting submits one setting-code byte from OptionPane-family controls. |
0x1C | CUse | Yes | Darkages.exe:0x004533C6. Payload semantics are not yet established. |
0x1D | CEmotion | Yes | Darkages.exe:0x00488694. Payload semantics are not yet established. |
0x23 | CExitEditingMode | Yes | ui_paper_dialog_handle_completion and net_c_send_exit_editing_mode serialize Paper mode, text length, and normalized text. |
0x24 | CDropGold | Yes | Darkages.exe:0x004534E1. Payload semantics are not yet established. |
0x26 | CChangePassword | Yes | Darkages.exe:0x00463A45, Darkages.exe:0x004642F0. Used during login or lobby. |
0x29 | CGive | Yes | Darkages.exe:0x0045336A. Payload semantics are not yet established. |
0x2A | CGiveGold | Yes | Darkages.exe:0x0045360B. Payload semantics are not yet established. |
0x2D | CSelfLook | Yes | net_c_send_self_look submits one action byte when a hidden current equipment pane is reopened. |
0x2E | CGroup | Yes | Darkages.exe:0x004306AE. Payload semantics are not yet established. |
0x2F | CGroupToggle | Yes | Darkages.exe:0x0042F1D0. Payload semantics are not yet established. |
0x30 | CChangeSlot | Yes | Darkages.exe:0x0042499F, Darkages.exe:0x00441216, Darkages.exe:0x004427DF, Darkages.exe:0x0044395F. |
0x38 | CRefreshUser | Yes | Darkages.exe:0x00488110. Payload semantics are not yet established. |
0x39 | CMenuCode | Yes | Darkages.exe:0x0047B8CD, Darkages.exe:0x0047BBB7, Darkages.exe:0x0047C175, Darkages.exe:0x0047C2CF, Darkages.exe:0x0047C60F, Darkages.exe:0x0047C8C8, Darkages.exe:0x0047D3BF, Darkages.exe:0x0047D46D, Darkages.exe:0x0047D7DC. |
0x3A | CMessage | Yes | Darkages.exe:0x0047E85C, Darkages.exe:0x0047F9AC, Darkages.exe:0x0047FA89, Darkages.exe:0x004805CC, Darkages.exe:0x004806A9, Darkages.exe:0x0048113C, Darkages.exe:0x004812AE, Darkages.exe:0x00481EEC, Darkages.exe:0x0048205B, Darkages.exe:0x00482B5C, Darkages.exe:0x00482F1C. |
0x3B | CBulletin | Yes | Darkages.exe:0x0040ECE1, Darkages.exe:0x004109EC, Darkages.exe:0x0041294C, Darkages.exe:0x004145B4, Darkages.exe:0x00415738, Darkages.exe:0x00415934, Darkages.exe:0x0041715C, Darkages.exe:0x0041817E, Darkages.exe:0x004187E8, Darkages.exe:0x0041A73C, Darkages.exe:0x0041AE45, Darkages.exe:0x0041B4E4, Darkages.exe:0x0041B60B, Darkages.exe:0x0041B8A8, Darkages.exe:0x0041BA1B. |
0x3C | CPutToContainer | Yes | Darkages.exe:0x00424B24. Payload semantics are not yet established. |
0x3D | CGetFromContainer | Yes | Darkages.exe:0x004412D8, Darkages.exe:0x00453A15. |
0x3E | CUseSkill | Yes | net_c_send_use_skill submits [0x3E, skill_slot]. |
0x3F | CFieldMap | Yes | Darkages.exe:0x0043953A. Payload semantics are not yet established. |
0x41 | CGetParcel | Yes | Darkages.exe:0x00494E2C. Payload semantics are not yet established. |
0x42 | CException | Yes | Darkages.exe:0x00434B43. Payload semantics are not yet established. |
0x43 | CRequestObjectInfo | Yes | Darkages.exe:0x0047B755, Darkages.exe:0x00488723, Darkages.exe:0x0048C820, Darkages.exe:0x0048F17F. |
0x44 | CRemoveEquip | Yes | Darkages.exe:0x0042F8E2. Payload semantics are not yet established. |
0x45 | CReplyCRC | Yes | Darkages.exe:0x00465522. Payload semantics are not yet established. |
0x46 | CGroupView | Yes | Darkages.exe:0x0042F919. Payload semantics are not yet established. |
0x47 | CAddStat | Yes | Darkages.exe:0x0044028E. Payload semantics are not yet established. |
0x48 | CRequestPatch | No | Darkages.exe:0x0048763C, Darkages.exe:0x004953EC, Darkages.exe:0x00495F55, Darkages.exe:0x0049609D. XOR bypass. May be used during login or lobby. |
0x4A | CExchange | Yes | Darkages.exe:0x0043071A, Darkages.exe:0x0043596E, Darkages.exe:0x00435A5A, Darkages.exe:0x00435ACA, Darkages.exe:0x00435E90, Darkages.exe:0x00437310, Darkages.exe:0x00437AE8. |
0x4D | CSpellDelayRequest | Yes | net_c_send_spell_delay_request submits the configured delay-seconds byte. |
0x4E | CSpellDelaySay | Yes | Skill and spell paths submit a one-byte phrase length followed by phrase bytes; the delayed spell path can repeat it on 1000 ms timers. |
0x4F | CSendPortrait | Yes | Darkages.exe:0x0040A7E3. net_c_build_portrait_response reads and validates the local portrait and includes its bytes in the response to server action 0x49. |
0x57 | CMulti | Yes | Darkages.exe:0x004A29D7, Darkages.exe:0x004A2EB0. |
0x62 | literal baram | Yes | Darkages.exe:0x004AD50B. The five submitted logical bytes are the literal ASCII string baram, so byte zero is action 0x62. This special packet is named by its literal bytes rather than the C... convention. |
baram bootstrap packet
Darkages.exe:0x004AD4E4 formats the literal ASCII string baram and submits exactly five bytes at Darkages.exe:0x004AD50B. The same control-flow branch immediately builds and queues CVersion at Darkages.exe:0x004AD6A3.
Action 0x62 is not one of the client XOR bypass values, so net_c_encode_packet_body inserts the current sequence byte and increments net_c_send_sequence when transmitting it. The global occupies uninitialized image storage and is therefore zero-filled when the process loads. On the initial startup path, baram carries sequence 0x00 and advances the client counter to 0x01; CVersion then bypasses the sequence machinery. The only code references to the counter are the read and increment inside net_c_encode_packet_body, so repeating this path does not explicitly reset the client counter. The packet may reset or synchronize server-side tracking, but that effect is not visible in this client code.
Server-directed dynamic send
Server action 0x4B is handled by Darkages.exe:0x0046CA54 net_forward_embedded_client_packet. It reads a big-endian uint16_t from server-packet offset 0x01, treats bytes beginning at offset 0x03 as a logical client packet of that length, and submits them to net_c_queue_send. The embedded first byte therefore selects the client action dynamically.
This path still receives the normal zero sentinel, direction-specific transformation, frame header, and transfer-state gate. It does not bypass the Socket send machinery.
CVersion (0x00)
Client action index | Next: CNewUser
CVersion is client-to-server action 0x00 in the 4.21 protocol.
Direction: client to server
Encrypted: No. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 2 | data_version | Unsigned big-endian value selected by the lobby startup code. |
0x02 | 2 | client_tag | Literal ASCII LK. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004AD6A3 | sub_4AD140 | Darkages.exe:0x004AD140 |
Schema status
The builder establishes the complete four-byte payload. It submits the action followed by these four bytes before the send queue adds the trailing zero.
CNewUser (0x02)
Previous: CVersion | Client action index | Next: CLogin
CNewUser is client-to-server action 0x02 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00462730 | sub_4625C4 | Darkages.exe:0x004625C4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CLogin (0x03)
Previous: CNewUser | Client action index | Next: CNewUserApperance
CLogin is client-to-server action 0x03 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00462D2F | sub_462BF4 | Darkages.exe:0x00462BF4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CNewUserApperance (0x04)
Previous: CLogin | Client action index | Next: CMapRequest
CNewUserApperance is client-to-server action 0x04 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00461725 | sub_4616A4 | Darkages.exe:0x004616A4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CMapRequest (0x05)
Previous: CNewUserApperance | Client action index | Next: CMove
CMapRequest is client-to-server action 0x05 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00465D7A | sub_465CC4 | Darkages.exe:0x00465CC4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CMove (0x06)
Previous: CMapRequest | Client action index | Next: CGet
CMove is client-to-server action 0x06 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | direction | Direction byte supplied by the local-user movement path. |
0x01 | 1 | move_sequence | Client movement sequence. The sender increments local user +0x6F28 before writing it. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0048853A | net_c_send_move | Darkages.exe:0x004884E4 |
Sender notes
net_c_send_move creates the complete three-byte logical packet: action 0x06, direction, and move_sequence. It submits length 3 to net_c_queue_send; the queue adds the trailing zero separately.
The sender also records timeGetTime at local user +0x6F2C. A later SMove uses this value for the lag indicator. Only one timestamp is retained, so a subsequent move replaces the timestamp even if an earlier server response remains outstanding.
Schema status
The complete payload and queue length are established directly from the 4.21 builder. Direction validation occurs in its callers and movement state code, not in this two-field serializer.
CGet (0x07)
Previous: CMove | Client action index | Next: CDrop
CGet is client-to-server action 0x07 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00454213 | sub_4541B4 | Darkages.exe:0x004541B4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CDrop (0x08)
Previous: CGet | Client action index | Next: CQuit
CDrop is client-to-server action 0x08 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004530AE | sub_453054 | Darkages.exe:0x00453054 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CQuit (0x0B)
Previous: CDrop | Client action index | Next: CPutGround
CQuit is client-to-server action 0x0B in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | subtype | The exit-wait constructor sends 0x01; the SReconnect subtype-one response sends 0x00. Values used by the other builders are not yet compared. |
0x01 | payload_length - 1 | unknown_01 | Any additional payload bytes. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0046471C | sub_4646F0 | Darkages.exe:0x004646F0 |
Darkages.exe:0x004889DC | sub_488974 | Darkages.exe:0x00488974 |
Darkages.exe:0x00488E2E | sub_488B44 | Darkages.exe:0x00488B44 |
Darkages.exe:0x004922B1 | ui_exit_wait_handle_server_packet | Darkages.exe:0x004921F0 |
Darkages.exe:0x0049236B | ui_exit_wait_pane_ctor | Darkages.exe:0x00492310 |
Darkages.exe:0x0049544D | sub_495414 | Darkages.exe:0x00495414 |
Schema status
The two exit-wait call sites establish the subtype exchange. The remaining four builders still need to be compared before assigning a complete subtype directory.
CPutGround (0x0C)
Previous: CQuit | Client action index | Next: CBlockListen
CPutGround is client-to-server action 0x0C in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0046B1C2 | sub_46B194 | Darkages.exe:0x0046B194 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CBlockListen (0x0D)
Previous: CPutGround | Client action index | Next: CSay
CBlockListen is client-to-server action 0x0D in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004C1691 | sub_4C1654 | Darkages.exe:0x004C1654 |
Darkages.exe:0x004C1956 | sub_4C18A4 | Darkages.exe:0x004C18A4 |
Darkages.exe:0x004C1A66 | sub_4C19B4 | Darkages.exe:0x004C19B4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CSay (0x0E)
Previous: CBlockListen | Client action index | Next: CUseSpell
CSay is client-to-server action 0x0E in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004811F1 | sub_481150 | Darkages.exe:0x00481150 |
Darkages.exe:0x00482DA9 | sub_482B70 | Darkages.exe:0x00482B70 |
Darkages.exe:0x0048865F | sub_4885D4 | Darkages.exe:0x004885D4 |
Darkages.exe:0x004C0987 | sub_4C0920 | Darkages.exe:0x004C0920 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CUseSpell (0x0F)
Previous: CSay | Client action index | Next: CTransferServer
CUseSpell is the documentation name for client-to-server action 0x0F in the 4.21 client. The friendly source class name is not retained, but the action’s spell-slot construction and delayed submission are established directly.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Confirmed immediate payload form
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | spell_slot | Slot byte read from the activated spell pane at object +0xF4. |
Darkages.exe:0x00455684 ui_spell_slot_begin_cast builds exactly two logical bytes, [0x0F, spell_slot], for the confirmed immediate path. Other targeting modes enter separate input panes; their final packet fields are not yet mapped.
Delayed submission
ui_spell_delay_begin copies the complete logical packet and its length into the delay controller before deciding when to send it. With a zero delay it calls net_c_queue_send immediately. With a nonzero delay it sends CSpellDelayRequest 0x4D, emits configured CSpellDelaySay 0x4E phrases on 1000 ms timers, and queues the held 0x0F packet on the final tick.
The delay phrases come from SpellBook.cfg and are matched to the spell name. This local delay means the final CUseSpell submission may be owned by a timer callback rather than the original mouse Event.
Functions
| Address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00455684 | ui_spell_slot_begin_cast | Build the confirmed [0x0F, slot] form. |
Darkages.exe:0x004556F4 | ui_spell_delay_begin | Copy and hold the logical packet, or submit it immediately. |
Darkages.exe:0x00456940 | ui_spell_delay_handle_timer | Submit the held packet on the final delay tick. |
See UI, Input, and Packet Flows for the input-to-timer-to-network path.
CTransferServer (0x10)
Previous: CUseSpell | Client action index | Next: CChangeDirection
CTransferServer is client-to-server action 0x10 in the 4.21 protocol.
Direction: client to server
Encrypted: No. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00460617 | sub_460584 | Darkages.exe:0x00460584 |
Darkages.exe:0x00463119 | sub_463010 | Darkages.exe:0x00463010 |
Darkages.exe:0x0046C901 | sub_46C7D4 | Darkages.exe:0x0046C7D4 |
Schema status
The payload fields remain placeholders. Stone diagnostics independently name kClientTransferServer, and the Socket transfer gate permits only this action while a server transfer is active.
CChangeDirection (0x11)
Previous: CTransferServer | Client action index | Next: CAttack
CChangeDirection is client-to-server action 0x11 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0046822F | sub_4681E4 | Darkages.exe:0x004681E4 |
Darkages.exe:0x004884D4 | sub_4884B4 | Darkages.exe:0x004884B4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CAttack (0x13)
Previous: CChangeDirection | Client action index | Next: CWho
CAttack is client-to-server action 0x13 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0048895C | sub_488944 | Darkages.exe:0x00488944 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CWho (0x18)
Previous: CAttack | Client action index | Next: CWhisper
CWho is client-to-server action 0x18 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
CWho has no payload. Darkages.exe:0x0043E384 net_c_send_who_request submits exactly one logical byte, the action.
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0043E39C | net_c_send_who_request | Darkages.exe:0x0043E384 |
UI flow
The E window button and mapped E/e key call ui_window_buttons_select, which sends CWho every time. On first use it also allocates, adds, and registers Users Dialog Pane, then hides it. SShowUsers 0x36 populates and shows that persistent pane when the reply arrives. See UI, Input, and Packet Flows.
CWhisper (0x19)
Previous: CWho | Client action index | Next: CUserSetting
CWhisper is client-to-server action 0x19 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004C1182 | sub_4C10D0 | Darkages.exe:0x004C10D0 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CUserSetting (0x1B)
Previous: CWhisper | Client action index | Next: CUse
CUserSetting is client-to-server action 0x1B in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | setting_code | Option selection code chosen by the active OptionPane-family control. Confirmed callers pass constants 0 through 8 or a control-derived byte. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00491AC4 | net_c_send_user_setting | Darkages.exe:0x00491AA4 |
UI flow
Window button Q creates the dynamic OptionPane without sending a packet. Its controls and related option subdialogs call net_c_send_user_setting, which submits exactly [0x1B, setting_code]. The server can acknowledge a saved selection with SSelfSaveOk 0x21; the registered OptionPane then creates a local “Saved.” message dialog.
The individual meanings of setting codes 0 through 8 have not yet been assigned because the current evidence establishes control branches but not their server-side setting names.
See UI, Input, and Packet Flows.
CUse (0x1C)
Previous: CUserSetting | Client action index | Next: CEmotion
CUse is client-to-server action 0x1C in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004533C6 | sub_453394 | Darkages.exe:0x00453394 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CEmotion (0x1D)
Previous: CUse | Client action index | Next: CExitEditingMode
CEmotion is client-to-server action 0x1D in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00488694 | sub_488674 | Darkages.exe:0x00488674 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CExitEditingMode (0x23)
Previous: CEmotion | Client action index | Next: CDropGold
CExitEditingMode is client-to-server action 0x23 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | paper_mode | Mode byte stored at Paper object +0x554. |
0x01 | 2 | text_length | Big-endian number of following text bytes. |
0x03 | text_length | text | Paper text bytes without a terminator. Carriage returns are changed to tab bytes before submission. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00493C99 | ui_paper_dialog_handle_completion | Darkages.exe:0x00493B60 |
Darkages.exe:0x0049421C | net_c_send_exit_editing_mode | Darkages.exe:0x00494100 |
UI flow
Both builders read the Paper text control, limit the local copy to 0x1F40 bytes, normalize carriage returns, and queue text_length + 4 logical bytes including the action. ui_paper_dialog_handle_completion performs this submission when its completion conditions allow it, then closes the Paper dialog. net_c_send_exit_editing_mode performs the same serialization without owning the close transition.
See UI, Input, and Packet Flows.
CDropGold (0x24)
Previous: CExitEditingMode | Client action index | Next: CChangePassword
CDropGold is client-to-server action 0x24 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004534E1 | sub_453424 | Darkages.exe:0x00453424 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CChangePassword (0x26)
Previous: CDropGold | Client action index | Next: CGive
CChangePassword is client-to-server action 0x26 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00463A45 | sub_4638C4 | Darkages.exe:0x004638C4 |
Darkages.exe:0x004642F0 | sub_464174 | Darkages.exe:0x00464174 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CGive (0x29)
Previous: CChangePassword | Client action index | Next: CGiveGold
CGive is client-to-server action 0x29 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0045336A | sub_453324 | Darkages.exe:0x00453324 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CGiveGold (0x2A)
Previous: CGive | Client action index | Next: CSelfLook
CGiveGold is client-to-server action 0x2A in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0045360B | sub_453564 | Darkages.exe:0x00453564 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CSelfLook (0x2D)
Previous: CGiveGold | Client action index | Next: CGroup
CSelfLook is client-to-server action 0x2D in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
CSelfLook has no payload. Darkages.exe:0x0043D4C4 net_c_send_self_look submits exactly one logical byte, the action.
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0043D4DC | net_c_send_self_look | Darkages.exe:0x0043D4C4 |
UI flow
The inventory/equipment selector sends this action only when the equipment pane is already the current content pane but has been hidden by selecting it twice. It sends CSelfLook before showing the pane again. Switching to equipment from another A/S/D/F/G content pane does not send it. SSelfLook 0x39 refreshes fields asynchronously and does not control visibility.
See UI, Input, and Packet Flows.
CGroup (0x2E)
Previous: CSelfLook | Client action index | Next: CGroupToggle
CGroup is client-to-server action 0x2E in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004306AE | sub_430644 | Darkages.exe:0x00430644 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CGroupToggle (0x2F)
Previous: CGroup | Client action index | Next: CChangeSlot
CGroupToggle is client-to-server action 0x2F in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0042F1D0 | sub_42F1A4 | Darkages.exe:0x0042F1A4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CChangeSlot (0x30)
Previous: CGroupToggle | Client action index | Next: CRefreshUser
CChangeSlot is client-to-server action 0x30 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0042499F | sub_424914 | Darkages.exe:0x00424914 |
Darkages.exe:0x00441216 | sub_441164 | Darkages.exe:0x00441164 |
Darkages.exe:0x004427DF | sub_442684 | Darkages.exe:0x00442684 |
Darkages.exe:0x0044395F | sub_443804 | Darkages.exe:0x00443804 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CRefreshUser (0x38)
Previous: CChangeSlot | Client action index | Next: CMenuCode
CRefreshUser is client-to-server action 0x38 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00488110 | sub_4880E4 | Darkages.exe:0x004880E4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CMenuCode (0x39)
Previous: CRefreshUser | Client action index | Next: CMessage
CMenuCode is client-to-server action 0x39 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | menu_type | Server-supplied menu or interaction type copied from the active menu pane. |
0x01 | 4 | object_id | Big-endian server object identifier copied from the active menu pane. |
0x05 | 2 | menu_code | Big-endian selected menu code or control identifier. |
0x07 | variable | variant_data | Optional selection byte, length-prefixed text, or class-specific values. Absent in the eight-byte logical form. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0047B8CD | sub_47B850 | Darkages.exe:0x0047B850 |
Darkages.exe:0x0047BBB7 | sub_47BB00 | Darkages.exe:0x0047BB00 |
Darkages.exe:0x0047C175 | sub_47C100 | Darkages.exe:0x0047C100 |
Darkages.exe:0x0047C2CF | sub_47C220 | Darkages.exe:0x0047C220 |
Darkages.exe:0x0047C60F | sub_47C560 | Darkages.exe:0x0047C560 |
Darkages.exe:0x0047C8C8 | sub_47C7C0 | Darkages.exe:0x0047C7C0 |
Darkages.exe:0x0047D3BF | sub_47D310 | Darkages.exe:0x0047D310 |
Darkages.exe:0x0047D46D | sub_47D3E0 | Darkages.exe:0x0047D3E0 |
Darkages.exe:0x0047D7DC | sub_47D6E4 | Darkages.exe:0x0047D6E4 |
UI flow
All nine traced builders share the seven-byte payload prefix above. They are called by controls owned by the dynamic SScreenMenu interaction. The simplest form submits eight logical bytes including the action. Other variants append a one-byte length and text, a selection byte, or three small class-specific values.
See UI, Input, and Packet Flows.
CMessage (0x3A)
Previous: CMenuCode | Client action index | Next: CBulletin
CMessage is client-to-server action 0x3A in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | dialog_type | Type byte retained from the server-created message dialog. |
0x01 | 4 | object_id | Big-endian object identifier retained by the dialog. |
0x05 | 2 | pursuit_id | Big-endian server pursuit or interaction identifier. |
0x07 | 2 | next_step | Big-endian stored step value plus one. |
0x09 | 1 | response_type | Value 1 in the confirmed question-answer builders. |
0x0A | 1 | answer | Selected answer index passed to the dialog’s answer virtual. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0047E85C | sub_47E7D0 | Darkages.exe:0x0047E7D0 |
Darkages.exe:0x0047F9AC | sub_47F920 | Darkages.exe:0x0047F920 |
Darkages.exe:0x0047FA89 | sub_47F9C0 | Darkages.exe:0x0047F9C0 |
Darkages.exe:0x004805CC | sub_480540 | Darkages.exe:0x00480540 |
Darkages.exe:0x004806A9 | sub_4805E0 | Darkages.exe:0x004805E0 |
Darkages.exe:0x0048113C | sub_4810B0 | Darkages.exe:0x004810B0 |
Darkages.exe:0x004812AE | sub_481150 | Darkages.exe:0x00481150 |
Darkages.exe:0x00481EEC | sub_481E60 | Darkages.exe:0x00481E60 |
Darkages.exe:0x0048205B | sub_481F00 | Darkages.exe:0x00481F00 |
Darkages.exe:0x00482B5C | sub_482AD0 | Darkages.exe:0x00482AD0 |
Darkages.exe:0x00482F1C | sub_482B70 | Darkages.exe:0x00482B70 |
Confirmed question answer form
ui_question_message_dialog_send_answer and ui_question_message_face_dialog_send_answer each submit 12 logical bytes including the action, exactly matching the fields above. These dialogs are created by SPursuitMenu subtypes 2 and 6. Other CMessage builders use related dialog variants and still require separate field comparison before their additional semantics are named.
See UI, Input, and Packet Flows.
CBulletin (0x3B)
Previous: CMessage | Client action index | Next: CPutToContainer
CBulletin is client-to-server action 0x3B in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
Every CBulletin payload begins with a one-byte command subtype.
| Subtype | Purpose | Confirmed remaining payload |
|---|---|---|
1 | Start bulletin session | None. The logical packet is [0x3B, 0x01]. |
2 | Request article or mail list | Two big-endian uint16_t values and one uint8_t. The concrete fields vary between board, article-list, and mail-list callers. |
3 | Request one article or mail | Two big-endian uint16_t identifiers and one uint8_t. |
4 | Post an article | Big-endian board identifier, one-byte title length and title, big-endian content length and content. |
5 | Delete an article or mail | Two big-endian uint16_t identifiers, with an additional zero byte in two list-originated variants. |
6 | Post mail | Big-endian identifier, one-byte receiver length and receiver, one-byte title length and title, big-endian content length and content. |
7 | Change article highlight state | Two big-endian uint16_t identifiers. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0040ECE1 | net_c_send_bulletin_start | Darkages.exe:0x0040ECA4 |
Darkages.exe:0x004109EC | net_c_send_bulletin_article_list_request | Darkages.exe:0x00410964 |
Darkages.exe:0x0041294C | net_c_send_bulletin_mail_list_request | Darkages.exe:0x004128C4 |
Darkages.exe:0x004145B4 | net_c_send_bulletin_board_article_list_request | Darkages.exe:0x00414534 |
Darkages.exe:0x00415738 | net_c_send_bulletin_article_request | Darkages.exe:0x004156B4 |
Darkages.exe:0x00415934 | net_c_send_bulletin_article_highlight | Darkages.exe:0x004158C4 |
Darkages.exe:0x0041715C | net_c_send_bulletin_article_request_from_dialog | Darkages.exe:0x004170D4 |
Darkages.exe:0x0041817E | net_c_send_bulletin_post_article | Darkages.exe:0x00417F94 |
Darkages.exe:0x004187E8 | net_c_send_bulletin_mail_request | Darkages.exe:0x00418764 |
Darkages.exe:0x0041A73C | net_c_send_bulletin_mail_request_from_dialog | Darkages.exe:0x0041A6B4 |
Darkages.exe:0x0041AE45 | net_c_send_bulletin_post_mail | Darkages.exe:0x0041AB84 |
Darkages.exe:0x0041B4E4 | net_c_send_bulletin_delete_article_from_list | Darkages.exe:0x0041B474 |
Darkages.exe:0x0041B60B | net_c_send_bulletin_delete_article | Darkages.exe:0x0041B5A4 |
Darkages.exe:0x0041B8A8 | net_c_send_bulletin_delete_mail_from_list | Darkages.exe:0x0041B824 |
Darkages.exe:0x0041BA1B | net_c_send_bulletin_delete_mail | Darkages.exe:0x0041B9B4 |
UI flow
Window button W creates BulletinSession and sends subtype 1. SBulletin subtype 1 creates BoardListDialog. Navigation, view, new, reply, delete, and highlight controls emit the later commands while the session remains registered. Server subtypes 1 through 5 replace the session’s active child with the matching board, article, or mail dialog.
The command field widths above are established at all 15 send sites. Some identifier meanings differ between board and mail contexts and remain unnamed until their server-side correspondence is confirmed.
See UI, Input, and Packet Flows.
CPutToContainer (0x3C)
Previous: CBulletin | Client action index | Next: CGetFromContainer
CPutToContainer is client-to-server action 0x3C in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00424B24 | sub_424A74 | Darkages.exe:0x00424A74 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CGetFromContainer (0x3D)
Previous: CPutToContainer | Client action index | Next: CUseSkill
CGetFromContainer is client-to-server action 0x3D in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004412D8 | sub_441234 | Darkages.exe:0x00441234 |
Darkages.exe:0x00453A15 | sub_4539B4 | Darkages.exe:0x004539B4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CUseSkill (0x3E)
Previous: CGetFromContainer | Client action index | Next: CFieldMap
CUseSkill is client-to-server action 0x3E in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | skill_slot | Slot byte read from the activated skill pane at object +0x276. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00454C96 | net_c_send_use_skill | Darkages.exe:0x00454C64 |
UI flow
Darkages.exe:0x00454C64 net_c_send_use_skill builds exactly [0x3E, skill_slot]. The slot activation path can first send a length-prefixed CSpellDelaySay 0x4E phrase loaded for the skill, then sends CUseSkill. Right-button down is separate and opens the local SkillBookDialog without network traffic.
See UI, Input, and Packet Flows.
CFieldMap (0x3F)
Previous: CUseSkill | Client action index | Next: CGetParcel
CFieldMap is client-to-server action 0x3F in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0043953A | sub_4394B4 | Darkages.exe:0x004394B4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CGetParcel (0x41)
Previous: CFieldMap | Client action index | Next: CException
CGetParcel is client-to-server action 0x41 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00494E2C | sub_494E14 | Darkages.exe:0x00494E14 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CException (0x42)
Previous: CGetParcel | Client action index | Next: CRequestObjectInfo
CException is client-to-server action 0x42 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00434B43 | sub_434A40 | Darkages.exe:0x00434A40 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CRequestObjectInfo (0x43)
Previous: CException | Client action index | Next: CRemoveEquip
CRequestObjectInfo is client-to-server action 0x43 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0047B755 | sub_47B704 | Darkages.exe:0x0047B704 |
Darkages.exe:0x00488723 | sub_4886A0 | Darkages.exe:0x004886A0 |
Darkages.exe:0x0048C820 | sub_48C774 | Darkages.exe:0x0048C774 |
Darkages.exe:0x0048F17F | sub_48F114 | Darkages.exe:0x0048F114 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CRemoveEquip (0x44)
Previous: CRequestObjectInfo | Client action index | Next: CReplyCRC
CRemoveEquip is client-to-server action 0x44 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0042F8E2 | sub_42F8A4 | Darkages.exe:0x0042F8A4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CReplyCRC (0x45)
Previous: CRemoveEquip | Client action index | Next: CGroupView
CReplyCRC is client-to-server action 0x45 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00465522 | sub_4654E4 | Darkages.exe:0x004654E4 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CGroupView (0x46)
Previous: CReplyCRC | Client action index | Next: CAddStat
CGroupView is client-to-server action 0x46 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0042F919 | sub_42F8F0 | Darkages.exe:0x0042F8F0 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CAddStat (0x47)
Previous: CGroupView | Client action index | Next: CRequestPatch
CAddStat is client-to-server action 0x47 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0044028E | sub_440234 | Darkages.exe:0x00440234 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CRequestPatch (0x48)
Previous: CAddStat | Client action index | Next: CExchange
CRequestPatch is client-to-server action 0x48 in the 4.21 protocol.
Direction: client to server
Encrypted: No. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0048763C | sub_487604 | Darkages.exe:0x00487604 |
Darkages.exe:0x004953EC | sub_495380 | Darkages.exe:0x00495380 |
Darkages.exe:0x00495F55 | sub_495E80 | Darkages.exe:0x00495E80 |
Darkages.exe:0x0049609D | sub_496054 | Darkages.exe:0x00496054 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CExchange (0x4A)
Previous: CRequestPatch | Client action index | Next: CSpellDelayRequest
CExchange is client-to-server action 0x4A in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0043071A | sub_4306C4 | Darkages.exe:0x004306C4 |
Darkages.exe:0x0043596E | sub_435904 | Darkages.exe:0x00435904 |
Darkages.exe:0x00435A5A | sub_435A04 | Darkages.exe:0x00435A04 |
Darkages.exe:0x00435ACA | sub_435A74 | Darkages.exe:0x00435A74 |
Darkages.exe:0x00435E90 | sub_435E24 | Darkages.exe:0x00435E24 |
Darkages.exe:0x00437310 | sub_4372A4 | Darkages.exe:0x004372A4 |
Darkages.exe:0x00437AE8 | sub_437A64 | Darkages.exe:0x00437A64 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
CSpellDelayRequest (0x4D)
Previous: CExchange | Client action index | Next: CSpellDelaySay
CSpellDelayRequest is client-to-server action 0x4D in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | delay_seconds | Configured number of one-second delay steps before the held spell packet is submitted. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x00455CDF | net_c_send_spell_delay_request | Darkages.exe:0x00455CA4 |
Delay flow
Darkages.exe:0x00455CA4 net_c_send_spell_delay_request submits exactly [0x4D, delay_seconds]. It is called after the final CUseSpell logical packet has already been copied into the spell-delay controller. Timed CSpellDelaySay packets follow, and the controller releases the held cast on the final tick.
See UI, Input, and Packet Flows.
CSpellDelaySay (0x4E)
Previous: CSpellDelayRequest | Client action index | Next: CSendPortrait
CSpellDelaySay is client-to-server action 0x4E in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | text_length | Number of following phrase bytes, truncated to one byte. |
0x01 | text_length | text | Delay phrase bytes copied without a terminator. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004548F1 | net_c_send_skill_delay_say | Darkages.exe:0x00454884 |
Darkages.exe:0x00455921 | net_c_send_spell_delay_say | Darkages.exe:0x00455834 |
Darkages.exe:0x00455815 | ui_spell_delay_begin | Darkages.exe:0x004556F4 |
Darkages.exe:0x00456997 | ui_spell_delay_handle_timer | Darkages.exe:0x00456940 |
Delay flow
Both the skill and spell paths build CSpellDelaySay as action 0x4E, a one-byte length, and the selected phrase. Spell delay phrases are loaded from SpellBook.cfg and emitted once per timer step before the held CUseSpell packet is released. The skill slot can emit its configured phrase immediately before CUseSkill.
See UI, Input, and Packet Flows.
CSendPortrait (0x4F)
Previous: CSpellDelaySay | Client action index | Next: CMulti
CSendPortrait is client-to-server action 0x4F in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x0040A7E3 | net_c_build_portrait_response | Darkages.exe:0x0040A664 |
Schema status
The action purpose is independently established: net_c_build_portrait_response validates a local portrait and sends it after server action 0x49. Individual payload fields remain to be mapped.
CMulti (0x57)
Previous: CSendPortrait | Client action index | Next: baram
CMulti is client-to-server action 0x57 in the 4.21 protocol.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004A29D7 | sub_4A2994 | Darkages.exe:0x004A2994 |
Darkages.exe:0x004A2EB0 | sub_4A2E54 | Darkages.exe:0x004A2E54 |
Schema status
The 4.21 client emits this action at the listed sites. The payload fields and client-side trigger still require tracing.
baram (0x62)
Previous: CMulti | Client action index
baram is the literal bootstrap packet whose first byte is client action 0x62.
Direction: client to server
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 4 | bootstrap_text | Literal ASCII aram, completing baram. |
Queue call sites
| Queue call | Containing IDA function | Function address |
|---|---|---|
Darkages.exe:0x004AD50B | sub_4AD140 | Darkages.exe:0x004AD140 |
Schema status
The complete five-byte submitted value is established. On initial process startup it carries sequence 0x00 and advances net_c_send_sequence to 0x01. Any reset or synchronization effect in the server is not visible in this client.
Server Actions
This index records every fixed action accepted by at least one pane’s socket-event handler. Each class name links to a dedicated page containing its payload format, handler functions, and current schema status. Message-page offsets are payload-relative and exclude framing, action, sequence, and the trailing zero.
The S... class names identify the 59 fixed server-to-client actions accepted by the traced 4.21 handlers. No additional fixed server action was found.
All entries use the server-to-client direction. Actions 0x00, 0x03, and 0x40 bypass the receive sequence and XOR decoder. Other entries are decoded before the Event type 9 packet is dispatched through pane virtual slot +0x40.
Fixed action index
| Action | Class name | Encrypted | 4.21 handlers and notes |
|---|---|---|---|
0x00 | SVersionCheck | No | Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet. Subtype zero reads the server version, selects table function 0 through 9, requires a nine-byte key, and queues both transformation updates. XOR bypass. Used during login or lobby. |
0x01 | SNewUserCheck | Yes | Darkages.exe:0x00461080 sub_461080. Used during login or lobby. |
0x02 | SLoginCheck | Yes | Darkages.exe:0x00461080 sub_461080, Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet, Darkages.exe:0x00464300 sub_464300, Darkages.exe:0x00463A60 sub_463A60. Used during login or lobby. |
0x03 | STransferServer | No | Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet and Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. The MapPane branch calls transfer-server processing and can send kClientTransferServer. XOR bypass. |
0x04 | SUserPosition | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x05 | SUserAppearance | Yes | Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x06 | SMap | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x07 | SDrawObjects | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x08 | SStatus | Yes | Darkages.exe:0x0043F420 sub_43F420, Darkages.exe:0x00441850 sub_441850, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet, Darkages.exe:0x004927F0 sub_4927F0, Darkages.exe:0x00494C60 sub_494C60. |
0x0A | SMessage | Yes | Darkages.exe:0x0041CCA0 sub_41CCA0, Darkages.exe:0x00444690 sub_444690, Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x00491520 sub_491520. |
0x0B | SMove | Yes | Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x0C | SMoveObject | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x0D | SSay | Yes | Darkages.exe:0x0041CCA0 sub_41CCA0, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x0E | SRemoveObjects | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. The mapped branch removes an object from map state. |
0x0F | SAddItem | Yes | Darkages.exe:0x00441850 sub_441850, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x10 | SRemoveItem | Yes | Darkages.exe:0x00441850 sub_441850, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x11 | SChangeDirection | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, which calls Darkages.exe:0x0046B574 ui_map_handle_object_direction. Offset 0x01 is a big-endian object ID and offset 0x05 is a direction value from 0 through 3. |
0x13 | SDamageEffect | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x15 | SMapInfo | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x17 | SAddSpell | Yes | Darkages.exe:0x00443B10 ui_spell_inventory_handle_server_packet, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x18 | SRemoveSpell | Yes | Darkages.exe:0x00443B10 ui_spell_inventory_handle_server_packet, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x19 | SSoundEffect | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x1A | SMotion | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x1B | SEnterEditingMode | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. Used for paper editing. |
0x1F | SChangeWeather | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x20 | SChangeHour | Yes | Darkages.exe:0x0043F420 sub_43F420, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x21 | SSelfSaveOk | Yes | Darkages.exe:0x004909E0 ui_option_pane_handle_server_packet. |
0x26 | SActionChange | Yes | Darkages.exe:0x004234E0 sub_4234E0. |
0x29 | SEffectLayer | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x2A | SAddContainer | Yes | Darkages.exe:0x00425370 sub_425370, Darkages.exe:0x00441850 sub_441850. |
0x2B | SRemoveContainer | Yes | Darkages.exe:0x00425370 sub_425370. |
0x2C | SAddSkill | Yes | Darkages.exe:0x00442990 ui_skill_inventory_handle_server_packet. |
0x2D | SRemoveSkill | Yes | Darkages.exe:0x00442990 ui_skill_inventory_handle_server_packet. |
0x2E | SFieldMap | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x2F | SScreenMenu | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x00479480 sub_479480, Darkages.exe:0x0047AFD0 sub_47AFD0. |
0x30 | SPursuitMenu | Yes | Darkages.exe:0x00461080 sub_461080, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x00479480 sub_479480. |
0x31 | SBulletin | Yes | Darkages.exe:0x00413CE0 ui_bulletin_session_handle_server_packet, Darkages.exe:0x00414260 ui_bulletin_dialog_handle_server_packet, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x32 | SStateObjectState | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. The mapped branch performs inline tile or object updates. |
0x33 | SDrawHumanObject | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x34 | SObjectInfo | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x35 | SShowPaper | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x36 | SShowUsers | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x37 | SAddEquip | Yes | Darkages.exe:0x0042EAF0 ui_equip_pane_handle_server_packet. |
0x38 | SRemoveEquip | Yes | Darkages.exe:0x0042EAF0 ui_equip_pane_handle_server_packet. |
0x39 | SSelfLook | Yes | Darkages.exe:0x0042EAF0 ui_equip_pane_handle_server_packet. |
0x3A | SSpelled | Yes | Darkages.exe:0x0043E8B0 sub_43E8B0. |
0x3B | SRequestCRC | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x3C | SMapPart | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. |
0x3D | SLevelPoint | Yes | Darkages.exe:0x0043F420 sub_43F420. |
0x3E | SWindowChange | Yes | Darkages.exe:0x0043D650 sub_43D650. |
0x3F | SActionCoolTime | Yes | Darkages.exe:0x00442990 ui_skill_inventory_handle_server_packet, Darkages.exe:0x00443B10 ui_spell_inventory_handle_server_packet. |
0x40 | SSendPatch | No | Darkages.exe:0x004955C0 sub_4955C0. XOR bypass. May be used during login or lobby. |
0x42 | SExchange | Yes | Darkages.exe:0x00435EA0 sub_435EA0, Darkages.exe:0x004374F0 sub_4374F0, Darkages.exe:0x00437C70 sub_437C70, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. Exchange-session handlers dispatch on a second byte with established subtypes 1 through 5. |
0x48 | SSpellDelayCancel | Yes | Darkages.exe:0x004568B0 sub_4568B0. |
0x49 | SRequestPortrait | Yes | Darkages.exe:0x0040B3A0 ui_handle_server_request_portrait. An embedded diagnostic names kServerRequestPortrait and states decimal value 73. The handler initiates client action 0x4F. |
0x4B | SBounce | Yes | Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, which calls Darkages.exe:0x0046CA54 net_forward_embedded_client_packet. Offset 0x01 is a big-endian embedded length and offset 0x03 begins the logical client packet. |
0x4C | SReconnect | Yes | Darkages.exe:0x004921F0 ui_exit_wait_handle_server_packet. The Stone handler consumes all 0x4C packets. Subtype 0x01 sends CQuit subtype 0x00 and changes an exit-wait pane to the safe-exit message; it does not call the Socket reconnect path. |
0x51 | SBlockInput | Yes | Darkages.exe:0x004889F0 ui_local_user_handle_server_packet. |
0x56 | SMulti | Yes | Darkages.exe:0x004A2ED0 ui_server_select_handle_server_packet. ServerSelectDialogPane reads a count, allocates replacement entry storage, and deserializes fixed-width integers and colon-delimited strings before loading the table. |
MapPane switch coverage
Darkages.exe:0x00468A90 accepts these 31 values:
03 04 06 07 08 0A 0C 0D 0E 11 13 15 19 1A 1B 1F
20 29 2E 2F 30 31 32 33 34 35 36 3B 3C 42 4B
Within its explicit 0x03 through 0x4B switch range, the other values return unhandled. A value can still be supported by another pane, as shown in the full index above.
SVersionCheck (0x00)
Server action index | Next: SNewUserCheck
SVersionCheck is server-to-client action 0x00 in the 4.21 protocol.
Direction: server to client
Encrypted: No. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | subtype | 0x00 negotiates transformation state, 0x01 reports an outdated client, and 0x02 enters the patch path. |
0x01 | 1 | server_version | Subtype 0x00: stored and compared with the active server version. |
0x02 | 1 | table_function | Subtype 0x00: queues table function 0 through 9. |
0x03 | 1 | key_length | Subtype 0x00: required to equal 9 by the main-menu handler. |
0x04 | key_length | key | Subtype 0x00: replacement repeating XOR key. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | Accepts the action in the main-menu packet handler. |
Darkages.exe:0x004A3700 | net_queue_xor_table_function | Queues the subtype-zero table-function selector as Socket work code 11. |
Darkages.exe:0x004A36C0 | net_queue_xor_key | Copies and queues the subtype-zero key as Socket work code 10. |
Handler notes
The subtype-zero branch reads the server version, queues the one-byte table selector, requires a nine-byte key, and queues that key for the socket worker. Action 0x00 bypasses the sequence and XOR decoder, so negotiation does not depend on the previous transformation state.
Schema status
The subtype-zero payload prefix is established through the field readers and both queue calls. The data consumed by subtypes 0x01 and 0x02 still requires separate tracing.
SNewUserCheck (0x01)
Previous: SVersionCheck | Server action index | Next: SLoginCheck
SNewUserCheck is server-to-client action 0x01 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00461080 | sub_461080 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00461080 sub_461080. Used during login or lobby.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SLoginCheck (0x02)
Previous: SNewUserCheck | Server action index | Next: STransferServer
SLoginCheck is server-to-client action 0x02 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00461080 | sub_461080 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | Accepts the action in the main-menu packet handler. |
Darkages.exe:0x00464300 | sub_464300 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00463A60 | sub_463A60 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00461080 sub_461080, Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet, Darkages.exe:0x00464300 sub_464300, Darkages.exe:0x00463A60 sub_463A60. Used during login or lobby.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
STransferServer (0x03)
Previous: SLoginCheck | Server action index | Next: SUserPosition
STransferServer is server-to-client action 0x03 in the 4.21 protocol.
Direction: server to client
Encrypted: No. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | Accepts the action in the main-menu packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet and Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. The MapPane branch calls transfer-server processing and can send kClientTransferServer. XOR bypass.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SUserPosition (0x04)
Previous: STransferServer | Server action index | Next: SUserAppearance
SUserPosition is server-to-client action 0x04 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SUserAppearance (0x05)
Previous: SUserPosition | Server action index | Next: SMap
SUserAppearance is server-to-client action 0x05 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x004889F0 ui_local_user_handle_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SMap (0x06)
Previous: SUserAppearance | Server action index | Next: SDrawObjects
SMap is server-to-client action 0x06 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SDrawObjects (0x07)
Previous: SMap | Server action index | Next: SStatus
SDrawObjects is server-to-client action 0x07 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SStatus (0x08)
Previous: SDrawObjects | Server action index | Next: SMessage
SStatus is server-to-client action 0x08 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0043F420 | sub_43F420 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00441850 | sub_441850 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Darkages.exe:0x004927F0 | sub_4927F0 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00494C60 | sub_494C60 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0043F420 sub_43F420, Darkages.exe:0x00441850 sub_441850, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet, Darkages.exe:0x004927F0 sub_4927F0, Darkages.exe:0x00494C60 sub_494C60.
The action is observed by several already registered game panes because status fields are distributed across more than one UI owner.
UI behavior
SStatus updates fields and redraw state but does not select or show the persistent status content pane. Status visibility is a client-only A/S/D/F/G transition. G or the status game button calls ui_game_buttons_select_status, hides the previous content pane, and shows the already populated status pane.
See UI, Input, and Packet Flows.
SMessage (0x0A)
Previous: SStatus | Server action index | Next: SMove
SMessage is server-to-client action 0x0A in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0041CCA0 | sub_41CCA0 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00444690 | sub_444690 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | Accepts the action in the main-menu packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x00491520 | sub_491520 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0041CCA0 sub_41CCA0, Darkages.exe:0x00444690 sub_444690, Darkages.exe:0x0045F780 ui_main_menu_handle_server_packet, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet, Darkages.exe:0x00491520 sub_491520.
In game, MapPane handles only message subtypes 8, 9, and 10 in this branch. It reads the big-endian text length at action-relative packet +2, changes tab bytes to carriage returns, allocates a dynamic message dialog, and registers it below BackgroundPane. Subtype 10 selects the alternate constructor mode; 8 and 9 use mode 0.
UI behavior
These subtypes create a new dialog rather than updating a static root. Other SMessage subtypes are not consumed by this MapPane branch and must not be assumed to share the same dialog format.
See UI, Input, and Packet Flows.
SMove (0x0B)
Previous: SMessage | Server action index | Next: SMoveObject
SMove is server-to-client action 0x0B in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | direction | Values 0 through 3 acknowledge ordinary directions. Value 4 selects the position-correction path. |
0x01 | 2 | x | Big-endian 16-bit map coordinate, sign-extended by the handler. Used when direction is 4. |
0x03 | 2 | y | Big-endian 16-bit map coordinate, sign-extended by the handler. Used when direction is 4. |
0x05 | 4 | unknown_05 | Present between the mapped coordinate fields and final byte. This handler does not read it. |
0x09 | 1 | unknown_09 | Read as an unsigned byte and discarded. It is not compared with the client’s movement sequence. |
0x0A | payload_length - 0x0A | unknown_0A | Any additional payload bytes; the mapped local-user handler does not read them. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts action 0x0B in the local-user Pane’s Event type 9 handler. |
Darkages.exe:0x004897C4 | ui_local_user_handle_smove | Parses the payload, updates movement timing, and applies acknowledgment or correction behavior. |
Handler notes
ui_local_user_handle_server_packet dispatches action 0x0B to ui_local_user_handle_smove. The handler does not perform a decoded-size check before reading through payload offset 0x09; transport framing and upstream event construction are expected to provide the bytes.
For direction 0 through 3, the handler checks that the value matches the local user’s current direction, updates the lag indicator, and completes its movement acknowledgment path. For value 4, it compares x and y with local position fields. A mismatch enters the position-correction path before the same lag update. Other direction values trigger the client’s diagnostic assertion and invalid-direction logging path.
Timing is recorded before the direction branch. The handler copies the current client movement sequence from local user +0x6F28 to +0x6F29, then stores this unsigned difference at +0x6F34:
uint32_t latest_move_response_ms = timeGetTime() - last_move_send_tick;
It calls ui_lag_indicator_update_from_local_user, which averages the sample with the previously displayed value. The client does not associate an SMove with a retained per-sequence send timestamp. See Lag indicator and movement timing for the memory fields and color bands.
Schema status
The ten-byte payload prefix read or skipped by this handler is established. The meanings of bytes 0x05 through 0x09 remain unknown because the mapped local-user handler either skips or discards them. This handler alone does not establish that offset 0x09 is the final payload byte.
SMoveObject (0x0C)
Previous: SMove | Server action index | Next: SSay
SMoveObject is server-to-client action 0x0C in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SSay (0x0D)
Previous: SMoveObject | Server action index | Next: SRemoveObjects
SSay is server-to-client action 0x0D in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0041CCA0 | sub_41CCA0 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x0041CCA0 sub_41CCA0, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SRemoveObjects (0x0E)
Previous: SSay | Server action index | Next: SAddItem
SRemoveObjects is server-to-client action 0x0E in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. The mapped branch removes an object from map state.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SAddItem (0x0F)
Previous: SRemoveObjects | Server action index | Next: SRemoveItem
SAddItem is server-to-client action 0x0F in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00441850 | sub_441850 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00441850 sub_441850, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SRemoveItem (0x10)
Previous: SAddItem | Server action index | Next: SChangeDirection
SRemoveItem is server-to-client action 0x10 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00441850 | sub_441850 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00441850 sub_441850, Darkages.exe:0x004889F0 ui_local_user_handle_server_packet.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SChangeDirection (0x11)
Previous: SRemoveItem | Server action index | Next: SDamageEffect
SChangeDirection is server-to-client action 0x11 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 4 | object_id | Unsigned big-endian object identifier. |
0x04 | 1 | direction | Direction value. The mapped handler applies values 0 through 3. |
0x05 | payload_length - 5 | unknown_05 | Any additional payload bytes; the mapped handler does not read them. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x0046B574 | ui_map_handle_object_direction | Reads the object ID and direction and updates the matching map object. |
Handler notes
ui_map_dispatch_server_packet calls ui_map_handle_object_direction. The helper reads the object ID at payload offset 0x00 and the direction at payload offset 0x04.
Schema status
The handler reads object_id, locates the map object, and applies direction only when its value is from 0 through 3. This payload prefix is established directly from the 4.21 instructions.
SDamageEffect (0x13)
Previous: SChangeDirection | Server action index | Next: SMapInfo
SDamageEffect is server-to-client action 0x13 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SMapInfo (0x15)
Previous: SDamageEffect | Server action index | Next: SAddSpell
SMapInfo is server-to-client action 0x15 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SAddSpell (0x17)
Previous: SMapInfo | Server action index | Next: SRemoveSpell
SAddSpell is server-to-client action 0x17 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | slot | 1-based spell inventory slot, valid range 1 through 36. |
0x01 | 2 | display_id | Big-endian display identifier retained by the slot pane. |
0x03 | 1 | spell_type | Signed byte in the established range 1 through 8; selects the client activation path. |
0x04 | 1 | name_length | Number of following spell-name bytes. |
0x05 | name_length | name | Spell name bytes. There is no terminator in the packet; the client appends one. |
0x05 + name_length | 1 | comment_length | Number of following comment bytes. |
0x06 + name_length | comment_length | comment | Spell comment bytes. There is no terminator in the packet; the client appends one. |
0x06 + name_length + comment_length | 1 | unknown_06_plus_name_length_plus_comment_length | Trailing byte retained at slot pane +0x1F9 and passed to SpellBookDialog. |
The payload occupies 7 + name_length + comment_length bytes. The string encoding and trailing-byte meaning are not established by this parser. ui_spell_inventory_apply_add_packet explicitly checks the slot range but does not compare either string length with the remaining decoded packet size before copying. The downstream constructor asserts that each NUL-terminated string is shorter than 128 bytes and that spell_type is 1 through 8.
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00443B10 | ui_spell_inventory_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00443BB4 | ui_spell_inventory_apply_add_packet | Parses the fields and replaces the indexed slot child. |
Darkages.exe:0x00443E14 | ui_spell_inventory_remove_slot_pane | Removes and deletes an existing slot child. |
Darkages.exe:0x00443F24 | ui_spell_inventory_create_slot_pane | Allocates and registers a 0x214-byte slot pane. |
Darkages.exe:0x00456C70 | ui_spell_slot_pane_ctor | Retains the slot, display ID, type, strings, and trailing byte. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
ui_spell_inventory_apply_add_packet parses the action-bearing decoded packet at offsets +1 onward, corresponding to the payload table above. It rejects a slot outside 1 through 36. For a valid slot, it removes any existing pane, allocates a 0x214-byte spell slot pane, and adds and registers it below the spell inventory owner. Darkages.exe:0x004889F0 also observes this action in its own registered pane context.
UI behavior
This action changes the spell inventory’s child panes but does not select or show the persistent spell inventory. The player selects that parent locally with D or its game button. Right-button up over a populated slot opens SpellBookDialog without another server request.
The parent owns a 36-entry pointer array and the child retains all packet-derived state at stable offsets. See Runtime UI Memory Map and UI, Input, and Packet Flows.
SRemoveSpell (0x18)
Previous: SAddSpell | Server action index | Next: SSoundEffect
SRemoveSpell is server-to-client action 0x18 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00443B10 | ui_spell_inventory_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00443B10 ui_spell_inventory_handle_server_packet routes this action to the spell-slot removal path. That path unregisters and removes the child, deletes it, and clears the indexed owner pointer. Darkages.exe:0x004889F0 also observes the action in its own pane context.
UI behavior
Removing a slot does not hide the parent spell inventory. See UI, Input, and Packet Flows.
SSoundEffect (0x19)
Previous: SRemoveSpell | Server action index | Next: SMotion
SSoundEffect is server-to-client action 0x19 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SMotion (0x1A)
Previous: SSoundEffect | Server action index | Next: SEnterEditingMode
SMotion is server-to-client action 0x1A in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SEnterEditingMode (0x1B)
Previous: SMotion | Server action index | Next: SChangeWeather
SEnterEditingMode is server-to-client action 0x1B in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. Used for paper editing.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SChangeWeather (0x1F)
Previous: SEnterEditingMode | Server action index | Next: SChangeHour
SChangeWeather is server-to-client action 0x1F in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SChangeHour (0x20)
Previous: SChangeWeather | Server action index | Next: SSelfSaveOk
SChangeHour is server-to-client action 0x20 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0043F420 | sub_43F420 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x0043F420 sub_43F420, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SSelfSaveOk (0x21)
Previous: SChangeHour | Server action index | Next: SActionChange
SSelfSaveOk is server-to-client action 0x21 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
This action has no payload fields read by the OptionPane handler.
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004909E0 | ui_option_pane_handle_server_packet | Accepts the action in OptionPane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x004909E0 ui_option_pane_handle_server_packet compares the decoded action byte to 0x21. It does not read later bytes.
UI behavior
While OptionPane is registered, this action allocates and constructs a local message dialog containing “Saved.” The packet acknowledges an earlier option save such as CUserSetting 0x1B; it does not create OptionPane itself.
See UI, Input, and Packet Flows.
SActionChange (0x26)
Previous: SSelfSaveOk | Server action index | Next: SEffectLayer
SActionChange is server-to-client action 0x26 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004234E0 | sub_4234E0 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x004234E0 sub_4234E0.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SEffectLayer (0x29)
Previous: SActionChange | Server action index | Next: SAddContainer
SEffectLayer is server-to-client action 0x29 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SAddContainer (0x2A)
Previous: SEffectLayer | Server action index | Next: SRemoveContainer
SAddContainer is server-to-client action 0x2A in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00425370 | sub_425370 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00441850 | sub_441850 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00425370 sub_425370, Darkages.exe:0x00441850 sub_441850.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SRemoveContainer (0x2B)
Previous: SAddContainer | Server action index | Next: SAddSkill
SRemoveContainer is server-to-client action 0x2B in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00425370 | sub_425370 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00425370 sub_425370.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SAddSkill (0x2C)
Previous: SRemoveContainer | Server action index | Next: SRemoveSkill
SAddSkill is server-to-client action 0x2C in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | slot | 1-based skill inventory slot, valid range 1 through 36. |
0x01 | 2 | display_id | Big-endian display identifier retained by the slot pane. |
0x03 | 1 | name_length | Number of following skill-name bytes. |
0x04 | name_length | name | Skill name bytes. There is no terminator in the packet; the client appends one. |
The payload occupies 4 + name_length bytes. The string encoding is not established by this parser. ui_skill_inventory_apply_add_packet checks the slot range but does not compare name_length with the remaining decoded packet size before copying. The downstream slot constructor asserts that the resulting NUL-terminated name is shorter than 128 bytes.
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00442990 | ui_skill_inventory_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00442A34 | ui_skill_inventory_apply_add_packet | Parses the fields and replaces the indexed slot child. |
Darkages.exe:0x00442C04 | ui_skill_inventory_remove_slot_pane | Removes and deletes an existing slot child. |
Darkages.exe:0x00442D14 | ui_skill_inventory_create_slot_pane | Allocates and registers a 0x294-byte slot pane. |
Darkages.exe:0x00456B90 | ui_skill_slot_pane_ctor | Retains the display ID, name, and 1-based slot in the new child. |
Handler notes
ui_skill_inventory_apply_add_packet parses the action-bearing decoded packet at offsets +1 through +4, corresponding to payload offsets 0x00 through 0x03. It rejects a slot outside 1 through 36. For a valid slot, it removes any existing pane, allocates a 0x294-byte skill slot pane, and adds and registers it below the skill inventory owner.
UI behavior
This action changes skill slot children but does not select or show the persistent skill inventory. The player selects that parent locally with S or its game button. Right-button down over a populated slot opens SkillBookDialog without another server request.
The parent owns a 36-entry pointer array and the child retains its packet-derived state at stable offsets. See Runtime UI Memory Map and UI, Input, and Packet Flows.
SRemoveSkill (0x2D)
Previous: SAddSkill | Server action index | Next: SFieldMap
SRemoveSkill is server-to-client action 0x2D in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00442990 | ui_skill_inventory_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00442990 ui_skill_inventory_handle_server_packet routes this action to the skill-slot removal path. That path unregisters and removes the child, deletes it, and clears the indexed owner pointer.
UI behavior
Removing a slot does not hide the parent skill inventory. See UI, Input, and Packet Flows.
SFieldMap (0x2E)
Previous: SRemoveSkill | Server action index | Next: SScreenMenu
SFieldMap is server-to-client action 0x2E in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SScreenMenu (0x2F)
Previous: SFieldMap | Server action index | Next: SPursuitMenu
SScreenMenu is server-to-client action 0x2F in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x00479480 | sub_479480 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x0047AFD0 | sub_47AFD0 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
ui_map_dispatch_server_packet calls Darkages.exe:0x00469474 ui_map_handle_screen_menu, which allocates a 0x104-byte full-screen pane and calls ui_screen_menu_pane_ctor. The constructor adds it below BackgroundPane, registers it, and parses the original action 0x2F packet. The handlers at 0x00479480 and 0x0047AFD0 can receive later menu packets while related panes remain registered.
UI response path
Selections and text controls in this menu family send CMenuCode 0x39. Every traced builder copies a server-supplied menu type, big-endian object identifier, and big-endian menu code before variant-specific data.
See UI, Input, and Packet Flows.
SPursuitMenu (0x30)
Previous: SScreenMenu | Server action index | Next: SBulletin
SPursuitMenu is server-to-client action 0x30 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | dialog_subtype | Selects one of seven dynamic message classes, values 0 through 6. |
0x01 | variable | dialog_data | Subtype-specific message, face, identifiers, choices, and text. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00461080 | sub_461080 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x00479480 | sub_479480 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
ui_map_dispatch_server_packet calls Darkages.exe:0x0046C004 ui_map_handle_pursuit_menu. Subtype 2 constructs QuestionMessageDialog; subtype 6 constructs QuestionMessageFaceDialog. The other five variants are dynamically allocated and registered, but their friendly source class names are not yet established. The handlers at 0x00461080 and 0x00479480 also observe pursuit traffic in their registered pane contexts.
UI response path
The two confirmed question classes send selected answers through CMessage 0x3A. Their answer builders retain the dialog type, object identifier, pursuit identifier, and step from the server packet, append response type 1 and the selected answer byte, and submit 12 logical bytes.
See UI, Input, and Packet Flows.
SBulletin (0x31)
Previous: SPursuitMenu | Server action index | Next: SStateObjectState
SBulletin is server-to-client action 0x31 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | response_subtype | Selects board list 1, article list 2, article 3, mail list 4, mail 5, or acknowledgement 6. |
0x01 | variable | response_data | Subtype-specific board, article, mail, or completion data. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00413CE0 | ui_bulletin_session_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00414260 | ui_bulletin_dialog_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00413CE0 ui_bulletin_session_handle_server_packet, Darkages.exe:0x00414260 ui_bulletin_dialog_handle_server_packet, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
UI behavior
An existing BulletinSession claims the packet through its Event type 9 handler and replaces its active child according to the subtype. Board, article-list, article, mail-list, and mail responses construct the correspondingly named dialog. Subtype 6 is accepted without constructing a child. Article-list and mail-list responses require the session request-wait flag.
When no session exists, ui_map_handle_bulletin can construct a server-opened session from this packet. Packet byte +2 bit 0 suppresses that unsolicited creation path when set.
See UI, Input, and Packet Flows.
SStateObjectState (0x32)
Previous: SBulletin | Server action index | Next: SDrawHumanObject
SStateObjectState is server-to-client action 0x32 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. The mapped branch performs inline tile or object updates.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SDrawHumanObject (0x33)
Previous: SStateObjectState | Server action index | Next: SObjectInfo
SDrawHumanObject is server-to-client action 0x33 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SObjectInfo (0x34)
Previous: SDrawHumanObject | Server action index | Next: SShowPaper
SObjectInfo is server-to-client action 0x34 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SShowPaper (0x35)
Previous: SObjectInfo | Server action index | Next: SShowUsers
SShowPaper is server-to-client action 0x35 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
ui_map_dispatch_server_packet calls Darkages.exe:0x0046C3F4 ui_map_handle_show_paper. The handler allocates a 0x560-byte Paper dialog and calls ui_paper_dialog_ctor in mode 1. Its parser copies the server text and flags, then ui_paper_dialog_build_content adds the dialog below BackgroundPane and registers it for Events.
UI flow
This action creates a new dynamic dialog. It does not reuse a static Paper root. SEnterEditingMode 0x1B uses the same class in constructor mode 0. Paper submission can send CExitEditingMode 0x23 with a mode byte, big-endian text length, and normalized text.
See UI, Input, and Packet Flows.
SShowUsers (0x36)
Previous: SShowPaper | Server action index | Next: SAddEquip
SShowUsers is server-to-client action 0x36 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 2 | unknown_00 | Big-endian list metadata retained by the pane. |
0x02 | 2 | user_count | Big-endian number of variable-length user records. |
0x04 | variable | users | Records containing flags, icon or status values, a name up to 48 bytes, another byte, and a secondary string up to 24 bytes. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
ui_map_dispatch_server_packet calls Darkages.exe:0x0046C464 ui_map_handle_show_users. It obtains the static Users Dialog Pane, parses the list, then calls ui_users_dialog_show.
UI flow
The pane is created lazily and hidden when the player first presses E or its window button. That input sends CWho 0x18. SShowUsers is the step that populates row controls, sets the visible state, installs a 100 ms pane timer, and invalidates the pane region.
See UI, Input, and Packet Flows.
SAddEquip (0x37)
Previous: SShowUsers | Server action index | Next: SRemoveEquip
SAddEquip is server-to-client action 0x37 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | slot | Equipment slot index. |
0x01 | 2 | item_id | Big-endian equipment icon or item identifier. |
0x03 | 1 | name_length | Number of following name bytes. |
0x04 | name_length | name | Equipment display name bytes. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0042EAF0 | ui_equip_pane_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0042EAF0 ui_equip_pane_handle_server_packet.
UI behavior
ui_equip_pane_add_item accepts normal slots 1 through 13. It stores the identifier at pane +0xB22 + 2 * slot and the name at +0xB3E + 0x80 * (slot - 1), then redraws. The identifier is native little-endian in memory after the packet’s big-endian value is decoded. This action does not show the equipment pane.
See Runtime UI Memory Map and UI, Input, and Packet Flows.
SRemoveEquip (0x38)
Previous: SAddEquip | Server action index | Next: SSelfLook
SRemoveEquip is server-to-client action 0x38 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | slot | Equipment slot whose identifier and name are cleared. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0042EAF0 | ui_equip_pane_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0042EAF0 ui_equip_pane_handle_server_packet.
UI behavior
ui_equip_pane_remove_item clears the slot identifier and first byte of its name, then redraws. It does not hide the equipment pane.
See UI, Input, and Packet Flows.
SSelfLook (0x39)
Previous: SRemoveEquip | Server action index | Next: SSpelled
SSelfLook is server-to-client action 0x39 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0042EAF0 | ui_equip_pane_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0042EAF0 ui_equip_pane_handle_server_packet calls ui_equip_pane_apply_self_look. The parser replaces appearance bytes, several length-prefixed character strings, a big-endian 32-bit value, and embedded legend/detail state, then invalidates the pane.
UI behavior
This action refreshes the persistent User Equip object but does not show it. Reopening an already selected but hidden equipment pane sends CSelfLook 0x2D and immediately shows the existing object; this response updates it asynchronously.
The static root is ui_equip_pane at Darkages.exe:0x004E32A4, RVA 0x000E32A4.
See UI, Input, and Packet Flows.
SSpelled (0x3A)
Previous: SSelfLook | Server action index | Next: SRequestCRC
SSpelled is server-to-client action 0x3A in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0043E8B0 | sub_43E8B0 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0043E8B0 sub_43E8B0.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SRequestCRC (0x3B)
Previous: SSpelled | Server action index | Next: SMapPart
SRequestCRC is server-to-client action 0x3B in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SMapPart (0x3C)
Previous: SRequestCRC | Server action index | Next: SLevelPoint
SMapPart is server-to-client action 0x3C in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00468A90 ui_map_dispatch_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SLevelPoint (0x3D)
Previous: SMapPart | Server action index | Next: SWindowChange
SLevelPoint is server-to-client action 0x3D in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0043F420 | sub_43F420 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0043F420 sub_43F420.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SWindowChange (0x3E)
Previous: SLevelPoint | Server action index | Next: SActionCoolTime
SWindowChange is server-to-client action 0x3E in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0043D650 | sub_43D650 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x0043D650 sub_43D650.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SActionCoolTime (0x3F)
Previous: SWindowChange | Server action index | Next: SSendPatch
SActionCoolTime is server-to-client action 0x3F in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00442990 | ui_skill_inventory_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00443B10 | ui_spell_inventory_handle_server_packet | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x00442990 ui_skill_inventory_handle_server_packet, Darkages.exe:0x00443B10 ui_spell_inventory_handle_server_packet.
Schema status
The 4.21 client accepts this action in the listed functions. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SSendPatch (0x40)
Previous: SActionCoolTime | Server action index | Next: SExchange
SSendPatch is server-to-client action 0x40 in the 4.21 protocol.
Direction: server to client
Encrypted: No. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004955C0 | sub_4955C0 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x004955C0 sub_4955C0. XOR bypass. May be used during login or lobby.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SExchange (0x42)
Previous: SSendPatch | Server action index | Next: SSpellDelayCancel
SExchange is server-to-client action 0x42 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | subtype | Exchange operation. Values 1 through 5 have mapped Stone branches. |
0x01 | payload_length - 1 | unknown_01 | Subtype-dependent payload bytes whose field boundaries remain to be mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00435EA0 | sub_435EA0 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x004374F0 | sub_4374F0 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00437C70 | sub_437C70 | Accepts the action in its pane’s Event type 9 packet handler. |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Handler notes
Darkages.exe:0x00435EA0 sub_435EA0, Darkages.exe:0x004374F0 sub_4374F0, Darkages.exe:0x00437C70 sub_437C70, Darkages.exe:0x00468A90 ui_map_dispatch_server_packet. Exchange-session handlers dispatch on a second byte with established subtypes 1 through 5.
Schema status
The 4.21 exchange-pane handlers establish the subtype byte and branches 1 through 5. Their individual payload schemas remain to be mapped.
SSpellDelayCancel (0x48)
Previous: SExchange | Server action index | Next: SRequestPortrait
SSpellDelayCancel is server-to-client action 0x48 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004568B0 | sub_4568B0 | Accepts the action in its pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x004568B0 sub_4568B0.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SRequestPortrait (0x49)
Previous: SSpellDelayCancel | Server action index | Next: SBounce
SRequestPortrait is server-to-client action 0x49 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x0040B3A0 | ui_handle_server_request_portrait | Handles the portrait request and starts client portrait serialization. |
Handler notes
Darkages.exe:0x0040B3A0 ui_handle_server_request_portrait. An embedded diagnostic names kServerRequestPortrait and states decimal value 73. The handler initiates client action 0x4F.
Schema status
The 4.21 diagnostic kServerRequestPortrait identifies this action. The handler initiates client action 0x4F; any request payload remains to be mapped.
SBounce (0x4B)
Previous: SRequestPortrait | Server action index | Next: SReconnect
SBounce is server-to-client action 0x4B in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 2 | embedded_length | Unsigned big-endian length of the embedded logical client packet. |
0x02 | embedded_length | embedded_client_packet | Begins with a client-direction action byte and is submitted through net_c_queue_send. |
0x02 + embedded_length | variable | unknown_tail | Any remaining outer payload bytes; their exact boundary is not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Accepts and dispatches the action in MapPane. |
Darkages.exe:0x0046CA54 | net_forward_embedded_client_packet | Reads the embedded length and submits the embedded client packet. |
Handler notes
ui_map_dispatch_server_packet calls net_forward_embedded_client_packet. The helper reads the big-endian embedded length at payload offset 0x00; the embedded logical client packet begins at payload offset 0x02.
Schema status
The 4.21 handler establishes the embedded length and forwarding behavior. The server can therefore select a client action dynamically rather than using one of the fixed action constants found in client builders.
SReconnect (0x4C)
Previous: SBounce | Server action index | Next: SBlockInput
SReconnect is server-to-client action 0x4C in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | 1 | subtype | Value 0x01 completes the Stone safe-exit exchange. Other values are consumed without the mapped side effects. |
0x01 | payload_length - 1 | unknown_01 | Any additional payload bytes. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004921F0 | ui_exit_wait_handle_server_packet | Consumes 0x4C; subtype 0x01 completes the safe-exit exchange. |
Darkages.exe:0x00492310 | ui_exit_wait_pane_ctor | Creates the exit-wait pane, shows the warning, and sends CQuit subtype 0x01. |
Handler notes
Darkages.exe:0x004921F0 ui_exit_wait_handle_server_packet. The Stone handler consumes all 0x4C packets. Subtype 0x01 sends CQuit subtype 0x00 and changes an exit-wait pane to the safe-exit message; it does not call the Socket reconnect path.
Schema status
SReconnect is the 4.21 class name. In the mapped exit-wait path, ui_exit_wait_handle_server_packet claims every 0x4C packet. Subtype 0x01 sets the pane completion flag, sends CQuit subtype 0x00, and replaces the warning with the safe-to-exit text. The paired ui_exit_wait_pane_ctor sends CQuit subtype 0x01 when the wait pane is created. No Socket connection or reconnect function is called by this handler.
SBlockInput (0x51)
Previous: SReconnect | Server action index | Next: SMulti
SBlockInput is server-to-client action 0x51 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Accepts the action in the local-user Pane’s Event type 9 packet handler. |
Handler notes
Darkages.exe:0x004889F0 ui_local_user_handle_server_packet.
Schema status
The 4.21 client accepts this action in the listed function. Payload field division remains a placeholder until its readers and client-side effects are traced end to end.
SMulti (0x56)
Previous: SBlockInput | Server action index
SMulti is server-to-client action 0x56 in the 4.21 protocol.
Direction: server to client
Encrypted: Yes. See Sequence and XOR Transformation.
Payload offsets begin with the first byte after the action. The frame marker, frame length, action, sequence, and trailing zero are excluded.
Payload format
| Offset | Width | Field | Established meaning |
|---|---|---|---|
0x00 | payload_length | unknown_00 | Payload bytes whose field boundaries are not yet mapped. |
Handler functions
| Function address | Current IDA name | Role |
|---|---|---|
Darkages.exe:0x004A2ED0 | ui_server_select_handle_server_packet | Deserializes the server-selection table. |
Handler notes
Darkages.exe:0x004A2ED0 ui_server_select_handle_server_packet. ServerSelectDialogPane reads a count, allocates replacement entry storage, and deserializes fixed-width integers and colon-delimited strings before loading the table.
Schema status
The 4.21 client replaces and deserializes the server-selection table for this action. The exact packet fields will be documented after the reader helpers are traced end to end.
Analyzed Binary
Reproducible findings depend on identifying the exact input. The current local sample is not stored in this repository.
| Property | Value | Source |
|---|---|---|
| Project label | Dark Ages 4.21 Stone client | supplied identification |
| Filename | Darkages.exe | local sample |
| Size | 1,126,476 bytes | local sample measurement |
| SHA-256 | 36093dc1572521ea8c4c5f25548068c971a3d41c7722ef9fe0c05c94e24d6979 | local sample measurement |
| Windows file version | 360, 0, 0, 0 | Windows version resource |
| Windows product version | 360, 0, 0, 0 | Windows version resource |
Open identity question
The working identification is 4.21 Stone client, while the executable’s Windows version resource reports 360, 0, 0, 0. The reason for this mismatch is unknown. Until another internal version marker, release artifact, or independently identified sample resolves it, references to 4.21 describe the project’s supplied identification rather than a value proven from the executable itself.
Reproducing the hash
On PowerShell:
Get-FileHash -Algorithm SHA256 -LiteralPath 'C:\path\to\Darkages.exe'
Do not place the executable in this repository.
Function Map
This appendix is a cross-subsystem index of important functions, grouped by primary subsystem. Cross-subsystem functions appear under the subsystem that owns their main control flow. Detailed decisions belong in the relevant subsystem chapter. An address remains the durable identifier when a name improves later.
Application lifecycle
| Address | Current IDA name | Subsystem | Prototype | Purpose | Detailed page |
|---|---|---|---|---|---|
Darkages.exe:0x0041D390 | app_check_conflicting_software | Application | int __cdecl(void) | Prompt for four process suffixes and two files before startup. | Startup and Shutdown |
Darkages.exe:0x0041D690 | win_top_level_window_exists | Win32 | BOOL __cdecl(LPCSTR class_name, LPCSTR window_title) | Test for a matching top-level window. | Startup and Shutdown |
Darkages.exe:0x0041D6C0 | app_check_runservices_entries | Application | int __cdecl(void) | Examine and optionally delete three RunServices values. | Startup and Shutdown |
Darkages.exe:0x0045B5F0 | app_win_main | Application and Win32 | int __stdcall(HINSTANCE, HINSTANCE, LPSTR, int) | Own class registration, checks, window creation, the message loop, and normal shutdown. | Startup and Shutdown |
Darkages.exe:0x0045B8F0 | app_shutdown | Application | void __cdecl(void) | Delete the initialized subsystem graph in fixed order. | Startup and Shutdown |
Darkages.exe:0x0045C840 | app_set_working_directory_from_command_line | Application | void __cdecl(void) | Parse the executable directory from the raw command line and make it current. | Startup and Shutdown |
Darkages.exe:0x0045CCA0 | app_initialize | Application | void __cdecl(void) | Construct resources, services, screen state, and event workers. | Startup and Shutdown |
Darkages.exe:0x00498430 | app_virus_check_module_callback | Application and Win32 | int __cdecl(void *, void *, const char *, FILE *) | Post the first virus-check module report to the main window. | Input and Windows Events |
Darkages.exe:0x00498780 | app_virus_check_thread | Application | void __cdecl(char *log_filename) | Run the process and module virus checker on a worker thread. | Input and Windows Events |
Darkages.exe:0x0049D790 | ui_screen_pane_activate | UI and Win32 | void __thiscall(void *screen_pane) | Restore active screen and presentation state. | Startup and Shutdown |
Darkages.exe:0x004C62A5 | app_crt_entry | Application and CRT | void __cdecl(void) | Initialize the Microsoft C runtime and call app_win_main. | Startup and Shutdown |
Events
| Address | Current IDA name | Subsystem | Prototype | Purpose | Detailed page |
|---|---|---|---|---|---|
Darkages.exe:0x0040E4D0 | event_deferred_delete_queue_ctor | Events | void *__thiscall(void *queue_object) | Construct the deferred-deletion queue. | Internal Event Routing |
Darkages.exe:0x0040E5C0 | event_deferred_delete_queue_drain | Events | void __thiscall(void *queue_object) | Delete and remove every deferred object. | Internal Event Routing |
Darkages.exe:0x00430F84 | event_dispatcher_dtor | Events and timing | void __thiscall(void *event_dispatcher_object) | Release dispatcher containers and balance multimedia timing state. | Internal Event Routing |
Darkages.exe:0x00430FE0 | event_dispatcher_ctor | Events and timing | void *__thiscall(void *event_dispatcher_object) | Construct the dispatcher, timer list, and worker timing state. | Internal Event Routing |
Darkages.exe:0x00431150 | event_dispatcher_register_pane | Events | void __thiscall(void *event_dispatcher_object, void *pane, int hierarchy, int position) | Register a pane and hierarchy metadata. | Internal Event Routing |
Darkages.exe:0x004311B0 | event_dispatcher_unregister_pane | Events | void __thiscall(void *event_dispatcher_object, void *pane) | Remove a pane from dispatcher traversal. | Internal Event Routing |
Darkages.exe:0x00431200 | event_dispatcher_queue_event_copy | Events | void __thiscall(void *event_dispatcher_object, const void *event) | Shallow-copy one 0x24-byte Event and queue dispatcher work code 3. | Hooks and Injection |
Darkages.exe:0x00431320 | event_dispatcher_insert_timer | Events and timing | void __thiscall(void *event_dispatcher_object, void *receiver, int callback_id, unsigned int delay_ms, int payload_0, int payload_1) | Insert a timer into the absolute-deadline sorted list. | Internal Event Routing |
Darkages.exe:0x00431470 | event_dispatcher_remove_pane_timers | Events and timing | void __thiscall(void *event_dispatcher_object, void *receiver) | Cancel every timer for one pane. | Internal Event Routing |
Darkages.exe:0x004315B0 | event_dispatcher_tick | Events and timing | void __thiscall(void *event_dispatcher_object) | Drain deferred deletion and dispatch at most one due timer. | Internal Event Routing |
Darkages.exe:0x004316E0 | event_dispatcher_process_work_item | Events | void __thiscall(void *event_dispatcher_object, int code, void *data, int value) | Execute dispatcher work codes, including timer insert and cancellation. | Internal Event Routing |
Darkages.exe:0x00431B84 | event_dispatch | Events | int __thiscall(void *this, void *event) | Dispatch one internal Event. | Internal Event Routing |
Darkages.exe:0x00431D54 | event_dispatch_hierarchy | Events | int __thiscall(void *this, void *event, void *hierarchy) | Walk panes and call the type-specific virtual handler. | Internal Event Routing |
Darkages.exe:0x00432514 | event_manager_dtor | Events and network | void __thiscall(void *event_manager_object) | Destroy EventMan and its owned Socket and clear both static roots. | Internal Event Routing |
Darkages.exe:0x00432630 | event_manager_ctor | Events and network | void *__thiscall(void *event_manager_object) | Construct and publish the EventMan singleton. | Internal Event Routing |
Darkages.exe:0x00432BD0 | event_queue_mouse_move | Events and input | void __thiscall(void *, int, int, unsigned int) | Queue copied mouse coordinates and message time as work code 4. | Input and Windows Events |
Darkages.exe:0x00432C50 | event_queue_left_button_down | Events and input | void __thiscall(void *, unsigned int) | Set left-button state and queue work code 5. | Input and Windows Events |
Darkages.exe:0x00432C90 | event_queue_left_button_up | Events and input | void __thiscall(void *, unsigned int) | Clear left-button state and queue work code 6. | Input and Windows Events |
Darkages.exe:0x00432CC0 | event_queue_right_button_down | Events and input | void __thiscall(void *, unsigned int) | Set right-button state and queue work code 7. | Input and Windows Events |
Darkages.exe:0x00432D00 | event_queue_right_button_up | Events and input | void __thiscall(void *, unsigned int) | Clear right-button state and queue work code 8. | Input and Windows Events |
Darkages.exe:0x00432D30 | event_queue_mouse_wheel | Events and input | void __thiscall(void *, int, unsigned int) | Queue wheel delta and message time as work code 9. | Input and Windows Events |
Darkages.exe:0x00432D60 | event_queue_key_down | Events and input | void __thiscall(void *, unsigned char, unsigned int) | Queue a physical key press as work code 0x0A. | Input and Windows Events |
Darkages.exe:0x00432D90 | event_queue_key_up | Events and input | void __thiscall(void *, unsigned char, unsigned int) | Queue a physical key release as work code 0x0B. | Input and Windows Events |
Darkages.exe:0x00432E50 | event_post_socket_bytes | Events and network | void __thiscall(void *this, const uint8_t *packet, int length) | Copy decoded packet bytes to work code 0x0E. | Internal Event Routing |
Darkages.exe:0x00432F10 | event_manager_queue_event_copy | Events | void __thiscall(void *event_manager_object, const void *event) | Shallow-copy one 0x24-byte Event and queue EventMan work code 0x0F. | Hooks and Injection |
Darkages.exe:0x00433060 | event_manager_get_instance | Events | void *__cdecl(void) | Assert and return the EventMan singleton. | Input and Windows Events |
Darkages.exe:0x00433110 | event_process_work_item | Events | void __thiscall(void *this, int code, void *data, int value) | Convert event-manager worker records to Event objects. | Internal Event Routing |
Darkages.exe:0x00433434 | event_dispatch_mouse_move | Events and input | void __thiscall(void *, int, int, unsigned int) | Dispatch mouse subtype 0. | Input and Windows Events |
Darkages.exe:0x00433504 | event_dispatch_left_button_down | Events and input | void __thiscall(void *, unsigned int) | Dispatch left single- or double-button-down input. | Input and Windows Events |
Darkages.exe:0x004336C4 | event_dispatch_left_button_up | Events and input | void __thiscall(void *, unsigned int) | Dispatch left-button release. | Input and Windows Events |
Darkages.exe:0x00433794 | event_dispatch_right_button_down | Events and input | void __thiscall(void *, unsigned int) | Dispatch right single- or double-button-down input. | Input and Windows Events |
Darkages.exe:0x00433944 | event_dispatch_right_button_up | Events and input | void __thiscall(void *, unsigned int) | Dispatch right-button release. | Input and Windows Events |
Darkages.exe:0x00433A14 | event_dispatch_mouse_wheel | Events and input | void __thiscall(void *, int, unsigned int) | Divide the wheel delta by 120 and dispatch subtype 7. | Input and Windows Events |
Darkages.exe:0x00433AB4 | event_dispatch_key_down | Events and input | void __thiscall(void *, unsigned char, unsigned int) | Update mapped key state and dispatch key subtype 8. | Input and Windows Events |
Darkages.exe:0x00433C34 | event_update_key_up | Events and input | void __thiscall(void *, unsigned char, unsigned int) | Clear pressed and modifier state without pane dispatch. | Input and Windows Events |
Darkages.exe:0x00433DC4 | event_queue_socket_packet | Events and network | void __stdcall(uint8_t *packet, uint32_t size) | Queue Event type 9 with packet ownership. | Internal Event Routing |
Darkages.exe:0x00434080 | event_manager_periodic_noop | Events | void __thiscall(void *event_manager_object) | Native no-op EventMan periodic worker callback. | Event Proxy Architecture |
Darkages.exe:0x00470DA0 | util_memory_manager_deleting_dtor | Cross-subsystem memory | established scalar deleting destructor | Clear the MemoryMan root and optionally delete the object. | Data Map |
Darkages.exe:0x00470E10 | util_memory_manager_ctor | Cross-subsystem memory | void *__thiscall(void *memory_manager) | Construct MemoryMan and publish its static root. | Data Map |
Darkages.exe:0x00470F30 | util_memory_manager_dtor | Cross-subsystem memory | void __thiscall(void *memory_manager) | Destroy MemoryMan and clear its static root. | Data Map |
Darkages.exe:0x00470F80 | util_memory_manager_get_instance | Cross-subsystem memory | void *__cdecl(void) | Return the client MemoryMan singleton. | Hooks and Injection |
Darkages.exe:0x00470FC0 | util_memory_manager_alloc | Cross-subsystem memory | void *__thiscall(void *memory_manager, int size) | Allocate through the executable’s CRT wrapper. | Hooks and Injection |
Darkages.exe:0x004710D0 | util_memory_manager_free | Cross-subsystem memory | int __thiscall(void *memory_manager, void *block) | Free through the executable’s CRT wrapper. | Internal Event Routing |
Darkages.exe:0x00497EB0 | util_ring_buffer_ctor | Cross-subsystem worker | void *__thiscall(void *ring_buffer, int element_size, int capacity) | Construct the monitor-protected bounded ring. | Data Map |
Darkages.exe:0x00498080 | util_ring_buffer_push_wait | Cross-subsystem worker | void __thiscall(void *ring_buffer, const void *element) | Wait for capacity and append one record under the queue monitor. | Internal Event Routing |
Darkages.exe:0x004981B0 | util_ring_buffer_pop_wait | Cross-subsystem worker | void __thiscall(void *ring_buffer, void *element_out) | Wait for data and remove one record under the queue monitor. | Internal Event Routing |
Darkages.exe:0x004BE9B0 | util_thread_queue_ctor | Cross-subsystem worker | void *__thiscall(void *worker_object, int queue_capacity) | Construct queue state and a suspended worker thread. | Internal Event Routing |
Darkages.exe:0x004BEC34 | util_thread_queue_add_wait_handle | Cross-subsystem worker | void __thiscall(void *worker_object, void *wait_handle) | Add one handle to the worker’s 16-entry wait set. | Internal Event Routing |
Darkages.exe:0x004BECB0 | util_thread_queue_dtor | Cross-subsystem worker | void __thiscall(void *worker_object) | Terminate the worker and release its handles and queues. | Internal Event Routing |
Darkages.exe:0x004BEDF0 | util_thread_queue_set_wait_timeout | Cross-subsystem worker | void __thiscall(void *worker_object, unsigned int timeout_ms) | Set the WaitForMultipleObjects timeout. | Internal Event Routing |
Darkages.exe:0x004BEE00 | util_thread_queue_start | Cross-subsystem worker | void __thiscall(void *worker_object) | Create the derived wait object and resume the worker. | Internal Event Routing |
Darkages.exe:0x004BF250 | util_thread_queue_worker_loop | Cross-subsystem worker | void __thiscall(void *worker_object) | Wait for work or timeout and invoke derived worker methods. | Internal Event Routing |
Darkages.exe:0x004BF440 | util_thread_queue_post_async | Cross-subsystem worker | void __thiscall(void *worker_object, int code, void *data, int value) | Append an asynchronous record and release the queue semaphore; admission can block on a full ring. | Internal Event Routing |
Networking
| Address | Current IDA name | Subsystem | Prototype | Purpose | Detailed page |
|---|---|---|---|---|---|
Darkages.exe:0x0040A664 | net_c_build_portrait_response | Network and UI | established __thiscall builder | Build client action 0x4F from the local portrait. | Networking |
Darkages.exe:0x0046CA54 | net_forward_embedded_client_packet | Network | established MapPane action handler | Forward the client packet embedded in server action 0x4B. | Client actions |
Darkages.exe:0x004884E4 | net_c_send_move | Network, input, and timing | void __thiscall(struct ui_local_user *, uint8_t) | Queue CMove and start the local movement response timer. | CMove |
Darkages.exe:0x004A3294 | net_socket_dtor | Network | void __thiscall(void *socket_object) | Close transports and tear down the Socket worker. | Networking |
Darkages.exe:0x004A32F0 | net_socket_ctor | Network | void *__thiscall(void *this, void *event_sink) | Construct and initialize the Socket object. | Networking |
Darkages.exe:0x004A3570 | net_c_queue_send | Network | void __thiscall(void *this, const uint8_t *packet, int16_t length) | Copy, terminate, and queue a logical client packet. | Networking |
Darkages.exe:0x004A36C0 | net_queue_xor_key | Network | void __thiscall(void *this, unsigned int key_length, const uint8_t *key) | Copy and queue a negotiated XOR key. | Sequence and XOR Transformation |
Darkages.exe:0x004A3700 | net_queue_xor_table_function | Network | void __thiscall(void *this, unsigned int function_index) | Queue selection of table function 0 through 9. | Sequence and XOR Transformation |
Darkages.exe:0x004A39C0 | net_poll_receive | Network | void __thiscall(void *socket_object) | Poll receive state from the Socket periodic worker callback. | Event Proxy Architecture |
Darkages.exe:0x004A3A74 | net_s_receive_frames | Network | void __thiscall(void *this) | Extract, decode, and post binary frames. | Networking |
Darkages.exe:0x004A44D4 | net_s_decode_packet_body | Network | int __stdcall(const uint8_t *src, int src_size, uint8_t *dest) | Reverse the ordinary server-body XOR transformation. | Sequence and XOR Transformation |
Darkages.exe:0x004A4D34 | net_read_transport_byte | Network | int __thiscall(void *this, uint8_t *out_byte) | Read a cached byte, refilling with recv or ReadFile. | Networking |
Darkages.exe:0x004A54D0 | net_process_work_item | Network | void __thiscall(void *this, int code, void *data, int value) | Execute queued Socket operations. | Networking |
Darkages.exe:0x004A59F4 | net_connect_configured_server | Network | void __thiscall(void *this) | Establish the configured socket and async notification. | Networking |
Darkages.exe:0x004A72B4 | net_c_send_packet_body | Network | void __thiscall(void *this, const uint8_t *packet, int16_t length) | Transform, frame, and call send. | Networking |
Darkages.exe:0x004A79E4 | net_c_encode_packet_body | Network | int __stdcall(const uint8_t *src, int src_size, uint8_t *dest) | Add sequence and apply ordinary client-body XOR passes. | Sequence and XOR Transformation |
Darkages.exe:0x004A8414 | net_set_xor_key | Network | void __stdcall(int length, const uint8_t *key) | Replace and repeat the runtime XOR key. | Sequence and XOR Transformation |
Darkages.exe:0x004A8590 | net_build_xor_table | Network | void __stdcall(int function_index) | Generate the 256-entry XOR table. | Sequence and XOR Transformation |
UI panes and registries
| Address | Current IDA name | Subsystem | Prototype | Purpose | Detailed page |
|---|---|---|---|---|---|
Darkages.exe:0x0040A0A0 | ui_dialog_default_socket_handler | UI and events | int __thiscall(void *, void *) | Decline an unhandled Dialog socket Event. | UI Panes and Registries |
Darkages.exe:0x0040A0B0 | ui_dialog_default_timer_handler | UI and timing | int __thiscall(void *, int, int, int) | Decline an unhandled Dialog timer callback. | UI Panes and Registries |
Darkages.exe:0x0040A5A0 | ui_pane_default_key_handler | UI and events | established Pane virtual handler | Default keyboard handler at vslot +0x3C. | UI Panes and Registries |
Darkages.exe:0x0040A5B0 | ui_background_pane_ctor | UI | established __thiscall constructor | Construct the in-game BackgroundPane. | UI Panes and Registries |
Darkages.exe:0x0040B460 | ui_pane_default_mouse_handler | UI and events | established Pane virtual handler | Default mouse handler at vslot +0x38. | UI Panes and Registries |
Darkages.exe:0x0040B470 | ui_pane_default_socket_handler | UI and events | established Pane virtual handler | Default socket Event handler at vslot +0x40. | UI Panes and Registries |
Darkages.exe:0x0040E660 | ui_bulletin_session_deleting_dtor | UI | established __thiscall deleting destructor | Destroy BulletinSession, its child history, and both registry memberships. | Runtime UI Memory Map |
Darkages.exe:0x0040EA50 | ui_pane_default_timer_handler | UI and timing | established Pane virtual handler | Default timer callback at vslot +0x44. | UI Panes and Registries |
Darkages.exe:0x0040EB10 | ui_bulletin_session_ctor | UI and network | established __thiscall constructor | Construct and publish BulletinSession. | UI, Input, and Packet Flows |
Darkages.exe:0x0040ED04 | ui_bulletin_session_process_packet | UI and network | established __thiscall method | Dispatch SBulletin subtype to a replaceable child dialog. | UI, Input, and Packet Flows |
Darkages.exe:0x00414130 | ui_bulletin_dialog_handle_key_event | UI and events | int __thiscall(void *, void *) | Process shared Bulletin-family keyboard actions. | UI Panes and Registries |
Darkages.exe:0x00414260 | ui_bulletin_dialog_handle_server_packet | UI and events | int __thiscall(void *, void *) | Claim server action 0x31 for Bulletin-family dialogs. | UI Panes and Registries |
Darkages.exe:0x004142E0 | ui_bulletin_dialog_handle_mouse_event | UI and events | int __thiscall(void *, void *) | Add Bulletin-family mouse capture and release behavior. | UI Panes and Registries |
Darkages.exe:0x00414BD0 | ui_article_list_dialog_start_new_article_editing | UI | established __thiscall method | Start new-article editing from ArticleListDialog. | Pane Virtual Table Inventory |
Darkages.exe:0x00415A10 | ui_article_list_dialog_handle_key_event | UI and events | int __thiscall(void *, void *) | Process ArticleListDialog keyboard actions. | UI Panes and Registries |
Darkages.exe:0x00416B20 | ui_article_list_pane_draw_cell | UI | established DrawCell method | Draw one ArticleListPane cell. | Pane Virtual Table Inventory |
Darkages.exe:0x004181A0 | ui_new_article_dialog_handle_key_event | UI and events | int __thiscall(void *, void *) | Process NewArticleDialog editing keys. | UI Panes and Registries |
Darkages.exe:0x004184C0 | ui_mail_list_dialog_handle_key_event | UI and events | int __thiscall(void *, void *) | Process MailListDialog keyboard actions. | UI Panes and Registries |
Darkages.exe:0x00419FE0 | ui_mail_list_pane_handle_selected_cell_double_click | UI | established __thiscall method | Open the selected mail from a list-cell double-click. | Pane Virtual Table Inventory |
Darkages.exe:0x0041A080 | ui_mail_list_pane_draw_cell | UI | established DrawCell method | Draw one MailListPane cell. | Pane Virtual Table Inventory |
Darkages.exe:0x0041AE70 | ui_new_mail_dialog_handle_key_event | UI and events | int __thiscall(void *, void *) | Process NewMailDialog editing keys. | UI Panes and Registries |
Darkages.exe:0x0041C4D0 | ui_legend_dialog_pane_ctor | UI | established __thiscall constructor | Construct LegendDialogPane and its inner list control. | Pane Virtual Table Inventory |
Darkages.exe:0x0041C770 | ui_legend_dialog_pane_handle_mouse_event | UI and events | int __thiscall(void *, void *) | Add LegendDialogPane left- and right-button behavior. | UI Panes and Registries |
Darkages.exe:0x00423984 | ui_lag_indicator_pane_dtor | UI | void __thiscall(struct ui_lag_indicator_pane *) | Destroy the persistent lag indicator and clear its root. | Runtime UI Memory Map |
Darkages.exe:0x004239C0 | ui_get_lag_indicator_pane | UI | struct ui_lag_indicator_pane *__cdecl(void) | Return the persistent lag indicator pointer. | Runtime UI Memory Map |
Darkages.exe:0x004239D0 | ui_lag_indicator_pane_ctor | UI | struct ui_lag_indicator_pane *__thiscall(struct ui_lag_indicator_pane *, void *) | Construct and publish the lag indicator. | Runtime UI Memory Map |
Darkages.exe:0x00423A44 | ui_lag_indicator_pane_attach | UI | void __thiscall(struct ui_lag_indicator_pane *, void *) | Add and register the lag indicator below BackgroundPane. | Runtime UI Memory Map |
Darkages.exe:0x00423AA0 | ui_lag_indicator_pane_draw | UI and rendering | void __thiscall(struct ui_lag_indicator_pane *) | Select the lag indicator frame from the smoothed value. | Runtime UI Memory Map |
Darkages.exe:0x00423B60 | ui_lag_indicator_update_from_local_user | UI and timing | void __thiscall(struct ui_lag_indicator_pane *) | Average the newest movement response sample and invalidate the Pane. | Runtime UI Memory Map |
Darkages.exe:0x0042A190 | ui_dialog_handle_mouse_event | UI and events | int __thiscall(void *, void *) | Process generic Dialog mouse subtypes. | UI Panes and Registries |
Darkages.exe:0x0042AED0 | ui_dialog_handle_key_event | UI and events | int __thiscall(void *, void *) | Process generic Dialog keys and focused controls. | UI Panes and Registries |
Darkages.exe:0x0042E1A0 | ui_equip_pane_ctor | UI | established __thiscall constructor | Construct and publish the persistent User Equip pane. | UI, Input, and Packet Flows |
Darkages.exe:0x0042EAF0 | ui_equip_pane_handle_server_packet | UI and network | int __thiscall(void *, void *) | Dispatch SAddEquip, SRemoveEquip, and SSelfLook. | UI, Input, and Packet Flows |
Darkages.exe:0x0042EBA4 | ui_equip_pane_apply_self_look | UI and character state | established __thiscall parser | Apply SSelfLook without changing visibility. | UI, Input, and Packet Flows |
Darkages.exe:0x0043A5F0 | ui_game_buttons_pane_ctor | UI | void *__thiscall(void *, void *, void *) | Construct GameButtonsPane and its persistent content graph. | Runtime UI Memory Map |
Darkages.exe:0x0043CF20 | ui_game_buttons_handle_key_event | UI and input | int __thiscall(void *, void *) | Map A/S/D/F/G to persistent content panes. | UI, Input, and Packet Flows |
Darkages.exe:0x0043D164 | ui_game_buttons_select_content | UI | void __thiscall(void *, int8_t) | Dispatch selection of equipment, skills, spells, chat, or status. | UI, Input, and Packet Flows |
Darkages.exe:0x0043D3D4 | ui_game_buttons_select_inventory | UI and network | void __thiscall(void *) | Select or toggle equipment and request self-look on hidden reopen. | UI, Input, and Packet Flows |
Darkages.exe:0x0043E1B4 | ui_window_buttons_select | UI and network | void __thiscall(void *, int8_t) | Dispatch Q/W/E window-button actions. | UI, Input, and Packet Flows |
Darkages.exe:0x0043E2B4 | ui_window_buttons_show_option_pane | UI | void __thiscall(void *) | Allocate and construct dynamic OptionPane. | UI, Input, and Packet Flows |
Darkages.exe:0x0043E384 | net_c_send_who_request | Network and UI | void __cdecl(void) | Send one-byte CWho for Users Dialog Pane. | CWho |
Darkages.exe:0x0043E700 | ui_users_dialog_show | UI and timing | established __thiscall wrapper | Enter the shared show body for the populated users pane. | SShowUsers |
Darkages.exe:0x00442A34 | ui_skill_inventory_apply_add_packet | UI and network | int __thiscall(void *, const uint8_t *) | Parse SAddSkill and replace one of 36 slot children. | SAddSkill |
Darkages.exe:0x00442C04 | ui_skill_inventory_remove_slot_pane | UI | void __thiscall(void *, uint8_t) | Remove, unregister, delete, and clear an indexed skill pane. | Runtime UI Memory Map |
Darkages.exe:0x00442D14 | ui_skill_inventory_create_slot_pane | UI | void __thiscall(void *, uint8_t, uint16_t, const char *) | Allocate and register an indexed skill pane. | Runtime UI Memory Map |
Darkages.exe:0x00443BB4 | ui_spell_inventory_apply_add_packet | UI and network | int __thiscall(void *, const uint8_t *) | Parse SAddSpell and replace one of 36 slot children. | SAddSpell |
Darkages.exe:0x00443E14 | ui_spell_inventory_remove_slot_pane | UI | void __thiscall(void *, uint8_t) | Remove, unregister, delete, and clear an indexed spell pane. | Runtime UI Memory Map |
Darkages.exe:0x00443F24 | ui_spell_inventory_create_slot_pane | UI | void __thiscall(void *, uint8_t, uint16_t, int8_t, const char *, const char *, uint8_t) | Allocate and register an indexed spell pane. | Runtime UI Memory Map |
Darkages.exe:0x00446090 | ui_point_init | UI | void __cdecl(void *, int, int) | Initialize a two-coordinate client point. | Runtime UI Memory Map |
Darkages.exe:0x004460B0 | ui_rect_init | UI | void __cdecl(void *, int, int, int, int) | Initialize a client rectangle while preserving its non-Win32 field order. | Runtime UI Memory Map |
Darkages.exe:0x00454CA0 | ui_skill_slot_finish_cooldown | UI and timing | established __thiscall method | Clear a skill slot’s cooldown-active state. | Runtime UI Memory Map |
Darkages.exe:0x00454CC0 | ui_skill_slot_start_cooldown | UI and timing | established __thiscall method | Set a skill slot’s cooldown-active state. | Runtime UI Memory Map |
Darkages.exe:0x004544B0 | ui_skill_slot_handle_mouse_event | UI and input | int __thiscall(void *, void *) | Open skill details or activate a skill slot. | UI, Input, and Packet Flows |
Darkages.exe:0x00455040 | ui_spell_slot_handle_mouse_event | UI and input | int __thiscall(void *, void *) | Open spell details or enter a spell cast path. | UI, Input, and Packet Flows |
Darkages.exe:0x00455684 | ui_spell_slot_begin_cast | UI and network | void __thiscall(void *) | Build the confirmed two-byte CUseSpell form. | CUseSpell |
Darkages.exe:0x004556F4 | ui_spell_delay_begin | UI, network, and timing | established __thiscall method | Hold or immediately submit a built spell packet. | UI, Input, and Packet Flows |
Darkages.exe:0x00456220 | ui_spell_slot_finish_cooldown | UI and timing | established __thiscall method | Clear a spell slot’s cooldown-active state. | Runtime UI Memory Map |
Darkages.exe:0x00456240 | ui_spell_slot_start_cooldown | UI and timing | established __thiscall method | Set a spell slot’s cooldown-active state. | Runtime UI Memory Map |
Darkages.exe:0x00456940 | ui_spell_delay_handle_timer | UI, network, and timing | int __thiscall(void *, int, int, int) | Emit delay phrases and submit the held spell on the final tick. | UI, Input, and Packet Flows |
Darkages.exe:0x00456B90 | ui_skill_slot_pane_ctor | UI | void *__thiscall(void *, uint8_t, uint16_t, const char *) | Initialize a persistent skill slot child. | Runtime UI Memory Map |
Darkages.exe:0x00456C70 | ui_spell_slot_pane_ctor | UI | void *__thiscall(void *, uint8_t, uint16_t, int8_t, const char *, const char *, uint8_t) | Initialize a persistent spell slot child. | Runtime UI Memory Map |
Darkages.exe:0x0044E4D0 | ui_hierarchy_list_ctor | UI and events | void *__thiscall(void *, int) | Construct a packed hierarchy list. | UI Panes and Registries |
Darkages.exe:0x004594A0 | ui_hierarchy_get_node | UI and events | void *__thiscall(void *, int) | Return a checked packed-node address. | UI Panes and Registries |
Darkages.exe:0x0045D930 | ui_main_menu_pane_ctor | UI | established __thiscall constructor | Construct and publish MainMenuPane. | UI Panes and Registries |
Darkages.exe:0x0045FA44 | ui_main_menu_create_game_panes | UI | established __thiscall transition method | Build and publish the in-game pane graph. | UI Panes and Registries |
Darkages.exe:0x004658C0 | ui_map_pane_ctor | UI | established __thiscall constructor | Construct the 0xF38-byte MapPane. | UI Panes and Registries |
Darkages.exe:0x00467320 | ui_map_handle_key_event | UI and events | established Pane virtual handler | Handle MapPane keyboard Events. | UI Panes and Registries |
Darkages.exe:0x00467360 | ui_map_handle_mouse_event | UI and events | established Pane virtual handler | Handle MapPane mouse Events. | UI Panes and Registries |
Darkages.exe:0x00469474 | ui_map_handle_screen_menu | UI and network | established MapPane action handler | Allocate and construct an SScreenMenu pane. | SScreenMenu |
Darkages.exe:0x0046C004 | ui_map_handle_pursuit_menu | UI and network | established MapPane action handler | Allocate one of seven SPursuitMenu dialog variants. | SPursuitMenu |
Darkages.exe:0x0046C3F4 | ui_map_handle_show_paper | UI and network | established MapPane action handler | Allocate Paper in server-show mode. | SShowPaper |
Darkages.exe:0x0046C464 | ui_map_handle_show_users | UI and network | established MapPane action handler | Populate and show Users Dialog Pane. | SShowUsers |
Darkages.exe:0x0046CAB4 | ui_map_handle_bulletin | UI and network | established MapPane action handler | Create a server-opened BulletinSession when allowed. | SBulletin |
Darkages.exe:0x00471D10 | ui_screen_menu_pane_ctor | UI and network | established __thiscall constructor | Add, register, and parse a full-screen server menu. | SScreenMenu |
Darkages.exe:0x0047DBA0 | ui_question_message_dialog_handle_mouse_event | UI and events | int __thiscall(void *, void *) | Add drag behavior for the question-dialog family. | UI Panes and Registries |
Darkages.exe:0x0047EFC0 | ui_question_message_dialog_ctor | UI and network | established __thiscall constructor | Construct SPursuitMenu subtype 2 question dialog. | SPursuitMenu |
Darkages.exe:0x0047F9C0 | ui_question_message_dialog_send_answer | UI and network | established __thiscall method | Send the selected answer from QuestionMessageDialog. | Pane Virtual Table Inventory |
Darkages.exe:0x004805E0 | ui_question_message_face_dialog_send_answer | UI and network | established __thiscall method | Send the selected answer from QuestionMessageFaceDialog. | Pane Virtual Table Inventory |
Darkages.exe:0x004862E0 | ui_monster_pane2_start_move | UI and world state | established __thiscall method | Start a MonsterPane2 movement transition. | Pane Virtual Table Inventory |
Darkages.exe:0x004876C4 | ui_local_user_dtor | UI and world state | void __thiscall(struct ui_local_user *) | Destroy the local-user Pane and clear its class-level alias. | Runtime UI Memory Map |
Darkages.exe:0x00487700 | ui_local_user_ctor | UI and world state | struct ui_local_user *__thiscall(struct ui_local_user *, void *) | Construct and publish the active local-user Pane. | Runtime UI Memory Map |
Darkages.exe:0x00487860 | ui_get_local_user | UI and world state | struct ui_local_user *__cdecl(void) | Return the active local-user pointer. | Runtime UI Memory Map |
Darkages.exe:0x0048D2E0 | ui_effect_object_pane_handle_timer | UI and timing | established Pane timer handler | Process EffectObjectPane timer callbacks. | Pane Virtual Table Inventory |
Darkages.exe:0x0048F600 | ui_option_pane_ctor | UI | void *__thiscall(void *) | Build, add, and register dynamic OptionPane. | UI, Input, and Packet Flows |
Darkages.exe:0x004909E0 | ui_option_pane_handle_server_packet | UI and network | int __thiscall(void *, void *) | Claim SSelfSaveOk and create a confirmation dialog. | SSelfSaveOk |
Darkages.exe:0x00490A80 | ui_option_pane_handle_mouse_event | UI and input | int __thiscall(void *, void *) | Handle OptionPane mouse state. | Pane Virtual Table Inventory |
Darkages.exe:0x00490D70 | ui_option_pane_handle_timer | UI and timing | int __thiscall(void *, int, int, int) | Advance OptionPane timer state. | Pane Virtual Table Inventory |
Darkages.exe:0x00490FE0 | ui_option_pane_handle_key_event | UI and input | int __thiscall(void *, void *) | Handle OptionPane keyboard shortcuts. | Pane Virtual Table Inventory |
Darkages.exe:0x00491AA4 | net_c_send_user_setting | Network and UI | void __thiscall(void *, uint8_t) | Send CUserSetting from an option control. | CUserSetting |
Darkages.exe:0x00492A90 | ui_pane_dtor | UI | void __thiscall(void *) | Cancel timers and destroy the Pane base. | UI Panes and Registries |
Darkages.exe:0x00492AD0 | ui_pane_ctor | UI | void *__thiscall(void *, unsigned char, unsigned char) | Construct common Pane fields and install the base vtable. | UI Panes and Registries |
Darkages.exe:0x00492C10 | ui_pane_is_visible | UI | int __thiscall(void *) | Query common pane visible state. | UI, Input, and Packet Flows |
Darkages.exe:0x00492C40 | ui_pane_show | UI | void __thiscall(void *) | Set visible state and invalidate the pane. | UI Panes and Registries |
Darkages.exe:0x00492D50 | ui_pane_hide | UI | void __thiscall(void *) | Clear visible state and invalidate the exposed region. | UI Panes and Registries |
Darkages.exe:0x00493240 | ui_pane_get_bound_rect | UI | void __thiscall(void *, int32_t *) | Translate a Pane’s local bounds by its relative origin. | Runtime UI Memory Map |
Darkages.exe:0x004932B0 | ui_pane_set_bound_rect | UI | void __thiscall(void *, const int32_t *) | Split Pane bounds into relative origin and local rectangle. | Runtime UI Memory Map |
Darkages.exe:0x004933C0 | ui_pane_add_to_screen | UI | void __thiscall(void *, const int32_t *, void *, void *) | Add a Pane to Screen composition using a client-order rectangle. | UI Panes and Registries |
Darkages.exe:0x00493480 | ui_pane_remove_from_screen | UI | void __thiscall(void *) | Remove a Pane from Screen composition. | UI Panes and Registries |
Darkages.exe:0x00493530 | ui_pane_register_events | UI and events | void __thiscall(void *, void *, int) | Register a Pane in event traversal. | UI Panes and Registries |
Darkages.exe:0x004935D0 | ui_pane_unregister_events | UI and events | void __thiscall(void *) | Remove a Pane from event traversal. | UI Panes and Registries |
Darkages.exe:0x004939C0 | ui_paper_dialog_ctor | UI and network | established __thiscall constructor | Parse and construct Paper in mode 0 or 1. | UI, Input, and Packet Flows |
Darkages.exe:0x00493B60 | ui_paper_dialog_handle_completion | UI and network | int __thiscall(void *, int, int) | Optionally submit Paper content, then close the dialog. | CExitEditingMode |
Darkages.exe:0x00494100 | net_c_send_exit_editing_mode | Network and UI | void __thiscall(void *) | Serialize current Paper content as action 0x23. | CExitEditingMode |
Darkages.exe:0x00498F20 | ui_screen_registry_ctor | UI | void *__thiscall(void *) | Construct the Screen composition hierarchy. | UI Panes and Registries |
Darkages.exe:0x00498F40 | ui_screen_add_pane | UI | established __thiscall method | Validate and insert a Screen node. | UI Panes and Registries |
Darkages.exe:0x004993C4 | ui_screen_find_pane_node | UI | established recursive __thiscall search | Find a pane’s packed Screen node. | UI Panes and Registries |
Darkages.exe:0x00499530 | ui_screen_remove_pane | UI | established __thiscall method | Remove a Screen node and child hierarchy. | UI Panes and Registries |
Darkages.exe:0x00499ED0 | ui_screen_get_pane_origin | UI | established __thiscall query | Accumulate a pane’s screen-space origin. | UI Panes and Registries |
Darkages.exe:0x004A1680 | ui_scrollable_pane_handle_mouse_event | UI and events | int __thiscall(void *, void *) | Route mouse input through scroll bars and list content. | UI Panes and Registries |
Darkages.exe:0x004A1900 | ui_scrollable_pane_handle_key_event | UI and events | int __thiscall(void *, void *) | Route keyboard input through scroll bars and list content. | UI Panes and Registries |
Darkages.exe:0x004A1B04 | ui_server_select_dialog_pane_dtor | UI | established __thiscall destructor | Unregister and remove ServerSelectDialogPane. | UI Panes and Registries |
Darkages.exe:0x004A2740 | ui_server_select_dialog_pane_ctor | UI | established __thiscall constructor | Construct, add, and register ServerSelectDialogPane. | UI Panes and Registries |
Darkages.exe:0x004AC944 | ui_terminal_pane_dtor | UI | established __thiscall destructor | Clear the terminal root and destroy derived state. | UI Panes and Registries |
Darkages.exe:0x004ACA00 | ui_terminal_pane_ctor | UI | established __thiscall constructor | Construct, publish, add, and register TerminalPane. | UI Panes and Registries |
Darkages.exe:0x004AD130 | ui_get_terminal_pane | UI | void *__cdecl(void) | Return the static TerminalPane pointer. | UI Panes and Registries |
Darkages.exe:0x004BE6F4 | ui_text_edit_handle_key_event | UI and events | established Pane virtual handler | Complete or cancel a temporary TextEdit pane. | UI Panes and Registries |
Darkages.exe:0x004C2D90 | ui_weather_pane_draw | UI and rendering | established Draw method | Draw WeatherPane cloud and weather state. | Pane Virtual Table Inventory |
UI protocol dispatch
| Address | Current IDA name | Subsystem | Prototype | Purpose | Detailed page |
|---|---|---|---|---|---|
Darkages.exe:0x0040B3A0 | ui_handle_server_request_portrait | Network and UI | int __thiscall(void *this, void *event) | Handle server action 0x49. | Server actions |
Darkages.exe:0x0045F780 | ui_main_menu_handle_server_packet | Network and UI | int __thiscall(void *this, void *event) | Handle initial main-menu server packets. | Server actions |
Darkages.exe:0x00468A90 | ui_map_dispatch_server_packet | Network and UI | int __thiscall(void *this, void *event) | Dispatch 31 in-game server actions. | Server actions |
Darkages.exe:0x0046B574 | ui_map_handle_object_direction | Network and UI | int __thiscall(void *this, const uint8_t *packet) | Apply server action 0x11 to a map object’s direction. | SChangeDirection |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | Network, UI, and world state | int __thiscall(struct ui_local_user *, void *) | Dispatch the server actions owned by the local-user Pane. | SMove |
Darkages.exe:0x004897C4 | ui_local_user_handle_smove | Network, UI, and timing | int __thiscall(struct ui_local_user *, const uint8_t *) | Apply SMove and update movement response timing. | SMove |
Darkages.exe:0x004921F0 | ui_exit_wait_handle_server_packet | Network and UI | int __thiscall(void *this, void *event) | Complete the subtype-one safe-exit exchange for server action 0x4C. | SReconnect |
Darkages.exe:0x00492310 | ui_exit_wait_pane_ctor | UI and network | established __thiscall constructor | Create the exit-wait pane and send CQuit subtype 0x01. | SReconnect |
Darkages.exe:0x004A2ED0 | ui_server_select_handle_server_packet | Network and UI | int __thiscall(void *this, void *event) | Deserialize the action 0x56 server table. | Server actions |
Input and Windows integration
| Address | Current IDA name | Subsystem | Prototype | Purpose | Detailed page |
|---|---|---|---|---|---|
Darkages.exe:0x0045BDC0 | win_main_window_proc | Win32, UI, input, and network | LRESULT __stdcall(HWND, UINT, WPARAM, LPARAM) | Route the 15 explicit DAClass messages and delegate all others. | Input and Windows Events |
Darkages.exe:0x0049DA30 | render_screen_pane_load_palette | Rendering and Win32 | void __thiscall(void *, const char *) | Load and apply the 256-entry screen palette. | Input and Windows Events |
Darkages.exe:0x0049E820 | ui_screen_pane_set_cursor_position | UI and input | void __thiscall(void *, int, int) | Update the 32 by 32 software-cursor rectangle. | Input and Windows Events |
Entry requirements
Add a function when it is useful outside its immediate subsystem context. Record the calling convention and types established by the code, and link to the detailed subsystem explanation.
Data Map
This appendix tracks important globals, tables, structures, virtual tables, and persistent buffers. See Runtime UI Memory Map for process-relative pointer chains, Pane tree traversal, class-specific UI fields, and safe snapshot guidance. C Structure Layouts provides packed C99 views of the same established fields without replacing the detailed offset tables below.
| Address | Current IDA name | Type or size | Owner | Purpose | Detailed page | Notes |
|---|---|---|---|---|---|---|
Darkages.exe:0x004D64C0 | app_window_active | uint8_t | Application and Win32 | Records whether the main window is treated as active. | Startup and Shutdown | Set before the message loop and by WM_ACTIVATEAPP handling. |
Darkages.exe:0x004E0F00 | net_xor_table | uint32_t[256] | Socket transformation | Generated XOR byte table. | Sequence and XOR Transformation | Each dword repeats one byte four times. Initial contents implement function 0. |
Darkages.exe:0x004E2D70 | ui_bulletin_session | void * | UI and bulletin/mail | Static root of the active heap BulletinSession Pane. | Runtime UI Memory Map | Ten child pointers begin at object +0xF8, current index is +0xF5, active child is +0x120, and the request-wait byte is +0x124. RVA 0x000E2D70. |
Darkages.exe:0x004E2E40 | ui_lag_indicator_pane | void *, object 0xF8 bytes | UI and timing | Static root of the persistent movement-latency indicator. | Runtime UI Memory Map | Smoothed millisecond value is at pane +0xF4. RVA 0x000E2E40. |
Darkages.exe:0x004E32A4 | ui_equip_pane | void * | UI and character state | Static root of the persistent heap User Equip pane. | Runtime UI Memory Map | Slot identifier expression is +0xB22 + 2 * slot; names use +0xB3E + 0x80 * (slot - 1); embedded legend/details ownership is at +0x11CC. RVA 0x000E32A4. |
Darkages.exe:0x004E32C0 | event_manager_instance | void * | Events and network | EventMan singleton that converts worker records into Event objects. | Internal Event Routing | Published by event_manager_ctor and started separately from event_dispatcher. |
Darkages.exe:0x004E3564 | ui_users_dialog_pane | void * | UI | Static root of the heap Users Dialog Pane. | UI, Input, and Packet Flows | Null before the first CWho UI action creates the pane; reused and shown by later SShowUsers replies. RVA 0x000E3564. |
Darkages.exe:0x004F5198 | ui_local_user | void *, object 0x776C bytes | UI, character, and timing | Owning in-game root of the local user object. | Runtime UI Memory Map | Movement sequence and timing fields are at +0x6F28 through +0x6F34. RVA 0x000F5198. |
Darkages.exe:0x004F51AC | ui_main_menu_pane | void *, object 0x124 bytes | UI | Current heap MainMenuPane. | UI Panes and Registries | Non-null during the main-menu phase and cleared on transition or teardown. RVA 0x000F51AC. |
Darkages.exe:0x004F51B0 | ui_map_pane | void *, object 0xF38 bytes | UI | Current heap MapPane. | UI Panes and Registries | Published after game-pane creation and cleared during game teardown. RVA 0x000F51B0. |
Darkages.exe:0x004F51B4 | ui_background_pane | void *, object 0x144 bytes | UI | Current heap BackgroundPane. | UI Panes and Registries | Published with the game UI and cleared during game teardown. RVA 0x000F51B4. |
Darkages.exe:0x004F51B8 | event_deferred_delete_queue | void * | Events | Holds objects awaiting deletion by the dispatcher tick. | Internal Event Routing | Created during initialization, drained from last element to first, and deleted after the dispatcher. |
Darkages.exe:0x004F51BC | net_socket_instance | void * | Network and EventMan | Static root of the active heap Socket object. | IPC, Rules, and State | EventMan owns the Socket at object +0x68, publishes this root during construction, and clears it during destruction. RVA 0x000F51BC. |
Darkages.exe:0x004F51C4 | app_config | void * | Application | Global configuration object. | Startup and Shutdown | Constructed from Darkages.cfg when English.nfo exists, otherwise from Legend.cfg. |
Darkages.exe:0x004F51C8 | ui_screen_pane | void * | UI and rendering | Global 640 by 480, 8-bit screen pane. | Startup and Shutdown | Registered with the event dispatcher and reactivated around the message loop. |
Darkages.exe:0x004F51CC | ui_screen_registry | void * | UI | Heap Screen composition hierarchy. | UI Panes and Registries | Stride 0x3F; list fields are at +0x0C, +0x14, +0x18, and +0x1C. RVA 0x000F51CC. |
Darkages.exe:0x004F51D0 | event_dispatcher | void * | Events | Global event dispatcher and timer-worker object. | Internal Event Routing | Created and started by app_initialize; destroyed by app_shutdown. |
Darkages.exe:0x004F51D4 | app_error_code | int32_t | Application | Process-wide subsystem initialization error. | Startup and Shutdown | Checked after most constructors and before the message loop. |
Darkages.exe:0x004F51DC | app_main_window | HWND | Application and Win32 | Main DAClass window handle. | Startup and Shutdown | A null result from CreateWindowExA is stored but not rejected. |
Darkages.exe:0x004F51E0 | app_instance | HINSTANCE | Application and Win32 | Saved process instance handle. | Startup and Shutdown | Assigned by app_win_main after class registration. |
Darkages.exe:0x004F51F4 | app_shutdown_requested | uint8_t | Application | Requests termination of the blocking message loop. | Startup and Shutdown | Observed only after GetMessageA returns. Several writers also post a message to wake the loop. |
Darkages.exe:0x004FA0E0 | app_single_instance_mutex | HANDLE | Application | Handle for Nexon.SingleInstance. | Startup and Shutdown | Closed and cleared only after normal app_shutdown. |
Darkages.exe:0x004FA160 | util_memory_manager_instance | void * | Cross-subsystem memory | Static MemoryMan root used by client allocation and free wrappers. | Hooks and Injection | Required when an injected server packet will be released by dispatcher cleanup. RVA 0x000FA160. |
Darkages.exe:0x004FC244 | ui_local_user_singleton | void * | UI and character state | Class-level alias of the active local user object. | Runtime UI Memory Map | Set by ui_local_user_ctor and cleared by ui_local_user_dtor. RVA 0x000FC244. |
Darkages.exe:0x004FD320 | app_virus_check_failure_detail | char[256] | Application checks | Detail inserted into the virus-scan failure prompt. | Input and Windows Events | Used when WM_USER + 0x2046 arrives with zero wParam. |
Darkages.exe:0x004FD420 | app_virus_report_module | char[256] | Application checks | First infected-module name reported to the window thread. | Input and Windows Events | Its address is posted in wParam for WM_USER + 0x2046. |
Darkages.exe:0x004FD5A0 | net_xor_key | key storage, at least 13 bytes | Socket transformation | Runtime repeating XOR key. | Sequence and XOR Transformation | Constructor default is ASCII NexonInc.. Negotiated updates require at most 9 bytes. |
Darkages.exe:0x004FD5AC | net_xor_key_length | int32_t | Socket transformation | Number of bytes in the active key. | Sequence and XOR Transformation | Default is 9. |
Darkages.exe:0x004FD5B0 | net_xor_key_repeated | uint8_t[48] | Socket transformation | Four adjacent copies of the key. | Sequence and XOR Transformation | Supports optimized four-byte XOR loops. |
Darkages.exe:0x004FD5E0 | net_c_send_sequence | uint8_t | Socket transformation | Sequence inserted after ordinary transformed client actions. | Sequence and XOR Transformation | Zero-initialized at process load and incremented modulo 256 only by the transformed client encoder; cleartext bypass actions do not consume it. |
Darkages.exe:0x004FD640 | ui_terminal_pane | void *, object 0xF78 bytes | UI | Current heap TerminalPane. | UI Panes and Registries | Published by the constructor, returned by ui_get_terminal_pane, and cleared by the destructor. RVA 0x000FD640. |
Darkages.exe:0x005002C0 | ui_aggrement_dialog_pane_vtable | virtual table | AggrementDialogPane | Runtime class discriminator for the agreement dialog. | Pane Virtual Table Inventory | Aggrement is the spelling retained in the binary. |
Darkages.exe:0x00500700 | ui_background_pane_vtable | virtual table | UI | Concrete BackgroundPane handler table. | UI Panes and Registries | Socket slot +0x40 handles server portrait requests. |
Darkages.exe:0x00501E60 | ui_bulletin_session_vtable | virtual table | BulletinSession | Runtime class discriminator for the bulletin and mail session Pane. | Runtime UI Memory Map | Socket slot +0x40 claims action 0x31. RVA 0x00101E60. |
Darkages.exe:0x00501EC0 | ui_bulletin_dialog_pane_vtable | virtual table | BulletinDialogPane | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Socket slot claims action 0x31. |
Darkages.exe:0x00501F40 | ui_article_list_dialog_vtable | virtual table | ArticleListDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Custom keyboard and editing handlers. |
Darkages.exe:0x00501FC0 | ui_mail_list_dialog_vtable | virtual table | MailListDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Custom keyboard and completion handlers. |
Darkages.exe:0x005020C0 | ui_new_article_dialog_vtable | virtual table | NewArticleDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Control-click handler is at vslot +0x4C. |
Darkages.exe:0x00502140 | ui_new_mail_dialog_vtable | virtual table | NewMailDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Control-click handler is at vslot +0x4C. |
Darkages.exe:0x005021C0 | ui_board_list_dialog_vtable | virtual table | BoardListDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Has a class-specific timer override. |
Darkages.exe:0x00502240 | ui_article_dialog_vtable | virtual table | ArticleDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Bulletin-family mouse and socket handlers. |
Darkages.exe:0x005022C0 | ui_mail_dialog_vtable | virtual table | MailDialog | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Bulletin-family mouse and socket handlers. |
Darkages.exe:0x005023E0 | ui_article_list_pane_vtable | virtual table | ArticleListPane | Runtime class discriminator for the scrollable article list. | Pane Virtual Table Inventory | DrawCell virtual is at +0x84. |
Darkages.exe:0x00502580 | ui_mail_list_pane_vtable | virtual table | MailListPane | Runtime class discriminator for the scrollable mail list. | Pane Virtual Table Inventory | Selection and DrawCell virtuals are at +0x80/+0x84. |
Darkages.exe:0x00506C80 | ui_legend_dialog_pane_vtable | virtual table | LegendDialogPane | Runtime class discriminator for the outer character-legend dialog. | Pane Virtual Table Inventory | The inner control vtable at 0x00506BE0 remains unnamed. |
Darkages.exe:0x00509C20 | ui_lag_indicator_pane_vtable | virtual table | lag indicator pane | Runtime discriminator for the persistent movement-latency indicator. | Runtime UI Memory Map | Draw vslot +0x48 selects one of four statcon.epf frames. RVA 0x00109C20. |
Darkages.exe:0x0050C6C0 | ui_equip_pane_vtable | virtual table | User Equip pane | Runtime discriminator for the persistent equipment and self-look Pane. | Runtime UI Memory Map | RVA 0x0010C6C0. |
Darkages.exe:0x0050D180 | event_dispatcher_vtable | void *[8] | Events | Virtual table for pane dispatch, work processing, wait handling, and periodic ticks. | Internal Event Routing | Slot +0x10 points to event_dispatcher_tick. |
Darkages.exe:0x0050D720 | event_manager_vtable | void *[8] | Events and network | EventMan worker and dispatch virtual table. | Internal Event Routing | Slot +0x10 points to event_manager_periodic_noop; slot +0x14 reaches event_process_work_item. |
Darkages.exe:0x0050D740 | event_vtable | void *[2] | Events | Runtime discriminator installed in copied mouse, keyboard, and socket Event objects. | Hooks and Injection | Event objects are 0x24 bytes in this build. RVA 0x0010D740. |
Darkages.exe:0x00510300 | ui_game_buttons_pane_vtable | virtual table | GameButtonsPane | Runtime discriminator for the persistent in-game content owner. | Runtime UI Memory Map | RVA 0x00110300. |
Darkages.exe:0x00510420 | ui_skill_inventory_pane_vtable | virtual table | skill inventory parent | Runtime discriminator for the 36-entry skill inventory. | Runtime UI Memory Map | RVA 0x00110420. |
Darkages.exe:0x00510480 | ui_spell_inventory_pane_vtable | virtual table | spell inventory parent | Runtime discriminator for the 36-entry spell inventory. | Runtime UI Memory Map | RVA 0x00110480. |
Darkages.exe:0x005106E0 | ui_users_dialog_pane_vtable | virtual table | Users Dialog Pane | Runtime discriminator for the reusable users dialog. | Runtime UI Memory Map | RVA 0x001106E0. |
Darkages.exe:0x005134E0 | ui_human_image_control_pane_vtable | virtual table | HumanImageControlPane | Runtime class discriminator for the human image control. | Pane Virtual Table Inventory | Draw virtual is at +0x48. |
Darkages.exe:0x00514820 | ui_skill_slot_pane_vtable | virtual table | skill slot child | Runtime discriminator for a populated skill slot. | Runtime UI Memory Map | RVA 0x00114820. |
Darkages.exe:0x00514880 | ui_spell_slot_pane_vtable | virtual table | spell slot child | Runtime discriminator for a populated spell slot. | Runtime UI Memory Map | RVA 0x00114880. |
Darkages.exe:0x005177C0 | ui_main_menu_pane_vtable | virtual table | MainMenuPane | Includes mouse, keyboard, and server-packet handlers. | UI Panes and Registries | Packet handler is ui_main_menu_handle_server_packet. |
Darkages.exe:0x00519280 | ui_map_pane_vtable | virtual table | MapPane | Includes mouse, keyboard, timer, and in-game server handlers. | UI Panes and Registries | Socket Event virtual slot +0x40 points to ui_map_dispatch_server_packet. |
Darkages.exe:0x0051B6C0 | ui_item_list_pane_vtable | virtual table | ItemListPane | Runtime class discriminator for the scrollable item list. | Pane Virtual Table Inventory | DrawCell virtual is at +0x84. |
Darkages.exe:0x0051BE60 | ui_monster_image_control_pane_vtable | virtual table | MonsterImageControlPane | Runtime class discriminator for the monster image control. | Pane Virtual Table Inventory | Draw virtual is at +0x48. |
Darkages.exe:0x0051C0C0 | ui_question_message_dialog_vtable | virtual table | QuestionMessageDialog | Runtime class discriminator for the question dialog. | Pane Virtual Table Inventory | Answer sender is at class vslot +0x74. |
Darkages.exe:0x0051C140 | ui_question_message_face_dialog_vtable | virtual table | QuestionMessageFaceDialog | Runtime class discriminator for the faced question dialog. | Pane Virtual Table Inventory | User-dialog and answer virtuals are at +0x64/+0x74. |
Darkages.exe:0x0051D700 | ui_monster_pane2_vtable | virtual table | MonsterPane2 | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | StartMove virtual is at +0x64. |
Darkages.exe:0x0051E980 | ui_local_user_vtable | virtual table | NewUser local-user pane | Runtime discriminator for the active local user object. | Runtime UI Memory Map | Socket vslot +0x40 dispatches SMove and the other local-user actions. RVA 0x0011E980. |
Darkages.exe:0x0051FB00 | ui_effect_object_pane_vtable | virtual table | EffectObjectPane | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Timer vslot +0x44 is class-specific. |
Darkages.exe:0x00520660 | ui_option_pane_vtable | virtual table | OptionPane | Runtime class discriminator for the main options dialog. | Pane Virtual Table Inventory | Mouse, key, socket, and timer slots are all class-specific. |
Darkages.exe:0x005207E0 | ui_exit_wait_pane_vtable | virtual table | exit-wait pane | Includes the server action 0x4C handler used by the orderly-exit exchange. | SReconnect | Installed by ui_exit_wait_pane_ctor; the source-level C++ class name is not yet established. |
Darkages.exe:0x005213A0 | ui_pane_vtable | virtual table | UI | Base Pane lifecycle and event slots. | UI Panes and Registries | Screen slots are +0x28/+0x2C; event registration is +0x30/+0x34; handlers begin at +0x38. |
Darkages.exe:0x00524CA0 | ui_screen_registry_vtable | virtual table | UI | Screen composition registry table. | UI Panes and Registries | Owns packed 0x3F-byte Screen nodes. |
Darkages.exe:0x00524CE0 | ui_screen_pane_vtable | virtual table | UI and rendering | Persistent ScreenPane handler table. | UI Panes and Registries | Keyboard and timer slots have concrete overrides. |
Darkages.exe:0x00526140 | ui_server_select_dialog_pane_vtable | virtual table | ServerSelectDialogPane | Includes the action 0x56 server-list handler. | UI Panes and Registries | Constructor and deletion diagnostics establish the class name. |
Darkages.exe:0x00526940 | net_socket_vtable | virtual table | Socket | Socket work-item interface. | Networking | Slot +0x10 points to net_poll_receive; slot +0x14 reaches net_process_work_item. |
Darkages.exe:0x00528280 | ui_spell_book_dialog_vtable | virtual table | SpellBookDialog | Runtime class discriminator for the spell-book dialog. | Pane Virtual Table Inventory | Draw virtual is at +0x48. |
Darkages.exe:0x005282E0 | ui_skill_book_dialog_vtable | virtual table | SkillBookDialog | Runtime class discriminator for the skill-book dialog. | Pane Virtual Table Inventory | Draw virtual is at +0x48. |
Darkages.exe:0x00529000 | ui_terminal_pane_vtable | virtual table | TerminalPane | Includes mouse, keyboard, and terminal server-packet handlers. | UI Panes and Registries | Installed by ui_terminal_pane_ctor. |
Darkages.exe:0x0052CFA0 | ui_weather_pane_vtable | virtual table | WeatherPane | Runtime class discriminator and Event handler table. | Pane Virtual Table Inventory | Timer and Draw virtuals are class-specific. |
GameButtons content fields
These offsets are relative to the GameButtons owner found through the Screen or Event hierarchy. They connect persistent visible content to the owner even though the owner itself does not have a static root. The Runtime UI Memory Map records the full traversal and class-specific layout.
| Offset | Width | Working field | Reads and writes |
|---|---|---|---|
+0x00F4 | 4 | map_pane | Constructor-owned link that normally matches ui_map_pane. |
+0x0100 | 1 | equipment_selected | Updated with the other four button-state bytes during content selection. |
+0x0108 | 1 | skill_selected | Updated with the other four button-state bytes during content selection. |
+0x0110 | 1 | spell_selected | Updated with the other four button-state bytes during content selection. |
+0x0118 | 1 | chat_selected | Updated with the other four button-state bytes during content selection. |
+0x0120 | 1 | status_selected | Updated with the other four button-state bytes during content selection. |
+0x0124 | 4 | current_content_pane | Compared and replaced by all five content selectors. |
+0x0130 | 4 | chat_pane | Selected by F/f or content index 3. |
+0x0134 | 4 | status_pane | Selected by G/g or content index 4. |
+0x0138 | 4 | equip_pane | Selected by A/a or content index 0; same-pane selection can toggle visibility. |
+0x013C | 4 | skill_inventory_pane | Selected by S/s or content index 1. |
+0x0140 | 4 | spell_inventory_pane | Selected by D/d or content index 2. |
+0x0144 | 4 | system_message | Constructor-owned link used by the GameButtons graph. |
Worker queue and bounded ring layouts
The dispatcher, EventMan, and Socket begin with the same 0x68-byte worker base. These fields establish the in-process wake point and the native queue state relevant to an Event Proxy.
| Offset | Width | Working field | Reads and writes |
|---|---|---|---|
+0x00 | 4 | vtable | Derived worker vtable. Periodic virtual slot is +0x10. |
+0x0C | 4 | wait_timeout_ms | INFINITE by default; dispatcher changes it to 1. |
+0x10 | 1 | wait_handle_count | Number of entries beginning at +0x14. |
+0x14 | 64 | wait_handles[16] | Entry 0 is the work semaphore released after a native queue push. |
+0x54 | 4 | work_queue | Pointer to the synchronized bounded ring. |
+0x58 | 4 | completion_monitor | Protects synchronous waiter records. |
+0x5C | 4 | completion_waiters | List used only by synchronous work requests. |
+0x60 | 4 | worker_thread | Thread handle created suspended and later resumed. |
+0x64 | 4 | worker_thread_id | Thread identifier written by _beginthreadex. |
Each native work record is 0x18 bytes. Asynchronous posting sets the final three fields to zero.
| Offset | Width | Working field |
|---|---|---|
+0x00 | 4 | code |
+0x04 | 4 | data |
+0x08 | 4 | value |
+0x0C | 4 | completion_event |
+0x10 | 4 | result_buffer |
+0x14 | 4 | result_buffer_size |
The ring object pointed to by worker +0x54 has this established state:
| Offset | Width | Working field |
|---|---|---|
+0x0C | 4 | monitor |
+0x10 | 4 | not_full_condition |
+0x14 | 4 | not_empty_condition |
+0x18 | 4 | element_size |
+0x1C | 4 | capacity |
+0x20 | 4 | buffer |
+0x24 | 4 | element_count |
+0x28 | 4 | head_index |
+0x2C | 4 | tail_index |
Programmer-oriented layouts follow. They are documentation types, not recovered source declarations.
typedef uint32_t client_ptr32;
typedef struct worker_record_421 {
uint32_t code;
client_ptr32 data;
int32_t value;
client_ptr32 completion_event;
client_ptr32 result_buffer;
int32_t result_buffer_size;
} worker_record_421;
typedef struct worker_queue_base_421 {
client_ptr32 vtable;
uint8_t unknown_04[8];
uint32_t wait_timeout_ms;
uint8_t wait_handle_count;
uint8_t unknown_11[3];
client_ptr32 wait_handles[16];
client_ptr32 work_queue;
client_ptr32 completion_monitor;
client_ptr32 completion_waiters;
client_ptr32 worker_thread;
uint32_t worker_thread_id;
} worker_queue_base_421;
typedef struct bounded_ring_421 {
client_ptr32 vtable;
uint8_t unknown_04[8];
client_ptr32 monitor;
client_ptr32 not_full_condition;
client_ptr32 not_empty_condition;
int32_t element_size;
int32_t capacity;
client_ptr32 buffer;
int32_t element_count;
int32_t head_index;
int32_t tail_index;
} bounded_ring_421;
The Socket and EventMan constructors pass capacity 0x80; the dispatcher passes 0x400. An observer can sample element_count, but the value may change concurrently and external code should not write queue metadata. For a worker-affine proxy wake, validate the root and vtable, read wait_handles[0], and call ReleaseSemaphore without touching the native ring.
Event dispatcher fields
Offsets below are from the object stored in event_dispatcher. The earlier fields belong to its worker-queue base.
| Offset | Width | Working field | Reads and writes |
|---|---|---|---|
+0x0000 | 4 | vtable | Set to event_dispatcher_vtable. |
+0x000C | 4 | wait_timeout_ms | Set to 1 and passed to WaitForMultipleObjects. |
+0x0010 | 1 | wait_handle_count | Number of entries beginning at +0x14. |
+0x0014 | variable | wait_handles | Handle index 0 is the queued-work notification. |
+0x0060 | 4 | worker_thread | Created suspended, resumed during initialization, and terminated by the base destructor. |
+0x0068 | 4 | pane_registry | Registry traversed by internal event dispatch. |
+0x006C | 4 | multimedia_period_ms | Passed to timeBeginPeriod and timeEndPeriod. |
+0x0070 | 4 | current_tick | Latest 32-bit timeGetTime sample and base for relative timer insertion. |
+0x0074 | 4 | next_deadline | First timer deadline, or UINT32_MAX when no timer is pending. |
+0x0078 | 4 | timer_list | Sorted list with capacity 256 and 20-byte records. |
Event timer record
| Offset | Width | Meaning |
|---|---|---|
+0x00 | 4 | Receiver pane pointer. |
+0x04 | 4 | Callback identifier. |
+0x08 | 4 | Absolute 32-bit timeGetTime deadline. |
+0x0C | 4 | First callback payload value. |
+0x10 | 4 | Second callback payload value. |
Event manager input fields
Offsets below are from the object stored in event_manager_instance. They are used by the window-message input queue and the EventMan worker.
| Offset | Width | Working field | Reads and writes |
|---|---|---|---|
+0x006C | 4 | mouse_y | Updated from the first word of internal (y, x) mouse-move work and copied into button and wheel Events. |
+0x0070 | 4 | mouse_x | Updated from the second word of internal (y, x) mouse-move work and copied into button and wheel Events. |
+0x007C | 4 | double_click_time_limit | A second button down is eligible only when its unsigned elapsed message time is strictly less than this value. |
+0x0080 | 1 | previous_click_button | Value 1 records a prior left down; value 0 records a prior right down. |
+0x0084 | 4 | previous_click_y | First coordinate used by internal double-click recognition. |
+0x0088 | 4 | previous_click_x | Second coordinate used by internal double-click recognition. |
+0x008C | 4 | previous_click_time | GetMessageTime value for the prior button-down candidate. |
+0x0390 | 256 | scan_code_pressed | Per-scan-code state; bit 7 is set on key down and cleared on key up. |
+0x0490 | 1 | input_modifier_state | Combined mouse-button and mapped keyboard modifier bits placed in Events. |
+0x04B0 | 4 | input_block_flags | Bit 0 suppresses movement, wheel, button-down, and key-down queueing. |
+0x04B4 | 4 | mouse_button_state | Bit 0 tracks left and bit 1 tracks right at window-message enqueue time. |
Socket fields used by binary TCP
Offsets below are from the Socket object passed as this. They are established by the constructor and the mapped receive and send paths. The complete class layout is not yet named.
| Offset | Width | Working field | Reads and writes |
|---|---|---|---|
+0x0068 | 4 | event_sink | Used as this for event_post_socket_bytes. |
+0x006C | 4 | receive_cache | Points at the object’s 0x18000 byte transport cache. |
+0x48074 | 4 | receive_cursor | Index of the next cached transport byte. |
+0x48078 | 4 | receive_remaining | Cached bytes remaining after the current byte. |
+0x4807C | 4 | current_body_length | Big-endian length assembled from the two frame header bytes. |
+0x48084 | 4 | serial_handle | Set to INVALID_HANDLE_VALUE when unused. |
+0x480BC | 1 | socket_connected | Cleared on recv == 0 and disconnect cleanup. |
+0x480C0 | 4 | socket | Winsock SOCKET; INVALID_SOCKET is 0xFFFFFFFF. |
+0x480C5 | 0x10000 | wire_body_buffer | Receives body bytes beginning with the server action. |
+0x580C8 | 4 | frame_offset | Counts length-header and body bytes while a frame is open. |
+0x680CC | at least 0x10000 | decoded_or_encoded_buffer | Destination for packet-body XOR transformation. |
+0x780CC | 1 | transport_mode | Value 5 selects the normal TCP byte path. |
+0x780CD | 1 | frame_open | Set after consuming 0xAA; cleared on body completion. |
+0x780CF | 1 | legacy_send_sequence | Decimal digit sequence used by the printable legacy transport. |
+0x780D2 | 1 | transfer_gate | When set, only client action 0x10 may be queued. |
Socket Event fields
The Event object created for a decoded packet has these fields:
| Offset | Width | Meaning | Lifetime |
|---|---|---|---|
+0x0C | 1 | Event type, value 9 for a socket packet. | Set before queueing. |
+0x14 | 4 | Owned decoded-packet pointer. | Freed by the dispatcher worker after event_dispatch returns. |
+0x18 | 4 | Decoded packet size. | Positive when the pointer is present. |
Structures should use unknown_XX for an established field location whose meaning remains unknown.
C Structure Layouts
This appendix presents the established 4.21 runtime layouts as C99-compatible structures. It is a programmer-oriented companion to the exact offset tables in Data Map and Runtime UI Memory Map. Those tables remain authoritative for field evidence, ownership, lifetime, and safe-reading guidance.
These declarations describe bytes in a 32-bit remote process. da_ptr32 is an address-sized value in the client, not a native pointer in a 64-bit inspector. Read a remote object into a byte buffer, validate its source pointer and vtable, and copy unaligned scalar fields with memcpy. Do not cast an arbitrary ReadProcessMemory destination to one of these packed types and then assume that every host permits unaligned access.
Unknown ranges are explicit so that each published field lands at its documented offset. A layout ending at the last known field is a prefix or view, not proof that the original C++ class ended there.
#include <stdint.h>
typedef uint32_t da_ptr32;
typedef struct da_client_point {
int32_t top;
int32_t left;
} da_client_point; /* 0x0008 bytes */
typedef struct da_client_rect {
int32_t top;
int32_t left;
int32_t bottom;
int32_t right;
} da_client_rect; /* 0x0010 bytes */
#pragma pack(push, 1)
Static root and vtable RVAs
These constants are RVAs, not absolute runtime addresses. Add each value to the loaded Darkages.exe module base. A root constant identifies four bytes of static pointer storage. A vtable constant identifies the relocated value expected in the first dword of a compatible live object.
#define DA_RVA_UI_BULLETIN_SESSION 0x000e2d70u
#define DA_RVA_UI_LAG_INDICATOR_PANE 0x000e2e40u
#define DA_RVA_UI_EQUIP_PANE 0x000e32a4u
#define DA_RVA_EVENT_MANAGER 0x000e32c0u
#define DA_RVA_UI_USERS_DIALOG_PANE 0x000e3564u
#define DA_RVA_UI_LOCAL_USER 0x000f5198u
#define DA_RVA_UI_MAIN_MENU_PANE 0x000f51acu
#define DA_RVA_UI_MAP_PANE 0x000f51b0u
#define DA_RVA_UI_BACKGROUND_PANE 0x000f51b4u
#define DA_RVA_NET_SOCKET 0x000f51bcu
#define DA_RVA_UI_SCREEN_PANE 0x000f51c8u
#define DA_RVA_UI_SCREEN_REGISTRY 0x000f51ccu
#define DA_RVA_EVENT_DISPATCHER 0x000f51d0u
#define DA_RVA_MEMORY_MANAGER 0x000fa160u
#define DA_RVA_UI_LOCAL_USER_ALIAS 0x000fc244u
#define DA_RVA_UI_TERMINAL_PANE 0x000fd640u
#define DA_RVA_VTABLE_BULLETIN_SESSION 0x00101e60u
#define DA_RVA_VTABLE_LAG_INDICATOR 0x00109c20u
#define DA_RVA_VTABLE_EQUIP_PANE 0x0010c6c0u
#define DA_RVA_VTABLE_EVENT_MANAGER 0x0010d720u
#define DA_RVA_VTABLE_GAME_BUTTONS 0x00110300u
#define DA_RVA_VTABLE_SKILL_INVENTORY 0x00110420u
#define DA_RVA_VTABLE_SPELL_INVENTORY 0x00110480u
#define DA_RVA_VTABLE_USERS_DIALOG 0x001106e0u
#define DA_RVA_VTABLE_SKILL_SLOT 0x00114820u
#define DA_RVA_VTABLE_SPELL_SLOT 0x00114880u
#define DA_RVA_VTABLE_LOCAL_USER 0x0011e980u
#define DA_RVA_VTABLE_NET_SOCKET 0x00126940u
For example, reading the lag value takes two remote reads. First read a da_ptr32 from module_base + DA_RVA_UI_LAG_INDICATOR_PANE, then read a da_lag_indicator_pane from the resulting heap address. Validate its first dword against module_base + DA_RVA_VTABLE_LAG_INDICATOR before using smoothed_move_rtt_ms.
Pane and hierarchy layouts
The common Pane prefix is 0xF4 bytes. The derived layouts below embed it so that a programmer can follow a Screen node to a pane, identify the vtable, and then select the matching derived view.
typedef struct da_pane_prefix {
da_ptr32 vtable; /* +0x0000 */
uint8_t unknown_0004[0x04]; /* +0x0004 */
int32_t error_code; /* +0x0008 */
uint8_t unknown_000c[0x2c]; /* +0x000C */
da_client_rect graphic_bounds; /* +0x0038 */
uint8_t unknown_0048[0x60]; /* +0x0048 */
da_client_point relative_origin; /* +0x00A8 */
uint8_t visible; /* +0x00B0 */
uint8_t unknown_b1; /* +0x00B1 */
uint8_t unknown_b2; /* +0x00B2 */
uint8_t screen_payload_flag; /* +0x00B3 */
uint8_t unknown_00b4[0x3c]; /* +0x00B4 */
uint8_t capture_related_state; /* +0x00F0 */
uint8_t unknown_00f1[0x03]; /* +0x00F1 */
} da_pane_prefix; /* 0x00F4 bytes */
typedef struct da_hierarchy_list {
uint8_t unknown_0000[0x0c]; /* +0x0000 */
uint32_t node_stride; /* +0x000C */
uint8_t unknown_0010[0x04]; /* +0x0010 */
uint32_t node_count; /* +0x0014 */
da_ptr32 node_bytes; /* +0x0018 */
da_ptr32 root_or_parent_node; /* +0x001C */
} da_hierarchy_list; /* 0x0020 bytes */
typedef struct da_event_hierarchy_node {
da_ptr32 parent_node; /* +0x00 */
da_ptr32 child_list; /* +0x04 */
da_ptr32 pane; /* +0x08 */
uint8_t unknown_0c[0x03]; /* +0x0C */
} da_event_hierarchy_node; /* 0x0F-byte packed stride */
typedef struct da_screen_hierarchy_node {
da_ptr32 parent_node; /* +0x00 */
da_ptr32 child_list; /* +0x04 */
da_ptr32 pane; /* +0x08 */
da_client_rect screen_region; /* +0x0C */
uint8_t visible; /* +0x1C */
uint8_t pane_screen_payload_flag; /* +0x1D */
uint8_t insertion_state; /* +0x1E */
uint8_t unknown_1f[0x20]; /* +0x1F */
} da_screen_hierarchy_node; /* 0x3F-byte packed stride */
The hierarchy array itself is packed. Record two begins at array + stride, which is unaligned for both known stride values.
Event and socket layouts
typedef struct da_worker_record {
uint32_t code; /* +0x00 */
da_ptr32 data; /* +0x04 */
int32_t value; /* +0x08 */
da_ptr32 completion_event; /* +0x0C */
da_ptr32 result_buffer; /* +0x10 */
int32_t result_buffer_size; /* +0x14 */
} da_worker_record; /* 0x18 bytes */
typedef struct da_worker_queue_base {
da_ptr32 vtable; /* +0x00 */
uint8_t unknown_04[0x08]; /* +0x04 */
uint32_t wait_timeout_ms; /* +0x0C */
uint8_t wait_handle_count; /* +0x10 */
uint8_t unknown_11[0x03]; /* +0x11 */
da_ptr32 wait_handles[16]; /* +0x14, index 0 is work */
da_ptr32 work_queue; /* +0x54 */
da_ptr32 completion_monitor; /* +0x58 */
da_ptr32 completion_waiters; /* +0x5C */
da_ptr32 worker_thread; /* +0x60 */
uint32_t worker_thread_id; /* +0x64 */
} da_worker_queue_base; /* 0x68 bytes */
typedef struct da_bounded_ring {
da_ptr32 vtable; /* +0x00 */
uint8_t unknown_04[0x08]; /* +0x04 */
da_ptr32 monitor; /* +0x0C */
da_ptr32 not_full_condition; /* +0x10 */
da_ptr32 not_empty_condition; /* +0x14 */
int32_t element_size; /* +0x18 */
int32_t capacity; /* +0x1C */
da_ptr32 buffer; /* +0x20 */
int32_t element_count; /* +0x24 */
int32_t head_index; /* +0x28 */
int32_t tail_index; /* +0x2C */
} da_bounded_ring; /* 0x30 bytes */
typedef struct da_event_dispatcher_prefix {
da_worker_queue_base worker; /* +0x0000 */
da_ptr32 pane_registry; /* +0x0068 */
uint32_t multimedia_period_ms; /* +0x006C */
uint32_t current_tick; /* +0x0070 */
uint32_t next_deadline; /* +0x0074 */
da_ptr32 timer_list; /* +0x0078 */
} da_event_dispatcher_prefix; /* known prefix through 0x007C */
typedef struct da_event_timer_record {
da_ptr32 receiver_pane; /* +0x00 */
uint32_t callback_identifier; /* +0x04 */
uint32_t absolute_deadline; /* +0x08 */
uint32_t payload_first; /* +0x0C */
uint32_t payload_second; /* +0x10 */
} da_event_timer_record; /* 0x14 bytes */
typedef struct da_event_manager_input_view {
da_worker_queue_base worker; /* +0x0000 */
da_ptr32 socket_object; /* +0x0068 */
int32_t mouse_y; /* +0x006C */
int32_t mouse_x; /* +0x0070 */
uint8_t unknown_0074[0x08]; /* +0x0074 */
uint32_t double_click_time_limit; /* +0x007C */
uint8_t previous_click_button; /* +0x0080 */
uint8_t unknown_0081[0x03]; /* +0x0081 */
int32_t previous_click_y; /* +0x0084 */
int32_t previous_click_x; /* +0x0088 */
uint32_t previous_click_time; /* +0x008C */
uint8_t unknown_0090[0x300]; /* +0x0090 */
uint8_t scan_code_pressed[256]; /* +0x0390 */
uint8_t input_modifier_state; /* +0x0490 */
uint8_t unknown_0491[0x1f]; /* +0x0491 */
uint32_t input_block_flags; /* +0x04B0 */
uint32_t mouse_button_state; /* +0x04B4 */
} da_event_manager_input_view; /* known prefix through 0x04B8 */
typedef struct da_socket_421 {
da_worker_queue_base worker; /* +0x00000 */
da_ptr32 event_sink; /* +0x00068 */
da_ptr32 receive_cache; /* +0x0006C */
uint8_t unknown_00070[0x48004]; /* +0x00070 */
uint32_t receive_cursor; /* +0x48074 */
uint32_t receive_remaining; /* +0x48078 */
uint32_t current_body_length; /* +0x4807C */
uint8_t unknown_48080[0x04]; /* +0x48080 */
da_ptr32 serial_handle; /* +0x48084 */
uint8_t unknown_48088[0x34]; /* +0x48088 */
uint8_t socket_connected; /* +0x480BC */
uint8_t unknown_480bd[0x03]; /* +0x480BD */
uint32_t socket; /* +0x480C0 */
uint8_t unknown_480c4; /* +0x480C4 */
uint8_t wire_body_buffer[0x10000]; /* +0x480C5 */
uint8_t unknown_580c5[0x03]; /* +0x580C5 */
uint32_t frame_offset; /* +0x580C8 */
uint8_t unknown_580cc[0x10000]; /* +0x580CC */
uint8_t decoded_or_encoded_buffer[0x10000]; /* +0x680CC */
uint8_t transport_mode; /* +0x780CC */
uint8_t frame_open; /* +0x780CD */
uint8_t unknown_780ce; /* +0x780CE */
uint8_t legacy_send_sequence; /* +0x780CF */
uint8_t unknown_780d0[0x02]; /* +0x780D0 */
uint8_t transfer_gate; /* +0x780D2 */
uint8_t unknown_780d3; /* +0x780D3 */
} da_socket_421; /* 0x780D4 bytes */
typedef struct da_socket_event {
da_ptr32 vtable; /* +0x00 */
uint8_t unknown_04[0x08]; /* +0x04 */
uint8_t event_type; /* +0x0C, value 9 */
uint8_t unknown_0d[0x07]; /* +0x0D */
da_ptr32 decoded_packet; /* +0x14 */
uint32_t decoded_packet_size; /* +0x18 */
uint8_t unknown_1c[0x08]; /* +0x1C */
} da_socket_event; /* 0x24 bytes */
da_worker_queue_base.wait_handles[0] is the native work semaphore. The worker tolerates that semaphore being signaled while work_queue is empty and still invokes its periodic vtable slot. This is the established wake primitive for the proposed worker-affine Event Proxy pumps. Do not modify da_bounded_ring indices or counts from an inspector. Socket and EventMan rings have capacity 0x80; the dispatcher ring has capacity 0x400.
Persistent game UI layouts
typedef struct da_lag_indicator_pane {
da_pane_prefix pane; /* +0x0000 */
uint32_t smoothed_move_rtt_ms; /* +0x00F4 */
} da_lag_indicator_pane; /* 0x00F8 bytes */
typedef struct da_game_buttons_pane {
da_pane_prefix pane; /* +0x0000 */
da_ptr32 map_pane; /* +0x00F4 */
uint8_t unknown_00f8[0x08]; /* +0x00F8 */
uint8_t equipment_selected; /* +0x0100 */
uint8_t unknown_0101[0x07]; /* +0x0101 */
uint8_t skill_selected; /* +0x0108 */
uint8_t unknown_0109[0x07]; /* +0x0109 */
uint8_t spell_selected; /* +0x0110 */
uint8_t unknown_0111[0x07]; /* +0x0111 */
uint8_t chat_selected; /* +0x0118 */
uint8_t unknown_0119[0x07]; /* +0x0119 */
uint8_t status_selected; /* +0x0120 */
uint8_t unknown_0121[0x03]; /* +0x0121 */
da_ptr32 current_content_pane; /* +0x0124 */
uint8_t unknown_0128[0x08]; /* +0x0128 */
da_ptr32 chat_pane; /* +0x0130 */
da_ptr32 status_pane; /* +0x0134 */
da_ptr32 equip_pane; /* +0x0138 */
da_ptr32 skill_inventory_pane; /* +0x013C */
da_ptr32 spell_inventory_pane; /* +0x0140 */
da_ptr32 system_message_pane; /* +0x0144 */
uint8_t unknown_0148[0x10]; /* +0x0148 */
} da_game_buttons_pane; /* 0x0158 bytes */
typedef struct da_inventory_parent_pane {
da_pane_prefix pane; /* +0x0000 */
da_ptr32 slot_panes[36]; /* +0x00F4, entry 0 is slot 1 */
uint8_t state_184; /* +0x0184 */
uint8_t unknown_0185[0x03]; /* +0x0185 */
} da_inventory_parent_pane; /* 0x0188 bytes */
typedef struct da_skill_slot_pane {
da_pane_prefix pane; /* +0x0000 */
uint16_t display_id; /* +0x00F4 */
char name[0x80]; /* +0x00F6 */
uint8_t unknown_0176[0x100]; /* +0x0176 */
uint8_t slot; /* +0x0276, 1 through 36 */
uint8_t pointer_capture_state; /* +0x0277 */
uint8_t cooldown_active; /* +0x0278 */
uint8_t unknown_0279[0x03]; /* +0x0279 */
da_client_point pointer_down_point; /* +0x027C */
da_client_rect tracking_rect; /* +0x0284 */
} da_skill_slot_pane; /* 0x0294 bytes */
typedef struct da_spell_slot_pane {
da_pane_prefix pane; /* +0x0000 */
uint8_t slot; /* +0x00F4, 1 through 36 */
uint8_t unknown_00f5; /* +0x00F5 */
uint16_t display_id; /* +0x00F6 */
int8_t spell_type; /* +0x00F8 */
char name[0x80]; /* +0x00F9 */
char comment[0x80]; /* +0x0179 */
uint8_t packet_flag; /* +0x01F9 */
uint8_t pointer_capture_state; /* +0x01FA */
uint8_t cooldown_active; /* +0x01FB */
da_client_point pointer_down_point; /* +0x01FC */
da_client_rect tracking_rect; /* +0x0204 */
} da_spell_slot_pane; /* 0x0214 bytes */
The skill and spell parent layouts are byte-identical in the published range. Select the child structure only after validating the parent and child vtable RVAs.
Character, bulletin, and users layouts
typedef struct da_equip_pane {
da_pane_prefix pane; /* +0x0000 */
uint8_t unknown_00f4[0x45c]; /* +0x00F4 */
uint8_t appearance[4]; /* +0x0550 */
char self_look_554[0x100]; /* +0x0554 */
char self_look_654[0x80]; /* +0x0654 */
char self_look_6d4[0x80]; /* +0x06D4 */
char self_look_754[0x80]; /* +0x0754 */
char self_look_7d4[0x100]; /* +0x07D4, established minimum */
uint8_t unknown_08d4[0x24e]; /* +0x08D4 */
uint8_t unknown_0b22[0x02]; /* +0x0B22 */
uint16_t item_id_by_slot[13]; /* +0x0B24, entry 0 is slot 1 */
char item_name_by_slot[13][0x80]; /* +0x0B3E */
uint8_t unknown_11be[0x02]; /* +0x11BE */
uint32_t unknown_11c0; /* +0x11C0 */
uint8_t unknown_11c4; /* +0x11C4 */
uint8_t unknown_11c5[0x03]; /* +0x11C5 */
da_ptr32 helper_pane; /* +0x11C8 */
da_ptr32 legend_dialog_pane; /* +0x11CC */
uint8_t unknown_11d0; /* +0x11D0 */
uint8_t unknown_11d1[0x03]; /* +0x11D1 */
} da_equip_pane; /* 0x11D4 bytes */
typedef struct da_bulletin_session {
da_pane_prefix pane; /* +0x0000 */
uint8_t unknown_00f4; /* +0x00F4 */
uint8_t current_history_index; /* +0x00F5 */
uint8_t unknown_00f6[0x02]; /* +0x00F6 */
da_ptr32 child_history[10]; /* +0x00F8 */
da_ptr32 active_child_pane; /* +0x0120 */
uint8_t request_wait_state; /* +0x0124 */
uint8_t unknown_0125[0x07]; /* +0x0125 */
} da_bulletin_session; /* 0x012C bytes */
typedef struct da_users_dialog_pane {
da_pane_prefix pane; /* +0x0000 */
uint8_t unknown_00f4[0x45c]; /* +0x00F4 */
da_ptr32 row_or_list_objects[9]; /* +0x0550 */
da_ptr32 list_storage_object; /* +0x0574 */
uint8_t unknown_0578; /* +0x0578 */
uint8_t unknown_0579; /* +0x0579 */
uint8_t unknown_057a[0x02]; /* +0x057A */
da_ptr32 scrolling_control; /* +0x057C */
uint8_t unknown_0580; /* +0x0580 */
uint8_t unknown_0581; /* +0x0581 */
uint8_t unknown_0582[0x06]; /* +0x0582 */
} da_users_dialog_pane; /* 0x0588 bytes */
self_look_7d4 is represented as the established minimum 0x100-byte region. The following unknown range keeps later fields at their confirmed offsets without asserting that the original source declared exactly that array size.
Local user movement timing
ui_local_user at Darkages.exe:0x004F5198 points to a 0x776C-byte object while the game UI is active. Only the timing slice needed for movement and the lag indicator is named here.
typedef struct da_local_user_timing_view {
uint8_t unknown_0000[0x6f28]; /* +0x0000 */
uint8_t move_send_sequence; /* +0x6F28 */
uint8_t move_sequence_at_last_smove;/* +0x6F29 */
uint8_t unknown_6f2a[0x02]; /* +0x6F2A */
uint32_t last_move_send_tick; /* +0x6F2C */
uint8_t unknown_6f30[0x04]; /* +0x6F30 */
uint32_t last_move_round_trip_ms; /* +0x6F34 */
uint8_t unknown_6f38[0x834]; /* +0x6F38 */
} da_local_user_timing_view; /* 0x776C bytes */
#pragma pack(pop)
last_move_send_tick is overwritten on every CMove, so last_move_round_trip_ms is not a per-sequence latency table. It is the unsigned timeGetTime difference between an SMove and the most recently queued CMove. The lag pane stores a separate smoothed value at its own +0xF4.
Validation checklist
Before interpreting any remote bytes:
- Resolve the loaded module base and add the documented RVA.
- Read the static pointer and reject null outside the expected lifecycle phase.
- Confirm that the complete requested range is readable.
- Validate the first dword against the expected relocated vtable where one is known.
- Copy bounded strings and require a NUL terminator inside the published buffer.
- Re-read the static pointer and vtable after the copy. Retry if either changed.
- Treat packed hierarchy nodes and odd socket offsets as unaligned data.
Raw writes are not structure-aware UI operations. Use these layouts for observation first. A controlled writer should invoke the established client method on the client’s UI or event path so that registries, invalidation, focus, timers, and ownership remain consistent.
Runtime UI Memory Map
The 4.21 client exposes enough stable process state to build a useful read-only UI inspector. Static module globals lead to the Screen composition tree, the Event receiver tree, and several important persistent panes. Heap pane objects share a common base layout, and their first pointer identifies the concrete implementation through a module-relative virtual-table address.
This page is a runtime lookup reference. It describes addresses and fields confirmed by code paths that construct, read, update, or destroy them. The broader static global catalog is in Data Map, packed programmer-facing declarations are in C Structure Layouts, and the complete pane-class discriminator list is in Pane Virtual Table Inventory. Event Proxy IPC, Rules, and State applies these paths to late-attach snapshots and guarded peek or poke requests.
High-level operation
Address model
Darkages.exe is a 32-bit image with preferred image base 0x00400000. Every pointer and uintptr_t value discussed here is four bytes. The documented virtual addresses identify the analyzed image, but an external reader should resolve the loaded module base and add an RVA:
uintptr_t runtime_address = loaded_module_base + documented_rva;
Do not assume that Darkages.exe loaded at its preferred base. A virtual-table address is also relocated with the module. For example, a live GameButtons pane is identified by:
int is_game_buttons_pane =
pane_vtable == loaded_module_base + 0x00110300;
Static runtime roots
Each address in the table is storage in the image. Read one 32-bit pointer from loaded_module_base + RVA to obtain the heap object.
| Static VA | RVA | Current IDA name | Heap object reached | Useful next step |
|---|---|---|---|---|
Darkages.exe:0x004E2D70 | 0x000E2D70 | ui_bulletin_session | Active BulletinSession | Read active child at session +0x120. |
Darkages.exe:0x004E2E40 | 0x000E2E40 | ui_lag_indicator_pane | Persistent lag indicator pane | Read its smoothed movement round-trip value at +0xF4. |
Darkages.exe:0x004E32A4 | 0x000E32A4 | ui_equip_pane | Persistent User Equip pane | Read visibility, equipment slots, self-look strings, and legend child pointers. |
Darkages.exe:0x004E3564 | 0x000E3564 | ui_users_dialog_pane | Users Dialog Pane | Read visibility and its nine row/list objects. |
Darkages.exe:0x004F5198 | 0x000F5198 | ui_local_user | Active local user object | Read movement timing at +0x6F2C/+0x6F34. |
Darkages.exe:0x004F51AC | 0x000F51AC | ui_main_menu_pane | MainMenuPane | Distinguishes the main-menu phase. |
Darkages.exe:0x004F51B0 | 0x000F51B0 | ui_map_pane | MapPane | Distinguishes the in-game phase and leads to map-owned state. |
Darkages.exe:0x004F51B4 | 0x000F51B4 | ui_background_pane | BackgroundPane | Persistent parent of the in-game UI. |
Darkages.exe:0x004F51C8 | 0x000F51C8 | ui_screen_pane | Root ScreenPane | Persistent after successful initialization. |
Darkages.exe:0x004F51CC | 0x000F51CC | ui_screen_registry | Screen hierarchy list | Read root node pointer at list +0x1C. |
Darkages.exe:0x004F51D0 | 0x000F51D0 | event_dispatcher | Event dispatcher | Read Event pane hierarchy at dispatcher +0x68. |
Darkages.exe:0x004FD640 | 0x000FD640 | ui_terminal_pane | TerminalPane | Distinguishes terminal/bootstrap phase. |
These roots are nullable according to lifecycle. A null pointer is ordinarily a phase or ownership result, not an error in the reader.
What the two pane trees reveal
The Screen tree is the primary inventory of composed UI. It contains persistent panes, hidden content panes that remain attached, dialogs, controls, and temporary slot panes. Its parent-child links also describe UI ownership and placement.
The Event tree is an independent inventory of panes registered for mouse, keyboard, socket-packet, or timer delivery. It is useful as a cross-check and can contain an object whose Screen membership is changing. Neither tree is an ownership-safe API for an external process. Both can change while they are read.
A practical read-only inspector can:
- Snapshot the Screen tree.
- Validate each pane pointer and vtable.
- Read the common visibility byte and bounds.
- Label known classes by vtable RVA.
- Follow class-specific pointers only after confirming the expected vtable.
- Optionally snapshot the Event tree and mark which visible or hidden panes currently receive Events.
Useful observation recipes
List current panes
Read ui_screen_registry, then its root at registry + 0x1C. Observe the root pane at root + 0x08, and recursively snapshot the child list at root + 0x04. For every node, read the pane pointer at node +0x08. Read the live pane’s vtable, local bound rectangle at +0x38, relative top/left origin at +0xA8, and visible byte at +0xB0.
The full vtable inventory contains construction and control variants as well as visible screens. A vtable match identifies the object’s current implementation, but does not imply that the pane is visible or top-level.
Find the selected game content
There is no separate static root for GameButtonsPane. Find the pane whose vtable is loaded_module_base + 0x00110300. Its field at +0x124 points to the currently selected content pane. Compare that pointer with fields +0x130 through +0x140 to label the selection as chat, status, equipment, skills, or spells.
This pointer records selection, while the selected pane’s +0xB0 byte records current visibility. Equipment can be the selected object while hidden because selecting equipment twice toggles its visibility.
Read skills and spells
From GameButtonsPane, read the skill inventory at +0x13C and the spell inventory at +0x140. Each parent owns a 36-entry pointer array beginning at +0xF4. Array entry zero represents protocol slot 1:
uint32_t slot_pane =
read_process_u32(inventory + 0x00f4 + 4 * (slot - 1));
A null entry is an empty slot. A non-null skill entry should use vtable RVA 0x00114820; a spell entry should use 0x00114880. The child objects retain the server-provided display ID and strings, their 1-based slot, pointer interaction state, and cooldown-active state.
Read equipment and self-look state
Read ui_equip_pane directly. The normal equipment range is 1 through 13. For a 1-based slot:
uintptr_t item_id_address = equip_pane + 0x0b22 + 2 * slot;
uintptr_t item_name_address =
equip_pane + 0x0b3e + 0x80 * (slot - 1);
The identifier is a native little-endian uint16_t in memory, even though the server packet supplied it in big-endian order. The name is a NUL-terminated byte string in a 0x80-byte record. The pane also retains the most recent SSelfLook appearance bytes, strings, a 32-bit value, and two child-pane pointers. Several field meanings remain unknown, so the code-level table uses unknown_XX names.
Read bulletin, mail, and users state
ui_bulletin_session points to a specialized Pane with vtable RVA 0x00101E60. Its current child pointer at +0x120 leads to the active bulletin or mail dialog. Session +0xF5 records its current child-history position and the one-byte field at +0x124 records request-wait state.
ui_users_dialog_pane is a Pane with a direct static root. It owns nine heap row/list objects at +0x550 through +0x570. The dialog remains allocated and is reused by later SShowUsers replies, so +0xB0 is the useful shown/hidden test.
Read movement timing and the lag indicator
Read ui_local_user while the game UI is active. Its latest queued CMove timestamp is a 32-bit timeGetTime value at +0x6F2C, and the latest SMove response sample is at +0x6F34. The persistent lag pane has its own direct root, ui_lag_indicator_pane, and stores the displayed smoothed value at pane +0xF4.
The raw sample and displayed sample are intentionally different. On every accepted SMove, the pane replaces its value with (old_value + latest_sample) / 2. A reader that wants to reproduce the visible band should use the pane field. A reader that wants the latest unsmoothed movement response should use the local-user field.
Peek versus poke
Read-only observation is practical with the layouts on this page. Raw writes are not equivalent to client UI operations.
Changing Pane + 0xB0 does not perform invalidation, capture release, focus changes, or parent redraw. Replacing a child pointer does not update either packed hierarchy and can create a use-after-free during Event traversal. Editing a vtable or packed node can redirect a virtual call. Editing a fixed string without preserving its limit and NUL termination can overwrite adjacent state.
If controlled UI mutation is investigated later, the safer design is to invoke the established pane method on the client UI path and let it update both registries and rendering state. This page intentionally documents a read-only snapshot model.
Code-level memory map
Packed hierarchy list
Both Screen and Event registries use the same packed hierarchy list. Multi-byte fields in a node may be unaligned, so an external reader should copy bytes or use an unaligned-safe read helper instead of casting a local aligned structure over remote memory.
| List offset | Width | Meaning |
|---|---|---|
+0x0C | 4 | Node stride. |
+0x14 | 4 | Node count. |
+0x18 | 4 | Pointer to contiguous packed node bytes. |
+0x1C | 4 | Root or parent node pointer. Screen stores its separately allocated root here. |
| Common node offset | Width | Meaning |
|---|---|---|
+0x00 | 4 | Parent node pointer. |
+0x04 | 4 | Child hierarchy-list pointer, or null. |
+0x08 | 4 | Pane pointer, the first field of both known payloads. |
Screen requests a 0x34-byte payload, so its node stride is 0x3F. Event requests a four-byte payload, so its stride is 0x0F.
| Screen node offset | Width | Meaning |
|---|---|---|
+0x08 | 4 | Pane pointer. |
+0x0C | 16 | Screen-owned rectangle or region state, initialized to zero on insertion. This is not the Pane bounds field. |
+0x1C | 1 | Visible state copied from ui_pane_is_visible during insertion and maintained by Screen operations. |
+0x1D | 1 | Pane +0xB3 flag copied during insertion. |
+0x1E | 1 | Initialized to zero during insertion. |
+0x1F | remaining payload | Additional Screen-owned state whose fields are not yet published. |
The runtime entry chains are:
uintptr_t screen_list =
read_process_u32(module_base + 0x000f51cc);
uintptr_t screen_root = read_process_u32(screen_list + 0x1c);
uintptr_t screen_children = read_process_u32(screen_root + 0x04);
uintptr_t dispatcher =
read_process_u32(module_base + 0x000f51d0);
uintptr_t event_list = read_process_u32(dispatcher + 0x68);
Common Pane fields
| Pane offset | Width | Meaning | Reader use |
|---|---|---|---|
+0x0000 | 4 | Current vtable pointer. | Convert to an RVA relative to the loaded module and match the inventory. |
+0x0008 | 4 | Last propagated Screen or dispatcher error. | Diagnostic state, not a class identifier. |
+0x0038 | 16 | Local graphic bound rectangle. | Four signed 32-bit values in client order: top, left, bottom, right. |
+0x00A8 | 8 | Relative origin point. | Signed 32-bit top at +0xA8 and left at +0xAC. |
+0x00B0 | 1 | Visible or active byte. | Nonzero means shown according to ui_pane_is_visible. |
+0x00B1 | 1 | unknown_B1 constructor flag. | Established location, semantics not established. |
+0x00B2 | 1 | unknown_B2 constructor flag. | Established location, semantics not established. |
+0x00B3 | 1 | Screen payload flag. | Copied into Screen node +0x1D. |
+0x00F0 | 1 | Capture-related state. | Checked before Screen removal. |
ui_pane_set_bound_rect retains the input rectangle’s top and left at +0xA8/+0xAC, translates the rectangle to a local origin, and applies it to the graphic bound rectangle at +0x38. ui_pane_get_bound_rect reverses that translation. The in-parent rectangle can therefore be reconstructed without a client call:
int32_t relative_top = pane_bound_top + pane_origin_top;
int32_t relative_left = pane_bound_left + pane_origin_left;
int32_t relative_bottom = pane_bound_bottom + pane_origin_top;
int32_t relative_right = pane_bound_right + pane_origin_left;
These client rectangles use top, left, bottom, right field order, not the Win32 RECT memory order. ui_screen_get_pane_origin then finds the Screen node and accumulates parent placement. An external tree walker can preserve the parent chain and perform the same accumulation when absolute screen coordinates are needed.
Runtime class discriminators
The following vtables are especially useful for state walking. The address to compare at runtime is loaded_module_base + RVA.
| Class or role | Static VA | RVA | Current IDA name |
|---|---|---|---|
| BulletinSession | Darkages.exe:0x00501E60 | 0x00101E60 | ui_bulletin_session_vtable |
| Lag indicator pane | Darkages.exe:0x00509C20 | 0x00109C20 | ui_lag_indicator_pane_vtable |
| GameButtonsPane | Darkages.exe:0x00510300 | 0x00110300 | ui_game_buttons_pane_vtable |
| User Equip pane | Darkages.exe:0x0050C6C0 | 0x0010C6C0 | ui_equip_pane_vtable |
| Users Dialog Pane | Darkages.exe:0x005106E0 | 0x001106E0 | ui_users_dialog_pane_vtable |
| Skill inventory parent | Darkages.exe:0x00510420 | 0x00110420 | ui_skill_inventory_pane_vtable |
| Spell inventory parent | Darkages.exe:0x00510480 | 0x00110480 | ui_spell_inventory_pane_vtable |
| Skill slot child | Darkages.exe:0x00514820 | 0x00114820 | ui_skill_slot_pane_vtable |
| Spell slot child | Darkages.exe:0x00514880 | 0x00114880 | ui_spell_slot_pane_vtable |
NewUser local-user pane | Darkages.exe:0x0051E980 | 0x0011E980 | ui_local_user_vtable |
Lag indicator and movement timing
The lag indicator is a 0xF8-byte Pane created with the rest of the in-game pane graph. Its constructor publishes ui_lag_indicator_pane, initializes +0xF4 to 300 milliseconds, sets a 32 by 32 local bound, and adds and registers the pane below BackgroundPane. The attach method passes left = 537, top = 314, right = 549, and bottom = 328 to ui_rect_init. The helper stores the client-order words (top = 314, left = 537, bottom = 328, right = 549), placing the visible indicator at x = 537..549, y = 314..328.
The value is not based on socket throughput, receive queue depth, or a periodic ping packet. net_c_send_move builds CMove action 0x06, increments the one-byte movement sequence at local user +0x6F28, and stores timeGetTime at +0x6F2C. When the local user receives SMove action 0x0B, ui_local_user_handle_smove stores the unsigned difference between the new clock sample and +0x6F2C at +0x6F34. It then calls ui_lag_indicator_update_from_local_user, which averages the new sample with pane +0xF4 and invalidates the pane rectangle.
uint32_t latest_sample = current_tick - last_move_send_tick;
uint32_t displayed_sample =
(previous_displayed_sample + latest_sample) / 2;
The sender keeps only one timestamp. If another CMove is queued before an earlier SMove arrives, the newer send overwrites +0x6F2C. The packet byte read at decoded-packet offset +0x0A is also discarded rather than compared against the client movement sequence. The indicator is therefore a smoothed response time relative to the most recent move timestamp, not a rigorous per-sequence network RTT measurement.
ui_lag_indicator_pane_draw selects statcon.epf frames with unsigned comparisons:
Smoothed value at pane +0xF4 | Frame | Visual color in the checked version-family asset | Notes |
|---|---|---|---|
0 | 2 | Green | Zero bypasses the 1 through 249 fast band. Normal construction starts at 300, not zero. |
1 through 249 | 3 | Blue | Fastest band. |
250 through 349 | 2 | Green | Constructor default 300 starts here. |
350 through 449 | 1 | Orange | Intermediate warning band. |
450 or more | 0 | Red | Slowest band. |
The frame numbers and thresholds come directly from the 4.21 draw code. The color labels were checked by decoding the same-named statcon.epf against legend.pal from an available 4.51 data set: frame 3 is blue, frame 2 green, frame 1 orange, and frame 0 red. Exact 4.21 asset identity was not available in this workspace, so tools that require byte-identical 4.21 colors should verify those four asset frames while retaining the code-established frame mapping.
| Object offset | Width | Meaning |
|---|---|---|
lag pane +0x00F4 | 4 | Unsigned smoothed movement response value used for frame selection. |
local user +0x6F28 | 1 | Incrementing CMove sequence byte. |
local user +0x6F29 | 1 | Current movement sequence copied when an SMove is handled. It is not populated from the packet byte read at +0x0A. |
local user +0x6F2C | 4 | timeGetTime value from the most recently queued CMove. |
local user +0x6F34 | 4 | Latest unsmoothed SMove minus latest-CMove clock difference. |
See Pane Virtual Table Inventory for dialogs, bulletin and mail panes, controls, MapPane, options, books, effects, and the remaining compatible tables.
GameButtonsPane fields
GameButtonsPane is constructed by ui_game_buttons_pane_ctor and found dynamically by its vtable. Its direct links make it the most useful bridge from generic pane enumeration to persistent in-game UI state.
| Offset | Width | Meaning |
|---|---|---|
+0x00F4 | 4 | MapPane pointer passed to the constructor. It normally matches ui_map_pane. |
+0x0100 | 1 | Equipment button selected state. |
+0x0108 | 1 | Skill button selected state. |
+0x0110 | 1 | Spell button selected state. |
+0x0118 | 1 | Chat button selected state. |
+0x0120 | 1 | Status button selected state. |
+0x0124 | 4 | Current content-pane pointer. |
+0x0130 | 4 | Chat pane pointer. |
+0x0134 | 4 | Status pane pointer. |
+0x0138 | 4 | Equipment pane pointer. It normally matches ui_equip_pane. |
+0x013C | 4 | Skill inventory parent pointer. |
+0x0140 | 4 | Spell inventory parent pointer. |
+0x0144 | 4 | System-message pointer passed to the constructor. |
The five selection methods hide the previous +0x124 pane, show the chosen field, and replace +0x124. The button-state bytes are also updated so exactly one ordinary content button is selected.
Skill inventory and slot panes
The skill inventory parent has vtable ui_skill_inventory_pane_vtable. It owns exactly 36 heap pointers.
| Parent offset | Width | Meaning |
|---|---|---|
+0x00F4 | 36 * 4 | Slot pane pointers for protocol slots 1 through 36. |
SAddSkill replaces the selected entry with a 0x294-byte pane. SRemoveSkill unregisters, removes, deletes, and clears the entry. SCooldown marks the selected child and later clears it through a parent timer.
| Skill slot offset | Width | Meaning |
|---|---|---|
+0x0000 | 4 | ui_skill_slot_pane_vtable. |
+0x00F4 | 2 | Server-provided big-endian display ID, stored in native little-endian form. |
+0x00F6 | variable, maximum observed name length 127 | NUL-terminated skill name. |
+0x0276 | 1 | 1-based skill slot. This byte is sent by CUseSkill. |
+0x0277 | 1 | Pointer-capture or pressed state used by the mouse handler. |
+0x0278 | 1 | Cooldown-active flag. ui_skill_slot_start_cooldown sets it. |
+0x027C | 8 | Stored pointer-down point, two signed 32-bit coordinates. |
+0x0284 | 16 | Pointer-tracking client rectangle. |
Spell inventory and slot panes
The spell inventory parent mirrors the skill parent and also owns 36 heap pointers at +0xF4.
| Spell slot offset | Width | Meaning |
|---|---|---|
+0x0000 | 4 | ui_spell_slot_pane_vtable. |
+0x00F4 | 1 | 1-based spell slot. This byte is sent by the immediate CUseSpell path. |
+0x00F6 | 2 | Server-provided big-endian display ID, stored in native little-endian form. |
+0x00F8 | 1 | Signed spell type, established range 1 through 8. It selects the client activation path. |
+0x00F9 | 0x80 | NUL-terminated spell name buffer. |
+0x0179 | 0x80 | NUL-terminated spell comment buffer. |
+0x01F9 | 1 | unknown_1F9, copied from the trailing SAddSpell byte and passed to SpellBookDialog. |
+0x01FA | 1 | Pointer-capture or pressed state used by the mouse handler. |
+0x01FB | 1 | Cooldown-active flag. ui_spell_slot_start_cooldown sets it. |
+0x01FC | 8 | Stored pointer-down point, two signed 32-bit coordinates. |
+0x0204 | 16 | Pointer-tracking client rectangle. |
Equipment and self-look fields
The User Equip pane uses vtable ui_equip_pane_vtable and has a direct static root. The item fields use a 1-based protocol slot directly in the identifier expression, while names use a zero-based record index.
| Offset or expression | Width | Meaning |
|---|---|---|
+0x0550 through +0x0553 | 4 | Four appearance bytes copied from the packet. |
+0x0554 | 0x100 | NUL-terminated unknown_554 self-look string buffer. |
+0x0654 | 0x80 | NUL-terminated unknown_654 self-look string buffer. |
+0x06D4 | 0x80 | NUL-terminated unknown_6D4 self-look string buffer. |
+0x0754 | 0x80 | NUL-terminated unknown_754 self-look string buffer. |
+0x07D4 | at least 0x100 | NUL-terminated unknown_7D4 self-look string buffer. |
+0x0B22 + 2 * slot | 2 | Item identifier for 1-based equipment slot 1 through 13. |
+0x0B3E + 0x80 * (slot - 1) | 0x80 | NUL-terminated item name record for slot 1 through 13. |
+0x11C0 | 4 | unknown_11C0 big-endian packet value stored in host order. |
+0x11C4 | 1 | unknown_11C4 packet byte. |
+0x11C8 | 4 | Owned helper-pane pointer, allocated by the equipment constructor. |
+0x11CC | 4 | Owned LegendDialogPane pointer. |
+0x11D0 | 1 | unknown_11D0 state flag initialized to zero. |
The five strings are updated as one SSelfLook transaction. A snapshot reader should copy each bounded buffer and accept it only if the pane pointer and vtable remain unchanged after the copy.
BulletinSession fields
| Session offset | Width | Meaning |
|---|---|---|
+0x0000 | 4 | ui_bulletin_session_vtable. |
+0x00F5 | 1 | Current child-history index. |
+0x00F8 | 10 * 4 | Ten child-dialog history pointers. |
+0x0120 | 4 | Active bulletin or mail child pane. |
+0x0124 | 1 | Request-wait state used to suppress overlapping operations. |
The session is a Pane and can be validated against vtable RVA 0x00101E60. Its active child at +0x120 is another Pane with its own bulletin or mail class vtable.
Users Dialog Pane fields
| Pane offset | Width | Meaning |
|---|---|---|
+0x0000 | 4 | ui_users_dialog_pane_vtable. |
+0x0550 | 9 * 4 | Nine owned heap row/list object pointers. |
+0x0574 | 4 | Additional owned list-storage object. |
+0x0578 | 1 | unknown_578 state flag. |
+0x0579 | 1 | unknown_579 state flag. |
+0x057C | 4 | Owned list or scrolling control pointer. |
+0x0581 | 1 | unknown_581 state flag. |
Row record internals are not yet mapped to the documentation threshold. The direct pane root, visibility, common bounds, and nine owned objects are established.
Stable external snapshot procedure
The client does not expose a lock for external readers. Screen insertion and removal can relocate a list’s packed node array, and deferred deletion can free a pane after it disappears from a registry. Use retryable snapshots:
- Read list stride, count, array pointer, and root pointer.
- Reject an unexpected stride, an implausible count, integer overflow in
count * stride, a null array with nonzero count, or an unreadable byte range. - Copy
count * stridebytes with oneReadProcessMemoryoperation. - Re-read the four metadata fields and discard the copy if any changed.
- Parse nodes from the local byte copy with unaligned-safe reads.
- Snapshot each non-null child list recursively.
- Before reading a pane, confirm that its first four bytes are readable and that its vtable is a known module address.
- After copying class-specific state, re-read the pane’s vtable and the owner pointer that led to it. Discard the object snapshot if either changed.
The core packed-node calculation is:
static uintptr_t packed_node_address(
uintptr_t node_array,
uint32_t node_stride,
uint32_t node_index)
{
return node_array + (uintptr_t)node_stride * node_index;
}
Do not cast the remote 0x3F- or 0x0F-byte nodes to an ordinary local C structure. Their second and later records are not four-byte aligned.
Function table
| Address | Current IDA name | Prototype | Purpose | Call relationships and notes |
|---|---|---|---|---|
Darkages.exe:0x0040E660 | ui_bulletin_session_deleting_dtor | established __thiscall deleting destructor | Destroy the active BulletinSession and its child history. | Clears ui_bulletin_session and unregisters and removes the Pane. |
Darkages.exe:0x0040EB10 | ui_bulletin_session_ctor | void *__thiscall(void *, int, const uint8_t *) | Construct and publish a 0x12C-byte BulletinSession Pane. | Initializes the ten-entry history and one-byte request-wait field. |
Darkages.exe:0x00423984 | ui_lag_indicator_pane_dtor | void __thiscall(struct ui_lag_indicator_pane *) | Destroy the lag indicator Pane. | Clears ui_lag_indicator_pane before base Pane cleanup. |
Darkages.exe:0x004239C0 | ui_get_lag_indicator_pane | struct ui_lag_indicator_pane *__cdecl(void) | Return the persistent lag indicator pointer. | Used by the SMove timing path and game-pane teardown. |
Darkages.exe:0x004239D0 | ui_lag_indicator_pane_ctor | struct ui_lag_indicator_pane *__thiscall(struct ui_lag_indicator_pane *, void *) | Construct and publish the 0xF8-byte lag indicator Pane. | Initializes the displayed value to 300 and calls the attach method. |
Darkages.exe:0x00423A44 | ui_lag_indicator_pane_attach | void __thiscall(struct ui_lag_indicator_pane *, void *) | Add and register the lag indicator below BackgroundPane. | Uses left/top/right/bottom arguments (537, 314, 549, 328). |
Darkages.exe:0x00423AA0 | ui_lag_indicator_pane_draw | void __thiscall(struct ui_lag_indicator_pane *) | Select and draw a statcon.epf frame. | Vtable slot +0x48; applies the five value bands documented above. |
Darkages.exe:0x00423B60 | ui_lag_indicator_update_from_local_user | void __thiscall(struct ui_lag_indicator_pane *) | Average the latest local-user timing sample into the displayed value. | Reads local user +0x6F34, writes pane +0xF4, and invalidates the Pane. |
Darkages.exe:0x00431150 | event_dispatcher_register_pane | void __thiscall(void *, void *, int, int) | Insert a pane into the Event hierarchy. | Event list is reached at dispatcher +0x68. |
Darkages.exe:0x0043A5F0 | ui_game_buttons_pane_ctor | void *__thiscall(void *, void *, void *) | Construct GameButtonsPane and its persistent content graph. | Installs ui_game_buttons_pane_vtable. |
Darkages.exe:0x0043CF20 | ui_game_buttons_handle_key_event | int __thiscall(void *, void *) | Select equipment, skills, spells, chat, or status. | Writes button state and calls ui_game_buttons_select_content. |
Darkages.exe:0x00442A34 | ui_skill_inventory_apply_add_packet | int __thiscall(void *, const uint8_t *) | Parse SAddSkill and replace a slot child. | Calls remove and create methods. |
Darkages.exe:0x00442C04 | ui_skill_inventory_remove_slot_pane | void __thiscall(void *, uint8_t) | Remove and delete one skill slot pane. | Clears one pointer at parent +0xF4. |
Darkages.exe:0x00442D14 | ui_skill_inventory_create_slot_pane | void __thiscall(void *, uint8_t, uint16_t, const char *) | Allocate and register one skill slot pane. | Object size 0x294. |
Darkages.exe:0x00443BB4 | ui_spell_inventory_apply_add_packet | int __thiscall(void *, const uint8_t *) | Parse SAddSpell and replace a slot child. | Calls remove and create methods. |
Darkages.exe:0x00443E14 | ui_spell_inventory_remove_slot_pane | void __thiscall(void *, uint8_t) | Remove and delete one spell slot pane. | Clears one pointer at parent +0xF4. |
Darkages.exe:0x00443F24 | ui_spell_inventory_create_slot_pane | void __thiscall(void *, uint8_t, uint16_t, int8_t, const char *, const char *, uint8_t) | Allocate and register one spell slot pane. | Object size 0x214. |
Darkages.exe:0x00446090 | ui_point_init | void __cdecl(void *, int, int) | Initialize an eight-byte client point. | Used for Pane relative-origin fields. |
Darkages.exe:0x004460B0 | ui_rect_init | void __cdecl(void *, int, int, int, int) | Initialize a client rectangle from left, top, right, and bottom. | Stores fields in top, left, bottom, right memory order. |
Darkages.exe:0x0044E4D0 | ui_hierarchy_list_ctor | void *__thiscall(void *, int) | Construct a packed hierarchy list. | Node stride is payload size plus 0x0B. |
Darkages.exe:0x00454CA0 | ui_skill_slot_finish_cooldown | established __thiscall method | Clear a skill slot’s cooldown-active byte. | Called by the parent inventory timer path. |
Darkages.exe:0x00454CC0 | ui_skill_slot_start_cooldown | established __thiscall method | Set a skill slot’s cooldown-active byte. | Reached from SCooldown processing. |
Darkages.exe:0x00456220 | ui_spell_slot_finish_cooldown | established __thiscall method | Clear a spell slot’s cooldown-active byte. | Called by the parent inventory timer path. |
Darkages.exe:0x00456240 | ui_spell_slot_start_cooldown | established __thiscall method | Set a spell slot’s cooldown-active byte. | Reached from SCooldown processing. |
Darkages.exe:0x00456B90 | ui_skill_slot_pane_ctor | void *__thiscall(void *, uint8_t, uint16_t, const char *) | Initialize a skill slot’s persistent fields. | Installs ui_skill_slot_pane_vtable. |
Darkages.exe:0x00456C70 | ui_spell_slot_pane_ctor | void *__thiscall(void *, uint8_t, uint16_t, int8_t, const char *, const char *, uint8_t) | Initialize a spell slot’s persistent fields. | Installs ui_spell_slot_pane_vtable. |
Darkages.exe:0x004594A0 | ui_hierarchy_get_node | void *__thiscall(void *, int) | Return array + index * stride after bounds checks. | Confirms unaligned packed-node addressing. |
Darkages.exe:0x004876C4 | ui_local_user_dtor | void __thiscall(struct ui_local_user *) | Destroy the local-user Pane and owned state. | Clears ui_local_user_singleton; the game-pane teardown path clears the owning ui_local_user root. |
Darkages.exe:0x00487700 | ui_local_user_ctor | struct ui_local_user *__thiscall(struct ui_local_user *, void *) | Construct and publish the 0x776C-byte local-user Pane. | Initializes the movement sequence and timing fields. |
Darkages.exe:0x00487860 | ui_get_local_user | struct ui_local_user *__cdecl(void) | Return the active local-user pointer. | Supplies the raw timing sample to the lag indicator update. |
Darkages.exe:0x004884E4 | net_c_send_move | void __thiscall(struct ui_local_user *, uint8_t) | Build and queue CMove while starting its timing sample. | Increments +0x6F28 and overwrites +0x6F2C. |
Darkages.exe:0x004889F0 | ui_local_user_handle_server_packet | int __thiscall(struct ui_local_user *, void *) | Dispatch local-user server actions. | Routes action 0x0B to ui_local_user_handle_smove. |
Darkages.exe:0x004897C4 | ui_local_user_handle_smove | int __thiscall(struct ui_local_user *, const uint8_t *) | Parse SMove, record a raw timing sample, and process movement acknowledgment or correction. | Calls the lag indicator update after timeGetTime - last_move_send_tick. |
Darkages.exe:0x00493240 | ui_pane_get_bound_rect | void __thiscall(void *, int32_t *) | Return Pane bounds translated by its relative origin. | Reads local rectangle +0x38 and point +0xA8. |
Darkages.exe:0x004932B0 | ui_pane_set_bound_rect | void __thiscall(void *, const int32_t *) | Split input bounds into relative origin and local rectangle. | Vtable slot +0x24; called during Screen insertion. |
Darkages.exe:0x00498F40 | ui_screen_add_pane | established __thiscall add method | Insert a pane and 0x34-byte payload into Screen. | Copies visibility and pane flags into the node. |
Darkages.exe:0x004993C4 | ui_screen_find_pane_node | established recursive __thiscall search | Find a pane in Screen. | Traverses nested packed child lists. |
Darkages.exe:0x00499ED0 | ui_screen_get_pane_origin | established __thiscall query | Accumulate a pane’s screen origin. | Uses Screen parent relationships. |
Pane Virtual Table Inventory
This appendix lists the 174 virtual tables confirmed to use the Pane layout in the analyzed client. The initial scan found 173 tables by locating data references to ui_pane_register_events at virtual slot +0x30, then confirming the adjacent Screen, unregister, and handler slots. GameButtonsPane was added separately because it overrides the registration slots but preserves the surrounding Pane layout. The result includes full panes, dialogs, controls, subclass variants, and temporary construction or destruction tables. It is not a claim that the client can display 174 independent windows at once.
No usable Microsoft C++ class RTTI or type-descriptor names were found for this family. The virtual-table address is therefore the reliable runtime discriminator. Each object begins with one of these table pointers while that implementation is active.
The linked code family is included only where constructor, destructor, handler, or diagnostic data flow associates the table with that implementation unit. It does not by itself assert an original C++ class name. Unassigned means that the common Pane layout is established but neither the owning implementation unit nor class has met the naming threshold.
Confirmed friendly class names
The following names meet the stronger class threshold. Each name is connected to its exact vtable by a direct table installation, a class-specific virtual stored in that table, or both. Diagnostic text is supporting evidence only after that instruction-level connection is established.
Event coverage uses M for mouse Event types 0 through 7, K for key-down Event type 8, S for socket Event type 9, and T for the separate timer callback slot. M1, for example, means mouse subtype 1. Default means the slot returns without class-specific behavior. The full handler addresses remain in the complete inventory below.
| Friendly class | Vtable and current IDA name | Direct confirming evidence | Concrete Event-facing behavior |
|---|---|---|---|
AggrementDialogPane | Darkages.exe:0x005002C0ui_aggrement_dialog_pane_vtable | Destructor diagnostic plus direct table installation. The spelling Aggrement is the binary’s spelling. | Generic Dialog M/K; default S/T. |
BackPane | Darkages.exe:0x00500700ui_background_pane_vtable | BackPane.cpp, the gpBackPane diagnostic, and ui_background_pane_ctor. Documentation uses BackgroundPane for its role. | M1; S 0x49; T 0,1,2. |
BulletinSession | Darkages.exe:0x00501E60ui_bulletin_session_vtable | Constructor and destructor diagnostics, direct table installation, and static root publication. | Custom M/K; S 0x31; default T. |
BulletinDialogPane | Darkages.exe:0x00501EC0ui_bulletin_dialog_pane_vtable | Constructor diagnostic and ui_bulletin_dialog_pane_register at class vslot +0x60. | Adds M1/M3 to Dialog behavior; custom K; S 0x31; default T. |
ArticleListDialog | Darkages.exe:0x00501F40ui_article_list_dialog_vtable | Constructor diagnostic and ui_article_list_dialog_start_new_article_editing at +0x4C. | Adds M1/M3; custom K; S 0x31; default T. |
MailListDialog | Darkages.exe:0x00501FC0ui_mail_list_dialog_vtable | Constructor diagnostic and class-specific key and completion handlers. | Adds M1/M3; custom K; S 0x31; default T. |
NewArticleDialog | Darkages.exe:0x005020C0ui_new_article_dialog_vtable | Constructor diagnostic and ui_new_article_dialog_handle_control_click at +0x4C. | Adds M1/M3; K Tab/Enter/Escape; S 0x31; default T. |
NewMailDialog | Darkages.exe:0x00502140ui_new_mail_dialog_vtable | Constructor diagnostic and ui_new_mail_dialog_handle_control_click at +0x4C. | Adds M1/M3; K Tab/Enter/Escape; S 0x31; default T. |
BoardListDialog | Darkages.exe:0x005021C0ui_board_list_dialog_vtable | Constructor diagnostics for its title, board count, controls, and completed object. | Adds M1/M3; shared Bulletin K; S 0x31; custom T. |
ArticleDialog | Darkages.exe:0x00502240ui_article_dialog_vtable | Constructor diagnostic and directly installed table. | Adds M1/M3; custom K; S 0x31; default T. |
MailDialog | Darkages.exe:0x005022C0ui_mail_dialog_vtable | Constructor diagnostic and directly installed table. | Adds M1/M3; custom K; S 0x31; default T. |
ArticleListPane | Darkages.exe:0x005023E0ui_article_list_pane_vtable | Constructor diagnostic and ui_article_list_pane_draw_cell at +0x84. | Scrollable M/K; default S/T. |
MailListPane | Darkages.exe:0x00502580ui_mail_list_pane_vtable | Constructor diagnostic plus selection-double-click and DrawCell virtuals at +0x80/+0x84. | Scrollable M/K; default S/T. |
LegendDialogPane | Darkages.exe:0x00506C80ui_legend_dialog_pane_vtable | ui_legend_dialog_pane_ctor installs this table on the outer dialog. Its separately allocated inner object uses 0x00506BE0 and remains unnamed. | Adds M1/M4 to Dialog behavior; generic Dialog K; default S/T. |
| Lag indicator pane | Darkages.exe:0x00509C20ui_lag_indicator_pane_vtable | ui_lag_indicator_pane_ctor directly installs the table, publishes a static root, and provides the statcon.epf draw method at +0x48. | Default M/K/S/T; Draw at +0x48. |
GameButtonsPane | Darkages.exe:0x00510300ui_game_buttons_pane_vtable | GameButtonsPane.cpp, direct installation by ui_game_buttons_pane_ctor, and the content-selection handlers in its Event slots. | Custom M/K/S/T. |
HumanImageControlPane | Darkages.exe:0x005134E0ui_human_image_control_pane_vtable | ui_human_image_control_pane_draw at vslot +0x48 and its class diagnostic. | Control M/K; default S/T; Draw at +0x48. |
MainMenuPane | Darkages.exe:0x005177C0ui_main_menu_pane_vtable | ui_main_menu_pane_ctor, static root publication, and class diagnostics. | M0/M3; custom K; S 0x00/0x02/0x03/0x0A; default T. |
MapPane | Darkages.exe:0x00519280ui_map_pane_vtable | ui_map_pane_ctor, static root publication, and multiple class-specific virtuals and diagnostics. | Custom M/K; 31 S actions; T 0..4. |
ItemListPane | Darkages.exe:0x0051B6C0ui_item_list_pane_vtable | ui_item_list_pane_draw_cell at +0x84 and its class diagnostic. | Scrollable M/K; default S/T; DrawCell at +0x84. |
MonsterImageControlPane | Darkages.exe:0x0051BE60ui_monster_image_control_pane_vtable | ui_monster_image_control_pane_draw at +0x48 and its class diagnostic. | Control M/K; default S/T; Draw at +0x48. |
QuestionMessageDialog | Darkages.exe:0x0051C0C0ui_question_message_dialog_vtable | ui_question_message_dialog_send_answer at +0x74 and matching class diagnostics. | Adds M1/M3 to Dialog behavior; generic Dialog K; default S/T. |
QuestionMessageFaceDialog | Darkages.exe:0x0051C140ui_question_message_face_dialog_vtable | Class-specific user-dialog virtual at +0x64 and ui_question_message_face_dialog_send_answer at +0x74. | Adds M1/M3 to Dialog behavior; generic Dialog K; default S/T. |
MonsterPane2 | Darkages.exe:0x0051D700ui_monster_pane2_vtable | ui_monster_pane2_start_move at +0x64 and its class diagnostics. | Custom M/S/T; base K. |
NewUser local-user pane | Darkages.exe:0x0051E980ui_local_user_vtable | NewUser.cpp, direct table installation by ui_local_user_ctor, its class-level singleton publication, and the separate owning game-pane root. | Custom M/K/T; S 0x04/0x05/0x08/0x0B/0x0F/0x10/0x17/0x18/0x51. |
EffectObjectPane | Darkages.exe:0x0051FB00ui_effect_object_pane_vtable | ui_effect_object_pane_handle_timer at +0x44 and its method diagnostic. | Custom M/S/T; base K. |
OptionPane | Darkages.exe:0x00520660ui_option_pane_vtable | OptionPane.cpp, direct table installation by ui_option_pane_ctor, and class-specific handlers in all four Event slots. | Custom M/K/T; S 0x21. |
Pane | Darkages.exe:0x005213A0ui_pane_vtable | ui_pane_ctor, base destructor, and shared registration layout. | Base handlers are default M/K/S/T. |
ScreenPane | Darkages.exe:0x00524CE0ui_screen_pane_vtable | Persistent root construction, static publication, and Screen-specific keyboard and timer virtuals. | Custom K/T; default M/S. |
ServerSelectDialogPane | Darkages.exe:0x00526140ui_server_select_dialog_pane_vtable | Constructor and destructor diagnostics plus ui_server_select_handle_mouse_event. | Adds M2 to Dialog behavior; generic Dialog K; S 0x56; default T. |
SpellBookDialog | Darkages.exe:0x00528280ui_spell_book_dialog_vtable | ui_spell_book_dialog_draw at +0x48 and its method diagnostic. | Generic Dialog M/K; default S/T; Draw at +0x48. |
SkillBookDialog | Darkages.exe:0x005282E0ui_skill_book_dialog_vtable | ui_skill_book_dialog_draw at +0x48 and its method diagnostic. | Generic Dialog M/K; default S/T; Draw at +0x48. |
TerminalPane | Darkages.exe:0x00529000ui_terminal_pane_vtable | Constructor, static root publication, and class-specific bootstrap diagnostics. | M1/M3; custom K; bootstrap-stream S; default T. |
WeatherPane | Darkages.exe:0x0052CFA0ui_weather_pane_vtable | ui_weather_pane_draw at +0x48 and its class diagnostics. | Custom T; default M/K/S; Draw at +0x48. |
The installing or restoring column contains functions with direct references to the table. It commonly includes both a constructor and a destructor that restores the derived table during cleanup. Handler slots have these established roles:
| Slot | Role |
|---|---|
+0x38 | Mouse Event types 0 through 7 |
+0x3C | Keyboard Event type 8 |
+0x40 | Socket-packet Event type 9 |
+0x44 | Pane timer callback |
+0x48 | Additional class-specific handler; generic role not yet established |
+0x4C | Optional class-specific handler; used as a completion hook by TextEdit |
All addresses below are virtual addresses in Darkages.exe.
| Linked code family or confirmed class | Vtable | Installing or restoring functions | Mouse +0x38 | Keyboard +0x3C | Socket +0x40 | Timer +0x44 | Other +0x48, +0x4C |
|---|---|---|---|---|---|---|---|
| AggrementDialogPane | Darkages.exe:0x005002C0 | Darkages.exe:0x0040A034Darkages.exe:0x0040A0E0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0040A420 |
| BackPane.cpp | Darkages.exe:0x00500700 | Darkages.exe:0x0040A564Darkages.exe:0x0040A5B0 | ui_background_pane_handle_mouse_eventDarkages.exe:0x0040B0C0 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_handle_server_request_portraitDarkages.exe:0x0040B3A0 | ui_background_pane_handle_timerDarkages.exe:0x0040B240 | Darkages.exe:0x0040AD000 |
| BalloonPane.cpp | Darkages.exe:0x00500E00 | Darkages.exe:0x0040B480 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x0040BB60 | Darkages.exe:0x0040BE700 |
| BlackHole.cpp | Darkages.exe:0x00501640 | Darkages.exe:0x0040CE10Darkages.exe:0x0040D060Darkages.exe:0x0040D270 | Darkages.exe:0x0040D2A0 | Darkages.exe:0x0040D5E0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x0040DE20 | Darkages.exe:0x0040D9400 |
| BlackHole.cpp | Darkages.exe:0x005016A0 | Darkages.exe:0x0040CE50Darkages.exe:0x0040E010 | Darkages.exe:0x0040E1F0 | Darkages.exe:0x0040E210 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x0040E360 | Darkages.exe:0x0040E2400 |
| BulletinSession | Darkages.exe:0x00501E60 | ui_bulletin_session_deleting_dtorDarkages.exe:0x0040E660ui_bulletin_session_ctorDarkages.exe:0x0040EB10Darkages.exe:0x00413930 | Darkages.exe:0x0040EAD0 | Darkages.exe:0x0040EAC0 | Darkages.exe:0x00413CE0 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| Bulletin.cpp | Darkages.exe:0x00501EC0 | Darkages.exe:0x0040F7D4 | Darkages.exe:0x004142E0 | Darkages.exe:0x00414130 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520nullsub_7Darkages.exe:0x0040EA80 |
| Bulletin.cpp | Darkages.exe:0x00501F40 | Darkages.exe:0x0040FC24 | Darkages.exe:0x004142E0 | Darkages.exe:0x00415A10 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00414BD0 |
| Bulletin.cpp | Darkages.exe:0x00501FC0 | Darkages.exe:0x00411C24 | Darkages.exe:0x004142E0 | Darkages.exe:0x004184C0 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00419390 |
| Bulletin.cpp | Darkages.exe:0x00502040 | Darkages.exe:0x00415BF4 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Bulletin.cpp | Darkages.exe:0x005020C0 | Darkages.exe:0x00415044 | Darkages.exe:0x004142E0 | Darkages.exe:0x004181A0 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00417E90 |
| Bulletin.cpp | Darkages.exe:0x00502140 | Darkages.exe:0x00418D74 | Darkages.exe:0x004142E0 | Darkages.exe:0x0041AE70 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0041AA80 |
| Bulletin.cpp | Darkages.exe:0x005021C0 | Darkages.exe:0x0040F1E4 | Darkages.exe:0x004142E0 | Darkages.exe:0x00414130 | Darkages.exe:0x00414260 | Darkages.exe:0x00414770 | Darkages.exe:0x0042B520Darkages.exe:0x00414340 |
| Bulletin.cpp | Darkages.exe:0x00502240 | Darkages.exe:0x00410DC4 | Darkages.exe:0x004142E0 | Darkages.exe:0x00416E60 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00417180 |
| Bulletin.cpp | Darkages.exe:0x005022C0 | Darkages.exe:0x00412D24 | Darkages.exe:0x004142E0 | Darkages.exe:0x0041A3B0 | Darkages.exe:0x00414260 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0041A760 |
| Bulletin.cpp | Darkages.exe:0x00502340 | Darkages.exe:0x0040F834 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Bulletin.cpp | Darkages.exe:0x005023E0 | Darkages.exe:0x004107B4 | Darkages.exe:0x00416E10 | Darkages.exe:0x00416DB0 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Bulletin.cpp | Darkages.exe:0x00502480 | Darkages.exe:0x004166E4 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Bulletin.cpp | Darkages.exe:0x00502500 | Darkages.exe:0x0041C000 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Bulletin.cpp | Darkages.exe:0x00502580 | Darkages.exe:0x00412714 | Darkages.exe:0x0041A310 | Darkages.exe:0x0041A360 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Bulletin.cpp | Darkages.exe:0x00502620 | Darkages.exe:0x0041A960 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Bulletin.cpp | Darkages.exe:0x005026A0 | Darkages.exe:0x0041BE70 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| CharacterLegend.cpp | Darkages.exe:0x00506BE0 | Darkages.exe:0x0041C230Darkages.exe:0x0041C2E0Darkages.exe:0x0041C4D0 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| CharacterLegend.cpp | Darkages.exe:0x00506C80 | Darkages.exe:0x0041C2A4Darkages.exe:0x0041C4D0 | Darkages.exe:0x0041C770 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0041C750 |
| ChattingPane.cpp | Darkages.exe:0x005070E0 | Darkages.exe:0x0041C924Darkages.exe:0x0041C9A0 | Darkages.exe:0x004A1680 | Darkages.exe:0x0041C9F0 | Darkages.exe:0x0041CCA0 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x004B5FA0 |
| Config.cpp | Darkages.exe:0x00508400 | Darkages.exe:0x0041E6A0 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x004234E0 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| Lag indicator pane | Darkages.exe:0x00509C20ui_lag_indicator_pane_vtable | ui_lag_indicator_pane_dtorDarkages.exe:0x00423984ui_lag_indicator_pane_ctorDarkages.exe:0x004239D0 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | ui_lag_indicator_pane_drawDarkages.exe:0x00423AA00 |
| Container.cpp | Darkages.exe:0x00509F80 | Darkages.exe:0x00423D10 | Darkages.exe:0x00424360 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00424330 |
| Container.cpp | Darkages.exe:0x0050A000 | Darkages.exe:0x00423BC0Darkages.exe:0x00423DA4Darkages.exe:0x00424400Darkages.exe:0x00424500 | Darkages.exe:0x00424F20 | Darkages.exe:0x00423CD0 | Darkages.exe:0x00425370 | Darkages.exe:0x00425230 | Darkages.exe:0x00425280Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050A9A0 | Darkages.exe:0x004262E4 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050AA20 | Darkages.exe:0x00425E10Darkages.exe:0x00425E84Darkages.exe:0x00425EE4Darkages.exe:0x00425F44Darkages.exe:0x00426654 | Darkages.exe:0x00426140 | Darkages.exe:0x00426490 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050AAA0 | Darkages.exe:0x00425F94Darkages.exe:0x00427B00 | Darkages.exe:0x00427D30 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004280A0Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050AB20 | Darkages.exe:0x00425FF4Darkages.exe:0x004285F0 | Darkages.exe:0x004287D0 | Darkages.exe:0x004287F0 | Darkages.exe:0x00428810 | Darkages.exe:0x00428830 | Darkages.exe:0x00428B00Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050ABA0 | Darkages.exe:0x00426054Darkages.exe:0x00428B60 | Darkages.exe:0x00429170 | Darkages.exe:0x00429190 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004293F0Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050AC20 | Darkages.exe:0x00425DD0Darkages.exe:0x00426200 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00426360Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050ACA0 | Darkages.exe:0x00425E84Darkages.exe:0x00426550 | Darkages.exe:0x00426140 | Darkages.exe:0x00426490 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00426C80Darkages.exe:0x004267E0 |
| Control.cpp | Darkages.exe:0x0050AD20 | Darkages.exe:0x00425EE4Darkages.exe:0x00426E80 | Darkages.exe:0x00426140 | Darkages.exe:0x00426490 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00427540Darkages.exe:0x004270A0 |
| Control.cpp | Darkages.exe:0x0050ADA0 | Darkages.exe:0x00427650 | Darkages.exe:0x00426140 | Darkages.exe:0x00426490 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00427A70Darkages.exe:0x004278A0 |
| Control.cpp | Darkages.exe:0x0050AE20 | Darkages.exe:0x004260C4Darkages.exe:0x004294D0 | Darkages.exe:0x004296B0 | Darkages.exe:0x004296E0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00429710Darkages.exe:0x00426180 |
| Control.cpp | Darkages.exe:0x0050AEA0 | Darkages.exe:0x00426100Darkages.exe:0x00429760 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004297C0Darkages.exe:0x00426180 |
| Dialog.cpp | Darkages.exe:0x0050B760 | Darkages.exe:0x00429880Darkages.exe:0x00429A30Darkages.exe:0x00429BB0Darkages.exe:0x0042C924 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520nullsub_7Darkages.exe:0x0040EA80 |
| Dialog.cpp | Darkages.exe:0x0050B7C0 | Darkages.exe:0x0042C0F0Darkages.exe:0x0042CB50 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| DraggedPane.cpp | Darkages.exe:0x0050BF80 | Darkages.exe:0x0042D454Darkages.exe:0x0042D4A0 | Darkages.exe:0x0042D530 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60nullsub_18Darkages.exe:0x0042DB00 |
| EquipPane.cpp | Darkages.exe:0x0050C3A0 | Darkages.exe:0x0042DB10Darkages.exe:0x0042DC20 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x0042DE60 | Darkages.exe:0x0042DDE00 |
| EquipPane.cpp | Darkages.exe:0x0050C6C0 | Darkages.exe:0x0042E0E4Darkages.exe:0x0042E1A0 | Darkages.exe:0x0042EF40 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0042EAF0 | Darkages.exe:0x0042EAA0 | Darkages.exe:0x0042F200Darkages.exe:0x0042F030 |
| EquipPane.cpp | Darkages.exe:0x0050C720 | Darkages.exe:0x0042E074Darkages.exe:0x0042E9A4 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x0042F930 | Darkages.exe:0x0042F9600 |
| EquipPane.cpp | Darkages.exe:0x0050C780 | Darkages.exe:0x0042E164Darkages.exe:0x0042FA90 | Darkages.exe:0x00430500 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x00430730Darkages.exe:0x004305A0 |
| Exchange.cpp | Darkages.exe:0x0050EA00 | Darkages.exe:0x00435174Darkages.exe:0x004352C0 | Darkages.exe:0x0042A190 | Darkages.exe:0x00435DC0 | Darkages.exe:0x00435EA0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00435980 |
| Exchange.cpp | Darkages.exe:0x0050EA60 | Darkages.exe:0x004352C0 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Exchange.cpp | Darkages.exe:0x0050EB00 | Darkages.exe:0x00435294Darkages.exe:0x00435FC4 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x00437C70 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00437920 |
| Exchange.cpp | Darkages.exe:0x0050EB60 | Darkages.exe:0x00435204Darkages.exe:0x00436D50 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x004374F0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x004370C0 |
| Exchange.cpp | Darkages.exe:0x0050EBC0 | Darkages.exe:0x00436D50Darkages.exe:0x00437580 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| FieldMap.cpp | Darkages.exe:0x0050F8E0 | Darkages.exe:0x004387E0Darkages.exe:0x00439240 | Darkages.exe:0x004392D0 | Darkages.exe:0x004387B0 | Darkages.exe:0x004387C0 | Darkages.exe:0x004387D0 | Darkages.exe:0x004395500 |
| GameButtonsPane | Darkages.exe:0x00510300 | ui_game_buttons_pane_dtorDarkages.exe:0x0043A274ui_game_buttons_pane_ctorDarkages.exe:0x0043A5F0 | Darkages.exe:0x0043BFB0 | ui_game_buttons_handle_key_eventDarkages.exe:0x0043CF20 | Darkages.exe:0x0043D650 | Darkages.exe:0x0043D7B0 | Darkages.exe:0x0043D8D00 |
| GameButtonsPane.cpp | Darkages.exe:0x00510360 | Darkages.exe:0x00439FF4Darkages.exe:0x0043AF04 | Darkages.exe:0x0043FE80 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0043F420 | Darkages.exe:0x004402A0 | Darkages.exe:0x004402F00 |
| GameButtonsPane.cpp | Darkages.exe:0x005103C0 | Darkages.exe:0x00439D00Darkages.exe:0x0043A9A4Darkages.exe:0x00440AC0Darkages.exe:0x00440BA0 | Darkages.exe:0x00441760 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00441850 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004421400 |
| GameButtonsPane.cpp | Darkages.exe:0x00510420 | Darkages.exe:0x00439DE0Darkages.exe:0x0043A9A4Darkages.exe:0x00442230Darkages.exe:0x00442300 | Darkages.exe:0x004428A0 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00442990 | Darkages.exe:0x00443190 | Darkages.exe:0x004432900 |
| GameButtonsPane.cpp | Darkages.exe:0x00510480 | Darkages.exe:0x00439EC0Darkages.exe:0x0043A9A4Darkages.exe:0x00443340Darkages.exe:0x00443440 | Darkages.exe:0x00443A20 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00443B10 | Darkages.exe:0x004443C0 | Darkages.exe:0x004444C00 |
| GameButtonsPane.cpp | Darkages.exe:0x005104E0 | Darkages.exe:0x00444570 | Darkages.exe:0x004A1680 | Darkages.exe:0x0043A5D0 | Darkages.exe:0x00444690 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x004B5FA0 |
| GameButtonsPane.cpp | Darkages.exe:0x00510560 | Darkages.exe:0x0043A164Darkages.exe:0x0043B2B4 | Darkages.exe:0x0043DE30 | Darkages.exe:0x0043E070 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x0043E3B00 |
| GameButtonsPane.cpp | Darkages.exe:0x005105C0 | Darkages.exe:0x0043A200Darkages.exe:0x0043A5F0 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x00444E40 | Darkages.exe:0x00444EB00 |
| GameButtonsPane.cpp | Darkages.exe:0x00510620 | Darkages.exe:0x0043A1D4Darkages.exe:0x0043A5F0Darkages.exe:0x0043E7A0 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0043E8B0 | Darkages.exe:0x0043EC30 | Darkages.exe:0x0043EC700 |
| GameButtonsPane.cpp | Darkages.exe:0x00510680 | Darkages.exe:0x0043B1E4 | Darkages.exe:0x00444B00 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00444D800 |
| GameButtonsPane.cpp | Darkages.exe:0x005106E0 | Darkages.exe:0x0043A4E0Darkages.exe:0x0043B374Darkages.exe:0x00444F50 | Darkages.exe:0x0042A190 | Darkages.exe:0x004450A0 | Darkages.exe:0x00445570 | Darkages.exe:0x004455E0 | Darkages.exe:0x00445260Darkages.exe:0x00444FD0 |
| GameButtonsPane.cpp | Darkages.exe:0x00510740 | Darkages.exe:0x0043A594Darkages.exe:0x0043B694 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00445EC0 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00445ED00 |
| HumanPane.cpp | Darkages.exe:0x005134E0 | Darkages.exe:0x0044FD10 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x0044FD40Darkages.exe:0x00426180 |
| HumanPane.cpp | Darkages.exe:0x005138E0 | Darkages.exe:0x0044FE60 | Darkages.exe:0x0048EC60 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0044FE20 | Darkages.exe:0x004503A0 | Darkages.exe:0x004506B0Darkages.exe:0x0048E3D0 |
| ItemPane.cpp | Darkages.exe:0x005146A0 | Darkages.exe:0x004536F4 | Darkages.exe:0x00452BB0 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00452BC0 | Darkages.exe:0x00452BD0 | Darkages.exe:0x00452C800 |
| ItemPane.cpp | Darkages.exe:0x00514700 | Darkages.exe:0x00453684 | Darkages.exe:0x00453A20 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00452BE0 | Darkages.exe:0x00452D00 | Darkages.exe:0x00452C800 |
| ItemPane.cpp | Darkages.exe:0x00514760 | Darkages.exe:0x00453640 | Darkages.exe:0x00453790 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00452BE0 | Darkages.exe:0x00452D00 | Darkages.exe:0x00452C800 |
| ItemPane.cpp | Darkages.exe:0x005147C0 | Darkages.exe:0x00456AF0 | Darkages.exe:0x00453E60 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00452BF0 | Darkages.exe:0x00454220 | Darkages.exe:0x00454310Darkages.exe:0x00452B90 |
| ItemPane.cpp | Darkages.exe:0x00514820 | Darkages.exe:0x00456B90 | Darkages.exe:0x004544B0 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00452C00 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00454CE00 |
| ItemPane.cpp | Darkages.exe:0x00514880 | Darkages.exe:0x00456C70 | Darkages.exe:0x00455040 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00452C10 | Darkages.exe:0x004560B0 | Darkages.exe:0x004562600 |
| ItemPane.cpp | Darkages.exe:0x005148E0 | Darkages.exe:0x00453790Darkages.exe:0x00453A24Darkages.exe:0x00453D40 | Darkages.exe:0x0042D530 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60Darkages.exe:0x00457270 |
| ItemPane.cpp | Darkages.exe:0x00514940 | Darkages.exe:0x004529F0Darkages.exe:0x00452DF4 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x004533E0 |
| ItemPane.cpp | Darkages.exe:0x005149A0 | Darkages.exe:0x00452A30Darkages.exe:0x004530C4 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00453520 |
| ItemPane.cpp | Darkages.exe:0x00514A00 | Darkages.exe:0x00453E60 | Darkages.exe:0x0042D530 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60Darkages.exe:0x004570B0 |
| ItemPane.cpp | Darkages.exe:0x00514A60 | Darkages.exe:0x004544B0 | Darkages.exe:0x0042D530 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60Darkages.exe:0x00456F10 |
| ItemPane.cpp | Darkages.exe:0x00514AC0 | Darkages.exe:0x00456074 | Darkages.exe:0x0042D530 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA60Darkages.exe:0x00456D60 |
| ItemPane.cpp | Darkages.exe:0x00514B20 | Darkages.exe:0x00455EC4 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE6F0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x00456470 |
| ItemPane.cpp | Darkages.exe:0x00514B80 | Darkages.exe:0x00455604 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE640 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x00456560 |
| ItemPane.cpp | Darkages.exe:0x00514BE0 | Darkages.exe:0x00452B64Darkages.exe:0x00456860 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x004568B0 | Darkages.exe:0x00456940 | nullsub_6Darkages.exe:0x0040EA600 |
| ListPane.cpp | Darkages.exe:0x00516260 | Darkages.exe:0x0045A080Darkages.exe:0x0045A0C0 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| MainMenu.cpp | Darkages.exe:0x005177C0 | Darkages.exe:0x0045D754Darkages.exe:0x0045D930 | ui_main_menu_handle_mouse_eventDarkages.exe:0x0045D9B0 | ui_main_menu_handle_key_eventDarkages.exe:0x0045F480 | ui_main_menu_handle_server_packetDarkages.exe:0x0045F780 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004607500 |
| MainMenu.cpp | Darkages.exe:0x00517820 | Darkages.exe:0x0045D7D0Darkages.exe:0x0045E934 | Darkages.exe:0x0042A190 | Darkages.exe:0x004629C0 | Darkages.exe:0x0045D8A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00462A60 |
| MainMenu.cpp | Darkages.exe:0x005178A0 | Darkages.exe:0x0045D790Darkages.exe:0x0045DD14 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x00461080 | Darkages.exe:0x00461D20 | Darkages.exe:0x00460B80Darkages.exe:0x00461E80 |
| MainMenu.cpp | Darkages.exe:0x00517900 | Darkages.exe:0x0045D820Darkages.exe:0x0045F004 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x00463A60 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00463210 |
| MainMenu.cpp | Darkages.exe:0x00517960 | Darkages.exe:0x00463594 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x00464300 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00464060 |
| MapPane.cpp | Darkages.exe:0x00519220 | Darkages.exe:0x00465530Darkages.exe:0x004655A4Darkages.exe:0x004657C0Darkages.exe:0x00465C84 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| MapPane.cpp | Darkages.exe:0x00519280 | Darkages.exe:0x004655A4Darkages.exe:0x004658C0 | ui_map_handle_mouse_eventDarkages.exe:0x00467360 | ui_map_handle_key_eventDarkages.exe:0x00467320 | ui_map_dispatch_server_packetDarkages.exe:0x00468A90 | ui_map_handle_timerDarkages.exe:0x0046CB20 | Darkages.exe:0x0046D0D00 |
| MapPane.cpp | Darkages.exe:0x005192E0 | Darkages.exe:0x004690F0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00470410 |
| MapView.cpp | Darkages.exe:0x0051A420 | Darkages.exe:0x004704C4Darkages.exe:0x00470770 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | Darkages.exe:0x00470D00 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004708100 |
| Merchant.cpp | Darkages.exe:0x0051AD20 | Darkages.exe:0x004716F0Darkages.exe:0x00471D10Darkages.exe:0x00479240 | Darkages.exe:0x00471C90 | Darkages.exe:0x00479470 | Darkages.exe:0x00479480 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| Merchant.cpp | Darkages.exe:0x0051AD80 | Darkages.exe:0x00471CB4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520nullsub_7Darkages.exe:0x0040EA80 |
| Merchant.cpp | Darkages.exe:0x0051AE00 | Darkages.exe:0x00472AA4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047BBF0 |
| Merchant.cpp | Darkages.exe:0x0051AE80 | Darkages.exe:0x004729A4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051AF00 | Darkages.exe:0x004747A4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047C630Darkages.exe:0x0047C2F0 |
| Merchant.cpp | Darkages.exe:0x0051AF80 | Darkages.exe:0x00471C64Darkages.exe:0x0047A984 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x0047D7F0 | Darkages.exe:0x0047D870Darkages.exe:0x00426180 |
| Merchant.cpp | Darkages.exe:0x0051B000 | Darkages.exe:0x00472074 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047B8E0Darkages.exe:0x0047B610 |
| Merchant.cpp | Darkages.exe:0x0051B080 | Darkages.exe:0x00473544 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047B8E0Darkages.exe:0x0047B610 |
| Merchant.cpp | Darkages.exe:0x0051B100 | Darkages.exe:0x00473CE4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B180 | Darkages.exe:0x00474FB4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047C630Darkages.exe:0x0047C2F0 |
| Merchant.cpp | Darkages.exe:0x0051B200 | Darkages.exe:0x00475814 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B280 | Darkages.exe:0x00476284 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B300 | Darkages.exe:0x00476C14 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B380 | Darkages.exe:0x00477F14 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B400 | Darkages.exe:0x00477594 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B480 | Darkages.exe:0x004788F4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047C190 |
| Merchant.cpp | Darkages.exe:0x0051B500 | Darkages.exe:0x004795A4 | Darkages.exe:0x0047B050 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0047AFD0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0047D480 |
| Merchant.cpp | Darkages.exe:0x0051B580 | Darkages.exe:0x00473264 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Merchant.cpp | Darkages.exe:0x0051B620 | Darkages.exe:0x004745F4 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Merchant.cpp | Darkages.exe:0x0051B6C0 | Darkages.exe:0x00476174 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Message.cpp | Darkages.exe:0x0051BE60 | Darkages.exe:0x0047E780 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00482F30Darkages.exe:0x00426180 |
| Message.cpp | Darkages.exe:0x0051BEE0 | Darkages.exe:0x0047DD84 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520nullsub_7Darkages.exe:0x0040EA80 |
| Message.cpp | Darkages.exe:0x0051BF40 | Darkages.exe:0x0047DD50 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047E870Darkages.exe:0x0047DED0 |
| Message.cpp | Darkages.exe:0x0051BFC0 | Darkages.exe:0x0047DF50Darkages.exe:0x0047F250Darkages.exe:0x00481650Darkages.exe:0x00483030 | Darkages.exe:0x00426140 | Darkages.exe:0x00423CD0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x00483060Darkages.exe:0x00426180 |
| Message.cpp | Darkages.exe:0x0051C040 | Darkages.exe:0x0047EAA0 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047EF30Darkages.exe:0x0047DED0 |
| Message.cpp | Darkages.exe:0x0051C0C0 | Darkages.exe:0x0047EFC0 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0047FAA0Darkages.exe:0x0047F200 |
| Message.cpp | Darkages.exe:0x0051C140 | Darkages.exe:0x0047FC80 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x004806C0Darkages.exe:0x0047FEC0 |
| Message.cpp | Darkages.exe:0x0051C1C0 | Darkages.exe:0x004808A0 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00480A20 |
| Message.cpp | Darkages.exe:0x0051C240 | Darkages.exe:0x004812C0 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x00482070Darkages.exe:0x00481600 |
| Message.cpp | Darkages.exe:0x0051C2C0 | Darkages.exe:0x00482230 | Darkages.exe:0x0047DBA0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x004824F0 |
| Unassigned | Darkages.exe:0x0051CAA0 | Darkages.exe:0x00483120Darkages.exe:0x00483770 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x00483C30 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| MonsterPane2 | Darkages.exe:0x0051D700 | Darkages.exe:0x004859E4Darkages.exe:0x00485B00 | Darkages.exe:0x0048EC60 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0044FE20 | Darkages.exe:0x004865A0 | Darkages.exe:0x00486100Darkages.exe:0x0048E3D0 |
| Unassigned | Darkages.exe:0x0051E100 | Darkages.exe:0x00487320Darkages.exe:0x00487360 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
NewUser local-user pane | Darkages.exe:0x0051E980ui_local_user_vtable | ui_local_user_dtorDarkages.exe:0x004876C4ui_local_user_ctorDarkages.exe:0x00487700 | Darkages.exe:0x00487C40 | Darkages.exe:0x00487C70 | ui_local_user_handle_server_packetDarkages.exe:0x004889F0 | Darkages.exe:0x00489980 | Darkages.exe:0x004506B0Darkages.exe:0x0048E3D0 |
| ObjectPane.cpp | Darkages.exe:0x0051FA40 | Darkages.exe:0x0048C514 | Darkages.exe:0x0048BFB0 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0044FE20 | Darkages.exe:0x0048BFC0 | nullsub_35Darkages.exe:0x0048BFD0Darkages.exe:0x00452B90 |
| ObjectPane.cpp | Darkages.exe:0x0051FAA0 | Darkages.exe:0x0048BC74Darkages.exe:0x0048C160 | Darkages.exe:0x0048C680 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0048BFE0 | Darkages.exe:0x0048C840 | nullsub_35Darkages.exe:0x0048BFD0Darkages.exe:0x00452B90 |
| ObjectPane.cpp | Darkages.exe:0x0051FB00 | Darkages.exe:0x0048C910 | Darkages.exe:0x0048BFF0 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0048C000 | Darkages.exe:0x0048D2E0 | Darkages.exe:0x0048D4C0Darkages.exe:0x00452B90 |
| ObjectPane.cpp | Darkages.exe:0x0051FB80 | Darkages.exe:0x0048D600 | Darkages.exe:0x0048C010 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0048C020 | Darkages.exe:0x0048DA40 | Darkages.exe:0x0048DB60Darkages.exe:0x00452B90 |
| ObjectPane.cpp | Darkages.exe:0x0051FC00 | Darkages.exe:0x0048BD80Darkages.exe:0x0048DC10 | Darkages.exe:0x0048C030 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0048C040 | Darkages.exe:0x0048BFC0 | Darkages.exe:0x0048E040Darkages.exe:0x00452B90 |
| ObjectPane.cpp | Darkages.exe:0x0051FC80 | Darkages.exe:0x0048BDF0Darkages.exe:0x0048E150 | Darkages.exe:0x0048EC60 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x0044FE20 | Darkages.exe:0x0048EC50 | nullsub_36Darkages.exe:0x0048C050Darkages.exe:0x0048E3D0 |
| OptionPane | Darkages.exe:0x00520660ui_option_pane_vtable | ui_option_pane_deleting_dtorDarkages.exe:0x0048F410ui_option_pane_ctorDarkages.exe:0x0048F600 | ui_option_pane_handle_mouse_eventDarkages.exe:0x00490A80 | ui_option_pane_handle_key_eventDarkages.exe:0x00490FE0 | ui_option_pane_handle_server_packetDarkages.exe:0x004909E0 | ui_option_pane_handle_timerDarkages.exe:0x00490D70 | Darkages.exe:0x00490E90Darkages.exe:0x0048FC50 |
| OptionPane.cpp | Darkages.exe:0x005206C0 | Darkages.exe:0x0048F5A4Darkages.exe:0x0048FD50 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x004920A0 | Darkages.exe:0x0042B520Darkages.exe:0x00491EB0 |
| OptionPane.cpp | Darkages.exe:0x00520720 | Darkages.exe:0x0048F484Darkages.exe:0x00490120 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x004913F0 | Darkages.exe:0x0042B520Darkages.exe:0x004911D0 |
| OptionPane.cpp | Darkages.exe:0x00520780 | Darkages.exe:0x0048F514Darkages.exe:0x00490470 | Darkages.exe:0x0042A190 | Darkages.exe:0x004917D0 | Darkages.exe:0x00491520 | Darkages.exe:0x00491D80 | Darkages.exe:0x0042B520Darkages.exe:0x00491AD0 |
| OptionPane.cpp | Darkages.exe:0x005207E0 | Darkages.exe:0x00492310 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | ui_exit_wait_handle_server_packetDarkages.exe:0x004921F0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| OrbPane.cpp | Darkages.exe:0x00520FA0 | Darkages.exe:0x004925F4Darkages.exe:0x00492734 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004929C00 |
| OrbPane.cpp | Darkages.exe:0x00521000 | Darkages.exe:0x00492644Darkages.exe:0x00492690 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | Darkages.exe:0x004927F0 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| Pane.cpp | Darkages.exe:0x005213A0 | Darkages.exe:0x00492A90Darkages.exe:0x00492AD0 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | nullsub_6Darkages.exe:0x0040EA600 |
| Paper.cpp | Darkages.exe:0x005218C0 | Darkages.exe:0x004939C0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x00493B60 |
| ParcelIconPane.cpp | Darkages.exe:0x00521C60 | Darkages.exe:0x00494BB4Darkages.exe:0x00494BE0 | Darkages.exe:0x00494EB0 | Darkages.exe:0x00494DC0 | Darkages.exe:0x00494C60 | Darkages.exe:0x00494E40 | Darkages.exe:0x004950500 |
| PatchPane.cpp | Darkages.exe:0x00522000 | Darkages.exe:0x004951D0Darkages.exe:0x004960B0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x004955C0 | Darkages.exe:0x00495E80 | Darkages.exe:0x0042B520Darkages.exe:0x00495E60 |
| PatchPane.cpp | Darkages.exe:0x00522060 | Darkages.exe:0x004955C0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| PatchPane.cpp | Darkages.exe:0x005220E0 | Darkages.exe:0x00496480 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Unassigned | Darkages.exe:0x00523840 | Darkages.exe:0x00497B90 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Unassigned | Darkages.exe:0x005245C0 | Darkages.exe:0x00498CE0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x0042D1E0 |
| Screen.cpp | Darkages.exe:0x00524CE0 | Darkages.exe:0x00498E44Darkages.exe:0x0049B120 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_screen_pane_handle_key_eventDarkages.exe:0x0049D2F0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_screen_pane_handle_timerDarkages.exe:0x0049BBA0 | nullsub_6Darkages.exe:0x0040EA600 |
| ScreenDimmer.cpp | Darkages.exe:0x005257E0 | Darkages.exe:0x0049F2F4Darkages.exe:0x0049F390 | Darkages.exe:0x0049F370 | Darkages.exe:0x0049F380 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x0049F4D00 |
| ScrollablePane.cpp | Darkages.exe:0x00525B40 | Darkages.exe:0x004A0E14 | Darkages.exe:0x0049F7F0 | Darkages.exe:0x004A0670 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x004A0680 | Darkages.exe:0x004A07400 |
| ScrollablePane.cpp | Darkages.exe:0x00525BA0 | Darkages.exe:0x0049F674Darkages.exe:0x004A0CC0 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0049F6D0 |
| ServerSelect dialog | Darkages.exe:0x00526140 | Darkages.exe:0x004A1B04Darkages.exe:0x004A2740 | ui_server_select_handle_mouse_eventDarkages.exe:0x004A2C80 | Darkages.exe:0x0042AED0 | ui_server_select_handle_server_packetDarkages.exe:0x004A2ED0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x004A29F0Darkages.exe:0x004A2AF0 |
| SpellSkillBook.cpp | Darkages.exe:0x00528280 | Darkages.exe:0x004A9A20Darkages.exe:0x004A9BC0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x004AA3B0Darkages.exe:0x004AA660 |
| SpellSkillBook.cpp | Darkages.exe:0x005282E0 | Darkages.exe:0x004A9A60Darkages.exe:0x004AB1D0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x004AB8A0Darkages.exe:0x004ABB30 |
| Terminal.cpp | Darkages.exe:0x00528C60 | Darkages.exe:0x004AC7B0 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004AC8200 |
| Terminal.cpp | Darkages.exe:0x00529000 | Darkages.exe:0x004AC944Darkages.exe:0x004ACA00 | ui_terminal_handle_mouse_eventDarkages.exe:0x004ADC50 | ui_terminal_handle_key_eventDarkages.exe:0x004AEF70 | ui_terminal_handle_server_packetDarkages.exe:0x004AD140 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004AF0500 |
| Terminal.cpp | Darkages.exe:0x00529060 | Darkages.exe:0x004AC9C0Darkages.exe:0x004AE044 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x004AFA90Darkages.exe:0x004AF260 |
| TextEdit.cpp | Darkages.exe:0x00529CE0 | Darkages.exe:0x004AFC90Darkages.exe:0x004BD504 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x004B5FA0 |
| TextEdit.cpp | Darkages.exe:0x00529D60 | Darkages.exe:0x004BE510 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x004B5FA0 |
| TextEdit.cpp | Darkages.exe:0x00529DE0 | Darkages.exe:0x004BD4B0 | Darkages.exe:0x004AFF50 | Darkages.exe:0x004AFF60 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x004B5FA0 |
| TextEdit.cpp | Darkages.exe:0x00529E60 | Darkages.exe:0x004AFE80Darkages.exe:0x004BE354 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE6F0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0nullsub_41Darkages.exe:0x004AFF70 |
| TextEdit.cpp | Darkages.exe:0x00529F00 | Darkages.exe:0x004BE300 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE5A0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0nullsub_41Darkages.exe:0x004AFF70 |
| TextEdit.cpp | Darkages.exe:0x00529F60 | Darkages.exe:0x004BE600 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE640 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0nullsub_41Darkages.exe:0x004AFF70 |
| Unassigned | Darkages.exe:0x0052B800 | Darkages.exe:0x004BF504Darkages.exe:0x004BF670 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | Darkages.exe:0x004BF750 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x004BF720 | Darkages.exe:0x004BF7700 |
| UserPane.cpp | Darkages.exe:0x0052BF20 | Darkages.exe:0x004C08A0 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE6F0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x004C0920 |
| UserPane.cpp | Darkages.exe:0x0052BF80 | Darkages.exe:0x004C09A0 | Darkages.exe:0x004BE100 | Darkages.exe:0x004C0A00 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x004C0BD0 |
| UserPane.cpp | Darkages.exe:0x0052BFE0 | Darkages.exe:0x004C1040 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE6F0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x004C10D0 |
| UserPane.cpp | Darkages.exe:0x0052C040 | Darkages.exe:0x004C0770Darkages.exe:0x004C11A0 | Darkages.exe:0x0042A190 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x004C14A0 |
| UserPane.cpp | Darkages.exe:0x0052C0A0 | Darkages.exe:0x004C07E4Darkages.exe:0x004C14E0 | Darkages.exe:0x004BE100 | Darkages.exe:0x004C15B0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x004C16A0 |
| UserPane.cpp | Darkages.exe:0x0052C100 | Darkages.exe:0x004C1724 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE6F0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x004C1860 |
| UserPane.cpp | Darkages.exe:0x0052C160 | Darkages.exe:0x004C17C4 | Darkages.exe:0x004BE100 | Darkages.exe:0x004BE6F0 | Darkages.exe:0x004BE1B0 | Darkages.exe:0x004BE260 | Darkages.exe:0x004BE2C0Darkages.exe:0x004C1970 |
| UsersList.cpp | Darkages.exe:0x0052CB40 | Darkages.exe:0x004C1A80Darkages.exe:0x004C1B30Darkages.exe:0x004C2650 | Darkages.exe:0x004C26F0 | Darkages.exe:0x0042AED0 | Darkages.exe:0x0040A0A0 | Darkages.exe:0x0040A0B0 | Darkages.exe:0x0042B520Darkages.exe:0x004C26B0 |
| UsersList.cpp | Darkages.exe:0x0052CBC0 | Darkages.exe:0x004C2614 | Darkages.exe:0x004A1680 | Darkages.exe:0x004A1900 | Darkages.exe:0x0040EA70 | ui_pane_default_timer_handlerDarkages.exe:0x0040EA50 | Darkages.exe:0x004A1A50Darkages.exe:0x0045A4C0 |
| Weather | Darkages.exe:0x0052CFA0 | Darkages.exe:0x004C2C60 | ui_pane_default_mouse_handlerDarkages.exe:0x0040B460 | ui_pane_default_key_handlerDarkages.exe:0x0040A5A0 | ui_pane_default_socket_handlerDarkages.exe:0x0040B470 | Darkages.exe:0x004C2FF0 | Darkages.exe:0x004C2D900 |
Glossary
IDA name
The current descriptive identifier assigned in the local IDA database. It is a research aid and does not claim to be an original source-level symbol.
RVA
Relative virtual address. An offset from the loaded image base, distinct from a file offset and a full virtual address.
Stone client
The community or project label for the client release being analyzed. Its exact relationship to the executable’s internal version metadata remains an open identity question.
XOR
Exclusive OR, a bitwise operation sometimes used in reversible byte transformations. Its use in this client is not established yet.