Tuesday, 16.04.2024, 18:02
Приветствую Вас Гость
Register | Login | RSS
Jedi Academy Server Security
[ New messages · Users · Forum rules · Search · RSS ]
  • Page 1 of 1
  • 1
Forum » JASS » English forum » Creating plugins for JAZZ tutorial
Creating plugins for JAZZ tutorial
BufferOverflowDate: Monday, 28.05.2012, 20:42 | Message # 1
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
So, I've translated this tutorial. Sry for my English.
By creating JAZZ plugins you can rly extend JAZZ features, customize gameplay and etc. But, JAZZ is server-side only, remember that. I'm assuming that you know C or C++, otherwise just forget about pluginning.
For the beginning, you need some sources. Archive can be downloaded here. It contains all the game libs, that is needed for pluginning. Just unpack it to "include" folder of your compiler. Also, you need latest jazz interface header (jassapi.h). You can found it here.
Finally, you need the base plugin - download it here. It contains some comments, but it's not enough to understand, how it's working.
At first, "engine" is jamp.exe or jampDed.exe or linuxjampDed - whatever you are using. "Mod" is jampgamex86.dll on Windows or jampgamei386 on Linux. It contains most of game source.
Engine and mod communicates by cmds, that formally contains 12 or 13 integer arguments. However, do not let that fool you, - this arguments can mean anything, in most it is pointers. First argument is command. It is integer number. All the commands is described inside if g_public. Other arguments can mean anything depending on command.

Main JAZZ API functions (more info inside the jassapi.h):


List of mod cmds (more inside g_public.h):


List of engine cmds (more inside g_public.h):


That's list of usual cmds. Really it's much more of them, but in most times you'll not need any other.
Source for example plugin (available for download in download section. Good luck with its search in Russian site tongue ):


So, I'll describe, how engine communicates with mod more detailed.

Functions we will works with are syscall and vmMain. Engine regularly appeals to mod, , and passing command through the arguments - as if asking: "Friend, there is such a situation, what shall we do?" vmMain, taking the arguments of the situation, decides what to do. In this mod will often cause the engine via the function syscall, as if say: "So, do this, this and this". The same two functions of your plugin is named g_syscall and g_vmMain. Another nuance - engine loads mod only during one session. Ie, when loading map engine loads mod, and then, when the map changes, or restarts, mod will be unloaded and loaded again. But in our case, instead of loading mod, engine will load jazz, and jazz will load plugins.

Example of work engine-jazz-plugins-mod:

1) Server starts. The engine immediately loads mod into memory - or rather, thinks that loads a mod, but loads the JAZZ. Jazz, in turn, loads all the plugins and calls JASS_Query() for each, then loads real mod.

2) Mod is loaded, and engine provokes initialisation by calling vmMain function with GAME_INIT cmd. By the way, cmd passes thru first argument of vmMain, the same with g_syscall. JAZZ responds by calling the plugin function JASS_Attach(). After that JAZZ calls JASS_vmMain with the same arguments as for mod. Ie, variable cmd will be set to GAME_INIT, arg0 - levelTime, arg1 - randomSeed, arg2 - restart. arg3--arg12 us zero, because they are unused in GAME_INIT. Read mod commands list for details.

3) When JASS_vmMain for all the plugins would be called, jazz will call mod's vmMain. vmMain will initialize whatever it wants. No, it's not finish - when initialization will be completed, jazz will call plugin's JASS_vmMain_Post() function with the same attributes. It's finish.

4) So, everything is initialized, and game have begun. Engine will call vmMain many times. For example, someone have connected. It's isn't usual example. Engine will call vmMain with cmd = GAME_CLIENT_CONNECT. JAZZ will call JASS_vmMain with cmd=GAME_CLIENT_CONNECT, arg0=clientNum, arg1=firstTime, arg2=isBot. clientNum is client ID and can be equals to 0-31 (every action on clients uses his ID). firstTime is means, that clients have connected to our server just now. When map changes, mod and plugins unloads and loads again, while players technically reconnects to server. To understand, was client connected just now or he is reconnecting after map restart, this value is used. isBot means that player is bot.
And again, jazz calls vmMain() from mod, JASS_vmMain_Post() from plugin. By the way, if you want to do something to client, better to do this in JASS_vmMain_Post(). Also, usually, when blocking cmd by using JASS_RET_SUPERCEDE(), you must write 1 in brackets, but in case GAME_CLIENT_CONNECT you must put there a pointer to string, that contains a reason of denying the connect. Remember, that exit from vmMain and syscall performed by macro JASS_RET*, that is described in jassapi.h:
JASS_RET_IGNORED(x) - original function will be called, and it's result will be parsed to engine.
JASS_RET_OVERRIDE(x) - original function will be called, but it's result will be changed to x.
JASS_RET_SUPERCEDE(x) - original function will not be called, and result will be x.
JASS_RET_ERROR(x) - JAZZ shows error message and tech info.
JASS_SET_RESULT(x) - changes result. The same as JASS_RET_OVERRIDE, but for _Post functions.

5) Player have connected, entered the game (GAME_CLIENT_BEGIN), and entered some command (like "say Hi!"). Engine call vmMain with cmd=GAME_CLIENT_COMMAND, arg0=clientNum... Wait, where is player command?
Everything is simple. Engine have already split the command. How to access it:
int argc = g_syscall(G_ARGC);
We have called engine's syscall by using g_syscall with cmd=G_ARGC. ARGC is arguments count. We have taken count of arguments, it's stored in argc. After that we will call g_syscall(G_ARGV, 0, command, sizeof(command)); to get first command's arg - command name (it will be "say").After that, using loop 1:argc we can take command arguments. Example - writing every vote in logs.
Код
//crossplatform comparision function
#define strcasecmp stricmp
int argc = g_syscall(G_ARGC);//arguments count
g_syscall(G_ARGV, 0, command, sizeof(command));//command by itself
if (!strcasecmp(command,"callvote")){//If it is callvote
                 int i = 0;
                 char tmparg[1024];
                 JASS_WRITEJASSLOG(command);//Writing to jazz logs
                 JASS_WRITEGAMELOG(command);//Writing to game logs
                 for (i=1; i<argc; ++i){
                  g_syscall(G_ARGV, i, tmparg, sizeof(tmparg));//argument with i number
                  JASS_WRITEJASSLOG (JASS_VARARGS(" %s",tmparg));//Writing to jazz logs
                  JASS_WRITEGAMELOG (JASS_VARARGS(" %s",tmparg));//Writing to game logs
                 }
                 //And, as I said - never forget about adding the "\n"
                 JASS_WRITEJASSLOG("\n");
                 JASS_WRITEGAMELOG("\n");
}

If you have created your own command that takes some player id, do not forget to check if id>=0 and id
6) So, we will imagine that mod calls engine's syscall() - we can catch it as well as vmMain. By the way, if you will call g_syscall, it will not be catched by another plugin. For example, mod tries to call with cmd=G_PRINT (ie tries to print something). JAZZ will call JASS_syscall from plugin, cmd=G_PRINT, arg0=string. It is the same as catching commands for JASS_vmMain.

7) So usual situations is described, player had disconnected (GAME_CLIENT_DISCONNECT), it's time to shutdown server. JAZZ calls JASS_vmMain with cmd=GAME_SHUTDOWN without args, then mod's vmMain, then JASS_vmMain_Post(). After that, JAZZ beguns to unload the plugins. It calling JASS_Detach(), and then unloads mod. And then engine unloads jazz.

8) Now about JASS_pluginCall. This func is not created for JA, it is designed to ensure that your plug-in could be usefull for others. If you're not going to provide an interface to other plugins, do not touch this function. If not - create a separate header file with the commands. For example, an interface to a standard plugin AntiDDoS looks like this:
Код
/********************************/
/* 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
As you can see, the interface is similar to the cmds of vmMain or syscall. To call JASS_pluginCall from another plugin, use JASS_PLUGINCALL (ID, ...). For example, Protection bans player via AntiDDoS:
Код
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));
    }
}
And here is the interface in the antiddos:
Код
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. Debugging. You've created a plugin, it's time to test it. So, server got crashed? Now I'll explain how to debug the plugins. I suppose you have MS Visual Studio.
Debugging on Windows:
1) open project properties.
2) Section "Debugging":
2.1) Command - full path to Jedi Academy executable. Ex.: "D:\Games\Jedi Academy\GameData\jampDed.exe"
2.2) Command arguments - arguments. Ex.: "+set net_port 29070 +set dedicated 2 +exec server.cfg +set fs_game JASS_Release"
2.3) Working Directory - full path to GameData. Ex.: "D:\Games\Jedi Academy\GameData"
3) Section "Linker":
3.1) Output file - full plugin file path, here compiled dll will be stored. Ex.: "D:\Games\Jedi Academy\GameData\JASS_Release\Plugins\$(ProjectName).dll".
4) Press F5 and begin debug.
Debugging on Linux:
1) Better.
2) debug.
3) on.
4) Windows.
5) 95% of bugs is common for both systems, so I'm creating stable Windows plugin and then just compiling stable plugin for Linux. If you meet Linux-only bug, use the gdb.

I hope you get the point. Feel free to ask any questions
 
BufferOverflowDate: Monday, 28.05.2012, 20:59 | Message # 2
Group: Developer
Messages: 47
Reputation: 0
Status: Offline
Usual solutions:

Getting list of entities and clients: ( 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;
                   }


Parsing command:

Code
if (cmd == GAME_CLIENT_COMMAND/*GAME_SERVER_COMMAND for server console or rcon*/) {
       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
       }
}


Print to chat:

Code
char *chat = JASS_VARARGS("chat \"%s\n\"", "What_you_want_to_print");
g_syscall(G_SEND_SERVER_COMMAND, PlayerNum, chat );
}
To send cmd to every player, you can use -1 as number.

Print to player console:

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

By the way, arg0 usually is used to store playerID.

Get and set any parameter from userinfo string. String by itself you can get using G_GET_USERINFO and sed using 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;
       }

}


Getting mod name. Use this function instead of getting it directly from fs_game

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


Check if player is a bot. It is needed because bot are connect to server without GAME_CLIENT_CONNECT (it calls only on bot reconnect when map changes). You need to get list of entities before.

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


Getting pointer to level struct:
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
           }
          }
}

Suddenly, this method works only if logs are enabled. Also on basejka v1.0.1 on windows pointer to level will always be 0x207EEA60, and for linux - lib entry point+0x0068A3A0. Lib entry point search well-defined in academy5 plugin.
 
Forum » JASS » English forum » Creating plugins for JAZZ tutorial
  • Page 1 of 1
  • 1
Search: