README for io.dll v0.8.1, I/O filter and command injection DLL for mIRC
Copyright (c) 2004-2007, David "Saturn" van Moolenbroek <saturn@quakenet.org>
This version of the DLL supports mIRC versions 6.14 - 6.3


1.     Introduction
2.     Description
2.1.     Rules and groups
2.2.     Rule matching
2.3.     The callback alias
2.4.     The IO alias
2.5.     Multi-server support
2.6.     Preserving spaces
3.     Commands
3.1.     ADD
3.2.     DEL
3.3.     GET
3.3.1.     GET DATA
3.3.2.     GET ID
3.3.3.     GET PARAMS
3.3.4.     GET RULE
3.4.     SET
3.4.1.     SET DATA
3.5.     RECV
3.6.     SEND
3.7.     NOOP
4.     Examples
4.1.     Faking a ping timeout
4.2.     Replacing swear words
4.3.     Alternative debug window
4.4.     Listing the size of a channel
4.5.     Filling the IAL with WHOX
5.     DLL support
5.1.     DLLAPI
5.2.     Exported functions
5.2.1.     CONN
5.2.2.     ADD
5.2.3.     DEL
5.2.4.     RECV
5.2.5.     SEND
5.3.     The ioapi.h file
6.     License and disclaimer
7.     Source code
8.     Version history


1. Introduction

  Once upon a time, on a private channel filled with true masterminds, the
  following enlightening conversation took place about mIRC, the program they
  all loved so much:

    <@k0sm0s> hmm, kinda miss a on SEND_LINE_TO_SERVER event
    <@Saturn> yea.. and i want a RECV_LINE_FROM_SERVER event too
    ...
    <@k0sm0s> hmm, maybe we should make a dll to hack core :]

  A few days later, one of the wizards had completed the journey into the deep
  internals of mIRC, and they nobly set out to spread the merits of their hard
  labour throughout the world; enabling anyone to enjoy the same power that
  they now possessed, and expecting nothing but mere gratitude in return:

    <@Voronoi> let's ask money for it
    <@Voronoi> I get 50% for the idea

  Yes, reality can be a bitch sometimes!

  Thanks go out to k0sm0s and Voronoi for helping this DLL become reality.


2. Description

  This DLL allows scripts to examine, and optionally block, any message that
  mIRC sends to, or receives from, IRC servers. Additionally, the DLL allows
  scripts to tell mIRC to pretend that it received a specific command from the
  IRC server.

  This is done by hooking into some of the internal functions of mIRC. The
  main advantage of hooking directly into mIRC, as opposed to for example
  hooking into winsock functions, is that this DLL also works for connections
  over SSL. The downside of this approach is that the DLL will only work for
  the mIRC versions it is written for. The range of supported mIRC versions
  by this copy of the DLL, can be found at the top of this README.

  The level of this DLL is ADVANCED. Please send bugs, comments and feature
  suggestions to the e-mail address at the top of this file, but if you e-mail
  the author regarding how to use the DLL, you might not get an answer. You
  can always try, though.

  Note that using this DLL is perfectly legal. Writing the DLL has been made
  possible by reverse engineering parts of mIRC; even though the mIRC license
  states that this is forbidden, the European law (applicable to the author
  of this DLL) explicitly allows reverse engineering of computer programs in
  order to achieve interoperability, in summary.

2.1. Rules and groups

  The initial version of this DLL passed all incoming and outgoing messages to
  (respectively) $in and $out script aliases. However, this turned out to be
  rather impractical and slow. So, in order to allow scripts to easily catch
  and block incoming/outgoing messages, this new version uses a system of
  rules and groups.

  A rule consists of a matchtext, a definition of what alias to call when the
  rule is triggered, and some flags. Everytime mIRC gets an incoming or
  outgoing message, the DLL tries to find matching rules, and if found, it
  executes them by calling the associated script alias. This script alias then
  determines whether or not to block the message. If the script alias blocks
  the message, no other matching rules will be fired. For details on how to
  add rules, see section 3.1 on the ADD command.

  Each rule is always part of exactly one group - the ADD command can either
  add the new rule to an existing group, or create a new group to put the new
  rule in. In other words, a group is a collection of one or more rules;
  groups are identified using ID numbers. Rules are always added to the end of
  the list of rules for a group, and groups are always added to the end of the
  list of groups. This means that by blocking a message, an earlier created
  rule automatically prevents a later created rule from being fired, so groups
  and their rules are dealt with on a "first come, first serve" basis.

  This whole concept of groups may seem a bit weird and unnecessary at first,
  but is actually a very convenient way of dealing with rules in practice. For
  two practical examples on how to take advantage of groups, see sections 4.4
  and 4.5.

  Neither rules nor groups are saved anywhere else than in memory. This means
  that all rules and groups will be gone after the I/O DLL is unloaded, and
  there will always be zero rules and zero groups when the I/O DLL is loaded.
  There are no default rules or groups.

2.2. Rule matching

  As indicated above, each rule has a matchtext which is a wildcard string.
  Additionally, a rule is set to either trigger on incoming messages from the
  IRC server to mIRC (called INPUT), outgoing messages from mIRC to the IRC
  server (called OUTPUT), or both.

  The matchtext can be set to either match the whole input/output message
  line, or only the command part of the line. The IRC protocol (RFC 1459)
  specifies these two types of messages:

  COMMAND [destination] parameters
  :source COMMAND destination parameters

  If a rule's matchtext is set to match against the command only, the DLL will
  compare it with the COMMAND part of the messages above. The distinction
  between the two is made by looking whether the first character of the line
  is a ':'. The command matchtext can also contain wildcards, but is still
  somewhat faster than a full line matchtext. Of course, the command can also
  be a numeric.

2.3. The callback alias

  When a certain rule matches an input or output message line, its associated
  callback alias is called. This alias is specified when adding the rule, see
  section 3.1 for details. The alias is called like this:

    $aliasname(messageline,groupID,[parameters])

  This means that:

    $1  contains the message line that is about to be sent or received. It
        really contains all of it, including commas and spaces. Don't confuse
        $1 with $1- here! You can, however, use "tokenize 32 $1" to extend the
        line to $1-, but then you lose the original $2 and $3 (see below), as
        well as double consecutive spaces that were originally in $1.
    $2  contains the ID number of the group that the triggering rule belongs
        to. This allows for easy access to the group's user-specified
        group data. These data can also be obtained using the "GET DATA"
        command though, see section 3.3.1 for details.
    $3  contains optional parameters that were specified along with the alias
        when the rule was added. The same parameters can be obtained using the
        "GET PARAMS" command, see section 3.3.3 for details.

  If this callback alias returns the value "1", then the message line will be
  blocked; that is, not sent the the IRC server or not received by mIRC. Also,
  as indicated in section 2.1, no other rules will be fired in that case. If
  the alias returns "2", then the line will also be blocked, but additionally,
  the group to which the triggered rule belongs will be deleted. If the
  callback alias returns "0" or nothing, the line will be passed on just as
  usual.

  Important note: the alias must be global, so do not use "alias -l". This is
  because the DLL can not determine what script the rule was created from, nor
  is it currently able to define what script the callback alias should be in.
  This of course also means you'll have to make sure that your aliasname is
  unique, so there is no other alias with the same name in your other scripts.

2.4. The IO alias

  In the rest of this document, I will heavily rely upon the presence of the
  following script alias:

    alias io return $dll(io.dll,$$1,$2-)

  This alias must be put in one of your scripts' Remote files, and the io.dll
  file must be present in your mIRC directory. You are of course free not to
  use this alias, or use a modified version, in your own scripts - but do
  remember it's being used like that in all of the examples below.

2.5. Multi-server support

  Multiple server connections are supported by this DLL: rules are by default
  specific to one server connection only, so they will only be triggered for
  messages sent from and received for that server connection. If the DLL
  detects that mIRC is disconnecting or being disconnected from a server, it
  will automatically remove all rules belonging to that connection.

  The exception is rules that are explicitly created to apply to all server
  connections, such rules will only be removed using an explicit 'del'
  call (see section 3.2).

2.6. Preserving spaces

  A short note about preserving multiple consecutive spaces: it is entirely
  possible to do this - as mentioned, the $1 parameter of the callback alias
  contains the unmodified incoming/outgoing line including all spaces, and if
  you call the 'recv' and 'send' commands (see sections 3.5 and 3.6) with $io
  instead of /io, then any multiple consecutive and leading/trailing spaces in
  the given line will be preserved. Needless to say, you have to know what
  you're doing or you might lose the spaces anyway. The examples in section 4
  do not preserve spaces in order to keep the code a bit more readable.


3. Commands

  Now that you (hopefully) have a basic idea of how the DLL works, let's move
  on to the list of commands it supports.

  None of the commands return error messages if executing them fails; that
  should, however, not really be a problem because errors should never occur
  if you use the commands correctly. Note that error handling will probably be
  improved in the next version if this DLL. If no "Return value" is specified
  explicitly in the reference below, a command will simply return nothing.

  However, all of the commands will behave as "/halt" if the DLL was loaded on
  an unsupported mIRC version, in which case it wasn't able to initialize.

3.1. ADD

  The ADD command adds a rule to a new or existing group.

  Syntax:

    /io add [-flags] [groupID] <matchtext> [alias [params]]

  Parameters:

    flags

      An optional set of flags that define properties of the rule. This can be
      a combination of the following flags:

        i  This is an INPUT rule, it is checked for incoming messages.
        o  This is an OUTPUT rule, it is checked for outgoing messages.

        m  Make a new group to add this rule to. If specified, the [groupID]
           field must be omitted (see below).

        a  Apply this rule to all server connections, instead of just the
           current one. This rule will not be removed when you disconnect; see
           section 2.5 for details.

        n  Trigger this rule only once, by deleting the rule's GROUP after it
           has been triggered. This flag is typically used on the rule that
           matches the last of a set of numerics (End of /LIST, End of /WHO,
           End of /WHOIS, etc).

      Both -i and -o can be present, in which case the rule is checked for
      both incoming and outgoing messages. If neither -i nor -o are specified,
      the rule is set to INPUT.

    groupID

      The ID of the group to add this rule to; it must only be present if the
      -m flag was NOT specified.

    matchtext

      A string that may contain wildcards, and which is used to match against
      incoming and/or outgoing messages. If this field is surrounded by quotes
      (in which case it may contain spaces), it is assumed to be a matchtext
      for the whole line. If no quotes are present (and the matchtext is
      therefore a single word), it is assumed to be a matchtext for the
      message's command only; see also section 2.2.

      This DLL's wildcard string matching is identical to mIRC's; the '*', '?'
      and '&' wildcards are supported, and as in mIRC, the '&' matches (the
      rest of) exactly one word. Character comparison is case-insensitive.

    alias

      The name of the alias to call. See section 2.3 on how this alias is
      called exactly. If no alias name is specified, the rule defaults to
      BLOCK.

    params

      Optional parameters to pass to the alias, see section 2.3 for details.

  Return value:

    The ID number of the group that this rule was added to, or $null in case
    of a failure. This value can be retrieved by either calling the /io alias
    as an identifier ($io(add,..)), or, by calling it as a command (as above)
    and using mIRC's built-in $result identifier to obtain the group ID.

3.2. DEL

  The DEL command deletes the first matching rule from a group, deleting the
  group completely if there are no rules left in it; the command can also be
  used to delete a whole group.

  Syntax:

    /io del [-flags] <groupID> [matchtext]

  Parameters:

    flags

      An optional set of flags, which can be a combination of the following:

        i  Look for an INPUT rule to delete.
        o  Look for an OUTPUT rule to delete.

        a  Delete rules added with the -a flag.

    groupID

      The ID number of the group to delete the rule from.

    matchtext

      The matchtext as specified in the ADD command. The first rule in the
      group with this matchtext will be deleted. If this field is omitted,
      the whole group specified by 'groupID' will be deleted; in that case,
      the 'flags' field is ignored.

3.3. GET

  The GET command allows you to retrieve certain information from the DLL. It
  has a number of subcommands, which are listed below.

3.3.1. GET DATA

  The GET DATA subcommand allows you to retrieve the user-defined data
  previously assigned to a certain group using the "SET DATA" command
  (see also section 3.3.1).

  Syntax:

    $io(get,data [groupID])

  Parameters:

    groupID

      The ID number of the group to retrieve the group data for. If this
      command is called from the callback alias of a triggered rule, the
      groupID parameter may be omitted, in which case the group data for that
      rule's group will be returned.

  Return value:

    The group's group data, or $null if none was set.

3.3.2. GET ID

  The GET ID subcommand returns the group ID for the currently executing
  callback alias. It is equal to the initial $2 parameter of that alias.

  Syntax:

    $io(get,id)

  Return value:

    The group ID, or $null if no callback alias is currently being evaluated.

3.3.3. GET PARAMS

  The GET PARAMS subcommand returns the parameters associated with the
  currently executing callback alias. It is equal to the initial $3 parameter
  of that alias, see section 2.3 for details.

  Syntax:

    $io(get,params)

  Return value:

    The parameters

3.3.4. GET RULE

  The GET RULE subcommand lets you enumerate the rules currently present in
  the DLL. Note that you can only enumerate groups implicitly.

  Syntax:

    $io(get,rule <number>)

  Parameters:

    number

      If this value is zero, the command will return the number of rules; if a
      positive integer between 1 and the number of rules (inclusive) is
      specified, data for that rule will be returned in the format below.

  Return value:

    If the specified number is zero, the number of rules are returned.
    Otherwise, the rule with that number will be returned, in the following
    format:

      <groupID> <flags> <cid> <matchtext> [alias [params]]

    The fields should be pretty self-explanatory; the flags field can contain
    any of the following flags: a, d, i, n, o. The 'd' flag means that this
    rule has been set by a DLL (see section 5). For the other flags, see
    section 3.1.

3.4. SET

  The SET command allows you to set certain data. At this time, only the SET
  DATA subcommand is supported.

3.4.1. SET DATA

  The SET DATA subcommand lets you associate custom data to a certain group;
  these data can be retrieved using the GET DATA command later (see also
  section 3.3.1).

  Syntax:

    /io set data <groupID> [data]

  Parameters:

    groupID

      The ID number of the group to set the groupdata for. Unlike with the GET
      DATA command, the group ID is mandatory here.

    data

      The custom data to associate with the specified group.

3.5. RECV

  The RECV command lets the I/O DLL tell mIRC that it has received a message
  line from the IRC server. It will return when mIRC has dealt with the
  command.

  Syntax:

    /io recv <line>

  Parameters:

    line

      The message line that mIRC receives.

  Notes:

    Message lines sent this way will NOT pass through the rules system of the
    I/O DLL, but rather be received by mIRC directly. However, As with any
    incoming messages, associated mIRC events are triggered for this message,
    and the message will also be logged to the debug window, if present.

    This command can not be called from a critical event within mIRC.

3.6. SEND

  The SEND command lets the I/O DLL tell mIRC that it should send a message to
  the IRC server.

  Syntax:

    /io send <line>

  Parameters:

     line

       The message line to send to the IRC server.

  Notes:

    This command is different from mIRC's /raw command only at the point that
    it will NOT apply any rules to message lines sent this way.

    As a sidenote, it is indeed possible to recursively trigger rules from
    a rule's callback alias by using /raw, although the I/O DLL has a safety
    recursion limit check hardcoded at a maximum depth of 100. This should
    really be sufficient to anybody.

3.7. NOOP

  The NOOP command does.. nothing.

  Syntax:

    /io noop

  Notes:

    Usually, the ADD command will be the first I/O command that a script
    executes. Any /io command that uses the I/O DLL, will also load the DLL
    first; mIRC does that automatically for the script. However, if you want
    to add rules from your own DLL instead (see section 5), the I/O DLL has to
    be loaded by mIRC first. This is where the NOOP command comes in: it can
    load the I/O DLL, without actually doing anything else. This saves you
    from having to use ugly hacks to get the I/O DLL loaded, prior to loading
    your own DLL.

    If you're not using this I/O DLL in combination with your own custom DLL,
    then this command will indeed be of no use to you.


4. Examples

  In order to illustrate how the commands listed above can be used in
  practice, here are a few examples that you can paste in Remote (without the
  line numbers of course).

  I provided alot of documentation along with the examples, to show how I came
  to writing the examples like this, how things could be done differently, and
  how the ideas behind them can be used for other, similar script snippets.

  Important note: all of the examples assume that the /io alias from section
  2.4 is present. No other conditions are required to get these script
  snippets to work.

4.1. Faking a ping timeout

  If you're directly connected to an IRC server, and not using a bouncer or
  something similar, you can fake a "Ping timeout" by not replying with a PONG
  to the PING command that the IRC server regulary sends to you to see if your
  mIRC client is still there.

     1.  alias pingtimeout { io add -om PONG }

  If you call /pingtimeout, a new rule will be added. The -o switch means it
  is an OUTPUT rule, affecting only messages sent by mIRC to the IRC server.
  The -m switch means a new group for this rule is to be created; this makes
  sense, because we didn't have any group yet to add the rule to. Finally,
  PONG indicates that we want this rule to match only PONG commands, that is,
  only outgoing PONG commands.

  Because we did not specify a callback alias, the behaviour of this rule is
  to BLOCK (as mentioned in section 3.1): whenever the I/O DLL sees that mIRC
  sends a PONG command to the IRC server, it will block this command. That
  way, the IRC server will never receive a PONG to the PING it will send, and
  eventually disconnect you with a "Ping timeout" error.

  You could also block incoming PINGs instead (io add -im PING), to achieve
  the same, but I prefer blocking the PONG because then you can still see the
  last "Ping? Pong?" in your status window. If you block the PING instead,
  mIRC will never know that the IRC server sent a PING command.

  Another option would be to add the -n flag here, so that the rule only works
  once; there is no real advantage or disadvantage in doing so though, as you
  will be disconnected after blocking one PONG anyway.

4.2. Replacing swear words

  Another example of a rule affecting outgoing messages, this time to modify
  messages on-the-fly: this example changes all occurrences of the swear word
  "fuck" in messages you send, into the more friendly "*peep*". Although this
  can also be achieved using "on INPUT" events, this way of doing so
  guarantees that ALL occurrences of the swear word will be replaced in
  outgoing messages, which also includes the ones sent with the /msg command.

     1.  on *:START:{ io add -amo "PRIVMSG & :*fuck*" antiswear }
     2.  alias antiswear {
     3.    tokenize 32 $1
     4.    io send $1-2 $replace($3-,fuck,*peep*)
     5.    return 1
     6.  }

  On line 1, a new rule is added right after mIRC is launched. This rule can
  be added at many places, but doing it at the start makes sense because we
  don't need to differentiate between various server connections in this
  example. This is closely related to the presence of the -a switch here: this
  flag makes the rule affect all server connections, permanently. Once again,
  the -m switch creates a new group to put this rule in, and the -o switch
  defines that this is an OUTPUT rule.

  The next field in the ADD command is the matchtext. Because I surrounded the
  matchtext by quotes ("PRIVMSG & :*fuck*"), the DLL knows it should match
  this against the whole line. Constructing such a matchtext involves a bit of
  knowledge of how the IRC protocol works; in most cases however, watching the
  debug window (created using /debug @mydebugwindow, for example) is
  sufficient to find out what's happening. In this case, all we need to know
  is that mIRC sends a message using the format "PRIVMSG <destination>
  :<text>", and the <text> bit here could include the swear word we'd like to
  replace.

  The last part of the ADD command is "antiswear", which is the name of the
  alias that will be called when the rule triggers (in other words, when we
  write "fuck" somewhere). We don't need any extra parameters in this case.

  On lines 2-6, we define what should happen when our rule triggers. First of
  all, we want to look at various parts of the message line (which is
  contained in $1, as mentioned in section 2.3), so we expand $1 to $1- by
  splitting up the line into space-separated words. Because of the matchtext
  we used, we know that $1 is now "PRIVMSG", $2 is the destination to which
  the message is being sent, and $3- is the text (including the leading ':'
  character).

  Using "/io send", we send a different message to the IRC server, this time
  replacing all occurrences of the word "fuck" with "*peep*" in the message
  text; we don't touch $1-2 as $2 just *may* contain "fuck" as part of the
  destination nickname/channel.

  Finally, we return 1 from the alias, which tells the DLL to block the
  original message that contains the swear word(s).

  A few notes. First of all, what if what if we wanted to replace more than
  one swear word? There are basically two options: either we add one rule for
  each swear word (possibly but not necessarily all to the same group), all
  calling the /antiswear callback alias, and we extend the $replace to replace
  more swear words than just "fuck", OR, we create one rule to catch ALL
  outgoing PRIVMSGs (using "*" instead of "*fuck*") and always use $replace on
  all outgoing text. I prefer the former, as the latter tends to slow down
  mIRC (a script will be executed everytime you write a line). These are
  pretty much the only options you have, as the I/O DLL does not support
  multiple matchtexts for one rule in any way.

  The same principle applies if you'd also like to replace "fuck" in notices;
  in that case, the choice is easily made: it is very easy to simply add a
  second rule that is set to match "NOTICE & :*fuck*". The /antiswear alias
  need not be changed in that case.

  A rather similar script snippet can be used to replace swear words in all
  INCOMING text. It would involve the -i switch instead of -o, a matchtext
  like ":& PRIVMSG & :*fuck*", and "/io recv" instead of "/io send"; I'll
  leave the exact implementation up to the reader.

4.3. Alternative debug window

  Here is an example that shows how you can imitate mIRC's debug window with a
  few simple commands. It is not useful in practice, but it shows how to use
  some of this DLL's features effectively.

     1.  on ^*:LOGON:*:{
     2.    io add -im * debuglog <-
     3.    io add -o $result * debuglog ->
     4.  }
     5.  alias debuglog { echo @debug $3 $1 }

  This example assumes that there already is a custom window called "@debug".

  The "on LOGON" event is used, with the ^ prefix, to ensure all of the text,
  including the NICK/USER pair, gets logged to our @debug window. On line 2,
  we add a new INPUT (-i) rule and put it in a new group (-m); the matchtext
  is set to * so ALL incoming messages will be passed to the "debuglog" alias
  that follows the matchtext in the ADD command; finally, we also specify a
  parameter "<-" along with the debuglog alias. Compare the command with the
  syntax of the ADD command mentioned in section 3.1 if you're not sure which
  parameter is what.

  Remember from the specification of the ADD command, that this command
  returns the number of the group that the new rule was added to. As we called
  the add command using /io (as opposed to $io()), that group number is now
  automatically stored in mIRC's built-in $result identifier. We now add a
  second rule, for all OUTPUT (-o), and add it to the group

  Another way of doing the same would be:

    var %id = $io(add,-im * debuglog <-)
    io add -o %id * debuglog ->

  But I prefer the somewhat neater coding style above. This is of course up to
  you when you're writing your own scripts. Note that you can also create a
  new group for the output rule (using "-om" instead of "-o $result"), without
  any functional difference. Such choices are up to you as well.

  Now that we created two rules, one for all input and one for all output, the
  "debuglog" alias on line 5 will be called for each incoming and outgoing
  message line. Remember from section 2.3 that $1 holds the incoming or
  outgoing line, and $3 holds the parameters passed to the ADD command.
  Therefore, $3 holds "<-" for incoming lines and "->" for outgoing lines. So
  using the /echo command on line 5, the message line will be printed in the
  @debug window, prefixed by either "<-" in case of an incoming message, or
  "->" in case of an outgoing message, just like mIRC's built-in debug window.

4.4. Listing the size of a channel

  On to a more advanced example, which shows how to overcome a problem with
  standard mIRC: the inability to prevent the Channels List window from
  appearing. An IRC server will reply to a /list command with a 321 numeric
  (list header), zero or more 322 numerics (one for each channel), and a 323
  numeric (end of list). mIRC always opens the Channels List window upon
  receipt of a 321 numeric; this example shows how to get around that problem
  and list the number of users of one channel without letting mIRC open the
  Channels List window.

     1.  alias chansize {
     2.    io add -im 321
     3.    io add -i $result 322 csset
     4.    io add -in $result 323 csres $1
     5.    io send list $1
     6.  }
     7.  alias csset {
     8.    io set data $2 $gettok($1,5,32)
     9.    return 1
    10.  }
    11.  alias csres {
    12.    if ($io(get,data) isnum) $&
    13.     echo $color(info) -at * $3 has $ifmatch users
    14.    else echo $color(info) -at * unable to list $3
    15.    return 1
    16.  }

  You can display the size of a channel using "/chansize #channel". The
  chansize alias adds three rules: one for each numeric mentioned above. They
  are all added to the same group (see also the previous example), and the
  last numeric (323) is created with the -n flag, which means its group will
  be deleted when it is triggered. There always will be a 323 numeric, and it
  will always be the last one, so this construction has the advantage that the
  set of rules will be deleted after the list reply is received.

  This idea can be applied to every set of numerics, when there's always at
  least one numeric that terminates the list: all of the rules for the
  numerics should be added to the same group, and then the last numeric
  "cleans up" by removing the whole group by means of the -n switch. Another
  advantage in this case is that the resulting commands can be "queued": you
  can execute two /chansize requests right after each other, and the created
  rules won't interfere with eachother because the second group will always be
  searched through for matching rules, AFTER the first group. Because all of
  the rules in the group block, the second group will never be looked at while
  the first request is still in progress. If you save all data relevant to the
  request as groupdata, your script won't even have to keep state in any way.

  Anyway, that's the general idea, and it's also exactly what this example
  does. The 321 numeric is simply blocked, the 322 numeric is passed to the
  /csset alias, and the 323 numeric is passed to the /csres alias. Both of
  these aliases also block the reply, by returning 1.

  The /csset alias is either called once, or not at all, because we requested
  a list on a specific channel on line 5. If this alias is called, it stores
  the number of users on the channel (the 5th parameter of the 322 reply which
  is stored in $1) as groupdata (for the group ID stored in $2). Note that
  the /csset alias does not actually check whether the returned channel is the
  one we requested using the list request on line 5; even if we performed such
  a check, it would have no use, as we've already blocked the 321 numeric
  anyway. If this was not a reply from OUR list command (and instead, the user
  requesting a list of all channels, for example), we're screwed. But that is
  not really likely to happen anyway.

  The /csres alias displays the result: if the /csset alias was called, then
  the groupdata contains the number of users on the channel; otherwise it is
  still $null as it was never touched. That is what the if-check on line 12
  checks for. Once again, $3 contains the requested channelname because that
  was specified as parameter on line 4. After the /csres alias is finished,
  the group and all of its rules will be deleted, including the groupdata,
  so there is nothing left in memory relating to the /chansize request. Nice,
  don't you think? We haven't had to use a single variable.

4.5. Filling the IAL with WHOX

  The following script snippet was one of the main motivations for me to write
  this DLL. It is designed to work with IRC servers that support the WHOX
  protocol, which allows you to create custom /who queries. This example shows
  how to use the I/O DLL to update mIRC's IAL using WHOX queries, something
  that is otherwise impossible because there is no way to update the IAL by
  means of a script. In short, the snippet intercepts WHOX replies and turns
  them into (faked) normal /who replies which are then passed to mIRC. The
  advantage? You can retrieve many more entries on large channels, as you're
  requesting less fields (see the WHOX documentation on this), and you can
  even change this snippet to additionally request, for example, the users'
  QuakeNet authname, and use those data in other parts of your script.

     1.  raw 366:*:if ($chan($2).ial == $false) ialwho $2
     2.  alias ialwho {
     3.    who $$1 $+ ,999 c%nuht,999
     4.    io add -im ":& 354 & 999 *" ialset $1
     5.    io add -in $result ":& 315 & $1 $+ ,999 :*" ialset $1
     6.  }
     7.  alias ialset {
     8.    tokenize 32 $1
     9.    set -u0 %ialwho 1
    10.    io get params
    11.    if ($2 == 354) io recv $1 352 $3 $result $5-6 *.* $7 x :0 *
    12.    else io recv $1 315 $3 $result :End of /WHO
    13.    return 1
    14.  }
    16.  raw 352:*:if (%ialwho) halt
    17.  raw 315:*:if (%ialwho) halt

  The script defines an /ialwho alias, that can be used as "/ialwho #channel".
  Line 1 merely shows an example of how to apply this in practice: it performs
  an /ialwho request from raw 366 (End of /NAMES) if the IAL for that channel
  is not already filled.

  Line 3 executes a rather complicated WHOX request, and lines 4 and 5 set the
  rules to catch the replies. The ",999" bit is only there to catch raw 315
  (End of /WHO) without mistaking another 315 numeric for it: the same ",999"
  bit can be seen on line 5. The "nuht,999" bit requests the resulting users'
  nick, userid, hostname and the query ID 999. This query ID is also used to
  ensure we're only catching the right replies, but this time it's for WHOX's
  354 numerics; the same "999" can be seen on as fourth parameter on line 4.
  Just to make sure: those 999's for raw 354 and raw 315 are different
  numbers, and you can use two completely different values.

  The same idea from the previous example is applies: a WHOX reply consists of
  zero or more 354 numerics, and one final 315 numeric; hence, the rule that
  catches the 315 numeric is once again added with the -n flag (see line 5).
  Note that both rules use the same callback alias called ialset, and the same
  parameter which is the requested channelname.

  The /ialset alias now converts the received replies to a format that mIRC
  can handle; lines 11 and 12 use "io recv" on the converted version of the
  354 and the 315 numeric, respectively. The parameters retrieved into $result
  on line 10 consist of the channelname, which must be used in the replies
  mIRC receives, but aren't actually present in the WHOX replies. Using "io
  get params" is necessary as we threw away $3 by expanding $1 to $1- on line
  8. After the new, faked /who replies are received by mIRC, the alias blocks
  the original WHOX replies by returning 1 on line 13.

  Lines 16 and 17, finally, ensure that your status window won't be cluttered
  by the fake /who replies, by halting any 352 and 315 numeric while the
  %ialwho variable is set. This variable has been set on line 9, and remains
  set only as long as the script runs. An important observation here is that
  the raws on lines 16 and 17 actually will be triggered as a direct result of
  the "io recv" commands on lines 11 and 12, respectively; so here you can see
  how scripts can be stacked. Indeed, you can also use /unset to unset %ialwho
  right after line 12, because the raws have already been finished by then.
  It's a somewhat unusual way of using scripts, but, to quote a CS teacher of
  mine, it works like hell.


5. DLL support

  If mIRC's scripting language is somehow not sufficient for you to deal with
  certain incoming or outgoing messages, don't worry - this DLL also comes
  with built-in support for third party DLLs that want to add and delete their
  own rules and process any matching lines. The assumed programming language
  is C/C++.

  In order to create your own DLL, you'll need the ioapi.h file. The complete
  header file is included in this README in section 5.3, below. Newer versions
  of the I/O DLL will most likely not be backwards compatible when it comes to
  the DLL API, so the ioapi.h file will probably change for the next version,
  too.

  Important: the I/O DLL MUST be loaded from mIRC before it can be used by
  other DLLs. You'll probably want to load your DLL from mIRC anyway, so make
  sure that you have called the NOOP command before letting your DLL add
  rules, that's what the NOOP command is there for (see also section 3.7).
  The reason for this is that the I/O DLL has to be able to initialize based
  on the parameters mIRC passes to it, before it can start doing its work.

5.1. DLLAPI

  Instead of exporting two versions of each function (one for mIRC, one for
  other DLLs), the I/O DLL exports one function for other DLLs. This function
  is called "dllapi" and uses the stdcall calling convention. It takes no
  parameters and returns a pointer to a static io_dllapi structure (see also
  section 5.3) which provides pointers to the other functions that your DLL
  may need.

  The first field in the structure is the version number; the current version
  of the DLL API is 0.8, defined in DLLAPI_VERSION. If the first field of the
  returned io_dllapi structure is not equal to this value, the two DLLs are
  probably incompatible.

  The second field in the structure is a flag that indicates whether the I/O
  DLL is actually loaded. If this flag is set to FALSE, none of the API
  functions in the rest of the structure should be called.

  Here is an example snippet of C code that loads io.dll and obtains a pointer
  to the io_dllapi structure:

    HINSTANCE hLib;
    io_dllapi *(__stdcall *pDllApiCall)(void);
    io_dllapi *pDllApi;

    /* Obtain the base pointer of the library, which was already loaded by
       mIRC before (see the "Important" note above).  */
    hLib = LoadLibrary("io.dll");

    /* Get the address of the "dllapi" function call. */
    pDllApiCall = (io_dllapi * __stdcall (*)(void))
     GetProcAddress(hLib, "dllapi");

    /* Now call this function, and thereby retrieve the pointer to the
       io_dllapi structure. Store the pointer in the pDllApi variable. */
    pDllApi = pDllApiCall();

    /* If the I/O DLL wasn't loaded properly, or the DLL's version
       doesn't match the header's, produce an error and stop loading. */
    if (pDllApi->loaded == FALSE || pDllApi->version != DLLAPI_VERSION) {
      ...
    }

    /* Add our own I/O rules here */
    ... = pDllApi->add(...);

  The hLib pointer should not be passed to FreeLibrary until the custom DLL
  itself unloads; this way, the pDllApi pointer will remain valid at all
  times, even if mIRC has already told the I/O DLL to unload. An example
  of how to unload your custom DLL:

    /* Check whether the I/O DLL is already unloaded. If not, the custom DLL
       might be unloaded early (as opposed to when mIRC shuts down), which
       means it has to clean up neatly. */
    if (pDllApi->loaded == TRUE) {

      /* Delete our own I/O rules here */
      pDllApi->del(...);
    }

    /* Unload the I/O DLL now. If pDllApi->loaded was FALSE, then the whole
       structure will now be automatically freed. */
    FreeLibrary(hLib);

5.2. Exported functions

  The rest of the io_dllapi structure, which consists of function calls, is
  explained below. Unlike the exported "dllapi" function, these functions use
  the normal C calling convention. Don't ask.

5.2.1. CONN

  The "conn" function assumes that its caller is actually a DLL function
  called by mIRC, and returns the current (what I call) connection pointer:
  a pointer that is unique for each server window in mIRC. This pointer can
  subsequently be used in other I/O DLL API calls (see below).

  Prototype:

    void *conn(void *nopause);

  Parameters:

    nopause

      A pointer to the caller's "nopause" parameter. This is the "BOOL
      nopause" parameter from the DLL routine in mIRC's "/help DLL Support".

  Return value:

    A pointer uniquely identifying mIRC's current server window.

  Example:

    void *conn = pDllApi->conn(&nopause);

  Notes:

    You do not necessarily need this function, especially not when you define
    your rules to apply to ALL server connections (see below).

    If you're interested: this function actually retrieves this pointer from
    the stack, at a static distance from the "nopause" parameter, which is
    also on the stack.

5.2.2. ADD

  The "add" function adds a rule in a way quite similar to the ADD command
  specified in section 3.1. Pay attention though, there are a few small but
  important differences.

  Prototype:

    int add(int gid, int flags, char *match, int (*proc)([...]), void *data,
     void *conn);

  Parameters:

    gid

      The ID of an existing group to add this rule to, or zero to create a new
      group for this rule.

    flags

      A bitwise OR combination of the following flags:

        FLAG_INPUT      this rule is for INPUT message lines.
        FLAG_OUTPUT     this rule is for OUTPUT message lines.
        FLAG_ONCE       delete this rule's group after being triggered.
        FLAG_ALL        this rule affects all server connections.
        FLAG_COMMAND    the match field is a command.

      As usual, if none of the INPUT/OUTPUT flags are specified, FLAG_INPUT is
      assumed.

    match

      A null-terminated string containing the matchtext for this rule; either
      a command or a full line wildcard string depending on the presence of
      the FLAG_COMMANd flag, so DON'T add quotes as in the script ADD command.

    proc

      A pointer to the callback procedure. See the notes below. If proc is
      NULL, the rule will block any matching lines.

    data

      An arbitrary pointer which will be passed untouched to the callback
      procedure; only relevant if 'proc' is not NULL.

    conn

      A connection pointer of the server connection to add this rule to. This
      parameter should be NULL if FLAG_ALL is specified, otherwise it should
      always be a valid connection pointer (see also section 5.2.1 above).

  Return value:

    The ID of the group that this rule was added to, or 0 in case of an error.
    If "gid" was nonzero, and no error occurred, the same "gid" value will be
    returned.

  Example:

    int gid = pDllApi->add(0, FLAG_OUTPUT|FLAG_ALL|FLAG_COMMAND, "JOIN",
     myProc, NULL, conn);

  Notes:

    The "proc" parameter should be a pointer to a function with the following
    prototype (again, C calling convention):

      int io_proc(const char *line, void *conn, int gid, void *data);

    Where "line" is a pointer to the message, "conn" is the connection pointer
    for the connection which triggered the rule (always non-NULL), "gid" is
    the rule's group ID, and "data" is the pointer as passed to the "add"
    function. If the return value is 1, the message will be blocked; if the
    return value is 2, the message will be blocked and the group to which the
    triggered rule belongs will be deleted.

    Note that the "line" parameter is constant for a good reason. Don't touch.

5.2.3. DEL

  The "del" function deletes a rule, or a whole group.

  Prototype:

    int del(int gid, int flags, char *match, void *conn);

  Parameters:

    gid

      The group ID of the group that this rule resides in. This value can not
      be zero.

    flags

      The flags as specified when the rule was added; the FLAG_ONCE flag is
      ignored.

    match

      The matchtext as specified when the rule was added. If this parameter is
      NULL, the whole group as specified by 'gid' is deleted, and both 'flags'
      and 'conn' are ignored.

    conn

      The connection pointer for the rule, or NULL if this was a rule created
      with the FLAG_ALL flag.

  Return value:

    Nonzero if a rule or group was successfully deleted, zero otherwise.

  Example:

    pDllApi->del(gid, FLAG_OUTPUT|FLAG_ALL|FLAG_COMMAND, "JOIN", conn);

5.2.4. RECV

  The "recv" function tells mIRC that it received a message line from the IRC
  server; it will return when mIRC is done processing this line.

  Prototype:

    void recv(const char *line, void *conn);

  Parameters:

    line

      The line that mIRC will receive. This line MUST NOT be terminated with
      any CR/LF characters.

    conn

      The connection pointer of the connection that will receive the line.
      This parameter can not be NULL in this case.

  Example:

    pDllApi->recv(":bar JOIN #foo", conn);

5.2.5. SEND

  The "send" function tells mIRC to send a message to the IRC server.

  Prototype:

    void send(const char *line, void *conn);

  Parameters:

    line

      The line that mIRC should send. This line MUST NOT be terminated with
      any CR/LF characters.

    conn

      The connection pointer of the connection that the line will be sent to.
      This parameter can not be NULL in this case.

  Example:

    pDllApi->send("JOIN #foo", conn);

5.3. The ioapi.h file

  #ifndef __IOAPI_H
  #define __IOAPI_H

  #define DLLAPI_VERSION MAKELONG(0,8)

  #define FLAG_INPUT   0x01
  #define FLAG_OUTPUT  0x02
  #define FLAG_ONCE    0x04
  #define FLAG_ALL     0x08
  #define FLAG_COMMAND 0x10

  typedef struct {
    DWORD version;
    BOOL loaded;
    void *(*conn)(void *nopause);
    int (*add)(int gid, int flags, const char *match,
     int (*proc)(const char *line, void *conn, int gid, void *data),
     void *data, void *conn);
    int (*del)(int gid, int flags, const char *match, void *conn);
    void (*recv)(const char *line, void *conn);
    void (*send)(const char *line, void *conn);
  } io_dllapi;

  #endif /*__IOAPI_H*/


6. License

  Distribution of io.dll and this README.txt is UNLIMITED, provided that the
  files are both UNMODIFIED. You are free to include the DLL without this
  readme in your script distribution, without giving credit, as long as the
  DLL is unmodified. You are not allowed to TAKE credit for this DLL (duh!).

  THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
  EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO WARRANTIES OF
  MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL THE
  AUTHOR BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING INCIDENTAL OR
  CONSEQUENTIAL DAMAGES, ARISING OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. YOU ACKNOWLEDGE THAT YOU HAVE
  READ THIS LICENSE, UNDERSTAND IT AND AGREE TO BE BOUND BY ITS TERMS.


7. Source code

  Due to legal reasons, this DLL has to remain closed-source. I'm not going to
  go into exact details about this, but although the creation of this DLL was
  perfectly legal, and using the DLL is *definitely* legal, I would break a
  law if I were to release its source, because this would reveal information
  about how mIRC works internally. I promise I'll update it as soon as I can
  after the release of new mIRC versions, though!


8. Version history

  Version  Released    Description/changes

  0.8.1    15-09-2007  Added support for mIRC 6.3

  0.8.0    23-11-2006  Can now delete a whole group with "io del"
                       Renamed "groupdata" to "data"; "groupdata" soon to go
                       Updated DLL interface, also compatible with msvc/mingw
                       Added support for mIRC 6.21

  0.7.10   08-08-2006  Now disallowing use of "io recv" from critical events
                       Added support for mIRC 6.2

  0.7.9    15-04-2006  Fixed third callback parameter evaluation/mangling bug
                       Switched from x.yz to x.y.z version numbering

  0.7.8    06-03-2006  Added disconnect auto-detection, removed "io flush"

  0.7.7    18-02-2006  Added support for mIRC 6.17

  0.7.6    05-07-2005  Fixed whole line match creation bug
                       Added callback retval of 2 to delete group

  0.7.5    07-06-2005  Fixed deleting of groups after alias fired

  0.7.4    02-06-2005  Fixed rule number creation bug

  0.7.3    09-04-2005  Fixed group name lookup bug

  0.7.2    23-03-2005  First public release

  0.7.1    08-01-2005  Added support for mIRC 6.16

  0.7      19-06-2004  First beta release

  0.6      03-06-2004  Limited technology preview
