На досуге задался вопросом возможности создания приложения, со следующими требованиями:
Вообще, эталон приложения с размером до 1 Кб — это 1k intro, являющееся разновидностью демосцен. Чаще всего это написанная на ассемблере инициализация OpenGL с последующим скармливанием ему шейдера, который и выполняет основную работу (отрисовывает какие-нибудь раскрашенные фракталы). Плюс всё это пожато упаковщиком (например crinkler).
Эти приложения в буквальном смысле вылизаны до байта, их разработка занимает недели и даже месяцы.
Такой подход слишком суров, я решил не выходить за рамки обычного прикладного программирования на WinAPI.
В это время в Steam началась летняя распродажа, где взрослым людям предлагалось дичайше затыкивать инопланетян мышой в браузере (да-да, Valve снова делает игры, как и обещал Гейб).
Это оскорбило меня до глубины души, и я решил попробовать реализовать простейший автокликер с минимальными настройками.
Требовалось уместить в 1 Кб:
Приложение будет создаваться в MSVC на чистом C без ассемблера и СМС, а затем сжиматься пакером crinkler. Следует отметить, что данные (особенно разреженные) crinkler сжимает гораздо эффективнее, чем код (раза эдак в два). Поэтому будем стараться максимум функционала переносить внутрь данных.
Начав с классических CreateWindow для каждого элемента окна, я понял, что в требуемый размер я никак не влезу.
Пришлось искать альтернативу. И ей стала функция CreateDialogIndirect, создающая диалог из заполненной структуры DLGTEMPLATE (состоящей из кучи DLGITEMTEMPLATE)
Для удобного создания и заполнения структуры я завёл немножко макросов вроде таких:
Теперь можно объявить и заполнить структуру элементами будущего окна:
Скармливаем структуру функции CreateDialogIndirect, и вот получившееся окно:

Так как мы умещаемся в 1 кб, то манифеста, как и всего прочего, у нас нет, а значит и визуальных стилей тоже. Всё серое и квадратное, как в молодости.
Но мы всё-таки извернёмся, дёрнув манифест из shell32.dll и применив контекст на его основе к нашему приложению:
Вот уже стильно, модно:

Оконную функцию удалось ужать до довольно компактной:
И тут я подумал, что хорошо бы добавить обработку аргументов командной строки, дабы пользователь имел возможность запускаться с нужными настройками.
Например эдак:
Перенесём часть функционала внутрь данных и получим что-то вроде:
Обработчик вышел весьма небольшим. Естественно, никаких проверок и защит от неверного ввода, только необходимый минимум.
Теперь основной функционал (его я вынес в отдельный поток):
Жмём получившийся obj-файл crinkler'ом — на выходе 973 байта.
Из них данные занимают 163 байта (степень сжатия 33.1%), код — 517 байт (степень сжатия 68.9%).
Можно ужать и сильнее (ещё байт на 50), но цель и так достигнута. Даже остался 51 запасной байт.
К первоначальнми требованиям по ходу добавились:
Местами код выглядит весьма криво. Это потому, что я его криво написал. А ещё кое-где это позволило сэкономить место.
Наверняка можно придумать ещё пару-тройку способов уменьшить размер приложения, но не кардинально (я думаю, байт на 50).
Результат:
Теперь можно закликивать инопланетян в автоматическом режиме с диким уроном в секунду.
Вполне возможно создавать сверхкомпактные приложения с реально используемым полезным функционалом и оконным интерфейсом.
Новизна:
Нулевая. Собрал в кучу несколько приёмов\наработок.
Целесообразность:
Бесполезно, забавы для.
Исходник
Бинарник
Принимаются критика, пожелания, предложения, восхищения, возмущения.
- хоть сколько-нибудь полезная прикладная функция (то есть не пустышка)
- наличие оконного интерфейса
- размер менее 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).
Результат:
Вполне возможно создавать сверхкомпактные приложения с реально используемым полезным функционалом и оконным интерфейсом.
Новизна:
Нулевая. Собрал в кучу несколько приёмов\наработок.
Целесообразность:
Бесполезно, забавы для.
Исходник
Бинарник
Принимаются критика, пожелания, предложения, восхищения, возмущения.