Atari ST Protection: Turrican

The boot sector starts quite interesting: copying code to address 8 and executing it there. The format of the disk is different, but not special: sector 1 in the track is 512 bytes long, sector 2-6 are each 1024 bytes long. 5632 bytes per track out of possible 6250 bytes, pretty good capacity on a double sided disk. The trick is that the last 1024 byte sector starts at the end of the track and extends for more than 500 bytes over the index marker. The first sector is packed right after the sector at the beginning. The tracks 75..79 are not used. Strangely on the back side Track 79 contains an empty 512 sector, which does not seem to be part of the protection.

Track 7 to 10 contain the actual protection. The test starts in track 7 and tests the protection, if it fails it continous till track 10 to find a valid protection. If it fails on all 4 tracks, it erases the memory till it crashes…

The usual 6 sectors exist in these tracks, but they also contain sector 0 and 16 in different positions (the position is not tested). They are supposed to be each 1024 bytes long, but they only have an address mark plus the sector header and can not be read without a CRC error. The reason for this is interesting and István Fábián figured out what is going on the the actual disk medium! http://www.atari-forum.com/viewtopic.php?f=47&t=19948&start=25

Track  7: sector order: 5,3,6, 0,16, 1,4,2
Track  8: sector order:  0,16, 1,4,2,5,3,6
Track  9: sector order:  5,3,6, 0,16, 1,4,2
Track 10: sector order:  0,16, 1,4,2,5,3,6

Sector 0 starts with the following data, as you can see it clearly contains the sector header of sector 16! You can also see four weird bytes: 0x14, 0x14, 0x14, 0x00. These bytes are actually 3 sync markers 0xa1 and another sector header 0xfe, but shifted by one bit and the FDC will re-sync to them when trying to read sector 16. And because data bits and clock bits are interleaved, the re-sync will now actually read the clock bits instead of the data bits! The protection therefore reads the data bits via sector 0 (re-sync is disabled after a 0xFE is found for 1024 bytes plus CRC) and then the clock bits via sector 16.

0000   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
0010   A1 A1 A1 FE 07 00 10 03 BB 21 4E 4E 4E 4E 4E 4E    .........!NNNNNN
0020   4E 4E 4E 4E 4E 4E 4E 4E 4E 4E 4E 4E 4E 4E 4E 4E    NNNNNNNNNNNNNNNN
0030   FF FF FF FF FF FF FF FF FF FF FF FE 14 14 14 00    ................
0040   FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF    ................
0050   80 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00    ................
0060   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

The DMA read code ignores the first 64 bytes, which is a gap and the re-sync for the sector 16. The following 16 bytes have to contain 0xFF bytes and other 32 bytes have to have more than 204 cleared bits (out of 256 possible bits).

Sector 16 (the clock bits) starts with the following data:

0000   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
0010   40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    @...............
0020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

The first 16 bytes have to contain 0x00 bytes and other 32 bytes have to have more than 204 cleared bits (out of 256 possible bits), because of clock drift the FDC probably can randomly read a 1 instead of a 0.

copyProtection:
	MOVEM.L   A0-A6/D0-D7,-(A7)
	MOVE.L    #$100000,D0
L056B:SUBQ.L    #1,D0           ;delay
	BNE.S     L056B
	MOVE      SR,-(A7)
	BTST      #5,(A7)
	BNE.S     L056C
	CLR.L     -(A7)
	MOVE.W    #$20,-(A7) 	;SUPER
	TRAP      #1
	MOVE.L    D0,2(A7)
L056C:MOVE      #$2700,SR

	BSR       clearKeyboard

	LEA       $FFFF8000.W,A4  ;ffff8000
	LEA       1549(A4),A3     ;ffff860d
	LEA       1542(A4),A2     ;ffff8606
	LEA       1540(A4),A1     ;ffff8604
	LEA       31233(A4),A0    ;fffffa01

	MOVEQ     #5,D0
	BSR       selectFloppy

	MOVE.W    #$82,(A2)       ;track register
	BSR       fdcDelay
	MOVE.W    (A1),D0
	ANDI.W    #$FF,D0
	MOVE.W    D0,-(A7)        ;current track

	MOVE.W    #$80,(A2)
	MOVEQ     #$01,D2
	BSR       fdcCommand      ;Restore

	MOVEQ     #7,D7           ;start with track 7
L056D:MOVE.W    D7,D2
	BSR       fdcSeek
	BSR       checkSectors
	TST.W     D6
	BPL.S     L056F
	ADDQ.W    #1,D7
	CMPI.W    #10,D7          ;up to track 10
	BLE.S     L056D

	LEA       $80000,A7
L056E:CLR.L     -(A7)           ;erase everything and crash...
	BRA.S     L056E

L056F:MOVE.W    (A7)+,D2        ;current track
	BSR       fdcSeek
L0570:MOVE.W    (A1),D0         ;wait for motor off
	TST.B     D0
	BMI.S     L0570
	MOVEQ     #7,D0
	BSR       selectFloppy    ;deselect drive
	BSR.S     clearKeyboard
	MOVE.W    (A7)+,D0
	CMPI.W    #$20,D0         ;supervisor mode active?
	BNE.S     L0571
	MOVEA.L   (A7)+,A0
	MOVE.W    (A7)+,D0
	MOVE      A7,USP          ;back to user mode
	MOVEA.L   A0,A7
L0571:MOVE      D0,SR
	MOVEM.L   (A7)+,A0-A6/D0-D7
	BRA       copyProtectionReturn

clearKeyboard
	MOVEQ     #$FF,D0
L0573:BTST      #0,$FFFFFC00.W
	BEQ.S     L0574
	MOVE.B    $FFFFFC02.W,D0
	BRA.S     clearKeyboard
L0574:DBF       D0,L0573
	RTS

checkSectors:
	MOVEQ     #4,D6           ;5 tries
checkSectorsTries:
	MOVEQ     #0,D1           ;sector 0
	MOVEQ     #4,D5           ;skip 5 DMA buffer (one empty at the beginning)
	LEA       -128(A7),A5     ;base address
	BSR       readSector
	MOVEQ     #15,D0
L0577:CMPI.B    #$FF,(A5)+      ;16 0xFF bytes have to be at the beginning of the buffer
	BNE.S     checkSectorsFailed
	DBF       D0,L0577
	BSR       countClearedBits
	CMPI.W    #$CC,D0         ;at least 204 cleared bits have to be in the buffer
	BLT.S     checkSectorsFailed

	MOVEQ     #16,D1          ;sector 16
	MOVEQ     #0,D5           ;skip 1 DMA buffer (which is empty anyway)
	LEA       -64(A7),A5      ;base address
	BSR       readSector
	MOVEQ     #15,D0
L0578:TST.B     (A5)+           ;16 0x00 bytes have to be at the beginning of the buffer
	BNE.S     checkSectorsFailed
	DBF       D0,L0578
	BSR.S     countClearedBits
	CMPI.W    #$CC,D0         ;at least 204 cleared bits have to be in the buffer
	BGE.S     checkSectorsSuccess
checkSectorsFailed:
	DBF       D6,checkSectorsTries
checkSectorsSuccess:
	RTS

;count the number of cleared bits in 32 bytes
countClearedBits:
	MOVEQ     #0,D0
	MOVEQ     #31,D1
L057C:MOVEQ     #7,D2
L057D:BTST      D2,(A5)
	BNE.S     L057E
	ADDQ.L    #1,D0
L057E:DBF       D2,L057D
	ADDQ.L    #1,A5
	DBF       D1,L057C
	RTS

readSector:
	MOVE.L    A5,D0
	MOVE.B    D0,(A3)
	LSR.W     #8,D0
	MOVE.B    D0,1547(A4)     ;set the DMA address
	SWAP      D0
	MOVE.B    D0,1545(A4)
	MOVE.W    #$90,(A2)
	MOVE.W    #$190,(A2)      ;read
	MOVE.W    #$90,(A2)
	MOVEQ     #$10,D2
	BSR       fdcWriteD2      ;16*512 byte
	MOVE.W    #$84,(A2)
	MOVE.W    D1,D2           ;sector number
	BSR       fdcWriteD2
	MOVE.W    #$80,(A2)
	BSR       fdcDelay
	MOVE.W    #$80,(A1)       ;read sector

;5+3 DMA buffer = 128 bytes

L0580:MOVEM.L   (A5),D0-D3      ;read original bytes from the buffer
	SUBQ.W    #1,D5           ;5 DMA buffers
	BMI.S     L0582
	MOVE.W    A5,D4
L0581:CMP.B     (A3),D4         ;DMA low unchanged?
	BEQ.S     L0581           ;wait!
	MOVEM.L   D0-D3,(A5)
	LEA       16(A5),A5
	BRA.S     L0580

L0582:MOVEQ     #2,D1
L0583:MOVE.B    (A3),D0
L0584:CMP.B     (A3),D0         ;wait for DMA low to change 3 times
	BEQ.S     L0584
	DBF       D1,L0583

	MOVE.W    #$50,(A2)
	MOVE.W    #$150,(A2)      ;just weird...
	MOVE.W    #$50,(A2)
	BRA       fdcWait

	RTS

selectFloppy:
	MOVE.B    #$E,2048(A4)
	MOVEQ     #$F8,D1
	AND.B     2048(A4),D1
	OR.B      D0,D1
	MOVE.B    D1,2050(A4)
	RTS

fdcSeek:
	MOVE.W    #$86,(A2)
	BSR.S     fdcWriteD2
	MOVE.W    #$80,(A2)
	MOVEQ     #$11,D2
fdcCommand:
	BSR.S     fdcWriteD2
fdcWait:
	BTST      #5,(A0)
	BNE.S     fdcWait
	RTS

fdcWriteD2:
	BSR.S     fdcDelay
	MOVE.W    D2,(A1)

fdcDelay:
	MOVEQ     #$7F,D3
L058B:DBF       D3,L058B
	RTS

copyProtectionReturn:
	RTS
Previous post:
Next post: