Авторам плагинов - Printable Version +- Wake of Gods Forum | Форум Во Имя Богов (http://wforum.heroes35.net) +-- Forum: Герои Меча и Магии 3.5 WoG/ERA (/forumdisplay.php?fid=99) +--- Forum: Моды (/forumdisplay.php?fid=104) +--- Thread: Авторам плагинов (/showthread.php?tid=5838) |
Авторам плагинов - Berserker - 19.11.2020 17:39 Работа с перенесёнными структурами данных Если Вы используете структуры данных, которые имеют вероятность быть расширенными Эрой или другими плагинами, то для сохранения совместимости и предотвращения вылетов следует поступать следующим образом: 1) Плагинам, которые расширяют структуры, следует в событиях "OnBeforeWoG" или "OnAfterWoG" вызвать функцию void __stdcall RedirectMemoryBlock (void* OldAddr, int BlockSize, void* NewAddr); Укажите старый адрес структуры, старый размер структуры и новый адрес структуры. 2) Плагинам, что используют потенциально расширяемые структуры нужно в событии "OnAfterStructRelocations" сохранить себе новые адреса структур через вызов GetRealAddr: void* __stdcall *GetRealAddr (void* Addr); Для подписки на события Эры используйте void (__stdcall *RegisterHandler (TEventHandler Handler, const char* EventName); Code: struct TEvent Таким образом Вы обеспечите корректную работу всех других плагинов и новых версий Эры. RE: Авторам плагинов - XEPOMAHT - 19.11.2020 18:40 (19.11.2020 17:39)Berserker Wrote: Если Вы используете структуры данных, которые имеют вероятность быть расширенными Эрой или другими плагинами, то для сохранения совместимости и предотвращения вылетов следует поступать следующим образом На практике это было использовано 1 раз в Тифоне для структуры монстров (и то только после того, как Берсеркер это сделал сам, у меня так и не вышло такое добавить в код плагина), больше нигде "вероятность быть расширенными Эрой" слава богу не встретилась. И вообще, тут понятнее термин "перенесёнными в памяти", а не "расширенными": например Тифон 3.2 расширяет структуру героев, но не переносит её в памяти (сделано для совместимости с HD-модом и прочими модами, в которых RedirectMemoryBlock никогда не будет в силу отсутствия исходных кодов или возможности перекомпиляции или просто автору плагина наплевать на совместимость). (19.11.2020 17:39)Berserker Wrote: Таким образом Вы обеспечите корректную работу всех других плагинов и новых версий Эры. Чтобы это работало на практике, нужно, чтобы это применялось в двухстороннем порядке. Например, тот же Тифон 3.2 переносит кучу всяких данных, но RedirectMemoryBlock применяется только для структуры монстров для совместимости с era.dll, но абсолютно не известно, какие перенесённые структуры потребуются ЭРЕ в будущем (а Тифон переносит ещё много что... ). В подавляющем большинстве случаев для чтения перенесённых структур лучше использовать базовые адреса структур (любая прямая адресация в коде может быть принята за базовый адрес) - это позволяет не договариваться с автором плагина, в котором перенесена структура, о добавлении RedirectMemoryBlock. Тем более, одна структура может быть перенесена только одним плагином (например Тифон никогда не заработает вместе с Аметистом так же как ERA+ будет всегда конфликтовать с вражеским Knightmare Kingdoms), а самих плагинов, меняющих структуры, существует не так много. Для совместимости плагинов как раз и созданы сборники плагинов - никому не нужный отечественный ERA+ и никому не нужный его зарубежный аналог Knightmare Kingdoms. Т.е. для создания какого-то по-настоящему глобального мода для ERA, типа Horn of the Abyss, выбираются сборники плагинов, которые имеют проверенную совместимость (но, как понимаете, до этого ещё дело не дошло, ВСЕ современные моддеры на ERA ограничиваются созданием небольших модов), а не подбираются отдельные плагины (которые всё равно будут частью подплатформ ERA). RE: Авторам плагинов - Berserker - 19.11.2020 19:12 XEPOMAHT, я не занимаюсь поддержкой плагинов, которые пишутся криво в расчёте, что других плагинов не существует. Предложенный механизм из Эры 2.0 (!) решает большинство проблем. Так, если в 10 плагинах нужен массив монстров, а 1-му плагину нужно его расширить (Typhon или Аместист), то все они будут работать. Исходники TyphonZ опубликованы. Как написать две команды на ассемблере можно и подглядеть. TyphonZ с нуля писал МоР. За консультациями никто не обращался. Я лишь исправил несколько самых очевидных проблем: убрал хуки на события, заменив их подпиской на события Эры, и добавил те самые RedirectMemoryBlock. Более того, UN:C в Эре тоже всегда использует GetRealAddr на базовый адрес (если в параметрах отдельно указывается и смещение). То есть всё работает прозрачно и надёжно. RE: Авторам плагинов - XEPOMAHT - 19.11.2020 20:38 (19.11.2020 19:12)Berserker Wrote: Так, если в 10 плагинах нужен массив монстров, а 1-му плагину нужно его расширить (Typhon или Аместист), то все они будут работать. К примеру, HD-мод как-то работает с перемещённым массивом монстров вместе с Тифоном или Аметистом. HD-моду не потребовалось использование GetRealAddr. А вот в ERA почему-то потребовалось. Где искать истину? Мы в плену HD-мода по-сути, совместимость с которым на 1-м месте. Если era.dll будет использовать перемещённые структуры, кроме структуры монстров, то добавить RedirectMemoryBlock в код Тифона - без проблем, нужно будет только сообщить для каких конкретно. Ну а для всех перемещаемых структур делать это боюсь - сказывается негативный опыт со структурой монстров (там помог только метод научного тыка). RE: Авторам плагинов - feanor - 19.11.2020 22:58 Известить эру о том, что мы переносим структуру, не западло, конечно. (хотя и в ряде случаев бессмысленно, если изменяется и способ работы со структурой, например, в случае реализации альтерветок/третьих грейдов) а вот самому использовать GetRealAddr() - фигня какая-то. Во-первых, совершенно неясно, куда его в существующей парадигме совать. Ну вот есть у нас есть тупо определенный дефайном указатель на игровые структуры, с нулевыми накладными расходами; их библиотека подключается как обычный заголовочник, и не нужно возиться ни с каким разрешением зависимостей и экспортами функций. Вот оч правильный вопрос Quote: HD-моду не потребовалось использование GetRealAddr. А вот в ERA почему-то потребовалось. Где искать истинуПотому что в НD используется двойной указатель на структуру монстров, указатель из кода на указатель на массив структур как-то так: было Code: //#define o_CreatureInfo ((_CreatureInfo_*)0x6703B8) Code: #define CREATURE_INFO_OFFSET (*(_ptr_*)0x6747B0) И именно так и надо делать, просто потому что расширялки мимо этих указателей в любом случае не пройдут, а выбор между предлагаемым методом, который требует от всех участников соблюдения конвенций (учитывая, что соблюдение конвенций и эра - кхмкхм такое себе сочетание) и методом, который не требует этого, ну, очевиден. RE: Авторам плагинов - Berserker - 19.11.2020 23:20 Quote:К примеру, HD-мод как-то работает с перемещённым массивом монстров вместе с Тифоном или Аметистом.То, как работает HD (через двойной указатель) — гораздо безопаснее. И HD-мод не расширяет структуру. Ему не нужен RedirectMemoryBlock. Quote:Если era.dll будет использовать перемещённые структуры, кроме структуры монстров, то добавить RedirectMemoryBlock в код Тифона - без проблемНе нужно ждать. Вызов RedirectMemoryBlock → это просто извещение, которое сразу даёт кислород всем плагинам на ERM/Lua/C++/Pascal/Brainfuck. Quote:а вот самому использовать GetRealAddr() - фигня какая-то.Тут два момента: 1) В вопросах скорости — экономия на спичках, даже не серьёзно. 2) Работать классически не через define, а через объявление указателя. _CreatureInfo_ *monsterAttrs = 0xAAAAAA; => OnAfterStructRelocations monsterAttrs = GetRealAddr(monsterAttrs); преобразовали адрес с оригинального в конечный. У меня так в одном из модулей: Code: procedure OnAfterStructRelocations (Event: GameExt.PEvent); stdcall; А там, где прямой адрес был, заменил на: Code: NextRecruitMonsDlgOpenEventDwellingId := pbyte(Heroes.GetTownManager.Town.TownType * 2 + Context.EDI + integer(GameExt.GetRealAddr(Ptr($68A3A2))))^; Если я берусь использовать в коде таблицу, то считаю, что навыки/монстров/тексты/героев кто-то наверняка можешь расширить. Или правильнее было бы использовать фиксированные адреса и писать, что у людей просто нерабочие плагины? RE: Авторам плагинов - Berserker - 19.11.2020 23:29 А теперь представьте, что вы написали 10 плагинов и ушли на пенсию. Чтобы сделать с ними совместимость, мне достаточно в пару мест добавить GetRealAddr в ранее неучтённых указателях. Как и в любом актуальном коде. Но если ваши плагины сами никогда не уведомляют о расширенных структурах и не пользуются GetRealAddr, то беда. Quote:#define o_CreatureInfo ((_CreatureInfo_*)CREATURE_INFO_OFFSET)Такой подход я применяю пока для определения лимита существ, но стоит какому-нибудь majaczek пропатчить код по данному адресу…Вы поняли. То, о чём извещено явно, можно явно же и узнать. Вариант HD-мода тоже неплох, но требует искать всегда такие неизменные участки кода (которые часто не выравнены) и надеяться, что там никто команду не пропатчил с полной заменой логики. У каждого разработчика будет свой участок кода, из которого он будет считывать указатель, что повышает хрупкость всей системы. Хотя в целом решение лежит на поверхности, его даже в patcher нужно было вводить: Redirect + GetAddress пара позволила бы всем договориться и не ссылаться на код. RE: Авторам плагинов - XEPOMAHT - 20.11.2020 00:58 (19.11.2020 23:20)Berserker Wrote: То, как работает HD (через двойной указатель) — гораздо безопаснее. И HD-мод не расширяет структуру. Ему не нужен RedirectMemoryBlock. Ну HD-мод видит перемещённые структуры от hota.dll, где много чего наменяли, и перемещённые данные воговского кода независимо от версии вога, в общем это вполне универсальное решение. Ну а мне откуда знать, что автор плагина сделал RedirectMemoryBlock? Авторов ведь палкой не заставишь делать в коде RedirectMemoryBlock (программисты в основном одни лентяи). (19.11.2020 23:20)Berserker Wrote: Если я берусь использовать в коде таблицу, то считаю, что навыки/монстров/тексты/героев кто-то наверняка можешь расширить. Или правильнее было бы использовать фиксированные адреса и писать, что у людей просто нерабочие плагины? Вот работало бы без перекомпиляции плагинов - другое дело. Вот если например завтра появится плагин на новые типы местности с RedirectMemoryBlock, что ж делать с плагинами, авторы которых и в самых страшных снах не могли представить, что кто-то увеличит в игре количество почв и что там надо было ставить GetAddress, когда ещё никто об этом не знал? Исходники давно утеряны или автору давно пофигу на моддинг. Устаревший плагин так и останется устаревшим и не будет работать. (19.11.2020 23:29)Berserker Wrote: А теперь представьте, что вы написали 10 плагинов и ушли на пенсию. Чтобы сделать с ними совместимость, мне достаточно в пару мест добавить GetRealAddr в ранее неучтённых указателях. Как и в любом актуальном коде. Но если ваши плагины сами никогда не уведомляют о расширенных структурах и не пользуются GetRealAddr, то беда. Если так сильно нужен GetRealAddr в UN:C например, то определение адресов структур можно добавить в саму ЭРУ, не перекладывая это на плечи плагинописцев. Можно даже сделать отдельным плагином, который укажет ЭРЕ о всех перемещённых структурах. (19.11.2020 23:29)Berserker Wrote: Такой подход я применяю пока для определения лимита существ, но стоит какому-нибудь majaczek пропатчить код по данному адресу…Вы поняли. То, о чём извещено явно, можно явно же и узнать. Бинарные патчи хороши для одного глобального мода типа MoP и являются абсолютным злом на платформе ERA (Тифон 3.хх тоже грешен небольшим количеством бинарных патчей, скопированных с MoP, которые было просто лень преобразовывать в отдельные функции). Никто не застрахован и от перекрывающих друг друга хуков, а если их накапливается 100500 штук на era.exe, то любой программист в них утонет и никакая база уже не поможет. (19.11.2020 23:29)Berserker Wrote: Вариант HD-мода тоже неплох, но требует искать всегда такие неизменные участки кода (которые часто не выравнены) и надеяться, что там никто команду не пропатчил с полной заменой логики. У каждого разработчика будет свой участок кода, из которого он будет считывать указатель, что повышает хрупкость всей системы. Да, только так. Это в MoP сильно изменён код игры, что ни HD-мод, ни ERA никогда там не смогут запуститься (и с каждой новой версией MoP тамошний код мутирует ещё сильнее). В ERA ж - только отслеживать чужой код и делать соответствующие правки у себя в плагине. И больше использовать события эры, где это возможно. Да и самих плагинов, расширяющих структуры, очень мало. У меня специально скомпилировано всё в одной dll, чтобы не мучится с адресацией перенесённых структур на разных dll. Для поддержки новых героев, например, придётся поглотить плагин Dwellings.dll, как раз из-за проблемы с адресацией. И т.д. RE: Авторам плагинов - Berserker - 20.11.2020 01:15 В любом случае, это информация для igrik-а, раз ему пришлось снова решить вылет, связанный с конфликтом доработанного аметиста от majaczek. Самому majaczek-у я пост перекинул, благо Яндекс прекрасно перевёл. Рекомендую всем новый код писать именно в таком варианте, как минимум используя GetRealAddr или на худой конец двойной указатель, а также вызывая RedirectMemoryBlock для работы Эры и плагинов, что будут следовать соглашению и UN:C. RE: Авторам плагинов - feanor - 20.11.2020 01:54 Quote:1) В вопросах скорости — экономия на спичках, даже не серьёзно.Минимум один раз я впилился в ситуацию, когда мне даже накладные расходы на патчеровский переходник для хука оказались велики. Так что я бы не стал говорить за спички. Quote:У меня так в одном из модулей:Тут пять объявлений. В оч базовой библиотеке типа старохотовского/третьехдшного Homm3.h их семьдесят, и это потому что я очень ленивый и не добавлял туда еще несколько десятков локально разбросанного по исходникам. Все переписывать, все в глобальные переменные >< Еще и возможность сравнительно быстро адаптировать несложные плагины под сод уйдет. Quote:А теперь представьте, что вы написали 10 плагинов и ушли на пенсию. Чтобы сделать с ними совместимость, мне достаточно в пару мест добавить GetRealAddr в ранее неучтённых указателях. Как и в любом актуальном коде. Но если ваши плагины сами никогда не уведомляют о расширенных структурах и не пользуются GetRealAddr, то беда.Седьмой плагин сломался, когда Эра 4.0.74 поставила свой хук в то же место, третий не актуален, потому что его функционал все равно ломает пару других популярных плагинов, восьмой отвалился при очередном обновлении HD-мода, еще четыре сдохли, когда Эра 5.70.05 в очередной раз вышла под девизом "Сбросим Пушкина с парохода современности", грохнув какое-то старое легаси. Общая хрупкость системы? Она у нас давно в отрицательных величинах. Более того, тут сама пресуппозиция неверна, прям обязательное использование GetRealAddr имеет смысл только если у нас _уже_ нету десятка плагинов от ушедших на покой авторов и кучи разбросанных по сети устаревших примеров, по которым учатся новички. А если мы вынуждены о них думать..только цепочки косвенных адресаций, только хардкор. Quote:Вариант HD-мода тоже неплох, но требует искать всегда такие неизменные участки кода (которые часто не выравнены) и надеяться, что там никто команду не пропатчил с полной заменой логики.а если у нас команда пропатчена с полной заменой логики, то, скорее всего, нам и GetRealAddr не поможет. Т.е. не искусственный пример для этого - тифон или что-то тифоноподобное (реализующее и альтерветки/апгрейды), что для работы с таблицей монстров заменяет почти все обращения к ней. Казалось бы, указателей нету, но ведь у нас теперь нету и таблицы, на которую можно было бы кидать редирект! Причем в подавляющем большинстве ненадуманных примеров так и будет по определению - если мы патчим обработчик таблицы, то, скорее всего, нас не устраивает таблица целиком. Кстати, а что мы делаем с тем фактом, что GetRealAddress не даст нам информации ни о дополнительной части таблицы, ни о её пределах? Как ни крути, нам все равно нужно изворачиваться, это ни разу не серебряная пуля. Для UN:C это отличный механизм, я не спорю, и плагинам он хотя бы не создает проблем (в отличие от), но я не считаю, что он реально необходим и обязателен. Quote:Хотя в целом решение лежит на поверхности, его даже в patcher нужно было вводить: Redirect + GetAddress пара позволила бы всем договориться и не ссылаться на код.Эм, нет, это не сработает. Потому что читаем данные мы не через патчер, а через *(_type_*). RE: Авторам плагинов - Berserker - 20.11.2020 02:41 feanor, ты прав насчёт конечных размеров структур. У меня вертится идея добавить аналог RedirectMemoryBlock, но с NewSize последним параметром. Сейчас лимиты монстров/героев я получаю по способу HD-мода: считываю значение из какого-нибудь воговского цикла. Если бы был четвёртый параметр, можно было бы писать функции а-ля GetTownLimit => GetStructSize(addr) / SINGLE_ITEM_SIZE. Итого, что мы имеем. На текущий момент у нас есть компромиссно-альтернативный вариант: 1) Плагины читают указатель на таблицы/структуры, не используя прямые адреса. 2) Плагины вызывают GetRealAddr в событии OnAfterStructRelocations. При этом в любом случае о перенесённых структурах сообщают через RedirectMemoryBlock, чтобы Era/ERM UN:C/Lua/православные плагины были счастливы. Возможно, найдутся предложения получше. RE: Авторам плагинов - feanor - 20.11.2020 13:04 Quote:feanor, ты прав насчёт конечных размеров структур. У меня вертится идея добавить аналог RedirectMemoryBlock, но с NewSize последним параметром. Сейчас лимиты монстров/героев я получаю по способу HD-мода: считываю значение из какого-нибудь воговского цикла. Если бы был четвёртый параметр, можно было бы писать функции а-ля GetTownLimit => GetStructSize(addr) / SINGLE_ITEM_SIZE.Не решает проблему с новой частью таблицы. Какой бы не очень надуманный пример придумать.. Вот у нас есть, допустим, код для изменения стрелков в башнях. Там как раз таблица, 36 двордов на девять городов. Есть использующий его скрипт, который в начале боя берет номер осаждаемого города, и допустим, берет-изменяет какие-то данные в этой таблице. Например, заменяет стрелков в зависимости от его отстройки. Все довольны, все работает, Солмир строит Капитолий и получает косметических титанов в башнях. Потом появляется плагин на расширение городов. Он эту таблицу переносит, регистирует редирект блока, заполняет данные десятого города.. И на десятом городе скрипт начинает в лучшем случае не работать (если он правит данные где-то за верхней границей массива, но там ничего важного нет), в обычном - ронять игру (если правит данные за верхней границей массива, но там критичное), в худшем - работать неправильно (если он пытатся брать данные из мусора за верхней границей). Теоретически мы можем зарегистрировать редирект с избытком, захватив область памяти за верхним краем. Тогда UN:C по таблице стрелков десятого города будет отрабатывать правильно. Но это концептуально неправильно, и, что важнее, как минимум (тут уже пошли теоретические вымышленные примеры, надо смотреть) способно грозить проблемами. Не дай бог у нас таблица стрелков упирается во что-то нужное другому скрипту, например, там какая-то другая таблица, или просто какие-то системные данные. Тогда у нас уже другой скрипт будет сходить с ума, причем, в отличие от чистого случая, на отладке вполне можно будет свихнуться. RE: Авторам плагинов - XEPOMAHT - 20.11.2020 13:21 (20.11.2020 13:04)feanor Wrote: Не решает проблему с новой частью таблицы. Резать таблицы в памяти на части - плохая идея. Сразу и оригинальный код слетит к майкрософту, а не только UN:C. Да и любые обращения к таким вещам как к единой таблице будут приводить к ошибкам. Поэтому такие варварские воговско-хотовские методы фрагментации таблиц - зло, которого необходимо избегать на ERA. На MoP например такие вещи вырезаны нафиг. (20.11.2020 13:04)feanor Wrote: Потом появляется плагин на расширение городов. Он эту таблицу переносит, регистирует редирект блока, заполняет данные десятого города.. Отчего ж будет работать не правильно? Редитект блок укажет новый адрес начала этой таблицы. Таблица уже заполнена готовыми данными непосредственно в самой dll и имеет в памяти следующий вид: Code: Town_SiegeTowersArmy = $ Хотим заменить Пиратов на Жриц RE: Авторам плагинов - Berserker - 20.11.2020 17:39 feanor, проблема уже решена новым подходом. 1) В Эру добавлена UN:C с четырьмя параметрами, поддерживающая Базовый адрес + Смещение. GetRealAddr вызывается только для базового адреса, затем прибавляется смещение. 2) В коде стоит вызывать GetRealAddr только для базового адреса. Таким образом даже если за таблицей для 9 городов следует другая таблица для 9 городов проблемы не будет. |