За время, прошедшее с Нового Года, 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.)
 
			 
		 |