Last active
June 15, 2021 05:01
-
-
Save flarn2006/2611b84101cdc48f954528479d7ddda0 to your computer and use it in GitHub Desktop.
Wireshark dissector for Sonic Robo Blast 2 netplay
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local srb2_proto = Proto('SRB2', 'Sonic Robo Blast 2') | |
srb2_proto.prefs.validate_checksum = Pref.bool('Validate checksums', false, 'Whether to validate the packet checksum') | |
local function add_fields(...) | |
for i,v in ipairs({...}) do | |
table.insert(srb2_proto.fields, v) | |
end | |
end | |
-- Version 2.2.9 | |
local packet_types = { | |
[0] = 'PT_NOTHING', | |
[1] = 'PT_SERVERCFG', | |
[2] = 'PT_CLIENTCMD', | |
[3] = 'PT_CLIENTMIS', | |
[4] = 'PT_CLIENT2CMD', | |
[5] = 'PT_CLIENT2MIS', | |
[6] = 'PT_NODEKEEPALIVE', | |
[7] = 'PT_NODEKEEPALIVEMIS', | |
[8] = 'PT_SERVERTICS', | |
[9] = 'PT_SERVERREFUSE', | |
[10] = 'PT_SERVERSHUTDOWN', | |
[11] = 'PT_CLIENTQUIT', | |
[12] = 'PT_ASKINFO', | |
[13] = 'PT_SERVERINFO', | |
[14] = 'PT_PLAYERINFO', | |
[15] = 'PT_REQUESTFILE', | |
[16] = 'PT_ASKINFOVIAMS (obsolete)', | |
[17] = 'PT_WILLRESENDGAMESTATE', | |
[18] = 'PT_CANRECEIVEGAMESTATE', | |
[19] = 'PT_RECEIVEDGAMESTATE', | |
[20] = 'PT_SENDINGLUAFILE', | |
[21] = 'PT_ASKLUAFILE', | |
[22] = 'PT_HASLUAFILE', | |
[23] = 'PT_FILEFRAGMENT', | |
[24] = 'PT_FILEACK', | |
[25] = 'PT_FILERECEIVED', | |
[26] = 'PT_TEXTCMD', | |
[27] = 'PT_TEXTCMD2', | |
[28] = 'PT_CLIENTJOIN', | |
[29] = 'PT_NODETIMEOUT', | |
[30] = 'PT_LOGIN', | |
[31] = 'PT_PING' | |
} | |
local packet_dissectors = {} | |
local function NetbufferChecksum(buf) | |
local c = 0x1234567 | |
for i=4,buf:len()-1 do | |
c = c + buf(i,1):uint() * (i-3) | |
end | |
return c | |
end | |
local f_checksum = ProtoField.uint32('srb2.checksum', 'Checksum', base.HEX) | |
local f_valid = ProtoField.bool('srb2.valid', 'Checksum Status') | |
local f_ack = ProtoField.uint8('srb2.ack', 'ACK') | |
local f_ackret = ProtoField.uint8('srb2.ackret', 'ACK Return') | |
local f_ptype = ProtoField.uint8('srb2.ptype', 'Packet Type', base.DEC, packet_types) | |
local f_reserved = ProtoField.uint8('srb2.reserved', 'Reserved') | |
local f_datalen = ProtoField.uint32('srb2.datalen', 'Data Length') | |
local f_data = ProtoField.bytes('srb2.data', 'Data', base.NONE) | |
add_fields(f_checksum, f_valid, f_ack, f_ackret, f_ptype, f_reserved, f_datalen, f_data) | |
-- PT_SERVERCFG | |
local game_states = { | |
[0] = 'GS_NULL', | |
[1] = 'GS_LEVEL', | |
[2] = 'GS_INTERMISSION', | |
[3] = 'GS_CONTINUING', | |
[4] = 'GS_TITLESCREEN', | |
[5] = 'GS_TIMEATTACK', | |
[6] = 'GS_CREDITS', | |
[7] = 'GS_EVALUATION', | |
[8] = 'GS_GAMEEND', | |
[9] = 'GS_INTRO', | |
[10] = 'GS_ENDING', | |
[11] = 'GS_CUTSCENE', | |
[12] = 'GS_DEDICATEDSERVER', | |
[13] = 'GS_WAITINGPLAYERS' | |
} | |
local game_types = { | |
[0] = 'GT_COOP', | |
[1] = 'GT_COMPETITION', | |
[2] = 'GT_RACE', | |
[3] = 'GT_MATCH', | |
[4] = 'GT_TEAMMATCH', | |
[5] = 'GT_TAG', | |
[6] = 'GT_HIDEANDSEEK', | |
[7] = 'GT_CTF' | |
} | |
-- PT_SERVERCFG | |
local f_version = ProtoField.uint8('srb2.data.version', 'Version') | |
local f_subversion = ProtoField.uint8('srb2.data.subversion', 'Build') | |
local f_serverplayer = ProtoField.uint8('srb2.data.serverplayer', 'Server Player') | |
local f_totalslotnum = ProtoField.uint8('srb2.data.totalslotnum', 'Total Slot Num') | |
local f_gametic = ProtoField.uint32('srb2.data.gametic', 'Game Tic') | |
local f_clientnode = ProtoField.uint8('srb2.data.clientnode', 'Client Node') | |
local f_gamestate = ProtoField.uint8('srb2.data.gamestate', 'Game State', base.DEC, game_states) | |
local f_gametype = ProtoField.uint8('srb2.data.gametype', 'Game Type', base.DEC, game_types) | |
local f_modifiedgame = ProtoField.uint8('srb2.data.modifiedgame', 'Modified Game') | |
local f_server_context = ProtoField.string('srb2.data.server_context', 'Server Context') | |
add_fields(f_version, f_subversion, f_serverplayer, f_totalslotnum, f_gametic, f_clientnode) | |
add_fields(f_gamestate, f_gametype, f_modifiedgame, f_server_context) | |
-- PT_SERVERREFUSE | |
local f_refuse_reason = ProtoField.string('srb2.data.refuse_reason', 'Refusal Reason') | |
add_fields(f_refuse_reason) | |
-- PT_REQUESTFILE | |
local f_fileid = ProtoField.uint8('srb2.data.fileid', 'File ID') | |
local f_filename = ProtoField.string('srb2.data.filename', 'Filename') | |
local f__255 = ProtoField.uint8('srb2.data._255', '_255') | |
add_fields(f_fileid, f_filename, f__255) | |
-- PT_FILEFRAGMENT | |
-- f_fileid | |
local f_filesize = ProtoField.uint32('srb2.data.filesize', 'File Size') | |
local f_iteration = ProtoField.uint8('srb2.data.iteration', 'Iteration') | |
local f_position = ProtoField.uint32('srb2.data.position', 'Position', base.HEX) | |
local f_size = ProtoField.uint16('srb2.data.fragmentsize', 'Fragment Size') | |
local f_fragment = ProtoField.bytes('srb2.data.fragment', 'Fragment', base.NONE) | |
add_fields(f_filesize, f_iteration, f_position, f_size, f_fragment) | |
-- PT_FILEACK | |
-- f_fileid | |
-- f_iteration | |
local f_numsegments = ProtoField.uint8('srb2.data.numsegments', '# of Segments') | |
add_fields(f_numsegments) | |
-- PT_FILERECEIVED | |
-- f_fileid | |
-- PT_CLIENTJOIN | |
-- f__255 | |
local f_packetversion = ProtoField.uint8('srb2.data.packetversion', 'Packet Version') | |
local f_application = ProtoField.string('srb2.data.application', 'Application') | |
-- f_version | |
-- f_subversion | |
local f_localplayers = ProtoField.uint8('srb2.data.localplayers', 'Local Players') | |
local f_mode = ProtoField.uint8('srb2.data.mode', 'Mode') | |
local f_p1name = ProtoField.string('srb2.data.p1name', 'Player 1 Name') | |
local f_p2name = ProtoField.string('srb2.data.p2name', 'Player 2 Name') | |
add_fields(f_packetversion, f_application, f_localplayers, f_mode, f_p1name, f_p2name) | |
function srb2_proto.dissector(buffer, pinfo, tree) | |
pinfo.cols.protocol = 'SRB2' | |
local ptype = buffer(6,1):uint() | |
local ptype_str = (packet_types[ptype] or 'Unknown')..' ('..ptype..')' | |
pinfo.cols.info = ptype_str | |
local cksum_expected = NetbufferChecksum(buffer) | |
local cksum_given = buffer(0,4):le_uint() | |
local cksum_valid = (cksum_given == cksum_expected) | |
local cksum_str = 'Checksum Status: ' | |
if srb2_proto.prefs.validate_checksum then | |
if cksum_valid then | |
cksum_str = cksum_str..'OK' | |
else | |
cksum_str = cksum_str..string.format('should be 0x%X!', cksum_expected) | |
end | |
else | |
cksum_str = cksum_str..'Unverified' | |
end | |
local datalen = buffer:len() - 8 | |
local subtree = tree:add(srb2_proto, buffer(), 'Sonic Robo Blast 2 netgame protocol') | |
subtree:add_le(f_checksum, buffer(0,4)) | |
subtree:add(f_valid, cksum_valid):set_text(cksum_str):set_generated(true) | |
subtree:add(f_ack, buffer(4,1)) | |
subtree:add(f_ackret, buffer(5,1)) | |
subtree:add(f_ptype, buffer(6,1)) | |
subtree:add(f_reserved, buffer(7,1)) | |
subtree:add(f_datalen, datalen):set_generated(true) | |
local data_tree = subtree:add(f_data, buffer(8)) | |
local stage2 = packet_dissectors[packet_types[ptype]] | |
if stage2 then | |
stage2(buffer(8), datalen, pinfo, data_tree) | |
end | |
end | |
local MAXAPPLICATION = 16 | |
local MAXPLAYERNAME = 21 | |
function packet_dissectors.PT_SERVERCFG(range, datalen, pinfo, tree) | |
tree:add(f_version, range(0,1)) | |
tree:add(f_subversion, range(1,1)) | |
tree:add(f_serverplayer, range(2,1)) | |
tree:add(f_totalslotnum, range(3,1)) | |
tree:add_le(f_gametic, range(4,4)) | |
tree:add(f_clientnode, range(8,1)) | |
tree:add(f_gamestate, range(9,1)) | |
tree:add(f_gametype, range(10,1)) | |
tree:add(f_modifiedgame, range(11,1)) | |
tree:add(f_server_context, range(12,8)) | |
end | |
function packet_dissectors.PT_SERVERREFUSE(range, datalen, pinfo, tree) | |
tree:add(f_refuse_reason, range) | |
end | |
function packet_dissectors.PT_REQUESTFILE(range, datalen, pinfo, tree) | |
tree:add(f_fileid, range(0,1)) | |
tree:add(f_filename, range(1, datalen-2)) | |
tree:add(f__255, range(datalen-1, 1)) | |
end | |
function packet_dissectors.PT_FILEFRAGMENT(range, datalen, pinfo, tree) | |
tree:add(f_fileid, range(0,1)) | |
tree:add_le(f_filesize, range(1,4)) | |
tree:add(f_iteration, range(5,1)) | |
tree:add_le(f_position, range(6,4)) | |
tree:add_le(f_size, range(10,2)) | |
tree:add(f_fragment, range(12)) | |
end | |
function packet_dissectors.PT_FILEACK(range, datalen, pinfo, tree) | |
tree:add(f_fileid, range(0,1)) | |
tree:add(f_iteration, range(1,1)) | |
tree:add(f_numsegments, range(2,1)) | |
local numsegments = (datalen - 3) / 8 | |
for i=0,numsegments-1 do | |
local segstart = 3 + 8*i | |
local start = range(segstart, 4):le_uint() | |
local acks = range(segstart+4, 4):le_uint() | |
local str = string.format('Segment %u: start=0x%X, acks=', i, start) | |
for j=1,32 do | |
if acks % 2 == 1 then | |
str = str..'1' | |
else | |
str = str..'0' | |
end | |
acks = math.floor(acks / 2) | |
end | |
tree:add(range(segstart, 8), str) | |
end | |
end | |
function packet_dissectors.PT_FILERECEIVED(range, datalen, pinfo, tree) | |
tree:add(f_fileid, range(0,1)) | |
end | |
function packet_dissectors.PT_CLIENTJOIN(range, datalen, pinfo, tree) | |
tree:add(f__255, range(0,1)) | |
tree:add(f_packetversion, range(1,1)) | |
tree:add(f_application, range(2, MAXAPPLICATION)) | |
tree:add(f_version, range(2+MAXAPPLICATION, 1)) | |
tree:add(f_subversion, range(3+MAXAPPLICATION, 1)) | |
tree:add(f_localplayers, range(4+MAXAPPLICATION, 1)) | |
tree:add(f_mode, range(5+MAXAPPLICATION, 1)) | |
tree:add(f_p1name, range(6+MAXAPPLICATION, MAXPLAYERNAME)) | |
tree:add(f_p2name, range(6+MAXAPPLICATION+MAXPLAYERNAME, MAXPLAYERNAME)) | |
end | |
DissectorTable.get('udp.port'):add(5029, srb2_proto) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment