Программист С++, студент
0,1
рейтинг
21 декабря 2015 в 12:47

Разработка → Как мы распаковку игры автоматизировали из песочницы

Мое хобби, помимо программирования — разработка модификаций под игру S.T.A.L.K.E.R. Работаем мы в команде, где, как и принято, каждый отвечает за что-то свое. Я, помимо того, что вхожу в круг разработчиков, еще и осуществляю разработку ПО для команды. Под катом читайте, как мы автоматизировали распаковку игровых архивов, с какими проблемами столкнулись и как их решили.

Ресурсы игры запакованы в архивы. Оценить сколько у нас архивов в текущем билде вы можете ниже:



У разработчиков все эти архивы есть в распакованном виде, а для тесторов мы выпускаем куммулятивные обновления, распространяющиеся единым архивом.

Помимо этого, существует еще и разные наборы изменений из 4-х нижних архивов. Иногда возникает необходимость распаковать их все. У нас есть разные инструменты для упаковки и распаковки, я знаю, как минимум, 2 набора – консольная и GUI версия. У каждой из них свои недостатки:

Консольная:
1. На распаковке архивов более ~ 1.6 ГБ падает.

GUI:
1. Выбор начальной папки, архива, конечной директории – производится руками.
2. возможна только распаковка 1 архива за раз.

Поскольку у нас есть архивы и почти по 2ГБ, то приходится использовать GUI. Когда мне надоело по сто раз делать одно и тоже, я решил его автоматизировать.

Распаковщик имеет вот такой интерфейс:







Это все диалоговые окна. Меня навело на мысль поле, в котором отображается имя файла. Если его можно вписать туда руками или выбрать, то это же можно повторить и программно.

Мы вооружимся C++ WinAPI и SPY++ и пока будем работать над первым окном. Запустим распаковщик и SPY++, найдем там его процесс:



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

Еще при первом обдумывании идеи у меня возникла мысль сделать удобный конфигурационный файл. Структура его была придумала сразу и так и не менялась:

Распаковщик.exe – пусть до GUI распаковщика
C:\S.T.A.L.K.E.R\ – пусть до игры
D:\Stalker SHоC\1\ – пусть до папки, в которую надо распаковать
YES – получить список архивов рекурсивно (NO) или из списка ниже
gamedata.db1 – список с именами архивов
/gamedata.db2 – закомментированная строчка

Я не буду подробно останавливаться на том, как считывается конфиг. Скажу лишь то, что у нас есть структура с аналогичными полями. Для того, чтобы нажимать на кнопочки в другом окне нам нужно получить его handle. Далее мы должны получить handlы нужных нам элементов управления. Причем, получать их именно в той последовательности, в которой они связаны (то, как они связаны видно по раскрывающимся спискам у элементов в SPY++. Посмотрим:



Получать handlы мы это будем следующим кодом:

HWND hwnd = FindWindow(NULL, "Select file to unpack...");
HWND hbnd = FindWindowEx(hwnd, NULL, "Button", "&Открыть");
HWND hсnd = FindWindowEx(hwnd, NULL, "ComboBoxEx32", "");
hсnd = FindWindowEx(hсnd, NULL, "ComboBox", NULL);
hсnd = FindWindowEx(hсnd, NULL, "Edit", NULL);
#ifdef _DEBUG 
BOOST_LOG_TRIVIAL(info) << "FindWindow(NULL, 'Select file to unpack...') " << hwnd;
BOOST_LOG_TRIVIAL(info) << "FindWindow(NULL, 'Button', '&Открыть') " << hbnd;
BOOST_LOG_TRIVIAL(info) << "FindWindow(NULL, 'Edit', '') " << hсnd;
#endif

Функция FindWindow(Ex) — возвращает handle по имени объекта. Вторым параметром может принимать значение класса объекта, а первым можно передать тот объект, в котором искать (например, мы передаем handle окна для поиска кнопки).

После того, как мы получили данные, нам нужно послать сообщение элементу управления. Делать мы будем это так:

if ((hwnd != NULL && hbnd != NULL ) && hсnd != NULL)
 {
  //устанавливаем текст
  SendMessage(hсnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR(path_to_db.c_str())));
  //кликаем
  SendMessage(hbnd, WM_LBUTTONDOWN, 0, 0);
  SendMessage(hbnd, WM_LBUTTONUP, 0, 0);
  #ifdef _DEBUG
  BOOST_LOG_TRIVIAL(info) << "Sended";
  #endif
}

Здесь я остановлюсь подробнее, так как возникли трудности при заполнении ComboBoxа. Изначально код был немного другим, и я получал только:

HWND hсnd = FindWindowEx(hwnd, NULL, "ComboBoxEx32", "");

И пытался заполнить его используя:

SendMessage(hсnd, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR(path_to_db.c_str())));

Но ничего не получалось. Почему – я не знаю до сих пор. Еще одна проблема была в том, что меня смутил принцип работы этого окна. На скриншоте выше видно, что когда мы выбираем архив то в ComboBox попадает только его название. Путь нигде не фигурирует. Надеясь на чудо, я передал туда полный путь до архива и все заработало. Чудеса есть? Думаю, нажатие на кнопку Ок очевидно, и мы его рассматривать не будем.

Теперь пришло время работы со вторым окном выбора папок. Сразу приведу код и потом прокомментирую.

//работа со 2м окном
Sleep(time);
hwnd = FindWindow(NULL, "Обзор папок");
hbnd = FindWindowEx(hwnd, NULL, "Button", "ОК");
hсnd = FindWindowEx(hwnd, NULL, "Edit", NULL);
if ((hwnd != NULL && hbnd != NULL) && hсnd != NULL)
{
//устанавливаем текст
SendMessage(hсnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR(config.path_to_output.c_str())));
//кликаем
SendMessage(hbnd, WM_LBUTTONDOWN, 0, 0);
SendMessage(hbnd, WM_LBUTTONUP, 0, 0);
#ifdef _DEBUG
BOOST_LOG_TRIVIAL(info) << "Sended";
#endif
}

С этим окном тоже появились проблемы. На нем нет никаких видимых элементов управления, кроме кнопок. Открываем SPY++ и смотрим что у нас внутри:



Я стал выбирать разные папки, и имя директории заносилось в элемент управления Edit, опять-таки, только имя (там что, обработчик клика мыши их в строку складывает?). И я совсем отчаялся, потому что не мог представить, как же мне свой путь туда передать. Первая мысль была запустить все это под отладчиком и найти тот адрес памяти, где лежит конечный путь, инжектить в процесс свою dll и менять значение в памяти (а DEP даст это сделать?).

Эта идея провалилась, потому что я не умею работать с отладчиком и никогда такое не делал, а учиться надо начинать с более простого. Снова надеясь на чудо, я передал в Edit полный путь — и все заработало! Радости не было придела. Далее было рутинное дописывание кода, с которым вы можете ознакомиться тут. Мы получили удобный и универсальный инструмент для распаковки. Я думаю, что мое решение проблемы не единственное и буду рад, если в комментариях расскажут что-то полезное. Спасибо за внимание.
Никита Аншаков @nanshakov
карма
6,0
рейтинг 0,1
Программист С++, студент
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (16)

  • +1
    inqsoft sign 0f misery позволял программировать это кликами мышкой. Не знаю работает ли он на современных версиях Windows, но раньше спасал кучу времени на подобных задачах.
  • +14
    Как-то кривенько. Не лучше ли было найти/реализовать алгоритм распаковки самостоятельно и к нему уже написать обвес в виде GUI/CLI?
    • +1
      Нет, конечно.
      Надстройка над GUI делается за полчаса.
      А искать(и уж тем-более реверсить) и реализовывать алгоритм распаковки/запаковки — значительно более долгая задача.
      Ну и ключевой вопрос: А зачем?
      Ради чего городить забор, если можно быстро и с минимумом затрат реализовать простое и эффективное решение?
    • +1
      Это есть в планах. Просто команде нужен был инструмент и быстро. А что именно кривенько?
  • +2
    Попробуйте autohotkey
  • 0
    А файлик Пароль.txt точно должен быть в репозитории? :)
    • 0
      Да, у меня возникли проблемы с выгрозкой кода в репозиторий, это пароль от Debug — сборка.7z. Как видно из хаоса в репозитории, я только начинаю разбираться с гитом.
  • +11
    Так у этой GUI-утилиты есть параметры командной строки…
    Распаковщик.exe <gamedata.db?> [output-directory]
    • +1
      Чувствую себя дураком (. А как вы это нашли?
      • 0
        Вообще гуглом нашел архив. А внутри был Readme.txt…

        А если бы не нашел, то рассматривал вариант, который предложил Prototik. Мы же это чисто теоретически да, в научных интересах? :o)
        1) распаковал бы exe утилиты или игры, если необходимо. Утилита упакована обычным upx — тут даже навыки никакие не нужны, существует миллион распаковщиков (например, встроенный в PE Explorer).
        2) посмотрел бы в IDA (или в другом инструменте), что там и куда вызывается.

        Спасибо Ida Pro и Hex-Rays

        Чтобы поправить псевдокод Hex-Rays до вида на картинке потребовалось пара минут. И стало видно, что readme не врет.

        А дальше можно разобрать формат архивов (unpack_procedure на картинке).
        • 0
          IDA стоит денег и больших. Далеко не у каждого разработчика она есть. Тем более, что далеко не каждому разработчику она нужна.
        • +1
          Спасибо, я попробую повторить шаги.
  • +1
    Полученный опыт это здорово, но с практической точки зрения на AutoIt это делается в три строчки (если бы утилита не принимала параметры, как уже заметили). Посмотрите, может еще пригодится.
    • 0
      Посмотрю, спасибо.
  • 0
    По поводу того, почему не работало сообщение CB_ADDSTRING, а сообщение WM_SETTEXT срабатывало. Я думаю это внутренний механизм Винды здесь выходит на сцену. Когда сообщение шлется эдит контролу, и оно содержит в себе указатель на память из другого процесса (программа автоматизатор щлет строку распоковщику), то винда сериализует буфер строки и в таргет процессе уже использует адрес буфера, валидный в контексте таргет процесса. На сколько я помню, это было сделано для обратной совместимости со всякими Win95. В случае с CB_ADDSTRING и ComboBoxEx32, возможно, этот механизм не работает.
    • 0
      Как не явно — то. Спасибо, не знал о таком.

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