Часть 1: Порядок действий и простейший пример

Добавлен , опубликован

Добавление собственных Native функций

Содержание:
Итак, обозначим порядок наших действий:
  • Написать нативку и скомпилировать dll
  • Добавить объявление написанной нативки в common.j
  • Написать код, использующий нативку
  • Запустить игру
Для начала возьмём классический пример "Hello world": напишем функцию, которая не принимает параметров, а возвращает некоторую строку. Вот наш test.cpp

#include <windows.h>
#include <stdio.h>

#define jNATIVE __stdcall
#define jAPI      __cdecl

#define jString long
#define jInt     long
#define jReal   long

typedef void(jAPI *jpAddNative)(void *routine, char *name, char *prototype);
typedef jString(jAPI *jpStrMap)(const char *str);
typedef const char*(jAPI *jpStrGet)(jString strid);

jpAddNativejAddNative = NULL;
jpStrMapjStrMap = NULL;
jpStrGetjStrGet = NULL;

jString jNATIVE test()
{
   char *s = "ZOMG TEH FUNC!";
   return jStrMap(s);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
   if (ul_reason_for_call != DLL_PROCESS_ATTACH) return TRUE;
   DisableThreadLibraryCalls(hModule);

   HMODULE hjApi = GetModuleHandle("japi.dll");

   jAddNative = (jpAddNative)GetProcAddress(hjApi, "jAddNative");
   jStrMap= (jpStrMap)GetProcAddress(hjApi, "jStrMap");
   jStrGet= (jpStrGet)GetProcAddress(hjApi, "jStrGet");

   jAddNative((void *)test, "test", "()S");

   return TRUE;
}
Два важных места здесь - это:

jString jNATIVE test()
{
   char *s = "ZOMG TEH FUNC!";
   return jStrMap(s);
}
и

jAddNative((void *)test, "test", "()S");
Третий аргумент функции jAddNative определяет типы, передаваемые функции и возвращаемые ей.
Теперь скомпилируем DLL. Для этого в командной строке напишем
bcc32 -WD -e"test.dll" test.cpp
-WD значит компилировать dll, -e определяет выходной файл и test.cpp - соответственно наш файл с исходным текстом.
Следующим шагом нам необходимо добавить эту функцию в common.j (если у вас его нет, всегда можно достать последнюю версию из war3patch.mpq):
native test takes nothing returns string
Теперь создадим карту, в которой будет вызвана наша функция. Назовем её test.w3m и сохраним в папку Maps директории Warcraft. В карте создадим триггер Test, выполняющийся при инициализации и в действия добавим:

call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,test())
Не забудьте импортировать в карту измененный common.j с путем "scripts\common.j", иначе при сохранении будет выдана ошибка.
Пришло время запускать игру. Распакуйте Grimoire в какую-то директорию неподалёку и приготовьтесь печатать. Открываем startwar3.bat, пишем и сохраняем:

bin\exehack.exe -s war3.lua -loadfile "Maps\test.w3m"
Теперь редактируем ongameload.lua, вот всё, что должно в нем остаться:

loaddll("bin\\japi.dll")
loaddll("bin\\test.dll")
Что такое луа?
Lua — интерпретируемый язык программирования, разработанный подразделением Tecgraf Католического университета Рио-де-Жанейро. Является свободно распространяемым, с открытыми исходными текстами на языке Си.
По возможностям, идеологии и реализации язык ближе всего к JavaScript, однако Lua отличается более мощными и гораздо более гибкими конструкциями, спроектирован с целью «не плодить сущности сверх необходимого». Хотя Lua не содержит понятия класса и объекта в явном виде, механизмы объектно-ориентированного программирования с поддержкой прототипов (включая множественное наследование) легко реализуются с использованием метатаблиц, которые также позволяют перегрузку операций и т. п. Реализуемая модель ООП (как и в JavaScript) — прототипная.
Lua в переводе с португальского значит «луна», поэтому неправильно писать «LUA» — так же, как и неправильно писать любое другое слово одними только прописными символами.
Предварительные ласки завершены. Теперь можно, скрепя сердце, запускать startwar3.bat, не забыв перед этим удостовериться что наша test.dll находится в папке grimoire\bin\. (да, и проверьте чтобы в папке с игрой не валялся scripts\common.j) Если вы все сделали правильно, игра запустится и сразу загрузит нашу тестовую карту, которая не преминет удивиться в ответ =)
Данный этап можно считать завершенным успешно.
А что собственно происходит?
Перехватчиком вызовов jass-функций выступает библиотека japi.dll, которая внедряется в процесс варкрафта с помощью Grimoire.
Конкретно внедрением занимается exehack.exe, который принимает параметры для своей работы из файла script.lua, расположенного с ним в одной папке. При запуске с ключом -s, парсит переданный ему в качестве аргумента файл lua.
Мы аргументом передаем war3.lua, в котором сначала определяется путь к Warcraft, а затем запускается игра и в процесс внедряется ongameload.dll
ongameload.dll считывает ongameload.lua и внедряет в процесс вызовы библиотек, указанных в нем, после чего процесс размораживается и идёт его нормальное выполнение.
Вот и вся схема подгрузки библиотек в нашем случае.

Содержание