Home | SCI Documentation | SCI Tutorials | SCI Tools | SCI Wiki | Community

The Sierra Creative Interpreter

PrevChapter 7. FreeSCI

Kernel Hacking

Kernel functions are the bridge between the abstract virtual machine, and the world of real programs. The VM may be able to solve RPN equations in the blink of an eye, but what good is this if it can't read input or produce output?[1]

All of the kernel functions are stored in src/core/kernel.c. Since kernel function mapping is done during runtime by string comparison, each kernel function and its name have to be registered in the array kfunct_mappers[]. Note that each version of the SCI interpreter (at least each pre-1.000.000 version) comes with one unidentified kernel function, which is handled by k_Unknown.
 

Kernel basics

Each kernel function is declared like this:

              void
              kFooBar(state_t *s, int funct_nr, int argc, heap_ptr argp);

So this is how you should start. The four parameters (think of them as the Four Accessories of a kernel function) mean the following:

state_t *s: A pointer to the state you are operating on.
int funct_nr: The number of this function. Mostly irrelevant.
int argc: The number of arguments.
heap_ptr argp: Heap pointer to the first argument.

s" contains a lot of important and interesting data. Have a look at src/include/engine.h for a complete description. What you will probably need mostly will be the heap, (s->heap), a unsigned char pointer, and the accumulator (s->acc), a word (guint16), which is used to return values to the SCI program.

Some kernel functions don't even need to refer to the heap. However, most of them are passed at least one, if not more parameters. This may sound shocking to you, but there is an easy way to work around the neccessity of peeling them off the heap manually: Use the PARAM macros. They are used as follows:

PARAM(x): Returns the value of the parameter x. Does not check for validity.
UPARAM(x): Same as PARAM(x), but casts the parameter to be unsigned.
PARAM_OR_ALT(x, y): Checks if PARAM(x) is valid and returns it, or returns y if PARAM(x) is invalid.
UPARAM_OR_ALT(x, y): PARAM_OR_ALT(x, y) unsigned.

everal kernel functions assume default values if a specific parameter is not present. Use the U?PARAM_OR_ALT(x, y) macros to detect this case, and you'll rarely have to care about using argc directly.
 

Hunk and heap

Accessing the heap for both reading and writing is surprisingly important for the kernel, especially when it has to deal with functions that would usually belong into user space, like handling of doubly-linked lists. To ease this, three macros are available:

GET_HEAP(x) - reads a signed SCI word (gint16) from heap address x
UGET_HEAP(x) - reads an unsigned SCI word (guint16)
PUT_HEAP(x, foo) - writes the value foo to the specified heap address

Some kernel functions, especially graphical kernel functions, additionally require the usage of what Sierra referred to as "hunk space". This is dynamically allocated memory; it can even be allocated and unallocated manually from SCI scripts by using the Load() and UnLoad() system calls (this is the sci_memory resource). To allow usage of this kind of memory, three functions have been provided:

int kalloc(state_t *, space) - allocate space bytes and return a handle
byte *kmem(state_t *, handle) - resolve a handle and return the memory address it points to
int kfree(state_t *, handle) - unallocate memory associated with a handle. Returns 0 on success, 1 otherwise
 

Error handling and debugging

Error handling and debugging probably are the most important aspects of program writing. FreeSCI provides three macros for printing debug output:

SCIkwarn(text, ...) - Print a warning message
SCIkdebug(text, ...) - Print a debug message
CHECK_THIS_KERNEL_FUNCTION - print the function name and parameters

he difference between SCIkwarn and SCIkdebug is that the latter can be easily removed (by commenting out the #define SCI_KERNEL_DEBUG on or about line 39). In practice this means that SCIkwarn should be used for warning or error messages in cases where it is likely that the vm or the kernel function are doing something wrong; e.g. if the program refers to a non-existant resource file, if a node list command does not come with a pointer to a node list, or if the number of parameters is insufficient. These messages are important and may point to misperceptions of details of the SCI engine. SCIkdebug, on the other hand, is your every-day "flood me with information until I'm blind" debug macro.

Sometimes it may happen that something goes wrong inside the kernel; e.g. a kernel function runs out of memory handles, or an internal variable somehow was set to an invalid value. In this case, kernel_oops(state_t *, char *) should be used. It prints an error message and halts the VM, which none of the macros does.
 

Selectors

Selectors are very important for some of the kernel functions. BaseSetter(), Animate(), Display(), GetEvent() and others take data from or write directly to selectors of a specified object (passed as a parameter or retreived from a node list), or even call object methods from kernel space[2] To prepare the usage of selectors, a variable has do be declared (in src/include/vm.h, selector_map_t). This variable will carry the numeric selector ID during run time. Now, the selector has to be mapped- this is happens once during initialization, to save time. It is performed by script_map_selectors(), which is located at the end of src/core/script.c (just use the "FIND_SELECTOR" macro).

If everything went right, accessing selectors should be really easy now. Just use the GET_SELECTOR(obj, selector) and PUT_SELECTOR(obj, selector, value) macros, where obj is a heap_ptr pointing to the object you want to read from or write to, and selector is the name of the selector to use.

Example 7-1. An example for PUT_SELECTOR and GET_SELECTOR

              void
              kSwapXY(state_t *s, int funct_nr, int argc, heap_ptr argp)
              {
                int posx, posy;
                heap_ptr obj = PARAM(0);

                posx = GET_SELECTOR(obj, x);
                posy = GET_SELECTOR(obj, y); /* x and y are defined in selector_map_t */

                PUT_SELECTOR(obj, y, posx);
                PUT_SELECTOR(obj, x, posy);
              }
              

Also, it may be neccessary to invoke an actual method. To do this, a varargs macro has been provided: INVOKE_SELECTOR(obj, selector, argc...). In theory, this macro can be used to set and read selectors as well (it would even handle multiple sends correctly), but this is discouraged for the sake of clarity.

INVOKE_SELECTOR works very much like the other macros; it must be called directly from a kernel function (or from any function supplying valid argc, argp and s).

Example 7-2. An example for INVOKE_SELECTOR

                INVOKE_SELECTOR(obj, doit, 0); /* Call doit() without any parameters */
                INVOKE_SELECTOR(s->game_obj, setCursor, 2, 999, 1);
                                /* Call game_obj::setCursor(999, 1) */
              

Notes

[1] It could be used to produce benchmarks.
[2] Yes, this is evil. Don't do this at home, kids!
 


Prev - The graphics subsystemHome
 

by helping to defray some of the costs of hosting this site. If it has been of help to you, please consider contributing to help keep it online.
Thank you.
pixe
Top

© 2013 to present The Sierra Help Pages. All rights reserved. All Sierra games, artwork and music © Sierra.