; Disk routine (pinched from Agile – sorry guys)

CIAAPRA EQU $bfe001 Cia A port register A
CIABPRB EQU $bfd100 Cia B port register B
CIABICR EQU $bfdd00 Cia B interrupt control register
CUSTOM EQU $dff000 Custom hardware
INTREQR EQU $1e Read interrupt request
DSKPTH EQU $20 Disk buffer pointer
DSKLEN EQU $24 Disk access length
DSKSYNC EQU $7e Disk sync word
DMACON EQU $96 Write DMA control
INTREQ EQU $9c Write interrupt request
ADKCON EQU $9e Write audio and disk controller

StepDel EQU $bb7 Step delay (dbf, naughty!)

; a0 is address to load file at, d0 is no. of file

movem.l d0-d7/a0-a6,-(sp) Store registers
move.l a0,a1 Copy addr. to load into a1
lea Filename(pc),a0 Get filename in a0
bsr MkName Put d0 (hex) into filename string
lea Variables(pc),a6 a6 = Variables base
lea CIABPRB,a5 a5 = CIA base (CIABPRB)
lea CUSTOM,a4 a4 = custom hardware base
move.l a1,20(a6) Store addr. to load in vars
clr.w 10(a6) Set ‘no track in buffer’
move.b #%11111111,(a5) All CIABPRB high
move.b #%10000111,(a5) SEL0-3 low (all motors now off)
move.b #%01111111,(a5) Ready for motors on again
bclr #3,(a5) Select drive 0, motor on
tst.w 0(a6) }
nop } blowed if I know why
not.w 0(a6) }
clr.l 2(a6) Clear current track and ?
clr.l 6(a6) Clear sec. to load, sec.no on track
btst #4,CIABICR-CIABPRB(a5) Test index (pointlessly)
.wtindx btst #4,CIABICR-CIABPRB(a5) Test index
beq.s .wtindx Repeat until one occurs
bset #1,(a5) Dir = out towards edge
.gttk0 btst #4,CIAAPRA-CIABPRB(A5) Are we at track 0?
beq.s .gottk0
.wtrdy BTST #5,CIAAPRA-CIABPRB(A5) Drive ready?
bne.s .wtrdy Wait until it is
bclr #0,(a5) Set step
nop Wait a few cycles
bset #0,(a5) Clear step
move.w #StepDel,D0 NAUGHTY – should use CIAs!
.stpdel dbf d0,.stpdel Step delay
bra.s .gttk0 Repeat until we get to track 0
.gottk0 bsr GetHash Calculate filename’s hash value
move.w #880,d0 Root block no. in d0
bsr ReadSec
tst.l d7 Was there an error?
bne DoneAll Clear up and return if so
move.w 18(a6),d0 Filename’s hash offset into d0
move.l (a3,d0.w),d0 Get no. of file header block
beq DoneAll If no file, clear up and return
.hshchn bsr ReadSec Read sector
tst.l d7 Was there an error?
bne.s DoneAll If so clear up, return
lea $1B0(a3),a2 a2 = pointer to filename in block
moveq #0,d0 Byte -> word
move.b (a2)+,d0 Get name length byte
cmp.w 16(a6),d0 Compare with wanted filename length
beq.s .sameln Go on if same lengths
.wrongF move.l $1F0(A3),d0 Get next hash chain
beq.s DoneAll If none, file not found – return
bra.s .hshchn Otherwise follow down the hash chain

.sameln subq.w #1,d0 Length -1 for dbf
move.l 12(a6),a1 Required filename ptr. in a1
.letlop move.b (a2)+,d1 Get byte from current file’s name
cmp.b #”a”-1,d1 Test case (badly)
ble.s .isupr Go on if upper case
sub.b #32,d1 Else convert to upper case
.isupr cmp.b (a1)+,d1 Compare with required filename
bne.s .wrongF If different, wrong file
dbf d0,.letlop Repeat to compare all letters

.loadlp move.l $10(a3),d0 Get first/next data block
beq.s DoneAll If none, clean up and return
cmp.w #1759,d0 Above valid range?
bgt.s DoneAll If so, clean up and return
bsr.s ReadSec Otherwise load it
tst.l d7 If there was an error
bne.s DoneAll then clear up and return
lea $200(a3),a0 Get end-of-block pointer in a0
move.l $14(a3),d1 Get checksum long
clr.l $14(a3) Clear same long in block
move.l a3,a1 Copy block pointer to a1
moveq #0,d0 Clear sum
.sumlop add.l (a1)+,d0 Add long to sum
cmp.l a0,a1 At end of block
blt.s .sumlop Sum all longs
neg.l d0 Negate sum
eor.l d0,d1 Compare with stored sum
bne.s DoneAll If checksum fails, clean up and return
move.l $c(a3),d0 Get no. of bytes in this block in d0
subq.w #1,d0 Prepare for dbf
lea $18(a3),a1 Get pointer to data in a1
move.l 20(a6),a0 Get address to load at in a0
.cpbyte move.b (a1)+,(a0)+ Copy data as bytes
dbf d0,.cpbyte Repeat
move.l a0,20(a6) Store new load address
bra.s .loadlp Carry on loading

; Routine to switch off motors and return
DoneAll move.b #%11111111,(a5) Select everything
move.b #%10000111,(a5) All motors off
move.b #%11111111,(a5) Left off
movem.l (sp)+,d0-d7/a0-a6 Restore registers
rte Return

; Routine to read a sector (in d0) from the disk
; Pointer to the loaded sector is returned in a3

ReadSec tst.w 10(a6) Check buffer status
bmi.s L701E6 Leave alone if negative
move.w #1,10(A6) Otherwise set to 1 (pointless)
L701E6 moveq #0,d7 d7 is error flag, none yet
move.w d0,6(a6) Store sector to load in vars
ext.l d0 word -> long
divu #11,d0 sector number…
swap d0 …mod 11
move.w d0,8(a6) gives sector on track, store in vars
swap d0 recover division result
move.l d0,d1 Copy to d1
lsr.w #1,d1 Get cylinder no. in d1.w
tst.w 10(a6) Is there a track in the buffer?
bpl.s .nedred If not, will have to read one
cmp.w 2(a6),d1 Is it the right cylinder?
bne.s .nedred If not, will have to read one
move.w d0,d2 Copy track no. to d2
and.w #1,d2 Mask to get side
cmp.w 4(a6),d2 Is the right side in the buffer?
beq L7036E If so, buffer is prepared
.nedred bset #2,(a5) Select side 0
clr.w 4(a6) Flag side 0 in vars
btst #0,d0 See which side is required
beq.s .sideok Go on if 0; side is correct
move.w #1,4(a6) Flag side 1 in vars
bclr #2,(a5) Select side 1
.sideok move.w d1,d0 Copy d0 to d1
bsr GetTkd0 Go to that track
move.w #$7F00,ADKCON(a4) Clear top byte of ADKCON
move.w #$9500,ADKCON(a4) MFM, no precomp, wordsync, FAST(MFM)
move.w #$4489,DSKSYNC(a4) Standard sync
move.l #$23600,a3 Get buffer pointer
move.l #$55555555,d6 Get clock value in d6
.fndlst move.l a3,a1 Copy buffer pointer to a1
move.w #2,INTREQ(a4) Clear disk interrupt request
move.l a3,DSKPTH(a4) Set disk buffer
.watrdy btst #5,CIAAPRA-CIABPRB(a5) Disk ready?
bne.s .watrdy Wait until it is
move.w #$8210,DMACON(a4) Disk DMA on
move.w #$8008,DSKLEN(a4)
move.w #$8008,DSKLEN(a4) Read len = sector info only
.wtdkdn btst #1,INTREQR+1(a4) Disk DMA done interrupt?
beq.s .wtdkdn Wait until one occurs
.skpsnc cmp.w #$4489,(a1)+ Compare syncs at (buffer+)
beq.s .skpsnc Keep going until past
subq.l #2,a1 a1 points to last sync
move.l (a1),d0
move.l 4(a1),d1 Get longs of sector info
and.l d6,d0
and.l d6,d1 Mask with clock
add.l d0,d0 Shift first one left
or.l d0,d1 Recombine
cmp.b #1,d1 Just before track gap?
bne.s .fndlst If not keep looking for last sector
move.l a3,DSKPTH(a4) Set buffer pointer again
move.w #$8210,DMACON(a4) Ensure disk DMA is still on
move.w #$9770,DSKLEN(a4)
move.w #$9770,DSKLEN(a4) Now read whole track
move.w #2,INTREQ(a4) Clear disk DMA done int. request
move.l #$186A0,d1 d1 = quite a long time
.wtdmad subq.l #1,d1 Count down
bne.s .notstk Go on if not end of count
not.l d7 Else flag an error
move.w #$4000,DSKLEN(a4) Stop disk DMA
rts Return
.notstk btst #1,INTREQR+1(A4) Disk DMA done interrupt?
beq.s .wtdmad If not, keep counting
move.w #$4000,DSKLEN(a4) Stop disk DMA

moveq #10,d5 11 sectors to decode
move.l a3,a2 Put them back over the buffer
move.l a3,a1 Save buffer pointer for later
.seclop move.w #$3ff,d0 Amount of space to look for sync in
.fndsnc cmp.w #$4489,(a3)+ Look for sync
beq.s .gotsc Go on if found it
dbf d0,.fndsnc Keep trying
not.l d7 If sync missing, flag error
rts and return
.gotsc cmp.w #$4489,(a3) Looking at sync?
bne.s .skpsnk If not, have passed it
addq.l #2,a3 Otherwise skip over it
.skpsnk movem.l (a3)+,d0-d1 Get two longs
and.l d6,d0
and.l d6,d1 And them with clock
add.l d0,d0 Shift first one left
or.l d1,d0 Recombine
lsr.w #8,d0 Shift right for sector number
move.w d0,(a2)+ Store sector number over buffer
lea $30(a3),a3 Skip sector info; a3 points to data
moveq #$7f,d2 128 longs of data to decode
.decdat move.l $200(a3),d1 Get second long of data
move.l (a3)+,d0 Get first long of data
and.l d6,d0
and.l d6,d1 And them with clock
add.l d0,d0 Shift first one left
or.l d1,d0 Recombine
move.l d0,(a2)+ Store over buffer again
dbf d2,.decdat Repeat for all data
dbf d5,.seclop Repeat for all sectors

; Track is now decoded as sectorno.w, data.b repeated 11 times
; Now bubblesort the sectors into order
.bubble move.l a1,a2 Get buffer pointer again
moveq #9,d0 10 (sector and next sector)s to compare
moveq #0,d1 d1 is count of sectors swapped
.cmpsec move.w (a2),d2 Get no. of current sector in d2
cmp.w $202(a2),d2 Compare with no. of next sector
blt.s .sortok If less, go on
move.l a2,a3 Copy current sec. pointer to a3
move.w #$100,d2 One sector + sector no. words to swap
.swpwrd move.w $202(a3),d3
move.w (a3),$202(a3)
move.w d3,(a3)+ Swap a word of sector data
dbf d2,.swpwrd Repeat
addq.w #1,d1 Inc. no. of sectors swapped
.sortok lea $202(a2),a2 Get next sector
dbf d0,.cmpsec Repeat for 10 (sector and next sector)s
tst.w d1 Did we have to swap anything?
bne.s .bubble If so, try again

lea 2(a1),a2 a2 is pointer to first sector data
moveq #10,d0 11 sectors to shift back
.seccpy moveq #$7f,d1 128 longs of data in each sector
.cpydat move.l (a2)+,(a1)+ Copy longs of data back down buffer
dbf d1,.cpydat Repeat for all data
addq.l #2,a2 Skip sector no., no longer needed
dbf d0,.seccpy Copy other sectors directly after

; At this point, the track is prepared in the buffer, so we just need
; to return a pointer to the sector in a3
L7036E move.l #$23600,a3 Get buffer pointer
moveq #9,d0 Constant for shift
moveq #0,d1 word -> long
move.w 8(a6),d1 Sector no. on track .w into d1
asl.l d0,d1 d1 << 9 = d1 * 512 add.l d1,a3 a3 = pointer to required sector move.w #-1,10(a6) Flag 'there is a track in the buffer' rts ; Routine to go to track d0 GetTkd0 move.w 2(a6),d1 Current track in d1 move.w d0,2(a6) Store which track it will be btst #4,CIABICR-CIABPRB(a5) Test for index (pointlessly?) .wtndex btst #4,CIABICR-CIABPRB(a5) Test for index beq.s .wtndex Wait until one occurs bclr #1,(a5) Dir = in towards centre sub.w d1,d0 Get no. of tracks to shift beq.s .aret Return if already at track bpl.s .centre Positive is towards centre, dir is correct bset #1,(a5) Dir = out towards edge neg.w d0 Positive no. of steps to make .centre subq.w #1,d0 Minus 1 for dbf .steplp btst #5,CIAAPRA-CIABPRB(a5) Test for disk ready bne.s .steplp Wait until it is bclr #0,(a5) Set step signal nop nop Wait a while nop bset #0,(a5) Clear step signal move.w #StepDel,d1 Step delay .stepdl dbf d1,.stepdl - NAUGHTY! should use CIA timers dbf d0,.steplp Step as many times as needed .aret rts Return ; Calculate hash table value for filename at a0 ; Won't work with real filenames, only $xx ones
; stores result in variables

GetHash move.l a0,12(a6) Store filename in vars
move.l a0,a1 Copy to a1
moveq #0,d0 Clear length counter
.getlen tst.b (a1)+ End of filename?
beq.s .gotlen Go on if so
addq.w #1,d0 Else increase length counter,
bra.s .getlen repeat

.gotlen move.w d0,d1 Copy filename length to d1
move.w d0,16(a6) Store filename length in vars
subq.w #1,d1 Prepare d1 (filename len) for dbf
.hshlop mulu #13,d0 d0 = filename length * 13
moveq #0,d2 Clear d2 for byte -> word
move.b (a0),d2 Get letter of filename
cmp.b #”a”-1,d2 Test case (badly)
ble.s .letok If less or equal, is upper case
sub.b #32,d2 Otherwise sub 32 to make it upper case
.letok move.b d2,(a0)+ Store back in filename
add.w d2,d0 Add to hash total in d0
and.w #$7ff,d0 Keep d0 in range
dbf d1,.hshlop Repeat for all bytes of filename
divu #72,D0 Hash total…
swap d0 …mod 72…
addq.w #6,d0 …plus 6…
asl.w #2,D0 …times 4
move.w d0,18(a6) store in variables

; Routine to put the number in d0 into the filename at a0 (“$xx”)
MkName move.w d0,d1 Copy fileno. to d1
move.w d1,d2 and d2
lsr.w #4,d2 d2 is upper nibble
bsr.s D2toHex convert to hex ascii
move.b d2,1(a0) store as first digit of filename
move.w d1,d2 move lower nibble into d2
bsr.s D2toHex convert to hex
move.b d2,2(a0) store as second nibble
rts return

; Routine to convert d2 into hex ascii
D2toHex and.w #$f,d2 Only numbers from 0-15
add.w #”0″,d2 Convert to ascii
cmp.w #”9″,D2 letter or number?
ble.s .digit return if number
addq.w #7,d2 else add 7 to convert to letter
.digit rts return

Variables: dc.w 0 0 = .w
dc.w 0 2 = Current track .w
dc.w 0 4 = Side in buffer (0/1) .w
dc.w 0 6 = sector to load .w
dc.w 0 8 = sector no. on track .w
dc.w 0 10 = Valid track in buffer .w
dc.l 0 12 = Pointer to filename .l
dc.w 0 16 = Length of filename in bytes .w
dc.w 0 18 = filename’s hash offset .w
dc.l 0 20 = Address to load at .l

Filename dc.b “$00″,0 Filename to load


Publication author

offline 11 hours


Comments: 1160Publics: 2780Registration: 06-03-2017

Notify of

Inline Feedbacks
View all comments
6 months ago

Weetibix? Cool! Makes me think of Double Dragon II which was my Renegade 2 / CPC Amstrad on Amiga 🙂

6 months ago

I’ve just come across your site – Fantastic! I’m actually looking for some old source code I wrote many years ago, my Amiga Fileloader routine which I used on most of my Oracle cracks. If anybody has a link to it, let me know 🙂 Thanks – Weetibix/Oracle

Reply to  Weetibix
6 months ago

I thought I had it, but I’ve just checked and I can’t find it sorry. The only thing that I think is by you is a loader I ripped from Jet Set Willy 2, but that wasn’t a file-loader!
I could rip it from one of the old Oracle cracks, but then again so could you 🙂
Out of interest are you still in touch with any of the old Oracle guys? I used to speak to Scooter regularly, and Scott/Mr.E/Action Man are all (relatively) local to me…

The Hulmerist
The Hulmerist
Reply to  WayneK
6 months ago

Did Scoot remember who you were? The best comparison would be Ozzy Osbourne as he seemed to have partied a lot.

13 years ago

What’s with the loop to do a sequence of small reads to probe for the track gap? It’s pointless, it will slow down track reads by 50% on average (waiting an extra 1/2 revolution before starting the full track read), and I wonder whether it works at all in emulators, which might reasonably start all DMAed reads from the track start.

Reply to  kaf
10 years ago

i disagree about the pointlessness of the system of probing used. It reduces the size of the mfm buffer required if you read the whole track starting from the first sector after the track gap. I was thinking this is particularly useful for a trackloader used in cracks. in many cases the custom mfm routines use only a single sync marker so only need a smaller mfm buffer than what is required to guarantee a successfully read of a dos track. this can cause issues if you need to try and find additional spare memory to use as an mfm… Read more »

Reply to  Phantasm
10 years ago

maybe a loader that reads the track one sector at a time in a loop filling up the mfm buffer as it goes would be a good approach to give the best of both worlds. so the sectors could be read in whatever order they appear as the disk spins but you dont need such a large mfm buffer to handle the track gap and all 11 complete sectors.

I’ve never seen such a loader though.

16 years ago

Hahaha, yes, that was probably the only reason to use that very dbf timing loop here 😀

16 years ago

I’m sure the dbf-timing loop was just put in there to make Codetapper feel all warm + fuzzy inside 🙂

16 years ago

using empty dbf loops for timing is not the way to go. 🙂 at least not if you want your crack working on 680×0 machines. =)


This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.


This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Password generation

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Would love your thoughts, please comment.x