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

الدرس الثالث: أسمبلي 65c816 – كيف يتفرع البرنامج (1)



طبعا أدركتم مما سبق أن البرمجة في جميع الأجهزة القديمة (و حتى برامج الكمبيوتر) تكتب بإستعمال الهيكس.

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

لكن هذا... ماذا لو كانت أهدافنا في تغيير البرمجة ليست بمثل البساطة و أردنا إضافة الكثير من التعليمات؟

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


طول التعليمات

أولا... سنتحدث عن مسألة طول التعليمات.

أظننا تحدثنا عن ذلك في السابق لكن لا ضير من التذكير.


التعليمات عادة متكونة من جزئين:

كود العملية نفسه (الأوبكود opcode): و هو يحدد إسم التعليمة.

حجج العملية: الأشياء التي ستنفذ عليها تعليمة البرمجة.


على سبيل المثال، في أسمبلي النيس، لو نأخذ مثلا التعليمة: LDA $0120

التي ستكتب بالهيكس هكذا AD 20 01

الجزء AD هو كود التعليمة. و هو يدل على طبيعة التعليمة نفسها و أنها LDA ("ضع في المراكم").

الجزء 20 01 هو حجة التعليمة الوحيدة، و هو عنوان الرام $0120 مكتوبا بترميز الطرف الأصغر.


كما نرى هنا، فهذه التعليمة كتبت بإستخدام 3 بايتات.

من المهم جدا أن نحسب كم من بايت نستخدم g ;jhfm ;g jugdlm.

أحيانا تكون التعليمة بدون حجج، مثل تعليمة NOP ("لا شيء") التي تكتب ببايت واحد و هو EA.

لو أردنا تعويض التعليمة السابقة LDA بـ "لا شيء" فسنعوضها ب NOP ثلاثة مرات.


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

(مثل MUL "ضرب عددين" في ARM7 (الأدفانس) التي تقبل حجتين)

و في بعض الأحيان صدرت في بعض الأجهزة الجديدة أنواع معالجات الأسمبلي فيها يصل حتى 3 حجج للتعليمة

(مثل معالج ال3DS (PICA2000 GPU) الذي فيه MAD "اضرب عددين و اجمع مع عدد ثالث").


لكن، طلبا للبساطة، سنكتفي في الوقت الحالي بأسمبلي النيس و السوبر نيس،

نظرا أن جميع تعليماته تقبل إما صفر حجة أو حجة واحدة.


عدا هذا...


أود العودة بخصوص LDA للحظة صغيرة.

لنلق نظرة صغيرة على الجزء بعد كود التعليمة، و هو الحجة.


لعلكم تتساءلون من أين آتي بمعلوماتي حول LDA هذه و التعليمات التي تحدثنا عنها في الدروس السابقة؟

كل لغة أسمبلي لديها وثائق بها قائمة كاملة بمختلف كودات التعليمات opcodes بها.

بإمكاننا مثلا أن نبحث عن 65c816 opcodes في أي محرك بحث لإيجاد القائمة التي تخص جهاز السوبر نيس...

أرفقت بهذا الدرس نسخة من هذه القائمة.

سأقتطف منه الجزء الذي يخص LDA بالذات:


Assembler Example

HEX

Addressing Mode

02

C02

816

Bytes

Cycles

LDA Load Accumulator from Memory [Flags affected: n,z]

LDA (dp,X)

A1

DP Indexed Indirect,X

x

x

x

2

6

LDA sr,S

A3

Stack Relative



x

2

4

LDA dp

A5

Direct Page

x

x

x

2

3

LDA [dp]

A7

DP Indirect Long



x

2

6

LDA #const

A9

Immediate

x

x

x

2

2

LDA addr

AD

Absolute

x

x

x

3

4

LDA long

AF

Absolute Long



x

4

5

LDA (dp),Y

B1

DP Indirect Indexed, Y

x

x

x

2

5

LDA (dp)

B2

DP Indirect


x

x

2

5

LDA (sr,S),Y

B3

SR Indirect Indexed,Y



x

2

7

LDA dp,X

B5

DP Indexed,X

x

x

x

2

4

LDA [dp],Y

B7

DP Indirect Long Indexed, Y



x

2

6

LDA addr,Y

B9

Absolute Indexed,Y

x

x

x

3

4

LDA addr,X

BD

Absolute Indexed,X

x

x

x

3

4

LDA long,X

BF

Absolute Long Indexed,X



x

4

5


هذا الجدول قد يبدو مرعبا قليلا، لكن محتواه ليس بالصعب.

كانت فيه أيضا أرقام صغيرة تشير نحو هوامش بها ملاحظات، لكني حذفتها من هنا كي يبدو الجدول جميلا.


أول شيء، هو يخبرنا بإختصار إسم التعليمة LDA.

و يقول لنا ماذا تفعل: تشحن في المراكم شيئا أتى من الذاكرة (الرام).

و يقول لنا أي شيم معالج Processor Flags تؤثر عليه هذه التعليمة:

شيمة الصفر z (لو كان المشحون صفرا)،

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


و الآن نمر للأمر الذي يهمنا.

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

رأينا في السابق مثلا أن LDA قد تقرأ القيمة مباشرة (LDA #$02)

أو قد تأخذها من عنوان رام (LDA $01EB) و طبعا هناك استخدامات أخرى...


هذا الجدول الذي سيساعدنا كثيرا يخبرنا:

تحت HEX: بطريقة كتابة رمز التعليمة بالهيكس. (و هذا يتغير حسب الإستعمال!)

تحت Bytes: كم سيكون طول سطر التعليمة الكامل (باحتساب رمز التعليمة و الحجة).

تحت Cycles: الزمن الذي سيستغرقه تنفيذ هذه التعليمة

(و هذا قد يكون مهما في حالات معينة لكن ليس الآن)

و هل هذه الطريقة لكتابة التعليمة موجودة في النيس 6502 (02)

أو PC-Engine أي C6502 (C02) أو السوبر نيس 65c816 (816).


و هناك طريقة العنوان Addressing Mode...

و كما يظهر لكم في هذا الجدول فهناك منها الكثير، خاصة في حالة LDA.


للتبسيط، سأتجنب الحديث عن التي تستعمل الدليل (Indexed)،

التي تستعمل العنوان غير المباشر (Indirect)،

و التي تستعمل المكدس (Stack).

و بذلك أكون تملصت من شرح أغلب هذا الجدول و أتجنب الإكثار عليكم.


ما يهمنا أساسا نحن المبتدئين هو طرق العنوان هذه:


الفوري Immediate:

سنقول للبرنامج مباشرة ما هي القيمة التي يأخذها.

ستكون كتابة التعليمة هذا LDA #$02

و حسب الجدول، فهذا النوع من LDA يكتب بالهيكس A9.

يعني أن التعليمة تكتب بالهيكس A9 02 باستخدام 2 بايت (تماما كما قال الجدول أعلاه).

(طبعا نفترض أننا في النيس، أو في السوبر نيس في وضع 8 بت.)


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

من أين ستأتي بهذا "الشيء"؟

هو أمامها مكتوب في التعليمة نفسها (02) و تستطيع أخذه من هناك على الفور!

لذلك نسميه "فوري".


المطلق Absolute:

هذا مختلف قليلا.

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

سنأخذ حالة جهاز السوبر نيس:

عنوان أي مكان في الذاكرة يكتب من 3 بايتات (24 بت) هكذا: $AABBCC


في حالة الصفحة المباشرة Direct Page، يكتب من ذلك العنوان $CC (1 بايت).

طول التعليمة (باضافة الكود، و هو A5) هو 2 بايت.

في حالة العنوان المطلق Absolute، يكتب من ذلك العنوان: $BBCC (2 بايت).

طول التعليمة (باضافة الكود، و هو AD) هو 3 بايت.

في حالة العنوان الطويل Long، يكتب من ذلك العنوان $AABBCC (3 بايت).

طول التعليمة (باضافة الكود، و هو AF) هو 4 بايت.


طبعا تتذكرون جيدا أن...

الجزء AA إسمه "رقم الكتلة" Bank و تحدثنا عنه في درس المؤشرات.

الجزء CC إسمه "الصفحة المباشرة" Direct Page و لا أدري لماذا إسمه كذلك.


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

عناوين النيس كلها تكتب ب 2 بايت أي بين $0000 و $FFFF

و في الحالات العديدة التي لا يكفي فيها هذا لمحتويات لعبة كاملة،

تكون من اللعبة عدة كتل حجمها $FFFF تبادل بينها بطريقة مبادلة الكتل Bank switching

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


لعلكم تتساءلون.

كيف تستطيع لعبة أن تستنتج من عنوان الصفحة المباشرة $CC

أن المقصود هو العنوان الكامل $AABBCC و الحال أنه لم يكتب كاملا في التعليمة؟


الجواب هو أن الجهاز يحتفظ بسجلين يسمحان له بإكمال ذلك العنوان، و هما:

سجل الصفحة المباشرة Direct Page Register (و به نسخة من BBCC سابقة)

سجل رقم الكتلة Data Bank Register (حصري للسوبر نيس، به نسخة من AA)


أخيرا و بخصوص السوبر نيس بالذات دون غيره،

فوضع الصفحة المباشرة يجبر رقم الكتلة AA على أخذ القيمة 00 دائما.


بقيت هناك 3 أوضاع أخرى قلنا أننا سنتجنب الحديث عنها الآن، و في الواقع سأتركها لآخر هذا الدرس.

على أية حال، أدعوكم لإستكشاف قائمة الأوبكودات بأنفسكم.

و الآن نكون عرفنا كيف نعرف طول كل تعليمة بعدد البايتات.


...


نعود الآن إلى مشكلتنا الأصلية.

بإستعمال قائمة كودات التحكم للسوبر نيس كما رأينا أعلاه (بالأخص خانة Bytes)،

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


لحد الآن نحن ما قمنا به هو مجرد تعويض تعليمات بتعليمات بنفس طولها بالضبط.

لو تتذكرون مثال السوبر ماريو 3، لتغيير نوع لباس ماريو غيرنا فقط LDA #$02 إلى LDA #$05

أي فقط الحجة، و قد كانت التعليمة الجديدة عندها بالضبط نفس عدد البايتات (2) في التعليمة الأصلية.


أما في مثال السوبر ماريو وورلد عندما أردنا أن يزيد عدد حيوات ماريو كلما مات،

فقد غيرنا التعليمة INC $XXXX إلى DEC $XXXX (حيث XXXX عنوان حيوات ماريو)

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


ماذا نفعل لو كانت برمجتنا الجديدة أطول من القديمة؟

يعني لو كان عدد بايتات البرمجة الأصلية لا يكفي للتعليمات الجديدة التي نود إضافتها؟

حان الوقت لعمل بعض الأمثلة لتجربة ذلك.


مثال 1 – عكس إتجاه سريان النص ليصبح من اليمين إلى اليسار،

لعبة Cowboy Kid (NES) نموذجا


بهذه السرعة سنبدأ في عكس إتجاه سريان النصوص؟

ليس هناك داع للقلق، نظرا أن هناك الكثير مازال يجب الحديث عنه.


على أية حال، الموضوع كبير جدا و لذلك لن يكفي مجرد درس وحيد له، سأشرح الآن بالضبط لماذا.


الروم (ROM): نسخة اللعبة التي لا تتغير و منها يأتي كل شيء (البرمجة، الصور، النص...)

في حالة الأجهزة بالأقراص لا نقول روم بل إيزو (ISO).


عند تشغيل اللعبة، هناك الكثير من الأمور تتغير.

هذه الأمور المتغيرة موجودة في الرام (RAM). هذه الرام مؤقتة و محتواها يندثر مع إغلاق الجهاز.

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

أو كل النصوص و الصور التي تستخرج من الروم أو الإيزو.

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


هناك أنواع خاصة من الرام.

مثلا هناك ذاكرة ملف حفظ اللعبة (SRAM) حيث تسجل بعض الألعاب تقدمك في اللعبة لتعود لها لاحقا.

و هناك نوع مهم جدا و هو الذاكرة البصرية (VRAM) حيث تخزن مسبقا أي صورة تستعمل لإظهارها على الشاشة.


...


كل ما يظهر على الشاشة هو صور.


في حالة الأجهزة ثنائية الأبعاد،

هناك نوعان من الصور:


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

لكن في المقابل هناك عدد أقصى منها لو تجاوزناه تبدأ في الإختفاء.

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

لا يمكننا وضع تايلات الصور أينما نشاء على الشاشة. بل نحن متقيدون بخانات شبكة تغطي كامل الشاشة:



يمكنكم تجربة اخفاء و اظهار هذه الخلفيات و السبرايتات للتعرف عليها

في عديد المحاكيات الموجودة، مثلا:


في حالة عديد الأجهزة، هناك طبقات Layers من الخلفيات.

السبرايت و كل هذه الطبقات تظهر فوق بعضها بترتيب معين.

هناك ما يسمى الأولوية يؤثر على هذا الترتيب و أحيانا كثيرة يغيره.

مثلا في حالة السوبر نيس، الخلفية BG1 لو كانت في وضع Mode-7 الذي تكبر و تدور فيه،

فلها أولوية على جميع الخلفيات الأخرى و فقط السبرايت يظهر فوقها.


مثلا لو نأخذ الصورة التالية:


فهي مكونة من الخلفيات و السبرايت التالية، و كلها رسمت فوق لون واحد...

قام مبدعو فرع كونامي كوبي (الذين غادروا الشركة و أسسوا Good-Feel) بعمل حيلة تقنية

كي يظهر ذلك "اللون الواحد" في شكل تدرج ألوان، و هي تغيير ذلك اللون أثناء رسم الجهاز لسطور الشاشة.


السبرايت (Sprites)

الخلفية 1 (BG1)

الخلفية 2 (BG2)

الخلفية 3 (BG3)


في كل هذه الخلفيات، فالبيكسل الذي يستعمل اللون 0 في جدول الألوان (الباليت Palette)

هو الذي يكون عادة اللون الشفاف، و يكون فراغا و يظهر ما خلفه.

هناك نوع آخر من الشفافية اسمه شفافية ألفا alpha-bending

يشمل كامل الصورة و ليس فقط اللون الشفاف، و يستطيع أن يكون جزئيا.


هذه الصور تم شحنها من الروم إلى الذاكرة البصرية (VRAM/PPU/VPU…) قبل ذلك و منها يؤخذ ما يظهر بالشاشة.

لو كانت تلك الصور في الأصل مضغوطة في الروم،

يتم فك ضغطها نحو الرام (مسودتنا) أولا حيث تصبح واضحة، ثم تؤخذ من هناك للذاكرة البصرية.

يمكنكم رؤية محتويات الذاكرة بالبصرية بمحاكيات مثل:

أو يمكنكم في Snes9X Geiger’s Debugger تحت Hex View استخراج محتويات ال VRAM إلى ملف و فتحه ببرنامج تايل.


مثلا لو نلقي نظرة على شاشة العنوان للعبة جزيرة المغامرة للنيس.

في الصورة الثانية، سوف نكتفي بالنظر إلى الخلفية

(أي أننا سنحجب السبرايت تحت NES/Display OBJ في المحاكي FCEUX).



الآن لو ألقينا نظرة على الذاكرة البصرية (تحت PPU Viewer)

سنجد ... هذه الكتلة من المربعات الصغيرة.

واضح أنها أجزاء من الصورة الكبيرة التي تظهر في الخلفية في شاشة العنوان.

و هي على فكرة موجودة بهذه الصفة في الروم.



كيف أمكن أن تعطينا هذه المربعات المبعثرة الصورة الكبيرة؟

هذه الصور تم تجميعها انطلاقا من قطع صغيرة، إسمها التايلات Tiles (مربعات قطع البازل).

أما طريقة تجميعها، فتسمى:

نحن في الوقت الحالي لن نرى غير خريطة التايل،

لأن أغلب النصوص تكون مرسومة على خلفيات و ليس بإستعمال السبرايت.


لنعد إلى شاشة عنوان Adventure Island للنيس.

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

يعني أن خريطة التايل ثابتة.


خرائط تايل النيس بسيطة مقارنة بغيرها من الأجهزة،

و يمكن إلقاء نظرة عليها باستخدام Name Table Viewer (هذا إسم آخر لخريطة التايل في حالة النيس) في FCEUX.

أولا لو نأخذ محتويات الذاكرة البصرية، و نرقم التايلات:



يمكننا عمل تجربة بسيطة لمعرفة خريطة التايل لشاشة العنوان هذه،

و ذلك بالذهاب إلى الروم و لزق الصورة الثانية (السوداء بترقيم التايلات)

فوق التايلات التي ستذهب للذاكرة البصرية لأجل شاشة العنوان.


لكي تكون الصورة واضحة، تركنا التايل مكان FF فارغا.

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

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



و بالفعل، يمكن لنا أن نعثر على خريطة التايل الثابتة هذه في الروم،

و ذلك بعمل بحث في برنامج هيكس بخاصية HEX Search،

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

الجميل في خرائط تايل النيس أنها غير معقدة.

كل مربع مكون للصورة الكبيرة في الصورة لديه بايت واحد،

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


وجدنا إذن التالي في الروم:

بالأحمر: السطر الأول من شاشة العنوان،

الذي فيه HUDSON’S

و النصف العلوي من كلمة ADVENTURE (نصفها السفلي في "السطر الثاني").



بهذه الطريقة نكون وجدنا خريطة التايل الثابتة في الروم و يمكننا التعديل عليها.

خرائط التايل الثابتة في الأجهزة الأخرى ليست بمثل بساطة خرائط النيس،

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

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


لكن.

هذه خريطة تايل ثابتة إستطعنا أن نجدها في الروم، لأن شاشة العنوان لا تتغير.


لكن، هناك أيضا خرائط التايل المتغيرة. على سبيل المثال لدينا:


خرائط التايل المتغيرة هذه تصنع مباشرة أثناء عمل الجهاز في الذاكرة البصرية،

يعني أننا لن نجدها في الروم.

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

لأن كل لاعب سوف يتحصل على نقاط مختلفة.

ما يجب أن نفعله هو التعديل على البرمجة (و هي في الروم) التي تصنع خريطة التايل المتغيرة هذه.


و ذلك الشرط الأساسي لتغيير إتجاه سريان النص من اليمين لليسار،

نظرا أن ذلك يدخل في عملية صنع خريطة تايل النص الذي يظهر على الشاشة.


أما الآن، لنلق نظرة عن أكثر الطرق رواجا في كتابة النص على الشاشة.

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


النوع 0-أ: النص كله صورة جاهزة


الحالات التالية مثلا ليست نصوصا،

بل هي صور جاهزة، و يجب البحث عنها و تغييرها في الروم مثل أي صورة أخرى.

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


كيف نتعرف عليه:


لا يوجد خط به كامل الحروف


النص يظهر كله دفعة واحدة، ليس كل حرف وحده

تكون الجملة الجاهزة مخزنة صورة في الروم

أحيانا يظهر نفس الحرف بالجملة عدة مرات بطريقة مختلفة


شاشات العنوان.


الكلمات المكتوبة بخطوط فريدة من نوعها لا تتكرر في بقية اللعبة (لكن بالطبع هناك استثناءات، مثلا ألعاب Advance Wars مع أن أسماء الحركات الخاصة فيها تظهر بحروف كبيرة بخط فريد من نوعه، الا أنه خط كامل و له عرض و تلك الأسماء هي نص عادي)


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

على سبيل المثال، النسخة اليابانية و الأمريكية من الزيلدا ويند ويكر جعلت الحوارات التي في نهاية اللعبة جزءا من الفيديو نفسه.

و كذلك عدد كبير من الألعاب على الساتورن و البلاي.

في الروم (العنوان 2C7934)

ضغط LZ77

البرنامج المستعمل LZ77Restructor2



شاشة عنوان ("المعركة الكبرى 3")

مضغوطة في الروم

يفك ضغطها ثم تنسخ للذاكرة البصرية

كل هذه الكلمات الصغيرة

هي في الواقع صور جاهزة.

النوع 0-ب: هناك خط يوضع بكامله في الرام،

لكن خريطة التايل جاهزة مسبقا.

يتم الرسم على خلفية.


كيف نتعرف عليه:

النص يظهر كله دفعة واحدة، ليس كل حرف وحده

الحروف متساوية العرض و عرضها من مضاعفات 8 (8، 16..)



الخط في الروم

نسخ بكامله للذاكرة البصرية VRAM

النص في الروم

بالنسبة لهذه الأسطر الأربعة

و يمكن أن نلاحظ فيه وجود الفراغات

النوع 1: الجمل ترسم على الشاشة

بتوليد خريطة تايل تكون الجمل من خط في الذاكرة البصرية.

يتم الرسم على خلفية.


الخط و النص موجودان في الروم.

يتم نسخ كامل الخط بجميع حروفه إلى الذاكرة البصرية.

مع كل جملة، تقوم اللعبة بصنع خريطة تايل تستعمل الحروف المذكورة.


كيف نتعرف عليه:

النص يظهر إما تدريجيا بالحرف بالحرف و إما دفعة واحدة

الحروف متساوية العرض و عرضها من مضاعفات 8 (8، 16..)

جميع حروف الخط موجودة في الذاكرة البصرية


مثال: الكثير من ألعاب النيس.

الخط في الروم (مضغوط)

ثم نسخ بكامله (بعد فك الضغط)

للذاكرة البصرية VRAM



النوع 2-أ: الجمل ترسم على الشاشة

بتصنيع صورة الجملة التي نحتاجها فقط.

يتم الرسم على خلفية.


الخط و النص موجودان في الروم.

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

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

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


كيف نتعرف عليه:

النص يظهر إما تدريجيا بالحرف بالحرف و إما دفعة واحدة

لو كان الخط متغير العرض

لو كان الخط ثابت العرض و ليس من مضاعفات 8 (8، 16)

فأكيد أننا مع نوع 2

هذا لا يمنع وجود ألعاب بخط ثابت العرض و تكون من نوع 2

لو كان الخط فيه المئات من الرموز

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


مثال: جل الألعاب على الأجهزة ثنائية الأبعاد بعد النيس.

في الروم


في الذاكرة البصرية

النوع 2-ب: الجمل ترسم على الشاشة

بتصنيع صورة الجملة التي نحتاجها فقط.

يتم إعداد تايلات في الرام يرسم في كل واحدة أكثر من حرف واحد.

يتم الرسم على خلفية.


المسودة في الرام ينسخ نحوها أكثر من حرف فوق بعضه، و عندما تجهز يتم نسخ كامل ذلك التايل للذاكرة البصرية.


مثل النوع 2أ لكن

هذه الطريقة تسمح برسم الحروف فوق بعضها، و تسمح بعمل الخطوط متغيرة العرض.


مثال: الألعاب اليابانية التي لا يكون فيها الداكوتن في خط الروم جزءا من الحرف نفسه

جميع الألعاب التي فيها خط متغير العرض.

في الروم: هذا الخط +

قيم عرض كل حرف.

نلاحظ أن كل حرف على الحافة اليسرى للتايل الذي به

في الذاكرة البصرية:

فقط الحروف المستعملة في الجملة.

في الرام:

هناك مسودة صغيرة رسم فيها كل مربع صغير من هذه المربعات على حدة لتجميع الحروف مع بعضها في تايل 8×8 كامل ثم نسخها للذاكرة البصرية.

مثلا هذان التايلان 2×(8×8):

النوع 3-أ: كل حرف هو قطعة سبرايت وحده

و اللعبة ترصف العديد من القطع "الحروف"


هذا النوع التعديل عليه هو الأصعب.

السبرايت يمكن أن يظهر في أي مكان من الشاشة.


كيف نتعرف عليه:

لو أخفينا كل الخلفيات و بقي النص على الشاشة.

المحاكيات بالمنقحات: النص لا يكون موجود في الخلفيات (Background/BG) بل على السبرايتات (Sprites)

الحروف قد تتحرك كثيرا عبر الشاشة (مثل Terranigma في أسماء الأماكن). هذه الحركة لا يتيحها إلا السبرايت عادة.


هناك حد أقصى للحروف على نفس الاستقامة الأفقية (عادة لا يفوق 16 في أجهزة السوبر نيس و 8 في النيس)

لذلك هذا النوع نادر الإستعمال في ألعاب النيس ما عدا النصوص العمودية لأنه لو تم بلوغ هذا الحد الأقصى تبدأ الحروف بالإختفاء.




ملاحظة: Terranigma استخدمت نصوص النوع 2أ لأغلب حواراتها ما عدا بعض الحالات منها المذكور هنا.

الخط الموجود في الروم

تم نسخ الحروف المطلوبة فقط للذاكرة البصرية و إليها تشير جداول تركيب السبرايت.

السبرايتات هي كالآتي هنا،

و حجم كل واحد 16×16 بيكسل.


لكن السبرايت يمكن أن يوضع في أي مكان على الشاشة، و هو وضع هنا بحيث يكون البعد بين الحروف 12 بيكسل (أي فوق بعضها قليلا) و هذا يناسب الخط الياباني.

النوع 3-ب: كل حرف هو قطعة وحدها من خامة ثلاثية أبعاد و اللعبة ترصف العديد من القطع "الحروف"


خامات المجسمات ثلاثية الأبعاد يمكن أن يظهر في أي مكان من الشاشة.


كيف نتعرف عليه:

المحاكيات بالمنقحات: النص لا يكون موجود في الخلفيات (Background/BG) بل على خامات المجسمات (Textures).

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


ملاحظة: هناك طرق أخرى بالنسبة للألعاب ثلاثية الأبعاد.

الخط في الإيزو (مخزن بصيغة TIM)

و بعد نسخه للرام

يوجد معه أيضا جدول عرض كل حرف



كل حرف في الصورة هو جسم مستقل ألصق على خامة مجسم مسطح

و كل حرف لديه عرضه الخاص

(يمكن رؤية العملية بمحاكي NO$PSX)


بالنسبة للنوع 3-ب، يجدر التنويه أن الأجهزة ثلاثية الأبعاد الخالصة أو التي قدراتها ثنائية الأبعاد محدودة (مثل البلاي 1)

لا يوجد فيها مفهوم التايل (أو يوجد لكن قدراته محدودة جدا).

بل كل شيء مجسمات ثلاثية الأبعاد (3D Models) فيها الشكل الهندسي العام للجسم (مثلا نقاط قمم المكعب)،

و تلك المجسمات يتم تلبيسها صورا مسطحة نسميها الخامات (Textures).


المجسم (العاري)

الخامات

المجسم بعد تلبيس الخامة



هذه الأجهزة تفضل أن ترسم أي شيء شكله مسطح أو ثنائي الأبعاد، باستعمال الرسوم ثلاثية الأبعاد،

لكنها تستخدم مجسما (3d model) يكون على شكل ورقة مسطحة ليوهمنا أن الصورة ثنائية الأبعاد.

و يتم تلبيس الصور ثنائية الأبعاد (الشخصيات، و أيضا و خاصة النصوص) على أساس أنها خامة لذلك المجسم.

على سبيل المثال هذه لعبة على جهاز Wii قمنا بتغيير زاوية الكاميرا بطرق غير مشروعة لنكشف هذه الحيلة:




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

(و لم نخص أصلا بعد في تفاصيل خرائط التايل و جداول السبرايت، سنعود لها في دروس أخرى إن شاء الله)،

دعنا نمر لبيت القصيد و ما يعنينا في الوقت الحاضر و هو عكس إتجاه سريان النص.


نحن لا نحتاج لعكس سريان النص في الحالات التي يظهر فيها النص كله دفعة واحدة،

مثل الحالات 0-أ و 0-ب، لأنه يكفي في تلك الحالات

مجرد عكس النص مع إضافة الفراغات كما كنا نفعل في السابق.


الآن سنمر إلى مثالنا.

لعبة Cowboy Kid للنيس، النسخة الأمريكية.

سنستخدم محاكي FCEUX.


لماذا النيس؟

لأنه جهاز بدائي جدا.

و من كثر بدائيته، لم يكن للمبرمجين مطلق الحرية في عمل ما يريدون،

لذلك كانت أغلب الألعاب فيه تستعمل إما الصور الجاهزة،

الحالة 0-ب (حيث هناك خريطة تايل جاهزة بفراغاتها ترسل للشاشة و تكون نصا انطلاقا من خط)

و إما...

الحالة 1.

(كل هذه الحالات ليس فيها خط متغير، أو هبل من هذا النوع... هناك حالات 2 و 3-أ في النيس لكنها تعد على أصابع اليد)


الحالة 1 هي التي يرسل فيها الخط بأكمله للذاكرة البصرية،

ثم تقوم اللعبة بصنع خريطة تايل متغيرة أثناء عملها تستخدم تلك الحروف لإظهار الكلام المكتوب على الشاشة،

و ينتج عن ذلك ظهور النص على الشاشة و سريانه من اليسار لليمين.


هذا السريان نريد تغييره ليصير من اليمين لليسار.

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

أي برمجة؟

نستطيع أن نحاول تغيير البرمجة التي تتعامل مباشرة مع خريطة التايل للخلفية و تقوم بتصنيعها.


لكن هذا... أمر صعب علينا قليلا الآن.

لذلك لن نغيرها هي، بل بالأحرى سنحاول تغيير البرمجة التي هي سبب هذه البرمجة.

يعني تغيير غير مباشر. لماذا هذا ممكن في حالتنا؟

لأن عديد مبرمجي النيس بحكم الضرورة لجأ الكثير منهم إلى نفس الحل.


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

ليس كل المبرمجين إستخدموه لكن الكثيرين في عهد النيس و حتى السوبر نيس إستعملوه.


طبعا هذا كان فقط مع الحالة 1 من النص. حيث كانت في تلك الألعاب:


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

و حينها بالطبع لن تنفع هذه الطريقة كثيرا.

هذه الطريقة غير مباشرة، و لا تؤثر على خريطة التايل مباشرة،

بل تؤثر على عنوان في الرام يؤثر عليها. لكن لو نجحت قد تكون هي الأبسط في أكثر الأحيان.


بما أن النيس جهاز ياباني و المعالجات غربية الصنع،

فحساب التايلات في الشاشة يبدأ من اليسار لليمين، و من فوق لتحت.


أعلى سطر سيكون 0، السطر تحته 1، و هكذا...

التايل/الحرف الذي على الحافة اليسرى للشاشة يكون 0،

و لو نأخذ تايل قريب من الحافة اليمنى سيكون شيء كبير مثل 20 مثلا...


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

من نوع 0 أو شيء كهذا. ثم مع الحرف الثاني ستزداد، ثم تزداد، و تزداد كلما اقتربنا لحافة الشاشة اليمنى.


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


أولا – البحث عن العداد و التأكد أن اللعبة تستعمله


نحتاج الى الذهاب لأحد المشاهد التي بها مربعات الحوارات في لعبة Cowboy Kid.

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


نصنع لقطة Save State قبل ظهور بالون الحوار الذي فيه النص.

و ذلك لنتمكن من الرجوع قبل بداية برمجة ظهور النص و تغيير ما نريد تغييره...

في حالتنا نصنعه في الباب قبل الدخول و الحديث مع الرجل في المبنى.



و الآن نبحث عن "العداد العمودي للحرف".


كالعادة نجهز اختصارات لوحة المفاتيح (ان لم نفعل ذلك في الدروس السابقة) لأمور سنستعملها كثيرا:

الإيقاف المؤقت Pause

المرور للإطار القادم Frame Advance

النص يتحرك بسرعة، لذلك اختصارا لوحة المفاتيح الذان لدينا سيساعداننا كثيرا.

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


نقوم بإستعمال خاصية البحث عن كود غش في المحاكي.

في حالة المحاكي الذي لدينا، هي Cheats>Search For New Cheats (active)


ما هي القيمة التي سنبحث عنها؟

سنبحث عن قيمة " العداد العمودي للحرف الحالي".

كلما كان الحرف أقرب ليمين الشاشة كلما كانت القيمة أكبر.

كلما قرب الحرف لليسار، قربت القيمة من الصفر.


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

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


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

و تطبق الفكرة بطريقة أخرى تماما.

لذلك كي نعرف إن كانت هذه الطريقة ناجعة في حالتنا، نجرب البحث عنها هكذا:


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



  1. في نافذة البحث، نعمل Reset (بحث جديد)

  2. نترك اللعبة تكتب بعض الحروف، المحبذ تكتب حرفين كل مرة (المهم ألا نصل إلى آخر الجملة)

  3. في نافذة البحث، نبحث عن القيم التي كبرت مقارنة بالسابق. أي Greater Than

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

  5. نكرر هذا قليلا إلى أن نقترب من نهاية السطر.


مراحل إختيارية:

  1. ثم نترك اللعبة تمر للسطر الثاني و تكتب حرفا أو إثنين فقط.

  2. في نافذة البحث، نبحث عن القيم التي صغرت مقارنة بالسابق، أي Less Than


لو اختفت جميع النتائج،

أو بقيت نتائج بقيم غير معقولة (كبيرة جدا، أو تتغير حتى و النص لا يتحرك)،

فهذا يعني أن حلنا لا يعمل هنا.

أو أن الحل يعمل لكن لم نبحث جيدا، و في تلك الحالة نعيد البحث باستخدام "لا يساوي" Not Equal.

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


لو بقيت هناك نتيجة أو بضع نتائج قليلة،




نجرب عمل كود غش يثبت تلك النتيجة بقيمة معينة

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

لو كانت النتيجة أن الحروف كلها تظهر في نفس المكان أثناء تدفق النص،

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



في حالتنا هذه كان عنوان الرام المطلوب الذي به "العداد العمودي للحرف" هو 04DF

في أول السطر يأخذ القيمة 04 و يظهر الحرف على يسار البالون،

و نستطيع تغيير قيمته بواسطة كودات الغش، لنختبر القيم المختلفة الممكنة له.

لو وضعنا فيه 1D فسيظهر النص على الحافة اليمنى للبالون.


أيضا، لو جربنا الخروج و معاودة الدخول و كود الغش هذا يعمل،

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

و هذا مهم كي يفيدنا.


إليكم مثالا لا يؤثر فيه العداد العمودي للحرف على جميع الحروف،

و هو من لعبة علاء الدين للسوبر نيس:



في هذه الحالة فتغيير البرمجة التي تؤثر على العداد ليس كافيا لأنه لا يؤثر على أول حرف في الجملة،

بل يجب أن نذهب مباشرة لتغيير برمجة كتابة خريطة التايل كي نتمكن من فعل شيء حيالها.

تلك حالة تكون فيها طريقة العداد ناقصة...

لكن من حسن حظنا أنها كافية بالنسبة لنا هنا على مثالنا Cowboy Kid للنيس.


إذن في حالة Cowboy Kid،

فاللعبة تعتمد الطريقة 1 لكتابة النصوص،

و تستعمل عدادا عموديا للحروف، و هذا العداد يؤثر بشكل كامل على جميع الحروف.

إذن يمكننا لقلب اتجاه سريان النص الإكتفاء بالتعديل على برمجة هذا العداد فقط.


في حالة المثال الذي نعمل عليه، وجدنا أن العنوان المطلوب هو 04DF

لو أخذ هذا العنوان القيمة 04 يظهر الحرف على أقصى يسار البالون،

و بتجربة قيم مختلفة وجدنا أن القيمة 1B (27 بالست عشري) تظهره في أقصى اليمين.


يمكننا وضع قيم أكبر، أو القيمة 00، لكن النص سيكتب فوق حافة البالون و النتيجة تكون بشعة،

عدا أن هناك إحتمال أن اللعبة لو أخفت النص بعد نهايته

قد لا تمسح الحروف التي فاضت عن البالون و ستظل عالقة على الشاشة.


كي نجعل النص يظهر في إتجاه سريان الكتابة العربية، ماذا علينا أن نغير في البرمجة؟


البرمجة الأصلية (النسخة الإنكليزية)

البرمجة المعدلة (بإتجاه الكتابة العربية)

في أول كل سطر:

04DF يأخذ القيمة 04 (الحافة اليسرى)

في أول كل سطر:

04DF يأخذ القيمة 1B (الحافة اليمنى)

بعد كتابة كل حرف:

04DF تزداد قيمته بواحد

بعد كتابة كل حرف:

04DF تنقص قيمته بواحد



ثانيا – إيجاد سطور البرمجة التي تؤثر على العداد العمودي لدينا

بالطبع ستكون نقاط التعطيل أو البريك بوينت Breakpoint هي حليفتنا.

يمكننا اختيارها تحت Debugger ثم Add تحت Breakpoint في محاكي FCEUX.


سنضع Breakpoint على العنوان 04DF من نوع write (كتابة).

متى نضعه؟ سؤال وجيه.

في البداية، سنبحث عن التعليمة التي تجعل النص يبدأ من يسار الشاشة.

سنعمل نقطة التعطيل قبل ظهور البالون أصلا.



و الآن نحاول دخول المحل.

سيتعطل المحاكي عدة مرات:


التعطيل الأول: عندما يظهر بالون الحوار.



التعليمة التي تسببت في التعطيل:




00:AE5B:8D DF 04 STA $04DF = #$16


أي دخان تكون قبله نار،

و أي STA (تأخذ محتوى A و تضعه في عنوان الرام التابع لعدادنا)

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

إذن، سنصعد في التعليمات قليلا.



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

لأننا نعرف أن البرنامج أحيانا يقفز بين أجزاء مختلفة من البرمجة (كما رأينا في الدرس الثاني).

مع هذا، فالشيء الذي نحن نعرفه هو أنه عادة تكون تعليمات LDA و STA متتالية.


إذن، هناك إحتمال كبير أن تكون التعليمة LDA $04EB

هي التي نفذت مباشرة قبل تعليمتنا التي غيرت العداد.


00:AE58:AD EB 04 LDA $04EB = #$04


عرفنا الآن ما الذي غير قيمة العداد.

أتت LDA من العنوان $04EB بالقيمة 04 و وضعتها في المراكم A،

ثم أتت STA فلفظ المراكم A محتواه ليصبح هو القيمة الجديدة لعدادنا $04DF.


نحتاج إذن لعمل نقطة تعطيل أخرى لكن هذه المرة تكون نقطة تعطيل كتابة write على العنوان $04EB.



ملاحظة: بعد STA $04DF التي غيرت العداد العمودي للحرف،

هناك تحته مباشرة 4 تعليمات سيتم تنفيذها بدون توقف حتى تعليمة القفز القادمة (و هي RTS هنا)،

و هذه التعليمات تعدل على:

ماذا تفعل هذه التعليمات؟

أمرها خارج عن موضوعنا الآن، و نحن يجب أن نركز على ما نفعله الآن.

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

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



نعود لما كنا نقوم به.

لن نقوم بعمل نقطة التعطيل على $04EB الآن.

سنضيف ذلك إلى قائمة أحلامنا المستقبلية.

و في الإنتظار، نواصل ما بدأنا به، و هي نقطة التعطيل على "عدادنا العمودي للحرف" $04DF.

الآن، نضغط Run لتشغيل المحاكي كي يواصل العمل و يخبرنا أي تعليمات أخرى تؤثر على عدادنا.


التعطيل الثاني: لا يبدو هناك تغيير ظاهر.

السبب هو أن هناك تأخير بين تغيير العداد، و بين ظهور الحروف على الشاشة.

ليس الأمر مستغربا بما أننا نراقب بشكل غير مباشر طريقة كتابة الحروف.



لكن... أضيف 1 إلى قيمة العداد الذي كان 04، و صار 05.


00:A45D:EE DF 04 INC $04DF = #$04


ملاحظة جانبية: بما أن حس الفضول صار يراودنا،

سنلقي نظرة بعد تلك التعليمة ماذا يوجد من تعليمات.

هذه المرة هناك تعليمة واحدة ستنفذ،

و بعدها مباشرة هناك تعليمة قفز (JMP) تحول وجهة البرنامج لمكان مجهول.



و نجد عنوانا مألوفا ... $04E3، و هو كان 00،

و مع زيادة 1 بتعليمة INC يصبح 01 (أي الحرف الثاني).


نعود.

الآن، نواصل اللعبة و نضغط على Run.

و هذه المرة المحاكي سيتعطل مرة أخرى... على نفس التعليمة بالضبط (نفس التعليمة من نفس العنوان).

نواصل... و تبدأ الحروف بالظهور على الشاشة واحدا تلو الآخر.

و كل حرف سيتعطل مع ظهوره المحاكي.


إلى أن ... يحصل تعطيل ثالث من نوع آخر.


التعطيل الثالث: و يحصل قبل انتهاء كتابة السطر الحالي بقليل.

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



التعليمة المذنبة هي:


00:A4B2:8D DF 04 STA $04DF = #$16


نصعد قليلا في البرمجة:



كالعادة، يتبين لنا أن هذه التعليمة كانت مسبوقة بتعليمة LDA،

و أتت إلى عدادنا بقيمة من نفس ذلك العنوان الغامض في التعطيل الأول، و هو $04EB.


إذن حان الوقت لعمل نقطة تعطيل على $04EB لنعرف من أين أتى هو أيضا بقيمته هذه.


ملاحظة جانبية (أخرى): نستطيع أن نرى أن العنوانين $04E0 و $04E3 قد تغيرا أيضا.

لاحظوا أن JSR $A4BC هي تعليمة قفز تذهب لسطر تحتها ب4 أسطر على الصورة فيه زيادة ل $04E3.

لكن هذا... لا يهمنا الآن.


لم تعد هناك نقاط تعطيل من نوع آخر تؤثر على عدادنا العمودي للحرف $04DF.

باختصار لدينا:


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

عرفناها من التعطيل الأول.

لنغيرها، نحتاج إلى تغيير سببها، و هو $04EB.


00:AE58:AD EB 04 LDA $04EB = #$04

00:AE5B:8D DF 04 STA $04DF = #$16


تعليمة زيادة العداد مع كل حرف جديد ليظهر على يمين الحرف قبله.

عرفناها من التعطيل الثاني.


تغييرها ليصبح السريان من اليمين لليسار سهل: يكفي تعويض INC ب DEC.

نضيفها إلى قائمة ما يجب تغييره (بالبنفسجي).


00:A45D:EE DF 04 INC $04DF = #$04


تعليمة تغيير العداد عند العودة للسطر.

عرفناها من التعطيل الثالث.

تغييرها يتطلب تغيير سببها و هو $04EB.


00:A4AF:AD EB 04 LDA $04EB = #$04

00:A4B2:8D DF 04 STA $04DF = #$16


إذن حان الوقت لمراقبة عنوان آخر كي يتسنى لنا إكمال مشاريعنا.

و هذا العنوان $04EB هو الذي يؤثر على عنواننا.


لذلك الغرض، نقوم أولا بإيقاف عمل نقطة التعطيل التي تتبع العداد $04DF،

و ذلك بالنقر فوقها مرتين إلى أن يزول الحرف E من أمامها.

كان بإمكاننا أيضا حذفها ب Delete لكن في ذلك شيء من المبالغة.



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

هذه النقطة هي ستكون من نوع الكتابة write على $04EB



و الآن نستخدم لقطتنا Load State لنعود إلى أمام المحل.

ثم ندخل ليبدأ البالون في الظهور.



و الآن.


التعطيل الأول: قبل أن يظهر البالون أصلا!



التعليمة المذنبة (بالأحمر)، بالإضافة إلى بعض التعليمات قبلها صعدت قليلا لإيجادها.


00:8D66:A9 00 LDA #$00

00:8D68:A2 04 LDX #$04

00:8D6A:4C 6F 8D JMP $8D6F

00:8D6D:A9 11 LDA #$11

00:8D6F:8D EF 04 STA $04EF = #$00

00:8D72:8E EB 04 STX $04EB = #$01


STX تضع محتويات السجل X في العنوان $04EB الذي يهمنا.

من أين أتت محتويات X هذه؟

الجواب أمامنا، في السطر LDX #$04 و الذي من عجائب الصدف أن فيه القيمة 04 التي يبدأ منها عدادنا!

نضيف ذلك السطر إذن لقائمة ما يجب تغييره (بالبنفسجي).


و الآن نضغط Run لتستمر اللعبة في عملها و...



لم يحصل لنا أي تعطيل.

اللعبة مازالت تشتغل بعد كتابة هذين السطرين، و تنتظر منا أن نضغط أي زر.

أستطيع حتى تحريك الشخصية.


يعني أننا وجدنا كل التغييرات التي تهمنا لهذا العنوان.

و بهذا نكون عثرنا على كل أجوبتنا و حان الوقت لتغيير البرمجة.


ثالثا – وضع برمجتنا الجديدة

للتذكير بأهدافنا:

البرمجة الأصلية (النسخة الإنكليزية)

البرمجة المعدلة (بإتجاه الكتابة العربية)

في أول كل سطر:

04DF يأخذ القيمة 04 (الحافة اليسرى)

في أول كل سطر:

04DF يأخذ القيمة 1B (الحافة اليمنى)

بعد كتابة كل حرف:

04DF تزداد قيمته بواحد

بعد كتابة كل حرف:

04DF تنقص قيمته بواحد


و قائمة "ما يجب تغييره" التي توصلنا إليها لحد الآن

انطلاقا من ملاحظاتنا السابقة هي:

00:8D68:A2 04 LDX #$04

و

00:A45D:EE DF 04 INC $04DF


لنعد إذن إلى قائمة ما يجب تغييره (الذي أشرنا لها بالبنفسجي، و فيها سطران فقط)

و سنبدأ بالسطر الذي فيه LDX #$04

مازالت نافذة Debugger (التنقيح) مفتوحة أمامنا.

نصعد إلى حيث ذلك السطر.

و ننقر بالزر الأيسر للفأرة مرة واحدة على المساحة البيضاء على يسار ذلك السطر.



يمكن الملاحظة أن النافذة التي نحن فيها

هي في الواقع نافذة تفكيك أسمبلي Disassembly:

أي أننا أخذنا بايتات الهيكس و ترجمناها إلى معانيها.

مثلا عوض A2 04، ذهب المحاكي إلى قائمة كودات التحكم (الأوبكودات) التي لديه (مثل التي تحدثنا عنها أول الدرس)

و أعطانا التعليمة التي تعادلها و هي LDX #$04

و التي منها نفهم أن هذه التعليمة تضع القيمة 04 في السجل X.

يعني أنه نوعا ما ترجمها لنا.


عندما كنا نذهب إلى Rom Patcher و كنا نغير هناك التعليمات بإستخدام الهيكس

مباشرة في الملف في عنوان مكان البرمجة

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

أو الأوفسيت لو أردنا إستخدام برنامج هيكس عادي و تغييرها بأنفسنا في الروم)

فتلك كانت طريقة من طرق تغيير البرمجة.

لكنها طريقة بدائية و جنونية.

مع ذلك هي كانت مفيدة لنا كي لا نتجاوز عدد البايتات الأصلي لكل تعليمة.


هنا، حيث الدائرة البنفسجية، ننقر مرة واحدة.

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



هذه النافذة هي نافذة مجمع الأسمبلي Assembler

و هي تفعل تماما عكس مفكك الأسمبلي.

نكتب هنا تعليمات البرمجة الواضحة للبشر، مثل LDX #$1D

و هو سيحولها إلى بايتات هيكس، مثل A2 1D.



نضغط الزر Enter.

ثم نضغط Apply، طبعا لو تأكدنا أننا لم نخرب التعليمة القادمة

(يكفي النظر للتعليمة التالية أمام PC).



التغييرات الآن تعمل.

لكن التغييرات لن تكون دائمة على الروم في القرص الصلب،

إلا لو ذهبنا إلى Rom Patcher و سجلنا الروم من هناك Save ROM File.


الآن بقي لنا السطر الآخر المهم للتغيير:


00:A45D:EE DF 04 INC $04DF


مكانه هو: العنوان A45D.

نستطيع النزول إليه.

نحن الآن في السطور في العناوين بعد 8D60.

و A45D بعدها في الأسفل، لذلك ننزل كثيرا كثيرا...



لماذا نكلف أنفسنا هذا العذاب؟

و الحال أن لدينا الزر Seek To (الذهاب إلى) الذي نستطيع كتابة العنوان أمامه (A45D) و يذهب بنا هناك فورا...

الجواب هو أننا لو فعلنا هذا فأحيانا قد نجد أشياء مثيرة للإهتمام:



ما كل هذه التعليمات التي كلها BRK ؟

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

يعني يستبعد أن تكون مستعملة.

لماذا هي مكررة؟

و لماذا هذه التعليمات أتت مباشرة قبلها

سطور برمجة كثيرة تليها تعليمة RTS تقفز بالبرنامج لمكان آخر؟


السبب هو أن هذه ليست متعمدة أن تكون BRK أو تعليمة مفيدة في الواقع، بل كما نرى في الهيكس تكتب 00.

هذه مساحة فارغة من الروم وسط الكتلة التي بها البرمجة.

و كثيرا ما تكون هناك العديد من الأصفار 00 أو FF مكررة.

هذه المساحات الفارغة مهمة، و سنحتاج للبحث عنها و استغلالها في بقية الدرس. لكن ليس الآن.


بعد بحث غير طويل، نجد تعليمتنا الثانية المطلوبة.



المطلوب هو تغيير الزيادة (التي تجعل الحروف الجديدة تظهر نحو اليمين)

تصبح نقصانا (فتجعل الحروف الجديدة تظهر نحو اليسار).


إذن التغيير سيكون بتعويض INC ب DEC.

كالعادة، ننقر بالزر الأيسر للفأرة على الحيز الفارغ يسار التعليمة،

و ذلك لفتح نافذة مجمع الأسمبلي، ثم نكتب تعليمتنا الجديدة، و نفعل ب Apply.



و الآن نجرب تغييراتنا في اللعبة.

نغلق جميع نقاط التعطيل أو نحذفها من تحت Breakpoint.

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

و النتيجة...



النص بدأ من اليمين لليسار!

لكن... القيمة 1D كانت فوق اللزوم قليلا.

نعوضها ب 1B بالعودة إلى التعليمات التي غيرناها قبل قليل.



... هكذا أفضل بكثير.


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

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

عوض ذلك، سأحاول العودة لبعض الملاحظات السابقة لنا.


أمور أخرى يمكننا تغييرها

نحن رأينا أن عناك عنوانا آخر يتغير مع نفس وقت تغيير عدادنا "للقيمة العمودية للحرف" $04DF.

و هو $04E0.


أول شيء نحاول عمله هو معرفة ماذا يفعل هذا العنوان.

نذهب إلى Tools (الأدوات) ثم Memory Watch (مراقبة الذاكرة)

و نكتب هذا العنوان.

و بعدها نترك هذه النافذة مفتوحة و نواصل اللعبة

و ندخل في مكان يظهر فيه بالون حوار و نص.



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

و تصبح 05 عندما تكتب السطر الثاني،

و تعود إلى 03 عندما تكتب السطر الأول...


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

لكن كي نتأكد، نذهب إلى نافذة الغش و نصنع كود غش يجعل العنوان 04E0 يأخذ القيمة 02.

و النتيجة:



إذن هذا العنوان 04E0 هو عداد السطور، النظير الأفقي لعدادنا السابق.

و يقول على أي سطر يظهر كل حرف.

في النسخة الأصلية من اللعبة النص يكتب بفراغ بين الأسطر،

حيث السطر الأول هو 03 و السطر الثاني هو 05.


للتخلص من ذلك الفراغ، و لكي يكون من الممكن كتابة 3 سطور في البالون أو حتى أربعة،

و هذه اللعبة من أحوج الألعاب لذلك نظرا أن كثيرا من النصوص قسمت على عدة بالونات

أحيانا لأن كلمة أو إثنتين لم يسعها السطران المتاحان.


يمكننا تعديل البرمجة التي تأمر بتغيير هذه الأسطر.

و لذلك الغرض، نعمل نقطة تعطيل Breakpoint من نوع write كتابة على العنوان 04E0 لننتبع ما الذي يغيره.

طبعا نحرص على حذف كود الغش قبل ذلك!

بما أننا صرنا متعودين نوعا ما على الطريقة، سأختصر هذه المرة الشرح.


التعطيل الأول: قبل السطر الأول.


00:AE5E:AD EC 04 LDA $04EC = #$03

00:AE61:8D E0 04 STA $04E0 = #$03


السبب ($04EC) تأثر بالتعليمات التالية.


التعطيل الثاني: لا يأتي إلا عندما يكاد السطر الأول ينتهي من الظهور و نمر للسطر الثاني.


هذه المرة إستخدمنا Trace Logger (أداة التمشيط)

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


التعليمة المتسببة في التعطيل (و هي في نافذة Debugger) كانت:

00:A4AC:8D E0 04 STA $04E0 = #$03


التعليمات التي نفذت قبلها كانت:

00:A4A6:29 0F AND #$0F

00:A4A8:18 CLC

00:A4A9:6D E0 04 ADC $04E0 = #$03

00:A4AC:8D E0 04 STA $04E0 = #$03


نافذة التمشيط توقفت هنا:



التمشيط توقف هنا... هذه مشكلة.

نقوم بالضغط على Step Into (خطوة للداخل) لكي نمر للتعليمة التالية.

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

نفعل ذلك بضع مرات إلى أن نرى التعليمة STA $04E0 تظهر في نافذة التمشيط.


ما يهمنا هو أن نفهم ماذا حصل بالضبط هنا.

عداد السطور $04E0 كان فيه 03 و صارت 05.

كل ما نراه الآن هو كتلة من التعقيد، و تعليمات مثل AND/ADC تؤثر على المراكم A،

فيصبح قيمته 05 و تذهب تلك القيمة (بواسطة STA) إلى عداد السطور $04E0.


نراقب في تقرير التمشيط السجل A في كل سطر. كيف كان، و ماذا حصل له.



BEQ لا تهمنا. هي تعليمة قفزت من مكان آخر إلى هنا.

ما يهمنا أنه في وقت تنفيذها كان A قد تعرض في السابق لأمر ما (لا يهمنا)

جعله يحمل القيمة 82 بالهيكس.


بعد تنفيذ السطر AND #$0F على محتويات المراكم A،

صار يحتوي القيمة 02.

سنرى في الدرس القادم ماذا تفعل AND هذه بالضبط.

لكن نكتفي بالقول هنا أن الصفر في 0F حذف الثمانية من 82.

صار لدينا 02 في السجل المراكم A.


و بعد هذا، قامت اللعبة بإضافة قيمة عداد السطور (03) إلى المراكم (02)

مع الحرص على وضع CLC كي تكون عملية الجمع صحيحة،

و نتيجة عملية الجمع (05) وضعتها ADC في المراكم.

و أخيرا أتت التعليمة STA و وضعت محتويات المراكم في عدادنا الذي صار أخيرا 05.


إذن، كل هذا التعقيد:


00:A4A6:29 0F AND #$0F

00:A4A8:18 CLC

00:A4A9:6D E0 04 ADC $04E0 = #$03

00:A4AC:8D E0 04 STA $04E0 = #$03


هو ببساطة عملية جمع (+2) لعداد السطور.

و لنحولها لعملية جمع (+1) و بذلك يمكننا إستعمال 3 أسطر (أو حتى أربعة)،

يمكننا أن نحذف كل هذا الهبل ... أقصد نعوض الزائد منه ب NOP.

و نضع مكانه فقط التعليمة: INC $04E0 مرة واحدة.


لحظة! هناك مشكل في هذا الأمر.

هو أن هذه التعليمات كلها تؤثر على محتويات المراكم A.

لو حصل و حذفناها، ألن يؤثر ذلك على التعليمات القادمة التي تستعمل المراكم؟

سيخرب عمل اللعبة في تلك الحالة.


... لحسن الحظ.

مباشرة بعد هذه التعليمات، هناك تعليمة LDA.

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

إذن و بدون أي تردد، ننقر بالزر الأيسر للفأرة على الحيز يسار السطر AND #$0F

و ذلك لفتح نافذة مجمع الأسمبلي.


نكتب هنا INC $04E0

هذه التعليمة لوحدها طولها 3 بايتات، و بذلك تبتلع السطرين الذي بهما AND و CLC.

نضغط زر Enter.

و بعد ذلك نكتب NOP بقدر عدد البايتات التي تكون بقية السطور،

الى ان نتخلص من STA $04E0 الأخيرة.



نضغط الآن Apply.


لقد صنعنا مساحة فارغة جميلة و تصلح لوضع تغييرات كثيرة لنا،

و ذلك لأننا توصلنا لعمل برمجة جديدة على أقل سطور من البرمحة الأصلية.

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


و الآن نجرب اللعبة من جديد.

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



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

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

هذا يوفر علينا جهد نقر الأزرار عديد المرات حتى يظهر كامل النص.


و بالطبع لا ننسى...

كي تكون التعديلات دائمة، يجب أن نذهب الى Rom Patcher

و نختار Save Rom File.

...


أما $04E3 ؟

هو يقول لنا أي حرف في النص نحن بصدد قراءته.

هل هو أول حرف في الجملة، ثاني حرف... إلخ.


قد ينفعنا كثيرا، لكن ليس الآن...


أرجو أنكم إستمتعتم بتعديل Cowboy Kid،

و أنكم فهمتم مبدأ عكس النص بصفة عامة على الأقل.


لأن هذا المثال من بين أسهل الأمثلة على الإطلاق و تعبت لإيجاده،

و جل الألعاب الأخرى أصعب بكثير... لسبب رئيسي.


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

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

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

و طبعا لا يصح أن نتلف التعليمات بدون حرص لنحشر إضافاتنا لأن اللعبة ستخرب.



أود أن نتذكر معا درس المؤشرات.

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

مثل هذه الحالة مثلا من لعبة Monster World 4:

كلمة Key طولها 3 حروف.

لكن كلمة مفتاح طولها... 5 حروف.

و ليست هذه الكلمة الوحيدة التي تعيقنا...

فهناك أيضا Bomb و Map و غيرها.



كيف السبيل لحل هكذا مشكلة؟

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

قدم الحل الأمثل لهذه القصة:

  1. نبحث عن مكان فارغ في أسفل الروم بعيدا عن هذه الزحمة

  2. نضع في ذلك المكان الفارغ كلمتنا الجديدة العربية الطويلة

  3. نغير المؤشر (البوينتر) الذي يشير للكلمة القديمة فيصبح يشير لذلك المكان الفارغ

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



بالنسبة لتغيير برمجة الألعاب، لو لم نستطع التعويض، نقوم بنفس الشيء بالضبط كما فعلنا مع النصوص.

حتى شرط أن "لا يكون المكان الفارغ (مسكن إضافاتنا الجديد) بعيدا جدا عن المؤشر و يستطيع أن يصله"

موجود أيضا هو نفسه، بل و مهم جدا في حالتنا.


و لهذا الغرض، توجد تعليمات نحن رأيناها في السابق و مررنا عليها مرور الكرام،

لكن حان الوقت الآن لنتعلمها و لنستخدمها لتحقيق مآربنا.

و هذه التعليمات نوعان: تعليمات القفز، و تعليمات التفرع.

نبدأ إذن على بركة الله.


تعليمات القفز


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

لذلك، لو كانت لدينا تعليمة مثل "موت ماريو" تنفذ في عشرات الحالات

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

سقط في حفرة، وصل التوقيت إلى صفر...) فلا يصح أن نكتب برمجة "موت ماريو" عشرات المرات أيضا.


يكفي كتابتها مرة واحدة، و عندما تحتاجها برمجة أخرى، تقوم بالقفز (Jump) نحوها،

ما يعني أن البرنامج ينتقل من مكان في الروم إلى مكان آخر و يبدأ في قراءة و تنفيذ التعليمات من المكان الجديد.


لهذا الغرض توجد لدينا عديد تعليمات القفز:

JSR – القفز نحو برنامج ثانوي، إلى العنوان المحدد ($XXXX) ثم عندما يجد RTS العودة من حيث أتى

JMP – القفز دون رجعة، إلى العنوان المحدد ($XXXX)


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

الفرق أنها تعليمات قفز طويل، و المدى الذي تغطيه هو كامل روم السوبر نيس.

في حين أن تعليمات القفز العادي قصيرة و مدى عملها هو فقط الكتلة التي هي فيها، بين ($0000 و $FFFF من تلك الكتلة)


JSL – القفز الطويل نحو برنامج ثانوي، إلى العنوان المحدد ($XXXXXX) ثم عندما يجد RTL العودة من حيث أتى

JML – القفز الطويل دون رجعة، إلى العنوان المحدد ($XXXXXX)


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

كي نفهم طريقة عمل القفز الطويل نحو البرنامج الثانوي، مثل JSR.

أفترض أن لدي هذا البرنامج:


00:8003:A9 02 LDA #$02

00:8005:85 EB STA $00EB

00:8007:20 20 80 JSR $8020

00:800A:A9 05 LDA #$05

00:800C:85 EB STA $00EB

...


00:8020:A9 50 LDA #$50

00:8022:8D ED 40 STA $40ED = #$FF

00:8025:60 RTS

00:8026:EE 00 01 INC $0100 = #$FF

00:8029:E8 INX

...


بما أننا إستخدمنا هنا JSR التي جعلت للقفز نحو برنامج ثانوي ثم العودة،

فإن تنفيذ هذا البرنامج سيكون مثل الآتي:

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


00:8003:A9 02 LDA #$02

00:8005:85 EB STA $00EB


إلى أن يجد JSR تلك. فيذهب نحو العنوان الذي أشارت إليه،

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


00:8020:A9 50 LDA #$50

00:8022:8D ED 40 STA $40ED = #$FF


إلى أن يجد السطر RTS.

هذا السطر يأمره بالعودة من حيث أتى، إلى المكان الذي كانت فيه JSR.

فيعود هناك و يواصل تنفيذ التعليمات التي أمامه.


00:800A:A9 05 LDA #$05

00:800C:85 EB STA $00EB

...


أما الآن، فسنرى القفز دون رجعة JMP.

هذا النوع من القفز لا تأتي معه تعليمة رجوع مثل RTS.


لذلك، لو كان مثلا لدينا المثال الآتي:


00:8003:A9 02 LDA #$02

00:8005:85 EB STA $00EB

00:8007:4C 20 80 JMP $8020

00:800A:A9 05 LDA #$05

00:800C:85 EB STA $00EB

...


00:8020:A9 50 LDA #$50

00:8022:8D ED 40 STA $40ED = #$FF

00:8025:E8 INX

00:8026:EE 00 01 INC $0100 = #$FF

00:8029:E8 INX

...


فما يحصل هو أن السطور التي يتم تنفيذها هي:


00:8003:A9 02 LDA #$02

00:8005:85 EB STA $00EB


ثم يتم تنفيذ السطور التالية:


00:8020:A9 50 LDA #$50

00:8022:8D ED 40 STA $40ED = #$FF

00:8025:E8 INX

00:8026:EE 00 01 INC $0100 = #$FF

00:8029:E8 INX

...


أما السطور التي بالأزرق، فيتم تجاهلها تماما و لا يعود البرنامج إليها.


تنبيه: مع كل JSR يجب العودة ب RTS. (نفس الشيء ل JSL و RTL)

لو تم العودة للمكان الأصلي بإستخدام تعليمة مثل JMP،

بحيث تبقى هناك JSR يتيمة بدون RTS فستحصل أمور فضيعة تظهر آثارها تدريجيا بعد فوات الأوان.


لنحاول إذن الإنتفاع بتعليمة القفز هذه لتحقيق ما نريده.

سنجرب الآن عكس سريان النص في لعبة أخرى، من النوع 1 لطرق إظهار النصوص.


سنستخدم محاكي FCEUX كالعادة، و لعبة أخرى من عصور النيس الأثرية،

و إختيارنا هذه المرة هو لعبة Yume Penguin no Monogatari "مغامرة بطريق أحلامي"

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

اللعبة صدرت باللغة اليابانية و لها ترجمة هواة انكليزية.


نحتاج الى الذهاب لأحد المشاهد التي بها مربعات الحوارات.

و يكون به أكثر من سطر واحد.

هناك مشهد المقدمة، و مشاهد نهاية المستوى.

أنا سأجرب مع مشهد نهاية أول مستوى.


أرفقت مع الدرس لقطة من المستوى يمكنكم فتحها في FCEUX باستخدام File/Load State From

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

طبعا كان بإمكانكم أيضا صنع كودات غش، و ذلك لتتمكنوا من لعب اللعبة بسرعة حتى المشهد المطلوب.


نصنع لقطة Save State قبل فتح المشهد،

و ذلك لنتمكن من الرجوع قبل بدايته و تغيير ما نريد تغييره...



كالعادة نجهز اختصارات لوحة المفاتيح مثل الإيقاف المؤقت Pause و التقدم للإطار القادم Frame Advance.

و ذلك لأنها ستنفعنا.


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

قمت بالبحث عن قيمة العداد العمودي للحرف، و ذلك بواسطة كودات الغش.

وجدت ما أبحث عنه في العنوان 00C4.



لو أخذ هذا العنوان القيمة 01 يظهر الحرف على أقصى يسار البالون،

و بتجربة قيم مختلفة وجدنا أن القيمة 0E (14 بالست عشري) تظهره في أقصى اليمين.


يمكننا وضع قيم أكبر، أو القيمة 00، لكن النص سيكتب فوق حافة البالون و النتيجة تكون بشعة،

عدا أن هناك إحتمال أن اللعبة لو أخفت النص بعد نهايته قد لا تمسح الحروف التي فاضت عن البالون و ستظل عالقة على الشاشة.


الآن نحاول أن نجد التعليمات التي تؤثر على العداد العمودي 00C4،

و ذلك بعمل نقطة تعطيل Breakpoint عليه من نوع الكتابة write.


هدفنا أن نجد هذه التعليمات التي أثرت عليه، ثم تغييرها.

تجعله يعود إلى يمين البالون (0E).

تجعله ينقص مع كل حرف ليتجه لليسار.


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


أشرنا باللون الأحمر إلى التعليمات المسؤولة عن التعطيل (التي تظهر أول واحدة في النافذة عند حصول التعطل).

كما أضفنا التعليمات من حولها التي حصلت معها لكي يكون السياق واضحا

(مثلا لو كانت STA، وضعنا تعليمة LDA التي كانت قبلها).


مع كل تعطيل يكشف لنا تعليمة تؤثر على 00C4 كتبنا التعليمات التي أثرت عليه،

ثم ضغطنا Run لنواصل اللعبة حتى التعطيل القادم.


التعطيل الأول: في لحظة الانتقال من المستوى الى المشهد

على الأرجح لا علاقة له بما نبحث عنه.


00:9AF0:A9 00 LDA #$00

00:9AF2:85 C4 STA $00C4 = #$0E

00:9AF4:85 C5 STA $00C5 = #$00

00:9AF6:85 C6 STA $00C6 = #$00


نواصل.

التعطيل الأول (مرة أخرى): بدأت موسيقى المشهد لثانية ثم تعطلت اللعبة.


00:9AF0:A9 00 LDA #$00

00:9AF2:85 C4 STA $00C4 = #$0E

00:9AF4:85 C5 STA $00C5 = #$00

00:9AF6:85 C6 STA $00C6 = #$00


التعليمات وراء هذا التعطيل هي نفسها للأول (أعرف أنها هي نفسها بالنظر للعنوان قبلها 00:9AF0)

إذن هي نفس البرمجة تكررت مرتين.

و هو نفس التعطيل.


نواصل.


التعطيل الثاني: نجد أن برواز إطار الحوارات قد رسم فعلا أثناء تلك الشاشات السوداء.

قد تكون إذن للعنوان 00C4 (عدا كونه القيمة العمودية للحرف) أهمية أخرى في رسم هذا الإطار (و بالفعل هو كذلك).


00:9BF2:A9 01 LDA #$01

00:9BF4:85 C5 STA $00C5 = #$01

00:9BF6:85 C4 STA $00C4 = #$00

00:9BF8:A5 C9 LDA $00C9 = #$07


ما حصل الآن هو أن العنوان 00C4 أخذ القيمة التي كانت في المراكم A، و هي 01.

ليس هو فقط بل العنوان 00C5.


نواصل.


التعطيل الثالث: نسمع جزء من النغمة...



00:9C36:E6 C4 INC $00C4 = #$01


زادت قيمة 00C4 بواحد. لكن لم نر شيئا بعد.


نواصل.


التعطيل الثالث (مرة أخرى): ظهر أول حرف.




00:9C36:E6 C4 INC $00C4 = #$01


نفس التعليمة السابقة.

عرفنا أنها هي نفسها من العنوان 9C36.


هذه التعليمة من هنا و صاعدا،

مع كل حرف من هذا السطر، تتكرر.


نواصل عدة مرات... حتى آخر السطر، حيث سيأتينا تعطيل معه تعليمة جديدة.


التعطيل الرابع: بعد أن واصلنا مع بعض الحروف، ظهر هذا التعطيل الجديد في نهاية هذا السطر.

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

00:9C46:A9 01 LDA #$01

00:9C48:85 C4 STA $00C4 = #$0A

00:9C4A:85 C6 STA $00C6 = #$07

00:9C4C:60 RTS


نواصل.


بعد هذا، يبدأ السطر الثاني في الكتابة، و يظهر مع كل حرف التعطيل الثالث (الذي به برمجة INC).



الآن نكون كشفنا التعليمات التي أثرت على برمجة عداد النص.

بقي علينا أن نغيرها.


برمجة التعطيل الأول على الأرجح لا يهمنا تغييرها...

حتى لو غيرناها فالتعطيل الثاني أتى و فرض القيمة 01 على العداد 00C4.

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

بل أن تغييرها يعد تهورا.


إذن هدفنا هو تغيير البرمجات التابعة للتعطيل الثاني، و الثالث، و الرابع.

سنقوم بتحديد ماذا تفعل الآن،

و كيف نريد أن نغيرها كي يصبح عدادنا العمودي للحروف $00C4

يتصرف كما نريد بحيث يسري النص من اليمين لليسار.


00:9BF2:A9 01 LDA #$01

00:9BF4:85 C5 STA $00C5

00:9BF6:85 C4 STA $00C4

00:9BF8:A5 C9 LDA $00C9


أول حرف في النص:

التعليمة الحالية تضع 01 في المراكم، ثم منه إلى 00C4.

هدفنا: أن تضع 0E (يمين البالون). يعني LDA #$0E

00:9C36:E6 C4 INC $00C4


المرور للحرف التالي:

التعليمة الحالية تزيد واحد إلى 00C4.

هدفنا: أن تنقص واحد منه. يعني DEC $00C4

00:9C46:A9 01 LDA #$01

00:9C48:85 C4 STA $00C4

00:9C4A:85 C6 STA $00C6

00:9C4C:60 RTS


العودة للسطر:

التعليمة الحالية تضع 01 في المراكم، ثم منه إلى 00C4.

هدفنا: أن تضع 0E (يمين البالون). يعني LDA #$0E


قد يتهيأ لنا أن عمل هذه التعديلات سهل.


صحيح أنه يمكننا تغيير INC $00C4 إلى DEC $00C4 بدون مشاكل.

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

أو بخاصية Rom Patcher لو أردتم كتابة التعليمة كما تظهر في الهيكس، بما أن معكم جدول الأوبكودات.

أي أن التعويض سيكون من E6 C4 إلى C6 C4.

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


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

لو أردنا تعويض LDA #$01 ب LDA #$0E

صحيح أننا سنضع في المراكم A القيمة 0E التي ستذهب للعداد 00C4

و التي نحتاجها كي يبدأ النص في الظهور من يمين البالون.

لكن لذلك آثارا جانبية وخيمة.


لأن قيمة المراكم A التي بدلناها و هي 0E ستذهب أيضا

(في السطر STA $00C5) إلى العنوان 00C5 و الذي لا نعلم عنه شيئا،

و تلك القيمة على الغالب لا تناسبه على الإطلاق.

(هناك تعليمة LDA بعد ذلك مباشرة أثرت على A، ما يعني أنه لا توجد أضرار جانبية أخرى)


ما يعني أن البرنامج سيخرب عمله.

في حالتنا هذه لو جربنا مجرد تعويض LDA #$01 ب LDA #$0E في برمجة "أول حرف في السطر"،

سيفسد البرنامج بهذه الطريقة:



السبب في حالتنا، أن 00C5 يصادف كونه عداد الأسطر في حالة هذه اللعبة،

و هو في هذه الحالة أخذ القيمة 0E ... يعني السطر الرابع عشر.

لذلك ظهرت كلمة Hello في الأسفل.

رغم أن العداد العمودي يظهر بشكل صحيح على "يمين البالون"...

إلا أن هذا لا يجدي نفعا بما أن النص ليس في البالون أصلا.


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

قد يتسبب في توقفها نهائيا عن العمل و ليس مجرد أخطاء بصرية مثل هذه.

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

المراكم مثل أي شيء في المنزل نأخذه و نستعمله،

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


ماذا نفعل إذن في حالتنا؟

الحل هو أن نجعل تغييراتنا على برمجتي "أول حرف في النص" و "العودة للسطر" لا تؤثر على بقية البرمجة.



A9 01 LDA #$01

85 C5 STA $00C5

85 C4 STA $00C4

A5 C9 LDA $00C9


أول حرف في النص: تصبح

A9 0E LDA #$0E

85 C4 STA $00C4

A9 01 LDA #$01

85 C5 STA $00C5

A5 C9 LDA $00C9

E6 C4 INC $00C4


المرور للحرف التالي: تصبح

C6 C4 DEC $00C4

A9 01 LDA #$01

85 C4 STA $00C4

85 C6 STA $00C6

60 RTS


العودة للسطر: تصبح

A9 0E LDA #$0E

85 C4 STA $00C4

A9 01 LDA #$01

85 C6 STA $00C6

60 RTS


لدينا مشكلة في حالة "أول حرف في النص" و "العودة للسطر"...

و هو أن برمجتنا أطول من البرمجة الأصلية بما أننا أضفنا السطر LDA #$01 و طوله 2 بايت.

إذن لا حل بيدنا غير إيجاد مكان آخر لإيواء هذه البرمجة الجديدة، ثم عمل قفزة نحوها.


سأبحث أولا عن مكان فارغ في الروم لوضع البرمجة فيه.

و هذا المكان فيه الكثير من الحيز لما نريد عمله:



أنقر بالز الأيسر للفأرة يسار السطر الذي في العنوان C710

لفتح نافذة مجمع الأسمبلي، ثم أكتب السطور الأربعة التالية:


LDA #$0E

STA $00C4

LDA #$01

RTS


السطر RTS هو الذي سيعود بنا إلى البرمجة الأصلية.

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

بطريقة تجعل هذا الجزء "المنفي" من البرمجة صالحا لتقفز إليه البرمجتان.

و بذلك نكون إدخرنا الحيز المكاني و هذا أمر مهم جدا.



بعد ضغط Apply لوضع البرمجة هنا،

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

كي تستغل برمجتنا الجديدة من الأساس.


نبدأ ببرمجة "أول حرف في النص"، و هي في الأصل هكذا:


00:9BF2:A9 01 LDA #$01

00:9BF4:85 C5 STA $00C5

00:9BF6:85 C4 STA $00C4

00:9BF8:A5 C9 LDA $00C9


نذهب إذن للعنوان 9BF2 (نكتبه أمام Seek To للوصول هناك بسرعة).

حيث توجد هذه البرمجة "أول حرف في النص".


الآن نريد وضع تعليمة JSR $C710 تقفز بالبرنامج و تحول وجهته إلى مكان برمجتنا الجديدة C710،

ثم تعود مع التعليمة RTS إلى البرنامج الأصلي و تكمله.


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

نعود إلى جدول الأوبكودات و نرى كم طول التعليمة JSR.

الجواب هو 3 بايتات.


سنضعها إذن مكان التعليمتين:

LDA #$01

STA $00C4

بما أنهما موجودتان في البرنامج الثانوي البعيد.

لكن طول هاتين التعليمتين هو 4 بايت.


إذن، بالإضافة إلى JSR $C710 (التي طولها 3 بايت) سنضيف التعليمة NOP (التي طولها بايت واحد).

بذلك يكون التعويض أيضا هو 4 بايت.

لأنه لو لم نفعل، فإن هناك نصف تعليمة ممزق أشلاء سوف يبقى،

و سوف يظن البرنامج أنه تعليمة مغايرة تماما و يحاول تنفيذها و النتائج ستكون عشوائية و كارثية.


في حالتنا، STA $00C5 أتت قبل STA $00C4.

هما يستخدمان نفس قيمة المراكم A التي أتت من LDA #$01 في الأصل،

و لم يحصل شيء آخر عدا ذلك، يعني يمكننا مبادلة ترتيبهما.

و ذلك ما سنفعله.


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

و نبدأ من السطر الذي فيه LDA #$01:



نضغط Enter، و الآن ستظهر لنا تعليمة عشوائية سبب ظهورها هكذا

أن المحاكي قام بتأويل أشلاء تعليمة على أساس أنها تعليمة صحيحة.

السبب أننا استخدمنا JSR التي طول تعليمتها 3 بايت.

نقوم بإضافة NOP التي طولها 1 بايت و لا تفعل شيئا لإصلاح غلطتنا.



و نضغط Enter.

الآن نضع STA $00C5 كي لا تضيع من البرنامج بسبب تغييرنا الترتيب.



و بعد أن نضغط Enter، نضغط Apply لتفعيل و تسجيل تغييراتنا.


أما الآن، فالدور على برمجة "العودة للسطر".

و التي موجودة في العنوان 9C46.


00:9C46:A9 01 LDA #$01

00:9C48:85 C4 STA $00C4

00:9C4A:85 C6 STA $00C6

00:9C4C:60 RTS


نعوض أول سطرين ب JSR $C710 ثم NOP.

و هذه المرة هذا كل ما في الأمر.



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

و نعوض INC $00C4 ب DEC $00C4.


و النتيجة كانت كما نريد بدون أي مشاكل!



هناك أمر لم أذكره بعد...

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

كان مكانا آمنا؟ لا تستخدمه اللعبة مثلا لوضع شاشة سوداء،

أو تراقبه وتتسبب في اشتغال برمجة تنتقم من القراصنة الصينيين؟

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

بنقاط التعطيل كالعادة، لكن في وضع القراءة read (يعني، لو قرأت اللعبة من ذلك المكان يتعطل المحاكي).

مع ذلك هذا ليس ناجعا كثيرا مع ألعاب النيس نظرا لوجود ما يسمى بتغيير الكتل bankswitching.


و كذلك لو لاحظتم، فقد اكتشفنا في هذه اللعبة أيضا عداد السطور و هو في 00C5.

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

و لو كان الفراغ بين الأسطر يزعجكم كثيرا،

بإمكاننا حل ذلك بتعديل بسيط جدا على تلك البرمجة،

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


00:9C42:E6 C5 INC $00C5

00:9C44:E6 C5 INC $00C5


و بعد تعديل بسيط (أترك أمره لكم)، يمكننا الحصول على:



في مثالنا هذا، رأينا خطورة الآثار الجانبية التي يمكن أن تحصل عند إضافة برمجة من عندنا.

فعلى سبيل المثال، لو أتينا وسط برمجة للعبة مثلا أردنا أن نضيف إليها "اجعل ماريو يأخذ الشكل كذا"،

و لنغير عنوان الرام المطلوب قمنا بعمل LDA و STA بسيطة...


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

طبعا، في حالتنا هذه الضرر كان بسيطا لأنه كان هناك تعليمة LDA $00C9 كانت ستتلف و تعوض محتويات المراكم على أية حال.


لكن ماذا لو لم يكن هناك تلك التعليمة؟

ماذا لو إحتاج البرنامج لاحقا لمحتويات المراكم A التي نحن أفسدناها لنضيف LDA و STA إضافاتنا (جعل ماريو يأخذ شكلا ما)؟

هل هناك وسيلة لنحتفظ في مكان آمن بمحتوى المراكم الأصلي و نعيده بعد ذلك سالما للعبة؟

هذه الوسيلة موجودة، و تدعى المكدس.


المكدس


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

كما لو كانت شخص أثناء العمل و كان يحمل شيئا فانشغل بأمور أخرى،

و لذلك قام بوضع ما كان يحمله على طاولة (المكدس)، ذهب لقضاء شؤونه،

ثم التقط الشيء الذي وضعه من الطاولة و ذهب به.



المكدس هو مثل كومة ألعاب الورق.

الآن نركز على حركة يدنا.

نستطيع أن ندفع (push) ورقة في أعلى تلك الكومة لنضعها فيها.

نستطيع أن نسحب (pull) الورقة من أعلى تلك الكومة لنأخذها منها.


السحب من وسط تلك الكومة ليس سهلا، بسبب وزن الأوراق،

و لأن ذلك يعتبر غشا. لذلك لا نعتبره جزءا من قوانين المكدس.


لدينا مكدسنا هذا.

نفترض أن كل ورقة هنا هي "بايت".



أتينا بورقة آس القلوب من عندنا.

أعني بهذا، أتينا بها من المراكم A أو إحدى السجلات XY، أو حتى من عنوان رام.

و قررنا إضافتها للمكدس، حيث ستظل ورقة آس القلوب هذه لاجئة إلى حين ننتهي من تغييراتنا.

عندما ندفع بها للمكدس،

ستضاف إلى قمة تلك الكومة بعد آخر بايت.



و الآن عندما نقوم بالسحب من المكدس، نحو، على سبيل المثال، المراكم A،

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

يصبح المكدس هكذا:



و يصبح لدينا في المراكم ورقة آس القلوب التي سحبناها:



و بالطبع الأمانة تختفي من الوجود من المكدس.


بالطبع، لو لاحظتم، نحن دفعنا مرة واحدة،

ثم سحبنا أيضا نفس عدد المرات، مرة واحدة.

و النتيجة كانت أن المكدس عاد لحالته الأصلية.


يجب أن يعود المكدس دائما كما وجدناه قبل إستخدامه،

لأن هناك أجزاء مختلفة من البرمجة تستخدمه.

من أخطر هذه الأجزاء هو التعليمة JSR و RTS التي لتعرف كيف تعود بعد القفزة،

تقوم بتدوين العنوان الذي أتت منه و تضعه في المكدس.

يعني أن أي إستخدام متهور للمكدس سينجر عنه تعطل البرنامج بشكل أكيد...

فالمكدس مستودع أمانات العديد من البرمجات،

و لو عملنا عملية سحب زائدة أو دفع زائدة، فقد تذهب أمانة لغير صاحبها و يحصل ما لا يحمد عقباه.


و لذلك، يجب أن نحرص أن يكون عدد مرات عمليات الدفع (نحو المكدس)

مساويا لعدد مرات عمليات السحب (إلى خارج المكدس).



أما عن موقعه، فهو جزء صغير من ذاكرة الرام

حجمه من $00:0000 إلى $00:FFFF في WRAM في حالة السوبر نيس (و لو أنه من الآمن الإكتفاء بنصفها)،

و في حالة النيس هو أصغر من ذلك و حجمه من $0100 إلى $01FF (256 بايت فقط).


هذه الكومة تكبر شيئا فشيئا و لذلك يوجد ما يسمى بعداد المكدس (Stack Pointer).

هذا العداد (و رمزه S) يشير نحو قمة الكومة في الذاكرة. و هو ينقص كلما زاد حجم الكومة.

هناك طاقة إستيعاب قصوى للمكدس.

و أحيانا، بعض الألعاب تستعمل المكدس لنسخ البيانات بسرعة،

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


لو حصل و دفعنا نحو المكدس و كان المكدس ممتلئا، فسيحصل طفح المكدس Stack Overflow.

و سيكتب بالتأكيد نحو مكان مستعمل و يفسد محتوياته.

و لن نكتشف ذلك إلا بعد العديد من عمليات السحب إلى أن نبلغ المكان الذي تضرر.

أما لو سحبنا من المكدس و كان المكدس فارغا، فسيحصل غيض المكدس Stack Underflow،

و هو العكس بالضبط و لديه أيضا عواقب وخيمة كبيرة.


هذه أخطاء سببها الأساسي هو أن عدد مرات الدفع لم يكن مساويا لعدد مرات السحب:


الآن نذكر بعض تعليمات السحب و الدفع:


كل مرة نسحب و ندفع فيها، فنحن ننقل 1 بايت فقط.

في حالة النيس، لو كنا في وضع 16 بت، فسننقل 2 بايت.


و بالطبع لدينا مكدس واحد فقط.

يعني يمكننا أن نجعل المراكم A يضع أمانة في المكدس بعملية دفع PHA،

لكن لو قمنا بعدها بعملية سحب نحو السجل X ب PLX،

فسيتسلم السجل X أمانة المراكم A.

و بالطبع الأمانة تختفي من الوجود من المكدس.


نعود لمثالنا السابق مثلا من لعبة البطريق للنيس.

حيث أن إضافاتنا كانت تؤثر على المراكم و تفسده.


LDA #$01

...

STA $00C5

...


نريدها أن تصبح هكذا

LDA #$01

...

LDA #$0E

STA $00C4

LDA #$01

STA $00C5

...


نفترض أن LDA الأولى بعيدة جدا في أول البرنامج و لا نستطيع تغييرها ببساطة.

كل ما نعرفه هو A صار فيه القيمة 01.

و نحن نريد أن نكتب 0E في 00C4 لكن لو إستعملنا LDA لذلك، فيضيع المحتوى الأصلي ل A و الذي تحتاجه STA $00C5 أن يكون 01.

فما العمل للحفاظ على هذا المحتوى الأصلي؟


الحل هو إستعمال المكدس.


LDA #$01

PHA

LDA #$0E

STA $00C4

PLA

STA $00C5

...


في السطر PHA، قيمة A الأصلية (01) أحتفظ بها في المكدس.

ثم أتت إضافاتنا التي تصرفت في A و أضاعت محتواه الأصلي.

ثم تأتي PLA لتنقذ الموقف و تسترجع القيمة الأصلية (01) التي تركناها أمانة عند المكدس.


لذلك تعتبر فكرة جيدة أن نقوم بوضع


عدا هذا،

لا يوجد فقط قيم السجلات AXY التي يمكننا تركها أمانات عند المكدس.

نستطيع أيضا استخدام المكدس ليأوي لنا:



أظن هذا هو الجواب على تساءلاتكم أعلاه في أول الدرس،

عن كيف ينطلق البرنامج من جزء من عنوان كتب بالصفحة المباشرة ($CC) أو بالمطلق ($BBCC)

و يستنتج العنوان الكامل في الروم ($AABBCC).


عدا رقم كتلة البيانات (DBR)، فهناك رقم كتلة البرنامج (PBR) و هو يشبهه.

و هو رقم الكتلة AA الذي يوجد فيه سطر البرمجة المقصود.

يعني أننا لو كتبنا مثلا: JMP $2000 و كان في رقم كتلة البرنامج 03

فسيفهم البرنامج أن عليه أن ينفذ البرمجة الموجودة في الروم في العنوان $032000


رقم كتلة البرنامج يمكن دفعه للمراكم بواسطة PHK.

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

لا يغير كتلة البرنامج إلا تعليمات القفز الطويل.


أين تكمن أهمية رقم كتلة البرنامج هذا؟

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

لكن مع تعليمات القفز الطويل، يتغير فقط رقم كتلة البرنامج.

أما رقم كتلة البيانات فما يزال يشير للمكان القديم.


لذلك السبب تستخدم التعليمات التالية كثيرا في عدة برمجات يضيفها المهكرون.


PHB ; Preserve data bank

PHK ; Push program bank

PLB ; Pull into data bank


توضع البرمجة الإضافية هنا


PLB


السطر الأول: PHB يدفع برقم كتلة البيانات القديم (الذي لا بد أن البرنامج يحتاجه كما هو) نحو المكدس.

لن يقوم بسحبه إلا في آخر سطر على الإطلاق PLB.


السطر الثاني و الثالث عمليتان سريعتان لا تؤثران على ما سبق،

هدفها جعل رقم كتلة البيانات يأخذ نفس رقم كتلة البرنامج.

و ذلك يتم بدفع الأخير نحو المكدس، ثم يتم بسرعة سحب ما في قمة المراكم نحو رقم كتلة البيانات.


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


هناك أمر آخر.

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

لكن هناك وسائل للتصرف في الأوراق التي وسط الكومة...

هل تتذكرون مثلا LDA في أعلى الدرس من قائمة الأوبكودات التي توضح إستعمالاتها المتعددة؟


من بين إستعمالاتها... النسبة إلى المكدس.

و تكتب التعليمة LDA $01,s

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

لو وضعنا 02، فستضع في A الورقة التي تحت الورقة في القمة.

لو وضعنا 03، فستضع في A الورقة الثالثة... و هكذا.


يمكننا أيضا وضع 00 لنضع في A آخر ورقة سحبت من المكدس و "زالت من الوجود"،

لكن هذا غير منصوح به نظرا لأنه لو استعمل المكدس بعد ذلك فقد تكون النتائج غير متوقعة.

و هناك أيضا تعليمة STA $01,s أيضا.


و بهذا، نكون أنهينا حديثنا عن المكدس و فوائده.

عندما نريد إضافة برمجة إضافية تعبث بالسجلات،

فمن الأحرى بنا إستخدام المكدس للحفاظ على هذه السجلات أولا قبل أن نبدأ عبثنا،

كي نتمكن من استرجاعها كما كانت بعد ذلك

و تعمل بها بقية اللعبة (لا يجب أن نكون أنانيين).


ختاما

أخذنا فكرة بسيطة عن إستخدام مجمع الأسمبلي،

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

كما رأينا المكدس، الذي يسمح لنا أن نحتفظ بحالة البرنامج الأصلية

دون أن تؤثر إضافاتنا على بقية تعليمات اللعبة اللاحقة التي تتوقع الحالة الأصلية.


أما بخصوص الأمثلة، فقد بدأنا (أخيرا!) في تناول مسألة عكس إتجاه سريان الحروف ليصير من اليمين لليسار.

لكن هناك عديد الحالات، و نحن اخترنا أبسطها (و بذلك نحن مكرهون في الوقت الراهن على أجهزة ال8 و 16 بت).

هناك إشكال في ما فعلناه...

و هو أن الأرقام عكست أيضا مع النص.

كيف نستطيع أن نجعل الأرقام لا تعكس مع النص؟

لذلك الغرض، يجب أن نكيل بمكيالين، واحد للأرقام و واحد لغيرها.

كيف عسانا نفعل ذلك؟

الجواب هو مع تعليمات مثل تعليمات القفز ... لكنها تعليمات لها وجهتان مختلفتان،

و هي تعليمات التفرع. و تعتمد على شروط تحدد أي الطريقين نختار.


تعليمات التفرع هذه ستكون إذن محل درسنا القادم إن شاء الله.

و سيكون الدرس القادم أيضا فرصة لنا لرؤية وسائل جديدة لإدخال برمجتنا الجديدة في الروم.

كما نعتزم تخصيص قسط من الحديث في المستقبل عن خرائط التايل...

بما أن التعديل المباشر عليها هو ما سيفتح لنا أبواب طرق عكس السريان الأخرى.


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

ليس بالضرورة للنيس فقط، بل حتى للسوبر نيس أيضا.