Sunday, 19.11.2017, 20:59
Приветствую Вас Гость
Register | Login | RSS
Jedi Academy Server Security
[ New messages · Users · Forum rules · Search · RSS ]
Page 1 of 11
Forum » JASS » Плагины » Плагиностроение (С чего начинать?)
Плагиностроение
BufferOverflowDate: Wednesday, 11.11.2009, 17:33 | Message # 1
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
Плагины для джаза могут значительно расширить его функционал, вносить изменения в геймплей и т.п., но только со стороны сервера.
Для начала создания плагина вам необходимы некоторые исходники. Архив можно скачать здесь. Он содержит все игровые библиотеки, необходимые для всех создаваемых плагинов. Распакуйте эти файлы в папку include вашего компилятора. Правда, есть некоторые проблемы с компилированием под линукс с асм-вставками - включите интеловские вставки. Кроме того, необходим заголовочный файл интерфейса, который может изменяться в зависимости от версии джаза. Соответствующий вашей версии интерфейс можно найти здесь.
Далее вам понадобится основа для создания плагина - ее можно скачать здесь. В ней даны все необходимые комментарии, здесь же я опишу основные функции.
Движок и мод обмениваются командами, формально состоящие из 12-13 целочисленных переменных. Однако, пусть это не введет вас в заблуждение - на самом деле эти цифры могут быть чем угодно, обычно указателями. Первая цифра определяет передаваемую команду (все команды описаны в g_public), остальные могут означать что угодно.

Основные функции, предоставляемые джазом (подробнее о них можно узнать в файле jassapi.h):


Список основных команд из мода (подробнее в g_public.h):


Список основных команд из движка (подробнее в g_public.h):


Вот список основных команд. Это не всё, команд на самом деле гораздо, гораздо больше, но по большей части вам понадобятся лишь эти.
Исходный файл для плагина-примера (доступен для скачивания в файловом разделе):


Итак, опишу подробнее, как выглядит общение между движком (jamp.exe/jampDed.exe/linuxjampded), джазом, плагинами и модом (jampgamex86.dll/jampgamei386.so).

Функции, с которыми нам придётся работать: syscall и vmMain. Движок во время работы регулярно обращается к моду, вызывая из него vmMain, и передавая через аргументы команды - как бы спрашивая: "Друг, тут такая ситуация, что делать будем?". vmMain, принимая через аргументы описание ситуации, решает, что делать. При этом довольно часто мод будет вызывать из движка функцию syscall, как бы говоря: "Так, сделай-ка это". Эти две функции можно вызывать из плагина при помощи g_syscall и g_vmMain. Ещё ньюанс - движок загружает мод лишь на время одного сеанса. То есть, при загрузке карты движок загружает мод, а затем, при смене карты или рестарте мод выгружается и загружается вновь. Только в нашем случае вместо мода грузится джаз, а уже джаз загружает мод - и плагины.

Пример работы связки движок-джаза-плагины-мод:

1) Сервер просыпается. Движок тут же загружает в память мод - вернее, думает, что загружает мод, а загружает джаз. Джаз, в свою очередь, загружает все плагины и из каждого вызывает функцию JASS_Query(), а так же загружает сам мод.

2) Мод загружен, и сервер начинает инициализацию, вызывая функцию vmMain с командой GAME_INIT. Команда, кстати, передаётся в первом аргументе функции vmMain, с g_syscall аналогично. На эту функцию реагирует джаз, вызывая из плагина функцию JASS_Attach(). Затем из этого же плагина вызывается JASS_vmMain с теми же самыми аргументами, что и для мода. То есть, при этом переменная cmd будет равна константе GAME_INIT, arg0 будет содержать levelTime, arg1 - randomSeed, arg2 - restart, а все arg3-arg12 будут нулевыми, т.к. никаких аргументов при передаче GAME_INIT моду больше не требуется. Для справки можете глянуть список команд из мода.

3) Далее, закончив с плагинами, джаз вызывает функцию vmMain из мода. Тот инициализирует всё, что захочет. Но, дело на этом не кончается - джаз, дождавшись конца инициализации, вновь вызывает из плагина функцию, но на этот раз JASS_vmMain_Post() - с теми же аргументами. На этом процесс завершается.

4) Всё-всё инициализировано, игра пошла. Здесь происходит обмен самыми разными сообщениями. Вот, к примеру, присоединился игрок. Я взял этот пример, т.к. он довольно нетиповой. Движок вызывает джаз с командой GAME_CLIENT_CONNECT. Джаз, подумав маленько, вызывает JASS_vmMain() с командой GAME_CLIENT_CONNECT, аргументами arg0=clientNum, arg1=firstTime, arg2=isBot. clientNum - идентификатор клиента 0-31 (всё-всё связанное с игроками происходит через идентификатор клиента). firstTime означает, что игрок только-только присоединился к серверу. Обратите внимание, что при смене карты джаз, мод и плагины выгружаются ии загружаются заново, а игравшие в это время игроки фактически выходят и переприсоединяются к серверу. Именно для того, чтобы отличить, пришёл ли игрок в первый раз, или он уже находился на сервере до рестарта, и введена эта переменная - во втором случае у этих игроков аргумент firstTime будет равен 1. Ну а isBot означает, что игрок - бот.
И снова, джаз вызывает vmMain() из мода, а затем JASS_vmMain_Post() из плагина. Кстати, если вы хотите что-то сделать с клиентом, лучше это делать в функции JASS_vmMain_Post(). Ещё одно "но" - если вы блокируете команды вызовом JASS_RET_SUPERCEDE(), то обычно в скобках указывается 1, но в случае GAME_CLIENT_CONNECT там должен быть указатель на строку с причиной, почему игроку не позволено зайти на сервер. Вообще, выход из функции осуществляется при помощи макросов JASS_RET*, которые описаны в jassapi.h:
JASS_RET_IGNORED(x) - оригинальная функция из мода будет вызвана, её результат будет передан движку.
JASS_RET_OVERRIDE(x) - оригинальная функция из мода будет вызвана, но ее результат будет подменён.
JASS_RET_SUPERCEDE(x) - оригинальная функция не будет вызвана и будет возвращен данный результат
JASS_RET_ERROR(x) - JASS выдает сообщение с ошибкой и именем сообщения движка/мода
JASS_SET_RESULT(x) - выход с подменой результата. Аналогична JASS_RET_OVERRIDE, но для _Post функций.

5) Игрок присоединился, зашёл в игру (GAME_CLIENT_BEGIN), и вызвал какую-то команду. Движок вызывает команду GAME_CLIENT_COMMAND, джаз передаёт её нам в плагин... Ну, вы уже поняли, вызывается JASS_vmMain с cmd=GAME_CLIENT_COMMAND, arg0=clientNum... Стоп, а сама команда где?
Всё просто. Движок уже разбил команду на слова - как мило с его стороны. Делаем так:
int argc = g_syscall(G_ARGC);
С помощью g_syscall мы вызвали функцию syscall с командой G_ARGC из движка. ARGC= arguments count. Проще говоря, мы спросили у движка, сколько аргументов в команде. Кстати, так же при помощи g_syscall(G_PRINT, "Message\n") мы напишем сообщение "Message" в консоль сервера.
Теперь в переменной argc содержится количество аргументов в команде. Дальше вызываем g_syscall(G_ARGV, 0, command, sizeof(command));
По этой команде мы получаем нулевой аргумент команды - то есть, саму команду. Затем, в цикле 1:argc мы можем брать аргументы команды. Пример - мы решили писать в логи про голосования игроков.
Code
//Кроссплатформенная функция сравнения
#define strcasecmp stricmp
int argc = g_syscall(G_ARGC);//кол-во аргументов взяли
g_syscall(G_ARGV, 0, command, sizeof(command));//команду взяли
if (!strcasecmp(command,"callvote")){//Если наша команда - голосование
                  int i = 0;
                  char tmparg[1024];
                  JASS_WRITEJASSLOG(command);//Запись в логи джаза команды
                  JASS_WRITEGAMELOG(command);//Запись в логи игры комадны
                  for (i=1; i<argc; ++i){
                   g_syscall(G_ARGV, i, tmparg, sizeof(tmparg));//Взяли аргумент под номером i
                   JASS_WRITEJASSLOG (JASS_VARARGS(" %s",tmparg));//Запись в логи джаза аргумента
                   JASS_WRITEGAMELOG (JASS_VARARGS(" %s",tmparg));//Запись в логи игры аргумента
                  }
                  //Ну и напоследок - нельзя забывать про перенос строки в конце
                  JASS_WRITEJASSLOG("\n");
                  JASS_WRITEGAMELOG("\n");
}

Кстати, чуть не забыл - если вы создали собственную команду, и в команде указывается номер другого игрока - к примеру, сообщить этому игроку что-то, то не забудьте проверять, что номер этого другого игрока принадлежит диапазону от 0 до sv_maxclients - "хакеры" не дремлют.

6) А теперь представим, что сам мод вызывает функцию syscall() из движка (напомню, плагины вызывают эту же функцию при помощи g_syscall(), и этот вызов другими плагинами не перехватится). Допустим, мод собирается что-то напечатать и вызывает syscall с командой G_PRINT. При этом джаз сначала вызовет JASS_syscall из плагина - командой будет G_PRINT, arg0=string. Его обработка примерно аналогична обработке в JASS_vmMain.

7) Ну, с самыми типовыми задачами разобрались, игрок вышел (GAME_CLIENT_DISCONNECT - не буду процесс описывать), пора и сервер вырубать. При этом вызывается функция плагина JASS_vmMain с командой GAME_SHUTDOWN и без аргументов, а потом GAME_SHUTDOWN передаётся моду, потом вызывается JASS_vmMain_Post(). Затем, джаз начинает выгрузку всех плагинов, вызывая при этом из них JASS_Detach(), и выгружает мод. А затем движок выгружает сам джаз.

8) А теперь про то, за что отвечает JASS_pluginCall. Эта функция не создана для ЖА, она создана для того, чтобы к вашему плагину могли обращаться другие. Если вы не собираетесь предоставлять интерфейс другим плагинам, не трогайте эту функцию. Однако, создание данной функции является правилом хорошего тона. В противном случае - создайте отдельный заголовочный файл с командами. К примеру, интерфейс для стандартного плагина AntiDDoS выглядит так:
Code
/********************************/
/* Inteface for AntiDDoS plugin */
/********************************/

#ifndef __AD_INTERFACE_H__
#define __AD_INTERFACE_H__

typedef enum {
      AD_BAN,   //BOOL (char *szIP, size_t ltime);
      AD_UNBAN,  //void (char * szIP);
      AD_BANLIST  //char *(int num, int count);
} antiddos_cmds_t;

#endif
Как видите, интерфейс схож с командами vmMain или syscall. Для вызова JASS_pluginCall из другого плагина следует использовать JASS_PLUGINCALL (ID,...).К примеру, как Protection банит через AntiDDoS:
Code
int adID=JASS_GETPLUGINID("AntiDDoS");
bool banned=false;
if(adID!=-1){
      banned = JASS_PLUGINCALL(adID,AD_BAN,(int)(char*)ip,300000);
      if(banned){
       PrintNLog(JASS_VARARGS("AntiFake: Client %d:\"%s\" was banned for 5 mins, ip: %s\n",arg0,getname(arg0),ip));
      }
}

А вот как выглядит интерфейс в самом антиддосе:
Code
C_DLLEXPORT int JASS_pluginCall(int cmd, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11, int arg12) {
      if(cmd==AD_BAN){   //BOOL (char *szIP, size_t ltime);
       return AddBan((char *)arg0, arg1);
      }else if(cmd==AD_UNBAN){    //void (char *szIP);
       if(!strcasecmp((char *)arg0,"all")){
        UnbanAll();
       }else{
        Unban((char *)arg0);
       }
      }else if(cmd==AD_BANLIST){    //char *(int num, int count);
       return (int)ListBan(arg0,arg1);
      }

      return 0;
}


9. Отладка. Сделали плагинчик, пора и тестировать. Ага, падает сервак? 100% гарантия, что без этого не обойдётся. Объясню, как плагины дебажить. Предполагается, что у вас есть MS Visual Studio.
Отладка под Windows:
1) Открываете настройки проекта (у меня MSVC на английском, вводить все значения без кавычек).
2) Раздел Debugging:
2.1) Command - вводите полный путь до exe-шника. Пример: "D:\Games\Jedi Academy\GameData\jampDed.exe"
2.2) Command arguments - вводите аргументы для запуска. К примеру, "+set net_port 29070 +set dedicated 2 +exec server.cfg +set fs_game JASS_Release"
2.3) Working Directory - путь до папки GameData. К примеру, "D:\Games\Jedi Academy\GameData"
3) Раздел Linker:
3.1) Output file - полный путь до файла плагина, по которому выкладывать скомпилированную dll-ку. К примеру, "D:\Games\Jedi Academy\GameData\JASS_Release\Plugins\$(ProjectName).dll". Путь достаточно сменить лишь для Debug - режима.
4) Нажимаете F5, и начинаете отладку.
Отладка под Linux:
1) Отлаживайте.
2) лучше.
3) под.
4) Windows.
5) 95% ошибок общие для обеих платформ, обычно я только делаю стабильную версию для Windows, портирую и получаю стабильную версию под Linux. Если же ошибка появляется только на Linux, используйте gdb, с гуёвыми дебаггерами лучше не связываться. Ибо эти дебагерры гуёвые совсем не только от слова GUI.

Надеюсь, суть вы уловили. Если возникнут вопросы - не стесняйтесь smile
 
BufferOverflowDate: Tuesday, 10.01.2012, 19:58 | Message # 2
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
Типовые решения:

Запоминаем списки энтити и игроков: (не знаете, что это такое - узнайте на http://www.jacoders.org/?do=tutorials ).
Code
    if (cmd == G_LOCATE_GAME_DATA) {
                  g_gents = (gentity_t*)arg0;
                  g_gentsize = arg2;
                  g_clients = (gclient_t*)arg3;
                  g_clientsize = arg4;
                 }


Считывание команды игрока или админа (через ркон):
Code

if (cmd == GAME_CLIENT_COMMAND/*GAME_SERVER_COMMAND для серверной консоли*/) {
                 int argc = g_syscall(G_ARGC);
                 g_syscall(G_ARGV, 0, command, sizeof(command));
                 if (!strcasecmp(command,"cmd")){
                  char tmparg[1024];
                  for (int i=1; i<argc; ++i){
                   //args
                  }
                 }
}


Печать в чат:
Code

int sv_maxclients = JASS_GETINTCVAR("sv_maxclients");
for(int i=0;i<sv_maxclients;++i){
      char *chat = JASS_VARARGS("chat \"%s\n\"", "What_you_want_to_print");
      g_syscall(G_SEND_SERVER_COMMAND, i, chat );
}
Для того, чтобы отправить команду всем игрокам, можно вместо номера игрока указывать -1, но тогда почему-то печатается сообщение в верхнем левом углу. Так что - цикл.

Печать игроку в консоль:
Code

g_syscall(G_SEND_SERVER_COMMAND, clientNum, "print \"What_you_want.\n\"");

Кстати, в arg0 для большинства команд передаётся номер инициировавшего её игрока.

Взятие параметра из информации об игроке и присваивание параметра из строки информации об игроке. Строка берётся с помощью G_GET_USERINFO и присваивается G_SET_USERINFO.
Code

char *Info_ValueForKey( const char *s, const char *key ) {
                 char    pkey[BIG_INFO_KEY];
                 static    char value[2][BIG_INFO_VALUE];    // use two buffers so compares
                        // work without stomping on each other
                 static    int    valueindex = 0;
                 char    *o;
                      
                 if ( !s || !key ) {
                  return "";
                 }

                 if ( strlen( s ) >= BIG_INFO_STRING ) {
                  g_syscall( G_PRINT, "Info_ValueForKey: oversize infostring" );
                  return "";
                 }

                 valueindex ^= 1;
                 if (*s == '\\')
                  s++;
                 while (1)
                 {
                  o = pkey;
                  while (*s != '\\')
                  {
                   if (!*s)
                    return "";
                   *o++ = *s++;
                  }
                  *o = 0;
                  s++;

                  o = value[valueindex];

                  while (*s != '\\' && *s)
                  {
                   *o++ = *s++;
                  }
                  *o = 0;

                  if (!strcmp(key, pkey) )
                   return value[valueindex];

                  if (!*s)
                   break;
                  s++;
                 }

                 return "";
}

void Info_SetValueForKey( char *s, const char *key, const char *value ) {
                 char    newi[MAX_INFO_STRING];

                 if ( strlen( s ) >= MAX_INFO_STRING ) {
                  g_syscall( G_PRINT, "Info_SetValueForKey: oversize infostring" );
                  return;
                 }

                 if (strchr (key, '\\') || strchr (value, '\\'))
                 {
                  g_syscall( G_PRINT, "Can't use keys or values with a \\\n");
                  return;
                 }

                 if (strchr (key, ';') || strchr (value, ';'))
                 {
                  g_syscall( G_PRINT, "Can't use keys or values with a semicolon\n");
                  return;
                 }

                 if (strchr (key, '\"') || strchr (value, '\"'))
                 {
                  g_syscall( G_PRINT, "Can't use keys or values with a \"\n");
                  return;
                 }

                 Info_RemoveKey (s, key);
                 if (!value || !strlen(value))
                  return;

                 sprintf_s (newi, sizeof(newi), "\\%s\\%s", key, value);

                 if (strlen(newi) + strlen(s) > MAX_INFO_STRING)
                 {
                  g_syscall( G_PRINT, "Info string length exceeded\n");
                  return;
                 }

                 strcat (newi, s);
                 strcpy (s, newi);
}

void Info_RemoveKey( char *s, const char *key ) {
              char    *start;
              char    pkey[MAX_INFO_KEY];
              char    value[MAX_INFO_VALUE];
              char    *o;

              if ( strlen( s ) >= MAX_INFO_STRING ) {
                       g_syscall( G_PRINT, "Info_RemoveKey: oversize infostring");
              }

              if (strchr (key, '\\')) {
               return;
              }

              while (1)
              {
               start = s;
               if (*s == '\\')
                s++;
               o = pkey;
               while (*s != '\\')
               {
                if (!*s)
                 return;
                *o++ = *s++;
               }
               *o = 0;
               s++;

               o = value;
               while (*s != '\\' && *s)
               {
                if (!*s)
                 return;
                *o++ = *s++;
               }
               *o = 0;

               if (!strcmp (key, pkey) )
               {
                strcpy (start, s);    // remove this part
                return;
               }

               if (!*s)
                return;
              }

}


Взятие имени мода. Используйте эту функцию вместо прямого взятия имени из fs_game
Code

char base []="base";
const char * gamedir () {
                const char * game = JASS_GETSTRCVAR("fs_game");
                if (!game[0])
                    return base;
                return game;
}


Проверка, является ли игрок ботом. Это нужно, так как бот приходят на сервер "неявно", минуя вызов GAME_CLIENT_CONNECT (эта команда вызывается только при перезаходе бота при смене карты). Для работы требуется взять список энтити (см выше).
Code

bool is_bot = g_gents[arg0].r.svFlags&SVF_BOT;


Получаем указатель на структуру level (в level хранятся всякие вкусности, как строки с голосованием smile ).
Code
if (cmd == G_FS_FOPEN_FILE) {
        if (arg2 == FS_APPEND || arg2== FS_APPEND_SYNC) {
         if (!strcasecmp(JASS_GETSTRCVAR("g_log"), (char*)(arg0))) {
          //level_locals_t *g_level = NULL;
          g_level=(level_locals_t*)(arg1-20); //arg is a pointer to level.logFile :D, 20 is offset
         }
        }
}

Увы, этот метод работает только когда логи включены. Кстати, открою секрет - на патче 1.0.1 для базы на винде указатель на level всегда 0x207EEA60, а для линукса - точка входа библиотеки+0x0068A3A0. Как взять точку входа - смотрите в исходниках academy5.
 
HepoDate: Tuesday, 10.01.2012, 20:58 | Message # 3
Group: User
Messages: 12
Reputation: 0
Status: Offline
Quote (Zlyden)
Печать в чат:
Code

int sv_maxclients = JASS_GETINTCVAR("sv_maxclients");
for(int i=0;i<maxclients;++i){
g_syscall(G_SEND_SERVER_COMMAND, i, JASS_VARARGS("chat \"%s\n\"", "What_you_want_to_print");
}
Для того, чтобы отправить команду всем игрокам, можно вместо номера игрока указывать -1, но тогда почему-то печатается сообщение в верхнем левом углу. Так что - цикл.
 
BufferOverflowDate: Tuesday, 10.01.2012, 21:33 | Message # 4
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
Hepo, исправил, спасибо. Особенность файрфокса - при двойном щелчке он не считает частью слова _
 
HepoDate: Tuesday, 17.01.2012, 11:52 | Message # 5
Group: User
Messages: 12
Reputation: 0
Status: Offline
ИЗМЕНЕНИЕ

JASS текущей версии не отлавливает вход ботов на сервер, только выход
 
BufferOverflowDate: Tuesday, 17.01.2012, 12:37 | Message # 6
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
Hepo, ага, особенность движка ЖА. Не знал об этом - я всегда делал if (cmd== GAME_CLIENT_CONNECT && !arg2), отсеивая ботов, и никогда не замечал что их и так не ловит О_о
 
HepoDate: Friday, 28.12.2012, 11:57 | Message # 7
Group: User
Messages: 12
Reputation: 0
Status: Offline
Нашел способ ловить ботов на входе на сервер
в JASS_syscall
Код

     if (cmd == G_PRINT){
   char tmp[12] = "ClientBegin";
          char p[12];
          snprintf(p,12,"%s",(char*)arg0);
          int slot = atoi((char*)arg0+12);
           if(!strncmp(p,tmp,strlen(tmp))){
               if(g_gents[slot].r.svFlags&SVF_BOT){
               //bot found!!
               }
           }
    }


Добавлено (28.12.2012, 11:57)
---------------------------------------------
нашел способ переименовывать игроков "на лету" в стиле ja+

Код
void TrueRename(int slot, char* newvalue){
       char userinfo[4096];
       g_syscall(G_GET_CONFIGSTRING,1131+slot,userinfo,4096);
       Info_SetValueForKey(userinfo,"n",newvalue);
       g_syscall(G_SET_CONFIGSTRING,1131+slot,userinfo);
       strcpy(g_gents[slot].client->pers.netname,newvalue);
       g_syscall(G_GET_USERINFO,slot,userinfo,4096);
       Info_SetValueForKey(userinfo,"name",newvalue);
       g_syscall(G_SET_USERINFO,slot,userinfo,strlen(userinfo));
}


вы спросите - что такое конфигстринг и откуда взялось 1311?
конфигстринги - это особые данные, которые передаются от сервера к клиентам и хранят в себе некоторые важные настройки.
Конкретно:
0 - информация о сервере
1 - еще одна информация о сервере (другая)
2 - музыка на карте
3 - название карты (пишется 4-той строкой при загрузке карты)
1131 -> 1163 обрезанные userinfo игроков

в jka 1700 этих конфигстрингов. Учтите, что ВСЕ именения будут приняты ТУТ ЖЕ.
экспериментируйте smile


Message edited by Hepo - Friday, 28.12.2012, 12:19
 
pinokkioDate: Thursday, 07.03.2013, 18:50 | Message # 8
Group: User
Messages: 1
Reputation: 0
Status: Offline
каковы все-таки названия констант или функций для определения смерти/фрага конкретного игрока?
 
BufferOverflowDate: Sunday, 10.03.2013, 14:18 | Message # 9
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
Всё-таки? Я когда-то затрагивал эту тему? По сабжу - в англоязычный раздел
 
Forum » JASS » Плагины » Плагиностроение (С чего начинать?)
Page 1 of 11
Search: