Examining disk protections: Vorpal

Ruben had offered to help with nibbling protected Commodore disks, so today he sent me a NIB file that he saved using my nibbling software for IECHost and his Commodore 1571 disk drive.

The protected game he nibbled is “Boulder Dash Construction Kit”, the Epyx/MAXX-OUT release. On Pete Rittwage’s website the title is listed as using Vorpal. It doesn’t say which Vorpal (Early or Later version) so I tried to look for hints in the drive code.

A first section of drive code is loaded into the disk drive’s RAM from sector 18/6 (“hidden” in the DIR track – track 18 – i.e. not allocated in the BAM) with a Block-Execute command:

; File "(c) 1987 epyx"

*=$0809
S0809   LDY #$0D      ; Point to command at T0839, "#"
        LDA #$F2      ; Secondary address (open channel 2)
        JSR S0814     

        LDY #$00      ; Point to command at T082C, "B-E 2 0 18 6" (channel 2, drive 0, t/s 18/6)
        LDA #$FF      ; Secondary address (open channel 15)

S0814   PHA           
        LDA $BA       
        JSR $FFB1     ; LISTEN: command serial bus device to LISTEN
        PLA           
        JSR $FF93     ; SECOND: send secondary address after LISTEN

B081E   LDA T082C,Y   
        BEQ *+8       
        JSR $FFA8     ; CIOUT : output byte to serial bus
        INY           
        BNE B081E     

        JMP $FFAE     ; UNLSN : command serial bus device to UNLISTEN

T082C   .text "B-E 2 0 18 6"
        .byte $00

T0839   .text "#"
        .byte $00

The drive code relocates itself to $0700 onward and executes the rest of the code at $0724:

DBOOT   SEI           
        CLD           

        LDA #$08      
        STA $1800     

        LDA #$60      ; Code for RTS
        STA $0300     
        JSR $0300     ; Execute a controlled JSR

        TSX           ; Retrieve the stack pointer

        LDA $0100,X   ; Find out in which page the sector was loaded...
        STA $15       
        LDY #$00      
        STY $14       

        LDA ($14),Y   ; ... and move contents to $0700-$07ff
        STA $0700,Y   
        INY           
        BNE *-6       

        JMP $0724     ; Execute drive code

It then loads 4 pages worth of drive code using job queues, fetching them from “hidden” blocks in the DIR track:

B0724   LDX #$0E      ; Set up 4 READ jobs
        LDA T0745,X   
        STA $00,X     
        DEX           
        BPL *-6       

        CLI           
        LDA $00       
        ORA $01       
        ORA $02       
        ORA $03       
        BMI *-8       
        CMP #$01      ; Repeat until successful
        BNE B0724     

        LDA #$00      
        STA $1800     

        JMP $0403     ; Execute drive code

T0745   .byte $80,$80,$80,$80,$00,$00 ; Request 4 READ jobs

        .byte $12,$09 ; t/s 18/9
        .byte $12,$0C ; t/s 18/12
        .byte $12,$0F ; t/2 18/15
        .byte $12,$12 ; t/s 18/18

The rather interesting thing is that Vorpal code is showing up inside these 4 pages of drive code but some of it doesn’t appear to be executed, at least not until the point I load a cave file:

Boulder Dash Construction Kit: Diamond cave by Luigi Di Fraia
Boulder Dash Construction Kit: Diamond cave

What follows is the Vorpal code that checks for pre-sync bytes and for the length of 17 header and data blocks in track 20. It is perhaps the first time it’s published on the Web (although I might not be aware of other published code). However, it doesn’t appear to be executed.

V0753   SEI           

        JSR S0796     ; Step to track 19
        JSR S0796     ; Step to track 20

        LDA $1C00     
        AND #$9F      
        ORA #$40      
        STA $1C00     

        LDA $1C0C     
        ORA #$0E      
        STA $1C0C     

        JSR B07D7     ; Get past t/s 20/0's track value in header block

        LDA #$11      ; 17 sectors to check (1..17)
        STA $08       

        JSR S07BA     ; Skip the check on sector 0 data block

B0776   JSR S07BA     ; Measure header block size (GCR bytes) and read pre-sync value
        CMP #$AF      ; Pre-sync value expected for header block
        BNE B0793     
        CPY #$11      ; Number of GCR bytes expected in header block before pre-sync
        BNE B0793     ; e.g. for sector 1: 52,56,F5,2D,6E,9A,A6,A5,55,55,AA,AA,AA,AA,AA,AA,AA,AF,FF,FF,FF,FF,FF

        JSR S07BA     ; Measure data block size (GCR bytes) and read pre-sync value
        CMP #$FF      ; Pre-sync value expected for data block
        BNE B0793     
        CPY #$4D      ; LSB of number of GCR bytes expected in data block before pre-sync (333 = $014D -> LSB = $4D)
        BNE B0793     

        DEC $08       ; One less check to do
        BNE B0776     ; All 17 sectors done?

        JMP $0403     ; Success

B0793   JMP $EAA0     ; Protection check failed

; Step to next track

S0796   LDX #$02      
B0798   LDY $1C00     
        INY           
        TYA           
        EOR $1C00     
        AND #$03      
        EOR $1C00     
        STA $1C00     

        LDY #$0A      
        LDA #$C7      
        SEC           
        SBC #$01      
        BCS *-2       
        DEY           
        BNE *-8       
        DEX           
        BNE B0798     

        INC $22       ; Current track++

        RTS           

; Return the number of GCR bytes before the next sync in Y and the pre-sync value in A

S07BA   CLV           
        BVC *+0       
        CLV           

        LDY #$00      
J07C0   LDX $1C00     
        BPL B07CF     
        BVC J07C0     
        CLV           
        LDA $1C01     
        INY           
        JMP J07C0     

B07CF   BVC *+7       
        LDA $1C01     
        CLV           
        INY           
        RTS           

; Position the read head in the middle of the header block for t/s 20/0

B07D7   JSR S07EA     
B07DA   BVC B07DA     
        CLV           
        LDA $1C01     
        CMP T07F6,Y   
        BNE B07D7     
        INY           
        CPY #$05      
        BNE B07DA     

S07EA   BIT $1C00     
        BMI S07EA     
        LDA $1C01     
        CLV           
        LDY #$00      
        RTS           

; GCR values of the first 4 bytes (ID, checksum, sector, track) of the header block at t/s 20/0
; %01010,01001,01011,01110,01010,01010,01011,01110 = $08,$14,$00,$14 -> t/s 20/0

T07F6   .byte $52,$56,$E5,$29,$6E

The way I tested that this code is not executed is by using breakpoints, but also by altering the pre-sync byte in t/s 20/1 from:

52,56,F5,2D,6E,9A,A6,A5,55,55,AA,AA,AA,AA,AA,AA,AA,AF,FF,FF,FF,FF,FF

To:

52,56,F5,2D,6E,9A,A6,A5,55,55,AA,AA,AA,AA,AA,AA,AA,AA,FF,FF,FF,FF,FF

Even after the alteration, I was still able to edit a cave file without problems.

Is it possible that the above Vorpal code is executed later on? Perhaps when saving work to disk? I shall find out next time as it’s already half past midnight now 🙂

Stay tuned for more!

About Luigi Di Fraia

I am a Senior DevOps Engineer so I get to work with the latest technologies and open-source software. However, in my private time I enjoy retro-computing.
This entry was posted in Retrocomputing, Reverse Engineering, Technical and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s