Writing your own modules for the DarkLord screen saver 1. Basic rules for modules written in C Writing external add-on modules for DarkLord is straightforward. Modules can be written in assembly language or `C'. If you are writing in C, then the Lattice C 5 compiler is recommended (if not essential) because fine control is needed over the code which is generated. All the modules included in this package have been written in C, and the source code is included for two of them, as examples. Writing modules in C is easy, but you need to follow certain rules. These are: 1.1 You must not use the AES in your module. In fact there is no need to do so; the AES is used for interaction with the user, which more or less by definition is not something you do with a screen saver once it starts up. You can (and probably will) use the VDI, subject to certain restrictions listed below. 1.2 There is no need to call appl_init(), as you would normally do in a GEM program. It won't do any harm, but having called appl_init() you would normally call appl_exit() at the end of a program. If you do this with a DarkLord module, the machine will hang, so don't do it. 1.3 You must not try to reserve any memory with malloc(), the internal memory manager function, nor call any function that will do so. This includes v_opnvwk(), the call to open a virtual workstation. There is no need to call this as DarkLord passes your module a valid workstation handle, which you should use. Any memory requirements your module has should be contained within the memory space available to it (currently there is a total of 32K available, which should be ample). Use arrays if you need to reserve a block of memory. I have tried to investigate the use of the GEMDOS function Malloc() to gain extra, temporary memory for when very large amounts are needed. This seems to work provided that the module calls Mfree() for any requested memory before returning control to DarkLord. 1.4 You should include in your C modules the header file "mod_head.h" supplied with this package. This contains the structure definitions for the structure which is passed as a parameter to your module. 1.5 Your module must have a main() function as usual, for which the function prototype is: int main(DKL_INFO *); This prototype is provided in the header file "mod_head.h" so you don't need to type in this definition if you have included the header file. On entry to the main() function, the parameter is a pointer to a data structure containing certain information used by the module. The nature and contents of this structure is given in the header file "mod_head.h" and is explained in detail below. The DKL_INFO structure contains most of the usual information you will need concerning the screen resolution, number of bitplanes, etc. If you need more you can always call vq_extnd(). The structure also contains the values of the variables and flags (if any) used by the module and which can be changed by the user via the DarkLord Control Panel. Your module should extract whichever of those values it needs from the structure. 1.6 The module should terminate with a return statement (returning an integer) and a simple closing brace at the end of the main() function (the compiler will generate an RTS instruction when it finds this). DO NOT use exit(), Pterm(), appl_exit() or any other program termination function on exiting - just let the module terminate gracefully. 1.7 All source code sections of your module should be compiled in the following way: - disable stack checking for all C modules; - compile all modules to use default far code and default far data (i.e. non-base relative, full 32-bit addressing, compiler option -b0). This is because the global data register a4 cannot be set up to point to your program's data and BSS areas. If you are using Lattice C, the linker will automatically use the correct libraries (lcnb.lib, lcgnb.lib, etc). - link with no startup code (in Lattice, do this by selecting the `Executable...' menu entry from Options menu in the compiler's integrated editor, and select `None' from the program type). - your module must use 32-bit integers, don't use the `default short integer' option in Lattice. - the module must be given an extender `.DMO' rather than .PRG (the Construction Kit looks for .DMO files when you include a module filename in a .DKL file. In any case, the last thing you want is someone double-clicking on a module file thinking it's an executable!). 1.8 Your module should be so written that it will work in any screen resolution. This has always been good practice, but is critical nowadays with the plethora of screen resolutions available on the Falcon. If your module genuinely cannot be made to work effectively in certain video modes, you can set the screen resolution flag appropriately in the controlling .DKL file using the DarkLord Construction Kit. DarkLord will not call a module which will not function in a particular resolution. (See the Kit's manual for more details). 1.9 Finally, and very importantly, how does your module know when it's time to stop? One of the structure elements passed to the module in the DKL_INFO structure is a pointer to a flag which the main DarkLord program maintains under interrupt control. Your program should poll this flag at frequent and regular intervals. As long as it is non- zero, the module should continue to operate; as soon as it is zeroed, it should terminate. The simplest possible shell in C for a DarkLord module would therefore look like this: /* ------------------------------------------------------------ */ #include "mod_head.h" int *exit_flag; int main(DKL_INFO *info) { exit_flag=info->dklord_flag; while(*exit_flag) { /* as long as the flag is non_zero */ /* do your thing here */ } return(0); /* return an int */ } /* ------------------------------------------------------------ */ This module would function, but all it would do is loop until something happened (e.g. a keypress) which sets the flag to zero. This is handled by interrupts and is independent of the module. 2. Module termination 2.1 Your module can terminate of its own accord, without the exit flag having been cleared, if you wish. If this happens, DarkLord will restore the screen and then re-execute your module from scratch. An example of this is the module `SPOTS.DMO' included in the package. If you look at the source code, you will see that, if flag 2 in the DarkLord Control Panel is set appropriately, this module will draw a circle 2,000 times and then terminate. As far as DarkLord itself is concerned however, nothing has happened to make it return control to the AES, so it will simply re-execute the module. This can be quite useful if you have a module which alters the screen to such an extent that it can go no further without restoring it and starting again. If your module does this, note that any variables used in your module are not guaranteed to keep their value between one call to the module and the next. Any initialised data which was included in your module WILL be left unchanged (for example, if you include bit image data for a picture). DarkLord itself handles the saving and restoring of the screen, the module can forget about this. 2.2 The module MUST return an int to DarkLord. Normally it would return a zero, which DarkLord interprets to mean that the module was executed without problems. In some cases your module may not be able to execute - perhaps it needed GDOS, or some other feature was missing. In this case the module should put a null-terminated error string into the element dk_errmes of the information structure, and return a non-zero number to DarkLord. DarkLord will then display an alert box to tell the user what has happened; the error message generated by the module will appear in this alert box. In a mixture of pseudocode and C this might go something like this: enter main() function check values in information structure if(some condition) { strpcy(info->dk_errmes, "An error occurred"); return 1; } else { execute rest of module } return 0; Important - the message MUST NOT be more than 30 characters long or you may overwrite something essential! 3. Using assembly language Writing modules in assembly language is even simpler if anything, because you don't have to worry about what the compiler does to your code. You should note the following points in addition to the general points made in sections 1 and 2 above: - you can use any registers, DarkLord will save and restore them for you. - it is probably best to allocate your own stack and restore the stack pointer just before termination. Otherwise you will be using DarkLord's stack, and if you corrupt it or mishandle the stack pointer a crash is inevitable. - you will need to allocate your own VDI parameter block if using the VDI, but you can use the workstation handle passed to the module. - your module should terminate by moving a 32-bit value into d0 and end with an RTS. 4. The information structure The information structure, the address of which is passed as a parameter to the module, contains the following information. In this list, an int is 4 bytes long, a short 2 bytes, and pointers of course are always 4 bytes. After the C definition the offset in bytes from the start of the data structure is given for the benefit of assembly language programmers. short dk_xres (offset 0) The screen width in pixels; e.g. 640 in ST high or medium resolution; the screen VDI coordinates would run from 0 to dk_xres-1. short dk_yres (offset 2) The screen height in pixels; e.g. 200 in ST medium resolution; the screen VDI coordinates would run from 0 to dk_yres-1. short dk_handle (offset 4) The VDI workstation handle to use for VDI screen output. short dk_pens (offset 6) The number of different colour pens available to the VDI (2 in ST high, 4 in ST medium, and 16 in ST low resolutions); note that Falcon True Colour returns 256. short dk_colsloaded (offset 8) The number of colours contained in the palette loaded as part of the .DKL file; possible values are: 0, 2, 4, 16, 256. short dk_planes (offset 10) The number of bitplanes in the current screen mode; possible values include 1 (ST high or Falcon 2-colour modes), 2 (ST medium or Falcon 4-colour modes), 4 (ST low or Falcon 16-colour modes), 8 (Falcon 256- colour modes); note that Falcon True Colour mode returns 16, even though it does not use a bitplane screen structure. int tc_flag (offset 12) If set to 1, the machine is in a True Colour mode (in fact any mode which does not support a colour palette lookup table, of which True Colour is probably the commonest); if zero, a colour palette lookup table is supported; this is important because in the absence of a lookup table animation by colour cycling is not possible. int *dklord_flag (offset 16) A pointer to the flag maintained by DarkLord itself under interrupt control; set to 1 on entry to the module, modules should poll this flag regularly and terminate if it becomes zero. int *counter (offset 20) A pointer to a countdown timer maintained by DarkLord's VBI routine; store any positive value in this counter and it will be decremented by 1 each vertical blank interval until it reaches zero; useful in slowing down very fast graphic effects. short *loaded_palette (offset 24) A pointer to the colour palette data loaded as part of the .DKL file so that it can be accessed (for colour cycling, for example); check dk_colsloaded to see how many colours were in fact loaded; each colour is stored as a VDI colour, using 3 shorts for the red, green, and blue components of the colour; a palette consisting of 16 loaded colours would contain 96 bytes of data (16 * 3 * 2 bytes). void *screen_ram (offset 28) A pointer to the screen memory as returned by Logbase(). unsigned long gdos (offset 32) A flag indicating whether GDOS is loaded, and if so what type; possible values are: 0xfffffffe = no GDOS; 0x5f46534d = FSM or SpeedoGDOS; 0x5f464e54 = FontGDOS; any other value = `ordinary' GDOS or clones such as G+Plus (these values are given in the file vdi.h supplied with Lattice C 5.6). int dk_flag1 (offset 36) The user-selected state for flag 1 (if there is a flag 1); ranges from 1 to 6. int dk_flag2 (offset 40) As for dk_flag1. int dk_flag3 (offset 44) As for dk_flag1. int dk_min1 (offset 48) The minimum possible value for variable 1, if there is a variable 1; this value is set by the programmer in the .DKL file and is not user- alterable. int dk_max1 (offset 52) The maximum possible value for variable 1, if there is a variable 1; this value is set by the programmer in the .DKL file and is not user- alterable. int dk_start1 (offset 56) The actual value of variable 1, as set by the user (if the variable exists); ranges from dk_min1 to dk_max1. int dk_min2 (offset 60) As for dk_min1. int dk_max2 (offset 64) As for dk_max1. int dk_start2 (offset 68) As for dk_start1. int dk_min3 (offset 72) As for dk_min1. int dk_max3 (offset 76) As for dk_max1. int dk_start3 (offset 80) As for dk_start1. char *dk_line1 (offset 84) Pointer to the first line of a user-entered message (if any); by default this is a blank string with ASCII NULL as its first character. char *dk_line2 (offset 88) Pointer to the second line of a user-entered message (if any). char *dk_line3 (offset 92) Pointer to the third line of a user-entered message (if any). char dk_errmes[35] (offset 96) Storage area for an error message to be returned by the module to DarkLord if required; the module must return a non-zero value for this error message to be displayed; the length of the message MUST NOT exceed 30 bytes and MUST be null-terminated. char *dk_fontname (offset 132) A pointer to the name of a GDOS font entered by the user. This font will be used by some modules to display on-screen text. 5. Using maths libraries in C modules The standard Lattice maths libraries expect certain internal variables to be provided as placeholders for the library routines. These are normally provided as part of the C startup routine, and if the linker can't find them (because, of course, you will have deliberately chosen not to link in a startup stub) then it will abort with a `Symbol not found' error. If this happens and you have to use the maths routines, try assembling and linking the file mathlink.s (provided with this package) in your project. This just supplies placeholders for the missing variables. Note that you cannot expect math error functions to work properly, so make sure that you don't generate underflow or overflow errors. Likewise, forget about using a maths co-processor. If using mathlink.s doesn't solve the problem, try looking for the missing symbol in the C startup code supplied with Lattice. It may be possible to simply add this to the mathlink.s file, but in some cases this may not work. 6. Debugging modules This is something of a problem because DarkLord modules cannot be run from the Desktop or a debugger in the usual way - they can only be run from DarkLord itself. In order to debug a module you need to do the following things: - enable the module debug facility by altering the .DKL file which loads the module (see the manual for the DarkLord Construction Kit); - run your favourite debugger; - load DARKLOR3.PRG and run it; - load the .DKL file, which in turn will load the module you wish to debug; - select the `Debug' button in DarkLord's Toolbox dialog so that it is highlighted; - turn keyboard monitoring OFF (or you won't be able to use the debugger without causing the module to terminate!); from now on, don't move the mouse or click the mouse buttons while stepping through the module code as this will cause the module to terminate; - execute the module by clicking on the `Activate' button in the DarkLord control panel. When you activate the module, if the Debug button is selected DarkLord will stop with an `illegal' exception. At this point you will see that the next instruction is `jsr (a0)' which is the instruction which actually transfers control to the module. The next instruction to execute after the jsr is the first one in your module. Note that symbolic debugging is not possible because the debugger has no way of geting access to your module's symbol table. 7. Loading and executing your module One final point - don't forget that in order to use your new module, you must generate a matching .DKL file from the Construction Kit. The minimum requirement in the .DKL file is the name of the DMO file to load, but you can of course include whatever other parameters are required.