Dalam artikel ini, saya akan menjelaskan cara membuat emulator NES dikendalikan dari jarak jauh, dan server untuk mengirim perintah ke sana dari jarak jauh.

Mengapa ini dibutuhkan?
Beberapa emulator dari berbagai konsol game, termasuk
Fceux , memungkinkan Anda untuk menulis dan menjalankan skrip khusus di Lua. Tapi Lua adalah bahasa yang buruk untuk menulis program serius. Ini lebih merupakan bahasa untuk fungsi panggilan yang ditulis dalam C. Para penulis emulator menggunakannya hanya karena ringan dan mudah ditanamkan. Emulasi yang akurat membutuhkan banyak sumber daya prosesor, dan kecepatan emulasi sebelumnya adalah salah satu tujuan utama penulis, dan jika mereka mengingat kemungkinan tindakan scripting, itu tidak di tempat pertama.
Sekarang kekuatan prosesor rata-rata sudah cukup untuk meniru NES, mengapa tidak menggunakan bahasa skrip yang kuat seperti Python atau JavaScript dalam emulator?
Sayangnya, tidak ada emulator NES populer yang memiliki kemampuan untuk menggunakan ini atau bahasa lain. Saya hanya menemukan proyek
Nintaco yang sedikit diketahui, yang juga didasarkan pada kernel Fceux, untuk beberapa alasan ditulis ulang di Jawa. Kemudian saya memutuskan untuk menambahkan kemampuan menulis skrip dengan Python untuk mengendalikan emulator sendiri.
Hasil saya adalah Proof-of-Concept dari kemampuan untuk mengendalikan emulator, ia tidak berpura-pura kecepatan atau keandalan, tetapi ia bekerja. Saya melakukannya untuk diri saya sendiri, tetapi karena pertanyaan tentang bagaimana mengendalikan emulator menggunakan skrip
cukup umum , saya meletakkan kode sumber pada
github .
Bagaimana cara kerjanya
Di sisi emulator
Emulator Fceux
sudah menyertakan beberapa perpustakaan Lua yang disertakan di dalamnya
dalam bentuk kode yang dikompilasi . Salah satunya adalah
LuaSocket . Ini didokumentasikan dengan buruk, tetapi saya berhasil menemukan contoh kode kerja di antara
kumpulan skrip Xkeeper0 . Dia menggunakan soket untuk mengontrol emulator melalui Mirc. Sebenarnya, kode yang membuka soket tcp adalah:
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)
Ini adalah soket tingkat rendah yang menerima dan mengirim data sebesar 1 byte.
Dalam emulator Fceux, loop utama skrip Lua terlihat seperti ini:
function main() while true do
Pemeriksaan data dari soket:
function passiveUpdate() local message, err, part = sock2:receive("*all") if not message then message = part end if message and string.len(message)>0 then
Kode ini cukup sederhana - data dibaca dari soket, dan jika perintah berikutnya terdeteksi, maka diuraikan dan dieksekusi. Parsing dan eksekusi diatur menggunakan
coroutine (coroutine) - ini adalah konsep yang kuat dari bahasa Lua untuk menjeda dan melanjutkan eksekusi kode.
Dan satu hal lagi yang penting tentang skrip Lua di Fceux - emulasi dapat dihentikan sementara dari skrip. Bagaimana mengatur kelanjutan eksekusi kode Lua dan menjalankannya kembali dengan perintah yang diterima dari soket? Ini tidak mungkin, tetapi ada kemampuan yang tidak terdokumentasi dengan baik untuk memanggil kode Lua bahkan ketika emulasi dihentikan (terima kasih karena telah
menunjukkannya ):
gui.register(passiveUpdate)
Dengannya , Anda dapat berhenti dan terus mengemulasi di dalam
pembaruan pasif - dengan cara ini Anda dapat mengatur pemasangan breakpoint emulator melalui soket.
Perintah sisi server
Saya menggunakan protokol teks RPC berbasis JSON yang sangat sederhana. Server membuat serial nama fungsi dan argumen menjadi string JSON dan mengirimkannya melalui soket. Lebih lanjut, eksekusi kode dihentikan sampai emulator merespons dengan garis untuk menyelesaikan perintah. Respons akan berisi bidang "
FUNCTIONNAME_finished " dan hasil dari fungsinya.
Idenya diimplementasikan di kelas
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])
Dengan kelas ini, metode Lua emulator Fceux dapat dibungkus dalam kelas 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)
Dan kemudian memanggil kata demi kata dengan cara yang sama seperti dari Lua:
Metode panggilan balik
Di Lua, Anda dapat mendaftarkan panggilan balik - fungsi yang akan dipanggil saat kondisi tertentu terpenuhi. Kita bisa port perilaku ini ke server di Python menggunakan trik berikut. Pertama, kita menyimpan pengenal fungsi callback yang ditulis dengan Python dan meneruskannya ke kode 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):
Lua-code juga menyimpan pengenal ini dan mendaftarkan Lua-callback biasa, yang akan mentransfer kontrol ke Python-code. Selanjutnya, utas terpisah dibuat dalam kode Python yang hanya berkaitan dengan memeriksa bahwa perintah panggilan balik dari Lua belum diterima:
def callbacksThread(): cycle = 0 while True: cycle += 1 try: cmd = messages.parseMessages(asyncCall.waitAnswer(), callbacks.callbackList) if cmd:
Langkah terakhir adalah bahwa setelah panggilan balik Python dieksekusi, kontrol dikembalikan ke Lua menggunakan perintah "
CALLBACKNAME_finished " untuk memberi tahu emulator bahwa panggilan balik telah selesai.
Cara menjalankan contoh
Itu saja, Anda dapat mengirim perintah dari laptop Jupyter di browser langsung ke emulator Fceux.
Anda dapat menjalankan semua baris laptop contoh secara berurutan dan mengamati hasil eksekusi di emulator.
Contoh lengkap:
https://github.com/spiiin/fceux_luaserver/blob/master/FceuxPythonServer.py.ipynbIni berisi fungsi sederhana seperti membaca memori:

Contoh panggilan balik yang lebih kompleks:

Dan skrip untuk game tertentu yang memungkinkan Anda untuk memindahkan musuh dari
Super Mario Bros. dengan mouse:

Video Jalankan Laptop:
Keterbatasan dan Aplikasi
Script tidak memiliki perlindungan terhadap kebodohan dan tidak dioptimalkan untuk kecepatan eksekusi - akan lebih baik menggunakan protokol RPC biner daripada teks satu dan pesan grup bersama, tetapi implementasi saya tidak memerlukan kompilasi. Script dapat mengubah konteks eksekusi dari Lua ke Python dan kembali 500-1000 kali per detik di laptop saya. Ini cukup untuk hampir semua aplikasi, kecuali untuk kasus-kasus tertentu dari pixel-by-pixel atau debugging baris-demi-baris dari prosesor video, tetapi Fceux masih tidak mengizinkan operasi semacam itu dari Lua, jadi itu tidak masalah.
Gagasan aplikasi yang mungkin:
- Sebagai contoh penerapan kontrol tersebut untuk emulator dan bahasa lainnya
- Penelitian game
- Menambahkan cheat atau fitur untuk mengatur bagian TAS
- Masukkan atau ekstrak data dan kode ke dalam game
- Meningkatkan kemampuan emulator - menulis debugger, skrip untuk merekam dan melihat langkah-langkah, perpustakaan skrip, editor game
- Game jaringan, kontrol game menggunakan perangkat seluler, layanan jarak jauh, joypad atau perangkat kontrol lainnya, menyimpan dan menambal layanan cloud
- Fitur Cross-Emulator
- Menggunakan Python atau perpustakaan bahasa lain untuk analisis data dan kontrol game (membuat bot)
Tumpukan teknologi
Saya menggunakan:
Fceux -
www.fceux.com/web/home.htmlIni adalah emulator NES klasik, dan kebanyakan orang menggunakannya. Ini belum diperbarui untuk waktu yang lama, dan bukan yang terbaik dalam fitur, tetapi tetap emulator default untuk banyak romhackers. Juga, saya memilihnya karena dukungan soket Lua terintegrasi ke dalamnya, dan tidak perlu menghubungkannya sendiri.
Json.lua -
github.com/spiiin/json.luaIni adalah implementasi JSON di Lua murni. Saya memilihnya karena saya ingin membuat contoh yang tidak memerlukan kompilasi kode. Tetapi saya masih harus mem-fork perpustakaan, karena beberapa perpustakaan yang dibangun di Fceux membebani fungsi
tostring perpustakaan dan memutus serialisasi (
permintaan pool saya yang ditolak kepada penulis perpustakaan asli).
Python 3 -
www.python.orgServer Fceux Lua membuka soket tcp dan mendengarkan perintah yang diterima darinya. Server yang mengirimkan perintah ke emulator dapat diimplementasikan dalam bahasa apa pun. Saya memilih Python karena filosofi "Termasuk baterai" - sebagian besar modul termasuk dalam perpustakaan standar (bekerja dengan soket dan JSON juga). Python juga tahu perpustakaan untuk bekerja dengan jaringan saraf, dan saya ingin mencoba menggunakannya untuk membuat bot di game NES.
Jupyter Notebook -
jupyter.orgJupyter Notebook adalah lingkungan yang sangat keren untuk mengeksekusi kode Python secara interaktif. Dengannya, Anda dapat menulis dan menjalankan perintah dalam editor spreadsheet di dalam browser. Ini juga bagus untuk membuat contoh yang bagus.
Dexpot -
www.dexpot.deSaya menggunakan manajer desktop virtual ini untuk merapat jendela emulator di atas yang lain. Ini sangat nyaman ketika menempatkan server dalam layar penuh untuk pelacakan instan perubahan di jendela emulator. Alat Windows asli tidak memungkinkan Anda untuk mengatur docking windows di atas yang lain.
Referensi
Sebenarnya,
repositori proyek .
Nintaco - Java NES Emulator dengan Remote Management
Xkeeper0 emu-lua collection - koleksi berbagai skrip Lua
Mesen adalah emulator NES modern di C # dengan kemampuan skrip Lua yang kuat. Sejauh ini tanpa dukungan soket dan remote control.
CadEditor adalah proyek saya editor tingkat universal untuk NES dan platform lainnya, serta alat yang kuat untuk meneliti game. Saya menggunakan skrip dan server yang dijelaskan dalam posting untuk menjelajahi permainan dan menambahkannya ke editor.
Saya akan menghargai umpan balik, pengujian, dan upaya untuk menggunakan skrip.