mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #617 from davidgiven/smaky
Add support for the Smaky 6.
This commit is contained in:
1
Makefile
1
Makefile
@@ -111,6 +111,7 @@ PROTOS = \
|
||||
arch/micropolis/micropolis.proto \
|
||||
arch/mx/mx.proto \
|
||||
arch/northstar/northstar.proto \
|
||||
arch/smaky6/smaky6.proto \
|
||||
arch/tids990/tids990.proto \
|
||||
arch/victor9k/victor9k.proto \
|
||||
arch/zilogmcz/zilogmcz.proto \
|
||||
|
||||
@@ -116,6 +116,7 @@ people who've had it work).
|
||||
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
|
||||
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
|
||||
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
|
||||
| [Smaky 6](doc/disk-smaky6.md) | 🦖 | | 5.25" hard sectored |
|
||||
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
|
||||
{: .datatable }
|
||||
|
||||
|
||||
@@ -13,16 +13,6 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
|
||||
|
||||
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
|
||||
|
||||
static Bytes reverse_bits(const Bytes& input)
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
|
||||
for (uint8_t b : input)
|
||||
bw.write_8(reverse_bits(b));
|
||||
return output;
|
||||
}
|
||||
|
||||
class AesLanierDecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
@@ -43,7 +33,7 @@ public:
|
||||
|
||||
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
|
||||
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
|
||||
const auto& reversed = reverse_bits(bytes);
|
||||
const auto& reversed = bytes.reverseBits();
|
||||
|
||||
_sector->logicalTrack = reversed[1];
|
||||
_sector->logicalSide = 0;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
LIBARCH_SRCS = \
|
||||
arch/aeslanier/decoder.cc \
|
||||
arch/agat/agat.cc \
|
||||
arch/agat/decoder.cc \
|
||||
arch/amiga/amiga.cc \
|
||||
arch/amiga/decoder.cc \
|
||||
arch/amiga/encoder.cc \
|
||||
@@ -16,18 +18,17 @@ LIBARCH_SRCS = \
|
||||
arch/ibm/encoder.cc \
|
||||
arch/macintosh/decoder.cc \
|
||||
arch/macintosh/encoder.cc \
|
||||
arch/micropolis/decoder.cc \
|
||||
arch/micropolis/encoder.cc \
|
||||
arch/mx/decoder.cc \
|
||||
arch/northstar/decoder.cc \
|
||||
arch/northstar/encoder.cc \
|
||||
arch/smaky6/decoder.cc \
|
||||
arch/tids990/decoder.cc \
|
||||
arch/tids990/encoder.cc \
|
||||
arch/victor9k/decoder.cc \
|
||||
arch/victor9k/encoder.cc \
|
||||
arch/zilogmcz/decoder.cc \
|
||||
arch/tids990/decoder.cc \
|
||||
arch/tids990/encoder.cc \
|
||||
arch/micropolis/decoder.cc \
|
||||
arch/micropolis/encoder.cc \
|
||||
arch/northstar/decoder.cc \
|
||||
arch/northstar/encoder.cc \
|
||||
arch/agat/agat.cc \
|
||||
arch/agat/decoder.cc \
|
||||
|
||||
LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS))
|
||||
OBJS += $(LIBARCH_OBJS)
|
||||
|
||||
154
arch/smaky6/decoder.cc
Normal file
154
arch/smaky6/decoder.cc
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "globals.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "protocol.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "sector.h"
|
||||
#include "smaky6.h"
|
||||
#include "bytes.h"
|
||||
#include "crc.h"
|
||||
#include "fmt/format.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa);
|
||||
|
||||
class Smaky6Decoder : public Decoder
|
||||
{
|
||||
public:
|
||||
Smaky6Decoder(const DecoderProto& config):
|
||||
Decoder(config),
|
||||
_config(config.smaky6())
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
/* Returns the sector ID of the _current_ sector. */
|
||||
int advanceToNextSector()
|
||||
{
|
||||
auto previous = tell();
|
||||
seekToIndexMark();
|
||||
auto now = tell();
|
||||
if ((now.ns() - previous.ns()) < 9e6)
|
||||
{
|
||||
seekToIndexMark();
|
||||
auto next = tell();
|
||||
if ((next.ns() - now.ns()) < 9e6)
|
||||
{
|
||||
/* We just found sector 0. */
|
||||
|
||||
_sectorId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Spurious... */
|
||||
|
||||
seek(now);
|
||||
}
|
||||
}
|
||||
|
||||
return _sectorId++;
|
||||
}
|
||||
|
||||
public:
|
||||
void beginTrack() override
|
||||
{
|
||||
/* Find the start-of-track index marks, which will be an interval
|
||||
* of about 6ms. */
|
||||
|
||||
seekToIndexMark();
|
||||
_sectorId = 99;
|
||||
for (;;)
|
||||
{
|
||||
auto pos = tell();
|
||||
advanceToNextSector();
|
||||
if (_sectorId < 99)
|
||||
{
|
||||
seek(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
if (eof())
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now we know where to start counting, start finding sectors. */
|
||||
|
||||
_sectorStarts.clear();
|
||||
for (;;)
|
||||
{
|
||||
auto now = tell();
|
||||
if (eof())
|
||||
break;
|
||||
|
||||
int id = advanceToNextSector();
|
||||
if (id < 16)
|
||||
_sectorStarts.push_back(std::make_pair(id, now));
|
||||
}
|
||||
|
||||
_sectorIndex = 0;
|
||||
}
|
||||
|
||||
nanoseconds_t advanceToNextRecord() override
|
||||
{
|
||||
if (_sectorIndex == _sectorStarts.size())
|
||||
{
|
||||
seekToIndexMark();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& p = _sectorStarts[_sectorIndex++];
|
||||
_sectorId = p.first;
|
||||
seek(p.second);
|
||||
|
||||
nanoseconds_t clock = seekToPattern(SECTOR_PATTERN);
|
||||
_sector->headerStartTime = tell().ns();
|
||||
|
||||
return clock;
|
||||
}
|
||||
|
||||
void decodeSectorRecord() override
|
||||
{
|
||||
readRawBits(33);
|
||||
const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16);
|
||||
if (rawbits.size() < SMAKY6_SECTOR_SIZE)
|
||||
return;
|
||||
const auto& rawbytes =
|
||||
toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16);
|
||||
|
||||
/* The Smaky bytes are stored backwards! Backwards! */
|
||||
|
||||
const auto& bytes =
|
||||
decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits();
|
||||
ByteReader br(bytes);
|
||||
|
||||
uint8_t track = br.read_8();
|
||||
Bytes data = br.read(SMAKY6_SECTOR_SIZE);
|
||||
uint8_t wantedChecksum = br.read_8();
|
||||
uint8_t gotChecksum = sumBytes(data) & 0xff;
|
||||
|
||||
if (track != _sector->physicalTrack)
|
||||
return;
|
||||
|
||||
_sector->logicalTrack = _sector->physicalTrack;
|
||||
_sector->logicalSide = _sector->physicalSide;
|
||||
_sector->logicalSector = _sectorId;
|
||||
|
||||
_sector->data = data;
|
||||
_sector->status =
|
||||
(wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
|
||||
}
|
||||
|
||||
private:
|
||||
const Smaky6DecoderProto& _config;
|
||||
nanoseconds_t _startOfTrack;
|
||||
std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts;
|
||||
int _sectorId;
|
||||
int _sectorIndex;
|
||||
};
|
||||
|
||||
std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config)
|
||||
{
|
||||
return std::unique_ptr<Decoder>(new Smaky6Decoder(config));
|
||||
}
|
||||
10
arch/smaky6/smaky6.h
Normal file
10
arch/smaky6/smaky6.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef SMAKY6_H
|
||||
#define SMAKY6_H
|
||||
|
||||
#define SMAKY6_SECTOR_SIZE 256
|
||||
#define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1)
|
||||
|
||||
extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config);
|
||||
|
||||
#endif
|
||||
|
||||
6
arch/smaky6/smaky6.proto
Normal file
6
arch/smaky6/smaky6.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto2";
|
||||
|
||||
import "lib/common.proto";
|
||||
|
||||
message Smaky6DecoderProto {}
|
||||
|
||||
33
doc/disk-smaky6.md
Normal file
33
doc/disk-smaky6.md
Normal file
@@ -0,0 +1,33 @@
|
||||
Disk: Smaky 6
|
||||
=============
|
||||
|
||||
The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based
|
||||
around a Z80 processor and has one or two Micropolis 5.25" drives which use
|
||||
16-sector hard sectored disks. The disk format is single-sided with 77 tracks
|
||||
and 256-byte sectors, resulting in 308kB disks. It uses MFM with a custom
|
||||
sector record scheme. It was later superceded by a 68000-based Smaky which used
|
||||
different disks.
|
||||
|
||||
FluxEngine supports these, although because the Micropolis drives use a 100tpi
|
||||
track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You
|
||||
will have to find a 100tpi drive from somewhere (they're rare).
|
||||
|
||||
Reading disks
|
||||
-------------
|
||||
|
||||
You must use a 100-tpi 80-track 5.25" floppy drive.
|
||||
|
||||
To read a Smaky 6 floppy, do:
|
||||
|
||||
```
|
||||
fluxengine read smaky6
|
||||
```
|
||||
|
||||
You should end up with a `smaky6.img` file.
|
||||
|
||||
Useful references
|
||||
-----------------
|
||||
|
||||
- [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo)
|
||||
|
||||
|
||||
10
lib/bytes.cc
10
lib/bytes.cc
@@ -205,6 +205,16 @@ std::vector<bool> Bytes::toBits() const
|
||||
return bits;
|
||||
}
|
||||
|
||||
Bytes Bytes::reverseBits() const
|
||||
{
|
||||
Bytes output;
|
||||
ByteWriter bw(output);
|
||||
|
||||
for (uint8_t b : *this)
|
||||
bw.write_8(reverse_bits(b));
|
||||
return output;
|
||||
}
|
||||
|
||||
uint8_t toByte(
|
||||
std::vector<bool>::const_iterator start,
|
||||
std::vector<bool>::const_iterator end)
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
Bytes compress() const;
|
||||
Bytes decompress() const;
|
||||
std::vector<bool> toBits() const;
|
||||
Bytes reverseBits() const;
|
||||
|
||||
ByteReader reader() const;
|
||||
ByteWriter writer();
|
||||
|
||||
@@ -1,223 +1,226 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "arch/agat/agat.h"
|
||||
#include "arch/aeslanier/aeslanier.h"
|
||||
#include "arch/amiga/amiga.h"
|
||||
#include "arch/apple2/apple2.h"
|
||||
#include "arch/brother/brother.h"
|
||||
#include "arch/c64/c64.h"
|
||||
#include "arch/f85/f85.h"
|
||||
#include "arch/fb100/fb100.h"
|
||||
#include "arch/ibm/ibm.h"
|
||||
#include "arch/macintosh/macintosh.h"
|
||||
#include "arch/micropolis/micropolis.h"
|
||||
#include "arch/mx/mx.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include "arch/tids990/tids990.h"
|
||||
#include "arch/victor9k/victor9k.h"
|
||||
#include "arch/zilogmcz/zilogmcz.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "flux.h"
|
||||
#include "protocol.h"
|
||||
#include "decoders/rawbits.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
|
||||
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
|
||||
{
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
|
||||
decoders = {
|
||||
{DecoderProto::kAgat, createAgatDecoder },
|
||||
{DecoderProto::kAeslanier, createAesLanierDecoder },
|
||||
{DecoderProto::kAmiga, createAmigaDecoder },
|
||||
{DecoderProto::kApple2, createApple2Decoder },
|
||||
{DecoderProto::kBrother, createBrotherDecoder },
|
||||
{DecoderProto::kC64, createCommodore64Decoder},
|
||||
{DecoderProto::kF85, createDurangoF85Decoder },
|
||||
{DecoderProto::kFb100, createFb100Decoder },
|
||||
{DecoderProto::kIbm, createIbmDecoder },
|
||||
{DecoderProto::kMacintosh, createMacintoshDecoder },
|
||||
{DecoderProto::kMicropolis, createMicropolisDecoder },
|
||||
{DecoderProto::kMx, createMxDecoder },
|
||||
{DecoderProto::kNorthstar, createNorthstarDecoder },
|
||||
{DecoderProto::kTids990, createTids990Decoder },
|
||||
{DecoderProto::kVictor9K, createVictor9kDecoder },
|
||||
{DecoderProto::kZilogmcz, createZilogMczDecoder },
|
||||
};
|
||||
|
||||
auto decoder = decoders.find(config.format_case());
|
||||
if (decoder == decoders.end())
|
||||
Error() << "no decoder specified";
|
||||
|
||||
return (decoder->second)(config);
|
||||
}
|
||||
|
||||
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
|
||||
std::shared_ptr<const Fluxmap> fluxmap,
|
||||
std::shared_ptr<const TrackInfo>& trackInfo)
|
||||
{
|
||||
_trackdata = std::make_shared<TrackDataFlux>();
|
||||
_trackdata->fluxmap = fluxmap;
|
||||
_trackdata->trackInfo = trackInfo;
|
||||
|
||||
FluxmapReader fmr(*fluxmap);
|
||||
_fmr = &fmr;
|
||||
|
||||
auto newSector = [&]
|
||||
{
|
||||
_sector = std::make_shared<Sector>(trackInfo, 0);
|
||||
_sector->status = Sector::MISSING;
|
||||
};
|
||||
|
||||
newSector();
|
||||
beginTrack();
|
||||
for (;;)
|
||||
{
|
||||
newSector();
|
||||
|
||||
Fluxmap::Position recordStart = fmr.tell();
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
return _trackdata;
|
||||
|
||||
/* Read the sector record. */
|
||||
|
||||
Fluxmap::Position before = fmr.tell();
|
||||
decodeSectorRecord();
|
||||
Fluxmap::Position after = fmr.tell();
|
||||
pushRecord(before, after);
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The data is in a separate record. */
|
||||
|
||||
for (;;)
|
||||
{
|
||||
_sector->headerStartTime = before.ns();
|
||||
_sector->headerEndTime = after.ns();
|
||||
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
break;
|
||||
|
||||
before = fmr.tell();
|
||||
decodeDataRecord();
|
||||
after = fmr.tell();
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
pushRecord(before, after);
|
||||
break;
|
||||
}
|
||||
|
||||
fmr.skipToEvent(F_BIT_PULSE);
|
||||
resetFluxDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
if (_sector->status != Sector::MISSING)
|
||||
{
|
||||
auto trackLayout = Layout::getLayoutOfTrack(
|
||||
_sector->logicalTrack, _sector->logicalSide);
|
||||
_trackdata->sectors.push_back(_sector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::pushRecord(
|
||||
const Fluxmap::Position& start, const Fluxmap::Position& end)
|
||||
{
|
||||
Fluxmap::Position here = _fmr->tell();
|
||||
|
||||
auto record = std::make_shared<Record>();
|
||||
_trackdata->records.push_back(record);
|
||||
_sector->records.push_back(record);
|
||||
|
||||
record->startTime = start.ns();
|
||||
record->endTime = end.ns();
|
||||
record->clock = _sector->clock;
|
||||
|
||||
record->rawData = toBytes(_recordBits);
|
||||
_recordBits.clear();
|
||||
}
|
||||
|
||||
void Decoder::resetFluxDecoder()
|
||||
{
|
||||
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
|
||||
}
|
||||
|
||||
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
|
||||
{
|
||||
nanoseconds_t clock = _fmr->seekToPattern(pattern);
|
||||
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
|
||||
return clock;
|
||||
}
|
||||
|
||||
void Decoder::seekToIndexMark()
|
||||
{
|
||||
_fmr->skipToEvent(F_BIT_PULSE);
|
||||
_fmr->seekToIndexMark();
|
||||
}
|
||||
|
||||
std::vector<bool> Decoder::readRawBits(unsigned count)
|
||||
{
|
||||
auto bits = _decoder->readBits(count);
|
||||
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint8_t Decoder::readRaw8()
|
||||
{
|
||||
return toBytes(readRawBits(8)).reader().read_8();
|
||||
}
|
||||
|
||||
uint16_t Decoder::readRaw16()
|
||||
{
|
||||
return toBytes(readRawBits(16)).reader().read_be16();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw20()
|
||||
{
|
||||
std::vector<bool> bits(4);
|
||||
for (bool b : readRawBits(20))
|
||||
bits.push_back(b);
|
||||
|
||||
return toBytes(bits).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw24()
|
||||
{
|
||||
return toBytes(readRawBits(24)).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw32()
|
||||
{
|
||||
return toBytes(readRawBits(32)).reader().read_be32();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw48()
|
||||
{
|
||||
return toBytes(readRawBits(48)).reader().read_be48();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw64()
|
||||
{
|
||||
return toBytes(readRawBits(64)).reader().read_be64();
|
||||
}
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "fluxmap.h"
|
||||
#include "decoders/decoders.h"
|
||||
#include "encoders/encoders.h"
|
||||
#include "arch/agat/agat.h"
|
||||
#include "arch/aeslanier/aeslanier.h"
|
||||
#include "arch/amiga/amiga.h"
|
||||
#include "arch/apple2/apple2.h"
|
||||
#include "arch/brother/brother.h"
|
||||
#include "arch/c64/c64.h"
|
||||
#include "arch/f85/f85.h"
|
||||
#include "arch/fb100/fb100.h"
|
||||
#include "arch/ibm/ibm.h"
|
||||
#include "arch/macintosh/macintosh.h"
|
||||
#include "arch/micropolis/micropolis.h"
|
||||
#include "arch/mx/mx.h"
|
||||
#include "arch/northstar/northstar.h"
|
||||
#include "arch/smaky6/smaky6.h"
|
||||
#include "arch/tids990/tids990.h"
|
||||
#include "arch/victor9k/victor9k.h"
|
||||
#include "arch/zilogmcz/zilogmcz.h"
|
||||
#include "decoders/fluxmapreader.h"
|
||||
#include "flux.h"
|
||||
#include "protocol.h"
|
||||
#include "decoders/rawbits.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "lib/decoders/decoders.pb.h"
|
||||
#include "lib/layout.h"
|
||||
#include "fmt/format.h"
|
||||
#include <numeric>
|
||||
|
||||
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
|
||||
{
|
||||
static const std::map<int,
|
||||
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
|
||||
decoders = {
|
||||
{DecoderProto::kAgat, createAgatDecoder },
|
||||
{DecoderProto::kAeslanier, createAesLanierDecoder },
|
||||
{DecoderProto::kAmiga, createAmigaDecoder },
|
||||
{DecoderProto::kApple2, createApple2Decoder },
|
||||
{DecoderProto::kBrother, createBrotherDecoder },
|
||||
{DecoderProto::kC64, createCommodore64Decoder},
|
||||
{DecoderProto::kF85, createDurangoF85Decoder },
|
||||
{DecoderProto::kFb100, createFb100Decoder },
|
||||
{DecoderProto::kIbm, createIbmDecoder },
|
||||
{DecoderProto::kMacintosh, createMacintoshDecoder },
|
||||
{DecoderProto::kMicropolis, createMicropolisDecoder },
|
||||
{DecoderProto::kMx, createMxDecoder },
|
||||
{DecoderProto::kNorthstar, createNorthstarDecoder },
|
||||
{DecoderProto::kSmaky6, createSmaky6Decoder },
|
||||
{DecoderProto::kTids990, createTids990Decoder },
|
||||
{DecoderProto::kVictor9K, createVictor9kDecoder },
|
||||
{DecoderProto::kZilogmcz, createZilogMczDecoder },
|
||||
};
|
||||
|
||||
auto decoder = decoders.find(config.format_case());
|
||||
if (decoder == decoders.end())
|
||||
Error() << "no decoder specified";
|
||||
|
||||
return (decoder->second)(config);
|
||||
}
|
||||
|
||||
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
|
||||
std::shared_ptr<const Fluxmap> fluxmap,
|
||||
std::shared_ptr<const TrackInfo>& trackInfo)
|
||||
{
|
||||
_trackdata = std::make_shared<TrackDataFlux>();
|
||||
_trackdata->fluxmap = fluxmap;
|
||||
_trackdata->trackInfo = trackInfo;
|
||||
|
||||
FluxmapReader fmr(*fluxmap);
|
||||
_fmr = &fmr;
|
||||
|
||||
auto newSector = [&]
|
||||
{
|
||||
_sector = std::make_shared<Sector>(trackInfo, 0);
|
||||
_sector->status = Sector::MISSING;
|
||||
};
|
||||
|
||||
newSector();
|
||||
beginTrack();
|
||||
for (;;)
|
||||
{
|
||||
newSector();
|
||||
|
||||
Fluxmap::Position recordStart = fmr.tell();
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
return _trackdata;
|
||||
|
||||
/* Read the sector record. */
|
||||
|
||||
Fluxmap::Position before = fmr.tell();
|
||||
decodeSectorRecord();
|
||||
Fluxmap::Position after = fmr.tell();
|
||||
pushRecord(before, after);
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The data is in a separate record. */
|
||||
|
||||
for (;;)
|
||||
{
|
||||
_sector->headerStartTime = before.ns();
|
||||
_sector->headerEndTime = after.ns();
|
||||
|
||||
_sector->clock = advanceToNextRecord();
|
||||
if (fmr.eof() || !_sector->clock)
|
||||
break;
|
||||
|
||||
before = fmr.tell();
|
||||
decodeDataRecord();
|
||||
after = fmr.tell();
|
||||
|
||||
if (_sector->status != Sector::DATA_MISSING)
|
||||
{
|
||||
_sector->position = before.bytes;
|
||||
_sector->dataStartTime = before.ns();
|
||||
_sector->dataEndTime = after.ns();
|
||||
pushRecord(before, after);
|
||||
break;
|
||||
}
|
||||
|
||||
fmr.skipToEvent(F_BIT_PULSE);
|
||||
resetFluxDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
if (_sector->status != Sector::MISSING)
|
||||
{
|
||||
auto trackLayout = Layout::getLayoutOfTrack(
|
||||
_sector->logicalTrack, _sector->logicalSide);
|
||||
_trackdata->sectors.push_back(_sector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::pushRecord(
|
||||
const Fluxmap::Position& start, const Fluxmap::Position& end)
|
||||
{
|
||||
Fluxmap::Position here = _fmr->tell();
|
||||
|
||||
auto record = std::make_shared<Record>();
|
||||
_trackdata->records.push_back(record);
|
||||
_sector->records.push_back(record);
|
||||
|
||||
record->position = start.bytes;
|
||||
record->startTime = start.ns();
|
||||
record->endTime = end.ns();
|
||||
record->clock = _sector->clock;
|
||||
|
||||
record->rawData = toBytes(_recordBits);
|
||||
_recordBits.clear();
|
||||
}
|
||||
|
||||
void Decoder::resetFluxDecoder()
|
||||
{
|
||||
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
|
||||
}
|
||||
|
||||
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
|
||||
{
|
||||
nanoseconds_t clock = _fmr->seekToPattern(pattern);
|
||||
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
|
||||
return clock;
|
||||
}
|
||||
|
||||
void Decoder::seekToIndexMark()
|
||||
{
|
||||
_fmr->skipToEvent(F_BIT_PULSE);
|
||||
_fmr->seekToIndexMark();
|
||||
}
|
||||
|
||||
std::vector<bool> Decoder::readRawBits(unsigned count)
|
||||
{
|
||||
auto bits = _decoder->readBits(count);
|
||||
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint8_t Decoder::readRaw8()
|
||||
{
|
||||
return toBytes(readRawBits(8)).reader().read_8();
|
||||
}
|
||||
|
||||
uint16_t Decoder::readRaw16()
|
||||
{
|
||||
return toBytes(readRawBits(16)).reader().read_be16();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw20()
|
||||
{
|
||||
std::vector<bool> bits(4);
|
||||
for (bool b : readRawBits(20))
|
||||
bits.push_back(b);
|
||||
|
||||
return toBytes(bits).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw24()
|
||||
{
|
||||
return toBytes(readRawBits(24)).reader().read_be24();
|
||||
}
|
||||
|
||||
uint32_t Decoder::readRaw32()
|
||||
{
|
||||
return toBytes(readRawBits(32)).reader().read_be32();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw48()
|
||||
{
|
||||
return toBytes(readRawBits(48)).reader().read_be48();
|
||||
}
|
||||
|
||||
uint64_t Decoder::readRaw64()
|
||||
{
|
||||
return toBytes(readRawBits(64)).reader().read_be64();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ public:
|
||||
return _fmr->tell();
|
||||
}
|
||||
|
||||
void rewind()
|
||||
{
|
||||
_fmr->rewind();
|
||||
}
|
||||
|
||||
void seek(const Fluxmap::Position& pos)
|
||||
{
|
||||
return _fmr->seek(pos);
|
||||
|
||||
@@ -13,13 +13,14 @@ import "arch/macintosh/macintosh.proto";
|
||||
import "arch/micropolis/micropolis.proto";
|
||||
import "arch/mx/mx.proto";
|
||||
import "arch/northstar/northstar.proto";
|
||||
import "arch/smaky6/smaky6.proto";
|
||||
import "arch/tids990/tids990.proto";
|
||||
import "arch/victor9k/victor9k.proto";
|
||||
import "arch/zilogmcz/zilogmcz.proto";
|
||||
import "lib/fluxsink/fluxsink.proto";
|
||||
import "lib/common.proto";
|
||||
|
||||
//NEXT: 30
|
||||
//NEXT: 31
|
||||
message DecoderProto {
|
||||
optional double pulse_debounce_threshold = 1 [default = 0.30,
|
||||
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
|
||||
@@ -46,6 +47,7 @@ message DecoderProto {
|
||||
MicropolisDecoderProto micropolis = 14;
|
||||
MxDecoderProto mx = 15;
|
||||
NorthstarDecoderProto northstar = 24;
|
||||
Smaky6DecoderProto smaky6 = 30;
|
||||
Tids990DecoderProto tids990 = 16;
|
||||
Victor9kDecoderProto victor9k = 17;
|
||||
ZilogMczDecoderProto zilogmcz = 18;
|
||||
|
||||
28
lib/flux.h
28
lib/flux.h
@@ -10,32 +10,32 @@ class TrackInfo;
|
||||
|
||||
struct Record
|
||||
{
|
||||
nanoseconds_t clock = 0;
|
||||
nanoseconds_t startTime = 0;
|
||||
nanoseconds_t endTime = 0;
|
||||
Bytes rawData;
|
||||
nanoseconds_t clock = 0;
|
||||
nanoseconds_t startTime = 0;
|
||||
nanoseconds_t endTime = 0;
|
||||
uint32_t position = 0;
|
||||
Bytes rawData;
|
||||
};
|
||||
|
||||
struct TrackDataFlux
|
||||
{
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::shared_ptr<const Fluxmap> fluxmap;
|
||||
std::vector<std::shared_ptr<const Record>> records;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::shared_ptr<const Fluxmap> fluxmap;
|
||||
std::vector<std::shared_ptr<const Record>> records;
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
};
|
||||
|
||||
struct TrackFlux
|
||||
{
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
std::shared_ptr<const TrackInfo> trackInfo;
|
||||
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
|
||||
std::set<std::shared_ptr<const Sector>> sectors;
|
||||
};
|
||||
|
||||
struct DiskFlux
|
||||
{
|
||||
std::vector<std::shared_ptr<TrackFlux>> tracks;
|
||||
std::shared_ptr<const Image> image;
|
||||
std::vector<std::shared_ptr<TrackFlux>> tracks;
|
||||
std::shared_ptr<const Image> image;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ FORMATS = \
|
||||
northstar87 \
|
||||
rx50 \
|
||||
shugart_drive \
|
||||
smaky6 \
|
||||
tids990 \
|
||||
victor9k_ds \
|
||||
victor9k_ss \
|
||||
|
||||
29
src/formats/smaky6.textpb
Normal file
29
src/formats/smaky6.textpb
Normal file
@@ -0,0 +1,29 @@
|
||||
comment: 'Smaky 6 308kB 5.25" 77-track 16-sector SSDD, hard sectored'
|
||||
|
||||
image_writer {
|
||||
filename: "smaky6.img"
|
||||
type: IMG
|
||||
}
|
||||
|
||||
layout {
|
||||
tracks: 77
|
||||
sides: 1
|
||||
layoutdata {
|
||||
sector_size: 256
|
||||
physical {
|
||||
start_sector: 0
|
||||
count: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drive {
|
||||
hard_sector_count: 16
|
||||
sync_with_index: false
|
||||
revolutions: 2.2
|
||||
}
|
||||
|
||||
decoder {
|
||||
smaky6 {}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "lib/sector.h"
|
||||
#include "lib/layout.h"
|
||||
#include "lib/decoders/fluxmapreader.h"
|
||||
#include "lib/crc.h"
|
||||
|
||||
DECLARE_COLOUR(BACKGROUND, 192, 192, 192);
|
||||
DECLARE_COLOUR(READ_SEPARATOR, 255, 0, 0);
|
||||
@@ -414,6 +415,14 @@ void FluxViewerControl::ShowSectorMenu(std::shared_ptr<const Sector> sector)
|
||||
{
|
||||
wxMenu menu;
|
||||
|
||||
menu.Bind(
|
||||
wxEVT_MENU,
|
||||
[&](wxCommandEvent&)
|
||||
{
|
||||
DisplaySectorSummary(sector);
|
||||
},
|
||||
menu.Append(wxID_ANY, "Show sector summary")->GetId());
|
||||
|
||||
menu.Bind(
|
||||
wxEVT_MENU,
|
||||
[&](wxCommandEvent&)
|
||||
@@ -461,7 +470,22 @@ static void dumpSectorMetadata(
|
||||
sector->physicalSide)
|
||||
<< fmt::format("Clock: {:.2f}us / {:.0f}kHz\n",
|
||||
sector->clock / 1000.0,
|
||||
1000000.0 / sector->clock);
|
||||
1000000.0 / sector->clock)
|
||||
<< fmt::format("Bytecode position: {}\n", sector->position)
|
||||
<< fmt::format(
|
||||
"Data CRC16: {:4x}\n", crc16(CCITT_POLY, sector->data));
|
||||
}
|
||||
|
||||
static void dumpRecordMetadata(
|
||||
std::ostream& s, std::shared_ptr<const Record> record)
|
||||
{
|
||||
s << fmt::format("Bytecode position: {}\n", record->position)
|
||||
<< fmt::format(
|
||||
"Start: {:.2f}ms\n", record->startTime / 1000000.0)
|
||||
<< fmt::format(
|
||||
"End: {:.2f}ms\n", record->endTime / 1000000.0)
|
||||
<< fmt::format(
|
||||
"Data CRC16: {:4x}\n", crc16(CCITT_POLY, record->rawData));
|
||||
}
|
||||
|
||||
void FluxViewerControl::DisplayDecodedData(std::shared_ptr<const Sector> sector)
|
||||
@@ -481,6 +505,44 @@ void FluxViewerControl::DisplayDecodedData(std::shared_ptr<const Sector> sector)
|
||||
TextViewerWindow::Create(this, title, s.str())->Show();
|
||||
}
|
||||
|
||||
void FluxViewerControl::DisplaySectorSummary(
|
||||
std::shared_ptr<const Sector> sector)
|
||||
{
|
||||
std::stringstream s;
|
||||
|
||||
auto title = fmt::format("Sector summary c{}.h{}.s{}",
|
||||
sector->logicalTrack,
|
||||
sector->logicalSide,
|
||||
sector->logicalSector);
|
||||
s << title << '\n';
|
||||
|
||||
std::vector<std::shared_ptr<const Sector>> sectors;
|
||||
for (auto& trackdata : _flux->trackDatas)
|
||||
{
|
||||
if ((trackdata->trackInfo->logicalTrack == sector->logicalTrack) &&
|
||||
(trackdata->trackInfo->logicalSide == sector->logicalSide))
|
||||
{
|
||||
for (auto& sec : trackdata->sectors)
|
||||
{
|
||||
if (*sec == *sector)
|
||||
sectors.push_back(sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s << fmt::format("Number of times seen: {}\n", sectors.size());
|
||||
|
||||
for (int i = 0; i < sectors.size(); i++)
|
||||
{
|
||||
auto& sec = sectors[i];
|
||||
s << fmt::format("\nInstance {}:\n\n", i);
|
||||
dumpSectorMetadata(s, sec);
|
||||
s << '\n';
|
||||
}
|
||||
|
||||
TextViewerWindow::Create(this, title, s.str())->Show();
|
||||
}
|
||||
|
||||
void FluxViewerControl::DisplayRawData(std::shared_ptr<const Sector> sector)
|
||||
{
|
||||
std::stringstream s;
|
||||
@@ -493,8 +555,11 @@ void FluxViewerControl::DisplayRawData(std::shared_ptr<const Sector> sector)
|
||||
dumpSectorMetadata(s, sector);
|
||||
s << fmt::format("Number of records: {}\n", sector->records.size());
|
||||
|
||||
for (auto& record : sector->records)
|
||||
for (int i = 0; i < sector->records.size(); i++)
|
||||
{
|
||||
auto& record = sector->records[i];
|
||||
s << fmt::format("\nRecord {}:\n\n", i);
|
||||
dumpRecordMetadata(s, record);
|
||||
s << '\n';
|
||||
hexdump(s, record->rawData);
|
||||
}
|
||||
@@ -511,7 +576,9 @@ void FluxViewerControl::DisplayRawData(std::shared_ptr<const TrackInfo>& layout,
|
||||
layout->physicalTrack,
|
||||
layout->physicalSide,
|
||||
record->startTime / 1e6);
|
||||
s << title << "\n\n";
|
||||
s << title << "\n";
|
||||
dumpRecordMetadata(s, record);
|
||||
s << '\n';
|
||||
hexdump(s, record->rawData);
|
||||
|
||||
TextViewerWindow::Create(this, title, s.str())->Show();
|
||||
|
||||
@@ -30,6 +30,7 @@ private:
|
||||
void ShowRecordMenu(std::shared_ptr<const TrackInfo>& layout,
|
||||
std::shared_ptr<const Record> record);
|
||||
void DisplayDecodedData(std::shared_ptr<const Sector> sector);
|
||||
void DisplaySectorSummary(std::shared_ptr<const Sector> sector);
|
||||
void DisplayRawData(std::shared_ptr<const Sector> sector);
|
||||
void DisplayRawData(std::shared_ptr<const TrackInfo>& layout,
|
||||
std::shared_ptr<const Record> record);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class ExecEvent;
|
||||
class MainWindow;
|
||||
|
||||
extern void postToUiThread(std::function<void()> callback);
|
||||
extern void runOnUiThread(std::function<void()> callback);
|
||||
extern void runOnWorkerThread(std::function<void()> callback);
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
wxString driveChoiceChoices[] = { wxT("drive:0"), wxT("drive:1") };
|
||||
int driveChoiceNChoices = sizeof( driveChoiceChoices ) / sizeof( wxString );
|
||||
driveChoice = new wxChoice( realDiskRadioButtonPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, driveChoiceNChoices, driveChoiceChoices, 0 );
|
||||
driveChoice->SetSelection( 0 );
|
||||
driveChoice->SetSelection( 1 );
|
||||
driveChoice->SetToolTip( wxT("Which drive on the device to use.") );
|
||||
|
||||
bSizer3->Add( driveChoice, 0, wxALL|wxEXPAND, 5 );
|
||||
@@ -174,7 +174,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
fgSizer8->Add( m_staticText19, 0, wxALIGN_CENTER|wxALL, 5 );
|
||||
|
||||
wxGridSizer* gSizer9;
|
||||
gSizer9 = new wxGridSizer( 1, 0, 0, 0 );
|
||||
gSizer9 = new wxGridSizer( 2, 3, 0, 0 );
|
||||
|
||||
readButton = new wxButton( idlePanel, wxID_ANY, wxT("Read disk"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
readButton->SetLabelMarkup( wxT("Read disk") );
|
||||
@@ -191,7 +191,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
|
||||
gSizer9->Add( writeButton, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
browseButton = new wxButton( idlePanel, wxID_ANY, wxT("Browse disk"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
browseButton = new wxButton( idlePanel, wxID_ANY, wxT("Browse files"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
|
||||
browseButton->SetBitmap( wxArtProvider::GetBitmap( wxART_FOLDER_OPEN, wxART_TOOLBAR ) );
|
||||
browseButton->SetToolTip( wxT("Access the files on the disk directly without needing to image it.") );
|
||||
@@ -203,6 +203,11 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
formatButton->SetBitmap( wxArtProvider::GetBitmap( wxART_DELETE, wxART_BUTTON ) );
|
||||
gSizer9->Add( formatButton, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
exploreButton = new wxButton( idlePanel, wxID_ANY, wxT("Explore disk"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
|
||||
exploreButton->SetBitmap( wxArtProvider::GetBitmap( wxART_INFORMATION, wxART_TOOLBAR ) );
|
||||
gSizer9->Add( exploreButton, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
fgSizer8->Add( gSizer9, 1, wxEXPAND, 5 );
|
||||
|
||||
@@ -372,6 +377,102 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
browsePanel->Layout();
|
||||
fgSizer23->Fit( browsePanel );
|
||||
dataNotebook->AddPage( browsePanel, wxT("a page"), false );
|
||||
explorePanel = new wxPanel( dataNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
wxBoxSizer* bSizer411;
|
||||
bSizer411 = new wxBoxSizer( wxVERTICAL );
|
||||
|
||||
explorerToolbar = new wxAuiToolBar( explorePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxAUI_TB_HORZ_LAYOUT|wxAUI_TB_TEXT );
|
||||
explorerBackTool = explorerToolbar->AddTool( wxID_ANY, wxT("Back"), wxArtProvider::GetBitmap( wxART_GO_BACK, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
explorerRefreshTool = explorerToolbar->AddTool( wxID_ANY, wxT("Refresh"), wxArtProvider::GetBitmap( wxART_REDO, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
explorerToolbar->Realize();
|
||||
|
||||
bSizer411->Add( explorerToolbar, 0, wxEXPAND, 5 );
|
||||
|
||||
wxFlexGridSizer* fgSizer12;
|
||||
fgSizer12 = new wxFlexGridSizer( 0, 2, 0, 0 );
|
||||
fgSizer12->AddGrowableCol( 1 );
|
||||
fgSizer12->AddGrowableRow( 0 );
|
||||
fgSizer12->SetFlexibleDirection( wxBOTH );
|
||||
fgSizer12->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
wxFlexGridSizer* fgSizer10;
|
||||
fgSizer10 = new wxFlexGridSizer( 0, 2, 0, 0 );
|
||||
fgSizer10->AddGrowableCol( 1 );
|
||||
fgSizer10->SetFlexibleDirection( wxHORIZONTAL );
|
||||
fgSizer10->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_staticText22 = new wxStaticText( explorePanel, wxID_ANY, wxT("Track"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText22->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText22, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
explorerTrackSpinCtrl = new wxSpinCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 82, 0 );
|
||||
fgSizer10->Add( explorerTrackSpinCtrl, 0, wxALL, 5 );
|
||||
|
||||
m_staticText26 = new wxStaticText( explorePanel, wxID_ANY, wxT("Side"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText26->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText26, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
explorerSideSpinCtrl = new wxSpinCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 1, 0 );
|
||||
fgSizer10->Add( explorerSideSpinCtrl, 0, wxALL, 5 );
|
||||
|
||||
m_staticText231 = new wxStaticText( explorePanel, wxID_ANY, wxT("Start time (ms)"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText231->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText231, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
explorerStartTimeSpinCtrl = new wxSpinCtrlDouble( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 99999, 0.000000, 1 );
|
||||
explorerStartTimeSpinCtrl->SetDigits( 3 );
|
||||
fgSizer10->Add( explorerStartTimeSpinCtrl, 0, wxALL, 5 );
|
||||
|
||||
m_staticText24 = new wxStaticText( explorePanel, wxID_ANY, wxT("Clock (us)"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText24->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText24, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
explorerClockSpinCtrl = new wxSpinCtrlDouble( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 100, 4, 1 );
|
||||
explorerClockSpinCtrl->SetDigits( 1 );
|
||||
fgSizer10->Add( explorerClockSpinCtrl, 0, wxALL, 5 );
|
||||
|
||||
m_staticText25 = new wxStaticText( explorePanel, wxID_ANY, wxT("Raw bit offset"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText25->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText25, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
explorerBitOffsetSpinCtrl = new wxSpinCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 99999999, 0 );
|
||||
fgSizer10->Add( explorerBitOffsetSpinCtrl, 0, wxALL, 5 );
|
||||
|
||||
m_staticText27 = new wxStaticText( explorePanel, wxID_ANY, wxT("Decode as"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText27->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText27, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
wxString explorerDecodeChoiceChoices[] = { wxT("Don't decode"), wxT("MFM / M²FM / FM") };
|
||||
int explorerDecodeChoiceNChoices = sizeof( explorerDecodeChoiceChoices ) / sizeof( wxString );
|
||||
explorerDecodeChoice = new wxChoice( explorePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, explorerDecodeChoiceNChoices, explorerDecodeChoiceChoices, 0 );
|
||||
explorerDecodeChoice->SetSelection( 0 );
|
||||
fgSizer10->Add( explorerDecodeChoice, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
m_staticText241 = new wxStaticText( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText241->Wrap( -1 );
|
||||
fgSizer10->Add( m_staticText241, 0, wxALL, 5 );
|
||||
|
||||
explorerReverseCheckBox = new wxCheckBox( explorePanel, wxID_ANY, wxT("Reverse bytes"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
fgSizer10->Add( explorerReverseCheckBox, 0, wxALL, 5 );
|
||||
|
||||
|
||||
fgSizer12->Add( fgSizer10, 1, wxEXPAND, 5 );
|
||||
|
||||
explorerText = new wxTextCtrl( explorePanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_PROCESS_TAB|wxTE_READONLY );
|
||||
explorerText->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
|
||||
|
||||
fgSizer12->Add( explorerText, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
bSizer411->Add( fgSizer12, 1, wxEXPAND, 5 );
|
||||
|
||||
|
||||
explorePanel->SetSizer( bSizer411 );
|
||||
explorePanel->Layout();
|
||||
bSizer411->Fit( explorePanel );
|
||||
dataNotebook->AddPage( explorePanel, wxT("a page"), false );
|
||||
|
||||
bSizer4->Add( dataNotebook, 1, wxEXPAND, 5 );
|
||||
|
||||
@@ -401,6 +502,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
writeButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnWriteButton ), NULL, this );
|
||||
browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowseButton ), NULL, this );
|
||||
formatButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnFormatButton ), NULL, this );
|
||||
exploreButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnExploreButton ), NULL, this );
|
||||
this->Connect( imagerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
imagerSaveImageButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveImageButton ), NULL, this );
|
||||
imagerSaveFluxButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveFluxButton ), NULL, this );
|
||||
@@ -422,6 +524,15 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( MainWindowGen::OnBrowserSelectionChanged ), NULL, this );
|
||||
browserDiscardButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserDiscardButton ), NULL, this );
|
||||
browserCommitButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserCommitButton ), NULL, this );
|
||||
this->Connect( explorerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
this->Connect( explorerRefreshTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerRefreshButton ) );
|
||||
explorerTrackSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerSideSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerStartTimeSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerClockSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerBitOffsetSpinCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerDecodeChoice->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerReverseCheckBox->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
}
|
||||
|
||||
MainWindowGen::~MainWindowGen()
|
||||
@@ -442,6 +553,7 @@ MainWindowGen::~MainWindowGen()
|
||||
writeButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnWriteButton ), NULL, this );
|
||||
browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowseButton ), NULL, this );
|
||||
formatButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnFormatButton ), NULL, this );
|
||||
exploreButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnExploreButton ), NULL, this );
|
||||
this->Disconnect( imagerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
imagerSaveImageButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveImageButton ), NULL, this );
|
||||
imagerSaveFluxButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveFluxButton ), NULL, this );
|
||||
@@ -459,6 +571,15 @@ MainWindowGen::~MainWindowGen()
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( MainWindowGen::OnBrowserSelectionChanged ), NULL, this );
|
||||
browserDiscardButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserDiscardButton ), NULL, this );
|
||||
browserCommitButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserCommitButton ), NULL, this );
|
||||
this->Disconnect( explorerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
this->Disconnect( explorerRefreshTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerRefreshButton ) );
|
||||
explorerTrackSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerSideSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerStartTimeSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerClockSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, wxSpinDoubleEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerBitOffsetSpinCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerDecodeChoice->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
explorerReverseCheckBox->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainWindowGen::OnExplorerSettingChange ), NULL, this );
|
||||
|
||||
delete browserMoreMenu;
|
||||
}
|
||||
|
||||
1323
src/gui/layout.fbp
1323
src/gui/layout.fbp
File diff suppressed because it is too large
Load Diff
@@ -34,9 +34,10 @@
|
||||
#include "visualisationcontrol.h"
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/dialog.h>
|
||||
#include "fluxviewercontrol.h"
|
||||
#include <wx/scrolbar.h>
|
||||
@@ -80,6 +81,7 @@ protected:
|
||||
wxButton* writeButton;
|
||||
wxButton* browseButton;
|
||||
wxButton* formatButton;
|
||||
wxButton* exploreButton;
|
||||
wxPanel* imagePanel;
|
||||
wxAuiToolBar* imagerToolbar;
|
||||
wxAuiToolBarItem* imagerBackTool;
|
||||
@@ -109,6 +111,25 @@ protected:
|
||||
wxButton* browserDiscardButton;
|
||||
wxButton* browserCommitButton;
|
||||
wxStaticText* m_staticText12;
|
||||
wxPanel* explorePanel;
|
||||
wxAuiToolBar* explorerToolbar;
|
||||
wxAuiToolBarItem* explorerBackTool;
|
||||
wxAuiToolBarItem* explorerRefreshTool;
|
||||
wxStaticText* m_staticText22;
|
||||
wxSpinCtrl* explorerTrackSpinCtrl;
|
||||
wxStaticText* m_staticText26;
|
||||
wxSpinCtrl* explorerSideSpinCtrl;
|
||||
wxStaticText* m_staticText231;
|
||||
wxSpinCtrlDouble* explorerStartTimeSpinCtrl;
|
||||
wxStaticText* m_staticText24;
|
||||
wxSpinCtrlDouble* explorerClockSpinCtrl;
|
||||
wxStaticText* m_staticText25;
|
||||
wxSpinCtrl* explorerBitOffsetSpinCtrl;
|
||||
wxStaticText* m_staticText27;
|
||||
wxChoice* explorerDecodeChoice;
|
||||
wxStaticText* m_staticText241;
|
||||
wxCheckBox* explorerReverseCheckBox;
|
||||
wxTextCtrl* explorerText;
|
||||
|
||||
// Virtual event handlers, override them in your derived class
|
||||
virtual void OnClose(wxCloseEvent& event)
|
||||
@@ -163,6 +184,10 @@ protected:
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
virtual void OnExploreButton(wxCommandEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
virtual void OnBackButton(wxCommandEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
@@ -243,6 +268,22 @@ protected:
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
virtual void OnExplorerRefreshButton(wxCommandEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
virtual void OnExplorerSettingChange(wxSpinEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
virtual void OnExplorerSettingChange(wxSpinDoubleEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
virtual void OnExplorerSettingChange(wxCommandEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
public:
|
||||
MainWindowGen(wxWindow* parent,
|
||||
|
||||
@@ -14,8 +14,9 @@ wxDEFINE_EVENT(EXEC_EVENT_TYPE, ExecEvent);
|
||||
class ExecEvent : public wxThreadEvent
|
||||
{
|
||||
public:
|
||||
ExecEvent(wxEventType commandType = EXEC_EVENT_TYPE, int id = 0):
|
||||
wxThreadEvent(commandType, id)
|
||||
ExecEvent(bool synchronous = true):
|
||||
wxThreadEvent(EXEC_EVENT_TYPE, 0),
|
||||
_synchronous(synchronous)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -35,6 +36,11 @@ public:
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
bool IsSynchronous() const
|
||||
{
|
||||
return _synchronous;
|
||||
}
|
||||
|
||||
void RunCallback() const
|
||||
{
|
||||
_callback();
|
||||
@@ -42,6 +48,7 @@ public:
|
||||
|
||||
private:
|
||||
std::function<void()> _callback;
|
||||
bool _synchronous;
|
||||
};
|
||||
|
||||
bool FluxEngineApp::OnInit()
|
||||
@@ -69,9 +76,11 @@ wxThread::ExitCode FluxEngineApp::Entry()
|
||||
Logger() << EmergencyStopMessage();
|
||||
}
|
||||
|
||||
runOnUiThread(
|
||||
postToUiThread(
|
||||
[&]
|
||||
{
|
||||
GetThread()->Wait();
|
||||
|
||||
_callback = nullptr;
|
||||
SendUpdateEvent();
|
||||
});
|
||||
@@ -114,7 +123,8 @@ bool FluxEngineApp::IsWorkerThreadRunning() const
|
||||
void FluxEngineApp::OnExec(const ExecEvent& event)
|
||||
{
|
||||
event.RunCallback();
|
||||
execSemaphore.Post();
|
||||
if (event.IsSynchronous())
|
||||
execSemaphore.Post();
|
||||
}
|
||||
|
||||
void runOnUiThread(std::function<void()> callback)
|
||||
@@ -125,4 +135,11 @@ void runOnUiThread(std::function<void()> callback)
|
||||
execSemaphore.Wait();
|
||||
}
|
||||
|
||||
void postToUiThread(std::function<void()> callback)
|
||||
{
|
||||
ExecEvent* event = new ExecEvent(false);
|
||||
event->SetCallback(callback);
|
||||
wxGetApp().QueueEvent(event);
|
||||
}
|
||||
|
||||
wxIMPLEMENT_APP(FluxEngineApp);
|
||||
|
||||
@@ -55,6 +55,30 @@ class MainWindow : public MainWindowGen
|
||||
private:
|
||||
class FilesystemOperation;
|
||||
|
||||
enum State
|
||||
{
|
||||
STATE_IDLE,
|
||||
STATE_IDLE__LAST,
|
||||
|
||||
STATE_READING_WORKING,
|
||||
STATE_READING_FAILED,
|
||||
STATE_READING_SUCCEEDED,
|
||||
STATE_READING__LAST,
|
||||
|
||||
STATE_WRITING_WORKING,
|
||||
STATE_WRITING_FAILED,
|
||||
STATE_WRITING_SUCCEEDED,
|
||||
STATE_WRITING__LAST,
|
||||
|
||||
STATE_BROWSING_WORKING,
|
||||
STATE_BROWSING_IDLE,
|
||||
STATE_BROWSING__LAST,
|
||||
|
||||
STATE_EXPLORING_WORKING,
|
||||
STATE_EXPLORING_IDLE,
|
||||
STATE_EXPLORING__LAST,
|
||||
};
|
||||
|
||||
public:
|
||||
MainWindow():
|
||||
MainWindowGen(nullptr),
|
||||
@@ -997,20 +1021,20 @@ public:
|
||||
|
||||
void QueueBrowserOperation(std::function<void()> f)
|
||||
{
|
||||
_filesystemQueue.push_back(f);
|
||||
KickBrowserQueue();
|
||||
_jobQueue.push_back(f);
|
||||
KickQueue(STATE_BROWSING_WORKING, STATE_BROWSING_IDLE);
|
||||
}
|
||||
|
||||
void KickBrowserQueue()
|
||||
void KickQueue(State workingState, State idleState)
|
||||
{
|
||||
bool running = wxGetApp().IsWorkerThreadRunning();
|
||||
if (!running)
|
||||
{
|
||||
_state = STATE_BROWSING_WORKING;
|
||||
_errorState = STATE_BROWSING_IDLE;
|
||||
_state = workingState;
|
||||
_errorState = idleState;
|
||||
UpdateState();
|
||||
runOnWorkerThread(
|
||||
[this]()
|
||||
[this, idleState]()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
@@ -1019,10 +1043,10 @@ public:
|
||||
[&]()
|
||||
{
|
||||
UpdateState();
|
||||
if (!_filesystemQueue.empty())
|
||||
if (!_jobQueue.empty())
|
||||
{
|
||||
f = _filesystemQueue.front();
|
||||
_filesystemQueue.pop_front();
|
||||
f = _jobQueue.front();
|
||||
_jobQueue.pop_front();
|
||||
}
|
||||
});
|
||||
if (!f)
|
||||
@@ -1034,7 +1058,7 @@ public:
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_state = STATE_BROWSING_IDLE;
|
||||
_state = idleState;
|
||||
UpdateState();
|
||||
});
|
||||
});
|
||||
@@ -1046,11 +1070,144 @@ public:
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
/* --- Config management
|
||||
* ------------------------------------------------ */
|
||||
/* --- Explorer -------------------------------------------------------- */
|
||||
|
||||
void OnExploreButton(wxCommandEvent& event) override
|
||||
{
|
||||
try
|
||||
{
|
||||
PrepareConfig();
|
||||
|
||||
visualiser->Clear();
|
||||
_filesystemModel->Clear(Path());
|
||||
_currentDisk = nullptr;
|
||||
|
||||
_state = STATE_EXPLORING_IDLE;
|
||||
UpdateState();
|
||||
ShowConfig();
|
||||
|
||||
_explorerFluxmap = nullptr;
|
||||
_explorerTrack = -1;
|
||||
_explorerSide = -1;
|
||||
_explorerUpdatePending = false;
|
||||
|
||||
UpdateExplorerData();
|
||||
}
|
||||
catch (const ErrorException& e)
|
||||
{
|
||||
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
|
||||
_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void OnExplorerSettingChange(wxSpinEvent& event) override
|
||||
{
|
||||
UpdateExplorerData();
|
||||
}
|
||||
|
||||
void OnExplorerSettingChange(wxSpinDoubleEvent& event) override
|
||||
{
|
||||
UpdateExplorerData();
|
||||
}
|
||||
|
||||
void OnExplorerSettingChange(wxCommandEvent& event) override
|
||||
{
|
||||
UpdateExplorerData();
|
||||
}
|
||||
|
||||
void OnExplorerRefreshButton(wxCommandEvent& event) override
|
||||
{
|
||||
_explorerFluxmap = nullptr;
|
||||
_explorerTrack = -1;
|
||||
_explorerSide = -1;
|
||||
_explorerUpdatePending = false;
|
||||
|
||||
UpdateExplorerData();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateExplorerData()
|
||||
{
|
||||
if (!_jobQueue.empty())
|
||||
{
|
||||
_explorerUpdatePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_explorerUpdatePending = false;
|
||||
QueueExplorerOperation(
|
||||
[this]()
|
||||
{
|
||||
/* You need to call this if the config changes to invalidate
|
||||
* any caches. */
|
||||
|
||||
Environment::reset();
|
||||
|
||||
int desiredTrack = explorerTrackSpinCtrl->GetValue();
|
||||
int desiredSide = explorerSideSpinCtrl->GetValue();
|
||||
if (!_explorerFluxmap || (desiredTrack != _explorerTrack) ||
|
||||
(desiredSide != _explorerSide))
|
||||
{
|
||||
auto fluxSource = FluxSource::create(config.flux_source());
|
||||
_explorerFluxmap =
|
||||
fluxSource->readFlux(desiredTrack, desiredSide)->next();
|
||||
_explorerTrack = desiredTrack;
|
||||
_explorerSide = desiredSide;
|
||||
}
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_state = STATE_EXPLORING_IDLE;
|
||||
UpdateState();
|
||||
|
||||
FluxmapReader fmr(*_explorerFluxmap);
|
||||
fmr.seek(explorerStartTimeSpinCtrl->GetValue() * 1e6);
|
||||
|
||||
FluxDecoder fluxDecoder(&fmr,
|
||||
explorerClockSpinCtrl->GetValue() * 1e3,
|
||||
DecoderProto());
|
||||
fluxDecoder.readBits(
|
||||
explorerBitOffsetSpinCtrl->GetValue());
|
||||
auto bits = fluxDecoder.readBits();
|
||||
|
||||
Bytes bytes;
|
||||
switch (explorerDecodeChoice->GetSelection())
|
||||
{
|
||||
case 0:
|
||||
bytes = toBytes(bits);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
bytes = decodeFmMfm(bits.begin(), bits.end());
|
||||
break;
|
||||
}
|
||||
|
||||
if (explorerReverseCheckBox->GetValue())
|
||||
bytes = bytes.reverseBits();
|
||||
|
||||
std::stringstream s;
|
||||
hexdump(s, bytes);
|
||||
|
||||
explorerText->SetValue(s.str());
|
||||
|
||||
if (_explorerUpdatePending)
|
||||
UpdateExplorerData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void QueueExplorerOperation(std::function<void()> f)
|
||||
{
|
||||
_jobQueue.push_back(f);
|
||||
KickQueue(STATE_EXPLORING_WORKING, STATE_EXPLORING_IDLE);
|
||||
}
|
||||
|
||||
/* --- Config management ----------------------------------------------- */
|
||||
|
||||
/* This sets the *global* config object. That's safe provided the worker
|
||||
* thread isn't running, otherwise you'll get a race. */
|
||||
public:
|
||||
void PrepareConfig()
|
||||
{
|
||||
assert(!wxGetApp().IsWorkerThreadRunning());
|
||||
@@ -1169,7 +1326,7 @@ public:
|
||||
_statusBar->HideProgressBar();
|
||||
_statusBar->SetRightLabel("");
|
||||
_state = _errorState;
|
||||
_filesystemQueue.clear();
|
||||
_jobQueue.clear();
|
||||
UpdateState();
|
||||
},
|
||||
|
||||
@@ -1362,8 +1519,15 @@ public:
|
||||
{
|
||||
dataNotebook->SetSelection(0);
|
||||
|
||||
readButton->Enable(_selectedSource != SELECTEDSOURCE_IMAGE);
|
||||
writeButton->Enable(_selectedSource == SELECTEDSOURCE_REAL);
|
||||
bool hasFormat = formatChoice->GetSelection() != wxNOT_FOUND;
|
||||
|
||||
readButton->Enable(
|
||||
(_selectedSource != SELECTEDSOURCE_IMAGE) && hasFormat);
|
||||
writeButton->Enable(
|
||||
(_selectedSource == SELECTEDSOURCE_REAL) && hasFormat);
|
||||
browseButton->Enable(hasFormat);
|
||||
formatButton->Enable(hasFormat);
|
||||
exploreButton->Enable(_selectedSource != SELECTEDSOURCE_IMAGE);
|
||||
}
|
||||
else if (_state < STATE_READING__LAST)
|
||||
{
|
||||
@@ -1391,7 +1555,7 @@ public:
|
||||
{
|
||||
dataNotebook->SetSelection(2);
|
||||
|
||||
bool running = !_filesystemQueue.empty();
|
||||
bool running = !_jobQueue.empty();
|
||||
bool selection = browserTree->GetSelection().IsOk();
|
||||
|
||||
browserToolbar->EnableTool(
|
||||
@@ -1421,6 +1585,13 @@ public:
|
||||
browserDiscardButton->Enable(!running && needsFlushing);
|
||||
browserCommitButton->Enable(!running && needsFlushing);
|
||||
}
|
||||
else if (_state < STATE_EXPLORING__LAST)
|
||||
{
|
||||
dataNotebook->SetSelection(3);
|
||||
|
||||
explorerToolbar->EnableTool(
|
||||
explorerBackTool->GetId(), _state == STATE_EXPLORING_IDLE);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
@@ -1461,26 +1632,6 @@ private:
|
||||
SELECTEDSOURCE_IMAGE
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
STATE_IDLE,
|
||||
STATE_IDLE__LAST,
|
||||
|
||||
STATE_READING_WORKING,
|
||||
STATE_READING_FAILED,
|
||||
STATE_READING_SUCCEEDED,
|
||||
STATE_READING__LAST,
|
||||
|
||||
STATE_WRITING_WORKING,
|
||||
STATE_WRITING_FAILED,
|
||||
STATE_WRITING_SUCCEEDED,
|
||||
STATE_WRITING__LAST,
|
||||
|
||||
STATE_BROWSING_WORKING,
|
||||
STATE_BROWSING_IDLE,
|
||||
STATE_BROWSING__LAST
|
||||
};
|
||||
|
||||
class FilesystemOperation
|
||||
{
|
||||
public:
|
||||
@@ -1539,8 +1690,8 @@ private:
|
||||
wxDataFormat _dndFormat;
|
||||
std::vector<std::string> _formatNames;
|
||||
std::vector<std::unique_ptr<const CandidateDevice>> _devices;
|
||||
int _state = STATE_IDLE;
|
||||
int _errorState;
|
||||
State _state = STATE_IDLE;
|
||||
State _errorState;
|
||||
int _selectedSource;
|
||||
bool _dontSaveConfig = false;
|
||||
std::shared_ptr<const DiskFlux> _currentDisk;
|
||||
@@ -1554,7 +1705,11 @@ private:
|
||||
bool _filesystemIsReadOnly;
|
||||
bool _filesystemNeedsFlushing;
|
||||
FilesystemModel* _filesystemModel;
|
||||
std::deque<std::function<void()>> _filesystemQueue;
|
||||
std::deque<std::function<void()>> _jobQueue;
|
||||
int _explorerTrack;
|
||||
int _explorerSide;
|
||||
bool _explorerUpdatePending;
|
||||
std::unique_ptr<const Fluxmap> _explorerFluxmap;
|
||||
};
|
||||
|
||||
wxWindow* FluxEngineApp::CreateMainWindow()
|
||||
|
||||
@@ -272,7 +272,8 @@ void VisualisationControl::Clear()
|
||||
|
||||
void VisualisationControl::SetTrackData(std::shared_ptr<const TrackFlux> track)
|
||||
{
|
||||
key_t key = {track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
|
||||
key_t key = {
|
||||
track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
|
||||
_tracks[key] = track;
|
||||
_sectors.erase(key);
|
||||
for (auto& sector : track->sectors)
|
||||
@@ -286,7 +287,8 @@ void VisualisationControl::SetDiskData(std::shared_ptr<const DiskFlux> disk)
|
||||
_sectors.clear();
|
||||
for (const auto& track : disk->tracks)
|
||||
{
|
||||
key_t key = {track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
|
||||
key_t key = {
|
||||
track->trackInfo->physicalTrack, track->trackInfo->physicalSide};
|
||||
_tracks[key] = track;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user