Skip to navigation

Elite on the BBC Micro

Loader 1 source [BBC Micro 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 reconstructed from a disassembly of 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 = (_VARIANT = 1) _STH_DISC = (_VARIANT = 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%