|
---------------------------------------- |
|
-- Wireshark Lua dissector for Eulora protocol |
|
-- |
|
-- author: Michael Schierl <schierlm at gmx dot de>, or <mihi> @ freenode |
|
-- |
|
-- Based on the Lua example dissector script by Hadriel Kaplan <hadrielk at yahoo dot com> which is |
|
-- Copyright (c) 2014, Hadriel Kaplan |
|
-- This code is in the Public Domain, or the BSD (3 clause) license if Public Domain does not apply |
|
-- in your country. |
|
-- |
|
---------------------------------------- |
|
|
|
local eulora = Proto("eulora","Eulora Protocol") |
|
|
|
local msgtypes = { |
|
[1] = "PING", |
|
[2] = "AUTHENTICATE", |
|
[3] = "PREAUTHENTICATE", |
|
[4] = "PREAUTHAPPROVED", |
|
[5] = "AUTHAPPROVED", |
|
[6] = "AUTHREJECTED", |
|
[7] = "DISCONNECT", |
|
[8] = "CHAT", |
|
[9] = "CHANNEL_JOIN", |
|
[10] = "CHANNEL_JOINED", |
|
[11] = "CHANNEL_LEAVE", |
|
[12] = "GUILDCMD", |
|
[13] = "USERCMD", |
|
[14] = "SYSTEM", |
|
[15] = "CHARREJECT", |
|
[16] = "DEAD_RECKONING", |
|
[17] = "FORCE_POSITION", |
|
[18] = "CELPERSIST", |
|
[19] = "CONFIRMQUESTION", |
|
[20] = "USERACTION", |
|
[21] = "ADMINCMD", |
|
[22] = "GUIINTERACT", |
|
[23] = "GUIINVENTORY", |
|
[24] = "VIEW_ITEM", |
|
[25] = "VIEW_CONTAINER", |
|
[26] = "VIEW_SKETCH", |
|
[27] = "VIEW_ACTION_LOCATION", |
|
[28] = "READ_BOOK", |
|
[29] = "WRITE_BOOK", |
|
[30] = "UPDATE_ITEM", |
|
[31] = "MODE", |
|
[32] = "WEATHER", |
|
[33] = "NEWSECTOR", |
|
[34] = "GUIGUILD", |
|
[35] = "EQUIPMENT", |
|
[36] = "GUIEXCHANGE", |
|
[37] = "EXCHANGE_REQUEST", |
|
[38] = "EXCHANGE_ADD_ITEM", |
|
[39] = "EXCHANGE_REMOVE_ITEM", |
|
[40] = "EXCHANGE_ACCEPT", |
|
[41] = "EXCHANGE_STATUS", |
|
[42] = "EXCHANGE_END", |
|
[43] = "EXCHANGE_AUTOGIVE", |
|
[44] = "EXCHANGE_MONEY", |
|
[45] = "GUIMERCHANT", |
|
[46] = "GUISTORAGE", |
|
[47] = "GROUPCMD", |
|
[48] = "GUIGROUP", |
|
[49] = "STATDRUPDATE", |
|
[50] = "SPELL_BOOK", |
|
[51] = "GLYPH_REQUEST", |
|
[52] = "GLYPH_ASSEMBLE", |
|
[53] = "PURIFY_GLYPH", |
|
[54] = "SPELL_CAST", |
|
[55] = "SPELL_CANCEL", |
|
[56] = "EFFECT", |
|
[57] = "EFFECT_STOP", |
|
[58] = "NPCAUTHENT", |
|
[59] = "NPCLIST", |
|
[60] = "GUITARGETUPDATE", |
|
[61] = "MAPLIST", |
|
[62] = "NPCCOMMANDLIST", |
|
[63] = "NPCREADY", |
|
[64] = "ALLENTITYPOS", |
|
[65] = "PERSIST_ALL_ENTITIES", |
|
[66] = "NEW_NPC", |
|
[67] = "PETITION", |
|
[68] = "MSGSTRINGS", |
|
[69] = "CHARACTERDATA", |
|
[70] = "AUTHCHARACTER", |
|
[71] = "AUTHCHARACTERAPPROVED", |
|
[72] = "CHAR_CREATE_CP", |
|
[73] = "COMBATEVENT", |
|
[74] = "LOOT", |
|
[75] = "LOOTITEM", |
|
[76] = "LOOTREMOVE", |
|
[77] = "GUISKILL", |
|
[78] = "OVERRIDEACTION", |
|
[79] = "QUESTLIST", |
|
[80] = "QUESTINFO", |
|
[81] = "GMGUI", |
|
[82] = "WORKCMD", |
|
[83] = "BUDDY_LIST", |
|
[84] = "BUDDY_STATUS", |
|
[85] = "MOTD", |
|
[86] = "MOTDREQUEST", |
|
[87] = "QUESTION", |
|
[88] = "QUESTIONRESPONSE", |
|
[89] = "SLOT_MOVEMENT", |
|
[90] = "QUESTIONCANCEL", |
|
[91] = "GUILDMOTDSET", |
|
[92] = "PLAYSOUND", |
|
[93] = "PLAYVOICE", |
|
[94] = "CHARACTERDETAILS", |
|
[95] = "CHARDETAILSREQUEST", |
|
[96] = "CHARDESCUPDATE", |
|
[97] = "FACTION_INFO", |
|
[98] = "QUESTREWARD", |
|
[99] = "NAMECHANGE", |
|
[100] = "GUILDCHANGE", |
|
[101] = "LOCKPICK", |
|
[102] = "GMSPAWNITEMS", |
|
[103] = "GMSPAWNTYPES", |
|
[104] = "GMSPAWNITEM", |
|
[105] = "ADVICE", |
|
[106] = "ACTIVEMAGIC", |
|
[107] = "GROUPCHANGE", |
|
[108] = "MAPACTION", |
|
[109] = "CLIENTSTATUS", |
|
[110] = "TUTORIAL", |
|
[111] = "BANKING", |
|
[112] = "CMDDROP", |
|
[113] = "REQUESTMOVEMENTS", |
|
[114] = "MOVEINFO", |
|
[115] = "MOVEMOD", |
|
[116] = "MOVELOCK", |
|
[117] = "CHAR_DELETE", |
|
[118] = "CHAR_CREATE_PARENTS", |
|
[119] = "CHAR_CREATE_CHILDHOOD", |
|
[120] = "CHAR_CREATE_LIFEEVENTS", |
|
[121] = "CHAR_CREATE_UPLOAD", |
|
[122] = "CHAR_CREATE_VERIFY", |
|
[123] = "CHAR_CREATE_NAME", |
|
[124] = "PERSIST_WORLD_REQUEST", |
|
[125] = "PERSIST_WORLD", |
|
[126] = "PERSIST_ACTOR_REQUEST", |
|
[127] = "PERSIST_ACTOR", |
|
[128] = "PERSIST_ITEM", |
|
[129] = "PERSIST_ACTIONLOCATION", |
|
[130] = "PERSIST_ALL", |
|
[131] = "REMOVE_OBJECT", |
|
[132] = "CHANGE_TRAIT", |
|
[133] = "DAMAGE_EVENT", |
|
[134] = "DEATH_EVENT", |
|
[135] = "TARGET_EVENT", |
|
[136] = "ZPOINT_EVENT", |
|
[137] = "BUY_EVENT", |
|
[138] = "SELL_EVENT", |
|
[139] = "PICKUP_EVENT", |
|
[140] = "DROP_EVENT", |
|
[141] = "LOOT_EVENT", |
|
[142] = "CONNECT_EVENT", |
|
[143] = "MOVEMENT_EVENT", |
|
[144] = "GENERIC_EVENT", |
|
[145] = "SOUND_EVENT", |
|
[146] = "CHAR_CREATE_TRAITS", |
|
[147] = "STATS", |
|
[148] = "PET_COMMAND", |
|
[149] = "PET_SKILL", |
|
[150] = "CRAFT_INFO", |
|
[151] = "PETITION_REQUEST", |
|
[152] = "HEART_BEAT", |
|
[153] = "NPC_COMMAND", |
|
[154] = "MINIGAME_STARTSTOP", |
|
[155] = "MINIGAME_BOARD", |
|
[156] = "MINIGAME_UPDATE", |
|
[157] = "ENTRANCE", |
|
[158] = "GMEVENT_LIST", |
|
[159] = "GMEVENT_INFO", |
|
[160] = "SEQUENCE", |
|
[161] = "NPCRACELIST", |
|
[162] = "INTRODUCTION", |
|
[163] = "CACHEFILE", |
|
[164] = "DIALOG_MENU", |
|
[165] = "SIMPLE_STRING", |
|
[166] = "ORDEREDTEST", |
|
[167] = "GENERICCMD", |
|
[168] = "CRAFT_CANCEL", |
|
[169] = "MUSICAL_SHEET", |
|
[170] = "PLAY_SONG", |
|
[171] = "STOP_SONG", |
|
[172] = "SIMPLE_RENDER_MESH", |
|
[173] = "NPC_WORKDONE", |
|
[174] = "PATH_NETWORK", |
|
[175] = "LOCATION", |
|
[176] = "MECS_ACTIVATE", |
|
[177] = "NPC_DELETED" |
|
} |
|
|
|
local pf_msgtype = ProtoField.uint8("eulora.msgtype", "Message type", base.DEC, msgtypes) |
|
|
|
local pf_ping_id = ProtoField.uint32("eulora.ping.id", "ID", base.HEX) |
|
local pf_ping_flags = ProtoField.uint8("eulora.ping.flags", "Flags", base.HEX) |
|
|
|
local pf_auth_version = ProtoField.uint32("eulora.auth.version", "Version", base.DEC) |
|
local pf_auth_user = ProtoField.stringz("eulora.auth.user", "User name", base.ASCII) |
|
|
|
eulora.fields = { pf_msgtype, pf_ping_id, pf_ping_flags, pf_auth_version, pf_auth_user } |
|
|
|
local msgtype_field = Field.new("eulora.msgtype") |
|
local ping_id_field = Field.new("eulora.ping.id") |
|
local ping_flags_field = Field.new("eulora.ping.flags") |
|
|
|
local function getStringLength(buffer, offset) |
|
return buffer(offset):stringz():len() |
|
end |
|
|
|
local function parseString(ctx, label) |
|
local val = ctx.tvbuf:range(ctx.offs):stringz() |
|
ctx.parent:add(ctx.tvbuf:range(ctx.offs, val:len()), label, val) |
|
ctx.offs = ctx.offs + val:len() + 1 |
|
end |
|
|
|
local function dissectPacket(tvbuf,pktinfo,tree) |
|
local pktlen = tvbuf:len() |
|
if pktlen < 15 then |
|
tree:add("Packet too short"); |
|
return |
|
end |
|
|
|
local msgsize = tvbuf:range(8,4):le_uint() |
|
local pktsize = tvbuf:range(12,2):le_uint() |
|
local msgflags = tvbuf:range(14,1):uint() |
|
|
|
tree:add(tvbuf:range(0,4), string.format("Packet ID: 0x%08x", tvbuf:range(0,4):le_uint())) |
|
tree:add(tvbuf:range(4,4), "Offset:", tvbuf:range(4,4):le_uint()) |
|
tree:add(tvbuf:range(8,4), "Message Size:", msgsize) |
|
tree:add(tvbuf:range(12,2), "Packet Size:", pktsize) |
|
tree:add(tvbuf:range(14,1), string.format("Flags: 0x%02x", msgflags)) |
|
|
|
if pktlen == 15 and pktsize == 0 then |
|
pktinfo.cols.info:set("ACK packet") |
|
return |
|
end |
|
if msgsize ~= pktsize then |
|
pktinfo.cols.info:set("Fragmented packet [not supported yet]") |
|
return |
|
end |
|
if pktlen < 18 or pktlen ~= msgsize + 15 then |
|
return |
|
end |
|
if msgflags == 2 or msgflags == 3 then |
|
local offs=15 |
|
while pktlen > offs + 14 do |
|
local mpsize = tvbuf:range(offs+12,2):le_uint() |
|
local subpacket = tree:add("Subpacket") |
|
dissectPacket(tvbuf:range(offs,mpsize+15):tvb(),pktinfo,subpacket) |
|
offs = offs + 15 + mpsize |
|
end |
|
pktinfo.cols.info:set("Multipacket") |
|
return |
|
end |
|
|
|
tree:add(pf_msgtype, tvbuf:range(15,1)) |
|
|
|
local msgtype_value = tvbuf:range(15,1):uint() |
|
pktinfo.cols.info:set(msgtype_field().display) |
|
|
|
local payloadsize_value = tvbuf:range(16,2):le_uint() |
|
tree:add(tvbuf:range(16,2), "Payload size:", payloadsize_value) |
|
|
|
if payloadsize_value ~= msgsize-3 then |
|
return |
|
end |
|
|
|
if msgtype_value == 1 and payloadsize_value == 5 then |
|
local payload_tree = tree:add(tvbuf:range(18, 5), "PING Payload") |
|
payload_tree:add_le(pf_ping_id, tvbuf:range(18,4)) |
|
payload_tree:add(pf_ping_flags, tvbuf:range(22,1)) |
|
pktinfo.cols.info:append(": "..ping_id_field().display.." ["..ping_flags_field().display.."]") |
|
elseif msgtype_value == 2 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "AUTH Payload") |
|
payload_tree:add_le(pf_auth_version, tvbuf:range(18,4)) |
|
local l = getStringLength(tvbuf, 22) |
|
payload_tree:add(pf_auth_user, tvbuf:range(22, l)) |
|
local ctx = {offs = 22 + l + 1, tvbuf = tvbuf, parent = payload_tree} |
|
parseString(ctx, "Password:") |
|
parseString(ctx, "Operating System:") |
|
parseString(ctx, "Graphics Card:") |
|
parseString(ctx, "Graphics Version:") |
|
parseString(ctx, "Password256:") |
|
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz()) |
|
elseif msgtype_value == 3 and payloadsize_value == 4 then |
|
local payload_tree = tree:add(tvbuf:range(18, 4), "PREAUTH Payload") |
|
payload_tree:add(tvbuf:range(18,4), "Net version: ", tvbuf:range(18,4):le_uint()) |
|
elseif msgtype_value == 4 and payloadsize_value == 4 then |
|
local payload_tree = tree:add(tvbuf:range(18, 4), "PREAUTH_APPROVED Payload") |
|
payload_tree:add(tvbuf:range(18,4), "Client number: ", tvbuf:range(18,4):le_uint()) |
|
elseif msgtype_value == 5 and payloadsize_value > 8 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "AUTH_APPROVED Payload") |
|
payload_tree:add(tvbuf:range(18,4), "ClientValidToken: ", tvbuf:range(18,4):le_uint()) |
|
payload_tree:add(tvbuf:range(22,4), "Player ID: ", tvbuf:range(22,4):le_uint()) |
|
payload_tree:add(tvbuf:range(26,1), "Number of Characters: ", tvbuf:range(26,1):uint()) |
|
local ctx = {offs = 27, tvbuf = tvbuf, parent = payload_tree} |
|
while ctx.offs < 18 + payloadsize_value do |
|
parseString(ctx, "Value:") |
|
end |
|
elseif msgtype_value == 7 and payloadsize_value > 4 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "DISCONECT Payload") |
|
payload_tree:add(tvbuf:range(18,4), "Actor: ", tvbuf:range(18,4):le_uint()) |
|
parseString({offs = 22, tvbuf = tvbuf, parent = payload_tree}, "Reason:") |
|
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz()) |
|
elseif msgtype_value == 9 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "CHANNEL_JOIN Payload") |
|
parseString({offs = 18, tvbuf = tvbuf, parent = payload_tree}, "Channel:") |
|
pktinfo.cols.info:append(": "..tvbuf:range(18):stringz()) |
|
elseif msgtype_value == 10 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "CHANNEL_JOINED Payload") |
|
local ctx = {offs = 18, tvbuf = tvbuf, parent = payload_tree} |
|
parseString(ctx, "Channel:") |
|
payload_tree:add(tvbuf:range(ctx.offs, 2), "Channel ID:", tvbuf:range(ctx.offs, 2):le_uint()) |
|
pktinfo.cols.info:append(": "..tvbuf:range(18):stringz()) |
|
elseif msgtype_value == 13 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "USERCMD Payload") |
|
parseString({offs = 18, tvbuf = tvbuf, parent = payload_tree}, "Command:") |
|
pktinfo.cols.info:append(": "..tvbuf:range(18):stringz()) |
|
elseif msgtype_value == 14 and payloadsize_value > 4 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "SYSTEM Payload") |
|
payload_tree:add(tvbuf:range(18,4), "Type: ", tvbuf:range(18,4):le_uint()) |
|
parseString({offs = 22, tvbuf = tvbuf, parent = payload_tree}, "Message:") |
|
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz()) |
|
elseif msgtype_value == 20 and payloadsize_value > 5 then |
|
local payload_tree = tree:add(tvbuf:range(18, payloadsize_value), "USERACTION Payload") |
|
payload_tree:add(tvbuf:range(18,4), "Target: ", tvbuf:range(18,4):le_uint()) |
|
local ctx = {offs = 22, tvbuf = tvbuf, parent = payload_tree} |
|
parseString(ctx, "Action:") |
|
parseString(ctx, "Default behaviors:") |
|
pktinfo.cols.info:append(": "..tvbuf:range(22):stringz()) |
|
elseif payloadsize_value == 0 then |
|
tree:add(tvbuf:range(18, 0), "Empty Payload") |
|
else |
|
tree:add(tvbuf:range(18, payloadsize_value), "Payload") |
|
pktinfo.cols.info:append(": [details not supported yet]") |
|
end |
|
end |
|
|
|
function eulora.dissector(tvbuf,pktinfo,root) |
|
pktinfo.cols.protocol:set("Eulora") |
|
local pktlen = tvbuf:reported_length_remaining() |
|
local tree = root:add(eulora, tvbuf:range(0,pktlen)) |
|
dissectPacket(tvbuf:range(0,pktlen):tvb(),pktinfo,tree) |
|
return pktlen |
|
end |
|
|
|
DissectorTable.get("udp.port"):add(13331, eulora) |