selber beibringen musste, hier ein kleines Tutorial für die Erstellung eines
Firefox-Add-ons mit dem man die übertragenen Daten abfangen kann.
Dies kann genutzt werden, um bspw. bei Flash-Spielen die Antwort vom
Server mitzulesen. Diese Daten wiederum können Informationen enthalten,
die in den Flash-Clients nicht angezeigt werden oder die Daten sammeln,
um so seinen Fortschritt zu dokumentieren. Man kann natürlich auch die
Daten miteinander vergleichen und sich eine Mitteilung geben lassen,
welches der angebotenen Sachen am Besten ist, ohne selber im Kopf
rechnen zu müssen.
Die Möglichkeiten sind vielfältig aber um die Daten auszuwerten, sind
Programmierkenntnisse in Javascript nötig. Diese werde ich hier allerdings
nicht vermitteln, da die Daten von Spiel zu Spiel sehr unterschiedlich sind.
Allerdings gehe ich kurz darauf ein, welche Möglichkeiten es gibt, um den
Spielen ein paar Details zu entlocken.
Teil 1 - Erstellung des Add-ons
Die detailierte Anleitung ist hier zu finden:

Vorbereitung: Zuerst einmal die benötigte Software herunterladen.
- Python (2.7.x, da 3.x.x nicht unterstützt wird):

- Add-on SDK von Mozilla:

und das Add-on SDK entpacken (bspw. in C:\mozilla-addon-sdk-1.16).
Für die Entwicklung unseres Add-on legen wir ein Verzeichnis an
(bspw. C:\TestAddon) und wechseln in dieses Verzeichnis.
Als nächstes aktivieren wir das Add-on SDK mit diesem Befehl:
Code:
C:\mozilla-addon-sdk-1.16\bin\activate
Code:
cfx init
Teil 2 - Erweiterung des Add-ons für den Zugriff auf die übertragenen Daten
Um es ein wenig übersichtlicher zu machen, können wir die Verzeichnisse
doc und test löschen. Die Datei package.json enthält grundlegende Daten,
wie den Namen des Add-ons und hier können auch Einstellungen konfiguriert
werden, die man später im Browser setzt und die man im Add-on verwenden
kann aber für unser Beispiel ist das nicht notwendig.
Nun nehmen wir eine png-Grafik in der Größe 16x16 Pixel
(bspw.
)und speichern sie im Verzeichnis data. Dort kopieren wir die Grafik zweimal
und benennen die drei Dateien so um:
Code:
testaddon-ico-gray-16.png testaddon-ico-red-16.png testaddon-ico-green-16.png
)und anschließend wechseln wir eine Ebene zurück und ins Verzeichnis lib.
Dort füllen die Datei main.js mit folgendem Inhalt:
Code:
// Funktionen des SDK einbinden
var data = require("sdk/self").data;
var widgets = require("sdk/widget");
// Ueberwachungsfunktionalitaet einbinden
var observer = require("./observer");
// Icons fuer die Darstellung der verschiedenen Zustaende (gestartet, aktiviert, deaktiviert)
var initializeIcon = data.url("testaddon-ico-gray-16.png");
var deactivatedIcon = data.url("testaddon-ico-red-16.png");
var activatedIcon = data.url("testaddon-ico-green-16.png");
var active = false;
// Aktivierung der Ueberwachung
function activate() {
if(!active) {
// Der Ueberwachungsfunktion mitteilen, dass die Daten mitgelesen werden sollen
observer.httpRequestObserver.register();
// Das Icon fuer aktiv anzeigen
widgetTestaddon.contentURL = activatedIcon;
active = true;
}
}
// Deaktivierung der Ueberwachung
function deactivate() {
if(active) {
// Der Ueberwachungsfunktion mitteilen, dass keine Daten mehr mitgelesen werden
observer.httpRequestObserver.unregister();
// Das Icon fuer inaktiv anzeigen
widgetTestaddon.contentURL = deactivatedIcon;
active = false;
}
}
// Darstellung des Add-ons im Add-on Bereich von Firefox
var widgetTestaddon = widgets.Widget({
id: "widgetTestaddon",
label: "Test-Addon",
contentURL: initializeIcon,
// Ein- oder Ausschalten des Add-ons durch klick auf das Icon
onClick: function (event) {
if(active) {
deactivate();
} else {
activate();
}
}
});
exports.main = function (options, callbacks) {
};
// Wenn das Add-on entfernt wird, die Ueberwachung beenden
exports.onUnload = function (reason) {
deactivate();
};
Die Zeile var observer = require("./observer"); verweist auf die Datei observer.js,
die wir als nächstes im Verzeichnis lib anlegen werden und zwar mit folgendem Inhalt:
Code:
const {Cc, Ci, Cr} = require("chrome");
var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
// Script, mit dem die Daten zur Anzeige-Aufbereitung geschickt werden
var sender = require("./sender");
// Damit nicht jede Antwort vom Server mitgelesen werden muss, sollten hier Filter definiert werden:
// Filter fuer URLs die bspw. http://server.de/api.php lauten koennten, um Antworten vom Server abzugreifen
var apiUrl = "server.de/api.php";
// Filter fuer URLs die bspw. http://server.de/test_resources/de_textfile.txt lauten koennten, um Texte abzugreifen
var txtUrl = "server.de/test_resources/de_";
// Filter fuer URLs die bspw. http://images.server.de/supergame/battleimages/armor.png lauten koennten, um Grafikdateien abzugreifen
var gfxUrl = "images.server.de/supergame/battleimages";
// Fuer den Test greifen wir alle(!) Antworten ab, die bei Anfragen an .php zurueck kommen.
var testUrl = ".php";
// Anlegen des Observers, der die uebertragenen Daten mitliest
var httpRequestObserver = {
isRegistered : false,
observe: function (subject, topic, data) {
// Pruefen, ob uns das Topic (Anfrage an den Server / Antwort vom Server / Antwort vom Browser-Cache, ...) interessiert
if (this.getTopicType(topic) == 1) {
var httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
var url = httpChannel.originalURI.spec;
// Pruefen, welche URL aufgerufen wurde und ob uns die Antwort interessiert
var reqType = this.getReqType(url);
if(reqType > 0) {
// Uebertragungsmethode pruefen (bei GET interessiert uns nur die URL,
// an die die Anfrage geschickt wurde und bei POST auch die Daten)
if(httpChannel.requestMethod == "GET") {
// Erzeugen unseres Listeners, um die Uebertragung abzuhoeren
var newListener = new TestListener(reqType, url, 0, "");
subject.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = subject.setNewListener(newListener);
} else if(httpChannel.requestMethod == "POST") {
var channel = httpChannel.QueryInterface(Ci.nsIUploadChannel).uploadStream;
channel.QueryInterface(Ci.nsISeekableStream).seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(channel);
// Auslesen der an den Server gesendeten Daten
var postBytes = stream.readByteArray(stream.available());
var postStr = decodeURIComponent(String.fromCharCode.apply(null, postBytes));
// Pruefen, welcher Inhalt gesendet wurde und ob uns die Antwort interessiert
var postType = this.getPostType(postStr);
if(postType > 0) {
// Erzeugen unseres Listeners, um die Uebertragung abzuhoeren
var newListener = new TestListener(reqType, url, postType, postStr);
subject.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = subject.setNewListener(newListener);
}
}
}
} else if (this.getTopicType(topic) == -1) {
// Wenn der Browser beendet wird, die Beobachtung beenden
this.unregister();
return;
}
},
// Abfragen des Topics und zurueckgeben einer ID, die dem Topic entspricht.
// Im Beispiel ist -1 bei Beendigung des Browsers und 1 bei Antworten vom Server (response),
// Antworten aus dem Browser-Cache (cached-response) oder gemischte Antworten (merged-response)
// bzw. 0 fuer den Rest
getTopicType: function(topic) {
if(topic == "http-on-examine-response" || topic == "http-on-examine-merged-response" || topic == "http-on-examine-cached-response") {
return 1;
} else if(topic == "quit-application") {
return -1;
}
return 0;
},
// Abfragen der verschiedenen URLs und zurueckgeben einer ID, die der URL entspricht.
// Im Beispiel ist dies 1 fuer API-URLs und 2 fuer Resource-URLs bzw. 0 fuer den Rest.
getReqType: function(url) {
if(url.indexOf(testUrl) > -1 || url.indexOf(apiUrl) > -1) {
return 1;
} else if(url.indexOf(txtUrl) > -1 || url.indexOf(gfxUrl) > -1) {
return 2;
}
return 0;
},
// Abfragen der Inhalte der Anfrage an den Server und zurueckgeben einer ID, die dem Inhalt entspricht.
// Im Beispiel ist dies 0 fuer den Rest.
getPostType: function(postStr) {
return 0;
},
// Registrieren unseres Observers, um auf das Beenden des Browsers und auf Antworten vom Server/Cache zu reagieren
register: function() {
if(this.isRegistered) {
console.log("already registered");
} else {
observerService.addObserver(this, "quit-application", false);
observerService.addObserver(this, "http-on-examine-response", false);
observerService.addObserver(this, "http-on-examine-merged-response", false);
observerService.addObserver(this, "http-on-examine-cached-response", false);
this.isRegistered = true;
}
},
// Zurueckziehen unseres Observers
unregister: function() {
if(this.isRegistered) {
observerService.removeObserver(this, "quit-application");
observerService.removeObserver(this, "http-on-examine-response");
observerService.removeObserver(this, "http-on-examine-merged-response");
observerService.removeObserver(this, "http-on-examine-cached-response");
this.isRegistered = false;
} else {
console.log("already unregistered");
}
},
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsIObserver) || aIID.equals(Ci.nsISupports)) {
return this;
}
throw Cr.NS_NOINTERFACE;
}
}
exports.httpRequestObserver = httpRequestObserver;
// Der Daten-Container fuer die uebertragenen Daten
function TestListener(reqType, url, postType, postStr) {
this.requestedType = reqType;
this.requestedUrl = url;
this.postedType = postType;
this.postedData = postStr;
this.receivedData = [];
}
TestListener.prototype = {
originalListener: null,
requestedType: 0, // ID der abgerufenen Daten (siehe getReqType: function(url))
requestedUrl: "", // Aufgerufene URL
postedType: 0, // ID der gesendeten Daten (siehe getPostType: function(postStr))
postedData: "", // Daten, die an den Server gesendet wurden
receivedData: [], // Daten, die vom Server empfangen wurden
// Wenn Daten-Pakete vom Server ankommen, wird diese Methode aufgerufen
onDataAvailable: function(request, context, inputStream, offset, count) {
if(this.requestedType == 1) {
// Wenn die Daten vom Typ 1 (Server-Daten) sind, werden diese aufbereitet
var binaryInputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
binaryInputStream.setInputStream(inputStream);
var storageStream = Cc["@mozilla.org/storagestream;1"].createInstance(Ci.nsIStorageStream);
// 8192 entspricht der Segment-Groesse in Bytes und count ist die maximale Groesse des Datenstroms in Bytes
storageStream.init(8192, count, null);
var binaryOutputStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// Ankommende Daten 1:1 kopieren
var data = binaryInputStream.readBytes(count);
// Daten an den Flash-Client weiterreichen
binaryOutputStream.writeBytes(data, count);
this.originalListener.onDataAvailable(request, context, storageStream.newInputStream(0), offset, count);
// Eine Kopie der Daten behalten und zu den bereits gesammelten Pakten hinzufuegen
this.receivedData.push(data);
} else {
// Bei Typ 2 (Texte oder Bilder) interessiert uns der Inhalt nicht
// Daten an den Flash-Client weiterreichen
this.originalListener.onDataAvailable(request, context, inputStream, offset, count);
}
},
// Wenn die Datenuebertragung beginnt, wird diese Methode aufgerufen
onStartRequest: function (request, context) {
// Signal fuer den Beginn der Uebertragung weitergeben
this.originalListener.onStartRequest(request, context);
},
// Wenn die Datenuebertragung endet, wird diese Methode aufgerufen
onStopRequest: function (request, context, statusCode) {
// Signal fuer das Ende der Uebertragung weitergeben
this.originalListener.onStopRequest(request, context, statusCode);
// Wenn wir alle Pakete haben, die gesammelten Daten zur Anzeige-Aufbereitung schicken
sender.scanData(this.requestedType, this.requestedUrl, this.postedType, this.postedData, this.receivedData.join(''));
},
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) || aIID.equals(Ci.nsISupports)) {
return this;
}
throw Cr.NS_NOINTERFACE;
}
}
die wir als nächstes im Verzeichnis lib anlegen werden und zwar mit folgendem Inhalt:
Code:
// Funktionen des SDK einbinden
var data = require("sdk/self").data;
var tabs = require('sdk/tabs');
var myTab = -1;
var worker = undefined;
// Wenn der Observer die Daten erhalten hat, kommen wir hier an
function scanData(reqType, url, postType, post, daten) {
// Je nach Inhalt (Daten oder Resourcen) senden wir einen anderen Befehl
if(reqType == 1) {
sendData("zeigeDaten", [daten, url]);
} else if(reqType == 2) {
sendData("zeigeUrl", [reqType, url]);
}
}
exports.scanData = scanData;
// Dies Funktion dient zum Senden der Befehle
function sendData(action, daten) {
// Wenn der Ausgabe-Tab noch nicht geoeffnet ist, oeffnen
if(myTab == -1) {
myTab = 0;
tabs.open({
url: data.url("ausgabe.html"),
inBackground: true,
isPinned: false,
// Im Tab einen Worker erzeugen, der auf Befehle und Daten wartet
onReady: function onOpen(tab) {
worker = tab.attach({
// Es werden JavaScripte eingebunden, die miteinander arbeiten sollen,
// da im anzeige.js bspw. JQuery verwendet wird.
contentScriptFile: [
data.url("jquery-2.1.1.min.js"),
data.url("anzeige.js")
]
});
// Der Befehl und die Daten werden an den Worker geschickt
worker.port.emit(action, daten);
},
// Wenn der Tab geschlossen wird, den Worker zuruecksetzen
onClose: function onClose(tab) {
myTab = -1;
worker = undefined;
}
});
} else if(worker != undefined) {
// Der Befehl und die Daten werden an den Worker geschickt
worker.port.emit(action, daten);
}
}
Damit ist der Ordner lib vollständig und wir wechseln zurück in den Ordner data.
Die Zeilen data.url("ausgabe.html") und data.url("anzeige.js") sagen bereits, welche
Dateien hier anzulegen sind. Beginnen wir mit ausgabe.html und folgendem Inhalt:
Code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta charset="utf-8"> <title>Test</title> </head> <body> <h1>Test</h1> <div id="daten"></div> <div id="urls"></div> </body> </html>
Code:
self.port.on("zeigeDaten", function(daten) {
$("#daten").html(daten[0] + ":<br />" + daten[1]);
});
var urls = [];
self.port.on("zeigeUrl", function(daten) {
urls.push(daten[0]);
$("#urls").html(
$("<ul />", {
"html": "<li>" + urls.join("</li><li>") + "</li>"
})
);
});
Div-Sektionen mit IDs an, um diese aus dem JavaScript heraus mit Inhalten zu füllen.
Das JavaScript enthält mit self.port.on einen Empfänger für Befehle aus der Datei
sender.js und zwar im gleichen Format, wie wir dort sendData(action, daten) aufrufen.
Damit ist das Addon auch schon fertig. Falls das SDK beendet wurde, mit dem Befehl
Code:
D:\mozilla-addon-sdk-1.16\bin\activate
Code:
cfx run
Dort auf das Icon des Add-ons klicken und eine Seite mit php (bspw.
aufrufen.Nun müsste ein neuer Tab aufgehen, in dem die aufgerufene URL und die empfangenen
Daten angezeigt werden. Wenn das funktioniert, kann mit dem Befehl
Code:
cfx xpi
ist das Add-on im Browser installiert.
Nun kann das Add-on beliebig erweitert und für die entsprechenden Browser-Games
angepasst werden.
Teil 3 - Weitere Tools
Hier noch ein paar Links zu Hilfmitteln, um den Flash-Spielen ein wenig unter die
Haube zu schauen und sich Inspiration zu holen, wie man sein Add-on aufpeppen kann.






