RLdev: a visual novel development kit
Hæleth
Version 1.41
— 2006-10-19 |
RLdev program and documentation copyright © 2006 Hæleth.
The programs, code, documentation, and data files in this package are
distributed without warranty under the terms of the GNU General Public License;
please refer to appendix B for details.
The additional libraries documented in chapter 6 are distributed
without warranty under the terms of the GNU Lesser General Public License, with
an exception permitting linking to proprietary code in specific circumstances;
please refer to chapter 6 for details of the exception, and
appendix C for the text of the license.
This is a development release. This software is incomplete; it is known
to contain some bugs that may cause it to generate unsound code, and many
implementation details are subject to change. You should not use this software
for any purpose whatsoever unless you are sure you know what you're doing.
This manual contains references to a number of proprietary names, including but
not limited to the RealLive brand. Where such names are trademarks, they
should be understood to be the property of their respective owners. In
particular, the author claims no affiliation to VisualArt's KK, who have not
authorised or endorsed this package in any way.
Contents
A number of people have been of great help to me in my efforts to understand and
document the RealLive system: Jagarl, on whose scn2kdump utility, now
developed into xclannad, I built much of my work; roxfan, whose
additional reverse-engineering work has revealed the details of the DLL plugin
system and many other built-in functions and functionality; and, not least, the
careless programmers of RealLive games themselves, whose consistent and
inexplicable failure to remove vast quantities of debugging code from their
software on release has been extremely enlightening.
My thanks are also due to those users who have contributed in various ways to
RLdev's development: Ed Keyes of insani, who tested an earlier version to its
limits with Planetarian, also generously giving permission for me to
incorporate his g00 compression code into Vaconv; 258-shi, whose
introduction of RLdev to a large userbase uncovered numerous bugs; MetaFX,
who helped develop the Chinese support, and Arte, who helped with Korean
support; and Soulfang, my first user, without whose request the program might
never have become anything more than a toy.
The RLdev package provides tools for manipulating the data used by the
VisualArt's RealLive virtual machine.1
Since the actual language used by the developers of these games is undocumented,
a simple programming language of my own design, called `Kepago', is used
instead. The present implementation is rather basic, but higher-level features
will be introduced in future versions. Anyone who has used my older Kpac
package (a similar unofficial development kit for the AVG32 virtual machine)
will find much here familiar, including many of the function names, although
they will doubtless miss the sophistication of that rather more mature Kepago
implementation.
RLdev was designed and written to make a Clannad translation
possible, and extended to support Insani's work on Planetarian and my own
on Kanon when the RealLive edition of the latter was released; it has
never been intended to supercede other free visual novel development systems
(most of which are easier to use and come with fewer restrictions), and it is
certainly not intended to replace the official VisualArt's toolkit, if you're
considering licensing the RealLive engine. In short, I hope this toolkit is
useful, but I make no apology if the programs have any deficiencies,
inaccuracies, inadequacies, or inconsistencies. You have the source code: as
the sage advises, “quicumque melior potest faceret”.
- 1
- I use the term RealLive to cover
the whole current-generation interpreter series from VisualArt's. Version 1.0 was
called AVG2000; the name was changed to RealLive around version 1.0.0.8.
RLdev is split up into utilities according to the types of file each
processes. Currently there are four: Kprl to handle RealLive scenario
files (see section 4.2), both as archives and separately,
Rlc to handle Kepago source code, Vaconv to handle conversions of
bitmap files, and RlXml to handle conversions of certain other data formats
(as of 1.21, just GAN animations).
All utilities have certain things in common: running them without arguments will
display a help screen, and the options --help (displays the same help
screen), --version (displays a brief copyright notice), and
-v, --verbose (increases the amount of diagnostic information printed) are
always available.
1.1 Kprl: the archiver/disassembler
Synopsis: kprl options
(files | archive
[ranges])
One option is always required, to select the operation to perform.
Which of files and archive is used, and what other options
(if any) are available, depends on the operation.
Where archive is used, in all cases other than for the -a
command, ranges is a list of integers between 0 and 9,999. Ranges
can be specified, e.g. `414-416'. The files actually processed
will be the intersection of the set specified on the command line and the set
of files present in the archive. Omitting ranges causes all files
to be processed.
Common operations
-
-a, --add
-
Updates files in archive; if the files listed are not present,
they will be added, and if the archive does not exist, it will be created.
ranges should be one or more filenames, which are taken to be the
names of scenarios to add. Their names must begin seenN,
where N is an integer between 0 and 9,999.
- -k, --delete
-
Removes the files indicated by ranges from archive. If
all files are removed, the archive will be deleted; to prevent accidents,
ranges may not be omitted.
- -l, --list
-
Lists files in archive, printing names, sizes, and compression
ratio where applicable. The files listed can be restricted with
ranges.
Additional options: -
-N, --names
-
Instead of sizes, print a list of characters appearing in the scenario
(not available in AVG2000).
- -d, --disassemble
-
Disassembles files, producing Kepago source code corresponding to their
RealLive bytecode. If an archive and ranges are
given, files will be extracted from the archive automatically; otherwise
files will be treated as a list of separate scenarios to be
disassembled.
Additional options: -
-o DIR, --outdir=DIR
-
Places output in DIR
- -e ENC, --encoding=ENC
-
Selects a character encoding for output. Valid options always include
cp9321, euc-jp, and
utf-8. The default is normally cp932, but this may be
configured differently depending on the settings used to build RLdev.
- --bom
-
If the output encoding is UTF-8, causes a BOM (byte-order mark) to be
prepended to all output files. The default is to leave the BOM out, but
some programs (notably Microsoft's text editors) can not edit UTF-8 files
without it.
- -s, --single-file
-
By default, Kprl puts text strings into a separate resource file; this
makes it much easier to translate text without being distracted by
implementation details. If this option is supplied, all strings are
included in the source code instead.
- -S, --separate-all
-
Normally the only strings placed in the resource file are those which
Kprl can identify as part of the game's script. If this option is
supplied, all strings containing Japanese text are separated. This
usually catches any parts of the game's script that the default mode might
miss, but it can also clutter up resource files with false positives. It
obviously cannot be used with -s.
- -u, --unreferenced
-
By default, all code is disassembled, even where it can be proven that it
is never executed (for example, code immediately following a
goto or end()). Enabling this option will
suppress any code that is provably unreferenced; this is normally safe.
- -n, --annotate
-
Adds copious comments to the generated code. Two types of comment are
created: offsets in the bytecode to each command are noted, and some
function parameters are given labels that roughly indicate their meaning.
- -r, --no-codes
-
By default, a number of functions which primarily affect text display are
disassembled as control codes within string literals. Enabling this
option will disable this behaviour.
- -g, --debug-info
-
The official VisualArt's RealLive compiler puts a large amount of strictly
unnecessary data in the bytecode for debugging purposes. By default it is
ignored, but this flag causes Kprl to read it and include information
from it in the source code generated.2
- -t TARGET, --target=TARGET
-
Specifies the format of the data to disassemble (one of reallive,
avg2k, or kinetic). By default, Kprl detects a format
automatically. It is normally only necessary to use this option when
disassembling code from a Kinetic Novel that uses kinetic.exe,
since the kinetic format is subtly different from the reallive
format but is always detected as the latter.
- -f VER, --target-version=VER
-
Specifies the version of the bytecode to disassemble (either as a version
number consisting of up to four integers separated by dots, or as the name
of an interpreter executable to query for a version number). By default,
Kprl detects a version automatically.
Less common operations
-
-x, --extract
-
Decompresses and decrypts files. As with -d, -x can process
archives or standalone scenarios. The suffix .uncompressed is added
to all file extensions, to prevent inadvertent clobbering.
Additional options: -
-o DIR, --outdir=DIR
-
Places output in DIR
- -b, --break
-
Extracts files from archive, without decompressing them.
Additional options: -
-o DIR, --outdir=DIR
-
Places output in DIR
- -c, --compress
-
Compresses and encrypts files. files must be a list of
uncompressed scenarios. It is normally not necessary to compress files
manually; Rlc compresses the bytecode it generates by default, and
Kprl compresses files if necessary when archiving them. When
compressing, Kprl removes the suffix .uncompressed from the file
extension, if present.
1.2 Rlc: the compiler
Synopsis: rlc [options] file
file is the name of a Kepago source file to compile. Only
one main file can be processed at a time, though it may include code from other
files. It will be compiled to RealLive bytecode in a standalone scenario,
which can either be interpreted directly or added to a scenario archive.
The following options are recognised:
-
-o FILE, --output=FILE
-
Sets name of output file to FILE. If no name is specified on the
command line, Rlc will look for a #file directive in the
source code; if none is found, the output file will be called
rlas_output.txt.
- -d DIR, --outdir=DIR
-
Places output file in DIR.
- -i FILE, --ini=FILE
-
Specifies a gameexe.ini to use, or the directory containing one. Access to
the gameexe.ini that will be used at runtime is required in order to read
various configuration data. If none is specified on the command line,
Rlc will look for an environment variable GAMEEXE; failing
that, it will look in the directory containing the source file being
compiled.
- -e ENC, --encoding=ENC
-
Selects a character encoding for the input. The options and default are the
same as in the disassembler (see above).
- -x ENC, --transform-output=ENC
-
Selects a character encoding transformation for the output. See
3.5.2 for details.
- -t TARGET, --target=TARGET
-
Specifies the output target (one of reallive, avg2k, or
kinetic).
By default, Rlc attempts to detect the target automatically by
looking for an interpreter executable in the directory containing the
gameexe.ini being used; failing that, it falls back on RealLive as the
most likely option. The #target directive can also be used to set a
different target. This option takes precedence over the directive, which in
turn takes precedence over auto-detected targets.
- -f VER, --target-version=VER
-
Specifies the version of RealLive bytecode to compile for; see section
4.1.1 for the distinction between target and version. VER
may be either a version number (between one and four integers separated by
dots) or the path to an interpreter executable which will be queried for
its version.
By default, Rlc attempts to detect a suitable version automatically
by looking for an interpreter executable in the same way as for the
--target option and querying its version number. If this fails, it
defaults to version 1.0 (if the target is AVG2000) or 1.2.6 (for other
targets). You can always use the #version directive to override
this; unlike the #target directive, #version also overrides
explicit version specifications.
- -u, --uncompressed
-
Disables automatic compression and encryption of output. The output will be
given a .uncompressed suffix, as though it had been compiled normally
and then decompressed with kprl -x. This is only useful if you need
to examine the generated bytecode.
- -g, --no-debug
-
Strips debugging information and calls to debugging functions from the
compiled bytecode. The output will be smaller and marginally faster.
- --no-assert
-
Disables assertions (see assert()).
- --safe-arrays
-
Enables runtime bounds checking for Kepago arrays. This option has no effect
if --no-assert has also been specified.
- --flag-labels
-
This feature is experimental and incomplete.
Enables automatic generation of the flag.ini symbol information file
used by the RealLive debugger. Variables declared with the
labelled directive will be added to flag.ini automatically.
Currently, flag.ini is created automatically in the same directory as
the gameexe.ini in use; any existing flag.ini will be deleted
automatically without confirmation, and you will have to make your own
arrangements if you wish to combine labels from more than one source file or
to make use of flag.ini features other than basic variable labels.
The generated labels consist of the variable's name (and, in the case of
arrays, its length). All labels are placed in flag group 0.
1.3 Vaconv: the graphic converter
Synopsis: vaconv [options] files
files is the names of the bitmap files to convert.
The following options are recognised:
-
-d [DIR], --outdir=[DIR]
-
Places output files in DIR. Passing this option without
specifying DIR, or giving it the special value
PRESERVE,
causes output files to be placed in the same directory as their respective
inputs. If this option is not passed at all, output files are placed in the
current working directory.
- -o FILE, --output=FILE
-
Sets name of output file to FILE. The default is the base name
of the input file with an extension selected according to the target
format. Note that this option can only be used when only one input file
is supplied.
- -f FMT, --format=FMT
-
Sets output format to FMT. Possible formats are PDT, G00, and
PNG.
The default behaviour depends on the extension of the first input file: if it
is .g00, the default output format is PNG, otherwise it is G00.
- -g FMT, --g00=FMT
-
Allows you to select the G00 format to use (0, 1, or 2); see
1.3.1. The default behaviour is to select an appropriate
format automatically based on the data.
This option implies -f G00; you should not use -f and
-g together.
- -i FMT, --input-format=FMT
-
If Vaconv is failing to recognise the format of the source file, you
can bypass the auto-detection by specifying the format manually. Legal
values of FMT are the same as for -f.
- -m FILE, --metadata=FILE
-
For G00 output, allows you to supply a metadata file to specify features
like regions; see 1.3.2. If no metadata file is specified,
Vaconv looks for one accompanying the source file with the same base
name and the extension .xml. If this is not found, default
values are used.
For G00 input, allows you to override the name to be used if a metadata
file is generated.
As with -o, this option can only be used when only one input file is
supplied.
- -q, --no-metadata
-
For G00 output, causes Vaconv to ignore all metadata. For G00 input,
disables metadata output even where this would lead to loss of information.
- -b, --best
-
Maximise compression. Where this has any effect, it runs significantly
slower (by an order of magnitude in the worst cases) than the default
algorithm; for G00 output in format 0, however, where the regular algorithm
is particularly inefficient, I have seen file sizes almost halved, so it may
be worth using for releases.
1.3.1 Bitmap formats
RealLive makes use of two proprietary bitmap formats: PDT and G00.
The PDT format is a holdover from the older AVG32 system, and is the
standard bitmap format in AVG2000: it stores 8-bit paletted or 24-bit RGB
bitmaps, together with an optional 8-bit alpha mask. In RealLive proper
it is normally used only for cursor images.3
G00 is the true native format, used for most graphics in RealLive games.
There are three G00 formats, each (naturally) with their own advantages
and disadvantages:
| 0 |
|
24-bit RGB, no mask |
| 1 |
|
8-bit paletted; palette is ARGB |
| 2 |
|
32-bit ARGB, plus extra features (see below) |
Formats 0 and 1 are usually the most efficient, depending on the
type of data; format 2 is the most flexible. By default, Vaconv attempts
to select the most appropriate format automatically when converting to G00.
1.3.2 Advanced G00 features
Format 2 G00 bitmaps can contain multiple sub-bitmaps, termed “regions”.
These are used in some object functions (see 5.12) to create
objects with multiple states, such as buttons; such functions generally have
a parameter pattern which references a G00 region.
When creating a G00 bitmap, regions are defined by supplying an XML metadata
file along with the source image. The format is very simple, and an example
should be sufficient documentation. This is for one of the CG mode thumbnails
in Clannad:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vas_g00 SYSTEM "vas_g00.dtd">
<vas_g00 format="2">
<regions>
<region x1="0" y1="0" x2="114" y2="86" />
<region x1="0" y1="0" x2="114" y2="86" />
<region x1="0" y1="87" x2="114" y2="173"/>
<region x1="0" y1="87" x2="114" y2="173"/>
<region x1="0" y1="0" x2="114" y2="86" />
</regions>
</vas_g00>
Here five selectable regions are defined, but only two real
regions. These are selectable in-game with commands such as
objOfFile (0, 'SCGMA00') // Initialises object 0.
objPattNo (0, 1) // Selects region 1 (this is the same as 0, so has
// no visible effect).
objPattNo (0, 2) // Selects region 2 (switches to display the other
// sub-image).
1.4 RlXml: the auxiliary data converter
Synopsis: rlxml [options] files
files is the names of the files to convert. Their format
will be determined automatically from their extension. If they are RealLive
data files, they will be converted to a human-readable XML format; if they are
XML files, they will be converted back to the appropriate RealLive binary
format.
Currently only two formats are recognised: GAN animation files (extension
.gan), and their corresponding XML versions (extension .ganxml).
The following options are recognised:
-
-o NAME, --output=NAME
-
The meaning of this option depends on the number of files being converted.
If only one is being converted at a time, then -o specifies the path
and name of the converted file; if multiple files are being converted at
once, -o specifies the directory in which the converted files should
be placed, and their names will be derived automatically from the input
files' names.
If the option is not given at all, then output is placed in the current
working directory with automatically-generated names.
- 1
- CP932 is Microsoft's version of Shift_JIS; this
is RealLive's native character encoding.
- 2
- In practice this just means
the source will be cluttered up with #line directives giving
the lineation of the original code from which the game was compiled,
which is probably not incredibly useful.
- 3
- Vaconv's
implementation can read all PDT images, but it cannot write paletted PDTs.
The paletted format is extremely rare, however.
Ease of use is a function of time, and I haven't had that much time to feed into
it, so you'll have to do a bit of work to start a project with RLdev.
Moreover, what's required will change (hopefully for the better) over time, so
be sure to check this section again with each new release.
2.0.1 The project header file
If there is a file called global.kh in the same directory as a source
file, Rlc will automatically include it, as though every source file
began with the line #load 'global'. Placing configuration
options and common definitions in such a file will automatically share them
among every source file in the directory.
You can of course use a different name for your project header, but if you do,
you will have to load it manually.
2.0.2 Memory allocation
You must identify two sections of memory for Rlc to use to allocate
temporary variables. At a minimum you must provide a block of 100 local
integers and a block of 10 local strings.
By default, Rlc uses the whole C[] array for temporary
integers, and S[1900] to S[1999] for temporary
strings. If you are writing a program from scratch, you can either avoid
using these memory areas manually, or change them to something more
convenient. If you are modifying an existing program, you will have to
analyse its memory usage yourself to discover whether these defaults are
suitable, and modify them if they are not. Be warned that any direct access
or modification of a variable in a space that has been assigned to Rlc
could cause your program to malfunction, but Rlc has no way to detect
such conflicts automatically.
To modify the settings for memory allocation, call the
rlcSetAllocation() function. You must call this function at the start
of every file in your project — it is evaluated at
compile-time, so it is not enough simply to call it at the start of the first
scenario. The easiest thing is just to place it in your global.kh (see
above), which is automatically included at the start of every scenario.
Kepago is a simple imperative programming language in the style of C or Pascal.
It was designed as a substitute for the unknown language used by VisualArt's
themselves. The reference implementation remains my kpc compiler for
AVG32.
The present implementation of Kepago for RealLive is limited in scope; it
currently lacks major features such as user-defined functions, which limits its
expressivity considerably. It is, however, adequately usable for simple
programming tasks.
3.1 Lexical structure
The encoding of a file is determined by the default encoding for which
Rlc was compiled (this is CP932 in the standard configuration), and
can be overridden on a case-by-case basis on the command line.
Identifiers can use almost any character defined in the current encoding.
They are not case-sensitive. They may not begin with numbers, or with the
reserved characters $ and @. Identifiers beginning and/or ending with two
underscores are reserved for internal use: they are valid, but you should
avoid using them except for the specific cases documented in this manual.
Numbers are decimal by default; digits can be separated with underscores, as
in ML. Hexadecimal numbers are identified by a prefix $, as in Pascal.
Binary numbers take the prefix $#, and octal numbers $%.
Comments come in two flavours. Line comments begin with //, and
block comments (which behave like C comments, i.e. they cannot be nested)
are delimited by {- and -}.
3.2 Structure of a file
A Kepago file is simply a text file containing a sequence of statements and
definitions. There is no statement separator: statements are neither
line-delimited like Basic nor semicolon-delimited like C and Pascal, although
line breaks and commas can be used almost arbitrarily to separate them for
clarity.
A file can be ended with the eof keyword; nothing following this
will be processed by Rlc.
3.3 Expressions
Expressions are fairly ordinary. The following binary operators are
recognised. Their meanings are identical to C, but their precedence is not:
<< >>
* / % & // higher precedence
+ - | ^
>= > <= <
== !=
&& // lower precedence
||
In addition, the operators -~! are recognised as unary
operators, again with the same semantics as in C; these all have equal
precedence greater than any binary operation. Precedence can, as usual, be
overridden with parentheses.
The arithmetic operators (from | upwards) can have an
= appended to form assignment operators, again as in C; note
however that these cannot be used on the right-hand side of an expression or
within function calls. There are currently no unary increment/decrement
operators and no address or pointer dereference operators.
Example:
x = 1
x += (strlen(s) + 2) * 3
When processing strings, the only permitted operator is +, which
performs string concatenation. It is also legal, within integer expressions,
to compare strings with the comparison operators. Apart from that it is not
legal to mix strings and integers in expressions, though the various integer
types may be mixed freely.
A number of Kepago constructs utilise the concept of a “constant
expression”. This refers to any expression that can trivially be fully
evaluated at compile-time. A constant expression may contain the full range
of operations, and it may refer to constant symbols, but not variables. It
may also contain calls to certain functions, such as max(), that can
be evaluated at compile-time, and to a set of macros, such as
defined?(), that are always evaluated at compile-time.
3.4 Statements
In addition to the statement types detailed in this section, it is also valid
to use a string expression by itself as a statement, which is how text display
is normally accomplished. This topic is discussed in depth in section
3.5.
3.4.1 Blocks and scopes
Anywhere a statement is valid, it can be replaced with a block. Blocks are
opened with : and closed with ;, and function in much
the same way as braces in C-style languages.
Statement blocks double as scoping constructors. Symbols are visible only
within the scope in which they are defined, or scopes nested within it. For
example:
int foo = 1
if (foo == 1):
int bar = 2
'foo is \i{foo} and bar is \i{bar}' // prints "foo is 1 and bar is 2"
pause;
'foo is \i{foo}' // prints "foo is 1"
'bar is \i{bar}' // compile-time error: bar is not visible in the outer scope
pause
While higher-level control structures should be used where possible, program
flow may also be controlled with goto,
gosub, and related functions. Labels for use with
goto statements can be defined at any point where a statement is
valid; a label is any valid identifier beginning with @. The
scope of a label is global; if the same label is defined multiple times, a
warning is issued and the later definition is used.
Example:
@loop
'This text is displayed repeatedly in an infinite loop.'
pause
goto @loop
You should, of course, write this instead:
while 1:
'This text is displayed repeatedly in an infinite loop.'
pause;
Rlc optimises conditional tests away when the conditions can be
evaluated at compile-time, so it generates identical code in both cases.
3.4.3 Variable declarations
Declaration syntax
Declaration statements are of the form
type [(directives)] variables
type is the type given to all the variables declared
by this statement, directives is an optional parenthesised list
of flags that control their declaration, and variables is a
comma-separated list of declarations.
Each declaration in variables consists of an identifier which
may optionally be followed by an array declaration (see next section), an
initial value, and/or an address specifier. Initial values are declared
with the form identifier = value; addresses
are declared with the form
identifier -> space.address. For
example:
byte a // value undefined, address automatic
byte b = 2 // initialised to 2, address automatic
byte c -> MEMARR_A.4000 // value undefined, address A8b[4000]
byte d = 2 -> MEMARR_Z.10 // initialised to 2, address Z8b[10]
The types currently supported are:
| int |
|
32-bit integer |
| byte |
|
8-bit integer |
| bit4 |
|
4-bit integer |
| bit2 |
|
2-bit integer |
| bit |
|
1-bit integer |
| str |
|
character string |
The directives currently supported are:
block
-
Ensures that all variables declared in the one statement are allocated
contiguously, and, in the case of integers smaller than 32 bits, ensures
that they are packed.
An example will illustrate the use of this:
int x, y, z
sum (x, z) // value is undefined
int(block) x, y, z // x, y, and z guaranteed to be contiguous
sum (x, z) // guaranteed to equal x + y + z
zero
-
Ensure that the allocated memory is initialised. Integers not otherwise
given initial values will be set to 0; strings will be set to the empty
string.
labelled
-
If the --flag-labels option was passed to Rlc, entries
will be created in flag.ini for variables declared with this
directive.
ext
-
Variables declared ext are not constrained by enclosing
blocks; they remain in scope permanently from the point of their
declaration unless deallocated manually.
Arrays
Arrays are allocated in the same way as other variable declarations, with
the addition of an array declaration immediately following the variable's
identifier. This consists of the standard square brackets surrounding
an integer constant giving the array's length.
An array can be initialised in any of three ways. Firstly, if the
zero directive is included in the declaration, the memory
allocated will be cleared automatically. Secondly, if a single expression
is given as an initial value, every member of the array will be initialised
to that value. Finally, individual members of the array can be initialised
by providing a tuple as the initial value:
int a[5] = { 1, 2, 3, 4, 5 }
In this last case, and only in this case, the square brackets
may be left empty, and the length of the array will be set automatically to
the number of elements provided:
str strings[] = { 'foo', 'bar', 'baz' }
Only one-dimensional arrays are supported.
Caveats
Memory allocation is currently handled statically based on the settings
passed to rlcSetAllocation().
All blocks allocated are word-aligned: that is to say,
bit x, y, z allocates three whole words even though only three
bits are being used. You can get round this by allocating variables of
smaller sizes in arrays or with the block directive.
Be aware that the variable allocation system is not sound.
if 1:
int x = 0
gosub @oops
'x = \i{x}'
pause
; // end of scope, x implicitly deallocated
end
@oops
int y = 1 // y allocated at the same address as x
ret
prints x = 1, not x = 0, even though
the value of the logical variable x was never modified!
Safe usage of the current system requires that any variables be allocated
outside the largest scope in which they are used; in the example above,
x would have to be declared at the top level, and any variable
which is to be used after a call to another scenario should be declared
globally in a project header file to ensure that its memory will not be
used for anything else unintentionally.
A future version of Rlc will hopefully introduce dynamic memory
allocation, which will mitigate these problems to some extent.
3.4.4 Constant declarations
“Constant” implies that the value is always known to the compiler, not
that the value is immutable; Kepago constants are actually more like
compile-time variables than constants in the strict sense.
Constants can hold integer or string values, and can be used in expressions
wherever a literal would be valid.
They are handled with the following directives (see also 3.4.5).
#define IDENT
-
Defines the symbol IDENT. Multiple symbols can be defined at
once, separated by commas. The value bound to IDENT is
a non-zero integer.
The scope of a symbol defined with this directive is not constrained by
blocks; it remains defined in all code that is compiled subsequent
to its definition. In the current state of the implementation, this
effectively means that it remains defined to the end of the file. Once
user-defined functions are implemented things will become a little more
complicated.
#undef IDENTS
-
Undefines the given symbol(s).
At present, the only symbols which may be undefined manually are those
which were defined globally with #define.
#const IDENT = EXPR
-
Defines a new constant symbol. The constant expression EXPR
(see 3.3) is evaluated, and its value assigned to
IDENT.
Multiple symbols can be defined at once, separated by commas.
The scope of a symbol defined with this directive is limited to the current
block.
#set IDENT = EXPR
-
Mutates the value assigned to the symbol IDENT. This does not
affect the symbol's scope.
As a shortcut, you can use the form #set foo += 1 instead of
#set foo = foo + 1; the same goes for other arithmetic operators.
3.4.5 Directives
Like in C, directives (that is, statements which modify compiler state
rather than generating code) begin with a hash sign #; unlike in
C, these are processed by the compiler itself, not a preprocessor.
The following directives are recognised in the current version of Rlc:
#load 'FILE'
-
Loads a header file. Rlc looks first for FILE, then for
FILE.kh, first in the same directory as the source file,
and then in the RLdev library directory. The contents of the file are
parsed at the current position in the same way as C's
#include
directive.
#file 'FILE'
-
Sets a default output filename. This can be overridden on the Rlc
command line with the -o option.
#target TARGET
-
Selects a default target; TARGET should be RealLive,
AVG2000, or Kinetic. This can be overridden on the Rlc
command line with the -t option. Note that the parameter is an
identifier, not a string.
#version A[.B[.C[.D]]]
-
Selects a bytecode version to generate code for; this is the equivalent of
the -f command-line option, and functions identically.
#resource 'FILE'
-
Loads the named resource file, providing access to all the strings it
contains. The scope of its definitions extends from the
#resource directive to the end of the file; it must precede
any #res directives referencing its contents.
#res<ID>
-
Inserts the resource string with the identifier ID at the
current position. See section 3.6 for details.
#entrypoint INDEX
-
Places an entrypoint at the current location in the code; entrypoints
function as labels for the jump() and farcall() functions to
jump into a scenario. INDEX should be an integer between 0 and
99 inclusive.
If INDEX is 0, the directive is ignored. Entrypoint 0 always
comes at the very start of the scenario, and Rlc defines it
automatically. Superfluous entrypoint directives are accepted for
compaitibility reasons.
#character 'NAME'
-
Adds NAME to the dramatis personae table in the header
of a bytecode file. This is what kprl -lN reads.
Character name data appears not actually to be used by RealLive; its
purpose is a mystery, but it's probably just debug information. It does
not exist in AVG2000, so the #character directive is ignored
when compiling for that target.
#line LINE
-
Tells Rlc to start counting lines from the given value, rather than
the actual line position in the source file. May be of use if you want to
preprocess code or use literate programming tools, but it's really only
there to give the disassembler something to do when asked to read debug
information.
#kidoku_type TYPE
-
Determines the format to use for kidoku markers in generated bytecode. If
the executable is to be run with RealLive 1.2.6.6 or later, this should
usually be 2, although it appears not to be critical that this be the
case; in all other cases it must be left with its default value of
1.
Note that this is a global setting; the value that will be used for a
given output file is that assigned most recently when the end of the file
is reached.
#print EXPR
-
Causes the compiler to print the current value of EXPR to stderr.
EXPR must be a constant expression, but it can be an integer or
a string.
#warn EXPR
-
As #print, but the output is formatted as a compiler warning.
#error EXPR
-
As #print, but the output is formatted as a compiler error, and
compilation is halted.
3.4.6 Conditional compilation
The code to be compiled can be selected at compile-time by using a further
set of directives. As with the others, note that these are not
related to any sort of preprocessor. They are block statements: they can
only be used where a statement would be valid, and the code they enclose
is analysed for syntactic validity even if it is not compiled.
#if EXPR
#elseif EXPR
#else
#endif
-
Select a block of code to compile based on the value of EXPR:
#const First = gameexe('SEEN_START')
#if First == 1
#print 'The game begins with scenario 1'
// Code for this case
#elseif First == 2
#print 'The game begins with scenario 2'
// Code for this case
#else
#error "The game begins with scenario \i{First}, and I don't \
know what to do"
#endif
Unlike :; blocks, the contents of these directives are not
counted as blocks for scoping purposes.
#ifdef EXPR
#ifndef EXPR
-
It is often convenient to be able to determine whether a given symbol has
or has not been defined. These two directives are shorthand ways to do
this: they are exactly equivalent to
#if, except that every
identifier in EXPR that is not directly compared with anything
else is replaced with a call to the defined?() macro.
#ifndef foo && bar && baz > 1 // -> #if !defined?(foo) && !defined?(bar) && baz > 1
{- do stuff -}
#endif
3.4.7 Control structures
if CONDITION STATEMENT
if CONDITION STATEMENT else STATEMENT
-
A standard conditional control structure. CONDITION is evaluated;
if the result is non-zero, STATEMENT is executed. The
optional
else allows a statement to be specified for execution
if the condition evaluates to zero.
Kepago adopts the usual rule to resolve the `dangling
else' problem: each else is taken to apply to the
innermost available if. This can be overridden by use of blocks:
if rnd(10) < 5:
if SyscomEnabled, 'your code here';
else
'Without the :; above, this would attach to \
the inner `if\'.'
while CONDITION STATEMENT
-
If the value of CONDITION is non-zero, STATEMENT
is executed repeatedly; CONDITION is checked after each
iteration, and the loop exits when it evaluates to zero.
You can exit the loop prematurely with the break
statement, or jump straight to the next iteration with the
continue statement.
repeat STATEMENTS till CONDITION
-
The opposite of
while. STATEMENTS is executed
at least once, and the loop continues for as long as CONDITION
is zero.
For historical reasons, and unlike the other structures in this section,
the repeat loop is a block in itself: you can include multiple
statements without a :; block.
for (INITIALISATION) (CONDITION) (INCREMENT) STATEMENT
-
A C-style `for' loop.
for (int i = 0) (i < 10) (i += 1):
do_stuff(i);
is equivalent to
: int i = 0
while i < 10:
do_stuff(i)
i += 1;;
case EXPR
of CASE
other
ecase
-
EXPR is compared to each CASE in turn; if a match is
found, the following code is executed. The
other clause is
equivalent to an of clause, but always matches; it is
optional, and if present must be the last clause.
As with C's `switch' statement, each case should be terminated with a
break statement, or execution will fall through to the next
case. This is useful where multiple values require the same treatment:
int qual = SoundQuality
'Sound mode is '
if qual % 2, '16-bit ' else '8-bit '
case qual
of 0
of 1
'11 kHz'
break
of 2
of 3
'22 kHz'
break
of 4
of 5
'44 kHz'
break
other
'48 kHz'
ecase
pause
Rlc will attempt to optimise the test and jumps away if
EXPR can be evaluated at compile-time. In certain cases it may
be desirable to know whether this optimisation has succeeded (for example,
when writing other clauses that raise an error, you may wish
to raise the error at compile-time in such cases). Rlc therefore
defines the symbol __ConstantCase__ when compiling
case blocks; if it is non-zero, then the innermost case block
currently being compiled has been optimised away.
3.4.8 Function calls
With one or two exceptions, function calls use the standard C-like syntax:
the function name followed by a list of parameters, bracketed and
comma-delimited.
Anything valid on the right-hand side of an assignment expression is also
valid as a function parameter. Certain functions permit other constructs,
such as tuples (parameters containing multiple expressions; in Kepago these
are enclosed in curly braces), and one or two have wholly different
syntaxes. All these exceptions are documented under the appropriate
function in chapter 5.
Unknown functions
It may sometimes be desirable to make use of a function that exists in
RealLive but is not currently exposed through the Kepago/RealLive
API. There exists a `raw opcode' command that can be used to insert such
functions: it has the format `op<type:module:opcode, overload>', which can be followed with a
parenthesised parameter list as any function (but cannot be used in
assignments). The variables are all integers; type and
module appear to be used to identify groups of related
functions, opcode identifies a particular function (its value is
only unique within a particular type/module combination), and finally
overload identifies an instance of that opcode (different
instances have the same broad semantics, but take varying numbers of
parameters).
Mnemonic aliases are defined for some values of module, in the
hope that this may give some hint as to the purpose of an unknown function
when Kprl disassembles a scenario containing it. For example, the
pause function can also be invoked with
op<0:Msg:00017, 0>.
In the general case, however, it is preferable to add support for the
function to the API than to use it with op<>. This can be
accomplished by editing reallive.kfn, or more easily by providing
the author with an example and description, upon which he'll happily do it
for you.
3.5 String handling
Strings can be delimited with either ' or "; there is
no semantic difference between the two. Due to the limitations of the
RealLive system itself, only JIS characters are valid in strings. To use
within a string a quote of the type being used to delimit that string, it must
be escaped with a backslash; line breaks must also be escaped, and any
whitespace at the start of the following line will be ignored, unless this too
is escaped:
str s = "This string's value \
is a single line, spaced\
\ normally.")
Note however that arbitrary characters may not be escaped: any escaped
alphabetic character is taken to open a control code, as will an escaped left
brace.
3.5.1 Displaying text
Display strings (that is, strings used directly as statements, rather than
in expressions) are passed directly to RealLive's text display routines.
When one is encountered, a text window of the currently active type is
opened and the text printed in it. (Text window types are defined in
gameexe.ini, as described in 7.2.3, and selected with the
TextWindow() function.)
3.5.2 Usable characters
The RealLive bytecode format requires all textual data to be in the
Shift_JIS encoding. By default, therefore, RLdev converts text to
Shift_JIS: it follows that the only characters which are valid in Kepago
strings are those in the JIS X 0201 or JIS X 0208 character sets. This is
perfect for Japanese text, and sufficient for most English.
Other languages require characters not present in the JIS character sets.
There are two solutions to this issue. One is to fake the required
characters: some glyphs can be included as bitmaps with \em, others
(such as accented letters) can be built up through overprinting by using
\mv. A better solution — in some cases, such as Chinese, the
only solution — is to use a non-JIS character set, with a
non-standard encoding that has the same characteristics as Shift_JIS, and to
arrange for this to be decoded on the fly in an intermediate layer between
RealLive and the operating system.
In RLdev, this is accomplished in three stages:
First, the UTF-8 encoding should be used for source code files. This is the
only supported input encoding that can handle non-Japanese text. If it is
not the default encoding for your copy of RLdev, you will have to specify
the option "-e utf8" when compiling the code.
Second, an output encoding transformation is applied. This is done
by passing the "-x ENC" option when compiling the code,
where ENC is the name of a supported transformation (see below).
The final step is to arrange for the RealLive interpreter to be able to
decode the transformed text. This can be done either by modifying the
interpreter itself, or by using an extension DLL to hook into it at
runtime. A suitable DLL is provided in the form of the rlBabel library
(see 6.1).
RLdev currently supports the following transformations:
-
None
-
For Japanese or ASCII text. This is the default.
- Chinese
-
For Simplified Chinese text. The output uses a non-standard encoding of
the GB2312 character set (based on a system formerly used by the Key Fans Club).
- Korean
-
For Korean text. The output uses a non-standard encoding and character set.
The characters encoded comprise the non-hanja parts of KS X 1001, plus the
4000-odd additional precomposed hangul from the ill-fated Unicode 1.1: this
appears to be the simplest practical compromise for encoding modern Korean
text, though the encoding could potentially be expanded further if necessary.
- Western
-
For Western European text. The output uses a non-standard encoding of
the Windows CP1252 character set (ISO-8859-1 with extensions).
Since this encoding uses double-byte characters to represent some
half-width characters, it should be used with the rlBabel lineation
library (6.1.2) to ensure correct character spacing.
3.5.3 Control codes
Control code syntax is TEX-style, \identifier{}, where what goes between the curly braces has the
same syntax as the contents of the parentheses in a normal function
call — in most cases the braces are either empty or contain a
single integer or variable. Note that for control codes of more than one
letter, braces are always required, even if no parameters are being passed
to the code.
Descriptions of the basic control codes currently available follow. In
addition to these, there are also more complex codes \name and
\g, described in the following subsections, and a set of control
codes only valid in resource files, described in section
3.6.
\n
-
Forces a line break at the current position, retaining indentation.
\r
-
Forces a line break at the current position, resetting indentation.
\p
-
Inserts a pause. Text display will halt until the player clicks to
advance, but it will then continue where it left off, without clearing the
screen.
\wait{time}
-
Pauses time ms before continuing to print text.
\m{name}
\l{name}
-
Inserts the value of a name variable: \m for global names, and
\l for local names. name can be either an integer
(the index of the variable, as used in calls to the GetName()
family) or one or two alphabetic characters (`A' to 'ZZ', as used in
the #NAME variables in gameexe.ini). See 4.4.2 for
details of name variables.
A second argument may optionally be supplied, which should be a constant
integer. If this is given, it identifies a single character of the name
to be printed (zero-indexed).
\i{int}
-
Displays the value of int, which can be an arbitrary integer
expression.
You may optionally supply a minimum length, of the format
\i:length{int}; if
this is supplied, the number will be left-padded with zeroes to ensure
that it is always at least length digits.
\s{str}
-
Displays the value of the string variable str.
\size{[pixels]}
-
pixels is optional. If it is given, the font size will be set
to that size; if it is not, the font size will be reset to the default.
\c{idx, [bg_idx]}
-
Sets the colour of the following text. The values are indices to the
game's palette, which is defined in gameexe.ini with the
#COLOR_TABLE command. \c can also be used
without any arguments, which resets the colours to their defaults.
For example, if COLOR_TABLE.001 were red and
COLOR_TABLE.002 were green, someone addicted to pointless
examples could write
'I like using \c{1}red\c{} text, and sometimes \
\c{1, 2}red with a green shadow\c{}.'
This control code behaves in the same way as the FontColour()
function: the colour will be reset automatically at the end of the
string. If you want to set multiple strings in the same non-default
colour, you will have to use \c at the start of all of them.
\ruby{text}={gloss}
-
Used to display interlinear glosses or ruby, also known as
furigana. text will be displayed normally, with
gloss printed above it in small type.
You must set the #WINDOW.LUBY_SIZE
(sic) variable for the current window to an appropriate size in
order to use this control code.
\e{index, [size]}
\em{...}
-
Prints a bitmapped character or icon at the current point in the text.
\e prints the bitmap in full colour; \em takes its alpha channel
and displays that in the current font colour.
Bitmaps are drawn from files defined with the #E_MOJI
settings in gameexe.ini. You must define at least E_MOJI.000 in order to
use these codes. If multiple files are defined, they should contain the
same characters at different sizes.
The bitmap used is always the largest available that is in height smaller
than or equal to the current font size; if no matching bitmap is
available, a space is left. The optional size argument can be
used to force the font size to a particular size temporarily, to select a
particular size manually. Note however that in the current implementation
it also resets the font size to the window's default - you will have to
follow it with a \size code if you were working at a different
size.
\mv{x, y}
\mvx{x}
\mvy{y}
-
Move the insertion point (i.e. the offset of the next character) by (x, y)
pixels.
If the new offset is beyond the right-hand margin of the text window, the text
will automatically wrap, effectively placing it at the start of the next line
instead. Other margins are not checked, but text will always be clipped to the
window margins; you can place text above or to the left of the window, but only
those parts of letters within the margins will be displayed.
There are three major uses for these codes. The first is character composition
by overprinting:
#const cw = gameexe('WINDOW.000.MOJI_SIZE') / 2
+ gameexe('WINDOW.000.MOJI_REP', 0) / 2
'A t^\mvx{-cw}ete-`\mvx{-cw}a-t^\mvx{-cw}ete with Sayuri'
page
displays “a tête-à-tête with Sayuri” fairly reliably,
even though the accented characters do not exist in most Japanese fonts.
(Note the code that calculates the width in pixels of a Latin character
- this would need modifying if you were not printing to window 0, or not
using the default font size.)
The second use is printing superscript and subscript text:
'\size{26}x\size{16}\mvy{-1}2\mvy{1}\size{26}' // x^{2}
' +\mvx{8}' // +
'log\mv{1,13}\size{18}16\size{26}\mv{6,-13}y' // \log_{16} y
'\mvx{8}=\mvx{8}z\size{}' // = z
page
displays a reasonably nicely spaced version of “x2 +
log16 y = z” in most fonts (observe the use of \mvx{8}
to insert a narrower-than-usual space).
The final use is printing non-square bitmapped characters:
'\e{0}\mvx{-4}' would be a suitable way to display a bitmap
containing a character four pixels narrower than the font height.
These codes are also used by the rlBabel DLL to implement proportional
text output (see 6.1.2 and the rlBabel source code
for implementation details).
\pos{x, y}
\posx{x}
\posy{y}
-
As \mv etc., except that the coordinates are interpreted as
absolute offsets from the origin, rather than relative to the current
insertion point.
As display strings are normally used for game text, RealLive naturally
provides means for identifying character names. This is accomplished
with the \name control code.
The format of this code is
\{text}. text is
arbitrary text, which is used as the current character name.
Note two unique features of this code. Firstly, it has no identifier; you
can use \name{} as well, but \{} is the canonical
form. This is just to save typing, as this is the most common control code.
Secondly, if it is followed by a space, that is gobbled. This
permits you to write '\{Foo} "..."', and have it appear correctly
spaced in the output. If you actually wanted a space after the name, escape
it with another backslash: '\{Foo}\ "..."'. (You probably won't
like the results.)
The effect this has is can be controlled to a great extent by settings in
gameexe.ini. If #WINDOW.NAME_MOD is non-zero for
the current window style, the name will be displayed in a separate smaller
window, based on the other #WINDOW.NAME settings. If
#WINDOW.NAME_MOD is zero, the name will be
printed inline at the current position, and followed with a space; in this
latter case, if #WINDOW.INDENT_USE is non-zero,
an indent point will then be set at the new x offset in the text window,
meaning that subsequent lines of text will begin at that offset.
A separate but related topic is the use of name variables to store
customisable character names. As described in section 4.4.2,
these can be referenced directly from within display strings with the
\l and \m control codes. For example, a common idiom to set
the speaker name to the player is the code \{\m{A}}.
Kepago provides for hypertext glosses using a control code \g.
The syntax is
\g{text}={gloss}.
text is arbitrary text, which is always output in the normal way,
and may be highlighted in some way. When it is clicked on, the value of
gloss is passed as a string to a handler routine, which would
typically display it in a subsidiary window.
Support for these is limited in Rlc. The control code is always
accepted, but it is only processed when the rlBabel library is in use for
dynamic lineation (see 6.1.2); in all other cases,
text is simply displayed as normal text.
Since the RealLive system is designed for use with Japanese only, it does
not implement the more complex line breaking logic required for Western
languages. In Japanese, it is acceptable to break a line anywhere,
including in the middle of words, so this is the behaviour RealLive
adopts. This makes it necessary to implement lineation specially for
Western text.
Versions of RLdev up to 1.03 implemented static lineation. Since this
is impossible to do correctly, however, this feature has been removed and
replaced with various dynamic lineation techniques, which ensure that text
is always correctly lineated even in the presence of variable-length
elements. These are implemented as libraries, and disabled by default; see
section 6 for usage details.
3.6 Resource files and resource strings
It can often be desirable to separate program logic from the game's script;
for example, if one is releasing a game in several languages, it is more
convenient to provide translators with just the text, while if one is
releasing versions of the same game with different code (for example, adult
and all-age versions) it can be convenient to be able to use the same script
with both. Resource files provide a simple means of accomplishing this.
3.6.1 Resource file syntax
A resource file is basically an association table of keys to strings.
A key is defined by enclosing it in angle brackets. Keys can contain any
combination of characters, with a few restrictions. Firstly, purely
numerical keys are treated as numbers: <$00d> identifies the
same string as <13> and <0013>. Secondly, keys may
not contain spaces, line breaks, or the characters > and
}. Both of these restrictions can be worked round by quoting
the key; such a key uses the normal string literal syntax, can contain any
character, and distinguishes between different representations of integers.
Each occurrence of a key begins a new string; the text between it and the
next key or the end of the file is interpreted as a resource string. The
syntax of a resource string is broadly similar to that of a display string
literal (see section 3.5), with a few exceptions:
-
The literal character
< must be written \<.
- Kepago comments are parsed, so literal
// must be written
\// and literal {-
must be written {\-.
For block comments, note that control codes take precedence: that is to
say, \{- will be interpreted as the opening of a name block
(see 3.5.4) that begins with a - character, and
\i{-1-}{1} will raise a syntax error, since it will be interpreted
as \i{ -1 - } followed by the text {1}, and Kepago
requires a right-hand side to what is being read as a subtraction operator.
- Quotes are always treated as literal characters, and never need
escaping.
- Line breaks do not need escaping; however, any whitespace before an
unescaped line break will be trimmed,1 so you can escape the line break to preserve
it. Alternatively,
\_ can be used to represent a
non-trimmable space.
3.6.2 Additional control codes
There are also some additional control codes that can be used in resource
strings.
\d
-
Where two versions of a script are being produced, and one requires
fewer strings than the base version, you can use \d to remove
superfluous strings without modifying the Kepago source code.
The resource string becomes a `remove string' command; referencing it
will remove the reference, and if it is referenced by a display string
command that is followed by a pause() call, the pause will also be
removed.
\a{[str]}
-
Where two versions of a script are being produced, and one requires
more strings than the base version, you can use \a to add extra
strings without modifying the Kepago source code.
The current resource string is processed as normal, but when it is
referenced by a display string command, the resource string
<str> will be added after it as
another display string command; if the referencing command was followed
by a pause() call, another pause() will be added after the
new string. If multiple \a codes appear in one resource string,
the extra strings will be added in the order of the \a codes.
str is optional. If it is not given (an `anonymous
reference'), the next string in the resource file will be used; see
3.6.3 below for how multiple anonymous references are
resolved.
3.6.3 Anonymous references
If multiple anonymous references are given in a single string, they are
resolved sequentially and recursively: that is, anonymous references
within a string referenced anonymously are resolved before any
further anonymous references in the original string.
An example may make this clearer:
<foo>
This is foo.\a\a
<bar>
This is bar.\a
<baz>
This is baz.
<quux>
This is quux.
This is equivalent to writing
<foo>
This is foo.\a{bar}\a{quux}
<bar>
This is bar.\a{baz}
<baz>
This is baz.
<quux>
This is quux.
Note how the second \a in <foo> resolves to
<quux>, because <baz> has been taken by the
\a in <bar>.
Resource strings for use with anonymous references can be anonymous
themselves: that is, you could also write
<foo>
This is foo.\a
<>
This is anonymous.
This can be clearer to read, and has the advantage that anonymous
resource strings normally generate an error, so you will be able to
tell whether you have included the correct number.
3.6.4 Using resource strings
To load a resource file and make the strings it contains available to your
code, use the #resource directive at the start of the Kepago source
file.
To reference a resource string, use the #res directive with the key
of the string you wish to include.
- 1
- LATEX users may expect
spacing to be automatic, but the syntactic similarities to LATEX are
coincidental; `and!the', where the ! is a line break, will produce
`andthe', not `and the'.
Chapter 4 The RealLive system
4.1 Overview
RealLive is an engine designed to power bishōjo games such as
visual novels and simple simulations; it is based around a fast
Turing-complete bytecode interpreter, and provides functionality for text and
graphics, sound, music, and simple animation, along the lines that such games
require. Notable products based on the RealLive engine include Key's
Clannad, Planetarian, and Kanon Standard Edition, and
130cm's Princess Bride (not to be confused with the book/film of the
same name).
A RealLive game is made up of four main parts:
the interpreter (avg2000.exe, reallive.exe, or
kinetic.exe), the configuration file (gameexe.ini), the scenario
file (typically seen.txt1),
and accompanying data files, which vary in type and number, but typically
include graphics (in the g00 and pdt formats), animations (in
the gan and anm formats), and music (in the nwa format).
Of these parts, RLdev deals only with the scenario file and graphics,
although Rlc reads certain settings from gameexe.ini when
compiling.2
Note that in the case of DRM-protected games, the configuration and data files
are further compacted into a single file (typically kineticdata.pak),
which is encrypted using a rather stronger method that I have not yet
discovered how to unlock. It is not possible to access the files this
contains directly with RLdev, although it is simple enough to extract the
contents manually from a memory dump of a running game.
There exists a clone of the RealLive interpreter, Jagarl's xclannad
(currently at http://www.creator.club.ne.jp/~jagarl/xclannad.html).
The latest version at the time of writing, 0.06, did not implement enough
features to be a viable replacement, but it promises to become such with
time.
4.1.1 Targets and versions
The family of interpreters I refer to here as RealLive actually comprises
three more-or-less distinct interpreters: AVG2000, RealLive, and Kinetic.
While the API and basic bytecode format used by all three is essentially
identical, they are not compatible: AVG2000 (the original successor to
AVG32, later renamed to RealLive) uses a different scenario header and
encoding scheme, and Kinetic (a special DRM-enabled interpreter used only
for the Kinetic Novel series) reassigns a large number of fundamental
operations in what appears to be a futile attempt to make
reverse-engineering harder.
To complicate matters, however, the API itself is under constant
development. There are significant differences between versions. For
example, the grpRotate() functions were introduced in 1.1.5, and auto
mode early in the 1.2 series. There is no fixed mapping between the
API/bytecode version (the “version”) and the header/basic operation types
(the “target”): the AVG2000 interpreter was retired at around version
1.0.0.8, and the Kinetic interpreter introduced at version 1.2.6.4, but
RealLive-proper interpreters can be found at all stages of development.
When compiling for a RealLive interpreter, therefore, the target and
version must both be chosen separately. Bytecode compiled for a
1.1-series RealLive will normally run perfectly on a 1.3-series
RealLive,3 but the converse
is not true, and bytecode compiled for RealLive 1.2.7.0 is incredibly
unlikely to run in Kinetic 1.2.7.0. Since different versions of games
sometimes use incompatible interpreters, you will probably have to
recompile code with different flags or directives if you are working on
such a game. In most cases, however, Rlc will manage to
generate suitable code for any interpreter from any source code, given
the correct compilation settings.
Except where otherwise specified, the version documented here is basically
1.2.6.8 (as supplied with Kanon Standard Edition). Other versions
have not been exhaustively tested: differences and limitations are
documented where I'm aware of them, but in general you should not assume
that any feature mentioned in this manual is necessarily available,
particularly if you're working with a 1.0- or 1.1-series interpreter. Note
that very few tests have been carried out with the 1.0.0.8 interpreter, and
practically none with AVG2000.
4.2 Scenarios
The scenario file contains the game logic. It is a simple archive of up to
9,999 individual compilation units, termed `scenarios', which are named
seen0001.txt to seen9999.txt; each of these is an individually
compressed and self-contained block of RealLive bytecode.
The scenarios represent the major divisions of a program; only code from one
scenario can be accessed at any one time, though switching between them can
easily be done with the jump() and farcall() functions, and up to
100 entrypoints may be defined within each scenario, effectively permitting
access to arbitrary locations. A common idiom is to define several related
functions (particularly where they share code) in one scenario, and use
farcall() with an entrypoint index to access them from the rest of the
game; this works on the same principle as linking a library into a C program,
but in Rlc it must be done by hand.
It is not necessary to build a scenario file in order to run code: if a file
of the form seenNNNN.txt exists in the same directory as the scenario
file, its contents will override the scenario of that number. This can be
convenient when debugging, and is the only trivial way to inject custom code
into a DRM-protected game.
4.3 Debugging
The RealLive interpreter contains a convenient debugger, which can be
enabled by defining #MEMORY in gameexe.ini. The various debugging
features can be accessed with function keys or from drop-down menus. Of
particular use are the single-step execution mode (F3), the memory
editor (F5) and the graphics DC viewer (F7).
Debug mode also enables a number of runtime warning messages relating to
matters such as memory management; these do not always represent problems, but
it's probably wise to try to eliminate them anyway.
Finally, there are various message-box and input-related functions which are
only processed in debug mode. See 5.15 for a full list.
From version 1.3 onwards, single-step mode is replaced with a simple source
debugger. This is usable with Kepago - you must rename your source file to
have the extension .ORG and set up the path to that file in the
debugging options dialog within the interpreter - but does not work very well
with Kepago features like header files, and it is not likely to be
usable with disassembled code (in which lineation information is never
preserved), so its utility to developers using RLdev is limited. It
does, however, provide a slightly more intrusive form of single-step
execution regardless of whether source code is available or not.
4.4 Memory
Only statically allocated memory is available, in the form of a number of
arrays of variables; there is no stack.
Variables are divided into `local' and `global' types. The values of local
variables are reset when the program is executed, and stored in saved games.
Global variables are persistent, and their values are shared between all saved
games.
Integers
RealLive provides unsigned 1, 2, 4, and 8-bit integers, and signed
32-bit integers; these share the same memory, so individual bits and bytes
can be examined and modified without resorting to bitwise operators and
shifts. There are eight separate integer spaces, each of 8,000 bytes.
Local 32-bit integers are stored in A[0] to
A[1999], and likewise B[] to F[];
G[] and Z[] are global equivalents.
The smaller elements are accessed through arrays A8b[],
A4b[], A2b[], Ab[], and similarly
named equivalents for B[] to G[] and
Z[]. Indexing is based on the element size: the 8-bit arrays
have elements from 0 to 7,999, arranged such that the four bytes of
A[100] can be accessed as A8b[400] to
A8b[403], in the little-endian order; this pattern is
consistent, so for example Gb[42784] shares the same memory
as the least significant bit of G[1337].
These memory spaces are accessed through variables, either those that you
define yourself, or a set of built-in arrays that correspond directly to
the memory spaces. To avoid restricting useful single-character
identifiers, Rlc adds the prefix `int' to the names in declaring
these arrays. For example, the memory cell A[100] may be
accessed by referencing the variable intA[100].
The store register
There also exists a special integer variable store. This is a
register used to return values from functions such as strlen() and
select(); in all other respects it behaves exactly like any other
variable.
While technically these functions do not have return values, Kepago
permits you to treat them as though they did: for example, the code that
RealLive bytecode represents literally as
strlen(strS[100])
intA[10] = store
can also be written
int x -> MEMARR_A.10
str s -> MEMARR_S.100
x = strlen(s)
It is sometimes more efficient to access store directly:
strlen(s)
if store > 5 && store < 10...
generates code marginally more efficient than either repeating
the strlen() call or assigning its value to a variable would. Note
however that Rlc makes no guarantee that it will not generate
code that affects the value of store; in general you cannot
assume that its value will remain unchanged between two statements.
String variables
RealLive strings are null-terminated character arrays; allocation is
handled automatically. Their length is not limited at runtime, but only
up to 10,000 characters are stored in saved games.
The array S[0] to S[1999] holds local string
variables. In RealLive (but not in AVG2000) there is also an array
M[], of the same size, the contents of which are global.
As with integers, this memory can be accessed either through variables
you declare or through two built-in arrays, strS[] and
strM[].
Name variables
In addition to the normal string variables, there also exist some special
variables designed primarily to hold character names. These cannot be
accessed directly in source code, but they can be included inline in
strings.
There are 702 global name variables; they can each hold between 12 and 20
characters, depending on the #NAME_MAXLEN setting.
Their default values are set in gameexe.ini with the #NAME
variables (NAME.A, NAME.B, and so on). Their values can
be read and modified with the GetName() and SetName()
functions. Within strings, they are included with the control code
\m: the first is \m{A}, the second \m{B},
through \m{Z}, \m{AA} to \m{AZ}, and
so on up to \m{ZZ}.
Names can also be accessed numerically, such that 0 is A, 26 is AA, and
701 is ZZ. The numerical form is valid everywhere but in gameexe.ini, and
the letter form is valid everywhere but in function
parameters.4
In RealLive (but not in AVG2000) there is a parallel set of local name
variables, which are to the normal set as S[] is to
M[]. These are introduced inline with \l in place of
\m, the getter/setter functions are GetLocalName() and
SetLocalName(), and the gameexe.ini variables defining their default
values are called #LOCALNAME.
For example, Clannad uses the global names A and B for the player's
family name and personal name respectively, and various local names for
certain addresser/addressee combinations that have to change over the
course of the game, such as the way the player addresses Nagisa
(\l{C}).
4.4.3 Call variables
Version 1.3 of RealLive introduced a set of “call variables”: these are
K[] (three string variables) and L[] (40 integer
variables). These can be used as ordinary variables, but they are intended
for use with the functions gosub_with() and
farcall_with().
They can be accessed from Kepago with two more built-in arrays,
strK[0] to strK[2] and intL[0] to
intL[39].
4.5 The system command menu
RealLive provides a context menu system to handle most actions and
configuration settings. The system command menu is configured with the
#SYSCOM variables in gameexe.ini. It can be disabled by setting
#SYSCOM_USE to 0, and if a #CANCELCALL hook is
defined it will never be used at all (Clannad does this, although it
uses the internal flags associated with the system command menu to control its
own menu system).
The menu is displayed manually by right-clicking or pressing Escape. It can
be displayed from code with the ContextMenu() function.
The shape of the menu is determined by the gameexe.ini variables
#SYSCOM_MOD, #SYSCOM_MOD2,
and #SAVELOADDLG_USE.
The system commands, and their equivalents in code, are listed below. They
can be managed with the functions described in 5.14.6. Items
marked with an asterisk have standard dialog boxes which can be accessed with
InvokeSyscom(); items marked with a plus sign have settings which can be
modified with InvokeSyscom() and retrieved with ReadSyscom() (this
category may be incomplete). Functions related to settings come in sets
including a getter, a setter, and sometimes a function to get the default
value. For example, the getter function MessageSpeed() is accompanied by
SetMessageSpeed() and DefMessageSpeed(). Refer to the individual
functions in the API reference for full details.