Войти
ФлеймФорумПрограммирование

Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++) (6 стр)

Advanced: Тема повышенной сложности или важная.

Страницы: 15 6 7 813 Следующая »
#75
(Правка: 14:58) 14:39, 27 июля 2021

Gradius
Ты же все равно все переписываешь, алгоритм перед твоими глазами, почему бы просто не сделать кастомную процедуру со стандартной передачей параметров, а не все эти обертки над стеком.
Gradius
> Про передачу параметра через регистр ESI ни слова. 
Это может быть не передача параметра вообще. А использование регистра на уровне прерываний. По представленному коду тут я не вижу чтобы она передавалась, это также может быть неправильная работа Hex Ray. Для тех же "rep movsd" пара ESI и EDI могут где то внешне присваиваться и не быть входными параметрами для процедур.


#76
15:01, 27 июля 2021

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

Я не просто переписываю.  Я вначале вызываю процедуру из самой загруженной в памяти игры.  Нельзя просто взять и всё переписать сразу.  Я по одной процедуре реверсю и проверяю её работу на игре.

Процедура  может внутри себя вызывать другие процедуры - ещё не отреверсенные.  И конвенцию вызовов надо свято блюсти, так как идут вызовы в бинарник игры. 

Лишь только когда процедура полностью себя исчерпала - переписаны ВСЕ входящие в неё процедуры (достаточно  первого уровня вложенности),  то можно сделать свои конвенции и расслабиться

#77
15:02, 27 июля 2021

Gradius
> Процедура  может внутри себя вызывать другие процедуры - ещё не отреверсенные.
А может надо было с низких методов начать?

#78
(Правка: 29 июля 2021, 4:25) 15:54, 27 июля 2021

foxes
> А может надо было с низких методов начать?

Вначале так и делал. Не понравилось. Есть процедуры, которые вызываются очень много раз в разных местах игры. Быстро упарился патчить EXE на предмет своих вызовов.  Плюс к тому же извольте в 5 байт уложиться чтобы сделать относительный вызов процедуры заместо старой:

"call NEAR"

И оффсетик не забудьте пересчитать в опкоде:  0xE8 + 4 байта (адрес где процедура - адрес откуда вызывается - 5)

Упарился быстро :)

Прямые вызовы тут не катят, потому что количество байт больше 5 и замена не влазит в бинарник.

А делать замену через прерывание INT - стек трещит по швам и всё рушится.

Я через это всё прошёл,  и в этой теме об этом написано.

Поэтому я пошёл сверху.  Нашёл точку входа - main(), заменил её на свой вызов.

Вот такой main() в игре:

int CPP_main(int argc, const char **argv)
{
    CPP_setup_game();    //загрузка, инит ресурсов
    CPP_game();          //сама игра
    reset_game();            
    ResetMemory();

   return 0;
}

В самом начале, кроме main() ничего не было.  setup_game, game, reset_game и ResetMemory - дёргались из самой игры.  На этом этапе важно соблюсти конвенцию всех 4-х функций.

Затем, я отреверсил  setup_game и game.  Добился их работоспособности в игре. В названия отреверсенных функций я добавляю "CPP_".  На фрагменте кода выше,  эти функции отреверсены - и они уже вызываются из моей рутины!  И конвенцию тут можно делать какую угодно ))

Остальные функции reset_game и ResetMemory - дёргаются пока из игры (конвенция их важна!).

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

В итоге, загруженная в память игра превращается в пассивную библиотеку, от которой я стремлюсь окончательно отпочковаться!

Плюс метода в том, что есть возможность сразу интегрировать реверс-код в бинарник игры  и проверить на работоспособность без 100%-ного реверса всей игры.  Это очень ценно!  Так как выхлоп Hex-Rays содержит много ошибок.
А так есть возможность постепенно отладить.

Если что-то не идёт - то временно делаем замену на функцию игры - временно обернув процедуру игры в свою:

void CPP_setup_game()
{
 return setup_game(); // вызываем из игры   - временно

....... - тут отреверсенный код,  который не пошёл и нуждается в отладке
}

#79
16:03, 27 июля 2021

Gradius
Все здорово, но тебе ни чего не мешает с начало поправить все методы в том что предлагает тебе Hex Ray снизу, а потом начать добавлять их в твои исходники сверху.

#80
(Правка: 29 июля 2021, 4:25) 16:06, 27 июля 2021

foxes
> ни чего не мешает с начало поправить все методы в том что предлагает тебе Hex
> Ray снизу, а потом начать добавлять их в твои исходники сверху.

Мне он ничего не предлагал! Мне пришлось даже уложить сегменты на реальные адреса, на которые легла игра. Об этом тоже написано, как были вычислены адреса сегментов. 

А зная адреса сегментов и относительные оффсеты процедур-переменных,  можно написать скрипт и сгенерить для Си и Асма хедеры для обращения к  объектам игры.  И сделать именно  относительные  вызовы, а не прямые.  Так как прямые - это указатели. 

А Hex-Rays мешает в кучу всё - и указатели и массивы и переменные.  И операция взятия адреса с массива и указателя - дают разные результаты. Я об этом ранее писал.  Поэтому только  относительные вызовы из большого ROM,  который потом усекается со стартовой точки.

#81
(Правка: 16:18) 16:13, 27 июля 2021

Gradius
> Мне никто ничего не предлагает! В том-то всё и дело.

+ Показать

Это тебе не Hex Rays предложил а ты сам написал. Так?

Gradius
> А Hex-Rays мешает в кучу всё - и указатели и массивы и переменные.
Я тебе уже сказал что с этим можно сделать на примере palette , в плоть до замены строчек кода, которые предложил Hex-Rays.

#82
(Правка: 18:50) 16:21, 27 июля 2021

foxes
> Это тебе не Hex Rays предложил а ты сам написал. Так?

Это мой патч на отреверсенную функцию. Ранее здесь был вызов штатной функции игры.

foxes
> Я тебе уже сказал что с этим можно сделать на примере palette , в плоть до
> замены строчек кода, которые предложил Hex-Rays.

foxes,  у меня голова уже кипит )))  Посмотрю позже

#83
(Правка: 16:28) 16:26, 27 июля 2021
+ Показать

https://youtu.be/Vj2OjGaLwtI?t=3029
#84
2:18, 28 июля 2021

Перечень новых отреверсенных процедур игры:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)
#85
(Правка: 10:44) 10:43, 28 июля 2021

Наконец-то удалось получить рабочую версию отреверсенной Си-процедуры рисования треугольника.
Данная процедура декомпилировалась более, чем в 10 000 строк кода (включая попкорн) и приводилась ранее здесь.

Отладку начал с вывода цветных треугольников, исходящих с центра экрана и расположенных по окружности.

Вначале было так:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Этот рисунок проясняет ситуацию - неправильно работают квадранты(четверти окружности) с отрицательными значениями. Синус, косинус.  Возможны 4 варианта: ++, +-, -+ и --.  Исправно работал только один квадрант(++) в правом нижнем углу экрана.

Глянув начало кода отрисовки треугольника, сразу стало понятно в чём проблема.  Почему-то X-координаты вершин треугольник декомпилер сделал беззнаковыми, а Y-координаты знаковые:

222 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Ну и ясен фиг, что дальнейшая арифметика с отрицательными координатами прёт неверно:

111 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Это насколько нужно быть декомпилятору упоротым, чтобы нулевой элемент массива рассматривать как беззнаковый указатель(DWORD*), а первый - уже как знаковый элемент массива (int [ ] ).

Решилось заменой всех указателей на координаты вершин - знакового типа (int).

Тест успешно пройден!

2 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Аналогично со вторым тестом - тут уже труба была:

было:

3 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

стало:

4 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Теперь, если использовать свой код вывода треугольника, в игре объекты не артефачат.

#86
(Правка: 4:14) 4:10, 29 июля 2021

Перечень новых успешно восстановленных процедур игры:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Остались процедуры, отвечающие за музыку и звук:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Музыка там - Adlib OPL, считывается поток с HMI-файла (поиск в интернете привёл меня к тому, что это некое подобие MIDI-файла. Банк инструментов там свой - имитируется МИДИ с помощью OPL).  Удалось раскопать формат файла, в игре именно такой:

  1. ==== ORIGINAL FORMAT (as used in descent) ======================
    Header  0x00 -  0x07: "HMIMIDIP"
        0x08 -  0x1F: 0's
        0x20 -  0x23: LSB int32, file length.
        0x24 -  0x2F: ??  (in all examined hmp's: zero's)
        0x30 -  0x33: LSB int32, number of chunks.
        0x34 -  0x37: ?? looks like an LSB number (which is one of 78,
                C0, 180, 1E0 in all hmp's I've seen)
        0x38 -  0x3B: LSB int32, midi division (with tempo = 1.0 s/beat (i.e. 60 beats per minute))
        0x3C -  0x3F: LSB int32, duration of the song (in seconds)
        0x40 - 0x307: ?????? looks like 4 bytes per instrument
                (175 instruments + 3 extra?).
    Data  0x308 ..    : music chunks.
    Chunk format (positions are relative to the start position of the chunk):
        0x00 - 0x03: LSB int32, chunk number.
        0x04 - 0x07: LSB int32, chunk length (i.e. including chunk header).
        0x08 - 0x0B: LSB int32, track number (?)
        0x0C ..    : MIDI data, which is standard MIDI, except for
                variable length encoded numbers, which have the
                LSB first (and a byte here means 7 bits), with
                bit 7=1 except for the last byte.

Советуют поставить плагин для Winamp или Foobar2000, чтобы играть такие файлы.  Поставил оба плагина - ни Фубар, ни Винамп не смогли воспроизвести этот файл!  Лишь только DOS-утилита HMP2MID  смогла конвертнуть в миди, и я убедился, что это действительно то.

HMP2MID (работает только в DOS, Win95-98, XP): HMP2MID

Со звуками тоже разобрался - там всё в одном файле. Формат звуков - 8 бит, 22050, 1 канал, Unsigned samples.
Загнал в Audacity как raw-data.
Смещения и размеры каждого семпла - в другом файле (имя то же, расширение другое).

Те процедуры что привёл выше - они реализованы с помощью библиотеки SOS.  Поиск в интернете привёл меня к тому, что это закрытая библиотека от HMI "Sound Operating System", сайт которой уже давно полёг.

Но архивы помнят:
http://web.archive.org/web/19970712134028/http://www.humanmachine… om:80/dev.htm
Не всё уже скачать возможно там...

Доступны только хедеры от SOS.  Например в этой игре:

https://github.com/Xrampino/Actua-Soccer/blob/master/TEST.MAP

Очень сильно помогло, чтобы узнать истинное число и размер аргументов функции, которые Hex-Rays неверно определяет.

Hex-Rays вообще очень плохо дружит с DOS-защищённым режимом.  Дальние указатели (far) он не понимает:  вместо того чтобы сделать дальний указатель на данные, он кладёт в параметры - селектор __DS__ и ближний указатель.

С дальними указателями на процедуры - аналогично: кладёт селектор __CS__ и ближний указатель.

Приходится вручную дорабатывать и переделывать в дальние указатели.

Вот, к примеру - установка и получение обработчика прерывания клавиатуры, переделанные на дальние указатели:

extern "C" void interrupt (far * __cdecl dos_getvect(unsigned))(void);

extern "C" void interrupt far (*OldInt)(void);

void interrupt far CPP_KInt(void) //FINAL
{
Inkey=in8(0x60);
char a=in8(0x61);
char b=a;
a|=0x80;
out8(0x61,a);
out8(0x61,b);
out8(0x20,0x20); //EOI
}

OldInt=dos_getvect(9);

dos_setvect(9,CPP_KInt);

Не помогает даже принудительное задание правильных аргументов в процедурах:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)
1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Оно ни вкакую не хочет делать правильно дальние указатели - пихает селектор CS и короткие указатели:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

А последний параметр вообще превратил в короткий указатель, пропустил 1 параметр - DS.

Итого, в стек будет передано на 2 байта меньше, чем надо.  Последствия очевидны.

#87
(Правка: 8:52) 8:48, 30 июля 2021

Нашёл плеер для HMP-файлов, написаный для WEB: https://github.com/arbruijn/hmpopl-em

Удалось переделать плеер под свои нужды:

1) Подрубить свои музыкальные банки: INST.BNK и DRUM.BNK (банк инструментов OPL3 для воспроизведения HMP-MIDI).

2) Код воспроизведения музыки - засунул полностью в обработчик прерывания таймера. Это очень важно!
Основной цикл программы - пустой!  Основная проблема была, и она решена - высчитать правильно дельты. Иначе ноты шли вразнобой.

При таком подходе - один тик таймера(DOS) или один апдейт звукового буфера SDL (Win32) диктует минимальную порцию времени MIDI (1 дельта).

Размер SDL-буфера в семплах при этом расчитывается так:  Ns = Fs / Tempo.
При Fs=48 кГц и Tempo = 120 BPM,  получается размер буфера SDL в 400 семплов.

Модифицированные фрагменты:

1) Win32 SDL:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

2) DOS32 Adlib OPL:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Для DOS вышло проще, так как там Adlib "искаропки" и запись в настоящие регистры: строить звуковую волну не нужно: OPL3_GenerateStream(&opl,(short int*)stream,len>>2);

Удалось  также решить ещё одну проблему - с dos4gw обработчик прерывания с музыкой не хотел работать - намертво повисал.  А с Pmodew работал.

Официальная документация на коммерческий dos4g говорит о том, что размер стека прерывания - всего 512 байт!:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Тут либо перенастраивать стек, или объявлять статическими переменные внутри функций.  Я выбрал второе.  Таких функций было всего две:

song_step() и hmp_get_event().

После переделки на static,  заработало с и dos4gw!

Осталось обернуть эти куски в функции игры и сделать своё воспроизведение музыки в игре, избавившись от закрытой либы SOS

СОС ))) :

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры

#88
18:29, 30 июля 2021

Настало время, когда я избавился от всех функций игры, заменив их на отреверсенные. Кроме этих (CLIB):

4 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Было принято решение собрать чистый проект с нуля, из полученных исходников игры. Игра пошла!

Только был один баг, связанный с хардкодом. Hex-Rays в цикле вместо указателя на память сделал константу:

hardcode | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Делается цикл, где итератор - есть адрес, с помощью которого идёт обращения к ячейкам памяти (подчёркнуто красным).

Это привело к артефактам в игре :  если сместить сегмент данных на другой адрес, то вершины объектов в игре были некорректными, текстуры отсутствовали. Всё распадалось на чёрные треугольники.

Переводим 2602400 в 16-ричное и получаем 0x27B5A0.  Смотрим MAP-файл что там:

2 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Смотрим ещё в IDA  - это массив :

3 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Я сразу понял в чём дело. С помощью Notepad++ и поиска по регулярному выражению нашёл в коде программе все "семизнаки":

5 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Часть из них я ранее исправил, но остался этот один  незамеченым. 

Тут только автоматизировать поиск-замену:  выхлоп вышел более 20 000 строчек на Си. И это только то, что нужно.  На самом деле,  он был больше.

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

#89
(Правка: 3:56) 3:52, 1 авг. 2021

Написал свои функции для воспроизведения музыки и звуков в игре:

void MY_OpenMusic(unsigned int a4);
void MY_FreeMusic(void);
void MY_StartMusic(__int16 a1, unsigned __int8 a2);
void MY_StopMusic(void);

void MY_OpenSound(void);
void MY_FreeSound(void);
char MY_PlaySample(unsigned a1, unsigned a2, unsigned a3, unsigned a4, unsigned a5, unsigned a6, unsigned a7);
void MY_FadeOutSample(short a1, short a2, short a3, short a4);
void MY_FadeInSample(short a1, short a2, short a3, short a4);
void MY_SetSamplePitch(short a1, short a2, short a3);
void MY_SetSamplePan(short a1, short a2, unsigned short a3);

Музыка играется рутиной плеера HMP, о которой я уже писал в этом посте:
https://gamedev.ru/flame/forum/?id=262348&page=6&m=5425328#m89

Со звуками пришлось повозиться.  Сделал своё микширование каналов, поддерживающее изменение тона, громкости; зацикливание. Движок игры уникальный, пришлось реализовывать поведение функций точно, как в игре.

Завёл всё это на SoundBlaster16. Благо, наработанного кода со времён DOS'а у меня много :)

Игра вышла 1-в-1, как оригинал.

Остался всего один шаг, перед портированием на винду/линукс (SDL) - преобразовать сегменты данных:  они сейчас на ассемблере и описаны как байты-полуслова и слова.  И линкуется всё в один блоб:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

В принципе, можно оставить как есть, но тогда под каждый компилятор придётся править АСМ в синтаксис того тулчейна, под который идёт сборка.

Причём, в некоторых случаях некоторые байты расчитываются при ассемблировании, формируя нужные смещения данных в памяти:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Объекты сегмента данных с точки зрения Си-компилятора могут трактоваться, как:

- переменная
- массив

extern "C" int _STACKLOW;
extern "C" char polypoints[];

Необходимо сохранить сишную трактовку объектов и их взаимное расположение в памяти.  Базовый адрес может быть любой.

Каким образом можно решить эту задачу?  (сегмент данных на ассемблере преобразовать в си)?

Hex-rays разрывает взаимное расположение объектов, превращая сегмент данных в негодность. Тут только  с АСМа ваять.

Страницы: 15 6 7 813 Следующая »
ФлеймФорумПрограммирование