في أوقات الفراغ ، سألت نفسي إمكانية إنشاء تطبيق بالمتطلبات التالية:
- على الأقل بعض وظائف التطبيق المفيدة (أي ليست دمية)
- وجود واجهة نافذة
- حجم أقل من 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++)=='/')
خرج المعالج صغير جداً. وبطبيعة الحال ، لا توجد ضوابط وحماية ضد المدخلات غير الصحيحة ، فقط الحد الأدنى الضروري.
الآن الوظيفة الرئيسية (أضعه في تيار منفصل):
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).
النتيجة:
الآن يمكنك استدعاء الأجانب في الوضع التلقائي مع حدوث ضرر شديد في الثانية.من الممكن إنشاء تطبيقات مدمجة للغاية مع وظائف مفيدة مستخدمة بالفعل وواجهة نافذة.
الجدة:
صفر جمعت مجموعة من التقنيات / التطورات.
النفعية:
لا جدوى من المرح.
كود المصدرثنائيالنقد المقبول والرغبات والاقتراحات والإعجاب والسخط.