Ремонтируем музыку в старой игре

Недавно игрался в забавную игру-головоломку на iPad под названием The Incredible Machine, игра понравилась, поэтому решил поискать что-то аналогичное на PC. Обнаружилось, что эта игра является переделкой старой серии игр. Окинув взглядом серию, решил скачать The Incredible Machine 3 под Windows, обладающую довольно сносной графикой на мой вкус.

tim3

Отличная игра, но обнаружился небольшой негативный момент, состоящий в том, что в качестве саундтрека выступали MIDI-файлы, несмотря на наличие качественных композиций в CD-версии игры (согласно Wiki). Неприятность была списана на недосмотр со стороны разработчиков, разместивших игру на GOG.com (откуда она и была взята изначально), однако качественный саундтрек с CD-версии всё-таки поставлялся в виде набора MP3-файлов, но без очевидной возможности интеграции его в игру. Я решил исправить это досадное упущение и реализовать костыль, позволяющий играть в игру и наслаждаться качественным звуком.

Для начала нам необходимо провести небольшое исследование игры, чтобы определить, где хранится информация о текущем проигрываемом треке, чтобы потом реализовать тривиальную программу, которая будет считывать её и воспроизводить необходимый MP3-файл.
Начнем с наивного пути, запустим игру под отладчиком (например, OllyDbg), начнем проходить какой-нибудь уровень и посмотрим имя проигрываемого в данный момент трека. На уровне, где в данный момент находился я, это был трек с названием Pictures. Откроем карту памяти в отладчике и поищем это название. Натыкаемся на любопытную таблицу:

memory

Мы видим перечень треков игры, хотя часть названий из списка не встречается в меню выборе трека для текущего уровня. Интересных мест с упоминанием названия трека в памяти больше не находится. Сделаем логичное предположение, что игра оперирует ID трека, а эта таблица необходима для сопоставления ID - Название. Сохраним эту табличку куда-нибудь, она нам ещё пригодится.

1000 TIM
1001 Unplugged
1007 Steel Drums
1002 New Age
1003 Hay Seed
1004 Progressive Rock
1005 Salsa
1006 Techno Rave
1013 1959 Prom
1011 Bongo Bango
1021 Ragtime
1014 Hip Hop
1012 Keep Tryin'
1017 Detective Theme
1015 Dreams
1016 Tuna Loaf
1018 60's Rock
1019 Pictures
1020 Huey Dewey

Кстати, если отвлечься и посмотреть содержимое архива с саундтреком, то мы увидим, что треки в нём идут в том же порядке, несмотря на непоследовательность ID.

soundtrack

Продолжим исследование. Попробуем оттолкнуться от того, как игра воспроизводит MIDI-файлы. MSDN намекает, на возможное использование WinAPI-функции midiOutOpen перед непосредственным проигрыванием композиции, также dx подсказал, что функция waveOutWrite тоже является возможным кандидатом на использование. Поставим точки останова на эти функции и попробуем поиграть. Ловим срабатывание точки на функции waveOutWrite.

stack

Видим, что обращение произошло из модуля SOS9502. Действительно, в директории с игрой присутствует SOS9502.DLL, которая используется для проигрывания треков. Посмотрим таблицу экспортов этой библиотеки:

exports

Солидная таблица, а в самом низу находится функция с занимательным именем sosMIDIStartSong (мы ведь помним, что в игре используется MIDI-саундтрек). Попробуем поставить на этой функции точку останова. Анализируем стек вызовов:

callstack

Отмечаем подозрительный аргумент 000003EA, который в десятичном виде соответствует числу 1002 или треку New Age из таблицы выше. Рассмотрим функцию по адресу 0041126D подробнее. В ней мы видим следующую конструкцию:

switch

Перед нами простой switch-case, который объясняет отсутствие части треков в настройках звука в игре. Его можно представить следующим кодом:

Треки с идентификаторами 1007, 1005, 1014 и 1015 действительно отсутствуют в настройках, хотя имеются в официальном саундтреке с диска. Также обращаем внимание на строку:

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

Теперь напишем простую программу, которая будет читать эту переменную и проигрывать нужный MP3-файл. Но для начала давайте переименуем файлы саундтрека, чтобы имя состояло только из идентификатора и расширения. Руками это утомительно, поэтому сделаем простенький скрипт на Perl:

Запускаем скрипт в директории с треками и получаем то, что хотели. Вернемся к программе.
Так как нам необходимо проигрывать mp3, то надо озаботиться выбором какой-нибудь библиотеки, которая позволяет это делать (проигрывание средствами Windows, например, с помощью mciSendString - не слишком надежное и плохо переваривает некоторые файлы). Я выбрал BASS Audio Library. Переходим к коду:

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

result

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

Проект для MSVC и Perl-скрипт: скачать.

P.S. Один из треков игры:

Ремонтируем музыку в старой игре: 5 комментариев

  1. С новым годом, Каими и Дх. Спасибо вам за ваши интересные и уникальные посты. Пусть в новом году творческая муза не покидает вас, оставайтесь такими же крутыми :)
    P.S. Новогоднюю картинку скачал, посмотрел не rar ли, нету ли чего в свойствах -- расстроился.

  2. О, классно. Прям как будто мысли мои прочитали. А я как раз хотел предложить добавить в разделы блога рубрику (ну или подраздел) - программирование звука. А если там иногда будет присутствовать реверсинг, то было б, вообще, - супер.
    А тут тебе и звук какой-никакой и элементы реверсинга. Хорошее начало.
    Ну, наверное, Kaimi и dx отнесутся к этому скептически. Скажут, что мы в этом не специалисты и все такое... Хотя тема редкая (особенно в RU секторе) и интересная. Может рискнуть попробовать?

  3. Каими, можешь поискать еще способы в стиме игры бесплатно получать, пожалуйста. Очень нужно

    P.S. С новым годом!

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *