#ifndef __PLUGIN_H
#define __PLUGIN_H
+// log levels
+
#define DEBUG 10
#define VERBOSE 20
#define DEFAULT 30
#define SPARSE 40
#define NONE 50
+// used with OnExtendedMode() method of modules
+
#define MT_CHANNEL 1
#define MT_CLIENT 2
#define MT_SERVER 3
+// used with OnAccessCheck() method of modules
+
+#define ACR_DEFAULT 0 // Do default action (act as if the module isnt even loaded)
+#define ACR_DENY 1 // deny the action
+#define ACR_ALLOW 2 // allow the action
+
+#define AC_KICK 0 // a user is being kicked
+#define AC_DEOP 1 // a user is being deopped
+#define AC_OP 2 // a user is being opped
+#define AC_VOICE 3 // a user is being voiced
+#define AC_DEVOICE 4 // a user is being devoiced
+#define AC_HALFOP 5 // a user is being halfopped
+#define AC_DEHALFOP 6 // a user is being dehalfopped
+#define AC_INVITE 7 // a user is being invited
+#define AC_GENERAL_MODE 8 // a user channel mode is being changed
+
#include "dynamic.h"
#include "base.h"
#include "ctables.h"
* If the mode is a channel mode, target is a chanrec*, and if it is a user mode, target is a userrec*.
* You must cast this value yourself to make use of it.
*/
- virtual bool OnExtendedMode(userrec* user, void* target, char modechar, int type, bool mode_on, string_list ¶ms);
+ virtual int OnExtendedMode(userrec* user, void* target, char modechar, int type, bool mode_on, string_list ¶ms);
/** Called whenever a user is about to join a channel, before any processing is done.
* Returning any nonzero value from this function stops the process immediately, causing no
* module to generate some meaninful output.
*/
virtual int OnUserPreNick(userrec* user, std::string newnick);
+
+ /** Called before an action which requires a channel privilage check.
+ * This function is called before many functions which check a users status on a channel, for example
+ * before opping a user, deopping a user, kicking a user, etc.
+ * There are several values for access_type which indicate for what reason access is being checked.
+ * These are:<br>
+ * AC_KICK (0) - A user is being kicked<br>
+ * AC_DEOP (1) - a user is being deopped<br>
+ * AC_OP (2) - a user is being opped<br>
+ * AC_VOICE (3) - a user is being voiced<br>
+ * AC_DEVOICE (4) - a user is being devoiced<br>
+ * AC_HALFOP (5) - a user is being halfopped<br>
+ * AC_DEHALFOP (6) - a user is being dehalfopped<br>
+ * AC_INVITE (7) - a user is being invited<br>
+ * AC_GENERAL_MODE (8) - a user channel mode is being changed<br>
+ * Upon returning from your function you must return either ACR_DEFAULT, to indicate the module wishes
+ * to do nothing, or ACR_DENY where approprate to deny the action, and ACR_ALLOW where appropriate to allow
+ * the action. Please note that in the case of some access checks (such as AC_GENERAL_MODE) access may be
+ * denied 'upstream' causing other checks such as AC_DEOP to not be reached. Be very careful with use of the
+ * AC_GENERAL_MODE type, as it may inadvertently override the behaviour of other modules. When the access_type
+ * is AC_GENERAL_MODE, the destination of the mode will be NULL (as it has not yet been determined).
+ */
+ virtual int OnAccessCheck(userrec* source,userrec* dest,chanrec* channel,int access_type);
};
* representing the user's privilages upon the channel you specify.
*/
virtual std::string ChanMode(userrec* User, chanrec* Chan);
+ /** Checks if a user is on a channel.
+ * This function will return true or false to indicate if user 'User' is on channel 'Chan'.
+ */
+ virtual bool IsOnChannel(userrec* User, chanrec* Chan);
/** Returns the server name of the server where the module is loaded.
*/
virtual std::string GetServerName();
* server where the module is loaded.
*/
virtual Admin GetAdmin();
- /** Adds an extended mode letter which is parsed by a module
+ /** Adds an extended mode letter which is parsed by a module.
* This allows modules to add extra mode letters, e.g. +x for hostcloak.
* the "type" parameter is either MT_CHANNEL, MT_CLIENT, or MT_SERVER, to
* indicate wether the mode is a channel mode, a client mode, or a server mode.
*/
virtual bool AddExtendedMode(char modechar, int type, bool requires_oper, int params_when_on, int params_when_off);
+ /** Adds an extended mode letter which is parsed by a module and handled in a list fashion.
+ * This call is used to implement modes like +q and +a. The characteristics of these modes are
+ * as follows:
+ *
+ * (1) They are ALWAYS on channels, not on users, therefore their type is MT_CHANNEL
+ *
+ * (2) They always take exactly one parameter when being added or removed
+ *
+ * (3) They can be set multiple times, usually on users in channels
+ *
+ * (4) The mode and its parameter are NOT stored in the channels modes structure
+ *
+ * It is down to the module handling the mode to maintain state and determine what 'items' (e.g. users,
+ * or a banlist) have the mode set on them, and process the modes at the correct times, e.g. during access
+ * checks on channels, etc. When the extended mode is triggered the OnExtendedMode method will be triggered
+ * as above. Note that the target you are given will be a channel, if for example your mode is set 'on a user'
+ * (in for example +a) you must use Server::Find to locate the user the mode is operating on.
+ * Your mode handler may return 1 to handle the mode AND tell the core to display the mode change, e.g.
+ * '+aaa one two three' in the case of the mode for 'two', or it may return -1 to 'eat' the mode change,
+ * so the above example would become '+aa one three' after processing.
+ */
+ virtual bool AddExtendedListMode(char modechar);
+
/** Adds a command to the command table.
* This allows modules to add extra commands into the command table. You must place a function within your
* module which is is of type handlerfunc:
log(DEFAULT,"*** BUG *** give_ops was given an invalid parameter");
return NULL;
}
- if ((status < STATUS_OP) && (!is_uline(user->server)))
+
+ if (!isnick(dest))
+ {
+ log(DEFAULT,"the target nickname given to give_ops was invalid");
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ return NULL;
+ }
+ d = Find(dest);
+ if (!d)
{
- log(DEBUG,"%s cant give ops to %s because they nave status %d and needs %d",user->nick,dest,status,STATUS_OP);
- WriteServ(user->fd,"482 %s %s :You're not a channel operator",user->nick, chan->name);
+ log(DEFAULT,"the target nickname given to give_ops couldnt be found");
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
return NULL;
}
else
{
- if (!isnick(dest))
- {
- log(DEFAULT,"the target nickname given to give_ops was invalid");
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,d,chan,AC_OP));
+
+ if (MOD_RESULT == ACR_DENY)
return NULL;
- }
- d = Find(dest);
- if (!d)
+ if (MOD_RESULT == ACR_DEFAULT)
{
- log(DEFAULT,"the target nickname given to give_ops couldnt be found");
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
- return NULL;
+ if ((status < STATUS_OP) && (!is_uline(user->server)))
+ {
+ log(DEBUG,"%s cant give ops to %s because they nave status %d and needs %d",user->nick,dest,status,STATUS_OP);
+ WriteServ(user->fd,"482 %s %s :You're not a channel operator",user->nick, chan->name);
+ return NULL;
+ }
}
- else
+
+
+ for (int i = 0; i != MAXCHANS; i++)
{
- for (int i = 0; i != MAXCHANS; i++)
+ if ((d->chans[i].channel != NULL) && (chan != NULL))
+ if (!strcasecmp(d->chans[i].channel->name,chan->name))
{
- if ((d->chans[i].channel != NULL) && (chan != NULL))
- if (!strcasecmp(d->chans[i].channel->name,chan->name))
+ if (d->chans[i].uc_modes & UCMODE_OP)
{
- if (d->chans[i].uc_modes & UCMODE_OP)
- {
- /* mode already set on user, dont allow multiple */
- log(DEFAULT,"The target user given to give_ops was already opped on the channel");
- return NULL;
- }
- d->chans[i].uc_modes = d->chans[i].uc_modes | UCMODE_OP;
- log(DEBUG,"gave ops: %s %s",d->chans[i].channel->name,d->nick);
- return d->nick;
+ /* mode already set on user, dont allow multiple */
+ log(DEFAULT,"The target user given to give_ops was already opped on the channel");
+ return NULL;
}
+ d->chans[i].uc_modes = d->chans[i].uc_modes | UCMODE_OP;
+ log(DEBUG,"gave ops: %s %s",d->chans[i].channel->name,d->nick);
+ return d->nick;
}
- log(DEFAULT,"The target channel given to give_ops was not in the users mode list");
}
+ log(DEFAULT,"The target channel given to give_ops was not in the users mode list");
}
return NULL;
}
log(DEFAULT,"*** BUG *** give_hops was given an invalid parameter");
return NULL;
}
- if ((status < STATUS_OP) && (!is_uline(user->server)))
+
+ d = Find(dest);
+ if (!isnick(dest))
+ {
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ return NULL;
+ }
+ if (!d)
{
- WriteServ(user->fd,"482 %s %s :You're not a channel operator",user->nick, chan->name);
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
return NULL;
}
else
{
- d = Find(dest);
- if (!isnick(dest))
- {
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,d,chan,AC_HALFOP));
+
+ if (MOD_RESULT == ACR_DENY)
return NULL;
- }
- if (!d)
+ if (MOD_RESULT == ACR_DEFAULT)
{
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
- return NULL;
+ if ((status < STATUS_OP) && (!is_uline(user->server)))
+ {
+ WriteServ(user->fd,"482 %s %s :You're not a channel operator",user->nick, chan->name);
+ return NULL;
+ }
}
- else
+
+ for (int i = 0; i != MAXCHANS; i++)
{
- for (int i = 0; i != MAXCHANS; i++)
+ if ((d->chans[i].channel != NULL) && (chan != NULL))
+ if (!strcasecmp(d->chans[i].channel->name,chan->name))
{
- if ((d->chans[i].channel != NULL) && (chan != NULL))
- if (!strcasecmp(d->chans[i].channel->name,chan->name))
+ if (d->chans[i].uc_modes & UCMODE_HOP)
{
- if (d->chans[i].uc_modes & UCMODE_HOP)
- {
- /* mode already set on user, dont allow multiple */
- return NULL;
- }
- d->chans[i].uc_modes = d->chans[i].uc_modes | UCMODE_HOP;
- log(DEBUG,"gave h-ops: %s %s",d->chans[i].channel->name,d->nick);
- return d->nick;
+ /* mode already set on user, dont allow multiple */
+ return NULL;
}
+ d->chans[i].uc_modes = d->chans[i].uc_modes | UCMODE_HOP;
+ log(DEBUG,"gave h-ops: %s %s",d->chans[i].channel->name,d->nick);
+ return d->nick;
}
}
}
log(DEFAULT,"*** BUG *** give_voice was given an invalid parameter");
return NULL;
}
- if ((status < STATUS_HOP) && (!is_uline(user->server)))
+
+ d = Find(dest);
+ if (!isnick(dest))
+ {
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ return NULL;
+ }
+ if (!d)
{
- WriteServ(user->fd,"482 %s %s :You must be at least a half-operator to change modes on this channel",user->nick, chan->name);
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
return NULL;
}
else
{
- d = Find(dest);
- if (!isnick(dest))
- {
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,d,chan,AC_VOICE));
+
+ if (MOD_RESULT == ACR_DENY)
return NULL;
- }
- if (!d)
+ if (MOD_RESULT == ACR_DEFAULT)
{
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
- return NULL;
+ if ((status < STATUS_HOP) && (!is_uline(user->server)))
+ {
+ WriteServ(user->fd,"482 %s %s :You must be at least a half-operator to change modes on this channel",user->nick, chan->name);
+ return NULL;
+ }
}
- else
+
+ for (int i = 0; i != MAXCHANS; i++)
{
- for (int i = 0; i != MAXCHANS; i++)
+ if ((d->chans[i].channel != NULL) && (chan != NULL))
+ if (!strcasecmp(d->chans[i].channel->name,chan->name))
{
- if ((d->chans[i].channel != NULL) && (chan != NULL))
- if (!strcasecmp(d->chans[i].channel->name,chan->name))
+ if (d->chans[i].uc_modes & UCMODE_VOICE)
{
- if (d->chans[i].uc_modes & UCMODE_VOICE)
- {
- /* mode already set on user, dont allow multiple */
- return NULL;
- }
- d->chans[i].uc_modes = d->chans[i].uc_modes | UCMODE_VOICE;
- log(DEBUG,"gave voice: %s %s",d->chans[i].channel->name,d->nick);
- return d->nick;
+ /* mode already set on user, dont allow multiple */
+ return NULL;
}
+ d->chans[i].uc_modes = d->chans[i].uc_modes | UCMODE_VOICE;
+ log(DEBUG,"gave voice: %s %s",d->chans[i].channel->name,d->nick);
+ return d->nick;
}
}
}
log(DEFAULT,"*** BUG *** take_ops was given an invalid parameter");
return NULL;
}
- if ((status < STATUS_OP) && (!is_uline(user->server)))
+
+ d = Find(dest);
+ if (!isnick(dest))
+ {
+ log(DEBUG,"take_ops was given an invalid target nickname of %s",dest);
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ return NULL;
+ }
+ if (!d)
{
- log(DEBUG,"%s cant give ops to %s because they have status %d and needs %d",user->nick,dest,status,STATUS_OP);
- WriteServ(user->fd,"482 %s %s :You're not a channel operator",user->nick, chan->name);
+ log(DEBUG,"take_ops couldnt resolve the target nickname: %s",dest);
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
return NULL;
}
else
{
- d = Find(dest);
- if (!isnick(dest))
- {
- log(DEBUG,"take_ops was given an invalid target nickname of %s",dest);
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,d,chan,AC_DEOP));
+
+ if (MOD_RESULT == ACR_DENY)
return NULL;
- }
- if (!d)
+ if (MOD_RESULT == ACR_DEFAULT)
{
- log(DEBUG,"take_ops couldnt resolve the target nickname: %s",dest);
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
- return NULL;
+ if ((status < STATUS_OP) && (!is_uline(user->server)))
+ {
+ WriteServ(user->fd,"482 %s %s :You are not a channel operator",user->nick, chan->name);
+ return NULL;
+ }
}
- else
+
+ for (int i = 0; i != MAXCHANS; i++)
{
- for (int i = 0; i != MAXCHANS; i++)
+ if ((d->chans[i].channel != NULL) && (chan != NULL))
+ if (!strcasecmp(d->chans[i].channel->name,chan->name))
{
- if ((d->chans[i].channel != NULL) && (chan != NULL))
- if (!strcasecmp(d->chans[i].channel->name,chan->name))
+ if ((d->chans[i].uc_modes & UCMODE_OP) == 0)
{
- if ((d->chans[i].uc_modes & UCMODE_OP) == 0)
- {
- /* mode already set on user, dont allow multiple */
- return NULL;
- }
- d->chans[i].uc_modes ^= UCMODE_OP;
- log(DEBUG,"took ops: %s %s",d->chans[i].channel->name,d->nick);
- return d->nick;
+ /* mode already set on user, dont allow multiple */
+ return NULL;
}
+ d->chans[i].uc_modes ^= UCMODE_OP;
+ log(DEBUG,"took ops: %s %s",d->chans[i].channel->name,d->nick);
+ return d->nick;
}
- log(DEBUG,"take_ops couldnt locate the target channel in the target users list");
}
+ log(DEBUG,"take_ops couldnt locate the target channel in the target users list");
}
return NULL;
}
log(DEFAULT,"*** BUG *** take_hops was given an invalid parameter");
return NULL;
}
- if ((status < STATUS_OP) && (!is_uline(user->server)))
+
+ d = Find(dest);
+ if (!isnick(dest))
+ {
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ return NULL;
+ }
+ if (!d)
{
- WriteServ(user->fd,"482 %s %s :You're not a channel operator",user->nick, chan->name);
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
return NULL;
}
else
{
- d = Find(dest);
- if (!isnick(dest))
- {
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,d,chan,AC_DEHALFOP));
+
+ if (MOD_RESULT == ACR_DENY)
return NULL;
- }
- if (!d)
+ if (MOD_RESULT == ACR_DEFAULT)
{
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
- return NULL;
+ if ((status < STATUS_OP) && (!is_uline(user->server)))
+ {
+ WriteServ(user->fd,"482 %s %s :You are not a channel operator",user->nick, chan->name);
+ return NULL;
+ }
}
- else
+
+ for (int i = 0; i != MAXCHANS; i++)
{
- for (int i = 0; i != MAXCHANS; i++)
+ if ((d->chans[i].channel != NULL) && (chan != NULL))
+ if (!strcasecmp(d->chans[i].channel->name,chan->name))
{
- if ((d->chans[i].channel != NULL) && (chan != NULL))
- if (!strcasecmp(d->chans[i].channel->name,chan->name))
+ if ((d->chans[i].uc_modes & UCMODE_HOP) == 0)
{
- if ((d->chans[i].uc_modes & UCMODE_HOP) == 0)
- {
- /* mode already set on user, dont allow multiple */
- return NULL;
- }
- d->chans[i].uc_modes ^= UCMODE_HOP;
- log(DEBUG,"took h-ops: %s %s",d->chans[i].channel->name,d->nick);
- return d->nick;
+ /* mode already set on user, dont allow multiple */
+ return NULL;
}
+ d->chans[i].uc_modes ^= UCMODE_HOP;
+ log(DEBUG,"took h-ops: %s %s",d->chans[i].channel->name,d->nick);
+ return d->nick;
}
}
}
log(DEFAULT,"*** BUG *** take_voice was given an invalid parameter");
return NULL;
}
- if ((status < STATUS_HOP) && (!is_uline(user->server)))
+
+ d = Find(dest);
+ if (!isnick(dest))
+ {
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ return NULL;
+ }
+ if (!d)
{
- WriteServ(user->fd,"482 %s %s :You must be at least a half-operator to change modes on this channel",user->nick, chan->name);
+ WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
return NULL;
}
else
{
- d = Find(dest);
- if (!isnick(dest))
- {
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,d,chan,AC_DEVOICE));
+
+ if (MOD_RESULT == ACR_DENY)
return NULL;
- }
- if (!d)
+ if (MOD_RESULT == ACR_DEFAULT)
{
- WriteServ(user->fd,"401 %s %s :No suck nick/channel",user->nick, dest);
- return NULL;
+ if ((status < STATUS_HOP) && (!is_uline(user->server)))
+ {
+ WriteServ(user->fd,"482 %s %s :You must be at least a half-operator to change modes on this channel",user->nick, chan->name);
+ return NULL;
+ }
}
- else
+
+ for (int i = 0; i != MAXCHANS; i++)
{
- for (int i = 0; i != MAXCHANS; i++)
+ if ((d->chans[i].channel != NULL) && (chan != NULL))
+ if (!strcasecmp(d->chans[i].channel->name,chan->name))
{
- if ((d->chans[i].channel != NULL) && (chan != NULL))
- if (!strcasecmp(d->chans[i].channel->name,chan->name))
+ if ((d->chans[i].uc_modes & UCMODE_VOICE) == 0)
{
- if ((d->chans[i].uc_modes & UCMODE_VOICE) == 0)
- {
- /* mode already set on user, dont allow multiple */
- return NULL;
- }
- d->chans[i].uc_modes ^= UCMODE_VOICE;
- log(DEBUG,"took voice: %s %s",d->chans[i].channel->name,d->nick);
- return d->nick;
+ /* mode already set on user, dont allow multiple */
+ return NULL;
}
+ d->chans[i].uc_modes ^= UCMODE_VOICE;
+ log(DEBUG,"took voice: %s %s",d->chans[i].channel->name,d->nick);
+ return d->nick;
}
}
}
return;
}
+ int MOD_RESULT = 0;
+ FOREACH_RESULT(OnAccessCheck(user,NULL,chan,AC_GENERAL_MODE));
+
+ if (MOD_RESULT == ACR_DENY)
+ return;
+
log(DEBUG,"process_modes: start: parameters=%d",pcnt);
strcpy(modelist,parameters[1]); /* mode list, e.g. +oo-o */
p.clear();
if (((!strchr(chan->custom_modes,modechar)) && (!mdir)) || ((strchr(chan->custom_modes,modechar)) && (mdir)))
{
- log(DEBUG,"Mode %c isnt set on %s but trying to remove!",modechar,chan->name);
- break;
+ if (!ModeIsListMode(modechar,MT_CHANNEL))
+ {
+ log(DEBUG,"Mode %c isnt set on %s but trying to remove!",modechar,chan->name);
+ break;
+ }
}
if (ModeDefined(modechar,MT_CHANNEL))
{
{
if (!handled)
{
- if (modules[i]->OnExtendedMode(user,chan,modechar,MT_CHANNEL,mdir,p))
+ int t = modules[i]->OnExtendedMode(user,chan,modechar,MT_CHANNEL,mdir,p);
+ if (t != 0)
{
log(DEBUG,"OnExtendedMode returned nonzero for a module");
char app[] = {modechar, 0};
- if (ptr>0)
+ if (ModeIsListMode(modechar,MT_CHANNEL))
{
- if ((modelist[ptr-1] == '+') || (modelist[ptr-1] == '-'))
+ if (t == -1)
{
- strcat(outlist, app);
+ pc++;
}
- else if (!strchr(outlist,modechar))
+ else
{
- strcat(outlist, app);
+ if (ptr>0)
+ {
+ strcat(outlist, app);
+ }
+ strcpy(outpars[pc++],parameters[param++]);
}
}
- chan->SetCustomMode(modechar,mdir);
- // include parameters in output if mode has them
- if ((ModeDefinedOn(modechar,MT_CHANNEL)>0) && (mdir))
+ else
{
- chan->SetCustomModeParam(modelist[ptr],parameters[param],mdir);
- strcpy(outpars[pc++],parameters[param++]);
+ if (ptr>0)
+ {
+ if ((modelist[ptr-1] == '+') || (modelist[ptr-1] == '-'))
+ {
+ strcat(outlist, app);
+ }
+ else if (!strchr(outlist,modechar))
+ {
+ strcat(outlist, app);
+ }
+ }
+ chan->SetCustomMode(modechar,mdir);
+ // include parameters in output if mode has them
+ if ((ModeDefinedOn(modechar,MT_CHANNEL)>0) && (mdir))
+ {
+ chan->SetCustomModeParam(modelist[ptr],parameters[param],mdir);
+ strcpy(outpars[pc++],parameters[param++]);
+ }
}
// break, because only one module can handle the mode.
handled = true;