تغيير برمجة اللعبة

الدرس الثاني: أسمبلي 65c816 – البدايات




في الدرس الفارط كانت لنا مقدمة مع الأسمبلي.

ارتأينا أن نبدأ مع لغة الأسمبلي المستعملة مع السوبر نيس،

نظرا أننا بذلك نضرب عدة عصافير بحجر واحد (لأن أغلب تعليماتها مشتركة مع النيس)

و لكونها لغة أبسط من غيرها.

عندما نقول أبسط، نعني أن عدد الأوبكودات opcodes (أي التعليمات) التي يجب أن نعرفها أقل.


رأينا هذه التعليمات:

INC – تزيد 1 للقيمة التي في العنوان أمامها

DEC – تنقص 1 للقيمة التي في العنوان أمامها

على فكرة.

ماذا يحصل لو أنقصنا 1 من 00؟ يصبح لدينا FF.

كذلك عندما نزيد 1 إلى FF يصبح لدينا 00.

العداد عندما يذهب فوق اللزوم نحو طرف أقصى، يعود إلى الطرف الأقصى المقابل.


أيضا رأينا:

STZ – تجعل العنوان أمامها يأخذ القيمة 00. (حصرية للسوبر نيس)

NOP – هذه التعليمة لا تفعل شيئا. عندما تريد حذف تعليمة ما من برنامج، تعوضها بهذه. (موجودة في جميع لغات الأسمبلي)

و هناك شيء آخر لا داعي لنتذكره.


سنمر الآن لتعلم المزيد من الأمور المشوقة الأخرى.


أولا – كيف أجعل عنوانا يأخذ قيمة من إختياري


في الدرس الفارط، تعاملنا مع لعبة السوبر ماريو وورلد على السوبر نيس.

حاولنا جعل قيمة معينة (عدد الحيوات) تأخذ 00

و لذلك كتبنا STZ $0DBE (حيث 0DBE هو عنوان الحيوات) و بالفعل أخذ ذلك العنوان القيمة صفر.


لكن. STZ حصرية للسوبر نيس.

و كذلك هي تعطي فقط القيمة صفر.

ماذا لو أردت أن أعطي القيمة 1 أو 2 أو 5 أو غير هذا؟


ماذا لو كان لدي في عنوان ما في الروم قيمة معينة (الجزء الذي يخص "التنين الأبيض أزرق العينين"، في قائمة بها قيم حياة كل وحوش اللعبة)،

و أردت أن أكتب تلك القيمة في عنوان في الرام (نقاط حياة الوحش الذي أمامك الآن) كي تستخدمها اللعبة عندما أصل ذلك التنين؟


الحل هو تعليمتان، هما في حالتنا LDA و STA.

لماذا تعليمتان و ليس تعليمة واحدة؟

لأن العملية لا تتم بصفة مباشرة.


عندما نريد أن نكتب قيمة إلى عنوان مقصود،

أو قيمة عنوان أصلي إلى العنوان المقصود (مثلا لو كنا ننقل صورة من الروم إلى الرام)،

تتم العملية على مرحلتين

(على عكس لغات برمجة الكمبيوتر عالية المستوى حيث نكتب فقط سطرا واحدا)


أولا – تحميل load القيمة الأصلية إلى داخل السجل.

ثانيا – تخزين store ما بداخل السجل في العنوان المقصود.


ما هو السجل register ؟

هو مكان في الذاكرة الحية نستخدمه كثيرا لمثل هذه الأمور.

في حالة النيس و السوبر نيس، لدينا:

(و سنرجئ الحديث عن بقية الذاكرات المهمة الأخرى لوقت آخر بما أننا نتحدث الآن فقط عن السجلات.)


هذا بالنسبة للنيس و السوبر نيس.

في أجهزة أخرى مثل البلاي ستايشن (أسمبلي MIPS3000) أو الجيم بوي أدفانس (ARM7)،

هناك الكثير من السجلات (دزينة على الأقل) و تسمى r0 و r1 و r2 الخ...(*)

(*) حسب الجهاز، و قد يكون بعضها غير متاح للإستعمال تحت ظروف معينة


في حالة النيس و السوبر نيس،

فالتعليمتان LDA و STA تستخدمان السجل A (المراكم) للقيام بالعملية.

نلاحظ أن الحرف A موجود في اسم التعليمة.


على سبيل المثال:

لو أردنا كتابة القيمة 07 نحو العنوان $0DBE في لعبة ماريو (كي تصبح لديه 7 أرواح)

سنكتب هذين السطرين:


LDA #$07

STA $0DBE


السطر الأول يرسل إلى المراكم القيمة 07 (عرفنا أنها قيمة بالرمز #$ و ليست عنوانا $ )

السطر الثاني يلفظ محتوى المراكم (أي بعبارة أخرى، القيمة 07) نحو العنوان 0DBE

و النتيجة أن العنوان 0DBE صار يحتوي 07.


لدينا تعليمات شبيهة، و تستعمل السجلات X و Y

و تستخدم بنفس الطريقة بالضبط.

مع ذلك عمليا فهذه السجلات X و Y غير مستخدمة كثيرا لمثل هذه الأمور البسيطة

لأن هناك أمور مهمة تستطيع فعلها أفضل من غيرها، و لكن عدا تلك المهام الصعبة المعينة فهي عاجزة في جل الأمور الأخرى،

ما يجعل المبرمجين ينفرون منها (لكن لا شيء يمنعهم من استعمالها) ... و ان شاء الله قد نرى لاحقا ما هذه الأمور بالضبط. ربما.


إذن في الإجمال، في أسمبلي 6502 (النيس) و 65c816 (السوبر نيس) لدينا:

تحميل LDA ثم تخزين STA

تحميل LDX ثم تخزين STX

تحميل LDY ثم تخزين STY


على فكرة، هناك أيضا تعليمات لنسخ قيم من سجل لآخر.

مثلا التعليمة TAX تنسخ محتوى المراكم A إلى السجل X.

لو كان لدينا في السجل A (المراكم) القيمة 04 و في السجل X القيمة 70 و نفذنا هذه التعليمة TAX،

يصبح لدينا في السجلين الإثنين القيمة 04.

توجد أيضا تعليمات النسخ بين السجلات التالية: TAX TAY TXA TXY TYA TYX


مثال: نوع الفطر الذي يأكله ماريو


لنعمل مثالا بسيطا.

سنختار النيس.

لكن لماذا النيس بالذات و نحن لدينا أفضل منه؟


أكيد أنكم سمعتم عن جهاز النيس أنه جهاز 8 بت.

لماذا؟ الجواب هو (كونه هو و سجلاته دائما في وضع 8 بت).

عندما يكون السجل في وضع 8 بت، و مع التعليمتين LDA و STA،

فإن القيمة التي نقلت للسجل، ثم من السجل إلى العنوان الوجهة،

يكون طولها 1 بايت. (مع العلم أن البايت الواحد (00-FF) مكون من 8 بت، حيث كل بت هو 0 أو 1).


و هذا يبسط الأمر علينا كثيرا.

(لأن السوبر نيس مثلا ليست دائما في وضع 8 بت. سنرى هذا لاحقا.)


المثال سيكون في النيس مع لعبة Super Mario Bros 3.

نريد أن نغير ماذا يحصل عندما يأكل ماريو الفطر.


أولا، نبحث عن العنوان المسؤول عن حالة ماريو،

هل هو صغير أم كبير أم لديه إحدى الملابس التنكرية الأخرى (تانوكي، ضفدع، مطرقة).

لذلك الغرض نستخدم خاصية البحث عن أكواد الغش Cheats في محاكي FCEUX.


النتيجة ستكون أن العنوان المطلوب هو $00ED

هذا العنوان قيمه الممكنة هي:

00: صغير، 01: كبير، 02: نار، 03: ريشة،

04: ضفدع، 05: تانوكي، 06: مطرقة.


سنعمل على هذا العنوان نقطة تعطيل breakpoint

و نريدها أن تكون عندما تكتب اللعبة أي شيء نحو هذا العنوان، اذن ستكون من نوع write.

نفتح Debug ثم Debugger و في أعلى يمين النافذة تحت Breakpoints

نضغط Add لإضافة نقطة تعطيل جديدة.

نحدد ما نحتاج إليه في نقطة التعطيل هذه،

أي عنوان الذاكرة، الذي سيكون في الرام أي CPU Mem.

و نحدد نوع التغيير الذي نريد أن تتعطل اللعبة لتنبهنا لحصوله، و هو تغيير هذا العنوان write.



الآن نعود للعبة و نسجل save state (لقطة) و ماريو قريب من المربع الذي فيه الفطر.

عندما نعمل load state نستطيع العودة بسهولة لتلك اللحظة متى أردنا.


الآن نضرب ذلك المربع و نحاول أكل الفطر الذي سيحول ماريو الصغير الى ماريو الكبير.



و في تلك اللحظة سيتعطل المحاكي و تظهر لنا هذه النافذة من جديد.

ملاحظة: نقطة التعطيل موجودة في أعلى اليمين في سطر في قائمة،

و لو نقرنا مرتين فوقها سينزع الحرف E (الذي يدل على أنها تعمل).

أيضا Run تسمح لنا بمواصلة عمل اللعبة.



التعليمة الأولى التي تظهر في الخانة على اليسار

هي التعليمة التي قامت بتغيير العنوان.

و هي في حالتنا STY $00ED

STY كما رأينا هي نفسها STA لكن عوض إستعمال السجل A (المراكم) فهي تعمل بالسجل Y

نصعد قليلا في الخانة على اليسار لنرى التعليمات التي سبقتها.


لأن STY تحتاج إلى وجود LDY أو شيء من هذا القبيل قبلها و فيه القيمة المطلوبة.

للتبسيط، نتخيل أن هذه عصابة،

و هذه التعليمة مجرد خادم في أسفل السلم ينفذ الأوامر،

و نحن نريد البحث عن أصل الداء و زعيم العصابة التي أعطى الأوامر.


سنجد في الأعلى قبلها مباشرة:


00:A3EE:A8 TAY

00:A3EF:88 DEY

00:A3F0:84 ED STY $00ED = #$00


(نأخذ في عين الإعتبار أن التعليمة في الأعلى صارت في الأول،

و التعليمة في الأسفل صارت في النهاية.

التعليمة الحمراء هي التي غيرت شكل ماريو لكنها النتيجة النهائية،

و نحن نبحث في الأعلى عن السبب.)


ماذا حصل هنا؟

DEY التي قد تبدو تعليمة مجهولة لنا،

هي في الواقع نفسها DEC (تنقص واحد من العنوان المطلوب).

لكنها نسخة مخصصة للسجل Y.

هناك DEX DEY و يعادلان DEC لكن ينقصان 1 من السجلين X و Y،

و هناك INX INY و يعادلان INC لكن يزيدان 1 من السجلين X و Y.


أما TAY، فكما رأينا في أول الدرس،

فهي تنسخ بين السجلات. في حالتنا هذه هي نسخت محتوى السجل A (المراكم) إلى السجل Y.

كي نستطيع إذن تغيير البرمجة هنا،

وجب أن نعرف أولا من أين أتت محتويات السجل A

(و على الأرجح هناك تعليمة LDA هي التي صنعت محتويات السجل A).


للتبسيط، مع العلم أن ما حصل هو أن العنوان $00ED تغير من 00 (ماريو صغير) إلى 01 (ماريو كبير)

حصل أمر مثل هذا في البرمجة:


  1. أمور مجهولة أدت لكتابة 02 للسجل A

  2. TAY نسخت محتوى A (02) إلى السجل Y (فصار فيه 02)

  3. DEY أنقصت واحد من قيمة السجل Y (فصار فيه 01)

  4. STY قامت بوضع قيمة السجل Y (01) في عنوان شكل ماريو $00ED (فصار فيه 01 و صار ماريو "كبيرا")


هدفنا هو معرفة "الأمور المجهولة" التي غيرت السجل A (المراكم)

و التي هي على الأرجح شيء من نوع LDA.

هل ننظر لبقية التعليمات فوق؟

قد لا يكون في ذلك الكثير من الفائدة.

و السبب، أن البرنامج يقفز كثيرا بين نقاط مختلفة من البرمجة أثناء التشغيل.


سنجرب الآن شيئا آخر.

سنقوم بعملية التمشيط Trace.

هذه العملية تعطينا تقريرا شاملا عن جميع (!!) التعليمات التي نفذتها اللعبة،

من اللحظة التي قلنا له أن يبدأ التمشيط إلى حين قلنا له أن يوقف التمشيط.


أغلب المحاكيات ترسل نتائج التقرير إلى ملف نص،

لكن في حالة FCEUX، فإن إختيار التمشيط (و هو تحت Debugger و Trace Logger)

يستطيع أن يظهر نتائج التمشيط على النافذة أثناء عمل المحاكي.


لكن ملف النص الناتج سيكون ضخما جدا.

أتصور أن حتى برنامج Notepad لو حاول فتحه سيتوقف عن العمل.

(حبذا في تلك الحالة أن نفتحه ببرامج أخرى مثل Notepad+ أو Microsoft Word أو حتى متصفح أنترنت).


لكن نحن لا نريده أن يكون ضخما.

الحل هو أن نحاول أن نقلص المدة الزمنية التي يتم عليها التمشيط.

حتى لو كانت المدة ثانية أو ثانيتين فهذه مدة مفرطة و زائدة عن اللزوم.


لذلك وجب أن نقوم في هذا المحاكي أيضا بوضع أزرار في لوحة المفاتيح لإيقاف اللعبة بسرعة.

هذه الأزرار التي نحتاجها هي أزرار ل Pause (توقيف مؤقت للعبة)

و Frame Advance (توقيف مؤقت مع تقديم بصورة واحدة).

يمكن في حالة FCEUX الذهاب إلى Config و Map Hotkeys لرؤية أي الأزرار تفعل هذا، و تغييرها حسب ما تحبذون لسهولة الإستعمال.


أولا، في نافذة Debugger، نتأكد أن نقطة التوقف تعمل (ننقر فوقها مرتين كي يظهر الحرف E لو لم يكن ظاهرا)

ثم نعود إلى Debugger ثم Trace Logger.

نستخدم اللقطة التي سجلناها للعبة قبل أكل الفطر و نفتحها Load State.

باستخدام خاصية التقديم بالصورة بالصورة Frame Advance

نضرب المربع ليخرج الفطر. نجعل ماريو قريبا جدا من الفطر لكن غير ملتصق به.


و الآن نعود للنافذة Trace Logger.



الإختيار Log to File (ارسال التقرير إلى ملف، و ليس للنافذة) هو الملائم بالنسبة لنا.

نضغط Browse و نعطي إسما لملفنا هذا. هذا الملف سيكون ملف نص بلاحقة txt نستطيع فتحه و قراءته.

نضغط الآن Start Logging كي يبدأ البرنامج في التمشيط.

و نعود للعبة و نبدأ بتقديم الصورة بزر لوحة المفاتيح الذي خصصناه Frame Advance


بعد صورتين أو ثلاثة (كلما كان عدد الصور أقل كلما كان أفضل)

لن نستطيع تقديم الصورة بزر Frame Advance أكثر، لأننا أصبنا نقطة التعطيل.


نذهب إلى المجلد الذي سجلنا نحوه تقرير عملية التمشيط.

و نفتح ملف النص الذي صنع، و سيكون شكله هكذا:



لكنه ضخم جدا!

أنا قمت بالتسجيل على 4 لقطات فقط و كان حجم الملف 2 ميغابايت (أكبر من الروم نفسها).

مع أجهزة متطورة أكثر من النيس، سيكون الحجم مرعبا أكثر.

و طبعا لو كانت مدة التسجيل أكثر سيكون الحجم ضخما بدرجة مهولة و غير صالح للاستعمال.


سأبحث في ملف النص هذا عن هذه السطور الثلاثة مجتمعة.

سأبدأ البحث من نهاية الملف عوض البداية لأن الحدث صار قبيل نقطة التعطل.


00:A3EE:A8 TAY

00:A3EF:88 DEY

00:A3F0:84 ED STY $00ED = #$00


لحسن حظنا فالتعليمات المطلوبة في النهاية مباشرة!



لم نر التعليمة STY (لأن المحاكي تعطل قبل عند تنفيذها) لكننا رأينا TAY و DEY و ...

الحبيب المجهول الذي غير حالة السجل A (المراكم).

و هو هذا السطر:


$A3CA:AD 78 05 LDA $0578 = #$02 A:80 X:01 Y:FC



لاحظوا العناوين على اليسار.

تلك العناوين هي مكان تعليمات البرمجة.

المفروض أن كل تعليمة تنفذ بعدها التعليمة التي كتبت بعدها في الروم،

لكننا نرى اللعبة تقفز فجأة من $A1A8 نحو $A3CA ثم $A3FA فبعدها $A3EC

هناك تعليمات تجعل البرمجة تقفز هكذا

و بعضها ظاهر في الصورة JSR و BEQ و BMI

بل و يمكنكم ملاحظة العناوين التي تم القفز نحوها في التعليمة نفسها.

سيكون هذا موضوع الدروس القادمة... و سنستغل هذا في صالحنا.


هل ضروري الآن أن نعرف معنى كل تلك التعليمات و ماذا تفعل؟

ليس بعد. و لا واحدة منها تؤثر على السجلات A X Y

على اليمين في ملف النص هناك تقرير مفصل حول قيمة هذه السجلات،

و التغيير الذي يهمنا صار في ذلك السطر.


في الواقع... السطر AND (عملية "و" المنطقية - سنرى معناه لاااااحقا) يؤثر على محتوى السجل A (المراكم) بطريقة أتحفظ عن ذكرها الآن،

لكن من حسن حظنا الشديد أن جميع القيم التي تأخذها القيمة الخاصة بحالة ماريو (بين 0 و 6)

تظل كما هي دون تغيير عندما نمارس عليها التعليمة AND. يعني أنها في حالتنا لا تفعل شيئا.

فائدة هذا السطر؟ هو أنه يقوم بإعادة هذه القيمة إلى السراط المستقيم لكن فقط لو خرجت عن حد معين...

سنرى ماذا يعني هذا بالضبط في الوقت المناسب (الذي هو ليس الآن).



نافذة Debugger في FCEUX،

و برامج الهيكس عند رؤية الروم نفسها، ستكون نهاية كل فقرة مع تعليمة من تعليمات القفز هذه.

و بعدها يأخذ المجرم المأجور الذي كلفه رئيس العصابة بالأوامر،

بأخذ سفرة نحو منطقة أخرى من الروم فيها برمجة أخرى تماما.

لذلك أفضل حل لهذه الحالات هو إستعمال خاصية التمشيط Trace.



هذه التعليمة هي طرف الخيط الناقص،

و صرنا نعرف ما هي الأمور المجهولة التي حصلت في المرحلة 1.

لو نراجع إذن ماذا نعرف عن البرمجة هنا؟


  1. أمور مجهولة أدت لكتابة 02 للسجل A

LDA نسخت محتوى عنوان الرام $0578 (و فيه 02) إلى السجل A (فصار فيه 02)

  1. TAY نسخت محتوى A (02) إلى السجل Y (فصار فيه 02)

  2. DEY أنقصت واحد من قيمة السجل Y (فصار فيه 01)

  3. STY قامت بوضع قيمة السجل Y (01) في عنوان شكل ماريو $00ED (فصار فيه 01 و صار ماريو "كبيرا")


قمنا ب 90 % من الشغل الصعب الآن،

و كشفنا رئيس العصابة الذي هو عنوان الرام $0578

و هو على ما يبدو عنوان مهم جدا،

و لديه دخل مباشر في عملية أكل الفطر و تحديد شكل ماريو.


سنحاول أن نعرف الآن ما الذي يغير قيمة عنوان الرام هذا.

عندما نغيره، سنستطيع التحكم في شكل ماريو عند أكل الفطر، و السيطرة على العالم !!!!


نستخدم لقطتنا Load State لنعود للحظة قبل أن يأكل ماريو الفطر.

و نحاول من جديد، لكن هذه المرة في نافذة Debugger...

نحذف نقطة التعطيل عن العنوان $00ED بواسطة Delete تحت Breakpoints

و ذلك كي لا تزعجنا في المستقبل.

فنحن الآن لسنا مهتمين بأخبار منفذ الأوامر، بل بمن يصدرها.



و نضيف بواسطة Add نقطة تعطيل جديدة،

لكن هذه المرة ستراقب نقطة التعطيل الجديدة العنوان $0578

و ستعطل المحاكي في حال حدوث أي تغيير لمحتوياته (أي write)

نظرا أن هذه المحتويات في النهاية انتقلت عبر السجلات لتؤثر على عنوان حالة ماريو.



و الآن نلعب بالماريو إلى أن ...


كان صغيرا و أكل فطرا أحمر يجعله كبيرا.

تتعطل اللعبة في لحظة لمس الفطر كالعادة.



أول سطر يظهر في الخانة على اليسار

هو السطر الذي تسبب في تعطيل اللعبة

و هو الذي غير العنوان $0578 و ما نبحث عنه:


00:A8A0:8D 78 05 STA $0578 = #$00


معنى هذا السطر،

أن التعليمة STA أخذت محتويات السجل A (المراكم)،

و كتبتها في العنوان $0578.


لكن، من أين أتت محتويات السجل A؟

لا يوجد دخان من دون نار، و لا توجد STA من دون LDA (في غالب الأحيان).

لو نصعد قليلا سنجد جوابنا.

و الجواب سيكون صريحا هذه المرة أخيرا !



منبع القيمة التي عبرت القارات و السجلات لتعطينا حالة ماريو الجديدة،

هو هذا السطر:


00:A89E:A9 02 LDA #$02


و معناه، هو نسخ القيمة 02 إلى السجل A (المراكم).

و بقية ما حصل لتلك القيمة معروفة كما رأينا قبل قليل.


لنعد قليلا للوراء لما نعرفه.

لدينا عنوانان مهمان في الرام:


حالة ماريو (المأمور) هو $00ED

هذا العنوان قيمه الممكنة (بالتجربة) هي:

00: صغير، 01: كبير، 02: نار، 03: ريشة،

04: ضفدع، 05: تانوكي، 06: مطرقة.


حالة ماريو (الآمر، عندما نغير البرمجة أو نعمل كود غش نهتم بهذا) هو $0578

هذا العنوان بالتجربة عند وضع قيمة فيه غير 00 يتغير شكل ماريو فورا.

لكن هذا ليس مهما جدا بالنسبة لنا الآن.

هذا العنوان يؤثر على $00ED و يرسل له قيمته مطروحا منها واحد.

أي أن القيم الممكنة هي:

01: صغير، 02: كبير، 03: نار، 04: ريشة،

05: ضفدع، 06: تانوكي، 07: مطرقة.


سطور البرمجة التي وجدناها الآن هي المسؤول الأصلي عن تغيير شكل ماريو،

في حال ما أكل الفطر الأحمر (الذي يجعله يصبح كبيرا، أي أنه يرسل صوب $0578 القيمة 02)

لكن لو أردنا تغيير تصرف اللعبة أثناء أكل الفطر الأحمر،

نستطيع عمل عديد الأشياء المثيرة بهذه القيمة.


لو وضعنا بدل LDA #$02 التعليمة LDA #$07

سيتخذ ماريو شكل المطرقة في كل مرة يأكل فيها الفطر الأحمر.

أولا، ننتبه قليلا أمام التعليمة التي فيها LDA #$02

سنجد على يسارها العنوان الذي فيه هذه التعليمة.


نستطيع إلقاء نظرة على ذلك العنوان و تغيير تلك التعليمة ببرنامج الهيكس،

لكن هذه المرة بما أن FCEUX لديه دعم متطور لهذه العملية سنستخدمه هو.



التعليمة المستهدفة هي LDA #$02 (و نريدها أن تصبح LDA #$07)

و هي تكتب بالهيكس A9 02 (و نريدها أن تصبح A9 07)

و هي موجودة في العنوان 00:A8A0


نذهب إلى Rom Patcher و هو معدل برمجة الروم على المباشر.

ستظهر لنا هذه النافذة اللطيفة.

نكتب في أول خانة العنوان المطلوب (بدون النقطتين) ثم نضغط Edit This Offset

و سيكون 00A89E ... في العنوان كما تراه اللعبة.


في الواقع في الروم في حالة النيس،

هناك ترويسة 10 بايتات في الأول خارجة من الحساب،

و أول بايت بعدها عنوانه في اللعبة ليس 0000 بل 8000

لسبب ما يخص نوعية تخطيط ذاكرة كارت الماريو 3 بالذات.

(أعتذر على عدم الخوض في هذه التفاصيل في درس المؤشرات)


لحسن حظنا المحاكي قام بتحويل العنوان و تجنيبنا وجع الرأس

لمن يفضل أن يذهب لبرنامج الهيكس و يغيره بنفسه، و هو هنا 28AE.

لكننا سنغير الروم هنا في هذه النافذة.



في هذا العنوان توجد بالفعل البايتات A9 02

التي قام المحاكي لأجل راحتنا بعمل تفكيك أسمبلي Disassembly لها

ليعطينا إسم التعليمة بشكل أوضح بقليل، و هو LDA #$02


تحت Patch Data علينا أن نكتب بايتات جديدة ستعوض نظيرتها انطلاقا من هذا العنوان.

يكفي أن نكتب A9 07 لتعويض A9 02.

نضغط Apply.

سيقع تسجيل هذا التغيير في داخل الروم...

لا داعي للقلق على ملف الروم الذي لدينا.

ما تغير كان الروم المخزنة في ذاكرة FCEUX المؤقتة، و عند اغلاق المحاكي لن تتأثر الروم الأصلية.

لكن لو أردنا الحفاظ على تغييراتنا في الملف الأصلي نضغط Save Rom File.


نعود الآن للعبة و نجرب أكل أي فطر أحمر:



تغييراتنا نجحت!

يمكننا عمل نقطة تعطيل على العنوان 0578

لكشف و تغيير أجزاء أخرى من البرمجة تتعلق بحالة ماريو،

مثل "ماذا يحصل عند أكله للريشة"، "ماذا يحصل عندما يصاب"، "بأي شكل يبدأ اللعبة" و أمور من هذا النوع.

يمكننا العبث كيفما أردنا بهذا العنوان.

أقترح عليكم تجربة إعادة هذه العملية، لكن مع نسخة السوبر نيس من هذه اللعبة،

و ذلك للتعود على الطريقة. بإمكانكم تجربة ألعاب أخرى أيضا!


و الآن صرنا نعرف أشياء جديدة،

مثل استخدام خاصية التمشيط للوصول إلى أسطر البرمجة التي نريدها،

إضافة إلى بعض التعليمات الجديدة مثل توائم شريرة من تعليمات رأيناها سابقا مع السجل A، لكن تخص السجلات X و Y.


كما إعترضتنا تعليمات غامضة قلنا عنها أنها تعليمات القفز التي تسمح بتنفيذ البرنامج في غير ترتيب كتابته.

إضافة إلى تعليمة AND الغامضة التي سمعنا أنها من التعليمات "المنطقية".

تلك النواحي الغامضة ستكون محل حديث الدروس القادمة ان شاء الله.


... لكن هناك أمور لم نوضحها بعد.


ما معنى 8 بت؟

و ما هذه الأشياء أمام P في تقرير التمشيط (و هي بالتأكيد مهمة جدا)؟



و كيف نفعل لو أردنا مثلا اضافة 20 لقيمة ما؟

هل نكتب INC اثنتين و ثلاثين مرة؟ (20 بالست عشري تعادل عندنا 32... 2×16+0×1)

سنحاول الإجابة عن هذا في بقية هذا الدرس.


في باب الحديث عن "ال 8 بت" و "ال 16 بت" الذي يتحدثون عنه


قلنا أن النيس جهاز من نوع "8 بت" (أو بالانكليزي 8-bit) أليس كذلك؟

هناك سبب وجيه لهذه التسمية،

و في الواقع سيكون موضوعنا الآن.


ما معنى البت bit و البايت byte ؟

البت هو إما صفر أو واحد.

8 بتات تعطينا البايت.


لو نتذكر مثال فطر ماريو الذي كنا نتحدث عنه الآن،

فإن القيمة التي أرسلناها عبر السجلات A و X و Y كان طولها 1 بايت، أي 8 بت.

في حالة معالج النيس، القيم التي ترسل عبر هذه السجلات هي دائما 8 بت.

لذلك نقول عن جهاز النيس أنه من أجهزة ال 8 بت.


بالنسبة للسوبر نيس، فالأمر مختلف.

من إسم نوع الأسمبلي 65c816 نستطيع أن نعرف أنه من عائلة سيموس 65 مثل النيس،

لكن لديه وضعان يستطيع التغيير بينهما: 8 بت (1 بايت) و 16 بت (2 بايت).

لذلك نقول عن جهاز السوبر نيس أنه من أجهزة ال 16 بت.


(الجيم بوي أدفانس، رغم كونه شبيها من ناحية صور الألعاب ظاهريا للسوبر نيس،

فإن القيم التي يرسلها إلى السجلات (التي إسمها ليس AXY بل r0,r1,r2, الخ)

طولها 4 بايتات دائما. لذلك نقول عنه أنه جهاز من أجهزة ال 32 بت – مثله مثل البلاي ستايشن 1 مثلا.

التسميات 8 بت و 16 بت و 32 بت لا علاقة لها بجودة و نوع الصور كما ترون.)


عندما يكون السجل A في وضع 8 بت،

فإن التعليمات المتعلقة به (أي هنا LDA و STA) تستقبل و ترسل قيمة طولها... 1 بايت (8 بت).

أي مثل المثال الذي في الأعلى، و الأمور على ما يرام.


لكن لو كان السجل A في وضع 16 بت، فتلك القيمة سيكون طولها ... 2 بايت (16 بت).

نأخذ المثال السابق. لو كتبنا هذا و نحن في وضع 16 بت:


LDA #$0007

STA $0DBE


ستكون النتيجة:

$0DBE = 07

$0DBF = 00

التعليمات التي كتبناها أثرت على 2 بايت.


على فكرة لو نظرنا في برنامج الهيكس في الرام في العنوان 0DBE سنجد 0700

و هي 0007 مكتوبة بالطرف الأصغر Low Endian (اي أن البايتات مكتوبة في الترتيب العكسي).

السبب هو أن السوبر نيس هو جهاز يعمل بالطرف الأصغر.


التعليمات التي كتبناها أثرت على 2 بايت.

لذلك وجب أن ننتبه جيدا على وضع السجل (8 أو 16 بت)

لو كنا لا نريد أن تحصل أضرار جانبية غير مقصودة للبايتات المجاورة.

أو بالأحرى، لأن المعالج لو حاول كتابة بايت واحد و السجل في وضع 16 بت

أو حاول كتابة بايتين اثنين و السجل في وضع 8 بت

سوف ينهار في تلك اللحظة في 99% من الحالات و تخرب اللعبة.


لكن...

كيف نعرف وضع السجل؟

هناك شيمات المعالج Processor Flags.


هذه الشيمات في حالة السوبر نيس هي 8 بتات، كل واحد منها إما صفر أو واحد.

للتبسيط، ترمز لها المحاكيات بسلسلة 8 حروف: nvmxdizc

(مع زيادة شيمة تاسعة e تأمر السوبر نيس أن يتصرف مثل النيس... غير مستعملة كثيرا)

كل حرف يشير لشيمة.

الحرف الصغير يعني أن الشيمة لا تعمل (صفر).

الحرف الكبير يعني أن الشيمة تعمل (واحد).


على سبيل المثال، ما يهمنا هنا هو:

الشيمة m = شيمة المراكم A. و لديها حالتان:

0 = A في وضع 16 بت

1 = A في وضع 8 بت

الشيمة x = شيمة سجل الدليل X و Y.

0 = X و Y في وضع 16 بت

1 = X و Y في وضع 8 بت


بقية الشيمات لديها معاني أخرى لكن لن نراها الآن.


لو نعود مثلا لمثالنا في الدرس الفارط من لعبة السوبر ماريو وورلد:



سنرى أن البرنامج قد أعطانا أمام تعليمة البرمجة معلومات قيمة نحتاجها،

ليس فقط حول التعليمة،

بل أيضا قيمة السجلات AXY

و الأهم من هذا، حالة هذه السجلات (أمام P و هي اختصار Processor Flags).

نلاحظ أن M و X كتبا بالحروف الكبيرة، هذا إذن يعني أنهما يحتويان القيمة 1.

بما معناه أن:


بالطبع هناك تعليمات خاصة لتغيير شيمات المعالج.

REP يكتب 0 نحو البتات المختارة

SEP يكتب 1 نحو البتات المختارة


في حالة السوبر نيس، الشيمات هكذا:

nvmxdizc

نفترض أننا مهتمون بالشيمات m و x في المثال أعلاه.

كيف نختارها؟ نكتب ببساطة هذا العدد بالنظام الثنائي:

00110000

و نحوله للنظام الست عشري:

30

إذن لإختيار هذه الشيمتين نستخدم القيمة 30


لو أخذنا المثال فوق و أردنا من اللعبة أن تتحول للعمل بوضع 8 بت للسجلات AXY

أي بعبارة أخرى أن نكتب 0 للشيمات m و x

نستخدم التعليمة:

REP #$30

و بعد هذا، لو غيرنا رأينا و أردنا أن تعود اللعبة لوضع 16 بت لجميع هذه السجلات AXY

أي أن نكتب 0 للشيمات m و x

نستخدم التعليمة:

SEP #$30


هناك شيمات أخرى و تفعل أمورا أخرى،

لكنها لم تتأثر على الإطلاق بما فعلناه قبل قليل.

سنتحدث عنها فقط عندما نضطر للحديث عنها.

و على فكرة، في حالة النيس فإن الشيمات m و x و d يتم تجاهلها تماما.


سنتحدث الآن عن أمر آخر.

هل توجد حلول بديلة ل INC DEC؟


ثانيا – كيف نجمع و نطرح


نحن رأينا في السابق التعليمات INC التي تزيد واحد، و DEC التي تنقص واحد.

و هناك نسخ منها تخص السجلات X و Y و هي INX INY DEX DEY.


لكن ماذا لو أردنا أن نضيف (أو نطرح) القيمة 2، أو 35 أو قيمة أخرى ليست واحد؟

هناك تعليمات تحل لنا هذا الإشكال.


التعليمة ADC تقوم بإضافة قيمة إلى سجل المراكم A.

التعليمة SBC تقوم بطرح قيمة معينة من سجل المراكم A.


هاتان التعليمتان تعملان على المراكم فقط.

نفترض مثلا أننا نريد زيادة القيمة 06 (بالست عشري) للقيمة التي بداخل العنوان $01EC

أي أنه لو كان لدينا في العنوان $01EC القيمة 04 (بالست عشري)،

سيصبح لنا بعد هذه الزيادة في هذا العنوان القيمة 0A (أي 10 بالست عشري).


كي نقوم بهذه الزيادة:


التعليمات المستعملة هي:


LDA $01EC

CLC

ADC #$04

STA $01EC


شيمة "المحتفظ به للجمع" يجب أن تكون 0 عند الزيادة.

لو كانت 1 في مثالنا هذا، لأضافت اللعبة 05 بدل 04.


كي نقوم بنفس الشيء لكن بإنقاص 04 عوض زيادتها،

نفعل نفس الشيء، لكن عند الطرح تكون الشيمة "المحتفظ به للجمع" تأخذ القيمة واحد.


التعليمات المستخدمة هي:


LDA $01EC

SEC

SBC #$04

STA $01EC


شيمة "المحتفظ به للجمع" يجب أن تكون 1 عند النقصان.

لو كانت 0 في مثالنا هذا، لأنقصت اللعبة 05 بدل 04.


متى نستعمل CLC؟ مع تعليمة الجمع ADC.

متى نستعمل SEC؟ مع تعليمة الطرح SBC.



كان بإمكاننا إستخدام التعليمة SEP #$01 لوضع صفر في شيمة "المحتفظ به للجمع" (و نظيرتها REP تضع 1)،

لكن بالنسبة لهذه الشيمة بالذات هناك تعليمات أقصر (و بالتالي محبذة) تفعل نفس الشيء،

و هي CLC التي تضع صفر في شيمة "المحتفظ به للجمع" (و SEC تضع فيها 1).


شيم المعالج التي تؤثر على الجمع و الطرح


لو نعود لمثالنا أعلاه، ماذا تكون النتيجة في حالة الزيادة؟

العنوان فيه 06

و لو زدنا له 04

تصبح لدينا عشرة، لكنها تكتب 0A بما أن حساباتنا في النظام الست عشري.


لكن... لو كانت شيمة "النظام العشري" و هي d تعمل و قيمتها واحد،

فإن سير عملية الجمع و الطرح بالتعليمتين ADC SBC يتغير.

فتصبح العشرة التي ذكرناها تكتب 10 في الرام.

كما أن الحدود لن تكون بين 00 و FF بل بين 00 و 99.


شيمة النظام العشري أيضا لديها تعليمات مختصرة لتغييرها بسرعة،

و هي CLD التي تضع 0 في الشيمة و تقفل النظام العشري (العمل العادي للعبة)،

و SED التي تضع 1 في الشيمة و تبدأ في عمل النظام العشري.


شيمة النظام العشري حصرية للسوبر نيس.

معالجات 6502 تحتوي النظام العشري أيضا، لكن بخل النينتندو تسبب في حذف النظام العشري من النيس.

لكن لم يحذف من النيس التعليمات CLD SED و بقيت شيمة d لا حول لها و لا قوة في المعالج.


على فكرة، ماذا يحصل عندما نضيف مثلا لبايت واحد كمية تجعله يتجاوز FF

أو ننقص منه كمية تجعله يصبح أقل من 00 ؟

لو إستعملنا التعليمات ADC (الجمع) و SBC (الطرح)،

فستعود القيمة إلى الطرف المقابل،

لو كنا قريبين من FF و تجاوزنا الحد من تلك الجهة ستعود إلى جهة 00 و العكس بالعكس.

يعني أنه لو أضفنا 02 إلى FF سنتحصل على 01.

و لو طرحنا 03 من 01 سنتحصل على FE.


هذا يسمى الطفح overflow.

لو استخدمنا ADC SBC و قمنا بعملية جمع أو طرح تجاوزت الحد،

سيكتب واحد نحو شيمة "الطفح" المرموز لها بالحرف v

يمكننا تنظيف هذه الشيمة و إعادتها للصفر بالطرق المذكورة أعلاه للشيم، أو بإستخدام التعليمة CLV.


ما الغاية من وجود هذه الشيمة أصلا؟

الجواب هو، أن هذه الشيمة تسمح لنا بمعرفة هل عملية الجمع التي قمنا بها سليمة أم لا.

لأنه لو صار هناك طفح، فنتيجة عملية الجمع و الطرح بالتأكيد ستكون مغلوطة تماما.

هناك تعليمات خاصة تتثبت من هذه الشيمة و تسمح لنا بالإحتياط من الحسابات المغلوطة...

لكن تلك التعليمات موضوع درسنا القادم.


في النهاية...

ماذا عرفنا عن أسمبلي السوبر نيس لحد الآن؟


التعليمات:


NOP

لا تفعل شيئا.

LDA

تضع قيمة ما أمامها (قيمة، أو عنوان به قيمة) في سجل المراكم A.

هناك تعليمات شبيهة LDX و LDY لكن تعمل على السجلين X Y.

تستعمل LDA و STA معا كلما كانت عندنا قيمة أردنا وضعها في عنوان.

STA

تضع القيمة في سجل المراكم A في العنوان الذي أمامها.

هناك تعليمات شبيهة STX و STY لكن تعمل على السجلين X Y.

TAX

تنسخ محتوى سجل المراكم A في السجل X.

هناك تعليمات شبيهة أخرى لنسخ محتويات السجلات بين بعضها،

مثل TAX TAY TXA TXY TYA TYX

INC

تضيف واحد للقيمة بما أمامها (عنوان به قيمة، مراكم A).

هناك INX INY و تعمل على السجلين X Y.

DEC

تنقص واحد من القيمة بما أمامها (عنوان به قيمة، مراكم A).

هناك DEX DEY و تعمل على السجلين X Y.

ADC

تزيد القيمة التي أمامها لمحتوى السجل A.

و فقط السجل A. لو كنا نريد زيادة قيمة عنوان مثلا،

ننسخه للسجل ب LDA و نعمل عليه الجمع، ثم نسترده ب STA.

عند استعمالها، نضع معها ايضا CLC.

SBC

تطرح القيمة التي أمامها من محتوى السجل A. (و فقط السجل A)

عند استعمالها، نضع معها أيضا SBC.


بعض التعليمات الحصرية للسوبر نيس.

هناك ثلاثة لم نرها بتاتا من قبل، لكنها ليست صعبة.

أضعها هنا كي لو اعترضتنا في لعبة ما نعرف معناها.


STZ

تضع صفر في قيمة العنوان الذي أمامها.

و هي اختصار على سطر واحد بدل اثنين

يجنبنا عمل LDA #$00 ثم STA $address (حيث address هو العنوان).

MVN

نسخ من البداية للنهاية لكميات كبيرة من البيانات.

و هي اختصار في السوبر نيس يجنبنا عمل كم هائل من تعليمات LDA و STA عندما نريد نقل كم كبير من البيانات من مكان لآخر.

التعليمة شكل كتابتها MVN 01 23

و تقرأ محتويات السجلات XAY معا، تحتاج أن يكون وضعها كلها 16 بت.

لو كان A = 0020، X = A200، Y = 8000


تقوم التعليمة أعلاه ب:

نسخ 0x0020 بايت (20 بالست عشري تعني 32 بايت)

من البيانات التي تبدأ في العنوان $01:A200 (المكتوب بعد X)

و لصقها انطلاقا من العنوان $23:8000 (المكتوب بعد Y)


يبدأ A في النقصان كلما نسخ المزيد، و عندما يصل 0 يتوقف النسخ.

MVP

عملها و شروطها مثل MVN بالضبط،

لكنها تبدأ النسخ من النهاية إلى البداية،

و العناوين التي تأخذها من X و Y هي عناوين آخر نقطة في البيانات.

XBA

مخصصة عندما يكون سجل المراكم A في وضع 16 بت

أي يكتب على 2 بايت: يقوم بقلب هاذين البايتين.

مثلا لو كان لدينا في المراكم 1234 تصبح 3412.



و الآن نتحدث عن الشيمات.

بالإضافة إلى السجلات A X Y، (و المكدس، لكن سنتحدث عن أمره لاحقا)،

هناك في جهاز السوبر نيس ما يسمى بشيمات المعالج و هي إما صفر أو واحد.


هناك 8 شيمات في حالة السوبر نيس: nvmxdizc

اضافة إلى شيمة اسمها e.

هذه الشيم نجدها في ملفات التمشيط و على الأقل وجب أن تكون عندنا فكرة صغيرة عنها.


عندما تكون الشيمة صفرا (clear) يكتب الحرف بالصغير،

و عندما تكون واحدا (set) يكتب الحرف بالكبير.

نستخدم التعليمات REP (تصفير الشيمات المختارة) و SEP (وضع 1 للشيمات المختارة) لتغييرها،

لكن توجد تعليمات مختصرة أكثر تفعل نفس الشيء.


n

شيمة "السلبي" (negative).

متى تعتبر قيمة سلبية بالنسبة له؟

لو كانت بين 80 و FF (وضع 8 بت)،

أو بين 8000 و FFFF (وضع 16 بت).

الشيمة تتغير حسب كون آخر ناتج عملية حسابية سلبيا أم لا.

0 = غير سالب

1 = سالب

v

شيمة "الطفح" (overflow).

لو تجاوزت عملية جمع أو طرح ب ADC SBC الحد من جهة 00 أو FF،

و عادت من الجهة الأخرى (و ستكون في تلك الحالة مغلوطة)،

تتغير هذه الشيمة.

0 = عادي

1 = حصل طفح


CLV تجعلها 0

m

شيمة "طول سجل المراكم A".

هل طوله 16 بت (2 بايت) أم 8 بت (1 بايت).

0 = 16 بت

1 = 8 بت

x

شيمة "طول سجل الدليل X و Y".

هل طوله 16 بت (2 بايت) أم 8 بت (1 بايت).

تؤثر على السجلين معا.

0 = 16 بت

1 = 8 بت

d

شيمة "النظام العشري" (decimal mode)

هل يتم كتابة نتائج عمليات الجمع و الطرح ADC SBC

بالنظام الست عشري، ام بالنظام العشري، في البايتات.

مثلا بالنظام العشري 09+01 نتيجتها تكتب للبايت 10

و بالنظام العادي الست عشري النتيجة تكتب للبايت 0A.

0= عادي (ست عشري)

1= عشري


CLD تجعلها 0

SED تجعلها 1

i

شيمة IRQ.

(شرحها ليس مجاله الآن).

0=IRQ يعمل 1=لا يعمل

CLI تجعلها 0

SEI تجعلها 1

z

شيمة الصفر.

هذه الشيمة تصبح 1 عندما يكون آخر ناتج عملية حسابية هو صفر.

0 = عادي

1 = آخر ناتج كان صفرا

c

شيمة "المحتفظ به عند الجمع" (carry)

لديها أهمية في عمليات الجمع و الضرب.

يرجى العودة لفقرة الجمع و لاحقا فقرة الضرب لأخذ فكرة عنها.

CLC تجعلها 0

SEC تجعلها 1


في حالة النيس، لا وجود للشيمات m و x،

أما الشيمة d التي بقيت (و بقي معها تعليمات تغييرها) فقد تم تعطيل صلاحياتها.


السوبر نيس لديها شيمة اسمها e (شيمة "المحاكي" emulation mode)

و هذه من القنابل الموقوتة في الجهاز... لخطورتها تم جعل تغييرها صعب المنال،

لا يمكن تغييرها إلا بتعليمة واحدة و هي XCE التي تقوم بمبادلة محتويات الشيمة c و الشيمة e بين بعضهما.

هذه الشيمة تجبر جهاز السوبر نيس على التصرف مثل معالج النيس تقريبا.

ما يعني أن كل شيء صار 8 بت، و الشيم mxd لم تعد متوفرة كما نعرفها

(لكن x يتم استخدامها لوظيفة أخرى و تصبح شيمة BRK ... و شرحها أيضا ليس مجاله الآن.)