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
xas99, a TMS9900 cross-assembler,
xga99, a GPL cross-assembler,
xda99, a TMS9900 disassembler,
xdg99, a GPL disassembler,
xbas99, a TI BASIC and TI Extended BASIC lister and encoder,
xdm99, a disk manager for sector-based TI disk images,
xhm99, a manager for HFE images used by HxC floppy emulators, and
xvm99, a volume manager for nanoPEB/CF7+ Compact Flash cards.
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:
xdt99-mode, a major mode for the GNU Emacs text editor, and
xdt99 IDEA, a plugin for the IntelliJ IDEA development environment.
The plugins offer syntax highlighting, navigation, and semantic renaming for assembly and TI Extended BASIC programs in addition to environment-specific functionality.
- a comprehensive and consistent developer experience,
- a flexible and powerful command-line interface, and
- cross-platform availability.
Future developments will focus on further simplifying typical development tasks such as data conversion and program generation.
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
xvm99.py 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
ide/ directory contains the editor plugins for GNU Emacs and IntelliJ
IDEA. Please refer to the
EDITORS.md file for further information about
lib/ directory contains the Jumpstart cartridge and some supporting
functions that you may use in your TI programs.
example/ directory of the binary distribution contains some sample files
that are referenced throughout this manual.
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.
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.
$ xas99.py -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:
$ xas99.py -R -i ashello.a99 $ xas99.py -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
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
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
Assembling Source Code
xas99 cross-assembler reads an assembly source code file and generates an
uncompressed object code file that is suitable for the original TI 99
$ xas99.py -R ashello.a99 $ xas99.py -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),
-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.
Currently, only options
-L are supported; option
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.
Creating Program Images
The image parameter
xas99 to generate image files that can be
loaded using Editor/Assembler option 5.
$ xas99.py -R -i ashello.a99
Images larger than 8 KB are split automatically into multiple files, using the filename convention of the Editor/Assembler module.
-i parameter simulates the
SAVE utility program shipped with the
Editor/Assembler package and honors the symbols
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
--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
xas99 to create an RPK cartridge file that
can be used with the MESS emulator.
$ xas99.py -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
-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
xas99 to generate raw binary files without
metadata that are suitable for burning EPROMs or supplying other devices.
$ xas99.py -b -R ascart.a99 --base 0x6000
The assembler will generate one binary file per code segment. For further
SAVE directive may be used (see below).
--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
$ xas99.py -t -R ascart.a99
The resulting text file contains an
AORG comment followed by many
directives for each assembled segment.
; aorg >1000 byte >04, >c0, >c0, >81, >04, >60, >20, >00 ; aorg >2000 byte >02, >e0, >83, >e0, >04, >d0
The text format is useful for including the binary result of another program into some assembly or GPL source source.
To make the code, assemble, run cycle as fast as possible,
generate so-called "jumpstart disks" that may be loaded and executed by the
Jumpstart cartridge included with xdt99.
$ xas99.py --jumpstart -R ascart.asm $ mess64 ti99_4ae -cart lib/jumpstart.rpk -flop1 ascart.dsk
The example above shows the invocation for MESS, but any emulator should work. Obviously, the emulator must be setup with 32K memory expansion and at least one floppy disk drive.
Selecting the Jumpstart option in the TI 99 main menu will load the jumpstarted program from any inserted disk and run it. Thus, selecting either option 3 or 5 in the Editor/Assembler module and manually entering the program filename is no longer required.
Currently, jumpstarted programs may consist of up to 8 segments and must fit
entirely into memory areas
For relocatable code not larger than around 24 KB,
xas99 can generate an
Extended BASIC program that invisibly contains the generated code within:
$ xas99.py --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:
>OLD DSK1.ASHELLO >RUN
--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:
1 CALL INIT :: CALL LOAD(163 76,88,89,90,90,89,32,255,228 ):: CALL LOAD(8196,63,248):: CALL LINK("XYZZY")
But be careful: editing the generated program is likely to corrupt the embedded assembly code!
Creating List Files
-L option instructs
xas99 to generate a list file for the assembled
$ xas99.py -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
BYTE directive, where individual byte values are grouped into words.
TMS9900 Assembly Support
xas99 is a complete TMS9900 assembler supporting all documented TMS9900
opcodes. TMS9995 opcodes such as
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.,
DEF REF EQU DATA BYTE TEXT BSS BES AORG RORG DORG EVEN IDT DXOP COPY END
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
PSEG PEND CSEG CEND DSEG DEND LOAD SREF
Listing generation is currently not supported, so directives
LIST UNL PAGE TITL
are also ignored.
Source Code Organization
COPY directive is used to break large assembly sources into individual
xas99 will search the current source directory for appropriately named source
files. For example, assembling
$ xas99.py src/file1.a99
file1.a99 contains the instruction
COPY "DSK1.FILE2" will search for
src/FILE2 src/FILE2.A99 src/FILE2.ASM src/FILE2.S
and its corresponding lower-case variants.
Additional search paths may be specified with the
-I option as a
comma-seperated list, e.g.,
$ xas99.by -I lib/,disk2/ ashello.asm
COPY also supports native file paths, e.g.,
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.
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
; 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
: may be appended to the label name. The colon is not part of the
name, but logically continues the current line to the next:
my_label_1: equ 1 ; assigns 1 to my_label_1 my_label_2: 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
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.
clear_data: li r0, >a000 li r2, >100 ! clr *r0+ ; make jump target without potential name conflicts dec r2 jne -! ; jump to target two lines above rt
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 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
The extended expression syntax supports parentheses
), the modulo
%, and binary operators bit-and
~ 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).
A source code preprocessor allows for conditional assembly based on well-defined
conditional expressions. The preprocessor commands
if a given symbol is defined or not.
.ifdef lang_de msg text 'Hallo Welt' .else msg text 'Hello World' .endif
.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
In addition to symbols defined by labels,
xas99 also sets exactly one of
depending on the assembly command
-c, ... used.
Additional symbols may be supplied on the command line.
$ xas99.py ashello.a99 -D symbol1 symbol2=2
If no value is given, the symbol is set to value
Conditional assembly preprocessor commands may be nested. Valid conditional
expressions and their rules of evaluation correspond to those of the
xas99 supports macros. The
.defm preprocessor command introduces a new
.endm command concludes the macro definition. Inside the macro
body the macro parameters
#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 -! .endm
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
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
#endif blocks. The correct way to define environment-dependent macros is
.defm mymacro .ifdef symbol clr r0 .else clr r1 .endif .endm
instead of the other way around.
xas99 also provides a new directive
BCOPY that 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
SPRITE BCOPY "sprite.raw"
is equivalent to the conventional assembly statement sequence
SPRITE BYTE >18,>3C,>7E,>FF,>FF,>7E,>3C,>18
AORG directive allows for an optional second argument that
specifies the memory bank for the following code segment.
* ASBANK.A99 AORG >6000,0 FUNC1 CLR R0 ... AORG >6000,1 FUNC2 LI R1,>1234 ...
Generating binary files with the
-b command stores banked segments in
separate files, e.g.,
$ xas99.py -b asbank.a99 $ ls asbank.a99 asbank_6000_b0.bin asbank_6000_b1.bin
xas99 warns about illegal cross-bank accesses, but it cannot guarantee that
the correct bank is always active.
AORG >6000,0 L1 B @L3 ; OK B @L2 ; error: different bank AORG >6000,1 L2 B @L3 ; OK B @L1 ; error: different bank AORG >A000 L3 B @L1 ; OK, needs correct bank B @L2 ; OK, needs correct bank
In this example, the
B instructions in segment
A000 will both branch to
L2, depending on which bank is active.
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 A2 L2 DATA 0 FUNC A @L2,@L1 ; adds >8380 to >6000 RT A3
The list file for this program shows that the code of
FUNC is placed within
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 0005 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.
* MOVE FUNCTION TO SCRATCH PAD RAM 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
xas99 cannot place
XORG code directly into the indented target
location; instead, all such blocks need to be copied manually in your program.
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.
XORG directive can be used for all
xas99 output formats and is
compatible with both E/A option 3 and E/A option 5.
SAVE directive controls the output format for the image
-i and raw
-b output formats.
SAVE >6000,>7000 ; generate single image for >6000->6FFF AORG >6000 MAIN LIMI 0 ... AORG >6100 SUBR CLR R0 ...
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.
SAVE directives are provided, the
-b command will place each segment
in its own output file, but merges adjacent segments into one file. The
command will save the region between symbols
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.
xas99 supports the F18A GPU instruction set.
CALL <gas> RET PUSH <gas> POP <gad> SLC <wa>, <count> PIC <gas>, <gad>
PIC is not an immediate instruction, so its arguments need to be
constructed separately by hand.
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:
$ xas99.py -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 adher to the two-space separator rule of the relaxed xdt99 whitespace mode:
R5LB EQU SUBWS+11 * REGISTER 5 LOW BYTE. ***** Unknown symbol: REGISTER 5 LOW BYTE.
Finally, note that case insensitivity cannot be disabled at the moment.
xga99 GPL Cross-Assembler
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.
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
$ xga99.py gahello.gpl $ xga99.py 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
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.
$ xga99.py -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
directives if there is not enough space available.
Program execution will start at the symbol provided with the
END directive, or
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.
$ xga99.py -c gahello.gpl
.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.
$ xga99.py -c gahello.gpl -n "HELLO GPL WORLD"
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 variation of the Ryte Data GPL
Assembler syntax as its native format, but other syntax styles may be chosen
with the syntax parameter
$ xga99.py sdemo.gpl -s rag
Currently supported syntax styles are
xdt99 for the
xga99 native format,
rag for the Ryte Data and R.A.G. GPL assemblers, 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
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
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
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
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
numbers prefixed by
:, and text literals enclosed in single quotes
BYTE 10, >10, :10, '1'
Expressions are built using arithmeical operators
** and bit operators
~. 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
section on expressions.
xga99 uses the following mnemonics for the
FMT sub-language, but
other syntax styles are available with the
HTEXT/VTEXT <text> HCHAR/VCHAR <len>, <char> HMOVE <len>, <gs> ROW/COL <n> ROW+/COL+ <n> BIAS <n>/<gs> FOR <n> ... FEND [<label>]
Note that unlike the Ryte Data GPL Assembler,
xga99 also supports the optional
address label for the
xga99 GPL assembler supports the following directives:
GROM AORG EQU DATA BYTE TEXT STRI BSS TITLE COPY
Directives affecting listing generation are currently ignored:
PAGE LIST UNL LISTM UNLM
xga99 directives work very similar to their
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'
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
STRI directive works similar to the
TEXT directive but prepends the
generated byte sequence with a length byte.
GROM directive sets the GROM base address for the code that follows.
Currently only one
GROM directive per source file is supported. Note that the
GROM directive expects the actual GROM address, e.g.,
>6000, instead of an
integer number such as
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
Instead of using the
AORG directives in the source file the
location of the final GPL byte code image may also be specified by command-line
-A, respectively. Note that cartridge parameter
-G 0x6000 and
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
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
Note, however, that GPL macros use macro parameters
$2, ... instead
#2, ..., as the latter are used to denote VDP registers in GPL.
The predefined symbols set by
_xga99_gbc, depending on the assembly command
-c, ... used.
xda99 is a command-line tool to convert machine code
into assembly source code.
To disassemble a binary machine code file, the disassembler needs to know the first address of the machine code as well as the starting address for the disassembly:
$ xda99.py ascart_6000.bin -a 6000 -f 600c
All command line values are interpreted as hexadecimal values. They can
optionally be prefixed by
The resulting file has the same name as the binary file but ends in
contains the disassembled instructions in a source-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
An optional to parameter
-t will limit the disassembly to below that address.
The from parameter
-f starts the disassembler in "top-down mode", which
disassembles the machine code sequentially word by word into. This is often
problematic, as programs consists of both code and data segments. Disassembling
data segments results in accurate, but meaningless statements, which can be seen
if above command line is changed to
AORG >6000 6000 4845 SZC R5, @>4C4C(R1) 6002 4C4C 6004 4F20 SZC @>4341, *R12+ 6006 4341 6008 5254 SZCB *R4, R9 600A 2100 COC R0, R4 600C 0300 LIMI >0000 600E 0000
If the data segments are known, those can be excluded from disassembly with the
$ xda99.py 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,
offers an additional "run mode"
-r that recognizes static branch, call, and
return statements, and disassembles only along the program flow.
$ xda99.py 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 seperate in that program.
Run mode is not limited to one starting address:
$ xda99.py 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
$ xda99.py suprdupC.bin -a 6000 -r start
If the binary is not a cartridge image,
start will currently default to the
beginning given by
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 L600E L6010 LWPI PAD L6012 L6014 CLR R0 ...
-p options will also include an
EQU stanza of all symbols used, in this
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
xas99, which is
admittedly a rare case in practice, or written manually in fairly free form,
S1 EQU >10 S2: 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
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
-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.
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
BR is often used as a shorter
B.) As a consequence, a
run may "run off" (no pun indented), 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 | disagree- 6002 8300 C R0, R12 | ment 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
which will always overwrite previous results. This is done cleanly, so that
run 2 above will reset the overridden instruction at address
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 all options with
xda99, and works very similar to this
disassembler. In fact, at some point in the furure, both programs might be
merged into one.
To show the similarities,
$ xdg99.py gacart.bin -a 6000 -f 6030
disassembles bytecode file
gacart.bin, i.e., a GROM file, into GPL
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
-s, which is already known from
$ xdg99.py gacart.bin -a 6000 -f 6030 ... 6206 31 MOVE >0010, G@>6EC4, V@>0033 ...
yields the standard syntax, but
-s selects RAG, Ryte Data, or TIMT style:
$ xdg99.py gacart.bin -a 6000 -f 6030 -s mizapf ... 6206 31 MOVE >0010 BYTES FROM GROM@>6EC4 TO VDP@>0033 ...
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 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
xbas99 also supports programs created in so-called long format
and in merge format
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.
$ xbas99.py -l bashello.bin 10 REM HELLO 20 INPUT "YOUR NAME? ":NAME$ 30 PRINT "HELLO ";NAME$ 40 END
The similar decode command
-d saves the program listing to a file instead:
$ xbas99.py -d bashello.bin
xbas99 uses extensions
.bas for BASIC programs in source format and
for programs in internal format. To override the default naming convention, the
-o argument may be used:
$ xbas99.py -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
(Technical note: On Windows, you currently cannot use
xdm99 to extract BASIC
programs saved in merge format. For some unknown reason merge programs are
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
$ xbas99.py -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
-j may be used to automatically join split, i.e., word-wrapped,
$ xbas99.py -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
is considered a continuation of the previous line.
The long option
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:
$ xdm99.py basic.dsk -a bashello.bin $ xdm99.py -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:
$ xbas99.py -c bashello.bas -o - | xdm99.py basic.dsk -a - -n HELLO
All tools in xdt99 follow the convention that the special filename
stdout, respectively. You can also pipe from
to list BASIC programs quickly that are stored on a disk image:
$ xdm99.py basic.dsk -p HELLO | xbas99.py -l -
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
:: or subprograms like
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
contain more advanced checks to assist developers in creating new BASIC
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.
The default operation of
xdm99 when invoked without any options is to print
the file catalog of the disk image to
$ xdm99.py ed-asm.dsk ED-ASSM : 97 used 263 free 90 KB 1S/1D 40 TpS ---------------------------------------------------------------------------- ASSM1 33 PROGRAM 8192 B ASSM2 18 PROGRAM 4102 B EDIT1 25 PROGRAM 5894 B SAVE 13 DIS/FIX 80 3072 B 36 recs SFIRST/O 3 DIS/FIX 80 512 B 5 recs SLAST/O 3 DIS/FIX 80 512 B 4 recs
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, and the actual number of records 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.
The extract parameter
-e extracts one or more files from a disk image to the
local file system.
$ xdm99.py 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 if only one file is extracted.
$ xdm99.py work.dsk -e HELLO-S -o hello.a99
To print the contents of a file to
stdout, the print parameter
-p may also
$ xdm99.py 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
Filenames given by
-e may be glob patterns containing wildcards
?. This will extract all files matching the given pattern.
$ xdm99.py work.dsk -e "HELLO-*"
Note that you may have to quote your glob pattern to prevent your shell from expanding the pattern prematurely.
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.
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
$ xdm99.py work.dsk -a ashello.a99 -n HELLO-S -f DIS/VAR80
The syntax for
-f is fairly permissible, e.g.,
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.,
$ xdm99.py work.dsk -a intro main appendix -n FILE
will add the files as
FILG to the disk image.
The rename parameter
-r renames one or more files on the disk.
$ xdm99.py 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.
The delete parameter
-d deletes one or more files on the disk.
$ xdm99.py work.dsk -d HELLO-I HELLO-O $ xdm99.py work.dsk -d "*-O"
Note that the current implementation of
xdm99 does not perform a "secure
erase", i.e., parts of the file contents may remain hidden in the sectors of the
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
-d, respectively, or when the disk is repaired with
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
-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
-9 options, respectively. To extract a file in either FIAD
format, simply add
-9 to the extract operation:
$ xdm99.py work.disk -t -e HELLO-S $ xdm99.py work.disk -9 -e HELLO-S
By default, files extracted in TIFiles or v9t9 format will have extension
To add a file in TIFiles format or v9t9 format, add
-9 to the add
$ xdm99.py work.disk -t -a hello-s.tfi $ xdm99.py 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
-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
-f are ignored when used in
xdm99 also handles "short" TIFiles used, e.g., by Classic 99.
$ xdm99.py 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
The info parameter
-I displays the meta file information contained in FIAD
files, while the print parameter
-P dumps the file contents to
$ xdm99.py -I hello-s.tfi $ xdm99.py -P hello-s.v9t9
xdm99 can also convert from FIAD files to plain files and vice versa without
relying on disk images using the
$ xdm99.py -F hello-s.tfi $ xdm99.py -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
FIAD file conversion
-F and information
-P infer the FIAD
format used automatically, but detection may be overridden with the
The check parameter
-C analyzes a disk image for errors and prints a summary
stderr. While all disk operations, including cataloging, also check and
report any disk errors found, the
-C parameter restricts the output of
to those errors only.
$ xdm99.py -C work.dsk
-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
The repair option
-R tries to fix any disk errors, mostly by deleting
erroneous files from it.
$ xdm99.py -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
--initialize option creates a new, blank disk image, using an
optional name provided by
$ xdm99.py 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>D, and an optional number of tracks
<n> is an
integer or the letters
<n>T is missing,
40T is assumed.
$ xdm99.py blank.dsk -X DSDD $ xdm99.py 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.
$ xdm99.py volume.dsk -X cf
You can combine
-X with other parameters such
-a to work with the newly
created image immediately:
$ xdm99.py 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.
$ xdm99.py 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:
$ xdm99.py 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.
--set-geometry parameter explicitly sets the number of sides, the
density, and the track information of the disk image.
$ xdm99.py work.dsk --set-geometry 2S1D80T
Note that the
--set-geometry command is rarely required for regular images
but may be helpful for experimenting with non-standard disk image formats.
The sector dump parameter
-S prints the hexadecimal contents of individual
stdout. This can be used to further analyze disk errors or to save
fragments of corrupted files.
$ xdm99.py work.dsk -S 1 $ xdm99.py work.dsk -S 0x22 -o first-file-sector
For convenience, integer arguments of
-Z may be specified in
either decimal or hexadecimal notation.
xhm99 HFE Image Manager
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
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
$ xhm99.py -T work.dsk [...] [-o <filename>]
This yields the file
work.hfe by default. Instead of
-T you may also use
the long format
Similarly, the from HFE
-F argument converts from HFE image back to disk
$ xhm99.py -F image.hfe [-o <filename>]
This yields the file
image.dsk by default. Instead of
-F you may also use
the long formats
Managing Image Contents
All options other than
-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.
$ xhm99.py 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
$ xhm99.py image.hfe -p SOMEFILE Hello xdt99, meet HFE!
You may also add, extract, rename, or delete files:
$ xhm99.py image.hfe -a manual.txt -f dv80 $ xhm99.py image.hfe -r MANUAL:README $ xhm99.py image.hfe -e SOMEFILE -o greeting.txt $ xhm99.py 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
$ xhm99.py new.hfe -X dssd -a somegame.tfi -t
You can also resize HFE images, e.g., if you want to create more free space:
$ xhm99.py 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:
$ xhm99.py dsdd_image.hfe -Z dssd80t
The only format currently not supported is DSDD80T.
For further information about available arguments please refer to the
xvm99 nanoPEB Volume Manager
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+
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.
$ xvm99.py /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
/dev/sdc on Linux,
/dev/Disk3 on Mac OS X, or
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.
The second argument may be a single volume number or a combination of value
1,3-4,6-10. In general, if more than one volume is specified,
then the command is applied to all volumes.
-w argument writes a disk image to one or more volumes.
$ xvm99.py /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.
-r argument reads a disk image from a volume and stores it on the local
$ xvm99.py /dev/sdc 2 -r vol2.dsk
When reading from multiple volumes the resulting disk images will be renamed
xvm99 trims disk images to match the sector count stored in
the image, unless the
--keep-size option is given.
Most commands provided by
xdm99 are also available for
For example, to catalog a volume, you use the same
-i command as for
$ xvm99.py /dev/sdc 8 -i
Other commands supported by
xvm99 are print files
-p, extract files
-a, delete files
-d, check disk
-c, and repair disk
Again, if more than one volume is specified, then the command is applied to all volumes. For example,
$ xvm99.py /dev/sdc 1-20 -a README -f DV80
adds the local file README to all disk images in volumes 1 through 20.
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
$ 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
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.
$ xas99.py -R ashello.a99
This should yield an object code file
ashello.obj that looks like
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
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:
$ xas99.py -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:
$ xas99.py -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
is included in the example folder of xdt99 for convenience:
$ xdm99.py work.dsk -a ashello.obj -n HELLO-O -f DIS/FIX80 $ xdm99.py work.dsk -a ashello.img -n HELLO-I $ xdm99.py 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
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
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:
$ xas99.py -R ashello.a99 -L ashello.lst
This yields a text file
ashello.lst that begins like this:
XAS99 CROSS-ASSEMBLER VERSION 1.2.3 0001 * HELLO WORLD 0002 0003 IDT 'ASHELLO' 0004 0005 DEF SLOAD,SFIRST,SLAST,START 0006 REF VSBW,VMBW,VWTR 0007 REF KSCAN 0008 0009 SLOAD 0010 0000 100D SFIRST JMP START 0011 0012 8300 WRKSP EQU >8300 0013 8374 KMODE EQU >8374 0014 8375 KCODE EQU >8375 0015 837C GPLST EQU >837C 0016 0017 0002 .... MESSG TEXT 'HELLO WORLD' 0018 000D .... TEXT ' hit any key!' 0019 001A MESSGL EQU $-MESSG 0020 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
$ xas99.py -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:
$ xvm99.py /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
$ xvm99.py /dev/sdc 2 -a ashello.obj -n HELLO-O -f DIS/FIX80
ashello.obj will be available as
HELLO-O in volume 2 and can
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
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:
$ xga99.py -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:
$ xga99.py -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
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.