Compare commits

...

166 Commits
trs80 ... d88

Author SHA1 Message Date
dg
acf32c2a5c Tidy. 2023-04-06 09:33:30 +00:00
dg
41460457a3 When overriding the layout, make sure that the previous layout is erased. 2023-04-05 21:11:34 +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
dg
d8528d889a Add another missing file. 2022-11-29 20:55:45 +00:00
dg
ec439a5f2a Add file which has been missing since forever... 2022-11-29 20:55:07 +00:00
David Given
d88110488e Merge pull request #624 from davidgiven/flx
Add support for reading FLUXCOPY .flx files.
2022-11-29 21:53:13 +01:00
dg
2f4b15293a Adjust the FLX tick rate to something more resembling normality. 2022-11-29 20:26:58 +00:00
dg
3d71f32587 Update documentation. 2022-11-29 20:18:21 +00:00
dg
5136dda598 Made the FLX reader work. 2022-11-29 20:17:24 +00:00
dg
d26940304b Boilerplate for a FLX file reader. 2022-11-29 19:33:47 +00:00
David Given
f9f11b4966 Merge pull request #623 from davidgiven/smaky
Improve the flux viewer.
2022-11-28 21:42:23 +01:00
dg
1297b568ba Display the raw flux view in bytes as well as bits. 2022-11-28 20:22:34 +00:00
dg
fd10840cc0 Display raw bits in the flux viewer. 2022-11-28 20:15:57 +00:00
David Given
7e86be979d Merge pull request #622 from davidgiven/smaky
Add basic filesystem support for the Smaky 6.
2022-11-28 00:00:48 +01:00
dg
731d1efcc4 Add missing file. 2022-11-27 22:38:44 +00:00
dg
dfda8be30c Support tabs in the file viewer text view. 2022-11-27 22:24:30 +00:00
dg
7dd1b6d8e9 Add Smaky 6 filesystem documentation. 2022-11-27 22:23:55 +00:00
dg
ec1bcdb9e5 Add a simple Smaky 6 filesystem backend. 2022-11-27 22:22:59 +00:00
dg
c0f46d2bd4 Create new branch named "smaky" 2022-11-27 22:21:41 +00:00
David Given
615dca3130 Merge pull request #621 from davidgiven/osx
Add a proper OSX GUI application.
2022-11-27 17:27:04 +01:00
David Given
9cf9597c54 Try and make an OSX release. 2022-11-27 17:26:43 +01:00
David Given
e24ee648e7 Add license texts for the included libraries. 2022-11-27 17:06:28 +01:00
David Given
e3518dc389 Upload the OSX package. 2022-11-27 16:59:45 +01:00
David Given
693ba20606 Add rules to build the OSX package. 2022-11-27 16:58:50 +01:00
dg
b947c6c186 Add the skeleton of an OSX application. 2022-11-27 15:00:31 +00:00
David Given
7f8ecb8514 Fix broken conditional due to typo. 2022-11-27 15:18:07 +01:00
David Given
4df6afa9c1 Merge pull request #620 from davidgiven/b593
Correct generation of SCP files.
2022-11-27 13:28:02 +01:00
dg
d0620f8efe Fix readTracks to use locations. 2022-11-27 11:55:25 +00:00
dg
2b1a6dbb03 Make the scp flux sink use the layout to determine which tracks and sectors are
being written rather than the range flags directly.
2022-11-27 11:52:16 +00:00
David Given
c6ef667c3f Merge pull request #619 from davidgiven/b602
Fix weird error when trying to create fatfs filesystems.
2022-11-27 12:33:33 +01:00
dg
8c1d1bec93 Create new branch named "b593" 2022-11-27 11:20:30 +00:00
dg
8be174e65a Fix incorrect types when calling fatfs which was leading to really weird
behaviour when optimisation was enabled.
2022-11-27 11:11:26 +00:00
dg
6d37fafb02 Add a helper function for checking if a file exists. 2022-11-27 11:10:58 +00:00
dg
46ce882daa Allow implicit creation of disk images when writing. 2022-11-27 11:09:46 +00:00
David Given
6d14cbdb9b Merge pull request #618 from davidgiven/b609
Fix --doc to not hang.
2022-11-27 11:57:38 +01:00
dg
4bf6b433ae Create new branch named "b602" 2022-11-27 10:26:11 +00:00
dg
87a1b2e6f8 Don't hang when calling findAllProtoFields on recursive protos. 2022-11-27 10:24:40 +00:00
dg
c6ee48ec85 Create new branch named "b609" 2022-11-27 10:04:42 +00:00
David Given
b58a6b1649 Merge pull request #617 from davidgiven/smaky
Add support for the Smaky 6.
2022-11-27 01:00:21 +01:00
dg
bd9736df93 Typo fix. 2022-11-26 23:59:50 +00:00
dg
3b9c966e3d Add Smaky documentation. 2022-11-26 23:34:20 +00:00
dg
96c9a89171 Typo fixes. 2022-11-26 23:28:23 +00:00
dg
c374ffd15e Increase the smaky disk signature to 32 bits. 2022-11-26 22:56:45 +00:00
dg
c53109e1a1 Add the disk explorer. 2022-11-26 22:56:24 +00:00
dg
4598b3a7a6 Fix line endings. 2022-11-26 11:19:05 +00:00
dg
cf975b74bf Rename smaky to smaky6. 2022-11-26 11:18:38 +00:00
dg
5d65dcf3c8 Even better Smaky reads, and maybe with sector 0 in the right place. 2022-11-25 21:28:59 +00:00
dg
f299ec1f8d More Smaky improvements. 2022-11-25 20:23:24 +00:00
dg
6677034774 Improved record view. 2022-11-25 20:22:52 +00:00
dg
3c7c4639a9 Massively improved the quality of Smaky reads. 2022-11-25 19:33:29 +00:00
dg
7e9a1268a5 Add an extremely prototype version of the Smaky decoder. 2022-11-23 21:44:40 +00:00
dg
a60b8e68ca Add a Bytes method for reversing each bit. 2022-11-23 21:44:10 +00:00
dg
b2161aa67e Create new branch named "smaky" 2022-11-22 19:09:54 +00:00
David Given
d1fffb1d08 Merge pull request #616 from davidgiven/gui
Fix a crash when using the GUI.
2022-11-21 20:37:52 +01:00
dg
52d66d9555 Only iterate the environment if the set has been created. 2022-11-21 19:10:37 +00:00
David Given
66f2d359e2 Merge pull request #615 from davidgiven/vfs
Don't define a global structure called Entry
2022-11-21 20:07:51 +01:00
dg
8327f33ee6 Create new branch named "gui" 2022-11-21 18:37:47 +00:00
dg
d4a94551d9 Don't define a global structure called Entry because it conflicts with
something in adflib.
2022-11-21 18:31:59 +00:00
dg
d2a545d83e Merge from master. 2022-11-21 17:39:39 +00:00
David Given
aee4eac271 Merge pull request #613 from davidgiven/proto
Do some long needed proto cleanup
2022-11-21 00:13:32 +01:00
David Given
cbeddb11bc Fix one last use of has_ to identify a flux writer type. 2022-11-20 23:37:04 +01:00
David Given
4c51cf8316 Update documentation. 2022-11-20 23:36:47 +01:00
David Given
345cd6bd92 Convert the VFS protos to use enums rather than oneofs. 2022-11-20 10:37:45 +01:00
David Given
48540245b5 Convert the imagereader/writer to use proto enums rather than oneofs. 2022-11-20 10:25:19 +01:00
David Given
088bd9434d Switch from using a oneof to an explicit enum for the flux source/sink
configurations, as this allows default options for multiple source/sink types.
2022-11-19 22:52:52 +01:00
David Given
8ba8c58377 Add a config for the ICL Model 30. 2022-11-19 20:10:39 +01:00
David Given
4ae43d0503 Merge pull request #607 from tdaede/fix_layout_cache
Remove the layout cache and rename layout -> trackInfo.
2022-10-13 23:01:44 +02:00
Thomas Daede
d20ce3dde7 Remove the layout cache and rename layout -> trackInfo.
The layout cache is busted and seems not worth the effort.

Fixes #603.
2022-10-02 09:43:48 -07:00
189 changed files with 12599 additions and 8121 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,39 @@ 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
with:
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
@@ -44,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: |
@@ -56,10 +80,10 @@ 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
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: fluxengine.zip
path: fluxengine-windows.zip

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: |
@@ -77,3 +78,42 @@ jobs:
tag_name: dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: brew
run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
- name: make
run: gmake
- name: tag
uses: EndBug/latest-tag@latest
with:
tag-name: dev
force-branch: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: delete-old-assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ github.token }}
tag: dev
assets: |
FluxEngine.pkg
fail-if-no-assets: false
- name: release
uses: softprops/action-gh-release@v1
with:
name: Development build ${{ env.RELEASE_DATE }}
files: |
FluxEngine.pkg
tag_name: dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -111,6 +111,7 @@ PROTOS = \
arch/micropolis/micropolis.proto \
arch/mx/mx.proto \
arch/northstar/northstar.proto \
arch/smaky6/smaky6.proto \
arch/tids990/tids990.proto \
arch/victor9k/victor9k.proto \
arch/zilogmcz/zilogmcz.proto \
@@ -159,16 +160,23 @@ include tests/build.mk
do-encodedecodetest = $(eval $(do-encodedecodetest-impl))
define do-encodedecodetest-impl
tests: $(OBJDIR)/$1$3.encodedecode
$(OBJDIR)/$1$3.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
tests: $(OBJDIR)/$1$3.flux.encodedecode
$(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2
@mkdir -p $(dir $$@)
@echo ENCODEDECODETEST $1 $3
@echo ENCODEDECODETEST $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 $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,9 @@ 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

@@ -13,16 +13,6 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR);
/* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */
static Bytes reverse_bits(const Bytes& input)
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : input)
bw.write_8(reverse_bits(b));
return output;
}
class AesLanierDecoder : public Decoder
{
public:
@@ -43,7 +33,7 @@ public:
const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
const auto& reversed = reverse_bits(bytes);
const auto& reversed = bytes.reverseBits();
_sector->logicalTrack = reversed[1];
_sector->logicalSide = 0;

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

@@ -1,5 +1,8 @@
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 \
@@ -16,18 +19,17 @@ LIBARCH_SRCS = \
arch/ibm/encoder.cc \
arch/macintosh/decoder.cc \
arch/macintosh/encoder.cc \
arch/micropolis/decoder.cc \
arch/micropolis/encoder.cc \
arch/mx/decoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/smaky6/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
arch/victor9k/decoder.cc \
arch/victor9k/encoder.cc \
arch/zilogmcz/decoder.cc \
arch/tids990/decoder.cc \
arch/tids990/encoder.cc \
arch/micropolis/decoder.cc \
arch/micropolis/encoder.cc \
arch/northstar/decoder.cc \
arch/northstar/encoder.cc \
arch/agat/agat.cc \
arch/agat/decoder.cc \
LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS))
OBJS += $(LIBARCH_OBJS)

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)

154
arch/smaky6/decoder.cc Normal file
View File

@@ -0,0 +1,154 @@
#include "globals.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "protocol.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "smaky6.h"
#include "bytes.h"
#include "crc.h"
#include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
#include <string.h>
#include <algorithm>
static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa);
class Smaky6Decoder : public Decoder
{
public:
Smaky6Decoder(const DecoderProto& config):
Decoder(config),
_config(config.smaky6())
{
}
private:
/* Returns the sector ID of the _current_ sector. */
int advanceToNextSector()
{
auto previous = tell();
seekToIndexMark();
auto now = tell();
if ((now.ns() - previous.ns()) < 9e6)
{
seekToIndexMark();
auto next = tell();
if ((next.ns() - now.ns()) < 9e6)
{
/* We just found sector 0. */
_sectorId = 0;
}
else
{
/* Spurious... */
seek(now);
}
}
return _sectorId++;
}
public:
void beginTrack() override
{
/* Find the start-of-track index marks, which will be an interval
* of about 6ms. */
seekToIndexMark();
_sectorId = 99;
for (;;)
{
auto pos = tell();
advanceToNextSector();
if (_sectorId < 99)
{
seek(pos);
break;
}
if (eof())
return;
}
/* Now we know where to start counting, start finding sectors. */
_sectorStarts.clear();
for (;;)
{
auto now = tell();
if (eof())
break;
int id = advanceToNextSector();
if (id < 16)
_sectorStarts.push_back(std::make_pair(id, now));
}
_sectorIndex = 0;
}
nanoseconds_t advanceToNextRecord() override
{
if (_sectorIndex == _sectorStarts.size())
{
seekToIndexMark();
return 0;
}
const auto& p = _sectorStarts[_sectorIndex++];
_sectorId = p.first;
seek(p.second);
nanoseconds_t clock = seekToPattern(SECTOR_PATTERN);
_sector->headerStartTime = tell().ns();
return clock;
}
void decodeSectorRecord() override
{
readRawBits(33);
const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16);
if (rawbits.size() < SMAKY6_SECTOR_SIZE)
return;
const auto& rawbytes =
toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16);
/* The Smaky bytes are stored backwards! Backwards! */
const auto& bytes =
decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits();
ByteReader br(bytes);
uint8_t track = br.read_8();
Bytes data = br.read(SMAKY6_SECTOR_SIZE);
uint8_t wantedChecksum = br.read_8();
uint8_t gotChecksum = sumBytes(data) & 0xff;
if (track != _sector->physicalTrack)
return;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalSector = _sectorId;
_sector->data = data;
_sector->status =
(wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
private:
const Smaky6DecoderProto& _config;
nanoseconds_t _startOfTrack;
std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts;
int _sectorId;
int _sectorIndex;
};
std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config)
{
return std::unique_ptr<Decoder>(new Smaky6Decoder(config));
}

10
arch/smaky6/smaky6.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef SMAKY6_H
#define SMAKY6_H
#define SMAKY6_SECTOR_SIZE 256
#define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1)
extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config);
#endif

6
arch/smaky6/smaky6.proto Normal file
View File

@@ -0,0 +1,6 @@
syntax = "proto2";
import "lib/common.proto";
message Smaky6DecoderProto {}

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
-----------------

43
doc/disk-smaky6.md Normal file
View File

@@ -0,0 +1,43 @@
Disk: Smaky 6
=============
The Smaky 6 is a Swiss computer from 1978 produced by Epsitec. It's based
around a Z80 processor and has one or two Micropolis 5.25" drives which use
16-sector hard sectored disks. The disk format is single-sided with 77 tracks
and 256-byte sectors, resulting in 308kB disks. It uses MFM with a custom
sector record scheme. It was later superceded by a 68000-based Smaky which used
different disks.
FluxEngine supports these, although because the Micropolis drives use a 100tpi
track pitch, you can't read Smaky 6 disks with a normal PC 96tpi drive. You
will have to find a 100tpi drive from somewhere (they're rare).
Reading disks
-------------
You must use a 100-tpi 80-track 5.25" floppy drive.
To read a Smaky 6 floppy, do:
```
fluxengine read smaky6
```
You should end up with a `smaky6.img` file.
Filesystem access
-----------------
There is experimental read-only support for the Smaky 6 filesystem, allowing
the directory to be listed and files read from disks. It's not known whether
this is completely correct, so don't trust it! See the [Filesystem
Access](filesystem.md) page for more information.
Useful references
-----------------
- [Smaky Info, 1978-2002 (in French)](https://www.smaky.ch/theme.php?id=sminfo)

View File

@@ -23,6 +23,7 @@ The following file systems are supported so far.
| FatFS (a.k.a. MS-DOS) | Y | Y | FAT12, FAT16, FAT32; not Atari (AFAIK!) |
| Macintosh HFS | Y | Y | Only AppleDouble files may be written |
| Apple ProDOS | Y | | |
| Smaky 6 | Y | | |
{: .datatable }
Please not that Atari disks do _not_ use standard FatFS, and the library I'm
@@ -82,7 +83,7 @@ default; for example, Macintosh HFS filesystems are common on 3.5" floppies. You
can do this as follows:
```
fluxengine format ibm1440 -f drive:1 --filesystem.machfs=
fluxengine format ibm1440 -f drive:1 --filesystem.type=MACHFS
```
Some filesystems won't work on some disks --- don't try this with Amiga FFS, for

BIN
doc/ju475-hd-hi.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
doc/ju475-hd-lo.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

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
@@ -213,6 +221,11 @@ FluxEngine supports a number of ways to get or put flux. When using the `-s` or
Read from a Kryoflux stream, where `<path>` is the directory containing the
stream files. **Read only.**
- `flx:<directory>`
Read from a FLUXCOPY stream, where `<path>` is the directory containing the
stream files. **Read only.**
- `erase:`
Read nothing --- writing this to a disk will magnetically erase a track.

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>FluxEngine.sh</string>
<key>CFBundleGetInfoString</key>
<string>FluxEngine 1.0</string>
<key>CFBundleIconFile</key>
<string>FluxEngine.icns</string>
<key>CFBundleIdentifier</key>
<string>com.cowlark.fluxengine.gui</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
</array>
<key>CFBundleName</key>
<string>FluxEngine</string>
<key>CFBundleDisplayName</key>
<string>FluxEngine</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>FluxEngine</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>LSRequiresCarbon</key>
<true/>
<key>NSAppleScriptEnabled</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
#!/bin/sh
dir=`dirname "$0"`
cd "$dir"
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 \
@@ -22,6 +23,8 @@ LIBFLUXENGINE_SRCS = \
lib/fluxsource/erasefluxsource.cc \
lib/fluxsource/fl2fluxsource.cc \
lib/fluxsource/fluxsource.cc \
lib/fluxsource/flx.cc \
lib/fluxsource/flxfluxsource.cc \
lib/fluxsource/hardwarefluxsource.cc \
lib/fluxsource/kryoflux.cc \
lib/fluxsource/kryofluxfluxsource.cc \
@@ -67,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 \
@@ -74,6 +78,8 @@ LIBFLUXENGINE_SRCS = \
lib/vfs/fatfs.cc \
lib/vfs/machfs.cc \
lib/vfs/prodos.cc \
lib/vfs/smaky6fs.cc \
lib/vfs/philefs.cc \
lib/vfs/vfs.cc \
lib/vfs/fluxsectorinterface.cc \
lib/vfs/imagesectorinterface.cc \

View File

@@ -205,6 +205,34 @@ std::vector<bool> Bytes::toBits() const
return bits;
}
Bytes Bytes::reverseBits() const
{
Bytes output;
ByteWriter bw(output);
for (uint8_t b : *this)
bw.write_8(reverse_bits(b));
return output;
}
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

@@ -62,6 +62,10 @@ public:
Bytes compress() const;
Bytes decompress() const;
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

@@ -10,6 +10,7 @@ message RangeProto {
extend google.protobuf.FieldOptions {
optional string help = 50000;
optional bool recurse = 50001 [default = true];
}
enum IndexMode {

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" ];
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

@@ -1,223 +1,226 @@
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "decoders/fluxmapreader.h"
#include "flux.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "sector.h"
#include "image.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/layout.h"
#include "fmt/format.h"
#include <numeric>
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
Error() << "no decoder specified";
return (decoder->second)(config);
}
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector->status = Sector::MISSING;
};
newSector();
beginTrack();
for (;;)
{
newSector();
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
/* Read the sector record. */
Fluxmap::Position before = fmr.tell();
decodeSectorRecord();
Fluxmap::Position after = fmr.tell();
pushRecord(before, after);
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
}
else
{
/* The data is in a separate record. */
for (;;)
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
}
}
void Decoder::pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end)
{
Fluxmap::Position here = _fmr->tell();
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->startTime = start.ns();
record->endTime = end.ns();
record->clock = _sector->clock;
record->rawData = toBytes(_recordBits);
_recordBits.clear();
}
void Decoder::resetFluxDecoder()
{
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
}
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
{
nanoseconds_t clock = _fmr->seekToPattern(pattern);
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
return clock;
}
void Decoder::seekToIndexMark()
{
_fmr->skipToEvent(F_BIT_PULSE);
_fmr->seekToIndexMark();
}
std::vector<bool> Decoder::readRawBits(unsigned count)
{
auto bits = _decoder->readBits(count);
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
return bits;
}
uint8_t Decoder::readRaw8()
{
return toBytes(readRawBits(8)).reader().read_8();
}
uint16_t Decoder::readRaw16()
{
return toBytes(readRawBits(16)).reader().read_be16();
}
uint32_t Decoder::readRaw20()
{
std::vector<bool> bits(4);
for (bool b : readRawBits(20))
bits.push_back(b);
return toBytes(bits).reader().read_be24();
}
uint32_t Decoder::readRaw24()
{
return toBytes(readRawBits(24)).reader().read_be24();
}
uint32_t Decoder::readRaw32()
{
return toBytes(readRawBits(32)).reader().read_be32();
}
uint64_t Decoder::readRaw48()
{
return toBytes(readRawBits(48)).reader().read_be48();
}
uint64_t Decoder::readRaw64()
{
return toBytes(readRawBits(64)).reader().read_be64();
}
#include "globals.h"
#include "flags.h"
#include "fluxmap.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "arch/agat/agat.h"
#include "arch/aeslanier/aeslanier.h"
#include "arch/amiga/amiga.h"
#include "arch/apple2/apple2.h"
#include "arch/brother/brother.h"
#include "arch/c64/c64.h"
#include "arch/f85/f85.h"
#include "arch/fb100/fb100.h"
#include "arch/ibm/ibm.h"
#include "arch/macintosh/macintosh.h"
#include "arch/micropolis/micropolis.h"
#include "arch/mx/mx.h"
#include "arch/northstar/northstar.h"
#include "arch/smaky6/smaky6.h"
#include "arch/tids990/tids990.h"
#include "arch/victor9k/victor9k.h"
#include "arch/zilogmcz/zilogmcz.h"
#include "decoders/fluxmapreader.h"
#include "flux.h"
#include "protocol.h"
#include "decoders/rawbits.h"
#include "sector.h"
#include "image.h"
#include "lib/decoders/decoders.pb.h"
#include "lib/layout.h"
#include "fmt/format.h"
#include <numeric>
std::unique_ptr<Decoder> Decoder::create(const DecoderProto& config)
{
static const std::map<int,
std::function<std::unique_ptr<Decoder>(const DecoderProto&)>>
decoders = {
{DecoderProto::kAgat, createAgatDecoder },
{DecoderProto::kAeslanier, createAesLanierDecoder },
{DecoderProto::kAmiga, createAmigaDecoder },
{DecoderProto::kApple2, createApple2Decoder },
{DecoderProto::kBrother, createBrotherDecoder },
{DecoderProto::kC64, createCommodore64Decoder},
{DecoderProto::kF85, createDurangoF85Decoder },
{DecoderProto::kFb100, createFb100Decoder },
{DecoderProto::kIbm, createIbmDecoder },
{DecoderProto::kMacintosh, createMacintoshDecoder },
{DecoderProto::kMicropolis, createMicropolisDecoder },
{DecoderProto::kMx, createMxDecoder },
{DecoderProto::kNorthstar, createNorthstarDecoder },
{DecoderProto::kSmaky6, createSmaky6Decoder },
{DecoderProto::kTids990, createTids990Decoder },
{DecoderProto::kVictor9K, createVictor9kDecoder },
{DecoderProto::kZilogmcz, createZilogMczDecoder },
};
auto decoder = decoders.find(config.format_case());
if (decoder == decoders.end())
Error() << "no decoder specified";
return (decoder->second)(config);
}
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
{
_trackdata = std::make_shared<TrackDataFlux>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector->status = Sector::MISSING;
};
newSector();
beginTrack();
for (;;)
{
newSector();
Fluxmap::Position recordStart = fmr.tell();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
return _trackdata;
/* Read the sector record. */
Fluxmap::Position before = fmr.tell();
decodeSectorRecord();
Fluxmap::Position after = fmr.tell();
pushRecord(before, after);
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
}
else
{
/* The data is in a separate record. */
for (;;)
{
_sector->headerStartTime = before.ns();
_sector->headerEndTime = after.ns();
_sector->clock = advanceToNextRecord();
if (fmr.eof() || !_sector->clock)
break;
before = fmr.tell();
decodeDataRecord();
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
{
_sector->position = before.bytes;
_sector->dataStartTime = before.ns();
_sector->dataEndTime = after.ns();
pushRecord(before, after);
break;
}
fmr.skipToEvent(F_BIT_PULSE);
resetFluxDecoder();
}
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
}
}
void Decoder::pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end)
{
Fluxmap::Position here = _fmr->tell();
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->position = start.bytes;
record->startTime = start.ns();
record->endTime = end.ns();
record->clock = _sector->clock;
record->rawData = toBytes(_recordBits);
_recordBits.clear();
}
void Decoder::resetFluxDecoder()
{
_decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config));
}
nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern)
{
nanoseconds_t clock = _fmr->seekToPattern(pattern);
_decoder.reset(new FluxDecoder(_fmr, clock, _config));
return clock;
}
void Decoder::seekToIndexMark()
{
_fmr->skipToEvent(F_BIT_PULSE);
_fmr->seekToIndexMark();
}
std::vector<bool> Decoder::readRawBits(unsigned count)
{
auto bits = _decoder->readBits(count);
_recordBits.insert(_recordBits.end(), bits.begin(), bits.end());
return bits;
}
uint8_t Decoder::readRaw8()
{
return toBytes(readRawBits(8)).reader().read_8();
}
uint16_t Decoder::readRaw16()
{
return toBytes(readRawBits(16)).reader().read_be16();
}
uint32_t Decoder::readRaw20()
{
std::vector<bool> bits(4);
for (bool b : readRawBits(20))
bits.push_back(b);
return toBytes(bits).reader().read_be24();
}
uint32_t Decoder::readRaw24()
{
return toBytes(readRawBits(24)).reader().read_be24();
}
uint32_t Decoder::readRaw32()
{
return toBytes(readRawBits(32)).reader().read_be32();
}
uint64_t Decoder::readRaw48()
{
return toBytes(readRawBits(48)).reader().read_be48();
}
uint64_t Decoder::readRaw64()
{
return toBytes(readRawBits(64)).reader().read_be64();
}

View File

@@ -71,6 +71,11 @@ public:
return _fmr->tell();
}
void rewind()
{
_fmr->rewind();
}
void seek(const Fluxmap::Position& pos)
{
return _fmr->seek(pos);

View File

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

View File

@@ -202,6 +202,23 @@ void FluxmapReader::seek(nanoseconds_t ns)
_pos.zeroes = 0;
}
void FluxmapReader::seekToByte(unsigned b)
{
if (b < _pos.bytes)
{
_pos.ticks = 0;
_pos.bytes = 0;
}
while (!eof() && (_pos.bytes < b))
{
int e;
unsigned t;
getNextEvent(e, t);
}
_pos.zeroes = 0;
}
nanoseconds_t FluxmapReader::seekToPattern(const FluxMatcher& pattern)
{
const FluxMatcher* unused;

View File

@@ -100,6 +100,7 @@ public:
/* Important! You can only reliably seek to 1 bits. */
void seek(nanoseconds_t ns);
void seekToByte(unsigned byte);
void seekToIndexMark();
nanoseconds_t seekToPattern(const FluxMatcher& pattern);

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;
}
}

View File

@@ -5,8 +5,9 @@ static std::unique_ptr<std::set<LocalBase*>> variables;
void Environment::reset()
{
for (LocalBase* var : *variables)
var->reset();
if (variables)
for (LocalBase* var : *variables)
var->reset();
}
void Environment::addVariable(LocalBase* local)

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

@@ -10,32 +10,32 @@ class TrackInfo;
struct Record
{
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
Bytes rawData;
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
uint32_t position = 0;
Bytes rawData;
};
struct TrackDataFlux
{
std::shared_ptr<const TrackInfo> trackInfo;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
std::shared_ptr<const TrackInfo> trackInfo;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
std::vector<std::shared_ptr<const Sector>> sectors;
};
struct TrackFlux
{
std::shared_ptr<const TrackInfo> trackInfo;
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
std::shared_ptr<const TrackInfo> trackInfo;
std::vector<std::shared_ptr<TrackDataFlux>> trackDatas;
std::set<std::shared_ptr<const Sector>> sectors;
};
struct DiskFlux
{
std::vector<std::shared_ptr<TrackFlux>> tracks;
std::shared_ptr<const Image> image;
std::vector<std::shared_ptr<TrackFlux>> tracks;
std::shared_ptr<const Image> image;
};
#endif

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

@@ -9,55 +9,85 @@
std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
{
switch (config.dest_case())
{
case FluxSinkProto::kDrive:
return createHardwareFluxSink(config.drive());
switch (config.type())
{
case FluxSinkProto::DRIVE:
return createHardwareFluxSink(config.drive());
case FluxSinkProto::kA2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::A2R:
return createA2RFluxSink(config.a2r());
case FluxSinkProto::kAu:
return createAuFluxSink(config.au());
case FluxSinkProto::AU:
return createAuFluxSink(config.au());
case FluxSinkProto::kVcd:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::VCD:
return createVcdFluxSink(config.vcd());
case FluxSinkProto::kScp:
return createScpFluxSink(config.scp());
case FluxSinkProto::SCP:
return createScpFluxSink(config.scp());
case FluxSinkProto::kFl2:
return createFl2FluxSink(config.fl2());
case FluxSinkProto::FLUX:
return createFl2FluxSink(config.fl2());
default:
Error() << "bad output disk config";
return std::unique_ptr<FluxSink>();
}
default:
Error() << "bad output disk config";
return std::unique_ptr<FluxSink>();
}
}
void FluxSink::updateConfigForFilename(FluxSinkProto* proto, const std::string& filename)
void FluxSink::updateConfigForFilename(
FluxSinkProto* proto, const std::string& filename)
{
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSinkProto*)>>> formats =
{
{ std::regex("^(.*\\.a2r)$"), [](auto& s, auto* proto) { proto->mutable_a2r()->set_filename(s); }},
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
{ std::regex("^vcd:(.*)$"), [](auto& s, auto* proto) { proto->mutable_vcd()->set_directory(s); }},
{ std::regex("^au:(.*)$"), [](auto& s, auto* proto) { proto->mutable_au()->set_directory(s); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
};
static const std::vector<std::pair<std::regex,
std::function<void(const std::string&, FluxSinkProto*)>>>
formats = {
{std::regex("^(.*\\.a2r)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::A2R);
proto->mutable_a2r()->set_filename(s);
}},
{std::regex("^(.*\\.flux)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{std::regex("^(.*\\.scp)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::SCP);
proto->mutable_scp()->set_filename(s);
}},
{std::regex("^vcd:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::VCD);
proto->mutable_vcd()->set_directory(s);
}},
{std::regex("^au:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::AU);
proto->mutable_au()->set_directory(s);
}},
{std::regex("^drive:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSinkProto::DRIVE);
config.mutable_drive()->set_drive(std::stoi(s));
}},
};
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
Error() << fmt::format("unrecognised flux filename '{}'", filename);
Error() << fmt::format("unrecognised flux filename '{}'", filename);
}

View File

@@ -14,7 +14,7 @@ message A2RFluxSinkProto {
}
message VcdFluxSinkProto {
optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"];
optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"];
}
message ScpFluxSinkProto {
@@ -27,15 +27,25 @@ message Fl2FluxSinkProto {
optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"];
}
// Next: 9
// Next: 10
message FluxSinkProto {
oneof dest {
HardwareFluxSinkProto drive = 2;
A2RFluxSinkProto a2r = 8;
AuFluxSinkProto au = 3;
VcdFluxSinkProto vcd = 4;
ScpFluxSinkProto scp = 5;
Fl2FluxSinkProto fl2 = 6;
enum FluxSinkType {
NOT_SET = 0;
DRIVE = 1;
A2R = 2;
AU = 3;
VCD = 4;
SCP = 5;
FLUX = 6;
}
optional FluxSinkType type = 9 [default = NOT_SET, (help) = "flux sink type"];
optional HardwareFluxSinkProto drive = 2;
optional A2RFluxSinkProto a2r = 8;
optional AuFluxSinkProto au = 3;
optional VcdFluxSinkProto vcd = 4;
optional ScpFluxSinkProto scp = 5;
optional Fl2FluxSinkProto fl2 = 6;
}

View File

@@ -9,6 +9,7 @@
#include "proto.h"
#include "fmt/format.h"
#include "fluxmap.h"
#include "layout.h"
#include "scp.h"
#include <fstream>
#include <sys/stat.h>
@@ -16,7 +17,7 @@
static int strackno(int track, int side)
{
return (track << 1) | side;
return (track << 1) | side;
}
static void write_le32(uint8_t dest[4], uint32_t v)
@@ -29,150 +30,170 @@ static void write_le32(uint8_t dest[4], uint32_t v)
static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
{
ByteReader br(bytes);
while (!br.eof())
checksum += br.read_8();
ByteReader br(bytes);
while (!br.eof())
checksum += br.read_8();
}
class ScpFluxSink : public FluxSink
{
public:
ScpFluxSink(const ScpFluxSinkProto& lconfig):
_config(lconfig)
{
bool singlesided = config.heads().start() == config.heads().end();
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig)
{
int minTrack;
int maxTrack;
int minSide;
int maxSide;
Layout::getBounds(
Layout::computeLocations(), minTrack, maxTrack, minSide, maxSide);
_fileheader.file_id[0] = 'S';
_fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte();
_fileheader.start_track = strackno(config.tracks().start(), config.heads().start());
_fileheader.end_track = strackno(config.tracks().end(), config.heads().end());
_fileheader.flags = SCP_FLAG_INDEXED
| SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
_fileheader.heads = singlesided;
_fileheader.file_id[0] = 'S';
_fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte();
_fileheader.start_track = strackno(minTrack, minSide);
_fileheader.end_track = strackno(maxTrack, maxSide);
_fileheader.flags = SCP_FLAG_INDEXED;
if (config.tpi() == 96)
_fileheader.flags |= SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
if ((minSide == 0) && (maxSide == 0))
_fileheader.heads = 1;
else if ((minSide == 1) && (maxSide == 1))
_fileheader.heads = 2;
else
_fileheader.heads = 0;
std::cout << fmt::format("SCP: writing 96 tpi {} file containing {} tracks\n",
singlesided ? "single sided" : "double sided",
_fileheader.end_track - _fileheader.start_track + 1
);
std::cout << fmt::format(
"SCP: writing 96 tpi {} file containing {} tracks\n",
(minSide == maxSide) ? "single sided" : "double sided",
_fileheader.end_track - _fileheader.start_track + 1);
}
}
~ScpFluxSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
Bytes((const uint8_t*)&_fileheader, sizeof(_fileheader))
.slice(0x10));
appendChecksum(checksum, _trackdata);
write_le32(_fileheader.checksum, checksum);
~ScpFluxSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
Bytes((const uint8_t*) &_fileheader, sizeof(_fileheader))
.slice(0x10));
appendChecksum(checksum, _trackdata);
write_le32(_fileheader.checksum, checksum);
std::cout << "SCP: writing output file...\n";
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
Error() << "cannot open output file";
of.write((const char*) &_fileheader, sizeof(_fileheader));
_trackdata.writeTo(of);
of.close();
}
std::cout << "SCP: writing output file...\n";
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
Error() << "cannot open output file";
of.write((const char*)&_fileheader, sizeof(_fileheader));
_trackdata.writeTo(of);
of.close();
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
int strack = strackno(track, head);
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
int strack = strackno(track, head);
if (strack >= std::size(_fileheader.track)) {
std::cout << fmt::format("SCP: cannot write track {} head {}, "
"there are not not enough Track Data Headers.\n",
track, head);
return;
if (strack >= std::size(_fileheader.track))
{
std::cout << fmt::format(
"SCP: cannot write track {} head {}, "
"there are not not enough Track Data Headers.\n",
track,
head);
return;
}
ScpTrack trackheader = {0};
trackheader.header.track_id[0] = 'T';
trackheader.header.track_id[1] = 'R';
trackheader.header.track_id[2] = 'K';
trackheader.header.strack = strack;
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
int revolution =
-1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index())
{
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
}
unsigned revTicks = 0;
unsigned totalTicks = 0;
unsigned ticksSinceLastPulse = 0;
uint32_t startOffset = 0;
while (revolution < 5)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
revTicks += ticks;
// if we haven't output any revolutions yet by the end of the track,
// assume that the whole track is one rev
// also discard any duplicate index pulses
if (((fmr.eof() && revolution <= 0) ||
((event & F_BIT_INDEX)) && revTicks > 0))
{
if (fmr.eof() && revolution == -1)
revolution = 0;
if (revolution >= 0)
{
auto* revheader = &trackheader.revolution[revolution];
write_le32(
revheader->offset, startOffset + sizeof(ScpTrack));
write_le32(revheader->length,
(fluxdataWriter.pos - startOffset) / 2);
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
revheader++;
}
ScpTrack trackheader = {0};
trackheader.header.track_id[0] = 'T';
trackheader.header.track_id[1] = 'R';
trackheader.header.track_id[2] = 'K';
trackheader.header.strack = strack;
revolution++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
if (fmr.eof())
break;
FluxmapReader fmr(fluxmap);
Bytes fluxdata;
ByteWriter fluxdataWriter(fluxdata);
if (event & F_BIT_PULSE)
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
}
}
int revolution = -1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index()) {
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
}
unsigned revTicks = 0;
unsigned totalTicks = 0;
unsigned ticksSinceLastPulse = 0;
uint32_t startOffset = 0;
while (revolution < 5)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
_fileheader.revolutions = revolution;
write_le32(
_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += fluxdata;
}
ticksSinceLastPulse += ticks;
totalTicks += ticks;
revTicks += ticks;
// if we haven't output any revolutions yet by the end of the track,
// assume that the whole track is one rev
// also discard any duplicate index pulses
if (((fmr.eof() && revolution <= 0) || ((event & F_BIT_INDEX)) && revTicks > 0))
{
if (fmr.eof() && revolution == -1)
revolution = 0;
if (revolution >= 0) {
auto* revheader = &trackheader.revolution[revolution];
write_le32(revheader->offset, startOffset + sizeof(ScpTrack));
write_le32(revheader->length, (fluxdataWriter.pos - startOffset) / 2);
write_le32(revheader->index, revTicks * NS_PER_TICK / 25);
revheader++;
}
revolution++;
revTicks = 0;
startOffset = fluxdataWriter.pos;
}
if (fmr.eof()) break;
if (event & F_BIT_PULSE)
{
unsigned t = ticksSinceLastPulse * NS_PER_TICK / 25;
while (t >= 0x10000)
{
fluxdataWriter.write_be16(0);
t -= 0x10000;
}
fluxdataWriter.write_be16(t);
ticksSinceLastPulse = 0;
}
}
_fileheader.revolutions = revolution;
write_le32(_fileheader.track[strack], trackdataWriter.pos + sizeof(ScpHeader));
trackdataWriter += Bytes((uint8_t*)&trackheader, sizeof(trackheader));
trackdataWriter += fluxdata;
}
operator std::string () const
{
return fmt::format("scp({})", _config.filename());
}
operator std::string() const
{
return fmt::format("scp({})", _config.filename());
}
private:
const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(const ScpFluxSinkProto& config)
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(
const ScpFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new ScpFluxSink(config));
}

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

@@ -17,93 +17,137 @@ static bool ends_with(const std::string& value, const std::string& ending)
std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)
{
switch (config.source_case())
{
case FluxSourceProto::kDrive:
return createHardwareFluxSource(config.drive());
switch (config.type())
{
case FluxSourceProto::DRIVE:
return createHardwareFluxSource(config.drive());
case FluxSourceProto::kErase:
return createEraseFluxSource(config.erase());
case FluxSourceProto::ERASE:
return createEraseFluxSource(config.erase());
case FluxSourceProto::kKryoflux:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::KRYOFLUX:
return createKryofluxFluxSource(config.kryoflux());
case FluxSourceProto::kTestPattern:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::TEST_PATTERN:
return createTestPatternFluxSource(config.test_pattern());
case FluxSourceProto::kScp:
return createScpFluxSource(config.scp());
case FluxSourceProto::SCP:
return createScpFluxSource(config.scp());
case FluxSourceProto::kCwf:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::CWF:
return createCwfFluxSource(config.cwf());
case FluxSourceProto::kFl2:
return createFl2FluxSource(config.fl2());
case FluxSourceProto::FLUX:
return createFl2FluxSource(config.fl2());
default:
Error() << "bad input disk configuration";
return std::unique_ptr<FluxSource>();
}
case FluxSourceProto::FLX:
return createFlxFluxSource(config.flx());
default:
Error() << "bad input disk configuration";
return std::unique_ptr<FluxSource>();
}
}
void FluxSource::updateConfigForFilename(FluxSourceProto* proto, const std::string& filename)
void FluxSource::updateConfigForFilename(
FluxSourceProto* proto, const std::string& filename)
{
static const std::vector<std::pair<std::regex, std::function<void(const std::string&, FluxSourceProto*)>>> formats =
{
{ std::regex("^(.*\\.a2r)$"), [](auto& s, auto* proto) { }},
{ std::regex("^(.*\\.flux)$"), [](auto& s, auto* proto) { proto->mutable_fl2()->set_filename(s); }},
{ std::regex("^(.*\\.scp)$"), [](auto& s, auto* proto) { proto->mutable_scp()->set_filename(s); }},
{ std::regex("^(.*\\.cwf)$"), [](auto& s, auto* proto) { proto->mutable_cwf()->set_filename(s); }},
{ std::regex("^erase:$"), [](auto& s, auto* proto) { proto->mutable_erase(); }},
{ std::regex("^kryoflux:(.*)$"), [](auto& s, auto* proto) { proto->mutable_kryoflux()->set_directory(s); }},
{ std::regex("^testpattern:(.*)"), [](auto& s, auto* proto) { proto->mutable_test_pattern(); }},
{ std::regex("^drive:(.*)"), [](auto& s, auto* proto) { proto->mutable_drive(); config.mutable_drive()->set_drive(std::stoi(s)); }},
};
static const std::vector<std::pair<std::regex,
std::function<void(const std::string&, FluxSourceProto*)>>>
formats = {
{std::regex("^(.*\\.flux)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::FLUX);
proto->mutable_fl2()->set_filename(s);
}},
{std::regex("^(.*\\.scp)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::SCP);
proto->mutable_scp()->set_filename(s);
}},
{std::regex("^(.*\\.cwf)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::CWF);
proto->mutable_cwf()->set_filename(s);
}},
{std::regex("^erase:$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::ERASE);
}},
{std::regex("^kryoflux:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::KRYOFLUX);
proto->mutable_kryoflux()->set_directory(s);
}},
{std::regex("^testpattern:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::TEST_PATTERN);
}},
{std::regex("^drive:(.*)"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::DRIVE);
config.mutable_drive()->set_drive(std::stoi(s));
}},
{std::regex("^flx:(.*)$"),
[](auto& s, auto* proto)
{
proto->set_type(FluxSourceProto::FLX);
proto->mutable_flx()->set_directory(s);
}},
};
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
for (const auto& it : formats)
{
std::smatch match;
if (std::regex_match(filename, match, it.first))
{
it.second(match[1], proto);
return;
}
}
Error() << fmt::format("unrecognised flux filename '{}'", filename);
Error() << fmt::format("unrecognised flux filename '{}'", filename);
}
class TrivialFluxSourceIterator : public FluxSourceIterator
{
public:
TrivialFluxSourceIterator(TrivialFluxSource* fluxSource, int track, int head):
_fluxSource(fluxSource),
_track(track),
_head(head)
{}
TrivialFluxSourceIterator(
TrivialFluxSource* fluxSource, int track, int head):
_fluxSource(fluxSource),
_track(track),
_head(head)
{
}
bool hasNext() const override
{
return !!_fluxSource;
}
bool hasNext() const override
{
return !!_fluxSource;
}
std::unique_ptr<const Fluxmap> next() override
{
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
_fluxSource = nullptr;
return fluxmap;
}
std::unique_ptr<const Fluxmap> next() override
{
auto fluxmap = _fluxSource->readSingleFlux(_track, _head);
_fluxSource = nullptr;
return fluxmap;
}
private:
TrivialFluxSource* _fluxSource;
int _track;
int _head;
TrivialFluxSource* _fluxSource;
int _track;
int _head;
};
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(int track, int head)
std::unique_ptr<FluxSourceIterator> TrivialFluxSource::readFlux(
int track, int head)
{
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
return std::make_unique<TrivialFluxSourceIterator>(this, track, head);
}

View File

@@ -14,14 +14,15 @@ class HardwareFluxSourceProto;
class KryofluxFluxSourceProto;
class ScpFluxSourceProto;
class TestPatternFluxSourceProto;
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
@@ -30,32 +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> 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,15 +30,33 @@ message Fl2FluxSourceProto {
(help) = ".fl2 file to read flux from"];
}
message FluxSourceProto {
oneof source {
HardwareFluxSourceProto drive = 2;
TestPatternFluxSourceProto test_pattern = 3;
EraseFluxSourceProto erase = 4;
KryofluxFluxSourceProto kryoflux = 5;
ScpFluxSourceProto scp = 6;
CwfFluxSourceProto cwf = 7;
Fl2FluxSourceProto fl2 = 8;
}
message FlxFluxSourceProto {
optional string directory = 1 [(help) = "path to FLX stream directory"];
}
// NEXT: 11
message FluxSourceProto {
enum FluxSourceType {
NOT_SET = 0;
DRIVE = 1;
TEST_PATTERN = 2;
ERASE = 3;
KRYOFLUX = 4;
SCP = 5;
CWF = 6;
FLUX = 7;
FLX = 8;
}
optional FluxSourceType type = 9 [default = NOT_SET, (help) = "flux source type"];
optional HardwareFluxSourceProto drive = 2;
optional TestPatternFluxSourceProto test_pattern = 3;
optional EraseFluxSourceProto erase = 4;
optional KryofluxFluxSourceProto kryoflux = 5;
optional ScpFluxSourceProto scp = 6;
optional CwfFluxSourceProto cwf = 7;
optional Fl2FluxSourceProto fl2 = 8;
optional FlxFluxSourceProto flx = 10;
}

50
lib/fluxsource/flx.cc Normal file
View File

@@ -0,0 +1,50 @@
#include "globals.h"
#include "fluxmap.h"
#include "kryoflux.h"
#include "protocol.h"
#include "lib/fluxsource/flx.h"
#include "fmt/format.h"
std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes)
{
ByteReader br(bytes);
/* Skip header. */
for (;;)
{
if (br.eof())
Error() << fmt::format("malformed FLX stream");
uint8_t b = br.read_8();
if (b == 0)
break;
}
auto fluxmap = std::make_unique<Fluxmap>();
while (!br.eof())
{
uint8_t b = br.read_8();
switch (b)
{
case FLX_INDEX:
fluxmap->appendIndex();
continue;
case FLX_STOP:
goto stop;
default:
{
if (b < 32)
Error() << fmt::format("unknown FLX opcode 0x{:2x}", b);
nanoseconds_t interval = b * FLX_TICK_NS;
fluxmap->appendInterval(interval / NS_PER_TICK);
fluxmap->appendPulse();
break;
}
}
}
stop:
return fluxmap;
}

16
lib/fluxsource/flx.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef FLX_H
#define FLX_H
#define FLX_TICK_NS 40 /* ns per tick */
/* Special FLX opcodes */
enum
{
FLX_INDEX = 0x08,
FLX_STOP = 0x0d
};
extern std::unique_ptr<Fluxmap> readFlxBytes(const Bytes& bytes);
#endif

View File

@@ -0,0 +1,32 @@
#include "lib/globals.h"
#include "lib/fluxmap.h"
#include "lib/fluxsource/fluxsource.pb.h"
#include "lib/fluxsource/fluxsource.h"
#include "lib/fluxsource/flx.h"
class FlxFluxSource : public TrivialFluxSource
{
public:
FlxFluxSource(const FlxFluxSourceProto& config): _path(config.directory())
{
}
public:
std::unique_ptr<const Fluxmap> readSingleFlux(int track, int side) override
{
std::string path =
fmt::format("{}/@TR{:02}S{}@.FLX", _path, track, side + 1);
return readFlxBytes(Bytes::readFromFile(path));
}
void recalibrate() {}
private:
const std::string _path;
};
std::unique_ptr<FluxSource> FluxSource::createFlxFluxSource(
const FlxFluxSourceProto& config)
{
return std::make_unique<FlxFluxSource>(config);
}

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

@@ -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

@@ -77,6 +77,7 @@ public:
config.set_tpi(48);
}
config.clear_layout();
auto layout = config.mutable_layout();
std::unique_ptr<Image> image(new Image);
for (int track = 0; track < trackTableSize / 4; track++)

View File

@@ -14,39 +14,39 @@
std::unique_ptr<ImageReader> ImageReader::create(const ImageReaderProto& config)
{
switch (config.format_case())
switch (config.type())
{
case ImageReaderProto::kDim:
case ImageReaderProto::DIM:
return ImageReader::createDimImageReader(config);
case ImageReaderProto::kD88:
case ImageReaderProto::D88:
return ImageReader::createD88ImageReader(config);
case ImageReaderProto::kFdi:
case ImageReaderProto::FDI:
return ImageReader::createFdiImageReader(config);
case ImageReaderProto::kImd:
case ImageReaderProto::IMD:
return ImageReader::createIMDImageReader(config);
case ImageReaderProto::kImg:
case ImageReaderProto::IMG:
return ImageReader::createImgImageReader(config);
case ImageReaderProto::kDiskcopy:
case ImageReaderProto::DISKCOPY:
return ImageReader::createDiskCopyImageReader(config);
case ImageReaderProto::kJv3:
case ImageReaderProto::JV3:
return ImageReader::createJv3ImageReader(config);
case ImageReaderProto::kD64:
case ImageReaderProto::D64:
return ImageReader::createD64ImageReader(config);
case ImageReaderProto::kNfd:
case ImageReaderProto::NFD:
return ImageReader::createNFDImageReader(config);
case ImageReaderProto::kNsi:
case ImageReaderProto::NSI:
return ImageReader::createNsiImageReader(config);
case ImageReaderProto::kTd0:
case ImageReaderProto::TD0:
return ImageReader::createTd0ImageReader(config);
default:
@@ -61,23 +61,23 @@ void ImageReader::updateConfigForFilename(
static const std::map<std::string, std::function<void(ImageReaderProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->mutable_img(); }},
{".d64", [](auto* proto) { proto->mutable_d64(); }},
{".d81", [](auto* proto) { proto->mutable_img(); }},
{".d88", [](auto* proto) { proto->mutable_d88(); }},
{".dim", [](auto* proto) { proto->mutable_dim(); }},
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
{".dsk", [](auto* proto) { proto->mutable_img(); }},
{".fdi", [](auto* proto) { proto->mutable_fdi(); }},
{".imd", [](auto* proto) { proto->mutable_imd(); }},
{".img", [](auto* proto) { proto->mutable_img(); }},
{".jv3", [](auto* proto) { proto->mutable_jv3(); }},
{".nfd", [](auto* proto) { proto->mutable_nfd(); }},
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
{".st", [](auto* proto) { proto->mutable_img(); }},
{".td0", [](auto* proto) { proto->mutable_td0(); }},
{".vgi", [](auto* proto) { proto->mutable_img(); }},
{".xdf", [](auto* proto) { proto->mutable_img(); }},
{".adf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageReaderProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageReaderProto::D88); }},
{".dim", [](auto* proto) { proto->set_type(ImageReaderProto::DIM); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageReaderProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".fdi", [](auto* proto) { proto->set_type(ImageReaderProto::FDI); }},
{".imd", [](auto* proto) { proto->set_type(ImageReaderProto::IMD); }},
{".img", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".jv3", [](auto* proto) { proto->set_type(ImageReaderProto::JV3); }},
{".nfd", [](auto* proto) { proto->set_type(ImageReaderProto::NFD); }},
{".nsi", [](auto* proto) { proto->set_type(ImageReaderProto::NSI); }},
{".st", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".td0", [](auto* proto) { proto->set_type(ImageReaderProto::TD0); }},
{".vgi", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageReaderProto::IMG); }},
// clang-format on
};
@@ -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

@@ -13,9 +13,9 @@ message Td0InputProto {}
message DimInputProto {}
message FdiInputProto {}
message D88InputProto {}
message NFDInputProto {}
message NfdInputProto {}
// NEXT_TAG: 14
// NEXT_TAG: 15
message ImageReaderProto
{
optional string filename = 1 [ (help) = "filename of input sector image" ];
@@ -24,18 +24,32 @@ message ImageReaderProto
default = false
];
oneof format
{
ImgInputOutputProto img = 2;
DiskCopyInputProto diskcopy = 3;
ImdInputProto imd = 4;
Jv3InputProto jv3 = 5;
D64InputProto d64 = 6;
NsiInputProto nsi = 7;
Td0InputProto td0 = 8;
DimInputProto dim = 9;
FdiInputProto fdi = 10;
D88InputProto d88 = 11;
NFDInputProto nfd = 12;
}
enum ImageReaderType {
NOT_SET = 0;
IMG = 1;
DISKCOPY = 2;
IMD = 3;
JV3 = 4;
D64 = 5;
NSI = 6;
TD0 = 7;
DIM = 8;
FDI = 9;
D88 = 10;
NFD = 11;
}
optional ImageReaderType type = 14 [default = NOT_SET, (help) = "input image type"];
optional ImgInputOutputProto img = 2;
optional DiskCopyInputProto diskcopy = 3;
optional ImdInputProto imd = 4;
optional Jv3InputProto jv3 = 5;
optional D64InputProto d64 = 6;
optional NsiInputProto nsi = 7;
optional Td0InputProto td0 = 8;
optional DimInputProto dim = 9;
optional FdiInputProto fdi = 10;
optional D88InputProto d88 = 11;
optional NfdInputProto nfd = 12;
}

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

@@ -14,30 +14,30 @@
std::unique_ptr<ImageWriter> ImageWriter::create(const ImageWriterProto& config)
{
switch (config.format_case())
switch (config.type())
{
case ImageWriterProto::kImg:
case ImageWriterProto::IMG:
return ImageWriter::createImgImageWriter(config);
case ImageWriterProto::kD64:
case ImageWriterProto::D64:
return ImageWriter::createD64ImageWriter(config);
case ImageWriterProto::kLdbs:
case ImageWriterProto::LDBS:
return ImageWriter::createLDBSImageWriter(config);
case ImageWriterProto::kDiskcopy:
case ImageWriterProto::DISKCOPY:
return ImageWriter::createDiskCopyImageWriter(config);
case ImageWriterProto::kNsi:
case ImageWriterProto::NSI:
return ImageWriter::createNsiImageWriter(config);
case ImageWriterProto::kRaw:
case ImageWriterProto::RAW:
return ImageWriter::createRawImageWriter(config);
case ImageWriterProto::kD88:
case ImageWriterProto::D88:
return ImageWriter::createD88ImageWriter(config);
case ImageWriterProto::kImd:
case ImageWriterProto::IMD:
return ImageWriter::createImdImageWriter(config);
default:
@@ -52,20 +52,20 @@ void ImageWriter::updateConfigForFilename(
static const std::map<std::string, std::function<void(ImageWriterProto*)>>
formats = {
// clang-format off
{".adf", [](auto* proto) { proto->mutable_img(); }},
{".d64", [](auto* proto) { proto->mutable_d64(); }},
{".d81", [](auto* proto) { proto->mutable_img(); }},
{".d88", [](auto* proto) { proto->mutable_d88(); }},
{".diskcopy", [](auto* proto) { proto->mutable_diskcopy(); }},
{".dsk", [](auto* proto) { proto->mutable_img(); }},
{".img", [](auto* proto) { proto->mutable_img(); }},
{".imd", [](auto* proto) { proto->mutable_imd(); }},
{".ldbs", [](auto* proto) { proto->mutable_ldbs(); }},
{".nsi", [](auto* proto) { proto->mutable_nsi(); }},
{".raw", [](auto* proto) { proto->mutable_raw(); }},
{".st", [](auto* proto) { proto->mutable_img(); }},
{".vgi", [](auto* proto) { proto->mutable_img(); }},
{".xdf", [](auto* proto) { proto->mutable_img(); }},
{".adf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d64", [](auto* proto) { proto->set_type(ImageWriterProto::D64); }},
{".d81", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".d88", [](auto* proto) { proto->set_type(ImageWriterProto::D88); }},
{".diskcopy", [](auto* proto) { proto->set_type(ImageWriterProto::DISKCOPY); }},
{".dsk", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".img", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".imd", [](auto* proto) { proto->set_type(ImageWriterProto::IMD); }},
{".ldbs", [](auto* proto) { proto->set_type(ImageWriterProto::LDBS); }},
{".nsi", [](auto* proto) { proto->set_type(ImageWriterProto::NSI); }},
{".raw", [](auto* proto) { proto->set_type(ImageWriterProto::RAW); }},
{".st", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".vgi", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
{".xdf", [](auto* proto) { proto->set_type(ImageWriterProto::IMG); }},
// clang-format on
};
@@ -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

@@ -63,24 +63,35 @@ message ImdOutputProto
optional string comment = 3 [ (help) = "comment to set in IMD file" ];
}
// NEXT_TAG: 11
// NEXT_TAG: 12
message ImageWriterProto
{
enum ImageWriterType {
NOT_SET = 0;
IMG = 1;
D64 = 2;
LDBS = 3;
DISKCOPY = 4;
NSI = 5;
RAW = 6;
D88 = 7;
IMD = 8;
}
optional string filename = 1 [ (help) = "filename of output sector image" ];
optional bool filesystem_sector_order = 10 [
(help) = "read/write sector image in filesystem order",
default = false
];
oneof format
{
ImgInputOutputProto img = 2;
D64OutputProto d64 = 3;
LDBSOutputProto ldbs = 4;
DiskCopyOutputProto diskcopy = 5;
NsiOutputProto nsi = 6;
RawOutputProto raw = 7;
D88OutputProto d88 = 8;
ImdOutputProto imd = 9;
}
optional ImageWriterType type = 11 [ default = NOT_SET, (help) = "image writer type" ];
optional ImgInputOutputProto img = 2;
optional D64OutputProto d64 = 3;
optional LDBSOutputProto ldbs = 4;
optional DiskCopyOutputProto diskcopy = 5;
optional NsiOutputProto nsi = 6;
optional RawOutputProto raw = 7;
optional D88OutputProto d88 = 8;
optional ImdOutputProto imd = 9;
}

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

@@ -4,9 +4,6 @@
#include "lib/environment.h"
#include <fmt/format.h>
static Local<std::map<std::pair<int, int>, std::shared_ptr<TrackInfo>>>
layoutCache;
static unsigned getTrackStep()
{
unsigned track_step =
@@ -61,6 +58,25 @@ std::vector<std::shared_ptr<const TrackInfo>> Layout::computeLocations()
return locations;
}
void Layout::getBounds(
const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack,
int& maxTrack,
int& minSide,
int& maxSide)
{
minTrack = minSide = INT_MAX;
maxTrack = maxSide = INT_MIN;
for (auto& ti : locations)
{
minTrack = std::min<int>(minTrack, ti->physicalTrack);
maxTrack = std::max<int>(maxTrack, ti->physicalTrack);
minSide = std::min<int>(minSide, ti->physicalSide);
maxSide = std::max<int>(maxSide, ti->physicalSide);
}
}
std::vector<std::pair<int, int>> Layout::getTrackOrdering(
unsigned guessedTracks, unsigned guessedSides)
{
@@ -109,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)
{
@@ -128,66 +158,57 @@ std::vector<unsigned> Layout::expandSectorList(
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
unsigned logicalTrack, unsigned logicalSide)
{
auto& layout = (*layoutCache)[std::make_pair(logicalTrack, logicalSide)];
if (!layout)
auto trackInfo = std::make_shared<TrackInfo>();
LayoutProto::LayoutdataProto layoutdata;
for (const auto& f : config.layout().layoutdata())
{
layout = std::make_shared<TrackInfo>();
if (f.has_track() && f.has_up_to_track() &&
((logicalTrack < f.track()) || (logicalTrack > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() &&
(logicalTrack != f.track()))
continue;
if (f.has_side() && (f.side() != logicalSide))
continue;
LayoutProto::LayoutdataProto layoutdata;
for (const auto& f : config.layout().layoutdata())
{
if (f.has_track() && f.has_up_to_track() &&
((logicalTrack < f.track()) ||
(logicalTrack > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() &&
(logicalTrack != f.track()))
continue;
if (f.has_side() && (f.side() != logicalSide))
continue;
layoutdata.MergeFrom(f);
}
layout->numTracks = config.layout().tracks();
layout->numSides = config.layout().sides();
layout->sectorSize = layoutdata.sector_size();
layout->logicalTrack = logicalTrack;
layout->logicalSide = logicalSide;
layout->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
layout->physicalSide = logicalSide ^ config.layout().swap_sides();
layout->groupSize = getTrackStep();
layout->diskSectorOrder = expandSectorList(layoutdata.physical());
layout->logicalSectorOrder = layout->diskSectorOrder;
std::sort(
layout->diskSectorOrder.begin(), layout->diskSectorOrder.end());
layout->numSectors = layout->logicalSectorOrder.size();
if (layoutdata.has_filesystem())
{
layout->filesystemSectorOrder =
expandSectorList(layoutdata.filesystem());
if (layout->filesystemSectorOrder.size() != layout->numSectors)
Error()
<< "filesystem sector order list doesn't contain the right "
"number of sectors";
}
else
{
for (unsigned sectorId : layout->logicalSectorOrder)
layout->filesystemSectorOrder.push_back(sectorId);
}
for (int i = 0; i < layout->numSectors; i++)
{
unsigned f = layout->logicalSectorOrder[i];
unsigned l = layout->filesystemSectorOrder[i];
layout->filesystemToLogicalSectorMap[f] = l;
layout->logicalToFilesystemSectorMap[l] = f;
}
layoutdata.MergeFrom(f);
}
return layout;
trackInfo->numTracks = config.layout().tracks();
trackInfo->numSides = config.layout().sides();
trackInfo->sectorSize = layoutdata.sector_size();
trackInfo->logicalTrack = logicalTrack;
trackInfo->logicalSide = logicalSide;
trackInfo->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
trackInfo->physicalSide = logicalSide ^ config.layout().swap_sides();
trackInfo->groupSize = getTrackStep();
trackInfo->diskSectorOrder = expandSectorList(layoutdata.physical());
trackInfo->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";
}
else
trackInfo->filesystemSectorOrder = trackInfo->naturalSectorOrder;
for (int i = 0; i < trackInfo->numSectors; i++)
{
unsigned fid = trackInfo->naturalSectorOrder[i];
unsigned lid = trackInfo->filesystemSectorOrder[i];
trackInfo->filesystemToNaturalSectorMap[fid] = lid;
trackInfo->naturalToFilesystemSectorMap[lid] = fid;
}
return trackInfo;
}
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical(

View File

@@ -23,10 +23,19 @@ public:
static unsigned remapSideLogicalToPhysical(unsigned logicalSide);
/* Uses the layout and current track and heads settings to determine
* which Locations are going to be read from or written to. 8/
* which Locations are going to be read from or written to.
*/
static std::vector<std::shared_ptr<const TrackInfo>> computeLocations();
/* Given a list of locations, determines the minimum and maximum track
* and side settings. */
static void getBounds(
const std::vector<std::shared_ptr<const TrackInfo>>& locations,
int& minTrack,
int& maxTrack,
int& minSide,
int& maxSide);
/* Returns a series of <track, side> pairs representing the filesystem
* ordering of the disk, in logical numbers. */
static std::vector<std::pair<int, int>> getTrackOrdering(
@@ -45,9 +54,10 @@ public:
const SectorListProto& sectorsProto);
};
class TrackInfo {
class TrackInfo
{
public:
TrackInfo() {}
TrackInfo() {}
private:
/* Can't copy. */
@@ -80,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

@@ -4,229 +4,245 @@
#include "fmt/format.h"
#include <regex>
ConfigProto config = []() {
ConfigProto config;
config.mutable_drive()->set_drive(0);
config.mutable_drive()->set_drive(0);
return config;
ConfigProto config = []()
{
ConfigProto config;
config.mutable_drive()->set_drive(0);
config.mutable_drive()->set_drive(0);
return config;
}();
static double toDouble(const std::string& value)
{
size_t idx;
double d = std::stod(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
size_t idx;
double d = std::stod(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
}
static int64_t toInt64(const std::string& value)
{
size_t idx;
int64_t d = std::stoll(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
size_t idx;
int64_t d = std::stoll(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
}
static uint64_t toUint64(const std::string& value)
{
size_t idx;
uint64_t d = std::stoull(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
size_t idx;
uint64_t d = std::stoull(value, &idx);
if (value[idx] != '\0')
Error() << fmt::format("invalid number '{}'", value);
return d;
}
void setRange(RangeProto* range, const std::string& data)
{
static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
static const std::regex DATA_REGEX(
"([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
std::smatch dmatch;
if (!std::regex_match(data, dmatch, DATA_REGEX))
Error() << "invalid range '" << data << "'";
int start = std::stoi(dmatch[1]);
range->set_start(start);
range->set_end(start);
range->clear_step();
if (!dmatch[2].str().empty())
range->set_end(std::stoi(dmatch[2]));
if (!dmatch[3].str().empty())
range->set_end(std::stoi(dmatch[3]) - range->start());
if (!dmatch[4].str().empty())
range->set_step(std::stoi(dmatch[4]));
std::smatch dmatch;
if (!std::regex_match(data, dmatch, DATA_REGEX))
Error() << "invalid range '" << data << "'";
int start = std::stoi(dmatch[1]);
range->set_start(start);
range->set_end(start);
range->clear_step();
if (!dmatch[2].str().empty())
range->set_end(std::stoi(dmatch[2]));
if (!dmatch[3].str().empty())
range->set_end(std::stoi(dmatch[3]) - range->start());
if (!dmatch[4].str().empty())
range->set_step(std::stoi(dmatch[4]));
}
ProtoField resolveProtoPath(google::protobuf::Message* message, const std::string& path)
ProtoField resolveProtoPath(
google::protobuf::Message* message, const std::string& path)
{
std::string::size_type dot = path.rfind('.');
std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot);
std::string trailing = (dot == std::string::npos) ? path : path.substr(dot+1);
std::string::size_type dot = path.rfind('.');
std::string leading = (dot == std::string::npos) ? "" : path.substr(0, dot);
std::string trailing =
(dot == std::string::npos) ? path : path.substr(dot + 1);
const auto* descriptor = message->GetDescriptor();
const auto* descriptor = message->GetDescriptor();
std::string item;
std::string item;
std::stringstream ss(leading);
while (std::getline(ss, item, '.'))
{
const auto* field = descriptor->FindFieldByName(item);
if (!field)
Error() << fmt::format("no such config field '{}' in '{}'", item, path);
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
Error() << fmt::format("config field '{}' in '{}' is not a message", item, path);
{
const auto* field = descriptor->FindFieldByName(item);
if (!field)
Error() << fmt::format(
"no such config field '{}' in '{}'", item, path);
if (field->type() != google::protobuf::FieldDescriptor::TYPE_MESSAGE)
Error() << fmt::format(
"config field '{}' in '{}' is not a message", item, path);
const auto* reflection = message->GetReflection();
switch (field->label())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
message = reflection->MutableMessage(message, field);
break;
const auto* reflection = message->GetReflection();
switch (field->label())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
message = reflection->MutableMessage(message, field);
break;
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
if (reflection->FieldSize(*message, field) == 0)
message = reflection->AddMessage(message, field);
else
message = reflection->MutableRepeatedMessage(message, field, 0);
break;
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
if (reflection->FieldSize(*message, field) == 0)
message = reflection->AddMessage(message, field);
else
message =
reflection->MutableRepeatedMessage(message, field, 0);
break;
default:
Error() << "bad proto label " << field->label();
}
default:
Error() << "bad proto label " << field->label();
}
descriptor = message->GetDescriptor();
descriptor = message->GetDescriptor();
}
const auto* field = descriptor->FindFieldByName(trailing);
if (!field)
Error() << fmt::format("no such config field '{}' in '{}'", trailing, path);
const auto* field = descriptor->FindFieldByName(trailing);
if (!field)
Error() << fmt::format(
"no such config field '{}' in '{}'", trailing, path);
return std::make_pair(message, field);
return std::make_pair(message, field);
}
void setProtoFieldFromString(ProtoField& protoField, const std::string& value)
{
google::protobuf::Message* message = protoField.first;
const google::protobuf::FieldDescriptor* field = protoField.second;
google::protobuf::Message* message = protoField.first;
const google::protobuf::FieldDescriptor* field = protoField.second;
const auto* reflection = message->GetReflection();
switch (field->type())
{
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
reflection->SetDouble(message, field, toDouble(value));
break;
const auto* reflection = message->GetReflection();
switch (field->type())
{
case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
reflection->SetDouble(message, field, toDouble(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
reflection->SetInt32(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT32:
reflection->SetInt32(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
reflection->SetInt64(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_INT64:
reflection->SetInt64(message, field, toInt64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
reflection->SetUInt32(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT32:
reflection->SetUInt32(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
reflection->SetUInt64(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_UINT64:
reflection->SetUInt64(message, field, toUint64(value));
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
reflection->SetString(message, field, value);
break;
case google::protobuf::FieldDescriptor::TYPE_STRING:
reflection->SetString(message, field, value);
break;
case google::protobuf::FieldDescriptor::TYPE_BOOL:
{
static const std::map<std::string, bool> boolvalues = {
{ "false", false },
{ "f", false },
{ "no", false },
{ "n", false },
{ "0", false },
{ "true", true },
{ "t", true },
{ "yes", true },
{ "y", true },
{ "1", true },
};
case google::protobuf::FieldDescriptor::TYPE_BOOL:
{
static const std::map<std::string, bool> boolvalues = {
{"false", false},
{"f", false},
{"no", false},
{"n", false},
{"0", false},
{"true", true },
{"t", true },
{"yes", true },
{"y", true },
{"1", true },
};
const auto& it = boolvalues.find(value);
if (it == boolvalues.end())
Error() << "invalid boolean value";
reflection->SetBool(message, field, it->second);
break;
}
const auto& it = boolvalues.find(value);
if (it == boolvalues.end())
Error() << "invalid boolean value";
reflection->SetBool(message, field, it->second);
break;
}
case google::protobuf::FieldDescriptor::TYPE_ENUM:
{
const auto* enumfield = field->enum_type();
const auto* enumvalue = enumfield->FindValueByName(value);
if (!enumvalue)
Error() << fmt::format("unrecognised enum value '{}'", value);
case google::protobuf::FieldDescriptor::TYPE_ENUM:
{
const auto* enumfield = field->enum_type();
const auto* enumvalue = enumfield->FindValueByName(value);
if (!enumvalue)
Error() << fmt::format("unrecognised enum value '{}'", value);
reflection->SetEnum(message, field, enumvalue);
break;
}
reflection->SetEnum(message, field, enumvalue);
break;
}
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
if (field->message_type() == RangeProto::descriptor())
{
setRange((RangeProto*)reflection->MutableMessage(message, field), value);
break;
}
if (field->containing_oneof() && value.empty())
{
reflection->MutableMessage(message, field);
break;
}
/* fall through */
default:
Error() << "can't set this config value type";
}
case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
if (field->message_type() == RangeProto::descriptor())
{
setRange(
(RangeProto*)reflection->MutableMessage(message, field),
value);
break;
}
if (field->containing_oneof() && value.empty())
{
reflection->MutableMessage(message, field);
break;
}
/* fall through */
default:
Error() << "can't set this config value type";
}
}
void setProtoByString(google::protobuf::Message* message, const std::string& path, const std::string& value)
void setProtoByString(google::protobuf::Message* message,
const std::string& path,
const std::string& value)
{
ProtoField protoField = resolveProtoPath(message, path);
setProtoFieldFromString(protoField, value);
ProtoField protoField = resolveProtoPath(message, path);
setProtoFieldFromString(protoField, value);
}
std::set<unsigned> iterate(const RangeProto& range)
{
std::set<unsigned> set;
int end = range.has_end()? range.end() : range.start();
for (unsigned i=range.start(); i<=end; i+=range.step())
set.insert(i);
return set;
std::set<unsigned> set;
int end = range.has_end() ? range.end() : range.start();
for (unsigned i = range.start(); i <= end; i += range.step())
set.insert(i);
return set;
}
std::set<unsigned> iterate(unsigned start, unsigned count)
{
std::set<unsigned> set;
for (unsigned i=0; i<count; i++)
set.insert(start + i);
return set;
std::set<unsigned> set;
for (unsigned i = 0; i < count; i++)
set.insert(start + i);
return set;
}
std::map<std::string, const google::protobuf::FieldDescriptor*> findAllProtoFields(google::protobuf::Message* message)
std::map<std::string, const google::protobuf::FieldDescriptor*>
findAllProtoFields(google::protobuf::Message* message)
{
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
const auto* descriptor = message->GetDescriptor();
std::map<std::string, const google::protobuf::FieldDescriptor*> fields;
const auto* descriptor = message->GetDescriptor();
std::function<void(const google::protobuf::Descriptor*, const std::string&)> recurse =
[&](auto* d, const auto& s) {
for (int i=0; i<d->field_count(); i++)
{
const google::protobuf::FieldDescriptor* f = d->field(i);
std::string n = s + f->name();
if (f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE)
recurse(f->message_type(), n + ".");
fields[n] = f;
}
};
std::function<void(const google::protobuf::Descriptor*, const std::string&)>
recurse = [&](auto* d, const auto& s)
{
for (int i = 0; i < d->field_count(); i++)
{
const google::protobuf::FieldDescriptor* f = d->field(i);
std::string n = s + f->name();
recurse(descriptor, "");
return fields;
if (f->options().GetExtension(::recurse) &&
(f->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE))
recurse(f->message_type(), n + ".");
fields[n] = f;
}
};
recurse(descriptor, "");
return fields;
}

View File

File diff suppressed because it is too large Load Diff

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

View File

@@ -3,6 +3,7 @@
#include "lib/bytes.h"
#include <fmt/format.h>
#include <iomanip>
#include <fstream>
bool emergencyStop = false;
@@ -190,3 +191,10 @@ std::string tohex(const std::string& s)
return ss.str();
}
bool doesFileExist(const std::string& filename)
{
std::ifstream f(filename);
return f.good();
}

View File

@@ -18,6 +18,7 @@ extern std::string toIso8601(time_t t);
extern std::string quote(const std::string& s);
extern std::string unhex(const std::string& s);
extern std::string tohex(const std::string& s);
extern bool doesFileExist(const std::string& filename);
/* If set, any running job will terminate as soon as possible (with an error).
*/

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

@@ -3,77 +3,77 @@
#include "lib/config.pb.h"
#include <fmt/format.h>
class Entry
{
public:
Entry(const Bytes& bytes, int map_entry_size)
{
user = bytes[0] & 0x0f;
{
std::stringstream ss;
ss << (char)(user + '0') << ':';
for (int i = 1; i <= 8; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 9; i <= 11; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
if (i == 9)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
{
std::stringstream ss;
if (bytes[9] & 0x80)
ss << 'R';
if (bytes[10] & 0x80)
ss << 'S';
if (bytes[11] & 0x80)
ss << 'A';
mode = ss.str();
}
extent = bytes[12] | (bytes[14] << 5);
records = bytes[15];
ByteReader br(bytes);
br.seek(16);
switch (map_entry_size)
{
case 1:
for (int i = 0; i < 16; i++)
allocation_map.push_back(br.read_8());
break;
case 2:
for (int i = 0; i < 8; i++)
allocation_map.push_back(br.read_le16());
break;
}
}
public:
std::string filename;
std::string mode;
unsigned user;
unsigned extent;
unsigned records;
std::vector<unsigned> allocation_map;
};
class CpmFsFilesystem : public Filesystem
{
class Entry
{
public:
Entry(const Bytes& bytes, int map_entry_size)
{
user = bytes[0] & 0x0f;
{
std::stringstream ss;
ss << (char)(user + '0') << ':';
for (int i = 1; i <= 8; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 9; i <= 11; i++)
{
uint8_t c = bytes[i] & 0x7f;
if (c == ' ')
break;
if (i == 9)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
{
std::stringstream ss;
if (bytes[9] & 0x80)
ss << 'R';
if (bytes[10] & 0x80)
ss << 'S';
if (bytes[11] & 0x80)
ss << 'A';
mode = ss.str();
}
extent = bytes[12] | (bytes[14] << 5);
records = bytes[15];
ByteReader br(bytes);
br.seek(16);
switch (map_entry_size)
{
case 1:
for (int i = 0; i < 16; i++)
allocation_map.push_back(br.read_8());
break;
case 2:
for (int i = 0; i < 8; i++)
allocation_map.push_back(br.read_le16());
break;
}
}
public:
std::string filename;
std::string mode;
unsigned user;
unsigned extent;
unsigned records;
std::vector<unsigned> allocation_map;
};
public:
CpmFsFilesystem(
const CpmFsProto& config, std::shared_ptr<SectorInterface> sectors):
@@ -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

@@ -246,11 +246,11 @@ public:
switch (cmd)
{
case GET_SECTOR_SIZE:
*(DWORD*)buffer = getLogicalSectorSize();
*(WORD*)buffer = getLogicalSectorSize();
break;
case GET_SECTOR_COUNT:
*(DWORD*)buffer = getLogicalSectorCount();
*(LBA_t*)buffer = getLogicalSectorCount();
break;
case CTRL_SYNC:

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);
}

211
lib/vfs/smaky6fs.cc Normal file
View File

@@ -0,0 +1,211 @@
#include "lib/globals.h"
#include "lib/vfs/vfs.h"
#include "lib/config.pb.h"
#include "lib/utils.h"
#include <fmt/format.h>
/* A directory entry looks like:
*
* 00-09: ten byte filename FFFFFFFF.EE
* 0a-0b: word: start sector
* 0c-17: unknown
*/
class Smaky6Filesystem : public Filesystem
{
class Entry
{
public:
Entry(const Bytes& bytes)
{
ByteReader br(bytes);
br.seek(10);
startSector = br.read_le16();
endSector = br.read_le16();
}
public:
std::string filename;
std::string mode;
uint16_t startSector;
uint16_t endSector;
};
class SmakyDirent : public Dirent
{
public:
SmakyDirent(const Bytes& dbuf)
{
{
std::stringstream ss;
for (int i = 0; i <= 7; i++)
{
uint8_t c = dbuf[i] & 0x7f;
if (c == ' ')
break;
ss << (char)c;
}
for (int i = 8; i <= 9; i++)
{
uint8_t c = dbuf[i] & 0x7f;
if (c == ' ')
break;
if (i == 8)
ss << '.';
ss << (char)c;
}
filename = ss.str();
}
std::string metadataBytes;
{
std::stringstream ss;
for (int i = 10; i < 0x18; i++)
ss << fmt::format("{:02x} ", (uint8_t)dbuf[i]);
metadataBytes = ss.str();
}
ByteReader br(dbuf);
br.skip(10); /* filename */
startSector = br.read_le16();
endSector = br.read_le16();
br.skip(2); /* unknown */
lastSectorLength = br.read_le16();
file_type = TYPE_FILE;
length = (endSector - startSector - 1) * 256 + lastSectorLength;
path = {filename};
attributes[Filesystem::FILENAME] = filename;
attributes[Filesystem::LENGTH] = std::to_string(length);
attributes[Filesystem::FILE_TYPE] = "file";
attributes[Filesystem::MODE] = "";
attributes["smaky6.start_sector"] = std::to_string(startSector);
attributes["smaky6.end_sector"] = std::to_string(endSector);
attributes["smaky6.sectors"] =
std::to_string(endSector - startSector);
attributes["smaky6.metadata_bytes"] = metadataBytes;
}
public:
unsigned startSector;
unsigned endSector;
unsigned lastSectorLength;
};
friend class Directory;
class Directory
{
public:
Directory(Smaky6Filesystem* fs)
{
/* Read the directory. */
auto bytes = fs->getLogicalSector(0, 3);
ByteReader br(bytes);
for (int i = 0; i < 32; i++)
{
auto dbuf = bytes.slice(i * 0x18, 0x18);
if (dbuf[0])
{
auto de = std::make_shared<SmakyDirent>(dbuf);
dirents.push_back(de);
}
}
}
std::shared_ptr<SmakyDirent> findFile(const std::string& filename)
{
for (auto& de : dirents)
if (de->filename == filename)
return de;
throw FileNotFoundException();
}
public:
std::vector<std::shared_ptr<SmakyDirent>> dirents;
};
public:
Smaky6Filesystem(
const Smaky6FsProto& config, std::shared_ptr<SectorInterface> sectors):
Filesystem(sectors),
_config(config)
{
}
uint32_t capabilities() const
{
return OP_LIST | OP_GETFILE | OP_GETFSDATA | OP_GETDIRENT;
}
FilesystemStatus check() override
{
return FS_OK;
}
std::map<std::string, std::string> getMetadata() override
{
Directory dir(this);
unsigned usedBlocks = 3;
for (auto& de : dir.dirents)
usedBlocks += (de->endSector - de->startSector);
std::map<std::string, std::string> attributes;
attributes[VOLUME_NAME] = "";
attributes[TOTAL_BLOCKS] = std::to_string(getLogicalSectorCount());
attributes[USED_BLOCKS] = std::to_string(usedBlocks);
attributes[BLOCK_SIZE] = std::to_string(getLogicalSectorSize(0, 0));
return attributes;
}
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
{
if (!path.empty())
throw FileNotFoundException();
Directory dir(this);
std::vector<std::shared_ptr<Dirent>> result;
for (auto& de : dir.dirents)
result.push_back(de);
return result;
}
std::shared_ptr<Dirent> getDirent(const Path& path) override
{
Directory dir(this);
if (path.size() != 1)
throw BadPathException();
return dir.findFile(path[0]);
}
Bytes getFile(const Path& path) override
{
if (path.size() != 1)
throw BadPathException(path);
Directory dir(this);
auto de = dir.findFile(path[0]);
Bytes data =
getLogicalSector(de->startSector, de->endSector - de->startSector);
data = data.slice(0, de->length);
return data;
}
private:
const Smaky6FsProto& _config;
};
std::unique_ptr<Filesystem> Filesystem::createSmaky6Filesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> sectors)
{
return std::make_unique<Smaky6Filesystem>(config.smaky6(), sectors);
}

View File

@@ -177,32 +177,41 @@ Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors):
std::unique_ptr<Filesystem> Filesystem::createFilesystem(
const FilesystemProto& config, std::shared_ptr<SectorInterface> image)
{
switch (config.filesystem_case())
switch (config.type())
{
case FilesystemProto::kBrother120:
case FilesystemProto::BROTHER120:
return Filesystem::createBrother120Filesystem(config, image);
case FilesystemProto::kAcorndfs:
case FilesystemProto::ACORNDFS:
return Filesystem::createAcornDfsFilesystem(config, image);
case FilesystemProto::kFatfs:
case FilesystemProto::FATFS:
return Filesystem::createFatFsFilesystem(config, image);
case FilesystemProto::kCpmfs:
case FilesystemProto::CPMFS:
return Filesystem::createCpmFsFilesystem(config, image);
case FilesystemProto::kAmigaffs:
case FilesystemProto::AMIGAFFS:
return Filesystem::createAmigaFfsFilesystem(config, image);
case FilesystemProto::kMachfs:
case FilesystemProto::MACHFS:
return Filesystem::createMacHfsFilesystem(config, image);
case FilesystemProto::kCbmfs:
case FilesystemProto::CBMFS:
return Filesystem::createCbmfsFilesystem(config, image);
case FilesystemProto::kProdos:
case FilesystemProto::PRODOS:
return Filesystem::createProdosFilesystem(config, image);
case FilesystemProto::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>();
@@ -218,13 +227,12 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
std::shared_ptr<Decoder> decoder;
std::shared_ptr<FluxSink> fluxSink;
std::shared_ptr<Encoder> encoder;
if (config.flux_source().source_case() !=
FluxSourceProto::SOURCE_NOT_SET)
if (config.flux_source().type() != FluxSourceProto::NOT_SET)
{
fluxSource = FluxSource::create(config.flux_source());
decoder = Decoder::create(config.decoder());
}
if (config.flux_sink().has_drive())
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
{
fluxSink = FluxSink::create(config.flux_sink());
encoder = Encoder::create(config.encoder());
@@ -236,11 +244,10 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
{
std::shared_ptr<ImageReader> reader;
std::shared_ptr<ImageWriter> writer;
if (config.image_reader().format_case() !=
ImageReaderProto::FORMAT_NOT_SET)
if ((config.image_reader().type() != ImageReaderProto::NOT_SET) &&
doesFileExist(config.image_reader().filename()))
reader = ImageReader::create(config.image_reader());
if (config.image_writer().format_case() !=
ImageWriterProto::FORMAT_NOT_SET)
if (config.image_writer().type() != ImageWriterProto::NOT_SET)
writer = ImageWriter::create(config.image_writer());
sectorInterface =

View File

@@ -2,6 +2,7 @@
#define VFS_H
#include "lib/bytes.h"
#include <fmt/format.h>
class Sector;
class Image;
@@ -58,6 +59,11 @@ public:
class BadPathException : public FilesystemException
{
public:
BadPathException(const Path& path):
FilesystemException(fmt::format("Bad path: '{}'", path.to_str()))
{
}
BadPathException(): FilesystemException("Bad path") {}
BadPathException(const std::string& msg): FilesystemException(msg) {}
@@ -242,6 +248,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,20 +59,53 @@ message CbmfsProto
message ProdosProto {}
// NEXT_TAG: 10
message AppledosProto {
optional uint32 filesystem_offset_sectors = 1 [
default = 0,
(help) = "offset the entire offset up the disk this many sectors"
];
}
message Smaky6FsProto {}
message PhileProto {
optional uint32 block_size = 1 [
default = 1024,
(help) = "Phile filesystem block size"
];
}
// NEXT_TAG: 14
message FilesystemProto
{
oneof filesystem
{
AcornDfsProto acorndfs = 1;
Brother120FsProto brother120 = 2;
FatFsProto fatfs = 3;
CpmFsProto cpmfs = 4;
AmigaFfsProto amigaffs = 5;
MacHfsProto machfs = 6;
CbmfsProto cbmfs = 7;
ProdosProto prodos = 8;
enum FilesystemType {
NOT_SET = 0;
ACORNDFS = 1;
BROTHER120 = 2;
FATFS = 3;
CPMFS = 4;
AMIGAFFS = 5;
MACHFS = 6;
CBMFS = 7;
PRODOS = 8;
SMAKY6 = 9;
APPLEDOS = 10;
PHILE = 11;
}
optional FilesystemType type = 10 [default = NOT_SET, (help) = "filesystem type"];
optional AcornDfsProto acorndfs = 1;
optional Brother120FsProto brother120 = 2;
optional FatFsProto fatfs = 3;
optional CpmFsProto cpmfs = 4;
optional AmigaFfsProto amigaffs = 5;
optional MacHfsProto machfs = 6;
optional CbmfsProto cbmfs = 7;
optional ProdosProto prodos = 8;
optional 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

@@ -211,10 +211,10 @@ static void draw_x_graticules(Agg2D& painter, double x1, double y1, double x2, d
int mainAnalyseDriveResponse(int argc, const char* argv[])
{
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
if (!config.flux_sink().has_drive())
if (config.flux_sink().type() != FluxSinkProto::DRIVE)
Error() << "this only makes sense with a real disk drive";
usbSetDrive(config.drive().drive(),

View File

@@ -210,7 +210,7 @@ static nanoseconds_t guessClock(const Fluxmap& fluxmap)
int mainInspect(int argc, const char* argv[])
{
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, {});
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

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

@@ -61,10 +61,10 @@ int mainRawRead(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawread", formats);
config.mutable_flux_source()->mutable_drive();
config.mutable_flux_source()->set_type(FluxSourceProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.flux_sink().has_drive())
if (config.flux_sink().type() == FluxSinkProto::DRIVE)
Error() << "you can't use rawread to write to hardware";
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

View File

@@ -54,7 +54,7 @@ static ActionFlag eraseFlag(
"erases the destination",
[]()
{
config.mutable_flux_source()->mutable_erase();
config.mutable_flux_source()->set_type(FluxSourceProto::ERASE);
});
int mainRawWrite(int argc, const char* argv[])
@@ -64,10 +64,10 @@ int mainRawWrite(int argc, const char* argv[])
if (argc == 1)
showProfiles("rawwrite", formats);
config.mutable_flux_sink()->mutable_drive();
config.mutable_flux_sink()->set_type(FluxSinkProto::DRIVE);
flags.parseFlagsWithConfigFiles(argc, argv, formats);
if (config.flux_source().has_drive())
if (config.flux_source().type() == FluxSourceProto::DRIVE)
Error() << "you can't use rawwrite to read from hardware";
std::unique_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));

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