mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			77 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 3876c07164 | ||
|  | ed315eade9 | ||
|  | 7456fd0c90 | ||
|  | 44160e66ac | ||
|  | 9bd969a57b | ||
|  | 0b585078d8 | ||
|  | 0d495ed934 | ||
|  | 95b703b1ea | ||
|  | 688061397b | ||
|  | 1f00176455 | ||
|  | 90da6b1e72 | ||
|  | 4deb45dc3f | ||
|  | eeec5d106a | ||
|  | 4e42d1d197 | ||
|  | 495d08c447 | ||
|  | 1b859015ae | ||
|  | 3db2109e01 | ||
|  | 294ac87503 | ||
|  | c297adb0c7 | ||
|  | 446b965794 | ||
|  | 96d4df296d | ||
|  | a149aac0e9 | ||
|  | aacc7be9f3 | ||
|  | 7409955701 | ||
|  | c623d95a80 | ||
|  | 1927cc7fe1 | ||
|  | 4eca254daf | ||
|  | c7d4fee3f6 | ||
|  | a6f798ae5b | ||
|  | c9ae836e52 | ||
|  | e3ffa63f7f | ||
|  | 4ffc2cc1dc | ||
|  | 7f9ba14687 | ||
|  | a24933e272 | ||
|  | 20bdacbecf | ||
|  | ab9d6cf5ed | ||
|  | 1f5903a9a0 | ||
|  | bb073b6bb3 | ||
|  | 516241f8f5 | ||
|  | 977b6831a0 | ||
|  | c61effb54f | ||
|  | 346d989944 | ||
|  | 60a73c8d1e | ||
|  | e52db4a837 | ||
|  | 4e317643bc | ||
|  | 5f520bf375 | ||
|  | 2efe521b3a | ||
|  | 5c21103646 | ||
|  | 9444696f37 | ||
|  | 082fe4e787 | ||
|  | 5e13cf23f9 | ||
|  | 8f98a1f557 | ||
|  | 5b21e8798b | ||
|  | b9ef5b7db8 | ||
|  | 9867f8c302 | ||
|  | 315889faf6 | ||
|  | 798e8fee89 | ||
|  | e1c49db329 | ||
|  | dae9537472 | ||
|  | 1330d56cdd | ||
|  | 6ce3ce20d0 | ||
|  | 362c5ee9b0 | ||
|  | 0f34ce0278 | ||
|  | 0c27c7c4c8 | ||
|  | 37595bf73c | ||
|  | 952aea46ba | ||
|  | 6a6536cf27 | ||
|  | 696368c92a | ||
|  | e3edc9327e | ||
|  | 8d2e6a664d | ||
|  | 9db6efe7a2 | ||
|  | ba1f8b8ed8 | ||
|  | 10605b3908 | ||
|  | e31e547322 | ||
|  | 9484a1b870 | ||
|  | 0a5a814a88 | ||
|  | 08ce455d1d | 
							
								
								
									
										5
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -43,7 +43,7 @@ jobs: | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine.FluxEngine.pkg | ||||
|         path: fluxengine/FluxEngine.pkg | ||||
|  | ||||
|   build-windows: | ||||
|     runs-on: windows-latest | ||||
| @@ -69,6 +69,9 @@ jobs: | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - name: update-protobuf | ||||
|       run: | | ||||
|          pacman -U --noconfirm https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-protobuf-21.9-1-any.pkg.tar.zst | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| name: Autorelease | ||||
|  | ||||
| concurrency:  | ||||
|   group: environment-${{ github.head_ref }} | ||||
|   group: environment-release-${{ github.head_ref }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| on: | ||||
| @@ -36,6 +36,10 @@ jobs: | ||||
|           vim | ||||
|     - uses: actions/checkout@v3 | ||||
|  | ||||
|     - name: update-protobuf | ||||
|       run: | | ||||
|          pacman -U --noconfirm https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-protobuf-21.9-1-any.pkg.tar.zst | ||||
|  | ||||
|     - name: build | ||||
|       run: | | ||||
|         make -j2 | ||||
|   | ||||
							
								
								
									
										10
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Makefile
									
									
									
									
									
								
							| @@ -30,6 +30,11 @@ ifeq ($(shell uname),Darwin) | ||||
| 		-framework Foundation | ||||
| endif | ||||
|  | ||||
| ifeq ($(shell uname),FreeBSD) | ||||
| 	PLATFORM = FreeBSD | ||||
| 	CFLAGS += -I/usr/local/include | ||||
| endif | ||||
|  | ||||
| #Check the Make version. | ||||
|  | ||||
|  | ||||
| @@ -138,12 +143,12 @@ PROTOS = \ | ||||
| PROTO_HDRS = $(patsubst %.proto, $(OBJDIR)/%.pb.h, $(PROTOS)) | ||||
| PROTO_SRCS = $(patsubst %.proto, $(OBJDIR)/%.pb.cc, $(PROTOS)) | ||||
| PROTO_OBJS = $(patsubst %.cc, %.o, $(PROTO_SRCS)) | ||||
| PROTO_CFLAGS = $(shell $(PKG_CONFIG) --cflags protobuf) | ||||
| PROTO_CFLAGS := $(shell $(PKG_CONFIG) --cflags protobuf) | ||||
| $(PROTO_SRCS): | $(PROTO_HDRS) | ||||
| $(PROTO_OBJS): CFLAGS += $(PROTO_CFLAGS) | ||||
| PROTO_LIB = $(OBJDIR)/libproto.a | ||||
| $(PROTO_LIB): $(PROTO_OBJS) | ||||
| PROTO_LDFLAGS = $(shell $(PKG_CONFIG) --libs protobuf) -pthread | ||||
| PROTO_LDFLAGS := $(shell $(PKG_CONFIG) --libs protobuf) -pthread | ||||
| .PRECIOUS: $(PROTO_HDRS) $(PROTO_SRCS) | ||||
|  | ||||
| include dep/agg/build.mk | ||||
| @@ -198,6 +203,7 @@ $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--192 --d | ||||
| $(call do-encodedecodetest,commodore,,--800 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,commodore,,--1620 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--264 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--608 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--616 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,hplif,,--770 --drive.tpi=135) | ||||
| $(call do-encodedecodetest,ibm,,--1200 --drive.tpi=96) | ||||
|   | ||||
| @@ -128,17 +128,18 @@ choices because they can store multiple types of file system. | ||||
| | [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 |  | CPMFS  | | ||||
| | [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS  | | ||||
| | [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 |  | | ||||
| | [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System |  |  | MICRODOS  | | ||||
| | [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 |  |  | | ||||
| | [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 |  | | ||||
| | [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 |  | | ||||
| | [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE  | | ||||
| | [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦖 |  |  | | ||||
| | [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND  | | ||||
| | [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 |  | | ||||
| | [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 |  | SMAKY6  | | ||||
| | [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 |  | | ||||
| | [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M |  |  | CPMFS  | | ||||
| | [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 |  | | ||||
| | [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 |  |  | | ||||
| | [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 |  | ZDOS  | | ||||
| {: .datatable } | ||||
|  | ||||
| <!-- FORMATSEND --> | ||||
|   | ||||
| @@ -51,26 +51,6 @@ static void write_bits( | ||||
|     } | ||||
| } | ||||
|  | ||||
| void bindump(std::ostream& stream, std::vector<bool>& buffer) | ||||
| { | ||||
|     size_t pos = 0; | ||||
|  | ||||
|     while ((pos < buffer.size()) and (pos < 520)) | ||||
|     { | ||||
|         stream << fmt::format("{:5d} : ", pos); | ||||
|         for (int i = 0; i < 40; i++) | ||||
|         { | ||||
|             if ((pos + i) < buffer.size()) | ||||
|                 stream << fmt::format("{:01b}", (buffer[pos + i])); | ||||
|             else | ||||
|                 stream << "-- "; | ||||
|             if ((((pos + i + 1) % 8) == 0) and i != 0) | ||||
|                 stream << "  "; | ||||
|         } | ||||
|         stream << std::endl; | ||||
|         pos += 40; | ||||
|     } | ||||
| } | ||||
| static std::vector<bool> encode_data(uint8_t input) | ||||
| { | ||||
|     /* | ||||
|   | ||||
| @@ -59,6 +59,70 @@ uint8_t mzosChecksum(const Bytes& bytes) | ||||
|     return checksum; | ||||
| } | ||||
|  | ||||
| static uint8_t b(uint32_t field, uint8_t pos) | ||||
| { | ||||
|     return (field >> pos) & 1; | ||||
| } | ||||
|  | ||||
| static uint8_t eccNextBit(uint32_t ecc, uint8_t data_bit) | ||||
| { | ||||
|     // This is 0x81932080 which is 0x0104C981 with reversed bits | ||||
|     return b(ecc, 7) ^ b(ecc, 13) ^ b(ecc, 16) ^ b(ecc, 17) ^ b(ecc, 20) | ||||
|             ^ b(ecc, 23) ^ b(ecc, 24) ^ b(ecc, 31) ^ data_bit; | ||||
| } | ||||
|  | ||||
| uint32_t vectorGraphicEcc(const Bytes& bytes) | ||||
| { | ||||
|     uint32_t e = 0; | ||||
|     Bytes payloadBytes = bytes.slice(0, bytes.size()-4); | ||||
|     ByteReader payload(payloadBytes); | ||||
|     while (!payload.eof()) { | ||||
|         uint8_t byte = payload.read_8(); | ||||
|         for (int i = 0; i < 8; i++) { | ||||
|             e = (e << 1) | eccNextBit(e, byte >> 7); | ||||
|             byte <<= 1; | ||||
|         } | ||||
|     } | ||||
|     Bytes trailerBytes = bytes.slice(bytes.size()-4); | ||||
|     ByteReader trailer(trailerBytes); | ||||
|     uint32_t res = e; | ||||
|     while (!trailer.eof()) { | ||||
|         uint8_t byte = trailer.read_8(); | ||||
|         for (int i = 0; i < 8; i++) { | ||||
|             res = (res << 1) | eccNextBit(e, byte >> 7); | ||||
|             e <<= 1; | ||||
|             byte <<= 1; | ||||
|         } | ||||
|     } | ||||
|     return res; | ||||
| } | ||||
|  | ||||
| /* Fixes bytes when possible, returning true if changed. */ | ||||
| static bool vectorGraphicEccFix(Bytes& bytes, uint32_t syndrome) | ||||
| { | ||||
|     uint32_t ecc = syndrome; | ||||
|     int pos = (MICROPOLIS_ENCODED_SECTOR_SIZE-5)*8+7; | ||||
|     bool aligned = false; | ||||
|     while ((ecc & 0xff000000) == 0) { | ||||
|         pos += 8; | ||||
|         ecc <<= 8; | ||||
|     } | ||||
|     for (; pos >= 0; pos--) { | ||||
|         bool bit = ecc & 1; | ||||
|         ecc >>= 1; | ||||
|         if (bit) | ||||
|             ecc ^= 0x808264c0; | ||||
|         if ((ecc & 0xff07ffff) == 0) | ||||
|             aligned = true; | ||||
|         if (aligned && pos % 8 == 0) | ||||
|             break; | ||||
|     } | ||||
|     if (pos < 0) | ||||
|         return false; | ||||
|     bytes[pos/8] ^= ecc >> 16; | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| class MicropolisDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| @@ -85,9 +149,10 @@ public: | ||||
|         /* Discard a possible partial sector at the end of the track. | ||||
|          * This partial sector could be mistaken for a conflicted sector, if | ||||
|          * whatever data read happens to match the checksum of 0, which is | ||||
|          * rare, but has been observed on some disks. | ||||
|          * rare, but has been observed on some disks. There's 570uS of slack in | ||||
|          * each sector, after accounting for preamble, data, and postamble. | ||||
|          */ | ||||
|         if (now > (getFluxmapDuration() - 12.5e6)) | ||||
|         if (now > (getFluxmapDuration() - 12.0e6)) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             return 0; | ||||
| @@ -114,9 +179,10 @@ public: | ||||
|         _sector->headerStartTime = tell().ns(); | ||||
|  | ||||
|         /* seekToPattern() can skip past the index hole, if this happens | ||||
|          * too close to the end of the Fluxmap, discard the sector. | ||||
|          * too close to the end of the Fluxmap, discard the sector. The | ||||
|          * preamble was expected to be 640uS long. | ||||
|          */ | ||||
|         if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6)) | ||||
|         if (_sector->headerStartTime > (getFluxmapDuration() - 11.3e6)) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
| @@ -130,6 +196,17 @@ public: | ||||
|         auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE * 16); | ||||
|         auto bytes = | ||||
|             decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE); | ||||
|  | ||||
|         bool eccPresent = bytes[274] == 0xaa; | ||||
|         uint32_t ecc = 0; | ||||
|         if (_config.ecc_type() == MicropolisDecoderProto::VECTOR && eccPresent) { | ||||
|             ecc = vectorGraphicEcc(bytes.slice(0, 274)); | ||||
|             if (ecc != 0) { | ||||
|                 vectorGraphicEccFix(bytes, ecc); | ||||
|                 ecc = vectorGraphicEcc(bytes.slice(0, 274)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
|         int syncByte = br.read_8(); /* sync */ | ||||
| @@ -191,8 +268,10 @@ public: | ||||
|             _sector->data = bytes; | ||||
|         else | ||||
|             error("Sector output size may only be 256 or 275"); | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|         if (wantChecksum == gotChecksum && (!eccPresent || ecc == 0)) | ||||
|             _sector->status = Sector::OK; | ||||
|         else | ||||
|             _sector->status = Sector::BAD_CHECKSUM; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|   | ||||
| @@ -8,7 +8,8 @@ | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, | ||||
|     unsigned& cursor, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
|     const std::shared_ptr<const Sector>& sector, | ||||
|     MicropolisEncoderProto::EccType eccType) | ||||
| { | ||||
|     if ((sector->data.size() != 256) && | ||||
|         (sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE)) | ||||
| @@ -45,8 +46,15 @@ static void write_sector(std::vector<bool>& bits, | ||||
|             writer.write_8(0); /* Padding */ | ||||
|         writer += sector->data; | ||||
|         writer.write_8(micropolisChecksum(sectorData.slice(1))); | ||||
|         for (int i = 0; i < 5; i++) | ||||
|             writer.write_8(0); /* 4 byte ECC and ECC not present flag */ | ||||
|  | ||||
|         uint8_t eccPresent = 0; | ||||
|         uint32_t ecc = 0; | ||||
|         if (eccType == MicropolisEncoderProto::VECTOR) { | ||||
|             eccPresent = 0xaa; | ||||
|             ecc = vectorGraphicEcc(sectorData + Bytes(4)); | ||||
|         } | ||||
|         writer.write_be32(ecc); | ||||
|         writer.write_8(eccPresent); | ||||
|     } | ||||
|     for (uint8_t b : sectorData) | ||||
|         fullSector->push_back(b); | ||||
| @@ -86,18 +94,32 @@ public: | ||||
|             (_config.rotational_period_ms() * 1e3) / _config.clock_period_us(); | ||||
|  | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         std::vector<unsigned> indexes; | ||||
|         unsigned prev_cursor = 0; | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         for (const auto& sectorData : sectors) | ||||
|             write_sector(bits, cursor, sectorData); | ||||
|         for (const auto& sectorData : sectors) { | ||||
|             indexes.push_back(cursor); | ||||
|             prev_cursor = cursor; | ||||
|             write_sector(bits, cursor, sectorData, _config.ecc_type()); | ||||
|         } | ||||
|         indexes.push_back(prev_cursor + (cursor - prev_cursor)/2); | ||||
|         indexes.push_back(cursor); | ||||
|  | ||||
|         if (cursor != bits.size()) | ||||
|             error("track data mismatched length"); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|         nanoseconds_t clockPeriod = calculatePhysicalClockPeriod( | ||||
|             _config.clock_period_us() * 1e3, | ||||
|             _config.rotational_period_ms() * 1e6); | ||||
|         auto pos = bits.begin(); | ||||
|         for (int i = 1; i < indexes.size(); i++) { | ||||
|             auto end = bits.begin() + indexes[i]; | ||||
|             fluxmap->appendBits(std::vector<bool>(pos, end), clockPeriod); | ||||
|             fluxmap->appendIndex(); | ||||
|             pos = end; | ||||
|         } | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -17,5 +17,6 @@ extern std::unique_ptr<Encoder> createMicropolisEncoder( | ||||
|     const EncoderProto& config); | ||||
|  | ||||
| extern uint8_t micropolisChecksum(const Bytes& bytes); | ||||
| extern uint32_t vectorGraphicEcc(const Bytes& bytes); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -8,17 +8,30 @@ message MicropolisDecoderProto { | ||||
| 		MICROPOLIS = 1; | ||||
| 		MZOS = 2; | ||||
| 	} | ||||
| 	enum EccType { | ||||
| 		NONE = 0; | ||||
| 		VECTOR = 1; | ||||
| 	} | ||||
|  | ||||
| 	optional int32 sector_output_size = 1 [default = 256, | ||||
| 		(help) = "How much of the raw sector should be saved. Must be 256 or 275"]; | ||||
| 	optional ChecksumType checksum_type = 2 [default = AUTO, | ||||
| 		(help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"]; | ||||
| 	optional EccType ecc_type = 3 [default = NONE, | ||||
| 		(help) = "ECC type to use: NONE, VECTOR"]; | ||||
| } | ||||
|  | ||||
| message MicropolisEncoderProto { | ||||
|     enum EccType { | ||||
|         NONE = 0; | ||||
|         VECTOR = 1; | ||||
|     } | ||||
|  | ||||
|     optional double clock_period_us = 1 | ||||
|         [ default = 2.0, (help) = "clock rate on the real device" ]; | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 200.0, (help) = "rotational period on the real device" ]; | ||||
|     optional EccType ecc_type = 3 [default = NONE, | ||||
|         (help) = "ECC type to use for IMG data: NONE, VECTOR"]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ LIBUSBP_SRCS += \ | ||||
| 	dep/libusbp/src/mac/list_mac.c \ | ||||
| 	dep/libusbp/src/mac/serial_port_mac.c \ | ||||
|  | ||||
| else | ||||
| else ifeq ($(shell uname),Linux) | ||||
|  | ||||
| LIBUSBP_CFLAGS += $(shell pkg-config --cflags libudev) | ||||
| LIBUSBP_LDFLAGS += $(shell pkg-config --libs libudev) | ||||
| @@ -48,6 +48,11 @@ LIBUSBP_SRCS += \ | ||||
| 	dep/libusbp/src/linux/udev_linux.c \ | ||||
| 	dep/libusbp/src/linux/usbfd_linux.c \ | ||||
|  | ||||
| else | ||||
|  | ||||
| LIBUSBP_SRCS += \ | ||||
| 	dep/libusbp/src/dummy.c | ||||
|  | ||||
| endif | ||||
|  | ||||
| LIBUSBP_OBJS = $(patsubst %.c, $(OBJDIR)/%.o, $(LIBUSBP_SRCS)) | ||||
|   | ||||
							
								
								
									
										103
									
								
								dep/libusbp/src/dummy.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								dep/libusbp/src/dummy.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
|  | ||||
| // This file contains failing place-holders to make things compile | ||||
| // on otherwise unsupported platforms. | ||||
|  | ||||
| #include <libusbp_internal.h> | ||||
|  | ||||
| struct libusbp_device | ||||
| { | ||||
|     char* syspath; | ||||
|     char* serial_number; // may be NULL | ||||
|     uint16_t product_id; | ||||
|     uint16_t vendor_id; | ||||
|     uint16_t revision; | ||||
| }; | ||||
|  | ||||
| static libusbp_error* fail() | ||||
| { | ||||
|     return error_create("USB hardware is not supported on this platform"); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_device_copy( | ||||
|     const libusbp_device* source, libusbp_device** dest) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_generic_interface_create(const libusbp_device* device, | ||||
|     uint8_t interface_number, | ||||
|     bool composite __attribute__((unused)), | ||||
|     libusbp_generic_interface** gi) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_generic_handle_open( | ||||
|     const libusbp_generic_interface* gi, libusbp_generic_handle** handle) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| void libusbp_device_free(libusbp_device* device) {} | ||||
|  | ||||
| void libusbp_generic_handle_close(libusbp_generic_handle* handle) {} | ||||
|  | ||||
| void libusbp_generic_interface_free(libusbp_generic_interface* gi) {} | ||||
|  | ||||
| libusbp_error* libusbp_device_get_vendor_id( | ||||
|     const libusbp_device* device, uint16_t* vendor_id) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_device_get_product_id( | ||||
|     const libusbp_device* device, uint16_t* product_id) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_device_get_serial_number( | ||||
|     const libusbp_device* device, char** serial_number) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_write_pipe(libusbp_generic_handle* handle, | ||||
|     uint8_t pipe_id, | ||||
|     const void* data, | ||||
|     size_t size, | ||||
|     size_t* transferred) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_read_pipe(libusbp_generic_handle* handle, | ||||
|     uint8_t pipe_id, | ||||
|     void* data, | ||||
|     size_t size, | ||||
|     size_t* transferred) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_serial_port_create(const libusbp_device* device, | ||||
|     uint8_t interface_number, | ||||
|     bool composite, | ||||
|     libusbp_serial_port** port) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| libusbp_error* libusbp_serial_port_get_name( | ||||
|     const libusbp_serial_port* port, char** name) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
|  | ||||
| void libusbp_serial_port_free(libusbp_serial_port* port) {} | ||||
|  | ||||
| libusbp_error* libusbp_list_connected_devices( | ||||
|     libusbp_device*** device_list, size_t* device_count) | ||||
| { | ||||
|     return fail(); | ||||
| } | ||||
| @@ -15,6 +15,7 @@ encoding scheme. | ||||
|  | ||||
|   - Format variants: | ||||
|       - `264`: 264kB 3.5" 66-track SSDD; HP9121 format | ||||
|       - `608`: 608kB 3.5" 76-track DSDD; HP9122 format | ||||
|       - `616`: 616kB 3.5" 77-track DSDD | ||||
|       - `770`: 770kB 3.5" 77-track DSDD | ||||
|  | ||||
| @@ -23,12 +24,19 @@ encoding scheme. | ||||
| To read: | ||||
|  | ||||
|   - `fluxengine read hplif --264 -s drive:0 -o hplif.img` | ||||
|   - `fluxengine read hplif --608 -s drive:0 -o hplif.img` | ||||
|   - `fluxengine read hplif --616 -s drive:0 -o hplif.img` | ||||
|   - `fluxengine read hplif --770 -s drive:0 -o hplif.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write hplif --264 -d drive:0 -i hplif.img` | ||||
|   - `fluxengine write hplif --608 -d drive:0 -i hplif.img` | ||||
|   - `fluxengine write hplif --616 -d drive:0 -i hplif.img` | ||||
|   - `fluxengine write hplif --770 -d drive:0 -i hplif.img` | ||||
|  | ||||
| ## References | ||||
|  | ||||
|   * [A summary of the Hewlett Packard floppy disk | ||||
|     formats](http://www.bitsavers.org/pdf/hp/disc/912x/HP_Flexible_Disk_Formats.pdf) | ||||
|  | ||||
|   | ||||
| @@ -22,13 +22,25 @@ pinout as a 96tpi PC 5.25" drive. In use they should be identical. | ||||
| While most operating systems use the standard Micropolis checksum, Vector | ||||
| Graphic MZOS uses a unique checksum.  The decoder will automatically detect | ||||
| the checksum type in use; however, a specific checksum type may be forced | ||||
| using the `--decoder.micropolis.checksum_type=n` where the type is one of: | ||||
| using the `--decoder.micropolis.checksum_type=TYPE` where TYPE is one of: | ||||
|  | ||||
| | Type | Description                             | | ||||
| |------|-----------------------------------------| | ||||
| | 0    | Automatically detect                    | | ||||
| | 1    | Standard Micropolis (MDOS, CP/M, OASIS) | | ||||
| | 2    | Vector Graphic MZOS                     | | ||||
| | Checksum   | Description                             | | ||||
| |------------|-----------------------------------------| | ||||
| | AUTO       | Automatically detect                    | | ||||
| | MICROPOLIS | Standard Micropolis (MDOS, CP/M, OASIS) | | ||||
| | MZOS       | Vector Graphic MZOS                     | | ||||
|  | ||||
| Later versions of the Micropolis format supported ECC, especially in | ||||
| controllers with HDD support. The ECC can detect and correct errors. However, | ||||
| it is unclear what ECC algorithm was used by each vendor. ECC is disabled by | ||||
| default, but available for checking and correcting using | ||||
| `--decoder.micropolis.ecc_type=TYPE` and for writing from IMG files using | ||||
| `--encoder.micropolis.ecc_type=TYPE`, where TYPE is one of: | ||||
|  | ||||
| | ECC    | Description                              | | ||||
| |--------|------------------------------------------| | ||||
| | NONE   | No ECC processing enabled                | | ||||
| | VECTOR | Vector Graphic Dual-Mode Disk Controller | | ||||
|  | ||||
| The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK, | ||||
| and SETSEC, but no function to select the head/side. Double-sided floppies | ||||
|   | ||||
| @@ -9,13 +9,29 @@ drive, used for saving MIDI sequences and samples. | ||||
| Weirdly, it seems to use precisely the same format as the Brother word | ||||
| processors: a thoroughly non-IBM-compatible custom GCR system. | ||||
|  | ||||
| FluxEngine pretends to support this, but it has had almost no testing, the only | ||||
| disk image I have seen for it was mostly corrupt, and very little is known | ||||
| about the format, so I have no idea whether it's correct or not. | ||||
| FluxEngine supports both reading and writing D20 disks, as well as basic support | ||||
| for the filesystem, allowing files to be read from and written to D20 disks. | ||||
| Note that the D20 was never intended to support arbitrary files on its disks and | ||||
| is very likely to crash if you put unexpected files on a disk. In addition, | ||||
| while the file format itself is currently unknown, there is a header at the top | ||||
| of the file containing what appears to be the name shown in the D20 file | ||||
| browser, so the name by which you see it is not necessarily the filename. | ||||
|  | ||||
| A word of warning --- just like the Brother word processors, the D20 floppy | ||||
| drive isn't very well aligned. The drive itself uses quarter-stepping to | ||||
| automatically adapt to whatever alignment the disk was formatted with. This | ||||
| means that trying to read such a disk on a PC drive, which does _not_ have | ||||
| adjustable alignment, may not work very well. In these situations it is possible | ||||
| to adjust the alignment of most modern drives, but this is a somewhat risky | ||||
| process and may result in permanently wrecking the drive alignment. | ||||
|  | ||||
| Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if | ||||
| you know anything about it. | ||||
|  | ||||
| Many thanks to trondl [on the VCF | ||||
| forums](https://forum.vcfed.org/index.php?threads/roland-d-20-decoding-the-mysterious-floppy-format.1243226/) | ||||
| for assistance with this!  | ||||
|  | ||||
| ## Options | ||||
|  | ||||
| (no options) | ||||
| @@ -26,3 +42,7 @@ To read: | ||||
|  | ||||
|   - `fluxengine read rolandd20 -s drive:0 -o rolandd20.img` | ||||
|  | ||||
| To write: | ||||
|  | ||||
|   - `fluxengine write rolandd20 -d drive:0 -i rolandd20.img` | ||||
|  | ||||
|   | ||||
| @@ -20,11 +20,8 @@ bytes per sector --- 128 bytes of user payload plus two two-byte metadata | ||||
| words used to construct linked lists of sectors for storing files. These | ||||
| stored 320kB each. | ||||
|  | ||||
| FluxEngine has experimental read support for these disks, based on a single | ||||
| Catweasel flux file I've been able to obtain, which only contained 70 tracks. | ||||
| I haven't been able to try this for real. If anyone has any of these disks, | ||||
| an 8-inch drive, a FluxEngine and the appropriate adapter, please [get in | ||||
| touch](https://github.com/davidgiven/fluxengine/issues/new)... | ||||
| FluxEngine has read support for these, including support for RIO's ZDOS file | ||||
| system. | ||||
|  | ||||
| ## Options | ||||
|  | ||||
|   | ||||
| @@ -26,6 +26,7 @@ The following file systems are supported so far. | ||||
| | Macintosh HFS                            |   Y   |   Y    | Only AppleDouble files may be written | | ||||
| | pSOS' PHILE                              |   Y   |        | Probably unreliable due to lack of documentation | | ||||
| | Smaky 6                                  |   Y   |        |       | | ||||
| | Zilog MCZ RIO's ZDOS                     |   Y   |        |       | | ||||
| {: .datatable } | ||||
|  | ||||
| Please not that Atari disks do _not_ use standard FatFS, and the library I'm | ||||
|   | ||||
| @@ -25,7 +25,7 @@ SetCompressor /solid lzma | ||||
|  	GreaseWeazle hardware. It also allows manipulation of flux files and disk \ | ||||
|  	images, so it's useful without any hardware.$\r$\n\  | ||||
| 	$\r$\n\ | ||||
| 	This wizard will install WordGrinder on your computer.$\r$\n\ | ||||
| 	This wizard will install FluxEngine on your computer.$\r$\n\ | ||||
| 	$\r$\n\ | ||||
| 	$_CLICK" | ||||
|  | ||||
| @@ -130,7 +130,7 @@ SectionEnd | ||||
|  | ||||
| Section "Desktop Shortcut" | ||||
| 	SetOutPath "$DOCUMENTS" | ||||
| 	CreateShortCut "$DESKTOP\WordGrinder.lnk" "$INSTDIR\fluxengine-gui.exe" "" "$INSTDIR\fluxengine-gui.exe" 0 | ||||
| 	CreateShortCut "$DESKTOP\FluxEngine.lnk" "$INSTDIR\fluxengine-gui.exe" "" "$INSTDIR\fluxengine-gui.exe" 0 | ||||
| SectionEnd | ||||
|  | ||||
| ;-------------------------------- | ||||
|   | ||||
							
								
								
									
										15
									
								
								lib/build.mk
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								lib/build.mk
									
									
									
									
									
								
							| @@ -77,14 +77,17 @@ LIBFLUXENGINE_SRCS = \ | ||||
| 	lib/vfs/cbmfs.cc \ | ||||
| 	lib/vfs/cpmfs.cc \ | ||||
| 	lib/vfs/fatfs.cc \ | ||||
| 	lib/vfs/lif.cc \ | ||||
| 	lib/vfs/machfs.cc \ | ||||
| 	lib/vfs/prodos.cc \ | ||||
| 	lib/vfs/smaky6fs.cc \ | ||||
| 	lib/vfs/philefs.cc \ | ||||
| 	lib/vfs/vfs.cc \ | ||||
| 	lib/vfs/fluxsectorinterface.cc \ | ||||
| 	lib/vfs/imagesectorinterface.cc \ | ||||
| 	lib/vfs/lif.cc \ | ||||
| 	lib/vfs/machfs.cc \ | ||||
| 	lib/vfs/microdos.cc \ | ||||
| 	lib/vfs/philefs.cc \ | ||||
| 	lib/vfs/prodos.cc \ | ||||
| 	lib/vfs/roland.cc \ | ||||
| 	lib/vfs/smaky6fs.cc \ | ||||
| 	lib/vfs/vfs.cc \ | ||||
| 	lib/vfs/zdos.cc \ | ||||
|  | ||||
| LIBFLUXENGINE_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBFLUXENGINE_SRCS)) | ||||
| OBJS += $(LIBFLUXENGINE_OBJS) | ||||
|   | ||||
| @@ -362,6 +362,14 @@ ByteWriter& ByteWriter::operator+=(std::istream& stream) | ||||
|     return *this; | ||||
| } | ||||
|  | ||||
| ByteWriter& ByteWriter::pad(unsigned count, uint8_t b) | ||||
| { | ||||
|     while (count--) | ||||
|         this->write_8(b); | ||||
|  | ||||
|     return *this; | ||||
| } | ||||
|  | ||||
| void BitWriter::push(uint32_t bits, size_t size) | ||||
| { | ||||
|     bits <<= 32 - size; | ||||
|   | ||||
| @@ -345,6 +345,8 @@ public: | ||||
|         return *this += stream; | ||||
|     } | ||||
|  | ||||
|     ByteWriter& pad(unsigned count, uint8_t byte = 0); | ||||
|  | ||||
| private: | ||||
|     Bytes& _bytes; | ||||
| }; | ||||
|   | ||||
| @@ -19,4 +19,36 @@ enum IndexMode { | ||||
| 	INDEXMODE_360 = 2; | ||||
| } | ||||
|  | ||||
| enum FluxSourceSinkType { | ||||
| 	FLUXTYPE_NOT_SET = 0; | ||||
| 	FLUXTYPE_A2R = 1; | ||||
| 	FLUXTYPE_AU = 2; | ||||
| 	FLUXTYPE_CWF = 3; | ||||
| 	FLUXTYPE_DRIVE = 4; | ||||
| 	FLUXTYPE_ERASE = 5; | ||||
| 	FLUXTYPE_FLUX = 6; | ||||
| 	FLUXTYPE_FLX = 7; | ||||
| 	FLUXTYPE_KRYOFLUX = 8; | ||||
| 	FLUXTYPE_SCP = 9; | ||||
| 	FLUXTYPE_TEST_PATTERN = 10; | ||||
| 	FLUXTYPE_VCD = 11; | ||||
| } | ||||
|  | ||||
| enum ImageReaderWriterType { | ||||
| 	IMAGETYPE_NOT_SET = 0; | ||||
| 	IMAGETYPE_D64 = 1; | ||||
| 	IMAGETYPE_D88 = 2; | ||||
| 	IMAGETYPE_DIM = 3; | ||||
| 	IMAGETYPE_DISKCOPY = 4; | ||||
| 	IMAGETYPE_FDI = 5; | ||||
| 	IMAGETYPE_IMD = 6; | ||||
| 	IMAGETYPE_IMG = 7; | ||||
| 	IMAGETYPE_JV3 = 8; | ||||
| 	IMAGETYPE_LDBS = 9; | ||||
| 	IMAGETYPE_NFD = 10; | ||||
| 	IMAGETYPE_NSI = 11; | ||||
| 	IMAGETYPE_RAW = 12; | ||||
| 	IMAGETYPE_TD0 = 13; | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										346
									
								
								lib/config.cc
									
									
									
									
									
								
							
							
						
						
									
										346
									
								
								lib/config.cc
									
									
									
									
									
								
							| @@ -11,10 +11,153 @@ | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include <fstream> | ||||
| #include <google/protobuf/text_format.h> | ||||
| #include <regex> | ||||
|  | ||||
| static Config config; | ||||
|  | ||||
| enum ConstructorMode | ||||
| { | ||||
|     MODE_RO, | ||||
|     MODE_WO, | ||||
|     MODE_RW | ||||
| }; | ||||
|  | ||||
| struct ImageConstructor | ||||
| { | ||||
|     std::string extension; | ||||
|     ImageReaderWriterType type; | ||||
|     ConstructorMode mode; | ||||
| }; | ||||
|  | ||||
| static const std::vector<FluxConstructor> fluxConstructors = { | ||||
|     {/* The .flux format must be first. */ | ||||
|         .name = "FluxEngine (.flux)", | ||||
|      .pattern = std::regex("^(.*\\.flux)$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_FLUX); | ||||
|             proto->mutable_fl2()->set_filename(s); | ||||
|         }, .sink = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_FLUX); | ||||
|             proto->mutable_fl2()->set_filename(s); | ||||
|         }}, | ||||
|     { | ||||
|      .name = "Supercard Pro (.scp)", | ||||
|      .pattern = std::regex("^(.*\\.scp)$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_SCP); | ||||
|             proto->mutable_scp()->set_filename(s); | ||||
|         }, .sink = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_SCP); | ||||
|             proto->mutable_scp()->set_filename(s); | ||||
|         }, }, | ||||
|     {.name = "AppleSauce (.a2r)", | ||||
|      .pattern = std::regex("^(.*\\.a2r)$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_A2R); | ||||
|             proto->mutable_a2r()->set_filename(s); | ||||
|         }, .sink = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_A2R); | ||||
|             proto->mutable_a2r()->set_filename(s); | ||||
|         }}, | ||||
|     {.name = "CatWeazle (.cwf)", | ||||
|      .pattern = std::regex("^(.*\\.cwf)$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_CWF); | ||||
|             proto->mutable_cwf()->set_filename(s); | ||||
|         }}, | ||||
|     {.pattern = std::regex("^erase:$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_ERASE); | ||||
|         }}, | ||||
|     {.name = "KryoFlux directory", | ||||
|      .pattern = std::regex("^kryoflux:(.*)$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_KRYOFLUX); | ||||
|             proto->mutable_kryoflux()->set_directory(s); | ||||
|         }}, | ||||
|     {.pattern = std::regex("^testpattern:(.*)"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_TEST_PATTERN); | ||||
|         }}, | ||||
|     {.pattern = std::regex("^drive:(.*)"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_DRIVE); | ||||
|             globalConfig().overrides()->mutable_drive()->set_drive( | ||||
|                 std::stoi(s)); | ||||
|         }, .sink = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_DRIVE); | ||||
|             globalConfig().overrides()->mutable_drive()->set_drive( | ||||
|                 std::stoi(s)); | ||||
|         }}, | ||||
|     {.name = "FluxCopy directory", | ||||
|      .pattern = std::regex("^flx:(.*)$"), | ||||
|      .source = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_FLX); | ||||
|             proto->mutable_flx()->set_directory(s); | ||||
|         }}, | ||||
|     {.name = "Value Change Dump directory", | ||||
|      .pattern = std::regex("^vcd:(.*)$"), | ||||
|      .sink = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_VCD); | ||||
|             proto->mutable_vcd()->set_directory(s); | ||||
|         }}, | ||||
|     {.name = "Audio file directory", | ||||
|      .pattern = std::regex("^au:(.*)$"), | ||||
|      .sink = | ||||
|             [](auto& s, auto* proto) | ||||
|         { | ||||
|             proto->set_type(FLUXTYPE_AU); | ||||
|             proto->mutable_au()->set_directory(s); | ||||
|         }}, | ||||
| }; | ||||
|  | ||||
| static const std::vector<ImageConstructor> imageConstructors = { | ||||
|     {".adf",      IMAGETYPE_IMG,      MODE_RW}, | ||||
|     {".d64",      IMAGETYPE_D64,      MODE_RW}, | ||||
|     {".d81",      IMAGETYPE_IMG,      MODE_RW}, | ||||
|     {".d88",      IMAGETYPE_D88,      MODE_RW}, | ||||
|     {".dim",      IMAGETYPE_DIM,      MODE_RO}, | ||||
|     {".diskcopy", IMAGETYPE_DISKCOPY, MODE_RW}, | ||||
|     {".dsk",      IMAGETYPE_IMG,      MODE_RW}, | ||||
|     {".fdi",      IMAGETYPE_FDI,      MODE_RO}, | ||||
|     {".imd",      IMAGETYPE_IMD,      MODE_RW}, | ||||
|     {".img",      IMAGETYPE_IMG,      MODE_RW}, | ||||
|     {".jv3",      IMAGETYPE_JV3,      MODE_RO}, | ||||
|     {".nfd",      IMAGETYPE_NFD,      MODE_RO}, | ||||
|     {".nsi",      IMAGETYPE_NSI,      MODE_RW}, | ||||
|     {".st",       IMAGETYPE_IMG,      MODE_RW}, | ||||
|     {".td0",      IMAGETYPE_TD0,      MODE_RO}, | ||||
|     {".vgi",      IMAGETYPE_IMG,      MODE_RW}, | ||||
|     {".xdf",      IMAGETYPE_IMG,      MODE_RW}, | ||||
| }; | ||||
|  | ||||
| Config& globalConfig() | ||||
| { | ||||
|     return config; | ||||
| @@ -29,7 +172,7 @@ ConfigProto* Config::combined() | ||||
|         /* First apply any standalone options. */ | ||||
|  | ||||
|         std::set<std::string> options = _appliedOptions; | ||||
|         std::set<const OptionRequirementProto*> requirements; | ||||
|         std::set<const OptionPrerequisiteProto*> prereqs; | ||||
|         for (const auto& option : _baseConfig.option()) | ||||
|         { | ||||
|             if (options.find(option.name()) != options.end()) | ||||
| @@ -257,7 +400,7 @@ const OptionProto& Config::findOption(const std::string& optionName) | ||||
|  | ||||
| void Config::checkOptionValid(const OptionProto& option) | ||||
| { | ||||
|     for (const auto& req : option.requires()) | ||||
|     for (const auto& req : option.prerequisite()) | ||||
|     { | ||||
|         bool matched = false; | ||||
|         try | ||||
| @@ -334,72 +477,17 @@ void Config::clearOptions() | ||||
|     invalidate(); | ||||
| } | ||||
|  | ||||
| static void setFluxSourceImpl(std::string filename, FluxSourceProto* proto) | ||||
| static void setFluxSourceImpl( | ||||
|     const std::string& filename, FluxSourceProto* proto) | ||||
| { | ||||
|     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("^(.*\\.a2r)$"), | ||||
|              [](auto& s, auto* proto) | ||||
|                 { | ||||
|                     proto->set_type(FluxSourceProto::A2R); | ||||
|                     proto->mutable_a2r()->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); | ||||
|                     globalConfig().overrides()->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) | ||||
|     for (const auto& it : fluxConstructors) | ||||
|     { | ||||
|         std::smatch match; | ||||
|         if (std::regex_match(filename, match, it.first)) | ||||
|         if (std::regex_match(filename, match, it.pattern)) | ||||
|         { | ||||
|             it.second(match[1], proto); | ||||
|             if (!it.source) | ||||
|                 throw new InapplicableValueException(); | ||||
|             it.source(match[1], proto); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| @@ -412,56 +500,16 @@ void Config::setFluxSource(std::string filename) | ||||
|     setFluxSourceImpl(filename, overrides()->mutable_flux_source()); | ||||
| } | ||||
|  | ||||
| static void setFluxSinkImpl(std::string filename, FluxSinkProto* proto) | ||||
| static void setFluxSinkImpl(const std::string& filename, FluxSinkProto* proto) | ||||
| { | ||||
|     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); | ||||
|                     globalConfig().overrides()->mutable_drive()->set_drive( | ||||
|                         std::stoi(s)); | ||||
|                 }}, | ||||
|     }; | ||||
|  | ||||
|     for (const auto& it : formats) | ||||
|     for (const auto& it : fluxConstructors) | ||||
|     { | ||||
|         std::smatch match; | ||||
|         if (std::regex_match(filename, match, it.first)) | ||||
|         if (std::regex_match(filename, match, it.pattern)) | ||||
|         { | ||||
|             it.second(match[1], proto); | ||||
|             if (!it.sink) | ||||
|                 throw new InapplicableValueException(); | ||||
|             it.sink(match[1], proto); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| @@ -487,34 +535,14 @@ void Config::setVerificationFluxSource(std::string filename) | ||||
|  | ||||
| void Config::setImageReader(std::string filename) | ||||
| { | ||||
|     static const std::map<std::string, std::function<void(ImageReaderProto*)>> | ||||
|         formats = { | ||||
|   // clang-format off | ||||
| 		{".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 | ||||
|     }; | ||||
|  | ||||
|     for (const auto& it : formats) | ||||
|     for (const auto& it : imageConstructors) | ||||
|     { | ||||
|         if (endsWith(filename, it.first)) | ||||
|         if (endsWith(filename, it.extension)) | ||||
|         { | ||||
|             it.second(overrides()->mutable_image_reader()); | ||||
|             if (it.mode == MODE_WO) | ||||
|                 throw new InapplicableValueException(); | ||||
|  | ||||
|             overrides()->mutable_image_reader()->set_type(it.type); | ||||
|             overrides()->mutable_image_reader()->set_filename(filename); | ||||
|             return; | ||||
|         } | ||||
| @@ -525,31 +553,14 @@ void Config::setImageReader(std::string filename) | ||||
|  | ||||
| void Config::setImageWriter(std::string filename) | ||||
| { | ||||
|     static const std::map<std::string, std::function<void(ImageWriterProto*)>> | ||||
|         formats = { | ||||
|   // clang-format off | ||||
| 		{".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 | ||||
|     }; | ||||
|  | ||||
|     for (const auto& it : formats) | ||||
|     for (const auto& it : imageConstructors) | ||||
|     { | ||||
|         if (endsWith(filename, it.first)) | ||||
|         if (endsWith(filename, it.extension)) | ||||
|         { | ||||
|             it.second(overrides()->mutable_image_writer()); | ||||
|             if (it.mode == MODE_RO) | ||||
|                 throw new InapplicableValueException(); | ||||
|  | ||||
|             overrides()->mutable_image_writer()->set_type(it.type); | ||||
|             overrides()->mutable_image_writer()->set_filename(filename); | ||||
|             return; | ||||
|         } | ||||
| @@ -560,7 +571,7 @@ void Config::setImageWriter(std::string filename) | ||||
|  | ||||
| bool Config::hasFluxSource() | ||||
| { | ||||
|     return (*this)->flux_source().type() != FluxSourceProto::NOT_SET; | ||||
|     return (*this)->flux_source().type() != FLUXTYPE_NOT_SET; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<FluxSource>& Config::getFluxSource() | ||||
| @@ -578,7 +589,7 @@ std::shared_ptr<FluxSource>& Config::getFluxSource() | ||||
|  | ||||
| bool Config::hasVerificationFluxSource() const | ||||
| { | ||||
|     return _verificationFluxSourceProto.type() != FluxSourceProto::NOT_SET; | ||||
|     return _verificationFluxSourceProto.type() != FLUXTYPE_NOT_SET; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<FluxSource>& Config::getVerificationFluxSource() | ||||
| @@ -596,7 +607,7 @@ std::shared_ptr<FluxSource>& Config::getVerificationFluxSource() | ||||
|  | ||||
| bool Config::hasImageReader() | ||||
| { | ||||
|     return (*this)->image_reader().type() != ImageReaderProto::NOT_SET; | ||||
|     return (*this)->image_reader().type() != IMAGETYPE_NOT_SET; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<ImageReader>& Config::getImageReader() | ||||
| @@ -614,7 +625,7 @@ std::shared_ptr<ImageReader>& Config::getImageReader() | ||||
|  | ||||
| bool Config::hasFluxSink() | ||||
| { | ||||
|     return (*this)->flux_sink().type() != FluxSinkProto::NOT_SET; | ||||
|     return (*this)->flux_sink().type() != FLUXTYPE_NOT_SET; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<FluxSink> Config::getFluxSink() | ||||
| @@ -627,7 +638,7 @@ std::unique_ptr<FluxSink> Config::getFluxSink() | ||||
|  | ||||
| bool Config::hasImageWriter() | ||||
| { | ||||
|     return (*this)->image_writer().type() != ImageWriterProto::NOT_SET; | ||||
|     return (*this)->image_writer().type() != IMAGETYPE_NOT_SET; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<ImageWriter> Config::getImageWriter() | ||||
| @@ -671,3 +682,8 @@ std::shared_ptr<Decoder>& Config::getDecoder() | ||||
|     } | ||||
|     return _decoder; | ||||
| } | ||||
|  | ||||
| const std::vector<FluxConstructor>& Config::getFluxFormats() | ||||
| { | ||||
|     return fluxConstructors; | ||||
| } | ||||
							
								
								
									
										21
									
								
								lib/config.h
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								lib/config.h
									
									
									
									
									
								
							| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| #include <google/protobuf/message.h> | ||||
| #include "lib/config.pb.h" | ||||
| #include "lib/common.pb.h" | ||||
|  | ||||
| class ConfigProto; | ||||
| class OptionProto; | ||||
| @@ -46,6 +47,22 @@ public: | ||||
|     } | ||||
| }; | ||||
|  | ||||
| class InapplicableValueException : public ErrorException | ||||
| { | ||||
| public: | ||||
|     InapplicableValueException(): | ||||
|         ErrorException("selected format cannot be used here") | ||||
|     {} | ||||
| }; | ||||
|  | ||||
| struct FluxConstructor | ||||
| { | ||||
|     std::string name; | ||||
|     std::regex pattern; | ||||
|     std::function<void(const std::string& filename, FluxSourceProto*)> source; | ||||
|     std::function<void(const std::string& filename, FluxSinkProto*)> sink; | ||||
| }; | ||||
|  | ||||
| class Config | ||||
| { | ||||
| public: | ||||
| @@ -146,6 +163,10 @@ public: | ||||
|     bool hasImageWriter(); | ||||
|     std::unique_ptr<ImageWriter> getImageWriter(); | ||||
|  | ||||
| public: | ||||
|     static const std::vector<FluxConstructor>& getFluxFormats(); | ||||
|     static std::vector<std::string> getImageFormats(); | ||||
|  | ||||
| private: | ||||
|     ConfigProto _baseConfig; | ||||
|     ConfigProto _overridesConfig; | ||||
|   | ||||
| @@ -50,7 +50,7 @@ message ConfigProto | ||||
|     repeated OptionGroupProto option_group = 20; | ||||
| } | ||||
|  | ||||
| message OptionRequirementProto | ||||
| message OptionPrerequisiteProto | ||||
| { | ||||
|     optional string key = 1 [ (help) = "path to config value" ]; | ||||
|     repeated string value = 2 [ (help) = "list of required values" ]; | ||||
| @@ -65,7 +65,7 @@ message OptionProto | ||||
|         [ (help) = "message to display when option is in use" ]; | ||||
|     optional bool set_by_default = 6 | ||||
|         [ (help) = "this option is applied by default", default = false ]; | ||||
|     repeated OptionRequirementProto requires = 7 | ||||
|     repeated OptionPrerequisiteProto prerequisite = 7 | ||||
|         [ (help) = "prerequisites for this option" ]; | ||||
|  | ||||
|     optional ConfigProto config = 4 | ||||
|   | ||||
| @@ -98,6 +98,9 @@ Fluxmap& Fluxmap::appendBits(const std::vector<bool>& bits, nanoseconds_t clock) | ||||
|             appendPulse(); | ||||
|         } | ||||
|     } | ||||
|     unsigned delta = (now - duration()) / NS_PER_TICK; | ||||
|     if (delta) | ||||
|         appendInterval(delta); | ||||
|  | ||||
|     return *this; | ||||
| } | ||||
|   | ||||
| @@ -30,8 +30,8 @@ static void upgradeFluxFile(FluxFileProto& proto) | ||||
|         error( | ||||
|             "this is a version {} flux file, but this build of the client can " | ||||
|             "only handle up to version {} --- please upgrade", | ||||
|             proto.version(), | ||||
|             FluxFileVersion::VERSION_2); | ||||
|             (int)proto.version(), | ||||
|             (int)FluxFileVersion::VERSION_2); | ||||
| } | ||||
|  | ||||
| FluxFileProto loadFl2File(const std::string filename) | ||||
|   | ||||
| @@ -10,26 +10,25 @@ std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config) | ||||
| { | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case FluxSinkProto::DRIVE: | ||||
|         case FLUXTYPE_DRIVE: | ||||
|             return createHardwareFluxSink(config.drive()); | ||||
|  | ||||
|         case FluxSinkProto::A2R: | ||||
|         case FLUXTYPE_A2R: | ||||
|             return createA2RFluxSink(config.a2r()); | ||||
|  | ||||
|         case FluxSinkProto::AU: | ||||
|         case FLUXTYPE_AU: | ||||
|             return createAuFluxSink(config.au()); | ||||
|  | ||||
|         case FluxSinkProto::VCD: | ||||
|         case FLUXTYPE_VCD: | ||||
|             return createVcdFluxSink(config.vcd()); | ||||
|  | ||||
|         case FluxSinkProto::SCP: | ||||
|         case FLUXTYPE_SCP: | ||||
|             return createScpFluxSink(config.scp()); | ||||
|  | ||||
|         case FluxSinkProto::FLUX: | ||||
|         case FLUXTYPE_FLUX: | ||||
|             return createFl2FluxSink(config.fl2()); | ||||
|  | ||||
|         default: | ||||
|             error("bad output disk config"); | ||||
|             return std::unique_ptr<FluxSink>(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -29,17 +29,8 @@ message Fl2FluxSinkProto { | ||||
|  | ||||
| // Next: 10 | ||||
| message FluxSinkProto { | ||||
| 	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 FluxSourceSinkType type = 9 | ||||
| 		[default = FLUXTYPE_NOT_SET, (help) = "flux sink type"]; | ||||
|  | ||||
| 	optional HardwareFluxSinkProto drive = 2; | ||||
| 	optional A2RFluxSinkProto a2r = 8; | ||||
|   | ||||
| @@ -180,7 +180,7 @@ public: | ||||
|         trackdataWriter += fluxdata; | ||||
|     } | ||||
|  | ||||
|     operator std::string() const | ||||
|     operator std::string() const override | ||||
|     { | ||||
|         return fmt::format("scp({})", _config.filename()); | ||||
|     } | ||||
|   | ||||
| @@ -64,7 +64,7 @@ public: | ||||
|         of << "\n"; | ||||
|     } | ||||
|  | ||||
|     operator std::string() const | ||||
|     operator std::string() const override | ||||
|     { | ||||
|         return fmt::format("vcd({})", _config.directory()); | ||||
|     } | ||||
|   | ||||
| @@ -142,7 +142,7 @@ public: | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|     void recalibrate() override {} | ||||
|  | ||||
| private: | ||||
|     Bytes findChunk(Bytes id) | ||||
|   | ||||
| @@ -15,7 +15,7 @@ public: | ||||
|         return std::unique_ptr<const Fluxmap>(); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|     void recalibrate() override {} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<FluxSource> FluxSource::createEraseFluxSource( | ||||
|   | ||||
| @@ -39,8 +39,8 @@ public: | ||||
|  | ||||
|         _extraConfig.mutable_drive()->set_rotational_period_ms( | ||||
|             _proto.rotational_period_ms()); | ||||
| 		if (_proto.has_tpi()) | ||||
| 			_extraConfig.mutable_drive()->set_tpi(_proto.tpi()); | ||||
|         if (_proto.has_tpi()) | ||||
|             _extraConfig.mutable_drive()->set_tpi(_proto.tpi()); | ||||
|     } | ||||
|  | ||||
| public: | ||||
| @@ -55,7 +55,7 @@ public: | ||||
|         return std::make_unique<EmptyFluxSourceIterator>(); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|     void recalibrate() override {} | ||||
|  | ||||
| private: | ||||
|     void check_for_error(std::ifstream& ifs) | ||||
|   | ||||
| @@ -10,35 +10,34 @@ std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config) | ||||
| { | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case FluxSourceProto::DRIVE: | ||||
|         case FLUXTYPE_DRIVE: | ||||
|             return createHardwareFluxSource(config.drive()); | ||||
|  | ||||
|         case FluxSourceProto::ERASE: | ||||
|         case FLUXTYPE_ERASE: | ||||
|             return createEraseFluxSource(config.erase()); | ||||
|  | ||||
|         case FluxSourceProto::KRYOFLUX: | ||||
|         case FLUXTYPE_KRYOFLUX: | ||||
|             return createKryofluxFluxSource(config.kryoflux()); | ||||
|  | ||||
|         case FluxSourceProto::TEST_PATTERN: | ||||
|         case FLUXTYPE_TEST_PATTERN: | ||||
|             return createTestPatternFluxSource(config.test_pattern()); | ||||
|  | ||||
|         case FluxSourceProto::SCP: | ||||
|         case FLUXTYPE_SCP: | ||||
|             return createScpFluxSource(config.scp()); | ||||
|  | ||||
|         case FluxSourceProto::A2R: | ||||
|         case FLUXTYPE_A2R: | ||||
|             return createA2rFluxSource(config.a2r()); | ||||
|  | ||||
|         case FluxSourceProto::CWF: | ||||
|         case FLUXTYPE_CWF: | ||||
|             return createCwfFluxSource(config.cwf()); | ||||
|  | ||||
|         case FluxSourceProto::FLUX: | ||||
|         case FLUXTYPE_FLUX: | ||||
|             return createFl2FluxSource(config.fl2()); | ||||
|  | ||||
|         case FluxSourceProto::FLX: | ||||
|         case FLUXTYPE_FLX: | ||||
|             return createFlxFluxSource(config.flx()); | ||||
|  | ||||
|         default: | ||||
|             error("bad input disk configuration"); | ||||
|             return std::unique_ptr<FluxSource>(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -41,20 +41,8 @@ message FlxFluxSourceProto { | ||||
|  | ||||
| // NEXT: 12 | ||||
| message FluxSourceProto { | ||||
| 	enum FluxSourceType { | ||||
| 		NOT_SET = 0; | ||||
| 		DRIVE = 1; | ||||
| 		TEST_PATTERN = 2; | ||||
| 		ERASE = 3; | ||||
| 		KRYOFLUX = 4; | ||||
| 		SCP = 5; | ||||
| 		CWF = 6; | ||||
| 		FLUX = 7; | ||||
| 		FLX = 8; | ||||
| 		A2R = 9; | ||||
| 	} | ||||
|  | ||||
| 	optional FluxSourceType type = 9 [default = NOT_SET, (help) = "flux source type"]; | ||||
| 	optional FluxSourceSinkType type = 9 | ||||
| 		[default = FLUXTYPE_NOT_SET, (help) = "flux source type"]; | ||||
|  | ||||
| 	optional A2rFluxSourceProto a2r = 11; | ||||
| 	optional CwfFluxSourceProto cwf = 7; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ public: | ||||
|         return readFlxBytes(Bytes::readFromFile(path)); | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|     void recalibrate() override {} | ||||
|  | ||||
| private: | ||||
|     const std::string _path; | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include <fstream> | ||||
| #include <sys/types.h> | ||||
| #include <dirent.h> | ||||
| #include <filesystem> | ||||
|  | ||||
| #define MCLK_HZ (((18432000.0 * 73.0) / 14.0) / 2.0) | ||||
| #define SCLK_HZ (MCLK_HZ / 2) | ||||
| @@ -23,10 +24,18 @@ static bool has_suffix(const std::string& haystack, const std::string& needle) | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Fluxmap> readStream( | ||||
|     const std::string& dir, unsigned track, unsigned side) | ||||
|     std::string dir, unsigned track, unsigned side) | ||||
| { | ||||
|     std::string suffix = fmt::format("{:02}.{}.raw", track, side); | ||||
|  | ||||
|     FILE* fp = fopen(dir.c_str(), "r"); | ||||
|     if (fp) | ||||
|     { | ||||
|         fclose(fp); | ||||
|         int i = dir.find_last_of("/\\"); | ||||
|         dir = dir.substr(0, i); | ||||
|     } | ||||
|  | ||||
|     DIR* dirp = opendir(dir.c_str()); | ||||
|     if (!dirp) | ||||
|         error("cannot access path '{}'", dir); | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| #define STREAM_H | ||||
|  | ||||
| extern std::unique_ptr<Fluxmap> readStream( | ||||
|     const std::string& dir, unsigned track, unsigned side); | ||||
|     std::string dir, unsigned track, unsigned side); | ||||
| extern std::unique_ptr<Fluxmap> readStream(const std::string& path); | ||||
| extern std::unique_ptr<Fluxmap> readStream(const Bytes& bytes); | ||||
|  | ||||
|   | ||||
| @@ -128,7 +128,7 @@ public: | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
|     void recalibrate() {} | ||||
|     void recalibrate() override {} | ||||
|  | ||||
| private: | ||||
|     void check_for_error() | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
| #include <climits> | ||||
| #include <variant> | ||||
| #include <optional> | ||||
| #include <regex> | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| #if defined(_WIN32) || defined(__WIN32__) | ||||
|   | ||||
| @@ -15,37 +15,37 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config) | ||||
| { | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case ImageReaderProto::DIM: | ||||
|         case IMAGETYPE_DIM: | ||||
|             return ImageReader::createDimImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::D88: | ||||
|         case IMAGETYPE_D88: | ||||
|             return ImageReader::createD88ImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::FDI: | ||||
|         case IMAGETYPE_FDI: | ||||
|             return ImageReader::createFdiImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::IMD: | ||||
|         case IMAGETYPE_IMD: | ||||
|             return ImageReader::createIMDImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::IMG: | ||||
|         case IMAGETYPE_IMG: | ||||
|             return ImageReader::createImgImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::DISKCOPY: | ||||
|         case IMAGETYPE_DISKCOPY: | ||||
|             return ImageReader::createDiskCopyImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::JV3: | ||||
|         case IMAGETYPE_JV3: | ||||
|             return ImageReader::createJv3ImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::D64: | ||||
|         case IMAGETYPE_D64: | ||||
|             return ImageReader::createD64ImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::NFD: | ||||
|         case IMAGETYPE_NFD: | ||||
|             return ImageReader::createNFDImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::NSI: | ||||
|         case IMAGETYPE_NSI: | ||||
|             return ImageReader::createNsiImageReader(config); | ||||
|  | ||||
|         case ImageReaderProto::TD0: | ||||
|         case IMAGETYPE_TD0: | ||||
|             return ImageReader::createTd0ImageReader(config); | ||||
|  | ||||
|         default: | ||||
|   | ||||
| @@ -24,22 +24,8 @@ message ImageReaderProto | ||||
|         default = false | ||||
|     ]; | ||||
|  | ||||
| 	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 ImageReaderWriterType type = 14 | ||||
| 		[default = IMAGETYPE_NOT_SET, (help) = "input image type"]; | ||||
|  | ||||
| 	optional ImgInputOutputProto img = 2; | ||||
| 	optional DiskCopyInputProto diskcopy = 3; | ||||
|   | ||||
| @@ -15,28 +15,28 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config) | ||||
| { | ||||
|     switch (config.type()) | ||||
|     { | ||||
|         case ImageWriterProto::IMG: | ||||
|         case IMAGETYPE_IMG: | ||||
|             return ImageWriter::createImgImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::D64: | ||||
|         case IMAGETYPE_D64: | ||||
|             return ImageWriter::createD64ImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::LDBS: | ||||
|         case IMAGETYPE_LDBS: | ||||
|             return ImageWriter::createLDBSImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::DISKCOPY: | ||||
|         case IMAGETYPE_DISKCOPY: | ||||
|             return ImageWriter::createDiskCopyImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::NSI: | ||||
|         case IMAGETYPE_NSI: | ||||
|             return ImageWriter::createNsiImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::RAW: | ||||
|         case IMAGETYPE_RAW: | ||||
|             return ImageWriter::createRawImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::D88: | ||||
|         case IMAGETYPE_D88: | ||||
|             return ImageWriter::createD88ImageWriter(config); | ||||
|  | ||||
|         case ImageWriterProto::IMD: | ||||
|         case IMAGETYPE_IMD: | ||||
|             return ImageWriter::createImdImageWriter(config); | ||||
|  | ||||
|         default: | ||||
|   | ||||
| @@ -66,25 +66,14 @@ message ImdOutputProto | ||||
| // 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 | ||||
|     ]; | ||||
|  | ||||
|     optional ImageWriterType type = 11 [ default = NOT_SET, (help) = "image writer type" ]; | ||||
|     optional ImageReaderWriterType type = 11 | ||||
|         [ default = IMAGETYPE_NOT_SET, (help) = "image writer type" ]; | ||||
|  | ||||
|     optional ImgInputOutputProto img = 2; | ||||
|     optional D64OutputProto d64 = 3; | ||||
|   | ||||
| @@ -273,6 +273,8 @@ public: | ||||
|                             case ImdOutputProto::RATE_DD: | ||||
|                                 RATE = 2000; | ||||
|                                 break; | ||||
|                             case ImdOutputProto::RATE_GUESS: | ||||
|                                 break; | ||||
|                         } | ||||
|                     } | ||||
|                     header.ModeValue = | ||||
|   | ||||
| @@ -119,6 +119,7 @@ static ProtoField resolveProtoPath( | ||||
|         switch (field->label()) | ||||
|         { | ||||
|             case google::protobuf::FieldDescriptor::LABEL_OPTIONAL: | ||||
|             case google::protobuf::FieldDescriptor::LABEL_REQUIRED: | ||||
|                 if (!create && !reflection->HasField(*message, field)) | ||||
|                     throw ProtoPathNotFoundException(fmt::format( | ||||
|                         "could not find config field '{}'", field->name())); | ||||
| @@ -139,7 +140,7 @@ static ProtoField resolveProtoPath( | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 error("bad proto label {}", field->label()); | ||||
|                 error("bad proto label for field '{}' in '{}'", item, path); | ||||
|         } | ||||
|  | ||||
|         descriptor = message->GetDescriptor(); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define PROTO_H | ||||
|  | ||||
| #include <google/protobuf/message.h> | ||||
| #include "lib/common.pb.h" | ||||
| #include "lib/config.pb.h" | ||||
|  | ||||
| class ProtoPathNotFoundException : public ErrorException | ||||
|   | ||||
| @@ -456,6 +456,9 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource, | ||||
|     auto trackFlux = std::make_shared<TrackFlux>(); | ||||
|     trackFlux->trackInfo = trackInfo; | ||||
|  | ||||
|     if (fluxSource.isHardware()) | ||||
|         measureDiskRotation(); | ||||
|  | ||||
|     FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource); | ||||
|     int retriesRemaining = globalConfig()->decoder().retries(); | ||||
|     for (;;) | ||||
| @@ -498,8 +501,6 @@ std::shared_ptr<const DiskFlux> readDiskCommand( | ||||
|     auto diskflux = std::make_shared<DiskFlux>(); | ||||
|  | ||||
|     log(BeginOperationLogMessage{"Reading and decoding disk"}); | ||||
|     if (fluxSource.isHardware()) | ||||
|         measureDiskRotation(); | ||||
|     auto locations = Layout::computeLocations(); | ||||
|     unsigned index = 0; | ||||
|     for (auto& trackInfo : locations) | ||||
|   | ||||
							
								
								
									
										10
									
								
								lib/sector.h
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								lib/sector.h
									
									
									
									
									
								
							| @@ -90,6 +90,16 @@ struct Sector : public LogicalLocation | ||||
|     } | ||||
| }; | ||||
|  | ||||
| template <> | ||||
| struct fmt::formatter<Sector::Status> : formatter<string_view> | ||||
| { | ||||
|     auto format(Sector::Status status, format_context& ctx) const | ||||
|     { | ||||
|         return formatter<string_view>::format( | ||||
|             Sector::statusToString(status), ctx); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| extern bool sectorPointerSortPredicate(const std::shared_ptr<const Sector>& lhs, | ||||
|     const std::shared_ptr<const Sector>& rhs); | ||||
| extern bool sectorPointerEqualsPredicate( | ||||
|   | ||||
| @@ -76,7 +76,7 @@ public: | ||||
|                 "your FluxEngine firmware is at version {} but the client is " | ||||
|                 "for version {}; please upgrade", | ||||
|                 version, | ||||
|                 FLUXENGINE_PROTOCOL_VERSION); | ||||
|                 (int) FLUXENGINE_PROTOCOL_VERSION); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|   | ||||
| @@ -26,32 +26,39 @@ static const std::string get_serial_number(const libusbp::device& device) | ||||
|  | ||||
| std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices() | ||||
| { | ||||
|     std::vector<std::shared_ptr<CandidateDevice>> candidates; | ||||
|     for (const auto& it : libusbp::list_connected_devices()) | ||||
|     try | ||||
|     { | ||||
|         auto candidate = std::make_unique<CandidateDevice>(); | ||||
|         candidate->device = it; | ||||
|  | ||||
|         uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); | ||||
|         if (VALID_DEVICES.find(id) != VALID_DEVICES.end()) | ||||
|         std::vector<std::shared_ptr<CandidateDevice>> candidates; | ||||
|         for (const auto& it : libusbp::list_connected_devices()) | ||||
|         { | ||||
|             candidate->id = id; | ||||
|             candidate->serial = get_serial_number(it); | ||||
|             auto candidate = std::make_unique<CandidateDevice>(); | ||||
|             candidate->device = it; | ||||
|  | ||||
|             if (id == GREASEWEAZLE_ID) | ||||
|             uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); | ||||
|             if (VALID_DEVICES.find(id) != VALID_DEVICES.end()) | ||||
|             { | ||||
|                 libusbp::serial_port port(candidate->device); | ||||
|                 candidate->serialPort = port.get_name(); | ||||
|                 candidate->type = DEVICE_GREASEWEAZLE; | ||||
|                 candidate->id = id; | ||||
|                 candidate->serial = get_serial_number(it); | ||||
|  | ||||
|                 if (id == GREASEWEAZLE_ID) | ||||
|                 { | ||||
|                     libusbp::serial_port port(candidate->device); | ||||
|                     candidate->serialPort = port.get_name(); | ||||
|                     candidate->type = DEVICE_GREASEWEAZLE; | ||||
|                 } | ||||
|                 else if (id == FLUXENGINE_ID) | ||||
|                     candidate->type = DEVICE_FLUXENGINE; | ||||
|  | ||||
|                 candidates.push_back(std::move(candidate)); | ||||
|             } | ||||
|             else if (id == FLUXENGINE_ID) | ||||
|                 candidate->type = DEVICE_FLUXENGINE; | ||||
|  | ||||
|             candidates.push_back(std::move(candidate)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return candidates; | ||||
|         return candidates; | ||||
|     } | ||||
|     catch (const libusbp::error& e) | ||||
|     { | ||||
|         error("USB error: {}", e.message()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::string getDeviceName(DeviceType type) | ||||
|   | ||||
							
								
								
									
										13
									
								
								lib/utils.cc
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								lib/utils.cc
									
									
									
									
									
								
							| @@ -6,7 +6,7 @@ | ||||
|  | ||||
| bool emergencyStop = false; | ||||
|  | ||||
| static const char* WHITESPACE = " \t\n\r\f\v"; | ||||
| static std::string WHITESPACE(" \t\n\r\f\v\0", 7); | ||||
| static const char* SEPARATORS = "/\\"; | ||||
|  | ||||
| void ErrorException::print() const | ||||
| @@ -197,6 +197,17 @@ bool doesFileExist(const std::string& filename) | ||||
|     return f.good(); | ||||
| } | ||||
|  | ||||
| int countSetBits(uint32_t word) | ||||
| { | ||||
|     int b = 0; | ||||
|     while (word) | ||||
|     { | ||||
|         b += word & 1; | ||||
|         word >>= 1; | ||||
|     } | ||||
|     return b; | ||||
| } | ||||
|  | ||||
| uint32_t unbcd(uint32_t bcd) | ||||
| { | ||||
|     uint32_t dec = 0; | ||||
|   | ||||
| @@ -19,6 +19,7 @@ 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); | ||||
| extern int countSetBits(uint32_t word); | ||||
| extern uint32_t unbcd(uint32_t bcd); | ||||
|  | ||||
| template <class K, class V> | ||||
|   | ||||
| @@ -56,7 +56,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE | | ||||
|                OP_GETDIRENT | OP_DELETE | OP_MOVE | OP_CREATEDIR; | ||||
| @@ -229,7 +229,7 @@ public: | ||||
|             throw CannotWriteException(); | ||||
|     } | ||||
|  | ||||
|     void createDirectory(const Path& path) | ||||
|     void createDirectory(const Path& path) override | ||||
|     { | ||||
|         AdfMount m(this); | ||||
|         if (path.empty()) | ||||
|   | ||||
| @@ -50,7 +50,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE; | ||||
|     } | ||||
|   | ||||
| @@ -232,7 +232,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE | OP_GETDIRENT | | ||||
|                OP_DELETE; | ||||
|   | ||||
| @@ -195,7 +195,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT; | ||||
|     } | ||||
|   | ||||
| @@ -81,7 +81,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT; | ||||
|     } | ||||
|   | ||||
| @@ -36,7 +36,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE | | ||||
|                OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE; | ||||
| @@ -199,7 +199,7 @@ public: | ||||
|         throwError(res); | ||||
|     } | ||||
|  | ||||
|     void createDirectory(const Path& path) | ||||
|     void createDirectory(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         auto pathStr = path.to_str(); | ||||
| @@ -296,7 +296,7 @@ private: | ||||
|  | ||||
|             default: | ||||
|                 throw FilesystemException( | ||||
|                     fmt::format("unknown fatfs error {}", res)); | ||||
|                     fmt::format("unknown fatfs error {}", (int)res)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -44,7 +44,7 @@ public: | ||||
|         return _changedSectors.put(track, side, sectorId); | ||||
|     } | ||||
|  | ||||
|     virtual bool isReadOnly() | ||||
|     virtual bool isReadOnly() override | ||||
|     { | ||||
|         return (_fluxSink == nullptr); | ||||
|     } | ||||
|   | ||||
| @@ -20,19 +20,19 @@ public: | ||||
|  | ||||
| public: | ||||
|     std::shared_ptr<const Sector> get( | ||||
|         unsigned track, unsigned side, unsigned sectorId) | ||||
|         unsigned track, unsigned side, unsigned sectorId) override | ||||
|     { | ||||
|         return _image->get(track, side, sectorId); | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Sector> put( | ||||
|         unsigned track, unsigned side, unsigned sectorId) | ||||
|         unsigned track, unsigned side, unsigned sectorId) override | ||||
|     { | ||||
|         _changed = true; | ||||
|         return _image->put(track, side, sectorId); | ||||
|     } | ||||
|  | ||||
|     virtual bool isReadOnly() | ||||
|     virtual bool isReadOnly() override | ||||
|     { | ||||
|         return (_writer == nullptr); | ||||
|     } | ||||
|   | ||||
| @@ -214,6 +214,7 @@ private: | ||||
|         _directoryBlock = rbr.read_be32(); | ||||
|         rbr.skip(4); | ||||
|         _directorySize = rbr.read_be32(); | ||||
| 		rbr.skip(4); | ||||
|         unsigned tracks = rbr.read_be32(); | ||||
|         unsigned heads = rbr.read_be32(); | ||||
|         unsigned sectors = rbr.read_be32(); | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE | | ||||
|                OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE; | ||||
|   | ||||
							
								
								
									
										223
									
								
								lib/vfs/microdos.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								lib/vfs/microdos.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "lib/utils.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| /* See https://www.hp9845.net/9845/projects/hpdir/#lif_filesystem for | ||||
|  * a description. */ | ||||
|  | ||||
| static void trimZeros(std::string s) | ||||
| { | ||||
|     s.erase(std::remove(s.begin(), s.end(), 0), s.end()); | ||||
| } | ||||
|  | ||||
| class MicrodosFilesystem : public Filesystem | ||||
| { | ||||
|     struct SDW | ||||
|     { | ||||
|         unsigned start; | ||||
|         unsigned length; | ||||
|     }; | ||||
|  | ||||
|     class MicrodosDirent : public Dirent | ||||
|     { | ||||
|     public: | ||||
|         MicrodosDirent(MicrodosFilesystem& fs, Bytes& bytes) | ||||
|         { | ||||
|             file_type = TYPE_FILE; | ||||
|  | ||||
|             ByteReader br(bytes); | ||||
|             auto stem = trimWhitespace(br.read(6)); | ||||
|             auto ext = trimWhitespace(br.read(3)); | ||||
|             filename = fmt::format("{}.{}", stem, ext); | ||||
|  | ||||
|             br.skip(1); | ||||
|             ssn = br.read_be16(); | ||||
|             attr = br.read_8(); | ||||
|  | ||||
|             Bytes rib = fs.getLogicalSector(ssn); | ||||
|             ByteReader rbr(rib); | ||||
|             for (int i = 0; i < 57; i++) | ||||
|             { | ||||
|                 unsigned w = rbr.read_be16(); | ||||
|                 if (w & 0x8000) | ||||
|                 { | ||||
|                     /* Last. */ | ||||
|                     sectors = w & 0x7fff; | ||||
|                     break; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     /* Each record except the last is 24 bits long. */ | ||||
|                     w = (w << 8) | rbr.read_8(); | ||||
|                     sdws.emplace_back(SDW{w & 0xffff, (w >> 16) + 1}); | ||||
|                 } | ||||
|             } | ||||
|             rbr.seek(500); | ||||
|             lastSectorBytes = rbr.read_be16(); | ||||
|             loadSectors = rbr.read_be16(); | ||||
|             loadAddress = rbr.read_be16(); | ||||
|             startAddress = rbr.read_be16(); | ||||
|  | ||||
|             length = sectors * 512; | ||||
|  | ||||
|             mode = ""; | ||||
|             path = {filename}; | ||||
|  | ||||
|             attributes[Filesystem::FILENAME] = filename; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|             attributes[Filesystem::FILE_TYPE] = "file"; | ||||
|             attributes[Filesystem::MODE] = mode; | ||||
|             attributes["microdos.ssn"] = std::to_string(ssn); | ||||
|             attributes["microdos.attr"] = fmt::format("0x{:x}", attr); | ||||
|             attributes["microdos.sdw_count"] = std::to_string(sdws.size()); | ||||
|             attributes["microdos.total_sectors"] = std::to_string(sectors); | ||||
|             attributes["microdos.lastSectorBytes"] = | ||||
|                 std::to_string(lastSectorBytes); | ||||
|             attributes["microdos.loadSectors"] = std::to_string(loadSectors); | ||||
|             attributes["microdos.loadAddress"] = | ||||
|                 fmt::format("0x{:x}", loadAddress); | ||||
|             attributes["microdos.startAddress"] = | ||||
|                 fmt::format("0x{:x}", startAddress); | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         unsigned ssn; | ||||
|         unsigned attr; | ||||
|         std::vector<SDW> sdws; | ||||
|         unsigned sectors; | ||||
|         unsigned lastSectorBytes; | ||||
|         unsigned loadSectors; | ||||
|         unsigned loadAddress; | ||||
|         unsigned startAddress; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     MicrodosFilesystem( | ||||
|         const MicrodosProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|         Filesystem(sectors), | ||||
|         _config(config) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT; | ||||
|     } | ||||
|  | ||||
|     FilesystemStatus check() override | ||||
|     { | ||||
|         return FS_OK; | ||||
|     } | ||||
|  | ||||
|     std::map<std::string, std::string> getMetadata() override | ||||
|     { | ||||
|         mount(); | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|  | ||||
|         attributes[VOLUME_NAME] = _volumeLabel; | ||||
|         attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks); | ||||
|         attributes[USED_BLOCKS] = std::to_string(_usedBlocks); | ||||
|         attributes[BLOCK_SIZE] = "512"; | ||||
|         return attributes; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Dirent> getDirent(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         return findFile(path.front()); | ||||
|     } | ||||
|  | ||||
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (!path.empty()) | ||||
|             throw FileNotFoundException(); | ||||
|  | ||||
|         std::vector<std::shared_ptr<Dirent>> result; | ||||
|         for (auto& de : _dirents) | ||||
|             result.push_back(de); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     Bytes getFile(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         auto dirent = findFile(path.front()); | ||||
|  | ||||
|         Bytes data; | ||||
|         ByteWriter bw(data); | ||||
|         for (const auto& sdw : dirent->sdws) | ||||
|             bw += getLogicalSector(sdw.start, sdw.length); | ||||
|  | ||||
|         return data.slice(512); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void mount() | ||||
|     { | ||||
|         _rootBlock = getLogicalSector(0); | ||||
|         _catBlock = getLogicalSector(9); | ||||
|         Bytes directory = getLogicalSector(1, 8); | ||||
|  | ||||
|         ByteReader rbr(_rootBlock); | ||||
|         rbr.seek(20); | ||||
|         _volumeLabel = trimWhitespace(rbr.read(44)); | ||||
|  | ||||
|         _dirents.clear(); | ||||
|         ByteReader dbr(directory); | ||||
|         while (!dbr.eof()) | ||||
|         { | ||||
|             Bytes direntBytes = dbr.read(16); | ||||
|             if ((direntBytes[0] != 0) && (direntBytes[0] != 0xff)) | ||||
|             { | ||||
|                 auto dirent = | ||||
|                     std::make_unique<MicrodosDirent>(*this, direntBytes); | ||||
|                 _dirents.push_back(std::move(dirent)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ByteReader cbr(_catBlock); | ||||
|         _totalBlocks = 630; | ||||
|         _usedBlocks = 0; | ||||
|         for (int i = 0; i < _totalBlocks / 8; i++) | ||||
|         { | ||||
|             uint8_t b = cbr.read_8(); | ||||
|             _usedBlocks += countSetBits(b); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<MicrodosDirent> findFile(const std::string filename) | ||||
|     { | ||||
|         for (const auto& dirent : _dirents) | ||||
|         { | ||||
|             if (dirent->filename == filename) | ||||
|                 return dirent; | ||||
|         } | ||||
|  | ||||
|         throw FileNotFoundException(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const MicrodosProto& _config; | ||||
|     Bytes _rootBlock; | ||||
|     Bytes _catBlock; | ||||
|     std::string _volumeLabel; | ||||
|     unsigned _totalBlocks; | ||||
|     unsigned _usedBlocks; | ||||
|     std::vector<std::shared_ptr<MicrodosDirent>> _dirents; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Filesystem> Filesystem::createMicrodosFilesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors) | ||||
| { | ||||
|     return std::make_unique<MicrodosFilesystem>(config.microdos(), sectors); | ||||
| } | ||||
| @@ -10,14 +10,14 @@ | ||||
|  * 0d		2a | ||||
|  * 0e		79 | ||||
|  * 0f		6d | ||||
|  * 10		07 0x07c10c19, creation timestamp | ||||
|  * 10		07 0x07c10c19, creation timestamp; year | ||||
|  * 11		c1 ^ | ||||
|  * 12		0c ^ | ||||
|  * 13		19 ^ | ||||
|  * 14		2f | ||||
|  * 12		0c month | ||||
|  * 13		19 day | ||||
|  * 14		2f time? minutes | ||||
|  * 15		00 | ||||
|  * 16		00 | ||||
|  * 17		18 | ||||
|  * 16		00 hours | ||||
|  * 17		18 seconds | ||||
|  * 18		03 0x320, number of blocks on the disk | ||||
|  * 19		20 ^ | ||||
|  * 1a		00 0x0010, first data block? | ||||
| @@ -88,11 +88,6 @@ | ||||
| 00000CD0   00 0F 47 52  45 59 2E 43  4C 54 00 00  00 00 00 00  ..GREY.CLT...... | ||||
|  */ | ||||
|  | ||||
| static void trimZeros(std::string s) | ||||
| { | ||||
|     s.erase(std::remove(s.begin(), s.end(), 0), s.end()); | ||||
| } | ||||
|  | ||||
| class PhileFilesystem : public Filesystem | ||||
| { | ||||
|     struct Span | ||||
| @@ -125,6 +120,15 @@ class PhileFilesystem : public Filesystem | ||||
|             this->filename = filename; | ||||
|             path = {filename}; | ||||
|  | ||||
|             attributes[Filesystem::CTIME] = | ||||
|                 fmt::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}", | ||||
|                     filedes.reader().seek(4).read_be16(), | ||||
|                     filedes[6], | ||||
|                     filedes[7] + 1, | ||||
|                     filedes[10] & 0x1f, | ||||
|                     filedes[8], | ||||
|                     filedes[11]); | ||||
|  | ||||
|             attributes[Filesystem::FILENAME] = filename; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|             attributes[Filesystem::FILE_TYPE] = "file"; | ||||
| @@ -160,7 +164,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT; | ||||
|     } | ||||
| @@ -175,7 +179,7 @@ public: | ||||
|         mount(); | ||||
|  | ||||
|         std::string volumename = _rootBlock.reader().read(0x0c); | ||||
|         trimZeros(volumename); | ||||
|         volumename = trimWhitespace(volumename); | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|         attributes[VOLUME_NAME] = volumename; | ||||
| @@ -246,7 +250,7 @@ private: | ||||
|         { | ||||
|             uint16_t fileno = br.read_be16(); | ||||
|             std::string filename = br.read(14); | ||||
|             trimZeros(filename); | ||||
|             filename = trimWhitespace(filename); | ||||
|  | ||||
|             if (fileno) | ||||
|             { | ||||
|   | ||||
| @@ -140,7 +140,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_LIST | OP_GETDIRENT | OP_GETFILE | OP_GETFSDATA; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										427
									
								
								lib/vfs/roland.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								lib/vfs/roland.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,427 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "lib/utils.h" | ||||
| #include <regex> | ||||
|  | ||||
| static std::string unmangleFilename(const std::string& mangled) | ||||
| { | ||||
|     std::string extension = mangled.substr(10); | ||||
|     extension.erase(extension.find_last_not_of("_") + 1); | ||||
|  | ||||
|     std::string root = mangled.substr(0, 10); | ||||
|     root.erase(root.find_last_not_of("_") + 1); | ||||
|  | ||||
|     if (!extension.empty()) | ||||
|         return root + "." + extension; | ||||
|     return root; | ||||
| } | ||||
|  | ||||
| static std::string mangleFilename(const std::string& human) | ||||
| { | ||||
|     int dot = human.rfind('.'); | ||||
|     std::string extension = | ||||
|         (dot == std::string::npos) ? "" : human.substr(dot + 1); | ||||
|     std::string root = | ||||
|         (dot == std::string::npos) ? human : human.substr(0, dot); | ||||
|  | ||||
|     if (extension.empty()) | ||||
|         extension = "___"; | ||||
|     if (extension.size() > 3) | ||||
|         throw BadPathException("Invalid filename: extension too long"); | ||||
|     if (root.size() > 10) | ||||
|         throw BadPathException("Invalid filename: root too long"); | ||||
|     root = (root + std::string(10, '_')).substr(0, 10); | ||||
|     std::string mangled = root + extension; | ||||
|  | ||||
|     static const std::regex checker("[A-Z0-9_$.]*"); | ||||
|     if (!std::regex_match(mangled, checker)) | ||||
|         throw BadPathException( | ||||
|             "Invalid filename: unsupported characters (remember to use " | ||||
|             "uppercase)"); | ||||
|     return mangled; | ||||
| } | ||||
|  | ||||
| class RolandFsFilesystem : public Filesystem | ||||
| { | ||||
| private: | ||||
|     class RolandDirent : public Dirent | ||||
|     { | ||||
|     public: | ||||
|         RolandDirent(const std::string& filename) | ||||
|         { | ||||
|             file_type = TYPE_FILE; | ||||
|             rename(filename); | ||||
|  | ||||
|             length = 0; | ||||
|             attributes[Filesystem::FILENAME] = filename; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|             attributes[Filesystem::FILE_TYPE] = "file"; | ||||
|             attributes[Filesystem::MODE] = ""; | ||||
|         } | ||||
|  | ||||
|         void rename(const std::string& name) | ||||
|         { | ||||
|             filename = name; | ||||
|             path = {filename}; | ||||
|         } | ||||
|  | ||||
|         void putBlock(RolandFsFilesystem* fs, uint8_t offset, uint8_t block) | ||||
|         { | ||||
|             if (blocks.size() <= offset) | ||||
|                 blocks.resize(offset+1); | ||||
|             blocks[offset] = block; | ||||
|  | ||||
|             length = (offset+1) * fs->_blockSectors * fs->_sectorSize; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|         } | ||||
|  | ||||
|         void putBlocks(RolandFsFilesystem* fs, uint8_t offset, Bytes& dirent) | ||||
|         { | ||||
|             for (int i = 0; i < 16; i++) | ||||
|             { | ||||
|                 uint8_t blocknumber = dirent[16 + i]; | ||||
|                 if (!blocknumber) | ||||
|                     break; | ||||
|  | ||||
|                 putBlock(fs, offset+i, blocknumber); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         std::vector<int> blocks; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     RolandFsFilesystem( | ||||
|         const RolandFsProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|         Filesystem(sectors), | ||||
|         _config(config) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_CREATE | OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_PUTFILE | | ||||
|                OP_GETDIRENT | OP_DELETE | OP_MOVE; | ||||
|     } | ||||
|  | ||||
|     std::map<std::string, std::string> getMetadata() override | ||||
|     { | ||||
|         mount(); | ||||
|  | ||||
|         int usedBlocks = 0; | ||||
|         for (bool b : _allocationBitmap) | ||||
|             usedBlocks += b; | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|         attributes[VOLUME_NAME] = ""; | ||||
|         attributes[TOTAL_BLOCKS] = std::to_string(_filesystemBlocks); | ||||
|         attributes[USED_BLOCKS] = std::to_string(usedBlocks); | ||||
|         attributes[BLOCK_SIZE] = std::to_string(_config.block_size()); | ||||
|         return attributes; | ||||
|     } | ||||
|  | ||||
|     FilesystemStatus check() override | ||||
|     { | ||||
|         return FS_OK; | ||||
|     } | ||||
|  | ||||
|     void create(bool quick, const std::string& volumeName) override | ||||
|     { | ||||
|         if (!quick) | ||||
|             eraseEverythingOnDisk(); | ||||
|  | ||||
|         init(); | ||||
|         _allocationBitmap[0] = true; | ||||
|         rewriteDirectory(); | ||||
|     } | ||||
|  | ||||
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (!path.empty()) | ||||
|             throw FileNotFoundException(); | ||||
|  | ||||
|         std::vector<std::shared_ptr<Dirent>> result; | ||||
|         for (auto& de : _dirents) | ||||
|             result.push_back(de); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Dirent> getDirent(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         return findFile(path.front()); | ||||
|     } | ||||
|  | ||||
|     Bytes getFile(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         Bytes data; | ||||
|         ByteWriter bw(data); | ||||
|         auto f = findFile(path.front()); | ||||
|         for (uint8_t b : f->blocks) | ||||
|             bw += getRolandBlock(b); | ||||
|  | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     void putFile(const Path& path, const Bytes& bytes) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|         if (findFileOrReturnNull(path.front())) | ||||
|             throw BadPathException("File exists"); | ||||
|  | ||||
|         int blocks = bytes.size() / _config.block_size(); | ||||
|         auto de = std::make_shared<RolandDirent>( | ||||
|             RolandDirent(mangleFilename(path.front()))); | ||||
|  | ||||
|         ByteReader br(bytes); | ||||
|         int offset = 0; | ||||
|         while (!br.eof()) | ||||
|         { | ||||
|             Bytes data = | ||||
|                 br.read(_config.block_size()).slice(0, _config.block_size()); | ||||
|             int block = allocateBlock(); | ||||
|             de->putBlock(this, offset, block); | ||||
|             putRolandBlock(block, data); | ||||
|             offset++; | ||||
|         } | ||||
|  | ||||
|         _dirents.push_back(de); | ||||
|         rewriteDirectory(); | ||||
|     } | ||||
|  | ||||
|     void deleteFile(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         auto de = findFile(path.front()); | ||||
|         for (uint8_t b : de->blocks) | ||||
|             freeBlock(b); | ||||
|  | ||||
|         for (auto it = _dirents.begin(); it != _dirents.end(); it++) | ||||
|         { | ||||
|             if (*it == de) | ||||
|             { | ||||
|                 _dirents.erase(it); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         rewriteDirectory(); | ||||
|     } | ||||
|  | ||||
|     void moveFile(const Path& oldName, const Path& newName) override | ||||
|     { | ||||
|         mount(); | ||||
|         if ((oldName.size() != 1) || (newName.size() != 1)) | ||||
|             throw BadPathException(); | ||||
|         if (findFileOrReturnNull(newName.front())) | ||||
|             throw BadPathException("File exists"); | ||||
|  | ||||
|         auto de = findFile(oldName.front()); | ||||
|         de->rename(mangleFilename(newName.front())); | ||||
|         rewriteDirectory(); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void init() | ||||
|     { | ||||
|         _directoryLba = getOffsetOfSector(_config.directory_track(), 0, 0); | ||||
|         _sectorSize = getLogicalSectorSize(0, 0); | ||||
|  | ||||
|         _blockSectors = _config.block_size() / _sectorSize; | ||||
|  | ||||
|         _filesystemBlocks = getLogicalSectorCount() / _blockSectors; | ||||
|         _midBlock = (getLogicalSectorCount() - _directoryLba) / _blockSectors; | ||||
|  | ||||
|         _dirents.clear(); | ||||
|         _allocationBitmap.clear(); | ||||
|         _allocationBitmap.resize(_filesystemBlocks); | ||||
|     } | ||||
|  | ||||
|     void rewriteDirectory() | ||||
|     { | ||||
|         Bytes directory; | ||||
|         ByteWriter bw(directory); | ||||
|  | ||||
|         bw.write_8(0); | ||||
|         bw.append("ROLAND-GCRDOS"); | ||||
|         bw.write_le16(0x4e); | ||||
|         bw.pad(16); | ||||
|  | ||||
|         for (auto& de : _dirents) | ||||
|         { | ||||
|             int blockIndex = 0; | ||||
|             for (;;) | ||||
|             { | ||||
|                 if (bw.pos == 0xa00) | ||||
|                     throw DiskFullException(); | ||||
|  | ||||
|                 if ((blockIndex % 16) == 0) | ||||
|                 { | ||||
|                     bw.write_8(0); | ||||
|                     int len = de->filename.size(); | ||||
|                     bw.append(de->filename); | ||||
|                     bw.pad(13 - len, '_'); | ||||
|                     bw.write_8(0); | ||||
|                     bw.write_8(blockIndex / 16); | ||||
|                 } | ||||
|  | ||||
|                 if (blockIndex == de->blocks.size()) | ||||
|                     break; | ||||
|  | ||||
|                 bw.write_8(de->blocks[blockIndex]); | ||||
|                 blockIndex++; | ||||
|             } | ||||
|  | ||||
|             bw.pad(16 - (blockIndex % 16), 0); | ||||
|         } | ||||
|  | ||||
|         while (bw.pos != 0xa00) | ||||
|         { | ||||
|             bw.write_8(0xe5); | ||||
|             bw.pad(13, ' '); | ||||
|             bw.write_le16(0); | ||||
|             bw.pad(16); | ||||
|         } | ||||
|  | ||||
|         for (bool b : _allocationBitmap) | ||||
|             bw.write_8(b ? 0xff : 0x00); | ||||
|  | ||||
|         putRolandBlock(0, directory.slice(0, _config.block_size())); | ||||
|     } | ||||
|  | ||||
|     void mount() | ||||
|     { | ||||
|         init(); | ||||
|  | ||||
|         Bytes directory = getRolandBlock(0); | ||||
|         ByteReader br(directory); | ||||
|         br.seek(1); | ||||
|         if (br.read(13) != "ROLAND-GCRDOS") | ||||
|             throw BadFilesystemException(); | ||||
|         br.seek(32); | ||||
|  | ||||
|         std::map<std::string, std::shared_ptr<RolandDirent>> files; | ||||
|         for (int i = 0; i < _config.directory_entries(); i++) | ||||
|         { | ||||
|             Bytes direntBytes = br.read(32); | ||||
|             if (direntBytes[0] == 0) | ||||
|             { | ||||
|                 int extent = direntBytes[15]; | ||||
|                 std::string filename = | ||||
|                     unmangleFilename(direntBytes.slice(1, 13)); | ||||
|  | ||||
|                 std::shared_ptr<RolandDirent> de; | ||||
|                 auto it = files.find(filename); | ||||
|                 if (it == files.end()) | ||||
|                 { | ||||
|                     files[filename] = de = | ||||
|                         std::make_shared<RolandDirent>(filename); | ||||
|                     _dirents.push_back(de); | ||||
|                 } | ||||
|                 else | ||||
|                     de = it->second; | ||||
|  | ||||
|                 de->putBlocks(this, extent*16, direntBytes); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         br.seek(0xa00); | ||||
|         for (int i = 0; i < _filesystemBlocks; i++) | ||||
|             _allocationBitmap[i] = br.read_8(); | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<RolandDirent> findFileOrReturnNull( | ||||
|         const std::string filename) | ||||
|     { | ||||
|         for (const auto& dirent : _dirents) | ||||
|         { | ||||
|             if (dirent->filename == filename) | ||||
|                 return dirent; | ||||
|         } | ||||
|  | ||||
|         return nullptr; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<RolandDirent> findFile(const std::string filename) | ||||
|     { | ||||
|         std::shared_ptr<RolandDirent> de = findFileOrReturnNull(filename); | ||||
|         if (!de) | ||||
|             throw FileNotFoundException(); | ||||
|         return de; | ||||
|     } | ||||
|  | ||||
|     int allocateBlock() | ||||
|     { | ||||
|         for (int i = 0; i < _filesystemBlocks; i++) | ||||
|             if (!_allocationBitmap[i]) | ||||
|             { | ||||
|                 _allocationBitmap[i] = true; | ||||
|                 return i; | ||||
|             } | ||||
|  | ||||
|         throw DiskFullException(); | ||||
|     } | ||||
|  | ||||
|     void freeBlock(int block) | ||||
|     { | ||||
|         if (block >= _filesystemBlocks) | ||||
|             throw BadFilesystemException(); | ||||
|  | ||||
|         if (!_allocationBitmap[block]) | ||||
|             throw BadFilesystemException(); | ||||
|         _allocationBitmap[block] = false; | ||||
|     } | ||||
|  | ||||
|     unsigned blockToLogicalSectorNumber(int block) | ||||
|     { | ||||
|         int track; | ||||
|         if (block < _midBlock) | ||||
|             track = _config.directory_track() + block; | ||||
|         else | ||||
|             track = _config.directory_track() - (1 + block - _midBlock); | ||||
|         return track * _blockSectors; | ||||
|     } | ||||
|  | ||||
|     Bytes getRolandBlock(int number) | ||||
|     { | ||||
|         return getLogicalSector( | ||||
|             blockToLogicalSectorNumber(number), _blockSectors); | ||||
|     } | ||||
|  | ||||
|     void putRolandBlock(int number, const Bytes& bytes) | ||||
|     { | ||||
|         assert(bytes.size() == _config.block_size()); | ||||
|         putLogicalSector(blockToLogicalSectorNumber(number), bytes); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const RolandFsProto& _config; | ||||
|     unsigned _sectorSize; | ||||
|     unsigned _blockSectors; | ||||
|     unsigned _midBlock; | ||||
|     unsigned _directoryLba; | ||||
|     unsigned _filesystemBlocks; | ||||
|     std::vector<std::shared_ptr<RolandDirent>> _dirents; | ||||
|     std::vector<bool> _allocationBitmap; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Filesystem> Filesystem::createRolandFsFilesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors) | ||||
| { | ||||
|     return std::make_unique<RolandFsFilesystem>(config.roland(), sectors); | ||||
| } | ||||
| @@ -11,6 +11,9 @@ class Encoder; | ||||
|  | ||||
| class SectorInterface | ||||
| { | ||||
| public: | ||||
| 	virtual ~SectorInterface() {} | ||||
|  | ||||
| public: | ||||
|     virtual std::shared_ptr<const Sector> get( | ||||
|         unsigned track, unsigned side, unsigned sectorId) = 0; | ||||
|   | ||||
| @@ -139,7 +139,7 @@ public: | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     uint32_t capabilities() const override | ||||
|     { | ||||
|         return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT; | ||||
|     } | ||||
|   | ||||
| @@ -216,6 +216,15 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem( | ||||
|         case FilesystemProto::LIF: | ||||
|             return Filesystem::createLifFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::MICRODOS: | ||||
|             return Filesystem::createMicrodosFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::ZDOS: | ||||
|             return Filesystem::createZDosFilesystem(config, image); | ||||
|  | ||||
|         case FilesystemProto::ROLAND: | ||||
|             return Filesystem::createRolandFsFilesystem(config, image); | ||||
|  | ||||
|         default: | ||||
|             error("no filesystem configured"); | ||||
|             return std::unique_ptr<Filesystem>(); | ||||
| @@ -236,7 +245,7 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig() | ||||
|             fluxSource = globalConfig().getFluxSource(); | ||||
|             decoder = globalConfig().getDecoder(); | ||||
|         } | ||||
|         if (globalConfig()->flux_sink().type() == FluxSinkProto::DRIVE) | ||||
|         if (globalConfig()->flux_sink().type() == FLUXTYPE_DRIVE) | ||||
|         { | ||||
|             fluxSink = globalConfig().getFluxSink(); | ||||
|             encoder = globalConfig().getEncoder(); | ||||
| @@ -273,8 +282,8 @@ Bytes Filesystem::getLogicalSector(uint32_t number, uint32_t count) | ||||
| { | ||||
|     if ((number + count) > _locations.size()) | ||||
|         throw BadFilesystemException( | ||||
|             fmt::format("invalid filesystem: sector {} is out of bounds", | ||||
|                 number + count - 1)); | ||||
|             fmt::format("invalid filesystem: sector {} is out of bounds ({} maximum)", | ||||
|                 number + count - 1, _locations.size())); | ||||
|  | ||||
|     Bytes data; | ||||
|     ByteWriter bw(data); | ||||
|   | ||||
| @@ -101,6 +101,14 @@ public: | ||||
|     DiskFullException(const std::string& msg): CannotWriteException(msg) {} | ||||
| }; | ||||
|  | ||||
| class ReadErrorException : public FilesystemException | ||||
| { | ||||
| public: | ||||
|     ReadErrorException(): FilesystemException("Fatal read error") {} | ||||
|  | ||||
|     ReadErrorException(const std::string& msg): FilesystemException(msg) {} | ||||
| }; | ||||
|  | ||||
| class ReadOnlyFilesystemException : public FilesystemException | ||||
| { | ||||
| public: | ||||
| @@ -135,6 +143,7 @@ public: | ||||
|     static constexpr const char* LENGTH = "length"; | ||||
|     static constexpr const char* MODE = "mode"; | ||||
|     static constexpr const char* FILE_TYPE = "file_type"; | ||||
|     static constexpr const char* CTIME = "ctime"; | ||||
|  | ||||
|     static constexpr const char* VOLUME_NAME = "volume_name"; | ||||
|     static constexpr const char* TOTAL_BLOCKS = "total_blocks"; | ||||
| @@ -256,6 +265,12 @@ public: | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createLifFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createMicrodosFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createZDosFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|     static std::unique_ptr<Filesystem> createRolandFsFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|  | ||||
|     static std::unique_ptr<Filesystem> createFilesystem( | ||||
|         const FilesystemProto& config, std::shared_ptr<SectorInterface> image); | ||||
|   | ||||
| @@ -88,7 +88,32 @@ message LifProto | ||||
|         [ default = 256, (help) = "LIF filesystem block size" ]; | ||||
| } | ||||
|  | ||||
| // NEXT_TAG: 15 | ||||
| message MicrodosProto {} | ||||
|  | ||||
| // NEXT_TAG: 16 | ||||
| message ZDosProto | ||||
| { | ||||
|     message Location | ||||
|     { | ||||
|         optional uint32 track = 1 [ (help) = "track number" ]; | ||||
|         optional uint32 sector = 3 [ (help) = "sector ID" ]; | ||||
|     } | ||||
|  | ||||
|     optional Location filesystem_start = 1 | ||||
|         [ (help) = "position of the filesystem superblock" ]; | ||||
| } | ||||
|  | ||||
| message RolandFsProto | ||||
| { | ||||
|     optional uint32 directory_track = 1 | ||||
|         [ (help) = "position of the directory", default = 39 ]; | ||||
|     optional uint32 block_size = 2 | ||||
|         [ (help) = "filesystem block size", default = 3072 ]; | ||||
|     optional uint32 directory_entries = 3 | ||||
|         [ (help) = "number of directory entries", default = 79 ]; | ||||
| } | ||||
|  | ||||
| // NEXT_TAG: 18 | ||||
| message FilesystemProto | ||||
| { | ||||
|     enum FilesystemType | ||||
| @@ -106,6 +131,9 @@ message FilesystemProto | ||||
|         APPLEDOS = 10; | ||||
|         PHILE = 11; | ||||
|         LIF = 12; | ||||
|         MICRODOS = 13; | ||||
|         ZDOS = 14; | ||||
|         ROLAND = 15; | ||||
|     } | ||||
|  | ||||
|     optional FilesystemType type = 10 | ||||
| @@ -123,6 +151,9 @@ message FilesystemProto | ||||
|     optional Smaky6FsProto smaky6 = 11; | ||||
|     optional PhileProto phile = 13; | ||||
|     optional LifProto lif = 14; | ||||
|     optional MicrodosProto microdos = 15; | ||||
|     optional ZDosProto zdos = 16; | ||||
|     optional RolandFsProto roland = 17; | ||||
|  | ||||
|     optional SectorListProto sector_order = 9 | ||||
|         [ (help) = "specify the filesystem order of sectors" ]; | ||||
|   | ||||
							
								
								
									
										325
									
								
								lib/vfs/zdos.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								lib/vfs/zdos.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,325 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/vfs/vfs.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "lib/layout.h" | ||||
| #include <iomanip> | ||||
|  | ||||
| /* See | ||||
|  * https://oldcomputers.dyndns.org/public/pub/rechner/zilog/zds/manuals/z80-rio_os_userman.pdf, | ||||
|  * page 116. */ | ||||
|  | ||||
| enum | ||||
| { | ||||
|     ZDOS_TYPE_DATA = 0x10, | ||||
|     ZDOS_TYPE_ASCII = 0x20, | ||||
|     ZDOS_TYPE_DIRECTORY = 0x40, | ||||
|     ZDOS_TYPE_PROCEDURE = 0x80 | ||||
| }; | ||||
|  | ||||
| enum | ||||
| { | ||||
|     ZDOS_MODE_FORCE = 1 << 2, | ||||
|     ZDOS_MODE_RANDOM = 1 << 3, | ||||
|     ZDOS_MODE_SECRET = 1 << 4, | ||||
|     ZDOS_MODE_LOCKED = 1 << 5, | ||||
|     ZDOS_MODE_ERASEPROTECT = 1 << 6, | ||||
|     ZDOS_MODE_WRITEPROTECT = 1 << 7 | ||||
| }; | ||||
|  | ||||
| static const std::map<uint8_t, std::string> fileTypeMap = { | ||||
|     {0,                   "INVALID"  }, | ||||
|     {ZDOS_TYPE_DATA,      "DATA"     }, | ||||
|     {ZDOS_TYPE_ASCII,     "ASCII"    }, | ||||
|     {ZDOS_TYPE_DIRECTORY, "DIRECTORY"}, | ||||
|     {ZDOS_TYPE_PROCEDURE, "PROCEDURE"} | ||||
| }; | ||||
|  | ||||
| static std::string convertTime(std::string zdosTime) | ||||
| { | ||||
|     /* Due to a bug in std::get_time, we can't parse the string directly --- a | ||||
|      * pattern of %y%m%d causes the first four digits of the string to become | ||||
|      * the year. So we need to reform the string. */ | ||||
|  | ||||
|     zdosTime = fmt::format("{}-{}-{}", | ||||
|         zdosTime.substr(0, 2), | ||||
|         zdosTime.substr(2, 2), | ||||
|         zdosTime.substr(4, 2)); | ||||
|  | ||||
|     std::tm tm = {}; | ||||
|     std::stringstream(zdosTime) >> std::get_time(&tm, "%y-%m-%d"); | ||||
|  | ||||
|     std::stringstream ss; | ||||
|     ss << std::put_time(&tm, "%FT%T%z"); | ||||
|     return ss.str(); | ||||
| } | ||||
|  | ||||
| class ZDosFilesystem : public Filesystem | ||||
| { | ||||
|     class ZDosDescriptor | ||||
|     { | ||||
|     public: | ||||
|         ZDosDescriptor(ZDosFilesystem* zfs, int block): zfs(zfs) | ||||
|         { | ||||
|             Bytes bytes = zfs->getLogicalSector(block); | ||||
|             ByteReader br(bytes); | ||||
|             br.seek(8); | ||||
|             firstRecord = zfs->readBlockNumber(br); | ||||
|             br.seek(12); | ||||
|             type = br.read_8(); | ||||
|             recordCount = br.read_le16(); | ||||
|             recordSize = br.read_le16(); | ||||
|             br.seek(19); | ||||
|             properties = br.read_8(); | ||||
|             startAddress = br.read_le16(); | ||||
|             lastRecordSize = br.read_le16(); | ||||
|             br.seek(24); | ||||
|             ctime = br.read(8); | ||||
|             mtime = br.read(8); | ||||
|  | ||||
|             rewind(); | ||||
|         } | ||||
|  | ||||
|         void rewind() | ||||
|         { | ||||
|             currentRecord = firstRecord; | ||||
|             eof = false; | ||||
|         } | ||||
|  | ||||
|         Bytes readRecord() | ||||
|         { | ||||
|             assert(!eof); | ||||
|             int count = recordSize / 0x80; | ||||
|  | ||||
|             Bytes result; | ||||
|             ByteWriter bw(result); | ||||
|  | ||||
|             while (count--) | ||||
|             { | ||||
|                 Bytes sector = zfs->getLogicalSector(currentRecord); | ||||
|                 ByteReader br(sector); | ||||
|  | ||||
|                 bw += br.read(0x80); | ||||
|                 br.skip(2); | ||||
|                 int sectorId = br.read_8(); | ||||
|                 int track = br.read_8(); | ||||
|                 currentRecord = zfs->toBlockNumber(sectorId, track); | ||||
|                 if (sectorId == 0xff) | ||||
|                     eof = true; | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         ZDosFilesystem* zfs; | ||||
|         uint16_t firstRecord; | ||||
|         uint8_t type; | ||||
|         uint16_t recordCount; | ||||
|         uint16_t recordSize; | ||||
|         uint8_t properties; | ||||
|         uint16_t startAddress; | ||||
|         uint16_t lastRecordSize; | ||||
|         std::string ctime; | ||||
|         std::string mtime; | ||||
|  | ||||
|         uint16_t currentRecord; | ||||
|         bool eof; | ||||
|     }; | ||||
|  | ||||
|     class ZDosDirent : public Dirent | ||||
|     { | ||||
|     public: | ||||
|         ZDosDirent(ZDosFilesystem* zfs, | ||||
|             const std::string& filename, | ||||
|             int descriptorBlock): | ||||
|             zfs(zfs), | ||||
|             descriptorBlock(descriptorBlock), | ||||
|             zd(zfs, descriptorBlock) | ||||
|         { | ||||
|             file_type = TYPE_FILE; | ||||
|             this->filename = filename; | ||||
|  | ||||
|             length = (zd.recordCount - 1) * zd.recordSize + zd.lastRecordSize; | ||||
|  | ||||
|             mode = ""; | ||||
|             if (zd.properties & ZDOS_MODE_FORCE) | ||||
|                 mode += 'F'; | ||||
|             if (zd.properties & ZDOS_MODE_RANDOM) | ||||
|                 mode += 'R'; | ||||
|             if (zd.properties & ZDOS_MODE_SECRET) | ||||
|                 mode += 'S'; | ||||
|             if (zd.properties & ZDOS_MODE_LOCKED) | ||||
|                 mode += 'L'; | ||||
|             if (zd.properties & ZDOS_MODE_ERASEPROTECT) | ||||
|                 mode += 'E'; | ||||
|             if (zd.properties & ZDOS_MODE_WRITEPROTECT) | ||||
|                 mode += 'W'; | ||||
|  | ||||
|             path = {filename}; | ||||
|  | ||||
|             attributes[Filesystem::FILENAME] = filename; | ||||
|             attributes[Filesystem::LENGTH] = std::to_string(length); | ||||
|             attributes[Filesystem::FILE_TYPE] = "file"; | ||||
|             attributes[Filesystem::MODE] = mode; | ||||
|             attributes["zdos.descriptor_record"] = | ||||
|                 std::to_string(descriptorBlock); | ||||
|             attributes["zdos.first_record"] = std::to_string(zd.firstRecord); | ||||
|             attributes["zdos.record_size"] = std::to_string(zd.recordSize); | ||||
|             attributes["zdos.record_count"] = std::to_string(zd.recordCount); | ||||
|             attributes["zdos.last_record_size"] = | ||||
|                 std::to_string(zd.lastRecordSize); | ||||
|             attributes["zdos.start_address"] = | ||||
|                 fmt::format("0x{:04x}", zd.startAddress); | ||||
|             attributes["zdos.type"] = fileTypeMap.at(zd.type & 0xf0); | ||||
|             attributes["zdos.ctime"] = convertTime(zd.ctime); | ||||
|             attributes["zdos.mtime"] = convertTime(zd.mtime); | ||||
|         } | ||||
|  | ||||
|     public: | ||||
|         ZDosFilesystem* zfs; | ||||
|         int descriptorBlock; | ||||
|         ZDosDescriptor zd; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     ZDosFilesystem( | ||||
|         const ZDosProto& config, std::shared_ptr<SectorInterface> sectors): | ||||
|         Filesystem(sectors), | ||||
|         _config(config) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     uint32_t capabilities() const | ||||
|     { | ||||
|         return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT; | ||||
|     } | ||||
|  | ||||
|     FilesystemStatus check() override | ||||
|     { | ||||
|         return FS_OK; | ||||
|     } | ||||
|  | ||||
|     std::map<std::string, std::string> getMetadata() override | ||||
|     { | ||||
|         mount(); | ||||
|  | ||||
|         std::map<std::string, std::string> attributes; | ||||
|  | ||||
|         attributes[VOLUME_NAME] = ""; | ||||
|         attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks); | ||||
|         attributes[USED_BLOCKS] = std::to_string(_usedBlocks); | ||||
|         attributes[BLOCK_SIZE] = "128"; | ||||
|         return attributes; | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<Dirent> getDirent(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         return findFile(path.front()); | ||||
|     } | ||||
|  | ||||
|     std::vector<std::shared_ptr<Dirent>> list(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (!path.empty()) | ||||
|             throw FileNotFoundException(); | ||||
|  | ||||
|         std::vector<std::shared_ptr<Dirent>> result; | ||||
|         for (auto& de : _dirents) | ||||
|             result.push_back(de); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     Bytes getFile(const Path& path) override | ||||
|     { | ||||
|         mount(); | ||||
|         if (path.size() != 1) | ||||
|             throw BadPathException(); | ||||
|  | ||||
|         auto dirent = findFile(path.front()); | ||||
|         dirent->zd.rewind(); | ||||
|  | ||||
|         Bytes bytes; | ||||
|         ByteWriter bw(bytes); | ||||
|         while (!dirent->zd.eof) | ||||
|         { | ||||
|             bw += dirent->zd.readRecord(); | ||||
|         } | ||||
|  | ||||
|         return bytes.slice(0, dirent->length); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void mount() | ||||
|     { | ||||
|         _sectorsPerTrack = Layout::getLayoutOfTrack(0, 0)->numSectors; | ||||
|  | ||||
|         int rootBlock = toBlockNumber(_config.filesystem_start().sector(), | ||||
|             _config.filesystem_start().track()); | ||||
|         ZDosDescriptor zd(this, rootBlock); | ||||
|         if (zd.type != ZDOS_TYPE_DIRECTORY) | ||||
|             throw BadFilesystemException(); | ||||
|  | ||||
|         _totalBlocks = getLogicalSectorCount(); | ||||
|         _usedBlocks = (zd.recordCount * zd.recordSize) / 0x80 + 1; | ||||
|         while (!zd.eof) | ||||
|         { | ||||
|             Bytes bytes = zd.readRecord(); | ||||
|             ByteReader br(bytes); | ||||
|             for (;;) | ||||
|             { | ||||
|                 int len = br.read_8(); | ||||
|                 if (len == 0xff) | ||||
|                     break; | ||||
|  | ||||
|                 std::string filename = br.read(len & 0x7f); | ||||
|                 int descriptorBlock = readBlockNumber(br); | ||||
|  | ||||
|                 auto dirent = std::make_unique<ZDosDirent>( | ||||
|                     this, filename, descriptorBlock); | ||||
|                 _usedBlocks += | ||||
|                     (dirent->zd.recordCount * dirent->zd.recordSize) / 0x80 + 1; | ||||
|                 _dirents.push_back(std::move(dirent)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::shared_ptr<ZDosDirent> findFile(const std::string filename) | ||||
|     { | ||||
|         for (const auto& dirent : _dirents) | ||||
|         { | ||||
|             if (dirent->filename == filename) | ||||
|                 return dirent; | ||||
|         } | ||||
|  | ||||
|         throw FileNotFoundException(); | ||||
|     } | ||||
|  | ||||
|     int toBlockNumber(int sectorId, int track) | ||||
|     { | ||||
|         return track * _sectorsPerTrack + sectorId; | ||||
|     } | ||||
|  | ||||
|     int readBlockNumber(ByteReader& br) | ||||
|     { | ||||
|         int sectorId = br.read_8(); | ||||
|         int track = br.read_8(); | ||||
|         return toBlockNumber(sectorId, track); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const ZDosProto& _config; | ||||
|     unsigned _sectorsPerTrack; | ||||
|     unsigned _totalBlocks; | ||||
|     unsigned _usedBlocks; | ||||
|     std::vector<std::shared_ptr<ZDosDirent>> _dirents; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Filesystem> Filesystem::createZDosFilesystem( | ||||
|     const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors) | ||||
| { | ||||
|     return std::make_unique<ZDosFilesystem>(config.zdos(), sectors); | ||||
| } | ||||
| @@ -14,9 +14,12 @@ static std::string supportStatus(SupportStatus status) | ||||
|  | ||||
|         case SupportStatus::UNICORN: | ||||
|             return "🦄"; | ||||
|  | ||||
|         case SupportStatus::UNSUPPORTED: | ||||
|             return ""; | ||||
|     } | ||||
|  | ||||
|     return ""; | ||||
| 	return ""; | ||||
| } | ||||
|  | ||||
| int main(int argc, const char* argv[]) | ||||
|   | ||||
| @@ -247,10 +247,10 @@ static void draw_x_graticules(Agg2D& painter, | ||||
| int mainAnalyseDriveResponse(int argc, const char* argv[]) | ||||
| { | ||||
|     globalConfig().overrides()->mutable_flux_source()->set_type( | ||||
|         FluxSourceProto::DRIVE); | ||||
|         FLUXTYPE_DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
|     if (globalConfig()->flux_sink().type() != FluxSinkProto::DRIVE) | ||||
|     if (globalConfig()->flux_sink().type() != FLUXTYPE_DRIVE) | ||||
|         error("this only makes sense with a real disk drive"); | ||||
|  | ||||
|     usbSetDrive(globalConfig()->drive().drive(), | ||||
|   | ||||
| @@ -132,7 +132,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap) | ||||
| int mainInspect(int argc, const char* argv[]) | ||||
| { | ||||
|     globalConfig().overrides()->mutable_flux_source()->set_type( | ||||
|         FluxSourceProto::DRIVE); | ||||
|         FLUXTYPE_DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
|     auto& fluxSource = globalConfig().getFluxSource(); | ||||
|   | ||||
| @@ -52,12 +52,13 @@ int mainLs(int argc, const char* argv[]) | ||||
|         uint32_t total = 0; | ||||
|         for (const auto& dirent : files) | ||||
|         { | ||||
|             fmt::print("{} {:{}}  {:6} {}\n", | ||||
|             fmt::print("{} {:{}}  {:6} {:4} {}\n", | ||||
|                 fileTypeChar(dirent->file_type), | ||||
|                 quote(dirent->filename), | ||||
|                 maxlen + 2, | ||||
|                 dirent->length, | ||||
|                 dirent->mode); | ||||
|         				dirent->mode, | ||||
|         				dirent->attributes[Filesystem::CTIME]); | ||||
|             total += dirent->length; | ||||
|         } | ||||
|         fmt::print("({} files, {} bytes)\n", files.size(), total); | ||||
|   | ||||
| @@ -57,10 +57,10 @@ int mainRawRead(int argc, const char* argv[]) | ||||
|     if (argc == 1) | ||||
|         showProfiles("rawread", formats); | ||||
|     globalConfig().overrides()->mutable_flux_source()->set_type( | ||||
|         FluxSourceProto::DRIVE); | ||||
|         FLUXTYPE_DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
|     if (globalConfig()->flux_sink().type() == FluxSinkProto::DRIVE) | ||||
|     if (globalConfig()->flux_sink().type() == FLUXTYPE_DRIVE) | ||||
|         error("you can't use rawread to write to hardware"); | ||||
|  | ||||
|     std::shared_ptr<FluxSource> fluxSource = globalConfig().getFluxSource(); | ||||
|   | ||||
| @@ -49,7 +49,7 @@ static ActionFlag eraseFlag({"--erase"}, | ||||
|     []() | ||||
|     { | ||||
|         globalConfig().overrides()->mutable_flux_source()->set_type( | ||||
|             FluxSourceProto::ERASE); | ||||
|             FLUXTYPE_ERASE); | ||||
|     }); | ||||
|  | ||||
| int mainRawWrite(int argc, const char* argv[]) | ||||
| @@ -60,10 +60,10 @@ int mainRawWrite(int argc, const char* argv[]) | ||||
|     if (argc == 1) | ||||
|         showProfiles("rawwrite", formats); | ||||
|     globalConfig().overrides()->mutable_flux_sink()->set_type( | ||||
|         FluxSinkProto::DRIVE); | ||||
|         FLUXTYPE_DRIVE); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
|     if (globalConfig()->flux_source().type() == FluxSourceProto::DRIVE) | ||||
|     if (globalConfig()->flux_source().type() == FLUXTYPE_DRIVE) | ||||
|         error("you can't use rawwrite to read from hardware"); | ||||
|  | ||||
|     auto& fluxSource = globalConfig().getFluxSource(); | ||||
|   | ||||
| @@ -61,10 +61,10 @@ int mainRead(int argc, const char* argv[]) | ||||
| { | ||||
|     if (argc == 1) | ||||
|         showProfiles("read", formats); | ||||
|     globalConfig().set("flux_source.type", "DRIVE"); | ||||
|     globalConfig().set("flux_source.type", "FLUXTYPE_DRIVE"); | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, formats); | ||||
|  | ||||
|     if (globalConfig()->decoder().copy_flux_to().type() == FluxSinkProto::DRIVE) | ||||
|     if (globalConfig()->decoder().copy_flux_to().type() == FLUXTYPE_DRIVE) | ||||
|         error("you cannot copy flux to a hardware device"); | ||||
|  | ||||
|     auto& fluxSource = globalConfig().getFluxSource(); | ||||
|   | ||||
| @@ -19,7 +19,7 @@ int mainRpm(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
|     if (globalConfig()->flux_source().type() != FluxSourceProto::DRIVE) | ||||
|     if (globalConfig()->flux_source().type() != FLUXTYPE_DRIVE) | ||||
|         error("this only makes sense with a real disk drive"); | ||||
|  | ||||
|     usbSetDrive(globalConfig()->drive().drive(), | ||||
|   | ||||
| @@ -23,7 +23,7 @@ int mainSeek(int argc, const char* argv[]) | ||||
| { | ||||
|     flags.parseFlagsWithConfigFiles(argc, argv, {}); | ||||
|  | ||||
|     if (globalConfig()->flux_source().type() != FluxSourceProto::DRIVE) | ||||
|     if (globalConfig()->flux_source().type() != FLUXTYPE_DRIVE) | ||||
|         error("this only makes sense with a real disk drive"); | ||||
|  | ||||
|     usbSetDrive(globalConfig()->drive().drive(), | ||||
|   | ||||
| @@ -14,6 +14,18 @@ | ||||
| #include <google/protobuf/text_format.h> | ||||
| #include <fstream> | ||||
|  | ||||
| static void ignoreInapplicableValueExceptions(std::function<void(void)> cb) | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         cb(); | ||||
|     } | ||||
|     catch (const InapplicableValueException* e) | ||||
|     { | ||||
|         /* swallow */ | ||||
|     } | ||||
| } | ||||
|  | ||||
| FlagGroup fileFlags; | ||||
|  | ||||
| static StringFlag image({"-i", "--image"}, | ||||
| @@ -21,8 +33,16 @@ static StringFlag image({"-i", "--image"}, | ||||
|     "", | ||||
|     [](const auto& value) | ||||
|     { | ||||
|         globalConfig().setImageReader(value); | ||||
|         globalConfig().setImageWriter(value); | ||||
|         ignoreInapplicableValueExceptions( | ||||
|             [&]() | ||||
|             { | ||||
|                 globalConfig().setImageReader(value); | ||||
|             }); | ||||
|         ignoreInapplicableValueExceptions( | ||||
|             [&]() | ||||
|             { | ||||
|                 globalConfig().setImageWriter(value); | ||||
|             }); | ||||
|     }); | ||||
|  | ||||
| static StringFlag flux({"-f", "--flux"}, | ||||
| @@ -30,6 +50,14 @@ static StringFlag flux({"-f", "--flux"}, | ||||
|     "", | ||||
|     [](const auto& value) | ||||
|     { | ||||
|         globalConfig().setFluxSource(value); | ||||
|         globalConfig().setFluxSink(value); | ||||
|         ignoreInapplicableValueExceptions( | ||||
|             [&]() | ||||
|             { | ||||
|                 globalConfig().setFluxSink(value); | ||||
|             }); | ||||
|         ignoreInapplicableValueExceptions( | ||||
|             [&]() | ||||
|             { | ||||
|                 globalConfig().setFluxSource(value); | ||||
|             }); | ||||
|     }); | ||||
|   | ||||
| @@ -22,7 +22,7 @@ they might require nudging as the side order can't be reliably autodetected. | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "acornadfs.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -25,12 +25,12 @@ documentation: | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "acorndfs.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "acorndfs.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -43,7 +43,7 @@ documentation: | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "aeslanier.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -28,7 +28,7 @@ documentation: | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "agat.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -31,12 +31,12 @@ documentation: | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "amiga.adf" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "amiga.adf" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -44,7 +44,7 @@ documentation: | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ampro.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -19,12 +19,12 @@ on what was available at the time, with the same format on both. | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "bk800.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "bk800.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -116,12 +116,12 @@ file system supports this. | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "brother.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "brother.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| encoder { | ||||
|   | ||||
| @@ -21,6 +21,7 @@ FORMATS = \ | ||||
| 	icl30 \ | ||||
| 	mac \ | ||||
| 	micropolis \ | ||||
| 	ms2000 \ | ||||
| 	mx \ | ||||
| 	n88basic \ | ||||
| 	northstar \ | ||||
|   | ||||
| @@ -54,12 +54,12 @@ documentation: | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "commodore.d64" | ||||
| 	type: D64 | ||||
| 	type: IMAGETYPE_D64 | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "commodore.d64" | ||||
| 	type: D64 | ||||
| 	type: IMAGETYPE_D64 | ||||
| } | ||||
|  | ||||
| filesystem { | ||||
|   | ||||
| @@ -37,7 +37,7 @@ documentation: | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "eco1.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -11,7 +11,7 @@ format itself is yet another IBM scheme variant. | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "epsonpf10.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
| @@ -42,7 +42,7 @@ There's amazingly little information about these things. | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "f85.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -43,7 +43,7 @@ documentation: | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "fb100.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -14,18 +14,26 @@ Floppy-disk wise, they're yet more variations of the standard IBM floppy | ||||
| encoding scheme. | ||||
| >>> | ||||
|  | ||||
| documentation: | ||||
| <<< | ||||
| ## References | ||||
|  | ||||
|   * [A summary of the Hewlett Packard floppy disk | ||||
|     formats](http://www.bitsavers.org/pdf/hp/disc/912x/HP_Flexible_Disk_Formats.pdf) | ||||
| >>> | ||||
|  | ||||
| drive { | ||||
| 	high_density: false | ||||
| } | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "hplif.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "hplif.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| @@ -55,10 +63,6 @@ option_group { | ||||
| 				layoutdata { | ||||
| 					sector_size: 256 | ||||
| 					physical { | ||||
| 						sector: 0 | ||||
| 						sector: 4 | ||||
| 						sector: 8 | ||||
| 						sector: 12 | ||||
| 						sector: 1 | ||||
| 						sector: 5 | ||||
| 						sector: 9 | ||||
| @@ -71,6 +75,56 @@ option_group { | ||||
| 						sector: 7 | ||||
| 						sector: 11 | ||||
| 						sector: 15 | ||||
| 						sector: 4 | ||||
| 						sector: 8 | ||||
| 						sector: 12 | ||||
| 						sector: 16 | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			encoder { | ||||
| 				ibm { | ||||
| 					trackdata { | ||||
| 						emit_iam: false | ||||
| 						target_rotational_period_ms: 200 | ||||
| 						target_clock_period_us: 4 | ||||
| 						gap0: 80 | ||||
| 						gap2: 22 | ||||
| 						gap3: 44 | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	option { | ||||
| 		name: "608" | ||||
| 		comment: '608kB 3.5" 76-track DSDD; HP9122 format' | ||||
|  | ||||
| 		config { | ||||
| 			layout { | ||||
| 				tracks: 76 | ||||
| 				sides: 2 | ||||
| 				layoutdata { | ||||
| 					sector_size: 256 | ||||
| 					physical { | ||||
| 						sector: 1 | ||||
| 						sector: 5 | ||||
| 						sector: 9 | ||||
| 						sector: 13 | ||||
| 						sector: 2 | ||||
| 						sector: 6 | ||||
| 						sector: 10 | ||||
| 						sector: 14 | ||||
| 						sector: 3 | ||||
| 						sector: 7 | ||||
| 						sector: 11 | ||||
| 						sector: 15 | ||||
| 						sector: 4 | ||||
| 						sector: 8 | ||||
| 						sector: 12 | ||||
| 						sector: 16 | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|   | ||||
| @@ -84,12 +84,12 @@ versa, so it shouldn't matter. | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "ibm.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "ibm.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| decoder { | ||||
|   | ||||
| @@ -11,7 +11,7 @@ track! Other than that it's another IBM scheme variation. | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "icl30.img" | ||||
| 	type: IMG | ||||
| 	type: IMAGETYPE_IMG | ||||
| } | ||||
|  | ||||
| layout { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user