netscripts - themeldingwars/Documentation GitHub Wiki

A bunch of small helper scripts for reversing the network proto.

Wireshark Filter

Add require ("Path to this file.lua") to your wiresharks init.lua file and use firefall || firefall_gss to filter to just Firefall traffic

FirefallMatrix_Proto = Proto("Firefall","Firefall Matrix Protocol")
FirefallGSS_Proto = Proto("Firefall_GSS","Firefall GSS Protocol")

local vars = 
{
	gssPort = 0,
	packetIds = {},
	packetTypeCount = {}
}

-- Decode the matrix proto
function FirefallMatrix_Proto.dissector(buffer,pinfo,tree)
    pinfo.cols.protocol = "Firefall Matrix"
    local subtree = tree:add(FirefallMatrix_Proto,buffer(),"Firefall Matrix")
	subtree:add(buffer(0,buffer:len()),"RawData: " .. buffer(0,buffer:len()))

	local id = buffer(0, 4)
	local typeStr = buffer(4, 4)
	local typeStr_string = typeStr:string()

	subtree:add(id,"ID: " .. id:uint())
	subtree:add(typeStr,"Type: " .. typeStr_string)

	if typeStr_string == "POKE" then
		--local protcolVer = buffer(10, 2) ? why??
		local protcolVer = buffer(8, 4)
		subtree:add(protcolVer,"Protocol Version: " .. protcolVer:uint())
	elseif typeStr_string == "HEHE" then
		local socketID = buffer(8, 4)
		subtree:add(socketID,"Socket ID: " .. socketID:uint())
	elseif typeStr_string == "KISS" then
		local socketID = buffer(8, 4)
		subtree:add(socketID,"Socket ID: " .. socketID:uint())

		local streamingProtocol = buffer(12, 2)
		subtree:add(streamingProtocol,"Streaming Protcol: " .. streamingProtocol:uint())
	elseif typeStr_string == "HUGG" then
		local sequenceStart = buffer(8, 2)
		local gameServerPort = buffer(10, 2)

		subtree:add(sequenceStart,"Sequence Start: " .. sequenceStart:uint())
		subtree:add(gameServerPort,"Game Server Port: " .. gameServerPort:uint())

		-- Set the port that the gss info will be on
		vars.gssPort = gameServerPort:uint()
		udp_table = DissectorTable.get("udp.port")
		udp_table:add(vars.gssPort, FirefallGSS_Proto) 
	end
end

udp_table = DissectorTable.get("udp.port")
udp_table:add(25000, FirefallMatrix_Proto) -- 25000 is the default matrix port for firefall

-- GSS Packet handlers
local GSS_PacketHandlers = {}
local GssPckOff = 11

-- Decode the matrix proto
function FirefallGSS_Proto.dissector(buffer,pinfo,tree)
    pinfo.cols.protocol = "Firefall GSS"
    local subtree = tree:add(FirefallGSS_Proto,buffer(),"Firefall GSS")
    subtree:add(buffer(0,buffer:len()),"RawData: " .. buffer(0,buffer:len()))

	local socketID = buffer(0, 4)
	local flags = buffer(4, 1) -- Guessing
	local packetID = buffer(5, 1) -- Im not sure on this any more, there seems to be way way too many
	local pckIdx = buffer(6, 2) -- Ok this matchs between a message and a reply
	local unknown = buffer(8, 2)

	--[[if (buffer(4, 1):uint() ~= 0) then
		report_failure("Packet ID padding was not 0, was: "..buffer(4, 1))
	end]]

	subtree:add(socketID,"Socket ID: " .. socketID:uint())
	subtree:add(packetID,"Packet ID: " .. packetID:uint())
	--subtree:add(buffer(5, 1),"Packet ID(byte): " .. buffer(5, 1):uint())
	subtree:add(flags,"Flags: " .. flags:uint())
	subtree:add(pckIdx,"Pck Idx: " .. pckIdx:uint())
	subtree:add(unknown,"Unknown: " .. unknown:uint())

	pinfo.cols.protocol:append(" ("..tostring(packetID:uint())..")")

	-- pass off to a packet handler if we have one
	local id = buffer(5, 1):uint()
	if GSS_PacketHandlers[id] then
		GSS_PacketHandlers[id](buffer, pinfo, subtree)
	end

	if not vars.packetTypeCount[id] then
		table.insert(vars.packetIds, id)
	end

	-- stats
	local count = 0
	if vars.packetTypeCount[id] and vars.packetTypeCount[id].count then
		count = vars.packetTypeCount[id].count
	end
	count = count + 1

	vars.packetTypeCount[id] = 
	{
		count = count,
		size = buffer:len()
	}
end

GSS_PacketHandlers[11] = function(buffer, pinfo, tree)
	local subtree = tree:add(FirefallGSS_Proto, buffer(), "Connection Accepct")

	local unknown = buffer(GssPckOff, 4)
	subtree:add(unknown,"Unknown: " .. unknown:uint())
end

-- Tools and stuff
local function ShowPacketStats()
	local splash = TextWindow.new("Firefall Packet Stats :D");
	splash:set("Firefall packet stats\n")

	table.sort(vars.packetIds, function( a,b ) return a < b end)
	splash:append("Number of packets of type:\n")
	splash:append("Total packet types: ".. #vars.packetIds .."\n")
	splash:append("-------------------------\n")
	splash:append("| PacketID\t | Count\t |\n")
	splash:append("-------------------------\n")
	for i in ipairs(vars.packetIds) do
		info(tostring(vars.packetTypeCount[vars.packetIds[i]]))
		local count = vars.packetTypeCount[vars.packetIds[i]].count
		local size = vars.packetTypeCount[vars.packetIds[i]].size
		splash:append("| ".. vars.packetIds[i] .."\t | ".. count .."\t | ".. size.."\n")
	end
	splash:append("-------------------------\n")
end

register_menu("Firefall Packet Stats", ShowPacketStats, MENU_TOOLS_UNSORTED)

Message flow

This node.js script will parse your console log and create a packet flow report, may be useful to cross ref with wire shark dumps.

Add this to your ini

[Debug]
LogLevel-NetLib = "debug";
LogLevel-Network = "debug";
MatrixMessages = true;

and then run this after your play session

// read the console log and output a textfile with a list of all the packets send/revced and their timestamps
// might be handy to compare with wiresahrk dumps
// Add the following to your Firefall.ini to enable logging of these

/*
[Debug]
LogLevel-NetLib = "debug";
LogLevel-Network = "debug";
MatrixMessages = true;
*/

var AsciiTable = require('ascii-table')

var logFilePath = process.env.LOCALAPPDATA + "/Red 5 Studios/Firefall/console.log";

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream(logFilePath)
});

var outFilePath = process.argv[2];
var writer = require("fs").createWriteStream(outFilePath || "Packet Flow.txt");

var RX_Line = /(\d*:\d*) RAM:(\d*)MB FPS:(\d*) (\w*) *(\w*) *(.*)/;
var RX_Matrix = /MATRIX *- *Sent *(.*)/;
var RX_Recv = /received *(.*)/;

var packetIdCounts = {
	sent: {},
	recv: {}
};

writer.write("-------------------------------------\r\n");
writer.write("| Time\t|    Direction     | ID \t|\r\n");
writer.write("-------------------------------------\r\n");

lineReader.on('line', function (line) {
	var m;

	if ((m = RX_Line.exec(line)) !== null) {
		var category = m[5];

		if (category == "NETWORK") {
			var match = RX_Recv.exec(m[6]);
			if (match != null) {
				var msgId = match[1];
				writer.write("| {0}\t| Server -> Client | {1}\t| \u2666\r\n".format(m[1] , msgId));

				var count = packetIdCounts.recv[msgId]
				if (count != null) {
					count++;
				}
				else {
					count = 1;
				}
				packetIdCounts.recv[msgId] = count
			}
		}
		else if (category == "GAME") {
			var match = RX_Matrix.exec(m[6]);
			if (match != null) {
				var msgId = match[1];
				writer.write("| {0}\t| Client -> Server | {1}\t|\r\n".format(m[1] , msgId));

				var count = packetIdCounts.sent[msgId]
				if (count != null) {
					count++;
				}
				else {
					count = 1;
				}
				packetIdCounts.sent[msgId] = count
			}
		}
	}
});

lineReader.on("close", function() {
	writer.write("-------------------------------------\r\n\r\n\r\n");

	var table = new AsciiTable('Sent');
	table.setHeading('ID', 'Count');
	Object.keys(packetIdCounts.sent).forEach(function(key) {
		var value = packetIdCounts.sent[key];
		table.addRow(key, value);
	});
	writer.write(table.toString()+"\r\n");

	table = new AsciiTable('Recived');
	table.setHeading('ID', 'Count');
	Object.keys(packetIdCounts.recv).forEach(function(key) {
		var value = packetIdCounts.recv[key];
		table.addRow(key, value);
	});
	writer.write(table.toString()+"\r\n");

	writer.end();

	console.log("Done :>");
});

// Util
//------------------------------

// http://stackoverflow.com/a/4673436
if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) { 
      return typeof args[number] != 'undefined'
        ? args[number]
        : match
      ;
    });
  };
}