_NOSTUB ASSEMBLY PROGRAMMING FOR THE TI-89/92+Written for Ti-Fr by Kevin Kofler.
Version 1.02This is the English version. The French original written for Ti-Fr is here and
at Ti-Fr.

I) Goals of this tutorial

This tutorial aims at teaching beginners at assembly (not C) programming, or assembly
programmers who have experience only at writing programs which need a kernel, how to write programs
which do not need any kernel (called "_nostub programs").
It does not aim at teaching the Motorola 68000 assembly language itself or the ROM_CALLs
of the Advanced Mathematics Software (AMS) operating system. For that, please refer to the
following links:

The most relevant differences between the kernel and _nostub programming modes are the
following:

A _nostub program does not need any kernel or patch. This is a very important
advantage: one saves the size of the kernel (12 KB in the case of Universal OS, the only
kernel which is currently updated regularly), and beginning users will be able to run the program
without any difficulties.

RAM_CALLs and BSS sections are not supported. Section IV will
explain which official AMS ROM_CALLs to use instead.

Dynamic librairies (in the form
of an external file to send to the calculator) are not directly supported. It is possible to create
dynamic librairies anyway (as for example Thomas Nussbaumer's FAT Engine), but there is a
much more practical solution: static librairies (which are included during the linking phase, the
last stage of the translation of an assembly source code into an executable program), like for
example TIGCCLIB by Zeljko Juric (with some functions, including the grayscales, by Thomas
Nussbaumer) and ExtGraph by Thomas Nussbaumer (with some contributions by Jim Haskell and
Zeljko Juric). Section V will treat this subject in detail. Moreover, for pure data files, it is easy
to render them external as section VI will show.

The registers d3-d7/a2-a6 have to be saved if
they are modified in the program (see section III.3).

The format of the ROM_CALLs is
different (see section III.4).

The screen is not saved automatically, but it is easy to do it
manually (see section III.7).

_nostub program, as there name tells, do not begin with
a "stub", a part of the kernel-dependent programs which calls the kernel and contains
some information for the relocation of the program. This saves at least about 50 bytes.

To sum up, a _nostub program is easier to use than a kernel-dependent program, and it can
do everything a kernel-dependent program is able to do, as long as one knows the appropriate
programming techniques, which are sometimes different. Do not get discouraged if the aforementioned
list of things to take care of looks complicated to you. It is much easier than what one might thing
at the first sight. I hope that after reading this tutorial, you will also program in _nostub
mode like me.

III) The bases of _nostub programming

III.1) Before you start

Before you start, you will have to download the utilities needed for programming, in other words the most recent version of the
TIGCC package. (We will mainly use the TIGCC IDE development environment,
the OS.h header file, the A68k assembler and, in section V, the TIGCCLIB static
library.)

III.2) Getting started at _nostub programming

We will begin with writing an empty _nostub program. Thus, you will have to open
TIGCC IDE and to create a new A68k assembly project. You will be able to use that same
project for everything which follows. Here is the program:

Note that there is no _main: nor xdef _main. In fact, that is totally optional
in _nostub assembly since the execution always starts at the beginning of the program.
It is possible to put a _main label directly after the xdef instructions, before the
rts. It would be a nonsense to put it anywhere else. In order to start the execution
somewhere else, it is enough to put a bra beginning directly after the xdef
instructions.

Another particularity: there is no END instruction at the end. That has nothing to do
with the _nostub mode, but the latest version of A68k does not require it anymore, so
we can leave it out.

III.3) A few words about the register saving

In _nostub mode, the registers have to be saved manually. We will see how to do that with
a very simple example: a program which only waits for a keypress.

Moreover, you have to save only the registers you actually use. (Since the ROM_CALLs
can destroy only the registers a0-a1/d0-d2 which you do not have to save, you do not need to take
care of those. However, BEWARE, the ROM_CALL macro destroys the register a4!)

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
move.l a4,-(a7) ;save a4 (here, move is enough because there is only 1 single register)
ROM_CALL ngetchx ;call the ngetchx ROM_CALL
;The next section (III.4) will show you how to optimize this call.
move.l (a7)+,a4 ;restore a4
rts

III.4) How to optimize the ROM_CALLs in _nostub mode

As we have seen in the previous example, the ROM_CALL macro allows you to easily do a
ROM_CALL (like jsr doorsos::ngetchx in kernel mode). However, that macro destroys a4
in order to place the address of the function there. This might be useful when calling the same
function more than once in a row, but most of the time, it is not the ideal solution. So, let's take
a look at how the ROM_CALL macro is defined and why it is defined in that way. Here is a
commented extract of OS.h:

ROM_CALL macro
move.l $C8,a4 ;places the address of the ROM_CALL jump table into the register a4
move.l \1*4(a4),a4 ;computes the address of the function and places it into a4
jsr (a4) ;calls the ROM function
endm

A first idea is to replace a4 with a0. This has 2 advantages:

The register a0 does not have to be saved by the program.

The
ROM_CALL will destroy a0 anyway, so we do not destroy any additional registers.

The only disadvantage is that if you want to call the same function a second time in a row,
you will have to redo all the calculations, whereas for the first method, jsr (a4) is
enough. But let's step to the implementation:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
;No registers have to be saved here.
move.l $c8,a0 ;places the address of the ROM_CALL jump table into the register a0
move.l ngetchx*4(a0),a0 ;computes the address of ngetchx and places it into a0
jsr (a0) ;calls ngetchx
rts

However, for a program calling many ROM_CALLs, there is an even better method: you can
place the address of the ROM_CALL jump table into a register which will not be destroyed
by the ROM_CALL and which we will not destroy afterwards. That is also the trick used in
C by the OPTIMIZE_ROM_CALLS directive of TIGCC.

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
move.l a5,-(a7) ;save a5
move.l $c8,a5 ;place the address of the ROM_CALL jump table into the register a5
;This has to be done only ONCE, AT THE BEGINNING of the program.
;We will then reuse a5 for each ROM_CALL!
move.l ngetchx*4(a5),a0 ;compute the address of ngetchx and place it into a0
;We do NOT use a5 here! a5 must stay constant.
jsr (a0) ;call ngetchx
move.l (a7)+,a5 ;restore a5
rts

Afterwards, for each ROM_CALL, it will be enough to use:

move.l ngetchx*4(a5),a0
jsr (a0)

This allows you to reduce the size required for each ROM_CALL to 6 bytes. The kernel mode
jsr doorsos::ngetchx uses between 8 and 10 bytes (8 bytes per call + 2 extra bytes for each
differentROM_CALL). Because of this and of the fact that there is no "stub",
a well-programmed _nostub program can be smaller than an equivalent kernel mode
program.

III.5) ROM_CALL2 or the AMS global variables

In the previous paragraph, we have seen some ROM_CALLs which represent functions.
However, there are also some of them which correspond to variables, like for example FirstWindow.
We will see how to use them by the means of a program which will only place that address into a2 for
a moment. (We will not see here how to display it. That is possible using the sprintf and
DrawStrROM_CALLs, which you will learn to use in the next paragraph.)
For those ROM_CALLs, the easiest way to use them is the ROM_CALL2 macro, which, like
ROM_CALL, destroys a4. Here is the program:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
movem.l a2/a4,-(a7) ;save a2 and a4
ROM_CALL2 FirstWindow ;place the address of the FirstWindow variable into a4
move.l (a4),a2 ;place the content of the FirstWindow variable, a pointeur to a WINDOW structure
;describing the current window, into a2
;Here, we would use a2.
movem.l (a7)+,a2/a4 ;restore a2 and a4
rts

Now, let's take a look at how the ROM_CALL macro is defined and why it is defined in that way. Here is a
commented extract of OS.h:

ROM_CALL2 macro
move.l $C8,a4 ;places the address of the ROM_CALL jump table into the register a4
move.l \1*4(a4),a4 ;computes the address of the function and places it into a4
;Contrary to ROM_CALL, there is no jsr (a4) to call the fonction here, but the address of the
;variable is placed into a4 and can be used.
endm

We can thus use the same optimization techniques as in the previous paragraph, i.e. either simply replace
a4 with another register like a0 which we do not have to save or a2 which we will destroy anyway
since it is the destination register, or use a constant register for the jump table. The latter
is the most interesting solution, so here is the adapted program:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
movem.l a2/a5,-(a7) ;save a2 and a5
move.l $c8,a5 ;place the address of the ROM_CALL jump table into the register a5
;This has to be done only ONCE, AT THE BEGINNING of the program.
;We will then reuse a5 for each ROM_CALL!
move.l FirstWindow*4(a5),a2 ;compute the address of FirstWindow and place it into a2
;We do NOT use a5 here! a5 must stay constant.
;I have chosen a2 because it is the chosen destination.
move.l (a2),a2 ;place the content of FirstWindow into a2
movem.l (a7)+,a2/a5 ;restore a2 and a5
rts

III.6) How to use the ROM_CALLs

In the previous examples, we have taken a look at 2 ROM_CALLs: ngetchx and
FirstWindow. But those are only 2 of the hundreds of ROM_CALLs of AMS. Of course, it is out
of discussion to document them all here. For that, please refer to the TIGCC documentation.
That documentation also contains a section labeled Information for Assembly Programmers, which
explains how to use them in assembly. However, I will repeat the most important pieces of information
here:

A ROM_CALL may destroy the registers a0-a1/d0-d2.

The parameters must be passed
on the stack in reverse order.

Parameters of type char, short or HANDLE
take 2 bytes, pointers (notated POINTED_TYPE *) and parameters of type long and ESI
take 4 bytes.

Return values of type char take 1 byte, the others are the same size
as the parameters of the same type.

Pointers and variables of type ESI (which are
in reality pointers) are returned in a0, everything else in d0.

After the call, the
parameters must be removed from the stack. WARNING: Even though most functions keep the
parameters on the stack intact, they are not at all forced to do so and there are functions like
sprintf which change them! It is thus recommended not to rely on the passed parameters being
left intact on the stack and to put them there again manually if you need them a second time.

We now have enough knowledge to write a Hello, World! in _nostub. Let's start with
writing our message in the status line:

Note that the text stays on the screen after execution and that this is not very pretty. On your
calculator, press [F5] twice to get rid of it. The next section will show you the solution to this
problem.

III.7) A few ready-made lines for saving the screen

In order to complete this section and to give an important application of the preceding paragraph,
I will give you a commented source code which you might also use as is if you want, but which I
recommend you to understand anyway because it represents an application of what precedes. Here
is it:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
movem.l d4/a5,-(a7) ;save d4 and a5
move.l $c8,a5 ;place the ROM_CALL jump table into a5
pea.l 3840 ;size to allocate
;(same as move.l #3840,-(a7), but more optimized)
move.l HeapAllocPtr*4(a5),a0
jsr (a0) ;allocate the 3840 bytes
move.l a0,d4 ;save the address of the allocated handle to d4
tst.l d4
beq nomem ;if the pointer is NULL, quit the program
move.l #3840,(a7) ;size to copy
;This instruction is not really necessary because HeapAllocPtr will not modify the value which is
;already on the stack, but it is better not to rely on it.
pea.l $4c00 ;source: address of the screen
move.l d4,-(a7) ;destination: adress of the allocated handle
move.l memcpy*4(a5),a0
jsr (a0) ;save the screen
lea.l 12(a7),a7 ;clean up the stack
;beginning of the main program
move.w #4,-(a7) ;pass the A_REPLACE attribute as an argument
pea.l hello_world(PC) ;pass the message to show as an argument
clr.l -(a7) ;pass x=0 and y=0 as arguments (We clear 4 bytes, 2 of them for x and 2 more for y.)
move.l DrawStr*4(a5),a0
jsr (a0) ;call DrawStr
lea.l 10(a7),a7 ;clean up the stack
;end of the main program
pea.l 3840 ;size to copy
;(same as move.l #3840,-(a7), but more optimized)
move.l d4,-(a7) ;source: adress of the allocated handle
pea.l $4c00 ;destination: address of the screen
move.l memcpy*4(a5),a0
jsr (a0) ;restore the screen
addq.l #8,a7
move.l d4,(a7)
move.l HeapFreePtr*4(a5),a0
jsr (a0) ;free the allocated handle
nomem:
addq.l #4,a7 ;clean up the stack
movem.l (a7)+,d4/a5 ;restore d4 and a5
rts
hello_world: dc.b 'Hello, World!',0

You will notice that the message has vanished. Let's thus insert a wait for a keypress.
Moreover, we can now afford to clear the screen without leaving too much damage. Here is the final
version of our Hello, World!:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
movem.l d4/a5,-(a7) ;save d4 and a5
move.l $c8,a5 ;place the ROM_CALL jump table into a5
pea.l 3840 ;size to allocate
;(same as move.l #3840,-(a7), but more optimized)
move.l HeapAllocPtr*4(a5),a0
jsr (a0) ;allocate the 3840 bytes
move.l a0,d4 ;save the address of the allocated handle to d4
tst.l d4
beq nomem ;if the pointer is NULL, quit the program
move.l #3840,(a7) ;size to copy
;This instruction is not really necessary because HeapAllocPtr will not modify the value which is
;already on the stack, but it is better not to rely on it.
pea.l $4c00 ;source: address of the screen
move.l d4,-(a7) ;destination: adress of the allocated handle
move.l memcpy*4(a5),a0
jsr (a0) ;save the screen
lea.l 12(a7),a7 ;clean up the stack
;beginning of the main program
move.l ScreenClear*4(a5),a0
jsr (a0) ;call ScreenClear (ClrScr in the TIGCC documentation - it is the only function I know of
;which has a differing name in the TIGCC documentation compared to OS.h)
move.w #4,-(a7) ;pass the A_REPLACE attribute as an argument
pea.l hello_world(PC) ;pass the message to show as an argument
clr.l -(a7) ;pass x=0 and y=0 as arguments (We clear 4 bytes, 2 of them for x and 2 more for y.)
move.l DrawStr*4(a5),a0
jsr (a0) ;call DrawStr
lea.l 10(a7),a7 ;clean up the stack
move.l ngetchx*4(a5),a0
jsr (a0) ;call ngetchx
;end of the main program
pea.l 3840 ;size to copy
;(same as move.l #3840,-(a7), but more optimized)
move.l d4,-(a7) ;source: adress of the allocated handle
pea.l $4c00 ;destination: address of the screen
move.l memcpy*4(a5),a0
jsr (a0) ;restore the screen
addq.l #8,a7
move.l d4,(a7)
move.l HeapFreePtr*4(a5),a0
jsr (a0) ;free the allocated handle
nomem:
addq.l #4,a7 ;clean up the stack
movem.l (a7)+,d4/a5 ;restore d4 and a5
rts
hello_world: dc.b 'Hello, World!',0

That's it, you now know the bases of programming in _nostub mode. With this, you should
be able to write some good _nostub programs. The 3 following sections will explain you how
to replace the functionalities of the kernels which you are likely to miss. The last section will be
dedicated to the ExePack compression.

IV) How to replace BSS sections and RAM_CALLs

IV.1) Dynamic memory allocation

One of the possibilities of the kernel mode which is often missed is the one to create "BSS
sections", which contain non-initialized data which is not stored in the program, but automatically
allocated and unallocated by the kernel. That is not possible in _nostub mode. Luckily, you
are not obliged to put all your variables into the program itself. In fact, there are some
ROM_CALLs which allow you to easily dynamically allocate and free memory blocks yourself.
I recommend you to read the documentation of alloc.h of TIGCCLIB. For a code example,
you might take a look at the screen content saving code above, which uses HeapAllocPtr and
HeapFreePtr.

font_medium, font_small and font_large: Here, it is much cleaner to use
DrawChar and/or DrawStr. font_small and font_large do not even work on
VTI with a *.??u type ROM.

FolderListHandle and MainHandle: Those
RAM_CALLs are useful only for direct acces to the VAT. It is much cleaner to use the
ROM_CALLs documented in the documentation of vat.h of TIGCC.

Heap: It is cleaner to call HeapDeref.

kb_globals: You are better off
by either using kbd_queue (WARNING: It is a ROM_CALL with a strange calling procedure:
moveq.l #6,d0 followed by trap #9. The result is returned in a0.) and
OSdequeue, or reading the keyboard directly.

One last thing I want to mention is that for the ROM version, there is a ROM_CALL in AMS 2
which gives the ROM version as a string (for example '2.05',0). It is the ROM_CALL
number $440. So:

move.l $c8,a5
cmp.l #$440,-4(a5) ;check if the relevant ROM_CALL is there
bcs AMSversion_AMS1 ;if not, use another routine
move.l $440*4(a5),a0 ;obtain the address of the string

For AMS 1, you will have to use another technique. You might for example use a table of the
ROM_CALL jump table addresses as Paxal recommends it. By the way, if you only need to know if
one uses AMS 1 or AMS 2, it is enough to write:

move.l $c8,a5
cmp.l #1000,-4(a5) ;check if there are at least 1000 ROM_CALLs
bcs AMS1 ;if not, it is AMS 1

At that point, you will probably ask the following question: Since the _nostub mode
does not support dynamic libraries (it is possible to work around this like Thomas Nussbaumer's
FAT Engine does, but it is very complicated), what should I do if I need a library function?
First of all, most functions of the standard libraries of the kernels (userlib,
graphlib, filelib) are already built-in into AMS: there are equivalent ROM_CALLs.
For example, userlib::idle_loop can be replaced with ngetchx, graphlib::clr_scr
with ScreenClear, ... If you have any doubts, the TIGCC documentation is a good
reference. However, unfortunately, there are a few important functions, like for example the
grayscales, which are not in AMS. What should one do in that case? Luckily, the dynamic libraries
are not the only way to share code. There is another type of libraries, static libraries, which
will be the subject of this section.

V.1) The advantages of static libraries over dynamic libraries

So, what are the particularities of that type of libraries? Static libraries are managed by
the linker during the translation of the source code into an executable program. This induces
many advantages when compared to dynamic libraries:

Static libraries are much easier to manage in _nostub mode.

The use of a
static library is totally transparent to the user (unless he wants to reassemble or recompile the
program). The functions used will be placed directly into the program by the linker. Thus, annoying
error messages like "Missing lib: xyzlib" belong to the past thanks to the static
libraries.

Only the really necessary functions are sent to the calculator. Do not hesitate
to use a static library for one single function. Unlike for dynamic libraries, where using one
single function makes it necessary to send the whole library to the calculator and thus induces
a significant waste of space and an additional possible source of error (missing library), for
static libraries, using one single function does not cause any practical disadvantage.

Thus, static libraries bring you all the advantages of using libraries, i.e. the ease of sharing code
which frees you from having to rewrite everything all the time, without the disadvantages of
dynamic libraries.

V.2) How to use a real static library (*.a) with A68k

Since version 0.92, TIGCC supports the use (and even the creation) of static libraries with A68k.
And their use is very easy: it is enough to do a bsr or jsr to the function you want
to use, and the linking system of TIGCC will take care of everything else. Let's give us an example: the assembly Gray Test
from the TIGCC examples converted to _nostub mode:

That's it. During execution of this program, the calculator will show 3 rectangles filled with
gray shades: a light gray one, a dark gray one and a black one. We have thus succeeded in writing
a grayscale program in _nostub assembly, which is often the main blocking point for
programmers who try to program in that way. Do not hesitate to reuse some code from this example,
it is there to help you.

By the way, in this example, we have used the TIGCCLIB static library, which your project
will be automatically linked to. If you want to use the functions from another static library
(such as ExtGraph), you will have to add it to your project before: Go to Project /
Add Files... and select extgraph.a, then OK. You can now use the functions
from ExtGraph just like those from TIGCCLIB.

V.3) Another solution for static code sharing

The "real" static libraries (the *.a files) are not the only way to statically share
code. There is another solution which works exactly like static libraries, except for a
disadvantage: the function to use is recompiled or reassembled each time you compile a project
which uses it. That solution is a very simple one: adding the source file containing the function
to the project. But let's start with an example: the InputStr function described in the
TIGCC FAQ. First of all, in addition to your A68k assembly file, add a second file,
in C, to the project. What options you choose does not matter, as we will delete the template
automatically created by TIGCC IDE anyway. WARNING: The name has to be different. In
fact, the extensions differ, but if you give the same name with just a different extension to the
2 files, there will be a collision for the created object files. The content of the C file is the
following:

include "OS.h"
xdef _ti89
xdef _ti92plus
xdef _nostub
;You need not reexport _nostub here, since TIGCC will do it in the C part.
movem.l d4/a5,-(a7) ;save d4 and a5
move.l $c8,a5 ;place the ROM_CALL jump table into a5
pea.l 3840 ;size to allocate
;(same as move.l #3840,-(a7), but more optimized)
move.l HeapAllocPtr*4(a5),a0
jsr (a0) ;allocate the 3840 bytes
move.l a0,d4 ;save the address of the allocated handle to d4
tst.l d4
beq nomem ;if the pointer is NULL, quit the program
move.l #3840,(a7) ;size to copy
;This instruction is not really necessary because HeapAllocPtr will not modify the value which is
;already on the stack, but it is better not to rely on it.
pea.l $4c00 ;source: address of the screen
move.l d4,-(a7) ;destination: adress of the allocated handle
move.l memcpy*4(a5),a0
jsr (a0) ;save the screen
lea.l 12(a7),a7 ;clean up the stack
;beginning of the main program
bsr clrscr ;clear the screen and set the coordinates for printf to 0 (calls a function from
;TIGCCLIB)
move.w #100,-(a7) ;pass the maxlen argument
pea.l buffer(PC) ;pass the buffer argument
bsr InputStr ;call the C function
addq.l #6,a7 ;clean up the stack
;end of the main program
pea.l 3840 ;size to copy
;(same as move.l #3840,-(a7), but more optimized)
move.l d4,-(a7) ;source: adress of the allocated handle
pea.l $4c00 ;destination: address of the screen
move.l memcpy*4(a5),a0
jsr (a0) ;restore the screen
addq.l #8,a7
move.l d4,(a7)
move.l HeapFreePtr*4(a5),a0
jsr (a0) ;free the allocated handle
nomem:
addq.l #4,a7 ;clean up the stack
movem.l (a7)+,d4/a5 ;restore d4 and a5
rts
buffer: ds.b 101

In general, it is recommendable to avoid this technique, and to prefer real static libraries, because
of the time spent to recompile the function every time it is used, but it might be useful
in some cases, like here where the function is not in a static library.

By the way, once more, I have not chosen the example randomly. In fact, as for the grayscales,
an easy to use InputStr routine is one of the few useful functions which you won't find in AMS.
With the help of the 2 previous examples, the code of which I invite you to reuse, we have passed
the 2 biggest obstacles which a beginner in _nostub assembly has to face.

VI) External data files

You might sometimes find yourself forced to use an external file for your data for 2 reasons:

The program would exceed 64 KB if the data was built-in.

Some data, like saved
states or highscores, has to be kept in memory between 2 executions of the program. If it was
saved in the program itself, that would be possible only if the program is neither archived nor
compressed with ExePack (see the next section).

Luckily, there are some ROM_CALLs which make this relatively easy. Please refer to the
documentation of vat.h of TIGCC. Here, I will limit myself to give a simple example,
which displays the content of the string called datafile in the main folder on the
screen. Let's start with creating that string. Enter the following on your calculator:

"Hello, World!"->main\datafile

(-> represents of course the character obtained by pressing the [STO->] key.)

Now, let's go on to our example program:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
movem.l d4/a5,-(a7) ;save d4 and a5
move.l $c8,a5 ;place the ROM_CALL jump table into a5
pea.l 3840 ;size to allocate
;(same as move.l #3840,-(a7), but more optimized)
move.l HeapAllocPtr*4(a5),a0
jsr (a0) ;allocate the 3840 bytes
move.l a0,d4 ;save the address of the allocated handle to d4
tst.l d4
beq nomem ;if the pointer is NULL, quit the program
move.l #3840,(a7) ;size to copy
;This instruction is not really necessary because HeapAllocPtr will not modify the value which is
;already on the stack, but it is better not to rely on it.
pea.l $4c00 ;source: address of the screen
move.l d4,-(a7) ;destination: adress of the allocated handle
move.l memcpy*4(a5),a0
jsr (a0) ;save the screen
lea.l 12(a7),a7 ;clean up the stack
;beginning of the main program
move.l ScreenClear*4(a5),a0
jsr (a0) ;clear the screen
move.w #4,-(a7) ;look in the main folder
pea.l sym(PC) ;symbol to look for
move.l SymFindPtr*4(a5),a0
jsr (a0) ;search for the variable
addq.l #4,a7
move.w 12(a0),(a7) ;The handle of the symbol is located at offset 12 of the SYM_ENTRY structure.
move.l HeapDeref*4(a5),a0
jsr (a0) ;dereference the handle
move.w #4,(a7) ;pass the A_REPLACE attribute as an argument
pea.l 3(a0) ;skip the size of the variable and the zero byte at the beginning of the string
clr.l -(a7) ;pass x=0 and y=0 as arguments (We clear 4 bytes, 2 of them for x and 2 more for y.)
move.l DrawStr*4(a5),a0
jsr (a0) ;display the content of the string
lea.l 10(a7),a7 ;clean up the stack
move.l ngetchx*4(a5),a0
jsr (a0) ;wait for a keypress
;end of the main program
pea.l 3840 ;size to copy
;(same as move.l #3840,-(a7), but more optimized)
move.l d4,-(a7) ;source: adress of the allocated handle
pea.l $4c00 ;destination: address of the screen
move.l memcpy*4(a5),a0
jsr (a0) ;restore the screen
addq.l #8,a7
move.l d4,(a7)
move.l HeapFreePtr*4(a5),a0
jsr (a0) ;free the allocated handle
nomem:
addq.l #4,a7 ;clean up the stack
movem.l (a7)+,d4/a5 ;restore d4 and a5
rts
dc.b 0,'datafile'
sym: dc.b 0
;The format of the symbols is special: You have to put a zero byte at the beginning and at the
;end, and the pointer must point to the final 0, not to the beginning.

A few remarks to conclude:

This is only an example. I recommend you strongly not to use strings to store your data,
especially if it is binary data, i.e. numbers like they are found in the registers rather than
in string form. It is much cleaner to use variables of custom type. For that, it is enough to place
the following characters at the end of the variable: 0,'TYPE',0,$f8. You might replace
TYPE with any string of 1 to 4 characters, which will appear in the Var-Link screen
as the type of the variable. The $f8 byte correspond to the OTH_TAG, which indicates
to AMS that the variable has a custom type.

WARNING: This program does not
unarchive the variable because it is read-only. If you want to write to a variable, you will first
have to unarchive it using EM_moveSymFromExtMem.

VII) The ExePack compression

At that point, you will probably have one last problem: What to do if your program exceeds 24 KB
(8 KB on AMS 2.00-2.03)? Luckily, it is not very difficult to create a launcher which works even on
HW2 AMS 2 without any kind of patches (like Julien Muchembled's HW2Patch) or memory resident
anti-protection programs (like my HW2 AMS 2 TSR support (h220xTSR)). However, there is an
even simpler and more practical solution: TIGCC can automatically compress your program for
you. That not only has the advantage that the automatically generated decompressor will bypass the
24 (or 8) KB limit for you, but it also allows you to reduce the size of the program while you are
at it. That compression is called ExePack.

Start with choosing an example file. You can use the empty program from section III.2:

include "OS.h"
xdef _nostub
xdef _ti89
xdef _ti92plus
rts

Now compress it:

In the TIGCC IDE menu, select Project, then Options....

Click on Compress File (*.89y/*.9xy) in the upper left, under Target, in order to
activate that option.

The field On-calc variable name should now be active. Enter
the name which the compressed file will take on the calculator there. WARNING: This name
has to be different than the name of the program. I recommend you to take as a name the 5 first
characters of the name of your program followed by ppg. For example, if your program is
called abcdefg, I recommend you to call your compressed file abcdeppg.

Click
on OK.

Reassemble your program (using for example Project/Build). It will
be compressed automatically.

I recommend you to use ExePack for each program larger than 8 KB. For programs of 8 KB or
less, it is not really worth it, so I do not recommend you to use ExePack in that case.

VIII) Conclusion

You should now be able to program in _nostub mode and to do everything you can do in
kernel-dependent programs in that mode too, and maybe even more. As you have been able to see,
the _nostub mode is not reserved for C programs! I hope that this tutorial has helped
you and that it will inspire you so as to program in _nostub mode like I do it since more
than a year.