View on GitHub


TI 99 Cross-Development Tools

Download this project as a .zip file Download this project as a tar.gz file

xdt99: TI 99 Cross-Development Tools

The TI 99 Cross-Development Tools (xdt99) are a small suite of programs that facilitate the development of programs for the TI 99 family of home computers on modern computer systems.

As of this release, the cross-development tools comprise

All programs are written in Python and thus run on any platform that Python supports, including Linux, Windows, and OS X.

Additionally, xdt99 provides TI-specific editor support for some freely available cross-platform development environments:

The plugins offer syntax highlighting, navigation, searching and semantic renaming for assembly, GPL, and TI (Extended) BASIC programs.

The major design goals of xdt99 and differentiators to similarly spirited projects such as the TI Image Tool or the Winasm99 assembler are

Future developments will focus on further simplifying typical development tasks such as data conversion and program generation.

xdt99 is released under the GNU GPLv2. All sources are available on GitHub. TI developers may also download the latest binary release of xdt99.

The xdt99 homepage always hosts the latest version of this document.


Download the latest binary release from GitHub (recommended) or clone the xdt99 repository. Please note that the big download buttons on top of the xdt99 homepage will include the entire repository; this is probably not what you want.

You will also need a working copy of Python 2.x installed on your computer. xdt99 has been developed using Python 2.7, but other versions should work as well. Note, however, that compatibility with Python 3 has been postponed to a later release for now.

Both cross-assemblers and the disk manager are self-contained Python programs that may be used independently of each other. The volume manager and the HFE manager depends on the disk manager and cannot be used without it. For installation, simply place the files,,,,, and somewhere in your $PATH or where your command-line interpreter will find them. Windows users will find additional information about installation and how to get started in the Windows Tutorial.

The ide/ directory contains the editor plugins for GNU Emacs and IntelliJ IDEA. Please refer to the file for further information about editor support.

The lib/ directory contains some supporting functions that you may use in your TI programs.

The example/ directory of the binary distribution contains some sample files that are referenced throughout this manual.

xas99 Cross-Assembler

The xas99 cross-assembler translates TMS9900 assembly code into executable programs for the TI 99 home computer equipped with the Editor/Assembler module or the Mini Memory module.

Invoking xas99 in standard mode will assemble a TMS9900 assembly source code file into an object code file that may be loaded using the Editor/Assembler module option 3.

$ -R ashello.a99

xas99 also generates program image files for the Editor/Assembler module option 5 or RPK cartridge files suitable for the MESS emulator:

$ -R -i ashello.a99
$ -R -c ashello.a99

All existing assembly code for the TI 99, e.g., the Tombstone City source code shipped with the Editor/Assembler module, should cross-assemble using xas99 without requiring any source code modifications.

The object code generated by xas99 is identical to uncompressed object code produced by the original TI Editor/Assembler package. This includes all of its quirks, such as shortened object code lines or excessive address specifications, but hopefully none of its bugs, such as invalid tags for certain DEFs.

For a detailed step-by-step example on how to cross-assemble and run an assembly program using the xdt99 tools and the MESS emulator please refer to the Example Usage section below.

Finally, please note that even though the object code format of the TI 99 home computer shares many similarities with that of other TMS9900-based systems, most notably the TI 990 mini computers, xas99 currently targets the TI 99 exclusively.

Assembling Source Code

The xas99 cross-assembler reads an assembly source code file and generates an uncompressed object code file that is suitable for the original TI 99 Editor/Assembler loader.

$ -R ashello.a99
$ -R ashello.a99 -o HELLO-O

The output parameter -o may be used to override the default output filename.

The assembly options -R (register symbols), -S (symbol table), -L (listing), and -C (compressed object code) correspond to the respective options of the TI Editor/Assembler module. You will need to add -R if you prefer to write MOV R0,*R1+ etc. instead of MOV 0,*1+ in your source code.

xas99 will report any errors to stderr during assembly. Note that the generated object code may differ from the code generated by the Editor/Assembler package in the case of errors. xas99 is slightly more permissive than the Editor/Assembler, but it should be able to assemble any source that the Editor/Assembler package can assemble.

The assembler may also issue a number of warnings, as shown in this example:

val  equ 0
     mov  r0, >000a    ; Treating as register, did you intent an @address?
     ci   r1, r2       ; Register used as immediate operand
     b    @lab         ; Possible B/JMP optimization
     data 0
lab  mov  @val(r1)     ; Using indexed address @0, could use *r1 instead
cons data 1            ; Unreferenced symbol (summary at end of assembly)

Warnings are also written to stderr. Option -w disables all warnings.

Creating Program Images

The image parameter -i tells xas99 to generate image files that can be loaded using Editor/Assembler option 5.

$ -R -i ashello.a99

Images larger than 8 KB are split automatically into multiple files, using the filename convention of the Editor/Assembler module.

The -i parameter simulates the SAVE utility program shipped with the Editor/Assembler package and honors the symbols SFIRST and SLAST to generate a single image for the entire memory area spanned by those two addresses. The image is automatically chunked into 8 KB files for the E/A loader.

Alternatively, if either symbol is missing, xas99 will generate separate image files for each program segment defined in the assembly source code. For example, the assembly of source file

     AORG >A000
L1   B @L2
     AORG >B000
L2   B @L1

will yield two images files of 10 bytes each instead of a single file of 4 KB.

Note that the E/A loader for program files happily loads non-contiguous image files for individual program segments even though original SAVE utility does not support this feature.

For further control about the memory regions to save see the SAVE directive below.

The optional --base argument can be used to define the base address for relocatable code. If no base address is given, default address >A000 is used.

All the usual restrictions for program images apply. In particular, the first word of the first image file must be an executable instruction.

Creating MESS Cartridges

The cartridge parameter -c tells xas99 to create an RPK cartridge file that can be used with the MESS emulator.

$ -c -R ascart.a99 -n "HELLO WORLD"

The optional name parameter -n overrides the default name of the program that shows up in the TI 99 menu selection screen.

The resulting RPK archive is a ZIP file containing the actual program code plus various information for the MESS emulator on how to execute the program. Typically, RPK files are passed as arguments to the MESS executable, or they may be mounted while running MESS using the emulator on-screen menu.

$ mess64 ti99_4ae -cart ascart.rpk

When the -c option is given, xas99 will automatically generate suitable GPL header information and relocate the program to address >6030, but it will not process the source code any further. In particular, the usual restrictions on using VDP memory access routines apply.

Note that cartridge files cannot be generated from split image files.

Creating Raw Binaries

Image files for the E/A loader option 5 contain the actual program code that is loaded verbatim into memory. They also contain a small amount of metadata that instructs the loader how many files to load and where to store the data.

The binary parameter -b tells xas99 to generate raw binary files without metadata that are suitable for burning EPROMs or supplying other devices.

$ -b -R ascart.a99 --base 0x6000

The assembler will generate one binary file per code segment. For further control, the SAVE directive may be used (see below).

The optional --base argument sets the base address for relocatable segments; if no argument is given, >0000 is used.

Creating Text Files

The text parameter -t generates a textual version of the raw binary generated by -b. Followed by a, it creates BYTE or DATA instructions to use in assembly or GPL; by b, it creates DATA instructions to use in BASIC; by c, it creates a list of hex values to use in C/C++ arrays. Including 2 or 4 in the value generates bytes or words, respectively. For target platforms with different endianness, include r to swap the bytes in words. As an example,

$ -t a2 -R example.a99

results in an assembly file with AORGs and BYTE directives:

;  aorg >1000
   byte >04, >c0, >c0, >81, >04, >60, >20, >00
;  aorg >2000
   byte >02, >e0, >83, >e0, >04, >d0

The result can be COPYed, #included, or just copy-and-pasted.

Other Formats

For relocatable code not larger than around 24 KB, xas99 can generate an Extended BASIC program that invisibly contains the generated code within:

$ --embed-xb ashello.a99

The resulting program is a regular Extended BASIC program in so-called "long" format that will execute the assembly code when run:


Thus, the --embed-xb options allows for the creation of assembly programs that do require the Editor/Assembler module for execution.

The generated Extended BASIC program will have only one visible line:

):: CALL LOAD(8196,63,248)::

But be careful: editing the generated program is likely to corrupt the embedded assembly code!

Creating List Files

The -L option instructs xas99 to generate a list file for the assembled source code:

$ -R ashello.a99 -L ashello.lst

The list file is useful for inferring the relative or absolute memory addresses of source code lines or the effective values of expressions.

The format of the list file is almost identical to that of the original Editor/Assembler module. The most prominent difference is the listing of the BYTE directive, where individual byte values are grouped into words.

TMS9900 Assembly Support

The xas99 is a complete TMS9900 assembler supporting all documented TMS9900 opcodes. TMS9995 opcodes such as MPYS and DIVS are currently not supported but may be added in a future release.

xas99 understands all assembler directives described in the Editor/Assembler manual that are supported by both TI 99 assembler and loader, i.e.,


Note that the DORG directive is supported, even though the TI assembler does not do so.

The following directives are not supported by the TI 99 loader and are thus silently ignored by xas99:


Source Code Organization

The COPY directive is used to break large assembly sources into individual files.

xas99 will search the current source directory for appropriately named source files. For example, assembling

$ src/file1.a99

where file1.a99 contains the instruction COPY "DSK1.FILE2" will search for include files


and its corresponding lower-case variants.

Additional search paths may be specified with the -I option as a comma-separated list, e.g.,

$ -I lib/,disk2/ ashello.asm

COPY also supports native file paths, e.g., COPY "ti/src/file2.a99".

xas99 also provides a new directive BCOPY that includes an external binary file as a sequence of BYTEs. Please refer to the section about xdt99 Extensions for further information.

xdt99 Extensions

The xas99 cross-assembler offers various "modern" extensions to the original TI Assembler to improve the developer experience for writing assembly programs. All extensions are backwards compatible in virtually all situations of practical relevance so that any existing source code should compile as-is.

Comments may be included anywhere in the source code by prepending them with a semicolon ;. A ; character inside a text literal '...' or filename "..." does not introduce a comment.

Source code is processed case insensitively so that all labels, expressions, and instructions may be written in upper case, lower case, or any mixture. Text literals are still case sensitive, though.

label1 byte >A,>b
LABEL2 TEXT 'Hello World'
Label3 mov Label1(R1),Label2(r2)

Labels may be of arbitrary length and may contain arbitrary characters except for whitespace and operators such as +, *, (, $, etc. An optional colon : may be appended to the label name. The colon is not part of the name, but logically continues the current line to the next:

    equ 1         ; assigns 1 to my_label_1
    aorg >a000    ; assigns >a000 to my_label_2
my_label_3        ; assigns >a000 to my_label_3  \  standard E/A
    aorg >b000    ; no label to assign >b000 to  /  behavior

Local labels simplify the implementation of small loops. A local label is introduced by an exclamation mark ! and an optional name. Thus, the simplest local label is just a single !. Local labels need not be unique within the program.

References to local labels are resolved relative to the current position. By default, matching labels are searched after the current position. References prefixed with a unary minus sign - are searched before the current position.

    li   r0, >a000
    li   r2, >100
!   clr  *r0+         ; make jump target without potential name conflicts
    dec  r2
    jne  -!           ; jump to target two lines above

Doubling, tripling, ... the number of !s before a reference refers to the second, third, ... match of the local label relative to the current position:

!   dec  r1              <-+
    jeq  !     --+         |
    inc  r2      |         |
    jne  !!      |  --+    |
    jmp  -!      |    |  --|
!   dec  r2    <-+    |    |
    jmp  -!!          |  --|
!   inc  r1         <-+    |
    jmp  -!!!            --+
!   rt

Note that labels label and !label are entirely different and can be used without conflict in the same program.

The use of whitespace has been relaxed. Single spaces may be used judiciously within the operand field to increase the legibility of expressions. Two or more spaces as well as tabs introduce the comment field.

label  data addr + len - 1  comment
       movb @addr + 2(r1), *r2+ ; comment

(Technical note: It is not possible to detect the beginning of the comment field based on the current instruction, as the example LABEL EQU 1 * 2 shows. The original TI Assembler parses * 2 as comment, even though 1 * 2 is a valid EQU expression.)

The extended expression syntax supports parentheses (, ), the modulo operator %, and binary operators bit-and &, bit-or |, bit-xor ^, and bit-not ~ as well as binary literals introduced by :.

area    equ (xmax + 1) * (ymax + 1)
addr2   equ addr1 | >A000 & ~>001F
padding bss size % 8
binval  equ :01011010

It is important to note that all operators have the same precedence, i.e., an expression such as 1 + 2 * 3 - 4 & 5 evaluates as (((1 + 2) * 3) - 4) & 5. This may sound annoying, but changing the established order of evaluation would break the compatibility of xas99 for existing sources. To adjust the order of evaluation, parentheses can be used: 1 + (2 * 3) - (4 & 5).

xas99 features a number of so-called modifiers that apply to symbols, literals, or registers.

Many programs use byte or word constants, e.g., for MOV/MOVB or C/CB instructions when immediate values are not available or feasible. A common problem then is to keep track of all used constants. xas99 assists the developer here by warning about unused constants (see Warnings).

A convenient alternative is to use auto-generated constants with modifiers b# and w#. As an example,

    mov  w#>ff01, @status
    socb b#81, r1
    cb   @keycode, b#Q

is equivalent to this code without modifiers:

    mov  @h_ff01, @status
    socb b_81, r1
    cb   @keycode, @b_81
    data >ff01
    byte 81   ; note that 'Q' == 81

For a word character constant such as 'w#A', a zero byte is appended to the generated byte, e.g., >4100.

The assembler ensures that each value is added only once, so for constants b#>41, b#65, and b#A, it will add only one byte to the code.

Please note that the constants must be literals, i.e., constructs like

msk equ >5555
    xor w#msk, r0

are not valid. For these case, use regular named constants instead.

The assembler logically appends all auto-generated constants to the end of the source code. So if we need to place some data after the code

    save >2000->2fff

    aorg >2000
    movb b#1, @acc

    aorg >2ffc
    data >8300
    data start

using auto-generated constants, we simply reverse the order of our sections

    save >2000->2fff
    aorg >2ffc

    aorg >2000
    ; auto-generated constants follow

so that the constants are appended to the >2000 chunk now.

All auto-generated constants will also appear in the list file.

The new register LSB modifier l# represents the address of the LSB of the modified register. In this code example,

    lwpi >8300
    movb l#r0, @vdpwa
    movb r0, @vdpwa

the expression l#r0 resolved to @>8301, i.e., the LSB of register 0. The modifier is relative to the workspace pointer (WP) so that

    lwpi >8300
    movb l#r1, r0
    lwpi >2000
    movb l#r1, r0
    lwpi >83e0
    movb l#r1, r0

reads >8303, >2003, and >83E3, respectively.

Caution! l# uses the syntactically most recent LWPI statement. When using multiple workspaces or inside BLWP subroutines, make sure that the workspace is still set correctly, or don't use 'l#' at all.

Finally, the symbol size modifier s# returns the size of the label it is attached to. Size here means the number of bytes from this symbol to the subsequent symbol in the source code.

     li   r0, 320
     li   r1, text1
     li   r2, s#text1   ; s#text1 == 12
     bl   @vmbw
    text 'HELLO WORLD!'
    text 'GOOD BYE!'

In this example, s#text1 equals 12, since there are 12 bytes from text1 to text2.

The size modifier detects if the last byte of the range is a padding byte and subtracts it from the size.

    text 'HELLO WORLD'
    text 'GOOD BYE!'

Here, s#text1 equals 11, even though there are still 12 bytes from text1 to text2. Please note that s# only applies to labels; symbols created by EQUs are not supported.

The cross-bank access modifier x# enables cross-bank symbol access. For a detailed description on x#, see the paragraphs on bank switching.

xas99 also provides new directives. The BCOPY directive includes an external binary file as a sequence of BYTEs. For example, if sprite.raw is a raw data file containing some sprite pattern

$ hexdump -C sprite.raw
00000000  18 3c 7e ff ff 7e 3c 18                           |.<~..~<.|

then including this file with BCOPY

SPRITE  BCOPY "sprite.raw"

is equivalent to the conventional assembly statement sequence

SPRITE  BYTE >18,>3C,>7E,>FF,>FF,>7E,>3C,>18

The STRI directive is similar to TEXT, but prepends the length of the string. In other words,


is equivalent to


The FLOA directive stores a decimal number in the 8-byte RADIX-100 format used by the TI 99. Note that digits exceeding the accuracy of RADIX-100 are silently ignored.

FLOA 123.456789012

The exponent notation 1e9 is currently not supported.

The BANK directive specifies the memory bank for the following code segment, or a shared code segment if the special value ALL is used. Banks count from zero.

      AORG >6000
      BANK ALL
      BANK 1
FUNC2 LI  R1,>1234

Note that the optional second argument of the AORG directive to specify the current bank is now deprecated, and might be removed in a future version of xas99.

Generating binary files with the -b command stores banked segments in separate files, e.g.,

$ -b asbank.a99
$ ls
asbank.a99  asbank_6000_b0.bin  asbank_6000_b1.bin

xas99 warns about illegal cross-bank accesses in address arguments, but access from and to shared code segments are not checked.

      AORG >6000
      BANK 0
L1    B    @L3      ; OK
      B    @L2      ; error: different bank

      BANK 1
L2    B    @L3      ; OK
      B    @L1      ; error: different bank

      AORG >7000
      BANK ALL
L3    B    @L1      ; OK, implies correct bank is present
      B    @L2      ; OK, implies correct bank is present

In this example, the B instructions in segment >7000 will branch to L1 or L2, depending on which bank is active.

To override the cross-bank check explicitly, e.g., because the caller will be relocated to a different memory address during runtime, the cross-bank modifier x# can be prepended to the offending label.

      BANK 0

      BANK 1
      B    @X#CONT  ; OK, no error

The new XORG directive sets the location counter to a new address but does not change the actual placement of the subsequent code segment.

      AORG >6000
L1    DATA 0
      MOV  @L1,@L2      ; moves >6000 to >8380
      BL   @FUNC        ; branches to >8382

A1    XORG >8380
L2    DATA 0
FUNC  A    @L2,@L1      ; adds >8380 to >6000

The list file for this program shows that the code of FUNC is placed within the >6000 segment:

0001                      AORG >6000
0002 6000 0000     L1     DATA 0
0003 6002 C820  54        MOV  @L1,@L2      ; moves >6000 to >8380
     6004 6000
     6006 8380
0004 6008 06A0  32        BL   @FUNC        ; branches to >8382
     600A 8382
0006               A1     XORG >8380
0007               A2
0008 600C 0000     L2     DATA 0
0009 600E A820  54 FUNC   A    @L2,@L1      ; adds >8380 to >6000
     6010 8380
     6012 6000
0010 6014 045B  20        RT
0011               A3

XORG is useful for assembling code blocks that will be moved to a different memory location, e.g., scratch pad RAM, before execution.

INIT  LI   R0,A1        ; source address of function
      LI   R1,A2        ; target address of function
      LI   R2,A3-A2     ; length of function to copy
      BL   @COPY        ; fictious RAM to RAM copy routine

Note that xas99 cannot place XORG code directly into the indented target location; instead, all such blocks need to be copied manually in your program. For this, XORG assigns the real placement address instead of the location counter to its label. Depending on the previous segment this address may be relocatable or not.

The XORG directive can be used for all xas99 output formats and is compatible with both E/A option 3 and E/A option 5.

The SAVE directive controls the output format for the image -i and raw binary -b output formats.

      SAVE >6000,>7000   ; generate single image for >6000->6FFF

      AORG >6000
      AORG >6100

For each SAVE, a binary file containing the values of the specified memory region will be generated. If the region contains banked memory, a separate file for each bank is written. Empty regions, i.e., regions in which no actual value is placed, are skipped.

If no SAVE directives are provided, the -b command will place each segment in its own output file, but merges adjacent segments into one file. The -i command will save the region between symbols SFIRST and SLAST, if present; otherwise, it generates images for each segment individually.

The use of SAVE is recommended to reduce the number of generated files if XORG is employed.

The source code preprocessor allows for conditional assembly based on well-defined conditional expressions. The preprocessor commands .ifdef and .ifndef check if a given symbol is defined or not.

       .ifdef lang_de
msg    text 'Hallo Welt'
msg    text 'Hello World'

The commands .ifeq, .ifne, .ifgt, and .ifge test if two arguments are equal, not equal, greater than, or greater than or equal, resp. If the second argument is missing, the first argument is compared against value 0.

Conditional assembly preprocessor commands may be nested. Valid conditional expressions and their rules of evaluation correspond to those of the EQU directive.

The .print preprocessor command prints its arguments to stdout.

val    equ 42
       .print 'Selected answer is', value

The .error command prints a message and aborts the assembly.

       aorg >6000


       .ifgt $, >7fff
       .error 'Catridge program too large'

In addition to symbols defined by labels, xas99 also sets exactly one of _xas99_image, _xas99_cart, _xas99_obj, _xas99_xb, or _xas99_js, depending on the assembly command -i, -c, ... used.

Additional symbols may be supplied on the command line.

$ ashello.a99 -D symbol1 symbol2=2

If no value is given, the symbol is set to value 1.

The symbol option -E dumps all symbols in EQU-like syntax to an external file sym.a99:

$ ashello.a99 -E sym.a99

which then reads:

       equ  >002E  ; REL    <-- was relocatable address
       equ  >837C  ;
       equ  >8375  ;
       equ  >005C  ; REL

xas99 supports macros. The .defm preprocessor command introduces a new macro. The .endm command concludes the macro definition. Inside the macro body the macro parameters #1, #2, ... refer to the actual arguments that are supplied when instantiating the macro:

* fill top <#1> rows with char <#2>
    .defm fill
    li   r0, >0040
    li   r1, #1
    li   r2, #2 * 32
    movb @vdpwa
    swpb r0
    movb @vdpwa
!   movb r1, @vdpwd
    dec  r2
    jne  -!

Macros are used like preprocessor commands, with any arguments separated by commas:

    .fill 10, '* '

Note that macro parameters are resolved by textual replacement. Thus, when instantiating

    li   r0, 2 * #1

inside some macro body with argument 1 + 2, the resulting code will assign the value 4 instead of 6 to R0.

Labels are allowed inside macro definitions. To avoid duplicate symbols, all labels should be local.

Macro definitions cannot be nested. Macro uses may be nested, but instantiations must not be circular.

Preprocessor commands are always executed, even inside inactive #ifdef ... #endif blocks. The correct way to define environment-dependent macros is thus

.defm mymacro
.ifdef symbol
clr r0
clr r1

instead of the other way around.

xas99 supports the F18A GPU instruction set.

CALL <gas>
PUSH <gas>
POP  <gad>
SLC  <wa>, <count>
PIC  <gas>, <gad>

Note that PIC is not an immediate instruction, so its arguments need to be constructed separately by hand.

The SPI family of instructions is not supported; please use their equivalents CKON, ... instead.


The strictness option -s disables all xas99-specific extensions to improve backwards compatibility for old sources:

$ -s ashello.a99

Strictness is required, for example, to compile the Tombstone City sample source code shipped with the original TI Editor/Assembler module. Some of the comments do not adhere to the two-space separator rule of the relaxed xdt99 whitespace mode:

***** Unknown symbol: REGISTER 5 LOW BYTE.

Finally, note that case insensitivity cannot be disabled at the moment.

xga99 GPL Cross-Assembler

The xga99 GPL cross-assembler translates programs written in TI's proprietary Graphics Programming Language into byte code that can be interpreted by the TI 99 home computer.

Invoking xga99 in standard mode will assemble a GPL source code file into plain GPL byte code that may be placed in a physical or emulated GROM or GRAM device.

$ gahello.gpl
$ gahello.gpl -o gahello.bin

The output parameter -o may be used to override the default output filename, which uses extension .gbc (for "GPL byte code").

The image parameter -i tells xga99 to generate suitable GPL header data for the program so that the GPL byte code interpreter built into the TI 99 can execute the image file.

$ -i gahello.gpl

xga99 will check if there is enough space at the beginning of the image file for inserting GPL header data. You may have to adjust (or remove) any AORG directives if there is not enough space available.

Program execution will start at the symbol provided with the END directive, or the START symbol, or the first byte of the byte code, in this order.

The cartridge parameter -c relocates the GPL program to the cartridge GROM area, generates GPL header data, and packages the byte code image into a cartridge file suitable for the MESS emulator.

$ -c gahello.gpl

The resulting .rpk file may be executed as-is by the MESS emulator:

$ mess64 ti99_4ae -cart gahello.rpk

The optional name parameter -n overrides the default name of the program that shows up in the TI 99 menu selection screen.

$ -c gahello.gpl -n "HELLO GPL WORLD"

The optional list file parameter -L creates a list file of the source that shows the addresses and their byte values for each source line.

$ gahello.gpl -L gahello.lst

When -L is given, the symbol dump parameter -S includes the symbol table in the list file.

As the Graphics Programming Language was never intended for public release, existing native tools for assembling GPL source code differ substantially in the language syntax they support. xga99 adopts a combination of the Ryte Data GPL Assembler and the RAG GPL Assembler syntax as its native format. Other syntax styles may be chosen with the syntax parameter -y.

$ sdemo.gpl -y mizapf

Currently supported syntax styles are the default xdt99 for the xga99 native format, and mizapf for the TI Image Tool GPL disassembler. Note that the original GPL syntax described in TI's GPL Programmer's Guide was considered too arcane for inclusion in xga99.

The native xdt99 syntax style is more "modern" in that it supports lower case sources and relaxes the use of whitespace. For details, please refer to the respective section of the xas99 manual.

GPL Instructions

xga99 supports all GPL mnemonics described in the GPL Programmer's Guide. As is common practice, however, the operand order for the shift instructions SLL etc. has been reversed from Gd, Gs to Gs, Gd.

Instruction operands use the well-established prefix notation to address CPU RAM, VDP RAM, and GROM/GRAM, respectively:

@<cpuram addr>  ....................  CPU RAM direct
*<cpuram addr>  ....................  CPU RAM indirect
V@<vdpram addr>  ...................  VDP RAM direct
V*<cpuram addr>  ...................  VDP RAM indirect
@<cpuram addr>(@<cpuram addr>)  ....  CPU RAM indexed
V@<vdpram addr>(@<cpuram addr>)  ...  VDP RAM indexed
G@<grom addr>  .....................  GROM/GRAM direct   (MOVE only)
G@<grom addr>(@<cpuram addr>)  .....  GROM/GRAM indexed  (MOVE only)
#<vdp reg>  ........................  VDP register       (MOVE only)

Note that symbols do not imply a certain memory type, so references to GROM addresses in MOVE instructions still need to prepend G@ to the symbol name:

t1 text 'HELLO'
l1 move 5,g@t1,v@100

For branch and call instructions, prefixing addresses by G@ is optional, as branch targets always reside in GROM/GRAM:

b l1
b G@l1

Instruction operands may be complex expressions of symbols and literals. Literals may be decimal numbers, hexadecimal numbers prefixed by >, binary numbers prefixed by :, and text literals enclosed in single quotes '.

byte 10, >10, :10, '1'

Expressions are built using arithmetical operators +, -, *, /, %, and ** and bit operators &, |, ^, and ~. Expressions are evaluated left-to-right with equal operator precedence; parentheses may be used to change the order of evaluation. For further details please refer to the xas99 section on expressions.

By default, xga99 uses the following mnemonics for the FMT sub-language, but other syntax styles are available with the -s option:

HCHAR/VCHAR <count>, <char>
HSTR <count>, <addr>        (no GROM, no VDP, no indexing, no indirection)
ROW/COL <count>
ROW+/COL+ <count>
BIAS <count/gs>
FOR <count> ... FEND [<label>]

All arguments are immediate values, unless noted.

Note that unlike the Ryte Data GPL Assembler, xga99 also supports the optional address label for the FEND instruction.

GPL Directives

The xga99 GPL assembler supports the following directives:


Directives affecting listing generation are currently ignored:


Most xga99 directives work very similar to their xas99 counterparts.

The BYTE and DATA directives insert bytes and words into the program, respectively, irrespective of the size of their arguments.

label byte 1,>02,:11011010,'@',>100
      data 1,>1000,'A'

The TEXT directive generates a sequence of bytes from a text literal or an extended hexadecimal literal.

label text 'Groovin'' With GPL'
      text >183C7EFFE7C381

Note that the second instruction is equivalent to BYTE >18,>3C,>7E,....

The STRI directive works similar to the TEXT directive but prepends the a length byte to the generated byte sequence.

The FLOAT directive stores a decimal number in the 8-byte RADIX-100 format used by the TI 99. Note that digits exceeding the accuracy of RADIX-100 are silently ignored.

float -123.456789012

The exponent notation 1e9 is currently not supported.

The GROM directive sets the GROM base address for the code that follows. You can specify either the GROM number 0, ..., 7, or the absolute address >0000, ..., >e000, where bits 0-12 are ignored.

If more than one GROM directive is placed in one program, each GROM segment will be placed in a separate file, whose name is appended with the address of that segment (similar to xas99 with multiple AORG directives).

The AORG directive is used to place individual code segments at specific addresses within the given GROM. The address argument is thus relative to the GROM base address given by GROM.

Instead of using the GROM and AORG directives in the source file the location of the final GPL byte code image may also be specified by command-line parameters -G and -A, respectively. The cartridge parameter -c implies -G 0x6000 and -A 0x30. -G and -A will not override GROM or AORG directives, but set the GROM and address of the first line of the code.

The COPY and BCOPY directives include text files or binary files, respectively. A text file becomes part of the assembly source, whereas a binary is included verbatim into the byte code.

xdt99 Extensions

The xga99 GPL cross-assembler offers various "modern" extensions to the original TI GPL specification to improve the developer experience for writing GPL programs. All extensions are backwards compatible in virtually all situations of practical relevance so that any existing source code should compile as-is.

The xas99 extensions regarding comments, labels, whitespace, and expressions also apply to xga99.

The source code preprocessor supports conditional assembly .ifdef and macros .defm. For a description of both features please refer to the respective section in the xas99 manual.

xga99 supports macros. Note, however, that GPL macros use macro parameters $1, $2, ... instead of #1, #2, ..., as the # sign is used to denote VDP registers in GPL.

The predefined symbols set by xga99 are _xga99_image, _xga99_cart, or _xga99_gbc, depending on the assembly command -i, -c, ... used.

Local labels are non-unique source-relative labels that begin with an exclamation mark !. For details about local labels, please refer to the Local labels section of the xas99 cross-assembler manual.

The command line options -D to define additional symbols and -E to dump all symbols in EQU format to an external file work similar to their counterparts in xas99.

xda99 Disassembler

The cross-disassembler xda99 is a command-line tool to convert machine code into assembly source code.

To disassemble a binary machine code file, we need to tell the disassembler the first address of the machine code with parameter -a and the starting address for the disassembly with -f:

$ ascart_6000.bin -a 6000 -f 600c

All command line values are interpreted as hexadecimal values. They can optionally be prefixed by > or 0x.

The strict option -s generates all output files in legacy Editor/Assembler format.

The resulting file has the same name as the binary file but ends in .dis. It contains the disassembled instructions in a list file-like list:

            aorg >6000
6000 4845?
6002 4c4c?
6004 4f20?
6006 4341?
6008 5254?
600a 2100?
600c 0300   limi  >0000
600e 0000
6010 02e0   lwpi  pad
6012 8300
6014 04c0   clr   r0

The output option -o redirects the output to a different file, or prints to stdout when using the special filename -.

The area to disassemble can be specified with the from parameter -f and the optional to parameter -t.

The skip option -k skips some prefix of the binary to disassemble. For example, when disassembling an E/A option 5 binary, use -k to skip the 6-byte header:

$ program5.bin -k 6 -a a000 -r a000

Machine code consists of both code and data segments, which are often intermingled. Without context information, however, a disassembler cannot tell data from code.

Using xda99 with the from parameter -f will start the disassembly in top-down mode, which disassembles the machine code sequentially word by word. As stated above, this mode generally yields bad results, as data segments will be translated into accurate, but meaningless statements. This can be seen with above example by changing the from parameter to -f 6000:

            aorg >6000
6000 4845   szc   r5, @>4c4c(r1)     |
6002 4c4c                            |  data erroneously
6004 4f20   szc   @>4341, *r12+      |  disassembled into
6006 4341                            |  source code
6008 5254   szcb  *r4, r9            |
600a 2100   coc   r0, r4             |
600c 0300   limi  >0000
600e 0000

Even worse, disassembling data into nonsense statements can spill over to the real code if the last data word is assembled into a two-word instruction:

    aorg >a000
    byte 4, 224
    lwpi >8300
    limi 0

Disassembling the machine code generated by above program with -f a000 yields

            aorg >a000
a000 04e0   clr  @>02e0        |  disassembled data
a002 02e0                      |  swallowed the LWPI
a004 8300   c    r0, r12       |  instruction
a006 0300   limi >0000
a008 0000

If the data segments are known, those can be excluded from disassembly with the exclude -e option:

$ ascart_6000.bin -a 6000 -f 6000 -e 6000-600c 70e0-7f00

The upper address yyyy of an exclude range xxxx-yyyy is not included in the range, so range 6000-6000 is an empty range.

For unknown programs, exclusion of data segments is difficult. Thus, xda99 offers an additional run mode -r that recognizes static branch, call, and return statements, and disassembles only along the program flow.

$ ascart_6000.bin -a 6000 -r 600c

Note that for the ascart program, there won't be any differences between run mode and top-down mode, as code and data are separate in that program.

Run mode is not limited to one starting address:

$ suprdupC.bin -a 6000 -r 6034 603c

For convenience, the special address start denotes all start addresses derived for the given machine code. Thus, the above line becomes

$ suprdupC.bin -a 6000 -r start

If the binary is not a cartridge image, start will currently default to the beginning given by -a.

Run mode also includes jump markers as comments that show from where an instruction was branched to:

6058 d809   movb r9, @>837c
605a 837c
605c d809   movb r9, @>8374           ; <- >6068
605e 8374
6060 0420   blwp @kscan
6062 2108
6064 9220   cb   @>8375, r8
6066 8375
6068 13f9   jeq  >605c
606a d020   movb @>8375, r0
606c 8375

The program option -p turns the disassembly into actual source code that can be re-assembled again:

       aorg >6000
vdpwd  equ  >8c00
pad    equ  >8300
gpllnk equ  >2100
vdpwa  equ  >8c02
l6000  data >4845
l6002  data >4c4c
l6004  data >4f20
l6006  data >4341
l6008  data >5254
l600a  data gpllnk
l600c  limi >0000
l6010  lwpi pad
l6014  clr  r0

The -p options will also include an EQU stanza of all symbols used, in this case all xas99 internal symbols that were referred (REF) by the program.

To use more symbols, a symbol file can be supplied with the -S parameter. The symbol file can be generated with the EQU option -E of xas99, which is admittedly a rare case in practice, or written manually in fairly free form, e.g.,

s1 equ >10
        equ 10
s3 >10
s4: 0x10

Data segments often contain strings, that can be restored heuristically by using the string option -n, either with or without the -p option.

            aorg >6000
6000 4845   text  'hello cart'
6002 4c4c
6004 4f20
6006 4341
6008 5254
600a 2100?
600c 0300   limi  >0000
600e 0000
6010 02e0   lwpi  pad

Option -n is only useful in run mode, as top-down mode will not leave behind any data segments, where strings could be found.

Note that currently, xda99 only disassembles even length strings.

The concise option -c ignores all non-disassembled addresses in the output by merging those addresses marked by ? and replacing them by .....

            aorg >2000
2000 1008   jmp  >2012
2012 c481   mov  r1, *r2
2014 05a2   inc  @>0002(r2)
2016 0002
2018 2881   xor  r1, r2
201a 1309   jeq  >202e
201c 10fa   jmp  >2012

Options -c and -p cannot be combined.

The strict option -s generates output files in legacy Editor/Assembler format, in particular upper-case.

The register option -R tells the disassembler to use plain integers for registers, i.e., to not prepend registers with R.

Run Mode and Conflicts

When the run mode disassembler hits an address which has already been disassembled, it stops the current run. This regularly happens for multiple calls to a subroutine, loops, or recursion. The disassembler hitting an address where it has previously disassembled a statement is perfectly normal and correct.

But run mode is not always 100% accurate, as xda99 cannot follow indirect branches such as B *R1, and doesn't know if a condition for JEQ LABEL is always true and thus has no alternate path. (The latter remark is more relevant for xdg99, where BR is often used as a shorter B.) As a consequence, a run may "run off", and worse, different runs may try to disassemble the same range differently:

               First run,              Second run,
               starting @>6000         starting @>6002

               aorg >6000              aorg >6000
6000 c820      mov  @pad, @>831c                         |
6002 8300                              c    r0, r12      | disagreement
6004 831c                              c    *r12, r12    |
6006 0a51      sla  r1, 5              sla  r1, 5
6008 1620      jne  >604a              jne  >604a

Above, the second run hit an address that is only part of a previously disassembled address (i.e., an operator), which raises a conflict about which version is correct.

The default behavior of xda99 is to stop the run, leaving the previous disassembly untouched. You can override the default with the force option -F, which will always overwrite previous results. This is done cleanly, so that run 2 above will reset the overridden instruction at address @>6000.

There is no recommendation to disassemble with or without override. The result of each disassembly may vary with each binary, and should be tried out.

xdg99 GPL Disassembler

The GPL disassembler xdg99 is a command-line tool to convert GPL bytecode into GPL source code.

xdg99 shares almost all options with xda99, and works very similar. In fact, at some point in the future, both programs might be merged into one.

To show the similarities,

$ gacart.bin -a 6000 -f 6030

disassembles bytecode file gacart.bin, i.e., a GROM file, into GPL instructions:

          grom >6000
          aorg >0000
6000 aa?
602f 00?
6030 07   all   >20
6031 20
6032 04   back  >04
6033 04
6034 be   st    >48, v@>0021
6035 a0
6036 21
6037 48

The only option that xdg99 features over xda99 is the syntax selection option -y, which is already known from xga99:

$ gacart.bin -a 6000 -f 6030
6206 31   move >0010, g@>6ec4, v@>0033

$ gacart.bin -a 6000 -f 6030 -y mizapf
6206 31   move >0010 bytes from grom@>6ec4 to vdp@>0033

At the same time, the -R option of xda99 has no meaning for GPL, and thus is not supported by xdg99.

xbas99 TI BASIC and TI Extended BASIC Tool

xbas99 is a command-line tool for converting TI BASIC and TI Extended BASIC programs from source format to internal format, and vice versa. For brevity, we will refer to both TI BASIC and TI Extended BASIC programs simply as BASIC programs.

Programs in source format are plain text files that contain the BASIC statements that a user would usually type in. These kind of text files are usually not stored on real TI 99 home computer systems.

Programs in internal format are TI-specific files in PROGRAM format that are generated by the SAVE command and understood by the OLD and RUN commands. xbas99 also supports programs created in so-called long format INT/VAR 254 and in merge format DIS/VAR 163.

Typical use cases for xbas99 include the listing of programs stored in internal format and the creation of program files for the BASIC interpreter from a text file with BASIC statements.

The list command -l lists the statements of a BASIC program in internal format on the screen. Formatting is identical to the built-in BASIC LIST command modulo the line wrapping.

$ -l bashello.bin
40 END

The similar decode command -d saves the program listing to a file instead:

$ -d bashello.bin

xbas99 uses extensions .bas for BASIC programs in source format and .bin for programs in internal format. To override the default naming convention, the -o argument may be used:

$ -d bashello.bin -o sample_program.txt

BASIC programs in long format are detected automatically. To list programs in merge format, simply add the merge option --merge.

(Technical note: On Windows, you currently cannot use xdm99 to extract BASIC programs saved in merge format. For some unknown reason merge programs are stored in DIS/VAR format even though they are binary data. Windows will try to translate suspected end-of-line markers and thus garble the file contents.)

The create command -c encodes a list of BASIC statements into internal format so that the resulting file can be loaded and run by the BASIC interpreter on a TI 99:

$ -c bashello.bas

xbas99 assumes that the text file is stored in the native file format of the host computer. Each program line should be stored on a separate line, but the join option -j may be used to automatically join split, i.e., word-wrapped, lines.

$ -c bashello.txt -j <line-delta>

Any line that does not begin with a number or whose supposed line number is not between the previous line number and the previous line number plus line-delta is considered a continuation of the previous line.

The long option --long instructs xbas99 to create the program in long format. Long programs are stored within the 32 KB memory expansion and may be larger than conventional programs. For petty technical reasons, the creation of programs in merge format is currently not supported.

The protection option --protect will add list protection to the generated program. Programs with list protection cannot be listed or edited by the BASIC interpreter. Note, however, that the list command of xbas99 will /not/ honor the protection flag.

Program files created by xbas99 are raw PROGRAM files that may have to be transferred to a disk image or converted to TIFILES format before they can be used by an emulator or transferred to a real TI 99. The xdm99 tool covers both of these operations:

$ basic.dsk -a bashello.bin
$ -T bashello.bin

Advanced users of xdt99 may also combine the creation of the BASIC program file and the transfer to a disk image in one single step:

$ -c bashello.bas -o - | basic.dsk -a - -n HELLO

All tools in xdt99 follow the convention that the special filename - denotes stdin or stdout, respectively. You can also pipe from xdm99 into xbas99 to list BASIC programs quickly that are stored on a disk image:

$ basic.dsk -p HELLO | -l -

Finally, xbas99 does not distinguish between TI BASIC and TI Extended BASIC programs. To create a TI BASIC program that does not rely on the TI Extended BASIC module simply do not use any of the advanced Extended BASIC features such as :: or subprograms like SPRITE.

Also note that the tool will read and encode any text file that you supply, with only minimal syntax checking. In other words, the resulting program file should always load with OLD, but it may not RUN. A future version of xbas99 may contain more advanced checks to assist developers in creating new BASIC programs.

xdm99 Disk Manager

xdm99 is a command-line tool for cataloging and manipulating sector-based TI disk images used by most emulators, including MESS. xdm99 also supports the TIFiles file format that retains TI-specific meta data for files that originate from TI disk images.

Cataloging Disks

The default operation of xdm99 when invoked without any options is to print the file catalog of the disk image to stdout:

$ ed-asm.dsk
ED-ASSM   :     97 used  263 free   90 KB  1S/1D  40 TpS
ASSM1         33  PROGRAM       8192 B            P
ASSM2         18  PROGRAM       4102 B            P
EDIT1         25  PROGRAM       5894 B            P
SAVE          13  DIS/FIX 80    3072 B   36 recs  P
SFIRST/O       3  DIS/FIX 80     512 B    5 recs  P
SLAST/O        3  DIS/FIX 80     512 B    4 recs  P

The top line shows the name of the disk, the number of used and free sectors as well as the disk geometry. For each file, the number of used sectors, the file type, the file length, the actual number of records, and the protection status is shown. If present, the file modification time is also shown.

xdm99 will warn about any inconsistencies it may find, e.g., blocks claimed by files but not allocated in the allocation map. When assembling programs natively these inconsistencies happen more frequently than one would assume. Files affected by inconsistencies are flagged with ERR in the catalog. You can try the -R option to automatically repair disks with inconsistencies.

Extracting Files

The extract parameter -e extracts one or more files from a disk image to the local file system.

$ work.dsk -e HELLO-S CART-S

The local output filename is derived automatically from the TI filename but may be overridden with the -o parameter.

$ work.dsk -e HELLO-S -o hello.a99

If -o specifies a directory, the output is placed into that directory.

$ work.dsk -e HELLO-O HELLO-S -o hello/

When extracting two or more files, -o may only be used with a directory argument.

To print the contents of a file to stdout, the print parameter -p may also be used:

$ work.dsk -p HELLO-S

In general, printing files only makes sense for files in DIS/FIX or DIS/VAR format. Following Unix conventions, -p is equivalent to combining parameters -e and -o "-".

Filenames given by -e may be glob patterns containing wildcards * and ?. This will extract all files matching the given pattern.

$ work.dsk -e "HELLO-*"

Note that you may have to quote your glob pattern to prevent your shell from expanding the pattern prematurely.

By default, xdm99 will convert between upper and lower case when moving PC files to and from disk images. To keep extracted filenames like they were on the disk image, use the --ti-names option.

Extracting files will yield the file contents only. In order to retain file meta data about file type and record length, use the TIFiles or v9t9 formats described below.

Manipulating Disks

The add parameter -a adds local files to the disk image. xdm99 will infer a suitable TI filename from the local filename unless an explicit filename is given by the -n parameter. If the file is not of type PROGRAM, the file type must be given using the -f parameter.

$ work.dsk -a ashello.a99 -n HELLO-S -f DIS/VAR80

The syntax for -f is fairly permissible, e.g., DIS/FIX 80, DISFIX80, or DF80 all work.

When adding multiple files with the -n option, the last character of the specified filename will be incremented by one for each subsequent file, e.g.,

$ work.dsk -a intro main appendix -n FILE

will add the files as FILE, FILF, and FILG to the disk image.

The rename parameter -r renames one or more files on the disk.

$ work.dsk -r HELLO-S:HELLO/S

For each file to rename, provide the old filename, followed by a colon :, followed by the new filename.

To rename the disk itself, use the -n options without any additional arguments.

$ work.dsk -n WORK-2

The delete parameter -d deletes one or more files on the disk.

$ work.dsk -d HELLO-I HELLO-O
$ work.dsk -d "*-O"

Note that the current implementation of xdm99 will actually perform a "secure erase", i.e., the entire contents of the deleted file will be removed from the disk image.

The write protection parameter -w toggles the current protection status of the given files.

$ work.dsk -w HELLO HELLO-CPY

Note that file protection is solely for TI 99 systems and emulators but will be ignored by xdm99.

File operations do not retain the overall sector structure of the disk. In particular, all files will be defragmented whenever files are added or deleted with -a or -d, respectively, or when the disk is repaired with -R. Simply cataloging the disk, however, will not modify the disk image.

By default, all modifying disk operations will change the disk image directly. To create an independent copy of the original disk image with the changes applied, the -o parameter may be used.

Files in a Directory (FIAD)

Extracting files from a TI disk image to the local file system will lose certain TI-specific file information, such as the file type or the record length. In order to retain this meta information along with the file contents, the v9t9 and TIFiles formats were created. The approach of storing TI files directly on the local file system instead of using a disk image is also known as "files in a directory" (FIAD).

xdm99 supports the TIFiles format and the v9t9 format for FIAD files by using the -t and -9 options, respectively. To extract a file in either FIAD format, simply add -t or -9 to the extract operation:

$ work.disk -t -e HELLO-S
$ work.disk -9 -e HELLO-S

By default, files extracted in TIFiles or v9t9 format will have extension .tfi or .v9t9, respectively.

To add a file in TIFiles format or v9t9 format, add -t or -9 to the add operation:

$ work.disk -t -a hello-s.tfi
$ work.disk -9 -a hello-s.v9t9

Note that for safety reasons xdm99 will not infer the file type automatically, so adding a FIAD file without -t or -9 option will incorrectly store the file metadata as part of the file contents.

As all information about the TI filename and the TI file format is retrieved from the FIAD meta data, parameters -n and -f are ignored when used in combination with -t or -9.

xdm99 also handles "short" TIFiles used, e.g., by Classic 99.

$ work.disk -t -a HELLO-S

Short TIFiles do not store TI filename and creation date, but use the host filesystem information instead. xdm99 detects automatically if a given TIFiles file is in long or short TIFiles format.

Extracted TIFiles are always in long format, but Classic 99 can use those files just as well if the extension .tfi is removed. To simplify exchange with Classic 99, the --ti-names option will use the uppercase name without extension.

The info parameter -I displays the meta file information contained in FIAD files, while the print parameter -P dumps the file contents to stdout:

$ -I hello-s.tfi
$ -P hello-s.v9t9

xdm99 can also convert from FIAD files to plain files and vice versa without relying on disk images using the -T and -F parameters:

$ -F hello-s.tfi
$ -T hello.a99 -f DIS/VAR80 -n HELLO-S -o hello-s.tfi

Note that creating a FIAD file using the -T option usually requires information about the TI filename and the TI file type, similar to adding plain files to a disk image using -a. When converting multiple files to FIAD format, the TI filename supplied by -n is incremented automatically for each file.

FIAD file conversion -T, -F and information -I and -P infer the FIAD format used automatically, but detection may be overridden with the -t or -9 options.

Analyzing Disks

The check parameter -C analyzes a disk image for errors and prints a summary to stderr. While all disk operations, including cataloging, also check and report any disk errors found, the -C parameter restricts the output of xdm99 to those errors only.

$ -C work.dsk

The -C parameter also causes xdm99 to set its return value to non-zero for warnings, making it simple to write shell scripts for batch processing bad disk images.

The repair option -R tries to fix any disk errors, mostly by deleting erroneous files from it.

$ -R work.dsk

The repair operation is likely to cause data loss, so it's best to extract erroneous files beforehand or to specify an alternative output file with -o.

The -X or --initialize option creates a new, blank disk image, using an optional name provided by -n.

$ blank.dsk --initialize 720 -n BLANK

The size of the disk image is given by the number of sectors. You may also use a disk geometry string, which is any combination of the number of sides <n>S, the density <n>D, and an optional number of tracks <n>T, where <n> is an integer or the letters S or D. If <n>T is missing, 40T is assumed.

$ blank.dsk -X DSDD
$ blank.dsk -X 1d2s80t

Note that the disk format used by the TI 99 supports up to 1600 sectors per disk.

The special geometry CF is used for disk images for the CF7+/nanoPEB devices and corresponds to 1600 sectors.

$ volume.dsk -X cf

You can combine -X with other parameters such -a to work with the newly created image immediately:

$ work.dsk -X SSSD -a file -f DV80

The resize parameter -Z will change the total number of sectors of the disk without changing the contents of the files currently stored.

$ work.dsk -Z 720

An integer argument will not change the geometry information of the disk. To change both size and geometry, -Z also accepts a disk geometry string:

$ corcomp.dsk -Z dssd80t -o ti-80t.dsk  # convert to 80 tracks

Resizing fails if more sectors than the target size are currently in use.

The --set-geometry parameter explicitly sets the number of sides, the density, and the track information of the disk image.

$ work.dsk --set-geometry 2S1D80T

The --set-geometry command is rarely required for regular images but may be helpful for experimenting with non-standard disk image formats.

Note that the 80-track DSDD format is currently not supported.

The sector dump parameter -S prints the hexadecimal contents of individual sectors to stdout. This can be used to further analyze disk errors or to save fragments of corrupted files.

$ work.dsk -S 1
$ work.dsk -S 0x22 -o first-file-sector

For convenience, integer arguments of -S, -X and -Z may be specified in either decimal or hexadecimal notation.

xhm99 HFE Image Manager

The xhm99 HFE image manager is an extension to the xdm99 disk manager that is both a conversion tool and a manager for HFE images used by HxC floppy emulators.

Converting Images

To convert an existing disk image to an HFE image that can be copied onto an SD card and used by the HxC floppy emulator, invoke xhm99 with a single to HFE -T argument:

$ -T work.dsk [...] [-o <filename>]

This yields the file work.hfe by default. Instead of -T you may also use the long format --to-hfe.

Similarly, the from HFE -F argument converts from HFE image back to disk image:

$ -F image.hfe [-o <filename>]

This yields the file image.dsk by default. Instead of -F you may also use the long formats --from-hfe or --to-dsk.

Managing Image Contents

All options other than -F and -T are similar to those of xdm99 and operate directly on the disk image that is contained in the HFE image supplied.

To show the contents of a HFE image, simply invoke xhm99 with the HFE filename and no further arguments.

$ image.hfe
SOMEDISK  :     4 used  356 free   90 KB  1S/1D 40T  9 S/T
SOMEFILE       2  DIS/FIX 60      60 B    1 recs  2016-08-18 20:50:12

To show the contents of a file on the console, use the print argument -P.

$ image.hfe -p SOMEFILE
Hello xdt99, meet HFE!

You may also add, extract, rename, or delete files:

$ image.hfe -a manual.txt -f dv80
$ image.hfe -r MANUAL:README
$ image.hfe -e SOMEFILE -o greeting.txt
$ image.hfe -d SOMEFILE

To create a new HFE image from a single FIAD file, combine the initialize option -X with the add file argument -a and the TIFiles option -t:

$ new.hfe -X dssd -a somegame.tfi -t

You can also resize HFE images, e.g., if you want to create more free space:

$ sssd_image.hfe -Z ssdd

The resize argument -Z can even change the number of tracks, e.g., converting from DSDD with 40 tracks to DSSD with 80 tracks:

$ dsdd_image.hfe -Z dssd80t

The only format currently not supported is DSDD80T.

For further information about available arguments please refer to the xdm99 section above.

xvm99 nanoPEB Volume Manager

The xvm99 volume manager is an extension to the xdm99 disk manager that is both a conversion tool and a manager for CF card volumes used by nanoPEB/CF7+ devices.

Managing Volumes

The default operation of xvm99 when invoked without any command arguments is to print a short summary of the disk images stored in the specified volumes.

$ /dev/sdc 1-4,8
[   1]  EXTBASIC  :     4 used  1596 free
[   2]  EMPTY     :     2 used  1598 free
[   3]  SSSD      :    39 used  1561 free
[   4]  INFOCOM   :   459 used  1141 free
[   8]  (not a valid disk image)

The first argument is the name of your Compact Flash card drive, i.e., something like /dev/sdc on Linux, /dev/Disk3 on Mac OS X, or \\.\PHYSICALDRIVE2 on Windows. Caution: These are examples only; make sure to identify your CF card device correctly, or you will lose data! Also note that your user needs appropriate read and/or write permissions to access the device.

On Linux, you can use sudo fdisk -l to find the correct device name of your memory card, on Windows you can use wmic diskdrive list brief instead.

The second argument may be a single volume number or a combination of value ranges, e.g., 1,3-4,6-10. In general, if more than one volume is specified, then the command is applied to all volumes.

The -w argument writes a disk image to one or more volumes.

$ /dev/sdc 1,3 -w work.dsk

xvm99 automatically extends the disk image to match the 1600 sector format used by the CF7+ device, unless the --keep-size option is given.

The -r argument reads a disk image from a volume and stores it on the local file system.

$ /dev/sdc 2 -r vol2.dsk

When reading from multiple volumes the resulting disk images will be renamed automatically. xvm99 trims disk images to match the sector count stored in the image, unless the --keep-size option is given.

Manipulating Volumes

Most commands provided by xdm99 are also available for xvm99.

For example, to catalog a volume, you use the same -i command as for xdm99:

$ /dev/sdc 8 -i

Other commands supported by xvm99 are print files -p, extract files -e, add files -a, delete files -d, check disk -c, and repair disk -R.

Again, if more than one volume is specified, then the command is applied to all volumes. For example,

$ /dev/sdc 1-20 -a README -f DV80

adds the local file README to all disk images in volumes 1 through 20.

Example Usage

This section gives an example on how to assemble a TI 99 assembly program and run it on the MESS emulator. The commands entered and the responses shown here originate from a Linux system, but they should look very similar on Windows and Mac OS X machines.

The binary distribution of xdt99 contains an example folder with some sample files that we're going to use. For the source distribution available on Github these files are located under the test folder.

$ cd example/
$ ls -l
-rw-rw---- 1 ralph ralph  1822 Jan 10 12:51 ascart.a99
-rw-rw---- 1 ralph ralph   925 Jan 10 12:32 ashello.a99
-rw-rw---- 1 ralph ralph 92160 Jan 10 12:33 work.dsk

The file ashello.a99 contains a simple assembly program that we want to assemble and run. Since the program uses register symbols like R0 to refer to registers, we need to specify the -R option for assembly.

$ -R ashello.a99

This should yield an object code file ashello.obj that looks like this:

0007EASHELLO A0000B100DB4845B4C4CB4F20B574FB524CB4420B2020B68697F19FF       0001
A0012B7420B616EB7920B6B65B7921B0300B0000B02E0B8300B04C0B02017F2F9F          0002
A0028B2A20B0202B0300B0420B0000B0580B0602B16FBB0200B0043B02017F336F          0003
A003EC0002B0202B001AB0420B0000B0208BFF00B04C9B0300B0002B10007F31FF          0004
A0054B0300B0000BD809B837CBD809B8374B0420B0000B9220B8375B13F97F2D4F          0005
A006ABD020B8375B0980B0240B000FB0260B0700B0420B0000B10E87F410F               0006
50000SLOAD 50000SFIRST5007ESLAST 5001CSTART 30030VSBW  7F28AF               0007
30046VMBW  3007AVWTR  30062KSCAN 7F827F                                     0008
:       xdt99 xas                                                           0009

This file can be loaded with the Editor/Assembler module using option 3, or alternatively with the TI Extended BASIC module using the CALL LOAD statement.

Uncompressed object code is not an efficient program format, though. If compatibility with Extended BASIC is not required, compressed object code reduces both size and loading time:

$ -R -C ashello.a99 - ashello-c.obj

Comparing both object files we see that the compressed version is only about two thirds of the size of the uncompressed file:

$ ls -l ashello*.obj
-rw-rw---- 1 user user 486 Jul 12 09:58 ashello-c.obj
-rw-rw---- 1 user user 729 Jul 12 09:58 ashello.obj

To save even more space, we'll also generate an image file for option 5:

$ -R -i ashello.a99

This time we should get a binary file ashello.img of 132 bytes.

$ ls -l ashello.img
-rw-rw---- 1 ralph ralph   132 Jan 10 13:11 ashello.img

We now need to transfer these files to a TI disk image so that the TI 99 emulated by MESS can load it. We'll use the SS/SD disk image work.dsk that is included in the example folder of xdt99 for convenience:

$ work.dsk -a ashello.obj -n HELLO-O -f DIS/FIX80
$ work.dsk -a ashello.img -n HELLO-I
$ work.dsk
WORK      :     8 used  352 free   90 KB  1S/1D  40 TpS
HELLO-I        2  PROGRAM        132 B            2015-01-10 13:15:18
HELLO-O        4  DIS/VAR 80     755 B    9 recs  2015-01-10 13:15:10

We start MESS with our work disk inserted in floppy drive 1:

$ mess64 ti99_4ae -peb:slot2 32kmem -peb:slot8 hfdc -cart EA.rpk -flop1 work.dsk

You may have to adjust the command for starting MESS based on the location of your Editor/Assembler cartridge file. When using a graphical frontend to launch MESS, use your GUI to select the Editor/Assembler module and the disk image previously created.

On the TI 99/4A startup screen, we hit any key, then select the Editor/Assembler module. We select option 3, LOAD AND RUN, then enter the name of the object code file at the FILE NAME? prompt:


Once the loader finishes, we hit ENTER to advance to the PROGRAM NAME? prompt, and type START to start the program. The words "HELLO WORLD" should appear on screen, and hitting any key will change the color of the screen border.

When done, we quit the program by hitting FCTN =. Again we select the Editor/Assembler module, but now we select option 5, RUN PROGRAM FILE. We enter the name of the image file:


The program will start automatically once loading has completed.

If we want to learn more about the internals of our assembled program we can take a look at its list file:

$ -R ashello.a99 -L ashello.lst

This yields a text file ashello.lst that begins like this:

0001            *  HELLO WORLD
0003                   IDT 'ASHELLO'
0005                   DEF SLOAD,SFIRST,SLAST,START
0006                   REF VSBW,VMBW,VWTR
0007                   REF KSCAN
0009            SLOAD
0010 0000 100D  SFIRST JMP  START
0012      8300  WRKSP  EQU  >8300
0013      8374  KMODE  EQU  >8374
0014      8375  KCODE  EQU  >8375
0015      837C  GPLST  EQU  >837C
0017 0002 ....  MESSG  TEXT 'HELLO WORLD'
0018 000D ....         TEXT '   hit any key!'
0019      001A  MESSGL EQU  $-MESSG
0021 001C 0300  START  LIMI 0
     001E 0000
0022 0020 02E0         LWPI WRKSP
     0022 8300

The first column shows the line number of our source file. As we can see, some source file lines may produce more than one list file lines. The second and third columns show the memory location and its contents, respectively. Some directives such as EQU do not correspond to a memory location, so their second and third columns may show other relevant information instead.

To run assembly programs without the Editor/Assembler module, we finally generate our own self-contained cartridge.

First we need to assemble our source code using the -c option.

$ -R -c ascart.a99 -n "HELLO CART"

Note that we cannot run the ashello.a99 program as a cartridge, since we call VSBW and other VDP subroutines, which are unavailable without Editor/Assembler module and memory expansion. The ascart.a99 program thus uses the VDP registers directly to write to VDP memory.

We don't have to transfer the resulting RPK file to a disk image but can plug the cartridge directly into the MESS emulator:

$ mess64 ti99_4ae -cart ascart.rpk

After pressing any key on the TI 99 startup screen you should now see "HELLO CART" as the second option on the menu screen. Pressing 2 will run the sample program.

Note that the programs runs without the 32K memory expansion, as the program code is stored inside a virtual cartridge ROM.

If we want to run our sample program on a real TI 99 using the CF7+ flash drive, we need to transfer our disk image to a flash card first:

$ /dev/sdc 2 -w work.dsk

This will make our work disk from above available as volume 2 on the CF7+, where it can be accessed as DSK2 by default on the TI 99. If we don't want to replace the entire disk contents of volume 2 we could also just transfer the file instead.

$ /dev/sdc 2 -a ashello.obj -n HELLO-O -f DIS/FIX80

Either way, ashello.obj will be available as HELLO-O in volume 2 and can loaded as DSK2.HELLO-O by the Editor/Assembler module.

Building Cartridges With GPL

This subsection shows how we can use the xga99 GPL cross-assembler to assemble GPL programs into virtual cartridges that run in any TI 99 emulator. (With the right device such as a GRAM Kracker this exercise would even work on a physical TI computer!)

The example/ directory included with xdt99 contains a small GPL program gahello.gpl that sets some sprites in motion, shows a simple animation in normal graphics mode, and plays a simple tune.

If you're using the MESS emulator running the sample program is very easy:

$ -c gahello.gpl
$ mess64 ti99_4ae -cart gahello.rpk

Inside the TI 99 emulation, you'll find a menu entry for the cartridge program on the TI menu selection screen.

For other emulators you'll probably need to work with the plain GPL image file instead:

$ -i gahello.gpl -G 0x6000 -A 0x20

This yields the file gahello.bin that contains the GPL byte code with suitable header data for the GPL interpreter of the TI 99. The -G and -A options tell xga99 that we want to place the byte code into a cartridge GROM.

To run the sample program, load gahello.bin as a cartridge into your emulator and reset the virtual TI 99. Again, you should find an entry for the program on the TI menu selection screen.

Feedback and Bug Reports

The xdt99 tools are released under the GNU GPL, in the hope that TI 99 enthusiasts may find them useful.

Please email feedback and bug reports to the developer at or use the issue tracker on the project GitHub page.