mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			111 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ccd9539015 | ||
|  | 9300aa79c3 | ||
|  | 9e522c7da2 | ||
|  | ef60bfff6b | ||
|  | 635c6c7bfe | ||
|  | df6e47fa50 | ||
|  | 654cdcd3d1 | ||
|  | a633b73e12 | ||
|  | ba93dae240 | ||
|  | 8e0ca85d1e | ||
|  | 56a4926bd3 | ||
|  | 6a2aae4ef2 | ||
|  | ec68ce3bfa | ||
|  | a777a5be30 | ||
|  | b553a8b1fb | ||
|  | b119e1f72d | ||
|  | 7345d3e6c1 | ||
|  | e9b7a7bb52 | ||
|  | 2022732dd9 | ||
|  | 63544647b6 | ||
|  | 6b62585ad5 | ||
|  | 14027210f7 | ||
|  | 3df17b23b8 | ||
|  | cbf3f56562 | ||
|  | 1f74d9189f | ||
|  | 137658d1d6 | ||
|  | 5b627bd2b1 | ||
|  | 38ff08885a | ||
|  | a89993aabb | ||
|  | d6353403e2 | ||
|  | bc62ee04c0 | ||
|  | d3ff836b63 | ||
|  | a7aac5578e | ||
|  | add5a141d3 | ||
|  | 330410ec61 | ||
|  | d0f49dcfa6 | ||
|  | 124f6ab7cb | ||
|  | 471f63592e | ||
|  | 50e210c72f | ||
|  | d3396aa535 | ||
|  | 5ed8b838bc | ||
|  | d1757eacc2 | ||
|  | 0692e5f5d5 | ||
|  | e4204196cd | ||
|  | 241d4342e4 | ||
|  | c04cbc631c | ||
|  | 29b273ad7b | ||
|  | 9720dab2f6 | ||
|  | bddc64a324 | ||
|  | eb324f14de | ||
|  | b78a057c81 | ||
|  | 5751725213 | ||
|  | f194392f99 | ||
|  | fea62178af | ||
|  | 33ef4ce8de | ||
|  | 3728120f95 | ||
|  | 2944b9b3f6 | ||
|  | 3430574364 | ||
|  | fc5a5212c0 | ||
|  | 20f724ed13 | ||
|  | 94c1d21938 | ||
|  | a1a9666b6f | ||
|  | 0551ddc276 | ||
|  | 049ffd3b04 | ||
|  | c28f757c5c | ||
|  | 91dbb86e64 | ||
|  | 27a04ee22b | ||
|  | 5cefce9922 | ||
|  | 8fb4c90bed | ||
|  | 81753669cc | ||
|  | 0a0a72bcf3 | ||
|  | c4a6e3e063 | ||
|  | 1138e6b77f | ||
|  | 030f9218d6 | ||
|  | 2fff32e8f2 | ||
|  | 5b2aa9926f | ||
|  | 921e178e83 | ||
|  | 25ffd900c8 | ||
|  | 7ea4e116cc | ||
|  | a9daec36f5 | ||
|  | cebc7c6cd2 | ||
|  | 3f85c9f006 | ||
|  | ed5efd7b87 | ||
|  | 4984a53bfd | ||
|  | b0c77653a2 | ||
|  | 909f0d628b | ||
|  | e27e3ada92 | ||
|  | 339ea3b5a4 | ||
|  | 9bd8b8915e | ||
|  | 35008656a9 | ||
|  | 825089458f | ||
|  | 4a086d94b7 | ||
|  | 0aeddf7e98 | ||
|  | 4922d1deb4 | ||
|  | 86d0893261 | ||
|  | e4c67f18bd | ||
|  | d07c5a94e1 | ||
|  | a91dee27e7 | ||
|  | e3219087c9 | ||
|  | cc9ec84aec | ||
|  | a33cc5710c | ||
|  | c2b148288a | ||
|  | a483567564 | ||
|  | bd99bc6d94 | ||
|  | 8f79071aad | ||
|  | ef9071049b | ||
|  | 60e1ab8cca | ||
|  | d3dbfd3154 | ||
|  | ee2dffb498 | ||
|  | 6d9510cc65 | ||
|  | 49f0f5d000 | 
| @@ -18,17 +18,19 @@ AlwaysBreakBeforeMultilineStrings: 'true' | ||||
| AlwaysBreakTemplateDeclarations: 'Yes' | ||||
| BinPackArguments: 'false' | ||||
| BinPackParameters: 'false' | ||||
| BreakConstructorInitializers: 'AfterColon' | ||||
| BreakBeforeBraces: Allman | ||||
| BreakConstructorInitializers: 'AfterColon' | ||||
| BreakInheritanceList: AfterColon | ||||
| BreakStringLiterals: 'true' | ||||
| IndentCaseLabels: 'true' | ||||
| IndentWidth: '4' | ||||
| ColumnLimit: '80' | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' | ||||
| FixNamespaceComments: 'false' | ||||
| IncludeBlocks: Preserve | ||||
| IndentCaseLabels: 'true' | ||||
| IndentWidth: '4' | ||||
| IndentWrappedFunctionNames: 'false' | ||||
| KeepEmptyLinesAtTheStartOfBlocks: 'true' | ||||
| NamespaceIndentation: All | ||||
| PointerAlignment: Left | ||||
| ReflowComments: 'true' | ||||
| SortIncludes: 'false' | ||||
|   | ||||
							
								
								
									
										30
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,16 +10,16 @@ jobs: | ||||
|     - name: apt | ||||
|       run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev | ||||
|     - name: make | ||||
|       run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make | ||||
|       run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j2 | ||||
|  | ||||
|   build-macos: | ||||
|   build-macos-current: | ||||
|     runs-on: macos-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: gmake | ||||
|       run: gmake -j2 | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
| @@ -27,6 +27,23 @@ jobs: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: FluxEngine.pkg | ||||
|  | ||||
|   build-macos-10-15: | ||||
|     runs-on: macos-10.15 | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: | | ||||
|         gmake -j2 | ||||
|         mv FluxEngine.pkg FluxEngine-10.15.pkg | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: FluxEngine-10.15.pkg | ||||
|  | ||||
|   build-windows: | ||||
|     runs-on: windows-latest | ||||
|     defaults: | ||||
| @@ -50,9 +67,10 @@ jobs: | ||||
|           mingw-w64-i686-zlib | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - uses: actions/checkout@v1 | ||||
|     - name: build | ||||
|       run: make | ||||
|       run: make -j2 | ||||
|  | ||||
|     - name: nsis | ||||
|       run: | | ||||
| @@ -62,7 +80,7 @@ jobs: | ||||
|  | ||||
|     - name: zip | ||||
|       run: | | ||||
|         zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe | ||||
|         zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|   | ||||
							
								
								
									
										5
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -29,11 +29,12 @@ jobs: | ||||
|           mingw-w64-i686-zlib | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - uses: actions/checkout@v3 | ||||
|  | ||||
|     - name: build | ||||
|       run: | | ||||
|         make | ||||
|         make -j2 | ||||
|  | ||||
|     - name: nsis | ||||
|       run: | | ||||
| @@ -83,7 +84,7 @@ jobs: | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: gmake | ||||
|  | ||||
|   | ||||
							
								
								
									
										10
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Makefile
									
									
									
									
									
								
							| @@ -48,7 +48,7 @@ AR ?= $(CCPREFIX)ar | ||||
| PKG_CONFIG ?= pkg-config | ||||
| WX_CONFIG ?= wx-config | ||||
| PROTOC ?= protoc | ||||
| CFLAGS ?= -g -O0 | ||||
| CFLAGS ?= -g -O3 | ||||
| CXXFLAGS += -std=c++17 | ||||
| LDFLAGS ?= | ||||
| PLATFORM ?= UNIX | ||||
| @@ -111,6 +111,7 @@ PROTOS = \ | ||||
| 	arch/micropolis/micropolis.proto \ | ||||
| 	arch/mx/mx.proto \ | ||||
| 	arch/northstar/northstar.proto \ | ||||
| 	arch/rolandd20/rolandd20.proto \ | ||||
| 	arch/smaky6/smaky6.proto \ | ||||
| 	arch/tids990/tids990.proto \ | ||||
| 	arch/victor9k/victor9k.proto \ | ||||
| @@ -163,19 +164,20 @@ define do-encodedecodetest-impl | ||||
| tests: $(OBJDIR)/$1$3.flux.encodedecode | ||||
| $(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST .flux $1 $3 | ||||
| 	@echo ENCODEDECODETEST $1 flux $(FLUXENGINE_BIN) $2 $3 | ||||
| 	@scripts/encodedecodetest.sh $1 flux $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| tests: $(OBJDIR)/$1$3.scp.encodedecode | ||||
| $(OBJDIR)/$1$3.scp.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST .scp $1 $3 | ||||
| 	@echo ENCODEDECODETEST $1 scp $(FLUXENGINE_BIN) $2 $3 | ||||
| 	@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| endef | ||||
|  | ||||
| $(call do-encodedecodetest,agat840) | ||||
| $(call do-encodedecodetest,amiga) | ||||
| $(call do-encodedecodetest,apple2) | ||||
| $(call do-encodedecodetest,appleii140) | ||||
| $(call do-encodedecodetest,atarist360) | ||||
| $(call do-encodedecodetest,atarist370) | ||||
| $(call do-encodedecodetest,atarist400) | ||||
|   | ||||
| @@ -106,7 +106,7 @@ people who've had it work). | ||||
| | [Acorn DFS](doc/disk-acorndfs.md)         |  🦄   |   🦖*  |                                     | | ||||
| | [Ampro Little Board](doc/disk-ampro.md)   |  🦖   |   🦖*  |                                     | | ||||
| | [Agat](doc/disk-agat.md)                  |  🦖   |        | Soviet Union Apple-II-like computer | | ||||
| | [Apple II](doc/disk-apple2.md)            |  🦄   |   🦄   |                                     | | ||||
| | [Apple II](doc/disk-apple2.md)            |  🦄   |   🦄   | both 140kB and 640kB formats        | | ||||
| | [Amiga](doc/disk-amiga.md)                |  🦄   |   🦄   |                                     | | ||||
| | [Commodore 64 1541/1581](doc/disk-c64.md) |  🦄   |   🦄   | and probably the other formats      | | ||||
| | [Brother 120kB](doc/disk-brother.md)      |  🦄   |   🦄   |                                     | | ||||
| @@ -115,7 +115,8 @@ people who've had it work). | ||||
| | [Elektronika BK](doc/disk-bd.md)          |  🦄   |   🦄   | Soviet Union PDP-11 clone           | | ||||
| | [Macintosh 400kB/800kB](doc/disk-macintosh.md)  |  🦄   |   🦄   |                                     | | ||||
| | [NEC PC-98](doc/disk-ibm.md)              |  🦄   |   🦄   | trimode drive not required          | | ||||
| | [Sharp X68000](doc/disk-ibm.md)           |  🦄   |   🦄   |                                     | | ||||
| | [pSOS](doc/disk-ibm.md)                   |  🦄   |   🦖*  | pSOS PHILE file system              | | ||||
| | [Sharp X68000](doc/disk-ibm.md)           |  🦄   |   🦄   | yet another IBM scheme             | | ||||
| | [Smaky 6](doc/disk-smaky6.md)             |  🦖   |       | 5.25" hard sectored | | ||||
| | [TRS-80](doc/disk-trs80.md)               |  🦖   |   🦖*  | a minor variation of the IBM scheme | | ||||
| {: .datatable } | ||||
|   | ||||
| @@ -3,7 +3,16 @@ | ||||
|  | ||||
| #define AGAT_SECTOR_SIZE 256 | ||||
|  | ||||
| static constexpr uint64_t SECTOR_ID = 0x8924555549111444; | ||||
| static constexpr uint64_t DATA_ID = 0x8924555514444911; | ||||
|  | ||||
| class Encoder; | ||||
| class EncoderProto; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config); | ||||
|  | ||||
| extern uint8_t agatChecksum(const Bytes& bytes); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,19 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message AgatDecoderProto {} | ||||
|  | ||||
| message AgatEncoderProto { | ||||
| 	optional double target_clock_period_us = 1 | ||||
| 		[default=2.00, (help)="Data clock period of target format."]; | ||||
| 	optional double target_rotational_period_ms = 2 | ||||
| 		[default=200.0, (help)="Rotational period of target format."]; | ||||
| 	optional int32 post_index_gap_bytes = 3 | ||||
| 		[default=40, (help)="Post-index gap before first sector header."]; | ||||
| 	optional int32 pre_sector_gap_bytes = 4 | ||||
| 		[default=11, (help)="Gap before each sector header."]; | ||||
| 	optional int32 pre_data_gap_bytes = 5 | ||||
| 		[default=2, (help)="Gap before each sector data record."]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,10 +33,7 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| static const uint64_t SECTOR_ID = 0x8924555549111444; | ||||
| static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID); | ||||
|  | ||||
| static const uint64_t DATA_ID = 0x8924555514444911; | ||||
| static const FluxPattern DATA_PATTERN(64, DATA_ID); | ||||
|  | ||||
| static const FluxMatchers ALL_PATTERNS = { | ||||
|   | ||||
							
								
								
									
										118
									
								
								arch/agat/encoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								arch/agat/encoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/encoders/encoders.h" | ||||
| #include "agat.h" | ||||
| #include "lib/crc.h" | ||||
| #include "lib/readerwriter.h" | ||||
| #include "lib/image.h" | ||||
| #include "lib/layout.h" | ||||
| #include "arch/agat/agat.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
|  | ||||
| class AgatEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     AgatEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.agat()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void writeRawBits(uint64_t data, int width) | ||||
|     { | ||||
|         _cursor += width; | ||||
|         _lastBit = data & 1; | ||||
|         for (int i = 0; i < width; i++) | ||||
|         { | ||||
|             unsigned pos = _cursor - i - 1; | ||||
|             if (pos < _bits.size()) | ||||
|                 _bits[pos] = data & 1; | ||||
|             data >>= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void writeBytes(const Bytes& bytes) | ||||
|     { | ||||
|         encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
|     } | ||||
|  | ||||
|     void writeByte(uint8_t byte) | ||||
|     { | ||||
|         Bytes b; | ||||
|         b.writer().write_8(byte); | ||||
|         writeBytes(b); | ||||
|     } | ||||
|  | ||||
|     void writeFillerRawBytes(int count, uint16_t byte) | ||||
|     { | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeRawBits(byte, 16); | ||||
|     }; | ||||
|  | ||||
|     void writeFillerBytes(int count, uint8_t byte) | ||||
|     { | ||||
|         Bytes b{byte}; | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeBytes(b); | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         auto trackLayout = Layout::getLayoutOfTrack( | ||||
|             trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         double clockRateUs = _config.target_clock_period_us() / 2.0; | ||||
|         int bitsPerRevolution = | ||||
|             (_config.target_rotational_period_ms() * 1000.0) / clockRateUs; | ||||
|         _bits.resize(bitsPerRevolution); | ||||
|         _cursor = 0; | ||||
|  | ||||
|         writeFillerRawBytes(_config.post_index_gap_bytes(), 0xaaaa); | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|         { | ||||
|             /* Header */ | ||||
|  | ||||
|             writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa); | ||||
|             writeRawBits(SECTOR_ID, 64); | ||||
|             writeByte(0x5a); | ||||
|             writeByte((sector->logicalTrack << 1) | sector->logicalSide); | ||||
|             writeByte(sector->logicalSector); | ||||
|             writeByte(0x5a); | ||||
|  | ||||
|             /* Data */ | ||||
|  | ||||
|             writeFillerRawBytes(_config.pre_data_gap_bytes(), 0xaaaa); | ||||
|             auto data = sector->data.slice(0, AGAT_SECTOR_SIZE); | ||||
|             writeRawBits(DATA_ID, 64); | ||||
|             writeBytes(data); | ||||
|             writeByte(agatChecksum(data)); | ||||
|             writeByte(0x5a); | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         fillBitmapTo(_bits, _cursor, _bits.size(), {true, false}); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(_bits, | ||||
|             calculatePhysicalClockPeriod(_config.target_clock_period_us() * 1e3, | ||||
|                 _config.target_rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const AgatEncoderProto& _config; | ||||
|     uint32_t _cursor; | ||||
|     bool _lastBit; | ||||
|     std::vector<bool> _bits; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new AgatEncoder(config)); | ||||
| } | ||||
| @@ -2,7 +2,10 @@ syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Apple2DecoderProto {} | ||||
| message Apple2DecoderProto { | ||||
| 	optional uint32 side_one_track_offset = 1 | ||||
| 		[ default = 0, (help) = "offset to apply to track numbers on side 1" ]; | ||||
| } | ||||
|  | ||||
| message Apple2EncoderProto | ||||
| { | ||||
| @@ -13,4 +16,7 @@ message Apple2EncoderProto | ||||
|     /* Apple II disk drives spin at 300rpm. */ | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 200.0, (help) = "rotational period on the real device" ]; | ||||
|  | ||||
| 	optional uint32 side_one_track_offset = 3 | ||||
| 		[ default = 0, (help) = "offset to apply to track numbers on side 1" ]; | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "apple2.h" | ||||
| #include "arch/apple2/apple2.pb.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
| @@ -12,22 +14,25 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD); | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
| const FluxMatchers ANY_RECORD_PATTERN( | ||||
|     {&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN}); | ||||
|  | ||||
| static int decode_data_gcr(uint8_t gcr) | ||||
| { | ||||
|     switch (gcr) | ||||
|     { | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case gcr: return data; | ||||
| 		#include "data_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case gcr:                \ | ||||
|         return data; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan Woods | ||||
|  * and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan | ||||
|  * Woods and R. Belmont: | ||||
|  * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
|  */ | ||||
| static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
| { | ||||
| @@ -47,9 +52,11 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
|         { | ||||
|             /* 3 * 2 bit */ | ||||
|             output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02); | ||||
|             output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02); | ||||
|             output[i + 86] = | ||||
|                 ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02); | ||||
|             if ((i + 172) < APPLE2_SECTOR_LENGTH) | ||||
|                 output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02); | ||||
|                 output[i + 172] = | ||||
|                     ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -67,88 +74,102 @@ static uint8_t combine(uint16_t word) | ||||
| class Apple2Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	Apple2Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     Apple2Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw24() != APPLE2_SECTOR_RECORD) | ||||
| 			return; | ||||
|     { | ||||
|         if (readRaw24() != APPLE2_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|         /* Read header. */ | ||||
|  | ||||
| 		auto header = toBytes(readRawBits(8*8)).slice(0, 8); | ||||
| 		ByteReader br(header); | ||||
|         auto header = toBytes(readRawBits(8 * 8)).slice(0, 8); | ||||
|         ByteReader br(header); | ||||
|  | ||||
| 		uint8_t volume = combine(br.read_be16()); | ||||
| 		_sector->logicalTrack = combine(br.read_be16()); | ||||
| 		_sector->logicalSector = combine(br.read_be16()); | ||||
| 		uint8_t checksum = combine(br.read_be16()); | ||||
|         uint8_t volume = combine(br.read_be16()); | ||||
|         _sector->logicalTrack = combine(br.read_be16()); | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = combine(br.read_be16()); | ||||
|         uint8_t checksum = combine(br.read_be16()); | ||||
|  | ||||
| 		// If the checksum is correct, upgrade the sector from MISSING | ||||
| 		// to DATA_MISSING in anticipation of its data record | ||||
| 		if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|         // If the checksum is correct, upgrade the sector from MISSING | ||||
|         // to DATA_MISSING in anticipation of its data record | ||||
|         if (checksum == | ||||
|             (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|  | ||||
|         if (_sector->logicalSide == 1) | ||||
|             _sector->logicalTrack -= _config.apple2().side_one_track_offset(); | ||||
|  | ||||
|         /* Sanity check. */ | ||||
|  | ||||
|         if (_sector->logicalTrack > 100) | ||||
|         { | ||||
|             _sector->status = Sector::MISSING; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		/* Check ID. */ | ||||
|     { | ||||
|         /* Check ID. */ | ||||
|  | ||||
| 		if (readRaw24() != APPLE2_DATA_RECORD) | ||||
| 			return; | ||||
|         if (readRaw24() != APPLE2_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
| 		// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and | ||||
| 		// the data itself.  This has been seen on real world disks | ||||
| 		// such as the Apple II Operating System Kit from Apple2Online. | ||||
| 		// However, I haven't seen it described in any of the various | ||||
| 		// references. | ||||
| 		// | ||||
| 		// This extra '0' bit would not affect the real disk interface, | ||||
| 		// as it was a '1' reaching the top bit of a shift register | ||||
| 		// that triggered a byte to be available, but it affects the | ||||
| 		// way the data is read here. | ||||
| 		// | ||||
| 		// While the floppies tested only seemed to need this applied | ||||
| 		// to the first byte of the data record, applying it | ||||
| 		// consistently to all of them doesn't seem to hurt, and | ||||
| 		// simplifies the code. | ||||
|         // Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and | ||||
|         // the data itself.  This has been seen on real world disks | ||||
|         // such as the Apple II Operating System Kit from Apple2Online. | ||||
|         // However, I haven't seen it described in any of the various | ||||
|         // references. | ||||
|         // | ||||
|         // This extra '0' bit would not affect the real disk interface, | ||||
|         // as it was a '1' reaching the top bit of a shift register | ||||
|         // that triggered a byte to be available, but it affects the | ||||
|         // way the data is read here. | ||||
|         // | ||||
|         // While the floppies tested only seemed to need this applied | ||||
|         // to the first byte of the data record, applying it | ||||
|         // consistently to all of them doesn't seem to hurt, and | ||||
|         // simplifies the code. | ||||
|  | ||||
| 		/* Read and decode data. */ | ||||
|         /* Read and decode data. */ | ||||
|  | ||||
| 		auto readApple8 = [&]() { | ||||
| 		    auto result = 0; | ||||
| 		    while((result & 0x80) == 0) { | ||||
| 			auto b = readRawBits(1); | ||||
|                         if(b.empty()) break; | ||||
| 			result = (result << 1) | b[0]; | ||||
| 		    } | ||||
| 		    return result; | ||||
| 		}; | ||||
|         auto readApple8 = [&]() | ||||
|         { | ||||
|             auto result = 0; | ||||
|             while ((result & 0x80) == 0) | ||||
|             { | ||||
|                 auto b = readRawBits(1); | ||||
|                 if (b.empty()) | ||||
|                     break; | ||||
|                 result = (result << 1) | b[0]; | ||||
|             } | ||||
|             return result; | ||||
|         }; | ||||
|  | ||||
| 		constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH+2; | ||||
|                 uint8_t bytes[recordLength]; | ||||
|                 for(auto &byte : bytes) { | ||||
|                     byte = readApple8(); | ||||
|                 } | ||||
|         constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2; | ||||
|         uint8_t bytes[recordLength]; | ||||
|         for (auto& byte : bytes) | ||||
|         { | ||||
|             byte = readApple8(); | ||||
|         } | ||||
|  | ||||
| 		// Upgrade the sector from MISSING to BAD_CHECKSUM. | ||||
| 		// If decode_crazy_data succeeds, it upgrades the sector to | ||||
| 		// OK. | ||||
| 		_sector->status = Sector::BAD_CHECKSUM; | ||||
| 		_sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
| 	} | ||||
|         // Upgrade the sector from MISSING to BAD_CHECKSUM. | ||||
|         // If decode_crazy_data succeeds, it upgrades the sector to | ||||
|         // OK. | ||||
|         _sector->status = Sector::BAD_CHECKSUM; | ||||
|         _sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Apple2Decoder(config)); | ||||
|     return std::unique_ptr<Decoder>(new Apple2Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -56,8 +56,7 @@ public: | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 _config.clock_period_us() * 1e3, | ||||
|             calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
| @@ -132,13 +131,17 @@ private: | ||||
|             // extra padding. | ||||
|             write_ff40(sector.logicalSector == 0 ? 32 : 8); | ||||
|  | ||||
|             int track = sector.logicalTrack; | ||||
|             if (sector.logicalSide == 1) | ||||
|                 track += _config.side_one_track_offset(); | ||||
|  | ||||
|             // Write address field: APPLE2_SECTOR_RECORD + sector identifier + | ||||
|             // DE AA EB | ||||
|             write_bits(APPLE2_SECTOR_RECORD, 24); | ||||
|             write_gcr44(volume_id); | ||||
|             write_gcr44(sector.logicalTrack); | ||||
|             write_gcr44(track); | ||||
|             write_gcr44(sector.logicalSector); | ||||
|             write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector); | ||||
|             write_gcr44(volume_id ^ track ^ sector.logicalSector); | ||||
|             write_bits(0xDEAAEB, 24); | ||||
|  | ||||
|             // Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector | ||||
|   | ||||
| @@ -2,6 +2,7 @@ LIBARCH_SRCS = \ | ||||
| 	arch/aeslanier/decoder.cc \ | ||||
| 	arch/agat/agat.cc \ | ||||
| 	arch/agat/decoder.cc \ | ||||
| 	arch/agat/encoder.cc \ | ||||
| 	arch/amiga/amiga.cc \ | ||||
| 	arch/amiga/decoder.cc \ | ||||
| 	arch/amiga/encoder.cc \ | ||||
| @@ -23,6 +24,7 @@ LIBARCH_SRCS = \ | ||||
| 	arch/mx/decoder.cc \ | ||||
| 	arch/northstar/decoder.cc \ | ||||
| 	arch/northstar/encoder.cc \ | ||||
| 	arch/rolandd20/decoder.cc \ | ||||
| 	arch/smaky6/decoder.cc \ | ||||
| 	arch/tids990/decoder.cc \ | ||||
| 	arch/tids990/encoder.cc \ | ||||
|   | ||||
| @@ -147,6 +147,7 @@ public: | ||||
|         _sector->logicalSide = br.read_8(); | ||||
|         _sector->logicalSector = br.read_8(); | ||||
|         _currentSectorSize = 1 << (br.read_8() + 7); | ||||
|  | ||||
|         uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos)); | ||||
|         uint16_t wantCrc = br.read_be16(); | ||||
|         if (wantCrc == gotCrc) | ||||
| @@ -206,6 +207,18 @@ public: | ||||
|         uint16_t wantCrc = br.read_be16(); | ||||
|         _sector->status = | ||||
|             (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|  | ||||
|         auto layout = Layout::getLayoutOfTrack( | ||||
|             _sector->logicalTrack, _sector->logicalSide); | ||||
|         if (_currentSectorSize != layout->sectorSize) | ||||
|             std::cerr << fmt::format( | ||||
|                 "Warning: configured sector size for t{}.h{}.s{} is {} bytes " | ||||
|                 "but that seen on disk is {} bytes\n", | ||||
|                 _sector->logicalTrack, | ||||
|                 _sector->logicalSide, | ||||
|                 _sector->logicalSector, | ||||
|                 layout->sectorSize, | ||||
|                 _currentSectorSize); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|   | ||||
| @@ -16,14 +16,14 @@ static bool lastBit; | ||||
| static double clockRateUsForTrack(unsigned track) | ||||
| { | ||||
|     if (track < 16) | ||||
|         return 2.623; | ||||
|         return 2.63; | ||||
|     if (track < 32) | ||||
|         return 2.861; | ||||
|         return 2.89; | ||||
|     if (track < 48) | ||||
|         return 3.148; | ||||
|         return 3.20; | ||||
|     if (track < 64) | ||||
|         return 3.497; | ||||
|     return 3.934; | ||||
|         return 3.57; | ||||
|     return 3.98; | ||||
| } | ||||
|  | ||||
| static unsigned sectorsForTrack(unsigned track) | ||||
|   | ||||
							
								
								
									
										56
									
								
								arch/rolandd20/decoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								arch/rolandd20/decoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/crc.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/decoders/fluxmapreader.h" | ||||
| #include "lib/sector.h" | ||||
| #include "lib/bytes.h" | ||||
| #include "rolandd20.h" | ||||
| #include <string.h> | ||||
|  | ||||
| /* Sector header record: | ||||
|  * | ||||
|  * BF FF FF FF FF FF FE AB | ||||
|  * | ||||
|  * This encodes to: | ||||
|  * | ||||
|  *    e    d    5    5    5    5    5    5 | ||||
|  * 1110 1101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    5    5    5    5    5 | ||||
|  * 0101 0101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    5    5    5    5    5 | ||||
|  * 0101 0101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    4    4    4    4    5 | ||||
|  * 0101 0101 0101 0100 0100 0100 0100 0101 | ||||
|  */ | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL); | ||||
|  | ||||
| class RolandD20Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	RolandD20Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto rawbits = readRawBits(256); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits); | ||||
| 		fmt::print("{} ", _sector->clock); | ||||
| 		hexdump(std::cout, bytes); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new RolandD20Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										4
									
								
								arch/rolandd20/rolandd20.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								arch/rolandd20/rolandd20.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| #pragma once | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config); | ||||
|  | ||||
							
								
								
									
										5
									
								
								arch/rolandd20/rolandd20.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								arch/rolandd20/rolandd20.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message RolandD20DecoderProto {} | ||||
|  | ||||
|  | ||||
| @@ -55,6 +55,7 @@ proto_cc_library { | ||||
| 		"./arch/micropolis/micropolis.proto", | ||||
| 		"./arch/mx/mx.proto", | ||||
| 		"./arch/northstar/northstar.proto", | ||||
| 		"./arch/rolandd20/rolandd20.proto", | ||||
| 		"./arch/tids990/tids990.proto", | ||||
| 		"./arch/victor9k/victor9k.proto", | ||||
| 		"./arch/zilogmcz/zilogmcz.proto", | ||||
| @@ -93,6 +94,7 @@ clibrary { | ||||
| 		"./arch/mx/decoder.cc", | ||||
| 		"./arch/northstar/decoder.cc", | ||||
| 		"./arch/northstar/encoder.cc", | ||||
| 		"./arch/rolandd20/rolandd20.cc", | ||||
| 		"./arch/tids990/decoder.cc", | ||||
| 		"./arch/tids990/encoder.cc", | ||||
| 		"./arch/victor9k/decoder.cc", | ||||
|   | ||||
| @@ -30,6 +30,34 @@ FluxEngine can remap the sectors from physical to logical using modifiers.  If | ||||
| you don't specify a remapping modifier, you get the sectors in the order they | ||||
| appear on the disk. | ||||
|  | ||||
| If you don't want an image in physical sector order, specify one of these options: | ||||
|  | ||||
|   - `--appledos` Selects AppleDOS sector translation | ||||
|   - `--prodos` Selects ProDOS sector translation | ||||
|   - `--cpm` Selects CP/M SoftCard sector translation[^1][^2] | ||||
|  | ||||
| These options also select the appropriate file system; FluxEngine has read-only | ||||
| support for all of these. For example: | ||||
|  | ||||
| ``` | ||||
| fluxengine ls appleii140 --appledos -f image.flux | ||||
| ``` | ||||
|  | ||||
| In addition, some third-party systems use 80-track double sides drives, with | ||||
| the same underlying disk format. These are supported with the `appleii640` | ||||
| profile. The complication here is that the AppleDOS filesystem only supports up | ||||
| to 50 tracks, so it needs tweaking to support larger disks. It treats the | ||||
| second side of the disk as a completely different volume. To access these | ||||
| files, use `--appledos --side1`. | ||||
|  | ||||
| [^1]: CP/M disks use the ProDOS translation for the first three tracks and a | ||||
|     different translation for all the tracks thereafter. | ||||
|  | ||||
| [^2]: 80-track CP/M disks are interesting because all the tracks on the second | ||||
|     side have on-disk track numbering from 80..159; the Apple II on-disk format | ||||
|     doesn't have a side byte, so presumably this is to allow tracks on the two | ||||
|     sides to be distinguished from each other. AppleDOS and ProDOS disks don't | ||||
|     do this. | ||||
|  | ||||
| Reading discs | ||||
| ------------- | ||||
| @@ -37,35 +65,25 @@ Reading discs | ||||
| Just do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read apple2 | ||||
| fluxengine read appleii140 | ||||
| ``` | ||||
|  | ||||
| You should end up with an `apple2.img` which is 143360 bytes long. It will be in | ||||
| physical sector ordering. You can specify a sector ordering, `--appledos` or | ||||
| `--prodos` to get an image intended for use in an emulator, due to the logical | ||||
| sector mapping issue described above: | ||||
| (or `appleii640`) | ||||
|  | ||||
| ``` | ||||
| fluxengine read apple2 --prodos | ||||
| ``` | ||||
|  | ||||
| You will also need this for filesystem access. | ||||
| You should end up with an `appleii140.img` which is 143360 bytes long. It will | ||||
| be in physical sector ordering if you don't specify a file system format as | ||||
| described above. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
| ``` | ||||
| fluxengine write apple2 -i apple2.img | ||||
| ``` | ||||
|  | ||||
| If your image is in logical sector ordering (images intended for emulators | ||||
| usually are), specify a modifier of `--appledos` or `--prodos`: | ||||
|  | ||||
| ``` | ||||
| fluxengine write apple2 --prodos -i apple2.img | ||||
| fluxengine write appleii140 -i appleii140.img | ||||
| ``` | ||||
|  | ||||
| The image will be expected to be in physical sector ordering if you don't | ||||
| specify a file system format as described above. | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|   | ||||
| @@ -155,6 +155,14 @@ more common tools. | ||||
| 	encoding. You can specify a profile if you want to write a subset of the | ||||
| 	disk. | ||||
|  | ||||
|   - `fluxengine merge -s <fluxfile> -s <fluxfile...> -d <fluxfile` | ||||
|  | ||||
|     Merges data from multiple flux files together. This is useful if you have | ||||
|     several reads from an unreliable disk where each read has a different set | ||||
|     of good sectors. By merging the flux files, you get to combine all the | ||||
|     data. Don't use this on reads of different disks, for obvious results! Note | ||||
|     that this works on flux files, not on flux sources. | ||||
|  | ||||
|   - `fluxengine inspect -s <flux source> -c <cylinder> -h <head> -B` | ||||
|  | ||||
| 	Reads flux (possibly from a disk) and does various analyses of it to try | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #!/bin/sh | ||||
| dir=`dirname "$0"` | ||||
| cd "$dir" | ||||
| export DYLD_FALLBACK_FRAMEWORK_PATH=../Resources | ||||
| export DYLD_FALLBACK_LIBRARY_PATH=../Resources:/opt/local/lib | ||||
| exec ./fluxengine-gui "$@" | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								extras/fluxfile.piko
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/fluxfile.piko
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extras/fluxfile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/fluxfile.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 365 B | 
							
								
								
									
										
											BIN
										
									
								
								extras/hardware.piko
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/hardware.piko
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extras/hardware.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/hardware.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 428 B | 
							
								
								
									
										
											BIN
										
									
								
								extras/imagefile.piko
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/imagefile.piko
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extras/imagefile.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								extras/imagefile.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 327 B | 
| @@ -9,6 +9,7 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/decoders/fmmfm.cc \ | ||||
| 	lib/encoders/encoders.cc \ | ||||
| 	lib/environment.cc \ | ||||
| 	lib/fl2.cc \ | ||||
| 	lib/flags.cc \ | ||||
| 	lib/fluxmap.cc \ | ||||
| 	lib/fluxsink/a2rfluxsink.cc \ | ||||
| @@ -69,6 +70,7 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/utils.cc \ | ||||
| 	lib/vfs/acorndfs.cc \ | ||||
| 	lib/vfs/amigaffs.cc \ | ||||
| 	lib/vfs/appledos.cc \ | ||||
| 	lib/vfs/applesingle.cc \ | ||||
| 	lib/vfs/brother120fs.cc \ | ||||
| 	lib/vfs/cbmfs.cc \ | ||||
| @@ -77,6 +79,7 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/vfs/machfs.cc \ | ||||
| 	lib/vfs/prodos.cc \ | ||||
| 	lib/vfs/smaky6fs.cc \ | ||||
| 	lib/vfs/philefs.cc \ | ||||
| 	lib/vfs/vfs.cc \ | ||||
| 	lib/vfs/fluxsectorinterface.cc \ | ||||
| 	lib/vfs/imagesectorinterface.cc \ | ||||
|   | ||||
							
								
								
									
										18
									
								
								lib/bytes.cc
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								lib/bytes.cc
									
									
									
									
									
								
							| @@ -215,6 +215,24 @@ Bytes Bytes::reverseBits() const | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::operator + (const Bytes& other) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     bw += *this; | ||||
|     bw += other; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::operator * (size_t count) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     while (count--) | ||||
|         bw += *this; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| uint8_t toByte( | ||||
|     std::vector<bool>::const_iterator start, | ||||
|     std::vector<bool>::const_iterator end) | ||||
|   | ||||
| @@ -64,6 +64,9 @@ public: | ||||
| 	std::vector<bool> toBits() const; | ||||
| 	Bytes reverseBits() const; | ||||
|  | ||||
| 	Bytes operator + (const Bytes& other); | ||||
| 	Bytes operator * (size_t count); | ||||
|  | ||||
|     ByteReader reader() const; | ||||
|     ByteWriter writer(); | ||||
|  | ||||
|   | ||||
| @@ -13,37 +13,44 @@ import "lib/common.proto"; | ||||
| import "lib/layout.proto"; | ||||
|  | ||||
| // NEXT_TAG: 21 | ||||
| message ConfigProto { | ||||
| 	optional string comment = 8; | ||||
| 	optional bool is_extension = 13; | ||||
| 	repeated string include = 19; | ||||
| message ConfigProto | ||||
| { | ||||
|     optional string comment = 8; | ||||
|     optional bool is_extension = 13; | ||||
|     repeated string include = 19; | ||||
|  | ||||
| 	optional LayoutProto layout = 18; | ||||
|     optional LayoutProto layout = 18; | ||||
|  | ||||
| 	optional ImageReaderProto image_reader = 12; | ||||
| 	optional ImageWriterProto image_writer = 9; | ||||
| 	 | ||||
| 	optional FluxSourceProto flux_source = 10; | ||||
| 	optional FluxSinkProto flux_sink = 11; | ||||
| 	optional DriveProto drive = 15; | ||||
|     optional ImageReaderProto image_reader = 12; | ||||
|     optional ImageWriterProto image_writer = 9; | ||||
|  | ||||
| 	optional EncoderProto encoder = 3; | ||||
| 	optional DecoderProto decoder = 4; | ||||
| 	optional UsbProto usb = 5; | ||||
|     optional FluxSourceProto flux_source = 10; | ||||
|     optional FluxSinkProto flux_sink = 11; | ||||
|     optional DriveProto drive = 15; | ||||
|  | ||||
| 	optional RangeProto tracks = 6; | ||||
| 	optional RangeProto heads = 7; | ||||
| 	optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ]; | ||||
|     optional EncoderProto encoder = 3; | ||||
|     optional DecoderProto decoder = 4; | ||||
|     optional UsbProto usb = 5; | ||||
|  | ||||
| 	optional FilesystemProto filesystem = 17; | ||||
| 	 | ||||
| 	repeated OptionProto option = 20; | ||||
|     optional RangeProto tracks = 6; | ||||
|     optional RangeProto heads = 7; | ||||
|     optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ]; | ||||
|  | ||||
|     optional FilesystemProto filesystem = 17; | ||||
|  | ||||
|     repeated OptionProto option = 20; | ||||
| } | ||||
|  | ||||
| message OptionProto { | ||||
| 	optional string name = 1 [(help) = "Option name" ]; | ||||
| 	optional string comment = 2 [(help) = "Help text for option" ]; | ||||
| 	optional string message = 3 [(help) = "Message to display when option is in use" ]; | ||||
| 	optional ConfigProto config = 4 [(help) = "Option data", (recurse) = false ]; | ||||
| message OptionProto | ||||
| { | ||||
|     optional string name = 1 [ (help) = "option name" ]; | ||||
|     optional string comment = 2 [ (help) = "help text for option" ]; | ||||
|     optional string message = 3 | ||||
|         [ (help) = "message to display when option is in use" ]; | ||||
|     optional string exclusivity_group = 5 [ | ||||
|         (help) = | ||||
|             "options with the same group cannot be selected at the same time" | ||||
|     ]; | ||||
|     optional ConfigProto config = 4 | ||||
|         [ (help) = "option data", (recurse) = false ]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
| #include "arch/micropolis/micropolis.h" | ||||
| #include "arch/mx/mx.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "arch/rolandd20/rolandd20.h" | ||||
| #include "arch/smaky6/smaky6.h" | ||||
| #include "arch/tids990/tids990.h" | ||||
| #include "arch/victor9k/victor9k.h" | ||||
| @@ -49,6 +50,7 @@ std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config) | ||||
|             {DecoderProto::kMicropolis, createMicropolisDecoder }, | ||||
|             {DecoderProto::kMx,         createMxDecoder         }, | ||||
|             {DecoderProto::kNorthstar,  createNorthstarDecoder  }, | ||||
|             {DecoderProto::kRolandd20,  createRolandD20Decoder  }, | ||||
|             {DecoderProto::kSmaky6,     createSmaky6Decoder     }, | ||||
|             {DecoderProto::kTids990,    createTids990Decoder    }, | ||||
|             {DecoderProto::kVictor9K,   createVictor9kDecoder   }, | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import "arch/macintosh/macintosh.proto"; | ||||
| import "arch/micropolis/micropolis.proto"; | ||||
| import "arch/mx/mx.proto"; | ||||
| import "arch/northstar/northstar.proto"; | ||||
| import "arch/rolandd20/rolandd20.proto"; | ||||
| import "arch/smaky6/smaky6.proto"; | ||||
| import "arch/tids990/tids990.proto"; | ||||
| import "arch/victor9k/victor9k.proto"; | ||||
| @@ -20,7 +21,7 @@ import "arch/zilogmcz/zilogmcz.proto"; | ||||
| import "lib/fluxsink/fluxsink.proto"; | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| //NEXT: 31 | ||||
| //NEXT: 32 | ||||
| message DecoderProto { | ||||
| 	optional double pulse_debounce_threshold = 1 [default = 0.30, | ||||
| 		(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; | ||||
| @@ -47,6 +48,7 @@ message DecoderProto { | ||||
| 		MicropolisDecoderProto micropolis = 14; | ||||
| 		MxDecoderProto mx = 15; | ||||
| 		NorthstarDecoderProto northstar = 24; | ||||
| 		RolandD20DecoderProto rolandd20 = 31; | ||||
| 		Smaky6DecoderProto smaky6 = 30; | ||||
| 		Tids990DecoderProto tids990 = 16; | ||||
| 		Victor9kDecoderProto victor9k = 17; | ||||
|   | ||||
| @@ -35,6 +35,15 @@ message DriveProto | ||||
|     optional int32 tpi = 11 [ default = 96, (help) = "TPI of drive" ]; | ||||
|     optional double rotational_period_ms = 12 | ||||
|         [ default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"]; | ||||
|  | ||||
|     enum ErrorBehaviour { | ||||
|         NOTHING = 0; | ||||
|         JIGGLE = 1; | ||||
|         RECALIBRATE = 2; | ||||
|     } | ||||
|  | ||||
|     optional ErrorBehaviour error_behaviour = 15 | ||||
|         [ default = JIGGLE, (help) = "what to do when an error occurs during reads" ]; | ||||
| } | ||||
|  | ||||
| // vim: ts=4 sw=4 et | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "arch/agat/agat.h" | ||||
| #include "arch/amiga/amiga.h" | ||||
| #include "arch/apple2/apple2.h" | ||||
| #include "arch/brother/brother.h" | ||||
| @@ -25,6 +26,7 @@ std::unique_ptr<Encoder> Encoder::create( | ||||
|         std::function<std::unique_ptr<Encoder>(const EncoderProto&)>> | ||||
|         encoders = { | ||||
|             {EncoderProto::kAmiga,      createAmigaEncoder      }, | ||||
|             {EncoderProto::kAgat,       createAgatEncoder      }, | ||||
|             {EncoderProto::kApple2,     createApple2Encoder     }, | ||||
|             {EncoderProto::kBrother,    createBrotherEncoder    }, | ||||
|             {EncoderProto::kC64,        createCommodore64Encoder}, | ||||
|   | ||||
| @@ -6,6 +6,7 @@ class Fluxmap; | ||||
| class Image; | ||||
| class Layout; | ||||
| class Sector; | ||||
| class TrackInfo; | ||||
|  | ||||
| class Encoder | ||||
| { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "arch/agat/agat.proto"; | ||||
| import "arch/amiga/amiga.proto"; | ||||
| import "arch/apple2/apple2.proto"; | ||||
| import "arch/brother/brother.proto"; | ||||
| @@ -25,5 +26,6 @@ message EncoderProto | ||||
|         MicropolisEncoderProto micropolis = 10; | ||||
|         Victor9kEncoderProto victor9k = 11; | ||||
|         Apple2EncoderProto apple2 = 12; | ||||
|         AgatEncoderProto agat = 13; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										69
									
								
								lib/fl2.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								lib/fl2.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| #include "globals.h" | ||||
| #include "proto.h" | ||||
| #include "fluxmap.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/fl2.pb.h" | ||||
| #include <fstream> | ||||
|  | ||||
| static void upgradeFluxFile(FluxFileProto& proto) | ||||
| { | ||||
|     if (proto.version() == FluxFileVersion::VERSION_1) | ||||
|     { | ||||
|         /* Change a flux datastream with multiple segments separated by F_DESYNC | ||||
|          * into multiple flux segments. */ | ||||
|  | ||||
|         for (auto& track : *proto.mutable_track()) | ||||
|         { | ||||
|             if (track.flux_size() != 0) | ||||
|             { | ||||
|                 Fluxmap oldFlux(track.flux(0)); | ||||
|  | ||||
|                 track.clear_flux(); | ||||
|                 for (const auto& flux : oldFlux.split()) | ||||
|                     track.add_flux(flux->rawBytes()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         proto.set_version(FluxFileVersion::VERSION_2); | ||||
|     } | ||||
|     if (proto.version() > FluxFileVersion::VERSION_2) | ||||
|         Error() << fmt::format( | ||||
|             "this is a version {} flux file, but this build of the client can " | ||||
|             "only handle up to version {} --- please upgrade", | ||||
|             proto.version(), | ||||
|             FluxFileVersion::VERSION_2); | ||||
| } | ||||
|  | ||||
| FluxFileProto loadFl2File(const std::string filename) | ||||
| { | ||||
|     std::ifstream ifs(filename, std::ios::in | std::ios::binary); | ||||
|     if (!ifs.is_open()) | ||||
|         Error() << fmt::format( | ||||
|             "cannot open input file '{}': {}", filename, strerror(errno)); | ||||
|  | ||||
|     char buffer[16]; | ||||
| 	ifs.read(buffer, sizeof(buffer)); | ||||
|     if (strncmp(buffer, "SQLite format 3", 16) == 0) | ||||
|         Error() << "this flux file is too old; please use the " | ||||
|                    "upgrade-flux-file tool to upgrade it"; | ||||
|  | ||||
|     FluxFileProto proto; | ||||
| 	ifs.seekg(0); | ||||
|     if (!proto.ParseFromIstream(&ifs)) | ||||
|         Error() << fmt::format("unable to read input file '{}'", filename); | ||||
|     upgradeFluxFile(proto); | ||||
|     return proto; | ||||
| } | ||||
|  | ||||
| void saveFl2File(const std::string filename, FluxFileProto& proto) | ||||
| { | ||||
|     proto.set_magic(FluxMagic::MAGIC); | ||||
|     proto.set_version(FluxFileVersion::VERSION_2); | ||||
|  | ||||
|     std::ofstream of(filename, std::ios::out | std::ios::binary); | ||||
|     if (!proto.SerializeToOstream(&of)) | ||||
|         Error() << fmt::format("unable to write output file '{}'", filename); | ||||
|     of.close(); | ||||
|     if (of.fail()) | ||||
|         Error() << "FL2 write I/O error: " << strerror(errno); | ||||
| } | ||||
							
								
								
									
										10
									
								
								lib/fl2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								lib/fl2.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #ifndef FL2_H | ||||
| #define FL2_H | ||||
|  | ||||
| class FluxFileProto; | ||||
|  | ||||
| extern FluxFileProto loadFl2File(const std::string filename); | ||||
| extern void saveFl2File(const std::string filename, FluxFileProto& proto); | ||||
|  | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										49
									
								
								lib/flags.cc
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								lib/flags.cc
									
									
									
									
									
								
							| @@ -55,31 +55,39 @@ static bool setFallbackFlag( | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             for (const auto& configs : config.option()) | ||||
|             { | ||||
|                 if (path == configs.name()) | ||||
|                 { | ||||
|                     if (configs.config().option_size() > 0) | ||||
|                         Error() << fmt::format( | ||||
|                             "option '{}' has an option inside it, which isn't " | ||||
|                             "allowed", | ||||
|                             path); | ||||
|                     if (configs.config().include_size() > 0) | ||||
|                         Error() << fmt::format( | ||||
|                             "option '{}' is trying to include something, which " | ||||
|                             "isn't allowed", | ||||
|                             path); | ||||
|  | ||||
|                     Logger() << fmt::format("OPTION: {}", configs.message()); | ||||
|                     config.MergeFrom(configs.config()); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             if (FlagGroup::applyOption(path)) | ||||
|                 return false; | ||||
|         } | ||||
|     } | ||||
|     Error() << "unrecognised flag; try --help"; | ||||
| } | ||||
|  | ||||
| bool FlagGroup::applyOption(const std::string& option) | ||||
| { | ||||
|     for (const auto& configs : config.option()) | ||||
|     { | ||||
|         if (option == configs.name()) | ||||
|         { | ||||
|             if (configs.config().option_size() > 0) | ||||
|                 Error() << fmt::format( | ||||
|                     "option '{}' has an option inside it, which isn't " | ||||
|                     "allowed", | ||||
|                     option); | ||||
|             if (configs.config().include_size() > 0) | ||||
|                 Error() << fmt::format( | ||||
|                     "option '{}' is trying to include something, which " | ||||
|                     "isn't allowed", | ||||
|                     option); | ||||
|  | ||||
|             Logger() << fmt::format("OPTION: {}", configs.message()); | ||||
|             config.MergeFrom(configs.config()); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc, | ||||
|     const char* argv[], | ||||
|     std::function<bool(const std::string&)> callback) | ||||
| @@ -244,7 +252,6 @@ ConfigProto FlagGroup::parseSingleConfigFile(const std::string& filename, | ||||
|         ss << f.rdbuf(); | ||||
|  | ||||
|         ConfigProto config; | ||||
|         std::cout << ss.str() << '\n'; | ||||
|         if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config)) | ||||
|             Error() << "couldn't load external config proto"; | ||||
|         return config; | ||||
|   | ||||
							
								
								
									
										272
									
								
								lib/flags.h
									
									
									
									
									
								
							
							
						
						
									
										272
									
								
								lib/flags.h
									
									
									
									
									
								
							| @@ -12,21 +12,42 @@ public: | ||||
|     FlagGroup(std::initializer_list<FlagGroup*> groups); | ||||
|  | ||||
| public: | ||||
|     void parseFlags(int argc, const char* argv[], | ||||
| 		std::function<bool(const std::string&)> callback = | ||||
| 			[](const auto&){ return false; }); | ||||
|     void parseFlags( | ||||
|         int argc, | ||||
|         const char* argv[], | ||||
|         std::function<bool(const std::string&)> callback = | ||||
|             [](const auto&) | ||||
|         { | ||||
|             return false; | ||||
|         }); | ||||
|     std::vector<std::string> parseFlagsWithFilenames( | ||||
| 		int argc, const char* argv[], | ||||
| 		std::function<bool(const std::string&)> callback = | ||||
| 			[](const auto&){ return false; }); | ||||
| 	void parseFlagsWithConfigFiles(int argc, const char* argv[], | ||||
| 			const std::map<std::string, std::string>& configFiles); | ||||
| 	static ConfigProto parseSingleConfigFile( | ||||
| 			const std::string& filename, | ||||
| 			const std::map<std::string, std::string>& configFiles); | ||||
| 	static void parseConfigFile( | ||||
| 			const std::string& filename, | ||||
| 			const std::map<std::string, std::string>& configFiles); | ||||
|         int argc, | ||||
|         const char* argv[], | ||||
|         std::function<bool(const std::string&)> callback = | ||||
|             [](const auto&) | ||||
|         { | ||||
|             return false; | ||||
|         }); | ||||
|     void parseFlagsWithConfigFiles(int argc, | ||||
|         const char* argv[], | ||||
|         const std::map<std::string, std::string>& configFiles); | ||||
|  | ||||
|     /* Load one config file (or internal config file), without expanding | ||||
|      * includes. */ | ||||
|  | ||||
|     static ConfigProto parseSingleConfigFile(const std::string& filename, | ||||
|         const std::map<std::string, std::string>& configFiles); | ||||
|  | ||||
|     /* Load a top-level config file (or internal config file), expanding | ||||
|      * includes. */ | ||||
|  | ||||
|     static void parseConfigFile(const std::string& filename, | ||||
|         const std::map<std::string, std::string>& configFiles); | ||||
|  | ||||
|     /* Modify the current config to engage the named option. */ | ||||
|  | ||||
|     static bool applyOption(const std::string& option); | ||||
|  | ||||
|     void addFlag(Flag* flag); | ||||
|     void checkInitialised() const; | ||||
|  | ||||
| @@ -40,14 +61,25 @@ class Flag | ||||
| { | ||||
| public: | ||||
|     Flag(const std::vector<std::string>& names, const std::string helptext); | ||||
|     virtual ~Flag() {}; | ||||
|     virtual ~Flag(){}; | ||||
|  | ||||
|     void checkInitialised() const | ||||
|     { _group.checkInitialised(); } | ||||
|     { | ||||
|         _group.checkInitialised(); | ||||
|     } | ||||
|  | ||||
|     const std::string& name() const { return _names[0]; } | ||||
|     const std::vector<std::string> names() const { return _names; } | ||||
|     const std::string& helptext() const { return _helptext; } | ||||
|     const std::string& name() const | ||||
|     { | ||||
|         return _names[0]; | ||||
|     } | ||||
|     const std::vector<std::string> names() const | ||||
|     { | ||||
|         return _names; | ||||
|     } | ||||
|     const std::string& helptext() const | ||||
|     { | ||||
|         return _helptext; | ||||
|     } | ||||
|  | ||||
|     virtual bool hasArgument() const = 0; | ||||
|     virtual const std::string defaultValueAsString() const = 0; | ||||
| @@ -62,15 +94,26 @@ private: | ||||
| class ActionFlag : Flag | ||||
| { | ||||
| public: | ||||
|     ActionFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             std::function<void(void)> callback): | ||||
|     ActionFlag(const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         std::function<void(void)> callback): | ||||
|         Flag(names, helptext), | ||||
|         _callback(callback) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     bool hasArgument() const { return false; } | ||||
|     const std::string defaultValueAsString() const { return ""; } | ||||
|     void set(const std::string& value) { _callback(); } | ||||
|     bool hasArgument() const | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|     const std::string defaultValueAsString() const | ||||
|     { | ||||
|         return ""; | ||||
|     } | ||||
|     void set(const std::string& value) | ||||
|     { | ||||
|         _callback(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const std::function<void(void)> _callback; | ||||
| @@ -79,16 +122,30 @@ private: | ||||
| class SettableFlag : public Flag | ||||
| { | ||||
| public: | ||||
|     SettableFlag(const std::vector<std::string>& names, const std::string helptext): | ||||
|     SettableFlag( | ||||
|         const std::vector<std::string>& names, const std::string helptext): | ||||
|         Flag(names, helptext) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     operator bool() const | ||||
|     { checkInitialised(); return _value; } | ||||
|     { | ||||
|         checkInitialised(); | ||||
|         return _value; | ||||
|     } | ||||
|  | ||||
|     bool hasArgument() const { return false; } | ||||
|     const std::string defaultValueAsString() const { return "false"; } | ||||
|     void set(const std::string& value) { _value = true; } | ||||
|     bool hasArgument() const | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|     const std::string defaultValueAsString() const | ||||
|     { | ||||
|         return "false"; | ||||
|     } | ||||
|     void set(const std::string& value) | ||||
|     { | ||||
|         _value = true; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     bool _value = false; | ||||
| @@ -98,72 +155,122 @@ template <typename T> | ||||
| class ValueFlag : public Flag | ||||
| { | ||||
| public: | ||||
|     ValueFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             const T defaultValue, | ||||
| 			std::function<void(const T&)> callback = [](const T&) {}): | ||||
|     ValueFlag( | ||||
|         const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         const T defaultValue, | ||||
|         std::function<void(const T&)> callback = | ||||
|             [](const T&) | ||||
|         { | ||||
|         }): | ||||
|         Flag(names, helptext), | ||||
|         _defaultValue(defaultValue), | ||||
|         _value(defaultValue), | ||||
| 		_callback(callback) | ||||
|     {} | ||||
|         _callback(callback) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     const T& get() const | ||||
|     { checkInitialised(); return _value; } | ||||
|     { | ||||
|         checkInitialised(); | ||||
|         return _value; | ||||
|     } | ||||
|  | ||||
|     operator const T& () const  | ||||
|     { return get(); } | ||||
|     operator const T&() const | ||||
|     { | ||||
|         return get(); | ||||
|     } | ||||
|  | ||||
| 	bool isSet() const | ||||
| 	{ return _isSet; } | ||||
|     bool isSet() const | ||||
|     { | ||||
|         return _isSet; | ||||
|     } | ||||
|  | ||||
|     void setDefaultValue(T value) | ||||
|     { | ||||
|         _value = _defaultValue = value; | ||||
|     } | ||||
|  | ||||
|     bool hasArgument() const { return true; } | ||||
|     bool hasArgument() const | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| protected: | ||||
|     T _defaultValue; | ||||
|     T _value; | ||||
| 	bool _isSet = false; | ||||
| 	std::function<void(const T&)> _callback; | ||||
|     bool _isSet = false; | ||||
|     std::function<void(const T&)> _callback; | ||||
| }; | ||||
|  | ||||
| class StringFlag : public ValueFlag<std::string> | ||||
| { | ||||
| public: | ||||
|     StringFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             const std::string defaultValue = "", | ||||
| 			std::function<void(const std::string&)> callback = [](const std::string&) {}): | ||||
|     StringFlag( | ||||
|         const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         const std::string defaultValue = "", | ||||
|         std::function<void(const std::string&)> callback = | ||||
|             [](const std::string&) | ||||
|         { | ||||
|         }): | ||||
|         ValueFlag(names, helptext, defaultValue, callback) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     const std::string defaultValueAsString() const { return _defaultValue; } | ||||
|     void set(const std::string& value) { _value = value; _callback(_value); _isSet = true; } | ||||
|     const std::string defaultValueAsString() const | ||||
|     { | ||||
|         return _defaultValue; | ||||
|     } | ||||
|     void set(const std::string& value) | ||||
|     { | ||||
|         _value = value; | ||||
|         _callback(_value); | ||||
|         _isSet = true; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class IntFlag : public ValueFlag<int> | ||||
| { | ||||
| public: | ||||
|     IntFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             int defaultValue = 0, | ||||
| 			std::function<void(const int&)> callback = [](const int&) {}): | ||||
|     IntFlag( | ||||
|         const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         int defaultValue = 0, | ||||
|         std::function<void(const int&)> callback = | ||||
|             [](const int&) | ||||
|         { | ||||
|         }): | ||||
|         ValueFlag(names, helptext, defaultValue, callback) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     const std::string defaultValueAsString() const { return std::to_string(_defaultValue); } | ||||
|     void set(const std::string& value) { _value = std::stoi(value); _callback(_value); _isSet = true; } | ||||
|     const std::string defaultValueAsString() const | ||||
|     { | ||||
|         return std::to_string(_defaultValue); | ||||
|     } | ||||
|     void set(const std::string& value) | ||||
|     { | ||||
|         _value = std::stoi(value); | ||||
|         _callback(_value); | ||||
|         _isSet = true; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class HexIntFlag : public IntFlag | ||||
| { | ||||
| public: | ||||
|     HexIntFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             int defaultValue = 0, | ||||
| 			std::function<void(const int&)> callback = [](const int&) {}): | ||||
|     HexIntFlag( | ||||
|         const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         int defaultValue = 0, | ||||
|         std::function<void(const int&)> callback = | ||||
|             [](const int&) | ||||
|         { | ||||
|         }): | ||||
|         IntFlag(names, helptext, defaultValue, callback) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     const std::string defaultValueAsString() const; | ||||
| }; | ||||
| @@ -171,26 +278,49 @@ public: | ||||
| class DoubleFlag : public ValueFlag<double> | ||||
| { | ||||
| public: | ||||
|     DoubleFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             double defaultValue = 1.0, | ||||
| 			std::function<void(const double&)> callback = [](const double&) {}): | ||||
|     DoubleFlag( | ||||
|         const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         double defaultValue = 1.0, | ||||
|         std::function<void(const double&)> callback = | ||||
|             [](const double&) | ||||
|         { | ||||
|         }): | ||||
|         ValueFlag(names, helptext, defaultValue, callback) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     const std::string defaultValueAsString() const { return std::to_string(_defaultValue); } | ||||
|     void set(const std::string& value) { _value = std::stod(value); _callback(_value); _isSet = true; } | ||||
|     const std::string defaultValueAsString() const | ||||
|     { | ||||
|         return std::to_string(_defaultValue); | ||||
|     } | ||||
|     void set(const std::string& value) | ||||
|     { | ||||
|         _value = std::stod(value); | ||||
|         _callback(_value); | ||||
|         _isSet = true; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class BoolFlag : public ValueFlag<bool> | ||||
| { | ||||
| public: | ||||
|     BoolFlag(const std::vector<std::string>& names, const std::string helptext, | ||||
|             bool defaultValue = false, | ||||
| 			std::function<void(const bool&)> callback = [](const bool&) {}): | ||||
|     BoolFlag( | ||||
|         const std::vector<std::string>& names, | ||||
|         const std::string helptext, | ||||
|         bool defaultValue = false, | ||||
|         std::function<void(const bool&)> callback = | ||||
|             [](const bool&) | ||||
|         { | ||||
|         }): | ||||
|         ValueFlag(names, helptext, defaultValue, callback) | ||||
|     {} | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     const std::string defaultValueAsString() const { return _defaultValue ? "true" : "false"; } | ||||
|     const std::string defaultValueAsString() const | ||||
|     { | ||||
|         return _defaultValue ? "true" : "false"; | ||||
|     } | ||||
|     void set(const std::string& value); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -76,3 +76,89 @@ std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const | ||||
|  | ||||
|     return maps; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Tries to guess the clock by finding the smallest common interval. | ||||
|  * Returns nanoseconds. | ||||
|  */ | ||||
| Fluxmap::ClockData Fluxmap::guessClock( | ||||
|     double noiseFloorFactor, double signalLevelFactor) const | ||||
| { | ||||
|     ClockData data = {}; | ||||
|  | ||||
|     FluxmapReader fr(*this); | ||||
|  | ||||
|     while (!fr.eof()) | ||||
|     { | ||||
|         unsigned interval; | ||||
|         fr.findEvent(F_BIT_PULSE, interval); | ||||
|         if (interval > 0xff) | ||||
|             continue; | ||||
|         data.buckets[interval]++; | ||||
|     } | ||||
|  | ||||
|     uint32_t max = | ||||
|         *std::max_element(std::begin(data.buckets), std::end(data.buckets)); | ||||
|     uint32_t min = | ||||
|         *std::min_element(std::begin(data.buckets), std::end(data.buckets)); | ||||
|     data.noiseFloor = min + (max - min) * noiseFloorFactor; | ||||
|     data.signalLevel = min + (max - min) * signalLevelFactor; | ||||
|  | ||||
|     /* Find a point solidly within the first pulse. */ | ||||
|  | ||||
|     int pulseindex = 0; | ||||
|     while (pulseindex < 256) | ||||
|     { | ||||
|         if (data.buckets[pulseindex] > data.signalLevel) | ||||
|             break; | ||||
|         pulseindex++; | ||||
|     } | ||||
|     if (pulseindex == -1) | ||||
|         return data; | ||||
|  | ||||
|     /* Find the upper and lower bounds of the pulse. */ | ||||
|  | ||||
|     int peaklo = pulseindex; | ||||
|     while (peaklo > 0) | ||||
|     { | ||||
|         if (data.buckets[peaklo] < data.noiseFloor) | ||||
|             break; | ||||
|         peaklo--; | ||||
|     } | ||||
|  | ||||
|     int peakhi = pulseindex; | ||||
|     while (peakhi < 255) | ||||
|     { | ||||
|         if (data.buckets[peakhi] < data.noiseFloor) | ||||
|             break; | ||||
|         peakhi++; | ||||
|     } | ||||
|  | ||||
|     /* Find the total accumulated size of the pulse. */ | ||||
|  | ||||
|     uint32_t total_size = 0; | ||||
|     for (int i = peaklo; i < peakhi; i++) | ||||
|         total_size += data.buckets[i]; | ||||
|  | ||||
|     /* Now find the median. */ | ||||
|  | ||||
|     uint32_t count = 0; | ||||
|     int median = peaklo; | ||||
|     while (median < peakhi) | ||||
|     { | ||||
|         count += data.buckets[median]; | ||||
|         if (count > (total_size / 2)) | ||||
|             break; | ||||
|         median++; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Okay, the median should now be a good candidate for the (or a) clock. | ||||
|      * How this maps onto the actual clock rate depends on the encoding. | ||||
|      */ | ||||
|  | ||||
|     data.peakStart = peaklo * NS_PER_TICK; | ||||
|     data.peakEnd = peakhi * NS_PER_TICK; | ||||
|     data.median = median * NS_PER_TICK; | ||||
|     return data; | ||||
| } | ||||
|   | ||||
| @@ -17,42 +17,57 @@ public: | ||||
|         unsigned zeroes = 0; | ||||
|  | ||||
|         nanoseconds_t ns() const | ||||
|         { return ticks * NS_PER_TICK; } | ||||
|         { | ||||
|             return ticks * NS_PER_TICK; | ||||
|         } | ||||
|  | ||||
|         operator std::string () { | ||||
|         operator std::string() | ||||
|         { | ||||
|             return fmt::format("[b:{}, t:{}, z:{}]", bytes, ticks, zeroes); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| public: | ||||
| 	Fluxmap() {} | ||||
|     Fluxmap() {} | ||||
|  | ||||
| 	Fluxmap(const std::string& s) | ||||
| 	{ | ||||
| 		appendBytes((const uint8_t*) s.c_str(), s.size()); | ||||
| 	} | ||||
|     Fluxmap(const std::string& s) | ||||
|     { | ||||
|         appendBytes((const uint8_t*)s.c_str(), s.size()); | ||||
|     } | ||||
|  | ||||
| 	Fluxmap(const Bytes bytes) | ||||
| 	{ | ||||
| 		appendBytes(bytes); | ||||
| 	} | ||||
|     Fluxmap(const Bytes bytes) | ||||
|     { | ||||
|         appendBytes(bytes); | ||||
|     } | ||||
|  | ||||
|     nanoseconds_t duration() const { return _duration; } | ||||
|     unsigned ticks() const { return _ticks; } | ||||
|     size_t bytes() const { return _bytes.size(); } | ||||
|     const Bytes& rawBytes() const { return _bytes; } | ||||
|     nanoseconds_t duration() const | ||||
|     { | ||||
|         return _duration; | ||||
|     } | ||||
|     unsigned ticks() const | ||||
|     { | ||||
|         return _ticks; | ||||
|     } | ||||
|     size_t bytes() const | ||||
|     { | ||||
|         return _bytes.size(); | ||||
|     } | ||||
|     const Bytes& rawBytes() const | ||||
|     { | ||||
|         return _bytes; | ||||
|     } | ||||
|  | ||||
|     const uint8_t* ptr() const | ||||
| 	{ | ||||
| 		if (!_bytes.empty()) | ||||
| 			return &_bytes[0]; | ||||
| 		return NULL; | ||||
| 	} | ||||
|     { | ||||
|         if (!_bytes.empty()) | ||||
|             return &_bytes[0]; | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     Fluxmap& appendInterval(uint32_t ticks); | ||||
|     Fluxmap& appendPulse(); | ||||
|     Fluxmap& appendIndex(); | ||||
| 	Fluxmap& appendDesync(); | ||||
|     Fluxmap& appendDesync(); | ||||
|  | ||||
|     Fluxmap& appendBytes(const Bytes& bytes); | ||||
|     Fluxmap& appendBytes(const uint8_t* ptr, size_t len); | ||||
| @@ -62,13 +77,27 @@ public: | ||||
|         return appendBytes(&byte, 1); | ||||
|     } | ||||
|  | ||||
| 	Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock); | ||||
|     Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock); | ||||
|  | ||||
|     std::unique_ptr<const Fluxmap> precompensate(int threshold_ticks, int amount_ticks); | ||||
|     std::unique_ptr<const Fluxmap> precompensate( | ||||
|         int threshold_ticks, int amount_ticks); | ||||
|     std::vector<std::unique_ptr<const Fluxmap>> split() const; | ||||
|  | ||||
|     struct ClockData | ||||
|     { | ||||
|         nanoseconds_t median; | ||||
|         uint32_t noiseFloor; | ||||
|         uint32_t signalLevel; | ||||
|         nanoseconds_t peakStart; | ||||
|         nanoseconds_t peakEnd; | ||||
|         uint32_t buckets[256]; | ||||
|     }; | ||||
|  | ||||
|     ClockData guessClock( | ||||
|         double noiseFloorFactor = 0.01, double signalLevelFactor = 0.05) const; | ||||
|  | ||||
| private: | ||||
| 	uint8_t& findLastByte(); | ||||
|     uint8_t& findLastByte(); | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _duration = 0; | ||||
|   | ||||
| @@ -9,73 +9,69 @@ | ||||
| #include "proto.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/fl2.pb.h" | ||||
| #include "fl2.h" | ||||
| #include <fstream> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/types.h> | ||||
| #include <filesystem> | ||||
|  | ||||
| class Fl2FluxSink : public FluxSink | ||||
| { | ||||
| public: | ||||
| 	Fl2FluxSink(const Fl2FluxSinkProto& lconfig): | ||||
| 		Fl2FluxSink(lconfig.filename()) | ||||
| 	{ | ||||
| 	} | ||||
|     Fl2FluxSink(const Fl2FluxSinkProto& lconfig): | ||||
|         Fl2FluxSink(lconfig.filename()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| 	Fl2FluxSink(const std::string& filename): | ||||
| 		_filename(filename), | ||||
| 		_of(_filename, std::ios::out | std::ios::binary) | ||||
| 	{ | ||||
| 		if (!_of.is_open()) | ||||
| 			Error() << "cannot open output file"; | ||||
| 	} | ||||
|     Fl2FluxSink(const std::string& filename): _filename(filename) | ||||
|     { | ||||
|         std::ofstream of(filename); | ||||
|         if (!of.is_open()) | ||||
|             Error() << "cannot open output file"; | ||||
|         of.close(); | ||||
|         std::filesystem::remove(filename); | ||||
|     } | ||||
|  | ||||
| 	~Fl2FluxSink() | ||||
| 	{ | ||||
| 		FluxFileProto proto; | ||||
| 		proto.set_magic(FluxMagic::MAGIC); | ||||
| 		proto.set_version(FluxFileVersion::VERSION_2); | ||||
| 		for (const auto& e : _data) | ||||
| 		{ | ||||
| 			auto track = proto.add_track(); | ||||
| 			track->set_track(e.first.first); | ||||
| 			track->set_head(e.first.second); | ||||
| 			for (const auto& fluxBytes : e.second) | ||||
| 				track->add_flux(fluxBytes); | ||||
| 		} | ||||
|     ~Fl2FluxSink() | ||||
|     { | ||||
|         FluxFileProto proto; | ||||
|         for (const auto& e : _data) | ||||
|         { | ||||
|             auto track = proto.add_track(); | ||||
|             track->set_track(e.first.first); | ||||
|             track->set_head(e.first.second); | ||||
|             for (const auto& fluxBytes : e.second) | ||||
|                 track->add_flux(fluxBytes); | ||||
|         } | ||||
|  | ||||
| 		if (!proto.SerializeToOstream(&_of)) | ||||
| 			Error() << "unable to write output file"; | ||||
| 		_of.close(); | ||||
| 		if (_of.fail()) | ||||
| 			Error() << "FL2 write I/O error: " << strerror(errno); | ||||
| 	} | ||||
|         saveFl2File(_filename, proto); | ||||
|     } | ||||
|  | ||||
| public: | ||||
| 	void writeFlux(int track, int head, const Fluxmap& fluxmap) override | ||||
| 	{ | ||||
| 		auto& vector = _data[std::make_pair(track, head)]; | ||||
| 		vector.push_back(fluxmap.rawBytes()); | ||||
| 	} | ||||
|     void writeFlux(int track, int head, const Fluxmap& fluxmap) override | ||||
|     { | ||||
|         auto& vector = _data[std::make_pair(track, head)]; | ||||
|         vector.push_back(fluxmap.rawBytes()); | ||||
|     } | ||||
|  | ||||
| 	operator std::string () const override | ||||
| 	{ | ||||
| 		return fmt::format("fl2({})", _filename); | ||||
| 	} | ||||
|     operator std::string() const override | ||||
|     { | ||||
|         return fmt::format("fl2({})", _filename); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	std::string _filename; | ||||
| 	std::ofstream _of; | ||||
| 	std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data; | ||||
|     std::string _filename; | ||||
|     std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config) | ||||
| std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink( | ||||
|     const Fl2FluxSinkProto& config) | ||||
| { | ||||
|     return std::unique_ptr<FluxSink>(new Fl2FluxSink(config)); | ||||
| } | ||||
|  | ||||
| std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const std::string& filename) | ||||
| std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink( | ||||
|     const std::string& filename) | ||||
| { | ||||
|     return std::unique_ptr<FluxSink>(new Fl2FluxSink(filename)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -55,7 +55,7 @@ public: | ||||
|         _fileheader.start_track = strackno(minTrack, minSide); | ||||
|         _fileheader.end_track = strackno(maxTrack, maxSide); | ||||
|         _fileheader.flags = SCP_FLAG_INDEXED; | ||||
|         if (config.tpi() == 96) | ||||
|         if (config.tpi() != 48) | ||||
|             _fileheader.flags |= SCP_FLAG_96TPI; | ||||
|         _fileheader.cell_width = 0; | ||||
| 		if ((minSide == 0) && (maxSide == 0)) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "lib/fl2.pb.h" | ||||
| #include "fluxsource/fluxsource.h" | ||||
| #include "proto.h" | ||||
| #include "fl2.h" | ||||
| #include "fmt/format.h" | ||||
| #include "fluxmap.h" | ||||
| #include <fstream> | ||||
| @@ -11,38 +12,36 @@ | ||||
| class Fl2FluxSourceIterator : public FluxSourceIterator | ||||
| { | ||||
| public: | ||||
| 	Fl2FluxSourceIterator(const TrackFluxProto& proto): | ||||
| 		_proto(proto) | ||||
| 	{} | ||||
|     Fl2FluxSourceIterator(const TrackFluxProto& proto): _proto(proto) {} | ||||
|  | ||||
| 	bool hasNext() const override | ||||
| 	{ | ||||
| 		return _count < _proto.flux_size(); | ||||
| 	} | ||||
|     bool hasNext() const override | ||||
|     { | ||||
|         return _count < _proto.flux_size(); | ||||
|     } | ||||
|  | ||||
| 	std::unique_ptr<const Fluxmap> next() override | ||||
| 	{ | ||||
| 		auto bytes = _proto.flux(_count); | ||||
| 		_count++; | ||||
| 		return std::make_unique<Fluxmap>(bytes); | ||||
| 	} | ||||
|     std::unique_ptr<const Fluxmap> next() override | ||||
|     { | ||||
|         auto bytes = _proto.flux(_count); | ||||
|         _count++; | ||||
|         return std::make_unique<Fluxmap>(bytes); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	const TrackFluxProto& _proto; | ||||
| 	int _count = 0; | ||||
|     const TrackFluxProto& _proto; | ||||
|     int _count = 0; | ||||
| }; | ||||
|  | ||||
| class EmptyFluxSourceIterator : public FluxSourceIterator | ||||
| { | ||||
| 	bool hasNext() const override | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
|     bool hasNext() const override | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| 	std::unique_ptr<const Fluxmap> next() override | ||||
| 	{ | ||||
| 		Error() << "no flux to read"; | ||||
| 	} | ||||
|     std::unique_ptr<const Fluxmap> next() override | ||||
|     { | ||||
|         Error() << "no flux to read"; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class Fl2FluxSource : public FluxSource | ||||
| @@ -50,15 +49,7 @@ class Fl2FluxSource : public FluxSource | ||||
| public: | ||||
|     Fl2FluxSource(const Fl2FluxSourceProto& config): _config(config) | ||||
|     { | ||||
|         std::ifstream ifs(_config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!ifs.is_open()) | ||||
|             Error() << fmt::format("cannot open input file '{}': {}", | ||||
|                 _config.filename(), | ||||
|                 strerror(errno)); | ||||
|  | ||||
|         if (!_proto.ParseFromIstream(&ifs)) | ||||
|             Error() << "unable to read input file"; | ||||
| 		upgradeFluxFile(); | ||||
|         _proto = loadFl2File(_config.filename()); | ||||
|     } | ||||
|  | ||||
| public: | ||||
| @@ -67,7 +58,7 @@ public: | ||||
|         for (const auto& trackFlux : _proto.track()) | ||||
|         { | ||||
|             if ((trackFlux.track() == track) && (trackFlux.head() == head)) | ||||
| 				return std::make_unique<Fl2FluxSourceIterator>(trackFlux); | ||||
|                 return std::make_unique<Fl2FluxSourceIterator>(trackFlux); | ||||
|         } | ||||
|  | ||||
|         return std::make_unique<EmptyFluxSourceIterator>(); | ||||
| @@ -82,31 +73,6 @@ private: | ||||
|             Error() << fmt::format("FL2 read I/O error: {}", strerror(errno)); | ||||
|     } | ||||
|  | ||||
| 	void upgradeFluxFile() | ||||
| 	{ | ||||
| 		if (_proto.version() == FluxFileVersion::VERSION_1) | ||||
| 		{ | ||||
| 			/* Change a flux datastream with multiple segments separated by F_DESYNC into multiple | ||||
| 			 * flux segments. */ | ||||
|  | ||||
| 			for (auto& track : *_proto.mutable_track()) | ||||
| 			{ | ||||
| 				if (track.flux_size() != 0) | ||||
| 				{ | ||||
| 					Fluxmap oldFlux(track.flux(0)); | ||||
|  | ||||
| 					track.clear_flux(); | ||||
| 					for (const auto& flux : oldFlux.split()) | ||||
| 						track.add_flux(flux->rawBytes()); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			_proto.set_version(FluxFileVersion::VERSION_2); | ||||
| 		} | ||||
| 		if (_proto.version() > FluxFileVersion::VERSION_2) | ||||
| 			Error() << fmt::format("this is a version {} flux file, but this build of the client can only handle up to version {} --- please upgrade", _proto.version(), FluxFileVersion::VERSION_2); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
|     const Fl2FluxSourceProto& _config; | ||||
|     FluxFileProto _proto; | ||||
| @@ -115,12 +81,5 @@ private: | ||||
| std::unique_ptr<FluxSource> FluxSource::createFl2FluxSource( | ||||
|     const Fl2FluxSourceProto& config) | ||||
| { | ||||
|     char buffer[16]; | ||||
|     std::ifstream(config.filename(), std::ios::in | std::ios::binary) | ||||
|         .read(buffer, 16); | ||||
|     if (strncmp(buffer, "SQLite format 3", 16) == 0) | ||||
|         Error() << "this flux file is too old; please use the " | ||||
|                    "upgrade-flux-file tool to upgrade it"; | ||||
|  | ||||
|     return std::unique_ptr<FluxSource>(new Fl2FluxSource(config)); | ||||
| } | ||||
|   | ||||
| @@ -19,10 +19,10 @@ class FlxFluxSourceProto; | ||||
| class FluxSourceIterator | ||||
| { | ||||
| public: | ||||
| 	virtual ~FluxSourceIterator() {} | ||||
|     virtual ~FluxSourceIterator() {} | ||||
|  | ||||
| 	virtual bool hasNext() const = 0; | ||||
| 	virtual std::unique_ptr<const Fluxmap> next() = 0; | ||||
|     virtual bool hasNext() const = 0; | ||||
|     virtual std::unique_ptr<const Fluxmap> next() = 0; | ||||
| }; | ||||
|  | ||||
| class FluxSource | ||||
| @@ -31,33 +31,48 @@ public: | ||||
|     virtual ~FluxSource() {} | ||||
|  | ||||
| private: | ||||
|     static std::unique_ptr<FluxSource> createCwfFluxSource(const CwfFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createEraseFluxSource(const EraseFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createFl2FluxSource(const Fl2FluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createFlxFluxSource(const FlxFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createHardwareFluxSource(const HardwareFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createKryofluxFluxSource(const KryofluxFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createScpFluxSource(const ScpFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createTestPatternFluxSource(const TestPatternFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createCwfFluxSource( | ||||
|         const CwfFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createEraseFluxSource( | ||||
|         const EraseFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createFl2FluxSource( | ||||
|         const Fl2FluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createFlxFluxSource( | ||||
|         const FlxFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createHardwareFluxSource( | ||||
|         const HardwareFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createKryofluxFluxSource( | ||||
|         const KryofluxFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createScpFluxSource( | ||||
|         const ScpFluxSourceProto& config); | ||||
|     static std::unique_ptr<FluxSource> createTestPatternFluxSource( | ||||
|         const TestPatternFluxSourceProto& config); | ||||
|  | ||||
| public: | ||||
|     static std::unique_ptr<FluxSource> createMemoryFluxSource(const DiskFlux& flux); | ||||
|     static std::unique_ptr<FluxSource> createMemoryFluxSource( | ||||
|         const DiskFlux& flux); | ||||
|  | ||||
|     static std::unique_ptr<FluxSource> create(const FluxSourceProto& spec); | ||||
| 	static void updateConfigForFilename(FluxSourceProto* proto, const std::string& filename); | ||||
|     static void updateConfigForFilename( | ||||
|         FluxSourceProto* proto, const std::string& filename); | ||||
|  | ||||
| public: | ||||
|     virtual std::unique_ptr<FluxSourceIterator> readFlux(int track, int side) = 0; | ||||
|     virtual std::unique_ptr<FluxSourceIterator> readFlux( | ||||
|         int track, int side) = 0; | ||||
|     virtual void recalibrate() {} | ||||
| 	virtual bool isHardware() { return false; } | ||||
|     virtual void seek(int track) {} | ||||
|     virtual bool isHardware() | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class TrivialFluxSource : public FluxSource | ||||
| { | ||||
| public: | ||||
|     std::unique_ptr<FluxSourceIterator> readFlux(int track, int side); | ||||
| 	virtual std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) = 0; | ||||
|     virtual std::unique_ptr<const Fluxmap> readSingleFlux( | ||||
|         int track, int side) = 0; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,9 @@ private: | ||||
|  | ||||
|         std::unique_ptr<const Fluxmap> next() | ||||
|         { | ||||
|             usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode()); | ||||
|             usbSetDrive(config.drive().drive(), | ||||
|                 config.drive().high_density(), | ||||
|                 config.drive().index_mode()); | ||||
|             usbSeek(_track); | ||||
|  | ||||
|             Bytes data = usbRead(_head, | ||||
| @@ -51,7 +53,7 @@ private: | ||||
| public: | ||||
|     HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf) | ||||
|     { | ||||
|     	measureDiskRotation(_oneRevolution, _hardSectorThreshold); | ||||
|         measureDiskRotation(_oneRevolution, _hardSectorThreshold); | ||||
|     } | ||||
|  | ||||
|     ~HardwareFluxSource() {} | ||||
| @@ -59,8 +61,7 @@ public: | ||||
| public: | ||||
|     std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override | ||||
|     { | ||||
|         return std::make_unique<HardwareFluxSourceIterator>( | ||||
|             *this, track, head); | ||||
|         return std::make_unique<HardwareFluxSourceIterator>(*this, track, head); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() override | ||||
| @@ -68,6 +69,11 @@ public: | ||||
|         usbRecalibrate(); | ||||
|     } | ||||
|  | ||||
|     void seek(int track) override | ||||
|     { | ||||
|         usbSeek(track); | ||||
|     } | ||||
|  | ||||
|     bool isHardware() override | ||||
|     { | ||||
|         return true; | ||||
|   | ||||
| @@ -21,122 +21,130 @@ static int headno(int strack) | ||||
|  | ||||
| static int strackno(int track, int side) | ||||
| { | ||||
| 	return (track << 1) | side; | ||||
|     return (track << 1) | side; | ||||
| } | ||||
|  | ||||
| class ScpFluxSource : public TrivialFluxSource | ||||
| { | ||||
| public: | ||||
|     ScpFluxSource(const ScpFluxSourceProto& config): | ||||
|         _config(config) | ||||
|     ScpFluxSource(const ScpFluxSourceProto& config): _config(config) | ||||
|     { | ||||
| 		_if.open(_config.filename(), std::ios::in | std::ios::binary); | ||||
| 		if (!_if.is_open()) | ||||
| 			Error() << fmt::format("cannot open input file '{}': {}", _config.filename(), strerror(errno)); | ||||
|         _if.open(_config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!_if.is_open()) | ||||
|             Error() << fmt::format("cannot open input file '{}': {}", | ||||
|                 _config.filename(), | ||||
|                 strerror(errno)); | ||||
|  | ||||
| 		_if.read((char*) &_header, sizeof(_header)); | ||||
| 		check_for_error(); | ||||
|         _if.read((char*)&_header, sizeof(_header)); | ||||
|         check_for_error(); | ||||
|  | ||||
| 		if ((_header.file_id[0] != 'S') | ||||
| 				|| (_header.file_id[1] != 'C') | ||||
| 				|| (_header.file_id[2] != 'P')) | ||||
| 			Error() << "input not a SCP file"; | ||||
|         if ((_header.file_id[0] != 'S') || (_header.file_id[1] != 'C') || | ||||
|             (_header.file_id[2] != 'P')) | ||||
|             Error() << "input not a SCP file"; | ||||
|  | ||||
| 		_resolution = 25 * (_header.resolution + 1); | ||||
| 		int startSide = (_header.heads == 2) ? 1 : 0; | ||||
| 		int endSide = (_header.heads == 1) ? 0 : 1; | ||||
|         ::config.set_tpi((_header.flags & SCP_FLAG_96TPI) ? 96 : 48); | ||||
|  | ||||
| 		if ((_header.cell_width != 0) && (_header.cell_width != 16)) | ||||
| 			Error() << "currently only 16-bit cells in SCP files are supported"; | ||||
|         _resolution = 25 * (_header.resolution + 1); | ||||
|         int startSide = (_header.heads == 2) ? 1 : 0; | ||||
|         int endSide = (_header.heads == 1) ? 0 : 1; | ||||
|  | ||||
| 		std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n", | ||||
| 			trackno(_header.start_track), trackno(_header.end_track), startSide, endSide); | ||||
| 		std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution); | ||||
| 	} | ||||
|         if ((_header.cell_width != 0) && (_header.cell_width != 16)) | ||||
|             Error() << "currently only 16-bit cells in SCP files are supported"; | ||||
|  | ||||
|         std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n", | ||||
|             trackno(_header.start_track), | ||||
|             trackno(_header.end_track), | ||||
|             startSide, | ||||
|             endSide); | ||||
|         std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override | ||||
|     { | ||||
| 		int strack = strackno(track, side); | ||||
| 		if (strack >= ARRAY_SIZE(_header.track)) | ||||
| 			return std::make_unique<Fluxmap>(); | ||||
| 		uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32(); | ||||
| 		if (offset == 0) | ||||
| 			return std::make_unique<Fluxmap>(); | ||||
|         int strack = strackno(track, side); | ||||
|         if (strack >= ARRAY_SIZE(_header.track)) | ||||
|             return std::make_unique<Fluxmap>(); | ||||
|         uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32(); | ||||
|         if (offset == 0) | ||||
|             return std::make_unique<Fluxmap>(); | ||||
|  | ||||
| 		ScpTrackHeader trackheader; | ||||
| 		_if.seekg(offset, std::ios::beg); | ||||
| 		_if.read((char*) &trackheader, sizeof(trackheader)); | ||||
| 		check_for_error(); | ||||
|         ScpTrackHeader trackheader; | ||||
|         _if.seekg(offset, std::ios::beg); | ||||
|         _if.read((char*)&trackheader, sizeof(trackheader)); | ||||
|         check_for_error(); | ||||
|  | ||||
| 		if ((trackheader.track_id[0] != 'T') | ||||
| 				|| (trackheader.track_id[1] != 'R') | ||||
| 				|| (trackheader.track_id[2] != 'K')) | ||||
| 			Error() << "corrupt SCP file"; | ||||
|         if ((trackheader.track_id[0] != 'T') || | ||||
|             (trackheader.track_id[1] != 'R') || | ||||
|             (trackheader.track_id[2] != 'K')) | ||||
|             Error() << "corrupt SCP file"; | ||||
|  | ||||
| 		std::vector<ScpTrackRevolution> revs(_header.revolutions); | ||||
| 		for (int revolution = 0; revolution < _header.revolutions; revolution++) | ||||
| 		{ | ||||
| 			ScpTrackRevolution trackrev; | ||||
| 			_if.read((char*) &trackrev, sizeof(trackrev)); | ||||
| 			check_for_error(); | ||||
| 			revs[revolution] = trackrev; | ||||
| 		} | ||||
|         std::vector<ScpTrackRevolution> revs(_header.revolutions); | ||||
|         for (int revolution = 0; revolution < _header.revolutions; revolution++) | ||||
|         { | ||||
|             ScpTrackRevolution trackrev; | ||||
|             _if.read((char*)&trackrev, sizeof(trackrev)); | ||||
|             check_for_error(); | ||||
|             revs[revolution] = trackrev; | ||||
|         } | ||||
|  | ||||
| 		auto fluxmap = std::make_unique<Fluxmap>(); | ||||
| 		nanoseconds_t pending = 0; | ||||
| 		unsigned inputBytes = 0; | ||||
| 		for (int revolution = 0; revolution < _header.revolutions; revolution++) | ||||
| 		{ | ||||
| 			if (revolution != 0) | ||||
| 				fluxmap->appendIndex(); | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         nanoseconds_t pending = 0; | ||||
|         unsigned inputBytes = 0; | ||||
|         for (int revolution = 0; revolution < _header.revolutions; revolution++) | ||||
|         { | ||||
|             if (revolution != 0) | ||||
|                 fluxmap->appendIndex(); | ||||
|  | ||||
| 			uint32_t datalength = Bytes(revs[revolution].length, 4).reader().read_le32(); | ||||
| 			uint32_t dataoffset = Bytes(revs[revolution].offset, 4).reader().read_le32(); | ||||
|             uint32_t datalength = | ||||
|                 Bytes(revs[revolution].length, 4).reader().read_le32(); | ||||
|             uint32_t dataoffset = | ||||
|                 Bytes(revs[revolution].offset, 4).reader().read_le32(); | ||||
|  | ||||
| 			Bytes data(datalength*2); | ||||
| 			_if.seekg(dataoffset + offset, std::ios::beg); | ||||
| 			_if.read((char*) data.begin(), data.size()); | ||||
| 			check_for_error(); | ||||
|             Bytes data(datalength * 2); | ||||
|             _if.seekg(dataoffset + offset, std::ios::beg); | ||||
|             _if.read((char*)data.begin(), data.size()); | ||||
|             check_for_error(); | ||||
|  | ||||
| 			ByteReader br(data); | ||||
| 			for (int cell = 0; cell < datalength; cell++) | ||||
| 			{ | ||||
| 				uint16_t interval = br.read_be16(); | ||||
| 				if (interval) | ||||
| 				{ | ||||
| 					fluxmap->appendInterval((interval + pending) * _resolution / NS_PER_TICK); | ||||
| 					fluxmap->appendPulse(); | ||||
| 					pending = 0; | ||||
| 				} | ||||
| 				else | ||||
| 					pending += 0x10000; | ||||
| 			} | ||||
|             ByteReader br(data); | ||||
|             for (int cell = 0; cell < datalength; cell++) | ||||
|             { | ||||
|                 uint16_t interval = br.read_be16(); | ||||
|                 if (interval) | ||||
|                 { | ||||
|                     fluxmap->appendInterval( | ||||
|                         (interval + pending) * _resolution / NS_PER_TICK); | ||||
|                     fluxmap->appendPulse(); | ||||
|                     pending = 0; | ||||
|                 } | ||||
|                 else | ||||
|                     pending += 0x10000; | ||||
|             } | ||||
|  | ||||
| 			inputBytes += datalength*2; | ||||
| 		} | ||||
|             inputBytes += datalength * 2; | ||||
|         } | ||||
|  | ||||
| 		return fluxmap; | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|  | ||||
| private: | ||||
| 	void check_for_error() | ||||
| 	{ | ||||
| 		if (_if.fail()) | ||||
| 			Error() << fmt::format("SCP read I/O error: {}", strerror(errno)); | ||||
| 	} | ||||
|     void check_for_error() | ||||
|     { | ||||
|         if (_if.fail()) | ||||
|             Error() << fmt::format("SCP read I/O error: {}", strerror(errno)); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const ScpFluxSourceProto& _config; | ||||
| 	std::ifstream _if; | ||||
| 	ScpHeader _header; | ||||
| 	nanoseconds_t _resolution; | ||||
|     std::ifstream _if; | ||||
|     ScpHeader _header; | ||||
|     nanoseconds_t _resolution; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(const ScpFluxSourceProto& config) | ||||
| std::unique_ptr<FluxSource> FluxSource::createScpFluxSource( | ||||
|     const ScpFluxSourceProto& config) | ||||
| { | ||||
|     return std::unique_ptr<FluxSource>(new ScpFluxSource(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,10 @@ void hexdump(std::ostream& stream, const Bytes& buffer) | ||||
| 				break; | ||||
|  | ||||
| 			uint8_t c = buffer[pos+i]; | ||||
| 			stream << (isprint(c) ? (char)c : '.'); | ||||
| 			if ((c >= 32) && (c <= 126)) | ||||
| 				stream << (char)c; | ||||
| 			else | ||||
| 				stream << '.'; | ||||
| 		} | ||||
| 		stream << std::endl; | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ void Image::createBlankImage() | ||||
|         unsigned side = trackAndHead.second; | ||||
|         auto trackLayout = Layout::getLayoutOfTrack(track, side); | ||||
|         Bytes blank(trackLayout->sectorSize); | ||||
|         for (unsigned sectorId : trackLayout->logicalSectorOrder) | ||||
|         for (unsigned sectorId : trackLayout->naturalSectorOrder) | ||||
|             put(track, side, sectorId)->data = blank; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| #ifndef IMAGE_H | ||||
| #define IMAGE_H | ||||
|  | ||||
| class Sector; | ||||
|  | ||||
| struct Geometry | ||||
| { | ||||
|     unsigned numTracks = 0; | ||||
|   | ||||
| @@ -112,7 +112,7 @@ std::unique_ptr<Image> ImageReader::readMappedImage() | ||||
|         auto newSector = std::make_shared<Sector>(); | ||||
|         *newSector = *e; | ||||
|         newSector->logicalSector = | ||||
|             trackLayout->filesystemToLogicalSectorMap.at(e->logicalSector); | ||||
|             trackLayout->filesystemToNaturalSectorMap.at(e->logicalSector); | ||||
|         sectors.insert(newSector); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -40,7 +40,7 @@ public: | ||||
|                 break; | ||||
|  | ||||
|             auto trackLayout = Layout::getLayoutOfTrack(track, side); | ||||
|             for (int sectorId : trackLayout->logicalSectorOrder) | ||||
|             for (int sectorId : trackLayout->naturalSectorOrder) | ||||
|             { | ||||
|                 Bytes data(trackLayout->sectorSize); | ||||
|                 inputFile.read((char*)data.begin(), data.size()); | ||||
|   | ||||
| @@ -218,7 +218,7 @@ void ImageWriter::writeMappedImage(const Image& image) | ||||
|             auto newSector = std::make_shared<Sector>(); | ||||
|             *newSector = *e; | ||||
|             newSector->logicalSector = | ||||
|                 trackLayout->logicalToFilesystemSectorMap.at(e->logicalSector); | ||||
|                 trackLayout->naturalToFilesystemSectorMap.at(e->logicalSector); | ||||
|             sectors.insert(newSector); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,7 @@ public: | ||||
|             int side = p.second; | ||||
|  | ||||
|             auto trackLayout = Layout::getLayoutOfTrack(track, side); | ||||
|             for (int sectorId : trackLayout->logicalSectorOrder) | ||||
|             for (int sectorId : trackLayout->naturalSectorOrder) | ||||
|             { | ||||
|                 const auto& sector = image.get(track, side, sectorId); | ||||
|                 if (sector) | ||||
|   | ||||
| @@ -58,19 +58,23 @@ std::vector<std::shared_ptr<const TrackInfo>> Layout::computeLocations() | ||||
|     return locations; | ||||
| } | ||||
|  | ||||
| void Layout::getBounds(const std::vector<std::shared_ptr<const TrackInfo>>& locations, | ||||
| 		int& minTrack, int& maxTrack, int& minSide, int& maxSide) | ||||
| void Layout::getBounds( | ||||
|     const std::vector<std::shared_ptr<const TrackInfo>>& locations, | ||||
|     int& minTrack, | ||||
|     int& maxTrack, | ||||
|     int& minSide, | ||||
|     int& maxSide) | ||||
| { | ||||
| 	minTrack = minSide = INT_MAX; | ||||
| 	maxTrack = maxSide = INT_MIN; | ||||
|     minTrack = minSide = INT_MAX; | ||||
|     maxTrack = maxSide = INT_MIN; | ||||
|  | ||||
| 	for (auto& ti : locations) | ||||
| 	{ | ||||
| 		minTrack = std::min<int>(minTrack, ti->physicalTrack); | ||||
| 		maxTrack = std::max<int>(maxTrack, ti->physicalTrack); | ||||
| 		minSide = std::min<int>(minSide, ti->physicalSide); | ||||
| 		maxSide = std::max<int>(maxSide, ti->physicalSide); | ||||
| 	} | ||||
|     for (auto& ti : locations) | ||||
|     { | ||||
|         minTrack = std::min<int>(minTrack, ti->physicalTrack); | ||||
|         maxTrack = std::max<int>(maxTrack, ti->physicalTrack); | ||||
|         minSide = std::min<int>(minSide, ti->physicalSide); | ||||
|         maxSide = std::max<int>(maxSide, ti->physicalSide); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::vector<std::pair<int, int>> Layout::getTrackOrdering( | ||||
| @@ -121,10 +125,24 @@ std::vector<unsigned> Layout::expandSectorList( | ||||
|             Error() << "LAYOUT: if you use a sector count, you can't use an " | ||||
|                        "explicit sector list"; | ||||
|  | ||||
|         std::set<unsigned> sectorset; | ||||
|         int id = sectorsProto.start_sector(); | ||||
|         for (int i = 0; i < sectorsProto.count(); i++) | ||||
|             sectors.push_back( | ||||
|                 sectorsProto.start_sector() + | ||||
|                 ((i * sectorsProto.skew()) % sectorsProto.count())); | ||||
|         { | ||||
|             while (sectorset.find(id) != sectorset.end()) | ||||
|             { | ||||
|                 id++; | ||||
|                 if (id >= (sectorsProto.start_sector() + sectorsProto.count())) | ||||
|                     id -= sectorsProto.count(); | ||||
|             } | ||||
|  | ||||
|             sectorset.insert(id); | ||||
|             sectors.push_back(id); | ||||
|  | ||||
|             id += sectorsProto.skew(); | ||||
|             if (id >= (sectorsProto.start_sector() + sectorsProto.count())) | ||||
|                 id -= sectorsProto.count(); | ||||
|         } | ||||
|     } | ||||
|     else if (sectorsProto.sector_size() > 0) | ||||
|     { | ||||
| @@ -146,8 +164,7 @@ std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack( | ||||
|     for (const auto& f : config.layout().layoutdata()) | ||||
|     { | ||||
|         if (f.has_track() && f.has_up_to_track() && | ||||
|             ((logicalTrack < f.track()) || | ||||
|              (logicalTrack > f.up_to_track()))) | ||||
|             ((logicalTrack < f.track()) || (logicalTrack > f.up_to_track()))) | ||||
|             continue; | ||||
|         if (f.has_track() && !f.has_up_to_track() && | ||||
|             (logicalTrack != f.track())) | ||||
| @@ -167,32 +184,28 @@ std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack( | ||||
|     trackInfo->physicalSide = logicalSide ^ config.layout().swap_sides(); | ||||
|     trackInfo->groupSize = getTrackStep(); | ||||
|     trackInfo->diskSectorOrder = expandSectorList(layoutdata.physical()); | ||||
|     trackInfo->logicalSectorOrder = trackInfo->diskSectorOrder; | ||||
|     std::sort( | ||||
|         trackInfo->diskSectorOrder.begin(), trackInfo->diskSectorOrder.end()); | ||||
|     trackInfo->numSectors = trackInfo->logicalSectorOrder.size(); | ||||
|     trackInfo->naturalSectorOrder = trackInfo->diskSectorOrder; | ||||
|     std::sort(trackInfo->naturalSectorOrder.begin(), | ||||
|         trackInfo->naturalSectorOrder.end()); | ||||
|     trackInfo->numSectors = trackInfo->naturalSectorOrder.size(); | ||||
|  | ||||
|     if (layoutdata.has_filesystem()) | ||||
|     { | ||||
|         trackInfo->filesystemSectorOrder = | ||||
|             expandSectorList(layoutdata.filesystem()); | ||||
|         if (trackInfo->filesystemSectorOrder.size() != trackInfo->numSectors) | ||||
|             Error() | ||||
|                 << "filesystem sector order list doesn't contain the right " | ||||
|                 "number of sectors"; | ||||
|             Error() << "filesystem sector order list doesn't contain the right " | ||||
|                        "number of sectors"; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         for (unsigned sectorId : trackInfo->logicalSectorOrder) | ||||
|             trackInfo->filesystemSectorOrder.push_back(sectorId); | ||||
|     } | ||||
|         trackInfo->filesystemSectorOrder = trackInfo->naturalSectorOrder; | ||||
|  | ||||
|     for (int i = 0; i < trackInfo->numSectors; i++) | ||||
|     { | ||||
|         unsigned f = trackInfo->logicalSectorOrder[i]; | ||||
|         unsigned l = trackInfo->filesystemSectorOrder[i]; | ||||
|         trackInfo->filesystemToLogicalSectorMap[f] = l; | ||||
|         trackInfo->logicalToFilesystemSectorMap[l] = f; | ||||
|         unsigned fid = trackInfo->naturalSectorOrder[i]; | ||||
|         unsigned lid = trackInfo->filesystemSectorOrder[i]; | ||||
|         trackInfo->filesystemToNaturalSectorMap[fid] = lid; | ||||
|         trackInfo->naturalToFilesystemSectorMap[lid] = fid; | ||||
|     } | ||||
|  | ||||
|     return trackInfo; | ||||
|   | ||||
							
								
								
									
										38
									
								
								lib/layout.h
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								lib/layout.h
									
									
									
									
									
								
							| @@ -27,10 +27,14 @@ public: | ||||
|      */ | ||||
|     static std::vector<std::shared_ptr<const TrackInfo>> computeLocations(); | ||||
|  | ||||
| 	/* Given a list of locations, determines the minimum and maximum track | ||||
| 	 * and side settings. */ | ||||
| 	static void getBounds(const std::vector<std::shared_ptr<const TrackInfo>>& locations, | ||||
| 		int& minTrack, int& maxTrack, int& minSide, int& maxSide); | ||||
|     /* Given a list of locations, determines the minimum and maximum track | ||||
|      * and side settings. */ | ||||
|     static void getBounds( | ||||
|         const std::vector<std::shared_ptr<const TrackInfo>>& locations, | ||||
|         int& minTrack, | ||||
|         int& maxTrack, | ||||
|         int& minSide, | ||||
|         int& maxSide); | ||||
|  | ||||
|     /* Returns a series of <track, side> pairs representing the filesystem | ||||
|      * ordering of the disk, in logical numbers. */ | ||||
| @@ -50,9 +54,10 @@ public: | ||||
|         const SectorListProto& sectorsProto); | ||||
| }; | ||||
|  | ||||
| class TrackInfo { | ||||
| class TrackInfo | ||||
| { | ||||
| public: | ||||
| 	TrackInfo() {} | ||||
|     TrackInfo() {} | ||||
|  | ||||
| private: | ||||
|     /* Can't copy. */ | ||||
| @@ -85,20 +90,23 @@ public: | ||||
|     /* Number of bytes in a sector. */ | ||||
|     unsigned sectorSize = 0; | ||||
|  | ||||
|     /* Sector IDs in disk order. */ | ||||
|     /* Sector IDs in sector ID order. This is the order in which the appear in | ||||
|      * disk images. */ | ||||
|     std::vector<unsigned> naturalSectorOrder; | ||||
|  | ||||
|     /* Sector IDs in disk order. This is the order they are written to the disk. | ||||
|      */ | ||||
|     std::vector<unsigned> diskSectorOrder; | ||||
|  | ||||
|     /* Sector IDs in logical order. */ | ||||
|     std::vector<unsigned> logicalSectorOrder; | ||||
|  | ||||
|     /* Sector IDs in filesystem order. */ | ||||
|     /* Sector IDs in filesystem order. This is the order in which the filesystem | ||||
|      * uses them. */ | ||||
|     std::vector<unsigned> filesystemSectorOrder; | ||||
|  | ||||
|     /* Mapping of filesystem order to logical order. */ | ||||
|     std::map<unsigned, unsigned> filesystemToLogicalSectorMap; | ||||
|     /* Mapping of filesystem order to natural order. */ | ||||
|     std::map<unsigned, unsigned> filesystemToNaturalSectorMap; | ||||
|  | ||||
|     /* Mapping of logical order to filesystem order. */ | ||||
|     std::map<unsigned, unsigned> logicalToFilesystemSectorMap; | ||||
|     /* Mapping of natural order to filesystem order. */ | ||||
|     std::map<unsigned, unsigned> naturalToFilesystemSectorMap; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -168,7 +168,7 @@ BadSectorsState combineRecordAndSectors(TrackFlux& trackFlux, | ||||
|  | ||||
|     /* Add the sectors which should be there. */ | ||||
|  | ||||
|     for (unsigned sectorId : trackLayout->logicalSectorOrder) | ||||
|     for (unsigned sectorId : trackLayout->naturalSectorOrder) | ||||
|     { | ||||
|         auto sector = std::make_shared<Sector>(LogicalLocation{ | ||||
|             trackLayout->logicalTrack, trackLayout->logicalSide, sectorId}); | ||||
| @@ -189,6 +189,26 @@ BadSectorsState combineRecordAndSectors(TrackFlux& trackFlux, | ||||
|     return HAS_NO_BAD_SECTORS; | ||||
| } | ||||
|  | ||||
| static void adjustTrackOnError(FluxSource& fluxSource, int baseTrack) | ||||
| { | ||||
|     switch (config.drive().error_behaviour()) | ||||
|     { | ||||
|         case DriveProto::NOTHING: | ||||
|             break; | ||||
|  | ||||
|         case DriveProto::RECALIBRATE: | ||||
|             fluxSource.recalibrate(); | ||||
|             break; | ||||
|  | ||||
|         case DriveProto::JIGGLE: | ||||
|             if (baseTrack > 0) | ||||
|                 fluxSource.seek(baseTrack - 1); | ||||
|             else | ||||
|                 fluxSource.seek(baseTrack + 1); | ||||
|             break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder, | ||||
|     std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|     TrackFlux& trackFlux, | ||||
| @@ -343,6 +363,7 @@ void writeTracksAndVerify(FluxSink& fluxSink, | ||||
|  | ||||
|             if (result != GOOD_READ) | ||||
|             { | ||||
|                 adjustTrackOnError(fluxSource, trackInfo->physicalTrack); | ||||
|                 Logger() << "bad read"; | ||||
|                 return false; | ||||
|             } | ||||
| @@ -453,9 +474,13 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource, | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         Logger() << fmt::format( | ||||
|             "retrying; {} retries remaining", retriesRemaining); | ||||
|         retriesRemaining--; | ||||
|         if (fluxSource.isHardware()) | ||||
|         { | ||||
|             adjustTrackOnError(fluxSource, trackInfo->physicalTrack); | ||||
|             Logger() << fmt::format( | ||||
|                 "retrying; {} retries remaining", retriesRemaining); | ||||
|             retriesRemaining--; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return trackFlux; | ||||
| @@ -584,7 +609,8 @@ void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink) | ||||
|     unsigned index = 0; | ||||
|     for (auto& trackInfo : locations) | ||||
|     { | ||||
|         Logger() << OperationProgressLogMessage{index * 100 / (int)locations.size()}; | ||||
|         Logger() << OperationProgressLogMessage{ | ||||
|             index * 100 / (int)locations.size()}; | ||||
|         index++; | ||||
|  | ||||
|         testForEmergencyStop(); | ||||
|   | ||||
| @@ -16,7 +16,7 @@ static USB* usb = NULL; | ||||
|  | ||||
| USB::~USB() {} | ||||
|  | ||||
| static std::unique_ptr<CandidateDevice> selectDevice() | ||||
| static std::shared_ptr<CandidateDevice> selectDevice() | ||||
| { | ||||
|     auto candidates = findUsbDevices(); | ||||
|     if (candidates.size() == 0) | ||||
| @@ -29,14 +29,14 @@ static std::unique_ptr<CandidateDevice> selectDevice() | ||||
|         for (auto& c : candidates) | ||||
|         { | ||||
|             if (c->serial == wantedSerial) | ||||
|                 return std::move(c); | ||||
|                 return c; | ||||
|         } | ||||
|         Error() << "serial number not found (try without one to list or " | ||||
|                    "autodetect devices)"; | ||||
|     } | ||||
|  | ||||
|     if (candidates.size() == 1) | ||||
|         return std::move(candidates[0]); | ||||
|         return candidates[0]; | ||||
|  | ||||
|     std::cerr << "More than one device detected; use --usb.serial=<serial> to " | ||||
|                  "select one:\n"; | ||||
|   | ||||
| @@ -24,9 +24,9 @@ static const std::string get_serial_number(const libusbp::device& device) | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices() | ||||
| std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices() | ||||
| { | ||||
|     std::vector<std::unique_ptr<CandidateDevice>> candidates; | ||||
|     std::vector<std::shared_ptr<CandidateDevice>> candidates; | ||||
|     for (const auto& it : libusbp::list_connected_devices()) | ||||
|     { | ||||
|         auto candidate = std::make_unique<CandidateDevice>(); | ||||
| @@ -42,7 +42,10 @@ std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices() | ||||
|             { | ||||
|                 libusbp::serial_port port(candidate->device); | ||||
|                 candidate->serialPort = port.get_name(); | ||||
| 				candidate->type = DEVICE_GREASEWEAZLE; | ||||
|             } | ||||
| 			else if (id == FLUXENGINE_ID) | ||||
| 				candidate->type = DEVICE_FLUXENGINE; | ||||
|  | ||||
|             candidates.push_back(std::move(candidate)); | ||||
|         } | ||||
| @@ -50,3 +53,19 @@ std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices() | ||||
|  | ||||
|     return candidates; | ||||
| } | ||||
|  | ||||
| std::string getDeviceName(DeviceType type) | ||||
| { | ||||
| 	switch (type) | ||||
| 	{ | ||||
| 		case DEVICE_GREASEWEAZLE: | ||||
| 			return "Greaseweazle"; | ||||
|  | ||||
| 		case DEVICE_FLUXENGINE: | ||||
| 			return "FluxEngine"; | ||||
|  | ||||
| 		default: | ||||
| 			return "unknown"; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,15 +4,24 @@ | ||||
| #include "libusbp_config.h" | ||||
| #include "libusbp.hpp" | ||||
|  | ||||
| enum DeviceType | ||||
| { | ||||
| 	DEVICE_FLUXENGINE, | ||||
| 	DEVICE_GREASEWEAZLE | ||||
| }; | ||||
|  | ||||
| extern std::string getDeviceName(DeviceType type); | ||||
|  | ||||
| struct CandidateDevice | ||||
| { | ||||
| 	DeviceType type; | ||||
| 	libusbp::device device; | ||||
| 	uint32_t id; | ||||
| 	std::string serial; | ||||
| 	std::string serialPort; | ||||
| }; | ||||
|  | ||||
| extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(); | ||||
| extern std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices(); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
							
								
								
									
										182
									
								
								lib/vfs/appledos.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								lib/vfs/appledos.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/utils.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| /* This is described here: | ||||
|  * http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system | ||||
|  * (also in Inside AppleDOS) | ||||
|  */ | ||||
|  | ||||
| class AppledosFilesystem : public Filesystem | ||||
| { | ||||
|     static constexpr int VTOC_BLOCK = 17 * 16; | ||||
|  | ||||
|     class AppledosDirent : public Dirent | ||||
|     { | ||||
|     public: | ||||
|         AppledosDirent(const Bytes& de) | ||||
|         { | ||||
|             ByteReader br(de); | ||||
|             track = br.read_8(); | ||||
|             sector = br.read_8(); | ||||
|             flags = br.read_8(); | ||||
|             filename = br.read(30); | ||||
|             length = br.read_le16() * 256; | ||||
|  | ||||
|             for (char& c : filename) | ||||
|                 c &= 0x7f; | ||||
|             filename = rightTrimWhitespace(filename); | ||||
|             path = {filename}; | ||||
|  | ||||
|             file_type = TYPE_FILE; | ||||
|  | ||||
|             attributes[FILENAME] = filename; | ||||
|             attributes[LENGTH] = std::to_string(length); | ||||
|             attributes[FILE_TYPE] = "file"; | ||||
|             attributes["appledos.flags"] = fmt::format("0x{:x}", flags); | ||||
|         } | ||||
|  | ||||
|         uint8_t track; | ||||
|         uint8_t sector; | ||||
|         uint8_t flags; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     AppledosFilesystem( | ||||
|         const AppledosProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|         Filesystem(sectors), | ||||
|         _config(config) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     { | ||||
|         return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE; | ||||
|     } | ||||
|  | ||||
|     std::map<std::string, std::string> getMetadata() override | ||||
|     { | ||||
|         mount(); | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|         attributes[VOLUME_NAME] = ""; | ||||
|         attributes[TOTAL_BLOCKS] = std::to_string(_vtoc[0x34] * _vtoc[0x35]); | ||||
|         attributes[USED_BLOCKS] = "0"; | ||||
|         attributes[BLOCK_SIZE] = "256"; | ||||
|         return attributes; | ||||
|     } | ||||
|  | ||||
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 0) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         std::vector<std::shared_ptr<Dirent>> results; | ||||
|         for (auto& de : _dirents) | ||||
|             results.push_back(de); | ||||
|  | ||||
|         return results; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Dirent> getDirent(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         return find(path.front()); | ||||
|     } | ||||
|  | ||||
|     Bytes getFile(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         auto dirent = find(path.front()); | ||||
|         int tstrack = dirent->track; | ||||
|         int tssector = dirent->sector; | ||||
|  | ||||
|         Bytes bytes; | ||||
|         ByteWriter bw(bytes); | ||||
|         while (tstrack) | ||||
|         { | ||||
|             Bytes ts = getAppleSector(tstrack * 16 + tssector); | ||||
|             ByteReader br(ts); | ||||
|             br.seek(0x0c); | ||||
|  | ||||
|             while (!br.eof()) | ||||
|             { | ||||
|                 int track = br.read_8(); | ||||
|                 int sector = br.read_8(); | ||||
|                 if (!track) | ||||
|                     goto done; | ||||
|  | ||||
|                 bw += getAppleSector(track * 16 + sector); | ||||
|             } | ||||
|  | ||||
|             tstrack = ts[1]; | ||||
|             tssector = ts[2]; | ||||
|         } | ||||
|  | ||||
|     done: | ||||
|         return bytes; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void mount() | ||||
|     { | ||||
|         _vtoc = getAppleSector(VTOC_BLOCK); | ||||
|         if ((_vtoc[0x27] != 122) || (_vtoc[0x36] != 0) || (_vtoc[0x37] != 1)) | ||||
|             throw BadFilesystemException(); | ||||
|  | ||||
|         _dirents.clear(); | ||||
|         int track = _vtoc[1]; | ||||
|         int sector = _vtoc[2]; | ||||
|         while (track) | ||||
|         { | ||||
|             Bytes dir = getAppleSector(track * 16 + sector); | ||||
|             ByteReader br(dir); | ||||
|             br.seek(0x0b); | ||||
|  | ||||
|             while (!br.eof()) | ||||
|             { | ||||
|                 Bytes fde = br.read(0x23); | ||||
|                 if ((fde[0] != 0) && (fde[0] != 255)) | ||||
|                     _dirents.push_back(std::make_shared<AppledosDirent>(fde)); | ||||
|             } | ||||
|  | ||||
|             track = dir[1]; | ||||
|             sector = dir[2]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<AppledosDirent> find(const std::string filename) | ||||
|     { | ||||
|         for (auto& de : _dirents) | ||||
|             if (de->filename == filename) | ||||
|                 return de; | ||||
|  | ||||
|         throw FileNotFoundException(); | ||||
|     } | ||||
|  | ||||
|     Bytes getAppleSector(uint32_t number, uint32_t count = 1) | ||||
|     { | ||||
|         return getLogicalSector( | ||||
|             number + _config.filesystem_offset_sectors(), count); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const AppledosProto& _config; | ||||
|     Bytes _vtoc; | ||||
|     std::vector<std::shared_ptr<AppledosDirent>> _dirents; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Filesystem> Filesystem::createAppledosFilesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors) | ||||
| { | ||||
|     return std::make_unique<AppledosFilesystem>(config.appledos(), sectors); | ||||
| } | ||||
| @@ -31,7 +31,18 @@ public: | ||||
|     { | ||||
|         ByteReader br(bytes); | ||||
|         filename = br.read(8); | ||||
|         filename = filename.substr(0, filename.find(' ')); | ||||
|  | ||||
|         for (int i = 0; filename.size(); i++) | ||||
|         { | ||||
|             if (filename[i] == ' ') | ||||
|             { | ||||
|                 filename = filename.substr(0, i); | ||||
|                 break; | ||||
|             } | ||||
|             if ((filename[i] < 32) || (filename[i] > 126)) | ||||
|                 throw BadFilesystemException(); | ||||
|         } | ||||
|  | ||||
|         path = {filename}; | ||||
|  | ||||
|         brotherType = br.read_8(); | ||||
| @@ -91,7 +102,7 @@ public: | ||||
|             for (int d = 0; d < SECTOR_SIZE / 16; d++) | ||||
|             { | ||||
|                 Bytes buffer = bytes.slice(d * 16, 16); | ||||
|                 if (buffer[0] == 0xf0) | ||||
|                 if (buffer[0] & 0x80) | ||||
|                     continue; | ||||
|  | ||||
|                 auto de = std::make_shared<Brother120Dirent>(buffer); | ||||
|   | ||||
| @@ -188,7 +188,6 @@ public: | ||||
|             /* Find a directory entry for this logical extent. */ | ||||
|  | ||||
|             std::unique_ptr<Entry> entry; | ||||
|             bool moreExtents = false; | ||||
|             for (int d = 0; d < _config.dir_entries(); d++) | ||||
|             { | ||||
|                 entry = getEntry(d); | ||||
| @@ -196,9 +195,10 @@ public: | ||||
|                     continue; | ||||
|                 if (path[0] != entry->filename) | ||||
|                     continue; | ||||
|                 if (entry->extent > logicalExtent) | ||||
|                     moreExtents = true; | ||||
|                 if (entry->extent == logicalExtent) | ||||
|                 if (entry->extent < logicalExtent) | ||||
|                     continue; | ||||
|                 if ((entry->extent & ~_logicalExtentMask) == | ||||
|                     (logicalExtent & ~_logicalExtentMask)) | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
| @@ -212,10 +212,10 @@ public: | ||||
|             /* Copy the data out. */ | ||||
|  | ||||
|             int i = | ||||
|                 (entry->extent & ~_logicalExtentMask) * _blocksPerLogicalExtent; | ||||
|                 (logicalExtent & _logicalExtentMask) * _blocksPerLogicalExtent; | ||||
|             unsigned records = | ||||
|                 (entry->extent == logicalExtent) ? entry->records : 128; | ||||
|             while ((records != 0) && (i != entry->allocation_map.size())) | ||||
|             while (records != 0) | ||||
|             { | ||||
|                 Bytes block; | ||||
|                 unsigned blockid = entry->allocation_map[i]; | ||||
| @@ -265,7 +265,7 @@ private: | ||||
|             physicalExtentSize = _config.block_size() * 8; | ||||
|         } | ||||
|         _logicalExtentsPerEntry = physicalExtentSize / 16384; | ||||
|         _logicalExtentMask = (1 << _logicalExtentsPerEntry) - 1; | ||||
|         _logicalExtentMask = _logicalExtentsPerEntry - 1; | ||||
|         _blocksPerLogicalExtent = 16384 / _config.block_size(); | ||||
|  | ||||
|         _directory = getCpmBlock(0, _dirBlocks); | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public: | ||||
|             if (!imageContainsAllSectorsOf(_changedSectors, | ||||
|                     track, | ||||
|                     side, | ||||
|                     trackLayout->logicalSectorOrder)) | ||||
|                     trackLayout->naturalSectorOrder)) | ||||
|             { | ||||
|                 /* If we don't have any loaded sectors for this track, pre-read | ||||
|                  * it. */ | ||||
| @@ -83,7 +83,7 @@ public: | ||||
|                 /* Now merge the loaded track with the changed one, and write | ||||
|                  * the result back. */ | ||||
|  | ||||
|                 for (unsigned sectorId : trackLayout->logicalSectorOrder) | ||||
|                 for (unsigned sectorId : trackLayout->naturalSectorOrder) | ||||
|                 { | ||||
|                     if (!_changedSectors.contains(track, side, sectorId)) | ||||
|                         _changedSectors.put(track, side, sectorId)->data = | ||||
|   | ||||
							
								
								
									
										296
									
								
								lib/vfs/philefs.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								lib/vfs/philefs.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "lib/utils.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| /* Root block: | ||||
|  * | ||||
|  * 00-0b	volume name | ||||
|  * 0c		01 | ||||
|  * 0d		2a | ||||
|  * 0e		79 | ||||
|  * 0f		6d | ||||
|  * 10		07 0x07c10c19, creation timestamp | ||||
|  * 11		c1 ^ | ||||
|  * 12		0c ^ | ||||
|  * 13		19 ^ | ||||
|  * 14		2f | ||||
|  * 15		00 | ||||
|  * 16		00 | ||||
|  * 17		18 | ||||
|  * 18		03 0x320, number of blocks on the disk | ||||
|  * 19		20 ^ | ||||
|  * 1a		00 0x0010, first data block? | ||||
|  * 1b		10 ^ | ||||
|  * 1c		00 0x0010, address of bitmap in HCS | ||||
|  * 1d		10 ^ | ||||
|  * 1e		00 0x0011, address of FLIST in HCS | ||||
|  * 1f		11 ^ | ||||
|  * 20		00 0x0017, address of last block of FLIST? | ||||
|  * 21		17 ^ | ||||
|  * 22		00 | ||||
|  * 23		6b | ||||
|  * 24		00 | ||||
|  * 25		20 | ||||
|  * | ||||
|  * 14 files | ||||
|  * file id 3 is not used | ||||
|  * directory at 0xc00 | ||||
|  * 0x4000, block 0x10, volume bitmap | ||||
|  * 0x4400, block 0x11, flist, 7 blocks long? | ||||
|  * file descriptors seem to be 64 bytes | ||||
|  * | ||||
|  * File descriptor, 64 bytes: | ||||
|  * 00		file type | ||||
|  * 0e+04    length in bytes | ||||
|  * 14...    spans | ||||
|  *          word: start block | ||||
|  *          word: number of blocks | ||||
|  * | ||||
| 00000C00   00 01 42 49  54 4D 41 50  2E 53 59 53  00 00 00 00  ..BITMAP.SYS.... | ||||
|  | ||||
| 00008040   41 00 00 00  07 C1 0C 19  1E 00 00 18  00 02 00 00  A............... | ||||
| 00008050   04 00 00 01  00 10 00 01  00 00 00 00  00 00 00 00  ................ | ||||
| 00008060   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................ | ||||
| 00008070   00 00 00 00  00 00 00 00  00 00 00 01  00 01 01 00  ................ | ||||
|  | ||||
| 00000C10   00 02 46 4C  49 53 54 2E  53 59 53 00  00 00 00 00  ..FLIST.SYS..... | ||||
|  | ||||
| 00008080   41 00 00 00  07 C1 0C 19  1E 00 00 18  00 02 00 00  A............... | ||||
| 00008090   1C 00 00 01  00 11 00 07  00 00 00 00  00 00 00 00  ................ | ||||
| 000080A0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................ | ||||
| 000080B0   00 00 00 00  00 00 00 00  00 00 00 07  00 07 01 00  ................ | ||||
|  | ||||
| 00000C20   00 04 53 4B  45 4C 00 00  00 00 00 00  00 00 00 00  ..SKEL.......... | ||||
|  | ||||
| 00008100   01 00 00 03  07 C1 0C 19  19 00 00 19  00 02 00 00  ................ | ||||
| 00008110   55 00 00 01  00 20 00 16  00 00 00 00  00 00 00 00  U.... .......... | ||||
| 00008120   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................ | ||||
| 00008130   00 00 00 00  00 00 00 00  00 00 00 16  00 16 01 00  ................ | ||||
|  | ||||
|  | ||||
| 00000C30   00 05 43 4F  44 45 00 00  00 00 00 00  00 00 00 00  ..CODE.......... | ||||
|  | ||||
| 00004540   01 00 00 03  07 C1 0C 19  26 00 00 1F  00 02 00 08  ........&....... | ||||
| 00004550   10 00 00 01  00 36 02 04  00 00 00 00  00 00 00 00  .....6.......... | ||||
| 00004560   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................ | ||||
| 00004570   00 00 00 00  00 00 00 00  00 00 02 04  02 04 01 00  ................ | ||||
|  | ||||
| 00000C40   00 06 53 43  53 49 00 00  00 00 00 00  00 00 00 00  ..SCSI.......... | ||||
| 00000C50   00 07 41 4F  46 00 00 00  00 00 00 00  00 00 00 00  ..AOF........... | ||||
| 00000C60   00 08 4D 43  46 00 00 00  00 00 00 00  00 00 00 00  ..MCF........... | ||||
| 00000C70   00 09 53 59  53 54 45 4D  2E 53 43 46  00 00 00 00  ..SYSTEM.SCF.... | ||||
| 00000C80   00 0A 53 59  53 54 45 4D  2E 50 44 46  00 00 00 00  ..SYSTEM.PDF.... | ||||
| 00000C90   00 0B 43 4C  54 31 2E 43  4C 54 00 00  00 00 00 00  ..CLT1.CLT...... | ||||
| 00000CA0   00 0C 43 4C  54 32 2E 43  4C 54 00 00  00 00 00 00  ..CLT2.CLT...... | ||||
| 00000CB0   00 0D 43 4C  54 33 2E 43  4C 54 00 00  00 00 00 00  ..CLT3.CLT...... | ||||
| 00000CC0   00 0E 43 4C  54 34 2E 43  4C 54 00 00  00 00 00 00  ..CLT4.CLT...... | ||||
| 00000CD0   00 0F 47 52  45 59 2E 43  4C 54 00 00  00 00 00 00  ..GREY.CLT...... | ||||
|  */ | ||||
|  | ||||
| static void trimZeros(std::string s) | ||||
| { | ||||
|     s.erase(std::remove(s.begin(), s.end(), 0), s.end()); | ||||
| } | ||||
|  | ||||
| class PhileFilesystem : public Filesystem | ||||
| { | ||||
|     struct Span | ||||
|     { | ||||
|         uint16_t startBlock; | ||||
|         uint16_t blockCount; | ||||
|     }; | ||||
|  | ||||
|     class PhileDirent : public Dirent | ||||
|     { | ||||
|     public: | ||||
|         PhileDirent( | ||||
|             int fileno, const std::string& filename, const Bytes& filedes): | ||||
|             _fileno(fileno) | ||||
|         { | ||||
|             file_type = TYPE_FILE; | ||||
|  | ||||
|             ByteReader br(filedes); | ||||
|             br.seek(0x0e); | ||||
|             length = br.read_be32(); | ||||
|  | ||||
|             { | ||||
|                 std::stringstream ss; | ||||
|                 ss << 'R'; | ||||
|                 if (filedes[0] & 0x40) | ||||
|                     ss << 'S'; | ||||
|                 mode = ss.str(); | ||||
|             } | ||||
|  | ||||
|             this->filename = filename; | ||||
|             path = {filename}; | ||||
|  | ||||
|             attributes[Filesystem::FILENAME] = filename; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|             attributes[Filesystem::FILE_TYPE] = "file"; | ||||
|             attributes[Filesystem::MODE] = mode; | ||||
|  | ||||
|             int spans = br.read_be16(); | ||||
|             for (int i = 0; i < spans; i++) | ||||
|             { | ||||
|                 Span span; | ||||
|                 span.startBlock = br.read_be16(); | ||||
|                 span.blockCount = br.read_be16(); | ||||
|                 _spans.push_back(span); | ||||
|             } | ||||
|  | ||||
|             attributes["phile.spans"] = std::to_string(spans); | ||||
|         } | ||||
|  | ||||
|         const std::vector<Span>& spans() const | ||||
|         { | ||||
|             return _spans; | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         int _fileno; | ||||
|         std::vector<Span> _spans; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     PhileFilesystem( | ||||
|         const PhileProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|         Filesystem(sectors), | ||||
|         _config(config) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT; | ||||
|     } | ||||
|  | ||||
|     FilesystemStatus check() override | ||||
|     { | ||||
|         return FS_OK; | ||||
|     } | ||||
|  | ||||
|     std::map<std::string, std::string> getMetadata() override | ||||
|     { | ||||
|         mount(); | ||||
|  | ||||
|         std::string volumename = _rootBlock.reader().read(0x0c); | ||||
|         trimZeros(volumename); | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|         attributes[VOLUME_NAME] = volumename; | ||||
|         attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks); | ||||
|         attributes[USED_BLOCKS] = attributes[TOTAL_BLOCKS]; | ||||
|         attributes[BLOCK_SIZE] = std::to_string(_config.block_size()); | ||||
|         return attributes; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Dirent> getDirent(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         return findFile(path.front()); | ||||
|     } | ||||
|  | ||||
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (!path.empty()) | ||||
|             throw FileNotFoundException(); | ||||
|  | ||||
|         std::vector<std::shared_ptr<Dirent>> result; | ||||
|         for (auto& de : _dirents) | ||||
|             result.push_back(de.second); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     Bytes getFile(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         auto dirent = findFile(path.front()); | ||||
|  | ||||
|         Bytes data; | ||||
|         ByteWriter bw(data); | ||||
|         for (const auto& span : dirent->spans()) | ||||
|             bw += getPsosBlock(span.startBlock, span.blockCount); | ||||
|  | ||||
|         data.resize(dirent->length); | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void mount() | ||||
|     { | ||||
|         _sectorSize = getLogicalSectorSize(); | ||||
|         _blockSectors = _config.block_size() / _sectorSize; | ||||
|  | ||||
|         _rootBlock = getPsosBlock(2, 1); | ||||
|         _bitmapBlockNumber = _rootBlock.reader().seek(0x1c).read_be16(); | ||||
|         _filedesBlockNumber = _rootBlock.reader().seek(0x1e).read_be16(); | ||||
|         _filedesLength = | ||||
|             _rootBlock.reader().seek(0x20).read_be16() - _filedesBlockNumber + 1; | ||||
|         _totalBlocks = _rootBlock.reader().seek(0x18).read_be16(); | ||||
|  | ||||
|         Bytes directoryBlock = getPsosBlock(3, 1); | ||||
|         Bytes filedesBlock = getPsosBlock(_filedesBlockNumber, _filedesLength); | ||||
|  | ||||
|         _dirents.clear(); | ||||
|         ByteReader br(directoryBlock); | ||||
|         ByteReader fr(filedesBlock); | ||||
|         while (!br.eof()) | ||||
|         { | ||||
|             uint16_t fileno = br.read_be16(); | ||||
|             std::string filename = br.read(14); | ||||
|             trimZeros(filename); | ||||
|  | ||||
|             if (fileno) | ||||
|             { | ||||
|                 fr.seek(fileno * 64); | ||||
|                 Bytes filedes = fr.read(64); | ||||
|                 auto dirent = | ||||
|                     std::make_unique<PhileDirent>(fileno, filename, filedes); | ||||
|                 _dirents[fileno] = std::move(dirent); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<PhileDirent> findFile(const std::string filename) | ||||
|     { | ||||
|         for (const auto& dirent : _dirents) | ||||
|         { | ||||
|             if (dirent.second->filename == filename) | ||||
|                 return dirent.second; | ||||
|         } | ||||
|  | ||||
|         throw FileNotFoundException(); | ||||
|     } | ||||
|  | ||||
|     Bytes getPsosBlock(uint32_t number, uint32_t count = 1) | ||||
|     { | ||||
|         unsigned sector = number * _blockSectors; | ||||
|         return getLogicalSector(sector, _blockSectors * count); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const PhileProto& _config; | ||||
|     int _sectorSize; | ||||
|     int _blockSectors; | ||||
|     int _totalBlocks; | ||||
|     int _bitmapBlockNumber; | ||||
|     int _filedesBlockNumber; | ||||
|     int _filedesLength; | ||||
|     Bytes _rootBlock; | ||||
|     std::map<int, std::shared_ptr<PhileDirent>> _dirents; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Filesystem> Filesystem::createPhileFilesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors) | ||||
| { | ||||
|     return std::make_unique<PhileFilesystem>(config.phile(), sectors); | ||||
| } | ||||
| @@ -203,9 +203,15 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem( | ||||
|         case FilesystemProto::PRODOS: | ||||
|             return Filesystem::createProdosFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::APPLEDOS: | ||||
|             return Filesystem::createAppledosFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::SMAKY6: | ||||
|             return Filesystem::createSmaky6Filesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::PHILE: | ||||
|             return Filesystem::createPhileFilesystem(config, image); | ||||
|  | ||||
|         default: | ||||
|             Error() << "no filesystem configured"; | ||||
|             return std::unique_ptr<Filesystem>(); | ||||
|   | ||||
| @@ -248,8 +248,12 @@ public: | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createProdosFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createAppledosFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createSmaky6Filesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createPhileFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|  | ||||
|     static std::unique_ptr<Filesystem> createFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|   | ||||
| @@ -59,9 +59,23 @@ message CbmfsProto | ||||
|  | ||||
| message ProdosProto {} | ||||
|  | ||||
| message AppledosProto { | ||||
| 	optional uint32 filesystem_offset_sectors = 1 [ | ||||
| 		default = 0, | ||||
| 		(help) = "offset the entire offset up the disk this many sectors" | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| message Smaky6FsProto {} | ||||
|  | ||||
| // NEXT_TAG: 12 | ||||
| message PhileProto { | ||||
| 	optional uint32 block_size = 1 [ | ||||
| 		default = 1024, | ||||
| 		(help) = "Phile filesystem block size" | ||||
| 	]; | ||||
| } | ||||
|  | ||||
| // NEXT_TAG: 14 | ||||
| message FilesystemProto | ||||
| { | ||||
|     enum FilesystemType { | ||||
| @@ -74,7 +88,9 @@ message FilesystemProto | ||||
|         MACHFS = 6; | ||||
|         CBMFS = 7; | ||||
|         PRODOS = 8; | ||||
| 		SMAKY6 = 9; | ||||
| 	      SMAKY6 = 9; | ||||
| 		    APPLEDOS = 10; | ||||
| 		    PHILE = 11; | ||||
|     } | ||||
|  | ||||
|     optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"]; | ||||
| @@ -87,7 +103,9 @@ message FilesystemProto | ||||
|     optional MacHfsProto machfs = 6; | ||||
|     optional CbmfsProto cbmfs = 7; | ||||
|     optional ProdosProto prodos = 8; | ||||
| 	optional Smaky6FsProto smaky6 = 11; | ||||
| 	  optional AppledosProto appledos = 12; | ||||
| 	  optional Smaky6FsProto smaky6 = 11; | ||||
| 	  optional PhileProto phile = 13; | ||||
|      | ||||
|     optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"]; | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ FLUXENGINE_SRCS = \ | ||||
| 	src/fe-getfileinfo.cc \ | ||||
| 	src/fe-inspect.cc \ | ||||
| 	src/fe-ls.cc \ | ||||
| 	src/fe-merge.cc \ | ||||
| 	src/fe-mkdir.cc \ | ||||
| 	src/fe-mv.cc \ | ||||
| 	src/fe-rm.cc \ | ||||
|   | ||||
| @@ -14,68 +14,51 @@ | ||||
|  | ||||
| static FlagGroup flags; | ||||
|  | ||||
| static StringFlag sourceFlux( | ||||
| 	{ "--source", "-s" }, | ||||
| 	"'drive:' flux source to use", | ||||
| 	"", | ||||
| 	[](const auto& value) | ||||
| 	{ | ||||
| 		FluxSource::updateConfigForFilename(config.mutable_flux_source(), value); | ||||
| 	}); | ||||
| static StringFlag sourceFlux({"--source", "-s"}, | ||||
|     "'drive:' flux source to use", | ||||
|     "", | ||||
|     [](const auto& value) | ||||
|     { | ||||
|         FluxSource::updateConfigForFilename( | ||||
|             config.mutable_flux_source(), value); | ||||
|     }); | ||||
|  | ||||
| static IntFlag trackFlag( | ||||
| 	{ "--cylinder", "-c" }, | ||||
| 	"Track to read.", | ||||
| 	0); | ||||
| static IntFlag trackFlag({"--cylinder", "-c"}, "Track to read.", 0); | ||||
|  | ||||
| static IntFlag headFlag( | ||||
| 	{ "--head", "-h" }, | ||||
| 	"Head to read.", | ||||
| 	0); | ||||
| static IntFlag headFlag({"--head", "-h"}, "Head to read.", 0); | ||||
|  | ||||
| static SettableFlag dumpFluxFlag( | ||||
| 	{ "--dump-flux", "-F" }, | ||||
| 	"Dump raw magnetic disk flux."); | ||||
|     {"--dump-flux", "-F"}, "Dump raw magnetic disk flux."); | ||||
|  | ||||
| static SettableFlag dumpBitstreamFlag( | ||||
| 	{ "--dump-bitstream", "-B" }, | ||||
| 	"Dump aligned bitstream."); | ||||
|     {"--dump-bitstream", "-B"}, "Dump aligned bitstream."); | ||||
|  | ||||
| static IntFlag dumpRawFlag( | ||||
| 	{ "--dump-raw", "-R" }, | ||||
| 	"Dump raw binary with offset.", | ||||
| 	0); | ||||
|     {"--dump-raw", "-R"}, "Dump raw binary with offset.", 0); | ||||
|  | ||||
| static SettableFlag dumpMfmFm( | ||||
| 	{ "--mfmfm" }, | ||||
| 	"When dumping raw binary, do MFM/FM decoding first."); | ||||
|     {"--mfmfm"}, "When dumping raw binary, do MFM/FM decoding first."); | ||||
|  | ||||
| static SettableFlag dumpBytecodesFlag( | ||||
|     { "--dump-bytecodes", "-H" }, | ||||
|     "Dump the raw FluxEngine bytecodes."); | ||||
|     {"--dump-bytecodes", "-H"}, "Dump the raw FluxEngine bytecodes."); | ||||
|  | ||||
| static IntFlag fluxmapResolutionFlag( | ||||
| 	{ "--fluxmap-resolution" }, | ||||
| 	"Resolution of flux visualisation (nanoseconds). 0 to autoscale", | ||||
| 	0); | ||||
| static IntFlag fluxmapResolutionFlag({"--fluxmap-resolution"}, | ||||
|     "Resolution of flux visualisation (nanoseconds). 0 to autoscale", | ||||
|     0); | ||||
|  | ||||
| static DoubleFlag seekFlag( | ||||
| 	{ "--seek", "-S" }, | ||||
| 	"Seek this many milliseconds into the track before displaying it.", | ||||
| 	0.0); | ||||
| static DoubleFlag seekFlag({"--seek", "-S"}, | ||||
|     "Seek this many milliseconds into the track before displaying it.", | ||||
|     0.0); | ||||
|  | ||||
| static DoubleFlag manualClockRate( | ||||
| 	{ "--manual-clock-rate-us", "-u" }, | ||||
| 	"If not zero, force this clock rate; if zero, try to autodetect it.", | ||||
| 	0.0); | ||||
| static DoubleFlag manualClockRate({"--manual-clock-rate-us", "-u"}, | ||||
|     "If not zero, force this clock rate; if zero, try to autodetect it.", | ||||
|     0.0); | ||||
|  | ||||
| static DoubleFlag noiseFloorFactor( | ||||
|     { "--noise-floor-factor" }, | ||||
| static DoubleFlag noiseFloorFactor({"--noise-floor-factor"}, | ||||
|     "Clock detection noise floor (min + (max-min)*factor).", | ||||
|     0.01); | ||||
|  | ||||
| static DoubleFlag signalLevelFactor( | ||||
|     { "--signal-level-factor" }, | ||||
| static DoubleFlag signalLevelFactor({"--signal-level-factor"}, | ||||
|     "Clock detection signal level (min + (max-min)*factor).", | ||||
|     0.05); | ||||
|  | ||||
| @@ -84,264 +67,214 @@ void setDecoderManualClockRate(double clockrate_us) | ||||
|     manualClockRate.setDefaultValue(clockrate_us); | ||||
| } | ||||
|  | ||||
| static const std::string BLOCK_ELEMENTS[] = | ||||
| { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" }; | ||||
| static const std::string BLOCK_ELEMENTS[] = { | ||||
|     " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"}; | ||||
|  | ||||
| /*  | ||||
| * Tries to guess the clock by finding the smallest common interval. | ||||
| /* | ||||
|  * Tries to guess the clock by finding the smallest common interval. | ||||
|  * Returns nanoseconds. | ||||
|  */ | ||||
| static nanoseconds_t guessClock(const Fluxmap& fluxmap) | ||||
| { | ||||
| 	if (manualClockRate != 0.0) | ||||
| 		return manualClockRate * 1000.0; | ||||
|     if (manualClockRate != 0.0) | ||||
|         return manualClockRate * 1000.0; | ||||
|  | ||||
|     uint32_t buckets[256] = {}; | ||||
|     FluxmapReader fr(fluxmap); | ||||
|     auto data = | ||||
|         fluxmap.guessClock(noiseFloorFactor.get(), signalLevelFactor.get()); | ||||
|  | ||||
|     while (!fr.eof()) | ||||
|     std::cout << "\nClock detection histogram:" << std::endl; | ||||
|  | ||||
|     uint32_t max = | ||||
|         *std::max_element(std::begin(data.buckets), std::end(data.buckets)); | ||||
|  | ||||
|     bool skipping = true; | ||||
|     for (int i = 0; i < 256; i++) | ||||
|     { | ||||
|         unsigned interval; | ||||
| 		fr.findEvent(F_BIT_PULSE, interval); | ||||
|         if (interval > 0xff) | ||||
|             continue; | ||||
|         buckets[interval]++; | ||||
|     } | ||||
|      | ||||
|     uint32_t max = *std::max_element(std::begin(buckets), std::end(buckets)); | ||||
|     uint32_t min = *std::min_element(std::begin(buckets), std::end(buckets)); | ||||
|     uint32_t noise_floor = min + (max-min)*noiseFloorFactor; | ||||
|     uint32_t signal_level = min + (max-min)*signalLevelFactor; | ||||
|         nanoseconds_t value = data.buckets[i]; | ||||
|         if (value < data.noiseFloor / 2) | ||||
|         { | ||||
|             if (!skipping) | ||||
|                 std::cout << "..." << std::endl; | ||||
|             skipping = true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             skipping = false; | ||||
|  | ||||
|     /* Find a point solidly within the first pulse. */ | ||||
|             int bar = 320 * value / max; | ||||
|             int fullblocks = bar / 8; | ||||
|  | ||||
|     int pulseindex = 0; | ||||
|     while (pulseindex < 256) | ||||
|     { | ||||
|         if (buckets[pulseindex] > signal_level) | ||||
|             break; | ||||
|         pulseindex++; | ||||
|     } | ||||
|     if (pulseindex == -1) | ||||
|         return 0; | ||||
|             std::string s; | ||||
|             for (int j = 0; j < fullblocks; j++) | ||||
|                 s += BLOCK_ELEMENTS[8]; | ||||
|             s += BLOCK_ELEMENTS[bar & 7]; | ||||
|  | ||||
|     /* Find the upper and lower bounds of the pulse. */ | ||||
|  | ||||
|     int peaklo = pulseindex; | ||||
|     while (peaklo > 0) | ||||
|     { | ||||
|         if (buckets[peaklo] < noise_floor) | ||||
|             break; | ||||
|         peaklo--; | ||||
|             std::cout << fmt::format( | ||||
|                 "{: 3} {:.2f} {:6} {}", i, (double)i * US_PER_TICK, value, s); | ||||
|             std::cout << std::endl; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     int peakhi = pulseindex; | ||||
|     while (peakhi < 255) | ||||
|     { | ||||
|         if (buckets[peakhi] < noise_floor) | ||||
|             break; | ||||
|         peakhi++; | ||||
|     } | ||||
|     std::cout << fmt::format("Noise floor:  {}\n", data.noiseFloor); | ||||
|     std::cout << fmt::format("Signal level: {}\n", data.signalLevel); | ||||
|     std::cout << fmt::format( | ||||
|         "Peak start:   {:.2f} us\n", data.peakStart / 1000.0); | ||||
|     std::cout << fmt::format( | ||||
|         "Peak end:     {:.2f} us\n", data.peakEnd / 1000.0); | ||||
|     std::cout << fmt::format("Median:       {:.2f} us\n", data.median / 1000.0); | ||||
|  | ||||
|     /* Find the total accumulated size of the pulse. */ | ||||
|  | ||||
|     uint32_t total_size = 0; | ||||
|     for (int i = peaklo; i < peakhi; i++) | ||||
|         total_size += buckets[i]; | ||||
|  | ||||
|     /* Now find the median. */ | ||||
|  | ||||
|     uint32_t count = 0; | ||||
|     int median = peaklo; | ||||
|     while (median < peakhi) | ||||
|     { | ||||
|         count += buckets[median]; | ||||
|         if (count > (total_size/2)) | ||||
|             break; | ||||
|         median++; | ||||
|     } | ||||
|  | ||||
| 	std::cout << "\nClock detection histogram:" << std::endl; | ||||
|  | ||||
| 	bool skipping = true; | ||||
| 	for (int i=0; i<256; i++) | ||||
| 	{ | ||||
| 		uint32_t value = buckets[i]; | ||||
| 		if (value < noise_floor/2) | ||||
| 		{ | ||||
| 			if (!skipping) | ||||
| 				std::cout << "..." << std::endl; | ||||
| 			skipping = true; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			skipping = false; | ||||
|  | ||||
| 			int bar = 320*value/max; | ||||
| 			int fullblocks = bar / 8; | ||||
|  | ||||
| 			std::string s; | ||||
| 			for (int j=0; j<fullblocks; j++) | ||||
| 				s += BLOCK_ELEMENTS[8]; | ||||
| 			s += BLOCK_ELEMENTS[bar & 7]; | ||||
|  | ||||
| 			std::cout << fmt::format("{: 3} {:.2f} {:6} {}", | ||||
| 					i, | ||||
| 					(double)i * US_PER_TICK, | ||||
| 					value, | ||||
| 					s); | ||||
| 			std::cout << std::endl; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::cout << fmt::format("Noise floor:  {}", noise_floor) << std::endl; | ||||
| 	std::cout << fmt::format("Signal level: {}", signal_level) << std::endl; | ||||
| 	std::cout << fmt::format("Peak start:   {} ({:.2f} us)", peaklo, peaklo*US_PER_TICK) << std::endl; | ||||
| 	std::cout << fmt::format("Peak end:     {} ({:.2f} us)", peakhi, peakhi*US_PER_TICK) << std::endl; | ||||
| 	std::cout << fmt::format("Median:       {} ({:.2f} us)", median, median*US_PER_TICK) << std::endl; | ||||
|  | ||||
|     /*  | ||||
|     /* | ||||
|      * Okay, the median should now be a good candidate for the (or a) clock. | ||||
|      * How this maps onto the actual clock rate depends on the encoding. | ||||
|      */ | ||||
|  | ||||
|     return median * NS_PER_TICK; | ||||
|     return data.median; | ||||
| } | ||||
|  | ||||
| int mainInspect(int argc, const char* argv[]) | ||||
| { | ||||
| 	config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source())); | ||||
| 	const auto fluxmap = fluxSource->readFlux(trackFlag, headFlag)->next(); | ||||
|     std::unique_ptr<FluxSource> fluxSource( | ||||
|         FluxSource::create(config.flux_source())); | ||||
|     const auto fluxmap = fluxSource->readFlux(trackFlag, headFlag)->next(); | ||||
|  | ||||
| 	std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n", | ||||
| 			fluxmap->bytes(), | ||||
| 			fluxmap->duration() / 1e6); | ||||
| 	std::cout << fmt::format("Required USB bandwidth: {}kB/s\n", | ||||
| 			fluxmap->bytes()/1024.0 / (fluxmap->duration() / 1e9)); | ||||
|     std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n", | ||||
|         fluxmap->bytes(), | ||||
|         fluxmap->duration() / 1e6); | ||||
|     std::cout << fmt::format("Required USB bandwidth: {}kB/s\n", | ||||
|         (int)(fluxmap->bytes() / 1024.0 / (fluxmap->duration() / 1e9))); | ||||
|  | ||||
| 	nanoseconds_t clockPeriod = guessClock(*fluxmap); | ||||
| 	std::cout << fmt::format("{:.2f} us clock detected.", (double)clockPeriod/1000.0) << std::flush; | ||||
|     nanoseconds_t clockPeriod = guessClock(*fluxmap); | ||||
|     std::cout << fmt::format( | ||||
|                      "{:.2f} us clock detected.", (double)clockPeriod / 1000.0) | ||||
|               << std::flush; | ||||
|  | ||||
| 	FluxmapReader fmr(*fluxmap); | ||||
| 	fmr.seek(seekFlag*1000000.0); | ||||
|     FluxmapReader fmr(*fluxmap); | ||||
|     fmr.seek(seekFlag * 1000000.0); | ||||
|  | ||||
| 	if (dumpFluxFlag) | ||||
| 	{ | ||||
| 		std::cout << "\n\nMagnetic flux follows (times in us):" << std::endl; | ||||
|     if (dumpFluxFlag) | ||||
|     { | ||||
|         std::cout << "\n\nMagnetic flux follows (times in us):" << std::endl; | ||||
|  | ||||
| 		int resolution = fluxmapResolutionFlag; | ||||
| 		if (resolution == 0) | ||||
| 			resolution = clockPeriod / 4; | ||||
|         int resolution = fluxmapResolutionFlag; | ||||
|         if (resolution == 0) | ||||
|             resolution = clockPeriod / 4; | ||||
|  | ||||
| 		nanoseconds_t nextclock = clockPeriod; | ||||
|         nanoseconds_t nextclock = clockPeriod; | ||||
|  | ||||
| 		nanoseconds_t now = fmr.tell().ns(); | ||||
| 		int ticks = now / NS_PER_TICK; | ||||
|         nanoseconds_t now = fmr.tell().ns(); | ||||
|         int ticks = now / NS_PER_TICK; | ||||
|  | ||||
| 		std::cout << fmt::format("{: 10.3f}:-", ticks*US_PER_TICK); | ||||
| 		nanoseconds_t lasttransition = 0; | ||||
| 		while (!fmr.eof()) | ||||
| 		{ | ||||
| 			unsigned thisTicks; | ||||
| 			fmr.findEvent(F_BIT_PULSE, thisTicks); | ||||
| 			ticks += thisTicks; | ||||
|         std::cout << fmt::format("{: 10.3f}:-", ticks * US_PER_TICK); | ||||
|         nanoseconds_t lasttransition = 0; | ||||
|         while (!fmr.eof()) | ||||
|         { | ||||
|             unsigned thisTicks; | ||||
|             fmr.findEvent(F_BIT_PULSE, thisTicks); | ||||
|             ticks += thisTicks; | ||||
|  | ||||
| 			nanoseconds_t transition = ticks*NS_PER_TICK; | ||||
| 			nanoseconds_t next; | ||||
| 			 | ||||
| 			bool clocked = false; | ||||
|             nanoseconds_t transition = ticks * NS_PER_TICK; | ||||
|             nanoseconds_t next; | ||||
|  | ||||
| 			bool bannered = false; | ||||
| 			auto banner = [&]() | ||||
| 			{ | ||||
| 				std::cout << fmt::format("\n{: 10.3f}:{}", (double)next / 1000.0, clocked ? '-' : ' '); | ||||
| 				bannered = true; | ||||
| 			}; | ||||
|             bool clocked = false; | ||||
|  | ||||
| 			for (;;) | ||||
| 			{ | ||||
| 				next = now + resolution; | ||||
| 				clocked = now >= nextclock; | ||||
| 				if (clocked) | ||||
| 					nextclock += clockPeriod; | ||||
| 				if (next >= transition) | ||||
| 					break; | ||||
| 				banner(); | ||||
| 				now = next; | ||||
| 			} | ||||
|             bool bannered = false; | ||||
|             auto banner = [&]() | ||||
|             { | ||||
|                 std::cout << fmt::format("\n{: 10.3f}:{}", | ||||
|                     (double)next / 1000.0, | ||||
|                     clocked ? '-' : ' '); | ||||
|                 bannered = true; | ||||
|             }; | ||||
|  | ||||
| 			nanoseconds_t length = transition - lasttransition; | ||||
| 			if (!bannered) | ||||
| 				banner(); | ||||
| 			std::cout << fmt::format("==== {:06x} {: 10.3f} +{:.3f} = {:.1f} clocks", | ||||
| 			    fmr.tell().bytes, | ||||
| 				(double)transition / 1000.0, | ||||
| 				(double)length / 1000.0, | ||||
| 				(double)length / clockPeriod); | ||||
| 			bannered = false; | ||||
| 			lasttransition = transition; | ||||
| 		} | ||||
| 	} | ||||
|             for (;;) | ||||
|             { | ||||
|                 next = now + resolution; | ||||
|                 clocked = now >= nextclock; | ||||
|                 if (clocked) | ||||
|                     nextclock += clockPeriod; | ||||
|                 if (next >= transition) | ||||
|                     break; | ||||
|                 banner(); | ||||
|                 now = next; | ||||
|             } | ||||
|  | ||||
| 	if (dumpBitstreamFlag) | ||||
| 	{ | ||||
| 		std::cout << fmt::format("\n\nAligned bitstream from {:.3f}ms follows:\n", | ||||
| 				fmr.tell().ns() / 1000000.0); | ||||
|             nanoseconds_t length = transition - lasttransition; | ||||
|             if (!bannered) | ||||
|                 banner(); | ||||
|             std::cout << fmt::format( | ||||
|                 "==== {:06x} {: 10.3f} +{:.3f} = {:.1f} clocks", | ||||
|                 fmr.tell().bytes, | ||||
|                 (double)transition / 1000.0, | ||||
|                 (double)length / 1000.0, | ||||
|                 (double)length / clockPeriod); | ||||
|             bannered = false; | ||||
|             lasttransition = transition; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 		FluxDecoder decoder(&fmr, clockPeriod, config.decoder()); | ||||
| 		while (!fmr.eof()) | ||||
| 		{ | ||||
| 			std::cout << fmt::format("{:06x} {: 10.3f} : ", | ||||
| 				fmr.tell().bytes, fmr.tell().ns() / 1000000.0); | ||||
| 			for (unsigned i=0; i<50; i++) | ||||
| 			{ | ||||
| 				if (fmr.eof()) | ||||
| 					break; | ||||
| 				bool b = decoder.readBit(); | ||||
| 				std::cout << (b ? 'X' : '-'); | ||||
| 			} | ||||
|     if (dumpBitstreamFlag) | ||||
|     { | ||||
|         std::cout << fmt::format( | ||||
|             "\n\nAligned bitstream from {:.3f}ms follows:\n", | ||||
|             fmr.tell().ns() / 1000000.0); | ||||
|  | ||||
| 			std::cout << std::endl; | ||||
| 		} | ||||
| 	} | ||||
|         FluxDecoder decoder(&fmr, clockPeriod, config.decoder()); | ||||
|         while (!fmr.eof()) | ||||
|         { | ||||
|             std::cout << fmt::format("{:06x} {: 10.3f} : ", | ||||
|                 fmr.tell().bytes, | ||||
|                 fmr.tell().ns() / 1000000.0); | ||||
|             for (unsigned i = 0; i < 50; i++) | ||||
|             { | ||||
|                 if (fmr.eof()) | ||||
|                     break; | ||||
|                 bool b = decoder.readBit(); | ||||
|                 std::cout << (b ? 'X' : '-'); | ||||
|             } | ||||
|  | ||||
| 	if (dumpRawFlag.isSet()) | ||||
| 	{ | ||||
| 		std::cout << fmt::format("\n\nRaw binary with offset {} from {:.3f}ms follows:\n", | ||||
| 				dumpRawFlag.get(), | ||||
| 				fmr.tell().ns() / 1000000.0); | ||||
|             std::cout << std::endl; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 		FluxDecoder decoder(&fmr, clockPeriod, config.decoder()); | ||||
| 		for (int i=0; i<dumpRawFlag; i++) | ||||
| 			decoder.readBit(); | ||||
|     if (dumpRawFlag.isSet()) | ||||
|     { | ||||
|         std::cout << fmt::format( | ||||
|             "\n\nRaw binary with offset {} from {:.3f}ms follows:\n", | ||||
|             dumpRawFlag.get(), | ||||
|             fmr.tell().ns() / 1000000.0); | ||||
|  | ||||
| 		while (!fmr.eof()) | ||||
| 		{ | ||||
| 			std::cout << fmt::format("{:06x} {: 10.3f} : ", | ||||
| 				fmr.tell().bytes, fmr.tell().ns() / 1000000.0); | ||||
|         FluxDecoder decoder(&fmr, clockPeriod, config.decoder()); | ||||
|         for (int i = 0; i < dumpRawFlag; i++) | ||||
|             decoder.readBit(); | ||||
|  | ||||
| 			Bytes bytes; | ||||
| 			if (dumpMfmFm) | ||||
| 				bytes = decodeFmMfm(decoder.readBits(32*8)); | ||||
| 			else | ||||
| 				bytes = toBytes(decoder.readBits(16*8)); | ||||
|         while (!fmr.eof()) | ||||
|         { | ||||
|             std::cout << fmt::format("{:06x} {: 10.3f} : ", | ||||
|                 fmr.tell().bytes, | ||||
|                 fmr.tell().ns() / 1000000.0); | ||||
|  | ||||
| 			ByteReader br(bytes); | ||||
|             Bytes bytes; | ||||
|             if (dumpMfmFm) | ||||
|                 bytes = decodeFmMfm(decoder.readBits(32 * 8)); | ||||
|             else | ||||
|                 bytes = toBytes(decoder.readBits(16 * 8)); | ||||
|  | ||||
| 			for (unsigned i=0; i<16; i++) | ||||
| 			{ | ||||
| 				if (br.eof()) | ||||
| 					break; | ||||
| 				std::cout << fmt::format("{:02x} ", br.read_8()); | ||||
| 			} | ||||
|             ByteReader br(bytes); | ||||
|  | ||||
| 			std::cout << std::endl; | ||||
| 		} | ||||
| 	} | ||||
| 	std::cout << std::endl; | ||||
|             for (unsigned i = 0; i < 16; i++) | ||||
|             { | ||||
|                 if (br.eof()) | ||||
|                     break; | ||||
|                 std::cout << fmt::format("{:02x} ", br.read_8()); | ||||
|             } | ||||
|  | ||||
|             std::cout << std::endl; | ||||
|         } | ||||
|     } | ||||
|     std::cout << std::endl; | ||||
|  | ||||
|     if (dumpBytecodesFlag) | ||||
|     { | ||||
| @@ -353,4 +286,3 @@ int mainInspect(int argc, const char* argv[]) | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										65
									
								
								src/fe-merge.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/fe-merge.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "sector.h" | ||||
| #include "proto.h" | ||||
| #include "flux.h" | ||||
| #include "fl2.h" | ||||
| #include "fl2.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include "fluxengine.h" | ||||
| #include <fstream> | ||||
|  | ||||
| static FlagGroup flags; | ||||
|  | ||||
| static std::vector<std::string> inputFluxFiles; | ||||
|  | ||||
| static StringFlag sourceFlux({"-s", "--source"}, | ||||
|     "flux file to read from (repeatable)", | ||||
|     "", | ||||
|     [](const auto& value) | ||||
|     { | ||||
|         inputFluxFiles.push_back(value); | ||||
|     }); | ||||
|  | ||||
| static StringFlag destFlux( | ||||
|     {"-d", "--dest"}, "destination flux file to write to", ""); | ||||
|  | ||||
| int mainMerge(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlags(argc, argv); | ||||
|  | ||||
|     if (inputFluxFiles.empty()) | ||||
|         Error() << "you must specify at least one input flux file (with -s)"; | ||||
|     if (destFlux.get() == "") | ||||
|         Error() << "you must specify an output flux file (with -d)"; | ||||
|  | ||||
|     std::map<std::pair<int, int>, TrackFluxProto> data; | ||||
|     for (const auto& s : inputFluxFiles) | ||||
|     { | ||||
|         fmt::print("Reading {}...\n", s); | ||||
|         FluxFileProto f = loadFl2File(s); | ||||
|  | ||||
|         for (auto& trackflux : f.track()) | ||||
|         { | ||||
|             auto key = std::make_pair(trackflux.track(), trackflux.head()); | ||||
|             auto i = data.find(key); | ||||
|             if (i == data.end()) | ||||
|                 data[key] = trackflux; | ||||
|             else | ||||
|             { | ||||
|                 for (auto flux : trackflux.flux()) | ||||
|                     i->second.add_flux(flux); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     FluxFileProto proto; | ||||
|     for (auto& i : data) | ||||
|         *proto.add_track() = i.second; | ||||
|  | ||||
|     fmt::print("Writing {}...\n", destFlux.get()); | ||||
|     saveFl2File(destFlux.get(), proto); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
| @@ -12,6 +12,7 @@ extern command_cb mainGetFile; | ||||
| extern command_cb mainGetFileInfo; | ||||
| extern command_cb mainInspect; | ||||
| extern command_cb mainLs; | ||||
| extern command_cb mainMerge; | ||||
| extern command_cb mainMkDir; | ||||
| extern command_cb mainMv; | ||||
| extern command_cb mainPutFile; | ||||
| @@ -44,6 +45,7 @@ static std::vector<Command> commands = | ||||
| 	{ "format",            mainFormat,            "Format a disk and make a file system on it.", }, | ||||
| 	{ "rawread",           mainRawRead,           "Reads raw flux from a disk. Warning: you can't use this to copy disks.", }, | ||||
|     { "rawwrite",          mainRawWrite,          "Writes a flux file to a disk. Warning: you can't use this to copy disks.", }, | ||||
| 	{ "merge",             mainMerge,             "Merge together multiple flux files.", }, | ||||
| 	{ "getdiskinfo",       mainGetDiskInfo,       "Read volume metadata off a disk (or image).", }, | ||||
| 	{ "ls",                mainLs,                "Show files on disk (or image).", }, | ||||
| 	{ "mv",                mainMv,                "Rename a file on a disk (or image).", }, | ||||
|   | ||||
							
								
								
									
										202
									
								
								src/formats/_apple2.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/formats/_apple2.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| comment: 'Apple II generic settings' | ||||
| is_extension: true | ||||
|  | ||||
| drive { | ||||
| 	high_density: false | ||||
| } | ||||
| 	 | ||||
| decoder { | ||||
| 	apple2 {} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	apple2 {} | ||||
| } | ||||
|  | ||||
| option { | ||||
| 	name: "nofs" | ||||
| 	comment: "use physical CHS sector order and no file system" | ||||
| 	exclusivity_group: "format" | ||||
| } | ||||
|  | ||||
| option { | ||||
| 	name: "appledos" | ||||
| 	comment: "use AppleDOS soft sector skew and file system" | ||||
| 	message: "compensating for AppleDOS soft sector skew" | ||||
| 	exclusivity_group: "format" | ||||
| 		 | ||||
| 	config { | ||||
| 		image_reader { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 		 | ||||
| 		image_writer { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 	 | ||||
| 		filesystem { | ||||
| 			type: APPLEDOS | ||||
| 		} | ||||
|  | ||||
| 		layout { | ||||
| 			layoutdata { | ||||
| 				filesystem { | ||||
| 					sector:  0 | ||||
| 					sector: 13 | ||||
| 					sector: 11 | ||||
| 					sector:  9 | ||||
| 					sector:  7 | ||||
| 					sector:  5 | ||||
| 					sector:  3 | ||||
| 					sector:  1 | ||||
| 					sector: 14 | ||||
| 					sector: 12 | ||||
| 					sector: 10 | ||||
| 					sector:  8 | ||||
| 					sector:  6 | ||||
| 					sector:  4 | ||||
| 					sector:  2 | ||||
| 					sector: 15 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| option { | ||||
| 	name: "prodos" | ||||
| 	comment: "use ProDOS soft sector skew and filesystem" | ||||
| 	message: "compensating for ProDOS soft sector skew" | ||||
| 	exclusivity_group: "format" | ||||
| 		 | ||||
| 	config { | ||||
| 		image_reader { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 		 | ||||
| 		image_writer { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 	 | ||||
| 		filesystem { | ||||
| 			type: PRODOS | ||||
| 		} | ||||
|  | ||||
| 		layout { | ||||
| 			layoutdata { | ||||
| 				filesystem { | ||||
| 					sector:  0 | ||||
| 					sector:  2 | ||||
| 					sector:  4 | ||||
| 					sector:  6 | ||||
| 					sector:  8 | ||||
| 					sector: 10 | ||||
| 					sector: 12 | ||||
| 					sector: 14 | ||||
| 					sector:  1 | ||||
| 					sector:  3 | ||||
| 					sector:  5 | ||||
| 					sector:  7 | ||||
| 					sector:  9 | ||||
| 					sector: 11 | ||||
| 					sector: 13 | ||||
| 					sector: 15 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| option { | ||||
| 	name: "cpm" | ||||
| 	comment: "use CP/M soft sector skew and filesystem" | ||||
| 	message: "compensating for CP/M soft sector skew" | ||||
| 	exclusivity_group: "format" | ||||
| 		 | ||||
| 	config { | ||||
| 		image_reader { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 		 | ||||
| 		image_writer { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 	 | ||||
| 		filesystem { | ||||
| 			type: CPMFS | ||||
| 			cpmfs { | ||||
| 				filesystem_start { | ||||
| 					track: 3 | ||||
| 				} | ||||
| 				block_size: 4096 | ||||
| 				dir_entries: 128 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		decoder { | ||||
| 			apple2 { | ||||
| 				side_one_track_offset: 80 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		encoder { | ||||
| 			apple2 { | ||||
| 				side_one_track_offset: 80 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		layout { | ||||
| 			layoutdata { | ||||
| 				# The boot tracks use ProDOS translation. | ||||
|  | ||||
| 				track: 0 | ||||
| 				up_to_track: 2 | ||||
| 				filesystem { | ||||
| 					sector:  0 | ||||
| 					sector:  2 | ||||
| 					sector:  4 | ||||
| 					sector:  6 | ||||
| 					sector:  8 | ||||
| 					sector: 10 | ||||
| 					sector: 12 | ||||
| 					sector: 14 | ||||
| 					sector:  1 | ||||
| 					sector:  3 | ||||
| 					sector:  5 | ||||
| 					sector:  7 | ||||
| 					sector:  9 | ||||
| 					sector: 11 | ||||
| 					sector: 13 | ||||
| 					sector: 15 | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			layoutdata { | ||||
| 				# The data tracks use their own, special translation. | ||||
|  | ||||
| 				track: 3 | ||||
| 				up_to_track: 79 | ||||
| 				filesystem { | ||||
| 					sector:  0 | ||||
| 					sector:  3 | ||||
| 					sector:  6 | ||||
| 					sector:  9 | ||||
| 					sector: 12 | ||||
| 					sector: 15 | ||||
| 					sector:  2 | ||||
| 					sector:  5 | ||||
| 					sector:  8 | ||||
| 					sector: 11 | ||||
| 					sector: 14 | ||||
| 					sector:  1 | ||||
| 					sector:  4 | ||||
| 					sector:  7 | ||||
| 					sector: 10 | ||||
| 					sector: 13 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -20,3 +20,8 @@ layout { | ||||
| decoder { | ||||
| 	agat {} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	agat {} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,115 +0,0 @@ | ||||
| comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "apple2.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 35 | ||||
| 	sides: 1 | ||||
| 	layoutdata { | ||||
| 		sector_size: 256 | ||||
| 		physical { | ||||
| 			start_sector: 0 | ||||
| 			count: 16 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "apple2.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	apple2 {} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	apple2 {} | ||||
| } | ||||
|  | ||||
| tpi: 48 | ||||
|  | ||||
| option { | ||||
| 	name: "appledos" | ||||
| 	comment: "specifies AppleDOS soft sector skew for filesystem access and images" | ||||
| 	message: "compensating for AppleDOS soft sector skew" | ||||
| 		 | ||||
| 	config { | ||||
| 		image_reader { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 		 | ||||
| 		image_writer { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 	 | ||||
| 		layout { | ||||
| 			layoutdata { | ||||
| 				filesystem { | ||||
| 					sector: 0 | ||||
| 					sector: 14 | ||||
| 					sector: 13 | ||||
| 					sector: 12 | ||||
| 					sector: 11 | ||||
| 					sector: 10 | ||||
| 					sector:  9 | ||||
| 					sector:  8 | ||||
| 					sector:  7 | ||||
| 					sector:  6 | ||||
| 					sector:  5 | ||||
| 					sector:  4 | ||||
| 					sector:  3 | ||||
| 					sector:  2 | ||||
| 					sector:  1 | ||||
| 					sector: 15 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| option { | ||||
| 	name: "prodos" | ||||
| 	comment: "specifies ProDOS soft sector skew for filesystem access and images" | ||||
| 	message: "compensating for ProDOS soft sector skew" | ||||
| 		 | ||||
| 	config { | ||||
| 		image_reader { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 		 | ||||
| 		image_writer { | ||||
| 			filesystem_sector_order: true | ||||
| 		} | ||||
| 	 | ||||
| 		filesystem { | ||||
| 			type: PRODOS | ||||
| 		} | ||||
|  | ||||
| 		layout { | ||||
| 			layoutdata { | ||||
| 				filesystem { | ||||
| 					sector:  0 | ||||
| 					sector:  2 | ||||
| 					sector:  4 | ||||
| 					sector:  6 | ||||
| 					sector:  8 | ||||
| 					sector: 10 | ||||
| 					sector: 12 | ||||
| 					sector: 14 | ||||
| 					sector:  1 | ||||
| 					sector:  3 | ||||
| 					sector:  5 | ||||
| 					sector:  7 | ||||
| 					sector:  9 | ||||
| 					sector: 11 | ||||
| 					sector: 13 | ||||
| 					sector: 15 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/formats/appleii140.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/formats/appleii140.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| comment: 'Apple II 140kB 5.25" 35 track SSDD' | ||||
| include: '_apple2' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "appleii140.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "appleii140.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 35 | ||||
| 	sides: 1 | ||||
| 	layoutdata { | ||||
| 		sector_size: 256 | ||||
| 		physical { | ||||
| 			start_sector: 0 | ||||
| 			count: 16 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| tpi: 48 | ||||
|  | ||||
							
								
								
									
										42
									
								
								src/formats/appleii640.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/formats/appleii640.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| comment: 'Apple II 640kB 5.25" 80 track DSDD' | ||||
| include: '_apple2' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "appleii640.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "appleii640.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 80 | ||||
| 	sides: 2 | ||||
| 	order: HCS | ||||
| 	layoutdata { | ||||
| 		sector_size: 256 | ||||
| 		physical { | ||||
| 			start_sector: 0 | ||||
| 			count: 16 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| tpi: 96 | ||||
|  | ||||
| option { | ||||
| 	name: "side1" | ||||
| 	comment: "read the volume on side 1 of a disk (AppleDOS only)" | ||||
| 	message: "accessing volume on side 1" | ||||
|  | ||||
| 	config { | ||||
| 		filesystem { | ||||
| 			appledos { | ||||
| 				filesystem_offset_sectors: 0x500 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -32,7 +32,7 @@ decoder { | ||||
| } | ||||
|  | ||||
| drive { | ||||
| 	head_bias: 1 | ||||
| 	head_bias: 3 | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| FORMATS = \ | ||||
| 	_acornadfs8 \ | ||||
| 	_acornadfs32 \ | ||||
| 	_apple2 \ | ||||
| 	_atari \ | ||||
| 	_micropolis \ | ||||
| 	_northstar \ | ||||
| @@ -18,7 +19,8 @@ FORMATS = \ | ||||
| 	ampro400 \ | ||||
| 	ampro800 \ | ||||
| 	apple2_drive \ | ||||
| 	apple2 \ | ||||
| 	appleii140 \ | ||||
| 	appleii640 \ | ||||
| 	atarist360 \ | ||||
| 	atarist370 \ | ||||
| 	atarist400 \ | ||||
| @@ -61,10 +63,16 @@ FORMATS = \ | ||||
| 	northstar175 \ | ||||
| 	northstar350 \ | ||||
| 	northstar87 \ | ||||
| 	psos800 \ | ||||
| 	rolandd20 \ | ||||
| 	rx50 \ | ||||
| 	shugart_drive \ | ||||
| 	smaky6 \ | ||||
| 	tids990 \ | ||||
| 	tiki90 \ | ||||
| 	tiki200 \ | ||||
| 	tiki400 \ | ||||
| 	tiki800 \ | ||||
| 	victor9k_ds \ | ||||
| 	victor9k_ss \ | ||||
| 	zilogmcz \ | ||||
|   | ||||
| @@ -17,6 +17,7 @@ layout { | ||||
| 		sector_size: 512 | ||||
| 		physical { | ||||
| 			start_sector: 0 | ||||
| 			skew: 6 | ||||
| 		} | ||||
| 	} | ||||
| 	layoutdata { | ||||
|   | ||||
							
								
								
									
										58
									
								
								src/formats/psos800.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/formats/psos800.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| comment: 'pSOS generic 800kB DSDD with PHILE' | ||||
|  | ||||
| drive { | ||||
| 	high_density: false | ||||
| 	rotational_period_ms: 200 | ||||
| } | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "pme.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "pme.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 80 | ||||
| 	sides: 2 | ||||
| 	order: HCS | ||||
| 	swap_sides: true | ||||
| 	layoutdata { | ||||
| 		sector_size: 1024 | ||||
| 		physical { | ||||
| 			sector: 1 | ||||
| 			sector: 2 | ||||
| 			sector: 3 | ||||
| 			sector: 4 | ||||
| 			sector: 5 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	ibm { | ||||
| 		trackdata { | ||||
| 			target_rotational_period_ms: 200 | ||||
| 			target_clock_period_us: 4 | ||||
| 			gap0: 80 | ||||
| 			gap2: 22 | ||||
| 			gap3: 80 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm { | ||||
| 		trackdata { | ||||
| 			ignore_side_byte: true | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	type: PHILE | ||||
| } | ||||
| 	 | ||||
							
								
								
									
										28
									
								
								src/formats/rolandd20.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/formats/rolandd20.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| comment: 'Roland D20' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "rolandd20.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 78 | ||||
| 	sides: 1 | ||||
| 	layoutdata { | ||||
| 		sector_size: 256 | ||||
| 		physical { | ||||
| 			start_sector: 0 | ||||
| 			count: 12 | ||||
| 			skew: 5 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	brother {} | ||||
| } | ||||
|  | ||||
| drive { | ||||
| 	head_bias: 1 | ||||
| } | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/formats/tiki200.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/formats/tiki200.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| comment: 'Tiki 100 200kB 40-track 10-sector SSSD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "tiki200.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 40 | ||||
| 	sides: 1 | ||||
| 	layoutdata { | ||||
| 		side: 0 | ||||
| 		sector_size: 512 | ||||
| 		physical { | ||||
| 			start_sector: 1 | ||||
| 			count: 10 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm {} | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	type: CPMFS | ||||
| 	cpmfs { | ||||
| 		filesystem_start { | ||||
| 			track: 2 | ||||
| 		} | ||||
| 		block_size: 1024 | ||||
| 		dir_entries: 64 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| tpi: 48 | ||||
|  | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/formats/tiki400.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/formats/tiki400.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| comment: 'Tiki 100 400kB 40-track 10-sector DSSD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "tiki400.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 40 | ||||
| 	sides: 2 | ||||
| 	layoutdata { | ||||
| 		sector_size: 512 | ||||
| 		physical { | ||||
| 			start_sector: 1 | ||||
| 			count: 10 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm {} | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	type: CPMFS | ||||
| 	cpmfs { | ||||
| 		filesystem_start { | ||||
| 			side: 0 | ||||
| 			track: 1 | ||||
| 		} | ||||
| 		block_size: 2048 | ||||
| 		dir_entries: 128 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| tpi: 48 | ||||
|  | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/formats/tiki800.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/formats/tiki800.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| comment: 'Tiki 100 800kB 80-track 10-sector DSSD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "tiki900.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 80 | ||||
| 	sides: 2 | ||||
| 	layoutdata { | ||||
| 		sector_size: 512 | ||||
| 		physical { | ||||
| 			start_sector: 1 | ||||
| 			count: 10 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm {} | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	type: CPMFS | ||||
| 	cpmfs { | ||||
| 		filesystem_start { | ||||
| 			side: 0 | ||||
| 			track: 1 | ||||
| 		} | ||||
| 		block_size: 2048 | ||||
| 		dir_entries: 128 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| tpi: 96 | ||||
|  | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/formats/tiki90.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/formats/tiki90.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| comment: 'Tiki 100 90kB 40-track 18-sector SSSD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "tiki90.img" | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| 	tracks: 40 | ||||
| 	sides: 1 | ||||
| 	layoutdata { | ||||
| 		side: 0 | ||||
| 		sector_size: 128 | ||||
| 		physical { | ||||
| 			start_sector: 1 | ||||
| 			count: 18 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm {} | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	type: CPMFS | ||||
| 	cpmfs { | ||||
| 		filesystem_start { | ||||
| 			track: 3 | ||||
| 		} | ||||
| 		block_size: 1024 | ||||
| 		dir_entries: 32 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| tpi: 48 | ||||
|  | ||||
|  | ||||
							
								
								
									
										692
									
								
								src/gui/browserpanel.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										692
									
								
								src/gui/browserpanel.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,692 @@ | ||||
| #include "globals.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/utils.h" | ||||
| #include "gui.h" | ||||
| #include "layout.h" | ||||
| #include "filesystemmodel.h" | ||||
| #include "fileviewerwindow.h" | ||||
| #include "textviewerwindow.h" | ||||
| #include "jobqueue.h" | ||||
|  | ||||
| const std::string DND_TYPE = "fluxengine.files"; | ||||
|  | ||||
| class BrowserPanelImpl : public BrowserPanelGen, public BrowserPanel, JobQueue | ||||
| { | ||||
|     enum | ||||
|     { | ||||
|         STATE_DEAD, | ||||
|         STATE_WORKING, | ||||
|         STATE_IDLE, | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     BrowserPanelImpl(MainWindow* mainWindow, wxSimplebook* parent): | ||||
|         BrowserPanelGen(parent), | ||||
|         BrowserPanel(mainWindow), | ||||
|         /* This is wrong. Apparently the wxDataViewCtrl doesn't work properly | ||||
|          * with DnD unless the format is wxDF_UNICODETEXT. It should be a custom | ||||
|          * value. */ | ||||
|         _dndFormat(wxDF_UNICODETEXT) | ||||
|     { | ||||
|         _filesystemModel = FilesystemModel::Associate(browserTree); | ||||
|  | ||||
|         /* This is a bug workaround for an issue in wxformbuilder's generated | ||||
|          * code; see https://github.com/wxFormBuilder/wxFormBuilder/pull/758. | ||||
|          * The default handler for the submenu doesn't allow events to fire on | ||||
|          * the button itself, so we have to override it with our own version. */ | ||||
|  | ||||
|         browserToolbar->Connect(browserMoreMenuButton->GetId(), | ||||
|             wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, | ||||
|             wxAuiToolBarEventHandler(BrowserPanelImpl::OnBrowserMoreMenuButton), | ||||
|             NULL, | ||||
|             this); | ||||
|  | ||||
|         /* This is a bug workaround for an issue where the calculation of the | ||||
|          * item being dropped on is wrong due to the header not being taken into | ||||
|          * account. See https://forums.wxwidgets.org/viewtopic.php?t=44752. */ | ||||
|  | ||||
|         browserTree->EnableDragSource(_dndFormat); | ||||
|         browserTree->EnableDropTarget(_dndFormat); | ||||
|  | ||||
|         parent->AddPage(this, "browser"); | ||||
|     } | ||||
|  | ||||
|     void OnBackButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartIdle(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void SetState(int state) | ||||
|     { | ||||
|         if (state != _state) | ||||
|         { | ||||
|             _state = state; | ||||
|             CallAfter( | ||||
|                 [&]() | ||||
|                 { | ||||
|                     UpdateState(); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void SwitchFrom() override | ||||
|     { | ||||
|         SetState(STATE_DEAD); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void StartBrowsing() override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SetPage(MainWindow::PAGE_BROWSER); | ||||
|             PrepareConfig(); | ||||
|  | ||||
|             _filesystemModel->Clear(Path()); | ||||
|             _filesystemCapabilities = 0; | ||||
|             _filesystemIsReadOnly = true; | ||||
|             _filesystemNeedsFlushing = false; | ||||
|  | ||||
|             SetState(STATE_WORKING); | ||||
|  | ||||
|             QueueJob( | ||||
|                 [this]() | ||||
|                 { | ||||
|                     _filesystem = Filesystem::createFilesystemFromConfig(); | ||||
|                     _filesystemCapabilities = _filesystem->capabilities(); | ||||
|                     _filesystemIsReadOnly = _filesystem->isReadOnly(); | ||||
|  | ||||
|                     runOnUiThread( | ||||
|                         [&]() | ||||
|                         { | ||||
|                             RepopulateBrowser(); | ||||
|                         }); | ||||
|                 }); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|             StartIdle(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void StartFormatting() override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SetPage(MainWindow::PAGE_BROWSER); | ||||
|             PrepareConfig(); | ||||
|  | ||||
|             _filesystemModel->Clear(Path()); | ||||
|             _filesystemCapabilities = 0; | ||||
|             _filesystemIsReadOnly = true; | ||||
|             _filesystemNeedsFlushing = false; | ||||
|  | ||||
|             SetState(STATE_WORKING); | ||||
|  | ||||
|             QueueJob( | ||||
|                 [this]() | ||||
|                 { | ||||
|                     _filesystem = Filesystem::createFilesystemFromConfig(); | ||||
|                     _filesystemCapabilities = _filesystem->capabilities(); | ||||
|                     _filesystemIsReadOnly = _filesystem->isReadOnly(); | ||||
|  | ||||
|                     runOnUiThread( | ||||
|                         [&]() | ||||
|                         { | ||||
|                             wxCommandEvent e; | ||||
|                             OnBrowserFormatButton(e); | ||||
|                         }); | ||||
|                 }); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|             StartIdle(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnQueueEmpty() override | ||||
|     { | ||||
|         SetState(STATE_IDLE); | ||||
|     } | ||||
|  | ||||
|     void QueueJob(std::function<void(void)> f) | ||||
|     { | ||||
|         SetState(STATE_WORKING); | ||||
|         JobQueue::QueueJob(f); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void UpdateState() | ||||
|     { | ||||
|         bool running = !IsQueueEmpty(); | ||||
|         bool selection = browserTree->GetSelection().IsOk(); | ||||
|  | ||||
|         browserToolbar->EnableTool( | ||||
|             browserBackTool->GetId(), _state == STATE_IDLE); | ||||
|  | ||||
|         uint32_t c = _filesystemCapabilities; | ||||
|         bool ro = _filesystemIsReadOnly; | ||||
|         bool needsFlushing = _filesystemNeedsFlushing; | ||||
|  | ||||
|         browserToolbar->EnableTool(browserInfoTool->GetId(), | ||||
|             !running && (c & Filesystem::OP_GETDIRENT) && selection); | ||||
|         browserToolbar->EnableTool(browserViewTool->GetId(), | ||||
|             !running && (c & Filesystem::OP_GETFILE) && selection); | ||||
|         browserToolbar->EnableTool(browserSaveTool->GetId(), | ||||
|             !running && (c & Filesystem::OP_GETFILE) && selection); | ||||
|         browserMoreMenu->Enable(browserAddMenuItem->GetId(), | ||||
|             !running && !ro && (c & Filesystem::OP_PUTFILE)); | ||||
|         browserMoreMenu->Enable(browserNewDirectoryMenuItem->GetId(), | ||||
|             !running && !ro && (c & Filesystem::OP_CREATEDIR)); | ||||
|         browserMoreMenu->Enable(browserRenameMenuItem->GetId(), | ||||
|             !running && !ro && (c & Filesystem::OP_MOVE) && selection); | ||||
|         browserMoreMenu->Enable(browserDeleteMenuItem->GetId(), | ||||
|             !running && !ro && (c & Filesystem::OP_DELETE) && selection); | ||||
|         browserToolbar->EnableTool(browserFormatTool->GetId(), | ||||
|             !running && !ro && (c & Filesystem::OP_CREATE)); | ||||
|  | ||||
|         browserDiscardButton->Enable(!running && needsFlushing); | ||||
|         browserCommitButton->Enable(!running && needsFlushing); | ||||
|  | ||||
|         browserToolbar->Refresh(); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserMoreMenuButton(wxAuiToolBarEvent& event) | ||||
|     { | ||||
|         browserToolbar->SetToolSticky(event.GetId(), true); | ||||
|         wxRect rect = browserToolbar->GetToolRect(event.GetId()); | ||||
|         wxPoint pt = browserToolbar->ClientToScreen(rect.GetBottomLeft()); | ||||
|         pt = ScreenToClient(pt); | ||||
|         browserToolbar->PopupMenu(browserMoreMenu, pt); | ||||
|         browserToolbar->SetToolSticky(event.GetId(), false); | ||||
|     } | ||||
|  | ||||
|     void RepopulateBrowser(Path path = Path()) | ||||
|     { | ||||
|         QueueJob( | ||||
|             [this, path]() | ||||
|             { | ||||
|                 auto files = _filesystem->list(path); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _filesystemModel->Clear(path); | ||||
|                         for (auto& f : files) | ||||
|                             _filesystemModel->Add(f); | ||||
|  | ||||
|                         auto node = _filesystemModel->Find(path); | ||||
|                         if (node) | ||||
|                             browserTree->Expand(node->item); | ||||
|                         UpdateFilesystemData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void UpdateFilesystemData() | ||||
|     { | ||||
|         QueueJob( | ||||
|             [this]() | ||||
|             { | ||||
|                 auto metadata = _filesystem->getMetadata(); | ||||
|                 _filesystemNeedsFlushing = _filesystem->needsFlushing(); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         try | ||||
|                         { | ||||
|                             uint32_t blockSize = | ||||
|                                 std::stoul(metadata.at(Filesystem::BLOCK_SIZE)); | ||||
|                             uint32_t totalBlocks = std::stoul( | ||||
|                                 metadata.at(Filesystem::TOTAL_BLOCKS)); | ||||
|                             uint32_t usedBlocks = std::stoul( | ||||
|                                 metadata.at(Filesystem::USED_BLOCKS)); | ||||
|  | ||||
|                             diskSpaceGauge->Enable(); | ||||
|                             diskSpaceGauge->SetRange(totalBlocks * blockSize); | ||||
|                             diskSpaceGauge->SetValue(usedBlocks * blockSize); | ||||
|                         } | ||||
|                         catch (const std::out_of_range& e) | ||||
|                         { | ||||
|                             diskSpaceGauge->Disable(); | ||||
|                         } | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserDirectoryExpanding(wxDataViewEvent& event) override | ||||
|     { | ||||
|         auto node = _filesystemModel->Find(event.GetItem()); | ||||
|         if (node && !node->populated && !node->populating) | ||||
|         { | ||||
|             node->populating = true; | ||||
|             RepopulateBrowser(node->dirent->path); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnBrowserInfoButton(wxCommandEvent&) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto node = _filesystemModel->Find(item); | ||||
|  | ||||
|         std::stringstream ss; | ||||
|         ss << "File attributes for " << node->dirent->path.to_str() << ":\n\n"; | ||||
|         for (const auto& e : node->dirent->attributes) | ||||
|             ss << e.first << "=" << quote(e.second) << "\n"; | ||||
|  | ||||
|         TextViewerWindow::Create( | ||||
|             this, node->dirent->path.to_str(), ss.str(), true) | ||||
|             ->Show(); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserViewButton(wxCommandEvent&) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto node = _filesystemModel->Find(item); | ||||
|  | ||||
|         QueueJob( | ||||
|             [this, node]() | ||||
|             { | ||||
|                 auto bytes = _filesystem->getFile(node->dirent->path); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         (new FileViewerWindow( | ||||
|                              this, node->dirent->path.to_str(), bytes)) | ||||
|                             ->Show(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserSaveButton(wxCommandEvent&) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto node = _filesystemModel->Find(item); | ||||
|  | ||||
|         GetfileDialog d(this, wxID_ANY); | ||||
|         d.filenameText->SetValue(node->dirent->path.to_str()); | ||||
|         d.targetFilePicker->SetFileName(wxFileName(node->dirent->filename)); | ||||
|         d.targetFilePicker->SetFocus(); | ||||
|         d.buttons_OK->SetDefault(); | ||||
|         if (d.ShowModal() != wxID_OK) | ||||
|             return; | ||||
|  | ||||
|         auto localPath = d.targetFilePicker->GetPath().ToStdString(); | ||||
|         QueueJob( | ||||
|             [this, node, localPath]() | ||||
|             { | ||||
|                 auto bytes = _filesystem->getFile(node->dirent->path); | ||||
|                 bytes.writeToFile(localPath); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     /* Called from worker thread only! */ | ||||
|     Path ResolveFileConflicts_WT(Path path) | ||||
|     { | ||||
|         do | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 _filesystem->getDirent(path); | ||||
|             } | ||||
|             catch (const FileNotFoundException& e) | ||||
|             { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             runOnUiThread( | ||||
|                 [&]() | ||||
|                 { | ||||
|                     FileConflictDialog d(this, wxID_ANY); | ||||
|                     d.oldNameText->SetValue(path.to_str()); | ||||
|                     d.newNameText->SetValue(path.to_str()); | ||||
|                     d.newNameText->SetFocus(); | ||||
|                     d.buttons_OK->SetDefault(); | ||||
|                     if (d.ShowModal() == wxID_OK) | ||||
|                         path = Path(d.newNameText->GetValue().ToStdString()); | ||||
|                     else | ||||
|                         path = Path(""); | ||||
|                 }); | ||||
|         } while (!path.empty()); | ||||
|         return path; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<FilesystemNode> GetTargetDirectoryNode(wxDataViewItem& item) | ||||
|     { | ||||
|         Path path; | ||||
|         if (item.IsOk()) | ||||
|         { | ||||
|             auto node = _filesystemModel->Find(item); | ||||
|             if (!node) | ||||
|                 return nullptr; | ||||
|             path = node->dirent->path; | ||||
|         } | ||||
|  | ||||
|         auto node = _filesystemModel->Find(path); | ||||
|         if (!node) | ||||
|             return nullptr; | ||||
|         if (node->dirent->file_type != TYPE_DIRECTORY) | ||||
|             return _filesystemModel->Find(path.parent()); | ||||
|         return node; | ||||
|     } | ||||
|  | ||||
|     void OnBrowserAddMenuItem(wxCommandEvent&) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto dirNode = GetTargetDirectoryNode(item); | ||||
|         if (!dirNode) | ||||
|             return; | ||||
|  | ||||
|         auto localPath = wxFileSelector("Choose the name of the file to add", | ||||
|             /* default_path= */ wxEmptyString, | ||||
|             /* default_filename= */ wxEmptyString, | ||||
|             /* default_extension= */ wxEmptyString, | ||||
|             /* wildcard= */ wxEmptyString, | ||||
|             /* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST) | ||||
|                              .ToStdString(); | ||||
|         if (localPath.empty()) | ||||
|             return; | ||||
|         auto path = dirNode->dirent->path.concat( | ||||
|             wxFileName(localPath).GetFullName().ToStdString()); | ||||
|  | ||||
|         QueueJob( | ||||
|             [this, path, localPath]() mutable | ||||
|             { | ||||
|                 path = ResolveFileConflicts_WT(path); | ||||
|                 if (path.empty()) | ||||
|                     return; | ||||
|  | ||||
|                 auto bytes = Bytes::readFromFile(localPath); | ||||
|                 _filesystem->putFile(path, bytes); | ||||
|  | ||||
|                 auto dirent = _filesystem->getDirent(path); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _filesystemModel->Add(dirent); | ||||
|                         UpdateFilesystemData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserDeleteMenuItem(wxCommandEvent&) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto node = _filesystemModel->Find(item); | ||||
|         if (!node) | ||||
|             return; | ||||
|  | ||||
|         QueueJob( | ||||
|             [this, node]() | ||||
|             { | ||||
|                 _filesystem->deleteFile(node->dirent->path); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _filesystemModel->Delete(node->dirent->path); | ||||
|                         UpdateFilesystemData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserFormatButton(wxCommandEvent&) override | ||||
|     { | ||||
|         FormatDialog d(this, wxID_ANY); | ||||
|         d.volumeNameText->SetFocus(); | ||||
|         d.buttons_OK->SetDefault(); | ||||
|         if (d.ShowModal() != wxID_OK) | ||||
|             return; | ||||
|  | ||||
|         auto volumeName = d.volumeNameText->GetValue().ToStdString(); | ||||
|         auto quickFormat = d.quickFormatCheckBox->GetValue(); | ||||
|         QueueJob( | ||||
|             [this, volumeName, quickFormat]() | ||||
|             { | ||||
|                 _filesystem->discardChanges(); | ||||
|                 _filesystem->create(quickFormat, volumeName); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         RepopulateBrowser(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserFilenameChanged(wxDataViewEvent& event) override | ||||
|     { | ||||
|         if (!(_filesystem->capabilities() & Filesystem::OP_MOVE)) | ||||
|             return; | ||||
|  | ||||
|         auto node = _filesystemModel->Find(event.GetItem()); | ||||
|         if (!node) | ||||
|             return; | ||||
|  | ||||
|         if (node->newname.empty()) | ||||
|             return; | ||||
|         if (node->newname == node->dirent->filename) | ||||
|             return; | ||||
|  | ||||
|         QueueJob( | ||||
|             [this, node]() mutable | ||||
|             { | ||||
|                 auto oldPath = node->dirent->path; | ||||
|                 auto newPath = oldPath.parent().concat(node->newname); | ||||
|  | ||||
|                 newPath = ResolveFileConflicts_WT(newPath); | ||||
|                 if (newPath.empty()) | ||||
|                     return; | ||||
|  | ||||
|                 _filesystem->moveFile(oldPath, newPath); | ||||
|  | ||||
|                 auto dirent = _filesystem->getDirent(newPath); | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _filesystemModel->Delete(oldPath); | ||||
|                         _filesystemModel->Add(dirent); | ||||
|                         UpdateFilesystemData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserRenameMenuItem(wxCommandEvent& event) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto node = _filesystemModel->Find(item); | ||||
|  | ||||
|         FileRenameDialog d(this, wxID_ANY); | ||||
|         d.oldNameText->SetValue(node->dirent->path.to_str()); | ||||
|         d.newNameText->SetValue(node->dirent->path.to_str()); | ||||
|         d.newNameText->SetFocus(); | ||||
|         d.buttons_OK->SetDefault(); | ||||
|         if (d.ShowModal() != wxID_OK) | ||||
|             return; | ||||
|  | ||||
|         ActuallyMoveFile( | ||||
|             node->dirent->path, Path(d.newNameText->GetValue().ToStdString())); | ||||
|     } | ||||
|  | ||||
|     void ActuallyMoveFile(const Path& oldPath, Path newPath) | ||||
|     { | ||||
|         QueueJob( | ||||
|             [this, oldPath, newPath]() mutable | ||||
|             { | ||||
|                 newPath = ResolveFileConflicts_WT(newPath); | ||||
|                 if (newPath.empty()) | ||||
|                     return; | ||||
|  | ||||
|                 _filesystem->moveFile(oldPath, newPath); | ||||
|  | ||||
|                 auto dirent = _filesystem->getDirent(newPath); | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _filesystemModel->Delete(oldPath); | ||||
|                         _filesystemModel->Add(dirent); | ||||
|                         UpdateFilesystemData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserNewDirectoryMenuItem(wxCommandEvent& event) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         auto node = GetTargetDirectoryNode(item); | ||||
|         if (!node) | ||||
|             return; | ||||
|         auto path = node->dirent->path; | ||||
|  | ||||
|         CreateDirectoryDialog d(this, wxID_ANY); | ||||
|         d.newNameText->SetValue(path.to_str() + "/"); | ||||
|         d.newNameText->SetFocus(); | ||||
|         d.buttons_OK->SetDefault(); | ||||
|         if (d.ShowModal() != wxID_OK) | ||||
|             return; | ||||
|  | ||||
|         auto newPath = Path(d.newNameText->GetValue().ToStdString()); | ||||
|         QueueJob( | ||||
|             [this, newPath]() mutable | ||||
|             { | ||||
|                 newPath = ResolveFileConflicts_WT(newPath); | ||||
|                 _filesystem->createDirectory(newPath); | ||||
|  | ||||
|                 auto dirent = _filesystem->getDirent(newPath); | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _filesystemModel->Add(dirent); | ||||
|                         UpdateFilesystemData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserBeginDrag(wxDataViewEvent& event) override | ||||
|     { | ||||
|         auto item = browserTree->GetSelection(); | ||||
|         if (!item.IsOk()) | ||||
|         { | ||||
|             event.Veto(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         auto node = _filesystemModel->Find(item); | ||||
|         if (!node) | ||||
|         { | ||||
|             event.Veto(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         wxTextDataObject* obj = new wxTextDataObject(); | ||||
|         obj->SetText(node->dirent->path.to_str()); | ||||
|         event.SetDataObject(obj); | ||||
|         event.SetDataFormat(_dndFormat); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserDropPossible(wxDataViewEvent& event) override | ||||
|     { | ||||
|         if (event.GetDataFormat() != _dndFormat) | ||||
|         { | ||||
|             event.Veto(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnBrowserDrop(wxDataViewEvent& event) override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (event.GetDataFormat() != _dndFormat) | ||||
|                 throw CancelException(); | ||||
|  | ||||
| #if defined __WXGTK__ | ||||
|             /* wxWidgets 3.0 data view DnD on GTK is borked. See | ||||
|              * https://forums.wxwidgets.org/viewtopic.php?t=44752. The hit | ||||
|              * detection is done against the wrong object, resulting in the | ||||
|              * header size not being taken into account, so we have to | ||||
|              * manually do hit detection correctly. */ | ||||
|  | ||||
|             auto* window = browserTree->GetMainWindow(); | ||||
|             auto screenPos = wxGetMousePosition(); | ||||
|             auto relPos = screenPos - window->GetScreenPosition(); | ||||
|  | ||||
|             wxDataViewItem item; | ||||
|             wxDataViewColumn* column; | ||||
|             browserTree->HitTest(relPos, item, column); | ||||
|             if (!item.IsOk()) | ||||
|                 throw CancelException(); | ||||
| #else | ||||
|             auto item = event.GetItem(); | ||||
| #endif | ||||
|  | ||||
|             auto destDirNode = GetTargetDirectoryNode(item); | ||||
|             if (!destDirNode) | ||||
|                 throw CancelException(); | ||||
|             auto destDirPath = destDirNode->dirent->path; | ||||
|  | ||||
|             wxTextDataObject obj; | ||||
|             obj.SetData(_dndFormat, event.GetDataSize(), event.GetDataBuffer()); | ||||
|             auto srcPath = Path(obj.GetText().ToStdString()); | ||||
|             if (srcPath.empty()) | ||||
|                 throw CancelException(); | ||||
|  | ||||
|             ActuallyMoveFile(srcPath, destDirPath.concat(srcPath.back())); | ||||
|         } | ||||
|         catch (const CancelException& e) | ||||
|         { | ||||
|             event.Veto(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnBrowserCommitButton(wxCommandEvent&) override | ||||
|     { | ||||
|         QueueJob( | ||||
|             [this]() | ||||
|             { | ||||
|                 _filesystem->flushChanges(); | ||||
|                 UpdateFilesystemData(); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserDiscardButton(wxCommandEvent&) override | ||||
|     { | ||||
|         QueueJob( | ||||
|             [this]() | ||||
|             { | ||||
|                 _filesystem->discardChanges(); | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         RepopulateBrowser(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     void OnBrowserSelectionChanged(wxDataViewEvent& event) override | ||||
|     { | ||||
|         UpdateState(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     int _state = STATE_DEAD; | ||||
|     std::unique_ptr<Filesystem> _filesystem; | ||||
|     uint32_t _filesystemCapabilities; | ||||
|     bool _filesystemIsReadOnly; | ||||
|     bool _filesystemNeedsFlushing; | ||||
|     FilesystemModel* _filesystemModel; | ||||
|     wxDataFormat _dndFormat; | ||||
| }; | ||||
|  | ||||
| BrowserPanel* BrowserPanel::Create(MainWindow* mainWindow, wxSimplebook* parent) | ||||
| { | ||||
|     return new BrowserPanelImpl(mainWindow, parent); | ||||
| } | ||||
| @@ -1,27 +1,39 @@ | ||||
| ifneq ($(shell $(WX_CONFIG) --version),) | ||||
|  | ||||
| FLUXENGINE_GUI_SRCS = \ | ||||
| 	src/gui/browserpanel.cc \ | ||||
| 	src/gui/customstatusbar.cc \ | ||||
| 	src/gui/explorerpanel.cc \ | ||||
| 	src/gui/filesystemmodel.cc \ | ||||
| 	src/gui/fileviewerwindow.cc \ | ||||
| 	src/gui/fluxviewercontrol.cc \ | ||||
| 	src/gui/fluxviewerwindow.cc \ | ||||
| 	src/gui/histogramviewer.cc \ | ||||
| 	src/gui/iconbutton.cc \ | ||||
| 	src/gui/idlepanel.cc \ | ||||
| 	src/gui/imagerpanel.cc \ | ||||
| 	src/gui/jobqueue.cc \ | ||||
| 	src/gui/layout.cpp \ | ||||
| 	src/gui/main.cc \ | ||||
| 	src/gui/mainwindow.cc \ | ||||
| 	src/gui/texteditorwindow.cc \ | ||||
| 	src/gui/textviewerwindow.cc \ | ||||
| 	src/gui/fileviewerwindow.cc \ | ||||
| 	src/gui/visualisationcontrol.cc \ | ||||
|   | ||||
| src/gui/idlepanel.cc: \ | ||||
| 	$(OBJDIR)/extras/hardware.h \ | ||||
| 	$(OBJDIR)/extras/fluxfile.h \ | ||||
| 	$(OBJDIR)/extras/imagefile.h | ||||
|  | ||||
| FLUXENGINE_GUI_OBJS = \ | ||||
| 	$(patsubst %.cpp, $(OBJDIR)/%.o, \ | ||||
| 	$(patsubst %.cc, $(OBJDIR)/%.o, $(FLUXENGINE_GUI_SRCS)) \ | ||||
| 	) | ||||
| OBJS += $(FLUXENGINE_GUI_OBJS) | ||||
| $(FLUXENGINE_GUI_SRCS): | $(PROTO_HDRS) | ||||
| $(FLUXENGINE_GUI_OBJS): CFLAGS += $(shell $(WX_CONFIG) --cxxflags core base adv aui) | ||||
| $(FLUXENGINE_GUI_OBJS): CFLAGS += $(shell $(WX_CONFIG) --cxxflags core base adv aui richtext) | ||||
| FLUXENGINE_GUI_BIN = $(OBJDIR)/fluxengine-gui.exe | ||||
| $(FLUXENGINE_GUI_BIN): LDFLAGS += $(shell $(WX_CONFIG) --libs core base adv aui) | ||||
| $(FLUXENGINE_GUI_BIN): LDFLAGS += $(shell $(WX_CONFIG) --libs core base adv aui richtext) | ||||
| $(FLUXENGINE_GUI_BIN): $(FLUXENGINE_GUI_OBJS) | ||||
|  | ||||
| $(call use-pkgconfig, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), fmt) | ||||
| @@ -40,6 +52,11 @@ fluxengine-gui$(EXT): $(FLUXENGINE_GUI_BIN) | ||||
| 	@echo CP $@ | ||||
| 	@cp $< $@ | ||||
|  | ||||
| $(OBJDIR)/%.h: %.png | ||||
| 	@echo ENCODE $@ | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@xxd -i $^ > $@ | ||||
|  | ||||
| ifeq ($(PLATFORM),OSX) | ||||
|  | ||||
| binaries: FluxEngine.pkg | ||||
| @@ -52,13 +69,16 @@ FluxEngine.app: fluxengine-gui$(EXT) $(OBJDIR)/fluxengine.icns | ||||
| 	@echo MAKEAPP $@ | ||||
| 	@rm -rf $@ | ||||
| 	@cp -a extras/FluxEngine.app.template $@ | ||||
| 	@touch $@ | ||||
| 	@cp fluxengine-gui$(EXT) $@/Contents/MacOS/fluxengine-gui | ||||
| 	@mkdir -p $@/Contents/Resources | ||||
| 	@cp $(OBJDIR)/fluxengine.icns $@/Contents/Resources/FluxEngine.icns | ||||
| 	@for name in `otool -L fluxengine-gui$(EXT) | tr -d '\t' | grep -v '^/System/' | grep -v '^/usr/lib/' | grep -v ':$$' | awk '{print $$1}'`; do cp "$$name" $@/Contents/Resources; done | ||||
| 	@cp /usr/local/opt/wxwidgets/README.md $@/Contents/Resources/wxWidgets.md | ||||
| 	@cp /usr/local/opt/protobuf/LICENSE $@/Contents/Resources/protobuf.txt | ||||
| 	@cp /usr/local/opt/fmt/LICENSE.rst $@/Contents/Resources/fmt.rst | ||||
| 	@dylibbundler -of -x $@/Contents/MacOS/fluxengine-gui -b -d $@/Contents/libs -cd > /dev/null | ||||
| 	@cp /usr/local/opt/wxwidgets/README.md $@/Contents/libs/wxWidgets.md | ||||
| 	@cp /usr/local/opt/protobuf/LICENSE $@/Contents/libs/protobuf.txt | ||||
| 	@cp /usr/local/opt/fmt/LICENSE.rst $@/Contents/libs/fmt.rst | ||||
| 	@cp /usr/local/opt/libpng/LICENSE $@/Contents/libs/libpng.txt | ||||
| 	@cp /usr/local/opt/libjpeg/README $@/Contents/libs/libjpeg.txt | ||||
|  | ||||
| $(OBJDIR)/fluxengine.icns: $(OBJDIR)/fluxengine.iconset | ||||
| 	@echo ICONUTIL $@ | ||||
|   | ||||
							
								
								
									
										235
									
								
								src/gui/explorerpanel.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/gui/explorerpanel.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,235 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/environment.h" | ||||
| #include "lib/fluxsource/fluxsource.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/proto.h" | ||||
| #include "gui.h" | ||||
| #include "layout.h" | ||||
| #include "jobqueue.h" | ||||
|  | ||||
| static Bytes fakeBits(const std::vector<bool>& bits) | ||||
| { | ||||
| 	Bytes result; | ||||
| 	ByteWriter bw(result); | ||||
|  | ||||
| 	auto it = bits.begin(); | ||||
| 	while (it != bits.end()) | ||||
| 	{ | ||||
| 		uint8_t b = (*it++) << 4; | ||||
| 		if (it != bits.end()) | ||||
| 			b |= *it++; | ||||
| 		bw.write_8(b); | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| class ExplorerPanelImpl : | ||||
|     public ExplorerPanelGen, | ||||
|     public ExplorerPanel, | ||||
|     JobQueue | ||||
| { | ||||
|     enum | ||||
|     { | ||||
|         STATE_DEAD, | ||||
|         STATE_WORKING, | ||||
|         STATE_IDLE, | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     ExplorerPanelImpl(MainWindow* mainWindow, wxSimplebook* parent): | ||||
|         ExplorerPanelGen(parent), | ||||
|         ExplorerPanel(mainWindow) | ||||
|     { | ||||
|         parent->AddPage(this, "explorer"); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void Start() override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SetPage(MainWindow::PAGE_EXPLORER); | ||||
|             PrepareConfig(); | ||||
|  | ||||
|             SetState(STATE_IDLE); | ||||
|  | ||||
|             _explorerFluxmap = nullptr; | ||||
|             _explorerTrack = -1; | ||||
|             _explorerSide = -1; | ||||
|             _explorerUpdatePending = false; | ||||
|  | ||||
|             UpdateExplorerData(); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|             StartIdle(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void UpdateState() | ||||
|     { | ||||
|         explorerToolbar->EnableTool( | ||||
|             explorerBackTool->GetId(), _state == STATE_IDLE); | ||||
|         explorerToolbar->Refresh(); | ||||
|     } | ||||
|  | ||||
|     void OnBackButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartIdle(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void SetState(int state) | ||||
|     { | ||||
|         if (state != _state) | ||||
|         { | ||||
|             _state = state; | ||||
|             CallAfter( | ||||
|                 [&]() | ||||
|                 { | ||||
|                     UpdateState(); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void SwitchFrom() override | ||||
|     { | ||||
|         SetState(STATE_DEAD); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void OnExplorerSettingChange(wxSpinEvent& event) override | ||||
|     { | ||||
|         UpdateExplorerData(); | ||||
|     } | ||||
|  | ||||
|     void OnExplorerSettingChange(wxSpinDoubleEvent& event) override | ||||
|     { | ||||
|         UpdateExplorerData(); | ||||
|     } | ||||
|  | ||||
|     void OnExplorerSettingChange(wxCommandEvent& event) override | ||||
|     { | ||||
|         UpdateExplorerData(); | ||||
|     } | ||||
|  | ||||
|     void OnExplorerRefreshButton(wxCommandEvent& event) override | ||||
|     { | ||||
|         _explorerFluxmap = nullptr; | ||||
|         _explorerTrack = -1; | ||||
|         _explorerSide = -1; | ||||
|         _explorerUpdatePending = false; | ||||
|  | ||||
|         UpdateExplorerData(); | ||||
|     } | ||||
|  | ||||
|     void OnGuessClockButton(wxCommandEvent& event) override | ||||
|     { | ||||
|         nanoseconds_t clock = histogram->GetMedian(); | ||||
|         explorerClockSpinCtrl->SetValue(clock / 1e3); | ||||
|         UpdateExplorerData(); | ||||
|     } | ||||
|  | ||||
|     void OnQueueEmpty() override | ||||
|     { | ||||
|         SetState(STATE_IDLE); | ||||
|     } | ||||
|  | ||||
|     void QueueJob(std::function<void(void)> f) | ||||
|     { | ||||
|         SetState(STATE_WORKING); | ||||
|         JobQueue::QueueJob(f); | ||||
|     } | ||||
|  | ||||
|     void UpdateExplorerData() | ||||
|     { | ||||
|         if (!IsQueueEmpty()) | ||||
|         { | ||||
|             _explorerUpdatePending = true; | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _explorerUpdatePending = false; | ||||
|         QueueJob( | ||||
|             [this]() | ||||
|             { | ||||
|                 /* You need to call this if the config changes to invalidate | ||||
|                  * any caches. */ | ||||
|  | ||||
|                 Environment::reset(); | ||||
|  | ||||
|                 int desiredTrack = explorerTrackSpinCtrl->GetValue(); | ||||
|                 int desiredSide = explorerSideSpinCtrl->GetValue(); | ||||
|                 if (!_explorerFluxmap || (desiredTrack != _explorerTrack) || | ||||
|                     (desiredSide != _explorerSide)) | ||||
|                 { | ||||
|                     auto fluxSource = FluxSource::create(config.flux_source()); | ||||
|                     _explorerFluxmap = | ||||
|                         fluxSource->readFlux(desiredTrack, desiredSide)->next(); | ||||
|                     _explorerTrack = desiredTrack; | ||||
|                     _explorerSide = desiredSide; | ||||
|                 } | ||||
|  | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         _state = STATE_IDLE; | ||||
|                         UpdateState(); | ||||
|  | ||||
|                         FluxmapReader fmr(*_explorerFluxmap); | ||||
|                         fmr.seek(explorerStartTimeSpinCtrl->GetValue() * 1e6); | ||||
|  | ||||
|                         nanoseconds_t clock = | ||||
|                             explorerClockSpinCtrl->GetValue() * 1e3; | ||||
|                         FluxDecoder fluxDecoder(&fmr, clock, DecoderProto()); | ||||
|                         fluxDecoder.readBits( | ||||
|                             explorerBitOffsetSpinCtrl->GetValue()); | ||||
|                         auto bits = fluxDecoder.readBits(); | ||||
|  | ||||
|                         Bytes bytes; | ||||
|                         switch (explorerDecodeChoice->GetSelection()) | ||||
|                         { | ||||
| 							case 0: | ||||
| 								bytes = fakeBits(bits); | ||||
| 								break; | ||||
|  | ||||
|                             case 1: | ||||
|                                 bytes = toBytes(bits); | ||||
|                                 break; | ||||
|  | ||||
|                             case 2: | ||||
|                                 bytes = decodeFmMfm(bits.begin(), bits.end()); | ||||
|                                 break; | ||||
|                         } | ||||
|  | ||||
|                         if (explorerReverseCheckBox->GetValue()) | ||||
|                             bytes = bytes.reverseBits(); | ||||
|  | ||||
|                         std::stringstream s; | ||||
|                         hexdump(s, bytes); | ||||
|                         explorerText->SetValue(s.str()); | ||||
|  | ||||
|                         histogram->Redraw(*_explorerFluxmap, clock); | ||||
|  | ||||
|                         if (_explorerUpdatePending) | ||||
|                             UpdateExplorerData(); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     int _state = STATE_DEAD; | ||||
|     int _explorerTrack; | ||||
|     int _explorerSide; | ||||
|     bool _explorerUpdatePending; | ||||
|     std::unique_ptr<const Fluxmap> _explorerFluxmap; | ||||
| }; | ||||
|  | ||||
| ExplorerPanel* ExplorerPanel::Create( | ||||
|     MainWindow* mainWindow, wxSimplebook* parent) | ||||
| { | ||||
|     return new ExplorerPanelImpl(mainWindow, parent); | ||||
| } | ||||
							
								
								
									
										157
									
								
								src/gui/gui.h
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								src/gui/gui.h
									
									
									
									
									
								
							| @@ -4,11 +4,14 @@ | ||||
| #include <wx/wx.h> | ||||
|  | ||||
| class ExecEvent; | ||||
| class MainWindow; | ||||
| class DiskFlux; | ||||
| class TrackFlux; | ||||
| class wxSimplebook; | ||||
|  | ||||
| extern void postToUiThread(std::function<void()> callback); | ||||
| extern void runOnUiThread(std::function<void()> callback); | ||||
| extern void runOnWorkerThread(std::function<void()> callback); | ||||
| extern bool isWorkerThread(); | ||||
|  | ||||
| wxDECLARE_EVENT(UPDATE_STATE_EVENT, wxCommandEvent); | ||||
|  | ||||
| @@ -34,7 +37,8 @@ private: | ||||
|     void OnExec(const ExecEvent& event); | ||||
|  | ||||
| public: | ||||
|     bool IsWorkerThreadRunning() const; | ||||
|     bool IsWorkerThread(); | ||||
|     bool IsWorkerThreadRunning(); | ||||
|  | ||||
| protected: | ||||
|     virtual wxThread::ExitCode Entry(); | ||||
| @@ -54,4 +58,153 @@ wxDECLARE_APP(FluxEngineApp); | ||||
|     static const wxBrush name##_BRUSH(name##_COLOUR);      \ | ||||
|     static const wxPen name##_PEN(name##_COLOUR) | ||||
|  | ||||
| class CancelException | ||||
| { | ||||
| }; | ||||
|  | ||||
| class MainWindow | ||||
| { | ||||
| public: | ||||
|     enum | ||||
|     { | ||||
|         PAGE_IDLE, | ||||
|         PAGE_IMAGER, | ||||
|         PAGE_BROWSER, | ||||
|         PAGE_EXPLORER, | ||||
|     }; | ||||
|  | ||||
|     virtual void StartIdle() = 0; | ||||
|     virtual void StartReading() = 0; | ||||
|     virtual void StartWriting() = 0; | ||||
|     virtual void StartBrowsing() = 0; | ||||
|     virtual void StartFormatting() = 0; | ||||
|     virtual void StartExploring() = 0; | ||||
|  | ||||
| 	virtual void SafeFit() = 0; | ||||
|     virtual void SetPage(int page) = 0; | ||||
|     virtual void PrepareConfig() = 0; | ||||
|     virtual void ClearLog() = 0; | ||||
| }; | ||||
|  | ||||
| class PanelComponent | ||||
| { | ||||
| public: | ||||
|     PanelComponent(MainWindow* mainWindow): _mainWindow(mainWindow) {} | ||||
|  | ||||
|     virtual void SwitchTo(){}; | ||||
|     virtual void SwitchFrom(){}; | ||||
|  | ||||
|     void PrepareConfig() | ||||
|     { | ||||
|         _mainWindow->PrepareConfig(); | ||||
|     } | ||||
|  | ||||
|     void SetPage(int page) | ||||
|     { | ||||
|         _mainWindow->SetPage(page); | ||||
|     } | ||||
|  | ||||
|     void ClearLog() | ||||
|     { | ||||
|         _mainWindow->ClearLog(); | ||||
|     } | ||||
|  | ||||
| 	void SafeFit() | ||||
| 	{ | ||||
| 		_mainWindow->SafeFit(); | ||||
| 	} | ||||
|  | ||||
|     void StartIdle() | ||||
|     { | ||||
|         _mainWindow->StartIdle(); | ||||
|     } | ||||
|  | ||||
| 	void StartReading() | ||||
|     { | ||||
|         _mainWindow->StartReading(); | ||||
|     } | ||||
|  | ||||
|     void StartWriting() | ||||
|     { | ||||
|         _mainWindow->StartWriting(); | ||||
|     } | ||||
|  | ||||
|     void StartBrowsing() | ||||
|     { | ||||
|         _mainWindow->StartBrowsing(); | ||||
|     } | ||||
|  | ||||
|     void StartFormatting() | ||||
|     { | ||||
|         _mainWindow->StartFormatting(); | ||||
|     } | ||||
|  | ||||
|     void StartExploring() | ||||
|     { | ||||
|         _mainWindow->StartExploring(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     MainWindow* _mainWindow; | ||||
| }; | ||||
|  | ||||
| class IdlePanel : public PanelComponent | ||||
| { | ||||
| public: | ||||
|     static IdlePanel* Create(MainWindow* mainWindow, wxSimplebook* parent); | ||||
|     IdlePanel(MainWindow* mainWindow): PanelComponent(mainWindow) {} | ||||
|  | ||||
| public: | ||||
|     virtual void Start() = 0; | ||||
|  | ||||
|     virtual void PrepareConfig() = 0; | ||||
| 	virtual const wxBitmap GetBitmap() = 0; | ||||
| }; | ||||
|  | ||||
| class ImagerPanel : public PanelComponent | ||||
| { | ||||
| public: | ||||
|     static ImagerPanel* Create(MainWindow* mainWindow, wxSimplebook* parent); | ||||
|     ImagerPanel(MainWindow* mainWindow): PanelComponent(mainWindow) {} | ||||
|  | ||||
| public: | ||||
|     enum | ||||
|     { | ||||
|         STATE_READING, | ||||
|         STATE_READING_SUCCEEDED, | ||||
|         STATE_WRITING, | ||||
|         STATE_WRITING_SUCCEEDED, | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     virtual void StartReading() = 0; | ||||
|     virtual void StartWriting() = 0; | ||||
|  | ||||
|     virtual void SetVisualiserMode(int head, int track, int mode) = 0; | ||||
|     virtual void SetVisualiserTrackData( | ||||
|         std::shared_ptr<const TrackFlux> track) = 0; | ||||
|     virtual void SetDisk(std::shared_ptr<const DiskFlux> disk) = 0; | ||||
| }; | ||||
|  | ||||
| class BrowserPanel : public PanelComponent | ||||
| { | ||||
| public: | ||||
|     static BrowserPanel* Create(MainWindow* mainWindow, wxSimplebook* parent); | ||||
|     BrowserPanel(MainWindow* mainWindow): PanelComponent(mainWindow) {} | ||||
|  | ||||
| public: | ||||
|     virtual void StartBrowsing() = 0; | ||||
|     virtual void StartFormatting() = 0; | ||||
| }; | ||||
|  | ||||
| class ExplorerPanel : public PanelComponent | ||||
| { | ||||
| public: | ||||
|     static ExplorerPanel* Create(MainWindow* mainWindow, wxSimplebook* parent); | ||||
|     ExplorerPanel(MainWindow* mainWindow): PanelComponent(mainWindow) {} | ||||
|  | ||||
| public: | ||||
|     virtual void Start() = 0; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										88
									
								
								src/gui/histogramviewer.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/gui/histogramviewer.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "gui.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "histogramviewer.h" | ||||
|  | ||||
| static constexpr int BORDER = 10; | ||||
| static constexpr int WIDTH = 256; | ||||
| static constexpr int HEIGHT = 100; | ||||
|  | ||||
| // clang-format off | ||||
| wxBEGIN_EVENT_TABLE(HistogramViewer, wxWindow) | ||||
| 	EVT_PAINT(HistogramViewer::OnPaint) | ||||
| wxEND_EVENT_TABLE(); | ||||
| // clang-format on | ||||
|  | ||||
| HistogramViewer::HistogramViewer(wxWindow* parent, | ||||
|     wxWindowID winid, | ||||
|     const wxPoint& pos, | ||||
|     const wxSize& size, | ||||
|     long style): | ||||
|     wxWindow(parent, | ||||
|         winid, | ||||
|         pos, | ||||
|         wxSize(WIDTH + 2 * BORDER, HEIGHT + 2 * BORDER), | ||||
|         style) | ||||
| { | ||||
|     _font = GetFont().MakeSmaller().MakeSmaller().MakeSmaller(); | ||||
| } | ||||
|  | ||||
| void HistogramViewer::Redraw(const Fluxmap& fluxmap, nanoseconds_t clock) | ||||
| { | ||||
|     _data = fluxmap.guessClock(); | ||||
|     _clock = clock; | ||||
| 	_blank = false; | ||||
|     Refresh(); | ||||
| } | ||||
|  | ||||
| void HistogramViewer::OnPaint(wxPaintEvent&) | ||||
| { | ||||
|     wxPaintDC dc(this); | ||||
|     dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); | ||||
|     dc.Clear(); | ||||
|  | ||||
| 	if (_blank) | ||||
| 		return; | ||||
|  | ||||
|     uint32_t max = | ||||
|         *std::max_element(std::begin(_data.buckets), std::end(_data.buckets)); | ||||
|     dc.SetPen(*wxGREY_PEN); | ||||
|     for (int x = 0; x < 256; x++) | ||||
|     { | ||||
|         double v = (double)_data.buckets[x] / (double)max; | ||||
|         dc.DrawLine({BORDER + x, BORDER + HEIGHT}, | ||||
|             {BORDER + x, BORDER + HEIGHT - (int)(v * HEIGHT)}); | ||||
|     } | ||||
|  | ||||
|     dc.SetPen(*wxBLACK_PEN); | ||||
|     dc.DrawLine({BORDER, BORDER + HEIGHT}, {BORDER + WIDTH, BORDER + HEIGHT}); | ||||
|     dc.DrawLine({BORDER, BORDER + HEIGHT}, {BORDER, BORDER}); | ||||
|  | ||||
|     dc.SetPen(*wxRED_PEN); | ||||
|     { | ||||
|         int y = ((double)_data.signalLevel / (double)max) * HEIGHT; | ||||
|         dc.DrawLine({0, BORDER + HEIGHT - y}, | ||||
|             {2 * BORDER + WIDTH, BORDER + HEIGHT - y}); | ||||
|     } | ||||
|  | ||||
| 	if (_clock != 0.0) | ||||
|     { | ||||
|         int x = _clock / NS_PER_TICK; | ||||
|         dc.DrawLine({BORDER + x, 2 * BORDER + HEIGHT}, {BORDER + x, 0}); | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         wxString text = "Clock interval"; | ||||
|         dc.SetFont(_font); | ||||
|         auto size = dc.GetTextExtent(text); | ||||
|         dc.DrawText(text, {BORDER + WIDTH - size.GetWidth(), BORDER + HEIGHT}); | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         wxString text = "Frequency"; | ||||
|         dc.SetFont(_font); | ||||
|         auto size = dc.GetTextExtent(text); | ||||
|         dc.DrawRotatedText( | ||||
|             text, BORDER - size.GetHeight(), BORDER + size.GetWidth(), 90); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/gui/histogramviewer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/gui/histogramviewer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "lib/globals.h" | ||||
| #include "lib/fluxmap.h" | ||||
|  | ||||
| class HistogramViewer : public wxWindow | ||||
| { | ||||
| public: | ||||
|     HistogramViewer(wxWindow* parent, | ||||
|         wxWindowID winid, | ||||
|         const wxPoint& pos = wxDefaultPosition, | ||||
|         const wxSize& size = wxDefaultSize, | ||||
|         long style = 0); | ||||
|     virtual ~HistogramViewer() {} | ||||
|  | ||||
| public: | ||||
|     void Redraw(const Fluxmap& fluxmap, nanoseconds_t clock=0); | ||||
|  | ||||
|     void Redraw(const Fluxmap* fluxmap, nanoseconds_t clock=0) | ||||
| 	{ Redraw(*fluxmap, clock); } | ||||
|  | ||||
| 	nanoseconds_t GetMedian() const { return _data.median; } | ||||
|  | ||||
| private: | ||||
|     void OnPaint(wxPaintEvent&); | ||||
|  | ||||
| private: | ||||
| 	bool _blank = true; | ||||
| 	Fluxmap::ClockData _data; | ||||
| 	wxFont _font; | ||||
| 	nanoseconds_t _clock; | ||||
|     wxDECLARE_EVENT_TABLE(); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										1063
									
								
								src/gui/icon.png.h
									
									
									
									
									
								
							
							
						
						
									
										1063
									
								
								src/gui/icon.png.h
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										55
									
								
								src/gui/iconbutton.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/gui/iconbutton.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "gui.h" | ||||
| #include "iconbutton.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| IconButton::IconButton(wxWindow* parent, | ||||
|     wxWindowID id, | ||||
|     const wxPoint& pos, | ||||
|     const wxSize& size, | ||||
|     long style, | ||||
|     const wxString& name): | ||||
|     wxPanel(parent, id, pos, size, style, name) | ||||
| { | ||||
|     _sizer = new wxFlexGridSizer(1, 0, 0); | ||||
|     SetSizer(_sizer); | ||||
|  | ||||
|     _bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap); | ||||
|     _sizer->Add(_bitmap, 0, wxALL | wxEXPAND, 0, nullptr); | ||||
|  | ||||
|     _text = new wxStaticText(this, | ||||
|         wxID_ANY, | ||||
|         "", | ||||
|         wxDefaultPosition, | ||||
|         wxDefaultSize, | ||||
|         wxALIGN_CENTRE_HORIZONTAL); | ||||
|     _sizer->Add(_text, 0, wxALL | wxEXPAND, 0, nullptr); | ||||
|  | ||||
|     _text->SetFont(_text->GetFont().MakeSmaller().MakeSmaller().MakeSmaller()); | ||||
|  | ||||
|     Bind(wxEVT_LEFT_DOWN, &IconButton::OnMouseClick, this); | ||||
|     _bitmap->Bind(wxEVT_LEFT_DOWN, &IconButton::OnMouseClick, this); | ||||
|     _text->Bind(wxEVT_LEFT_DOWN, &IconButton::OnMouseClick, this); | ||||
| } | ||||
|  | ||||
| void IconButton::SetSelected(bool selected) | ||||
| { | ||||
|     _selected = selected; | ||||
|     wxColor bg = wxSystemSettings::GetColour( | ||||
|         _selected ? wxSYS_COLOUR_HIGHLIGHT : wxSYS_COLOUR_FRAMEBK); | ||||
|     SetBackgroundColour(bg); | ||||
|     Refresh(); | ||||
| } | ||||
|  | ||||
| void IconButton::OnMouseClick(wxMouseEvent&) | ||||
| { | ||||
|     auto* event = new wxCommandEvent(wxEVT_BUTTON, 0); | ||||
|     wxQueueEvent(this, event); | ||||
| } | ||||
|  | ||||
| void IconButton::SetBitmapAndLabel( | ||||
|     const wxBitmap bitmap, const std::string text) | ||||
| { | ||||
|     _bitmap->SetBitmap(bitmap); | ||||
|     _text->SetLabel(text); | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/gui/iconbutton.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/gui/iconbutton.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #pragma once | ||||
|  | ||||
| class wxToggleButton; | ||||
|  | ||||
| class IconButton : public wxPanel | ||||
| { | ||||
| public: | ||||
|     IconButton(wxWindow* parent, | ||||
|         wxWindowID id = wxID_ANY, | ||||
|         const wxPoint& pos = wxDefaultPosition, | ||||
|         const wxSize& size = wxDefaultSize, | ||||
|         long style = wxTAB_TRAVERSAL, | ||||
|         const wxString& name = wxPanelNameStr); | ||||
|  | ||||
|     void SetBitmapAndLabel(const wxBitmap bitmap, const std::string text); | ||||
|     void SetSelected(bool selected); | ||||
|  | ||||
| private: | ||||
|     void OnMouseClick(wxMouseEvent& e); | ||||
|  | ||||
| private: | ||||
|     wxFlexGridSizer* _sizer; | ||||
|     wxStaticBitmap* _bitmap; | ||||
|     wxStaticText* _text; | ||||
|     bool _selected; | ||||
| }; | ||||
							
								
								
									
										700
									
								
								src/gui/idlepanel.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										700
									
								
								src/gui/idlepanel.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,700 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "gui.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/usb/usbfinder.h" | ||||
| #include "lib/proto.h" | ||||
| #include "lib/flags.h" | ||||
| #include "lib/utils.h" | ||||
| #include "lib/fluxsource/fluxsource.h" | ||||
| #include "lib/fluxsink/fluxsink.h" | ||||
| #include "lib/imagereader/imagereader.h" | ||||
| #include "lib/imagewriter/imagewriter.h" | ||||
| #include "layout.h" | ||||
| #include "texteditorwindow.h" | ||||
| #include "iconbutton.h" | ||||
| #include <wx/config.h> | ||||
| #include <wx/mstream.h> | ||||
| #include <wx/image.h> | ||||
| #include <wx/bitmap.h> | ||||
| #include ".obj/extras/hardware.h" | ||||
| #include ".obj/extras/fluxfile.h" | ||||
| #include ".obj/extras/imagefile.h" | ||||
|  | ||||
| extern const std::map<std::string, std::string> formats; | ||||
|  | ||||
| #define CONFIG_SELECTEDSOURCE "SelectedSource" | ||||
| #define CONFIG_DEVICE "Device" | ||||
| #define CONFIG_DRIVE "Drive" | ||||
| #define CONFIG_FORTYTRACK "FortyTrack" | ||||
| #define CONFIG_HIGHDENSITY "HighDensity" | ||||
| #define CONFIG_FORMAT "Format" | ||||
| #define CONFIG_FORMATOPTIONS "FormatOptions" | ||||
| #define CONFIG_EXTRACONFIG "ExtraConfig" | ||||
| #define CONFIG_FLUXIMAGE "FluxImage" | ||||
| #define CONFIG_DISKIMAGE "DiskImage" | ||||
|  | ||||
| const std::string DEFAULT_EXTRA_CONFIGURATION = | ||||
|     "# Place any extra configuration here.\n" | ||||
|     "# Each line can contain a key=value pair to set a property,\n" | ||||
|     "# or the name of a built-in configuration, or the filename\n" | ||||
|     "# of a text proto file. Or a comment, of course.\n\n"; | ||||
|  | ||||
| wxDEFINE_EVENT(PAGE_SELECTED_EVENT, wxCommandEvent); | ||||
|  | ||||
| static wxBitmap createBitmap(const uint8_t* data, size_t length) | ||||
| { | ||||
|     wxMemoryInputStream stream(data, length); | ||||
|     wxImage image(stream, wxBITMAP_TYPE_PNG); | ||||
|     return wxBitmap(image); | ||||
| } | ||||
|  | ||||
| class IdlePanelImpl : public IdlePanelGen, public IdlePanel | ||||
| { | ||||
|     enum | ||||
|     { | ||||
|         SELECTEDSOURCE_REAL, | ||||
|         SELECTEDSOURCE_FLUX, | ||||
|         SELECTEDSOURCE_IMAGE | ||||
|     }; | ||||
|  | ||||
|     enum | ||||
|     { | ||||
|         ICON_HARDWARE, | ||||
|         ICON_FLUXFILE, | ||||
|         ICON_IMAGEFILE | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     IdlePanelImpl(MainWindow* mainWindow, wxSimplebook* parent): | ||||
|         IdlePanelGen(parent), | ||||
|         IdlePanel(mainWindow), | ||||
|         _imageList(48, 48, true, 0), | ||||
|         _config("FluxEngine") | ||||
|     { | ||||
|         int defaultFormat = 0; | ||||
|         int i = 0; | ||||
|         for (const auto& it : formats) | ||||
|         { | ||||
|             auto config = std::make_unique<ConfigProto>(); | ||||
|             if (!config->ParseFromString(it.second)) | ||||
|                 continue; | ||||
|             if (config->is_extension()) | ||||
|                 continue; | ||||
|  | ||||
|             formatChoice->Append(it.first); | ||||
|             _formatNames.push_back(it.first); | ||||
|             i++; | ||||
|         } | ||||
|  | ||||
|         LoadConfig(); | ||||
|         UpdateDevices(); | ||||
|         UpdateFormatOptions(); | ||||
|  | ||||
|         parent->AddPage(this, "idle"); | ||||
|  | ||||
|         _imageList.Add( | ||||
|             createBitmap(extras_hardware_png, sizeof(extras_hardware_png))); | ||||
|         _imageList.Add( | ||||
|             createBitmap(extras_fluxfile_png, sizeof(extras_fluxfile_png))); | ||||
|         _imageList.Add( | ||||
|             createBitmap(extras_imagefile_png, sizeof(extras_imagefile_png))); | ||||
|  | ||||
|         UpdateSources(); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void Start() override | ||||
|     { | ||||
|         SetPage(MainWindow::PAGE_IDLE); | ||||
|     } | ||||
|  | ||||
|     void UpdateState() | ||||
|     { | ||||
|         bool hasFormat = formatChoice->GetSelection() != wxNOT_FOUND; | ||||
|  | ||||
|         readButton->Enable( | ||||
|             (_selectedSource != SELECTEDSOURCE_IMAGE) && hasFormat); | ||||
|         writeButton->Enable( | ||||
|             (_selectedSource == SELECTEDSOURCE_REAL) && hasFormat); | ||||
|         browseButton->Enable(hasFormat); | ||||
|         formatButton->Enable(hasFormat); | ||||
|         exploreButton->Enable(_selectedSource != SELECTEDSOURCE_IMAGE); | ||||
|  | ||||
|         UpdateFormatOptions(); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void OnReadButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartReading(); | ||||
|     } | ||||
|  | ||||
|     void OnWriteButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartWriting(); | ||||
|     } | ||||
|  | ||||
|     void OnBrowseButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartBrowsing(); | ||||
|     } | ||||
|  | ||||
|     void OnFormatButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartFormatting(); | ||||
|     } | ||||
|  | ||||
|     void OnExploreButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartExploring(); | ||||
|     } | ||||
|  | ||||
|     void OnControlsChanged(wxCommandEvent& event) override | ||||
|     { | ||||
|         SaveConfig(); | ||||
|         UpdateState(); | ||||
|         UpdateFormatOptions(); | ||||
|     } | ||||
|  | ||||
|     void OnControlsChanged(wxFileDirPickerEvent& event) | ||||
|     { | ||||
|         wxCommandEvent e; | ||||
|         OnControlsChanged(e); | ||||
|     } | ||||
|  | ||||
|     void OnCustomConfigurationButton(wxCommandEvent& event) override | ||||
|     { | ||||
|         auto* editor = TextEditorWindow::Create( | ||||
|             this, "Configuration editor", _extraConfiguration); | ||||
|         editor->Bind(EDITOR_SAVE_EVENT, | ||||
|             [this](auto& event) | ||||
|             { | ||||
|                 _extraConfiguration = event.text; | ||||
|                 SaveConfig(); | ||||
|             }); | ||||
|         editor->Show(); | ||||
|     } | ||||
|  | ||||
|     void PrepareConfig() override | ||||
|     { | ||||
|         assert(!wxGetApp().IsWorkerThreadRunning()); | ||||
|  | ||||
|         auto formatSelection = formatChoice->GetSelection(); | ||||
|         if (formatSelection == wxNOT_FOUND) | ||||
|             Error() << "no format selected"; | ||||
|  | ||||
|         config.Clear(); | ||||
|         auto formatName = _formatNames[formatChoice->GetSelection()]; | ||||
|         FlagGroup::parseConfigFile(formatName, formats); | ||||
|  | ||||
|         /* Apply any format options. */ | ||||
|  | ||||
|         for (const auto& e : _formatOptions) | ||||
|         { | ||||
|             if (e.first == formatName) | ||||
|                 FlagGroup::applyOption(e.second); | ||||
|         } | ||||
|  | ||||
|         /* Merge in any custom config. */ | ||||
|  | ||||
|         for (auto setting : split(_extraConfiguration, '\n')) | ||||
|         { | ||||
|             setting = trimWhitespace(setting); | ||||
|             if (setting.size() == 0) | ||||
|                 continue; | ||||
|             if (setting[0] == '#') | ||||
|                 continue; | ||||
|  | ||||
|             auto equals = setting.find('='); | ||||
|             if (equals != std::string::npos) | ||||
|             { | ||||
|                 auto key = setting.substr(0, equals); | ||||
|                 auto value = setting.substr(equals + 1); | ||||
|                 setProtoByString(&config, key, value); | ||||
|             } | ||||
|             else | ||||
|                 FlagGroup::parseConfigFile(setting, formats); | ||||
|         } | ||||
|  | ||||
|         /* Locate the device, if any. */ | ||||
|  | ||||
|         auto serial = _selectedDevice; | ||||
|         if (!serial.empty() && (serial[0] == '/')) | ||||
|             setProtoByString(&config, "usb.greaseweazle.port", serial); | ||||
|         else | ||||
|             setProtoByString(&config, "usb.serial", serial); | ||||
|  | ||||
|         ClearLog(); | ||||
|  | ||||
|         /* Apply the source/destination. */ | ||||
|  | ||||
|         switch (_selectedSource) | ||||
|         { | ||||
|             case SELECTEDSOURCE_REAL: | ||||
|             { | ||||
|                 config.mutable_drive()->set_high_density(_selectedHighDensity); | ||||
|  | ||||
|                 if (_selectedFortyTrack) | ||||
|                     FlagGroup::parseConfigFile("40track_drive", formats); | ||||
|  | ||||
|                 std::string filename = _selectedDrive ? "drive:1" : "drive:0"; | ||||
|                 FluxSink::updateConfigForFilename( | ||||
|                     config.mutable_flux_sink(), filename); | ||||
|                 FluxSource::updateConfigForFilename( | ||||
|                     config.mutable_flux_source(), filename); | ||||
|  | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             case SELECTEDSOURCE_FLUX: | ||||
|             { | ||||
|                 FluxSink::updateConfigForFilename( | ||||
|                     config.mutable_flux_sink(), _selectedFluxfilename); | ||||
|                 FluxSource::updateConfigForFilename( | ||||
|                     config.mutable_flux_source(), _selectedFluxfilename); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             case SELECTEDSOURCE_IMAGE: | ||||
|             { | ||||
|                 ImageReader::updateConfigForFilename( | ||||
|                     config.mutable_image_reader(), _selectedImagefilename); | ||||
|                 ImageWriter::updateConfigForFilename( | ||||
|                     config.mutable_image_writer(), _selectedImagefilename); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const wxBitmap GetBitmap() override | ||||
|     { | ||||
|         return applicationBitmap->GetBitmap(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void LoadConfig() | ||||
|     { | ||||
|         /* Prevent saving the config half-way through reloading it when the | ||||
|          * widget states all change. */ | ||||
|  | ||||
|         _dontSaveConfig = true; | ||||
|  | ||||
|         /* Radio button config. */ | ||||
|  | ||||
|         wxString s = std::to_string(SELECTEDSOURCE_IMAGE); | ||||
|         _config.Read(CONFIG_SELECTEDSOURCE, &s); | ||||
|         _selectedSource = std::atoi(s.c_str()); | ||||
|  | ||||
|         /* Real disk block. */ | ||||
|  | ||||
|         s = ""; | ||||
|         _config.Read(CONFIG_DEVICE, &s); | ||||
|         _selectedDevice = s; | ||||
|  | ||||
|         s = "0"; | ||||
|         _config.Read(CONFIG_DRIVE, &s); | ||||
|         _selectedDrive = wxAtoi(s); | ||||
|  | ||||
|         s = "0"; | ||||
|         _config.Read(CONFIG_HIGHDENSITY, &s); | ||||
|         _selectedHighDensity = wxAtoi(s); | ||||
|  | ||||
|         s = "0"; | ||||
|         _config.Read(CONFIG_FORTYTRACK, &s); | ||||
|         _selectedFortyTrack = wxAtoi(s); | ||||
|  | ||||
|         /* Flux image block. */ | ||||
|  | ||||
|         s = ""; | ||||
|         _config.Read(CONFIG_FLUXIMAGE, &s); | ||||
|         _selectedFluxfilename = s; | ||||
|  | ||||
|         /* Disk image block. */ | ||||
|  | ||||
|         s = ""; | ||||
|         _config.Read(CONFIG_DISKIMAGE, &s); | ||||
|         _selectedImagefilename = s; | ||||
|  | ||||
|         /* Format block. */ | ||||
|  | ||||
|         s = "ibm"; | ||||
|         _config.Read(CONFIG_FORMAT, &s); | ||||
|  | ||||
|         int defaultFormat = 0; | ||||
|         int i = 0; | ||||
|         for (const auto& it : _formatNames) | ||||
|         { | ||||
|             if (it == s) | ||||
|             { | ||||
|                 formatChoice->SetSelection(i); | ||||
|                 break; | ||||
|             } | ||||
|             i++; | ||||
|         } | ||||
|  | ||||
|         s = DEFAULT_EXTRA_CONFIGURATION; | ||||
|         _config.Read(CONFIG_EXTRACONFIG, &s); | ||||
|         _extraConfiguration = s; | ||||
|  | ||||
|         /* Format options. */ | ||||
|  | ||||
|         _formatOptions.clear(); | ||||
|         s = ""; | ||||
|         _config.Read(CONFIG_FORMATOPTIONS, &s); | ||||
|         for (auto combined : split(std::string(s), ',')) | ||||
|         { | ||||
|             auto pair = split(combined, ':'); | ||||
|             try | ||||
|             { | ||||
|                 auto key = std::make_pair(pair.at(0), pair.at(1)); | ||||
|                 _formatOptions.insert(key); | ||||
|             } | ||||
|             catch (std::exception&) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Triggers SaveConfig */ | ||||
|  | ||||
|         _dontSaveConfig = false; | ||||
|     } | ||||
|  | ||||
|     void SaveConfig() | ||||
|     { | ||||
|         if (_dontSaveConfig) | ||||
|             return; | ||||
|  | ||||
|         _config.Write( | ||||
|             CONFIG_SELECTEDSOURCE, wxString(std::to_string(_selectedSource))); | ||||
|  | ||||
|         /* Real disk block. */ | ||||
|  | ||||
|         _config.Write(CONFIG_DEVICE, wxString(_selectedDevice)); | ||||
|         _config.Write(CONFIG_DRIVE, wxString(std::to_string(_selectedDrive))); | ||||
|         _config.Write( | ||||
|             CONFIG_HIGHDENSITY, wxString(std::to_string(_selectedHighDensity))); | ||||
|         _config.Write( | ||||
|             CONFIG_FORTYTRACK, wxString(std::to_string(_selectedFortyTrack))); | ||||
|  | ||||
|         /* Flux image block. */ | ||||
|  | ||||
|         _config.Write(CONFIG_FLUXIMAGE, wxString(_selectedFluxfilename)); | ||||
|  | ||||
|         /* Disk image block. */ | ||||
|  | ||||
|         _config.Write(CONFIG_DISKIMAGE, wxString(_selectedImagefilename)); | ||||
|  | ||||
|         /* Format block. */ | ||||
|  | ||||
|         _config.Write(CONFIG_FORMAT, | ||||
|             formatChoice->GetString(formatChoice->GetSelection())); | ||||
|         _config.Write(CONFIG_EXTRACONFIG, wxString(_extraConfiguration)); | ||||
|  | ||||
|         /* Format options. */ | ||||
|  | ||||
|         { | ||||
|             std::vector<std::string> options; | ||||
|             for (auto& e : _formatOptions) | ||||
|                 options.push_back(fmt::format("{}:{}", e.first, e.second)); | ||||
|  | ||||
|             _config.Write(CONFIG_FORMATOPTIONS, wxString(join(options, ","))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void UpdateSources() | ||||
|     { | ||||
|         sourceBook->DeleteAllPages(); | ||||
|         sourceIconPanel->DestroyChildren(); | ||||
|  | ||||
|         int page = 0; | ||||
|         bool switched = false; | ||||
|         for (auto& device : _devices) | ||||
|         { | ||||
|             for (int drive = 0; drive <= 1; drive++) | ||||
|             { | ||||
|                 auto* panel = new HardwareSourcePanelGen(sourceBook); | ||||
|                 sourceBook->AddPage(panel, ""); | ||||
|                 panel->label->SetLabel( | ||||
|                     fmt::format("{}; serial number {}; drive:{}", | ||||
|                         getDeviceName(device->type), | ||||
|                         device->serial, | ||||
|                         drive)); | ||||
|  | ||||
|                 auto* button = AddIcon(ICON_HARDWARE, | ||||
|                     fmt::format( | ||||
|                         "{}\ndrive:{}", device->serial.substr(0, 10), drive)); | ||||
|                 button->Bind(wxEVT_BUTTON, | ||||
|                     [=](auto& e) | ||||
|                     { | ||||
|                         SwitchToPage(page); | ||||
|  | ||||
|                         _selectedSource = SELECTEDSOURCE_REAL; | ||||
|                         _selectedDevice = device->serial; | ||||
|                         _selectedDrive = drive; | ||||
|                         OnControlsChanged(e); | ||||
|                     }); | ||||
|  | ||||
|                 panel->highDensityToggle->SetValue(_selectedHighDensity); | ||||
|                 panel->highDensityToggle->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, | ||||
|                     [=](wxCommandEvent& e) | ||||
|                     { | ||||
|                         _selectedHighDensity = | ||||
|                             panel->highDensityToggle->GetValue(); | ||||
|                         OnControlsChanged(e); | ||||
|                     }); | ||||
|  | ||||
|                 panel->fortyTrackDriveToggle->SetValue(_selectedFortyTrack); | ||||
|                 panel->fortyTrackDriveToggle->Bind( | ||||
|                     wxEVT_COMMAND_CHECKBOX_CLICKED, | ||||
|                     [=](wxCommandEvent& e) | ||||
|                     { | ||||
|                         _selectedFortyTrack = | ||||
|                             panel->fortyTrackDriveToggle->GetValue(); | ||||
|                         OnControlsChanged(e); | ||||
|                     }); | ||||
|  | ||||
|                 if ((_selectedSource == SELECTEDSOURCE_REAL) && | ||||
|                     (_selectedDevice == device->serial) && | ||||
|                     (_selectedDrive == drive)) | ||||
|                 { | ||||
|                     SwitchToPage(page); | ||||
|                     switched = true; | ||||
|                 } | ||||
|  | ||||
|                 page++; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             auto* panel = new FluxfileSourcePanelGen(sourceBook); | ||||
|             sourceBook->AddPage(panel, ""); | ||||
|  | ||||
|             auto* button = AddIcon(ICON_FLUXFILE, "Flux file"); | ||||
|             button->Bind(wxEVT_BUTTON, | ||||
|                 [=](auto& e) | ||||
|                 { | ||||
|                     SwitchToPage(page); | ||||
|  | ||||
|                     _selectedSource = SELECTEDSOURCE_FLUX; | ||||
|                     OnControlsChanged(e); | ||||
|                 }); | ||||
|  | ||||
|             panel->fluxImagePicker->SetPath(_selectedFluxfilename); | ||||
|             panel->fluxImagePicker->Bind(wxEVT_COMMAND_FILEPICKER_CHANGED, | ||||
|                 [=](wxFileDirPickerEvent& e) | ||||
|                 { | ||||
|                     _selectedFluxfilename = e.GetPath(); | ||||
|                     OnControlsChanged(e); | ||||
|                 }); | ||||
|  | ||||
|             if (_selectedSource == SELECTEDSOURCE_FLUX) | ||||
|             { | ||||
|                 SwitchToPage(page); | ||||
|                 switched = true; | ||||
|             } | ||||
|  | ||||
|             page++; | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             auto* panel = new ImagefileSourcePanelGen(sourceBook); | ||||
|             sourceBook->AddPage(panel, ""); | ||||
|  | ||||
|             auto* button = AddIcon(ICON_IMAGEFILE, "Disk image"); | ||||
|             button->Bind(wxEVT_BUTTON, | ||||
|                 [=](auto& e) | ||||
|                 { | ||||
|                     SwitchToPage(page); | ||||
|  | ||||
|                     _selectedSource = SELECTEDSOURCE_IMAGE; | ||||
|                     OnControlsChanged(e); | ||||
|                 }); | ||||
|  | ||||
|             panel->diskImagePicker->SetPath(_selectedImagefilename); | ||||
|             panel->diskImagePicker->Bind(wxEVT_COMMAND_FILEPICKER_CHANGED, | ||||
|                 [=](wxFileDirPickerEvent& e) | ||||
|                 { | ||||
|                     _selectedImagefilename = e.GetPath(); | ||||
|                     OnControlsChanged(e); | ||||
|                 }); | ||||
|  | ||||
|             if (_selectedSource == SELECTEDSOURCE_IMAGE) | ||||
|             { | ||||
|                 SwitchToPage(page); | ||||
|                 switched = true; | ||||
|             } | ||||
|  | ||||
|             page++; | ||||
|         } | ||||
|  | ||||
|         Fit(); | ||||
|         Layout(); | ||||
|         if (!switched) | ||||
|             SwitchToPage(0); | ||||
|     } | ||||
|  | ||||
|     IconButton* AddIcon(int bitmapIndex, const std::string text) | ||||
|     { | ||||
|         auto* button = new IconButton(sourceIconPanel, wxID_ANY); | ||||
|         button->SetBitmapAndLabel(_imageList.GetBitmap(bitmapIndex), text); | ||||
|         sourceIconPanel->GetSizer()->Add( | ||||
|             button, 0, wxALL | wxEXPAND, 5, nullptr); | ||||
|         return button; | ||||
|     } | ||||
|  | ||||
|     void SwitchToPage(int page) | ||||
|     { | ||||
|         int i = 0; | ||||
|         for (auto* window : sourceIconPanel->GetChildren()) | ||||
|         { | ||||
|             IconButton* button = dynamic_cast<IconButton*>(window); | ||||
|             if (button) | ||||
|                 button->SetSelected(i == page); | ||||
|             i++; | ||||
|         } | ||||
|  | ||||
|         sourceBook->ChangeSelection(page); | ||||
|     } | ||||
|  | ||||
|     void UpdateFormatOptions() | ||||
|     { | ||||
|         assert(!wxGetApp().IsWorkerThreadRunning()); | ||||
|  | ||||
|         int formatSelection = formatChoice->GetSelection(); | ||||
|         if (formatSelection != _currentlyDisplayedFormat) | ||||
|         { | ||||
|             _currentlyDisplayedFormat = formatSelection; | ||||
|             formatOptionsContainer->DestroyChildren(); | ||||
|             auto* sizer = new wxBoxSizer(wxVERTICAL); | ||||
|  | ||||
|             if (formatSelection == wxNOT_FOUND) | ||||
|                 sizer->Add(new wxStaticText( | ||||
|                     formatOptionsContainer, wxID_ANY, "(no format selected)")); | ||||
|             else | ||||
|             { | ||||
|                 config.Clear(); | ||||
|                 std::string formatName = | ||||
|                     _formatNames[formatChoice->GetSelection()]; | ||||
|                 FlagGroup::parseConfigFile(formatName, formats); | ||||
|  | ||||
|                 std::set<std::string> exclusivityGroups; | ||||
|                 for (auto& option : config.option()) | ||||
|                 { | ||||
|                     if (option.has_exclusivity_group()) | ||||
|                         exclusivityGroups.insert(option.exclusivity_group()); | ||||
|                 } | ||||
|  | ||||
|                 if (config.option().empty()) | ||||
|                     sizer->Add(new wxStaticText(formatOptionsContainer, | ||||
|                         wxID_ANY, | ||||
|                         "(no options for this format)")); | ||||
|                 else | ||||
|                 { | ||||
|                     /* Add grouped radiobuttons for anything in an exclusivity | ||||
|                      * group. */ | ||||
|  | ||||
|                     for (auto& group : exclusivityGroups) | ||||
|                     { | ||||
|                         bool first = true; | ||||
|                         for (auto& option : config.option()) | ||||
|                         { | ||||
|                             if (option.exclusivity_group() != group) | ||||
|                                 continue; | ||||
|  | ||||
|                             auto* rb = new wxRadioButton(formatOptionsContainer, | ||||
|                                 wxID_ANY, | ||||
|                                 option.comment()); | ||||
|                             auto key = | ||||
|                                 std::make_pair(formatName, option.name()); | ||||
|                             sizer->Add(rb); | ||||
|  | ||||
|                             rb->Bind(wxEVT_RADIOBUTTON, | ||||
|                                 [=](wxCommandEvent& e) | ||||
|                                 { | ||||
|                                     for (auto& option : config.option()) | ||||
|                                     { | ||||
|                                         if (option.exclusivity_group() == group) | ||||
|                                             _formatOptions.erase(std::make_pair( | ||||
|                                                 formatName, option.name())); | ||||
|                                     } | ||||
|  | ||||
|                                     _formatOptions.insert(key); | ||||
|  | ||||
|                                     OnControlsChanged(e); | ||||
|                                 }); | ||||
|  | ||||
|                             if (_formatOptions.find(key) != | ||||
|                                 _formatOptions.end()) | ||||
|                                 rb->SetValue(true); | ||||
|  | ||||
|                             if (first) | ||||
|                                 rb->SetExtraStyle(wxRB_GROUP); | ||||
|                             first = false; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     /* Anything that's _not_ in a group gets a checkbox. */ | ||||
|  | ||||
|                     for (auto& option : config.option()) | ||||
|                     { | ||||
|                         if (option.has_exclusivity_group()) | ||||
|                             continue; | ||||
|  | ||||
|                         auto* choice = new wxCheckBox( | ||||
|                             formatOptionsContainer, wxID_ANY, option.comment()); | ||||
|                         auto key = std::make_pair(formatName, option.name()); | ||||
|                         sizer->Add(choice); | ||||
|  | ||||
|                         if (_formatOptions.find(key) != _formatOptions.end()) | ||||
|                             choice->SetValue(true); | ||||
|  | ||||
|                         choice->Bind(wxEVT_CHECKBOX, | ||||
|                             [=](wxCommandEvent& e) | ||||
|                             { | ||||
|                                 if (choice->GetValue()) | ||||
|                                     _formatOptions.insert(key); | ||||
|                                 else | ||||
|                                     _formatOptions.erase(key); | ||||
|  | ||||
|                                 OnControlsChanged(e); | ||||
|                             }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             formatOptionsContainer->SetSizerAndFit(sizer); | ||||
|             Layout(); | ||||
|             SafeFit(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void UpdateDevices() | ||||
|     { | ||||
|         auto candidates = findUsbDevices(); | ||||
|         for (auto& candidate : candidates) | ||||
|             _devices.push_back(candidate); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     wxConfig _config; | ||||
|     wxImageList _imageList; | ||||
|     ConfigProto _configProto; | ||||
|     std::vector<std::string> _formatNames; | ||||
|     std::vector<std::shared_ptr<const CandidateDevice>> _devices; | ||||
|     int _selectedSource; | ||||
|     std::string _selectedDevice; | ||||
|     int _selectedDrive; | ||||
|     bool _selectedFortyTrack; | ||||
|     bool _selectedHighDensity; | ||||
|     std::string _selectedFluxfilename; | ||||
|     std::string _selectedImagefilename; | ||||
|     bool _dontSaveConfig = false; | ||||
|     std::string _extraConfiguration; | ||||
|     std::set<std::pair<std::string, std::string>> _formatOptions; | ||||
|     int _currentlyDisplayedFormat = wxNOT_FOUND - 1; | ||||
| }; | ||||
|  | ||||
| IdlePanel* IdlePanel::Create(MainWindow* mainWindow, wxSimplebook* parent) | ||||
| { | ||||
|     return new IdlePanelImpl(mainWindow, parent); | ||||
| } | ||||
							
								
								
									
										327
									
								
								src/gui/imagerpanel.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								src/gui/imagerpanel.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,327 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/environment.h" | ||||
| #include "lib/fluxsource/fluxsource.h" | ||||
| #include "lib/fluxsink/fluxsink.h" | ||||
| #include "lib/imagereader/imagereader.h" | ||||
| #include "lib/imagewriter/imagewriter.h" | ||||
| #include "lib/encoders/encoders.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/proto.h" | ||||
| #include "lib/readerwriter.h" | ||||
| #include "gui.h" | ||||
| #include "layout.h" | ||||
| #include "fluxviewerwindow.h" | ||||
| #include "jobqueue.h" | ||||
|  | ||||
| class ImagerPanelImpl : public ImagerPanelGen, public ImagerPanel, JobQueue | ||||
| { | ||||
| private: | ||||
|     enum | ||||
|     { | ||||
|         STATE_DEAD, | ||||
|         STATE_READING_WORKING, | ||||
|         STATE_READING_SUCCEEDED, | ||||
|         STATE_READING_FAILED, | ||||
|         STATE_WRITING_WORKING, | ||||
|         STATE_WRITING_SUCCEEDED, | ||||
|         STATE_WRITING_FAILED, | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     ImagerPanelImpl(MainWindow* mainWindow, wxSimplebook* parent): | ||||
|         ImagerPanelGen(parent), | ||||
|         ImagerPanel(mainWindow) | ||||
|     { | ||||
|         visualiser->Bind( | ||||
|             TRACK_SELECTION_EVENT, &ImagerPanelImpl::OnTrackSelection, this); | ||||
|  | ||||
|         parent->AddPage(this, "imager"); | ||||
|     } | ||||
|  | ||||
|     void OnBackButton(wxCommandEvent&) override | ||||
|     { | ||||
|         StartIdle(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void SetState(int state) | ||||
|     { | ||||
|         if (state != _state) | ||||
|         { | ||||
|             _state = state; | ||||
|             CallAfter( | ||||
|                 [&]() | ||||
|                 { | ||||
|                     UpdateState(); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void SwitchFrom() override | ||||
|     { | ||||
|         SetState(STATE_DEAD); | ||||
|     } | ||||
|  | ||||
|     void OnQueueEmpty() override | ||||
|     { | ||||
|         if (_state == STATE_READING_WORKING) | ||||
|             _state = STATE_READING_SUCCEEDED; | ||||
|         else if (_state == STATE_WRITING_WORKING) | ||||
|             _state = STATE_WRITING_SUCCEEDED; | ||||
|         UpdateState(); | ||||
|     } | ||||
|  | ||||
|     void OnQueueFailed() override | ||||
|     { | ||||
|         fmt::print("queue failed\n"); | ||||
|         if (_state == STATE_READING_WORKING) | ||||
|             _state = STATE_READING_FAILED; | ||||
|         else if (_state == STATE_WRITING_WORKING) | ||||
|             _state = STATE_WRITING_FAILED; | ||||
|         UpdateState(); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void StartReading() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SetPage(MainWindow::PAGE_IMAGER); | ||||
|             PrepareConfig(); | ||||
|  | ||||
|             visualiser->Clear(); | ||||
|             _currentDisk = nullptr; | ||||
|  | ||||
|             SetState(STATE_READING_WORKING); | ||||
|             QueueJob( | ||||
|                 [this]() | ||||
|                 { | ||||
|                     /* You need to call this if the config changes to invalidate | ||||
|                      * any caches. */ | ||||
|  | ||||
|                     Environment::reset(); | ||||
|  | ||||
|                     auto fluxSource = FluxSource::create(config.flux_source()); | ||||
|                     auto decoder = Decoder::create(config.decoder()); | ||||
|                     auto diskflux = readDiskCommand(*fluxSource, *decoder); | ||||
|  | ||||
|                     runOnUiThread( | ||||
|                         [&]() | ||||
|                         { | ||||
|                             visualiser->SetDiskData(diskflux); | ||||
|                         }); | ||||
|                 }); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|             if (_state == STATE_READING_WORKING) | ||||
|                 SetState(STATE_READING_FAILED); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void StartWriting() override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             SetPage(MainWindow::PAGE_IMAGER); | ||||
|             PrepareConfig(); | ||||
|             if (!config.has_image_reader()) | ||||
|                 Error() << "This format cannot be read from images."; | ||||
|  | ||||
|             auto filename = wxFileSelector("Choose a image file to read", | ||||
|                 /* default_path= */ wxEmptyString, | ||||
|                 /* default_filename= */ config.image_reader().filename(), | ||||
|                 /* default_extension= */ wxEmptyString, | ||||
|                 /* wildcard= */ wxEmptyString, | ||||
|                 /* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||||
|             if (filename.empty()) | ||||
|                 return; | ||||
|  | ||||
|             ImageReader::updateConfigForFilename( | ||||
|                 config.mutable_image_reader(), filename.ToStdString()); | ||||
|             ImageWriter::updateConfigForFilename( | ||||
|                 config.mutable_image_writer(), filename.ToStdString()); | ||||
|             visualiser->Clear(); | ||||
|             _currentDisk = nullptr; | ||||
|  | ||||
|             SetState(STATE_WRITING_WORKING); | ||||
|             QueueJob( | ||||
|                 [this]() | ||||
|                 { | ||||
|                     auto image = ImageReader::create(config.image_reader()) | ||||
|                                      ->readMappedImage(); | ||||
|                     auto encoder = Encoder::create(config.encoder()); | ||||
|                     auto fluxSink = FluxSink::create(config.flux_sink()); | ||||
|  | ||||
|                     std::unique_ptr<Decoder> decoder; | ||||
|                     std::unique_ptr<FluxSource> fluxSource; | ||||
|                     if (config.has_decoder()) | ||||
|                     { | ||||
|                         decoder = Decoder::create(config.decoder()); | ||||
|                         fluxSource = FluxSource::create(config.flux_source()); | ||||
|                     } | ||||
|  | ||||
|                     writeDiskCommand(*image, | ||||
|                         *encoder, | ||||
|                         *fluxSink, | ||||
|                         decoder.get(), | ||||
|                         fluxSource.get()); | ||||
|                 }); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|             if (_state == STATE_WRITING_WORKING) | ||||
|                 SetState(STATE_WRITING_FAILED); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void UpdateState() | ||||
|     { | ||||
|         switch (_state) | ||||
|         { | ||||
|             case STATE_READING_WORKING: | ||||
|             case STATE_READING_SUCCEEDED: | ||||
|             case STATE_READING_FAILED: | ||||
|                 imagerSaveImageButton->Enable( | ||||
|                     _state == STATE_READING_SUCCEEDED); | ||||
|                 imagerSaveFluxButton->Enable(_state == STATE_READING_SUCCEEDED); | ||||
|                 imagerGoAgainButton->Enable(_state != STATE_READING_WORKING); | ||||
|  | ||||
|                 imagerToolbar->EnableTool( | ||||
|                     imagerBackTool->GetId(), _state != STATE_READING_WORKING); | ||||
|                 break; | ||||
|  | ||||
|             case STATE_WRITING_WORKING: | ||||
|             case STATE_WRITING_SUCCEEDED: | ||||
|             case STATE_WRITING_FAILED: | ||||
|                 imagerSaveImageButton->Enable(false); | ||||
|                 imagerSaveFluxButton->Enable(false); | ||||
|                 imagerGoAgainButton->Enable(_state != STATE_WRITING_WORKING); | ||||
|  | ||||
|                 imagerToolbar->EnableTool( | ||||
|                     imagerBackTool->GetId(), _state != STATE_WRITING_WORKING); | ||||
|                 break; | ||||
|         } | ||||
|         imagerToolbar->Refresh(); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void SetVisualiserMode(int head, int track, int mode) override | ||||
|     { | ||||
|         visualiser->SetMode(head, track, mode); | ||||
|     } | ||||
|  | ||||
|     void SetVisualiserTrackData( | ||||
|         std::shared_ptr<const TrackFlux> trackdata) override | ||||
|     { | ||||
|         visualiser->SetTrackData(trackdata); | ||||
| 		if (!trackdata->trackDatas.empty()) | ||||
| 			histogram->Redraw(*(*trackdata->trackDatas.begin())->fluxmap, 0); | ||||
|     } | ||||
|  | ||||
|     void SetDisk(std::shared_ptr<const DiskFlux> diskdata) override | ||||
|     { | ||||
|         _currentDisk = diskdata; | ||||
|     } | ||||
|  | ||||
|     void OnImagerGoAgainButton(wxCommandEvent& event) override | ||||
|     { | ||||
|         switch (_state) | ||||
|         { | ||||
|             case STATE_READING_SUCCEEDED: | ||||
|             case STATE_READING_FAILED: | ||||
|                 StartReading(); | ||||
|                 break; | ||||
|  | ||||
|             case STATE_WRITING_SUCCEEDED: | ||||
|             case STATE_WRITING_FAILED: | ||||
|                 StartWriting(); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnSaveImageButton(wxCommandEvent&) override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (!config.has_image_writer()) | ||||
|                 Error() << "This format cannot be saved."; | ||||
|  | ||||
|             auto filename = | ||||
|                 wxFileSelector("Choose the name of the image file to write", | ||||
|                     /* default_path= */ wxEmptyString, | ||||
|                     /* default_filename= */ config.image_writer().filename(), | ||||
|                     /* default_extension= */ wxEmptyString, | ||||
|                     /* wildcard= */ wxEmptyString, | ||||
|                     /* flags= */ wxFD_SAVE | wxFD_OVERWRITE_PROMPT); | ||||
|             if (filename.empty()) | ||||
|                 return; | ||||
|  | ||||
|             ImageWriter::updateConfigForFilename( | ||||
|                 config.mutable_image_writer(), filename.ToStdString()); | ||||
|  | ||||
|             auto image = _currentDisk->image; | ||||
|  | ||||
|             QueueJob( | ||||
|                 [image, this]() | ||||
|                 { | ||||
|                     auto imageWriter = | ||||
|                         ImageWriter::create(config.image_writer()); | ||||
|                     imageWriter->writeMappedImage(*image); | ||||
|                 }); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnSaveFluxButton(wxCommandEvent&) override | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             auto filename = | ||||
|                 wxFileSelector("Choose the name of the flux file to write", | ||||
|                     /* default_path= */ wxEmptyString, | ||||
|                     /* default_filename= */ "image.flux", | ||||
|                     /* default_extension= */ wxEmptyString, | ||||
|                     /* wildcard= */ wxEmptyString, | ||||
|                     /* flags= */ wxFD_SAVE | wxFD_OVERWRITE_PROMPT); | ||||
|             if (filename.empty()) | ||||
|                 return; | ||||
|  | ||||
|             FluxSink::updateConfigForFilename( | ||||
|                 config.mutable_flux_sink(), filename.ToStdString()); | ||||
|  | ||||
|             QueueJob( | ||||
|                 [this]() | ||||
|                 { | ||||
|                     auto fluxSource = | ||||
|                         FluxSource::createMemoryFluxSource(*_currentDisk); | ||||
|                     auto fluxSink = FluxSink::create(config.flux_sink()); | ||||
|                     writeRawDiskCommand(*fluxSource, *fluxSink); | ||||
|                 }); | ||||
|         } | ||||
|         catch (const ErrorException& e) | ||||
|         { | ||||
|             wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void OnTrackSelection(TrackSelectionEvent& event) | ||||
|     { | ||||
|         (new FluxViewerWindow(this, event.trackFlux))->Show(true); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     int _state = STATE_DEAD; | ||||
|     std::shared_ptr<const DiskFlux> _currentDisk; | ||||
| }; | ||||
|  | ||||
| ImagerPanel* ImagerPanel::Create(MainWindow* mainWindow, wxSimplebook* parent) | ||||
| { | ||||
|     return new ImagerPanelImpl(mainWindow, parent); | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user