Application GUI inférieure à 1 ko

À loisir, je me suis demandé la possibilité de créer une application répondant aux exigences suivantes:

  • au moins une fonction d'application utile (c'est-à-dire pas un mannequin)
  • la présence d'une interface de fenêtre
  • taille inférieure à 1 Kb

En général, la norme d'application avec une taille allant jusqu'à 1 Kb est 1k intro, ce qui est une sorte de demoscene. Le plus souvent, cela est écrit dans l'initialisation de l'assembleur d'OpenGL, puis en lui fournissant un shader, qui fait le travail principal (dessine des fractales colorées). De plus, tout cela est récolté par le packer (par exemple Crinkler).
Ces applications sont littéralement léchées à un octet, leur développement prend des semaines voire des mois.

Cette approche étant trop dure, j'ai décidé de ne pas aller au-delà de la programmation applicative habituelle sur WinAPI.

À cette époque, une vente d'été a commencé sur Steam, où les adultes ont été invités à agiter sagement les extraterrestres avec une souris dans le navigateur (oui, Valve fait à nouveau des jeux, comme Gabe l'a promis).
Cela m'a offensé pour l'essentiel, et j'ai décidé d'essayer d'implémenter l'autoclicker le plus simple avec des paramètres minimaux.

Requis pour tenir dans 1 Ko:

  • création et initialisation d'interface
  • fonction de fenêtre avec des gestionnaires d'événements
  • logique d'application principale (basée sur les fonctions GetAsyncKeyState et SendInput)

L'application sera créée en MSVC en C pur sans assembleur et SMS, puis compressée avec le pack de crinkler. Il convient de noter que Crinkler compresse les données (particulièrement raréfiées) beaucoup plus efficacement que le code (environ deux fois). Par conséquent, nous essaierons de transférer le maximum de fonctionnalités dans les données.

En commençant par le CreateWindow classique pour chaque élément de fenêtre, j'ai réalisé que je ne pouvais pas tenir dans la taille requise.

J'ai dû chercher une alternative. Et elle est devenue la fonction CreateDialogIndirect, créant un dialogue à partir de la structure DLGTEMPLATE remplie (constituée d'un tas DLGITEMTEMPLATE)

Pour faciliter la création et le remplissage de la structure, j'ai commencé quelques macros comme celles-ci:

#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} 

Vous pouvez maintenant déclarer et remplir la structure avec les éléments de la future fenêtre:

 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), }; 

Nous alimentons la structure de la fonction CreateDialogIndirect, et voici la fenêtre résultante:



Puisque nous tenons dans 1 ko, nous n'avons pas de manifeste, comme tout le reste, et donc il n'y a pas non plus de styles visuels. Tout est gris et carré, comme dans la jeunesse.

Mais nous esquivons toujours, en tirant le manifeste de shell32.dll et en appliquant le contexte basé sur celui-ci à notre application:

 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); 

C'est déjà élégant, à la mode:



Nous avons réussi à réduire la fonction de fenêtre à une fonction assez compacte:

 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); 

Et puis j'ai pensé qu'il serait bien d'ajouter le traitement des arguments de ligne de commande afin que l'utilisateur ait la possibilité de commencer avec les paramètres nécessaires.

Par exemple:

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

Déplacez la partie de la fonctionnalité à l'intérieur des données et obtenez quelque chose comme:

 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; } } 

Le gestionnaire est sorti très petit. Naturellement, aucun contrôle et protection contre une saisie incorrecte, seulement le minimum nécessaire.

Maintenant, la fonctionnalité principale (je l'ai mise dans un flux séparé):

 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); } 


Nous appuyons sur le fichier obj résultant crinkler'om - la sortie est de 973 octets.

Parmi celles-ci, les données occupent 163 octets (taux de compression 33,1%), le code - 517 octets (taux de compression 68,9%).

Il est possible de rétrécir et de renforcer (encore 50 octets), mais l'objectif est déjà atteint. Il restait même 51 octets de réserve.

Les exigences initiales en cours de route ont été ajoutées:

  • traitement des arguments de ligne de commande
  • afficher une fenêtre avec des styles visuels

Dans certains endroits, le code semble très tordu. C'est parce que je l'ai écrit de travers. Et dans certains endroits, cela a permis d'économiser de l'espace.

Vous pouvez sûrement trouver deux autres façons de réduire la taille de l'application, mais pas de façon spectaculaire (je pense que l'octet est de 50).

Résultat:

Maintenant, vous pouvez appeler des extraterrestres en mode automatique avec des dégâts sauvages par seconde.

Il est possible de créer des applications ultra-compactes avec des fonctionnalités utiles réellement utilisées et une interface de fenêtre.

Nouveauté:
Zéro J'ai rassemblé un tas de techniques / développements.

Opportunité:
Inutile, amusant pour.

Code source
Binaire

Critiques, souhaits, suggestions, admiration, indignation acceptés.

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


All Articles