Atari ST Protection: Silent Service, F15 Strike Eagle

This is a more interesting protection! For one it decrypts the track testing code only while it is executed (not really a problem with an Atari ST emulator) and encrypts it afterwards.

It reads track 79 and takes the very FIRST byte from the track and searches for the first position where this byte changes. Usually this is the first GAP before the first sector header and without a SYNC it can be shifted by even 1/2 bit by the FDC. However the code has a lookup table for all 16 possible bit-shift combinations and tests if this first byte plus the following 5 are in this table. This way even without a correct SYNC the protection test still succeeds!

But there is more! It skips sector 1..9 with their headers and data blocks and then stores the sector number of the first sector after these 9 sector (e.g. 0x13). This sector extends over the index mark and has to have random data at the end, because read track stops at the index mark. However up to 496 bytes at the beginning have to be identical. If there are no garbage data at the end of the sector, the protection fails, because the sector didn’t extend over the index!

As a last test the sector is read normally via XBIOS Floprd() and this call should return NO error (read sector doesn’t stop at the index mark, if it already started reading before the index mark). The whole 512 byte sector should have identical byte in it.

If all this succeeds, the copy protection test was successfull, too.

A very nice protection indeed!

silentServiceProtection:
    move.l  a6,$148d6
    lea     $1491a,a6
    movem.l d1-7/a0-5,-(a6)
    move.l  a6,$148da

    move.w  #$22,-(a7)
    trap    #14                 ;Kbdvbase()
    addq.w  #2,a7
    movea.l d0,a0
    move.l  16(a0),$148ae       ;->kb_mousevec

    move.l  $148ae,-(a7)
    lea     $13cd4,a0
    move.l  a0,-(a7)
    clr.w   -(a7)
    clr.w   -(a7)
    trap    #14                 ;Initmouse(0,...)
    adda.l  #12,a7

    move    sr,d0
    btst    #13,d0
    bne.s   L0102F4
    clr.l   -(a7)
    move.w  #$20,-(a7)
    trap    #1
    addq.w  #6,a7
    move.l  d0,$148ce
    move.l  #-1,$148c2
    bra.s   L0102FA
L0102F4
    clr.l   $148c2

L0102FA
    move.b  #$ff,$43e           ;set flock

    move    sr,-(a7)
    ori.w   #$700,sr
    clr.l   d0
    clr.l   d7
    move    (a7)+,sr

    move.w  #$19,-(a7)
    trap    #1                  ;Dgetdrv()
    addq.w  #2,a7
    cmpi.b  #1,d0
    bne.s   L01032A
    move.w  $4a6,d5
    cmpi.w  #2,d5
    beq.s   L01032A
    clr.l   d0
L01032A
    move.w  d0,$148ac

    addq.b  #1,d0               ;drive = 1/2
    lsl.b   #1,d0               ;drive = 2/4
    or.w    #0,d0               ;side 1
    eori.b  #7,d0
    and.b   #7,d0
    moveq   #2,d4               ;3 retries
    bsr     fdcSelect

    move.w  #$82,$ff8606        ;Track Register
    bsr     fdcReadD0
    move.w  d0,$148b4           ;save track register

cpRetry
    bsr     fdcRestore
    btst    #0,d6
    bne.l   cpFailed

    bsr     fdcSeek79
    btst    #0,d6
    bne.l   cpFailed

    clr.l   d3
    moveq   #2,d5
cpNextTrack:
    bsr     fdcReadTrack
    btst    #0,d6
    bne.l   cpFailed
    bsr     decryptCode

    lea     $1491e,a0       ;track buffer
    move.l  #$1f0,d0
    move.b  (a0)+,d7
cpSearch
    move.b  (a0)+,d6        ;find first changed byte
    cmp.b   d6,d7
    bne.s   cpNext
    dbra    d0,cpSearch
    bra.l   cpFailed
cpNext
    subq.l  #2,a0           ;back to the last byte from the block
    lea     $13c72,a2

    ;a possible bit shift mask
        $01,$03,$3F,$0F,$FF,$FF,
        $02,$06,$7C,$1F,$FF,$FF,
        $04,$0C,$F8,$3F,$FF,$FF,
        $08,$19,$F0,$7F,$FF,$FF,
        $10,$33,$E0,$FF,$FF,$FF,
        $20,$67,$C1,$FF,$FF,$FF,
        $40,$CF,$83,$FF,$FF,$FF,
        $80,$81,$9F,$07,$FF,$FF,

        $5E,$5C,$40,$B0,$00,$00,
        $BC,$B8,$81,$60,$00,$00,
        $97,$10,$2C,$00,$00,$00,
        $2F,$2E,$20,$58,$00,$00,
        $E5,$C4,$0B,$00,$00,$00,
        $CB,$88,$16,$00,$00,$00,
        $79,$71,$02,$C0,$00,$00,
        $F2,$E2,$05,$80,$00,$00,

        $55,$55

    move.b  (a0)+,d6        ;byte at the track beginning that was repeated
cpLoop
    move.b  (a2)+,d7        ;find the repeated byte
    cmp.b   #$55,d7
    beq.l   cpFailed
    cmp.b   d6,d7
    beq.s   cpNextB
    addq.w  #5,a2
    bra.s   cpLoop
cpNextB

    moveq   #4,d0
cpLoopB
    move.b  (a0)+,d6        ;the following 5 bytes have to be the same
    cmp.b   (a2)+,d6
    bne.l   cpFailed
    dbra    d0,cpLoopB

    ; skip 9 sectors 1..9 in the current track
    clr.b   d0
cpLoopC
    bsr     cpSearchSync    ;search for $A1,$A1
    addq.b  #1,d0           ;sector + 1
    move.b  (a0)+,d6
    cmp.b   #$fe,d6         ;Address Mark Header
    bne.l   cpFailed
    move.b  (a0)+,d6
    cmp.b   #$4f,d6         ;Track 79
    bne.l   cpFailed
    move.b  (a0)+,d6
    cmp.b   #0,d6           ;Side == 0
    bne.l   cpFailed
    move.b  (a0)+,d6
    cmp.b   d0,d6           ;Sector correct?
    bne.l   cpFailed

    bsr     cpSearchSync    ;search for $A1,$A1
    move.b  (a0)+,d6
    cmp.b   #$fb,d6         ;Data Header
    bne.l   cpFailed
    adda.l  #$200,a0        ;Skip Sector
    cmp.b   #9,d0
    bne.s   cpLoopC

    bsr     cpSearchSync    ;search for $A1,$A1
    move.b  (a0)+,d6
    cmp.b   #$fe,d6         ;Addres Mark Header
    bne.s   cpFailed
    move.b  (a0)+,d6
    movea.l #10,a6
    cmp.b   #$4f,d6         ;Track 79
    bne.s   cpFailed
    move.b  (a0)+,d6
    cmp.b   #0,d6           ;Side == 0
    bne.s   cpFailed
    clr.w   d6
    move.b  (a0)+,d6        ;Sector >= 10
    cmp.b   #10,d6
    blt.s   cpFailed
    move.w  d6,$13c6e       ;save found sector number ($0D)

    bsr     cpSearchSync    ;search for $A1,$A1
    move.b  (a0)+,d6
    cmp.b   #$fb,d6         ;Data Header
    bne.s   cpFailed

    clr.w   d0
    clr.w   d6
    move.b  (a0)+,d6
    move.w  d6,$13c70       ;first byte in the sector ($E5)
cpLoopD
    addq.w  #1,d0
    move.b  (a0)+,d6
    cmp.w   $13c70,d6       ;skip same bytes (first byte in the sector ($E5))
    bne.s   cpNextC
    cmp.w   #$1f0,d0        ;too many bytes?
    beq.s   cpFailed
    bra.s   cpLoopD
cpNextC

    cmp.b   #0,d6           ;end of track reached before end of sector?
    beq.s   cpOut

cpFailed
    clr.w   $13c6e          ;save found sector number = illegal

    bsr     encryptCode
    dbra    d5,cpNextTrack
    dbra    d4,cpRetry

    bsr     fdcDone
    move.b  d0,d6
    cmpi.l  #$0,$148c2
    beq.s   L0104A8
    move.l  $148ce,-(a7)
    move.w  #$20,-(a7)
    trap    #1
    addq.w  #6,a7
L0104A8
    bsr     cpCreateReturncode
    bsr     cpRestoreRegister
    rts

cpOut
    bsr     encryptCode
    bsr     fdcDone

    cmpi.l  #$0,$148c2
    beq.s   L0104D4
    move.l  $148ce,-(a7)
    move.w  #$20,-(a7)
    trap    #1
    addq.w  #6,a7

L0104D4
    move.w  #1,-(a7)            ;count
    move.w  #0,-(a7)            ;sideno
    move.w  #$4f,-(a7)          ;trackno
    move.w  $13c6e,-(a7)        ;sectno = saved found sector number ($0D)
    move.w  $148ac,-(a7)        ;devno
    clr.l   -(a7)               ;filler
    lea     $1491e,a0           ;track buffer
    move.l  a0,-(a7)
    move.w  #8,-(a7)
    trap    #14                 ;int16_t Floprd( void *buf, int32_t filler, int16_t devno, int16_t sectno, int16_t trackno, int16_t sideno, int16_t count )
    adda.l  #20,a7
    cmp.l   #0,d0
    beq.s   L010512             ;should be 0! =>
    andi.l  #$ffffff,d0
L010510
    rts

L010512
    ori.l   #$37000000,d0

    ;the sector has to have all the same byte
    move.l  #511,d7
    lea     $1491e,a0           ;track buffer
    clr.w   d6
L010526
    move.b  (a0)+,d6
    cmp.w   $13c70,d6           ;first byte in the sector ($E5)
    bne.s   L010510
    dbra    d7,L010526

    ori.l   #$4f,d0
    bsr     cpCreateReturncode
    bsr     cpRestoreRegister
    rts

fdcReadTrack:
    move.l  d7,$148ca
    lea     $1491e,a0           ;track buffer
    move.l  a0,$148d2
    move.b  $148d5,$ff860d
    move.b  $148d4,$ff860b
    move.b  $148d3,$ff8609
    move.l  #$680,d7
fdcReadTrackLoopA
    clr.l   (a0)+
    dbra    d7,fdcReadTrackLoopA
    move.w  #$90,$ff8606
    move.w  #$190,$ff8606
    move.w  #$90,$ff8606
    move.w  #31,d7
    bsr     fdcWriteD7          ;31 DMA sectors (31*512 bytes)
    move.w  #$80,$ff8606
    move.w  #$e4,d7
    bsr     fdcWriteD7          ;Read Track
    move.l  #$40000,d7
    move.b  #255,d6             ;Failed flag
fdcReadTrackLoopB
    btst    #5,$fffa01
    beq.s   fdcReadTrackDone
    subq.l  #1,d7
    bne.s   fdcReadTrackLoopB
    bsr     fdcForceInterrupt
    rts
fdcReadTrackDone
    move.w  #$90,$ff8606
    move.w  $ff8606,d0
    btst    #0,d0               ;DMA ok?
    beq.s   fdcReadTrackFailed
    clr.b   d6                  ;success
fdcReadTrackFailed
    rts

cpRestoreRegister:
    movea.l $148da,a6
    movem.l (a6)+,d1-7/a0-5
    movea.l $148d6,a6
    rts

cpSearchSync:
    move.l  #$1f0,d7
cpSearchSyncLoop
    move.b  (a0)+,d6
    cmp.b   #$a1,d6
    bne.s   cpSearchSyncCheck
    move.b  (a0)+,d6
    cmp.b   #$a1,d6
    bne.s   cpSearchSyncCheck
    rts
cpSearchSyncCheck
    dbra    d7,cpSearchSyncLoop
    suba.l  a6,a6
    addq.w  #4,a7
    bra.l   cpFailed

cpCreateReturncode:             ;$3700004F
    andi.l  #$ff0000ff,d0
    clr.l   d7
    move.w  $13c70,d7           ;first byte in the sector ($E5)
    rol.w   #8,d7
    or.w    $13c6e,d7           ;save found sector number ($0D)
    rol.w   #4,d7
    rol.l   #8,d7
    or.l    d7,d0               ;== $3750DE4F (Silent Service) / $37512E4F (F15 Strike Eagle)
    rts

Calculation after return:
0 == ($0062EB61 ^ $3750DE4F) - $3732352E


fdcDone
    movea.l $148c6,a5
    move.w  $148b4,d7           ;save track register
    bsr     fdcSeekD7
fdcDoneLoop
    move.w  #$80,$ff8606
    bsr     fdcReadD0           ;wait for the motor off
    btst    #7,d0
    bne.s   fdcDoneLoop
    move.b  d2,d0
    move.b  #$0,$43e            ;reset flock
    bsr     fdcSelect           ;deselect floppy

    move.l  $148ae,-(a7)
    lea     $13cd4,a0
    move.l  a0,-(a7)
    move.w  #1,-(a7)
    clr.w   -(a7)
    trap    #14                 ;Initmouse(1,...)
    adda.l  #12,a7
    rts

CryptRetA0Plus_16
    addq.l  #1,a0
    bsr CryptRetA0Plus_10
CryptRetA0Plus_5
    subq.l  #1,a0
    bsr CryptRetA0Plus_5B
CryptRetA0Plus_1
    subq.l  #1,a0
    bsr CryptRetA0Plus_2
CryptRetA0Plus_0
    rts


fdcWriteD7
    bsr.s   fdcDelay
    move.w  d7,$ff8604

fdcDelay
    move    sr,-(a7)
    move.w  d7,-(a7)
    move.w  #32,d7
fdcDelayLoop
    dbra    d7,fdcDelayLoop
    move.w  (a7)+,d7
    move    (a7)+,sr
    rts

encryptCode:
    bsr.s   CryptBlock
    move    $148b2,sr
    rts

decryptCode:
    move    sr,$148b2
    ori.w   #$700,sr
    bsr.s   CryptBlock
    rts

CryptBlock
    move    sr,d7
    lsr.w   #8,d7
    bset    #6,d7
    bclr    #4,d7
    bclr    #3,d7

    lea     $13ce1,a0
    lea     $1038a,a2
    bsr.s   CryptRetA0Plus_16
    addq.l  #1,a0               ;A0 = $13CF2
    moveq   #8,d0
    bsr.s   CryptBlockSub

    lea     $13ce1,a0
    lea     $103a8,a2
    bsr     CryptRetA0Plus_10   ;A0 = $13CF5
    moveq   #34,d0
    bsr.s   CryptBlockSub

    lea     $13ce1,a0
    lea     $103fe,a2
    bsr     CryptRetA0Plus_5B   ;A0 = $13CE6
    moveq   #26,d0
    bsr.s   CryptBlockSub

    addq.l  #5,a0               ;A0 = $13D06
    lea     $10444,a2
    moveq   #4,d0
    bsr.s   CryptBlockSub

    lea     $13ce1,a6
    lea     $10466,a2
    bsr     CryptRetA0Plus_1
    bsr     CryptRetA0Plus_2    ;A0 = $13D0E
    moveq   #4,d0
    bsr.s   CryptBlockSub
    rts

CryptBlockSub
    clr.l   d6
    move.b  (a0)+,d6
    adda.l  d6,a2
    eor.b   d7,(a2)
    dbra    d0,CryptBlockSub
    rts


fdcSelect:
    move    sr,-(a7)
    ori.w   #$700,sr
    move.b  #$e,$ff8800
    move.b  $ff8800,d1
    move.b  d1,d2
    and.b   #$f8,d1
    or.b    d0,d1
    move.b  d1,$ff8802
    move    (a7)+,sr
    rts


fdcReadD0
    bsr     fdcDelay
    move.w  $ff8604,d0
    bra.l   fdcDelay


fdcRestore
    move.w  #$03,d7
    bsr.s   fdcCommandAndWait
fdcRestoreLoop
    subq.l  #1,d6
    beq.s   fdcRestoreTimeout
    btst    #5,$fffa01
    bne.s   fdcRestoreLoop
    clr.l   d6
    move.w  #$80,$ff8606
    bsr.s   fdcReadD0
    btst    #2,d0           ;Track 0?
    bne.s   fdcRestoreSuccess
fdcRestoreTimeout
    bsr.s   fdcForceInterrupt
    moveq   #1,d6
fdcRestoreSuccess
    rts


fdcForceInterrupt
    move.w  #$80,$ff8606
    move.w  #$d0,d7
    bsr     fdcWriteD7
    bsr     fdcDelay
    rts


fdcSeek79
    move.w  #$4f,d7
fdcSeekD7
    move.w  #$86,$ff8606
    bsr     fdcWriteD7
    move.w  #$13,d7
    bsr.s   fdcCommandAndWait
fdcSeek79Loop
    subq.l  #1,d6
    beq.s   fdcSeek79Done
    btst    #5,$fffa01
    bne.s   fdcSeek79Loop
fdcSeek79Done
    clr.l   d6
    rts


fdcCommandAndWait
    move.l  #$40000,d6
    move.w  #$80,$ff8606
    bsr     fdcReadD0
    btst    #7,d0
    bne.s   fdcCommandAndWait2
    move.l  #$60000,d6
fdcCommandAndWait2
    bsr     fdcWriteD7
    rts


CryptRetA0Plus_10
    bsr     CryptRetA0Plus_5
CryptRetA0Plus_5B
    addq.l  #2,a0
    bsr     CryptRetA0Plus_1
CryptRetA0Plus_2
    addq.l  #2,a0
    bsr     CryptRetA0Plus_0
    rts