Compare commits

..

111 Commits
gui2 ... scp

Author SHA1 Message Date
dg
ccd9539015 Adjust the SCP write logic so an unspecified TPI is treated as 96 (the usual). 2023-04-07 09:02:46 +00:00
dg
9300aa79c3 Read 48tpi SCP files correctly. 2023-04-06 21:49:06 +00:00
David Given
9e522c7da2 Merge ef60bfff6b into df6e47fa50 2023-04-06 18:20:31 +00:00
dg
ef60bfff6b Looks like the Roland D-20 format is the same as Brother240??? 2023-04-06 17:07:00 +00:00
dg
635c6c7bfe Add an explorer option to show raw bits. 2023-04-06 16:07:18 +00:00
David Given
df6e47fa50 Merge pull request #659 from davidgiven/n88
Add a histogram viewer to the imager. Because it's there.
2023-04-06 11:20:36 +02:00
dg
654cdcd3d1 Add a histogram viewer to the imager. Because it's there. 2023-04-06 08:59:05 +00:00
dg
a633b73e12 Add boilerplate for Roland D20 decoder. 2023-04-05 22:36:54 +00:00
David Given
ba93dae240 Merge pull request #657 from davidgiven/d20
Improve the explorer.
2023-04-05 23:11:49 +02:00
dg
8e0ca85d1e Add the histogram viewer and clock guess button. 2023-04-05 20:43:49 +00:00
dg
56a4926bd3 Factor out the clock guess code so it can be used elsewhere. 2023-04-05 19:17:37 +00:00
dg
6a2aae4ef2 Create new branch named "d20" 2023-04-05 17:47:31 +00:00
dg
ec68ce3bfa Try to fix dev release. 2023-04-04 22:43:06 +00:00
dg
a777a5be30 Typo fix. 2023-04-04 21:48:45 +00:00
David Given
b553a8b1fb Merge pull request #654 from davidgiven/search
Overhaul the GUI, to make it... gooier.
2023-04-04 22:37:37 +01:00
dg
b119e1f72d Tidying. 2023-04-04 21:02:03 +00:00
dg
7345d3e6c1 Fix merge conflict. 2023-04-04 20:23:05 +00:00
dg
e9b7a7bb52 Fix the icon background colour on Windows. 2023-04-04 20:20:32 +00:00
dg
2022732dd9 Some final tidying. 2023-04-04 20:12:21 +00:00
dg
63544647b6 Add a custom IconButton class. Rework the source icon list. Again. 2023-04-04 19:42:24 +00:00
dg
6b62585ad5 Be more intelligent about resizing the main window. 2023-04-03 22:45:25 +00:00
dg
14027210f7 Even more GUI tweaking. 2023-04-03 21:53:36 +00:00
dg
3df17b23b8 Turns out you can't unselect exclusive options in the GUI, so add an 'off' for
the Apple filesystem selection.
2023-04-03 21:53:26 +00:00
dg
cbf3f56562 The xxd binary is in the vim package. For some reason. 2023-04-02 22:59:20 +00:00
dg
1f74d9189f Make the new GUI actually work, to a certain extent. 2023-04-02 22:54:09 +00:00
dg
137658d1d6 Flesh out the source list a bit. 2023-04-02 21:34:02 +00:00
dg
5b627bd2b1 wxImageList tweak. 2023-04-02 19:54:08 +00:00
dg
38ff08885a Experiment with wxImageList. 2023-04-02 19:42:55 +00:00
dg
a89993aabb Fix the UI. 2023-04-02 19:19:16 +00:00
dg
d6353403e2 Set the icon again. 2023-04-02 17:14:59 +00:00
dg
bc62ee04c0 Some random tweaks to improve state machine look and feel. 2023-04-02 17:11:46 +00:00
dg
d3ff836b63 Put the headers in the right order to keep Windows happy. 2023-04-02 16:54:37 +00:00
dg
a7aac5578e Remove the explorer search button for now. 2023-04-02 16:41:56 +00:00
dg
add5a141d3 Actually make the new GUI model work. Mostly? 2023-04-02 12:38:12 +00:00
dg
330410ec61 Rework the GUI so that each panel is a different class. It doesn't work yet,
but the bulk of the restructuring is done.
2023-04-02 12:37:27 +00:00
dg
d0f49dcfa6 Add (but don't implement) the explorer search box. 2023-04-01 18:27:01 +00:00
David Given
124f6ab7cb Merge 471f63592e into e4204196cd 2023-04-01 13:05:59 +00:00
dg
471f63592e Typo fix. 2023-04-01 12:56:17 +00:00
dg
50e210c72f It seems the build artifact needs to be renamed for 10.15. 2023-04-01 12:40:05 +00:00
dg
d3396aa535 Use two threads for building --- seems we can do this on github. 2023-04-01 12:32:47 +00:00
dg
5ed8b838bc Another typo fix. 2023-04-01 12:15:04 +00:00
dg
d1757eacc2 Typo fix. 2023-04-01 12:14:37 +00:00
dg
0692e5f5d5 Try building for OSX 10.15 and see what happens. 2023-04-01 12:13:34 +00:00
David Given
e4204196cd Merge pull request #650 from davidgiven/flags
Allow options to be set in the GUI.
2023-03-31 23:37:17 +01:00
dg
241d4342e4 Make exclusivity groups work in the GUI. 2023-03-31 22:11:40 +00:00
dg
c04cbc631c Option name tidy. 2023-03-31 22:11:19 +00:00
dg
29b273ad7b Correctly set the path of files. 2023-03-31 22:10:47 +00:00
dg
9720dab2f6 Optimise the option radiobuttons a bit. 2023-03-31 22:10:13 +00:00
dg
bddc64a324 Merge from master. 2023-03-31 22:09:11 +00:00
David Given
eb324f14de Merge pull request #648 from davidgiven/basis
Add support for the Basis-108 Apple II clone.
2023-03-31 22:34:32 +01:00
David Given
b78a057c81 Merge branch 'master' into basis 2023-03-31 22:10:47 +01:00
dg
5751725213 Allow options to be selected in the GUI. 2023-03-31 21:09:40 +00:00
dg
f194392f99 Fix the broken AppleDOS double-sided disks. Allow access to side 1 on AppleDOS
volumes.
2023-03-31 18:24:03 +00:00
dg
fea62178af Apply what might be the right translation to the CP/M boot tracks. 2023-03-31 18:06:21 +00:00
David Given
33ef4ce8de Merge pull request #649 from davidgiven/pme
Rename the PME format to psos800.
2023-03-31 18:56:34 +01:00
dg
3728120f95 Add support for CP/M disks and filesystems. 2023-03-31 17:56:18 +00:00
dg
2944b9b3f6 Rename the PME format to psos800. 2023-03-31 17:23:33 +00:00
David Given
3430574364 Merge pull request #646 from davidgiven/pme
Add a format for the PME-68-12 SBC.
2023-03-31 11:59:00 +01:00
dg
fc5a5212c0 Merge. 2023-03-30 22:21:30 +00:00
dg
20f724ed13 Update README. 2023-03-30 22:21:00 +00:00
dg
94c1d21938 Rename the pme profile to pme68_800. 2023-03-30 22:20:29 +00:00
David Given
a1a9666b6f Fix the AppleDOS sector translation. 2023-03-30 12:26:13 +02:00
dg
0551ddc276 Add write support for Apple II 640kB disks. 2023-03-28 20:36:43 +00:00
dg
049ffd3b04 Add a profile for the Basis Apple II format. 2023-03-28 19:40:58 +00:00
dg
c28f757c5c Add a very prototype AppleDOS VFS plugin. 2023-03-28 19:29:02 +00:00
dg
91dbb86e64 Add missing files. Rename the Apple II formats. 2023-03-28 16:29:59 +00:00
dg
27a04ee22b Add initial support for the Basis-108. 2023-03-27 23:07:59 +00:00
dg@cowlark.com
5cefce9922 Fix the thread termination errors every time the directory browser is used. 2023-03-27 21:06:59 +00:00
dg
8fb4c90bed Remove the retry limit when reading from virtual flux sources, to allow flux
files with very large numbers of reads to be processed.
2023-03-27 20:14:49 +00:00
dg
81753669cc Add the 'fluxengine merge' command. 2023-03-27 20:12:46 +00:00
dg
0a0a72bcf3 Add configurable head jiggle on error, just to see if the head needs settling. 2023-03-27 18:40:35 +00:00
dg
c4a6e3e063 Fix the Windows development build artifact. 2023-03-26 23:15:20 +00:00
dg
1138e6b77f Try a different way to fetch the filedes length. 2023-03-26 21:22:11 +00:00
dg
030f9218d6 Hopefully fix the layout this time? 2023-03-26 21:17:07 +00:00
dg
2fff32e8f2 Don't return bad data which makes the GUI crash. 2023-03-26 18:52:29 +00:00
dg
5b2aa9926f Robustness and warning fixes. 2023-03-26 18:50:14 +00:00
dg
921e178e83 Tone down the bad-sector-size warning a bit. 2023-03-26 18:23:25 +00:00
dg
25ffd900c8 Realise that the PME format is HCS. Add a really basic and probably broken
PHILE filesystem reader.
2023-03-26 18:21:51 +00:00
dg
7ea4e116cc Add a warning if the configured sector size doesn't match the one on disk. 2023-03-26 16:25:40 +00:00
dg
a9daec36f5 Add prototype PME-68-12 format. 2023-03-24 21:07:48 +00:00
David Given
cebc7c6cd2 Merge 3f85c9f006 into 909f0d628b 2023-01-06 21:30:18 +00:00
dg
3f85c9f006 Adjust timings to be more correct. 2023-01-06 21:28:51 +00:00
dg
ed5efd7b87 Reenable optimisation. Again. 2023-01-06 21:28:35 +00:00
dg
4984a53bfd First hypothetically working version of the agat encoder. 2023-01-05 18:36:01 +00:00
dg
b0c77653a2 Add the boilerplate for the Agat encoder. 2023-01-05 12:04:36 +00:00
David Given
909f0d628b Merge pull request #637 from davidgiven/cpm
Fix an issue with extent handling in the CP/M file system.
2022-12-18 23:21:45 +01:00
dg
e27e3ada92 Fix an issue with extent handling in the CP/M file system; actually add a CP/M
test.
2022-12-18 22:00:52 +00:00
dg
339ea3b5a4 Move the * and + Bytes methods onto Bytes itself. 2022-12-18 22:00:16 +00:00
dg
9bd8b8915e Update format file. 2022-12-18 21:59:14 +00:00
dg
35008656a9 Remove stray logging. 2022-12-17 17:54:33 +00:00
David Given
825089458f Merge pull request #636 from davidgiven/tiki
Add support for the Tiki 100 formats.
2022-12-17 12:20:18 +01:00
dg
4a086d94b7 Add best-guess CP/M filesystem definitions for the Tiki 90kB and 800kB formats. 2022-12-17 11:01:46 +00:00
dg
0aeddf7e98 Add support for the Tiki 100 formats. 2022-12-17 10:59:30 +00:00
David Given
4922d1deb4 Merge pull request #634 from davidgiven/mac2
Fix sector skew, again.
2022-12-05 21:57:45 +01:00
dg
86d0893261 Adjust mac encoder clock to be more like the real thing. 2022-12-05 20:27:52 +00:00
dg
e4c67f18bd Fix the sector skew stuff, again. Modify the mac400 format to emit sectors in
the right order.
2022-12-05 20:22:01 +00:00
David Given
d07c5a94e1 Merge pull request #632 from davidgiven/layout
Rework the layout stuff to be more correct.
2022-12-04 21:32:17 +01:00
dg
a91dee27e7 Rework the layout stuff to be more correct. Physical skew no longer affects the
order in the resulting images.
2022-12-04 19:19:37 +00:00
David Given
e3219087c9 Merge pull request #630 from davidgiven/brother
Fix some nasty Brother bugs.
2022-12-02 22:20:32 +01:00
dg
cc9ec84aec Physical skew turns out to be horribly broken, so turn it off for the Brother
formats (the only ones which use it) until we can sort it out.
2022-12-02 20:17:42 +00:00
dg
a33cc5710c Be more rigorous about checking for invalid brother120fs filesystems --- even
though the filesystem is so simple that positively identifying it is quite
hard.
2022-12-02 19:54:58 +00:00
David Given
c2b148288a Merge pull request #628 from davidgiven/osx
Fix a bunch of OSX things.
2022-12-01 22:24:47 +01:00
David Given
a483567564 Fix the explorer to work on OSX. Lots of other vaguely OSX-related changes. 2022-12-01 21:37:59 +01:00
David Given
bd99bc6d94 Don't trust isprint() to return ascii characters, because Unicode. 2022-12-01 21:28:49 +01:00
David Given
8f79071aad Turn optimisation back on! 2022-12-01 21:28:31 +01:00
David Given
ef9071049b Merge pull request #627 from davidgiven/osx
Produce more correct OSX app bundles.
2022-12-01 20:53:58 +01:00
David Given
60e1ab8cca Dependency fix? 2022-12-01 20:21:33 +01:00
David Given
d3dbfd3154 Use dylibbundler to create possibly-working OSX app bundles. 2022-12-01 19:49:50 +01:00
David Given
ee2dffb498 Try and generate correct OSX app bundles. 2022-12-01 19:45:51 +01:00
David Given
6d9510cc65 Merge pull request #626 from elosha/macosxfixes
Library fallback path fixed & MacPorts compatible
2022-12-01 17:17:36 +01:00
Eliza Winterborn
49f0f5d000 Library fallback path fixed & MacPorts compatible
Use correct variable. Also look for libs in MacPorts' default lib path /opt/local/lib, not just HomeBrew's
2022-12-01 17:03:36 +01:00
112 changed files with 10610 additions and 8411 deletions

View File

@@ -18,17 +18,19 @@ AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakConstructorInitializers: 'AfterColon'
BreakBeforeBraces: Allman
BreakConstructorInitializers: 'AfterColon'
BreakInheritanceList: AfterColon
BreakStringLiterals: 'true'
IndentCaseLabels: 'true'
IndentWidth: '4'
ColumnLimit: '80'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
FixNamespaceComments: 'false'
IncludeBlocks: Preserve
IndentCaseLabels: 'true'
IndentWidth: '4'
IndentWrappedFunctionNames: 'false'
KeepEmptyLinesAtTheStartOfBlocks: 'true'
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'false'

View File

@@ -10,16 +10,16 @@ jobs:
- name: apt
run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev
- name: make
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j2
build-macos:
build-macos-current:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: gmake
run: gmake -j2
- name: Upload build artifacts
uses: actions/upload-artifact@v2
@@ -27,6 +27,23 @@ jobs:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: FluxEngine.pkg
build-macos-10-15:
runs-on: macos-10.15
steps:
- uses: actions/checkout@v2
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: |
gmake -j2
mv FluxEngine.pkg FluxEngine-10.15.pkg
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: FluxEngine-10.15.pkg
build-windows:
runs-on: windows-latest
defaults:
@@ -50,9 +67,10 @@ jobs:
mingw-w64-i686-zlib
mingw-w64-i686-nsis
zip
vim
- uses: actions/checkout@v1
- name: build
run: make
run: make -j2
- name: nsis
run: |
@@ -62,7 +80,7 @@ jobs:
- name: zip
run: |
zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe
zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe
- name: Upload build artifacts
uses: actions/upload-artifact@v2

View File

@@ -29,11 +29,12 @@ jobs:
mingw-w64-i686-zlib
mingw-w64-i686-nsis
zip
vim
- uses: actions/checkout@v3
- name: build
run: |
make
make -j2
- name: nsis
run: |
@@ -83,7 +84,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: gmake

View File

@@ -48,7 +48,7 @@ AR ?= $(CCPREFIX)ar
PKG_CONFIG ?= pkg-config
WX_CONFIG ?= wx-config
PROTOC ?= protoc
CFLAGS ?= -g -O0
CFLAGS ?= -g -O3
CXXFLAGS += -std=c++17
LDFLAGS ?=
PLATFORM ?= UNIX
@@ -111,6 +111,7 @@ PROTOS = \
arch/micropolis/micropolis.proto \
arch/mx/mx.proto \
arch/northstar/northstar.proto \
arch/rolandd20/rolandd20.proto \
arch/smaky6/smaky6.proto \
arch/tids990/tids990.proto \
arch/victor9k/victor9k.proto \
@@ -163,19 +164,20 @@ define do-encodedecodetest-impl
tests: $(OBJDIR)/$1$3.flux.encodedecode
$(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
@mkdir -p $(dir $$@)
@echo ENCODEDECODETEST .flux $1 $3
@echo ENCODEDECODETEST $1 flux $(FLUXENGINE_BIN) $2 $3
@scripts/encodedecodetest.sh $1 flux $(FLUXENGINE_BIN) $2 $3 > $$@
tests: $(OBJDIR)/$1$3.scp.encodedecode
$(OBJDIR)/$1$3.scp.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
@mkdir -p $(dir $$@)
@echo ENCODEDECODETEST .scp $1 $3
@echo ENCODEDECODETEST $1 scp $(FLUXENGINE_BIN) $2 $3
@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@
endef
$(call do-encodedecodetest,agat840)
$(call do-encodedecodetest,amiga)
$(call do-encodedecodetest,apple2)
$(call do-encodedecodetest,appleii140)
$(call do-encodedecodetest,atarist360)
$(call do-encodedecodetest,atarist370)
$(call do-encodedecodetest,atarist400)

View File

@@ -106,7 +106,7 @@ people who've had it work).
| [Acorn DFS](doc/disk-acorndfs.md) | 🦄 | 🦖* | |
| [Ampro Little Board](doc/disk-ampro.md) | 🦖 | 🦖* | |
| [Agat](doc/disk-agat.md) | 🦖 | | Soviet Union Apple-II-like computer |
| [Apple II](doc/disk-apple2.md) | 🦄 | 🦄 | |
| [Apple II](doc/disk-apple2.md) | 🦄 | 🦄 | both 140kB and 640kB formats |
| [Amiga](doc/disk-amiga.md) | 🦄 | 🦄 | |
| [Commodore 64 1541/1581](doc/disk-c64.md) | 🦄 | 🦄 | and probably the other formats |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦄 | |
@@ -115,7 +115,8 @@ people who've had it work).
| [Elektronika BK](doc/disk-bd.md) | 🦄 | 🦄 | Soviet Union PDP-11 clone |
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
| [pSOS](doc/disk-ibm.md) | 🦄 | 🦖* | pSOS PHILE file system |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | yet another IBM scheme |
| [Smaky 6](doc/disk-smaky6.md) | 🦖 | | 5.25" hard sectored |
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
{: .datatable }

View File

@@ -3,7 +3,16 @@
#define AGAT_SECTOR_SIZE 256
static constexpr uint64_t SECTOR_ID = 0x8924555549111444;
static constexpr uint64_t DATA_ID = 0x8924555514444911;
class Encoder;
class EncoderProto;
class Decoder;
class DecoderProto;
extern std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config);
extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config);
extern uint8_t agatChecksum(const Bytes& bytes);

View File

@@ -1,5 +1,19 @@
syntax = "proto2";
import "lib/common.proto";
message AgatDecoderProto {}
message AgatEncoderProto {
optional double target_clock_period_us = 1
[default=2.00, (help)="Data clock period of target format."];
optional double target_rotational_period_ms = 2
[default=200.0, (help)="Rotational period of target format."];
optional int32 post_index_gap_bytes = 3
[default=40, (help)="Post-index gap before first sector header."];
optional int32 pre_sector_gap_bytes = 4
[default=11, (help)="Gap before each sector header."];
optional int32 pre_data_gap_bytes = 5
[default=2, (help)="Gap before each sector data record."];
}

View File

@@ -33,10 +33,7 @@
*
*/
static const uint64_t SECTOR_ID = 0x8924555549111444;
static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID);
static const uint64_t DATA_ID = 0x8924555514444911;
static const FluxPattern DATA_PATTERN(64, DATA_ID);
static const FluxMatchers ALL_PATTERNS = {

118
arch/agat/encoder.cc Normal file
View File

@@ -0,0 +1,118 @@
#include "lib/globals.h"
#include "lib/decoders/decoders.h"
#include "lib/encoders/encoders.h"
#include "agat.h"
#include "lib/crc.h"
#include "lib/readerwriter.h"
#include "lib/image.h"
#include "lib/layout.h"
#include "arch/agat/agat.pb.h"
#include "lib/encoders/encoders.pb.h"
class AgatEncoder : public Encoder
{
public:
AgatEncoder(const EncoderProto& config):
Encoder(config),
_config(config.agat())
{
}
private:
void writeRawBits(uint64_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void writeByte(uint8_t byte)
{
Bytes b;
b.writer().write_8(byte);
writeBytes(b);
}
void writeFillerRawBytes(int count, uint16_t byte)
{
for (int i = 0; i < count; i++)
writeRawBits(byte, 16);
};
void writeFillerBytes(int count, uint8_t byte)
{
Bytes b{byte};
for (int i = 0; i < count; i++)
writeBytes(b);
};
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
auto trackLayout = Layout::getLayoutOfTrack(
trackInfo->logicalTrack, trackInfo->logicalSide);
double clockRateUs = _config.target_clock_period_us() / 2.0;
int bitsPerRevolution =
(_config.target_rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
writeFillerRawBytes(_config.post_index_gap_bytes(), 0xaaaa);
for (const auto& sector : sectors)
{
/* Header */
writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa);
writeRawBits(SECTOR_ID, 64);
writeByte(0x5a);
writeByte((sector->logicalTrack << 1) | sector->logicalSide);
writeByte(sector->logicalSector);
writeByte(0x5a);
/* Data */
writeFillerRawBytes(_config.pre_data_gap_bytes(), 0xaaaa);
auto data = sector->data.slice(0, AGAT_SECTOR_SIZE);
writeRawBits(DATA_ID, 64);
writeBytes(data);
writeByte(agatChecksum(data));
writeByte(0x5a);
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
fillBitmapTo(_bits, _cursor, _bits.size(), {true, false});
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBits(_bits,
calculatePhysicalClockPeriod(_config.target_clock_period_us() * 1e3,
_config.target_rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const AgatEncoderProto& _config;
uint32_t _cursor;
bool _lastBit;
std::vector<bool> _bits;
};
std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config)
{
return std::unique_ptr<Encoder>(new AgatEncoder(config));
}

View File

@@ -2,7 +2,10 @@ syntax = "proto2";
import "lib/common.proto";
message Apple2DecoderProto {}
message Apple2DecoderProto {
optional uint32 side_one_track_offset = 1
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
}
message Apple2EncoderProto
{
@@ -13,4 +16,7 @@ message Apple2EncoderProto
/* Apple II disk drives spin at 300rpm. */
optional double rotational_period_ms = 2
[ default = 200.0, (help) = "rotational period on the real device" ];
optional uint32 side_one_track_offset = 3
[ default = 0, (help) = "offset to apply to track numbers on side 1" ];
}

View File

@@ -5,6 +5,8 @@
#include "decoders/decoders.h"
#include "sector.h"
#include "apple2.h"
#include "arch/apple2/apple2.pb.h"
#include "lib/decoders/decoders.pb.h"
#include "bytes.h"
#include "fmt/format.h"
#include <string.h>
@@ -12,22 +14,25 @@
const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
const FluxMatchers ANY_RECORD_PATTERN(
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
#define GCR_ENTRY(gcr, data) \
case gcr: return data;
#include "data_gcr.h"
#undef GCR_ENTRY
#define GCR_ENTRY(gcr, data) \
case gcr: \
return data;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
/* This is extremely inspired by the MESS implementation, written by Nathan
* Woods and R. Belmont:
* https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
*/
static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
{
@@ -47,9 +52,11 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status)
{
/* 3 * 2 bit */
output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02);
output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
output[i + 86] =
((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02);
if ((i + 172) < APPLE2_SECTOR_LENGTH)
output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
output[i + 172] =
((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02);
}
}
@@ -67,88 +74,102 @@ static uint8_t combine(uint16_t word)
class Apple2Decoder : public Decoder
{
public:
Apple2Decoder(const DecoderProto& config):
Decoder(config)
{}
Apple2Decoder(const DecoderProto& config): Decoder(config) {}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ANY_RECORD_PATTERN);
}
{
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord() override
{
if (readRaw24() != APPLE2_SECTOR_RECORD)
return;
{
if (readRaw24() != APPLE2_SECTOR_RECORD)
return;
/* Read header. */
/* Read header. */
auto header = toBytes(readRawBits(8*8)).slice(0, 8);
ByteReader br(header);
auto header = toBytes(readRawBits(8 * 8)).slice(0, 8);
ByteReader br(header);
uint8_t volume = combine(br.read_be16());
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSector = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16());
uint8_t volume = combine(br.read_be16());
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16());
// If the checksum is correct, upgrade the sector from MISSING
// to DATA_MISSING in anticipation of its data record
if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
// If the checksum is correct, upgrade the sector from MISSING
// to DATA_MISSING in anticipation of its data record
if (checksum ==
(volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
if (_sector->logicalSide == 1)
_sector->logicalTrack -= _config.apple2().side_one_track_offset();
/* Sanity check. */
if (_sector->logicalTrack > 100)
{
_sector->status = Sector::MISSING;
return;
}
}
void decodeDataRecord() override
{
/* Check ID. */
{
/* Check ID. */
if (readRaw24() != APPLE2_DATA_RECORD)
return;
if (readRaw24() != APPLE2_DATA_RECORD)
return;
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
/* Read and decode data. */
/* Read and decode data. */
auto readApple8 = [&]() {
auto result = 0;
while((result & 0x80) == 0) {
auto b = readRawBits(1);
if(b.empty()) break;
result = (result << 1) | b[0];
}
return result;
};
auto readApple8 = [&]()
{
auto result = 0;
while ((result & 0x80) == 0)
{
auto b = readRawBits(1);
if (b.empty())
break;
result = (result << 1) | b[0];
}
return result;
};
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH+2;
uint8_t bytes[recordLength];
for(auto &byte : bytes) {
byte = readApple8();
}
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
uint8_t bytes[recordLength];
for (auto& byte : bytes)
{
byte = readApple8();
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
};
std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new Apple2Decoder(config));
return std::unique_ptr<Decoder>(new Apple2Decoder(config));
}

View File

@@ -56,8 +56,7 @@ public:
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
calculatePhysicalClockPeriod(
_config.clock_period_us() * 1e3,
calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6));
return fluxmap;
}
@@ -132,13 +131,17 @@ private:
// extra padding.
write_ff40(sector.logicalSector == 0 ? 32 : 8);
int track = sector.logicalTrack;
if (sector.logicalSide == 1)
track += _config.side_one_track_offset();
// Write address field: APPLE2_SECTOR_RECORD + sector identifier +
// DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
write_gcr44(volume_id);
write_gcr44(sector.logicalTrack);
write_gcr44(track);
write_gcr44(sector.logicalSector);
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
write_gcr44(volume_id ^ track ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector

View File

@@ -2,6 +2,7 @@ LIBARCH_SRCS = \
arch/aeslanier/decoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
arch/agat/encoder.cc \
arch/amiga/amiga.cc \
arch/amiga/decoder.cc \
arch/amiga/encoder.cc \
@@ -23,6 +24,7 @@ LIBARCH_SRCS = \
arch/mx/decoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/rolandd20/decoder.cc \
arch/smaky6/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \

View File

@@ -147,6 +147,7 @@ public:
_sector->logicalSide = br.read_8();
_sector->logicalSector = br.read_8();
_currentSectorSize = 1 << (br.read_8() + 7);
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
uint16_t wantCrc = br.read_be16();
if (wantCrc == gotCrc)
@@ -206,6 +207,18 @@ public:
uint16_t wantCrc = br.read_be16();
_sector->status =
(wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
auto layout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
if (_currentSectorSize != layout->sectorSize)
std::cerr << fmt::format(
"Warning: configured sector size for t{}.h{}.s{} is {} bytes "
"but that seen on disk is {} bytes\n",
_sector->logicalTrack,
_sector->logicalSide,
_sector->logicalSector,
layout->sectorSize,
_currentSectorSize);
}
private:

View File

@@ -16,14 +16,14 @@ static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{
if (track < 16)
return 2.623;
return 2.63;
if (track < 32)
return 2.861;
return 2.89;
if (track < 48)
return 3.148;
return 3.20;
if (track < 64)
return 3.497;
return 3.934;
return 3.57;
return 3.98;
}
static unsigned sectorsForTrack(unsigned track)

56
arch/rolandd20/decoder.cc Normal file
View File

@@ -0,0 +1,56 @@
#include "lib/globals.h"
#include "lib/decoders/decoders.h"
#include "lib/crc.h"
#include "lib/fluxmap.h"
#include "lib/decoders/fluxmapreader.h"
#include "lib/sector.h"
#include "lib/bytes.h"
#include "rolandd20.h"
#include <string.h>
/* Sector header record:
*
* BF FF FF FF FF FF FE AB
*
* This encodes to:
*
* e d 5 5 5 5 5 5
* 1110 1101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 5 5 5 5 5
* 0101 0101 0101 0101 0101 0101 0101 0101
* 5 5 5 4 4 4 4 5
* 0101 0101 0101 0100 0100 0100 0100 0101
*/
static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL);
class RolandD20Decoder : public Decoder
{
public:
RolandD20Decoder(const DecoderProto& config):
Decoder(config)
{}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(SECTOR_PATTERN);
}
void decodeSectorRecord() override
{
auto rawbits = readRawBits(256);
const auto& bytes = decodeFmMfm(rawbits);
fmt::print("{} ", _sector->clock);
hexdump(std::cout, bytes);
}
};
std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new RolandD20Decoder(config));
}

View File

@@ -0,0 +1,4 @@
#pragma once
extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config);

View File

@@ -0,0 +1,5 @@
syntax = "proto2";
message RolandD20DecoderProto {}

View File

@@ -55,6 +55,7 @@ proto_cc_library {
"./arch/micropolis/micropolis.proto",
"./arch/mx/mx.proto",
"./arch/northstar/northstar.proto",
"./arch/rolandd20/rolandd20.proto",
"./arch/tids990/tids990.proto",
"./arch/victor9k/victor9k.proto",
"./arch/zilogmcz/zilogmcz.proto",
@@ -93,6 +94,7 @@ clibrary {
"./arch/mx/decoder.cc",
"./arch/northstar/decoder.cc",
"./arch/northstar/encoder.cc",
"./arch/rolandd20/rolandd20.cc",
"./arch/tids990/decoder.cc",
"./arch/tids990/encoder.cc",
"./arch/victor9k/decoder.cc",

View File

@@ -30,6 +30,34 @@ FluxEngine can remap the sectors from physical to logical using modifiers. If
you don't specify a remapping modifier, you get the sectors in the order they
appear on the disk.
If you don't want an image in physical sector order, specify one of these options:
- `--appledos` Selects AppleDOS sector translation
- `--prodos` Selects ProDOS sector translation
- `--cpm` Selects CP/M SoftCard sector translation[^1][^2]
These options also select the appropriate file system; FluxEngine has read-only
support for all of these. For example:
```
fluxengine ls appleii140 --appledos -f image.flux
```
In addition, some third-party systems use 80-track double sides drives, with
the same underlying disk format. These are supported with the `appleii640`
profile. The complication here is that the AppleDOS filesystem only supports up
to 50 tracks, so it needs tweaking to support larger disks. It treats the
second side of the disk as a completely different volume. To access these
files, use `--appledos --side1`.
[^1]: CP/M disks use the ProDOS translation for the first three tracks and a
different translation for all the tracks thereafter.
[^2]: 80-track CP/M disks are interesting because all the tracks on the second
side have on-disk track numbering from 80..159; the Apple II on-disk format
doesn't have a side byte, so presumably this is to allow tracks on the two
sides to be distinguished from each other. AppleDOS and ProDOS disks don't
do this.
Reading discs
-------------
@@ -37,35 +65,25 @@ Reading discs
Just do:
```
fluxengine read apple2
fluxengine read appleii140
```
You should end up with an `apple2.img` which is 143360 bytes long. It will be in
physical sector ordering. You can specify a sector ordering, `--appledos` or
`--prodos` to get an image intended for use in an emulator, due to the logical
sector mapping issue described above:
(or `appleii640`)
```
fluxengine read apple2 --prodos
```
You will also need this for filesystem access.
You should end up with an `appleii140.img` which is 143360 bytes long. It will
be in physical sector ordering if you don't specify a file system format as
described above.
Writing discs
-------------
Just do:
```
fluxengine write apple2 -i apple2.img
```
If your image is in logical sector ordering (images intended for emulators
usually are), specify a modifier of `--appledos` or `--prodos`:
```
fluxengine write apple2 --prodos -i apple2.img
fluxengine write appleii140 -i appleii140.img
```
The image will be expected to be in physical sector ordering if you don't
specify a file system format as described above.
Useful references
-----------------

View File

@@ -155,6 +155,14 @@ more common tools.
encoding. You can specify a profile if you want to write a subset of the
disk.
- `fluxengine merge -s <fluxfile> -s <fluxfile...> -d <fluxfile`
Merges data from multiple flux files together. This is useful if you have
several reads from an unreliable disk where each read has a different set
of good sectors. By merging the flux files, you get to combine all the
data. Don't use this on reads of different disks, for obvious results! Note
that this works on flux files, not on flux sources.
- `fluxengine inspect -s <flux source> -c <cylinder> -h <head> -B`
Reads flux (possibly from a disk) and does various analyses of it to try

View File

@@ -1,6 +1,5 @@
#!/bin/sh
dir=`dirname "$0"`
cd "$dir"
export DYLD_FALLBACK_FRAMEWORK_PATH=../Resources
export DYLD_FALLBACK_LIBRARY_PATH=../Resources:/opt/local/lib
exec ./fluxengine-gui "$@"

BIN
extras/fluxfile.piko Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
extras/fluxfile.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

BIN
extras/hardware.piko Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
extras/hardware.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

BIN
extras/imagefile.piko Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
extras/imagefile.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

View File

@@ -9,6 +9,7 @@ LIBFLUXENGINE_SRCS = \
lib/decoders/fmmfm.cc \
lib/encoders/encoders.cc \
lib/environment.cc \
lib/fl2.cc \
lib/flags.cc \
lib/fluxmap.cc \
lib/fluxsink/a2rfluxsink.cc \
@@ -69,6 +70,7 @@ LIBFLUXENGINE_SRCS = \
lib/utils.cc \
lib/vfs/acorndfs.cc \
lib/vfs/amigaffs.cc \
lib/vfs/appledos.cc \
lib/vfs/applesingle.cc \
lib/vfs/brother120fs.cc \
lib/vfs/cbmfs.cc \
@@ -77,6 +79,7 @@ LIBFLUXENGINE_SRCS = \
lib/vfs/machfs.cc \
lib/vfs/prodos.cc \
lib/vfs/smaky6fs.cc \
lib/vfs/philefs.cc \
lib/vfs/vfs.cc \
lib/vfs/fluxsectorinterface.cc \
lib/vfs/imagesectorinterface.cc \

View File

@@ -215,6 +215,24 @@ Bytes Bytes::reverseBits() const
return output;
}
Bytes Bytes::operator + (const Bytes& other)
{
Bytes output;
ByteWriter bw(output);
bw += *this;
bw += other;
return output;
}
Bytes Bytes::operator * (size_t count)
{
Bytes output;
ByteWriter bw(output);
while (count--)
bw += *this;
return output;
}
uint8_t toByte(
std::vector<bool>::const_iterator start,
std::vector<bool>::const_iterator end)

View File

@@ -64,6 +64,9 @@ public:
std::vector<bool> toBits() const;
Bytes reverseBits() const;
Bytes operator + (const Bytes& other);
Bytes operator * (size_t count);
ByteReader reader() const;
ByteWriter writer();

View File

@@ -13,37 +13,44 @@ import "lib/common.proto";
import "lib/layout.proto";
// NEXT_TAG: 21
message ConfigProto {
optional string comment = 8;
optional bool is_extension = 13;
repeated string include = 19;
message ConfigProto
{
optional string comment = 8;
optional bool is_extension = 13;
repeated string include = 19;
optional LayoutProto layout = 18;
optional LayoutProto layout = 18;
optional ImageReaderProto image_reader = 12;
optional ImageWriterProto image_writer = 9;
optional FluxSourceProto flux_source = 10;
optional FluxSinkProto flux_sink = 11;
optional DriveProto drive = 15;
optional ImageReaderProto image_reader = 12;
optional ImageWriterProto image_writer = 9;
optional EncoderProto encoder = 3;
optional DecoderProto decoder = 4;
optional UsbProto usb = 5;
optional FluxSourceProto flux_source = 10;
optional FluxSinkProto flux_sink = 11;
optional DriveProto drive = 15;
optional RangeProto tracks = 6;
optional RangeProto heads = 7;
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
optional EncoderProto encoder = 3;
optional DecoderProto decoder = 4;
optional UsbProto usb = 5;
optional FilesystemProto filesystem = 17;
repeated OptionProto option = 20;
optional RangeProto tracks = 6;
optional RangeProto heads = 7;
optional int32 tpi = 16 [ (help) = "TPI of image; if 0, use TPI of drive" ];
optional FilesystemProto filesystem = 17;
repeated OptionProto option = 20;
}
message OptionProto {
optional string name = 1 [(help) = "Option name" ];
optional string comment = 2 [(help) = "Help text for option" ];
optional string message = 3 [(help) = "Message to display when option is in use" ];
optional ConfigProto config = 4 [(help) = "Option data", (recurse) = false ];
message OptionProto
{
optional string name = 1 [ (help) = "option name" ];
optional string comment = 2 [ (help) = "help text for option" ];
optional string message = 3
[ (help) = "message to display when option is in use" ];
optional string exclusivity_group = 5 [
(help) =
"options with the same group cannot be selected at the same time"
];
optional ConfigProto config = 4
[ (help) = "option data", (recurse) = false ];
}

View File

@@ -16,6 +16,7 @@
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/rolandd20/rolandd20.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
@@ -49,6 +50,7 @@ std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kRolandd20, createRolandD20Decoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },

View File

@@ -13,6 +13,7 @@ import "arch/macintosh/macintosh.proto";
import "arch/micropolis/micropolis.proto";
import "arch/mx/mx.proto";
import "arch/northstar/northstar.proto";
import "arch/rolandd20/rolandd20.proto";
import "arch/smaky6/smaky6.proto";
import "arch/tids990/tids990.proto";
import "arch/victor9k/victor9k.proto";
@@ -20,7 +21,7 @@ import "arch/zilogmcz/zilogmcz.proto";
import "lib/fluxsink/fluxsink.proto";
import "lib/common.proto";
//NEXT: 31
//NEXT: 32
message DecoderProto {
optional double pulse_debounce_threshold = 1 [default = 0.30,
(help) = "ignore pulses with intervals shorter than this, in fractions of a clock"];
@@ -47,6 +48,7 @@ message DecoderProto {
MicropolisDecoderProto micropolis = 14;
MxDecoderProto mx = 15;
NorthstarDecoderProto northstar = 24;
RolandD20DecoderProto rolandd20 = 31;
Smaky6DecoderProto smaky6 = 30;
Tids990DecoderProto tids990 = 16;
Victor9kDecoderProto victor9k = 17;

View File

@@ -35,6 +35,15 @@ message DriveProto
optional int32 tpi = 11 [ default = 96, (help) = "TPI of drive" ];
optional double rotational_period_ms = 12
[ default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"];
enum ErrorBehaviour {
NOTHING = 0;
JIGGLE = 1;
RECALIBRATE = 2;
}
optional ErrorBehaviour error_behaviour = 15
[ default = JIGGLE, (help) = "what to do when an error occurs during reads" ];
}
// vim: ts=4 sw=4 et

View File

@@ -2,6 +2,7 @@
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
@@ -25,6 +26,7 @@ std::unique_ptr<Encoder> Encoder::create(
std::function<std::unique_ptr<Encoder>(const EncoderProto&)>>
encoders = {
{EncoderProto::kAmiga, createAmigaEncoder },
{EncoderProto::kAgat, createAgatEncoder },
{EncoderProto::kApple2, createApple2Encoder },
{EncoderProto::kBrother, createBrotherEncoder },
{EncoderProto::kC64, createCommodore64Encoder},

View File

@@ -6,6 +6,7 @@ class Fluxmap;
class Image;
class Layout;
class Sector;
class TrackInfo;
class Encoder
{

View File

@@ -1,5 +1,6 @@
syntax = "proto2";
import "arch/agat/agat.proto";
import "arch/amiga/amiga.proto";
import "arch/apple2/apple2.proto";
import "arch/brother/brother.proto";
@@ -25,5 +26,6 @@ message EncoderProto
MicropolisEncoderProto micropolis = 10;
Victor9kEncoderProto victor9k = 11;
Apple2EncoderProto apple2 = 12;
AgatEncoderProto agat = 13;
}
}

69
lib/fl2.cc Normal file
View File

@@ -0,0 +1,69 @@
#include "globals.h"
#include "proto.h"
#include "fluxmap.h"
#include "fmt/format.h"
#include "lib/fl2.pb.h"
#include <fstream>
static void upgradeFluxFile(FluxFileProto& proto)
{
if (proto.version() == FluxFileVersion::VERSION_1)
{
/* Change a flux datastream with multiple segments separated by F_DESYNC
* into multiple flux segments. */
for (auto& track : *proto.mutable_track())
{
if (track.flux_size() != 0)
{
Fluxmap oldFlux(track.flux(0));
track.clear_flux();
for (const auto& flux : oldFlux.split())
track.add_flux(flux->rawBytes());
}
}
proto.set_version(FluxFileVersion::VERSION_2);
}
if (proto.version() > FluxFileVersion::VERSION_2)
Error() << fmt::format(
"this is a version {} flux file, but this build of the client can "
"only handle up to version {} --- please upgrade",
proto.version(),
FluxFileVersion::VERSION_2);
}
FluxFileProto loadFl2File(const std::string filename)
{
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
if (!ifs.is_open())
Error() << fmt::format(
"cannot open input file '{}': {}", filename, strerror(errno));
char buffer[16];
ifs.read(buffer, sizeof(buffer));
if (strncmp(buffer, "SQLite format 3", 16) == 0)
Error() << "this flux file is too old; please use the "
"upgrade-flux-file tool to upgrade it";
FluxFileProto proto;
ifs.seekg(0);
if (!proto.ParseFromIstream(&ifs))
Error() << fmt::format("unable to read input file '{}'", filename);
upgradeFluxFile(proto);
return proto;
}
void saveFl2File(const std::string filename, FluxFileProto& proto)
{
proto.set_magic(FluxMagic::MAGIC);
proto.set_version(FluxFileVersion::VERSION_2);
std::ofstream of(filename, std::ios::out | std::ios::binary);
if (!proto.SerializeToOstream(&of))
Error() << fmt::format("unable to write output file '{}'", filename);
of.close();
if (of.fail())
Error() << "FL2 write I/O error: " << strerror(errno);
}

10
lib/fl2.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef FL2_H
#define FL2_H
class FluxFileProto;
extern FluxFileProto loadFl2File(const std::string filename);
extern void saveFl2File(const std::string filename, FluxFileProto& proto);
#endif

View File

@@ -55,31 +55,39 @@ static bool setFallbackFlag(
}
else
{
for (const auto& configs : config.option())
{
if (path == configs.name())
{
if (configs.config().option_size() > 0)
Error() << fmt::format(
"option '{}' has an option inside it, which isn't "
"allowed",
path);
if (configs.config().include_size() > 0)
Error() << fmt::format(
"option '{}' is trying to include something, which "
"isn't allowed",
path);
Logger() << fmt::format("OPTION: {}", configs.message());
config.MergeFrom(configs.config());
return false;
}
}
if (FlagGroup::applyOption(path))
return false;
}
}
Error() << "unrecognised flag; try --help";
}
bool FlagGroup::applyOption(const std::string& option)
{
for (const auto& configs : config.option())
{
if (option == configs.name())
{
if (configs.config().option_size() > 0)
Error() << fmt::format(
"option '{}' has an option inside it, which isn't "
"allowed",
option);
if (configs.config().include_size() > 0)
Error() << fmt::format(
"option '{}' is trying to include something, which "
"isn't allowed",
option);
Logger() << fmt::format("OPTION: {}", configs.message());
config.MergeFrom(configs.config());
return true;
}
}
return false;
}
std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
const char* argv[],
std::function<bool(const std::string&)> callback)
@@ -244,7 +252,6 @@ ConfigProto FlagGroup::parseSingleConfigFile(const std::string& filename,
ss << f.rdbuf();
ConfigProto config;
std::cout << ss.str() << '\n';
if (!google::protobuf::TextFormat::MergeFromString(ss.str(), &config))
Error() << "couldn't load external config proto";
return config;

View File

@@ -12,21 +12,42 @@ public:
FlagGroup(std::initializer_list<FlagGroup*> groups);
public:
void parseFlags(int argc, const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&){ return false; });
void parseFlags(
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
std::vector<std::string> parseFlagsWithFilenames(
int argc, const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&){ return false; });
void parseFlagsWithConfigFiles(int argc, const char* argv[],
const std::map<std::string, std::string>& configFiles);
static ConfigProto parseSingleConfigFile(
const std::string& filename,
const std::map<std::string, std::string>& configFiles);
static void parseConfigFile(
const std::string& filename,
const std::map<std::string, std::string>& configFiles);
int argc,
const char* argv[],
std::function<bool(const std::string&)> callback =
[](const auto&)
{
return false;
});
void parseFlagsWithConfigFiles(int argc,
const char* argv[],
const std::map<std::string, std::string>& configFiles);
/* Load one config file (or internal config file), without expanding
* includes. */
static ConfigProto parseSingleConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Load a top-level config file (or internal config file), expanding
* includes. */
static void parseConfigFile(const std::string& filename,
const std::map<std::string, std::string>& configFiles);
/* Modify the current config to engage the named option. */
static bool applyOption(const std::string& option);
void addFlag(Flag* flag);
void checkInitialised() const;
@@ -40,14 +61,25 @@ class Flag
{
public:
Flag(const std::vector<std::string>& names, const std::string helptext);
virtual ~Flag() {};
virtual ~Flag(){};
void checkInitialised() const
{ _group.checkInitialised(); }
{
_group.checkInitialised();
}
const std::string& name() const { return _names[0]; }
const std::vector<std::string> names() const { return _names; }
const std::string& helptext() const { return _helptext; }
const std::string& name() const
{
return _names[0];
}
const std::vector<std::string> names() const
{
return _names;
}
const std::string& helptext() const
{
return _helptext;
}
virtual bool hasArgument() const = 0;
virtual const std::string defaultValueAsString() const = 0;
@@ -62,15 +94,26 @@ private:
class ActionFlag : Flag
{
public:
ActionFlag(const std::vector<std::string>& names, const std::string helptext,
std::function<void(void)> callback):
ActionFlag(const std::vector<std::string>& names,
const std::string helptext,
std::function<void(void)> callback):
Flag(names, helptext),
_callback(callback)
{}
{
}
bool hasArgument() const { return false; }
const std::string defaultValueAsString() const { return ""; }
void set(const std::string& value) { _callback(); }
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "";
}
void set(const std::string& value)
{
_callback();
}
private:
const std::function<void(void)> _callback;
@@ -79,16 +122,30 @@ private:
class SettableFlag : public Flag
{
public:
SettableFlag(const std::vector<std::string>& names, const std::string helptext):
SettableFlag(
const std::vector<std::string>& names, const std::string helptext):
Flag(names, helptext)
{}
{
}
operator bool() const
{ checkInitialised(); return _value; }
{
checkInitialised();
return _value;
}
bool hasArgument() const { return false; }
const std::string defaultValueAsString() const { return "false"; }
void set(const std::string& value) { _value = true; }
bool hasArgument() const
{
return false;
}
const std::string defaultValueAsString() const
{
return "false";
}
void set(const std::string& value)
{
_value = true;
}
private:
bool _value = false;
@@ -98,72 +155,122 @@ template <typename T>
class ValueFlag : public Flag
{
public:
ValueFlag(const std::vector<std::string>& names, const std::string helptext,
const T defaultValue,
std::function<void(const T&)> callback = [](const T&) {}):
ValueFlag(
const std::vector<std::string>& names,
const std::string helptext,
const T defaultValue,
std::function<void(const T&)> callback =
[](const T&)
{
}):
Flag(names, helptext),
_defaultValue(defaultValue),
_value(defaultValue),
_callback(callback)
{}
_callback(callback)
{
}
const T& get() const
{ checkInitialised(); return _value; }
{
checkInitialised();
return _value;
}
operator const T& () const
{ return get(); }
operator const T&() const
{
return get();
}
bool isSet() const
{ return _isSet; }
bool isSet() const
{
return _isSet;
}
void setDefaultValue(T value)
{
_value = _defaultValue = value;
}
bool hasArgument() const { return true; }
bool hasArgument() const
{
return true;
}
protected:
T _defaultValue;
T _value;
bool _isSet = false;
std::function<void(const T&)> _callback;
bool _isSet = false;
std::function<void(const T&)> _callback;
};
class StringFlag : public ValueFlag<std::string>
{
public:
StringFlag(const std::vector<std::string>& names, const std::string helptext,
const std::string defaultValue = "",
std::function<void(const std::string&)> callback = [](const std::string&) {}):
StringFlag(
const std::vector<std::string>& names,
const std::string helptext,
const std::string defaultValue = "",
std::function<void(const std::string&)> callback =
[](const std::string&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{}
{
}
const std::string defaultValueAsString() const { return _defaultValue; }
void set(const std::string& value) { _value = value; _callback(_value); _isSet = true; }
const std::string defaultValueAsString() const
{
return _defaultValue;
}
void set(const std::string& value)
{
_value = value;
_callback(_value);
_isSet = true;
}
};
class IntFlag : public ValueFlag<int>
{
public:
IntFlag(const std::vector<std::string>& names, const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback = [](const int&) {}):
IntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{}
{
}
const std::string defaultValueAsString() const { return std::to_string(_defaultValue); }
void set(const std::string& value) { _value = std::stoi(value); _callback(_value); _isSet = true; }
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stoi(value);
_callback(_value);
_isSet = true;
}
};
class HexIntFlag : public IntFlag
{
public:
HexIntFlag(const std::vector<std::string>& names, const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback = [](const int&) {}):
HexIntFlag(
const std::vector<std::string>& names,
const std::string helptext,
int defaultValue = 0,
std::function<void(const int&)> callback =
[](const int&)
{
}):
IntFlag(names, helptext, defaultValue, callback)
{}
{
}
const std::string defaultValueAsString() const;
};
@@ -171,26 +278,49 @@ public:
class DoubleFlag : public ValueFlag<double>
{
public:
DoubleFlag(const std::vector<std::string>& names, const std::string helptext,
double defaultValue = 1.0,
std::function<void(const double&)> callback = [](const double&) {}):
DoubleFlag(
const std::vector<std::string>& names,
const std::string helptext,
double defaultValue = 1.0,
std::function<void(const double&)> callback =
[](const double&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{}
{
}
const std::string defaultValueAsString() const { return std::to_string(_defaultValue); }
void set(const std::string& value) { _value = std::stod(value); _callback(_value); _isSet = true; }
const std::string defaultValueAsString() const
{
return std::to_string(_defaultValue);
}
void set(const std::string& value)
{
_value = std::stod(value);
_callback(_value);
_isSet = true;
}
};
class BoolFlag : public ValueFlag<bool>
{
public:
BoolFlag(const std::vector<std::string>& names, const std::string helptext,
bool defaultValue = false,
std::function<void(const bool&)> callback = [](const bool&) {}):
BoolFlag(
const std::vector<std::string>& names,
const std::string helptext,
bool defaultValue = false,
std::function<void(const bool&)> callback =
[](const bool&)
{
}):
ValueFlag(names, helptext, defaultValue, callback)
{}
{
}
const std::string defaultValueAsString() const { return _defaultValue ? "true" : "false"; }
const std::string defaultValueAsString() const
{
return _defaultValue ? "true" : "false";
}
void set(const std::string& value);
};

View File

@@ -76,3 +76,89 @@ std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const
return maps;
}
/*
* Tries to guess the clock by finding the smallest common interval.
* Returns nanoseconds.
*/
Fluxmap::ClockData Fluxmap::guessClock(
double noiseFloorFactor, double signalLevelFactor) const
{
ClockData data = {};
FluxmapReader fr(*this);
while (!fr.eof())
{
unsigned interval;
fr.findEvent(F_BIT_PULSE, interval);
if (interval > 0xff)
continue;
data.buckets[interval]++;
}
uint32_t max =
*std::max_element(std::begin(data.buckets), std::end(data.buckets));
uint32_t min =
*std::min_element(std::begin(data.buckets), std::end(data.buckets));
data.noiseFloor = min + (max - min) * noiseFloorFactor;
data.signalLevel = min + (max - min) * signalLevelFactor;
/* Find a point solidly within the first pulse. */
int pulseindex = 0;
while (pulseindex < 256)
{
if (data.buckets[pulseindex] > data.signalLevel)
break;
pulseindex++;
}
if (pulseindex == -1)
return data;
/* Find the upper and lower bounds of the pulse. */
int peaklo = pulseindex;
while (peaklo > 0)
{
if (data.buckets[peaklo] < data.noiseFloor)
break;
peaklo--;
}
int peakhi = pulseindex;
while (peakhi < 255)
{
if (data.buckets[peakhi] < data.noiseFloor)
break;
peakhi++;
}
/* Find the total accumulated size of the pulse. */
uint32_t total_size = 0;
for (int i = peaklo; i < peakhi; i++)
total_size += data.buckets[i];
/* Now find the median. */
uint32_t count = 0;
int median = peaklo;
while (median < peakhi)
{
count += data.buckets[median];
if (count > (total_size / 2))
break;
median++;
}
/*
* Okay, the median should now be a good candidate for the (or a) clock.
* How this maps onto the actual clock rate depends on the encoding.
*/
data.peakStart = peaklo * NS_PER_TICK;
data.peakEnd = peakhi * NS_PER_TICK;
data.median = median * NS_PER_TICK;
return data;
}

View File

@@ -17,42 +17,57 @@ public:
unsigned zeroes = 0;
nanoseconds_t ns() const
{ return ticks * NS_PER_TICK; }
{
return ticks * NS_PER_TICK;
}
operator std::string () {
operator std::string()
{
return fmt::format("[b:{}, t:{}, z:{}]", bytes, ticks, zeroes);
}
};
public:
Fluxmap() {}
Fluxmap() {}
Fluxmap(const std::string& s)
{
appendBytes((const uint8_t*) s.c_str(), s.size());
}
Fluxmap(const std::string& s)
{
appendBytes((const uint8_t*)s.c_str(), s.size());
}
Fluxmap(const Bytes bytes)
{
appendBytes(bytes);
}
Fluxmap(const Bytes bytes)
{
appendBytes(bytes);
}
nanoseconds_t duration() const { return _duration; }
unsigned ticks() const { return _ticks; }
size_t bytes() const { return _bytes.size(); }
const Bytes& rawBytes() const { return _bytes; }
nanoseconds_t duration() const
{
return _duration;
}
unsigned ticks() const
{
return _ticks;
}
size_t bytes() const
{
return _bytes.size();
}
const Bytes& rawBytes() const
{
return _bytes;
}
const uint8_t* ptr() const
{
if (!_bytes.empty())
return &_bytes[0];
return NULL;
}
{
if (!_bytes.empty())
return &_bytes[0];
return NULL;
}
Fluxmap& appendInterval(uint32_t ticks);
Fluxmap& appendPulse();
Fluxmap& appendIndex();
Fluxmap& appendDesync();
Fluxmap& appendDesync();
Fluxmap& appendBytes(const Bytes& bytes);
Fluxmap& appendBytes(const uint8_t* ptr, size_t len);
@@ -62,13 +77,27 @@ public:
return appendBytes(&byte, 1);
}
Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock);
Fluxmap& appendBits(const std::vector<bool>& bits, nanoseconds_t clock);
std::unique_ptr<const Fluxmap> precompensate(int threshold_ticks, int amount_ticks);
std::unique_ptr<const Fluxmap> precompensate(
int threshold_ticks, int amount_ticks);
std::vector<std::unique_ptr<const Fluxmap>> split() const;
struct ClockData
{
nanoseconds_t median;
uint32_t noiseFloor;
uint32_t signalLevel;
nanoseconds_t peakStart;
nanoseconds_t peakEnd;
uint32_t buckets[256];
};
ClockData guessClock(
double noiseFloorFactor = 0.01, double signalLevelFactor = 0.05) const;
private:
uint8_t& findLastByte();
uint8_t& findLastByte();
private:
nanoseconds_t _duration = 0;

View File

@@ -9,73 +9,69 @@
#include "proto.h"
#include "fmt/format.h"
#include "lib/fl2.pb.h"
#include "fl2.h"
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <filesystem>
class Fl2FluxSink : public FluxSink
{
public:
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const std::string& filename):
_filename(filename),
_of(_filename, std::ios::out | std::ios::binary)
{
if (!_of.is_open())
Error() << "cannot open output file";
}
Fl2FluxSink(const std::string& filename): _filename(filename)
{
std::ofstream of(filename);
if (!of.is_open())
Error() << "cannot open output file";
of.close();
std::filesystem::remove(filename);
}
~Fl2FluxSink()
{
FluxFileProto proto;
proto.set_magic(FluxMagic::MAGIC);
proto.set_version(FluxFileVersion::VERSION_2);
for (const auto& e : _data)
{
auto track = proto.add_track();
track->set_track(e.first.first);
track->set_head(e.first.second);
for (const auto& fluxBytes : e.second)
track->add_flux(fluxBytes);
}
~Fl2FluxSink()
{
FluxFileProto proto;
for (const auto& e : _data)
{
auto track = proto.add_track();
track->set_track(e.first.first);
track->set_head(e.first.second);
for (const auto& fluxBytes : e.second)
track->add_flux(fluxBytes);
}
if (!proto.SerializeToOstream(&_of))
Error() << "unable to write output file";
_of.close();
if (_of.fail())
Error() << "FL2 write I/O error: " << strerror(errno);
}
saveFl2File(_filename, proto);
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes());
}
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes());
}
operator std::string () const override
{
return fmt::format("fl2({})", _filename);
}
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
private:
std::string _filename;
std::ofstream _of;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
std::string _filename;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
};
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const Fl2FluxSinkProto& config)
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
const Fl2FluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config));
}
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(const std::string& filename)
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
const std::string& filename)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(filename));
}

View File

@@ -55,7 +55,7 @@ public:
_fileheader.start_track = strackno(minTrack, minSide);
_fileheader.end_track = strackno(maxTrack, maxSide);
_fileheader.flags = SCP_FLAG_INDEXED;
if (config.tpi() == 96)
if (config.tpi() != 48)
_fileheader.flags |= SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
if ((minSide == 0) && (maxSide == 0))

View File

@@ -4,6 +4,7 @@
#include "lib/fl2.pb.h"
#include "fluxsource/fluxsource.h"
#include "proto.h"
#include "fl2.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include <fstream>
@@ -11,38 +12,36 @@
class Fl2FluxSourceIterator : public FluxSourceIterator
{
public:
Fl2FluxSourceIterator(const TrackFluxProto& proto):
_proto(proto)
{}
Fl2FluxSourceIterator(const TrackFluxProto& proto): _proto(proto) {}
bool hasNext() const override
{
return _count < _proto.flux_size();
}
bool hasNext() const override
{
return _count < _proto.flux_size();
}
std::unique_ptr<const Fluxmap> next() override
{
auto bytes = _proto.flux(_count);
_count++;
return std::make_unique<Fluxmap>(bytes);
}
std::unique_ptr<const Fluxmap> next() override
{
auto bytes = _proto.flux(_count);
_count++;
return std::make_unique<Fluxmap>(bytes);
}
private:
const TrackFluxProto& _proto;
int _count = 0;
const TrackFluxProto& _proto;
int _count = 0;
};
class EmptyFluxSourceIterator : public FluxSourceIterator
{
bool hasNext() const override
{
return false;
}
bool hasNext() const override
{
return false;
}
std::unique_ptr<const Fluxmap> next() override
{
Error() << "no flux to read";
}
std::unique_ptr<const Fluxmap> next() override
{
Error() << "no flux to read";
}
};
class Fl2FluxSource : public FluxSource
@@ -50,15 +49,7 @@ class Fl2FluxSource : public FluxSource
public:
Fl2FluxSource(const Fl2FluxSourceProto& config): _config(config)
{
std::ifstream ifs(_config.filename(), std::ios::in | std::ios::binary);
if (!ifs.is_open())
Error() << fmt::format("cannot open input file '{}': {}",
_config.filename(),
strerror(errno));
if (!_proto.ParseFromIstream(&ifs))
Error() << "unable to read input file";
upgradeFluxFile();
_proto = loadFl2File(_config.filename());
}
public:
@@ -67,7 +58,7 @@ public:
for (const auto& trackFlux : _proto.track())
{
if ((trackFlux.track() == track) && (trackFlux.head() == head))
return std::make_unique<Fl2FluxSourceIterator>(trackFlux);
return std::make_unique<Fl2FluxSourceIterator>(trackFlux);
}
return std::make_unique<EmptyFluxSourceIterator>();
@@ -82,31 +73,6 @@ private:
Error() << fmt::format("FL2 read I/O error: {}", strerror(errno));
}
void upgradeFluxFile()
{
if (_proto.version() == FluxFileVersion::VERSION_1)
{
/* Change a flux datastream with multiple segments separated by F_DESYNC into multiple
* flux segments. */
for (auto& track : *_proto.mutable_track())
{
if (track.flux_size() != 0)
{
Fluxmap oldFlux(track.flux(0));
track.clear_flux();
for (const auto& flux : oldFlux.split())
track.add_flux(flux->rawBytes());
}
}
_proto.set_version(FluxFileVersion::VERSION_2);
}
if (_proto.version() > FluxFileVersion::VERSION_2)
Error() << fmt::format("this is a version {} flux file, but this build of the client can only handle up to version {} --- please upgrade", _proto.version(), FluxFileVersion::VERSION_2);
}
private:
const Fl2FluxSourceProto& _config;
FluxFileProto _proto;
@@ -115,12 +81,5 @@ private:
std::unique_ptr<FluxSource> FluxSource::createFl2FluxSource(
const Fl2FluxSourceProto& config)
{
char buffer[16];
std::ifstream(config.filename(), std::ios::in | std::ios::binary)
.read(buffer, 16);
if (strncmp(buffer, "SQLite format 3", 16) == 0)
Error() << "this flux file is too old; please use the "
"upgrade-flux-file tool to upgrade it";
return std::unique_ptr<FluxSource>(new Fl2FluxSource(config));
}

View File

@@ -19,10 +19,10 @@ class FlxFluxSourceProto;
class FluxSourceIterator
{
public:
virtual ~FluxSourceIterator() {}
virtual ~FluxSourceIterator() {}
virtual bool hasNext() const = 0;
virtual std::unique_ptr<const Fluxmap> next() = 0;
virtual bool hasNext() const = 0;
virtual std::unique_ptr<const Fluxmap> next() = 0;
};
class FluxSource
@@ -31,33 +31,48 @@ public:
virtual ~FluxSource() {}
private:
static std::unique_ptr<FluxSource> createCwfFluxSource(const CwfFluxSourceProto& config);
static std::unique_ptr<FluxSource> createEraseFluxSource(const EraseFluxSourceProto& config);
static std::unique_ptr<FluxSource> createFl2FluxSource(const Fl2FluxSourceProto& config);
static std::unique_ptr<FluxSource> createFlxFluxSource(const FlxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createHardwareFluxSource(const HardwareFluxSourceProto& config);
static std::unique_ptr<FluxSource> createKryofluxFluxSource(const KryofluxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createScpFluxSource(const ScpFluxSourceProto& config);
static std::unique_ptr<FluxSource> createTestPatternFluxSource(const TestPatternFluxSourceProto& config);
static std::unique_ptr<FluxSource> createCwfFluxSource(
const CwfFluxSourceProto& config);
static std::unique_ptr<FluxSource> createEraseFluxSource(
const EraseFluxSourceProto& config);
static std::unique_ptr<FluxSource> createFl2FluxSource(
const Fl2FluxSourceProto& config);
static std::unique_ptr<FluxSource> createFlxFluxSource(
const FlxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createHardwareFluxSource(
const HardwareFluxSourceProto& config);
static std::unique_ptr<FluxSource> createKryofluxFluxSource(
const KryofluxFluxSourceProto& config);
static std::unique_ptr<FluxSource> createScpFluxSource(
const ScpFluxSourceProto& config);
static std::unique_ptr<FluxSource> createTestPatternFluxSource(
const TestPatternFluxSourceProto& config);
public:
static std::unique_ptr<FluxSource> createMemoryFluxSource(const DiskFlux& flux);
static std::unique_ptr<FluxSource> createMemoryFluxSource(
const DiskFlux& flux);
static std::unique_ptr<FluxSource> create(const FluxSourceProto& spec);
static void updateConfigForFilename(FluxSourceProto* proto, const std::string& filename);
static void updateConfigForFilename(
FluxSourceProto* proto, const std::string& filename);
public:
virtual std::unique_ptr<FluxSourceIterator> readFlux(int track, int side) = 0;
virtual std::unique_ptr<FluxSourceIterator> readFlux(
int track, int side) = 0;
virtual void recalibrate() {}
virtual bool isHardware() { return false; }
virtual void seek(int track) {}
virtual bool isHardware()
{
return false;
}
};
class TrivialFluxSource : public FluxSource
{
public:
std::unique_ptr<FluxSourceIterator> readFlux(int track, int side);
virtual std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) = 0;
virtual std::unique_ptr<const Fluxmap> readSingleFlux(
int track, int side) = 0;
};
#endif

View File

@@ -30,7 +30,9 @@ private:
std::unique_ptr<const Fluxmap> next()
{
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
usbSetDrive(config.drive().drive(),
config.drive().high_density(),
config.drive().index_mode());
usbSeek(_track);
Bytes data = usbRead(_head,
@@ -51,7 +53,7 @@ private:
public:
HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf)
{
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
}
~HardwareFluxSource() {}
@@ -59,8 +61,7 @@ public:
public:
std::unique_ptr<FluxSourceIterator> readFlux(int track, int head) override
{
return std::make_unique<HardwareFluxSourceIterator>(
*this, track, head);
return std::make_unique<HardwareFluxSourceIterator>(*this, track, head);
}
void recalibrate() override
@@ -68,6 +69,11 @@ public:
usbRecalibrate();
}
void seek(int track) override
{
usbSeek(track);
}
bool isHardware() override
{
return true;

View File

@@ -21,122 +21,130 @@ static int headno(int strack)
static int strackno(int track, int side)
{
return (track << 1) | side;
return (track << 1) | side;
}
class ScpFluxSource : public TrivialFluxSource
{
public:
ScpFluxSource(const ScpFluxSourceProto& config):
_config(config)
ScpFluxSource(const ScpFluxSourceProto& config): _config(config)
{
_if.open(_config.filename(), std::ios::in | std::ios::binary);
if (!_if.is_open())
Error() << fmt::format("cannot open input file '{}': {}", _config.filename(), strerror(errno));
_if.open(_config.filename(), std::ios::in | std::ios::binary);
if (!_if.is_open())
Error() << fmt::format("cannot open input file '{}': {}",
_config.filename(),
strerror(errno));
_if.read((char*) &_header, sizeof(_header));
check_for_error();
_if.read((char*)&_header, sizeof(_header));
check_for_error();
if ((_header.file_id[0] != 'S')
|| (_header.file_id[1] != 'C')
|| (_header.file_id[2] != 'P'))
Error() << "input not a SCP file";
if ((_header.file_id[0] != 'S') || (_header.file_id[1] != 'C') ||
(_header.file_id[2] != 'P'))
Error() << "input not a SCP file";
_resolution = 25 * (_header.resolution + 1);
int startSide = (_header.heads == 2) ? 1 : 0;
int endSide = (_header.heads == 1) ? 0 : 1;
::config.set_tpi((_header.flags & SCP_FLAG_96TPI) ? 96 : 48);
if ((_header.cell_width != 0) && (_header.cell_width != 16))
Error() << "currently only 16-bit cells in SCP files are supported";
_resolution = 25 * (_header.resolution + 1);
int startSide = (_header.heads == 2) ? 1 : 0;
int endSide = (_header.heads == 1) ? 0 : 1;
std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n",
trackno(_header.start_track), trackno(_header.end_track), startSide, endSide);
std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution);
}
if ((_header.cell_width != 0) && (_header.cell_width != 16))
Error() << "currently only 16-bit cells in SCP files are supported";
std::cout << fmt::format("SCP tracks {}-{}, heads {}-{}\n",
trackno(_header.start_track),
trackno(_header.end_track),
startSide,
endSide);
std::cout << fmt::format("SCP sample resolution: {} ns\n", _resolution);
}
public:
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::make_unique<Fluxmap>();
int strack = strackno(track, side);
if (strack >= ARRAY_SIZE(_header.track))
return std::make_unique<Fluxmap>();
uint32_t offset = Bytes(_header.track[strack], 4).reader().read_le32();
if (offset == 0)
return std::make_unique<Fluxmap>();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
_if.read((char*) &trackheader, sizeof(trackheader));
check_for_error();
ScpTrackHeader trackheader;
_if.seekg(offset, std::ios::beg);
_if.read((char*)&trackheader, sizeof(trackheader));
check_for_error();
if ((trackheader.track_id[0] != 'T')
|| (trackheader.track_id[1] != 'R')
|| (trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
if ((trackheader.track_id[0] != 'T') ||
(trackheader.track_id[1] != 'R') ||
(trackheader.track_id[2] != 'K'))
Error() << "corrupt SCP file";
std::vector<ScpTrackRevolution> revs(_header.revolutions);
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
ScpTrackRevolution trackrev;
_if.read((char*) &trackrev, sizeof(trackrev));
check_for_error();
revs[revolution] = trackrev;
}
std::vector<ScpTrackRevolution> revs(_header.revolutions);
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
ScpTrackRevolution trackrev;
_if.read((char*)&trackrev, sizeof(trackrev));
check_for_error();
revs[revolution] = trackrev;
}
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap->appendIndex();
auto fluxmap = std::make_unique<Fluxmap>();
nanoseconds_t pending = 0;
unsigned inputBytes = 0;
for (int revolution = 0; revolution < _header.revolutions; revolution++)
{
if (revolution != 0)
fluxmap->appendIndex();
uint32_t datalength = Bytes(revs[revolution].length, 4).reader().read_le32();
uint32_t dataoffset = Bytes(revs[revolution].offset, 4).reader().read_le32();
uint32_t datalength =
Bytes(revs[revolution].length, 4).reader().read_le32();
uint32_t dataoffset =
Bytes(revs[revolution].offset, 4).reader().read_le32();
Bytes data(datalength*2);
_if.seekg(dataoffset + offset, std::ios::beg);
_if.read((char*) data.begin(), data.size());
check_for_error();
Bytes data(datalength * 2);
_if.seekg(dataoffset + offset, std::ios::beg);
_if.read((char*)data.begin(), data.size());
check_for_error();
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap->appendInterval((interval + pending) * _resolution / NS_PER_TICK);
fluxmap->appendPulse();
pending = 0;
}
else
pending += 0x10000;
}
ByteReader br(data);
for (int cell = 0; cell < datalength; cell++)
{
uint16_t interval = br.read_be16();
if (interval)
{
fluxmap->appendInterval(
(interval + pending) * _resolution / NS_PER_TICK);
fluxmap->appendPulse();
pending = 0;
}
else
pending += 0x10000;
}
inputBytes += datalength*2;
}
inputBytes += datalength * 2;
}
return fluxmap;
return fluxmap;
}
void recalibrate() {}
private:
void check_for_error()
{
if (_if.fail())
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
}
void check_for_error()
{
if (_if.fail())
Error() << fmt::format("SCP read I/O error: {}", strerror(errno));
}
private:
const ScpFluxSourceProto& _config;
std::ifstream _if;
ScpHeader _header;
nanoseconds_t _resolution;
std::ifstream _if;
ScpHeader _header;
nanoseconds_t _resolution;
};
std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(const ScpFluxSourceProto& config)
std::unique_ptr<FluxSource> FluxSource::createScpFluxSource(
const ScpFluxSourceProto& config)
{
return std::unique_ptr<FluxSource>(new ScpFluxSource(config));
}

View File

@@ -23,7 +23,10 @@ void hexdump(std::ostream& stream, const Bytes& buffer)
break;
uint8_t c = buffer[pos+i];
stream << (isprint(c) ? (char)c : '.');
if ((c >= 32) && (c <= 126))
stream << (char)c;
else
stream << '.';
}
stream << std::endl;

View File

@@ -31,7 +31,7 @@ void Image::createBlankImage()
unsigned side = trackAndHead.second;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
Bytes blank(trackLayout->sectorSize);
for (unsigned sectorId : trackLayout->logicalSectorOrder)
for (unsigned sectorId : trackLayout->naturalSectorOrder)
put(track, side, sectorId)->data = blank;
}
}

View File

@@ -1,6 +1,8 @@
#ifndef IMAGE_H
#define IMAGE_H
class Sector;
struct Geometry
{
unsigned numTracks = 0;

View File

@@ -112,7 +112,7 @@ std::unique_ptr<Image> ImageReader::readMappedImage()
auto newSector = std::make_shared<Sector>();
*newSector = *e;
newSector->logicalSector =
trackLayout->filesystemToLogicalSectorMap.at(e->logicalSector);
trackLayout->filesystemToNaturalSectorMap.at(e->logicalSector);
sectors.insert(newSector);
}

View File

@@ -40,7 +40,7 @@ public:
break;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
for (int sectorId : trackLayout->logicalSectorOrder)
for (int sectorId : trackLayout->naturalSectorOrder)
{
Bytes data(trackLayout->sectorSize);
inputFile.read((char*)data.begin(), data.size());

View File

@@ -218,7 +218,7 @@ void ImageWriter::writeMappedImage(const Image& image)
auto newSector = std::make_shared<Sector>();
*newSector = *e;
newSector->logicalSector =
trackLayout->logicalToFilesystemSectorMap.at(e->logicalSector);
trackLayout->naturalToFilesystemSectorMap.at(e->logicalSector);
sectors.insert(newSector);
}

View File

@@ -37,7 +37,7 @@ public:
int side = p.second;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
for (int sectorId : trackLayout->logicalSectorOrder)
for (int sectorId : trackLayout->naturalSectorOrder)
{
const auto& sector = image.get(track, side, sectorId);
if (sector)

View File

@@ -58,19 +58,23 @@ std::vector<std::shared_ptr<const TrackInfo>> Layout::computeLocations()
return locations;
}
void Layout::getBounds(const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack, int& maxTrack, int& minSide, int& maxSide)
void Layout::getBounds(
const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack,
int& maxTrack,
int& minSide,
int& maxSide)
{
minTrack = minSide = INT_MAX;
maxTrack = maxSide = INT_MIN;
minTrack = minSide = INT_MAX;
maxTrack = maxSide = INT_MIN;
for (auto& ti : locations)
{
minTrack = std::min<int>(minTrack, ti->physicalTrack);
maxTrack = std::max<int>(maxTrack, ti->physicalTrack);
minSide = std::min<int>(minSide, ti->physicalSide);
maxSide = std::max<int>(maxSide, ti->physicalSide);
}
for (auto& ti : locations)
{
minTrack = std::min<int>(minTrack, ti->physicalTrack);
maxTrack = std::max<int>(maxTrack, ti->physicalTrack);
minSide = std::min<int>(minSide, ti->physicalSide);
maxSide = std::max<int>(maxSide, ti->physicalSide);
}
}
std::vector<std::pair<int, int>> Layout::getTrackOrdering(
@@ -121,10 +125,24 @@ std::vector<unsigned> Layout::expandSectorList(
Error() << "LAYOUT: if you use a sector count, you can't use an "
"explicit sector list";
std::set<unsigned> sectorset;
int id = sectorsProto.start_sector();
for (int i = 0; i < sectorsProto.count(); i++)
sectors.push_back(
sectorsProto.start_sector() +
((i * sectorsProto.skew()) % sectorsProto.count()));
{
while (sectorset.find(id) != sectorset.end())
{
id++;
if (id >= (sectorsProto.start_sector() + sectorsProto.count()))
id -= sectorsProto.count();
}
sectorset.insert(id);
sectors.push_back(id);
id += sectorsProto.skew();
if (id >= (sectorsProto.start_sector() + sectorsProto.count()))
id -= sectorsProto.count();
}
}
else if (sectorsProto.sector_size() > 0)
{
@@ -146,8 +164,7 @@ std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
for (const auto& f : config.layout().layoutdata())
{
if (f.has_track() && f.has_up_to_track() &&
((logicalTrack < f.track()) ||
(logicalTrack > f.up_to_track())))
((logicalTrack < f.track()) || (logicalTrack > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() &&
(logicalTrack != f.track()))
@@ -167,32 +184,28 @@ std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
trackInfo->physicalSide = logicalSide ^ config.layout().swap_sides();
trackInfo->groupSize = getTrackStep();
trackInfo->diskSectorOrder = expandSectorList(layoutdata.physical());
trackInfo->logicalSectorOrder = trackInfo->diskSectorOrder;
std::sort(
trackInfo->diskSectorOrder.begin(), trackInfo->diskSectorOrder.end());
trackInfo->numSectors = trackInfo->logicalSectorOrder.size();
trackInfo->naturalSectorOrder = trackInfo->diskSectorOrder;
std::sort(trackInfo->naturalSectorOrder.begin(),
trackInfo->naturalSectorOrder.end());
trackInfo->numSectors = trackInfo->naturalSectorOrder.size();
if (layoutdata.has_filesystem())
{
trackInfo->filesystemSectorOrder =
expandSectorList(layoutdata.filesystem());
if (trackInfo->filesystemSectorOrder.size() != trackInfo->numSectors)
Error()
<< "filesystem sector order list doesn't contain the right "
"number of sectors";
Error() << "filesystem sector order list doesn't contain the right "
"number of sectors";
}
else
{
for (unsigned sectorId : trackInfo->logicalSectorOrder)
trackInfo->filesystemSectorOrder.push_back(sectorId);
}
trackInfo->filesystemSectorOrder = trackInfo->naturalSectorOrder;
for (int i = 0; i < trackInfo->numSectors; i++)
{
unsigned f = trackInfo->logicalSectorOrder[i];
unsigned l = trackInfo->filesystemSectorOrder[i];
trackInfo->filesystemToLogicalSectorMap[f] = l;
trackInfo->logicalToFilesystemSectorMap[l] = f;
unsigned fid = trackInfo->naturalSectorOrder[i];
unsigned lid = trackInfo->filesystemSectorOrder[i];
trackInfo->filesystemToNaturalSectorMap[fid] = lid;
trackInfo->naturalToFilesystemSectorMap[lid] = fid;
}
return trackInfo;

View File

@@ -27,10 +27,14 @@ public:
*/
static std::vector<std::shared_ptr<const TrackInfo>> computeLocations();
/* Given a list of locations, determines the minimum and maximum track
* and side settings. */
static void getBounds(const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack, int& maxTrack, int& minSide, int& maxSide);
/* Given a list of locations, determines the minimum and maximum track
* and side settings. */
static void getBounds(
const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack,
int& maxTrack,
int& minSide,
int& maxSide);
/* Returns a series of <track, side> pairs representing the filesystem
* ordering of the disk, in logical numbers. */
@@ -50,9 +54,10 @@ public:
const SectorListProto& sectorsProto);
};
class TrackInfo {
class TrackInfo
{
public:
TrackInfo() {}
TrackInfo() {}
private:
/* Can't copy. */
@@ -85,20 +90,23 @@ public:
/* Number of bytes in a sector. */
unsigned sectorSize = 0;
/* Sector IDs in disk order. */
/* Sector IDs in sector ID order. This is the order in which the appear in
* disk images. */
std::vector<unsigned> naturalSectorOrder;
/* Sector IDs in disk order. This is the order they are written to the disk.
*/
std::vector<unsigned> diskSectorOrder;
/* Sector IDs in logical order. */
std::vector<unsigned> logicalSectorOrder;
/* Sector IDs in filesystem order. */
/* Sector IDs in filesystem order. This is the order in which the filesystem
* uses them. */
std::vector<unsigned> filesystemSectorOrder;
/* Mapping of filesystem order to logical order. */
std::map<unsigned, unsigned> filesystemToLogicalSectorMap;
/* Mapping of filesystem order to natural order. */
std::map<unsigned, unsigned> filesystemToNaturalSectorMap;
/* Mapping of logical order to filesystem order. */
std::map<unsigned, unsigned> logicalToFilesystemSectorMap;
/* Mapping of natural order to filesystem order. */
std::map<unsigned, unsigned> naturalToFilesystemSectorMap;
};
#endif

View File

@@ -168,7 +168,7 @@ BadSectorsState combineRecordAndSectors(TrackFlux& trackFlux,
/* Add the sectors which should be there. */
for (unsigned sectorId : trackLayout->logicalSectorOrder)
for (unsigned sectorId : trackLayout->naturalSectorOrder)
{
auto sector = std::make_shared<Sector>(LogicalLocation{
trackLayout->logicalTrack, trackLayout->logicalSide, sectorId});
@@ -189,6 +189,26 @@ BadSectorsState combineRecordAndSectors(TrackFlux& trackFlux,
return HAS_NO_BAD_SECTORS;
}
static void adjustTrackOnError(FluxSource& fluxSource, int baseTrack)
{
switch (config.drive().error_behaviour())
{
case DriveProto::NOTHING:
break;
case DriveProto::RECALIBRATE:
fluxSource.recalibrate();
break;
case DriveProto::JIGGLE:
if (baseTrack > 0)
fluxSource.seek(baseTrack - 1);
else
fluxSource.seek(baseTrack + 1);
break;
}
}
ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder,
std::shared_ptr<const TrackInfo>& trackInfo,
TrackFlux& trackFlux,
@@ -343,6 +363,7 @@ void writeTracksAndVerify(FluxSink& fluxSink,
if (result != GOOD_READ)
{
adjustTrackOnError(fluxSource, trackInfo->physicalTrack);
Logger() << "bad read";
return false;
}
@@ -453,9 +474,13 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
break;
}
Logger() << fmt::format(
"retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
if (fluxSource.isHardware())
{
adjustTrackOnError(fluxSource, trackInfo->physicalTrack);
Logger() << fmt::format(
"retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
}
return trackFlux;
@@ -584,7 +609,8 @@ void rawReadDiskCommand(FluxSource& fluxsource, FluxSink& fluxsink)
unsigned index = 0;
for (auto& trackInfo : locations)
{
Logger() << OperationProgressLogMessage{index * 100 / (int)locations.size()};
Logger() << OperationProgressLogMessage{
index * 100 / (int)locations.size()};
index++;
testForEmergencyStop();

View File

@@ -16,7 +16,7 @@ static USB* usb = NULL;
USB::~USB() {}
static std::unique_ptr<CandidateDevice> selectDevice()
static std::shared_ptr<CandidateDevice> selectDevice()
{
auto candidates = findUsbDevices();
if (candidates.size() == 0)
@@ -29,14 +29,14 @@ static std::unique_ptr<CandidateDevice> selectDevice()
for (auto& c : candidates)
{
if (c->serial == wantedSerial)
return std::move(c);
return c;
}
Error() << "serial number not found (try without one to list or "
"autodetect devices)";
}
if (candidates.size() == 1)
return std::move(candidates[0]);
return candidates[0];
std::cerr << "More than one device detected; use --usb.serial=<serial> to "
"select one:\n";

View File

@@ -24,9 +24,9 @@ static const std::string get_serial_number(const libusbp::device& device)
}
}
std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices()
std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices()
{
std::vector<std::unique_ptr<CandidateDevice>> candidates;
std::vector<std::shared_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
{
auto candidate = std::make_unique<CandidateDevice>();
@@ -42,7 +42,10 @@ std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices()
{
libusbp::serial_port port(candidate->device);
candidate->serialPort = port.get_name();
candidate->type = DEVICE_GREASEWEAZLE;
}
else if (id == FLUXENGINE_ID)
candidate->type = DEVICE_FLUXENGINE;
candidates.push_back(std::move(candidate));
}
@@ -50,3 +53,19 @@ std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices()
return candidates;
}
std::string getDeviceName(DeviceType type)
{
switch (type)
{
case DEVICE_GREASEWEAZLE:
return "Greaseweazle";
case DEVICE_FLUXENGINE:
return "FluxEngine";
default:
return "unknown";
}
}

View File

@@ -4,15 +4,24 @@
#include "libusbp_config.h"
#include "libusbp.hpp"
enum DeviceType
{
DEVICE_FLUXENGINE,
DEVICE_GREASEWEAZLE
};
extern std::string getDeviceName(DeviceType type);
struct CandidateDevice
{
DeviceType type;
libusbp::device device;
uint32_t id;
std::string serial;
std::string serialPort;
};
extern std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices();
extern std::vector<std::shared_ptr<CandidateDevice>> findUsbDevices();
#endif

182
lib/vfs/appledos.cc Normal file
View File

@@ -0,0 +1,182 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/utils.h"
#include "lib/config.pb.h"
#include <fmt/format.h>
/* This is described here:
* http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system
* (also in Inside AppleDOS)
*/
class AppledosFilesystem : public Filesystem
{
static constexpr int VTOC_BLOCK = 17 * 16;
class AppledosDirent : public Dirent
{
public:
AppledosDirent(const Bytes& de)
{
ByteReader br(de);
track = br.read_8();
sector = br.read_8();
flags = br.read_8();
filename = br.read(30);
length = br.read_le16() * 256;
for (char& c : filename)
c &= 0x7f;
filename = rightTrimWhitespace(filename);
path = {filename};
file_type = TYPE_FILE;
attributes[FILENAME] = filename;
attributes[LENGTH] = std::to_string(length);
attributes[FILE_TYPE] = "file";
attributes["appledos.flags"] = fmt::format("0x{:x}", flags);
}
uint8_t track;
uint8_t sector;
uint8_t flags;
};
public:
AppledosFilesystem(
const AppledosProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_LIST | OP_GETDIRENT | OP_GETFSDATA | OP_GETFILE;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(_vtoc[0x34] * _vtoc[0x35]);
attributes[USED_BLOCKS] = "0";
attributes[BLOCK_SIZE] = "256";
return attributes;
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (path.size() != 0)
throw BadPathException();
std::vector<std::shared_ptr<Dirent>> results;
for (auto& de : _dirents)
results.push_back(de);
return results;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return find(path.front());
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = find(path.front());
int tstrack = dirent->track;
int tssector = dirent->sector;
Bytes bytes;
ByteWriter bw(bytes);
while (tstrack)
{
Bytes ts = getAppleSector(tstrack * 16 + tssector);
ByteReader br(ts);
br.seek(0x0c);
while (!br.eof())
{
int track = br.read_8();
int sector = br.read_8();
if (!track)
goto done;
bw += getAppleSector(track * 16 + sector);
}
tstrack = ts[1];
tssector = ts[2];
}
done:
return bytes;
}
private:
void mount()
{
_vtoc = getAppleSector(VTOC_BLOCK);
if ((_vtoc[0x27] != 122) || (_vtoc[0x36] != 0) || (_vtoc[0x37] != 1))
throw BadFilesystemException();
_dirents.clear();
int track = _vtoc[1];
int sector = _vtoc[2];
while (track)
{
Bytes dir = getAppleSector(track * 16 + sector);
ByteReader br(dir);
br.seek(0x0b);
while (!br.eof())
{
Bytes fde = br.read(0x23);
if ((fde[0] != 0) && (fde[0] != 255))
_dirents.push_back(std::make_shared<AppledosDirent>(fde));
}
track = dir[1];
sector = dir[2];
}
}
std::shared_ptr<AppledosDirent> find(const std::string filename)
{
for (auto& de : _dirents)
if (de->filename == filename)
return de;
throw FileNotFoundException();
}
Bytes getAppleSector(uint32_t number, uint32_t count = 1)
{
return getLogicalSector(
number + _config.filesystem_offset_sectors(), count);
}
private:
const AppledosProto& _config;
Bytes _vtoc;
std::vector<std::shared_ptr<AppledosDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createAppledosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<AppledosFilesystem>(config.appledos(), sectors);
}

View File

@@ -31,7 +31,18 @@ public:
{
ByteReader br(bytes);
filename = br.read(8);
filename = filename.substr(0, filename.find(' '));
for (int i = 0; filename.size(); i++)
{
if (filename[i] == ' ')
{
filename = filename.substr(0, i);
break;
}
if ((filename[i] < 32) || (filename[i] > 126))
throw BadFilesystemException();
}
path = {filename};
brotherType = br.read_8();
@@ -91,7 +102,7 @@ public:
for (int d = 0; d < SECTOR_SIZE / 16; d++)
{
Bytes buffer = bytes.slice(d * 16, 16);
if (buffer[0] == 0xf0)
if (buffer[0] & 0x80)
continue;
auto de = std::make_shared<Brother120Dirent>(buffer);

View File

@@ -188,7 +188,6 @@ public:
/* Find a directory entry for this logical extent. */
std::unique_ptr<Entry> entry;
bool moreExtents = false;
for (int d = 0; d < _config.dir_entries(); d++)
{
entry = getEntry(d);
@@ -196,9 +195,10 @@ public:
continue;
if (path[0] != entry->filename)
continue;
if (entry->extent > logicalExtent)
moreExtents = true;
if (entry->extent == logicalExtent)
if (entry->extent < logicalExtent)
continue;
if ((entry->extent & ~_logicalExtentMask) ==
(logicalExtent & ~_logicalExtentMask))
break;
}
@@ -212,10 +212,10 @@ public:
/* Copy the data out. */
int i =
(entry->extent & ~_logicalExtentMask) * _blocksPerLogicalExtent;
(logicalExtent & _logicalExtentMask) * _blocksPerLogicalExtent;
unsigned records =
(entry->extent == logicalExtent) ? entry->records : 128;
while ((records != 0) && (i != entry->allocation_map.size()))
while (records != 0)
{
Bytes block;
unsigned blockid = entry->allocation_map[i];
@@ -265,7 +265,7 @@ private:
physicalExtentSize = _config.block_size() * 8;
}
_logicalExtentsPerEntry = physicalExtentSize / 16384;
_logicalExtentMask = (1 << _logicalExtentsPerEntry) - 1;
_logicalExtentMask = _logicalExtentsPerEntry - 1;
_blocksPerLogicalExtent = 16384 / _config.block_size();
_directory = getCpmBlock(0, _dirBlocks);

View File

@@ -72,7 +72,7 @@ public:
if (!imageContainsAllSectorsOf(_changedSectors,
track,
side,
trackLayout->logicalSectorOrder))
trackLayout->naturalSectorOrder))
{
/* If we don't have any loaded sectors for this track, pre-read
* it. */
@@ -83,7 +83,7 @@ public:
/* Now merge the loaded track with the changed one, and write
* the result back. */
for (unsigned sectorId : trackLayout->logicalSectorOrder)
for (unsigned sectorId : trackLayout->naturalSectorOrder)
{
if (!_changedSectors.contains(track, side, sectorId))
_changedSectors.put(track, side, sectorId)->data =

296
lib/vfs/philefs.cc Normal file
View File

@@ -0,0 +1,296 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* Root block:
*
* 00-0b volume name
* 0c 01
* 0d 2a
* 0e 79
* 0f 6d
* 10 07 0x07c10c19, creation timestamp
* 11 c1 ^
* 12 0c ^
* 13 19 ^
* 14 2f
* 15 00
* 16 00
* 17 18
* 18 03 0x320, number of blocks on the disk
* 19 20 ^
* 1a 00 0x0010, first data block?
* 1b 10 ^
* 1c 00 0x0010, address of bitmap in HCS
* 1d 10 ^
* 1e 00 0x0011, address of FLIST in HCS
* 1f 11 ^
* 20 00 0x0017, address of last block of FLIST?
* 21 17 ^
* 22 00
* 23 6b
* 24 00
* 25 20
*
* 14 files
* file id 3 is not used
* directory at 0xc00
* 0x4000, block 0x10, volume bitmap
* 0x4400, block 0x11, flist, 7 blocks long?
* file descriptors seem to be 64 bytes
*
* File descriptor, 64 bytes:
* 00 file type
* 0e+04 length in bytes
* 14... spans
* word: start block
* word: number of blocks
*
00000C00 00 01 42 49 54 4D 41 50 2E 53 59 53 00 00 00 00 ..BITMAP.SYS....
00008040 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A...............
00008050 04 00 00 01 00 10 00 01 00 00 00 00 00 00 00 00 ................
00008060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00008070 00 00 00 00 00 00 00 00 00 00 00 01 00 01 01 00 ................
00000C10 00 02 46 4C 49 53 54 2E 53 59 53 00 00 00 00 00 ..FLIST.SYS.....
00008080 41 00 00 00 07 C1 0C 19 1E 00 00 18 00 02 00 00 A...............
00008090 1C 00 00 01 00 11 00 07 00 00 00 00 00 00 00 00 ................
000080A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000080B0 00 00 00 00 00 00 00 00 00 00 00 07 00 07 01 00 ................
00000C20 00 04 53 4B 45 4C 00 00 00 00 00 00 00 00 00 00 ..SKEL..........
00008100 01 00 00 03 07 C1 0C 19 19 00 00 19 00 02 00 00 ................
00008110 55 00 00 01 00 20 00 16 00 00 00 00 00 00 00 00 U.... ..........
00008120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00008130 00 00 00 00 00 00 00 00 00 00 00 16 00 16 01 00 ................
00000C30 00 05 43 4F 44 45 00 00 00 00 00 00 00 00 00 00 ..CODE..........
00004540 01 00 00 03 07 C1 0C 19 26 00 00 1F 00 02 00 08 ........&.......
00004550 10 00 00 01 00 36 02 04 00 00 00 00 00 00 00 00 .....6..........
00004560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00004570 00 00 00 00 00 00 00 00 00 00 02 04 02 04 01 00 ................
00000C40 00 06 53 43 53 49 00 00 00 00 00 00 00 00 00 00 ..SCSI..........
00000C50 00 07 41 4F 46 00 00 00 00 00 00 00 00 00 00 00 ..AOF...........
00000C60 00 08 4D 43 46 00 00 00 00 00 00 00 00 00 00 00 ..MCF...........
00000C70 00 09 53 59 53 54 45 4D 2E 53 43 46 00 00 00 00 ..SYSTEM.SCF....
00000C80 00 0A 53 59 53 54 45 4D 2E 50 44 46 00 00 00 00 ..SYSTEM.PDF....
00000C90 00 0B 43 4C 54 31 2E 43 4C 54 00 00 00 00 00 00 ..CLT1.CLT......
00000CA0 00 0C 43 4C 54 32 2E 43 4C 54 00 00 00 00 00 00 ..CLT2.CLT......
00000CB0 00 0D 43 4C 54 33 2E 43 4C 54 00 00 00 00 00 00 ..CLT3.CLT......
00000CC0 00 0E 43 4C 54 34 2E 43 4C 54 00 00 00 00 00 00 ..CLT4.CLT......
00000CD0 00 0F 47 52 45 59 2E 43 4C 54 00 00 00 00 00 00 ..GREY.CLT......
*/
static void trimZeros(std::string s)
{
s.erase(std::remove(s.begin(), s.end(), 0), s.end());
}
class PhileFilesystem : public Filesystem
{
struct Span
{
uint16_t startBlock;
uint16_t blockCount;
};
class PhileDirent : public Dirent
{
public:
PhileDirent(
int fileno, const std::string& filename, const Bytes& filedes):
_fileno(fileno)
{
file_type = TYPE_FILE;
ByteReader br(filedes);
br.seek(0x0e);
length = br.read_be32();
{
std::stringstream ss;
ss << 'R';
if (filedes[0] & 0x40)
ss << 'S';
mode = ss.str();
}
this->filename = filename;
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = mode;
int spans = br.read_be16();
for (int i = 0; i < spans; i++)
{
Span span;
span.startBlock = br.read_be16();
span.blockCount = br.read_be16();
_spans.push_back(span);
}
attributes["phile.spans"] = std::to_string(spans);
}
const std::vector<Span>& spans() const
{
return _spans;
}
private:
int _fileno;
std::vector<Span> _spans;
};
public:
PhileFilesystem(
const PhileProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
mount();
std::string volumename = _rootBlock.reader().read(0x0c);
trimZeros(volumename);
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = volumename;
attributes[TOTAL_BLOCKS] = std::to_string(_totalBlocks);
attributes[USED_BLOCKS] = attributes[TOTAL_BLOCKS];
attributes[BLOCK_SIZE] = std::to_string(_config.block_size());
return attributes;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
return findFile(path.front());
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
mount();
if (!path.empty())
throw FileNotFoundException();
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : _dirents)
result.push_back(de.second);
return result;
}
Bytes getFile(const Path& path) override
{
mount();
if (path.size() != 1)
throw BadPathException();
auto dirent = findFile(path.front());
Bytes data;
ByteWriter bw(data);
for (const auto& span : dirent->spans())
bw += getPsosBlock(span.startBlock, span.blockCount);
data.resize(dirent->length);
return data;
}
private:
void mount()
{
_sectorSize = getLogicalSectorSize();
_blockSectors = _config.block_size() / _sectorSize;
_rootBlock = getPsosBlock(2, 1);
_bitmapBlockNumber = _rootBlock.reader().seek(0x1c).read_be16();
_filedesBlockNumber = _rootBlock.reader().seek(0x1e).read_be16();
_filedesLength =
_rootBlock.reader().seek(0x20).read_be16() - _filedesBlockNumber + 1;
_totalBlocks = _rootBlock.reader().seek(0x18).read_be16();
Bytes directoryBlock = getPsosBlock(3, 1);
Bytes filedesBlock = getPsosBlock(_filedesBlockNumber, _filedesLength);
_dirents.clear();
ByteReader br(directoryBlock);
ByteReader fr(filedesBlock);
while (!br.eof())
{
uint16_t fileno = br.read_be16();
std::string filename = br.read(14);
trimZeros(filename);
if (fileno)
{
fr.seek(fileno * 64);
Bytes filedes = fr.read(64);
auto dirent =
std::make_unique<PhileDirent>(fileno, filename, filedes);
_dirents[fileno] = std::move(dirent);
}
}
}
std::shared_ptr<PhileDirent> findFile(const std::string filename)
{
for (const auto& dirent : _dirents)
{
if (dirent.second->filename == filename)
return dirent.second;
}
throw FileNotFoundException();
}
Bytes getPsosBlock(uint32_t number, uint32_t count = 1)
{
unsigned sector = number * _blockSectors;
return getLogicalSector(sector, _blockSectors * count);
}
private:
const PhileProto& _config;
int _sectorSize;
int _blockSectors;
int _totalBlocks;
int _bitmapBlockNumber;
int _filedesBlockNumber;
int _filedesLength;
Bytes _rootBlock;
std::map<int, std::shared_ptr<PhileDirent>> _dirents;
};
std::unique_ptr<Filesystem> Filesystem::createPhileFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<PhileFilesystem>(config.phile(), sectors);
}

View File

@@ -203,9 +203,15 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
case FilesystemProto::PRODOS:
return Filesystem::createProdosFilesystem(config, image);
case FilesystemProto::APPLEDOS:
return Filesystem::createAppledosFilesystem(config, image);
case FilesystemProto::SMAKY6:
return Filesystem::createSmaky6Filesystem(config, image);
case FilesystemProto::PHILE:
return Filesystem::createPhileFilesystem(config, image);
default:
Error() << "no filesystem configured";
return std::unique_ptr<Filesystem>();

View File

@@ -248,8 +248,12 @@ public:
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createProdosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createAppledosFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createSmaky6Filesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createPhileFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
static std::unique_ptr<Filesystem> createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);

View File

@@ -59,9 +59,23 @@ message CbmfsProto
message ProdosProto {}
message AppledosProto {
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
}
message Smaky6FsProto {}
// NEXT_TAG: 12
message PhileProto {
optional uint32 block_size = 1 [
default = 1024,
(help) = "Phile filesystem block size"
];
}
// NEXT_TAG: 14
message FilesystemProto
{
enum FilesystemType {
@@ -74,7 +88,9 @@ message FilesystemProto
MACHFS = 6;
CBMFS = 7;
PRODOS = 8;
SMAKY6 = 9;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
}
optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"];
@@ -87,7 +103,9 @@ message FilesystemProto
optional MacHfsProto machfs = 6;
optional CbmfsProto cbmfs = 7;
optional ProdosProto prodos = 8;
optional Smaky6FsProto smaky6 = 11;
optional AppledosProto appledos = 12;
optional Smaky6FsProto smaky6 = 11;
optional PhileProto phile = 13;
optional SectorListProto sector_order = 9 [(help) = "specify the filesystem order of sectors"];
}

View File

@@ -9,6 +9,7 @@ FLUXENGINE_SRCS = \
src/fe-getfileinfo.cc \
src/fe-inspect.cc \
src/fe-ls.cc \
src/fe-merge.cc \
src/fe-mkdir.cc \
src/fe-mv.cc \
src/fe-rm.cc \

View File

@@ -14,68 +14,51 @@
static FlagGroup flags;
static StringFlag sourceFlux(
{ "--source", "-s" },
"'drive:' flux source to use",
"",
[](const auto& value)
{
FluxSource::updateConfigForFilename(config.mutable_flux_source(), value);
});
static StringFlag sourceFlux({"--source", "-s"},
"'drive:' flux source to use",
"",
[](const auto& value)
{
FluxSource::updateConfigForFilename(
config.mutable_flux_source(), value);
});
static IntFlag trackFlag(
{ "--cylinder", "-c" },
"Track to read.",
0);
static IntFlag trackFlag({"--cylinder", "-c"}, "Track to read.", 0);
static IntFlag headFlag(
{ "--head", "-h" },
"Head to read.",
0);
static IntFlag headFlag({"--head", "-h"}, "Head to read.", 0);
static SettableFlag dumpFluxFlag(
{ "--dump-flux", "-F" },
"Dump raw magnetic disk flux.");
{"--dump-flux", "-F"}, "Dump raw magnetic disk flux.");
static SettableFlag dumpBitstreamFlag(
{ "--dump-bitstream", "-B" },
"Dump aligned bitstream.");
{"--dump-bitstream", "-B"}, "Dump aligned bitstream.");
static IntFlag dumpRawFlag(
{ "--dump-raw", "-R" },
"Dump raw binary with offset.",
0);
{"--dump-raw", "-R"}, "Dump raw binary with offset.", 0);
static SettableFlag dumpMfmFm(
{ "--mfmfm" },
"When dumping raw binary, do MFM/FM decoding first.");
{"--mfmfm"}, "When dumping raw binary, do MFM/FM decoding first.");
static SettableFlag dumpBytecodesFlag(
{ "--dump-bytecodes", "-H" },
"Dump the raw FluxEngine bytecodes.");
{"--dump-bytecodes", "-H"}, "Dump the raw FluxEngine bytecodes.");
static IntFlag fluxmapResolutionFlag(
{ "--fluxmap-resolution" },
"Resolution of flux visualisation (nanoseconds). 0 to autoscale",
0);
static IntFlag fluxmapResolutionFlag({"--fluxmap-resolution"},
"Resolution of flux visualisation (nanoseconds). 0 to autoscale",
0);
static DoubleFlag seekFlag(
{ "--seek", "-S" },
"Seek this many milliseconds into the track before displaying it.",
0.0);
static DoubleFlag seekFlag({"--seek", "-S"},
"Seek this many milliseconds into the track before displaying it.",
0.0);
static DoubleFlag manualClockRate(
{ "--manual-clock-rate-us", "-u" },
"If not zero, force this clock rate; if zero, try to autodetect it.",
0.0);
static DoubleFlag manualClockRate({"--manual-clock-rate-us", "-u"},
"If not zero, force this clock rate; if zero, try to autodetect it.",
0.0);
static DoubleFlag noiseFloorFactor(
{ "--noise-floor-factor" },
static DoubleFlag noiseFloorFactor({"--noise-floor-factor"},
"Clock detection noise floor (min + (max-min)*factor).",
0.01);
static DoubleFlag signalLevelFactor(
{ "--signal-level-factor" },
static DoubleFlag signalLevelFactor({"--signal-level-factor"},
"Clock detection signal level (min + (max-min)*factor).",
0.05);
@@ -84,264 +67,214 @@ void setDecoderManualClockRate(double clockrate_us)
manualClockRate.setDefaultValue(clockrate_us);
}
static const std::string BLOCK_ELEMENTS[] =
{ " ", "", "", "", "", "", "", "", "" };
static const std::string BLOCK_ELEMENTS[] = {
" ", "", "", "", "", "", "", "", ""};
/*
* Tries to guess the clock by finding the smallest common interval.
/*
* Tries to guess the clock by finding the smallest common interval.
* Returns nanoseconds.
*/
static nanoseconds_t guessClock(const Fluxmap& fluxmap)
{
if (manualClockRate != 0.0)
return manualClockRate * 1000.0;
if (manualClockRate != 0.0)
return manualClockRate * 1000.0;
uint32_t buckets[256] = {};
FluxmapReader fr(fluxmap);
auto data =
fluxmap.guessClock(noiseFloorFactor.get(), signalLevelFactor.get());
while (!fr.eof())
std::cout << "\nClock detection histogram:" << std::endl;
uint32_t max =
*std::max_element(std::begin(data.buckets), std::end(data.buckets));
bool skipping = true;
for (int i = 0; i < 256; i++)
{
unsigned interval;
fr.findEvent(F_BIT_PULSE, interval);
if (interval > 0xff)
continue;
buckets[interval]++;
}
uint32_t max = *std::max_element(std::begin(buckets), std::end(buckets));
uint32_t min = *std::min_element(std::begin(buckets), std::end(buckets));
uint32_t noise_floor = min + (max-min)*noiseFloorFactor;
uint32_t signal_level = min + (max-min)*signalLevelFactor;
nanoseconds_t value = data.buckets[i];
if (value < data.noiseFloor / 2)
{
if (!skipping)
std::cout << "..." << std::endl;
skipping = true;
}
else
{
skipping = false;
/* Find a point solidly within the first pulse. */
int bar = 320 * value / max;
int fullblocks = bar / 8;
int pulseindex = 0;
while (pulseindex < 256)
{
if (buckets[pulseindex] > signal_level)
break;
pulseindex++;
}
if (pulseindex == -1)
return 0;
std::string s;
for (int j = 0; j < fullblocks; j++)
s += BLOCK_ELEMENTS[8];
s += BLOCK_ELEMENTS[bar & 7];
/* Find the upper and lower bounds of the pulse. */
int peaklo = pulseindex;
while (peaklo > 0)
{
if (buckets[peaklo] < noise_floor)
break;
peaklo--;
std::cout << fmt::format(
"{: 3} {:.2f} {:6} {}", i, (double)i * US_PER_TICK, value, s);
std::cout << std::endl;
}
}
int peakhi = pulseindex;
while (peakhi < 255)
{
if (buckets[peakhi] < noise_floor)
break;
peakhi++;
}
std::cout << fmt::format("Noise floor: {}\n", data.noiseFloor);
std::cout << fmt::format("Signal level: {}\n", data.signalLevel);
std::cout << fmt::format(
"Peak start: {:.2f} us\n", data.peakStart / 1000.0);
std::cout << fmt::format(
"Peak end: {:.2f} us\n", data.peakEnd / 1000.0);
std::cout << fmt::format("Median: {:.2f} us\n", data.median / 1000.0);
/* Find the total accumulated size of the pulse. */
uint32_t total_size = 0;
for (int i = peaklo; i < peakhi; i++)
total_size += buckets[i];
/* Now find the median. */
uint32_t count = 0;
int median = peaklo;
while (median < peakhi)
{
count += buckets[median];
if (count > (total_size/2))
break;
median++;
}
std::cout << "\nClock detection histogram:" << std::endl;
bool skipping = true;
for (int i=0; i<256; i++)
{
uint32_t value = buckets[i];
if (value < noise_floor/2)
{
if (!skipping)
std::cout << "..." << std::endl;
skipping = true;
}
else
{
skipping = false;
int bar = 320*value/max;
int fullblocks = bar / 8;
std::string s;
for (int j=0; j<fullblocks; j++)
s += BLOCK_ELEMENTS[8];
s += BLOCK_ELEMENTS[bar & 7];
std::cout << fmt::format("{: 3} {:.2f} {:6} {}",
i,
(double)i * US_PER_TICK,
value,
s);
std::cout << std::endl;
}
}
std::cout << fmt::format("Noise floor: {}", noise_floor) << std::endl;
std::cout << fmt::format("Signal level: {}", signal_level) << std::endl;
std::cout << fmt::format("Peak start: {} ({:.2f} us)", peaklo, peaklo*US_PER_TICK) << std::endl;
std::cout << fmt::format("Peak end: {} ({:.2f} us)", peakhi, peakhi*US_PER_TICK) << std::endl;
std::cout << fmt::format("Median: {} ({:.2f} us)", median, median*US_PER_TICK) << std::endl;
/*
/*
* Okay, the median should now be a good candidate for the (or a) clock.
* How this maps onto the actual clock rate depends on the encoding.
*/
return median * NS_PER_TICK;
return data.median;
}
int mainInspect(int argc, const char* argv[])
{
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
const auto fluxmap = fluxSource->readFlux(trackFlag, headFlag)->next();
std::unique_ptr<FluxSource> fluxSource(
FluxSource::create(config.flux_source()));
const auto fluxmap = fluxSource->readFlux(trackFlag, headFlag)->next();
std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n",
fluxmap->bytes(),
fluxmap->duration() / 1e6);
std::cout << fmt::format("Required USB bandwidth: {}kB/s\n",
fluxmap->bytes()/1024.0 / (fluxmap->duration() / 1e9));
std::cout << fmt::format("0x{:x} bytes of data in {:.3f}ms\n",
fluxmap->bytes(),
fluxmap->duration() / 1e6);
std::cout << fmt::format("Required USB bandwidth: {}kB/s\n",
(int)(fluxmap->bytes() / 1024.0 / (fluxmap->duration() / 1e9)));
nanoseconds_t clockPeriod = guessClock(*fluxmap);
std::cout << fmt::format("{:.2f} us clock detected.", (double)clockPeriod/1000.0) << std::flush;
nanoseconds_t clockPeriod = guessClock(*fluxmap);
std::cout << fmt::format(
"{:.2f} us clock detected.", (double)clockPeriod / 1000.0)
<< std::flush;
FluxmapReader fmr(*fluxmap);
fmr.seek(seekFlag*1000000.0);
FluxmapReader fmr(*fluxmap);
fmr.seek(seekFlag * 1000000.0);
if (dumpFluxFlag)
{
std::cout << "\n\nMagnetic flux follows (times in us):" << std::endl;
if (dumpFluxFlag)
{
std::cout << "\n\nMagnetic flux follows (times in us):" << std::endl;
int resolution = fluxmapResolutionFlag;
if (resolution == 0)
resolution = clockPeriod / 4;
int resolution = fluxmapResolutionFlag;
if (resolution == 0)
resolution = clockPeriod / 4;
nanoseconds_t nextclock = clockPeriod;
nanoseconds_t nextclock = clockPeriod;
nanoseconds_t now = fmr.tell().ns();
int ticks = now / NS_PER_TICK;
nanoseconds_t now = fmr.tell().ns();
int ticks = now / NS_PER_TICK;
std::cout << fmt::format("{: 10.3f}:-", ticks*US_PER_TICK);
nanoseconds_t lasttransition = 0;
while (!fmr.eof())
{
unsigned thisTicks;
fmr.findEvent(F_BIT_PULSE, thisTicks);
ticks += thisTicks;
std::cout << fmt::format("{: 10.3f}:-", ticks * US_PER_TICK);
nanoseconds_t lasttransition = 0;
while (!fmr.eof())
{
unsigned thisTicks;
fmr.findEvent(F_BIT_PULSE, thisTicks);
ticks += thisTicks;
nanoseconds_t transition = ticks*NS_PER_TICK;
nanoseconds_t next;
bool clocked = false;
nanoseconds_t transition = ticks * NS_PER_TICK;
nanoseconds_t next;
bool bannered = false;
auto banner = [&]()
{
std::cout << fmt::format("\n{: 10.3f}:{}", (double)next / 1000.0, clocked ? '-' : ' ');
bannered = true;
};
bool clocked = false;
for (;;)
{
next = now + resolution;
clocked = now >= nextclock;
if (clocked)
nextclock += clockPeriod;
if (next >= transition)
break;
banner();
now = next;
}
bool bannered = false;
auto banner = [&]()
{
std::cout << fmt::format("\n{: 10.3f}:{}",
(double)next / 1000.0,
clocked ? '-' : ' ');
bannered = true;
};
nanoseconds_t length = transition - lasttransition;
if (!bannered)
banner();
std::cout << fmt::format("==== {:06x} {: 10.3f} +{:.3f} = {:.1f} clocks",
fmr.tell().bytes,
(double)transition / 1000.0,
(double)length / 1000.0,
(double)length / clockPeriod);
bannered = false;
lasttransition = transition;
}
}
for (;;)
{
next = now + resolution;
clocked = now >= nextclock;
if (clocked)
nextclock += clockPeriod;
if (next >= transition)
break;
banner();
now = next;
}
if (dumpBitstreamFlag)
{
std::cout << fmt::format("\n\nAligned bitstream from {:.3f}ms follows:\n",
fmr.tell().ns() / 1000000.0);
nanoseconds_t length = transition - lasttransition;
if (!bannered)
banner();
std::cout << fmt::format(
"==== {:06x} {: 10.3f} +{:.3f} = {:.1f} clocks",
fmr.tell().bytes,
(double)transition / 1000.0,
(double)length / 1000.0,
(double)length / clockPeriod);
bannered = false;
lasttransition = transition;
}
}
FluxDecoder decoder(&fmr, clockPeriod, config.decoder());
while (!fmr.eof())
{
std::cout << fmt::format("{:06x} {: 10.3f} : ",
fmr.tell().bytes, fmr.tell().ns() / 1000000.0);
for (unsigned i=0; i<50; i++)
{
if (fmr.eof())
break;
bool b = decoder.readBit();
std::cout << (b ? 'X' : '-');
}
if (dumpBitstreamFlag)
{
std::cout << fmt::format(
"\n\nAligned bitstream from {:.3f}ms follows:\n",
fmr.tell().ns() / 1000000.0);
std::cout << std::endl;
}
}
FluxDecoder decoder(&fmr, clockPeriod, config.decoder());
while (!fmr.eof())
{
std::cout << fmt::format("{:06x} {: 10.3f} : ",
fmr.tell().bytes,
fmr.tell().ns() / 1000000.0);
for (unsigned i = 0; i < 50; i++)
{
if (fmr.eof())
break;
bool b = decoder.readBit();
std::cout << (b ? 'X' : '-');
}
if (dumpRawFlag.isSet())
{
std::cout << fmt::format("\n\nRaw binary with offset {} from {:.3f}ms follows:\n",
dumpRawFlag.get(),
fmr.tell().ns() / 1000000.0);
std::cout << std::endl;
}
}
FluxDecoder decoder(&fmr, clockPeriod, config.decoder());
for (int i=0; i<dumpRawFlag; i++)
decoder.readBit();
if (dumpRawFlag.isSet())
{
std::cout << fmt::format(
"\n\nRaw binary with offset {} from {:.3f}ms follows:\n",
dumpRawFlag.get(),
fmr.tell().ns() / 1000000.0);
while (!fmr.eof())
{
std::cout << fmt::format("{:06x} {: 10.3f} : ",
fmr.tell().bytes, fmr.tell().ns() / 1000000.0);
FluxDecoder decoder(&fmr, clockPeriod, config.decoder());
for (int i = 0; i < dumpRawFlag; i++)
decoder.readBit();
Bytes bytes;
if (dumpMfmFm)
bytes = decodeFmMfm(decoder.readBits(32*8));
else
bytes = toBytes(decoder.readBits(16*8));
while (!fmr.eof())
{
std::cout << fmt::format("{:06x} {: 10.3f} : ",
fmr.tell().bytes,
fmr.tell().ns() / 1000000.0);
ByteReader br(bytes);
Bytes bytes;
if (dumpMfmFm)
bytes = decodeFmMfm(decoder.readBits(32 * 8));
else
bytes = toBytes(decoder.readBits(16 * 8));
for (unsigned i=0; i<16; i++)
{
if (br.eof())
break;
std::cout << fmt::format("{:02x} ", br.read_8());
}
ByteReader br(bytes);
std::cout << std::endl;
}
}
std::cout << std::endl;
for (unsigned i = 0; i < 16; i++)
{
if (br.eof())
break;
std::cout << fmt::format("{:02x} ", br.read_8());
}
std::cout << std::endl;
}
}
std::cout << std::endl;
if (dumpBytecodesFlag)
{
@@ -353,4 +286,3 @@ int mainInspect(int argc, const char* argv[])
return 0;
}

65
src/fe-merge.cc Normal file
View File

@@ -0,0 +1,65 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "sector.h"
#include "proto.h"
#include "flux.h"
#include "fl2.h"
#include "fl2.pb.h"
#include "fmt/format.h"
#include "fluxengine.h"
#include <fstream>
static FlagGroup flags;
static std::vector<std::string> inputFluxFiles;
static StringFlag sourceFlux({"-s", "--source"},
"flux file to read from (repeatable)",
"",
[](const auto& value)
{
inputFluxFiles.push_back(value);
});
static StringFlag destFlux(
{"-d", "--dest"}, "destination flux file to write to", "");
int mainMerge(int argc, const char* argv[])
{
flags.parseFlags(argc, argv);
if (inputFluxFiles.empty())
Error() << "you must specify at least one input flux file (with -s)";
if (destFlux.get() == "")
Error() << "you must specify an output flux file (with -d)";
std::map<std::pair<int, int>, TrackFluxProto> data;
for (const auto& s : inputFluxFiles)
{
fmt::print("Reading {}...\n", s);
FluxFileProto f = loadFl2File(s);
for (auto& trackflux : f.track())
{
auto key = std::make_pair(trackflux.track(), trackflux.head());
auto i = data.find(key);
if (i == data.end())
data[key] = trackflux;
else
{
for (auto flux : trackflux.flux())
i->second.add_flux(flux);
}
}
}
FluxFileProto proto;
for (auto& i : data)
*proto.add_track() = i.second;
fmt::print("Writing {}...\n", destFlux.get());
saveFl2File(destFlux.get(), proto);
return 0;
}

View File

@@ -12,6 +12,7 @@ extern command_cb mainGetFile;
extern command_cb mainGetFileInfo;
extern command_cb mainInspect;
extern command_cb mainLs;
extern command_cb mainMerge;
extern command_cb mainMkDir;
extern command_cb mainMv;
extern command_cb mainPutFile;
@@ -44,6 +45,7 @@ static std::vector<Command> commands =
{ "format", mainFormat, "Format a disk and make a file system on it.", },
{ "rawread", mainRawRead, "Reads raw flux from a disk. Warning: you can't use this to copy disks.", },
{ "rawwrite", mainRawWrite, "Writes a flux file to a disk. Warning: you can't use this to copy disks.", },
{ "merge", mainMerge, "Merge together multiple flux files.", },
{ "getdiskinfo", mainGetDiskInfo, "Read volume metadata off a disk (or image).", },
{ "ls", mainLs, "Show files on disk (or image).", },
{ "mv", mainMv, "Rename a file on a disk (or image).", },

202
src/formats/_apple2.textpb Normal file
View File

@@ -0,0 +1,202 @@
comment: 'Apple II generic settings'
is_extension: true
drive {
high_density: false
}
decoder {
apple2 {}
}
encoder {
apple2 {}
}
option {
name: "nofs"
comment: "use physical CHS sector order and no file system"
exclusivity_group: "format"
}
option {
name: "appledos"
comment: "use AppleDOS soft sector skew and file system"
message: "compensating for AppleDOS soft sector skew"
exclusivity_group: "format"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: APPLEDOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 13
sector: 11
sector: 9
sector: 7
sector: 5
sector: 3
sector: 1
sector: 14
sector: 12
sector: 10
sector: 8
sector: 6
sector: 4
sector: 2
sector: 15
}
}
}
}
}
option {
name: "prodos"
comment: "use ProDOS soft sector skew and filesystem"
message: "compensating for ProDOS soft sector skew"
exclusivity_group: "format"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: PRODOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
}
}
}
option {
name: "cpm"
comment: "use CP/M soft sector skew and filesystem"
message: "compensating for CP/M soft sector skew"
exclusivity_group: "format"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 3
}
block_size: 4096
dir_entries: 128
}
}
decoder {
apple2 {
side_one_track_offset: 80
}
}
encoder {
apple2 {
side_one_track_offset: 80
}
}
layout {
layoutdata {
# The boot tracks use ProDOS translation.
track: 0
up_to_track: 2
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
layoutdata {
# The data tracks use their own, special translation.
track: 3
up_to_track: 79
filesystem {
sector: 0
sector: 3
sector: 6
sector: 9
sector: 12
sector: 15
sector: 2
sector: 5
sector: 8
sector: 11
sector: 14
sector: 1
sector: 4
sector: 7
sector: 10
sector: 13
}
}
}
}
}

View File

@@ -20,3 +20,8 @@ layout {
decoder {
agat {}
}
encoder {
agat {}
}

View File

@@ -1,115 +0,0 @@
comment: 'Apple II 140kB DOS 3.3 5.25" 40 track SSSD'
image_reader {
filename: "apple2.img"
type: IMG
}
layout {
tracks: 35
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
image_writer {
filename: "apple2.img"
type: IMG
}
decoder {
apple2 {}
}
encoder {
apple2 {}
}
tpi: 48
option {
name: "appledos"
comment: "specifies AppleDOS soft sector skew for filesystem access and images"
message: "compensating for AppleDOS soft sector skew"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 14
sector: 13
sector: 12
sector: 11
sector: 10
sector: 9
sector: 8
sector: 7
sector: 6
sector: 5
sector: 4
sector: 3
sector: 2
sector: 1
sector: 15
}
}
}
}
}
option {
name: "prodos"
comment: "specifies ProDOS soft sector skew for filesystem access and images"
message: "compensating for ProDOS soft sector skew"
config {
image_reader {
filesystem_sector_order: true
}
image_writer {
filesystem_sector_order: true
}
filesystem {
type: PRODOS
}
layout {
layoutdata {
filesystem {
sector: 0
sector: 2
sector: 4
sector: 6
sector: 8
sector: 10
sector: 12
sector: 14
sector: 1
sector: 3
sector: 5
sector: 7
sector: 9
sector: 11
sector: 13
sector: 15
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
comment: 'Apple II 140kB 5.25" 35 track SSDD'
include: '_apple2'
image_reader {
filename: "appleii140.img"
type: IMG
}
image_writer {
filename: "appleii140.img"
type: IMG
}
layout {
tracks: 35
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
tpi: 48

View File

@@ -0,0 +1,42 @@
comment: 'Apple II 640kB 5.25" 80 track DSDD'
include: '_apple2'
image_reader {
filename: "appleii640.img"
type: IMG
}
image_writer {
filename: "appleii640.img"
type: IMG
}
layout {
tracks: 80
sides: 2
order: HCS
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 16
}
}
}
tpi: 96
option {
name: "side1"
comment: "read the volume on side 1 of a disk (AppleDOS only)"
message: "accessing volume on side 1"
config {
filesystem {
appledos {
filesystem_offset_sectors: 0x500
}
}
}
}

View File

@@ -32,7 +32,7 @@ decoder {
}
drive {
head_bias: 1
head_bias: 3
}
filesystem {

View File

@@ -1,6 +1,7 @@
FORMATS = \
_acornadfs8 \
_acornadfs32 \
_apple2 \
_atari \
_micropolis \
_northstar \
@@ -18,7 +19,8 @@ FORMATS = \
ampro400 \
ampro800 \
apple2_drive \
apple2 \
appleii140 \
appleii640 \
atarist360 \
atarist370 \
atarist400 \
@@ -61,10 +63,16 @@ FORMATS = \
northstar175 \
northstar350 \
northstar87 \
psos800 \
rolandd20 \
rx50 \
shugart_drive \
smaky6 \
tids990 \
tiki90 \
tiki200 \
tiki400 \
tiki800 \
victor9k_ds \
victor9k_ss \
zilogmcz \

View File

@@ -17,6 +17,7 @@ layout {
sector_size: 512
physical {
start_sector: 0
skew: 6
}
}
layoutdata {

View File

@@ -0,0 +1,58 @@
comment: 'pSOS generic 800kB DSDD with PHILE'
drive {
high_density: false
rotational_period_ms: 200
}
image_reader {
filename: "pme.img"
type: IMG
}
image_writer {
filename: "pme.img"
type: IMG
}
layout {
tracks: 80
sides: 2
order: HCS
swap_sides: true
layoutdata {
sector_size: 1024
physical {
sector: 1
sector: 2
sector: 3
sector: 4
sector: 5
}
}
}
encoder {
ibm {
trackdata {
target_rotational_period_ms: 200
target_clock_period_us: 4
gap0: 80
gap2: 22
gap3: 80
}
}
}
decoder {
ibm {
trackdata {
ignore_side_byte: true
}
}
}
filesystem {
type: PHILE
}

View File

@@ -0,0 +1,28 @@
comment: 'Roland D20'
image_writer {
filename: "rolandd20.img"
type: IMG
}
layout {
tracks: 78
sides: 1
layoutdata {
sector_size: 256
physical {
start_sector: 0
count: 12
skew: 5
}
}
}
decoder {
brother {}
}
drive {
head_bias: 1
}

View File

@@ -0,0 +1,38 @@
comment: 'Tiki 100 200kB 40-track 10-sector SSSD (ro)'
image_writer {
filename: "tiki200.img"
type: IMG
}
layout {
tracks: 40
sides: 1
layoutdata {
side: 0
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 2
}
block_size: 1024
dir_entries: 64
}
}
tpi: 48

View File

@@ -0,0 +1,38 @@
comment: 'Tiki 100 400kB 40-track 10-sector DSSD (ro)'
image_writer {
filename: "tiki400.img"
type: IMG
}
layout {
tracks: 40
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
side: 0
track: 1
}
block_size: 2048
dir_entries: 128
}
}
tpi: 48

View File

@@ -0,0 +1,38 @@
comment: 'Tiki 100 800kB 80-track 10-sector DSSD (ro)'
image_writer {
filename: "tiki900.img"
type: IMG
}
layout {
tracks: 80
sides: 2
layoutdata {
sector_size: 512
physical {
start_sector: 1
count: 10
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
side: 0
track: 1
}
block_size: 2048
dir_entries: 128
}
}
tpi: 96

38
src/formats/tiki90.textpb Normal file
View File

@@ -0,0 +1,38 @@
comment: 'Tiki 100 90kB 40-track 18-sector SSSD (ro)'
image_writer {
filename: "tiki90.img"
type: IMG
}
layout {
tracks: 40
sides: 1
layoutdata {
side: 0
sector_size: 128
physical {
start_sector: 1
count: 18
}
}
}
decoder {
ibm {}
}
filesystem {
type: CPMFS
cpmfs {
filesystem_start {
track: 3
}
block_size: 1024
dir_entries: 32
}
}
tpi: 48

692
src/gui/browserpanel.cc Normal file
View File

@@ -0,0 +1,692 @@
#include "globals.h"
#include "lib/fluxmap.h"
#include "lib/vfs/vfs.h"
#include "lib/utils.h"
#include "gui.h"
#include "layout.h"
#include "filesystemmodel.h"
#include "fileviewerwindow.h"
#include "textviewerwindow.h"
#include "jobqueue.h"
const std::string DND_TYPE = "fluxengine.files";
class BrowserPanelImpl : public BrowserPanelGen, public BrowserPanel, JobQueue
{
enum
{
STATE_DEAD,
STATE_WORKING,
STATE_IDLE,
};
public:
BrowserPanelImpl(MainWindow* mainWindow, wxSimplebook* parent):
BrowserPanelGen(parent),
BrowserPanel(mainWindow),
/* This is wrong. Apparently the wxDataViewCtrl doesn't work properly
* with DnD unless the format is wxDF_UNICODETEXT. It should be a custom
* value. */
_dndFormat(wxDF_UNICODETEXT)
{
_filesystemModel = FilesystemModel::Associate(browserTree);
/* This is a bug workaround for an issue in wxformbuilder's generated
* code; see https://github.com/wxFormBuilder/wxFormBuilder/pull/758.
* The default handler for the submenu doesn't allow events to fire on
* the button itself, so we have to override it with our own version. */
browserToolbar->Connect(browserMoreMenuButton->GetId(),
wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN,
wxAuiToolBarEventHandler(BrowserPanelImpl::OnBrowserMoreMenuButton),
NULL,
this);
/* This is a bug workaround for an issue where the calculation of the
* item being dropped on is wrong due to the header not being taken into
* account. See https://forums.wxwidgets.org/viewtopic.php?t=44752. */
browserTree->EnableDragSource(_dndFormat);
browserTree->EnableDropTarget(_dndFormat);
parent->AddPage(this, "browser");
}
void OnBackButton(wxCommandEvent&) override
{
StartIdle();
}
private:
void SetState(int state)
{
if (state != _state)
{
_state = state;
CallAfter(
[&]()
{
UpdateState();
});
}
}
void SwitchFrom() override
{
SetState(STATE_DEAD);
}
public:
void StartBrowsing() override
{
try
{
SetPage(MainWindow::PAGE_BROWSER);
PrepareConfig();
_filesystemModel->Clear(Path());
_filesystemCapabilities = 0;
_filesystemIsReadOnly = true;
_filesystemNeedsFlushing = false;
SetState(STATE_WORKING);
QueueJob(
[this]()
{
_filesystem = Filesystem::createFilesystemFromConfig();
_filesystemCapabilities = _filesystem->capabilities();
_filesystemIsReadOnly = _filesystem->isReadOnly();
runOnUiThread(
[&]()
{
RepopulateBrowser();
});
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
StartIdle();
}
}
void StartFormatting() override
{
try
{
SetPage(MainWindow::PAGE_BROWSER);
PrepareConfig();
_filesystemModel->Clear(Path());
_filesystemCapabilities = 0;
_filesystemIsReadOnly = true;
_filesystemNeedsFlushing = false;
SetState(STATE_WORKING);
QueueJob(
[this]()
{
_filesystem = Filesystem::createFilesystemFromConfig();
_filesystemCapabilities = _filesystem->capabilities();
_filesystemIsReadOnly = _filesystem->isReadOnly();
runOnUiThread(
[&]()
{
wxCommandEvent e;
OnBrowserFormatButton(e);
});
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
StartIdle();
}
}
void OnQueueEmpty() override
{
SetState(STATE_IDLE);
}
void QueueJob(std::function<void(void)> f)
{
SetState(STATE_WORKING);
JobQueue::QueueJob(f);
}
private:
void UpdateState()
{
bool running = !IsQueueEmpty();
bool selection = browserTree->GetSelection().IsOk();
browserToolbar->EnableTool(
browserBackTool->GetId(), _state == STATE_IDLE);
uint32_t c = _filesystemCapabilities;
bool ro = _filesystemIsReadOnly;
bool needsFlushing = _filesystemNeedsFlushing;
browserToolbar->EnableTool(browserInfoTool->GetId(),
!running && (c & Filesystem::OP_GETDIRENT) && selection);
browserToolbar->EnableTool(browserViewTool->GetId(),
!running && (c & Filesystem::OP_GETFILE) && selection);
browserToolbar->EnableTool(browserSaveTool->GetId(),
!running && (c & Filesystem::OP_GETFILE) && selection);
browserMoreMenu->Enable(browserAddMenuItem->GetId(),
!running && !ro && (c & Filesystem::OP_PUTFILE));
browserMoreMenu->Enable(browserNewDirectoryMenuItem->GetId(),
!running && !ro && (c & Filesystem::OP_CREATEDIR));
browserMoreMenu->Enable(browserRenameMenuItem->GetId(),
!running && !ro && (c & Filesystem::OP_MOVE) && selection);
browserMoreMenu->Enable(browserDeleteMenuItem->GetId(),
!running && !ro && (c & Filesystem::OP_DELETE) && selection);
browserToolbar->EnableTool(browserFormatTool->GetId(),
!running && !ro && (c & Filesystem::OP_CREATE));
browserDiscardButton->Enable(!running && needsFlushing);
browserCommitButton->Enable(!running && needsFlushing);
browserToolbar->Refresh();
}
void OnBrowserMoreMenuButton(wxAuiToolBarEvent& event)
{
browserToolbar->SetToolSticky(event.GetId(), true);
wxRect rect = browserToolbar->GetToolRect(event.GetId());
wxPoint pt = browserToolbar->ClientToScreen(rect.GetBottomLeft());
pt = ScreenToClient(pt);
browserToolbar->PopupMenu(browserMoreMenu, pt);
browserToolbar->SetToolSticky(event.GetId(), false);
}
void RepopulateBrowser(Path path = Path())
{
QueueJob(
[this, path]()
{
auto files = _filesystem->list(path);
runOnUiThread(
[&]()
{
_filesystemModel->Clear(path);
for (auto& f : files)
_filesystemModel->Add(f);
auto node = _filesystemModel->Find(path);
if (node)
browserTree->Expand(node->item);
UpdateFilesystemData();
});
});
}
void UpdateFilesystemData()
{
QueueJob(
[this]()
{
auto metadata = _filesystem->getMetadata();
_filesystemNeedsFlushing = _filesystem->needsFlushing();
runOnUiThread(
[&]()
{
try
{
uint32_t blockSize =
std::stoul(metadata.at(Filesystem::BLOCK_SIZE));
uint32_t totalBlocks = std::stoul(
metadata.at(Filesystem::TOTAL_BLOCKS));
uint32_t usedBlocks = std::stoul(
metadata.at(Filesystem::USED_BLOCKS));
diskSpaceGauge->Enable();
diskSpaceGauge->SetRange(totalBlocks * blockSize);
diskSpaceGauge->SetValue(usedBlocks * blockSize);
}
catch (const std::out_of_range& e)
{
diskSpaceGauge->Disable();
}
});
});
}
void OnBrowserDirectoryExpanding(wxDataViewEvent& event) override
{
auto node = _filesystemModel->Find(event.GetItem());
if (node && !node->populated && !node->populating)
{
node->populating = true;
RepopulateBrowser(node->dirent->path);
}
}
void OnBrowserInfoButton(wxCommandEvent&) override
{
auto item = browserTree->GetSelection();
auto node = _filesystemModel->Find(item);
std::stringstream ss;
ss << "File attributes for " << node->dirent->path.to_str() << ":\n\n";
for (const auto& e : node->dirent->attributes)
ss << e.first << "=" << quote(e.second) << "\n";
TextViewerWindow::Create(
this, node->dirent->path.to_str(), ss.str(), true)
->Show();
}
void OnBrowserViewButton(wxCommandEvent&) override
{
auto item = browserTree->GetSelection();
auto node = _filesystemModel->Find(item);
QueueJob(
[this, node]()
{
auto bytes = _filesystem->getFile(node->dirent->path);
runOnUiThread(
[&]()
{
(new FileViewerWindow(
this, node->dirent->path.to_str(), bytes))
->Show();
});
});
}
void OnBrowserSaveButton(wxCommandEvent&) override
{
auto item = browserTree->GetSelection();
auto node = _filesystemModel->Find(item);
GetfileDialog d(this, wxID_ANY);
d.filenameText->SetValue(node->dirent->path.to_str());
d.targetFilePicker->SetFileName(wxFileName(node->dirent->filename));
d.targetFilePicker->SetFocus();
d.buttons_OK->SetDefault();
if (d.ShowModal() != wxID_OK)
return;
auto localPath = d.targetFilePicker->GetPath().ToStdString();
QueueJob(
[this, node, localPath]()
{
auto bytes = _filesystem->getFile(node->dirent->path);
bytes.writeToFile(localPath);
});
}
/* Called from worker thread only! */
Path ResolveFileConflicts_WT(Path path)
{
do
{
try
{
_filesystem->getDirent(path);
}
catch (const FileNotFoundException& e)
{
break;
}
runOnUiThread(
[&]()
{
FileConflictDialog d(this, wxID_ANY);
d.oldNameText->SetValue(path.to_str());
d.newNameText->SetValue(path.to_str());
d.newNameText->SetFocus();
d.buttons_OK->SetDefault();
if (d.ShowModal() == wxID_OK)
path = Path(d.newNameText->GetValue().ToStdString());
else
path = Path("");
});
} while (!path.empty());
return path;
}
std::shared_ptr<FilesystemNode> GetTargetDirectoryNode(wxDataViewItem& item)
{
Path path;
if (item.IsOk())
{
auto node = _filesystemModel->Find(item);
if (!node)
return nullptr;
path = node->dirent->path;
}
auto node = _filesystemModel->Find(path);
if (!node)
return nullptr;
if (node->dirent->file_type != TYPE_DIRECTORY)
return _filesystemModel->Find(path.parent());
return node;
}
void OnBrowserAddMenuItem(wxCommandEvent&) override
{
auto item = browserTree->GetSelection();
auto dirNode = GetTargetDirectoryNode(item);
if (!dirNode)
return;
auto localPath = wxFileSelector("Choose the name of the file to add",
/* default_path= */ wxEmptyString,
/* default_filename= */ wxEmptyString,
/* default_extension= */ wxEmptyString,
/* wildcard= */ wxEmptyString,
/* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST)
.ToStdString();
if (localPath.empty())
return;
auto path = dirNode->dirent->path.concat(
wxFileName(localPath).GetFullName().ToStdString());
QueueJob(
[this, path, localPath]() mutable
{
path = ResolveFileConflicts_WT(path);
if (path.empty())
return;
auto bytes = Bytes::readFromFile(localPath);
_filesystem->putFile(path, bytes);
auto dirent = _filesystem->getDirent(path);
runOnUiThread(
[&]()
{
_filesystemModel->Add(dirent);
UpdateFilesystemData();
});
});
}
void OnBrowserDeleteMenuItem(wxCommandEvent&) override
{
auto item = browserTree->GetSelection();
auto node = _filesystemModel->Find(item);
if (!node)
return;
QueueJob(
[this, node]()
{
_filesystem->deleteFile(node->dirent->path);
runOnUiThread(
[&]()
{
_filesystemModel->Delete(node->dirent->path);
UpdateFilesystemData();
});
});
}
void OnBrowserFormatButton(wxCommandEvent&) override
{
FormatDialog d(this, wxID_ANY);
d.volumeNameText->SetFocus();
d.buttons_OK->SetDefault();
if (d.ShowModal() != wxID_OK)
return;
auto volumeName = d.volumeNameText->GetValue().ToStdString();
auto quickFormat = d.quickFormatCheckBox->GetValue();
QueueJob(
[this, volumeName, quickFormat]()
{
_filesystem->discardChanges();
_filesystem->create(quickFormat, volumeName);
runOnUiThread(
[&]()
{
RepopulateBrowser();
});
});
}
void OnBrowserFilenameChanged(wxDataViewEvent& event) override
{
if (!(_filesystem->capabilities() & Filesystem::OP_MOVE))
return;
auto node = _filesystemModel->Find(event.GetItem());
if (!node)
return;
if (node->newname.empty())
return;
if (node->newname == node->dirent->filename)
return;
QueueJob(
[this, node]() mutable
{
auto oldPath = node->dirent->path;
auto newPath = oldPath.parent().concat(node->newname);
newPath = ResolveFileConflicts_WT(newPath);
if (newPath.empty())
return;
_filesystem->moveFile(oldPath, newPath);
auto dirent = _filesystem->getDirent(newPath);
runOnUiThread(
[&]()
{
_filesystemModel->Delete(oldPath);
_filesystemModel->Add(dirent);
UpdateFilesystemData();
});
});
}
void OnBrowserRenameMenuItem(wxCommandEvent& event) override
{
auto item = browserTree->GetSelection();
auto node = _filesystemModel->Find(item);
FileRenameDialog d(this, wxID_ANY);
d.oldNameText->SetValue(node->dirent->path.to_str());
d.newNameText->SetValue(node->dirent->path.to_str());
d.newNameText->SetFocus();
d.buttons_OK->SetDefault();
if (d.ShowModal() != wxID_OK)
return;
ActuallyMoveFile(
node->dirent->path, Path(d.newNameText->GetValue().ToStdString()));
}
void ActuallyMoveFile(const Path& oldPath, Path newPath)
{
QueueJob(
[this, oldPath, newPath]() mutable
{
newPath = ResolveFileConflicts_WT(newPath);
if (newPath.empty())
return;
_filesystem->moveFile(oldPath, newPath);
auto dirent = _filesystem->getDirent(newPath);
runOnUiThread(
[&]()
{
_filesystemModel->Delete(oldPath);
_filesystemModel->Add(dirent);
UpdateFilesystemData();
});
});
}
void OnBrowserNewDirectoryMenuItem(wxCommandEvent& event) override
{
auto item = browserTree->GetSelection();
auto node = GetTargetDirectoryNode(item);
if (!node)
return;
auto path = node->dirent->path;
CreateDirectoryDialog d(this, wxID_ANY);
d.newNameText->SetValue(path.to_str() + "/");
d.newNameText->SetFocus();
d.buttons_OK->SetDefault();
if (d.ShowModal() != wxID_OK)
return;
auto newPath = Path(d.newNameText->GetValue().ToStdString());
QueueJob(
[this, newPath]() mutable
{
newPath = ResolveFileConflicts_WT(newPath);
_filesystem->createDirectory(newPath);
auto dirent = _filesystem->getDirent(newPath);
runOnUiThread(
[&]()
{
_filesystemModel->Add(dirent);
UpdateFilesystemData();
});
});
}
void OnBrowserBeginDrag(wxDataViewEvent& event) override
{
auto item = browserTree->GetSelection();
if (!item.IsOk())
{
event.Veto();
return;
}
auto node = _filesystemModel->Find(item);
if (!node)
{
event.Veto();
return;
}
wxTextDataObject* obj = new wxTextDataObject();
obj->SetText(node->dirent->path.to_str());
event.SetDataObject(obj);
event.SetDataFormat(_dndFormat);
}
void OnBrowserDropPossible(wxDataViewEvent& event) override
{
if (event.GetDataFormat() != _dndFormat)
{
event.Veto();
return;
}
}
void OnBrowserDrop(wxDataViewEvent& event) override
{
try
{
if (event.GetDataFormat() != _dndFormat)
throw CancelException();
#if defined __WXGTK__
/* wxWidgets 3.0 data view DnD on GTK is borked. See
* https://forums.wxwidgets.org/viewtopic.php?t=44752. The hit
* detection is done against the wrong object, resulting in the
* header size not being taken into account, so we have to
* manually do hit detection correctly. */
auto* window = browserTree->GetMainWindow();
auto screenPos = wxGetMousePosition();
auto relPos = screenPos - window->GetScreenPosition();
wxDataViewItem item;
wxDataViewColumn* column;
browserTree->HitTest(relPos, item, column);
if (!item.IsOk())
throw CancelException();
#else
auto item = event.GetItem();
#endif
auto destDirNode = GetTargetDirectoryNode(item);
if (!destDirNode)
throw CancelException();
auto destDirPath = destDirNode->dirent->path;
wxTextDataObject obj;
obj.SetData(_dndFormat, event.GetDataSize(), event.GetDataBuffer());
auto srcPath = Path(obj.GetText().ToStdString());
if (srcPath.empty())
throw CancelException();
ActuallyMoveFile(srcPath, destDirPath.concat(srcPath.back()));
}
catch (const CancelException& e)
{
event.Veto();
}
}
void OnBrowserCommitButton(wxCommandEvent&) override
{
QueueJob(
[this]()
{
_filesystem->flushChanges();
UpdateFilesystemData();
});
}
void OnBrowserDiscardButton(wxCommandEvent&) override
{
QueueJob(
[this]()
{
_filesystem->discardChanges();
runOnUiThread(
[&]()
{
RepopulateBrowser();
});
});
}
void OnBrowserSelectionChanged(wxDataViewEvent& event) override
{
UpdateState();
}
private:
int _state = STATE_DEAD;
std::unique_ptr<Filesystem> _filesystem;
uint32_t _filesystemCapabilities;
bool _filesystemIsReadOnly;
bool _filesystemNeedsFlushing;
FilesystemModel* _filesystemModel;
wxDataFormat _dndFormat;
};
BrowserPanel* BrowserPanel::Create(MainWindow* mainWindow, wxSimplebook* parent)
{
return new BrowserPanelImpl(mainWindow, parent);
}

View File

@@ -1,27 +1,39 @@
ifneq ($(shell $(WX_CONFIG) --version),)
FLUXENGINE_GUI_SRCS = \
src/gui/browserpanel.cc \
src/gui/customstatusbar.cc \
src/gui/explorerpanel.cc \
src/gui/filesystemmodel.cc \
src/gui/fileviewerwindow.cc \
src/gui/fluxviewercontrol.cc \
src/gui/fluxviewerwindow.cc \
src/gui/histogramviewer.cc \
src/gui/iconbutton.cc \
src/gui/idlepanel.cc \
src/gui/imagerpanel.cc \
src/gui/jobqueue.cc \
src/gui/layout.cpp \
src/gui/main.cc \
src/gui/mainwindow.cc \
src/gui/texteditorwindow.cc \
src/gui/textviewerwindow.cc \
src/gui/fileviewerwindow.cc \
src/gui/visualisationcontrol.cc \
src/gui/idlepanel.cc: \
$(OBJDIR)/extras/hardware.h \
$(OBJDIR)/extras/fluxfile.h \
$(OBJDIR)/extras/imagefile.h
FLUXENGINE_GUI_OBJS = \
$(patsubst %.cpp, $(OBJDIR)/%.o, \
$(patsubst %.cc, $(OBJDIR)/%.o, $(FLUXENGINE_GUI_SRCS)) \
)
OBJS += $(FLUXENGINE_GUI_OBJS)
$(FLUXENGINE_GUI_SRCS): | $(PROTO_HDRS)
$(FLUXENGINE_GUI_OBJS): CFLAGS += $(shell $(WX_CONFIG) --cxxflags core base adv aui)
$(FLUXENGINE_GUI_OBJS): CFLAGS += $(shell $(WX_CONFIG) --cxxflags core base adv aui richtext)
FLUXENGINE_GUI_BIN = $(OBJDIR)/fluxengine-gui.exe
$(FLUXENGINE_GUI_BIN): LDFLAGS += $(shell $(WX_CONFIG) --libs core base adv aui)
$(FLUXENGINE_GUI_BIN): LDFLAGS += $(shell $(WX_CONFIG) --libs core base adv aui richtext)
$(FLUXENGINE_GUI_BIN): $(FLUXENGINE_GUI_OBJS)
$(call use-pkgconfig, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), fmt)
@@ -40,6 +52,11 @@ fluxengine-gui$(EXT): $(FLUXENGINE_GUI_BIN)
@echo CP $@
@cp $< $@
$(OBJDIR)/%.h: %.png
@echo ENCODE $@
@mkdir -p $(dir $@)
@xxd -i $^ > $@
ifeq ($(PLATFORM),OSX)
binaries: FluxEngine.pkg
@@ -52,13 +69,16 @@ FluxEngine.app: fluxengine-gui$(EXT) $(OBJDIR)/fluxengine.icns
@echo MAKEAPP $@
@rm -rf $@
@cp -a extras/FluxEngine.app.template $@
@touch $@
@cp fluxengine-gui$(EXT) $@/Contents/MacOS/fluxengine-gui
@mkdir -p $@/Contents/Resources
@cp $(OBJDIR)/fluxengine.icns $@/Contents/Resources/FluxEngine.icns
@for name in `otool -L fluxengine-gui$(EXT) | tr -d '\t' | grep -v '^/System/' | grep -v '^/usr/lib/' | grep -v ':$$' | awk '{print $$1}'`; do cp "$$name" $@/Contents/Resources; done
@cp /usr/local/opt/wxwidgets/README.md $@/Contents/Resources/wxWidgets.md
@cp /usr/local/opt/protobuf/LICENSE $@/Contents/Resources/protobuf.txt
@cp /usr/local/opt/fmt/LICENSE.rst $@/Contents/Resources/fmt.rst
@dylibbundler -of -x $@/Contents/MacOS/fluxengine-gui -b -d $@/Contents/libs -cd > /dev/null
@cp /usr/local/opt/wxwidgets/README.md $@/Contents/libs/wxWidgets.md
@cp /usr/local/opt/protobuf/LICENSE $@/Contents/libs/protobuf.txt
@cp /usr/local/opt/fmt/LICENSE.rst $@/Contents/libs/fmt.rst
@cp /usr/local/opt/libpng/LICENSE $@/Contents/libs/libpng.txt
@cp /usr/local/opt/libjpeg/README $@/Contents/libs/libjpeg.txt
$(OBJDIR)/fluxengine.icns: $(OBJDIR)/fluxengine.iconset
@echo ICONUTIL $@

235
src/gui/explorerpanel.cc Normal file
View File

@@ -0,0 +1,235 @@
#include "lib/globals.h"
#include "lib/fluxmap.h"
#include "lib/environment.h"
#include "lib/fluxsource/fluxsource.h"
#include "lib/decoders/decoders.h"
#include "lib/proto.h"
#include "gui.h"
#include "layout.h"
#include "jobqueue.h"
static Bytes fakeBits(const std::vector<bool>& bits)
{
Bytes result;
ByteWriter bw(result);
auto it = bits.begin();
while (it != bits.end())
{
uint8_t b = (*it++) << 4;
if (it != bits.end())
b |= *it++;
bw.write_8(b);
}
return result;
}
class ExplorerPanelImpl :
public ExplorerPanelGen,
public ExplorerPanel,
JobQueue
{
enum
{
STATE_DEAD,
STATE_WORKING,
STATE_IDLE,
};
public:
ExplorerPanelImpl(MainWindow* mainWindow, wxSimplebook* parent):
ExplorerPanelGen(parent),
ExplorerPanel(mainWindow)
{
parent->AddPage(this, "explorer");
}
public:
void Start() override
{
try
{
SetPage(MainWindow::PAGE_EXPLORER);
PrepareConfig();
SetState(STATE_IDLE);
_explorerFluxmap = nullptr;
_explorerTrack = -1;
_explorerSide = -1;
_explorerUpdatePending = false;
UpdateExplorerData();
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
StartIdle();
}
}
void UpdateState()
{
explorerToolbar->EnableTool(
explorerBackTool->GetId(), _state == STATE_IDLE);
explorerToolbar->Refresh();
}
void OnBackButton(wxCommandEvent&) override
{
StartIdle();
}
private:
void SetState(int state)
{
if (state != _state)
{
_state = state;
CallAfter(
[&]()
{
UpdateState();
});
}
}
void SwitchFrom() override
{
SetState(STATE_DEAD);
}
private:
void OnExplorerSettingChange(wxSpinEvent& event) override
{
UpdateExplorerData();
}
void OnExplorerSettingChange(wxSpinDoubleEvent& event) override
{
UpdateExplorerData();
}
void OnExplorerSettingChange(wxCommandEvent& event) override
{
UpdateExplorerData();
}
void OnExplorerRefreshButton(wxCommandEvent& event) override
{
_explorerFluxmap = nullptr;
_explorerTrack = -1;
_explorerSide = -1;
_explorerUpdatePending = false;
UpdateExplorerData();
}
void OnGuessClockButton(wxCommandEvent& event) override
{
nanoseconds_t clock = histogram->GetMedian();
explorerClockSpinCtrl->SetValue(clock / 1e3);
UpdateExplorerData();
}
void OnQueueEmpty() override
{
SetState(STATE_IDLE);
}
void QueueJob(std::function<void(void)> f)
{
SetState(STATE_WORKING);
JobQueue::QueueJob(f);
}
void UpdateExplorerData()
{
if (!IsQueueEmpty())
{
_explorerUpdatePending = true;
return;
}
_explorerUpdatePending = false;
QueueJob(
[this]()
{
/* You need to call this if the config changes to invalidate
* any caches. */
Environment::reset();
int desiredTrack = explorerTrackSpinCtrl->GetValue();
int desiredSide = explorerSideSpinCtrl->GetValue();
if (!_explorerFluxmap || (desiredTrack != _explorerTrack) ||
(desiredSide != _explorerSide))
{
auto fluxSource = FluxSource::create(config.flux_source());
_explorerFluxmap =
fluxSource->readFlux(desiredTrack, desiredSide)->next();
_explorerTrack = desiredTrack;
_explorerSide = desiredSide;
}
runOnUiThread(
[&]()
{
_state = STATE_IDLE;
UpdateState();
FluxmapReader fmr(*_explorerFluxmap);
fmr.seek(explorerStartTimeSpinCtrl->GetValue() * 1e6);
nanoseconds_t clock =
explorerClockSpinCtrl->GetValue() * 1e3;
FluxDecoder fluxDecoder(&fmr, clock, DecoderProto());
fluxDecoder.readBits(
explorerBitOffsetSpinCtrl->GetValue());
auto bits = fluxDecoder.readBits();
Bytes bytes;
switch (explorerDecodeChoice->GetSelection())
{
case 0:
bytes = fakeBits(bits);
break;
case 1:
bytes = toBytes(bits);
break;
case 2:
bytes = decodeFmMfm(bits.begin(), bits.end());
break;
}
if (explorerReverseCheckBox->GetValue())
bytes = bytes.reverseBits();
std::stringstream s;
hexdump(s, bytes);
explorerText->SetValue(s.str());
histogram->Redraw(*_explorerFluxmap, clock);
if (_explorerUpdatePending)
UpdateExplorerData();
});
});
}
private:
int _state = STATE_DEAD;
int _explorerTrack;
int _explorerSide;
bool _explorerUpdatePending;
std::unique_ptr<const Fluxmap> _explorerFluxmap;
};
ExplorerPanel* ExplorerPanel::Create(
MainWindow* mainWindow, wxSimplebook* parent)
{
return new ExplorerPanelImpl(mainWindow, parent);
}

View File

@@ -4,11 +4,14 @@
#include <wx/wx.h>
class ExecEvent;
class MainWindow;
class DiskFlux;
class TrackFlux;
class wxSimplebook;
extern void postToUiThread(std::function<void()> callback);
extern void runOnUiThread(std::function<void()> callback);
extern void runOnWorkerThread(std::function<void()> callback);
extern bool isWorkerThread();
wxDECLARE_EVENT(UPDATE_STATE_EVENT, wxCommandEvent);
@@ -34,7 +37,8 @@ private:
void OnExec(const ExecEvent& event);
public:
bool IsWorkerThreadRunning() const;
bool IsWorkerThread();
bool IsWorkerThreadRunning();
protected:
virtual wxThread::ExitCode Entry();
@@ -54,4 +58,153 @@ wxDECLARE_APP(FluxEngineApp);
static const wxBrush name##_BRUSH(name##_COLOUR); \
static const wxPen name##_PEN(name##_COLOUR)
class CancelException
{
};
class MainWindow
{
public:
enum
{
PAGE_IDLE,
PAGE_IMAGER,
PAGE_BROWSER,
PAGE_EXPLORER,
};
virtual void StartIdle() = 0;
virtual void StartReading() = 0;
virtual void StartWriting() = 0;
virtual void StartBrowsing() = 0;
virtual void StartFormatting() = 0;
virtual void StartExploring() = 0;
virtual void SafeFit() = 0;
virtual void SetPage(int page) = 0;
virtual void PrepareConfig() = 0;
virtual void ClearLog() = 0;
};
class PanelComponent
{
public:
PanelComponent(MainWindow* mainWindow): _mainWindow(mainWindow) {}
virtual void SwitchTo(){};
virtual void SwitchFrom(){};
void PrepareConfig()
{
_mainWindow->PrepareConfig();
}
void SetPage(int page)
{
_mainWindow->SetPage(page);
}
void ClearLog()
{
_mainWindow->ClearLog();
}
void SafeFit()
{
_mainWindow->SafeFit();
}
void StartIdle()
{
_mainWindow->StartIdle();
}
void StartReading()
{
_mainWindow->StartReading();
}
void StartWriting()
{
_mainWindow->StartWriting();
}
void StartBrowsing()
{
_mainWindow->StartBrowsing();
}
void StartFormatting()
{
_mainWindow->StartFormatting();
}
void StartExploring()
{
_mainWindow->StartExploring();
}
private:
MainWindow* _mainWindow;
};
class IdlePanel : public PanelComponent
{
public:
static IdlePanel* Create(MainWindow* mainWindow, wxSimplebook* parent);
IdlePanel(MainWindow* mainWindow): PanelComponent(mainWindow) {}
public:
virtual void Start() = 0;
virtual void PrepareConfig() = 0;
virtual const wxBitmap GetBitmap() = 0;
};
class ImagerPanel : public PanelComponent
{
public:
static ImagerPanel* Create(MainWindow* mainWindow, wxSimplebook* parent);
ImagerPanel(MainWindow* mainWindow): PanelComponent(mainWindow) {}
public:
enum
{
STATE_READING,
STATE_READING_SUCCEEDED,
STATE_WRITING,
STATE_WRITING_SUCCEEDED,
};
public:
virtual void StartReading() = 0;
virtual void StartWriting() = 0;
virtual void SetVisualiserMode(int head, int track, int mode) = 0;
virtual void SetVisualiserTrackData(
std::shared_ptr<const TrackFlux> track) = 0;
virtual void SetDisk(std::shared_ptr<const DiskFlux> disk) = 0;
};
class BrowserPanel : public PanelComponent
{
public:
static BrowserPanel* Create(MainWindow* mainWindow, wxSimplebook* parent);
BrowserPanel(MainWindow* mainWindow): PanelComponent(mainWindow) {}
public:
virtual void StartBrowsing() = 0;
virtual void StartFormatting() = 0;
};
class ExplorerPanel : public PanelComponent
{
public:
static ExplorerPanel* Create(MainWindow* mainWindow, wxSimplebook* parent);
ExplorerPanel(MainWindow* mainWindow): PanelComponent(mainWindow) {}
public:
virtual void Start() = 0;
};
#endif

View File

@@ -0,0 +1,88 @@
#include "lib/globals.h"
#include "gui.h"
#include "lib/fluxmap.h"
#include "histogramviewer.h"
static constexpr int BORDER = 10;
static constexpr int WIDTH = 256;
static constexpr int HEIGHT = 100;
// clang-format off
wxBEGIN_EVENT_TABLE(HistogramViewer, wxWindow)
EVT_PAINT(HistogramViewer::OnPaint)
wxEND_EVENT_TABLE();
// clang-format on
HistogramViewer::HistogramViewer(wxWindow* parent,
wxWindowID winid,
const wxPoint& pos,
const wxSize& size,
long style):
wxWindow(parent,
winid,
pos,
wxSize(WIDTH + 2 * BORDER, HEIGHT + 2 * BORDER),
style)
{
_font = GetFont().MakeSmaller().MakeSmaller().MakeSmaller();
}
void HistogramViewer::Redraw(const Fluxmap& fluxmap, nanoseconds_t clock)
{
_data = fluxmap.guessClock();
_clock = clock;
_blank = false;
Refresh();
}
void HistogramViewer::OnPaint(wxPaintEvent&)
{
wxPaintDC dc(this);
dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
dc.Clear();
if (_blank)
return;
uint32_t max =
*std::max_element(std::begin(_data.buckets), std::end(_data.buckets));
dc.SetPen(*wxGREY_PEN);
for (int x = 0; x < 256; x++)
{
double v = (double)_data.buckets[x] / (double)max;
dc.DrawLine({BORDER + x, BORDER + HEIGHT},
{BORDER + x, BORDER + HEIGHT - (int)(v * HEIGHT)});
}
dc.SetPen(*wxBLACK_PEN);
dc.DrawLine({BORDER, BORDER + HEIGHT}, {BORDER + WIDTH, BORDER + HEIGHT});
dc.DrawLine({BORDER, BORDER + HEIGHT}, {BORDER, BORDER});
dc.SetPen(*wxRED_PEN);
{
int y = ((double)_data.signalLevel / (double)max) * HEIGHT;
dc.DrawLine({0, BORDER + HEIGHT - y},
{2 * BORDER + WIDTH, BORDER + HEIGHT - y});
}
if (_clock != 0.0)
{
int x = _clock / NS_PER_TICK;
dc.DrawLine({BORDER + x, 2 * BORDER + HEIGHT}, {BORDER + x, 0});
}
{
wxString text = "Clock interval";
dc.SetFont(_font);
auto size = dc.GetTextExtent(text);
dc.DrawText(text, {BORDER + WIDTH - size.GetWidth(), BORDER + HEIGHT});
}
{
wxString text = "Frequency";
dc.SetFont(_font);
auto size = dc.GetTextExtent(text);
dc.DrawRotatedText(
text, BORDER - size.GetHeight(), BORDER + size.GetWidth(), 90);
}
}

34
src/gui/histogramviewer.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "lib/globals.h"
#include "lib/fluxmap.h"
class HistogramViewer : public wxWindow
{
public:
HistogramViewer(wxWindow* parent,
wxWindowID winid,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0);
virtual ~HistogramViewer() {}
public:
void Redraw(const Fluxmap& fluxmap, nanoseconds_t clock=0);
void Redraw(const Fluxmap* fluxmap, nanoseconds_t clock=0)
{ Redraw(*fluxmap, clock); }
nanoseconds_t GetMedian() const { return _data.median; }
private:
void OnPaint(wxPaintEvent&);
private:
bool _blank = true;
Fluxmap::ClockData _data;
wxFont _font;
nanoseconds_t _clock;
wxDECLARE_EVENT_TABLE();
};

View File

File diff suppressed because it is too large Load Diff

55
src/gui/iconbutton.cc Normal file
View File

@@ -0,0 +1,55 @@
#include "lib/globals.h"
#include "gui.h"
#include "iconbutton.h"
#include <fmt/format.h>
IconButton::IconButton(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name):
wxPanel(parent, id, pos, size, style, name)
{
_sizer = new wxFlexGridSizer(1, 0, 0);
SetSizer(_sizer);
_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
_sizer->Add(_bitmap, 0, wxALL | wxEXPAND, 0, nullptr);
_text = new wxStaticText(this,
wxID_ANY,
"",
wxDefaultPosition,
wxDefaultSize,
wxALIGN_CENTRE_HORIZONTAL);
_sizer->Add(_text, 0, wxALL | wxEXPAND, 0, nullptr);
_text->SetFont(_text->GetFont().MakeSmaller().MakeSmaller().MakeSmaller());
Bind(wxEVT_LEFT_DOWN, &IconButton::OnMouseClick, this);
_bitmap->Bind(wxEVT_LEFT_DOWN, &IconButton::OnMouseClick, this);
_text->Bind(wxEVT_LEFT_DOWN, &IconButton::OnMouseClick, this);
}
void IconButton::SetSelected(bool selected)
{
_selected = selected;
wxColor bg = wxSystemSettings::GetColour(
_selected ? wxSYS_COLOUR_HIGHLIGHT : wxSYS_COLOUR_FRAMEBK);
SetBackgroundColour(bg);
Refresh();
}
void IconButton::OnMouseClick(wxMouseEvent&)
{
auto* event = new wxCommandEvent(wxEVT_BUTTON, 0);
wxQueueEvent(this, event);
}
void IconButton::SetBitmapAndLabel(
const wxBitmap bitmap, const std::string text)
{
_bitmap->SetBitmap(bitmap);
_text->SetLabel(text);
}

26
src/gui/iconbutton.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
class wxToggleButton;
class IconButton : public wxPanel
{
public:
IconButton(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL,
const wxString& name = wxPanelNameStr);
void SetBitmapAndLabel(const wxBitmap bitmap, const std::string text);
void SetSelected(bool selected);
private:
void OnMouseClick(wxMouseEvent& e);
private:
wxFlexGridSizer* _sizer;
wxStaticBitmap* _bitmap;
wxStaticText* _text;
bool _selected;
};

700
src/gui/idlepanel.cc Normal file
View File

@@ -0,0 +1,700 @@
#include "lib/globals.h"
#include "gui.h"
#include "lib/fluxmap.h"
#include "lib/usb/usbfinder.h"
#include "lib/proto.h"
#include "lib/flags.h"
#include "lib/utils.h"
#include "lib/fluxsource/fluxsource.h"
#include "lib/fluxsink/fluxsink.h"
#include "lib/imagereader/imagereader.h"
#include "lib/imagewriter/imagewriter.h"
#include "layout.h"
#include "texteditorwindow.h"
#include "iconbutton.h"
#include <wx/config.h>
#include <wx/mstream.h>
#include <wx/image.h>
#include <wx/bitmap.h>
#include ".obj/extras/hardware.h"
#include ".obj/extras/fluxfile.h"
#include ".obj/extras/imagefile.h"
extern const std::map<std::string, std::string> formats;
#define CONFIG_SELECTEDSOURCE "SelectedSource"
#define CONFIG_DEVICE "Device"
#define CONFIG_DRIVE "Drive"
#define CONFIG_FORTYTRACK "FortyTrack"
#define CONFIG_HIGHDENSITY "HighDensity"
#define CONFIG_FORMAT "Format"
#define CONFIG_FORMATOPTIONS "FormatOptions"
#define CONFIG_EXTRACONFIG "ExtraConfig"
#define CONFIG_FLUXIMAGE "FluxImage"
#define CONFIG_DISKIMAGE "DiskImage"
const std::string DEFAULT_EXTRA_CONFIGURATION =
"# Place any extra configuration here.\n"
"# Each line can contain a key=value pair to set a property,\n"
"# or the name of a built-in configuration, or the filename\n"
"# of a text proto file. Or a comment, of course.\n\n";
wxDEFINE_EVENT(PAGE_SELECTED_EVENT, wxCommandEvent);
static wxBitmap createBitmap(const uint8_t* data, size_t length)
{
wxMemoryInputStream stream(data, length);
wxImage image(stream, wxBITMAP_TYPE_PNG);
return wxBitmap(image);
}
class IdlePanelImpl : public IdlePanelGen, public IdlePanel
{
enum
{
SELECTEDSOURCE_REAL,
SELECTEDSOURCE_FLUX,
SELECTEDSOURCE_IMAGE
};
enum
{
ICON_HARDWARE,
ICON_FLUXFILE,
ICON_IMAGEFILE
};
public:
IdlePanelImpl(MainWindow* mainWindow, wxSimplebook* parent):
IdlePanelGen(parent),
IdlePanel(mainWindow),
_imageList(48, 48, true, 0),
_config("FluxEngine")
{
int defaultFormat = 0;
int i = 0;
for (const auto& it : formats)
{
auto config = std::make_unique<ConfigProto>();
if (!config->ParseFromString(it.second))
continue;
if (config->is_extension())
continue;
formatChoice->Append(it.first);
_formatNames.push_back(it.first);
i++;
}
LoadConfig();
UpdateDevices();
UpdateFormatOptions();
parent->AddPage(this, "idle");
_imageList.Add(
createBitmap(extras_hardware_png, sizeof(extras_hardware_png)));
_imageList.Add(
createBitmap(extras_fluxfile_png, sizeof(extras_fluxfile_png)));
_imageList.Add(
createBitmap(extras_imagefile_png, sizeof(extras_imagefile_png)));
UpdateSources();
}
public:
void Start() override
{
SetPage(MainWindow::PAGE_IDLE);
}
void UpdateState()
{
bool hasFormat = formatChoice->GetSelection() != wxNOT_FOUND;
readButton->Enable(
(_selectedSource != SELECTEDSOURCE_IMAGE) && hasFormat);
writeButton->Enable(
(_selectedSource == SELECTEDSOURCE_REAL) && hasFormat);
browseButton->Enable(hasFormat);
formatButton->Enable(hasFormat);
exploreButton->Enable(_selectedSource != SELECTEDSOURCE_IMAGE);
UpdateFormatOptions();
}
public:
void OnReadButton(wxCommandEvent&) override
{
StartReading();
}
void OnWriteButton(wxCommandEvent&) override
{
StartWriting();
}
void OnBrowseButton(wxCommandEvent&) override
{
StartBrowsing();
}
void OnFormatButton(wxCommandEvent&) override
{
StartFormatting();
}
void OnExploreButton(wxCommandEvent&) override
{
StartExploring();
}
void OnControlsChanged(wxCommandEvent& event) override
{
SaveConfig();
UpdateState();
UpdateFormatOptions();
}
void OnControlsChanged(wxFileDirPickerEvent& event)
{
wxCommandEvent e;
OnControlsChanged(e);
}
void OnCustomConfigurationButton(wxCommandEvent& event) override
{
auto* editor = TextEditorWindow::Create(
this, "Configuration editor", _extraConfiguration);
editor->Bind(EDITOR_SAVE_EVENT,
[this](auto& event)
{
_extraConfiguration = event.text;
SaveConfig();
});
editor->Show();
}
void PrepareConfig() override
{
assert(!wxGetApp().IsWorkerThreadRunning());
auto formatSelection = formatChoice->GetSelection();
if (formatSelection == wxNOT_FOUND)
Error() << "no format selected";
config.Clear();
auto formatName = _formatNames[formatChoice->GetSelection()];
FlagGroup::parseConfigFile(formatName, formats);
/* Apply any format options. */
for (const auto& e : _formatOptions)
{
if (e.first == formatName)
FlagGroup::applyOption(e.second);
}
/* Merge in any custom config. */
for (auto setting : split(_extraConfiguration, '\n'))
{
setting = trimWhitespace(setting);
if (setting.size() == 0)
continue;
if (setting[0] == '#')
continue;
auto equals = setting.find('=');
if (equals != std::string::npos)
{
auto key = setting.substr(0, equals);
auto value = setting.substr(equals + 1);
setProtoByString(&config, key, value);
}
else
FlagGroup::parseConfigFile(setting, formats);
}
/* Locate the device, if any. */
auto serial = _selectedDevice;
if (!serial.empty() && (serial[0] == '/'))
setProtoByString(&config, "usb.greaseweazle.port", serial);
else
setProtoByString(&config, "usb.serial", serial);
ClearLog();
/* Apply the source/destination. */
switch (_selectedSource)
{
case SELECTEDSOURCE_REAL:
{
config.mutable_drive()->set_high_density(_selectedHighDensity);
if (_selectedFortyTrack)
FlagGroup::parseConfigFile("40track_drive", formats);
std::string filename = _selectedDrive ? "drive:1" : "drive:0";
FluxSink::updateConfigForFilename(
config.mutable_flux_sink(), filename);
FluxSource::updateConfigForFilename(
config.mutable_flux_source(), filename);
break;
}
case SELECTEDSOURCE_FLUX:
{
FluxSink::updateConfigForFilename(
config.mutable_flux_sink(), _selectedFluxfilename);
FluxSource::updateConfigForFilename(
config.mutable_flux_source(), _selectedFluxfilename);
break;
}
case SELECTEDSOURCE_IMAGE:
{
ImageReader::updateConfigForFilename(
config.mutable_image_reader(), _selectedImagefilename);
ImageWriter::updateConfigForFilename(
config.mutable_image_writer(), _selectedImagefilename);
break;
}
}
}
const wxBitmap GetBitmap() override
{
return applicationBitmap->GetBitmap();
}
private:
void LoadConfig()
{
/* Prevent saving the config half-way through reloading it when the
* widget states all change. */
_dontSaveConfig = true;
/* Radio button config. */
wxString s = std::to_string(SELECTEDSOURCE_IMAGE);
_config.Read(CONFIG_SELECTEDSOURCE, &s);
_selectedSource = std::atoi(s.c_str());
/* Real disk block. */
s = "";
_config.Read(CONFIG_DEVICE, &s);
_selectedDevice = s;
s = "0";
_config.Read(CONFIG_DRIVE, &s);
_selectedDrive = wxAtoi(s);
s = "0";
_config.Read(CONFIG_HIGHDENSITY, &s);
_selectedHighDensity = wxAtoi(s);
s = "0";
_config.Read(CONFIG_FORTYTRACK, &s);
_selectedFortyTrack = wxAtoi(s);
/* Flux image block. */
s = "";
_config.Read(CONFIG_FLUXIMAGE, &s);
_selectedFluxfilename = s;
/* Disk image block. */
s = "";
_config.Read(CONFIG_DISKIMAGE, &s);
_selectedImagefilename = s;
/* Format block. */
s = "ibm";
_config.Read(CONFIG_FORMAT, &s);
int defaultFormat = 0;
int i = 0;
for (const auto& it : _formatNames)
{
if (it == s)
{
formatChoice->SetSelection(i);
break;
}
i++;
}
s = DEFAULT_EXTRA_CONFIGURATION;
_config.Read(CONFIG_EXTRACONFIG, &s);
_extraConfiguration = s;
/* Format options. */
_formatOptions.clear();
s = "";
_config.Read(CONFIG_FORMATOPTIONS, &s);
for (auto combined : split(std::string(s), ','))
{
auto pair = split(combined, ':');
try
{
auto key = std::make_pair(pair.at(0), pair.at(1));
_formatOptions.insert(key);
}
catch (std::exception&)
{
}
}
/* Triggers SaveConfig */
_dontSaveConfig = false;
}
void SaveConfig()
{
if (_dontSaveConfig)
return;
_config.Write(
CONFIG_SELECTEDSOURCE, wxString(std::to_string(_selectedSource)));
/* Real disk block. */
_config.Write(CONFIG_DEVICE, wxString(_selectedDevice));
_config.Write(CONFIG_DRIVE, wxString(std::to_string(_selectedDrive)));
_config.Write(
CONFIG_HIGHDENSITY, wxString(std::to_string(_selectedHighDensity)));
_config.Write(
CONFIG_FORTYTRACK, wxString(std::to_string(_selectedFortyTrack)));
/* Flux image block. */
_config.Write(CONFIG_FLUXIMAGE, wxString(_selectedFluxfilename));
/* Disk image block. */
_config.Write(CONFIG_DISKIMAGE, wxString(_selectedImagefilename));
/* Format block. */
_config.Write(CONFIG_FORMAT,
formatChoice->GetString(formatChoice->GetSelection()));
_config.Write(CONFIG_EXTRACONFIG, wxString(_extraConfiguration));
/* Format options. */
{
std::vector<std::string> options;
for (auto& e : _formatOptions)
options.push_back(fmt::format("{}:{}", e.first, e.second));
_config.Write(CONFIG_FORMATOPTIONS, wxString(join(options, ",")));
}
}
void UpdateSources()
{
sourceBook->DeleteAllPages();
sourceIconPanel->DestroyChildren();
int page = 0;
bool switched = false;
for (auto& device : _devices)
{
for (int drive = 0; drive <= 1; drive++)
{
auto* panel = new HardwareSourcePanelGen(sourceBook);
sourceBook->AddPage(panel, "");
panel->label->SetLabel(
fmt::format("{}; serial number {}; drive:{}",
getDeviceName(device->type),
device->serial,
drive));
auto* button = AddIcon(ICON_HARDWARE,
fmt::format(
"{}\ndrive:{}", device->serial.substr(0, 10), drive));
button->Bind(wxEVT_BUTTON,
[=](auto& e)
{
SwitchToPage(page);
_selectedSource = SELECTEDSOURCE_REAL;
_selectedDevice = device->serial;
_selectedDrive = drive;
OnControlsChanged(e);
});
panel->highDensityToggle->SetValue(_selectedHighDensity);
panel->highDensityToggle->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,
[=](wxCommandEvent& e)
{
_selectedHighDensity =
panel->highDensityToggle->GetValue();
OnControlsChanged(e);
});
panel->fortyTrackDriveToggle->SetValue(_selectedFortyTrack);
panel->fortyTrackDriveToggle->Bind(
wxEVT_COMMAND_CHECKBOX_CLICKED,
[=](wxCommandEvent& e)
{
_selectedFortyTrack =
panel->fortyTrackDriveToggle->GetValue();
OnControlsChanged(e);
});
if ((_selectedSource == SELECTEDSOURCE_REAL) &&
(_selectedDevice == device->serial) &&
(_selectedDrive == drive))
{
SwitchToPage(page);
switched = true;
}
page++;
}
}
{
auto* panel = new FluxfileSourcePanelGen(sourceBook);
sourceBook->AddPage(panel, "");
auto* button = AddIcon(ICON_FLUXFILE, "Flux file");
button->Bind(wxEVT_BUTTON,
[=](auto& e)
{
SwitchToPage(page);
_selectedSource = SELECTEDSOURCE_FLUX;
OnControlsChanged(e);
});
panel->fluxImagePicker->SetPath(_selectedFluxfilename);
panel->fluxImagePicker->Bind(wxEVT_COMMAND_FILEPICKER_CHANGED,
[=](wxFileDirPickerEvent& e)
{
_selectedFluxfilename = e.GetPath();
OnControlsChanged(e);
});
if (_selectedSource == SELECTEDSOURCE_FLUX)
{
SwitchToPage(page);
switched = true;
}
page++;
}
{
auto* panel = new ImagefileSourcePanelGen(sourceBook);
sourceBook->AddPage(panel, "");
auto* button = AddIcon(ICON_IMAGEFILE, "Disk image");
button->Bind(wxEVT_BUTTON,
[=](auto& e)
{
SwitchToPage(page);
_selectedSource = SELECTEDSOURCE_IMAGE;
OnControlsChanged(e);
});
panel->diskImagePicker->SetPath(_selectedImagefilename);
panel->diskImagePicker->Bind(wxEVT_COMMAND_FILEPICKER_CHANGED,
[=](wxFileDirPickerEvent& e)
{
_selectedImagefilename = e.GetPath();
OnControlsChanged(e);
});
if (_selectedSource == SELECTEDSOURCE_IMAGE)
{
SwitchToPage(page);
switched = true;
}
page++;
}
Fit();
Layout();
if (!switched)
SwitchToPage(0);
}
IconButton* AddIcon(int bitmapIndex, const std::string text)
{
auto* button = new IconButton(sourceIconPanel, wxID_ANY);
button->SetBitmapAndLabel(_imageList.GetBitmap(bitmapIndex), text);
sourceIconPanel->GetSizer()->Add(
button, 0, wxALL | wxEXPAND, 5, nullptr);
return button;
}
void SwitchToPage(int page)
{
int i = 0;
for (auto* window : sourceIconPanel->GetChildren())
{
IconButton* button = dynamic_cast<IconButton*>(window);
if (button)
button->SetSelected(i == page);
i++;
}
sourceBook->ChangeSelection(page);
}
void UpdateFormatOptions()
{
assert(!wxGetApp().IsWorkerThreadRunning());
int formatSelection = formatChoice->GetSelection();
if (formatSelection != _currentlyDisplayedFormat)
{
_currentlyDisplayedFormat = formatSelection;
formatOptionsContainer->DestroyChildren();
auto* sizer = new wxBoxSizer(wxVERTICAL);
if (formatSelection == wxNOT_FOUND)
sizer->Add(new wxStaticText(
formatOptionsContainer, wxID_ANY, "(no format selected)"));
else
{
config.Clear();
std::string formatName =
_formatNames[formatChoice->GetSelection()];
FlagGroup::parseConfigFile(formatName, formats);
std::set<std::string> exclusivityGroups;
for (auto& option : config.option())
{
if (option.has_exclusivity_group())
exclusivityGroups.insert(option.exclusivity_group());
}
if (config.option().empty())
sizer->Add(new wxStaticText(formatOptionsContainer,
wxID_ANY,
"(no options for this format)"));
else
{
/* Add grouped radiobuttons for anything in an exclusivity
* group. */
for (auto& group : exclusivityGroups)
{
bool first = true;
for (auto& option : config.option())
{
if (option.exclusivity_group() != group)
continue;
auto* rb = new wxRadioButton(formatOptionsContainer,
wxID_ANY,
option.comment());
auto key =
std::make_pair(formatName, option.name());
sizer->Add(rb);
rb->Bind(wxEVT_RADIOBUTTON,
[=](wxCommandEvent& e)
{
for (auto& option : config.option())
{
if (option.exclusivity_group() == group)
_formatOptions.erase(std::make_pair(
formatName, option.name()));
}
_formatOptions.insert(key);
OnControlsChanged(e);
});
if (_formatOptions.find(key) !=
_formatOptions.end())
rb->SetValue(true);
if (first)
rb->SetExtraStyle(wxRB_GROUP);
first = false;
}
}
/* Anything that's _not_ in a group gets a checkbox. */
for (auto& option : config.option())
{
if (option.has_exclusivity_group())
continue;
auto* choice = new wxCheckBox(
formatOptionsContainer, wxID_ANY, option.comment());
auto key = std::make_pair(formatName, option.name());
sizer->Add(choice);
if (_formatOptions.find(key) != _formatOptions.end())
choice->SetValue(true);
choice->Bind(wxEVT_CHECKBOX,
[=](wxCommandEvent& e)
{
if (choice->GetValue())
_formatOptions.insert(key);
else
_formatOptions.erase(key);
OnControlsChanged(e);
});
}
}
}
formatOptionsContainer->SetSizerAndFit(sizer);
Layout();
SafeFit();
}
}
void UpdateDevices()
{
auto candidates = findUsbDevices();
for (auto& candidate : candidates)
_devices.push_back(candidate);
}
private:
wxConfig _config;
wxImageList _imageList;
ConfigProto _configProto;
std::vector<std::string> _formatNames;
std::vector<std::shared_ptr<const CandidateDevice>> _devices;
int _selectedSource;
std::string _selectedDevice;
int _selectedDrive;
bool _selectedFortyTrack;
bool _selectedHighDensity;
std::string _selectedFluxfilename;
std::string _selectedImagefilename;
bool _dontSaveConfig = false;
std::string _extraConfiguration;
std::set<std::pair<std::string, std::string>> _formatOptions;
int _currentlyDisplayedFormat = wxNOT_FOUND - 1;
};
IdlePanel* IdlePanel::Create(MainWindow* mainWindow, wxSimplebook* parent)
{
return new IdlePanelImpl(mainWindow, parent);
}

327
src/gui/imagerpanel.cc Normal file
View File

@@ -0,0 +1,327 @@
#include "lib/globals.h"
#include "lib/fluxmap.h"
#include "lib/environment.h"
#include "lib/fluxsource/fluxsource.h"
#include "lib/fluxsink/fluxsink.h"
#include "lib/imagereader/imagereader.h"
#include "lib/imagewriter/imagewriter.h"
#include "lib/encoders/encoders.h"
#include "lib/decoders/decoders.h"
#include "lib/proto.h"
#include "lib/readerwriter.h"
#include "gui.h"
#include "layout.h"
#include "fluxviewerwindow.h"
#include "jobqueue.h"
class ImagerPanelImpl : public ImagerPanelGen, public ImagerPanel, JobQueue
{
private:
enum
{
STATE_DEAD,
STATE_READING_WORKING,
STATE_READING_SUCCEEDED,
STATE_READING_FAILED,
STATE_WRITING_WORKING,
STATE_WRITING_SUCCEEDED,
STATE_WRITING_FAILED,
};
public:
ImagerPanelImpl(MainWindow* mainWindow, wxSimplebook* parent):
ImagerPanelGen(parent),
ImagerPanel(mainWindow)
{
visualiser->Bind(
TRACK_SELECTION_EVENT, &ImagerPanelImpl::OnTrackSelection, this);
parent->AddPage(this, "imager");
}
void OnBackButton(wxCommandEvent&) override
{
StartIdle();
}
private:
void SetState(int state)
{
if (state != _state)
{
_state = state;
CallAfter(
[&]()
{
UpdateState();
});
}
}
void SwitchFrom() override
{
SetState(STATE_DEAD);
}
void OnQueueEmpty() override
{
if (_state == STATE_READING_WORKING)
_state = STATE_READING_SUCCEEDED;
else if (_state == STATE_WRITING_WORKING)
_state = STATE_WRITING_SUCCEEDED;
UpdateState();
}
void OnQueueFailed() override
{
fmt::print("queue failed\n");
if (_state == STATE_READING_WORKING)
_state = STATE_READING_FAILED;
else if (_state == STATE_WRITING_WORKING)
_state = STATE_WRITING_FAILED;
UpdateState();
}
public:
void StartReading()
{
try
{
SetPage(MainWindow::PAGE_IMAGER);
PrepareConfig();
visualiser->Clear();
_currentDisk = nullptr;
SetState(STATE_READING_WORKING);
QueueJob(
[this]()
{
/* You need to call this if the config changes to invalidate
* any caches. */
Environment::reset();
auto fluxSource = FluxSource::create(config.flux_source());
auto decoder = Decoder::create(config.decoder());
auto diskflux = readDiskCommand(*fluxSource, *decoder);
runOnUiThread(
[&]()
{
visualiser->SetDiskData(diskflux);
});
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
if (_state == STATE_READING_WORKING)
SetState(STATE_READING_FAILED);
}
}
void StartWriting() override
{
try
{
SetPage(MainWindow::PAGE_IMAGER);
PrepareConfig();
if (!config.has_image_reader())
Error() << "This format cannot be read from images.";
auto filename = wxFileSelector("Choose a image file to read",
/* default_path= */ wxEmptyString,
/* default_filename= */ config.image_reader().filename(),
/* default_extension= */ wxEmptyString,
/* wildcard= */ wxEmptyString,
/* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (filename.empty())
return;
ImageReader::updateConfigForFilename(
config.mutable_image_reader(), filename.ToStdString());
ImageWriter::updateConfigForFilename(
config.mutable_image_writer(), filename.ToStdString());
visualiser->Clear();
_currentDisk = nullptr;
SetState(STATE_WRITING_WORKING);
QueueJob(
[this]()
{
auto image = ImageReader::create(config.image_reader())
->readMappedImage();
auto encoder = Encoder::create(config.encoder());
auto fluxSink = FluxSink::create(config.flux_sink());
std::unique_ptr<Decoder> decoder;
std::unique_ptr<FluxSource> fluxSource;
if (config.has_decoder())
{
decoder = Decoder::create(config.decoder());
fluxSource = FluxSource::create(config.flux_source());
}
writeDiskCommand(*image,
*encoder,
*fluxSink,
decoder.get(),
fluxSource.get());
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
if (_state == STATE_WRITING_WORKING)
SetState(STATE_WRITING_FAILED);
}
}
void UpdateState()
{
switch (_state)
{
case STATE_READING_WORKING:
case STATE_READING_SUCCEEDED:
case STATE_READING_FAILED:
imagerSaveImageButton->Enable(
_state == STATE_READING_SUCCEEDED);
imagerSaveFluxButton->Enable(_state == STATE_READING_SUCCEEDED);
imagerGoAgainButton->Enable(_state != STATE_READING_WORKING);
imagerToolbar->EnableTool(
imagerBackTool->GetId(), _state != STATE_READING_WORKING);
break;
case STATE_WRITING_WORKING:
case STATE_WRITING_SUCCEEDED:
case STATE_WRITING_FAILED:
imagerSaveImageButton->Enable(false);
imagerSaveFluxButton->Enable(false);
imagerGoAgainButton->Enable(_state != STATE_WRITING_WORKING);
imagerToolbar->EnableTool(
imagerBackTool->GetId(), _state != STATE_WRITING_WORKING);
break;
}
imagerToolbar->Refresh();
}
public:
void SetVisualiserMode(int head, int track, int mode) override
{
visualiser->SetMode(head, track, mode);
}
void SetVisualiserTrackData(
std::shared_ptr<const TrackFlux> trackdata) override
{
visualiser->SetTrackData(trackdata);
if (!trackdata->trackDatas.empty())
histogram->Redraw(*(*trackdata->trackDatas.begin())->fluxmap, 0);
}
void SetDisk(std::shared_ptr<const DiskFlux> diskdata) override
{
_currentDisk = diskdata;
}
void OnImagerGoAgainButton(wxCommandEvent& event) override
{
switch (_state)
{
case STATE_READING_SUCCEEDED:
case STATE_READING_FAILED:
StartReading();
break;
case STATE_WRITING_SUCCEEDED:
case STATE_WRITING_FAILED:
StartWriting();
break;
}
}
void OnSaveImageButton(wxCommandEvent&) override
{
try
{
if (!config.has_image_writer())
Error() << "This format cannot be saved.";
auto filename =
wxFileSelector("Choose the name of the image file to write",
/* default_path= */ wxEmptyString,
/* default_filename= */ config.image_writer().filename(),
/* default_extension= */ wxEmptyString,
/* wildcard= */ wxEmptyString,
/* flags= */ wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (filename.empty())
return;
ImageWriter::updateConfigForFilename(
config.mutable_image_writer(), filename.ToStdString());
auto image = _currentDisk->image;
QueueJob(
[image, this]()
{
auto imageWriter =
ImageWriter::create(config.image_writer());
imageWriter->writeMappedImage(*image);
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
}
}
void OnSaveFluxButton(wxCommandEvent&) override
{
try
{
auto filename =
wxFileSelector("Choose the name of the flux file to write",
/* default_path= */ wxEmptyString,
/* default_filename= */ "image.flux",
/* default_extension= */ wxEmptyString,
/* wildcard= */ wxEmptyString,
/* flags= */ wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (filename.empty())
return;
FluxSink::updateConfigForFilename(
config.mutable_flux_sink(), filename.ToStdString());
QueueJob(
[this]()
{
auto fluxSource =
FluxSource::createMemoryFluxSource(*_currentDisk);
auto fluxSink = FluxSink::create(config.flux_sink());
writeRawDiskCommand(*fluxSource, *fluxSink);
});
}
catch (const ErrorException& e)
{
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
}
}
void OnTrackSelection(TrackSelectionEvent& event)
{
(new FluxViewerWindow(this, event.trackFlux))->Show(true);
}
private:
int _state = STATE_DEAD;
std::shared_ptr<const DiskFlux> _currentDisk;
};
ImagerPanel* ImagerPanel::Create(MainWindow* mainWindow, wxSimplebook* parent)
{
return new ImagerPanelImpl(mainWindow, parent);
}

Some files were not shown because too many files have changed in this diff Show More