LPack Library - Windower/Lua GitHub Wiki

Source: https://github.com/LuaDist/lpack/

Adds functions to pack and unpack binary strings based on a provided format string. Note that this has been adjusted from the original source for this module in a some ways to make it more appropriate for our use case. This will be mentioned where applicable.

Usage

require('pack')

Formatting

The format string uses one character to represent one type, optionally followed by a number to denote the amount. The following table lists all recognized characters. Note that b was changed to C from the original source. Furthermore, b, q, B and S were newly added.

Char Description
z zero-terminated string
p string preceded by length byte
P string preceded by length word
a string preceded by length size_t
A string
S Fixed-length string followed by length)
f float
d double
B Boolean
n Lua number
c char
C unsigned char
h short
H unsigned short
i int
I unsigned int
l long
L unsigned long
b Bits followed by length
q Bits representing a boolean followed by length
< little endian
> big endian
= native endian

Functions

string.pack(format, ...)

res = string.pack(format, ...)
  • res string

  • format string

  • ... any

Packs the provided arguments ... into a binary string according to format and returns the result res.

string.unpack(str, format, position)

... = string.unpack(str, format, position)
  • ... any

  • str string

  • format string

  • position integer [optional]

Unpacks the provided binary string str according to format and returns all the unpacked results .... Note that this does not return the next unread position in the string as the first value, unlike the original source.

Examples

Packet data is passed to addons as a binary string containing certain data. To read it, we have to extract certain pieces of it and convert it to a correct type. string.unpack can be used just for that.

The incoming 0x017 packet has incoming chat information. The following lists its bytes and what they mean:

Bytes Type Description
1- 4 int Header
5- 5 unsigned char Chat mode (say, tell, shout, etc.)
6- 6 bool true if GM, false otherwise
7- 8 unsigned short Zone ID (needs to be there for yells)
9-24 char[16] Sender Name
25- * char* Arbitrary length message

To get this data out of the string, we need an appropriate formatting string. We can see in the table under Formatting that int uses the character i, unsigned char uses C, bool uses B, unsigned short uses H, char[16] is a 16 byte long string, i.e. a fixed-size string, so we can use S16 and finally char* is an arbitrary length string, which means it's zero-terminated and we can use z. Since they all appear right next to each other, we can just concatenate the characters and get the formatting string we need: iCBHS16z

Now we can write the following addon:

require('pack')

windower.register_event('incoming chunk', function(id, data)
    if id == 0x017 then
        local header, mode, gm, zone, sender, message = data:unpack('iCBHS16z')
    end
end)

Now all the variables hold the respective values that were encoded in the binary string data. We can use this to adjust the result as well. For example, we can change the mode from say (ID 0) to shout (ID 1), so that say text appears in the shout color and shows up in the shout tab:

require('pack')

windower.register_event('incoming chunk', function(id, data)
    if id == 0x017 then
        local header, mode, gm, zone, sender, message = data:unpack('iCBHS16z')
        if mode == 0 then
            local new_mode = 1
            local adjusted_data = 'iCBHS16z':pack(header, new_mode, gm, zone, sender, message)
            return adjusted_data
        end
    end
end)

We still have many unused variables in this case. string.unpack allows us to only extract data from a certain position, which we can use to only extract the mode, which is an unsigned char (i.e. C) that starts at position 5. That way the previous can be shortened a bit by just extracting the mode and splicing it into the middle of the data binary string:

require('pack')

windower.register_event('incoming chunk', function(id, data)
    if id == 0x017 then
        local mode = data:unpack('C', 5)
        if mode == 0 then
            local new_mode = 1
            local adjusted_data = data:sub(1, 4) .. 'C':pack(new_mode) .. data:sub(6)
            return adjusted_data
        end
    end
end)

Note that this is just demonstrative for how string.pack and string.unpack can be used. For general packet reading and manipulation you may want to use the Packets library.