На досуге задался вопросом возможности создания приложения, со следующими требованиями:
- хоть сколько-нибудь полезная прикладная функция (то есть не пустышка)
- наличие оконного интерфейса
- размер менее 1 Кб
Вообще, эталон приложения с размером до 1 Кб — это 1k intro, являющееся разновидностью демосцен. Чаще всего это написанная на ассемблере инициализация OpenGL с последующим скармливанием ему шейдера, который и выполняет основную работу (отрисовывает какие-нибудь раскрашенные фракталы). Плюс всё это пожато упаковщиком (например crinkler)
Эти приложения в буквальном смысле вылизаны до байта, их разработка занимает недели и даже месяцы.
Такой подход слишком суров, я решил не выходить за рамки обычного прикладного программирования на WinAPI.
В это время в Steam началась летняя распродажа, где взрослым людям предлагалось дичайше затыкивать инопланетян мышой в браузере (да-да, Valve снова делает игры, как и обещал Гейб).
Это оскорбило меня до глубины души, и я решил попробовать реализовать простейший автокликер с минимальными настройками.
Требовалось уместить в 1 Кб:
- создание и инициализацию интерфейса
- оконную функцию с обработчиками событий
- основную логику приложения (построенную на функциях GetAsyncKeyState и SendInput)
Приложение будет создаваться в MSVC на чистом C без ассемблера и СМС, а затем сжиматься пакером crinkler. Следует отметить, что данные (особенно разреженные) 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);
}
Жмём получившийся obj-файл crinkler'ом — на выходе 973 байта.
Из них данные занимают 163 байта (степень сжатия 33.1%), код — 517 байт (степень сжатия 68.9%).
Можно ужать и сильнее (ещё байт на 50), но цель и так достигнута. Даже остался 51 запасной байт.
К первоначальнми требованиям по ходу добавились:
- обработка аргументов командной строки
- отображение окна с визуальными стилями
Местами код выглядят весьма криво. Это потому, что я его криво написал. А ещё кое-где это позволило сэкономить место.
Наверняка можно придумать ещё пару-тройку способов уменьшить размер приложения, но не кардинально (я думаю, байт на 50).
Результат:
Теперь можно закликивать инопланетян в автоматическом режиме с диким уроном в секунду.
Вполне возможно создавать сверхкомпактные приложения с реально используемым полезным функционалом и оконным интерфейсом.
Новизна:
Нулевая. Собрал в кучу несколько приёмовнаработок.
Целесообразность:
Бесполезно, забавы для.
Принимаются критика, пожелания, предложения, восхищения, возмущения.
Автор: Boroda1