aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/notes.md5
-rw-r--r--wireshark/ieee.lua96
-rw-r--r--wireshark/melon.lua20
-rw-r--r--wireshark/nifi.lua57
-rw-r--r--wireshark/pictochat.lua63
-rw-r--r--wireshark/rxhdr.lua50
-rw-r--r--wireshark/txhdr.lua47
-rw-r--r--wireshark/util.lua5
-rwxr-xr-xwireshark/wireshark6
9 files changed, 239 insertions, 110 deletions
diff --git a/docs/notes.md b/docs/notes.md
index 204ac57..c50a390 100644
--- a/docs/notes.md
+++ b/docs/notes.md
@@ -304,6 +304,11 @@ messages from the system that joined later)
|e|`#0030fb` <b style="color: #0030fb">■</b>|
|f|`#2800fb` <b style="color: #2800fb">■</b>|
+## More interesting sources / link dump
+
+- <https://www.problemkaputt.de/gbatek.htm#dswifiieee80211frames>
+- <https://www.problemkaputt.de/gbatek.htm#dswifinintendobeacons>
+
## Unsure/notes
- Is the endianness of the DS properly emulated?
diff --git a/wireshark/ieee.lua b/wireshark/ieee.lua
new file mode 100644
index 0000000..bbdc397
--- /dev/null
+++ b/wireshark/ieee.lua
@@ -0,0 +1,96 @@
+require "util"
+local bit = require "bit"
+
+local p = Proto("ieee", "IEEE802.11 frame header")
+
+-- based off <https://www.problemkaputt.de/gbatek.htm#dswifiieee80211frames>
+
+p.fields.ctl = ProtoField.new("Frame control", "ieee.ctl", ftypes.BYTES)
+p.fields.ctl_ver = ProtoField.uint16("ieee.ctl.ver", "Protocol version", base.DEC, {
+ [0] = "Current",
+ [1] = "Reserved",
+ [2] = "Reserved",
+ [3] = "Reserved",
+}, bits(0, 2))
+p.fields.ctl_type = ProtoField.uint16("ieee.ctl.type", "Type", base.DEC, {
+ [0] = "Management",
+ [1] = "Control",
+ [2] = "Data",
+ [3] = "Reserved",
+}, bits(2, 2))
+p.fields.ctl_subtype = ProtoField.uint16("ieee.ctl.subtype", "Subtype", base.DEC, nil, bits(4, 4))
+p.fields.ctl_to_ds = ProtoField.bool("ieee.ctl.tods", "To DS", base.DEC, nil, bits(8))
+p.fields.ctl_from_ds = ProtoField.bool("ieee.ctl.fromds", "From DS", base.DEC, nil, bits(9))
+p.fields.ctl_fragment = ProtoField.bool("ieee.ctl.fragment", "More fragments", base.DEC, nil, bits(10))
+p.fields.ctl_retry = ProtoField.bool("ieee.ctl.retry", "Retry", base.DEC, nil, bits(11))
+p.fields.ctl_power = ProtoField.bool("ieee.ctl.power", "Power management", base.DEC, nil, bits(12))
+p.fields.ctl_data = ProtoField.bool("ieee.ctl.data", "More data", base.DEC, nil, bits(13))
+p.fields.ctl_wep = ProtoField.bool("ieee.ctl.wep", "WEP encrypt", base.DEC, nil, bits(14))
+p.fields.ctl_order = ProtoField.bool("ieee.ctl.order", "Order", base.DEC, nil, bits(15))
+
+p.fields.duration = ProtoField.uint16("ieee.duration", "Duration / ID")
+p.fields.addr1 = ProtoField.ether("ieee.addr1", "Address 1")
+p.fields.addr2 = ProtoField.ether("ieee.addr2", "Address 2")
+p.fields.addr3 = ProtoField.ether("ieee.addr3", "Address 3")
+p.fields.seq = ProtoField.uint16("ieee.seq", "Sequence control")
+p.fields.seq_frag = ProtoField.uint16("ieee.seq.frag", "Fragment", base.DEC, nil, bits(0, 4))
+p.fields.seq_num = ProtoField.uint16("ieee.seq.num", "Sequence number", base.DEC, nil, bits(4, 12))
+
+p.fields.body = ProtoField.bytes("ieee.body", "Body")
+
+local pc_dissector = Dissector.get("pictochat")
+
+function p.dissector(buffer, pinfo, tree)
+ local buffer_len = buffer:len()
+ -- invalid
+ if buffer_len < 10 then return 0 end
+
+ -- pretty wireshark shit
+ pinfo.cols.protocol = p.name
+
+ -- MAC header is (usually) 0x18 bytes, but also sometimes contains values in
+ -- the trailer. The 0x18 here is so wireshark only highlights the MAC header
+ -- when clicking this item in the dissection tree.
+ local subtree = tree:add(p, buffer(0x00, 0x18), p.description)
+ local trailer_size = 0
+
+ local ctl_tree = subtree:add_le(p.fields.ctl, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_ver, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_type, buffer(0x00, 2))
+ local ctl_type = bit.rshift(bit.band(buffer(0x00, 2):le_uint(), bits(2, 2)), 2)
+ ctl_tree:add_le(p.fields.ctl_subtype, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_to_ds, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_from_ds, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_fragment, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_retry, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_power, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_data, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_wep, buffer(0x00, 2))
+ ctl_tree:add_le(p.fields.ctl_order, buffer(0x00, 2))
+
+ subtree:add_le(p.fields.duration, buffer(0x02, 2))
+ subtree:add(p.fields.addr1, buffer(0x04, 6))
+ subtree:add(p.fields.addr2, buffer(0x0a, 6))
+ subtree:add(p.fields.addr3, buffer(0x10, 6))
+ local seq_tree = subtree:add_le(p.fields.seq, buffer(0x16, 2))
+ seq_tree:add_le(p.fields.seq_frag, buffer(0x16, 2))
+ seq_tree:add_le(p.fields.seq_num, buffer(0x16, 2))
+
+ if ctl_type ~= 0 then
+ trailer_size = 4 -- Frame Check Sequence (FCS) (hardware-generated CRC32)
+ end
+
+ buffer = buffer(0x18) -- seek forward
+
+ local body_size = buffer:len() - trailer_size
+ subtree:add(p.fields.body, buffer(0, body_size))
+ pc_dissector:call(buffer(0, body_size):tvb(), pinfo, tree)
+
+ if trailer_size == 0 then
+ return buffer_len
+ end
+
+ buffer = buffer(body_size)
+
+ return buffer_len
+end
diff --git a/wireshark/melon.lua b/wireshark/melon.lua
index a431781..265af01 100644
--- a/wireshark/melon.lua
+++ b/wireshark/melon.lua
@@ -1,5 +1,4 @@
-local p = Proto("melon", "MelonDS packet header")
-local dt = DissectorTable.new("melon")
+local p = Proto("melon", "MelonDS Ni-Fi header")
p.fields.magic = ProtoField.uint32("melon.magic", "Magic", base.HEX)
p.fields.src = ProtoField.int32("melon.src", "Instance ID", base.DEC)
@@ -16,11 +15,15 @@ p.fields.timestamp = ProtoField.uint64("melon.timestamp", "Timestamp", base.DEC)
local p_type_enum_field = Field.new("melon.type.enum")
+local txhdr_dissector = Dissector.get("txhdr")
+local rxhdr_dissector = Dissector.get("rxhdr")
+
function p.dissector(buffer, pinfo, tree)
local header_size = 0x18
-
+ -- check buffer size
+ if buffer:len() < header_size then return 0 end
-- check magic ("NIFI")
- if buffer(0x00, 4):uint() ~= 0x4e494649 then return end
+ if buffer(0x00, 4):uint() ~= 0x4e494649 then return 0 end
local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size))
subtree:add(p.fields.magic, buffer(0x00, 4))
@@ -40,9 +43,12 @@ function p.dissector(buffer, pinfo, tree)
pinfo.cols.src = string.format("instance %d", instance)
pinfo.cols.info = p_type_enum_field().display
- -- melonds packets always contain NIFI packets, I use 0 as the pattern
- -- because this function doesn't seem to like nil
- dt:try(0, buffer(header_size):tvb(), pinfo, tree)
+ -- melonds packets always contain NIFI packets
+ local next_dissector = txhdr_dissector
+ -- if instance ~= 0 then
+ -- next_dissector = rxhdr_dissector
+ -- end
+ next_dissector:call(buffer(header_size):tvb(), pinfo, tree)
return header_size
end
diff --git a/wireshark/nifi.lua b/wireshark/nifi.lua
deleted file mode 100644
index 94a563c..0000000
--- a/wireshark/nifi.lua
+++ /dev/null
@@ -1,57 +0,0 @@
-local p = Proto("nifi", "Ni-Fi header")
-local dt = DissectorTable.new("nifi")
-
-p.fields.frame_type = ProtoField.uint16("nifi.type", "Frame type", base.DEC, {
- [0] = "Normal", -- Used for actual messages, ack packets
- [1] = "Announcement", -- TODO: send broadcast???
-})
-p.fields.original = ProtoField.bool("nifi.original", "Original") -- only 0 or 2
-p.fields.gameid = ProtoField.uint16("nifi.gid", "Game ID", base.HEX, {
- [0xfffd] = "PictoChat (CMD)",
- [0x7dcb] = "PictoChat (ACK)",
- [0x5a5a] = "Mario Kart DS (CMD)",
- [0x7c02] = "Mario Kart DS (ACK)",
-})
-p.fields.magic = ProtoField.bytes("nifi.magic", "Magic")
-p.fields.length = ProtoField.uint16("nifi.len", "Remaining message length")
-
-local melon_type_enum_field = Field.new("melon.type.enum")
-
-function p.init()
- -- register nifi as a subdissector for melon
- DissectorTable.get("melon"):add(0, p)
-end
-
-function p.dissector(buffer, pinfo, tree)
- local header_size = 12
-
- local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size))
- subtree:add_le(p.fields.frame_type, buffer(0x00, 2))
- subtree:add_le(p.fields.original, buffer(0x02, 2))
- local original = buffer(0x02, 2):le_uint() > 0
-
- local melon_type = melon_type_enum_field()()
- local gameid_buf = buffer(0x04, 4)
- local gameid = 0
- if melon_type == 1 then -- CMD
- gameid_buf = gameid_buf(2, 2)
- elseif melon_type == 3 then -- ACK
- gameid_buf = gameid_buf(0, 2)
- end
- gameid = gameid_buf():le_uint()
- subtree:add_le(p.fields.gameid, gameid_buf)
-
- subtree:add_le(p.fields.magic, buffer(0x08, 2)) -- const 0x1401
- subtree:add_le(p.fields.length, buffer(0x0a, 2))
- local length = buffer(0x0a, 2):le_uint()
-
- -- pretty wireshark shit
- pinfo.cols.protocol = p.name
- pinfo.cols.info = string.format("GID %04x %s, %s", gameid, pinfo.cols.info, (original and "Original" or "Resend"))
-
- -- try to call subdissector for gameid
- dt:try(gameid, buffer(header_size, length):tvb(), pinfo, tree)
-
- return header_size
-end
-
diff --git a/wireshark/pictochat.lua b/wireshark/pictochat.lua
index ba16ee0..79345f6 100644
--- a/wireshark/pictochat.lua
+++ b/wireshark/pictochat.lua
@@ -12,10 +12,6 @@ p.fields.msg_type = ProtoField.uint8("pictochat.msg_type", "Message type", base.
[86] = "Message body", -- contains tile data for drawing
[184] = "???", -- user leave??
})
-p.fields.length = ProtoField.uint16("pictochat.length", "Length")
-p.fields.host = ProtoField.ether("pictochat.host", "Room host")
-p.fields.src = ProtoField.ether("pictochat.src", "Source")
-p.fields.dst = ProtoField.ether("pictochat.dst", "Destination")
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")
@@ -49,45 +45,26 @@ p.fields.user_bday_month = ProtoField.uint8("pictochat.user.bday_month", "Month"
p.fields.user_bday_day = ProtoField.uint8("pictochat.user.bday_day", "Day")
p.fields.msg_start_len = ProtoField.uint8("pictochat.msg.start_len", "Total length")
-local pc_src_field = Field.new("pictochat.src")
-local pc_dst_field = Field.new("pictochat.dst")
-local data_remaining = 0
-
local state = {}
-function p.init()
- -- register pictochat as a subdissector for nifi
- local dt = DissectorTable.get("nifi")
- dt:add(0xfffd, p) -- CMD messages
- -- dt:add(0x7dcb, p) -- ACK messages
-end
-
function p.dissector(buffer, pinfo, tree)
local subtree = tree:add(p, buffer(), string.format("%s: %d bytes", p.description, buffer():len()))
- subtree:add_le(p.fields.unknown, buffer(0x00, 2))
- subtree:add_le(p.fields.unknown, buffer(0x02, 2))
- subtree:add_le(p.fields.dst, buffer(0x04, 6))
- subtree:add_le(p.fields.src, buffer(0x0a, 6))
- subtree:add_le(p.fields.host, buffer(0x10, 6))
- subtree:add_le(p.fields.sequence, buffer(0x16, 2))
- subtree:add_le(p.fields.unknown, buffer(0x18, 2))
- subtree:add_le(p.fields.unknown, buffer(0x1a, 2))
- subtree:add_le(p.fields.msg_type, buffer(0x1c, 1))
- local msg_type = buffer(0x1c, 1):le_uint()
- subtree:add_le(p.fields.unknown, buffer(0x1d, 1))
- subtree:add_le(p.fields.unknown, buffer(0x1e, 2))
- buffer = buffer(0x20)
+
+ subtree:add_le(p.fields.unknown, buffer(0x00, 2))
+ subtree:add_le(p.fields.unknown, buffer(0x02, 2))
+ subtree:add_le(p.fields.msg_type, buffer(0x04, 1))
+ local msg_type = buffer(0x04, 1):le_uint()
+ subtree:add_le(p.fields.unknown, buffer(0x05, 1))
+ subtree:add_le(p.fields.unknown, buffer(0x06, 2))
+ buffer = buffer(0x08)
-- pretty wireshark shit
pinfo.cols.protocol = p.name
- pinfo.cols.src = tostring(pc_src_field())
- pinfo.cols.dst = tostring(pc_dst_field())
subtree:add_le(p.fields.payload_len, buffer(0x00, 2))
local payload_length = buffer(0x00, 2):le_uint()
buffer = buffer(0x02)
local payload = subtree:add(buffer(0x00, payload_length), "Payload: " .. payload_length .. " bytes")
- local buffer_next = buffer(payload_length)
if
msg_type == 48 -- user join
@@ -105,10 +82,10 @@ function p.dissector(buffer, pinfo, tree)
msg_type == 10 -- msg start
then
payload:add_le(p.fields.msg_start_len, buffer(0x04, 2))
- data_remaining = buffer(0x04, 2):le_uint()
- local segment = buffer(0):bytes()
- local buf = ByteArray.tvb(segment, "Complete message???")
+ state.data_remaining = buffer(0x04, 2):le_uint()
+ state.buf = ByteArray.new()
+ state.want_next = 0
end
if
@@ -119,32 +96,30 @@ function p.dissector(buffer, pinfo, tree)
payload:add_le(p.fields.data_len, buffer(0x02, 1))
local data_length = buffer(0x02, 1):le_uint()
- if data_remaining > 0 then
- data_remaining = data_remaining - data_length
- pinfo.cols.info = string.format("Message body [remaining 0x%04x (%d) bytes]", data_remaining, data_remaining)
- end
-
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
- subtree:add_le(p.fields.unknown, buffer(0x04, 2))
- buffer = buffer(0x06)
end
-
- buffer = buffer_next -- after payload
- subtree:add_le(p.fields.magic_trailer, buffer(0x02, 2)) -- const 0xb8b6
end
diff --git a/wireshark/rxhdr.lua b/wireshark/rxhdr.lua
new file mode 100644
index 0000000..3924603
--- /dev/null
+++ b/wireshark/rxhdr.lua
@@ -0,0 +1,50 @@
+require "util"
+
+local p = Proto("rxhdr", "Hardware RX header")
+
+-- based off <https://www.problemkaputt.de/gbatek.htm#dswifihardwareheaders>
+
+p.fields.unknown = ProtoField.bytes("rxhdr.unknown", "Unknown")
+
+p.fields.flags = ProtoField.new("Flags", "rxhdr.flags", ftypes.BYTES)
+p.fields.flag_type = ProtoField.uint16("rxhdr.flags.type", "Frame type", base.DEC, nil, bits(0, 4))
+p.fields.flag_more = ProtoField.bool("rxhdr.flags.more", "More fragments", base.DEC, nil, bits(8))
+
+p.fields.new = ProtoField.bool("rxhdr.new", "New frame")
+p.fields.magic = ProtoField.bytes("rxhdr.magic", "Magic")
+p.fields.channel = ProtoField.uint8("rxhdr.channel", "802.11 channel")
+
+p.fields.rate = ProtoField.uint16("rxhdr.rate", "Transfer rate", base.HEX, {
+ [0x0a] = "1 Mbit/s",
+ [0x14] = "2 Mbit/s",
+})
+p.fields.length = ProtoField.uint16("rxhdr.len", "Remaining message length")
+
+local ieee_dissector = Dissector.get("ieee")
+
+function p.dissector(buffer, pinfo, tree)
+ local header_size = 12
+ -- check buffer size
+ if buffer:len() < header_size then return 0 end
+
+ local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size))
+
+ local flags_tree = subtree:add(p.fields.flags, buffer(0x00, 2))
+ flags_tree:add_le(p.fields.flag_type, buffer(0x00, 2))
+ flags_tree:add_le(p.fields.flag_more, buffer(0x00, 2))
+
+ subtree:add(p.fields.unknown, buffer(0x02, 2))
+ subtree:add(p.fields.unknown, buffer(0x04, 2))
+
+ subtree:add_le(p.fields.rate, buffer(0x06, 2))
+ subtree:add_le(p.fields.length, buffer(0x08, 2))
+ local length = buffer(0x08, 2):le_uint()
+
+ -- pretty wireshark shit
+ pinfo.cols.protocol = p.name
+
+ ieee_dissector:call(buffer(header_size, length):tvb(), pinfo, tree)
+
+ return header_size
+end
+
diff --git a/wireshark/txhdr.lua b/wireshark/txhdr.lua
new file mode 100644
index 0000000..fc8db21
--- /dev/null
+++ b/wireshark/txhdr.lua
@@ -0,0 +1,47 @@
+local p = Proto("txhdr", "Hardware TX header")
+
+-- based off <https://www.problemkaputt.de/gbatek.htm#dswifihardwareheaders>
+
+p.fields.unknown = ProtoField.bytes("txhdr.unknown", "Unknown")
+
+p.fields.status = ProtoField.uint16("txhdr.status", "Status", base.DEC, {
+ [0x00] = "Retrying?",
+ [0x01] = "OK",
+ [0x03] = "Failed",
+ [0x05] = "Failed",
+}, 0x00ff)
+p.fields.new = ProtoField.bool("txhdr.new", "New frame")
+p.fields.rate = ProtoField.uint8("txhdr.rate", "Transfer rate", base.HEX, {
+ [0x0a] = "1 Mbit/s",
+ [0x14] = "2 Mbit/s",
+})
+p.fields.channel = ProtoField.uint8("txhdr.channel", "802.11 channel")
+p.fields.length = ProtoField.uint16("txhdr.len", "Remaining message length")
+
+local ieee_dissector = Dissector.get("ieee")
+
+function p.dissector(buffer, pinfo, tree)
+ local header_size = 12
+ -- check buffer size
+ if buffer:len() < header_size then return 0 end
+
+ local subtree = tree:add(p, buffer(0, header_size), string.format("%s: %d bytes", p.description, header_size))
+
+ subtree:add_le(p.fields.status, buffer(0x00, 2))
+ subtree:add_le(p.fields.new, buffer(0x02, 2))
+ subtree:add_le(p.fields.unknown, buffer(0x04, 1))
+ subtree:add_le(p.fields.unknown, buffer(0x05, 1))
+ subtree:add_le(p.fields.unknown, buffer(0x06, 2))
+ subtree:add_le(p.fields.rate, buffer(0x08, 1))
+ subtree:add_le(p.fields.channel, buffer(0x09, 1))
+ subtree:add_le(p.fields.length, buffer(0x0a, 2))
+ local length = buffer(0x0a, 2):le_uint()
+
+ -- pretty wireshark shit
+ pinfo.cols.protocol = p.name
+
+ ieee_dissector:call(buffer(header_size, length):tvb(), pinfo, tree)
+
+ return header_size
+end
+
diff --git a/wireshark/util.lua b/wireshark/util.lua
new file mode 100644
index 0000000..e23f958
--- /dev/null
+++ b/wireshark/util.lua
@@ -0,0 +1,5 @@
+function bits(idx, len)
+ len = len or 1
+ return ((2^len)-1) * 2^idx
+end
+
diff --git a/wireshark/wireshark b/wireshark/wireshark
index d6a285b..94d64d9 100755
--- a/wireshark/wireshark
+++ b/wireshark/wireshark
@@ -2,9 +2,11 @@
# simple wrapper to load lua scripts for DLT_USER0
here="$(dirname "$0")"
exec wireshark \
- -X "lua_script:$here/melon.lua" \
- -X "lua_script:$here/nifi.lua" \
-X "lua_script:$here/pictochat.lua" \
+ -X "lua_script:$here/ieee.lua" \
+ -X "lua_script:$here/txhdr.lua" \
+ -X "lua_script:$here/rxhdr.lua" \
+ -X "lua_script:$here/melon.lua" \
-o 'uat:user_dlts:"User 0 (DLT=147)","melon","","","",""' \
"$@"