using Ubot idea - a little "slavebot"


(brugal) #1

/* ../sdk-2.60/src/slavebot.c for linux
 * 
 * License: GPL (uses code from ubersoldat mod) 
 * Based on the shim idea from Ubot http://ubot.org/ 
 * gcc -Wall -fPIC -DGAMEDLL -shared slavebot.c -o qagame.mp.i386.so
 *
 * using with etpro:
 * cd <enemy territory installation>  (/usr/games/enemy-territor)
 * mkdir slavebot
 * cd slavebot
 * unzip ~/games/et/etpro-3_2_0.zip
 * cd etpro
 * mv * ../
 * cd ..
 * rmdir etpro
 * unzip -o etpro-3_2_0.pk3
 * mv qagame.mp.i386.so libetpro.so  
 *   change REAL_MOD_FILENAME below to point to it
 * cp <compiled slavebot.c> qagame.mp.i386.so
 *
 * et +set cl_punkbuster 0 +set sv_punkbuster 0 +set sv_pure 0 
 *    +set g_gametype 2 +set fs_game slavebot +set b_anticheat 0 
 *    +set b_antiwarp 0 +devmap radar
 * 
 * addslavebot [same options as 'team' command]
 * botcommand or botc +sprint
 * botcommand +moveright
 * etc..
 */
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include "game/q_shared.h"
#include "game/g_local.h"

#define BOT_CLIENT_NUMBER 13
#define REAL_MOD_FILENAME "/home/user/own_src/work/slavebot/src/libetpro.so"
#define MAX_ARGC 5

static char *sva (char *format, ... );
static void Botthink (void);

static int (*modVmMain) (int, int, int, int, int, int, int, int);
static void (*modDllEntry) (int (QDECL *)(int arg, ...));

static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)(int, ...)) - 1;

static void *qagameLib = NULL;
static int frameTime;

/* data snooped from syscall:G_LOCATE_GAME_DATA */
static gentity_t *gEnts = NULL;
static int numGEntities;
static int sizeofGEntity_t = 0;
static playerState_t *clients = NULL;
static int sizeofGClient = 0;

static char fakeArgv[MAX_TOKEN_CHARS][MAX_ARGC];
static int fakeArgc = -1;
static qboolean handleArgv = qfalse;


static usercmd_t prevCmd, currCmd;
static int botFaceEnemy = -1;
static int botTargetingError = 0;

static gentity_t *get_gentity_pointer (int entNumber)
{
    return (gentity_t *)((void *)&gEnts[0] + (entNumber * sizeofGEntity_t));
}

static playerState_t *get_playerState_pointer (int entNumber)
{
    return (playerState_t *)((void *)&clients[0] + (entNumber * sizeofGClient));
}

static int our_trap_Argv (int n, char *buffer, int bufferLength) 
{
    if (n >= MAX_ARGC) {
        buffer[0] = '\0';
        return qtrue;
    }
    strncpy (buffer, fakeArgv[n], bufferLength - 1);
    return qtrue;  //FIXME
}

static int our_syscall (int arg, ...)
{
    va_list va;

#define syscall_jmp asm volatile ( \
        "movl -4(%%ebp), %%ebx
	" \
        "movl %%ebp, %%esp
	" \
        "popl %%ebp
	" \
        "jmp *%0" \
        :: "r" (syscall));
    
    switch (arg) {
    case G_LOCATE_GAME_DATA: {
        va_start (va, arg);
        gEnts = va_arg (va, gentity_t *);
        numGEntities = va_arg (va, int);
        sizeofGEntity_t = va_arg (va, int);
        clients = va_arg (va, playerState_t *);
        sizeofGClient = va_arg (va, int);
        va_end (va);
        break;
    }
    case G_ARGC: {

        if (!handleArgv)
            break;
        return fakeArgc;
    }
    case G_ARGV: {
        int n;
        char *buffer;
        int bufferLength;

        va_start (va, arg);
        n = va_arg (va, int);
        buffer = va_arg (va, char *);
        bufferLength = va_arg (va, int);

        if (!handleArgv)
            break;
        return our_trap_Argv(n, buffer, bufferLength);
    }
    default:
        syscall_jmp;
    }

    syscall_jmp;
    return -1;  /* never gets here */
#undef syscall_jmp
}

extern void dllEntry( int (QDECL *syscallptr)(int arg, ...) ) {
    syscall = syscallptr;
    modDllEntry ((int (*)(int, ...))our_syscall);
}



static void addslavebot_f (void)
{
    char buf[MAX_TOKEN_CHARS];
    char *userinfo = "\\cg_uinfo\\13 0 30\\snaps\\20\\rate\\25000\
ame\\^bSlave
^0Bot^7\0";

    syscall (G_SET_USERINFO, BOT_CLIENT_NUMBER, userinfo);
    if (modVmMain(GAME_CLIENT_CONNECT, BOT_CLIENT_NUMBER, qtrue, qfalse, 0, 0, 0
, 0)) {
        syscall (G_PRINT, "error:  bot couldn't connect
");
        return;
    }
    memset (&prevCmd, 0, sizeof(usercmd_t));
    botFaceEnemy = -1;
    modVmMain (GAME_CLIENT_BEGIN, BOT_CLIENT_NUMBER, 0, 0, 0, 0, 0, 0);

    // set team
    strncpy (fakeArgv[0], "team", sizeof("team") + 1);
    syscall (G_ARGV, 1, buf, MAX_TOKEN_CHARS);
    strncpy (fakeArgv[1], buf, MAX_TOKEN_CHARS - 1);  // "blue", "red"
    syscall (G_ARGV, 2, buf, MAX_TOKEN_CHARS);
    strncpy (fakeArgv[2], buf, MAX_TOKEN_CHARS - 1);  // 0:sold 1:medic 2:eng 3:
field 4:cov
    syscall (G_ARGV, 3, buf, MAX_TOKEN_CHARS);
    strncpy (fakeArgv[3], buf, MAX_TOKEN_CHARS - 1);
    fakeArgc = 4;

#if 0
    // set team
    strncpy (fakeArgv[0], "team", sizeof("team") + 1);
    strncpy (fakeArgv[1], "blue", sizeof("blue") + 1);
    strncpy (fakeArgv[2], "2", 2);  // 0:sold 1:medic 2:eng 3:field 4:cov
    strncpy (fakeArgv[3], "8", 2);
    fakeArgc = 4;
#endif

    handleArgv = qtrue;
    modVmMain (GAME_CLIENT_COMMAND, BOT_CLIENT_NUMBER, 0, 0, 0, 0, 0, 0);
    handleArgv = qfalse;
}

static void botcommand_f (void)
{
    int argc, i, numCommands;
    char buf[MAX_TOKEN_CHARS];

    argc = syscall (G_ARGC);
    
    numCommands = 0;
    for (i = 1;  i < argc;  i++) {
        syscall (G_ARGV, i, buf, sizeof(buf));
        if (strncmp(buf, "+moveleft", sizeof("+moveleft")) == 0) {
            currCmd.rightmove = -127;
        } else if (strncmp(buf, "+moveright", sizeof("+moveright")) == 0) {
            currCmd.rightmove = 127;
        } else if (strncmp(buf, "+moveup", sizeof("+moveup")) == 0) {
            currCmd.upmove = 127;
        } else if (strncmp(buf, "-moveup", sizeof("-moveup")) == 0) {
            currCmd.upmove = 0;
        } else if (strncmp(buf, "+movedown", sizeof("+movedown")) == 0) {
            currCmd.upmove = -127;
        } else if (strncmp(buf, "-movedown", sizeof("-movedown")) == 0) {
            currCmd.upmove = 0;
        } else if (strncmp(buf, "+forward", sizeof("+forward")) == 0) {
            currCmd.forwardmove = 127;
        } else if (strncmp(buf, "+back", sizeof("+back")) == 0) {
            currCmd.forwardmove = -127;
        } else if (strncmp(buf, "+prone", sizeof("+prone")) == 0) {
            currCmd.wbuttons |= WBUTTON_PRONE;
        } else if (strncmp(buf, "+sprint", sizeof("+sprint")) == 0) {
            currCmd.buttons |= BUTTON_SPRINT;
        } else if (strncmp(buf, "-sprint", sizeof("-sprint")) == 0) {
            currCmd.buttons &= ~BUTTON_SPRINT;
        } else if (strncmp(buf, "+speed", sizeof("+speed")) == 0) {
            currCmd.buttons |= BUTTON_WALKING;
        } else if (strncmp(buf, "-speed", sizeof("-speed")) == 0) {
            currCmd.buttons &= ~BUTTON_WALKING;
        } else if (strncmp(buf, "+reload", sizeof("+reload")) == 0) {
            currCmd.wbuttons |= WBUTTON_RELOAD;
        } else if (strncmp(buf, "+attack", sizeof("+attack")) == 0) {
            currCmd.buttons |= BUTTON_ATTACK;
        } else if (strncmp(buf, "-attack", sizeof("-attack")) == 0) {
            currCmd.buttons &= ~BUTTON_ATTACK;
        } else if (strncmp(buf, "faceenemy", sizeof("faceenemy")) == 0) {
            i++;
            if (i >= argc) {
                syscall (G_PRINT, "botcommand faceenemy: needs entity/client to 
face
");
                return;
            }
            syscall (G_ARGV, i, buf, sizeof(buf));
            //FIXME check for int
            botFaceEnemy = atoi (buf);
            i++;
            if (i >= argc) {
                syscall (G_PRINT, "FIXME need targeting error
");
                return;
            }
            syscall (G_ARGV, i, buf, sizeof(buf));
            botTargetingError = atoi (buf);
        } else if (strncmp(buf, "angles", sizeof("angles")) == 0) {
            int pitch, yaw, roll;

            i++;
            syscall (G_ARGV, i, buf, sizeof(buf));
            pitch = atoi (buf);
            i++;
            syscall (G_ARGV, i, buf, sizeof(buf));
            yaw = atoi (buf);
            i++;
            syscall (G_ARGV, i, buf, sizeof(buf));
            roll = atoi (buf);
            currCmd.angles[PITCH] = pitch;
            currCmd.angles[YAW] = yaw;
            currCmd.angles[ROLL] = roll;
        } else if (strncmp(buf, "weapon", sizeof("weapon")) == 0) {
            i++;
            syscall (G_ARGV, i, buf, sizeof(buf));
            currCmd.weapon = atoi (buf);
        } else if (strncmp(buf, "+activate", sizeof("+activate")) == 0) {
            currCmd.buttons |= BUTTON_ACTIVATE;
        } else if (strncmp(buf, "-activate", sizeof("-activate")) == 0) {
            currCmd.buttons &= ~BUTTON_ACTIVATE;
        } else if (strncmp(buf, "stop", sizeof("stop")) == 0) {
            currCmd.forwardmove = 0;
            currCmd.rightmove = 0;
            currCmd.upmove = 0;
            currCmd.wbuttons = 0;
            currCmd.buttons = 0;
            botFaceEnemy = -1;
            botTargetingError = 0;
        } else {
            syscall (G_PRINT, sva("unknown botcommand: '%s'
", buf));
        }
    }
}

static void cinfo_f (void)
{
    int argc;
    char buf[MAX_TOKEN_CHARS];
    gentity_t *ent;

    argc = syscall (G_ARGC);
    if (argc < 2) {
        syscall (G_PRINT, "cinfo: needs client number
");
        return;
    }
    syscall (G_ARGV, 1, buf, sizeof(buf));
    ent = get_gentity_pointer (atoi(buf));
    syscall (G_PRINT, "ent->s  (entityState_t)
");
    syscall (G_PRINT, sva("  origin: %f, %f, %f
", ent->s.origin[0], ent->s.ori
gin[1], ent->s.origin[2]));
    syscall (G_PRINT, sva("  origin2: %f, %f, %f
", ent->s.origin2[0], ent->s.o
rigin2[1], ent->s.origin2[2]));
    syscall (G_PRINT, sva("  angles: %f, %f, %f
", ent->s.angles[0], ent->s.ang
les[1], ent->s.angles[2]));
    syscall (G_PRINT, sva("  angles2: %f, %f, %f
", ent->s.angles2[0], ent->s.a
ngles2[1], ent->s.angles2[2]));
}
        
static void testit_f (void)
{
    strncpy (fakeArgv[0], "say", 66);
    strncpy (fakeArgv[1], "blah blah", 66);
    fakeArgc = 2;

    handleArgv = qtrue;
    modVmMain (GAME_CLIENT_COMMAND, BOT_CLIENT_NUMBER, 0, 0, 0, 0, 0, 0);
    handleArgv = qfalse;
}

static qboolean SConsoleCommand (void)
{
    char cmd[MAX_TOKEN_CHARS];

    syscall (G_ARGV, 0, cmd, sizeof(cmd));
    if (strncmp(cmd, "addslavebot", sizeof("addslavebot")) == 0) {
        addslavebot_f ();
        return qtrue;
    } else if (strncmp(cmd, "botcommand", sizeof("botcommand")) == 0  ||
               strncmp(cmd, "botc", sizeof("botc")) == 0) {
        botcommand_f ();
        return qtrue;
    } else if (strncmp(cmd, "testit", sizeof("testit")) == 0) {
        testit_f ();
        return qtrue;
    } else if (strncmp(cmd, "cinfo", sizeof("cinfo")) == 0) {
        cinfo_f ();
        return qtrue;
    }
    
    return qfalse;
}




static void BotParseServerCommand(gentity_t *bot, int *state)
{
    char buf[1024];

    while (syscall(BOTLIB_GET_CONSOLE_MESSAGE, BOT_CLIENT_NUMBER, buf, sizeof(bu
f)))
    {
    }
}

static char *sva (char *format, ... ) {
    va_list         argptr;
#define MAX_VA_STRING   32000
    static char             temp_buffer[MAX_VA_STRING];
    static char             string[MAX_VA_STRING];  // in case va is called by n
ested functions
    static int              index = 0;
    char    *buf;
    int len;


    va_start (argptr, format);
    vsprintf (temp_buffer, format,argptr);
    va_end (argptr);

    if ((len = strlen(temp_buffer)) >= MAX_VA_STRING) {
        //Com_Error( ERR_DROP, "Attempted to overrun string in call to va()
");
        syscall (G_PRINT, "^1error: ^7Attempted to overrun string in call to sva
()
");
    }

    if (len + index >= MAX_VA_STRING-1) {
        index = 0;
    }

    buf = &string[index];
    memcpy( buf, temp_buffer, len+1 );
    
    index += len + 1;

    return buf;
#undef MAX_VA_STRING
}

static void svectoangles( const vec3_t value1, vec3_t angles ) {
    float   forward;
    float   yaw, pitch;
        
    if ( value1[1] == 0 && value1[0] == 0 ) {
        yaw = 0;
        if ( value1[2] > 0 ) {
            pitch = 90;
        }
        else {
            pitch = 270;
        }
    }
    else {
        if ( value1[0] ) {
            yaw = ( atan2 ( value1[1], value1[0] ) * 180 / M_PI );
        }
        else if ( value1[1] > 0 ) {
            yaw = 90;
        }
        else {
            yaw = 270;
        }
        if ( yaw < 0 ) {
            yaw += 360;
        }

        forward = sqrt ( value1[0]*value1[0] + value1[1]*value1[1] );
        pitch = ( atan2(value1[2], forward) * 180 / M_PI );
        if ( pitch < 0 ) {
            pitch += 360;
        }
    }

    angles[PITCH] = -pitch;
    angles[YAW] = yaw;
    angles[ROLL] = 0;
}

static void BotTargetEnemy (void)
{
    gentity_t *bot, *enemy;
    playerState_t *botState, *enemyState;
    vec3_t vec, enemyvec, enemyangles;

    bot = get_gentity_pointer (BOT_CLIENT_NUMBER);
    botState = get_playerState_pointer (BOT_CLIENT_NUMBER);
    enemy = get_gentity_pointer (botFaceEnemy);
    enemyState = get_playerState_pointer (botFaceEnemy);

    if (enemy->inuse) {
        VectorCopy (enemyState->origin, enemyvec);
        if (enemyState->eFlags & EF_PRONE) {
            enemyvec[2] = PRONE_VIEWHEIGHT;  //FIXME not sure, crouch and prone 
diff for etpro anyway
        }
        else if (enemyState->pm_flags & PMF_DUCKED)
            enemyvec[2] -= enemyState->crouchViewHeight;

        enemyvec[PITCH] += (crandom() * botTargetingError);
        enemyvec[YAW] += (crandom() * botTargetingError);
        //enemyvec[ROLL] += (crandom() * (botTargetingError / 2));
        // ROLL ??

        VectorSubtract (enemyvec, bot->r.currentOrigin, vec);
        svectoangles (vec, enemyangles);

        currCmd.angles[PITCH] = ANGLE2SHORT(enemyangles[PITCH]);
        currCmd.angles[YAW] = ANGLE2SHORT(enemyangles[YAW]);
        currCmd.angles[ROLL] = ANGLE2SHORT(enemyangles[ROLL]);

        svectoangles (vec, bot->s.angles);
    }
}

static void Botthink (void)
{
    gentity_t *ent;
    playerState_t *botState;
    qboolean tappingOut = qfalse;
    int tmpUpmove = 0;

    if (!gEnts)
        return;

    ent = get_gentity_pointer (BOT_CLIENT_NUMBER);
    botState = get_playerState_pointer (BOT_CLIENT_NUMBER);

    if (!ent->inuse) 
        return;

    BotParseServerCommand (NULL, NULL);
    VectorSet (botState->delta_angles, 0, 0, 0);
    //currCmd.serverTime = frameTime - 20;
    currCmd.serverTime = frameTime;

    //    if (ent->client->ps.eFlags & EF_DEAD  &&  !(ent->client->ps.pm_flags &
 PMF_LIMBO)) {
    if (botState->eFlags & EF_DEAD  &&  !(botState->pm_flags & PMF_LIMBO)) {
        // tap out
        tmpUpmove = currCmd.upmove;
        currCmd.upmove = 127;
        tappingOut = qtrue;
    }

    if (botFaceEnemy > -1  &&  botFaceEnemy < MAX_CLIENTS) {
        if (!(botState->eFlags & EF_DEAD))
            BotTargetEnemy ();
    }

#if 0
    if (ent->client) {
        client = ent->client;
        // remove spawn invulnerability for bots                                
        client->ps.powerups[PW_INVULNERABLE] = 0;
    }
#endif
    botState->powerups[PW_INVULNERABLE] = 0;  // no spawn invuln for bots

    syscall (BOTLIB_USER_COMMAND, BOT_CLIENT_NUMBER, &currCmd);
    if (tappingOut) 
        currCmd.upmove = tmpUpmove;
    memcpy (&prevCmd, &currCmd, sizeof(usercmd_t));
    /* clear some flags */
    currCmd.wbuttons &= ~WBUTTON_PRONE;
    currCmd.wbuttons &= ~WBUTTON_RELOAD;

    modVmMain (GAME_CLIENT_THINK, BOT_CLIENT_NUMBER, 0, 0, 0, 0, 0, 0);
}

extern int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4
, int arg5, int arg6 ) 
{
    int returnVal;
    int val;
    
    switch ( command ) {
    case GAME_RUN_FRAME:
        frameTime = syscall (G_MILLISECONDS);
        Botthink ();
        break;
    case GAME_CONSOLE_COMMAND:
        val = SConsoleCommand ();
        if (val)
            return val;
        break;
    default:
        break;
    }

   returnVal = modVmMain(command, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
   return returnVal;
}

void __attribute__ ((constructor)) my_init (void)
{
    const char *errorMsg;

    qagameLib = dlopen (REAL_MOD_FILENAME, RTLD_LAZY);
    if (qagameLib == NULL) {
        printf ("shim: error opening real .so
");
        return;
    }
    modVmMain = (int (*)(int,int,int,int,int,int,int,int)) dlsym (qagameLib, "vm
Main");
    errorMsg = dlerror ();
    if (errorMsg) {
        printf ("shim: error getting vmMain() from mod
");
        return;
    }

    modDllEntry = dlsym (qagameLib, "dllEntry");
    errorMsg = dlerror ();
    if (errorMsg) {
        printf ("shim: error getting dllEntry() from mod
");
        return;
    }
}