mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			66 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 534a76f3c8 | ||
|  | d8528d889a | ||
|  | ec439a5f2a | ||
|  | d88110488e | ||
|  | 2f4b15293a | ||
|  | 3d71f32587 | ||
|  | 5136dda598 | ||
|  | d26940304b | ||
|  | f9f11b4966 | ||
|  | 1297b568ba | ||
|  | fd10840cc0 | ||
|  | 7e86be979d | ||
|  | 731d1efcc4 | ||
|  | dfda8be30c | ||
|  | 7dd1b6d8e9 | ||
|  | ec1bcdb9e5 | ||
|  | c0f46d2bd4 | ||
|  | 615dca3130 | ||
|  | 9cf9597c54 | ||
|  | e24ee648e7 | ||
|  | e3518dc389 | ||
|  | 693ba20606 | ||
|  | b947c6c186 | ||
|  | 7f8ecb8514 | ||
|  | 4df6afa9c1 | ||
|  | d0620f8efe | ||
|  | 2b1a6dbb03 | ||
|  | c6ef667c3f | ||
|  | 8c1d1bec93 | ||
|  | 8be174e65a | ||
|  | 6d37fafb02 | ||
|  | 46ce882daa | ||
|  | 6d14cbdb9b | ||
|  | 4bf6b433ae | ||
|  | 87a1b2e6f8 | ||
|  | c6ee48ec85 | ||
|  | b58a6b1649 | ||
|  | bd9736df93 | ||
|  | 3b9c966e3d | ||
|  | 96c9a89171 | ||
|  | c374ffd15e | ||
|  | c53109e1a1 | ||
|  | 4598b3a7a6 | ||
|  | cf975b74bf | ||
|  | 5d65dcf3c8 | ||
|  | f299ec1f8d | ||
|  | 6677034774 | ||
|  | 3c7c4639a9 | ||
|  | 7e9a1268a5 | ||
|  | a60b8e68ca | ||
|  | b2161aa67e | ||
|  | d1fffb1d08 | ||
|  | 52d66d9555 | ||
|  | 66f2d359e2 | ||
|  | 8327f33ee6 | ||
|  | d4a94551d9 | ||
|  | d2a545d83e | ||
|  | aee4eac271 | ||
|  | cbeddb11bc | ||
|  | 4c51cf8316 | ||
|  | 345cd6bd92 | ||
|  | 48540245b5 | ||
|  | 088bd9434d | ||
|  | 8ba8c58377 | ||
|  | 4ae43d0503 | ||
|  | d20ce3dde7 | 
							
								
								
									
										8
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,6 +21,12 @@ jobs: | ||||
|     - name: make | ||||
|       run: gmake | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: FluxEngine.pkg | ||||
|  | ||||
|   build-windows: | ||||
|     runs-on: windows-latest | ||||
|     defaults: | ||||
| @@ -62,4 +68,4 @@ jobs: | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine.zip | ||||
|         path: fluxengine-windows.zip | ||||
|   | ||||
							
								
								
									
										39
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										39
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -77,3 +77,42 @@ jobs: | ||||
|         tag_name: dev | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|   build-macos: | ||||
|     runs-on: macos-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils | ||||
|     - name: make | ||||
|       run: gmake | ||||
|  | ||||
|     - name: tag | ||||
|       uses: EndBug/latest-tag@latest | ||||
|       with: | ||||
|         tag-name: dev | ||||
|         force-branch: false | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|     - name: delete-old-assets | ||||
|       uses: mknejp/delete-release-assets@v1 | ||||
|       with: | ||||
|         token: ${{ github.token }} | ||||
|         tag: dev | ||||
|         assets: |  | ||||
|           FluxEngine.pkg | ||||
|         fail-if-no-assets: false | ||||
|  | ||||
|     - name: release | ||||
|       uses: softprops/action-gh-release@v1 | ||||
|       with: | ||||
|         name: Development build ${{ env.RELEASE_DATE }} | ||||
|         files: | | ||||
|           FluxEngine.pkg | ||||
|         tag_name: dev | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										15
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Makefile
									
									
									
									
									
								
							| @@ -48,7 +48,7 @@ AR ?= $(CCPREFIX)ar | ||||
| PKG_CONFIG ?= pkg-config | ||||
| WX_CONFIG ?= wx-config | ||||
| PROTOC ?= protoc | ||||
| CFLAGS ?= -g -O3 | ||||
| CFLAGS ?= -g -O0 | ||||
| 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/smaky6/smaky6.proto \ | ||||
| 	arch/tids990/tids990.proto \ | ||||
| 	arch/victor9k/victor9k.proto \ | ||||
| 	arch/zilogmcz/zilogmcz.proto \ | ||||
| @@ -159,12 +160,18 @@ include tests/build.mk | ||||
| do-encodedecodetest = $(eval $(do-encodedecodetest-impl)) | ||||
| define do-encodedecodetest-impl | ||||
|  | ||||
| tests: $(OBJDIR)/$1$3.encodedecode | ||||
| $(OBJDIR)/$1$3.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| tests: $(OBJDIR)/$1$3.flux.encodedecode | ||||
| $(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST $1 $3 | ||||
| 	@echo ENCODEDECODETEST .flux $1 $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 | ||||
| 	@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| endef | ||||
|  | ||||
| $(call do-encodedecodetest,amiga) | ||||
|   | ||||
| @@ -116,6 +116,7 @@ people who've had it work). | ||||
| | [Macintosh 400kB/800kB](doc/disk-macintosh.md)  |  🦄   |   🦄   |                                     | | ||||
| | [NEC PC-98](doc/disk-ibm.md)              |  🦄   |   🦄   | trimode drive not required          | | ||||
| | [Sharp X68000](doc/disk-ibm.md)           |  🦄   |   🦄   |                                     | | ||||
| | [Smaky 6](doc/disk-smaky6.md)             |  🦖   |       | 5.25" hard sectored | | ||||
| | [TRS-80](doc/disk-trs80.md)               |  🦖   |   🦖*  | a minor variation of the IBM scheme | | ||||
| {: .datatable } | ||||
|  | ||||
|   | ||||
| @@ -13,16 +13,6 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR); | ||||
|  | ||||
| /* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */ | ||||
|  | ||||
| static Bytes reverse_bits(const Bytes& input) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
|     for (uint8_t b : input) | ||||
|         bw.write_8(reverse_bits(b)); | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| class AesLanierDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| @@ -43,7 +33,7 @@ public: | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); | ||||
| 		const auto& reversed = reverse_bits(bytes); | ||||
| 		const auto& reversed = bytes.reverseBits(); | ||||
|  | ||||
| 		_sector->logicalTrack = reversed[1]; | ||||
| 		_sector->logicalSide = 0; | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| LIBARCH_SRCS = \ | ||||
| 	arch/aeslanier/decoder.cc \ | ||||
| 	arch/agat/agat.cc \ | ||||
| 	arch/agat/decoder.cc \ | ||||
| 	arch/amiga/amiga.cc \ | ||||
| 	arch/amiga/decoder.cc \ | ||||
| 	arch/amiga/encoder.cc \ | ||||
| @@ -16,18 +18,17 @@ LIBARCH_SRCS = \ | ||||
| 	arch/ibm/encoder.cc \ | ||||
| 	arch/macintosh/decoder.cc \ | ||||
| 	arch/macintosh/encoder.cc \ | ||||
| 	arch/micropolis/decoder.cc \ | ||||
| 	arch/micropolis/encoder.cc \ | ||||
| 	arch/mx/decoder.cc \ | ||||
| 	arch/northstar/decoder.cc \ | ||||
| 	arch/northstar/encoder.cc \ | ||||
| 	arch/smaky6/decoder.cc \ | ||||
| 	arch/tids990/decoder.cc \ | ||||
| 	arch/tids990/encoder.cc \ | ||||
| 	arch/victor9k/decoder.cc \ | ||||
| 	arch/victor9k/encoder.cc \ | ||||
| 	arch/zilogmcz/decoder.cc \ | ||||
| 	arch/tids990/decoder.cc \ | ||||
| 	arch/tids990/encoder.cc \ | ||||
| 	arch/micropolis/decoder.cc \ | ||||
| 	arch/micropolis/encoder.cc \ | ||||
| 	arch/northstar/decoder.cc \ | ||||
| 	arch/northstar/encoder.cc \ | ||||
| 	arch/agat/agat.cc \ | ||||
| 	arch/agat/decoder.cc \ | ||||
|  | ||||
| LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS)) | ||||
| OBJS += $(LIBARCH_OBJS) | ||||
|   | ||||
							
								
								
									
										154
									
								
								arch/smaky6/decoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								arch/smaky6/decoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "smaky6.h" | ||||
| #include "bytes.h" | ||||
| #include "crc.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa); | ||||
|  | ||||
| class Smaky6Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
|     Smaky6Decoder(const DecoderProto& config): | ||||
|         Decoder(config), | ||||
|         _config(config.smaky6()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     /* Returns the sector ID of the _current_ sector. */ | ||||
|     int advanceToNextSector() | ||||
|     { | ||||
|         auto previous = tell(); | ||||
|         seekToIndexMark(); | ||||
|         auto now = tell(); | ||||
|         if ((now.ns() - previous.ns()) < 9e6) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             auto next = tell(); | ||||
|             if ((next.ns() - now.ns()) < 9e6) | ||||
|             { | ||||
|                 /* We just found sector 0. */ | ||||
|  | ||||
|                 _sectorId = 0; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 /* Spurious... */ | ||||
|  | ||||
|                 seek(now); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return _sectorId++; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void beginTrack() override | ||||
|     { | ||||
|         /* Find the start-of-track index marks, which will be an interval | ||||
|          * of about 6ms. */ | ||||
|  | ||||
|         seekToIndexMark(); | ||||
|         _sectorId = 99; | ||||
|         for (;;) | ||||
|         { | ||||
|             auto pos = tell(); | ||||
|             advanceToNextSector(); | ||||
|             if (_sectorId < 99) | ||||
|             { | ||||
|                 seek(pos); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if (eof()) | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
|         /* Now we know where to start counting, start finding sectors. */ | ||||
|  | ||||
|         _sectorStarts.clear(); | ||||
|         for (;;) | ||||
|         { | ||||
|             auto now = tell(); | ||||
|             if (eof()) | ||||
|                 break; | ||||
|  | ||||
|             int id = advanceToNextSector(); | ||||
|             if (id < 16) | ||||
|                 _sectorStarts.push_back(std::make_pair(id, now)); | ||||
|         } | ||||
|  | ||||
|         _sectorIndex = 0; | ||||
|     } | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         if (_sectorIndex == _sectorStarts.size()) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         const auto& p = _sectorStarts[_sectorIndex++]; | ||||
|         _sectorId = p.first; | ||||
|         seek(p.second); | ||||
|  | ||||
|         nanoseconds_t clock = seekToPattern(SECTOR_PATTERN); | ||||
|         _sector->headerStartTime = tell().ns(); | ||||
|  | ||||
|         return clock; | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         readRawBits(33); | ||||
|         const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16); | ||||
|         if (rawbits.size() < SMAKY6_SECTOR_SIZE) | ||||
|             return; | ||||
|         const auto& rawbytes = | ||||
|             toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16); | ||||
|  | ||||
|         /* The Smaky bytes are stored backwards! Backwards! */ | ||||
|  | ||||
|         const auto& bytes = | ||||
|             decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits(); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
|         uint8_t track = br.read_8(); | ||||
|         Bytes data = br.read(SMAKY6_SECTOR_SIZE); | ||||
|         uint8_t wantedChecksum = br.read_8(); | ||||
|         uint8_t gotChecksum = sumBytes(data) & 0xff; | ||||
|  | ||||
|         if (track != _sector->physicalTrack) | ||||
|             return; | ||||
|  | ||||
|         _sector->logicalTrack = _sector->physicalTrack; | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = _sectorId; | ||||
|  | ||||
|         _sector->data = data; | ||||
|         _sector->status = | ||||
|             (wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Smaky6DecoderProto& _config; | ||||
|     nanoseconds_t _startOfTrack; | ||||
|     std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts; | ||||
|     int _sectorId; | ||||
|     int _sectorIndex; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Decoder>(new Smaky6Decoder(config)); | ||||
| } | ||||
							
								
								
									
										10
									
								
								arch/smaky6/smaky6.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								arch/smaky6/smaky6.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #ifndef SMAKY6_H | ||||
| #define SMAKY6_H | ||||
|  | ||||
| #define SMAKY6_SECTOR_SIZE 256 | ||||
| #define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1) | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										6
									
								
								arch/smaky6/smaky6.proto
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								arch/smaky6/smaky6.proto
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Smaky6DecoderProto {} | ||||
|  | ||||
							
								
								
									
										43
									
								
								doc/disk-smaky6.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								doc/disk-smaky6.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| Disk: Smaky 6 | ||||
| ============= | ||||
|  | ||||
| The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based | ||||
| around a Z80 processor and has one or two Micropolis 5.25" drives which use | ||||
| 16-sector hard sectored disks. The disk format is single-sided with 77 tracks | ||||
| and 256-byte sectors, resulting in 308kB disks. It uses MFM with a custom | ||||
| sector record scheme. It was later superceded by a 68000-based Smaky which used | ||||
| different disks. | ||||
|  | ||||
| FluxEngine supports these, although because the Micropolis drives use a 100tpi | ||||
| track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You | ||||
| will have to find a 100tpi drive from somewhere (they're rare). | ||||
|  | ||||
| Reading disks | ||||
| ------------- | ||||
|  | ||||
| You must use a 100-tpi 80-track 5.25" floppy drive. | ||||
|  | ||||
| To read a Smaky 6 floppy, do: | ||||
|  | ||||
| ``` | ||||
| fluxengine read smaky6 | ||||
| ``` | ||||
|  | ||||
| You should end up with a `smaky6.img` file. | ||||
|  | ||||
|  | ||||
| Filesystem access | ||||
| ----------------- | ||||
|  | ||||
| There is experimental read-only support for the Smaky 6 filesystem, allowing | ||||
| the directory to be listed and files read from disks. It's not known whether | ||||
| this is completely correct, so don't trust it! See the [Filesystem | ||||
| Access](filesystem.md) page for more information. | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   - [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo) | ||||
|      | ||||
|  | ||||
| @@ -23,6 +23,7 @@ The following file systems are supported so far. | ||||
| | FatFS (a.k.a. MS-DOS)                    |   Y   |   Y    | FAT12, FAT16, FAT32; not Atari (AFAIK!) | | ||||
| | Macintosh HFS                            |   Y   |   Y    | Only AppleDouble files may be written | | ||||
| | Apple ProDOS                             |   Y   |        |       | | ||||
| | Smaky 6                                  |   Y   |        |       | | ||||
| {: .datatable } | ||||
|  | ||||
| Please not that Atari disks do _not_ use standard FatFS, and the library I'm | ||||
| @@ -82,7 +83,7 @@ default; for example, Macintosh HFS filesystems are common on 3.5" floppies. You | ||||
| can do this as follows: | ||||
|  | ||||
| ``` | ||||
| fluxengine format ibm1440 -f drive:1 --filesystem.machfs= | ||||
| fluxengine format ibm1440 -f drive:1 --filesystem.type=MACHFS | ||||
| ``` | ||||
|  | ||||
| Some filesystems won't work on some disks --- don't try this with Amiga FFS, for | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/ju475-hd-hi.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/ju475-hd-hi.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 39 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/ju475-hd-lo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/ju475-hd-lo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 33 KiB | 
| @@ -213,6 +213,11 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or | ||||
| 	Read from a Kryoflux stream, where `<path>` is the directory containing the | ||||
| 	stream files. **Read only.** | ||||
|    | ||||
|   - `flx:<directory>` | ||||
|  | ||||
|     Read from a FLUXCOPY stream, where `<path>` is the directory containing the | ||||
|     stream files. **Read only.** | ||||
|  | ||||
|   - `erase:` | ||||
|  | ||||
| 	Read nothing --- writing this to a disk will magnetically erase a track. | ||||
|   | ||||
							
								
								
									
										41
									
								
								extras/FluxEngine.app.template/Contents/Info.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								extras/FluxEngine.app.template/Contents/Info.plist
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||
| <plist version="1.0"> | ||||
| <dict> | ||||
| 	<key>CFBundleDevelopmentRegion</key> | ||||
| 	<string>English</string> | ||||
| 	<key>CFBundleExecutable</key> | ||||
| 	<string>FluxEngine.sh</string> | ||||
| 	<key>CFBundleGetInfoString</key> | ||||
| 	<string>FluxEngine 1.0</string> | ||||
| 	<key>CFBundleIconFile</key> | ||||
| 	<string>FluxEngine.icns</string> | ||||
| 	<key>CFBundleIdentifier</key> | ||||
| 	<string>com.cowlark.fluxengine.gui</string> | ||||
| 	<key>CFBundleInfoDictionaryVersion</key> | ||||
| 	<string>6.0</string> | ||||
| 	<key>CFBundleLocalizations</key> | ||||
| 	<array> | ||||
| 		<string>en</string> | ||||
| 	</array> | ||||
| 	<key>CFBundleName</key> | ||||
| 	<string>FluxEngine</string> | ||||
|         <key>CFBundleDisplayName</key> | ||||
|         <string>FluxEngine</string> | ||||
| 	<key>CFBundlePackageType</key> | ||||
| 	<string>APPL</string> | ||||
| 	<key>CFBundleShortVersionString</key> | ||||
| 	<string>1.0</string> | ||||
| 	<key>CFBundleSignature</key> | ||||
| 	<string>FluxEngine</string> | ||||
| 	<key>CFBundleVersion</key> | ||||
| 	<string>1.0</string> | ||||
| 	<key>LSMinimumSystemVersion</key> | ||||
| 	<string>10.13.0</string> | ||||
| 	<key>LSRequiresCarbon</key> | ||||
| 	<true/> | ||||
| 	<key>NSAppleScriptEnabled</key> | ||||
| 	<true/> | ||||
| </dict> | ||||
| </plist> | ||||
|  | ||||
							
								
								
									
										6
									
								
								extras/FluxEngine.app.template/Contents/MacOS/FluxEngine.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								extras/FluxEngine.app.template/Contents/MacOS/FluxEngine.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #!/bin/sh | ||||
| dir=`dirname "$0"` | ||||
| cd "$dir" | ||||
| export DYLD_FALLBACK_FRAMEWORK_PATH=../Resources | ||||
| exec ./fluxengine-gui "$@" | ||||
|  | ||||
| @@ -22,6 +22,8 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/fluxsource/erasefluxsource.cc \ | ||||
| 	lib/fluxsource/fl2fluxsource.cc \ | ||||
| 	lib/fluxsource/fluxsource.cc \ | ||||
| 	lib/fluxsource/flx.cc \ | ||||
| 	lib/fluxsource/flxfluxsource.cc \ | ||||
| 	lib/fluxsource/hardwarefluxsource.cc \ | ||||
| 	lib/fluxsource/kryoflux.cc \ | ||||
| 	lib/fluxsource/kryofluxfluxsource.cc \ | ||||
| @@ -74,6 +76,7 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/vfs/fatfs.cc \ | ||||
| 	lib/vfs/machfs.cc \ | ||||
| 	lib/vfs/prodos.cc \ | ||||
| 	lib/vfs/smaky6fs.cc \ | ||||
| 	lib/vfs/vfs.cc \ | ||||
| 	lib/vfs/fluxsectorinterface.cc \ | ||||
| 	lib/vfs/imagesectorinterface.cc \ | ||||
|   | ||||
							
								
								
									
										10
									
								
								lib/bytes.cc
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								lib/bytes.cc
									
									
									
									
									
								
							| @@ -205,6 +205,16 @@ std::vector<bool> Bytes::toBits() const | ||||
| 	return bits; | ||||
| } | ||||
|  | ||||
| Bytes Bytes::reverseBits() const | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
|     for (uint8_t b : *this) | ||||
|         bw.write_8(reverse_bits(b)); | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| uint8_t toByte( | ||||
|     std::vector<bool>::const_iterator start, | ||||
|     std::vector<bool>::const_iterator end) | ||||
|   | ||||
| @@ -62,6 +62,7 @@ public: | ||||
|     Bytes compress() const; | ||||
|     Bytes decompress() const; | ||||
| 	std::vector<bool> toBits() const; | ||||
| 	Bytes reverseBits() const; | ||||
|  | ||||
|     ByteReader reader() const; | ||||
|     ByteWriter writer(); | ||||
|   | ||||
| @@ -10,6 +10,7 @@ message RangeProto { | ||||
|  | ||||
| extend google.protobuf.FieldOptions { | ||||
|   optional string help = 50000; | ||||
|   optional bool recurse = 50001 [default = true]; | ||||
| } | ||||
|  | ||||
| enum IndexMode { | ||||
|   | ||||
| @@ -44,6 +44,6 @@ 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" ]; | ||||
| 	optional ConfigProto config = 4 [(help) = "Option data", (recurse) = false ]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,223 +1,226 @@ | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "arch/agat/agat.h" | ||||
| #include "arch/aeslanier/aeslanier.h" | ||||
| #include "arch/amiga/amiga.h" | ||||
| #include "arch/apple2/apple2.h" | ||||
| #include "arch/brother/brother.h" | ||||
| #include "arch/c64/c64.h" | ||||
| #include "arch/f85/f85.h" | ||||
| #include "arch/fb100/fb100.h" | ||||
| #include "arch/ibm/ibm.h" | ||||
| #include "arch/macintosh/macintosh.h" | ||||
| #include "arch/micropolis/micropolis.h" | ||||
| #include "arch/mx/mx.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "arch/tids990/tids990.h" | ||||
| #include "arch/victor9k/victor9k.h" | ||||
| #include "arch/zilogmcz/zilogmcz.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "flux.h" | ||||
| #include "protocol.h" | ||||
| #include "decoders/rawbits.h" | ||||
| #include "sector.h" | ||||
| #include "image.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include "lib/layout.h" | ||||
| #include "fmt/format.h" | ||||
| #include <numeric> | ||||
|  | ||||
| std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config) | ||||
| { | ||||
|     static const std::map<int, | ||||
|         std::function<std::unique_ptr<Decoder>(const DecoderProto&)>> | ||||
|         decoders = { | ||||
|             {DecoderProto::kAgat,       createAgatDecoder       }, | ||||
|             {DecoderProto::kAeslanier,  createAesLanierDecoder  }, | ||||
|             {DecoderProto::kAmiga,      createAmigaDecoder      }, | ||||
|             {DecoderProto::kApple2,     createApple2Decoder     }, | ||||
|             {DecoderProto::kBrother,    createBrotherDecoder    }, | ||||
|             {DecoderProto::kC64,        createCommodore64Decoder}, | ||||
|             {DecoderProto::kF85,        createDurangoF85Decoder }, | ||||
|             {DecoderProto::kFb100,      createFb100Decoder      }, | ||||
|             {DecoderProto::kIbm,        createIbmDecoder        }, | ||||
|             {DecoderProto::kMacintosh,  createMacintoshDecoder  }, | ||||
|             {DecoderProto::kMicropolis, createMicropolisDecoder }, | ||||
|             {DecoderProto::kMx,         createMxDecoder         }, | ||||
|             {DecoderProto::kNorthstar,  createNorthstarDecoder  }, | ||||
|             {DecoderProto::kTids990,    createTids990Decoder    }, | ||||
|             {DecoderProto::kVictor9K,   createVictor9kDecoder   }, | ||||
|             {DecoderProto::kZilogmcz,   createZilogMczDecoder   }, | ||||
|     }; | ||||
|  | ||||
|     auto decoder = decoders.find(config.format_case()); | ||||
|     if (decoder == decoders.end()) | ||||
|         Error() << "no decoder specified"; | ||||
|  | ||||
|     return (decoder->second)(config); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors( | ||||
|     std::shared_ptr<const Fluxmap> fluxmap, | ||||
|     std::shared_ptr<const TrackInfo>& trackInfo) | ||||
| { | ||||
|     _trackdata = std::make_shared<TrackDataFlux>(); | ||||
|     _trackdata->fluxmap = fluxmap; | ||||
|     _trackdata->trackInfo = trackInfo; | ||||
|  | ||||
|     FluxmapReader fmr(*fluxmap); | ||||
|     _fmr = &fmr; | ||||
|  | ||||
|     auto newSector = [&] | ||||
|     { | ||||
|         _sector = std::make_shared<Sector>(trackInfo, 0); | ||||
|         _sector->status = Sector::MISSING; | ||||
|     }; | ||||
|  | ||||
|     newSector(); | ||||
|     beginTrack(); | ||||
|     for (;;) | ||||
|     { | ||||
|         newSector(); | ||||
|  | ||||
|         Fluxmap::Position recordStart = fmr.tell(); | ||||
|         _sector->clock = advanceToNextRecord(); | ||||
|         if (fmr.eof() || !_sector->clock) | ||||
|             return _trackdata; | ||||
|  | ||||
|         /* Read the sector record. */ | ||||
|  | ||||
|         Fluxmap::Position before = fmr.tell(); | ||||
|         decodeSectorRecord(); | ||||
|         Fluxmap::Position after = fmr.tell(); | ||||
|         pushRecord(before, after); | ||||
|  | ||||
|         if (_sector->status != Sector::DATA_MISSING) | ||||
|         { | ||||
|             _sector->position = before.bytes; | ||||
|             _sector->dataStartTime = before.ns(); | ||||
|             _sector->dataEndTime = after.ns(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             /* The data is in a separate record. */ | ||||
|  | ||||
|             for (;;) | ||||
|             { | ||||
|                 _sector->headerStartTime = before.ns(); | ||||
|                 _sector->headerEndTime = after.ns(); | ||||
|  | ||||
|                 _sector->clock = advanceToNextRecord(); | ||||
|                 if (fmr.eof() || !_sector->clock) | ||||
|                     break; | ||||
|  | ||||
|                 before = fmr.tell(); | ||||
|                 decodeDataRecord(); | ||||
|                 after = fmr.tell(); | ||||
|  | ||||
|                 if (_sector->status != Sector::DATA_MISSING) | ||||
|                 { | ||||
|                     _sector->position = before.bytes; | ||||
|                     _sector->dataStartTime = before.ns(); | ||||
|                     _sector->dataEndTime = after.ns(); | ||||
|                     pushRecord(before, after); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 fmr.skipToEvent(F_BIT_PULSE); | ||||
|                 resetFluxDecoder(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_sector->status != Sector::MISSING) | ||||
|         { | ||||
|             auto trackLayout = Layout::getLayoutOfTrack( | ||||
|                 _sector->logicalTrack, _sector->logicalSide); | ||||
|             _trackdata->sectors.push_back(_sector); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Decoder::pushRecord( | ||||
|     const Fluxmap::Position& start, const Fluxmap::Position& end) | ||||
| { | ||||
|     Fluxmap::Position here = _fmr->tell(); | ||||
|  | ||||
|     auto record = std::make_shared<Record>(); | ||||
|     _trackdata->records.push_back(record); | ||||
|     _sector->records.push_back(record); | ||||
|  | ||||
|     record->startTime = start.ns(); | ||||
|     record->endTime = end.ns(); | ||||
|     record->clock = _sector->clock; | ||||
|  | ||||
|     record->rawData = toBytes(_recordBits); | ||||
|     _recordBits.clear(); | ||||
| } | ||||
|  | ||||
| void Decoder::resetFluxDecoder() | ||||
| { | ||||
|     _decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config)); | ||||
| } | ||||
|  | ||||
| nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern) | ||||
| { | ||||
|     nanoseconds_t clock = _fmr->seekToPattern(pattern); | ||||
|     _decoder.reset(new FluxDecoder(_fmr, clock, _config)); | ||||
|     return clock; | ||||
| } | ||||
|  | ||||
| void Decoder::seekToIndexMark() | ||||
| { | ||||
|     _fmr->skipToEvent(F_BIT_PULSE); | ||||
|     _fmr->seekToIndexMark(); | ||||
| } | ||||
|  | ||||
| std::vector<bool> Decoder::readRawBits(unsigned count) | ||||
| { | ||||
|     auto bits = _decoder->readBits(count); | ||||
|     _recordBits.insert(_recordBits.end(), bits.begin(), bits.end()); | ||||
|     return bits; | ||||
| } | ||||
|  | ||||
| uint8_t Decoder::readRaw8() | ||||
| { | ||||
|     return toBytes(readRawBits(8)).reader().read_8(); | ||||
| } | ||||
|  | ||||
| uint16_t Decoder::readRaw16() | ||||
| { | ||||
|     return toBytes(readRawBits(16)).reader().read_be16(); | ||||
| } | ||||
|  | ||||
| uint32_t Decoder::readRaw20() | ||||
| { | ||||
|     std::vector<bool> bits(4); | ||||
|     for (bool b : readRawBits(20)) | ||||
|         bits.push_back(b); | ||||
|  | ||||
|     return toBytes(bits).reader().read_be24(); | ||||
| } | ||||
|  | ||||
| uint32_t Decoder::readRaw24() | ||||
| { | ||||
|     return toBytes(readRawBits(24)).reader().read_be24(); | ||||
| } | ||||
|  | ||||
| uint32_t Decoder::readRaw32() | ||||
| { | ||||
|     return toBytes(readRawBits(32)).reader().read_be32(); | ||||
| } | ||||
|  | ||||
| uint64_t Decoder::readRaw48() | ||||
| { | ||||
|     return toBytes(readRawBits(48)).reader().read_be48(); | ||||
| } | ||||
|  | ||||
| uint64_t Decoder::readRaw64() | ||||
| { | ||||
|     return toBytes(readRawBits(64)).reader().read_be64(); | ||||
| } | ||||
| #include "globals.h" | ||||
| #include "flags.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "arch/agat/agat.h" | ||||
| #include "arch/aeslanier/aeslanier.h" | ||||
| #include "arch/amiga/amiga.h" | ||||
| #include "arch/apple2/apple2.h" | ||||
| #include "arch/brother/brother.h" | ||||
| #include "arch/c64/c64.h" | ||||
| #include "arch/f85/f85.h" | ||||
| #include "arch/fb100/fb100.h" | ||||
| #include "arch/ibm/ibm.h" | ||||
| #include "arch/macintosh/macintosh.h" | ||||
| #include "arch/micropolis/micropolis.h" | ||||
| #include "arch/mx/mx.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "arch/smaky6/smaky6.h" | ||||
| #include "arch/tids990/tids990.h" | ||||
| #include "arch/victor9k/victor9k.h" | ||||
| #include "arch/zilogmcz/zilogmcz.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "flux.h" | ||||
| #include "protocol.h" | ||||
| #include "decoders/rawbits.h" | ||||
| #include "sector.h" | ||||
| #include "image.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include "lib/layout.h" | ||||
| #include "fmt/format.h" | ||||
| #include <numeric> | ||||
|  | ||||
| std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config) | ||||
| { | ||||
|     static const std::map<int, | ||||
|         std::function<std::unique_ptr<Decoder>(const DecoderProto&)>> | ||||
|         decoders = { | ||||
|             {DecoderProto::kAgat,       createAgatDecoder       }, | ||||
|             {DecoderProto::kAeslanier,  createAesLanierDecoder  }, | ||||
|             {DecoderProto::kAmiga,      createAmigaDecoder      }, | ||||
|             {DecoderProto::kApple2,     createApple2Decoder     }, | ||||
|             {DecoderProto::kBrother,    createBrotherDecoder    }, | ||||
|             {DecoderProto::kC64,        createCommodore64Decoder}, | ||||
|             {DecoderProto::kF85,        createDurangoF85Decoder }, | ||||
|             {DecoderProto::kFb100,      createFb100Decoder      }, | ||||
|             {DecoderProto::kIbm,        createIbmDecoder        }, | ||||
|             {DecoderProto::kMacintosh,  createMacintoshDecoder  }, | ||||
|             {DecoderProto::kMicropolis, createMicropolisDecoder }, | ||||
|             {DecoderProto::kMx,         createMxDecoder         }, | ||||
|             {DecoderProto::kNorthstar,  createNorthstarDecoder  }, | ||||
|             {DecoderProto::kSmaky6,     createSmaky6Decoder     }, | ||||
|             {DecoderProto::kTids990,    createTids990Decoder    }, | ||||
|             {DecoderProto::kVictor9K,   createVictor9kDecoder   }, | ||||
|             {DecoderProto::kZilogmcz,   createZilogMczDecoder   }, | ||||
|     }; | ||||
|  | ||||
|     auto decoder = decoders.find(config.format_case()); | ||||
|     if (decoder == decoders.end()) | ||||
|         Error() << "no decoder specified"; | ||||
|  | ||||
|     return (decoder->second)(config); | ||||
| } | ||||
|  | ||||
| std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors( | ||||
|     std::shared_ptr<const Fluxmap> fluxmap, | ||||
|     std::shared_ptr<const TrackInfo>& trackInfo) | ||||
| { | ||||
|     _trackdata = std::make_shared<TrackDataFlux>(); | ||||
|     _trackdata->fluxmap = fluxmap; | ||||
|     _trackdata->trackInfo = trackInfo; | ||||
|  | ||||
|     FluxmapReader fmr(*fluxmap); | ||||
|     _fmr = &fmr; | ||||
|  | ||||
|     auto newSector = [&] | ||||
|     { | ||||
|         _sector = std::make_shared<Sector>(trackInfo, 0); | ||||
|         _sector->status = Sector::MISSING; | ||||
|     }; | ||||
|  | ||||
|     newSector(); | ||||
|     beginTrack(); | ||||
|     for (;;) | ||||
|     { | ||||
|         newSector(); | ||||
|  | ||||
|         Fluxmap::Position recordStart = fmr.tell(); | ||||
|         _sector->clock = advanceToNextRecord(); | ||||
|         if (fmr.eof() || !_sector->clock) | ||||
|             return _trackdata; | ||||
|  | ||||
|         /* Read the sector record. */ | ||||
|  | ||||
|         Fluxmap::Position before = fmr.tell(); | ||||
|         decodeSectorRecord(); | ||||
|         Fluxmap::Position after = fmr.tell(); | ||||
|         pushRecord(before, after); | ||||
|  | ||||
|         if (_sector->status != Sector::DATA_MISSING) | ||||
|         { | ||||
|             _sector->position = before.bytes; | ||||
|             _sector->dataStartTime = before.ns(); | ||||
|             _sector->dataEndTime = after.ns(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             /* The data is in a separate record. */ | ||||
|  | ||||
|             for (;;) | ||||
|             { | ||||
|                 _sector->headerStartTime = before.ns(); | ||||
|                 _sector->headerEndTime = after.ns(); | ||||
|  | ||||
|                 _sector->clock = advanceToNextRecord(); | ||||
|                 if (fmr.eof() || !_sector->clock) | ||||
|                     break; | ||||
|  | ||||
|                 before = fmr.tell(); | ||||
|                 decodeDataRecord(); | ||||
|                 after = fmr.tell(); | ||||
|  | ||||
|                 if (_sector->status != Sector::DATA_MISSING) | ||||
|                 { | ||||
|                     _sector->position = before.bytes; | ||||
|                     _sector->dataStartTime = before.ns(); | ||||
|                     _sector->dataEndTime = after.ns(); | ||||
|                     pushRecord(before, after); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 fmr.skipToEvent(F_BIT_PULSE); | ||||
|                 resetFluxDecoder(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_sector->status != Sector::MISSING) | ||||
|         { | ||||
|             auto trackLayout = Layout::getLayoutOfTrack( | ||||
|                 _sector->logicalTrack, _sector->logicalSide); | ||||
|             _trackdata->sectors.push_back(_sector); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Decoder::pushRecord( | ||||
|     const Fluxmap::Position& start, const Fluxmap::Position& end) | ||||
| { | ||||
|     Fluxmap::Position here = _fmr->tell(); | ||||
|  | ||||
|     auto record = std::make_shared<Record>(); | ||||
|     _trackdata->records.push_back(record); | ||||
|     _sector->records.push_back(record); | ||||
|  | ||||
|     record->position = start.bytes; | ||||
|     record->startTime = start.ns(); | ||||
|     record->endTime = end.ns(); | ||||
|     record->clock = _sector->clock; | ||||
|  | ||||
|     record->rawData = toBytes(_recordBits); | ||||
|     _recordBits.clear(); | ||||
| } | ||||
|  | ||||
| void Decoder::resetFluxDecoder() | ||||
| { | ||||
|     _decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config)); | ||||
| } | ||||
|  | ||||
| nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern) | ||||
| { | ||||
|     nanoseconds_t clock = _fmr->seekToPattern(pattern); | ||||
|     _decoder.reset(new FluxDecoder(_fmr, clock, _config)); | ||||
|     return clock; | ||||
| } | ||||
|  | ||||
| void Decoder::seekToIndexMark() | ||||
| { | ||||
|     _fmr->skipToEvent(F_BIT_PULSE); | ||||
|     _fmr->seekToIndexMark(); | ||||
| } | ||||
|  | ||||
| std::vector<bool> Decoder::readRawBits(unsigned count) | ||||
| { | ||||
|     auto bits = _decoder->readBits(count); | ||||
|     _recordBits.insert(_recordBits.end(), bits.begin(), bits.end()); | ||||
|     return bits; | ||||
| } | ||||
|  | ||||
| uint8_t Decoder::readRaw8() | ||||
| { | ||||
|     return toBytes(readRawBits(8)).reader().read_8(); | ||||
| } | ||||
|  | ||||
| uint16_t Decoder::readRaw16() | ||||
| { | ||||
|     return toBytes(readRawBits(16)).reader().read_be16(); | ||||
| } | ||||
|  | ||||
| uint32_t Decoder::readRaw20() | ||||
| { | ||||
|     std::vector<bool> bits(4); | ||||
|     for (bool b : readRawBits(20)) | ||||
|         bits.push_back(b); | ||||
|  | ||||
|     return toBytes(bits).reader().read_be24(); | ||||
| } | ||||
|  | ||||
| uint32_t Decoder::readRaw24() | ||||
| { | ||||
|     return toBytes(readRawBits(24)).reader().read_be24(); | ||||
| } | ||||
|  | ||||
| uint32_t Decoder::readRaw32() | ||||
| { | ||||
|     return toBytes(readRawBits(32)).reader().read_be32(); | ||||
| } | ||||
|  | ||||
| uint64_t Decoder::readRaw48() | ||||
| { | ||||
|     return toBytes(readRawBits(48)).reader().read_be48(); | ||||
| } | ||||
|  | ||||
| uint64_t Decoder::readRaw64() | ||||
| { | ||||
|     return toBytes(readRawBits(64)).reader().read_be64(); | ||||
| } | ||||
|   | ||||
| @@ -71,6 +71,11 @@ public: | ||||
|         return _fmr->tell(); | ||||
|     } | ||||
|  | ||||
| 	void rewind() | ||||
| 	{ | ||||
| 		_fmr->rewind(); | ||||
| 	} | ||||
|  | ||||
|     void seek(const Fluxmap::Position& pos) | ||||
|     { | ||||
|         return _fmr->seek(pos); | ||||
|   | ||||
| @@ -13,13 +13,14 @@ import "arch/macintosh/macintosh.proto"; | ||||
| import "arch/micropolis/micropolis.proto"; | ||||
| import "arch/mx/mx.proto"; | ||||
| import "arch/northstar/northstar.proto"; | ||||
| import "arch/smaky6/smaky6.proto"; | ||||
| import "arch/tids990/tids990.proto"; | ||||
| import "arch/victor9k/victor9k.proto"; | ||||
| import "arch/zilogmcz/zilogmcz.proto"; | ||||
| import "lib/fluxsink/fluxsink.proto"; | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| //NEXT: 30 | ||||
| //NEXT: 31 | ||||
| message DecoderProto { | ||||
| 	optional double pulse_debounce_threshold = 1 [default = 0.30, | ||||
| 		(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; | ||||
| @@ -46,6 +47,7 @@ message DecoderProto { | ||||
| 		MicropolisDecoderProto micropolis = 14; | ||||
| 		MxDecoderProto mx = 15; | ||||
| 		NorthstarDecoderProto northstar = 24; | ||||
| 		Smaky6DecoderProto smaky6 = 30; | ||||
| 		Tids990DecoderProto tids990 = 16; | ||||
| 		Victor9kDecoderProto victor9k = 17; | ||||
| 		ZilogMczDecoderProto zilogmcz = 18; | ||||
|   | ||||
| @@ -202,6 +202,23 @@ void FluxmapReader::seek(nanoseconds_t ns) | ||||
|     _pos.zeroes = 0; | ||||
| } | ||||
|  | ||||
| void FluxmapReader::seekToByte(unsigned b) | ||||
| { | ||||
| 	if (b < _pos.bytes) | ||||
|     { | ||||
|         _pos.ticks = 0; | ||||
|         _pos.bytes = 0; | ||||
|     } | ||||
|  | ||||
|     while (!eof() && (_pos.bytes < b)) | ||||
|     { | ||||
| 		int e; | ||||
|         unsigned t; | ||||
|         getNextEvent(e, t); | ||||
|     } | ||||
|     _pos.zeroes = 0; | ||||
| } | ||||
|  | ||||
| nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern) | ||||
| { | ||||
|     const FluxMatcher* unused; | ||||
|   | ||||
| @@ -100,6 +100,7 @@ public: | ||||
|  | ||||
|     /* Important! You can only reliably seek to 1 bits. */ | ||||
|     void seek(nanoseconds_t ns); | ||||
| 	void seekToByte(unsigned byte); | ||||
|  | ||||
|     void seekToIndexMark(); | ||||
|     nanoseconds_t seekToPattern(const FluxMatcher& pattern); | ||||
|   | ||||
| @@ -5,8 +5,9 @@ static std::unique_ptr<std::set<LocalBase*>> variables; | ||||
|  | ||||
| void Environment::reset() | ||||
| { | ||||
|     for (LocalBase* var : *variables) | ||||
|         var->reset(); | ||||
|     if (variables) | ||||
|         for (LocalBase* var : *variables) | ||||
|             var->reset(); | ||||
| } | ||||
|  | ||||
| void Environment::addVariable(LocalBase* local) | ||||
|   | ||||
							
								
								
									
										28
									
								
								lib/flux.h
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								lib/flux.h
									
									
									
									
									
								
							| @@ -10,32 +10,32 @@ class TrackInfo; | ||||
|  | ||||
| struct Record | ||||
| { | ||||
| 	nanoseconds_t clock = 0; | ||||
| 	nanoseconds_t startTime = 0; | ||||
| 	nanoseconds_t endTime = 0; | ||||
| 	Bytes rawData; | ||||
|     nanoseconds_t clock = 0; | ||||
|     nanoseconds_t startTime = 0; | ||||
|     nanoseconds_t endTime = 0; | ||||
|     uint32_t position = 0; | ||||
|     Bytes rawData; | ||||
| }; | ||||
|  | ||||
| struct TrackDataFlux | ||||
| { | ||||
| 	std::shared_ptr<const TrackInfo> trackInfo; | ||||
| 	std::shared_ptr<const Fluxmap> fluxmap; | ||||
| 	std::vector<std::shared_ptr<const Record>> records; | ||||
| 	std::vector<std::shared_ptr<const Sector>> sectors; | ||||
|     std::shared_ptr<const TrackInfo> trackInfo; | ||||
|     std::shared_ptr<const Fluxmap> fluxmap; | ||||
|     std::vector<std::shared_ptr<const Record>> records; | ||||
|     std::vector<std::shared_ptr<const Sector>> sectors; | ||||
| }; | ||||
|  | ||||
| struct TrackFlux | ||||
| { | ||||
| 	std::shared_ptr<const TrackInfo> trackInfo; | ||||
| 	std::vector<std::shared_ptr<TrackDataFlux>> trackDatas; | ||||
| 	std::set<std::shared_ptr<const Sector>> sectors; | ||||
|     std::shared_ptr<const TrackInfo> trackInfo; | ||||
|     std::vector<std::shared_ptr<TrackDataFlux>> trackDatas; | ||||
|     std::set<std::shared_ptr<const Sector>> sectors; | ||||
| }; | ||||
|  | ||||
| struct DiskFlux | ||||
| { | ||||
| 	std::vector<std::shared_ptr<TrackFlux>> tracks; | ||||
| 	std::shared_ptr<const Image> image; | ||||
|     std::vector<std::shared_ptr<TrackFlux>> tracks; | ||||
|     std::shared_ptr<const Image> image; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -9,55 +9,85 @@ | ||||
|  | ||||
| std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config) | ||||
| { | ||||
| 	switch (config.dest_case()) | ||||
| 	{ | ||||
| 		case FluxSinkProto::kDrive: | ||||
| 			return createHardwareFluxSink(config.drive()); | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case FluxSinkProto::DRIVE: | ||||
|             return createHardwareFluxSink(config.drive()); | ||||
|  | ||||
| 		case FluxSinkProto::kA2R: | ||||
| 			return createA2RFluxSink(config.a2r()); | ||||
|         case FluxSinkProto::A2R: | ||||
|             return createA2RFluxSink(config.a2r()); | ||||
|  | ||||
| 		case FluxSinkProto::kAu: | ||||
| 			return createAuFluxSink(config.au()); | ||||
|         case FluxSinkProto::AU: | ||||
|             return createAuFluxSink(config.au()); | ||||
|  | ||||
| 		case FluxSinkProto::kVcd: | ||||
| 			return createVcdFluxSink(config.vcd()); | ||||
|         case FluxSinkProto::VCD: | ||||
|             return createVcdFluxSink(config.vcd()); | ||||
|  | ||||
| 		case FluxSinkProto::kScp: | ||||
| 			return createScpFluxSink(config.scp()); | ||||
|         case FluxSinkProto::SCP: | ||||
|             return createScpFluxSink(config.scp()); | ||||
|  | ||||
| 		case FluxSinkProto::kFl2: | ||||
| 			return createFl2FluxSink(config.fl2()); | ||||
|         case FluxSinkProto::FLUX: | ||||
|             return createFl2FluxSink(config.fl2()); | ||||
|  | ||||
| 		default: | ||||
| 			Error() << "bad output disk config"; | ||||
| 			return std::unique_ptr<FluxSink>(); | ||||
| 	} | ||||
|         default: | ||||
|             Error() << "bad output disk config"; | ||||
|             return std::unique_ptr<FluxSink>(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& filename) | ||||
| void FluxSink::updateConfigForFilename( | ||||
|     FluxSinkProto* proto, const std::string& filename) | ||||
| { | ||||
| 	static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSinkProto*)>>> formats = | ||||
| 	{ | ||||
| 		{ std::regex("^(.*\\.a2r)$"),  [](auto& s, auto* proto) { proto->mutable_a2r()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.scp)$"),  [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }}, | ||||
| 		{ std::regex("^vcd:(.*)$"),    [](auto& s, auto* proto) { proto->mutable_vcd()->set_directory(s); }}, | ||||
| 		{ std::regex("^au:(.*)$"),     [](auto& s, auto* proto) { proto->mutable_au()->set_directory(s); }}, | ||||
| 		{ std::regex("^drive:(.*)"),   [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }}, | ||||
| 	}; | ||||
|     static const std::vector<std::pair<std::regex, | ||||
|         std::function<void(const std::string&, FluxSinkProto*)>>> | ||||
|         formats = { | ||||
|             {std::regex("^(.*\\.a2r)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSinkProto::A2R); | ||||
|                     proto->mutable_a2r()->set_filename(s); | ||||
|                 }}, | ||||
|             {std::regex("^(.*\\.flux)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSinkProto::FLUX); | ||||
|                     proto->mutable_fl2()->set_filename(s); | ||||
|                 }}, | ||||
|             {std::regex("^(.*\\.scp)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSinkProto::SCP); | ||||
|                     proto->mutable_scp()->set_filename(s); | ||||
|                 }}, | ||||
|             {std::regex("^vcd:(.*)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSinkProto::VCD); | ||||
|                     proto->mutable_vcd()->set_directory(s); | ||||
|                 }}, | ||||
|             {std::regex("^au:(.*)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSinkProto::AU); | ||||
|                     proto->mutable_au()->set_directory(s); | ||||
|                 }}, | ||||
|             {std::regex("^drive:(.*)"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSinkProto::DRIVE); | ||||
|                     config.mutable_drive()->set_drive(std::stoi(s)); | ||||
|                 }}, | ||||
|     }; | ||||
|  | ||||
| 	for (const auto& it : formats) | ||||
| 	{ | ||||
| 		std::smatch match; | ||||
| 		if (std::regex_match(filename, match, it.first)) | ||||
| 		{ | ||||
| 			it.second(match[1], proto); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|     for (const auto& it : formats) | ||||
|     { | ||||
|         std::smatch match; | ||||
|         if (std::regex_match(filename, match, it.first)) | ||||
|         { | ||||
|             it.second(match[1], proto); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	Error() << fmt::format("unrecognised flux filename '{}'", filename); | ||||
|     Error() << fmt::format("unrecognised flux filename '{}'", filename); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ message A2RFluxSinkProto { | ||||
| } | ||||
|  | ||||
| message VcdFluxSinkProto { | ||||
| 	optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"]; | ||||
| 	optional string directory = 1 	   [default = "vcdfiles", (help) = "directory to write .vcd files to"]; | ||||
| } | ||||
|  | ||||
| message ScpFluxSinkProto { | ||||
| @@ -27,15 +27,25 @@ message Fl2FluxSinkProto { | ||||
| 	optional string filename = 1       [default = "flux.fl2", (help) = ".fl2 file to write to"]; | ||||
| } | ||||
|  | ||||
| // Next: 9 | ||||
| // Next: 10 | ||||
| message FluxSinkProto { | ||||
| 	oneof dest { | ||||
| 		HardwareFluxSinkProto drive = 2; | ||||
| 		A2RFluxSinkProto a2r = 8; | ||||
| 		AuFluxSinkProto au = 3; | ||||
| 		VcdFluxSinkProto vcd = 4; | ||||
| 		ScpFluxSinkProto scp = 5; | ||||
| 		Fl2FluxSinkProto fl2 = 6; | ||||
| 	enum FluxSinkType { | ||||
| 		NOT_SET = 0; | ||||
| 		DRIVE = 1; | ||||
| 		A2R = 2; | ||||
| 		AU = 3; | ||||
| 		VCD = 4; | ||||
| 		SCP = 5; | ||||
| 		FLUX = 6; | ||||
| 	} | ||||
|  | ||||
| 	optional FluxSinkType type = 9 [default = NOT_SET, (help) = "flux sink type"]; | ||||
|  | ||||
| 	optional HardwareFluxSinkProto drive = 2; | ||||
| 	optional A2RFluxSinkProto a2r = 8; | ||||
| 	optional AuFluxSinkProto au = 3; | ||||
| 	optional VcdFluxSinkProto vcd = 4; | ||||
| 	optional ScpFluxSinkProto scp = 5; | ||||
| 	optional Fl2FluxSinkProto fl2 = 6; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| #include "proto.h" | ||||
| #include "fmt/format.h" | ||||
| #include "fluxmap.h" | ||||
| #include "layout.h" | ||||
| #include "scp.h" | ||||
| #include <fstream> | ||||
| #include <sys/stat.h> | ||||
| @@ -16,7 +17,7 @@ | ||||
|  | ||||
| static int strackno(int track, int side) | ||||
| { | ||||
| 	return (track << 1) | side; | ||||
|     return (track << 1) | side; | ||||
| } | ||||
|  | ||||
| static void write_le32(uint8_t dest[4], uint32_t v) | ||||
| @@ -29,150 +30,170 @@ static void write_le32(uint8_t dest[4], uint32_t v) | ||||
|  | ||||
| static void appendChecksum(uint32_t& checksum, const Bytes& bytes) | ||||
| { | ||||
| 	ByteReader br(bytes); | ||||
| 	while (!br.eof()) | ||||
| 		checksum += br.read_8(); | ||||
|     ByteReader br(bytes); | ||||
|     while (!br.eof()) | ||||
|         checksum += br.read_8(); | ||||
| } | ||||
|  | ||||
| class ScpFluxSink : public FluxSink | ||||
| { | ||||
| public: | ||||
| 	ScpFluxSink(const ScpFluxSinkProto& lconfig): | ||||
| 		_config(lconfig) | ||||
| 	{ | ||||
| 		bool singlesided = config.heads().start() == config.heads().end(); | ||||
|     ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig) | ||||
|     { | ||||
|         int minTrack; | ||||
|         int maxTrack; | ||||
|         int minSide; | ||||
|         int maxSide; | ||||
|         Layout::getBounds( | ||||
|             Layout::computeLocations(), minTrack, maxTrack, minSide, maxSide); | ||||
|  | ||||
| 		_fileheader.file_id[0] = 'S'; | ||||
| 		_fileheader.file_id[1] = 'C'; | ||||
| 		_fileheader.file_id[2] = 'P'; | ||||
| 		_fileheader.version = 0x18; /* Version 1.8 of the spec */ | ||||
| 		_fileheader.type = _config.type_byte(); | ||||
| 		_fileheader.start_track = strackno(config.tracks().start(), config.heads().start()); | ||||
| 		_fileheader.end_track = strackno(config.tracks().end(), config.heads().end()); | ||||
| 		_fileheader.flags = SCP_FLAG_INDEXED | ||||
| 				| SCP_FLAG_96TPI; | ||||
| 		_fileheader.cell_width = 0; | ||||
| 		_fileheader.heads = singlesided; | ||||
|         _fileheader.file_id[0] = 'S'; | ||||
|         _fileheader.file_id[1] = 'C'; | ||||
|         _fileheader.file_id[2] = 'P'; | ||||
|         _fileheader.version = 0x18; /* Version 1.8 of the spec */ | ||||
|         _fileheader.type = _config.type_byte(); | ||||
|         _fileheader.start_track = strackno(minTrack, minSide); | ||||
|         _fileheader.end_track = strackno(maxTrack, maxSide); | ||||
|         _fileheader.flags = SCP_FLAG_INDEXED; | ||||
|         if (config.tpi() == 96) | ||||
|             _fileheader.flags |= SCP_FLAG_96TPI; | ||||
|         _fileheader.cell_width = 0; | ||||
| 		if ((minSide == 0) && (maxSide == 0)) | ||||
| 			_fileheader.heads = 1; | ||||
| 		else if ((minSide == 1) && (maxSide == 1)) | ||||
| 			_fileheader.heads = 2; | ||||
| 		else | ||||
| 			_fileheader.heads = 0; | ||||
|  | ||||
| 		std::cout << fmt::format("SCP: writing 96 tpi {} file containing {} tracks\n", | ||||
| 			singlesided ? "single sided" : "double sided", | ||||
| 			_fileheader.end_track - _fileheader.start_track + 1 | ||||
| 		); | ||||
|         std::cout << fmt::format( | ||||
|             "SCP: writing 96 tpi {} file containing {} tracks\n", | ||||
|             (minSide == maxSide) ? "single sided" : "double sided", | ||||
|             _fileheader.end_track - _fileheader.start_track + 1); | ||||
|     } | ||||
|  | ||||
| 	} | ||||
|     ~ScpFluxSink() | ||||
|     { | ||||
|         uint32_t checksum = 0; | ||||
|         appendChecksum(checksum, | ||||
|             Bytes((const uint8_t*)&_fileheader, sizeof(_fileheader)) | ||||
|                 .slice(0x10)); | ||||
|         appendChecksum(checksum, _trackdata); | ||||
|         write_le32(_fileheader.checksum, checksum); | ||||
|  | ||||
| 	~ScpFluxSink() | ||||
| 	{ | ||||
| 		uint32_t checksum = 0; | ||||
| 		appendChecksum(checksum, | ||||
| 			Bytes((const uint8_t*) &_fileheader, sizeof(_fileheader)) | ||||
| 				.slice(0x10)); | ||||
| 		appendChecksum(checksum, _trackdata); | ||||
| 		write_le32(_fileheader.checksum, checksum); | ||||
|  | ||||
| 		std::cout << "SCP: writing output file...\n"; | ||||
| 		std::ofstream of(_config.filename(), std::ios::out | std::ios::binary); | ||||
| 		if (!of.is_open()) | ||||
| 			Error() << "cannot open output file"; | ||||
| 		of.write((const char*) &_fileheader, sizeof(_fileheader)); | ||||
| 		_trackdata.writeTo(of); | ||||
| 		of.close(); | ||||
| 	} | ||||
|         std::cout << "SCP: writing output file...\n"; | ||||
|         std::ofstream of(_config.filename(), std::ios::out | std::ios::binary); | ||||
|         if (!of.is_open()) | ||||
|             Error() << "cannot open output file"; | ||||
|         of.write((const char*)&_fileheader, sizeof(_fileheader)); | ||||
|         _trackdata.writeTo(of); | ||||
|         of.close(); | ||||
|     } | ||||
|  | ||||
| public: | ||||
| 	void writeFlux(int track, int head, const Fluxmap& fluxmap) override | ||||
| 	{ | ||||
| 		ByteWriter trackdataWriter(_trackdata); | ||||
| 		trackdataWriter.seekToEnd(); | ||||
| 		int strack = strackno(track, head); | ||||
|     void writeFlux(int track, int head, const Fluxmap& fluxmap) override | ||||
|     { | ||||
|         ByteWriter trackdataWriter(_trackdata); | ||||
|         trackdataWriter.seekToEnd(); | ||||
|         int strack = strackno(track, head); | ||||
|  | ||||
|                 if (strack >= std::size(_fileheader.track)) { | ||||
|                     std::cout << fmt::format("SCP: cannot write track {} head {}, " | ||||
|                             "there are not not enough Track Data Headers.\n", | ||||
|                             track, head); | ||||
|                     return; | ||||
|         if (strack >= std::size(_fileheader.track)) | ||||
|         { | ||||
|             std::cout << fmt::format( | ||||
|                 "SCP: cannot write track {} head {}, " | ||||
|                 "there are not not enough Track Data Headers.\n", | ||||
|                 track, | ||||
|                 head); | ||||
|             return; | ||||
|         } | ||||
|         ScpTrack trackheader = {0}; | ||||
|         trackheader.header.track_id[0] = 'T'; | ||||
|         trackheader.header.track_id[1] = 'R'; | ||||
|         trackheader.header.track_id[2] = 'K'; | ||||
|         trackheader.header.strack = strack; | ||||
|  | ||||
|         FluxmapReader fmr(fluxmap); | ||||
|         Bytes fluxdata; | ||||
|         ByteWriter fluxdataWriter(fluxdata); | ||||
|  | ||||
|         int revolution = | ||||
|             -1; // -1 indicates that we are before the first index pulse | ||||
|         if (_config.align_with_index()) | ||||
|         { | ||||
|             fmr.skipToEvent(F_BIT_INDEX); | ||||
|             revolution = 0; | ||||
|         } | ||||
|         unsigned revTicks = 0; | ||||
|         unsigned totalTicks = 0; | ||||
|         unsigned ticksSinceLastPulse = 0; | ||||
|         uint32_t startOffset = 0; | ||||
|         while (revolution < 5) | ||||
|         { | ||||
|             unsigned ticks; | ||||
|             int event; | ||||
|             fmr.getNextEvent(event, ticks); | ||||
|  | ||||
|             ticksSinceLastPulse += ticks; | ||||
|             totalTicks += ticks; | ||||
|             revTicks += ticks; | ||||
|  | ||||
|             // if we haven't output any revolutions yet by the end of the track, | ||||
|             // assume that the whole track is one rev | ||||
|             // also discard any duplicate index pulses | ||||
|             if (((fmr.eof() && revolution <= 0) || | ||||
|                     ((event & F_BIT_INDEX)) && revTicks > 0)) | ||||
|             { | ||||
|                 if (fmr.eof() && revolution == -1) | ||||
|                     revolution = 0; | ||||
|                 if (revolution >= 0) | ||||
|                 { | ||||
|                     auto* revheader = &trackheader.revolution[revolution]; | ||||
|                     write_le32( | ||||
|                         revheader->offset, startOffset + sizeof(ScpTrack)); | ||||
|                     write_le32(revheader->length, | ||||
|                         (fluxdataWriter.pos - startOffset) / 2); | ||||
|                     write_le32(revheader->index, revTicks * NS_PER_TICK / 25); | ||||
|                     revheader++; | ||||
|                 } | ||||
| 		ScpTrack trackheader = {0}; | ||||
| 		trackheader.header.track_id[0] = 'T'; | ||||
| 		trackheader.header.track_id[1] = 'R'; | ||||
| 		trackheader.header.track_id[2] = 'K'; | ||||
| 		trackheader.header.strack = strack; | ||||
|                 revolution++; | ||||
|                 revTicks = 0; | ||||
|                 startOffset = fluxdataWriter.pos; | ||||
|             } | ||||
|             if (fmr.eof()) | ||||
|                 break; | ||||
|  | ||||
| 		FluxmapReader fmr(fluxmap); | ||||
| 		Bytes fluxdata; | ||||
| 		ByteWriter fluxdataWriter(fluxdata); | ||||
|             if (event & F_BIT_PULSE) | ||||
|             { | ||||
|                 unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25; | ||||
|                 while (t >= 0x10000) | ||||
|                 { | ||||
|                     fluxdataWriter.write_be16(0); | ||||
|                     t -= 0x10000; | ||||
|                 } | ||||
|                 fluxdataWriter.write_be16(t); | ||||
|                 ticksSinceLastPulse = 0; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		int revolution = -1; // -1 indicates that we are before the first index pulse | ||||
| 		if (_config.align_with_index()) { | ||||
| 			fmr.skipToEvent(F_BIT_INDEX); | ||||
| 			revolution = 0; | ||||
| 		} | ||||
| 		unsigned revTicks = 0; | ||||
| 		unsigned totalTicks = 0; | ||||
| 		unsigned ticksSinceLastPulse = 0; | ||||
| 		uint32_t startOffset = 0; | ||||
| 		while (revolution < 5) | ||||
| 		{ | ||||
| 			unsigned ticks; | ||||
| 			int event; | ||||
| 			fmr.getNextEvent(event, ticks); | ||||
|         _fileheader.revolutions = revolution; | ||||
|         write_le32( | ||||
|             _fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader)); | ||||
|         trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader)); | ||||
|         trackdataWriter += fluxdata; | ||||
|     } | ||||
|  | ||||
| 			ticksSinceLastPulse += ticks; | ||||
| 			totalTicks += ticks; | ||||
| 			revTicks += ticks; | ||||
|  | ||||
| 			// if we haven't output any revolutions yet by the end of the track, | ||||
| 			// assume that the whole track is one rev | ||||
| 			// also discard any duplicate index pulses | ||||
| 			if (((fmr.eof() && revolution <= 0) || ((event & F_BIT_INDEX)) && revTicks > 0)) | ||||
| 			{ | ||||
| 				if (fmr.eof() && revolution == -1) | ||||
| 					revolution = 0; | ||||
| 				if (revolution >= 0) { | ||||
| 					auto* revheader = &trackheader.revolution[revolution]; | ||||
| 					write_le32(revheader->offset, startOffset + sizeof(ScpTrack)); | ||||
| 					write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2); | ||||
| 					write_le32(revheader->index, revTicks * NS_PER_TICK / 25); | ||||
| 					revheader++; | ||||
| 				} | ||||
| 				revolution++; | ||||
| 				revTicks = 0; | ||||
| 				startOffset = fluxdataWriter.pos; | ||||
| 			} | ||||
| 			if (fmr.eof()) break; | ||||
|  | ||||
| 			if (event & F_BIT_PULSE) | ||||
| 			{ | ||||
| 				unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25; | ||||
| 				while (t >= 0x10000) | ||||
| 				{ | ||||
| 					fluxdataWriter.write_be16(0); | ||||
| 					t -= 0x10000; | ||||
| 				} | ||||
| 				fluxdataWriter.write_be16(t); | ||||
| 				ticksSinceLastPulse = 0; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		_fileheader.revolutions = revolution; | ||||
| 		write_le32(_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader)); | ||||
| 		trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader)); | ||||
| 		trackdataWriter += fluxdata; | ||||
| 	} | ||||
|  | ||||
| 	operator std::string () const | ||||
| 	{ | ||||
| 		return fmt::format("scp({})", _config.filename()); | ||||
| 	} | ||||
|     operator std::string() const | ||||
|     { | ||||
|         return fmt::format("scp({})", _config.filename()); | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	const ScpFluxSinkProto& _config; | ||||
| 	ScpHeader _fileheader = {0}; | ||||
| 	Bytes _trackdata; | ||||
|     const ScpFluxSinkProto& _config; | ||||
|     ScpHeader _fileheader = {0}; | ||||
|     Bytes _trackdata; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(const ScpFluxSinkProto& config) | ||||
| std::unique_ptr<FluxSink> FluxSink::createScpFluxSink( | ||||
|     const ScpFluxSinkProto& config) | ||||
| { | ||||
|     return std::unique_ptr<FluxSink>(new ScpFluxSink(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -17,93 +17,137 @@ static bool ends_with(const std::string& value, const std::string& ending) | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config) | ||||
| { | ||||
| 	switch (config.source_case()) | ||||
| 	{ | ||||
| 		case FluxSourceProto::kDrive: | ||||
| 			return createHardwareFluxSource(config.drive()); | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case FluxSourceProto::DRIVE: | ||||
|             return createHardwareFluxSource(config.drive()); | ||||
|  | ||||
| 		case FluxSourceProto::kErase: | ||||
| 			return createEraseFluxSource(config.erase()); | ||||
|         case FluxSourceProto::ERASE: | ||||
|             return createEraseFluxSource(config.erase()); | ||||
|  | ||||
| 		case FluxSourceProto::kKryoflux: | ||||
| 			return createKryofluxFluxSource(config.kryoflux()); | ||||
|         case FluxSourceProto::KRYOFLUX: | ||||
|             return createKryofluxFluxSource(config.kryoflux()); | ||||
|  | ||||
| 		case FluxSourceProto::kTestPattern: | ||||
| 			return createTestPatternFluxSource(config.test_pattern()); | ||||
|         case FluxSourceProto::TEST_PATTERN: | ||||
|             return createTestPatternFluxSource(config.test_pattern()); | ||||
|  | ||||
| 		case FluxSourceProto::kScp: | ||||
| 			return createScpFluxSource(config.scp()); | ||||
|         case FluxSourceProto::SCP: | ||||
|             return createScpFluxSource(config.scp()); | ||||
|  | ||||
| 		case FluxSourceProto::kCwf: | ||||
| 			return createCwfFluxSource(config.cwf()); | ||||
|         case FluxSourceProto::CWF: | ||||
|             return createCwfFluxSource(config.cwf()); | ||||
|  | ||||
| 		case FluxSourceProto::kFl2: | ||||
| 			return createFl2FluxSource(config.fl2()); | ||||
|         case FluxSourceProto::FLUX: | ||||
|             return createFl2FluxSource(config.fl2()); | ||||
|  | ||||
| 		default: | ||||
| 			Error() << "bad input disk configuration"; | ||||
| 			return std::unique_ptr<FluxSource>(); | ||||
| 	} | ||||
|         case FluxSourceProto::FLX: | ||||
|             return createFlxFluxSource(config.flx()); | ||||
|  | ||||
|         default: | ||||
|             Error() << "bad input disk configuration"; | ||||
|             return std::unique_ptr<FluxSource>(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::string& filename) | ||||
| void FluxSource::updateConfigForFilename( | ||||
|     FluxSourceProto* proto, const std::string& filename) | ||||
| { | ||||
|  | ||||
| 	static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSourceProto*)>>> formats = | ||||
| 	{ | ||||
| 		{ std::regex("^(.*\\.a2r)$"),  [](auto& s, auto* proto) { }}, | ||||
| 		{ std::regex("^(.*\\.flux)$"),     [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.scp)$"),      [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.cwf)$"),      [](auto& s, auto* proto) { proto->mutable_cwf()->set_filename(s); }}, | ||||
| 		{ std::regex("^erase:$"),          [](auto& s, auto* proto) { proto->mutable_erase(); }}, | ||||
| 		{ std::regex("^kryoflux:(.*)$"),   [](auto& s, auto* proto) { proto->mutable_kryoflux()->set_directory(s); }}, | ||||
| 		{ std::regex("^testpattern:(.*)"), [](auto& s, auto* proto) { proto->mutable_test_pattern(); }}, | ||||
| 		{ std::regex("^drive:(.*)"),       [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }}, | ||||
| 	}; | ||||
|     static const std::vector<std::pair<std::regex, | ||||
|         std::function<void(const std::string&, FluxSourceProto*)>>> | ||||
|         formats = { | ||||
|             {std::regex("^(.*\\.flux)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::FLUX); | ||||
|                     proto->mutable_fl2()->set_filename(s); | ||||
|                 }}, | ||||
|             {std::regex("^(.*\\.scp)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::SCP); | ||||
|                     proto->mutable_scp()->set_filename(s); | ||||
|                 }}, | ||||
|             {std::regex("^(.*\\.cwf)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::CWF); | ||||
|                     proto->mutable_cwf()->set_filename(s); | ||||
|                 }}, | ||||
|             {std::regex("^erase:$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::ERASE); | ||||
|                 }}, | ||||
|             {std::regex("^kryoflux:(.*)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::KRYOFLUX); | ||||
|                     proto->mutable_kryoflux()->set_directory(s); | ||||
|                 }}, | ||||
|             {std::regex("^testpattern:(.*)"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::TEST_PATTERN); | ||||
|                 }}, | ||||
|             {std::regex("^drive:(.*)"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::DRIVE); | ||||
|                     config.mutable_drive()->set_drive(std::stoi(s)); | ||||
|                 }}, | ||||
|             {std::regex("^flx:(.*)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::FLX); | ||||
|                     proto->mutable_flx()->set_directory(s); | ||||
|                 }}, | ||||
|     }; | ||||
|  | ||||
| 	for (const auto& it : formats) | ||||
| 	{ | ||||
| 		std::smatch match; | ||||
| 		if (std::regex_match(filename, match, it.first)) | ||||
| 		{ | ||||
| 			it.second(match[1], proto); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|     for (const auto& it : formats) | ||||
|     { | ||||
|         std::smatch match; | ||||
|         if (std::regex_match(filename, match, it.first)) | ||||
|         { | ||||
|             it.second(match[1], proto); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	Error() << fmt::format("unrecognised flux filename '{}'", filename); | ||||
|     Error() << fmt::format("unrecognised flux filename '{}'", filename); | ||||
| } | ||||
|  | ||||
| class TrivialFluxSourceIterator : public FluxSourceIterator | ||||
| { | ||||
| public: | ||||
| 	TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int track, int head): | ||||
| 		_fluxSource(fluxSource), | ||||
| 		_track(track), | ||||
| 		_head(head) | ||||
| 	{} | ||||
|     TrivialFluxSourceIterator( | ||||
|         TrivialFluxSource* fluxSource, int track, int head): | ||||
|         _fluxSource(fluxSource), | ||||
|         _track(track), | ||||
|         _head(head) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| 	bool hasNext() const override | ||||
| 	{ | ||||
| 		return !!_fluxSource; | ||||
| 	} | ||||
|     bool hasNext() const override | ||||
|     { | ||||
|         return !!_fluxSource; | ||||
|     } | ||||
|  | ||||
| 	std::unique_ptr<const Fluxmap> next() override | ||||
| 	{ | ||||
| 		auto fluxmap = _fluxSource->readSingleFlux(_track, _head); | ||||
| 		_fluxSource = nullptr; | ||||
| 		return fluxmap; | ||||
| 	} | ||||
|     std::unique_ptr<const Fluxmap> next() override | ||||
|     { | ||||
|         auto fluxmap = _fluxSource->readSingleFlux(_track, _head); | ||||
|         _fluxSource = nullptr; | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
| 	TrivialFluxSource* _fluxSource; | ||||
| 	int _track; | ||||
| 	int _head; | ||||
|     TrivialFluxSource* _fluxSource; | ||||
|     int _track; | ||||
|     int _head; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int track, int head) | ||||
| std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux( | ||||
|     int track, int head) | ||||
| { | ||||
| 	return std::make_unique<TrivialFluxSourceIterator>(this, track, head); | ||||
|     return std::make_unique<TrivialFluxSourceIterator>(this, track, head); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class HardwareFluxSourceProto; | ||||
| class KryofluxFluxSourceProto; | ||||
| class ScpFluxSourceProto; | ||||
| class TestPatternFluxSourceProto; | ||||
| class FlxFluxSourceProto; | ||||
|  | ||||
| class FluxSourceIterator | ||||
| { | ||||
| @@ -33,6 +34,7 @@ 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); | ||||
|   | ||||
| @@ -30,15 +30,33 @@ message Fl2FluxSourceProto { | ||||
| 		(help) = ".fl2 file to read flux from"]; | ||||
| } | ||||
|  | ||||
| message FluxSourceProto { | ||||
| 	oneof source { | ||||
| 		HardwareFluxSourceProto drive = 2; | ||||
| 		TestPatternFluxSourceProto test_pattern = 3; | ||||
| 		EraseFluxSourceProto erase = 4; | ||||
| 		KryofluxFluxSourceProto kryoflux = 5; | ||||
| 		ScpFluxSourceProto scp = 6; | ||||
| 		CwfFluxSourceProto cwf = 7; | ||||
| 		Fl2FluxSourceProto fl2 = 8; | ||||
| 	} | ||||
| message FlxFluxSourceProto { | ||||
| 	optional string directory = 1 [(help) = "path to FLX stream directory"]; | ||||
| } | ||||
|  | ||||
| // NEXT: 11 | ||||
| message FluxSourceProto { | ||||
| 	enum FluxSourceType { | ||||
| 		NOT_SET = 0; | ||||
| 		DRIVE = 1; | ||||
| 		TEST_PATTERN = 2; | ||||
| 		ERASE = 3; | ||||
| 		KRYOFLUX = 4; | ||||
| 		SCP = 5; | ||||
| 		CWF = 6; | ||||
| 		FLUX = 7; | ||||
| 		FLX = 8; | ||||
| 	} | ||||
|  | ||||
| 	optional FluxSourceType type = 9 [default = NOT_SET, (help) = "flux source type"]; | ||||
|  | ||||
| 	optional HardwareFluxSourceProto drive = 2; | ||||
| 	optional TestPatternFluxSourceProto test_pattern = 3; | ||||
| 	optional EraseFluxSourceProto erase = 4; | ||||
| 	optional KryofluxFluxSourceProto kryoflux = 5; | ||||
| 	optional ScpFluxSourceProto scp = 6; | ||||
| 	optional CwfFluxSourceProto cwf = 7; | ||||
| 	optional Fl2FluxSourceProto fl2 = 8; | ||||
| 	optional FlxFluxSourceProto flx = 10; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										50
									
								
								lib/fluxsource/flx.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								lib/fluxsource/flx.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "kryoflux.h" | ||||
| #include "protocol.h" | ||||
| #include "lib/fluxsource/flx.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|  | ||||
|     /* Skip header. */ | ||||
|  | ||||
|     for (;;) | ||||
|     { | ||||
|         if (br.eof()) | ||||
|             Error() << fmt::format("malformed FLX stream"); | ||||
|         uint8_t b = br.read_8(); | ||||
|         if (b == 0) | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|     while (!br.eof()) | ||||
|     { | ||||
|         uint8_t b = br.read_8(); | ||||
|         switch (b) | ||||
|         { | ||||
|             case FLX_INDEX: | ||||
|                 fluxmap->appendIndex(); | ||||
|                 continue; | ||||
|  | ||||
|             case FLX_STOP: | ||||
|                 goto stop; | ||||
|  | ||||
|             default: | ||||
|             { | ||||
|                 if (b < 32) | ||||
|                     Error() << fmt::format("unknown FLX opcode 0x{:2x}", b); | ||||
|                 nanoseconds_t interval = b * FLX_TICK_NS; | ||||
|                 fluxmap->appendInterval(interval / NS_PER_TICK); | ||||
|                 fluxmap->appendPulse(); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| stop: | ||||
|  | ||||
|     return fluxmap; | ||||
| } | ||||
							
								
								
									
										16
									
								
								lib/fluxsource/flx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/fluxsource/flx.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef FLX_H | ||||
| #define FLX_H | ||||
|  | ||||
| #define FLX_TICK_NS 40 /* ns per tick */ | ||||
|  | ||||
| /* Special FLX opcodes */ | ||||
|  | ||||
| enum | ||||
| { | ||||
|     FLX_INDEX = 0x08, | ||||
|     FLX_STOP = 0x0d | ||||
| }; | ||||
|  | ||||
| extern std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes); | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										32
									
								
								lib/fluxsource/flxfluxsource.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/fluxsource/flxfluxsource.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/fluxsource/fluxsource.pb.h" | ||||
| #include "lib/fluxsource/fluxsource.h" | ||||
| #include "lib/fluxsource/flx.h" | ||||
|  | ||||
| class FlxFluxSource : public TrivialFluxSource | ||||
| { | ||||
| public: | ||||
|     FlxFluxSource(const FlxFluxSourceProto& config): _path(config.directory()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override | ||||
|     { | ||||
|         std::string path = | ||||
|             fmt::format("{}/@TR{:02}S{}@.FLX", _path, track, side + 1); | ||||
|         return readFlxBytes(Bytes::readFromFile(path)); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|  | ||||
| private: | ||||
|     const std::string _path; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::createFlxFluxSource( | ||||
|     const FlxFluxSourceProto& config) | ||||
| { | ||||
|     return std::make_unique<FlxFluxSource>(config); | ||||
| } | ||||
| @@ -14,39 +14,39 @@ | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config) | ||||
| { | ||||
|     switch (config.format_case()) | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case ImageReaderProto::kDim: | ||||
|         case ImageReaderProto::DIM: | ||||
|             return ImageReader::createDimImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kD88: | ||||
|         case ImageReaderProto::D88: | ||||
|             return ImageReader::createD88ImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kFdi: | ||||
|         case ImageReaderProto::FDI: | ||||
|             return ImageReader::createFdiImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kImd: | ||||
|         case ImageReaderProto::IMD: | ||||
|             return ImageReader::createIMDImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kImg: | ||||
|         case ImageReaderProto::IMG: | ||||
|             return ImageReader::createImgImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kDiskcopy: | ||||
|         case ImageReaderProto::DISKCOPY: | ||||
|             return ImageReader::createDiskCopyImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kJv3: | ||||
|         case ImageReaderProto::JV3: | ||||
|             return ImageReader::createJv3ImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kD64: | ||||
|         case ImageReaderProto::D64: | ||||
|             return ImageReader::createD64ImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kNfd: | ||||
|         case ImageReaderProto::NFD: | ||||
|             return ImageReader::createNFDImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kNsi: | ||||
|         case ImageReaderProto::NSI: | ||||
|             return ImageReader::createNsiImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::kTd0: | ||||
|         case ImageReaderProto::TD0: | ||||
|             return ImageReader::createTd0ImageReader(config); | ||||
|  | ||||
|         default: | ||||
| @@ -61,23 +61,23 @@ void ImageReader::updateConfigForFilename( | ||||
|     static const std::map<std::string, std::function<void(ImageReaderProto*)>> | ||||
|         formats = { | ||||
|   // clang-format off | ||||
| 		{".adf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d64",      [](auto* proto) { proto->mutable_d64(); }}, | ||||
| 		{".d81",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d88",      [](auto* proto) { proto->mutable_d88(); }}, | ||||
| 		{".dim",      [](auto* proto) { proto->mutable_dim(); }}, | ||||
| 		{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }}, | ||||
| 		{".dsk",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".fdi",      [](auto* proto) { proto->mutable_fdi(); }}, | ||||
| 		{".imd",      [](auto* proto) { proto->mutable_imd(); }}, | ||||
| 		{".img",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".jv3",      [](auto* proto) { proto->mutable_jv3(); }}, | ||||
| 		{".nfd",      [](auto* proto) { proto->mutable_nfd(); }}, | ||||
| 		{".nsi",      [](auto* proto) { proto->mutable_nsi(); }}, | ||||
| 		{".st",       [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".td0",      [](auto* proto) { proto->mutable_td0(); }}, | ||||
| 		{".vgi",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".adf",      [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
| 		{".d64",      [](auto* proto) { proto->set_type(ImageReaderProto::D64); }}, | ||||
| 		{".d81",      [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
| 		{".d88",      [](auto* proto) { proto->set_type(ImageReaderProto::D88); }}, | ||||
| 		{".dim",      [](auto* proto) { proto->set_type(ImageReaderProto::DIM); }}, | ||||
| 		{".diskcopy", [](auto* proto) { proto->set_type(ImageReaderProto::DISKCOPY); }}, | ||||
| 		{".dsk",      [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
| 		{".fdi",      [](auto* proto) { proto->set_type(ImageReaderProto::FDI); }}, | ||||
| 		{".imd",      [](auto* proto) { proto->set_type(ImageReaderProto::IMD); }}, | ||||
| 		{".img",      [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
| 		{".jv3",      [](auto* proto) { proto->set_type(ImageReaderProto::JV3); }}, | ||||
| 		{".nfd",      [](auto* proto) { proto->set_type(ImageReaderProto::NFD); }}, | ||||
| 		{".nsi",      [](auto* proto) { proto->set_type(ImageReaderProto::NSI); }}, | ||||
| 		{".st",       [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
| 		{".td0",      [](auto* proto) { proto->set_type(ImageReaderProto::TD0); }}, | ||||
| 		{".vgi",      [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
| 		{".xdf",      [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }}, | ||||
|   // clang-format on | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -13,9 +13,9 @@ message Td0InputProto {} | ||||
| message DimInputProto {} | ||||
| message FdiInputProto {} | ||||
| message D88InputProto {} | ||||
| message NFDInputProto {} | ||||
| message NfdInputProto {} | ||||
|  | ||||
| // NEXT_TAG: 14 | ||||
| // NEXT_TAG: 15 | ||||
| message ImageReaderProto | ||||
| { | ||||
|     optional string filename = 1 [ (help) = "filename of input sector image" ]; | ||||
| @@ -24,18 +24,32 @@ message ImageReaderProto | ||||
|         default = false | ||||
|     ]; | ||||
|  | ||||
|     oneof format | ||||
|     { | ||||
|         ImgInputOutputProto img = 2; | ||||
|         DiskCopyInputProto diskcopy = 3; | ||||
|         ImdInputProto imd = 4; | ||||
|         Jv3InputProto jv3 = 5; | ||||
|         D64InputProto d64 = 6; | ||||
|         NsiInputProto nsi = 7; | ||||
|         Td0InputProto td0 = 8; | ||||
|         DimInputProto dim = 9; | ||||
|         FdiInputProto fdi = 10; | ||||
|         D88InputProto d88 = 11; | ||||
|         NFDInputProto nfd = 12; | ||||
|     } | ||||
| 	enum ImageReaderType { | ||||
| 		NOT_SET = 0; | ||||
| 		IMG = 1; | ||||
| 		DISKCOPY = 2; | ||||
| 		IMD = 3; | ||||
| 		JV3 = 4; | ||||
| 		D64 = 5; | ||||
| 		NSI = 6; | ||||
| 		TD0 = 7; | ||||
| 		DIM = 8; | ||||
| 		FDI = 9; | ||||
| 		D88 = 10; | ||||
| 		NFD = 11; | ||||
| 	} | ||||
|  | ||||
| 	optional ImageReaderType type = 14 [default = NOT_SET, (help) = "input image type"]; | ||||
|  | ||||
| 	optional ImgInputOutputProto img = 2; | ||||
| 	optional DiskCopyInputProto diskcopy = 3; | ||||
| 	optional ImdInputProto imd = 4; | ||||
| 	optional Jv3InputProto jv3 = 5; | ||||
| 	optional D64InputProto d64 = 6; | ||||
| 	optional NsiInputProto nsi = 7; | ||||
| 	optional Td0InputProto td0 = 8; | ||||
| 	optional DimInputProto dim = 9; | ||||
| 	optional FdiInputProto fdi = 10; | ||||
| 	optional D88InputProto d88 = 11; | ||||
| 	optional NfdInputProto nfd = 12; | ||||
| } | ||||
|   | ||||
| @@ -14,30 +14,30 @@ | ||||
|  | ||||
| std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config) | ||||
| { | ||||
|     switch (config.format_case()) | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case ImageWriterProto::kImg: | ||||
|         case ImageWriterProto::IMG: | ||||
|             return ImageWriter::createImgImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kD64: | ||||
|         case ImageWriterProto::D64: | ||||
|             return ImageWriter::createD64ImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kLdbs: | ||||
|         case ImageWriterProto::LDBS: | ||||
|             return ImageWriter::createLDBSImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kDiskcopy: | ||||
|         case ImageWriterProto::DISKCOPY: | ||||
|             return ImageWriter::createDiskCopyImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kNsi: | ||||
|         case ImageWriterProto::NSI: | ||||
|             return ImageWriter::createNsiImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kRaw: | ||||
|         case ImageWriterProto::RAW: | ||||
|             return ImageWriter::createRawImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kD88: | ||||
|         case ImageWriterProto::D88: | ||||
|             return ImageWriter::createD88ImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::kImd: | ||||
|         case ImageWriterProto::IMD: | ||||
|             return ImageWriter::createImdImageWriter(config); | ||||
|  | ||||
|         default: | ||||
| @@ -52,20 +52,20 @@ void ImageWriter::updateConfigForFilename( | ||||
|     static const std::map<std::string, std::function<void(ImageWriterProto*)>> | ||||
|         formats = { | ||||
|   // clang-format off | ||||
| 		{".adf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d64",      [](auto* proto) { proto->mutable_d64(); }}, | ||||
| 		{".d81",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d88",      [](auto* proto) { proto->mutable_d88(); }}, | ||||
| 		{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }}, | ||||
| 		{".dsk",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".img",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".imd",      [](auto* proto) { proto->mutable_imd(); }}, | ||||
| 		{".ldbs",     [](auto* proto) { proto->mutable_ldbs(); }}, | ||||
| 		{".nsi",      [](auto* proto) { proto->mutable_nsi(); }}, | ||||
| 		{".raw",      [](auto* proto) { proto->mutable_raw(); }}, | ||||
| 		{".st",       [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".vgi",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".adf",      [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
| 		{".d64",      [](auto* proto) { proto->set_type(ImageWriterProto::D64); }}, | ||||
| 		{".d81",      [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
| 		{".d88",      [](auto* proto) { proto->set_type(ImageWriterProto::D88); }}, | ||||
| 		{".diskcopy", [](auto* proto) { proto->set_type(ImageWriterProto::DISKCOPY); }}, | ||||
| 		{".dsk",      [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
| 		{".img",      [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
| 		{".imd",      [](auto* proto) { proto->set_type(ImageWriterProto::IMD); }}, | ||||
| 		{".ldbs",     [](auto* proto) { proto->set_type(ImageWriterProto::LDBS); }}, | ||||
| 		{".nsi",      [](auto* proto) { proto->set_type(ImageWriterProto::NSI); }}, | ||||
| 		{".raw",      [](auto* proto) { proto->set_type(ImageWriterProto::RAW); }}, | ||||
| 		{".st",       [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
| 		{".vgi",      [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
| 		{".xdf",      [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }}, | ||||
|   // clang-format on | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -63,24 +63,35 @@ message ImdOutputProto | ||||
|     optional string comment = 3 [ (help) = "comment to set in IMD file" ]; | ||||
| } | ||||
|  | ||||
| // NEXT_TAG: 11 | ||||
| // NEXT_TAG: 12 | ||||
| message ImageWriterProto | ||||
| { | ||||
|     enum ImageWriterType { | ||||
|         NOT_SET = 0; | ||||
|         IMG = 1; | ||||
|         D64 = 2; | ||||
|         LDBS = 3; | ||||
|         DISKCOPY = 4; | ||||
|         NSI = 5; | ||||
|         RAW = 6; | ||||
|         D88 = 7; | ||||
|         IMD = 8; | ||||
|     } | ||||
|  | ||||
|     optional string filename = 1 [ (help) = "filename of output sector image" ]; | ||||
|     optional bool filesystem_sector_order = 10 [ | ||||
|         (help) = "read/write sector image in filesystem order", | ||||
|         default = false | ||||
|     ]; | ||||
|  | ||||
|     oneof format | ||||
|     { | ||||
|         ImgInputOutputProto img = 2; | ||||
|         D64OutputProto d64 = 3; | ||||
|         LDBSOutputProto ldbs = 4; | ||||
|         DiskCopyOutputProto diskcopy = 5; | ||||
|         NsiOutputProto nsi = 6; | ||||
|         RawOutputProto raw = 7; | ||||
|         D88OutputProto d88 = 8; | ||||
|         ImdOutputProto imd = 9; | ||||
|     } | ||||
|     optional ImageWriterType type = 11 [ default = NOT_SET, (help) = "image writer type" ]; | ||||
|  | ||||
|     optional ImgInputOutputProto img = 2; | ||||
|     optional D64OutputProto d64 = 3; | ||||
|     optional LDBSOutputProto ldbs = 4; | ||||
|     optional DiskCopyOutputProto diskcopy = 5; | ||||
|     optional NsiOutputProto nsi = 6; | ||||
|     optional RawOutputProto raw = 7; | ||||
|     optional D88OutputProto d88 = 8; | ||||
|     optional ImdOutputProto imd = 9; | ||||
| } | ||||
|   | ||||
							
								
								
									
										126
									
								
								lib/layout.cc
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								lib/layout.cc
									
									
									
									
									
								
							| @@ -4,9 +4,6 @@ | ||||
| #include "lib/environment.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| static Local<std::map<std::pair<int, int>, std::shared_ptr<TrackInfo>>> | ||||
|     layoutCache; | ||||
|  | ||||
| static unsigned getTrackStep() | ||||
| { | ||||
|     unsigned track_step = | ||||
| @@ -61,6 +58,21 @@ 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) | ||||
| { | ||||
| 	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); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::vector<std::pair<int, int>> Layout::getTrackOrdering( | ||||
|     unsigned guessedTracks, unsigned guessedSides) | ||||
| { | ||||
| @@ -128,66 +140,62 @@ std::vector<unsigned> Layout::expandSectorList( | ||||
| std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack( | ||||
|     unsigned logicalTrack, unsigned logicalSide) | ||||
| { | ||||
|     auto& layout = (*layoutCache)[std::make_pair(logicalTrack, logicalSide)]; | ||||
|     if (!layout) | ||||
|     auto trackInfo = std::make_shared<TrackInfo>(); | ||||
|  | ||||
|     LayoutProto::LayoutdataProto layoutdata; | ||||
|     for (const auto& f : config.layout().layoutdata()) | ||||
|     { | ||||
|     	layout = std::make_shared<TrackInfo>(); | ||||
|         if (f.has_track() && f.has_up_to_track() && | ||||
|             ((logicalTrack < f.track()) || | ||||
|              (logicalTrack > f.up_to_track()))) | ||||
|             continue; | ||||
|         if (f.has_track() && !f.has_up_to_track() && | ||||
|             (logicalTrack != f.track())) | ||||
|             continue; | ||||
|         if (f.has_side() && (f.side() != logicalSide)) | ||||
|             continue; | ||||
|  | ||||
|         LayoutProto::LayoutdataProto layoutdata; | ||||
|         for (const auto& f : config.layout().layoutdata()) | ||||
|         { | ||||
|             if (f.has_track() && f.has_up_to_track() && | ||||
|                 ((logicalTrack < f.track()) || | ||||
|                     (logicalTrack > f.up_to_track()))) | ||||
|                 continue; | ||||
|             if (f.has_track() && !f.has_up_to_track() && | ||||
|                 (logicalTrack != f.track())) | ||||
|                 continue; | ||||
|             if (f.has_side() && (f.side() != logicalSide)) | ||||
|                 continue; | ||||
|  | ||||
|             layoutdata.MergeFrom(f); | ||||
|         } | ||||
|  | ||||
|         layout->numTracks = config.layout().tracks(); | ||||
|         layout->numSides = config.layout().sides(); | ||||
|         layout->sectorSize = layoutdata.sector_size(); | ||||
|         layout->logicalTrack = logicalTrack; | ||||
|         layout->logicalSide = logicalSide; | ||||
|         layout->physicalTrack = remapTrackLogicalToPhysical(logicalTrack); | ||||
|         layout->physicalSide = logicalSide ^ config.layout().swap_sides(); | ||||
|         layout->groupSize = getTrackStep(); | ||||
|         layout->diskSectorOrder = expandSectorList(layoutdata.physical()); | ||||
|         layout->logicalSectorOrder = layout->diskSectorOrder; | ||||
|         std::sort( | ||||
|             layout->diskSectorOrder.begin(), layout->diskSectorOrder.end()); | ||||
|         layout->numSectors = layout->logicalSectorOrder.size(); | ||||
|  | ||||
|         if (layoutdata.has_filesystem()) | ||||
|         { | ||||
|             layout->filesystemSectorOrder = | ||||
|                 expandSectorList(layoutdata.filesystem()); | ||||
|             if (layout->filesystemSectorOrder.size() != layout->numSectors) | ||||
|                 Error() | ||||
|                     << "filesystem sector order list doesn't contain the right " | ||||
|                        "number of sectors"; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             for (unsigned sectorId : layout->logicalSectorOrder) | ||||
|                 layout->filesystemSectorOrder.push_back(sectorId); | ||||
|         } | ||||
|  | ||||
|         for (int i = 0; i < layout->numSectors; i++) | ||||
|         { | ||||
|             unsigned f = layout->logicalSectorOrder[i]; | ||||
|             unsigned l = layout->filesystemSectorOrder[i]; | ||||
|             layout->filesystemToLogicalSectorMap[f] = l; | ||||
|             layout->logicalToFilesystemSectorMap[l] = f; | ||||
|         } | ||||
|         layoutdata.MergeFrom(f); | ||||
|     } | ||||
|  | ||||
|     return layout; | ||||
|     trackInfo->numTracks = config.layout().tracks(); | ||||
|     trackInfo->numSides = config.layout().sides(); | ||||
|     trackInfo->sectorSize = layoutdata.sector_size(); | ||||
|     trackInfo->logicalTrack = logicalTrack; | ||||
|     trackInfo->logicalSide = logicalSide; | ||||
|     trackInfo->physicalTrack = remapTrackLogicalToPhysical(logicalTrack); | ||||
|     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(); | ||||
|  | ||||
|     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"; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         for (unsigned sectorId : trackInfo->logicalSectorOrder) | ||||
|             trackInfo->filesystemSectorOrder.push_back(sectorId); | ||||
|     } | ||||
|  | ||||
|     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; | ||||
|     } | ||||
|  | ||||
|     return trackInfo; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical( | ||||
|   | ||||
| @@ -23,10 +23,15 @@ public: | ||||
|     static unsigned remapSideLogicalToPhysical(unsigned logicalSide); | ||||
|  | ||||
|     /* Uses the layout and current track and heads settings to determine | ||||
|      * which Locations are going to be read from or written to. 8/ | ||||
|      * which Locations are going to be read from or written to. | ||||
|      */ | ||||
|     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); | ||||
|  | ||||
|     /* Returns a series of <track, side> pairs representing the filesystem | ||||
|      * ordering of the disk, in logical numbers. */ | ||||
|     static std::vector<std::pair<int, int>> getTrackOrdering( | ||||
|   | ||||
							
								
								
									
										342
									
								
								lib/proto.cc
									
									
									
									
									
								
							
							
						
						
									
										342
									
								
								lib/proto.cc
									
									
									
									
									
								
							| @@ -4,229 +4,245 @@ | ||||
| #include "fmt/format.h" | ||||
| #include <regex> | ||||
|  | ||||
| ConfigProto config = []() { | ||||
| 	ConfigProto config; | ||||
| 	config.mutable_drive()->set_drive(0); | ||||
| 	config.mutable_drive()->set_drive(0); | ||||
| 	return config; | ||||
| ConfigProto config = []() | ||||
| { | ||||
|     ConfigProto config; | ||||
|     config.mutable_drive()->set_drive(0); | ||||
|     config.mutable_drive()->set_drive(0); | ||||
|     return config; | ||||
| }(); | ||||
|  | ||||
| static double toDouble(const std::string& value) | ||||
| { | ||||
| 	size_t idx; | ||||
| 	double d = std::stod(value, &idx); | ||||
| 	if (value[idx] != '\0') | ||||
| 		Error() << fmt::format("invalid number '{}'", value); | ||||
| 	return d; | ||||
|     size_t idx; | ||||
|     double d = std::stod(value, &idx); | ||||
|     if (value[idx] != '\0') | ||||
|         Error() << fmt::format("invalid number '{}'", value); | ||||
|     return d; | ||||
| } | ||||
|  | ||||
| static int64_t toInt64(const std::string& value) | ||||
| { | ||||
| 	size_t idx; | ||||
| 	int64_t d = std::stoll(value, &idx); | ||||
| 	if (value[idx] != '\0') | ||||
| 		Error() << fmt::format("invalid number '{}'", value); | ||||
| 	return d; | ||||
|     size_t idx; | ||||
|     int64_t d = std::stoll(value, &idx); | ||||
|     if (value[idx] != '\0') | ||||
|         Error() << fmt::format("invalid number '{}'", value); | ||||
|     return d; | ||||
| } | ||||
|  | ||||
| static uint64_t toUint64(const std::string& value) | ||||
| { | ||||
| 	size_t idx; | ||||
| 	uint64_t d = std::stoull(value, &idx); | ||||
| 	if (value[idx] != '\0') | ||||
| 		Error() << fmt::format("invalid number '{}'", value); | ||||
| 	return d; | ||||
|     size_t idx; | ||||
|     uint64_t d = std::stoull(value, &idx); | ||||
|     if (value[idx] != '\0') | ||||
|         Error() << fmt::format("invalid number '{}'", value); | ||||
|     return d; | ||||
| } | ||||
|  | ||||
| void setRange(RangeProto* range, const std::string& data) | ||||
| { | ||||
| 	static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?"); | ||||
|     static const std::regex DATA_REGEX( | ||||
|         "([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?"); | ||||
|  | ||||
| 	std::smatch dmatch; | ||||
| 	if (!std::regex_match(data, dmatch, DATA_REGEX)) | ||||
| 		Error() << "invalid range '" << data << "'"; | ||||
| 	 | ||||
| 	int start = std::stoi(dmatch[1]); | ||||
| 	range->set_start(start); | ||||
| 	range->set_end(start); | ||||
| 	range->clear_step(); | ||||
| 	if (!dmatch[2].str().empty()) | ||||
| 		range->set_end(std::stoi(dmatch[2])); | ||||
| 	if (!dmatch[3].str().empty()) | ||||
| 		range->set_end(std::stoi(dmatch[3]) - range->start()); | ||||
| 	if (!dmatch[4].str().empty()) | ||||
| 		range->set_step(std::stoi(dmatch[4])); | ||||
|     std::smatch dmatch; | ||||
|     if (!std::regex_match(data, dmatch, DATA_REGEX)) | ||||
|         Error() << "invalid range '" << data << "'"; | ||||
|  | ||||
|     int start = std::stoi(dmatch[1]); | ||||
|     range->set_start(start); | ||||
|     range->set_end(start); | ||||
|     range->clear_step(); | ||||
|     if (!dmatch[2].str().empty()) | ||||
|         range->set_end(std::stoi(dmatch[2])); | ||||
|     if (!dmatch[3].str().empty()) | ||||
|         range->set_end(std::stoi(dmatch[3]) - range->start()); | ||||
|     if (!dmatch[4].str().empty()) | ||||
|         range->set_step(std::stoi(dmatch[4])); | ||||
| } | ||||
|  | ||||
| ProtoField resolveProtoPath(google::protobuf::Message* message, const std::string& path) | ||||
| ProtoField resolveProtoPath( | ||||
|     google::protobuf::Message* message, const std::string& path) | ||||
| { | ||||
| 	std::string::size_type dot = path.rfind('.'); | ||||
| 	std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot); | ||||
| 	std::string trailing = (dot == std::string::npos) ? path : path.substr(dot+1); | ||||
|     std::string::size_type dot = path.rfind('.'); | ||||
|     std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot); | ||||
|     std::string trailing = | ||||
|         (dot == std::string::npos) ? path : path.substr(dot + 1); | ||||
|  | ||||
| 	const auto* descriptor = message->GetDescriptor(); | ||||
|     const auto* descriptor = message->GetDescriptor(); | ||||
|  | ||||
| 	std::string item; | ||||
|     std::string item; | ||||
|     std::stringstream ss(leading); | ||||
|     while (std::getline(ss, item, '.')) | ||||
| 	{ | ||||
| 		const auto* field = descriptor->FindFieldByName(item); | ||||
| 		if (!field) | ||||
| 			Error() << fmt::format("no such config field '{}' in '{}'", item, path); | ||||
| 		if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE) | ||||
| 			Error() << fmt::format("config field '{}' in '{}' is not a message", item, path); | ||||
|     { | ||||
|         const auto* field = descriptor->FindFieldByName(item); | ||||
|         if (!field) | ||||
|             Error() << fmt::format( | ||||
|                 "no such config field '{}' in '{}'", item, path); | ||||
|         if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE) | ||||
|             Error() << fmt::format( | ||||
|                 "config field '{}' in '{}' is not a message", item, path); | ||||
|  | ||||
| 		const auto* reflection = message->GetReflection(); | ||||
| 		switch (field->label()) | ||||
| 		{ | ||||
| 			case google::protobuf::FieldDescriptor::LABEL_OPTIONAL: | ||||
| 				message = reflection->MutableMessage(message, field); | ||||
| 				break; | ||||
|         const auto* reflection = message->GetReflection(); | ||||
|         switch (field->label()) | ||||
|         { | ||||
|             case google::protobuf::FieldDescriptor::LABEL_OPTIONAL: | ||||
|                 message = reflection->MutableMessage(message, field); | ||||
|                 break; | ||||
|  | ||||
| 			case google::protobuf::FieldDescriptor::LABEL_REPEATED: | ||||
| 				if (reflection->FieldSize(*message, field) == 0) | ||||
| 					message = reflection->AddMessage(message, field); | ||||
| 				else | ||||
| 					message = reflection->MutableRepeatedMessage(message, field, 0); | ||||
| 				break; | ||||
|             case google::protobuf::FieldDescriptor::LABEL_REPEATED: | ||||
|                 if (reflection->FieldSize(*message, field) == 0) | ||||
|                     message = reflection->AddMessage(message, field); | ||||
|                 else | ||||
|                     message = | ||||
|                         reflection->MutableRepeatedMessage(message, field, 0); | ||||
|                 break; | ||||
|  | ||||
| 			default: | ||||
| 				Error() << "bad proto label " << field->label(); | ||||
| 		} | ||||
|             default: | ||||
|                 Error() << "bad proto label " << field->label(); | ||||
|         } | ||||
|  | ||||
| 		descriptor = message->GetDescriptor(); | ||||
|         descriptor = message->GetDescriptor(); | ||||
|     } | ||||
|  | ||||
| 	const auto* field = descriptor->FindFieldByName(trailing); | ||||
| 	if (!field) | ||||
| 		Error() << fmt::format("no such config field '{}' in '{}'", trailing, path); | ||||
|     const auto* field = descriptor->FindFieldByName(trailing); | ||||
|     if (!field) | ||||
|         Error() << fmt::format( | ||||
|             "no such config field '{}' in '{}'", trailing, path); | ||||
|  | ||||
| 	return std::make_pair(message, field); | ||||
|     return std::make_pair(message, field); | ||||
| } | ||||
|  | ||||
| void setProtoFieldFromString(ProtoField& protoField, const std::string& value) | ||||
| { | ||||
| 	google::protobuf::Message* message = protoField.first; | ||||
| 	const google::protobuf::FieldDescriptor* field = protoField.second; | ||||
|     google::protobuf::Message* message = protoField.first; | ||||
|     const google::protobuf::FieldDescriptor* field = protoField.second; | ||||
|  | ||||
| 	const auto* reflection = message->GetReflection(); | ||||
| 	switch (field->type()) | ||||
| 	{ | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_DOUBLE: | ||||
| 			reflection->SetDouble(message, field, toDouble(value)); | ||||
| 			break; | ||||
|     const auto* reflection = message->GetReflection(); | ||||
|     switch (field->type()) | ||||
|     { | ||||
|         case google::protobuf::FieldDescriptor::TYPE_DOUBLE: | ||||
|             reflection->SetDouble(message, field, toDouble(value)); | ||||
|             break; | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_INT32: | ||||
| 			reflection->SetInt32(message, field, toInt64(value)); | ||||
| 			break; | ||||
|         case google::protobuf::FieldDescriptor::TYPE_INT32: | ||||
|             reflection->SetInt32(message, field, toInt64(value)); | ||||
|             break; | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_INT64: | ||||
| 			reflection->SetInt64(message, field, toInt64(value)); | ||||
| 			break; | ||||
|         case google::protobuf::FieldDescriptor::TYPE_INT64: | ||||
|             reflection->SetInt64(message, field, toInt64(value)); | ||||
|             break; | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_UINT32: | ||||
| 			reflection->SetUInt32(message, field, toUint64(value)); | ||||
| 			break; | ||||
|         case google::protobuf::FieldDescriptor::TYPE_UINT32: | ||||
|             reflection->SetUInt32(message, field, toUint64(value)); | ||||
|             break; | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_UINT64: | ||||
| 			reflection->SetUInt64(message, field, toUint64(value)); | ||||
| 			break; | ||||
|         case google::protobuf::FieldDescriptor::TYPE_UINT64: | ||||
|             reflection->SetUInt64(message, field, toUint64(value)); | ||||
|             break; | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_STRING: | ||||
| 			reflection->SetString(message, field, value); | ||||
| 			break; | ||||
|         case google::protobuf::FieldDescriptor::TYPE_STRING: | ||||
|             reflection->SetString(message, field, value); | ||||
|             break; | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_BOOL: | ||||
| 		{ | ||||
| 			static const std::map<std::string, bool> boolvalues = { | ||||
| 				{ "false", false }, | ||||
| 				{ "f",     false }, | ||||
| 				{ "no",    false }, | ||||
| 				{ "n",     false }, | ||||
| 				{ "0",     false }, | ||||
| 				{ "true",  true }, | ||||
| 				{ "t",     true }, | ||||
| 				{ "yes",   true }, | ||||
| 				{ "y",     true }, | ||||
| 				{ "1",     true }, | ||||
| 			}; | ||||
|         case google::protobuf::FieldDescriptor::TYPE_BOOL: | ||||
|         { | ||||
|             static const std::map<std::string, bool> boolvalues = { | ||||
|                 {"false", false}, | ||||
|                 {"f",     false}, | ||||
|                 {"no",    false}, | ||||
|                 {"n",     false}, | ||||
|                 {"0",     false}, | ||||
|                 {"true",  true }, | ||||
|                 {"t",     true }, | ||||
|                 {"yes",   true }, | ||||
|                 {"y",     true }, | ||||
|                 {"1",     true }, | ||||
|             }; | ||||
|  | ||||
| 			const auto& it = boolvalues.find(value); | ||||
| 			if (it == boolvalues.end()) | ||||
| 				Error() << "invalid boolean value"; | ||||
| 			reflection->SetBool(message, field, it->second); | ||||
| 			break; | ||||
| 		} | ||||
|             const auto& it = boolvalues.find(value); | ||||
|             if (it == boolvalues.end()) | ||||
|                 Error() << "invalid boolean value"; | ||||
|             reflection->SetBool(message, field, it->second); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_ENUM: | ||||
| 		{ | ||||
| 			const auto* enumfield = field->enum_type(); | ||||
| 			const auto* enumvalue = enumfield->FindValueByName(value); | ||||
| 			if (!enumvalue) | ||||
| 				Error() << fmt::format("unrecognised enum value '{}'", value); | ||||
|         case google::protobuf::FieldDescriptor::TYPE_ENUM: | ||||
|         { | ||||
|             const auto* enumfield = field->enum_type(); | ||||
|             const auto* enumvalue = enumfield->FindValueByName(value); | ||||
|             if (!enumvalue) | ||||
|                 Error() << fmt::format("unrecognised enum value '{}'", value); | ||||
|  | ||||
| 			reflection->SetEnum(message, field, enumvalue); | ||||
| 			break; | ||||
| 		} | ||||
|             reflection->SetEnum(message, field, enumvalue); | ||||
|             break; | ||||
|         } | ||||
|  | ||||
| 		case google::protobuf::FieldDescriptor::TYPE_MESSAGE: | ||||
| 			if (field->message_type() == RangeProto::descriptor()) | ||||
| 			{ | ||||
| 				setRange((RangeProto*)reflection->MutableMessage(message, field), value); | ||||
| 				break; | ||||
| 			} | ||||
| 			if (field->containing_oneof() && value.empty()) | ||||
| 			{ | ||||
| 				reflection->MutableMessage(message, field); | ||||
| 				break; | ||||
| 			} | ||||
| 			/* fall through */ | ||||
| 		default: | ||||
| 			Error() << "can't set this config value type"; | ||||
| 	} | ||||
|         case google::protobuf::FieldDescriptor::TYPE_MESSAGE: | ||||
|             if (field->message_type() == RangeProto::descriptor()) | ||||
|             { | ||||
|                 setRange( | ||||
|                     (RangeProto*)reflection->MutableMessage(message, field), | ||||
|                     value); | ||||
|                 break; | ||||
|             } | ||||
|             if (field->containing_oneof() && value.empty()) | ||||
|             { | ||||
|                 reflection->MutableMessage(message, field); | ||||
|                 break; | ||||
|             } | ||||
|             /* fall through */ | ||||
|         default: | ||||
|             Error() << "can't set this config value type"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void setProtoByString(google::protobuf::Message* message, const std::string& path, const std::string& value) | ||||
| void setProtoByString(google::protobuf::Message* message, | ||||
|     const std::string& path, | ||||
|     const std::string& value) | ||||
| { | ||||
| 	ProtoField protoField = resolveProtoPath(message, path); | ||||
| 	setProtoFieldFromString(protoField, value); | ||||
|     ProtoField protoField = resolveProtoPath(message, path); | ||||
|     setProtoFieldFromString(protoField, value); | ||||
| } | ||||
|  | ||||
| std::set<unsigned> iterate(const RangeProto& range) | ||||
| { | ||||
| 	std::set<unsigned> set; | ||||
| 	int end = range.has_end()? range.end() : range.start(); | ||||
| 	for (unsigned i=range.start(); i<=end; i+=range.step()) | ||||
| 		set.insert(i); | ||||
| 	return set; | ||||
|     std::set<unsigned> set; | ||||
|     int end = range.has_end() ? range.end() : range.start(); | ||||
|     for (unsigned i = range.start(); i <= end; i += range.step()) | ||||
|         set.insert(i); | ||||
|     return set; | ||||
| } | ||||
|  | ||||
| std::set<unsigned> iterate(unsigned start, unsigned count) | ||||
| { | ||||
| 	std::set<unsigned> set; | ||||
| 	for (unsigned i=0; i<count; i++) | ||||
| 		set.insert(start + i); | ||||
| 	return set; | ||||
|     std::set<unsigned> set; | ||||
|     for (unsigned i = 0; i < count; i++) | ||||
|         set.insert(start + i); | ||||
|     return set; | ||||
| } | ||||
|  | ||||
| std::map<std::string, const google::protobuf::FieldDescriptor*> findAllProtoFields(google::protobuf::Message* message) | ||||
| std::map<std::string, const google::protobuf::FieldDescriptor*> | ||||
| findAllProtoFields(google::protobuf::Message* message) | ||||
| { | ||||
| 	std::map<std::string, const google::protobuf::FieldDescriptor*> fields; | ||||
| 	const auto* descriptor = message->GetDescriptor(); | ||||
|     std::map<std::string, const google::protobuf::FieldDescriptor*> fields; | ||||
|     const auto* descriptor = message->GetDescriptor(); | ||||
|  | ||||
| 	std::function<void(const google::protobuf::Descriptor*, const std::string&)> recurse = | ||||
| 		[&](auto* d, const auto& s) { | ||||
| 			for (int i=0; i<d->field_count(); i++) | ||||
| 			{ | ||||
| 				const google::protobuf::FieldDescriptor* f = d->field(i); | ||||
| 				std::string n = s + f->name(); | ||||
| 				if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) | ||||
| 					recurse(f->message_type(), n + "."); | ||||
| 				fields[n] = f; | ||||
| 			} | ||||
| 		}; | ||||
|     std::function<void(const google::protobuf::Descriptor*, const std::string&)> | ||||
|         recurse = [&](auto* d, const auto& s) | ||||
|     { | ||||
|         for (int i = 0; i < d->field_count(); i++) | ||||
|         { | ||||
|             const google::protobuf::FieldDescriptor* f = d->field(i); | ||||
|             std::string n = s + f->name(); | ||||
|  | ||||
| 	recurse(descriptor, ""); | ||||
| 	return fields; | ||||
|             if (f->options().GetExtension(::recurse) && | ||||
|                 (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE)) | ||||
|                 recurse(f->message_type(), n + "."); | ||||
|  | ||||
|             fields[n] = f; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     recurse(descriptor, ""); | ||||
|     return fields; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										1247
									
								
								lib/readerwriter.cc
									
									
									
									
									
								
							
							
						
						
									
										1247
									
								
								lib/readerwriter.cc
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,6 +3,7 @@ | ||||
| #include "lib/bytes.h" | ||||
| #include <fmt/format.h> | ||||
| #include <iomanip> | ||||
| #include <fstream> | ||||
|  | ||||
| bool emergencyStop = false; | ||||
|  | ||||
| @@ -190,3 +191,10 @@ std::string tohex(const std::string& s) | ||||
|  | ||||
|     return ss.str(); | ||||
| } | ||||
|  | ||||
| bool doesFileExist(const std::string& filename) | ||||
| { | ||||
| 	std::ifstream f(filename); | ||||
| 	return f.good(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ extern std::string toIso8601(time_t t); | ||||
| extern std::string quote(const std::string& s); | ||||
| extern std::string unhex(const std::string& s); | ||||
| extern std::string tohex(const std::string& s); | ||||
| extern bool doesFileExist(const std::string& filename); | ||||
|  | ||||
| /* If set, any running job will terminate as soon as possible (with an error). | ||||
|  */ | ||||
|   | ||||
							
								
								
									
										138
									
								
								lib/vfs/cpmfs.cc
									
									
									
									
									
								
							
							
						
						
									
										138
									
								
								lib/vfs/cpmfs.cc
									
									
									
									
									
								
							| @@ -3,77 +3,77 @@ | ||||
| #include "lib/config.pb.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| class Entry | ||||
| { | ||||
| public: | ||||
|     Entry(const Bytes& bytes, int map_entry_size) | ||||
|     { | ||||
|         user = bytes[0] & 0x0f; | ||||
|  | ||||
|         { | ||||
|             std::stringstream ss; | ||||
|             ss << (char)(user + '0') << ':'; | ||||
|  | ||||
|             for (int i = 1; i <= 8; i++) | ||||
|             { | ||||
|                 uint8_t c = bytes[i] & 0x7f; | ||||
|                 if (c == ' ') | ||||
|                     break; | ||||
|                 ss << (char)c; | ||||
|             } | ||||
|             for (int i = 9; i <= 11; i++) | ||||
|             { | ||||
|                 uint8_t c = bytes[i] & 0x7f; | ||||
|                 if (c == ' ') | ||||
|                     break; | ||||
|                 if (i == 9) | ||||
|                     ss << '.'; | ||||
|                 ss << (char)c; | ||||
|             } | ||||
|             filename = ss.str(); | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             std::stringstream ss; | ||||
|             if (bytes[9] & 0x80) | ||||
|                 ss << 'R'; | ||||
|             if (bytes[10] & 0x80) | ||||
|                 ss << 'S'; | ||||
|             if (bytes[11] & 0x80) | ||||
|                 ss << 'A'; | ||||
|             mode = ss.str(); | ||||
|         } | ||||
|  | ||||
|         extent = bytes[12] | (bytes[14] << 5); | ||||
|         records = bytes[15]; | ||||
|  | ||||
|         ByteReader br(bytes); | ||||
|         br.seek(16); | ||||
|         switch (map_entry_size) | ||||
|         { | ||||
|             case 1: | ||||
|                 for (int i = 0; i < 16; i++) | ||||
|                     allocation_map.push_back(br.read_8()); | ||||
|                 break; | ||||
|  | ||||
|             case 2: | ||||
|                 for (int i = 0; i < 8; i++) | ||||
|                     allocation_map.push_back(br.read_le16()); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::string filename; | ||||
|     std::string mode; | ||||
|     unsigned user; | ||||
|     unsigned extent; | ||||
|     unsigned records; | ||||
|     std::vector<unsigned> allocation_map; | ||||
| }; | ||||
|  | ||||
| class CpmFsFilesystem : public Filesystem | ||||
| { | ||||
|     class Entry | ||||
|     { | ||||
|     public: | ||||
|         Entry(const Bytes& bytes, int map_entry_size) | ||||
|         { | ||||
|             user = bytes[0] & 0x0f; | ||||
|  | ||||
|             { | ||||
|                 std::stringstream ss; | ||||
|                 ss << (char)(user + '0') << ':'; | ||||
|  | ||||
|                 for (int i = 1; i <= 8; i++) | ||||
|                 { | ||||
|                     uint8_t c = bytes[i] & 0x7f; | ||||
|                     if (c == ' ') | ||||
|                         break; | ||||
|                     ss << (char)c; | ||||
|                 } | ||||
|                 for (int i = 9; i <= 11; i++) | ||||
|                 { | ||||
|                     uint8_t c = bytes[i] & 0x7f; | ||||
|                     if (c == ' ') | ||||
|                         break; | ||||
|                     if (i == 9) | ||||
|                         ss << '.'; | ||||
|                     ss << (char)c; | ||||
|                 } | ||||
|                 filename = ss.str(); | ||||
|             } | ||||
|  | ||||
|             { | ||||
|                 std::stringstream ss; | ||||
|                 if (bytes[9] & 0x80) | ||||
|                     ss << 'R'; | ||||
|                 if (bytes[10] & 0x80) | ||||
|                     ss << 'S'; | ||||
|                 if (bytes[11] & 0x80) | ||||
|                     ss << 'A'; | ||||
|                 mode = ss.str(); | ||||
|             } | ||||
|  | ||||
|             extent = bytes[12] | (bytes[14] << 5); | ||||
|             records = bytes[15]; | ||||
|  | ||||
|             ByteReader br(bytes); | ||||
|             br.seek(16); | ||||
|             switch (map_entry_size) | ||||
|             { | ||||
|                 case 1: | ||||
|                     for (int i = 0; i < 16; i++) | ||||
|                         allocation_map.push_back(br.read_8()); | ||||
|                     break; | ||||
|  | ||||
|                 case 2: | ||||
|                     for (int i = 0; i < 8; i++) | ||||
|                         allocation_map.push_back(br.read_le16()); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         std::string filename; | ||||
|         std::string mode; | ||||
|         unsigned user; | ||||
|         unsigned extent; | ||||
|         unsigned records; | ||||
|         std::vector<unsigned> allocation_map; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     CpmFsFilesystem( | ||||
|         const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|   | ||||
| @@ -246,11 +246,11 @@ public: | ||||
|         switch (cmd) | ||||
|         { | ||||
|             case GET_SECTOR_SIZE: | ||||
|                 *(DWORD*)buffer = getLogicalSectorSize(); | ||||
|                 *(WORD*)buffer = getLogicalSectorSize(); | ||||
|                 break; | ||||
|  | ||||
|             case GET_SECTOR_COUNT: | ||||
|                 *(DWORD*)buffer = getLogicalSectorCount(); | ||||
|                 *(LBA_t*)buffer = getLogicalSectorCount(); | ||||
|                 break; | ||||
|  | ||||
|             case CTRL_SYNC: | ||||
|   | ||||
							
								
								
									
										211
									
								
								lib/vfs/smaky6fs.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								lib/vfs/smaky6fs.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "lib/utils.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| /* A directory entry looks like: | ||||
|  * | ||||
|  * 00-09: ten byte filename FFFFFFFF.EE | ||||
|  * 0a-0b: word: start sector | ||||
|  * 0c-17: unknown | ||||
|  */ | ||||
|  | ||||
| class Smaky6Filesystem : public Filesystem | ||||
| { | ||||
|     class Entry | ||||
|     { | ||||
|     public: | ||||
|         Entry(const Bytes& bytes) | ||||
|         { | ||||
|             ByteReader br(bytes); | ||||
|             br.seek(10); | ||||
|             startSector = br.read_le16(); | ||||
|             endSector = br.read_le16(); | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         std::string filename; | ||||
|         std::string mode; | ||||
|         uint16_t startSector; | ||||
|         uint16_t endSector; | ||||
|     }; | ||||
|  | ||||
|     class SmakyDirent : public Dirent | ||||
|     { | ||||
|     public: | ||||
|         SmakyDirent(const Bytes& dbuf) | ||||
|         { | ||||
|             { | ||||
|                 std::stringstream ss; | ||||
|  | ||||
|                 for (int i = 0; i <= 7; i++) | ||||
|                 { | ||||
|                     uint8_t c = dbuf[i] & 0x7f; | ||||
|                     if (c == ' ') | ||||
|                         break; | ||||
|                     ss << (char)c; | ||||
|                 } | ||||
|                 for (int i = 8; i <= 9; i++) | ||||
|                 { | ||||
|                     uint8_t c = dbuf[i] & 0x7f; | ||||
|                     if (c == ' ') | ||||
|                         break; | ||||
|                     if (i == 8) | ||||
|                         ss << '.'; | ||||
|                     ss << (char)c; | ||||
|                 } | ||||
|                 filename = ss.str(); | ||||
|             } | ||||
|  | ||||
|             std::string metadataBytes; | ||||
|             { | ||||
|                 std::stringstream ss; | ||||
|  | ||||
|                 for (int i = 10; i < 0x18; i++) | ||||
|                     ss << fmt::format("{:02x} ", (uint8_t)dbuf[i]); | ||||
|  | ||||
|                 metadataBytes = ss.str(); | ||||
|             } | ||||
|  | ||||
|             ByteReader br(dbuf); | ||||
|  | ||||
|             br.skip(10); /* filename */ | ||||
|             startSector = br.read_le16(); | ||||
|             endSector = br.read_le16(); | ||||
|             br.skip(2); /* unknown */ | ||||
|             lastSectorLength = br.read_le16(); | ||||
|  | ||||
|             file_type = TYPE_FILE; | ||||
|             length = (endSector - startSector - 1) * 256 + lastSectorLength; | ||||
|  | ||||
|             path = {filename}; | ||||
|             attributes[Filesystem::FILENAME] = filename; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|             attributes[Filesystem::FILE_TYPE] = "file"; | ||||
|             attributes[Filesystem::MODE] = ""; | ||||
|             attributes["smaky6.start_sector"] = std::to_string(startSector); | ||||
|             attributes["smaky6.end_sector"] = std::to_string(endSector); | ||||
|             attributes["smaky6.sectors"] = | ||||
|                 std::to_string(endSector - startSector); | ||||
|             attributes["smaky6.metadata_bytes"] = metadataBytes; | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         unsigned startSector; | ||||
|         unsigned endSector; | ||||
|         unsigned lastSectorLength; | ||||
|     }; | ||||
|  | ||||
|     friend class Directory; | ||||
|     class Directory | ||||
|     { | ||||
|     public: | ||||
|         Directory(Smaky6Filesystem* fs) | ||||
|         { | ||||
|             /* Read the directory. */ | ||||
|  | ||||
|             auto bytes = fs->getLogicalSector(0, 3); | ||||
|             ByteReader br(bytes); | ||||
|  | ||||
|             for (int i = 0; i < 32; i++) | ||||
|             { | ||||
|                 auto dbuf = bytes.slice(i * 0x18, 0x18); | ||||
|                 if (dbuf[0]) | ||||
|                 { | ||||
|                     auto de = std::make_shared<SmakyDirent>(dbuf); | ||||
|                     dirents.push_back(de); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         std::shared_ptr<SmakyDirent> findFile(const std::string& filename) | ||||
|         { | ||||
|             for (auto& de : dirents) | ||||
|                 if (de->filename == filename) | ||||
|                     return de; | ||||
|  | ||||
|             throw FileNotFoundException(); | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         std::vector<std::shared_ptr<SmakyDirent>> dirents; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     Smaky6Filesystem( | ||||
|         const Smaky6FsProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|         Filesystem(sectors), | ||||
|         _config(config) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     { | ||||
|         return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT; | ||||
|     } | ||||
|  | ||||
|     FilesystemStatus check() override | ||||
|     { | ||||
|         return FS_OK; | ||||
|     } | ||||
|  | ||||
|     std::map<std::string, std::string> getMetadata() override | ||||
|     { | ||||
|         Directory dir(this); | ||||
|         unsigned usedBlocks = 3; | ||||
|         for (auto& de : dir.dirents) | ||||
|             usedBlocks += (de->endSector - de->startSector); | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|         attributes[VOLUME_NAME] = ""; | ||||
|         attributes[TOTAL_BLOCKS] = std::to_string(getLogicalSectorCount()); | ||||
|         attributes[USED_BLOCKS] = std::to_string(usedBlocks); | ||||
|         attributes[BLOCK_SIZE] = std::to_string(getLogicalSectorSize(0, 0)); | ||||
|         return attributes; | ||||
|     } | ||||
|  | ||||
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override | ||||
|     { | ||||
|         if (!path.empty()) | ||||
|             throw FileNotFoundException(); | ||||
|  | ||||
|         Directory dir(this); | ||||
|         std::vector<std::shared_ptr<Dirent>> result; | ||||
|         for (auto& de : dir.dirents) | ||||
|             result.push_back(de); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Dirent> getDirent(const Path& path) override | ||||
|     { | ||||
|         Directory dir(this); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         return dir.findFile(path[0]); | ||||
|     } | ||||
|  | ||||
|     Bytes getFile(const Path& path) override | ||||
|     { | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(path); | ||||
|  | ||||
|         Directory dir(this); | ||||
|         auto de = dir.findFile(path[0]); | ||||
|  | ||||
|         Bytes data = | ||||
|             getLogicalSector(de->startSector, de->endSector - de->startSector); | ||||
|         data = data.slice(0, de->length); | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Smaky6FsProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Filesystem> Filesystem::createSmaky6Filesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors) | ||||
| { | ||||
|     return std::make_unique<Smaky6Filesystem>(config.smaky6(), sectors); | ||||
| } | ||||
| @@ -177,32 +177,35 @@ Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors): | ||||
| std::unique_ptr<Filesystem> Filesystem::createFilesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> image) | ||||
| { | ||||
|     switch (config.filesystem_case()) | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case FilesystemProto::kBrother120: | ||||
|         case FilesystemProto::BROTHER120: | ||||
|             return Filesystem::createBrother120Filesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kAcorndfs: | ||||
|         case FilesystemProto::ACORNDFS: | ||||
|             return Filesystem::createAcornDfsFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kFatfs: | ||||
|         case FilesystemProto::FATFS: | ||||
|             return Filesystem::createFatFsFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kCpmfs: | ||||
|         case FilesystemProto::CPMFS: | ||||
|             return Filesystem::createCpmFsFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kAmigaffs: | ||||
|         case FilesystemProto::AMIGAFFS: | ||||
|             return Filesystem::createAmigaFfsFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kMachfs: | ||||
|         case FilesystemProto::MACHFS: | ||||
|             return Filesystem::createMacHfsFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kCbmfs: | ||||
|         case FilesystemProto::CBMFS: | ||||
|             return Filesystem::createCbmfsFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::kProdos: | ||||
|         case FilesystemProto::PRODOS: | ||||
|             return Filesystem::createProdosFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::SMAKY6: | ||||
|             return Filesystem::createSmaky6Filesystem(config, image); | ||||
|  | ||||
|         default: | ||||
|             Error() << "no filesystem configured"; | ||||
|             return std::unique_ptr<Filesystem>(); | ||||
| @@ -218,13 +221,12 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig() | ||||
|         std::shared_ptr<Decoder> decoder; | ||||
|         std::shared_ptr<FluxSink> fluxSink; | ||||
|         std::shared_ptr<Encoder> encoder; | ||||
|         if (config.flux_source().source_case() != | ||||
|             FluxSourceProto::SOURCE_NOT_SET) | ||||
|         if (config.flux_source().type() != FluxSourceProto::NOT_SET) | ||||
|         { | ||||
|             fluxSource = FluxSource::create(config.flux_source()); | ||||
|             decoder = Decoder::create(config.decoder()); | ||||
|         } | ||||
|         if (config.flux_sink().has_drive()) | ||||
|         if (config.flux_sink().type() == FluxSinkProto::DRIVE) | ||||
|         { | ||||
|             fluxSink = FluxSink::create(config.flux_sink()); | ||||
|             encoder = Encoder::create(config.encoder()); | ||||
| @@ -236,11 +238,10 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig() | ||||
|     { | ||||
|         std::shared_ptr<ImageReader> reader; | ||||
|         std::shared_ptr<ImageWriter> writer; | ||||
|         if (config.image_reader().format_case() != | ||||
|             ImageReaderProto::FORMAT_NOT_SET) | ||||
|         if ((config.image_reader().type() != ImageReaderProto::NOT_SET) && | ||||
|             doesFileExist(config.image_reader().filename())) | ||||
|             reader = ImageReader::create(config.image_reader()); | ||||
|         if (config.image_writer().format_case() != | ||||
|             ImageWriterProto::FORMAT_NOT_SET) | ||||
|         if (config.image_writer().type() != ImageWriterProto::NOT_SET) | ||||
|             writer = ImageWriter::create(config.image_writer()); | ||||
|  | ||||
|         sectorInterface = | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define VFS_H | ||||
|  | ||||
| #include "lib/bytes.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| class Sector; | ||||
| class Image; | ||||
| @@ -58,6 +59,11 @@ public: | ||||
| class BadPathException : public FilesystemException | ||||
| { | ||||
| public: | ||||
|     BadPathException(const Path& path): | ||||
|         FilesystemException(fmt::format("Bad path: '{}'", path.to_str())) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     BadPathException(): FilesystemException("Bad path") {} | ||||
|  | ||||
|     BadPathException(const std::string& msg): FilesystemException(msg) {} | ||||
| @@ -242,6 +248,8 @@ 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> createSmaky6Filesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|  | ||||
|     static std::unique_ptr<Filesystem> createFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|   | ||||
| @@ -59,20 +59,35 @@ message CbmfsProto | ||||
|  | ||||
| message ProdosProto {} | ||||
|  | ||||
| // NEXT_TAG: 10 | ||||
| message Smaky6FsProto {} | ||||
|  | ||||
| // NEXT_TAG: 12 | ||||
| message FilesystemProto | ||||
| { | ||||
|     oneof filesystem | ||||
|     { | ||||
|         AcornDfsProto acorndfs = 1; | ||||
|         Brother120FsProto brother120 = 2; | ||||
|         FatFsProto fatfs = 3; | ||||
|         CpmFsProto cpmfs = 4; | ||||
|         AmigaFfsProto amigaffs = 5; | ||||
|         MacHfsProto machfs = 6; | ||||
|         CbmfsProto cbmfs = 7; | ||||
|         ProdosProto prodos = 8; | ||||
|     enum FilesystemType { | ||||
|         NOT_SET = 0; | ||||
|         ACORNDFS = 1; | ||||
|         BROTHER120 = 2; | ||||
|         FATFS = 3; | ||||
|         CPMFS = 4; | ||||
|         AMIGAFFS = 5; | ||||
|         MACHFS = 6; | ||||
|         CBMFS = 7; | ||||
|         PRODOS = 8; | ||||
| 		SMAKY6 = 9; | ||||
|     } | ||||
|  | ||||
|     optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"]; | ||||
|  | ||||
|     optional AcornDfsProto acorndfs = 1; | ||||
|     optional Brother120FsProto brother120 = 2; | ||||
|     optional FatFsProto fatfs = 3; | ||||
|     optional CpmFsProto cpmfs = 4; | ||||
|     optional AmigaFfsProto amigaffs = 5; | ||||
|     optional MacHfsProto machfs = 6; | ||||
|     optional CbmfsProto cbmfs = 7; | ||||
|     optional ProdosProto prodos = 8; | ||||
| 	optional Smaky6FsProto smaky6 = 11; | ||||
|      | ||||
|     optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"]; | ||||
| } | ||||
|   | ||||
| @@ -211,10 +211,10 @@ static void draw_x_graticules(Agg2D& painter, double x1, double y1, double x2, d | ||||
|  | ||||
| int mainAnalyseDriveResponse(int argc, const char* argv[]) | ||||
| { | ||||
| 	config.mutable_flux_source()->mutable_drive(); | ||||
| 	config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
| 	if (!config.flux_sink().has_drive()) | ||||
| 	if (config.flux_sink().type() != FluxSinkProto::DRIVE) | ||||
| 		Error() << "this only makes sense with a real disk drive"; | ||||
|  | ||||
|     usbSetDrive(config.drive().drive(), | ||||
|   | ||||
| @@ -210,7 +210,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap) | ||||
|  | ||||
| int mainInspect(int argc, const char* argv[]) | ||||
| { | ||||
| 	config.mutable_flux_source()->mutable_drive(); | ||||
| 	config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source())); | ||||
|   | ||||
| @@ -61,10 +61,10 @@ int mainRawRead(int argc, const char* argv[]) | ||||
|  | ||||
| 	if (argc == 1) | ||||
| 		showProfiles("rawread", formats); | ||||
| 	config.mutable_flux_source()->mutable_drive(); | ||||
| 	config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
| 	if (config.flux_sink().has_drive()) | ||||
| 	if (config.flux_sink().type() == FluxSinkProto::DRIVE) | ||||
| 		Error() << "you can't use rawread to write to hardware"; | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source())); | ||||
|   | ||||
| @@ -54,7 +54,7 @@ static ActionFlag eraseFlag( | ||||
| 	"erases the destination", | ||||
| 	[]() | ||||
| 	{ | ||||
| 		config.mutable_flux_source()->mutable_erase(); | ||||
| 		config.mutable_flux_source()->set_type(FluxSourceProto::ERASE); | ||||
| 	}); | ||||
|  | ||||
| int mainRawWrite(int argc, const char* argv[]) | ||||
| @@ -64,10 +64,10 @@ int mainRawWrite(int argc, const char* argv[]) | ||||
|  | ||||
| 	if (argc == 1) | ||||
| 		showProfiles("rawwrite", formats); | ||||
| 	config.mutable_flux_sink()->mutable_drive(); | ||||
| 	config.mutable_flux_sink()->set_type(FluxSinkProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
| 	if (config.flux_source().has_drive()) | ||||
| 	if (config.flux_source().type() == FluxSourceProto::DRIVE) | ||||
| 		Error() << "you can't use rawwrite to read from hardware"; | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source())); | ||||
|   | ||||
| @@ -67,10 +67,10 @@ int mainRead(int argc, const char* argv[]) | ||||
| { | ||||
| 	if (argc == 1) | ||||
| 		showProfiles("read", formats); | ||||
| 	config.mutable_flux_source()->mutable_drive(); | ||||
| 	config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
| 	if (config.decoder().copy_flux_to().has_drive()) | ||||
| 	if (config.decoder().copy_flux_to().type() == FluxSinkProto::DRIVE) | ||||
| 		Error() << "you cannot copy flux to a hardware device"; | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source())); | ||||
|   | ||||
| @@ -20,7 +20,7 @@ int mainRpm(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
| 	if (!config.flux_source().has_drive()) | ||||
| 	if (config.flux_source().type() != FluxSourceProto::DRIVE) | ||||
| 		Error() << "this only makes sense with a real disk drive"; | ||||
|  | ||||
|     usbSetDrive(config.drive().drive(), false, config.drive().index_mode()); | ||||
|   | ||||
| @@ -27,7 +27,7 @@ int mainSeek(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
| 	if (!config.flux_source().has_drive()) | ||||
| 	if (config.flux_source().type() != FluxSourceProto::DRIVE) | ||||
| 		Error() << "this only makes sense with a real disk drive"; | ||||
|  | ||||
|     usbSetDrive(config.drive().drive(), false, config.drive().index_mode()); | ||||
|   | ||||
| @@ -67,9 +67,9 @@ int mainWrite(int argc, const char* argv[]) | ||||
| { | ||||
| 	if (argc == 1) | ||||
| 		showProfiles("write", formats); | ||||
| 	config.mutable_flux_sink()->mutable_drive(); | ||||
| 	config.mutable_flux_sink()->set_type(FluxSinkProto::DRIVE); | ||||
| 	if (verify) | ||||
| 		config.mutable_flux_source()->mutable_drive(); | ||||
| 		config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
| 	std::unique_ptr<ImageReader> reader(ImageReader::create(config.image_reader())); | ||||
| @@ -83,7 +83,7 @@ int mainWrite(int argc, const char* argv[]) | ||||
| 		decoder = Decoder::create(config.decoder()); | ||||
|  | ||||
| 	std::unique_ptr<FluxSource> fluxSource; | ||||
| 	if (verify && config.has_flux_source() && config.flux_source().has_drive()) | ||||
| 	if (verify && (config.flux_source().type() == FluxSourceProto::DRIVE)) | ||||
| 		fluxSource = FluxSource::create(config.flux_source()); | ||||
|  | ||||
| 	writeDiskCommand(*image, *encoder, *fluxSink, decoder.get(), fluxSource.get()); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ is_extension: true | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "acornadfs.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ is_extension: true | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "acornadfs.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -7,12 +7,12 @@ drive { | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "micropolis.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "micropolis.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -41,12 +41,12 @@ option { | ||||
| 	config { | ||||
| 		image_reader { | ||||
| 			filename: "micropolis.vgi" | ||||
| 			img {} | ||||
| 			type: IMG | ||||
| 		} | ||||
|  | ||||
| 		image_writer { | ||||
| 			filename: "micropolis.vgi" | ||||
| 			img {} | ||||
| 			type: IMG | ||||
| 		} | ||||
|  | ||||
| 		layout { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ is_extension: true | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "mx.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -3,12 +3,12 @@ is_extension: true | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "northstar.nsi" | ||||
| 	nsi {} | ||||
| 	type: NSI | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "northstar.nsi" | ||||
| 	nsi {} | ||||
| 	type: NSI | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Acorn DFS 100kB/200kB 3.5" or 5.25" 40- or 80-track SS (ro)' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "acorndfs.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "acorndfs.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -44,6 +44,6 @@ decoder { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	acorndfs {} | ||||
| 	type: ACORNDFS | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'AES Lanier "No Problem" 616kB 5.25" 77-track SSDD hard sectored (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "aeslanier.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Agat 840kB 5.25" 80-track DS (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "agat.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Amiga 880kB 3.5" DSDD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "amiga.adf" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "amiga.adf" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -31,6 +31,6 @@ decoder { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	amigaffs {} | ||||
| 	type: AMIGAFFS | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Ampro 400kB/800kB 5.25" 40/80 track SSDD/DSDD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ampro.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Ampro 400kB/800kB 5.25" 40/80 track SSDD/DSDD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ampro.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "apple2.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -19,7 +19,7 @@ layout { | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "apple2.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| @@ -86,7 +86,7 @@ option { | ||||
| 		} | ||||
| 	 | ||||
| 		filesystem { | ||||
| 			prodos {} | ||||
| 			type: PRODOS | ||||
| 		} | ||||
|  | ||||
| 		layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist360.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist360.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist370.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist370.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist400.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist400.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist410.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist410.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist720.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist720.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist740.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist740.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist800.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist800.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -4,12 +4,12 @@ include: '_atari' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "atarist820.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "atarist820.st" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'BK 800kB 5.25"/3.5" 80-track 10-sector DSDD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "bk800.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "bk800.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Brother 120kB 3.5" 39-track SS GCR' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "brother120.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "brother120.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -41,6 +41,6 @@ drive { | ||||
| tpi: 48 | ||||
|  | ||||
| filesystem { | ||||
| 	brother120 {} | ||||
| 	type: BROTHER120 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Brother 240kB 3.5" 78-track SS GCR' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "brother240.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "brother240.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -36,6 +36,6 @@ drive { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	fatfs {} | ||||
| 	type: FATFS | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -46,6 +46,7 @@ FORMATS = \ | ||||
| 	ibm180 \ | ||||
| 	ibm360 \ | ||||
| 	ibm720 \ | ||||
| 	icl30 \ | ||||
| 	mac400 \ | ||||
| 	mac800 \ | ||||
| 	micropolis143 \ | ||||
| @@ -62,6 +63,7 @@ FORMATS = \ | ||||
| 	northstar87 \ | ||||
| 	rx50 \ | ||||
| 	shugart_drive \ | ||||
| 	smaky6 \ | ||||
| 	tids990 \ | ||||
| 	victor9k_ds \ | ||||
| 	victor9k_ss \ | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'CMD FD2000 1620kB 3.5" DSHD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "cmd_fd2000.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "cmd_fd2000.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Commodore 1541 171kB/192kB 5.25" 35/40-track SS GCR' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "commodore1541.d64" | ||||
| 	d64 {} | ||||
| 	type: D64 | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "commodore1541.d64" | ||||
| 	d64 {} | ||||
| 	type: D64 | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -61,7 +61,7 @@ decoder { | ||||
| tpi: 48 | ||||
|  | ||||
| filesystem { | ||||
| 	cbmfs {} | ||||
| 	type: CBMFS | ||||
| } | ||||
|  | ||||
| option { | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Commodore 1581 800kB 3.5" DSDD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "commodore1581.d81" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "commodore1581.d81" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'VDS Eco1 1210kB 77-track mixed format DSHD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "eco1.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -42,6 +42,7 @@ decoder { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	type: CPMFS | ||||
| 	cpmfs { | ||||
| 		filesystem_start { | ||||
| 			track: 2 | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Epson PF-10 40-track DS DD (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "epsonpf10.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -24,6 +24,7 @@ decoder { | ||||
| tpi: 48 | ||||
|  | ||||
| filesystem { | ||||
| 	type: CPMFS | ||||
| 	cpmfs { | ||||
| 		filesystem_start { | ||||
| 			track: 4 | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Durango F85 461kB 5.25" 77-track SS (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "f85.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Brother FB-100 100kB 3.5" 40-track SS (ro)' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "fb100.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ comment: 'Hewlett-Packard 9121 264kB 3.5" SSDD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "hp9121.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -7,12 +7,12 @@ drive { | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "hplif770.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "hplif770.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -2,18 +2,13 @@ comment: 'PC 3.5"/5.25" autodetect double sided format' | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm {} | ||||
| } | ||||
|  | ||||
| tracks { | ||||
| 	start: 0 | ||||
| 	end: 81 | ||||
| } | ||||
|  | ||||
| heads { | ||||
| 	start: 0 | ||||
| 	end: 1 | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'PC 1200kB 5.25" 80-track 15-sector DSHD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "ibm1200.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm1200.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -40,7 +40,7 @@ drive { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	fatfs {} | ||||
| 	type: FATFS | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'Japanese PC 1232kB 5.25"/3.5" 77-track 8-sector DSHD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "ibm1232.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm1232.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -40,7 +40,7 @@ drive { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	fatfs {} | ||||
| 	type: FATFS | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'PC 1440kB 3.5" 80-track 18-sector DSHD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "ibm1440.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm1440.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -36,6 +36,6 @@ decoder { | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
| 	fatfs {} | ||||
| 	type: FATFS | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'PC 180kB 5.25" 40-track 9-sector SSDD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "ibm180.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm180.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -42,7 +42,7 @@ drive { | ||||
| tpi: 48 | ||||
|  | ||||
| filesystem { | ||||
| 	fatfs {} | ||||
| 	type: FATFS | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,12 @@ comment: 'PC 360kB 5.25" 40-track 9-sector DSDD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "ibm360.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm360.img" | ||||
| 	img {} | ||||
| 	type: IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
| @@ -38,7 +38,7 @@ decoder { | ||||
| tpi: 48 | ||||
|  | ||||
| filesystem { | ||||
| 	fatfs {} | ||||
| 	type: FATFS | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user