authored gmSecureTradeUI window, and both retail open paths
Three-lane research first (docs/research/2026-08-14-trade-lane{A,B,C}):
retail gmSecureTradeUI decode, the byte-exact ACE/decomp/holtburger
three-way wire agreement, and the acdream seam map (which found both
open paths ALREADY classified by the ported policy - OpenSecureTrade on
Use-a-player, StartSecureTrade on drag-item-onto-player with the
DragItemOnPlayerOpensSecureTrade option - dead-ending at a stub toast).
- Core.Net: TradeRequests builders (0x1F6-0x204, retail's CM_Trade
senders byte-checked against ACE's readers; the ACE-discarded
AcceptTrade echo carries zero-count item lists - AD-94), corrected +
completed inbound parsers (0x1FD-0x208; the old AddToTrade parser
missed the SIDE dword, TradeFailure missed the reason), delegate-hole
registrars, six WorldSession sends. 10 golden-byte tests.
- Runtime: RuntimeTradeState, the third sibling J-owner (fellowship/
allegiance shape): session-scoped, clears at generation reset (new
stage Trade=14), staged teardown stage 11 (Identity/EntityObjects
shift 12/13, TeardownStageCount 14 - the FA2-era per-stage-flag test
caught the mapping exactly as designed), combined ownership ledger,
event routing with ACE's wrong-initiator RegisterTrade landmine
honored (partner = whichever guid is not mine). 7 conformance tests.
- App: SecureTradeUiController binds the dedicated authored LayoutDesc
0x2100000D (root 0x1000007A - gmSecureTradeUI::PostInit's exact ids):
partner name/status/count/grid, the authored 'Trade' accept toggle
(accept <-> decline withdraw), 'Clear All' (ACE clears BOTH sides -
surfaced honestly), the X close, drop-on-your-grid staging, per-mode
accept cues (partner icon's authored Highlight state + Trade button
Selected latch). Mounted via the vendor recipe (nine-slice chrome,
hidden until RegisterTrade). ItemInteractionController's two policy
arms now raise SecureTradeRequested instead of the stub toast; the
drag path queues the dragged item until the window registers
(ClientTradeSystem::AttemptToTradeItem @0x0056DF80's shape).
Register: AD-94 (accept-echo zero-count lists), AD-95 (numeric-only
count texts pending template verification).
Suites: App 4,990/3, Core.Net 905, Runtime 1,626 - all green. The
panel itself is user-gate acceptance (two-client connected trade), the
#372-class lesson: fixture-green alone is not acceptance for a mount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
24 KiB
Retail secure-trade UI (gmSecureTradeUI) — decomp decode
Source: docs/research/named-retail/acclient_2013_pseudo_c.txt (Sept 2013 EoR
build, PDB-named). All addresses are acclient.exe v11.4186 file offsets
(module base ~0x00400000). The class is gmSecureTradeUI, not
gmTradeUI — grep the correct name if re-deriving.
Element-type-id convention observed in this file (per DynamicCast/
GetUIElementType pairs): 1=Button, 3(alias 0xa? see ItemList note
below)=Field-ish base, 0xc=Text, and high 0x100000XX values are
per-class dynamic type ids assigned to more complex controls
(gmSecureTradeUI itself is 0x10000012, UIElement_ItemList is
0x10000031 (also answers to legacy id 5), UIElement_UIItem (an
item icon inside a list/paperdoll) is 0x10000032).
1. Class shape: Create / Register / PostInit / ListenToElementMessage
| Method | Address | Notes |
|---|---|---|
gmSecureTradeUI::gmSecureTradeUI (ctor) |
0x004c97c0 |
Base-inits as UIElement_Field, then wires in ObjectRangeHandler and ItemListDragHandler interface vtables (multiple-inheritance vtable-slot assignment — the pseudo-C shows two of these assignments mislabeled as &gmUrgentAssistanceUI::\vftable' / plain vtable-thunk addresses; **this is a known Binary-Ninja mislabeling artifact, not a real base-class relationship** — trust the interface list (ObjectRangeHandler, ItemListDragHandler), not the printed symbol name). Zeroes the button/list pointer block (memset(&m_pTradeButton, 0, 0x28)), inits splitItemStackSize=0, splitItemClassID=INVALID_DID`. |
gmSecureTradeUI::Create |
0x004c9890 |
operator new(0x634) (0x634-byte instance) → ctor. |
gmSecureTradeUI::Register |
0x004c9cc0 |
UIElement::RegisterElementClass(0x10000012, gmSecureTradeUI::Create). Element class id = 0x10000012. |
gmSecureTradeUI::DynamicCast |
0x004c96d0 |
Answers 0x10000012 (self) or 3. |
gmSecureTradeUI::GetUIElementType |
0x004c96f0 |
Returns 0x10000012. |
gmSecureTradeUI::PostInit |
0x004ca160 |
See binding table below. |
gmSecureTradeUI::ListenToElementMessage |
0x004cae80 |
See click table below. |
gmSecureTradeUI::~gmSecureTradeUI / scalar-deleting-dtor |
0x004c9650 / 0x004c9870 |
Nothing trade-specific. |
PostInit element bindings (GetChildRecursive + DynamicCast)
All at 0x004ca160. Order as they appear in code:
| Child element id | DynamicCast arg |
Field | Meaning |
|---|---|---|---|
0x10000086 |
1 (Button) |
m_pTradeButton |
Combined Accept/Decline action button (see click table). |
0x10000085 |
0xc (Text) |
m_pSelfPlayerName |
Your own name label. |
0x10000087 |
0xc (Text) |
m_pSelfTotalItemsLabel |
"N items" label, your side. |
0x10000088 |
0x10000031 (ItemList) |
m_pSelfItemsList |
Your item grid. Also calls UIElement_ItemList::RegisterItemListDragHandler(eax_22, &this->vtable) — gmSecureTradeUI is its own drag handler. |
0x1000007f |
(no cast — raw UIElement*) |
m_pOtherTradeStatusIndicator |
Partner "has accepted" status icon/indicator. |
0x1000007e |
0xc (Text) |
m_pOtherPlayerName |
Partner's name label. |
0x10000080 |
0xc (Text) |
m_pOtherTotalItemsLabel |
"N items" label, partner side. |
0x10000081 |
0x10000031 (ItemList) |
m_pOtherItemsList |
Partner's item grid (no drag handler registered — you can't drag out of/into it). |
0x1000008a |
1 (Button) |
m_pClearAllItemsButton |
"Clear all" / Reset button — same id is also read directly in ListenToElementMessage (see below), so it's bound to a field and hard-checked by id on click. |
PostInit ends by tail-calling gmSecureTradeUI::Reset(this) unconditionally
(both the found- and not-found-0x1000008a paths converge there).
Two more ids are referenced by id only (not bound to named fields in PostInit):
| id | Where used | Meaning |
|---|---|---|
0x1000008b |
ListenToElementMessage |
Click → this->vtable->SetVisible(0). No server notify — this is the panel's own close/"X" button; it just hides the window. |
RegisterNoticeHandler calls in PostInit
PostInit also does 13 GlobalEventHandler::GetGlobalEventHandler()-> RegisterNoticeHandler(<id>, &this->vtable) calls (lines ~0x004ca17a–
0x004ca2e5). The printed <id> values are decompiler noise — most
show as raw hex (0x4dd231…0x4dd23a, 0x186a8, 0x186a9, 0x186ab)
and one shows as a symbol (gmKeyboardUI::ListenToElementMessage) despite
being an unrelated class's method — confirmed by checking 0x4dd230
directly: it is the start of gmKeyboardUI::ListenToElementMessage
(0x004dd230), i.e. Binary Ninja is printing "nearest known symbol" for a
raw 32-bit notice-type constant that happens to numerically fall inside
.text. Do not port these literal values — they are opaque
compile-time notice-type tags, not meaningful addresses. What's reliable
is the count (13, matching the 13 RecvNotice_*/Recv... handlers
below) and each handler's own address/behavior.
2. RecvNotice_* / notice handlers on gmSecureTradeUI
| Handler | Address | Behavior |
|---|---|---|
RecvNotice_RegisterTrade(iidInitiator?, iidPartner, arg4) |
0x004ca5c0 |
Calls SetTradePartner(this, <partner id via a vtable call on this-0x5f8>), then CPlayerSystem::RegisterObjectRangeHandler(playerSystem, &this->m_hashElementsRegisteredWith, arg3/*partner id*/, 0.0, 1, 0, 0.0, 0.0) — registers an object-range handler on the trade partner, so leaving visual range auto-closes the trade (see OnObjectRangeExit below). This is the "trade window opened" handler. |
RecvNotice_AddItemToTrade(itemId, side, slot) |
0x004ca500 |
side==2 → AddPartnerItem(itemId, slot); side==1 → AddMyItem(itemId, slot). |
RecvNotice_RemoveItemFromTrade(itemId, side) |
0x004ca630 |
side==2 → RemovePartnerItem; side==1 → RemoveAddedItem. |
RecvNotice_AcceptTrade(playerGuid) |
0x004c9ce0 |
playerGuid==0: reset-ish path — touches hash-registration bucket [0] (self status) then UpdateTradeButtonState. playerGuid==local player: bucket [0] (self) gets state 6, then UpdateTradeButtonState. Otherwise (partner accepted): bucket [4] (== m_pOtherTradeStatusIndicator's registration slot) gets state 6. (The "ecx->m_hashKey->m_alphaImage(N)" call syntax is another BN vtable-slot-mislabel — functionally this is "set the accept-status indicator element's state to N" via the notice-registration hash, most likely a plain SetState dispatch, not a literal alpha-image setter; treat the numeric state, not the printed method name, as ground truth.) |
RecvNotice_DeclineTrade(playerGuid) |
0x004c9d70 |
Mirror of Accept: playerGuid==local player → bucket [0] state 1 + UpdateTradeButtonState; otherwise (partner declined) → bucket [4] (partner indicator) state 0xd. |
RecvNotice_ClearTradeAcceptance() |
0x004ca540 |
Tailcalls Reset(). |
RecvNotice_CloseTrade(arg2) |
0x004ca550 |
Reset(), then if hash bucket [5] is bound, sets that element's text to the empty string (PStringBase::s_NullBuffer) — likely clears a status/announcement text field. |
RecvNotice_ResetTrade(arg2) |
0x004ca670 |
Tailcalls Reset(). |
RecvNotice_TradeFailure(itemId, arg3) |
0x004ca680 |
RemoveAddedItem(itemId) — rolls back an optimistically-added self item that the server rejected. |
RecvNotice_TradeAnItemForDummies(itemId) |
0x004caf30 |
Tailcalls TradeAnItemForDummies(itemId) (see below — this is the "double-click to add" convenience path, driven by a server/engine notice, not just UI). |
RecvNotice_ServerSaysAttemptFailed(arg2) |
0x004c98c0 |
this->m_hashElementsRegisteredWith...m_aInplaceBuckets[9] = nullptr — clears a registration slot directly (no method call); likely cancels an in-flight optimistic-add tracking entry. Low confidence on exact semantics — flagged, not guessed. |
RecvNotice_ServerSaysMoveItem(...) → ServerSaysMoveItem |
0x004cad30 → 0x004cac20 |
Full signature (itemId, arg3, arg4, arg5, destContainerOrPlayerId, arg7, arg8, arg9). If destContainerOrPlayerId == m_iidTradePartner and ClientTradeSystem::IsPartnerTradingItem(itemId) and the item isn't already in m_pOtherItemsList: pulls the item off any pending destruction queue and calls AddPartnerItem(itemId, ClientTradeSystem::GetItemLocationInPartnerTradeList(itemId)). This is the "partner's item physically arrived via a container-move notice" path (items placed into the trade appear to move into a hidden container owned by the partner; this handler is what makes that show up in the partner grid). |
RecvNotice_ItemAttributesChanged(itemId, arg3) → ItemAttributesChanged |
0x004cad40 → 0x004ca9d0 |
Only acts if this->splitItemID != 0 (i.e. mid-split-for-trade) and arg3 & 1. If the changed item matches the pending split's class id and its new stack size equals the expected split remainder size, calls AddItem(itemId, 0, 0, 0, 1) and clears splitItemID. This is the completion of the "split stack before trading" flow started in AcceptDragObject. |
OnObjectRangeExit(arg2) |
0x004ca4c0 |
If arg2 == ClientTradeSystem::GetTradeSystem()->m_iidTradePartner: ClientTradeSystem::CloseTradeNegotiations() + Reset(). This is the range-based auto-close wired up by RecvNotice_RegisterTrade's RegisterObjectRangeHandler call. |
OnVisibilityChanged(arg2) |
0x004ca470 |
On becoming hidden (need to confirm polarity from arg2, not fully traced) calls Reset(). |
Reset() itself (0x004ca100): guarded by an internal bit-flag check
((this->__inner23 >> 0x11) & 1, likely "is this element actually
constructed/active" — early-outs otherwise). Then: SetTradePartner(0)
(clears partner name), m_pOtherTradeStatusIndicator->SetState(0xd)
(neutral), FlushTradeLists(), SetMyItemNumber(),
SetOtherItemNumber(), UpdateTradeButtonState().
FlushTradeLists() (0x004c9ac0): for every item currently in
m_pSelfItemsList, calls ACCWeenieObject::SetTradeState(item, 0)
(un-flags it as "in a trade") then ItemList_Flush. For every item in
m_pOtherItemsList, if it's not player-owned and not already flagged
container-location 0x3f00000, queues it for destruction
(AddContentsToDestructionQueue) — the client-side proxy objects
representing the partner's offered items are throwaway and get destroyed
when the trade lists are cleared.
3. Opening the trade panel
Use-on-player path (confirmed)
CPlayerSystem::UsingItem(itemId, arg3, arg4) at 0x00562f70 calls
ItemHolder::DetermineUseResult(item) (0x00588460) and switches on
(result - 2). case 3 → result 5 → ClientTradeSystem:: AttemptToOpenTradeNegotiations(GetTradeSystem(), itemId) at
0x00563022.
ItemHolder::DetermineUseResult returns 5 specifically at
0x005885f7 when: the target is not player-owned, has no capacity/
component-pack shortcut, isn't a "negative InqType" special object,
isn't directly ItemUses::IsUseable, and esi->vtable->IsPlayer()
is true and esi->id != <local player id> — i.e., "use" on any
other player, with no other higher-priority use-result, resolves to
"open trade." This is the canonical "use on player → trade" trigger.
ItemHolder::DetermineUseResult — 0x00588460.
ClientTradeSystem::AttemptToOpenTradeNegotiations — 0x0056dee0:
refuses (shows "You need to be in peace mode to …") if
ClientCombatSystem::GetCombatSystem()->combatMode != NONCOMBAT_COMBAT_MODE.
Otherwise sends CM_Trade::Event_OpenTradeNegotiations(targetPlayerId)
(0x0056df6a) — the outbound wire request.
Drag-item-on-player path (confirmed)
ItemHolder::AttemptPlaceIn3D(arg1, arg2, arg3) at 0x00588600 is the
generic "item dropped onto object X in the 3D view" dispatcher. At
0x005887d0:
if (PlayerModule::DragItemOnPlayerOpensSecureTrade(&playerModule) != 0
&& droppedOnObject->vtable->IsPlayer() != 0)
{
ClientTradeSystem::AttemptToTradeItem(GetTradeSystem(), targetPlayerId, droppedItemId);
return 0;
}
So the player-option gate is real and it's checked before the
InqType()==0x10 (give-directly) and container/lock checks that follow
in the same function — dragging onto a player short-circuits straight to
trade-attempt when the option is on, before any "give" logic runs.
- Option getter/setter:
PlayerModule::DragItemOnPlayerOpensSecureTrade(0x005d31b0) /PlayerModule::SetDragItemOnPlayerOpensSecureTrade(0x005d31c0). Player-option enum id:DragItemOnPlayerOpensSecureTrade_PlayerOption. StringTable label/help keys:ID_PlayerOption_DragItemOnPlayerOpensSecureTrade/..._Help, registered viacompute_str_hashat0x004a0f5f/0x004a0f85, added to the options page viaPlayerOptionPage::AddToggleOptionat0x004a0fa4.
ClientTradeSystem::AttemptToTradeItem(targetPlayerId, itemId) —
0x0056df80: requires the item be player-owned. If already trading with
someone:
- same partner →
CM_Trade::SendNotice_TradeAnItemForDummies(itemId)(adds the item directly to the already-open trade — the "convenience" add-while-already-negotiating path). - different partner → refuses with "You are already trading with
som…".
If not yet trading: calls
ItemHolder::UseObject(targetPlayerId, 0, 0)(i.e. re-enters the same use path as above, effectively "use the target player") and stashesattemptTradeToPlayerID/attemptTradeObjectIDfor later. OnceClientTradeSystem::Handle_Trade__Recv_RegisterTrade(0x0056e050, the inbound "trade window opened" handler that also firesCM_Trade::SendNotice_RegisterTrade→gmSecureTradeUI::RecvNotice_RegisterTradeabove) seesm_iidTradePartner == attemptTradeToPlayerID, it callsAttemptToTradeItemagain to actually queue the drag-dropped item into the now-open trade.
Internal UI-queue notice ids (bonus — confirms §1's PostInit id noise is noise)
CM_Trade::DispatchUI_Recv_* (the layer between the network/engine queue
and the RecvNotice_* UI calls) gate on small, contiguous internal
tag values — these are not the raw wire opcodes, but they cross-check
cleanly against each other and confirm the PostInit RegisterNoticeHandler
hex noise (§1) is unrelated decompiler artifact, not meaningful data:
| Notice | Internal tag | Dispatch fn addr |
|---|---|---|
| RegisterTrade | 0x1fd |
0x006acf20 |
| OpenTrade | 0x1fe |
0x006acef0 |
| CloseTrade | 0x1ff |
0x006ace90 |
| AddToTrade | 0x200 |
0x006ace20 |
| RemoveFromTrade | 0x201 |
0x006acf80 |
| AcceptTrade | 0x202 |
0x006ace09/0x006acdf0 |
| DeclineTrade | 0x203 |
0x006ace60(dispatch fn header 0x006acec0) |
| ResetTrade | 0x205 |
0x006acfb0 |
| TradeFailure | 0x207 |
0x006acfe0 |
| ClearTradeAcceptance | 0x208 |
0x006ace60 |
4. Item grids
- Your grid: element id
0x10000088, bound tom_pSelfItemsList, typeUIElement_ItemList(element-type-id0x10000031). This list registers itself as anItemListDragHandlertarget withthis(gmSecureTradeUI) as handler — items are dragged in here by the player. Individual entries, when read back withUIElement_ListBox::GetItem, areDynamicCast(0x10000032)— aUIElement_UIItemicon wrapping anACCWeenieObject(weenObjfield). - Partner's grid: element id
0x10000081, bound tom_pOtherItemsList, sameUIElement_ItemListtype, no drag handler registered — populated only by network notices (AddPartnerItem/RemovePartnerItem/ServerSaysMoveItem), not by local drag-drop. - Counts:
SetMyItemNumber()(0x004c98d0) /SetOtherItemNumber()(0x004c9970) build aStringInfowithSetStringIDandTableEnum(&info, <ID>, 0x10000001)+AddVariable_Int(count)and write the resolved string intom_pSelfTotalItemsLabel/m_pOtherTotalItemsLabel. The printed<ID>literal is0, which is almost certainly the decompiler showing the static pre-initializer value of the globalID_SecureTrade_TotalItemsLabel(see §5) rather than the true runtime-computed hash — same class of artifact as thePostInitnotice ids. Table-enum0x10000001is a StringTable category constant, not further resolved here. - Accept/decline is presented via:
m_pTradeButton(id0x10000086) — one physical button whose currentm_statedecides what a click does (see §1 click table):m_state==6→ click triggersAcceptTheTrade;m_state==1→ click triggersDeclineTheTrade.UpdateTradeButtonState()(0x004c9700) disables the button (state0xd) whenever total items across both sides is0, and re-enables (state1) once items exist. Caution:UIElement_Button::SetState(0x00471da0) shows this button uses a "latch" attribute pair (attrs0xb/0xe) whereSetState(6)/SetState(1)toggle a latch flag and early-return without necessarily rewritingm_stateto that literal value unless the latch was already in the requested position, in which case it falls through to the generic disabled-state path which does writem_state. The exact accept/decline toggle semantics are therefore genuinely convoluted in the retail binary; recommend the port drive the button's visual state fromClientTradeSystem's own accepted/declined booleans rather than replicating this latch dance literally, and validate against a live retail trade if uncertain (this is exactly the kind of "state interacts with prior state in ways not obvious from reading" case the CLAUDE.md's cdb workflow exists for).m_pOtherTradeStatusIndicator(id0x1000007f) — the partner-side accept/decline indicator icon, driven directly byRecvNotice_AcceptTrade/RecvNotice_DeclineTrade(state6= accepted,0xd= neutral/declined) and reset to0xdon everyAddMyItem/AddPartnerItem/RemoveAddedItem/RemovePartnerItem(any list change silently un-accepts the visual, matching retail's "adding an item clears both sides' acceptance" rule).- Drag-affordance color feedback during hover uses the same generic
accept/reject drag-state pair used elsewhere in the UI:
UIElement_UIItem::SetDragAcceptState(item, 0x10000040)(acceptable — green) /0x10000041(rejected — red), set fromOnItemListDragOver(0x004ca980) viaDragItemAcceptable.
DragItemAcceptable(itemId, quiet) (0x004ca6a0): if the item is
player-owned and not already in the self list → acceptable. If not
player-owned: acceptable only path is absent (falls to return 0); if
quiet==0 it also posts a rejection StringInfo via
ECM_UI::SendNotice_DisplayStringInfo(0x1a, …) (the literal string text
itself is unresolved — printed as a mislabeled vtable-slot symbol,
another string-adjacent-to-vtable BN artifact, not a real symbol
reference).
AddItem(itemId, slot, quietFlag, splitAllowedFlag, fromRecursion)
(0x004ca780): if the item has no contained items/containers (i.e. not
itself a container), inserts it directly into m_pSelfItemsList and
calls ClientTradeSystem::AddItemToSelfTradeList (the outbound wire
call). If it is a container (has contained items) and splitAllowedFlag
is set, it instead announces "Trading contents of %s" and recursively
calls AddItem for every contained item — dragging a container into
the trade grid trades its contents individually, not the container
itself.
AcceptDragObject(itemId) (0x004caa40): if DragItemAcceptable
passes and the dropped item's current stack size already equals the max
split size, adds it directly. Otherwise attempts
ItemHolder::AttemptToPlaceInContainer to split off the correct amount
first (stashing splitItemID/splitItemClassID/splitItemStackSize,
announcing "Splitting the %s before trading …"); the actual add happens
later when RecvNotice_ItemAttributesChanged/ItemAttributesChanged
sees the split completion (§2). If the split attempt itself fails,
announces "Cannot split the stack to trade …".
TradeAnItemForDummies(itemId) (0x004cad50): the convenience
"just trade this stack, splitting-and-all" entry point invoked from the
RecvNotice_TradeAnItemForDummies notice (fired e.g. from
AttemptToTradeItem's same-partner re-add case). Refuses with "You must
split the stack before …" if the item is the globally
ACCWeenieObject::selectedID and a split is already in flight
(GenItemHolder::splitSize == maxSplitSize); otherwise calls AddItem
directly.
HandleDropRelease (0x004cae10) / message id 0x15 in
ListenToElementMessage: only processes a drop if the drop's ancestor
chain lands inside m_pSelfItemsList (UIElement::IsAncestorOfMe) —
confirms drops are only ever accepted onto your own grid, never the
partner's.
ListenToElementMessage click table (0x004cae80)
idMessage |
idElement |
Action |
|---|---|---|
1 (click) |
0x10000086 (trade button) |
m_state==6 → AcceptTheTrade(); m_state==1 → DeclineTheTrade(). |
1 (click) |
0x1000008a (clear-all) |
ClientTradeSystem::ResetTrade(GetTradeSystem()) — outbound reset. |
1 (click) |
0x1000008b (close/X) |
this->vtable->SetVisible(0) — local-only hide, no wire traffic. |
0x15 (drop) |
— | HandleDropRelease (self-grid-only, see §4). |
AcceptTheTrade() (0x004c9a10): before sending accept, verifies the
locally-displayed item counts (GetNumUIItems on both lists) match the
server-known counts (ClientTradeSystem::GetNumSelfObjectsInTrade/
GetNumPartnerObjectsInTrade); mismatch →
ClientTradeSystem::NotifyServerThatTradeIsOutOfSync() instead of
accepting — an explicit desync guard the port should replicate.
DeclineTheTrade() (0x004c9a90) is unconditional:
ClientTradeSystem::DeclineTrade().
5. StringTable keys
Only one trade-panel-specific key was found via compute_str_hash("ID_...")
scan of the whole file:
| Key | Hash-init address |
|---|---|
ID_SecureTrade_TotalItemsLabel |
0x006f2d8d |
Related but not-panel-body keys (player options / chat, not the panel
itself): ID_PlayerOption_DragItemOnPlayerOpensSecureTrade (+_Help) at
0x004a0f5f/0x004a0f85; ID_PlayerOption_IgnoreTradeRequests (+_Help)
at 0x004a0eee/0x004a0f14; ID_ChatOption_TextFilter_Trade (+_Desc)
at 0x006f06cd/0x006f06ed; ID_Chat_ChatTargetMenuTrade /
ID_Chat_TellToTrade at 0x006f39ad/0x006f3a6d.
No other ID_SecureTrade* or ID_Trade* keys exist in the pseudo-C.
Player-name and other-side labels are populated directly from
ACCWeenieObject::GetObjectNameWide (not StringTable), and the button's
own caption/tooltip text is presumably baked into the LayoutDesc/DAT
authoring for element 0x10000086 rather than resolved at runtime here —
the porting engineer should pull the actual authored panel (element class
0x10000012, its children 0x1000007e–0x1000008b) from the game's
LayoutDesc DAT resource via the existing LayoutImporter/UI-Studio
tooling to get real control names/captions/positions; this decomp pass
only recovers behavior, not layout.
NOT FOUND
- The literal wire/network opcode for the trade
GameAction/GameEventfamily (as opposed to the internal0x1fd–0x208UI-queue tags in §3) was not located inacclient.hor the pseudo-C by direct grep; cross-checkreferences/ACE/server-side trade handlers if the exact byte-level wire opcode is needed for a lane-B/network task. - The exact text of the two
StringInfo/ECM_UI::SendNotice_DisplayStringInfomessages inDragItemAcceptableand one inAcceptDragObjectthat show as mislabeled vtable-slot symbols instead of string literals (BN string-adjacent-to-vtable artifact) — content unrecoverable from this file alone. - Full struct layout / field offsets for
gmSecureTradeUIandClientTradeSystemare not present inacclient.h(not reconstructed in this PDB pass); field names above are taken from the pseudo-C's ownthis->fieldNamelabels, which the decompiler DID resolve correctly (these are real PDB member names, unlike the notice-id/string-literal artifacts flagged above).