بطبيعة الحال ، من غير المحتمل أن تتبادر فكرة كتابة لعبة بلغة التجميع إلى ذهن شخص بمفرده ، ومع ذلك ، فهو شكل معقد من التقارير تم ممارسته منذ فترة طويلة في السنة الأولى من VMK بجامعة موسكو الحكومية. ولكن نظرًا لأن التقدم لا يقف ساكنًا ، يصبح كل من DOS و Masm تاريخًا ، ويأتي nasm و Linux في طليعة إعداد العزاب. ربما في غضون عشر سنوات ، ستكتشف قيادة الكلية الثعبان ، ولكن هذا ليس حول ذلك الآن.
برمجة Assembler في Linux ، بكل مزاياها ، تجعل من المستحيل استخدام مقاطعات BIOS ، ونتيجة لذلك ، تحرمهم من الوظائف. بدلاً من ذلك ، يجب عليهم استخدام مكالمات النظام والاتصال بواجهة برمجة التطبيقات الطرفية. لذلك ، كتابة محاكاة لعبة ورق أو معركة بحرية لا يسبب صعوبات كبيرة ، وهناك مشاكل مع الثعبان العادي. والحقيقة هي أن نظام الإدخال والإخراج يتحكم فيه الطرف ، ولا يمكن استخدام وظائف النظام C مباشرة. لذلك ، عند كتابة ألعاب بسيطة إلى حد ما ، تولد كتلتان متعثرتان: كيفية تحويل المحطة الطرفية إلى الوضع غير القانوني وكيفية جعل إدخال لوحة المفاتيح غير قابل للحظر. سيتم مناقشة هذا في المقالة.
1. الوضع غير القانوني للمحطة
كما تعلمون ، لفهم ما تفعله دالة في C ، تحتاج إلى التفكير مثل وظيفة في C. لحسن الحظ ، ليس من الصعب تبديل الجهاز الطرفي إلى الوضع غير القانوني. هذا ما يقدمه لنا
المثال في وثائق GNU الرسمية إذا قمت بإزالة رمز المساعد منه:
struct termios saved_attributes; void reset_input_mode (void) { tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes); } void set_input_mode (void) { struct termios tattr; tcgetattr (STDIN_FILENO, &saved_attributes); tcgetattr (STDIN_FILENO, &tattr); tattr.c_lflag &= ~(ICANON|ECHO); tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr); }
في هذا الكود ، يعني STDIN_FILENO مقبض دفق الإدخال الذي نعمل معه (بشكل افتراضي هو 0) ، و ICANON هي علامة التمكين لنفس الإدخال الأساسي ، و ECHO هي علامة لعرض أحرف الإدخال على الشاشة ، و TCSANOW و TCSAFLUSH عبارة عن وحدات ماكرو محددة بالمكتبة. وهكذا ، تبدو الخوارزمية "العارية" ، الخالية من الفحوصات الأمنية ، كما يلي:
- الحفاظ على هيكل النمل الأصلي ؛
- نسخ محتوياته مع تغيير علامتي ICANON و ECHO ؛
- إرسال الهيكل المتغير إلى المحطة ؛
- عند الانتهاء من العمل ، ارجع إلى الهيكل المحفوظ.
يبقى أن نفهم ما تقوم به المكتبة tcsetattr و tcgetattr.
في الواقع ، إنهم يفعلون الكثير من الأشياء ، ولكن
استدعاء نظام
ioctl هو مفتاح عملهم. الحجة الأولى التي تأخذها هي واصف دفق (0 في حالتنا) ، والثانية هي مجموعة من العلامات التي تم تعريفها للتو من قبل وحدات الماكرو TCSANOW و TCSAFLUSH ، والثالث هو مؤشر للبنية (في مصطلحات الحالة الخاصة بنا). على بناء جملة nasm وتحت اتفاقية استدعاءات النظام على لينكس ، سيأخذ الشكل التالي:
mov rax, 16 ; ioctl mov rdi, 0 ; mov rsi, TCGETS ; mov rdx, tattr ; syscall
بشكل عام ، هذه هي النقطة الكاملة لوظائف tcsetattr و tcgetattr. بالنسبة لبقية الشفرة ، نحتاج إلى معرفة حجم وهيكل بنية النهايات ، والتي يسهل العثور عليها أيضًا في
الوثائق الرسمية . حجمها افتراضيًا هو 60 بايت ، وصفيف الأعلام التي نحتاجها هو 4 بايت في الحجم ويقع في المركز الرابع على التوالي. يبقى كتابة إجراءين ودمجهما في رمز واحد.
تحت المفسد ، فإن أبسط تنفيذه ليس هو الأكثر أمانًا بأي حال من الأحوال ، ولكنه يعمل جيدًا على أي نظام تشغيل يدعم معايير POSIX. تم أخذ القيم الكلية من المصادر المذكورة أعلاه لمكتبة C القياسية.
الانتقال إلى الوضع غير القانوني %define ICANON 2 %define ECHO 8 %define TCGETS 21505 ; %define TCPUTS 21506 ; global setcan ; global setnoncan ; section .bss stty resb 12 ; termios - 60 slflag resb 4 ;slflag 3*4 srest resb 44 tty resb 12 lflag resb 4 brest resb 44 section .text setnoncan: push stty call tcgetattr push tty call tcgetattr and dword[lflag], (~ICANON) and dword[lflag], (~ECHO) call tcsetattr add rsp, 16 ret setcan: push stty call tcsetattr add rsp, 8 ret tcgetattr: mov rdx, qword[rsp+8] push rax push rbx push rcx push rdi push rsi mov rax, 16 ;ioctl system call mov rdi, 0 mov rsi, TCGETS syscall pop rsi pop rdi pop rcx pop rbx pop rax ret tcsetattr: mov rdx, qword[rsp+8] push rax push rbx push rcx push rdi push rsi mov rax, 16 ;ioctl system call mov rdi, 0 mov rsi, TCPUTS syscall pop rsi pop rdi pop rcx pop rbx pop rax ret
2. المدخلات غير المحظورة في المحطة
بالنسبة لمدخلات الأموال غير المحظورة ، فإن المحطة ليست كافية بالنسبة لنا. سنكتب وظيفة تتحقق من المخزن المؤقت القياسي للتدفق من أجل الاستعداد لإرسال المعلومات: إذا كان هناك رمز في المخزن المؤقت ، فسوف يعيد رمزه ؛ إذا كان المخزن المؤقت فارغًا ، فسوف يعود 0. لهذا الغرض ، يمكنك استخدام مكالمتين للنظام - الاستطلاع () أو تحديد (). كلاهما قادر على عرض تيارات المدخلات والمخرجات المختلفة على حقيقة أي حدث. على سبيل المثال ، إذا وصلت المعلومات في أي من التدفقات ، فيمكن لكل من مكالمات النظام هذه التقاط هذا وعرضه في البيانات التي تم إرجاعها. ومع ذلك ، فإن الثاني منهم هو في الأساس نسخة محسنة من الأولى وهو مفيد عند العمل مع سلاسل رسائل متعددة. ليس لدينا مثل هذا الهدف (نحن نعمل فقط مع الدفق القياسي) ، لذلك سنستخدم مكالمة الاستطلاع ().
كما يقبل ثلاث معلمات كمدخلات:
- مؤشر إلى بنية البيانات ، والذي يحتوي على معلومات حول واصفات التدفقات المراقبة (سنناقشها أدناه) ؛
- عدد الخيوط المعالجة (لدينا واحدة) ؛
- الوقت بالمللي ثانية الذي يمكن خلاله توقع حدث (نحتاج إلى حدوثه على الفور ، بحيث تكون هذه المعلمة 0).
من
الوثائق يمكنك معرفة أن بنية البيانات المطلوبة لديها الجهاز التالي:
struct pollfd { int fd; short events; short revents; };
يتم استخدام واصفها كواصف ملف (نحن نعمل مع دفق قياسي ، وبالتالي فهو 0) ، والأحداث المطلوبة نستخدم علامات مختلفة ، نحتاج فقط إلى العلم لوجود البيانات في المخزن المؤقت. لها اسم POLLIN وتساوي 1. نتجاهل مجال الأحداث التي تم إرجاعها ، لأننا لا نقدم أي معلومات لدفق الإدخال. ثم ستبدو مكالمة النظام المطلوبة كما يلي:
section .data fd dd 0 ; eve dw 1 ; - POLLIN rev dw 0 ; section .text poll: nop push rbx push rcx push rdx push rdi push rsi mov rax, 7 ; poll mov rdi, fd ; mov rsi, 1 ; mov rdx, 0 ; syscall
يقوم استدعاء نظام الاستقصاء () بإرجاع عدد مؤشرات الترابط التي حدثت فيها أحداث "مثيرة للاهتمام". نظرًا لأن لدينا مؤشر ترابط واحد فقط ، فإن القيمة المرتجعة هي 1 (يتم إدخال البيانات) أو 0 (لا يوجد). ومع ذلك ، إذا لم يكن المخزن المؤقت فارغًا ، فإننا نقوم فورًا باستدعاء نظام آخر - قراءة - وقراءة رمز الحرف الذي تم إدخاله. نتيجة لذلك ، نحصل على الكود التالي.
المدخلات غير المحظورة في المحطة section .data fd dd 0 ; eve dw 1 ; - POLLIN rev dw 0 ; sym db 1 section .text poll: nop push rbx push rcx push rdx push rdi push rsi mov rax, 7 ; poll mov rdi, fd ; mov rsi, 1 ; mov rdx, 0 ; syscall test rax, rax ; 0 jz .e mov rax, 0 mov rdi, 0 ; mov rsi, sym ; read mov rdx, 1 syscall xor rax, rax mov al, byte[sym] ; , .e: pop rsi pop rdi pop rdx pop rcx pop rbx ret
وبالتالي ، يمكنك الآن استخدام وظيفة الاستطلاع لقراءة المعلومات. إذا لم يتم إدخال أي بيانات ، أي أنه لم يتم الضغط على أي زر ، فسيتم إرجاع 0 وبالتالي لن يمنع عمليتنا. بالطبع ، هذا التطبيق له عيوب ، على وجه الخصوص ، يمكن أن يعمل فقط مع أحرف ascii ، ولكن يمكن تغييره بسهولة اعتمادًا على المهمة.
الوظائف الثلاث الموضحة أعلاه (setcan و setnoncan و poll) كافية لضبط الإدخال الطرفي لنفسك أنت. فهي بسيطة للغاية لفهمها واستخدامها. ومع ذلك ، في لعبة حقيقية ، سيكون من الجيد تأمينها وفقًا لمنهج C المعتاد ، ولكن هذا بالفعل عمل مبرمج.
مصادر
1)
مصادر الدالتين tcgetattr و tcsetattr ؛
2)
توثيق استدعاء نظام ioctl ؛
3)
وثائق استدعاء نظام الاقتراع .
4)
وثائق النهايات .
5)
جدول استدعاء النظام تحت Linux x64 .