Specification

A .wowm file starts with 0..* commands, and ends with 0..* statements. Commands are prefix with # and statements start with an object keyword.

Commands

The file must start with all commands. A command appearing after a statement is invalid.

Commands take the form

#<command> <command_parameters>;

For example:

#tag_all versions "1.12";

Built-in Types

The following types are not looked up but are expected to be handled by the compiler and code generator:

  • The basic integer types u8, u16, u32, and u64 are sent as little endian over the network.
  • The floating point type f32 is sent as little endian over the network.

The String type is the same as a sized u8 array (u8[len]) containing valid UTF-8 values plus a u8 for the length of the string, although it should be presented in the native string type.

The CString type is an array of valid UTF-8 u8s terminated by a null (0) byte.

The SizedCString is the same as a u32 followed by a CString, but they are kept in the same type to semantically convey that the u32 field has no purpose other than to parse the CString.

TypePurposeC Name
u8Unsigned 8 bit integer. Min value 0, max value 256.unsigned char
u16Unsigned 16 bit integer. Min value 0, max value 65536.unsigned short
u32Unsigned 32 bit integer. Min value 0, max value 4294967296.unsigned int
u64Unsigned 64 bit integer. Min value 0, max value 18446744073709551616.unsigned long long
i32Unsigned 32 bit integer. Min value -2147483648, max value 4294967296.signed int
BoolUnsigned 1 bit integer. 0 means false and all other values mean true.unsigned char
Bool32Unsigned 4 bit integer. 0 means false and all other values mean true.unsigned int
PackedGuidGuid sent in the "packed" format. See PackedGuid.-
GuidUnsigned 8 bit integer. Can be replaced with a u64.unsigned long long
NamedGuidA Guid (u64) followed by a CString if the value of the Guid is not 0.-
DateTimeu32 in a special format. See DateTime.unsigned int
f32Floating point value of 4 bytes.f32
CStringUTF-8 string type that is terminated by a zero byte value.char*
SizedCStringA u32 field that determines the size of the string followed by a UTF-8 string type that is terminated by a zero byte value.unsigned int + char*
StringUTF-8 string type of exactly length len.unsigned char + char*
UpdateMaskUpdate values sent in a relatively complex format. See UpdateMask.-
MonsterMoveSplinesArray of positions. See MonsterMoveSpline.-
AuraMaskAura values sent using a mask. See Masks.-
AchievementDoneArrayArray that terminates on a sentinel value. See AchievementDoneArray-
AchievementInProgressArrayArray that terminates on a sentinel value. See AchievementInProgressArray-
EnchantMaskEnchant values sent using a mask. See EnchantMasks.-
InspectTalentGearMaskInspectTalentGear values sent using a mask. See Masks.-
GoldAlias for u32.unsigned int
Populationf32 with the special behavior that a value of 200 always means GREEN_RECOMMENDED, 400 always means RED_FULL, and 600 always means BLUE_RECOMMENDED.float
LevelAlias for u8.unsigned char
Level16Alias for u16.unsigned short
Level32Alias for u32.unsigned int
VariableItemRandomPropertyA u32 followed by another u32 if the first value is not equal to 0.-
AddonArrayArray of Addons for TBC and Wrath that rely on externally knowing the amount of array members. See AddonArray.-
IpAddressAlias for big endian u32.unsigned int
SecondsAlias for u32.unsigned int
MillisecondsAlias for u32.unsigned int
SpellAlias for u32 that represents a spell.unsigned int
Spell16Alias for u16 that represents a spell.unsigned short
ItemAlias for u32 that represents an item entry.unsigned int
CacheMaskClient info sent using a mask. See CacheMask.-

Arrays

Arrays are semi-built-in types of the form <type>[<length>] where <type> is any built-in or user defined type, and <length> is either a constant integer value, a previous integer field in the same object, or the character - for "endless" arrays.

Endless arrays do not have a field that specifies how many items the array contains. This information is instead deduced from the total size of the message minus the sizes of any previous fields.

Statements

Statements start with one of the following keywords:

  • enum, a definer, for descriptions of values where only one value is valid.
  • flag, a definer, for descriptions of values where several values are valid at the same time.
  • struct, a container, for collections of fields that do not fit into any of the below.
  • clogin, a container, for login messages sent from the client.
  • slogin, a container, for login messages sent from the server.
  • msg, a container, for world messages that can be sent from both client and server.
  • smsg, a container, for world messages sent from the server.
  • cmsg, a container, for world messages sent from the client.
  • test, a description of a full valid message and the expected values.

A definer creates a new type that gives names to basic integer values, like an enum would do in most programming languages. A container is a collection of types that describes the order in which they are sent, as well as other information.

All statements can be followed by a tags block of the form

{
    <tags>
}

Where <tags> is of the form

<tag_name> = "<tag_value>";

Where <tag_name> is a valid identifier, and <tag_value> is a string value.

A list of tags with meaning for the compiler can be found at Tags.

Definer

Definers take the form of

enum <name> : <basic_type> {
    <enumerators>
}

Where <name> is a unique identifier, <basic_type> is an integer type u8, u16, u32, and u64.

<enumerators> is one or more enumerators of the form

<name> = <value>;

where <name> is a unique identifier inside the definer, and <value> is a valid value. Enums can not have multiple names with the same value, while flags can. Flags can not be signed types (i*), while enums can.

The allowed number formats in definers and how they are sent over the network are:

enum EnumAllowedInteger : u32 {
    INTEGER = 22; /* 22, [22, 0, 0, 0]  */
    HEXADECIMAL = 0xFF; /* 255, [255, 0, 0, 0] */
    BINARY = 0b0101; /* 5, [5, 0, 0, 0] */
    STRING = "\0AB"; /* 0x4142, [66, 65, 0, 0] */
}

The string syntax has the special case of \0 which is replaced with a single zero byte.

Container

Containers take the form of

<keyword> <name> [= <opcode>] {
    <declaration | if_statement | optional_statement>*
}

Where <keyword> is one of

  • struct.
  • clogin, for a login message sent from the client.
  • slogin, for a login message sent from the server.
  • msg, for a world message sent by both the client and server.
  • smsg, for a world message sent from the server.
  • cmsg, for a world message sent from the client.

<name> is a valid identifier.

[= <opcode>] is an allowed value in the same format as for definer values that defines the unique opcode value for the container. The <opcode> is required for every container except for structs, which have no opcodes.

For msg, smsg, and cmsg the size field is implicitly added as part of the message header. clogin and slogin messages that require a specific size field must set the field equal to self.size.

Declaration

<declaration> is of the form

[<upcast>]<type> <identifier> [= <constant_value>];

Where <type> is either a built-in or user defined type.

<identifier> is a legal identifier. Two declarations or optional statements in the same object must not have identical identifiers, even across if statement blocks.

The optional <constant_value> defines which value this field should always be sent as, used for padding values. Fields received with a different value will not lead to failed parsing.

The optional <upcast> is used for an enum which should be sent over the network as a different type than it is defined with. This is in order to prevent needing multiple enums for the same concept. Upcasts have the form ( <integer_type> ) where integer_type is an integer type of larger size or different endianness from the type in the original enum.

If Statement

<if_statement> is of the form

if (<variable_name> <operator> <definer_enumerator>
    [|| <variable_name> <operator> <definer_enumerator>]*) {
    <declaration | if_statement | optional_statement>*
} [ else if (<variable_name> <operator> <definer_enumerator> 
    [|| <variable_name> <operator> <definer_enumerator>]* {
    <declaration | if_statement | optional_statement>*
}]* [ else {
    <declaration | if_statement | optional_statement>*
}]?

Where:

  • <variable_name> is the name of a variable from a declaration that has previously appeared. The variable name must be the same in all statements.
  • <operator> is either ==, &, or !=. Restrictions apply to !=.
  • <definer_enumerator> is a valid enumerator in the type of <variable_name>.

If statements that use != can not have any else if or || options, but they can have the else option.

Optional Statement

<optional_statement> is of the form

optional <identifier> {
    <declaration | if_statement | optional_statement>*
}

Where <identifier> is a legal identifier. Two declarations or optional statements in the same object must not have identical identifiers, even across if statement blocks.

Optional statements can only occur as the last element of a message.

Test

Tests take the form of

test <name> {
    <fields>
} [
    <bytes>
]

Where <name> is a valid name of an existing container.

<fields> is zero or more field definitions of the form

<name> = <value> | "[" <array_fields>[","] "]" | "{" <subobject_fields> "}" ;

that describe which value to expect for a specific field.

<array_fields> and <subobject_fields> consist of 1 or more <fields>.

<bytes> are the raw byte values sent over the network, including the size and opcode fields in unencrypted format. The allowed formats are the same as those in definer values.