Some questions about plugins

Started by GodlySOB, February 17, 2011, 04:37:38 AM

Previous topic - Next topic

GodlySOB

How did you compile using MingW to make the plugins? Ive tried compiling using MingW but Im unsure how to make a .dll.

I tried modifying your MassMover plugin to kick all users but I have trouble compiling to test!

Could you please tell me what to do or create/modify a plugin with the ability to kick all users on server and kick a single client or group? It would be pretty much almost exactly the same as your moving client coding I would think just replaced with the Kick coding. Thanks!

Stefan1200

What is the exact problem you have? I am not very good with C++, because I am primary a Java developer.

Currently I have crashing problems with the Windows Teamspeak 3 client rc1-pre9. The last version I was able to create plugins was TS3 client beta 36. Maybe I am not the perfect guy to ask me C++ and TS3 Client Plugin development related questions :(. Sorry for this.

Stefan1200

#2
Seems our messenger clients had some troubles :).

This is my idea of a kick all function, remember to change that first line (case CMD_KA) and the kick reason.

That piece of code is untested, don't know if the compiler would like that.

case CMD_KA:
/* Get own clientID */
if(ts3Functions.getClientID(serverConnectionHandlerID, &myID) != ERROR_ok)
{
ts3Functions.logMessage("Error querying client ID", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
break;
}

anyID* clientList;
if(ts3Functions.getClientList(serverConnectionHandlerID, &clientList) != ERROR_ok)
{
ts3Functions.logMessage("Error querying client list", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
break;
}

int clientErrorCount = 0;
int clientCount = 0;
int clientType;
for(i=0; clientList[i]; i++)
{
if(ts3Functions.getClientVariableAsInt(serverConnectionHandlerID, clientList[i], CLIENT_TYPE, &clientType) != ERROR_ok)
{
continue;
}
if (clientType == 1)
{
continue;
}
if (myID == clientList[i])
{
continue;
}

/* Move clients to specified channel */
if(ts3Functions.requestClientKickFromServer(serverConnectionHandlerID, clientList[i], "kick reason", NULL) != ERROR_ok)
{
++clientErrorCount;
}

++clientCount;
}

char buffer [50];
sprintf (buffer, "Kicked %d of %d clients!", (clientCount - clientErrorCount), clientCount);
ts3Functions.printMessageToCurrentTab(buffer);

ts3Functions.freeMemory(clientList);
break;

GodlySOB

Doesnt work

In function 'int ts3plugin_processCommand<uint64, const chat*>':
207:87: error: invalid operands of types 'const char* and "const char [5]' to binary 'operator+'

Stefan1200

I changed the printMessageToCurrentTab stuff at the end, look in my previous post.

GodlySOB

#5
It compiles now but doesnt recognize any commands.

Heres the full code of your code
#include "main.h"
#define _UNICODE
#include <windows.h>
#include <cwchar>
#include <cstdio>

static struct TS3Functions ts3Functions;

#ifdef WINDOWS
#define _strcpy(dest, destSize, src) strcpy_s(dest, destSize, src)
#define snprintf sprintf_s
#else
#define _strcpy(dest, destSize, src) { strncpy(dest, src, destSize-1); dest[destSize-1] = '\0'; }
#endif

#define PATH_BUFSIZE 512
#define COMMAND_BUFSIZE 128

static char* pluginCommandID = NULL;

using namespace std;

/*********************************** Required functions ************************************/
/*
* If any of these required functions is not implemented, TS3 will refuse to load the plugin
*/

/* Unique name identifying this plugin */
const char* ts3plugin_name()
{
    return "KickAll";
}

/* Plugin version */
const char* ts3plugin_version()
{
    return "0.3";
}

/* Plugin API version. Must be the same as the clients API major version, else the plugin fails to load. */
int ts3plugin_apiVersion()
{
return 8;
}

/* Plugin author */
const char* ts3plugin_author()
{
    return "Stefan1200/GodlySOB";
}

/* Plugin description */
const char* ts3plugin_description()
{
    return "Kicks people";
}

/* Set TeamSpeak 3 callback functions */
void ts3plugin_setFunctionPointers(const struct TS3Functions funcs)
{
    ts3Functions = funcs;
}

/*
* Custom code called right after loading the plugin. Returns 0 on success, 1 on failure.
* If the function returns 1 on failure, the plugin will be unloaded again.
*/
int ts3plugin_init()
{
    return 0;  /* 0 = success, 1 = failure */
}

/* Custom code called right before the plugin is unloaded */
void ts3plugin_shutdown()
{
    /* Your plugin cleanup code here */
    printf("TS3MassMover PLUGIN: shutdown\n");

/* Free pluginCommandID if we registered it */
if(pluginCommandID)
{
free(pluginCommandID);
pluginCommandID = NULL;
}
}

/*
* If the plugin wants to use plugin commands, it needs to register a command ID. This function will be automatically called after
* the plugin was initialized. This function is optional. If you don't use plugin commands, the function can be omitted.
* Note the passed commandID parameter is no longer valid after calling this function, so you *must* copy it and store it in the plugin.
*/
void ts3plugin_registerPluginCommandID(const char* commandID)
{
const size_t sz = strlen(commandID) + 1;
pluginCommandID = (char*)malloc(sz);
memset(pluginCommandID, 0, sz);
_strcpy(pluginCommandID, sz, commandID);  /* The commandID buffer will invalidate after existing this function */
printf("TS3MassMover PLUGIN: registerPluginCommandID: %s\n", pluginCommandID);
}

/* Plugin command keyword. Return NULL or "" if not used. */
const char* ts3plugin_commandKeyword()
{
return "a";
}

/* Plugin processes console command. Return 0 if plugin handled the command, 1 if not handled. */
int ts3plugin_processCommand(uint64 serverConnectionHandlerID, const char* command)
{
char buf[COMMAND_BUFSIZE];
char *s, *param1 = NULL, *param2 = NULL, *param3 = NULL, *kickReason = const_cast<char *>("");
int i = 0;
anyID myID;
enum { CMD_NONE = 0, CMD_KICK, CMD_KICKALL, CMD_KA} cmd = CMD_NONE;
#ifdef WINDOWS
char* context = NULL;
#endif

printf("TS3MassMover PLUGIN: process command: '%s'\n", command);

_strcpy(buf, COMMAND_BUFSIZE, command);
#ifdef WINDOWS
s = strtok_s(buf, " ", &context);
#else
s = strtok(buf, " ");
#endif
while(s != NULL)
{
if(i == 0)
{
    if(!strcmp(s, "ka"))
{
cmd = CMD_KA;
}
else if(!strcmp(s, "kickall"))
{
cmd = CMD_KICKALL;
}

}
else if(i == 1)
{
param1 = s;
}
else if(i == 2)
{
param2 = s;
}
else
{
param3 = s;
}
#ifdef WINDOWS
s = strtok_s(NULL, " ", &context);
#else
s = strtok(NULL, " ");
#endif
i++;
}

switch(cmd)
{
case CMD_NONE:
return 1;  /* Command not handled by plugin */
case CMD_KA:
/* Get own clientID */
if(ts3Functions.getClientID(serverConnectionHandlerID, &myID) != ERROR_ok)
{
ts3Functions.logMessage("Error querying client ID", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
break;
}

anyID* clientList;
if(ts3Functions.getClientList(serverConnectionHandlerID, &clientList) != ERROR_ok)
{
ts3Functions.logMessage("Error querying client list", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
break;
}

int clientErrorCount = 0;
int clientCount = 0;
int clientType;
for(i=0; clientList[i]; i++)
{
if(ts3Functions.getClientVariableAsInt(serverConnectionHandlerID, clientList[i], CLIENT_TYPE, &clientType) != ERROR_ok)
{
continue;
}
if (clientType == 1)
{
continue;
}
if (myID == clientList[i])
{
continue;
}

/* Move clients to specified channel */
if(ts3Functions.requestClientKickFromServer(serverConnectionHandlerID, clientList[i], "kick reason", NULL) != ERROR_ok)
{
++clientErrorCount;
}

++clientCount;
}

char buffer [50];
sprintf (buffer, "Kicked %d of %d clients!", (clientCount - clientErrorCount), clientCount);
ts3Functions.printMessageToCurrentTab(buffer);

ts3Functions.freeMemory(clientList);
break;
}

return 0;  /* Plugin handled command */
}

bool isServerGroupIDinList(char* list, uint64 id)
{
    char* token = strtok(list,",");
    uint64 tmp = 0;
    while (token != NULL)
    {
        tmp = (uint64)atoi(token);
        if (id == tmp)
        {
            return true;
        }
        token = strtok(NULL,",");
    }

    return false;
}

bool isClientInList(anyID* clientList, anyID* clientID)
{
    for(int i=0; clientList[i]; i++)
    {
        if (clientList[i] == *clientID)
        {
            return true;
        }
    }

    return false;
}

/*
* Plugin requests to be always automatically loaded by the TeamSpeak 3 client unless
* the user manually disabled it in the plugin dialog.
* This function is optional. If missing, no autoload is assumed.
*/
int ts3plugin_requestAutoload()
{
return 1;  /* 1 = request autoloaded, 0 = do not request autoload */
}
int main() {
    int argc;
    wchar_t** argv = CommandLineToArgvW( GetCommandLineW(), &argc);
    if (argc != 3) {
        wprintf(L"usage: this [file] [text]\n");
        return 1;
    }
    FILE* out = _wfopen( argv[1], L"wb");
    if (!out) {
        return 1;
    }
    fwprintf(out, L"%c", 0xFEFF);
    fwprintf(out, L"%s", argv[2]);
    fclose(out);
}


And heres mine.
#include "main.h"

static struct TS3Functions ts3Functions;

#ifdef WINDOWS
#define _strcpy(dest, destSize, src) strcpy_s(dest, destSize, src)
#define snprintf sprintf_s
#else
#define _strcpy(dest, destSize, src) { strncpy(dest, src, destSize-1); dest[destSize-1] = '\0'; }
#endif

#define PATH_BUFSIZE 512
#define COMMAND_BUFSIZE 128

static char* pluginCommandID = NULL;

/*********************************** Required functions ************************************/
/*
* If any of these required functions is not implemented, TS3 will refuse to load the plugin
*/

/* Unique name identifying this plugin */
const char* ts3plugin_name()
{
    return "Kick";
}

/* Plugin version */
const char* ts3plugin_version()
{
    return "0.3";
}

/* Plugin API version. Must be the same as the clients API major version, else the plugin fails to load. */
int ts3plugin_apiVersion()
{
return 8;
}

/* Plugin author */
const char* ts3plugin_author()
{
    return "Stefan1200/GodlySOB";
}

/* Plugin description */
const char* ts3plugin_description()
{
    return "Kick people   /a KA = Kicks all";
}

/* Set TeamSpeak 3 callback functions */
void ts3plugin_setFunctionPointers(const struct TS3Functions funcs)
{
    ts3Functions = funcs;
}

/*
* Custom code called right after loading the plugin. Returns 0 on success, 1 on failure.
* If the function returns 1 on failure, the plugin will be unloaded again.
*/
int ts3plugin_init()
{
    return 0;  /* 0 = success, 1 = failure */
}

/* Custom code called right before the plugin is unloaded */
void ts3plugin_shutdown()
{
    /* Your plugin cleanup code here */
    printf("TS3MassMover PLUGIN: shutdown\n");

/* Free pluginCommandID if we registered it */
if(pluginCommandID)
{
free(pluginCommandID);
pluginCommandID = NULL;
}
}

/*
* If the plugin wants to use plugin commands, it needs to register a command ID. This function will be automatically called after
* the plugin was initialized. This function is optional. If you don't use plugin commands, the function can be omitted.
* Note the passed commandID parameter is no longer valid after calling this function, so you *must* copy it and store it in the plugin.
*/
void ts3plugin_registerPluginCommandID(const char* commandID)
{
const size_t sz = strlen(commandID) + 1;
pluginCommandID = (char*)malloc(sz);
memset(pluginCommandID, 0, sz);
_strcpy(pluginCommandID, sz, commandID);  /* The commandID buffer will invalidate after existing this function */
printf("TS3MassMover PLUGIN: registerPluginCommandID: %s\n", pluginCommandID);
}

/* Plugin command keyword. Return NULL or "" if not used. */
const char* ts3plugin_commandKeyword()
{
return "a";
}

/* Plugin processes console command. Return 0 if plugin handled the command, 1 if not handled. */
int ts3plugin_processCommand(uint64 serverConnectionHandlerID, const char* command)
{
char buf[COMMAND_BUFSIZE];
char *s, *param1 = NULL, *param2 = NULL, *param3 = NULL, *kickReason = const_cast<char *>("");
int i = 0;
anyID myID;
enum { CMD_NONE = 0, CMD_KA } cmd = CMD_NONE;
#ifdef WINDOWS
char* context = NULL;
#endif

printf("TS3MassMover PLUGIN: process command: '%s'\n", command);

_strcpy(buf, COMMAND_BUFSIZE, command);
#ifdef WINDOWS
s = strtok_s(buf, " ", &context);
#else
s = strtok(buf, " ");
#endif
while(s != NULL)
{
if(i == 0)
{
if(!strcmp(s, "ka"))
{
cmd = CMD_KA;
}
}
else if(i == 1)
{
param1 = s;
}
else if(i == 2)
{
param2 = s;
}
else
{
param3 = s;
}
#ifdef WINDOWS
s = strtok_s(NULL, " ", &context);
#else
s = strtok(NULL, " ");
#endif
i++;
}

switch(cmd)
{
case CMD_NONE:
return 1;  /* Command not handled by plugin */

        case CMD_KA:
            kickReason = const_cast<char *>(param1 ? param1 : "");

            /* Get own clientID */
            if(ts3Functions.getClientID(serverConnectionHandlerID, &myID) != ERROR_ok)
            {
                ts3Functions.logMessage("Error querying client ID", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
                break;
            }

            anyID* clientList;
            if(ts3Functions.getClientList(serverConnectionHandlerID, &clientList) != ERROR_ok)
            {
                ts3Functions.logMessage("Error querying client list", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
                break;
            }

            //char buffer [60];

            int clientType;
            for(i=0; clientList[i]; i++)
            {
                if(ts3Functions.getClientVariableAsInt(serverConnectionHandlerID, clientList[i], CLIENT_TYPE, &clientType) != ERROR_ok)
                {
                    continue;
                }
                if (clientType == 1)
                {
                    continue;
                }

                if (isClientInList(&clientList[i], &clientList[i]))
                {
                    continue;
                }

                // Debug
                //sprintf (buffer, "Debug: Moving client ID %d to channel ID %d", clientList[i], currentChannelID);
                //ts3Functions.printMessageToCurrentTab(buffer);

                /* Move clients to specified channel */
                if(ts3Functions.requestClientKickFromServer(serverConnectionHandlerID, clientList[i], "kick reason", NULL) != ERROR_ok)
                {
                    ts3Functions.logMessage("Error requesting client move", LogLevel_ERROR, "TS3MassMover Plugin", serverConnectionHandlerID);
                }
            }

            ts3Functions.freeMemory(clientList);

break;
}

return 0;  /* Plugin handled command */
}

bool isServerGroupIDinList(char* list, uint64 id)
{
    char* token = strtok(list,",");
    uint64 tmp = 0;
    while (token != NULL)
    {
        tmp = (uint64)atoi(token);
        if (id == tmp)
        {
            return true;
        }
        token = strtok(NULL,",");
    }

    return false;
}

bool isClientInList(anyID* clientList, anyID* clientID)
{
    for(int i=0; clientList[i]; i++)
    {
        if (clientList[i] == *clientID)
        {
            return true;
        }
    }

    return false;
}

/*
* Plugin requests to be always automatically loaded by the TeamSpeak 3 client unless
* the user manually disabled it in the plugin dialog.
* This function is optional. If missing, no autoload is assumed.
*/
int ts3plugin_requestAutoload()
{
return 1;  /* 1 = request autoloaded, 0 = do not request autoload */
}


I changed the command key to 'a' for both.

Mine uses the MAH coding. I got rid of the channelclient stuff and put 2 of the &clientlist in the bool on line 186 if that matters.

Can you look whichever and diagnose whats wrong? They both compile but do not recognize commands as in /a ka.


Stefan1200

#6
This source work here, tested with TS3 client beta 36:

main.cpp:
#include "main.h"

static struct TS3Functions ts3Functions;

#ifdef WINDOWS
#define _strcpy(dest, destSize, src) strcpy_s(dest, destSize, src)
#define snprintf sprintf_s
#else
#define _strcpy(dest, destSize, src) { strncpy(dest, src, destSize-1); dest[destSize-1] = '\0'; }
#endif

#define PATH_BUFSIZE 512
#define COMMAND_BUFSIZE 128

static char* pluginCommandID = NULL;

using namespace std;

/*********************************** Required functions ************************************/
/*
* If any of these required functions is not implemented, TS3 will refuse to load the plugin
*/

/* Unique name identifying this plugin */
const char* ts3plugin_name()
{
    return "TS3KickAll";
}

/* Plugin version */
const char* ts3plugin_version()
{
    return "0.1";
}

/* Plugin API version. Must be the same as the clients API major version, else the plugin fails to load. */
int ts3plugin_apiVersion()
{
return 8;
}

/* Plugin author */
const char* ts3plugin_author()
{
    return "Stefan1200/GodlySOB";
}

/* Plugin description */
const char* ts3plugin_description()
{
    return "Kicks people";
}

/* Set TeamSpeak 3 callback functions */
void ts3plugin_setFunctionPointers(const struct TS3Functions funcs)
{
    ts3Functions = funcs;
}

/*
* Custom code called right after loading the plugin. Returns 0 on success, 1 on failure.
* If the function returns 1 on failure, the plugin will be unloaded again.
*/
int ts3plugin_init()
{
    return 0;  /* 0 = success, 1 = failure */
}

/* Custom code called right before the plugin is unloaded */
void ts3plugin_shutdown()
{
    /* Your plugin cleanup code here */
    printf("TS3KickAll PLUGIN: shutdown\n");

/* Free pluginCommandID if we registered it */
if(pluginCommandID)
{
free(pluginCommandID);
pluginCommandID = NULL;
}
}

/*
* If the plugin wants to use plugin commands, it needs to register a command ID. This function will be automatically called after
* the plugin was initialized. This function is optional. If you don't use plugin commands, the function can be omitted.
* Note the passed commandID parameter is no longer valid after calling this function, so you *must* copy it and store it in the plugin.
*/
void ts3plugin_registerPluginCommandID(const char* commandID)
{
const size_t sz = strlen(commandID) + 1;
pluginCommandID = (char*)malloc(sz);
memset(pluginCommandID, 0, sz);
_strcpy(pluginCommandID, sz, commandID);  /* The commandID buffer will invalidate after existing this function */
printf("TS3KickAll PLUGIN: registerPluginCommandID: %s\n", pluginCommandID);
}

/* Plugin command keyword. Return NULL or "" if not used. */
const char* ts3plugin_commandKeyword()
{
return "a";
}

/* Plugin processes console command. Return 0 if plugin handled the command, 1 if not handled. */
int ts3plugin_processCommand(uint64 serverConnectionHandlerID, const char* command)
{
char buf[COMMAND_BUFSIZE];
char *s, *param1 = NULL, *param2 = NULL, *param3 = NULL, *kickReason = const_cast<char *>("");
int i = 0;
anyID myID;
enum { CMD_NONE = 0, CMD_KICK, CMD_KICKALL} cmd = CMD_NONE;
#ifdef WINDOWS
char* context = NULL;
#endif

printf("TS3KickAll PLUGIN: process command: '%s'\n", command);

_strcpy(buf, COMMAND_BUFSIZE, command);
#ifdef WINDOWS
s = strtok_s(buf, " ", &context);
#else
s = strtok(buf, " ");
#endif
while(s != NULL)
{
if(i == 0)
{
    if(!strcmp(s, "ka"))
{
cmd = CMD_KICKALL;
}
else if(!strcmp(s, "kickall"))
{
cmd = CMD_KICKALL;
}

}
else if(i == 1)
{
param1 = s;
}
else if(i == 2)
{
param2 = s;
}
else
{
param3 = s;
}
#ifdef WINDOWS
s = strtok_s(NULL, " ", &context);
#else
s = strtok(NULL, " ");
#endif
i++;
}

switch(cmd)
{
case CMD_NONE:
return 1;  /* Command not handled by plugin */
case CMD_KICKALL:
            /* Get own clientID */
            if(ts3Functions.getClientID(serverConnectionHandlerID, &myID) != ERROR_ok)
            {
                ts3Functions.logMessage("Error querying client ID", LogLevel_ERROR, "TS3KickAll Plugin", serverConnectionHandlerID);
                break;
            }

            anyID* clientList;
            if(ts3Functions.getClientList(serverConnectionHandlerID, &clientList) != ERROR_ok)
            {
                ts3Functions.logMessage("Error querying client list", LogLevel_ERROR, "TS3KickAll Plugin", serverConnectionHandlerID);
                break;
            }

            int clientCount = 0;
            int clientType;
            for(i=0; clientList[i]; i++)
            {
                if(ts3Functions.getClientVariableAsInt(serverConnectionHandlerID, clientList[i], CLIENT_TYPE, &clientType) != ERROR_ok)
                {
                    continue;
                }
                if (clientType == 1)
                {
                    continue;
                }
                if (myID == clientList[i])
                {
                    continue;
                }

                /* Request client kick from server */
                ts3Functions.requestClientKickFromServer(serverConnectionHandlerID, clientList[i], "kick reason", NULL);

                ++clientCount;
            }

            char buffer [50];
            sprintf (buffer, "Requested kick of %d clients!", clientCount);
            ts3Functions.printMessageToCurrentTab(buffer);

            ts3Functions.freeMemory(clientList);
            break;
}

return 0;  /* Plugin handled command */
}

bool isServerGroupIDinList(char* list, uint64 id)
{
    char* token = strtok(list,",");
    uint64 tmp = 0;
    while (token != NULL)
    {
        tmp = (uint64)atoi(token);
        if (id == tmp)
        {
            return true;
        }
        token = strtok(NULL,",");
    }

    return false;
}

bool isClientInList(anyID* clientList, anyID* clientID)
{
    for(int i=0; clientList[i]; i++)
    {
        if (clientList[i] == *clientID)
        {
            return true;
        }
    }

    return false;
}

/*
* Plugin requests to be always automatically loaded by the TeamSpeak 3 client unless
* the user manually disabled it in the plugin dialog.
* This function is optional. If missing, no autoload is assumed.
*/
int ts3plugin_requestAutoload()
{
return 1;  /* 1 = request autoloaded, 0 = do not request autoload */
}


main.h:
#ifndef PLUGIN_H
#define PLUGIN_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "include/public_errors.h"
#include "include/public_definitions.h"
#include "include/public_rare_definitions.h"
#include "include/ts3_functions.h"
#include "include/plugin_events.h"

#ifdef WIN32
#define PLUGINS_EXPORTDLL __declspec(dllexport)
#else
#define PLUGINS_EXPORTDLL __attribute__ ((visibility("default")))
#endif

#ifdef __cplusplus
extern "C" {
#endif

bool isClientInList(anyID* clientList, anyID* clientID);
bool isServerGroupIDinList(char* list, uint64 id);

/* Required functions */
PLUGINS_EXPORTDLL const char* ts3plugin_name();
PLUGINS_EXPORTDLL const char* ts3plugin_version();
PLUGINS_EXPORTDLL int ts3plugin_apiVersion();
PLUGINS_EXPORTDLL const char* ts3plugin_author();
PLUGINS_EXPORTDLL const char* ts3plugin_description();
PLUGINS_EXPORTDLL void ts3plugin_setFunctionPointers(const struct TS3Functions funcs);
PLUGINS_EXPORTDLL int ts3plugin_init();
PLUGINS_EXPORTDLL void ts3plugin_shutdown();

/* Optional functions */
PLUGINS_EXPORTDLL void ts3plugin_registerPluginCommandID(const char* commandID);
PLUGINS_EXPORTDLL const char* ts3plugin_commandKeyword();
PLUGINS_EXPORTDLL int ts3plugin_processCommand(uint64 serverConnectionHandlerID, const char* command);
PLUGINS_EXPORTDLL int ts3plugin_requestAutoload();

#ifdef __cplusplus
}
#endif

#endif


In TS3 client:
/a kickall

Output:
Requested kick of 2 clients!

GodlySOB

Awesome it works! Thanks a lot man! I cant express how grateful I am for this. Its been a pleasure working with you. If you ever need help in something hit me up Ill do my best.