The message library is released under the GLPL by me; 'me' can be reached
by e-mail through this address: Steffen.Kaiser@FH-Rhein-Sieg.DE.

Note: The organization I'm working for is not responsible for this source
code at all.

#include <standard_disclaimer>
/* You know:
	1) I'm not responsible for anything.
	2) I don't assure anything.
*/


Well, this package contains a message library. All utilities that are
necessary to use the library are either included or can be found at the
very same place you got this library from. The package names are
mentioned here.

0. Introduction

This library shall provide an interface to a pack of functions dealing
with messages that will be displayed onto screen or dumped into files.
It has nothing to do with IPC (Inter Process Communication) or data
transmission via modem (telephone), LAN (Local Area Network), or WAN
(Wide Area Network).

The reason to code this library rose from the need to support multiple
languages with the same source code with as less work as possible, when
switching from one language to another.

The library functions base upon the concept of the programming language C,
what means that in the last instance messages are represented by character
strings that includes all necessary format information the way printf()
does. The usual functionality of this function has been improved to
support this and that functionality that seemed to be useful.

Imagine the following statement:
printf("Sorry, but you have to %s to version %s\n",
	mode? "update": "upgrade", requiredVersion);

To support a language different from English, you must change the line
manually to, e.g. (it's German, by the way)
printf("Schade, aber Sie mssen auf die Version %s %s\n",
	requiredVersion, mode? "updaten": "upgraden");

You can see, at least, two problems:
1) The format string has been changed, and
2) the order of the arguments has changed.

Hmm, to support even more languages the required work increases even more.

The first step is to centralize all strings into one file to allow an
easy change of them, even making it possible to simply switch this file
for a different language, then recompile all the program files.

The second step would be to make all messages that simple or that unreadeble
that the order of the arguments will never change.

Well, there are several approaches about this problem, here is presented
one solution.

1. Target compilers & programming environment

Currently two compilers are supported:
+ Micro-C by DDS; see http://www.dunfield.com
+ Borland C; see http://www.borland.com

Micro-C is not ANSI compliant, what means that there are differences in
both implementations, but this problem is totally hidden by the
programming interface of the library. Micro-C's format strings won't
support data formats not available in Micro-C itself, such as (float) or
(void far*), though.

You should make sure that you use Micro-C v3.14 or later.
The accompanying tools are: ASM v2.0 and VAL.

Under the following versions of the Borland C compiler the message library
has been compiled: v2.0, v3.1, v4.52,& v5.0.
The linker always was the one of the package of version 4.52 and, when
it became available, the one of v5.0.

As the librarian LIB is used, available under GPL as LIB-SK??.ZIP.

To compile all the sources DMake is used. A patched version of DMake
v4.00 can be found in DMAKE4??.ZIP. Please make sure, you have at least
patchlevel 6.
You will also find batch files that compile the stuff for either Micro-C or
Borland C.

You will need some utilties to use the message library, they are included
here as binary and source code, although to compile them you also need
the supplemental library SUPPL-??.ZIP.

2. Features

The core functionality of the message library is a wrapper of the fprintf()
function. It does:
- re-order the arguments
- include another format string into a format string
- include ANSI escape sequences that are emitting only under some conditions
- include raw data that is never interpreted as a format specification
- repetively emit a character
- conform the output of Micro-C to be more ANSI-compliant
- add the '%b' format to non-Micro-C
- add the '%x' and '%X' format to Micro-C
- handle the output buffer overflow (more than 132 characters) for Micro-C

There are some functions that wraps this function. They do:
- prepend and append text to a message according to its class
- suppress messages by their class
- support different ways to store format strings
- invoke a function prior exiting from Error or Fatal message

3. How to include the message library into a program

To include the message library into a program requires more than one
step, though, it may be very easy, if the output of messages is already
centralized into a very few functions.

First, the programmer must stop thinking of the messages as strings. From
now on messages are identified by an identifier (ID) of the type MSGID. That
includes that messages cannot be accessed straight forward, but through
functions only.

Second, the process how the executable is generated changes. It will now
include that the messages are somehow preprocessed. The supplied
Makefiles and batch files automatically includes all necessary steps.

3.1) The necessary steps outside of a program

For each language one global and one local file are required.

The message library is divided into two parts: a local and a global one.
The global part contains:
- The functions of the message library that will be included into the
executable.
- Definitions of general purpose messages that can be used as they were
defined locally.
- Definitions and declarations of internally used messages.

The local part consists of message definitions only.

The global part itself is divided into three parts:
1) The functions are located in a object module archive (== a .LIB library).
If the way how the messages are accessed includes to store information
in .OBJ files, this information is put into this library, too.
One library is required per compiler and memory model and language.
2) The function prototypes and macros are located in a C header file named
MSGLIB.H.
3) The messages are declared in so-called message files that have an equal
status to a header file and, thus, should be kept in the INCLUDE directory
near the MSGLIB.H file. One global message file per language is required.

The syntax of the message files are described in the section "The
message file".

Currently, the message files are named after the language the messages
are in. The produced global library files are named: ?_<lng>.LIB. '?'
is the code of the memory model: t - tiny, s - small, c - compact, m -
medium, l - large,& h - huge. '<lng>' is the first six characters of the
name of the appertaining message file. The global library files should
be kept in the LIB directory.

How the local representation of the messages looks like depends on the way
how messages are accessed. See the sections "Method #?? to access messages".
In general, the message files must be preprocessed and are converted into
one or more files that can be accessed in a C program.

One of these files is a file that declares identifiers of the programming
language to identify each particular message. This file is called
"local message declaration file". Normally, this file #include's other
files, e.g. the header file of the message library.

3.2) What to do inside the source codes

Independed on the way how messages are accessed is how programs are
written for the message library. This is pertained by hiding all functionality
of internal nature, e.g. where messages are stored, how the extensions are
processed, how messages are identified.

First of all, each source file accessing messages must include the
local message declaration file.

Second, the functions of the message library must be initialized. To do
this the function 'msgInit()' must be invoked. The best place to do this
seems to be in the main() function immediately behind the declaration of
the variables, e.g.:

#include "local message declaration file"

int main(int arc, char **argv)
{	int var1,....;
	char *var2,...;

	msgInit();

	... <any code> ...

	return 0;
}

If functions are accessed prior the initialization, the result is
undefined, though, no program crash should arise.

Because a message is something virtual now, they must be accessed
through the block-box type called MSGID. This type can be assumed to be
scalar, that means that variables of that type can be assigned to each
other, they can be passed as arguments to functions easily, and you can
take an address of them (make a pointer). But they have a very limited
operation set: (assume 'MSGID msg' and msg# are message IDs)
- 0 is distinct from the values of the MSGID type.
- "msg = msg#;" to keep a message ID.
- "function(MSGID m)" to declare a parameter as a message.
- "function(msg)" or "function(msg#)" to pass a message as an argument.
- msgLock(msg) creates a (char*) variable from the message, possibly by
	invoking malloc(). Note: This function is the only way to get a
	"real" access to the string of a message.
- msgUnlock(msg), msgUnlockString(string) reverses msgLock().
- msgRetrieve(msg) is equal to msgLock(), but each call to msgRetrieve()
	automatically unlocks the previously retrieved message.

Note: The msgLock()/msgUnlock() functions enables to place the messages
everywhere outside the program's data segment, e.g. if the messages are
stored in a file, they are read from it with this call into the local
heap with msgLock() and with msgUnlock() the memory in the heap is
released.

To emit a message to a file or onto a screen, a function is defined. This
function is not invoked directly but by wrappers that add a noise level
to the message. Six noise levels are currently defined: Interactive,
Informative, Warning, Error, Fatal error,& Plain message.

The output function can suppress messages of a particular noise level,
also it knows in which stream the message is to be emitted and what text
is to be prepended and appended. The following table displays how the
noise level modifies the behavious of the output function:

noise level	stream		prepend	append	special
-------------------------------------------------------------------------
interactive	stdout		none	none	ignored, if stdin or stdout no TTY
informative	stderr		%A:		'\n'
warning		stderr		%A: %W:	'\n'
error		stderr		%A: %E:	'\n'	terminates programm
fatal error	stderr		%A: %F:	'\n'	terminates programm
plain		supplied/	none	none
			stdout

%A is replaced by the application's name
%W is I_warning, default: "Warning"
%E is I_error, default: "Error" red on black blinking
%F is I_fatal, default: "Fatal" red on black blinking

The functions are:
interactive(MSGID msg, ...)				: noise level "interactive"
informative(MSGID msg, ...)				: noise level "informative"
warning(MSGID msg, ...)					: noise level "warning"
error(MSGID msg, ...)					: noise level "error"
fatal(MSGID msg, ...)					: noise level "fatal error"
smessage(MSGID msg, ...)				: noise level "plain", stream: stdout
message(FILE *stream, MSGID msg, ...)	: noise level "plain"

The output function handles the locking/unlocking of the message 'msg'
itself.

The output function does not perform the interpretation of the format
string itself, instead, it calls the function skprintf(). This function
is the core function of the message library as it does the most of the
work. In opposit of a message ID a string is passed to the function
skprintf() that contains the string of the message. This enables the
programmer to use improved format strings without actually using the
message handling environment.
Note: The syntax of the format strings of skprintf() and the message
within the message files differs, see the particular section for more
details.

The in-built support for ANSI escape sequences currently supports only
an externally installed ANSI driver. That means if there is installed no
ANSI driver, the escape sequences are ignored; see the section "ANSI
enclosures" for more details.

4.) The improved format string of skprintf()

skprintf() supports all ANSI compliant format specifications and adds
some new ones.

Special syntaxes within the format string:
	+ %-%* insert special format *, following formats are currently
		available:
		+ m<MSGID>: include message string right at this place,
					<MSGID> is in binary form and may contain NUL's.
		+ M:		take one argument of the type (MSGID) and include
					this message string.
		+ r<count><char>: both are 1 byte wide: insert the character
					<char> <count> times.
		+ R<count>: <count> is 1 byte wide: take one argument of the
					type (int) and insert it <count> times.
		+ A<count>???: <count> is 1 byte wide: Number of characters
					of ???. ANSI escape sequence enclosure.
		+ W<count>???: <count> is 1 byte wide: Number of characters
					of ???. Raw byte enclosure.
	+ %fmt		(fmt == normal format specification)
		=> is replaced by the next argument, which has the specified type
		fmt 'b' == binary is added to non-Micro-C
		fmt 's' == string is re-implemented for Micro-C
		fmt 'X' == upper-cased hex is added to Micro-C
		fmt 'x' == lower-cased hex is added to Micro-C

	+ %#%fmt (# == decimal; fmt == format specification as above %fmt)
		=> is replaced by the #th argument, which has the specified
			type; first argument with # == 1
	+ %-#%fmt	(# == decimal; fmt as in %fmt above)
		=> defines, that the #th argument has the type of %fmt, but is
			ignored fully; first argument with # == 1

	+ there can be multiple statements with the same number for:
	  %#%fmt and %-#%fmt, but always the last is used for defining
	  the argument size.
	+ the "next argument" is the first undefined argument, e.g.:
	  "...%1%s...%s...%2%s"
	  ==> both %s and %2%s expand to the string given as the 2nd argument
	  whereas:
	  "...%s..%2%s..%d"
	  ==> %s is 1st, %2%s is 2nd, %d is 3rd
	  and
	  "...%2%s...%s..%d"
	  ==> %2%s is 2nd, %s is 1st, %d is 3rd
	  and
	  "..%1%s..%1%p..%1%u..%1%c..%s"
	  ==> in near data model: %1%s, %1%p, %1%u,& %1%c will define the
		  first argument as 2 bytes wide
		  Note: The one-byte type '%c' is promoted to (int)!
	  ==> in far data model: 
			arg2-byte 3
			arg2-byte 2
			arg2-byte 1 \
			arg2-byte 0  \(3)
			arg1-byte 3  /    \
			arg1-byte 2	/      \(2)
			arg1-byte 1 \ (1)  /
			arg1-byte 0 /     /

			(1) used by %1%u,& %1%c
			(2) used by %1%s,& %1%p
			(3) used by %s, because the 2 bytes-wide definition for
				argument 1 is define last (with %1%c).


 examples: (double quotes shall mark the C string)
	"disk %c: is full"

	Three-argument message:
	"Disk %c: has the label '%s' and is %s"
	If this message should be altered to:
	"Disk %c: is %s"	the second argument is given to the message() function,
						but should be avoided.
	=> correct: "Disk %c: is %3%s%-2%s"
								 ^^^^^ defines argument 2 as char*, but is ignored
							 ^^^^ is replaced by the third argument

	"Disk with label '%-%A\4\33[7m%s%-%A\4\33[0m' is full"
					     |  |     | |     ^^^^^^ ANSI escape sequence
					     |  |     | ^^^^^^ define, that next 4 characters are ANSI
					     |  |     ^^ normal format string to emit a char*
					     |  ^^^^^^ ANSI escape sequence
					     ^^^ define, that next 4 characters are ANSI




5.) The message file

A message file contains all the messages for one language. The files both
local and global ones are syntactically identical, but the interpretation
of some messages may be different.

The basic syntax is this:
.........
any comment text
.........
>>>>>>?_****** #
...........
text of this message
...........
>>>>>>?_****** #
...........
text of this message
...........


The '>>>>>>' indicates the start of a message. This line is called
message header, all the following lines upto the end of the file or
the next message header are called message body.

The message header defines:
1) '?': The message class. There are currently defined four classes:
	'I': internally used, 'M': normal message, 'W': warning,
	'E': error message
2) '_': The underscore
3) '******': The name of the message. The message ID that con be used
	in C consists of: '?_*****'
4) '#': The exit code if the message is of class 'E'.
	With this exist code the program terminates, if a message with one of the
	noise levels 'error' or 'fatal' is emitted. Note: An exit code can only
	be defined for messages of the class 'E'!

The message body consists of all characters of the following lines, except
the last newline, e.g.:
>>>>>>M_msg1
message #1
>>>>>>M_msg2
message #2

>>>>>>.....

M_msg1 contains "message #1", but M_msg2 "message #2\n".

All the text preceeding the first message header is ignored totally.
It should be considered to be the comment to the messages.

The most portions of the message body is passed to the C compiler without
change. That means that the message body may contain C-style backslash escape
codes.

Control characters, from ASCII(0) to ASCII(0x1F), are translated into
C-style escape codes; the ones with special notation, such as '\n', '\r',
'\b',& '\t', are translated into this notation, the other ones into
the octal notation.

Besides that, special formats are prefixed by: "%-%" or "%--%".
Note: To use these strings is completely save, because they violate
the ANSI format strings and ANSI defines in such case that the behaviour
of the program is undefined. Thus, one can give a special meaning to
the two "undefined behaviours".

The message body may contain special strings: "%-%*%", where '*' stands
for a comma delimited list of the following ANSI keywords:
	cls			clear screen
	clreol		clear from cursor to end of line
	
	home		cursor to upper left corner
	up #		cursor up # rows		\
	down #		cursor down # rows		 | neither scroll nor change
	left #		cursor left # columns	 | of the other coordiante
	right #		cursor right # columns	/
	goto # #	move cursor to position row,column (one based!!)

	save		save one cursor position
	restore		restore saved cursor position

  colour related keywords:
	bright		set attribute: foreground colour highlighted
	blink		set attribute: blinking character
	inverse		set attribute: inverse
	underline	set attribute: underline
	hidden		set attribute: hidden
	normal		unset all attributes
	black		foreground colour: black 
	red			foreground colour: red 
	green		foreground colour: green 
	yellow		foreground colour: yellow 
	blue		foreground colour: blue 
	magenta		foreground colour: magenta 
	cyan		foreground colour: cyan 
	white		foreground colour: white 

Colour related keywords may be combined in any order WITHOUT a
delimiting comma. If more than one colour is given, the first is treated
as the foreground colour, all others as background colours.

The ANSI enclosures are transformed into the %-%A format spec.

The character representations of these keywords are defined in the
global message files. Each keyword can have two character strings
associated with: one from the global message file of the English
language, the other one from the language specific global message file.
If the same string is defined in both files, the language specific
meaning takes precedence.

The special string: %--% includes the following functionality:
	%--%A?****?
		ANSI enclosure. **** is any string that is emitted, if the
		output of ANSI sequences is allowed. ? is any character that
		is not contained within ****. The string may be longer than
		255 characters, but it will be broken into pieces smaller
		than 256 characters.
		Note: The string **** itself is not interpreted in any way,
		especially no check is performed, if the string really is
		an ANSI escape sequence.
	%--%W?****?
		Raw data enclosure; as %--%A, but the string is always emitted.
	%--%r?###%
		Output the single character ? ### times. Although ### may be
		greater than 255, it will be broken into pieces less than
		256.
	%--%R###%
		As above, but the character to be emitted is an argument of the
		type (int). ### must be less than 256.
	%--%m****%
		Recursively include the message with the ID ****. The text of
		the message **** is interpreted, as if it would replace this
		special token, that means the "%1%fmt" means the same argument
		in both the including and the included message.
	%--%M
		As above, but the message to be included is specified by one
		argument of the type (MSGID). Because the included message may
		also contain argument specifications all these argument should
		preceed all other arguments in both conjunctions the argument
		type declaration and as parameter, e.g.:
			>>>>>>M_include
			%-1%MAny arguments %s %d, and the message %1%M, and other
			arguments %s %c.
			>>>>>>M_includedMessage
			%s

			To output the message use that parameter sequence:
				<message to be included>, <para of 1st %s>, <para of %d>,
				<para of %s of M_includedMessage>,
				<para of 2nd %s>, <para of %c>
			e.g.:
				smessage(M_include, M_includedMessage, "s1", 1,
					"s of included msg", "s2", 'c');
			If M_includedMessage would contain "%1%s", "s1" would
			be emitted.
They are transformed into the associated %-%* format specification for the
skprintf() fucntion.

5.1) The internal messages of the global message files

Global message files are mostly equal for all languages, but the English
language is considered to be the language that is supported by all programs.
That's why the global message file for the English language contains some
additional information and defines the secondary keywords.

The syntax of the global message files are equal to the one described in 5).

There are some internally used messages either passing information about
global messages to the message compiler, or providing the string
representation of keywords that are used in the message compiler and the
message library functions, or declaring version infromation about the
particular message file.

Passing information:
I_features:	This message's body looks exactly like the feature list file
generated by the message compiler. This list tells what functionality the
defined messages are using and will be used in future to optimize the
function included for the message library; e.g. if the functions does not
use "argument reorder", the code dealing with it need not be included.
This message is used by the message compiler to include these features
into the local copy of the feature list.
I_internalKeywords:	Defines the string representation of the keywords used
within the message compiler. These keywords are defined within the message
body, there may be more than one I_internalKeyword message. This message
differs between English and the other languages, see below.

Declaring version information:
I_glbMsgFile:	The version information of the global message file.

Used by the message library:
I_error:	The string that is emitted before messages of the noise level
'Error', behind the application's name message.
I_fatal:	As I_error, but for noise level 'Fatal error'.
I_warning:	As I_error, but for noise level 'Warning'.
I_appName:	The message that is emitted before anything else of messages of
one of the noise levels: 'Informative', 'Warning', 'Error', 'Fatal error'.
It takes one argument of the type (char*) that contains the application's
name.
I_noise:	The message that is emitted between the I_appName and the real
message. It takes one argument of the type (char*) that contains the string
representation of the noise level, I_warning, I_error, or I_fatal.

The I_internalKeywords message defines all string representations of the
keywords used within the message compiler, including the keywords of the
'%-%*%' ANSI enclosures.
The body within the global message file of the English language defines:
(see the ENGLISH.MSG file for the details)
1) how many keywords there are;
2) the keyword id (in numerical and symbolical format) and the string for
the English language.
The numerical id identifies the keyword toward the message compiler.
The symbolical id identifies the keywords from other languages and is
the macro name within the message compiler, e.g.:
<ENGLISH.MSG>
22 IN_cyan cyan
23 IN_white white

<DEUTSCH.MSG>
IN_cyan zyan
IN_white wei

Causes that the message compiler uses:
#define IN_white 23
#define IN_cyan 22
^^ These macro definitions are extracted out of the ENGLISH.MSG file; therefore
the symbolical names are *fixed* and must not be changed!

and uses
'wei' and 'white' for the ANSI keyword of the colour "white";
'zyan' and 'cyan' for the ANSI keyword of the colour "cyan".

5.2) The internal messages of the local message files

Local message files currently defines only one internal message:
I_locMsgFile:	The version of the local message file.

6) ANSI enclosures & Raw data enclosures

They are stored in the format string in the very same form as they are
accepted by the ANSI driver. The available specs are limited by the
capabilities of the standard ANSI driver, but the key-redefinition
feature is not supported via the '%-%*%' form directly.

ANSI enclosures are not very different from the raw data enclosures.
The raw data is always emitted, it is neither scanned for included
format specs nor for a string termination character.
ANSI enclosures perform a check before they are emitted, if an ANSI
driver is present; if not, they are simply skipped.

The check is made by the multiplex interrupt: MUX-1A-00. This will miss
some elder drivers, but it's better than nothing, especially it is
non-destructive to the screen and doesn't suppose that a driver is
installed, if there is any TSR is installed that hooked the 'fast
character ouput' interrupt. But described below is a method to overcome
this problem.

Within the skprintf() function the behaviour of the ANSI enclosures is
controlled by a global variable, what can be modified by:
msgSetAnsiFlag() and msgGetAnsiFlag(). This flag can contain one of
three values:
ANSI_ALLOW: to emit ANSI enclosures
ANSI_DENY: to skip them
ANSI_TEST: to perform a test via the checkANSI() function
By default the flag contains: ANSI_TEST. The initialization function
msgInit() will perform an ANSI check, if this value is set, and then it
will set the flag to either ANSI_ALLOW or ANSI_DENY. If you assume that
the ANSI driver will be enabled or disabled during the life-time of the
program or you know a better way to detect an ANSI driver, you should
either reset the flag to ANSI_TEST or set the flag yourself at arbitary
time.

7) Noise levels

As described in section 3.2) there are six noise levels. All of them can
be set to be ignored. This behaviour can be controlled by the functions
setNoiseLevel() and getNoiseLevel().
You can set any particular noise level to either: NOISE_ALLOW,
NOISE_DENY,& NOISE_TEST. The first one enables the noise level, both latter
ones disable the noise level. The only noise level that differs between
NOISE_DENY and NOISE_TEST is "Interactive". If NOISE_TEST is set, the
interactive() function checks, if stdin points to a TTY. If not, the
message is ignored.

During the initialization by msgInit() the noise level NOISE_TEST of
the ENoise_interactive level is alterred to NOISE_ALLOW or NOISE_DENY.
If you expect that the redirection status changes during the life-time
of the program, you should reset the noise level back to NOISE_TEST or
change the noise level at arbitary time.

8.1) Method #1 to access messages

The first implemented method bases upon statically linked messages. The
messages id (MSGID) is the pointer to the first character of the
string and the error number is placed at ((char*)msg)[-1].

The message compiler stores each message into one file, these files are
compiled and the .OBJ modules are archived together into a .LIB file.
This library is then fed to the linker, which identifies the messages
that are referenced within the program and includes the .OBJ the
message is defined in.

msgLock() and msgRetrieve() simply return the argument itself, msgUnlock()
and msgUnlockString() are dummies.

The messages are no further differed by their message group.

8.2) Method #2 to access messages

The secondly implemented method bases upon messages stored within a file.
By simply change that file or the entry point within the file the same
program can support multiple languages without even relink it, if the
feature list files are equivalent.

This message uses:
typedef word MSGID;

with sizeof(word) == 2 and (word) and (unsigned) type.

The message IDs are contructed that way:
MSGID = <message group> * 0x1000 + <message number>;

The message compiler extracts all the messages fromout the message file and
repacks them into a file. This file is a binary file, the message compiler
has preprocessed all the C-style backslash escape codes and filled in the
binary form of the message enclosures.

The generated file is a compact file that can be concated together with
as many other such files as needed. That enables to easily construct
libraries that supports multiple languages even without relinking the
program. The structure of the file itself is hidden within functions
to access the file. This supports later changes of the structure without
porting problems. For the details of the actual structure see the source
file MSGLOCK2.C.

Because the language used while run-time is choosen by the program
itself rather than by the programmer, each language is marked with one
country code. Then the program can choose the proper language by the
current set DOS country code and some more or less good assumptions
which language might be the best for the current country. A complete
list was extracted fromout Ralf Brown's interrupt list and has been
saved into the file COUNTRY.COD.

The "choosing" relates to what to do when the requested country code is
not contained in the message library, e.g. if the current country code
is 0x2C (United Kingdom), the program could choose 0x01 (United States)
as this seems to be the best choice. This is supported by country code
aliases. The single country code a language can have can be improved by
one or more aliases. When the program searches for a country code, but
cannot find it, all the aliases are searched. That way, if the programs
is shipped with messages of the code 0x2C, the ones are used, but if
not, the ones are used, where 0x2C is an alias to. I guess, that would
be 0x01.
