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

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

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

Страницы: 17 8 9 1013 Следующая »
#105
(Правка: 10:45) 10:44, 8 авг. 2021

Gradius

Это я все понимаю, но ситуация немного сложнее, т.к. коллбек вызывается из другого потока.
Рассмотрим такой код:

volatile flag = 0;
int buffer[4];

void thread1()
{
    buffer[0] = 1;
    buffer[1] = 2;
    buffer[2] = 3;
    buffer[3] = 4;

    flag = 1;
}

void thread2()
{
     while(!flag) {}
     print(buffer, countof(buffer));
}
Вопрос - что выведет print? Ответ - что угодно, от "0 0 0 0" до "1 2 3 4".

> как в МК или ДОС
Эти практики не работают в современных многоядерных процессорах.

> Коллбэк выставляет флаг в 1, после завершения работы с данными
Вот факт завершения работы с данными никак не следует ни из того, что флаг выставляется последним, ни из того, что он volatile.


#106
(Правка: 12:00) 11:58, 8 авг. 2021

Основная программа (не поток):

static volatile unsigned char AUDIO_FLAG=0;

int main(void)
{
 ............

if(SDL_Init(SDL_INIT_AUDIO)<0)
 {
  printf("error!\n");
  exit(-1);
 }

 SDL_AudioSpec spec,obt;
 SDL_AudioDeviceID dev;

 spec.freq     = 44100;               //частота семплирования
 spec.format   = AUDIO_S16SYS;        //16 бит, знаковый
 spec.channels = 1;                   //1 канал
 spec.samples  = SB_SAMPLE;           //Fs/TEMPO
 spec.callback = mySDL_AudioCallback;


 if(SDL_OpenAudio(&spec,NULL)<0)
 {
  printf("error!\n");
  exit(-1);
 }

 if(MusicMode)OPL3_Reset(&opl,spec.freq);

 song_init();

 SDL_PauseAudio(0); //включение кольцевого буфера  - с этого момента пошёл вызов mySDL_AudioCallback с частотой 120 Гц

.....

while(!game_end) // игровой цикл
{
 AUDIO_FLAG=0; //сбрасываем

....
}

//завершение работы программы:

 while(!AUDIO_FLAG); //ждём пока коллбэк не пошлёт нам завершение

 SDL_PauseAudio(1); //выключение кольцевого буфера - теперь  mySDL_AudioCallback   никогда не вызовется
 
if(hf)         // освобождаем память!
 {
  hmp_close(hf);
  hf=NULL;
 }

...........

 return 0;  //выход из программы
}

Поток (колбэк):

static void mySDL_AudioCallback(void*, Uint8 *stream, int len) //аналог обработчика прерывания звуковой системы
{
  MIXER((s16*)stream);  //рендерим новую звуковую волну в буфер

  AUDIO_FLAG=1;  // выставляем флаг окончания  работы с данными - их можно освободить при выходе
}

Работает, ничего не заваливается. Уже раз 50 наверное проверял))

#107
(Правка: 12:49) 12:40, 8 авг. 2021

Gradius

Вызов MIXER может ещё работать когда while(!AUDIO_FLAG); уже вышел. Если, например, сразу после while будет unmap памяти звуковухи, то будет сегфолт. Впрочем, это крайне маловероятно, шанс такое поймать практически нулевой.

PS. Хотя нет, сегфолта по любому не будет. Но данные таким образом от одного потока другому точно передавать не стоит.

#108
(Правка: 13:33) 13:30, 8 авг. 2021

Ghost2
> Но данные таким образом от одного потока другому точно передавать не стоит.

1) Почему?  Вышло прерывание, которое само себя снабжает новыми данными, когда нужно.

Основной цикл программы при этом никак его не касается, разве что посылает команды - начать/прекратить воспроизведение/построение аудиоданных.

2) Каким образом стоит передавать данные?

Ghost2
> Вызов MIXER может ещё работать когда while(!AUDIO_FLAG); уже вышел. Если,
> например, сразу после while будет unmap памяти звуковухи, то будет сегфолт.

Это исключено, потому что освобождается не *stream,  а *hf - дескриптор на область данных  с нотами.
stream - это указатель самого коллбэка, в него надо лить новые данные, которые строятся из  нот, и они отыграют спустя 1/120 секунды.

#109
(Правка: 14:03) 14:01, 8 авг. 2021

Gradius

> Почему?  Вышло прерывание
Это не прерывание, а ожидающий события из ядра поток исполнения. Ни одна ОС не даст работать в контексте прерывания из юзерспейса. А 120 герц - это весьма грубо и реально недостижимо в системе подобной Windows. В среднем да, но не более.

> Каким образом стоит передавать данные?
Если речь идёт о том, чтобы сделать нечто над данными, просигналить другому потоку что они готовы и завершиться, то стандартные атомики, дающие гарантию упорядоченности доступа к памяти. atomic_store из C11 даёт гарантию, что все записи до его вызова будут видны параллельному потоку после того, как atomic_load вернёт нужное значение.
Опять же, если устраивает busy wait, для всего остального созданы примитивы синхронизации и всякие wait функции над ними, которые по определению упорядочивают доступ.

> потому что освобождается не *stream
Он тоже освобождается после while, но, возможно, до того, как все данные из коллбека будут видимы основному потоку. И тут спасают две вещи - в первую очередь то, что освобождение ресурсов звуковой карты безальтернативно сопровождается синхронизацией доступов к памяти и то, что на всю эту кухню уходит приличное количество тактов процессора.

#110
14:23, 8 авг. 2021

Gradius
> //аналог обработчика прерывания звуковой системы
Но в SDL оно реализуется натурально потоком:
https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlopenaudio.html :

This should be set to a function that will be called when the audio device is ready for more data. It is passed a pointer to the audio buffer, and the length in bytes of the audio buffer. This function usually runs in a separate thread, and so you should protect data structures that it accesses by calling SDL_LockAudio and SDL_UnlockAudio in your code. The callback prototype is:

И volatile не является адекватным инструментом межпотокового общения (примитивом синхронизации).
https://software.intel.com/en-us/blogs/2007/11/30/volatile-almost… -programming/ (возможно, неправильная ссылка)
https://www.it610.com/article/2644476.htm
https://blog.regehr.org/archives/28
https://sites.google.com/site/kjellhedstrom2/stay-away-from-volat… threaded-code

Адекватным инструментом межпотокового общения являются атомики. Вот их можно и юзать.
https://en.cppreference.com/w/cpp/atomic/atomic ( https://en.cppreference.com/w/c/language/atomic )

Конкретно в MVSC возможно несколько более сильные гарантии на volatile:
https://docs.microsoft.com/en-us/cpp/cpp/volatile-cpp

#111
14:49, 8 авг. 2021

FordPerfect

> https://blog.regehr.org/archives/28
О, спасибо, давно искал такой реф. Это вообще должно быть топ-постом на любом embedded форуме.

#112
(Правка: 15:26) 14:57, 8 авг. 2021

Ghost2
> А 120 герц - это весьма грубо и реально недостижимо в системе подобной Windows.
> В среднем да, но не более.

Очень достижимо. Иначе музыка шла бы в разнобой, а этого нет.  В Линуксе тоже всё работает.  МИДИ-дельты отсчитываются именно по этому коллбэку.

120 Герц на 44100 - это буфер из 368  аудиосемплов(16 бит).  Вполне себе нормально.

Ghost2
> Опять же, если устраивает busy wait, для всего остального созданы примитивы
> синхронизации и всякие wait функции над ними, которые по определению
> упорядочивают доступ.

Мне ничего не нужно синхронизировать в рабочем цикле игры.  Потому что коллбэк сам себя подпитывает новыми данными.  Синхронизация нужна один раз и в конце, чтобы затушить коллбэк и освободить динамические данные. Всё.  Более аудио-системы программа не касается вообще!

Ghost2
> Он тоже освобождается после while, но, возможно, до того, как все данные из
> коллбека будут видимы основному потоку.

Нет.  Никто не знает когда он освобождается.  Но это не важно.  Я с ним ничего не делаю.  А после  SDL_PauseAudio(1);  коллбэк  не жилец.  И далее - забота  SDL'я что с ним делать.

И вообще, есть примеры у SDL как работать с аудио-системой.  А то раздули из мухи слона )))

И вообще речь шла об освобождении не *stream,  а нотного массива(дескриптор *hf), который используется при построении звуковой волны в функции MIXER().

MIXER() отработал в коллбэке?  Да!  Значит можно делать с *hf всё что угодно, до наступления нового коллбэка.

FordPerfect
> Адекватным инструментом межпотокового общения являются атомики. Вот их можно и
> юзать.

Спасибо, интересно! Почитаю для общего развития.

Но правда, с этими атомиками можно намудрить настолько, что хрен потом портанёшь на ARM или DSP какой-нибудь.

FordPerfect
> This should be set to a function that will be called when the audio device is
> ready for more data. It is passed a pointer to the audio buffer, and the length
> in bytes of the audio buffer. This function usually runs in a separate thread,
> and so you should protect data structures that it accesses by calling
> SDL_LockAudio and SDL_UnlockAudio in your code. The callback prototype is:

У меня поток только рид-онли, надобности в локе нет.  Да и основная программа, тоже ничё не делает с этими данными )))  Поток сам вызывает MIXER()  и строит данные в аудиобуфер.

Основная программа посылает только команды - начать/остановить воспроизведение,  сменить номер мелодии.
Рендеринг - на плечах потока )))

FordPerfect, Ghost2 !  Вы всё сильно усложняете!  На деле есть SDL и он сам на себя берёт черовую работу.  Атомики - это уже оверинжиниринг выходит (с использованием SDL).

---

Gradius
> Следующий шаг - порт под WEB с помощью emscripten!

Ага! Фигушки!  Emscripten не понимает объектник в ELF-формате.  А LLVM ассемблер слишком мудрёный (похож на Си), чтобы на него переписывать весь сегмент данных игры.

Поэтому сейчас копаю в сторону избавления от ассемблерного блоба.  Разрабатываю концепцию псевдо-массива и псевдо-переменной.

Есть одна неоднозначность, напомню её.  Декомпилятор использует переменные и массивы.  Может брать с них адрес или индексировать как массивы.  Возможны случаи:

Array - взятие адреса массива
&Array - тоже взятие адреса массива (эквивалентное первому варианту. Доказательства ниже.)
&Array[16] - взятие адреса масива + смещение

Var - значение переменной
&Var - адрес переменной

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

&Pointer и &Array - не эквивалентны.  Чтобы избавиться от такой неопределённости,  написал скрипты, которые по хедеру делают выборку всех массивов.  Затем происходит поиск по всему исходнику игры, где упоминаются массивы со взятием адреса и без индексации (смещения в квадратных скобках).

Тоесть : &Array[16] - хорошо кладётся на псевдо-массив.  А вот &Array - проблема:  решается путём удаления операции взятия адреса:

&Array = Array,  при условии если Array - массив и объявлен как Array[].

При этом преобразование массива в указатель: Array => Pointer - равнозначно.

Проверил гипотезу на практике:  собранный бинарник из пофикшенных сорцов оказался точно таким же(размер естественно, тоже одинаков)!

Тоесть компилятору пофигу на визуальную разницу взятия адреса - просто упоминание имени массива или взятие адреса с имени.  Это одно и то же. 

Без взятия адреса синтаксис предпочтителен, так как позволяет перейти к указателям (псевдо-массивам).

О концепции.  Вот псевдо-массив:

#define shipY ( (int*)(BASE+0x00000008)) /* имитация массива    */

А вот псевдо-переменная:

#define shipX (*(int*)(BASE+0x00000004)) /* имитация переменной */

BASE - это переменная, которая хранит адрес выделенной памяти, куда закачан бинарник данных игры(те самые сегменты данных, оставшиеся на ассемблере):

unsigned int BASE=(unsigned int)&mempool;
- не забыть про выравнивание как минимум на 16 байт.

Относительные смещения(пример выше - 0x4, 0x8) - будут взяты из ассемблерного листинга или MAP-файла.

Примеры обращений к таким псевдо- сущностям такие же:

 shipX=777;

 printf("%d \n",shipX);                            //OK
 printf("%d %d\n",&shipX,&mempool); //OK
 printf("%d \n",((int*)&shipX)[0]);           //OK


 shipY[0]=555;

 printf("%d \n",shipY[0]); //OK

 printf("%d %d\n",&shipY[0],&mempool); //OK

 printf("%d %d\n",shipY,&mempool); //OK

#113
16:12, 8 авг. 2021

Gradius
> MIXER() отработал в коллбэке? Да! Значит можно делать с *hf всё что угодно, до наступления нового коллбэка.
Так static volatile unsigned char AUDIO_FLAG равный 1 тебе не гарантирует, что "Да!".
https://blog.regehr.org/archives/28

Take this simple example (which originated with Arch Robison):

volatile int ready;
int message[100];

void foo (int i) {
  message[i/10] = 42;
  ready = 1;
}

The purpose of foo() is to store a value into the message array and then set the ready flag so that another interrupt or thread can see the value.  From this code, GCC, Intel CC, Sun CC, and Open64 emit very similar assembly:

$ gcc -O2 barrier1.c -S -o -
foo:
  movl 4(%esp), %ecx
  movl $1717986919, %edx
  movl $1, ready
  movl %ecx, %eax
  imull %edx
  sarl $31, %ecx
  sarl $2, %edx
  subl %ecx, %edx
  movl $42, message(,%edx,4)
  ret

Obviously the programmer’s intent is not respected here, since the flag is stored prior to the value being written into the array.

#114
17:12, 8 авг. 2021

FordPerfect
> Так static volatile unsigned char AUDIO_FLAG равный 1 тебе не гарантирует, что
> "Да!".
> https://blog.regehr.org/archives/28

OK, вернусь к этому немного позже.  Сейчас занят скриптингом данных.

Было так:

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

Станет так (пока заготовка - номера сегментов и оффсеты пока не проставлены. Это будет второй шаг):

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)
#115
(Правка: 9 авг. 2021, 1:47) 17:40, 8 авг. 2021

Gradius
> Станет так (пока заготовка - номера сегментов и оффсеты пока не проставлены.
> Это будет второй шаг):

С массивами - аналогично.  Было так:

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

будет так:

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

Gradius
> Поэтому сейчас копаю в сторону избавления от ассемблерного блоба. Разрабатываю
> концепцию псевдо-массива и псевдо-переменной.

Успешно избавился от сегментов данных на ассемблере и от объектников всяких.  Концепция псевдо-переменных работает.  Игра теперь чисто на Си/C++.

#117
12:20, 10 авг. 2021
s4_result | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++) s4 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++) s5_result | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Завершил работу над проектом реверса игры.

Сделал порт под Web. Поиграть в игру в браузере: https://clobberasm.itch.io/tube

Выложил исходный код игры на GitHub: https://github.com/rep-stosw/tube-game-dos

Там же и готовые сборки для Windows/Linux.

Управление(сохранено с оригиналом):

1,2  - Player Select
Esc  - Exit
Q  - Forward / Accelerate
- Backward / Reverse
- Left
- Right
Spacebar - Shoot
Enter  - Bomb

Видео с моим геймплеем:

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

#118
12:34, 10 авг. 2021

Хм... недурно и даже играбельно, надеюсь её не забанят до вечера.

#119
(Правка: 7:07) 7:06, 11 авг. 2021

0iStalker
> надеюсь её не забанят до вечера.

Я не сильно расстроюсь, если её прибьют по копирастическим соображениям.  Моя конечная цель - запустить игру на своих консолях. Остальные порты - проверка кода на компиляцию и нормальную работу программы.

0iStalker
> Хм... недурно и даже играбельно,

В браузере Google Chrome было гудение. Немного переделал вывод звука, теперь в Хроме звук не гудит.

А вообще, Web - это побочка. Уровень привилегий настолько низок -  нет гарантий, что  аудио-коллбэк будет вызываться точно и в срок.

Частота семплирования в вёб-версии теперь 44100 вместо 32000, эмулятор Adlib более качественный.  Увеличил длину аудиобуфера до 512,  было 256. 

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

Перезалил игру на itch и обновил исходники:

https://clobberasm.itch.io/tube

https://github.com/rep-stosw/tube-game-dos

Ну и наконец-то уделил внимание той самой проблеме, что подымали Ghost2 и  FordPerfect.  Я вначале их не понял. 

Проще всего им было написать:  "нет гарантий, что написанный тобой код будет исполняться в строгом порядке как ты написал..."  Я бы сразу понял тогда.

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

Страницы: 17 8 9 1013 Следующая »
ФлеймФорумПрограммирование