Blog. Just Blog

Замена главной иконки исполняемого файла

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Замена главной иконки исполняемого файла
Замена главной иконки исполняемого файла

Продолжаем тему иконок. Сегодня разберем способ, как можно заменить главную иконку приложения на другую. Зачем это надо? Например, может возникнуть потребность заменить иконку в неком исполняемом файле на иконку документа Word или PDF ;) Или надо перенести в свежесобранный патч иконку из обрабатываемой им софтины. Или, как в моем случае, вам не нравится оригинальная иконка какого-нибудь приложения и вы хотите поменять ее на что-нибудь свое. Применение этой технологии вы определите для себя сами. Как говорила Масяня в одноименном мультике: "Мое дело подарок подарить, а ты уж думай, что с этой хренью делать".

Исходное положение. Есть файл с иконкой или мультииконкой и есть целевой исполняемый файл с одной или несколькими иконками в ресурсах. Задача заключается в переносе иконок из файла в ресурсы.

Для работы потребуются две структуры, описывающие внутренности группы иконок в ресурсах и в файле с иконками. FASM про них, естественно, ничего не знает.
  1. ; Структуры для работы с иконками в группе
  2. struct ICONDIR
  3.     idReserved dw ? ; Зарезервировано (должно быть 0)
  4.     idType     dw ? ; Тип ресурса (1 для иконок)
  5.     idCount    dw ? ; Количество изображений в иконке
  6. ends
  7.  
  8. struct GRPICONDIRENTRY
  9.     bWidth        db ? ; Ширина изображения в пикселах
  10.     bHeight       db ? ; Высота изображения в пикселах
  11.     bColorCount   db ? ; Количество цветов
  12.     bReserved     db ? ; Зарезервировано
  13.     wPlanes       dw ? ; Зарезервировано
  14.     wBitCount     dw ? ; Зарезервировано
  15.     dwBytesInRes  dd ? ; Размер данных изображения
  16.     nID           dw ? ; ID иконки в ресурсах
  17. ends
Следующий блок - константы и переменные из сегмента данных. Они прокомментированы, но использование подробно будет описано в соответствующих блоках кода.
  1. ; Максимально допустимое количество иконок в группе
  2. MAX_ICONS = 128
  3.  
  4. ; Целевой файл
  5. szTarget  db 'change_target.exe',0
  6. ; Файл с иконками
  7. szIcon    db 'change_source.ico',0
  8.  
  9. pMem      dd ? ; Память для файла с иконками
  10. dFIconNum dd ? ; Количество иконок в файле
  11.  
  12. hTarget   dd ? ; Хэндл загруженного целевого файла
  13. hResource dd ? ; Хэндл обновляемых ресурсов целевого файла
  14. hGroup    dd ? ; Хэндл и память для группы иконок
  15. pGroup    dd ?
  16. dGIconNum dd ? ; Количество иконок в группе
  17. dID       dd ? ; ID обновляемой иконки
  18.  
  19. dDiscard  dd ? ; Флаг применения сделанных изменений
  20.  
  21. ; Массив с ID иконок в группе
  22. IconsID   rw MAX_ICONS
  23. iid_len   = $-IconsID
  24.  
  25. ; Заготовка для RT_GROUP_ICON
  26. res_head ICONDIR
  27. res_data rb (sizeof.GRPICONDIRENTRY*MAX_ICONS)
  28. res_len  = $-res_head
Начнем с загрузки файла иконки в память и простенькой проверки на корректность данных внутри этого файла. Подразумевается, что файл на диске есть и доступен для чтения.
  1.         ; Загрузить в память файл иконки
  2.         invoke  CreateFile,szIcon,GENERIC_READ,0,0,OPEN_EXISTING,0,0
  3.         cmp     eax,-1
  4.         je      loc_exit
  5.         mov     ebx,eax
  6.         invoke  GetFileSize,ebx,NULL
  7.         mov     esi,eax
  8.         invoke  GlobalAlloc,GMEM_FIXED,eax
  9.         or      eax,eax
  10.         jz      loc_exit
  11.         mov     [pMem],eax
  12.         invoke  _lread,ebx,eax,esi
  13.         invoke  CloseHandle,ebx
  14.  
  15.         ; Проверить корректность файла с иконками
  16.         mov     esi,[pMem]
  17.         cmp     word [esi+ICONDIR.idReserved],0
  18.         jne     loc_free_mem
  19.         cmp     word [esi+ICONDIR.idType],1
  20.         jne     loc_free_mem
  21.         ; Получить количество иконок в файле
  22.         movzx   eax,word [esi+ICONDIR.idCount]
  23.         or      eax,eax
  24.         jz      loc_free_mem
  25.         ; Установленное нами максимальное количество иконок в файле
  26.         cmp     eax,MAX_ICONS
  27.         ja      loc_free_mem
  28.         ; Сохранить количество иконок
  29.         mov     [dFIconNum],eax
  30.         ...
  31.         ...
  32.         ...
  33. loc_free_mem:
  34.         ; Очистить память файла иконки
  35.         invoke  GlobalFree,[pMem]
Внутренний формат файла с иконками подробно описан в официальной документации. Быстрая проверка заключается в том, что значение idType в структуре ICONDIR должно быть 1, idReserved всегда 0, а количество иконок в файле idCount должно быть ненулевым. Теоретически, количество иконок может быть ограничено только максимальным значением WORD, то есть 65535. Официально установленной или рекомендованной верхней границы по количеству иконок я нигде в документации не нашел. Но, так как у нас будет резервироваться память на основании этого значения, для внутреннего использования примем максимальное значение в 128 иконок. На практике я ни разу не встречал мультииконки с таким количеством изображений, так что этого значения должно хватить с лихвой. Соответственно, значение idCount будет сравниваться еще и с установленной верхней границей. При невыполнении любого из этих условий файл иконки признается негодным для работы. Конечно, можно еще дополнительно заморочиться и проверить корректность всех структур ICONDIRENTRY, следующих за заголовком, но в рамках статьи я этого делать не буду. Вы же, для обеспечения максимальной отказоустойчивости ваших приложений, имейте это в виду.

Переходим к обработке целевого файла. Его надо загрузить в память функцией LoadLibrary и параллельно с этим подготовить ресурсы для редактирования при помощи функции BeginUpdateResource. Если замена иконок пройдет без ошибок, то останется только выгрузить файл из памяти, а изменения в ресурсах зафиксировать функцией EndUpdateResource.
  1.         ; Сохранять изменения в ресурсах
  2.         mov     [dDiscard],FALSE
  3.  
  4.         ; Загрузить целевой файл в память
  5.         invoke  LoadLibrary,szTarget
  6.         or      eax,eax
  7.         jz      loc_free_mem
  8.  
  9.         ; Хэндл загруженного целевого файла
  10.         mov     [hTarget],eax
  11.  
  12.         ; Начать обновление ресурсов
  13.         invoke  BeginUpdateResource,szTarget,FALSE
  14.         or      eax,eax
  15.         jz      loc_unload
  16.  
  17.         ; Хэндл обновляемых ресурсов целевого файла
  18.         mov     [hResource],eax
  19.  
  20.         ; Найти и обработать первую группу иконок в ресурсах
  21.         invoke  EnumResourceNames,[hTarget],RT_GROUP_ICON,\
  22.                 EnumResNameProc,NULL
  23.  
  24.         ; Выгрузить файл из памяти
  25.         invoke  FreeLibrary,[hTarget]
  26.  
  27.         ; Применить изменения в ресурсах, но только если не было ошибок
  28.         invoke  EndUpdateResource,[hResource],[dDiscard]
  29.         jmp     loc_free_mem
  30.  
  31. loc_unload:
  32.         ; Выгрузить файл из памяти
  33.         invoke  FreeLibrary,[hTarget]
При кажущейся простоте работы с ресурсами, приходится учитывать несколько важных моментов. Надо обрабатывать первый по счету ресурс типа RT_GROUP_ICON, но просто так по какому-то фиксированному имени его не загрузить, так как ресурс может иметь как цифровой индекс, так и строковый. Поэтому придется воспользоваться функцией перебора всех ресурсов нужного типа EnumResourceNames.
  1. ;---------------------------------------------------
  2. ; Поиск первой группы иконок
  3. ;---------------------------------------------------
  4. proc EnumResNameProc hModule:DWORD, lpszType:DWORD, lpszName:DWORD, lParam:DWORD
  5.         pusha
  6.  
  7.         ; Обновить данные для всех языков
  8.         invoke  EnumResourceLanguages,[hModule],[lpszType],[lpszName],\
  9.                 EnumResLangProc,NULL
  10.         or      eax,eax
  11.         jnz     @f
  12.  
  13.         ; Языков не найдено, возможно, битый ресурс
  14.         mov     [dDiscard],TRUE
  15. @@:
  16.         popa
  17.         ; Обрабатываем только первую группу иконок
  18.         mov     eax,FALSE
  19.         ret
  20. endp
В этой маленькой callback-функции тоже не все так просто. Ресурсы в исполняемых файлах могут быть с привязкой к какому-либо одному языку, а также разделены по нескольким языкам. Теоретически, для каждого языка в файле может быть своя группа иконок, да еще и количество иконок в каждой языковой группе может отличаться. Другой вопрос, что на практике для главных иконок обычно создается не более одной группы, но при любом раскладе все равно придется задействовать функцию EnumResourceLanguages и перебрать поочередно все языки, так как используемый язык ресурсов мы пока не знаем. Если ни одного языка не найдено, значит ресурсы повреждены и модифицировать файл нельзя.

Вся основная работа с ресурсами выполняется в callback-функции перебора языков. Код подробно прокомментирован, но я еще поясню все происходящее словами.
  1. ;---------------------------------------------------
  2. ; Обработка всех языков для этой группы иконок
  3. ;---------------------------------------------------
  4. proc EnumResLangProc hModule:DWORD, lpszType:DWORD, lpszName:DWORD,\
  5.         wIDLanguage:DWORD, lParam:DWORD
  6.  
  7.         pusha
  8.  
  9.         ; Очистить память для новой группы
  10.         invoke  RtlZeroMemory,res_head,res_len
  11.         invoke  RtlZeroMemory,IconsID,iid_len
  12.  
  13.         ; Найти группу иконок в ресурсах
  14.         invoke  FindResourceEx,[hModule],[lpszType],[lpszName],[wIDLanguage]
  15.         or      eax,eax
  16.         jnz     @f
  17.  
  18.         ; Группа не найдена, файл не обновлять
  19.         mov     [dDiscard],TRUE
  20.         jmp     .loc_next
  21. @@:
  22.         mov     [hGroup],eax
  23.  
  24.         ; Загрузить группу иконок
  25.         invoke  LoadResource,[hModule],[hGroup]
  26.         or      eax,eax
  27.         jnz     @f
  28.  
  29.         ; Загрузить не удалось, файл не обновлять
  30.         mov     [dDiscard],TRUE
  31.         jmp     .loc_next
  32. @@:
  33.         ; Получить указатель на начало данных в памяти
  34.         invoke  LockResource,eax
  35.         mov     [pGroup],eax
  36.  
  37.         ; Проверить корректность группы
  38.         mov     esi,[pGroup]
  39.         cmp     word [esi+ICONDIR.idType],1
  40.         je      @f
  41.         ; Тип ресурса не соответствует группе иконок
  42.         mov     [dDiscard],TRUE
  43.         jmp     .loc_next
  44. @@:
  45.         ; Получить количество иконок в группе
  46.         movzx   eax,word [esi+ICONDIR.idCount]
  47.         or      eax,eax
  48.         jnz     @f
  49.         ; Количество иконок нулевое
  50.         mov     [dDiscard],TRUE
  51.         jmp     .loc_next
  52. @@:
  53.         cmp     eax,MAX_ICONS
  54.         jbe     @f
  55.         ; Количество иконок превышает максимально допустимое
  56.         mov     [dDiscard],TRUE
  57.         jmp     .loc_next
  58. @@:
  59.         mov     [dGIconNum],eax
  60.  
  61.         ; Сохранить ID отдельных иконок из группы
  62.         add     esi,sizeof.ICONDIR
  63.         mov     edi,IconsID
  64.         mov     ecx,[dGIconNum]
  65. @@:
  66.         add     esi,12
  67.         movsw
  68.         loop    @b
  69.  
  70.         ; Скопировать заголовок иконок из файла в группу
  71.         mov     esi,[pMem]
  72.         mov     edi,res_head
  73.         mov     ecx,sizeof.ICONDIR
  74.         rep     movsb
  75.  
  76.         xor     ebx,ebx
  77. .process_icon:
  78.         ; Скопировать параметры иконок из файла в группу
  79.         ; bWidth, bHeight, bColorCount, bReserved
  80.         movsd
  81.         ; wPlanes, wBitCount
  82.         movsd
  83.         ; dwBytesInRes
  84.         lodsd
  85.         ; Размер данных иконки
  86.         mov     edx,eax
  87.         stosd
  88.  
  89.         ; ID иконки из списка ресурсов
  90.         movzx   ecx,word [IconsID+ebx*2]
  91.         or      ecx,ecx
  92.         jnz     .id_ok
  93.  
  94.         ; В ресурсах иконки закончились, но в файле еще есть
  95.         push    edx
  96.         ; Найти первый свободный ID для новой иконки
  97.         mov     ecx,[dID]
  98. .scan_res:
  99.         inc     ecx
  100.         push    ecx
  101.         invoke  FindResourceEx,[hModule],RT_ICON,ecx,[wIDLanguage]
  102.         pop     ecx
  103.         or      eax,eax
  104.         jnz     .scan_res
  105.         pop     edx
  106. .id_ok:
  107.         ; Записать ID иконки в группу
  108.         mov     eax,ecx
  109.         mov     [dID],eax
  110.         stosw
  111.  
  112.         ; Смещение данных относительно начала файла иконки
  113.         lodsd
  114.         add     eax,[pMem]
  115.         ; Обновить ресурсы
  116.         invoke  UpdateResource,[hResource],RT_ICON,ecx,\
  117.                 [wIDLanguage],eax,edx
  118.         or      eax,eax
  119.         jnz     @f
  120.         ; Заменить иконку не удалось
  121.         mov     [dDiscard],TRUE
  122.         jmp     .loc_next
  123. @@:
  124.         ; Следующая иконка из файла
  125.         inc     ebx
  126.         cmp     ebx,[dFIconNum]
  127.         jb      .process_icon
  128.  
  129.         ; Удалить все оставшиеся в группе иконки
  130. .delete_icons:
  131.         movzx   eax,[IconsID+ebx*2]
  132.         or      eax,eax
  133.         jz      .delete_icons_done
  134.         invoke  UpdateResource,[hResource],RT_ICON,eax,[wIDLanguage],0,0
  135.         or      eax,eax
  136.         jnz     @f
  137.         ; Удалить иконку не удалось
  138.         mov     [dDiscard],TRUE
  139.         jmp     .loc_next
  140. @@:
  141.         inc     ebx
  142.         jmp     .delete_icons
  143. .delete_icons_done:
  144.  
  145.         ; Вычислить размер группы иконок
  146.         mov     eax,[dFIconNum]
  147.         mov     ecx,sizeof.GRPICONDIRENTRY
  148.         mul     ecx
  149.         add     eax,sizeof.ICONDIR
  150.  
  151.         ; Обновить группу иконок в файле
  152.         invoke  UpdateResource,[hResource],RT_GROUP_ICON,[lpszName],\
  153.                 [wIDLanguage],res_head,eax
  154.         or      eax,eax
  155.         jnz     .loc_next
  156.         ; Обновить группу иконок не удалось
  157.         mov     [dDiscard],TRUE
  158. .loc_next:
  159.         popa
  160.         mov     eax,TRUE
  161.         ; Если есть хоть одна ошибка, то больше ничего не обрабатываем
  162.         cmp     [dDiscard],TRUE
  163.         jne     @f
  164.         mov     eax,FALSE
  165. @@:
  166.         ret
  167. endp
Сперва в память загружается описатель группы иконок и проверяется на соответствие формату. Ситуация, когда тип отличается, маловероятная, но тем не менее возможная. Затем из него в отдельный массив переносятся все идентификаторы отдельных иконок, которые находятся в этой группе. Следующим шагом заполняется структура новой группы иконок на основании загруженного файла с иконками. При этом, с помощью функции UpdateResource иконки в ресурсах последовательно заменяются на иконки из файла. В отличие от традиционных редакторов ресурсов типа eXeScope и Restorator, не требуется, чтобы размер новой иконки в точности соответствовал размеру иконки в ресурсах. Если количество иконок в ресурсах совпадает с количеством иконок в файле, то они просто будут заменены на новые. Но может возникнуть ситуация, когда в ресурсах иконки уже закончились, а в файле еще нет. В этом случае вычисляется первый не занятый ID иконки и в ресурсы добавляется новая иконка. Кроме того, может возникнуть противоположная ситуация, когда в файле больше нет иконок, а в ресурсах еще остались. В этом случае все лишние иконки будут удалены из ресурсов. Вся прелесть работы с функцией UpdateResource в том, нам не надо задумываться о таких сложных вещах, как изменение размера ресурсов при модификации, добавлении или удалении иконок, всю грязную работу система выполняет самостоятельно. Когда обработка иконок будет завершена, в ресурсах надо будет обновить группу иконок, заменив ее на созданную группу. При любой ошибке взводится флаг, что ресурсы в исполняемом файле обновлять не надо, и дальнейшая обработка прекращается. Если ошибок нет, то сделанные изменения фиксируются при помощи функции EndUpdateResource.

В приложении пример программы с исходным текстом, которая заменяет иконку в ресурсах другого исполняемого файла на иконку из файла.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Change.Icon.Demo.zip (12,199 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 2025 | Комментариев: 2

Метки: Assembler, иконки
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (26.04.2018 в 10:44):
Не проблема, нарисую статью и про извлечение иконок из файлов.
Сергей Озеров (26.04.2018 в 00:06):
Спасибо за Ваш блог и Ваши уроки ! Вы упомянули извлечение значка из файла для патчера, можете поделиться как это написать на fasm?

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0055 сек.) / Память: 4.5 Mb
Наверх