Есть ли идеи? С чего начать? Что следует посмотреть? Существует ли редактор, который перегонит gif-файл в иконку или курсор? Достаточно ли просто найти анимированную иконку/курсор и присоединить ее при помощи LoadCursorFromFile к WC структуре и вызвать RegisterClassEx? Или нужны еще какие-то пляски с бубном?
Надеюсь поможет, надо проверять на пракике: --- --- Анимированный курсор: всё проще, чем кажется Вы почти правы. Последовательность действий для курсора действительно проста: Конвертируйте: Возьмите ваш GIF и превратите его в формат .ani. Загрузите: Используйте LoadCursorFromFile() для получения хэндла. Примените: Передайте этот хэндл в поле hCursor структуры WNDCLASSEX перед вызовом RegisterClassEx(). --- --- Анимированная иконка: нужен «костыль». Здесь есть важный нюанс. Стандартные поля hIcon и hIconSm ожидают статическую иконку (.ico). Если вы попытаетесь передать туда .ani, система, скорее всего, просто покажет первый кадр или ничего-6-12. Чтобы сделать иконку анимированной, нужно самостоятельно управлять её отрисовкой: Загрузите несколько статических иконок (HICON) в массив. Создайте таймер (SetTimer). В обработчике WM_TIMER меняйте текущую иконку окна, отправляя сообщение WM_SETICON. --- --- 1. Использование библиотеки Clickgen. Эта библиотека специально создана для сборки курсоров (включая Windows .ani) и абстрагирует всю работу с форматом. pip install clickgen 2. Подготовка кадров: Вам нужно сначала разбить ваш GIF на отдельные PNG-файлы (по одному на каждый кадр). Это можно сделать любым удобным способом (например, через Pillow или онлайн-конвертер). Пусть они лежат в папке frames/ с именами типа frame-001.png, frame-002.png и т.д. 3. Скрипт конвертации: Код (Text): from glob import glob from clickgen.parser import open_blob from clickgen.writer import to_win # 1. Собираем пути ко всем PNG-кадрам fnames = sorted(glob("frames/*.png")) # 2. Читаем файлы как бинарные данные pngs = [] for f in fnames: with open(f, "rb") as p: pngs.append(p.read()) # 3. Создаем объект курсора. # hotspot — это координаты "горячей точки" (x, y), которую система считает точкой клика. # Для стрелки это обычно (0, 0), для других курсоров нужно подбирать. cur = open_blob(pngs, hotspot=(0, 0)) # 4. Конвертируем в формат Windows # 'to_win' возвращает кортеж: (расширение_файла, бинарные_данные) ext, result = to_win(cur.frames) # 5. Сохраняем результат output_filename = f"my_cursor{ext}" with open(output_filename, "wb") as o: o.write(result) print(f"Готово! Файл сохранен как {output_filename}") Самая важная часть в коде — параметр hotspot в функции open_blob. Это координаты (x, y) внутри картинки, которые Windows будет считать «острием» курсора. Если поставить их неправильно (например, для курсора-руки поставить в левый верхний угол), то при клике система будет думать, что вы кликаете не туда, куда указывает курсор. Для простой стрелки обычно подходит (0, 0), но в некоторых случаях (например, если у вас есть прозрачные поля вокруг рисунка) их нужно подбирать вручную. --- --- Чем еще можно конвертировать GIF в ANI? С конвертацией GIF в .ani проблем нет, есть несколько путей: Онлайн-сервисы: Самый быстрый вариант. Например, iotools.cloud позволяет загрузить GIF, выбрать размер, задать «горячую точку» (hotspot) и скачать готовый .ani-4. Специализированный софт: AniTuner — бесплатная программа для Windows, умеет создавать и редактировать .ani из GIF, AVI или набора картинок-14. Steam: Simple Cursor Maker — платная, но удобная утилита в Steam, которая конвертирует GIF/PNG в .ani и позволяет сразу собрать установочный пакет-7. --- ---
Application, спасибо Нашел статью у Manhanter'a Создание анимированной иконки приложения Там он загружает 8 иконок, Код (ASM): .wminitdialog: invoke LoadIcon,[hInstance],1; Загрузить иконку invoke SendMessage,[hwnddlg],WM_SETICON,ICON_BIG,eax; Установить иконку окна invoke SetTimer,[hwnddlg],1,100,NULL; Установить таймер обновления иконки mov [nIcon],1 jmp .processed .animate: inc [nIcon]; Анимация иконки окна cmp [nIcon],8 jbe @f mov [nIcon],1; Перейти к первому кадру @@: invoke LoadIcon,[hInstance],[nIcon]; Загрузить следующую иконку invoke SendMessage,[hwnddlg],WM_SETICON,ICON_BIG,eax; Обновить иконку окна jmp .processed
Код (ASM): ; GUI # include win64a.inc IDC_ICON1 equ 1 SAVE macro REG for R,<REG> push R endm endm .code WinMain proc local msg:MSG local hCursor:qword xor ebx,ebx lea rdi,ClassName lea rsi,WndProc invoke LoadCursorFromFileA,"Printer.ani" mov hCursor,rax invoke LoadIconA,IMAGE_BASE,IDC_ICON1 SAVE <rax,rdi,rbx,COLOR_WINDOWTEXT,hCursor,rax,IMAGE_BASE,rbx,rsi,(sizeof WNDCLASSEX)> invoke RegisterClassExA,rsp;в rsp адрес WNDCLASSEX SAVE <rbx,IMAGE_BASE,rbx,rbx,300,600,100,100> sub esp,20h invoke CreateWindowExA,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE ;Стили окна lea edi,msg @@: invoke GetMessageA,edi,NULL,0,0 invoke DispatchMessageA,edi jmp @b WinMain endp WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM mov hWnd,rcx for uMsg,<DESTROY,CREATE,TIMER> cmp edx,WM_&uMsg je wm&uMsg endm cmp edx,WM_DESTROY je wmDESTROY leave jmp NtdllDefWindowProc_ wmDESTROY:invoke RtlExitUserProcess,NULL wmCREATE:invoke SetTimer,hWnd,1,100,NULL jmp wmBYE wmTIMER:and hIcon,111b inc hIcon invoke LoadIconA,IMAGE_BASE,hIcon invoke SendMessageA,hWnd,WM_SETICON,ICON_BIG,rax wmBYE: leave retn WndProc endp .data ClassName db "Анимированные курсор и иконка",0 hIcon dq 1 end rc-файл Код (C): 1 ICON "icon1.ico" 2 ICON "icon2.ico" 3 ICON "icon3.ico" 4 ICON "icon4.ico" 5 ICON "icon5.ico" 6 ICON "icon6.ico" 7 ICON "icon7.ico" 8 ICON "icon8.ico" В аттаче ani-курсор, фазы для иконки с сайта Manhunter'a, asm/rc/exe-файлы
На базе кода от Mikl___ сделал ещё один вариант - свободный от "зашифрованных" макросов Mikl___ . Код (ASM): option casemap:none ; ========================================================== ; Собирается: ml64 + rc + link /ENTRY:WinMain ; Стек НЕ "дышит", как у VS2022 ; ========================================================== WM_CREATE equ 1 WM_DESTROY equ 2 WM_TIMER equ 0113h WM_SETICON equ 80h ICON_BIG equ 1 ICON_SMALL equ 0 WS_OVERLAPPEDWINDOW equ 000CF0000h WS_VISIBLE equ 10000000h IDC_ARROW equ 32512 IMAGE_CURSOR equ 2 COLOR_WINDOW equ 5 ; ---------- ПРОТОТИПЫ СТРУКТУР ---------- MY_POINT STRUCT pt_x DWORD ? pt_y DWORD ? MY_POINT ENDS MY_MSG STRUCT msg_hwnd QWORD ? msg_message DWORD ? msg_pad1 DWORD ? msg_wParam QWORD ? msg_lParam QWORD ? msg_time DWORD ? msg_pad2 DWORD ? msg_pt MY_POINT <> MY_MSG ENDS MY_WNDCLASSEXA STRUCT wc_cbSize DWORD ? wc_style DWORD ? wc_lpfnWndProc QWORD ? wc_cbClsExtra DWORD ? wc_cbWndExtra DWORD ? wc_hInstance QWORD ? wc_hIcon QWORD ? wc_hCursor QWORD ? wc_hbrBackground QWORD ? wc_lpszMenuName QWORD ? wc_lpszClassName QWORD ? wc_hIconSm QWORD ? MY_WNDCLASSEXA ENDS includelib user32.lib includelib kernel32.lib extern LoadIconA:PROC extern LoadCursorA:PROC extern LoadCursorFromFileA:PROC extern LoadImageA:PROC extern RegisterClassExA:PROC extern CreateWindowExA:PROC extern GetMessageA:PROC extern DispatchMessageA:PROC extern DefWindowProcA:PROC extern SendMessageA:PROC extern SetTimer:PROC extern GetModuleHandleA:PROC extern ExitProcess:PROC .data ClassName db 0C0h,0EDh,0E8h,0ECh,0E8h,0F0h,0EEh,0E2h,0E0h,0EDh,0EDh,0FBh,0E5h,20h,0EAh,0F3h,0F0h,0F1h,0EEh,0F0h,20h,0E8h,20h,0E8h,0EAh,0EEh,0EDh,0EAh,0E0h,0 szAniFile db "Printer.ani",0 g_IconId dq 1 g_hInstance dq 0 g_hCursor dq 0 g_Msg MY_MSG <> .code WndProc proc FRAME sub rsp, 38h .allocstack 38h .endprolog mov qword ptr [rsp+20h], rcx cmp edx, WM_DESTROY je _WmDestroy cmp edx, WM_CREATE je _WmCreate cmp edx, WM_TIMER je _WmTimer _WmDefault: mov rcx, qword ptr [rsp+20h] call DefWindowProcA jmp _WmExit _WmDestroy: xor ecx, ecx call ExitProcess _WmCreate: mov rcx, qword ptr [rsp+20h] mov edx, 1 mov r8d, 100 xor r9d, r9d call SetTimer xor eax, eax jmp _WmExit _WmTimer: mov rax, g_IconId and rax, 7 inc rax mov g_IconId, rax mov rcx, g_hInstance mov rdx, rax call LoadIconA test rax, rax jz _WmExit mov rcx, qword ptr [rsp+20h] mov edx, WM_SETICON mov r8d, ICON_BIG mov r9, rax call SendMessageA mov rcx, qword ptr [rsp+20h] mov edx, WM_SETICON mov r8d, ICON_SMALL mov r9, rax call SendMessageA xor eax, eax _WmExit: add rsp, 38h ret WndProc endp public WinMain WinMain proc FRAME ; Приводим в порядок стек : C8h = shadow 20h + args 40h + WNDCLASSEXA 50h + выравнивание 18h sub rsp, 0C8h .allocstack 0C8h .endprolog ; --- WinMain - стабильный RSP до конца --- xor ecx, ecx call GetModuleHandleA mov g_hInstance, rax mov rcx, rax mov rdx, 100 call LoadCursorA test rax, rax jnz _HaveCur mov rcx, g_hInstance mov rdx, 100 mov r8d, IMAGE_CURSOR xor r9d, r9d mov qword ptr [rsp+20h], 0 mov qword ptr [rsp+28h], 0 call LoadImageA test rax, rax jnz _HaveCur lea rcx, szAniFile call LoadCursorFromFileA _HaveCur: mov g_hCursor, rax test rax, rax jnz _LoadIcon xor ecx, ecx mov edx, IDC_ARROW call LoadCursorA mov g_hCursor, rax _LoadIcon: mov rcx, g_hInstance mov rdx, g_IconId call LoadIconA mov rbx, rax mov qword ptr [rsp+30h], rbx ; сохраним локально (вне перекрытия с будущими args) ; WNDCLASSEXA на rsp+40h (80 байт) -> занимает 40h..90h, не пересекается с shadow lea rcx, [rsp+40h] mov dword ptr [rcx+MY_WNDCLASSEXA.wc_cbSize], 80 mov dword ptr [rcx+MY_WNDCLASSEXA.wc_style], 0 lea rax, WndProc mov qword ptr [rcx+MY_WNDCLASSEXA.wc_lpfnWndProc], rax mov dword ptr [rcx+MY_WNDCLASSEXA.wc_cbClsExtra], 0 mov dword ptr [rcx+MY_WNDCLASSEXA.wc_cbWndExtra], 0 mov rax, g_hInstance mov qword ptr [rcx+MY_WNDCLASSEXA.wc_hInstance], rax mov qword ptr [rcx+MY_WNDCLASSEXA.wc_hIcon], rbx mov rax, g_hCursor mov qword ptr [rcx+MY_WNDCLASSEXA.wc_hCursor], rax mov qword ptr [rcx+MY_WNDCLASSEXA.wc_hbrBackground], COLOR_WINDOW+1 mov qword ptr [rcx+MY_WNDCLASSEXA.wc_lpszMenuName], 0 lea rax, ClassName mov qword ptr [rcx+MY_WNDCLASSEXA.wc_lpszClassName], rax mov qword ptr [rcx+MY_WNDCLASSEXA.wc_hIconSm], rbx call RegisterClassExA ; CreateWindowExA использует тот же rsp, без доп. sub xor ecx, ecx lea rdx, ClassName lea r8, ClassName mov r9d, WS_OVERLAPPEDWINDOW or WS_VISIBLE mov dword ptr [rsp+20h], 100 mov dword ptr [rsp+28h], 100 mov dword ptr [rsp+30h], 600 mov dword ptr [rsp+38h], 300 mov qword ptr [rsp+40h], 0 mov qword ptr [rsp+48h], 0 mov rax, g_hInstance mov qword ptr [rsp+50h], rax mov qword ptr [rsp+58h], 0 call CreateWindowExA _MsgLoop: lea rcx, g_Msg xor edx, edx xor r8d, r8d xor r9d, r9d call GetMessageA test eax, eax jz _MsgExit lea rcx, g_Msg call DispatchMessageA jmp _MsgLoop _MsgExit: xor ecx, ecx call ExitProcess ; сюда не доходим, но для FRAME нужно восстановление add rsp, 0C8h ret WinMain endp public start start: jmp WinMain end P.S. Все ресурсы вшиты в EXE-файл.
Согласен, но также иногда, удлиняют понимание работы этого исходника. Чем шире выбор - тем лучше (кашу маслом не испортишь).
Ага, а потом, у Инди на компьютере запустить, чтобы выяснить сколько матерных выражений он помнит наизусть.
Это ядерный эквивалент: Код (Text): ZwTerminateProcess(NtCurrentProcess() Если прокинуть фаулт в юзер не получается, значит поврежден стек и задача неисполнима.