Добавлен , опубликован
Программы
Предназначение:
Прочее
Порой, имеющегося функционала игры не хватает, а реализовывать дополнительные возможности посредством JASS-сценариев с мемхаком неудобно да и результат не может похвастаться высокой производительностью.
В таком случае, может помочь добавление собственных нативных функций, которые будут написаны на более подходящих языках программирования, а то и вовсе поместить весь сценарий во внешнюю библиотеку.
Исходники с примером использования этой библиотеки можете скачать здесь.
заголовочный файл
unit jassapi;

{$mode objfpc}{$H+}

interface

const
	JASS_API_DLL_NAME = 'JassApi.dll';

type
	PJassString = Pointer;

	TJassStringHandle = Dword;
	TJassObjectHandle = Dword;
	TJassFunctionHandle = Dword;

	TSingle = record Value: Single end;

	TJassEventType = (
		jeMainBeingCalled = 0,
		jeMainThreadDestruction = 1,
		jeMainThreadDestroyed = 2
	);
	TJassEventCallback = procedure (Parameter: Pointer); stdcall;
	TEventRegistrationHandle = Dword;

function RegisterJassNative(Name: PChar; Signature: PChar; Address: Pointer): LongBool; stdcall; external JASS_API_DLL_NAME;
function UnregisterJassNative(Name: PChar): LongBool; stdcall; external JASS_API_DLL_NAME;
function GetJassNative(Name: PChar): Pointer; stdcall; external JASS_API_DLL_NAME;

function GetStringHandle(Value: PChar): TJassStringHandle; stdcall; external JASS_API_DLL_NAME;
function GetStringFromHandle(StringHandle: TJassStringHandle): PChar; stdcall; external JASS_API_DLL_NAME;
function CreateJassString(AContent: PChar): PJassString; stdcall; external JASS_API_DLL_NAME;
procedure DestroyJassString(AString: PJassString); stdcall; external JASS_API_DLL_NAME;
procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall; external JASS_API_DLL_NAME;
function GetJassStringContent(JassString: PJassString): PChar; stdcall; external JASS_API_DLL_NAME;
procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall; external JASS_API_DLL_NAME;
procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall; external JASS_API_DLL_NAME;

procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; external JASS_API_DLL_NAME;
procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; external JASS_API_DLL_NAME;

function AddEventHandler(EventType: TJassEventType; Callback: TJassEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall; external JASS_API_DLL_NAME;
function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall; external JASS_API_DLL_NAME;
procedure PostEvent(EventType: TJassEventType); stdcall; external JASS_API_DLL_NAME;

implementation

end.
список изменений
  • 03.06.2023
    • Изменения в RedirectCalls
      • В случае, если мод был загружен прямо во время игры, он конвертирует всё ранее созданые функции из кэша в новый формат.
      • Если мод загружен второй раз, что может произойти при одновременной установке его в папку с игрой и встраивании в карту, игра не будет модифицироваться повторно.
  • 04.04.2023
    • Добавил функции CreateJassString, DestroyJassString и SetJassStringContent на замену небезопасной функции GetJassString.
  • 02.04.2023
    • Добавил событие происходящее после уничтожения главного потока.
    • Дополнительный мод, позволяющий использовать нативки, принимающие параметры с типом code.
  • 18.03.2023
    • Не ожидал, что функция из стандартной библиотеки для получения индекса элемента в словаре выбросит исключение. Игра больше не должна падать.
    • Исправил неинициализирвоанные локальные переменные в UnregisterJassNative, GetJassNative.
    • Функции для работы со строками теперь проверяют индексы перед использованием.

JASS API

С этим вам поможет библиотека, которая предлагается к скачиванию в этом ресурсе.
Она предоставляет набор функций, достаточный для достижения поставленной цели.
В примерах используются JASS и Pascal.

Работа с нативными функциями

Вызов нативных функций

Для вызова нативных функций используется конвенция вызова cdecl.
Следующая таблица покажет, какие типы принимает или возвращает нативка, в зависимости от типов аргументов и результата, указаных при её объявлении в JASS-скрипте.
Таблица типов
JASS типТип параметраТип результата
integerInt32Int32
realPSingleTSingle (костыль, чтобы значение возвращалось как и целые числа, через регистр процессора EAX, а не через специальный стэк FPU)
booleanLongBoolLongBool
stringPJassStringTJassStringHandle
handleTJassObjectHandleTJassObjectHandle
codeTJassFunctionHandleУказатель на байт-код (на деле нет ни одной нативки, что возвращает такое значение)
Нативки увеличивают счетчик ссылок возвращаемых строк, поэтому не забудьте уменьшить его функцией DecrementStringReferences, когда строка уже не будет нужна.

Register​Jass​Native

function RegisterJassNative(Name: PChar; Signature: PChar; Address: Pointer): LongBool; stdcall;
Регистрирует нативную функцию с адресом Address под именем Name и сигнатурой Signature (Подробнее о сигнатурах).
Пример
Предположим, имеется следующая функция, которую нужно добавить в игру:
function SetMinimapImage(FilePath: String): Boolean;
В таком случае, для неё нужно будет сделать адаптированную версию:
function jass_SetMinimapImage(FilePath: PJassString): LongBool; cdecl;
begin
	Result:= SetMinimapImage(GetJassStringContent(FilePath));
end;
И затем зарегистрировать:
RegisterJassNative('SetMinimapImage', '(S)B', @jass_SetMinimapImage);
Теперь в игре можно будет воспользоваться ею:
native SetMinimapImage takes string FilePath returns boolean

Unregister​Jass​Native

function UnregisterJassNative(Name: PChar): LongBool; stdcall;
Удаляет функцию с именем Name из списка зарегистрированных.
Если функция была ранее зарегистрирована, то возвращает True, а иначе False.
Пример
UnregisterJassNative("SetMinimapImage");

GetJassNative

function GetJassNative(Name: PChar): Pointer; stdcall;
Возвращает указатель на нативную функцию с именем Name.
В случае, если функция с указанным именем не была найдена, возвращает нулевой указатель.
Пример
type
	TSetMinimapImageFunction = function (FilePath: PJassString): LongBool; cdecl;

var
	pSetMinimapImage: TSetMinimapImageFunction;
	jFilePath: PJassString;

Pointer(pSetMinimapImage):= GetJassNative("SetMinimapImage");
jFilePath:= CreateJassString('war3mapImported/minimap.blp');
pSetMinimapImage(jFilePath);
DestroyJassString(jFilePath);

Работа со строками

GetStringHandle

function GetStringHandle(Value: PChar): TJassStringHandle; stdcall; 
Возвращает хэндл строки со значение Value в таблице строк главного потока.
Если такой строки не существует, то создается новая.
Не увеличивает счетчик ссылок, так что, прежде чем возвращать хэндл скрипту, следует воспользоваться функцией IncrementStringReferences. В оригинальном интерпретаторе строки продолжат утекать, но с альтернативным эта проблема будет устранена.
Пример
Если нужно создать нативку, возвращающую строку скрипту.
function jass_GetMinimapImage: TJassStringHandle;
begin
	Result:= GetStringHandle(FilePath);
	IncrementStringReferences(Result);
end;

RegisterJassNative('GetMinimapImage', '()S', @jass_GetMinimapImage);
В карте:
native GetMinimapImage takes nothing returns string

Get​String​From​Handle

function GetStringFromHandle(StringHandle: TJassStringHandle): PChar; stdcall;
Возвращает указатель на содержимое строки с хэндлом StringHandle из таблицы главного потока.
Если главный потока еще не был создан или в его таблицы нет такого хэндла, то возвращается нулевой указатель.
Пример
Если нужно воспользоваться строкой, возвращенной другой нативной функцией.
procedure OnChatMessage;
var
	hMessage: TJassStringHandle;
	Message: PChar;
begin
	hMessage:= pGetEventPlayerChatString();
	Message:= GetStringFromHandle(hMessage);

	case Message of
		'-start': Start();
		'-stop': Stop();
	end;
end;

Create​Jass​String

function CreateJassString(AContent: PChar): PJassString; stdcall;
Создает новый объект-строку с указанным содержимым AContent и возвращает указатель на него.
Такие объекты затем можно передавать нативкам, требующим строки.
Примечание: функция использует объектный пул, поэтому можете не переживать о падении производительности из-за частого создания и уничтожения строк.
Пример использования смотрите в описании функции GetJassNative.

Destroy​Jass​String

procedure DestroyJassString(AString: PJassString); stdcall;
Уничтожает объект-строку AString.
Пример использования смотрите в описании функции GetJassNative.

Set​Jass​String​Content

procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall;   
Устанавливает значение строки AString равным AContent.
Не используйте её для изменения значения строковых параметров нативных функций.

Get​Jass​String​Content

function GetJassStringContent(JassString: PJassString): PChar; stdcall;
Извлекает строку из элемента таблицы строк главного потока, на который ссылается JassString.
Применяется для извлечения строк из получаемых аргументов в нативных функциях.
Пример смотрите в описании функции RegisterJassNative.

Increment​String​References

procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Увеличивает счетчик ссылок у строки с хэндом StringHandle.
Для того, чтобы гарантировать что строка, хэндлом которой вы владеете, не будет внезапно уничтожена, необходимо увеличить её счетчик ссылок.

Decrement​String​References

procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Уменьшает счетчик ссылок у строки с хэндом StringHandle.
Если счетчик упадет до нуля, то строка будет уничтожена и её хэндл будет переиспользован.
Если ранее вы увеличили счетчик ссылок строки, но теперь она вам уже не нужна, то важно уменьшить её счетчик, а иначе она никогда не будет уничтожена.

Хэндлы

Increment​Handle​References

procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall;
Увеличивает счетчик ссылок на хэндл ObjectHandle, препятствуя его преждевременному уничтожению.

Decrement​Handle​References

procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; 
Уменьшает счетчик ссылок на хэндл ObjectHandle.
При падении счетчика до нуля, хэндл освобождается и может быть переиспользован.
Также, в некоторых случаях, агент, скрывающийся за ним, также может быть уничтожен.

Полезные события

AddEventHandler

function AddEventHandler(EventType: TJassEventType; Callback: TEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall;
Добавляет в реестр функцию Callback и вызывает её с параметром Parameter при событии EventType.
Всегда создает новую запись и возвращает её хэндл.
Возможные события:
ИмяОписание
jeMainBeingCalledСобытие создается перед вызовом функции "main" в главном потоке. Здесь можно инициализировать какие-либо данные перед игрой.
jeMainThreadDestructionСобытие создается перед уничтожением главного потока. Может пригодиться для очистки накопившихся за прошедшую игру данных.
jeMainThreadDestroyedСобытие создается после уничтожения главного потока. Не вызывайте нативные функции на этой стадии.

Remove​Event​Handler

function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall;
Отменяет подписку с хэндлом ID, который был возвращен функцией AddEventHandler.

PostEvent

procedure PostEvent(EventType: TJassEventType); stdcall;
Уведомляет всех подписчиков, зарегистрированных при помощи функции AddEventHandler о событии EventType.
Используется внутри библиотеки для создания событий, но может пригодится и пользователям.
Например, если библиотека JassApi.dll и другие, что добавляют нативки, были загружены после вызова главной функции, то событие уже не произойдет и они не инициализируются.
В таком случае, можно вручную создать событие:
PostEvent(jeMainBeingCalled);

Передача функций в качестве параметров

Для того, чтобы создавать более сложные нативные функции или сценарии, может потребоваться получать события от игры, но, в качестве действий, триггеры принимают только JASS-функции.
Эту проблему решает дополнительный мод RedirectCalls. Смените расширение файла из архива на ".mix" и киньте в папку с игрой, после чего все нативки начнут принимать в качестве параметров указатели на внешние функции (указатель на машинные инструкции реального процессора).
При этом JASS-скрипт продолжает работать как ни в чем не бывало.
Поддерживается загрузка в процессе игры: мод откорректирует ранее созданые функции.
Также, не будет проблем, если мод одновременно и установлен в папку с игрой и встроен в загружаемую карту: активным будет лишь первый загруженый экземпляр.
Становится возможным создавать триггеры на равне со скриптом.
Пример нативного скрипта, использующего триггеры.
В ответ на сообщение "hello" от игрока красного пишет "Good bye.".
library NativeScript;

{$mode objfpc}{$H+}

uses
	jassapi;

type
	TAgentHandle = Dword;
	TPlayerHandle = TAgentHandle;
	TTriggerHandle = TAgentHandle;
	TTriggerActionHandle = TAgentHandle;
	TEventHandle = TAgentHandle;

	TNativeScriptActionFunction = procedure;
	TNativeScriptConditionFunction = function: LongBool;

var
	pPlayer: function (number: Int32): TPlayerHandle; cdecl;
	pDisplayTimedTextToPlayer: procedure (toPlayer: TPlayerHandle; x, y, duration: PSingle; message: PJassString); cdecl;
	pCreateTrigger: function: TTriggerHandle; cdecl;
	pTriggerAddAction: function (whichTrigger: TTriggerHandle; actionFunc: TNativeScriptActionFunction): TTriggerActionHandle; cdecl;
	pTriggerRegisterPlayerChatEvent: function (whichTrigger: TTriggerHandle; whichPlayer: TPlayerHandle; chatMessageToDetect: PJassString; exactMatchOnly: LongBool): TEventHandle; cdecl;

	hChatTrigger: TTriggerHandle;

function Player(Number: Integer): TPlayerHandle;
begin
	Result:= pPlayer(Number);
end;

procedure DisplayTimedTextToPlayer(toPlayer: TPlayerHandle; x, y, duration: Single; message: String);
var
	jMessage: PJassString;
begin
	jMessage:= CreateJassString(PChar(Message));
	pDisplayTimedTextToPlayer(toPlayer, @x, @y, @duration, jMessage);
	DestroyJassString(jMessage);
end;

function CreateTrigger: TTriggerHandle;
begin
	Result:= pCreateTrigger();
end;

function TriggerAddAction(whichTrigger: TTriggerHandle; actionFunc: TNativeScriptActionFunction): TTriggerActionHandle;
begin
	Result:= pTriggerAddAction(whichTrigger, actionFunc);
end;

function TriggerRegisterPlayerChatEvent(whichTrigger: TTriggerHandle; whichPlayer: TPlayerHandle; chatMessageToDetect: String; exactMatchOnly: Boolean): TEventHandle;
var
	jChatMessageToDetect: PJassString;
begin
	jChatMessageToDetect:= CreateJassString(PChar(chatMessageToDetect));
	Result:= pTriggerRegisterPlayerChatEvent(whichTrigger, whichPlayer, jChatMessageToDetect, exactMatchOnly);
	DestroyJassString(jChatMessageToDetect);
end;

procedure OnChatMessage;
begin
	DisplayTimedTextToPlayer(Player(0), 0.0, 0.0, 60.0, 'Good bye.');
end;

procedure InitTrigger;
begin
	hChatTrigger:= CreateTrigger();
	IncrementHandleReferences(hChatTrigger);
	TriggerAddAction(hChatTrigger, @OnChatMessage);
	TriggerRegisterPlayerChatEvent(hChatTrigger, Player(0), 'hello', True);
end;

procedure InitNatives;
begin
	Pointer(pPlayer):= GetJassNative('Player');
	Pointer(pDisplayTimedTextToPlayer):= GetJassNative('DisplayTimedTextToPlayer');
	Pointer(pCreateTrigger):= GetJassNative('CreateTrigger');
	Pointer(pTriggerAddAction):= GetJassNative('TriggerAddAction');
	Pointer(pTriggerRegisterPlayerChatEvent):= GetJassNative('TriggerRegisterPlayerChatEvent');
end;

procedure OnInit({%H-}Parameter: Pointer); stdcall;
begin
	InitNatives;
	InitTrigger;
end;

begin
	AddEventHandler(jeMainBeingCalled, @OnInit, nil);
end.
`
ОЖИДАНИЕ РЕКЛАМЫ...
3 комментария удалено
0
29
1 год назад
0
Упакуйте вашу dll в файл zip
0
37
1 год назад
0
Строки ведь не уничтожаются до конца игры
1
14
1 год назад
1
Строки ведь не уничтожаются до конца игры
В этой версии виртуальной машины работа со строками идет таким образом, что они не утекают.
0
9
11 месяцев назад
Отредактирован Vampir_kolik
0
IceFog, Пожалуйста выложи свой JassApi,dll, а то твоя библиотека RedirectCalls.dll требует функции AddEventHandler у библиотеки JassApi.dll. Вот скриншот.
Загруженные файлы
1
14
11 месяцев назад
Отредактирован IceFog
1
IceFog, Пожалуйста выложи свой JassApi,dll
Исправил. Теперь кнопка скачать ссылается на правильный архив.
Похоже, что главным файлом ресурса считается идущий последним в списке, а не первым.
0
14
11 месяцев назад
0
Вышла новая версия!
  • Изменения в RedirectCalls
    • Теперь может быть безопасно загружен во время игры.
    • Может быть одновременно установлен в папку с игрой и встроен в карту: активен будет лишь первый загруженый экземпляр.
0
9
6 месяцев назад
0
Как играть по сети со своими нативными функциями?
0
29
6 месяцев назад
0
Vampir_kolik, дать их всем, кто приконектился.
0
9
6 месяцев назад
0
nazarpunk, чувствую, что нужно заливать в карту свою dll.
1
20
6 месяцев назад
1
nazarpunk, чувствую, что нужно заливать в карту свою dll.
Нативки так не получится использовать. Да и без мемхака ты никак не подгрузишь свою дллку.
0
9
6 месяцев назад
0
Unryze, у меня получилось с помощью в этой статьи xgm.guru/p/wc3/anyscript (Сценарий на любом языке) поиграть по сети со своей dll'кой. Это и есть мемхак?
1
20
6 месяцев назад
1
Unryze, у меня получилось с помощью в этой статьи xgm.guru/p/wc3/anyscript (Сценарий на любом языке) поиграть по сети со своей dll'кой. Это и есть мемхак?
"Сначала, предоставленный JASS-скрипт, используя уязвимости в виртуальной машине, выделяет испольняемую память и записывает туда мини-программу, которая распаковывает и загружает библиотеку "bin\Loader.dll", после чего вызывает её функцию "DoIt". Та же, в свою очередь, распаковывает и загружает библиотеки, перечисленные в списке "libraries" файла "config.json"."
Ага.
1
14
6 месяцев назад
Отредактирован IceFog
1
Как играть по сети со своими нативными функциями?
Я как-то делал интереса ради одну систему, которая позволяла достичь этой цели.
Принцип работы: сначала временный скрипт распаковывает и загружает библиотеки, а затем он компилирует и запускает оригинальный скрипт карты, после чего самоуничтожается. Таким образом, к моменту запуска основного сценария, нативки уже зарегистрированы и компилятор не ругается.
Вот только, на сайте сейчас нерабочая версия висит. Я скрывал ресурс ибо лень исправлять, но модератор зачем-то его опубликовал.
Впрочем, думаю есть способ получше: можно модифицировать байт-код во время исполнения таким образом, чтобы вызов обычной функции превратился в вызов нативки.
Будет выглядеть как-то так
function MyNativeFunction takes nothing returns nothing
endfunction

function main takes nothing returns nothing
	call RedirectToNative(function MyNativeFunction)
	// На самом деле вызовется не пустая функция объявленная выше, а нативка с таким же именем.
	call MyNativeFunction()
endfunction
0
9
6 месяцев назад
0
А в .ai скрипте я могу вызвать функцию из погруженной библиотеки, если она упакована в карту?
1
14
6 месяцев назад
Отредактирован IceFog
1
Если нативка зарегистрирована, то виртуальная машина JASS'а сможет её использовать.
Учитывая, что и обычный и ИИ скрипты исполняются ею, то почему бы и нет?
Единственная проблема — регистрация должна произойти до компиляции, а иначе компилятор откажется работать, сетуя на несуществующую нативку.
0
9
6 месяцев назад
Отредактирован Vampir_kolik
0
Если нативка зарегистрирована, то виртуальная машина JASS'а сможет её использовать.
Учитывая, что и обычный и ИИ скрипты исполняются ею, то почему бы и нет?
Единственная проблема — регистрация должна произойти до компиляции, а иначе компилятор откажется работать, сетуя на несуществующую нативку.
Буду благодарен за пример.
Я добавляю свою нативку в common.ai
native fBeginGameProcessAI takes nothing returns integer
Запускаю её у человека human,ai
call fBeginGameProcessAI()
Инициализирую библиотеку до:
call InitBlizzard()
call MeleeStartingAI()
И скрипт ИИ не работает.
Или её надо редеректом вызывать?

Просто на некоторых машинах у меня не получается запустить .mix файл где установлен лицензионный Windows 10. А если подгружать через карту, то работает на этой машине.
1
14
6 месяцев назад
1
Если ты регистрируешь нативку в библиотеке, загружаемой этой системой, то учти, что она ждет пока не завершится функция main в JASS скрипте и только после этого загружает библиотеки. До этого момента запустить ИИ с пользовательскими нативками не выйдет.
Наверно, нужно будет исправить это, так как сейчас уже можно загружать библиотеки сразу.
Раньше это было нужно, потому что там еще был код, который пересоздавал главный поток, а делать это пока тот исполняется — плохая идея.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.