Alive
News Team Current issue History Online Support Download Forum @Pouet

01 - 02 - SE - 03 - 04 - 05 - 06 - 07 - 08 - 09 - 10 - 11 - 12 - 13 - 14

Alive 10
Bootsector Mania

In 2005 a boot-sector competition was offered to the crowd by the Outline team 
and it seems, this compo has been willingly accepted by the coders. Six compo 
entries were submitted by five different people, which is more than in most 
other compos. Why did this competition, which appeared very late on the Outline 
website, got so many entries? One reason, amongst others, is for sure the amount 
of time you have to put into such an entry. A full blown intro can easily 
consume a week or a month. A real demo often takes several months to years (hi 
ray :) until it is finished, games might even use more time. While a bootsector
can be done in a few hours or days. 

Another reason might be the fact that you can do things much prettier than with 
a 128bytro for example. Anyway the task is basically the same, squeeze your code 
into the 512 bytes of the bootsector and do a nice effect. Unlike to 4ktros it’s 
not really recommended to use packers for bootsectors, because you will not have 
much space left to unpack. For this reason the 1st aim of a bootsector demo is 
to optimize for small code.



Which size is it?

So how much space do we have in a bootsector? By default the size of a sector on
a floppy is 512 bytes, but we have to take into consideration that some of these
bytes are taken for information about the size and layout of the floppy, also
the position of the FATs is stored in the bootsector. So we have to subtract the
size of this information, at least if we are going to obey the compo rules. The
size of this information is 30 bytes including a branch which skips this memory
area when executing the bootsector. This mean we have 512-30=482 bytes left.
Unfortunately TOS needs a special checksum ($1234) to execute a bootsector and
so the last word of the bootsector is also lost for coding. This leaves us with
480 bytes starting at org+30 unless you want to render the disk useless for
normal GEMDOS file storage. Perhaps a memory map of the bootsector helps to
understand why you shouldn't put code in these areas.

BRA      $00 This WORD contains a BRA.S instruction to the boot code in this
sector if the disk is executable, otherwise it is unused. The branch is used to
skip the information block of the bootsector.

OEM      $02 These six bytes are reserved for use as any necessary filler
information. The disk-based TOS loader program places the string 'Loader' here.
It should be save to place a very short subroutine here or to put some init
commands before the branch, however six bytes is not much gain.

SERIAL   $08 The low 24-bits of this LONG represent a unique disk serial
number. This area shouldn't be touched since it identifies the disk, TOS uses
this serial to determine if the disk has been exchanged, so creating several
disks with the same serial might result in data loss.

BPS      $0B This is an Intel format WORD (low byte first) which indicates
the number of bytes per sector on the disk. On a standard Atari Floppy this
should contain a 512 or $200 in INTEL order.

SPC      $0D This is a BYTE which indicates the number of sectors per cluster
on the disk, on a standard ST floppy this is set to two.

RES      $0E This is an Intel format WORD which indicates the number of
reserved sectors at the beginning of the media for floppies this is usually set
to one, because only the bootsector is reserved.

NFATS    $10 This is a BYTE indicating the number of File Allocation Table's
(FAT's) on the disk, usually this is set to two.

NDIRS    $11 This is an Intel format WORD indicating the maximum number of
ROOT directory entries, normally this is set to 70.

NSECTS   $13 This is an Intel format WORD indicating the total number of
sectors on the disk, this means reserved sectors are included in that number. On
single sided disks this contains a $2d0 on double sided disk its $05a0.

MEDIA    $15 This BYTE is a media descriptor, it's not used on the ST but its
present to preserver compatibility with MS-DOS. The following values can be
found: $f8 single sided / 80 tracks, $F9 double sided / 80 tracks, $FC single
sided / 40 tracks, $FD double sided 40 tracks.

SPF      $16 This is an Intel format WORD indicating the number of sectors
per FAT, usually this is set to five.

SPT      $18 This is an Intel format WORD indicating the number of sectors
per track. Usually this is set to nine.

NSIDES   $1A This is an Intel format WORD indicating the number of sides on
the disk, unless you have a single sided floppy drive (SF 354) it should contain
a two.

NHID     $1C This is an Intel format WORD indicating the number of hidden
sectors on a disk (not used on ST so it should be zero).

BOOTCODE $1E This area is used by any executable boot code. The code
must be completely relocatable as its loaded position in memory is not
guaranteed.

CHECKSUM $FE The entire boot sector WORD summed with this Motorola
format WORD will equal 0x1234 if the boot sector is executable or some other
value if not.

Please not that there is an additional specification from Atari for loading the
RAM TOS but since it cannot be applied here we will just note that it specifies
further reserved areas from $1fe to $3a where the bootroutine resides in that
case.



What else is important?

When you start a normal program (PRG, TOS, TTP, APP, whatever) it will be run in
user mode, a bootsector however is executed in supervisor mode, so you have full
access to all hardware registers and memory areas without the need to switch to
supervisor mode first. Another important fact is the way to exit a bootsector.
You will need nothing more than a plain rts and you are done. Pretty easy isn't
it? Well there is one thing I didn't mention yet, the whole code in the
bootsector has to be PC-relative, because it's not relocated on load. It will be
executed as it is and the best way to go around this issue is to use a base
register and relative offsets for all variables. However this leads to the
question:



Where do I put my

variables?

There are several possibilities, however since there is no DATA or BSS segments
in the bootsector you will have to handle them different to a normal program.
You could place them within your code, but with only 480 bytes it would be a
waste of precious memory. There are several ways to handle this, I choose to use
a base-register, so I can put it anywhere in memory I want and define the
variables with the RS-command of the assembler. Of course you can also use EQU
but it will be less flexible if any changes occur. We will determine a suitable
memory area later, so what else should be considered? Well as you surely noticed
while reading the above is the fact that our variables shouldn't exceed the 16
bit range, otherwise we would need to take countermeasures to avoid addressing
problems. Taking all this into account there is still another question left:



How do I generate a

valid bootsector?

And we want to do it in an easy way, so it seems to be the best to rely on the
operating system for that. The basic steps you need to perform are:

- read the bootsector from the disk you want to modify (this step is important
  if you don't want to trash the disk, since the bootsector contains vital
  information about the disk format)

- load and copy your preassembled, pc-relative code to the right position inside
  the bootsector (org+30)

- call xbios (protobt) to automatically generate the necessary checksums, and
  make sure that the disktype and serial are not changed.

- write the modified bootsector back to the disk

- boot the machine to test your bootsector

Lets have a closer look at the necessary code to perform the tasks described
above.

; read bootsector from floppy disk in drive a:
; the bootsector can be found on side 0, track 0, sector 1 of any disk

        move.w  #1,-(sp)        ; count
        clr.w   -(sp)           ; side   = 0
        clr.w   -(sp)           ; track  = 0
        move.w  #1,-(sp)        ; sector = 1
        clr.w   -(sp)           ; device = 0 (a:)
        clr.l   -(sp)           ; reserved (should be 0)
        pea     bootsector      ; buffer address
        move.w  #$08,-(sp)      ; XBIOS 8 (Floprd)
        trap    #14             ; call xbios
        lea     20(sp),sp       ; correct stack

; copy the pc-relative code into the buffer

        lea     bootcode,a0     ; startaddress of executable code
        lea     bootsector+30,a1; startaddress within bootsector buffer
        move.w  #119,d0         ; 480/4 = 120 longs
.copycode:
        move.l  (a0)+,(a1)+     ; copy code to bootsector buffer
        dbra    d0,.copycode

; prepare bootsector in memory

        move.w  #1,-(sp)        ; execflag (1 executable / 0 not executable /
;           -1 no change)
        move.w  #-1,-(sp)       ; type (-1 don't change disktype)
        move.l  #-1,-(sp)       ; serial (-1 don't change)
        pea     bootsector      ; startaddress of bootsector
        move.w  #$12,-(sp)      ; XBIOS 18 (Protobt)
        trap    #14             ; call xbios
        lea     14(sp),sp       ; correct stack

; write bootsector to floppy disk in drive a:

        move.w  #1,-(sp)        ; count
        clr.w   -(sp)           ; side   = 0
        clr.w   -(sp)           ; track  = 0
        move.w  #1,-(sp)        ; sector = 1
        clr.w   -(sp)           ; device = 0 (a:)
        clr.l   -(sp)           ; reserved (should be 0)
        pea     bootsector      ; buffer address
        move.w  #$09,-(sp)      ; XBIOS 9 (Flopwr)
        trap    #14             ; call xbios
        lea     20(sp),sp       ; correct stack



What else might be

important?

We still need to know where to put our variables and other memory consuming
stuff. To get some rough directions I modified the cookie reader from the last
Alive issue to be pc-relative and capable to run from a bootsector. It can be
found in appendix A.

By examining different versions of the German TOS it appears that on a one
megabyte machine the end of available GEMDOS memory (_memtop) and the video base
address (_v_bas_ad) were both located at $f8000, the available amount of free
memory which could be reserved by malloc equals ( _memtop minus _membot ) in all
cases. Usually The system variable _memtop depends on the available memory in
your system minus the amount of necessary video-ram (32 kb on a ST(e)).

TOS   TOS-Org  Boot-Org  Stack   _membot  Cookies
1.00  $fc0000  $167a     $4da8   $a100    No
1.02  $fc0000  $16da     $754a   $ca00    No
1.04  $fc0000  $181c     $377a   $a84e    No
1.60  $e00000  $185c     $37ba   $a892    Yes
1.62  $e00000  $185c     $37ba   $a892    Yes
2.05  $e00000  $1410     $3ba4   $c8ae    Yes
2.06  $e00000  $1644     $3dd8   $ccb2    Yes

However it's more important to know where the available memory starts. If you
use malloc() from the bootsector you always get the same address which is stored
in _membot, so it should be safe to place your variables and stuff there. If you
compare the values from the table above, you can easily see that $ccb2 is the
highest memory location for the ST(e) range of machines, but it is probably
safer to use the content of _membot as an indicator for the start of free
memory.

So these are all the informations you need to create a bootsector, with some
general assembly coding skills it should be possible to create your own one.
To get you started ggn, p01 and myself have included the more or less commented
sources of our bootsector entries in this issue of alive. It should be easy to
start from there and modify things or even create something completely new.



Appendix A

Bootinfo Source

bootsector      EQU 1
tv              EQU 0
;-------------------------------------------------------------------------------
; Title:       Boot Information Reader
;-------------------------------------------------------------------------------
; Description: This program reads available system information from the boot-
;              sector and writes it to a file called "B" on floppy A:
;-------------------------------------------------------------------------------
; Author:      Cyclone / X-Troll
; Mail:        cxt(at)atari.org
; Web:         http://www.edv-rudolf.de/xtroll/
;-------------------------------------------------------------------------------
                RSRESET
os_entry:       RS.W 1
os_version:     RS.W 1
os_start:       RS.L 1
os_base:        RS.L 1
os_membot:      RS.L 1
os_shell:       RS.L 1
os_magic:       RS.L 1
os_gendat:      RS.L 1
os_palmode:     RS.W 1
os_gendatg:     RS.W 1
os_mifl_root:   RS.L 1                  ; these exist from TOS 1.02 upwards
os_kbshift:     RS.L 1
os_act_PD:      RS.L 1
;-------------------------------------------------------------------------------
                RSRESET
cookie_ptr:     RS.L 1
cookie_str:     RS.B 20
cookie_file:    RS.B 512
;-------------------------------------------------------------------------------
Reset_SSP       EQU 0
Reset_PC        EQU 4
_membot         EQU $0432               ; start of free GEMdos memory
_memtop         EQU $0436               ; end of free GEMdos memory
_v_bas_ad       EQU $044E               ; start of screen memory
_sysbase        EQU $04F2               ; start of OS vars
_p_cookies      EQU $05A0               ; start of cookie-jar
;-------------------------------------------------------------------------------
org:
                IFNE tv
                OPT D+,P+
                DEFAULT 1
                illegal
                ELSE
                OPT D-,P+
                DEFAULT 2
                ENDC

                lea     baseptr(PC),A0
                move.l  _membot.w,(A0)

                IFNE bootsector
                DEFAULT 5

                bsr     cookieview

                ELSE

                linea   #0 [ Init ]
                linea   #10 [ Hidem ]

                pea     cookieview(PC)
                move.w  #$26,-(SP)
                trap    #14
                addq.w  #6,SP
wait_key:
                move.w  #2,-(SP)           ; check for pressed key
                move.w  #1,-(SP)
                trap    #13
                addq.w  #4,SP
                tst.w   D0
                beq.s   wait_key

                move.w  #2,-(SP)
                move.w  #2,-(SP)
                trap    #13
                addq.w  #4,SP

                linea   #9 [ Showm ]

                ENDC

                clr.w   -(SP)              ; Fcreate
                pea     fname(PC)
                move.w  #$3C,-(SP)
                trap    #1
                addq.l  #8,SP
                tst.w   D0
                bmi.s   error

; for some strange reasons calling Fcreate from bootsector kills my baseptr
; even more strange this doesn't happen if you execute the same binary from
; the debugger after the system has booted
; However, this is the reason to read _membot.w, which contains the same
; value. I know it's no clean solution but I am a bit out of ideas :)

                movea.l _membot.w,A0       ; patch for killed baseptr
                lea     cookie_file(A0),A1
                movea.l cookie_ptr(A0),A0
                suba.l  A1,A0

                pea     (A1)               ; start
                pea     (A0)               ; length
                move.w  D0,-(SP)           ; handle
                move.w  #$40,-(SP)         ; FWrite
                trap    #1
                addq.l  #2,SP              ; handle is on stack

                move.w  #$3E,-(SP)         ; Fclose
                trap    #1
                lea     $0C(SP),SP
out:
error:
                IFEQ bootsector
                clr.w   -(SP)
                trap    #1
                ELSE
                rts
                ENDC

tos_ver:
                movea.l _sysbase.w,A3
                move.l  #"tver",D0
                moveq   #0,D1
                move.w  os_version(A3),D1  ; Mirrored TOS Version
                bsr     print
                move.l  #"tbas",D0
                move.l  os_base(A3),D1     ; TOS Base Address
                move.l  D1,-(SP)
                bsr     print
                move.l  #"tdat",D0
                move.l  os_gendat(A3),D1   ; TOS Base Address
                bsr     print
                movea.l (SP)+,A3           ; not initialized during boot
                move.l  #"tsyn",D0         ; get from rom tos instead
                moveq   #0,D1
                move.w  os_palmode(A3),D1  ; lsb set = PAL else NTSC
                and.w   #1,D1

                bra     print

boot_nfo:       lea     org-30(PC),A0
                move.l  #"org ",D0
                move.l  A0,D1
                bsr     print
                move.l  #"sp  ",D0
                move.l  SP,D1
                bsr     print
                move.l  #"vbas",D0
                move.l  _v_bas_ad.w,D1
                bsr     print
                move.l  #"mbot",D0
                move.l  _membot.w,D1
                bsr     print
                move.l  #"mtop",D0
                move.l  _memtop.w,D1
                bsr.s   print
                pea     -1
                move.w  #$48,-(SP)         ; GEMDOS malloc
                trap    #1
                addq.l  #6,SP
                move.l  D0,D1
                move.l  #"mfre",D0
                bsr.s   print
                pea     $0400
                move.w  #$48,-(SP)
                trap    #1
                addq.l  #6,SP
                move.l  D0,D1
                move.l  #"malc",D0
                bra.s   print
cookieview:
                movea.l baseptr(PC),A0
                pea     cookie_file(A0)
                lea     cookie_ptr(A0),A0
                move.l  (SP)+,(A0)
                bsr.s   cls

                bsr     boot_nfo
                bsr     tos_ver            ; get TOS Info

                move.l  _p_cookies.w,D0    ; fetch pointer to cookie jar
                beq.s   no_cookies
                movea.l D0,A0
loop:
                move.l  (A0)+,D0           ; get cookie
                beq.s   end_of_jar
                move.l  (A0)+,D1
                bsr.s   print
                bra.s   loop
end_of_jar:
                bra.s   exit
no_cookies:
                pea     no_cookies_str(PC)
                move.w  #9,-(SP)
                trap    #1
                addq.w  #6,SP
exit:
                rts

cls:
                pea     cls_str(PC)
                move.w  #9,-(SP)
                trap    #1
                addq.w  #6,SP
                rts

print:
                movea.l baseptr(PC),A1
                lea     cookie_str(A1),A1
                move.l  D0,(A1)+
                move.w  #" $",(A1)+
                lea     cipher_tab(PC),A2
                moveq   #7,D2
print1:
                rol.l   #4,D1
                move.w  D1,D0
                and.w   #$0F,D0
                move.b  0(A2,D0.w),(A1)+
                dbra    D2,print1

                move.b  #13,(A1)+
                move.b  #10,(A1)+
                clr.b   (A1)+

                move.l  A0,-(SP)
                movea.l baseptr(PC),A0
                pea     cookie_str(A0)
                move.w  #9,-(SP)
                trap    #1
                addq.w  #6,SP

                movea.l baseptr(PC),A0
                lea     cookie_str(A0),A1
                lea     cookie_ptr(A0),A0
                movea.l (A0),A2
copy_loop:
                move.b  (A1)+,(A2)+
                bne.s   copy_loop
                subq.w  #1,A2
                move.l  A2,(A0)
                movea.l (SP)+,A0

                rts
;-------------------------------------------
cipher_tab:     DC.B "0123456789abcdef"
cls_str:        DC.B 27,"E",0
no_cookies_str: DC.B "?",0
fname:          DC.B "A:\B",0
                EVEN
baseptr:
;-------------------------------------------
                END

Cyclone/X-Troll for Alive Diskmagazine, 2005-05-01 


        
Alive 10