mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			107 Commits
		
	
	
		
			drivetypes
			...
			d20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1b859015ae | ||
|  | 294ac87503 | ||
|  | c297adb0c7 | ||
|  | 446b965794 | ||
|  | 1927cc7fe1 | ||
|  | 4eca254daf | ||
|  | c7d4fee3f6 | ||
|  | a6f798ae5b | ||
|  | c9ae836e52 | ||
|  | e3ffa63f7f | ||
|  | 4ffc2cc1dc | ||
|  | 7f9ba14687 | ||
|  | a24933e272 | ||
|  | 20bdacbecf | ||
|  | 1f5903a9a0 | ||
|  | bb073b6bb3 | ||
|  | 516241f8f5 | ||
|  | 977b6831a0 | ||
|  | c61effb54f | ||
|  | 346d989944 | ||
|  | 60a73c8d1e | ||
|  | e52db4a837 | ||
|  | 4e317643bc | ||
|  | 5f520bf375 | ||
|  | 2efe521b3a | ||
|  | 5c21103646 | ||
|  | 9444696f37 | ||
|  | 082fe4e787 | ||
|  | 5e13cf23f9 | ||
|  | 8f98a1f557 | ||
|  | 5b21e8798b | ||
|  | b9ef5b7db8 | ||
|  | 9867f8c302 | ||
|  | 315889faf6 | ||
|  | 798e8fee89 | ||
|  | e1c49db329 | ||
|  | dae9537472 | ||
|  | 1330d56cdd | ||
|  | 6ce3ce20d0 | ||
|  | 362c5ee9b0 | ||
|  | 0f34ce0278 | ||
|  | 0c27c7c4c8 | ||
|  | 9db6efe7a2 | ||
|  | 8b8a22d7fb | ||
|  | 0a70344bc1 | ||
|  | e77d01911c | ||
|  | d4c0853e1f | ||
|  | 363a4e959c | ||
|  | 9336a04681 | ||
|  | 214ff38749 | ||
|  | a8f3c01d8b | ||
|  | 4da6585ef9 | ||
|  | df40100feb | ||
|  | f2d92e93fb | ||
|  | b4d8d569d2 | ||
|  | 854b3e9c59 | ||
|  | 28ca2b72f1 | ||
|  | 7781c8179f | ||
|  | 69ece3ffa0 | ||
|  | 53adcd92ed | ||
|  | 2bef6ca646 | ||
|  | bab350d771 | ||
|  | 048dac223f | ||
|  | b7634da310 | ||
|  | 882c92c64a | ||
|  | 4592dbe28b | ||
|  | edc0f21ae7 | ||
|  | 8715b7f6c1 | ||
|  | 99511910dd | ||
|  | a03478b011 | ||
|  | 5c428e1f07 | ||
|  | ee57615735 | ||
|  | 67300e5769 | ||
|  | 873e05051c | ||
|  | 4daaec46a7 | ||
|  | dd8cc7bfd4 | ||
|  | 5568ac382f | ||
|  | dcdb3e4455 | ||
|  | 17b29b1626 | ||
|  | dcfcc6271c | ||
|  | 1d77ba6429 | ||
|  | ff5f019ac1 | ||
|  | e61eeb8c6f | ||
|  | 68d22e7f54 | ||
|  | 790f0a42e3 | ||
|  | 08e9e508cc | ||
|  | ad1a8d608f | ||
|  | d74ed71023 | ||
|  | 0c7f9e0888 | ||
|  | ba5f6528a8 | ||
|  | 65cf552ec2 | ||
|  | 715c0a0c42 | ||
|  | 9e383575d1 | ||
|  | d84c366480 | ||
|  | 42e6c11081 | ||
|  | 9ba3f90f1e | ||
|  | 24ff51274b | ||
|  | 4c4c752827 | ||
|  | 5022b67e4a | ||
|  | 6b990a9f51 | ||
|  | e69ce3b8df | ||
|  | cf537b6222 | ||
|  | 9d1160faff | ||
|  | ed4067f194 | ||
|  | d4b55cd8f5 | ||
|  | baaeb0bca7 | ||
|  | 466c3c34e5 | 
							
								
								
									
										11
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,9 +2,13 @@ name: C/C++ CI | ||||
|  | ||||
| on: [push] | ||||
|  | ||||
| concurrency:  | ||||
|   group: environment-${{ github.head_ref }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| jobs: | ||||
|   build-linux: | ||||
|     runs-on: ubuntu-20.04 | ||||
|     runs-on: ubuntu-22.04 | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
| @@ -39,7 +43,7 @@ jobs: | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine.FluxEngine.pkg | ||||
|         path: fluxengine/FluxEngine.pkg | ||||
|  | ||||
|   build-windows: | ||||
|     runs-on: windows-latest | ||||
| @@ -65,6 +69,9 @@ jobs: | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - name: update-protobuf | ||||
|       run: | | ||||
|          pacman -U --noconfirm https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-protobuf-21.9-1-any.pkg.tar.zst | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,9 @@ | ||||
| name: Autorelease | ||||
|  | ||||
| concurrency:  | ||||
|   group: environment-${{ github.head_ref }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
| @@ -32,6 +36,10 @@ jobs: | ||||
|           vim | ||||
|     - uses: actions/checkout@v3 | ||||
|  | ||||
|     - name: update-protobuf | ||||
|       run: | | ||||
|          pacman -U --noconfirm https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-protobuf-21.9-1-any.pkg.tar.zst | ||||
|  | ||||
|     - name: build | ||||
|       run: | | ||||
|         make -j2 | ||||
|   | ||||
							
								
								
									
										118
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Special Windows settings. | ||||
| #Special Windows settings. | ||||
|  | ||||
| ifeq ($(OS), Windows_NT) | ||||
| 	MINGWBIN = /mingw32/bin | ||||
| @@ -16,12 +16,12 @@ ifeq ($(OS), Windows_NT) | ||||
| 		-Wno-deprecated-enum-float-conversion \ | ||||
| 		-Wno-deprecated-enum-enum-conversion | ||||
|  | ||||
| 	# Required to get the gcc run-time libraries on the path. | ||||
| #Required to get the gcc run - time libraries on the path. | ||||
| 	export PATH := $(PATH):$(MINGWBIN) | ||||
| 	EXT ?= .exe | ||||
| endif | ||||
|  | ||||
| # Special OSX settings. | ||||
| #Special OSX settings. | ||||
|  | ||||
| ifeq ($(shell uname),Darwin) | ||||
| 	PLATFORM = OSX | ||||
| @@ -30,14 +30,14 @@ ifeq ($(shell uname),Darwin) | ||||
| 		-framework Foundation | ||||
| endif | ||||
|  | ||||
| # Check the Make version. | ||||
| #Check the Make version. | ||||
|  | ||||
|  | ||||
| ifeq ($(findstring 4.,$(MAKE_VERSION)),) | ||||
| $(error You need GNU Make 4.x for this (if you're on OSX, use gmake).) | ||||
| endif | ||||
|  | ||||
| # Normal settings. | ||||
| #Normal settings. | ||||
|  | ||||
| OBJDIR ?= .obj | ||||
| CCPREFIX ?= | ||||
| @@ -65,6 +65,7 @@ CFLAGS += \ | ||||
| 	-I$(OBJDIR)/arch \ | ||||
| 	-I$(OBJDIR)/lib \ | ||||
| 	-I$(OBJDIR) \ | ||||
| 	-Wno-deprecated-declarations \ | ||||
|  | ||||
| LDFLAGS += \ | ||||
| 	-lz \ | ||||
| @@ -142,7 +143,7 @@ $(PROTO_SRCS): | $(PROTO_HDRS) | ||||
| $(PROTO_OBJS): CFLAGS += $(PROTO_CFLAGS) | ||||
| PROTO_LIB = $(OBJDIR)/libproto.a | ||||
| $(PROTO_LIB): $(PROTO_OBJS) | ||||
| PROTO_LDFLAGS = $(shell $(PKG_CONFIG) --libs protobuf) -pthread $(PROTO_LIB) | ||||
| PROTO_LDFLAGS = $(shell $(PKG_CONFIG) --libs protobuf) -pthread | ||||
| .PRECIOUS: $(PROTO_HDRS) $(PROTO_SRCS) | ||||
|  | ||||
| include dep/agg/build.mk | ||||
| @@ -164,57 +165,58 @@ include tests/build.mk | ||||
| do-encodedecodetest = $(eval $(do-encodedecodetest-impl)) | ||||
| define do-encodedecodetest-impl | ||||
|  | ||||
| tests: $(OBJDIR)/$1$3.flux.encodedecode | ||||
| $(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| tests: $(OBJDIR)/$1$$(subst $$(space),_,$3).flux.encodedecode | ||||
| $(OBJDIR)/$1$$(subst $$(space),_,$3).flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@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 | ||||
| tests: $(OBJDIR)/$1$$(subst $$(space),_,$3).scp.encodedecode | ||||
| $(OBJDIR)/$1$$(subst $$(space),_,$3).scp.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@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,,--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,agat,,--drive.tpi=96) | ||||
| $(call do-encodedecodetest,amiga,,--drive.tpi=135) | ||||
| $(call do-encodedecodetest,apple2,,--140 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,atarist,,--360 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--370 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--400 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--410 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--720 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--740 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--800 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,atarist,,--820 --drive.tpi=135) | ||||
| $(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,,--612) | ||||
| $(call do-encodedecodetest,victor9k,,--1224) | ||||
| $(call do-encodedecodetest,brother,,--120 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,brother,,--240 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--171 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--192 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,commodore,,--800 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,commodore,,--1620 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--264 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--616 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--770 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,ibm,,--1200 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--1232 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--1440 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,ibm,,--1680 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,ibm,,--180 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--160 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--320 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--360 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--720_96 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,ibm,,--720_135 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,mac,scripts/mac400_test.textpb,--400 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,mac,scripts/mac800_test.textpb,--800 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,n88basic,,--drive.tpi=96) | ||||
| $(call do-encodedecodetest,rx50,,--drive.tpi=96) | ||||
| $(call do-encodedecodetest,tids990,,--drive.tpi=48) | ||||
| $(call do-encodedecodetest,victor9k,,--612 --drive.tpi=96) | ||||
| $(call do-encodedecodetest,victor9k,,--1224 --drive.tpi=96) | ||||
|  | ||||
| do-corpustest = $(eval $(do-corpustest-impl)) | ||||
| define do-corpustest-impl | ||||
| @@ -231,20 +233,20 @@ 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,amiga.flux,amiga.adf,amiga --drive.tpi=135) | ||||
| $(call do-corpustest,atarist360.flux,atarist360.st,atarist --360 --drive.tpi=135) | ||||
| $(call do-corpustest,atarist720.flux,atarist720.st,atarist --720 --drive.tpi=135) | ||||
| $(call do-corpustest,brother120.flux,brother120.img,brother --120 --drive.tpi=135) | ||||
| $(call do-corpustest,cmd-fd2000.flux,cmd-fd2000.img,commodore --1620 --drive.tpi=135) | ||||
| $(call do-corpustest,ibm1232.flux,ibm1232.img,ibm --1232 --drive.tpi=96) | ||||
| $(call do-corpustest,ibm1440.flux,ibm1440.img,ibm --1440 --drive.tpi=135) | ||||
| $(call do-corpustest,mac800.flux,mac800.dsk,mac --800 --drive.tpi=135) | ||||
| $(call do-corpustest,micropolis315.flux,micropolis315.img,micropolis --315 --drive.tpi=100) | ||||
| $(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) | ||||
| $(call do-corpustest,victor9k_ss.flux,victor9k_ss.img,victor9k --612 --drive.tpi=96) | ||||
| $(call do-corpustest,victor9k_ds.flux,victor9k_ds.img,victor9k --1224 --drive.tpi=96) | ||||
|  | ||||
| endif | ||||
|  | ||||
| @@ -256,7 +258,7 @@ $(OBJDIR)/%.a: | ||||
| %.exe: | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo LINK $@ | ||||
| 	@$(CXX) -o $@ $^ $(LDFLAGS) $(LDFLAGS) | ||||
| 	@$(CXX) -o $@ $(filter %.o,$^) $(filter %.a,$^) $(LDFLAGS) $(filter %.a,$^) $(LDFLAGS) | ||||
|  | ||||
| $(OBJDIR)/%.o: %.cpp | ||||
| 	@mkdir -p $(dir $@) | ||||
|   | ||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							| @@ -35,11 +35,11 @@ Don't believe me? Watch the demo reel! | ||||
| </div> | ||||
|  | ||||
| **New!** The FluxEngine client software now works with | ||||
| [GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you | ||||
| [Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you | ||||
| can't find a PSoC5 development kit, or don't want to use the Cypress Windows | ||||
| tools for programming it, you can use one of these instead. Very nearly all | ||||
| FluxEngine features are available with the GreaseWeazle and it works out-of-the | ||||
| box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md) | ||||
| FluxEngine features are available with the Greaseweazle and it works out-of-the | ||||
| box. See the [dedicated Greaseweazle documentation page](doc/greaseweazle.md) | ||||
| for more information. | ||||
|  | ||||
| Where? | ||||
| @@ -65,7 +65,7 @@ following friendly articles: | ||||
|   - [Using a FluxEngine](doc/using.md) ∾ what to do with your new hardware ∾ | ||||
|     flux files and image files ∾ knowing what you're doing | ||||
|  | ||||
|   - [Using GreaseWeazle hardware with the FluxEngine client | ||||
|   - [Using Greaseweazle hardware with the FluxEngine client | ||||
|     software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to | ||||
|     go for help | ||||
|  | ||||
| @@ -129,16 +129,16 @@ choices because they can store multiple types of file system. | ||||
| | [`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 | 🦄 | 🦄 |  | | ||||
| | [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 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 | 🦖 |  |  | | ||||
| | [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND  | | ||||
| | [`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 | 🦖 |  |  | | ||||
| | [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 |  | ZDOS  | | ||||
| {: .datatable } | ||||
|  | ||||
| <!-- FORMATSEND --> | ||||
| @@ -260,5 +260,3 @@ __Important:__ Because of all these exceptions, if you distribute the | ||||
| FluxEngine package as a whole, you must comply with the terms of _all_ of the | ||||
| licensing terms. This means that __effectively the FluxEngine package is | ||||
| distributable under the terms of the GPL 2.0__. | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,9 +2,10 @@ | ||||
| #define AESLANIER_H | ||||
|  | ||||
| #define AESLANIER_RECORD_SEPARATOR 0x55555122 | ||||
| #define AESLANIER_SECTOR_LENGTH    256 | ||||
| #define AESLANIER_RECORD_SIZE      (AESLANIER_SECTOR_LENGTH + 5) | ||||
| #define AESLANIER_SECTOR_LENGTH 256 | ||||
| #define AESLANIER_RECORD_SIZE (AESLANIER_SECTOR_LENGTH + 5) | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createAesLanierDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createAesLanierDecoder( | ||||
|     const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -11,56 +11,54 @@ | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR); | ||||
|  | ||||
| /* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */ | ||||
| /* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine | ||||
|  * with it. */ | ||||
|  | ||||
| class AesLanierDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	AesLanierDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     AesLanierDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(SECTOR_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */ | ||||
|     { | ||||
|         /* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */ | ||||
|  | ||||
| 		readRawBits(16); | ||||
|         readRawBits(16); | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); | ||||
| 		const auto& reversed = bytes.reverseBits(); | ||||
|         const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE * 16); | ||||
|         const auto& bytes = | ||||
|             decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); | ||||
|         const auto& reversed = bytes.reverseBits(); | ||||
|  | ||||
| 		_sector->logicalTrack = reversed[1]; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalSector = reversed[2]; | ||||
|         _sector->logicalTrack = reversed[1]; | ||||
|         _sector->logicalSide = 0; | ||||
|         _sector->logicalSector = reversed[2]; | ||||
|  | ||||
| 		/* Check header 'checksum' (which seems far too simple to mean much). */ | ||||
|         /* Check header 'checksum' (which seems far too simple to mean much). */ | ||||
|  | ||||
| 		{ | ||||
| 			uint8_t wanted = reversed[3]; | ||||
| 			uint8_t got = reversed[1] + reversed[2]; | ||||
| 			if (wanted != got) | ||||
| 				return; | ||||
| 		} | ||||
|         { | ||||
|             uint8_t wanted = reversed[3]; | ||||
|             uint8_t got = reversed[1] + reversed[2]; | ||||
|             if (wanted != got) | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
| 		/* Check data checksum, which also includes the header and is | ||||
| 			* significantly better. */ | ||||
|         /* Check data checksum, which also includes the header and is | ||||
|          * significantly better. */ | ||||
|  | ||||
| 		_sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); | ||||
| 		uint16_t wanted = reversed.reader().seek(0x101).read_le16(); | ||||
| 		uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); | ||||
| 		_sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); | ||||
|         uint16_t wanted = reversed.reader().seek(0x101).read_le16(); | ||||
|         uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); | ||||
|         _sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createAesLanierDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new AesLanierDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new AesLanierDecoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -8,15 +8,13 @@ uint8_t agatChecksum(const Bytes& bytes) | ||||
| { | ||||
|     uint16_t checksum = 0; | ||||
|  | ||||
| 	for (uint8_t b : bytes) | ||||
| 	{ | ||||
| 		if (checksum > 0xff) | ||||
| 			checksum = (checksum + 1) & 0xff; | ||||
|     for (uint8_t b : bytes) | ||||
|     { | ||||
|         if (checksum > 0xff) | ||||
|             checksum = (checksum + 1) & 0xff; | ||||
|  | ||||
| 		checksum += b; | ||||
| 	} | ||||
|         checksum += b; | ||||
|     } | ||||
|  | ||||
| 	return checksum & 0xff; | ||||
|     return checksum & 0xff; | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -17,4 +17,3 @@ extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config); | ||||
| extern uint8_t agatChecksum(const Bytes& bytes); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -9,13 +9,14 @@ | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
|  | ||||
| // clang-format off | ||||
| /* | ||||
|  * data:    X  X  X  X   X  X  X  X   X  -  -  X   -  X  -  X   -  X  X  -   X  -  X  -  = 0xff956a | ||||
|  * flux:   01 01 01 01  01 01 01 01  01 00 10 01  00 01 00 01  00 01 01 00  01 00 01 00  = 0x555549111444 | ||||
|  *  | ||||
|  * data:    X  X  X  X   X  X  X  X   -  X  X  -   X  -  X  -   X  -  -  X   -  X  -  X  = 0xff6a95 | ||||
|  * flux:   01 01 01 01  01 01 01 01  00 01 01 00  01 00 01 00  01 00 10 01  00 01 00 01  = 0x555514444911 | ||||
|  *  | ||||
|  * | ||||
|  * Each pattern is prefixed with this one: | ||||
|  * | ||||
|  * data:          -  -   -  X   -  -   X  - = 0x12 | ||||
| @@ -30,65 +31,59 @@ | ||||
|  *          0100010010010010  = MFM encoded | ||||
|  *           1000100100100100 = with trailing zero | ||||
|  *            - - - X - - X - = effective bitstream = 0x12 | ||||
|  * | ||||
|  */ | ||||
| // clang-format on | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID); | ||||
| static const FluxPattern DATA_PATTERN(64, DATA_ID); | ||||
|  | ||||
| static const FluxMatchers ALL_PATTERNS = { | ||||
| 	&SECTOR_PATTERN, | ||||
| 	&DATA_PATTERN | ||||
| }; | ||||
| static const FluxMatchers ALL_PATTERNS = {&SECTOR_PATTERN, &DATA_PATTERN}; | ||||
|  | ||||
| class AgatDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	AgatDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     AgatDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ALL_PATTERNS); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ALL_PATTERNS); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw64() != SECTOR_ID) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw64() != SECTOR_ID) | ||||
|             return; | ||||
|  | ||||
| 		auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4); | ||||
| 		if (bytes[3] != 0x5a) | ||||
| 			return; | ||||
|         auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4); | ||||
|         if (bytes[3] != 0x5a) | ||||
|             return; | ||||
|  | ||||
| 		_sector->logicalTrack = bytes[1] >> 1; | ||||
| 		_sector->logicalSector = bytes[2]; | ||||
| 		_sector->logicalSide = bytes[1] & 1; | ||||
| 		_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|         _sector->logicalTrack = bytes[1] >> 1; | ||||
|         _sector->logicalSector = bytes[2]; | ||||
|         _sector->logicalSide = bytes[1] & 1; | ||||
|         _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|     } | ||||
|  | ||||
| 	void decodeDataRecord() override | ||||
| 	{ | ||||
| 		if (readRaw64() != DATA_ID) | ||||
| 			return; | ||||
|     void decodeDataRecord() override | ||||
|     { | ||||
|         if (readRaw64() != DATA_ID) | ||||
|             return; | ||||
|  | ||||
| 		Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE+2)*16)).slice(0, AGAT_SECTOR_SIZE+2); | ||||
|         Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE + 2) * 16)) | ||||
|                           .slice(0, AGAT_SECTOR_SIZE + 2); | ||||
|  | ||||
| 		if (bytes[AGAT_SECTOR_SIZE+1] != 0x5a) | ||||
| 			return; | ||||
|         if (bytes[AGAT_SECTOR_SIZE + 1] != 0x5a) | ||||
|             return; | ||||
|  | ||||
| 		_sector->data = bytes.slice(0, AGAT_SECTOR_SIZE); | ||||
| 		uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE]; | ||||
| 		uint8_t gotChecksum = agatChecksum(_sector->data); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = bytes.slice(0, AGAT_SECTOR_SIZE); | ||||
|         uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE]; | ||||
|         uint8_t gotChecksum = agatChecksum(_sector->data); | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new AgatDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new AgatDecoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -95,7 +95,7 @@ public: | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|             error("track data overrun"); | ||||
|         fillBitmapTo(_bits, _cursor, _bits.size(), {true, false}); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|   | ||||
| @@ -18,61 +18,61 @@ uint32_t amigaChecksum(const Bytes& bytes) | ||||
|  | ||||
| static uint8_t everyother(uint16_t x) | ||||
| { | ||||
| 	                  /* aabb ccdd eeff gghh */ | ||||
| 	x &= 0x6666;      /* 0ab0 0cd0 0ef0 0gh0 */ | ||||
| 	x >>= 1;          /* 00ab 00cd 00ef 00gh */ | ||||
| 	x |= x << 2;      /* abab cdcd efef ghgh */ | ||||
| 	x &= 0x3c3c;      /* 00ab cd00 00ef gh00 */ | ||||
| 	x >>= 2;          /* 0000 abcd 0000 efgh */ | ||||
| 	x |= x >> 4;      /* 0000 abcd abcd efgh */ | ||||
| 	return x; | ||||
|     /* aabb ccdd eeff gghh */ | ||||
|     x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */ | ||||
|     x >>= 1;     /* 00ab 00cd 00ef 00gh */ | ||||
|     x |= x << 2; /* abab cdcd efef ghgh */ | ||||
|     x &= 0x3c3c; /* 00ab cd00 00ef gh00 */ | ||||
|     x >>= 2;     /* 0000 abcd 0000 efgh */ | ||||
|     x |= x >> 4; /* 0000 abcd abcd efgh */ | ||||
|     return x; | ||||
| } | ||||
|  | ||||
| Bytes amigaInterleave(const Bytes& input) | ||||
| { | ||||
| 	Bytes output; | ||||
| 	ByteWriter bw(output); | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
| 	/* Write all odd bits. (Numbering starts at 0...) */ | ||||
|     /* Write all odd bits. (Numbering starts at 0...) */ | ||||
|  | ||||
| 	{ | ||||
| 		ByteReader br(input); | ||||
| 		while (!br.eof()) | ||||
| 		{ | ||||
| 			uint16_t x = br.read_be16(); | ||||
| 			x &= 0xaaaa;       /* a0b0 c0d0 e0f0 g0h0 */ | ||||
| 			x |= x >> 1;       /* aabb ccdd eeff gghh */ | ||||
| 			x = everyother(x); /* 0000 0000 abcd efgh */ | ||||
| 			bw.write_8(x); | ||||
| 		} | ||||
| 	} | ||||
|     { | ||||
|         ByteReader br(input); | ||||
|         while (!br.eof()) | ||||
|         { | ||||
|             uint16_t x = br.read_be16(); | ||||
|             x &= 0xaaaa;       /* a0b0 c0d0 e0f0 g0h0 */ | ||||
|             x |= x >> 1;       /* aabb ccdd eeff gghh */ | ||||
|             x = everyother(x); /* 0000 0000 abcd efgh */ | ||||
|             bw.write_8(x); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	/* Write all even bits. */ | ||||
|     /* Write all even bits. */ | ||||
|  | ||||
| 	{ | ||||
| 		ByteReader br(input); | ||||
| 		while (!br.eof()) | ||||
| 		{ | ||||
| 			uint16_t x = br.read_be16(); | ||||
| 			x &= 0x5555;       /* 0a0b 0c0d 0e0f 0g0h */ | ||||
| 			x |= x << 1;       /* aabb ccdd eeff gghh */ | ||||
| 			x = everyother(x); /* 0000 0000 abcd efgh */ | ||||
| 			bw.write_8(x); | ||||
| 		} | ||||
| 	} | ||||
|     { | ||||
|         ByteReader br(input); | ||||
|         while (!br.eof()) | ||||
|         { | ||||
|             uint16_t x = br.read_be16(); | ||||
|             x &= 0x5555;       /* 0a0b 0c0d 0e0f 0g0h */ | ||||
|             x |= x << 1;       /* aabb ccdd eeff gghh */ | ||||
|             x = everyother(x); /* 0000 0000 abcd efgh */ | ||||
|             bw.write_8(x); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	return output; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes amigaDeinterleave(const uint8_t*& input, size_t len) | ||||
| { | ||||
|     assert(!(len & 1)); | ||||
|     const uint8_t* odds = &input[0]; | ||||
|     const uint8_t* evens = &input[len/2]; | ||||
|     const uint8_t* evens = &input[len / 2]; | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
|     for (size_t i=0; i<len/2; i++) | ||||
|     for (size_t i = 0; i < len / 2; i++) | ||||
|     { | ||||
|         uint8_t o = *odds++; | ||||
|         uint8_t e = *evens++; | ||||
| @@ -81,11 +81,15 @@ Bytes amigaDeinterleave(const uint8_t*& input, size_t len) | ||||
|          * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN | ||||
|          */ | ||||
|         uint16_t result = | ||||
|             (((e * 0x0101010101010101ULL & 0x8040201008040201ULL) | ||||
|                 * 0x0102040810204081ULL >> 49) & 0x5555) | | ||||
|             (((o * 0x0101010101010101ULL & 0x8040201008040201ULL) | ||||
|                 * 0x0102040810204081ULL >> 48) & 0xAAAA); | ||||
|          | ||||
|             (((e * 0x0101010101010101ULL & 0x8040201008040201ULL) * | ||||
|                      0x0102040810204081ULL >> | ||||
|                  49) & | ||||
|                 0x5555) | | ||||
|             (((o * 0x0101010101010101ULL & 0x8040201008040201ULL) * | ||||
|                      0x0102040810204081ULL >> | ||||
|                  48) & | ||||
|                 0xAAAA); | ||||
|  | ||||
|         bw.write_be16(result); | ||||
|     } | ||||
|  | ||||
| @@ -95,6 +99,6 @@ Bytes amigaDeinterleave(const uint8_t*& input, size_t len) | ||||
|  | ||||
| Bytes amigaDeinterleave(const Bytes& input) | ||||
| { | ||||
| 	const uint8_t* ptr = input.cbegin(); | ||||
| 	return amigaDeinterleave(ptr, input.size()); | ||||
|     const uint8_t* ptr = input.cbegin(); | ||||
|     return amigaDeinterleave(ptr, input.size()); | ||||
| } | ||||
|   | ||||
| @@ -11,70 +11,74 @@ | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| /*  | ||||
| /* | ||||
|  * Amiga disks use MFM but it's not quite the same as IBM MFM. They only use | ||||
|  * a single type of record with a different marker byte. | ||||
|  *  | ||||
|  * | ||||
|  * See the big comment in the IBM MFM decoder for the gruesome details of how | ||||
|  * MFM works. | ||||
|  */ | ||||
|           | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD); | ||||
|  | ||||
| class AmigaDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	AmigaDecoder(const DecoderProto& config): | ||||
| 		Decoder(config), | ||||
| 		_config(config.amiga()) | ||||
| 	{} | ||||
|     AmigaDecoder(const DecoderProto& config): | ||||
|         Decoder(config), | ||||
|         _config(config.amiga()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(SECTOR_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw48() != AMIGA_SECTOR_RECORD) | ||||
| 			return; | ||||
| 			 | ||||
| 		const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16); | ||||
| 		if (rawbits.size() < (AMIGA_RECORD_SIZE*16)) | ||||
| 			return; | ||||
| 		const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE); | ||||
|     { | ||||
|         if (readRaw48() != AMIGA_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		const uint8_t* ptr = bytes.begin(); | ||||
|         const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE * 16); | ||||
|         if (rawbits.size() < (AMIGA_RECORD_SIZE * 16)) | ||||
|             return; | ||||
|         const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE * 2); | ||||
|         const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE); | ||||
|  | ||||
| 		Bytes header = amigaDeinterleave(ptr, 4); | ||||
| 		Bytes recoveryinfo = amigaDeinterleave(ptr, 16); | ||||
|         const uint8_t* ptr = bytes.begin(); | ||||
|  | ||||
| 		_sector->logicalTrack = header[1] >> 1; | ||||
| 		_sector->logicalSide = header[1] & 1; | ||||
| 		_sector->logicalSector = header[2]; | ||||
|         Bytes header = amigaDeinterleave(ptr, 4); | ||||
|         Bytes recoveryinfo = amigaDeinterleave(ptr, 16); | ||||
|  | ||||
| 		uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32(); | ||||
| 		uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40)); | ||||
| 		if (gotheaderchecksum != wantedheaderchecksum) | ||||
| 			return; | ||||
|         _sector->logicalTrack = header[1] >> 1; | ||||
|         _sector->logicalSide = header[1] & 1; | ||||
|         _sector->logicalSector = header[2]; | ||||
|  | ||||
| 		uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32(); | ||||
| 		uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024)); | ||||
|         uint32_t wantedheaderchecksum = | ||||
|             amigaDeinterleave(ptr, 4).reader().read_be32(); | ||||
|         uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40)); | ||||
|         if (gotheaderchecksum != wantedheaderchecksum) | ||||
|             return; | ||||
|  | ||||
| 		Bytes data; | ||||
| 		data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo); | ||||
| 		_sector->data = data; | ||||
| 		_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         uint32_t wanteddatachecksum = | ||||
|             amigaDeinterleave(ptr, 4).reader().read_be32(); | ||||
|         uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024)); | ||||
|  | ||||
|         Bytes data; | ||||
|         data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo); | ||||
|         _sector->data = data; | ||||
|         _sector->status = (gotdatachecksum == wanteddatachecksum) | ||||
|                               ? Sector::OK | ||||
|                               : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	const AmigaDecoderProto& _config; | ||||
| 	nanoseconds_t _clock; | ||||
|     const AmigaDecoderProto& _config; | ||||
|     nanoseconds_t _clock; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createAmigaDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new AmigaDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new AmigaDecoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -59,7 +59,7 @@ static void write_sector(std::vector<bool>& bits, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
| { | ||||
|     if ((sector->data.size() != 512) && (sector->data.size() != 528)) | ||||
|         Error() << "unsupported sector size --- you must pick 512 or 528"; | ||||
|         error("unsupported sector size --- you must pick 512 or 528"); | ||||
|  | ||||
|     uint32_t checksum = 0; | ||||
|  | ||||
| @@ -114,7 +114,8 @@ public: | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         /* Number of bits for one nominal revolution of a real 200ms Amiga disk. */ | ||||
|         /* Number of bits for one nominal revolution of a real 200ms Amiga disk. | ||||
|          */ | ||||
|         int bitsPerRevolution = 200e3 / _config.clock_rate_us(); | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
| @@ -129,13 +130,12 @@ public: | ||||
|             write_sector(bits, cursor, sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|             error("track data overrun"); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 _config.clock_rate_us() * 1e3, 200e6)); | ||||
|             calculatePhysicalClockPeriod(_config.clock_rate_us() * 1e3, 200e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -5,16 +5,15 @@ | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define APPLE2_SECTOR_RECORD   0xd5aa96 | ||||
| #define APPLE2_DATA_RECORD     0xd5aaad | ||||
| #define APPLE2_SECTOR_RECORD 0xd5aa96 | ||||
| #define APPLE2_DATA_RECORD 0xd5aaad | ||||
|  | ||||
| #define APPLE2_SECTOR_LENGTH   256 | ||||
| #define APPLE2_SECTOR_LENGTH 256 | ||||
| #define APPLE2_ENCODED_SECTOR_LENGTH 342 | ||||
|  | ||||
| #define APPLE2_SECTORS         16 | ||||
| #define APPLE2_SECTORS 16 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createApple2Encoder(const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -50,8 +50,7 @@ public: | ||||
|             writeSector(bits, cursor, *sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|             error("track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| @@ -118,8 +117,7 @@ private: | ||||
|  | ||||
|             // There is data to encode to disk. | ||||
|             if ((sector.data.size() != APPLE2_SECTOR_LENGTH)) | ||||
|                 Error() << fmt::format( | ||||
|                     "unsupported sector size {} --- you must pick 256", | ||||
|                 error("unsupported sector size {} --- you must pick 256", | ||||
|                     sector.data.size()); | ||||
|  | ||||
|             // Write address syncing leader : A sequence of "FF40"s; 5 of them | ||||
|   | ||||
| @@ -3,17 +3,19 @@ | ||||
|  | ||||
| /* Brother word processor format (or at least, one of them) */ | ||||
|  | ||||
| #define BROTHER_SECTOR_RECORD            0xFFFFFD57 | ||||
| #define BROTHER_DATA_RECORD              0xFFFFFDDB | ||||
| #define BROTHER_DATA_RECORD_PAYLOAD      256 | ||||
| #define BROTHER_DATA_RECORD_CHECKSUM     3 | ||||
| #define BROTHER_SECTOR_RECORD 0xFFFFFD57 | ||||
| #define BROTHER_DATA_RECORD 0xFFFFFDDB | ||||
| #define BROTHER_DATA_RECORD_PAYLOAD 256 | ||||
| #define BROTHER_DATA_RECORD_CHECKSUM 3 | ||||
| #define BROTHER_DATA_RECORD_ENCODED_SIZE 415 | ||||
|  | ||||
| #define BROTHER_TRACKS_PER_240KB_DISK    78 | ||||
| #define BROTHER_TRACKS_PER_120KB_DISK    39 | ||||
| #define BROTHER_SECTORS_PER_TRACK        12 | ||||
| #define BROTHER_TRACKS_PER_240KB_DISK 78 | ||||
| #define BROTHER_TRACKS_PER_120KB_DISK 39 | ||||
| #define BROTHER_SECTORS_PER_TRACK 12 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createBrotherDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createBrotherEncoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createBrotherDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createBrotherEncoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| GCR_ENTRY(0x55, 0) // 00000 | ||||
| GCR_ENTRY(0x57, 1) // 00001 | ||||
| GCR_ENTRY(0x5b, 2) // 00010 | ||||
| GCR_ENTRY(0x5d, 3) // 00011 | ||||
| GCR_ENTRY(0x5f, 4) // 00100  | ||||
| GCR_ENTRY(0x6b, 5) // 00101 | ||||
| GCR_ENTRY(0x6d, 6) // 00110 | ||||
| GCR_ENTRY(0x6f, 7) // 00111 | ||||
| GCR_ENTRY(0x75, 8) // 01000 | ||||
| GCR_ENTRY(0x77, 9) // 01001 | ||||
| GCR_ENTRY(0x55, 0)  // 00000 | ||||
| GCR_ENTRY(0x57, 1)  // 00001 | ||||
| GCR_ENTRY(0x5b, 2)  // 00010 | ||||
| GCR_ENTRY(0x5d, 3)  // 00011 | ||||
| GCR_ENTRY(0x5f, 4)  // 00100 | ||||
| GCR_ENTRY(0x6b, 5)  // 00101 | ||||
| GCR_ENTRY(0x6d, 6)  // 00110 | ||||
| GCR_ENTRY(0x6f, 7)  // 00111 | ||||
| GCR_ENTRY(0x75, 8)  // 01000 | ||||
| GCR_ENTRY(0x77, 9)  // 01001 | ||||
| GCR_ENTRY(0x7b, 10) // 01010 | ||||
| GCR_ENTRY(0x7d, 11) // 01011 | ||||
| GCR_ENTRY(0x7f, 12) // 01100 | ||||
| @@ -30,4 +30,3 @@ GCR_ENTRY(0xef, 28) // 11100 | ||||
| GCR_ENTRY(0xf5, 29) // 11101 | ||||
| GCR_ENTRY(0xf7, 30) // 11110 | ||||
| GCR_ENTRY(0xfb, 31) // 11111 | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,8 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(32, BROTHER_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(32, BROTHER_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 std::vector<uint8_t> outputbuffer; | ||||
|  | ||||
| @@ -32,88 +33,89 @@ 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; | ||||
| } | ||||
|  | ||||
| static int decode_header_gcr(uint16_t word) | ||||
| { | ||||
| 	switch (word) | ||||
| 	{ | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case gcr: return data; | ||||
| 		#include "header_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
| 	}                        | ||||
| 	return -1;              | ||||
|     switch (word) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case gcr:                \ | ||||
|         return data; | ||||
| #include "header_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| class BrotherDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
|     BrotherDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     BrotherDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw32() != BROTHER_SECTOR_RECORD) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw32() != BROTHER_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(32); | ||||
| 		const auto& bytes = toBytes(rawbits).slice(0, 4); | ||||
|         const auto& rawbits = readRawBits(32); | ||||
|         const auto& bytes = toBytes(rawbits).slice(0, 4); | ||||
|  | ||||
| 		ByteReader br(bytes); | ||||
| 		_sector->logicalTrack = decode_header_gcr(br.read_be16()); | ||||
| 		_sector->logicalSector = decode_header_gcr(br.read_be16()); | ||||
|         ByteReader br(bytes); | ||||
|         _sector->logicalTrack = decode_header_gcr(br.read_be16()); | ||||
|         _sector->logicalSector = decode_header_gcr(br.read_be16()); | ||||
|  | ||||
| 		/* Sanity check the values read; there's no header checksum and | ||||
| 			* occasionally we get garbage due to bit errors. */ | ||||
| 		if (_sector->logicalSector > 11) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack > 79) | ||||
| 			return; | ||||
|         /* Sanity check the values read; there's no header checksum and | ||||
|          * occasionally we get garbage due to bit errors. */ | ||||
|         if (_sector->logicalSector > 11) | ||||
|             return; | ||||
|         if (_sector->logicalTrack > 79) | ||||
|             return; | ||||
|  | ||||
|         _sector->status = Sector::DATA_MISSING; | ||||
|     } | ||||
|  | ||||
| 		_sector->status = Sector::DATA_MISSING; | ||||
| 	} | ||||
| 	 | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		if (readRaw32() != BROTHER_DATA_RECORD) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw32() != BROTHER_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE*8); | ||||
| 		const auto& rawbytes = toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE); | ||||
|         const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE * 8); | ||||
|         const auto& rawbytes = | ||||
|             toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE); | ||||
|  | ||||
| 		Bytes bytes; | ||||
| 		ByteWriter bw(bytes); | ||||
| 		BitWriter bitw(bw); | ||||
| 		for (uint8_t b : rawbytes) | ||||
| 		{ | ||||
| 			uint32_t nibble = decode_data_gcr(b); | ||||
| 			bitw.push(nibble, 5); | ||||
| 		} | ||||
| 		bitw.flush(); | ||||
|         Bytes bytes; | ||||
|         ByteWriter bw(bytes); | ||||
|         BitWriter bitw(bw); | ||||
|         for (uint8_t b : rawbytes) | ||||
|         { | ||||
|             uint32_t nibble = decode_data_gcr(b); | ||||
|             bitw.push(nibble, 5); | ||||
|         } | ||||
|         bitw.flush(); | ||||
|  | ||||
| 		_sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD); | ||||
| 		uint32_t realCrc = crcbrother(_sector->data); | ||||
| 		uint32_t wantCrc = bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24(); | ||||
| 		_sector->status = (realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD); | ||||
|         uint32_t realCrc = crcbrother(_sector->data); | ||||
|         uint32_t wantCrc = | ||||
|             bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24(); | ||||
|         _sector->status = | ||||
|             (realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createBrotherDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new BrotherDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new BrotherDecoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -67,7 +67,7 @@ static void write_sector_data( | ||||
|     int width = 0; | ||||
|  | ||||
|     if (data.size() != BROTHER_DATA_RECORD_PAYLOAD) | ||||
|         Error() << "unsupported sector size"; | ||||
|         error("unsupported sector size"); | ||||
|  | ||||
|     auto write_byte = [&](uint8_t byte) | ||||
|     { | ||||
| @@ -107,8 +107,7 @@ public: | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode( | ||||
| 		std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
| @@ -116,8 +115,8 @@ public: | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
| 		int sectorCount = 0; | ||||
| 		for (const auto& sectorData : sectors) | ||||
|         int sectorCount = 0; | ||||
|         for (const auto& sectorData : sectors) | ||||
|         { | ||||
|             double headerMs = _config.post_index_gap_ms() + | ||||
|                               sectorCount * _config.sector_spacing_ms(); | ||||
| @@ -126,16 +125,18 @@ public: | ||||
|             unsigned dataCursor = dataMs * 1e3 / _config.clock_rate_us(); | ||||
|  | ||||
|             fillBitmapTo(bits, cursor, headerCursor, {true, false}); | ||||
|             write_sector_header( | ||||
|                 bits, cursor, sectorData->logicalTrack, sectorData->logicalSector); | ||||
|             write_sector_header(bits, | ||||
|                 cursor, | ||||
|                 sectorData->logicalTrack, | ||||
|                 sectorData->logicalSector); | ||||
|             fillBitmapTo(bits, cursor, dataCursor, {true, false}); | ||||
|             write_sector_data(bits, cursor, sectorData->data); | ||||
|  | ||||
| 			sectorCount++; | ||||
|             sectorCount++; | ||||
|         } | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|             error("track data overrun"); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| @@ -147,8 +148,7 @@ private: | ||||
|     const BrotherEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createBrotherEncoder( | ||||
|     const EncoderProto& config) | ||||
| std::unique_ptr<Encoder> createBrotherEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new BrotherEncoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -76,4 +76,3 @@ GCR_ENTRY(0x6BAB, 74) | ||||
| GCR_ENTRY(0xAD5F, 75) | ||||
| GCR_ENTRY(0xDBED, 76) | ||||
| GCR_ENTRY(0x55BB, 77) | ||||
|  | ||||
|   | ||||
| @@ -37,8 +37,7 @@ OBJS += $(LIBARCH_OBJS) | ||||
| $(LIBARCH_SRCS): | $(PROTO_HDRS) | ||||
| $(LIBARCH_SRCS): CFLAGS += $(PROTO_CFLAGS) | ||||
| LIBARCH_LIB = $(OBJDIR)/libarch.a | ||||
| LIBARCH_LDFLAGS = | ||||
| $(LIBARCH_LIB): $(LIBARCH_OBJS) | ||||
|  | ||||
| LIBARCH_LDFLAGS = $(LIBARCH_LIB) | ||||
|  | ||||
| $(call use-pkgconfig, $(LIBARCH_LIB), $(LIBARCH_OBJS), fmt) | ||||
|   | ||||
| @@ -2,27 +2,27 @@ | ||||
| #include "c64.h" | ||||
|  | ||||
| /* | ||||
|     *   Track   Sectors/track   # Sectors   Storage in Bytes   Clock rate | ||||
|     *   -----   -------------   ---------   ----------------   ---------- | ||||
|     *    1-17        21            357           7820             3.25 | ||||
|     *   18-24        19            133           7170             3.5 | ||||
|     *   25-30        18            108           6300             3.75 | ||||
|     *   31-40(*)     17             85           6020             4 | ||||
|     *                              --- | ||||
|     *                              683 (for a 35 track image) | ||||
|     *  | ||||
|     * The clock rate is normalised for a 200ms drive. | ||||
|     */ | ||||
|  *   Track   Sectors/track   # Sectors   Storage in Bytes   Clock rate | ||||
|  *   -----   -------------   ---------   ----------------   ---------- | ||||
|  *    1-17        21            357           7820             3.25 | ||||
|  *   18-24        19            133           7170             3.5 | ||||
|  *   25-30        18            108           6300             3.75 | ||||
|  *   31-40(*)     17             85           6020             4 | ||||
|  *                              --- | ||||
|  *                              683 (for a 35 track image) | ||||
|  * | ||||
|  * The clock rate is normalised for a 200ms drive. | ||||
|  */ | ||||
|  | ||||
| nanoseconds_t clockPeriodForC64Track(unsigned track) | ||||
| { | ||||
|     constexpr double BYTE_SIZE = 8.0; | ||||
|     constexpr double b = 8.0; | ||||
|  | ||||
|     if (track < 17) | ||||
|         return 26.0 / BYTE_SIZE; | ||||
|         return 26.0 / b; | ||||
|     if (track < 24) | ||||
|         return 28.0 / BYTE_SIZE; | ||||
|         return 28.0 / b; | ||||
|     if (track < 30) | ||||
|         return 30.0 / BYTE_SIZE; | ||||
|     return 32.0 / BYTE_SIZE; | ||||
|         return 30.0 / b; | ||||
|     return 32.0 / b; | ||||
| } | ||||
|   | ||||
| @@ -4,11 +4,11 @@ | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define C64_SECTOR_RECORD    0xffd49 | ||||
| #define C64_DATA_RECORD      0xffd57 | ||||
| #define C64_SECTOR_LENGTH    256 | ||||
| #define C64_SECTOR_RECORD 0xffd49 | ||||
| #define C64_DATA_RECORD 0xffd57 | ||||
| #define C64_SECTOR_LENGTH 256 | ||||
|  | ||||
| /* Source: http://www.unusedino.de/ec64/technical/formats/g64.html  | ||||
| /* Source: http://www.unusedino.de/ec64/technical/formats/g64.html | ||||
|    1. Header sync       FF FF FF FF FF (40 'on' bits, not GCR) | ||||
|    2. Header info       52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) | ||||
|    3. Header gap        55 55 55 55 55 55 55 55 55 (9 bytes, never read) | ||||
| @@ -17,18 +17,20 @@ | ||||
|    6. Inter-sector gap  55 55 55 55...55 55 (4 to 12 bytes, never read) | ||||
|    1. Header sync       (SYNC for the next sector) | ||||
| */ | ||||
| #define C64_HEADER_DATA_SYNC        0xFF | ||||
| #define C64_HEADER_BLOCK_ID         0x08 | ||||
| #define C64_DATA_BLOCK_ID           0x07 | ||||
| #define C64_HEADER_GAP              0x55 | ||||
| #define C64_INTER_SECTOR_GAP        0x55 | ||||
| #define C64_PADDING                 0x0F | ||||
| #define C64_HEADER_DATA_SYNC 0xFF | ||||
| #define C64_HEADER_BLOCK_ID 0x08 | ||||
| #define C64_DATA_BLOCK_ID 0x07 | ||||
| #define C64_HEADER_GAP 0x55 | ||||
| #define C64_INTER_SECTOR_GAP 0x55 | ||||
| #define C64_PADDING 0x0F | ||||
|  | ||||
| #define C64_TRACKS_PER_DISK         40 | ||||
| #define C64_BAM_TRACK               17 | ||||
| #define C64_TRACKS_PER_DISK 40 | ||||
| #define C64_BAM_TRACK 17 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createCommodore64Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createCommodore64Encoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createCommodore64Decoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createCommodore64Encoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| extern nanoseconds_t clockPeriodForC64Track(unsigned track); | ||||
|  | ||||
|   | ||||
| @@ -96,8 +96,7 @@ public: | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createCommodore64Decoder( | ||||
|     const DecoderProto& config) | ||||
| std::unique_ptr<Decoder> createCommodore64Decoder(const DecoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Decoder>(new Commodore64Decoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -51,26 +51,6 @@ static void write_bits( | ||||
|     } | ||||
| } | ||||
|  | ||||
| void bindump(std::ostream& stream, std::vector<bool>& buffer) | ||||
| { | ||||
|     size_t pos = 0; | ||||
|  | ||||
|     while ((pos < buffer.size()) and (pos < 520)) | ||||
|     { | ||||
|         stream << fmt::format("{:5d} : ", pos); | ||||
|         for (int i = 0; i < 40; i++) | ||||
|         { | ||||
|             if ((pos + i) < buffer.size()) | ||||
|                 stream << fmt::format("{:01b}", (buffer[pos + i])); | ||||
|             else | ||||
|                 stream << "-- "; | ||||
|             if ((((pos + i + 1) % 8) == 0) and i != 0) | ||||
|                 stream << "  "; | ||||
|         } | ||||
|         stream << std::endl; | ||||
|         pos += 40; | ||||
|     } | ||||
| } | ||||
| static std::vector<bool> encode_data(uint8_t input) | ||||
| { | ||||
|     /* | ||||
| @@ -214,8 +194,7 @@ public: | ||||
|             writeSector(bits, cursor, sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|             error("track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| @@ -243,8 +222,7 @@ private: | ||||
|         { | ||||
|             // There is data to encode to disk. | ||||
|             if ((sector->data.size() != C64_SECTOR_LENGTH)) | ||||
|                 Error() << fmt::format( | ||||
|                     "unsupported sector size {} --- you must pick 256", | ||||
|                 error("unsupported sector size {} --- you must pick 256", | ||||
|                     sector->data.size()); | ||||
|  | ||||
|             // 1. Write header Sync (not GCR) | ||||
|   | ||||
| @@ -13,16 +13,18 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(24, F85_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(24, F85_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; | ||||
| } | ||||
| @@ -37,11 +39,11 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
|     while (ii != bits.end()) | ||||
|     { | ||||
|         uint8_t inputfifo = 0; | ||||
|         for (size_t i=0; i<5; i++) | ||||
|         for (size_t i = 0; i < 5; i++) | ||||
|         { | ||||
|             if (ii == bits.end()) | ||||
|                 break; | ||||
|             inputfifo = (inputfifo<<1) | *ii++; | ||||
|             inputfifo = (inputfifo << 1) | *ii++; | ||||
|         } | ||||
|  | ||||
|         bitw.push(decode_data_gcr(inputfifo), 4); | ||||
| @@ -54,56 +56,55 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
| class DurangoF85Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	DurangoF85Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     DurangoF85Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Skip sync bits and ID byte. */ | ||||
|     { | ||||
|         /* Skip sync bits and ID byte. */ | ||||
|  | ||||
| 		if (readRaw24() != F85_SECTOR_RECORD) | ||||
| 			return; | ||||
|         if (readRaw24() != F85_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|         /* Read header. */ | ||||
|  | ||||
| 		const auto& bytes = decode(readRawBits(6*10)); | ||||
|         const auto& bytes = decode(readRawBits(6 * 10)); | ||||
|  | ||||
| 		_sector->logicalSector = bytes[2]; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalTrack = bytes[0]; | ||||
|         _sector->logicalSector = bytes[2]; | ||||
|         _sector->logicalSide = 0; | ||||
|         _sector->logicalTrack = bytes[0]; | ||||
|  | ||||
| 		uint16_t wantChecksum = bytes.reader().seek(4).read_be16(); | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4)); | ||||
| 		if (wantChecksum == gotChecksum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|         uint16_t wantChecksum = bytes.reader().seek(4).read_be16(); | ||||
|         uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4)); | ||||
|         if (wantChecksum == gotChecksum) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		/* Skip sync bits ID byte. */ | ||||
|     { | ||||
|         /* Skip sync bits ID byte. */ | ||||
|  | ||||
| 		if (readRaw24() != F85_DATA_RECORD) | ||||
| 			return; | ||||
|         if (readRaw24() != F85_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH+3)*10)) | ||||
| 			.slice(0, F85_SECTOR_LENGTH+3); | ||||
| 		ByteReader br(bytes); | ||||
|         const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH + 3) * 10)) | ||||
|                                 .slice(0, F85_SECTOR_LENGTH + 3); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->data = br.read(F85_SECTOR_LENGTH); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = br.read(F85_SECTOR_LENGTH); | ||||
|         uint16_t wantChecksum = br.read_be16(); | ||||
|         uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data); | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createDurangoF85Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new DurangoF85Decoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new DurangoF85Decoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,9 +2,10 @@ | ||||
| #define F85_H | ||||
|  | ||||
| #define F85_SECTOR_RECORD 0xffffce /* 1111 1111 1111 1111 1100 1110 */ | ||||
| #define F85_DATA_RECORD 0xffffcb /* 1111 1111 1111 1111 1100 1101 */ | ||||
| #define F85_SECTOR_LENGTH    512 | ||||
| #define F85_DATA_RECORD 0xffffcb   /* 1111 1111 1111 1111 1100 1101 */ | ||||
| #define F85_SECTOR_LENGTH 512 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createDurangoF85Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createDurangoF85Decoder( | ||||
|     const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -14,10 +14,10 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_ID_PATTERN(16, 0xabaa); | ||||
|  | ||||
| /*  | ||||
| /* | ||||
|  * Reverse engineered from a dump of the floppy drive's ROM. I have no idea how | ||||
|  * it works. | ||||
|  *  | ||||
|  * | ||||
|  * LF8BA: | ||||
|  *         clra | ||||
|  *         staa    X00B0 | ||||
| @@ -100,45 +100,43 @@ static uint16_t checksum(const Bytes& bytes) | ||||
| class Fb100Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	Fb100Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     Fb100Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_ID_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(SECTOR_ID_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto rawbits = readRawBits(FB100_RECORD_SIZE*16); | ||||
|     { | ||||
|         auto rawbits = readRawBits(FB100_RECORD_SIZE * 16); | ||||
|  | ||||
| 		const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE); | ||||
| 		ByteReader br(bytes); | ||||
| 		br.seek(1); | ||||
| 		const Bytes id = br.read(FB100_ID_SIZE); | ||||
| 		uint16_t wantIdCrc = br.read_be16(); | ||||
| 		uint16_t gotIdCrc = checksum(id); | ||||
| 		const Bytes payload = br.read(FB100_PAYLOAD_SIZE); | ||||
| 		uint16_t wantPayloadCrc = br.read_be16(); | ||||
| 		uint16_t gotPayloadCrc = checksum(payload); | ||||
|         const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE); | ||||
|         ByteReader br(bytes); | ||||
|         br.seek(1); | ||||
|         const Bytes id = br.read(FB100_ID_SIZE); | ||||
|         uint16_t wantIdCrc = br.read_be16(); | ||||
|         uint16_t gotIdCrc = checksum(id); | ||||
|         const Bytes payload = br.read(FB100_PAYLOAD_SIZE); | ||||
|         uint16_t wantPayloadCrc = br.read_be16(); | ||||
|         uint16_t gotPayloadCrc = checksum(payload); | ||||
|  | ||||
| 		if (wantIdCrc != gotIdCrc) | ||||
| 			return; | ||||
|         if (wantIdCrc != gotIdCrc) | ||||
|             return; | ||||
|  | ||||
| 		uint8_t abssector = id[2]; | ||||
| 		_sector->logicalTrack = abssector >> 1; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalSector = abssector & 1; | ||||
| 		_sector->data.writer().append(id.slice(5, 12)).append(payload); | ||||
|         uint8_t abssector = id[2]; | ||||
|         _sector->logicalTrack = abssector >> 1; | ||||
|         _sector->logicalSide = 0; | ||||
|         _sector->logicalSector = abssector & 1; | ||||
|         _sector->data.writer().append(id.slice(5, 12)).append(payload); | ||||
|  | ||||
| 		_sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->status = (wantPayloadCrc == gotPayloadCrc) | ||||
|                               ? Sector::OK | ||||
|                               : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Fb100Decoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new Fb100Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -8,4 +8,3 @@ | ||||
| extern std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -112,10 +112,11 @@ public: | ||||
|         const Image& image) override | ||||
|     { | ||||
|         IbmEncoderProto::TrackdataProto trackdata; | ||||
|         getEncoderTrackData(trackdata, trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|         getEncoderTrackData( | ||||
|             trackdata, trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         auto trackLayout = | ||||
|             Layout::getLayoutOfTrack(trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|         auto trackLayout = Layout::getLayoutOfTrack( | ||||
|             trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         auto writeBytes = [&](const Bytes& bytes) | ||||
|         { | ||||
| @@ -257,7 +258,7 @@ public: | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|             error("track data overrun"); | ||||
|         while (_cursor < _bits.size()) | ||||
|             writeFillerRawBytes(1, gapFill); | ||||
|  | ||||
|   | ||||
| @@ -31,9 +31,7 @@ class Decoder; | ||||
| class DecoderProto; | ||||
| class EncoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createIbmDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createIbmEncoder( | ||||
|     const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createIbmDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createIbmEncoder(const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -12,22 +12,25 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(24, MAC_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(24, MAC_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/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan | ||||
|  * Woods and R. Belmont: | ||||
|  * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp | ||||
|  */ | ||||
| static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status) | ||||
| { | ||||
| @@ -41,7 +44,7 @@ static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status) | ||||
|     uint8_t b2[LOOKUP_LEN + 1]; | ||||
|     uint8_t b3[LOOKUP_LEN + 1]; | ||||
|  | ||||
|     for (int i=0; i<=LOOKUP_LEN; i++) | ||||
|     for (int i = 0; i <= LOOKUP_LEN; i++) | ||||
|     { | ||||
|         uint8_t w4 = br.read_8(); | ||||
|         uint8_t w1 = br.read_8(); | ||||
| @@ -125,67 +128,68 @@ uint8_t decode_side(uint8_t side) | ||||
| class MacintoshDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	MacintoshDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     MacintoshDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw24() != MAC_SECTOR_RECORD) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw24() != MAC_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|         /* Read header. */ | ||||
|  | ||||
| 		auto header = toBytes(readRawBits(7*8)).slice(0, 7); | ||||
| 					 | ||||
| 		uint8_t encodedTrack = decode_data_gcr(header[0]); | ||||
| 		if (encodedTrack != (_sector->physicalTrack & 0x3f)) | ||||
| 			return; | ||||
| 					 | ||||
| 		uint8_t encodedSector = decode_data_gcr(header[1]); | ||||
| 		uint8_t encodedSide = decode_data_gcr(header[2]); | ||||
| 		uint8_t formatByte = decode_data_gcr(header[3]); | ||||
| 		uint8_t wantedsum = decode_data_gcr(header[4]); | ||||
|         auto header = toBytes(readRawBits(7 * 8)).slice(0, 7); | ||||
|  | ||||
| 		if (encodedSector > 11) | ||||
| 			return; | ||||
|         uint8_t encodedTrack = decode_data_gcr(header[0]); | ||||
|         if (encodedTrack != (_sector->physicalTrack & 0x3f)) | ||||
|             return; | ||||
|  | ||||
| 		_sector->logicalTrack = _sector->physicalTrack; | ||||
| 		_sector->logicalSide = decode_side(encodedSide); | ||||
| 		_sector->logicalSector = encodedSector; | ||||
| 		uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; | ||||
| 		if (wantedsum == gotsum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|         uint8_t encodedSector = decode_data_gcr(header[1]); | ||||
|         uint8_t encodedSide = decode_data_gcr(header[2]); | ||||
|         uint8_t formatByte = decode_data_gcr(header[3]); | ||||
|         uint8_t wantedsum = decode_data_gcr(header[4]); | ||||
|  | ||||
|         if (encodedSector > 11) | ||||
|             return; | ||||
|  | ||||
|         _sector->logicalTrack = _sector->physicalTrack; | ||||
|         _sector->logicalSide = decode_side(encodedSide); | ||||
|         _sector->logicalSector = encodedSector; | ||||
|         uint8_t gotsum = | ||||
|             (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; | ||||
|         if (wantedsum == gotsum) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		if (readRaw24() != MAC_DATA_RECORD) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw24() != MAC_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read data. */ | ||||
|         /* Read data. */ | ||||
|  | ||||
| 		readRawBits(8); /* skip spare byte */ | ||||
| 		auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH*8)) | ||||
| 			.slice(0, MAC_ENCODED_SECTOR_LENGTH); | ||||
|         readRawBits(8); /* skip spare byte */ | ||||
|         auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH * 8)) | ||||
|                                .slice(0, MAC_ENCODED_SECTOR_LENGTH); | ||||
|  | ||||
| 		for (unsigned i=0; i<inputbuffer.size(); i++) | ||||
| 			inputbuffer[i] = decode_data_gcr(inputbuffer[i]); | ||||
| 			 | ||||
| 		_sector->status = Sector::BAD_CHECKSUM; | ||||
| 		Bytes userData = decode_crazy_data(inputbuffer, _sector->status); | ||||
| 		_sector->data.clear(); | ||||
| 		_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12)); | ||||
| 	} | ||||
|         for (unsigned i = 0; i < inputbuffer.size(); i++) | ||||
|             inputbuffer[i] = decode_data_gcr(inputbuffer[i]); | ||||
|  | ||||
|         _sector->status = Sector::BAD_CHECKSUM; | ||||
|         Bytes userData = decode_crazy_data(inputbuffer, _sector->status); | ||||
|         _sector->data.clear(); | ||||
|         _sector->data.writer() | ||||
|             .append(userData.slice(12, 512)) | ||||
|             .append(userData.slice(0, 12)); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createMacintoshDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new MacintoshDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new MacintoshDecoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -174,7 +174,7 @@ static void write_sector(std::vector<bool>& bits, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
| { | ||||
|     if ((sector->data.size() != 512) && (sector->data.size() != 524)) | ||||
|         Error() << "unsupported sector size --- you must pick 512 or 524"; | ||||
|         error("unsupported sector size --- you must pick 512 or 524"); | ||||
|  | ||||
|     write_bits(bits, cursor, 0xff, 1 * 8); /* pad byte */ | ||||
|     for (int i = 0; i < 7; i++) | ||||
| @@ -239,13 +239,12 @@ public: | ||||
|             write_sector(bits, cursor, sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|             error("track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6)); | ||||
|         fluxmap->appendBits( | ||||
|             bits, calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| @@ -253,8 +252,7 @@ private: | ||||
|     const MacintoshEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createMacintoshEncoder( | ||||
|     const EncoderProto& config) | ||||
| std::unique_ptr<Encoder> createMacintoshEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new MacintoshEncoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| #ifndef MACINTOSH_H | ||||
| #define MACINTOSH_H | ||||
|  | ||||
| #define MAC_SECTOR_RECORD   0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */ | ||||
| #define MAC_DATA_RECORD     0xd5aaad /* 1101 0101 1010 1010 1010 1101 */ | ||||
| #define MAC_SECTOR_RECORD 0xd5aa96 /* 1101 0101 1010 1010 1001 0110 */ | ||||
| #define MAC_DATA_RECORD 0xd5aaad   /* 1101 0101 1010 1010 1010 1101 */ | ||||
|  | ||||
| #define MAC_SECTOR_LENGTH   524 /* yes, really */ | ||||
| #define MAC_SECTOR_LENGTH 524 /* yes, really */ | ||||
| #define MAC_ENCODED_SECTOR_LENGTH 703 | ||||
| #define MAC_FORMAT_BYTE     0x22 | ||||
| #define MAC_FORMAT_BYTE 0x22 | ||||
|  | ||||
| #define MAC_TRACKS_PER_DISK 80 | ||||
|  | ||||
| @@ -15,8 +15,9 @@ class Decoder; | ||||
| class DecoderProto; | ||||
| class EncoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createMacintoshDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createMacintoshEncoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createMacintoshDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createMacintoshEncoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -20,17 +20,20 @@ static const FluxPattern SECTOR_SYNC_PATTERN(64, 0xAAAAAAAAAAAA5555LL); | ||||
| static const FluxPattern SECTOR_ADVANCE_PATTERN(64, 0xAAAAAAAAAAAAAAAALL); | ||||
|  | ||||
| /* Standard Micropolis checksum.  Adds all bytes, with carry. */ | ||||
| uint8_t micropolisChecksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint16_t sum = 0; | ||||
| 	while (!br.eof()) { | ||||
| 		if (sum > 0xFF) { | ||||
| 			sum -= 0x100 - 1; | ||||
| 		} | ||||
| 		sum += br.read_8(); | ||||
| 	} | ||||
| 	/* The last carry is ignored */ | ||||
| 	return sum & 0xFF; | ||||
| uint8_t micropolisChecksum(const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     uint16_t sum = 0; | ||||
|     while (!br.eof()) | ||||
|     { | ||||
|         if (sum > 0xFF) | ||||
|         { | ||||
|             sum -= 0x100 - 1; | ||||
|         } | ||||
|         sum += br.read_8(); | ||||
|     } | ||||
|     /* The last carry is ignored */ | ||||
|     return sum & 0xFF; | ||||
| } | ||||
|  | ||||
| /* Vector MZOS does not use the standard Micropolis checksum. | ||||
| @@ -41,145 +44,164 @@ uint8_t micropolisChecksum(const Bytes& bytes) { | ||||
|  * Unlike the Micropolis checksum, this does not cover the 12-byte | ||||
|  * header (track, sector, 10 OS-specific bytes.) | ||||
|  */ | ||||
| uint8_t mzosChecksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint8_t checksum = 0; | ||||
| 	uint8_t databyte; | ||||
| uint8_t mzosChecksum(const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     uint8_t checksum = 0; | ||||
|     uint8_t databyte; | ||||
|  | ||||
| 	while (!br.eof()) { | ||||
| 		databyte = br.read_8(); | ||||
| 	checksum ^= ((databyte << 1) | (databyte >> 7)); | ||||
| 	} | ||||
|     while (!br.eof()) | ||||
|     { | ||||
|         databyte = br.read_8(); | ||||
|         checksum ^= ((databyte << 1) | (databyte >> 7)); | ||||
|     } | ||||
|  | ||||
| 	return checksum; | ||||
|     return checksum; | ||||
| } | ||||
|  | ||||
| class MicropolisDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	MicropolisDecoder(const DecoderProto& config): | ||||
| 		Decoder(config), | ||||
| 		_config(config.micropolis()) | ||||
| 	{ | ||||
| 		_checksumType = _config.checksum_type(); | ||||
| 	} | ||||
|     MicropolisDecoder(const DecoderProto& config): | ||||
|         Decoder(config), | ||||
|         _config(config.micropolis()) | ||||
|     { | ||||
|         _checksumType = _config.checksum_type(); | ||||
|     } | ||||
|  | ||||
| 	nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		nanoseconds_t now = tell().ns(); | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         nanoseconds_t now = tell().ns(); | ||||
|  | ||||
| 		/* For all but the first sector, seek to the next sector pulse. | ||||
| 		 * The first sector does not contain the sector pulse in the fluxmap. | ||||
| 		 */ | ||||
| 		if (now != 0) { | ||||
| 			seekToIndexMark(); | ||||
| 			now = tell().ns(); | ||||
| 		} | ||||
|         /* For all but the first sector, seek to the next sector pulse. | ||||
|          * The first sector does not contain the sector pulse in the fluxmap. | ||||
|          */ | ||||
|         if (now != 0) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             now = tell().ns(); | ||||
|         } | ||||
|  | ||||
| 		/* Discard a possible partial sector at the end of the track. | ||||
| 		 * This partial sector could be mistaken for a conflicted sector, if | ||||
| 		 * whatever data read happens to match the checksum of 0, which is | ||||
| 		 * rare, but has been observed on some disks. | ||||
| 		 */ | ||||
| 		if (now > (getFluxmapDuration() - 12.5e6)) { | ||||
| 			seekToIndexMark(); | ||||
| 			return 0; | ||||
| 		} | ||||
|         /* Discard a possible partial sector at the end of the track. | ||||
|          * This partial sector could be mistaken for a conflicted sector, if | ||||
|          * whatever data read happens to match the checksum of 0, which is | ||||
|          * rare, but has been observed on some disks. | ||||
|          */ | ||||
|         if (now > (getFluxmapDuration() - 12.5e6)) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
| 		nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN); | ||||
|         nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN); | ||||
|  | ||||
| 		auto syncDelta = tell().ns() - now; | ||||
| 		/* Due to the weak nature of the Micropolis SYNC patern, | ||||
| 		 * it's possible to detect a false SYNC during the gap | ||||
| 		 * between the sector pulse and the write gate.  If the SYNC | ||||
| 		 * is detected less than 100uS after the sector pulse, search | ||||
| 		 * for another valid SYNC. | ||||
| 		 * | ||||
| 		 * Reference: Vector Micropolis Disk Controller Board Technical | ||||
| 		 * Information Manual, pp. 1-16. | ||||
| 		 */ | ||||
| 		if ((syncDelta > 0) && (syncDelta < 100e3)) { | ||||
| 			seekToPattern(SECTOR_ADVANCE_PATTERN); | ||||
| 			clock = seekToPattern(SECTOR_SYNC_PATTERN); | ||||
| 		} | ||||
|         auto syncDelta = tell().ns() - now; | ||||
|         /* Due to the weak nature of the Micropolis SYNC patern, | ||||
|          * it's possible to detect a false SYNC during the gap | ||||
|          * between the sector pulse and the write gate.  If the SYNC | ||||
|          * is detected less than 100uS after the sector pulse, search | ||||
|          * for another valid SYNC. | ||||
|          * | ||||
|          * Reference: Vector Micropolis Disk Controller Board Technical | ||||
|          * Information Manual, pp. 1-16. | ||||
|          */ | ||||
|         if ((syncDelta > 0) && (syncDelta < 100e3)) | ||||
|         { | ||||
|             seekToPattern(SECTOR_ADVANCE_PATTERN); | ||||
|             clock = seekToPattern(SECTOR_SYNC_PATTERN); | ||||
|         } | ||||
|  | ||||
| 		_sector->headerStartTime = tell().ns(); | ||||
|         _sector->headerStartTime = tell().ns(); | ||||
|  | ||||
| 		/* seekToPattern() can skip past the index hole, if this happens | ||||
| 		 * too close to the end of the Fluxmap, discard the sector. | ||||
| 		 */ | ||||
| 		if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6)) { | ||||
| 			return 0; | ||||
| 		} | ||||
|         /* seekToPattern() can skip past the index hole, if this happens | ||||
|          * too close to the end of the Fluxmap, discard the sector. | ||||
|          */ | ||||
|         if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6)) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
| 		return clock; | ||||
| 	} | ||||
|         return clock; | ||||
|     } | ||||
|  | ||||
| 	void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		readRawBits(48); | ||||
| 		auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16); | ||||
| 		auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE); | ||||
| 		ByteReader br(bytes); | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         readRawBits(48); | ||||
|         auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE * 16); | ||||
|         auto bytes = | ||||
|             decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
| 		int syncByte = br.read_8();  /* sync */ | ||||
| 		if (syncByte != 0xFF) | ||||
| 			return; | ||||
|         int syncByte = br.read_8(); /* sync */ | ||||
|         if (syncByte != 0xFF) | ||||
|             return; | ||||
|  | ||||
| 		_sector->logicalTrack = br.read_8(); | ||||
| 		_sector->logicalSide = _sector->physicalSide; | ||||
| 		_sector->logicalSector = br.read_8(); | ||||
| 		if (_sector->logicalSector > 15) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack > 76) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack != _sector->physicalTrack) | ||||
| 			return; | ||||
|         _sector->logicalTrack = br.read_8(); | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = br.read_8(); | ||||
|         if (_sector->logicalSector > 15) | ||||
|             return; | ||||
|         if (_sector->logicalTrack > 76) | ||||
|             return; | ||||
|         if (_sector->logicalTrack != _sector->physicalTrack) | ||||
|             return; | ||||
|  | ||||
| 		br.read(10);  /* OS data or padding */ | ||||
| 		auto data = br.read(MICROPOLIS_PAYLOAD_SIZE); | ||||
| 		uint8_t wantChecksum = br.read_8(); | ||||
|         br.read(10); /* OS data or padding */ | ||||
|         auto data = br.read(MICROPOLIS_PAYLOAD_SIZE); | ||||
|         uint8_t wantChecksum = br.read_8(); | ||||
|  | ||||
| 		/* If not specified, automatically determine the checksum type. | ||||
| 		* Once the checksum type is determined, it will be used for the | ||||
| 		* entire disk. | ||||
| 		*/ | ||||
| 		if (_checksumType == MicropolisDecoderProto::AUTO) { | ||||
| 			/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS checksums */ | ||||
| 			if (wantChecksum == micropolisChecksum(bytes.slice(1, 2+266))) { | ||||
| 				_checksumType = MicropolisDecoderProto::MICROPOLIS; | ||||
| 			} else if (wantChecksum == mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE))) { | ||||
| 				_checksumType = MicropolisDecoderProto::MZOS; | ||||
| 				std::cout << "Note: MZOS checksum detected." << std::endl; | ||||
| 			} | ||||
| 		} | ||||
|         /* If not specified, automatically determine the checksum type. | ||||
|          * Once the checksum type is determined, it will be used for the | ||||
|          * entire disk. | ||||
|          */ | ||||
|         if (_checksumType == MicropolisDecoderProto::AUTO) | ||||
|         { | ||||
|             /* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS | ||||
|              * checksums */ | ||||
|             if (wantChecksum == micropolisChecksum(bytes.slice(1, 2 + 266))) | ||||
|             { | ||||
|                 _checksumType = MicropolisDecoderProto::MICROPOLIS; | ||||
|             } | ||||
|             else if (wantChecksum == | ||||
|                      mzosChecksum(bytes.slice( | ||||
|                          MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE))) | ||||
|             { | ||||
|                 _checksumType = MicropolisDecoderProto::MZOS; | ||||
|                 std::cout << "Note: MZOS checksum detected." << std::endl; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		uint8_t gotChecksum; | ||||
|         uint8_t gotChecksum; | ||||
|  | ||||
| 		if (_checksumType == MicropolisDecoderProto::MZOS) { | ||||
| 			gotChecksum = mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE)); | ||||
| 		} else { | ||||
| 			gotChecksum = micropolisChecksum(bytes.slice(1, 2+266)); | ||||
| 		} | ||||
|         if (_checksumType == MicropolisDecoderProto::MZOS) | ||||
|         { | ||||
|             gotChecksum = mzosChecksum( | ||||
|                 bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             gotChecksum = micropolisChecksum(bytes.slice(1, 2 + 266)); | ||||
|         } | ||||
|  | ||||
| 		br.read(5);  /* 4 byte ECC and ECC-present flag */ | ||||
|         br.read(5); /* 4 byte ECC and ECC-present flag */ | ||||
|  | ||||
| 		if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE) | ||||
| 			_sector->data = data; | ||||
| 		else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE) | ||||
| 			_sector->data = bytes; | ||||
| 		else | ||||
| 			Error() << "Sector output size may only be 256 or 275"; | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE) | ||||
|             _sector->data = data; | ||||
|         else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE) | ||||
|             _sector->data = bytes; | ||||
|         else | ||||
|             error("Sector output size may only be 256 or 275"); | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	const MicropolisDecoderProto& _config; | ||||
| 	MicropolisDecoderProto_ChecksumType _checksumType;	/* -1 = auto, 1 = Micropolis, 2=MZOS */ | ||||
|     const MicropolisDecoderProto& _config; | ||||
|     MicropolisDecoderProto_ChecksumType | ||||
|         _checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */ | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createMicropolisDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new MicropolisDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new MicropolisDecoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ static void write_sector(std::vector<bool>& bits, | ||||
| { | ||||
|     if ((sector->data.size() != 256) && | ||||
|         (sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE)) | ||||
|         Error() << "unsupported sector size --- you must pick 256 or 275"; | ||||
|         error("unsupported sector size --- you must pick 256 or 275"); | ||||
|  | ||||
|     int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35; | ||||
|     auto fullSector = std::make_shared<std::vector<uint8_t>>(); | ||||
| @@ -24,8 +24,9 @@ static void write_sector(std::vector<bool>& bits, | ||||
|     if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE) | ||||
|     { | ||||
|         if (sector->data[0] != 0xFF) | ||||
|             Error() << "275 byte sector doesn't start with sync byte 0xFF. " | ||||
|                        "Corrupted sector"; | ||||
|             error( | ||||
|                 "275 byte sector doesn't start with sync byte 0xFF. " | ||||
|                 "Corrupted sector"); | ||||
|         uint8_t wantChecksum = sector->data[1 + 2 + 266]; | ||||
|         uint8_t gotChecksum = | ||||
|             micropolisChecksum(sector->data.slice(1, 2 + 266)); | ||||
| @@ -57,7 +58,7 @@ static void write_sector(std::vector<bool>& bits, | ||||
|         fullSector->push_back(0); | ||||
|  | ||||
|     if (fullSector->size() != fullSectorSize) | ||||
|         Error() << "sector mismatched length"; | ||||
|         error("sector mismatched length"); | ||||
|     bool lastBit = false; | ||||
|     encodeMfm(bits, cursor, fullSector, lastBit); | ||||
|     /* filler */ | ||||
| @@ -91,12 +92,11 @@ public: | ||||
|             write_sector(bits, cursor, sectorData); | ||||
|  | ||||
|         if (cursor != bits.size()) | ||||
|             Error() << "track data mismatched length"; | ||||
|             error("track data mismatched length"); | ||||
|  | ||||
|         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; | ||||
|     } | ||||
| @@ -105,8 +105,7 @@ private: | ||||
|     const MicropolisEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createMicropolisEncoder( | ||||
|     const EncoderProto& config) | ||||
| std::unique_ptr<Encoder> createMicropolisEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new MicropolisEncoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -1,17 +1,20 @@ | ||||
| #ifndef MICROPOLIS_H | ||||
| #define MICROPOLIS_H | ||||
|  | ||||
| #define MICROPOLIS_PAYLOAD_SIZE         (256) | ||||
| #define MICROPOLIS_HEADER_SIZE          (1+2+10) | ||||
| #define MICROPOLIS_ENCODED_SECTOR_SIZE  (MICROPOLIS_HEADER_SIZE + MICROPOLIS_PAYLOAD_SIZE + 6) | ||||
| #define MICROPOLIS_PAYLOAD_SIZE (256) | ||||
| #define MICROPOLIS_HEADER_SIZE (1 + 2 + 10) | ||||
| #define MICROPOLIS_ENCODED_SECTOR_SIZE \ | ||||
|     (MICROPOLIS_HEADER_SIZE + MICROPOLIS_PAYLOAD_SIZE + 6) | ||||
|  | ||||
| class Decoder; | ||||
| class Encoder; | ||||
| class EncoderProto; | ||||
| class DecoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createMicropolisDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createMicropolisEncoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createMicropolisDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createMicropolisEncoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| extern uint8_t micropolisChecksum(const Bytes& bytes); | ||||
|  | ||||
|   | ||||
| @@ -26,52 +26,51 @@ const FluxPattern ID_PATTERN(32, 0xaaaaffaf); | ||||
| class MxDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	MxDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     MxDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     void beginTrack() override | ||||
| 	{ | ||||
| 		_clock = _sector->clock = seekToPattern(ID_PATTERN); | ||||
| 		_currentSector = 0; | ||||
| 	} | ||||
|     { | ||||
|         _clock = _sector->clock = seekToPattern(ID_PATTERN); | ||||
|         _currentSector = 0; | ||||
|     } | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		if (_currentSector == 11) | ||||
| 		{ | ||||
| 			/* That was the last sector on the disk. */ | ||||
| 			return 0; | ||||
| 		} | ||||
| 		else | ||||
| 			return _clock; | ||||
| 	} | ||||
|     { | ||||
|         if (_currentSector == 11) | ||||
|         { | ||||
|             /* That was the last sector on the disk. */ | ||||
|             return 0; | ||||
|         } | ||||
|         else | ||||
|             return _clock; | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Skip the ID pattern and track word, which is only present on the | ||||
| 		 * first sector. We don't trust the track word because some driver | ||||
| 		 * don't write it correctly. */ | ||||
|     { | ||||
|         /* Skip the ID pattern and track word, which is only present on the | ||||
|          * first sector. We don't trust the track word because some driver | ||||
|          * don't write it correctly. */ | ||||
|  | ||||
| 		if (_currentSector == 0) | ||||
| 			readRawBits(64); | ||||
|         if (_currentSector == 0) | ||||
|             readRawBits(64); | ||||
|  | ||||
| 		auto bits = readRawBits((SECTOR_SIZE+2)*16); | ||||
| 		auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2); | ||||
|         auto bits = readRawBits((SECTOR_SIZE + 2) * 16); | ||||
|         auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE + 2); | ||||
|  | ||||
| 		uint16_t gotChecksum = 0; | ||||
| 		ByteReader br(bytes); | ||||
| 		for (int i=0; i<(SECTOR_SIZE/2); i++) | ||||
| 			gotChecksum += br.read_be16(); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
|         uint16_t gotChecksum = 0; | ||||
|         ByteReader br(bytes); | ||||
|         for (int i = 0; i < (SECTOR_SIZE / 2); i++) | ||||
|             gotChecksum += br.read_be16(); | ||||
|         uint16_t wantChecksum = br.read_be16(); | ||||
|  | ||||
| 		_sector->logicalTrack = _sector->physicalTrack; | ||||
| 		_sector->logicalSide = _sector->physicalSide; | ||||
| 		_sector->logicalSector = _currentSector; | ||||
| 		_sector->data = bytes.slice(0, SECTOR_SIZE).swab(); | ||||
| 		_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 		_currentSector++; | ||||
| 	} | ||||
|         _sector->logicalTrack = _sector->physicalTrack; | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = _currentSector; | ||||
|         _sector->data = bytes.slice(0, SECTOR_SIZE).swab(); | ||||
|         _sector->status = | ||||
|             (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|         _currentSector++; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _clock; | ||||
| @@ -80,7 +79,5 @@ private: | ||||
|  | ||||
| std::unique_ptr<Decoder> createMxDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new MxDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new MxDecoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -22,7 +22,7 @@ | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| #define MFM_ID 0xaaaaaaaaaaaa5545LL | ||||
| #define FM_ID  0xaaaaaaaaaaaaffefLL | ||||
| #define FM_ID 0xaaaaaaaaaaaaffefLL | ||||
| /* | ||||
|  * MFM sectors have 32 bytes of 00's followed by two sync characters, | ||||
|  * specified in the North Star MDS manual as 0xFBFB. | ||||
| @@ -44,133 +44,143 @@ static const FluxPattern MFM_PATTERN(64, MFM_ID); | ||||
|  */ | ||||
| static const FluxPattern FM_PATTERN(64, FM_ID); | ||||
|  | ||||
| const FluxMatchers ANY_SECTOR_PATTERN( | ||||
| 	{ | ||||
| 		&MFM_PATTERN, | ||||
| 		&FM_PATTERN, | ||||
| 	} | ||||
| ); | ||||
| const FluxMatchers ANY_SECTOR_PATTERN({ | ||||
|     &MFM_PATTERN, | ||||
|     &FM_PATTERN, | ||||
| }); | ||||
|  | ||||
| /* Checksum is initially 0. | ||||
|  * For each data byte, XOR with the current checksum. | ||||
|  * Rotate checksum left, carrying bit 7 to bit 0. | ||||
|  */ | ||||
| uint8_t northstarChecksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint8_t checksum = 0; | ||||
| uint8_t northstarChecksum(const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     uint8_t checksum = 0; | ||||
|  | ||||
| 	while (!br.eof()) { | ||||
| 		checksum ^= br.read_8(); | ||||
| 		checksum = ((checksum << 1) | ((checksum >> 7))); | ||||
| 	} | ||||
|     while (!br.eof()) | ||||
|     { | ||||
|         checksum ^= br.read_8(); | ||||
|         checksum = ((checksum << 1) | ((checksum >> 7))); | ||||
|     } | ||||
|  | ||||
| 	return checksum; | ||||
|     return checksum; | ||||
| } | ||||
|  | ||||
| class NorthstarDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	NorthstarDecoder(const DecoderProto& config): | ||||
| 		Decoder(config), | ||||
| 		_config(config.northstar()) | ||||
| 	{} | ||||
|     NorthstarDecoder(const DecoderProto& config): | ||||
|         Decoder(config), | ||||
|         _config(config.northstar()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| 	/* Search for FM or MFM sector record */ | ||||
| 	nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		nanoseconds_t now = tell().ns(); | ||||
|     /* Search for FM or MFM sector record */ | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         nanoseconds_t now = tell().ns(); | ||||
|  | ||||
| 		/* For all but the first sector, seek to the next sector pulse. | ||||
| 		 * The first sector does not contain the sector pulse in the fluxmap. | ||||
| 		 */ | ||||
| 		if (now != 0) { | ||||
| 			seekToIndexMark(); | ||||
| 			now = tell().ns(); | ||||
| 		} | ||||
|         /* For all but the first sector, seek to the next sector pulse. | ||||
|          * The first sector does not contain the sector pulse in the fluxmap. | ||||
|          */ | ||||
|         if (now != 0) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             now = tell().ns(); | ||||
|         } | ||||
|  | ||||
| 		/* Discard a possible partial sector at the end of the track. | ||||
| 		 * This partial sector could be mistaken for a conflicted sector, if | ||||
| 		 * whatever data read happens to match the checksum of 0, which is | ||||
| 		 * rare, but has been observed on some disks. | ||||
| 		 */ | ||||
| 		if (now > (getFluxmapDuration() - 21e6)) { | ||||
| 			seekToIndexMark(); | ||||
| 			return 0; | ||||
| 		} | ||||
|         /* Discard a possible partial sector at the end of the track. | ||||
|          * This partial sector could be mistaken for a conflicted sector, if | ||||
|          * whatever data read happens to match the checksum of 0, which is | ||||
|          * rare, but has been observed on some disks. | ||||
|          */ | ||||
|         if (now > (getFluxmapDuration() - 21e6)) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
| 		int msSinceIndex = std::round(now / 1e6); | ||||
|         int msSinceIndex = std::round(now / 1e6); | ||||
|  | ||||
| 		/* Note that the seekToPattern ignores the sector pulses, so if | ||||
| 		 * a sector is not found for some reason, the seek will advance | ||||
| 		 * past one or more sector pulses.  For this reason, calculate | ||||
| 		 * _hardSectorId after the sector header is found. | ||||
| 		 */ | ||||
| 		nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN); | ||||
| 		_sector->headerStartTime = tell().ns(); | ||||
|         /* Note that the seekToPattern ignores the sector pulses, so if | ||||
|          * a sector is not found for some reason, the seek will advance | ||||
|          * past one or more sector pulses.  For this reason, calculate | ||||
|          * _hardSectorId after the sector header is found. | ||||
|          */ | ||||
|         nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN); | ||||
|         _sector->headerStartTime = tell().ns(); | ||||
|  | ||||
| 		/* Discard a possible partial sector. */ | ||||
| 		if (_sector->headerStartTime > (getFluxmapDuration() - 21e6)) { | ||||
| 			return 0; | ||||
| 		} | ||||
|         /* Discard a possible partial sector. */ | ||||
|         if (_sector->headerStartTime > (getFluxmapDuration() - 21e6)) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
| 		int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6); | ||||
| 		int sectorFoundTime; | ||||
|         int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6); | ||||
|         int sectorFoundTime; | ||||
|  | ||||
| 		/* Round time to the nearest 20ms */ | ||||
| 		if ((sectorFoundTimeRaw % 20) < 10) { | ||||
| 			sectorFoundTime = (sectorFoundTimeRaw / 20) * 20; | ||||
| 		} | ||||
| 		else { | ||||
| 			sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20; | ||||
| 		} | ||||
|         /* Round time to the nearest 20ms */ | ||||
|         if ((sectorFoundTimeRaw % 20) < 10) | ||||
|         { | ||||
|             sectorFoundTime = (sectorFoundTimeRaw / 20) * 20; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20; | ||||
|         } | ||||
|  | ||||
| 		/* Calculate the sector ID based on time since the index */ | ||||
| 		_hardSectorId = (sectorFoundTime / 20) % 10; | ||||
|         /* Calculate the sector ID based on time since the index */ | ||||
|         _hardSectorId = (sectorFoundTime / 20) % 10; | ||||
|  | ||||
| 		return clock; | ||||
| 	} | ||||
|         return clock; | ||||
|     } | ||||
|  | ||||
| 	void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		uint64_t id = toBytes(readRawBits(64)).reader().read_be64(); | ||||
| 		unsigned recordSize, payloadSize, headerSize; | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         uint64_t id = toBytes(readRawBits(64)).reader().read_be64(); | ||||
|         unsigned recordSize, payloadSize, headerSize; | ||||
|  | ||||
| 		if (id == MFM_ID) { | ||||
| 			recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD; | ||||
| 			payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD; | ||||
| 			headerSize = NORTHSTAR_HEADER_SIZE_DD; | ||||
| 		} | ||||
| 		else { | ||||
| 			recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD; | ||||
| 			payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD; | ||||
| 			headerSize = NORTHSTAR_HEADER_SIZE_SD; | ||||
| 		} | ||||
|         if (id == MFM_ID) | ||||
|         { | ||||
|             recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD; | ||||
|             payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD; | ||||
|             headerSize = NORTHSTAR_HEADER_SIZE_DD; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD; | ||||
|             payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD; | ||||
|             headerSize = NORTHSTAR_HEADER_SIZE_SD; | ||||
|         } | ||||
|  | ||||
| 		auto rawbits = readRawBits(recordSize * 16); | ||||
| 		auto bytes = decodeFmMfm(rawbits).slice(0, recordSize); | ||||
| 		ByteReader br(bytes); | ||||
|         auto rawbits = readRawBits(recordSize * 16); | ||||
|         auto bytes = decodeFmMfm(rawbits).slice(0, recordSize); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->logicalSide = _sector->physicalSide; | ||||
| 		_sector->logicalSector = _hardSectorId; | ||||
| 		_sector->logicalTrack = _sector->physicalTrack; | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = _hardSectorId; | ||||
|         _sector->logicalTrack = _sector->physicalTrack; | ||||
|  | ||||
| 		if (headerSize == NORTHSTAR_HEADER_SIZE_DD) { | ||||
| 			br.read_8();	/* MFM second Sync char, usually 0xFB */ | ||||
| 		} | ||||
|         if (headerSize == NORTHSTAR_HEADER_SIZE_DD) | ||||
|         { | ||||
|             br.read_8(); /* MFM second Sync char, usually 0xFB */ | ||||
|         } | ||||
|  | ||||
| 		_sector->data = br.read(payloadSize); | ||||
| 		uint8_t wantChecksum = br.read_8(); | ||||
| 		uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize - 1, payloadSize)); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = br.read(payloadSize); | ||||
|         uint8_t wantChecksum = br.read_8(); | ||||
|         uint8_t gotChecksum = | ||||
|             northstarChecksum(bytes.slice(headerSize - 1, payloadSize)); | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	const NorthstarDecoderProto& _config; | ||||
| 	uint8_t _hardSectorId; | ||||
|     const NorthstarDecoderProto& _config; | ||||
|     uint8_t _hardSectorId; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createNorthstarDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new NorthstarDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new NorthstarDecoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -49,7 +49,7 @@ static void write_sector(std::vector<bool>& bits, | ||||
|             doubleDensity = true; | ||||
|             break; | ||||
|         default: | ||||
|             Error() << "unsupported sector size --- you must pick 256 or 512"; | ||||
|             error("unsupported sector size --- you must pick 256 or 512"); | ||||
|             break; | ||||
|     } | ||||
|  | ||||
| @@ -96,9 +96,10 @@ static void write_sector(std::vector<bool>& bits, | ||||
|             fullSector->push_back(GAP2_FILL_BYTE); | ||||
|  | ||||
|         if (fullSector->size() != fullSectorSize) | ||||
|             Error() << "sector mismatched length (" << sector->data.size() | ||||
|                     << ") expected: " << fullSector->size() << " got " | ||||
|                     << fullSectorSize; | ||||
|             error("sector mismatched length ({}); expected {}, got {}", | ||||
|                 sector->data.size(), | ||||
|                 fullSector->size(), | ||||
|                 fullSectorSize); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
| @@ -148,7 +149,7 @@ public: | ||||
|             write_sector(bits, cursor, sectorData); | ||||
|  | ||||
|         if (cursor > bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|             error("track data overrun"); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
| @@ -161,8 +162,7 @@ private: | ||||
|     const NorthstarEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createNorthstarEncoder( | ||||
|     const EncoderProto& config) | ||||
| std::unique_ptr<Encoder> createNorthstarEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new NorthstarEncoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| #ifndef NORTHSTAR_H | ||||
| #define NORTHSTAR_H | ||||
|  | ||||
| /* Northstar floppies are 10-hard sectored disks with a sector format as follows: | ||||
| /* Northstar floppies are 10-hard sectored disks with a sector format as | ||||
|  * follows: | ||||
|  * | ||||
|  * |----------------------------------| | ||||
|  * | SYNC Byte  | Payload  | Checksum | | ||||
| @@ -12,15 +13,19 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #define NORTHSTAR_PREAMBLE_SIZE_SD		(16) | ||||
| #define NORTHSTAR_PREAMBLE_SIZE_DD		(32) | ||||
| #define NORTHSTAR_HEADER_SIZE_SD		(1) | ||||
| #define NORTHSTAR_HEADER_SIZE_DD		(2) | ||||
| #define NORTHSTAR_PAYLOAD_SIZE_SD		(256) | ||||
| #define NORTHSTAR_PAYLOAD_SIZE_DD		(512) | ||||
| #define NORTHSTAR_CHECKSUM_SIZE		(1) | ||||
| #define NORTHSTAR_ENCODED_SECTOR_SIZE_SD	(NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE) | ||||
| #define NORTHSTAR_ENCODED_SECTOR_SIZE_DD	(NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE) | ||||
| #define NORTHSTAR_PREAMBLE_SIZE_SD (16) | ||||
| #define NORTHSTAR_PREAMBLE_SIZE_DD (32) | ||||
| #define NORTHSTAR_HEADER_SIZE_SD (1) | ||||
| #define NORTHSTAR_HEADER_SIZE_DD (2) | ||||
| #define NORTHSTAR_PAYLOAD_SIZE_SD (256) | ||||
| #define NORTHSTAR_PAYLOAD_SIZE_DD (512) | ||||
| #define NORTHSTAR_CHECKSUM_SIZE (1) | ||||
| #define NORTHSTAR_ENCODED_SECTOR_SIZE_SD                    \ | ||||
|     (NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + \ | ||||
|         NORTHSTAR_CHECKSUM_SIZE) | ||||
| #define NORTHSTAR_ENCODED_SECTOR_SIZE_DD                    \ | ||||
|     (NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + \ | ||||
|         NORTHSTAR_CHECKSUM_SIZE) | ||||
|  | ||||
| class Decoder; | ||||
| class Encoder; | ||||
| @@ -29,7 +34,9 @@ class DecoderProto; | ||||
|  | ||||
| extern uint8_t northstarChecksum(const Bytes& bytes); | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createNorthstarDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createNorthstarEncoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createNorthstarDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createNorthstarEncoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| #endif /* NORTHSTAR */ | ||||
|   | ||||
| @@ -29,28 +29,23 @@ static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL); | ||||
| class RolandD20Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	RolandD20Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     RolandD20Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(SECTOR_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto rawbits = readRawBits(256); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits); | ||||
| 		fmt::print("{} ", _sector->clock); | ||||
| 		hexdump(std::cout, bytes); | ||||
| 	} | ||||
|     { | ||||
|         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)); | ||||
|     return std::unique_ptr<Decoder>(new RolandD20Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| #pragma once | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config); | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createRolandD20Decoder( | ||||
|     const DecoderProto& config); | ||||
|   | ||||
| @@ -7,4 +7,3 @@ | ||||
| extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -38,61 +38,63 @@ const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244); | ||||
| const uint16_t DATA_ID = 0x550b; | ||||
| const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245); | ||||
|  | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
| const FluxMatchers ANY_RECORD_PATTERN( | ||||
|     {&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN}); | ||||
|  | ||||
| class Tids990Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	Tids990Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     Tids990Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16); | ||||
| 		auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE); | ||||
|     { | ||||
|         auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE * 16); | ||||
|         auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE); | ||||
|  | ||||
| 		ByteReader br(bytes); | ||||
| 		if (br.read_be16() != SECTOR_ID) | ||||
| 			return; | ||||
|         ByteReader br(bytes); | ||||
|         if (br.read_be16() != SECTOR_ID) | ||||
|             return; | ||||
|  | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3)); | ||||
|         uint16_t gotChecksum = | ||||
|             crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE - 3)); | ||||
|  | ||||
| 		_sector->logicalSide = br.read_8() >> 3; | ||||
| 		_sector->logicalTrack = br.read_8(); | ||||
| 		br.read_8(); /* number of sectors per track */ | ||||
| 		_sector->logicalSector = br.read_8(); | ||||
| 		br.read_be16(); /* sector size */ | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
|         _sector->logicalSide = br.read_8() >> 3; | ||||
|         _sector->logicalTrack = br.read_8(); | ||||
|         br.read_8(); /* number of sectors per track */ | ||||
|         _sector->logicalSector = br.read_8(); | ||||
|         br.read_be16(); /* sector size */ | ||||
|         uint16_t wantChecksum = br.read_be16(); | ||||
|  | ||||
| 		if (wantChecksum == gotChecksum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
| 	} | ||||
|         if (wantChecksum == gotChecksum) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
|     } | ||||
|  | ||||
| 	void decodeDataRecord() override | ||||
| 	{ | ||||
| 		auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16); | ||||
| 		auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE); | ||||
|     void decodeDataRecord() override | ||||
|     { | ||||
|         auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE * 16); | ||||
|         auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE); | ||||
|  | ||||
| 		ByteReader br(bytes); | ||||
| 		if (br.read_be16() != DATA_ID) | ||||
| 			return; | ||||
|         ByteReader br(bytes); | ||||
|         if (br.read_be16() != DATA_ID) | ||||
|             return; | ||||
|  | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3)); | ||||
|         uint16_t gotChecksum = | ||||
|             crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE - 3)); | ||||
|  | ||||
| 		_sector->data = br.read(TIDS990_PAYLOAD_SIZE); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = br.read(TIDS990_PAYLOAD_SIZE); | ||||
|         uint16_t wantChecksum = br.read_be16(); | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createTids990Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Tids990Decoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new Tids990Decoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -127,14 +127,14 @@ public: | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|             error("track data overrun"); | ||||
|         while (_cursor < _bits.size()) | ||||
|             writeBytes(1, 0x55); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(_bits, | ||||
|             calculatePhysicalClockPeriod(clockRateUs * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 clockRateUs * 1e3, _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| @@ -145,8 +145,7 @@ private: | ||||
|     bool _lastBit; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createTids990Encoder( | ||||
|     const EncoderProto& config) | ||||
| std::unique_ptr<Encoder> createTids990Encoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new Tids990Encoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| #ifndef TIDS990_H | ||||
| #define TIDS990_H | ||||
|  | ||||
| #define TIDS990_PAYLOAD_SIZE       288 /* bytes */ | ||||
| #define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */ | ||||
| #define TIDS990_DATA_RECORD_SIZE   (TIDS990_PAYLOAD_SIZE + 4) /* bytes */ | ||||
| #define TIDS990_PAYLOAD_SIZE 288                            /* bytes */ | ||||
| #define TIDS990_SECTOR_RECORD_SIZE 10                       /* bytes */ | ||||
| #define TIDS990_DATA_RECORD_SIZE (TIDS990_PAYLOAD_SIZE + 4) /* bytes */ | ||||
|  | ||||
| class Encoder; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
| class EncoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createTids990Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createTids990Encoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createTids990Decoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createTids990Encoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -13,16 +13,18 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(32, VICTOR9K_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(32, VICTOR9K_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; | ||||
| } | ||||
| @@ -37,11 +39,11 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
|     while (ii != bits.end()) | ||||
|     { | ||||
|         uint8_t inputfifo = 0; | ||||
|         for (size_t i=0; i<5; i++) | ||||
|         for (size_t i = 0; i < 5; i++) | ||||
|         { | ||||
|             if (ii == bits.end()) | ||||
|                 break; | ||||
|             inputfifo = (inputfifo<<1) | *ii++; | ||||
|             inputfifo = (inputfifo << 1) | *ii++; | ||||
|         } | ||||
|  | ||||
|         uint8_t decoded = decode_data_gcr(inputfifo); | ||||
| @@ -55,63 +57,62 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
| class Victor9kDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	Victor9kDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     Victor9kDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Check the ID. */ | ||||
|     { | ||||
|         /* Check the ID. */ | ||||
|  | ||||
| 		if (readRaw32() != VICTOR9K_SECTOR_RECORD) | ||||
| 			return; | ||||
|         if (readRaw32() != VICTOR9K_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|         /* Read header. */ | ||||
|  | ||||
| 		auto bytes = decode(readRawBits(3*10)).slice(0, 3); | ||||
|         auto bytes = decode(readRawBits(3 * 10)).slice(0, 3); | ||||
|  | ||||
| 		uint8_t rawTrack = bytes[0]; | ||||
| 		_sector->logicalSector = bytes[1]; | ||||
| 		uint8_t gotChecksum = bytes[2]; | ||||
|         uint8_t rawTrack = bytes[0]; | ||||
|         _sector->logicalSector = bytes[1]; | ||||
|         uint8_t gotChecksum = bytes[2]; | ||||
|  | ||||
| 		_sector->logicalTrack = rawTrack & 0x7f; | ||||
| 		_sector->logicalSide = rawTrack >> 7; | ||||
| 		uint8_t wantChecksum = bytes[0] + bytes[1]; | ||||
| 		if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1)) | ||||
| 			return; | ||||
| 					 | ||||
| 		if (wantChecksum == gotChecksum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|         _sector->logicalTrack = rawTrack & 0x7f; | ||||
|         _sector->logicalSide = rawTrack >> 7; | ||||
|         uint8_t wantChecksum = bytes[0] + bytes[1]; | ||||
|         if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || | ||||
|             (_sector->logicalSide > 1)) | ||||
|             return; | ||||
|  | ||||
|         if (wantChecksum == gotChecksum) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		/* Check the ID. */ | ||||
|     { | ||||
|         /* Check the ID. */ | ||||
|  | ||||
| 		if (readRaw32() != VICTOR9K_DATA_RECORD) | ||||
| 			return; | ||||
|         if (readRaw32() != VICTOR9K_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read data. */ | ||||
|         /* Read data. */ | ||||
|  | ||||
| 		auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+4)*10)) | ||||
| 			.slice(0, VICTOR9K_SECTOR_LENGTH+4); | ||||
| 		ByteReader br(bytes); | ||||
|         auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH + 4) * 10)) | ||||
|                          .slice(0, VICTOR9K_SECTOR_LENGTH + 4); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->data = br.read(VICTOR9K_SECTOR_LENGTH); | ||||
| 		uint16_t gotChecksum = sumBytes(_sector->data); | ||||
| 		uint16_t wantChecksum = br.read_le16(); | ||||
| 		_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->data = br.read(VICTOR9K_SECTOR_LENGTH); | ||||
|         uint16_t gotChecksum = sumBytes(_sector->data); | ||||
|         uint16_t wantChecksum = br.read_le16(); | ||||
|         _sector->status = | ||||
|             (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createVictor9kDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Victor9kDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new Victor9kDecoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -169,14 +169,15 @@ public: | ||||
|         const Image& image) override | ||||
|     { | ||||
|         Victor9kEncoderProto::TrackdataProto trackdata; | ||||
|         getTrackFormat(trackdata, trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|         getTrackFormat( | ||||
|             trackdata, trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         unsigned bitsPerRevolution = (trackdata.rotational_period_ms() * 1e3) / | ||||
|                                      trackdata.clock_period_us(); | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         nanoseconds_t clockPeriod = calculatePhysicalClockPeriod( | ||||
|             trackdata.clock_period_us() * 1e3, | ||||
|             trackdata.rotational_period_ms() * 1e6); | ||||
|         nanoseconds_t clockPeriod = | ||||
|             calculatePhysicalClockPeriod(trackdata.clock_period_us() * 1e3, | ||||
|                 trackdata.rotational_period_ms() * 1e6); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         fillBitmapTo(bits, | ||||
| @@ -189,8 +190,7 @@ public: | ||||
|             write_sector(bits, cursor, trackdata, *sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|             error("track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| @@ -202,8 +202,7 @@ private: | ||||
|     const Victor9kEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createVictor9kEncoder( | ||||
|     const EncoderProto& config) | ||||
| std::unique_ptr<Encoder> createVictor9kEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new Victor9kEncoder(config)); | ||||
| } | ||||
|   | ||||
| @@ -13,12 +13,14 @@ class DecoderProto; | ||||
|  | ||||
| /* ... 1101 0100 1001 | ||||
|  *       ^^ ^^^^ ^^^^ ten bit IO byte */ | ||||
| #define VICTOR9K_DATA_RECORD   0xfffffd49 | ||||
| #define VICTOR9K_DATA_RECORD 0xfffffd49 | ||||
| #define VICTOR9K_DATA_ID 0x8 | ||||
|  | ||||
| #define VICTOR9K_SECTOR_LENGTH 512 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createVictor9kDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createVictor9kEncoder(const EncoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createVictor9kDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createVictor9kEncoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -16,42 +16,40 @@ static const FluxPattern SECTOR_START_PATTERN(16, 0xaaab); | ||||
| class ZilogMczDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	ZilogMczDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     ZilogMczDecoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		seekToIndexMark(); | ||||
| 		return seekToPattern(SECTOR_START_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         seekToIndexMark(); | ||||
|         return seekToPattern(SECTOR_START_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		readRawBits(14); | ||||
|     { | ||||
|         readRawBits(14); | ||||
|  | ||||
| 		auto rawbits = readRawBits(140*16); | ||||
| 		auto bytes = decodeFmMfm(rawbits).slice(0, 140); | ||||
| 		ByteReader br(bytes); | ||||
|         auto rawbits = readRawBits(140 * 16); | ||||
|         auto bytes = decodeFmMfm(rawbits).slice(0, 140); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->logicalSector = br.read_8() & 0x1f; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalTrack = br.read_8() & 0x7f; | ||||
| 		if (_sector->logicalSector > 31) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack > 80) | ||||
| 			return; | ||||
|         _sector->logicalSector = br.read_8() & 0x1f; | ||||
|         _sector->logicalSide = 0; | ||||
|         _sector->logicalTrack = br.read_8() & 0x7f; | ||||
|         if (_sector->logicalSector > 31) | ||||
|             return; | ||||
|         if (_sector->logicalTrack > 80) | ||||
|             return; | ||||
|  | ||||
| 		_sector->data = br.read(132); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
| 		uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134)); | ||||
|         _sector->data = br.read(132); | ||||
|         uint16_t wantChecksum = br.read_be16(); | ||||
|         uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134)); | ||||
|  | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createZilogMczDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new ZilogMczDecoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new ZilogMczDecoder(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| #ifndef ZILOGMCZ_H | ||||
| #define ZILOGMCZ_H | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createZilogMczDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Decoder> createZilogMczDecoder( | ||||
|     const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,6 @@ $(ADFLIB_OBJS): CFLAGS += -Idep/adflib/src -Idep/adflib | ||||
| ADFLIB_LIB = $(OBJDIR)/libadflib.a | ||||
| $(ADFLIB_LIB): $(ADFLIB_OBJS) | ||||
| ADFLIB_CFLAGS = -Idep/adflib/src | ||||
| ADFLIB_LDFLAGS = $(ADFLIB_LIB) | ||||
| ADFLIB_LDFLAGS = | ||||
| OBJS += $(ADFLIB_OBJS) | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,6 @@ $(FATFS_OBJS): CFLAGS += -Idep/fatfs/source | ||||
| FATFS_LIB = $(OBJDIR)/libfatfs.a | ||||
| $(FATFS_LIB): $(FATFS_OBJS) | ||||
| FATFS_CFLAGS = -Idep/fatfs/source | ||||
| FATFS_LDFLAGS = $(FATFS_LIB) | ||||
| FATFS_LDFLAGS =  | ||||
| OBJS += $(FATFS_OBJS) | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,6 @@ $(HFSUTILS_OBJS): CFLAGS += -Idep/hfsutils/libhfs | ||||
| HFSUTILS_LIB = $(OBJDIR)/libhfsutils.a | ||||
| $(HFSUTILS_LIB): $(HFSUTILS_OBJS) | ||||
| HFSUTILS_CFLAGS = -Idep/hfsutils/libhfs | ||||
| HFSUTILS_LDFLAGS = $(HFSUTILS_LIB) | ||||
| HFSUTILS_LDFLAGS = | ||||
| OBJS += $(HFSUTILS_OBJS) | ||||
|  | ||||
|   | ||||
| @@ -54,7 +54,7 @@ LIBUSBP_OBJS = $(patsubst %.c, $(OBJDIR)/%.o, $(LIBUSBP_SRCS)) | ||||
| $(LIBUSBP_OBJS): private CFLAGS += -Idep/libusbp/src -Idep/libusbp/include | ||||
| LIBUSBP_LIB = $(OBJDIR)/libusbp.a | ||||
| LIBUSBP_CFLAGS += -Idep/libusbp/include | ||||
| LIBUSBP_LDFLAGS += $(LIBUSBP_LIB) | ||||
| LIBUSBP_LDFLAGS += | ||||
| $(LIBUSBP_LIB): $(LIBUSBP_OBJS) | ||||
| OBJS += $(LIBUSBP_OBJS) | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/FluxEngine_eagle_pcb.zip
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/FluxEngine_eagle_pcb.zip
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -93,6 +93,22 @@ You're now looking at the _top_ of the board. | ||||
| row of header sockets allowing you to plug the board directly onto the floppy | ||||
| disk drive; for simplicity I'm leaving that as an exercise for the reader.) | ||||
|  | ||||
| ### If you want to use a PCB | ||||
|  | ||||
| Alternatively, you can make an actual PCB! | ||||
|  | ||||
| <div style="text-align: center"> | ||||
| <a href="pcb.png"><img src="pcb.png" style="width:80%" alt="the PCB schematic"></a> | ||||
| </div> | ||||
|  | ||||
| This is a passive breakout board designed to take a PSoC5 development board, a | ||||
| standard 34-way PC connector, and a 50-way 8" drive connector. It was | ||||
| contributed by a user --- thanks! | ||||
|  | ||||
| <a href="FluxEngine_eagle_pcb.zip">Download this to get it</a>. This package | ||||
| contains the layout in Eagle format, a printable PDF of the PCB layout, and | ||||
| gerbers suitable for sending off for manufacture. | ||||
|  | ||||
| ### Grounding | ||||
|  | ||||
| You _also_ need to solder a wire between a handy GND pin on the board and | ||||
| @@ -185,10 +201,14 @@ generic libusb stuff and should build and run on Windows, Linux and OSX as | ||||
| well, although on Windows it'll need MSYS2 and mingw32. You'll need to | ||||
| install some support packages. | ||||
|  | ||||
|   - For Linux (this is Ubuntu, but this should apply to Debian too): | ||||
|   - For Linux with Ubuntu/Debian: | ||||
| 	`libusb-1.0-0-dev`, `libsqlite3-dev`, `zlib1g-dev`, | ||||
| 	`libudev-dev`, `protobuf-compiler`, `libwxgtk3.0-gtk3-dev`, | ||||
| 	`libfmt-dev`. | ||||
|   - For Linux with Fedora/Red Hat: | ||||
|     `git`, `make`, `gcc`, `gcc-c++`, `xxd`, `protobuf-compiler`, | ||||
|     `protobuf-devel`, `fmt-devel`, `systemd-devel`, `wxGTK3-devel`, | ||||
|     `libsqlite3x-devel` | ||||
|   - For OSX with Homebrew: `libusb`, `pkg-config`, `sqlite`, | ||||
|     `protobuf`, `truncate`, `wxwidgets`, `fmt`. | ||||
|   - For Windows with MSYS2: `make`, `mingw-w64-i686-libusb`, | ||||
|   | ||||
| @@ -1,4 +1,15 @@ | ||||
| 40track_drive | ||||
| ==== | ||||
| ## Adjust configuration for a 40-track drive | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Adjust configuration for a 40-track drive | ||||
|  | ||||
| (This format has no documentation. Please file a bug.) | ||||
| This is an extension profile; adding this to the command line will configure | ||||
| FluxEngine to read from 40-track, 48tpi 5.25" drives. You have to tell it because there is | ||||
| no way to detect this automatically. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read ibm --180 40track_drive | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| acornadfs | ||||
| ==== | ||||
| ## BBC Micro, Archimedes | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # BBC Micro, Archimedes | ||||
|  | ||||
| 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 | ||||
| @@ -18,5 +20,20 @@ they might require nudging as the side order can't be reliably autodetected. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `160`: 160kB 3.5" or 5.25" 40-track SSDD; S format | ||||
|       - `320`: 320kB 3.5" or 5.25" 80-track SSDD; M format | ||||
|       - `640`: 640kB 3.5" or 5.25" 80-track DSDD; L format | ||||
|       - `800`: 800kB 3.5" 80-track DSDD; D and E formats | ||||
|       - `1600`: 1600kB 3.5" 80-track DSHD; F formats | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read acornadfs --160 -s drive:0 -o acornadfs.img` | ||||
|   - `fluxengine read acornadfs --320 -s drive:0 -o acornadfs.img` | ||||
|   - `fluxengine read acornadfs --640 -s drive:0 -o acornadfs.img` | ||||
|   - `fluxengine read acornadfs --800 -s drive:0 -o acornadfs.img` | ||||
|   - `fluxengine read acornadfs --1600 -s drive:0 -o acornadfs.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| acorndfs | ||||
| ==== | ||||
| ## Acorn Atom, BBC Micro series | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Acorn Atom, BBC Micro series | ||||
|  | ||||
| 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 | ||||
| @@ -14,7 +16,21 @@ requires a bit of fiddling as they have the same tracks on twice. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `100`: 100kB 40-track SSSD | ||||
|       - `200`: 200kB 80-track SSSD | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read acorndfs --100 -s drive:0 -o acorndfs.img` | ||||
|   - `fluxengine read acorndfs --200 -s drive:0 -o acorndfs.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write acorndfs --100 -d drive:0 -i acorndfs.img` | ||||
|   - `fluxengine write acorndfs --200 -d drive:0 -i acorndfs.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| aeslanier | ||||
| ==== | ||||
| ## 616kB 5.25" 77-track SSDD hard sectored | ||||
| <!-- 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 | ||||
| @@ -31,6 +33,12 @@ based on what looks right. If anyone knows _anything_ about these disks, | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read aeslanier -s drive:0 -o aeslanier.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   * [SA800 Diskette Storage Drive - Theory Of | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| agat | ||||
| ==== | ||||
| ## 840kB 5.25" 80-track DS | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 840kB 5.25" 80-track DS | ||||
|  | ||||
| The Agat (Russian: ↊fd74 | ||||
| 1983. These were based around a 6502 and were nominally Apple II-compatible | ||||
| @@ -14,6 +16,16 @@ profile. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read agat -s drive:0 -o agat.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write agat -d drive:0 -i agat.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Magazine article on the | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| amiga | ||||
| ==== | ||||
| ## 880kB 3.5" DSDD | ||||
| <!-- 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 | ||||
| @@ -16,7 +18,19 @@ distinctly subpar and not particularly good at detecting errors. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Sector size: | ||||
|       - `without_metadata`: 512-byte sectors | ||||
|       - `with_metadata`: 528-byte sectors | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read amiga -s drive:0 -o amiga.adf` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write amiga -d drive:0 -i amiga.adf` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| ampro | ||||
| ==== | ||||
| ## CP/M | ||||
| <!-- 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 | ||||
| @@ -33,7 +35,16 @@ kayinfo.lbr | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `400`: 400kB 40-track DSDD | ||||
|       - `800`: 800kB 80-track DSDD | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read ampro --400 -s drive:0 -o ampro.img` | ||||
|   - `fluxengine read ampro --800 -s drive:0 -o ampro.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| apple2 | ||||
| ==== | ||||
| ## Prodos, Appledos, and CP/M | ||||
| <!-- 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 | ||||
| @@ -42,7 +44,7 @@ volume. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
|   - Format variant: | ||||
|   - Format variants: | ||||
|       - `140`: 140kB 5.25" 35-track SS | ||||
|       - `640`: 640kB 5.25" 80-track DS | ||||
|   - Filesystem and sector skew: | ||||
| @@ -52,6 +54,18 @@ volume. | ||||
|       - `cpm`: use CP/M soft sector skew and filesystem | ||||
|   - `side1`: for AppleDOS file system access, read the volume on side 1 of a disk | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read apple2 --140 -s drive:0 -o apple2.img` | ||||
|   - `fluxengine read apple2 --640 -s drive:0 -o apple2.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write apple2 --140 -d drive:0 -i apple2.img` | ||||
|   - `fluxengine write apple2 --640 -d drive:0 -i apple2.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Beneath Apple DOS](https://fabiensanglard.net/fd_proxy/prince_of_persia/Beneath%20Apple%20DOS.pdf) | ||||
|   | ||||
| @@ -1,4 +1,16 @@ | ||||
| apple2_drive | ||||
| ==== | ||||
| ## Adjust configuration for a 40-track Apple II drive | ||||
| <!-- 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.) | ||||
| This is an extension profile; adding this to the command line will configure | ||||
| FluxEngine to adjust the pinout and track spacing to work with an Apple II | ||||
| drive.  This only works on Greaseweazle hardware and requires a custom | ||||
| connector. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read apple2 --160 apple2_drive | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| atarist | ||||
| ==== | ||||
| ## Almost PC compatible | ||||
| <!-- 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 | ||||
| @@ -13,7 +15,39 @@ Be aware that many PC drives (including mine) won't do the 82 track formats. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `360`: 360kB 3.5" 80-track 9-sector SSDD | ||||
|       - `370`: 370kB 3.5" 82-track 9-sector SSDD | ||||
|       - `400`: 400kB 3.5" 80-track 10-sector SSDD | ||||
|       - `410`: 410kB 3.5" 82-track 10-sector SSDD | ||||
|       - `720`: 720kB 3.5" 80-track 9-sector DSDD | ||||
|       - `740`: 740kB 3.5" 82-track 9-sector DSDD | ||||
|       - `800`: 800kB 3.5" 80-track 10-sector DSDD | ||||
|       - `820`: 820kB 3.5" 82-track 10-sector DSDD | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read atarist --360 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --370 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --400 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --410 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --720 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --740 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --800 -s drive:0 -o atarist.img` | ||||
|   - `fluxengine read atarist --820 -s drive:0 -o atarist.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write atarist --360 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --370 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --400 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --410 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --720 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --740 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --800 -d drive:0 -i atarist.img` | ||||
|   - `fluxengine write atarist --820 -d drive:0 -i atarist.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| bk | ||||
| ==== | ||||
| ## 800kB 5.25"/3.5" 80-track 10-sector DSDD | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # 800kB 5.25"/3.5" 80-track 10-sector DSDD | ||||
|  | ||||
| The BK (an abbreviation for 1ba9 | ||||
| is a Soviet era personal computer from Elektronika based on a PDP-11 | ||||
| @@ -16,3 +18,13 @@ on what was available at the time, with the same format on both. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read bk -s drive:0 -o bk800.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write bk -d drive:0 -i bk800.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| brother | ||||
| ==== | ||||
| ## GCR family | ||||
| <!-- 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 | ||||
| @@ -34,7 +36,21 @@ investigate. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `120`: 120kB 3.5" 39-track SS GCR | ||||
|       - `240`: 240kB 3.5" 78-track SS GCR | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read brother --120 -s drive:0 -o brother.img` | ||||
|   - `fluxengine read brother --240 -s drive:0 -o brother.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write brother --120 -d drive:0 -i brother.img` | ||||
|   - `fluxengine write brother --240 -d drive:0 -i brother.img` | ||||
|  | ||||
| Dealing with misaligned disks | ||||
| ----------------------------- | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| commodore | ||||
| ==== | ||||
| ## 1541, 1581, 8050 and variations | ||||
| <!-- 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 | ||||
| @@ -41,7 +43,29 @@ A CMD FD2000 disk (a popular third-party Commodore disk drive) | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `171`: 171kB 1541, 35-track variant | ||||
|       - `192`: 192kB 1541, 40-track variant | ||||
|       - `800`: 800kB 3.5" 1581 | ||||
|       - `1042`: 1042kB 5.25" 8051 | ||||
|       - `1620`: 1620kB, CMD FD2000 | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read commodore --171 -s drive:0 -o commodore.d64` | ||||
|   - `fluxengine read commodore --192 -s drive:0 -o commodore.d64` | ||||
|   - `fluxengine read commodore --800 -s drive:0 -o commodore.d64` | ||||
|   - `fluxengine read commodore --1042 -s drive:0 -o commodore.d64` | ||||
|   - `fluxengine read commodore --1620 -s drive:0 -o commodore.d64` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write commodore --171 -d drive:0 -i commodore.d64` | ||||
|   - `fluxengine write commodore --192 -d drive:0 -i commodore.d64` | ||||
|   - `fluxengine write commodore --800 -d drive:0 -i commodore.d64` | ||||
|   - `fluxengine write commodore --1620 -d drive:0 -i commodore.d64` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| eco1 | ||||
| ==== | ||||
| ## CP/M; 1210kB 77-track mixed format DSHD | ||||
| <!-- 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 | ||||
| @@ -27,6 +29,12 @@ images. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read eco1 -s drive:0 -o eco1.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Apulio Retrocomputing's page on the | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| epsonpf10 | ||||
| ==== | ||||
| ## CP/M; 3.5" 40-track DSDD | ||||
| <!-- 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 | ||||
| @@ -9,3 +11,9 @@ format itself is yet another IBM scheme variant. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read epsonpf10 -s drive:0 -o epsonpf10.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| f85 | ||||
| ==== | ||||
| ## 461kB 5.25" 77-track SS | ||||
| <!-- 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 | ||||
| @@ -30,6 +32,12 @@ touch](https://github.com/davidgiven/fluxengine/issues/new). | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read f85 -s drive:0 -o f85.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
| There's amazingly little information about these things. | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| fb100 | ||||
| ==== | ||||
| ## 100kB 3.5" 40-track SSSD | ||||
| <!-- 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 | ||||
| @@ -24,6 +26,12 @@ I don't have access to one of those disks. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read fb100 -s drive:0 -o fb100.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Tandy Portable Disk Drive operations | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| hplif | ||||
| ==== | ||||
| ## a variety of disk formats used by HP | ||||
| <!-- 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 | ||||
| @@ -11,5 +13,22 @@ encoding scheme. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `264`: 264kB 3.5" 66-track SSDD; HP9121 format | ||||
|       - `616`: 616kB 3.5" 77-track DSDD | ||||
|       - `770`: 770kB 3.5" 77-track DSDD | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read hplif --264 -s drive:0 -o hplif.img` | ||||
|   - `fluxengine read hplif --616 -s drive:0 -o hplif.img` | ||||
|   - `fluxengine read hplif --770 -s drive:0 -o hplif.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write hplif --264 -d drive:0 -i hplif.img` | ||||
|   - `fluxengine write hplif --616 -d drive:0 -i hplif.img` | ||||
|   - `fluxengine write hplif --770 -d drive:0 -i hplif.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| ibm | ||||
| ==== | ||||
| ## Generic PC 3.5"/5.25" disks | ||||
| <!-- 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 | ||||
| @@ -36,7 +38,47 @@ image format. FluxEngine will use these parameters. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `auto`: try to autodetect the format (unreliable) | ||||
|       - `160`: 160kB 5.25" 40-track 8-sector SSDD | ||||
|       - `180`: 180kB 5.25" 40-track 9-sector SSDD | ||||
|       - `320`: 320kB 5.25" 40-track 8-sector DSDD | ||||
|       - `360`: 360kB 5.25" 40-track 9-sector DSDD | ||||
|       - `720_96`: 720kB 5.25" 80-track 9-sector DSDD | ||||
|       - `720_135`: 720kB 3.5" 80-track 9-sector DSDD | ||||
|       - `1200`: 1200kB 5.25" 80-track 15-sector DSHD | ||||
|       - `1232`: 1232kB 5.25" 77-track 8-sector DSHD | ||||
|       - `1440`: 1440kB 3.5" 80-track 18-sector DSHD | ||||
|       - `1680`: 1680kB 3.5" 80-track 21-sector DSHD; DMF | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read ibm --auto -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --160 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --180 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --320 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --360 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --720_96 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --720_135 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --1200 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --1232 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --1440 -s drive:0 -o ibm.img` | ||||
|   - `fluxengine read ibm --1680 -s drive:0 -o ibm.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write ibm --160 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --180 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --320 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --360 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --720_96 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --720_135 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --1200 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --1232 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --1440 -d drive:0 -i ibm.img` | ||||
|   - `fluxengine write ibm --1680 -d drive:0 -i ibm.img` | ||||
|  | ||||
| Mixed-format disks | ||||
| ------------------ | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| icl30 | ||||
| ==== | ||||
| ## CP/M; 263kB 35-track DSSD | ||||
| <!-- 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 | ||||
| @@ -9,3 +11,9 @@ track! Other than that it's another IBM scheme variation. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read icl30 -s drive:0 -o icl30.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| mac | ||||
| ==== | ||||
| ## 400kB/800kB 3.5" GCR | ||||
| <!-- 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 | ||||
| @@ -36,11 +38,23 @@ standard for disk images is to omit it. If you want them, specify that you want | ||||
|  | ||||
| ## Options | ||||
|  | ||||
|   - Format variant: | ||||
|   - Format variants: | ||||
|       - `400`: 400kB 80-track SSDD | ||||
|       - `800`: 800kB 80-track DSDD | ||||
|   - `metadata`: read/write 524 byte sectors | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read mac --400 -s drive:0 -o mac.dsk` | ||||
|   - `fluxengine read mac --800 -s drive:0 -o mac.dsk` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write mac --400 -d drive:0 -i mac.dsk` | ||||
|   - `fluxengine write mac --800 -d drive:0 -i mac.dsk` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [MAME's ap_dsk35.cpp file](https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp), | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| micropolis | ||||
| ==== | ||||
| ## 100tpi MetaFloppy disks | ||||
| <!-- 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 | ||||
| @@ -45,6 +47,16 @@ need to apply extra options to change the format if desired. | ||||
|       - `630`: 630kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod II | ||||
|   - `vgi`: Read/write VGI format images with 275 bytes per sector | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read micropolis -s drive:0 -o micropolis.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write micropolis -d drive:0 -i micropolis.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Micropolis 1040/1050 S-100 Floppy Disk Subsystems User's Manual][micropolis1040/1050]. | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| mx | ||||
| ==== | ||||
| ## Soviet-era PDP-11 clone | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Soviet-era PDP-11 clone | ||||
|  | ||||
| The DVK (in Russian, 沾7d65 | ||||
| Computing Complex) was a late 1970s Soviet personal computer, a cut-down | ||||
| @@ -40,7 +42,20 @@ Words are all stored little-endian. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `110`: 110kB 5.25" 40-track SSSD | ||||
|       - `220ds`: 220kB 5.25" 40-track DSSD | ||||
|       - `220ss`: 220kB 5.25" 80-track SSSD | ||||
|       - `440`: 440kB 5.25" 80-track DSSD | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read mx --110 -s drive:0 -o mx.img` | ||||
|   - `fluxengine read mx --220ds -s drive:0 -o mx.img` | ||||
|   - `fluxengine read mx --220ss -s drive:0 -o mx.img` | ||||
|   - `fluxengine read mx --440 -s drive:0 -o mx.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| n88basic | ||||
| ==== | ||||
| ## PC8800/PC98 5.25" 77-track 26-sector DSHD | ||||
| <!-- 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 | ||||
| @@ -12,3 +14,13 @@ boot ROM could only read single density data.) | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read n88basic -s drive:0 -o n88basic.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write n88basic -d drive:0 -i n88basic.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| northstar | ||||
| ==== | ||||
| ## 5.25" hard sectored | ||||
| <!-- 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 | ||||
| @@ -20,7 +22,24 @@ equivalent to .img images. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `87`: 87.5kB 5.25" 35-track SSSD hard-sectored | ||||
|       - `175`: 175kB 5.25" 40-track SSDD hard-sectored | ||||
|       - `350`: 350kB 5.25" 40-track DSDD hard-sectored | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read northstar --87 -s drive:0 -o northstar.nsi` | ||||
|   - `fluxengine read northstar --175 -s drive:0 -o northstar.nsi` | ||||
|   - `fluxengine read northstar --350 -s drive:0 -o northstar.nsi` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write northstar --87 -d drive:0 -i northstar.nsi` | ||||
|   - `fluxengine write northstar --175 -d drive:0 -i northstar.nsi` | ||||
|   - `fluxengine write northstar --350 -d drive:0 -i northstar.nsi` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| psos | ||||
| ==== | ||||
| ## 800kB DSDD with PHILE | ||||
| <!-- 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 | ||||
| @@ -18,3 +20,13 @@ and, oddly, swapped sides. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read psos -s drive:0 -o pme.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write psos -d drive:0 -i pme.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| rolandd20 | ||||
| ==== | ||||
| ## 3.5" electronic synthesiser disks | ||||
| <!-- 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. | ||||
| @@ -7,9 +9,13 @@ 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. | ||||
| FluxEngine supports both reading and writing D20 disks, as well as basic support | ||||
| for the filesystem, allowing files to be read from and written to D20 disks. | ||||
| Note that the D20 was never intended to support arbitrary files on its disks and | ||||
| is very likely to crash if you put unexpected files on a disk. In addition, | ||||
| while the file format itself is currently unknown, there is a header at the top | ||||
| of the file containing what appears to be the name shown in the D20 file | ||||
| browser, so the name by which you see it is not necessarily the filename. | ||||
|  | ||||
| Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if | ||||
| you know anything about it. | ||||
| @@ -18,3 +24,13 @@ you know anything about it. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read rolandd20 -s drive:0 -o rolandd20.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write rolandd20 -d drive:0 -i rolandd20.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| rx50 | ||||
| ==== | ||||
| ## 400kB 5.25" 80-track 10-sector SSDD | ||||
| <!-- 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 | ||||
| @@ -9,3 +11,13 @@ vanilla single-sided IBM scheme variation. | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read rx50 -s drive:0 -o rx50.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write rx50 -d drive:0 -i rx50.img` | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,15 @@ | ||||
| shugart_drive | ||||
| ==== | ||||
| ## Adjust configuration for a Shugart drive | ||||
| <!-- This file is automatically generated. Do not edit. --> | ||||
| # Adjust configuration for a Shugart drive | ||||
|  | ||||
| (This format has no documentation. Please file a bug.) | ||||
| This is an extension profile; adding this to the command line will configure | ||||
| FluxEngine to adjust the pinout to work with a Shugart drive. This only works | ||||
| on Greaseweazle hardware. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine read ibm --720 shugart_drive | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| smaky6 | ||||
| ==== | ||||
| ## 308kB 5.25" 77-track 16-sector SSDD, hard sectored | ||||
| <!-- 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 | ||||
| @@ -20,6 +22,12 @@ this is completely correct, so don't trust it! | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read smaky6 -s drive:0 -o smaky6.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo) | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| tids990 | ||||
| ==== | ||||
| ## 1126kB 8" DSSD | ||||
| <!-- 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 | ||||
| @@ -20,6 +22,16 @@ FluxEngine will read and write these (but only the DSDD MFM variant). | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read tids990 -s drive:0 -o tids990.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write tids990 -d drive:0 -i tids990.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   - [The FD1000 Depot Maintenance | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| tiki | ||||
| ==== | ||||
| ## CP/M | ||||
| <!-- 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 | ||||
| @@ -8,5 +10,18 @@ on the precise format. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `90`: 90kB 40-track 18-sector SSSD | ||||
|       - `200`: 200kB 40-track 10-sector SSDD | ||||
|       - `400`: 400kB 40-track 10-sector DSDD | ||||
|       - `800`: 800kB 80-track 10-sector DSDD | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read tiki --90 -s drive:0 -o tiki.img` | ||||
|   - `fluxengine read tiki --200 -s drive:0 -o tiki.img` | ||||
|   - `fluxengine read tiki --400 -s drive:0 -o tiki.img` | ||||
|   - `fluxengine read tiki --800 -s drive:0 -o tiki.img` | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| victor9k | ||||
| ==== | ||||
| ## 1224kB 5.25" DSDD GCR | ||||
| <!-- 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 | ||||
| @@ -36,7 +38,21 @@ FluxEngine can read and write both the single-sided and double-sided variants. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|   - Format variants: | ||||
|       - `612`: 612kB 80-track DSHD GCR | ||||
|       - `1224`: 1224kB 80-track DSHD GCR | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read victor9k --612 -s drive:0 -o victor9k.img` | ||||
|   - `fluxengine read victor9k --1224 -s drive:0 -o victor9k.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write victor9k --612 -d drive:0 -i victor9k.img` | ||||
|   - `fluxengine write victor9k --1224 -d drive:0 -i victor9k.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| zilogmcz | ||||
| ==== | ||||
| ## 320kB 8" 77-track SSSD hard-sectored | ||||
| <!-- 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 | ||||
| @@ -18,16 +20,19 @@ bytes per sector --- 128 bytes of user payload plus two two-byte metadata | ||||
| words used to construct linked lists of sectors for storing files. These | ||||
| stored 320kB each. | ||||
|  | ||||
| FluxEngine has experimental read support for these disks, based on a single | ||||
| Catweasel flux file I've been able to obtain, which only contained 70 tracks. | ||||
| 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)... | ||||
| FluxEngine has read support for these, including support for RIO's ZDOS file | ||||
| system. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read zilogmcz -s drive:0 -o zilogmcz.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   * [About the Zilog MCZ](http://www.retrotechnology.com/restore/zilog.html), | ||||
|   | ||||
| @@ -39,10 +39,10 @@ If you actually have a forty track drive, you need to tell FluxEngine. This is | ||||
| done by adding the special profile `40track_drive`: | ||||
|  | ||||
| ``` | ||||
| fluxengine write ibm360 40track_drive -i image.img -d drive:0 | ||||
| fluxengine write ibm --360 40track_drive -i image.img -d drive:0 | ||||
| ``` | ||||
|  | ||||
| It should then Just Work. This is supported by both FluxEngine and GreaseWeazle | ||||
| It should then Just Work. This is supported by both FluxEngine and Greaseweazle | ||||
| hardware. | ||||
|  | ||||
| Obviously you can't write an eighty-track format using a forty-track drive! | ||||
| @@ -63,7 +63,7 @@ The FluxEngine client supports these with the `apple2_drive` profile: | ||||
| fluxengine write apple2 apple2_drive -i image.img -d drive:0 | ||||
| ``` | ||||
|  | ||||
| This is supported only by GreaseWeazle hardware. | ||||
| This is supported only by Greaseweazle hardware. | ||||
|  | ||||
| Shugart drives | ||||
| -------------- | ||||
| @@ -86,5 +86,5 @@ fluxengine write atarist720 shugart_drive -i image.img -d drive:0 | ||||
| (If you have a 40-track Shugart drive, use _both_ `shugart_drive` and | ||||
| `40track_drive`.) | ||||
|  | ||||
| This is supported only by GreaseWeazle hardware. | ||||
| This is supported only by Greaseweazle hardware. | ||||
|  | ||||
|   | ||||
| @@ -17,13 +17,16 @@ The following file systems are supported so far. | ||||
| |:-----------------------------------------|:-----:|:------:|-------| | ||||
| | Acorn DFS                                |   Y   |        |       | | ||||
| | Amiga FFS                                |   Y   |   Y    | Both OFS and FFS | | ||||
| | AppleDOS / ProDOS                        |   Y   |   Y    | With a choice of sector remapping | | ||||
| | Brother 120kB                            |   Y   |   Y    |       | | ||||
| | Commodore CbmFS                          |   Y   |        | Only 1541 disks so far | | ||||
| | CP/M                                     |   Y   |        | Requires configuration for each machine | | ||||
| | FatFS (a.k.a. MS-DOS)                    |   Y   |   Y    | FAT12, FAT16, FAT32; not Atari (AFAIK!) | | ||||
| | Hewlett-Packard LIF                      |   Y   |        |       | | ||||
| | Macintosh HFS                            |   Y   |   Y    | Only AppleDouble files may be written | | ||||
| | Apple ProDOS                             |   Y   |        |       | | ||||
| | pSOS' PHILE                              |   Y   |        | Probably unreliable due to lack of documentation | | ||||
| | Smaky 6                                  |   Y   |        |       | | ||||
| | Zilog MCZ RIO's ZDOS                     |   Y   |        |       | | ||||
| {: .datatable } | ||||
|  | ||||
| Please not that Atari disks do _not_ use standard FatFS, and the library I'm | ||||
| @@ -38,19 +41,19 @@ Using it | ||||
| To use, try syntax like this: | ||||
|  | ||||
| ``` | ||||
| fluxengine ls ibm180 -f drive:0 | ||||
| fluxengine ls ibm --180 -f drive:0 | ||||
| ``` | ||||
|  | ||||
| `ibm180` is the format, which selects the most common filesystem automatically. | ||||
| `-f drive:0` specifies a flux source/sink, in this case a real disk. You may | ||||
| also specify a flux file (read only). Disk images may be specified with `-i | ||||
| disk.img` (read/write). | ||||
| `ibm --180` is the format, which selects the most common filesystem | ||||
| automatically.  `-f drive:0` specifies a flux source/sink, in this case a real | ||||
| disk. You may also specify a flux file (read only). Disk images may be | ||||
| specified with `-i disk.img` (read/write). | ||||
|  | ||||
| Commands which take filename paramaters typically use `-p` to indicate the path | ||||
| on the disk, and `-l` for the local filename. For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine putfile ibm180 -f drive:0 -p ondisk.pcx -l z.pcx | ||||
| fluxengine putfile ibm --180 -f drive:0 -p ondisk.pcx -l z.pcx | ||||
| ``` | ||||
|  | ||||
| This will copy the file `z.pcx` onto the disk and call it `ONDISK.PCX`. | ||||
| @@ -83,7 +86,7 @@ default; for example, Macintosh HFS filesystems are common on 3.5" floppies. You | ||||
| can do this as follows: | ||||
|  | ||||
| ``` | ||||
| fluxengine format ibm1440 -f drive:1 --filesystem.type=MACHFS | ||||
| fluxengine format ibm --1440 -f drive:1 --filesystem.type=MACHFS | ||||
| ``` | ||||
|  | ||||
| Some filesystems won't work on some disks --- don't try this with Amiga FFS, for | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| Using the FluxEngine client software with GreaseWeazle hardware | ||||
| Using the FluxEngine client software with Greaseweazle hardware | ||||
| =============================================================== | ||||
|  | ||||
| The FluxEngine isn't the only project which does this; another one is the | ||||
| [GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki), a Blue Pill based | ||||
| [Greaseweazle](https://github.com/keirf/Greaseweazle/wiki), a Blue Pill based | ||||
| completely open source solution. This requires more work to set up (or you can | ||||
| buy a prebuilt GreaseWeazle board), but provides completely open source | ||||
| buy a prebuilt Greaseweazle board), but provides completely open source | ||||
| hardware which doesn't require the use of the Cypress Windows-based tools that | ||||
| the FluxEngine does. Luckily, the FluxEngine software supports it almost | ||||
| out-of-the-box --- just plug it in and nearly everything should work. The | ||||
| @@ -16,10 +16,10 @@ FluxEngine makes things complicated when you're not using the FluxEngine client | ||||
| software with a FluxEngine board, but I'm afraid it's too late to change that | ||||
| now. Sorry. | ||||
|  | ||||
| **If you are using GreaseWeazle-compatible hardware** such as the | ||||
| **If you are using Greaseweazle-compatible hardware** such as the | ||||
| [adafruit-floppy](https://github.com/adafruit/Adafruit_Floppy) project, then | ||||
| FluxEngine will still work; however, as the USB VID/PID won't be that of a real | ||||
| GreaseWeazle, the the FluxEngine client can't autodetect it. Instead, you'll | ||||
| Greaseweazle, the the FluxEngine client can't autodetect it. Instead, you'll | ||||
| need to specify the serial port manually with something like | ||||
| `--usb.greaseweazle.port=/dev/ttyACM0` or `--usb.greaseweazle.port=COM5`. | ||||
|  | ||||
| @@ -32,7 +32,7 @@ Driver box says `WinUSB` and the right one says `USB Serial (CDC)`. Then press | ||||
| What works | ||||
| ---------- | ||||
|  | ||||
| Supported features with the GreaseWeazle include: | ||||
| Supported features with the Greaseweazle include: | ||||
|  | ||||
|   - simple reading and writing of disks, seeking etc | ||||
|   - erasing disks | ||||
| @@ -41,8 +41,9 @@ Supported features with the GreaseWeazle include: | ||||
| 	`--usb.greaseweazle.bus_type=SHUGART` or `IBMPC`; the default is `IBMPC`) | ||||
|   - Apple 5.25 floppy interfaces (via `--usb.greaseweazle.bus_type=APPLE2`) | ||||
|  | ||||
| Which device types are supported depend on the hardware. Genuine Greaseweazle hardware supports SHUGART and IBMPC. | ||||
| APPLE2 is only supported with hand wiring and the Adafruit\_Floppy greaseweazle-compatible firmware. | ||||
| Which device types are supported depend on the hardware. Genuine Greaseweazle | ||||
| hardware supports SHUGART and IBMPC.  APPLE2 is only supported with hand wiring | ||||
| and the Adafruit\_Floppy greaseweazle-compatible firmware. | ||||
|  | ||||
| What doesn't work | ||||
| ----------------- | ||||
| @@ -59,12 +60,12 @@ Who to contact | ||||
| -------------- | ||||
|  | ||||
| I want to make it clear that the FluxEngine code is _not_ supported by the | ||||
| GreaseWeazle team. If you have any problems, please [contact | ||||
| Greaseweazle team. If you have any problems, please [contact | ||||
| me](https://github.com/davidgiven/fluxengine/issues/new) and not them. | ||||
|  | ||||
| In addition, the GreaseWeazle release cycle is not synchronised to the | ||||
| In addition, the Greaseweazle release cycle is not synchronised to the | ||||
| FluxEngine release cycle, so it's possible you'll have a version of the | ||||
| GreaseWeazle firmware which is not supported by FluxEngine. Hopefully, it'll | ||||
| Greaseweazle firmware which is not supported by FluxEngine. Hopefully, it'll | ||||
| detect this and complain. Again, [file an | ||||
| issue](https://github.com/davidgiven/fluxengine/issues/new) and I'll look into | ||||
| it. | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/pcb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/pcb.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/screenshot-details.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/screenshot-details.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 28 KiB | 
| @@ -80,11 +80,11 @@ as you need an adapter cable or board, but this will allow you to replicate the | ||||
| FluxEngine hardware on a $2 Blue Pill. | ||||
|  | ||||
| I am _not_ planning on replacing the PSoC5 with a Blue Pill, because someone | ||||
| already has: [the GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki) is | ||||
| already has: [the Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) is | ||||
| a completely open source firmware package which will read and write Supercard | ||||
| Pro files via a standard Blue Pill or via a prebuilt board. It's supported by | ||||
| the FluxEngine client software, and you should, mostly, be able to use | ||||
| GreaseWeazle hardware interchangeably with FluxEngine hardware. See the | ||||
| Greaseweazle hardware interchangeably with FluxEngine hardware. See the | ||||
| [dedicated page](greaseweazle.md) for more information. | ||||
|  | ||||
|  | ||||
| @@ -95,41 +95,43 @@ For reference, here are the FDC pinouts: | ||||
| ```ditaa | ||||
| :-E -s 0.75 | ||||
|  | ||||
|                 +--+--+                   +--+--+    | ||||
|      DISKCHG ---+34+33+        DISKCHG ---+34+33+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        SIDE1 ---+32+31+          SIDE1 ---+32+31+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        RDATA ---+30+29+          RDATA ---+30+29+ | ||||
|                 +--+--+                   +--+--+ | ||||
|          WPT ---+28+27+            WPT ---+28+27+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        TRK00 ---+26+25+          TRK00 ---+26+25+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        WGATE ---+24+23+          WGATE ---+24+23+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        WDATA ---+22+21+          WDATA ---+22+21+ | ||||
|                 +--+--+                   +--+--+ | ||||
|         STEP ---+20+19+           STEP ---+20+19+ | ||||
|                 +--+--+                   +--+--+ | ||||
|    DIR/SIDE1 ---+18+17+      DIR/SIDE1 ---+18+17+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        MOTEB ---+16+15+          MOTEB ---+16+15+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        DRVSA ---+14+13+            DS3 ---+14+13+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        DRVSB ---+12+11+            DS2 ---+12+11+ | ||||
|                 +--+--+                   +--+--+ | ||||
|        MOTEA ---+10+9 +            DS1 ---+10+9 + | ||||
|                 +--+--+                   +--+--+ | ||||
|        INDEX ---+8 +7 +          INDEX ---+8 +7 + | ||||
|                 +--+--+                   +--+--+ | ||||
|          n/c ---+6 +5 +            DS4 ---+6 +5 + | ||||
|                 +--+--+                   +--+--+ | ||||
|          n/c ---+4 +3 +            INU ---+4 +3 + | ||||
|                 +--+--+                   +--+--+ | ||||
|        REDWC ---+2 +1 +          REDWC ---+2 +1 + | ||||
|                 +--+--+                   +--+--+ | ||||
|                        +-- GND                       +-- GND | ||||
|                        |   (entire column)           |   (entire column) | ||||
|                 +----+-+--+                   +----+-+--+    | ||||
|      DISKCHG ---+ 34 + 33 +        DISKCHG ---+ 34 + 33 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        SIDE1 ---+ 32 + 31 +          SIDE1 ---+ 32 + 31 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        RDATA ---+ 30 + 29 +          RDATA ---+ 30 + 29 + | ||||
|                 +----+----+                   +----+----+ | ||||
|          WPT ---+ 28 + 27 +            WPT ---+ 28 + 27 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        TRK00 ---+ 26 + 25 +          TRK00 ---+ 26 + 25 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        WGATE ---+ 24 + 23 +          WGATE ---+ 24 + 23 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        WDATA ---+ 22 + 21 +          WDATA ---+ 22 + 21 + | ||||
|                 +----+----+                   +----+----+ | ||||
|         STEP ---+ 20 + 19 +           STEP ---+ 20 + 19 + | ||||
|                 +----+----+                   +----+----+ | ||||
|    DIR/SIDE1 ---+ 18 + 17 +      DIR/SIDE1 ---+ 18 + 17 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        MOTEB ---+ 16 + 15 +          MOTEB ---+ 16 + 15 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        DRVSA ---+ 14 + 13 +            DS3 ---+ 14 + 13 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        DRVSB ---+ 12 + 11 +            DS2 ---+ 12 + 11 + | ||||
|                 +----+----+                   +----+----+ | ||||
|        MOTEA ---+ 10 + 9  +            DS1 ---+ 10 + 9  + | ||||
|                 +----+----+                   +----+----+ | ||||
|        INDEX ---+ 8  + 7  +          INDEX ---+ 8  + 7  + | ||||
|                 +----+----+                   +----+----+ | ||||
|          n/c ---+ 6  + 5  +            DS4 ---+ 6  + 5  + | ||||
|                 +----+----+                   +----+----+ | ||||
|          n/c ---+ 4  + 3  +            INU ---+ 4  + 3  + | ||||
|                 +----+----+                   +----+----+ | ||||
|        REDWC ---+ 2  + 1  +          REDWC ---+ 2  + 1  + | ||||
|                 +----+----+                   +----+----+    | ||||
|                             | ||||
|               PC interface           Shugart interface | ||||
|   | ||||
|   | ||||
							
								
								
									
										191
									
								
								doc/using.md
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								doc/using.md
									
									
									
									
									
								
							| @@ -11,6 +11,13 @@ moving too quickly for the documentation to keep up. It does respond to | ||||
| `--help` or `help` depending on context. There are some common properties, | ||||
| described below. | ||||
|  | ||||
| If possible, try using the GUI, which should provide simplified access for most | ||||
| common operations. | ||||
|  | ||||
| <div style="text-align: center"> | ||||
| <a href="doc/screenshot-details.png"><img src="doc/screenshot-details.png" style="width:60%" alt="screenshot of the GUI in action"></a> | ||||
| </div> | ||||
|  | ||||
| ### Core concepts | ||||
|  | ||||
| FluxEngine's job is to read magnetic data (called _flux_) off a disk, decode | ||||
| @@ -43,7 +50,7 @@ file while changing the decoder options, to save disk wear. It's also much faste | ||||
|  | ||||
| ### Connecting it up | ||||
|  | ||||
| To use, simply plug your FluxEngine (or [GreaseWeazle](greaseweazle.md)) into | ||||
| To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md)) into | ||||
| your computer and run the client. If a single device is plugged in, it will be | ||||
| automatically detected and used. | ||||
|  | ||||
| @@ -68,10 +75,10 @@ Here are some sample invocations: | ||||
| ``` | ||||
| # Read an PC 1440kB disk, producing a disk image with the default name | ||||
| # (ibm.img) | ||||
| $ fluxengine read ibm1440 | ||||
| $ fluxengine read ibm --1440 | ||||
|  | ||||
| # Write a PC 1440kB disk to drive 1 | ||||
| $ fluxengine write ibm1440 -i image.img -d drive:1 | ||||
| $ fluxengine write ibm --1440 -i image.img -d drive:1 | ||||
|  | ||||
| # Read a Eco1 CP/M disk, making a copy of the flux into a file | ||||
| $ fluxengine read eco1 --copy-flux-to copy.flux -o eco1.ldbs | ||||
| @@ -90,30 +97,31 @@ $ cat config.textpb | ||||
| encoder { | ||||
|   ibm { | ||||
|     trackdata { | ||||
| 	  emit_iam: false | ||||
| 	} | ||||
|       emit_iam: false | ||||
|     } | ||||
|   } | ||||
| } | ||||
| $ fluxengine write ibm1440 config.textpb -i image.img | ||||
| $ fluxengine write ibm --1440 config.textpb -i image.img | ||||
| ``` | ||||
|  | ||||
| ...or you can specify them on the command line: | ||||
|  | ||||
| ``` | ||||
| $ fluxengine write ibm1440 -i image.img --encoder.ibm.trackdata.emit_iam=false | ||||
| $ fluxengine write ibm --1440 -i image.img --encoder.ibm.trackdata.emit_iam=false | ||||
| ``` | ||||
|  | ||||
| Both the above invocations are equivalent. The text files use [Google's | ||||
| protobuf syntax](https://developers.google.com/protocol-buffers), which is | ||||
| hierarchical, type-safe, and easy to read. | ||||
|  | ||||
| The `ibm1440` string above is actually a reference to an internal configuration | ||||
| file containing all the settings for writing PC 1440kB disks. You may specify | ||||
| as many profile names or textpb files as you wish; they are all merged left to | ||||
| right.  You can see all these settings by doing: | ||||
| The `ibm` string above is actually a reference to an internal configuration file | ||||
| containing all the settings for writing PC disks, and the `--1140` refers to a | ||||
| specific definition inside it. You may specify as many profile names or textpb | ||||
| files as you wish; they are all merged left to right.  You can see all these | ||||
| settings by doing: | ||||
|  | ||||
| ``` | ||||
| $ fluxengine write ibm1440 --config | ||||
| $ fluxengine write ibm --1440 --config | ||||
| ``` | ||||
|  | ||||
| The `--config` option will cause the current configuration to be dumped to the | ||||
| @@ -131,29 +139,30 @@ different task. Run each one with `--help` to get a full list of | ||||
| (non-configuration-setting) options; this describes only basic usage of the | ||||
| more common tools. | ||||
|  | ||||
|   - `fluxengine read <profile> -s <flux source> -o <image output>` | ||||
|   - `fluxengine read <profile> <options> -s <flux source> -o <image output>` | ||||
|  | ||||
| 	Reads flux (possibly from a disk) and decodes it into a file system image. | ||||
| 	`<profile>` is a reference to an internal input configuration file | ||||
| 	describing the format. | ||||
|     Reads flux (possibly from a disk) and decodes it into a file system image. | ||||
|     `<profile>` is a reference to an internal input configuration file | ||||
|     describing the format. `<options>` may be any combination of options | ||||
|     defined by the profile. | ||||
|  | ||||
|   - `fluxengine write <profile> -i <image input> -d <flux destination>` | ||||
|  | ||||
| 	Reads a filesystem image and encodes it into flux (possibly writing to a | ||||
| 	disk). `<profile>` is a reference to an internal output configuration file | ||||
| 	describing the format. | ||||
|     Reads a filesystem image and encodes it into flux (possibly writing to a | ||||
|     disk). `<profile>` is a reference to an internal output configuration file | ||||
|     describing the format. | ||||
|  | ||||
|   - `fluxengine rawread -s <flux source> -d <flux destination>` | ||||
|  | ||||
| 	Reads flux (possibly from a disk) and writes it to a flux file without | ||||
| 	doing any decoding. You can specify a profile if you want to read a subset | ||||
| 	of the disk. | ||||
|     Reads flux (possibly from a disk) and writes it to a flux file without doing | ||||
|     any decoding. You can specify a profile if you want to read a subset of the | ||||
|     disk. | ||||
|  | ||||
|   - `fluxengine rawwrite -s <flux source> -d <flux destination>` | ||||
|  | ||||
| 	Reads flux from a file and writes it (possibly to a disk) without doing any | ||||
| 	encoding. You can specify a profile if you want to write a subset of the | ||||
| 	disk. | ||||
|     Reads flux from a file and writes it (possibly to a disk) without doing any | ||||
|     encoding. You can specify a profile if you want to write a subset of the | ||||
|     disk. | ||||
|  | ||||
|   - `fluxengine merge -s <fluxfile> -s <fluxfile...> -d <fluxfile` | ||||
|  | ||||
| @@ -165,20 +174,20 @@ more common tools. | ||||
|  | ||||
|   - `fluxengine inspect -s <flux source> -c <cylinder> -h <head> -B` | ||||
|  | ||||
| 	Reads flux (possibly from a disk) and does various analyses of it to try | ||||
| 	and detect the clock rate, display raw flux information, examine the | ||||
| 	underlying data from the FluxEngine board, etc. There are lots of options | ||||
| 	but the command above is the most useful. | ||||
|     Reads flux (possibly from a disk) and does various analyses of it to try and | ||||
|     detect the clock rate, display raw flux information, examine the underlying | ||||
|     data from the FluxEngine board, etc. There are lots of options but the | ||||
|     command above is the most useful. | ||||
|  | ||||
|   - `fluxengine rpm` | ||||
|  | ||||
| 	Measures the rotation speed of a drive. For hard-sectored disks, you | ||||
| 	probably want to add the name of a read profile to configure the number of | ||||
| 	sectors. | ||||
|     Measures the rotation speed of a drive. For hard-sectored disks, you | ||||
|     probably want to add the name of a read profile to configure the number of | ||||
|     sectors. | ||||
|  | ||||
|   - `fluxengine seek -c <cylinder>` | ||||
|  | ||||
| 	Seeks a drive to a particular cylinder. | ||||
|     Seeks a drive to a particular cylinder. | ||||
|  | ||||
| There are other tools; try `fluxengine --help`. | ||||
|  | ||||
| @@ -198,19 +207,19 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or | ||||
|  | ||||
|   - `drive:<n>` | ||||
|  | ||||
| 	 Read from or write to a specific drive. | ||||
|     Read from or write to a specific drive. | ||||
|    | ||||
|   - `<filename.flux>` | ||||
|  | ||||
| 	Read from or write to a native FluxEngine flux file. | ||||
|     Read from or write to a native FluxEngine flux file. | ||||
|    | ||||
|   - `<filename.scp>` | ||||
|  | ||||
| 	Read from or write to a Supercard Pro `.scp` flux file. | ||||
|     Read from or write to a Supercard Pro `.scp` flux file. | ||||
|    | ||||
|   - `<filename.cwf>` | ||||
|  | ||||
| 	Read from a Catweasel flux file. **Read only.** | ||||
|     Read from a Catweasel flux file. **Read only.** | ||||
|    | ||||
|   - `<filename.a2r>` | ||||
|  | ||||
| @@ -218,8 +227,8 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or | ||||
|  | ||||
|   - `kryoflux:<directory>` | ||||
|  | ||||
| 	Read from a Kryoflux stream, where `<path>` is the directory containing the | ||||
| 	stream files. **Read only.** | ||||
|     Read from a Kryoflux stream, where `<path>` is the directory containing | ||||
|     the stream files. **Read only.** | ||||
|    | ||||
|   - `flx:<directory>` | ||||
|  | ||||
| @@ -228,53 +237,54 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or | ||||
|  | ||||
|   - `erase:` | ||||
|  | ||||
| 	Read nothing --- writing this to a disk will magnetically erase a track. | ||||
| 	**Read only.** | ||||
|     Read nothing --- writing this to a disk will magnetically erase a track. | ||||
|     **Read only.** | ||||
|    | ||||
|   - `testpattern:` | ||||
|  | ||||
| 	Read a test pattern, which can be written to a disk to help diagnosis. | ||||
| 	**Read only.** | ||||
|     Read a test pattern, which can be written to a disk to help diagnosis. | ||||
|     **Read only.** | ||||
|    | ||||
|   - `au:<directory>` | ||||
|  | ||||
| 	Write to a series of `.au` files, one file per track, which can be loaded | ||||
| 	into an audio editor (such as Audacity) as a simple logic analyser. **Write | ||||
| 	only.** | ||||
|     Write to a series of `.au` files, one file per track, which can be loaded | ||||
|     into an audio editor (such as Audacity) as a simple logic analyser. | ||||
|     **Write only.** | ||||
|    | ||||
|   - `vcd:<directory>` | ||||
|  | ||||
| 	Write to a series of `.vcd` files, one file per track, which can be loaded | ||||
| 	into a logic analyser (such as Pulseview) for analysis. **Write only.** | ||||
|     Write to a series of `.vcd` files, one file per track, which can be loaded | ||||
|     into a logic analyser (such as Pulseview) for analysis. **Write only.** | ||||
|  | ||||
| ### Image sources and destinations | ||||
|  | ||||
| FluxEngine also supports a number of file system image formats. When using the | ||||
| `-i` or `-o` options (for input and output), you can use any of these strings: | ||||
|  | ||||
|   - `<filename.adf>`, `<filename.d81>`, `<filename.img>`, `<filename.st>`, `<filename.xdf>` | ||||
|   - `<filename.adf>`, `<filename.d81>`, `<filename.img>`, `<filename.st>`, | ||||
|     `<filename.xdf>` | ||||
|  | ||||
| 	Read from or write to a simple headerless image file (all these formats are | ||||
| 	the same). This will probably want configuration via the | ||||
| 	`input/output.image.img.*` configuration settings to specify all the | ||||
| 	parameters. | ||||
|   Read from or write to a simple headerless image file (all these formats are | ||||
|   the same). This will probably want configuration via the | ||||
|   `input/output.image.img.*` configuration settings to specify all the | ||||
|   parameters. | ||||
|    | ||||
|   - `<filename.diskcopy>` | ||||
|  | ||||
| 	Read from or write to a [DiskCopy | ||||
| 	4.2](https://en.wikipedia.org/wiki/Disk_Copy) image file, commonly used by | ||||
| 	Apple Macintosh emulators. | ||||
|   Read from or write to a [DiskCopy | ||||
|   4.2](https://en.wikipedia.org/wiki/Disk_Copy) image file, commonly used by | ||||
|   Apple Macintosh emulators. | ||||
|    | ||||
|   - `<filename.td0>` | ||||
|  | ||||
| 	Read a [Sydex Teledisk TD0 | ||||
| 	file](https://web.archive.org/web/20210420224336/http://dunfield.classiccmp.org/img47321/teledisk.htm) | ||||
| 	image file. Note that only uncompressed images are supported (so far). | ||||
|   Read a [Sydex Teledisk TD0 | ||||
|   file](https://web.archive.org/web/20210420224336/http://dunfield.classiccmp.org/img47321/teledisk.htm) | ||||
|   image file. Note that only uncompressed images are supported (so far). | ||||
|  | ||||
|   - `<filename.jv3>` | ||||
|  | ||||
| 	Read from a JV3 image file, commonly used by TRS-80 emulators. **Read | ||||
| 	only.** | ||||
|   Read from a JV3 image file, commonly used by TRS-80 emulators. **Read | ||||
|   only.** | ||||
|  | ||||
|   - `<filename.dim>` | ||||
|  | ||||
| @@ -382,38 +392,27 @@ behaviour. | ||||
|  | ||||
|   - `--drive.revolutions=X` | ||||
|  | ||||
|     When reading, spin the disk X times. X | ||||
| 	can be a floating point number. The default is usually 1.2. Some formats | ||||
| 	default to 1.  Increasing the number will sample more data, and can be | ||||
| 	useful on dubious disks to try and get a better read. | ||||
|     When reading, spin the disk X times. X can be a floating point number. The | ||||
|     default is usually 1.2. Some formats default to 1.  Increasing the number | ||||
|     will sample more data, and can be useful on dubious disks to try and get a | ||||
|     better read. | ||||
|  | ||||
|   - `--drive.sync_with_index=true|false` | ||||
|  | ||||
|     Wait for an index pulse | ||||
| 	before starting to read the disk. (Ignored for write operations.) By | ||||
| 	default FluxEngine doesn't, as it makes reads faster, but when diagnosing | ||||
| 	disk problems it's helpful to have all your data start at the same place | ||||
| 	each time. | ||||
|     Wait for an index pulse before starting to read the disk. (Ignored for write | ||||
|     operations.) By default FluxEngine doesn't, as it makes reads faster, but | ||||
|     when diagnosing disk problems it's helpful to have all your data start at | ||||
|     the same place each time. | ||||
|  | ||||
|   - `--drive.index_source=X` | ||||
|   - `--drive.index_mode=X` | ||||
|  | ||||
| 	Set the source of index pulses when reading or writing respectively. This | ||||
| 	is for use with drives which don't produce index pulse data. `X` can be | ||||
| 	`INDEXMODE_DRIVE` to get index pulses from the drive, `INDEXMODE_300` to | ||||
| 	fake 300RPM pulses, or `INDEXMODE_360` to fake 360RPM pulses.  Note this | ||||
| 	has no effect on the _drive_, so it doesn't help with flippy disks, but is | ||||
| 	useful for using very old drives with FluxEngine itself. If you use this | ||||
| 	option, then any index marks in the sampled flux are, of course, garbage. | ||||
|    | ||||
|   - `--flux_source.rescale=X, --flux_sink.rescale=X` | ||||
|    | ||||
|   When reading or writing a floppy on a drive that doesn't match the | ||||
|   original drive RPM, the flux periods can be scaled to compensate. | ||||
|    | ||||
|   For example, to read or write a PC-98 1.2MB (360rpm) floppy using a 300rpm | ||||
|   floppy drive: | ||||
|    | ||||
|   `--flux_source.rescale=1.2 --flux_sink.rescale=1.2` | ||||
|     Set the source of index pulses when reading or writing respectively. This | ||||
|     is for use with drives which don't produce index pulse data. `X` can be | ||||
|     `INDEXMODE_DRIVE` to get index pulses from the drive, `INDEXMODE_300` to | ||||
|     fake 300RPM pulses, or `INDEXMODE_360` to fake 360RPM pulses.  Note this | ||||
|     has no effect on the _drive_, so it doesn't help with flippy disks, but is | ||||
|     useful for using very old drives with FluxEngine itself. If you use this | ||||
|     option, then any index marks in the sampled flux are, of course, garbage. | ||||
|  | ||||
| ## Visualisation | ||||
|  | ||||
| @@ -439,8 +438,8 @@ wrote to do useful things. These are built alongside FluxEngine. | ||||
|  | ||||
|   - `brother120tool`, `brother240tool` | ||||
|  | ||||
| 	Does things to Brother word processor disks. These are [documented on the | ||||
| 	Brother disk format page](disk-brother.md). | ||||
|   Does things to Brother word processor disks. These are [documented on the | ||||
|   Brother disk format page](disk-brother.md). | ||||
|    | ||||
| ## The recommended workflow | ||||
|  | ||||
| @@ -466,13 +465,13 @@ $ fluxengine read brother -s brother.flux -o brother.img --decoder.write_csv_to= | ||||
| Apart from being drastically faster, this avoids touching the (potentially | ||||
| physically fragile) disk. | ||||
|  | ||||
| If the disk is particularly dodgy, you can force FluxEngine not to retry | ||||
| failed reads with `--retries=0`. This reduces head movement. **This is not | ||||
| If the disk is particularly dodgy, you can force FluxEngine not to retry failed | ||||
| reads with `--decoder.retries=0`. This reduces head movement. **This is not | ||||
| recommended.** Floppy disks are inherently unreliable, and the occasional bit | ||||
| error is perfectly normal; FluxEngine will retry and the sector will read | ||||
| fine next time. If you prevent retries, then not only do you get bad sectors | ||||
| in the resulting image, but the flux file itself contains the bad read, so | ||||
| attempting a decode of it will just reproduce the same bad data. | ||||
| error is perfectly normal; FluxEngine will retry and the sector will read fine | ||||
| next time. If you prevent retries, then not only do you get bad sectors in the | ||||
| resulting image, but the flux file itself contains the bad read, so attempting a | ||||
| decode of it will just reproduce the same bad data. | ||||
|  | ||||
| See also the [troubleshooting page](problems.md) for more information about | ||||
| reading dubious disks. | ||||
|   | ||||
| @@ -25,7 +25,7 @@ SetCompressor /solid lzma | ||||
|  	GreaseWeazle hardware. It also allows manipulation of flux files and disk \ | ||||
|  	images, so it's useful without any hardware.$\r$\n\  | ||||
| 	$\r$\n\ | ||||
| 	This wizard will install WordGrinder on your computer.$\r$\n\ | ||||
| 	This wizard will install FluxEngine on your computer.$\r$\n\ | ||||
| 	$\r$\n\ | ||||
| 	$_CLICK" | ||||
|  | ||||
| @@ -130,7 +130,7 @@ SectionEnd | ||||
|  | ||||
| Section "Desktop Shortcut" | ||||
| 	SetOutPath "$DOCUMENTS" | ||||
| 	CreateShortCut "$DESKTOP\WordGrinder.lnk" "$INSTDIR\fluxengine-gui.exe" "" "$INSTDIR\fluxengine-gui.exe" 0 | ||||
| 	CreateShortCut "$DESKTOP\FluxEngine.lnk" "$INSTDIR\fluxengine-gui.exe" "" "$INSTDIR\fluxengine-gui.exe" 0 | ||||
| SectionEnd | ||||
|  | ||||
| ;-------------------------------- | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user