require "util" local p = Proto("pictochat", "PictoChat") function p.init() local dt = DissectorTable.get("dslmp") dt:add(GAMEID.PICTOCHAT, p) end local MSG_TYPE = { INIT = 0, MSG_START = 10, MSG_END = 24, USER_JOIN = 48, STATUS = 52, MSG_BODY = 86, } local MSG_TYPE_MAP = { [MSG_TYPE.INIT] = "Init", -- sent once (init?) [MSG_TYPE.MSG_START] = "Message start", -- sent before a message packet [MSG_TYPE.MSG_END] = "Message end", -- sent after multi-frame message body packets [MSG_TYPE.USER_JOIN] = "User join", -- these packets contain a username [MSG_TYPE.STATUS] = "Status", -- constantly spammed (status?) [MSG_TYPE.MSG_BODY] = "Message body", -- contains tile data for drawing } p.fields.unknown = ProtoField.uint16("pictochat.unknown", "Unknown") local function dissect_payload(buffer, pinfo, tree) tree:add_le(p.fields.payload_len, buffer(0x00, 2)) local payload_length = buffer(0x00, 2):le_uint() buffer = buffer(0x02) return buffer, tree:add(buffer(0x00, payload_length), "Payload: " .. payload_length .. " bytes") end local dissect_msg_type = { } p.fields.msg_start_len = ProtoField.uint8("pictochat.msg_start.len", "Total length") local state = {} dissect_msg_type[MSG_TYPE.MSG_START] = function (buffer, pinfo, tree) local payload buffer, payload = dissect_payload(buffer, pinfo, tree) payload:add_le(p.fields.msg_start_len, buffer(0x04, 2)) local start_len = buffer(0x04, 2):le_uint() state.data_remaining = start_len state.buf = ByteArray.new() state.want_next = 0 end p.fields.user_addr = ProtoField.bytes("pictochat.user.addr", "Address", base.COLON) p.fields.user_name = ProtoField.string("pictochat.user.name", "Nickname") p.fields.user_msg = ProtoField.string("pictochat.user.msg", "Message") p.fields.user_color = ProtoField.uint16("pictochat.user.color", "Color", base.DEC, PROFILE_COLOR_MAP) p.fields.user_bday_month = ProtoField.uint8("pictochat.user.bday_month", "Month") p.fields.user_bday_day = ProtoField.uint8("pictochat.user.bday_day", "Day") dissect_msg_type[MSG_TYPE.USER_JOIN] = function (buffer, pinfo, tree) local payload buffer, payload = dissect_payload(buffer, pinfo, tree) add_addr_le(payload, p.fields.user_addr, buffer(0x0a, 6)) local user_name = buffer(0x10, 20):le_ustring() payload:add(p.fields.user_name, buffer(0x10, 20), user_name) register_addr_le(buffer(0x0a, 6):raw(), user_name) pinfo.cols.info = string.format("%s (%s)", pinfo.cols.info, user_name) payload:add(p.fields.user_msg, buffer(0x24, 52), buffer(0x24, 52):le_ustring()) payload:add_le(p.fields.user_color, buffer(0x58, 2)) local bday = payload:add(buffer(0x5a, 2), string.format("Birthday: %02d/%02d", buffer(0x5a, 1):uint(), buffer(0x5b, 1):uint())) bday:add(p.fields.user_bday_month, buffer(0x5a, 1)) bday:add(p.fields.user_bday_day, buffer(0x5b, 1)) end p.fields.payload_len = ProtoField.uint16("pictochat.payload_len", "Payload length") p.fields.data_len = ProtoField.uint8("pictochat.data_len", "Data length") p.fields.data_end = ProtoField.bool("pictochat.data_end", "Data end") p.fields.data_offset = ProtoField.uint16("pictochat.data_offset", "Data offset") p.fields.data_sequence = ProtoField.uint16("pictochat.data_sequence", "Data sequence") p.fields.data = ProtoField.bytes("pictochat.data", "Data") p.fields.sequence = ProtoField.uint16("pictochat.sequence", "Packet sequence") dissect_msg_type[MSG_TYPE.MSG_BODY] = function (buffer, pinfo, tree) local payload buffer, payload = dissect_payload(buffer, pinfo, tree) payload:add_le(p.fields.unknown, buffer(0x00, 2)) payload:add_le(p.fields.data_len, buffer(0x02, 1)) local data_length = buffer(0x02, 1):le_uint() payload:add_le(p.fields.data_end, buffer(0x03, 1)) -- This appears to be some kind of offset for indicating where to store the -- current frame's data in a larger buffer. Messages sent in multiple parts -- increment this value by 160 for each new original (p.fields.original == -- True) message. payload:add_le(p.fields.data_offset, buffer(0x04, 2)) local data_offset = buffer(0x04, 2):le_uint() payload:add_le(p.fields.unknown, buffer(0x06, 2)) -- usually 0 buffer = buffer(0x08) -- This appears to be the actual message data (the drawing) sent as an -- array of 8x8 tiles. payload:add(p.fields.data, buffer(0, data_length)) if state.want_next == data_offset then state.want_next = data_offset + data_length state.data_remaining = state.data_remaining - data_length state.buf:append(buffer(0, data_length):bytes()) pinfo.cols.info = "Message body" ByteArray.tvb(state.buf, "Complete message") end buffer = buffer(data_length) payload:add_le(p.fields.data_sequence, buffer(0x00, 2)) payload:add_le(p.fields.unknown, buffer(0x02, 2)) -- copy end p.fields.status_addr = ProtoField.bytes("pictochat.status.addr", "Address", base.COLON) dissect_msg_type[MSG_TYPE.STATUS] = function (buffer, pinfo, tree) local payload buffer, payload = dissect_payload(buffer, pinfo, tree) payload:add_le(p.fields.unknown, buffer(0x00, 2)) payload:add_le(p.fields.unknown, buffer(0x02, 2)) buffer = buffer(0x04) for _ = 0, 15 do -- payload:add(p.fields.status_addr, buffer(0, 6)) add_addr_le(payload, p.fields.status_addr, buffer(0, 6)) buffer = buffer(6) end payload:add_le(p.fields.unknown, buffer(0x00, 2)) payload:add_le(p.fields.unknown, buffer(0x02, 2)) end p.fields.magic = ProtoField.bytes("pictochat.magic", "Magic") p.fields.new = ProtoField.bool("pictochat.new", "New") p.fields.msg_type = ProtoField.uint8("pictochat.msg_type", "Message type", base.DEC, MSG_TYPE_MAP) function p.dissector(buffer, pinfo, tree) -- check magic if buffer(0x00, 2):uint() ~= GAMEID.PICTOCHAT then return 0 end local subtree = tree:add(p, buffer(), string.format("%s: %d bytes", p.description, buffer():len())) local buffer_len = buffer():len() subtree:add_le(p.fields.magic, buffer(0x00, 2)) subtree:add_le(p.fields.new, buffer(0x02, 2)) subtree:add_le(p.fields.msg_type, buffer(0x04, 1)) local msg_type = buffer(0x04, 1):le_uint() local msg_type_str = MSG_TYPE_MAP[msg_type] subtree:add_le(p.fields.unknown, buffer(0x05, 1)) -- pretty wireshark shit pinfo.cols.protocol = p.name if msg_type_str ~= nil then pinfo.cols.info = msg_type_str end if msg_type == 0 then -- no more content return buffer_len end subtree:add_le(p.fields.unknown, buffer(0x06, 2)) buffer = buffer(0x08) local subdissector = dissect_msg_type[msg_type] if subdissector ~= nil then subdissector(buffer, pinfo, subtree) end end