Notes on porting TADS ===================== Following are notes on changes made to the OS layer in recent versions, starting with the most recent changes. If you're updating a TADS port, you should refer to the changes made since the last working version on your system. At the end of the file, you'll find more general information on how the portability layer is designed and how to get TADS working on a new operating system. If you're porting TADS to a new system for the first time, you should look at the general notes at the end of this file. Quick Start Guide ----------------- To port TADS to a new platform, you should start by looking at osifc.h, which specifies the entire TADS Operating System Interface. You must provide an implementation of this interface for your platform. If you're porting TADS to a machine that resembles another platform where TADS already runs, you can probably use the OS Interface implementation for that existing platform as a starting point; if not, you should read the documentation in osifc.h to learn what each function must do. You'll also need to construct a makefile or equivalent for your platform, to compile the C files that make up TADS and link the results into executables for the TADS Interpreter, and optionally the TADS Compiler and other tools. You should refer to an existing makefile (such as the DOS or Unix makefile) as a starting point. Version 2.5.2 changes --------------------- In preparation for TADS 3, I've been tightening up the interfaces between the OS layer and the portable code. My goal is to ensure that the a common OS layer can be shared by TADS 2 and TADS 3, which will dramatically simplify the porting of TADS 3 by eliminating the need to write a new set of OS routines. Porting TADS 3 will simply be a matter of compiling and linking the TADS 3 portable code with the existing TADS 2 OS code for your platform; the only coding you should have to do is to create the makefile. I've been working on this OS interface improvement for some time. This process started with the creation of osifc.h, which provides us with a comprehensive definition of the entire interface that the portable code uses to call the OS code. I've also been expanding and improving the OS interfaces slightly over the past few releases, but I've been trying to minimize these changes to minimize the resulting porting work created. With this release, I've addressed a new area, which is references from the OS code to the portable code. In general, the OS code should never call the portable code or use any global variables defined in the portable code; doing so ties the OS code to a particular portable system, which makes it difficult to re-use the OS code in a new system (such as TADS 3). The OS code should NOT henceforth refer to ANY function or global variable defined in the portable code. If your OS code refers to anything in the portable code, your OS code will not work with TADS 3, because those portable symbols will probably not be defined at all in TADS 3. I've tried to remove all of the references I could find from the OS code into the portable code. However, until TADS 3 is ported to the existing platforms, where the linker can root out the missing symbols, it is difficult to know for sure that all of the references are gone. * OS code should no longer call runstat(). Most os_gets() implementations started by calling runstat() to update the status line prior to reading input. The OS code is no longer responsible for this. You should simply remove any runstat() calls from your OS code. If you have runstat() calls that are needed in places other than at the start of os_gets(), contact me (mjr_@hotmail.com) if you would like advice on how to remove the dependency and I'll try to help figure out what to do. * The global variables "pagelength" and "linewidth" have been renamed to G_os_pagelength and G_os_linewidth. This formally moves these variables into the OS layer rather than leaving them in the portable formatter. * The global variable "moremode" has been renamed to G_os_moremode, moving it into the OS layer. * OS routines should no longer call tio_is_html_mode() (or any other tio routines). * OS routines should no longer call qasclose(). Most of the os_term() implementations in the OS code were calling this routine; this should instead be handled in the portable code, so the OS code should not use this method at all. * OS code should no longer call outformat() or getstring(). The text-only implementations of os_askfile() and os_input_dialog() called these routines, because they implemented their dialogs using formatted text. To address this, I've moved these text implementations to the portable layer; there is no longer a text-only version of os_askfile() or os_input_dialog(). Refer to askf_tx.c, askf_os.c, indlg_tx.c, and indlg_os.c for details on the new portable-layer implementations. MAKEFILE CHANGES: You must include one of askf_tx.c or askf_os.c in each executable you build. You must include one of indlg_tx.c or indlg_os.c in each executable. Refer to these files to determine which one to choose. In general, the _tx versions are text-only implementations -- these do not invoke os_xxx routines; the _os versions simply pass through the operation to the corresponding os_xxx routines. You should choose the _os version if you have a corresponding os_xxx implementation that you want to use; you should choose the _tx version if you want to use the plain text version. If your os_xxx routine was calling outformat() or getstring(), you should simply delete your os_xxx routine entirely and use the _tx version; otherwise, keep your os_xxx routine and use the _os version. OTHER CHANGES: * added os_is_special_file() * added os_uninit() * added OS_CHARMAP_FILECONTENTS identifier Version 2.5.1 changes --------------------- * I added a new command code for os_getc(): CMD_EOF. This new code indicates that "end of file" has been reached on the console input; this usually means that the player has terminated the application through some means such as closing its main window, or disconnected the terminal, or something like that. os_getc() should return this new code when it detects this type of condition so that code that calls os_getc() can finish up whatever it's doing and terminate the application gracefully. * There's a new function called os_getc_raw(). This is conceptually a lower-level version of os_getc(). The difference is that os_getc() returns "translated" keystroke CMD_xxx codes, whereas os_getc_raw() returns "raw" CMD_xxx codes. The translated codes are higher-level functional codes; for example, on Unix, when the user types Ctrl-E, os_getc() would return CMD_END, because Ctrl-E is bound to the end-of-line command. In contrast, os_getc_raw() would simply return ASCII 5. Similarly, on DOS, if the user presses the Escape key, os_getc() would return CMD_KILL, but os_getc_raw() returns ASCII 27. Note that some keys return different CMD_xxx codes for os_getc() and os_getc_raw(); for example, on DOS, if the user presses F1, os_getc() returns CMD_SCR (to toggle scrollback mode), but os_getc_raw() returns CMD_F1. The purpose of os_getc_raw() is to allow lower-level access to keystroke information, particularly for the inputkey() TADS function, while still preserving the port-specific functional mapping that os_getc() affords. Refer to osifc.h for documentation of the new function. Note that the CMD_xxx codes defined in osifc.h are now commented to indicate which command codes are "translated" and which are "raw"; when a particular key maps to one of each type, os_getc() should return the translated CMD_xxx code and os_getc_raw() should return the raw code. * The osfoprwb() function has traditionally been implemented incorrectly. On most platforms, this has been implemented as a call to fopen() with the mode "r+b" (or simply "r+" on platforms with no text/binary mode distinction), following the example of the DOS code. In fact, the intended function, which was not adequately documented in the osifc.h comments, was to open an existing file or create a new file if it didn't already exist. The C fopen() "r+" mode fails if the file doesn't exist. The intended behavior has now been correctly documented in osifc.h, and the DOS, Windows, Unix, and Mac versions have been changed to use the correct behavior. Version 2.4.1 changes --------------------- * New OS function: os_input_dialog(). A default character-mode implementation in osgen.c should suffice for most ports, but GUI ports should show a dialog box if possible. The character-mode implementation in osgen.c is selected at compile time with #define symbol USE_STDIO_INPDLG, which osgen.c defines implicitly if USE_STDIO is defined. * New OS function: os_get_str_rsc(). Most platforms should be able to use the default implementation in ostadrsc.c (in the generic code directory). Where possible, though, this function should be implemented using native OS string resources; this will make it easier for translators to localize the TADS executables. * TADSVER.TXT has been changed to TADSVER.HTM, and DOSVER.TXT is now DOSVER.HTM. * The interface to the os_askfile() function has changed very slightly, in that the result code values are now more precisely specified. In the past, this function simply returns zero for success and non-zero to indicate failure of some kind. The result codes can now indicate the type of failure; in particular, the function can distinguish actual errors from the case where the user simply chooses to cancel the dialog. Refer to osifc.h for details. I've attempted to change all of the OS sources that I have in my source tree; experts on the code for the individual platforms are encouraged to double-check my changes. Version 2.4.0 changes --------------------- * os.h has been reorganized to make it cleaner and more consistent. All of the DOS code has been removed from os.h and moved to new DOS-specific headers; this leaves os.h as a very simple "switchboard" file whose only function is to select the appropriate set of headers to #include based on the system macro settings. All of the non-DOS headers already worked this way, so this change should be transparent to other ports. * osifc.h now has (as far as I can tell) complete documentation of the entire TADS Operating System Interface. In particular, all of the file-related functions (osfoprb, osfrb, etc) are now specified and documented in osifc.h, as are the former "library" functions (osmalloc, osfree). This change should not affect any existing code; the additions are mostly comments. * The os_find_first_file() and os_find_next_file() interfaces have changed. Two new parameters, outpathbuf and outpathbufsiz, have been added; the new buffer is to receive the full path of the current file, so that the caller doesn't have to attempt to construct the path name from its components using possibly non-portable assumptions. This should have minimal impact, since the only ports that appear to implement these functions currently are DOS, Windows, OS/2, and Macintosh. Refer to osifc.h for documentation. * The os_progress() interface has changed slightly. This function no longer takes a (struct linfdef *) argument, which tied the function to the particulars of an internal structure in the TADS 2 compiler; instead, the function now takes a filename string and a line number. This change should have minimal impact, since only the Macintosh port currently provides a non-trivial implementation of this function. Version 2.3.0 changes --------------------- * ATTENTION UNIX USERS - If you run into weird display problems in the interpreter, especially with spurious extra spaces at the beginning of each line, try these definitions in your makefile: LIBS= -lncurses CFLAGS= -DHAVE_TPARM (plus whatever other CFLAGS you were already using) * The existing os_term() function is now used more rigorously. In the past, the generic code called exit() directly in a few places, which prevented the OS layer from doing any clean-up at termination. The generic code should now use os_term() exclusively to terminate execution. This shouldn't require any new porting work, since nothing about os_term() has changed -- the only change is that the generic code is calling it more predictably now. However, a few ports may accidentally have omitted an os_term() implementation from some executables, since it wasn't always needed before; if you get a link error for this symbol during building, adjust your makefile to include an os_term() implementation. * A new file, oem.c, defines some global strings for use in identifying the version of TADS. You must fill in that file with a couple of customized settings for your version. Please refer to oem.h and oem.c for details on how to do this. * A few new OS interface functions have been added. These functions are described in comments in osifc.h. The new functions are: os_get_sys_clock_ms() os_sleep_ms() os_get_event() os_set_title() osfdel_temp() These functions should be reasonably simple to implement on most systems. os_get_event() in particular can simply degrade to os_getc() with some additional argument checks and changes in the return value if it's hard to implement fully on your system; the USE_STDIO version in osgen.c can be used as a default implementation. Note also that a completely portable implementation of os_sleep_ms() is in msdos/oswin.c. You can use this implementation if you wish, but the function is included in the OS interface so that you can replace it with something more suitable for your platform. The implementation in msdos/oswin.c discards keystrokes and other events that occur during the delay, which may be undesirable on your system. This implementation has the desirable effect of processing UI events, such as window sizing, during the delay. Most platforms can provide an empty implementation of os_set_title(); this function merely notifies the OS layer that the game has set a title string via the HTML tag. If it's convenient, the OS layer can use the title string as a window caption, or whatever else makes sense. Most character-mode display code will simply ignore this; osgen.c has a default implementation that does nothing. osfdel_temp() is a new function to complement os_create_tempfile(). This function deletes a temporary file previously created with os_create_tempfile(), after the temporary file has been closed; callers previously used osfdel(), the generic file deletion function, to delete these temporary files. The purpose of this new function is to support systems that can be instructed to delete temporary files automatically when closed; on such systems, a call to osfdel() is redundant (and problematic), since the system will already have automatically deleted the temporary file by the time the caller gets around to invoking osfdel(). osnoui.c provides a simple implementation of osfdel_temp() that simply calls osfdel(); this works with the implementation of os_create_tempfile() in osnoui.c, so you won't need to make any changes if you're already using this file. If you're using a customized os_create_tempfile(), you may want to provide a correspondingly customized osfdel_temp(). In particular, if your os_create_tempfile() opens the file in such a way that the underlying OS will automatically delete the file when closed, your osfdel_temp() should simply do nothing. * The os_askfile() interface (defined in osifc.h) has been changed to provide more information about what kind of prompt to use. The extra parameters indicate whether we're opening an existing file or saving a new file, and what type of file we're interested in. Many GUI systems use different dialogs for opening and saving files, and filter files displayed in a file selector dialog according to the type of file to be chosen. Systems that don't need the extra information can ignore the new parameters; they're purely to make it easier for the system code to determine what type of dialog to show. I've updated my copies of the MSDOS, Win32, Mac, and Unix sources for the new os_askfile interface, but I don't have a copy of every port-specific version, so you may need to propagate this change into your version. * The #define's for the OSFTxxx codes (the file type codes) are now in osifc.h. In the past, these were scattered among the different system-specific headers for no good reason -- these are used by portable code, so they are necessarily part of the OS interface, and hence belong in osifc.h. If you encounter any errors with redundant #define's for these symbols, you should simply remove the #define's from your system-specific headers and use the ones in osifc.h. * The #define's for the CMD_xxx codes (the os_getc() extended keystroke codes) are now in osifc.h. In the past, as with the OSFTxxx codes, these were scattered among the system-specific headers. It's desirable to be able to use these key codes in the portable code, so these are now part of the general OS interface. As with the OSFTxxx codes, if you run into any compilation errors with redundant #define's for CMD_xxx symbols, you should be able to remove the #define's from your system- specific headers and use the new osifc.h definitions. Version 2.2.6 changes --------------------- In past versions, the release notes for generic changes and platform- specific changes were combined into a single file, which had to be maintained separately on each platform. For example, the DOS release notes were in TADSVER.DOS. To create a comprehensive set of release notes for another platform, someone porting TADS had to sift through TADSVER.DOS to pick out the generic changes, then add any changes specific to the ported version. In order to remove this extra work, I've broken up the release notes into two separate files. The new file TADSVER.TXT contains only the generic changes. Since these changes will automatically be included in any port (because such changes are always in the portable portion of the code), the distribution for a port should always be able to include TADSVER.TXT unchanged. The separate new file TADSVER.DOS contains changes that apply only to the DOS version. Since such changes are only in the DOS osxxx code, they should not affect any other platforms, so only DOS users should need to look at this file. When creating a ported version, you can now simply create your own platform-specific release notes file where you can describe any changes that you made that apply only to your platform. Please let me know (mjr_@hotmail.com) the name of your port-specific release notes file, and I'll add it to the table at the top of TADSVER.TXT, which refers users to the appropriate file for each platform. (You may want to add an entry to the table yourself for your initial version, if you plan to include TADSVER.TXT with your distribution.) Version 2.2.4 changes --------------------- I've changed the source distribution zip file's layout slightly to make the TADS source tree more self-contained. In the previous version, several files were in a separate LIB directory, which was at the same level in the hierarchy as the TADS2 directory. This was an undesirable layout for some people, since it meant that TADS had two high-level directories. Since keeping the files in a separate directory doesn't serve any real purpose currently (the separation is historical), I've merged these files into the base TADS directory (except that a few of the files are now in TADS/MSDOS, because they're DOS-specific). Of course, you can still lay out your source files however you want them, of course, but if you follow the layout in the zip file, you'll need to adjust your makefile for the changed location of these files. If you change the default memory sizes in your osXXX.h file, you should also define message strings that correspond to your new defaults for the usage messages. To do this, look at errmsg.c, and notice the big list of TxD_xxxSIZ_MSG definitions. For each value that you override in your OS header, you should provide a corresponding TxD_xxxSIZ_MSG definition that looks like the one in errmsg.c but uses your changed default value. Version 2.2.3 changes --------------------- The operating system interfaces have changed somewhat between version 2.2.2 and 2.2.3 (the HTML TADS release). The OS interfaces have been stable for a long time, so most systems where TADS has been ported should have OS-dependent implementations that conform to the 2.2.2 interfaces. Unfortunately, all of these ports will have to be reworked slightly to accomodate the changes in 2.2.3. The changes in 2.2.3 should be fairly minor, though, so it shouldn't require a huge effort to upgrade port-specific code that worked with the previous version. The main changes are that the OS interfaces have been rearranged slightly in the header files (note in particular the creation of osifc.h), and some of the functions that were in osgen.c have been moved to the new file osnoui.c. A few functions have had minor changes to their interfaces as well. It will probably be necessary to make some small changes to your makefiles because of the rearrangements. One of the biggest changes may be the use of ANSI-style function prototypes throughout the code. Depending on your compiler, you may or may not have to change your OS function implementations to use ANSI prototypes. ============================================================================== General notes on porting TADS ----------------------------- Code Organization ----------------- The TADS code is divided into portable and system-specific sections. In porting TADS to a new platform, only the system-specific portions should need to be changed. If any changes are needed to the portable code, we consider the portable code in error and will try to correct it so that it is the same on all platforms. We want the portable portion to have a single set of sources, without port-dependent ifdefs, across all platforms. For the most part, the system-specific sections are isolated in files with the prefix "os", and system-specific functions and macros all begin with "os" or "OS". Historical note: At one point we started to group some code that High Energy was internally sharing between multiple products into a code library, all of whose files start with "L". The file los.h formerly many system-specific macros and definitions; these have been moved into the OS files along with everything else. oem.h ----- This file defines information about the person who built a particular version ("OEM" for "Original Equipment Manufacturer"). Refer to oem.h for details about how to set up the definitions there. osxxx.h, os.h, and osifc.h -------------------------- The first thing to do when starting a new port is to create a new system-specific header file for your platform. You should choose a name for the header based on your platform name; for example, on DOS, the file is osdos.h, and on Unix it's osunix.h. Next, you must define a C preprocessor macro that will identify your platform. This macro will be used to select the appropriate code to include in your version of TADS, using #ifdef preprocessor directives in the TADS code. Many compilers automatically pre-define a symbol for this purpose, in which case you should use this symbol; otherwise, make up a symbol of your own and define it on the C compiler command line or equivalent when you compile the TADS C files. Edit the file os.h. This is a "switchboard" file that selects an appropriate system-specific header to include. The TADS portable C files #include os.h, and os.h must in turn #include your header file. You should add a few lines to os.h to #include your header; follow the example of DOS, Unix, and the other platforms: #ifdef FROBNIX_OS /* Frobnix operating system definitions are in osfrob.h */ #include "osfrob.h" #endif You shouldn't add anything else to os.h - the rest of your code should go in your own "osfrob.h" file. Now you must write the code in your OS-specific header ("osfrob.h" in the example above). To do this, refer to osifc.h, which defines the TADS Operating System Interface, which is what you must implement for your machine. The purpose of your osxxx.h is to isolate certain platform-specific definitions to a well-defined location in the code. The rest of the TADS C code is written so that it uses the definitions in osxxx.h; this way, the other code doesn't have to be changed when moving TADS to a new platform. The file osifc.h contains the specification and documentation of the TADS Operating System Interface, which contains the portable interface definitions for functions, types, and macros that must be tailored to each platform. osifc.h contains a portable interface to code that varies by platform; because the interface is portable, the rest of the TADS code doesn't have to know anything about different platforms, since it can expect the exact same functions to be available on every machine. osifc.h contains only portable code, so you shouldn't make any changes in this file. You can probably take one of the existing osxxx.h implementations as a starting point, depending on your hardware and operating system. For example, for a 68000 or 68000-like processor, you might want to start with the Macintosh header (osmac.h), since the alignment and byte ordering macros will be appropriate. For an 8086-like processor, start with the DOS headers (os.h and los.h). Other platforms may need additional work. IMPORTANT NOTE: Do not make changes in osifc.h - this file should never contain any #ifdef's for different platforms, because it defines a portable interface that is the same everywhere, even though the implementation of the interface varies by platform. In particular, the #define constants (OS_EVT_xxx, SYSINFO_xxx, and so on) MUST be the same on all platforms, because some of these values can be used programmatically by TADS games; a game can be compiled on one platform and run on any other machine, so these constant values must be the same everywhere for games to work properly. Datatype configuration ---------------------- TADS makes relatively few assumptions about datatype sizes. TADS expects that shorts are at least 16 bits, ints are at least 16 bits, and longs are at least 32 bits. System-specific function implementation --------------------------------------- Depending on your platform, you may be able to re-use some code that was originally written for the DOS platform, but works with some minor changes on other types of machines as well. The files osdos.c, osgen.c, and osnoui.c provide an implementation of the system routines for DOS. osgen.c and osnoui.c should be usable on any system with DOS-like display characteristics. Note osgen.c and osdos.c check to see if several USE_xxx preprocessor symbols are defined; these macros are NOT defined anywhere in the source, but are to be defined by the makefile. On DOS, these os files are compiled multiple times with different combinations of the USE_xxx symbols to produce different object files for linking the compiler and run-time. A stdio (C run-time library "standard input/output") implementation of many of the routines can also be selected by defining the preprocessor symbol USE_STDIO. The file osnoui.c is similar in purpose to osgen.c. The functions implemented in osnoui.c are semi-portable implementations of functions that have no user interface component ("no UI"). Building the system ------------------- TADS consists of three main pieces: the compiler, the run-time, and the debugger. Refer to the DOS makefile to see which object files are included in each. Note that it may not be possible to let a librarian/archiver resolve the links for all symbols, because a few of the object files provide overlapping sets of symbols -- it is necessary to manually include such object files in the links. (The overlapping symbol sets are generally used to provide empty definitions for certain functions that are referenced but not actually used by one executable, but must be fully implemented in another.) Debugger -------- The debugger diverges somewhat from the convention of keeping the os-dependent functions in a file prefixed by "os" or "los". The debugger's user interface is implemented in the file dbgu.c on DOS, and dbgumac.c on Macintosh. The DOS version also has a file, osdbg.c, which provides lower-level functions; it should be possible to port the debugger to a new platform by using the DOS user interface and replacing only osdbg.c with an appropriate set of functions for the new system. However, for any system without DOS-like display and input device characteristics, the entire user interface will usually need to be replaced; for example, on the Mac, dbgu.c is not used at all, but is replaced by dbgumac.c. This separation of the user interface provides maximum flexibility for the debugger's user interface while minimizing the amount of work to port it to a new platform. Testing ------- The first test is to build a run-time and see if it can run games compiled on another system. The second is to build the compiler, use it to compile game on your system, and see if that runs. You can further test using the regression tests. Compile DEEP.T, then run tr like this: tr -i dsdwalk.in -l dsdwalk.new deep Then compare dsdwalk.new (the new log generated by your version of DEEP) with dsdwalk.log (the reference version) -- if they're identical, your system is probably in pretty good shape. You can test similary with DITCH.T and DDDWALK.IN, and BUGS.T and BUGS.IN. MS-DOS and Windows Compilation ------------------------------ Two makefiles are provided for compilation on MS-DOS and Windows machines; both makefiles are in the msdos directory. To avoid confusion, these makefiles build object files into separate directories, and name their executables differently from one another. makefile.bc is for compilation with the 16-bit Borland C compiler, version 4.5. With this makefile, you can build the real-mode and protected-mode 16-bit DOS executables. (You'll need the Borland Power Pack for DOS in order to build the protected-mode versions of the executables.) Note that you should run makefile.bc with Borland make. makefile.bc compiles into objs (for real-mode object files) and xobjs (for protected-mode object files), and generates the following executables: tc.exe - real-mode TADS compiler tr.exe - real-mode TADS run-time tdb.exe - real-mode TADS debugger tcx.exe - 16-bit protected-mode TADS compiler trx.exe - 16-bit protected-mode TADS run-time tdbx.exe - 16-bit protected-mode TADS debugger maketrx.exe - single-file executable binder tadsrsc.exe - TADS resource manager trcolor.exe - run-time color picker makefile.vc5 is for Microsoft Visual C++ version 5.0. VC++ 5.0 can only perform 32-bit compilations, so this makefile builds only 32-bit versions of the executables. Note that this makefile builds the two TADS libraries that are required for building HTML TADS: tr32h.lib (the TADS run-time 32-bit Windows library for HTML) and tdb32h.lib (the TADS debugger 32-bit Windows library for HTML). You should run makefile.vc5 with Microsoft nmake. makefile.vc5 compiles into winobjs32 (for 32-bit Windows object files), and generates the following executables: tc32.exe - TADS compiler 32-bit Windows console application tr32.exe - TADS run-time 32-bit Windows console application tdb32.exe - TADS debugger 32-bit Windows console application maketrx32.exe - executable binder 32-bit Windows console application tadsrsc32.exe - TADS resource manager 32-bit Windows console application trcolor32.exe - run-time color picker 32-bit Windows console application