Skip to navigation

Elite on the BBC Micro

Loader 1 source [Disc version]

DISC ELITE LOADER (PART 1) SOURCE Elite was written by Ian Bell and David Braben and is copyright Acornsoft 1984 The code on this site has been disassembled from the version released on Ian Bell's personal website at http://www.elitehomepage.org/ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes in the documentation are entirely my fault The terminology and notations used in this commentary are explained at https://www.bbcelite.com/about_site/terminology_used_in_this_commentary.html The deep dive articles referred to in this commentary can be found at https://www.bbcelite.com/deep_dives
This source file produces the following binary file: * ELITE2.bin
INCLUDE "1-source-files/main-sources/elite-header.h.asm" _IB_DISC = (_RELEASE = 1) _STH_DISC = (_RELEASE = 2) GUARD &7C00 \ Guard against assembling over screen memory
Configuration variables
BYTEV = &020A \ The BYTEV vector that we check as part of the copy \ protection OSWRCH = &FFEE \ The address for the OSWRCH routine OSBYTE = &FFF4 \ The address for the OSBYTE routine OSWORD = &FFF1 \ The address for the OSWORD routine OSCLI = &FFF7 \ The address for the OSCLI routine
Name: ZP [Show more] Type: Workspace Address: &0001 to &0002 Category: Workspaces Summary: Important variables used by the loader
Context: See this workspace on its own page References: This workspace is used as follows: * Elite loader calls ZP
ORG &0001 .ZP SKIP 2 \ Stores addresses used for moving content around
ELITE LOADER
CODE% = &2F00 LOAD% = &2F00 ORG CODE%
Name: BEGIN [Show more] Type: Subroutine Category: Copy protection Summary: Decrypt the loader code using a rolling EOR that uses the decryption routine itself to seed the decryption
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.BEGIN \ Note that the following copy protection code is \ skipped in the unprotected version built here, as the \ binaries are not encrypted and therefore do not need \ to be decyrpted \ \ Instead, the execution address for the ELITE2 binary \ points to ENTRY rather than BEGIN LDX p1c+1 \ Set X to the comparison value from the CMP instruction \ below, which has the value p1d - BEGIN, which we use \ as the offset of the first byte to decrypt (so we \ decrypt from p1d onwards) .p1 LDA BEGIN \ Fetch the first byte from BEGIN to act as the initial \ seed for the rolling EOR process (this address gets \ modified by the following to work through the whole \ decryption routine) .p1a EOR BEGIN,X \ Set A = A EOR the X-th byte of the loader STA BEGIN,X \ Store the decrypted byte in the X-th byte the loader INX \ Increment the pointer BNE p1a \ Loop back until we have decrypted to the end of the \ page .p1b INC p1+1 \ Increment the low byte of the argument to the LDA \ instruction at p1 above, so this would change it from \ LDA BEGIN to LDA BEGIN+1, for example (so the next \ time we do the EOR, we choose the next byte of the \ decryption routine as the initial seed) BEQ p1d \ If it equals zero (so the LDA BEGIN has worked itself \ to LDA BEGIN+255 and round again to LDA BEGIN), jump \ down to p1d as we have finally finished decrypting \ the code LDA p1+1 \ Fetch the newly incremented low byte of the argument \ to the LDA instruction at p1 above .p1c CMP #(p1d - BEGIN) \ If we have used all the bytes from the decryption BEQ p1b \ routine as seeds, skip one byte and then continue on \ to keep using seeds until we have done the whole page. \ This means we decrypt using the following seeds: \ \ * The contents of BEGIN \ * The contents of BEGIN+1 \ * The contents of BEGIN+2 \ ... \ * The contents of p1d-1 \ * The contents of p1d+1 \ ... \ * The contents of BEGIN+254 \ * The contents of BEGIN+255 \ \ and then we are done JMP BEGIN \ Otherwise look up to BEGIN to do another decryption \ run, but using the next byte of the decryption routine \ as the seed .p1d \ From this point on, the code is encrypted by \ elite-checksum.py BIT BYTEV+1 \ If the high byte of BYTEV does not have bit 7 set (so BPL BEGIN \ BYTEV is less than &8000, i.e. it's pointing to user \ RAM rather than the ROm routine), then jump up to \ BEGIN to hang machine, as it has presumably been \ changed by someone trying to crack the game \ Otherwise fall through into the now-decrypted loader \ code to get on with loading the game
Name: Elite loader [Show more] Type: Subroutine Category: Loader Summary: Reset vectors, change to mode 7, and load and run the ELITE3 loader
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.ENTRY LDA #0 \ Call OSBYTE with A = 0 and X = 1 to fetch bit 0 of the LDX #1 \ operating system version into X JSR OSBYTE LDY #0 \ Set Y to 0 so we can use it as an index for setting \ all the vectors to their default states SEI \ Disable all interrupts CPX #1 \ If X = 1 then this is OS 1.20, so jump to os120 BEQ os120 .os100 LDA &D941,Y \ Copy the Y-th byte from the default vector table at STA &0200 \ &D941 into location &0200 (this is surely supposed to \ be the Y-th byte in &0200, i.e. STA &0200,Y, but it \ isn't, which feels like a bug) INY \ Increment the loop counter CPY #54 \ Loop back to copy the next byte until we have copied BNE os100 \ 54 bytes (27 vectors) BEQ disk \ Jump down to disk to skip the OS 1.20 routine .os120 LDA &FFB7 \ Set ZP(1 0) to the location stored in &FFB7-&FFB8, STA ZP \ which contains the address of the default vector table LDA &FFB8 STA ZP+1 .ABCDEFG LDA (ZP),Y \ Copy the Y-th byte from the default vector table into STA &0200,Y \ the vector table in &0200 INY \ Increment the loop counter CPY &FFB6 \ Compare the loop counter with the contents of &FFB6, \ which contains the length of the default vector table BNE ABCDEFG \ Loop back for the next vector until we have done them \ all .disk CLI \ Re-enable interrupts LDX #LO(MESS1) \ Set (Y X) to point to MESS1 ("DISK") LDY #HI(MESS1) JSR OSCLI \ Call OSCLI to run the OS command in MESS1, which \ switches to the disc filing system (DFS) LDA #201 \ Call OSBYTE with A = 201, X = 1 and Y = 0 to disable LDX #1 \ the keyboard LDY #0 JSR OSBYTE LDA #200 \ Call OSBYTE with A = 200, X = 0 and Y = 0 to enable LDX #0 \ the ESCAPE key and disable memory clearing if the LDY #0 \ BREAK key is pressed JSR OSBYTE LDA #119 \ Call OSBYTE with A = 119 to close any *SPOOL or *EXEC JSR OSBYTE \ files LDY #0 \ Set Y to 0 so we can use it as an index for the \ following, which has been disabled (so perhaps this \ was part of the copy protection) .loop1 LDA BLOCK,Y \ Fetch the Y-th byte from BLOCK NOP \ This instruction has been disabled, so this loop does \ nothing INY \ Increment the loop counter CPY #9 \ Loop back to do the next byte until we have done 9 of BNE loop1 \ them LDY #0 \ We are now going to send the 12 VDU bytes in the table \ at B% to OSWRCH to switch to mode 7 .loop2 LDA B%,Y \ Pass the Y-th byte of the B% table to OSWRCH JSR OSWRCH INY \ Increment the loop counter CPY #12 \ Loop back for the next byte until we have done all 10 BNE loop2 \ of them .load3 LDX #LO(MESS2) \ Set (Y X) to point to MESS2 ("LOAD Elite3") LDY #HI(MESS2) JSR OSCLI \ Call OSCLI to run the OS command in MESS2, which loads \ the ELITE3 binary to its load address of &5700 JMP &5700 \ Jump to the start of the ELITE3 loader code at &5700 NOP \ These bytes appear to be unused NOP NOP NOP
Name: MESS2 [Show more] Type: Variable Category: Loader Summary: The OS command string for loading the the ELITE3 binary
Context: See this variable on its own page References: This variable is used as follows: * Elite loader calls MESS2
.MESS2 EQUS "LOAD Elite3" EQUB 13
Name: LOAD [Show more] Type: Subroutine Category: Copy protection Summary: Load a hidden file from disc (not used in this version as disc protection is disabled)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.LOAD JSR p2 \ Call p2 below JMP load3 \ Jump to load3 to load and run the next part of the \ loader LDA #2 \ Set PARAMS1+8 = 2, which is the track number in the STA PARAMS1+8 \ OSWORD parameter block LDA #127 \ Call OSWORD with A = 127 and (Y X) = PARAMS1 to seek LDX #LO(PARAMS1) \ disc track 2 LDY #HI(PARAMS1) JMP OSWORD .p2 STA PARAMS2+7 \ Set PARAMS2+7 = A, which is the track number in the \ OSWORD parameter block LDA #127 \ Call OSWORD with A = 127 and (Y X) = PARAMS2 to seek LDX #LO(PARAMS2) \ the disc track given in A LDY #HI(PARAMS2) JMP OSWORD
Name: B% [Show more] Type: Variable Category: Screen mode Summary: VDU commands for switching to a mode 7 screen
Context: See this variable on its own page References: This variable is used as follows: * Elite loader calls B%
.B% EQUB 22, 7 \ Switch to screen mode 7 EQUB 23, 0, 10, 32 \ Set 6845 register R10 = 32 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "cursor start" register, which sets the \ cursor start line at 0, so it turns the cursor off SKIP 2 \ These bytes appear to be unused
Name: PARAMS3 [Show more] Type: Variable Category: Copy protection Summary: OSWORD parameter block for loading the ELITE3 loader file (not used in this version as disc protection is disabled)
Context: See this variable on its own page References: No direct references to this variable in this source file
.PARAMS3 EQUB &FF \ 0 = Drive = &FF (previously used drive and density) EQUD &FFFF5700 \ 1 = Data address (&FFFF5700) EQUB 3 \ 5 = Number of parameters = 3 EQUB &53 \ 6 = Command = &53 (read data) EQUB 38 \ 7 = Track = 38 EQUB 246 \ 8 = Sector = 246 EQUB %00101001 \ 9 = Load 9 sectors of 256 bytes EQUB 0 \ 10 = The result of the OSWORD call is returned here
Name: PARAMS2 [Show more] Type: Variable Category: Copy protection Summary: OSWORD parameter block for accessing a specific track on the disc (not used in this version as disc protection is disabled)
Context: See this variable on its own page References: This variable is used as follows: * LOAD calls PARAMS2
.PARAMS2 EQUB &FF \ 0 = Drive = &FF (previously used drive and density) EQUD &FFFFFFFF \ 1 = Data address (not required) EQUB 1 \ 5 = Number of parameters = 1 EQUB &69 \ 6 = Command = &69 (seek track) EQUB 2 \ 7 = Parameter = 2 (track number) EQUB 0 \ 8 = The result of the OSWORD call is returned here
Name: PARAMS1 [Show more] Type: Variable Category: Copy protection Summary: OSWORD parameter block for accessing a specific track on the disc (not used in this version as disc protection is disabled)
Context: See this variable on its own page References: This variable is used as follows: * LOAD calls PARAMS1
.PARAMS1 EQUB &FF \ 0 = Drive = &FF (previously used drive and density) EQUD &FFFFFFFF \ 1 = Data address (not required) EQUB 2 \ 5 = Number of parameters = 2 EQUB &7A \ 6 = Command = &7A (write special register) EQUB &12 \ 7 = Parameter = &12 (register = track number) EQUB 38 \ 8 = Parameter = &26 (track number) EQUB &00 \ 9 = The result of the OSWORD call is returned here
Name: MESS1 [Show more] Type: Variable Category: Loader Summary: The OS command string for switching to the disc filing system
Context: See this variable on its own page References: This variable is used as follows: * Elite loader calls MESS1
.MESS1 EQUS "DISK" EQUB 13
Name: BLOCK [Show more] Type: Variable Category: Copy protection Summary: These bytes are copied and decrypted by the loader routine (not used in this version as disc protection is disabled)
Context: See this variable on its own page References: This variable is used as follows: * Elite loader calls BLOCK
.BLOCK EQUB &19, &7A, &02, &01, &EC, &19, &00, &56 EQUB &FF, &00, &00
Save ELITE2.bin
PRINT "S.ELITE2 ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD% SAVE "3-assembled-output/ELITE2.bin", CODE%, P%, LOAD%