Thursday, October 18, 2012

sscanf 2.6

Contents

  • Contents - This list of post sections.
  • Introduction - What this plugin is.
  • Download - Where to get it.
  • Errors/Warnings - How to fix common problems.
    • MSVRC100.dll Not Found - Common Windows error.
    • sscanf error: System not initialised. - Run-time compilation problem.
    • sscanf warning: String buffer overflow. - Too much user data entered.
  • Use - Basic plugin usage instructions.
  • Specifiers - List of all the unformatting options.
    • Basic specifiers - Single-letter specifiers.
    • Strings - Code to read in text.
    • Arrays - Code to read in multiple similar values.
    • Enums - Code to read in complex data.
    • Quiet - Loaded and discarded data.
    • Searches - Explicit search text.
    • Delimiters - Symbols between data.
    • Optional specifiers - Specifiers with default values.
    • Custom (kustom) specifiers - Custom specifiers.
    • All specifiers - A handy reference list.
  • Kustomisation - Third-party "k" specifiers.
    • player_name - Alternate "u" by Slice.
  • Conclusion - A brief challenge.
  • Changelog - History of changes.

Introduction

I have been hinting at this plugin for quite a while now (to some people anyway, and it was posted about on twitter) and it's finally complete - I really don't have much spare time and there's quite a lot of code to this (it puts the old sscanf to shame). I was initially trying to fix a few problems with the old sscanf in PAWN - things like the lack of optional integers and buffer overflows on strings, it was going OK but the code was getting quite large - it got over 1000 lines for a single function before I decided to move it out to a plugin. As I was at it there was also a large number of new specifiers I wanted to add.

This is possibly my most thoroughly tested code ever - I've got a huge file of unit tests, but given the complexity I have no doubt there will still be bugs about. See the bottom of the post for a history of the bugs and various fixes and versions.

Download

Source, Windows .dll, Linux .so:

http://dl.dropbox.com/u/21683085/sscanf.rar

Alternative Linux .so compiled by blank. on CentOS:

http://www.sendspace.com/file/qgdgnc

Errors/Warnings
  • MSVRC100.dll Not Found

If you get this error, DO NOT just download the dll from a random website (click here for why). This is part of the "Microsoft Visual Studio Redistributable Package". This is required for many programs, but they often come with it. Download it here:

http://www.microsoft.com/download/en...s.aspx?id=5555
  • sscanf error: System not initialised.

If you get this error, you need to make sure that you have recompiled ALL your scripts using the LATEST version of "sscanf2.inc". Older versions didn't really require this as they only had two natives - "sscanf" and "unformat", the new version has some other functions - you don't need to worry about them, but you must use "sscanf2.inc" so that they are correctly called. If you think you have done this and STILL get the error then try again - make sure you are using the correct version of PAWNO for example.
  • sscanf warning: String buffer overflow.

This error comes up when people try and put too much data in to a string. For example:

Code:
new str[10];
sscanf("Hello there, how are you?", "s[10]", str);

That code will try and put the string "Hello there, how are you?" in to the variable called "str". However, "str" is only 10 cells big and can thus only hold the string "Hello ther" (with a NULL terminator). In this case, the rest of the data is ignored - which could be good or bad:

Code:
new str[10], num;
sscanf("Hello there you|42", "p<|>s[10]i", str, num);

In this case "num" is still correctly set to "42", but the warning is given for lost data ("e you").

Currently there is nothing you can do about this from a programming side (you can't even detect it - that is a problem I intend to address), as long as you specify how much data a user should enter this will simply discard the excess, or make the destination variable large enough to handle all cases.

Use

This behaves exactly as the old sscanf did, just MUCH faster and much more flexibly. To use it add:

Code:
#include <sscanf2>

To your modes and remove the old sscanf (the new include will detect the old version and throw an error if it is detected). On windows add:

Code:
plugins sscanf
To server.cfg. On Linux add:

Code:
plugins sscanf.so
The basic code looks like:

Code:
if (sscanf(params, "ui", giveplayerid, amount))
{
 return SendClientMessage(playerid, 0xFF0000AA, "Usage: /givecash <playerid/name> <amount>");
}

However it should be noted that sscanf can be used for any text processing you like. For example an ini processor could look like (don't worry about what the bits mean at this stage):

Code:
if (sscanf(szFileLine, "p<=>s[8]s[32]", szIniName, szIniValue))
{
 printf("Invalid INI format line");
}

There is also an alternate function name to avoid confusion with the C standard sscanf:

Code:
if (unformat(params, "ui", giveplayerid, amount))
{
 return SendClientMessage(playerid, 0xFF0000AA, "Usage: /givecash <playerid/name> <amount>");
}

Specifiers

The available specifiers (the letters "u", "i" and "s" in the codes above) are below.
  • Basic specifiers

Code:
Specifier(s)   Name    Example values
 i, d   Integer    1, 42, -10
 c   Character   a, o, *
 l   Logical    true, false
 b   Binary    01001, 0b1100
 h, x   Hex    1A, 0x23
 o   Octal    045 12
 n   Number    42, 0b010, 0xAC, 045
 f   Float    0.7, -99.5
 g   IEEE Float   0.7, -99.5, INFINITY, -INFINITY, NAN, NAN_E
 u   User name/id (bots and players) Y_Less, 0
 q   Bot name/id   ShopBot, 27
 r   Player name/id   Y_Less, 42
  • Strings

The specifier "s" is used, as before, for strings - but they are now more advanced. As before they support collection, so doing:

Code:
sscanf("hello 27", "si", str, val);

Will give:

Code:
hello
27
Doing:

Code:
sscanf("hello there 27", "si", str, val);

Will fail as "there" is not a number. However doing:

Code:
sscanf("hello there", "s", str);

Will give:

Code:
hello there
Because there is nothing after "s" in the specifier, the string gets everything. To stop this simply add a space:

Code:
sscanf("hello there", "s ", str);

Will give:

Code:
hello
You can also escape parts of strings with "\\" - note that it is two backslashes as 1 is used by the compiler:

Code:
sscanf("hello\\ there 27", "si", str, val);

Will give:

Code:
hello there
27
All these examples however will give warnings in the server as the new version has array sizes. The above code should be:

Code:
new
 str[32],
 val;
sscanf("hello\\ there 27", "s[32]i", str, val);

As you can see - the format specifier now contains the length of the target string, ensuring that you can never have your strings overflow and cause problems. This can be combined with the SA:MP compiler's stringizing:

Code:
#define STR_SIZE 32
new
 str[STR_SIZE],
 val;
sscanf("hello\\ there 27", "s[" #STR_SIZE "]i", str, val);

So when you change your string size you don't need to change your specifiers.

What happened to "z", the optional string? z has been removed (you can still use it but will get a server warning) to make way for the new optional parameter system described later on.
  • Arrays

One of the advanced new specifiers is "a", which creates an array, obviously. The syntax is similar to that of strings and, as you will see later, the delimiter code:

Code:
new
 arr[5];
sscanf("1 2 3 4 5", "a<i>[5]", arr);

The "a" specifier is immediately followed by a single type enclosed in angle brackets - this type can be any of the basic types listed above. It is the followed, as with strings now, by an array size. The code above will put the numbers 1 to 5 into the 5 indexes of the "arr" array variable.
  • Enums

This is possibly the most powerful addition to sscanf ever. This gives you the ability to define the structure of an enum within your specifier string and read any data straight into it. The format takes after that of arrays, but with more types - and you can include strings in enums (but not other enums or arrays):

Code:
enum
 E_DATA
{
 E_DATE_C,
 Float:E_DATA_X,
 E_DATA_NAME[32],
 E_DATA_Z
}

main
{
 new
  var[E_DATA];
 sscanf("1 12.0 Bob c", "e<ifs[32]c>", var);
}

Now I'll be impressed if you can read that code straight off, so I'll explain it slowly:

Code:
e - Start of the "enum" type
< - Starts the specification of the structure of the enum
i - An integer, corresponds with E_DATA_C
f - A float, corresponds with E_DATA_X
s[32] - A 32 cell string, corresponds with E_DATA_NAME
c - A character, corresponds with E_DATA_Z
> - End of the enum specification
Note that an enum doesn't require a size like arrays and strings - it's size is determined by the number and size of the types.
  • Quiet

The two new specifiers "{" and "}" are used for what are known as "quiet" strings. These are strings which are read and checked, but not saved. For example:

Code:
sscanf("42 -100", "{i}i", var);

Clearly there are two numbers and two "i", but only one return variable. This is because the first "i" is quiet so is not saved, but affects the return value. The code above makes "var" "-100". The code below will fail in an if check:

Code:
sscanf("hi -100", "{i}i", var);

Although the first integer is not saved it is still read - and "hi" is not an integer. Quiet zones can be as long as you like, even for the whole string if you only want to check values are right, not save them:

Code:
sscanf("1 2 3", "i{ii}", var);
sscanf("1 2 3", "{iii}");
sscanf("1 2 3", "i{a<i>[2]}", var);

You can also embed quiet sections inside enum specifications:

Code:
sscanf("1 12.0 Bob 42 INFINITY c", "e<ifs[32]{ig}c>", var);

Quiet sections cannot contain other quiet sections, however they can include enums which contain quiet sections.
  • Searches

Searches were in the last version of sscanf too, but I'm explaining them again anyway. Strings enclosed in single quotes (') are scanned for in the main string and the position moved on. Note that to search for a single quote you escape it as above using "\\":

Code:
sscanf("10 11 woo 12", "i'woo'i", var0, var1);

Gives:

Code:
10
12
You could achieve the same effect with:

Code:
sscanf("10 11 woo 12", "i{is[1000]}i", var0, var1);

But that wouldn't check that the string was "woo". Also note the use of "1000" for the string size. Quiet strings must still have a length, but as they aren't saved anywhere you can make this number as large as you like to cover any eventuality. Enum specifications can include search strings.
  • Delimiters

The previous version of sscanf had "p" to change the symbol used to separate tokens. This specifier still exists but it has been formalised to match the array and enum syntax. What was previously:

Code:
sscanf("1,2,3", "p,iii", var0, var1, var2);

Is now:

Code:
sscanf("1,2,3", "p<,>iii", var0, var1, var2);

The old version will still work, but it will give a warning. Enum specifications can include delimiters, and is the only time "<>"s are contained in other "<>"s:

Code:
sscanf("1 12.0 Bob,c", "e<ifp<,>s[32]c>", var);

Note that the delimiter will remain in effect after the enum is complete.

When used with strings, the collection behaviour is overruled. Most specifiers are still space delimited, so for example this will work:

Code:
sscanf("1 2 3", "p<;>iii", var0, var1, var2);

Despite the fact that there are no ";"s. However, strings will ONLY use the specified delimiters, so:

Code:
sscanf("hello 1", "p<->s[32]i", str, var);

Will NOT work - the variable "str" will contain "hello 1". On the other hand, the example from earlier, slightly modified:

Code:
sscanf("hello there>27", "p<>>s[32]i", str, var);

WILL work and will give an output of:

Code:
hello there
27
  • Optional specifiers

EVERY format specifier (that is, everything except '', {} and p) now has an optional equivalent - this is just their letter capitalised, so for example the old "z" optional string specifier is now "S" (there is still "z" and, for completeness, "Z", but both give warnings). In addition to optional specifiers, there are also now default values:

Code:
sscanf("", "I(12)", var);

The "()"s (round brackets) contain the default value for the optional integer and, as the main string has no data, the value of "var" becomes "12". Default values come before array sizes and after specifications, so an optional array would look like:

Code:
sscanf("1 2", "A<i>(3)[4]", arr);

Note that the size of the array is "4" and the default value is "3". There are also two values which are defined, so the final value of "arr" is:

Code:
1, 2, 3, 3
Array default values are clever, the final value of:

Code:
sscanf("", "A<i>(3,6)[4]", arr);

Will be:

Code:
3, 6, 9, 12
The difference between "3" and "6" is "3", so the values increase by that every index. Note that it is not very clever, so:

Code:
sscanf("", "A<i>(1,2,2)[4]", arr);

Will produce:

Code:
1, 2, 2, 2
The difference between "2" and "2" (the last 2 numbers in the default) is 0, so there will be no further increase. For "l" (logical) arrays, the value is always the same as the last value, as it is with "g" if the last value is one of the special values (INFINITY, NEG_INFINITY (same as -INFINITY), NAN or NAN_E). Note that:

Code:
sscanf("", "a<I>(1,2,2)[4]", arr);

Is invalid syntax, the "A" must be the capital part.

Enums can also be optional:

Code:
sscanf("4", "E<ifs[32]c>(1, 12.0, Bob, c)", var);

In that code all values except "4" will be default. Also, again, you can escape commas with "\\" in default enum strings. Some final examples:

Code:
sscanf("1", "I(2)I(3)I(4)", var0, var1, var2);
sscanf("", "O(045)H(0xF4)B(0b0100)U(Y_Less)", octnum, hexnum, binnum, user);
sscanf("0xFF", "N(0b101)");

That last example is of a specifier not too well described yet - the "number" specifier, which will work out the format of the number from the leading characters (0x, 0b, 0 or nothing).
  • Custom (kustom) specifiers

The latest version of sscanf adds a new "k" specifier to allow you to define your own specifers in PAWN:

Code:
SSCANF:playerstate(string[])
{
 if ('0' <= string[0] <= '9')
 {
  new
   ret = strval(string);
  if (0 <= ret <= 9)
  {
   return ret;
  }
 }
 else if (!strcmp(string, "PLAYER_STATE_NONE")) return 0;
 else if (!strcmp(string, "PLAYER_STATE_ONFOOT")) return 1;
 else if (!strcmp(string, "PLAYER_STATE_DRIVER")) return 2;
 else if (!strcmp(string, "PLAYER_STATE_PASSENGER")) return 3;
 else if (!strcmp(string, "PLAYER_STATE_WASTED")) return 7;
 else if (!strcmp(string, "PLAYER_STATE_SPAWNED")) return 8;
 else if (!strcmp(string, "PLAYER_STATE_SPECTATING")) return 9;
}

The code above, when added to the top level of your mode, will add the "playerstate" specifier, allowing you to do:

Code:
sscanf(params, "uk<playerstate>", playerid, state);

This system supports optional custom specifiers with no additional PAWN code:

Code:
sscanf(params, "uK<playerstate>(PLAYER_STATE_NONE)", playerid, state);

The new version of "sscanf2.inc" includes functions for "k<weapon>" and "k<vehicle>" allowing you to enter either the ID or name and get the ID back, but both are VERY basic at the moment and I expect other people will improve on them.

Note that custom specifiers are not supported in either arrays or enumerations.

Note also that custom specifiers always take a string input and always return a number, but this can be a Float, bool, or any other single cell tag type.
  • All specifiers

For quick reference, here is a list of ALL the specifiers and their use:

Code:
Format     Use
L(true/false)    Optional logical truthity
l     Logical truthity
K<callback>(default text)  Optional custom operator
k<callback>    Custom operator
B(binary)    Optional binary number
b     Binary number
N(any format number)   Optional number
n     Number
C(character)    Optional character
c     Character
I(integer)    Optional integer
i     Integer
D(integer)    Optional integer
d     Integer
H(hex value)    Optional hex number
h     Hex number
O(octal value)    Optional octal value
o     Octal value
F(float)    Optional floating point number
f     Floating point number
G(float/INFINITY/-INFINITY/NAN/NAN_E) Optional float with IEEE definitions
g     Float with IEEE definitions
{     Open quiet section
}     Close quiet section
P<delimiter>    Invalid delimiter change
p<delimiter>    Delimiter change
Z(string)[length]   Invalid optional string
z(string)[length]   Deprecated optional string
S(string)[length]   Optional string
s[length]    String
U(name/id)    Optional user (bot/player)
u     User (bot/player)
Q(name/id)    Optional bot (bot)
q     Bot (bot)
R(name/id)    Optional player (player)
r     Player (player)
A<type>(default)[length]  Optional array of given type
a<type>[length]    Array of given type
E<specification>(default)  Optional enumeration of given layout
e<specification>   Enumeration of given layout
'string'    Search string
%     Deprecated optional specifier prefix
Kustomisation

This is a list of all the know-to-me third-party "k" specifiers (custom specifiers written by other people). Usage is either "k<name>" or "K<name>(default)". The names are the section headers below.
  • player_name
  • Author: Slice
  • Code: http://forum.sa-mp.com/showpost.php?...postcount=2249
  • Usage: The default "u" specifier does not detect names such as "[ABC]Def" when only "Def" is typed. This is an implementation of "u" providing alternate behaviour in this case and returns "INVALID_PLAYER_ID" when multiple matching names are found (could be changed to another value to differentiate between no found players and multiple players).

Conclusion

See if you can figure out what, with this new version, this does (note that this is an extreme example):

Code:
"{S("f<5>")if}jE<np<l>{u}ns[" #LEN "]g>(0777, 5, 0xF2, hi\\, there, NAN_E)A<n>(0b1010, 012, 10, 0xA)[11]"

Changelog
  • Update 11

Fixed the problem with "OnPlayerUpdate" not being called AGAIN!
  • Update 10

OK, I've compiled the plugin for Linux (on Ubuntu), and blank. has provided an additional .so for CentOS if the Ubuntu version doesn't work (should do in most Linux cases). The new sscanf package is available here:

http://dl.dropbox.com/u/21683085/sscanf(2).rar

The CentOS .so is available here:

http://www.sendspace.com/file/qgdgnc
  • Update 9

Finally fixed the incorrect IDs problem thanks to leong124. Currently only compiled for Windows as my Linux box is broken - any help in this regard would be vastly appreciated:

http://dl.dropbox.com/u/21683085/sscanf.rar
  • Update 8

Fixed a small bug. New download:

http://dl.dropbox.com/u/21683085/sscanf%281%29.rar
  • Update 7

I have FINALLY written an entirely future-compatible, memory-hack-free, global version of sscanf. This will work for all versions of the server, past and present, and can be downloaded from here:

http://dl.dropbox.com/u/21683085/sscanf.rar

This wasn't done before as I was hesitant about using the GDK which has bugs, and didn't want to use other methods to call "GetPlayerName" for speed reasons, but this version does it a different way entirely (hooks "OnPlayerConnect").

This version also adds the new "k" specifier, described above.
  • Update 6

sscanf for 0.3dR2 (500 and 800 player versions):

http://www.mediafire.com/?bfp2h0d4231jjdr

Thanks to dnee`THA.
  • Update 5

Quote:
Originally Posted by Thomas. View Post
For those of you having the "missing MSVCR100.dll" error; install the x86 version of the C++ 2010 redistributable package, no matter if your system is 64-bit or not.
Or just recompile the plugin. The old download contains a solution for Visual Studio 2008, the new download contains a solution for Visual Studio 2010. Make sure you use the latest files, just replace the old ones if you're using the old solution - everything else is the same.
  • Update 4

Full final 0.3d version. I'm getting fairly smooth at updating things now, which is good! I've also fixed the "Format specifier does not match parameter count" bug. Download source, Linux and Windows plugins here:

http://dl.dropbox.com/u/21683085/sscanf-0.3d.rar

Note that this version is for 0.3d ONLY. I removed the other code as maintaining the code to detect which was the current version was getting inefficient.
  • Update 3

Thanks to Scott there is a new temporary fix for for 0.3d on Windows using ZeeK's GDK plugin. The download also includes a new version of the streamer plugin for those of you who use it to make the two work together better:

http://dl.dropbox.com/u/44207623/source.rar
  • Update 2

Fixed another major bug. Everyone using sscanf please update now! Note however that the .so file hasn't been updated yet.

Edit: Now it has been thanks to Calg00ne.
  • Update 1

Fixed a major bug (thanks pyrodave).

The name checks are now case insensitive after lots of requests.

They also now refuse names which are too long. The old version had a bug where "Y_Lessmoo" would have matched a player called "Y_Less".

No comments:

Post a Comment