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
|