In diesem Artikel werde ich beschreiben, wie der NES-Emulator ferngesteuert und ein Server zum Senden von Befehlen an ihn ferngesteuert wird.

Warum wird das benötigt?
Mit einigen Emulatoren verschiedener Spielekonsolen, einschließlich
Fceux , können Sie benutzerdefinierte Skripte auf Lua schreiben und ausführen. Aber Lua ist eine schlechte Sprache, um ernsthafte Programme zu schreiben. Es ist eher eine Sprache zum Aufrufen von Funktionen, die in C geschrieben sind. Die Autoren von Emulatoren verwenden es nur wegen der Leichtigkeit und einfachen Einbettung. Eine genaue Emulation erfordert viele Prozessorressourcen, und die frühere Emulationsgeschwindigkeit war eines der Hauptziele der Autoren, und wenn sie sich an die Möglichkeit von Skriptaktionen erinnerten, war dies nicht an erster Stelle.
Jetzt reicht die Leistung eines durchschnittlichen Prozessors für die Emulation von NES aus. Warum nicht leistungsstarke Skriptsprachen wie Python oder JavaScript in Emulatoren verwenden?
Leider kann keiner der beliebten NES-Emulatoren diese oder andere Sprachen verwenden. Ich habe nur ein wenig bekanntes
Nintaco- Projekt gefunden, das ebenfalls auf dem Fceux-Kernel basiert und aus irgendeinem Grund in Java neu geschrieben wurde. Dann habe ich beschlossen, die Möglichkeit hinzuzufügen, Skripte in Python zu schreiben, um den Emulator selbst zu steuern.
Mein Ergebnis ist der Proof-of-Concept der Fähigkeit, den Emulator zu steuern. Er gibt nicht vor, schnell oder zuverlässig zu sein, aber er funktioniert. Ich habe es selbst gemacht, aber da die Frage, wie der Emulator mithilfe von Skripten gesteuert werden kann,
häufig genug ist , habe ich den Quellcode auf den
Github gestellt .
Wie funktioniert es?
Auf der Seite des Emulators
Der Fceux-Emulator enthält
bereits mehrere Lua-Bibliotheken
in Form von kompiliertem Code . Einer von ihnen ist
LuaSocket . Es ist schlecht dokumentiert, aber ich habe es geschafft, ein Beispiel
für Arbeitscode in der
Sammlung von Xkeeper0- Skripten zu finden . Er benutzte Sockel, um den Emulator über Mirc zu steuern. Der Code, der den TCP-Socket öffnet, lautet:
function connect(address, port, laddress, lport) local sock, err = socket.tcp() if not sock then return nil, err end if laddress then local res, err = sock:bind(laddress, lport, -1) if not res then return nil, err end end local res, err = sock:connect(address, port) if not res then return nil, err end return sock end sock2, err2 = connect("127.0.0.1", 81) sock2:settimeout(0)
Dies ist ein Low-Level-Socket, der Daten mit 1 Byte empfängt und sendet.
Im Fceux-Emulator sieht die Hauptschleife des Lua-Skripts folgendermaßen aus:
function main() while true do
Eine Überprüfung der Daten aus dem Socket:
function passiveUpdate() local message, err, part = sock2:receive("*all") if not message then message = part end if message and string.len(message)>0 then
Der Code ist recht einfach: Daten werden aus dem Socket gelesen, und wenn der nächste Befehl erkannt wird, werden sie analysiert und ausgeführt. Das Parsen und Ausführen wird mithilfe von
Coroutine (Coroutinen) organisiert - dies ist ein leistungsstarkes Konzept der Lua-Sprache zum Anhalten und Fortsetzen der Codeausführung.
Und noch etwas Wichtiges an Lua-Skripten in Fceux - die Emulation kann vorübergehend aus dem Skript gestoppt werden. Wie organisiere ich die fortgesetzte Ausführung von Lua-Code und führe ihn mit einem vom Socket empfangenen Befehl erneut aus? Dies wäre nicht möglich, aber es gibt eine schlecht dokumentierte Möglichkeit, Lua-Code aufzurufen, selbst wenn die Emulation gestoppt ist (danke
feos für das
Zeigen darauf):
gui.register(passiveUpdate)
Mit dieser Funktion können Sie die Emulation in
passiveUpdate stoppen und fortsetzen.
Auf diese Weise können Sie die Installation von Haltepunkten des Emulators über einen Socket organisieren.
Serverseitiger Befehl
Ich verwende ein sehr einfaches JSON-basiertes RPC-Textprotokoll. Der Server serialisiert den Funktionsnamen und die Argumente in eine JSON-Zeichenfolge und sendet sie über den Socket. Ferner wird die Ausführung des Codes gestoppt, bis der Emulator mit einer Zeile antwortet, um den Befehl abzuschließen. Die Antwort enthält die Felder "
FUNCTIONNAME_finished " und das Ergebnis der Funktion.
Die Idee ist in der
syncCall- Klasse implementiert:
class syncCall: @classmethod def waitUntil(cls, messageName): """cycle for reading data from socket until needed message was read from it. All other messages will added in message queue""" while True: cmd = messages.parseMessages(asyncCall.waitAnswer(), [messageName])
Mit dieser Klasse können Fceux-Emulator-Lua-Methoden in Python-Klassen eingeschlossen werden:
class emu: @classmethod def poweron(cls): return syncCall.call("emu.poweron") @classmethod def pause(cls): return syncCall.call("emu.pause") @classmethod def unpause(cls): return syncCall.call("emu.unpause") @classmethod def message(cls, str): return syncCall.call("emu.message", str) @classmethod def softreset(cls): return syncCall.call("emu.softreset") @classmethod def speedmode(cls, str): return syncCall.call("emu.speedmode", str)
Und dann wörtlich genannt wie von Lua:
Rückrufmethoden
In Lua können Sie Rückrufe registrieren - Funktionen, die aufgerufen werden, wenn eine bestimmte Bedingung erfüllt ist. Wir können dieses Verhalten mit dem folgenden Trick auf den Server in Python portieren. Zuerst speichern wir die Kennung der in Python geschriebenen Rückruffunktion und übergeben sie an den Lua-Code:
class callbacks: functions = {} callbackList = [ "emu.registerbefore_callback", "emu.registerafter_callback", "memory.registerexecute_callback", "memory.registerwrite_callback", ] @classmethod def registerfunction(cls, func): if func == None: return 0 hfunc = hash(func) callbacks.functions[hfunc] = func return hfunc @classmethod def error(cls, e): emu.message("Python error: " + str(e)) @classmethod def checkAllCallbacks(cls, cmd):
Lua-Code speichert auch diese Kennung und registriert einen regulären Lua-Rückruf, der die Kontrolle auf Python-Code überträgt. Als Nächstes wird im Python-Code ein separater Thread erstellt, der nur überprüft, ob der Rückrufbefehl von Lua nicht akzeptiert wurde:
def callbacksThread(): cycle = 0 while True: cycle += 1 try: cmd = messages.parseMessages(asyncCall.waitAnswer(), callbacks.callbackList) if cmd:
Der letzte Schritt besteht darin, dass nach Ausführung des Python-Rückrufs die Steuerung mit dem
Befehl "
CALLBACKNAME_finished " an Lua zurückgegeben wird, um den Emulator darüber zu informieren, dass der Rückruf beendet ist.
So führen Sie ein Beispiel aus
Das ist alles, Sie können Befehle vom Jupyter-Laptop im Browser direkt an den Fceux-Emulator senden.
Sie können alle Zeilen des Beispiel-Laptops nacheinander ausführen und das Ergebnis der Ausführung im Emulator beobachten.
Vollständiges Beispiel:
https://github.com/spiiin/fceux_luaserver/blob/master/FceuxPythonServer.py.ipynbEs enthält einfache Funktionen wie das Lesen des Speichers:

Komplexere Rückrufbeispiele:

Und ein Skript für ein bestimmtes Spiel, mit dem Sie Feinde von
Super Mario Bros. bewegen können
. mit der Maus:

Laptop Run Video:
Einschränkungen und Anwendungen
Das Skript hat keinen Schutz vor Narren und ist nicht für die Ausführungsgeschwindigkeit optimiert. Es ist besser, ein binäres RPC-Protokoll anstelle eines Textprotokolls und von Gruppennachrichten zu verwenden, aber meine Implementierung erfordert keine Kompilierung. Das Skript kann auf meinem Laptop 500-1000 Mal pro Sekunde Ausführungskontexte von Lua zu Python und zurück wechseln. Dies ist für fast jede Anwendung ausreichend, außer für bestimmte Fälle des pixelweisen oder zeilenweisen Debuggens des Videoprozessors. Fceux lässt solche Vorgänge von Lua jedoch immer noch nicht zu, sodass dies keine Rolle spielt.
Mögliche Anwendungsideen:
- Als Beispiel für die Implementierung einer solchen Steuerung für andere Emulatoren und Sprachen
- Spielforschung
- Hinzufügen von Cheats oder Funktionen zum Organisieren von TAS-Passagen
- Fügen Sie Daten und Code in Spiele ein oder extrahieren Sie sie
- Verbesserung der Funktionen von Emulatoren - Schreiben von Debuggern, Skripten zum Aufzeichnen und Anzeigen von exemplarischen Vorgehensweisen, Skriptbibliotheken und Spieleditoren
- Netzwerkspiel, Spielsteuerung über mobile Geräte, Remote-Dienste, Joypads oder andere Steuergeräte, Speichern und Patches in Cloud-Diensten
- Emulatorübergreifende Funktionen
- Verwendung von Python oder anderen Sprachbibliotheken zur Datenanalyse und Spielsteuerung (Erstellen von Bots)
Technologie-Stack
Ich habe verwendet:
Fceux -
www.fceux.com/web/home.htmlDies ist ein klassischer NES-Emulator, den die meisten Leute verwenden. Es wurde lange Zeit nicht aktualisiert und bietet nicht die besten Funktionen, bleibt jedoch der Standardemulator für viele Romhacker. Ich habe mich auch dafür entschieden, weil die Lua-Sockelunterstützung integriert ist und es nicht erforderlich ist, sie selbst anzuschließen.
Json.lua -
github.com/spiiin/json.luaDies ist eine JSON-Implementierung in reinem Lua. Ich habe es gewählt, weil ich ein Beispiel erstellen wollte, für das keine Codekompilierung erforderlich ist. Aber ich musste die Bibliothek immer noch teilen, da einige der in Fceux integrierten Bibliotheken die Bibliotheksfunktion
überlasteten und die Serialisierung brachen (meine abgelehnte
Pool-Anfrage an den Autor der ursprünglichen Bibliothek).
Python 3 -
www.python.orgDer Fceux Lua-Server öffnet den TCP-Socket und wartet auf die von ihm empfangenen Befehle. Ein Server, der Befehle an den Emulator sendet, kann in einer beliebigen Sprache implementiert werden. Ich habe Python wegen seiner Philosophie „Batterie enthalten“ ausgewählt - die meisten Module sind in der Standardbibliothek enthalten (einschließlich der Arbeit mit Sockets und JSON). Python kennt auch die Bibliothek für die Arbeit mit neuronalen Netzen, und ich möchte versuchen, sie zum Erstellen von Bots in NES-Spielen zu verwenden.
Jupyter Notebook -
jupyter.orgJupyter Notebook ist eine sehr coole Umgebung für die interaktive Ausführung von Python-Code. Damit können Sie Befehle in einem Tabellenkalkulationseditor im Browser schreiben und ausführen. Es ist auch gut, um vorzeigbare Beispiele zu erstellen.
Dexpot -
www.dexpot.deIch habe diesen virtuellen Desktop-Manager verwendet, um das Emulatorfenster über andere zu andocken. Dies ist sehr praktisch, wenn Sie den Server im Vollbildmodus bereitstellen, um Änderungen im Emulatorfenster sofort zu verfolgen. Mit nativen Windows-Tools können Sie das Andocken von Fenstern nicht über anderen organisieren.
Referenzen
Eigentlich
das Projekt-Repository .
Nintaco - Java NES Emulator mit
FernverwaltungXkeeper0 Emu-Lua-Sammlung - eine Sammlung verschiedener Lua-Skripte
Mesen ist ein moderner NES-Emulator in C # mit leistungsstarken Lua-Skriptfunktionen. Bisher ohne Sockelunterstützung und Fernbedienung.
CadEditor ist mein Projekt eines universellen Level-Editors für NES und andere Plattformen sowie leistungsstarker Tools für die Erforschung von Spielen. Ich benutze das im Beitrag beschriebene Skript und den Server, um die Spiele zu erkunden und sie dem Editor hinzuzufügen.
Ich würde mich über Feedback, Tests und Versuche freuen, das Skript zu verwenden.