في المقالة ، سوف أصف كيفية التحكم في محاكي NES عن بُعد ، وخادم لإرسال الأوامر إليه عن بُعد.

لماذا هذا مطلوب؟
تتيح لك بعض برامج محاكاة الألعاب المختلفة ، بما في ذلك
Fceux ، كتابة وتشغيل برامج نصية مخصصة على Lua. لكن لوا هي لغة سيئة لكتابة برامج جادة. هي بالأحرى لغة للاتصال بالوظائف المكتوبة باللغة C. يستخدمه مؤلفو المحاكاة فقط بسبب خفة وسهولة التضمين. يتطلب المحاكاة الدقيقة الكثير من موارد المعالج ، وكانت سرعة المضاهاة السابقة أحد الأهداف الرئيسية للمؤلفين ، وإذا تذكروا إمكانية إجراء عمليات البرمجة ، فلن يكون ذلك في المقام الأول.
الآن قوة المعالج المتوسط كافية لمحاكاة NES ، لماذا لا تستخدم لغات البرمجة النصية القوية مثل Python أو JavaScript في برامج المحاكاة؟
لسوء الحظ ، ليس لدى أي من برامج محاكاة NES الشائعة القدرة على استخدام هذه اللغات أو اللغات الأخرى. لقد وجدت فقط مشروع
Nintaco غير معروف ، والذي يستند أيضًا إلى Fceux kernel ، لسبب ما تمت إعادة كتابته في Java. ثم قررت إضافة القدرة على كتابة البرامج النصية في بيثون للسيطرة على المحاكي بنفسي.
النتيجة هي إثبات القدرة على التحكم في المحاكي ، فهي لا تتظاهر بالسرعة أو الموثوقية ، ولكنها تعمل. لقد فعلت ذلك بنفسي ، ولكن نظرًا لأن مسألة كيفية التحكم في المحاكي باستخدام البرامج النصية أمر
شائع بما فيه الكفاية ، فقد وضعت الكود المصدري على
جيثب .
كيف يعمل؟
على جانب المحاكي
يحتوي المحاكي Fceux
بالفعل على العديد من مكتبات Lua المضمنة فيه
في شكل تعليمات
برمجية برمجية . واحد منهم هو
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)
هذا مقبس منخفض المستوى يستقبل ويرسل البيانات بمقدار 1 بايت.
في محاكي Fceux ، تبدو الحلقة الرئيسية في نص Lua كما يلي:
function main() while true do
فحص البيانات من المقبس:
function passiveUpdate() local message, err, part = sock2:receive("*all") if not message then message = part end if message and string.len(message)>0 then
الرمز بسيط جدًا - تتم قراءة البيانات من المقبس ، وإذا تم اكتشاف الأمر التالي ، فسيتم تحليله وتنفيذه. يتم تنظيم التحليل والتنفيذ باستخدام
coroutine (coroutines) - وهذا مفهوم قوي للغة Lua للتوقف المؤقت وتنفيذ التعليمات البرمجية المستمرة.
وهناك شيء آخر مهم حول برمجة Lua في Fceux - محاكاة يمكن إيقافها مؤقتًا من البرنامج النصي. كيفية تنظيم التنفيذ المستمر لرمز Lua وإعادة تشغيله باستخدام أمر تم استلامه من المقبس؟ لن يكون ذلك ممكنًا ، ولكن هناك قدرة موثقة بشكل سيء على استدعاء رمز Lua حتى عند إيقاف المحاكاة (شكرًا
لك على
الإشارة إليه):
gui.register(passiveUpdate)
مع ذلك ، يمكنك إيقاف ومتابعة مضاهاة داخل
passiveUpdate - بهذه الطريقة يمكنك تنظيم تثبيت نقاط التوقف الخاصة
بالمضاهاة عبر مأخذ توصيل.
أمر جانب الخادم
أستخدم بروتوكول نص RPC بسيط جدًا يستند إلى JSON. يقوم الخادم بتسلسل اسم الوظيفة والوسيطات في سلسلة 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])
باستخدام هذه الفئة ، يمكن لف أساليب المحاكي 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 ، يمكنك تسجيل عمليات الاسترجاعات - الوظائف التي سيتم استدعاؤها عند تلبية شرط معين. يمكننا نقل هذا السلوك إلى الخادم في 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):
يحفظ Lua-code أيضًا هذا المعرّف ويسجل رد اتصال Lua-call منتظم ، والذي سينقل التحكم إلى كود Python. بعد ذلك ، يتم إنشاء سلسلة رسائل منفصلة في شفرة Python التي تهتم فقط بالتحقق من عدم قبول أمر رد الاتصال من Lua:
def callbacksThread(): cycle = 0 while True: cycle += 1 try: cmd = messages.parseMessages(asyncCall.waitAnswer(), callbacks.callbackList) if cmd:
الخطوة الأخيرة هي أنه بعد تنفيذ رد الاتصال Python ، يتم إرجاع التحكم إلى Lua باستخدام الأمر "
CALLBACKNAME_finished " لإبلاغ المحاكي بإنهاء رد الاتصال.
كيفية تشغيل مثال
هذا كل شيء ، يمكنك إرسال أوامر من الكمبيوتر المحمول Jupyter في المستعرض مباشرة إلى محاكي Fceux.
يمكنك تنفيذ جميع أسطر الكمبيوتر المحمول المثال بالتتابع ومراقبة نتيجة التنفيذ في المحاكي.
مثال كامل:
https://github.com/spiiin/fceux_luaserver/blob/master/FceuxPythonServer.py.ipynbيحتوي على وظائف بسيطة مثل قراءة الذاكرة:

أمثلة رد الاتصال أكثر تعقيدًا:

ونصًا لعبة معينة تسمح لك بنقل الأعداء من
Super Mario Bros. مع الماوس:

تشغيل الفيديو المحمول:
القيود والتطبيقات
لا يحتوي البرنامج النصي على أي حماية ضد الخداع ولم يتم تحسينه لسرعة التنفيذ - سيكون من الأفضل استخدام بروتوكول RPC ثنائي بدلاً من نص واحد وتجميع الرسائل معًا ، لكن تطبيقي لا يتطلب تجميعًا. يمكن للبرنامج النصي تبديل سياقات التنفيذ من Lua إلى Python وإعادة 500-1000 مرة في الثانية على جهاز الكمبيوتر المحمول. هذا يكفي لأي تطبيق تقريبًا ، باستثناء حالات محددة من تصحيح البيكسل أو بكسل لسطر معالج الفيديو ، لكن Fceux لا يزال لا يسمح بمثل هذه العمليات من Lua ، لذلك لا يهم.
أفكار التطبيق الممكنة:
- كمثال على تنفيذ هذه الرقابة لغيرها من المحاكيات واللغات
- لعبة البحث
- إضافة غش أو ميزات لتنظيم مقاطع TAS
- إدراج أو استخراج البيانات والرمز في الألعاب
- تعزيز قدرات المحاكيات - كتابة مصححات الأخطاء والبرامج النصية لتسجيل وعرض الإرشادات التفصيلية ، مكتبات النصوص ، محرري الألعاب
- لعبة الشبكة ، والتحكم في اللعبة باستخدام الأجهزة المحمولة ، والخدمات عن بعد ، joypads أو غيرها من أجهزة التحكم ، وتوفير والبقع في الخدمات السحابية
- عبر محاكي الميزات
- استخدام Python أو مكتبات اللغات الأخرى لتحليل البيانات والتحكم في اللعبة (إنشاء برامج روبوتات)
كومة التكنولوجيا
اعتدت:
Fceux -
www.fceux.com/web/home.htmlهذا محاكي NES كلاسيكي ، ويستخدمه معظم الناس. لم يتم تحديثه لفترة طويلة ، وليس الأفضل في الميزات ، لكنه يظل محاكيًا افتراضيًا للعديد من برامج قراءة الرموز. أيضًا ، لقد اخترتها لأن دعم مأخذ التوصيل Lua مدمج فيه ، وليست هناك حاجة لتوصيله بنفسي.
Json.lua -
github.com/spiiin/json.luaهذا هو تنفيذ JSON في لوا النقي. لقد اخترت ذلك لأنني أردت أن أقدم مثالًا لا يتطلب تجميع التعليمات البرمجية. لكنني ما زلت مضطرًا لتفرع المكتبة ، لأن بعض المكتبات المضمّنة في Fceux أثقلت وظيفة المكتبة على
التتابع وكسرت التسلسل (
طلب التجميع المرفوض إلى مؤلف المكتبة الأصلية).
بيثون 3 -
www.python.orgيفتح خادم Fceux Lua مقبس tcp ويستمع للأوامر الواردة منه. يمكن تنفيذ خادم يرسل أوامر إلى المحاكي بأي لغة. لقد اخترت Python لفلسفتها "Battery included" - يتم تضمين معظم الوحدات في المكتبة القياسية (تعمل مع مآخذ و JSON أيضًا). يعرف Python أيضًا المكتبة للعمل مع الشبكات العصبية ، وأريد أن أحاول استخدامها لإنشاء روبوتات في ألعاب NES.
Jupyter Notebook -
jupyter.orgJupyter Notebook هو بيئة رائعة جدًا لتنفيذ كود Python بشكل تفاعلي. مع ذلك ، يمكنك كتابة وتنفيذ الأوامر في محرر جدول بيانات داخل المتصفح. إنه جيد أيضًا لإنشاء أمثلة رائعة.
Dexpot -
www.dexpot.deلقد استخدمت مدير سطح المكتب الافتراضي هذا لإرساء نافذة المحاكي أعلى الآخرين. هذا مناسب جدًا عند نشر الخادم بملء الشاشة للتتبع الفوري للتغييرات في نافذة المحاكي. لا تسمح لك أدوات Windows الأصلية بتنظيم إرساء النوافذ أعلى الآخرين.
المراجع
في الواقع ،
مستودع المشروع .
Nintaco - Java NES Emulator مع الإدارة عن بعد
مجموعة Xkeeper0 emu-lua - مجموعة من نصوص Lua المختلفة
Mesen هو محاكي NES الحديث في C # مع قدرات البرمجة النصية قوية لوا. حتى الآن دون دعم المقبس والتحكم عن بعد.
CadEditor هو مشروعي لمحرر المستوى العالمي لـ NES
والأنظمة الأساسية الأخرى ، بالإضافة إلى أدوات قوية للبحث في الألعاب. أستخدم البرنامج النصي والخادم الموصوفين في المنشور لاستكشاف الألعاب وإضافتها إلى المحرر.
سأكون ممتنًا للتعليقات والاختبارات ومحاولات استخدام البرنامج النصي.