app+protocol: fire CHAT_MSG_HARDCORE, the realm's own chat type
`reference_ui`'s `UNPRODUCED` carried `CHAT_MSG_HARDCORE` as a chain event nothing here fires: the window registered for it and benilla routed no such message, so the realm's hardcore-death broadcasts simply never arrived. Three of grom's addons listen for it too. The missing piece was one number, and the realm's patched `WoW.exe` has it in the PE section it names `.tdata`, beside the string `"HARDCORE"` at `0xd06000`. Two detours do the whole job: `0x49f7aa`, in the chat type-name lookup, jumps to `0xd06020`, which sets the name pointer to that string and the type to `$0x5e`; and `0x49b193`, inside the `CHAT_MSG_*` fire helper `0x49b0b0` whose format string this file already cites, jumps to `0xd06040`, which tests the type against `$0x5e` and swaps in the event-name index `0x21f`. So wire type `0x5E` is `ChatEventKind::Hardcore`. Nothing in 1.12 claims it — its last type is `CHAT_MSG_BATTLEGROUND_LEADER` at `0x5D` and its `CHAT_MSG_BG_SYSTEM_*` block sits at `0x52`-`0x54` — so there is no collision to arbitrate, and the test pins that adjacency along with the decode, since a wrong id would look exactly like the silence it replaces. The chain's `ChatFrame.xml` registers the event and its `GlobalStrings.lua` has the `[Hardcore] %s: ` format, so the window needed nothing from us but the fire; it takes the window's default colour, as `FILTERED` does, the realm adding no `ChatTypeInfo` row. Not covered: sending. The ruRU locale carries `SLASH_HARDCORE1 = "/h"` and `CHAT_HARDCORE_SEND` where enUS has only the receive format, so the send side needs its own look. 269 data-gated failures, identical to the baseline, all nine gates green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -41,6 +41,12 @@ pub(crate) enum ChatEventKind {
|
||||
RaidLeader,
|
||||
RaidWarning,
|
||||
RaidBossEmote,
|
||||
/// `CHAT_MSG_HARDCORE` (`0x5E`), the realm's hardcore-death broadcast channel — beyond 1.12,
|
||||
/// which has no type past `0x5D` ([`benilla_protocol::messages::CHAT_MSG_HARDCORE`] carries
|
||||
/// where the id was read). The chain's own `ChatFrame.xml` registers the event and its
|
||||
/// `GlobalStrings.lua` has the `[Hardcore] %s: ` format, so the window needs nothing from us
|
||||
/// but the fire.
|
||||
Hardcore,
|
||||
/// `CHAT_MSG_FILTERED` (`0x5B`), the server's "your message was filtered" notice; `arg2` is the
|
||||
/// addressee the stock frame formats `CHAT_FILTERED` over (`ChatFrame.lua:1406`).
|
||||
Filtered,
|
||||
@@ -225,6 +231,7 @@ impl ChatEventKind {
|
||||
K::RaidWarning,
|
||||
K::RaidBossEmote,
|
||||
K::Filtered,
|
||||
K::Hardcore,
|
||||
K::Battleground,
|
||||
K::BattlegroundLeader,
|
||||
K::BgSystemNeutral,
|
||||
@@ -409,6 +416,7 @@ pub(crate) fn event_name(kind: ChatEventKind) -> &'static str {
|
||||
K::RaidWarning => "CHAT_MSG_RAID_WARNING",
|
||||
K::RaidBossEmote => "CHAT_MSG_RAID_BOSS_EMOTE",
|
||||
K::Filtered => "CHAT_MSG_FILTERED",
|
||||
K::Hardcore => "CHAT_MSG_HARDCORE",
|
||||
K::Battleground => "CHAT_MSG_BATTLEGROUND",
|
||||
K::BattlegroundLeader => "CHAT_MSG_BATTLEGROUND_LEADER",
|
||||
K::BgSystemNeutral => "CHAT_MSG_BG_SYSTEM_NEUTRAL",
|
||||
@@ -546,6 +554,9 @@ pub(crate) fn default_color(kind: ChatEventKind) -> [u8; 3] {
|
||||
// `ChatTypeInfo["FILTERED"]` has no colour (`ChatFrame.lua:112`), so the line takes the
|
||||
// window's default, white; no bubble or edit box header shows this kind.
|
||||
K::Filtered => [255, 255, 255],
|
||||
// The realm adds no `ChatTypeInfo["HARDCORE"]` colour of its own, so the line takes the
|
||||
// window's default the way `FILTERED` does.
|
||||
K::Hardcore => [255, 255, 255],
|
||||
K::Skill => [85, 85, 255],
|
||||
K::Loot => [0, 170, 0],
|
||||
K::Money => [255, 255, 0],
|
||||
@@ -643,6 +654,7 @@ pub(crate) fn kind_of_wire(chat_type: u8) -> Option<ChatEventKind> {
|
||||
m::CHAT_MSG_RAID_WARNING => K::RaidWarning,
|
||||
m::CHAT_MSG_RAID_BOSS_EMOTE => K::RaidBossEmote,
|
||||
m::CHAT_MSG_FILTERED => K::Filtered,
|
||||
m::CHAT_MSG_HARDCORE => K::Hardcore,
|
||||
m::CHAT_MSG_BATTLEGROUND => K::Battleground,
|
||||
m::CHAT_MSG_BATTLEGROUND_LEADER => K::BattlegroundLeader,
|
||||
m::CHAT_MSG_BG_SYSTEM_NEUTRAL => K::BgSystemNeutral,
|
||||
@@ -685,3 +697,32 @@ pub(crate) fn language_name(id: u32) -> &'static str {
|
||||
_ => "", // 0, Universal, and unknown ids: no header
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod hardcore_tests {
|
||||
use super::{event_name, kind_of_wire, ChatEventKind as K};
|
||||
|
||||
/// The realm's hardcore channel, end to end through the decode: wire type `0x5E` is a kind,
|
||||
/// that kind names the event the chain's `ChatFrame.xml` registers, and nothing in 1.12's own
|
||||
/// range moved to make room for it.
|
||||
///
|
||||
/// Pinned because the id came off two detours in the realm's patched `WoW.exe` and nothing
|
||||
/// else states it: `reference/1.12-globals.tsv` has no `CHAT_MSG_HARDCORE`, so a wrong id
|
||||
/// would simply mean a channel that never arrives, which is what it looked like before.
|
||||
#[test]
|
||||
fn the_realms_hardcore_chat_type_decodes_to_its_event() {
|
||||
assert_eq!(
|
||||
kind_of_wire(benilla_protocol::messages::CHAT_MSG_HARDCORE),
|
||||
Some(K::Hardcore)
|
||||
);
|
||||
assert_eq!(event_name(K::Hardcore), "CHAT_MSG_HARDCORE");
|
||||
assert_eq!(benilla_protocol::messages::CHAT_MSG_HARDCORE, 0x5E);
|
||||
// 1.12's own types stop at BATTLEGROUND_LEADER, and its BG_SYSTEM block is far below, so
|
||||
// `0x5E` was free; a decode for `0x5F` is still nobody's.
|
||||
assert_eq!(
|
||||
benilla_protocol::messages::CHAT_MSG_BATTLEGROUND_LEADER + 1,
|
||||
benilla_protocol::messages::CHAT_MSG_HARDCORE
|
||||
);
|
||||
assert_eq!(kind_of_wire(0x5F), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,7 +1107,7 @@ fn every_kind_is_in_all() {
|
||||
seen.sort_unstable();
|
||||
seen.dedup();
|
||||
assert_eq!(seen.len(), before, "a kind is listed twice in ALL");
|
||||
assert_eq!(before, 93, "93 kinds — update this when the kind set grows");
|
||||
assert_eq!(before, 94, "94 kinds — update this when the kind set grows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2250,12 +2250,6 @@ mod tests {
|
||||
|
||||
// Registered by a chain file we load and fired by nothing here; each says why.
|
||||
const UNPRODUCED: &[(&str, &str)] = &[
|
||||
(
|
||||
"CHAT_MSG_HARDCORE",
|
||||
"ChatFrame.xml — the realm's own hardcore-death broadcast channel. Its chat type \
|
||||
has no producer here: benilla routes no such server message, so the window \
|
||||
simply never receives one. Costs the death announcements, nothing functional",
|
||||
),
|
||||
(
|
||||
"MODIFIER_STATE_CHANGED",
|
||||
"AIO's Dep_LibWindow-1.1 — a 2.x event for live modifier-key tracking, used to \
|
||||
|
||||
@@ -40,6 +40,18 @@ pub const CHAT_MSG_FILTERED: u8 = 0x5B;
|
||||
pub const CHAT_MSG_BATTLEGROUND: u8 = 0x5C;
|
||||
pub const CHAT_MSG_BATTLEGROUND_LEADER: u8 = 0x5D;
|
||||
|
||||
/// Beyond 1.12, whose last type is `0x5D`: the realm's own hardcore-death broadcast channel,
|
||||
/// `[Hardcore] %s: ` (`CHAT_HARDCORE_GET` in its `GlobalStrings.lua`).
|
||||
///
|
||||
/// The id is read off the realm's patched `WoW.exe`, which carries this in a PE section it names
|
||||
/// `.tdata` alongside the string `"HARDCORE"` at `0xd06000`. Two detours do it: `0x49f7aa`, in the
|
||||
/// type-name lookup, jumps to `0xd06020`, which sets the name pointer to that string and the type
|
||||
/// to `$0x5e` before returning; and `0x49b193`, inside the `CHAT_MSG_*` fire helper `0x49b0b0`,
|
||||
/// jumps to `0xd06040`, which tests the type against `$0x5e` and swaps in the event-name index
|
||||
/// `0x21f`. Nothing in 1.12 claims `0x5E` — its `CHAT_MSG_BG_SYSTEM_*` sit at `0x52`-`0x54` — so
|
||||
/// there is no collision to arbitrate.
|
||||
pub const CHAT_MSG_HARDCORE: u8 = 0x5E;
|
||||
|
||||
/// The only chat types the 1.12 client runs its `$`-macro expander over; the rest reach the frame
|
||||
/// verbatim. This is the wire gate (`0x49d5e4-0x49d606`); the re-expansion after a name query
|
||||
/// (`0x49cf36`) omits `0x5A`.
|
||||
|
||||
@@ -83,9 +83,9 @@ pub use chat::{
|
||||
chat_tag, ChatMessage, CHAT_MSG_AFK, CHAT_MSG_BATTLEGROUND, CHAT_MSG_BATTLEGROUND_LEADER,
|
||||
CHAT_MSG_BG_SYSTEM_ALLIANCE, CHAT_MSG_BG_SYSTEM_HORDE, CHAT_MSG_BG_SYSTEM_NEUTRAL,
|
||||
CHAT_MSG_CHANNEL, CHAT_MSG_DND, CHAT_MSG_EMOTE, CHAT_MSG_FILTERED, CHAT_MSG_GUILD,
|
||||
CHAT_MSG_IGNORED, CHAT_MSG_MONSTER_EMOTE, CHAT_MSG_MONSTER_SAY, CHAT_MSG_MONSTER_WHISPER,
|
||||
CHAT_MSG_MONSTER_YELL, CHAT_MSG_OFFICER, CHAT_MSG_PARTY, CHAT_MSG_RAID,
|
||||
CHAT_MSG_RAID_BOSS_EMOTE, CHAT_MSG_RAID_BOSS_WHISPER, CHAT_MSG_RAID_LEADER,
|
||||
CHAT_MSG_HARDCORE, CHAT_MSG_IGNORED, CHAT_MSG_MONSTER_EMOTE, CHAT_MSG_MONSTER_SAY,
|
||||
CHAT_MSG_MONSTER_WHISPER, CHAT_MSG_MONSTER_YELL, CHAT_MSG_OFFICER, CHAT_MSG_PARTY,
|
||||
CHAT_MSG_RAID, CHAT_MSG_RAID_BOSS_EMOTE, CHAT_MSG_RAID_BOSS_WHISPER, CHAT_MSG_RAID_LEADER,
|
||||
CHAT_MSG_RAID_WARNING, CHAT_MSG_SAY, CHAT_MSG_SYSTEM, CHAT_MSG_WHISPER,
|
||||
CHAT_MSG_WHISPER_INFORM, CHAT_MSG_YELL, MACRO_EXPANDED_TYPES,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user