├── .gitattributes ├── .gitignore ├── 1-source-files ├── README.md ├── basic-programs │ ├── $.ELITE-cassette.bin │ ├── $.ELITE-disc.bin │ └── README.md ├── boot-files │ ├── $.!BOOT.bin │ └── README.md ├── images │ ├── P.(C)ASFT.bin │ ├── P.A-SOFT.bin │ ├── P.DIALS.bin │ ├── P.ELITE.bin │ └── README.md └── main-sources │ ├── README.md │ ├── elite-bcfs.asm │ ├── elite-build-options.asm │ ├── elite-disc.asm │ ├── elite-loader.asm │ ├── elite-readme.asm │ └── elite-source.asm ├── 2-build-files ├── README.md ├── crc32.py ├── elite-checksum.py ├── libiconv2.dll ├── libintl3.dll ├── make.exe ├── mktibet-0.3.php └── tibetuef-0.8.php ├── 3-assembled-output ├── ELITECO.bin ├── ELITECO.unprot.bin ├── ELITEDA.bin ├── ELTA.bin ├── ELTB.bin ├── ELTC.bin ├── ELTD.bin ├── ELTE.bin ├── ELTF.bin ├── ELTG.bin ├── README.md ├── README.txt ├── SHIPS.bin ├── WORDS9.bin └── compile.txt ├── 4-reference-binaries ├── README.md ├── ib-acornsoft │ ├── ELITECO.bin │ ├── ELITECO.unprot.bin │ ├── ELITEDA.bin │ ├── ELTA.bin │ ├── ELTB.bin │ ├── ELTC.bin │ ├── ELTD.bin │ ├── ELTE.bin │ ├── ELTF.bin │ ├── ELTG.bin │ ├── SHIPS.bin │ └── WORDS9.bin └── ib-superior │ ├── ELITECO.bin │ ├── ELITECO.unprot.bin │ ├── ELITEDA.bin │ ├── ELTA.bin │ ├── ELTB.bin │ ├── ELTC.bin │ ├── ELTD.bin │ ├── ELTE.bin │ ├── ELTF.bin │ ├── ELTG.bin │ ├── SHIPS.bin │ └── WORDS9.bin ├── 5-compiled-game-discs ├── README.md ├── elite-electron-ib-acornsoft.ssd ├── elite-electron-ib-acornsoft.uef ├── elite-electron-ib-superior.ssd └── elite-electron-ib-superior.uef ├── Makefile ├── README.md └── make.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # IDE files 22 | .vscode/ 23 | *.code-workspace 24 | run.bat 25 | -------------------------------------------------------------------------------- /1-source-files/README.md: -------------------------------------------------------------------------------- 1 | # Source files for the Electron version of Elite 2 | 3 | This folder contains the source files for the Electron version of Elite. 4 | 5 | * [basic-programs](basic-programs) contains any BASIC programs to be included on the final game disc 6 | 7 | * [boot-files](boot-files) contains any !BOOT files to be included on the final game disc 8 | 9 | * [images](images) contains the image binaries for the title screen and dashboard 10 | 11 | * [main-sources](main-sources) contains the annotated source code 12 | 13 | --- 14 | 15 | Right on, Commanders! 16 | 17 | _Mark Moxon_ -------------------------------------------------------------------------------- /1-source-files/basic-programs/$.ELITE-cassette.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/1-source-files/basic-programs/$.ELITE-cassette.bin -------------------------------------------------------------------------------- /1-source-files/basic-programs/$.ELITE-disc.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/1-source-files/basic-programs/$.ELITE-disc.bin -------------------------------------------------------------------------------- /1-source-files/basic-programs/README.md: -------------------------------------------------------------------------------- 1 | # BASIC programs for the Electron version of Elite 2 | 3 | This folder contains the BASIC programs from the original game disc for the Electron version of Elite on Ian Bell's personal website. 4 | 5 | * [$.ELITE-cassette.bin]($.ELITE-cassette.bin) is the "mode 7" loader program for the cassette version, which includes a blank window for showing loading progress 6 | 7 | * [$.ELITE-disc.bin]($.ELITE-disc.bin) is the "mode 7" loader program for the disc version, which doesn't include a blank window 8 | 9 | By default the disc version is included in the build, but this can be changed in the [elite-disc.asm](../main-sources/elite-disc.asm) source file. 10 | 11 | --- 12 | 13 | Right on, Commanders! 14 | 15 | _Mark Moxon_ -------------------------------------------------------------------------------- /1-source-files/boot-files/$.!BOOT.bin: -------------------------------------------------------------------------------- 1 | *BASIC CHAIN "ELITE" -------------------------------------------------------------------------------- /1-source-files/boot-files/README.md: -------------------------------------------------------------------------------- 1 | # Boot files for the Electron version of Elite 2 | 3 | This folder contains the boot file from the original game disc for the Electron version of Elite on Ian Bell's personal website. 4 | 5 | * [$.!BOOT.bin]($.!BOOT.bin) is the original boot file from the game disc 6 | 7 | --- 8 | 9 | Right on, Commanders! 10 | 11 | _Mark Moxon_ -------------------------------------------------------------------------------- /1-source-files/images/P.(C)ASFT.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/1-source-files/images/P.(C)ASFT.bin -------------------------------------------------------------------------------- /1-source-files/images/P.A-SOFT.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/1-source-files/images/P.A-SOFT.bin -------------------------------------------------------------------------------- /1-source-files/images/P.DIALS.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/1-source-files/images/P.DIALS.bin -------------------------------------------------------------------------------- /1-source-files/images/P.ELITE.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/1-source-files/images/P.ELITE.bin -------------------------------------------------------------------------------- /1-source-files/images/README.md: -------------------------------------------------------------------------------- 1 | # Image binaries for the Electron version of Elite 2 | 3 | This folder contains the image binaries from the original game disc for the Electron version of Elite on Ian Bell's personal website. 4 | 5 | * [P.(C)ASFT.bin](P.(C)ASFT.bin) is the "(c) ACORNSOFT 1984" image for the bottom of the title screen 6 | 7 | * [P.A-SOFT.bin](P.A-SOFT.bin) is the "ACORNSOFT" image for the very top of the title screen 8 | 9 | * [P.DIALS.bin](P.DIALS.bin) is the dashboard image 10 | 11 | * [P.ELITE.bin](P.ELITE.bin) is the "ELITE" image for the title screen 12 | 13 | --- 14 | 15 | Right on, Commanders! 16 | 17 | _Mark Moxon_ -------------------------------------------------------------------------------- /1-source-files/main-sources/README.md: -------------------------------------------------------------------------------- 1 | # Annotated source code for the Electron version of Elite 2 | 3 | This folder contains the annotated source code for the Electron version of Elite. 4 | 5 | * Main source files: 6 | 7 | * [elite-source.asm](elite-source.asm) contains the main source for the game 8 | 9 | * [elite-bcfs.asm](elite-bcfs.asm) contains the Big Code File source, which concatenates individually assembled binaries into the final game binary 10 | 11 | * Other source files: 12 | 13 | * [elite-loader.asm](elite-loader.asm) contains the source for the loader 14 | 15 | * [elite-disc.asm](elite-disc.asm) builds the SSD disc image from the assembled binaries and other source files 16 | 17 | * [elite-readme.asm](elite-readme.asm) generates a README file for inclusion on the SSD disc image 18 | 19 | * Files that are generated during the build process: 20 | 21 | * [elite-build-options.asm](elite-build-options.asm) stores the make options in BeebAsm format so they can be included in the assembly process 22 | 23 | --- 24 | 25 | Right on, Commanders! 26 | 27 | _Mark Moxon_ -------------------------------------------------------------------------------- /1-source-files/main-sources/elite-bcfs.asm: -------------------------------------------------------------------------------- 1 | \ ****************************************************************************** 2 | \ 3 | \ ACORN ELECTRON ELITE BIG CODE FILE SOURCE 4 | \ 5 | \ Acorn Electron Elite was written by Ian Bell and David Braben and is copyright 6 | \ Acornsoft 1984 7 | \ 8 | \ The code in this file has been reconstructed from a disassembly of the version 9 | \ released on Ian Bell's personal website at http://www.elitehomepage.org/ 10 | \ 11 | \ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes 12 | \ in the documentation are entirely my fault 13 | \ 14 | \ The terminology and notations used in this commentary are explained at 15 | \ https://elite.bbcelite.com/terminology 16 | \ 17 | \ The deep dive articles referred to in this commentary can be found at 18 | \ https://elite.bbcelite.com/deep_dives 19 | \ 20 | \ ------------------------------------------------------------------------------ 21 | \ 22 | \ This source file contains code to produce the Big Code File for Acorn Electron 23 | \ Elite. The Big Code File comprises the game code, the ship blueprints and the 24 | \ game text. 25 | \ 26 | \ ------------------------------------------------------------------------------ 27 | \ 28 | \ This source file produces the following binary files: 29 | \ 30 | \ * ELITECO.bin 31 | \ 32 | \ after reading in the following files: 33 | \ 34 | \ * ELTA.bin 35 | \ * ELTB.bin 36 | \ * ELTC.bin 37 | \ * ELTD.bin 38 | \ * ELTE.bin 39 | \ * ELTF.bin 40 | \ * ELTG.bin 41 | \ * SHIPS.bin 42 | \ * WORDS9.bin 43 | \ 44 | \ ****************************************************************************** 45 | 46 | INCLUDE "1-source-files/main-sources/elite-build-options.asm" 47 | 48 | _IB_SUPERIOR = (_VARIANT = 1) 49 | _IB_ACORNSOFT = (_VARIANT = 2) 50 | 51 | GUARD &5800 \ Guard against assembling over screen memory 52 | 53 | \ ****************************************************************************** 54 | \ 55 | \ Configuration variables 56 | \ 57 | \ ****************************************************************************** 58 | 59 | CODE% = &0D00 \ CODE% is set to the location that the main game code 60 | \ gets moved to after it is loaded 61 | 62 | IF _DISC 63 | 64 | LOAD% = &2000 \ The load address of the main game code file 65 | 66 | ELSE 67 | 68 | LOAD% = &0E00 \ The load address of the main game code file 69 | 70 | ENDIF 71 | 72 | \ ****************************************************************************** 73 | \ 74 | \ Load the compiled binaries to create the Big Code File 75 | \ 76 | \ ****************************************************************************** 77 | 78 | ORG CODE% 79 | 80 | .elitea 81 | 82 | PRINT "elitea = ", ~P% 83 | INCBIN "3-assembled-output/ELTA.bin" 84 | 85 | .eliteb 86 | 87 | PRINT "eliteb = ", ~P% 88 | INCBIN "3-assembled-output/ELTB.bin" 89 | 90 | .elitec 91 | 92 | PRINT "elitec = ", ~P% 93 | INCBIN "3-assembled-output/ELTC.bin" 94 | 95 | .elited 96 | 97 | PRINT "elited = ", ~P% 98 | INCBIN "3-assembled-output/ELTD.bin" 99 | 100 | .elitee 101 | 102 | PRINT "elitee = ", ~P% 103 | INCBIN "3-assembled-output/ELTE.bin" 104 | 105 | .elitef 106 | 107 | PRINT "elitef = ", ~P% 108 | INCBIN "3-assembled-output/ELTF.bin" 109 | 110 | .eliteg 111 | 112 | PRINT "eliteg = ", ~P% 113 | INCBIN "3-assembled-output/ELTG.bin" 114 | 115 | .checksum0 116 | 117 | PRINT "checksum0 = ", ~P% 118 | 119 | SKIP 1 \ We skip this byte so we can insert the checksum later 120 | \ in elite-checksum.py 121 | 122 | IF _IB_ACORNSOFT 123 | 124 | SKIP 1 \ This byte appears to be unused 125 | 126 | ENDIF 127 | 128 | .ships 129 | 130 | PRINT "ships = ", ~P% 131 | INCBIN "3-assembled-output/SHIPS.bin" 132 | 133 | .end 134 | 135 | \ ****************************************************************************** 136 | \ 137 | \ Save ELITECO.unprot.bin 138 | \ 139 | \ ****************************************************************************** 140 | 141 | PRINT "P% = ", ~P% 142 | PRINT "S.ELITECO ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD% 143 | SAVE "3-assembled-output/ELITECO.unprot.bin", CODE%, P%, LOAD% 144 | -------------------------------------------------------------------------------- /1-source-files/main-sources/elite-build-options.asm: -------------------------------------------------------------------------------- 1 | _VERSION=5 2 | _VARIANT=1 3 | _REMOVE_CHECKSUMS=FALSE 4 | _MAX_COMMANDER=FALSE 5 | _DISC=TRUE 6 | -------------------------------------------------------------------------------- /1-source-files/main-sources/elite-disc.asm: -------------------------------------------------------------------------------- 1 | \ ****************************************************************************** 2 | \ 3 | \ ACORN ELECTRON ELITE DISC IMAGE SCRIPT 4 | \ 5 | \ Acorn Electron Elite was written by Ian Bell and David Braben and is copyright 6 | \ Acornsoft 1984 7 | \ 8 | \ The code in this file has been reconstructed from a disassembly of the version 9 | \ released on Ian Bell's personal website at http://www.elitehomepage.org/ 10 | \ 11 | \ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes 12 | \ in the documentation are entirely my fault 13 | \ 14 | \ The terminology and notations used in this commentary are explained at 15 | \ https://elite.bbcelite.com/terminology 16 | \ 17 | \ The deep dive articles referred to in this commentary can be found at 18 | \ https://elite.bbcelite.com/deep_dives 19 | \ 20 | \ ------------------------------------------------------------------------------ 21 | \ 22 | \ This source file produces an SSD disc image for Acorn Electron Elite. 23 | \ 24 | \ ------------------------------------------------------------------------------ 25 | \ 26 | \ This source file produces one of the following SSD disc images, depending on 27 | \ which release is being built: 28 | \ 29 | \ * elite-electron-ib-superior.ssd 30 | \ * elite-electron-ib-acornsoft.ssd 31 | \ 32 | \ This can be loaded into an emulator or a real Electron. 33 | \ 34 | \ ****************************************************************************** 35 | 36 | INCLUDE "1-source-files/main-sources/elite-build-options.asm" 37 | 38 | _IB_SUPERIOR = (_VARIANT = 1) 39 | _IB_ACORNSOFT = (_VARIANT = 2) 40 | 41 | IF _DISC 42 | 43 | PUTFILE "1-source-files/boot-files/$.!BOOT.bin", "!BOOT", &FFFFFF, &FFFFFF 44 | PUTFILE "1-source-files/basic-programs/$.ELITE-disc.bin", "ELITE", &FF0E00, &FF8023 45 | PUTFILE "3-assembled-output/ELITECO.bin", "ELITECO", &000000, &FFFFFF 46 | PUTFILE "3-assembled-output/ELITEDA.bin", "ELITEDA", &FF4400, &FF5200 47 | 48 | ELSE 49 | 50 | PUTFILE "1-source-files/boot-files/$.!BOOT.bin", "!BOOT", &FFFFFF, &FFFFFF 51 | PUTFILE "1-source-files/basic-programs/$.ELITE-cassette.bin", "ELITE", &FF0E00, &FF8023 52 | \PUTFILE "3-assembled-output/ELITECO.bin", "ELITEcode", &000000, &FFFFFF 53 | \PUTFILE "3-assembled-output/ELITEDA.bin", "ELITEdata", &FF4400, &FF5200 54 | PUTFILE "3-assembled-output/ELITECO.bin", "ELITEco", &000000, &FFFFFF 55 | PUTFILE "3-assembled-output/ELITEDA.bin", "ELITEda", &FF4400, &FF5200 56 | 57 | ENDIF 58 | 59 | PUTFILE "3-assembled-output/README.txt", "README", &FFFFFF, &FFFFFF 60 | -------------------------------------------------------------------------------- /1-source-files/main-sources/elite-readme.asm: -------------------------------------------------------------------------------- 1 | \ ****************************************************************************** 2 | \ 3 | \ ACORN ELECTRON ELITE README SOURCE 4 | \ 5 | \ Acorn Electron Elite was written by Ian Bell and David Braben and is copyright 6 | \ Acornsoft 1984 7 | \ 8 | \ The code in this file has been reconstructed from a disassembly of the version 9 | \ released on Ian Bell's personal website at http://www.elitehomepage.org/ 10 | \ 11 | \ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes 12 | \ in the documentation are entirely my fault 13 | \ 14 | \ The terminology and notations used in this commentary are explained at 15 | \ https://elite.bbcelite.com/terminology 16 | \ 17 | \ The deep dive articles referred to in this commentary can be found at 18 | \ https://elite.bbcelite.com/deep_dives 19 | \ 20 | \ ------------------------------------------------------------------------------ 21 | \ 22 | \ This source file produces a README file for Acorn Electron Elite. 23 | \ 24 | \ ------------------------------------------------------------------------------ 25 | \ 26 | \ This source file produces the following binary file: 27 | \ 28 | \ * README.txt 29 | \ 30 | \ ****************************************************************************** 31 | 32 | INCLUDE "1-source-files/main-sources/elite-build-options.asm" 33 | 34 | _IB_SUPERIOR = (_VARIANT = 1) 35 | _IB_ACORNSOFT = (_VARIANT = 2) 36 | 37 | .readme 38 | 39 | EQUB 10, 13 40 | EQUS "---------------------------------------" 41 | EQUB 10, 13 42 | EQUS "Acornsoft Elite" 43 | EQUB 10, 13 44 | EQUB 10, 13 45 | EQUS "Version: Acorn Electron" 46 | EQUB 10, 13 47 | 48 | IF _IB_ACORNSOFT 49 | 50 | EQUS "Variant: Ian Bell's Acornsoft UEF" 51 | EQUB 10, 13 52 | EQUS "Product: Acornsoft SLG38 (TBC)" 53 | EQUB 10, 13 54 | 55 | ELIF _IB_SUPERIOR 56 | 57 | EQUS "Variant: Ian Bell's Superior UEF" 58 | EQUB 10, 13 59 | EQUS "Product: Superior Software (TBC)" 60 | EQUB 10, 13 61 | 62 | ENDIF 63 | 64 | EQUB 10, 13 65 | EQUS "See www.bbcelite.com for details" 66 | EQUB 10, 13 67 | EQUS "---------------------------------------" 68 | EQUB 10, 13 69 | 70 | SAVE "3-assembled-output/README.txt", readme, P% 71 | 72 | -------------------------------------------------------------------------------- /2-build-files/README.md: -------------------------------------------------------------------------------- 1 | # Build files for the Electron version of Elite 2 | 3 | This folder contains support scripts for building the Electron version of Elite. 4 | 5 | * [crc32.py](crc32.py) calculates checksums during the verify stage and compares the results with the relevant binaries in the [4-reference-binaries](../4-reference-binaries) folder 6 | 7 | * [elite-checksum.py](elite-checksum.py) adds checksums and encryption to the assembled output 8 | 9 | * mktibet.php and tibetuef.php are used to create UEFs from files. 10 | 11 | It also contains the `make.exe` executable for Windows, plus the required DLL files. 12 | 13 | --- 14 | 15 | Right on, Commanders! 16 | 17 | _Mark Moxon_ -------------------------------------------------------------------------------- /2-build-files/crc32.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # ****************************************************************************** 4 | # 5 | # ELITE VERIFICATION SCRIPT 6 | # 7 | # Written by Kieran Connell, extended by Mark Moxon 8 | # 9 | # This script performs checksums on the compiled files from the build process, 10 | # and checks them against the extracted files from the original source disc 11 | # 12 | # ****************************************************************************** 13 | 14 | from __future__ import print_function 15 | import sys 16 | import os 17 | import os.path 18 | import zlib 19 | 20 | 21 | def main(): 22 | if len(sys.argv) <= 2: 23 | # Do CRC on single folder 24 | folder = sys.argv[1] if len(sys.argv) == 2 else "." 25 | names = sorted(os.listdir(folder)) 26 | 27 | print() 28 | print('Checksum Size Filename') 29 | print('------------------------------------------') 30 | 31 | for name in names: 32 | if name.endswith(".bin"): 33 | full_name = os.path.join(folder, name) 34 | if not os.path.isfile(full_name): 35 | continue 36 | with open(full_name, 'rb') as f: 37 | data = f.read() 38 | print('%08x %5d %s' % ( 39 | zlib.crc32(data) & 0xffffffff, 40 | len(data), 41 | full_name) 42 | ) 43 | print() 44 | else: 45 | # Do CRC on two folders 46 | folder1 = sys.argv[1] 47 | names1 = sorted(os.listdir(folder1)) 48 | folder2 = sys.argv[2] 49 | names2 = sorted(os.listdir(folder2)) 50 | names = list(names1) 51 | names.extend(x for x in names2 if x not in names) 52 | 53 | if '4-reference-binaries' in folder1: 54 | src = '[--originals--]' 55 | elif 'output' in folder1: 56 | src = '[---output----]' 57 | else: 58 | src = '[{0: ^13}]'.format(folder1[0:13]).replace(' ', '-') 59 | 60 | if '4-reference-binaries' in folder2: 61 | dest = '[--originals--]' 62 | elif 'output' in folder2: 63 | dest = '[---output----]' 64 | else: 65 | dest = '[{0: ^13}]'.format(folder2[0:13]).replace(' ', '-') 66 | 67 | print('Results for variant: ' + os.path.basename(folder1)) 68 | print(src + ' ' + dest) 69 | print('Checksum Size Checksum Size Match Filename') 70 | print('-----------------------------------------------------------') 71 | 72 | for name in names: 73 | if name.endswith(".bin"): 74 | full_name1 = os.path.join(folder1, name) 75 | full_name2 = os.path.join(folder2, name) 76 | 77 | if name in names1 and name in names2 and os.path.isfile(full_name1) and os.path.isfile(full_name2): 78 | with open(full_name1, 'rb') as f: 79 | data1 = f.read() 80 | with open(full_name2, 'rb') as f: 81 | data2 = f.read() 82 | crc1 = zlib.crc32(data1) & 0xffffffff 83 | crc2 = zlib.crc32(data2) & 0xffffffff 84 | match = ' Yes ' if crc1 == crc2 and len(data1) == len(data2) else ' No ' 85 | print('%08x %5d %08x %5d %s %s' % ( 86 | crc1, 87 | len(data1), 88 | crc2, 89 | len(data2), 90 | match, 91 | name) 92 | ) 93 | elif name in names1 and os.path.isfile(full_name1): 94 | with open(full_name1, 'rb') as f: 95 | data = f.read() 96 | print('%08x %5d %s %s %s %s' % ( 97 | zlib.crc32(data) & 0xffffffff, 98 | len(data), 99 | '- ', 100 | ' -', 101 | ' - ', 102 | name) 103 | ) 104 | elif name in names2 and os.path.isfile(full_name2): 105 | with open(full_name2, 'rb') as f: 106 | data = f.read() 107 | print('%s %s %08x %5d %s %s' % ( 108 | '- ', 109 | ' -', 110 | zlib.crc32(data) & 0xffffffff, 111 | len(data), 112 | ' - ', 113 | name) 114 | ) 115 | print() 116 | 117 | 118 | if __name__ == '__main__': 119 | main() 120 | -------------------------------------------------------------------------------- /2-build-files/elite-checksum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # ****************************************************************************** 4 | # 5 | # ELITE CHECKSUM SCRIPT 6 | # 7 | # Written by Kieran Connell and Mark Moxon 8 | # 9 | # This script applies encryption, checksums and obfuscation to the compiled 10 | # binaries for the main game and the loader. The script has two parts: 11 | # 12 | # * The first part generates an encrypted version of the main game's "ELTcode" 13 | # binary, based on the code in the original "S.BCFS" BASIC source program 14 | # 15 | # * The second part generates an encrypted version of the main game's "ELITE" 16 | # binary, based on the code in the original "ELITES" BASIC source program 17 | # 18 | # ****************************************************************************** 19 | 20 | from __future__ import print_function 21 | import sys 22 | 23 | argv = sys.argv 24 | encrypt = True 25 | release = 1 26 | 27 | for arg in argv[1:]: 28 | if arg == "-u": 29 | encrypt = False 30 | if arg == "-rel1": 31 | release = 1 32 | if arg == "-rel2": 33 | release = 2 34 | 35 | print("Electron Elite Checksum") 36 | print("Encryption = ", encrypt) 37 | 38 | # Load assembled code files that make up big code file 39 | 40 | data_block = bytearray() 41 | eliteb_offset = 0 42 | 43 | # Append all assembled code files 44 | 45 | elite_names = ("ELTA", "ELTB", "ELTC", "ELTD", "ELTE", "ELTF", "ELTG") 46 | 47 | for file_name in elite_names: 48 | print(str(len(data_block)), file_name) 49 | if file_name == "ELTB": 50 | eliteb_offset = len(data_block) 51 | elite_file = open("3-assembled-output/" + file_name + ".bin", "rb") 52 | data_block.extend(elite_file.read()) 53 | elite_file.close() 54 | 55 | # Commander data checksum 56 | 57 | commander_offset = 0x52 58 | CH = 0x4B - 2 59 | CY = 0 60 | for i in range(CH, 0, -1): 61 | CH = CH + CY + data_block[eliteb_offset + i + 7] 62 | CY = (CH > 255) & 1 63 | CH = CH % 256 64 | CH = CH ^ data_block[eliteb_offset + i + 8] 65 | 66 | print("Commander checksum = ", hex(CH)) 67 | 68 | data_block[eliteb_offset + commander_offset] = CH ^ 0xA9 69 | data_block[eliteb_offset + commander_offset + 1] = CH 70 | 71 | # Skip one byte for checksum0 72 | 73 | checksum0_offset = len(data_block) 74 | data_block.append(0) 75 | 76 | # Skip another byte for the unused byte after checksum0 for IB Disc variant 77 | 78 | if release == 2: 79 | data_block.append(0) 80 | 81 | # Append SHIPS file 82 | 83 | ships_file = open("3-assembled-output/SHIPS.bin", "rb") 84 | data_block.extend(ships_file.read()) 85 | ships_file.close() 86 | 87 | print("3-assembled-output/SHIPS.bin file read") 88 | 89 | # Calculate checksum0 90 | 91 | if encrypt: 92 | checksum0 = 0 93 | for n in range(0x0, 0x4600): 94 | checksum0 += data_block[n + 0x28] 95 | 96 | # This is an unprotected version, so let's just hard-code the checksum 97 | # to the value from the extracted binary 98 | checksum0 = 0x67 99 | 100 | print("checksum 0 = ", hex(checksum0)) 101 | 102 | data_block[checksum0_offset] = checksum0 % 256 103 | 104 | # Write output file for ELITECO 105 | 106 | output_file = open("3-assembled-output/ELITECO.bin", "wb") 107 | output_file.write(data_block) 108 | output_file.close() 109 | 110 | print("3-assembled-output/ELITECO.bin file saved") 111 | -------------------------------------------------------------------------------- /2-build-files/libiconv2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/2-build-files/libiconv2.dll -------------------------------------------------------------------------------- /2-build-files/libintl3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/2-build-files/libintl3.dll -------------------------------------------------------------------------------- /2-build-files/make.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/2-build-files/make.exe -------------------------------------------------------------------------------- /2-build-files/mktibet-0.3.php: -------------------------------------------------------------------------------- 1 | =7, reduce stupidity 28 | declare (strict_types=1); 29 | 30 | define ("MKT_E_OK", 0); 31 | define ("MKT_E_CLI", 1); 32 | define ("MKT_E_LOAD", 2); 33 | define ("MKT_E_LARGE_FILE", 3); 34 | define ("MKT_E_SAVE", 4); 35 | 36 | define ("CLI_STATE_IDLE", 0); 37 | define ("CLI_STATE_LOAD", 1); 38 | define ("CLI_STATE_EXEC", 2); 39 | define ("CLI_STATE_NAME", 3); 40 | define ("CLI_STATE_TIBET", 4); 41 | define ("CLI_STATE_BAUD", 5); 42 | 43 | define ("MAX_FILE_SIZE", 32 * 1024); 44 | 45 | define ("DURATION_PRE_GAP_S", 2.0); 46 | define ("DURATION_MID_GAP_S", 1.0); 47 | define ("DURATION_POST_GAP_S", 2.0); 48 | define ("DURATION_LEADER_S", 1.0); 49 | define ("TIBET_VERSION", 0.4); 50 | 51 | define ("MKTIBET_VERSION", 0.3); 52 | 53 | define ("DURATION_LEADER_CYCS", (int) (DURATION_LEADER_S * 2400.0)); 54 | 55 | $argv = $_SERVER['argv']; 56 | 57 | print "\n$argv[0] v".MKTIBET_VERSION."\n\n"; 58 | 59 | $e = process($argv); 60 | 61 | return $e; 62 | 63 | 64 | class FileOpts { 65 | 66 | var $load; 67 | var $exec; 68 | var $baud; 69 | var $prepend_dummy_byte; // 0.3 70 | 71 | var $have_load; 72 | var $have_exec; 73 | var $have_baud; 74 | var $have_no_dummy; // 0.3 75 | 76 | function __construct() { 77 | $this->load = 0xffff1900; 78 | $this->exec = 0xffff8023; 79 | $this->baud = 1200; 80 | $this->prepend_dummy_byte = TRUE; // 0.3 81 | $this->have_load = 0; 82 | $this->have_exec = 0; 83 | $this->have_baud = 0; 84 | $this->have_no_dummy = 0; // 0.3 85 | } 86 | 87 | function to_string() : string { 88 | $s="("; 89 | $s .= sprintf("load &%x; ", $this->load); 90 | $s .= sprintf("exec &%x; ", $this->exec); 91 | $s .= "baud ".$this->baud."; "; 92 | $s .= "prepend_dummy $this->prepend_dummy_byte"; // 0.3 93 | $s .= ")"; 94 | return $s; 95 | } 96 | 97 | } 98 | 99 | 100 | class TibetFile { 101 | 102 | var $mos_name; 103 | var $path_to_src; 104 | var $opts; 105 | var $data; 106 | var $have_mos_name; 107 | 108 | function __construct (int $baud) { 109 | $this->mos_name=NULL; 110 | $this->data=""; 111 | $this->path_to_src = ""; 112 | $this->have_mos_name = 0; 113 | $this->opts = new FileOpts; 114 | $this->opts->baud = $baud; 115 | $this->opts->prepend_dummy_byte = TRUE; // v0.3: BeebEm fail 116 | } 117 | 118 | function to_string() : string { 119 | $s=""; 120 | $s .= "MOS: \"$this->mos_name\", "; 121 | //$s .= "path: \"$this->path_to_src\", "; 122 | $s .= $this->opts->to_string(); 123 | return $s; 124 | } 125 | 126 | } 127 | 128 | 129 | function process(array $argv) { 130 | 131 | $files = array(); 132 | $tibet_fn="tibet.tibet"; 133 | 134 | $e = parse_cli ($argv, $files, $tibet_fn); // files, tibet_fn populated 135 | if (MKT_E_OK != $e) { 136 | usage($argv[0]); 137 | return $e; 138 | } 139 | 140 | $e = fill_missing_names ($files); 141 | if (MKT_E_OK != $e) { return $e; } 142 | 143 | $e = load_files ($files); 144 | if (MKT_E_OK != $e) { return $e; } 145 | 146 | foreach ($files as $k=>$v) { 147 | print $v->path_to_src."\n"; 148 | print " ".$v->to_string(); 149 | print "\n"; 150 | } 151 | 152 | $tibet_s = ""; 153 | $e = build_tibet ($files, $tibet_s); 154 | if (MKT_E_OK != $e) { return $e; } 155 | 156 | if (FALSE === @file_put_contents ($tibet_fn, $tibet_s)) { 157 | print "E: Could not write output file: $tibet_fn\n"; 158 | return MKT_E_SAVE; 159 | } 160 | 161 | print "Wrote ".strlen($tibet_s)." bytes: $tibet_fn\n"; 162 | 163 | return MKT_E_OK; 164 | } 165 | 166 | 167 | 168 | function build_tibet (array $files, string &$out) : int { 169 | $out .= "tibet ".TIBET_VERSION."\n\n"; 170 | $out .= "silence ".DURATION_PRE_GAP_S."\n\n"; 171 | $num_files = count($files); 172 | $prev_baud = 0; 173 | for ($n=0; $n < $num_files; $n++) { 174 | $f = $files[$n]; 175 | $d = $f->data; 176 | $len = strlen($d); 177 | // v0.3: Maddeningly, BeebEm misses the start of the first block unless you 178 | // send some dummy data first, because ... no, I've no idea why on earth it would do this 179 | if ($f->opts->prepend_dummy_byte) { 180 | $out .= "leader ".DURATION_LEADER_CYCS."\n"; 181 | $out .= "data\n----..--..--..--....\nend\n"; // standard MOS 1.2 &AA dummy byte 182 | $out .= "leader ".DURATION_LEADER_CYCS."\n\n"; 183 | } 184 | // split into blocks 185 | for ($i=0, $rem=$len, $bn=0; 186 | $i < $len; 187 | $i += 256, $rem -= 256, $bn++) { 188 | if ($rem < 256) { 189 | $blklen = $rem; 190 | } else { 191 | $blklen = 256; 192 | } 193 | $blk_payload = substr ($d, $i, $blklen); 194 | $blk_built = ""; 195 | $e = build_block ($bn, $rem <= 256, FALSE, $blk_payload, $f, $blk_built); 196 | if (MKT_E_OK != $e) { return $e; } 197 | $blk_encoded = ""; 198 | $e = tibet_encode ($blk_built, 199 | $f->opts->baud, 200 | $prev_baud != $f->opts->baud, // baud changed? 201 | $blk_encoded); 202 | if (MKT_E_OK != $e) { return $e; } 203 | $leader_cycs = DURATION_LEADER_CYCS; 204 | $out .= "leader ".$leader_cycs."\n\n"; 205 | $out .= $blk_encoded; 206 | //printf("%02x len %u\n", $bn, strlen($blk)); 207 | $prev_baud = $f->opts->baud; 208 | } 209 | $out .= "leader ".DURATION_LEADER_CYCS."\n\n"; 210 | if ($n < ($num_files - 1)) { 211 | $out .= "silence ".DURATION_MID_GAP_S."\n\n"; 212 | } 213 | } 214 | $out .= "silence ".DURATION_POST_GAP_S."\n\n"; 215 | return MKT_E_OK; 216 | } 217 | 218 | 219 | function tibet_encode (string $in, int $baud, bool $baud_changed, string &$out) : int { 220 | $out = ""; 221 | if ($baud_changed) { 222 | $out .= "/baud ".$baud."\n"; 223 | } 224 | $out .= "data\n"; 225 | $len = strlen($in); 226 | $one = ($baud == 1200) ? ".." : "........"; 227 | $zero = ($baud == 1200) ? "--" : "--------"; 228 | $newline_limit = ($baud == 1200) ? 3 : 0; 229 | for ($n=0, $x=0; $n < $len; $n++, $x++) { 230 | $c = ord($in[$n]); 231 | $out .= $zero; // start bit 232 | for ($i=0; $i < 8; $i++) { 233 | $out .= ($c&1) ? $one : $zero; 234 | $c = ($c >> 1) & 0x7f; 235 | } 236 | $out .= $one; // stop bit 237 | if ($x == $newline_limit) { 238 | $out .= "\n"; 239 | $x = -1; 240 | } 241 | } 242 | if ($x!=0) { $out .= "\n"; } 243 | $out .= "end\n\n"; 244 | return MKT_E_OK; 245 | } 246 | 247 | 248 | function build_block (int $bn, 249 | bool $final, 250 | bool $locked, 251 | string $payload, 252 | TibetFile $tf, 253 | string &$out) : int { 254 | $len = strlen($payload); 255 | $flags = ($final ? 0x80 : 0) | (($len == 0) ? 0x40 : 0) | ($locked ? 1 : 0); 256 | $hdr = $tf->mos_name. 257 | "\x00". 258 | to_le32($tf->opts->load). 259 | to_le32($tf->opts->exec). 260 | to_le16($bn). 261 | to_le16($len). 262 | chr($flags). 263 | "\x00\x00\x00\x00"; 264 | $hcrc = acorn_crc($hdr); 265 | $out = "*" . $hdr . to_be16($hcrc); 266 | if ($len > 0) { 267 | $dcrc = acorn_crc($payload); 268 | $out .= $payload . to_be16($dcrc); 269 | } 270 | return MKT_E_OK; 271 | } 272 | 273 | 274 | function acorn_crc (string $s) : int { 275 | $crc = 0; 276 | for ($n=0; $n < strlen($s); $n++) { 277 | //u8_t i; 278 | //u32_t c; 279 | //u16_t h; 280 | $b = ord($s[$n]); 281 | $c = $crc; 282 | $h = ($c>>8) & 0xff; 283 | $h = $b ^ $h; 284 | $c = ($c & 0x00ff) | (($h << 8) & 0xff00); 285 | for ($i=0; $i<8; $i++) { 286 | //u32_t t; 287 | $t = 0; 288 | if ($c & 0x8000) { 289 | $c = $c ^ 0x810; 290 | $t = 1; 291 | } 292 | $c = 0xffff & ($t | (($c<<1) & 0xfffe)); 293 | } 294 | $crc = $c; 295 | } 296 | return $crc; 297 | } 298 | 299 | 300 | function to_be16 (int $i) : string { 301 | $s = ""; 302 | $s .= chr(($i >> 8) & 0xff); 303 | $s .= chr($i & 0xff); 304 | return $s; 305 | } 306 | 307 | 308 | function to_le16 (int $i) : string { 309 | $s = ""; 310 | $s .= chr($i & 0xff); 311 | $s .= chr(($i >> 8) & 0xff); 312 | return $s; 313 | } 314 | 315 | 316 | function to_le32 (int $i) : string { 317 | $s = ""; 318 | $s .= chr($i & 0xff); 319 | $s .= chr(($i >> 8) & 0xff); 320 | $s .= chr(($i >> 16) & 0xff); 321 | $s .= chr(($i >> 24) & 0xff); 322 | return $s; 323 | } 324 | 325 | 326 | function load_files (array &$files) : int { 327 | foreach ($files as $k=>$file) { 328 | $p = $file->path_to_src; 329 | $f = @file_get_contents($p); 330 | if (FALSE === $f) { 331 | print "E: Could not load file: $p\n"; 332 | return MKT_E_LOAD; 333 | } 334 | if (strlen($f) > MAX_FILE_SIZE) { 335 | print "E: File was too large: $p\n"; 336 | return MKT_E_LARGE_FILE; 337 | } 338 | $file->data = $f; 339 | $files[$k] = $file; 340 | } 341 | return MKT_E_OK; 342 | } 343 | 344 | 345 | function fill_missing_names (array &$a) : int { 346 | foreach ($a as $k=>$file) { 347 | if ( ! isset($file->mos_name) ) { 348 | $basename = basename($file->path_to_src); 349 | $e = check_mos_filename($basename); 350 | if (MKT_E_OK != $e) { return $e; } 351 | $file->mos_name = $basename; 352 | $a[$k] = $file; // replace in array 353 | } 354 | } 355 | return MKT_E_OK; 356 | } 357 | 358 | 359 | function parse_cli (array $argv, array &$files, string &$tibet_fn) : int { 360 | 361 | $opts_done = 0; 362 | $state = 0; 363 | $argc = count($argv) - 1; 364 | 365 | $have_tibet_filename = 0; 366 | 367 | // current working file 368 | $f = new TibetFile(1200); 369 | 370 | $state = CLI_STATE_IDLE; 371 | 372 | for ($n=1; $n <= $argc; $n++) { 373 | $v = $argv[$n]; 374 | if ($state == CLI_STATE_IDLE) { 375 | if ($v[0] == "+") { 376 | // opt 377 | if ($v == "+x") { 378 | // execution address 379 | if ($f->opts->have_exec) { 380 | print "E: Cannot specify exec address twice.\n"; 381 | return MKT_E_CLI; 382 | } 383 | $f->opts->have_exec = 1; 384 | $state = CLI_STATE_EXEC; 385 | } else if ($v == "+d") { 386 | // load address 387 | if ($f->opts->have_load) { 388 | print "E: Cannot specify load address twice.\n"; 389 | return MKT_E_CLI; 390 | } 391 | $f->opts->have_load = 1; 392 | $state = CLI_STATE_LOAD; 393 | } else if ($v == "+b") { 394 | // baud 395 | if ($f->opts->have_baud) { 396 | print "E: Cannot specify baud rate twice.\n"; 397 | return MKT_E_CLI; 398 | } 399 | $f->opts->have_baud = 1; 400 | $state = CLI_STATE_BAUD; 401 | } else if ($v == "+n") { 402 | // overridden MOS filename 403 | if ($f->have_mos_name) { 404 | print "E: Cannot specify MOS filename twice.\n"; 405 | return MKT_E_CLI; 406 | } 407 | $f->have_mos_name = 1; 408 | $state = CLI_STATE_NAME; 409 | } else if ($v == "+t") { 410 | // TIBET filename 411 | if ($have_tibet_filename) { 412 | print "E: Cannot specify TIBET filename twice.\n"; 413 | return MKT_E_CLI; 414 | } 415 | $have_tibet_filename = 1; 416 | $state = CLI_STATE_TIBET; 417 | } else if ($v == "+no-dummy") { 418 | if ($f->opts->have_no_dummy) { 419 | print "E: Cannot specify no-dummy twice.\n"; 420 | return MKT_E_CLI; 421 | } 422 | $f->have_no_dummy = 1; 423 | $f->opts->prepend_dummy_byte = FALSE; 424 | } else { 425 | print "E: unknown option $v\n"; 426 | $e = MKT_E_CLI; 427 | return $e; 428 | } 429 | } else { 430 | // no '+' => filename, completes file 431 | $f->path_to_src = $v; 432 | $files[] = $f; 433 | // start a new one, but persist the baud from previous one 434 | $f = new TibetFile($f->opts->baud); 435 | } 436 | } else if ($state == CLI_STATE_LOAD) { 437 | $i=0; 438 | $e = cli_parse_x32($v, $i); 439 | if (MKT_E_OK != $e) { return $e; } 440 | $f->opts->load = $i; 441 | $state = CLI_STATE_IDLE; 442 | } else if ($state == CLI_STATE_EXEC) { 443 | $i=0; 444 | $e = cli_parse_x32($v, $i); 445 | if (MKT_E_OK != $e) { return $e; } 446 | $f->opts->exec = $i; 447 | $state = CLI_STATE_IDLE; 448 | } else if ($state == CLI_STATE_BAUD) { 449 | $i=0; 450 | $e = cli_parse_baud($v, $i); 451 | if (MKT_E_OK != $e) { return $e; } 452 | $f->opts->baud = $i; 453 | $state = CLI_STATE_IDLE; 454 | } else if ($state == CLI_STATE_NAME) { 455 | $e = check_mos_filename($v); 456 | if (MKT_E_OK != $e) { return $e; } 457 | $f->mos_name = $v; 458 | $state = CLI_STATE_IDLE; 459 | } else if ($state == CLI_STATE_TIBET) { 460 | $tibet_fn = $v; 461 | $state = CLI_STATE_IDLE; 462 | } 463 | } // next arg 464 | 465 | if ( ! $have_tibet_filename ) { 466 | print "\nE: Must specify output TIBET filename with +t.\n"; 467 | return MKT_E_CLI; 468 | } 469 | 470 | return MKT_E_OK; 471 | 472 | } 473 | 474 | 475 | function check_mos_filename (string $v) : int { 476 | if (strlen($v) > 10) { 477 | print "E: overridden MOS filename is too long (max 10 chars): $v\n"; 478 | return MKT_E_CLI; 479 | } else if (strlen($v) == 0) { 480 | print "E: overridden MOS filename is empty: $v\n"; 481 | return MKT_E_CLI; 482 | } 483 | $len = strlen($v); 484 | for ($n=0; $n < $len; $n++) { 485 | if ($v[$n] == "\x00") { 486 | print "E: overridden MOS filename contains null byte\n"; 487 | return MKT_E_CLI; 488 | } 489 | } 490 | return MKT_E_OK; 491 | } 492 | 493 | 494 | function cli_parse_baud (string $v, int &$baud_out) : int { 495 | if ($v == "300") { 496 | $baud_out = 300; 497 | } else if ($v == "1200") { 498 | $baud_out = 1200; 499 | } else { 500 | print "E: Illegal baud: $v\n"; 501 | return MKT_E_CLI; 502 | } 503 | return MKT_E_OK; 504 | } 505 | 506 | 507 | function cli_parse_x32 (string $v, int &$out) : int { 508 | $len = strlen($v); 509 | if ($len == 0) { 510 | print "E: illegal 32-bit hex: $v\n"; 511 | return MKT_E_CLI; 512 | } 513 | // removed because it causes backgrounding on Unix! 514 | //if ($v[0]=="&") { 515 | // $v = substr($v, 1); 516 | //} 517 | if (($v[0]=="0") && (($v[1]=="x") || ($v[1]=="X"))) { 518 | $v = substr($v, 2); 519 | } 520 | $len = strlen($v); 521 | if ($len > 8) { 522 | print "E: illegal 32-bit hex: $v\n"; 523 | return MKT_E_CLI; 524 | } 525 | for ($n=0; $n < $len; $n++) { 526 | if ( ! ctype_xdigit($v[$n]) ) { 527 | print "E: illegal 32-bit hex: $v\n"; 528 | return MKT_E_CLI; 529 | } 530 | } 531 | $i=0; 532 | $e = sscanf($v, "%x", $i); 533 | if (FALSE === $e) { 534 | print "E: illegal 32-bit hex: $v\n"; 535 | return MKT_E_CLI; 536 | } 537 | $out = $i; 538 | return MKT_E_OK; 539 | } 540 | 541 | 542 | function usage (string $argv0) { 543 | print "\n\nUsage:\n\n"; 544 | print " php -f $argv0 +t [opts] [opts] [opts] ...\n\n"; 545 | print "where each per-file [opts] may be:\n\n"; 546 | print " +x specify execute address for next file\n"; 547 | print " +d specify load address for next file\n"; 548 | print " +b <1200|300> specify baud rate for next file\n"; 549 | print " +n override MOS filename for next file\n"; 550 | print " +no-dummy disable prepending &AA byte before next file\n"; 551 | print "\n"; 552 | } 553 | 554 | ?> 555 | -------------------------------------------------------------------------------- /2-build-files/tibetuef-0.8.php: -------------------------------------------------------------------------------- 1 | =7, reduce stupidity 23 | declare (strict_types=1); 24 | 25 | define ("APPNAME", "tibetuef.php"); 26 | define ("TIBETUEF_VERSION", "0.8"); 27 | //define ("TIBET_VERSION_STG", "0.4"); 28 | define ("TIBET_MAJOR_VERSION", "0"); 29 | 30 | define ("TBT_E_OK", 0); 31 | define ("TBT_E_USAGE", 1); 32 | define ("TBT_E_LOAD", 2); 33 | define ("TBT_E_IPFN_MATCHES_OPFN", 3); 34 | define ("TBT_E_PARSE_VERSION", 4); 35 | define ("TBT_E_BAD_VERSION", 5); 36 | define ("TBT_E_PARSE_BAD_LINE", 6); 37 | define ("TBT_E_PARSE_SILENCE", 7); 38 | define ("TBT_E_BAD_SILENCE", 8); 39 | define ("TBT_E_PARSE_LEADER", 9); 40 | define ("TBT_E_BAD_LEADER", 10); 41 | define ("TBT_E_PARSE_DATA", 11); 42 | define ("TBT_E_BAD_PHASE", 12); 43 | define ("TBT_E_PARSE_CYCLES", 13); 44 | define ("TBT_E_BUG", 14); 45 | define ("TBT_E_WRITE_FILE", 15); 46 | define ("TBT_E_PARSE_HINT", 16); 47 | define ("TBT_E_BAD_FRAMING", 17); 48 | define ("TBT_E_PARSE_DUP_VERSION", 18); 49 | define ("TBT_E_BAD_INT", 19); 50 | define ("TBT_E_BAD_FLOAT", 20); 51 | define ("TBT_E_GZIP", 21); 52 | // 0.7: handle void chunks 53 | define ("TBT_E_ZL_CHUNK", 22); 54 | // 0.8: major version mismatch 55 | define ("TBT_E_INCOMPATIBLE", 23); 56 | 57 | define ("STATE_VERSION", 0); 58 | define ("STATE_IDLE", 1); 59 | define ("STATE_CYCLES", 2); 60 | 61 | class Span { 62 | var $linenum; 63 | var $span_ix; 64 | } 65 | 66 | class TimeHint extends Span { 67 | var $timestamp; 68 | } 69 | 70 | class ParsedTibet { 71 | var $version; 72 | var $spans; 73 | function __construct() { 74 | $this->spans = array(); 75 | $this->version = ""; 76 | } 77 | } 78 | 79 | class TibetSilence extends Span { 80 | var $secs; 81 | } 82 | 83 | class TibetLeader extends Span { 84 | var $cycles; 85 | } 86 | 87 | class DataFraming extends Span { 88 | var $framelen; // 7 or 8: FIXME: rename to wordlen 89 | var $parity; // string; "N", "O", "E" 90 | var $stops; // 1 or 2 91 | //var $autodetected; 92 | function to_string() : string { 93 | return "$this->framelen$this->parity$this->stops"; 94 | } 95 | function __construct() { 96 | // defaults 97 | $this->framelen = 8; 98 | $this->parity = "N"; 99 | $this->stops = 1; 100 | } 101 | } 102 | 103 | class BaudRate extends Span { 104 | var $rate; 105 | function to_string() : string { 106 | return "$this->rate"; 107 | } 108 | function __construct() { 109 | // default 110 | $this->rate = 1200; 111 | } 112 | } 113 | 114 | class TibetData extends Span { 115 | var $squawk; 116 | var $cycles; // array 117 | var $bits; 118 | var $framing; // DataFraming 119 | function __construct() { 120 | $this->cycles = array(); // TibetCycles 121 | $this->bits = array(); // also TibetCycles ... 122 | $this->squawk = 0; 123 | } 124 | } 125 | 126 | class TibetCycle extends Span { 127 | var $value; 128 | } 129 | 130 | class DummyByte extends Span { 131 | var $pre_leader_cycs; 132 | var $post_leader_cycs; 133 | var $byte_value; 134 | } 135 | 136 | $e = tbt_main ($_SERVER['argc'], $_SERVER['argv']); 137 | if (TBT_E_USAGE == $e) { usage($_SERVER['argv'][0]); } 138 | return $e; 139 | 140 | die(); 141 | 142 | function tbt_main ($argc, $argv) : int { 143 | 144 | print "\ntibetuef.php ".TIBETUEF_VERSION."\n\n"; 145 | 146 | $insert_timestamps = 0; 147 | 148 | $use_chunk_102 = 0; 149 | //$use_chunk_102b = 0; 150 | $use_chunk_104 = 0; 151 | $use_chunk_114 = 0; 152 | // 0.8: option to use &112 for silence 153 | $use_chunk_112_for_silence = 0; 154 | $have_nz = 0; 155 | $have_no_117 = 0; // 0.6 156 | 157 | if ($argc < 3) { // PHP filename, ipfn, opfn 158 | return TBT_E_USAGE; 159 | } 160 | 161 | $chunk_change_count = 0; 162 | 163 | // -2 for ipfn, opfn 164 | for ($i=1, $dupe=0; $i < ($argc - 2); $i++) { 165 | $a = $argv[$i]; 166 | if ($a[0] != "+") { break; } 167 | //if (CLI_STATE_IDLE == $state) { 168 | //if ($a == "+f") { 169 | // if ($autodetect_framings) { return TBT_E_USAGE; } // dup 170 | // $autodetect_framings = 1; 171 | //} else 172 | if ($a == "+t") { 173 | if ($insert_timestamps) { $dupe = 1; } // dup 174 | $insert_timestamps = 1; 175 | } else if ($a == "+102") { 176 | if ($use_chunk_102) { $dupe = 1; } 177 | $use_chunk_102 = 1; 178 | $chunk_change_count++; 179 | //} else if ($a == "+102b") { 180 | // if ($use_chunk_102b) { $dupe = 1; } 181 | // $use_chunk_102b = 1; 182 | // $chunk_change_count++; 183 | } else if ($a == "+104") { 184 | if ($use_chunk_104) { $dupe = 1; } 185 | $use_chunk_104 = 1; 186 | $chunk_change_count++; 187 | } else if ($a == "+114") { 188 | if ($use_chunk_114) { $dupe = 1; } 189 | $use_chunk_114 = 1; 190 | $chunk_change_count++; 191 | } else if ($a == "+112") { // 0.8 192 | if ($use_chunk_112_for_silence) { $dupe = 1; } 193 | $use_chunk_112_for_silence = 1; 194 | } else if ($a == "+nz") { 195 | if ($have_nz) { $dupe = 1; } 196 | $have_nz = 1; 197 | } else if ($a == "+no-117") { // 0.6 198 | if ($have_no_117) { $dupe = 1; } 199 | $have_no_117 = 1; 200 | } else { 201 | print "E: Unknown option $a\n\n"; 202 | return TBT_E_USAGE; 203 | } 204 | 205 | if ($dupe) { 206 | print "E: Duplicate option $a specified.\n\n"; 207 | return TBT_E_USAGE; 208 | } 209 | 210 | } 211 | 212 | if ($chunk_change_count > 1) { 213 | print "E: Can only supply one of +102, +104 and +114.\n\n"; 214 | return TBT_E_USAGE; 215 | } 216 | 217 | $ipfn = $argv[$argc - 2]; 218 | $opfn = $argv[$argc - 1]; 219 | 220 | print "Input file: $ipfn\n"; 221 | print "Output file: $opfn\n"; 222 | if ($insert_timestamps) { 223 | print "Inserting &120 chunks for /time hints.\n"; 224 | } 225 | 226 | // 0.8 227 | if ($use_chunk_112_for_silence) { 228 | print "Using &112 for silence instead of &116.\n"; 229 | } 230 | 231 | //$chunk_102_interpretation = -1; 232 | 233 | $chunk_to_use_for_data = 0x100; 234 | if ($use_chunk_102) { // || $use_chunk_102b) { 235 | $chunk_to_use_for_data = 0x102; 236 | //$chunk_102_interpretation = $use_chunk_102b; 237 | } else if ($use_chunk_104) { 238 | $chunk_to_use_for_data = 0x104; 239 | } else if ($use_chunk_114) { 240 | $chunk_to_use_for_data = 0x114; 241 | } 242 | if ($chunk_change_count > 0) { 243 | print "Using chunk type &".sprintf("%x", $chunk_to_use_for_data)." for data.\n"; 244 | } 245 | 246 | if ($have_nz) { 247 | print "Will not compress output UEF file.\n"; 248 | } 249 | 250 | //if ($autodetect_framings) { 251 | // print "Autodetecting framings.\n"; 252 | //} else { 253 | // print "Taking framings from TIBET file.\n"; 254 | //} 255 | 256 | print "\n"; 257 | 258 | if ($ipfn == $opfn) { 259 | print "E: Input and output filenames cannot match.\n"; 260 | return TBT_E_IPFN_MATCHES_OPFN; 261 | } 262 | 263 | if (FALSE === ($ip = file_get_contents($ipfn))) { 264 | print "E: Could not load file: $ipfn\n"; 265 | return TBT_E_LOAD; 266 | } 267 | 268 | $len = strlen($ip); 269 | 270 | print "Loaded $len bytes.\n"; 271 | 272 | // try gzdecode 273 | $ip_unz = @gzdecode($ip); 274 | if (FALSE === $ip_unz) { 275 | print "Input TIBET was uncompressed.\n"; 276 | } else { 277 | print "Decompressed input TIBET: ".strlen($ip)." -> ".strlen($ip_unz)." bytes.\n"; 278 | $ip = $ip_unz; 279 | } 280 | 281 | $tbt = new ParsedTibet; 282 | $e = tbt_process ($ip, ($insert_timestamps == 1), $tbt); // tbt populated 283 | if (TBT_E_OK != $e) { return $e; } 284 | 285 | $e = cycles_to_bits ($tbt); // tbt modified 286 | if (TBT_E_OK != $e) { return $e; } 287 | 288 | //if ($autodetect_framings) { 289 | // override framings that may have been read from the TIBET file earlier 290 | //$e = autopopulate_framings($tbt); // tbt modified 291 | //if (TBT_E_OK != $e) { return $e; } 292 | //} else { 293 | //$e = populate_framings_from_tibet ($tbt); // tbt modified 294 | //if (TBT_E_OK != $e) { return $e; } 295 | //} 296 | 297 | // 0.4 298 | $e = spans_fix_up_long_leader($tbt); 299 | if (TBT_E_OK != $e) { return $e; } 300 | 301 | // 0.4 302 | $e = spans_detect_and_convert_dummy_bits($tbt); 303 | if (TBT_E_OK != $e) { return $e; } 304 | 305 | $uef = ""; 306 | $msg = ""; 307 | $e = build_uef ($tbt, 308 | $chunk_to_use_for_data, 309 | $use_chunk_112_for_silence, // 0.8 310 | //$chunk_102_interpretation, 311 | $have_no_117, // 0.6 312 | $uef, 313 | $msg); 314 | if (TBT_E_OK != $e) { return $e; } 315 | print "\nChunks: (type-len)\n"; 316 | print_chunk_messages($msg); 317 | print "\n"; 318 | 319 | if ( ! $have_nz ) { 320 | $uef_z = gzencode($uef, 9, FORCE_GZIP); 321 | if (FALSE === $uef_z) { 322 | print "E: Could not compress output UEF.\n"; 323 | return TBT_E_GZIP; 324 | } 325 | print "Compressed output UEF: ".strlen($uef)." -> ".strlen($uef_z)." bytes.\n"; 326 | $uef = $uef_z; 327 | } 328 | 329 | if (FALSE === file_put_contents($opfn, $uef)) { 330 | print "E: Could not write file: $opfn\n"; 331 | return TBT_E_WRITE_FILE; 332 | } 333 | 334 | return TBT_E_OK; 335 | 336 | } 337 | 338 | 339 | // 0.4 340 | function spans_fix_up_long_leader (ParsedTibet &$tbt) : int { 341 | $spans_new = array(); 342 | foreach ($tbt->spans as $k=>$span) { 343 | if ((get_class($span) != "TibetLeader") || ($span->cycles <= 0xffff)) { 344 | $spans_new[] = $span; 345 | continue; 346 | } 347 | for ($rem = $span->cycles; $rem > 0; $rem -= 0xffff) { 348 | $num_cycs = $rem; 349 | if ($num_cycs > 0xffff) { 350 | $num_cycs = 0xffff; 351 | } 352 | $new_span = new TibetLeader; 353 | $new_span->linenum = $span->linenum; 354 | $new_span->span_ix = $span->span_ix; // preserve this 355 | $new_span->cycles = $num_cycs; 356 | $spans_new[] = $new_span; 357 | //~ print "M: L$span->linenum, span $span->span_ix: Leader: $num_cycs cycs\n"; 358 | } 359 | } 360 | $tbt->spans = $spans_new; 361 | //foreach ($tbt->spans as $k=>$v) { if ("TibetLeader"==get_class($v)) { print "L: $v->cycles\n"; } } 362 | return TBT_E_OK; 363 | } 364 | 365 | 366 | function print_chunk_messages ($msg) { 367 | print wordwrap($msg); 368 | } 369 | 370 | 371 | function cycles_to_bits (ParsedTibet &$tbt) : int { 372 | 373 | $current_baud = 1200; 374 | 375 | foreach ($tbt->spans as $sn => $span) { 376 | 377 | if (gettype($span) != "object") { 378 | print "B: cycles_to_bits: Bad span type: \"".gettype($span)."\"\n"; 379 | return TBT_E_BUG; 380 | } 381 | 382 | $type = get_class($span); 383 | 384 | if ("BaudRate" == $type) { 385 | print "span #$span->span_ix: Baud rate change: $current_baud -> $span->rate\n"; 386 | $current_baud = $span->rate; 387 | } 388 | 389 | if ("TibetData" != $type) { 390 | continue; 391 | } 392 | 393 | //print count($tbd->cycles)." cycles in span\n"; 394 | 395 | $span->bits = array(); 396 | $i=0; 397 | 398 | //print_r($span); die(); 399 | 400 | $bitlen = 2; 401 | if ($current_baud == 300) { 402 | $bitlen = 8; 403 | } 404 | 405 | $num_atoms = count($span->cycles); 406 | 407 | //for ($i=0; $i < (count($span->cycles) - 1); $i+=2) { 408 | for ($i=0; $i < ($num_atoms - ($bitlen - 1)); $i += $bitlen) { 409 | //$atoms = array(); 410 | $ln = $span->cycles[$i]->linenum; 411 | $zeros = 0; 412 | $ones = 0; 413 | for ($j=0; ($j < $bitlen) && (($i + $j) < $num_atoms); $j++) { 414 | //$atoms[$j] = $span->cycles[$i + $j]; 415 | if ($span->cycles[$i + $j]->value) { 416 | $ones++; 417 | } else { 418 | $zeros++; 419 | } 420 | } 421 | if ($j != $bitlen) { 422 | print "W: cycles_to_bits: line $ln, span #$span->span_ix: Partial bit\n"; 423 | $atom = $span->cycles[$i]; // partial bit: use the first atom's value 424 | } else { 425 | /* 426 | for ($j=0; $j < $bitlen; $j++) { 427 | if ($span->cycles[$i + $j]->value) { 428 | $ones++; 429 | } else { 430 | $zeros++; 431 | } 432 | } 433 | */ 434 | if (($ones == $bitlen) || ($zeros == $bitlen)) { 435 | // unanimous 436 | $atom = $span->cycles[$i]; 437 | } else { 438 | // bad bit 439 | print "W: cycles_to_bits: line $ln, span #$span->span_ix: Fuzzy bit\n"; 440 | // skip one atom and resynchronise 441 | $i -= ($bitlen - 1); 442 | continue; 443 | } 444 | 445 | // this is the old code that allowed voting on 446 | // 300 baud bits; it now violates the TIBET 447 | // specification on decode, so it's been replaced 448 | // with the simple skip-one-atom-and-resync logic from the spec 449 | /* 450 | } else { 451 | print "W: cycles_to_bits: line $ln, span #$span->span_ix: Fuzzy bit\n"; 452 | if ($ones > $zeros) { 453 | // find first one 454 | for ($j=0; $j < $bitlen; $j++) { 455 | if ($span->cycles[$i + $j]->value) { 456 | $atom = $span->cycles[$i]; 457 | } 458 | } 459 | } else if ($ones < $zeros) { 460 | // find first zero 461 | for ($j=0; $j < $bitlen; $j++) { 462 | if ( ! $span->cycles[$i + $j]->value ) { 463 | $atom = $span->cycles[$i]; 464 | } 465 | } 466 | } else { 467 | // it's a tie, just use first atom 468 | $atom = $span->cycles[$i]; 469 | } 470 | } 471 | */ 472 | 473 | } 474 | 475 | // at this point we should have an atom 476 | //$a = $span->cycles[$i]->value; 477 | //$b = $span->cycles[$i+1]->value; 478 | 479 | /* 480 | if ($a != $b) { 481 | print "W: cycles_to_bits: L$ln, span #$span->span_ix: Cycle pair mismatch\n"; 482 | // skip one and resynchronise 483 | $i--; 484 | continue; 485 | } 486 | $span->bits[] = $span->cycles[$i]; 487 | */ 488 | 489 | $span->bits[] = $atom; 490 | 491 | } 492 | 493 | //print count($bits)." bits in span\n"; 494 | 495 | } // next span 496 | 497 | return TBT_E_OK; 498 | 499 | } 500 | 501 | 502 | function populate_framings_from_tibet (ParsedTibet &$tbt) : int { 503 | // use "framing ..." lines to set framings on subsequent data spans 504 | $framing = new DataFraming; // 8N1 default 505 | foreach ($tbt->spans as $sn => $span) { 506 | if (gettype($span) != "object") { 507 | print "B: populate_framings_from_tibet: Bad span type: \"".gettype($span)."\"\n"; 508 | return TBT_E_BUG; 509 | } 510 | $type = get_class($span); 511 | if ("TibetData" == $type) { 512 | // note that this means the framing field on the data span 513 | // is itself a span, with a span_ix and a line number, which 514 | // will be the originating span where this framing was programmed. 515 | $tbt->spans[$sn]->framing = $framing; 516 | } else if ("DataFraming" == $type) { 517 | $framing = $span; 518 | } 519 | } 520 | return TBT_E_OK; 521 | } 522 | 523 | /* 524 | function autopopulate_framings (ParsedTibet &$tbt) : int { 525 | 526 | // default to 8N1 framing, since BASIC always needs this to load 527 | $prev_framing = new DataFraming; 528 | 529 | foreach ($tbt->spans as $sn => $span) { 530 | 531 | if (gettype($span) != "object") { 532 | print "B: autopopulate_framings: Bad span type: \"".gettype($span)."\"\n"; 533 | return TBT_E_BUG; 534 | } 535 | 536 | $type = get_class($span); 537 | 538 | if ("TibetData" != $type) { 539 | continue; 540 | } 541 | 542 | if ($span->squawk) { 543 | continue; 544 | } 545 | 546 | // need to identify framing format 547 | 548 | // "For the BBC/Electron, the following formats may be encountered: 549 | // 7E1, 7E2, 7O1, 7O2, 8E1, 8N2, 8O1. 550 | // Format 8N1 would produce the same output as chunk &0100." 551 | 552 | // 7E1/7O1: 0xxxxxxxP1 } 553 | // 8N1: 0xxxxxxxx1 } "short" 554 | // ^ short_stops_score counts these 555 | 556 | // 7E2/7O2: 0xxxxxxxP11 } "long" 557 | // 8N2: 0xxxxxxxx11 } 558 | // 8E1/8O1: 0xxxxxxxxP1 } 559 | // ^ long_stops_score counts these 560 | // ^ double_stop_score counts these 561 | 562 | $bits = $span->bits; 563 | $nb = count($bits); 564 | 565 | if ($nb < 1000) { 566 | print "W: L$span->linenum, span #$span->span_ix, $nb bits: too short to derive framing, using previous ".$prev_framing->to_string()."\n"; 567 | $span->framing = $prev_framing; 568 | continue; 569 | } 570 | 571 | // find first start bit (should be right at the start) 572 | for ($i=0; $i < $nb; $i++) { 573 | $b = $bits[$i]; 574 | if (0 == $b->value) { 575 | break; 576 | } 577 | } 578 | 579 | if ($i != 0) { 580 | print "W: autopopulate_framings: L$b->linenum, span #$b->span_ix, $nb bits: Late start bit (i=$i)\n"; 581 | } 582 | 583 | $i++; // first data bit 584 | 585 | $short_stops_score = 0; 586 | $short_odd_score = 0; 587 | $short_even_score = 0; 588 | 589 | // assume frame len of 10 590 | for ($j=$i; $j < ($nb - 9); $j += 10) { 591 | $short_stops_score += $bits[$j+8]->value; 592 | $ones = 0; 593 | // check all data bits, plus the parity bit (8 bits); count the ones 594 | for ($k=$j; $k < $j+8; $k++) { 595 | $ones += $bits[$k]->value; 596 | } 597 | if ($ones & 1) { 598 | $short_odd_score++; 599 | } else { 600 | $short_even_score++; 601 | } 602 | } 603 | 604 | $long_stops_score = 0; 605 | $double_stop_score = 0; 606 | $long_7bit_odd_score = 0; 607 | $long_7bit_even_score = 0; 608 | $long_8bit_odd_score = 0; 609 | $long_8bit_even_score = 0; 610 | 611 | // now assume frame len of 11 612 | for ($j=$i; $j < ($nb - 10); $j+=11) { 613 | $long_stops_score += $bits[$j+9]->value; 614 | $double_stop_score += $bits[$j+8]->value; 615 | $ones = 0; 616 | // check all data bits, plus the potential parity bit (8 bits); count the ones 617 | for ($k=$j+1; $k < $j+9; $k++) { 618 | $ones += $bits[$k]->value; 619 | } 620 | if ($ones & 1) { 621 | $long_7bit_odd_score++; 622 | } else { 623 | $long_7bit_even_score++; 624 | } 625 | // do it again but include an extra bit, another potential parity bit 626 | $ones += $bits[$j+9]->value; 627 | if ($ones & 1) { 628 | $long_8bit_odd_score++; 629 | } else { 630 | $long_8bit_even_score++; 631 | } 632 | } 633 | 634 | //print "short_stops_score = $short_stops_score\n"; 635 | //print "short_even_score = $short_even_score\n"; 636 | //print "short_odd_score = $short_odd_score\n"; 637 | 638 | //print "long_stops_score = $long_stops_score\n"; 639 | //print "double_stop_score = $double_stop_score\n"; 640 | 641 | // if short_stops_score > long_stops_score 642 | // 7E1, 7O1, 8N1 643 | // else 644 | // if double_stop_score ~= long_stops_score 645 | // 7E2, 7O2, 8N2 646 | // else 647 | // 8E1, 8O1 648 | 649 | $framelen = 0; 650 | $parity = ""; 651 | $stops = 0; 652 | 653 | // $epsilon is "approximately zero" 654 | $epsilon = $nb / 320; 655 | 656 | if ($short_stops_score > ($long_stops_score * 1.1)) { 657 | // 7E1/7O1: 0xxxxxxxP1 } 658 | // 8N1: 0xxxxxxxx1 } "short" 659 | // for a perfect signal, 660 | // 7E1: short_odd_score = 0, short_even_score = lots 661 | // 7O1: short_odd_score = lots, short_even_score = 0 662 | // 8N1: short_odd_score = some, short_even_score = some 663 | $stops = 1; 664 | if ($short_odd_score < $epsilon) { 665 | // 7E1 666 | $framelen = 7; 667 | $parity = "E"; 668 | } else if ($short_even_score < $epsilon) { 669 | // 7O1 670 | $framelen = 7; 671 | $parity = "O"; 672 | } else { 673 | // 8N1 674 | $framelen = 8; 675 | $parity = "N"; 676 | } 677 | } else if (abs($double_stop_score - $long_stops_score) < $epsilon) { 678 | // 7E2/7O2: 0xxxxxxxP11 } "long" 679 | // 8N2: 0xxxxxxxx11 } 680 | $stops = 2; 681 | if ($long_7bit_odd_score < $epsilon) { 682 | // 7E2 683 | $framelen = 7; 684 | $parity = "E"; 685 | } else if ($long_7bit_even_score < $epsilon) { 686 | // 7O2 687 | $framelen = 7; 688 | $parity = "O"; 689 | } else { 690 | // 8N2 691 | $framelen = 8; 692 | $parity = "N"; 693 | } 694 | } else { 695 | // 8E1/8O1: 0xxxxxxxxP1 696 | $stops = 1; 697 | $framelen = 8; 698 | if ($long_8bit_odd_score < $epsilon) { 699 | // 8E1 700 | $parity = "E"; 701 | } else { 702 | // 8O1 703 | $parity = "O"; 704 | } 705 | } 706 | 707 | $framing = new DataFraming; 708 | $framing->stops = $stops; 709 | $framing->framelen = $framelen; 710 | $framing->parity = $parity; 711 | //$framing->autodetected = TRUE; 712 | 713 | $tbt->spans[$sn]->framing = $framing; 714 | $prev_framing = $framing; 715 | 716 | print "Framing: L$b->linenum, span #$b->span_ix, $nb bits: ".$span->framing->to_string()."\n"; 717 | 718 | } 719 | 720 | return TBT_E_OK; 721 | 722 | } 723 | */ 724 | 725 | 726 | function spans_detect_and_convert_dummy_bits (ParsedTibet &$tbt) : int { 727 | 728 | $dummies = array(); 729 | 730 | // for a dummy bit span, we need , 731 | // so we start at 1 and end at N-2 732 | for ($i=1; $i < (count($tbt->spans) - 1); $i++) { 733 | $prev = $tbt->spans[$i-1]; 734 | $cur = $tbt->spans[$i]; 735 | $next = $tbt->spans[$i+1]; 736 | if ( (get_class($prev) != "TibetLeader") 737 | || (get_class($cur) != "TibetData") 738 | || (get_class($next) != "TibetLeader")) { 739 | continue; // nope 740 | } 741 | $num_bits = count($cur->bits); 742 | if ($num_bits < 10) { 743 | continue; // nope 744 | } 745 | // candidate span may have some leader tone following the 746 | // data burst, so we need to ignore that. 747 | for ($end=($num_bits - 1); ($end>=0) && ($cur->bits[$end]->value == 1); $end--) { } 748 | 749 | // $end should now point to the last zero in the chunk 750 | if ($end != 7) { 751 | continue; // nope 752 | } 753 | 754 | // OK, expect &AA 755 | $bits = $cur->bits; 756 | 757 | if ( !$bits[0]->value 758 | && !$bits[1]->value 759 | && $bits[2]->value 760 | && !$bits[3]->value 761 | && $bits[4]->value 762 | && !$bits[5]->value 763 | && $bits[6]->value 764 | && !$bits[7]->value) { 765 | print "M: line $cur->linenum: Dummy byte detected.\n"; 766 | $dummies[] = $i; 767 | } 768 | 769 | } 770 | 771 | // rather than mess about trying to delete elements from 772 | // the spans list, which would mess up the numbering in 773 | // dummies, we'll just set them to NULL. 774 | foreach ($dummies as $k=>$span_ix) { 775 | $tdb = new DummyByte; 776 | 777 | $tdb->linenum = $tbt->spans[$span_ix]->linenum; 778 | $tdb->pre_leader_cycs = $tbt->spans[$span_ix-1]->cycles; 779 | $tdb->post_leader_cycs = $tbt->spans[$span_ix+1]->cycles; 780 | $tdb->value = 0xAA; 781 | 782 | $tbt->spans[$span_ix] = $tdb; 783 | 784 | // delete the formerly leader spans: 785 | $tbt->spans[$span_ix - 1] = NULL; 786 | $tbt->spans[$span_ix + 1] = NULL; 787 | } 788 | 789 | $spans_new = array(); 790 | for ($i=0; $i < count($tbt->spans); $i++) { 791 | // DON'T rewrite the span indices. We want these to reflect 792 | // the original lineup in the TIBET file, so 793 | // will end up with a pair of discontinuities: ... N-3, N-2, N, N+2, N+3 ... 794 | if (isset($tbt->spans[$i])) { // now skip the NULLs 795 | $spans_new[] = $tbt->spans[$i]; 796 | } 797 | } 798 | 799 | //print "orig: ".count($tbt->spans).", final: ".count($spans_new)."\n"; 800 | 801 | $tbt->spans = $spans_new; // replace orig array 802 | 803 | return TBT_E_OK; 804 | } 805 | 806 | 807 | function build_uef (ParsedTibet $tbt, 808 | int $chunk_to_use_for_data, 809 | int $use_chunk_112_for_silence, 810 | int $omit_chunk_117, // 0.6 811 | string &$uef, 812 | string &$msg) : int { 813 | 814 | // header, versions 815 | $uef = "UEF File!\x00\x0a\x00"; 816 | 817 | // origin chunk 818 | $origin = "Created with ".APPNAME." v".TIBETUEF_VERSION."\0"; 819 | $orglen = strlen($origin); 820 | $uef .= "\x00\x00".le32($orglen).$origin; 821 | 822 | $limit = count($tbt->spans); 823 | 824 | $framing = new DataFraming; 825 | 826 | $cycs_to_steal = 0; 827 | 828 | // 0.5 829 | $cur_active_baud = 1200; 830 | 831 | foreach ($tbt->spans as $sn => $span) { 832 | 833 | if (gettype($span) != "object") { 834 | print "B: Bad span type: \"".gettype($span)."\"\n"; 835 | return TBT_E_BUG; 836 | } 837 | $type = get_class($span); 838 | $chunkbuf=""; 839 | $chunktype = 0; 840 | 841 | if ("TibetLeader" == $type) { 842 | // we (maybe) stole some 1-cycles from this leader to pad the 843 | // preceding data cycle 844 | $span->cycles -= $cycs_to_steal; 845 | } 846 | 847 | $cycs_to_steal = 0; 848 | 849 | if ("TibetLeader" == $type) { 850 | $chunktype = 0x110; 851 | // may need multiple chunks 852 | // 0.4 -- not any more -- we handle this earlier now, 853 | // in a separate span-processing pass, 854 | // because there's a potential problem with chunk &111 855 | // which amalgamates leader spans 856 | /* 857 | for ($rem = $span->cycles; $rem > 0; $rem -= 0xffff) { 858 | $num_cycs = $rem; 859 | if ($num_cycs > 0xffff) { 860 | $num_cycs = 0xffff; 861 | } 862 | //~ print "M: L$span->linenum, span $span->span_ix: Leader: $num_cycs cycs\n"; 863 | $e = build_uef_leader ($num_cycs, $chunkbuf); 864 | if (TBT_E_OK != $e) { return $e; } 865 | $uef .= wrap_chunk ($chunktype, $chunkbuf, $msg); 866 | } 867 | */ 868 | $e = build_uef_leader ($span->cycles, $chunkbuf); 869 | if (TBT_E_OK != $e) { return $e; } 870 | $uef .= wrap_chunk ($chunktype, $chunkbuf, $msg); 871 | } else if ("TibetSilence" == $type) { 872 | // 0.8 873 | if ($use_chunk_112_for_silence) { 874 | // complicated: multiple chunks may be needed 875 | $e = build_uef_silence_112($span, $uef, $msg); 876 | } else { 877 | $e = build_uef_silence_116($span, $chunkbuf); 878 | $uef .= wrap_chunk (0x116, $chunkbuf, $msg); 879 | } 880 | if (TBT_E_OK != $e) { return $e; } 881 | } else if ("TibetData" == $type) { 882 | if (count($span->cycles) > 0) { 883 | $span->framing = $framing; // assign current framing value 884 | $trailing_1_cycles = 0; 885 | if ($span->squawk) { 886 | $chunktype = 0x114; 887 | $e = build_uef_squawk($span, $chunkbuf, $trailing_1_cycles); 888 | } else { 889 | $e = build_uef_data ($span, 890 | $cur_active_baud, // 0.5 891 | $chunkbuf, 892 | $chunktype, 893 | $cycs_to_steal, // 0.7: can be -ve now, => same as trailing_1_cycles 894 | $chunk_to_use_for_data); 895 | } 896 | // 0.7: error handling for chunk &114: 897 | if (TBT_E_OK == $e) { 898 | // do not create chunk on error, or void-chunk condition 899 | $uef .= wrap_chunk ($chunktype, $chunkbuf, $msg); 900 | } 901 | // trap & nullify void-chunk error: 902 | if (TBT_E_ZL_CHUNK == $e) { $e = TBT_E_OK; } 903 | // abort on any other error: 904 | if (TBT_E_OK != $e) { return $e; } 905 | 906 | // assign unused trailing 1-cycles to subsequent leader cycle 907 | // FIXME: consider doing this in prior separate pass, in separate function 908 | 909 | // 0.7 910 | if ($cycs_to_steal < 0) { 911 | // negative cycs_to_steal is same as trailing_1_cycles 912 | $trailing_1_cycles = -$cycs_to_steal; 913 | $cycs_to_steal = 0; 914 | } 915 | 916 | //if ($span->squawk && ($sn+1 < $limit) && (get_class($tbt->spans[$sn+1]) == "TibetLeader")) { 917 | if (($trailing_1_cycles > 0) && ($sn+1 < $limit) && (get_class($tbt->spans[$sn+1]) == "TibetLeader")) { 918 | print "M: Assign $trailing_1_cycles trailing 1-cycles to subsequent leader\n"; 919 | $tbt->spans[$sn+1]->cycles += $trailing_1_cycles; 920 | } 921 | 922 | } else { 923 | print "W: skipped zero-length data section\n"; 924 | } 925 | } else if ("DataFraming" == $type) { 926 | $framing = $span; 927 | } else if ("BaudRate" == $type) { 928 | if ($span->rate != $cur_active_baud) { 929 | // 0.6: 930 | if ( $omit_chunk_117 ) { 931 | print "W: Omitting chunk &117 (baud rate) as instructed.\n"; 932 | } else { 933 | $baudrate = $span; 934 | $baud_uef = ""; 935 | build_uef_baud($baudrate->rate, $baud_uef); 936 | $uef .= wrap_chunk (0x117, $baud_uef, $msg); 937 | // 0.5: 938 | $cur_active_baud = $baudrate->rate; 939 | } 940 | } 941 | } else if ("TimeHint" == $type) { 942 | // paste this text into the UEF 943 | $chunkbuf = sprintf("time: %f seconds\0", $span->timestamp); 944 | $uef .= wrap_chunk (0x120, $chunkbuf, $msg); 945 | } else if ("DummyByte" == $type) { 946 | $dummy_byte=""; 947 | build_dummy_byte($span, $dummy_byte); // dummy_byte populated 948 | $uef .= wrap_chunk (0x111, $dummy_byte, $msg); 949 | } else { 950 | print "B: Bad span class: \"$type\"\n"; 951 | return TBT_E_BUG; 952 | } 953 | } 954 | return TBT_E_OK; 955 | } 956 | 957 | function build_dummy_byte (DummyByte $db, string &$chunkbuf) : int { 958 | $chunkbuf = le16($db->pre_leader_cycs).le16($db->post_leader_cycs); 959 | return TBT_E_OK; 960 | } 961 | 962 | function build_uef_baud (int $baud, string &$chunkbuf) : int { 963 | $chunkbuf = le16($baud); 964 | return TBT_E_OK; 965 | } 966 | 967 | function build_uef_leader (int $num_cycs, string &$chunkbuf) : int { 968 | $chunkbuf = le16($num_cycs); 969 | return TBT_E_OK; 970 | } 971 | 972 | function build_uef_silence_116 (TibetSilence $tbs, string &$chunkbuf) : int { 973 | $chunkbuf = uef_float($tbs->secs); 974 | return TBT_E_OK; 975 | } 976 | 977 | // 0.8 978 | // more complicated than the others, as it may generate multiple chunks 979 | // $uef is passed in 980 | function build_uef_silence_112 (TibetSilence $tbs, string &$uef, string &$msg) : int { 981 | // chunk 112 expresses silence as equivalent 2403.8 Hz cycles 982 | // if gap is large, multiple chunks will be needed ... 983 | // two byte field, so maximum gap in a single chunk is (65535 / 2403.8) = 27.263 seconds 984 | // not very long! 985 | $num_cycs = (int) round($tbs->secs * (2000000.0 / 832.0)); // correct frequency value 986 | if ($num_cycs == 0) { 987 | print "W: silence as &112: tiny gap (".$tbs->secs." s); round up to 1/2400\n"; 988 | $num_cycs = 1; 989 | } 990 | // multiple chunks needed 991 | for ( $rem = $num_cycs; $rem > 0; $rem -= 65535 ) { 992 | $chunkbuf = le16(($rem > 65535) ? 65535 : $rem); 993 | $uef .= wrap_chunk(0x112, $chunkbuf, $msg); 994 | } 995 | return TBT_E_OK; 996 | } 997 | 998 | function build_uef_squawk (TibetData $tbd, 999 | string &$chunkbuf, 1000 | int &$trailing_unused_1_cycles) : int { 1001 | 1002 | $num_cycles = 0.0; 1003 | $cycs_buf = ""; 1004 | $bitcount=0; 1005 | $b=0; 1006 | $cycle_count=0; 1007 | $zero_half_cycle_count = 0; 1008 | 1009 | // determine length of trailing leader section 1010 | 1011 | // e.g., count=2, one trailing 2400 1012 | // 01 1013 | // ^ count-1 1014 | // ^ limit 1015 | 1016 | //print "cyc_count = ".count($tbd->cycles)."\n"; 1017 | 1018 | // count down from num cycles to 0, expecting cycle value of 1 1019 | // (i.e. trailing 1-cycles which should be leader instead); 1020 | // stop when first 0-cycle is found; this becomes the limit 1021 | for ($limit = count($tbd->cycles) - 1; $limit >= 0; $limit--) { 1022 | if (0 == $tbd->cycles[$limit]->value) { 1023 | break; 1024 | } 1025 | } 1026 | 1027 | // and what remains at the end is the trailing unused 1-cycles 1028 | // to be returned to the caller 1029 | $trailing_unused_1_cycles = count($tbd->cycles) - (1 + $limit); 1030 | 1031 | // 0.7: prevent void squawk chunks from happening 1032 | if ($limit == -1) { 1033 | print "W: squawk: all cycles are 1-cycles! no actual squawk data! ". 1034 | "do not create chunk &114\n"; 1035 | return TBT_E_ZL_CHUNK; 1036 | } 1037 | 1038 | //print "M: L#$tbd->linenum, span $tbd->span_ix: Squawk: "; 1039 | for ($i=0; $i <= $limit; $i++) { 1040 | if (0 == $tbd->cycles[$i]->value) { 1041 | $zero_half_cycle_count++; // number of '-' chars in TIBET 1042 | } 1043 | // a cycle is either two '-'s or one '.' 1044 | if ((2 == $zero_half_cycle_count) || (1 == $tbd->cycles[$i]->value)) { 1045 | // cycle available 1046 | //print $tbd->cycles[$i]->value ? "S" : "L"; 1047 | $zero_half_cycle_count = 0; // reset 1048 | $b <<= 1; 1049 | $b |= $tbd->cycles[$i]->value; 1050 | $bitcount++; 1051 | $cycle_count++; 1052 | if (8 == $bitcount) { 1053 | // bit complete 1054 | $cycs_buf .= chr($b); 1055 | $b = 0; 1056 | $bitcount = 0; 1057 | } 1058 | } 1059 | } 1060 | //print "\n"; 1061 | if ($zero_half_cycle_count != 0) { 1062 | $ln = $tbd->cycles[$i-1]->linenum; 1063 | $span_ix = $tbd->cycles[$i-1]->span_ix; 1064 | print "W: line $ln, span $span_ix: ". 1065 | "finished squawk with zero_half_cycle_count=$zero_half_cycle_count (want 0)\n"; 1066 | } 1067 | if ($bitcount != 0) { 1068 | // there are bits remaining; add them to the buffer 1069 | $b <<= (8 - $bitcount); 1070 | $cycs_buf .= chr($b); 1071 | } 1072 | 1073 | //print "build_uef_squawk: num_cycles = $num_cycles\n"; 1074 | //for ($i=0; $i < strlen($cycs_buf); $i++) { 1075 | // printf ("%02x ", ord($cycs_buf[$i])); 1076 | //} 1077 | //print "\n"; 1078 | 1079 | // fix in v0.4: cycle count field incorrectly contained byte count 1080 | $chunkbuf = le24($cycle_count)."WW".$cycs_buf; 1081 | 1082 | return TBT_E_OK; 1083 | 1084 | } 1085 | 1086 | 1087 | function build_uef_data (TibetData $tbd, 1088 | int $baud, // 0.5: baud, for chunk &114 1089 | string &$chunkbuf, 1090 | int &$chunktype_used, 1091 | int &$cycs_to_steal, 1092 | int $chunk_to_use_for_data) { 1093 | //int $chunk_102_interpretation) : int { 1094 | 1095 | $cycs_to_steal = 0; 1096 | $e = TBT_E_OK; 1097 | 1098 | //~ print "build_uef_data: framing is ".$tbd->framing->to_string()."\n"; 1099 | 1100 | // If chunk &100 is requested but framing is incompatible, 1101 | // use chunk &104 instead. 1102 | if ($tbd->framing->to_string() != "8N1") { 1103 | /* cannot use chunk &100 */ 1104 | if (0x100 == $chunk_to_use_for_data) { 1105 | $chunk_to_use_for_data = 0x104; 1106 | } 1107 | } 1108 | 1109 | // 0.7: sanity check for zero-length chunk errors 1110 | $chunkbuf_local = ""; 1111 | 1112 | // Otherwise just use whatever chunk type was requested. 1113 | 1114 | if (0x100 == $chunk_to_use_for_data) { 1115 | $e = build_uef_data_100($tbd, $chunkbuf_local, $cycs_to_steal); 1116 | } else if (0x102 == $chunk_to_use_for_data) { 1117 | $e = build_uef_data_102($tbd, $chunkbuf_local, $cycs_to_steal); //, $chunk_102_interpretation); 1118 | } else if (0x104 == $chunk_to_use_for_data) { 1119 | $e = build_uef_data_104($tbd, $chunkbuf_local, $cycs_to_steal); 1120 | } else if (0x114 == $chunk_to_use_for_data) { 1121 | $e = build_uef_data_114($tbd, $baud, $chunkbuf_local, $cycs_to_steal); // 0.5: baud 1122 | } 1123 | 1124 | // 0.7: suppress zero-length generated chunk, it's probably all 1-bits 1125 | // (not sure why this happens, something to do with concatenating TIBETs) 1126 | if (strlen($chunkbuf_local) == 0) { 1127 | printf("W: Zero-length data chunk, type &%x\n", $chunk_to_use_for_data); 1128 | //print "count(bits) = ".count($tbd->bits).", cycs_to_steal = $cycs_to_steal\n"; 1129 | //print_r($tbd->bits); 1130 | $e = TBT_E_ZL_CHUNK; 1131 | // this "zero-length chunk" problem happens when 1132 | // the entire chunk is 1-cycles. 1133 | // So, we have the *opposite* of a "cycs to steal" 1134 | // situation here; this is a "cycs to donate" situation. 1135 | if (0 != $cycs_to_steal) { 1136 | print "E: Impossible? Zero-length chunk, but nonzero cycs to steal from subsequent leader?!\n"; 1137 | return TBT_E_BUG; 1138 | } 1139 | // OK, so actually we probably have some 1-cycles to donate. 1140 | // Sanity check that all bits are 1s. (Still don't know why this happens.) 1141 | for ($i=0; $i < count($tbd->bits); $i++) { 1142 | if ($tbd->bits[$i]->value != 1) { 1143 | print "E: Checking empty chunk data: should be all 1s, but found a 0 in here!\n"; 1144 | return TBT_E_BUG; 1145 | } 1146 | } 1147 | $cycs_to_steal = -count($tbd->bits) * 2; 1148 | print "W: donating ".(count($tbd->bits) * 2). 1149 | " 1-cycles from empty chunk (chunk was all 1s somehow)\n"; 1150 | } else { 1151 | $chunkbuf .= $chunkbuf_local; 1152 | } 1153 | 1154 | $chunktype_used = $chunk_to_use_for_data; // out 1155 | 1156 | return $e; 1157 | 1158 | } 1159 | 1160 | 1161 | function build_uef_data_114 (TibetData $tbd, 1162 | int $baud, // 0.5 1163 | string &$chunkbuf, 1164 | int &$cycs_to_steal) : int { 1165 | $bits = $tbd->bits; 1166 | $nb = count($bits); 1167 | $chunkbuf=" WW"; 1168 | 1169 | for ($i=0, $total_cycs=0, $bitpos=0, $v=0, $cycs_to_add=0; 1170 | $i < $nb; 1171 | $i++) { 1172 | 1173 | $b = $bits[$i]; 1174 | 1175 | $cycs_to_add = ($b->value == 1) ? 2 : 1; 1176 | 1177 | // 0.5 1178 | if ($baud == 300) { 1179 | $cycs_to_add *= 4; 1180 | } 1181 | 1182 | for ($n=0; $n < $cycs_to_add; $n++) { 1183 | $v <<= 1; 1184 | $v |= ($b->value == 1) ? 1 : 0; 1185 | $bitpos++; 1186 | $total_cycs++; 1187 | if ($bitpos == 8) { 1188 | // end of UEF byte; 8 cycles inserted 1189 | $chunkbuf .= chr($v); 1190 | $bitpos = 0; 1191 | } 1192 | } 1193 | 1194 | } 1195 | 1196 | if ($bitpos != 0) { 1197 | // rem bits 1198 | $v <<= (8 - $bitpos); 1199 | $chunkbuf .= chr($v); 1200 | } 1201 | 1202 | // rewrite total cycs in chunk header 1203 | $chunkbuf[0] = chr($total_cycs & 0xff); 1204 | $chunkbuf[1] = chr(($total_cycs >> 8 ) & 0xff); 1205 | $chunkbuf[2] = chr(($total_cycs >> 16) & 0xff); 1206 | 1207 | return TBT_E_OK; 1208 | 1209 | } 1210 | 1211 | function build_uef_data_100 (TibetData $tbd, 1212 | string &$chunkbuf, 1213 | int &$cycs_to_steal) : int { // cycs will be double this 1214 | 1215 | $bits = $tbd->bits; 1216 | $nb = count($bits); 1217 | 1218 | $s = ""; 1219 | $bitnum = -1; // expecting start bit 1220 | $late_start_bit = 0; 1221 | $bits_to_steal = 0; 1222 | 1223 | for ($i=0; $i < $nb; $i++) { 1224 | $b = $bits[$i]; 1225 | if ($bitnum == -1) { 1226 | if ($b->value != 0) { 1227 | if ( ! $late_start_bit ) { 1228 | print "W: build_uef_data_100: line $b->linenum, span #$b->span_ix, bit $i: Late start bit\n"; 1229 | } 1230 | $late_start_bit = 1; // latch, to avoid error message spam 1231 | continue; 1232 | } 1233 | $late_start_bit = 0; // unlatch 1234 | $bitnum++; // bitnum = 0 1235 | $v = 0; // byte value 1236 | } else if (($bitnum >= 0) && ($bitnum <= 7)) { 1237 | $v >>= 1; 1238 | $v |= (($bits[$i]->value) << 7) & 0x80; 1239 | $bitnum++; 1240 | } else { // bitnum = 8 1241 | // byte finished 1242 | $s .= chr($v); 1243 | // expect stop bit 1244 | if ($bits[$i]->value != 1) { 1245 | print "W: build_uef_data_100: line $b->linenum, span #$b->span_ix, bit $i: Bad stop bit\n"; 1246 | } 1247 | $bitnum = -1; // go back to expecting start bit 1248 | } 1249 | } 1250 | 1251 | if ($bitnum != -1) { 1252 | // byte unfinished 1253 | // polish it off by stealing 1-bits from subsequent leader tone 1254 | $rem = (8 - $bitnum); 1255 | //print "rem = $rem\n"; 1256 | for ($i=0; $i < $rem; $i++) { 1257 | $v >>= 1; 1258 | $v |= 0x80; 1259 | } 1260 | $s .= chr($v); // store final byte 1261 | $bits_to_steal += $rem + 1; // +1 for stop bit 1262 | } 1263 | 1264 | $chunkbuf = $s; 1265 | 1266 | $cycs_to_steal = $bits_to_steal * 2; 1267 | 1268 | return TBT_E_OK; 1269 | 1270 | } 1271 | 1272 | 1273 | 1274 | function build_uef_data_104 (TibetData $tbd, 1275 | string &$chunkbuf, 1276 | int &$cycs_to_steal) : int { 1277 | 1278 | /* 1279 | The first byte holds the number of data bits per packet, 1280 | not counting start/stop/parity bits. 1281 | 1282 | The second byte holds the ascii code for 'N', 'E' or 'O', 1283 | which specifies that parity is not present, even or odd. 1284 | 1285 | The third byte holds information concerning stop bits. 1286 | If it is a positive number then it is a count of stop bits. 1287 | If it is a negative number then it is a negatived count of 1288 | stop bits to which an extra short wave should be added. 1289 | */ 1290 | 1291 | $frame7 = ($tbd->framing->framelen == 7) ? 1 : 0; 1292 | $stops1 = ($tbd->framing->stops == 1) ? 1 : 0; 1293 | $parity = ($tbd->framing->parity != "N") ? 1 : 0; 1294 | 1295 | //print_r($tbd->framing->stops); print "\n"; 1296 | //print "stops = $tbd->framing->stops \n"; 1297 | 1298 | $s = chr($tbd->framing->framelen). 1299 | $tbd->framing->parity. 1300 | chr($tbd->framing->stops); 1301 | 1302 | $bits = $tbd->bits; 1303 | $nb = count($bits); 1304 | $wordlen = $tbd->framing->framelen; 1305 | $framelen = $wordlen 1306 | + (($tbd->framing->parity == "N") ? 0 : 1) 1307 | + $tbd->framing->stops; 1308 | $late_start_bit = 0; 1309 | $bitnum = -1; 1310 | $bits_to_steal = 0; 1311 | 1312 | // 7E1/7O1: 0xxxxxxxP1 1313 | // 8N1: 0xxxxxxxx1 1314 | // 7E2/7O2: 0xxxxxxxP11 1315 | // 8N2: 0xxxxxxxx11 1316 | // 8E1/8O1: 0xxxxxxxxP1 1317 | 1318 | for ($i=0; $i < $nb; $i++) { 1319 | 1320 | $b = $bits[$i]; 1321 | 1322 | if ($bitnum == -1) { 1323 | if ($b->value != 0) { 1324 | if ( ! $late_start_bit ) { 1325 | print "W: build_uef_data_104: line $b->linenum, span #$b->span_ix, bit $i: Late start bit\n"; 1326 | } 1327 | $late_start_bit = 1; // latch, to avoid error message spam 1328 | continue; 1329 | } 1330 | $late_start_bit = 0; // unlatch 1331 | //$bitnum++; // bitnum = 0 1332 | $v = 0; // byte value 1333 | } else if (($bitnum >= 0) && ($bitnum <= ($wordlen - 1))) { 1334 | //die(); 1335 | $v >>= 1; 1336 | $v |= (($bits[$i]->value) << 7) & 0x80; 1337 | //$bitnum++; 1338 | } else if ($bitnum == $wordlen) { 1339 | // byte finished 1340 | if ($wordlen == 7) { 1341 | // extra shift needed for 7-bit word 1342 | $v >>= 1; 1343 | $v &= 0x7f; 1344 | } 1345 | $s .= chr($v); 1346 | //$bitnum++; 1347 | } 1348 | 1349 | //print "?\n"; die(); 1350 | 1351 | //print "bitnum=$bitnum\n"; 1352 | 1353 | //print "parity=$parity , frame7=$frame7 \n"; 1354 | 1355 | if ($bitnum == 7) { 1356 | if ($parity && $frame7) { 1357 | // 7E1, 7O1, 7E2, 7O2 1358 | // parity bit 1359 | if ( ! check_parity ($v, $bits[$i]->value, $tbd->framing->parity) ) { 1360 | print "W: build_uef_data_104: line $b->linenum, span #$b->span_ix, bit $i: Bad parity bit\n"; 1361 | } 1362 | } 1363 | } else if ($bitnum == 8) { 1364 | if ($parity && ! $frame7) { 1365 | // 8E1, 8O1 1366 | // parity bit 1367 | if ( ! check_parity ($v, $bits[$i]->value, $tbd->framing->parity) ) { 1368 | print "W: build_uef_data_104: line $b->linenum, span #$b->span_ix, bit $i: Bad parity bit\n"; 1369 | } 1370 | } else { 1371 | // 7E1, 7O1, 8N1, 7E2, 7O2, 8N2 1372 | // first stop bit 1373 | if ($bits[$i]->value != 1) { 1374 | print "W: build_uef_data_104: line $b->linenum, span #$b->span_ix, bit $i: Bad stop bit\n"; 1375 | } 1376 | // if 7E1, 7O1, 8N1, byte is now over 1377 | if ($frame7 && $parity) { 1378 | // 7E1, 7O1 1379 | $bitnum = -2; 1380 | } else if (! $frame7 && ! $parity && $stops1) { 1381 | // 8N1 1382 | $bitnum = -2; 1383 | } 1384 | } 1385 | } else if ($bitnum == 9) { 1386 | // 7E2, 7O2, 8N2, 8E1, 8O1 1387 | // second stop bit 1388 | if ($bits[$i]->value != 1) { 1389 | print "W: build_uef_data_104: line $b->linenum, span #$b->span_ix, bit $i: Bad stop bit\n"; 1390 | } 1391 | $bitnum = -2; 1392 | } 1393 | 1394 | $bitnum++; 1395 | 1396 | } // next bit 1397 | 1398 | 1399 | if ($bitnum != -1) { 1400 | // byte unfinished 1401 | // polish it off by stealing 1-bits from subsequent leader tone 1402 | $rem = ($wordlen - $bitnum); 1403 | //print "rem = $rem\n"; 1404 | for ($i=0; $i < $rem; $i++) { 1405 | $v >>= 1; 1406 | $v |= 0x80; 1407 | } 1408 | if ($wordlen == 7) { 1409 | // extra shift needed for 7-bit word 1410 | $v >>= 1; 1411 | $v &= 0x7f; 1412 | } 1413 | $s .= chr($v); // store final byte 1414 | $bits_to_steal += $rem + $parity + ($stops1 ? 1 : 2); 1415 | } 1416 | 1417 | $chunkbuf = $s; 1418 | $cycs_to_steal = $bits_to_steal * 2; // out 1419 | 1420 | return TBT_E_OK; 1421 | 1422 | } 1423 | 1424 | 1425 | function build_uef_data_102 (TibetData $tbd, 1426 | string &$chunkbuf, 1427 | int &$cycs_to_steal) : int { 1428 | //int $chunk_102_interpretation) : int { 1429 | 1430 | $bits = $tbd->bits; 1431 | $nb = count($bits); 1432 | 1433 | // regregex's argument for how this should be implemented was compelling: 1434 | 1435 | // https://stardot.org.uk/forums/viewtopic.php?f=4&t=26822 1436 | 1437 | // --- 1438 | // Chunk &0102 is a raw representation of data bits stored on 1439 | // cassette. Unlike chunk &0100 there are no implicit start/stop 1440 | // bits. 1441 | // The first byte of this chunk is used to calculate chunk length 1442 | // at the bit level. Only the first 1443 | // (chunk length [1] * 8) - (value of first byte) 1444 | // bits are used in this chunk. 1445 | // --- 1446 | 1447 | // Let's assume there are 15 bits of data in the chunk. We need 1448 | // two bytes of data to hold 15 bits, with one bit spare. Hence the 1449 | // chunk is going to look like this (bits): 1450 | 1451 | // ZZZZZZZZ YYYYYYYY xYYYYYYY 1452 | // Interpretation B ($chunk_102_interpretation = 1): 1453 | // If "chunk length" refers to all three bytes of the chunk, then 1454 | // we need: 1455 | 1456 | // (3 * 8) - Z = 15 1457 | // 24 - 15 = Z 1458 | // Z = 9 1459 | 1460 | // When reading a chunk &102 (which we obviously don't do here), it 1461 | // should be possible to auto-detect which interpretation was used 1462 | // by examining that first byte; if it's < 8 then interpretation A 1463 | // was used; if it's >= 8 then B was used. This isn't watertight 1464 | // because it is possible to encode a pathological chunk &102 that 1465 | // has, say, three unused bytes at the end, all of which are 1466 | // "switched off" by that first byte (Z=24 or Z=32), but hey ho. 1467 | 1468 | $rem = $nb % 8; 1469 | 1470 | if ($rem > 0) { 1471 | $first_byte = 8 - $rem; 1472 | } else { 1473 | $first_byte = 0; 1474 | } 1475 | 1476 | //if ($chunk_102_interpretation == 1) { 1477 | $first_byte += 8; 1478 | //} 1479 | 1480 | $chunkbuf .= chr($first_byte); 1481 | 1482 | for ($i=0, $bitnum=0, $b=0; $i < $nb; $i++, $bitnum++) { 1483 | $b >>= 1; 1484 | $b |= ($bits[$i]->value ? 0x80 : 0); 1485 | if ($bitnum == 7) { 1486 | $chunkbuf .= chr($b); 1487 | $bitnum = -1; 1488 | $b = 0; 1489 | } 1490 | } 1491 | 1492 | // we may have some bits not yet sent. 1493 | // we'll use 1-bits as filler, so that in case the decoder doesn't 1494 | // respect the first byte and carries on sending bits right up to 1495 | // the end of the chunk, they will be leader bits, which is most 1496 | // likely to work. 1497 | if ($bitnum != 0) { 1498 | for (; $bitnum < 8; $bitnum++) { 1499 | $b >>= 1; 1500 | $b |= 0x80; 1501 | } 1502 | $chunkbuf .= chr($b); 1503 | } 1504 | 1505 | // we can have an arbitrary number of bits, so we don't need to 1506 | // steal filler bits from subsequent leader to complete bytes, 1507 | // as we do for the other data chunk types. 1508 | $cycs_to_steal = 0; 1509 | 1510 | return TBT_E_OK; 1511 | 1512 | } 1513 | 1514 | 1515 | /* 1516 | function my_hexdump (array $mem, bool $include_offset) { 1517 | $s=""; 1518 | //$start_of_line = 1; 1519 | if (!isset($mem) || (count($mem)==0)) { return; } 1520 | $l=count($mem); 1521 | if (defined("HEXDUMP_MAX") && $l>HEXDUMP_MAX) { 1522 | $l=HEXDUMP_MAX; 1523 | } 1524 | $sbuf=""; 1525 | for ($i=0;$i<$l;$i++) { 1526 | if (!($i%16)) { 1527 | if ($include_offset) { 1528 | $s.=sprintf ("%02x ", $i); 1529 | } else { 1530 | $s.=" "; 1531 | } 1532 | } 1533 | $s.=sprintf ("%02x ", $z=ord($mem[$i])); 1534 | if (!ctype_print($w=($mem[$i]))||$z>127||$w==="\r"||$w==="\n") { 1535 | $sbuf.="."; 1536 | } else { 1537 | $sbuf.=$w; 1538 | } 1539 | if ($i%16 == 15) { // append text bit yet? 1540 | $s .= " ".$sbuf."\n"; 1541 | $sbuf=""; 1542 | $start_of_line = 1; 1543 | } 1544 | } 1545 | // ending 1546 | // append any remaining text bit 1547 | if ($i%16!=0) { 1548 | for ($i=(16-$i%16);$i;$i--) { 1549 | // pad up to start of text bit 1550 | $s.= " "; 1551 | } 1552 | $s.= " ".$sbuf."\n"; 1553 | } 1554 | return $s; 1555 | } 1556 | */ 1557 | 1558 | 1559 | function check_parity (int $word, int $parity_bit, string $parity_mode) : bool { 1560 | $num_ones = 0; 1561 | // include bit 7 even if in 7-bit mode, it'll be zero in 7-bit mode so it doesn't matter 1562 | for ($i=0; $i < 8; $i++) { 1563 | $num_ones += ($word & 1); 1564 | $word >>= 1; 1565 | } 1566 | $num_ones += ($parity_bit ? 1 : 0); 1567 | return ($parity_mode == "E") XOR ($num_ones & 1); 1568 | } 1569 | 1570 | 1571 | 1572 | function frexp ( $f, &$exponent) { 1573 | $exponent = ( floor(log($f, 2)) + 1 ); 1574 | return ( $f * pow(2, -$exponent) ); 1575 | } 1576 | 1577 | function uef_float (float $f) : string { 1578 | 1579 | $a = array(); 1580 | 1581 | $a[3] = 0; 1582 | 1583 | // sign bit 1584 | if ($f < 0) { 1585 | $f = -$f; 1586 | $a[3] = 0x80; 1587 | } 1588 | 1589 | // decode mantissa and exponent 1590 | $mantissa = (float) frexp ($f, $exponent); 1591 | $exponent += 126; 1592 | 1593 | // store mantissa 1594 | $im = (int) ($mantissa * (1 << 24)); // hmm. was cast to u32_t. problem? 1595 | $a[0] = $im&0xff; 1596 | $a[1] = ($im >> 8)&0xff; 1597 | $a[2] = ($im >> 16)&0x7f; 1598 | 1599 | // store exponent 1600 | $a[3] |= $exponent >> 1; 1601 | $a[2] |= ($exponent&1) << 7; 1602 | 1603 | $buf = ""; 1604 | $buf .= chr($a[0]); 1605 | $buf .= chr($a[1]); 1606 | $buf .= chr($a[2]); 1607 | $buf .= chr($a[3]); 1608 | 1609 | return $buf; 1610 | 1611 | } 1612 | 1613 | 1614 | function wrap_chunk (int $type, string $in, string &$msg) : string { 1615 | $out = ""; 1616 | $len = strlen($in); 1617 | $out .= le16($type); 1618 | $out .= le32($len); 1619 | $out .= $in; 1620 | //printf("Chunk: 0x%04x, payload len 0x%x\n", $type, $len); 1621 | //~ $msg .= sprintf("[_&%03x_len_&%x_] ", $type, $len); 1622 | $msg .= sprintf("0x%03x-0x%04x ", $type, $len); 1623 | return $out; 1624 | } 1625 | 1626 | function le16 (int $i) : string { 1627 | $s = ""; 1628 | $s .= chr($i & 0xff); 1629 | $s .= chr(($i >> 8) & 0xff); 1630 | return $s; 1631 | } 1632 | 1633 | function le32 (int $i) : string { 1634 | $s = ""; 1635 | $s .= chr($i & 0xff); 1636 | $s .= chr(($i >> 8) & 0xff); 1637 | $s .= chr(($i >> 16) & 0xff); 1638 | $s .= chr(($i >> 24) & 0xff); 1639 | return $s; 1640 | } 1641 | 1642 | function le24 (int $i) : string { 1643 | $s = ""; 1644 | $s .= chr($i & 0xff); 1645 | $s .= chr(($i >> 8) & 0xff); 1646 | $s .= chr(($i >> 16) & 0xff); 1647 | return $s; 1648 | } 1649 | 1650 | 1651 | 1652 | function process_line (int $ln, 1653 | string $line, 1654 | int &$state, 1655 | int &$span_ix, 1656 | bool $insert_timestamps, 1657 | ParsedTibet &$tbt) : int { 1658 | 1659 | // FIXME: doesn't quite meet TIBET specifications 1660 | // more checking needed ... 1661 | 1662 | // eliminate comments 1663 | $line_tmp = explode("#", $line); 1664 | $line = $line_tmp[0]; 1665 | 1666 | // split by space 1667 | $words_tmp = explode(" ", $line); 1668 | $words = array(); 1669 | 1670 | // remove any blank words 1671 | foreach ($words_tmp as $tmp=>$w) { 1672 | $w = trim($w); 1673 | if (strlen($w) > 0) { 1674 | $words[] = $w; 1675 | } 1676 | } 1677 | 1678 | $wc = count($words); 1679 | 1680 | // skip empty lines 1681 | if ($wc == 0) { 1682 | return TBT_E_OK; 1683 | } 1684 | 1685 | $e = TBT_E_OK; 1686 | 1687 | $w0 = $words[0]; 1688 | 1689 | // the default state at the start of a parse is STATE_VERSION ... 1690 | if (STATE_VERSION == $state) { 1691 | // version line must be the first non-comment, non-blank 1692 | // line in the file. 1693 | // any subsequent version lines will simply be ignored. 1694 | // (this is deliberate and makes concatenating files easy) 1695 | $e = parse_version ($words, $ln, $tbt->version, $line); 1696 | $state = STATE_IDLE; 1697 | } else if (STATE_IDLE == $state) { 1698 | // this is a whitelist; we could ignore unknown keywords 1699 | // instead, but we'll leave it like this for now 1700 | if ($w0 == "tibet") { 1701 | // duplicate version line; just check it for validity 1702 | $dummy = ""; 1703 | $e = parse_version ($words, $ln, $dummy, $line); 1704 | if ($dummy != $tbt->version) { 1705 | print "E: line $ln, span $span_ix: Mismatched duplicate version: $line\n"; 1706 | return TBT_E_PARSE_DUP_VERSION; 1707 | } 1708 | // TIBET 0.4: reset framing and baud hints for file concatenation 1709 | $df = new DataFraming; // constructor defaults to 8N1 1710 | $df->linenum = $ln; 1711 | $df->span_ix = $span_ix; 1712 | $tbt->spans[] = $df; // token rather than span 1713 | $br = new BaudRate; // constructor defaults to 1200 1714 | $br->linenum = $ln; 1715 | $br->span_ix = $span_ix; 1716 | $tbt->spans[] = $br; // token rather than span 1717 | } else if ($w0 == "silence") { 1718 | if ($wc != 2) { 1719 | print "E: line $ln, span $span_ix: Bad silence line: $line\n"; 1720 | return TBT_E_PARSE_SILENCE; 1721 | } 1722 | $silence = new TibetSilence; 1723 | $silence->linenum = $ln; 1724 | $f = 0.0; 1725 | $e = parse_float ($words[1], $f); // f populated 1726 | if (TBT_E_OK != $e) { return $e; } 1727 | $silence->secs = $f; //(float) $words[1]; 1728 | $silence->span_ix = $span_ix; 1729 | $span_ix++; 1730 | if (($silence->secs <= 0.0) || ($silence->secs > 1000000.0)) { 1731 | print "E: line $ln, span $span_ix: Illegal silence length: $words[1]\n"; 1732 | return TBT_E_BAD_SILENCE; 1733 | } 1734 | $tbt->spans[] = $silence; 1735 | } else if ($w0 == "leader") { 1736 | if ($wc != 2) { 1737 | print "E: line $ln: Bad leader line: $line\n"; 1738 | return TBT_E_PARSE_LEADER; 1739 | } 1740 | $leader = new TibetLeader; 1741 | $leader->linenum = $ln; 1742 | $num_cycs = 0; 1743 | $e = parse_int($words[1], $num_cycs); // $num_cycs populated 1744 | if (TBT_E_OK != $e) { 1745 | print "E: line $ln, span $span_ix: Non-integer leader cycles count: $words[1]\n"; 1746 | return $e; 1747 | } 1748 | $leader->cycles = $num_cycs; //(int) $words[1]; 1749 | $leader->span_ix = $span_ix; 1750 | $span_ix++; 1751 | if (($leader->cycles < 1) || ($leader->cycles > 30000000)) { 1752 | print "E: line $ln, span $span_ix: Illegal leader length: $words[1]\n"; 1753 | return TBT_E_BAD_LEADER; 1754 | } 1755 | $tbt->spans[] = $leader; 1756 | } else if (($w0 == "squawk") || ($w0 == "data")) { 1757 | if ($wc != 1) { 1758 | print "E: line $ln, span $span_ix: Illegal $w0 line: $line\n"; 1759 | return TBT_E_PARSE_DATA; 1760 | } 1761 | $data = new TibetData; 1762 | $data->linenum = $ln; 1763 | $data->span_ix = $span_ix; 1764 | $data->squawk = ($w0 == "squawk"); 1765 | $span_ix++; 1766 | $state = STATE_CYCLES; 1767 | $tbt->spans[] = $data; 1768 | } else if ($w0 == "/phase") { 1769 | // don't care; it's partially a function of playback, 1770 | // so I disagree that it should be regarded 1771 | } else if ($w0 == "/speed") { 1772 | // don't care; again, it's a function of playback, 1773 | // not of the source 1774 | } else if ($w0 == "/time") { 1775 | if ($insert_timestamps) { 1776 | $timehint = new TimeHint; 1777 | $timestamp = 0.0; 1778 | $e = parse_float($words[1], $timestamp); // timestamp populated 1779 | if (TBT_E_OK != $e) { 1780 | print "E: line $ln, span $span_ix: Illegal /time hint: $line\n"; 1781 | return $e; 1782 | } 1783 | $timehint->timestamp = $timestamp; //(float) $words[1]; 1784 | $timehint->linenum = $ln; 1785 | $timehint->span_ix = $span_ix; 1786 | $tbt->spans[] = $timehint; // token rather than span 1787 | } 1788 | } else if ($w0 == "/framing") { 1789 | // quadbike doesn't export this, as it can't detect framing, 1790 | // but it could be added by manually editing a TIBET file. 1791 | // UEF chunk 104 needs to know about non-standard framings, and 1792 | // if they e.g. change in the middle of a block, we stand no 1793 | // chance of detecting them automatically, so 1794 | $df = new DataFraming; 1795 | $df->linenum = $ln; 1796 | $df->span_ix = $span_ix; 1797 | $e = parse_framing ($words[1], $df); // df populated 1798 | if (TBT_E_OK != $e) { return $e; } 1799 | $tbt->spans[] = $df; // token rather than span 1800 | } else if ($w0 == "/baud") { 1801 | // again, not exported by QB 1802 | $br = new BaudRate; 1803 | $br->linenum = $ln; 1804 | $br->span_ix = $span_ix; 1805 | $e = parse_baudrate ($words[1], $br); // br populated 1806 | if (TBT_E_OK != $e) { return $e; } 1807 | $tbt->spans[] = $br; // token rather than span 1808 | } else { 1809 | print "E: line $ln, span $span_ix: Unrecognised: $line\n"; 1810 | return TBT_E_PARSE_BAD_LINE; 1811 | } 1812 | } else if (STATE_CYCLES == $state) { 1813 | if ($w0 == "end") { 1814 | $state = STATE_IDLE; 1815 | } else { 1816 | $len = strlen($words[0]); 1817 | for ($i=0; $i < $len; $i++) { 1818 | $span_ix = count($tbt->spans) - 1; 1819 | $span = $tbt->spans[$span_ix]; // TibetData 1820 | if ($words[0][$i] == "-") { 1821 | $cyc = new TibetCycle; 1822 | $cyc->value = 0; 1823 | $cyc->linenum = $ln; //$span->linenum; 1824 | $cyc->span_ix = $span->span_ix; 1825 | $span->cycles[] = $cyc; 1826 | } else if ($words[0][$i] == ".") { 1827 | $cyc = new TibetCycle; 1828 | $cyc->value = 1; 1829 | $cyc->linenum = $ln; //$span->linenum; 1830 | $cyc->span_ix = $span->span_ix; 1831 | $span->cycles[] = $cyc; 1832 | } else if ($words[0][$i] == "P") { 1833 | // P cannot be turned into a bit, so decoders just skip it. 1834 | } else { 1835 | print "E: line $ln, span $span_ix: Bad cycle line: $line\n"; 1836 | return TBT_E_PARSE_CYCLES; 1837 | } 1838 | $tbt->spans[$span_ix] = $span; // replace the modified value 1839 | } 1840 | } 1841 | } 1842 | 1843 | if (TBT_E_OK != $e) { return $e; } 1844 | 1845 | //print "\n"; 1846 | return TBT_E_OK; 1847 | 1848 | } 1849 | 1850 | 1851 | function parse_float (string $w, float &$f) : int { 1852 | $len = strlen($w); 1853 | $dp_count=0; 1854 | if ($len > 50) { 1855 | return TBT_E_BAD_FLOAT; 1856 | } 1857 | for ($i=0; $i < $len; $i++) { 1858 | $c = $w[$i]; 1859 | if ($c == ".") { 1860 | if ($dp_count != 0) { // only one decimal point allowed 1861 | return TBT_E_BAD_FLOAT; 1862 | } else if ($i == ($len - 1)) { // decimal point may not be the final character 1863 | return TBT_E_BAD_FLOAT; 1864 | } 1865 | $dp_count++; 1866 | } else if ( ! ctype_digit ($c) ) { // chars other than digits and decimal point are illegal 1867 | return TBT_E_BAD_FLOAT; 1868 | } 1869 | } 1870 | $f = (float) $w; 1871 | return TBT_E_OK; 1872 | } 1873 | 1874 | function parse_int (string $w, int &$i) : int { 1875 | $len = strlen($w); 1876 | if ($len > 19) { 1877 | return TBT_E_BAD_INT; 1878 | } 1879 | for ($j=0; $j < $len; $j++) { 1880 | $v = $w[$j]; 1881 | if (!ctype_digit($v)) { 1882 | return TBT_E_BAD_INT; 1883 | } 1884 | } 1885 | $i = (int) $w; 1886 | return TBT_E_OK; 1887 | } 1888 | 1889 | 1890 | function parse_framing (string $w, DataFraming &$f) : int { 1891 | if (strlen($w) != 3) { return FALSE; } 1892 | if (($w[0] != "7") && ($w[0] != "8")) { return FALSE; } 1893 | if (($w[1] != "N") && ($w[1] != "O") && ($w[1] != "E")) { return FALSE; } 1894 | if (($w[2] != "1") && ($w[2] != "2")) { return FALSE; } 1895 | $framelen = 0; 1896 | $e = parse_int ($w[0], $framelen); // framelen populated 1897 | if (TBT_E_OK != $e) { return $e; } 1898 | $f->framelen = $framelen; 1899 | $f->parity = $w[1]; 1900 | $num_stops = 0; 1901 | $e = parse_int ($w[2], $num_stops); // num_stops populated 1902 | if (TBT_E_OK != $e) { return $e; } 1903 | $f->stops = $num_stops; 1904 | return TBT_E_OK; 1905 | } 1906 | 1907 | function parse_baudrate (string $w, BaudRate &$r) : int { 1908 | $i = 0; 1909 | $e = parse_int ($w, $i); // i populated 1910 | if (TBT_E_OK != $e) { return $e; } 1911 | $r->rate = $i; 1912 | return TBT_E_OK; 1913 | } 1914 | 1915 | 1916 | function parse_version (array $words, int $ln, string &$tbt_version, string $line) : int { 1917 | $wc = count($words); 1918 | if ($words[0] != "tibet") { 1919 | print "E: Version line not found: $line\n"; 1920 | return TBT_E_PARSE_VERSION; 1921 | } 1922 | if ($wc != 2) { 1923 | print "E: line $ln: Bad version line: $line\n"; 1924 | return TBT_E_PARSE_VERSION; 1925 | } 1926 | // 0.8: switched to major/minor version paradigm 1927 | $tbt_version = $words[1]; 1928 | $v = explode(".", $tbt_version); 1929 | if (count($v) != 2) { 1930 | print "E: line $ln: Bad version: $tbt_version\n"; 1931 | return TBT_E_BAD_VERSION; 1932 | } 1933 | //if (TIBET_VERSION_STG != $tbt_version) { 1934 | if ($v[0] != TIBET_MAJOR_VERSION) { 1935 | print "E: line $ln: Incompatible TIBET version: $tbt_version\n"; 1936 | return TBT_E_INCOMPATIBLE; 1937 | } 1938 | return TBT_E_OK; 1939 | } 1940 | 1941 | 1942 | function tbt_process (string $ip, bool $insert_timestamps, ParsedTibet &$tbt) : int { 1943 | $state = STATE_VERSION; 1944 | $lines = explode ("\n", $ip); 1945 | $span_ix = 0; 1946 | print count($lines)." lines.\n"; 1947 | foreach ($lines as $ln=>$v) { 1948 | $ln++; // linenum; 1-indexed 1949 | $e = process_line ($ln, $v, $state, $span_ix, $insert_timestamps, $tbt); // state, tbt, span_ix modified 1950 | if (TBT_E_OK != $e) { return $e; } 1951 | } 1952 | if ((STATE_IDLE != $state) && (STATE_DATA != $state)) { 1953 | print "W: Finished in unexpected state $state\n"; 1954 | } 1955 | print "TIBET version: $tbt->version\n"; 1956 | return TBT_E_OK; 1957 | } 1958 | 1959 | 1960 | function usage(string $argv0) { 1961 | print "Usage:\n\n"; 1962 | print " php -f $argv0 [options] \n\n"; 1963 | print "where [options] may be:\n\n"; 1964 | //print " +f activates automatic framing detection\n"; 1965 | //print " (overrides all framing lines in input)\n\n"; 1966 | print " +t use \"/time\" hints in TIBET file to insert &120 label chunks into UEF\n"; 1967 | print " (currently breaks beebjit)\n\n"; 1968 | print " +102 use chunk &102 for data\n"; 1969 | //print " +102b (interpretation B)\n"; 1970 | print " +104 use chunk &104 for data\n"; 1971 | print " +114 use chunk &114 for data\n\n"; 1972 | print " +112 use chunk &112 instead of &116 for silence\n\n"; 1973 | print " +no-117 omit baud rate chunk &117 (Elkulator compatibility)\n\n"; 1974 | print " +nz do not compress output UEF file\n"; 1975 | print "\n"; 1976 | } 1977 | 1978 | ?> 1979 | -------------------------------------------------------------------------------- /3-assembled-output/ELITECO.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELITECO.bin -------------------------------------------------------------------------------- /3-assembled-output/ELITECO.unprot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELITECO.unprot.bin -------------------------------------------------------------------------------- /3-assembled-output/ELITEDA.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELITEDA.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTA.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTA.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTB.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTC.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTD.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTE.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTE.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTF.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTF.bin -------------------------------------------------------------------------------- /3-assembled-output/ELTG.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/ELTG.bin -------------------------------------------------------------------------------- /3-assembled-output/README.md: -------------------------------------------------------------------------------- 1 | # Assembled output for the Electron version of Elite 2 | 3 | This folder contains the output binaries from the build process for the Electron version of Elite. 4 | 5 | It also contains [compile.txt](compile.txt), which contains the output from the assembly process. This is very useful when debugging the build process. 6 | 7 | --- 8 | 9 | Right on, Commanders! 10 | 11 | _Mark Moxon_ -------------------------------------------------------------------------------- /3-assembled-output/README.txt: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------- 3 | Acornsoft Elite 4 | 5 | Version: Acorn Electron 6 | Variant: Ian Bell's Superior UEF 7 | Product: Superior Software (TBC) 8 | 9 | See www.bbcelite.com for details 10 | --------------------------------------- 11 | -------------------------------------------------------------------------------- /3-assembled-output/SHIPS.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/SHIPS.bin -------------------------------------------------------------------------------- /3-assembled-output/WORDS9.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/3-assembled-output/WORDS9.bin -------------------------------------------------------------------------------- /4-reference-binaries/README.md: -------------------------------------------------------------------------------- 1 | # Original binaries for the Electron version of Elite 2 | 3 | This folder contains the binaries from the game disc for the Electron version of Elite. 4 | 5 | * [ib-acornsoft](ib-acornsoft) contains the binaries from the Acornsoft version on Ian Bell's website 6 | 7 | * [ib-superior](ib-superior) contains the binaries from the Superior Software version on Ian Bell's website 8 | 9 | --- 10 | 11 | Right on, Commanders! 12 | 13 | _Mark Moxon_ -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELITECO.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELITECO.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELITECO.unprot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELITECO.unprot.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELITEDA.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELITEDA.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTA.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTA.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTB.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTC.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTD.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTE.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTE.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTF.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTF.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/ELTG.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/ELTG.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/SHIPS.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/SHIPS.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-acornsoft/WORDS9.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-acornsoft/WORDS9.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELITECO.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELITECO.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELITECO.unprot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELITECO.unprot.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELITEDA.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELITEDA.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTA.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTA.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTB.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTC.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTD.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTE.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTE.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTF.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTF.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/ELTG.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/ELTG.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/SHIPS.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/SHIPS.bin -------------------------------------------------------------------------------- /4-reference-binaries/ib-superior/WORDS9.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/4-reference-binaries/ib-superior/WORDS9.bin -------------------------------------------------------------------------------- /5-compiled-game-discs/README.md: -------------------------------------------------------------------------------- 1 | # Compiled game discs for the Electron version of Elite 2 | 3 | This folder contains the SSD disc images for the Electron version of Elite, as produced by the build process. There is one SSD file for each supported release. These SSD images can be loaded into an emulator like JSBeeb or BeebEm, or into a real Electron. 4 | 5 | --- 6 | 7 | Right on, Commanders! 8 | 9 | _Mark Moxon_ -------------------------------------------------------------------------------- /5-compiled-game-discs/elite-electron-ib-acornsoft.ssd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/5-compiled-game-discs/elite-electron-ib-acornsoft.ssd -------------------------------------------------------------------------------- /5-compiled-game-discs/elite-electron-ib-acornsoft.uef: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/5-compiled-game-discs/elite-electron-ib-acornsoft.uef -------------------------------------------------------------------------------- /5-compiled-game-discs/elite-electron-ib-superior.ssd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/5-compiled-game-discs/elite-electron-ib-superior.ssd -------------------------------------------------------------------------------- /5-compiled-game-discs/elite-electron-ib-superior.uef: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markmoxon/elite-source-code-acorn-electron/4e183ca0d918dfc22cba4f105928f7762f7e5e0f/5-compiled-game-discs/elite-electron-ib-superior.uef -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BEEBASM?=beebasm 2 | PYTHON?=python 3 | PHP?=php 4 | 5 | # A make command with no arguments will build the Ian Bell Superior Software 6 | # variant with the standard commander and crc32 verification of the game 7 | # binaries 8 | # 9 | # Optional arguments for the make command are: 10 | # 11 | # variant= Build the specified variant: 12 | # 13 | # ib-superior (default) 14 | # ib-acornsoft 15 | # 16 | # disc=no Build a version to load from cassette rather than disc 17 | # 18 | # commander=max Start with a maxed-out commander 19 | # 20 | # verify=no Disable crc32 verification of the game binaries 21 | # 22 | # So, for example: 23 | # 24 | # make variant=ib-acornsoft commander=max verify=no 25 | # 26 | # will build the Ian Bell Acornsoft variant with a maxed-out commander and 27 | # no crc32 verification 28 | # 29 | # The following variables are written into elite-build-options.asm depending on 30 | # the above arguments, so they can be passed to BeebAsm: 31 | # 32 | # _VERSION 33 | # 5 = Acorn Electron 34 | # 35 | # _VARIANT 36 | # 1 = Ian Bell's Superior Software UEF (default) 37 | # 2 = Ian Bell's Acornsoft UEF 38 | # 39 | # _MAX_COMMANDER 40 | # TRUE = Maxed-out commander 41 | # FALSE = Standard commander 42 | # 43 | # _DISC 44 | # TRUE = Build for loading from disc 45 | # FALSE = Build for loading from cassette 46 | # 47 | # The verify argument is passed to the crc32.py script, rather than BeebAsm 48 | 49 | ifeq ($(commander), max) 50 | max-commander=TRUE 51 | else 52 | max-commander=FALSE 53 | endif 54 | 55 | ifeq ($(encrypt), no) 56 | unencrypt=-u 57 | remove-checksums=TRUE 58 | else 59 | unencrypt= 60 | remove-checksums=FALSE 61 | endif 62 | 63 | ifeq ($(disc), no) 64 | build-for-disc=FALSE 65 | else 66 | build-for-disc=TRUE 67 | endif 68 | 69 | ifeq ($(variant), ib-acornsoft) 70 | variant-number=2 71 | folder=ib-acornsoft 72 | suffix=-ib-acornsoft 73 | else 74 | variant-number=1 75 | folder=ib-superior 76 | suffix=-ib-superior 77 | endif 78 | 79 | .PHONY:all 80 | all: 81 | echo _VERSION=5 > 1-source-files/main-sources/elite-build-options.asm 82 | echo _VARIANT=$(variant-number) >> 1-source-files/main-sources/elite-build-options.asm 83 | echo _REMOVE_CHECKSUMS=$(remove-checksums) >> 1-source-files/main-sources/elite-build-options.asm 84 | echo _MAX_COMMANDER=$(max-commander) >> 1-source-files/main-sources/elite-build-options.asm 85 | echo _DISC=$(build-for-disc) >> 1-source-files/main-sources/elite-build-options.asm 86 | $(BEEBASM) -i 1-source-files/main-sources/elite-source.asm -v > 3-assembled-output/compile.txt 87 | $(BEEBASM) -i 1-source-files/main-sources/elite-bcfs.asm -v >> 3-assembled-output/compile.txt 88 | $(BEEBASM) -i 1-source-files/main-sources/elite-loader.asm -v >> 3-assembled-output/compile.txt 89 | $(BEEBASM) -i 1-source-files/main-sources/elite-readme.asm -v >> 3-assembled-output/compile.txt 90 | $(PYTHON) 2-build-files/elite-checksum.py $(unencrypt) -rel$(variant-number) 91 | $(BEEBASM) -i 1-source-files/main-sources/elite-disc.asm -do 5-compiled-game-discs/elite-electron$(suffix).ssd -opt 3 -title "E L I T E" 92 | ifneq ($(verify), no) 93 | @$(PYTHON) 2-build-files/crc32.py 4-reference-binaries/$(folder) 3-assembled-output 94 | endif 95 | 96 | .PHONY:uef-electron 97 | uef: all 98 | $(PHP) 2-build-files/mktibet-0.3.php +t temp.tbt +n ELITE +d FFFF0E00 +x FFFF8023 1-source-files/basic-programs/$$.ELITE-cassette.bin +n ELITEdata +d FFFF4400 +x FFFF5200 3-assembled-output/ELITEDA.bin +n ELITEcode +d 00000000 +x FFFFFFFF 3-assembled-output/ELITECO.bin +n README +d FFFFFFFF +x FFFFFFFF 3-assembled-output/README.txt 99 | php 2-build-files/tibetuef-0.8.php +nz temp.tbt 5-compiled-game-discs/elite-electron$(suffix).uef 100 | rm temp.tbt 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fully documented source code for Elite on the Acorn Electron 2 | 3 | [BBC Micro cassette Elite](https://github.com/markmoxon/elite-source-code-bbc-micro-cassette) | [BBC Micro disc Elite](https://github.com/markmoxon/elite-source-code-bbc-micro-disc) | **Acorn Electron Elite** | [6502 Second Processor Elite](https://github.com/markmoxon/elite-source-code-6502-second-processor) | [Commodore 64 Elite](https://github.com/markmoxon/elite-source-code-commodore-64) | [Apple II Elite](https://github.com/markmoxon/elite-source-code-apple-ii) | [BBC Master Elite](https://github.com/markmoxon/elite-source-code-bbc-master) | [NES Elite](https://github.com/markmoxon/elite-source-code-nes) | [Elite-A](https://github.com/markmoxon/elite-a-source-code-bbc-micro) | [Teletext Elite](https://github.com/markmoxon/teletext-elite) | [Elite Universe Editor](https://github.com/markmoxon/elite-universe-editor) | [Elite Compendium (BBC Master)](https://github.com/markmoxon/elite-compendium-bbc-master) | [Elite Compendium (BBC Micro)](https://github.com/markmoxon/elite-compendium-bbc-micro) | [Elite over Econet](https://github.com/markmoxon/elite-over-econet) | [!EliteNet](https://github.com/markmoxon/elite-over-econet-acorn-archimedes) | [Flicker-free Commodore 64 Elite](https://github.com/markmoxon/c64-elite-flicker-free) | [BBC Micro Aviator](https://github.com/markmoxon/aviator-source-code-bbc-micro) | [BBC Micro Revs](https://github.com/markmoxon/revs-source-code-bbc-micro) | [Archimedes Lander](https://github.com/markmoxon/lander-source-code-acorn-archimedes) 4 | 5 | ![Screenshot of Elite on the Acorn Electron](https://elite.bbcelite.com/images/github/Elite-Electron.png) 6 | 7 | This repository contains source code for Elite on the Acorn Electron, with every single line documented and (for the most part) explained. It has been reconstructed by hand from a disassembly of the original game binaries. 8 | 9 | It is a companion to the [elite.bbcelite.com website](https://elite.bbcelite.com). 10 | 11 | See the [introduction](#introduction) for more information, or jump straight into the [documented source code](1-source-files/main-sources). 12 | 13 | ## Contents 14 | 15 | * [Introduction](#introduction) 16 | 17 | * [Acknowledgements](#acknowledgements) 18 | 19 | * [A note on licences, copyright etc.](#user-content-a-note-on-licences-copyright-etc) 20 | 21 | * [Browsing the source in an IDE](#browsing-the-source-in-an-ide) 22 | 23 | * [Folder structure](#folder-structure) 24 | 25 | * [Flicker-free Elite](#flicker-free-elite) 26 | 27 | * [Building Acorn Electron Elite from the source](#building-electron-elite-from-the-source) 28 | 29 | * [Requirements](#requirements) 30 | * [Windows](#windows) 31 | * [Mac and Linux](#mac-and-linux) 32 | * [Build options](#build-options) 33 | * [Verifying the output](#verifying-the-output) 34 | * [Log files](#log-files) 35 | * [Building a UEF tape image](#building-a-uef-tape-image) 36 | 37 | * [Building different variants of the Electron version of Elite](#building-different-variants-of-the-electron-version-of-elite) 38 | 39 | * [Building the Ian Bell Superior Software variant](#building-the-ian-bell-superior-software-variant) 40 | * [Building the Ian Bell Acornsoft variant](#building-the-ian-bell-acornsoft-variant) 41 | * [Differences between the variants](#differences-between-the-variants) 42 | 43 | ## Introduction 44 | 45 | This repository contains source code for Elite on the Acorn Electron, with every single line documented and (for the most part) explained. 46 | 47 | You can build the fully functioning game from this source. [Two variants](#building-different-variants-of-the-electron-version-of-elite) are currently supported: the Superior Software version from Ian Bell's personal website, and the original Acornsoft version from the same site (which has the galactic hyperspace bug). 48 | 49 | This repository is a companion to the [elite.bbcelite.com website](https://elite.bbcelite.com), which contains all the code from this repository, but laid out in a much more human-friendly fashion. The links at the top of this page will take you to repositories for the other versions of Elite that are covered by this project. 50 | 51 | * If you want to browse the source and read about how Elite works under the hood, you will probably find [the website](https://elite.bbcelite.com) a better place to start than this repository. 52 | 53 | * If you would rather explore the source code in your favourite IDE, then the [annotated source](1-source-files/main-sources) is what you're looking for. It contains the exact same content as the website, so you won't be missing out (the website is generated from the source files, so they are guaranteed to be identical). You might also like to read the section on [browsing the source in an IDE](#browsing-the-source-in-an-ide) for some tips. 54 | 55 | * If you want to build Acorn Electron Elite from the source on a modern computer, to produce a working game disc that can be loaded into an Electron or an emulator, then you want the section on [building Acorn Electron Elite from the source](#building-acorn-electron-elite-from-the-source). 56 | 57 | Note that the disc images produced by this repository let you load the game from disc, but you can only save and load commander files to cassette. This is because the game in the disc images is still the cassette version of Electron Elite, it's just loading from disc, so it has to revert to cassette once it's loaded. 58 | 59 | Also, the build process for Electon Elite does not include the encryption from the original, so although the game code is identical, the binaries produced will not match the original cassettes, as the originals are encrypted. 60 | 61 | My hope is that this repository and the [accompanying website](https://elite.bbcelite.com) will be useful for those who want to learn more about Elite and what makes it tick. It is provided on an educational and non-profit basis, with the aim of helping people appreciate one of the most iconic games of the 8-bit era. 62 | 63 | ## Acknowledgements 64 | 65 | Acorn Electron Elite was written by Ian Bell and David Braben and is copyright © Acornsoft 1984. 66 | 67 | The code on this site has been reconstructed from a disassembly of the version released on [Ian Bell's personal website](http://www.elitehomepage.org/). 68 | 69 | The commentary is copyright © Mark Moxon. Any misunderstandings or mistakes in the documentation are entirely my fault. 70 | 71 | Huge thanks are due to the original authors for not only creating such an important piece of my childhood, but also for releasing the source code for us to play with; to Paul Brink for his annotated disassembly; and to Kieran Connell for his [BeebAsm version](https://github.com/kieranhj/elite-beebasm), which I forked as the original basis for this project. You can find more information about this project in the [accompanying website's project page](https://elite.bbcelite.com/about_site/about_this_project.html). 72 | 73 | Thank you to Diminished for the UEF scripts, which are part of the [Quadbike 2](https://stardot.org.uk/forums/viewtopic.php?t=26669) tape transcriber. 74 | 75 | The following archives from Ian Bell's personal website forms the basis for this project: 76 | 77 | * [Acorn Electron Elite, Acornsoft version](http://www.elitehomepage.org/archive/a/a4090000.zip) 78 | * [Acorn Electron Elite, Superior Software version](http://www.elitehomepage.org/archive/a/a4090010.zip) 79 | 80 | ### A note on licences, copyright etc. 81 | 82 | This repository is _not_ provided with a licence, and there is intentionally no `LICENSE` file provided. 83 | 84 | According to [GitHub's licensing documentation](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/licensing-a-repository), this means that "the default copyright laws apply, meaning that you retain all rights to your source code and no one may reproduce, distribute, or create derivative works from your work". 85 | 86 | The reason for this is that my commentary is intertwined with the original Elite source code, and the original source code is copyright. The whole site is therefore covered by default copyright law, to ensure that this copyright is respected. 87 | 88 | Under GitHub's rules, you have the right to read and fork this repository... but that's it. No other use is permitted, I'm afraid. 89 | 90 | My hope is that the educational and non-profit intentions of this repository will enable it to stay hosted and available, but the original copyright holders do have the right to ask for it to be taken down, in which case I will comply without hesitation. I do hope, though, that along with the various other disassemblies and commentaries of this source, it will remain viable. 91 | 92 | ## Browsing the source in an IDE 93 | 94 | If you want to browse the source in an IDE, you might find the following useful. 95 | 96 | * The most interesting files are in the [main-sources](1-source-files/main-sources) folder: 97 | 98 | * The main game's source code is in the [elite-source.asm](1-source-files/main-sources/elite-source.asm) file - this is the motherlode and probably contains all the stuff you're interested in. 99 | 100 | * The game's loader is in the [elite-loader.asm](1-source-files/main-sources/elite-loader.asm) file - this is mainly concerned with setup and copy protection. 101 | 102 | * It's probably worth skimming through the [notes on terminology and notations](https://elite.bbcelite.com/terminology/) on the accompanying website, as this explains a number of terms used in the commentary, without which it might be a bit tricky to follow at times (in particular, you should understand the terminology I use for multi-byte numbers). 103 | 104 | * The accompanying website contains [a number of "deep dive" articles](https://elite.bbcelite.com/deep_dives/), each of which goes into an aspect of the game in detail. Routines that are explained further in these articles are tagged with the label `Deep dive:` and the relevant article name. 105 | 106 | * There are loads of routines and variables in Elite - literally hundreds. You can find them in the source files by searching for the following: `Type: Subroutine`, `Type: Variable`, `Type: Workspace` and `Type: Macro`. 107 | 108 | * If you know the name of a routine, you can find it by searching for `Name: `, as in `Name: SCAN` (for the 3D scanner routine) or `Name: LL9` (for the ship-drawing routine). 109 | 110 | * The entry point for the [main game code](1-source-files/main-sources/elite-source.asm) is routine `TT170`, which you can find by searching for `Name: TT170`. If you want to follow the program flow all the way from the title screen around the main game loop, then you can find a number of [deep dives on program flow](https://elite.bbcelite.com/deep_dives/) on the accompanying website. 111 | 112 | * The source code is designed to be read at an 80-column width and with a monospaced font, just like in the good old days. 113 | 114 | I hope you enjoy exploring the inner workings of Electron Elite as much as I have. 115 | 116 | ## Folder structure 117 | 118 | There are five main folders in this repository, which reflect the order of the build process. 119 | 120 | * [1-source-files](1-source-files) contains all the different source files, such as the main assembler source files, image binaries, fonts, boot files and so on. 121 | 122 | * [2-build-files](2-build-files) contains build-related scripts, such as the checksum, encryption and crc32 verification scripts. 123 | 124 | * [3-assembled-output](3-assembled-output) contains the output from the assembly process, when the source files are assembled and the results processed by the build files. 125 | 126 | * [4-reference-binaries](4-reference-binaries) contains the correct binaries for each variant, so we can verify that our assembled output matches the reference. 127 | 128 | * [5-compiled-game-discs](5-compiled-game-discs) contains the final output of the build process: an SSD disc image that contains the compiled game and which can be run on real hardware or in an emulator. 129 | 130 | ## Flicker-free Elite 131 | 132 | This repository also includes a flicker-free version, which incorporates the backported flicker-free ship-drawing routines from the BBC Master, as well as a fix for planets so they no longer flicker. The flicker-free code is in a separate branch called `flicker-free`, and apart from the code differences for reducing flicker, this branch is identical to the main branch and the same build process applies. 133 | 134 | The flicker-free Electron version also includes a number of extra features, all of which are backported from the BBC Micro version. The complete feature list is as follows: 135 | 136 | * Flicker-free ships 137 | 138 | * Flicker-free planets 139 | 140 | * The escape capsule animation from the BBC Micro has been added, which is not present in the original Electron version 141 | 142 | * There are now three sizes of stardust (like the BBC Micro) rather than two, with the addition of one-pixel stardust 143 | 144 | * Planets are more high-fidelity, so the planet's circle looks more like the BBC Micro, and less like a 50p; this does slow things down a little, but overall the faster algorithm for flicker-free planets compensates for this 145 | 146 | * For the SSD disc version, the black box that shows loading progress has been removed from the Acornsoft loading screen (it is still used to show loading progress in the UEF cassette version) 147 | 148 | The annotated source files in the `flicker-free` branch contain both the original Acornsoft code and all of the modifications for flicker-free Elite, so you can look through the source to see exactly what's changed. Any code that I've removed from the original version is commented out in the source files, so when they are assembled they produce the flicker-free binaries, while still containing details of all the modifications. You can find all the diffs by searching the sources for `Mod:`. 149 | 150 | For more information on flicker-free Elite, see the [hacks section of the accompanying website](https://elite.bbcelite.com/hacks/flicker-free_elite.html). 151 | 152 | ## Building Acorn Electron Elite from the source 153 | 154 | Builds are supported for both Windows and Mac/Linux systems. In all cases the build process is defined in the `Makefile` provided. 155 | 156 | ### Requirements 157 | 158 | You will need the following to build Acorn Electron Elite from the source: 159 | 160 | * BeebAsm, which can be downloaded from the [BeebAsm repository](https://github.com/stardot/beebasm). Mac and Linux users will have to build their own executable with `make code`, while Windows users can just download the `beebasm.exe` file. 161 | 162 | * Python. The build process has only been tested on 3.x, but 2.7 might work. 163 | 164 | * Mac and Linux users may need to install `make` if it isn't already present (for Windows users, `make.exe` is included in this repository). 165 | 166 | For details of how the build process works, see the [build documentation on bbcelite.com](https://elite.bbcelite.com/about_site/building_elite.html). 167 | 168 | Let's look at how to build Acorn Electron Elite from the source. 169 | 170 | ### Windows 171 | 172 | For Windows users, there is a batch file called `make.bat` which you can use to build the game. Before this will work, you should edit the batch file and change the values of the `BEEBASM` and `PYTHON` variables to point to the locations of your `beebasm.exe` and `python.exe` executables. You also need to change directory to the repository folder (i.e. the same folder as `make.bat`). 173 | 174 | All being well, entering the following into a command window: 175 | 176 | ``` 177 | make.bat 178 | ``` 179 | 180 | will produce a file called `elite-electron-ib-superior.ssd` in the `5-compiled-game-discs` folder that contains the Ian Bell Superior Software variant, which you can then load into an emulator, or into a real Electron using a device like a Gotek. 181 | 182 | ### Mac and Linux 183 | 184 | The build process uses a standard GNU `Makefile`, so you just need to install `make` if your system doesn't already have it. If BeebAsm or Python are not on your path, then you can either fix this, or you can edit the `Makefile` and change the `BEEBASM` and `PYTHON` variables in the first two lines to point to their locations. You also need to change directory to the repository folder (i.e. the same folder as `Makefile`). 185 | 186 | All being well, entering the following into a terminal window: 187 | 188 | ``` 189 | make 190 | ``` 191 | 192 | will produce a file called `elite-electron-ib-superior.ssd` in the `5-compiled-game-discs` folder that contains the Ian Bell Superior Software variant, which you can then load into an emulator, or into a real Electron using a device like a Gotek. 193 | 194 | ### Build options 195 | 196 | By default the build process will create a typical Elite game disc with a standard commander and verified binaries. There are various arguments you can pass to the build to change how it works. They are: 197 | 198 | * `variant=` - Build the specified variant: 199 | 200 | * `variant=ib-superior` (default) 201 | * `variant=ib-acornsoft` 202 | 203 | * `disc=no` - Build a version to load from cassette rather than disc (the default is to build a version that loads from disc) 204 | 205 | * `commander=max` - Start with a maxed-out commander (specifically, this is the test commander file from the original source, which is almost but not quite maxed-out) 206 | 207 | * `verify=no` - Disable crc32 verification of the game binaries 208 | 209 | So, for example: 210 | 211 | `make variant=ib-acornsoft commander=max verify=no` 212 | 213 | will build the Ian Bell Acornsoft variant with a maxed-out commander and no crc32 verification. 214 | 215 | See below for more on the verification process. 216 | 217 | ### Verifying the output 218 | 219 | The default build process prints out checksums of all the generated files, along with the checksums of the files from the original sources. You can disable verification by passing `verify=no` to the build. 220 | 221 | The Python script `crc32.py` in the `2-build-files` folder does the actual verification, and shows the checksums and file sizes of both sets of files, alongside each other, and with a Match column that flags any discrepancies. If you are building an unencrypted set of files then there will be lots of differences, while the encrypted files should mostly match (see the Differences section below for more on this). 222 | 223 | The binaries in the `4-reference-binaries` folder are those extracted from the released version of the game, while those in the `3-assembled-output` folder are produced by the build process. For example, if you don't make any changes to the code and build the project with `make`, then this is the output of the verification process: 224 | 225 | ``` 226 | Results for variant: ib-superior 227 | [--originals--] [---output----] 228 | Checksum Size Checksum Size Match Filename 229 | ----------------------------------------------------------- 230 | 171314b3 19200 171314b3 19200 Yes ELITECO.bin 231 | a173fec7 19200 a173fec7 19200 Yes ELITECO.unprot.bin 232 | 97e920c8 4864 97e920c8 4864 Yes ELITEDA.bin 233 | 2e0a1a46 2205 2e0a1a46 2205 Yes ELTA.bin 234 | 7f230b24 2338 7f230b24 2338 Yes ELTB.bin 235 | 41e0d10e 2699 41e0d10e 2699 Yes ELTC.bin 236 | 7b227167 2786 7b227167 2786 Yes ELTD.bin 237 | 142b20dd 1812 142b20dd 1812 Yes ELTE.bin 238 | 440253b4 2671 440253b4 2671 Yes ELTF.bin 239 | 553b0078 2340 553b0078 2340 Yes ELTG.bin 240 | f23f7ef2 2348 f23f7ef2 2348 Yes SHIPS.bin 241 | a6ee7213 1024 a6ee7213 1024 Yes WORDS9.bin 242 | ``` 243 | 244 | All the compiled binaries match the originals, so we know we are producing the same final game as the Ian Bell Superior Software variant. 245 | 246 | ### Log files 247 | 248 | During compilation, details of every step are output in a file called `compile.txt` in the `3-assembled-output` folder. If you have problems, it might come in handy, and it's a great reference if you need to know the addresses of labels and variables for debugging (or just snooping around). 249 | 250 | ### Building a UEF tape image 251 | 252 | Despite this being the cassette version of Acorn Electron Elite, this repository only builds disc images by default, as that's how BeebAsm works. If you want the authentic experience of loading Elite from cassette, then you can build a UEF with the following command on Windows: 253 | 254 | ``` 255 | make.bat uef disc=no 256 | ``` 257 | 258 | or this on Mac/Linux: 259 | 260 | ``` 261 | make uef disc=no 262 | ``` 263 | 264 | You should now be able to load Elite from your UEF on an Electron, by entering `CHAIN "ELITE"`. These UEF tape images will work with emulators and the TZXDuino (though you may need to unzip them before they will work with the latter). 265 | 266 | For this to work, you will need PHP installed, and you should edit the `Makefile` (and `make.bat` on Windows) to point to PHP (specifically, you'll need to point the `PHP` variable to point to the locations of your `php` or `php.exe` binary). 267 | 268 | Note that in order for your UEF to work, you need to include the `disc=no` argument to the build (as above). This ensures that the binaries are built to load at the correct address for tape systems, and it also disables the block-level tape protection system, as this only works with specially created tape images, like the ones that Acornsoft originally released. 269 | 270 | ## Building different variants of the Electron version of Elite 271 | 272 | This repository contains the source code for two different variants of the Acorn Electron version of Elite: 273 | 274 | * The variant from the Superior Software UEF on Ian Bell's website 275 | 276 | * The variant from the Acornsoft UEF on Ian Bell's website 277 | 278 | By default the build process builds the Superior Software variant, but you can build a specified variant using the `variant=` build parameter. 279 | 280 | ### Building the Ian Bell Superior Software variant 281 | 282 | You can add `variant=ib-superior` to produce the `elite-electron-ib-superior.ssd` file containing the Superior Software variant, though that's the default value so it isn't necessary. In other words, you can build it like this: 283 | 284 | ``` 285 | make.bat variant=ib-superior 286 | ``` 287 | 288 | or this on a Mac or Linux: 289 | 290 | ``` 291 | make variant=ib-superior 292 | ``` 293 | 294 | This will produce a file called `elite-electron-ib-superior.ssd` in the `5-compiled-game-discs` folder that contains the Ian Bell Superior Software variant. 295 | 296 | The verification checksums for this version are as follows: 297 | 298 | ``` 299 | Results for variant: ib-superior 300 | [--originals--] [---output----] 301 | Checksum Size Checksum Size Match Filename 302 | ----------------------------------------------------------- 303 | 171314b3 19200 171314b3 19200 Yes ELITECO.bin 304 | a173fec7 19200 a173fec7 19200 Yes ELITECO.unprot.bin 305 | 97e920c8 4864 97e920c8 4864 Yes ELITEDA.bin 306 | 2e0a1a46 2205 2e0a1a46 2205 Yes ELTA.bin 307 | 7f230b24 2338 7f230b24 2338 Yes ELTB.bin 308 | 41e0d10e 2699 41e0d10e 2699 Yes ELTC.bin 309 | 7b227167 2786 7b227167 2786 Yes ELTD.bin 310 | 142b20dd 1812 142b20dd 1812 Yes ELTE.bin 311 | 440253b4 2671 440253b4 2671 Yes ELTF.bin 312 | 553b0078 2340 553b0078 2340 Yes ELTG.bin 313 | f23f7ef2 2348 f23f7ef2 2348 Yes SHIPS.bin 314 | a6ee7213 1024 a6ee7213 1024 Yes WORDS9.bin 315 | ``` 316 | 317 | ### Building the Ian Bell Acornsoft variant 318 | 319 | You can build the Ian Bell Acornsoft variant by appending `variant=ib-acornsoft` to the `make` command, like this on Windows: 320 | 321 | ``` 322 | make.bat variant=ib-acornsoft 323 | ``` 324 | 325 | or this on a Mac or Linux: 326 | 327 | ``` 328 | make variant=ib-acornsoft 329 | ``` 330 | 331 | This will produce a file called `elite-disc-ib-acornsoft.ssd` in the `5-compiled-game-discs` folder that contains the Ian Bell Acornsoft variant. 332 | 333 | The verification checksums for this version are as follows: 334 | 335 | ``` 336 | Results for variant: ib-acornsoft 337 | [--originals--] [---output----] 338 | Checksum Size Checksum Size Match Filename 339 | ----------------------------------------------------------- 340 | be5d6b7c 19200 be5d6b7c 19200 Yes ELITECO.bin 341 | e983beb3 19200 e983beb3 19200 Yes ELITECO.unprot.bin 342 | 97e920c8 4864 97e920c8 4864 Yes ELITEDA.bin 343 | d27b7e45 2205 d27b7e45 2205 Yes ELTA.bin 344 | 30655bb7 2338 30655bb7 2338 Yes ELTB.bin 345 | fdeca895 2699 fdeca895 2699 Yes ELTC.bin 346 | f43fb8fd 2783 f43fb8fd 2783 Yes ELTD.bin 347 | e08543b0 1814 e08543b0 1814 Yes ELTE.bin 348 | 1114d856 2671 1114d856 2671 Yes ELTF.bin 349 | ea71aacd 2340 ea71aacd 2340 Yes ELTG.bin 350 | f23f7ef2 2348 f23f7ef2 2348 Yes SHIPS.bin 351 | a6ee7213 1024 a6ee7213 1024 Yes WORDS9.bin 352 | ``` 353 | 354 | ### Differences between the variants 355 | 356 | You can see the differences between the variants by searching the source code for `_IB_SUPERIOR` (for features in the Ian Bell Superior Software variant) or `_IB_ACORNSOFT` (for features in the Ian Bell Acornsoft variant). There are only a few differences: 357 | 358 | * The Acornsoft variant contains the galactic hyperspace bug from the first release of the game, which prevents the galactic hyperspace from working 359 | 360 | * Galactic hyperspace does not work in the Acornsoft variant, but if it did, it would drop you at a randomly generated point in the new galaxy, rather than the closest system to galactic coordinates (96, 96), which is how all the other versions work 361 | 362 | * If the galactic hyperspace worked in the Acornsoft variant, it would be triggered by CAPS-LOCK-H rather than CTRL-H 363 | 364 | * The Acornsoft variant contains the same "hyperspace while docking" bug as the original cassette and disc versions; the Superior Software variant contains part of the fix for this issue, but it isn't completely fixed 365 | 366 | See the [accompanying website](https://elite.bbcelite.com/electron/releases.html) for a comprehensive list of differences between the variants. 367 | 368 | --- 369 | 370 | Right on, Commanders! 371 | 372 | _Mark Moxon_ -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | SETLOCAL 3 | SET BEEBASM=C:\Users\user\bin\beebasm.exe 4 | SET PYTHON=C:\Users\user\AppData\Local\Microsoft\WindowsApps\python.exe 5 | 2-build-files\make %* 6 | --------------------------------------------------------------------------------