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, searching and semantic renaming for assembly, GPL, and TI (Extended) BASIC programs.
- 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 some supporting functions that you may use in your
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.
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
-w disables all warnings.
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
-b. Followed by
a, it creates
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
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
$ xas99.py -t a2 -R example.a99
results in an assembly file with
; aorg >1000 byte >04, >c0, >c0, >81, >04, >60, >20, >00 ; aorg >2000 byte >02, >e0, >83, >e0, >04, >d0
The result can be
#included, or just copy-and-pasted.
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 LIST UNL PAGE TITL
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-separated 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 semicolon
; character inside a text literal
"..." 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
: 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).
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
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
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 h_ff01: data >ff01 b_81: 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.,
The assembler ensures that each value is added only once, so for constants
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 start: movb b#1, @acc ... aorg >2ffc start_vector: 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
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
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 ... text1: text 'HELLO WORLD!' text2: text 'GOOD BYE!'
In this example,
s#text1 equals 12, since there are 12 bytes from
The size modifier detects if the last byte of the range is a padding byte and subtracts it from the size.
text1: text 'HELLO WORLD' text2: text 'GOOD BYE!'
s#text1 equals 11, even though there are still 12 bytes from
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
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
STRI directive is similar to
TEXT, but prepends the length of the
string. In other words,
STRI 'HELLO WORLD'
is equivalent to
TEXT >0b, 'HELLO WORLD'
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
The exponent notation
1e9 is currently not supported.
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
* ASBANK.A99 AORG >6000 BANK ALL FUNC1 CLR R0 ... 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
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 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
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
x# can be prepended to the offending label.
BANK 0 CONT CLR R0 BANK 1 B @X#CONT ; OK, no error
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.
The source code preprocessor allows for conditional assembly based on
well-defined conditional expressions. The preprocessor commands
.ifndef check 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
Conditional assembly preprocessor commands may be nested. Valid conditional
expressions and their rules of evaluation correspond to those of the
val equ 42 .print 'Selected answer is', value
.error command prints a message and aborts the assembly.
aorg >6000 ... .ifgt $, >7fff .error 'Catridge program too large' .endif
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
The symbol option
-E dumps all symbols in EQU-like syntax to an external
$ xas99.py ashello.a99 -E sym.a99
which then reads:
CLS: equ >002E ; REL <-- was relocatable address GPLST: equ >837C ; KCODE: equ >8375 ; KEYSC: equ >005C ; REL ...
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 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 adhere 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"
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.
$ xga99.py gahello.gpl -L gahello.lst
-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
$ xga99.py sdemo.gpl -y mizapf
Currently supported syntax styles are the default
xdt99 for the
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
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 arithmetical 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
but other syntax styles are available with the
HTEXT/VTEXT <text> 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
xga99 GPL assembler supports the following directives:
GROM AORG EQU DATA BYTE TEXT STRI FLOAT BSS TITLE COPY BCOPY
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
a length byte to the generated byte sequence.
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
The exponent notation
1e9 is currently not supported.
GROM directive sets the GROM base address for the code that follows.
You can specify either the GROM number
7, or the absolute address
>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 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. The cartridge parameter
-G 0x6000 and
-A will not override
directives, but set the GROM and address of the first line of the code.
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.
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
xas99 extensions regarding comments, labels, whitespace, and
expressions also apply to
The source code preprocessor supports conditional assembly
.defm. For a description of both features please refer to the
respective section in the
xga99 supports macros. Note, however, that GPL macros use macro
$2, ... instead of
#2, ..., as the
# sign is used
to denote VDP registers in GPL.
The predefined symbols set by
_xga99_gbc, depending on the assembly command
-c, ... used.
Local labels are non-unique source-relative labels that begin with an
!. 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
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
$ 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 strict option
-s generates all output files in legacy Editor/Assembler
The resulting file has the same name as the binary file but ends in
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
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
$ xda99.py 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.
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
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 start: 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
$ 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 separate 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.
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
-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
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
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
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,
$ 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
-y, which is already known from
$ xdg99.py gacart.bin -a 6000 -f 6030 ... 6206 31 move >0010, g@>6ec4, v@>0033 ... $ xdg99.py 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
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 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.
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
$ xdm99.py work.dsk -e HELLO-S -o hello.a99
-o specifies a directory, the output is placed into that directory.
$ xdm99.py work.dsk -e HELLO-O HELLO-S -o hello/
When extracting two or more files,
-o may only be used with a directory
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.
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
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.
To rename the disk itself, use the
-n options without any additional
$ xdm99.py work.dsk -n WORK-2
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 will actually perform a "secure
erase", i.e., the entire contents of the deleted file will be removed from the
The write protection parameter
-w toggles the current protection status of
the given files.
$ xdm99.py work.dsk -w HELLO HELLO-CPY
Note that file protection is solely for TI 99 systems and emulators but will be
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
--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
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.
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
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.