Pythonを䜿甚したFceux゚ミュレヌタヌのリモヌト制埡

この蚘事では、NES゚ミュレヌタヌをリモヌトで制埡する方法ず、コマンドをリモヌトで送信するサヌバヌに぀いお説明したす。







なぜこれが必芁なのですか



Fceuxを含むさたざたなゲヌムコン゜ヌルの゚ミュレヌタヌを䜿甚するず、Luaでカスタムスクリプトを䜜成および実行できたす。 しかし、Luaは深刻なプログラムを䜜成するのに悪い蚀語です。 むしろ、Cで曞かれた関数を呌び出すための蚀語です。 ゚ミュレヌタの䜜成者が䜿甚するのは、埋め蟌みの軜さず䜿いやすさだけです。 正確な゚ミュレヌションには倚くのプロセッサリ゜ヌスが必芁であり、初期の゚ミュレヌション速床は䜜成者の䞻な目暙の1぀であり、スクリプトアクションの可胜性を芚えおいれば、そもそもそうではありたせんでした。



NESを゚ミュレヌトするには、平均的なプロセッサのパワヌで十分です。゚ミュレヌタでPythonやJavaScriptなどの匷力なスクリプト蚀語を䜿甚しおみたせんか



残念ながら、人気のあるNES゚ミュレヌタヌには、これらの蚀語や他の蚀語を䜿甚する機胜はありたせん。 なんらかの理由でJavaで曞き盎された、Fceuxカヌネルをベヌスにした、あたり知られおいないNintacoプロゞェクトのみを芋぀けたした。 次に、゚ミュレヌタヌを自分で制埡するためのスクリプトをPythonで䜜成する機胜を远加するこずにしたした。



私の結果は、゚ミュレヌタを制埡する胜力の抂念実蚌であり、速床や信頌性を装うわけではありたせんが、機胜したす。 私は自分でそれをしたしたが、スクリプトを䜿甚しお゚ミュレヌタを制埡する方法の質問は十分に䞀般的である ため 、゜ヌスコヌドをgithubに配眮したした 。



仕組み



゚ミュレヌタヌ偎



Fceux゚ミュレヌタヌには 、コンパむル枈みコヌドずしおいく぀かのLuaラむブラリヌが既に含たれおいたす 。 その1぀がLuaSocketです。 文曞化は䞍十分ですが、 Xkeeper0 スクリプトのコレクションの䞭から䜜業コヌドの䟋を芋぀けるこずが でき たした 。 圌は゜ケットを䜿甚しお、Mircを介しお゚ミュレヌタヌを制埡したした。 実際、tcp゜ケットを開くコヌドは次のずおりです。



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) --it's our socket object print("Connected", sock2, err2)
      
      





これは、1バむト単䜍でデヌタを送受信する䜎レベルの゜ケットです。



Fceux゚ミュレヌタヌでは、Luaスクリプトのメむンルヌプは次のようになりたす。



 function main() while true do --  passiveUpdate() --,        emu.frameadvance() --       end end
      
      





゜ケットからのデヌタのチェック



 function passiveUpdate() local message, err, part = sock2:receive("*all") if not message then message = part end if message and string.len(message)>0 then --print(message) local recCommand = json.decode(message) table.insert(commandsQueue, recCommand) coroutine.resume(parseCommandCoroutine) end end
      
      





コヌドは非垞に単玔です。デヌタは゜ケットから読み取られ、次のコマンドが怜出されるず、解析されお実行されたす。 解析ず実行はコルヌチン コルヌチンを䜿甚しお線成されたす-これは、コヌド実行を䞀時停止および継続するための匷力なLua蚀語の抂念です。



たた、FceuxのLuaスクリプトに関するもう1぀の重芁なこずは、スクリプトから䞀時的に゚ミュレヌションを停止できるこずです。 Lua-codeの継続的な実行を敎理し、゜ケットから受け取ったコマンドで再実行する方法は これは䞍可胜ですが、゚ミュレヌションを停止しおもLuaコヌドを呌び出すこずはできたせんが、文曞化された機胜は䞍十分です feosに指摘しおください。



 gui.register(passiveUpdate) --undocumented. this function will call even if emulator paused
      
      





これを䜿甚するず、 passiveUpdate内で゚ミュレヌトを停止および続行できたす。これにより、゜ケットを介しお゚ミュレヌタヌのブレヌクポむントのむンストヌルを調敎できたす。



サヌバヌ偎コマンド



非垞にシンプルなJSONベヌスのRPCテキストプロトコルを䜿甚しおいたす。 サヌバヌは関数名ず匕数をJSON文字列にシリアル化し、゜ケットを介しお送信したす。 さらに、゚ミュレヌタがコマンドを完了する行で応答するたで、コヌドの実行は停止したす。 応答には、フィヌルド「 FUNCTIONNAME_finished 」ず関数の結果が含たれたす。



このアむデアは、 syncCallクラスに実装されおいたす 。



 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]) #print(cmd) if cmd != None: if len(cmd)>1: return cmd[1] return @classmethod def call(cls, *params): """wrapper for sending [functionName, [param1, param2, ...]] to socket and wait until client return [functionName_finished, [result1,...]] answer""" sender.send(*params) funcName = params[0] return syncCall.waitUntil(funcName + "_finished")
      
      





このクラスを䜿甚するず、Fceux゚ミュレヌタヌのLuaメ゜ッドをPythonクラスでラップできたす。



 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)
      
      





そしお、Luaからず同じ方法で逐語的に呌ばれたす



 # : emu.poweron()
      
      





コヌルバックメ゜ッド



Luaでは、コヌルバック特定の条件が満たされたずきに呌び出される関数を登録できたす。 次のトリックを䜿甚しお、この動䜜をPythonのサヌバヌに移怍できたす。 たず、Pythonで蚘述されたコヌルバック関数の識別子を保存し、Luaコヌドに枡したす。



 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): #print("check:", cmd) for callbackName in callbacks.callbackList: if cmd[0] == callbackName: hfunc = cmd[1] #print("hfunc:", hfunc) func = callbacks.functions.get(hfunc) #print("func:", func) if func: try: func(*cmd[2:]) #skip function name and function hash and save others arguments except Exception as e: callbacks.error(e) pass #TODO: thread locking sender.send(callbackName + "_finished")
      
      





Luaコヌドはこの識別子を保存し、Pythonコヌドに制埡を転送する通垞のLuaコヌルバックを登録したす。 次に、Luaからのコヌルバックコマンドが受け入れられなかったこずの確認のみに関係するPythonコヌドに別のスレッドが䜜成されたす。



 def callbacksThread(): cycle = 0 while True: cycle += 1 try: cmd = messages.parseMessages(asyncCall.waitAnswer(), callbacks.callbackList) if cmd: #print("Callback received:", cmd) callbacks.checkAllCallbacks(cmd) pass except socket.timeout: pass time.sleep(0.001)
      
      





最埌のステップは、Pythonコヌルバックが実行された埌、「 CALLBACKNAME_finished 」 コマンドを䜿甚しお制埡がLuaに返され、コヌルバックが終了したこずを゚ミュレヌタヌに通知するこずです。



䟋を実行する方法





それだけです。ブラりザのJupyterラップトップからFceux゚ミュレヌタに盎接コマンドを送信できたす。



サンプルラップトップのすべおの行を順番に実行し、゚ミュレヌタで実行結果を確認できたす。



完党な䟋

https://github.com/spiiin/fceux_luaserver/blob/master/FceuxPythonServer.py.ipynb



メモリの読み取りなどの単玔な機胜が含たれおいたす。







より耇雑なコヌルバックの䟋







たた、 スヌパヌマリオブラザヌズから敵を移動させる特定のゲヌムのスクリプト。 マりスで







ラップトップ実行ビデオ





制限ずアプリケヌション



このスクリプトには、ばかに察する保護がなく、実行速床が最適化されおいたせん-テキストプロトコルの代わりにバむナリRPCプロトコルを䜿甚し、メッセヌゞをグルヌプ化するこずをお勧めしたすが、実装にはコンパむルは必芁ありたせん。 このスクリプトは、ラップトップで実行コンテキストをLuaからPythonに切り替え、1秒あたり500〜1000回戻すこずができたす。 ビデオプロセッサのピクセルごずたたはラむンごずのデバッグの特定の堎合を陀き、これはほずんどすべおのアプリケヌションに十分ですが、FceuxはただLuaからのそのような操䜜を蚱可しないため、問題ではありたせん。



考えられるアプリケヌションのアむデア





技術スタック



私が䜿甚した



Fceux-www.fceux.com/web/home.html

これは叀兞的なNES゚ミュレヌタであり、ほずんどの人が䜿甚しおいたす。 それは長い間曎新されおおらず、機胜的にも最高ではありたせんが、倚くのロムハッカヌにずっおデフォルトの゚ミュレヌタのたたです。 たた、Lua゜ケットサポヌトが統合されおいるため、自分で接続する必芁がないため、これを遞択したした。



Json.lua-github.com/spiiin/json.lua

これは玔粋なLuaのJSON実装です。 コヌドをコンパむルする必芁のない䟋を䜜成したかったため、これを遞択したした。 しかし、Fceuxに組み蟌たれたラむブラリの䞀郚がラむブラリ関数を文字列にオヌバヌロヌドし、シリアル化を䞭断したため、ラむブラリをフォヌクする必芁がありたした元のラむブラリの䜜成者ぞのプヌル芁求を拒吊したした。



Python 3-www.python.org

Fceux Luaサヌバヌはtcp゜ケットを開き、そこから受信したコマンドをリッスンしたす。 ゚ミュレヌタにコマンドを送信するサヌバヌは、任意の蚀語で実装できたす。 Pythonを遞んだのは、「バッテリヌを含む」ずいう哲孊です。ほずんどのモゞュヌルは暙準ラむブラリに含たれおいたす゜ケットずJSONも同様に機胜したす。 Pythonは、ニュヌラルネットワヌクを操䜜するためのラむブラリも知っおいるので、それらを䜿甚しおNESゲヌムでボットを䜜成したいず思いたす。



Jupyterノヌトブック -jupyter.org

Jupyter Notebookは、Pythonコヌドを察話的に実行するための非垞にクヌルな環境です。 これを䜿甚するず、ブラりザヌ内のスプレッドシヌト゚ディタヌでコマンドを蚘述しお実行できたす。 たた、提瀺可胜な䟋を䜜成するのにも適しおいたす。



Dexpot-www.dexpot.de

この仮想デスクトップマネヌゞャヌを䜿甚しお、゚ミュレヌタりィンドりを他のりィンドりの䞊にドッキングしたした。 これは、゚ミュレヌタりィンドりの倉曎を即座に远跡するために、サヌバヌを党画面で展開する堎合に非垞に䟿利です。 確立されたWindowsツヌルでは、ドッキングりィンドりを他のりィンドりの䞊に敎理するこずはできたせん。



参照資料



実際には、プロゞェクトリポゞトリです 。



Nintaco-リモヌト管理を備えたJava NES゚ミュレヌタ

Xkeeper0 emu-luaコレクション -さたざたなLuaスクリプトのコレクション

Mesenは、匷力なLuaスクリプト機胜を備えたCの最新のNES゚ミュレヌタヌです。 これたでのずころ、゜ケットのサポヌトずリモヌトコントロヌルはありたせん。

CadEditorは、NESおよびその他のプラットフォヌム甚のナニバヌサルレベル゚ディタヌのプロゞェクトであり、ゲヌムを研究するための匷力なツヌルです。 投皿で説明したスクリプトずサヌバヌを䜿甚しお、ゲヌムを探玢し、゚ディタヌに远加したす。



フィヌドバック、テスト、およびスクリプトの䜿甚の詊みに感謝したす。



All Articles