Русификация программ, помощь и обучение

Topaz Star Effects

(Изменение размеров элементов управления в приложениях Qt)

Введение

Довольно часто при локализации программы или приложения приходится подгонять размеры элементов управления и их положение под переведенный текст. Это легко сделать в шаблонном редакторе при помощи визуального редактора форм и диалогов, если приложение и программа написана, например, на Delphi, или содержит внедренные ресурсы. Но как изменить свойства элементов, если видимых ресурсов нет, а перевод приложения доступен только в жестко-закодированных строках. Шаблонники, как и простейшие редакторы ресурсов типа Resource Hacker, Restorator и др., будут бессильны. В таких случаях помочь может только отладчик, например, OllyDdb, Interactive Disassembler (IDA), x64dbg и др.

В этой статье, на примере плагина Topaz Star Effects для графического редактора Photoshop, мы рассмотрим способы нахождения проблемных элементов управления и научимся корректировать их размеры. Для решения подобных задач вам потребуется справочная документация по библиотеке Qt. Умение и навыки работы с отладчиком, а также знание и понимание ассемблерных инструкций.


Плагин Topaz Star Effects

Мы будем работать не с файлом плагина, а с полноценной программой, которая запускается посредством хост-файла *.8bf через приложение Photoshop. Исполняемый файл Topaz Star Effects написан на языке Visual C++ c использованием кроссплатформенной библиотеки Qt (см. рис. 1):

Анализ исполняемого файла программы
Рисунок 1

Кстати вы легко можете определить, что выбранная вами программа для локализации написана с её использованием. Если в каталоге установки находятся файлы DLL с приставкой 'Qt', например, QtCore4.dll, QtGui4.dll и т.д., значит используется библиотека Qt. Все строки для перевода в таких приложениях являются жестко-закодированными, а из видимых ресурсов для редактирования будет доступен только значок и манифест (см. рис. 2):

Обрезание строк
Рисунок 2

Ну что же, строки вы перевели, запустили приложение и.... Вот незадача, некоторые строки не помещаются в границы элементов управления и обрезаются (см. рис. 3).

Ресурсы программы
Рисунок 3

Есть несколько вариантов, как поступить в таких случаях. Вариант первый: так и оставить, наш пользователь не привередлив, ему по-барабану что и как переведено, главное русские буковки в интерфейсе. Вариант второй: подобрать другие слова, близкие по смыслу и короче по длине. И, наконец, третий вариант: изменить размеры элементов управления с проблемными строками. Последний вариант очень сложный, но поверьте, на такую работу очень приятно смотреть, приложение выглядит законченным, а работа с ним не напрягает обрезанными строками.

Предлагаю вам ознакомиться в третьим вариантом более подробно. Возьмем 64-бит версию плагина — файл tlstareffects_x64.exe. Из инструментов нам понадобятся отладчик (x64dbg), калькулятор Windows (хотя можно использовать калькулятор, встроенный в отладчик), графический редактор (Paint.Net). Не забываем также про шаблонный редактор Radialix в связке с дизассемблером IDA.

Правка размеров

Итак, чтобы найти в отладчике определенный элемент управления для возможности его изменения, необходимо определить его текущие размеры в окне приложения. Для обычных приложений с этим отлично справляется утилита WinSpy++, к примеру, посмотрите руководство, "Правка "невидимых" элементов управления". Но в данном случае она нам не поможет. Вот такая коварная библиотека Qt. Не беда, у нас есть графический редактор. Сделайте скриншот области окна приложения с проблемным элементов и откройте его в редакторе. Сделайте выделение элемента управления по его границе и посмотрите размеры выделенной области. В редакторе Paint.Net они указываются в пикселях (см. рис. 4).

Выделение области элемента управления
Рисунок 4

Размеры кнопки 72х22 пикселя. Первое значение - это ширина (W), а второе - высота (H). Чтобы надпись полностью поместилась в элементе управления, необходимо увеличить его ширину, т.е. значение 72 сделать больше. Поэтому преобразуем это значение в шестнадцатеричный формат. С этим отлично справляется обычный калькулятор Windows в режиме программирования. Введите значение в десятичном формате, а потом переключите в шестнадцатеричный. Значению 72 будет соответствовать шестнадцатеричное число 48h (см. рис. 5):

Преобразование чисел в калькуляторе Windows
Рисунок 5

Аналогично переводим значение 22 в шестнадцатеричный формат - получаем 16h. Оно необходимо нам для ориентировки в коде программы. Обычно размеры элементов управления находятся рядом, а значений 48h может быть множество. Теперь поищем значения 48h в отладчике. Загружаем файл tlstareffects_x64.exe. Убедитесь что вы находитесь в модуле программы, в секции кода (в заголовке отладчика будет указано имя файла tlstareffects_x64.exe). Итак, перед вами дизассемблированный код программы (см. рис. 6).

Файл программы загружен в отладчик
Рисунок 6

Попробуем найти константу 48, которая определяет ширину проблемного элемента управления. Для этого в окне отладчика нажмите правую кнопку мышки и выберите в контекстном меню команду "Search for -> Constant" (см. рис. 7):

Поиск констант
Рисунок 7

Откроется окошко ввода значения константы. Здесь можно вводить значение в шестнадцатеричном формате (поле "Expression"), а можно в десятичном (поле "Signed"). При вводе в одно из полей значение автоматически преобразуется в другие форматы. Поэтому калькулятором Windows можно и не пользоваться. В общем вводим значение, которое мы хотим найти и нажимаем кнопку [OK] (см. рис. 8):

Ввод константы
Рисунок 8

Отладчик произведет поиск и предоставит нам список найденных инструкций, в которых используется значение 48h (см. рис. 9):

Список констант
Рисунок 9

Ох, список огромен. Но его придется исследовать и найти единственно верную инструкцию. В первую очередь необходимо обратить внимание на инструкции PUSH. Если среди них нет адреса, по которому идет формирование элемента управления, переходим к инструкциям MOV. Причем в инструкциях MOV заслуживают внимания именно значения, а не значение смещения. Например, инструкция следующего вида

mov dword ptr ds:[rax+18],12 — записывает значение 12h в память по адресу [rax+18].

А вот инструкция вида

mov edx,dword ptr ss:[rsp+12] — записывает значение из памяти [rsp+12] со значением смещения 12h в регистр edx. Конечно, без знаний ассемблера исследовать код программы бесполезно. Если анализ инструкций MOV не дал положительного результата, переходим к инструкциям SUB (вычитание) и ADD (сложение). Ведь размеры и координаты элементов управления могут быть получены косвенным путем, а не заданы напрямую. На инструкции CMP, CALL не обращайте внимание, они не участвуют в инициализации размеров и координат элементов управления.

Еще раз вам напомню, что обычно, если в коде идет инициализация одного размера элемента, например высоты, то рядом будет идти инструкция инициализации другого размера. Это же касается и координат положения элемента управления. Поэтому когда будете просматривать код программы по месту искомых констант, то на адресах с одиночными значениями не останавливайтесь. Естественно могут быть исключения, но подобные случаи требуют индивидуального исследования.

Итак посмотрим, что у нас есть. Инструкций PUSH нет (воспользуйтесь поиском по списку). Плохо. Это был бы самый простой вариант. Переходим к инструкциям MOV. Как было сказано ранее, нас интересует в первую очередь значение, а не величина смещения. Поэтому отфильтруем список при помощи регулярного выражения, чтобы не запутаться в списке. Установим отметку поиска регулярных выражений (RegEx), а в поле поиска введем регулярное выражение вида mov([^\n\r]*),48 (см. рис. 10):

Фильтрация списка
Рисунок 10

Вот теперь другое дело. Пробежитесь по адресам и просмотрите код программы. Вы увидите, что в области адреса 000000013FB5A001 как раз то и выполняется инициализация проблемного элемента управления (см. рис. 11):

Размеры и координаты элемента
Рисунок 11

Видите, как все просто. По адресу 000000013FB59FF3, инструкцией mov dword ptr ds:[rax+10],14, задается значение координаты Х левого верхнего угла элемента управления от границы родительского элемента. Значение 14h в десятичном формате — 20 пикселей. По адресу 000000013FB59FFA, инструкцией mov dword ptr ds:[rax+14],10, задается значение координаты Y левого верхнего угла элемента управления от границы родительского элемента. Значение 10h в десятичном формате 16 пикселей. По адресу 000000013FB5A001, вы уже знаете, значение ширины (W) элемента управления — 72 пикселя. И, наконец, по адресу 000000013FB5A008, инструкцией mov dword ptr ds:[rax+1C],16, задается значение высоты (H) элемента управления. Помните, мы в начале преобразовывали значения размеров выделенной области элемента управления в шестнадцатеричный формат, так вот оно и послужило ориентиром определения нужного участка кода. Также, как видите, все значения задаются в одном месте. 

Чтобы убедиться, что найден нужный участок кода, изменяем значение 48h на, к примеру, 50h (в десятичном формате — 80). Наверняка этого будет достаточно, чтобы надпись на элементе поместилась полностью. Устанавливаем выделение строки с адресом 000000013FB5A001, нажимаем клавишу [Пробел], изменяем в инструкции значение 48 на 50 (см. рис. 12):

Модификация инструкции
Рисунок 12

Нажимаем [OK]. У нас получится вот так (см. рис. 13):

Изменили значения инструкций
Рисунок 13

Теперь запускаем программу на выполнением и смотрим, что у нас получилось (см. рис. 14). Примечание, т.к. плагин не работает самостоятельно, то изменения нужно сохранить в файл и запустить его через поддерживаемое приложение.

Изменили ширину элемента управления
Рисунок 14

Отлично! Надпись помещается полностью. Также обратите внимание, что автоматически изменилась ширина элемента управления для кнопки с надписью "Удалить". Это означает, что для её размеров в программе используются те же значения, что и для кнопки с надписью "Добавить". Ну что же, это хорошо, нам меньше работы.

Переходим ко второму проблемному элементу управления. Также с помощью графического редактора выделяем на скриншоте окна область элемента и смотрим размеры (см. рис. 15).

Определение размеров элемента
Рисунок 15

Имеем: ширина 42 пикселя, в шестнадцатеричном формате 2Ah, и высота 21 пиксел - 15h. Переходим в отладчик и задаем поиск константы 2Ah. Получим небольшой список (см. рис. 16):

Список найденных инструкций, согласно условию поиска
Рисунок 16

Сразу обращает на себя внимание инструкция mov edx,2A по адресу 000000013FB5F48B. Переходим на этот адрес в коде программы и смотрим. Рядом находится другое значение - 15h (инструкция mov edx,15 по адресу 000000013FB5F47D) (см. рис. 17).

Размеры элемента
Рисунок 17

Скорее всего здесь формируются размеры нашего второго проблемного элемента. Проверяем. Изменим значение 2A на 36 (в десятичном формате — 54 пикселя) (см. рис. 18).

Правка значения инструкции
Рисунок 18

Подтверждаем изменение инструкции и у нас теперь будет вот так (см. рис. 19):

Изменили значение в инструкции
Рисунок 19

Сохраним изменения в файл, запускаем и смотрим (см. рис. 20):

Изменили ширину кнопки
Рисунок 20

Хорошо! Все как доктор прописал. Идем дальше.

У нас осталась группа кнопок с надписями "Сохранить", "Удалить", "Импорт" и "Экспорт". Вы уже знаете, что нужно сделать? Правильно, определить их размеры. Так вот, размеры кнопок будут следующие 56х22 пикселя. В шестнадцатеричном формате соответственно 38h (W) и 16h (H). Переходим в отладчик и задаем поиск константы 38. Ого, список внушительный. Но при внимательном разборе и исследованию тех немногих инструкций, которые удовлетворяют условиям поиска, нет ничего похожего на формирование элемента управления. Также рядом нигде не мелькает значение 16h, что однозначно бы указывало нам искомые области формирования элементов управления. Разобраться в данном вопросе поможет справочная документацию Qt. Изложение всего этого материала выходит за рамки этой статьи. Могу вам лишь порекомендовать его для самостоятельного изучения. Не вдаваясь в теорию, скажу вам, что размеры элемента управления могут устанавливаться  при помощи функции QWidget::setMaximumSize. Перед вызовом функции обязательно идет обращение к значениям с размерами. Но знание о самой функции еще не дает нам информацию о конкретном элементе управления. Какие еще отличительные особенности имеет проблемный элемент? Значок. На кнопке помимо надписи есть значок. Если вы внимательно посмотрите на список строк в проекте Radialix, то чуть выше строк с надписями на кнопках увидите строки с путями к значкам (см. рис. 21):

Жестко-закодированные строки (Radialix)
Рисунок 21

Не трудно догадаться, что кнопке с надписью "Save" (Сохранить) соответствует значок, который находится по пути :/resources/presetAdd.png (внутренние ресурсы приложения), а кнопке с надписью "Delete" (Удалить) — значок :/resources/presetDelete.png и т.д. Посмотрим в отладчике IDA на код программы в районе адреса с вызовом строки со значком для кнопки "Save". Сделайте двойной щелчок левой кнопки мышки по адресу ссылки напротив строки ":/resources/presetAdd.png". В открывшемся окошке снова сделайте двойной щелчок по ячейке с адресом (см. рис. 22):

Переход из Radialix в IDA
Рисунок 22

Переключитесь на окно отладчика. Курсор буде установлен на адресе обращения к строке с путем к значку для кнопки "Save" (на рис. 23 указано стрелкой красного цвета). Прокрутите код выше, пока не встретите функцию установки размеров элемента — QWidget::setMaximumSize. Вот мы и нашли размеры кнопки с надписью "Save" (см. рис. 23):

Код программы в отладчике IDA
Рисунок 23

В коде видно, что для элемента управления значение ширины устанавливается равным 3Ah, что в десятичном формате равняется 58 пикселям. Почему у нас получилось 56? Дело в том, что у библиотеки Qt есть компоненты выравнивания элементов. Например, компонент QHBoxLayout с одноименной функцией, который выполняет выравнивание элементов относительно родительского элемента. Такие компоненты помогают организовать симметричное размещение элементов в определенной области, независимо от её размеров. В данном случае, для нашей программы, такие компоненты применяются. Если в окне программы вы потяните за сплиттер в сторону рабочей области (см. рис. 24), то увидите, что кнопки с надписями "Сохранить", "Удалить", "Импорт" и "Экспорт" равномерно размещаются в отведенной для них области.

Изменение размеров панели
Рисунок 24

Вот немного увеличили размер левой панели (см. рис. 25). Хорошо видно, что элементы кнопок симметрично разместились по ширине панели. А вот, например, ширина элемента списка осталась без изменений. Это явное упущение разработчика.

Изменили размер панели
Рисунок 25

Но суть в другом. Вспомните, какой функцией устанавливается размер элементов кнопок? Посмотрите еще раз в листинг кода — это функция QWidget::setMaximumSize. Наличие этой функции предполагает, что минимальный размер элемента может быть каким угодно. Поэтому при использовании функции выравнивания автоматика пытается уменьшить размер элементов относительно размера родительского элемента. Вот у неё получается сжать ширину кнопок до величины 56 пикселей. Больше этого не дают сделать вложенные в элемент кнопки другие элементы: надпись и значок. Вот в таких случаях искать элементы по видимому размеру в интерфейсе программы будет бесполезно.

С шириной разобрались, а где же высота? Её значение берется из памяти [rdx-22h] (см. рис. 23), инструкция lea r8d, [rdx-22h], куда значение высоты было помещено где-то ранее. Это мы рассматриваем 64-бит версию плагина, а, к примеру, в 32-бит версии этого же плагина значение высоты указано рядом со значением ширины. Вот такие могут быть отличия для различных разрядностей в одном и том же приложении. 

Аналогично обстоит дело с формированием размеров для остальных элементов. Вы может увидеть их, если просмотрите листинг кода вниз. Осталось только проверить наши предположения на практике. Переходим к отладчику x64dbg и задаем поиск константы 3Ah (см. рис. 26). На что обращать внимание вы уже знаете, поэтому, надеюсь, сразу определитесь с адресами в коде.

Список иструкций: константа 3Ah
Рисунок 26

Изменяем по каждому из этих адресов значение 3Ah на новое. Я поступил следующим образом: надпись "Сохранить" самая длинная, соответственно под неё нужно сделать элемент с несколько большим значением ширины, чем у остальных элементов. Поэтому для этого элемента установил значение 51h (81 пикс.), а для остальных 44h (68 пикс.), 40h (64 пикс.) и 40h (64 пикс.) соответственно. Сохраняем изменения в файл и проверяем приложение в работе (см. рис. 27).

Исправленные размеры кнопок
Рисунок 27

Супер! Теперь можно еще немного увеличить размер элемента со списком пресетов, чтобы он гармонично выглядел вместе с кнопками. Исходная ширина элемента 240 пикселей (F0h), а новая должна быть примерно 271 пикселя (110h) (см. рис. 28):

Определение новых размеров элемента
Рисунок 28

То что элемент списка не увеличил свою ширину автоматически при изменении ширины родительского компонента, говорит о том, что у него фиксированное значение. В приложения Qt неизменное значение ширины устанавливается при помощи функции QWidget::setFixedWidth. На неё также можно ориентироваться в коде программы при поиске нужного элемента управления. Замечу, что чуть ранее мы изменяли ширину кнопки на этой же панели, и которая находится выше элемента со списком. По идее его размеры должны быть где-то рядом относительно размеров кнопки по адресу 000000013FB5F48B. Зададим поиск константы F0h, не забывайте фильтровать строки при помощи регулярных выражений (см. рис. 29):

Список инструкций: константа F0h
Рисунок 29

Выполнить анализ кода необходимо по всем найденным адресам. Но в первую очередь нужно глянуть адреса, которые отмечены на рисунке 29. Они находятся рядом с адресом 000000013FB5F48B, по которому мы уже изменяли инструкцию для элемента на этой же панели. Посмотрим на первый адрес 000000013FB5F3C1, с инструкцией mov edx,F0 (см. рис. 30).

Исследование кода
Рисунок 30

Обратите внимание, следом идет вызов функции QWidget::setFixedWidth, поэтому не откладывая на потом, сразу изменяем значение F0h на 118h (см. рис. 31):

Изменение значения ширины элемента
Рисунок 31

Получится вот так:

Изменили значение ширины элемента
Рисунок 32

Запускаем плагин на выполнение и смотрим что получилось (см. рис. 33):

Окно приложения - результат правки размера
Рисунок 33

Здорово! Другой адрес нет смысла проверять, но для интереса можете посмотреть, что там (как и по остальным, согласно списка). Небольшая подсказка: панель слева имеет свое зеркальное отражение справа.

Теперь рассмотрим тяжелый случай. После окончательного перевода программы на русский язык, было принято решение увеличить размер выпадающего списка со строками. Перевод оказался несколько длиннее оригинала и строки в списке не помещаются полностью.

Интерфейс программы: элемент списка
Рисунок 34

Замер размеров элемента показал значения 105х20 пикселей, что в шестнадцатеричном формате соответствует значениям 69h и 14h. Поиск по константе 69h ничего не дал. По константе 14h искать нет смысла. Какие еще есть варианты? Мы знаем, что такой тип элемента управления (Выпадающий список) называется "ComboBox". Одной из особенностей приложений созданных с использование библиотеки Qt является то, то если используется какой-то элемент управления, то в жестко-закодированных строках будет на него отсылка, будь-то название элемента, или функция с именем элемента и т.д. В общем, если вы знаете названия элементов управления (Label, Edit, Memo, List, и т.д.), то можно выйти на нужный элемент по его имени. И еще, если вы внимательно изучите интерфейс приложения, то заметите, что элементов "ComboBox" всего два: в настройках параметров эффекта, который мы собрались изменить, и в диалоге сохранения пресета. Зададим поиск строки "ComboBox" в проекте Radialix (без учета регистра). Также откройте файл плагина в отладчике IDA, чтобы можно было переходить по адресам из проекта Radialix. Будет найдено 4 вхождения. Смотрим первое из них (рис. 35):

Radialix: найденная строка combobox
Рисунок 35

У строки есть ссылка 0x1400481B7, поэтому переходим по ней в дизассемблер и отладчик IDA. Анализируем код программы вверх и вниз по листингу от адреса с обращением к строке "combobox". Применяем свои знания по ассемблеру и функциям библиотеки Qt. Если смотреть листинг кода вверх, то там идет добавления слоя для размещения элементов. Поэтому посмотрим вниз и, вот здесь, мы увидим явные признаки создания нашего элемента (см. рис. 36).

IDA: анализ кода
Рисунок 36

По адресу 00000001400484D4 идет обращение к функции QComboBox::setEditable, которая устанавливает свойство для элемента "ComboBox" (это очевидно, т.к. в названии функции есть имя элемента), а ниже, по адресу 000000014004850B вызывается функция QWidget::setFixedWidth установки фиксированного значения ширины. Вполне возможно, что здесь устанавливается ширина для нашего проблемного элемента. Непосредственно перед вызовом функции QWidget::setFixedWidth, по адресам 00000001400484F0 - 0000000140048501, идет вычисление значения ширины, которое будет установлено для элемента.

Инструкция mov r11, [rsp+9C8h+arg_0] считывает из заданной области памяти в регистр определенный адрес. Затем инструкцией mov eax, [r11+50h] в регистр EAX считывается определенное значение относительно полученного ранее адреса. После этого значение в регистре EAX инструкцией cdq преобразуется из двойного слова в учетверенное. Далее инструкцией sub eax, edx выполняется операция вычитания значения в регистре EDX из значения в регистре EAX. Результат арифметического действия помещается в регистр EAX. Потом инструкцией sar eax, 1 выполняется целочисленное деление значения в регистре EAX на 2. Наконец инструкцией mov edx, eax полученное значение в регистре EAX помещается в регистр EDX.

Если запустить программу под отладчиком x64dbg и посмотреть на результаты выполнения этих инструкций в пошаговом режиме, то можно увидитеть, что берется значение D2h, в десятичном формате 210, и делится на 2. Получается 69h, в десятичном формате это 105. Если вы измеряли размер элемента, то знаете, что он имеет ширину как раз 105 пикселей (см. рис. 37).

Окно приложения: определение размеров элемента
Рисунок 37

Тут же в графическом редакторе можно прикинуть примерную величину новой ширины элемента "ComboBox". У меня получилось 160 пикселей, в шестнадцатеричном формате A0h. Теперь осталось только сделать так, чтобы на вход функции QWidget::setFixedWidth поступало наше значение. Это можно сделать при помощи инструкции MOV EAX, A0. Таким образом, модифицируем код следующим образом (см. рис. 38):

Анализ кода
Рисунок 38

Вот что получится после модификации кода:

Модификация кода
Рисунок 39

Сохраните сделанные изменения в файл. Для этого в отладчике есть команда "Patch" в меню "View", горячие клавиши [Ctrl+P] (см. рис. 40).

Патчинг файла
Рисунок 40

Откроется окно патча. В списке слева (1) указан файл, на базе которого будет создан новый файл. В списке справа (2) перечислены сделанные в коде изменения, соответственно адрес и байты, которые будут внесены в новый файл. Здесь можно выбрать, записывать ли все изменения или только те, которые отмечены (см. рис. 41).

Окно патча файла
Рисунок 41

Как видите, сейчас в списке патча перечислены все сделанные нами изменения в коде программы. Нажмите кнопку "Patch File" (3). Откроется обычный диалог сохранения файла. Укажите каталог сохранения и имя файла. После этого проверяем модифицированный файл в работе. Вот как у нас получился выпадающий список (см. рис. 42):

Окно программы: изменена ширина элементанструкции
Рисунок 42

Теперь полный порядок. Локализованный файл можно отправлять в печать.



3. Заключение

Подведем итог. Чтобы найти в программах Qt элементы управления, которые мы хотели бы изменить, можно действовать несколькими способами:

1) Осуществлять поиск по видимым размерам элементов управления в интерфейсе программы;

2) Выполнять поиск элементов согласно принятым для них в среде программирования именам (Label, Edit, Memo, List, CheckBox и т.д.);

3) Выполнять ориентировочный поиск по функциям, которые используются в Qt. Большинство функций создания интерфейса Qt имеют характерные имена, которые указывают на выполняемое ими действие (QWidget::setMaximumSize — установка максимальных размеров, QWidget::setFixedWidth установка фиксированной ширины, и т.д.). С другими очевидными функциями вы можете ознакомиться в справочной документации.

В некоторых случаях описание интерфейсной части программы может быть включено в исполняемый файл в качестве внутреннего ресурса. Тогда необходимые правки элементов управления можно сделать прямо в Radialix'e, в секции жестко-закодированных строк. Ориентируйтесь по стандартным именам элементов управления.

Всего наилучшего.




Специально для сайта http://www.wylek.ru/
24.05.2015
Leserg