| 
 
				За время, прошедшее с Нового Года, WERD достаточна оформилась и теперь у меня есть о ней чёткое представление. Написал что-то вроде краткой статьи о ней - вдруг кто-то захочет сделать нечто подобное, только на высокоуровневом языке. Система WERD (Without ERM Relating Dynamic) – альтернативная платформа для модостроения в Героях III, чьё использование возможно только в моде Master of Puppets (MoP). Для сторонних мододелов может представлять лишь теоретический интерес в области низкоуровневого программирования.Аббревиатура WERD изначально является анаграммой имени Drew, так как автор платформы страдает ДБГМ и СПГС.
 
 Основу WERD составляет MoP.exe и собственно Werd.dll, написанная на flat assembler-е (fasm) – свободно распространяемом компиляторе языка ассемблера, чьим автором является Tomasz Grysztar.
 
 Упор WERD сделан на скорость и компактность с сохранением достаточной читаемости исходного кода.
 
 Сутью замены ERM является всего один хук, открывающий WERD доступ ко всем ERM-триггерам. По получении номера вызываемого триггера происходит быстрое сканирование (repne scasd) в таблице номеров триггеров. Если найденный триггер соответствует вызванному – вызывается функция, чей адрес расположен в другой таблице (WERD-триггеры) на том же смещении относительно базового адреса блока данных. Таким образом, каждому ERM-триггеру соответствует всего одна процедура.
 
 Исходя из сказанного выше, нетрудно догадаться, что можно дополнительно регулировать скорость вызова отдельных триггерных процедур, просто поставив их в начало таблицы. В первую очередь это – ведение мышью, передвижение героя, клики и прочие очень часто вызываемые триггеры. Впрочем, даже при списке в тысячу триггеров этот процесс не может заметно замедлиться.
 
 Благодаря нужному макросу, две таблицы в исходниках представляют собой одну, что исключает возможность запутаться, какой триггер какой процедуре соответствует.
 
 Удаление или закомментирование триггера в таблице автоматически исключает его из списка компиляции, так как одной из особенностей fasm-а является то, что он не компилирует «мёртвый код» - процедуры, которые нигде не вызываются и даже не присутствуют в виде бинарных данных.
 
 В исходниках каждый триггер начинается в файле \WERD\Begin\Название_триггера.inc объявлением процедуры и заканчивается в файле \WERD\End\Название_триггера.inc её завершением. В первом файле триггер получает данные и кладёт их в свои локальные переменные. В последнем файле могут находиться некоторые специфические данные триггера (особые функции, свитчи и пр.), но чаще всего там просто завершение процедуры.
 
 Так как хук, реализующий WERD-триггеры, сохраняет в стеке регистры процессора, то эти регистры нет нужды пересохранять при вызове триггерной процедуры. WERD-триггер по сути – автономное образование, влияющее на последующий за ним код только через вложенные в него вызовы функций или через глобальные переменные.
 
 Каждому триггеру соответствует файл с листингом include-ов – файлов с кодом, который присоединяется к триггеру в процессе компиляции. Листинг начинается с файла из папки Begin и заканчивается файлом из папки End. Файлы с триггерной «начинкой» находятся в папках, структурированных по игровым элементам.
 
 Файлы с архитектурой триггеров также собраны в список include-ов в файле Werd.inc, определяющем порядок их положения в коде.
 
 Естественным дополнением к этой системе являются описания игровых структур, константы, переменные, строки и различного рода макросы.
 
 Главной достопримечательностью WERD является её нарастающая коллекция переходников к игровым функциям. Если специфические функции WERD расположены в самой библиотеке, то практически весь доступ к игровым процедурам осуществляется через короткую конструкцию push %Адрес функции%; ret. Остальная работа по оптимизации уже проделана в MoP.exe, как то:
 1. Неудобные вог-процедуры с си-соглашением получают stdcall-ные переходники к ним.
 2. Функции с постоянными параметрами (вроде ecx = [постоянный адрес]) обрезаются в переходнике по этим параметрам.
 3. Частая последовательность вызова одних и тех же процедур для такого же постоянного действия (вроде инициализации DL-диалога) оборачивается в одну функцию с минимальным количеством параметров.
 4. Короткие, но важные функции (вроде получения адреса структуры какого-либо игрового объекта или некой математической операции над массивами данных), тоже прописаны в exe.
 Описанный подход неоднозначен. С одной стороны, именно эта особенность, главным образом, делает WERD несовместимой с другим exe, а с другой – обуславливает свободу в пределах данного мода. Сам список переходников обёрнут макросом в таблицу, которая служит и самим кодом, и наглядной документацией к нему (параметры вызова подробно комментируются). Это делает Werd.dll чрезвычайно компактной и нетребовательной. Кроме того: таблица переходников может быть экспортирована целиком в другую длл, если таковая появится в моде, и займёт в ней ничтожное пространство.
 
 WERD поддерживает все необходимые соглашения вызова процедур – stdcall, ccall, pascal, thiscall, fastcall и стандартный вызов API-процедур – invoke.
 
 Большинство констант, функций, структур, переменных и даже самих source-файлов в WERD имеет русские названия. Причём эти названия обычно очень длинные и подробные. Ассемблер вообще к этому приучает…
 
 WERD – очень разветвлённая система. На настоящий момент её исходники состоят из 55 папок с более тремястами файлов при весе самой готовой библиотеки всего чуть более 20 Кб.
 
 За месяц, прошедший с выхода версии мода Seek & Destroy, система WERD поглотила более 1000 строк ERM-кода в различных участках скрипта, при этом едва увеличив свой объём вдвое.
Пример кода, реализующего то, что изображено на скрине выше. Блоки, расположенные в разных файлах, отделены коммент-символами.
 
Code:
 ; Правый клик на карте приключенийinclude 'WERD\Begin\Правый клик на карте приключений.inc'
 .if signed Активный_герой > -1
 include 'WERD\Переименование героя и города\Клик в окне статуса - переименование героя.inc'
 include 'WERD\XL-портреты\Выбор в окне статуса.ASM'
 include 'WERD\Показ бонусов Морали и Удачи героя при кликах в окне статуса\Показ бонусов Морали и Удачи героя при кликах в окне статуса - правый клик.ASM'
 .endif
 .if signed Активный_город > -1
 include 'WERD\Переименование героя и города\Клик в окне статуса - переименование города.inc'
 include 'WERD\Покупка существ в городе\Подсказка к кнопке Купить Всех в окне статуса.ASM'
 .endif
 .if [Тип_объекта_в_кликнутой_клетке] = 20 | [Тип_объекта_в_кликнутой_клетке] = 17
 include 'WERD\Улучшенные подсказки по внешним жилищам\Улучшенные подсказки по внешним жилищам.ASM'
 include 'WERD\Улучшение поселения\Улучшение поселения.ASM'
 .endif
 include 'WERD\Сброс артефактов на карту\Подсказка к Мешку по ПКМ.ASM'
 include 'WERD\Показ количества Дуалита\Показ количества Дуалита.ASM'
 include 'WERD\Объект Древо Жизни\Подсказка по ПКМ.ASM'
 include 'WERD\Магистрат\Подсказка по ПКМ.ASM'
 include 'WERD\RB\Подсказка по ПКМ.ASM'
 include 'WERD\Подсказки к артефактам на карте приключений\Подсказка по ПКМ.ASM'
 include 'WERD\End\Правый клик на карте приключений.inc'
 ; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
 .if Область_клика = Окно_статуса_по_городу_!_Иконка_Форта & Подтип_клика = Нажата_правая_кнопка_мыши
 stdcall Подсказка_к_кнопке_Купить_Всех, [Структура_активного_города]
 ret
 .endif
 ; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
 proc Подсказка_к_кнопке_Купить_Всех uses ebx esi edi, TownStr
 
 locals
 Ресурсы_игрока dd 7 dup (?)
 Тип_существ dd ?
 Количество_существ dd ?
 Номер_города dd ?
 endl
 
 Отмена_стандартной_реакции_клика
 mov eax, Структура_текущего_игрока
 stdcall Копирование_памяти, addr eax + Структура_игрока.Дерево, addr Ресурсы_игрока, 4*7
 mov ebx, [TownStr]
 movsx eax, [ebx + Структура_города.Номер_города]
 mov [Номер_города], eax
 rv esi, stdcall, Инициализация_игрового_диалога, Диалог_подсказки_к_кнопке_Купить_Всех, _Шаблон_диалога_подсказки_к_кнопке_Купить_Всех
 stdcall Настройка_элемента_игрового_диалога, esi, 100, 13, Текущий_игрок
 ccall Преобразование_текста_с_включёнными_подстроками, Буфер_для_текста, стр 452 MoPSpec, [ebx + Структура_города.Название]
 stdcall Настройка_элемента_игрового_диалога, esi, 101, 3, Буфер_для_текста
 ; Настройка верхних слотов
 mov edi, 6
 .Цикл_слотов:
 fastcall Проверка_доступности_существа_в_городе, [Номер_города], edi
 .if word [ERM_Flag_2]
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 201, 9, mwcrport_def
 lea eax, [edi * 2 + ebx + Структура_города.Количество_улучшенных_существ_0]
 mov ecx, [Номер_города]
 movsx edx, [ebx + Структура_города.Тип]
 Умножить edx, 56
 Умножить ecx, 504
 add ecx, edx
 lea edx, [edi * 4 + ecx + Структура_типов_обитателей_для_каждого_города + 28]
 .if ~byte [ERM_Flag_3]
 sub eax, 14
 sub edx, 28
 .endif
 movsx eax, word [eax]
 mov [Количество_существ], eax
 m2m dword [edx], [Тип_существ]
 stdcall Конвертирование_числа_в_текст, eax, Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 31, 3, Буфер_для_текста
 mov edx, [Тип_существ]
 add edx, 2
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 201, 4, edx
 ; Настройка нижних слотов
 stdcall Вычисление_количества_нанимаемых_монстров, [Тип_существ], [Количество_существ], addr Ресурсы_игрока
 .if eax
 stdcall Конвертирование_числа_в_текст, eax, Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 131, 3, Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 301, 9, mwcrport_def
 mov edx, [Тип_существ]
 add edx, 2
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 301, 4, edx
 .endif
 .endif
 dec edi
 jge .Цикл_слотов
 ; Настройка Портала Вызова (верхний слот)
 mov eax, Размер_структуры_городских_реликвий
 imul eax, [Номер_города]
 Проверка_бита [ebx + Структура_города.Биты_строений1], Бит_Портал_Вызова
 .if CARRY? & [ebx + Структура_города.Тип] = Темница | [eax + Адрес_структуры_городских_реликвий + Структура_городских_реликвий.Тёмный_Круг_Баа_за_Дерис]
 stdcall Настройка_элемента_игрового_диалога, esi, 208, 9, mwcrport_def
 mov eax, [ebx + Структура_города.Тип_существа_в_Портале_Вызова]
 mov [Тип_существ], eax
 add eax, 2
 stdcall Настройка_элемента_игрового_диалога, esi, 208, 4, eax
 movsx eax, [ebx + Структура_города.Количество_существ_в_Портале_Вызова]
 mov [Количество_существ], eax
 stdcall Конвертирование_числа_в_текст, eax, Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, 38, 3, Буфер_для_текста
 ; Настройка Портала Вызова (нижний слот)
 stdcall Вычисление_количества_нанимаемых_монстров, [Тип_существ], [Количество_существ], addr Ресурсы_игрока
 .if eax
 stdcall Конвертирование_числа_в_текст, eax, Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, 138, 3, Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, 308, 9, mwcrport_def
 mov edx, [Тип_существ]
 add edx, 2
 stdcall Настройка_элемента_игрового_диалога, esi, 308, 4, edx
 .endif
 .endif
 mov edi, 6; счётчик ресурсов
 .Цикл_ресурсов:
 stdcall Конвертирование_числа_в_текст, [edi * 4 + Ресурсы_игрока], Буфер_для_текста
 stdcall Настройка_элемента_игрового_диалога, esi, addr edi + 63, 3, Буфер_для_текста
 dec edi
 jge .Цикл_ресурсов
 thiscall Показ_игрового_диалога_Pop_Up, Диалог_подсказки_к_кнопке_Купить_Всех
 ret
 endp
 
 proc Вычисление_количества_нанимаемых_монстров uses ebx edi, тип, количество, адрес_строки_вычитания_ресурсов
 xor edi, edi; счётчик существ, которые могут быть наняты
 mov ebx, [тип]
 Умножить ebx, Размер_структуры_монстра
 lea ebx, [ebx + Адрес_структуры_монстров + Структура_монстра.Цена_в_дереве]
 .while signed edi < [количество]
 stdcall Сравнение_значений_двух_массивов_двойных_слов, ebx, [адрес_строки_вычитания_ресурсов], 7
 test eax, eax
 je .недостаточно_ресурсов
 inc edi
 fastcall Вычитание_массива_из_массива, [адрес_строки_вычитания_ресурсов], ebx, 2, 7
 .endw
 .недостаточно_ресурсов:
 mov eax, edi
 ret
 endp
 Circle of destruction, hammer comes crushing
 Powerhouse of energy
 Whipping up a fury, dominating flurry
 We create the battery
 
				
(This post was last modified: 22.02.2012 20:40 by MOP.)
 |