User saw "[System] WeenieError 0x0504" on login after a PK Lite reversion. 0x0504 is YouAreNonPKAgain; only ~56 of 378 codes had strings, so the raw hex fallback fired. Retail's source is ClientCommunicationSystem::HandleFailureEvent @0x00571990 — a switch with per-case literal UTF-16 strings, not a DAT string-table lookup, so hardcoding them is retail-faithful. The decomp could not be trusted for the text. Its dump of data_7d32c0 declares [0x5f] and shows 95 characters ending mid-word at "...protection of the Lig". The real string is 139 characters. The 0x5f is Binary Ninja's PREVIEW TRUNCATION LENGTH, not the array size — worth remembering for the rest of the switch, since a copy-paste from the dump would have shipped a truncated sentence. Recovered by PE byte read (VA 0x007D32C0 -> RVA -> .rdata file offset), cross-confirmed against the raw hex the pseudo-C carries immediately after the preview. Mapped 0x0504, 0x0505, 0x04EC, 0x04ED, each byte-verified and cited with its case address. Retail's trailing newline is dropped deliberately (documented in-comment): acdream renders one ChatEntry per system message where retail has a single scrolling buffer. Adjacent codes are deliberately left unmapped with a test pinning that 0x04EE still falls back to hex — a wrong message is worse than a raw code. Files #306 for the full port, with three findings that make it more than a string table: the switch is SIX compiler-lowered blocks spanning 339 distinct case values from 0x17 to 0x593, not one contiguous band; retail passes a colour argument with three values in use (0 x162, 0x1a x113, 7 x59) and acdream's chat has no colour concept; and HandleFailureEvent aborts an in-progress automatic attack on 0x43/0x3f7/0x3e/0x23/0x36 — verified against the decomp, with the nuance that 0x43 has no display case at all and is abort-only, so that one is a pure gameplay gap. Gates: complete Release solution 10,909 passed / 4 skipped / 0 failed (baseline 10,904; +5 = the five new tests). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
210 lines
7.5 KiB
C#
210 lines
7.5 KiB
C#
using AcDream.Core.Chat;
|
|
|
|
namespace AcDream.Core.Tests.Chat;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="WeenieErrorMessages"/>. The retail client showed
|
|
/// these as plain-language strings; we mirror that via templated lookup.
|
|
/// Filed after the 2026-04-25 live launch where the user saw cryptic
|
|
/// "WeenieError 0x051B" in chat for what was actually a friendly login
|
|
/// notification.
|
|
/// </summary>
|
|
public sealed class WeenieErrorMessagesTests
|
|
{
|
|
// ── known codes — informational, parameterised ───────────────────
|
|
|
|
[Fact]
|
|
public void Format_YouHaveEnteredChannel_SubstitutesParam()
|
|
{
|
|
// 0x051B = WeenieErrorWithString.YouHaveEnteredThe_Channel.
|
|
// Template "You have entered the _ channel." with `_` placeholder.
|
|
Assert.Equal(
|
|
"You have entered the General channel.",
|
|
WeenieErrorMessages.Format(0x051B, "General"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_YouHaveEnteredChannel_WorksForEachChannelName()
|
|
{
|
|
Assert.Equal("You have entered the Trade channel.", WeenieErrorMessages.Format(0x051B, "Trade"));
|
|
Assert.Equal("You have entered the LFG channel.", WeenieErrorMessages.Format(0x051B, "LFG"));
|
|
Assert.Equal("You have entered the Roleplay channel.",WeenieErrorMessages.Format(0x051B, "Roleplay"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_YouHaveLeftChannel_SubstitutesParam()
|
|
{
|
|
Assert.Equal(
|
|
"You have left the General channel.",
|
|
WeenieErrorMessages.Format(0x051C, "General"));
|
|
}
|
|
|
|
// ── known codes — informational, no parameter ────────────────────
|
|
|
|
[Fact]
|
|
public void Format_TurbineChatIsEnabled_NoParamForm()
|
|
{
|
|
// 0x051D came in WeenieError (no param) form at login.
|
|
Assert.Equal(
|
|
"Turbine Chat is enabled.",
|
|
WeenieErrorMessages.Format(0x051D, param: null));
|
|
}
|
|
|
|
// ── known codes — error-level ────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Format_CharacterNotAvailable_NoParam()
|
|
{
|
|
// 0x052B fired by the server when a Tell target lookup fails
|
|
// (e.g. the user typed "/t je, hello" → server got "je," → no
|
|
// character). Should read like the retail message.
|
|
Assert.Equal(
|
|
"That person is not available now.",
|
|
WeenieErrorMessages.Format(0x052B, param: null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_TradeComplete()
|
|
{
|
|
Assert.Equal("Trade Complete!", WeenieErrorMessages.Format(0x0529, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_ThatIsNotAValidCommand()
|
|
{
|
|
// 0x0026 fires on /-prefixed text that ACE's command parser
|
|
// can't resolve. Filed after a 2026-04-25 trace where /help
|
|
// produced cryptic "WeenieError 0x0026" lines.
|
|
Assert.Equal(
|
|
"That is not a valid command.",
|
|
WeenieErrorMessages.Format(0x0026, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_YouAreNotInAllegiance()
|
|
{
|
|
Assert.Equal(
|
|
"You are not in an allegiance!",
|
|
WeenieErrorMessages.Format(0x0414, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_YouDoNotBelongToAFellowship()
|
|
{
|
|
Assert.Equal(
|
|
"You do not belong to a Fellowship.",
|
|
WeenieErrorMessages.Format(0x050F, null));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0x0036u, "Action cancelled!")]
|
|
[InlineData(0x003Du, "You charged too far!")]
|
|
[InlineData(0x004Au, "Ack! You killed yourself!")]
|
|
[InlineData(0x0550u, "Out of Range!")]
|
|
public void Format_CombatMovementErrors(uint code, string expected)
|
|
=> Assert.Equal(expected, WeenieErrorMessages.Format(code, null));
|
|
|
|
// ── PK status codes ───────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void Format_YouAreNonPKAgain_ExactRetailText()
|
|
{
|
|
// 0x0504 = WeenieError.YouAreNonPKAgain. Filed after the user saw
|
|
// "WeenieError 0x0504" on login following a PK Lite status
|
|
// reversion. Retail: ClientCommunicationSystem::HandleFailureEvent
|
|
// @0x00571990 case 0x504 @0x005745cd; string recovered byte-exact
|
|
// from data_7d32c0 in the PDB-paired binary (the pseudo-C dump
|
|
// truncates at the declared 0x5f-wchar16 array bound, mid-sentence
|
|
// at "...protection of the Lig").
|
|
Assert.Equal(
|
|
"You are enveloped in a feeling of warmth as you are brought back into the protection of the Light. You are once again a Non-Player Killer.",
|
|
WeenieErrorMessages.Format(0x0504, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_YoureTooCloseToYourSanctuary()
|
|
{
|
|
// 0x0505, case 0x505 @0x00574c65, data_7d2640.
|
|
Assert.Equal(
|
|
"You're too close to your sanctuary!",
|
|
WeenieErrorMessages.Format(0x0505, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_CannotChangePKStatusWhileRecovering()
|
|
{
|
|
// 0x04EC, case 0x4ec @0x0057446f, data_7d3820.
|
|
Assert.Equal(
|
|
"You cannot modify your player killer status while you are recovering from a PK death.",
|
|
WeenieErrorMessages.Format(0x04EC, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_AdvocatesCannotChangePKStatus()
|
|
{
|
|
// 0x04ED, case 0x4ed @0x005744a1, data_7d37b0.
|
|
Assert.Equal(
|
|
"Advocates may not change their player killer status!",
|
|
WeenieErrorMessages.Format(0x04ED, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_AdjacentUnmappedPKCode_StillFallsBackToHex()
|
|
{
|
|
// 0x04EE (LevelTooLowToChangePKStatusWithObject) sits right next
|
|
// to the codes above but its retail literal
|
|
// ("Your level is too low to change…") was NOT independently
|
|
// byte-recovered in this pass — confirms the fallback still
|
|
// covers the untouched neighbours rather than silently guessing.
|
|
Assert.Equal("WeenieError 0x04EE", WeenieErrorMessages.Format(0x04EE, null));
|
|
}
|
|
|
|
// ── unknown codes — graceful fallback preserves debug info ───────
|
|
|
|
[Fact]
|
|
public void Format_UnknownCode_NoParam_FallsBackToHexForm()
|
|
{
|
|
Assert.Equal("WeenieError 0xABCD", WeenieErrorMessages.Format(0xABCD, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_UnknownCode_WithParam_FallsBackToColonForm()
|
|
{
|
|
Assert.Equal(
|
|
"WeenieError 0xDEAD: Mana Stone",
|
|
WeenieErrorMessages.Format(0xDEAD, "Mana Stone"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_UnknownCode_EmptyParam_StaysAsHexOnly()
|
|
{
|
|
// Empty string param shouldn't add a stray colon.
|
|
Assert.Equal("WeenieError 0xCAFE", WeenieErrorMessages.Format(0xCAFE, ""));
|
|
}
|
|
|
|
// ── parameterised templates with non-trivial params ──────────────
|
|
|
|
[Fact]
|
|
public void Format_HearListAdded_SubstitutesParam()
|
|
{
|
|
Assert.Equal(
|
|
"Caith has been added to the list of people you can hear.",
|
|
WeenieErrorMessages.Format(0x0521, "Caith"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_FailToAffectCannotBeHarmed_SubstitutesParam()
|
|
{
|
|
Assert.Equal(
|
|
"You fail to affect Drudge because they cannot be harmed!",
|
|
WeenieErrorMessages.Format(0x004F, "Drudge"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Format_HealingTargetAlreadyFull_SubstitutesParam()
|
|
{
|
|
Assert.Equal(
|
|
"+Acdream is already at full health!",
|
|
WeenieErrorMessages.Format(0x04FF, "+Acdream"));
|
|
}
|
|
}
|