fix(ui): restore authentic 1.13c mercenary hire stone dialog and packet protocol (Hireables.cpp / D2Common ordinal 10458) #558

Closed
opened 2026-10-02 09:57:02 +00:00 by troytt · 0 comments
Owner

Bug Report: Mercenary Hire UI & Packet Protocol (1.13c Ground Truth Mismatch)

Visual Evidence & Symptoms (play.html)

Reference: https://screenshot-v2.corp.google.com/2qe6rvuna5ci8

  1. Fake Left-Dock Panel Layout: Clicking Hire on mercenary vendors (Kashya, Greiz, Asheara, Qual-Kehk) opens a fake 320x432 dark rectangle at (80, 60) with an [X] button, instead of the authentic centered 490x350 carved stone dialog (D2Client.dll 0x6faf9440 + dialog.cpp 0x6fb53e40).
  2. Unclipped Vertical Overflow Over HUD: Candidate cards are rendered as unclipped stacked boxes (i * 68) with no scrollbar, overflowing past 432px all the way down to y = 720px over the bottom control bar (ctrlpnl-800.png) and health globe.
  3. Simultaneous Menu Collision: Both the NPC interaction menu (Kashya / talk / hire / cancel) in the center of the screen and the left hire panel are open at the same time because ClientWorld.applyClientCommand resets window: 'none' when sending 0x38 NpcEntityAction(3), causing HudModel.syncNpcInteraction() to re-open wp.npcMenu on top of the hire panel when 0x4F/0x4E arrives from the server.
  4. Invented Fake Formulas: Candidate level, cost, and stats are generated using fake math (level = (seed % 15) + 5, cost = level * 150 + 100, name = Mercenary #${nameId}, HP = 40 + level * 12, DEF = 20 + level * 6) in src/client/ui-model/hud-model.ts, completely ignoring Hireling.txt / hireling.bin and D2Common.dll ordinal 10458 (0x6fd7ccc0).
  5. C2S 0x36 (MercHire) Protocol Mismatch: When hiring, the client sends seed instead of wMercName (nameId), causing authentic D2GS (D2Game.dll 0x6fcdd5e0) to reject the transaction because candidate verification fails at 0x6fcdd490.
  6. Missing Replace Mercenary Warning Dialog: When the player already possesses a living or dead mercenary, hiring a candidate immediately executes without the authentic 300x160 confirmation prompt (0x6faf7500) warning that the current mercenary and their equipped gear will be lost.

Diablo II v1.13c Binary Ground Truth

  1. Candidate Stat & Cost Derivation (D2Common.dll Ordinals 10988 0x6fd7c9e0 & 10458 0x6fd7ccc0):
    • Act resolution scans hireling.bin name ID range [NameFirst..NameLast] to map wMercName to act index (0..3).
    • Candidate evaluation queries matching base rows in Hireling.txt and applies D2's 64-bit Linear Congruential Generator (seedLo = dwSeed >>> 0, seedHi = 666, multiplier 0x6AC690C5) to pick sub-type, level (Math.max(2, playerLevel + levelOffset - 5)), HP, STR, DEX, DEF, hire cost (Math.trunc((baseCost * (100 + 15 * levelDelta)) / 100)), and ability description (bHireDesc).
  2. Packet Protocol Flow (D2Client.dll & D2Game.dll):
    • Pre-fetching: Opening vendor menu for Kashya (150), Greiz (198), Asheara (252), Qual-Kehk (515) sends 0x38 NpcEntityAction(action: 3, npcId, selfId) (0x6faf8b67).
    • Server returns 0x4F MercForHireListStart (1B) followed by up to 10 0x4E MercForHire (7B: u8 0x4E | u16 wMercName | u32 dwSeed).
    • Hiring: 0x36 MercHire (9B) sends u8 0x36 | u32 npcId | u32 wMercName (the zero-extended wMercName / nameId, NOT dwSeed).
  3. UI Layout & Controls (D2Client.dll 0x6faf9440 + 0x6fb17d00 + 0x6faf7500):
    • Centered 490x350 stone dialog at x = (screenWidth - 490) / 2 = 155, y = screenHeight / 2 - 195 = 105 with boxpieces.dc6 frame.
    • Header at y = 126: ItemDesc1s formatted with totalGold ("Your Gold: %d Hire which Mercenary?" / "你的金錢: %d 雇用那個傭兵? ").
    • Scrollable candidate list at x = 155, y = 140, w = 490, h = 280 with textslid.dc6 scrollbar control.
    • Each candidate formatted into 2 lines in font16 (Line 1: <Name> - Lvl: <lvl> Life: <hp> Def: <def> Cost: <cost>; Line 2: " " + <HireDesc>). Color is blue (3) when hovered/selected, white (0) unselected.
    • Bottom Cancel option at y = 420: String 0x0d48 (Back: "cancel" / "取消"), returning to NPC menu (0x6faf9090).
    • Confirmation dialog (0x6faf7500): If player already has a mercenary, prompt 300x160 stone box with String 0x0d18 (VerifyTransaction9: "This Mercenary will replace your current one." / "這個傭兵會換掉前一位。"), Yes / No.
## Bug Report: Mercenary Hire UI & Packet Protocol (1.13c Ground Truth Mismatch) ### Visual Evidence & Symptoms (`play.html`) Reference: https://screenshot-v2.corp.google.com/2qe6rvuna5ci8 1. **Fake Left-Dock Panel Layout**: Clicking **Hire** on mercenary vendors (Kashya, Greiz, Asheara, Qual-Kehk) opens a fake `320x432` dark rectangle at `(80, 60)` with an `[X]` button, instead of the authentic centered `490x350` carved stone dialog (`D2Client.dll` `0x6faf9440` + `dialog.cpp` `0x6fb53e40`). 2. **Unclipped Vertical Overflow Over HUD**: Candidate cards are rendered as unclipped stacked boxes (`i * 68`) with no scrollbar, overflowing past 432px all the way down to `y = 720px` over the bottom control bar (`ctrlpnl-800.png`) and health globe. 3. **Simultaneous Menu Collision**: Both the NPC interaction menu (`Kashya / talk / hire / cancel`) in the center of the screen and the left hire panel are open at the same time because `ClientWorld.applyClientCommand` resets `window: 'none'` when sending `0x38 NpcEntityAction(3)`, causing `HudModel.syncNpcInteraction()` to re-open `wp.npcMenu` on top of the hire panel when `0x4F`/`0x4E` arrives from the server. 4. **Invented Fake Formulas**: Candidate level, cost, and stats are generated using fake math (`level = (seed % 15) + 5`, `cost = level * 150 + 100`, `name = Mercenary #${nameId}`, `HP = 40 + level * 12`, `DEF = 20 + level * 6`) in `src/client/ui-model/hud-model.ts`, completely ignoring `Hireling.txt` / `hireling.bin` and `D2Common.dll` ordinal 10458 (`0x6fd7ccc0`). 5. **C2S `0x36` (`MercHire`) Protocol Mismatch**: When hiring, the client sends `seed` instead of `wMercName` (`nameId`), causing authentic D2GS (`D2Game.dll` `0x6fcdd5e0`) to reject the transaction because candidate verification fails at `0x6fcdd490`. 6. **Missing Replace Mercenary Warning Dialog**: When the player already possesses a living or dead mercenary, hiring a candidate immediately executes without the authentic `300x160` confirmation prompt (`0x6faf7500`) warning that the current mercenary and their equipped gear will be lost. ### Diablo II v1.13c Binary Ground Truth 1. **Candidate Stat & Cost Derivation (`D2Common.dll` Ordinals 10988 `0x6fd7c9e0` & 10458 `0x6fd7ccc0`)**: - Act resolution scans `hireling.bin` name ID range `[NameFirst..NameLast]` to map `wMercName` to act index (`0..3`). - Candidate evaluation queries matching base rows in `Hireling.txt` and applies D2's 64-bit Linear Congruential Generator (`seedLo = dwSeed >>> 0`, `seedHi = 666`, multiplier `0x6AC690C5`) to pick sub-type, level (`Math.max(2, playerLevel + levelOffset - 5)`), HP, STR, DEX, DEF, hire cost (`Math.trunc((baseCost * (100 + 15 * levelDelta)) / 100)`), and ability description (`bHireDesc`). 2. **Packet Protocol Flow (`D2Client.dll` & `D2Game.dll`)**: - Pre-fetching: Opening vendor menu for Kashya (150), Greiz (198), Asheara (252), Qual-Kehk (515) sends `0x38 NpcEntityAction(action: 3, npcId, selfId)` (`0x6faf8b67`). - Server returns `0x4F MercForHireListStart` (1B) followed by up to 10 `0x4E MercForHire` (7B: `u8 0x4E | u16 wMercName | u32 dwSeed`). - Hiring: `0x36 MercHire` (9B) sends `u8 0x36 | u32 npcId | u32 wMercName` (the zero-extended `wMercName` / `nameId`, NOT `dwSeed`). 3. **UI Layout & Controls (`D2Client.dll` `0x6faf9440` + `0x6fb17d00` + `0x6faf7500`)**: - Centered `490x350` stone dialog at `x = (screenWidth - 490) / 2 = 155, y = screenHeight / 2 - 195 = 105` with `boxpieces.dc6` frame. - Header at `y = 126`: `ItemDesc1s` formatted with `totalGold` (`"Your Gold: %d Hire which Mercenary?"` / `"你的金錢: %d 雇用那個傭兵? "`). - Scrollable candidate list at `x = 155, y = 140, w = 490, h = 280` with `textslid.dc6` scrollbar control. - Each candidate formatted into 2 lines in `font16` (Line 1: `<Name> - Lvl: <lvl> Life: <hp> Def: <def> Cost: <cost>`; Line 2: `" "` + `<HireDesc>`). Color is `blue` (3) when hovered/selected, `white` (0) unselected. - Bottom Cancel option at `y = 420`: String `0x0d48` (`Back`: `"cancel"` / `"取消"`), returning to NPC menu (`0x6faf9090`). - Confirmation dialog (`0x6faf7500`): If player already has a mercenary, prompt `300x160` stone box with String `0x0d18` (`VerifyTransaction9`: `"This Mercenary will replace your current one."` / `"這個傭兵會換掉前一位。"`), `Yes` / `No`.
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#558
No description provided.