Цитата
Берсерк, а где можно поподробнее про твою работу, а то видел ты в разных ветках форума выкладываешь по чуть-чуть информации.
Желательно бы с примерами.
Прежде всего, если можно, то Ангел. В крайнем случае Берсеркер/Берс, от Берсерка коробит
Что касается информации, то опишу очень кратко и применимо к последней версии.
I. Эра основана на
ехешнике версии ТЕ. Из статического патчинга только исправление пары злостных ошибок ЕРМ, вроде отключения скриптов или проблем с дорогами. Также пропатчено место после того, как ZVS заканчивает патчить ехе с переходом на код, вызывающий библиотеку Angel.dll, которая и реализует дальнейший функционал.
II. Angel.dll инициализирует модули, каждый из которых патчит нужные для работы места в ехе. Затем управление возвращается игре.
III. Взаимодействие Эры с ЕРМ довольно прозрачно, хотя кодить будет удобнее с новым компилятором, поддерживающим полноценные макросы. Для реализации взаимодействия ЕРМ и кода использован триггер и ресивер SN. Они самые редко используемые и не загруженные бесконечными case-ами.
Переменные
v50-v99 зарезервированны под нужды Эры (хотя в исходниках и можно произвольно изменить стартовый индекс).
v50 используется как команда ядру Эры. ЕРМ вызывает триггер SN через ресивер проигрывания звука
SN:Pz1. Процедура
PlaySound перехвачена и управление получает Эра. Далее анализируется содержимое v50. Если это
0 (стандартное значение), значит проигрывается звук, вызывается ЕРМ триггер. В общем всё так, как будто никто ничего не менял.
Возможные значения v50 (и параметров: соответственно v51, v52...)
Код
[1 - LoadLibrary (команда обнуляется)]
Name: ZIndex; VAR Result: VIndex;
[2 - GetProcAddress (команда обнуляется)]
hModule: INTEGER; ProcName: ZIndex; Result: VIndex;
[3 - Call Proc (команда обнуляется)]
Proc: POINTER; CallConversion: TCallConvType; Params: TParams;
[4 - вызов PlaySound без ЕРМ-триггера. (команда обнуляется)]
Другое: - вызвать !?SN-триггер без вызова функции PlaySound (команда не обнуляется). Используется для реализации пользовательских событий, но в основном - для реализации произвольного кол-ва новых триггеров.
Тут же стоит отметить, что код ядра написан на чистом ассемблере и быстр настолько, насколько это возможно.
В частности, вы вызываете !!VR:C и инициализируете сразу все параметры. Быстрее через ЕРМ это можно сделать лишь отдельной командой, что, правда, лишь ещё больше раздует ЕРМ case-ми, и приведёт лишь к мизерному приросту производительности.
Далее вы вызываете !!SN:Pz1, что очень быстро приводит нас к PlaySound, а там анализ умещается в пару десятков строк, причём сверху asm-овских case-ов идут самые частоиспользуемые команды.
Пояснения:
Код
TCallConvType = (CDECL = 0, PASCAL = 1, STDCALL = 2);
В новой версии добавятся ещё FastCall и, возможно, ThisCall (хотя врядли последнее будет использоваться).
TParams = RECORD
Count: INTEGER; // Кол-во параметров
Params: ARRAY Count OF INTEGER; // Сами параметры
END;
Последний список изменений уже намусолил всем глаза:
Цитата
1) Деактивированы проверки CRC
2) ЕРМ больше не отключает скрипты при ошибке
3) Исправлен баг с установкой дорог через ЕРМ
4) Возможность сохранять игру через ЕРМ
5) Событие: Игра Сохранена (если сохраняли через ЕРМ); #100
6) Событие: Запись данных в сейв; #101
7) PROCEDURE GZipWrite(Address: POINTER; Count: INTEGER); CDECL; AT $704062;
8) PROCEDURE GZipRead(Address: POINTER; Count: INTEGER); CDECL; AT $7040A7;
9) Событие: Загрузка данных из сейва; #102
10) Опция: Отключить вопрос "Вы действительно хотите загрузить игру?"; #0
11) Событие: Нажатие Клавиши - полный контроль реакции через ЕРМ; #103
12) Событие: Вход в Окно Героя; #104
13) Событие: Выход из Окна Героя; #105
14) Исправление триггера ЕРМ !?HE на универсальный, где v51 и v52 - инициатор и цель соответственно, а сам триггер = !?HE199
15) Событие: Контроль над очерёдностью хода в битве; #106
16) Опция: Отключение ERM таймера (ускоряет игру); #1
17) Опция: Грааль не даёт все спелы; #2
18) Событие: До действия (пример: регенерация троллей) - полный контроль через ЕРМ; #107
Для поддержки
плагинов Эра после своей инициализации до передачи управления игре загружает библиотеки, имена которых прописаны в файле EraPlugins.dat в формате 1 имя на 1 строку. В конце обязательно наличие символов #13#10. В пользовательских dll можно произвести нужный
динамический патчинг.
Функции для Паскаля/Делфи:
Код
// Assume Win=Windows
CONST
(* HookCode constants *)
C_HOOKTYPE_JUMP = FALSE;
C_HOOKTYPE_CALL = TRUE;
C_OPCODE_JUMP = $E9;
C_OPCODE_CALL = $E8;
C_UNIHOOK_SIZE = 5;
TYPE
(* Запись, необходимая для работы с функциями патчинга *)
THookRec = RECORD
Opcode: BYTE;
Ofs: INTEGER;
END; // .record THookRec
VAR
Temp: INTEGER; // Универсальная временная переменная
(* *)
PROCEDURE WriteAtCode(P: POINTER; Buf: POINTER; Count: INTEGER);
BEGIN
Win.VirtualProtect(P, Count, PAGE_READWRITE, @Temp);
Win.CopyMemory(P, Buf, Count);
Win.VirtualProtect(P, Count, Temp, NIL);
END; // .procedure WriteAtCode
PROCEDURE HookCode(P: POINTER; NewAddr: POINTER; UseCall: BOOLEAN);
VAR
HookRec: THookRec;
BEGIN
IF UseCall THEN BEGIN
HookRec.Opcode:=C_OPCODE_CALL;
END // .if
ELSE BEGIN
HookRec.Opcode:=C_OPCODE_JUMP;
END; // .else
HookRec.Ofs:=INTEGER(NewAddr)-INTEGER(P)-C_UNIHOOK_SIZE;
WriteAtCode(P, @HookRec, 5);
END; // .procedure HookCode
Интерфейс:
Код
(* WriteAtCode *) {Записывает данные из буфера в указанное место}
PROCEDURE WriteAtCode(P: POINTER; Buf: POINTER; Count: INTEGER);
(* HookCode *) {Патчит указанное место 5-байтовым хуком на новое, адрес которого указывается в параметре. Хук может быть вызовом или прыжком}
PROCEDURE HookCode(P: POINTER; NewAddr: POINTER; UseCall: BOOLEAN);
IV. Применение на практике.
1. Использование новых событий. В текущей версии обязательно, иначе будут происходить очень странные вещи. В новых версиях всё будет несколько дружелюбнее. Поехали.
[События нажатии клавиши]Код
Параметры: 103 (код события)/Код клавиши
Нужно вернуть: v52: Block? (Boolean); Блокировать игре доступ к событию или нет.
События нажатия клавиши очень тонкое и требует бережного обращения. Представьте себе, что вы желаете изменить стандартную реакцию нажатия на 'L' (Load, загрузка сейва). Тут же нарисовываются несколько проблем. Первая - нет возможности определить, в каком диалоге мы находимся. Это карта приключений, битва, опции...А может сейчас отображается обычное сообщение через !!IF:M? Второе, в ЕРМ нет понятия стёка. Так, событие привело нас на код ЕРМ, что вызывает диалог, который тоже порождает событие, которое вызывает ещё один ЕРМ диалог...В это время куча переменных по сто раз меняют своё значение!
В итоге код должен быть довольно гибким. Ниже представлен каркас для последующих примеров. Это лишь мой вариант, каждый может реализовать всё удобнее для себя. В нём предполагается, что скрипт00 - единственный в папке, а также то, что во время показа стандартных диалогов не нужно перехватывать нажатия клавиш.
Код
v20 - сохранённый код команды
v21..v39 - сохранённые параметры события
v40 - флаг ЕРМ-диалога
!#VRv40:S0;
!?SN; Универсальный триггер
!!VRv20:Sv50; Сохранили код команды в v20
!?SN&v20=103; Событие нажатия любой клавиши
!!VRv52:S0; Игра по умолчанию получит событие
!!if&v40=1:; Если в данный момент отображается ЕРМ-диалог, то событие не должно идти дальше по триггерам
!!VRv20:S1; Номера событий начинаются со 100, так что 1 значит - нет события
!!FU:E;
!!en:;
!!VRv21:Sv51; Сохранили код клавиши в v21
Знаете, играл в Mount & Blade. Игровой движок там на Питоне. Тормозит каждое простейшее меню, а у скриптописцев возможности практически на нуле. Эру это не касается, даже перехваты клавиш происходят очень быстро и пользователь мгновенно увидит работу скрипта, как будто это игровой код, а не ЕРМ+DLL.А теперь код простейшего скрипта в стиле Реквизита. Нажатие на клавишу 'L' не вызывает вопрос "Вы действительно хотите загрузить игру?" и т.д, а выводит сообщение с просьбой перечислить 5 у.е на их счёт. Поехали:
Код
!?SN&v20=103/v21=76; Событие нажатия клавиши 'L', #76
!!VRv40:S1; Сейчас мы будем отображать диалог ЕРМ и не хотим в это время перехватывать нажатия клавиш
!!IF:M^Хотите загрузить игру?
Данные потеряются и т.д.?
Перечислите 5 у.е на счёт {Реквизита}.
Не хотите? Тогда грузитесь через {Опции}, блин.^;
!!VRv40:S0; Диалог уже закрыт
!!VRv52:S1; А вот событие доставлять игре не нужно
!!VRv20:S1; Равно как и давать управление другим перехватчикам события
На тестовой карте (ссылки в конце поста) нажмите L и увидите реакцию.
А теперь давайте напишем другой скрипт. При нажатии на клавишу пользователя спрашивают, отсылать сообщение игре за 5.уе или нет. При ответе да следует стандартная реакция игры, при ответе нет - ничего не происходит.
Код
!?SN&v20=103; Событие нажатия любой клавиши
!!VRv40:S1; Сейчас мы будем отображать диалог ЕРМ и не хотим в это время перехватывать нажатия клавиш
!!IF:Q2^Хотите, чтобы игра обработала нажатие на клавишу?
Перечислите 5 у.е на счёт {Реквизита}.
Заранее благодарим.^;
!!VRv40:S0; Диалог уже закрыт
!!VRv52:S1; А вот событие доставлять не нужно
!!VRv52&2:S0; Или нужно? :-)
!!VRv20:S1; Не нужно давать управление другим перехватчикам события
[События нажатии клавиши]Код
Параметры: 103 (код события)/Код клавиши
Нужно вернуть: v52: Block? (Boolean); Блокировать игре доступ к событию или нет.
Пожалуй, для полноты рассмотрим ещё одно событие с примером. Для остальных будет просто описание.
[Событие: Контроль над очерёдностью хода в битве]Код
Параметры: 106 (код события)/Сторона/Номер стёка 0..20
Нужно вернуть: v51 = Сторона/v52 = Номер стёка
Тестовый скрипт позволит нам на тестовой карте выигрывать любые сражения за...1 ход. Почти любые. У героя имеется один стёк - 50 арбалетчиков. Пусть они всегда ходят (считается, что МЫ всегда нападает на противника). Таким образом мы попросту растреляем армию противника, не дав кому-то походить.
; Все клавишные события направляются игре
Код
!?SN&v50=103;
!!VRv51:S0;
!?SN&v50=106; Событие: кто ходит?
!!VRv51:S0; Сторона - левая (0 - левая, 1 - правая)
!!VRv52:S0; Номер стёка - 0 (у нас это арбалетчики и 1 - командир, если есть)
Как видите, всё довольно просто и внутренняя рутина скрыта от глаз скриптописцев.
[Событие: Игра сохранена]Код
Параметры: 100 (код события)
[Событие: Запись данных в сейв]Код
Параметры: 101 (код события)
Удобные функции:
Код
PROCEDURE GZipWrite(Address: POINTER; Count: INTEGER); CDECL; AT $704062;
[Событие: Загрузка данных из сейва]Код
Параметры: 102 (код события)
Удобные функции:
Код
PROCEDURE GZipRead(Address: POINTER; Count: INTEGER); CDECL; AT $7040A7;
[Событие: Вход в Окно Героя]Код
Параметры: 104 (код события)
[Событие: Выход из Окна Героя]Код
Параметры: 105 (код события)
[Событие: До действия (пример: регенерация троллей)]Код
Параметры: 107 (код события)/Номер стёка 0..41/Адрес структуры TBattleMonster
Описание TBattleMonster я позаимствовал из исходников ЕРМ и прилагаю к материалам данного кратчайшего руководства.
V. Опции Эры.На данный момент их три:
Код
C_ERA_OPTION_LOADGAME_QUESTION = 0; // Опция отключения вопроса о загрузке игры
C_ERA_OPTION_ERMTIMER_ONOFF = 1; // Опция отключения ERM таймера для ускорения игры
C_ERA_OPTION_CONFLUXGRAILALLSPELLS_ONOFF = 2; // Опция отключения свойства грааля в Сопряжении по обучению всем спелам
Изменяются через экспортируемую Angel.dll функцию:
Код
SetOption(ID: INTEGER; Value: INTEGER);
Цитата
Я так вижу из твоих наработок можно ещё 1 оч неплохую идею ТЕшников реализовать - запароленые сейвы. Так же видимо можно будет написать встроенный автосейвкаунтер.
Запароленные сейва только через Эру+ЕРМ - невозможно. Хотя, если нужно всего лишь запретить загрузку сейва, то есть вариант. Правда, загрузив сейв через другой ехе, этот вариант обойдут.
Что касается столь сложного слова автосейвкаунтера, то гораздо проще просто автосейвы по дням + в начале хода, что элементарно, но тестировалось на сингле и для сингла.
Цитата
calling_conversion: 0 - stdcall, 1 - thiscall, 2 - fastcall
А где Pascal? Или его уже отожествляют с Делфи, а FreePascal, TMTPascal, Virtual Pascal и прочее забыты?

Цитата
Кое-какие изменения уже внёс (спасибо Берсерку за разлоченный экзешник)
Взял термин на вооружение
Цитата
Поэтому с Турбо-Паскалем ковыряюсь
Старик ещё жив???
[ВЛОЖЕНИЯ]Тестовая карта: Высокомерие =
СкачатьEra_6_12_08.rar