N64: إعادة الترسيب هي أداة لإعادة ترجمة ثنائيات N64 بشكل ثابت في رمز C الذي يمكن تجميعه لأي منصة. يمكن استخدام ذلك للمنافذ أو الأدوات وكذلك لمحاكاة السلوكيات بشكل أسرع بكثير من المترجمين الفوريين أو إعادة التحسس الديناميكي. على نطاق أوسع ، يمكن استخدامه في أي سياق تريد تشغيل جزء من ثنائي N64 في بيئة مستقلة.
ليس هذا هو المشروع الأول الذي يستخدم إعادة التجميع الثابت على ثنائيات وحدة التحكم في اللعبة. مثال معروف هو Jamulator ، الذي يستهدف ثنائيات NES. بالإضافة إلى ذلك ، ليس هذا هو أول مشروع يطبق إعادة التجميع الثابت على المشاريع المتعلقة بـ N64: إعادة ترجمة IDO الثابتة لمرجم SGI Irix IDO على الأنظمة الحديثة لتسهيل مطابقة إلغاء تجميع ألعاب N64. يعمل هذا المشروع بشكل مشابه لمشروع RECOMP الثابت في IDO في بعض النواحي ، وكان هذا المشروع مصدر إلهامي الرئيسي لإنشاء هذا.
يعمل Recompiler من خلال قبول قائمة من الرموز والبيانات الوصفية إلى جانب الثنائي بهدف تقسيم المدخلات الثنائية إلى وظائف يتم إعادة تجميعها بشكل فردي في وظيفة C ، والتي سميت وفقًا للبيانات الوصفية.
تتم معالجة الإرشادات واحدة تلو الأخرى ويتم تنبعث رمز C المقابل عند معالجة كل واحدة. هذه الترجمة حرفية للغاية من أجل الحفاظ على انخفاض التعقيد. على سبيل المثال ، تعليمة addiu $r4, $r4, 0x20
، والتي تضيف 0x20
إلى القيمة 32 بت في بايتات منخفضة من السجل $r4
وتخزن العلامة الممتدة 64 بت النتيجة $r4
، يتم إعادة ترجمة إلى ctx->r4 = ADD32(ctx->r4, 0X20);
يتم إعادة تنسيق تعليمات jal
(القفز والربط) مباشرة في استدعاء الوظيفة ، وتعليمات j
أو b
(القفزات والفروع غير المشروطة) التي يمكن تحديدها على أنها تحسينات استدعاء الذيل يتم إعادة تنسيقها أيضًا في مكالمات الوظائف أيضًا. يتم التعامل مع فتحات تأخير الفرع عن طريق تكرار التعليمات حسب الضرورة. هناك سلوكيات محددة أخرى لتعليمات معينة ، مثل محاولة إعادة التجميع لتحويل تعليمات jr
إلى عبارة حالة تبديل إذا كان بإمكانها معرفة أنه يتم استخدامه مع جدول القفز. تم اختبار Recompiler في الغالب على الثنائيات التي تم بناؤها مع مجمعات MIPS القديمة (مثل MIPS GCC 2.7.2 و IDO) بالإضافة إلى MIPs المستهدفة الحديثة. يجوز لـ Modern MIPS GCC رحلة إلى أعلى إعادة التجميع بسبب بعض التحسينات التي يمكن أن تفعلها ، ولكن من المحتمل أن يتم تجنب تلك الحالات عن طريق تعيين أعلام تجميع محددة.
كل وظيفة إخراج التي أنشأتها Recompiler تنبعث حاليًا في ملفها الخاص. قد يتم توفير خيار في المستقبل لتجميع وظائف العمل معًا في ملفات الإخراج ، والتي من شأنها أن تساعد في تحسين أوقات بناء إخراج Recompiler عن طريق تقليل ملف I/O في عملية الإنشاء.
يمكن تجميع إخراج Recompiler مع أي مترجم C (تم اختباره باستخدام MSVC و GCC و Clang). من المتوقع استخدام الإخراج مع وقت تشغيل يمكن أن يوفر الوظائف اللازمة وتطبيقات الماكرو لتشغيله. يتم توفير وقت التشغيل في N64ModernRuntime والذي يمكن رؤيته أثناء العمل في مشروع Zelda 64: إعادة ترجمة.
يمكن التعامل مع هذه الأداة المرتبطة بشكل ثابت ومرتبط بها. في كلتا الحالتين ، تنبعث الأداة بحث الوظائف لـ Jump and Link-Legister (IE Function Pointers أو الوظائف الافتراضية) والتي يمكن أن ينفذها وقت التشغيل المقدم باستخدام أي نوع من جدول البحث. على سبيل المثال ، سيتم إعادة ترجمة التعليمات jalr $25
كـ LOOKUP_FUNC(ctx->r25)(rdram, ctx);
يمكن لوقت التشغيل بعد ذلك الحفاظ على قائمة بأقسام البرنامج التي يتم تحميلها وفي أي عنوان موجود من أجل تحديد الوظيفة التي يجب تشغيلها كلما تم تشغيل البحث أثناء وقت التشغيل.
بالنسبة للتراكبات القابلة للنقل ، ستقوم الأداة بتعديل الإرشادات المدعومة التي تمتلك بيانات النقل ( lui
و addiu
و Load and Store) عن طريق انبعاث ماكرو إضافي يمكّن وقت التشغيل من نقل حقل القيمة الفوري للتعليمات. على سبيل المثال ، فإن التعليمات lui $24, 0x80C0
في قسم يبدأ في العنوان 0x80BFA100
مع نقل مقابل رمز مع عنوان 0x80BFA730
سيتم إعادة ترجمة كـ ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);
، حيث 1754 هو فهرس هذا القسم. يمكن لوقت التشغيل بعد ذلك تنفيذ وحدات الماكرو RELOC_HI16 و RELOC_LO16 من أجل التعامل مع تعديل الفوري بناءً على العنوان المحمّل الحالي للقسم.
يأتي دعم عمليات نقل TLB في المستقبل ، مما سيضيف القدرة على توفير قائمة من عمليات نقل MIPS32 بحيث يمكن لوقت التشغيل نقلها عند الحمل. يجب أن يسمح الجمع بين هذا مع الوظائف المستخدمة في التراكبات القابلة للنقل إلى تشغيل معظم التعليمات البرمجية المعينة TLB دون تكبد عقوبة الأداء على كل وصول ذاكرة الوصول العشوائي.
يتم تكوين Recompiler من خلال توفير ملف TOML من أجل تكوين سلوك Recompiler ، وهو الوسيطة الوحيدة المقدمة إلى Recompiler. TOML هو المكان الذي تحدد فيه مسارات ملفات الإدخال والإخراج ، وكذلك اختياريًا من وظائف محددة ، وتخطي إعادة توحيد وظائف محددة ، وتعليمات واحدة في الثنائي الهدف. هناك أيضًا وظائف مخططة لتكون قادرة على إصدار السنانير في إخراج Recompiler عن طريق إضافتها إلى TOML ( [[patches.func]]
[[patches.hook]]
لا ينفذ. الوثائق حول كل خيار يوفره Recompiler غير متاح حاليًا ، ولكن يمكن العثور على مثال على Toml في مشروع Zelda 64: Recongipiled هنا.
حاليًا ، فإن الطريقة الوحيدة لتوفير البيانات الوصفية المطلوبة هي تمرير ملف ELF إلى هذه الأداة. أسهل طريقة للحصول على مثل هذا القزم هي إنشاء تفكيك أو فك تشفير الثنائي المستهدف ، ولكن سيكون هناك دعم لتوفير البيانات الوصفية عبر تنسيق مخصص لتجاوز الحاجة إلى القيام بذلك في المستقبل.
يمكن أيضًا تكوين هذه الأداة لإعادة ترجمة في وضع "إخراج الملف المفرد" عبر خيار في تكوين TOML. سيؤدي ذلك إلى تنبعث جميع الوظائف في ELF المقدمة في ملف إخراج واحد. الغرض من هذا الوضع هو أن تكون قادرًا على تجميع إصدارات مصححة من الوظائف من الثنائي الهدف.
يمكن دمج هذا الوضع مع الوظائف التي توفرها جميع الروابط تقريبًا (LD ، LLD ، LINK.EXE ، إلخ) لاستبدال الوظائف من إخراج Recompiler الأصلي مع إصدارات معدلة. تبحث هذه الروابط فقط عن الرموز في مكتبة ثابتة إذا لم يتم العثور عليها بالفعل في ملف إدخال سابق ، وبالتالي فإن توفير التصحيحات المعاد ترجمة إلى الرابط قبل توفير إخراج Recompiler الأصلي سيؤدي إلى أخذ التصحيحات على الأولوية على نفس الأسماء من إخراج Recompiler الأصلي.
هذا يوفر قدراً هائلاً من الوقت أثناء التكرار على بقع للثنائي المستهدف ، حيث يمكنك تجاوز إعادة تشغيل إعادة التجميع على المستهدف الثنائي وكذلك تجميع إخراج Recompiler الأصلي. يمكن العثور على مثال على استخدام وضع إخراج الملف المفرد لهذا الغرض في المشروع Zelda 64: إعادة ترجمة هنا ، مع Makefile المقابلة التي تعتاد على إنشاء ELF لتلك التصحيحات هنا.
يمكن أيضًا إعادة ترجمة رمز Microcode RSP باستخدام هذه الأداة. لا يوجد حاليًا أي دعم لإعادة ترجمة RSP ، ولكن يمكن إضافته في المستقبل إذا رغبت في ذلك. ستأتي الوثائق حول كيفية استخدام هذه الوظيفة قريبًا.
يمكن بناء هذا المشروع باستخدام Cmake 3.20 أو أعلى ومترجم C ++ الذي يدعم C ++ 20. يستخدم هذا الريبو عارضات فرعية GIT ، لذا تأكد من استنساخ بشكل متكرر ( git clone --recurse-submodules
) أو تهيئة العوامل الفرعية بشكل متكرر بعد الاستنساخ ( git submodule update --init --recursive
). من هناك ، يتطابق البناء مع أي مشروع CMAKE آخر ، على سبيل المثال ، قم بتشغيل cmake
في مجلد البناء الهدف وأوجهه إلى جذر هذا الريبو ، ثم قم بتشغيل cmake --build .
من هذا المجلد الهدف.