tnylpo(1)

NAME
tnylpo - a utility to run programs written for CP/M-80

SYNOPSIS

tnylpo [-abnrsw] [-c (<n> | @)] [-d <drive>] [-e [h][ b<bytes> | p<pages> | r<addr>-<addr> ] :<file> ] [-f <config-file>] [-l (<n> | @)] [-o(n|y|[y,] <fg>,<bg> )] [-t (<n> | @)] [-v <level>] [-y (n | <count>,<nanoseconds>)] [-z{a|e|i|n|s|x}] <command> [<arg> ...]

tnylpo -h

DESCRIPTION
tnylpo allows the execution of programs written for the CP/M-80 operating system in a Unix environment. To accomplish this task, tnylpo combines an emulation of the Zilog Z80 eight-bit CPU with a software layer translating calls to the CP/M application programming interface (the BDOS and BIOS calls of CP/M-80 version 2.2 plus a few BDOS calls of CP/M 3) to Unix library and system calls.

tnylpo doesn't even try to re-create the look-and-feel of the CP/M-80 operating system by emulating its Console Command Processor (CCP) or providing implementations of utility programs like PIP or STAT; in fact, its aim is to integrate CP/M-80 applications and compilers as much as possible into the Unix command line interface to allow their use in combination with the much more capable utilities and editors of the host operating system; tnylpo users are supposed to use cp(1) and dd(1) instead of PIP, chmod(1), df(1), and ls(1) instead of STAT, and shell scripts, here-documents, and redirections instead of SUBMIT and XSUB.

Files in the CP/M environment are directly mapped to Unix files; while this comes at the cost of some minor incompatibilities with CP/M-80 (see below under NOTES), it vastly simplifies the interaction between CP/M and Unix programs compared to the alternative approach of using disk images.

Thanks to its system call translation layer, tnylpo neither contains any code from the CP/M operating system nor needs any additional software apart from the actual applications it is meant to run.

Since CP/M-80 programs can use eight-bit characters at best (many of them even share the firm belief of CP/M in a seven-bit ASCII world), another translation layer mediates between the internal one-byte-per-character text representation and the almost universal multibyte character (most commonly UTF-8 based) approach of contemporary Unix systems. This translation layer affects the character orientated I/O channels of CP/M only and may be disabled for the printer, punch, and reader devices.

All screen-orientated CP/M-80 programs are unable to cope with the ability of graphical user interfaces to resize text windows (in fact, even the ability of the DEC VT100 to switch between 80 and 132 column mode overtaxes them); to alleviate this problem (and to provide the capability of running the same CP/M application on different terminal emulations or on serial terminals without reconfiguring the CP/M binaries), tnylpo includes a curses based emulation of the DEC VT52 terminal (with certain extensions in functionality) for use as CP/M console device. tnylpo gives the user the choice between this terminal emulation and a line orientated console device more suitable for programs like assemblers and compilers.

True to its aim of allowing the integration of CP/M programs into the Unix environment, tnylpo happily does away with certain non-essential, rarely used, or extremely un-Unixy features of CP/M-80 (some of which —to be frank— would be disproportionately hard to implement or integrate under Unix), among them user areas, the rudimentary character device redirection implemented by the I/O byte, file attributes, and the printer echo. Whereever some functionality has been left out, care has been taken not to offend CP/M programs unduly (programs may e.g. set the user area, and on query tnylpo will report the current value back to them, but will ignore it otherwise). As a rule, tnylpo or Unix offer better solutions to replace the omitted CP/M features, e.g. different tnylpo configurations (and drives/directories) for different applications or users (user areas), shell output redirection (I/O byte), chmod(1) (file attributes), or script(1) (console protocol).

  CPU emulation
tnylpo strives to provide a full implementation of both documented and undocumented features of the Z80 CPU; undocumented instructions and side effects are supported to the best of the author's knowledge.

I/O instructions are available, but are dummies; writing to I/O ports has no direct effect, and reading them always returns a null byte. Same is true for all interrupt related instructions (while the I register may be set and read and the EI and DI instructions have the expected side effects, no interrupt will ever occur). Executing a HALT instruction will end program execution (and thereby the execution of tnylpo) and is considered an error situation (see below under Program termination). Well-behaved CP/M applications should never use any of these instructions anyway.

Note that the behaviour of the HALT instruction has changed from earlier versions of tnylpo, where it was simply ignored (resp. acted as if a NMI had been received immediately after entering the halt state). This change allows unused memory areas to be initialized with HALT instructions, which increases the chances of detecting and stopping a runaway application program early.

The R register will be increased as expected; since application programs may use the current value of the R register for the generation of random numbers (the Randomize procedure of Turbo Pascal seems to do this), the R register is initialized with a random value (otherwise, repeated runs of the same program would always generate the same sequence of random numbers).

At the start of program execution, all unused memory (i.e. the part of the TPA not occupied by the loaded executable as well as the unused parts of the zero page and the BIOS area) are initialized with the byte value 0x76 (the HALT instruction). All registers apart from PC, SP, and R are set to zero, PC is set to 0x100, and SP points to a stack area at the top of the TPA.

  File name translation
In translating CP/M disk operations to Unix disk operations, tnylpo has to translate CP/M file names to Unix file names and vice versa; since CP/M de facto allows almost arbitrary seven-bit characters in its file names (including null bytes and slashes), and Unix file names are not bound to the 8+3 character restriction of CP/M, tnylpo enforces a common subset: only decimal digits, the 26 letters of the alphabet, the minus (-), at (@), dollar ($), and hash (#) signs are allowed in file names, which are restricted to one to eight characters in length. The name may have an extension of up to three characters from the same set; the dot between name and extension may only be present if there is at least one extension character. On the Unix side, all letters appear in lower case, while tnylpo accepts (and returns) only upper case letters in CP/M FCBs. Unix files with names containing upper case letters or otherwise not conforming to tnylpo's restrictions are not accessible to CP/M programs. (Dollar signs in Unix file names are a nuisance in shell scripts, but since they are habitually used in CP/M temporary file names, tnylpo could not forbid them; fortunately, CP/M programs creating files with file names containing dollar signs usually remove them before exiting.)

OPTIONS
Many of tnylpo's command line options have corresponding entries in the configuration file, so they will be discussed in this context below (see Configuration options). Four options have no counterparts:

-a  selects the alternate character set from the configuration file before starting execution (see Character sets).

-e [h][b<bytes>|p<pages>|r[<addr>]-<addr>]:<filename>
    causes tnylpo to write (parts of) the CP/M memory to the specified file after program termination (see Saving a memory image).

-f <config-file>
    tells tnylpo to read its configuration from the named file.

-h  asks tnylpo to show a short command line synopsis (-h cannot be used in combination with any other command line option).

  Command line arguments
tnylpo interprets its first positional command line argument as the name of the CP/M program (.com file) it is supposed to execute. If this argument contains a slash (/), it is interpreted as a Unix path name, otherwise it is treated as a CP/M file name, i.e. it may be prefixed by a drive specification and is interpreted as relative to the path corresponding to this drive or the default drive according to tnylpo's configuration (see below). In both cases, the suffix .com is appended if the file name does not already end in .com.

All further positional arguments are passed as command line arguments to the CP/M program (i.e. they are converted to upper case and copied to the default DMA area at 0x0080; additionally, the default FCB at 0x005c is initialized according to the second and third positional arguments).

  Configuration files
While some features of tnylpo can be controlled by command line options, the most convenient way to configure it is by using a configuration file; if a configuration file is explicitly specified on the command line (option -f), it will be used; otherwise, tnylpo looks for a file named .tnylpo.conf in the current working directory. If none is found, tnylpo looks for .tnylpo.conf in the user's home directory. As a last resort, tnylpo uses its built-in defaults. If conflicting options are specified in the configuration file and on the command line, the command line takes precedence.

A tnylpo configuration file is a regular text file; empty lines and lines starting with a hash sign (#) or a semicolon (;) are ignored. All other lines have the form

    <keyword> [<token> ...] = <token> [<token> ...]

<token> is either a keyword (a sequence of alphanumeric characters starting with a letter), a number (hexadecimal, octal or decimal using the usual Unix convention of being prefixed by 0x, 0, resp. some other digit), a string in double quotes, or a comma.

  Configuration options

drive <drive letter> = [readonly ,] <path>

    Up to 16 drives can be defined by repeated use of this configuration option; <drive letter> is a single lower case letter in the range a–p, and <path> is a string containing the name of a directory on the host computer system. CP/M programs trying to create or access a file on the corresponding disk drive will create or access a file in this directory. Only regular files up to 8MB in size with names corresponding to tnylpo's idea of well-behaved file names suitable for both CP/M and Unix (see above) are visible to CP/M programs. If <path> is preceeded by the optional keyword readonly, programs running on tnylpo will not be able to create new files on this drive or rename, delete, or modify existing files (any attempt to modify a read-only drive will terminate the offending CP/M program).

    There is no corresponding command line option. If no drive has been defined in the configuration file (or if there is no configuration file), tnylpo will use

        drive a = "."

    as default, i.e. the current working directory will be made available as CP/M drive A.

default drive = <drive letter>
command line option -d <drive letter>

    define the drive identified by <drive letter> (a single lower case letter in the range a–p) as default drive, i.e. the drive all file specifications not including an explicit drive name refer to. This drive must be assigned to a host system directory, either implicitly or by the drive configuration option. If default drive is not specified, tnylpo assumes drive A as default drive.

close files = (true | false)
command line option -n

    If close files is set to false (or if the -n command line option is present), files closed by the CP/M program are kept open by tnylpo, i.e. the corresponding FCBs are not invalidated. This is required by some CP/M programs (see File closing), but should be avoided if possible, since otherwise tnylpo might run out of file descriptors. By default, tnylpo actually closes files closed by the CP/M program.

logfile = <path>

    <path> is a quoted string containing the path of a file to which tnylpo appends error messages and other logging information (fatal error messages are also written to stderr). If the logfile configuration option is not used, no logging information will be written. There is no corresponding command line option.

loglevel = <level>
command line option -v <level>

    The amount of data written to the logfile is controlled by the loglevel configuration option resp. its command line equivalent -v. Both take a numeric argument; the higher the number, the more information is written (causing the emulation to run progressively slower). Valid log levels are:

    0   write error messages only.

    1   additionally, count the machine instructions executed by the emulator; at program termination, tnylpo will output tables showing which instructions have been executed how often.

    2   additionally, trace FDOS functions (i.e. BDOS functions related to file I/O).

    3   additionally, dump FCBs for FDOS functions using a FCB.

    4   additionally, dump file records read and written by the FDOS functions.

    5   additionally, trace all other system calls (BDOS and BIOS functions); since all character I/O functions are traced, this will produce a lot of output.

    The logging facility is a leftover from the development and testing of tnylpo itself; since it may provide important clues if applications do not work as expected, it has been retained.

console = (full | line)
command line options -s or -b

    tell tnylpo to use the full screen VT52 emulation (full, -s) or the line orientated (line, -b) console interface. Some other configuration options, e.g. lines, columns, application cursor, and screen delay are only effective in the full screen mode. By default, tnylpo uses the line orientated console interface.

screen delay = (<number> | key)
command line option -t (<number> | @)

    define the number of seconds tnylpo should wait between program termination and resetting the display. If key (resp. @) is specified, tnylpo waits for a key being pressed before exiting the VT52 emulation. This option allows the user to see the final display of the CP/M application even if resetting the display restores the original screen contents or clears the screen. Default value is 0 (don't wait).

lines = (<number> | current)
columns = (<number> | current)
command line options -l (<number> | @) and -c (<number> | @)

    define the display size used by the terminal emulation; the number of lines must be between 5 and 95, the number of columns between 20 and 95. Using the keyword current (resp. @ in case of the command line options) tells tnylpo to use the current size of the display device. If no display size is specified in the configuration file or on the command line, tnylpo defaults to 24 lines of 80 columns.

application cursor = (true | false)
command line option -w

    If application cursor is set to true (or the command line option -w is present), pressing the cursor keys up, left, right, or down will send the control characters ^E (0x05), ^S (0x13), ^D (0x04), or ^X (0x18) to the running CP/M program (i.e. the appropriate cursor motion commands for programs like WordStar or Turbo Pascal). Otherwise, the cursor keys will generate the default VT52 escape sequences, <esc> A, <esc> D, <esc> C, and <esc> B. This option is only effective in full screen console mode.

exchange delete = (true | false)
command line option -r

    If exchange delete is set to true (or the command line option -r is present), the backspace (^H, 0x08) key and the delete (0x7f) key are reversed in full screen mode.

[alt] char <number> = <string>
[alt] charset = (ascii | vt52 | latin1 | tnylpo)
unprintable = <string>

    serve to define the primary and alternate character sets used by tnylpo; they have no corresponding command line options and are explained below (see Character sets).

(printer | punch | reader) file = <path>
(printer | punch | reader) mode = (text | raw)

    define the path and the format of the data files representing the CP/M character I/O devices LST:, PUN:, and RDR:; there are no corresponding command line options. Details are explained below (see Character devices).

colors = ( false | true ) [ , <fg> , <bg> ]
command line options -on, -oy, -oy,<fg>,<bg> and -o<fg>,<bg>

    switch color support on or off in full screen mode; optionally, the default foreground and background colors (digits in the range from 0 to 7) may be selected (see Color support).

cpu delay = <count> , <nanoseconds>
command line options -yn and -y<count>,<nanoseconds>

    slow down the emulation by adding a delay (specified by <nanoseconds>) after every <count> instructions executed by the emulated CPU. The command line option -yn disables this delay, overriding a delay specified in the configuration file. Slowing the emulation allows using software (e.g. interactive games) which would otherwise run much too fast on tnylpo; another application is reducing the high load tnylpo puts on the host CPU.

    Useful values for <count> and <nanoseconds> depend on the speed of the host CPU, on the host operating system, and on the particular application program; they need to be determined by experimentation. Please bear in mind that the minimal useful delay (i.e. the minimal value for <nanoseconds>) depends on the host operating system and may be as large as several milliseconds; since smaller delays may be rounded up to that minimum implicitly, specifying e.g. -y1,1 may give unexpected results.

dump = none
dump = all
dump = (startup | exit | error | signal) [, ...]
command line options -zn, -za, and -z{s|x|e|i}

    define if and when tnylpo writes a machine dump (including register values and the contents of main memory) to the log file. The keywords startup, exit, error, and signal (resp. the suboptions s, x, e, and i) request a dump at program startup (immediately before passing control to the CP/M command), at program exit, at program exit due to a fatal execution error, or at the receipt of a SIGUSR1 signal and may be arbitrarily combined (only exit and error are mutually exclusive). dump = all (resp. -za) is a shorthand for dump = startup, exit, signal (resp. -zsxi), and dump = none (resp. -zn) turns all dumps off (this is also the default setting). Dumps are only written if a log file has been defined by the configuration option logfile.

  Terminal emulation
tnylpo provides a curses based emulation of the DEC VT52 terminal, which can be used instead of the default line orientated console to accommodate full-screen applications; this terminal emulation is selected by the command line option -s resp. by the entry console = full in the configuration file.

tnylpo's terminal emulation mimicks the VT52 fairly accurately, but offers a number of extensions, among them the ability to support screen sizes of up to 95 by 95 characters (this limitation is due to the limitations of the VT52 direct cursor positioning command), eight-bit operation, a dynamically switchable alternate character set, insert and delete line commands, and bold, underlined, inverted, and blinking (i.e. everybody's favourite) character rendition. To protect the CP/M application (resp. its user) from the effects of screen resizing, the terminal emulation provides a fixed size screen area (typically 80 columns by 24 lines, but this may be changed by command line or configuration file options) within the actual display (a terminal emulator like xterm(1) or the screen of an actual serial terminal). If the display device/window is larger than this area, there will be blank margins to the right and below the VT52 display area; if it is smaller, parts of the output from the emulator will be invisible to the user, but will (re-)appear as soon as the window is enlarged.

The terminal emulation (like the VT52) does not do an automatic line wrap (i.e. the cursor will not move to the first column of the next line if a character is displayed in the last column of a line) and supports (or at least tolerates) all of the VT52 control sequences:

<bel> (0x07)
    gives an accustic (or visual) signal.

<bs> (0x08)
    moves the cursor left, but stops at column 1.

<tab> (0x09)
    moves the cursor to the next tabulator stop if it is before the last tabulator stop on the current line (tabulator stops are at columns 9, 17, 25, ...); otherwise, it moves the cursor one column to the right (resp. does nothing if the cursor is already in the last column).

<lf> (0x0a)
    moves the cursor down one line and scrolls up on bottom line.

<cr> (0x0d)
    moves the cursor to the first column of the current line.

<esc> (0x1b)
    marks the start of an escape sequence (see below).

All other characters in the ASCII control character range (0x00–0x1f, 0x7f) are ignored. The VT52 escape sequences are:

<esc> ) (0x1b 0x29) and <esc> = (0x1b 0x3d)
    switch the keypad to application resp. to regular (numeric) mode (no effect in tnylpo).

<esc> A (0x1b 0x41)
    moves the cursor up one line, but stops at the top line.

<esc> B (0x1b 0x42)
    moves the cursor down one line, but stops at the bottom line.

<esc> C (0x1b 0x43)
    moves the cursor right one column, but stops at the last column.

<esc> D (0x1b 0x44)
    moves the cursor left one column, but stops at the first column.

<esc> F (0x1b 0x46) and <esc> G (0x1b 0x47)
    display character codes 0x5e–0x7e as graphical resp. as ASCII characters (see below).

<esc> H (0x1b 0x48)
    moves the cursor to the left top corner of the display.

<esc> I (0x1b 0x49)
    moves the cursor up one line and scrolls down at the first line.

<esc> J (0x1b 0x4a)
    clears the display from the current cursor position to the end of the screen.

<esc> K (0x1b 0x4b)
    clears the display from the current cursor position to the end of the line.

<esc> Y <line> <column> (0x1b 0x59 0xYY 0xXX)
    moves the cursor to the specified position on the display; line and column numbers are given as graphical characters in the range of 0x20 (position 1) to 0x7e (position 95). If the column number is larger than the display width, the horizontal position is not changed; a line number larger than the height of the display moves the cursor to the last line.

<esc> Z (0x1b 0x5a)
    identifies the terminal type; the terminal emulation responds by sending the sequence <esc> / K (0x1b 0x2f 0x4b), i.e. it identifies itself as VT52 without hardcopy device.

<esc> [ (0x1b 0x5b) and <esc> \ (0x1b 0x5c)
    enter resp. exit "hold screen" mode (see below).

In addition, the terminal emulation in tnylpo supports the following extensions to the VT52 escape sequences:

<esc> E (0x1b 0x45)
    clears the display, moves the cursor to the top left corner of the display.

<esc> L (0x1b 0x4c)
    inserts a blank line at the cursor position and moves lines below down one line (the last line is lost).

<esc> M (0x1b 0x4d)
    deletes the line at the cursor position and moves lines below up one line (an empty line will appear at the bottom of the display).

<esc> N (0x1b 0x4e)
    inserts a blank character at the cursor position and moves characters to the right one position to the right (the last character on the line is lost).

<esc> O (0x1b 0x4f)
    deletes the character at the cursor position and moves characters to the right one position to the left (a blank character appears in the last column of the line).

<esc> S <color> (0x1b 0x53 0xCC)
    sets the background color of subsequent output characters; the color code is described below (Color support).

<esc> T <color> (0x1b 0x54 0xCC)
    sets the foreground color of subsequent output characters; the color code is described below (Color support).

<esc> a (0x1b 0x61) and <esc> b (0x1b 0x62)
    make the cursor invisible resp. visible.

<esc> c (0x1b 0x63) and <esc> d (0x1b 0x64)
    switch to the alternate resp. to the primary character set (see below).

<esc> e (0x1b 0x65) and <esc> f (0x1b 0x66)
    switch on resp. off bold characters.

<esc> g (0x1b 0x67) and <esc> h (0x1b 0x68)
    switch on resp. off underlined characters.

<esc> i (0x1b 0x69) and <esc> j (0x1b 0x6a)
    switch on resp. off inverted characters.

<esc> k (0x1b 0x6a) and <esc> l (0x1b 0x6c)
    switch on (arrgh!) resp. off (phew!) blinking characters.

<esc> m (0x1b 0x6d)
    switches off bold, underlined, blinking, and inverted characters as well as standout mode (see below); resets the foreground and background colors to their defaults.

<esc> n (0x1b 0x6e) and <esc> o (0x1b 0x6f)
    switch the cursor keys to application mode resp. back to regular (VT52) mode: in regular mode, the cursor keys send the sequences <esc> A (up), <esc> B (down), <esc> C (right), and <esc> D (left); in application mode, they send WordStar-compatible commands, namely ^E (0x05, up), ^X (0x18, down), ^D (0x04, right), and ^S (0x13, left).

<esc> p (0x1b 0x70) and <esc> q (0x1b 0x71)
    switch on resp. off standout mode; standout mode is the most visible character attribute provided by curses (this is usually inverted video, so using <esc> p and <esc> q is usually equivalent to using <esc> i and <esc> j).

"Hold screen" mode is a feature of the VT52 terminal: it is entered and exited either by the computer sending the relevant control sequence or by the user by pressing the "hold screen" key (in tnylpo, this is the F5 key). In "hold screen" mode, trying to scroll up the screen (by a <lf> on the bottom line of the screen) blocks further output until either "hold screen" mode is exited or the user presses the "scroll page" key (F6 in tnylpo) or the "scroll line" key (F7 in tnylpo), which allow one more screenfull resp. one more line of output.

Displaying graphical characters instead of ASCII characters for the byte range 0x5e–0x7e is another feature of the VT52 terminal which allows access to certain additional shapes like subscripted digits or the ± sign; in tnylpo, graphical mode allows displaying the shapes defined for the character positions 0x00–0x1f and 0x7f, which cannot be displayed directly.

  Character sets
Switching between a primary and an alternate character set is an extension provided by tnylpo: two full character sets of 256 shapes (each containing a separate graphical character set in the positions 0x00–0x1f and 0x7f) may be defined in the configuration file; programs may switch between these two sets by using <esc> c resp. <esc> d. Switching character sets doesn't change characters already written to the display. This feature allows programs to use e.g. a national variant of the ISO 646 seven-bit character set in parallel to standard ASCII characters.

Character set definition is done in the configuration file by using the option

    [alt] char <number> = <string>

which defines <string> (a one-character string literal in double quotes) as representation of code <number> (a number in the range 0–255) in the primary (or alternate, if the line is prefixed by alt) character set. Characters not explicitly defined in this way are taken from a default character set, which may be selected by the option

    [alt] charset = (ascii | vt52 | latin1 | tnylpo)

The possible values correspond to the four built-in character sets ASCII, VT52 (ASCII plus an approximation of the VT52 graphical characters), the ISO 8859-1 (Latin 1) eight-bit character set, which supplements the ASCII code by characters used in Western European languages in positions 0xa0–0xff, and finally a homespun superset of Windows code page 1252 (and thereby a superset of ISO 8859-1 and ASCII), which supports block graphics and the VT100 box drawing characters as graphical character set.

If no character set is specified, tnylpo uses the VT52 character set by default.

By using the command line option -a, a program may be started with the alternate character set selected; the effect is almost identical to the program issuing <esc> c as its very first action. (There is a difference in full screen console mode: During screen initialization, tnylpo passes the blank character from the selected character set to the curses library for use as background character. Since the program itself can select the alternate character set only after this initialization has been done, curses will receive the blank character from the primary character set; with the -a command line option, it will receive the blank character from the alternate character set. This difference is mostly academic, since it is not recommended to redefine the blank character anyway, see below.)

Output characters which are undefined in the currently selected character set are ignored by the terminal emulation; the configuration option

    unprintable = <string>

substitutes undefined characters on output by the value of <string> (which must contain a single character).

  Function keys
Apart from F5, F6, and F7, which are used to implement the "hold screen" function of the VT52 (see above), tnylpo's terminal emulation defines the function keys F1, F2, and F3 as equivalents of the three unlabeled keys of a VT52 terminal; if pressed, they return the sequences <esc> P (0x1b 0x50), <esc> Q (0x1b 0x51), and <esc> R (0x1b 0x52). F4 causes the terminal emulation to redraw its display, which is useful if some other program or the host operating system mess up the user's screen. F10 sends a SIGINT to tnylpo, causing the emulation to stop abruptly, but allowing tnylpo itself to exit gracefully (this key is meant as a last-resort way of stopping a CP/M program gone haywire).

  Color support
If the underlying software and display hardware allow color output, tnylpo's terminal emulation supports the use of eight different colors, which may be selected as foreground resp. background colors of subsequently displayed characters. These colors are identified by the following numeric codes:

    0    black
    1    blue
    2    red
    3    magenta
    4    green
    5    cyan
    6    yellow
    7    white

By default, color support is disabled even on color-capable displays; it must be explicitly enabled either by the configuration option

    colors = true

or by the command line option -oy. Both colors = true and -oy may be followed by two comma-separated color codes, which specify the default foreground resp. background colors of tnylpo's terminal emulation, e.g.

    colors = true, 6, 1

(resp. -oy,6,1) will select yellow characters on a blue background. The default colors may be specified in the configuration file by colors = false, 6, 1 even if color support is disabled (since this may be overridden by -oy). The command line option -o6,1 is a shorthand form of -oy,6,1, and -on may be used to override the configuration file and disable color support. If no default colors have been specified either in the configuration file or on the command line, tnylpo defaults to 7 and 0, i.e. to white text on a black background.

Application programs may select colors by issuing the escape sequences <esc> S <color> and <esc> T <color> to choose the background resp. foreground colors of subsequently displayed characters; <color> is either a single ASCII digit in the range from 0 to 7 (0x31 to 0x37) corresponding to one of the colors supported by tnylpo, or the character = (0x3d), which will select the respective default color. To render e.g. the text Warning! in red, a program may display the character sequence

    <esc> T 2 W a r n i n g ! <esc> T =

on the full-screen console. Both foreground and background colors will also be reset to their defaults by the escape sequence <esc> m, which resets all text attributes. On monochrome devices or if color support has been disabled, <esc> S and <esc> T will still be accepted but silently ignored.

  Character devices
Besides the bidirectional console device, CP/M supports three unidirectional character devices, the output-only printer and paper tape punch devices LST: and PUN: and the input-only paper tape reader device RDR:. tnylpo represents these devices by host system files, to which the data provided by the CP/M program are written resp. from which the data presented to the CP/M program are read.

These files are defined by the configuration options

    (printer | punch | reader) file = <path>

where <path> is a string in double quotes containing the Unix file path; in case of the output devices (LST: and PUN:), data written by the CP/M program are appended to the contents of the named files; RDR: input will start at the beginning of the reader file. If a CP/M program reads past the end of the reader file, tnylpo will continue to return ^Z (0x1a) bytes as end-of-file indication.

tnylpo's character devices can operate in two modes, depending on the configuration options

    (printer | punch | reader) mode = (text | raw)

In raw mode, bytes are written to the file exactly as they are generated by the CP/M program resp. passed to the CP/M program exactly as they are read from the file (^Z bytes are still returned as EOF indication). In text mode, tnylpo will translate the characters read resp. written using the translation table used by the console. Character set switching by the console (using the escape sequences <esc> c and <esc> d) will affect this translation, but switching to graphical characters (<esc> F and <esc> G) will not. Moreover, tnylpo will convert the line end markers from CP/M (^M^J, 0x0d 0x0a) to Unix (^J, 0x0a) and vice versa in this mode.

  Saving a memory image

The command line option -e causes tnylpo to save parts of the CP/M memory to a file after a regular program termination (i.e. one that is not caused by a fatal error or receipt of a signal); it takes as argument a string of suboptions which specify the name and the format of the output file and the range of CP/M memory addresses to save. Possible suboptions and their parameters are

h   causes the memory contents to be written in Intel Hex format; if this option is absent, a binary file will be written.

b<bytes>
    selects a number of bytes (specified as decimal number) starting from address 0x100.

p<pages>
    selects a number of pages (units of 256 bytes, specified as decimal number) starting from address 0x100.

r[<low-addr>]-<high-addr>
    selects a range of addresses to save (both addresses may be specified as octal, hexadecimal, or decimal numbers using the Unix convention of prefixing them with 0, 0x, or any other digit, respectively); <high-addr> may not be less than <low-addr>, and if <low-addr> is missing, 0x100 will be assumed in its place.

:<filename>
    specifies the Unix file to which the memory contents are written; this is mandatory and must be the very last suboption (in fact, everything following the first colon in the argument of the -e command line option is taken as part of the file name).

Individual suboptions resp. suboptions and their parameters may not be separated by whitespace. The three range specifications b, p, and r may not exceed the range of the CP/M memory space (0x0000–0xffff) and are mutually exclusive; if none of them is specified, the whole TPA (starting at address 0x100 and including the last byte before the BDOS area) is saved.

The command line option -e serves as a replacement for the SAVE command of CP/M, which is most often used to save an executable file after applying patches with a debugger (most CP/M debuggers don't provide a command for writing memory contents to a disk file). Invoking tnylpo with the additional option -ep34:new.com is equivalent to issuing the command SAVE 34 NEW.COM immediately after termination of an executable under CP/M.

  Supported system calls

This subsection gives a list of system calls supported by tnylpo. While there are some extensions mainly taken from the CP/M 3 BDOS interface, tnylpo prioritizes compatibilty with CP/M 2.2. All unimplemented BDOS calls (i.e. those not listed in this section) are tolerated and conform to the (admittedly suboptimal) CP/M 2.2 convention of returning 0 in registers A, B, and HL.

    #0    System Reset
    #1    Console Input
    #2    Console Output
    #3    Reader Input
    #4    Punch Output
    #5    List Output
    #6    Direct Console I/O
    #7    Get I/O Byte (dummy)
    #8    Set I/O Byte (dummy)
    #9    Print String
    #10    Read Console Buffer
    #11    Get Console Status
    #12    Return Version Number
    #13    Reset Disk System
    #14    Select Disk
    #15    Open File
    #16    Close File
    #17    Search for First
    #18    Search for Next
    #19    Delete File
    #20    Read Sequential
    #21    Write Sequential
    #22    Make File
    #23    Rename File
    #24    Return Log-In Vector
    #25    Return Current Disk
    #26    Set DMA Address
    #27    Get Addr(Alloc) (dummy)
    #28    Write Protect Disk
    #29    Get Read-Only Vector
    #30    Set File Attributes (dummy)
    #31    Get Addr(Diskparams) (dummy)
    #32    Get/Set User Code (dummy)
    #33    Read Random
    #34    Write Random
    #35    Compute File Size
    #36    Set Random Record
    #37    Reset Drive
    #40    Write Random with Zero Fill
    #49    Get/Set System Control Block (CP/M 3)
    #101    Return Directory Label Data (CP/M 3)
    #102    Read File Date Stamps and Password Mode (CP/M 3)
    #105    Get Date and Time (CP/M 3)
    #108    Get/Set Program Return Code (CP/M 3)
    #141    Delay (MP/M)

BDOS function #49 (Get/Set System Control Block) is only partially implemented: Attempts to write to the SCB are silently ignored, and only the SCB offsets 0x05 (BDOS version number, always 0x22), 0x10–0x11 (program return code), 0x1a (console columns), 0x1c (console lines), 0x37 (output delimiter, always 0x24), 0x3c–0x3d (current DMA address), 0x3e (current disk), 0x44 (current user number), and 0x4a (current multi sector count, always 1) provide meaningful information (all other byte offsets return 0).

BDOS function #102 (Read File Date Stamps and Password Mode) always returns the access and modification timestamps of the underlying Unix file, and function #101 (Return Directory Label Data) correspondingly always returns a value of 0x61.

BDOS function #141 (Delay) delays program execution for the number of system clock ticks passed in register DE; tnylpo defines a tick to last 20 milliseconds, i.e. it emulates a ticker frequency of 50 Hertz.

In addition to the BDOS calls, tnylpo implements all BIOS calls of CP/M 2.2, but all disk related functions are dummies:

    BOOT    System Cold Start Initialization
    WBOOT    Warm Start
    CONST    Console Input Status
    CONIN    Console Character Input
    CONOUT    Console Character Output
    LIST    Printer Character Output
    PUNCH    Paper Tape Punch Output
    READER    Paper Tape Reader Input
    HOME    Recalibrate Disk Drive (dummy)
    SELDSK    Select Disk Drive (dummy)
    SETTRK    Set Track Number (dummy)
    SETSEC    Set Sector Number (dummy)
    SETDMA    Set DMA Address (dummy)
    READ    Read Selected Sector (dummy)
    WRITE    Write Selected Sector (dummy)
    LISTST    Printer Output Status
    SECTRAN    Sector Number Translation (dummy)

No additional BIOS calls from CP/M 3 have been implemented, not even as dummies, i.e. any attempt to call them will result in undefined program behaviour.

  Program termination

tnylpo terminates as soon as the CP/M program it executes terminates by either calling BDOS function #0 (System Reset) or BIOS function WBOOT; jumping to address 0x0000 or using the stack area set up by tnylpo and ending execution by a RET instruction is equivalent to calling BIOS function WBOOT directly. Finally, the user may terminate a program by pressing ^C (0x03) when prompted for console input by BDOS function #10 (Read Console Buffer). All these forms of program termination are considered regular for the purpose of determining the exit status from tnylpo (see below, under EXIT STATUS).

Furthermore, a CP/M program will be terminated if it performs an action considered illegal by tnylpo, like executing a HALT instruction, trying to write to a read-only file or to a file on a disk configured as read-only, passing invalid arguments to system calls, accessing a disk drive not configured, or requesting an illegal sequence of operations (like trying to read from a disk file which has already been closed).

When the full screen console is active, a program may be terminated by the user pressing the F10 key (see above, "Function keys"); this is equivalent to sending a SIGINT to tnylpo from another terminal/terminal window. Pressing F10 terminates the CP/M program abruptly and should (like sending a signal to tnylpo) only be used as a last resort when dealing with a hung application.

Both program termination due to an illegal action and terminating a program by pressing F10 (or sending tnylpo a signal) are considered irregular forms of program termination.

EXIT STATUS
tnylpo exits with status 1 if it encountered a fatal error and status 0 otherwise. Fatal errors are command line errors, configuration file errors, or an irregular termination of the CP/M program (see above).

CP/M-80 version 2.2 has no concept of a program exit status, so there is no well-established way of communicating an unsuccessful CP/M program execution to the Unix environment. To alleviate this deficit, tnylpo implements BDOS function #108 (Get/Put Program Return Code) from CP/M 3; if a CP/M program sets its return code to a value in the "unsuccessful return" range (0xff00–0xfffe) prior to termination, tnylpo will exit with status 1.

FILES

  ./.tnylpo.conf
The file .tnylpo.conf in the current working directory is used as configuration file, if it is present and no configuration file has been specified on the command line.

  ~/.tnylpo.conf
The file .tnylpo.conf in the user's home directory is used as a configuration file, if it is present, if no configuration file has been specified on the command line, and if there is no file .tnylpo.conf in the current working directory.

Using configuration informations from a file in the current working directory (./.tnylpo.conf) is convenient in many situations, but poses a potential security risk; mainly for this reason, tnylpo refuses to run if its effective user ID is 0.

NOTES

  Differences between tnylpo and CP/M-80
By design, there are some incompatibilities between CP/M-80 and the emulation provided by tnylpo:

  Direct access to the BDOS and BIOS areas
Since it doesn't contain any actual CP/M code, all programs trying to patch the BDOS or otherwise make assumptions about the layout of the operating system or its internal data structures will fail while running on tnylpo.

BDOS and BIOS function emulations are activated by the simulated processor executing a RET instruction fetched from one of the uppermost 19 addresses of the CP/M memory space (0xffed for the BDOS entry, 0xffee to 0xfffe for the 17 BIOS entries of CP/M-80 2.2 and 0xffff for one tnylpo-specific delay routine hiding as 18th BIOS entry); this use of "magic addresses" might confuse debuggers trying to trace system calls.

The BIOS area (starting three bytes before the address stored at 0x0001) is page-aligned and contains the 17(+1)-element BIOS jump vector, the dummy disk structures (see below) and the above mentioned 19 RET instructions. The BDOS area (starting at the address stored at 0x0006) is even shorter, containing only a jump to 0xffed and the table of target addresses for the four fatal BDOS error conditions (non-existing disk, bad sector, read-only disk, and read-only file; these addresses may be modified by an application program, but are completely ignored by tnylpo). Any program expecting the BDOS or the BIOS areas to have the sizes and alignment characteristics of a real CP/M-80 environment will be disappointed.

The OS serial number stored in the six bytes immediately before the BDOS area is always 0x00 0x16 0x00 0xc0 0xff 0xee, a (hopefully inoffensive) dummy indicating BDOS version 2.2.

  Direct access to the disk drives
All programs trying to access the CP/M disk structure directly (e.g. disk editors) will not work, since there is no underlying CP/M disk structure (tnylpo translates FDOS calls into operations on Unix files); all disk related BIOS calls are implemented as dummy functions (those few which can return an error indication —SELDSK, READ, and WRITE— will do so). The BDOS functions returning disk structure related information (#27 and #31) will return dummy structures containing meaningless (but consistent) data; for example, all disk drives will be reported as having a block size of 16KB, 2048 directory entries and a capacity of 8MB, of which 8128KB (8MB less four directory blocks) are free (to save space, all drives share the same dummy allocation vector, which is of course impossible with real CP/M-80). All block information returned by the BDOS functions #17 and #18 is meaningless; regardless of the contents of the S2 field (FCB offset 14), all otherwise matching files are returned only once (as opposed to once per physical extent under CP/M-80). Likewise, all block information in the FCBs of open files (FCB byte offsets 16 to 31) is meaningless (but may not be disturbed since tnylpo stores some state information there, see below). The emulation is good enough for programs searching for file name patterns or just listing the disk directory, but will fail for those trying to analyse the block structure of the emulated disk from the returned information.

  Console Command Processor

tnylpo doesn't emulate the CCP, so every program using its features (by e.g. creating a $$$.SUB file and expecting the CCP to execute its contents) will not work correctly. Ending a program by simply returning to the CCP is supported; tnylpo initializes an 8 level CCP stack at the end of the TPA (below the OS serial number) and pushes the address of the WBOOT entry in the BIOS vector, so a program trying to return to the CCP will terminate correctly.

  File attributes
File attributes (read-only and system attribute resp. the four attributes available for user programs) are not supported by tnylpo; the corresponding BDOS function #30 is a dummy. File names characters will always be returned with the most significant bits reset.

  User areas
The concept of user areas is not implemented by tnylpo; while the user code may be set by BDOS function #32 (and will be returned when queried), this has no influence on file operations.

  I/O byte
Likewise, the I/O byte functionality is not implemented; while the I/O byte may be queried and set (by BDOS functions #7 and #8) and is stored in address 0x0003, it has no influence on the character devices.

  FCB structures
FDOS calls are translated into Unix file operations. For this to work, tnylpo maintains a separate list of corresponding Unix files; references to the entries of this list are stored in the FCBs of open CP/M files (a 16 bit reference number is stored at offsets 16 and 17 in the FCBs, and the same number XORed by 0xafcb is stored at offsets 18 and 19). This allows programs to copy the FCBs of open files and use the copies to further access the same files (e.g. programs written in Turbo Pascal do such things). The drive and file name portions of a FCB are only referenced by BDOS functions #15, #17, #19, #22, #23, #30, #35, and #102, all other functions (especially the read and write functions) use the file reference number to identify the relevant Unix file. The current record number in sequential I/O operations is stored in the FCB fields EX, S2, and RC (offsets 12, 14, and 32).

  Sparse files
Since CP/M file operations are directly mapped to Unix file operations, some characteristics of Unix files are visible to CP/M programs running on tnylpo: Trying to read a record within an unwritten block in the middle of a sparse file would result in an error indication (reading unwritten data) under CP/M-80, but simply returns a record of zero bytes on tnylpo, since unallocated areas in the middle of a Unix file read as zero. Hopefully, few programs will take offence at this difference.

  File closing
Normally, closing a file in a CP/M program (BDOS function #16) will cause tnylpo to close the corresponding Unix file and free its entry in the file list. This causes problems with certain CP/M software (e.g. dBase II), which continue to use FCBs for file operations after calling BDOS function #16. Since the close operation just writes the updated FCB data back to the disk directory (but doesn't change the FCB), this is possible in CP/M-80 (though a little dirty in my opinion). Contrarily, tnylpo (in addition to closing the corresponding Unix file) removes its file reference from the FCB, thereby marking it as invalid for further I/O, causing subsequent operations by the CP/M program to fail.

To accommodate such programs, tnylpo provides the -n command line option (and the corresponding close files = false configuration option), which prevent closing the Unix file and removing the file reference from the FCB (effectively making BDOS function #16 a dummy operation). If many files are opened (sequentially or concurrently) by a CP/M program, this may cause tnylpo to run out of files, since the closing of all Unix files is deferred until program termination.

  Text file format
Disk file I/O is always done untranslated, i.e. the contents of disk files written by programs running on tnylpo are always in the character set used internally by the CP/M emulation. Likewise, the line end convention in text files is that of CP/M (^M^J, 0x0d 0x0a) and text files not ending exactly at the end of a 128 byte CP/M file record will contain a ^Z (0x1a) byte as logical EOF marker (ideally, they should be padded with ^Z bytes to the next record border, but in reality they usually end in a single ^Z followed by arbitrary rubbish). This must be taken into account when using Unix utilities to process files generated by tnylpo.

On the other hand, text files generated under Unix should be converted to the CP/M line end convention before processing them with programs running on tnylpo (the command set ff=dos might come handy for people using Vim!). The CP/M convention for marking the logical end of text files is honoured automatically, since tnylpo's BDOS emulation pads Unix files to the CP/M record size of 128 bytes with ^Z bytes.

Text file conversion between the Unix and the CP/M formats may be done conveniently using the companion program tnylpo-convert(1).

  Performance
tnylpo has been optimized for portability, not for performance; some parts of the processor emulation (e.g. the addition and subtraction operations) may be ridiculously inefficient compared to a hand-optimized assembler version (or even a C version making moderate use of platform specific things like byte order or number representation). On the plus side, tnylpo should compile and run on every Unixy platform supporting ISO C99 (for wide and multibyte character support), a wide character capable version of the ncurses library and int variables with more than 16 bits. That said, I found tnylpo blindingly fast compared to the real thing even on the outdated hardware I used for its development.

  The delay routine
Since CP/M-80 version 2.2 offers no functions for time keeping or delays, programs are forced to use the cycle time of certain instructions in combination with the CPU clock frequency if they need to delay program execution; this approach is doomed under an emulator, especially if it is running on a multiprogramming system.

To allow CP/M programs to delay execution for a defined (wall clock) time period, tnylpo supplies a delay routine masquerading as 18th BIOS entry: it expects a single parameter, an unsigned 16-bit number in register BC, and waits this many milliseconds before returning to the caller.

The benefit of this routine is at best limited, but most likely nonexistent; since it is an extension, no existing CP/M application (for which tnylpo has been created in the first place) uses it, and even in case somebody would develop new CP/M software, employing tnylpo's delay function would break compatibility with all other CP/M machines or emulators.

As an alternative, tnylpo implements function #141 (Delay) from the MP/M BDOS; while this function is a little more standardized than tnylpo's proprietary extension, it is equally unavailable on real CP/M and most compatible systems and suffers from the additional drawback that it measures time in system timer ticks, which are implementation dependent (tnylpo emulates a tick length of 20 milliseconds, i.e. a 50 Hertz timer).

  The name of the program
"tnylpo" is a fantasy word. Neither is it an acronym nor does it have any meaning I know of (which is the main reason I chose it). "tnylpo" is pronounced like a native speaker of German (e.g. I) would naively pronounce "tnülpo" (or like a speaker of Finnish would pronounce "tnylpo", provided she survives the initial consonant cluster). In its formation, "tnylpo" with its two syllables and ending in "-po" aligns nicely with other nonsense words I invented earlier (like "ilpo" or "sampo") —until I moved to Finland and discovered that most of these supposed nonsense words have currency here...

BUGS
See the remarks under NOTES. Most of them may be interpreted as bugs in the implementation, including the program name.

  Character set switching
A program running in full screen mode (VT52 emulation) may change its display character set from the primary character set to the alternate set and back by issuing an appropriate escape sequence. This switching also affects the character set translation of the other character devices (LST:, PUN:, and RDR:), unless they are operating in raw mode.

  Character redefinition
It is not recommended to redefine any of the characters which may appear in CP/M file names, their Unix equivalents or in the file name/file extension fields of an FCB; these include the decimal digits (0x30–0x39), the upper- and lower case characters (0x41–0x5a and 0x61–0x7a), space (0x20), dollar sign (0x24), asterisk (0x2a), minus (0x2d), dot (0x2e), colon (0x3a), and the question mark (0x3f).

  Host system locales
tnylpo has been implemented with the intention to work with all possible Unix CTYPE locale settings (other aspects of the current locale are ignored); unfortunately, only ASCII based systems and locales are supported, since there is no character set independent way to represent some of the control characters relevant for CP/M (e.g. ^C and ^Z). Currently, tnylpo always assumes that the character codes 0x00–0x1f and 0x7f of the host operating system single- or multibyte character set correspond to the ASCII control characters. Frankly, tnylpo has only been extensively tested to work with UTF-8 based locales.

  File namespace clashes
tnylpo tries to hide unsuitable files in host directories used as CP/M drives from CP/M programs; file searches and open requests will ignore everything but regular files up to 8MB in size which have acceptable names. Unfortunately, this attempt at hiding other objects is far from perfect: a CP/M program trying e.g. to create a file whose name matches the name of a (hidden) directory will mysteriously fail (a rename operation may meet similar problems). It is therefore recommended to reserve directories used as CP/M drives to conforming regular files or to give all other objects names not acceptable as CP/M file names (a leading dot may work wonders!).

  Color limitiations
The color support in tnylpo's terminal emulation is constrained by the capabilities of the ncurses library resp. of the display hardware. These may cause a limitation of the number of combinations of foreground and background colors (color pairs) which may be used on the display simultaneously. If tnylpo runs out of color pairs, the affected combinations will revert to the default foreground and background colors. This problem should only occur with pathological ("angry fruit salad") user interface designs.

EXAMPLES
tnylpo -f ws.conf ws hugo.txt

will use the configuration file ws.conf and run the program ws.com, passing it the string hugo.txt as command line parameter.

If the file ws.conf contains the following lines

    #
    # WordStar configuration
    #
    drive a = "."
    printer file = "./printer.txt"
    printer mode = text
    logfile = "./.tnylpo.log"
    loglevel = 0
    #
    # use German character set
    #
    charset = ascii
    char 0x40 = "§"
    char 0x5b = "Ä"
    char 0x5c = "Ö"
    char 0x5d = "Ü"
    char 0x7b = "ä"
    char 0x7c = "ö"
    char 0x7d = "ü"
    char 0x7e = "ß"
    unprintable = "¿"
    console = full
    lines = 24
    columns = 80
    application cursor = true
    screen delay = 2

tnylpo will search for ws.com (and other files) in the local directory, which will be used as drive A in the emulation; console I/O will be handled in full screen mode by the VT52 emulation which uses a screen of 24 lines of 80 columns. The cursor keys will be translated to ^E (up), ^S (left), ^D (right), and ^X (down), and the character set will correspond to the German ISO/IEC 646 variant (umlaut characters in place of the brackets and curly braces of ASCII). Characters in the range of 0x80 to 0xff will be displayed as inverted question marks. Printer output will be written to printer.txt in the current directory, will be in the Unix character set and will follow Unix line end conventions. A log will be appended to .tnylpo.log in the current directory and will contain error messages only. After program termination, tnylpo will wait for two seconds before resetting the screen.

  Patching a CP/M executable
Many CP/M applications do not supply installation programs but rely on the user applying (hopefully well-documented) binary patches to support e.g. the control sequences of a particular video terminal. Under CP/M, this is usually done using a debugger (e.g. DDT) in combination with the CCP built-in command SAVE. The following example shall demonstrate the process of patching an executable using tnylpo:

    $ ls -la
    total 72
    drwxr-xr-x   7 tnylpo  users   224 Sep  5 13:04 .
    drwxr-xr-x  39 tnylpo  users  1248 Sep  5 13:04 ..
    -rw-r--r--   1 tnylpo  users  8192 Sep  5 13:02 asm.com
    -rw-r--r--   1 tnylpo  users  4864 Sep  5 13:02 ddt.com
    -rw-r--r--   1 tnylpo  users  2091 Sep  4 13:08 patch.asm
    -rw-r--r--   1 tnylpo  users  9728 Sep  5 13:02 wm.com
    -rw-r--r--   1 tnylpo  users  2688 Sep  5 13:02 wm.hlp

wm.com is the binary to be patched (wm.hlp is an associated data file). asm.com and ddt.com are the standard CP/M absolute assembler and debugger (ASM and DDT) which will be used to prepare resp. apply the patch. patch.asm contains the assembler source file of the patch. Note the size of wm.com, which is 9728 bytes.

First, the patch is assembled, which creates patch.hex, a file in the Intel Hex format.

    $ tnylpo asm patch
    CP/M ASSEMBLER - VER 2.0
    01D8
    000H USE FACTOR
    END OF ASSEMBLY

Now the debugger is started, which relocates itself to the top of the TPA and loads wm.com into the TPA starting at address 0x100. The option -eb9728:wmnew.com asks tnylpo to save 9728 bytes (the size of wm.com) starting from address 0x100 to the file wmnew.com after the CP/M program (i.e. DDT) has terminated.

    $ tnylpo -eb9728:wmnew.com ddt wm.com
    DDT VERS 2.2
    NEXT  PC
    2700 0100

DDT displays two hexadecimal numbers, the first of which is the first free memory address after the loaded program (0x2700 is decimal 9984, i.e. 0x100 plus 9728, the size of wm.com). Now DDT is used to apply the patch: The command i tells DDT the name of the patch file, and the command r reads it into memory (the Intel Hex format provides the necessary address information to apply the changes at the correct locations). Finally, g0 directs DDT to jump to adress 0, which causes it to terminate.

    -ipatch.hex
    -r
    NEXT  PC
    2700 0000
    -g0

As soon as DDT terminates, tnylpo saves the memory contents as directed, creating a patched version of the executable, which can be tested and renamed.

    $ ls -la wmnew.com
    -rw-r--r--   1 tnylpo  users  9728 Sep  5 13:06 wmnew.com

AUTHOR
tnylpo and its manual page were written by Georg Brein (tnylpo@gmx.at), a programmer, IT systems administrator and guerrilla egyptologist.

SEE ALSO
tnylpo-convert(1)

The implementation of the Z80 processor emulation, especially of the features not covered by the official documentation (Zilog Inc., Z80 CPU User Manual, Revision 11: August 2016 [UM008011-0816]), is heavily influenced by Sean Young's The Undocumented Z80 Documented (Version 0.91, 18th September 2005).

The CP/M-80 2.2 system interface emulation is based on the CP/M Operating System Manual by Digital Research (Third Edition: September 1983).

Some additional system functions are based on their descriptions in the CP/M Plus (CP/M Version 3) Operating System Programmer's Guide by Digital Research (Second Edition: April 1983).