mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Merge from master.
This commit is contained in:
		
							
								
								
									
										7
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ jobs: | ||||
|       with: | ||||
|         fetch-depth: 1 | ||||
|     - name: apt | ||||
|       run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev ninja-build protobuf-compiler | ||||
|       run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev ninja-build protobuf-compiler libwxgtk3.0-gtk3-dev | ||||
|     - name: make | ||||
|       run: make | ||||
|  | ||||
| @@ -21,7 +21,7 @@ jobs: | ||||
|       with: | ||||
|         fetch-depth: 1 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb ninja protobuf truncate | ||||
|       run: brew install sqlite pkg-config libusb ninja protobuf truncate wxwidgets | ||||
|     - name: make | ||||
|       run: make | ||||
|  | ||||
| @@ -46,6 +46,7 @@ jobs: | ||||
|           mingw-w64-i686-protobuf | ||||
|           vim | ||||
|           diffutils | ||||
|           mingw-w64-i686-wxWidgets | ||||
|     - uses: actions/checkout@v1 | ||||
|       with: | ||||
|         fetch-depth: 1 | ||||
| @@ -55,7 +56,7 @@ jobs: | ||||
|  | ||||
|     - name: zip | ||||
|       run: | | ||||
|         zip -9 fluxengine.zip fluxengine.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|         zip -9 fluxengine.zip fluxengine.exe fluxengine-debug.exe fluxengine-gui.exe fluxengine-gui-debug.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|   | ||||
							
								
								
									
										3
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -27,6 +27,7 @@ jobs: | ||||
|           mingw-w64-i686-protobuf | ||||
|           vim | ||||
|           diffutils | ||||
|           mingw-w64-i686-wxWidgets | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         fetch-depth: 1 | ||||
| @@ -35,7 +36,7 @@ jobs: | ||||
|         make | ||||
|     - name: zip | ||||
|       run: | | ||||
|         zip -9 fluxengine.zip fluxengine.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|         zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|     - name: date | ||||
|       run: | | ||||
|         echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV} | ||||
|   | ||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,9 @@ | ||||
| .obj | ||||
| .project | ||||
| /.ninja* | ||||
| /brother120tool | ||||
| /brother240tool | ||||
| /fluxengine | ||||
| /brother120tool-* | ||||
| /brother240tool-* | ||||
| /fluxengine-* | ||||
|   | ||||
							
								
								
									
										16
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Makefile
									
									
									
									
									
								
							| @@ -29,9 +29,12 @@ export CXX = /mingw32/bin/g++ | ||||
| export AR = /mingw32/bin/ar rc | ||||
| export RANLIB = /mingw32/bin/ranlib | ||||
| export STRIP = /mingw32/bin/strip | ||||
| export CFLAGS += -I/mingw32/include/libusb-1.0 -I/mingw32/include | ||||
| export LDFLAGS += | ||||
| export LIBS += -L/mingw32/lib -static -lz -lsqlite3 \ | ||||
| export CFLAGS += -I/mingw32/include | ||||
| export CXXFLAGS += $(shell wx-config --cxxflags --static=yes) | ||||
| export GUILDFLAGS += -lmingw32 | ||||
| export LIBS += -L/mingw32/lib -static -lsqlite3 \ | ||||
| 	$(shell wx-config --libs --static=yes core base) -lz \ | ||||
| 	-lcomctl32 -loleaut32 -lspoolss -loleacc -lwinspool \ | ||||
| 	-lsetupapi -lwinusb -lole32 -lprotobuf -luuid | ||||
| export EXTENSION = .exe | ||||
| else | ||||
| @@ -41,6 +44,10 @@ ifneq ($(packages-exist),yes) | ||||
| $(warning These pkg-config packages are installed: $(shell pkg-config --list-all | sort | awk '{print $$1}')) | ||||
| $(error You must have these pkg-config packages installed: $(PACKAGES)) | ||||
| endif | ||||
| wx-exist = $(shell wx-config --cflags > /dev/null && echo yes) | ||||
| ifneq ($(wx-exist),yes) | ||||
| $(error You must have these wx-config installed) | ||||
| endif | ||||
|  | ||||
| export PROTOC = protoc | ||||
| export CC = gcc | ||||
| @@ -48,9 +55,10 @@ export CXX = g++ | ||||
| export AR = ar rc | ||||
| export RANLIB = ranlib | ||||
| export STRIP = strip | ||||
| export CFLAGS += $(shell pkg-config --cflags $(PACKAGES)) | ||||
| export CFLAGS += $(shell pkg-config --cflags $(PACKAGES)) $(shell wx-config --cxxflags) | ||||
| export LDFLAGS += | ||||
| export LIBS += $(shell pkg-config --libs $(PACKAGES)) | ||||
| export GUILIBS += $(shell wx-config --libs core base) | ||||
| export EXTENSION = | ||||
|  | ||||
| ifeq ($(shell uname),Darwin) | ||||
|   | ||||
| @@ -97,10 +97,10 @@ people who've had it work). | ||||
| | [Acorn ADFS](doc/disk-acornadfs.md)       |  🦄   |   🦖*  | single- and double- sided           | | ||||
| | [Acorn DFS](doc/disk-acorndfs.md)         |  🦄   |   🦖*  |                                     | | ||||
| | [Ampro Little Board](doc/disk-ampro.md)   |  🦖   |   🦖*  |                                     | | ||||
| | [Apple II DOS 3.3](doc/disk-apple2.md)    |  🦄   |        | doesn't do logical sector remapping | | ||||
| | [Apple II DOS 3.3](doc/disk-apple2.md)    |  🦄   |   🦖   | doesn't do logical sector remapping | | ||||
| | [Amiga](doc/disk-amiga.md)                |  🦄   |   🦄   |                                     | | ||||
| | [Commodore 64 1541/1581](doc/disk-c64.md) |  🦄   |   🦄   | and probably the other formats      | | ||||
| | [Brother 120kB](doc/disk-brother.md)      |  🦄   |   🦖   |                                     | | ||||
| | [Brother 120kB](doc/disk-brother.md)      |  🦄   |   🦄   |                                     | | ||||
| | [Brother 240kB](doc/disk-brother.md)      |  🦄   |   🦄   |                                     | | ||||
| | [Brother FB-100](doc/disk-fb100.md)       |  🦖   |        | Tandy Model 100, Husky Hunter, knitting machines | | ||||
| | [Macintosh 400kB/800kB](doc/disk-macintosh.md)  |  🦄   |   🦄   |                                     | | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| #ifndef APPLE2_H | ||||
| #define APPLE2_H | ||||
|  | ||||
| #include <memory.h> | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define APPLE2_SECTOR_RECORD   0xd5aa96 | ||||
| #define APPLE2_DATA_RECORD     0xd5aaad | ||||
|  | ||||
| @@ -8,6 +12,7 @@ | ||||
| #define APPLE2_ENCODED_SECTOR_LENGTH 342 | ||||
|  | ||||
| extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -2,3 +2,4 @@ syntax = "proto2"; | ||||
|  | ||||
| message Apple2DecoderProto {} | ||||
|  | ||||
| message Apple2EncoderProto {} | ||||
|   | ||||
| @@ -90,6 +90,9 @@ public: | ||||
| 		_sector->logicalTrack = combine(br.read_be16()); | ||||
| 		_sector->logicalSector = combine(br.read_be16()); | ||||
| 		uint8_t checksum = combine(br.read_be16()); | ||||
|  | ||||
| 		// If the checksum is correct, upgrade the sector from MISSING | ||||
| 		// to DATA_MISSING in anticipation of its data record | ||||
| 		if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
| @@ -101,11 +104,42 @@ public: | ||||
| 		if (readRaw24() != APPLE2_DATA_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and | ||||
| 		// the data itself.  This has been seen on real world disks | ||||
| 		// such as the Apple II Operating System Kit from Apple2Online. | ||||
| 		// However, I haven't seen it described in any of the various | ||||
| 		// references. | ||||
| 		// | ||||
| 		// This extra '0' bit would not affect the real disk interface, | ||||
| 		// as it was a '1' reaching the top bit of a shift register | ||||
| 		// that triggered a byte to be available, but it affects the | ||||
| 		// way the data is read here. | ||||
| 		// | ||||
| 		// While the floppies tested only seemed to need this applied | ||||
| 		// to the first byte of the data record, applying it | ||||
| 		// consistently to all of them doesn't seem to hurt, and | ||||
| 		// simplifies the code. | ||||
|  | ||||
| 		/* Read and decode data. */ | ||||
|  | ||||
| 		unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2; | ||||
| 		Bytes bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength); | ||||
| 		auto readApple8 = [&]() { | ||||
| 		    auto result = readRaw8(); | ||||
| 		    while(result & 0x80 == 0) { | ||||
| 			auto b = readRawBits(1); | ||||
| 			result = (result << 1) | b[0]; | ||||
| 		    } | ||||
| 		    return result; | ||||
| 		}; | ||||
|  | ||||
| 		constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2; | ||||
|                 uint8_t bytes[recordLength]; | ||||
|                 for(auto &byte : bytes) { | ||||
|                     byte = readApple8(); | ||||
|                 } | ||||
|  | ||||
| 		// Upgrade the sector from MISSING to BAD_CHECKSUM. | ||||
| 		// If decode_crazy_data succeeds, it upgrades the sector to | ||||
| 		// OK. | ||||
| 		_sector->status = Sector::BAD_CHECKSUM; | ||||
| 		_sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										187
									
								
								arch/apple2/encoder.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								arch/apple2/encoder.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| #include "globals.h" | ||||
| #include "arch/apple2/apple2.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "sector.h" | ||||
| #include "writer.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include <ctype.h> | ||||
| #include "bytes.h" | ||||
|  | ||||
| static int encode_data_gcr(uint8_t data) { | ||||
|     switch(data) | ||||
|     { | ||||
| 	#define GCR_ENTRY(gcr, data) \ | ||||
| 	case data: return gcr; | ||||
| 	#include "data_gcr.h" | ||||
| 	#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| class Apple2Encoder : public AbstractEncoder | ||||
| { | ||||
| public: | ||||
|     Apple2Encoder(const EncoderProto& config): | ||||
| 	AbstractEncoder(config) | ||||
|     {} | ||||
|  | ||||
| public: | ||||
|     std::vector<std::shared_ptr<const Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override | ||||
|     { | ||||
| 	std::vector<std::shared_ptr<const Sector>> sectors; | ||||
| 	constexpr auto numSectors = 16; | ||||
| 	if (physicalSide == 0) | ||||
| 	{ | ||||
| 	    int logicalTrack = physicalTrack / 2; | ||||
| 	    unsigned numSectors = 16; | ||||
| 	    for (int sectorId=0; sectorId<numSectors; sectorId++) | ||||
| 	    { | ||||
| 		const auto& sector = image.get(logicalTrack, 0, sectorId); | ||||
| 		if (sector) | ||||
| 		    sectors.push_back(sector); | ||||
| 	    } | ||||
| 	} | ||||
|  | ||||
| 	return sectors; | ||||
|     } | ||||
|  | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, | ||||
| 	    const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override | ||||
|     { | ||||
| 	if (physicalSide != 0) | ||||
| 	    return std::unique_ptr<Fluxmap>(); | ||||
|  | ||||
| 	int logicalTrack = physicalTrack / 2; | ||||
| 	double clockRateUs = 4.; | ||||
|  | ||||
| 	int bitsPerRevolution = 200000.0 / clockRateUs; | ||||
|  | ||||
| 	std::vector<bool> bits(bitsPerRevolution); | ||||
| 	unsigned cursor = 0; | ||||
|  | ||||
| 	for (const auto& sector : sectors) { | ||||
| 	    if(sector) { | ||||
| 		writeSector(bits, cursor, *sector); | ||||
| 	    } | ||||
| 	} | ||||
|  | ||||
| 	if (cursor >= bits.size()) | ||||
| 	    Error() << fmt::format("track data overrun by {} bits", cursor - bits.size()); | ||||
| 	fillBitmapTo(bits, cursor, bits.size(), { true, false }); | ||||
|  | ||||
| 	std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| 	fluxmap->appendBits(bits, clockRateUs*1e3); | ||||
| 	return fluxmap; | ||||
|  | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     uint8_t volume_id = 254; | ||||
|  | ||||
|     /* This is extremely inspired by the MESS implementation, written by Nathan Woods | ||||
|      * and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
|      * as well as Understanding the Apple II (1983) Chapter 9 | ||||
|      * https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater | ||||
|      */ | ||||
|  | ||||
|     void writeSector(std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const | ||||
|     { | ||||
| 	if ((sector.status == Sector::OK) or (sector.status == Sector::BAD_CHECKSUM)) | ||||
| 	{ | ||||
| 	    auto write_bit = [&](bool val) { | ||||
| 		if(cursor <= bits.size()) { bits[cursor] = val; } | ||||
| 		cursor++; | ||||
| 	    }; | ||||
|  | ||||
| 	    auto write_bits = [&](uint32_t bits, int width) { | ||||
| 		for(int i=width; i--;) { | ||||
| 		    write_bit(bits & (1u << i)); | ||||
| 		} | ||||
| 	    }; | ||||
|  | ||||
| 	    auto write_gcr44 = [&](uint8_t value) { | ||||
| 		write_bits((value << 7) | value | 0xaaaa, 16); | ||||
| 	    }; | ||||
|  | ||||
| 	    auto write_gcr6 = [&](uint8_t value) { | ||||
| 		write_bits(encode_data_gcr(value), 8); | ||||
| 	    }; | ||||
|  | ||||
| 	    auto write_ff40 = [&]() { | ||||
| 		write_bits(0xff0, 12); | ||||
| 	    }; | ||||
|  | ||||
| 	    auto write_ff36 = [&]() { | ||||
| 		write_bits(0xff << 2, 10); | ||||
| 	    }; | ||||
|  | ||||
| 	    auto write_ff32 = [&]() { | ||||
| 		write_bits(0xff, 8); | ||||
| 	    }; | ||||
|  | ||||
| 	    // There is data to encode to disk. | ||||
| 	    if ((sector.data.size() != APPLE2_SECTOR_LENGTH)) | ||||
| 		Error() << fmt::format("unsupported sector size {} --- you must pick 256", sector.data.size());     | ||||
|  | ||||
| 	    // Write address syncing leader : A sequence of "FF40"s followed by an "FF32", 5 to 40 of them | ||||
| 	    // "FF40" seems to indicate that the actual data written is "1111 1111 0000" i.e., 8 1s and a total of 40 microseconds | ||||
| 	    for(int i=0; i<4; i++) { write_ff40(); } | ||||
| 	    write_ff32(); | ||||
|  | ||||
| 	    // Write address field: APPLE2_SECTOR_RECORD + sector identifier + DE AA EB | ||||
| 	    write_bits(APPLE2_SECTOR_RECORD, 24); | ||||
| 	    write_gcr44(volume_id); | ||||
| 	    write_gcr44(sector.logicalTrack); | ||||
| 	    write_gcr44(sector.logicalSector); | ||||
| 	    write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector); | ||||
| 	    write_bits(0xDEAAEB, 24); | ||||
|  | ||||
| 	    // Write the "zip": a gap of 50 (we actually do 52, hopefully it's OK). | ||||
| 	    // In real HW this is actually turning OFF the write head for 50 cycles | ||||
| 	    write_bits(0, 13); | ||||
|  | ||||
| 	    // Write data syncing leader: FF40 x4 + FF36 + APPLE2_DATA_RECORD + sector data + sum + DE AA EB (+ mystery bits cut off of the scan?) | ||||
| 	    for(int i=0; i<4; i++) write_ff40(); | ||||
| 	    write_ff36(); | ||||
| 	    write_bits(APPLE2_DATA_RECORD, 24); | ||||
|  | ||||
| 	    // Convert the sector data to GCR, append the checksum, and write it out | ||||
| 	    constexpr auto TWOBIT_COUNT = 0x56; // Size of the 'twobit' area at the start of the GCR data | ||||
| 	    uint8_t checksum = 0; | ||||
| 	    for(int i=0; i<APPLE2_ENCODED_SECTOR_LENGTH; i++) { | ||||
| 		int value; | ||||
| 		if(i >= TWOBIT_COUNT) { | ||||
| 		    value = sector.data[i - TWOBIT_COUNT] >> 2; | ||||
| 		} else { | ||||
| 		    uint8_t tmp = sector.data[i]; | ||||
| 		    value = ((tmp & 1) << 1) | ((tmp & 2) >> 1); | ||||
|  | ||||
| 		    tmp = sector.data[i + TWOBIT_COUNT]; | ||||
| 		    value |= ((tmp & 1) << 3) | ((tmp & 2) << 1); | ||||
|  | ||||
| 		    if(i + 2*TWOBIT_COUNT < APPLE2_SECTOR_LENGTH) { | ||||
| 			tmp = sector.data[i + 2*TWOBIT_COUNT]; | ||||
| 			value |= ((tmp & 1) << 5) | ((tmp & 2) << 3); | ||||
| 		    } | ||||
| 		} | ||||
| 		checksum ^= value; | ||||
| 		// assert(checksum & ~0x3f == 0); | ||||
| 		write_gcr6(checksum); | ||||
| 		checksum = value; | ||||
| 	    } | ||||
| 	    if(sector.status == Sector::BAD_CHECKSUM) checksum ^= 0x3f; | ||||
| 	    write_gcr6(checksum); | ||||
| 	    write_bits(0xDEAAEB, 24); | ||||
| 	} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<AbstractEncoder>(new Apple2Encoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -113,7 +113,6 @@ public: | ||||
| 	{ | ||||
| 		std::vector<std::shared_ptr<const Sector>> sectors; | ||||
|  | ||||
| 		int logicalTrack; | ||||
| 		if (physicalSide != 0) | ||||
| 			return sectors; | ||||
| 		physicalTrack -= _config.bias(); | ||||
| @@ -123,19 +122,17 @@ public: | ||||
| 				if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2)) | ||||
| 						|| (physicalTrack & 1)) | ||||
| 					return sectors; | ||||
| 				logicalTrack = physicalTrack/2; | ||||
| 				break; | ||||
|  | ||||
| 			case BROTHER240: | ||||
| 				if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK)) | ||||
| 					return sectors; | ||||
| 				logicalTrack = physicalTrack; | ||||
| 				break; | ||||
| 		} | ||||
|  | ||||
|         for (int sectorId=0; sectorId<BROTHER_SECTORS_PER_TRACK; sectorId++) | ||||
|         { | ||||
|             const auto& sector = image.get(logicalTrack, 0, sectorId); | ||||
|             const auto& sector = image.get(physicalTrack, 0, sectorId); | ||||
|             if (sector) | ||||
|                 sectors.push_back(sector); | ||||
| 		} | ||||
| @@ -146,7 +143,6 @@ public: | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, | ||||
| 			const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override | ||||
| 	{ | ||||
| 		int logicalTrack; | ||||
| 		if (physicalSide != 0) | ||||
| 			return std::unique_ptr<Fluxmap>(); | ||||
| 		physicalTrack -= _config.bias(); | ||||
| @@ -156,13 +152,11 @@ public: | ||||
| 				if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2)) | ||||
| 						|| (physicalTrack & 1)) | ||||
| 					return std::unique_ptr<Fluxmap>(); | ||||
| 				logicalTrack = physicalTrack/2; | ||||
| 				break; | ||||
|  | ||||
| 			case BROTHER240: | ||||
| 				if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK)) | ||||
| 					return std::unique_ptr<Fluxmap>(); | ||||
| 				logicalTrack = physicalTrack; | ||||
| 				break; | ||||
| 		} | ||||
|  | ||||
| @@ -179,10 +173,10 @@ public: | ||||
| 			double dataMs = headerMs + _config.post_header_spacing_ms(); | ||||
| 			unsigned dataCursor = dataMs*1e3 / _config.clock_rate_us(); | ||||
|  | ||||
| 			const auto& sectorData = image.get(logicalTrack, 0, sectorId); | ||||
| 			const auto& sectorData = image.get(physicalTrack, 0, sectorId); | ||||
|  | ||||
| 			fillBitmapTo(bits, cursor, headerCursor, { true, false }); | ||||
| 			write_sector_header(bits, cursor, logicalTrack, sectorId); | ||||
| 			write_sector_header(bits, cursor, sectorData->logicalTrack, sectorId); | ||||
| 			fillBitmapTo(bits, cursor, dataCursor, { true, false }); | ||||
| 			write_sector_data(bits, cursor, sectorData->data); | ||||
| 		} | ||||
|   | ||||
| @@ -45,6 +45,18 @@ You should end up with an `apple2.img` which is 143360 bytes long. | ||||
| **Big warning!** The image may not work in an emulator, due to the | ||||
| logical sector mapping issue described above. | ||||
|  | ||||
| Writing discs | ||||
| ------------- | ||||
|  | ||||
| Just do: | ||||
| ``` | ||||
| fluxengine write apple2 -i apple2.img | ||||
| ``` | ||||
|  | ||||
| **Big warning!** An image designed for an emulator may not work, due to the | ||||
| logical sector mapping issue described above. | ||||
|  | ||||
|  | ||||
| Useful references | ||||
| ----------------- | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ metadata. Systems which use IBM scheme disks include but are not limited to: | ||||
|   - NEC PC-98 series | ||||
|   - Sharp X68000 | ||||
|   - Fujitsu FM Towns | ||||
|   - VAX & PDP-11 | ||||
|   - etc | ||||
|  | ||||
| FluxEngine supports reading these. However, some variants are more peculiar | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "arch/amiga/amiga.h" | ||||
| #include "arch/apple2/apple2.h" | ||||
| #include "arch/brother/brother.h" | ||||
| #include "arch/c64/c64.h" | ||||
| #include "arch/ibm/ibm.h" | ||||
| @@ -20,6 +21,7 @@ std::unique_ptr<AbstractEncoder> AbstractEncoder::create(const EncoderProto& con | ||||
| 		std::function<std::unique_ptr<AbstractEncoder>(const EncoderProto&)>> encoders = | ||||
| 	{ | ||||
| 		{ EncoderProto::kAmiga,     createAmigaEncoder }, | ||||
| 		{ EncoderProto::kApple2,    createApple2Encoder }, | ||||
| 		{ EncoderProto::kBrother,   createBrotherEncoder }, | ||||
| 		{ EncoderProto::kC64,       createCommodore64Encoder }, | ||||
| 		{ EncoderProto::kIbm,       createIbmEncoder }, | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "arch/amiga/amiga.proto"; | ||||
| import "arch/apple2/apple2.proto"; | ||||
| import "arch/brother/brother.proto"; | ||||
| import "arch/c64/c64.proto"; | ||||
| import "arch/ibm/ibm.proto"; | ||||
| @@ -22,5 +23,6 @@ message EncoderProto { | ||||
| 		NorthstarEncoderProto northstar = 9; | ||||
| 		MicropolisEncoderProto micropolis = 10; | ||||
| 		Victor9kEncoderProto victor9k = 11; | ||||
|                 Apple2EncoderProto apple2 = 12; | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										48
									
								
								lib/flags.cc
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								lib/flags.cc
									
									
									
									
									
								
							| @@ -182,32 +182,38 @@ void FlagGroup::parseFlagsWithConfigFiles(int argc, const char* argv[], | ||||
| { | ||||
|     parseFlags(argc, argv, | ||||
| 		[&](const auto& filename) { | ||||
| 			const auto& it = configFiles.find(filename); | ||||
| 			if (it != configFiles.end()) | ||||
| 			{ | ||||
| 				ConfigProto newConfig; | ||||
| 				if (!newConfig.ParseFromString(it->second)) | ||||
| 					Error() << "couldn't load built-in config proto"; | ||||
| 				config.MergeFrom(newConfig); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				std::ifstream f(filename, std::ios::out); | ||||
| 				if (f.fail()) | ||||
| 					Error() << fmt::format("Cannot open '{}': {}", filename, strerror(errno)); | ||||
|  | ||||
| 				std::ostringstream ss; | ||||
| 				ss << f.rdbuf(); | ||||
|  | ||||
| 				if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config)) | ||||
| 					Error() << "couldn't load external config proto"; | ||||
| 			} | ||||
|  | ||||
| 			parseConfigFile(filename, configFiles); | ||||
| 			return true; | ||||
| 		} | ||||
| 	); | ||||
| } | ||||
|  | ||||
| void FlagGroup::parseConfigFile( | ||||
| 		const std::string& filename, | ||||
| 		const std::map<std::string, std::string>& configFiles) | ||||
| { | ||||
| 	const auto& it = configFiles.find(filename); | ||||
| 	if (it != configFiles.end()) | ||||
| 	{ | ||||
| 		ConfigProto newConfig; | ||||
| 		if (!newConfig.ParseFromString(it->second)) | ||||
| 			Error() << "couldn't load built-in config proto"; | ||||
| 		config.MergeFrom(newConfig); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		std::ifstream f(filename, std::ios::out); | ||||
| 		if (f.fail()) | ||||
| 			Error() << fmt::format("Cannot open '{}': {}", filename, strerror(errno)); | ||||
|  | ||||
| 		std::ostringstream ss; | ||||
| 		ss << f.rdbuf(); | ||||
|  | ||||
| 		if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config)) | ||||
| 			Error() << "couldn't load external config proto"; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void FlagGroup::checkInitialised() const | ||||
| { | ||||
|     if (!_initialised) | ||||
|   | ||||
| @@ -15,11 +15,15 @@ public: | ||||
|     void parseFlags(int argc, const char* argv[], | ||||
| 		std::function<bool(const std::string&)> callback = | ||||
| 			[](const auto&){ return false; }); | ||||
|     std::vector<std::string> parseFlagsWithFilenames(int argc, const char* argv[], | ||||
|     std::vector<std::string> parseFlagsWithFilenames( | ||||
| 		int argc, const char* argv[], | ||||
| 		std::function<bool(const std::string&)> callback = | ||||
| 			[](const auto&){ return false; }); | ||||
| 	void parseFlagsWithConfigFiles(int argc, const char* argv[], | ||||
| 			const std::map<std::string, std::string>& configFiles); | ||||
| 	static void parseConfigFile( | ||||
| 			const std::string& filename, | ||||
| 			const std::map<std::string, std::string>& configFiles); | ||||
|     void addFlag(Flag* flag); | ||||
|     void checkInitialised() const; | ||||
|  | ||||
|   | ||||
| @@ -18,7 +18,7 @@ struct TrackDataFlux | ||||
| 	unsigned physicalHead; | ||||
| 	std::shared_ptr<const Fluxmap> fluxmap; | ||||
| 	std::vector<std::shared_ptr<const Record>> records; | ||||
| 	std::vector<std::shared_ptr<Sector>> sectors; | ||||
| 	std::vector<std::shared_ptr<const Sector>> sectors; | ||||
| }; | ||||
|  | ||||
| struct TrackFlux | ||||
| @@ -32,7 +32,7 @@ struct TrackFlux | ||||
| struct DiskFlux | ||||
| { | ||||
| 	std::vector<std::shared_ptr<const TrackFlux>> tracks; | ||||
| 	std::unique_ptr<const Image> image; | ||||
| 	std::shared_ptr<const Image> image; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -34,13 +34,13 @@ std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config) | ||||
|  | ||||
| void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& filename) | ||||
| { | ||||
| 	static const std::vector<std::pair<std::regex, std::function<void(const std::string&)>>> formats = | ||||
| 	static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSinkProto*)>>> formats = | ||||
| 	{ | ||||
| 		{ std::regex("^(.*\\.flux)$"), [&](const auto& s) { proto->mutable_fl2()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.scp)$"),  [&](const auto& s) { proto->mutable_scp()->set_filename(s); }}, | ||||
| 		{ std::regex("^vcd:(.*)$"),    [&](const auto& s) { proto->mutable_vcd()->set_directory(s); }}, | ||||
| 		{ std::regex("^au:(.*)$"),     [&](const auto& s) { proto->mutable_au()->set_directory(s); }}, | ||||
| 		{ std::regex("^drive:(.*)"),   [&](const auto& s) { proto->mutable_drive()->set_drive(std::stoi(s)); }}, | ||||
| 		{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.scp)$"),  [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }}, | ||||
| 		{ std::regex("^vcd:(.*)$"),    [](auto& s, auto* proto) { proto->mutable_vcd()->set_directory(s); }}, | ||||
| 		{ std::regex("^au:(.*)$"),     [](auto& s, auto* proto) { proto->mutable_au()->set_directory(s); }}, | ||||
| 		{ std::regex("^drive:(.*)"),   [](auto& s, auto* proto) { proto->mutable_drive()->set_drive(std::stoi(s)); }}, | ||||
| 	}; | ||||
|  | ||||
| 	for (const auto& it : formats) | ||||
| @@ -48,7 +48,7 @@ void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& | ||||
| 		std::smatch match; | ||||
| 		if (std::regex_match(filename, match, it.first)) | ||||
| 		{ | ||||
| 			it.second(match[1]); | ||||
| 			it.second(match[1], proto); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -48,15 +48,15 @@ std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config) | ||||
| void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::string& filename) | ||||
| { | ||||
|  | ||||
| 	static const std::vector<std::pair<std::regex, std::function<void(const std::string&)>>> formats = | ||||
| 	static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSourceProto*)>>> formats = | ||||
| 	{ | ||||
| 		{ std::regex("^(.*\\.flux)$"),     [&](const auto& s) { proto->mutable_fl2()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.scp)$"),      [&](const auto& s) { proto->mutable_scp()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.cwf)$"),      [&](const auto& s) { proto->mutable_cwf()->set_filename(s); }}, | ||||
| 		{ std::regex("^erase:$"),          [&](const auto& s) { proto->mutable_erase(); }}, | ||||
| 		{ std::regex("^kryoflux:(.*)$"),   [&](const auto& s) { proto->mutable_kryoflux()->set_directory(s); }}, | ||||
| 		{ std::regex("^testpattern:(.*)"), [&](const auto& s) { proto->mutable_test_pattern(); }}, | ||||
| 		{ std::regex("^drive:(.*)"),       [&](const auto& s) { proto->mutable_drive()->set_drive(std::stoi(s)); }}, | ||||
| 		{ std::regex("^(.*\\.flux)$"),     [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.scp)$"),      [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }}, | ||||
| 		{ std::regex("^(.*\\.cwf)$"),      [](auto& s, auto* proto) { proto->mutable_cwf()->set_filename(s); }}, | ||||
| 		{ std::regex("^erase:$"),          [](auto& s, auto* proto) { proto->mutable_erase(); }}, | ||||
| 		{ std::regex("^kryoflux:(.*)$"),   [](auto& s, auto* proto) { proto->mutable_kryoflux()->set_directory(s); }}, | ||||
| 		{ std::regex("^testpattern:(.*)"), [](auto& s, auto* proto) { proto->mutable_test_pattern(); }}, | ||||
| 		{ std::regex("^drive:(.*)"),       [](auto& s, auto* proto) { proto->mutable_drive()->set_drive(std::stoi(s)); }}, | ||||
| 	}; | ||||
|  | ||||
| 	for (const auto& it : formats) | ||||
| @@ -64,7 +64,7 @@ void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::stri | ||||
| 		std::smatch match; | ||||
| 		if (std::regex_match(filename, match, it.first)) | ||||
| 		{ | ||||
| 			it.second(match[1]); | ||||
| 			it.second(match[1], proto); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -31,13 +31,22 @@ extern double getCurrentTime(); | ||||
| extern void hexdump(std::ostream& stream, const Bytes& bytes); | ||||
| extern void hexdumpForSrp16(std::ostream& stream, const Bytes& bytes); | ||||
|  | ||||
| struct ErrorException | ||||
| { | ||||
| 	const std::string message; | ||||
| }; | ||||
|  | ||||
| class Error | ||||
| { | ||||
| public: | ||||
|     [[ noreturn ]] ~Error() | ||||
| 	Error() | ||||
| 	{ | ||||
| 		_stream << "Error: "; | ||||
| 	} | ||||
|  | ||||
|     [[ noreturn ]] ~Error() noexcept(false) | ||||
|     { | ||||
|         std::cerr << "Error: " << _stream.str() << std::endl; | ||||
|         exit(1); | ||||
| 		throw ErrorException { _stream.str() }; | ||||
|     } | ||||
|  | ||||
|     template <typename T> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ public: | ||||
|  | ||||
| 	public: | ||||
| 		const_iterator(const wrapped_iterator_t& it): _it(it) {} | ||||
| 		const Sector* operator* () { return _it->second.get(); } | ||||
| 		std::shared_ptr<const Sector> operator* () { return _it->second; } | ||||
| 		void operator++ () { _it++; } | ||||
| 		bool operator== (const const_iterator& other) const { return _it == other._it; } | ||||
| 		bool operator!= (const const_iterator& other) const { return _it != other._it; } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "fmt/format.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "proto.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -12,13 +13,12 @@ | ||||
| class D64ImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	D64ImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     D64ImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| @@ -26,24 +26,22 @@ public: | ||||
|         uint32_t begin = inputFile.tellg(); | ||||
|         inputFile.seekg(0, inputFile.end); | ||||
|         uint32_t end = inputFile.tellg(); | ||||
|         uint32_t inputFileSize = (end-begin); | ||||
|         uint32_t inputFileSize = (end - begin); | ||||
|         inputFile.seekg(0, inputFile.beg); | ||||
| 		Bytes data; | ||||
| 		data.writer() += inputFile; | ||||
| 		ByteReader br(data); | ||||
|         Bytes data; | ||||
|         data.writer() += inputFile; | ||||
|         ByteReader br(data); | ||||
|  | ||||
| 		unsigned numCylinders = 39; | ||||
| 		unsigned numHeads = 1; | ||||
| 		unsigned numSectors = 0; | ||||
|         unsigned numCylinders = 39; | ||||
|         unsigned numHeads = 1; | ||||
|         unsigned numSectors = 0; | ||||
|  | ||||
| 		std::cout << "reading D64 image\n" | ||||
| 		          << fmt::format("{} cylinders, {} heads\n", | ||||
| 				  		numCylinders, numHeads); | ||||
|         Logger() << fmt::format("D64: reading image with {} cylinders, {} heads", numCylinders, numHeads); | ||||
|  | ||||
|         uint32_t offset = 0; | ||||
|  | ||||
| 		auto sectorsPerTrack = [&](int track) -> int | ||||
| 		{ | ||||
|         auto sectorsPerTrack = [&](int track) -> int | ||||
|         { | ||||
|             if (track < 17) | ||||
|                 return 21; | ||||
|             if (track < 24) | ||||
| @@ -51,36 +49,37 @@ public: | ||||
|             if (track < 30) | ||||
|                 return 18; | ||||
|             return 17; | ||||
| 		}; | ||||
|         }; | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
|         for (int track = 0; track < 40; track++) | ||||
|         { | ||||
| 			int numSectors = sectorsPerTrack(track); | ||||
| 			int physicalCylinder = track*2; | ||||
|             int numSectors = sectorsPerTrack(track); | ||||
|             int physicalCylinder = track * 2; | ||||
|             for (int head = 0; head < numHeads; head++) | ||||
|             { | ||||
|                 for (int sectorId = 0; sectorId < numSectors; sectorId++) | ||||
|                 { | ||||
| 					const auto& sector = image->put(track, head, sectorId); | ||||
|                     const auto& sector = image->put(track, head, sectorId); | ||||
|                     if ((offset < inputFileSize)) | ||||
|                     {    //still data available sector OK | ||||
| 						br.seek(offset); | ||||
|                     { // still data available sector OK | ||||
|                         br.seek(offset); | ||||
|                         Bytes payload = br.read(256); | ||||
|                         offset += 256; | ||||
|  | ||||
|                         sector->status = Sector::OK; | ||||
|                         sector->logicalTrack = track; | ||||
| 						sector->physicalCylinder = physicalCylinder; | ||||
|                         sector->physicalCylinder = physicalCylinder; | ||||
|                         sector->logicalSide = sector->physicalHead = head; | ||||
|                         sector->logicalSector = sectorId; | ||||
|                         sector->data.writer().append(payload); | ||||
|                     } | ||||
| 					else | ||||
|                     {   //no more data in input file. Write sectors with status: DATA_MISSING | ||||
|                     else | ||||
|                     { // no more data in input file. Write sectors with status: | ||||
|                       // DATA_MISSING | ||||
|                         sector->status = Sector::DATA_MISSING; | ||||
|                         sector->logicalTrack = track; | ||||
| 						sector->physicalCylinder = physicalCylinder; | ||||
|                         sector->physicalCylinder = physicalCylinder; | ||||
|                         sector->logicalSide = sector->physicalHead = head; | ||||
|                         sector->logicalSector = sectorId; | ||||
|                     } | ||||
| @@ -88,14 +87,13 @@ public: | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
|         image->calculateSize(); | ||||
|         return image; | ||||
| 	} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createD64ImageReader(const ImageReaderProto& config) | ||||
| std::unique_ptr<ImageReader> ImageReader::createD64ImageReader( | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new D64ImageReader(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| @@ -16,18 +17,17 @@ | ||||
| class D88ImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	D88ImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     D88ImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
|         Bytes header(0x24); // read first entry of track table as well | ||||
|         inputFile.read((char*) header.begin(), header.size()); | ||||
|         inputFile.read((char*)header.begin(), header.size()); | ||||
|  | ||||
|         // the DIM header technically has a bit field for sectors present, | ||||
|         // however it is currently ignored by this reader | ||||
| @@ -35,45 +35,55 @@ public: | ||||
|         std::string diskName = header.slice(0, 0x16); | ||||
|  | ||||
|         if (diskName[0]) | ||||
|             std::cout << "D88: disk name: " << diskName << "\n"; | ||||
|             Logger() << fmt::format("D88: disk name: {}", diskName); | ||||
|  | ||||
|         ByteReader headerReader(header); | ||||
|  | ||||
|         char mediaFlag = headerReader.seek(0x1b).read_8(); | ||||
|  | ||||
|         inputFile.seekg( 0, std::ios::end ); | ||||
|         inputFile.seekg(0, std::ios::end); | ||||
|         int fileSize = inputFile.tellg(); | ||||
|  | ||||
|         int diskSize = headerReader.seek(0x1c).read_le32(); | ||||
|  | ||||
|         if (diskSize > fileSize) | ||||
|             std::cout << "D88: found multiple disk images. Only using first\n"; | ||||
|             Logger() << "D88: found multiple disk images. Only using first"; | ||||
|  | ||||
|         int trackTableEnd = headerReader.seek(0x20).read_le32(); | ||||
|         int trackTableSize = trackTableEnd - 0x20; | ||||
|  | ||||
|         Bytes trackTable(trackTableSize); | ||||
|         inputFile.seekg(0x20); | ||||
|         inputFile.read((char*) trackTable.begin(), trackTable.size()); | ||||
|         inputFile.read((char*)trackTable.begin(), trackTable.size()); | ||||
|         ByteReader trackTableReader(trackTable); | ||||
|  | ||||
|         if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|             std::cout << "D88: overriding configured format\n"; | ||||
|         if (config.encoder().format_case() != | ||||
|             EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|             Logger() << "D88: overriding configured format"; | ||||
|  | ||||
|         auto ibm = config.mutable_encoder()->mutable_ibm(); | ||||
|         int physicalStep = 1; | ||||
|         int clockRate = 500; | ||||
|         if (mediaFlag == 0x20) { | ||||
|             std::cout << "D88: high density mode\n"; | ||||
|             if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) { | ||||
|                 config.mutable_flux_sink()->mutable_drive()->set_high_density(true); | ||||
|         if (mediaFlag == 0x20) | ||||
|         { | ||||
|             Logger() << "D88: high density mode"; | ||||
|             if (config.flux_sink().dest_case() == | ||||
|                 FluxSinkProto::DestCase::kDrive) | ||||
|             { | ||||
|                 config.mutable_flux_sink()->mutable_drive()->set_high_density( | ||||
|                     true); | ||||
|             } | ||||
|         } else { | ||||
|             std::cout << "D88: single/double density mode\n"; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Logger() << "D88: single/double density mode"; | ||||
|             physicalStep = 2; | ||||
|             clockRate = 300; | ||||
|             if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) { | ||||
|                 config.mutable_flux_sink()->mutable_drive()->set_high_density(false); | ||||
|             if (config.flux_sink().dest_case() == | ||||
|                 FluxSinkProto::DestCase::kDrive) | ||||
|             { | ||||
|                 config.mutable_flux_sink()->mutable_drive()->set_high_density( | ||||
|                     false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -81,11 +91,13 @@ public: | ||||
|         for (int track = 0; track < trackTableSize / 4; track++) | ||||
|         { | ||||
|             int trackOffset = trackTableReader.seek(track * 4).read_le32(); | ||||
|             if (trackOffset == 0) continue; | ||||
|             if (trackOffset == 0) | ||||
|                 continue; | ||||
|  | ||||
|             int currentTrackOffset = trackOffset; | ||||
|             int currentTrackCylinder = -1; | ||||
|             int currentSectorsInTrack = 0xffff; // don't know # of sectors until we read the first one | ||||
|             int currentSectorsInTrack = | ||||
|                 0xffff; // don't know # of sectors until we read the first one | ||||
|             int trackSectorSize = -1; | ||||
|             int trackMfm = -1; | ||||
|  | ||||
| @@ -94,9 +106,12 @@ public: | ||||
|             trackdata->set_track_length_ms(167); | ||||
|             auto sectors = trackdata->mutable_sectors(); | ||||
|  | ||||
|             for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack; sectorInTrack++){ | ||||
|             for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack; | ||||
|                  sectorInTrack++) | ||||
|             { | ||||
|                 Bytes sectorHeader(0x10); | ||||
|                 inputFile.read((char*) sectorHeader.begin(), sectorHeader.size()); | ||||
|                 inputFile.read( | ||||
|                     (char*)sectorHeader.begin(), sectorHeader.size()); | ||||
|                 ByteReader sectorHeaderReader(sectorHeader); | ||||
|                 int cylinder = sectorHeaderReader.seek(0).read_8(); | ||||
|                 int head = sectorHeaderReader.seek(1).read_8(); | ||||
| @@ -107,50 +122,70 @@ public: | ||||
|                 int ddam = sectorHeaderReader.seek(7).read_8(); | ||||
|                 int fddStatusCode = sectorHeaderReader.seek(8).read_8(); | ||||
|                 int rpm = sectorHeaderReader.seek(13).read_8(); | ||||
|                 // D88 provides much more sector information that is currently ignored | ||||
|                 // D88 provides much more sector information that is currently | ||||
|                 // ignored | ||||
|                 if (ddam != 0) | ||||
|                     Error() << "D88: nonzero ddam currently unsupported"; | ||||
|                 if (rpm != 0) | ||||
|                     Error() << "D88: 1.44MB 300rpm formats currently unsupported"; | ||||
|                     Error() | ||||
|                         << "D88: 1.44MB 300rpm formats currently unsupported"; | ||||
|                 if (fddStatusCode != 0) | ||||
|                     Error() << "D88: nonzero fdd status codes are currently unsupported"; | ||||
|                 if (currentSectorsInTrack == 0xffff) { | ||||
|                     Error() << "D88: nonzero fdd status codes are currently " | ||||
|                                "unsupported"; | ||||
|                 if (currentSectorsInTrack == 0xffff) | ||||
|                 { | ||||
|                     currentSectorsInTrack = sectorsInTrack; | ||||
|                 } else if (currentSectorsInTrack != sectorsInTrack) { | ||||
|                 } | ||||
|                 else if (currentSectorsInTrack != sectorsInTrack) | ||||
|                 { | ||||
|                     Error() << "D88: mismatched number of sectors in track"; | ||||
|                 } | ||||
|                 if (currentTrackCylinder < 0) { | ||||
|                 if (currentTrackCylinder < 0) | ||||
|                 { | ||||
|                     currentTrackCylinder = cylinder; | ||||
|                 } else if (currentTrackCylinder != cylinder) { | ||||
|                     Error() << "D88: all sectors in a track must belong to the same cylinder"; | ||||
|                 } | ||||
|                 if (trackSectorSize < 0) { | ||||
|                 else if (currentTrackCylinder != cylinder) | ||||
|                 { | ||||
|                     Error() << "D88: all sectors in a track must belong to the " | ||||
|                                "same cylinder"; | ||||
|                 } | ||||
|                 if (trackSectorSize < 0) | ||||
|                 { | ||||
|                     trackSectorSize = sectorSize; | ||||
|                     // this is the first sector we've read, use it settings for per-track data | ||||
|                     // this is the first sector we've read, use it settings for | ||||
|                     // per-track data | ||||
|                     trackdata->set_cylinder(cylinder * physicalStep); | ||||
|                     trackdata->set_head(head); | ||||
|                     trackdata->set_sector_size(sectorSize); | ||||
|                     trackdata->set_use_fm(fm); | ||||
|                     if (fm) { | ||||
|                     if (fm) | ||||
|                     { | ||||
|                         trackdata->set_gap_fill_byte(0xffff); | ||||
|                         trackdata->set_idam_byte(0xf57e); | ||||
|                         trackdata->set_dam_byte(0xf56f); | ||||
|                     } | ||||
|                     // create timings to approximately match N88-BASIC | ||||
|                     if (sectorSize <= 128) { | ||||
|                     if (sectorSize <= 128) | ||||
|                     { | ||||
|                         trackdata->set_gap0(0x1b); | ||||
|                         trackdata->set_gap2(0x09); | ||||
|                         trackdata->set_gap3(0x1b); | ||||
|                     } else if (sectorSize <= 256) { | ||||
|                     } | ||||
|                     else if (sectorSize <= 256) | ||||
|                     { | ||||
|                         trackdata->set_gap0(0x36); | ||||
|                         trackdata->set_gap3(0x36); | ||||
|                     } | ||||
|                 } else if (trackSectorSize != sectorSize) { | ||||
|                     Error() << "D88: multiple sector sizes per track are currently unsupported"; | ||||
|                 } | ||||
|                 else if (trackSectorSize != sectorSize) | ||||
|                 { | ||||
|                     Error() << "D88: multiple sector sizes per track are " | ||||
|                                "currently unsupported"; | ||||
|                 } | ||||
|                 Bytes data(sectorSize); | ||||
|                 inputFile.read((char*) data.begin(), data.size()); | ||||
|                 const auto& sector = image->put(cylinder * physicalStep, head, sectorId); | ||||
|                 inputFile.read((char*)data.begin(), data.size()); | ||||
|                 const auto& sector = | ||||
|                     image->put(cylinder * physicalStep, head, sectorId); | ||||
|                 sector->status = Sector::OK; | ||||
|                 sector->logicalTrack = cylinder; | ||||
|                 sector->physicalCylinder = cylinder * physicalStep; | ||||
| @@ -161,7 +196,8 @@ public: | ||||
|                 sectors->add_sector(sectorId); | ||||
|             } | ||||
|  | ||||
|             if (physicalStep == 2) { | ||||
|             if (physicalStep == 2) | ||||
|             { | ||||
|                 auto trackdata = ibm->add_trackdata(); | ||||
|                 trackdata->set_clock_rate_khz(clockRate); | ||||
|                 trackdata->set_track_length_ms(167); | ||||
| @@ -170,31 +206,30 @@ public: | ||||
|  | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("D88: read {} tracks, {} sides\n", | ||||
|                         geometry.numTracks, geometry.numSides); | ||||
|         Logger() << fmt::format("D88: read {} tracks, {} sides", | ||||
|             geometry.numTracks, | ||||
|             geometry.numSides); | ||||
|  | ||||
| 		if (!config.has_heads()) | ||||
| 		{ | ||||
| 			auto* heads = config.mutable_heads(); | ||||
| 			heads->set_start(0); | ||||
| 			heads->set_end(geometry.numSides - 1); | ||||
| 		} | ||||
|         if (!config.has_heads()) | ||||
|         { | ||||
|             auto* heads = config.mutable_heads(); | ||||
|             heads->set_start(0); | ||||
|             heads->set_end(geometry.numSides - 1); | ||||
|         } | ||||
|  | ||||
| 		if (!config.has_cylinders()) | ||||
| 		{ | ||||
| 			auto* cylinders = config.mutable_cylinders(); | ||||
| 			cylinders->set_start(0); | ||||
| 			cylinders->set_end(geometry.numTracks - 1); | ||||
| 		} | ||||
|         if (!config.has_cylinders()) | ||||
|         { | ||||
|             auto* cylinders = config.mutable_cylinders(); | ||||
|             cylinders->set_start(0); | ||||
|             cylinders->set_end(geometry.numTracks - 1); | ||||
|         } | ||||
|  | ||||
|         return image; | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createD88ImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new D88ImageReader(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "sector.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "proto.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| @@ -16,18 +17,17 @@ | ||||
| class DimImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	DimImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     DimImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
|         Bytes header(256); | ||||
|         inputFile.read((char*) header.begin(), header.size()); | ||||
|         inputFile.read((char*)header.begin(), header.size()); | ||||
|         if (header.slice(0xAB, 13) != Bytes("DIFC HEADER  ")) | ||||
|             Error() << "DIM: could not find DIM header, is this a DIM file?"; | ||||
|  | ||||
| @@ -38,7 +38,8 @@ public: | ||||
|         int tracks; | ||||
|         int sectorsPerTrack; | ||||
|         int sectorSize; | ||||
|         switch (mediaByte) { | ||||
|         switch (mediaByte) | ||||
|         { | ||||
|             case 0: | ||||
|                 tracks = 77; | ||||
|                 sectorsPerTrack = 8; | ||||
| @@ -65,12 +66,12 @@ public: | ||||
|         } | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
| 		int trackCount = 0; | ||||
|         int trackCount = 0; | ||||
|         for (int track = 0; track < tracks; track++) | ||||
|         { | ||||
| 			if (inputFile.eof()) | ||||
| 				break; | ||||
| 			int physicalCylinder = track; | ||||
|             if (inputFile.eof()) | ||||
|                 break; | ||||
|             int physicalCylinder = track; | ||||
|  | ||||
|             for (int side = 0; side < 2; side++) | ||||
|             { | ||||
| @@ -81,30 +82,34 @@ public: | ||||
|                 for (int sectorId : sectors) | ||||
|                 { | ||||
|                     Bytes data(sectorSize); | ||||
|                     inputFile.read((char*) data.begin(), data.size()); | ||||
|                     inputFile.read((char*)data.begin(), data.size()); | ||||
|  | ||||
| 					const auto& sector = image->put(physicalCylinder, side, sectorId); | ||||
|                     const auto& sector = | ||||
|                         image->put(physicalCylinder, side, sectorId); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = track; | ||||
| 					sector->physicalCylinder = physicalCylinder; | ||||
|                     sector->physicalCylinder = physicalCylinder; | ||||
|                     sector->logicalSide = sector->physicalHead = side; | ||||
|                     sector->logicalSector = sectorId; | ||||
|                     sector->data = data; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| 			trackCount++; | ||||
|             trackCount++; | ||||
|         } | ||||
|  | ||||
|         if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|         if (config.encoder().format_case() == | ||||
|             EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|         { | ||||
|             auto ibm = config.mutable_encoder()->mutable_ibm(); | ||||
|             auto trackdata = ibm->add_trackdata(); | ||||
|             trackdata->set_clock_rate_khz(500); | ||||
|             auto sectors = trackdata->mutable_sectors(); | ||||
|             switch (mediaByte) { | ||||
|             switch (mediaByte) | ||||
|             { | ||||
|                 case 0x00: | ||||
|                     std::cout << "DIM: automatically setting format to 1.2MB (1024 byte sectors)\n"; | ||||
|                     Logger() << "DIM: automatically setting format to 1.2MB " | ||||
|                                 "(1024 byte sectors)"; | ||||
|                     config.mutable_cylinders()->set_end(76); | ||||
|                     trackdata->set_track_length_ms(167); | ||||
|                     trackdata->set_sector_size(1024); | ||||
| @@ -112,55 +117,58 @@ public: | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 case 0x02: | ||||
|                     std::cout << "DIM: automatically setting format to 1.2MB (512 byte sectors)\n"; | ||||
|                     Logger() << "DIM: automatically setting format to 1.2MB " | ||||
|                                 "(512 byte sectors)"; | ||||
|                     trackdata->set_track_length_ms(167); | ||||
|                     trackdata->set_sector_size(512); | ||||
|                     for (int i = 0; i < 15; i++) | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 case 0x03: | ||||
|                     std::cout << "DIM: automatically setting format to 1.44MB\n"; | ||||
|                     Logger() << "DIM: automatically setting format to 1.44MB"; | ||||
|                     trackdata->set_track_length_ms(200); | ||||
|                     trackdata->set_sector_size(512); | ||||
|                     for (int i = 0; i < 18; i++) | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 default: | ||||
|                     Error() << fmt::format("DIM: unknown media byte 0x%02x, could not determine write profile automatically", mediaByte); | ||||
|                     Error() << fmt::format( | ||||
|                         "DIM: unknown media byte 0x%02x, could not determine " | ||||
|                         "write profile automatically", | ||||
|                         mediaByte); | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
| 			config.mutable_decoder()->mutable_ibm(); | ||||
|             config.mutable_decoder()->mutable_ibm(); | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
| 		const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("DIM: read {} tracks, {} sides, {} kB total\n", | ||||
|                         geometry.numTracks, geometry.numSides, | ||||
| 						((int)inputFile.tellg() - 256) / 1024); | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         Logger() << fmt::format("DIM: read {} tracks, {} sides, {} kB total", | ||||
|             geometry.numTracks, | ||||
|             geometry.numSides, | ||||
|             ((int)inputFile.tellg() - 256) / 1024); | ||||
|  | ||||
| 		if (!config.has_heads()) | ||||
| 		{ | ||||
| 			auto* heads = config.mutable_heads(); | ||||
| 			heads->set_start(0); | ||||
| 			heads->set_end(geometry.numSides - 1); | ||||
| 		} | ||||
|         if (!config.has_heads()) | ||||
|         { | ||||
|             auto* heads = config.mutable_heads(); | ||||
|             heads->set_start(0); | ||||
|             heads->set_end(geometry.numSides - 1); | ||||
|         } | ||||
|  | ||||
| 		if (!config.has_cylinders()) | ||||
| 		{ | ||||
| 			auto* cylinders = config.mutable_cylinders(); | ||||
| 			cylinders->set_start(0); | ||||
| 			cylinders->set_end(geometry.numTracks - 1); | ||||
| 		} | ||||
|         if (!config.has_cylinders()) | ||||
|         { | ||||
|             auto* cylinders = config.mutable_cylinders(); | ||||
|             cylinders->set_start(0); | ||||
|             cylinders->set_end(geometry.numTracks - 1); | ||||
|         } | ||||
|  | ||||
|         return image; | ||||
| 	} | ||||
|  | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createDimImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new DimImageReader(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "sector.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| @@ -12,98 +13,99 @@ | ||||
| class DiskCopyImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	DiskCopyImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     DiskCopyImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		Bytes data; | ||||
| 		data.writer() += inputFile; | ||||
| 		ByteReader br(data); | ||||
|         Bytes data; | ||||
|         data.writer() += inputFile; | ||||
|         ByteReader br(data); | ||||
|  | ||||
| 		br.seek(1); | ||||
| 		std::string label = br.read(data[0]); | ||||
|         br.seek(1); | ||||
|         std::string label = br.read(data[0]); | ||||
|  | ||||
| 		br.seek(0x40); | ||||
| 		uint32_t dataSize = br.read_be32(); | ||||
|         br.seek(0x40); | ||||
|         uint32_t dataSize = br.read_be32(); | ||||
|  | ||||
| 		br.seek(0x50); | ||||
| 		uint8_t encoding = br.read_8(); | ||||
| 		uint8_t formatByte = br.read_8(); | ||||
|         br.seek(0x50); | ||||
|         uint8_t encoding = br.read_8(); | ||||
|         uint8_t formatByte = br.read_8(); | ||||
|  | ||||
| 		unsigned numCylinders = 80; | ||||
| 		unsigned numHeads = 2; | ||||
| 		unsigned numSectors = 0; | ||||
| 		bool mfm = false; | ||||
|         unsigned numCylinders = 80; | ||||
|         unsigned numHeads = 2; | ||||
|         unsigned numSectors = 0; | ||||
|         bool mfm = false; | ||||
|  | ||||
| 		switch (encoding) | ||||
| 		{ | ||||
| 			case 0: /* GCR CLV 400kB */ | ||||
| 				numHeads = 1; | ||||
| 				break; | ||||
|         switch (encoding) | ||||
|         { | ||||
|             case 0: /* GCR CLV 400kB */ | ||||
|                 numHeads = 1; | ||||
|                 break; | ||||
|  | ||||
| 			case 1: /* GCR CLV 800kB */ | ||||
| 				break; | ||||
|             case 1: /* GCR CLV 800kB */ | ||||
|                 break; | ||||
|  | ||||
| 			case 2: /* MFM CAV 720kB */ | ||||
| 				numSectors = 9; | ||||
| 				mfm = true; | ||||
| 				break; | ||||
|             case 2: /* MFM CAV 720kB */ | ||||
|                 numSectors = 9; | ||||
|                 mfm = true; | ||||
|                 break; | ||||
|  | ||||
| 			case 3: /* MFM CAV 1440kB */ | ||||
| 				numSectors = 18; | ||||
| 				mfm = true; | ||||
| 				break; | ||||
|             case 3: /* MFM CAV 1440kB */ | ||||
|                 numSectors = 18; | ||||
|                 mfm = true; | ||||
|                 break; | ||||
|  | ||||
| 			default: | ||||
| 				Error() << fmt::format("don't understand DiskCopy disks of type {}", encoding); | ||||
| 		} | ||||
|             default: | ||||
|                 Error() << fmt::format( | ||||
|                     "don't understand DiskCopy disks of type {}", encoding); | ||||
|         } | ||||
|  | ||||
| 		std::cout << "reading DiskCopy 4.2 image\n" | ||||
| 		          << fmt::format("{} cylinders, {} heads; {}; {}\n", | ||||
| 				  		numCylinders, numHeads, | ||||
| 						mfm ? "MFM" : "GCR", | ||||
| 						label); | ||||
|         Logger() << fmt::format( | ||||
|             "DC42: reading image with {} cylinders, {} heads; {}; {}", | ||||
|             numCylinders, | ||||
|             numHeads, | ||||
|             mfm ? "MFM" : "GCR", | ||||
|             label); | ||||
|  | ||||
| 		auto sectorsPerTrack = [&](int track) -> int | ||||
| 		{ | ||||
| 			if (mfm) | ||||
| 				return numSectors; | ||||
|         auto sectorsPerTrack = [&](int track) -> int | ||||
|         { | ||||
|             if (mfm) | ||||
|                 return numSectors; | ||||
|  | ||||
| 			if (track < 16) | ||||
| 				return 12; | ||||
| 			if (track < 32) | ||||
| 				return 11; | ||||
| 			if (track < 48) | ||||
| 				return 10; | ||||
| 			if (track < 64) | ||||
| 				return 9; | ||||
| 			return 8; | ||||
| 		}; | ||||
|             if (track < 16) | ||||
|                 return 12; | ||||
|             if (track < 32) | ||||
|                 return 11; | ||||
|             if (track < 48) | ||||
|                 return 10; | ||||
|             if (track < 64) | ||||
|                 return 9; | ||||
|             return 8; | ||||
|         }; | ||||
|  | ||||
| 		uint32_t dataPtr = 0x54; | ||||
| 		uint32_t tagPtr = dataPtr + dataSize; | ||||
|         uint32_t dataPtr = 0x54; | ||||
|         uint32_t tagPtr = dataPtr + dataSize; | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
|         for (int track = 0; track < numCylinders; track++) | ||||
|         { | ||||
| 			int numSectors = sectorsPerTrack(track); | ||||
|             int numSectors = sectorsPerTrack(track); | ||||
|             for (int head = 0; head < numHeads; head++) | ||||
|             { | ||||
|                 for (int sectorId = 0; sectorId < numSectors; sectorId++) | ||||
|                 { | ||||
| 					br.seek(dataPtr); | ||||
| 					Bytes payload = br.read(512); | ||||
| 					dataPtr += 512; | ||||
|                     br.seek(dataPtr); | ||||
|                     Bytes payload = br.read(512); | ||||
|                     dataPtr += 512; | ||||
|  | ||||
| 					br.seek(tagPtr); | ||||
| 					Bytes tag = br.read(12); | ||||
| 					tagPtr += 12; | ||||
|                     br.seek(tagPtr); | ||||
|                     Bytes tag = br.read(12); | ||||
|                     tagPtr += 12; | ||||
|  | ||||
|                     const auto& sector = image->put(track, head, sectorId); | ||||
|                     sector->status = Sector::OK; | ||||
| @@ -115,21 +117,17 @@ public: | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->setGeometry({ | ||||
| 			.numTracks = numCylinders, | ||||
| 			.numSides = numHeads, | ||||
| 			.numSectors = 12, | ||||
| 			.sectorSize = 512 + 12, | ||||
| 			.irregular = true | ||||
| 		}); | ||||
|         image->setGeometry({.numTracks = numCylinders, | ||||
|             .numSides = numHeads, | ||||
|             .numSectors = 12, | ||||
|             .sectorSize = 512 + 12, | ||||
|             .irregular = true}); | ||||
|         return image; | ||||
| 	} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createDiskCopyImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new DiskCopyImageReader(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| @@ -16,24 +17,23 @@ | ||||
| class FdiImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	FdiImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     FdiImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
|         Bytes header(32); | ||||
|         inputFile.read((char*) header.begin(), header.size()); | ||||
|         inputFile.read((char*)header.begin(), header.size()); | ||||
|         ByteReader headerReader(header); | ||||
|         if (headerReader.seek(0).read_le32() != 0) | ||||
|             Error() << "FDI: could not find FDI header, is this a FDI file?"; | ||||
|  | ||||
|         // we currently don't use fddType but it could be used to automatically select | ||||
|         // profile parameters in the future | ||||
|         // we currently don't use fddType but it could be used to automatically | ||||
|         // select profile parameters in the future | ||||
|         // | ||||
|         int fddType = headerReader.seek(4).read_le32(); | ||||
|         int headerSize = headerReader.seek(0x08).read_le32(); | ||||
| @@ -45,12 +45,12 @@ public: | ||||
|         inputFile.seekg(headerSize); | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
| 		int trackCount = 0; | ||||
|         int trackCount = 0; | ||||
|         for (int track = 0; track < tracks; track++) | ||||
|         { | ||||
| 			if (inputFile.eof()) | ||||
| 				break; | ||||
| 			int physicalCylinder = track; | ||||
|             if (inputFile.eof()) | ||||
|                 break; | ||||
|             int physicalCylinder = track; | ||||
|  | ||||
|             for (int side = 0; side < sides; side++) | ||||
|             { | ||||
| @@ -61,30 +61,34 @@ public: | ||||
|                 for (int sectorId : sectors) | ||||
|                 { | ||||
|                     Bytes data(sectorSize); | ||||
|                     inputFile.read((char*) data.begin(), data.size()); | ||||
|                     inputFile.read((char*)data.begin(), data.size()); | ||||
|  | ||||
| 					const auto& sector = image->put(physicalCylinder, side, sectorId); | ||||
|                     const auto& sector = | ||||
|                         image->put(physicalCylinder, side, sectorId); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = track; | ||||
| 					sector->physicalCylinder = physicalCylinder; | ||||
|                     sector->physicalCylinder = physicalCylinder; | ||||
|                     sector->logicalSide = sector->physicalHead = side; | ||||
|                     sector->logicalSector = sectorId; | ||||
|                     sector->data = data; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| 			trackCount++; | ||||
|             trackCount++; | ||||
|         } | ||||
|  | ||||
|         if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|         if (config.encoder().format_case() == | ||||
|             EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|         { | ||||
|             auto ibm = config.mutable_encoder()->mutable_ibm(); | ||||
|             auto trackdata = ibm->add_trackdata(); | ||||
|             trackdata->set_clock_rate_khz(500); | ||||
|             auto sectors = trackdata->mutable_sectors(); | ||||
|             switch (fddType) { | ||||
|             switch (fddType) | ||||
|             { | ||||
|                 case 0x90: | ||||
|                     std::cout << "FDI: automatically setting format to 1.2MB (1024 byte sectors)\n"; | ||||
|                     Logger() << "FDI: automatically setting format to 1.2MB " | ||||
|                                 "(1024 byte sectors)"; | ||||
|                     config.mutable_cylinders()->set_end(76); | ||||
|                     trackdata->set_track_length_ms(167); | ||||
|                     trackdata->set_sector_size(1024); | ||||
| @@ -92,46 +96,48 @@ public: | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 case 0x30: | ||||
|                     std::cout << "FDI: automatically setting format to 1.44MB\n"; | ||||
|                     Logger() << "FDI: automatically setting format to 1.44MB"; | ||||
|                     trackdata->set_track_length_ms(200); | ||||
|                     trackdata->set_sector_size(512); | ||||
|                     for (int i = 0; i < 18; i++) | ||||
|                         sectors->add_sector(i); | ||||
|                     break; | ||||
|                 default: | ||||
|                     Error() << fmt::format("FDI: unknown fdd type 0x%02x, could not determine write profile automatically", fddType); | ||||
|                     Error() << fmt::format( | ||||
|                         "FDI: unknown fdd type 0x{:2x}, could not determine " | ||||
|                         "write profile automatically", | ||||
|                         fddType); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
| 		const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("FDI: read {} tracks, {} sides, {} kB total\n", | ||||
|                         geometry.numTracks, geometry.numSides, | ||||
| 						((int)inputFile.tellg() - headerSize) / 1024); | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         Logger() << fmt::format("FDI: read {} tracks, {} sides, {} kB total", | ||||
|             geometry.numTracks, | ||||
|             geometry.numSides, | ||||
|             ((int)inputFile.tellg() - headerSize) / 1024); | ||||
|  | ||||
| 		if (!config.has_heads()) | ||||
| 		{ | ||||
| 			auto* heads = config.mutable_heads(); | ||||
| 			heads->set_start(0); | ||||
| 			heads->set_end(geometry.numSides - 1); | ||||
| 		} | ||||
|         if (!config.has_heads()) | ||||
|         { | ||||
|             auto* heads = config.mutable_heads(); | ||||
|             heads->set_start(0); | ||||
|             heads->set_end(geometry.numSides - 1); | ||||
|         } | ||||
|  | ||||
|         if (!config.has_cylinders()) | ||||
|         { | ||||
|             auto* cylinders = config.mutable_cylinders(); | ||||
|             cylinders->set_start(0); | ||||
|             cylinders->set_end(geometry.numTracks - 1); | ||||
|         } | ||||
|  | ||||
| 		if (!config.has_cylinders()) | ||||
| 		{ | ||||
| 			auto* cylinders = config.mutable_cylinders(); | ||||
| 			cylinders->set_start(0); | ||||
| 			cylinders->set_end(geometry.numTracks - 1); | ||||
| 		} | ||||
| 		 | ||||
|         return image; | ||||
| 	} | ||||
|  | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createFdiImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new FdiImageReader(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -55,32 +55,32 @@ std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config) | ||||
|  | ||||
| void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::string& filename) | ||||
| { | ||||
| 	static const std::map<std::string, std::function<void(void)>> formats = | ||||
| 	static const std::map<std::string, std::function<void(ImageReaderProto*)>> formats = | ||||
| 	{ | ||||
| 		{".adf",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".d64",      [&]() { proto->mutable_d64(); }}, | ||||
| 		{".d81",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".d88",      [&]() { proto->mutable_d88(); }}, | ||||
| 		{".dim",      [&]() { proto->mutable_dim(); }}, | ||||
| 		{".diskcopy", [&]() { proto->mutable_diskcopy(); }}, | ||||
| 		{".dsk",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".fdi",      [&]() { proto->mutable_fdi(); }}, | ||||
| 		{".imd",      [&]() { proto->mutable_imd(); }}, | ||||
| 		{".img",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".jv3",      [&]() { proto->mutable_jv3(); }}, | ||||
| 		{".nfd",      [&]() { proto->mutable_nfd(); }}, | ||||
| 		{".nsi",      [&]() { proto->mutable_nsi(); }}, | ||||
| 		{".st",       [&]() { proto->mutable_img(); }}, | ||||
| 		{".td0",      [&]() { proto->mutable_td0(); }}, | ||||
| 		{".vgi",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".adf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d64",      [](auto* proto) { proto->mutable_d64(); }}, | ||||
| 		{".d81",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d88",      [](auto* proto) { proto->mutable_d88(); }}, | ||||
| 		{".dim",      [](auto* proto) { proto->mutable_dim(); }}, | ||||
| 		{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }}, | ||||
| 		{".dsk",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".fdi",      [](auto* proto) { proto->mutable_fdi(); }}, | ||||
| 		{".imd",      [](auto* proto) { proto->mutable_imd(); }}, | ||||
| 		{".img",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".jv3",      [](auto* proto) { proto->mutable_jv3(); }}, | ||||
| 		{".nfd",      [](auto* proto) { proto->mutable_nfd(); }}, | ||||
| 		{".nsi",      [](auto* proto) { proto->mutable_nsi(); }}, | ||||
| 		{".st",       [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".td0",      [](auto* proto) { proto->mutable_td0(); }}, | ||||
| 		{".vgi",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 	}; | ||||
|  | ||||
| 	for (const auto& it : formats) | ||||
| 	{ | ||||
| 		if (endsWith(filename, it.first)) | ||||
| 		{ | ||||
| 			it.second(); | ||||
| 			it.second(proto); | ||||
| 			proto->set_filename(filename); | ||||
| 			return; | ||||
| 		} | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| @@ -140,9 +141,7 @@ public: | ||||
|         headerPtr = n; //set pointer to after comment | ||||
|         comment[n] = '\0'; // null-terminate the string | ||||
|         //write comment to screen | ||||
|         std::cout   << "Comment in IMD image:\n" | ||||
|                     << fmt::format("{}\n", | ||||
|                     comment); | ||||
|         Logger()   << fmt::format("IMD: comment: {}", comment); | ||||
|  | ||||
|         //first read header | ||||
|         for (;;) | ||||
| @@ -237,7 +236,7 @@ public: | ||||
|         size_t headSize = header.numSectors * sectorSize; | ||||
|         size_t trackSize = headSize * (header.Head + 1); | ||||
|  | ||||
|         std::cout << fmt::format("IMD: {} tracks, {} heads; {}; {} kbps; {} sectors; sectorsize {};\n" | ||||
|         Logger() << fmt::format("IMD: {} tracks, {} heads; {}; {} kbps; {} sectors; sectorsize {};" | ||||
|                                  "     sectormap {}; {} kB total\n", | ||||
|                     header.track, header.Head + 1, | ||||
|                     mfm ? "MFM" : "FM", | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "sector.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "imginputoutpututils.h" | ||||
| #include "fmt/format.h" | ||||
| @@ -13,85 +14,91 @@ | ||||
| class ImgImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	ImgImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     ImgImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		if (!_config.img().tracks() || !_config.img().sides()) | ||||
| 			Error() << "IMG: bad configuration; did you remember to set the tracks, sides and trackdata fields?"; | ||||
|         if (!_config.img().tracks() || !_config.img().sides()) | ||||
|             Error() << "IMG: bad configuration; did you remember to set the " | ||||
|                        "tracks, sides and trackdata fields?"; | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
| 		for (const auto& p : getTrackOrdering(_config.img())) | ||||
| 		{ | ||||
| 			int track = p.first; | ||||
| 			int side = p.second; | ||||
|         for (const auto& p : getTrackOrdering(_config.img())) | ||||
|         { | ||||
|             int track = p.first; | ||||
|             int side = p.second; | ||||
|  | ||||
| 			if (inputFile.eof()) | ||||
| 				break; | ||||
| 			int physicalCylinder = track * _config.img().physical_step() + _config.img().physical_offset(); | ||||
|             if (inputFile.eof()) | ||||
|                 break; | ||||
|             int physicalCylinder = track * _config.img().physical_step() + | ||||
|                                    _config.img().physical_offset(); | ||||
|  | ||||
| 			ImgInputOutputProto::TrackdataProto trackdata; | ||||
| 			getTrackFormat(_config.img(), trackdata, track, side); | ||||
|             ImgInputOutputProto::TrackdataProto trackdata; | ||||
|             getTrackFormat(_config.img(), trackdata, track, side); | ||||
|  | ||||
| 			for (int sectorId : getSectors(trackdata)) | ||||
| 			{ | ||||
| 				Bytes data(trackdata.sector_size()); | ||||
| 				inputFile.read((char*) data.begin(), data.size()); | ||||
|             for (int sectorId : getSectors(trackdata)) | ||||
|             { | ||||
|                 Bytes data(trackdata.sector_size()); | ||||
|                 inputFile.read((char*)data.begin(), data.size()); | ||||
|  | ||||
| 				const auto& sector = image->put(physicalCylinder, side, sectorId); | ||||
| 				sector->status = Sector::OK; | ||||
| 				sector->logicalTrack = track; | ||||
| 				sector->physicalCylinder = physicalCylinder; | ||||
| 				sector->logicalSide = sector->physicalHead = side; | ||||
| 				sector->logicalSector = sectorId; | ||||
| 				sector->data = data; | ||||
| 			} | ||||
|                 const auto& sector = | ||||
|                     image->put(physicalCylinder, side, sectorId); | ||||
|                 sector->status = Sector::OK; | ||||
|                 sector->logicalTrack = track; | ||||
|                 sector->physicalCylinder = physicalCylinder; | ||||
|                 sector->logicalSide = sector->physicalHead = side; | ||||
|                 sector->logicalSector = sectorId; | ||||
|                 sector->data = data; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
| 		const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("IMG: read {} tracks, {} sides, {} kB total\n", | ||||
|                         geometry.numTracks, geometry.numSides, | ||||
| 						inputFile.tellg() / 1024); | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         Logger() << fmt::format("IMG: read {} tracks, {} sides, {} kB total", | ||||
|             geometry.numTracks, | ||||
|             geometry.numSides, | ||||
|             inputFile.tellg() / 1024); | ||||
|         return image; | ||||
| 	} | ||||
|     } | ||||
|  | ||||
| 	std::vector<unsigned> getSectors(const ImgInputOutputProto::TrackdataProto& trackdata) | ||||
| 	{ | ||||
| 		std::vector<unsigned> sectors; | ||||
| 		switch (trackdata.sectors_oneof_case()) | ||||
| 		{ | ||||
| 			case ImgInputOutputProto::TrackdataProto::SectorsOneofCase::kSectors: | ||||
| 			{ | ||||
| 				for (int sectorId : trackdata.sectors().sector()) | ||||
| 					sectors.push_back(sectorId); | ||||
| 				break; | ||||
| 			} | ||||
|     std::vector<unsigned> getSectors( | ||||
|         const ImgInputOutputProto::TrackdataProto& trackdata) | ||||
|     { | ||||
|         std::vector<unsigned> sectors; | ||||
|         switch (trackdata.sectors_oneof_case()) | ||||
|         { | ||||
|             case ImgInputOutputProto::TrackdataProto::SectorsOneofCase:: | ||||
|                 kSectors: | ||||
|             { | ||||
|                 for (int sectorId : trackdata.sectors().sector()) | ||||
|                     sectors.push_back(sectorId); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
| 			case ImgInputOutputProto::TrackdataProto::SectorsOneofCase::kSectorRange: | ||||
| 			{ | ||||
| 				int sectorId = trackdata.sector_range().start_sector(); | ||||
| 				for (int i=0; i<trackdata.sector_range().sector_count(); i++) | ||||
| 					sectors.push_back(sectorId + i); | ||||
| 				break; | ||||
| 			} | ||||
|             case ImgInputOutputProto::TrackdataProto::SectorsOneofCase:: | ||||
|                 kSectorRange: | ||||
|             { | ||||
|                 int sectorId = trackdata.sector_range().start_sector(); | ||||
|                 for (int i = 0; i < trackdata.sector_range().sector_count(); | ||||
|                      i++) | ||||
|                     sectors.push_back(sectorId + i); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
| 			default: | ||||
| 				Error() << "no list of sectors provided in track format"; | ||||
| 		} | ||||
| 		return sectors; | ||||
| 	} | ||||
|             default: | ||||
|                 Error() << "no list of sectors provided in track format"; | ||||
|         } | ||||
|         return sectors; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createImgImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new ImgImageReader(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -3,15 +3,17 @@ | ||||
| #include "sector.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| /* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, which may appear | ||||
|  * in any order, followed by the same again for more sectors. To find the second data block | ||||
|  * you need to know the size of the first data block, which requires parsing it. | ||||
| /* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, | ||||
|  * which may appear in any order, followed by the same again for more sectors. | ||||
|  * To find the second data block you need to know the size of the first data | ||||
|  * block, which requires parsing it. | ||||
|  * | ||||
|  * https://www.tim-mann.org/trs80/dskconfig.html | ||||
|  * | ||||
| @@ -33,108 +35,117 @@ | ||||
|  | ||||
| struct SectorHeader | ||||
| { | ||||
| 	uint8_t track; | ||||
| 	uint8_t sector; | ||||
| 	uint8_t flags; | ||||
|     uint8_t track; | ||||
|     uint8_t sector; | ||||
|     uint8_t flags; | ||||
| }; | ||||
|  | ||||
| #define JV3_DENSITY     0x80  /* 1=dden, 0=sden */ | ||||
| #define JV3_DAM         0x60  /* data address mark code; see below */ | ||||
| #define JV3_SIDE        0x10  /* 0=side 0, 1=side 1 */ | ||||
| #define JV3_ERROR       0x08  /* 0=ok, 1=CRC error */ | ||||
| #define JV3_NONIBM      0x04  /* 0=normal, 1=short */ | ||||
| #define JV3_SIZE        0x03  /* in used sectors: 0=256,1=128,2=1024,3=512 | ||||
|                                  in free sectors: 0=512,1=1024,2=128,3=256 */ | ||||
| #define JV3_DENSITY 0x80 /* 1=dden, 0=sden */ | ||||
| #define JV3_DAM 0x60     /* data address mark code; see below */ | ||||
| #define JV3_SIDE 0x10    /* 0=side 0, 1=side 1 */ | ||||
| #define JV3_ERROR 0x08   /* 0=ok, 1=CRC error */ | ||||
| #define JV3_NONIBM 0x04  /* 0=normal, 1=short */ | ||||
| #define JV3_SIZE                                      \ | ||||
|     0x03 /* in used sectors: 0=256,1=128,2=1024,3=512 \ | ||||
|             in free sectors: 0=512,1=1024,2=128,3=256 */ | ||||
|  | ||||
| #define JV3_FREE        0xFF  /* in track and sector fields of free sectors */ | ||||
| #define JV3_FREEF       0xFC  /* in flags field, or'd with size code */ | ||||
| #define JV3_FREE 0xFF  /* in track and sector fields of free sectors */ | ||||
| #define JV3_FREEF 0xFC /* in flags field, or'd with size code */ | ||||
|  | ||||
| static unsigned getSectorSize(uint8_t flags) | ||||
| { | ||||
| 	if ((flags & JV3_FREEF) == JV3_FREEF) | ||||
| 	{ | ||||
| 		switch (flags & JV3_SIZE) | ||||
| 		{ | ||||
| 			case 0: return 512; | ||||
| 			case 1: return 1024; | ||||
| 			case 2: return 128; | ||||
| 			case 3: return 256; | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		switch (flags & JV3_SIZE) | ||||
| 		{ | ||||
| 			case 0: return 256; | ||||
| 			case 1: return 128; | ||||
| 			case 2: return 1024; | ||||
| 			case 3: return 512; | ||||
| 		} | ||||
| 	} | ||||
| 	Error() << "not reachable"; | ||||
|     if ((flags & JV3_FREEF) == JV3_FREEF) | ||||
|     { | ||||
|         switch (flags & JV3_SIZE) | ||||
|         { | ||||
|             case 0: | ||||
|                 return 512; | ||||
|             case 1: | ||||
|                 return 1024; | ||||
|             case 2: | ||||
|                 return 128; | ||||
|             case 3: | ||||
|                 return 256; | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         switch (flags & JV3_SIZE) | ||||
|         { | ||||
|             case 0: | ||||
|                 return 256; | ||||
|             case 1: | ||||
|                 return 128; | ||||
|             case 2: | ||||
|                 return 1024; | ||||
|             case 3: | ||||
|                 return 512; | ||||
|         } | ||||
|     } | ||||
|     Error() << "not reachable"; | ||||
| } | ||||
|  | ||||
| class Jv3ImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	Jv3ImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     Jv3ImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		inputFile.seekg( 0, std::ios::end); | ||||
| 		unsigned inputFileSize = inputFile.tellg(); | ||||
| 		unsigned headerPtr = 0; | ||||
|         inputFile.seekg(0, std::ios::end); | ||||
|         unsigned inputFileSize = inputFile.tellg(); | ||||
|         unsigned headerPtr = 0; | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
| 		for (;;) | ||||
| 		{ | ||||
| 			unsigned dataPtr = headerPtr + 2901*3 + 1; | ||||
| 			if (dataPtr >= inputFileSize) | ||||
| 				break; | ||||
|         for (;;) | ||||
|         { | ||||
|             unsigned dataPtr = headerPtr + 2901 * 3 + 1; | ||||
|             if (dataPtr >= inputFileSize) | ||||
|                 break; | ||||
|  | ||||
| 			for (unsigned i=0; i<2901; i++) | ||||
| 			{ | ||||
| 				SectorHeader header = {0, 0, 0xff}; | ||||
| 				inputFile.seekg(headerPtr); | ||||
| 				inputFile.read((char*) &header, 3); | ||||
| 				unsigned sectorSize = getSectorSize(header.flags); | ||||
| 				if ((header.flags & JV3_FREEF) != JV3_FREEF) | ||||
| 				{ | ||||
| 					Bytes data(sectorSize); | ||||
| 					inputFile.seekg(dataPtr); | ||||
| 					inputFile.read((char*) data.begin(), sectorSize); | ||||
|             for (unsigned i = 0; i < 2901; i++) | ||||
|             { | ||||
|                 SectorHeader header = {0, 0, 0xff}; | ||||
|                 inputFile.seekg(headerPtr); | ||||
|                 inputFile.read((char*)&header, 3); | ||||
|                 unsigned sectorSize = getSectorSize(header.flags); | ||||
|                 if ((header.flags & JV3_FREEF) != JV3_FREEF) | ||||
|                 { | ||||
|                     Bytes data(sectorSize); | ||||
|                     inputFile.seekg(dataPtr); | ||||
|                     inputFile.read((char*)data.begin(), sectorSize); | ||||
|  | ||||
| 					unsigned head = !!(header.flags & JV3_SIDE); | ||||
| 					const auto& sector = image->put(header.track, head, header.sector); | ||||
|                     unsigned head = !!(header.flags & JV3_SIDE); | ||||
|                     const auto& sector = | ||||
|                         image->put(header.track, head, header.sector); | ||||
|                     sector->status = Sector::OK; | ||||
|                     sector->logicalTrack = sector->physicalCylinder = header.track; | ||||
|                     sector->logicalTrack = sector->physicalCylinder = | ||||
|                         header.track; | ||||
|                     sector->logicalSide = sector->physicalHead = head; | ||||
|                     sector->logicalSector = header.sector; | ||||
|                     sector->data = data; | ||||
| 				} | ||||
|                 } | ||||
|  | ||||
| 				headerPtr += 3; | ||||
| 				dataPtr += sectorSize; | ||||
| 			} | ||||
|                 headerPtr += 3; | ||||
|                 dataPtr += sectorSize; | ||||
|             } | ||||
|  | ||||
| 			/* dataPtr is now pointing at the beginning of the next chunk. */ | ||||
|             /* dataPtr is now pointing at the beginning of the next chunk. */ | ||||
|  | ||||
| 			headerPtr = dataPtr; | ||||
| 		} | ||||
|             headerPtr = dataPtr; | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
|         image->calculateSize(); | ||||
|         return image; | ||||
| 	} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader(const ImageReaderProto& config) | ||||
| std::unique_ptr<ImageReader> ImageReader::createJv3ImageReader( | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new Jv3ImageReader(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "proto.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include <algorithm> | ||||
| @@ -16,44 +17,48 @@ | ||||
| class NFDImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	NFDImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     NFDImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
|         Bytes fileId(14); // read first entry of track table as well | ||||
|         inputFile.read((char*) fileId.begin(), fileId.size()); | ||||
|         inputFile.read((char*)fileId.begin(), fileId.size()); | ||||
|  | ||||
|         if (fileId == Bytes("T98FDDIMAGE.R1")) { | ||||
|         if (fileId == Bytes("T98FDDIMAGE.R1")) | ||||
|         { | ||||
|             Error() << "NFD: r1 images are not currently supported"; | ||||
|         } | ||||
|         if (fileId != Bytes("T98FDDIMAGE.R0")) { | ||||
|         if (fileId != Bytes("T98FDDIMAGE.R0")) | ||||
|         { | ||||
|             Error() << "NFD: could not find NFD header"; | ||||
|         } | ||||
|  | ||||
|         Bytes header(0x10a10); | ||||
|         inputFile.seekg( 0, std::ios::beg ); | ||||
|         inputFile.read((char*) header.begin(), header.size()); | ||||
|         inputFile.seekg(0, std::ios::beg); | ||||
|         inputFile.read((char*)header.begin(), header.size()); | ||||
|  | ||||
|         ByteReader headerReader(header); | ||||
|  | ||||
|         char heads = headerReader.seek(0x115).read_8(); | ||||
|         if (heads != 2) { | ||||
|         if (heads != 2) | ||||
|         { | ||||
|             Error() << "NFD: unsupported number of heads"; | ||||
|         } | ||||
|  | ||||
|         if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|             std::cout << "NFD: overriding configured format"; | ||||
|         if (config.encoder().format_case() != | ||||
|             EncoderProto::FormatCase::FORMAT_NOT_SET) | ||||
|             Logger() << "NFD: overriding configured format"; | ||||
|  | ||||
|         auto ibm = config.mutable_encoder()->mutable_ibm(); | ||||
|         config.mutable_cylinders()->set_end(0); | ||||
|         std::cout << "NFD: HD 1.2MB mode\n"; | ||||
|         if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) { | ||||
|         Logger() << "NFD: HD 1.2MB mode"; | ||||
|         if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) | ||||
|         { | ||||
|             config.mutable_flux_sink()->mutable_drive()->set_high_density(true); | ||||
|         } | ||||
|  | ||||
| @@ -68,7 +73,8 @@ public: | ||||
|             int currentTrackHead = -1; | ||||
|             int trackSectorSize = -1; | ||||
|  | ||||
|             for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++){ | ||||
|             for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++) | ||||
|             { | ||||
|                 headerReader.seek(0x120 + track * 26 * 16 + sectorInTrack * 16); | ||||
|                 int cylinder = headerReader.read_8(); | ||||
|                 int head = headerReader.read_8(); | ||||
| @@ -83,41 +89,58 @@ public: | ||||
|                 if (ddam != 0) | ||||
|                     Error() << "NFD: nonzero ddam currently unsupported"; | ||||
|                 if (status != 0) | ||||
|                     Error() << "NFD: nonzero fdd status codes are currently unsupported"; | ||||
|                 if (currentTrackCylinder < 0) { | ||||
|                     Error() << "NFD: nonzero fdd status codes are currently " | ||||
|                                "unsupported"; | ||||
|                 if (currentTrackCylinder < 0) | ||||
|                 { | ||||
|                     currentTrackCylinder = cylinder; | ||||
|                     currentTrackHead = head; | ||||
|                 } else if (currentTrackCylinder != cylinder) { | ||||
|                     Error() << "NFD: all sectors in a track must belong to the same cylinder"; | ||||
|                 } else if (currentTrackHead != head) { | ||||
|                     Error() << "NFD: all sectors in a track must belong to the same head"; | ||||
|                 } | ||||
|                 if (trackSectorSize < 0) { | ||||
|                 else if (currentTrackCylinder != cylinder) | ||||
|                 { | ||||
|                     Error() << "NFD: all sectors in a track must belong to the " | ||||
|                                "same cylinder"; | ||||
|                 } | ||||
|                 else if (currentTrackHead != head) | ||||
|                 { | ||||
|                     Error() << "NFD: all sectors in a track must belong to the " | ||||
|                                "same head"; | ||||
|                 } | ||||
|                 if (trackSectorSize < 0) | ||||
|                 { | ||||
|                     trackSectorSize = sectorSize; | ||||
|                     // this is the first sector we've read, use it settings for per-track data | ||||
|                     // this is the first sector we've read, use it settings for | ||||
|                     // per-track data | ||||
|                     trackdata->set_cylinder(cylinder); | ||||
|                     trackdata->set_head(head); | ||||
|                     trackdata->set_sector_size(sectorSize); | ||||
|                     trackdata->set_use_fm(!mfm); | ||||
|                     if (!mfm) { | ||||
|                     if (!mfm) | ||||
|                     { | ||||
|                         trackdata->set_gap_fill_byte(0xffff); | ||||
|                         trackdata->set_idam_byte(0xf57e); | ||||
|                         trackdata->set_dam_byte(0xf56f); | ||||
|                     } | ||||
|                     // create timings to approximately match N88-BASIC | ||||
|                     if (sectorSize <= 128) { | ||||
|                     if (sectorSize <= 128) | ||||
|                     { | ||||
|                         trackdata->set_gap0(0x1b); | ||||
|                         trackdata->set_gap2(0x09); | ||||
|                         trackdata->set_gap3(0x1b); | ||||
|                     } else if (sectorSize <= 256) { | ||||
|                     } | ||||
|                     else if (sectorSize <= 256) | ||||
|                     { | ||||
|                         trackdata->set_gap0(0x36); | ||||
|                         trackdata->set_gap3(0x36); | ||||
|                     } | ||||
|                 } else if (trackSectorSize != sectorSize) { | ||||
|                     Error() << "NFD: multiple sector sizes per track are currently unsupported"; | ||||
|                 } | ||||
|                 else if (trackSectorSize != sectorSize) | ||||
|                 { | ||||
|                     Error() << "NFD: multiple sector sizes per track are " | ||||
|                                "currently unsupported"; | ||||
|                 } | ||||
|                 Bytes data(sectorSize); | ||||
|                 inputFile.read((char*) data.begin(), data.size()); | ||||
|                 inputFile.read((char*)data.begin(), data.size()); | ||||
|                 const auto& sector = image->put(cylinder, head, sectorId); | ||||
|                 sector->status = Sector::OK; | ||||
|                 sector->logicalTrack = cylinder; | ||||
| @@ -134,16 +157,15 @@ public: | ||||
|  | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("NFD: read {} tracks, {} sides\n", | ||||
|                         geometry.numTracks, geometry.numSides); | ||||
|         Logger() << fmt::format("NFD: read {} tracks, {} sides", | ||||
|             geometry.numTracks, | ||||
|             geometry.numSides); | ||||
|         return image; | ||||
|     } | ||||
|  | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createNFDImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new NFDImageReader(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include "logger.h" | ||||
| #include "lib/imagereader/imagereader.pb.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -14,55 +15,59 @@ | ||||
| class NsiImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	NsiImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     NsiImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		const auto begin = inputFile.tellg(); | ||||
| 		inputFile.seekg(0, std::ios::end); | ||||
| 		const auto end = inputFile.tellg(); | ||||
| 		const auto fsize = (end - begin); | ||||
|         const auto begin = inputFile.tellg(); | ||||
|         inputFile.seekg(0, std::ios::end); | ||||
|         const auto end = inputFile.tellg(); | ||||
|         const auto fsize = (end - begin); | ||||
|  | ||||
| 		std::cout << "NSI: Autodetecting geometry based on file size: " << fsize << std::endl; | ||||
|         Logger() << fmt::format( | ||||
|             "NSI: Autodetecting geometry based on file size: {}", fsize); | ||||
|  | ||||
| 		unsigned numCylinders = 35; | ||||
| 		unsigned numSectors = 10; | ||||
| 		unsigned numHeads = 2; | ||||
| 		unsigned sectorSize = 512; | ||||
|         unsigned numCylinders = 35; | ||||
|         unsigned numSectors = 10; | ||||
|         unsigned numHeads = 2; | ||||
|         unsigned sectorSize = 512; | ||||
|  | ||||
| 		switch (fsize) { | ||||
| 			case 358400: | ||||
| 				numHeads = 2; | ||||
| 				sectorSize = 512; | ||||
| 				break; | ||||
|         switch (fsize) | ||||
|         { | ||||
|             case 358400: | ||||
|                 numHeads = 2; | ||||
|                 sectorSize = 512; | ||||
|                 break; | ||||
|  | ||||
| 			case 179200: | ||||
| 				numHeads = 1; | ||||
| 				sectorSize = 512; | ||||
| 				break; | ||||
| 				 | ||||
| 			case 89600: | ||||
| 				numHeads = 1; | ||||
| 				sectorSize = 256; | ||||
| 				break; | ||||
|             case 179200: | ||||
|                 numHeads = 1; | ||||
|                 sectorSize = 512; | ||||
|                 break; | ||||
|  | ||||
| 			default: | ||||
| 				Error() << "NSI: unknown file size"; | ||||
| 		} | ||||
|             case 89600: | ||||
|                 numHeads = 1; | ||||
|                 sectorSize = 256; | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 Error() << "NSI: unknown file size"; | ||||
|         } | ||||
|  | ||||
|         size_t trackSize = numSectors * sectorSize; | ||||
|  | ||||
|         std::cout << fmt::format("reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} kB total", | ||||
|                         numCylinders, numHeads, | ||||
|                         numSectors, sectorSize, | ||||
|                         numCylinders * numHeads * trackSize / 1024) | ||||
|                 << std::endl; | ||||
|         Logger() << fmt::format( | ||||
|             "reading {} tracks, {} heads, {} sectors, {} bytes per sector, {} " | ||||
|             "kB total", | ||||
|             numCylinders, | ||||
|             numHeads, | ||||
|             numSectors, | ||||
|             sectorSize, | ||||
|             numCylinders * numHeads * trackSize / 1024); | ||||
|  | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
|         unsigned sectorFileOffset; | ||||
| @@ -73,19 +78,24 @@ public: | ||||
|             { | ||||
|                 for (unsigned sectorId = 0; sectorId < numSectors; sectorId++) | ||||
|                 { | ||||
|                     if (head == 0) { /* Head 0 is from track 0-34 */ | ||||
|                         sectorFileOffset = track * trackSize + sectorId * sectorSize; | ||||
|                     if (head == 0) | ||||
|                     { /* Head 0 is from track 0-34 */ | ||||
|                         sectorFileOffset = | ||||
|                             track * trackSize + sectorId * sectorSize; | ||||
|                     } | ||||
|                     else { /* Head 1 is from track 70-35 */ | ||||
|                         sectorFileOffset = (trackSize * numCylinders) + /* Skip over side 0 */ | ||||
|                     else | ||||
|                     { /* Head 1 is from track 70-35 */ | ||||
|                         sectorFileOffset = | ||||
|                             (trackSize * numCylinders) + /* Skip over side 0 */ | ||||
|                             ((numCylinders - track - 1) * trackSize) + | ||||
|                             (sectorId * sectorSize); /* Sector offset from beginning of track. */ | ||||
|                             (sectorId * sectorSize); /* Sector offset from | ||||
|                                                         beginning of track. */ | ||||
|                     } | ||||
|  | ||||
|                     inputFile.seekg(sectorFileOffset, std::ios::beg); | ||||
|  | ||||
|                     Bytes data(sectorSize); | ||||
|                     inputFile.read((char*) data.begin(), sectorSize); | ||||
|                     inputFile.read((char*)data.begin(), sectorSize); | ||||
|  | ||||
|                     const auto& sector = image->put(track, head, sectorId); | ||||
|                     sector->status = Sector::OK; | ||||
| @@ -97,19 +107,16 @@ public: | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->setGeometry({ | ||||
| 			.numTracks = numCylinders, | ||||
| 			.numSides = numHeads, | ||||
| 			.numSectors = numSectors, | ||||
| 			.sectorSize = sectorSize | ||||
| 		}); | ||||
|         image->setGeometry({.numTracks = numCylinders, | ||||
|             .numSides = numHeads, | ||||
|             .numSectors = numSectors, | ||||
|             .sectorSize = sectorSize}); | ||||
|         return image; | ||||
| 	} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createNsiImageReader( | ||||
| 	const ImageReaderProto& config) | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new NsiImageReader(config)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "image.h" | ||||
| #include "crc.h" | ||||
| #include "logger.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "fmt/format.h" | ||||
| @@ -31,175 +32,182 @@ | ||||
|  | ||||
| enum | ||||
| { | ||||
| 	TD0_ENCODING_RAW = 0, | ||||
| 	TD0_ENCODING_REPEATED = 1, | ||||
| 	TD0_ENCODING_RLE = 2, | ||||
|     TD0_ENCODING_RAW = 0, | ||||
|     TD0_ENCODING_REPEATED = 1, | ||||
|     TD0_ENCODING_RLE = 2, | ||||
|  | ||||
| 	TD0_FLAG_DUPLICATE = 0x01, | ||||
| 	TD0_FLAG_CRC_ERROR = 0x02, | ||||
| 	TD0_FLAG_DELETED   = 0x04, | ||||
| 	TD0_FLAG_SKIPPED   = 0x10, | ||||
| 	TD0_FLAG_IDNODATA  = 0x20, | ||||
| 	TD0_FLAG_DATANOID  = 0x40, | ||||
|     TD0_FLAG_DUPLICATE = 0x01, | ||||
|     TD0_FLAG_CRC_ERROR = 0x02, | ||||
|     TD0_FLAG_DELETED = 0x04, | ||||
|     TD0_FLAG_SKIPPED = 0x10, | ||||
|     TD0_FLAG_IDNODATA = 0x20, | ||||
|     TD0_FLAG_DATANOID = 0x40, | ||||
| }; | ||||
|  | ||||
| class Td0ImageReader : public ImageReader | ||||
| { | ||||
| public: | ||||
| 	Td0ImageReader(const ImageReaderProto& config): | ||||
| 		ImageReader(config) | ||||
| 	{} | ||||
|     Td0ImageReader(const ImageReaderProto& config): ImageReader(config) {} | ||||
|  | ||||
| 	std::unique_ptr<Image> readImage() | ||||
| 	{ | ||||
|         std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); | ||||
|     std::unique_ptr<Image> readImage() | ||||
|     { | ||||
|         std::ifstream inputFile( | ||||
|             _config.filename(), std::ios::in | std::ios::binary); | ||||
|         if (!inputFile.is_open()) | ||||
|             Error() << "cannot open input file"; | ||||
|  | ||||
| 		Bytes input; | ||||
| 		input.writer() += inputFile; | ||||
| 		ByteReader br(input); | ||||
|         Bytes input; | ||||
|         input.writer() += inputFile; | ||||
|         ByteReader br(input); | ||||
|  | ||||
| 		uint16_t signature = br.read_be16(); | ||||
| 		br.skip(2); /* sequence and checksequence */ | ||||
| 		uint8_t version = br.read_8(); | ||||
| 		br.skip(2); /* data rate, drive type */ | ||||
| 		uint8_t stepping = br.read_8(); | ||||
| 		br.skip(1); /* sparse flag */ | ||||
| 		uint8_t sides = (br.read_8() == 1) ? 1 : 2; | ||||
| 		uint16_t headerCrc = br.read_le16(); | ||||
|         uint16_t signature = br.read_be16(); | ||||
|         br.skip(2); /* sequence and checksequence */ | ||||
|         uint8_t version = br.read_8(); | ||||
|         br.skip(2); /* data rate, drive type */ | ||||
|         uint8_t stepping = br.read_8(); | ||||
|         br.skip(1); /* sparse flag */ | ||||
|         uint8_t sides = (br.read_8() == 1) ? 1 : 2; | ||||
|         uint16_t headerCrc = br.read_le16(); | ||||
|  | ||||
| 		uint16_t gotCrc = crc16(0xa097, 0, input.slice(0, 10)); | ||||
| 		if (gotCrc != headerCrc) | ||||
| 			Error() << "TD0: header checksum mismatch"; | ||||
| 		if (signature != 0x5444) | ||||
| 			Error() << "TD0: unsupported file type (only uncompressed files are supported for now)"; | ||||
|         uint16_t gotCrc = crc16(0xa097, 0, input.slice(0, 10)); | ||||
|         if (gotCrc != headerCrc) | ||||
|             Error() << "TD0: header checksum mismatch"; | ||||
|         if (signature != 0x5444) | ||||
|             Error() << "TD0: unsupported file type (only uncompressed files " | ||||
|                        "are supported for now)"; | ||||
|  | ||||
| 		std::string comment = "(no comment)"; | ||||
| 		if (stepping & 0x80) | ||||
| 		{ | ||||
| 			/* Comment block */ | ||||
|         std::string comment = "(no comment)"; | ||||
|         if (stepping & 0x80) | ||||
|         { | ||||
|             /* Comment block */ | ||||
|  | ||||
| 			br.skip(2); /* comment CRC */ | ||||
| 			uint16_t length = br.read_le16(); | ||||
| 			br.skip(6); /* timestamp */ | ||||
| 			comment = br.read(length); | ||||
| 			std::replace(comment.begin(), comment.end(), '\0', '\n'); | ||||
|             br.skip(2); /* comment CRC */ | ||||
|             uint16_t length = br.read_le16(); | ||||
|             br.skip(6); /* timestamp */ | ||||
|             comment = br.read(length); | ||||
|             std::replace(comment.begin(), comment.end(), '\0', '\n'); | ||||
|  | ||||
| 			/* Strip trailing newlines */ | ||||
|             /* Strip trailing newlines */ | ||||
|  | ||||
| 			auto nl = std::find_if(comment.rbegin(), comment.rend(), | ||||
| 					[](unsigned char ch) { return !std::isspace(ch); }); | ||||
| 			comment.erase(nl.base(), comment.end()); | ||||
| 		} | ||||
|             auto nl = std::find_if(comment.rbegin(), | ||||
|                 comment.rend(), | ||||
|                 [](unsigned char ch) | ||||
|                 { | ||||
|                     return !std::isspace(ch); | ||||
|                 }); | ||||
|             comment.erase(nl.base(), comment.end()); | ||||
|         } | ||||
|  | ||||
| 		std::cout << fmt::format("TD0: TeleDisk {}.{}: {}\n", | ||||
| 			version / 10, version % 10, comment); | ||||
|         Logger() << fmt::format( | ||||
|             "TD0: TeleDisk {}.{}: {}", version / 10, version % 10, comment); | ||||
|  | ||||
| 		unsigned totalSize = 0; | ||||
|         unsigned totalSize = 0; | ||||
|         std::unique_ptr<Image> image(new Image); | ||||
| 		for (;;) | ||||
| 		{ | ||||
| 			/* Read track header */ | ||||
|         for (;;) | ||||
|         { | ||||
|             /* Read track header */ | ||||
|  | ||||
| 			uint8_t sectorCount = br.read_8(); | ||||
| 			if (sectorCount == 0xff) | ||||
| 				break; | ||||
|             uint8_t sectorCount = br.read_8(); | ||||
|             if (sectorCount == 0xff) | ||||
|                 break; | ||||
|  | ||||
| 			uint8_t physicalCylinder = br.read_8(); | ||||
| 			uint8_t physicalHead = br.read_8() & 1; | ||||
| 			br.skip(1); /* crc */ | ||||
|             uint8_t physicalCylinder = br.read_8(); | ||||
|             uint8_t physicalHead = br.read_8() & 1; | ||||
|             br.skip(1); /* crc */ | ||||
|  | ||||
| 			for (int i = 0; i < sectorCount; i++) | ||||
| 			{ | ||||
| 				/* Read sector */ | ||||
|             for (int i = 0; i < sectorCount; i++) | ||||
|             { | ||||
|                 /* Read sector */ | ||||
|  | ||||
| 				uint8_t logicalTrack = br.read_8(); | ||||
| 				uint8_t logicalSide = br.read_8(); | ||||
| 				uint8_t sectorId = br.read_8(); | ||||
| 				uint8_t sectorSizeEncoded = br.read_8(); | ||||
| 				unsigned sectorSize = 128<<sectorSizeEncoded; | ||||
| 				uint8_t flags = br.read_8(); | ||||
| 				br.skip(1); /* CRC */ | ||||
|                 uint8_t logicalTrack = br.read_8(); | ||||
|                 uint8_t logicalSide = br.read_8(); | ||||
|                 uint8_t sectorId = br.read_8(); | ||||
|                 uint8_t sectorSizeEncoded = br.read_8(); | ||||
|                 unsigned sectorSize = 128 << sectorSizeEncoded; | ||||
|                 uint8_t flags = br.read_8(); | ||||
|                 br.skip(1); /* CRC */ | ||||
|  | ||||
| 				uint16_t dataSize = br.read_le16(); | ||||
| 				Bytes encodedData = br.read(dataSize); | ||||
| 				ByteReader bre(encodedData); | ||||
| 				uint8_t encoding = bre.read_8(); | ||||
|                 uint16_t dataSize = br.read_le16(); | ||||
|                 Bytes encodedData = br.read(dataSize); | ||||
|                 ByteReader bre(encodedData); | ||||
|                 uint8_t encoding = bre.read_8(); | ||||
|  | ||||
| 				Bytes data; | ||||
| 				if (!(flags & (TD0_FLAG_SKIPPED|TD0_FLAG_IDNODATA))) | ||||
| 				{ | ||||
| 					switch (encoding) | ||||
| 					{ | ||||
| 						case TD0_ENCODING_RAW: | ||||
| 							data = encodedData.slice(1); | ||||
| 							break; | ||||
|                 Bytes data; | ||||
|                 if (!(flags & (TD0_FLAG_SKIPPED | TD0_FLAG_IDNODATA))) | ||||
|                 { | ||||
|                     switch (encoding) | ||||
|                     { | ||||
|                         case TD0_ENCODING_RAW: | ||||
|                             data = encodedData.slice(1); | ||||
|                             break; | ||||
|  | ||||
| 						case TD0_ENCODING_REPEATED: | ||||
| 						{ | ||||
| 							ByteWriter bw(data); | ||||
| 							while (!bre.eof()) | ||||
| 							{ | ||||
| 								uint16_t pattern = bre.read_le16(); | ||||
| 								uint16_t count = bre.read_le16(); | ||||
| 								while (count--) | ||||
| 									bw.write_le16(pattern); | ||||
| 							} | ||||
| 							break; | ||||
| 						} | ||||
|                         case TD0_ENCODING_REPEATED: | ||||
|                         { | ||||
|                             ByteWriter bw(data); | ||||
|                             while (!bre.eof()) | ||||
|                             { | ||||
|                                 uint16_t pattern = bre.read_le16(); | ||||
|                                 uint16_t count = bre.read_le16(); | ||||
|                                 while (count--) | ||||
|                                     bw.write_le16(pattern); | ||||
|                             } | ||||
|                             break; | ||||
|                         } | ||||
|  | ||||
| 						case TD0_ENCODING_RLE: | ||||
| 						{ | ||||
| 							ByteWriter bw(data); | ||||
| 							while (!bre.eof()) | ||||
| 							{ | ||||
| 								uint8_t length = bre.read_8()*2; | ||||
| 								if (length == 0) | ||||
| 								{ | ||||
| 									/* Literal block */ | ||||
|                         case TD0_ENCODING_RLE: | ||||
|                         { | ||||
|                             ByteWriter bw(data); | ||||
|                             while (!bre.eof()) | ||||
|                             { | ||||
|                                 uint8_t length = bre.read_8() * 2; | ||||
|                                 if (length == 0) | ||||
|                                 { | ||||
|                                     /* Literal block */ | ||||
|  | ||||
| 									length = bre.read_8(); | ||||
| 									bw += bre.read(length); | ||||
| 								} | ||||
| 								else | ||||
| 								{ | ||||
| 									/* Repeated block */ | ||||
|                                     length = bre.read_8(); | ||||
|                                     bw += bre.read(length); | ||||
|                                 } | ||||
|                                 else | ||||
|                                 { | ||||
|                                     /* Repeated block */ | ||||
|  | ||||
| 									uint8_t count = bre.read_8(); | ||||
| 									Bytes b = bre.read(length); | ||||
| 									while (count--) | ||||
| 										bw += b; | ||||
| 								} | ||||
| 							} | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|                                     uint8_t count = bre.read_8(); | ||||
|                                     Bytes b = bre.read(length); | ||||
|                                     while (count--) | ||||
|                                         bw += b; | ||||
|                                 } | ||||
|                             } | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| 				const auto& sector = image->put(logicalTrack, logicalSide, sectorId); | ||||
| 				sector->status = Sector::OK; | ||||
| 				sector->physicalCylinder = physicalCylinder; | ||||
| 				sector->physicalHead = physicalHead; | ||||
| 				sector->data = data.slice(0, sectorSize); | ||||
| 				totalSize += sectorSize; | ||||
| 			} | ||||
| 		} | ||||
|                 const auto& sector = | ||||
|                     image->put(logicalTrack, logicalSide, sectorId); | ||||
|                 sector->status = Sector::OK; | ||||
|                 sector->physicalCylinder = physicalCylinder; | ||||
|                 sector->physicalHead = physicalHead; | ||||
|                 sector->data = data.slice(0, sectorSize); | ||||
|                 totalSize += sectorSize; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		image->calculateSize(); | ||||
| 		const Geometry& geometry = image->getGeometry(); | ||||
|         std::cout << fmt::format("TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, {} kB total\n", | ||||
|                         geometry.numTracks, geometry.numSides, geometry.numSectors, | ||||
| 						geometry.sectorSize, | ||||
| 						totalSize / 1024); | ||||
|         image->calculateSize(); | ||||
|         const Geometry& geometry = image->getGeometry(); | ||||
|         Logger() << fmt::format( | ||||
|             "TD0: found {} tracks, {} sides, {} sectors, {} bytes per sector, " | ||||
|             "{} kB total", | ||||
|             geometry.numTracks, | ||||
|             geometry.numSides, | ||||
|             geometry.numSectors, | ||||
|             geometry.sectorSize, | ||||
|             totalSize / 1024); | ||||
|         return image; | ||||
| 	} | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<ImageReader> ImageReader::createTd0ImageReader(const ImageReaderProto& config) | ||||
| std::unique_ptr<ImageReader> ImageReader::createTd0ImageReader( | ||||
|     const ImageReaderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<ImageReader>(new Td0ImageReader(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "fmt/format.h" | ||||
| #include "image.h" | ||||
| #include "ldbs.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -30,7 +31,7 @@ public: | ||||
|  | ||||
| 	void writeImage(const Image& image) | ||||
| 	{ | ||||
| 		std::cout << "writing D64 triangular image\n"; | ||||
| 		Logger() << "D64: writing triangular image"; | ||||
|  | ||||
| 		std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary); | ||||
| 		if (!outputFile.is_open()) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "fmt/format.h" | ||||
| #include "ldbs.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -52,8 +53,8 @@ public: | ||||
| 				Error() << "this image is not compatible with the DiskCopy 4.2 format"; | ||||
| 		} | ||||
|  | ||||
| 		std::cout << "writing DiskCopy 4.2 image\n" | ||||
| 		          << fmt::format("{} tracks, {} sides, {} sectors, {} bytes per sector; {}\n", | ||||
| 		Logger() << "DC42: writing DiskCopy 4.2 image" | ||||
| 		          << fmt::format("DC42: {} tracks, {} sides, {} sectors, {} bytes per sector; {}", | ||||
| 				  		geometry.numTracks, geometry.numSides, geometry.numSectors, geometry.sectorSize, | ||||
| 						mfm ? "MFM" : "GCR"); | ||||
|  | ||||
|   | ||||
| @@ -40,27 +40,27 @@ std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config) | ||||
|  | ||||
| void ImageWriter::updateConfigForFilename(ImageWriterProto* proto, const std::string& filename) | ||||
| { | ||||
| 	static const std::map<std::string, std::function<void(void)>> formats = | ||||
| 	static const std::map<std::string, std::function<void(ImageWriterProto*)>> formats = | ||||
| 	{ | ||||
| 		{".adf",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".d64",      [&]() { proto->mutable_d64(); }}, | ||||
| 		{".d81",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".diskcopy", [&]() { proto->mutable_diskcopy(); }}, | ||||
| 		{".dsk",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".img",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".ldbs",     [&]() { proto->mutable_ldbs(); }}, | ||||
| 		{".nsi",      [&]() { proto->mutable_nsi(); }}, | ||||
| 		{".raw",      [&]() { proto->mutable_raw(); }}, | ||||
| 		{".st",       [&]() { proto->mutable_img(); }}, | ||||
| 		{".vgi",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [&]() { proto->mutable_img(); }}, | ||||
| 		{".adf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".d64",      [](auto* proto) { proto->mutable_d64(); }}, | ||||
| 		{".d81",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }}, | ||||
| 		{".dsk",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".img",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".ldbs",     [](auto* proto) { proto->mutable_ldbs(); }}, | ||||
| 		{".nsi",      [](auto* proto) { proto->mutable_nsi(); }}, | ||||
| 		{".raw",      [](auto* proto) { proto->mutable_raw(); }}, | ||||
| 		{".st",       [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".vgi",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 		{".xdf",      [](auto* proto) { proto->mutable_img(); }}, | ||||
| 	}; | ||||
|  | ||||
| 	for (const auto& it : formats) | ||||
| 	{ | ||||
| 		if (endsWith(filename, it.first)) | ||||
| 		{ | ||||
| 			it.second(); | ||||
| 			it.second(proto); | ||||
| 			proto->set_filename(filename); | ||||
| 			return; | ||||
| 		} | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "lib/config.pb.h" | ||||
| #include "imginputoutpututils.h" | ||||
| #include "fmt/format.h" | ||||
| #include "logger.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
| @@ -56,7 +57,7 @@ public: | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		std::cout << fmt::format("IMG: wrote {} tracks, {} sides, {} kB total\n", | ||||
| 		Logger() << fmt::format("IMG: wrote {} tracks, {} sides, {} kB total", | ||||
| 						tracks, sides, | ||||
| 						outputFile.tellp() / 1024); | ||||
| 	} | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "fmt/format.h" | ||||
| #include "ldbs.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include <algorithm> | ||||
| #include <iostream> | ||||
| @@ -23,10 +24,9 @@ public: | ||||
|  | ||||
| 		const Geometry geometry = image.getGeometry(); | ||||
|  | ||||
| 		std::cout << fmt::format("LDBS: writing {} tracks, {} sides, {} sectors, {} bytes per sector", | ||||
| 		Logger() << fmt::format("LDBS: writing {} tracks, {} sides, {} sectors, {} bytes per sector", | ||||
| 						geometry.numTracks, geometry.numSides, geometry.numSectors, | ||||
| 						geometry.sectorSize) | ||||
| 				<< std::endl; | ||||
| 						geometry.sectorSize); | ||||
|  | ||||
|         Bytes trackDirectory; | ||||
|         ByteWriter trackDirectoryWriter(trackDirectory); | ||||
| @@ -39,14 +39,14 @@ public: | ||||
| 			dataRate = (geometry.numSectors > 10) ? LDBSOutputProto::RATE_HD : LDBSOutputProto::RATE_DD; | ||||
| 			if (geometry.sectorSize <= 256) | ||||
| 				dataRate = LDBSOutputProto::RATE_SD; | ||||
| 			std::cout << fmt::format("LDBS: guessing data rate as {}\n", LDBSOutputProto::DataRate_Name(dataRate)); | ||||
| 			Logger() << fmt::format("LDBS: guessing data rate as {}", LDBSOutputProto::DataRate_Name(dataRate)); | ||||
| 		} | ||||
|  | ||||
| 		LDBSOutputProto::RecordingMode recordingMode = _config.ldbs().recording_mode(); | ||||
| 		if (recordingMode == LDBSOutputProto::RECMODE_GUESS) | ||||
| 		{ | ||||
| 			recordingMode = LDBSOutputProto::RECMODE_MFM; | ||||
| 			std::cout << fmt::format("LDBS: guessing recording mode as {}\n", LDBSOutputProto::RecordingMode_Name(recordingMode)); | ||||
| 			Logger() << fmt::format("LDBS: guessing recording mode as {}", LDBSOutputProto::RecordingMode_Name(recordingMode)); | ||||
| 		} | ||||
|  | ||||
| 		for (int track = 0; track < geometry.numTracks; track++) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "fmt/format.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "lib/imagewriter/imagewriter.pb.h" | ||||
| #include <algorithm> | ||||
| @@ -26,15 +27,14 @@ public: | ||||
| 		size_t trackSize = geometry.numSectors * geometry.sectorSize; | ||||
|  | ||||
| 		if (geometry.numTracks * trackSize == 0) { | ||||
| 			std::cout << "No sectors in output; skipping .nsi image file generation." << std::endl; | ||||
| 			Logger() << "No sectors in output; skipping .nsi image file generation."; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		std::cout << fmt::format("Writing {} cylinders, {} sides, {} sectors, {} ({} bytes/sector), {} kB total", | ||||
| 		Logger() << fmt::format("Writing {} cylinders, {} sides, {} sectors, {} ({} bytes/sector), {} kB total", | ||||
| 				geometry.numTracks, geometry.numSides, | ||||
| 				geometry.numSectors, geometry.sectorSize == 256 ? "SD" : "DD", geometry.sectorSize, | ||||
| 				geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024) | ||||
| 				<< std::endl; | ||||
| 				geometry.numTracks * geometry.numSides * geometry.numSectors * geometry.sectorSize / 1024); | ||||
|  | ||||
| 		std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary); | ||||
| 		if (!outputFile.is_open()) | ||||
| @@ -69,7 +69,7 @@ public: | ||||
| 						char fill[256]; | ||||
| 						memset(fill, ' ', sizeof(fill)); | ||||
| 						if (mixedDensity == false) { | ||||
| 							std::cout << "Warning: Disk contains mixed single/double-density sectors." << std::endl; | ||||
| 							Logger() << "Warning: Disk contains mixed single/double-density sectors."; | ||||
| 						} | ||||
| 						mixedDensity = true; | ||||
| 						sector->data.slice(0, 256).writeTo(outputFile); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "fmt/format.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "arch/northstar/northstar.h" | ||||
| #include "lib/imagewriter/imagewriter.pb.h" | ||||
| #include <algorithm> | ||||
| @@ -25,11 +26,11 @@ public: | ||||
| 		size_t trackSize = geometry.numSectors * geometry.sectorSize; | ||||
|  | ||||
| 		if (geometry.numTracks * trackSize == 0) { | ||||
| 			std::cout << "RAW: no sectors in output; skipping image file generation." << std::endl; | ||||
| 			Logger() << "RAW: no sectors in output; skipping image file generation."; | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		std::cout << fmt::format("RAW: writing {} cylinders, {} sides\n", | ||||
| 		Logger() << fmt::format("RAW: writing {} cylinders, {} sides", | ||||
| 				geometry.numTracks, geometry.numSides); | ||||
|  | ||||
| 		std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary); | ||||
|   | ||||
| @@ -31,11 +31,12 @@ std::string Logger::toString(const AnyLogMessage& message) | ||||
| { | ||||
|     std::stringstream stream; | ||||
|  | ||||
| 	auto indent = [&]() { | ||||
| 		if (!indented) | ||||
| 			stream << "      "; | ||||
| 		indented = false; | ||||
| 	}; | ||||
|     auto indent = [&]() | ||||
|     { | ||||
|         if (!indented) | ||||
|             stream << "      "; | ||||
|         indented = false; | ||||
|     }; | ||||
|  | ||||
|     std::visit( | ||||
|         overloaded{ | ||||
| @@ -58,20 +59,29 @@ std::string Logger::toString(const AnyLogMessage& message) | ||||
|                     60e9 / m.rotationalPeriod); | ||||
|             }, | ||||
|  | ||||
| 			/* Indicates that we're working on a given cylinder and head */ | ||||
|             [&](const DiskContextLogMessage& m) | ||||
|             /* Indicates that we're starting a write operation. */ | ||||
|             [&](const BeginWriteOperationLogMessage& m) | ||||
|             { | ||||
|                 stream << fmt::format("{:2}.{}: ", m.cylinder, m.head); | ||||
|                 indented = true; | ||||
|             }, | ||||
|  | ||||
|             /* A single read has happened */ | ||||
|             [&](const SingleReadLogMessage& m) | ||||
|             /* Indicates that we're starting a read operation. */ | ||||
|             [&](const BeginReadOperationLogMessage& m) | ||||
|             { | ||||
|                 const auto& trackdataflux = m.trackDataFlux; | ||||
|                 stream << fmt::format("{:2}.{}: ", m.cylinder, m.head); | ||||
|                 indented = true; | ||||
|             }, | ||||
|  | ||||
|             /* We've just read a track (we might reread it if there are errors) | ||||
|              */ | ||||
|             [&](const TrackReadLogMessage& m) | ||||
|             { | ||||
|                 const auto& track = *m.track; | ||||
|                 const auto& trackdataflux = track.trackDatas.end()[-1]; | ||||
|  | ||||
|                 indent(); | ||||
|                 stream << fmt::format("{} records, {} sectors", | ||||
|                 stream << fmt::format("{} raw records, {} raw sectors", | ||||
|                     trackdataflux->records.size(), | ||||
|                     trackdataflux->sectors.size()); | ||||
|                 if (trackdataflux->sectors.size() > 0) | ||||
| @@ -89,14 +99,9 @@ std::string Logger::toString(const AnyLogMessage& message) | ||||
|                 stream << "sectors:"; | ||||
|  | ||||
|                 std::vector<std::shared_ptr<const Sector>> sectors( | ||||
|                     m.sectors.begin(), m.sectors.end()); | ||||
|                 std::sort(sectors.begin(), | ||||
|                     sectors.end(), | ||||
|                     [](const std::shared_ptr<const Sector>& s1, | ||||
|                         const std::shared_ptr<const Sector>& s2) | ||||
|                     { | ||||
|                         return s1->logicalSector < s2->logicalSector; | ||||
|                     }); | ||||
|                     track.sectors.begin(), track.sectors.end()); | ||||
|                 std::sort( | ||||
|                     sectors.begin(), sectors.end(), sectorPointerSortPredicate); | ||||
|  | ||||
|                 for (const auto& sector : sectors) | ||||
|                     stream << fmt::format(" {}{}", | ||||
| @@ -104,11 +109,7 @@ std::string Logger::toString(const AnyLogMessage& message) | ||||
|                         Sector::statusToChar(sector->status)); | ||||
|  | ||||
|                 stream << '\n'; | ||||
|             }, | ||||
|  | ||||
|             /* We've finished reading a track */ | ||||
|             [&](const TrackReadLogMessage& m) | ||||
|             { | ||||
|                 int size = 0; | ||||
|                 std::set<std::pair<int, int>> track_ids; | ||||
|                 for (const auto& sector : m.track->sectors) | ||||
|   | ||||
							
								
								
									
										58
									
								
								lib/logger.h
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								lib/logger.h
									
									
									
									
									
								
							| @@ -3,50 +3,70 @@ | ||||
|  | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| class DiskFlux; | ||||
| class TrackDataFlux; | ||||
| class TrackFlux; | ||||
| class Sector; | ||||
|  | ||||
| struct BeginSpeedOperationLogMessage {}; | ||||
| struct ErrorLogMessage | ||||
| { | ||||
| 	std::string message; | ||||
| }; | ||||
|  | ||||
| struct BeginSpeedOperationLogMessage | ||||
| { | ||||
| }; | ||||
|  | ||||
| struct EndSpeedOperationLogMessage | ||||
| { | ||||
| 	nanoseconds_t rotationalPeriod; | ||||
|     nanoseconds_t rotationalPeriod; | ||||
| }; | ||||
| 	 | ||||
| struct DiskContextLogMessage | ||||
|  | ||||
| struct TrackReadLogMessage | ||||
| { | ||||
|     std::shared_ptr<const TrackFlux> track; | ||||
| }; | ||||
|  | ||||
| struct DiskReadLogMessage | ||||
| { | ||||
| 	std::shared_ptr<const DiskFlux> disk; | ||||
| }; | ||||
|  | ||||
| struct BeginReadOperationLogMessage | ||||
| { | ||||
|     unsigned cylinder; | ||||
|     unsigned head; | ||||
| }; | ||||
|  | ||||
| struct SingleReadLogMessage | ||||
| struct EndReadOperationLogMessage | ||||
| { | ||||
| 	std::shared_ptr<const TrackDataFlux> trackDataFlux; | ||||
| 	std::set<std::shared_ptr<const Sector>> sectors; | ||||
| }; | ||||
|  | ||||
| struct TrackReadLogMessage | ||||
| struct BeginWriteOperationLogMessage | ||||
| { | ||||
| 	std::shared_ptr<TrackFlux> track; | ||||
|     unsigned cylinder; | ||||
|     unsigned head; | ||||
| }; | ||||
|  | ||||
| struct BeginReadOperationLogMessage { }; | ||||
| struct EndReadOperationLogMessage { }; | ||||
| struct BeginWriteOperationLogMessage { }; | ||||
| struct EndWriteOperationLogMessage { }; | ||||
| struct EndWriteOperationLogMessage | ||||
| { | ||||
| }; | ||||
|  | ||||
| class TrackFlux; | ||||
|  | ||||
| typedef std::variant<std::string, | ||||
| 	SingleReadLogMessage, | ||||
| 	TrackReadLogMessage, | ||||
|     DiskContextLogMessage, | ||||
| 	BeginSpeedOperationLogMessage, | ||||
| 	EndSpeedOperationLogMessage, | ||||
| typedef std::variant< | ||||
| 	std::string, | ||||
| 	ErrorLogMessage, | ||||
|     TrackReadLogMessage, | ||||
| 	DiskReadLogMessage, | ||||
|     BeginSpeedOperationLogMessage, | ||||
|     EndSpeedOperationLogMessage, | ||||
|     BeginReadOperationLogMessage, | ||||
|     EndReadOperationLogMessage, | ||||
| 	BeginWriteOperationLogMessage, | ||||
| 	EndWriteOperationLogMessage> | ||||
|     BeginWriteOperationLogMessage, | ||||
|     EndWriteOperationLogMessage> | ||||
|     AnyLogMessage; | ||||
|  | ||||
| class Logger | ||||
|   | ||||
| @@ -15,24 +15,21 @@ | ||||
| #include "logger.h" | ||||
| #include "fmt/format.h" | ||||
| #include "proto.h" | ||||
| #include "utils.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include <iostream> | ||||
| #include <fstream> | ||||
|  | ||||
| static std::unique_ptr<FluxSink> outputFluxSink; | ||||
|  | ||||
| static std::shared_ptr<Fluxmap> readFluxmap( | ||||
|     FluxSource& fluxsource, unsigned cylinder, unsigned head) | ||||
| static std::shared_ptr<Fluxmap> readFluxmap(FluxSource& fluxsource, unsigned cylinder, unsigned head) | ||||
| { | ||||
|     Logger() << DiskContextLogMessage{cylinder, head} | ||||
|              << BeginReadOperationLogMessage(); | ||||
|     std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head); | ||||
|     fluxmap->rescale(1.0 / config.flux_source().rescale()); | ||||
|     Logger() << EndReadOperationLogMessage() | ||||
|              << fmt::format("{0:.0} ms in {1} bytes", | ||||
|                     fluxmap->duration() / 1e6, | ||||
|                     fluxmap->bytes()); | ||||
|     return fluxmap; | ||||
| 	Logger() << BeginReadOperationLogMessage { cylinder, head }; | ||||
| 	std::shared_ptr<Fluxmap> fluxmap = fluxsource.readFlux(cylinder, head); | ||||
| 	fluxmap->rescale(1.0/config.flux_source().rescale()); | ||||
| 	Logger() << EndReadOperationLogMessage() | ||||
| 			 << fmt::format("{0:.0} ms in {1} bytes", fluxmap->duration()/1e6, fluxmap->bytes()); | ||||
| 	return fluxmap; | ||||
| } | ||||
|  | ||||
| static bool conflictable(Sector::Status status) | ||||
| @@ -88,19 +85,24 @@ static std::set<std::shared_ptr<const Sector>> collect_sectors( | ||||
|     return sector_set; | ||||
| } | ||||
|  | ||||
| void readDiskCommand( | ||||
|     FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer) | ||||
| std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder) | ||||
| { | ||||
|     if (config.decoder().has_copy_flux_to()) | ||||
|         outputFluxSink = FluxSink::create(config.decoder().copy_flux_to()); | ||||
|  | ||||
|     auto diskflux = std::make_unique<DiskFlux>(); | ||||
|     auto diskflux = std::make_shared<DiskFlux>(); | ||||
|     bool failures = false; | ||||
|     for (int cylinder : iterate(config.cylinders())) | ||||
|     { | ||||
|         for (int head : iterate(config.heads())) | ||||
|         { | ||||
| 			testForEmergencyStop(); | ||||
|  | ||||
|             auto track = std::make_shared<TrackFlux>(); | ||||
| 			track->physicalCylinder = cylinder; | ||||
| 			track->physicalHead = head; | ||||
| 			diskflux->tracks.push_back(track); | ||||
|  | ||||
|             std::set<std::shared_ptr<const Sector>> track_sectors; | ||||
|             std::set<std::shared_ptr<const Record>> track_records; | ||||
|             Fluxmap totalFlux; | ||||
| @@ -141,7 +143,10 @@ void readDiskCommand( | ||||
|                     hasBadSectors = true; | ||||
|                 } | ||||
|  | ||||
|                 Logger() << SingleReadLogMessage{trackdataflux, result_sectors}; | ||||
| 				track->sectors = collect_sectors(result_sectors); | ||||
|  | ||||
| 				/* track can't be modified below this point. */ | ||||
|                 Logger() << TrackReadLogMessage { track }; | ||||
|  | ||||
|                 if (hasBadSectors) | ||||
|                     failures = false; | ||||
| @@ -192,39 +197,43 @@ void readDiskCommand( | ||||
|                     std::cout << std::endl; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             for (const auto& sector : collect_sectors(track_sectors)) | ||||
|                 track->sectors.insert(sector); | ||||
|  | ||||
|             Logger() << TrackReadLogMessage{track}; | ||||
|             diskflux->tracks.push_back(track); | ||||
|         } | ||||
| 		} | ||||
|     } | ||||
|  | ||||
|     std::set<std::shared_ptr<const Sector>> all_sectors; | ||||
|     for (auto& track : diskflux->tracks) | ||||
|         for (auto& sector : track->sectors) | ||||
|             all_sectors.insert(sector); | ||||
|     all_sectors = collect_sectors(all_sectors); | ||||
|     diskflux->image.reset(new Image(all_sectors)); | ||||
| 	if (failures) | ||||
| 		Logger() << "Warning: some sectors could not be decoded."; | ||||
|  | ||||
| 	std::set<std::shared_ptr<const Sector>> all_sectors; | ||||
| 	for (auto& track : diskflux->tracks) | ||||
| 		for (auto& sector : track->sectors) | ||||
| 			all_sectors.insert(sector); | ||||
| 	all_sectors = collect_sectors(all_sectors); | ||||
| 	diskflux->image = std::make_shared<Image>(all_sectors); | ||||
|  | ||||
| 	/* diskflux can't be modified below this point. */ | ||||
| 	Logger() << DiskReadLogMessage { diskflux }; | ||||
| 	return diskflux; | ||||
| } | ||||
|  | ||||
| void readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder, ImageWriter& writer) | ||||
| { | ||||
| 	auto diskflux = readDiskCommand(fluxsource, decoder); | ||||
|  | ||||
|     writer.printMap(*diskflux->image); | ||||
|     if (config.decoder().has_write_csv_to()) | ||||
|         writer.writeCsv(*diskflux->image, config.decoder().write_csv_to()); | ||||
|     writer.writeImage(*diskflux->image); | ||||
|  | ||||
|     if (failures) | ||||
|         std::cerr << "Warning: some sectors could not be decoded." << std::endl; | ||||
| } | ||||
|  | ||||
| void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink) | ||||
| { | ||||
|     for (int cylinder : iterate(config.cylinders())) | ||||
|     { | ||||
|         for (int head : iterate(config.heads())) | ||||
|         { | ||||
|             auto fluxmap = readFluxmap(fluxsource, cylinder, head); | ||||
|             fluxsink.writeFlux(cylinder, head, *fluxmap); | ||||
|         } | ||||
| 	for (int cylinder : iterate(config.cylinders())) | ||||
| 	{ | ||||
| 		for (int head : iterate(config.heads())) | ||||
| 		{ | ||||
| 			testForEmergencyStop(); | ||||
| 			auto fluxmap = readFluxmap(fluxsource, cylinder, head); | ||||
| 			fluxsink.writeFlux(cylinder, head, *fluxmap); | ||||
| 		} | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #define READER_H | ||||
|  | ||||
| class AbstractDecoder; | ||||
| class DiskFlux; | ||||
| class FluxSink; | ||||
| class FluxSource; | ||||
| class Fluxmap; | ||||
| @@ -10,6 +11,8 @@ class TrackDataFlux; | ||||
|  | ||||
| extern std::unique_ptr<TrackDataFlux> readAndDecodeTrack( | ||||
| 		FluxSource& source, AbstractDecoder& decoder, unsigned cylinder, unsigned head); | ||||
|  | ||||
| extern std::shared_ptr<const DiskFlux> readDiskCommand(FluxSource& fluxsource, AbstractDecoder& decoder); | ||||
| extern void readDiskCommand(FluxSource& source, AbstractDecoder& decoder, ImageWriter& writer); | ||||
| extern void rawReadDiskCommand(FluxSource& source, FluxSink& sink); | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
| #include "bytes.h" | ||||
| #include "proto.h" | ||||
| #include "usbfinder.h" | ||||
| #include "logger.h" | ||||
| #include "greaseweazle.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| @@ -17,7 +18,7 @@ USB::~USB() {} | ||||
|  | ||||
| static std::unique_ptr<CandidateDevice> selectDevice() | ||||
| { | ||||
|     auto candidates = findUsbDevices({FLUXENGINE_ID, GREASEWEAZLE_ID}); | ||||
|     auto candidates = findUsbDevices(); | ||||
|     if (candidates.size() == 0) | ||||
|         Error() << "no devices found (is one plugged in? Do you have the " | ||||
|                    "appropriate permissions?"; | ||||
| @@ -65,8 +66,8 @@ USB* get_usb_impl() | ||||
|         config.usb().greaseweazle().has_port()) | ||||
|     { | ||||
|         const auto& conf = config.usb().greaseweazle(); | ||||
|         std::cerr << fmt::format( | ||||
|             "Using GreaseWeazle on serial port {}\n", conf.port()); | ||||
|         Logger() << fmt::format( | ||||
|             "Using GreaseWeazle on serial port {}", conf.port()); | ||||
|         return createGreaseWeazleUsb(conf.port(), conf); | ||||
|     } | ||||
|  | ||||
| @@ -76,12 +77,12 @@ USB* get_usb_impl() | ||||
|     switch (candidate->id) | ||||
|     { | ||||
|         case FLUXENGINE_ID: | ||||
|             std::cerr << fmt::format( | ||||
|                 "Using FluxEngine {}\n", candidate->serial); | ||||
|             Logger() << fmt::format( | ||||
|                 "Using FluxEngine {}", candidate->serial); | ||||
|             return createFluxengineUsb(candidate->device); | ||||
|  | ||||
|         case GREASEWEAZLE_ID: | ||||
|             std::cerr << fmt::format("Using GreaseWeazle {} on {}\n", | ||||
|             Logger() << fmt::format("Using GreaseWeazle {} on {}", | ||||
|                 candidate->serial, | ||||
|                 candidate->serialPort); | ||||
|             return createGreaseWeazleUsb( | ||||
|   | ||||
| @@ -5,8 +5,11 @@ | ||||
| #include "fmt/format.h" | ||||
| #include "usbfinder.h" | ||||
| #include "greaseweazle.h" | ||||
| #include "protocol.h" | ||||
| #include "libusbp.hpp" | ||||
|  | ||||
| static const std::set<uint32_t> VALID_DEVICES = { GREASEWEAZLE_ID, FLUXENGINE_ID }; | ||||
|  | ||||
| static const std::string get_serial_number(const libusbp::device& device) | ||||
| { | ||||
|     try | ||||
| @@ -21,8 +24,7 @@ static const std::string get_serial_number(const libusbp::device& device) | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices( | ||||
|     const std::set<uint32_t>& ids) | ||||
| std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices() | ||||
| { | ||||
|     std::vector<std::unique_ptr<CandidateDevice>> candidates; | ||||
|     for (const auto& it : libusbp::list_connected_devices()) | ||||
| @@ -31,7 +33,7 @@ std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices( | ||||
|         candidate->device = it; | ||||
|  | ||||
|         uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); | ||||
|         if (ids.find(id) != ids.end()) | ||||
|         if (VALID_DEVICES.find(id) != VALID_DEVICES.end()) | ||||
|         { | ||||
|             candidate->id = id; | ||||
|             candidate->serial = get_serial_number(it); | ||||
|   | ||||
| @@ -12,7 +12,7 @@ struct CandidateDevice | ||||
| 	std::string serialPort; | ||||
| }; | ||||
|  | ||||
| extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(const std::set<uint32_t>& id); | ||||
| extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								lib/utils.cc
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								lib/utils.cc
									
									
									
									
									
								
							| @@ -1,4 +1,9 @@ | ||||
| #include "globals.h" | ||||
| #include "utils.h" | ||||
|  | ||||
| bool emergencyStop = false; | ||||
|  | ||||
| static const char* WHITESPACE = " \t\n\r\f\v"; | ||||
|  | ||||
| bool beginsWith(const std::string& value, const std::string& ending) | ||||
| { | ||||
| @@ -18,4 +23,25 @@ bool endsWith(const std::string& value, const std::string& ending) | ||||
|         std::equal(ending.rbegin(), ending.rend(), lowercase.begin()); | ||||
| } | ||||
|  | ||||
| void leftTrimWhitespace(std::string& value) | ||||
| { | ||||
| 	value.erase(0, value.find_first_not_of(WHITESPACE)); | ||||
| } | ||||
|  | ||||
| void rightTrimWhitespace(std::string& value) | ||||
| { | ||||
| 	value.erase(value.find_last_not_of(WHITESPACE) + 1); | ||||
| } | ||||
|  | ||||
| void trimWhitespace(std::string& value) | ||||
| { | ||||
| 	leftTrimWhitespace(value); | ||||
| 	rightTrimWhitespace(value); | ||||
| } | ||||
|  | ||||
| void testForEmergencyStop() | ||||
| { | ||||
| 	if (emergencyStop) | ||||
| 		throw EmergencyStopException(); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										10
									
								
								lib/utils.h
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								lib/utils.h
									
									
									
									
									
								
							| @@ -5,6 +5,16 @@ | ||||
|  | ||||
| extern bool beginsWith(const std::string& value, const std::string& beginning); | ||||
| extern bool endsWith(const std::string& value, const std::string& ending); | ||||
| extern void leftTrimWhitespace(std::string& value); | ||||
| extern void rightTrimWhitespace(std::string& value); | ||||
| extern void trimWhitespace(std::string& value); | ||||
|  | ||||
| /* If set, any running job will terminate as soon as possible (with an error). | ||||
|  */ | ||||
|  | ||||
| extern bool emergencyStop; | ||||
| class EmergencyStopException {}; | ||||
| extern void testForEmergencyStop(); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
| #include "sector.h" | ||||
| #include "image.h" | ||||
| #include "logger.h" | ||||
| #include "utils.h" | ||||
| #include "lib/config.pb.h" | ||||
| #include "proto.h" | ||||
|  | ||||
| @@ -23,9 +24,8 @@ void writeTracks(FluxSink& fluxSink, | ||||
|     { | ||||
|         for (unsigned head : iterate(config.heads())) | ||||
|         { | ||||
|             Logger() << DiskContextLogMessage { cylinder, head } | ||||
|                      << fmt::format("{0:>3}.{1}: writing", cylinder, head) | ||||
|                      << BeginWriteOperationLogMessage(); | ||||
|             testForEmergencyStop(); | ||||
|             Logger() << BeginWriteOperationLogMessage{cylinder, head}; | ||||
|  | ||||
|             std::unique_ptr<Fluxmap> fluxmap = producer(cylinder, head); | ||||
|             if (!fluxmap) | ||||
| @@ -44,8 +44,8 @@ void writeTracks(FluxSink& fluxSink, | ||||
|                 // fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2); | ||||
|                 fluxSink.writeFlux(cylinder, head, *fluxmap); | ||||
|                 Logger() << fmt::format("{0} ms in {1} bytes", | ||||
|                                 int(fluxmap->duration() / 1e6), | ||||
|                                 fluxmap->bytes()); | ||||
|                     int(fluxmap->duration() / 1e6), | ||||
|                     fluxmap->bytes()); | ||||
|             } | ||||
|             Logger() << EndWriteOperationLogMessage(); | ||||
|         } | ||||
| @@ -58,14 +58,13 @@ void writeTracksAndVerify(FluxSink& fluxSink, | ||||
|     AbstractDecoder& decoder, | ||||
|     const Image& image) | ||||
| { | ||||
|     std::cout << "Writing to: " << fluxSink << std::endl; | ||||
|     Logger() << fmt::format("Writing to: {}", (std::string)fluxSink); | ||||
|  | ||||
|     for (unsigned cylinder : iterate(config.cylinders())) | ||||
|     { | ||||
|         for (unsigned head : iterate(config.heads())) | ||||
|         { | ||||
|             Logger() << DiskContextLogMessage { cylinder, head } | ||||
|                      << fmt::format("{0:>3}.{1}", cylinder, head); | ||||
|             testForEmergencyStop(); | ||||
|  | ||||
|             auto sectors = encoder.collectSectors(cylinder, head, image); | ||||
|             std::unique_ptr<Fluxmap> fluxmap = | ||||
| @@ -74,10 +73,11 @@ void writeTracksAndVerify(FluxSink& fluxSink, | ||||
|             { | ||||
|                 /* Erase this track rather than writing. */ | ||||
|  | ||||
|                 Logger() << BeginWriteOperationLogMessage() << "erasing"; | ||||
|                 Logger() << BeginWriteOperationLogMessage{cylinder, head}; | ||||
|                 fluxmap.reset(new Fluxmap()); | ||||
|                 fluxSink.writeFlux(cylinder, head, *fluxmap); | ||||
|                 Logger() << EndWriteOperationLogMessage(); | ||||
|                 Logger() << EndWriteOperationLogMessage() | ||||
|                          << fmt::format("erased"); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| @@ -91,18 +91,18 @@ void writeTracksAndVerify(FluxSink& fluxSink, | ||||
|                      * let's leave it disabled for now. */ | ||||
|                     // fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, | ||||
|                     // 2); | ||||
|                     Logger() << BeginWriteOperationLogMessage() << "writing"; | ||||
|                     Logger() << BeginWriteOperationLogMessage{cylinder, head}; | ||||
|                     fluxSink.writeFlux(cylinder, head, *fluxmap); | ||||
|                     Logger() << EndWriteOperationLogMessage() | ||||
|                              << fmt::format("{0} ms in {1} bytes", | ||||
|                              << fmt::format("writing {0} ms in {1} bytes", | ||||
|                                     int(fluxmap->duration() / 1e6), | ||||
|                                     fluxmap->bytes()); | ||||
|  | ||||
|                     Logger() << "verifying" << BeginReadOperationLogMessage(); | ||||
|                     Logger() << BeginReadOperationLogMessage{cylinder, head}; | ||||
|                     std::shared_ptr<Fluxmap> writtenFluxmap = | ||||
|                         fluxSource.readFlux(cylinder, head); | ||||
|                     Logger() << EndReadOperationLogMessage() | ||||
|                              << fmt::format("{0} ms in {1} bytes", | ||||
|                              << fmt::format("verifying {0} ms in {1} bytes", | ||||
|                                     int(writtenFluxmap->duration() / 1e6), | ||||
|                                     writtenFluxmap->bytes()); | ||||
|  | ||||
| @@ -110,7 +110,7 @@ void writeTracksAndVerify(FluxSink& fluxSink, | ||||
|                         decoder.decodeToSectors(writtenFluxmap, cylinder, head); | ||||
|  | ||||
|                     std::vector<std::shared_ptr<const Sector>> gotSectors( | ||||
| 						trackdata->sectors.begin(), trackdata->sectors.end()); | ||||
|                         trackdata->sectors.begin(), trackdata->sectors.end()); | ||||
|                     gotSectors.erase(std::remove_if(gotSectors.begin(), | ||||
|                                          gotSectors.end(), | ||||
|                                          [](const auto& s) | ||||
| @@ -173,6 +173,8 @@ void writeDiskCommand(const Image& image, | ||||
|             { | ||||
|                 const auto& sectors = | ||||
|                     encoder.collectSectors(physicalTrack, physicalSide, image); | ||||
| 				if (sectors.empty()) | ||||
| 					return std::make_unique<Fluxmap>(); | ||||
|                 return encoder.encode( | ||||
|                     physicalTrack, physicalSide, sectors, image); | ||||
|             }); | ||||
|   | ||||
							
								
								
									
										25
									
								
								mkninja.sh
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								mkninja.sh
									
									
									
									
									
								
							| @@ -45,7 +45,7 @@ rule link | ||||
|  | ||||
| rule linkgui | ||||
|     command = $CXX $LDFLAGS $GUILDFLAGS -o \$out \$in \$flags $LIBS $GUILIBS | ||||
|     description = LINK-OBJC \$in | ||||
|     description = LINK-GUI \$in | ||||
|  | ||||
| rule test | ||||
|     command = \$in && touch \$out | ||||
| @@ -402,6 +402,7 @@ buildlibrary libbackend.a \ | ||||
|     arch/amiga/decoder.cc \ | ||||
|     arch/amiga/encoder.cc \ | ||||
|     arch/apple2/decoder.cc \ | ||||
|     arch/apple2/encoder.cc \ | ||||
|     arch/brother/decoder.cc \ | ||||
|     arch/brother/encoder.cc \ | ||||
|     arch/c64/decoder.cc \ | ||||
| @@ -528,6 +529,7 @@ FORMATS="\ | ||||
|     northstar175 \ | ||||
|     northstar350 \ | ||||
|     northstar87 \ | ||||
|     rx50 \ | ||||
|     tids990 \ | ||||
|     vgi \ | ||||
|     victor9k_ss \ | ||||
| @@ -564,6 +566,15 @@ buildlibrary libfrontend.a \ | ||||
|     src/fe-write.cc \ | ||||
|     src/fluxengine.cc \ | ||||
|  | ||||
| buildlibrary libgui.a \ | ||||
|     -I$OBJDIR/proto \ | ||||
|     -Idep/libusbp/include \ | ||||
|     -d $OBJDIR/proto/libconfig.def \ | ||||
|     src/gui/main.cc \ | ||||
|     src/gui/layout.cpp \ | ||||
|     src/gui/visualisation.cc \ | ||||
|     src/gui/mainwindow.cc \ | ||||
|  | ||||
| buildprogram fluxengine \ | ||||
|     libfrontend.a \ | ||||
|     libformats.a \ | ||||
| @@ -574,6 +585,16 @@ buildprogram fluxengine \ | ||||
|     libfmt.a \ | ||||
|     libagg.a \ | ||||
|  | ||||
| buildprogram fluxengine-gui \ | ||||
|     -rule linkgui \ | ||||
|     libgui.a \ | ||||
|     libformats.a \ | ||||
|     libbackend.a \ | ||||
|     libconfig.a \ | ||||
|     libfl2.a \ | ||||
|     libusbp.a \ | ||||
|     libfmt.a \ | ||||
|  | ||||
| buildlibrary libemu.a \ | ||||
|     dep/emu/fnmatch.c | ||||
|  | ||||
| @@ -626,6 +647,7 @@ runtest proto-test          -I$OBJDIR/proto \ | ||||
|                             $OBJDIR/proto/tests/testproto.cc | ||||
|  | ||||
| encodedecodetest amiga | ||||
| encodedecodetest apple2 | ||||
| encodedecodetest atarist360 | ||||
| encodedecodetest atarist370 | ||||
| encodedecodetest atarist400 | ||||
| @@ -649,6 +671,7 @@ encodedecodetest ibm720_525 | ||||
| encodedecodetest mac400 scripts/mac400_test.textpb | ||||
| encodedecodetest mac800 scripts/mac800_test.textpb | ||||
| encodedecodetest n88basic | ||||
| encodedecodetest rx50 | ||||
| encodedecodetest tids990 | ||||
| encodedecodetest victor9k_ss | ||||
| encodedecodetest victor9k_ds | ||||
|   | ||||
| @@ -141,7 +141,17 @@ int main(int argc, const char* argv[]) | ||||
|     for (Command& c : commands) | ||||
|     { | ||||
|         if (command == c.name) | ||||
|             return c.main(argc-1, argv+1); | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				return c.main(argc-1, argv+1); | ||||
| 			} | ||||
| 			catch (const ErrorException& e) | ||||
| 			{ | ||||
| 				std::cerr << e.message << '\n'; | ||||
| 				exit(1); | ||||
| 			} | ||||
| 		} | ||||
|     } | ||||
|  | ||||
|     std::cerr << "fluxengine: unrecognised command (try --help)\n"; | ||||
|   | ||||
| @@ -1,4 +1,19 @@ | ||||
| comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD (ro)' | ||||
| comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD' | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "apple2.img" | ||||
| 	img { | ||||
| 		tracks: 40 | ||||
| 		sides: 1 | ||||
| 		trackdata { | ||||
| 			sector_size: 256 | ||||
| 			sector_range { | ||||
| 				start_sector: 0 | ||||
| 				sector_count: 16 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "apple2.img" | ||||
| @@ -9,6 +24,10 @@ decoder { | ||||
| 	apple2 {} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	apple2 {} | ||||
| } | ||||
|  | ||||
| cylinders { | ||||
| 	start: 0 | ||||
| 	end: 79 | ||||
|   | ||||
| @@ -5,6 +5,7 @@ image_reader { | ||||
| 	img { | ||||
| 		tracks: 39 | ||||
| 		sides: 1 | ||||
| 		physical_step: 2 | ||||
| 		trackdata { | ||||
| 			sector_size: 256 | ||||
| 			sector_range { | ||||
| @@ -20,6 +21,7 @@ image_writer { | ||||
| 	img { | ||||
| 		tracks: 39 | ||||
| 		sides: 1 | ||||
| 		physical_step: 2 | ||||
| 		trackdata { | ||||
| 			sector_size: 256 | ||||
| 			sector_range { | ||||
| @@ -37,12 +39,13 @@ encoder { | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	retries: 1 | ||||
| 	brother {} | ||||
| } | ||||
|  | ||||
| cylinders { | ||||
| 	start: 0 | ||||
| 	end: 39 | ||||
| 	end: 77 | ||||
| } | ||||
|  | ||||
| heads { | ||||
|   | ||||
							
								
								
									
										77
									
								
								src/formats/rx50.textpb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/formats/rx50.textpb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| comment: 'Digital RX50 400kB 5.25" 80-track 10-sector SSQD' | ||||
|  | ||||
| flux_sink { | ||||
| 	drive { | ||||
| 		high_density: true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| flux_source { | ||||
| 	drive { | ||||
| 		high_density: true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| image_reader { | ||||
| 	filename: "rx50.img" | ||||
| 	img { | ||||
| 		tracks: 80 | ||||
| 		sides: 1 | ||||
| 		trackdata { | ||||
| 			sector_size: 512 | ||||
| 			sector_range { | ||||
| 				start_sector: 1 | ||||
| 				sector_count: 10 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| image_writer { | ||||
| 	filename: "rx50.img" | ||||
| 	img {} | ||||
| } | ||||
|  | ||||
| encoder { | ||||
| 	ibm { | ||||
| 		trackdata { | ||||
| 			track_length_ms: 167 | ||||
| 			clock_rate_khz: 300 | ||||
| 			gap3: 30 | ||||
| 			sectors { | ||||
| 				sector: 1 | ||||
| 				sector: 2 | ||||
| 				sector: 3 | ||||
| 				sector: 4 | ||||
| 				sector: 5 | ||||
| 				sector: 6 | ||||
| 				sector: 7 | ||||
| 				sector: 8 | ||||
| 				sector: 9 | ||||
| 				sector: 10 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| decoder { | ||||
| 	ibm { | ||||
| 		trackdata { | ||||
| 			sector_range { | ||||
| 				min_sector: 1 | ||||
| 				max_sector: 10 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| cylinders { | ||||
| 	start: 0 | ||||
| 	end: 79 | ||||
| } | ||||
|  | ||||
| heads { | ||||
| 	start: 0 | ||||
| 	end: 0 | ||||
| } | ||||
|  | ||||
							
								
								
									
										46
									
								
								src/gui/gui.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/gui/gui.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #ifndef RENDEZVOUS_H | ||||
| #define RENDEZVOUS_H | ||||
|  | ||||
| #include <wx/wx.h> | ||||
|  | ||||
| class ExecEvent; | ||||
| class MainWindow; | ||||
|  | ||||
| extern void runOnUiThread(std::function<void()> callback); | ||||
| extern void runOnWorkerThread(std::function<void()> callback); | ||||
|  | ||||
| template <typename R> | ||||
| static inline R runOnUiThread(std::function<R()> callback) | ||||
| { | ||||
| 	R retvar; | ||||
| 	runOnUiThread( | ||||
| 		[&]() { | ||||
| 			retvar = callback(); | ||||
| 		} | ||||
| 	); | ||||
| 	return retvar; | ||||
| } | ||||
|  | ||||
| class FluxEngineApp : public wxApp, public wxThreadHelper | ||||
| { | ||||
| public: | ||||
|     virtual bool OnInit(); | ||||
| 	void RunOnWorkerThread(std::function<void()> callback); | ||||
|  | ||||
| private: | ||||
| 	void OnExec(const ExecEvent& event); | ||||
|  | ||||
| public: | ||||
| 	bool IsWorkerThreadRunning() const; | ||||
|  | ||||
| protected: | ||||
| 	virtual wxThread::ExitCode Entry(); | ||||
|  | ||||
| private: | ||||
| 	std::function<void()> _callback; | ||||
| 	MainWindow* _mainWindow; | ||||
| }; | ||||
| wxDECLARE_APP(FluxEngineApp); | ||||
|  | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										187
									
								
								src/gui/layout.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/gui/layout.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| /////////////////////////////////////////////////////////////////////////// | ||||
| // C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) | ||||
| // http://www.wxformbuilder.org/ | ||||
| // | ||||
| // PLEASE DO *NOT* EDIT THIS FILE! | ||||
| /////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| #include "layout.h" | ||||
|  | ||||
| /////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) | ||||
| { | ||||
| 	this->SetSizeHints( wxSize( 450,500 ), wxDefaultSize ); | ||||
| 	this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) ); | ||||
|  | ||||
| 	bSizer1 = new wxFlexGridSizer( 0, 2, 0, 0 ); | ||||
| 	bSizer1->AddGrowableCol( 1 ); | ||||
| 	bSizer1->AddGrowableRow( 0 ); | ||||
| 	bSizer1->SetFlexibleDirection( wxHORIZONTAL ); | ||||
| 	bSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); | ||||
|  | ||||
| 	wxFlexGridSizer* fgSizer4; | ||||
| 	fgSizer4 = new wxFlexGridSizer( 2, 1, 0, 0 ); | ||||
| 	fgSizer4->AddGrowableRow( 0 ); | ||||
| 	fgSizer4->SetFlexibleDirection( wxBOTH ); | ||||
| 	fgSizer4->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); | ||||
|  | ||||
| 	visualiser = new VisualisationControl( this, wxID_ANY, wxDefaultPosition, wxSize( 200,480 ), wxBORDER_THEME ); | ||||
| 	visualiser->SetMinSize( wxSize( 200,480 ) ); | ||||
|  | ||||
| 	fgSizer4->Add( visualiser, 1, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
| 	stopButton = new wxButton( this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	fgSizer4->Add( stopButton, 0, wxALIGN_CENTER|wxALL, 5 ); | ||||
|  | ||||
|  | ||||
| 	bSizer1->Add( fgSizer4, 1, wxEXPAND, 5 ); | ||||
|  | ||||
| 	wxFlexGridSizer* fgSizer2; | ||||
| 	fgSizer2 = new wxFlexGridSizer( 0, 1, 0, 0 ); | ||||
| 	fgSizer2->AddGrowableCol( 0 ); | ||||
| 	fgSizer2->AddGrowableRow( 1 ); | ||||
| 	fgSizer2->SetFlexibleDirection( wxVERTICAL ); | ||||
| 	fgSizer2->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL ); | ||||
|  | ||||
| 	wxFlexGridSizer* fgSizer3; | ||||
| 	fgSizer3 = new wxFlexGridSizer( 0, 2, 0, 0 ); | ||||
| 	fgSizer3->AddGrowableCol( 1 ); | ||||
| 	fgSizer3->SetFlexibleDirection( wxBOTH ); | ||||
| 	fgSizer3->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); | ||||
|  | ||||
| 	m_staticText4 = new wxStaticText( this, wxID_ANY, wxT("Device:"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_staticText4->Wrap( -1 ); | ||||
| 	fgSizer3->Add( m_staticText4, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 ); | ||||
|  | ||||
| 	deviceCombo = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_SORT ); | ||||
| 	fgSizer3->Add( deviceCombo, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
| 	m_staticText5 = new wxStaticText( this, wxID_ANY, wxT("Flux source/sink:"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_staticText5->Wrap( -1 ); | ||||
| 	fgSizer3->Add( m_staticText5, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 ); | ||||
|  | ||||
| 	fluxSourceSinkCombo = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); | ||||
| 	fluxSourceSinkCombo->Append( wxT("drive:0") ); | ||||
| 	fluxSourceSinkCombo->Append( wxT("drive:1") ); | ||||
| 	fgSizer3->Add( fluxSourceSinkCombo, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
| 	m_staticText51 = new wxStaticText( this, wxID_ANY, wxT("Format:"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_staticText51->Wrap( -1 ); | ||||
| 	fgSizer3->Add( m_staticText51, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 ); | ||||
|  | ||||
| 	wxArrayString formatChoiceChoices; | ||||
| 	formatChoice = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, formatChoiceChoices, wxCB_SORT ); | ||||
| 	formatChoice->SetSelection( 0 ); | ||||
| 	fgSizer3->Add( formatChoice, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
|  | ||||
| 	fgSizer3->Add( 0, 0, 1, wxEXPAND, 5 ); | ||||
|  | ||||
| 	highDensityToggle = new wxCheckBox( this, wxID_ANY, wxT("High density disk"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	fgSizer3->Add( highDensityToggle, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT|wxALL, 5 ); | ||||
|  | ||||
|  | ||||
| 	fgSizer2->Add( fgSizer3, 1, wxEXPAND, 5 ); | ||||
|  | ||||
| 	notebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_panel1 = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); | ||||
| 	wxFlexGridSizer* fgSizer5; | ||||
| 	fgSizer5 = new wxFlexGridSizer( 0, 2, 0, 0 ); | ||||
| 	fgSizer5->AddGrowableCol( 0 ); | ||||
| 	fgSizer5->AddGrowableRow( 0 ); | ||||
| 	fgSizer5->SetFlexibleDirection( wxBOTH ); | ||||
| 	fgSizer5->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); | ||||
|  | ||||
| 	additionalSettingsEntry = new wxTextCtrl( m_panel1, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE ); | ||||
| 	fgSizer5->Add( additionalSettingsEntry, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
|  | ||||
| 	m_panel1->SetSizer( fgSizer5 ); | ||||
| 	m_panel1->Layout(); | ||||
| 	fgSizer5->Fit( m_panel1 ); | ||||
| 	notebook->AddPage( m_panel1, wxT("Additional settings"), true ); | ||||
| 	m_panel2 = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); | ||||
| 	wxFlexGridSizer* fgSizer8; | ||||
| 	fgSizer8 = new wxFlexGridSizer( 0, 2, 0, 0 ); | ||||
| 	fgSizer8->AddGrowableCol( 0 ); | ||||
| 	fgSizer8->AddGrowableRow( 0 ); | ||||
| 	fgSizer8->SetFlexibleDirection( wxBOTH ); | ||||
| 	fgSizer8->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); | ||||
|  | ||||
| 	logEntry = new wxTextCtrl( m_panel2, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxTE_RICH ); | ||||
| 	fgSizer8->Add( logEntry, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
|  | ||||
| 	m_panel2->SetSizer( fgSizer8 ); | ||||
| 	m_panel2->Layout(); | ||||
| 	fgSizer8->Fit( m_panel2 ); | ||||
| 	notebook->AddPage( m_panel2, wxT("Logs"), false ); | ||||
| 	m_panel3 = new wxPanel( notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); | ||||
| 	wxFlexGridSizer* fgSizer9; | ||||
| 	fgSizer9 = new wxFlexGridSizer( 0, 2, 0, 0 ); | ||||
| 	fgSizer9->AddGrowableCol( 0 ); | ||||
| 	fgSizer9->AddGrowableRow( 0 ); | ||||
| 	fgSizer9->SetFlexibleDirection( wxBOTH ); | ||||
| 	fgSizer9->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); | ||||
|  | ||||
| 	protoConfigEntry = new wxTextCtrl( m_panel3, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY ); | ||||
| 	fgSizer9->Add( protoConfigEntry, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
|  | ||||
| 	m_panel3->SetSizer( fgSizer9 ); | ||||
| 	m_panel3->Layout(); | ||||
| 	fgSizer9->Fit( m_panel3 ); | ||||
| 	notebook->AddPage( m_panel3, wxT("Debug info"), false ); | ||||
|  | ||||
| 	fgSizer2->Add( notebook, 1, wxEXPAND | wxALL, 5 ); | ||||
|  | ||||
| 	wxGridSizer* m_sizer; | ||||
| 	m_sizer = new wxGridSizer( 0, 2, 0, 0 ); | ||||
|  | ||||
| 	readFluxButton = new wxButton( this, wxID_ANY, wxT("Read flux"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_sizer->Add( readFluxButton, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
| 	readImageButton = new wxButton( this, wxID_ANY, wxT("Read image"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_sizer->Add( readImageButton, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
| 	writeFluxButton = new wxButton( this, wxID_ANY, wxT("Write flux"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_sizer->Add( writeFluxButton, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
| 	writeImageButton = new wxButton( this, wxID_ANY, wxT("Write image"), wxDefaultPosition, wxDefaultSize, 0 ); | ||||
| 	m_sizer->Add( writeImageButton, 0, wxALL|wxEXPAND, 5 ); | ||||
|  | ||||
|  | ||||
| 	fgSizer2->Add( m_sizer, 1, wxEXPAND|wxFIXED_MINSIZE, 5 ); | ||||
|  | ||||
|  | ||||
| 	bSizer1->Add( fgSizer2, 1, wxEXPAND, 5 ); | ||||
|  | ||||
|  | ||||
| 	this->SetSizer( bSizer1 ); | ||||
| 	this->Layout(); | ||||
| 	m_menubar1 = new wxMenuBar( 0 ); | ||||
| 	m_menu1 = new wxMenu(); | ||||
| 	wxMenuItem* m_menuItem2; | ||||
| 	m_menuItem2 = new wxMenuItem( m_menu1, wxID_ABOUT, wxString( wxT("About") ) , wxEmptyString, wxITEM_NORMAL ); | ||||
| 	m_menu1->Append( m_menuItem2 ); | ||||
|  | ||||
| 	wxMenuItem* m_menuItem1; | ||||
| 	m_menuItem1 = new wxMenuItem( m_menu1, wxID_EXIT, wxString( wxT("E&xit") ) , wxEmptyString, wxITEM_NORMAL ); | ||||
| 	m_menu1->Append( m_menuItem1 ); | ||||
|  | ||||
| 	m_menubar1->Append( m_menu1, wxT("&File") ); | ||||
|  | ||||
| 	this->SetMenuBar( m_menubar1 ); | ||||
|  | ||||
|  | ||||
| 	// Connect Events | ||||
| 	m_menu1->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnAbout ), this, m_menuItem2->GetId()); | ||||
| 	m_menu1->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnExit ), this, m_menuItem1->GetId()); | ||||
| } | ||||
|  | ||||
| MainWindowGen::~MainWindowGen() | ||||
| { | ||||
| 	// Disconnect Events | ||||
|  | ||||
| } | ||||
							
								
								
									
										1537
									
								
								src/gui/layout.fbp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1537
									
								
								src/gui/layout.fbp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										79
									
								
								src/gui/layout.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/gui/layout.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| /////////////////////////////////////////////////////////////////////////// | ||||
| // C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b) | ||||
| // http://www.wxformbuilder.org/ | ||||
| // | ||||
| // PLEASE DO *NOT* EDIT THIS FILE! | ||||
| /////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <wx/artprov.h> | ||||
| #include <wx/xrc/xmlres.h> | ||||
| #include "visualisation.h" | ||||
| #include <wx/gdicmn.h> | ||||
| #include <wx/font.h> | ||||
| #include <wx/colour.h> | ||||
| #include <wx/settings.h> | ||||
| #include <wx/string.h> | ||||
| #include <wx/button.h> | ||||
| #include <wx/bitmap.h> | ||||
| #include <wx/image.h> | ||||
| #include <wx/icon.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/combobox.h> | ||||
| #include <wx/choice.h> | ||||
| #include <wx/checkbox.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/panel.h> | ||||
| #include <wx/notebook.h> | ||||
| #include <wx/menu.h> | ||||
| #include <wx/frame.h> | ||||
|  | ||||
| /////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
| /// Class MainWindowGen | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
| class MainWindowGen : public wxFrame | ||||
| { | ||||
| 	private: | ||||
| 		wxFlexGridSizer* bSizer1; | ||||
|  | ||||
| 	protected: | ||||
| 		VisualisationControl* visualiser; | ||||
| 		wxButton* stopButton; | ||||
| 		wxStaticText* m_staticText4; | ||||
| 		wxComboBox* deviceCombo; | ||||
| 		wxStaticText* m_staticText5; | ||||
| 		wxComboBox* fluxSourceSinkCombo; | ||||
| 		wxStaticText* m_staticText51; | ||||
| 		wxChoice* formatChoice; | ||||
| 		wxCheckBox* highDensityToggle; | ||||
| 		wxNotebook* notebook; | ||||
| 		wxPanel* m_panel1; | ||||
| 		wxTextCtrl* additionalSettingsEntry; | ||||
| 		wxPanel* m_panel2; | ||||
| 		wxTextCtrl* logEntry; | ||||
| 		wxPanel* m_panel3; | ||||
| 		wxTextCtrl* protoConfigEntry; | ||||
| 		wxButton* readFluxButton; | ||||
| 		wxButton* readImageButton; | ||||
| 		wxButton* writeFluxButton; | ||||
| 		wxButton* writeImageButton; | ||||
| 		wxMenuBar* m_menubar1; | ||||
| 		wxMenu* m_menu1; | ||||
|  | ||||
| 		// Virtual event handlers, override them in your derived class | ||||
| 		virtual void OnAbout( wxCommandEvent& event ) { event.Skip(); } | ||||
| 		virtual void OnExit( wxCommandEvent& event ) { event.Skip(); } | ||||
|  | ||||
|  | ||||
| 	public: | ||||
|  | ||||
| 		MainWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("FluxEngine"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 587,595 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); | ||||
|  | ||||
| 		~MainWindowGen(); | ||||
|  | ||||
| }; | ||||
|  | ||||
							
								
								
									
										117
									
								
								src/gui/main.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/gui/main.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| #include "globals.h" | ||||
| #include "gui.h" | ||||
| #include "mainwindow.h" | ||||
| #include "utils.h" | ||||
|  | ||||
| class FluxEngineApp; | ||||
| class ExecEvent; | ||||
|  | ||||
| static wxSemaphore execSemaphore(0); | ||||
|  | ||||
| wxDEFINE_EVENT(EXEC_EVENT_TYPE, ExecEvent); | ||||
| class ExecEvent : public wxThreadEvent | ||||
| { | ||||
| public: | ||||
|     ExecEvent(wxEventType commandType = EXEC_EVENT_TYPE, int id = 0): | ||||
|         wxThreadEvent(commandType, id) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     ExecEvent(const ExecEvent& event): | ||||
|         wxThreadEvent(event), | ||||
|         _callback(event._callback) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     wxEvent* Clone() const | ||||
|     { | ||||
|         return new ExecEvent(*this); | ||||
|     } | ||||
|  | ||||
| 	void SetCallback(const std::function<void()> callback) | ||||
| 	{ | ||||
| 		_callback = callback; | ||||
| 	} | ||||
|  | ||||
| 	void RunCallback() const | ||||
| 	{ | ||||
| 		_callback(); | ||||
| 	} | ||||
|  | ||||
| private: | ||||
|     std::function<void()> _callback; | ||||
| }; | ||||
|  | ||||
| bool FluxEngineApp::OnInit() | ||||
| { | ||||
| 	Bind(EXEC_EVENT_TYPE, &FluxEngineApp::OnExec, this); | ||||
|     _mainWindow = new MainWindow(); | ||||
|     _mainWindow->Show(true); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| wxThread::ExitCode FluxEngineApp::Entry() | ||||
| { | ||||
| 	try | ||||
| 	{ | ||||
| 		if (_callback) | ||||
| 			_callback(); | ||||
| 	} | ||||
| 	catch (const ErrorException& e) | ||||
| 	{ | ||||
| 		Logger() << ErrorLogMessage { e.message+'\n' }; | ||||
| 	} | ||||
| 	catch (const EmergencyStopException& e) | ||||
| 	{ | ||||
| 		Logger() << "Emergency stop!\n"; | ||||
| 	} | ||||
|  | ||||
| 	runOnUiThread( | ||||
| 		[&] { | ||||
| 			_callback = nullptr; | ||||
| 			_mainWindow->UpdateState(); | ||||
| 		} | ||||
| 	); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void FluxEngineApp::RunOnWorkerThread(std::function<void()> callback) | ||||
| { | ||||
| 	if (_callback) | ||||
| 		std::cerr << "Cannot start new worker task as one is already running\n"; | ||||
| 	_callback = callback; | ||||
|  | ||||
| 	if (GetThread()) | ||||
| 		GetThread()->Wait(); | ||||
|  | ||||
| 	emergencyStop = false; | ||||
| 	CreateThread(wxTHREAD_JOINABLE); | ||||
| 	GetThread()->Run(); | ||||
| 	_mainWindow->UpdateState(); | ||||
| } | ||||
|  | ||||
| void runOnWorkerThread(std::function<void()> callback) | ||||
| { | ||||
| 	wxGetApp().RunOnWorkerThread(callback); | ||||
| } | ||||
|  | ||||
| bool FluxEngineApp::IsWorkerThreadRunning() const | ||||
| { | ||||
| 	return !!_callback; | ||||
| } | ||||
|  | ||||
| void FluxEngineApp::OnExec(const ExecEvent& event) | ||||
| { | ||||
| 	event.RunCallback(); | ||||
| 	execSemaphore.Post(); | ||||
| } | ||||
|  | ||||
| void runOnUiThread(std::function<void()> callback) | ||||
| { | ||||
| 	ExecEvent* event = new ExecEvent(); | ||||
| 	event->SetCallback(callback); | ||||
| 	wxGetApp().QueueEvent(event); | ||||
| 	execSemaphore.Wait(); | ||||
| } | ||||
|  | ||||
| wxIMPLEMENT_APP(FluxEngineApp); | ||||
							
								
								
									
										355
									
								
								src/gui/mainwindow.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								src/gui/mainwindow.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,355 @@ | ||||
| #include "globals.h" | ||||
| #include "proto.h" | ||||
| #include "gui.h" | ||||
| #include "logger.h" | ||||
| #include "reader.h" | ||||
| #include "writer.h" | ||||
| #include "fluxsource/fluxsource.h" | ||||
| #include "fluxsink/fluxsink.h" | ||||
| #include "imagereader/imagereader.h" | ||||
| #include "imagewriter/imagewriter.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "lib/usb/usbfinder.h" | ||||
| #include "fmt/format.h" | ||||
| #include "utils.h" | ||||
| #include "mainwindow.h" | ||||
| #include <google/protobuf/text_format.h> | ||||
|  | ||||
| extern const std::map<std::string, std::string> formats; | ||||
|  | ||||
| MainWindow::MainWindow(): MainWindowGen(nullptr) | ||||
| { | ||||
|     Logger::setLogger( | ||||
|         [&](std::shared_ptr<const AnyLogMessage> message) | ||||
|         { | ||||
|             runOnUiThread( | ||||
|                 [message, this]() | ||||
|                 { | ||||
|                     OnLogMessage(message); | ||||
|                 }); | ||||
|         }); | ||||
|  | ||||
|     for (const auto& it : formats) | ||||
|     { | ||||
|         auto config = std::make_unique<ConfigProto>(); | ||||
|         if (!config->ParseFromString(it.second)) | ||||
|             continue; | ||||
|         if (config->is_extension()) | ||||
|             continue; | ||||
|  | ||||
|         formatChoice->Append(it.first); | ||||
|         _formats.push_back(std::move(config)); | ||||
|     } | ||||
|  | ||||
|     UpdateDevices(); | ||||
|     if (deviceCombo->GetCount() > 0) | ||||
|         deviceCombo->SetValue(deviceCombo->GetString(0)); | ||||
|  | ||||
|     fluxSourceSinkCombo->SetValue(fluxSourceSinkCombo->GetString(0)); | ||||
|  | ||||
|     readFluxButton->Bind(wxEVT_BUTTON, &MainWindow::OnReadFluxButton, this); | ||||
|     readImageButton->Bind(wxEVT_BUTTON, &MainWindow::OnReadImageButton, this); | ||||
| 	writeFluxButton->Bind(wxEVT_BUTTON, &MainWindow::OnWriteFluxButton, this); | ||||
|     writeImageButton->Bind(wxEVT_BUTTON, &MainWindow::OnWriteImageButton, this); | ||||
|     stopButton->Bind(wxEVT_BUTTON, &MainWindow::OnStopButton, this); | ||||
|  | ||||
|     UpdateState(); | ||||
| } | ||||
|  | ||||
| void MainWindow::OnExit(wxCommandEvent& event) | ||||
| { | ||||
|     Close(true); | ||||
| } | ||||
|  | ||||
| void MainWindow::OnStopButton(wxCommandEvent&) | ||||
| { | ||||
|     emergencyStop = true; | ||||
| } | ||||
|  | ||||
| void MainWindow::OnReadFluxButton(wxCommandEvent&) | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         PrepareConfig(); | ||||
|  | ||||
|         FluxSource::updateConfigForFilename(config.mutable_flux_source(), | ||||
|             fluxSourceSinkCombo->GetValue().ToStdString()); | ||||
| 		visualiser->Clear(); | ||||
| 		_currentDisk = nullptr; | ||||
|  | ||||
| 		SetHighDensity(); | ||||
| 		ShowConfig(); | ||||
|         runOnWorkerThread( | ||||
|             [this]() | ||||
|             { | ||||
|                 auto fluxSource = FluxSource::create(config.flux_source()); | ||||
|                 auto decoder = AbstractDecoder::create(config.decoder()); | ||||
|                 auto diskflux = readDiskCommand(*fluxSource, *decoder); | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         visualiser->SetDiskData(diskflux); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|     catch (const ErrorException& e) | ||||
|     { | ||||
|         wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MainWindow::OnWriteFluxButton(wxCommandEvent&) | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         PrepareConfig(); | ||||
|  | ||||
|         FluxSink::updateConfigForFilename(config.mutable_flux_sink(), | ||||
|             fluxSourceSinkCombo->GetValue().ToStdString()); | ||||
|         FluxSource::updateConfigForFilename(config.mutable_flux_source(), | ||||
|             fluxSourceSinkCombo->GetValue().ToStdString()); | ||||
|  | ||||
| 		SetHighDensity(); | ||||
| 		ShowConfig(); | ||||
| 		auto image = _currentDisk->image; | ||||
|         runOnWorkerThread( | ||||
|             [image, this]() | ||||
|             { | ||||
| 				auto encoder = AbstractEncoder::create(config.encoder()); | ||||
| 				auto fluxSink = FluxSink::create(config.flux_sink()); | ||||
|  | ||||
| 				std::unique_ptr<AbstractDecoder> decoder; | ||||
| 				std::unique_ptr<FluxSource> fluxSource; | ||||
| 				if (config.has_decoder()) | ||||
| 				{ | ||||
| 					decoder = AbstractDecoder::create(config.decoder()); | ||||
| 					fluxSource = FluxSource::create(config.flux_source()); | ||||
| 				} | ||||
| 				writeDiskCommand(*image, *encoder, *fluxSink, decoder.get(), fluxSource.get()); | ||||
|             }); | ||||
|     } | ||||
|     catch (const ErrorException& e) | ||||
|     { | ||||
|         wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MainWindow::OnReadImageButton(wxCommandEvent&) | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         PrepareConfig(); | ||||
|         if (!config.has_image_reader()) | ||||
|             Error() << "This format is read-only."; | ||||
|  | ||||
|         auto filename = wxFileSelector( | ||||
| 			"Choose a image file to read", | ||||
| 			/* default_path= */ wxEmptyString, | ||||
| 			/* default_filename= */ config.image_reader().filename(), | ||||
| 			/* default_extension= */ wxEmptyString, | ||||
| 			/* wildcard= */ wxEmptyString, | ||||
| 			/* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||||
|         if (filename.empty()) | ||||
|             return; | ||||
|  | ||||
|         ImageReader::updateConfigForFilename( | ||||
|             config.mutable_image_reader(), filename.ToStdString()); | ||||
| 		visualiser->Clear(); | ||||
| 		_currentDisk = nullptr; | ||||
|  | ||||
| 		ShowConfig(); | ||||
|         runOnWorkerThread( | ||||
|             [this]() | ||||
|             { | ||||
|                 auto imageReader = ImageReader::create(config.image_reader()); | ||||
|                 std::unique_ptr<const Image> image = imageReader->readImage(); | ||||
|                 runOnUiThread( | ||||
|                     [&]() | ||||
|                     { | ||||
|                         auto disk = std::make_shared<DiskFlux>(); | ||||
|                         disk = std::make_shared<DiskFlux>(); | ||||
|                         disk->image = std::move(image); | ||||
|                         _currentDisk = disk; | ||||
|                         visualiser->SetDiskData(_currentDisk); | ||||
|                     }); | ||||
|             }); | ||||
|     } | ||||
|     catch (const ErrorException& e) | ||||
|     { | ||||
|         wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MainWindow::OnWriteImageButton(wxCommandEvent&) | ||||
| { | ||||
|     try | ||||
|     { | ||||
|         PrepareConfig(); | ||||
|         if (!config.has_image_writer()) | ||||
|             Error() << "This format is write-only."; | ||||
|  | ||||
|         auto filename = wxFileSelector( | ||||
| 			"Choose a image file to write", | ||||
| 			/* default_path= */ wxEmptyString, | ||||
| 			/* default_filename= */ config.image_writer().filename(), | ||||
| 			/* default_extension= */ wxEmptyString, | ||||
| 			/* wildcard= */ wxEmptyString, | ||||
| 			/* flags= */ wxFD_SAVE | wxFD_OVERWRITE_PROMPT); | ||||
|         if (filename.empty()) | ||||
|             return; | ||||
|  | ||||
|         ImageWriter::updateConfigForFilename( | ||||
|             config.mutable_image_writer(), filename.ToStdString()); | ||||
|  | ||||
| 		ShowConfig(); | ||||
| 		auto image = _currentDisk->image; | ||||
|         runOnWorkerThread( | ||||
|             [image, this]() | ||||
|             { | ||||
| 				auto imageWriter = ImageWriter::create(config.image_writer()); | ||||
| 				imageWriter->writeImage(*image); | ||||
|             }); | ||||
|     } | ||||
|     catch (const ErrorException& e) | ||||
|     { | ||||
|         wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* This sets the *global* config object. That's safe provided the worker thread | ||||
|  * isn't running, otherwise you'll get a race. */ | ||||
| void MainWindow::PrepareConfig() | ||||
| { | ||||
| 	assert(!wxGetApp().IsWorkerThreadRunning()); | ||||
|  | ||||
|     auto formatSelection = formatChoice->GetSelection(); | ||||
|     if (formatSelection == wxNOT_FOUND) | ||||
|         Error() << "no format selected"; | ||||
|  | ||||
| 	config = *_formats[formatChoice->GetSelection()]; | ||||
|  | ||||
|     auto serial = deviceCombo->GetValue().ToStdString(); | ||||
|     if (!serial.empty() && (serial[0] == '/')) | ||||
|         setProtoByString(&config, "usb.greaseweazle.port", serial); | ||||
|     else | ||||
|         setProtoByString(&config, "usb.serial", serial); | ||||
|  | ||||
|     ApplyCustomSettings(); | ||||
|     logEntry->Clear(); | ||||
| } | ||||
|  | ||||
| void MainWindow::SetHighDensity() | ||||
| { | ||||
| 	bool hd = highDensityToggle->GetValue(); | ||||
| 	if (config.flux_source().has_drive()) | ||||
| 		config.mutable_flux_source()->mutable_drive()->set_high_density(hd); | ||||
| 	if (config.flux_sink().has_drive()) | ||||
| 		config.mutable_flux_sink()->mutable_drive()->set_high_density(hd); | ||||
| } | ||||
|  | ||||
| void MainWindow::ShowConfig() | ||||
| { | ||||
| 	std::string s; | ||||
| 	google::protobuf::TextFormat::PrintToString(config, &s); | ||||
| 	protoConfigEntry->Clear(); | ||||
| 	protoConfigEntry->AppendText(s); | ||||
| } | ||||
|  | ||||
| void MainWindow::ApplyCustomSettings() | ||||
| { | ||||
|     for (int i = 0; i < additionalSettingsEntry->GetNumberOfLines(); i++) | ||||
|     { | ||||
|         auto setting = additionalSettingsEntry->GetLineText(i).ToStdString(); | ||||
|         trimWhitespace(setting); | ||||
|         if (setting.size() == 0) | ||||
|             continue; | ||||
|  | ||||
|         auto equals = setting.find('='); | ||||
|         if (equals != std::string::npos) | ||||
|         { | ||||
|             auto key = setting.substr(0, equals); | ||||
|             auto value = setting.substr(equals + 1); | ||||
|             setProtoByString(&config, key, value); | ||||
|         } | ||||
|         else | ||||
|             FlagGroup::parseConfigFile(setting, formats); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void MainWindow::OnLogMessage(std::shared_ptr<const AnyLogMessage> message) | ||||
| { | ||||
|     logEntry->AppendText(Logger::toString(*message)); | ||||
|     notebook->SetSelection(1); | ||||
|  | ||||
|     std::visit( | ||||
|         overloaded{ | ||||
|             /* Fallback --- do nothing */ | ||||
|             [&](const auto& m) | ||||
|             { | ||||
|             }, | ||||
|  | ||||
|             /* A fatal error. */ | ||||
|             [&](const ErrorLogMessage& m) | ||||
|             { | ||||
|                 wxMessageBox(m.message, "Error", wxOK | wxICON_ERROR); | ||||
|             }, | ||||
|  | ||||
|             /* Indicates that we're starting a write operation. */ | ||||
|             [&](const BeginWriteOperationLogMessage& m) | ||||
|             { | ||||
|                 visualiser->SetMode(m.cylinder, m.head, VISMODE_WRITING); | ||||
|             }, | ||||
|  | ||||
|             [&](const EndWriteOperationLogMessage& m) | ||||
|             { | ||||
|                 visualiser->SetMode(0, 0, VISMODE_NOTHING); | ||||
|             }, | ||||
|  | ||||
|             /* Indicates that we're starting a read operation. */ | ||||
|             [&](const BeginReadOperationLogMessage& m) | ||||
|             { | ||||
|                 visualiser->SetMode(m.cylinder, m.head, VISMODE_READING); | ||||
|             }, | ||||
|  | ||||
|             [&](const EndReadOperationLogMessage& m) | ||||
|             { | ||||
|                 visualiser->SetMode(0, 0, VISMODE_NOTHING); | ||||
|             }, | ||||
|  | ||||
|             [&](const TrackReadLogMessage& m) | ||||
|             { | ||||
|                 visualiser->SetTrackData(m.track); | ||||
|             }, | ||||
|  | ||||
|             [&](const DiskReadLogMessage& m) | ||||
|             { | ||||
|                 _currentDisk = m.disk; | ||||
|             }, | ||||
|         }, | ||||
|         *message); | ||||
| } | ||||
|  | ||||
| void MainWindow::UpdateState() | ||||
| { | ||||
| 	bool running = wxGetApp().IsWorkerThreadRunning(); | ||||
|  | ||||
|     writeImageButton->Enable(!running && !!_currentDisk); | ||||
|     writeFluxButton->Enable(!running && !!_currentDisk); | ||||
|     stopButton->Enable(running); | ||||
|     readFluxButton->Enable(!running); | ||||
|     readImageButton->Enable(!running); | ||||
| } | ||||
|  | ||||
| void MainWindow::UpdateDevices() | ||||
| { | ||||
|     auto candidates = findUsbDevices(); | ||||
|  | ||||
|     deviceCombo->Clear(); | ||||
|     _devices.clear(); | ||||
|     for (auto& candidate : candidates) | ||||
|     { | ||||
|         deviceCombo->Append(candidate->serial); | ||||
|         _devices.push_back(std::move(candidate)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										42
									
								
								src/gui/mainwindow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/gui/mainwindow.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #ifndef MAINWINDOW_H | ||||
| #define MAINWINDOW_H | ||||
|  | ||||
| #include "layout.h" | ||||
| #include "logger.h" | ||||
|  | ||||
| class CandidateDevice; | ||||
| class ConfigProto; | ||||
| class DiskFlux; | ||||
|  | ||||
| class MainWindow : public MainWindowGen | ||||
| { | ||||
| public: | ||||
|     MainWindow(); | ||||
|  | ||||
| private: | ||||
|     void OnExit(wxCommandEvent& event); | ||||
| 	void OnStopButton(wxCommandEvent&); | ||||
| 	void OnReadFluxButton(wxCommandEvent&); | ||||
| 	void OnReadImageButton(wxCommandEvent&); | ||||
| 	void OnWriteFluxButton(wxCommandEvent&); | ||||
| 	void OnWriteImageButton(wxCommandEvent&); | ||||
| 	void OnLogMessage(std::shared_ptr<const AnyLogMessage> message); | ||||
|  | ||||
| public: | ||||
| 	void UpdateState(); | ||||
| 	void UpdateDevices(); | ||||
| 	void PrepareConfig(); | ||||
| 	void ShowConfig(); | ||||
| 	void ApplyCustomSettings(); | ||||
|  | ||||
| private: | ||||
| 	void SetHighDensity(); | ||||
|  | ||||
| private: | ||||
| 	std::vector<std::unique_ptr<const ConfigProto>> _formats; | ||||
| 	std::vector<std::unique_ptr<const CandidateDevice>> _devices; | ||||
| 	std::shared_ptr<const DiskFlux> _currentDisk; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
							
								
								
									
										179
									
								
								src/gui/visualisation.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/gui/visualisation.cc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | ||||
| #include "globals.h" | ||||
| #include "gui.h" | ||||
| #include "visualisation.h" | ||||
| #include "fluxmap.h" | ||||
| #include "flux.h" | ||||
| #include "sector.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| #define BORDER 20 | ||||
| #define TICK 3 | ||||
| #define TRACKS 82 | ||||
|  | ||||
| #define SECTORSIZE 5 | ||||
|  | ||||
| #define DECLARE_COLOUR(name, red, green, blue)             \ | ||||
|     static const wxColour name##_COLOUR(red, green, blue); \ | ||||
|     static const wxBrush name##_BRUSH(name##_COLOUR);      \ | ||||
|     static const wxPen name##_PEN(name##_COLOUR) | ||||
|  | ||||
| DECLARE_COLOUR(AXIS, 128, 128, 128); | ||||
| DECLARE_COLOUR(GOOD_SECTOR, 0, 158, 115); | ||||
| DECLARE_COLOUR(BAD_SECTOR, 213, 94, 0); | ||||
| DECLARE_COLOUR(MISSING_SECTOR, 86, 180, 233); | ||||
| DECLARE_COLOUR(READ_ARROW, 0, 128, 0); | ||||
| DECLARE_COLOUR(WRITE_ARROW, 128, 0, 0); | ||||
|  | ||||
| VisualisationControl::VisualisationControl(wxWindow* parent, | ||||
|     wxWindowID id, | ||||
|     const wxPoint& pos, | ||||
|     const wxSize& size, | ||||
|     long style): | ||||
|     wxWindow(parent, id, pos, size, style, "VisualisationControl") | ||||
| { | ||||
| 	SetDoubleBuffered(true); | ||||
| } | ||||
|  | ||||
| wxBEGIN_EVENT_TABLE(VisualisationControl, wxPanel) | ||||
|     EVT_PAINT(VisualisationControl::OnPaint) wxEND_EVENT_TABLE() | ||||
|  | ||||
|         void VisualisationControl::OnPaint(wxPaintEvent&) | ||||
| { | ||||
|     auto size = GetSize(); | ||||
|     int w = size.GetWidth(); | ||||
|     int w2 = w / 2; | ||||
|     int h = size.GetHeight(); | ||||
|  | ||||
|     int centrey = h * 1.5; | ||||
|     int outerradius = centrey - BORDER; | ||||
|     int innerradius = centrey - h + BORDER; | ||||
|     int scalesize = TRACKS * SECTORSIZE; | ||||
|     int scaletop = h / 2 - scalesize / 2; | ||||
|     int scalebottom = scaletop + scalesize - 1; | ||||
|  | ||||
|     wxPaintDC dc(this); | ||||
| 	dc.SetBackground(*wxWHITE_BRUSH); | ||||
| 	dc.Clear(); | ||||
|  | ||||
|     dc.SetPen(*wxBLACK_PEN); | ||||
|     dc.SetBrush(*wxLIGHT_GREY_BRUSH); | ||||
|     dc.DrawCircle({w2, centrey}, outerradius); | ||||
|     dc.SetBrush(dc.GetBackground()); | ||||
|     dc.DrawCircle({w2, centrey}, innerradius); | ||||
|  | ||||
|     dc.SetPen(AXIS_PEN); | ||||
|     dc.DrawLine({w2, scaletop}, {w2, scalebottom}); | ||||
|  | ||||
|     if (_mode != VISMODE_NOTHING) | ||||
|     { | ||||
|         if (_mode == VISMODE_READING) | ||||
| 		{ | ||||
| 			dc.SetPen(READ_ARROW_PEN); | ||||
| 			dc.SetBrush(READ_ARROW_BRUSH); | ||||
| 		} | ||||
|         else if (_mode == VISMODE_WRITING) | ||||
| 		{ | ||||
| 			dc.SetPen(WRITE_ARROW_PEN); | ||||
| 			dc.SetBrush(WRITE_ARROW_BRUSH); | ||||
| 		} | ||||
|  | ||||
|         int factor = (_head == 0) ? -1 : 1; | ||||
|  | ||||
|         int y = scaletop + _cylinder * SECTORSIZE; | ||||
| 		wxPoint points[] = { | ||||
| 			{ w2 + factor*TICK, y-1 }, | ||||
| 			{ w2 + factor*TICK, y+SECTORSIZE-1 }, | ||||
| 			{ w2 + factor*TICK*2, y+SECTORSIZE/2 } | ||||
| 		}; | ||||
| 		dc.DrawPolygon(3, points); | ||||
|     } | ||||
|  | ||||
|     for (int track = 0; track <= TRACKS; track++) | ||||
|     { | ||||
|         int y = scaletop + track * SECTORSIZE; | ||||
|         dc.SetBrush(AXIS_BRUSH); | ||||
|         dc.SetPen(AXIS_PEN); | ||||
|         dc.DrawLine({w2 - TICK, y-1}, {w2 + TICK, y-1}); | ||||
|  | ||||
|         auto drawSectors = [&](int head) | ||||
|         { | ||||
|             key_t key = {track, head}; | ||||
|             std::vector<std::shared_ptr<const Sector>> sectors; | ||||
|             for (auto it = _sectors.lower_bound(key); | ||||
|                  it != _sectors.upper_bound(key); | ||||
|                  it++) | ||||
|                 sectors.push_back(it->second); | ||||
|             std::sort( | ||||
|                 sectors.begin(), sectors.end(), sectorPointerSortPredicate); | ||||
|  | ||||
|             int x = 1; | ||||
|             for (const auto& sector : sectors) | ||||
|             { | ||||
|                 if (sector->status == Sector::OK) | ||||
|                 { | ||||
|                     dc.SetBrush(GOOD_SECTOR_BRUSH); | ||||
|                     dc.SetPen(GOOD_SECTOR_PEN); | ||||
|                 } | ||||
|                 else if (sector->status == Sector::MISSING) | ||||
|                 { | ||||
|                     dc.SetBrush(MISSING_SECTOR_BRUSH); | ||||
|                     dc.SetPen(MISSING_SECTOR_PEN); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     dc.SetBrush(BAD_SECTOR_BRUSH); | ||||
|                     dc.SetPen(BAD_SECTOR_PEN); | ||||
|                 } | ||||
|  | ||||
|                 if (head == 0) | ||||
|                     dc.DrawRectangle( | ||||
|                         {w2 - x * SECTORSIZE - (SECTORSIZE - 1), y}, | ||||
|                         {SECTORSIZE - 1, SECTORSIZE - 1}); | ||||
|                 else | ||||
|                     dc.DrawRectangle({w2 + x * SECTORSIZE + 1, y}, | ||||
|                         {SECTORSIZE - 1, SECTORSIZE - 1}); | ||||
|                 x++; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         drawSectors(0); | ||||
|         drawSectors(1); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void VisualisationControl::SetMode(int cylinder, int head, int mode) | ||||
| { | ||||
|     _cylinder = cylinder; | ||||
|     _head = head; | ||||
|     _mode = mode; | ||||
|     Refresh(); | ||||
| } | ||||
|  | ||||
| void VisualisationControl::Clear() | ||||
| { | ||||
|     _sectors.clear(); | ||||
|     Refresh(); | ||||
| } | ||||
|  | ||||
| void VisualisationControl::SetTrackData(std::shared_ptr<const TrackFlux> track) | ||||
| { | ||||
|     key_t key = {track->physicalCylinder, track->physicalHead}; | ||||
|     _sectors.erase(key); | ||||
|     for (auto& sector : track->sectors) | ||||
|         _sectors.insert({key, sector}); | ||||
|  | ||||
|     Refresh(); | ||||
| } | ||||
|  | ||||
| void VisualisationControl::SetDiskData(std::shared_ptr<const DiskFlux> disk) | ||||
| { | ||||
| 	_sectors.clear(); | ||||
| 	for (const auto& sector : *(disk->image)) | ||||
| 	{ | ||||
| 		key_t key = {sector->physicalCylinder, sector->physicalHead}; | ||||
| 		_sectors.insert({key, sector}); | ||||
| 	} | ||||
|  | ||||
|     Refresh(); | ||||
| } | ||||
							
								
								
									
										46
									
								
								src/gui/visualisation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/gui/visualisation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #ifndef VISUALISATION_H | ||||
| #define VISUALISATION_H | ||||
|  | ||||
| #include <memory> | ||||
| #include <map> | ||||
| #include <wx/control.h> | ||||
|  | ||||
| class Sector; | ||||
| class DiskFlux; | ||||
| class TrackFlux; | ||||
|  | ||||
| enum { | ||||
| 	VISMODE_NOTHING, | ||||
| 	VISMODE_READING, | ||||
| 	VISMODE_WRITING | ||||
| }; | ||||
|  | ||||
| class VisualisationControl : public wxWindow | ||||
| { | ||||
| public: | ||||
|     VisualisationControl(wxWindow* parent, | ||||
|         wxWindowID winid, | ||||
|         const wxPoint& pos = wxDefaultPosition, | ||||
|         const wxSize& size = wxDefaultSize, | ||||
|         long style = 0); | ||||
|  | ||||
| public: | ||||
| 	void Clear(); | ||||
| 	void SetMode(int head, int cylinder, int mode); | ||||
| 	void SetTrackData(std::shared_ptr<const TrackFlux> track); | ||||
| 	void SetDiskData(std::shared_ptr<const DiskFlux> disk); | ||||
|  | ||||
| private: | ||||
| 	void OnPaint(wxPaintEvent & evt); | ||||
|  | ||||
| private: | ||||
| 	typedef std::pair<unsigned, unsigned> key_t; | ||||
|  | ||||
| 	int _head; | ||||
| 	int _cylinder; | ||||
| 	int _mode = VISMODE_NOTHING; | ||||
| 	std::multimap<key_t, std::shared_ptr<const Sector>> _sectors; | ||||
|     wxDECLARE_EVENT_TABLE(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user