Grunt Auth Misconceptions

This is in response to this blog post.

First of all, the “NLS” protocol mentioned in the post has basically nothing to do with Battle.net. It is the so-called Grunt protocol. This protocol is a hacky, ad-hoc protocol that most Blizzard games used up until around World of Warcraft’s Wrath of the Lich King expansion. After that came the Battle.net 2.0 protocol (which still seems to differ across games). Battle.net 2.0, as opposed to Grunt, actually brings authentication together with Battle.net features like chatting, friend lists, etc. Battle.net 2.0 uses SRP v6a and uses SHA-256 instead of SHA-1. That’s not all — the process is much more involved (even involves the server sending the client raw machine code modules that the client then executes; similar to, but not quite the same as, Warden) — but the details are beyond the scope of this post.

Grunt is entirely unused by World of Warcraft, Starcraft 2, and Diablo 3 today. The World of Warcraft client does still have the code necessary to perform authentication using Grunt, but it takes some binary patching to get there (most WoW 3.x private servers do this). In other words, the aforementioned blog post sheds light on a protocol that is almost entirely unused today, and the information is therefore not relevant to understanding modern Blizzard games’ authentication processes.

The Grunt protocol was reverse engineered years ago, and has been reversed further as Blizzard added more stuff to it. The results have always been public in just about every World of Warcraft emulator ever, such as MaNGOS, TrinityCore, WCell, etc. One example of an implementation can be found here and here. Further, the opcodes are defined here. This stuff is not exactly revolutionary. Also, the packet names you see in those sources are what they are actually called in the client.

I really don’t want to appear to be a giant dick, but this stuff is not news, nor are the results published on the aforementioned blog and wiki entirely accurate.

(All this being said, the author of the aforementioned blog post is correct in his analysis of SRP.)

Regarding APB

I see some people still drop by #apb-dev now and then. For your information; no, we’re no longer touching the game. We haven’t been for a long time, as we all lost interest pretty early. And seeing how the game is officially dead, it won’t be much fun now, either way.

This makes for a great opportunity for the people who still persist on emulating the game for the sake of having servers up, I guess.

WoW 4.x: Anti-Reversing

18:39:19 | Hasbro: [12:37]  I can come up with several reasons why the recent changes all have some "kill private servers" intention behind them
18:39:20 | Hasbro: post that?

Update: Added some more stuff.

So per request:

  • Battle.net: One might argue that it’s simply a new and innovative serialization platform, but I do believe there’s more to it. With BNet, they switched to SRP-6a (as opposed to SRP-6) and now use SHA-256, rather than SHA-1. Battle.net also uses a VM for (de)serialization, and bit streams to read/write the serialized data. Furthermore, Battle.net packets don’t contain a length in their header, making parsing rather annoying. Why not just stick to SRP-6 and SHA-1, why not just stick to byte streams? I think it’s rather obvious that some of these changes were made to kill private servers (think; a private server storing account passwords with SHA-1 would now have to somehow switch to SHA-256 (which, by the way, Blizzard did with the “Battle.net conversions”)). To top it off, it would be lots of fun if they removed Grunt while they’re at it.
  • Randomized opcodes: Speaks for itself.
  • Direct opcode handler assignments: In the 4.x client, opcode handlers are being assigned directly to the internal handler array in NetClient, rather than through NetClient__RegisterMessageHandler and ClientServices__RegisterMessageHandler. Again, an obvious move to make it harder to spot opcode handlers.
  • Obfuscation: Battle.net.dll is now obfuscated – obvious move against emulation, again. Who knows, maybe that’ll hit the game executable too?
  • Streamed client patches: Makes it hard to work against a specific patch, which we’ve been able to do so far. While this one also serves as a general improvement to their patching model, it’s also a change that will make emulation very uncoordinated.
  • Circular debugging: See here. This is a means of preventing debuggers from attaching to the process.

Disclaimer: The above points represent my speculations solely. I really have no idea what Blizzard’s real intentions are.

Structural Packet Parsing

Okay, I actually came up with this idea like a week ago, but I’ll post about it anyway.

I’ve been thinking lately that I’m tired of the conventional ways of parsing packets, because:

  • Manually indenting the output sucks.
  • Manually typing out an unknown value counter sucks.
  • The code gets way too long.

Optimally, you’d automatically serialize the packets 100% transparently, but that’s just not an option with WoW, due to its obnoxious packet structures. I mean, it’s possible, but the overhead from doing it automatically is just not worth the syntactic sugar. Plus, I’m after short code here, and writing support for such complex packet structures (and then actually writing the structures themselves) would not be worth it. At all.

So, what I want is something that feels like I’m actually writing a structure (or a class if you will), while still supporting conditions, switches, loops, so on. And so I present:

[WorldParser(WorldServerOpCodes.SMSG_CREATURE_QUERY_RESPONSE)]
public static void HandleCreatureQueryResponse(WorldServerOpCodes opCode, PacketProcessor packet)
{
    var entry = packet.UInt32("Entry ID");
    packet.If("(Entry ID & 0x80000000) == 0", (entry & 0x80000000) == 0, () =>
    {
        packet.For(0, 4, i =>
            packet.String("Name"));
        packet.String("Sub Name");
        packet.String("Icon Name");
        packet.Enum<CreatureTypeFlags>("Type Flags");
        packet.Enum<CreatureTypes>("Creature Type");
        packet.Enum<CreatureFamily>("Creature Family");
        packet.Enum<CreatureRank>("Creature Rank");
        packet.For(0, 2, i =>
            packet.UInt32("Kill Credit"));
        packet.For(0, 4, i =>
            packet.UInt32("Display ID"));
        packet.Single("Health Modifier");
        packet.Single("Mana Modifier");
        packet.Boolean("Racial Leader");
        packet.For(0, 6, i =>
            packet.UInt32("Quest Item ID"));
        packet.UInt32("Movement ID");
    });
}

This here packet handler would normally have taken 3 times the lines without the PacketProcessor class I’m using (I won’t post the code, since it’s part of a private project – but writing it is not that hard anyway).

There are several nice things about it:

  • Every data type is read with a very short method call, in fact, shorter than a line you would type out in a struct.
  • If a name is not given to the data type methods, it assumes the purpose of the variable is unknown, and thus uses and increments an internal unknown counter for that specific data type.
  • Conditions, loops, and switches are done through lambdas, meaning you’ll almost never have to use any C# keywords. The PacketProcessor class also has IfMore (executes if the packet has more data) and IfDirection (executes if the packet’s direction matches the given direction), which do not output anything, as you don’t need to see those conditions in the dump.
  • It reads enums 100% correctly. Yes, flags too. It uses the underlying type of the enum to do so. It was brought to my attention by Phazerz, though, that some enums are written as different types in various packets, so I’ll have to come up with a way to handle that. No biggie, though.
  • I get a nice, universal, and automatically formatted output.

And now for the output of the above code:

ServerToClient: SMSG_CREATURE_QUERY_RESPONSE (0x0061) Length: 117 Time: 15-07-2010 14:26:51
Entry ID: 352
If ((Entry & 0x80000000) == 0) => True
{
    For (0 -> 4)
    {
        0 =>
        [
            Name: "Dungar Longdrink"
        ]
        1 =>
        [
            Name: ""
        ]
        2 =>
        [
            Name: ""
        ]
        3 =>
        [
            Name: ""
        ]
    }
    Sub Name: "Gryphon Master"
    Icon Name: ""
    Type Flags: None
    Creature Type: Humanoid
    Creature Family: None
    Creature Rank: Elite
    For (0 -> 2)
    {
        0 =>
        [
            Kill Credit: 0
        ]
        1 =>
        [
            Kill Credit: 0
        ]
    }
    For (0 -> 4)
    {
        0 =>
        [
            Display ID: 5128
        ]
        1 =>
        [
            Display ID: 0
        ]
        2 =>
        [
            Display ID: 0
        ]
        3 =>
        [
            Display ID: 0
        ]
    }
    Health Modifier: 3
    Mana Modifier: 3
    Racial Leader: False
    For (0 -> 6)
    {
        0 =>
        [
            Quest Item ID: 0
        ]
        1 =>
        [
            Quest Item ID: 0
        ]
        2 =>
        [
            Quest Item ID: 0
        ]
        3 =>
        [
            Quest Item ID: 0
        ]
        4 =>
        [
            Quest Item ID: 0
        ]
        5 =>
        [
            Quest Item ID: 0
        ]
    }
    Movement ID: 0
}

As seen, indentation is nicely handled by PacketProcessor, due to the If/For/Switch methods, which all internally take care of indenting and dedenting when necessary.

Some code demonstrating switching (I want to improve this a bit eventually):

[WorldParser(WorldServerOpCodes.SMSG_AUTH_RESPONSE)]
public static void HandleAuthResponse(WorldServerOpCodes opCode, PacketProcessor packet)
{
    var authCode = packet.Enum<ResponseCodes>("Response Code");
    packet.Switch("Code", authCode, new Switch
    {
        { ResponseCodes.AUTH_OK, () =>
            ReadAuthResponse(packet)
        },
        { ResponseCodes.AUTH_WAIT_QUEUE, () =>
        {
            if (packet.Length <= 6)
            {
                ReadQueuePosition(packet);
                return;
            }

            ReadAuthResponse(packet);
            ReadQueuePosition(packet);
        }}
    });
}

And the output:

ServerToClient: SMSG_AUTH_RESPONSE (0x01EE) Length: 11 Time: 15-07-2010 14:26:38
Response Code: AUTH_OK
Switch (Code): AUTH_OK
{
    Case AUTH_OK:
    [
        Remaining Game Time: 8541
        Billing Flags: Trial1, Restricted
        Rested Game Time: 0
        Client Type: WorldOfWarcraft
    ]
    Case AUTH_WAIT_QUEUE:
    [
        <Unmatched>
    ]
}

And some code demonstrating unknown value counting:

[WorldParser(WorldServerOpCodes.SMSG_ACCOUNT_DATA_TIMES)]
public static void HandleAccountDataTimes(WorldServerOpCodes opCode, PacketProcessor packet)
{
    packet.UInt32();
    packet.Byte();
    var mask = packet.UInt32("Time Mask");
    packet.For(0, 8, i =>
        packet.If("(Time Mask & (1 << i)) != 0", (mask & (1 << (int)i)) != 0, () =>
            packet.Int32()));
}

Output:

ServerToClient: SMSG_ACCOUNT_DATA_TIMES (0x0209) Length: 21 Time: 15-07-2010 14:26:39
Unknown UInt32 0: 1279196803
Unknown Byte 0: 1
Time Mask: 21
For (0 -> 8)
{
    0 =>
    [
        If ((Time Mask & (1 << i)) != 0) => True
        {
            Unknown Int32 1: 1278872250
        }
    ]
    1 =>
    [
        If ((Time Mask & (1 << i)) != 0) => False
        {
            <Unmatched>
        }
    ]
    2 =>
    [
        If ((Time Mask & (1 << i)) != 0) => True
        {
            Unknown Int32 1: 0
        }
    ]
    3 =>
    [
        If ((Time Mask & (1 << i)) != 0) => False
        {
            <Unmatched>
        }
    ]
    4 =>
    [
        If ((Time Mask & (1 << i)) != 0) => True
        {
            Unknown Int32 1: 0
        }
    ]
    5 =>
    [
        If ((Time Mask & (1 << i)) != 0) => False
        {
            <Unmatched>
        }
    ]
    6 =>
    [
        If ((Time Mask & (1 << i)) != 0) => False
        {
            <Unmatched>
        }
    ]
    7 =>
    [
        If ((Time Mask & (1 << i)) != 0) => False
        {
            <Unmatched>
        }
    ]
}

Take note of how the unknown counter is kept intact across loops (admittedly the backing code for that is a bit hacky, but hey – it works).

Overall, I’m pretty happy with this. I achieved what I wanted; to be able to write packet parsing in a functional fashion, while preserving the feeling of writing a structure. Only the switching came out a bit bad. As mentioned, I’ll have to look at that again; it most likely could be nicer.

WoW’s SRP Implementation

This is old news (if it can even be called news), but I figured I’d post about it anyway.

While talking to Derex about WoW’s authentication scheme, we stumbled upon several faults in the game’s SRP implementation.

First of all, S is calculated incorrectly at the client:

S = (B - (k * ((g ^ x) % N))) ^ ((a + u * x) % N)

While, according to the SRP standard (all arithmetic is modulo N), it should be:

S = ((B - ((k * ((g ^ x) % N)) % N)) % N) ^ ((a + ((u * x) % N)) % N)

Moving on, the calculation of B on the server end is wrong as well:

B = ((k * v) % N) + ((g ^ b) % N)

Should be:

B = (((k * v) % N) + ((g ^ b) % N)) % N

WolfgangSt pointed out that they might’ve been working against the TLS SRP specification, but if they did, they sure didn’t do it right.

Edit: Forgot to mention; they also send N and g over the wire, both of which are hardcoded anyway…

WoW 3.3.5: Anti-Debugging

Check out this paper on SC2′s anti-debugging. It’s pretty much the same thing WoW has incorporated (quite expected).

Of course, there’re also the Warden changes. It appears Warden now interfaces with Battle.net.dll – another move one would expect from Blizzard, in light of the design decisions they made for SC2. This one, I don’t know so much about, and don’t intend to look into either; I hold no great interest in Warden.

APB: Beta Over, Reversing Progress

Update: We all pretty much lost interest, and stopped working on it.

The beta ended yesterday. Me and a few others have been actively reversing the game, seeking to emulate it.

What we know so far:

  • All Game Client to/from Login Server opcodes (GC2LS/LS2GC).
  • All Game Client to/from World Server opcodes (GC2WS/WS2GC).
  • How to connect to another server than retail.
  • What encryption/auth schemes we’re dealing with (RC4, SRP 6a).
  • The game/servers use some sort of serialization framework for packets.
  • The back end runs a UT3-like server model.
  • There is a separate (HTTP-based) Music Server that the Game Client connects to.
  • There is a separate Image Server that the Image Client (in the Game Client) connects to (IC2IS/IS2IC).

Here are the opcodes we’ve mapped (should be all existing opcodes):

// Login Server OpCodes, C->S
0x3E8: ASK_LOGIN
0x3E9: LOGIN_PROOF
0x3EA: ASK_CHARACTER_INFO
0x3EB: ASK_WORLD_LIST
0x3EC: ASK_CHARACTER_NAME_CHECK
0x3ED: ASK_CHARACTER_NAME_CHANGE
0x3EE: ASK_CHARACTER_CREATE
0x3EF: ASK_CHARACTER_DELETE
0x3F0: ASK_WORLD_ENTER
0x3F1: ASK_CONFIGFILE_LOAD
0x3F2: ASK_CONFIGFILE_SAVE

// Login Server OpCodes, S->C
0x7D0: ERROR
0x7D1: KICK
0x7D2: LOGIN_PUZZLE
0x7D3: LOGIN_SALT
0x7D4: ANS_LOGIN_SUCCESS
0x7D5: ANS_LOGIN_FAILED
0x7D6: CHARACTER_LIST
0x7D7: ANS_CHARACTER_INFO
0x7D8: WORLD_LIST
0x7D9: ANS_CHARACTER_NAME_CHECK
0x7DA: ANS_CHARACTER_NAME_CHANGE
0x7DB: ANS_CHARACTER_CREATE
0x7DC: ANS_CHARACTER_DELETE
0x7DD: ANS_WORLD_ENTER
0x7DE: WORLD_STATUS
0x7DF: ANS_CONFIGFILE_LOAD
0x7E0: ANS_CONFIGFILE_SAVE
0x7E1: POINTS_CHANGE

// World Server OpCodes, C->S
0xBB8: ASK_WORLD_ENTER
0xBB9: ASK_WORLD_QUEUE_CANCEL
0xBBA: ASK_INSTANCE_LIST
0xBBB: ASK_DISTRICT_RESERVE
0xBBC: ASK_DISTRICT_RESERVE_CANCEL
0xBBD: ASK_DISTRICT_ENTER
0xBBE: ASK_DISTRICT_EXIT
0xBBF: ASK_DISTRICT_QUEUE_CANCEL
0xBC0: LOGOUT
0xBC1: ASK_NAME_QUERY
0xBC2: ASK_CHAT_WHISPER
0xBC3: CHAT_GROUP
0xBC4: CHAT_CLAN
0xBC5: CHAT_OFFICER
0xBC6: CHAT_DISTRICT
0xBC7: CHAT_STATE
0xBC8: ASK_GROUP_CONFIG
0xBC9: ASK_GROUP_JOIN
0xBCA: ASK_GROUP_INVITE
0xBCB: ANS_GROUP_INVITE
0xBCC: ASK_GROUP_LEAVE
0xBCD: ASK_GROUP_REMOVE
0xBCE: ASK_GROUP_LEADER
0xBCF: ASK_GROUP_STATE
0xBD0: ASK_GROUP_LIST
0xBD1: ASK_GROUP_INFO
0xBD2: ASK_CLAN_CREATE
0xBD3: ASK_CLAN_DELETE
0xBD4: ASK_CLAN_INVITE
0xBD5: ANS_CLAN_INVITE
0xBD6: ASK_CLAN_LEAVE
0xBD7: ASK_CLAN_REMOVE
0xBD8: ASK_CLAN_LEADER
0xBD9: ASK_CLAN_BIO
0xBDA: ASK_CLAN_BIO_EDIT
0xBDB: ASK_CLAN_SYMBOL
0xBDC: ASK_CLAN_SYMBOL_EDIT
0xBDD: ASK_CLAN_THEME
0xBDE: ASK_CLAN_THEME_EDIT
0xBDF: ASK_CLAN_MEMBER_STATE
0xBE0: ASK_CLAN_MEMBER_STATS
0xBE1: ASK_CLAN_MEMBER_PROFILE
0xBE2: ASK_CLAN_MEMBER_BIO_EDIT
0xBE3: ASK_CLAN_MEMBER_NOTE_EDIT
0xBE4: ASK_CLAN_RANK_CREATE
0xBE5: ASK_CLAN_RANK_EDIT
0xBE6: ASK_CLAN_RANK_DELETE
0xBE7: ASK_CLAN_RANK_ASSIGN
0xBE8: ASK_CLAN_INFORMATION
0xBE9: ASK_CLAN_INFORMATION_EDIT
0xBEA: ASK_CLAN_MOTD_EDIT
0xBEB: ASK_FRIENDLIST_ADD
0xBEC: ASK_FRIENDLIST_REMOVE
0xBED: ASK_FRIENDLIST_STATE
0xBEE: ASK_INGORELIST_ADD
0xBEF: ASK_IGNORELIST_REMOVE
0xBF0: ASK_CHARACTERFINDER_INFO
0xBF1: ASK_MARKETPLACE_AUCTION_CANCEL
0xBF2: ASK_MARKETPLACE_AUCTION_ITEM
0xBF3: ASK_MARKETPLACE_AUCTION_LIST
0xBF4: ASK_MARKETPLACE_MYAUCTION_LIST
0xBF5: ASK_MARKETPLACE_MYBID_LIST
0xBF6: ASK_MARKETPLACE_SELLER_LIST
0xBF7: ASK_MAIL_LIST
0xBF8: ASK_MAIL_READ
0xBF9: ASK_MAIL_ITEM
0xBFA: ASK_MAIL_DELETE
0xBFB: ASK_CONFIGFILE_LOAD
0xBFC: ASK_CONFIGFILE_SAVE
0xBFD: ASK_CONFIG_SAVE
0xBFE: ASK_LEAGUE_LIST
0xBFF: ASK_LEAGUE_MYLEAGUE_LIST
0xC00: ASK_LEAGUE_VALUE
0xC01: ASK_PLAYED
0xC02: ASK_POPULATION
0xC03: ASK_WHO
0xC04: ASK_GM_COMMAND
0xC05: ASK_SUBSCRIPTION_REMAINING_TIME
0xC06: LFG
0xC07: ASK_MARKETPLACE_THUMBNAIL

// World Server OpCodes, S->C
0xFA0: ERROR
0xFA1: KICK
0xFA2: WORLD_SHUTDOWN_NOTIFY
0xFA3: ANS_WORLD_ENTER
0xFA4: ANS_WORLD_QUEUE_CANCEL
0xFA5: WORLD_QUEUE_STATUS
0xFA6: DISTRICT_LIST
0xFA7: ANS_INSTANCE_LIST
0xFA8: ANS_DISTRICT_RESERVE
0xFA9: ANS_DISTRICT_RESERVE_CANCEL
0xFAA: ANS_DISTRICT_ENTER
0xFAB: ANS_DISTRICT_EXIT
0xFAC: ANS_DISTRICT_QUEUE_CANCEL
0xFAD: DISTRICT_QUEUE_STATUS
0xFAE: ANS_NAME_QUERY
0xFAF: ANS_CHAT_WHISPER
0xFB0: CHAT_WHISPER
0xFB1: CHAT_GROUP
0xFB2: CHAT_CLAN
0xFB3: CHAT_OFFICER
0xFB4: CHAT_DISTRICT
0xFB5: CHAT_SYSTEM
0xFB6: CHAT_AFK
0xFB7: CHAT_DND
0xFB8: CHAT_CONVERSATION_END
0xFB9: ANS_GROUP_CONFIG
0xFBA: ANS_GROUP_JOIN
0xFBB: ASK_GROUP_INVITE
0xFBC: ANS_GROUP_INVITE
0xFBD: ANS_GROUP_LEAVE
0xFBE: ANS_GROUP_REMOVE
0xFBD: ANS_GROUP_LEADER
0xFC0: ANS_GROUP_STATE
0xFC1: ANS_GROUP_LIST
0xFC2: ANS_GROUP_INFO
0xFC3: GROUP_INFO
0xFC4: GROUP_CONFIG
0xFC5: GROUP_JOIN
0xFC6: GROUP_LEAVE
0xFC7: GROUP_REMOVE
0xFC8: GROUP_LEADER
0xFC9: GROUP_STATUS
0xFCA: GROUP_INVITE_CANCELLED
0xFCB: ANS_CLAN_CREATED
0xFCC: ANS_CLAN_DELETE
0xFCD: ASK_CLAN_INVITE
0xFCE: ANS_CLAN_INVITE1
0xFCF: ANS_CLAN_INVITE2
0xFD0: ANS_CLAN_REMOVE
0xFD1: ANS_CLAN_LEADER
0xFD2: ANS_CLAN_BIO
0xFD3: ANS_CLAN_BIO_EDIT
0xFD4: ANS_CLAN_SYMBOL
0xFD5: ANS_CLAN_SYMBOL_EDIT
0xFD6: ANS_CLAN_THEME
0xFD7: ANS_CLAN_THEME_EDIT
0xFD8: ANS_CLAN_MEMBER_STATE
0xFD9: ANS_CLAN_MEMBER_STATS
0xFDA: ANS_CLAN_MEMBER_PROFILE
0xFDB: ANS_CLAN_MEMBER_BIO_EDIT
0xFDC: ANS_CLAN_MEMBER_NOTE_EDIT
0xFDD: ANS_CLAN_RANK_EDIT
0xFDE: ANS_CLAN_RANK_CREATE
0xFDF: ANS_CLAN_RANK_DELETE
0xFE0: ANS_CLAN_RANK_ASSIGN
0xFE1: ANS_CLAN_INFORMATION
0xFE2: ANS_CLAN_INFORMATION_EDIT
0xFE3: ANS_CLAN_MOTD_EDIT
0xFE4: CLAN_RANK_INFO
0xFE5: CLAN_INFO
0xFE6: CLAN_MOTD
0xFE7: CLAN_RANK_CREATE
0xFE8: CLAN_RANK_DELETE
0xFE9: CLAN_RANK_ASSIGN
0xFEA: CLAN_RANK_EDIT
0xFEB: CLAN_JOIN
0xFEC: CLAN_LEAVE
0xFED: CLAN_REMOVE
0xFEE: CLAN_LEADER
0xFEF: CLAN_DELETE
0xFF0: CLAN_STATUS
0xFF1: ANS_FRIENDLIST_ADD
0xFF2: ANS_FRIENDLIST_REMOVE
0xFF3: ANS_FRIENDLIST_STATE
0xFF4: FRIENDLIST_INFO
0xFF5: FRIENDLIST_STATUS
0xFF6: ANS_IGNORELIST_ADD
0xFF7: ANS_IGNORELIST_REMOVE
0xFF8: IGNORELIST_INFO
0xFF9: ANS_CHARACTERFINDER_INFO
0xFFA: ANS_MARKETPLACE_AUCTION_CANCEL
0xFFB: ANS_MARKETPLACE_AUCTION_ITEM
0xFFC: ANS_MARKETPLACE_AUCTION_LIST
0xFFD: ANS_MARKETPLACE_MYAUCTION_LIST
0xFFE: ANS_MARKETPLACE_MYBID_LIST
0xFFF: ANS_MARKETPLACE_SELLER_LIST
0x1000: MARKETPLACE_AUCTION_INFO
0x1001: ANS_MAIL_LIST
0x1002: ANS_MAIL_READ
0x1003: ANS_MAIL_ITEM
0x1004: ANS_MAIL_DELETE
0x1005: MAIL_INFO
0x1006: ANS_CONFIGFILE_LOAD
0x1007: ANS_CONFIGFILE_SAVE
0x1008: ANS_CONFIG_SAVE
0x1009: ANS_LEAGUE_LIST
0x100A: ANS_LEAGUE_MYLEAGUE_LIST
0x100B: ANS_LEAGUE_VALUE
0x100C: ANS_PLAYED
0x100D: ANS_POPULATION
0x100E: ANS_WHO
0x100F: ANS_GM_COMMAND
0x1010: ANS_SUBSCRIPTION_REMAINING_TIME
0x1011: VOICE_CHANNEL_INFO
0x1012: SUBSCRIPTION_INFO
0x1013: MARKETPLACE_THUMBNAIL

The packet headers appear to be:

int32 length;
int32 opcode;

This listing doesn’t include District Server opcodes as packets on that server appear to be of a totally different protocol (and probably uses UDP), which leads us to believe that the District Server is powered by Unreal Engine’s server code.

We’re working against APB.exe version 1.2.0.530873.

If you want to join us, hop into #apb-dev on irc.quakenet.org.

WoW 4.0.0: Build 12065 Opcodes

12065 was pushed to the test realms today, and I’ve been taking a peek into it (while I should be sleeping, damn it); seemingly, opcode randomization did not happen this time. Yes, this means that 12025 and 12065 share the same opcode IDs.

Sample client code:

  ClientServices__RegisterHandler(0x5D62u, sub_833F30, 0);
  v0 = sub_5FB4A0("EnableVoiceChat");

It is completely identical for the two builds.

Thanks to Marla for passing along the binary!