Вторник, 26.11.2024, 23:06
Приветствую Вас Гость | RSS
Главная | | Регистрация | Вход
Меню сайта
Форма входа
Поиск
Календарь
«  Февраль 2012  »
ПнВтСрЧтПтСбВс
  12345
6789101112
13141516171819
20212223242526
272829
Архив записей
Наш опрос
Оцените мой сайт
Всего ответов: 20
Мини-чат
Друзья сайта
  • Заказ Художник
  • Рыбалка
  • Новости
  • Наш университет io-96
  • Железо и другие
  • Наш Counter-Strike
  • Google ot Turbokherson
  • Google ot Turbokherson87
  • Android
  • Программы и Софт
  • Программирование
  • Лечение здоровья
  • О Ремонт компьютер
  • Наш сайт Turbokherson
  • Программирование ICQ 378204653 от Turbokherson
    Главная » 2012 » Февраль » 21 » Расширение функциональности готовых программ
    11:37
    Расширение функциональности готовых программ
    В мире ПО существует огромное количество программ, забытых своими разработчиками. Хорошо, когда уже есть хорошая альтернатива. А если ее нет? В программе может катастрофически не хватать каких-то мелочей, некоторые досадные ошибки могут годами доставлять массу неудобств пользователям, а на новых версиях ОС программа и вовсе может отказаться работать. Далеко не всегда имеются исходные коды, чтобы привести программу в порядок. Если программа простая — не составит труда за короткий срок создать альтернативу. Но если программа большая и сложная, что же делать в таком случае? Не всегда рационально тратить время и деньги на разработку полного аналога, ведь расширить в разумных рамках функциональность и исправить большинство ошибок можно уже в готовом исполняемом файле.
    В этой статье будут продемонстрированы методики модификации исполняемых файлов на примере расширения функциональности легендарной игры Age of Empires II (стратегия реального времени).

    Итак, что же мы имеем:
    • игра вышла в 1999 году и имеет статус abandonware — то есть она более не выставляется на продажу производителем и не приносит прибыли
    • последнее обновление выпущено в 2000 году, доработка не производится
    • игра является хорошим примером сложного ПО, полный аналог которого практически невозможно создать заново
    • имеются досадные ошибки, которые не были исправлены разработчиками
    Пожалуй, можно попробовать что-то изменить, не глядя на запрет в лицензионном соглашении. Вы так не думаете? :)

    Отказ от ответственности


    Работа проведена исключительно в образовательных целях, и ни в коем случае не является призывом к нарушению действующего законодательства. Автор не несёт никакой ответственности за незаконное использование представленных материалов.

    Сведения о «пациенте»


    Исследуемая игра написана на движке Genie, разработкой которого с 1997 до 2000 года занималась компания Ensemble Studios. За это время на данном движке было выпущено 4 игры из серии Age of Empires. Далее Genie был лицензирован компании LucasArts, которая в 2001-2002 годах выпустила еще 2 игры на этом движке, но уже из серии Star Wars. К этому времени движок уже сильно устарел, поскольку работал в режиме 256 цветов, что очень скромно по сравнению с играми даже 2000 года. Именно поэтому доработка движка была остановлена, новых игр на его основе не выпускалось.
    Игровой движок Genie загружает всю игровую графику, музыку, а главное — списки юнитов и их характеристики — из внешних файлов. То есть все игры на базе этого движка по большей части имеют очень похожие исполняемые файлы, и практически все модификации для одной игры без особых проблем портируются на другие.

    Чего мы хотим добиться?


    1. Отключить проверку наличия CD
    В современных ноутбуках (нетбуках и т.д.) уже не редкость, когда нет встроенного CD привода. Более того, поскольку игра очень стара, оригинальный CD может перестать читаться, а кто-то и вовсе мог потерять его. В итоге владельцы вполне легальной копии игры теряют возможность запускать игру. Так что, пожалуй, разрешим игре запускаться без диска.

    2. Добавить поддержку оконного режима
    Age of Empires поддерживает 3 фиксированных разрешения: 800×600, 1024×768 и 1280×1024. С такими разрешениями картинка на широкоформатных мониторах выглядит растянутой, что доставляет дискомфорт. Добавить поддержку новых разрешений в игру можно, но так как в игре для каждого разрешения используются своя графика интерфейса, оставим это на потом, и ограничимся пока что добавлением оконного режима работы в игру.

    3. Добавить поддержку конфигурационных файлов
    Для включения каких-то системных игровых опций, их приходится передавать через командную строку при каждом запуске программы. Можно поместить все параметры в ярлык, но после перемещения игры в другой каталог ярлык перестанет работать. Выход из ситуации — добавление поддержки конфигурационных файлов.

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

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

    Что нам для этого понадобится?


    IDA Pro — интерактивный дизассемблер и отладчик, широко используется для реверс-инжиниринга. Умеет строить понятные даже новичкам блок-схемы. Вся его сила проявляется в интерактивном взаимодействии с пользователем. После автоматического анализа пользователь может давать функциям и переменным осмысленные имена, комментировать код и т.д. Достаточно небольших познаниях в области ассемблера для того, чтобы начать заниматься исследованием программ прямо сейчас!

    Flat Assembler — свободно распространяемый многопроходной ассемблер. FASM обладает небольшими размерами и очень высокой скоростью компиляции, имеет богатый и ёмкий макро-синтаксис, позволяющий автоматизировать множество рутинных задач. Одна из полезных возможностей — генерация чистого машинного кода без заголовков и т.д. Все это пригодится нам для генерации ассемблерных вставок.

    Hexplorer — бесплатный HEX-редактор. Имеет простой дизассемблер x86, что позволяет удобнее ориентироваться внутри исполняемого файла. Используется непосредственно для ручной модификации двоичного файла без изменения его размера.

    Итак, начнем.

    Отключение проверки наличия CD


    Найти код, который отвечает за проверку наличия CD, нам поможет IDA. Это оказалось очень просто сделать. Достаточно проследить вызовы функций GetDriveTypeA и GetVolumeInformationA, которые используются для получения информации о приводе, как мы обнаружим функцию проверки наличия CD по адресу 4485A0h (базовый адрес 400000h, то есть физически в файле машинный код находится по адресу 485A0h), которая при удачной проверке возвращает в eax единицу.
    CheckCD proc near
     sub esp, 214h
     push ebx
     push esi
     ; ...
     ; множество проверок
     ; ...
     test eax, eax
     jz cd_check_fail
    cd_check_ok:
     pop esi
     mov al, 1
     pop ebx
     add esp, 214h
     retn 4
    cd_check_fail:
     pop esi
     pop ebx
     add esp, 214h
     retn 4
    CheckCD endp
    
    При этом стоит заметить, что эта функция запрашивает значение CDPath из реестра, где хранится буква диска, с которого была установлена игра. То есть игра привязывается конкретно к той букве диска, с которого она была установлена, и при наличии оригинального диска в другом приводе игра все равно не запустится. Наиболее простое решение в данном случае — это заменить команду jz cd_check_fail, которая занимает 2 байта, на 2 операции nop, чтобы после проверки диска в eax всегда возвращалась единица. Однако, функция устроена таким образом, что при отсутсвии значения CDPath в реестре, функция сразу возвращает 0, что мешает переносимости программы. Поэтому всю функцию мы заменим на 3 простые команды:
    use32
     xor eax, eax
     inc eax
     retn 4
    Для получения машинных кодов этих команд нам пригодится FASM.
    fasm src.asm dst.bin
    Основной плюс этого ассемблера для нас сейчас — это то, что он позволяет генерировать чистый машинный код без заголовков. Получаем (по строчке на команду):
    31 C0
    40
    C2 04 00
    
    То есть нам нужно поместить эти 6 байт в начало функции определения наличия CD, а остальные команды заменить на nop (код 90h).
    Исходный машинный код Полученный машинный код
    8B 44 24 04 81 EC 14 02
    00 00 85 C0 53 56 8B D9
    ...
    33 С0 40 С2 04 00 90 90
    90 90 90 90 90 90 90 90
    ...
    В обычном HEX редакторе заменяем начиная с смещения 485A0h указанные выше машинные коды, после чего игра больше никогда не требует для запуска CD, даже если в реестре нет необходимого ключа. При этом мы освободили 288 байт для дополнительного машинного кода, что пригодится нам в дальнейшем.

    Исправление ошибки


    При подключении к сетевой игре по IP игра выводит список IP и названий игр, к которым игрок подключался ранее. Однако, по каким-то причинам IP адрес часто сохранялся не полностью, обрезалось несколько его последних символов. Необходимо разобраться в чем дело и исправить ошибку.
    Займемся поиском кода, отвечающего за сохранение IP адресов последних игр в реестре. Нам известно, что игра сохраняет список последних IP игр в ключах с именами вида RecentGameName%d и RecentGameAddr%d. При помощи IDA находим по 3 ссылки на эти строки. Определить где нужный код очень просто — в коде со смещением 0512116h происходит запись этих ключей, в остальных случаях — считывание. Не будем приводить найденный участок машинного кода — там около 50 инструкций, общий смысл которых можно представить в виде маленького фрагмента кода на C.
    for(int i = 0; i < count; i++)
    {
     char* keyname;
     sprintf(keyname, "RecentGameName%d", i);
     RegSetVal(1, keyname, gamename[i], strlen(gamename[i]));
     sprintf(keyname, "RecentGameAddr%d", i);
     RegSetVal(1, keyname, gameaddr[i], strlen(gamename[i]));
    }
    Для установки значения в реестре игрой используется собственная функция-обертка RegSetVal(int, char* ValueName, char* Data, int DataSize) — название функции и параметров конечно же придуманы, в машинном коде их нет. Как видно, игра сохраняет в реестре ровно столько символов IP адреса, сколько их в названии сетевой игры. Судя по всему, программист скопировал 2 строчки сохранения названия игры для сохранения адреса, но при этом забыл исправить название массива в вызове strlen.
    Для исправления ошибки у нас есть 2 варианта: подставить в strlen правильную строку или вместо strlen помещать константу 16 (максимальная длина строки адреса, например «255.255.255.255»).
    Как же выглядит вызов strlen(gamename[i]) в машинном коде?
    call _sprintf ; +0x1022CF
    mov edi, ebp
    or ecx, 0FFFFFFFFh
    xor eax, eax
    add esp, 0Ch
    repne scasb
    not ecx
    push ecx
    Компилятор оптимизировал код и заменил вызов функции strlen() вычислением длины на месте. В предыдущем вызове strlen() использовалась та же строка, в вот опять оптимизация — указатель помещается в ebp один раз при первом вызове. То есть у нас нет драгоценных пары байт для того, чтобы поместить правильный указатель в ebp. Можно, конечно, сделать jmp за пределы функции, потом обратно — но это не самый красивый метод. Поэтому остановимся пока на самом простом варианте исправления ошибки — будем всегда сохранять 16 байт адреса. Для этого заменим приведенный выше машинный код на следующий:
    Ассемблерный код Машинный код
    call _sprintf ; +0x1022CF
    add esp, 0Ch
    mov ecx, 0Fh
    E8 CF 22 10 00
    83 С4 0С
    B9 0F 00 00 00

    Поддержка файла конфигурации


    Игра поддерживает множество полезных параметров командной строки, которые было бы удобно сохранить где-нибудь в файле, чтобы не передавать их каждый раз при запуске. Лучший способ реализовать подобное — это заставить игру при каждом старте загружать внешнюю библиотеку и выполнять функцию инициализации. При такой организации можно добавить множество полезных вещей в конфигурационные файлы, такие как: загрузка дополнительных библиотек (модулей) без необходимости модификации исполняемого файла, перехват функций работы с реестром для организации хранения всех игровых данных в конфигурационном файле игры, а не в реестре. Не лишним будет поддержка множества профилей с различными наборами настроек с возможностью быстрого их переключения. Итак, приступим.
    Наиболее удобным местом подключения внешней библиотеки оказалась функция загрузки библиотеки ebueulax.dll, отвечающей за отображение лицензионного соглашения. Она как раз вызывается практически сразу после начала работы WinMain, однако помимо загрузки библиотеки ebueulax.dll и выполнения функции EBUEula она делает много лишних действий, поэтому мы полностью перепишем ее, чтобы она загружала библиотеку config.dll и выполняла библиотечную функцию LoadConfig с указателем на расположение командной строки в памяти (для её изменения) в качестве параметра.
    Ассемблерный код Машинный код
    LoadConfigLib proc near
     sub esp, 208h
     push esi
     push edi
     push ebx
     push ecx
     push edx
     mov esi, ds:LoadLibraryA
     push 67B8C4h; lpLibFileName
     call esi ; LoadLibraryA
     mov ebx, eax
     test ebx, ebx
     jz short loc_587443
     push offset aLoadconfig
     push ebx ; hModule
     call ds:GetProcAddress
     mov edi, eax
     test edi, edi
     jz short loc_58743C
     mov eax, [esp+220h];**args
     push eax
     call edi
     jmp short loc_587443
    loc_58743C:
     push ebx ; hLibModule
     call ds:FreeLibrary
    loc_587443:
     pop edx
     pop ecx
     pop ebx
     pop edi
     pop esi
     add esp, 208h
     retn
    LoadConfigLib endp
    81 EC 08 02 00 00
    56
    57
    53
    51
    52
    8B 35 E0 51 63 00
    68 C4 B8 67 00
    FF D6
    8B D8
    85 DB
    74 25
    68 B8 B8 67 00
    53
    FF 15 C8 50 63 00
    8B F8
    85 FF
    74 0C
    8B 84 24 20 02 00 00
    50
    FF D7
    EB 07
    
    53
    FF 15 20 51 63 00
    
    5A
    59
    5B
    5F
    5E
    81 C4 08 02 00 00
    C3
    Приведенный код делает несколько вызовов библиотечных функций. Их адреса были подставлены вручную в машинный код. В следующей статье (если вы не будете против :) ) я постараюсь рассказать как при помощи FASM можно генерировать код, полностью готовый к вставке в исполняемый файл.
    Заменяем исходную функцию (начинается со смещения 187400h) новой при помощи обычного HEX-редактора. Поскольку новая функция значительно короче исходной, не забываем заменить оставшиеся байты кодом операции nop — 90h. Мы получили еще 192 байта свободных байта для дополнительного машинного кода. Это место можно будет использовать в дальнейшем.
    Исходный машинный код Полученный машинный код
    81 EC 08 02 00 00 53 56 
    8B 35 E0 51 63 00 57 68
    CC B8 67 00 FF D6 8B D8 
    85 DB 75 0A 5F 5E 5B 81
    C4 08 02 00 00 C3 68 C4 
    B8 67 00 53 FF 15 C8 50
    63 00 8B F8 85 FF 75 13 
    53 FF 15 20 51 63 00 5F
    5E 33 C0 5B 81 C4 08 02 
    00 00 C3 8B 84 24 18 02
    00 00 50 FF D6 8B F0 85
    ...
    81 EC 08 02 00 00 56 57
    53 51 52 8B 35 E0 51 63
    00 68 C4 B8 67 00 FF D6 
    8B D8 85 DB 74 25 68 B8
    B8 67 00 53 FF 15 C8 50 
    63 00 8B F8 85 FF 74 0C
    8B 84 24 20 02 00 00 50 
    FF D7 EB 07 53 FF 15 20
    51 63 00 5A 59 5B 5F 5E 
    81 C4 08 02 00 00 C3 90
    90 90 90 90 90 90 90 90 
    ...
    Всю остальную работу берет на себя модуль config.dll. Его можно написать на C/C++/Delphi. Его задача проста — принимать от исполняемого файла указатель на командную строку приложения и в зависимости от настроек в конфигурационном файле изменять ее. Как бонус — нам пригодится возможность подгружать дополнительные модули, настраивая это в конфигурационном файле. Для этого нам нужно просто вызывать WinAPI функцию LoadLibrary именем файла модуля, которое необходимо загрузить в адресное пространство игры.

    Поддержка оконного режима


    Поддержка оконного режима организована в виде отдельного модуля wndmode.dll, загрузкой которого занимается config.dll. Этот модуль занимается тем, что перехватывает все вызовы DirectDraw, изменяет параметры таким образом, чтобы программа работала в окне, и только после этого передаёт управление оригинальным функциям DirectDraw.

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

    Сам по себе модуль wndmode.dll является сильно модифицированной версией библиотеки d3dhook.dll, где реализовано:
    • Полная независимость от программы D3D Windower
    • Настройки загружаются из секции [WINDOWMODE] файла wndmode.ini
    • Настройки по умолчанию заменены для совместимости с Genie
    • Добавлен параметр Border, который включает/выключает рамку вокруг окна
    • Если игровое разрешение равно системному, автоматически убирается рамка
    Теперь эта библиотека готова внедриться практически в любую DirectDraw игру для того, чтобы сделать ее оконной. Так что если кто-то будет заниматься модификацией других игр — это пригодится.

    Что получилось?


    Работа по модификации проделана огромная, здесь мы рассмотрели лишь некоторые изменения, поскольку описание даже небольших изменений требует огромных усилий. Остальные модификации при желании вы сможете исследовать в IDA, скачав готовый модифицированный файл.
    В целом удалось добиться таких результатов:
    • Вместо ebueulax.dll (отображение лицензии) загружается config.dll (поддержка конфигурационных файлов)
    • Оконный режим работы (модуль wndmode.dll — модифицированный d3dhook.dll)
    • Код функций работы с курсором адаптирован для работы в оконном режиме (решает много неприятных проблем, вызванных тем, что игра не рассчитана на работу в окне)
    • Игра корректно работает без ее предварительной установки (достаточно скопировать файлы игры), для игры не нужен CD
    • Исправлена ошибка оригинальной игры, когда при игре по IP в списке последних игр обрезались IP адреса
    • Изменен порядок и расположение кнопок в меню «Один игрок» и «Редактор», исправлена ошибка с выделением кнопок в меню «Один игрок» (переписан внушительный участок кода)
    • Исполняемый файл может запускаться из любого подкаталога, а не только из age2_x1 или корня (практически полностью переписано начало процедуры WinMain)
    • Нет проверки наличия файла empires2.exe (его можно удалить)
    • При включенном параметре MIDIMUSIC после сворачивания мелодия начинается заново (не сбрасываются инструменты)
    Все изменения работают стабильно, никаких сбоев не обнаружено. В оконном режиме осталась пока что нерешенной проблема отображения системного курсора одновременно с игровым после того, как он покинет область игрового окна и вернется назад. Однако, это вызвано лишь только тем, что игровой движок не рассчитан на запуск в окне и не предусматривает вариант, когда курсор покинет игровое окно. В ближайшей версии модификации это будет исправлено.
    Логическим завершением развития данной модификации может стать внедрение в игру поддержки игровых комнат с созданием соответсвующего игрового сервера, поскольку официальные игровые комнаты Age of Empires на Zone были закрыты ещё несколько лет назад. Всё это вполне возможно реализовать, не имея при этом на руках никаких исходных кодов.

    Заключение


    Если вам интересно попробовать в действии то, что у меня получилось, можете скачать Age of Empires II: The Conquerors (Lite Edition) (всего 94МБ) с моего сайта.
    Простите, что здесь написано обо всем и сразу, и не сильно подробно. Я старался описать общую схему. В следующих статьях можно рассматривать каждый этап модификации в отдельности, давать какие-то советы и т.д.
    Просмотров: 845 | Добавил: Turbokherson | Рейтинг: 0.0/0
    Всего комментариев: 0
    Имя *:
    Email *:
    Код *:
    Создать бесплатный сайт с uCozCopyright MyCorp © 2024