INCLUDE "1-source-files/main-sources/elite-build-options.asm" _SOURCE_DISC = (_VARIANT = 1) _TEXT_SOURCES = (_VARIANT = 2) _STH_CASSETTE = (_VARIANT = 3) GUARD &6000 \ Guard against assembling over screen memoryELITE LOADER SOURCE Elite was written by Ian Bell and David Braben and is copyright Acornsoft 1984 The code on this site is identical to the source discs released on Ian Bell's personal website at http://www.elitehomepage.org/ (it's just been reformatted to be more readable) 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://elite.bbcelite.com/terminology The deep dive articles referred to in this commentary can be found at https://elite.bbcelite.com/deep_dives
This source file produces the following binary file: * ELITE.unprot.bin after reading in the following files: * DIALS.bin * P.ELITE.bin * P.A-SOFT.bin * P.(C)ASFT.bin * WORDS9.bin * PYTHON.binDISC = _DISC \ Set to TRUE to load the code above DFS and relocate \ down, so we can load the cassette version from disc, \ or set to FALSE to load the code as if it were from a \ cassette PROT = _PROT \ Set to TRUE to enable the tape protection code, or set \ to FALSE to disable the code (for TRUE, the file must \ be saved to tape with the block data corrupted, so you \ probably want to leave this as FALSE) IF DISC CODE% = &1100 \ CODE% is set to the assembly address of the loader \ code file that we assemble in this file ("ELITE"), \ which is at the lowest DFS page value of &1100 for the \ version that loads from disc \ \ [Show more]Configuration variables\ \ This variable is used by the following: \ \ * Elite loader (Part 3 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveLOAD% = &1100 \ LOAD% is the load address of the main game code file \ ("ELTcode" for loading from disc, "ELITEcode" for \ loading from tape) \ \ [Show more]\ \ This variable is used by the following: \ \ * CHECKER \ * CHECKV \ * IRQ1 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveL% = &1128 \ L% points to the start of the actual game code from \ elite-source.asm, after the &28 bytes of header code \ that are inserted by elite-bcfs.asm \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 5 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveELSE CODE% = &0E00 \ CODE% is set to the assembly address of the loader \ code file that we assemble in this file ("ELITE"), \ which is at the standard &0E00 address for the version \ that loads from cassette \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 3 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveLOAD% = &0F1F \ LOAD% is the load address of the main game code file \ ("ELTcode" for loading from disc, "ELITEcode" for \ loading from tape) \ \ [Show more]\ \ This variable is used by the following: \ \ * CHECKER \ * CHECKV \ * IRQ1 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveL% = &0F47 \ L% points to the start of the actual game code from \ elite-source.asm, after the &28 bytes of header code \ that are inserted by elite-bcfs.asm \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 5 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveENDIF LEN1 = 15 \ Size of the BEGIN% routine that gets pushed onto the \ stack and executed there \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 3 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveLEN2 = 18 \ Size of the MVDL routine that gets pushed onto the \ stack and executed there LEN = LEN1 + LEN2 \ Total number of bytes that get pushed on the stack for \ execution there (33) \ \ [Show more]\ \ This variable is used by the following: \ \ * BLOCK \ * David23 \ * Elite loader (Part 2 of 6) \ * Elite loader (Part 5 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveVSCAN = 57-1 \ Defines the split position in the split-screen mode \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 4 of 6) \ * Elite loader (Part 6 of 6) \ * IRQ1 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveLE% = &0B00 \ LE% is the address to which the code from UU% onwards \ is copied in part 3. It contains: \ \ * ENTRY2, the entry point for the second block of \ loader code \ \ * IRQ1, the interrupt routine for the split-screen \ mode \ \ * BLOCK, which by this point has already been put \ on the stack by this point \ \ * The variables used by the above \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 3 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveNETV = &0224 \ The NETV vector that we intercept as part of the copy \ protection \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveIRQ1V = &0204 \ The IRQ1V vector that we intercept to implement the \ split-screen mode \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 4 of 6) \ * Elite loader (Part 6 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveOSPRNT = &0234 \ The address for the OSPRNT vector \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 5 of 6) \ * osprint \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveC% = &0F40 \ C% is set to the location that the main game code gets \ moved to after it is loaded \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 4 of 6) \ * Elite loader (Part 5 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveS% = C% \ S% points to the entry point for the main game code \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 5 of 6) \ * Elite loader (Part 6 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveIF _SOURCE_DISC D% = &563A \ D% is set to the address of the byte after the end of \ the code, i.e. the byte after checksum0 at XX21 \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 6 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveELIF _TEXT_SOURCES D% = &5638 \ D% is set to the address of the byte after the end of \ the code, i.e. the byte after checksum0 at XX21 \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 6 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveELIF _STH_CASSETTE D% = &563A \ D% is set to the address of the byte after the end of \ the code, i.e. the byte after checksum0 at XX21 \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 6 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveENDIF LC% = &6000 - C% \ LC% is set to the maximum size of the main game code \ (as the code starts at C% and screen memory starts \ at &6000) \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 5 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveN% = 67 \ N% is set to the number of bytes in the VDU table, so \ we can loop through them in part 2 below \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveSVN = &7FFD \ SVN is where we store the "saving in progress" flag, \ and it matches the location in elite-source.asm \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 5 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveVEC = &7FFE \ VEC is where we store the original value of the IRQ1 \ vector, and it matches the value in elite-source.asm \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 4 of 6) \ * IRQ1 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveVIA = &FE00 \ Memory-mapped space for accessing internal hardware, \ such as the video ULA, 6845 CRTC and 6522 VIAs (also \ known as SHEILA) \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 4 of 6) \ * Elite loader (Part 6 of 6) \ * IRQ1 \ * PLL1 (Part 1 of 3) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveOSWRCH = &FFEE \ The address for the OSWRCH routine \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveOSBYTE = &FFF4 \ The address for the OSBYTE routine \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ * Elite loader (Part 4 of 6) \ * Elite loader (Part 5 of 6) \ * Elite loader (Part 6 of 6) \ * OSB \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveOSWORD = &FFF1 \ The address for the OSWORD routine \ \ [Show more]\ \ This variable is used by the following: \ \ * FNE \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveORG &0004 .TRTB% SKIP 2 \ Contains the address of the keyboard translation \ table, which is used to translate internal key \ numbers to ASCII \ \ [Show more]Name: ZP [Show more] Type: Workspace Address: &0004 to &0005 and &0070 to &0086 Category: Workspaces Summary: Important variables used by the loaderContext: See this workspace on its own page Variations: See code variations for this workspace in the different versions References: This workspace is used as follows: * CHECKER uses ZP * DOMOVE uses ZP * Elite loader (Part 2 of 6) uses ZP * Elite loader (Part 3 of 6) uses ZP * Elite loader (Part 5 of 6) uses ZP * Elite loader (Part 6 of 6) uses ZP * PIX uses ZP * PLL1 (Part 1 of 3) uses ZP * PLL1 (Part 2 of 3) uses ZP * PLL1 (Part 3 of 3) uses ZP * ROOT uses ZP * TT26 uses ZP\ \ This variable is used by the following: \ \ * doPROT1 \ * Elite loader (Part 2 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveORG &0070 .ZP SKIP 2 \ Stores addresses used for moving content around \ \ [Show more]\ \ This variable is used by the following: \ \ * CHECKER \ * DOMOVE \ * Elite loader (Part 2 of 6) \ * Elite loader (Part 3 of 6) \ * Elite loader (Part 5 of 6) \ * Elite loader (Part 6 of 6) \ * PIX \ * PLL1 (Part 1 of 3) \ * PLL1 (Part 2 of 3) \ * PLL1 (Part 3 of 3) \ * ROOT \ * TT26 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.P SKIP 1 \ Temporary storage, used in a number of places \ \ [Show more]\ \ This variable is used by the following: \ \ * DOMOVE \ * Elite loader (Part 3 of 6) \ * Elite loader (Part 5 of 6) \ * PLL1 (Part 1 of 3) \ * ROOT \ * SQUA2 \ * TT26 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.Q SKIP 1 \ Temporary storage, used in a number of places \ \ [Show more]\ \ This variable is used by the following: \ \ * ROOT \ * SQUA2 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.YY SKIP 1 \ Temporary storage, used in a number of places \ \ [Show more]\ \ This variable is used by the following: \ \ * PLL1 (Part 1 of 3) \ * PLL1 (Part 2 of 3) \ * PLL1 (Part 3 of 3) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.T SKIP 1 \ Temporary storage, used in a number of places \ \ [Show more]\ \ This variable is used by the following: \ \ * PLL1 (Part 3 of 3) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.SC SKIP 1 \ Screen address (low byte) \ \ Elite draws on-screen by poking bytes directly into \ screen memory, and SC(1 0) is typically set to the \ address of the character block containing the pixel \ we want to draw (see the deep dives on "Drawing \ monochrome pixels in mode 4" and "Drawing colour \ pixels in mode 5" for more details) \ \ [Show more]\ \ This variable is used by the following: \ \ * TT26 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.SCH SKIP 1 \ Screen address (high byte) .BLPTR SKIP 2 \ Gets set to &03CA as part of the obfuscation code \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ * IRQ1 \ * PLL1 (Part 2 of 3) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.V219 SKIP 2 \ Gets set to &0218 as part of the obfuscation code \ \ [Show more]\ \ This variable is used by the following: \ \ * doPROT1 \ * Elite loader (Part 2 of 6) \ * Elite loader (Part 3 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveSKIP 4 \ These bytes appear to be unused .K3 SKIP 1 \ Temporary storage, used in a number of places \ \ [Show more]\ \ This variable is used by the following: \ \ * TT26 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.BLCNT SKIP 2 \ Stores the tape loader block count as part of the copy \ protection code in IRQ1 \ \ [Show more]\ \ This variable is used by the following: \ \ * CHECKER \ * Elite loader (Part 3 of 6) \ * IRQ1 \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.BLN SKIP 2 \ Gets set to &03C6 as part of the obfuscation code \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ * IRQ1 \ * PLL1 (Part 2 of 3) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.EXCN SKIP 2 \ Gets set to &03C2 as part of the obfuscation code \ \ [Show more]\ \ This variable is used by the following: \ \ * Elite loader (Part 2 of 6) \ * IRQ1 \ * PLL1 (Part 1 of 3) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned aboveORG CODE%ELITE LOADERPRINT "WORDS9 = ", ~P% INCBIN "3-assembled-output/WORDS9.bin" ALIGN 256 PRINT "P.DIALS = ", ~P% INCBIN "1-source-files/images/P.DIALS.bin" PRINT "PYTHON = ", ~P% INCBIN "3-assembled-output/PYTHON.bin" PRINT "P.ELITE = ", ~P% INCBIN "1-source-files/images/P.ELITE.bin" PRINT "P.A-SOFT = ", ~P% INCBIN "1-source-files/images/P.A-SOFT.bin" PRINT "P.(C)ASFT = ", ~P% INCBIN "1-source-files/images/P.(C)ASFT.bin" .run JMP ENTRY \ Jump to ENTRY to start the loading processName: Elite loader (Part 1 of 6) [Show more] Type: Subroutine Category: Loader Summary: Include binaries for recursive tokens, Python blueprint and imagesContext: See this subroutine on its own page References: No direct references to this subroutine in this source file
The loader bundles a number of binary files in with the loader code, and moves them to their correct memory locations in part 3 below. There are two files containing code: * WORDS9.bin contains the recursive token table, which is moved to &0400 before the main game is loaded * PYTHON.bin contains the Python ship blueprint, which gets moved to &7F00 before the main game is loaded and four files containing images, which are all moved into screen memory by the loader: * P.A-SOFT.bin contains the "ACORNSOFT" title across the top of the loading screen, which gets moved to screen address &6100, on the second character row of the monochrome mode 4 screen * P.ELITE.bin contains the "ELITE" title across the top of the loading screen, which gets moved to screen address &6300, on the fourth character row of the monochrome mode 4 screen * P.(C)ASFT.bin contains the "(C) Acornsoft 1984" title across the bottom of the loading screen, which gets moved to screen address &7600, the penultimate character row of the monochrome mode 4 screen, just above the dashboard * P.DIALS.bin contains the dashboard, which gets moved to screen address &7800, which is the starting point of the four-colour mode 5 portion at the bottom of the split screen The routine ends with a jump to the start of the loader code at ENTRY..B% EQUB 22, 4 \ Switch to screen mode 4 EQUB 28 \ Define a text window as follows: EQUB 2, 17, 15, 16 \ \ * Left = 2 \ * Right = 15 \ * Top = 16 \ * Bottom = 17 \ \ i.e. 1 row high, 13 columns wide at (2, 16) EQUB 23, 0, 6, 31 \ Set 6845 register R6 = 31 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "vertical displayed" register, and sets \ the number of displayed character rows to 31. For \ comparison, this value is 32 for standard modes 4 and \ 5, but we claw back the last row for storing code just \ above the end of screen memory EQUB 23, 0, 12, &0C \ Set 6845 register R12 = &0C and R13 = &00 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This sets 6845 registers (R12 R13) = &0C00 to point EQUB 23, 0, 13, &00 \ to the start of screen memory in terms of character EQUB 0, 0, 0 \ rows. There are 8 pixel lines in each character row, EQUB 0, 0, 0 \ so to get the actual address of the start of screen \ memory, we multiply by 8: \ \ &0C00 * 8 = &6000 \ \ So this sets the start of screen memory to &6000 EQUB 23, 0, 1, 32 \ Set 6845 register R1 = 32 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "horizontal displayed" register, which \ defines the number of character blocks per horizontal \ character row. For comparison, this value is 40 for \ modes 4 and 5, but our custom screen is not as wide at \ only 32 character blocks across EQUB 23, 0, 2, 45 \ Set 6845 register R2 = 45 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "horizontal sync position" register, which \ defines the position of the horizontal sync pulse on \ the horizontal line in terms of character widths from \ the left-hand side of the screen. For comparison this \ is 49 for modes 4 and 5, but needs to be adjusted for \ our custom screen's width EQUB 23, 0, 10, 32 \ Set 6845 register R10 = %00100000 = 32 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "cursor start" register, and bits 5 and 6 \ define the "cursor display mode", as follows: \ \ * %00 = steady, non-blinking cursor \ \ * %01 = do not display a cursor \ \ * %10 = fast blinking cursor (blink at 1/16 of the \ field rate) \ \ * %11 = slow blinking cursor (blink at 1/32 of the \ field rate) \ \ We can therefore turn off the cursor completely by \ setting cursor display mode %01, with bit 6 of R10 \ clear and bit 5 of R10 setName: B% [Show more] Type: Variable Category: Drawing the screen Summary: VDU commands for setting the square mode 4 screen Deep dive: The split-screen mode in BBC Micro Elite Drawing monochrome pixels in mode 4Context: See this variable on its own page Variations: See code variations for this variable in the different versions References: This variable is used as follows: * Elite loader (Part 2 of 6) uses B%
This block contains the bytes that get written by OSWRCH to set up the screen mode (this is equivalent to using the VDU statement in BASIC). It defines the whole screen using a square, monochrome mode 4 configuration; the mode 5 part for the dashboard is implemented in the IRQ1 routine. The top part of Elite's screen mode is based on mode 4 but with the following differences: * 32 columns, 31 rows (256 x 248 pixels) rather than 40, 32 * The horizontal sync position is at character 45 rather than 49, which pushes the screen to the right (which centres it as it's not as wide as the normal screen modes) * Screen memory goes from &6000 to &7EFF, which leaves another whole page for code (i.e. 256 bytes) after the end of the screen. This is where the Python ship blueprint slots in * The text window is 1 row high and 13 columns wide, and is at (2, 16) * The cursor is disabled This almost-square mode 4 variant makes life a lot easier when drawing to the screen, as there are 256 pixels on each row (or, to put it in screen memory terms, there's one page of memory per row of pixels). For more details of the screen mode, see the deep dive on "Drawing monochrome pixels in mode 4". There is also an interrupt-driven routine that switches the bytes-per-pixel setting from that of mode 4 to that of mode 5, when the raster reaches the split between the space view and the dashboard. See the deep dive on "The split-screen mode" for details..E% EQUB 1, 1, 0, 111, -8, 4, 1, 8, 8, -2, 0, -1, 112, 44 EQUB 2, 1, 14, -18, -1, 44, 32, 50, 6, 1, 0, -2, 120, 126 EQUB 3, 1, 1, -1, -3, 17, 32, 128, 1, 0, 0, -1, 1, 1 EQUB 4, 1, 4, -8, 44, 4, 6, 8, 22, 0, 0, -127, 126, 0Name: E% [Show more] Type: Variable Category: Sound Summary: Sound envelope definitionsContext: See this variable on its own page Variations: See code variations for this variable in the different versions References: This variable is used as follows: * FNE uses E%
This table contains the sound envelope data, which is passed to OSWORD by the FNE macro to create the four sound envelopes used in-game. Refer to chapter 30 of the BBC Micro User Guide for details of sound envelopes and what all the parameters mean. The envelopes are as follows: * Envelope 1 is the sound of our own laser firing * Envelope 2 is the sound of lasers hitting us, or hyperspace * Envelope 3 is the first sound in the two-part sound of us dying, or the second sound in the two-part sound of us hitting or killing an enemy ship * Envelope 4 is the sound of E.C.M. firing.swine LDA #%01111111 \ Set 6522 System VIA interrupt enable register IER STA &FE4E \ (SHEILA &4E) bits 0-6 (i.e. disable all hardware \ interrupts from the System VIA) JMP (&FFFC) \ Jump to the address in &FFFC to reset the machineName: swine [Show more] Type: Subroutine Category: Copy protection Summary: Resets the machine if the copy protection detects a problemContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: This subroutine is called as follows: * Elite loader (Part 4 of 6) calls swine.OSB LDY #0 \ Call OSBYTE with Y = 0, returning from the subroutine JMP OSBYTE \ using a tail call (so we can call OSB to call OSBYTE \ for when we know we want Y set to 0)Name: OSB [Show more] Type: Subroutine Category: Utility routines Summary: A convenience routine for calling OSBYTE with Y = 0Context: See this subroutine on its own page References: This subroutine is called as follows: * DOMOVE calls OSB * Elite loader (Part 2 of 6) calls OSBEQUS "R.ELITEcode" \ This is short for "*RUN ELITEcode" EQUB 13 EQUS "By D.Braben/I.Bell" EQUB 13 EQUB &B0Name: Authors' names [Show more] Type: Variable Category: Copy protection Summary: The authors' names, buried in the codeContext: See this variable on its own page Variations: See code variations for this variable in the different versions References: No direct references to this variable in this source file
Contains the authors' names, plus an unused OS command string that would *RUN the main game code, which isn't what actually happens (so presumably this is to throw the crackers off the scent)..oscliv EQUW &FFF7 \ Address of OSCLIV, for executing OS commands \ (specifically the *LOAD that loads the main game code)Name: oscliv [Show more] Type: Variable Category: Utility routines Summary: Contains the address of OSCLIV, for executing OS commandsContext: See this variable on its own page References: This variable is used as follows: * command uses oscliv.David9 EQUW David5 \ The address of David5 CLD \ This instruction is not usedName: David9 [Show more] Type: Variable Category: Copy protection Summary: Address used as part of the stack-based decryption loopContext: See this variable on its own page References: This variable is used as follows: * BEGIN% uses David9
This address is used in the decryption loop starting at David2 in part 4, and is used to jump back into the loop at David5..David23 EQUW (512-LEN) \ The address of LEN bytes before the start of the stackName: David23 [Show more] Type: Variable Category: Copy protection Summary: Address pointer to the start of the 6502 stackContext: See this variable on its own page Variations: See code variations for this variable in the different versions References: This variable is used as follows: * Elite loader (Part 4 of 6) uses David23
This two-byte address points to the start of the 6502 stack, which descends from the end of page 2, less LEN bytes, which comes out as &01DF. So when we push 33 bytes onto the stack (LEN being 33), this address will point to the start of those bytes, which means we can push executable code onto the stack and run it by calling this address with a JMP (David23) instruction. Sneaky stuff!.doPROT1 LDY #&DB \ Store &EFDB in TRTB%(1 0) to point to the keyboard STY TRTB% \ translation table for OS 0.1 (which we will overwrite LDY #&EF \ with a call to OSBYTE later) STY TRTB%+1 LDY #2 \ Set the high byte of V219(1 0) to 2 STY V219+1 STA PROT1-255,X \ Poke &48 into PROT1, which changes the instruction \ there to a PHA LDY #&18 \ Set the low byte of V219(1 0) to &18 (as X = 255), so STY V219+1,X \ V219(1 0) now contains &0218 RTS \ Return from the subroutineName: doPROT1 [Show more] Type: Subroutine Category: Copy protection Summary: Routine to self-modify the loader codeContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: This subroutine is called as follows: * Elite loader (Part 2 of 6) calls doPROT1
This routine modifies various bits of code in-place as part of the copy protection mechanism. It is called with A = &48 and X = 255..MHCA EQUB &CA \ The low byte of BLPTR(1 0)Name: MHCA [Show more] Type: Variable Category: Copy protection Summary: Used to set one of the vectors in the copy protection codeContext: See this variable on its own page References: This variable is used as follows: * PLL1 (Part 2 of 3) uses MHCA
This value is used to set the low byte of BLPTR(1 0), when it's set in PLL1 as part of the copy protection..David7 BCC Ian1 \ This instruction is part of the multi-jump obfuscation \ in PROT1Name: David7 [Show more] Type: Subroutine Category: Copy protection Summary: Part of the multi-jump obfuscation code in PROT1Context: See this subroutine on its own page References: This subroutine is called as follows: * Elite loader (Part 2 of 6) calls David7
This instruction is part of the multi-jump obfuscation in PROT1 (see part 2 of the loader), which does the following jumps: David8 -> FRED1 -> David7 -> Ian1 -> David3MACRO FNE I% LDX #LO(E%+I%*14) \ Set (Y X) to point to the I%-th set of envelope data LDY #HI(E%+I%*14) \ in E% LDA #8 \ Call OSWORD with A = 8 to set up sound envelope I% JSR OSWORD ENDMACROName: FNE [Show more] Type: Macro Category: Sound Summary: Macro definition for defining a sound envelopeContext: See this macro on its own page References: This macro is used as follows: * Elite loader (Part 2 of 6) uses FNE
The following macro is used to define the four sound envelopes used in the game. It uses OSWORD 8 to create an envelope using the 14 parameters in the I%-th block of 14 bytes at location E%. This OSWORD call is the same as BBC BASIC's ENVELOPE command. See variable E% for more details of the envelopes themselves..ENTRY SEI \ Disable all interrupts CLD \ Clear the decimal flag, so we're not in decimal mode IF NOT(DISC) LDA #0 \ Call OSBYTE with A = 0 and X = 255 to fetch the LDX #255 \ operating system version into X JSR OSBYTE TXA \ If X = 0 then this is OS 1.00, so jump down to OS100 BEQ OS100 \ to skip the following LDY &FFB6 \ Otherwise this is OS 1.20, so set Y to the contents of \ &FFB6, which contains the length of the default vector \ table 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 DEY \ Decrement Y so we can use it as an index for setting \ all the vectors to their default states .ABCDEFG LDA (ZP),Y \ Copy the Y-th byte from the default vector table into STA &0200,Y \ the vector table in &0200 DEY \ Decrement the loop counter BPL ABCDEFG \ Loop back for the next vector until we have done them \ all .OS100 ENDIF LDA #%01111111 \ Set 6522 System VIA interrupt enable register IER STA &FE4E \ (SHEILA &4E) bits 0-6 (i.e. disable all hardware \ interrupts from the System VIA) STA &FE6E \ Set 6522 User VIA interrupt enable register IER \ (SHEILA &6E) bits 0-6 (i.e. disable all hardware \ interrupts from the User VIA) LDA &FFFC \ Fetch the low byte of the reset address in &FFFC, \ which will reset the machine if called STA &0200 \ Set the low bytes of USERV, BRKV, IRQ2V and EVENTV STA &0202 STA &0206 STA &0220 LDA &FFFD \ Fetch the high byte of the reset address in &FFFD, \ which will reset the machine if called STA &0201 \ Set the high bytes of USERV, BRKV, IRQ2V and EVENTV STA &0203 STA &0207 STA &0221 LDX #&2F-2 \ We now step through all the vectors from &0204 to \ &022F and OR their high bytes with &C0, so they all \ point into the MOS ROM space (which is from &C000 and \ upwards), so we set a counter in X to count through \ them .purge LDA &0202,X \ Set the high byte of the vector in &0202+X so it ORA #&C0 \ points to the MOS ROM STA &0202,X DEX \ Increment the counter to point to the next high byte DEX BPL purge \ Loop back until we have done all the vectors LDA #&60 \ Store an RTS instruction in location &0232 STA &0232 LDA #&2 \ Point the NETV vector to &0232, which we just filled STA NETV+1 \ with an RTS LDA #&32 STA NETV LDA #&20 \ Set A to the op code for a JSR call with absolute \ addressing EQUB &2C \ Skip the next instruction by turning it into \ &2C &D0 &66, or BIT &66D0, which does nothing apart \ from affect the flags .Ian1 BNE David3 \ This instruction is skipped if we came from above, \ otherwise this is part of the multi-jump obfuscation \ in PROT1 STA David2 \ Store &20 in location David2, which modifies the \ instruction there (see David2 for details) LSR A \ Set A = 16 LDX #3 \ Set the high bytes of BLPTR(1 0), BLN(1 0) and STX BLPTR+1 \ EXCN(1 0) to &3. We will fill in the high bytes in STX BLN+1 \ the PLL1 routine, and will then use these values in STX EXCN+1 \ the IRQ1 handler DEX \ Set X = 2 JSR OSBYTE \ Call OSBYTE with A = 16 and X = 2 to set the ADC to \ sample 2 channels from the joystick EQUB &2C \ Skip the next instruction by turning it into \ &2C &D0 &A1, or BIT &A1D0, which does nothing apart \ from affect the flags .FRED1 BNE David7 \ This instruction is skipped if we came from above, \ otherwise this is part of the multi-jump obfuscation \ in PROT1 LDX #255 \ Call doPROT1 to change an instruction in the PROT1 LDA #&48 \ routine and set up another couple of variables JSR doPROT1 LDA #144 \ Call OSBYTE with A = 144, X = 255 and Y = 0 to move JSR OSB \ the screen down one line and turn screen interlace on LDA #247 \ Call OSBYTE with A = 247 and X = Y = 0 to disable the LDX #0 \ BREAK intercept code by poking 0 into the first value JSR OSB \LDA #129 \ These instructions are commented out in the original \LDY #255 \ source, along with the comment "Damn 0.1", so \LDX #1 \ presumably MOS version 0.1 was a bit of a pain to \JSR OSBYTE \ support - which is probably why Elite doesn't bother \TXA \ and only supports 1.0 and 1.2 \BPL OS01 \Damn 0.1 LDA #190 \ Call OSBYTE with A = 190, X = 8 and Y = 0 to set the LDX #8 \ ADC conversion type to 8 bits, for the joystick JSR OSB EQUB &2C \ Skip the next instruction by turning it into \ &2C &D0 &E1, or BIT &E1D0, which does nothing apart \ from affect the flags .David8 BNE FRED1 \ This instruction is skipped if we came from above, \ otherwise this is part of the multi-jump obfuscation \ in PROT1 LDA #143 \ Call OSBYTE 143 to issue a paged ROM service call of LDX #&C \ type &C with argument &FF, which is the "NMI claim" LDY #&FF \ service call that asks the current user of the NMI JSR OSBYTE \ space to clear it out LDA #13 \ Set A = 13 for the next OSBYTE call .abrk LDX #0 \ Call OSBYTE with A = 13, X = 0 and Y = 0 to disable JSR OSB \ the "output buffer empty" event LDA #225 \ Call OSBYTE with A = 225, X = 128 and Y = 0 to set LDX #128 \ the function keys to return ASCII codes for SHIFT-fn JSR OSB \ keys (i.e. add 128) LDA #172 \ Call OSBYTE 172 to read the address of the MOS LDX #0 \ keyboard translation table into (Y X) LDY #255 JSR OSBYTE STX TRTB% \ Store the address of the keyboard translation table in STY TRTB%+1 \ TRTB%(1 0) LDA #200 \ Call OSBYTE with A = 200, X = 3 and Y = 0 to disable LDX #3 \ the ESCAPE key and clear memory if the BREAK key is JSR OSB \ pressed IF PROT AND NOT(DISC) CPX #3 \ If the previous value of X from the call to OSBYTE 200 BNE abrk+1 \ was not 3 (ESCAPE disabled, clear memory), jump to \ abrk+1, which contains a BRK instruction which will \ reset the computer (as we set BRKV to point to the \ reset address above) ENDIF LDA #13 \ Call OSBYTE with A = 13, X = 2 and Y = 0 to disable LDX #2 \ the "character entering keyboard buffer" event JSR OSB .OS01 LDX #&FF \ Set the stack pointer to &01FF, which is the standard TXS \ location for the 6502 stack, so this instruction \ effectively resets the stack INX \ Set X = 0, to use as a counter in the following loop .David3 LDA BEGIN%,X \ This routine pushes 33 bytes from BEGIN% onto the \ stack, so fetch the X-th byte from BEGIN% .PROT1 INY \ This instruction gets changed to a PHA instruction by \ the doPROT1 routine that's called above, so by the \ time we get here, this instruction actually pushes the \ X-th byte from BEGIN% onto the stack INX \ Increment the loop counter CPX #LEN \ If X < #LEN (which is 33), loop back for the next one. BNE David8 \ This branch actually takes us on a wild goose chase \ through the following locations, where each BNE is \ prefaced by an EQUB &2C that disables the branch \ instruction during the normal instruction flow: \ \ David8 -> FRED1 -> David7 -> Ian1 -> David3 \ \ so in the end this just loops back to push the next \ byte onto the stack, but in a really sneaky way LDA #LO(B%) \ Set the low byte of ZP(1 0) to point to the VDU code STA ZP \ table at B% LDA #&C8 \ Poke &C8 into PROT1 to change the instruction that we STA PROT1 \ modified back to an INY instruction, rather than a PHA LDA #HI(B%) \ Set the high byte of ZP(1 0) to point to the VDU code STA ZP+1 \ table at B% LDY #0 \ We are now going to send the N% VDU bytes in the table \ at B% to OSWRCH to set up the special mode 4 screen \ that forms the basis for the split-screen mode .LOOP LDA (ZP),Y \ Pass the Y-th byte of the B% table to OSWRCH JSR OSWRCH INY \ Increment the loop counter CPY #N% \ Loop back for the next byte until we have done them BNE LOOP \ all (the number of bytes was set in N% above) LDA #1 \ In doPROT1 above we set V219(1 0) = &0218, so this TAX \ code sets the contents of &0219 (the high byte of TAY \ BPUTV) to 1. We will see why this later, at the start STA (V219),Y \ of part 4 LDA #4 \ Call OSBYTE with A = 4, X = 1 and Y = 0 to disable JSR OSB \ cursor editing, so the cursor keys return ASCII values \ and can therefore be used in-game LDA #9 \ Call OSBYTE with A = 9, X = 0 and Y = 0 to disable LDX #0 \ flashing colours JSR OSB LDA #&6C \ Poke &6C into crunchit after EOR'ing it first (which EOR crunchit \ has no effect as crunchit contains a BRK instruction STA crunchit \ with opcode 0), to change crunchit to an indirect JMP FNE 0 \ Set up sound envelopes 0-3 using the FNE macro FNE 1 FNE 2 FNE 3Name: Elite loader (Part 2 of 6) [Show more] Type: Subroutine Category: Loader Summary: Perform a number of OS calls, set up sound, push routines on stackContext: See this subroutine on its own page References: This subroutine is called as follows: * David7 calls via Ian1
This part of the loader does a number of calls to OS routines, sets up the sound envelopes, pushes 33 bytes onto the stack that will be used later, and sends us on a wild goose chase, just for kicks.
Other entry points: Ian1 Re-entry point following the wild goose chase obfuscationLDX #4 \ Set the following: STX P+1 \ LDA #HI(CODE%) \ P(1 0) = &0400 STA ZP+1 \ ZP(1 0) = CODE% LDY #0 \ (X Y) = &400 = 1024 LDA #256-LEN1 \ STA (V219-4,X) \ In doPROT1 above we set V219(1 0) = &0218, so this STY ZP \ also sets the contents of &0218 (the low byte of STY P \ BPUTV) to 256 - LEN1, or &F1. We set the low byte to \ 1 above, so BPUTV now contains &01F1, which we will \ use at the start of part 4 JSR crunchit \ Call crunchit, which has now been modified to call the \ MVDL routine on the stack, to move and decrypt &400 \ bytes from CODE% to &0400. We loaded WORDS9.bin to \ CODE% in part 1, so this moves WORDS9 LDX #1 \ Set the following: LDA #(HI(CODE%)+&C) \ STA ZP+1 \ P(1 0) = &6300 LDA #&63 \ ZP(1 0) = CODE% + &C STA P+1 \ (X Y) = &100 = 256 LDY #0 JSR crunchit \ Call crunchit to move and decrypt &100 bytes from \ CODE% + &C to &6300, so this moves P.ELITE LDX #1 \ Set the following: LDA #(HI(CODE%)+&D) \ STA ZP+1 \ P(1 0) = &6100 LDA #&61 \ ZP(1 0) = CODE% + &D STA P+1 \ (X Y) = &100 = 256 LDY #0 JSR crunchit \ Call crunchit to move and decrypt &100 bytes from \ CODE% + &D to &6100, so this moves P.A-SOFT LDX #1 \ Set the following: LDA #(HI(CODE%)+&E) \ STA ZP+1 \ P(1 0) = &7600 LDA #&76 \ ZP(1 0) = CODE% + &E STA P+1 \ (X Y) = &100 = 256 LDY #0 JSR crunchit \ Call crunchit to move and decrypt &100 bytes from \ CODE% + &E to &7600, so this moves P.(C)ASFT JSR PLL1 \ Call PLL1 to draw Saturn LDX #8 \ Set the following: LDA #(HI(CODE%)+4) \ STA ZP+1 \ P(1 0) = &7800 LDA #&78 \ ZP(1 0) = CODE% + &4 STA P+1 \ (X Y) = &800 = 2048 LDY #0 \ STY ZP \ Also set BLCNT = 0 STY BLCNT STY P JSR crunchit \ Call crunchit to move and decrypt &800 bytes from \ CODE% + &4 to &7800, so this moves P.DIALS and PYTHON IF DISC LDX #2 \ Set the following: LDA #HI(UU%) \ STA ZP+1 \ P(1 0) = LE% LDA #LO(UU%) \ ZP(1 0) = UU% STA ZP \ (X Y) = &200 = 512 (as we are building for disc) LDA #HI(LE%) STA P+1 LDY #0 STY P ELSE LDX #3 \ Set the following: LDA #HI(UU%) \ STA ZP+1 \ P(1 0) = LE% LDA #LO(UU%) \ ZP(1 0) = UU% STA ZP \ (X Y) = &300 = 768 (as we are building for tape) LDA #HI(LE%) STA P+1 LDY #0 STY P ENDIF JSR crunchit \ Call crunchit to move and decrypt either &200 or &300 \ bytes from UU% to LE%, leaving X = 0Name: Elite loader (Part 3 of 6) [Show more] Type: Subroutine Category: Loader Summary: Move and decrypt recursive tokens, Python blueprint and imagesContext: See this subroutine on its own page References: No direct references to this subroutine in this source file
Move and decrypt the following memory blocks: * WORDS9: move 4 pages (1024 bytes) from CODE% to &0400 * P.ELITE: move 1 page (256 bytes) from CODE% + &0C00 to &6300 * P.A-SOFT: move 1 page (256 bytes) from CODE% + &0D00 to &6100 * P.(C)ASFT: move 1 page (256 bytes) from CODE% + &0E00 to &7600 * P.DIALS and PYTHON: move 8 pages (2048 bytes) from CODE% + &0400 to &7800 * Move 2 pages (512 bytes) from UU% to &0B00-&0CFF and call the routine to draw Saturn between P.(C)ASFT and P.DIALS. See part 1 above for more details on the above files and the locations that they are moved to. The code at UU% (see below) forms part of the loader code and is moved before being run, so it's tucked away safely while the main game code is loaded and decrypted.STY David3-2 \ Y was set to 0 above, so this modifies the OS01 \ routine above by changing the TXS instruction to BRK, \ so calls to OS01 will now do this: \ \ LDX #&FF \ BRK \ \ This is presumably just to confuse any cracker, as we \ don't call OS01 again \ We now enter a loop that starts with the counter in Y \ (initially set to 0). It calls JSR &01F1 on the stack, \ which pushes the Y-th byte of BLOCK on the stack \ before encrypting the Y-th byte of BLOCK in-place. It \ then jumps back to David5 below, where we increment Y \ until it reaches a value of ENDBLOCK - BLOCK. So this \ loop basically decrypts the code from TUT onwards, and \ at the same time it pushes the code between BLOCK and \ ENDBLOCK onto the stack, so it's there ready to be run \ (at address &0163) .David2 EQUB &AC \ This byte was changed to &20 by part 2, so by the time EQUW &FFD4 \ we get here, these three bytes together become JSR \ &FFD4, or JSR OSBPUT. Amongst all the code above, \ we've also managed to set BPUTV to &01F1, and as BPUTV \ is the vector that OSBPUT goes through, these three \ bytes are actually doing JSR &01F1 \ \ That address is in the stack, and is the address of \ the BEGIN% routine, which we pushed onto the stack in \ the modified PROT1 routine. That routine doesn't \ return with an RTS, but instead it removes the return \ address from the stack and jumps to David5 below after \ pushing the Y-th byte of BLOCK onto the stack and \ EOR'ing the Y-th byte of TUT with the Y-th byte of \ BLOCK \ \ This obfuscation probably kept the crackers busy for a \ while - it's difficult enough to work out when you \ have the source code in front of you! .LBLa \ If, for some reason, the above JSR doesn't call the \ routine on the stack and returns normally, which might \ happen if crackers manage to unpick the BPUTV \ redirection, then we end up here. We now obfuscate the \ first 255 bytes of the location where the main game \ gets loaded (which is set in C%), just to make things \ hard, and then we reset the machine... all in a \ completely twisted manner, of course LDA C%,X \ Obfuscate the X-th byte of C% by EOR'ing with &A5 EOR #&A5 STA C%,X DEX \ Decrement the loop counter BNE LBLa \ Loop back until X wraps around, after EOR'ing a whole \ page JMP (C%+&CF) \ C%+&CF is &100F, which in the main game code contains \ an LDA KY17 instruction (it's in the main loader in \ the MA76 section). This has opcode &A5 &4E, and the \ EOR above changes the first of these to &00, so this \ jump goes to a BRK instruction, which in turn goes to \ BRKV, which in turn resets the computer (as we set \ BRKV to point to the reset address in part 2) .swine2 JMP swine \ Jump to swine to reset the machine EQUW &4CFF \ This data doesn't appear to be used .crunchit BRK \ This instruction gets changed to an indirect JMP at EQUW David23 \ the end of part 2, so this does JMP (David23). David23 \ contains &01DF, so these bytes are actually doing JMP \ &01DF. That address is in the stack, and is the \ address of the MVDL routine, which we pushed onto the \ stack in the modified PROT1 routine... so this \ actually does the following: \ \ JMP MVDL \ \ meaning that this instruction: \ \ JSR crunchit \ \ actually does this, because it's a tail call: \ \ JSR MVDL \ \ It's yet another impressive bit of obfuscation and \ misdirection .RAND EQUD &6C785349 \ The random number seed used for drawing Saturn .David5 INY \ Increment the loop counter CPY #(ENDBLOCK-BLOCK) \ Loop back to copy the next byte until we have copied BNE David2 \ all the bytes between BLOCK and ENDBLOCK SEI \ Disable interrupts while we set up our interrupt \ handler to support the split-screen mode LDA #%11000010 \ Clear 6522 System VIA interrupt enable register IER STA VIA+&4E \ (SHEILA &4E) bits 1 and 7 (i.e. enable CA1 and TIMER1 \ interrupts from the System VIA, which enable vertical \ sync and the 1 MHz timer, which we need enabled for \ the split-screen interrupt code to work) LDA #%01111111 \ Set 6522 User VIA interrupt enable register IER STA &FE6E \ (SHEILA &6E) bits 0-7 (i.e. disable all hardware \ interrupts from the User VIA) LDA IRQ1V \ Store the low byte of the current IRQ1V vector in VEC STA VEC LDA IRQ1V+1 \ If the current high byte of the IRQ1V vector is less BPL swine2 \ than &80, which means it points to user RAM rather \ the MOS ROM, then something is probably afoot, so jump \ to swine2 to reset the machine STA VEC+1 \ Otherwise all is well, so store the high byte of the \ current IRQ1V vector in VEC+1, so VEC(1 0) now \ contains the original address of the IRQ1 handler LDA #HI(IRQ1) \ Set the IRQ1V vector to IRQ1, so IRQ1 is now the STA IRQ1V+1 \ interrupt handler LDA #LO(IRQ1) STA IRQ1V LDA #VSCAN \ Set 6522 System VIA T1C-L timer 1 high-order counter STA VIA+&45 \ (SHEILA &45) to VSCAN (56) to start the T1 counter \ counting down from 14080 at a rate of 1 MHz (this is \ a different value to the main game code) CLI \ Re-enable interrupts IF DISC LDA #%10000001 \ Clear 6522 System VIA interrupt enable register IER STA &FE4E \ (SHEILA &4E) bit 1 (i.e. enable the CA2 interrupt, \ which comes from the keyboard) LDY #20 \ Set Y = 20 for the following OSBYTE call IF _REMOVE_CHECKSUMS NOP \ If we have disabled checksums, skip the OSBYTE call NOP NOP ELSE JSR OSBYTE \ A was set to 129 above, so this calls OSBYTE with \ A = 129 and Y = 20, as well as a value of X = 0, \ which was set by the call to crunchit at the end of \ part 3 \ \ This reads the keyboard with a time limit of (Y X) \ centiseconds, or 20 * 256 = 5120 centiseconds, or \ 51.2 seconds ENDIF LDA #%00000001 \ Set 6522 System VIA interrupt enable register IER STA &FE4E \ (SHEILA &4E) bit 1 (i.e. disable the CA2 interrupt, \ which comes from the keyboard) ENDIF RTS \ This RTS actually does a jump to ENTRY2, to the next \ step of the loader in part 5. See the documentation \ for the stack routine at BEGIN% for more detailsName: Elite loader (Part 4 of 6) [Show more] Type: Subroutine Category: Loader Summary: Copy more code onto stack, decrypt TUT block, set up IRQ1 handlerContext: See this subroutine on its own page References: No direct references to this subroutine in this source file
This part copies more code onto the stack (from BLOCK to ENDBLOCK), decrypts the code from TUT onwards, and sets up the IRQ1 handler for the split-screen mode..PLL1 \ The following loop iterates CNT(1 0) times, i.e. &500 \ or 1280 times, and draws the planet part of the \ loading screen's Saturn LDA VIA+&44 \ Read the 6522 System VIA T1C-L timer 1 low-order STA RAND+1 \ counter (SHEILA &44), which decrements one million \ times a second and will therefore be pretty random, \ and store it in location RAND+1, which is among the \ main game code's random seeds in RAND (so this seeds \ the random number generator) JSR DORND \ Set A and X to random numbers, say A = r1 JSR SQUA2 \ Set (A P) = A * A \ = r1^2 STA ZP+1 \ Set ZP(1 0) = (A P) LDA P \ = r1^2 STA ZP JSR DORND \ Set A and X to random numbers, say A = r2 STA YY \ Set YY = A \ = r2 JSR SQUA2 \ Set (A P) = A * A \ = r2^2 TAX \ Set (X P) = (A P) \ = r2^2 LDA P \ Set (A ZP) = (X P) + ZP(1 0) ADC ZP \ STA ZP \ first adding the low bytes TXA \ And then adding the high bytes ADC ZP+1 BCS PLC1 \ If the addition overflowed, jump down to PLC1 to skip \ to the next pixel STA ZP+1 \ Set ZP(1 0) = (A ZP) \ = r1^2 + r2^2 LDA #1 \ Set ZP(1 0) = &4001 - ZP(1 0) - (1 - C) SBC ZP \ = 128^2 - ZP(1 0) STA ZP \ \ (as the C flag is clear), first subtracting the low \ bytes LDA #&40 \ And then subtracting the high bytes SBC ZP+1 STA ZP+1 BCC PLC1 \ If the subtraction underflowed, jump down to PLC1 to \ skip to the next pixel \ If we get here, then both calculations fitted into \ 16 bits, and we have: \ \ ZP(1 0) = 128^2 - (r1^2 + r2^2) \ \ where ZP(1 0) >= 0 JSR ROOT \ Set ZP = SQRT(ZP(1 0)) LDA ZP \ Set X = ZP >> 1 LSR A \ = SQRT(128^2 - (a^2 + b^2)) / 2 TAX LDA YY \ Set A = YY \ = r2 CMP #128 \ If YY >= 128, set the C flag (so the C flag is now set \ to bit 7 of A) ROR A \ Rotate A and set the sign bit to the C flag, so bits \ 6 and 7 are now the same, i.e. A is a random number in \ one of these ranges: \ \ %00000000 - %00111111 = 0 to 63 (r2 = 0 - 127) \ %11000000 - %11111111 = 192 to 255 (r2 = 128 - 255) \ \ The PIX routine flips bit 7 of A before drawing, and \ that makes -A in these ranges: \ \ %10000000 - %10111111 = 128-191 \ %01000000 - %01111111 = 64-127 \ \ so that's in the range 64 to 191 JSR PIX \ Draw a pixel at screen coordinate (X, -A), i.e. at \ \ (ZP / 2, -A) \ \ where ZP = SQRT(128^2 - (r1^2 + r2^2)) \ \ So this is the same as plotting at (x, y) where: \ \ r1 = random number from 0 to 255 \ r2 = random number from 0 to 255 \ (r1^2 + r2^2) < 128^2 \ \ y = r2, squished into 64 to 191 by negation \ \ x = SQRT(128^2 - (r1^2 + r2^2)) / 2 \ \ which is what we want .PLC1 DEC CNT \ Decrement the counter in CNT (the low byte) BNE PLL1 \ Loop back to PLL1 until CNT = 0 DEC CNT+1 \ Decrement the counter in CNT+1 (the high byte) BNE PLL1 \ Loop back to PLL1 until CNT+1 = 0 LDX #&C2 \ Set the low byte of EXCN(1 0) to &C2, so we now have STX EXCN \ EXCN(1 0) = &03C2, which we will use in the IRQ1 \ handler (this has nothing to do with drawing Saturn, \ it's all part of the copy protection)Name: PLL1 (Part 1 of 3) [Show more] Type: Subroutine Category: Drawing planets Summary: Draw Saturn on the loading screen (draw the planet) Deep dive: Drawing Saturn on the loading screenContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: This subroutine is called as follows: * Elite loader (Part 3 of 6) calls PLL1\ The following loop iterates CNT2(1 0) times, i.e. &1DD \ or 477 times, and draws the background stars on the \ loading screen .PLL2 JSR DORND \ Set A and X to random numbers, say A = r3 TAX \ Set X = A \ = r3 JSR SQUA2 \ Set (A P) = A * A \ = r3^2 STA ZP+1 \ Set ZP+1 = A \ = r3^2 / 256 JSR DORND \ Set A and X to random numbers, say A = r4 STA YY \ Set YY = r4 JSR SQUA2 \ Set (A P) = A * A \ = r4^2 ADC ZP+1 \ Set A = A + r3^2 / 256 \ = r4^2 / 256 + r3^2 / 256 \ = (r3^2 + r4^2) / 256 CMP #&11 \ If A < 17, jump down to PLC2 to skip to the next pixel BCC PLC2 LDA YY \ Set A = r4 JSR PIX \ Draw a pixel at screen coordinate (X, -A), i.e. at \ (r3, -r4), where (r3^2 + r4^2) / 256 >= 17 \ \ Negating a random number from 0 to 255 still gives a \ random number from 0 to 255, so this is the same as \ plotting at (x, y) where: \ \ x = random number from 0 to 255 \ y = random number from 0 to 255 \ (x^2 + y^2) div 256 >= 17 \ \ which is what we want .PLC2 DEC CNT2 \ Decrement the counter in CNT2 (the low byte) BNE PLL2 \ Loop back to PLL2 until CNT2 = 0 DEC CNT2+1 \ Decrement the counter in CNT2+1 (the high byte) BNE PLL2 \ Loop back to PLL2 until CNT2+1 = 0 LDX MHCA \ Set the low byte of BLPTR(1 0) to the contents of MHCA STX BLPTR \ (which is &CA), so we now have BLPTR(1 0) = &03CA, \ which we will use in the IRQ1 handler (this has \ nothing to do with drawing Saturn, it's all part of \ the copy protection) LDX #&C6 \ Set the low byte of BLN(1 0) to &C6, so we now have STX BLN \ BLN(1 0) = &03C6, which we will use in the IRQ1 \ handler (this has nothing to do with drawing Saturn, \ it's all part of the copy protection)Name: PLL1 (Part 2 of 3) [Show more] Type: Subroutine Category: Drawing planets Summary: Draw Saturn on the loading screen (draw the stars) Deep dive: Drawing Saturn on the loading screenContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: No direct references to this subroutine in this source file\ The following loop iterates CNT3(1 0) times, i.e. &500 \ or 1280 times, and draws the rings around the loading \ screen's Saturn .PLL3 JSR DORND \ Set A and X to random numbers, say A = r5 STA ZP \ Set ZP = r5 JSR SQUA2 \ Set (A P) = A * A \ = r5^2 STA ZP+1 \ Set ZP+1 = A \ = r5^2 / 256 JSR DORND \ Set A and X to random numbers, say A = r6 STA YY \ Set YY = r6 JSR SQUA2 \ Set (A P) = A * A \ = r6^2 STA T \ Set T = A \ = r6^2 / 256 ADC ZP+1 \ Set ZP+1 = A + r5^2 / 256 STA ZP+1 \ = r6^2 / 256 + r5^2 / 256 \ = (r5^2 + r6^2) / 256 LDA ZP \ Set A = ZP \ = r5 CMP #128 \ If A >= 128, set the C flag (so the C flag is now set \ to bit 7 of ZP, i.e. bit 7 of A) ROR A \ Rotate A and set the sign bit to the C flag, so bits \ 6 and 7 are now the same CMP #128 \ If A >= 128, set the C flag (so again, the C flag is \ set to bit 7 of A) ROR A \ Rotate A and set the sign bit to the C flag, so bits \ 5-7 are now the same, i.e. A is a random number in one \ of these ranges: \ \ %00000000 - %00011111 = 0-31 \ %11100000 - %11111111 = 224-255 \ \ In terms of signed 8-bit integers, this is a random \ number from -32 to 31. Let's call it r7 ADC YY \ Set A = A + YY \ = r7 + r6 TAX \ Set X = A \ = r6 + r7 JSR SQUA2 \ Set (A P) = A * A \ = (r6 + r7)^2 TAY \ Set Y = A \ = (r6 + r7)^2 / 256 ADC ZP+1 \ Set A = A + ZP+1 \ = (r6 + r7)^2 / 256 + (r5^2 + r6^2) / 256 \ = ((r6 + r7)^2 + r5^2 + r6^2) / 256 BCS PLC3 \ If the addition overflowed, jump down to PLC3 to skip \ to the next pixel CMP #80 \ If A >= 80, jump down to PLC3 to skip to the next BCS PLC3 \ pixel CMP #32 \ If A < 32, jump down to PLC3 to skip to the next pixel BCC PLC3 TYA \ Set A = Y + T ADC T \ = (r6 + r7)^2 / 256 + r6^2 / 256 \ = ((r6 + r7)^2 + r6^2) / 256 CMP #16 \ If A >= 16, skip to PL1 to plot the pixel BCS PL1 LDA ZP \ If ZP is positive (i.e. r5 < 128), jump down to PLC3 BPL PLC3 \ to skip to the next pixel .PL1 \ If we get here then the following is true: \ \ 32 <= ((r6 + r7)^2 + r5^2 + r6^2) / 256 < 80 \ \ and either this is true: \ \ ((r6 + r7)^2 + r6^2) / 256 >= 16 \ \ or both these are true: \ \ ((r6 + r7)^2 + r6^2) / 256 < 16 \ r5 >= 128 LDA YY \ Set A = YY \ = r6 JSR PIX \ Draw a pixel at screen coordinate (X, -A), where: \ \ X = (random -32 to 31) + r6 \ A = r6 \ \ Negating a random number from 0 to 255 still gives a \ random number from 0 to 255, so this is the same as \ plotting at (x, y) where: \ \ r5 = random number from 0 to 255 \ r6 = random number from 0 to 255 \ r7 = r5, squashed into -32 to 31 \ \ x = r6 + r7 \ y = r6 \ \ 32 <= ((r6 + r7)^2 + r5^2 + r6^2) / 256 < 80 \ \ Either: ((r6 + r7)^2 + r6^2) / 256 >= 16 \ \ Or: ((r6 + r7)^2 + r6^2) / 256 < 16 \ r5 >= 128 \ \ which is what we want .PLC3 DEC CNT3 \ Decrement the counter in CNT3 (the low byte) BNE PLL3 \ Loop back to PLL3 until CNT3 = 0 DEC CNT3+1 \ Decrement the counter in CNT3+1 (the high byte) BNE PLL3 \ Loop back to PLL3 until CNT3+1 = 0Name: PLL1 (Part 3 of 3) [Show more] Type: Subroutine Category: Drawing planets Summary: Draw Saturn on the loading screen (draw the rings) Deep dive: Drawing Saturn on the loading screenContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: No direct references to this subroutine in this source file.DORND LDA RAND+1 \ r1´ = r1 + r3 + C TAX \ r3´ = r1 ADC RAND+3 STA RAND+1 STX RAND+3 LDA RAND \ X = r2´ = r0 TAX \ A = r0´ = r0 + r2 ADC RAND+2 STA RAND STX RAND+2 RTS \ Return from the subroutineName: DORND [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Generate random numbers Deep dive: Generating random numbers Fixing ship positionsContext: See this subroutine on its own page References: This subroutine is called as follows: * PLL1 (Part 1 of 3) calls DORND * PLL1 (Part 2 of 3) calls DORND * PLL1 (Part 3 of 3) calls DORND
Set A and X to random numbers (though note that X is set to the random number that was returned in A the last time DORND was called). The C and V flags are also set randomly. This is a simplified version of the DORND routine in the main game code. It swaps the two calculations around and omits the ROL A instruction, but is otherwise very similar. See the DORND routine in the main game code for more details..SQUA2 BPL SQUA \ If A > 0, jump to SQUA EOR #&FF \ Otherwise we need to negate A for the SQUA algorithm CLC \ to work, so we do this using two's complement, by ADC #1 \ setting A = ~A + 1 .SQUA STA Q \ Set Q = A and P = A STA P \ Set P = A LDA #0 \ Set A = 0 so we can start building the answer in A LDY #8 \ Set up a counter in Y to count the 8 bits in P LSR P \ Set P = P >> 1 \ and C flag = bit 0 of P .SQL1 BCC SQ1 \ If C (i.e. the next bit from P) is set, do the CLC \ addition for this bit of P: ADC Q \ \ A = A + Q .SQ1 ROR A \ Shift A right to catch the next digit of our result, \ which the next ROR sticks into the left end of P while \ also extracting the next bit of P ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation into the C flag DEY \ Decrement the loop counter BNE SQL1 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutineName: SQUA2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = A * A Deep dive: Shift-and-add multiplicationContext: See this subroutine on its own page References: This subroutine is called as follows: * PLL1 (Part 1 of 3) calls SQUA2 * PLL1 (Part 2 of 3) calls SQUA2 * PLL1 (Part 3 of 3) calls SQUA2
Do the following multiplication of signed 8-bit numbers: (A P) = A * A This uses a similar approach to routine SQUA2 in the main game code, which itself uses the MU11 routine to do the multiplication. However, this version first ensures that A is positive, so it can support signed numbers..PIX TAY \ Copy A into Y, for use later EOR #%10000000 \ Flip the sign of A LSR A \ Set ZP+1 = &60 + A >> 3 LSR A LSR A ORA #&60 STA ZP+1 TXA \ Set ZP = (X >> 3) * 8 EOR #%10000000 AND #%11111000 STA ZP TYA \ Set Y = Y AND %111 AND #%00000111 TAY TXA \ Set X = X AND %111 AND #%00000111 TAX LDA TWOS,X \ Fetch a pixel from TWOS and OR it into ZP+Y ORA (ZP),Y STA (ZP),Y RTS \ Return from the subroutineName: PIX [Show more] Type: Subroutine Category: Drawing pixels Summary: Draw a single pixel at a specific coordinateContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: This subroutine is called as follows: * PLL1 (Part 1 of 3) calls PIX * PLL1 (Part 2 of 3) calls PIX * PLL1 (Part 3 of 3) calls PIX
Draw a pixel at screen coordinate (X, -A). The sign bit of A gets flipped before drawing, and then the routine uses the same approach as the PIXEL routine in the main game code, except it plots a single pixel from TWOS instead of a two pixel dash from TWOS2. This applies to the top part of the screen (the monochrome mode 4 space view). See the PIXEL routine in the main game code for more details.
Arguments: X The screen x-coordinate of the pixel to draw A The screen y-coordinate of the pixel to draw, negated.TWOS EQUB %10000000 EQUB %01000000 EQUB %00100000 EQUB %00010000 EQUB %00001000 EQUB %00000100 EQUB %00000010 EQUB %00000001Name: TWOS [Show more] Type: Variable Category: Drawing pixels Summary: Ready-made single-pixel character row bytes for mode 4Context: See this variable on its own page Variations: See code variations for this variable in the different versions References: This variable is used as follows: * PIX uses TWOS
Ready-made bytes for plotting one-pixel points in mode 4 (the top part of the split screen). See the PIX routine for details..CNT EQUW &0500 \ The number of iterations of the PLL1 loop (1280)Name: CNT [Show more] Type: Variable Category: Drawing planets Summary: A counter for use in drawing Saturn's planetary bodyContext: See this variable on its own page Variations: See code variations for this variable in the different versions References: This variable is used as follows: * PLL1 (Part 1 of 3) uses CNT
Defines the number of iterations of the PLL1 loop, which draws the planet part of the loading screen's Saturn..CNT2 EQUW &01DD \ The number of iterations of the PLL2 loop (477)Name: CNT2 [Show more] Type: Variable Category: Drawing planets Summary: A counter for use in drawing Saturn's background starsContext: See this variable on its own page References: This variable is used as follows: * PLL1 (Part 2 of 3) uses CNT2
Defines the number of iterations of the PLL2 loop, which draws the background stars on the loading screen..CNT3 EQUW &0500 \ The number of iterations of the PLL3 loop (1280)Name: CNT3 [Show more] Type: Variable Category: Drawing planets Summary: A counter for use in drawing Saturn's ringsContext: See this variable on its own page Variations: See code variations for this variable in the different versions References: This variable is used as follows: * PLL1 (Part 3 of 3) uses CNT3
Defines the number of iterations of the PLL3 loop, which draws the rings around the loading screen's Saturn..ROOT LDY ZP+1 \ Set (Y Q) = ZP(1 0) LDA ZP STA Q \ So now to calculate ZP = SQRT(Y Q) LDX #0 \ Set X = 0, to hold the remainder STX ZP \ Set ZP = 0, to hold the result LDA #8 \ Set P = 8, to use as a loop counter STA P .LL6 CPX ZP \ If X < ZP, jump to LL7 BCC LL7 BNE LL8 \ If X > ZP, jump to LL8 CPY #64 \ If Y < 64, jump to LL7 with the C flag clear, BCC LL7 \ otherwise fall through into LL8 with the C flag set .LL8 TYA \ Set Y = Y - 64 SBC #64 \ TAY \ This subtraction will work as we know C is set from \ the BCC above, and the result will not underflow as we \ already checked that Y >= 64, so the C flag is also \ set for the next subtraction TXA \ Set X = X - ZP SBC ZP TAX .LL7 ROL ZP \ Shift the result in Q to the left, shifting the C flag \ into bit 0 and bit 7 into the C flag ASL Q \ Shift the dividend in (Y S) to the left, inserting TYA \ bit 7 from above into bit 0 ROL A TAY TXA \ Shift the remainder in X to the left ROL A TAX ASL Q \ Shift the dividend in (Y S) to the left TYA ROL A TAY TXA \ Shift the remainder in X to the left ROL A TAX DEC P \ Decrement the loop counter BNE LL6 \ Loop back to LL6 until we have done 8 loops RTS \ Return from the subroutineName: ROOT [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate ZP = SQRT(ZP(1 0))Context: See this subroutine on its own page References: This subroutine is called as follows: * PLL1 (Part 1 of 3) calls ROOT
Calculate the following square root: ZP = SQRT(ZP(1 0)) This routine is identical to LL5 in the main game code - it even has the same label names. The only difference is that LL5 calculates Q = SQRT(R Q), but apart from the variables used, the instructions are identical, so see the LL5 routine in the main game code for more details on the algorithm used here..BEGIN% EQUB HI(David9) \ JMP (David9) EQUB LO(David9) EQUB &6C EQUB HI(TUT) \ STA TUT,Y EQUB LO(TUT) EQUB &99 IF _REMOVE_CHECKSUMS EQUB HI(TUT) \ If we have disabled checksums, then just load the Y-th EQUB LO(TUT) \ byte of TUT with LDA TUT,Y EQUB &B9 ELSE EQUB HI(TUT) \ EOR TUT,Y EQUB LO(TUT) EQUB &59 ENDIF PHA \ PHA EQUB HI(BLOCK) \ LDA BLOCK,Y EQUB LO(BLOCK) EQUB &B9 PLA \ PLA PLA \ PLAName: BEGIN% [Show more] Type: Subroutine Category: Copy protection Summary: Single-byte decryption and copying routine, run on the stackContext: See this subroutine on its own page References: This subroutine is called as follows: * Elite loader (Part 2 of 6) calls BEGIN%
This routine is copied to the stack at &01F1. It pushes BLOCK to ENDBLOCK onto the stack, and decrypts the code from TUT onwards. The 15 instructions for this routine are pushed onto the stack and executed there. The instructions are pushed onto the stack in reverse (as the stack grows downwards in memory), so first the JMP gets pushed, then the STA, and so on. This is the code that is pushed onto the stack. It gets run by a JMP call to David2, which then calls the routine on the stack with JSR &01F1. 01F1 : PLA \ Remove the return address from the stack that was 01F2 : PLA \ put here by the JSR that called this routine 01F3 : LDA BLOCK,Y \ Set A = the Y-th byte of BLOCK 01F6 : PHA \ Push A onto the stack 01F7 : EOR TUT,Y \ EOR the Y-th byte of TUT with A 01FA : STA TUT,Y 01FD : JMP (David9) \ Jump to the address in David9 The routine is called inside a loop with Y as the counter. It counts from 0 to ENDBLOCK - BLOCK, so the routine eventually pushes every byte between BLOCK and ENDBLOCK onto the stack, as well as EOR'ing each byte from TUT onwards to decrypt that section. The elite-checksums.py script reverses the order of the bytes between BLOCK and ENDBLOCK in the final file, so pushing them onto the stack (which is a descending stack) realigns them in memory as assembled below. Not only that, but the last two bytes pushed on the stack are the ones that are at the start of the block at BLOCK, and these contain the address of ENTRY2. This is why the RTS at the end of part 4 above actually jumps to ENTRY2 in part 5..DOMOVE RTS \ RTS EQUW &D0EF \ BNE MVDL DEX \ DEX EQUB ZP+1 \ INC ZP+1 INC P+1 \ INC P+1 EQUB &E6 EQUW &D0F6 \ BNE MVDL DEY \ DEY EQUB P \ STA(P),Y EQUB &91 IF _REMOVE_CHECKSUMS NOP \ If we have disabled checksums, skip the EOR so the NOP \ routine just does the copying part NOP ELSE EQUB HI(OSB) \ EOR OSB,Y EQUB LO(OSB) EQUB &59 ENDIF EQUB ZP \ LDA(ZP),Y EQUB &B1Name: DOMOVE [Show more] Type: Subroutine Category: Copy protection Summary: Multi-byte decryption and copying routine, run on the stackContext: See this subroutine on its own page References: No direct references to this subroutine in this source file
This routine is copied to the stack at &01DF. It moves and decrypts a block of memory. The original source refers to the stack routine as MVDL. The 18 instructions for this routine are pushed onto the stack and executed there. The instructions are pushed onto the stack in reverse (as the stack grows downwards in memory), so first the RTS gets pushed, then the BNE, and so on. This is the code that is pushed onto the stack. It gets run by a JMP call to crunchit, which then calls the routine on the stack at MVDL, or &01DF. The label MVDL comes from a comment in the original source file ELITES. 01DF : .MVDL 01DF : LDA (ZP),Y \ Set A = the Y-th byte from the block whose address \ is in ZP(1 0) 01E1 : EOR OSB,Y \ EOR A with the Y-th byte on from OSB 01E4 : STA (P),Y \ Store A in the Y-th byte of the block whose \ address is in P(1 0) 01E6 : DEY \ Decrement the loop counter 01E7 : BNE MVDL \ Loop back to copy and EOR the next byte until we \ have copied an entire page (256 bytes) 01E9 : INC P+1 \ Increment the high byte of P(1 0) so it points to \ the next page of 256 bytes 01EB : INC ZP+1 \ Increment ZP(1 0) so it points to the next page of \ 256 bytes 01ED : DEX \ Decrement X 01EE : BNE MVDL \ Loop back to copy the next page 01F0 : RTS \ Return from the subroutine, which takes us back \ to the caller of the crunchit routine using a \ tail call, as we called this with JMP crunchit We call MVDL with the following arguments: (X Y) The number of bytes to copy ZP(1 0) The source address P(1 0) The destination address The routine moves and decrypts a block of memory, and is used in part 3 to move blocks of code and images that are embedded within the loader binary, either into low memory locations below PAGE (for the recursive token table and page at UU%), or into screen memory (for the loading screen and dashboard images). If checksums are disabled in the build, we don't do the EOR instruction, so the routine just moves and doesn't decrypt..UU% Q% = P% - LE% ORG LE%Name: UU% [Show more] Type: Workspace Address: &0B00 Category: Workspaces Summary: Marker for a block that is moved as part of the obfuscationContext: See this workspace on its own page References: This workspace is used as follows: * Elite loader (Part 3 of 6) uses UU%
The code from here to the end of the file gets copied to &0B00 (LE%) by part 3. It is called from the end of part 4, via ENTRY2 in part 5 below..CHECKbyt BRK \ This could be an EQUB 0 directive instead of a BRK, \ but this is what's in the source code \ \ [Show more]Name: CHECKbyt [Show more] Type: Variable Category: Copy protection Summary: Checksum for the validity of the UU% workspaceContext: See this variable on its own page References: No direct references to this variable in this source file
We calculate the value of the CHECKbyt checksum in elite-checksum.py, so this just reserves a byte. It checks the validity of the first two pages of the UU% workspace, which gets copied to LE%.\ \ This variable is used by the following: \ \ * Elite loader (Part 3 of 6) \ \ This list only includes code that refers to the \ variable by name; there may be other references to \ this memory location that don't use this label, and \ these will not be mentioned above.MAINSUM EQUB &CB \ This is the checksum value of the decryption header \ code (from LBL to elitea) that gets prepended to the \ main game code by elite-bcfs.asm and saved as \ ELThead.bin EQUB 0 \ This is the checksum value for the recursive token \ table from &0400 to &07FF. We calculate the value in \ elite-checksum.py, so this just reserves a byteName: MAINSUM [Show more] Type: Variable Category: Copy protection Summary: Two checksums for the decryption header and text token tableContext: See this variable on its own page References: This variable is used as follows: * CHECKER uses MAINSUM
Contains two checksum values, one for the header code at LBL, and the other for the recursive token table from &0400 to &07FF..FOOLV EQUW FOOL \ The address of FOOL, which contains an RTSName: FOOLV [Show more] Type: Variable Category: Copy protection Summary: Part of the AFOOL roundabout obfuscation routineContext: See this variable on its own page References: This variable is used as follows: * Elite loader (Part 5 of 6) uses FOOLV
FOOLV contains the address of FOOL. This is part of the JSR AFOOL obfuscation routine, which calls AFOOL, which then jumps to the address in FOOLV, which contains the address of FOOL, which contains an RTS instruction... so overall it does nothing, but in a rather roundabout fashion..CHECKV EQUW LOAD%+1 \ The address of the LBL routineName: CHECKV [Show more] Type: Variable Category: Copy protection Summary: The address of the LBL routine in the decryption headerContext: See this variable on its own page References: This variable is used as follows: * CHECKER uses CHECKV
CHECKV contains the address of the LBL routine at the very start of the main game code file, in the decryption header code that gets prepended to the main game code by elite-bcfs.asm and saved as ELThead.bin.block1 EQUB &F5, &E5 EQUB &B5, &A5 EQUB &76, &66 EQUB &36, &26 EQUB &D4, &C4 EQUB &94, &84Name: block1 [Show more] Type: Variable Category: Drawing the screen Summary: Palette data for the two dashboard colour schemeContext: See this variable on its own page References: This variable is used as follows: * IRQ1 uses block1
Palette bytes for use with the split-screen mode 5. See TVT1 in the main game code for an explanation..block2 EQUB &D0, &C0 EQUB &B0, &A0 EQUB &F0, &E0 EQUB &90, &80 EQUB &77, &67 EQUB &37, &27Name: block2 [Show more] Type: Variable Category: Drawing the screen Summary: Palette data for the space part of the screenContext: See this variable on its own page References: This variable is used as follows: * IRQ1 uses block2
Palette bytes for use with the split-screen mode 4. See TVT1 in the main game code for an explanation..TT26 STA K3 \ Store the A, X and Y registers (in K3 for A, and on TYA \ the stack for the others), so we can restore them at PHA \ the end (so they don't get changed by this routine) TXA PHA .rr LDA K3 \ Set A = the character to be printed CMP #7 \ If this is a beep character (A = 7), jump to R5, BEQ R5 \ which will emit the beep, restore the registers and \ return from the subroutine CMP #32 \ If this is an ASCII character (A >= 32), jump to RR1 BCS RR1 \ below, which will print the character, restore the \ registers and return from the subroutine CMP #13 \ If this is control code 13 (carriage return) then jump BEQ RRX1 \ to RRX1, which will move along on character, restore \ the registers and return from the subroutine (as we \ don't have room in the text window for new lines) INC YC \ If we get here, then this is control code 10, a line \ feed, so move down one line and fall through into RRX1 \ to move the cursor to the start of the line .RRX1 LDX #7 \ Set the column number (x-coordinate) of the text STX XC \ to 7 BNE RR4 \ Jump to RR4 to restore the registers and return from \ the subroutine (this BNE is effectively a JMP as Y \ will never be zero) .RR1 LDX #&BF \ Set X to point to the first font page in ROM minus 1, \ which is &C0 - 1, or &BF ASL A \ If bit 6 of the character is clear (A is 32-63) ASL A \ then skip the following instruction BCC P%+4 LDX #&C1 \ A is 64-126, so set X to point to page &C1 ASL A \ If bit 5 of the character is clear (A is 64-95) BCC P%+3 \ then skip the following instruction INX \ Increment X, so X now contains the high byte \ (the page) of the address of the definition that we \ want, while A contains the low byte (the offset into \ the page) of the address STA P \ Store the address of this character's definition in STX P+1 \ P(1 0) LDA XC \ If the column number (x-coordinate) of the text is CMP #20 \ less than 20, skip to NOLF BCC NOLF LDA #7 \ Otherwise we just reached the end of the line, so STA XC \ move the text cursor to column 7, and down onto the INC YC \ next line .NOLF ASL A \ Multiply the x-coordinate (column) of the text by 8 ASL A \ and store in ZP, to get the low byte of the screen ASL A \ address for the character we want to print STA ZP INC XC \ Once we print the character, we want to move the text \ cursor to the right, so we do this by incrementing XC LDA YC \ If the row number (y-coordinate) of the text is less CMP #19 \ than 19, skip to RR3 BCC RR3 \ Otherwise we just reached the bottom of the screen, \ which is a small 14x14 character text window we use \ for showing the tape loading messages, so now we need \ to clear that window and move the cursor to the top LDA #7 \ Move the text cursor to column 7 STA XC LDA #&65 \ Set the high byte of the SC(1 0) to &65, for character STA SC+1 \ row 5 of the screen LDY #7*8 \ Set Y = 7 * 8, for column 7 (as there are 8 bytes per \ character block) LDX #14 \ Set X = 14, to count the number of character rows we \ need to clear STY SC \ Set the low byte of SC(1 0) to 7*8, so SC(1 0) now \ points to the character block at row 5, column 7, at \ the top-left corner of the small text window LDA #0 \ Set A = 0 for use in clearing the screen (which we do \ by setting the screen memory to 0) TAY \ Set Y = 0 .David1 STA (SC),Y \ Clear the Y-th byte of the block pointed to by SC(1 0) INY \ Increment the counter in Y CPY #14*8 \ Loop back to clear the next byte until we have done 14 BCC David1 \ lots of 8 bytes (i.e. 14 characters, the width of the \ small text window) TAY \ Set Y = 0, ready for the next row INC SC+1 \ Point SC(1 0) to the next page in memory, i.e. the \ next character row DEX \ Decrement the counter in X BPL David1 \ Loop back to David1 until we have done 14 character \ rows (the height of the small text window) LDA #5 \ Move the text cursor to row 5 STA YC BNE rr \ Jump to rr to print the character we were about to \ print when we ran out of space (this BNE is \ effectively a JMP as A will never be zero) .RR3 ORA #&60 \ Add &60 to YC, giving us the page number that we want STA ZP+1 \ Store the page number of the destination screen \ location in ZP+1, so ZP now points to the full screen \ location where this character should go LDY #7 \ We want to print the 8 bytes of character data to the \ screen (one byte per row), so set up a counter in Y \ to count these bytes .RRL1 LDA (P),Y \ The character definition is at P(1 0) - we set this up \ above - so load the Y-th byte from P(1 0) STA (ZP),Y \ Store the Y-th byte at the screen address for this \ character location DEY \ Decrement the loop counter BPL RRL1 \ Loop back for the next byte to print to the screen .RR4 PLA \ We're done printing, so restore the values of the TAX \ A, X and Y registers that we saved above, loading them PLA \ from K3 (for A) and the stack (for X and Y) TAY LDA K3 .FOOL RTS \ Return from the subroutine .R5 LDA #7 \ Control code 7 makes a beep, so load this into A JSR osprint \ Call OSPRINT to "print" the beep character JMP RR4 \ Jump to RR4 to restore the registers and return from \ the subroutine using a tail callName: TT26 [Show more] Type: Subroutine Category: Text Summary: Print a character at the text cursor (WRCHV points here)Context: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: This subroutine is called as follows: * Elite loader (Part 5 of 6) calls TT26
This routine prints a character at the text cursor (XC, YC). It is very similar to the routine of the same name in the main game code, so refer to that routine for a more detailed description. This routine, however, only works within a small 14x14 character text window, which we use for the tape loading messages, so there is extra code for fitting the text into the window (and it also reverses the effect of line feeds and carriage returns).
Arguments: A The character to be printed XC Contains the text column to print at (the x-coordinate) YC Contains the line number to print on (the y-coordinate)
Returns: A A is preserved X X is preserved Y Y is preserved.TUT .osprint JMP (OSPRNT) \ Jump to the address in OSPRNT and return using a \ tail call EQUB &6C \ This byte appears to be unusedName: osprint [Show more] Type: Subroutine Category: Utility routines Summary: Print a characterContext: See this subroutine on its own page References: This subroutine is called as follows: * TT26 calls osprint
Arguments: A The character to print.command JMP (oscliv) \ Jump to &FFF7 to execute the OS command pointed to \ by (Y X) and return using a tail callName: command [Show more] Type: Subroutine Category: Utility routines Summary: Execute an OS commandContext: See this subroutine on its own page References: This subroutine is called as follows: * Elite loader (Part 5 of 6) calls command
Arguments: (Y X) The address of the OS command string to execute.MESS1 IF DISC EQUS "L.ELTcode 1100" \ This is short for "*LOAD ELTcode 1100" ELSE EQUS "L.ELITEcode F1F" \ This is short for "*LOAD ELITEcode F1F" ENDIF EQUB 13Name: MESS1 [Show more] Type: Variable Category: Utility routines Summary: Contains an OS command string for loading the main game codeContext: See this variable on its own page References: This variable is used as follows: * Elite loader (Part 5 of 6) uses MESS1.ENTRY2 \ We start this part of the loader by setting the \ following: \ \ OSPRNT(1 0) = WRCHV \ WRCHV(1 0) = TT26 \ (Y X) = MESS1(1 0) \ \ so any character printing will use the TT26 routine LDA &020E \ Copy the low byte of WRCHV to the low byte of OSPRNT STA OSPRNT LDA #LO(TT26) \ Set the low byte of WRCHV to the low byte of TT26 STA &020E LDX #LO(MESS1) \ Set X to the low byte of MESS1 LDA &020F \ Copy the high byte of WRCHV to the high byte of OSPRNT STA OSPRNT+1 LDA #HI(TT26) \ Set the high byte of WRCHV to the high byte of TT26 LDY #HI(MESS1) \ and set Y to the high byte of MESS1 STA &020F JSR AFOOL \ This calls AFOOL, which jumps to the address in FOOLV, \ which contains the address of FOOL, which contains an \ RTS instruction... so overall this does nothing, but \ in a rather roundabout fashion JSR command \ Call command to execute the OSCLI command pointed to \ by (Y X) in MESS1, which starts loading the main game \ code JSR 512-LEN+CHECKER-ENDBLOCK \ Call the CHECKER routine in its new location on \ the stack, to run a number of checksums on the \ code (this routine, along with the whole of part \ 6, was pushed onto the stack in part 4) JSR AFOOL \ Another call to the round-the-houses routine to try \ and distract the crackers, presumably IF DISC LDA #140 \ Call OSBYTE with A = 140 and X = 12 to select the LDX #12 \ tape filing system (i.e. do a *TAPE command) JSR OSBYTE ENDIF LDA #0 \ Set SVN to 0, as the main game code checks the value STA SVN \ of this location in its IRQ1 routine, so it needs to \ be set to 0 so it can work properly once it takes over \ when the game itself runs \ We now decrypt and move the main game code from &1128 \ to &0F40 LDX #HI(LC%) \ Set X = high byte of LC%, the maximum size of the main \ game code, so if we move this number of pages, we will \ have definitely moved all the game code down LDA #LO(L%) \ Set ZP(1 0) = L% (the start of the game code) STA ZP LDA #HI(L%) STA ZP+1 LDA #LO(C%) \ Set P(1 0) = C% = &0F40 STA P LDA #HI(C%) STA P+1 LDY #0 \ Set Y as a counter for working our way through every \ byte of the game code. We EOR the counter with the \ current byte to decrypt it .ML1 TYA \ Copy the counter into A IF _REMOVE_CHECKSUMS LDA (ZP),Y \ If we have disabled checksums, just fetch the byte to \ copy from the Y-th block pointed to by ZP(1 0) ELSE EOR (ZP),Y \ Fetch the byte and EOR it with the counter ENDIF STA (P),Y \ Store the copied (and decrypted) byte in the Y-th byte \ of the block pointed to by P(1 0) INY \ Increment the loop counter BNE ML1 \ Loop back for the next byte until we have finished the \ first 256 bytes INC ZP+1 \ Increment the high bytes of both ZP(1 0) and P(1 0) to INC P+1 \ point to the next 256 bytes DEX \ Decrement the number of pages we need to copy in X BPL ML1 \ Loop back to copy and decrypt the next page of bytes \ until we have done them all \ S% points to the entry point for the main game code, \ so the following copies the addresses from the start \ of the main code (see the S% label in the main game \ code for the vector values) LDA S%+6 \ Set BRKV to point to the BR1 routine in the main game STA &0202 \ code LDA S%+7 STA &0203 LDA S%+2 \ Set WRCHV to point to the TT26 routine in the main STA &020E \ game code LDA S%+3 STA &020F RTS \ This RTS actually does a jump to the first instruction \ in BLOCK, after the two EQUW operatives, which is now \ on the stack. This takes us to the next and final \ step of the loader in part 6. See the documentation \ for the stack routine at BEGIN% for more details .AFOOL JMP (FOOLV) \ This jumps to the address in FOOLV as part of the \ JSR AFOOL instruction above, which does nothing except \ take us on wild goose chaseName: Elite loader (Part 5 of 6) [Show more] Type: Subroutine Category: Loader Summary: Load main game code, decrypt it, move it to the correct locationContext: See this subroutine on its own page References: No direct references to this subroutine in this source file
This part loads the main game code, decrypts it and moves it to the correct location for it to run. The code in this part is encrypted by elite-checksum.py and is decrypted in part 4 by the same routine that moves part 6 onto the stack..M2 EQUB %00000010 \ Bit 1 is setName: M2 [Show more] Type: Variable Category: Utility routines Summary: Used for testing the 6522 System VIA status byte in IRQ1Context: See this variable on its own page References: This variable is used as follows: * IRQ1 uses M2
Used for testing bit 1 of the 6522 System VIA status byte in the IRQ1 routine, as well as bit 1 of the block flag..VIA2 LDA #%00000100 \ Set the Video ULA control register (SHEILA &20) to STA &FE20 \ %00000100, which is the same as switching to mode 5, \ (i.e. the bottom part of the screen) but with no \ cursor LDY #11 \ We now apply the palette bytes from block1 to the \ mode 5 screen, so set a counter in Y for 12 bytes .inlp1 LDA block1,Y \ Copy the Y-th palette byte from block1 to SHEILA &21 STA &FE21 \ to map logical to actual colours for the bottom part \ of the screen (i.e. the dashboard) DEY \ Decrement the palette byte counter BPL inlp1 \ Loop back to the inlp1 until we have copied all the \ palette bytes PLA \ Restore Y from the stack TAY JMP (VEC) \ Jump to the address in VEC, which was set to the \ original IRQ1V vector in part 4, so this instruction \ passes control to the next interrupt handler .IRQ1 TYA \ Store Y on the stack PHA IF PROT AND NOT(DISC) \ By this point, we have set up the following in \ various places throughout the loader code (such as \ part 2 and PLL1): \ \ BLPTR(1 0) = &03CA \ BLN(1 0) = &03C6 \ EXCN(1 0) = &03C2 \ \ BLPTR (&03CA) is a byte in the MOS workspace that \ stores the block flag of the most recent block loaded \ from tape \ \ BLN (&03C6) is the low byte of the number of the last \ block loaded from tape \ \ EXCN (&03C2) is the low byte of the execution address \ of the file being loaded LDY #0 \ Set A to the block flag of the most recent block LDA (BLPTR),Y \ loaded from tape BIT M2 \ If bit 1 of the block flag is set, jump to itdone BNE itdone EOR #%10000011 \ Otherwise flip bits 0, 1 and 7 of A. This has two \ main effects: \ \ * Bit 0 of the block flag gets cleared. Most \ cassette versions of Acornsoft games are saved to \ tape with locked blocks, so you can't just load \ the game into memory (you'll get a "Locked" error \ for each block). Locked blocks have bit 0 set, so \ this clears the locked status, so when the MOS \ gets round to checking whether the block is \ locked, we've already cleared it and updated it in \ memory (which we do below), so the block loads \ without throwing an error \ \ * Bit 1 of the block flag gets set, so we won't \ increment BLCNT again until the next block starts \ loading (so in this way we count the number of \ blocks loaded in BLCNT) INC BLCNT \ Increment BLCNT, which was initialised to 0 in part 3 BNE ZQK \ If BLCNT is non-zero, skip the next instruction DEC BLCNT \ If incrementing BLCNT set it to zero, decrement it, so \ this sets a maximum of 255 on BLCNT .ZQK STA (BLPTR),Y \ Store the updated value of A in the block flag, so the \ block gets unlocked LDA #35 \ If the block number in BLN is 35, skip the next CMP (BLN),Y \ instruction, leaving A = 32 = &23 BEQ P%+4 EOR #17 \ Set A = 35 EOR 17 = 50 = &32 CMP (EXCN),Y \ If the low byte of the execution address of the file BEQ itdone \ we are loading is equal to A (which is either &23 or \ &32), skip to itdone DEC LOAD% \ Otherwise decrement LOAD%, which is the address of the \ first byte of the main game code file (i.e. the load \ address of "ELTcode"), so this decrements the first \ byte of the file we are loading, i.e. the LBL variable \ added by the Big Code File source .itdone ENDIF LDA VIA+&4D \ Read the 6522 System VIA status byte bit 1 (SHEILA BIT M2 \ &4D), which is set if vertical sync has occurred on \ the video system BNE LINSCN \ If we are on the vertical sync pulse, jump to LINSCN \ to set up the timers to enable us to switch the \ screen mode between the space view and dashboard AND #%01000000 \ If the 6522 System VIA status byte bit 6 is set, which BNE VIA2 \ means timer 1 has timed out, jump to VIA2 PLA \ Restore Y from the stack TAY JMP (VEC) \ Jump to the address in VEC, which was set to the \ original IRQ1V vector in part 4, so this instruction \ passes control to the next interrupt handler .LINSCN LDA #50 \ Set 6522 System VIA T1C-L timer 1 low-order counter STA VIA+&44 \ (SHEILA &44) to 50 LDA #VSCAN \ Set 6522 System VIA T1C-L timer 1 high-order counter STA VIA+&45 \ (SHEILA &45) to VSCAN (56) to start the T1 counter \ counting down from 14386 at a rate of 1 MHz LDA #8 \ Set the Video ULA control register (SHEILA &20) to STA &FE20 \ %00001000, which is the same as switching to mode 4 \ (i.e. the top part of the screen) but with no cursor LDY #11 \ We now apply the palette bytes from block2 to the \ mode 4 screen, so set a counter in Y for 12 bytes .inlp2 LDA block2,Y \ Copy the Y-th palette byte from block2 to SHEILA &21 STA &FE21 \ to map logical to actual colours for the top part of \ the screen (i.e. the space view) DEY \ Decrement the palette byte counter BPL inlp2 \ Loop back to the inlp1 until we have copied all the \ palette bytes PLA \ Restore Y from the stack TAY JMP (VEC) \ Jump to the address in VEC, which was set to the \ original IRQ1V vector in part 4, so this instruction \ passes control to the next interrupt handlerName: IRQ1 [Show more] Type: Subroutine Category: Drawing the screen Summary: The loader's screen-mode interrupt handler (IRQ1V points here) Deep dive: The split-screen mode in BBC Micro EliteContext: See this subroutine on its own page Variations: See code variations for this subroutine in the different versions References: This subroutine is called as follows: * Elite loader (Part 4 of 6) calls IRQ1
The main interrupt handler, which implements Elite's split-screen mode. This routine is similar to the main IRQ1 routine in the main game code, except it's a bit simpler (it doesn't need to support the mode-flashing effect of hyperspace, for example). It also sets Timer 1 to a different value, 14386 instead of 14622. The split in the split-screen mode does overlap more in the loader than in the game, so it's interesting that they didn't fine-tune this version as much. For more details on how the following works, see the IRQ1 routine in the main game code..BLOCK EQUW ENTRY2-1 EQUW 512-LEN+BLOCK-ENDBLOCK+3Name: BLOCK [Show more] Type: Variable Category: Copy protection Summary: Addresses for the obfuscated jumps that use RTS not JMPContext: See this variable on its own page References: This variable is used as follows: * BEGIN% uses BLOCK * Elite loader (Part 4 of 6) uses BLOCK
These two addresses get pushed onto the stack in part 4. The first EQUW is the address of ENTRY2, while the second is the address of the first instruction in part 6, after it is pushed onto the stack. This entire section from BLOCK to ENDBLOCK gets copied into the stack at location &015E by part 4, so by the time we call the routine at the second EQUW address at the start, the entry point is on the stack at &0163. This means that the RTS instructions at the end of parts 4 and 5 jump to ENTRY2 and the start of part 6 respectively. See part 4 for details.LDA VIA+&44 \ Read the 6522 System VIA T1C-L timer 1 low-order STA &0001 \ counter (SHEILA &44), which decrements one million \ times a second and will therefore be pretty random, \ and store it in location &0001, which is among the \ main game code's random seeds (so this seeds the \ random number generator for the main game) SEI \ Disable all interrupts LDA #%00111001 \ Set 6522 System VIA interrupt enable register IER STA VIA+&4E \ (SHEILA &4E) bits 0 and 3-5 (i.e. disable the Timer1, \ CB1, CB2 and CA2 interrupts from the System VIA) \LDA #&7F \ These instructions are commented out in the original \STA &FE6E \ source with the comment "already done", which they \LDA IRQ1V \ were, in part 4 \STA VEC \LDA IRQ1V+1 \STA VEC+1 LDA S%+4 \ S% points to the entry point for the main game code, STA IRQ1V \ so this copies the address of the main game's IRQ1 LDA S%+5 \ routine from the start of the main code into IRQ1V STA IRQ1V+1 LDA #VSCAN \ Set 6522 System VIA T1C-L timer 1 high-order counter STA VIA+&45 \ (SHEILA &45) to VSCAN (56) to start the T1 counter \ counting down from 14080 at a rate of 1 MHz (this is \ a different value to the main game code) CLI \ Re-enable interrupts \LDA #129 \ These instructions are commented out in the original \LDY #&FF \ source. They call OSBYTE with A = 129, X = 1 and \LDX #1 \ Y = &FF, which returns the machine type in X, so \JSR OSBYTE \ this code would detect the MOS version \TXA \EOR #&FF \STA MOS \BMI BLAST LDY #0 \ Call OSBYTE with A = 200, X = 3 and Y = 0 to disable LDA #200 \ the ESCAPE key and clear memory if the BREAK key is LDX #3 \ pressed JSR OSBYTE \ The rest of the routine calculates various checksums \ and makes sure they are correct before proceeding, to \ prevent code tampering. We start by calculating the \ checksum for the main game code from &0F40 to &5540, \ which just adds up every byte and checks it against \ the checksum stored at the end of the main game code .BLAST LDA #HI(S%) \ Set ZP(1 0) = S% STA ZP+1 \ LDA #LO(S%) \ so ZP(1 0) points to the start of the main game code STA ZP LDX #&45 \ We are going to checksum &45 pages from &0F40 to &5540 \ so set a page counter in X LDY #0 \ Set Y to count through each byte within each page TYA \ Set A = 0 for building the checksum .CHK CLC \ Add the Y-th byte of this page of the game code to A ADC (ZP),Y INY \ Increment the counter for this page BNE CHK \ Loop back for the next byte until we have finished \ adding up this page INC ZP+1 \ Increment the high byte of ZP(1 0) to point to the \ next page DEX \ Decrement the page counter we set in X BPL CHK \ Loop back to add up the next page until we have done \ them all IF _REMOVE_CHECKSUMS LDA #0 \ If we have disabled checksums, just set A to 0 so the NOP \ BEQ below jumps to itsOK ELSE CMP D%-1 \ D% is set to the address of the byte after the end of \ the code, so this compares the result to the last byte \ in the main game code at location checksum0 ENDIF BEQ itsOK \ If the checksum we just calculated matches the value \ in location checksum0, jump to itsOK .nononono STA S%+1 \ If we get here then the checksum was wrong, so first \ we store the incorrect checksum value in the low byte \ of the address stored at the start of the main game \ code, which contains the address of TT170, the entry \ point for the main game (so this hides this address \ from prying eyes) LDA #%01111111 \ Set 6522 System VIA interrupt enable register IER STA &FE4E \ (SHEILA &4E) bits 0-6 (i.e. disable all hardware \ interrupts from the System VIA) JMP (&FFFC) \ Jump to the address in &FFFC to reset the machine .itsOK JMP (S%) \ The checksum was correct, so we call the address held \ in the first two bytes of the main game code, which \ point to TT170, the entry point for the main game \ code, so this, finally, is where we hand over to the \ game itselfName: Elite loader (Part 6 of 6) [Show more] Type: Subroutine Category: Loader Summary: Set up interrupt vectors, calculate checksums, run main game codeContext: See this subroutine on its own page References: This subroutine is called as follows: * CHECKER calls via nononono
This is the final part of the loader. It sets up some of the main game's interrupt vectors and calculates various checksums, before finally handing over to the main game.
Other entry points: nononono Reset the machine.CHECKER \ First we check the MAINSUM+1 checksum for the \ recursive token table from &0400 to &07FF LDY #0 \ Set Y = 0 to count through each byte within each page LDX #4 \ We are going to checksum 4 pages from &0400 to &07FF \ so set a page counter in X STX ZP+1 \ Set ZP(1 0) = &0400, to point to the start of the code STY ZP \ we want to checksum TYA \ Set A = 0 for building the checksum .CHKq CLC \ Add the Y-th byte of this page of the token table to A ADC (ZP),Y INY \ Increment the counter for this page BNE CHKq \ Loop back for the next byte until we have finished \ adding up this page INC ZP+1 \ Increment the high byte of ZP(1 0) to point to the \ next page DEX \ Decrement the page counter we set in X BNE CHKq \ Loop back to add up the next page until we have done \ them all CMP MAINSUM+1 \ Compare the result to the contents of MAINSUM+1, which \ contains the checksum for the table (this gets set by \ elite-checksum.py) IF _REMOVE_CHECKSUMS NOP \ If we have disabled checksums, do nothing NOP ELSE BNE nononono \ If the checksum we just calculated does not match the \ contents of MAINSUM+1, jump to nononono to reset the \ machine ENDIF \ Next, we check the LBL routine in the header that's \ appended to the main game code in elite-bcfs.asm, and \ which is currently loaded at LOAD% (which contains the \ load address of the main game code file) TYA \ Set A = 0 for building the checksum (as Y is still 0 \ from the above checksum loop) .CHKb CLC \ Add the Y-th byte of LOAD% to A ADC LOAD%,Y INY \ Increment the counter CPY #40 \ There are 40 bytes in the loader, so loop back until BNE CHKb \ we have added them all CMP MAINSUM \ Compare the result to the contents of MAINSUM, which \ contains the checksum for loader code IF _REMOVE_CHECKSUMS NOP \ If we have disabled checksums, do nothing NOP ELSE BNE nononono \ If the checksum we just calculated does not match the \ contents of MAINSUM, jump to nononono to reset the \ machine ENDIF \ Finally, we check the block count from the tape \ loading code in the IRQ1 routine, which counts the \ number of blocks in the main game code IF PROT AND NOT(DISC) LDA BLCNT \ If the tape protection is enabled and we are loading CMP #&4F \ from tape (as opposed to disc), check that the block BCC nononono \ count in BLCNT is &4F, and if it isn't, jump to \ nononono to reset the machine ENDIF IF _REMOVE_CHECKSUMS RTS \ If we have disabled checksums, return from the NOP \ subroutine NOP ELSE JMP (CHECKV) \ Call the LBL routine in the header (whose address is \ in CHECKV). This routine is inserted before the main \ game code by elite-bcfs.asm, and it checks the \ validity of the first two pages of the UU% routine, \ which was copied to LE% above, and which contains a \ checksum byte in CHECKbyt. We then return from the \ subroutine using a tail call ENDIF .ENDBLOCKName: CHECKER [Show more] Type: Subroutine Category: Copy protection Summary: Run checksum checks on tokens, loader and tape block countContext: See this subroutine on its own page References: This subroutine is called as follows: * Elite loader (Part 5 of 6) calls CHECKER * BLOCK calls via ENDBLOCK * Elite loader (Part 4 of 6) calls via ENDBLOCK * Elite loader (Part 5 of 6) calls via ENDBLOCK
This routine runs checksum checks on the recursive token table and the loader code at the start of the main game code file, to prevent tampering with these areas of memory. It also runs a check on the tape loading block count.
Other entry points: ENDBLOCK Denotes the end of the encrypted code that starts at BLOCK.XC EQUB 7Name: XC [Show more] Type: Variable Category: Text Summary: The x-coordinate of the text cursorContext: See this variable on its own page References: This variable is used as follows: * TT26 uses XC
Contains the x-coordinate of the text cursor (i.e. the text column) with an initial value of column 7, at the top-left corner of the 14x14 text window where we show the tape loading messages (see TT26 for details)..YC EQUB 6Name: YC [Show more] Type: Variable Category: Text Summary: The y-coordinate of the text cursorContext: See this variable on its own page References: This variable is used as follows: * TT26 uses YC
Contains the y-coordinate of the text cursor (i.e. the text row) with an initial value of row 6, at the top-left corner of the 14x14 text window where we show the tape loading messages (see TT26 for details).COPYBLOCK LE%, P%, UU% \ Copy the block that we assembled at LE% to \ UU%, which is where it will actually run PRINT "Addresses for the scramble routines in elite-checksum.py" PRINT "BLOCK_offset = ", ~(BLOCK - LE%) + (UU% - CODE%) PRINT "ENDBLOCK_offset = ", ~(ENDBLOCK - LE%) + (UU% - CODE%) PRINT "MAINSUM_offset = ", ~(MAINSUM - LE%) + (UU% - CODE%) PRINT "TUT_offset = ", ~(TUT - LE%) + (UU% - CODE%) PRINT "CHECKbyt_offset = ", ~(CHECKbyt - LE%) + (UU% - CODE%) PRINT "CODE_offset = ", ~(OSB - CODE%) PRINT "UU% = ", ~UU% PRINT "Q% = ", ~Q% PRINT "OSB = ", ~OSB PRINT "Memory usage: ", ~LE%, " - ", ~P% PRINT "Stack: ",LEN + ENDBLOCK - BLOCK PRINT "S. ELITE ", ~CODE%, " ", ~UU% + (P% - LE%), " ", ~run, " ", ~CODE% SAVE "3-assembled-output/ELITE.unprot.bin", CODE%, UU% + (P% - LE%), run, CODE%Save ELITE.unprot.bin
[X]
Label ABCDEFG in subroutine Elite loader (Part 2 of 6)
[X]
Label AFOOL in subroutine Elite loader (Part 5 of 6)
[X]
Variable B% (category: Drawing the screen)
VDU commands for setting the square mode 4 screen
[X]
Subroutine BEGIN% (category: Copy protection)
Single-byte decryption and copying routine, run on the stack
[X]
Variable BLOCK (category: Copy protection)
Addresses for the obfuscated jumps that use RTS not JMP
[X]
Configuration variable C%
C% is set to the location that the main game code gets moved to after it is loaded
[X]
Subroutine CHECKER (category: Copy protection)
Run checksum checks on tokens, loader and tape block count
[X]
Variable CHECKV (category: Copy protection)
The address of the LBL routine in the decryption header
[X]
Label CHK in subroutine Elite loader (Part 6 of 6)
[X]
Variable CNT (category: Drawing planets)
A counter for use in drawing Saturn's planetary body
[X]
Variable CNT2 (category: Drawing planets)
A counter for use in drawing Saturn's background stars
[X]
Variable CNT3 (category: Drawing planets)
A counter for use in drawing Saturn's rings
[X]
Configuration variable CODE%
CODE% is set to the assembly address of the loader code file that we assemble in this file ("ELITE"), which is at the lowest DFS page value of &1100 for the version that loads from disc
[X]
Configuration variable D%
D% is set to the address of the byte after the end of the code, i.e. the byte after checksum0 at XX21
[X]
Subroutine DORND (category: Maths (Arithmetic))
Generate random numbers
[X]
Variable David2 in subroutine Elite loader (Part 4 of 6)
This byte was changed to &20 by part 2, so by the time
[X]
Variable David23 (category: Copy protection)
Address pointer to the start of the 6502 stack
[X]
Label David3 in subroutine Elite loader (Part 2 of 6)
[X]
Label David5 in subroutine Elite loader (Part 4 of 6)
[X]
Subroutine David7 (category: Copy protection)
Part of the multi-jump obfuscation code in PROT1
[X]
Label David8 in subroutine Elite loader (Part 2 of 6)
[X]
Variable David9 (category: Copy protection)
Address used as part of the stack-based decryption loop
[X]
Variable E% (category: Sound)
Sound envelope definitions
[X]
Label ENTRY in subroutine Elite loader (Part 2 of 6)
[X]
Label ENTRY2 in subroutine Elite loader (Part 5 of 6)
[X]
Macro FNE (category: Sound)
Macro definition for defining a sound envelope
[X]
Variable FOOLV (category: Copy protection)
Part of the AFOOL roundabout obfuscation routine
[X]
Label FRED1 in subroutine Elite loader (Part 2 of 6)
[X]
Subroutine IRQ1 (category: Drawing the screen)
The loader's screen-mode interrupt handler (IRQ1V points here)
[X]
Configuration variable IRQ1V
The IRQ1V vector that we intercept to implement the split-screen mode
[X]
Entry point Ian1 in subroutine Elite loader (Part 2 of 6) (category: Loader)
Re-entry point following the wild goose chase obfuscation
[X]
Configuration variable L%
L% points to the start of the actual game code from elite-source.asm, after the &28 bytes of header code that are inserted by elite-bcfs.asm
[X]
Label LBLa in subroutine Elite loader (Part 4 of 6)
[X]
Configuration variable LC%
LC% is set to the maximum size of the main game code (as the code starts at C% and screen memory starts at &6000)
[X]
Configuration variable LE%
LE% is the address to which the code from UU% onwards is copied in part 3. It contains
[X]
Configuration variable LEN
Total number of bytes that get pushed on the stack for execution there (33)
[X]
Configuration variable LEN1
Size of the BEGIN% routine that gets pushed onto the stack and executed there
[X]
Configuration variable LOAD%
LOAD% is the load address of the main game code file ("ELTcode" for loading from disc, "ELITEcode" for loading from tape)
[X]
Label LOOP in subroutine Elite loader (Part 2 of 6)
[X]
Variable M2 (category: Utility routines)
Used for testing the 6522 System VIA status byte in IRQ1
[X]
Variable MAINSUM (category: Copy protection)
Two checksums for the decryption header and text token table
[X]
Variable MESS1 (category: Utility routines)
Contains an OS command string for loading the main game code
[X]
Variable MHCA (category: Copy protection)
Used to set one of the vectors in the copy protection code
[X]
Label ML1 in subroutine Elite loader (Part 5 of 6)
[X]
Configuration variable N%
N% is set to the number of bytes in the VDU table, so we can loop through them in part 2 below
[X]
Configuration variable NETV
The NETV vector that we intercept as part of the copy protection
[X]
Label OS100 in subroutine Elite loader (Part 2 of 6)
[X]
Subroutine OSB (category: Utility routines)
A convenience routine for calling OSBYTE with Y = 0
[X]
Configuration variable OSBYTE
The address for the OSBYTE routine
[X]
Configuration variable OSPRNT
The address for the OSPRNT vector
[X]
Configuration variable OSWORD
The address for the OSWORD routine
[X]
Configuration variable OSWRCH
The address for the OSWRCH routine
[X]
Subroutine PIX (category: Drawing pixels)
Draw a single pixel at a specific coordinate
[X]
Label PL1 in subroutine PLL1 (Part 3 of 3)
[X]
Label PLC1 in subroutine PLL1 (Part 1 of 3)
[X]
Label PLC2 in subroutine PLL1 (Part 2 of 3)
[X]
Label PLC3 in subroutine PLL1 (Part 3 of 3)
[X]
Subroutine PLL1 (Part 1 of 3) (category: Drawing planets)
Draw Saturn on the loading screen (draw the planet)
[X]
Label PLL2 in subroutine PLL1 (Part 2 of 3)
[X]
Label PLL3 in subroutine PLL1 (Part 3 of 3)
[X]
Label PROT1 in subroutine Elite loader (Part 2 of 6)
[X]
Variable RAND in subroutine Elite loader (Part 4 of 6)
The random number seed used for drawing Saturn
[X]
Subroutine ROOT (category: Maths (Arithmetic))
Calculate ZP = SQRT(ZP(1 0))
[X]
Configuration variable S%
S% points to the entry point for the main game code
[X]
Subroutine SQUA2 (category: Maths (Arithmetic))
Calculate (A P) = A * A
[X]
Configuration variable SVN
SVN is where we store the "saving in progress" flag, and it matches the location in elite-source.asm
[X]
Subroutine TT26 (category: Text)
Print a character at the text cursor (WRCHV points here)
[X]
Variable TWOS (category: Drawing pixels)
Ready-made single-pixel character row bytes for mode 4
[X]
Workspace UU% (category: Workspaces)
Marker for a block that is moved as part of the obfuscation
[X]
Configuration variable VEC
VEC is where we store the original value of the IRQ1 vector, and it matches the value in elite-source.asm
[X]
Configuration variable VIA
Memory-mapped space for accessing internal hardware, such as the video ULA, 6845 CRTC and 6522 VIAs (also known as SHEILA)
[X]
Configuration variable VSCAN
Defines the split position in the split-screen mode
[X]
Variable XC (category: Text)
The x-coordinate of the text cursor
[X]
Variable YC (category: Text)
The y-coordinate of the text cursor
[X]
Workspace ZP (category: Workspaces)
Important variables used by the loader
[X]
Label abrk in subroutine Elite loader (Part 2 of 6)
[X]
[X]
Variable block1 (category: Drawing the screen)
Palette data for the two dashboard colour scheme
[X]
Variable block2 (category: Drawing the screen)
Palette data for the space part of the screen
[X]
Subroutine command (category: Utility routines)
Execute an OS command
[X]
Label crunchit in subroutine Elite loader (Part 4 of 6)
[X]
Subroutine doPROT1 (category: Copy protection)
Routine to self-modify the loader code
[X]
Label itsOK in subroutine Elite loader (Part 6 of 6)
[X]
Entry point nononono in subroutine Elite loader (Part 6 of 6) (category: Loader)
Reset the machine
[X]
Variable oscliv (category: Utility routines)
Contains the address of OSCLIV, for executing OS commands
[X]
Subroutine osprint (category: Utility routines)
Print a character
[X]
Label purge in subroutine Elite loader (Part 2 of 6)
[X]
Subroutine swine (category: Copy protection)
Resets the machine if the copy protection detects a problem
[X]
Label swine2 in subroutine Elite loader (Part 4 of 6)