تطبيق واجهة المستخدم الرسومية أصغر من 1 كيلو بايت

في أوقات الفراغ ، سألت نفسي إمكانية إنشاء تطبيق بالمتطلبات التالية:

  • على الأقل بعض وظائف التطبيق المفيدة (أي ليست دمية)
  • وجود واجهة نافذة
  • حجم أقل من 1 كيلو بايت

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

هذا النهج قاسي للغاية ، قررت عدم تجاوز برمجة التطبيقات المعتادة على WinAPI.

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

مطلوب للتوافق مع 1 كيلوبايت:

  • إنشاء واجهة والتهيئة
  • وظيفة النافذة مع معالجات الأحداث
  • منطق التطبيق الرئيسي (مبني على وظائف GetAsyncKeyState و SendInput)

سيتم إنشاء التطبيق في MSVC في C خالٍ بدون المجمّع والرسائل النصية القصيرة ، ثم ضغطه باستخدام أداة التكسير. وتجدر الإشارة إلى أن crinkler يضغط البيانات (خاصة المضحكة) بكفاءة أكبر بكثير من الكود (حوالي مرتين). لذلك ، سنحاول نقل الحد الأقصى من الوظائف إلى البيانات.

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

كان علي أن أبحث عن بديل. وأصبحت وظيفة CreateDialogIndirect ، حيث أنشأت حوارًا من بنية DLGTEMPLATE المملوءة (تتكون من كومة DLGITEMTEMPLATE)

من أجل إنشاء وملء مريح للهيكل ، بدأت بعض وحدات الماكرو مثل هذه:

#define NUMCHARS(p) (sizeof(p)/sizeof((p)[0])) #define DLGCTL(a) struct{DWORD style; DWORD exStyle; short x; short y; short cx; short cy; WORD id; WORD sysClass; WORD idClass; WCHAR wszTitle[NUMCHARS(a)]; WORD cbCreationData;} #define RADIO(x,y,cx,cy,id,title) {WS_VISIBLE|WS_CHILD|BS_RADIOBUTTON, 0, (x)/2, (y)/2,\n (cx)/2,(cy)/2, id, 0xFFFF, 0x0080, title, 0} 

الآن يمكنك التصريح وملء الهيكل بعناصر النافذة المستقبلية:

 static struct { DWORD style; DWORD dwExtendedStyle; WORD ccontrols; short x; short y; short cx; short cy; WORD menu; WORD windowClass; DLGCTL(LBL_BTN_LEFT) button_left; DLGCTL(LBL_BTN_MIDDLE) button_middle; DLGCTL(LBL_BTN_RIGHT) button_right; } Dlg = { WS_VISIBLE|WS_POPUP|WS_BORDER, 0, TOTAL_CONTROLS, 50/2, 50/2, WND_CX/2, WND_CY/2, 0, 0, RADIO(10, 0, 80, 30, IDC_BTN_LEFT, LBL_BTN_LEFT), RADIO(100, 0, 80, 30, IDC_BTN_MIDDLE, LBL_BTN_MIDDLE), RADIO(190, 0, 68, 30, IDC_BTN_RIGHT, LBL_BTN_RIGHT), }; 

نقوم بتغذية هيكل دالة CreateDialogIndirect ، وهنا النافذة الناتجة:



نظرًا لأننا نتناسب بحجم 1 كيلوبايت ، فليس لدينا بيان ، مثل كل شيء آخر ، وبالتالي لا توجد أنماط مرئية أيضًا. كل شيء رمادي ومربع ، كما هو الحال في الشباب.

لكننا ما زلنا نراوغ ، ونخرج البيان من shell32.dll ونطبق السياق بناءً عليه على تطبيقنا:

 static ACTCTX actCtx = {sizeof(ACTCTX), ACTCTX_FLAG_RESOURCE_NAME_VALID|ACTCTX_FLAG_SET_PROCESS_DEFAULT|ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, "shell32.dll", 0, 0, tmp, (LPCTSTR)124, 0, 0}; GetSystemDirectory(tmp,sizeof(tmp)); LoadLibrary("shell32.dll"); ActivateActCtx(CreateActCtx(&actCtx),(ULONG_PTR*)&tmp); 

إنه أنيق وعصري بالفعل:



تمكنا من تقليص وظيفة النافذة إلى وظيفة مضغوطة إلى حد ما:

 switch(msg) { case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_BTN_LEFT: case IDC_BTN_MIDDLE: case IDC_BTN_RIGHT: input[0].mi.dwFlags = wParam; input[1].mi.dwFlags = (wParam<<1); CheckRadioButton(hWnd,IDC_BTN_LEFT,IDC_BTN_MIDDLE,wParam); break; case IDC_BTN_HOLD: case IDC_BTN_TRIGGER: trigger_mode = (wParam==IDC_BTN_TRIGGER); CheckRadioButton(hWnd,IDC_BTN_HOLD,IDC_BTN_TRIGGER,wParam); break; case IDC_EDIT_PERIOD: period = GetDlgItemInt(hWnd,IDC_EDIT_PERIOD,(BOOL*)&tmp[0],0); break; case IDC_BTN_EXIT: exit(0); } break; } return DefWindowProc(hWnd,msg,wParam,lParam); 

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

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

input.exe /L /T /P:20 /K:9 - 20 , / Tab

انقل جزء الوظيفة داخل البيانات واحصل على شيء مثل:

 static unsigned int arg_to_cmd[] = {IDC_BTN_HOLD, 0, 0, IDC_EDIT_KEY, IDC_BTN_LEFT, IDC_BTN_MIDDLE, 0, 0, IDC_EDIT_PERIOD, 0, IDC_BTN_RIGHT, 0, IDC_BTN_TRIGGER}; i = (char*)GetCommandLine(); while(*i) { if (*(i++)=='/')//looking for argument switch(*i) { case 'L': case 'M': case 'R': case 'H': case 'T': SendMessage(hWnd,WM_COMMAND,arg_to_cmd[*i-'H'],0);//send button command break; case 'P': case 'K': t = atoi(i+2); SetDlgItemInt(hWnd,arg_to_cmd[*i-'H'],t,0); if(*i=='P') period = t; else key = t; break; } } 

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

الآن الوظيفة الرئيسية (أضعه في تيار منفصل):

 while(1) { key_state = (GetAsyncKeyState(key)>>1); if (trigger_mode) { if ((key_state)&&(key_state!=prev_key_state)) active^= 1; prev_key_state = key_state; } else active = key_state; if (active) SendInput(2,(LPINPUT)&input,sizeof(INPUT)); Sleep(period); } 


نضغط على crinkler'om الناتج عن ملف obj - الناتج هو 973 بايت.

تشغل البيانات 163 بايت (نسبة الضغط 33.1٪) ، الرمز - 517 بايت (نسبة الضغط 68.9٪).

من الممكن أن تتقلص وأقوى (50 بايت أخرى) ، ولكن تم تحقيق الهدف بالفعل. بقيت حتى 51 بايت احتياطي.

تمت إضافة المتطلبات الأولية على طول الطريق:

  • معالجة الحجج سطر الأوامر
  • عرض نافذة ذات أنماط مرئية

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

بالتأكيد يمكنك الخروج بطريقتين إضافيتين لتقليل حجم التطبيق ، ولكن ليس بشكل كبير (أعتقد أن البايت هو 50).

النتيجة:

الآن يمكنك استدعاء الأجانب في الوضع التلقائي مع حدوث ضرر شديد في الثانية.

من الممكن إنشاء تطبيقات مدمجة للغاية مع وظائف مفيدة مستخدمة بالفعل وواجهة نافذة.

الجدة:
صفر جمعت مجموعة من التقنيات / التطورات.

النفعية:
لا جدوى من المرح.

كود المصدر
ثنائي

النقد المقبول والرغبات والاقتراحات والإعجاب والسخط.

Source: https://habr.com/ru/post/ar416135/


All Articles