Значит я разобрался. Пишу это больше для самого себя чтоб закрепить, но может и кому-то другому пригодиться так как вероятно устройство остальных хуков в лаунчере подобны этим.
P.S. я опушу различные проверки так как они всегда выполняются успешно если не будет стороннего вмешательства.
У хука есть 3 глобальных переменных, которые идут друг за другом и используются в нём (адреса могут меняться)
libcef.asi + 0x7E5F8 хранит адрес начала оригинальной функции samp.dll + 0xA1E0
libcef.asi + 0x7E5FC хранит адрес на структуру хука с оффсетом 12 байт от начала (4 элемент)
libcef.asi + 0x7E600 хранит адрес на начало структуры хука
Хуки ставятся в функции libcef.asi + 0x35610 и что в неё происходит:
Получаем и записываем в libcef.asi + 0x7E5F8 адрес оригинальной функции
Выделяем участок памяти из 5 байт, далее передаём указатель на него функции, которая заполнит его нопами (0x90 nop)
В 1 байт участка записываем джамп (0xE9 jmp), далее считаем адрес относительно адрес до функции обработчика и записывает в остальные 4 байта (не забываем -5 из-за размера команды)
Выделяем участок памяти в 44 байта. Это по сути та самая структура с данными хука. Представим как массив из 11 элементов 4-байтовых значений
После идёт функция куда передаются указатели и она заполняет эту структуру:
[0] = адрес std::_Ref_count_obj2<urmem::patch>::`vftable' (не используется)
[1] = 0x00000001 (не используется)
[2] = 0x00000001 (не используется)
[3] = адрес оригинальной функции (используется для перезаписи памяти)
[4] = указатель на новый 5-байтовый участок памяти куда будут скопированны данные из предыдущего
[5] = указатель + 5 (записывается уже после выделения памяти)
[6] = указатель + 5 (записывается в функции выделения памяти)
[7] = указатель на новый 5-байтовый участок памяти куда будут скопированны данные из оригинальной функции
[8] = указатель + 5 (записывается уже после выделения памяти)
[9] = указатель + 5 (записывается в функции выделения памяти)
[10] = 0x00000001 (если 1 то по адресу samp.dll + 0xA1E0 наш хук, если 0 то там оригинальные байты)
Также в нёй снимается защита памяти. 5 байтом начиная с samp.dll + 0xA1E0 устанавливается значение 0x40 (можно исполнять, читать и записывать)
И туда записываются эти 5 байт с джампом на функцию обработчик
На выходе в libcef.asi + 0x7E5FC записывается адрес 3 индекса нашего массика, который структура хука
На выходе в libcef.asi + 0x7E600 записывается адрес начала нашего массика, который структура хука
А далее самые первые 5 байт который мы выделили освобождаются. Мы их всё равно скопипастили в участок на который указывает 4 элемент массива
Теперь собственно что в функции обработчике:
Все обращения к структуре тут через libcef.asi + 0x7E5FC (т.е. начиная с 3 индекса)
При срабатывании первым делом возвращаются оригинальные байты в функцию (указатель на них по 7 индексу массива)
Далее при помощи libcef.asi + 0x7E5F8 мы вызываем оригинальную функцию и она выполняется как должно (ведь оригинальные байты мы вернули)
После мы копируем оригинальные байты обратно в памяти указатель на которую в 7 индексе и ставим обратно наш джамп из памяти указатель на которую в 4 индексе
Дальше выполняется собственная логика скрипта (получается уже после выполнения оригинальной функции)
Ровно такой же хук ставится из vorbisFile.dll и он ставится раньше. Получается что libcef.asi сохраняет нее оригинальные 5 байт, а 5 байт джампа, которые записал vorbisFile.dll
И при выполнении якобы оригинала функции в libcef.asi мы на самом деле триггерим хук vorbisFile.dll
обработчик libcef.asi -> обработчик vorbisFile.dll -> выполнение оригинала -> выполнение логики vorbisFile.dll -> выполнение логики libcef.asi
Таким образом у нас 2 независимых скрипта успешно хукают 1 и ту же функцию. И потенциально могут сколько угодно скриптом делать также.
Теперь почему мой хук срабатывал только 1 раз:
vorbisFile.dll по определению загружается и поставит хук раньше любых asi. Мой asi загружается вторым. libcef.asi последним
Выходит libcef.asi сохраняет мой джамп как оригинальные байты и ставит свой хук. При выполнении libcef.asi восстанавливаем мой джамп, запускает его, и тригеррит мой обработчик. Он сразу выполняет свою логику и после хочет выполнить в трамплине оригинальные 5 байт прежде чем вернуться, но это оказываются байты обработчика из vorbisFile.dll и он срабатывает. vorbisFile.dll возвращает уже реально оригинальные байты, выполняет оригинальные скрипт и ставит свой хук обратно. После исполнения его логики происходит retn, который возвращает нас в libcef.asi так как там была инструкции call, а моём хуке возврат был через jmp. libcef.asi обратно сохраняет оригинальные байты (которые хук vorbisFile.dll) и ставит свой хук, после выполняет свою логику.
Получается состояние после 1 исполнения ровно такой же как если бы моего хука вовсе не было. Он тупо затирается. vorbisFile.dll и libcef.asi ставят inline хуки. А мой asi ставит jmp хук. В этом и проблема. Мне нужно также использовать inline хук, чтобы все работало как надо.
Приветствую исправления моего объяснения на более грамотное.