mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			190 Commits
		
	
	
		
			gui2
			...
			drivetypes
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 099d7969ca | ||
|  | 5adfa95a85 | ||
|  | bfa0846ad0 | ||
|  | 7099264334 | ||
|  | 69b44e7968 | ||
|  | fe39977ff7 | ||
|  | b9fc8de5b5 | ||
|  | f7b8022d3a | ||
|  | a62346c515 | ||
|  | e372d757ad | ||
|  | ab1b10f935 | ||
|  | 8e918706dc | ||
|  | 76450d00bf | ||
|  | ee53542e18 | ||
|  | db004bc787 | ||
|  | 71a7f3554e | ||
|  | 5c3f422a53 | ||
|  | 2fe0cec04a | ||
|  | de59e781b5 | ||
|  | 8c77af651b | ||
|  | 638f6928cf | ||
|  | ccc8e597a7 | ||
|  | 585f19d884 | ||
|  | bb2b7d7df6 | ||
|  | e75d218438 | ||
|  | 7f81b554fd | ||
|  | 2490f19a1a | ||
|  | 30f382bf22 | ||
|  | ad03c187cf | ||
|  | 06560b5a5a | ||
|  | 7c40093698 | ||
|  | d37c75d703 | ||
|  | 82bfb9a303 | ||
|  | 01682101a6 | ||
|  | 3c46f787b1 | ||
|  | 591d200283 | ||
|  | 195534c21e | ||
|  | 0f9d851a29 | ||
|  | 18a03baf99 | ||
|  | 5e06db4a52 | ||
|  | bf78508ef7 | ||
|  | 137c0340fb | ||
|  | e6d9de2d80 | ||
|  | d9b319eaed | ||
|  | f2e713bde3 | ||
|  | 94e2e494c9 | ||
|  | 5af408e1d1 | ||
|  | 77bdc727ab | ||
|  | eb26426424 | ||
|  | f624bb6e5b | ||
|  | 4a8fb9288c | ||
|  | f8f5873973 | ||
|  | 5f4903f2d1 | ||
|  | b02a894663 | ||
|  | 510b530551 | ||
|  | c36662205b | ||
|  | a2ffe06792 | ||
|  | 0f56108bf5 | ||
|  | 199cefdb71 | ||
|  | 1bdeaa326c | ||
|  | cce8cfe88d | ||
|  | bcfc0217dc | ||
|  | 7cfa080220 | ||
|  | 45ebc0f40f | ||
|  | 38d575eda7 | ||
|  | 9cb284583b | ||
|  | 137b921e8d | ||
|  | 8c876f555d | ||
|  | 0988dd524b | ||
|  | 2dc649ef09 | ||
|  | baf02cb849 | ||
|  | 51fa3c5293 | ||
|  | 134dd6c37d | ||
|  | d766e1f9a9 | ||
|  | d298f5b16e | ||
|  | ed634fbbf6 | ||
|  | 4c776d584b | ||
|  | c2c04862a2 | ||
|  | ccd9539015 | ||
|  | 624c597735 | ||
|  | 9300aa79c3 | ||
|  | 9e522c7da2 | ||
|  | ef60bfff6b | ||
|  | 635c6c7bfe | ||
|  | df6e47fa50 | ||
|  | 654cdcd3d1 | ||
|  | a633b73e12 | ||
|  | ba93dae240 | ||
|  | 8e0ca85d1e | ||
|  | 56a4926bd3 | ||
|  | 6a2aae4ef2 | ||
|  | ec68ce3bfa | ||
|  | a777a5be30 | ||
|  | b553a8b1fb | ||
|  | b119e1f72d | ||
|  | 7345d3e6c1 | ||
|  | e9b7a7bb52 | ||
|  | 2022732dd9 | ||
|  | 63544647b6 | ||
|  | 6b62585ad5 | ||
|  | 14027210f7 | ||
|  | 3df17b23b8 | ||
|  | cbf3f56562 | ||
|  | 1f74d9189f | ||
|  | 137658d1d6 | ||
|  | 5b627bd2b1 | ||
|  | 38ff08885a | ||
|  | a89993aabb | ||
|  | d6353403e2 | ||
|  | bc62ee04c0 | ||
|  | d3ff836b63 | ||
|  | a7aac5578e | ||
|  | add5a141d3 | ||
|  | 330410ec61 | ||
|  | d0f49dcfa6 | ||
|  | 124f6ab7cb | ||
|  | 471f63592e | ||
|  | 50e210c72f | ||
|  | d3396aa535 | ||
|  | 5ed8b838bc | ||
|  | d1757eacc2 | ||
|  | 0692e5f5d5 | ||
|  | e4204196cd | ||
|  | 241d4342e4 | ||
|  | c04cbc631c | ||
|  | 29b273ad7b | ||
|  | 9720dab2f6 | ||
|  | bddc64a324 | ||
|  | eb324f14de | ||
|  | b78a057c81 | ||
|  | 5751725213 | ||
|  | f194392f99 | ||
|  | fea62178af | ||
|  | 33ef4ce8de | ||
|  | 3728120f95 | ||
|  | 2944b9b3f6 | ||
|  | 3430574364 | ||
|  | fc5a5212c0 | ||
|  | 20f724ed13 | ||
|  | 94c1d21938 | ||
|  | a1a9666b6f | ||
|  | 0551ddc276 | ||
|  | 049ffd3b04 | ||
|  | c28f757c5c | ||
|  | 91dbb86e64 | ||
|  | 27a04ee22b | ||
|  | 5cefce9922 | ||
|  | 8fb4c90bed | ||
|  | 81753669cc | ||
|  | 0a0a72bcf3 | ||
|  | c4a6e3e063 | ||
|  | 1138e6b77f | ||
|  | 030f9218d6 | ||
|  | 2fff32e8f2 | ||
|  | 5b2aa9926f | ||
|  | 921e178e83 | ||
|  | 25ffd900c8 | ||
|  | 7ea4e116cc | ||
|  | a9daec36f5 | ||
|  | cebc7c6cd2 | ||
|  | 3f85c9f006 | ||
|  | ed5efd7b87 | ||
|  | 4984a53bfd | ||
|  | b0c77653a2 | ||
|  | 909f0d628b | ||
|  | e27e3ada92 | ||
|  | 339ea3b5a4 | ||
|  | 9bd8b8915e | ||
|  | 35008656a9 | ||
|  | 825089458f | ||
|  | 4a086d94b7 | ||
|  | 0aeddf7e98 | ||
|  | 4922d1deb4 | ||
|  | 86d0893261 | ||
|  | e4c67f18bd | ||
|  | d07c5a94e1 | ||
|  | a91dee27e7 | ||
|  | e3219087c9 | ||
|  | cc9ec84aec | ||
|  | a33cc5710c | ||
|  | c2b148288a | ||
|  | a483567564 | ||
|  | bd99bc6d94 | ||
|  | 8f79071aad | ||
|  | ef9071049b | ||
|  | 60e1ab8cca | ||
|  | d3dbfd3154 | ||
|  | ee2dffb498 | ||
|  | 6d9510cc65 | ||
|  | 49f0f5d000 | 
| @@ -18,17 +18,19 @@ AlwaysBreakBeforeMultilineStrings: 'true' | ||||
| AlwaysBreakTemplateDeclarations: 'Yes' | ||||
| BinPackArguments: 'false' | ||||
| BinPackParameters: 'false' | ||||
| BreakConstructorInitializers: 'AfterColon' | ||||
| BreakBeforeBraces: Allman | ||||
| BreakConstructorInitializers: 'AfterColon' | ||||
| BreakInheritanceList: AfterColon | ||||
| BreakStringLiterals: 'true' | ||||
| IndentCaseLabels: 'true' | ||||
| IndentWidth: '4' | ||||
| ColumnLimit: '80' | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' | ||||
| FixNamespaceComments: 'false' | ||||
| IncludeBlocks: Preserve | ||||
| IndentCaseLabels: 'true' | ||||
| IndentWidth: '4' | ||||
| IndentWrappedFunctionNames: 'false' | ||||
| KeepEmptyLinesAtTheStartOfBlocks: 'true' | ||||
| NamespaceIndentation: All | ||||
| PointerAlignment: Left | ||||
| ReflowComments: 'true' | ||||
| SortIncludes: 'false' | ||||
|   | ||||
							
								
								
									
										44
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -6,26 +6,40 @@ jobs: | ||||
|   build-linux: | ||||
|     runs-on: ubuntu-20.04 | ||||
|     steps: | ||||
|     - uses: actions/checkout@v1 | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|         path: 'fluxengine' | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine-testdata' | ||||
|         path: 'fluxengine-testdata' | ||||
|     - name: apt | ||||
|       run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev | ||||
|     - name: make | ||||
|       run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make | ||||
|       run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j2 -C fluxengine | ||||
|  | ||||
|   build-macos: | ||||
|   build-macos-current: | ||||
|     runs-on: macos-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|         path: 'fluxengine' | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine-testdata' | ||||
|         path: 'fluxengine-testdata' | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: gmake | ||||
|       run: gmake -j2 -C fluxengine | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: FluxEngine.pkg | ||||
|         path: fluxengine.FluxEngine.pkg | ||||
|  | ||||
|   build-windows: | ||||
|     runs-on: windows-latest | ||||
| @@ -50,22 +64,32 @@ jobs: | ||||
|           mingw-w64-i686-zlib | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|     - uses: actions/checkout@v1 | ||||
|           vim | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|         path: 'fluxengine' | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine-testdata' | ||||
|         path: 'fluxengine-testdata' | ||||
|     - name: build | ||||
|       run: make | ||||
|       run: make -j2 -C fluxengine | ||||
|  | ||||
|     - name: nsis | ||||
|       run: | | ||||
|         cd fluxengine | ||||
|         strip fluxengine.exe -o fluxengine-stripped.exe | ||||
|         strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe | ||||
|         makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi | ||||
|  | ||||
|     - name: zip | ||||
|       run: | | ||||
|         zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe | ||||
|         cd fluxengine | ||||
|         zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine-windows.zip | ||||
|         path: fluxengine/fluxengine-windows.zip | ||||
|   | ||||
							
								
								
									
										5
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -29,11 +29,12 @@ jobs: | ||||
|           mingw-w64-i686-zlib | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - uses: actions/checkout@v3 | ||||
|  | ||||
|     - name: build | ||||
|       run: | | ||||
|         make | ||||
|         make -j2 | ||||
|  | ||||
|     - name: nsis | ||||
|       run: | | ||||
| @@ -83,7 +84,7 @@ jobs: | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: gmake | ||||
|  | ||||
|   | ||||
							
								
								
									
										104
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								Makefile
									
									
									
									
									
								
							| @@ -48,7 +48,7 @@ AR ?= $(CCPREFIX)ar | ||||
| PKG_CONFIG ?= pkg-config | ||||
| WX_CONFIG ?= wx-config | ||||
| PROTOC ?= protoc | ||||
| CFLAGS ?= -g -O0 | ||||
| CFLAGS ?= -g -O3 | ||||
| CXXFLAGS += -std=c++17 | ||||
| LDFLAGS ?= | ||||
| PLATFORM ?= UNIX | ||||
| @@ -77,6 +77,9 @@ define nl | ||||
|  | ||||
| endef | ||||
|  | ||||
| empty := | ||||
| space := $(empty) $(empty) | ||||
|  | ||||
| use-library = $(eval $(use-library-impl)) | ||||
| define use-library-impl | ||||
| $1: $(call $3_LIB) | ||||
| @@ -95,7 +98,7 @@ $(2): private CFLAGS += $(shell $(PKG_CONFIG) --cflags $(3)) | ||||
| endef | ||||
|  | ||||
| .PHONY: all binaries tests clean install install-bin | ||||
| all: binaries tests | ||||
| all: binaries tests docs | ||||
|  | ||||
| PROTOS = \ | ||||
| 	arch/aeslanier/aeslanier.proto \ | ||||
| @@ -111,6 +114,7 @@ PROTOS = \ | ||||
| 	arch/micropolis/micropolis.proto \ | ||||
| 	arch/mx/mx.proto \ | ||||
| 	arch/northstar/northstar.proto \ | ||||
| 	arch/rolandd20/rolandd20.proto \ | ||||
| 	arch/smaky6/smaky6.proto \ | ||||
| 	arch/tids990/tids990.proto \ | ||||
| 	arch/victor9k/victor9k.proto \ | ||||
| @@ -163,48 +167,86 @@ define do-encodedecodetest-impl | ||||
| tests: $(OBJDIR)/$1$3.flux.encodedecode | ||||
| $(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST .flux $1 $3 | ||||
| 	@echo ENCODEDECODETEST $1 flux $(FLUXENGINE_BIN) $2 $3 | ||||
| 	@scripts/encodedecodetest.sh $1 flux $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| tests: $(OBJDIR)/$1$3.scp.encodedecode | ||||
| $(OBJDIR)/$1$3.scp.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST .scp $1 $3 | ||||
| 	@echo ENCODEDECODETEST $1 scp $(FLUXENGINE_BIN) $2 $3 | ||||
| 	@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| endef | ||||
|  | ||||
| $(call do-encodedecodetest,agat) | ||||
| $(call do-encodedecodetest,amiga) | ||||
| $(call do-encodedecodetest,apple2) | ||||
| $(call do-encodedecodetest,atarist360) | ||||
| $(call do-encodedecodetest,atarist370) | ||||
| $(call do-encodedecodetest,atarist400) | ||||
| $(call do-encodedecodetest,atarist410) | ||||
| $(call do-encodedecodetest,atarist720) | ||||
| $(call do-encodedecodetest,atarist740) | ||||
| $(call do-encodedecodetest,atarist800) | ||||
| $(call do-encodedecodetest,atarist820) | ||||
| $(call do-encodedecodetest,bk800) | ||||
| $(call do-encodedecodetest,brother120) | ||||
| $(call do-encodedecodetest,brother240) | ||||
| $(call do-encodedecodetest,commodore1541,scripts/commodore1541_test.textpb,--35) | ||||
| $(call do-encodedecodetest,commodore1541,scripts/commodore1541_test.textpb,--40) | ||||
| $(call do-encodedecodetest,commodore1581) | ||||
| $(call do-encodedecodetest,cmd_fd2000) | ||||
| $(call do-encodedecodetest,hp9121) | ||||
| $(call do-encodedecodetest,ibm1200) | ||||
| $(call do-encodedecodetest,ibm1232) | ||||
| $(call do-encodedecodetest,ibm1440) | ||||
| $(call do-encodedecodetest,ibm180) | ||||
| $(call do-encodedecodetest,ibm360) | ||||
| $(call do-encodedecodetest,ibm720) | ||||
| $(call do-encodedecodetest,mac400,scripts/mac400_test.textpb) | ||||
| $(call do-encodedecodetest,mac800,scripts/mac800_test.textpb) | ||||
| $(call do-encodedecodetest,apple2,,--140) | ||||
| $(call do-encodedecodetest,atarist,,--360) | ||||
| $(call do-encodedecodetest,atarist,,--370) | ||||
| $(call do-encodedecodetest,atarist,,--400) | ||||
| $(call do-encodedecodetest,atarist,,--410) | ||||
| $(call do-encodedecodetest,atarist,,--720) | ||||
| $(call do-encodedecodetest,atarist,,--740) | ||||
| $(call do-encodedecodetest,atarist,,--800) | ||||
| $(call do-encodedecodetest,atarist,,--820) | ||||
| $(call do-encodedecodetest,bk) | ||||
| $(call do-encodedecodetest,brother,,--120) | ||||
| $(call do-encodedecodetest,brother,,--240) | ||||
| $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--171) | ||||
| $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--192) | ||||
| $(call do-encodedecodetest,commodore,,--800) | ||||
| $(call do-encodedecodetest,commodore,,--1620) | ||||
| $(call do-encodedecodetest,hplif,,--264) | ||||
| $(call do-encodedecodetest,hplif,,--616) | ||||
| $(call do-encodedecodetest,hplif,,--770) | ||||
| $(call do-encodedecodetest,ibm,,--1200) | ||||
| $(call do-encodedecodetest,ibm,,--1232) | ||||
| $(call do-encodedecodetest,ibm,,--1440) | ||||
| $(call do-encodedecodetest,ibm,,--1680) | ||||
| $(call do-encodedecodetest,ibm,,--180) | ||||
| $(call do-encodedecodetest,ibm,,--160) | ||||
| $(call do-encodedecodetest,ibm,,--320) | ||||
| $(call do-encodedecodetest,ibm,,--360) | ||||
| $(call do-encodedecodetest,ibm,,--720) | ||||
| $(call do-encodedecodetest,mac,scripts/mac400_test.textpb,--400) | ||||
| $(call do-encodedecodetest,mac,scripts/mac800_test.textpb,--800) | ||||
| $(call do-encodedecodetest,n88basic) | ||||
| $(call do-encodedecodetest,rx50) | ||||
| $(call do-encodedecodetest,tids990) | ||||
| $(call do-encodedecodetest,victor9k_ss) | ||||
| $(call do-encodedecodetest,victor9k_ds) | ||||
| $(call do-encodedecodetest,victor9k,,--612) | ||||
| $(call do-encodedecodetest,victor9k,,--1224) | ||||
|  | ||||
| do-corpustest = $(eval $(do-corpustest-impl)) | ||||
| define do-corpustest-impl | ||||
|  | ||||
| tests: $(OBJDIR)/corpustest/$2 | ||||
| $(OBJDIR)/corpustest/$2: $(FLUXENGINE_BIN) \ | ||||
| 		../fluxengine-testdata/data/$1 ../fluxengine-testdata/data/$2 | ||||
| 	@mkdir -p $(OBJDIR)/corpustest | ||||
| 	@echo CORPUSTEST $1 $2 $3 | ||||
| 	@$(FLUXENGINE_BIN) read $3 -s ../fluxengine-testdata/data/$1 -o $$@ > $$@.log | ||||
| 	@cmp $$@ ../fluxengine-testdata/data/$2 | ||||
|  | ||||
| endef | ||||
|  | ||||
| ifneq ($(wildcard ../fluxengine-testdata/data),) | ||||
|  | ||||
| $(call do-corpustest,amiga.flux,amiga.adf,amiga) | ||||
| $(call do-corpustest,atarist360.flux,atarist360.st,atarist --360) | ||||
| $(call do-corpustest,atarist720.flux,atarist720.st,atarist --720) | ||||
| $(call do-corpustest,brother120.flux,brother120.img,brother --120) | ||||
| $(call do-corpustest,cmd-fd2000.flux,cmd-fd2000.img,commodore --1620) | ||||
| $(call do-corpustest,ibm1232.flux,ibm1232.img,ibm --1232) | ||||
| $(call do-corpustest,ibm1440.flux,ibm1440.img,ibm --1440) | ||||
| $(call do-corpustest,mac800.flux,mac800.dsk,mac --800) | ||||
| $(call do-corpustest,micropolis315.flux,micropolis315.img,micropolis --315) | ||||
| $(call do-corpustest,northstar87-synthetic.flux,northstar87-synthetic.nsi,northstar --87 --drive.tpi=48) | ||||
| $(call do-corpustest,northstar175-synthetic.flux,northstar175-synthetic.nsi,northstar --175 --drive.tpi=48) | ||||
| $(call do-corpustest,northstar350-synthetic.flux,northstar350-synthetic.nsi,northstar --350 --drive.tpi=48) | ||||
| $(call do-corpustest,victor9k_ss.flux,victor9k_ss.img,victor9k --612) | ||||
| $(call do-corpustest,victor9k_ds.flux,victor9k_ds.img,victor9k --1224) | ||||
|  | ||||
| endif | ||||
|  | ||||
| $(OBJDIR)/%.a: | ||||
| 	@mkdir -p $(dir $@) | ||||
|   | ||||
							
								
								
									
										100
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								README.md
									
									
									
									
									
								
							| @@ -88,62 +88,60 @@ Which? | ||||
|  | ||||
| The current support state is as follows. | ||||
|  | ||||
| Dinosaurs (🦖) have yet to be observed in real life --- I've written the | ||||
| decoder based on Kryoflux (or other) dumps I've found. I don't (yet) have | ||||
| real, physical disks in my hand to test the capture process. | ||||
| Dinosaurs (🦖) have yet to be observed in real life --- I've written the encoder | ||||
| and/or decoder based on Kryoflux (or other) dumps I've found. I don't (yet) have | ||||
| real, physical disks in my hand to test the capture process, or hardware to | ||||
| verify that written disks work. | ||||
|  | ||||
| Unicorns (🦄) are completely real --- this means that I've read actual, | ||||
| physical disks with these formats and so know they work (or had reports from | ||||
| people who've had it work). | ||||
| Unicorns (🦄) are completely real --- this means that I've read actual, physical | ||||
| disks with these formats and/or written real, physical disks and then used them | ||||
| on real hardware, and so know they work (or had reports from people who've had | ||||
| it work). | ||||
|  | ||||
| ### Old disk formats | ||||
| If a filesystem is listed, this means that FluxEngine natively supports that | ||||
| particular filesystem and can read (and sometimes write, support varies) files | ||||
| directly from disks, flux files or disk images. Some formats have multiple | ||||
| choices because they can store multiple types of file system. | ||||
|  | ||||
| | Format                                    | Read? | Write? | Notes | | ||||
| |:------------------------------------------|:-----:|:------:|-------| | ||||
| | [IBM PC compatible](doc/disk-ibm.md)      |  🦄   |   🦄   | and compatibles (like the Atari ST) | | ||||
| | [Atari ST](doc/disk-atarist.md)           |  🦄   |   🦄   | technically the same as IBM, almost | | ||||
| | [Acorn ADFS](doc/disk-acornadfs.md)       |  🦄   |   🦖*  | single- and double- sided           | | ||||
| | [Acorn DFS](doc/disk-acorndfs.md)         |  🦄   |   🦖*  |                                     | | ||||
| | [Ampro Little Board](doc/disk-ampro.md)   |  🦖   |   🦖*  |                                     | | ||||
| | [Agat](doc/disk-agat.md)                  |  🦖   |        | Soviet Union Apple-II-like computer | | ||||
| | [Apple II](doc/disk-apple2.md)            |  🦄   |   🦄   |                                     | | ||||
| | [Amiga](doc/disk-amiga.md)                |  🦄   |   🦄   |                                     | | ||||
| | [Commodore 64 1541/1581](doc/disk-c64.md) |  🦄   |   🦄   | and probably the other formats      | | ||||
| | [Brother 120kB](doc/disk-brother.md)      |  🦄   |   🦄   |                                     | | ||||
| | [Brother 240kB](doc/disk-brother.md)      |  🦄   |   🦄   |                                     | | ||||
| | [Brother FB-100](doc/disk-fb100.md)       |  🦖   |        | Tandy Model 100, Husky Hunter, knitting machines | | ||||
| | [Elektronika BK](doc/disk-bd.md)          |  🦄   |   🦄   | Soviet Union PDP-11 clone           | | ||||
| | [Macintosh 400kB/800kB](doc/disk-macintosh.md)  |  🦄   |   🦄   |                                     | | ||||
| | [NEC PC-98](doc/disk-ibm.md)              |  🦄   |   🦄   | trimode drive not required          | | ||||
| | [Sharp X68000](doc/disk-ibm.md)           |  🦄   |   🦄   |                                     | | ||||
| | [Smaky 6](doc/disk-smaky6.md)             |  🦖   |       | 5.25" hard sectored | | ||||
| | [TRS-80](doc/disk-trs80.md)               |  🦖   |   🦖*  | a minor variation of the IBM scheme | | ||||
| <!-- FORMATSSTART --> | ||||
| <!-- This section is automatically generated. Do not edit. --> | ||||
|  | ||||
| | Profile | Format | Read? | Write? | Filesystem? | | ||||
| |:--------|:-------|:-----:|:------:|:------------| | ||||
| | [`acornadfs`](doc/disk-acornadfs.md) | Acorn ADFS: BBC Micro, Archimedes | 🦖 |  |  | | ||||
| | [`acorndfs`](doc/disk-acorndfs.md) | Acorn DFS: Acorn Atom, BBC Micro series | 🦄 |  | ACORNDFS  | | ||||
| | [`aeslanier`](doc/disk-aeslanier.md) | AES Lanier "No Problem": 616kB 5.25" 77-track SSDD hard sectored | 🦖 |  |  | | ||||
| | [`agat`](doc/disk-agat.md) | Agat: 840kB 5.25" 80-track DS | 🦖 | 🦖 |  | | ||||
| | [`amiga`](doc/disk-amiga.md) | Amiga: 880kB 3.5" DSDD | 🦄 | 🦄 | AMIGAFFS  | | ||||
| | [`ampro`](doc/disk-ampro.md) | Ampro Little Board: CP/M | 🦖 |  | CPMFS  | | ||||
| | [`apple2`](doc/disk-apple2.md) | Apple II: Prodos, Appledos, and CP/M | 🦄 | 🦄 | APPLEDOS CPMFS PRODOS  | | ||||
| | [`atarist`](doc/disk-atarist.md) | Atari ST: Almost PC compatible | 🦄 | 🦄 |  | | ||||
| | [`bk`](doc/disk-bk.md) | BK: 800kB 5.25"/3.5" 80-track 10-sector DSDD | 🦖 | 🦖 |  | | ||||
| | [`brother`](doc/disk-brother.md) | Brother word processors: GCR family | 🦄 | 🦄 | BROTHER120 FATFS  | | ||||
| | [`commodore`](doc/disk-commodore.md) | Commodore: 1541, 1581, 8050 and variations | 🦄 | 🦄 | CBMFS  | | ||||
| | [`eco1`](doc/disk-eco1.md) | VDS Eco1: CP/M; 1210kB 77-track mixed format DSHD | 🦖 |  | CPMFS  | | ||||
| | [`epsonpf10`](doc/disk-epsonpf10.md) | Epson PF-10: CP/M; 3.5" 40-track DSDD | 🦖 |  | CPMFS  | | ||||
| | [`f85`](doc/disk-f85.md) | Durango F85: 461kB 5.25" 77-track SS | 🦖 |  |  | | ||||
| | [`fb100`](doc/disk-fb100.md) | Brother FB-100: 100kB 3.5" 40-track SSSD | 🦖 |  |  | | ||||
| | [`hplif`](doc/disk-hplif.md) | Hewlett-Packard LIF: a variety of disk formats used by HP | 🦄 | 🦄 | LIF  | | ||||
| | [`ibm`](doc/disk-ibm.md) | IBM PC: Generic PC 3.5"/5.25" disks | 🦄 | 🦄 | FATFS  | | ||||
| | [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 |  | CPMFS  | | ||||
| | [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS  | | ||||
| | [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 |  | | ||||
| | [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 |  |  | | ||||
| | [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25"/3.5" 77-track 26-sector DSHD | 🦄 | 🦄 |  | | ||||
| | [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 |  | | ||||
| | [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE  | | ||||
| | [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦖 |  |  | | ||||
| | [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 |  | | ||||
| | [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 |  | SMAKY6  | | ||||
| | [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 |  | | ||||
| | [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M |  |  | CPMFS  | | ||||
| | [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 |  | | ||||
| | [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 |  |  | | ||||
| {: .datatable } | ||||
|  | ||||
| `*`: these formats are variations of the generic IBM format, and since the | ||||
| IBM writer is completely generic, it should be configurable for these | ||||
| formats... theoretically. I don't have the hardware to try it. | ||||
|  | ||||
| ### Even older disk formats | ||||
|  | ||||
| These formats are for particularly old, weird architectures, even by the | ||||
| standards of floppy disks. They've largely been implemented from single flux | ||||
| files with no access to physical hardware. Typically the reads were pretty | ||||
| bad and I've had to make a number of guesses as to how things work. They do, | ||||
| at least, check the CRC so what data's there is probably good. | ||||
|  | ||||
| | Format                                   | Read? | Write? | Notes | | ||||
| |:-----------------------------------------|:-----:|:------:|-------| | ||||
| | [AES Superplus / No Problem](doc/disk-aeslanier.md) |  🦖   | | hard sectors! | | ||||
| | [Durango F85](doc/disk-durangof85.md)    |  🦖   |        | 5.25" | | ||||
| | [DVK MX](doc/disk-mx.md)                 |  🦖   |        | Soviet PDP-11 clone | | ||||
| | [VDS Eco1](doc/disk-eco1.md)             |  🦖   |        | 8" mixed format | | ||||
| | [Micropolis](doc/disk-micropolis.md)     |  🦄   |        | Micropolis 100tpi drives | | ||||
| | [Northstar](doc/disk-northstar.md)       |  🦖   |   🦖   | 5.25" hard sectors | | ||||
| | [TI DS990 FD1000](doc/disk-tids990.md)   |  🦄   |  🦄    | 8" | | ||||
| | [Victor 9000](doc/disk-victor9k.md)      |  🦖   |        | 5.25" GCR encoded | | ||||
| | [Zilog MCZ](doc/disk-zilogmcz.md)        |  🦖   |        | 8" _and_ hard sectors | | ||||
| {: .datatable } | ||||
| <!-- FORMATSEND --> | ||||
|  | ||||
| ### Notes | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,16 @@ | ||||
|  | ||||
| #define AGAT_SECTOR_SIZE 256 | ||||
|  | ||||
| static constexpr uint64_t SECTOR_ID = 0x8924555549111444; | ||||
| static constexpr uint64_t DATA_ID = 0x8924555514444911; | ||||
|  | ||||
| class Encoder; | ||||
| class EncoderProto; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config); | ||||
|  | ||||
| extern uint8_t agatChecksum(const Bytes& bytes); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,19 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message AgatDecoderProto {} | ||||
|  | ||||
| message AgatEncoderProto { | ||||
| 	optional double target_clock_period_us = 1 | ||||
| 		[default=2.00, (help)="Data clock period of target format."]; | ||||
| 	optional double target_rotational_period_ms = 2 | ||||
| 		[default=200.0, (help)="Rotational period of target format."]; | ||||
| 	optional int32 post_index_gap_bytes = 3 | ||||
| 		[default=40, (help)="Post-index gap before first sector header."]; | ||||
| 	optional int32 pre_sector_gap_bytes = 4 | ||||
| 		[default=11, (help)="Gap before each sector header."]; | ||||
| 	optional int32 pre_data_gap_bytes = 5 | ||||
| 		[default=2, (help)="Gap before each sector data record."]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,10 +33,7 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| static const uint64_t SECTOR_ID = 0x8924555549111444; | ||||
| static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID); | ||||
|  | ||||
| static const uint64_t DATA_ID = 0x8924555514444911; | ||||
| static const FluxPattern DATA_PATTERN(64, DATA_ID); | ||||
|  | ||||
| static const FluxMatchers ALL_PATTERNS = { | ||||
|   | ||||
							
								
								
									
										118
									
								
								arch/agat/encoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								arch/agat/encoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/encoders/encoders.h" | ||||
| #include "agat.h" | ||||
| #include "lib/crc.h" | ||||
| #include "lib/readerwriter.h" | ||||
| #include "lib/image.h" | ||||
| #include "lib/layout.h" | ||||
| #include "arch/agat/agat.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
|  | ||||
| class AgatEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     AgatEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.agat()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void writeRawBits(uint64_t data, int width) | ||||
|     { | ||||
|         _cursor += width; | ||||
|         _lastBit = data & 1; | ||||
|         for (int i = 0; i < width; i++) | ||||
|         { | ||||
|             unsigned pos = _cursor - i - 1; | ||||
|             if (pos < _bits.size()) | ||||
|                 _bits[pos] = data & 1; | ||||
|             data >>= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void writeBytes(const Bytes& bytes) | ||||
|     { | ||||
|         encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
|     } | ||||
|  | ||||
|     void writeByte(uint8_t byte) | ||||
|     { | ||||
|         Bytes b; | ||||
|         b.writer().write_8(byte); | ||||
|         writeBytes(b); | ||||
|     } | ||||
|  | ||||
|     void writeFillerRawBytes(int count, uint16_t byte) | ||||
|     { | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeRawBits(byte, 16); | ||||
|     }; | ||||
|  | ||||
|     void writeFillerBytes(int count, uint8_t byte) | ||||
|     { | ||||
|         Bytes b{byte}; | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeBytes(b); | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         auto trackLayout = Layout::getLayoutOfTrack( | ||||
|             trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         double clockRateUs = _config.target_clock_period_us() / 2.0; | ||||
|         int bitsPerRevolution = | ||||
|             (_config.target_rotational_period_ms() * 1000.0) / clockRateUs; | ||||
|         _bits.resize(bitsPerRevolution); | ||||
|         _cursor = 0; | ||||
|  | ||||
|         writeFillerRawBytes(_config.post_index_gap_bytes(), 0xaaaa); | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|         { | ||||
|             /* Header */ | ||||
|  | ||||
|             writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa); | ||||
|             writeRawBits(SECTOR_ID, 64); | ||||
|             writeByte(0x5a); | ||||
|             writeByte((sector->logicalTrack << 1) | sector->logicalSide); | ||||
|             writeByte(sector->logicalSector); | ||||
|             writeByte(0x5a); | ||||
|  | ||||
|             /* Data */ | ||||
|  | ||||
|             writeFillerRawBytes(_config.pre_data_gap_bytes(), 0xaaaa); | ||||
|             auto data = sector->data.slice(0, AGAT_SECTOR_SIZE); | ||||
|             writeRawBits(DATA_ID, 64); | ||||
|             writeBytes(data); | ||||
|             writeByte(agatChecksum(data)); | ||||
|             writeByte(0x5a); | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         fillBitmapTo(_bits, _cursor, _bits.size(), {true, false}); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(_bits, | ||||
|             calculatePhysicalClockPeriod(_config.target_clock_period_us() * 1e3, | ||||
|                 _config.target_rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const AgatEncoderProto& _config; | ||||
|     uint32_t _cursor; | ||||
|     bool _lastBit; | ||||
|     std::vector<bool> _bits; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new AgatEncoder(config)); | ||||
| } | ||||
| @@ -2,7 +2,10 @@ syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Apple2DecoderProto {} | ||||
| message Apple2DecoderProto { | ||||
| 	optional uint32 side_one_track_offset = 1 | ||||
| 		[ default = 0, (help) = "offset to apply to track numbers on side 1" ]; | ||||
| } | ||||
|  | ||||
| message Apple2EncoderProto | ||||
| { | ||||
| @@ -13,4 +16,7 @@ message Apple2EncoderProto | ||||
|     /* Apple II disk drives spin at 300rpm. */ | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 200.0, (help) = "rotational period on the real device" ]; | ||||
|  | ||||
| 	optional uint32 side_one_track_offset = 3 | ||||
| 		[ default = 0, (help) = "offset to apply to track numbers on side 1" ]; | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "apple2.h" | ||||
| #include "arch/apple2/apple2.pb.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
| @@ -12,22 +14,25 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD); | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
| const FluxMatchers ANY_RECORD_PATTERN( | ||||
|     {&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN}); | ||||
|  | ||||
| static int decode_data_gcr(uint8_t gcr) | ||||
| { | ||||
|     switch (gcr) | ||||
|     { | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case gcr: return data; | ||||
| 		#include "data_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case gcr:                \ | ||||
|         return data; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan Woods | ||||
|  * and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan | ||||
|  * Woods and R. Belmont: | ||||
|  * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
|  */ | ||||
| static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
| { | ||||
| @@ -47,9 +52,11 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
|         { | ||||
|             /* 3 * 2 bit */ | ||||
|             output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02); | ||||
|             output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02); | ||||
|             output[i + 86] = | ||||
|                 ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02); | ||||
|             if ((i + 172) < APPLE2_SECTOR_LENGTH) | ||||
|                 output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02); | ||||
|                 output[i + 172] = | ||||
|                     ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -67,88 +74,102 @@ static uint8_t combine(uint16_t word) | ||||
| class Apple2Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	Apple2Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     Apple2Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw24() != APPLE2_SECTOR_RECORD) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw24() != APPLE2_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|         /* Read header. */ | ||||
|  | ||||
| 		auto header = toBytes(readRawBits(8*8)).slice(0, 8); | ||||
| 		ByteReader br(header); | ||||
|         auto header = toBytes(readRawBits(8 * 8)).slice(0, 8); | ||||
|         ByteReader br(header); | ||||
|  | ||||
| 		uint8_t volume = combine(br.read_be16()); | ||||
| 		_sector->logicalTrack = combine(br.read_be16()); | ||||
| 		_sector->logicalSector = combine(br.read_be16()); | ||||
| 		uint8_t checksum = combine(br.read_be16()); | ||||
|         uint8_t volume = combine(br.read_be16()); | ||||
|         _sector->logicalTrack = combine(br.read_be16()); | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = combine(br.read_be16()); | ||||
|         uint8_t checksum = combine(br.read_be16()); | ||||
|  | ||||
| 		// If the checksum is correct, upgrade the sector from MISSING | ||||
| 		// to DATA_MISSING in anticipation of its data record | ||||
| 		if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|         // If the checksum is correct, upgrade the sector from MISSING | ||||
|         // to DATA_MISSING in anticipation of its data record | ||||
|         if (checksum == | ||||
|             (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|  | ||||
|         if (_sector->logicalSide == 1) | ||||
|             _sector->logicalTrack -= _config.apple2().side_one_track_offset(); | ||||
|  | ||||
|         /* Sanity check. */ | ||||
|  | ||||
|         if (_sector->logicalTrack > 100) | ||||
|         { | ||||
|             _sector->status = Sector::MISSING; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		/* Check ID. */ | ||||
|     { | ||||
|         /* Check ID. */ | ||||
|  | ||||
| 		if (readRaw24() != APPLE2_DATA_RECORD) | ||||
| 			return; | ||||
|         if (readRaw24() != APPLE2_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and | ||||
| 		// the data itself.  This has been seen on real world disks | ||||
| 		// such as the Apple II Operating System Kit from Apple2Online. | ||||
| 		// However, I haven't seen it described in any of the various | ||||
| 		// references. | ||||
| 		// | ||||
| 		// This extra '0' bit would not affect the real disk interface, | ||||
| 		// as it was a '1' reaching the top bit of a shift register | ||||
| 		// that triggered a byte to be available, but it affects the | ||||
| 		// way the data is read here. | ||||
| 		// | ||||
| 		// While the floppies tested only seemed to need this applied | ||||
| 		// to the first byte of the data record, applying it | ||||
| 		// consistently to all of them doesn't seem to hurt, and | ||||
| 		// simplifies the code. | ||||
|         // Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and | ||||
|         // the data itself.  This has been seen on real world disks | ||||
|         // such as the Apple II Operating System Kit from Apple2Online. | ||||
|         // However, I haven't seen it described in any of the various | ||||
|         // references. | ||||
|         // | ||||
|         // This extra '0' bit would not affect the real disk interface, | ||||
|         // as it was a '1' reaching the top bit of a shift register | ||||
|         // that triggered a byte to be available, but it affects the | ||||
|         // way the data is read here. | ||||
|         // | ||||
|         // While the floppies tested only seemed to need this applied | ||||
|         // to the first byte of the data record, applying it | ||||
|         // consistently to all of them doesn't seem to hurt, and | ||||
|         // simplifies the code. | ||||
|  | ||||
| 		/* Read and decode data. */ | ||||
|         /* Read and decode data. */ | ||||
|  | ||||
| 		auto readApple8 = [&]() { | ||||
| 		    auto result = 0; | ||||
| 		    while((result & 0x80) == 0) { | ||||
| 			auto b = readRawBits(1); | ||||
|                         if(b.empty()) break; | ||||
| 			result = (result << 1) | b[0]; | ||||
| 		    } | ||||
| 		    return result; | ||||
| 		}; | ||||
|         auto readApple8 = [&]() | ||||
|         { | ||||
|             auto result = 0; | ||||
|             while ((result & 0x80) == 0) | ||||
|             { | ||||
|                 auto b = readRawBits(1); | ||||
|                 if (b.empty()) | ||||
|                     break; | ||||
|                 result = (result << 1) | b[0]; | ||||
|             } | ||||
|             return result; | ||||
|         }; | ||||
|  | ||||
| 		constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH+2; | ||||
|                 uint8_t bytes[recordLength]; | ||||
|                 for(auto &byte : bytes) { | ||||
|                     byte = readApple8(); | ||||
|                 } | ||||
|         constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2; | ||||
|         uint8_t bytes[recordLength]; | ||||
|         for (auto& byte : bytes) | ||||
|         { | ||||
|             byte = readApple8(); | ||||
|         } | ||||
|  | ||||
| 		// Upgrade the sector from MISSING to BAD_CHECKSUM. | ||||
| 		// If decode_crazy_data succeeds, it upgrades the sector to | ||||
| 		// OK. | ||||
| 		_sector->status = Sector::BAD_CHECKSUM; | ||||
| 		_sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
| 	} | ||||
|         // Upgrade the sector from MISSING to BAD_CHECKSUM. | ||||
|         // If decode_crazy_data succeeds, it upgrades the sector to | ||||
|         // OK. | ||||
|         _sector->status = Sector::BAD_CHECKSUM; | ||||
|         _sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Apple2Decoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new Apple2Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -56,8 +56,7 @@ public: | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 _config.clock_period_us() * 1e3, | ||||
|             calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
| @@ -132,13 +131,17 @@ private: | ||||
|             // extra padding. | ||||
|             write_ff40(sector.logicalSector == 0 ? 32 : 8); | ||||
|  | ||||
|             int track = sector.logicalTrack; | ||||
|             if (sector.logicalSide == 1) | ||||
|                 track += _config.side_one_track_offset(); | ||||
|  | ||||
|             // Write address field: APPLE2_SECTOR_RECORD + sector identifier + | ||||
|             // DE AA EB | ||||
|             write_bits(APPLE2_SECTOR_RECORD, 24); | ||||
|             write_gcr44(volume_id); | ||||
|             write_gcr44(sector.logicalTrack); | ||||
|             write_gcr44(track); | ||||
|             write_gcr44(sector.logicalSector); | ||||
|             write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector); | ||||
|             write_gcr44(volume_id ^ track ^ sector.logicalSector); | ||||
|             write_bits(0xDEAAEB, 24); | ||||
|  | ||||
|             // Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector | ||||
|   | ||||
| @@ -2,6 +2,7 @@ LIBARCH_SRCS = \ | ||||
| 	arch/aeslanier/decoder.cc \ | ||||
| 	arch/agat/agat.cc \ | ||||
| 	arch/agat/decoder.cc \ | ||||
| 	arch/agat/encoder.cc \ | ||||
| 	arch/amiga/amiga.cc \ | ||||
| 	arch/amiga/decoder.cc \ | ||||
| 	arch/amiga/encoder.cc \ | ||||
| @@ -23,6 +24,7 @@ LIBARCH_SRCS = \ | ||||
| 	arch/mx/decoder.cc \ | ||||
| 	arch/northstar/decoder.cc \ | ||||
| 	arch/northstar/encoder.cc \ | ||||
| 	arch/rolandd20/decoder.cc \ | ||||
| 	arch/smaky6/decoder.cc \ | ||||
| 	arch/tids990/decoder.cc \ | ||||
| 	arch/tids990/encoder.cc \ | ||||
|   | ||||
| @@ -147,6 +147,7 @@ public: | ||||
|         _sector->logicalSide = br.read_8(); | ||||
|         _sector->logicalSector = br.read_8(); | ||||
|         _currentSectorSize = 1 << (br.read_8() + 7); | ||||
|  | ||||
|         uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos)); | ||||
|         uint16_t wantCrc = br.read_be16(); | ||||
|         if (wantCrc == gotCrc) | ||||
| @@ -206,6 +207,18 @@ public: | ||||
|         uint16_t wantCrc = br.read_be16(); | ||||
|         _sector->status = | ||||
|             (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|  | ||||
|         auto layout = Layout::getLayoutOfTrack( | ||||
|             _sector->logicalTrack, _sector->logicalSide); | ||||
|         if (_currentSectorSize != layout->sectorSize) | ||||
|             std::cerr << fmt::format( | ||||
|                 "Warning: configured sector size for t{}.h{}.s{} is {} bytes " | ||||
|                 "but that seen on disk is {} bytes\n", | ||||
|                 _sector->logicalTrack, | ||||
|                 _sector->logicalSide, | ||||
|                 _sector->logicalSector, | ||||
|                 layout->sectorSize, | ||||
|                 _currentSectorSize); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|   | ||||
| @@ -16,14 +16,14 @@ static bool lastBit; | ||||
| static double clockRateUsForTrack(unsigned track) | ||||
| { | ||||
|     if (track < 16) | ||||
|         return 2.623; | ||||
|         return 2.63; | ||||
|     if (track < 32) | ||||
|         return 2.861; | ||||
|         return 2.89; | ||||
|     if (track < 48) | ||||
|         return 3.148; | ||||
|         return 3.20; | ||||
|     if (track < 64) | ||||
|         return 3.497; | ||||
|     return 3.934; | ||||
|         return 3.57; | ||||
|     return 3.98; | ||||
| } | ||||
|  | ||||
| static unsigned sectorsForTrack(unsigned track) | ||||
|   | ||||
| @@ -19,6 +19,6 @@ message MicropolisEncoderProto { | ||||
|     optional double clock_period_us = 1 | ||||
|         [ default = 2.0, (help) = "clock rate on the real device" ]; | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 166.0, (help) = "rotational period on the real device" ]; | ||||
|         [ default = 200.0, (help) = "rotational period on the real device" ]; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										56
									
								
								arch/rolandd20/decoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								arch/rolandd20/decoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/crc.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/decoders/fluxmapreader.h" | ||||
| #include "lib/sector.h" | ||||
| #include "lib/bytes.h" | ||||
| #include "rolandd20.h" | ||||
| #include <string.h> | ||||
|  | ||||
| /* Sector header record: | ||||
|  * | ||||
|  * BF FF FF FF FF FF FE AB | ||||
|  * | ||||
|  * This encodes to: | ||||
|  * | ||||
|  *    e    d    5    5    5    5    5    5 | ||||
|  * 1110 1101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    5    5    5    5    5 | ||||
|  * 0101 0101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    5    5    5    5    5 | ||||
|  * 0101 0101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    4    4    4    4    5 | ||||
|  * 0101 0101 0101 0100 0100 0100 0100 0101 | ||||
|  */ | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL); | ||||
|  | ||||
| class RolandD20Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	RolandD20Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto rawbits = readRawBits(256); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits); | ||||
| 		fmt::print("{} ", _sector->clock); | ||||
| 		hexdump(std::cout, bytes); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new RolandD20Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										4
									
								
								arch/rolandd20/rolandd20.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								arch/rolandd20/rolandd20.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| #pragma once | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config); | ||||
|  | ||||
							
								
								
									
										5
									
								
								arch/rolandd20/rolandd20.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								arch/rolandd20/rolandd20.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message RolandD20DecoderProto {} | ||||
|  | ||||
|  | ||||
| @@ -55,6 +55,7 @@ proto_cc_library { | ||||
| 		"./arch/micropolis/micropolis.proto", | ||||
| 		"./arch/mx/mx.proto", | ||||
| 		"./arch/northstar/northstar.proto", | ||||
| 		"./arch/rolandd20/rolandd20.proto", | ||||
| 		"./arch/tids990/tids990.proto", | ||||
| 		"./arch/victor9k/victor9k.proto", | ||||
| 		"./arch/zilogmcz/zilogmcz.proto", | ||||
| @@ -93,6 +94,7 @@ clibrary { | ||||
| 		"./arch/mx/decoder.cc", | ||||
| 		"./arch/northstar/decoder.cc", | ||||
| 		"./arch/northstar/encoder.cc", | ||||
| 		"./arch/rolandd20/rolandd20.cc", | ||||
| 		"./arch/tids990/decoder.cc", | ||||
| 		"./arch/tids990/encoder.cc", | ||||
| 		"./arch/victor9k/decoder.cc", | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| cmake_minimum_required (VERSION 2.8.11) | ||||
| cmake_minimum_required (VERSION 3.10.0) | ||||
|  | ||||
| # Fix behavior of CMAKE_CXX_STANDARD when targeting macOS. | ||||
| if (POLICY CMP0025) | ||||
| @@ -18,7 +18,7 @@ endif () | ||||
| project (libusbp) | ||||
|  | ||||
| set (LIBUSBP_VERSION_MAJOR 1) | ||||
| set (LIBUSBP_VERSION_MINOR 2) | ||||
| set (LIBUSBP_VERSION_MINOR 3) | ||||
| set (LIBUSBP_VERSION_PATCH 0) | ||||
|  | ||||
| # Make 'Release' be the default build type, since the debug builds | ||||
| @@ -49,28 +49,8 @@ set(VBOX_LINUX_ON_WINDOWS FALSE CACHE BOOL | ||||
| set(ENABLE_GCOV FALSE CACHE BOOL | ||||
|   "Compile with special options needed for gcov.") | ||||
|  | ||||
| # Our C code uses features from the C99 standard. | ||||
| macro(use_c99) | ||||
|   if (CMAKE_VERSION VERSION_LESS "3.1") | ||||
|     if (CMAKE_C_COMPILER_ID STREQUAL "GNU") | ||||
|       set (CMAKE_C_FLAGS "--std=gnu99 ${CMAKE_C_FLAGS}") | ||||
|     endif () | ||||
|   else () | ||||
|     set (CMAKE_C_STANDARD 99) | ||||
|   endif () | ||||
| endmacro(use_c99) | ||||
|  | ||||
| # Our C++ code uses features from the C++11 standard. | ||||
| macro(use_cxx11) | ||||
|   if (CMAKE_VERSION VERSION_LESS "3.1") | ||||
|     if (CMAKE_C_COMPILER_ID STREQUAL "GNU") | ||||
|       # Use --std=gnu++0x instead of --std=gnu++11 in order to support GCC 4.6. | ||||
|       set (CMAKE_CXX_FLAGS "--std=gnu++0x ${CMAKE_C_FLAGS}") | ||||
|     endif () | ||||
|   else () | ||||
|     set (CMAKE_CXX_STANDARD 11) | ||||
|   endif () | ||||
| endmacro(use_cxx11) | ||||
| set (CMAKE_C_STANDARD 99) | ||||
| set (CMAKE_CXX_STANDARD 11) | ||||
|  | ||||
| set (LIBUSBP_VERSION ${LIBUSBP_VERSION_MAJOR}.${LIBUSBP_VERSION_MINOR}.${LIBUSBP_VERSION_PATCH}) | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| # libusbp: Pololu USB Library | ||||
|  | ||||
| Version: 1.2.0<br/> | ||||
| Release date: 2020-11-16<br/> | ||||
| [www.pololu.com](https://www.pololu.com/) | ||||
|  | ||||
| The **Pololu USB Library** (also known as **libusbp**) is a cross-platform C library for accessing USB devices. | ||||
| @@ -17,7 +15,7 @@ The **Pololu USB Library** (also known as **libusbp**) is a cross-platform C lib | ||||
| - Provides detailed error information to the caller. | ||||
|   - Each error includes one or more English sentences describing the error, including error codes from underlying APIs. | ||||
|   - Some errors have libusbp-defined error codes that can be used to programmatically decide how to handle the error. | ||||
| - Provides an object-oriented C++ wrapper (using features of C++11). | ||||
| - Provides an object-oriented C++ wrapper. | ||||
| - Provides access to underlying identifiers, handles, and file descriptors. | ||||
|  | ||||
|  | ||||
| @@ -139,9 +137,9 @@ If you are using GCC and a shell that supports Bash-like syntax, here is an exam | ||||
|  | ||||
|     gcc program.c `pkg-config --cflags --libs libusbp-1` | ||||
|  | ||||
| Here is an equivalent command for C++.  Note that we use the `--std=gnu++11` option because the libusbp C++ API requires features from C++11: | ||||
| Here is an equivalent command for C++: | ||||
|  | ||||
|     g++ --std=gnu++11 program.cpp `pkg-config --cflags --libs libusbp-1` | ||||
|     g++ program.cpp `pkg-config --cflags --libs libusbp-1` | ||||
|  | ||||
| The order of the arguments above matters: the user program must come before libusbp because it relies on symbols that are defined by libusbp. | ||||
|  | ||||
| @@ -167,6 +165,9 @@ For detailed documentation of this library, see the header files `libusb.h` and | ||||
|  | ||||
| ## Version history | ||||
|  | ||||
| * 1.3.0 (2023-01-02): | ||||
|   * Windows: Added support for detecting FTDI serial ports.  (FTDI devices with more than one port have not been tested and the interface for detecting them might change in the future.) | ||||
|   * macOS: Fixed the detection of serial ports for devices that are not CDC ACM. | ||||
| * 1.2.0 (2020-11-16): | ||||
|   * Linux: Made the library work with devices attached to the cp210x driver. | ||||
|   * Windows: Made the library work with devices that have lowercase letters in their hardware IDs. | ||||
|   | ||||
| @@ -1,2 +1,3 @@ | ||||
| This was taken from https://github.com/pololu/libusbp on 2021-12-11. | ||||
| This is version 1.3.0 taken from https://github.com/pololu/libusbp on | ||||
| 2023-05-06. | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(async_in async_in.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(lsport lsport.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(lsusb lsusb.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(port_name port_name.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -41,7 +41,6 @@ extern "C" { | ||||
| #ifdef LIBUSBP_STATIC | ||||
| #  define LIBUSBP_API | ||||
| #else | ||||
| #error not static | ||||
| #  ifdef LIBUSBP_EXPORTS | ||||
| #    define LIBUSBP_API LIBUSBP_DLL_EXPORT | ||||
| #  else | ||||
|   | ||||
| @@ -107,7 +107,7 @@ namespace libusbp | ||||
|     { | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer. */ | ||||
|         explicit unique_pointer_wrapper(T * p = nullptr) noexcept | ||||
|         explicit unique_pointer_wrapper(T * p = NULL) noexcept | ||||
|             : pointer(p) | ||||
|         { | ||||
|         } | ||||
| @@ -133,9 +133,9 @@ namespace libusbp | ||||
|  | ||||
|         /*! Implicit conversion to bool.  Returns true if the underlying pointer | ||||
|          *  is not NULL. */ | ||||
|         explicit operator bool() const noexcept | ||||
|         operator bool() const noexcept | ||||
|         { | ||||
|             return pointer != nullptr; | ||||
|             return pointer != NULL; | ||||
|         } | ||||
|  | ||||
|         /*! Returns the underlying pointer. */ | ||||
| @@ -146,19 +146,19 @@ namespace libusbp | ||||
|  | ||||
|         /*! Sets the underlying pointer to the specified value, freeing the | ||||
|          * previous pointer and taking ownership of the specified one. */ | ||||
|         void pointer_reset(T * p = nullptr) noexcept | ||||
|         void pointer_reset(T * p = NULL) noexcept | ||||
|         { | ||||
|             pointer_free(pointer); | ||||
|             pointer = p; | ||||
|         } | ||||
|  | ||||
|         /*! Releases the pointer, transferring ownership of it to the caller and | ||||
|          * resetting the underlying pointer of this object to nullptr.  The caller | ||||
|          * resetting the underlying pointer of this object to NULL.  The caller | ||||
|          * is responsible for freeing the returned pointer if it is not NULL. */ | ||||
|         T * pointer_release() noexcept | ||||
|         { | ||||
|             T * p = pointer; | ||||
|             pointer = nullptr; | ||||
|             pointer = NULL; | ||||
|             return p; | ||||
|         } | ||||
|  | ||||
| @@ -193,14 +193,14 @@ namespace libusbp | ||||
|     { | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer. */ | ||||
|         explicit unique_pointer_wrapper_with_copy(T * p = nullptr) noexcept | ||||
|         explicit unique_pointer_wrapper_with_copy(T * p = NULL) noexcept | ||||
|             : unique_pointer_wrapper<T>(p) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /*! Move constructor. */ | ||||
|         unique_pointer_wrapper_with_copy( | ||||
|             unique_pointer_wrapper_with_copy && other) noexcept = default; | ||||
|             unique_pointer_wrapper_with_copy && other) = default; | ||||
|  | ||||
|         /*! Copy constructor */ | ||||
|         unique_pointer_wrapper_with_copy( | ||||
| @@ -228,13 +228,14 @@ namespace libusbp | ||||
|     { | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer.  */ | ||||
|         explicit error(libusbp_error * p = nullptr) noexcept | ||||
|         explicit error(libusbp_error * p = NULL) noexcept | ||||
|             : unique_pointer_wrapper_with_copy(p) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /*! Wrapper for libusbp_error_get_message(). */ | ||||
|         const char * what() const noexcept override { | ||||
|         virtual const char * what() const noexcept | ||||
|         { | ||||
|             return libusbp_error_get_message(pointer); | ||||
|         } | ||||
|  | ||||
| @@ -255,7 +256,7 @@ namespace libusbp | ||||
|     /*! \cond */ | ||||
|     inline void throw_if_needed(libusbp_error * err) | ||||
|     { | ||||
|         if (err != nullptr) | ||||
|         if (err != NULL) | ||||
|         { | ||||
|             throw error(err); | ||||
|         } | ||||
| @@ -267,7 +268,7 @@ namespace libusbp | ||||
|     { | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer. */ | ||||
|         explicit async_in_pipe(libusbp_async_in_pipe * pointer = nullptr) | ||||
|         explicit async_in_pipe(libusbp_async_in_pipe * pointer = NULL) | ||||
|             : unique_pointer_wrapper(pointer) | ||||
|         { | ||||
|         } | ||||
| @@ -303,8 +304,8 @@ namespace libusbp | ||||
|         bool handle_finished_transfer(void * buffer, size_t * transferred, | ||||
|             error * transfer_error) | ||||
|         { | ||||
|             libusbp_error ** error_out = nullptr; | ||||
|             if (transfer_error != nullptr) | ||||
|             libusbp_error ** error_out = NULL; | ||||
|             if (transfer_error != NULL) | ||||
|             { | ||||
|                 transfer_error->pointer_reset(); | ||||
|                 error_out = transfer_error->pointer_to_pointer_get(); | ||||
| @@ -328,7 +329,7 @@ namespace libusbp | ||||
|     { | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer. */ | ||||
|         explicit device(libusbp_device * pointer = nullptr) : | ||||
|         explicit device(libusbp_device * pointer = NULL) : | ||||
|             unique_pointer_wrapper_with_copy(pointer) | ||||
|         { | ||||
|         } | ||||
| @@ -387,7 +388,7 @@ namespace libusbp | ||||
|         std::vector<device> vector; | ||||
|         for(size_t i = 0; i < size; i++) | ||||
|         { | ||||
|             vector.emplace_back(device_list[i]); | ||||
|             vector.push_back(device(device_list[i])); | ||||
|         } | ||||
|         libusbp_list_free(device_list); | ||||
|         return vector; | ||||
| @@ -408,13 +409,13 @@ namespace libusbp | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer.  This object will free the pointer | ||||
|          *  when it is destroyed. */ | ||||
|         explicit generic_interface(libusbp_generic_interface * pointer = nullptr) | ||||
|         explicit generic_interface(libusbp_generic_interface * pointer = NULL) | ||||
|             : unique_pointer_wrapper_with_copy(pointer) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /*! Wrapper for libusbp_generic_interface_create. */ | ||||
|         explicit generic_interface(const device & device, | ||||
|         generic_interface(const device & device, | ||||
|             uint8_t interface_number = 0, bool composite = false) | ||||
|         { | ||||
|             throw_if_needed(libusbp_generic_interface_create( | ||||
| @@ -448,13 +449,13 @@ namespace libusbp | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer.  This object will free the pointer | ||||
|          *  when it is destroyed. */ | ||||
|         explicit generic_handle(libusbp_generic_handle * pointer = nullptr) noexcept | ||||
|         explicit generic_handle(libusbp_generic_handle * pointer = NULL) noexcept | ||||
|             : unique_pointer_wrapper(pointer) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /*! Wrapper for libusbp_generic_handle_open(). */ | ||||
|         explicit generic_handle(const generic_interface & gi) | ||||
|         generic_handle(const generic_interface & gi) | ||||
|         { | ||||
|             throw_if_needed(libusbp_generic_handle_open(gi.pointer_get(), &pointer)); | ||||
|         } | ||||
| @@ -486,9 +487,9 @@ namespace libusbp | ||||
|             uint8_t bRequest, | ||||
|             uint16_t wValue, | ||||
|             uint16_t wIndex, | ||||
|             void * buffer = nullptr, | ||||
|             void * buffer = NULL, | ||||
|             uint16_t wLength = 0, | ||||
|             size_t * transferred = nullptr) | ||||
|             size_t * transferred = NULL) | ||||
|         { | ||||
|             throw_if_needed(libusbp_control_transfer(pointer, | ||||
|                 bmRequestType, bRequest, wValue, wIndex, | ||||
| @@ -542,13 +543,13 @@ namespace libusbp | ||||
|     public: | ||||
|         /*! Constructor that takes a pointer.  This object will free the pointer | ||||
|          *  when it is destroyed. */ | ||||
|         explicit serial_port(libusbp_serial_port * pointer = nullptr) | ||||
|         explicit serial_port(libusbp_serial_port * pointer = NULL) | ||||
|             : unique_pointer_wrapper_with_copy(pointer) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /*! Wrapper for libusbp_serial_port_create(). */ | ||||
|         explicit serial_port(const device & device, | ||||
|         serial_port(const device & device, | ||||
|             uint8_t interface_number = 0, bool composite = false) | ||||
|         { | ||||
|             throw_if_needed(libusbp_serial_port_create( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_c99() | ||||
|  | ||||
| add_library (install_helper SHARED install_helper_windows.c dll.def) | ||||
|  | ||||
| target_link_libraries (install_helper setupapi msi) | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(test_async_in test_async_in.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(test_long_read test_long_read.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(test_long_write test_long_write.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_cxx11() | ||||
|  | ||||
| add_executable(test_transitions test_transitions.cpp) | ||||
|  | ||||
| include_directories ( | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use_c99() | ||||
|  | ||||
| # Settings for GCC | ||||
| if (CMAKE_C_COMPILER_ID STREQUAL "GNU") | ||||
|   # By default, symbols are not visible outside of the library. | ||||
|   | ||||
| @@ -124,7 +124,7 @@ libusbp_error * error_add_v(libusbp_error * error, const char * format, va_list | ||||
|         int result = vsnprintf(x, 0, format, ap2); | ||||
|         if (result > 0) | ||||
|         { | ||||
|             outer_message_length = (size_t) result; | ||||
|             outer_message_length = result; | ||||
|         } | ||||
|         va_end(ap2); | ||||
|     } | ||||
|   | ||||
| @@ -37,7 +37,10 @@ libusbp_error * libusbp_find_device_with_vid_pid( | ||||
|  | ||||
|     libusbp_device ** new_list = NULL; | ||||
|     size_t size = 0; | ||||
|     error = libusbp_list_connected_devices(&new_list, &size); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = libusbp_list_connected_devices(&new_list, &size); | ||||
|     } | ||||
|  | ||||
|     assert(error != NULL || new_list != NULL); | ||||
|  | ||||
|   | ||||
| @@ -37,6 +37,7 @@ | ||||
| #include <usbioctl.h> | ||||
| #include <stringapiset.h> | ||||
| #include <winusb.h> | ||||
| #include <ntddmodm.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef __linux__ | ||||
|   | ||||
| @@ -51,11 +51,14 @@ libusbp_error * async_in_transfer_create( | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     // Allocate memory for the transfer struct. | ||||
|     async_in_transfer * new_transfer = calloc(1, sizeof(async_in_transfer)); | ||||
|  | ||||
|     if (new_transfer == NULL) | ||||
|     async_in_transfer * new_transfer = NULL; | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = &error_no_memory; | ||||
|         new_transfer = calloc(1, sizeof(async_in_transfer)); | ||||
|         if (new_transfer == NULL) | ||||
|         { | ||||
|             error = &error_no_memory; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Allocate memory for the buffer. | ||||
|   | ||||
| @@ -25,9 +25,14 @@ libusbp_error * create_device(io_service_t service, libusbp_device ** device) | ||||
|     assert(service != MACH_PORT_NULL); | ||||
|     assert(device != NULL); | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     // Allocate the device. | ||||
|     libusbp_device * new_device = NULL; | ||||
|     libusbp_error * error = device_allocate(&new_device); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = device_allocate(&new_device); | ||||
|     } | ||||
|  | ||||
|     // Get the numeric IDs. | ||||
|     if (error == NULL) | ||||
| @@ -84,7 +89,10 @@ libusbp_error * libusbp_device_copy(const libusbp_device * source, libusbp_devic | ||||
|  | ||||
|     // Allocate the device. | ||||
|     libusbp_device * new_device = NULL; | ||||
|     error = device_allocate(&new_device); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = device_allocate(&new_device); | ||||
|     } | ||||
|  | ||||
|     // Copy the simple fields, while leaving the pointers owned by the | ||||
|     // device NULL so that libusbp_device_free is still OK to call. | ||||
|   | ||||
| @@ -63,9 +63,8 @@ static libusbp_error * process_pipe_properties(libusbp_generic_handle * handle) | ||||
|         uint8_t transfer_type; | ||||
|         uint16_t max_packet_size; | ||||
|         uint8_t interval; | ||||
|         kr = (*handle->ioh)->GetPipeProperties(handle->ioh, (UInt8) i, | ||||
|           &direction, &endpoint_number, &transfer_type, &max_packet_size, &interval); | ||||
|  | ||||
|         kern_return_t kr = (*handle->ioh)->GetPipeProperties(handle->ioh, i, | ||||
|             &direction, &endpoint_number, &transfer_type, &max_packet_size, &interval); | ||||
|         if (kr != KERN_SUCCESS) | ||||
|         { | ||||
|             return error_create_mach(kr, "Failed to get pipe properties for pipe %d.", i); | ||||
| @@ -75,11 +74,11 @@ static libusbp_error * process_pipe_properties(libusbp_generic_handle * handle) | ||||
|         { | ||||
|             if (direction) | ||||
|             { | ||||
|                 handle->in_pipe_index[endpoint_number] = (uint8_t) i; | ||||
|                 handle->in_pipe_index[endpoint_number] = i; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 handle->out_pipe_index[endpoint_number] = (uint8_t) i; | ||||
|                 handle->out_pipe_index[endpoint_number] = i; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -96,11 +95,14 @@ static libusbp_error * set_configuration(io_service_t service) | ||||
|     // Turn io_service_t into something we can actually use. | ||||
|     IOUSBDeviceInterface ** dev_handle = NULL; | ||||
|     IOCFPlugInInterface ** plug_in = NULL; | ||||
|     error = service_to_interface(service, | ||||
|         kIOUSBDeviceUserClientTypeID, | ||||
|         CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID197), | ||||
|         (void **)&dev_handle, | ||||
|         &plug_in); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = service_to_interface(service, | ||||
|             kIOUSBDeviceUserClientTypeID, | ||||
|             CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID197), | ||||
|             (void **)&dev_handle, | ||||
|             &plug_in); | ||||
|     } | ||||
|  | ||||
|     uint8_t config_num = 0; | ||||
|     if (error == NULL) | ||||
| @@ -170,7 +172,10 @@ static libusbp_error * set_configuration_and_get_service( | ||||
|  | ||||
|     // Get an io_service_t for the physical device. | ||||
|     io_service_t device_service = MACH_PORT_NULL; | ||||
|     error = service_get_from_id(device_id, &device_service); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = service_get_from_id(device_id, &device_service); | ||||
|     } | ||||
|  | ||||
|     // Set the configruation to 1 if it is not set. | ||||
|     if (error == NULL) | ||||
| @@ -209,11 +214,13 @@ libusbp_error * libusbp_generic_handle_open( | ||||
|  | ||||
|     // Allocate memory for the handle. | ||||
|     libusbp_generic_handle * new_handle = NULL; | ||||
|     new_handle = calloc(1, sizeof(libusbp_generic_handle)); | ||||
|  | ||||
|     if (new_handle == NULL) | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = &error_no_memory; | ||||
|         new_handle = calloc(1, sizeof(libusbp_generic_handle)); | ||||
|         if (new_handle == NULL) | ||||
|         { | ||||
|             error = &error_no_memory; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Get the io_service_t representing the IOUSBInterface. | ||||
| @@ -323,11 +330,14 @@ libusbp_error * libusbp_generic_handle_set_timeout( | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     error = check_pipe_id(pipe_id); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = check_pipe_id(pipe_id); | ||||
|     } | ||||
|  | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER; | ||||
|         uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER; | ||||
|  | ||||
|         if (pipe_id & 0x80) | ||||
|         { | ||||
| @@ -401,7 +411,7 @@ libusbp_error * libusbp_read_pipe( | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     if (size == 0) | ||||
|     if (error == NULL && size == 0) | ||||
|     { | ||||
|         error = error_create("Transfer size 0 is not allowed."); | ||||
|     } | ||||
| @@ -423,12 +433,12 @@ libusbp_error * libusbp_read_pipe( | ||||
|  | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER; | ||||
|         uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER; | ||||
|         uint32_t no_data_timeout = 0; | ||||
|         uint32_t completion_timeout = handle->in_timeout[endpoint_number]; | ||||
|         uint32_t iokit_size = (uint32_t) size; | ||||
|         uint32_t iokit_size = size; | ||||
|         uint32_t pipe_index = handle->in_pipe_index[endpoint_number]; | ||||
|         kern_return_t kr = (*handle->ioh)->ReadPipeTO(handle->ioh, (UInt8) pipe_index, | ||||
|         kern_return_t kr = (*handle->ioh)->ReadPipeTO(handle->ioh, pipe_index, | ||||
|           buffer, &iokit_size, no_data_timeout, completion_timeout); | ||||
|         if (transferred != NULL) { *transferred = iokit_size; } | ||||
|         if (kr != KERN_SUCCESS) | ||||
| @@ -464,7 +474,7 @@ libusbp_error * libusbp_write_pipe( | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     if (size > UINT32_MAX) | ||||
|     if (error == NULL && size > UINT32_MAX) | ||||
|     { | ||||
|         error = error_create("Transfer size is too large."); | ||||
|     } | ||||
| @@ -481,12 +491,12 @@ libusbp_error * libusbp_write_pipe( | ||||
|  | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER; | ||||
|         uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER; | ||||
|         uint32_t no_data_timeout = 0; | ||||
|         uint32_t completion_timeout = handle->out_timeout[endpoint_number]; | ||||
|         uint32_t pipe_index = handle->out_pipe_index[endpoint_number]; | ||||
|         kern_return_t kr = (*handle->ioh)->WritePipeTO(handle->ioh, (UInt8) pipe_index, | ||||
|           (void *)buffer, (UInt32) size, no_data_timeout, completion_timeout); | ||||
|         kern_return_t kr = (*handle->ioh)->WritePipeTO(handle->ioh, pipe_index, | ||||
|           (void *)buffer, size, no_data_timeout, completion_timeout); | ||||
|         if (kr != KERN_SUCCESS) | ||||
|         { | ||||
|             error = error_create_mach(kr, ""); | ||||
| @@ -588,7 +598,7 @@ IOUSBInterfaceInterface182 ** generic_handle_get_ioh(const libusbp_generic_handl | ||||
|  | ||||
| uint8_t generic_handle_get_pipe_index(const libusbp_generic_handle * handle, uint8_t pipe_id) | ||||
| { | ||||
|     uint8_t endpoint_number = pipe_id & (uint8_t) MAX_ENDPOINT_NUMBER; | ||||
|     uint8_t endpoint_number = pipe_id & MAX_ENDPOINT_NUMBER; | ||||
|     if (pipe_id & 0x80) | ||||
|     { | ||||
|         return handle->in_pipe_index[endpoint_number]; | ||||
|   | ||||
| @@ -82,7 +82,10 @@ libusbp_error * libusbp_generic_interface_create( | ||||
|     { | ||||
|         // Get an io_service_t for the physical device. | ||||
|         io_service_t device_service = MACH_PORT_NULL; | ||||
|         error = service_get_from_id(new_gi->device_id, &device_service); | ||||
|         if (error == NULL) | ||||
|         { | ||||
|             error = service_get_from_id(new_gi->device_id, &device_service); | ||||
|         } | ||||
|  | ||||
|         // Get the io_service_t for the interface. | ||||
|         io_service_t interface_service = MACH_PORT_NULL; | ||||
| @@ -146,7 +149,10 @@ libusbp_error * libusbp_generic_interface_copy( | ||||
|  | ||||
|     // Allocate the generic interface. | ||||
|     libusbp_generic_interface * new_gi = NULL; | ||||
|     error = generic_interface_allocate(&new_gi); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = generic_interface_allocate(&new_gi); | ||||
|     } | ||||
|  | ||||
|     // Copy the simple fields. | ||||
|     if (error == NULL) | ||||
|   | ||||
| @@ -42,12 +42,14 @@ libusbp_error * service_get_usb_interface(io_service_t service, | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     io_iterator_t iterator = MACH_PORT_NULL; | ||||
|     kern_return_t result = IORegistryEntryGetChildIterator( | ||||
|         service, kIOServicePlane, &iterator); | ||||
|  | ||||
|     if (result != KERN_SUCCESS) | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = error_create_mach(result, "Failed to get child iterator."); | ||||
|         kern_return_t result = IORegistryEntryGetChildIterator( | ||||
|             service, kIOServicePlane, &iterator); | ||||
|         if (result != KERN_SUCCESS) | ||||
|         { | ||||
|             error = error_create_mach(result, "Failed to get child iterator."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Loop through the devices to find the right one. | ||||
| @@ -57,7 +59,7 @@ libusbp_error * service_get_usb_interface(io_service_t service, | ||||
|         if (candidate == MACH_PORT_NULL) { break; } | ||||
|  | ||||
|         // Filter out candidates that are not of class IOUSBInterface. | ||||
|         bool conforms = (bool) IOObjectConformsTo(candidate, kIOUSBInterfaceClassName); | ||||
|         bool conforms = IOObjectConformsTo(candidate, kIOUSBInterfaceClassName); | ||||
|         if (!conforms) | ||||
|         { | ||||
|             IOObjectRelease(candidate); | ||||
| @@ -88,54 +90,6 @@ libusbp_error * service_get_usb_interface(io_service_t service, | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| libusbp_error * service_get_child_by_class(io_service_t service, | ||||
|     const char * class_name, io_service_t * interface_service) | ||||
| { | ||||
|     assert(service != MACH_PORT_NULL); | ||||
|     assert(interface_service != NULL); | ||||
|  | ||||
|     *interface_service = MACH_PORT_NULL; | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     io_iterator_t iterator = MACH_PORT_NULL; | ||||
|     kern_return_t result = IORegistryEntryCreateIterator( | ||||
|         service, kIOServicePlane, kIORegistryIterateRecursively, &iterator); | ||||
|  | ||||
|     if (result != KERN_SUCCESS) | ||||
|     { | ||||
|         error = error_create_mach(result, "Failed to get recursive iterator."); | ||||
|     } | ||||
|  | ||||
|     // Loop through the devices to find the right one. | ||||
|     while (error == NULL) | ||||
|     { | ||||
|         io_service_t candidate = IOIteratorNext(iterator); | ||||
|         if (candidate == MACH_PORT_NULL) { break; } | ||||
|  | ||||
|         // Filter out candidates that are not the right class. | ||||
|         bool conforms = (bool) IOObjectConformsTo(candidate, class_name); | ||||
|         if (!conforms) | ||||
|         { | ||||
|             IOObjectRelease(candidate); | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // This is the right one.  Pass it to the caller. | ||||
|         *interface_service = candidate; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     if (error == NULL && *interface_service == MACH_PORT_NULL) | ||||
|     { | ||||
|         error = error_create("Could not find entry with class %s.", class_name); | ||||
|         error = error_add_code(error, LIBUSBP_ERROR_NOT_READY); | ||||
|     } | ||||
|  | ||||
|     if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); } | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| libusbp_error * service_to_interface( | ||||
|     io_service_t service, | ||||
|     CFUUIDRef pluginType, | ||||
| @@ -154,13 +108,15 @@ libusbp_error * service_to_interface( | ||||
|  | ||||
|     // Create the plug-in interface. | ||||
|     IOCFPlugInInterface ** new_plug_in = NULL; | ||||
|     kern_return_t kr = IOCreatePlugInInterfaceForService(service, | ||||
|         pluginType, kIOCFPlugInInterfaceID, | ||||
|         &new_plug_in, &score); | ||||
|  | ||||
|     if (kr != KERN_SUCCESS) | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = error_create_mach(kr, "Failed to create plug-in interface."); | ||||
|         kern_return_t kr = IOCreatePlugInInterfaceForService(service, | ||||
|             pluginType, kIOCFPlugInInterfaceID, | ||||
|             &new_plug_in, &score); | ||||
|         if (kr != KERN_SUCCESS) | ||||
|         { | ||||
|             error = error_create_mach(kr, "Failed to create plug-in interface."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Create the device interface and pass it to the caller. | ||||
| @@ -223,7 +179,7 @@ libusbp_error * get_string(io_registry_entry_t entry, CFStringRef name, char ** | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     if (CFGetTypeID(cf_value) != CFStringGetTypeID()) | ||||
|     if (error == NULL && CFGetTypeID(cf_value) != CFStringGetTypeID()) | ||||
|     { | ||||
|         error = error_create("Property is not a string."); | ||||
|     } | ||||
| @@ -244,7 +200,7 @@ libusbp_error * get_string(io_registry_entry_t entry, CFStringRef name, char ** | ||||
|         error = string_copy(buffer, value); | ||||
|     } | ||||
|  | ||||
|     CFRelease(cf_value); | ||||
|     if (cf_value != NULL) { CFRelease(cf_value); } | ||||
|     return error; | ||||
| } | ||||
|  | ||||
| @@ -258,11 +214,14 @@ libusbp_error * get_int32(io_registry_entry_t entry, CFStringRef name, int32_t * | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     CFTypeRef cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0); | ||||
|  | ||||
|     if (cf_value == NULL) | ||||
|     CFTypeRef cf_value = NULL; | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = error_create("Failed to get int32 property from IORegistryEntry."); | ||||
|         cf_value = IORegistryEntryCreateCFProperty(entry, name, kCFAllocatorDefault, 0); | ||||
|         if (cf_value == NULL) | ||||
|         { | ||||
|             error = error_create("Failed to get int32 property from IORegistryEntry."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (error == NULL && CFGetTypeID(cf_value) != CFNumberGetTypeID()) | ||||
| @@ -292,13 +251,16 @@ libusbp_error * get_uint16(io_registry_entry_t entry, CFStringRef name, uint16_t | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     int32_t tmp; | ||||
|     error = get_int32(entry, name, &tmp); | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = get_int32(entry, name, &tmp); | ||||
|     } | ||||
|  | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         // There is an unchecked conversion of an int32_t to a uint16_t here but | ||||
|         // There is an implicit conversion of an int32_t to a uint16_t here but | ||||
|         // we don't expect any data to be lost. | ||||
|         *value = (uint16_t) tmp; | ||||
|         *value = tmp; | ||||
|     } | ||||
|  | ||||
|     return error; | ||||
|   | ||||
| @@ -37,10 +37,13 @@ libusbp_error * libusbp_list_connected_devices( | ||||
|     // Create a dictionary that says "IOProviderClass" => "IOUSBDevice" | ||||
|     // This dictionary is CFReleased by IOServiceGetMatchingServices. | ||||
|     CFMutableDictionaryRef dict = NULL; | ||||
|     dict = IOServiceMatching("IOUSBHostDevice"); | ||||
|     if (dict == NULL) | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = error_create("IOServiceMatching returned null."); | ||||
|         dict = IOServiceMatching("IOUSBHostDevice"); | ||||
|         if (dict == NULL) | ||||
|         { | ||||
|             error = error_create("IOServiceMatching returned null."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Create an iterator for all the connected USB devices. | ||||
|   | ||||
| @@ -2,9 +2,6 @@ | ||||
|  | ||||
| struct libusbp_serial_port | ||||
| { | ||||
|     // The I/O Registry ID of the IOBSDSerialClient. | ||||
|     uint64_t id; | ||||
|  | ||||
|     // A port filename like "/dev/cu.usbmodemFD123". | ||||
|     char * port_name; | ||||
| }; | ||||
| @@ -29,23 +26,16 @@ libusbp_error * libusbp_serial_port_create( | ||||
|         return error_create("Device is null."); | ||||
|     } | ||||
|  | ||||
|     // Add one to the interface number because that is what we need for the | ||||
|     // typical case: The user specifies the lower of the two interface numbers, | ||||
|     // which corresponds to the control interface of a CDC ACM device.  We | ||||
|     // actually need the data interface because that is the one that the | ||||
|     // IOSerialBSDClient lives under.  If this +1 causes any problems, it is | ||||
|     // easy for the user to address it using an an ifdef.  Also, we might make | ||||
|     // this function more flexible in the future if we need to handle different | ||||
|     // types of serial devices with different drivers or interface layouts. | ||||
|     interface_number += 1; | ||||
|  | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     libusbp_serial_port * new_port = calloc(1, sizeof(libusbp_serial_port)); | ||||
|  | ||||
|     if (new_port == NULL) | ||||
|     libusbp_serial_port * new_port = NULL; | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = &error_no_memory; | ||||
|         new_port = calloc(1, sizeof(libusbp_serial_port)); | ||||
|         if (new_port == NULL) | ||||
|         { | ||||
|             error = &error_no_memory; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Get the ID for the physical device. | ||||
| @@ -62,19 +52,67 @@ libusbp_error * libusbp_serial_port_create( | ||||
|         error = service_get_from_id(device_id, &device_service); | ||||
|     } | ||||
|  | ||||
|     // Get an io_service_t for the interface. | ||||
|     io_service_t interface_service = MACH_PORT_NULL; | ||||
|     io_iterator_t iterator = MACH_PORT_NULL; | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = service_get_usb_interface(device_service, interface_number, &interface_service); | ||||
|         kern_return_t result = IORegistryEntryCreateIterator( | ||||
|             device_service, kIOServicePlane, kIORegistryIterateRecursively, &iterator); | ||||
|         if (result != KERN_SUCCESS) | ||||
|         { | ||||
|             error = error_create_mach(result, "Failed to get recursive iterator."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Get an io_service_t for the IOSerialBSDClient | ||||
|     io_service_t serial_service = MACH_PORT_NULL; | ||||
|     if (error == NULL) | ||||
|     int32_t current_interface = -1; | ||||
|     int32_t last_acm_control_interface_with_no_port = -1; | ||||
|     int32_t last_acm_data_interface = -1; | ||||
|     while (error == NULL) | ||||
|     { | ||||
|         error = service_get_child_by_class(interface_service, | ||||
|             kIOSerialBSDServiceValue, &serial_service); | ||||
|         io_service_t service = IOIteratorNext(iterator); | ||||
|         if (service == MACH_PORT_NULL) { break; } | ||||
|  | ||||
|         if (IOObjectConformsTo(service, kIOUSBHostInterfaceClassName)) | ||||
|         { | ||||
|             error = get_int32(service, CFSTR("bInterfaceNumber"), ¤t_interface); | ||||
|         } | ||||
|         else if (IOObjectConformsTo(service, "AppleUSBACMControl")) | ||||
|         { | ||||
|             last_acm_control_interface_with_no_port = current_interface; | ||||
|         } | ||||
|         else if (IOObjectConformsTo(service, "AppleUSBACMData")) | ||||
|         { | ||||
|             last_acm_data_interface = current_interface; | ||||
|         } | ||||
|         else if (IOObjectConformsTo(service, kIOSerialBSDServiceValue)) | ||||
|         { | ||||
|           int32_t fixed_interface = current_interface; | ||||
|           if (last_acm_data_interface == current_interface && | ||||
|               last_acm_control_interface_with_no_port >= 0) | ||||
|           { | ||||
|               // We found an ACM control interface with no serial port, then | ||||
|               // an ACM data interface with a serial port.  For consistency with | ||||
|               // other operating systems, we will consider this serial port to | ||||
|               // actually be associated with the control interface instead of the | ||||
|               // data interface. | ||||
|               fixed_interface = last_acm_control_interface_with_no_port; | ||||
|           } | ||||
|           last_acm_control_interface_with_no_port = -1; | ||||
|  | ||||
|           if (fixed_interface == interface_number) | ||||
|           { | ||||
|               // We found the serial port the user is looking for. | ||||
|               serial_service = service; | ||||
|               break; | ||||
|           } | ||||
|         } | ||||
|         IOObjectRelease(service); | ||||
|     } | ||||
|  | ||||
|     if (error == NULL && serial_service == MACH_PORT_NULL) | ||||
|     { | ||||
|         error = error_create("Could not find entry with class IOSerialBSDClient."); | ||||
|         error = error_add_code(error, LIBUSBP_ERROR_NOT_READY); | ||||
|     } | ||||
|  | ||||
|     // Get the port name. | ||||
| @@ -91,7 +129,7 @@ libusbp_error * libusbp_serial_port_create( | ||||
|     } | ||||
|  | ||||
|     if (serial_service != MACH_PORT_NULL) { IOObjectRelease(serial_service); } | ||||
|     if (interface_service != MACH_PORT_NULL) { IOObjectRelease(interface_service); } | ||||
|     if (iterator != MACH_PORT_NULL) { IOObjectRelease(iterator); } | ||||
|     if (device_service != MACH_PORT_NULL) { IOObjectRelease(device_service); } | ||||
|     libusbp_serial_port_free(new_port); | ||||
|  | ||||
| @@ -125,11 +163,14 @@ libusbp_error * libusbp_serial_port_copy(const libusbp_serial_port * source, | ||||
|     libusbp_error * error = NULL; | ||||
|  | ||||
|     // Allocate memory for the new object. | ||||
|     libusbp_serial_port * new_port = calloc(1, sizeof(libusbp_serial_port)); | ||||
|  | ||||
|     if (new_port == NULL) | ||||
|     libusbp_serial_port * new_port = NULL; | ||||
|     if (error == NULL) | ||||
|     { | ||||
|         error = &error_no_memory; | ||||
|         new_port = calloc(1, sizeof(libusbp_serial_port)); | ||||
|         if (new_port == NULL) | ||||
|         { | ||||
|             error = &error_no_memory; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Copy the port name. | ||||
|   | ||||
| @@ -113,42 +113,79 @@ static libusbp_error * get_interface_composite( | ||||
|         return error; | ||||
|     } | ||||
|  | ||||
|     // Get a list of all the USB-related devices. | ||||
|     HDEVINFO new_list = SetupDiGetClassDevs(NULL, "USB", NULL, | ||||
|         DIGCF_ALLCLASSES | DIGCF_PRESENT); | ||||
|     if (new_list == INVALID_HANDLE_VALUE) | ||||
|     { | ||||
|         return error_create_winapi( | ||||
|             "Failed to get list of all USB devices while finding an interface."); | ||||
|     } | ||||
|     unsigned int list_index = 0; | ||||
|     HDEVINFO new_list = INVALID_HANDLE_VALUE; | ||||
|     DWORD i = 0; | ||||
|  | ||||
|     // Iterate through the list until we find a device whose | ||||
|     // Iterate through various device lists until we find a device whose | ||||
|     // parent device is ours and which controls the interface | ||||
|     // specified by the caller. | ||||
|     for (DWORD i = 0; ; i++) | ||||
|  | ||||
|     while (true) | ||||
|     { | ||||
|         if (new_list == INVALID_HANDLE_VALUE) | ||||
|         { | ||||
|             if (list_index == 0) | ||||
|             { | ||||
|                 // Get a list of all the USB-related devices. | ||||
|                 // It includes native USB interfaces and usbser.sys ports but | ||||
|                 // not FTDI ports. | ||||
|                 new_list = SetupDiGetClassDevs(NULL, | ||||
|                   "USB", NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT); | ||||
|             } | ||||
|             else if (list_index == 1) | ||||
|             { | ||||
|                 // Get a list of all the COM port devices. | ||||
|                 // This includes FTDI and usbser.sys ports. | ||||
|                 new_list = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, | ||||
|                   NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); | ||||
|             } | ||||
|             else if (list_index == 2) | ||||
|             { | ||||
|                 // Get a list of all modem devices. | ||||
|                 // Rationale: https://github.com/pyserial/pyserial/commit/7bb1dcc5aea16ca1c957690cb5276df33af1c286 | ||||
|                 new_list = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MODEM, | ||||
|                   NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // Could not find the child interface in any list. | ||||
|                 // This could be a temporary condition. | ||||
|                 libusbp_error * error = error_create("Could not find interface %d.", | ||||
|                     interface_number); | ||||
|                 error = error_add_code(error, LIBUSBP_ERROR_NOT_READY); | ||||
|                 return error; | ||||
|             } | ||||
|  | ||||
|             if (new_list == INVALID_HANDLE_VALUE) | ||||
|             { | ||||
|                 return error_create_winapi( | ||||
|                     "Failed to list devices to find an interface (%u).", | ||||
|                     list_index); | ||||
|             } | ||||
|             i = 0; | ||||
|         } | ||||
|  | ||||
|         SP_DEVINFO_DATA device_info_data; | ||||
|         device_info_data.cbSize = sizeof(SP_DEVINFO_DATA); | ||||
|         bool success = SetupDiEnumDeviceInfo(new_list, i, &device_info_data); | ||||
|         if (!success) | ||||
|         { | ||||
|             libusbp_error * error; | ||||
|  | ||||
|             if (GetLastError() == ERROR_NO_MORE_ITEMS) | ||||
|             { | ||||
|                 // Could not find the child interface.  This could be | ||||
|                 // a temporary condition. | ||||
|                 error = error_create("Could not find interface %d.", | ||||
|                     interface_number); | ||||
|                 error = error_add_code(error, LIBUSBP_ERROR_NOT_READY); | ||||
|                 // This list is done.  Try the next list. | ||||
|                 SetupDiDestroyDeviceInfoList(new_list); | ||||
|                 new_list = INVALID_HANDLE_VALUE; | ||||
|                 list_index++; | ||||
|                 continue; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 error = error_create_winapi( | ||||
|                     "Failed to get device info while finding an interface."); | ||||
|                 libusbp_error * error = error_create_winapi( | ||||
|                     "Failed to get device info to find an interface."); | ||||
|                 SetupDiDestroyDeviceInfoList(new_list); | ||||
|                 return error; | ||||
|             } | ||||
|             SetupDiDestroyDeviceInfoList(new_list); | ||||
|             return error; | ||||
|         } | ||||
|  | ||||
|         DEVINST parent_dev_inst; | ||||
| @@ -162,6 +199,7 @@ static libusbp_error * get_interface_composite( | ||||
|         if (parent_dev_inst != dev_inst) | ||||
|         { | ||||
|             // This device is not a child of our device. | ||||
|             i++; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
| @@ -179,9 +217,21 @@ static libusbp_error * get_interface_composite( | ||||
|         unsigned int actual_interface_number; | ||||
|         int result = sscanf(device_id, "USB\\VID_%*4x&PID_%*4x&MI_%2x\\", | ||||
|             &actual_interface_number); | ||||
|         if (result != 1 || actual_interface_number != interface_number) | ||||
|         if (result != 1) | ||||
|         { | ||||
|           result = sscanf(device_id, "FTDIBUS\\%*[^\\]\\%x", | ||||
|             &actual_interface_number); | ||||
|           if (result != 1) | ||||
|           { | ||||
|             // Could not figure out the interface number. | ||||
|             i++; | ||||
|             continue; | ||||
|           } | ||||
|         } | ||||
|         if (actual_interface_number != interface_number) | ||||
|         { | ||||
|             // This is not the right interface. | ||||
|             i++; | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -124,6 +124,26 @@ libusbp_error * libusbp_serial_port_create( | ||||
|     libusbp_string_free(usb_device_id); | ||||
|     libusbp_serial_port_free(new_sp); | ||||
|  | ||||
|     // FTDI devices like the FT232RL aren't actually composite but they look | ||||
|     // like it on Windows because the serial port device is a child of the USB | ||||
|     // device.  On Linux and macOS, those devices can be detected with | ||||
|     // composite=false (or composite=true and interface_number=0). | ||||
|     // This workaround allows that to work on Windows too, and it might make | ||||
|     // this API easier to use for some non-FTDI devices too. | ||||
|     if (error && !composite) | ||||
|     { | ||||
|         libusbp_error * error2 = libusbp_serial_port_create(device, 0, true, port); | ||||
|         if (error2) | ||||
|         { | ||||
|             libusbp_error_free(error2); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             libusbp_error_free(error); | ||||
|             return NULL; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return error; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| INCLUDE (CheckIncludeFileCXX) | ||||
|  | ||||
| # If catch.hpp is not present, we want to simply skip compiling the tests.  This | ||||
| # allows someone to compile and install libusbp without having catch installed. | ||||
| # The header can either be installed in this directory or in a standard system | ||||
| # location. | ||||
| # If catch.hpp is not present, we want to simply skip compiling the tests. | ||||
| # Download catch.hpp and put it in this directory: | ||||
| # https://raw.githubusercontent.com/catchorg/Catch2/v2.x/single_include/catch2/catch.hpp | ||||
| set (CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}") | ||||
| CHECK_INCLUDE_FILE_CXX (catch.hpp HAVE_CATCH_FRAMEWORK) | ||||
| if (NOT HAVE_CATCH_FRAMEWORK) | ||||
| @@ -11,8 +10,6 @@ if (NOT HAVE_CATCH_FRAMEWORK) | ||||
|   return () | ||||
| endif () | ||||
|  | ||||
| use_cxx11 () | ||||
|  | ||||
| set(USE_TEST_DEVICE_A FALSE CACHE BOOL | ||||
|   "Run tests that require Test Device A.") | ||||
|  | ||||
|   | ||||
| @@ -486,6 +486,9 @@ TEST_CASE("async_in_pipe for an interrupt endpoint") | ||||
|         // ms, a three-packet transfer will quickly receive those two packets | ||||
|         // and then keep waiting for more. | ||||
|  | ||||
|         // Previous versions of macOS returned one packet instead of two, | ||||
|         // but this is no longer true on Darwin Kernel 22.1.0 (Oct 2022). | ||||
|  | ||||
|         // Pause the ADC for 100 ms. | ||||
|         handle.control_transfer(0x40, 0xA0, 100, 0); | ||||
|  | ||||
| @@ -508,9 +511,6 @@ TEST_CASE("async_in_pipe for an interrupt endpoint") | ||||
|  | ||||
|                 #if defined(VBOX_LINUX_ON_WINDOWS) | ||||
|                 CHECK(transferred == 0); | ||||
|                 #elif defined(__APPLE__) | ||||
|                 CHECK(transferred == transfer_size); | ||||
|                 CHECK(buffer[4] == 0xAB); | ||||
|                 #else | ||||
|                 CHECK(transferred == transfer_size * 2); | ||||
|                 CHECK(buffer[4] == 0xAB); | ||||
| @@ -662,9 +662,10 @@ TEST_CASE("async_in_pipe for an interrupt endpoint") | ||||
|         expected_message = "Asynchronous IN transfer failed.  " | ||||
|             "Incorrect function.  Windows error code 0x1."; | ||||
|         #elif defined(__linux__) | ||||
|         // This request results in an error in Linux but it is only detected | ||||
|         // after some data is transferred. | ||||
|         expected_transferred = transfer_size + 1; | ||||
|         // This request results in an error in Linux after some data is transferred. | ||||
|         // On some older Linux systems, expected_transferred was transfer_size + 1. | ||||
|         // With Linux 5.15 on a Raspberry Pi 4, expected_transferred is transfer_size. | ||||
|         expected_transferred = transfer_size; | ||||
|         expected_message = "Asynchronous IN transfer failed.  " | ||||
|             "The transfer overflowed.  Error code 75."; | ||||
|         #elif defined(__APPLE__) | ||||
|   | ||||
							
								
								
									
										4
									
								
								doc/disk-40track_drive.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								doc/disk-40track_drive.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Adjust configuration for a 40-track drive | ||||
|  | ||||
| (This format has no documentation. Please file a bug.) | ||||
| @@ -1,23 +1,14 @@ | ||||
| Disk: Acorn ADFS | ||||
| ================ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # BBC Micro, Archimedes | ||||
|  | ||||
| Acorn ADFS disks are pretty standard MFM encoded IBM scheme disks, although | ||||
| with different sector sizes and with the 0-based sector identifiers rather | ||||
| than 1-based sector identifiers. The index hole is ignored and sectors are | ||||
| written whereever, requiring FluxEngine to do two revolutions to read a | ||||
| disk. | ||||
| Acorn ADFS disks are used by the 6502-based BBC Micro and ARM-based Archimedes | ||||
| series of computers. They are yet another variation on MFM encoded IBM scheme | ||||
| disks, although with different sector sizes and with the 0-based sector | ||||
| identifiers rather than 1-based sector identifiers. The index hole is ignored | ||||
| and sectors are written whereever, requiring FluxEngine to do two revolutions | ||||
| to read a disk. | ||||
|  | ||||
| There are various different kinds, which should all work out of the box. | ||||
| Tested ones are: | ||||
|  | ||||
|   - ADFS L: 80 track, 16 sector, 2 sides, 256 bytes per sector == 640kB. | ||||
|   - ADFE D/E: 80 track, 5 sector, 2 sides, 1024 bytes per sector == 800kB. | ||||
|   - ADFS F: 80 track, 10 sector, 2 sides, 1024 bytes per sector == 1600kB. | ||||
|  | ||||
| I expect the others to work, but haven't tried them; [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new) if you have any | ||||
| news. For ADFS S (single-sided 40 track) you'll want `--heads 0 --cylinders | ||||
| 0-79x2`. For ADFS M (single-sided 80 track) you'll want `--heads 0`. | ||||
|  | ||||
| Be aware that Acorn logical block numbering goes all the way up side 0 and | ||||
| then all the way up side 1. However, FluxEngine uses traditional disk images | ||||
| @@ -25,15 +16,7 @@ with alternating sides, with the blocks from track 0 side 0 then track 0 side | ||||
| 1 then track 1 side 0 etc. Most Acorn emulators will use both formats, but | ||||
| they might require nudging as the side order can't be reliably autodetected. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read acornadfs | ||||
| ``` | ||||
|  | ||||
| You should end up with an `acornadfs.img` of the appropriate size for your disk | ||||
| format. This is an alias for `fluxengine read ibm` with preconfigured | ||||
| parameters. | ||||
|   | ||||
| @@ -1,34 +1,22 @@ | ||||
| Disk: Acorn DFS | ||||
| =============== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Acorn Atom, BBC Micro series | ||||
|  | ||||
| Acorn DFS disks are pretty standard FM encoded IBM scheme disks, with | ||||
| 256-sectors and 0-based sector identifiers. There's nothing particularly | ||||
| special here. | ||||
| Acorn DFS disks are used by the Acorn Atom and BBC Micro series of computers. | ||||
| They are pretty standard FM encoded IBM scheme disks, with 256-sectors and | ||||
| 0-based sector identifiers. There's nothing particularly special here. | ||||
|  | ||||
| DFS disks are all single-sided, but allow the other side of the disk to be | ||||
| used as another drive. FluxEngine supports these; read one side at a time | ||||
| with `--heads 0` or `--heads 1`. | ||||
| used as another volume. | ||||
|  | ||||
| DFS comes in two varieties, 40 track and 80 track. These should both work.  For | ||||
| 40 track you'll want `--cylinders 0-79x2`. Some rare disks are both at the same | ||||
| time. FluxEngine can read these but it requires a bit of fiddling as they have | ||||
| the same tracks on twice. | ||||
| They come in two varieties, 40 track and 80 track. These should both work. | ||||
| Some rare disks are both at the same time. FluxEngine can read these but it | ||||
| requires a bit of fiddling as they have the same tracks on twice. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read acorndfs | ||||
| ``` | ||||
| ## References | ||||
|  | ||||
| You should end up with an `acorndfs.img` of the appropriate size for your disk | ||||
| format. This is an alias for `fluxengine read ibm` with preconfigured | ||||
| parameters. | ||||
|  | ||||
| References | ||||
| ---------- | ||||
|  | ||||
|   - [The Acord DFS disc format](https://beebwiki.mdfs.net/Acorn_DFS_disc_format) | ||||
|   - [The Acorn DFS disc format](https://beebwiki.mdfs.net/Acorn_DFS_disc_format) | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| Disk: AES Lanier word processor | ||||
| =============================== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 616kB 5.25" 77-track SSDD hard sectored | ||||
|  | ||||
| Back in 1980 Lanier released a series of very early integrated word processor | ||||
| appliances, the No Problem. These were actually [rebranded AES Data Superplus | ||||
| machines](http://vintagecomputers.site90.net/aes/). They were gigantic, | ||||
| weighed 40kg, and one example I've found cost £13,000 in 1981 (the equivalent | ||||
| of nearly £50,000 in 2018!). | ||||
| weighed 40kg, and one example I've found cost ꆲ5bb4 | ||||
| of nearly ㇢1a6e | ||||
|  | ||||
| 8080 machines with 32kB of RAM, they ran their own proprietary word | ||||
| processing software off twin 5.25" drive units, but apparently other software | ||||
| @@ -27,18 +27,14 @@ disk image, and I've had to make a lot of guesses as to the sector format | ||||
| based on what looks right. If anyone knows _anything_ about these disks, | ||||
| [please get in touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read aeslanier | ||||
| ``` | ||||
| ## References | ||||
|  | ||||
| You'll end up with an `aeslanier.img` file. | ||||
|   * [SA800 Diskette Storage Drive - Theory Of | ||||
|     Operations](http://www.hartetechnologies.com/manuals/Shugart/50664-1_SA800_TheorOp_May78.pdf): | ||||
|     talks about MMFM a lot, but the Lanier machines didn't use this disk | ||||
|     format. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   * [SA800 Diskette Storage Drive - Theory Of Operations](http://www.hartetechnologies.com/manuals/Shugart/50664-1_SA800_TheorOp_May78.pdf): talks about MMFM a lot, but the Lanier machines didn't use this disk format. | ||||
|   | ||||
| @@ -1,34 +1,24 @@ | ||||
| Disk: Agat | ||||
| ========== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 840kB 5.25" 80-track DS | ||||
|  | ||||
| The Agat (Russian: Агат) was a series Soviet-era computer, first released about | ||||
| The Agat (Russian: ↊fd74 | ||||
| 1983. These were based around a 6502 and were nominally Apple II-compatible | ||||
| although with enough differences to be problematic. | ||||
|  | ||||
| They could use either standard Apple II 140kB disks, or a proprietary 840kb | ||||
| MFM-based double-sided format. FluxEngine supports both of these; this profile | ||||
| is for the proprietary format. for the Apple II format, use the [Apple II | ||||
| profile](disk-apple2.md). | ||||
| is for the proprietary format. for the Apple II format, use the `apple2` | ||||
| profile. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read agat840 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `agat840.img` which is 860160 bytes long. | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [Magazine article on the | ||||
| 	Agat](https://sudonull.com/post/54185-Is-AGAT-a-bad-copy-of-Apple) | ||||
|         Agat](https://sudonull.com/post/54185-Is-AGAT-a-bad-copy-of-Apple) | ||||
|  | ||||
|   - [Forum thread with (some) documentation on the | ||||
| 	format](https://torlus.com/floppy/forum/viewtopic.php?t=1385) | ||||
|         format](https://torlus.com/floppy/forum/viewtopic.php?t=1385) | ||||
|  | ||||
|   | ||||
| @@ -1,65 +1,27 @@ | ||||
| Disk: Amiga | ||||
| =========== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 880kB 3.5" DSDD | ||||
|  | ||||
| Amiga disks use MFM, but don't use IBM scheme. Instead, the entire track is | ||||
| read and written as a unit, with each sector butting up against the previous | ||||
| one. This saves a lot of space which allows the Amiga to not just store 880kB | ||||
| on a DD disk, but _also_ allows an extra 16 bytes of metadata per sector. | ||||
|  | ||||
| This metadata is mostly unused, so the default for FluxEngine is to ignore it | ||||
| and just use the 512 bytes of main sector data. If you want it, specify a | ||||
| 528-byte sector size. The metadata will come after the user data. | ||||
|  | ||||
| Bizarrely, the data in each sector is stored with all the odd bits first, and | ||||
| then all the even bits. This is tied into the checksum algorithm, which is | ||||
| distinctly subpar and not particularly good at detecting errors. | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read amiga | ||||
| ``` | ||||
|  | ||||
| You should end up with an `amiga.adf` which is 901120 bytes long (for a | ||||
| normal DD disk) --- it ought to be a perfectly normal ADF file which you can | ||||
| use in an emulator. | ||||
|  | ||||
| If you want the metadata as well, specify a 528 byte sector size for the | ||||
| output image: | ||||
|  | ||||
| ``` | ||||
| fluxengine read amiga -o amiga.adf --output.image.img.trackdata.sector_size=528 | ||||
| ``` | ||||
|  | ||||
| You will end up with a 929280 byte long image which you probably _can't_ use | ||||
| in an emulator; each sector will contain the 512 bytes of user payload | ||||
| followed by the 16 bytes of metadata. | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write amiga -i amiga.adf | ||||
| ``` | ||||
|  | ||||
| This will take a normal 901120 byte long ADF file and write it to a DD disk. | ||||
| Note that writing to an HD disk will probably not work (this will depend on | ||||
| your drive and disk and potential FluxEngine bugs I'm still working on --- | ||||
| please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if | ||||
| you have any insight here). | ||||
|  | ||||
| If you want to write the metadata as well, specify a 528 byte sector size for | ||||
| the output image and supply a 929280 byte long file as described above. | ||||
|  | ||||
| ``` | ||||
| fluxengine write amiga -i amiga.adf --input.image.img.trackdata.sector_size=528 | ||||
| ``` | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [The Amiga Floppy Boot Process and Physical | ||||
|     Layout](https://wiki.amigaos.net/wiki/Amiga_Floppy_Boot_Process_and_Physical_Layout) | ||||
|  | ||||
|   - [The Amiga Disk File FAQ](http://lclevy.free.fr/adflib/adf_info.html) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Ampro Little Board | ||||
| ======================== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # CP/M | ||||
|  | ||||
| The Ampro Little Board was a very simple and cheap Z80-based computer from | ||||
| 1984, which ran CP/M. It was, in fact, a single PCB which you could mount | ||||
| @@ -12,20 +12,8 @@ double-sided 80 track drive. The disk format it used was a slightly quirky | ||||
| variation of the standard MFM IBM scheme --- sector numbering starts at 17 | ||||
| rather than 1 (or Acorn's 0). FluxEngine supports this. | ||||
|  | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read ampro400 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `ampro.img` which is 409600. If you have a | ||||
| double-sided disk, use `ampro800`, which will give you a file819200 bytes long. | ||||
| These is an alias for `fluxengine read ibm` with preconfigured parameters.  You | ||||
| can pass this straight into [cpmtools](http://www.moria.de/~michael/cpmtools/): | ||||
| FluxEngine has direct filesystem support for these disks, or you can pass the | ||||
| disk images into [cpmtools](http://www.moria.de/~michael/cpmtools/): | ||||
|  | ||||
| ``` | ||||
| $ cpmls -f ampdsdd ampro.img | ||||
| @@ -43,7 +31,11 @@ kayinfo.lbr | ||||
| ...etc... | ||||
| ``` | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [The Ampro Little Board](http://oldcomputers.net/ampro-little-board.html) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Apple II | ||||
| ============== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Prodos, Appledos, and CP/M | ||||
|  | ||||
| Apple II disks are nominally fairly sensible 40-track, single-sided, 256 | ||||
| bytes-per-sector jobs. However, they come in two varieties: DOS 3.3/ProDOS and | ||||
| @@ -30,46 +30,31 @@ FluxEngine can remap the sectors from physical to logical using modifiers.  If | ||||
| you don't specify a remapping modifier, you get the sectors in the order they | ||||
| appear on the disk. | ||||
|  | ||||
| If you don't want an image in physical sector order, specify one of the | ||||
| filesystem ordering options. These also select the appropriate file system; | ||||
| FluxEngine has read-only support for all of these. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| In addition, some third-party systems use 80-track double sides drives, with | ||||
| the same underlying disk format. The complication here is that the AppleDOS | ||||
| filesystem only supports up to 50 tracks, so it needs tweaking to support | ||||
| larger disks. It treats the second side of the disk as a completely different | ||||
| volume. | ||||
|  | ||||
| Just do: | ||||
| ## Options | ||||
|  | ||||
| ``` | ||||
| fluxengine read apple2 | ||||
| ``` | ||||
|   - Format variant: | ||||
|       - `140`: 140kB 5.25" 35-track SS | ||||
|       - `640`: 640kB 5.25" 80-track DS | ||||
|   - Filesystem and sector skew: | ||||
|       - `nofs`: use physical CHS sector order and no file system | ||||
|       - `appledos`: use AppleDOS soft sector skew and file system | ||||
|       - `prodos`: use ProDOS soft sector skew and filesystem | ||||
|       - `cpm`: use CP/M soft sector skew and filesystem | ||||
|   - `side1`: for AppleDOS file system access, read the volume on side 1 of a disk | ||||
|  | ||||
| You should end up with an `apple2.img` which is 143360 bytes long. It will be in | ||||
| physical sector ordering. You can specify a sector ordering, `--appledos` or | ||||
| `--prodos` to get an image intended for use in an emulator, due to the logical | ||||
| sector mapping issue described above: | ||||
|  | ||||
| ``` | ||||
| fluxengine read apple2 --prodos | ||||
| ``` | ||||
|  | ||||
| You will also need this for filesystem access. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
| ``` | ||||
| fluxengine write apple2 -i apple2.img | ||||
| ``` | ||||
|  | ||||
| If your image is in logical sector ordering (images intended for emulators | ||||
| usually are), specify a modifier of `--appledos` or `--prodos`: | ||||
|  | ||||
| ``` | ||||
| fluxengine write apple2 --prodos -i apple2.img | ||||
| ``` | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [Beneath Apple DOS](https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf) | ||||
|  | ||||
|   - [MAME's ap2_dsk.cpp file](https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap2_dsk.cpp) | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								doc/disk-apple2_drive.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								doc/disk-apple2_drive.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Adjust configuration for a 40-track Apple II drive | ||||
|  | ||||
| (This format has no documentation. Please file a bug.) | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Atari ST | ||||
| ============== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Almost PC compatible | ||||
|  | ||||
| Atari ST disks are standard MFM encoded IBM scheme disks without an IAM header. | ||||
| Disks are typically formatted 512 bytes per sector with between 9-10 (sometimes | ||||
| @@ -11,63 +11,17 @@ Atari profiles below are configured to ignore these. | ||||
|  | ||||
| Be aware that many PC drives (including mine) won't do the 82 track formats.  | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
|     fluxengine read <format> | ||||
|  | ||||
| ...and you'll end up with an `atarist.st` file. The size of the disk image will | ||||
| vary depending on the format. This is an alias for `fluxengine read ibm` with | ||||
| preconfigured parameters. | ||||
|  | ||||
| Note that the profile by default assumes a double-sided disk; if you're reading | ||||
| a single-sided disk, add `--heads 0` to prevent FluxEngine from looking at the | ||||
| other side and getting confused by any data it sees there. | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| FluxEngine can also write Atari ST scheme disks. | ||||
|  | ||||
| The syntax is: | ||||
|  | ||||
|     fluxengine write <format> -i input.st | ||||
|  | ||||
| Available formats | ||||
| ----------------- | ||||
|  | ||||
| `<format>` can be one of these: | ||||
|  | ||||
|   - `atarist360`: a 360kB 3.5" disk, with 80 cylinders, 1 side, and 9 sectors | ||||
| 	per track. | ||||
|   - `atarist370`: a 370kB 3.5" disk, with 82 cylinders, 1 side, and 9 sectors | ||||
| 	per track. | ||||
|   - `atarist400`: a 400kB 3.5" disk, with 80 cylinders, 1 side, and 10 sectors | ||||
| 	per track. | ||||
|   - `atarist410`: a 410kB 3.5" disk, with 82 cylinders, 1 side, and 10 sectors | ||||
| 	per track. | ||||
|   - `atarist720`: a 720kB 3.5" disk, with 80 cylinders, 2 sides, and 9 sectors | ||||
| 	per track. | ||||
|   - `atarist740`: a 740kB 3.5" disk, with 82 cylinders, 2 sides, and 9 sectors | ||||
| 	per track. | ||||
|   - `atarist800`: a 800kB 3.5" disk, with 80 cylinders, 2 sides, and 10 sectors | ||||
| 	per track. | ||||
|   - `atarist820`: a 820kB 3.5" disk, with 82 cylinders, 2 sides, and 10 sectors | ||||
| 	per track. | ||||
|  | ||||
| See [the IBM format documentation](disk-ibm.md) for more information. Note that | ||||
| only some PC 3.5" floppy disk drives are capable of seeking to the 82nd track. | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [Atari ST Floppy Drive Hardware | ||||
| 	Information](https://info-coach.fr/atari/hardware/FD-Hard.php) by Jean | ||||
| 	Louis-Guerin | ||||
|         Information](https://info-coach.fr/atari/hardware/FD-Hard.php) by Jean | ||||
|         Louis-Guerin | ||||
|  | ||||
|   - [Atari ST Floppy Drive Software | ||||
| 	Information](https://info-coach.fr/atari/software/FD-Soft.php) by Jean | ||||
| 	Louis-Guerin | ||||
|         Information](https://info-coach.fr/atari/software/FD-Soft.php) by Jean | ||||
|         Louis-Guerin | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| Disk: Elektronica BK | ||||
| ==================== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 800kB 5.25"/3.5" 80-track 10-sector DSDD | ||||
|  | ||||
| The BK (an abbreviation for бытовой компьютер --- 'home computer' in Russian) | ||||
| The BK (an abbreviation for 1ba9 | ||||
| is a Soviet era personal computer from Elektronika based on a PDP-11 | ||||
| single-chip processor. It was the _only_ official, government approved home | ||||
| computer in mass production at the time. | ||||
| @@ -12,26 +12,7 @@ sectors per track, resulting in 800kB disks. The format is, in fact, identical | ||||
| to the Atari ST 800kB format. Either 5.25" or 3.5" drives were used depending | ||||
| on what was available at the time, with the same format on both. | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read bk800 -o bk800.img | ||||
| ``` | ||||
|  | ||||
| You should end up with an `bk800.img` containing all the sectors concatenated | ||||
| one after the other in CHS order. This will work on both 5.25" and 3.5" drives. | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write bk800 -i bk800.img | ||||
| ``` | ||||
|  | ||||
| This will write the disk image to either a 5.25" or 3.5" drive. | ||||
| (no options) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Brother word processor | ||||
| ============================ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # GCR family | ||||
|  | ||||
| Brother word processor disks are weird, using custom tooling and chipsets. | ||||
| They are completely not PC compatible in every possible way other than the | ||||
| @@ -32,28 +32,9 @@ If you find one of these misaligned disks then *please* [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new); I want to | ||||
| investigate. | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read `<format>` | ||||
| ``` | ||||
|  | ||||
| ... where `<format>` can be `brother120` or `brother240`. You should end up | ||||
| with a `brother.img` which is either 119808 or 239616 bytes long. | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write `<format>` -i brother.img | ||||
| ``` | ||||
|  | ||||
| ...where `<format>` can be `brother120` or `brother240`. | ||||
| (no options) | ||||
|  | ||||
| Dealing with misaligned disks | ||||
| ----------------------------- | ||||
| @@ -63,10 +44,10 @@ _can_. If you have access to a compatible word processor, there's a fairly | ||||
| simple workaround to allow you to extract the data: | ||||
|  | ||||
|   1. Format a disk using FluxEngine (by simply writing a blank filesystem image | ||||
| 	 to a disk). This will have the correct alignment to work on a PC drive. | ||||
|          to a disk). This will have the correct alignment to work on a PC drive. | ||||
|  | ||||
|   2. Use a word processor to copy the misaligned disk to the newly formatted | ||||
| 	 disk. The machine will happily adjust itself to both sets of alignments. | ||||
|          disk. The machine will happily adjust itself to both sets of alignments. | ||||
|  | ||||
|   3. Use FluxEngine to read the data off the correctly aligned disk. | ||||
|  | ||||
| @@ -102,50 +83,19 @@ record.) The sector order is 05a3816b4927, which gives a sector skew of 5. | ||||
| High level format | ||||
| ----------------- | ||||
|  | ||||
| Once decoded, you end up with a file system image. | ||||
| Once decoded, you end up with a file system image. FluxEngine supports direct | ||||
| filesystem access for both kinds of disks. | ||||
|  | ||||
| ### 120kB disks | ||||
|  | ||||
| These disks use a proprietary and very simple file system. It's FAT-like | ||||
| with an obvious directory and allocation table. I have reversed engineered | ||||
| a very simple tool for extracting files from it. To show the directory, do: | ||||
|  | ||||
| ``` | ||||
| brother120tool image.img | ||||
| ``` | ||||
|  | ||||
| To extract a file, do: | ||||
|  | ||||
| ``` | ||||
| brother120tool image.img filename | ||||
| ``` | ||||
|  | ||||
| Wildcards are supported, so use `'*'` for the filename (remember to quote it) | ||||
| if you want to extract everything. | ||||
|  | ||||
| The files are usually in the format known as WP-1, which aren't well | ||||
| supported by modern tools (to nobody's great surprise). Matthias Henckell has | ||||
| [reverse engineered the file | ||||
| format](https://mathesoft.eu/brother-wp-1-dokumente/) and has produced a | ||||
| (Windows-only, but runs in Wine) [tool which will convert these files into | ||||
| RTF](https://mathesoft.eu/sdm_downloads/wp2rtf/). This will only work on WP-1 | ||||
| files. | ||||
|  | ||||
| To create a disk image (note: this creates a _new_ disk image, overwriting the | ||||
| previous image), do: | ||||
|  | ||||
| ``` | ||||
| brother120tool --create image.img filename1 filename2... | ||||
| ``` | ||||
| with an obvious directory and allocation table. It's supported by FluxEngine. | ||||
|  | ||||
| Any files whose names begin with an asterisk (`*`) will be marked as hidden. If | ||||
| the file is named `*boot`, then a boot sector will be created which will load | ||||
| and run the file at 0x7000 if the machine is started with CODE+Q pressed. So | ||||
| far this has only been confirmed to work on a WP-1. | ||||
|  | ||||
| Any questions? please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
| ### 240kB disks | ||||
|  | ||||
| Conversely, the 240kB disks turns out to be a completely normal Microsoft FAT | ||||
| @@ -157,17 +107,6 @@ reverse engineered it to find out. | ||||
| Standard Linux mtools will access the filesystem image and allow you to move | ||||
| files in and out. However, you'll need to change the media type bytes at | ||||
| offsets 0x015 and 0x100 from 0x58 to 0xf0 before mtools will touch it. The | ||||
| supplied `brother240tool` will do this. Once done, this will work: | ||||
| supplied `brother240tool` will do this. Additionally, FluxEngine's own FAT | ||||
| file system supports this. | ||||
|  | ||||
| ``` | ||||
| mdir -i brother.img | ||||
| mcopy -i brother.img ::brother.doc linux.doc | ||||
| ``` | ||||
|  | ||||
| The word processor checks the media byte, unfortunately, so you'll need to | ||||
| change it back to 0x58 before writing an image to disk. Just run | ||||
| `brother240tool` on the image again and it will flip it back. | ||||
|  | ||||
| The file format is not WP-1, and currently remains completely unknown, | ||||
| although it's probably related. If anyone knows anything about this, please | ||||
| [get in touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|   | ||||
							
								
								
									
										103
									
								
								doc/disk-c64.md
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								doc/disk-c64.md
									
									
									
									
									
								
							| @@ -1,103 +0,0 @@ | ||||
| Disk: Commodore 64 | ||||
| ================== | ||||
|  | ||||
| Commodore 64 disks come in two varieties: GCR, which are the overwhelming | ||||
| majority; and MFM, only used on the 1571 and 1581. The latter were (as far as I | ||||
| can tell) standard IBM PC format disks with a slightly odd sector count. | ||||
|  | ||||
| The GCR disks are much more interesting. They could store 170kB on a | ||||
| single-sided disk (although later drives were double-sided), using a proprietary | ||||
| encoding and record scheme; like [Apple Macintosh disks](macintosh.md) they | ||||
| stored varying numbers of sectors per track to make the most of the physical | ||||
| disk area, although unlike them they did it by changing the bitrate rather than | ||||
| adjusting the motor speed. | ||||
|  | ||||
| The drives were also intelligent and ran DOS on a CPU inside them. The | ||||
| computer itself knew nothing about file systems. You could even upload | ||||
| programs onto the drive and run them there, allowing all sorts of custom disk | ||||
| formats, although this was mostly used to compensate for the [cripplingly | ||||
| slow connection to the | ||||
| computer](https://ilesj.wordpress.com/2014/05/14/1541-why-so-complicated/) of | ||||
| 300 bytes per second (!). (The drive itself could transfer data reasonably | ||||
| quickly.) | ||||
|  | ||||
| A standard 1541 disk has 35 tracks of 17 to 21 sectors, each 256 bytes long | ||||
| (sometimes 40 tracks). | ||||
|  | ||||
| A standard 1581 disk has 80 tracks and two sides, each with 10 sectors, 512 | ||||
| bytes long. | ||||
|  | ||||
| Reading 1541 disks | ||||
| ------------------ | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read commodore1541 -o commodore1541.d64 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `commodore1541.d64` file which is 174848 bytes long. | ||||
| You can load this straight into a Commodore 64 emulator such as | ||||
| [VICE](http://vice-emu.sourceforge.net/). | ||||
|  | ||||
| If you have a 40-track disk, add `--40`. | ||||
|  | ||||
| **Big warning!** Commodore 64 disk images are complicated due to the way the | ||||
| tracks are different sizes and the odd sector size, so you need the special D64 | ||||
| or LDBS output formats to represent them sensibly. Don't use IMG unless you | ||||
| know what you're doing. | ||||
|  | ||||
| Writing 1541 disks | ||||
| ------------------ | ||||
|  | ||||
| Just do: | ||||
| ``` | ||||
| fluxengine write commodore1541 -i file.d64 | ||||
| ``` | ||||
|  | ||||
| If you have a 40-track disk, add `--40`. | ||||
|  | ||||
| Note that only standard Commodore 64 BAM file systems can be written this way, | ||||
| as the disk ID in the BAM has to be copied to every sector on the disk. | ||||
|  | ||||
| Reading and writing 1581 disks | ||||
| ------------------------------ | ||||
|  | ||||
| 1581 disks are just another version of the standard IBM scheme. | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read commodore1581 -o commodore1581.d81 | ||||
| ``` | ||||
|  | ||||
| or: | ||||
|  | ||||
| ``` | ||||
| fluxengine write commodore1581 -i commodore1581.img | ||||
| ``` | ||||
|  | ||||
| Reading and writing CMD FD2000 disks | ||||
| ------------------------------------ | ||||
|  | ||||
| Yet again, these are another IBM scheme variant. | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read cmd_fd2000 -o cmd_fd2000.d81 | ||||
| ``` | ||||
|  | ||||
| or: | ||||
|  | ||||
| ``` | ||||
| fluxengine write cmd_fd2000 -i cmd_fd2000.img | ||||
| ``` | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [Ruud's Commodore Site: 1541](http://www.baltissen.org/newhtm/1541c.htm): | ||||
|     documentation on the 1541 disk format. | ||||
|  | ||||
							
								
								
									
										50
									
								
								doc/disk-commodore.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								doc/disk-commodore.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 1541, 1581, 8050 and variations | ||||
|  | ||||
| Commodore 8-bit computer disks come in two varieties: GCR, which are the | ||||
| overwhelming majority; and MFM, only used on the 1571 and 1581. The latter were | ||||
| (as far as I can tell) standard IBM PC format disks with a slightly odd sector | ||||
| count. | ||||
|  | ||||
| The GCR disks are much more interesting. They could store 170kB on a | ||||
| single-sided disk (although later drives were double-sided), using a proprietary | ||||
| encoding and record scheme; like [Apple Macintosh disks](macintosh.md) they | ||||
| stored varying numbers of sectors per track to make the most of the physical | ||||
| disk area, although unlike them they did it by changing the bitrate rather than | ||||
| adjusting the motor speed. | ||||
|  | ||||
| The drives were also intelligent and ran DOS on a CPU inside them. The | ||||
| computer itself knew nothing about file systems. You could even upload | ||||
| programs onto the drive and run them there, allowing all sorts of custom disk | ||||
| formats, although this was mostly used to compensate for the [cripplingly | ||||
| slow connection to the | ||||
| computer](https://ilesj.wordpress.com/2014/05/14/1541-why-so-complicated/) of | ||||
| 300 bytes per second (!). (The drive itself could transfer data reasonably | ||||
| quickly.) | ||||
|  | ||||
|   - a 1541 disk has 35 tracks of 17 to 21 sectors, each 256 bytes long | ||||
| 	(sometimes 40 tracks), and uses GCR encoding. | ||||
|  | ||||
|   - a standard 1581 disk has 80 tracks and two sides, each with 10 sectors, 512 | ||||
| 	bytes long, and uses normal IBM encoding. | ||||
|  | ||||
|   - an 8050 disk has 77 tracks and two sides, with four speed zones; the number | ||||
| 	of sectors varies from 23 to 29, using GCR encoding. These will store | ||||
| 	1042kB. These drives are peculiar because they are 100tpi and therefore the | ||||
| 	disks cannot be read in normal 96tpi drives. | ||||
|  | ||||
|   - a CMD FD2000 disk (a popular third-party Commodore disk drive) has 81 | ||||
| 	tracks and two sides, each with 10 1024-byte sectors, for a massive 1620kB | ||||
| 	of storage. This also uses IBM encoding. | ||||
|  | ||||
| A CMD FD2000 disk (a popular third-party Commodore disk drive)  | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Ruud's Commodore Site: 1541](http://www.baltissen.org/newhtm/1541c.htm): | ||||
|     documentation on the 1541 disk format. | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: VDS Eco1 | ||||
| ============== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # CP/M; 1210kB 77-track mixed format DSHD | ||||
|  | ||||
| The Eco1 is a Italian CP/M machine produced in 1982. It had 64kB of RAM, in | ||||
| later models expandable up to 384kB, and _two_ Z80 processors. One of these was | ||||
| @@ -23,20 +23,12 @@ to the format confusing the size autodetection the images need postprocessing | ||||
| to be useful, so there's a custom profile for the Eco1 which produces sensible | ||||
| images. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read eco1 -o eco1.img | ||||
| ``` | ||||
| ## References | ||||
|  | ||||
| You should end up with an `eco1.img` of 1255168 bytes, containing all the | ||||
| sectors concatenated one after the other in CHS order, with no padding. | ||||
|  | ||||
| References | ||||
| ---------- | ||||
|  | ||||
|   - [Apulio Retrocomputing's page on the Eco1](https://www.apuliaretrocomputing.it/wordpress/?p=8976) | ||||
|   - [Apulio Retrocomputing's page on the | ||||
| 	Eco1](https://www.apuliaretrocomputing.it/wordpress/?p=8976) | ||||
|  | ||||
|   | ||||
							
								
								
									
										11
									
								
								doc/disk-epsonpf10.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								doc/disk-epsonpf10.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # CP/M; 3.5" 40-track DSDD | ||||
|  | ||||
| The Epson PF10 is the disk unit for the Epson Z80 series of 'laptops', running | ||||
| CP/M. It uses a single-sided 40-track 3.5" format, which is unusual, but the | ||||
| format itself is yet another IBM scheme variant. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Durango F85 | ||||
| ================= | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 461kB 5.25" 77-track SS | ||||
| 
 | ||||
| The Durango F85 was an early office computer based around a 5MHz 8085 processor, | ||||
| sold in 1977. It had an impressive 64kB of RAM, upgradable to 128kB, and ran | ||||
| @@ -26,21 +26,14 @@ disks; I don't have access to an image of one so don't know how they work | ||||
| store the side). As always, if you have one, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
| 
 | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
| 
 | ||||
| Just do: | ||||
| (no options) | ||||
| 
 | ||||
| ``` | ||||
| fluxengine read f85 | ||||
| ``` | ||||
| 
 | ||||
| You should end up with an `f85.img` which is 472064 bytes long. | ||||
| 
 | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
| 
 | ||||
| There's amazingly little information about these things. | ||||
| 
 | ||||
|   * [Chuck Guzis' F85 page](http://www.sydex.com/durango/durango.html) with lots of pictures | ||||
|   * [Chuck Guzis' F85 page](http://www.sydex.com/durango/durango.html) with | ||||
|     lots of pictures | ||||
| 
 | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Brother FB-100 | ||||
| ==================== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 100kB 3.5" 40-track SSSD | ||||
|  | ||||
| The Brother FB-100 is a serial-attached smart floppy drive used by a several | ||||
| different machines for mass storage, including the Tandy Model 100 and | ||||
| @@ -20,26 +20,21 @@ ID data used for filesystem management. | ||||
| There was also apparently a TPDD-2 which could store twice as much data, but | ||||
| I don't have access to one of those disks. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read fb100 | ||||
| ``` | ||||
| ## References | ||||
|  | ||||
| You should end up with an `fb100.img` of the appropriate size. It's a simple | ||||
| array of 80 1292-byte sectors (12 bytes for the ID record plus 1280 bytes for | ||||
| the data). | ||||
|   - [Tandy Portable Disk Drive operations | ||||
| 	manual](http://www.classiccmp.org/cini/pdf/Tandy/Portable%20Disk%20Drive%20Operation%20Manual.pdf) | ||||
|  | ||||
| References | ||||
| ---------- | ||||
|   - [Tandy Portable Disk Drive service | ||||
| 	manual](https://archive.org/details/TandyPortableDiskDriveSoftwareManual26-3808s) | ||||
|  | ||||
|   - [Tandy Portable Disk Drive operations manual](http://www.classiccmp.org/cini/pdf/Tandy/Portable%20Disk%20Drive%20Operation%20Manual.pdf) | ||||
|   - [TPDD design notes (including a dump of the | ||||
| 	ROM)](http://bitchin100.com/wiki/index.php?title=TPDD_Design_Notes) | ||||
|  | ||||
|   - [Tandy Portable Disk Drive service manual](https://archive.org/details/TandyPortableDiskDriveSoftwareManual26-3808s) | ||||
|   - [Knitting machine FB-100 | ||||
| 	resources](http://www.k2g2.org/wiki:brother_fb-100) | ||||
|  | ||||
|   - [TPDD design notes (including a dump of the ROM)](http://bitchin100.com/wiki/index.php?title=TPDD_Design_Notes) | ||||
|  | ||||
|   - [Knitting machine FB-100 resources](http://www.k2g2.org/wiki:brother_fb-100) | ||||
|   | ||||
							
								
								
									
										15
									
								
								doc/disk-hplif.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								doc/disk-hplif.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # a variety of disk formats used by HP | ||||
|  | ||||
| LIF, a.k.a. Logical Interchange Format, is a series of formats used by | ||||
| Hewlett-Packard across their entire range of computers, from calculators to | ||||
| modern servers. It also defines a simple non-hierarchical filesystem which is | ||||
| bizarrely _still_ supported by HP-UX systems. | ||||
|  | ||||
| Floppy-disk wise, they're yet more variations of the standard IBM floppy | ||||
| encoding scheme. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Generic IBM | ||||
| ================= | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Generic PC 3.5"/5.25" disks | ||||
|  | ||||
| IBM scheme disks are _the_ most common disk format, ever. They're used by a | ||||
| huge variety of different systems, and they come in a huge variety of different | ||||
| @@ -24,76 +24,19 @@ metadata. Systems which use IBM scheme disks include but are not limited to: | ||||
| FluxEngine supports reading these. However, some variants are more peculiar | ||||
| than others, and as a result there are specific decoders which set the defaults | ||||
| correctly for certain formats (for example: on PC disks the sector numbers | ||||
| start from 1, but on [Acorn](disk-acorndfs.md) disks they start from 0). The | ||||
| IBM decoder described here is the generic one, and is suited for 'conventional' | ||||
| PC disks. While you can read all the variant formats with it if you use the | ||||
| right set of arguments, it's easier to use the specific decoder. | ||||
| start from 1, but on Acorn disks they start from 0). The IBM decoder described | ||||
| here is the generic one, and is suited for 'conventional' PC disks. While you | ||||
| can read all the variant formats with it if you use the right set of arguments, | ||||
| it's easier to use the specific decoder. | ||||
|  | ||||
| The generic decoder is mostly self-configuring, and will detect the format of | ||||
| your disk for you. | ||||
| There is a generic decoder which should adjust automatically to whichever disk | ||||
| format you are using, but it's unreliable and not recommended. This format | ||||
| should also be used if you are writing images such as DIM which specify the | ||||
| image format. FluxEngine will use these parameters. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
|     fluxengine read `<format>` | ||||
|  | ||||
| ...and you'll end up with a `<format>.img` file. This should work on most PC | ||||
| disks (including FM 360kB disks, 3.5" 1440kB disks, 5.25" 1200kB disks, etc.) | ||||
| The size of the disk image will vary depending on the format. | ||||
|  | ||||
| The common PC formats are `ibm720` and `ibm1440`, but there are _many_ others, | ||||
| and there's too many configuration options to usefully list. Use `fluxengine | ||||
| write` to list all formats, and try `fluxengine write ibm1440 --config` to see | ||||
| a sample configuration. | ||||
|  | ||||
| Configuration options you'll want include: | ||||
|  | ||||
|   - `--decoder.ibm.sector_id_base=N`: specifies the ID of the first sector; | ||||
| 	this defaults to 1. Some formats (like the Acorn ones) start at 0. This | ||||
| 	can't be autodetected because FluxEngine can't distinguish between a disk | ||||
| 	which starts at sector 1 and a disk which starts at sector 0 but all the | ||||
| 	sector 0s are missing. | ||||
|  | ||||
|   - `--decoder.ibm.ignore_side_byte=true|false`: each sector header describes | ||||
| 	the location of the sector: sector ID, track and side. Some formats use the | ||||
| 	wrong side ID, so the sectors on side 1 are labelled as belonging to side | ||||
| 	0. This causes FluxEngine to see duplicate sectors (as it can't distinguish | ||||
| 	between the two sides). This option tells FluxEngine to ignore the side | ||||
| 	byte completely and use the physical side instead. | ||||
|  | ||||
|   - `--decoder.ibm.required_sectors=range`: if you know how many sectors to | ||||
| 	expect per track, you can improve reads by telling FluxEngine what to | ||||
| 	expect here. If a track is read and a sector on this list is _not_ present, | ||||
| 	then FluxEngine assumes the read failed and will retry. This avoids the | ||||
| 	situation where FluxEngine can't tell the difference between a sector | ||||
| 	missing because it's bad or a sector missing because it was never written | ||||
| 	in the first place. If sectors are seen outside the range here, it will | ||||
| 	still be read. You can use the same syntax as for track specifiers: e.g. | ||||
| 	`0-9`, `0,1,2,3`, etc. | ||||
|  | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| FluxEngine can also write IBM scheme disks. Unfortunately the format is | ||||
| incredibly flexible and you need to specify every single parameter, which | ||||
| makes things slightly awkward. Preconfigured profiles are available. | ||||
|  | ||||
| The syntax is: | ||||
|  | ||||
|     fluxengine write <format> -i input.img <options> | ||||
|  | ||||
| The common PC formats are `ibm720` and `ibm1440`, but there are _many_ others, | ||||
| and there's too many configuration options to usefully list. Use `fluxengine | ||||
| write` to list all formats, and try `fluxengine write ibm1440 --config` to see | ||||
| a sample configuration. | ||||
|  | ||||
| Some image formats, such as DIM, specify the image format, For these you can | ||||
| specify the `ibm` format and FluxEngine will automatically determine the | ||||
| correct format to use. | ||||
| (no options) | ||||
|  | ||||
| Mixed-format disks | ||||
| ------------------ | ||||
| @@ -131,11 +74,7 @@ drives, feature "tri-mode" support which in addition to normal 300rpm modes, | ||||
| can change their speed to read and write 360rpm DD and HD disks. | ||||
|  | ||||
| Neither the FluxEngine or Greaseweazle hardware can currently command a | ||||
| tri-mode drive to spin at 360rpm, however an older 360rpm-only drive will work | ||||
| to read these formats. | ||||
| tri-mode drive to spin at 360rpm. However on both devices the FluxEngine | ||||
| software is capable of both reading and writing 300rpm disks at 360rpm and vice | ||||
| versa, so it shouldn't matter. | ||||
|  | ||||
| Alternately, the FluxEngine software can rescale the flux pulses to enable | ||||
| reading and writing these formats with a plain 300rpm drive. To do this, | ||||
| specify the following two additional options: | ||||
|  | ||||
|     --flux_source.rescale=1.2 --flux_sink.rescale=1.2 | ||||
|   | ||||
							
								
								
									
										11
									
								
								doc/disk-icl30.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								doc/disk-icl30.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # CP/M; 263kB 35-track DSSD | ||||
|  | ||||
| The ICL Model 30 is a reasonably standard CP/M machine using 35-track single | ||||
| density disks and the traditional CP/M 128-byte secotrs --- 30 of them per | ||||
| track! Other than that it's another IBM scheme variation. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
							
								
								
									
										59
									
								
								doc/disk-mac.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								doc/disk-mac.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 400kB/800kB 3.5" GCR | ||||
|  | ||||
| Macintosh disks come in two varieties: the newer 1440kB ones, which are | ||||
| perfectly ordinary PC disks you should use the `ibm` profile to read them, and | ||||
| the older 800kB disks (and 400kB for the single sides ones). They have 80 | ||||
| tracks and up to 12 sectors per track. | ||||
|  | ||||
| They are also completely insane. | ||||
|  | ||||
| It's not just the weird, custom GCR encoding. It's not just the utterly | ||||
| bizarre additional encoding/checksum built on top of that where [every byte | ||||
| is mutated according to the previous bytes in the | ||||
| sector](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/). | ||||
| It's not just the odd way in which disks think they have four sides, two on one | ||||
| side and two on the other, so that the track byte stores only the bottom 6 bits | ||||
| of the track number. It's not just the way that Macintosh sectors are 524 bytes | ||||
| long. No, it's the way the Macintosh drive changes speed depending on which | ||||
| track it's looking at, so that each track contains a different amount of data. | ||||
|  | ||||
| The reason for this is actually quite sensible: the tracks towards the centre | ||||
| of the disk are obviously moving more slowly, so you can't pack the bits in | ||||
| quite as closely (due to limitations in the magnetic media). You can use a | ||||
| higher bitrate at the edge of the disk than in the middle. Many platforms, for | ||||
| example the Commodore 64 1541 drive, changed bitrate this way. | ||||
|  | ||||
| But Macintosh disks used a constant bitrate and changed the speed that the disk | ||||
| spun instead to achieve the same effect... | ||||
|  | ||||
| _Anyway_: FluxEngine will read them fine on conventional drives.  Because it's | ||||
| clever. | ||||
|  | ||||
| Macintosh computers never really used the twelve bytes of metadata and the | ||||
| standard for disk images is to omit it. If you want them, specify that you want | ||||
| 524-byte sectors. The metadata will follow the 512 bytes of user data. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
|   - Format variant: | ||||
|       - `400`: 400kB 80-track SSDD | ||||
|       - `800`: 800kB 80-track DSDD | ||||
|   - `metadata`: read/write 524 byte sectors | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [MAME's ap_dsk35.cpp file](https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp), | ||||
|     without which I'd never have managed to do this | ||||
|  | ||||
|   - [Crazy Disk Encoding | ||||
|     Schemes](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/), which made | ||||
|     me realise just how nuts the format is | ||||
|  | ||||
|   - [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an | ||||
|     epicly detailed writeup of the Apple II disk format (which is closely related) | ||||
|  | ||||
|   - [The DiskCopy 4.2 | ||||
|         format](https://www.discferret.com/wiki/Apple_DiskCopy_4.2), described on | ||||
|         the DiskFerret website. | ||||
|  | ||||
| @@ -1,92 +0,0 @@ | ||||
| Disk: Macintosh | ||||
| =============== | ||||
|  | ||||
| Macintosh disks come in two varieties: the newer 1440kB ones, which are | ||||
| perfectly ordinary PC disks you should use `fluxengine read ibm` to read, and | ||||
| the older 800kB disks (and 400kB for the single sides ones). They have 80 | ||||
| tracks and up to 12 sectors per track. | ||||
|  | ||||
| They are also completely insane. | ||||
|  | ||||
| It's not just the weird, custom GCR encoding. It's not just the utterly | ||||
| bizarre additional encoding/checksum built on top of that where [every byte | ||||
| is mutated according to the previous bytes in the | ||||
| sector](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/). | ||||
| It's not just the odd way in which disks think they have four sides, two on | ||||
| one side and two on the other, so that the track byte stores only the bottom | ||||
| 6 bits of the track number. It's not just the way that Macintosh sectors are | ||||
| 524 bytes long. No, it's the way the Macintosh drive changes speed depending | ||||
| on which track it's looking at, so that each track contains a different | ||||
| amount of data. | ||||
|  | ||||
| The reason for this is actually quite sensible: the tracks towards the centre | ||||
| of the disk are obviously moving more slowly, so you can't pack the bits in | ||||
| quite as closely (due to limitations in the magnetic media). You can use a | ||||
| higher bitrate at the edge of the disk than in the middle. Many platforms, | ||||
| for example the Commodore 64 1541 drive, changed bitrate this way. | ||||
|  | ||||
| But Macintosh disks used a constant bitrate and changed the speed that the | ||||
| disk spun instead to achieve the same effect... | ||||
|  | ||||
| _Anyway_: FluxEngine will read them fine on conventional drives. | ||||
| Because it's clever. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read <format> -o mac.dsk | ||||
| ``` | ||||
|  | ||||
| ...where `<format>` can be `mac400` or `mac800`. | ||||
|  | ||||
| You should end up with a `mac.dsk` file containing a raw sector image | ||||
| (equivalent to `.img`). | ||||
|  | ||||
| **Big warning!** Mac disk images are complicated due to the way the tracks are | ||||
| different sizes and the odd sector size. What you get above is a triangular | ||||
| disk image, which contains all the 512-byte user data areas concatenated | ||||
| together in filesystem order. It does not contain the twelve bytes of metadata. | ||||
| If you want these as well, specify that you want 524 byte sectors with | ||||
| `--output.image.img.trackdata.sector_size=512`. The metadata will follow the | ||||
| 512 bytes of user data. | ||||
|  | ||||
| FluxEngine also supports DiskCopy 4.2 disk images, which may be a better option | ||||
| if you're going to be using the image on a real Macintosh (or with other | ||||
| tooling which supports it). To get one of these, use a filename like | ||||
| `mac.diskcopy`. This will contain both user data and metadata. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write <format> -i mac.dsk | ||||
| ``` | ||||
|  | ||||
| ...where `<format>` can be `mac400` or `mac800`. | ||||
|  | ||||
| It'll read the image file and write it out. | ||||
|  | ||||
| The same warning as above applies --- you can use normal `.dsk` files but it's | ||||
| problematic. Consider using DiskCopy 4.2 files instead. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [MAME's ap_dsk35.cpp file](https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp), | ||||
|     without which I'd never have managed to do this | ||||
|  | ||||
|   - [Crazy Disk Encoding | ||||
|     Schemes](https://www.bigmessowires.com/2011/10/02/crazy-disk-encoding-schemes/), which made | ||||
|     me realise just how nuts the format is | ||||
|  | ||||
|   - [Les Disquettes et le drive Disk II](http://www.hackzapple.com/DISKII/DISKIITECH.HTM), an | ||||
|     epicly detailed writeup of the Apple II disk format (which is closely related) | ||||
|  | ||||
|   - [The DiskCopy 4.2 | ||||
| 	format](https://www.discferret.com/wiki/Apple_DiskCopy_4.2), described on | ||||
| 	the DiskFerret website. | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Micropolis | ||||
| ================ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 100tpi MetaFloppy disks | ||||
|  | ||||
| Micropolis MetaFloppy disks use MFM and hard sectors. Mod I was 48 TPI and | ||||
| stored 143k per side. Mod II was 100 TPI and stored 315k per side. Each of the | ||||
| @@ -17,43 +17,6 @@ of the drive and can't be changed in software. You'll need to get hold of a | ||||
| 100tpi Micropolis drive. Luckily these seem to use the same connector and | ||||
| pinout as a 96tpi PC 5.25" drive. In use they should be identical. | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| Based on your floppy drive, just do one of: | ||||
|  | ||||
| ``` | ||||
| fluxengine read micropolis143 # single-sided Mod I | ||||
| fluxengine read micropolis287 # double-sided Mod I | ||||
| fluxengine read micropolis315 # single-sided Mod II | ||||
| fluxengine read micropolis630 # double-sided Mod II | ||||
| ``` | ||||
|  | ||||
| You should end up with a `micropolis.img` of the corresponding size. The image | ||||
| is written in CHS order, but HCS is generally used by CP/M tools so | ||||
| double-sided disk images may need to be post-processed. Half-full double-sided | ||||
| disks can be read as single-sided disks to work around the problem. | ||||
|  | ||||
| The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK, | ||||
| and SETSEC, but no function to select the head/side. Double-sided floppies | ||||
| could be represented as having either twice the number of sectors, for CHS, or | ||||
| twice the number of tracks, HCS; the second side's tracks in opposite order | ||||
| logically followed the first side (e.g., tracks 77-153). Micropolis disks | ||||
| tended to be the latter. | ||||
|  | ||||
| It's also possible to output to VGI, which retains OS-specific "user data" and | ||||
| machine-specific ECC. Add `--vgi` to the command line after the chosen | ||||
| Micropolis profile: | ||||
| ``` | ||||
| fluxengine read micropolis143 --vgi # single-sided Mod I | ||||
| fluxengine read micropolis287 --vgi # double-sided Mod I | ||||
| fluxengine read micropolis315 --vgi # single-sided Mod II | ||||
| fluxengine read micropolis630 --vgi # double-sided Mod II | ||||
| ``` | ||||
|  | ||||
| You should end up with a `micropolis.vgi` instead. The format is well-defined | ||||
| for double-sided disks so post-processing is not necessary. | ||||
|  | ||||
| While most operating systems use the standard Micropolis checksum, Vector | ||||
| Graphic MZOS uses a unique checksum.  The decoder will automatically detect | ||||
| the checksum type in use; however, a specific checksum type may be forced | ||||
| @@ -65,26 +28,24 @@ using the `--decoder.micropolis.checksum_type=n` where the type is one of: | ||||
| | 1    | Standard Micropolis (MDOS, CP/M, OASIS) | | ||||
| | 2    | Vector Graphic MZOS                     | | ||||
|  | ||||
| The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK, | ||||
| and SETSEC, but no function to select the head/side. Double-sided floppies | ||||
| could be represented as having either twice the number of sectors, for CHS, or | ||||
| twice the number of tracks, HCS; the second side's tracks in opposite order | ||||
| logically followed the first side (e.g., tracks 77-153). Micropolis disks | ||||
| tended to be the latter. FluxEngine always emits CHS format disks, so you may | ||||
| need to apply extra options to change the format if desired. | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do one of: | ||||
|   - : | ||||
|       - `143`: 143kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod I | ||||
|       - `287`: 287kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod I | ||||
|       - `315`: 315kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod II | ||||
|       - `630`: 630kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod II | ||||
|   - `vgi`: Read/write VGI format images with 275 bytes per sector | ||||
|  | ||||
| ``` | ||||
| fluxengine write micropolis143 # single-sided Mod I | ||||
| fluxengine write micropolis287 # double-sided Mod I | ||||
| fluxengine write micropolis315 # single-sided Mod II | ||||
| fluxengine write micropolis630 # double-sided Mod II | ||||
|  | ||||
| fluxengine write micropolis143 --vgi # single-sided Mod I | ||||
| fluxengine write micropolis287 --vgi # double-sided Mod I | ||||
| fluxengine write micropolis315 --vgi # single-sided Mod II | ||||
| fluxengine write micropolis630 --vgi # double-sided Mod II | ||||
| ``` | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [Micropolis 1040/1050 S-100 Floppy Disk Subsystems User's Manual][micropolis1040/1050]. | ||||
|     Section 6, pages 261-266. Documents pre-ECC sector format. Note that the | ||||
| @@ -99,3 +60,4 @@ Useful references | ||||
| [micropolis1040/1050]: http://www.bitsavers.org/pdf/micropolis/metafloppy/1084-01_1040_1050_Users_Manual_Apr79.pdf | ||||
| [vectordualmode]: http://bitsavers.org/pdf/vectorGraphic/hardware/7200-1200-02-1_Dual-Mode_Disk_Controller_Board_Engineering_Documentation_Feb81.pdf | ||||
| [altairz80]: http://www.bitsavers.org/simh.trailing-edge.com_201206/pdf/altairz80_doc.pdf | ||||
|  | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
| Disk: DVK MX | ||||
| ============ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Soviet-era PDP-11 clone | ||||
|  | ||||
| The DVK (in Russian, ДВК, Диалоговый вычислительный комплекс or Dialogue | ||||
| The DVK (in Russian, 沾7d65 | ||||
| Computing Complex) was a late 1970s Soviet personal computer, a cut-down | ||||
| version of the professional SM EVM (СМ ЭВМ, abbreviation of Система Малых ЭВМ | ||||
| version of the professional SM EVM (ⵁ241c | ||||
| --- literally System of Mini Computers), which _itself_ was an unlicensed | ||||
| clone of the PDP-11. The MX board was an early floppy drive controller board | ||||
| for it. | ||||
|  | ||||
| <div style="text-align: center"> | ||||
| <a href="http://www.leningrad.su/museum/show_big.php?n=1006"><img src="dvk3m.jpg" style="max-width: 60%" alt="A Durango F85, held precariously"></a> | ||||
| <a href="http://www.leningrad.su/museum/show_big.php?n=1006"><img src="dvk3m.jpg" style="max-width: 60%" alt="A DVK computer"></a> | ||||
| </div> | ||||
|  | ||||
| The MX format is interesting in that it has to be read a track at a time. The | ||||
| @@ -38,23 +38,11 @@ A track is: | ||||
| The checksum is just the unsigned integer sum of all the words in the sector. | ||||
| Words are all stored little-endian. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| ``` | ||||
| fluxengine read mx440 | ||||
| ``` | ||||
| (no options) | ||||
|  | ||||
| You should end up with an `mx.img` which will vary in length depending on the format. The default is double-sided 80-track. For the other formats, use: | ||||
|  | ||||
|   * single-sided 40-track: `mx110` | ||||
|   * double-sided 40-track: `mx220_ds` | ||||
|   * single-sided 80-track: `mx220_ss` | ||||
|   * double-sided 80-track: `mx440` | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [The Soviet Digital Electronics | ||||
|     Museum](http://www.leningrad.su/museum/main.php) (source of the image | ||||
| @@ -63,3 +51,4 @@ Useful references | ||||
|   - [a random post on the HxC2001 support | ||||
|     forum](http://torlus.com/floppy/forum/viewtopic.php?t=1384) with lots of | ||||
|     information on the format | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								doc/disk-n88basic.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								doc/disk-n88basic.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # PC8800/PC98 5.25"/3.5" 77-track 26-sector DSHD | ||||
|  | ||||
| The N88-BASIC disk format is the one used by the operating system of the same | ||||
| name for the Japanese PC8800 and PC98 computers. It is another IBM scheme | ||||
| variant, and is very similar to some mixed-format CP/M disk formats, where | ||||
| track 0 side 0 uses 128-byte single density sectors and the rest of the disk | ||||
| uses 512-byte double density sectors. (The reason for this is that the PC8800 | ||||
| boot ROM could only read single density data.) | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Northstar | ||||
| ================ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 5.25" hard sectored | ||||
|  | ||||
| Northstar Floppy disks use 10-sector hard sectored disks with either FM or MFM | ||||
| encoding.  They may be single- or double-sided.  Each of the 10 sectors contains | ||||
| @@ -18,51 +18,14 @@ writer are provided for double-sided disks.  The .nsi image writer supports | ||||
| both single- and double-sided disks; however single-sided .nsi images are | ||||
| equivalent to .img images. | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| You must use a 48-TPI (40-track) 300RPM 5.25" floppy drive. | ||||
| (no options) | ||||
|  | ||||
| To read a double-sided North Star floppy, run: | ||||
|  | ||||
| ``` | ||||
| fluxengine read <format> | ||||
| ``` | ||||
|  | ||||
| ...where `<format>` is one of the formats listed below. | ||||
|  | ||||
| You should end up with a `northstar.nsi` with a file size dependent on the floppy | ||||
| disk type. | ||||
|  | ||||
| Writing disks | ||||
| ------------- | ||||
|  | ||||
| You must use a 48-TPI (40-track) 300RPM 5.25" floppy drive and make | ||||
| sure that the drive's spindle speed is adjusted to exactly 300RPM. | ||||
|  | ||||
| To write a double-sided North Star floppy, run: | ||||
|  | ||||
| ``` | ||||
| fluxengine write <format> -i image_to_write.nsi | ||||
| ``` | ||||
|  | ||||
| ...where `<format>` is one of the formats listed below. | ||||
|  | ||||
| Available formats | ||||
| ----------------- | ||||
|  | ||||
| The following formats are supported: | ||||
|  | ||||
| | Format name    | Disk Type                           | File Size (bytes) | | ||||
| | -------------- | ----------------------------------- | ----------------- | | ||||
| | `northstar87`  | Single-Sided, Single-Density (SSSD) | 89,600            | | ||||
| | `northstar175` | Single-Sided, Double-Density (SSDD) | 179,200           | | ||||
| | `northstar350` | Double-Sided, Double-Density (DSDD) | 358,400           | | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [MICRO-DISK SYSTEM MDS-A-D DOUBLE DENSITY Manual][northstar_mds]. | ||||
|     Page 33 documents sector format for single- and double-density. | ||||
|  | ||||
| [northstar_mds]: http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf | ||||
|  | ||||
|   | ||||
							
								
								
									
										20
									
								
								doc/disk-psos.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								doc/disk-psos.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 800kB DSDD with PHILE | ||||
|  | ||||
| pSOS was an influential real-time operating system from the 1980s, used mainly | ||||
| on 68000-based machines, lasting up until about 2000 when it was bought (and | ||||
| cancelled) by Wind River. It had its own floppy disk format and file system, | ||||
| both of which are partially supported here. | ||||
|  | ||||
| The PHILE file system is almost completely undocumented and so many of the data | ||||
| structures have had to be reverse engineered and are not well known. Please | ||||
| [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if you know | ||||
| anything about it. | ||||
|  | ||||
| The floppy disk format itself is an IBM scheme variation with 1024-byte sectors | ||||
| and, oddly, swapped sides. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
							
								
								
									
										20
									
								
								doc/disk-rolandd20.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								doc/disk-rolandd20.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 3.5" electronic synthesiser disks | ||||
|  | ||||
| The Roland D20 is a classic electronic synthesiser with a built-in floppy | ||||
| drive, used for saving MIDI sequences and samples. | ||||
|  | ||||
| Weirdly, it seems to use precisely the same format as the Brother word | ||||
| processors: a thoroughly non-IBM-compatible custom GCR system. | ||||
|  | ||||
| FluxEngine pretends to support this, but it has had almost no testing, the only | ||||
| disk image I have seen for it was mostly corrupt, and very little is known | ||||
| about the format, so I have no idea whether it's correct or not. | ||||
|  | ||||
| Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if | ||||
| you know anything about it. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
							
								
								
									
										11
									
								
								doc/disk-rx50.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								doc/disk-rx50.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 400kB 5.25" 80-track 10-sector SSDD | ||||
|  | ||||
| The Digital RX50 is one of the external floppy drive units used by Digital's | ||||
| range of computers, especially the DEC Rainbow microcomputer. It is a fairly | ||||
| vanilla single-sided IBM scheme variation. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
							
								
								
									
										4
									
								
								doc/disk-shugart_drive.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								doc/disk-shugart_drive.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Adjust configuration for a Shugart drive | ||||
|  | ||||
| (This format has no documentation. Please file a bug.) | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Smaky 6 | ||||
| ============= | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 308kB 5.25" 77-track 16-sector SSDD, hard sectored | ||||
|  | ||||
| The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based | ||||
| around a Z80 processor and has one or two Micropolis 5.25" drives which use | ||||
| @@ -12,32 +12,15 @@ FluxEngine supports these, although because the Micropolis drives use a 100tpi | ||||
| track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You | ||||
| will have to find a 100tpi drive from somewhere (they're rare). | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| You must use a 100-tpi 80-track 5.25" floppy drive. | ||||
|  | ||||
| To read a Smaky 6 floppy, do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read smaky6 | ||||
| ``` | ||||
|  | ||||
| You should end up with a `smaky6.img` file. | ||||
|  | ||||
|  | ||||
| Filesystem access | ||||
| ----------------- | ||||
|  | ||||
| There is experimental read-only support for the Smaky 6 filesystem, allowing | ||||
| the directory to be listed and files read from disks. It's not known whether | ||||
| this is completely correct, so don't trust it! See the [Filesystem | ||||
| Access](filesystem.md) page for more information. | ||||
| this is completely correct, so don't trust it! | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| (no options) | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo) | ||||
|      | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: TI DS990 FD1000 | ||||
| ===================== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 1126kB 8" DSSD | ||||
|  | ||||
| The Texas Instruments DS990 was a multiuser modular computing system from 1998, | ||||
| based around the TMS-9900 processor (as used by the TI-99). It had an 8" floppy | ||||
| @@ -16,31 +16,12 @@ href="https://www.old-computers.com/museum/computer.asp?st=1&c=1025">old-compute | ||||
|  | ||||
| FluxEngine will read and write these (but only the DSDD MFM variant). | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read tids990 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `tids990.img` which is 1153152 bytes long. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine write tids990 -i tids990.img | ||||
| ``` | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [The FD1000 Depot Maintenance | ||||
| 	Manual](http://www.bitsavers.org/pdf/ti/990/disk/2261885-9701_FD1000depotVo1_Jan81.pdf) | ||||
|  | ||||
|         Manual](http://www.bitsavers.org/pdf/ti/990/disk/2261885-9701_FD1000depotVo1_Jan81.pdf) | ||||
|  | ||||
|   | ||||
							
								
								
									
										12
									
								
								doc/disk-tiki.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								doc/disk-tiki.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # CP/M | ||||
|  | ||||
| The Tiki 100 is a Z80-based Norwegian microcomputer from the mid 1980s intended | ||||
| for eductional use. It mostly ran an unbranded CP/M clone, and uses fairly | ||||
| normal CP/M disks --- IBM scheme and from 128 to 512 bytes per sector depending | ||||
| on the precise format.  | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| @@ -1,42 +0,0 @@ | ||||
| Disk: TRS-80 | ||||
| ============ | ||||
|  | ||||
| The TRS-80 models I, III and IV (but not the II, 100, 2000, Colour Computer | ||||
| or Pocket Computer) was a popular line of Z80-based home computers made by | ||||
| Tandy Corporation and sold by Radio Shack. There were some of the first | ||||
| generation of domestic micromputers, with the Model I released in 1978. | ||||
|  | ||||
| There were a myriad of different floppy disk interfaces, some produced by | ||||
| Tandy and some by third parties, using all the various combinations of 40- | ||||
| and 80-track, FM, MFM, etc. | ||||
|  | ||||
| Luckily the encoding scheme was mostly compatible with the IBM scheme, with a | ||||
| few minor variations: when using FM encoding, the TRS-80 wrote the sectors on | ||||
| track 17 (where the directory was) with a non-standard DAM byte. | ||||
|  | ||||
| FluxEngine's IBM reader can handle TRS-80 disks natively. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read ibm -o trs80.jv3 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `trs80.jv3` of the appropriate size. It's a simple | ||||
| array of sectors in JV3 format. | ||||
|  | ||||
| If you've got a 40-track disk, use `--cylinders=0-79x2`. | ||||
|  | ||||
| If you've got a single density disk, use | ||||
| `--decoder.ibm.trackdata.read_fm=true`. (Double density is the default.) | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [The JV3 file format](https://www.tim-mann.org/trs80/dskspec.html): | ||||
| 	documents the most popular emulator disk image. | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Victor 9000 | ||||
| ================= | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 1224kB 5.25" DSDD GCR | ||||
|  | ||||
| The Victor 9000 / Sirius One was a rather strange old 8086-based machine | ||||
| which used a disk format very reminiscent of the Commodore format; not a | ||||
| @@ -8,7 +8,7 @@ sector GCR disks, with a variable-speed drive and a varying number of sectors | ||||
| per track --- from 19 to 12. Disks can be double-sided, meaning that they can | ||||
| store 1224kB per disk, which was almost unheard of back then. Because the way | ||||
| that the tracks on head 1 are offset from head 0 (this happens with all disks), | ||||
| the speed zone allocation on head 1 differ from head 0... | ||||
| the speed zone allocation on head 1 differs from head 0... | ||||
|  | ||||
| | Zone | Head 0 tracks | Head 1 tracks | Sectors | Original period (ms) | | ||||
| |:----:|:-------------:|:-------------:|:-------:|:--------------------:| | ||||
| @@ -34,39 +34,11 @@ and track 39 on Head 1. | ||||
|  | ||||
| FluxEngine can read and write both the single-sided and double-sided variants.  | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read <format> | ||||
|  | ||||
| ``` | ||||
|  | ||||
| ...where `<format>` can be `victor9k_ss` or `victor9k_ds`.  | ||||
|  | ||||
| For `victor9k_ss` you should end up with an `victor9k.img` which is 627200 bytes long.  | ||||
| For `victor9k_ds` you should end up with an `victor9k.img` which is 1224192 bytes long. | ||||
|  | ||||
| **Big warning!** The image is triangular, where each track occupies a different | ||||
| amount of space. Victor disk images are complicated due to the way the tracks | ||||
| are different sizes and the odd sector size. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read victor9k_ss -i victor9k.img | ||||
| ``` | ||||
|  | ||||
| **Big warning!** This uses the same triangular disk image that reading uses. | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   - [The Victor 9000 technical reference manual](http://bitsavers.org/pdf/victor/victor9000/Victor9000TechRef_Jun82.pdf) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| Disk: Zilog MCZ | ||||
| =============== | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 320kB 8" 77-track SSSD hard-sectored | ||||
|  | ||||
| The Zilog MCZ is an extremely early Z80 development system, produced by | ||||
| Zilog, which came out in 1976. It used twin 8-inch hard sectored floppy | ||||
| @@ -24,22 +24,14 @@ I haven't been able to try this for real. If anyone has any of these disks, | ||||
| an 8-inch drive, a FluxEngine and the appropriate adapter, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new)... | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| ## Options | ||||
|  | ||||
| Just do: | ||||
| (no options) | ||||
|  | ||||
| ``` | ||||
| fluxengine read zilogmcz | ||||
| ``` | ||||
|  | ||||
| You should end up with an `zilogmcz.img` which is 315392 bytes long. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
| ## References | ||||
|  | ||||
|   * [About the Zilog MCZ](http://www.retrotechnology.com/restore/zilog.html), | ||||
|     containing lots of useful links | ||||
|  | ||||
|   * [The hardware user's manual](https://amaus.org/static/S100/zilog/ZDS/Zilog%20ZDS%201-25%20Hardware%20Users%20Manual.pdf) | ||||
|    | ||||
|  | ||||
|   | ||||
| @@ -155,6 +155,14 @@ more common tools. | ||||
| 	encoding. You can specify a profile if you want to write a subset of the | ||||
| 	disk. | ||||
|  | ||||
|   - `fluxengine merge -s <fluxfile> -s <fluxfile...> -d <fluxfile` | ||||
|  | ||||
|     Merges data from multiple flux files together. This is useful if you have | ||||
|     several reads from an unreliable disk where each read has a different set | ||||
|     of good sectors. By merging the flux files, you get to combine all the | ||||
|     data. Don't use this on reads of different disks, for obvious results! Note | ||||
|     that this works on flux files, not on flux sources. | ||||
|  | ||||
|   - `fluxengine inspect -s <flux source> -c <cylinder> -h <head> -B` | ||||
|  | ||||
| 	Reads flux (possibly from a disk) and does various analyses of it to try | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #!/bin/sh | ||||
| dir=`dirname "$0"` | ||||
| cd "$dir" | ||||
| export DYLD_FALLBACK_FRAMEWORK_PATH=../Resources | ||||
| export DYLD_FALLBACK_LIBRARY_PATH=../Resources:/opt/local/lib | ||||
| exec ./fluxengine-gui "$@" | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								extras/fluxfile.piko
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/fluxfile.piko
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extras/fluxfile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/fluxfile.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 365 B | 
							
								
								
									
										
											BIN
										
									
								
								extras/hardware.piko
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/hardware.piko
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extras/hardware.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/hardware.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 428 B | 
							
								
								
									
										
											BIN
										
									
								
								extras/imagefile.piko
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/imagefile.piko
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extras/imagefile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/imagefile.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 327 B | 
| @@ -9,6 +9,7 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/decoders/fmmfm.cc \ | ||||
| 	lib/encoders/encoders.cc \ | ||||
| 	lib/environment.cc \ | ||||
| 	lib/fl2.cc \ | ||||
| 	lib/flags.cc \ | ||||
| 	lib/fluxmap.cc \ | ||||
| 	lib/fluxsink/a2rfluxsink.cc \ | ||||
| @@ -18,6 +19,7 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/fluxsink/hardwarefluxsink.cc \ | ||||
| 	lib/fluxsink/scpfluxsink.cc \ | ||||
| 	lib/fluxsink/vcdfluxsink.cc \ | ||||
| 	lib/fluxsource/a2rfluxsource.cc \ | ||||
| 	lib/fluxsource/cwffluxsource.cc \ | ||||
| 	lib/fluxsource/erasefluxsource.cc \ | ||||
| 	lib/fluxsource/fl2fluxsource.cc \ | ||||
| @@ -69,14 +71,17 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/utils.cc \ | ||||
| 	lib/vfs/acorndfs.cc \ | ||||
| 	lib/vfs/amigaffs.cc \ | ||||
| 	lib/vfs/appledos.cc \ | ||||
| 	lib/vfs/applesingle.cc \ | ||||
| 	lib/vfs/brother120fs.cc \ | ||||
| 	lib/vfs/cbmfs.cc \ | ||||
| 	lib/vfs/cpmfs.cc \ | ||||
| 	lib/vfs/fatfs.cc \ | ||||
| 	lib/vfs/lif.cc \ | ||||
| 	lib/vfs/machfs.cc \ | ||||
| 	lib/vfs/prodos.cc \ | ||||
| 	lib/vfs/smaky6fs.cc \ | ||||
| 	lib/vfs/philefs.cc \ | ||||
| 	lib/vfs/vfs.cc \ | ||||
| 	lib/vfs/fluxsectorinterface.cc \ | ||||
| 	lib/vfs/imagesectorinterface.cc \ | ||||
|   | ||||
							
								
								
									
										24
									
								
								lib/bytes.cc
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								lib/bytes.cc
									
									
									
									
									
								
							| @@ -47,6 +47,12 @@ Bytes::Bytes(const std::string& s): | ||||
| 	_high(s.size()) | ||||
| {} | ||||
|  | ||||
| Bytes::Bytes(const char* s): | ||||
| 	_data(createVector((const uint8_t*)s, strlen(s))), | ||||
| 	_low(0), | ||||
| 	_high(strlen(s)) | ||||
| {} | ||||
|  | ||||
| Bytes::Bytes(std::initializer_list<uint8_t> data): | ||||
|     _data(createVector(data)), | ||||
|     _low(0), | ||||
| @@ -215,6 +221,24 @@ Bytes Bytes::reverseBits() const | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::operator + (const Bytes& other) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     bw += *this; | ||||
|     bw += other; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::operator * (size_t count) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     while (count--) | ||||
|         bw += *this; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| uint8_t toByte( | ||||
|     std::vector<bool>::const_iterator start, | ||||
|     std::vector<bool>::const_iterator end) | ||||
|   | ||||
| @@ -12,6 +12,7 @@ public: | ||||
|     Bytes(); | ||||
|     Bytes(unsigned size); | ||||
|     Bytes(const uint8_t* ptr, size_t len); | ||||
|     Bytes(const char* data); | ||||
|     Bytes(const std::string& data); | ||||
|     Bytes(std::initializer_list<uint8_t> data); | ||||
|     Bytes(std::shared_ptr<std::vector<uint8_t>> data); | ||||
| @@ -64,6 +65,9 @@ public: | ||||
| 	std::vector<bool> toBits() const; | ||||
| 	Bytes reverseBits() const; | ||||
|  | ||||
| 	Bytes operator + (const Bytes& other); | ||||
| 	Bytes operator * (size_t count); | ||||
|  | ||||
|     ByteReader reader() const; | ||||
|     ByteWriter writer(); | ||||
|  | ||||
|   | ||||
| @@ -12,38 +12,64 @@ import "lib/drive.proto"; | ||||
| import "lib/common.proto"; | ||||
| import "lib/layout.proto"; | ||||
|  | ||||
| // NEXT_TAG: 21 | ||||
| message ConfigProto { | ||||
| 	optional string comment = 8; | ||||
| 	optional bool is_extension = 13; | ||||
| 	repeated string include = 19; | ||||
|  | ||||
| 	optional LayoutProto layout = 18; | ||||
|  | ||||
| 	optional ImageReaderProto image_reader = 12; | ||||
| 	optional ImageWriterProto image_writer = 9; | ||||
| 	 | ||||
| 	optional FluxSourceProto flux_source = 10; | ||||
| 	optional FluxSinkProto flux_sink = 11; | ||||
| 	optional DriveProto drive = 15; | ||||
|  | ||||
| 	optional EncoderProto encoder = 3; | ||||
| 	optional DecoderProto decoder = 4; | ||||
| 	optional UsbProto usb = 5; | ||||
|  | ||||
| 	optional RangeProto tracks = 6; | ||||
| 	optional RangeProto heads = 7; | ||||
| 	optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ]; | ||||
|  | ||||
| 	optional FilesystemProto filesystem = 17; | ||||
| 	 | ||||
| 	repeated OptionProto option = 20; | ||||
| enum SupportStatus | ||||
| { | ||||
| 	UNSUPPORTED = 0; | ||||
| 	DINOSAUR = 1; | ||||
| 	UNICORN = 2; | ||||
| } | ||||
|  | ||||
| message OptionProto { | ||||
| 	optional string name = 1 [(help) = "Option name" ]; | ||||
| 	optional string comment = 2 [(help) = "Help text for option" ]; | ||||
| 	optional string message = 3 [(help) = "Message to display when option is in use" ]; | ||||
| 	optional ConfigProto config = 4 [(help) = "Option data", (recurse) = false ]; | ||||
| // NEXT_TAG: 27 | ||||
| message ConfigProto | ||||
| { | ||||
| 	optional string shortname = 24; | ||||
|     optional string comment = 8; | ||||
|     optional bool is_extension = 13; | ||||
| 	repeated string documentation = 23; | ||||
| 	optional SupportStatus read_support_status = 25 [ default = UNSUPPORTED ]; | ||||
| 	optional SupportStatus write_support_status = 26 [ default = UNSUPPORTED ]; | ||||
|  | ||||
|     optional LayoutProto layout = 18; | ||||
|  | ||||
|     optional ImageReaderProto image_reader = 12; | ||||
|     optional ImageWriterProto image_writer = 9; | ||||
|  | ||||
|     optional FluxSourceProto flux_source = 10; | ||||
|     optional FluxSinkProto flux_sink = 11; | ||||
|     optional DriveProto drive = 15; | ||||
|  | ||||
|     optional EncoderProto encoder = 3; | ||||
|     optional DecoderProto decoder = 4; | ||||
|     optional UsbProto usb = 5; | ||||
|  | ||||
|     optional RangeProto tracks = 6; | ||||
|     optional RangeProto heads = 7; | ||||
|     optional float tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ]; | ||||
|  | ||||
|     optional FilesystemProto filesystem = 17; | ||||
|  | ||||
|     repeated OptionProto option = 20; | ||||
| 	repeated OptionGroupProto option_group = 22; | ||||
| } | ||||
|  | ||||
| // NEXT_TAG: 7 | ||||
| message OptionProto | ||||
| { | ||||
|     optional string name = 1 [ (help) = "option name" ]; | ||||
|     optional string comment = 2 [ (help) = "help text for option" ]; | ||||
|     optional string message = 3 | ||||
|         [ (help) = "message to display when option is in use" ]; | ||||
| 	optional bool set_by_default = 6 [ | ||||
| 		(help) = "this option is applied by default", | ||||
| 		default = false | ||||
| 	]; | ||||
|     optional ConfigProto config = 4 | ||||
|         [ (help) = "option data", (recurse) = false ]; | ||||
| } | ||||
|  | ||||
| message OptionGroupProto | ||||
| { | ||||
| 	optional string comment = 1 [ (help) = "help text for option group" ]; | ||||
| 	repeated OptionProto option = 2; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
| #include "arch/micropolis/micropolis.h" | ||||
| #include "arch/mx/mx.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "arch/rolandd20/rolandd20.h" | ||||
| #include "arch/smaky6/smaky6.h" | ||||
| #include "arch/tids990/tids990.h" | ||||
| #include "arch/victor9k/victor9k.h" | ||||
| @@ -49,6 +50,7 @@ std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config) | ||||
|             {DecoderProto::kMicropolis, createMicropolisDecoder }, | ||||
|             {DecoderProto::kMx,         createMxDecoder         }, | ||||
|             {DecoderProto::kNorthstar,  createNorthstarDecoder  }, | ||||
|             {DecoderProto::kRolandd20,  createRolandD20Decoder  }, | ||||
|             {DecoderProto::kSmaky6,     createSmaky6Decoder     }, | ||||
|             {DecoderProto::kTids990,    createTids990Decoder    }, | ||||
|             {DecoderProto::kVictor9K,   createVictor9kDecoder   }, | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import "arch/macintosh/macintosh.proto"; | ||||
| import "arch/micropolis/micropolis.proto"; | ||||
| import "arch/mx/mx.proto"; | ||||
| import "arch/northstar/northstar.proto"; | ||||
| import "arch/rolandd20/rolandd20.proto"; | ||||
| import "arch/smaky6/smaky6.proto"; | ||||
| import "arch/tids990/tids990.proto"; | ||||
| import "arch/victor9k/victor9k.proto"; | ||||
| @@ -20,7 +21,7 @@ import "arch/zilogmcz/zilogmcz.proto"; | ||||
| import "lib/fluxsink/fluxsink.proto"; | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| //NEXT: 31 | ||||
| //NEXT: 32 | ||||
| message DecoderProto { | ||||
| 	optional double pulse_debounce_threshold = 1 [default = 0.30, | ||||
| 		(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; | ||||
| @@ -47,6 +48,7 @@ message DecoderProto { | ||||
| 		MicropolisDecoderProto micropolis = 14; | ||||
| 		MxDecoderProto mx = 15; | ||||
| 		NorthstarDecoderProto northstar = 24; | ||||
| 		RolandD20DecoderProto rolandd20 = 31; | ||||
| 		Smaky6DecoderProto smaky6 = 30; | ||||
| 		Tids990DecoderProto tids990 = 16; | ||||
| 		Victor9kDecoderProto victor9k = 17; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user