Accreted Drivel

Steven Tattersall's personal blog

Using Natfeats in 68000 Assembly Language

Created on: 2020-08-31

Posted in demos atari source code assembler lowlevel 68000


I did a little work investigating the "Natfeats" feature in Atari ST emulators like Hatari and Aranym. It allows you to control the emulation state in some ways. The features include:

To enable natfeats in Hatari, you might need to run with the --natfeats true command-line option.

I do a lot of work in the emulator, so for me the fast-forward option is of particular note. It means you can launch Hatari in quick mode using --fast-forward true, then switch to normal emulation speed once your code is loaded. This massively reduces turnaround time when you are developing.

Unfortunately the system is laboriously documented, so I had to do some printf debugging in Hatari to get my head around it.

To use a feature, it's a little more involved than simply inserting a special opcode into your codebase. Firstly you need to query the interface to get the 32-bit command ID for a feature. Push onto the stack the address of a null-terminated string containing the name of the feature (e.g. "NF_DEBUGGER"), then a dummy long. Then call the query using one special opcode ($7300). Natfeats returns the command ID in register d0. If the feature is not available, 0 is returned.

To invoke this ID, push any extra arguments on the stack, then the command ID, then a dummy long. Then insert the second special opcode ($7301) to call the command.

(This API really makes me appreciate how well‐designed GEMDOS and XBIOS are...)

Here is an example of a correct way to do this. Note that if you want this code to run (i.e. not crash) on real hardware, the $7300 opcode will cause an Illegal Instruction exception crash. So either remove it on real hardware, or swallow the failure with your own exception vector at $10.

        ; Natfeats tests.
        ; Use "hatari --enable-natfeats true"
        ; What you are meant to do is query the function ID based on a string,
        ; then store it somewhere.
 
        ; Save the ID for triggering debugger
        pea     natfeats_name           ;dc.b "NF_DEBUGGER",0
        clr.l   -(a7)                   ;dummy because of C API
        dc.w    $7300                   ;query natfeats
        move.l  d0,natfeats_debugger_id ;save ID. ID is 0 if not supported.
        addq.l  #8,a7
 
        ; (Later) Invoke debugger.
        move.l  natfeats_debugger_id,-(a7)          ;"debugger"
        clr.l   -(a7)                   ;dummy
        dc.w    $7301                   ;natfeats
        addq.l  #8,a7

        ...

        section data
natfeats_name:          dc.b "NF_DEBUGGER",0     ;string name of the natfeats command to use
                        even
natfeats_debugger_id:   ds.l 1                   ;stored command ID

A pre-packaged version

As ever, the awesome tIn of Absence has been here before me. He has a full example code sample to use.

The naughty and lazy approach

If, like me, you don't want to leave this code lying around for real hardware, and are just using it for debugging/testing, you can take some shortcuts. The IDs of the Natfeats commands don't change between runs of an emulator, they are effectively hardcoded. So here's the quick version, which is not portable, but skips the query phase.

The command IDs will depend on your Hatari build. Look at the table in the features[] array of src/debug/natfeats.c, add 1 to the entry index, and shift up by 20. This example is from a Linux build of Hatari:

        ; Returns emu version in d0 (always 0x10000 in Hatari, so basically useless)
        move.l  #2<<20,-(a7)            ;"version"
        clr.l   -(a7)                   ;dummy
        dc.w    $7301                   ;natfeats
        addq.l  #8,a7
 
        ; print string to stderr
        move.l  #test_string,-(a7)      ;string to print
        move.l  #3<<20,-(a7)            ;"stderr"
        clr.l   -(a7)                   ;dummy
        dc.w    $7301                   ;natfeats
        lea     12(a7),a7
 
        ; 4<<20 is shutdown (reset), needs to be in supervisor
        ; 5<<20 is exit (quit emu)
 
        ; Set fastforward mode
        ; Returns old mode in d0
        move.l  #0,-(a7)                ;fastforward setting (0 = off, 1 == on)
        move.l  #7<<20,-(a7)            ;"fastforward"
        clr.l   -(a7)                   ;dummy
        dc.w    $7301                   ;natfeats
        lea     12(a7),a7
 
        ; Trigger debugger
        move.l  #6<<20,-(a7)            ;"debugger"
        clr.l   -(a7)                   ;dummy
        dc.w    $7301                   ;natfeats
        addq.l  #8,a7

Taking it Further.

If you are feeling adventurous, it's pretty straightforward to add your own features to Hatari: just edit natfeats.c and add your own commands.

Some of the most useful things are:

Obviously you can do some of these things with debugger scripts in Hatari, but I find that simply inserting a macro in my code is much more convenient.

Back to Index