aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoek Le Blansch <loek@pipeframe.xyz>2024-08-30 17:25:18 +0200
committerLoek Le Blansch <loek@pipeframe.xyz>2024-08-30 17:25:18 +0200
commit882d9bc46d6da8a4134a66010f21b85d733353d4 (patch)
tree43686d3c99672130e9926de45ea0ec59e5df9fe8
parent6dc1b4db2286f3e6fc2b5edc327b292d4f7f0245 (diff)
dissect some of the reassembled pictochat content
-rw-r--r--wireshark/main.lua2
-rw-r--r--wireshark/pcmeta.lua186
-rw-r--r--wireshark/pictochat.lua205
-rw-r--r--wireshark/util.lua6
4 files changed, 236 insertions, 163 deletions
diff --git a/wireshark/main.lua b/wireshark/main.lua
index 91323da..ce48940 100644
--- a/wireshark/main.lua
+++ b/wireshark/main.lua
@@ -1,5 +1,7 @@
require "pictochat"
+require "pcmeta"
+
require "ieee"
require "txhdr"
diff --git a/wireshark/pcmeta.lua b/wireshark/pcmeta.lua
new file mode 100644
index 0000000..0541823
--- /dev/null
+++ b/wireshark/pcmeta.lua
@@ -0,0 +1,186 @@
+require "util"
+
+local p = Proto("pcmeta", "PictoChat Meta")
+
+local pictochat_dissector = Dissector.get("pictochat")
+
+function p.init()
+ local dt = DissectorTable.get("dslmp")
+ dt:add(GAMEID.PICTOCHAT, p)
+end
+
+local MSG_TYPE = {
+ MSG_START = 1,
+ MSG_FRAG = 2,
+
+ -- There doesn't seem to be an actual difference between type 4 and 5, even
+ -- the sequence number is shared between these message types
+ STATUS_A = 4,
+ STATUS_B = 5,
+}
+
+local MSG_TYPE_MAP = {
+ [MSG_TYPE.MSG_START] = "msg start",
+ [MSG_TYPE.MSG_FRAG] = "msg fragment",
+ [MSG_TYPE.STATUS_A] = "status", -- constantly spammed, contains room users' addresses
+ [MSG_TYPE.STATUS_B] = "status",
+}
+
+p.fields.unknown = ProtoField.uint16("pcmeta.unknown", "Unknown")
+
+local dissect_msg_type = { }
+
+p.fields.msg_start_len = ProtoField.uint8("pcmeta.msg_start.len", "Total length")
+p.fields.msg_start_id = ProtoField.uint32("pcmeta.msg_start.id", "ID")
+p.fields.msg_start_seq = ProtoField.uint16("pcmeta.msg_start.seq", "Sequence")
+p.fields.msg_start_new = ProtoField.bool("pcmeta.msg_start.new", "New")
+dissect_msg_type[MSG_TYPE.MSG_START] = function (buffer, pinfo, tree)
+ local subtree = tree:add(p, buffer(), string.format("PictoChat Meta Message start: %d bytes", buffer:len()))
+
+ subtree:add_le(p.fields.unknown, buffer(0x00, 2))
+ subtree:add_le(p.fields.unknown, buffer(0x02, 2)) -- usu. ffffh
+ subtree:add_le(p.fields.msg_start_len, buffer(0x04, 2))
+ subtree:add_le(p.fields.unknown, buffer(0x06, 2)) -- usu. 0000h
+ subtree:add_le(p.fields.unknown, buffer(0x08, 2))
+ subtree:add_le(p.fields.unknown, buffer(0x0a, 2)) -- usu. 0000h
+ subtree:add_le(p.fields.msg_start_id, buffer(0x0c, 4))
+ local id = buffer(0x0c, 4):le_uint()
+ subtree:add_le(p.fields.msg_start_seq, buffer(0x10, 2))
+ subtree:add_le(p.fields.new, buffer(0x12, 2))
+ local new = buffer(0x12, 2):le_uint() ~= 0
+
+ if not pinfo.visited then
+ if new then
+ pc_global.mid = id
+ end
+
+ pc_global.pid_mid_map[pinfo.number] = pc_global.mid
+ pc_global.msg[pc_global.mid] = ByteArray.new()
+ end
+end
+
+p.fields.body_len = ProtoField.uint8("pcmeta.body.len", "Length")
+p.fields.body_end = ProtoField.bool("pcmeta.body.end", "End")
+p.fields.body_offset = ProtoField.uint16("pcmeta.body.offset", "Offset")
+p.fields.body = ProtoField.bytes("pcmeta.body", "Data")
+p.fields.body_seq = ProtoField.uint16("pcmeta.body.seq", "Sequence")
+p.fields.body_new = ProtoField.bool("pcmeta.body.new", "New")
+dissect_msg_type[MSG_TYPE.MSG_FRAG] = function (buffer, pinfo, tree)
+ local subtree = tree:add(p, buffer(), string.format("PictoChat Meta Message Fragment: %d bytes", buffer:len()))
+
+ subtree:add_le(p.fields.unknown, buffer(0x00, 2))
+ subtree:add_le(p.fields.body_len, buffer(0x02, 1))
+ local body_len = buffer(0x02, 1):le_uint()
+
+ subtree:add_le(p.fields.body_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.
+ subtree:add_le(p.fields.body_offset, buffer(0x04, 2))
+ local body_offset = buffer(0x04, 2):le_uint()
+ subtree: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.
+ local body = buffer(0, body_len)
+ subtree:add(p.fields.body, body)
+
+ buffer = buffer(body_len)
+
+ subtree:add_le(p.fields.body_seq, buffer(0x00, 2))
+ subtree:add_le(p.fields.body_new, buffer(0x02, 2))
+ local new = buffer(0x02, 2):le_uint() ~= 0
+
+ if not pinfo.visited then
+ pc_global.pid_mid_map[pinfo.number] = pc_global.mid
+
+ -- reassembly
+ if pc_global.msg[pc_global.mid]:len() == body_offset and new == true then
+ pc_global.msg[pc_global.mid]:append(body:bytes())
+ end
+ end
+end
+
+p.fields.status_id = ProtoField.uint32("pcmeta.status.id", "ID")
+p.fields.status_addr = ProtoField.bytes("pcmeta.status.addr", "Address", base.COLON)
+p.fields.status_seq = ProtoField.uint16("pcmeta.status.seq", "Sequence")
+p.fields.status_new = ProtoField.bool("pcmeta.status.new", "New")
+dissect_msg_type[MSG_TYPE.STATUS_A] = function (buffer, pinfo, tree)
+ local subtree = tree:add(p, buffer(), string.format("PictoChat Meta Status: %d bytes", buffer:len()))
+
+ subtree:add_le(p.fields.status_id, buffer(0x00, 4))
+ local id = buffer(0x00, 4):le_uint()
+
+ buffer = buffer(0x04)
+
+ for _ = 0, 15 do
+ add_addr_le(subtree, p.fields.status_addr, buffer(0, 6))
+ buffer = buffer(6)
+ end
+
+ subtree:add_le(p.fields.status_seq, buffer(0x00, 2))
+ subtree:add_le(p.fields.status_new, buffer(0x02, 2))
+
+ pc_global.pid_mid_map[pinfo.number] = id
+end
+dissect_msg_type[MSG_TYPE.STATUS_B] = dissect_msg_type[MSG_TYPE.STATUS_A]
+
+p.fields.magic = ProtoField.bytes("pcmeta.magic", "Magic")
+p.fields.new = ProtoField.bool("pcmeta.new", "New")
+p.fields.type = ProtoField.uint8("pcmeta.type", "Content type", base.DEC, MSG_TYPE_MAP)
+p.fields.len = ProtoField.uint16("pcmeta.len", "Payload length")
+p.fields.data = ProtoField.bytes("pcmeta.data", "Payload")
+function p.dissector(buffer, pinfo, tree)
+ -- check magic
+ if buffer(0x00, 2):uint() ~= GAMEID.PICTOCHAT then return 0 end
+ -- check length (avoid errors)
+ if buffer:len() < 6 then return 0 end
+
+ pinfo.cols.protocol = p.name
+ local buffer_len = math.min(buffer:len(), 0x0a)
+ local subtree = tree:add(p, buffer(0, buffer_len), string.format("PictoChat Meta Header: %d bytes", 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.unknown, buffer(0x04, 2))
+ local unknown = buffer(0x04, 2):le_uint()
+ if unknown == 0 then
+ -- no idea what this field means but if it is 0 then this is the last field
+ -- in the pictochat body, and we must return in order to prevent lua errors
+ pinfo.cols.info = "???"
+ return buffer_len
+ end
+
+ subtree:add_le(p.fields.type, buffer(0x06, 2))
+ local type = buffer(0x06, 2):le_uint()
+ local type_str = MSG_TYPE_MAP[type]
+
+ if type_str ~= nil then
+ pinfo.cols.info = type_str
+ end
+
+ subtree:add_le(p.fields.len, buffer(0x08, 2))
+ local payload_length = buffer(0x08, 2):le_uint()
+ local payload = buffer(0x0a, payload_length)
+
+ subtree:add(p.fields.data, payload)
+
+ local subdissector = dissect_msg_type[type]
+ if subdissector ~= nil then
+ subdissector(payload, pinfo, tree)
+ end
+
+ -- prefix info field with message ID
+ pinfo.cols.info = string.format("[%08x] %s", pc_global.pid_mid_map[pinfo.number] or 0, pinfo.cols.info)
+
+ local reassembly = pc_global.msg[pc_global.pid_mid_map[pinfo.number]]
+ if reassembly ~= nil then
+ local tvb = ByteArray.tvb(reassembly, "Reassembly")
+ pictochat_dissector:call(tvb, pinfo, tree)
+ end
+
+ return buffer_len
+end
+
diff --git a/wireshark/pictochat.lua b/wireshark/pictochat.lua
index 655f27e..507e486 100644
--- a/wireshark/pictochat.lua
+++ b/wireshark/pictochat.lua
@@ -2,190 +2,69 @@ require "util"
local p = Proto("pictochat", "PictoChat")
-function p.init()
- local dt = DissectorTable.get("dslmp")
- dt:add(GAMEID.PICTOCHAT, p)
-end
-
local MSG_TYPE = {
- MSG_START = 1,
- MSG_BODY = 2,
- STATUS_END = 4,
- STATUS = 5,
+ USER_JOIN_A = 0,
+ USER_JOIN_B = 1,
+ MESSAGE = 2,
}
local MSG_TYPE_MAP = {
- [MSG_TYPE.MSG_START] = "Message start",
- [MSG_TYPE.MSG_BODY] = "Message body",
- [MSG_TYPE.STATUS_END] = "Status (end)",
- [MSG_TYPE.STATUS] = "Status", -- constantly spammed, contains room users' addresses
+ [MSG_TYPE.USER_JOIN_A] = "User join",
+ [MSG_TYPE.USER_JOIN_B] = "User join",
+ [MSG_TYPE.MESSAGE] = "Message",
}
p.fields.unknown = ProtoField.uint16("pictochat.unknown", "Unknown")
local dissect_msg_type = { }
-local state = {
- pid_mid_map = {}, -- map packet id to pictochat message id (key = pid)
- msg = {}, -- pictochat messages (key = mid)
- mid = 0, -- current pictochat message id
-}
-
-p.fields.msg_start_len = ProtoField.uint8("pictochat.msg_start.len", "Total length")
-p.fields.msg_start_id = ProtoField.uint32("pictochat.msg_start.id", "ID")
-p.fields.msg_start_seq = ProtoField.uint16("pictochat.msg_start.seq", "Sequence")
-p.fields.msg_start_new = ProtoField.bool("pictochat.msg_start.new", "New")
-dissect_msg_type[MSG_TYPE.MSG_START] = function (buffer, pinfo, tree)
- local subtree = tree:add(p, buffer(), string.format("PictoChat Message start: %d bytes", buffer:len()))
-
- subtree:add_le(p.fields.unknown, buffer(0x00, 2))
- subtree:add_le(p.fields.unknown, buffer(0x02, 2)) -- usu. ffffh
- subtree:add_le(p.fields.msg_start_len, buffer(0x04, 2))
- local len = buffer(0x04, 2):le_uint()
- subtree:add_le(p.fields.unknown, buffer(0x06, 2)) -- usu. 0000h
- subtree:add_le(p.fields.unknown, buffer(0x08, 2))
- subtree:add_le(p.fields.unknown, buffer(0x0a, 2)) -- usu. 0000h
- subtree:add_le(p.fields.msg_start_id, buffer(0x0c, 4))
- local id = buffer(0x0c, 4):le_uint()
- subtree:add_le(p.fields.msg_start_seq, buffer(0x10, 2))
- subtree:add_le(p.fields.new, buffer(0x12, 2))
- local new = buffer(0x12, 2):le_uint() ~= 0
-
- if new then
- state.mid = id
-
- if not pinfo.visited then
- state.msg[state.mid] = {
- buf = ByteArray.new(),
- size = len,
- }
- end
- end
-
- state.pid_mid_map[pinfo.number] = state.mid
-
- if pinfo.visited then
- ByteArray.tvb(state.msg[state.pid_mid_map[pinfo.number]].buf, "Complete message")
- 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_A] = function (buffer, pinfo, tree)
+ local user_name = buffer(0x06, 20):le_ustring()
+ add_addr_le(tree, p.fields.user_addr, buffer(0x00, 6))
+ register_addr_le(buffer(0x00, 6):raw(), user_name)
+ tree:add(p.fields.user_name, buffer(0x06, 20), user_name)
+ tree:add(p.fields.user_msg, buffer(0x1a, 52), buffer(0x1a, 52):le_ustring())
+ tree:add_le(p.fields.user_color, buffer(0x4e, 2))
+ local bday_str = string.format("Birthday: %02d/%02d", buffer(0x50, 1):uint(), buffer(0x51, 1):uint())
+ local bday = tree:add(buffer(0x50, 2), bday_str)
+ bday:add(p.fields.user_bday_month, buffer(0x50, 1))
+ bday:add(p.fields.user_bday_day, buffer(0x51, 1))
+
+ pinfo.cols.info = string.format("%s, user join (%s)", pinfo.cols.info, user_name)
end
+dissect_msg_type[MSG_TYPE.USER_JOIN_B] = dissect_msg_type[MSG_TYPE.USER_JOIN_A]
-p.fields.body_len = ProtoField.uint8("pictochat.body.len", "Length")
-p.fields.body_end = ProtoField.bool("pictochat.body.end", "End")
-p.fields.body_offset = ProtoField.uint16("pictochat.body.offset", "Offset")
-p.fields.body = ProtoField.bytes("pictochat.body", "Data")
-p.fields.body_seq = ProtoField.uint16("pictochat.body.seq", "Sequence")
-p.fields.body_new = ProtoField.bool("pictochat.body.new", "New")
-dissect_msg_type[MSG_TYPE.MSG_BODY] = function (buffer, pinfo, tree)
- local subtree = tree:add(p, buffer(), string.format("PictoChat Message Body: %d bytes", buffer:len()))
-
- subtree:add_le(p.fields.unknown, buffer(0x00, 2))
- subtree:add_le(p.fields.body_len, buffer(0x02, 1))
- local body_len = buffer(0x02, 1):le_uint()
-
- subtree:add_le(p.fields.body_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.
- subtree:add_le(p.fields.body_offset, buffer(0x04, 2))
- local body_offset = buffer(0x04, 2):le_uint()
- subtree: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.
- local body = buffer(0, body_len)
- subtree:add(p.fields.body, body)
-
- buffer = buffer(body_len)
-
- subtree:add_le(p.fields.body_seq, buffer(0x00, 2))
- subtree:add_le(p.fields.body_new, buffer(0x02, 2))
- local new = buffer(0x02, 2):le_uint() ~= 0
-
- -- reassembly
- if pinfo.visited == false and state.msg[state.mid].buf:len() == body_offset and new == true then
- state.msg[state.mid].buf:append(body:bytes())
- end
-
- state.pid_mid_map[pinfo.number] = state.mid
-
- if pinfo.visited then
- ByteArray.tvb(state.msg[state.pid_mid_map[pinfo.number]].buf, "Complete message")
- end
-end
-
-p.fields.status_id = ProtoField.uint32("pictochat.status.id", "ID")
-p.fields.status_addr = ProtoField.bytes("pictochat.status.addr", "Address", base.COLON)
-p.fields.status_seq = ProtoField.uint16("pictochat.status.seq", "Sequence")
-p.fields.status_new = ProtoField.bool("pictochat.status.new", "New")
-dissect_msg_type[MSG_TYPE.STATUS] = function (buffer, pinfo, tree)
- local subtree = tree:add(p, buffer(), string.format("PictoChat Status: %d bytes", buffer:len()))
-
- subtree:add_le(p.fields.status_id, buffer(0x00, 4))
- local id = buffer(0x00, 4):le_uint()
-
- buffer = buffer(0x04)
-
- for _ = 0, 15 do
- add_addr_le(subtree, p.fields.status_addr, buffer(0, 6))
- buffer = buffer(6)
- end
-
- subtree:add_le(p.fields.status_seq, buffer(0x00, 2))
- subtree:add_le(p.fields.status_new, buffer(0x02, 2))
-
- state.pid_mid_map[pinfo.number] = id
+p.fields.msg_addr = ProtoField.bytes("pictochat.msg.addr", "Address", base.COLON)
+p.fields.msg_data = ProtoField.bytes("pictochat.msg.data", "Drawing data")
+dissect_msg_type[MSG_TYPE.MESSAGE] = function (buffer, pinfo, tree)
+ add_addr_le(tree, p.fields.msg_addr, buffer(0x00, 6))
end
-dissect_msg_type[MSG_TYPE.STATUS_END] = dissect_msg_type[MSG_TYPE.STATUS]
+dissect_msg_type[MSG_TYPE.USER_JOIN_B] = dissect_msg_type[MSG_TYPE.USER_JOIN_A]
-p.fields.magic = ProtoField.bytes("pictochat.magic", "Magic")
-p.fields.new = ProtoField.bool("pictochat.new", "New")
-p.fields.flags = ProtoField.uint16("pictochat.flags", "Flags", base.HEX, nil, 0xffff)
-p.fields.msg_type = ProtoField.uint8("pictochat.msg_type", "Message type", base.DEC, MSG_TYPE_MAP)
-p.fields.len = ProtoField.uint16("pictochat.len", "Payload length")
-p.fields.data = ProtoField.bytes("pictochat.data", "Payload")
+p.fields.type = ProtoField.uint8("pictochat.type", "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
- -- check length (avoid errors)
- if buffer:len() < 6 then return 0 end
-
pinfo.cols.protocol = p.name
- local buffer_len = math.min(buffer:len(), 0x0a)
- local subtree = tree:add(p, buffer(0, buffer_len), string.format("PictoChat Header: %d bytes", buffer_len))
+ local buffer_len = buffer:len()
+ local mid = pc_global.pid_mid_map[pinfo.number]
+ local type = buffer(0x01, 1):le_uint()
+ local type_str = MSG_TYPE_MAP[type] or ""
+ local subtree = tree:add(p, buffer(), string.format("%s %s (mid=%08x): %d bytes", p.description, type_str, mid, 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.flags, buffer(0x04, 2))
- local flags = buffer(0x04, 2):le_uint()
- if flags == 0 then
- pinfo.cols.info = "???"
- return buffer_len
- end
+ subtree:add_le(p.fields.unknown, buffer(0x00, 1))
+ subtree:add_le(p.fields.type, buffer(0x01, 1))
+ buffer = buffer(2)
- subtree:add_le(p.fields.msg_type, buffer(0x06, 2))
- local msg_type = buffer(0x06, 2):le_uint()
- local msg_type_str = MSG_TYPE_MAP[msg_type]
-
- if msg_type_str ~= nil then
- pinfo.cols.info = msg_type_str
- end
-
- subtree:add_le(p.fields.len, buffer(0x08, 2))
- local payload_length = buffer(0x08, 2):le_uint()
- local payload = buffer(0x0a, payload_length)
-
- subtree:add(p.fields.data, payload)
-
- local subdissector = dissect_msg_type[msg_type]
+ local subdissector = dissect_msg_type[type]
if subdissector ~= nil then
- subdissector(payload, pinfo, tree)
+ subdissector(buffer, pinfo, subtree)
end
- -- prefix info field with message ID
- pinfo.cols.info = string.format("[%08x] %s", state.pid_mid_map[pinfo.number] or 0, pinfo.cols.info)
-
return buffer_len
end
diff --git a/wireshark/util.lua b/wireshark/util.lua
index 6051351..7c9a16d 100644
--- a/wireshark/util.lua
+++ b/wireshark/util.lua
@@ -1,3 +1,9 @@
+pc_global = {
+ pid_mid_map = {}, -- map packet id to pictochat message id (key = pid)
+ msg = {}, -- pictochat messages (key = mid)
+ mid = 0, -- current pictochat message id
+}
+
function bits(idx, len)
len = len or 1
return ((2^len)-1) * 2^idx