Compare commits

...

229 Commits

Author SHA1 Message Date
David Given
dcae381973 Add some more GUI for the disk exerciser. 2025-10-24 01:00:11 +02:00
David Given
2142bc7cce Add a Disk menu. 2025-10-23 01:12:40 +02:00
David Given
ae3f82264a Add the boilerplate for the exerciser. 2025-10-22 01:13:36 +02:00
David Given
710e83c098 Try to fix OSX build failure. 2025-10-18 13:03:14 +02:00
David Given
4f46fff3be Remove a bunch of extraneous Providers. 2025-10-18 01:03:39 +02:00
David Given
58ea21a9a2 Add a menu option to allow resetting the workspace. 2025-10-18 00:36:52 +02:00
David Given
0fd1aa82a6 Crudely bodge image writes into working. 2025-10-17 23:41:16 +02:00
David Given
5b7f9d84f9 Fix some rendering issues. 2025-10-16 22:32:20 +02:00
David Given
4b7e8e74a7 Fix NPE. 2025-10-16 22:32:02 +02:00
David Given
5375c72d02 Correctly set the rotational period on Disks even on non-hardware readers. 2025-10-16 21:34:40 +02:00
David Given
5c257be164 Add a message if there's no flux data to draw. 2025-10-16 21:15:38 +02:00
David Given
7fa17322dc Remember to debounce index marks. 2025-10-16 21:00:57 +02:00
David Given
ed3640d945 Add a first draft visualiser. 2025-10-16 00:52:54 +02:00
David Given
87ce3ad61d Fluxmaps can now be queried for a (cached) list of index marks. Tracks
now contain both the raw list of sectors and a deduplicated list,
suitable for the visualiser.
2025-10-16 00:52:37 +02:00
David Given
6d75feb0ce Increase the default number of revolutions to 2.5 to ensure we get at
least one complete revolution.
2025-10-16 00:51:51 +02:00
David Given
168b8b6f6c Turn optimisation back on _again_. 2025-10-15 14:11:30 +02:00
David Given
3d063e932a Rereading disks through the GUI now works. 2025-10-15 00:41:49 +02:00
David Given
157ec569b2 Some more renaming. 2025-10-14 23:16:56 +02:00
David Given
f63c8dadf1 Lots more renaming. 2025-10-14 22:53:26 +02:00
David Given
d17f6116f0 Lots of symbol renaming. 2025-10-14 22:32:42 +02:00
David Given
2d6cb22e3a Looks like we're going to have to rework the reader/writer/source/sink
interfaces, so do fluxsink. This lets us test for overwriting a flux
file on writing in the GUI. HG: Enter commit message.
2025-10-14 21:54:59 +02:00
David Given
2de8b52e56 Fix the Micropolis options. 2025-10-14 00:18:27 +02:00
David Given
171576e538 Rename some stuff. 2025-10-14 00:14:11 +02:00
David Given
2db9f65e8b Move the flux file button into the config area. 2025-10-14 00:06:56 +02:00
David Given
2572b64bd1 You can now load images. 2025-10-14 00:03:44 +02:00
David Given
533aaf85f2 Add a status line. 2025-10-13 23:27:31 +02:00
David Given
f67ddc1f77 Writes now work. 2025-10-13 22:24:00 +02:00
David Given
b1d64f3683 Try and fix the Linux build. 2025-10-13 00:39:26 +02:00
David Given
7e8840e03f Add a rotational speed global option. 2025-10-13 00:38:10 +02:00
David Given
b003297b22 Remove the partially-finished MemoryFluxSink. 2025-10-13 00:33:55 +02:00
David Given
7341cec2c4 Add missing file. 2025-10-13 00:30:24 +02:00
David Given
a98b7f72fd Rearrange some UI. 2025-10-13 00:29:55 +02:00
David Given
2e97579394 Fix optimisation. 2025-10-13 00:20:16 +02:00
David Given
f960c7efd0 Added functionality for faking the necessary data in a DecodedDisk to
make the visualiser work. Blank images can now be created in memory.
2025-10-13 00:19:59 +02:00
David Given
c2e7f32cba Fix various issues to do with shared state and occasional crashes. 2025-10-12 15:51:33 +02:00
David Given
137528fc53 Format. 2025-10-12 15:51:02 +02:00
David Given
cbf4cc35fb Fix some default setting issues. 2025-10-12 15:50:50 +02:00
David Given
cd7b3de1b3 Finally add support for setting the default option. 2025-10-12 15:50:33 +02:00
David Given
fddc2270e5 Ensure that the layout's sector size is honoured. 2025-10-11 18:48:31 +02:00
David Given
2a96d9bd78 Some cleanup. 2025-10-11 18:48:14 +02:00
David Given
fd554f0808 Update imhex module. 2025-10-11 12:25:04 +02:00
David Given
6776c51b23 Add read/write indicators to the summary view. Fix a pile of minor bugs. 2025-10-11 00:49:59 +02:00
David Given
ef58295304 Better and more consistent exception handling. 2025-10-10 21:49:43 +02:00
David Given
2e2c3e3e34 Set a best-guess physical location on missing sectors. 2025-10-10 21:49:14 +02:00
David Given
e87bb44a2d Another OSX fix. 2025-10-10 20:53:11 +02:00
David Given
0ba0a9cce5 Tweak to try and make OSX happy. 2025-10-10 00:54:22 +02:00
David Given
97bb563ba0 Another massive overhaul to rip out the last remaining bits of Layout. 2025-10-10 00:21:47 +02:00
David Given
8f047f842e Massive overhaul to use the new disklayout stuff while
encoding/decoding. Fix lots of bugs, be more consistent with logical and
physical locations.
2025-10-08 22:41:13 +02:00
David Given
9d596ef530 Rename many things for clarity. 2025-10-06 23:29:20 +02:00
David Given
580ffa8cf7 Rename flux.h. 2025-10-06 23:11:50 +02:00
David Given
341e0a320d Rename the stuff in flux.h to actually make sense. 2025-10-06 23:09:26 +02:00
David Given
cff0a9703c Rework the flux data structures to be a bit more sensibly designed and
more amenable to copying.
2025-10-06 22:58:24 +02:00
David Given
38618532c4 Change sandboxing settings again. 2025-10-05 21:17:16 +02:00
David Given
6026dcd86d Try and fix sandboxing. 2025-10-05 21:02:17 +02:00
David Given
3949971546 Add a log viewer view. 2025-10-05 19:55:16 +02:00
David Given
6146f442fb Fix a bunch of minor UI issues. 2025-10-05 13:18:47 +02:00
David Given
7090c1bfdf Rework the way jobs are run so that everything happens in one callback,
which means thrown exceptions propagate properly and will cancel ongoing
jobs. Also, the state machine is much cleaner.
2025-10-05 01:35:16 +02:00
David Given
563babc969 Disable the config settings when the worker thread is busy. 2025-10-03 22:45:19 +02:00
David Given
b649c2b9af Tweak the way the debug menu works. 2025-10-03 22:34:55 +02:00
David Given
f7f887789c Override the debug and feedback menu URLs. 2025-10-03 22:27:44 +02:00
David Given
a8fcdcc528 Add a custom default layout. 2025-10-03 22:27:21 +02:00
David Given
a988578cc7 Oops, turn the sandbox back on! 2025-10-03 22:27:02 +02:00
David Given
ee585b24f0 Hopefully now almost correctly build the OSX package. 2025-10-03 20:49:54 +02:00
David Given
3d6e980990 Remove debugging. 2025-10-03 19:47:00 +02:00
David Given
f5d19416a9 Try something else for OSX. 2025-10-03 02:58:43 +02:00
David Given
4187fa5a09 Adjust Windows dependencies. 2025-10-03 02:55:48 +02:00
David Given
eb7613c03f Debugging. 2025-10-03 02:27:08 +02:00
David Given
7910429037 Debugging. 2025-10-03 02:24:28 +02:00
David Given
cd1cc736a7 Make the new gui canonical. 2025-10-03 02:24:17 +02:00
David Given
e6d6805f25 Switch from pkg-config to pkgconf. 2025-10-03 01:52:58 +02:00
David Given
9733879360 Debugging. 2025-10-03 01:48:48 +02:00
David Given
725712f796 I think boost needs to be linked. 2025-10-02 22:53:11 +02:00
David Given
2122cea5c4 More missing dependencies. 2025-10-02 22:37:13 +02:00
David Given
5466e716a9 Finally figure out the macos dependency problem. 2025-10-02 22:15:28 +02:00
David Given
0dc0e3d9a1 Debugging. 2025-10-02 21:51:08 +02:00
David Given
4bb12b2caa Debugging. 2025-10-02 21:43:48 +02:00
David Given
0d9c5f5150 Try updating homebrew before building. 2025-10-02 21:31:32 +02:00
David Given
4030031a2c Update OSX dependencies. 2025-10-02 21:22:00 +02:00
David Given
3143c87f1c Adjust dependencies... again. 2025-10-02 20:05:26 +02:00
David Given
f16f02c4c7 Adjust dependencies. 2025-10-02 19:57:29 +02:00
David Given
3e13b2461d Adjust dependencies. 2025-10-02 19:52:26 +02:00
David Given
5fd0d1589e Update msys before use. 2025-10-02 19:46:58 +02:00
David Given
23e6d234d0 Change the way we install msys to see if that helps. 2025-10-02 19:44:27 +02:00
David Given
cf2a97f8aa Update splash screen to contain the imhex logo. 2025-10-02 00:23:45 +02:00
David Given
5a815e0cd6 Extend DiskLayout to contain the sector offset and block number data.
Update the gui to use it.
2025-10-01 23:33:21 +02:00
David Given
06a3af2a1d Add filesystem sector offsets to the disk layout structure. 2025-10-01 00:34:09 +02:00
David Given
0558d95fa3 Attach the current layout to DiskFlux objects. 2025-10-01 00:26:19 +02:00
David Given
81f9246ab8 Rework summaryview to use the new DiskLayout. 2025-09-30 00:26:22 +02:00
David Given
6979567429 Add basic tests for DiskLayout. 2025-09-29 20:54:05 +02:00
David Given
348de4165d Initial version of the new DiskLayout class. 2025-09-25 00:04:51 +02:00
David Given
0755d420dd Change gitmodules to use my own fork of imhex. 2025-09-24 23:51:17 +02:00
David Given
dead21bce5 Typo fix. 2025-09-22 18:27:25 +02:00
David Given
4cf451ce60 We can't update msys because it kills the terminal. 2025-09-22 18:26:15 +02:00
David Given
72298ac805 Force msys update before installing dependencies. 2025-09-22 18:22:21 +02:00
David Given
3d1ad81652 Don't try and materialise the symbolic link; it breaks things. 2025-09-21 21:20:40 +02:00
David Given
88c79169b6 Don't log the environment any more. 2025-09-21 20:02:43 +02:00
David Given
d9747b9021 Don't build macos 13 any more. 2025-09-21 19:53:08 +02:00
David Given
256976a5a1 Adjust dependencies. 2025-09-21 19:52:20 +02:00
David Given
0ba4b82e10 Only require libtre on Windows. 2025-09-21 19:49:46 +02:00
David Given
ffd9e28b42 Try and build the Windows package. 2025-09-21 19:49:32 +02:00
David Given
9c919c786d Tweak dependencies. 2025-09-21 19:54:04 +02:00
David Given
47a9a56959 Remember to set -static for source file compilation, not just linking. Use the correct
protoc on mingw.
2025-09-21 17:49:17 +02:00
David Given
6e03bc604a Turns out undefined format_type and drive_type fields are normal. 2025-09-21 17:07:54 +02:00
David Given
feea6a027a Add another ignored variable. 2025-09-21 17:07:21 +02:00
David Given
08fa06b7fe Enable static builds on Windows. 2025-09-21 03:40:25 +02:00
David Given
8a976edef9 Warning fix. 2025-09-20 23:19:22 +02:00
David Given
c71d8d6c74 Another typo fix. 2025-09-20 20:59:39 +02:00
David Given
e809af7426 Typo fix. 2025-09-20 20:54:55 +02:00
David Given
ab05db9040 Switch back to gcc. 2025-09-20 20:49:18 +02:00
David Given
04f916741e Try big-obj instead. 2025-09-20 20:26:42 +02:00
David Given
f6224f3718 Try mcmodel=medium. 2025-09-20 20:09:07 +02:00
David Given
10185bb7a1 Do I need -fPIC for Windows? 2025-09-20 17:36:37 +02:00
David Given
d565960c70 Flag adjustment. 2025-09-20 16:58:24 +02:00
David Given
c21073294f More adjustments. 2025-09-20 16:13:32 +02:00
David Given
3cd95de434 Try using clang instead of gcc. 2025-09-20 16:00:38 +02:00
David Given
6552dba9aa Even more dependencies. 2025-09-20 13:00:15 +02:00
David Given
c8ebe55aa9 Warning fix. 2025-09-20 13:00:04 +02:00
David Given
1eefa2d604 Even even more dependencies. 2025-09-20 12:28:53 +02:00
David Given
a359394eea Even more dependencies. 2025-09-20 12:25:56 +02:00
David Given
9f13026bec Even more dependency changes. 2025-09-20 12:23:14 +02:00
David Given
8fcc99b2a1 More dependencies. 2025-09-20 12:20:26 +02:00
David Given
125a0536ff More dependency tweaks. 2025-09-20 12:17:53 +02:00
David Given
4115947d80 Don't use /dev/stderr as it makes msys sad. 2025-09-20 12:17:09 +02:00
David Given
2f1dcd7c9a More build tweak. 2025-09-20 12:11:34 +02:00
David Given
5e00ffca13 Remove some more WSL stuff. 2025-09-20 12:07:44 +02:00
David Given
ac27095493 Don't need this any more. 2025-09-20 11:54:28 +02:00
David Given
e27ca5cd4c Reformat. 2025-09-20 12:00:52 +02:00
David Given
cc72ac6327 Ignore some more variables. 2025-09-20 12:00:33 +02:00
David Given
5443aa6501 Some work towards making things build on Windows. 2025-09-20 03:24:49 +02:00
David Given
902bf32169 Allow bypassing the sandbox, as it makes msys sad. 2025-09-20 03:24:10 +02:00
David Given
d200633747 Fix windows weirdness. 2025-09-20 03:23:55 +02:00
David Given
a48b749c2e Support Windows protoc command syntax. 2025-09-20 01:22:24 +02:00
David Given
46fab84b95 Progress towards making everything build on Windows. 2025-09-19 02:02:34 +02:00
David Given
b0290f858c Tweak build system. 2025-09-18 22:28:03 +02:00
David Given
fe09c12cd6 Modernise protobuf usage. 2025-09-18 16:37:56 +02:00
David Given
b5ae5a1cea Don't try and use the dbus library on OSX. 2025-09-18 16:26:35 +02:00
David Given
113cb85512 Fix dependencies. 2025-09-18 14:47:43 +02:00
David Given
da276bcb3b Fix dependencies. 2025-09-18 14:45:34 +02:00
David Given
9a78d0f38c Fix OSX build script. 2025-09-18 14:40:23 +02:00
David Given
ec2e1666e7 Make build on Linux again. 2025-09-18 14:39:38 +02:00
David Given
478df40d4b Attempt to make build on OSX. 2025-09-18 00:56:53 +02:00
David Given
a8b9d79cb1 Header fix for OSX. 2025-09-18 00:55:17 +02:00
David Given
23865d1a10 Rip out a lot of imhex stuff we don't want. 2025-09-17 22:46:30 +02:00
David Given
458b3f24fe Make a basic splash screen. 2025-09-17 22:18:54 +02:00
David Given
86fa23e6fa Add missing files. 2025-09-17 21:09:21 +02:00
David Given
dd9d5aaed5 Remember to build all of libpl. 2025-09-17 21:08:50 +02:00
David Given
b22df17bb5 Fix some fmt strings. 2025-09-17 14:28:41 +02:00
David Given
b81e609e66 Hopefully fix the format_to ambiguity. 2025-09-17 14:11:17 +02:00
David Given
d41e57cba6 Fix format errors. 2025-09-17 01:07:37 +02:00
David Given
da7e83e257 Update dependencies. 2025-09-17 00:41:39 +02:00
David Given
83be12fcf1 Update dependencies. 2025-09-17 00:26:25 +02:00
David Given
a999e2d6c9 Adjust dependencies. 2025-09-17 00:14:58 +02:00
David Given
6d6251e757 Adjust dependencies. 2025-09-17 00:12:59 +02:00
David Given
be8b26ef94 Try updating Ubuntu to get a better compiler. 2025-09-17 00:09:40 +02:00
David Given
c6b8bce5d6 Reenable optimisation. 2025-09-17 00:08:29 +02:00
David Given
d8b3452c07 Update dependencies. 2025-09-17 00:05:09 +02:00
David Given
eddbd43cd9 Update dependencies. 2025-09-17 00:03:14 +02:00
David Given
168189180d Update dependencies. 2025-09-16 23:53:58 +02:00
David Given
9e092bab6a Nope, pkg-config for mbedtls doesn't work. 2025-09-16 23:47:28 +02:00
David Given
2c35126b3a mbedcrypto and mbedtls are linked on most systems, and the pkg-config
files seem bad.
2025-09-16 23:42:47 +02:00
David Given
7dc0e4ca31 Rearrange for consistency. 2025-09-16 22:55:58 +02:00
David Given
96257f89d5 Use a local lunasvg. 2025-09-16 22:40:46 +02:00
David Given
09919343b4 Import LunaSVG. 2025-09-16 22:33:20 +02:00
David Given
b070c1068c Add a mostly-functioning logical map in the summary view. 2025-09-16 22:30:53 +02:00
David Given
5628a576db Don't mix up physical and logical units when seeking to a track. 2025-09-16 22:30:09 +02:00
David Given
073c78e25f Be firmer about rebuilding the configuration. 2025-09-16 22:29:29 +02:00
David Given
6a826d6eb5 Move the controls into their own panel. 2025-09-16 00:42:02 +02:00
David Given
11a6143d4c Move all the settings out to a config view again, but designed
differently. Much better.
2025-09-15 22:49:30 +02:00
David Given
6127c9a46d Wire up some more control panel buttons. Create an in-memory sector
interface for doing filesystem stuff.
2025-09-15 20:06:16 +02:00
David Given
98f7febef7 Make two variations on the sector map; one for the physical view and one
for the logical view.
2025-09-13 22:54:48 +02:00
David Given
85afadacf0 Don't make images with holes --- ensure that all sectors in the image
are populated, even if with a stub 'MISSING' sector, with layout
information.
2025-09-13 01:55:01 +02:00
David Given
01cd812162 Use std::optional a bit more right. 2025-09-13 01:54:09 +02:00
David Given
39329acc77 Remove the obsolete configview. 2025-09-12 22:37:27 +02:00
David Given
bdc96038ef Get the format part of the new control panel working. 2025-09-12 22:31:50 +02:00
David Given
93760d989a Get the device config part of the new control panel working. 2025-09-12 22:14:13 +02:00
David Given
b306c7063b Start moving the config view functionality into the control panel. The
current config is now displayed there.
2025-09-12 00:20:46 +02:00
David Given
e3d7fa69d8 Add a --hd global option for setting high density. 2025-09-12 00:20:10 +02:00
David Given
f6c0e5405a Wire up some control panel buttons. Make toasts work. 2025-09-11 01:05:15 +02:00
David Given
fc12a2662c ImGui text entries are very sensitive and don't like skipping frames or
being disabled, so do it less often.
2025-09-10 23:52:23 +02:00
David Given
ab5b16488c The control panel mostly works. 2025-09-10 00:13:07 +02:00
David Given
4d5900268b Create a DiskProvider on startup so we go straight into the GUI. 2025-09-09 00:27:49 +02:00
David Given
b5c5a4335d Wire up the summary and sector map views; although it's shown that the
layout code really needs refactoring. Again.
2025-09-09 00:14:21 +02:00
David Given
e76235541a Add custom out-of-box and welcome screen implementations. 2025-09-08 21:11:26 +02:00
David Given
e75e1a6e27 Make the track/cylinder side/head terminology more consistent. 2025-09-08 20:08:23 +02:00
David Given
aa220ecbcb Sectors and Images now store more unseful information. The DiskProvider
now works.
2025-09-08 19:52:57 +02:00
David Given
edc8d74418 The sector map view is now mostly working. 2025-09-07 00:56:37 +02:00
David Given
2831aa09ae Add CylinderHeadSector in place of some of the 3-tuples I was previously
using; do some refactoring.
2025-09-07 00:56:18 +02:00
David Given
e1b4b0d3a3 Fix kryoflux reading and writing. 2025-09-07 00:55:49 +02:00
David Given
e5df6ca33b Remember to set the correct drive type for 80-track disks. 2025-09-07 00:31:52 +02:00
David Given
68c3cbb020 Sketch out the image view. 2025-09-06 13:22:40 +02:00
David Given
ca3c37d20a Add logic to Image for access filesystem blocks. 2025-09-06 13:22:29 +02:00
David Given
6fcd9233ea Disks can now be read, and incremental progress shows up in the summary view. 2025-09-06 01:21:46 +02:00
David Given
3761c4b1e2 Broadcast a DiskReadLogMessage every time a track is read, with the
entire accumulated disk data (including all the resolved sectors). This
makes it much easier to show partial results in the GUI.
2025-09-06 01:21:07 +02:00
David Given
c89c53b1c7 Try displaying the track status. 2025-09-06 00:04:46 +02:00
David Given
be0f63a133 The summary view now shows a respectable track summary. 2025-09-05 23:14:12 +02:00
David Given
a8216995ad Merge. 2025-09-04 00:57:35 +02:00
David Given
995359ef45 Start preparing to do a read. 2025-09-04 00:55:50 +02:00
David Given
bc84e3c8a0 It doesn't look great, but I think the config view is mostly done. 2025-09-03 23:48:48 +02:00
David Given
af12a25a9d Update dependencies. 2025-09-03 14:47:24 +02:00
David Given
f6b2821221 Update dependencies. 2025-09-03 14:32:20 +02:00
David Given
458601a139 Update dependencies. 2025-09-03 14:29:39 +02:00
David Given
a89130edbd The config viewer now mostly works, and sets mostly correct
configurations in the settings.
2025-09-03 01:14:53 +02:00
David Given
c95cd8a4da Add hints for global options to tell the GUI where to show them. 2025-09-03 01:14:23 +02:00
David Given
4d313a8495 Add in our custom view. 2025-09-01 00:45:36 +02:00
David Given
263eef3442 Try and enable submodules on github. 2025-09-01 00:30:52 +02:00
David Given
2e97996211 Get a minimal FE plugin working. 2025-09-01 00:29:55 +02:00
David Given
7035b9c3c2 Rearrange and clean up. 2025-08-31 23:58:07 +02:00
David Given
5628d2ca06 We need a custom romfs generator. 2025-08-31 23:55:24 +02:00
David Given
61cf7fbccf Get a fully working static imhex. 2025-08-31 23:37:51 +02:00
David Given
ce347c6326 A fair number of assets now load. 2025-08-31 16:43:13 +02:00
David Given
94119b19fe Getting my head around the way imhex works. 2025-08-31 00:35:47 +02:00
David Given
9c7be1268f Build some more bits. 2025-08-29 21:18:14 +02:00
David Given
a9d59f67ba It finally builds! Although does nothing. 2025-08-29 20:37:39 +02:00
David Given
8d2a72228f A reasonable amount of the imhex framework now builds. 2025-08-29 00:35:00 +02:00
David Given
60b95dd3f3 Added throwing_ptr. 2025-08-28 23:41:51 +02:00
David Given
b1094f40dc Add libromfs. 2025-08-28 23:27:40 +02:00
David Given
e40ea80e34 Add xdgpp. 2025-08-28 22:34:27 +02:00
David Given
9e1222d38a Add Native File Dialog. 2025-08-28 22:20:31 +02:00
David Given
4446785729 Put pattern-language in the right place. 2025-08-28 21:40:09 +02:00
David Given
790f015d72 Add the pattern language. 2025-08-28 21:36:42 +02:00
David Given
ccb0dcea3c Finally remember to add the build file! 2025-08-28 21:35:59 +02:00
David Given
15a0632af0 Add imgui. 2025-08-28 21:30:21 +02:00
David Given
3c0da28947 Some more building. 2025-08-28 21:23:22 +02:00
David Given
95227f32ca Start putting together the imhex build. 2025-08-28 21:10:41 +02:00
David Given
edf75b5cda Add libwolv. 2025-08-28 21:09:27 +02:00
David Given
af87c48451 Add an imhex submodule. 2025-08-28 20:18:02 +02:00
David Given
7cde8e3aa6 Merge pull request #824 from davidgiven/ab
Update ab to the new ninja version.
2025-08-27 19:47:09 +01:00
David Given
46b90d9c36 Merge pull request #825 from boamaod/master
Correct Juku E5104 documentation
2025-08-27 13:44:49 +01:00
Märt Põder
7ee67082aa Fix ambigious description about "both sides" and update links 2025-08-27 13:22:09 +03:00
218 changed files with 10616 additions and 1790 deletions

View File

@@ -8,19 +8,21 @@ concurrency:
jobs:
build-linux:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine'
path: 'fluxengine'
submodules: 'true'
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine-testdata'
path: 'fluxengine-testdata'
- name: apt
run: |
sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev libprotobuf-dev
sudo apt update
sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.2-dev libfmt-dev libprotobuf-dev libmagic-dev libmbedtls-dev libcurl4-openssl-dev libmagic-dev nlohmann-json3-dev libdbus-1-dev libglfw3-dev libmd4c-dev libfreetype-dev libcli11-dev libboost-regex-dev
- name: make
run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j`nproc` -C fluxengine
@@ -50,20 +52,22 @@ jobs:
build-macos-current:
strategy:
matrix:
runs-on: [macos-13, macos-latest]
runs-on: [macos-15, macos-15-intel]
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine'
path: 'fluxengine'
submodules: 'true'
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine-testdata'
path: 'fluxengine-testdata'
- name: brew
run: |
brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg
brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg libmagic nlohmann-json cli11 boost glfw3 md4c ninja python freetype2 mbedtls
brew upgrade
- name: make
run: gmake -C fluxengine
- name: Upload build artifacts
@@ -76,29 +80,33 @@ jobs:
build-windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- name: setup WSL
run: |
curl -L https://github.com/WhitewaterFoundry/Fedora-Remix-for-WSL/releases/download/41.0.0/Fedora-Remix-for-WSL-SL_41.0.0.0_x64_arm64.msixbundle -o fedora.msixbundle
unzip fedora.msixbundle Fedora-Remix-for-WSL-SL_41.0.0.0_x64.msix
unzip Fedora-Remix-for-WSL-SL_41.0.0.0_x64.msix install.tar.gz
wsl --update
wsl --set-default-version 1
wsl --import fedora fedora install.tar.gz
wsl --set-default fedora
wsl sh -c 'dnf -y install https://github.com/rpmsphere/noarch/raw/master/r/rpmsphere-release-40-1.noarch.rpm'
wsl sh -c 'dnf -y install gcc gcc-c++ protobuf-c-compiler protobuf-devel fmt-devel systemd-devel sqlite-devel wxGTK-devel mingw32-gcc mingw32-gcc-c++ mingw32-zlib-static mingw32-protobuf-static mingw32-sqlite-static mingw32-wxWidgets3-static mingw32-libpng-static mingw32-libjpeg-static mingw32-libtiff-static mingw32-nsis png2ico ninja-build'
- uses: msys2/setup-msys2@v2
with:
msystem: mingw64
update: true
install: |
python diffutils ninja make zip
pacboy: |
protobuf:p pkgconf:p curl-winssl:p file:p glfw:p mbedtls:p
sqlite:p freetype:p boost:p gcc:p binutils:p nsis:p abseil-cpp:p
- name: fix line endings
- name: debug
run: |
git config --global core.autocrlf false
git config --global core.eol lf
pacboy -Q --info protobuf:p
cat /mingw64/lib/pkgconfig/protobuf.pc
/mingw64/bin/pkg-config.exe protobuf --cflags
/mingw64/bin/pkg-config.exe protobuf --cflags --static
- uses: actions/checkout@v4
with:
repository: 'davidgiven/fluxengine'
path: 'fluxengine'
submodules: 'true'
- uses: actions/checkout@v4
with:
@@ -107,17 +115,18 @@ jobs:
- name: run
run: |
wsl sh -c 'make -C fluxengine BUILDTYPE=windows -j$(nproc)'
make -C fluxengine BUILDTYPE=windows
- name: nsis
run: |
wsl sh -c 'cd fluxengine && strip fluxengine.exe -o fluxengine-stripped.exe'
wsl sh -c 'cd fluxengine && strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe'
wsl sh -c 'cd fluxengine && makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi'
cd fluxengine
strip fluxengine.exe -o fluxengine-stripped.exe
strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe
makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi
- name: zip
run: |
wsl sh -c 'cd fluxengine && 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'
cd fluxengine && 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@v4

36
.gitmodules vendored Normal file
View File

@@ -0,0 +1,36 @@
[submodule "dep/imhex"]
path = dep/imhex
url = git@github.com:davidgiven/ImHex.git
[submodule "dep/libwolv"]
path = dep/libwolv
url = https://github.com/WerWolv/libwolv.git
[submodule "dep/imgui"]
path = dep/imgui
url = https://github.com/ocornut/imgui.git
[submodule "dep/pattern-language"]
path = dep/pattern-language
url = https://github.com/WerWolv/PatternLanguage.git
[submodule "dep/native-file-dialog"]
path = dep/native-file-dialog
url = https://github.com/btzy/nativefiledialog-extended.git
[submodule "dep/xdgpp"]
path = dep/xdgpp
url = https://github.com/WerWolv/xdgpp.git
[submodule "dep/libromfs"]
path = dep/libromfs
url = https://github.com/WerWolv/libromfs.git
[submodule "dep/throwing_ptr"]
path = dep/throwing_ptr
url = https://github.com/rockdreamer/throwing_ptr.git
[submodule "dep/lunasvg"]
path = dep/lunasvg
url = https://github.com/sammycage/lunasvg.git
[submodule "dep/md4c"]
path = dep/md4c
url = https://github.com/mity/md4c
[submodule "dep/nlohmann_json"]
path = dep/nlohmann_json
url = https://github.com/nlohmann/json
[submodule "dep/cli11"]
path = dep/cli11
url = https://github.com/CLIUtils/CLI11

View File

@@ -8,30 +8,52 @@ ifeq ($(BUILDTYPE),)
endif
export BUILDTYPE
OPTFLAGS = -g -O3
ifeq ($(BUILDTYPE),windows)
MINGW = i686-w64-mingw32-
MINGW = x86_64-w64-mingw32-
CC = $(MINGW)gcc
CXX = $(MINGW)g++ -std=c++20
CFLAGS += -g -O3 \
-Wno-unknown-warning-option \
CXX = $(MINGW)g++
CFLAGS += \
$(OPTFLAGS) \
-ffunction-sections \
-fdata-sections
-fdata-sections \
-Wno-attributes \
-Wa,-mbig-obj \
-static
CXXFLAGS += \
-fext-numeric-literals \
$(OPTFLAGS) \
-std=c++23 \
-Wno-deprecated-enum-float-conversion \
-Wno-deprecated-enum-enum-conversion
LDFLAGS += -static -Wl,--gc-sections
AR = $(MINGW)ar
PKG_CONFIG = $(MINGW)pkg-config -static
-Wno-deprecated-enum-enum-conversion \
-Wno-attributes \
-Wa,-mbig-obj \
-static
LDFLAGS += -Wl,--gc-sections -static
AR = $(MINGW)gcc-ar
PKG_CONFIG = $(MINGW)pkg-config --static
WINDRES = $(MINGW)windres
WX_CONFIG = /usr/i686-w64-mingw32/sys-root/mingw/bin/wx-config-3.0 --static=yes
NINJA = /bin/ninja
PROTOC = /mingw64/bin/protoc
PROTOC_SEPARATOR = ;
EXT = .exe
AB_SANDBOX = no
else
CC = gcc
CXX = g++ -std=c++20
CFLAGS = -g -O3 \
CC = clang
CXX = clang++
CFLAGS = \
$(OPTFLAGS) \
-I/opt/homebrew/include -I/usr/local/include \
-Wno-unknown-warning-option
CXXFLAGS += \
CXXFLAGS = \
$(OPTFLAGS) \
-std=c++23 \
-fexperimental-library \
-I/opt/homebrew/include -I/usr/local/include \
-Wformat \
-Wformat-security \
-Wno-deprecated-enum-float-conversion \
-Wno-deprecated-enum-enum-conversion
LDFLAGS =
@@ -56,31 +78,33 @@ BINDIR ?= $(PREFIX)/bin
# Special Windows settings.
ifeq ($(OS), Windows_NT)
EXT ?= .exe
MINGWBIN = /mingw32/bin
CCPREFIX = $(MINGWBIN)/
PKG_CONFIG = $(MINGWBIN)/pkg-config
WX_CONFIG = /usr/bin/sh $(MINGWBIN)/wx-config --static=yes
PROTOC = $(MINGWBIN)/protoc
WINDRES = windres
LDFLAGS += \
-static
CXXFLAGS += \
-fext-numeric-literals \
-Wno-deprecated-enum-float-conversion \
-Wno-deprecated-enum-enum-conversion
# Required to get the gcc run - time libraries on the path.
export PATH := $(PATH):$(MINGWBIN)
endif
#ifeq ($(OS), Windows_NT)
# EXT ?= .exe
# MINGWBIN = /mingw32/bin
# CCPREFIX = $(MINGWBIN)/
# PKG_CONFIG = $(MINGWBIN)/pkg-config
# WX_CONFIG = /usr/bin/sh $(MINGWBIN)/wx-config --static=yes
# PROTOC = $(MINGWBIN)/protoc
# WINDRES = windres
# LDFLAGS += \
# -static
# CXXFLAGS += \
# -fext-numeric-literals \
# -Wno-deprecated-enum-float-conversion \
# -Wno-deprecated-enum-enum-conversion
#
# # Required to get the gcc run - time libraries on the path.
# export PATH := $(PATH):$(MINGWBIN)
#endif
# Special OSX settings.
ifeq ($(shell uname),Darwin)
LDFLAGS += \
-framework IOKit \
-framework Foundation
-framework AppKit \
-framework UniformTypeIdentifiers \
-framework UserNotifications
endif
.PHONY: all

View File

@@ -36,8 +36,8 @@ public:
decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE);
const auto& reversed = bytes.reverseBits();
_sector->logicalTrack = reversed[1];
_sector->logicalSide = 0;
_sector->logicalCylinder = reversed[1];
_sector->logicalHead = 0;
_sector->logicalSector = reversed[2];
/* Check header 'checksum' (which seems far too simple to mean much). */

View File

@@ -59,9 +59,9 @@ public:
if (bytes[3] != 0x5a)
return;
_sector->logicalTrack = bytes[1] >> 1;
_sector->logicalCylinder = bytes[1] >> 1;
_sector->logicalSector = bytes[2];
_sector->logicalSide = bytes[1] & 1;
_sector->logicalHead = bytes[1] & 1;
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}

View File

@@ -58,13 +58,10 @@ private:
};
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
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;
@@ -80,7 +77,7 @@ public:
writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa);
writeRawBits(SECTOR_ID, 64);
writeByte(0x5a);
writeByte((sector->logicalTrack << 1) | sector->logicalSide);
writeByte((sector->logicalCylinder << 1) | sector->logicalHead);
writeByte(sector->logicalSector);
writeByte(0x5a);

View File

@@ -52,8 +52,8 @@ public:
Bytes header = amigaDeinterleave(ptr, 4);
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
_sector->logicalTrack = header[1] >> 1;
_sector->logicalSide = header[1] & 1;
_sector->logicalCylinder = header[1] >> 1;
_sector->logicalHead = header[1] & 1;
_sector->logicalSector = header[2];
uint32_t wantedheaderchecksum =

View File

@@ -84,7 +84,7 @@ static void write_sector(std::vector<bool>& bits,
checksum = 0;
Bytes header = {0xff, /* Amiga 1.0 format byte */
(uint8_t)((sector->logicalTrack << 1) | sector->logicalSide),
(uint8_t)((sector->logicalCylinder << 1) | sector->logicalHead),
(uint8_t)sector->logicalSector,
(uint8_t)(AMIGA_SECTORS_PER_TRACK - sector->logicalSector)};
write_interleaved_bytes(header);
@@ -110,7 +110,7 @@ public:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{

View File

@@ -5,6 +5,7 @@
#include "protocol.h"
#include "lib/decoders/decoders.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
#include "arch/apple2/apple2.h"
#include "arch/apple2/apple2.pb.h"
#include "lib/decoders/decoders.pb.h"
@@ -93,24 +94,25 @@ public:
ByteReader br(header);
uint8_t volume = combine(br.read_be16());
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSide = _sector->physicalSide;
_sector->logicalCylinder = combine(br.read_be16());
_sector->logicalHead = _ltl->logicalHead;
_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))
(volume ^ _sector->logicalCylinder ^ _sector->logicalSector))
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
if (_sector->logicalSide == 1)
_sector->logicalTrack -= _config.apple2().side_one_track_offset();
if (_sector->logicalHead == 1)
_sector->logicalCylinder -=
_config.apple2().side_one_track_offset();
/* Sanity check. */
if (_sector->logicalTrack > 100)
if (_sector->logicalCylinder > 100)
{
_sector->status = Sector::MISSING;
return;

View File

@@ -36,7 +36,7 @@ private:
const Apple2EncoderProto& _config;
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
@@ -129,8 +129,8 @@ private:
// extra padding.
write_ff40(sector.logicalSector == 0 ? 32 : 8);
int track = sector.logicalTrack;
if (sector.logicalSide == 1)
int track = sector.logicalCylinder;
if (sector.logicalHead == 1)
track += _config.side_one_track_offset();
// Write address field: APPLE2_SECTOR_RECORD + sector identifier +

View File

@@ -75,14 +75,14 @@ public:
const auto& bytes = toBytes(rawbits).slice(0, 4);
ByteReader br(bytes);
_sector->logicalTrack = decode_header_gcr(br.read_be16());
_sector->logicalCylinder = decode_header_gcr(br.read_be16());
_sector->logicalSector = decode_header_gcr(br.read_be16());
/* Sanity check the values read; there's no header checksum and
* occasionally we get garbage due to bit errors. */
if (_sector->logicalSector > 11)
return;
if (_sector->logicalTrack > 79)
if (_sector->logicalCylinder > 79)
return;
_sector->status = Sector::DATA_MISSING;

View File

@@ -107,7 +107,7 @@ public:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
@@ -127,7 +127,7 @@ public:
fillBitmapTo(bits, cursor, headerCursor, {true, false});
write_sector_header(bits,
cursor,
sectorData->logicalTrack,
sectorData->logicalCylinder,
sectorData->logicalSector);
fillBitmapTo(bits, cursor, dataCursor, {true, false});
write_sector_data(bits, cursor, sectorData->data);

View File

@@ -74,8 +74,8 @@ public:
uint8_t checksum = bytes[0];
_sector->logicalSector = bytes[1];
_sector->logicalSide = 0;
_sector->logicalTrack = bytes[2] - 1;
_sector->logicalHead = 0;
_sector->logicalCylinder = bytes[2] - 1;
if (checksum == xorBytes(bytes.slice(1, 4)))
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */

View File

@@ -155,7 +155,7 @@ public:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
@@ -178,7 +178,7 @@ public:
else
_formatByte1 = _formatByte2 = 0;
double clockRateUs = clockPeriodForC64Track(trackInfo->logicalTrack);
double clockRateUs = clockPeriodForC64Track(ltl.logicalCylinder);
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
@@ -245,7 +245,7 @@ private:
* 06-07 - $0F ("off" bytes)
*/
uint8_t encodedTrack =
((sector->logicalTrack) +
((sector->logicalCylinder) +
1); // C64 track numbering starts with 1. Fluxengine with 0.
uint8_t encodedSector = sector->logicalSector;
// uint8_t formatByte1 = C64_FORMAT_ID_BYTE1;

View File

@@ -76,8 +76,8 @@ public:
const auto& bytes = decode(readRawBits(6 * 10));
_sector->logicalSector = bytes[2];
_sector->logicalSide = 0;
_sector->logicalTrack = bytes[0];
_sector->logicalHead = 0;
_sector->logicalCylinder = bytes[0];
uint16_t wantChecksum = bytes.reader().seek(4).read_be16();
uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4));

View File

@@ -126,8 +126,8 @@ public:
return;
uint8_t abssector = id[2];
_sector->logicalTrack = abssector >> 1;
_sector->logicalSide = 0;
_sector->logicalCylinder = abssector >> 1;
_sector->logicalHead = 0;
_sector->logicalSector = abssector & 1;
_sector->data.writer().append(id.slice(5, 12)).append(payload);

View File

@@ -141,11 +141,10 @@ public:
bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN);
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(
trackdata, _sector->physicalTrack, _sector->physicalSide);
getTrackFormat(trackdata, _ltl->logicalCylinder, _ltl->logicalHead);
_sector->logicalTrack = br.read_8();
_sector->logicalSide = br.read_8();
_sector->logicalCylinder = br.read_8();
_sector->logicalHead = br.read_8();
_sector->logicalSector = br.read_8();
_currentSectorSize = 1 << (br.read_8() + 7);
@@ -156,11 +155,10 @@ public:
Sector::DATA_MISSING; /* correct but unintuitive */
if (trackdata.ignore_side_byte())
_sector->logicalSide =
Layout::remapSidePhysicalToLogical(_sector->physicalSide);
_sector->logicalSide ^= trackdata.invert_side_byte();
_sector->logicalHead = _ltl->logicalHead;
_sector->logicalHead ^= trackdata.invert_side_byte();
if (trackdata.ignore_track_byte())
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalCylinder = _ltl->logicalCylinder;
for (int sector : trackdata.ignore_sector())
if (_sector->logicalSector == sector)
@@ -209,16 +207,14 @@ public:
_sector->status =
(wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
auto layout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
if (_currentSectorSize != layout->sectorSize)
if (_currentSectorSize != _ltl->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->logicalCylinder,
_sector->logicalHead,
_sector->logicalSector,
layout->sectorSize,
_ltl->sectorSize,
_currentSectorSize);
}

View File

@@ -107,16 +107,12 @@ private:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
IbmEncoderProto::TrackdataProto trackdata;
getEncoderTrackData(
trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
auto trackLayout = Layout::getLayoutOfTrack(
trackInfo->logicalTrack, trackInfo->logicalSide);
getEncoderTrackData(trackdata, ltl.logicalCylinder, ltl.logicalHead);
auto writeBytes = [&](const Bytes& bytes)
{
@@ -152,7 +148,7 @@ public:
uint8_t sectorSize = 0;
{
int s = trackLayout->sectorSize >> 7;
int s = ltl.sectorSize >> 7;
while (s > 1)
{
s >>= 1;
@@ -202,9 +198,9 @@ public:
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(idamUnencoded);
bw.write_8(sectorData->logicalTrack);
bw.write_8(sectorData->logicalCylinder);
bw.write_8(
sectorData->logicalSide ^ trackdata.invert_side_byte());
sectorData->logicalHead ^ trackdata.invert_side_byte());
bw.write_8(sectorData->logicalSector);
bw.write_8(sectorSize);
uint16_t crc = crc16(CCITT_POLY, header);
@@ -237,8 +233,7 @@ public:
}
bw.write_8(damUnencoded);
Bytes truncatedData =
sectorData->data.slice(0, trackLayout->sectorSize);
Bytes truncatedData = sectorData->data.slice(0, ltl.sectorSize);
bw += truncatedData;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);

View File

@@ -5,6 +5,7 @@
#include "protocol.h"
#include "lib/decoders/decoders.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
#include "arch/macintosh/macintosh.h"
#include "lib/core/bytes.h"
#include "fmt/format.h"
@@ -146,7 +147,7 @@ public:
auto header = toBytes(readRawBits(7 * 8)).slice(0, 7);
uint8_t encodedTrack = decode_data_gcr(header[0]);
if (encodedTrack != (_sector->physicalTrack & 0x3f))
if (encodedTrack != (_ltl->logicalCylinder & 0x3f))
return;
uint8_t encodedSector = decode_data_gcr(header[1]);
@@ -157,8 +158,8 @@ public:
if (encodedSector > 11)
return;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = decode_side(encodedSide);
_sector->logicalCylinder = _ltl->logicalCylinder;
_sector->logicalHead = decode_side(encodedSide);
_sector->logicalSector = encodedSector;
uint8_t gotsum =
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;

View File

@@ -181,10 +181,10 @@ static void write_sector(std::vector<bool>& bits,
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */
write_bits(bits, cursor, MAC_SECTOR_RECORD, 3 * 8);
uint8_t encodedTrack = sector->logicalTrack & 0x3f;
uint8_t encodedTrack = sector->logicalCylinder & 0x3f;
uint8_t encodedSector = sector->logicalSector;
uint8_t encodedSide =
encode_side(sector->logicalTrack, sector->logicalSide);
encode_side(sector->logicalCylinder, sector->logicalHead);
uint8_t formatByte = MAC_FORMAT_BYTE;
uint8_t headerChecksum =
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
@@ -220,11 +220,11 @@ public:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
double clockRateUs = clockRateUsForTrack(trackInfo->logicalTrack);
double clockRateUs = clockRateUsForTrack(ltl.logicalCylinder);
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;

View File

@@ -4,6 +4,7 @@
#include "lib/data/fluxpattern.h"
#include "lib/decoders/decoders.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
#include "arch/micropolis/micropolis.h"
#include "lib/core/bytes.h"
#include "fmt/format.h"
@@ -222,14 +223,14 @@ public:
if (syncByte != 0xFF)
return;
_sector->logicalTrack = br.read_8();
_sector->logicalSide = _sector->physicalSide;
_sector->logicalCylinder = br.read_8();
_sector->logicalHead = _ltl->logicalHead;
_sector->logicalSector = br.read_8();
if (_sector->logicalSector > 15)
return;
if (_sector->logicalTrack > 76)
if (_sector->logicalCylinder > 76)
return;
if (_sector->logicalTrack != _sector->physicalTrack)
if (_sector->logicalCylinder != _ltl->logicalCylinder)
return;
br.read(10); /* OS data or padding */

View File

@@ -40,7 +40,7 @@ static void write_sector(std::vector<bool>& bits,
{
ByteWriter writer(sectorData);
writer.write_8(0xff); /* Sync */
writer.write_8(sector->logicalTrack);
writer.write_8(sector->logicalCylinder);
writer.write_8(sector->logicalSector);
for (int i = 0; i < 10; i++)
writer.write_8(0); /* Padding */
@@ -87,7 +87,7 @@ public:
{
}
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{

View File

@@ -6,6 +6,7 @@
#include "lib/data/fluxmapreader.h"
#include "lib/data/fluxpattern.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
#include <string.h>
const int SECTOR_SIZE = 256;
@@ -64,8 +65,8 @@ public:
gotChecksum += br.read_be16();
uint16_t wantChecksum = br.read_be16();
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalCylinder = _ltl->logicalCylinder;
_sector->logicalHead = _ltl->logicalHead;
_sector->logicalSector = _currentSector;
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();
_sector->status =

View File

@@ -17,6 +17,7 @@
#include "lib/data/fluxpattern.h"
#include "lib/decoders/decoders.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
#include "arch/northstar/northstar.h"
#include "lib/core/bytes.h"
#include "lib/decoders/decoders.pb.h"
@@ -159,9 +160,9 @@ public:
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
ByteReader br(bytes);
_sector->logicalSide = _sector->physicalSide;
_sector->logicalHead = _ltl->logicalHead;
_sector->logicalSector = _hardSectorId;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalCylinder = _ltl->logicalCylinder;
if (headerSize == NORTHSTAR_HEADER_SIZE_DD)
{

View File

@@ -129,7 +129,7 @@ public:
{
}
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{

View File

@@ -5,6 +5,7 @@
#include "protocol.h"
#include "lib/decoders/decoders.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
#include "arch/smaky6/smaky6.h"
#include "lib/core/bytes.h"
#include "lib/core/crc.h"
@@ -129,11 +130,11 @@ public:
uint8_t wantedChecksum = br.read_8();
uint8_t gotChecksum = sumBytes(data) & 0xff;
if (track != _sector->physicalTrack)
if (track != _ltl->logicalCylinder)
return;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalSide;
_sector->logicalCylinder = _ltl->physicalCylinder;
_sector->logicalHead = _ltl->logicalHead;
_sector->logicalSector = _sectorId;
_sector->data = data;

View File

@@ -43,8 +43,8 @@ public:
ByteReader br(bytes);
uint8_t track = br.read_8();
_sector->logicalTrack = track >> 1;
_sector->logicalSide = track & 1;
_sector->logicalCylinder = track >> 1;
_sector->logicalHead = track & 1;
br.skip(1); /* seems always to be 1 */
_sector->logicalSector = br.read_8();
uint8_t wantChecksum = br.read_8();

View File

@@ -17,7 +17,7 @@ public:
{
}
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
@@ -83,7 +83,7 @@ private:
Bytes bytes;
ByteWriter bw(bytes);
bw.write_8(
(sectorData->logicalTrack << 1) | sectorData->logicalSide);
(sectorData->logicalCylinder << 1) | sectorData->logicalHead);
bw.write_8(1);
bw.write_8(sectorData->logicalSector);
bw.write_8(~sumBytes(bytes.slice(0, 3)));

View File

@@ -64,8 +64,8 @@ public:
uint16_t gotChecksum =
crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE - 3));
_sector->logicalSide = br.read_8() >> 3;
_sector->logicalTrack = br.read_8();
_sector->logicalHead = br.read_8() >> 3;
_sector->logicalCylinder = br.read_8();
br.read_8(); /* number of sectors per track */
_sector->logicalSector = br.read_8();
br.read_be16(); /* sector size */

View File

@@ -59,7 +59,7 @@ private:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
@@ -95,8 +95,8 @@ public:
writeBytes(12, 0x55);
bw.write_8(am1Unencoded);
bw.write_8(sectorData->logicalSide << 3);
bw.write_8(sectorData->logicalTrack);
bw.write_8(sectorData->logicalHead << 3);
bw.write_8(sectorData->logicalCylinder);
bw.write_8(_config.sector_count());
bw.write_8(sectorData->logicalSector);
bw.write_be16(sectorData->data.size());

View File

@@ -80,11 +80,11 @@ public:
_sector->logicalSector = bytes[1];
uint8_t gotChecksum = bytes[2];
_sector->logicalTrack = rawTrack & 0x7f;
_sector->logicalSide = rawTrack >> 7;
_sector->logicalCylinder = rawTrack & 0x7f;
_sector->logicalHead = rawTrack >> 7;
uint8_t wantChecksum = bytes[0] + bytes[1];
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) ||
(_sector->logicalSide > 1))
if ((_sector->logicalSector > 20) || (_sector->logicalCylinder > 85) ||
(_sector->logicalHead > 1))
return;
if (wantChecksum == gotChecksum)

View File

@@ -112,7 +112,7 @@ static void write_sector(std::vector<bool>& bits,
write_one_bits(bits, cursor, trackdata.pre_header_sync_bits());
write_bits(bits, cursor, VICTOR9K_SECTOR_RECORD, 10);
uint8_t encodedTrack = sector.logicalTrack | (sector.logicalSide << 7);
uint8_t encodedTrack = sector.logicalCylinder | (sector.logicalHead << 7);
uint8_t encodedSector = sector.logicalSector;
write_bytes(bits,
cursor,
@@ -164,13 +164,12 @@ private:
}
public:
std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(
trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
getTrackFormat(trackdata, ltl.logicalCylinder, ltl.logicalHead);
unsigned bitsPerRevolution = (trackdata.rotational_period_ms() * 1e3) /
trackdata.clock_period_us();

View File

@@ -34,11 +34,11 @@ public:
ByteReader br(bytes);
_sector->logicalSector = br.read_8() & 0x1f;
_sector->logicalSide = 0;
_sector->logicalTrack = br.read_8() & 0x7f;
_sector->logicalHead = 0;
_sector->logicalCylinder = br.read_8() & 0x7f;
if (_sector->logicalSector > 31)
return;
if (_sector->logicalTrack > 80)
if (_sector->logicalCylinder > 80)
return;
_sector->data = br.read(132);

View File

@@ -104,15 +104,15 @@ export(
name="all",
items={
"fluxengine$(EXT)": "src+fluxengine",
"fluxengine-gui$(EXT)": "src/gui",
"fluxengine-gui$(EXT)": "src/gui2",
"brother120tool$(EXT)": "tools+brother120tool",
"brother240tool$(EXT)": "tools+brother240tool",
"upgrade-flux-file$(EXT)": "tools+upgrade-flux-file",
}
| (
{
"FluxEngine.pkg": "src/gui+fluxengine_pkg",
"FluxEngine.app.zip": "src/gui+fluxengine_app_zip",
"FluxEngine.pkg": "src/gui2+fluxengine_pkg",
"FluxEngine.app.zip": "src/gui2+fluxengine_app_zip",
}
if config.osx
else {}

View File

@@ -70,7 +70,7 @@ define newline
endef
define check_for_command
$(shell command -v $1 >/dev/null || (echo "Required command '$1' missing" >/dev/stderr && kill $$PPID))
$(shell command -v $1 >/dev/null || (echo "Required command '$1' missing" >&2 && kill $$PPID))
endef
$(call check_for_command,ninja)
@@ -84,12 +84,15 @@ build-file-timestamps = $(shell ls -l $(build-files) | md5sum)
# Wipe the build file (forcing a regeneration) if the make environment is different.
# (Conveniently, this includes the pkg-config hash calculated above.)
ignored-variables = MAKE_RESTARTS .VARIABLES MAKECMDGOALS MAKEFLAGS MFLAGS
ignored-variables = MAKE_RESTARTS .VARIABLES MAKECMDGOALS MAKEFLAGS MFLAGS PAGER _ \
DESKTOP_STARTUP_ID XAUTHORITY ICEAUTHORITY SSH_AUTH_SOCK SESSION_MANAGER \
INVOCATION_ID SYSTEMD_EXEC_PID MANAGER_PID SSH_AGENT_PID JOURNAL_STREAM \
GPG_TTY WINDOWID MANAGERPID MAKE_TERMOUT MAKE_TERMERR OLDPWD
$(shell mkdir -p $(OBJ))
$(file >$(OBJ)/newvars.txt,$(foreach v,$(filter-out $(ignored-variables),$(.VARIABLES)),$(v)=$($(v))$(newline)))
$(shell touch $(OBJ)/vars.txt)
#$(shell diff -u $(OBJ)/vars.txt $(OBJ)/newvars.txt > /dev/stderr)
$(shell cmp -s $(OBJ)/newvars.txt $(OBJ)/vars.txt || (rm -f $(OBJ)/build.ninja && echo "Environment changed --- regenerating" > /dev/stderr))
#$(shell diff -u $(OBJ)/vars.txt $(OBJ)/newvars.txt >&2)
$(shell cmp -s $(OBJ)/newvars.txt $(OBJ)/vars.txt || (rm -f $(OBJ)/build.ninja && echo "Environment changed --- regenerating" >&2))
$(shell mv $(OBJ)/newvars.txt $(OBJ)/vars.txt)
.PHONY: update-ab
@@ -104,6 +107,9 @@ clean::
@echo CLEAN
$(hide) rm -rf $(OBJ)
compile_commands.json: $(OBJ)/build.ninja
+$(hide) $(NINJA) -f $(OBJ)/build.ninja -t compdb > $@
export PYTHONHASHSEED = 1
$(OBJ)/build.ninja $(OBJ)/build.targets &:
@echo "AB"

View File

@@ -558,19 +558,23 @@ def emit_rule(self, ins, outs, cmds=[], label=None):
os.makedirs(self.dir, exist_ok=True)
rule = []
sandbox = join(self.dir, "sandbox")
emit(f"rm -rf {sandbox}", into=rule)
emit(
f"{G.PYTHON} build/_sandbox.py --link -s", sandbox, *fins, into=rule
)
for c in cmds:
emit(f"(cd {sandbox} &&", c, ")", into=rule)
emit(
f"{G.PYTHON} build/_sandbox.py --export -s",
sandbox,
*fouts,
into=rule,
)
if G.AB_SANDBOX == "yes":
sandbox = join(self.dir, "sandbox")
emit(f"rm -rf {sandbox}", into=rule)
emit(
f"{G.PYTHON} build/_sandbox.py --link -s", sandbox, *fins, into=rule
)
for c in cmds:
emit(f"(cd {sandbox} &&", c, ")", into=rule)
emit(
f"{G.PYTHON} build/_sandbox.py --export -s",
sandbox,
*fouts,
into=rule,
)
else:
for c in cmds:
emit(c, into=rule)
ruletext = "".join(rule)
if len(ruletext) > 7000:
@@ -581,7 +585,7 @@ def emit_rule(self, ins, outs, cmds=[], label=None):
fp.write("set -e\n")
fp.write(ruletext)
emit("build", *fouts, ":rule", *fins, rulef)
emit("build", *fouts, ":rule", *fins)
emit(" command=sh", rulef)
else:
emit("build", *fouts, ":rule", *fins)
@@ -696,6 +700,7 @@ def main():
if "=" in line:
name, value = line.split("=", 1)
G.setdefault(name.strip(), value.strip())
G.setdefault("AB_SANDBOX", "yes")
global ninjaFp, shellFp, outputdir
outputdir = args.outputdir

View File

@@ -4,11 +4,13 @@ from os.path import join, abspath, dirname, relpath
from build.pkg import has_package
G.setdefault("PROTOC", "protoc")
G.setdefault("PROTOC_SEPARATOR", ":")
G.setdefault("HOSTPROTOC", "hostprotoc")
assert has_package("protobuf"), "required package 'protobuf' not installed"
def _getprotodeps(deps):
r = set()
for d in deps:
@@ -19,7 +21,7 @@ def _getprotodeps(deps):
@Rule
def proto(self, name, srcs: Targets = [], deps: Targets = []):
protodeps = _getprotodeps(deps)
descriptorlist = ":".join(
descriptorlist = (G.PROTOC_SEPARATOR).join(
[
relpath(f, start=self.dir)
for f in filenamesmatchingof(protodeps, "*.descriptor")
@@ -46,7 +48,7 @@ def proto(self, name, srcs: Targets = [], deps: Targets = []):
f"--descriptor_set_out={self.localname}.descriptor",
]
+ (
[f"--descriptor_set_in={descriptorlist}"]
[f"--descriptor_set_in='{descriptorlist}'"]
if descriptorlist
else []
)
@@ -89,7 +91,7 @@ def protocc(self, name, srcs: Targets = [], deps: Targets = []):
outs += ["=" + cc, "=" + h]
protodeps = _getprotodeps(deps + srcs)
descriptorlist = ":".join(
descriptorlist = G.PROTOC_SEPARATOR.join(
[
relpath(f, start=self.dir)
for f in filenamesmatchingof(protodeps, "*.descriptor")
@@ -110,7 +112,7 @@ def protocc(self, name, srcs: Targets = [], deps: Targets = []):
"$(PROTOC)",
"--proto_path=.",
"--cpp_out=.",
f"--descriptor_set_in={descriptorlist}",
f"--descriptor_set_in='{descriptorlist}'",
]
+ protos
)

1
dep/cli11 Submodule

Submodule dep/cli11 added at 89dc726939

1
dep/imgui Submodule

Submodule dep/imgui added at 4d216d4510

1
dep/imhex Submodule

Submodule dep/imhex added at a76eae2c11

1
dep/libromfs Submodule

Submodule dep/libromfs added at fa444f2995

1
dep/libwolv Submodule

Submodule dep/libwolv added at 56f77945fe

1
dep/lunasvg Submodule

Submodule dep/lunasvg added at 83c58df810

1
dep/md4c Submodule

Submodule dep/md4c added at 481fbfbdf7

1
dep/nlohmann_json Submodule

Submodule dep/nlohmann_json added at 44bee1b138

1
dep/pattern-language Submodule

Submodule dep/pattern-language added at f97999d4da

1
dep/throwing_ptr Submodule

Submodule dep/throwing_ptr added at cd28490ebf

1
dep/xdgpp Submodule

Submodule dep/xdgpp added at f01f810714

47
doc/disk-juku.md Normal file
View File

@@ -0,0 +1,47 @@
juku
====
## CP/M
<!-- This file is automatically generated. Do not edit. -->
Juku E5104 is an Estonian school computer from late 1980s and
early 1990s. It was designed by EKTA in 1985, and starting
from 1988 produced in Narva "Baltijets" factory. Arguably
the school computer was technically outdated already when
released, but still occupies a precious spot in the memories
of a whole generation of Estonian IT professionals.
The system uses dual 5.25 inch 2ce9
diskette drive with regular MFM encoded DSDD. The disks have
a sector skew factor 2 and tracks are written on one side of
the floppy until it is full and then continued on the other
side, starting from the outside of the disk again. This differs
from the most common alternating sides method and somewhat
complicates reading CP/M filesystem content with common tools.
Mostly 800kB (786kB) DSDD disks were used, but there are also
400kB (386kB) SSDD floppies in circulation.
## References (all in Estonian)
- [How to read/write Juku disk images?](https://j3k.infoaed.ee/kettad/)
- [List of recovered Juku software](https://j3k.infoaed.ee/tarkvara-kataloog/)
- [System disks for E5104](https://elektroonikamuuseum.ee/juku_arvuti_tarkvara.html)
## Options
- Format variants:
- `800`: 800kB 80-track 10-sector DSDD
- `400`: 400kB 80-track 10-sector SSDD
## Examples
To read:
- `fluxengine read -c juku --800 -s drive:0 -o image.juk`
- `fluxengine read -c juku --400 -s drive:0 -o image.juk`
To write:
- `fluxengine write -c juku --800 -d drive:0 -i image.juk`
- `fluxengine write -c juku --400 -d drive:0 -i image.juk`

View File

@@ -52,7 +52,7 @@ need to apply extra options to change the format if desired.
## Options
- :
- $format:
- `143`: 143kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod I
- `287`: 287kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod I
- `315`: 315kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod II

26
doc/disk-ti99.md Normal file
View File

@@ -0,0 +1,26 @@
ti99
====
## 90kB 35-track SSSD
<!-- This file is automatically generated. Do not edit. -->
The TI-99 was a deeply weird microcomputer from 1981, whose main claim to fame
was being built around a 16-bit TMS9900 CPU --- and also having only 256 bytes
of system RAM, with an additional 16kB of video RAM, requiring the BASIC to
store the user's program in video RAM.
It had an optional rack-mount expansion system with an optional disk drive. This
was controlled by a standard FD1771 or FD179x chip, meaning a relatively normal
IBM-scheme disk format of 35 tracks containing nine 256-byte sectors.
FluxEngine can read these.
## Options
(no options)
## Examples
To read:
- `fluxengine read -c ti99 -s drive:0 -o ti99.img`

View File

@@ -385,9 +385,8 @@ disks, and have different magnetic properties. 3.5" drives can usually
autodetect what kind of medium is inserted into the drive based on the hole in
the disk casing, but 5.25" drives can't. As a result, you need to explicitly
tell FluxEngine on the command line whether you're using a high density disk or
not with the `--drive.high_density` configuration setting.
**If you don't do this, your disks may not read correctly and will _certainly_
fail to write correctly.**
not with the `--hd` configuration setting. **If you don't do this, your disks
may not read correctly and will _certainly_ fail to write correctly.**
You can distinguish high density 5.25" floppies from the presence of a
traction ring around the hole in the middle of the disk; if the ring is not

View File

@@ -84,16 +84,12 @@ void renderLogMessage(
void renderLogMessage(
LogRenderer& r, std::shared_ptr<const TrackReadLogMessage> m)
{
const auto& track = *m->track;
std::set<std::shared_ptr<const Sector>> rawSectors;
std::set<std::shared_ptr<const Record>> rawRecords;
for (const auto& trackDataFlux : track.trackDatas)
for (const auto& track : m->tracks)
{
rawSectors.insert(
trackDataFlux->sectors.begin(), trackDataFlux->sectors.end());
rawRecords.insert(
trackDataFlux->records.begin(), trackDataFlux->records.end());
rawSectors.insert(track->allSectors.begin(), track->allSectors.end());
rawRecords.insert(track->records.begin(), track->records.end());
}
nanoseconds_t clock = 0;
@@ -114,22 +110,22 @@ void renderLogMessage(
r.newline().add("sectors:");
std::vector<std::shared_ptr<const Sector>> sectors(
track.sectors.begin(), track.sectors.end());
m->sectors.begin(), m->sectors.end());
std::sort(sectors.begin(), sectors.end(), sectorPointerSortPredicate);
for (const auto& sector : sectors)
for (const auto& sector : rawSectors)
r.add(fmt::format("{}.{}.{}{}",
sector->logicalTrack,
sector->logicalSide,
sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector,
Sector::statusToChar(sector->status)));
int size = 0;
std::set<std::pair<int, int>> track_ids;
for (const auto& sector : m->track->sectors)
for (const auto& sector : m->sectors)
{
track_ids.insert(
std::make_pair(sector->logicalTrack, sector->logicalSide));
std::make_pair(sector->logicalCylinder, sector->logicalHead));
size += sector->data.size();
}
@@ -184,12 +180,16 @@ private:
_cache;
};
void measureDiskRotation()
static nanoseconds_t getRotationalPeriodFromConfig()
{
return globalConfig()->drive().rotational_period_ms() * 1e6;
}
static nanoseconds_t measureDiskRotation()
{
log(BeginSpeedOperationLogMessage());
nanoseconds_t oneRevolution =
globalConfig()->drive().rotational_period_ms() * 1e6;
nanoseconds_t oneRevolution = getRotationalPeriodFromConfig();
if (oneRevolution == 0)
{
usbSetDrive(globalConfig()->drive().drive(),
@@ -224,22 +224,24 @@ void measureDiskRotation()
error("Failed\nIs a disk in the drive?");
log(EndSpeedOperationLogMessage{oneRevolution});
return oneRevolution;
}
/* Given a set of sectors, deduplicates them sensibly (e.g. if there is a good
* and bad version of the same sector, the bad version is dropped). */
static std::set<std::shared_ptr<const Sector>> collectSectors(
std::set<std::shared_ptr<const Sector>>& track_sectors,
static std::vector<std::shared_ptr<const Sector>> collectSectors(
std::vector<std::shared_ptr<const Sector>>& trackSectors,
bool collapse_conflicts = true)
{
typedef std::tuple<unsigned, unsigned, unsigned> key_t;
std::multimap<key_t, std::shared_ptr<const Sector>> sectors;
for (const auto& sector : track_sectors)
for (const auto& sector : trackSectors)
{
key_t sectorid = {
sector->logicalTrack, sector->logicalSide, sector->logicalSector};
key_t sectorid = {sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector};
sectors.insert({sectorid, sector});
}
@@ -281,42 +283,52 @@ static std::set<std::shared_ptr<const Sector>> collectSectors(
sector_set.insert(new_sector);
it = ub;
}
return sector_set;
return sector_set | std::ranges::to<std::vector>();
}
BadSectorsState combineRecordAndSectors(TrackFlux& trackFlux,
Decoder& decoder,
std::shared_ptr<const TrackInfo>& trackLayout)
struct CombinationResult
{
std::set<std::shared_ptr<const Sector>> track_sectors;
BadSectorsState result;
std::vector<std::shared_ptr<const Sector>> sectors;
};
static CombinationResult combineRecordAndSectors(
std::vector<std::shared_ptr<const Track>>& tracks,
Decoder& decoder,
const std::shared_ptr<const LogicalTrackLayout>& ltl)
{
CombinationResult cr = {HAS_NO_BAD_SECTORS};
std::vector<std::shared_ptr<const Sector>> track_sectors;
/* Add the sectors which were there. */
for (auto& trackdataflux : trackFlux.trackDatas)
track_sectors.insert(
trackdataflux->sectors.begin(), trackdataflux->sectors.end());
for (auto& track : tracks)
for (auto& sector : track->allSectors)
track_sectors.push_back(sector);
/* Add the sectors which should be there. */
for (unsigned sectorId : trackLayout->naturalSectorOrder)
for (unsigned sectorId : ltl->diskSectorOrder)
{
auto sector = std::make_shared<Sector>(LogicalLocation{
trackLayout->logicalTrack, trackLayout->logicalSide, sectorId});
auto sector = std::make_shared<Sector>(
LogicalLocation{ltl->logicalCylinder, ltl->logicalHead, sectorId});
sector->status = Sector::MISSING;
track_sectors.insert(sector);
sector->physicalLocation = std::make_optional(
CylinderHead(ltl->physicalCylinder, ltl->physicalHead));
track_sectors.push_back(sector);
}
/* Deduplicate. */
trackFlux.sectors = collectSectors(track_sectors);
if (trackFlux.sectors.empty())
return HAS_BAD_SECTORS;
for (const auto& sector : trackFlux.sectors)
cr.sectors = collectSectors(track_sectors);
if (cr.sectors.empty())
cr.result = HAS_BAD_SECTORS;
for (const auto& sector : cr.sectors)
if (sector->status != Sector::OK)
return HAS_BAD_SECTORS;
cr.result = HAS_BAD_SECTORS;
return HAS_NO_BAD_SECTORS;
return cr;
}
static void adjustTrackOnError(FluxSource& fluxSource, int baseTrack)
@@ -339,179 +351,226 @@ static void adjustTrackOnError(FluxSource& fluxSource, int baseTrack)
}
}
ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder,
std::shared_ptr<const TrackInfo>& trackInfo,
TrackFlux& trackFlux,
struct ReadGroupResult
{
ReadResult result;
std::vector<std::shared_ptr<const Sector>> combinedSectors;
};
static ReadGroupResult readGroup(const DiskLayout& diskLayout,
FluxSourceIteratorHolder& fluxSourceIteratorHolder,
const std::shared_ptr<const LogicalTrackLayout>& ltl,
std::vector<std::shared_ptr<const Track>>& tracks,
Decoder& decoder)
{
ReadResult result = BAD_AND_CAN_NOT_RETRY;
ReadGroupResult rgr = {BAD_AND_CAN_NOT_RETRY};
/* Before doing the read, look to see if we already have the necessary
* sectors. */
for (unsigned offset = 0; offset < trackInfo->groupSize;
offset += Layout::getHeadWidth())
{
log(BeginReadOperationLogMessage{
trackInfo->physicalTrack + offset, trackInfo->physicalSide});
auto [result, sectors] = combineRecordAndSectors(tracks, decoder, ltl);
rgr.combinedSectors = sectors;
if (result == HAS_NO_BAD_SECTORS)
{
/* We have all necessary sectors, so can stop here. */
rgr.result = GOOD_READ;
if (globalConfig()->decoder().skip_unnecessary_tracks())
return rgr;
}
}
for (unsigned offset = 0; offset < ltl->groupSize;
offset += diskLayout.headWidth)
{
unsigned physicalCylinder = ltl->physicalCylinder + offset;
unsigned physicalHead = ltl->physicalHead;
auto& ptl = diskLayout.layoutByPhysicalLocation.at(
{physicalCylinder, physicalHead});
/* Do the physical read. */
log(BeginReadOperationLogMessage{physicalCylinder, physicalHead});
auto& fluxSourceIterator = fluxSourceIteratorHolder.getIterator(
trackInfo->physicalTrack + offset, trackInfo->physicalSide);
physicalCylinder, physicalHead);
if (!fluxSourceIterator.hasNext())
continue;
std::shared_ptr<const Fluxmap> fluxmap = fluxSourceIterator.next();
// ->rescale(
// 1.0 / globalConfig()->flux_source().rescale());
auto fluxmap = fluxSourceIterator.next();
log(EndReadOperationLogMessage());
log("{0} ms in {1} bytes",
(int)(fluxmap->duration() / 1e6),
fluxmap->bytes());
auto trackdataflux = decoder.decodeToSectors(fluxmap, trackInfo);
trackFlux.trackDatas.push_back(trackdataflux);
if (combineRecordAndSectors(trackFlux, decoder, trackInfo) ==
HAS_NO_BAD_SECTORS)
auto flux = decoder.decodeToSectors(std::move(fluxmap), ptl);
flux->normalisedSectors = collectSectors(flux->allSectors);
tracks.push_back(flux);
/* Decode what we've got so far. */
auto [result, sectors] = combineRecordAndSectors(tracks, decoder, ltl);
rgr.combinedSectors = sectors;
if (result == HAS_NO_BAD_SECTORS)
{
result = GOOD_READ;
/* We have all necessary sectors, so can stop here. */
rgr.result = GOOD_READ;
if (globalConfig()->decoder().skip_unnecessary_tracks())
return result;
break;
}
else if (fluxSourceIterator.hasNext())
result = BAD_AND_CAN_RETRY;
{
/* The flux source claims it can do more reads, so mark this
* group as being retryable. */
rgr.result = BAD_AND_CAN_RETRY;
}
}
return result;
return rgr;
}
void writeTracks(FluxSink& fluxSink,
void writeTracks(const DiskLayout& diskLayout,
FluxSinkFactory& fluxSinkFactory,
std::function<std::unique_ptr<const Fluxmap>(
std::shared_ptr<const TrackInfo>& trackInfo)> producer,
std::function<bool(std::shared_ptr<const TrackInfo>& trackInfo)> verifier,
std::vector<std::shared_ptr<const TrackInfo>>& trackInfos)
const std::shared_ptr<const LogicalTrackLayout>&)> producer,
std::function<bool(const std::shared_ptr<const LogicalTrackLayout>&)>
verifier,
const std::vector<CylinderHead>& logicalLocations)
{
log(BeginOperationLogMessage{"Encoding and writing to disk"});
if (fluxSink.isHardware())
if (fluxSinkFactory.isHardware())
measureDiskRotation();
int index = 0;
for (auto& trackInfo : trackInfos)
{
log(OperationProgressLogMessage{
index * 100 / (unsigned)trackInfos.size()});
index++;
testForEmergencyStop();
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
auto fluxSink = fluxSinkFactory.create();
int index = 0;
for (auto& ch : logicalLocations)
{
for (int offset = 0; offset < trackInfo->groupSize;
offset += Layout::getHeadWidth())
log(OperationProgressLogMessage{
index * 100 / (unsigned)logicalLocations.size()});
index++;
testForEmergencyStop();
const auto& ltl = diskLayout.layoutByLogicalLocation.at(ch);
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
{
unsigned physicalTrack = trackInfo->physicalTrack + offset;
log(BeginWriteOperationLogMessage{
physicalTrack, trackInfo->physicalSide});
if (offset == globalConfig()->drive().group_offset())
for (int offset = 0; offset < ltl->groupSize;
offset += diskLayout.headWidth)
{
auto fluxmap = producer(trackInfo);
if (!fluxmap)
goto erase;
unsigned physicalCylinder = ltl->physicalCylinder + offset;
unsigned physicalHead = ltl->physicalHead;
fluxSink.writeFlux(
physicalTrack, trackInfo->physicalSide, *fluxmap);
log("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
log(BeginWriteOperationLogMessage{
physicalCylinder, ltl->physicalHead});
Fluxmap blank;
fluxSink.writeFlux(
physicalTrack, trackInfo->physicalSide, blank);
log("erased");
if (offset == globalConfig()->drive().group_offset())
{
auto fluxmap = producer(ltl);
if (!fluxmap)
goto erase;
fluxSink->addFlux(
physicalCylinder, physicalHead, *fluxmap);
log("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
Fluxmap blank;
fluxSink->addFlux(
physicalCylinder, physicalHead, blank);
log("erased");
}
log(EndWriteOperationLogMessage());
}
log(EndWriteOperationLogMessage());
if (verifier(ltl))
break;
if (retriesRemaining == 0)
error("fatal error on write");
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
if (verifier(trackInfo))
break;
if (retriesRemaining == 0)
error("fatal error on write");
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
}
log(EndOperationLogMessage{"Write complete"});
}
void writeTracks(FluxSink& fluxSink,
void writeTracks(const DiskLayout& diskLayout,
FluxSinkFactory& fluxSinkFactory,
Encoder& encoder,
const Image& image,
std::vector<std::shared_ptr<const TrackInfo>>& trackInfos)
const std::vector<CylinderHead>& chs)
{
writeTracks(
fluxSink,
[&](std::shared_ptr<const TrackInfo>& trackInfo)
diskLayout,
fluxSinkFactory,
[&](const std::shared_ptr<const LogicalTrackLayout>& ltl)
{
auto sectors = encoder.collectSectors(trackInfo, image);
return encoder.encode(trackInfo, sectors, image);
auto sectors = encoder.collectSectors(*ltl, image);
return encoder.encode(*ltl, sectors, image);
},
[](const auto&)
{
return true;
},
trackInfos);
chs);
}
void writeTracksAndVerify(FluxSink& fluxSink,
void writeTracksAndVerify(const DiskLayout& diskLayout,
FluxSinkFactory& fluxSinkFactory,
Encoder& encoder,
FluxSource& fluxSource,
Decoder& decoder,
const Image& image,
std::vector<std::shared_ptr<const TrackInfo>>& trackInfos)
const std::vector<CylinderHead>& chs)
{
writeTracks(
fluxSink,
[&](std::shared_ptr<const TrackInfo>& trackInfo)
diskLayout,
fluxSinkFactory,
[&](const std::shared_ptr<const LogicalTrackLayout>& ltl)
{
auto sectors = encoder.collectSectors(trackInfo, image);
return encoder.encode(trackInfo, sectors, image);
auto sectors = encoder.collectSectors(*ltl, image);
return encoder.encode(*ltl, sectors, image);
},
[&](std::shared_ptr<const TrackInfo>& trackInfo)
[&](const std::shared_ptr<const LogicalTrackLayout>& ltl)
{
auto trackFlux = std::make_shared<TrackFlux>();
trackFlux->trackInfo = trackInfo;
FluxSourceIteratorHolder fluxSourceIteratorHolder(fluxSource);
auto result = readGroup(
fluxSourceIteratorHolder, trackInfo, *trackFlux, decoder);
log(TrackReadLogMessage{trackFlux});
std::vector<std::shared_ptr<const Track>> tracks;
auto [result, sectors] = readGroup(
diskLayout, fluxSourceIteratorHolder, ltl, tracks, decoder);
log(TrackReadLogMessage{tracks, sectors});
if (result != GOOD_READ)
{
adjustTrackOnError(fluxSource, trackInfo->physicalTrack);
adjustTrackOnError(fluxSource, ltl->physicalCylinder);
log("bad read");
return false;
}
Image wanted;
for (const auto& sector : encoder.collectSectors(trackInfo, image))
for (const auto& sector : encoder.collectSectors(*ltl, image))
wanted
.put(sector->logicalTrack,
sector->logicalSide,
.put(sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector)
->data = sector->data;
for (const auto& sector : trackFlux->sectors)
for (const auto& sector : sectors)
{
const auto s = wanted.get(sector->logicalTrack,
sector->logicalSide,
const auto s = wanted.get(sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector);
if (!s)
{
@@ -523,8 +582,8 @@ void writeTracksAndVerify(FluxSink& fluxSink,
log("data mismatch on verify");
return false;
}
wanted.erase(sector->logicalTrack,
sector->logicalSide,
wanted.erase(sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector);
}
if (!wanted.empty())
@@ -534,60 +593,75 @@ void writeTracksAndVerify(FluxSink& fluxSink,
}
return true;
},
trackInfos);
chs);
}
void writeDiskCommand(const Image& image,
void writeDiskCommand(const DiskLayout& diskLayout,
const Image& image,
Encoder& encoder,
FluxSink& fluxSink,
FluxSinkFactory& fluxSinkFactory,
Decoder* decoder,
FluxSource* fluxSource,
const std::vector<CylinderHead>& physicalLocations)
{
auto trackinfos = Layout::getLayoutOfTracksPhysical(physicalLocations);
auto chs = std::ranges::views::keys(diskLayout.layoutByLogicalLocation) |
std::ranges::to<std::vector>();
if (fluxSource && decoder)
writeTracksAndVerify(
fluxSink, encoder, *fluxSource, *decoder, image, trackinfos);
writeTracksAndVerify(diskLayout,
fluxSinkFactory,
encoder,
*fluxSource,
*decoder,
image,
chs);
else
writeTracks(fluxSink, encoder, image, trackinfos);
writeTracks(diskLayout, fluxSinkFactory, encoder, image, chs);
}
void writeDiskCommand(const Image& image,
void writeDiskCommand(const DiskLayout& diskLayout,
const Image& image,
Encoder& encoder,
FluxSink& fluxSink,
FluxSinkFactory& fluxSinkFactory,
Decoder* decoder,
FluxSource* fluxSource)
{
auto locations = Layout::computePhysicalLocations();
writeDiskCommand(image, encoder, fluxSink, decoder, fluxSource, locations);
writeDiskCommand(diskLayout,
image,
encoder,
fluxSinkFactory,
decoder,
fluxSource,
std::ranges::views::keys(diskLayout.layoutByLogicalLocation) |
std::ranges::to<std::vector>());
}
void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
void writeRawDiskCommand(const DiskLayout& diskLayout,
FluxSource& fluxSource,
FluxSinkFactory& fluxSinkFactory)
{
auto physicalLocations = Layout::computePhysicalLocations();
auto trackinfos = Layout::getLayoutOfTracksPhysical(physicalLocations);
writeTracks(
fluxSink,
[&](std::shared_ptr<const TrackInfo>& trackInfo)
diskLayout,
fluxSinkFactory,
[&](const std::shared_ptr<const LogicalTrackLayout>& ltl)
{
return fluxSource
.readFlux(trackInfo->physicalTrack, trackInfo->physicalSide)
.readFlux(ltl->physicalCylinder, ltl->physicalHead)
->next();
},
[](const auto&)
{
return true;
},
trackinfos);
diskLayout.logicalLocations);
}
std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
void readAndDecodeTrack(const DiskLayout& diskLayout,
FluxSource& fluxSource,
Decoder& decoder,
std::shared_ptr<const TrackInfo>& trackInfo)
const std::shared_ptr<const LogicalTrackLayout>& ltl,
std::vector<std::shared_ptr<const Track>>& tracks,
std::vector<std::shared_ptr<const Sector>>& combinedSectors)
{
auto trackFlux = std::make_shared<TrackFlux>();
trackFlux->trackInfo = trackInfo;
if (fluxSource.isHardware())
measureDiskRotation();
@@ -595,8 +669,9 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
{
auto result =
readGroup(fluxSourceIteratorHolder, trackInfo, *trackFlux, decoder);
auto [result, sectors] = readGroup(
diskLayout, fluxSourceIteratorHolder, ltl, tracks, decoder);
combinedSectors = sectors;
if (result == GOOD_READ)
break;
if (result == BAD_AND_CAN_NOT_RETRY)
@@ -613,131 +688,172 @@ std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
if (fluxSource.isHardware())
{
adjustTrackOnError(fluxSource, trackInfo->physicalTrack);
adjustTrackOnError(fluxSource, ltl->physicalCylinder);
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
}
return trackFlux;
}
std::shared_ptr<const DiskFlux> readDiskCommand(
FluxSource& fluxSource, Decoder& decoder)
void readDiskCommand(const DiskLayout& diskLayout,
FluxSource& fluxSource,
Decoder& decoder,
Disk& disk)
{
std::unique_ptr<FluxSink> outputFluxSink;
std::unique_ptr<FluxSinkFactory> outputFluxSinkFactory;
if (globalConfig()->decoder().has_copy_flux_to())
outputFluxSink =
FluxSink::create(globalConfig()->decoder().copy_flux_to());
outputFluxSinkFactory =
FluxSinkFactory::create(globalConfig()->decoder().copy_flux_to());
auto diskflux = std::make_shared<DiskFlux>();
std::map<CylinderHead, std::vector<std::shared_ptr<const Track>>>
tracksByLogicalLocation;
for (auto& [ch, track] : disk.tracksByPhysicalLocation)
tracksByLogicalLocation[CylinderHead(track->ltl->logicalCylinder,
track->ltl->logicalHead)]
.push_back(track);
log(BeginOperationLogMessage{"Reading and decoding disk"});
auto physicalLocations = Layout::computePhysicalLocations();
unsigned index = 0;
for (auto& physicalLocation : physicalLocations)
if (fluxSource.isHardware())
disk.rotationalPeriod = measureDiskRotation();
else
disk.rotationalPeriod = getRotationalPeriodFromConfig();
{
auto trackInfo = Layout::getLayoutOfTrackPhysical(
physicalLocation.cylinder, physicalLocation.head);
log(OperationProgressLogMessage{
index * 100 / (unsigned)physicalLocations.size()});
index++;
testForEmergencyStop();
auto trackFlux = readAndDecodeTrack(fluxSource, decoder, trackInfo);
diskflux->tracks.push_back(trackFlux);
if (outputFluxSink)
std::unique_ptr<FluxSink> outputFluxSink;
if (outputFluxSinkFactory)
outputFluxSink = outputFluxSinkFactory->create();
unsigned index = 0;
for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
{
for (const auto& data : trackFlux->trackDatas)
outputFluxSink->writeFlux(trackInfo->physicalTrack,
trackInfo->physicalSide,
*data->fluxmap);
}
log(OperationProgressLogMessage{
index * 100 /
(unsigned)diskLayout.layoutByLogicalLocation.size()});
index++;
if (globalConfig()->decoder().dump_records())
{
std::vector<std::shared_ptr<const Record>> sorted_records;
testForEmergencyStop();
for (const auto& data : trackFlux->trackDatas)
sorted_records.insert(sorted_records.end(),
data->records.begin(),
data->records.end());
auto& trackFluxes = tracksByLogicalLocation[logicalLocation];
std::vector<std::shared_ptr<const Sector>> trackSectors;
readAndDecodeTrack(diskLayout,
fluxSource,
decoder,
ltl,
trackFluxes,
trackSectors);
std::sort(sorted_records.begin(),
sorted_records.end(),
[](const auto& o1, const auto& o2)
{
return o1->startTime < o2->startTime;
});
/* Replace all tracks on the disk by the new combined set. */
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
for (const auto& flux : trackFluxes)
disk.tracksByPhysicalLocation.erase(CylinderHead{
flux->ptl->physicalCylinder, flux->ptl->physicalHead});
for (const auto& flux : trackFluxes)
disk.tracksByPhysicalLocation.emplace(
CylinderHead{
flux->ptl->physicalCylinder, flux->ptl->physicalHead},
flux);
/* Likewise for sectors. */
for (const auto& sector : trackSectors)
disk.sectorsByPhysicalLocation.erase(
sector->physicalLocation.value());
for (const auto& sector : trackSectors)
disk.sectorsByPhysicalLocation.emplace(
sector->physicalLocation.value(), sector);
if (outputFluxSink)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
for (const auto& data : trackFluxes)
outputFluxSink->addFlux(data->ptl->physicalCylinder,
data->ptl->physicalHead,
*data->fluxmap);
}
}
if (globalConfig()->decoder().dump_sectors())
{
auto collected_sectors = collectSectors(trackFlux->sectors, false);
std::vector<std::shared_ptr<const Sector>> sorted_sectors(
collected_sectors.begin(), collected_sectors.end());
std::sort(sorted_sectors.begin(),
sorted_sectors.end(),
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sorted_sectors)
if (globalConfig()->decoder().dump_records())
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalTrack,
sector->logicalSide,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
}
}
std::vector<std::shared_ptr<const Record>> sorted_records;
/* track can't be modified below this point. */
log(TrackReadLogMessage{trackFlux});
for (const auto& data : trackFluxes)
sorted_records.insert(sorted_records.end(),
data->records.begin(),
data->records.end());
std::sort(sorted_records.begin(),
sorted_records.end(),
[](const auto& o1, const auto& o2)
{
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
}
}
if (globalConfig()->decoder().dump_sectors())
{
auto sectors = collectSectors(trackSectors, false);
std::ranges::sort(sectors,
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sectors)
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
}
}
/* track can't be modified below this point. */
log(TrackReadLogMessage{trackFluxes, trackSectors});
std::vector<std::shared_ptr<const Sector>> all_sectors;
for (auto& [ch, sector] : disk.sectorsByPhysicalLocation)
all_sectors.push_back(sector);
all_sectors = collectSectors(all_sectors);
disk.image = std::make_shared<Image>(all_sectors);
/* Log a _copy_ of the disk structure so that the logger
* doesn't see the disk get mutated in subsequent reads. */
log(DiskReadLogMessage{std::make_shared<Disk>(disk)});
}
}
std::set<std::shared_ptr<const Sector>> all_sectors;
for (auto& track : diskflux->tracks)
for (auto& sector : track->sectors)
all_sectors.insert(sector);
all_sectors = collectSectors(all_sectors);
diskflux->image = std::make_shared<Image>(all_sectors);
if (!disk.image)
disk.image = std::make_shared<Image>();
/* diskflux can't be modified below this point. */
log(DiskReadLogMessage{diskflux});
log(EndOperationLogMessage{"Read complete"});
return diskflux;
}
void readDiskCommand(
FluxSource& fluxsource, Decoder& decoder, ImageWriter& writer)
void readDiskCommand(const DiskLayout& diskLayout,
FluxSource& fluxSource,
Decoder& decoder,
ImageWriter& writer)
{
auto diskflux = readDiskCommand(fluxsource, decoder);
Disk disk;
readDiskCommand(diskLayout, fluxSource, decoder, disk);
writer.printMap(*diskflux->image);
writer.printMap(*disk.image);
if (globalConfig()->decoder().has_write_csv_to())
writer.writeCsv(
*diskflux->image, globalConfig()->decoder().write_csv_to());
writer.writeImage(*diskflux->image);
writer.writeCsv(*disk.image, globalConfig()->decoder().write_csv_to());
writer.writeImage(*disk.image);
}

View File

@@ -3,19 +3,20 @@
#include "lib/data/locations.h"
class Disk;
class Track;
class Decoder;
class DiskLayout;
class Encoder;
class DiskFlux;
class FluxSink;
class FluxSinkFactory;
class FluxSource;
class FluxSourceIteratorHolder;
class Fluxmap;
class Image;
class ImageReader;
class ImageWriter;
class TrackInfo;
class TrackFlux;
class TrackDataFlux;
class LogicalTrackLayout;
class PhysicalTrackLayout;
class Sector;
struct BeginSpeedOperationLogMessage
@@ -29,12 +30,13 @@ struct EndSpeedOperationLogMessage
struct TrackReadLogMessage
{
std::shared_ptr<const TrackFlux> track;
std::vector<std::shared_ptr<const Track>> tracks;
std::vector<std::shared_ptr<const Sector>> sectors;
};
struct DiskReadLogMessage
{
std::shared_ptr<const DiskFlux> disk;
std::shared_ptr<const Disk> disk;
};
struct BeginReadOperationLogMessage
@@ -45,7 +47,7 @@ struct BeginReadOperationLogMessage
struct EndReadOperationLogMessage
{
std::shared_ptr<const TrackDataFlux> trackDataFlux;
std::shared_ptr<const Track> trackDataFlux;
std::set<std::shared_ptr<const Sector>> sectors;
};
@@ -74,42 +76,56 @@ struct OperationProgressLogMessage
unsigned progress;
};
extern void measureDiskRotation();
extern void writeTracks(FluxSink& fluxSink,
extern void writeTracks(const DiskLayout& diskLayout,
FluxSinkFactory& fluxSinkFactory,
const std::function<std::unique_ptr<const Fluxmap>(
std::shared_ptr<const TrackInfo>& layout)> producer,
std::vector<std::shared_ptr<const TrackInfo>>& locations);
const LogicalTrackLayout& ltl)> producer,
const std::vector<CylinderHead>& locations);
extern void writeTracksAndVerify(FluxSink& fluxSink,
extern void writeTracksAndVerify(const DiskLayout& diskLayout,
FluxSinkFactory& fluxSinkFactory,
Encoder& encoder,
FluxSource& fluxSource,
Decoder& decoder,
const Image& image,
std::vector<std::shared_ptr<const TrackInfo>>& locations);
const std::vector<CylinderHead>& locations);
extern void writeDiskCommand(const Image& image,
extern void writeDiskCommand(const DiskLayout& diskLayout,
const Image& image,
Encoder& encoder,
FluxSink& fluxSink,
FluxSinkFactory& fluxSinkFactory,
Decoder* decoder,
FluxSource* fluxSource,
const std::vector<CylinderHead>& locations);
extern void writeDiskCommand(const Image& image,
extern void writeDiskCommand(const DiskLayout& diskLayout,
const Image& image,
Encoder& encoder,
FluxSink& fluxSink,
FluxSinkFactory& fluxSinkFactory,
Decoder* decoder = nullptr,
FluxSource* fluxSource = nullptr);
extern void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink);
extern void writeRawDiskCommand(const DiskLayout& diskLayout,
FluxSource& fluxSource,
FluxSinkFactory& fluxSinkFactory);
extern std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
/* Reads a single group of tracks. tracks and combinedSectors are populated.
* tracks may contain preexisting data which will be taken into account. */
extern void readAndDecodeTrack(const DiskLayout& diskLayout,
FluxSource& fluxSource,
Decoder& decoder,
std::shared_ptr<const TrackInfo>& layout);
const std::shared_ptr<const LogicalTrackLayout>& ltl,
std::vector<std::shared_ptr<const Track>>& tracks,
std::vector<std::shared_ptr<const Sector>>& combinedSectors);
extern std::shared_ptr<const DiskFlux> readDiskCommand(
FluxSource& fluxsource, Decoder& decoder);
extern void readDiskCommand(
FluxSource& source, Decoder& decoder, ImageWriter& writer);
extern void readDiskCommand(const DiskLayout& diskLayout,
FluxSource& fluxSource,
Decoder& decoder,
Disk& disk);
extern void readDiskCommand(const DiskLayout& diskLayout,
FluxSource& source,
Decoder& decoder,
ImageWriter& writer);
#endif

View File

@@ -211,6 +211,13 @@ void Config::clear()
_appliedOptions.clear();
}
static std::string getValidValues(const OptionGroupProto& group)
{
return fmt::format("{}",
fmt::join(
std::views::transform(group.option(), &OptionProto::name), ", "));
}
std::vector<std::string> Config::validate()
{
std::vector<std::string> results;
@@ -218,7 +225,7 @@ std::vector<std::string> Config::validate()
/* Ensure that only one item in each group is set. */
std::map<const OptionGroupProto*, const OptionProto*> optionsByGroup;
for (auto [group, option, hasArgument] : _appliedOptions)
for (auto& [group, option, hasArgument] : _appliedOptions)
if (group)
{
auto& o = optionsByGroup[group];
@@ -227,12 +234,23 @@ std::vector<std::string> Config::validate()
fmt::format("multiple mutually exclusive values set for "
"group '{}': valid values are: {}",
group->comment(),
fmt::join(std::views::transform(
group->option(), &OptionProto::name),
", ")));
getValidValues(*group)));
o = option;
}
/* Ensure that every group has an option set. */
for (const auto& group : base()->option_group())
{
if (!optionsByGroup.contains(&group))
{
results.push_back(
fmt::format("no value set for group '{}': valid values are: {}",
group.comment(),
getValidValues(group)));
}
}
/* Check option requirements. */
for (auto [group, option, hasArgument] : _appliedOptions)
@@ -357,7 +375,7 @@ Config::OptionInfo Config::findOption(
{
if (optionGroup.name().empty())
if (searchOptionList(optionGroup.option(), name))
return {nullptr, found, false};
return {&optionGroup, found, false};
}
throw OptionNotFoundException(fmt::format("option {} not found", name));
@@ -395,8 +413,7 @@ void Config::checkOptionValid(const OptionProto& option)
ss << ']';
throw InapplicableOptionException(
fmt::format("option '{}' is inapplicable to this "
"configuration "
fmt::format("option '{}' is inapplicable to this configuration "
"because {}={} could not be met",
option.name(),
req.key(),
@@ -434,6 +451,52 @@ bool Config::applyOption(const std::string& name, const std::string value)
return optionInfo.usesValue;
}
void Config::applyOptionsFile(const std::string& data)
{
if (!data.empty())
{
for (auto setting : split(data, '\n'))
{
setting = trimWhitespace(setting);
if (setting.size() == 0)
continue;
if (setting[0] == '#')
continue;
auto equals = setting.find('=');
if (equals == std::string::npos)
error("Malformed setting line '{}'", setting);
auto key = setting.substr(0, equals);
auto value = setting.substr(equals + 1);
globalConfig().set(key, value);
}
}
}
void Config::applyDefaultOptions()
{
std::set<const OptionGroupProto*> appliedOptionGroups;
for (auto& [group, option, hasArgument] : _appliedOptions)
if (group)
appliedOptionGroups.insert(group);
/* For every group which doesn't have an option set, find the default and
* set it. */
for (const auto& group : base()->option_group())
{
if (!appliedOptionGroups.contains(&group))
{
for (const auto& option : group.option())
{
if (option.set_by_default())
applyOption({&group, &option, false});
}
}
}
}
void Config::clearOptions()
{
_appliedOptions.clear();

View File

@@ -9,7 +9,7 @@
class ConfigProto;
class OptionProto;
class FluxSource;
class FluxSink;
class FluxSinkFactory;
class ImageReader;
class ImageWriter;
class Encoder;
@@ -142,6 +142,8 @@ public:
bool isOptionValid(const OptionProto& option);
void applyOption(const OptionInfo& optionInfo);
bool applyOption(const std::string& name, const std::string value = "");
void applyOptionsFile(const std::string& data);
void applyDefaultOptions();
void clearOptions();
/* Adjust overall inputs and outputs. */

View File

@@ -55,7 +55,16 @@ message OptionPrerequisiteProto
repeated string value = 2 [(help) = "list of required values"];
}
// NEXT_TAG: 8
enum OptionApplicabilityHint
{
FORMAT = 0;
ANY_SOURCESINK = 1;
HARDWARE_SOURCESINK = 2;
MANUAL_SOURCESINK = 3;
FLUXFILE_SOURCESINK = 4;
}
// NEXT_TAG: 9
message OptionProto
{
optional string name = 1 [(help) = "option name"];
@@ -68,11 +77,14 @@ message OptionProto
7 [(help) = "prerequisites for this option"];
optional ConfigProto config = 4 [(help) = "option data"];
repeated OptionApplicabilityHint applicability = 8;
}
// NEXT_TAG: 5
message OptionGroupProto
{
optional string comment = 1 [(help) = "help text for option group"];
optional string name = 2 [(help) = "option group name"];
repeated OptionProto option = 3;
repeated OptionApplicabilityHint applicability = 4;
}

View File

@@ -16,11 +16,11 @@ message DriveProto
[ default = 0, (help) = "index pulses longer than this interval are "
"considered sector markers; shorter indicates an true index marker" ];
optional bool high_density = 5
[ default = true, (help) = "set if this is a high density disk" ];
[ default = false, (help) = "set if this is a high density disk" ];
optional bool sync_with_index = 6
[ default = false, (help) = "start reading at index mark" ];
optional double revolutions = 7
[ default = 1.2, (help) = "number of revolutions to read" ];
[ default = 2.5, (help) = "number of revolutions to read" ];
optional string tracks = 8
[ default = "c0-80h0-1", (help) = "Tracks supported by drive" ];

View File

@@ -174,6 +174,7 @@ std::vector<std::string> FlagGroup::parseFlagsWithFilenames(int argc,
index++;
}
globalConfig().applyDefaultOptions();
globalConfig().validateAndThrow();
return filenames;
}

View File

@@ -117,38 +117,28 @@ static ProtoField resolveProtoPath(
"config field '{}' in '{}' is not a message", item, path));
const auto* reflection = message->GetReflection();
if ((field->label() !=
google::protobuf::FieldDescriptor::LABEL_REPEATED) &&
(index != -1))
if (!field->is_repeated() && (index != -1))
throw ProtoPathNotFoundException(fmt::format(
"config field '{}[{}]' is indexed, but not repeated",
item,
index));
switch (field->label())
if (field->is_repeated())
{
case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
case google::protobuf::FieldDescriptor::LABEL_REQUIRED:
if (!create && !reflection->HasField(*message, field))
throw ProtoPathNotFoundException(fmt::format(
"could not find config field '{}'", field->name()));
message = reflection->MutableMessage(message, field);
break;
if (index == -1)
throw ProtoPathNotFoundException(fmt::format(
"config field '{}' is repeated and must be indexed", item));
while (reflection->FieldSize(*message, field) <= index)
reflection->AddMessage(message, field);
case google::protobuf::FieldDescriptor::LABEL_REPEATED:
if (index == -1)
throw ProtoPathNotFoundException(fmt::format(
"config field '{}' is repeated and must be indexed",
item));
while (reflection->FieldSize(*message, field) <= index)
reflection->AddMessage(message, field);
message =
reflection->MutableRepeatedMessage(message, field, index);
break;
default:
error("bad proto label for field '{}' in '{}'", item, path);
message = reflection->MutableRepeatedMessage(message, field, index);
}
else
{
if (!create && !reflection->HasField(*message, field))
throw ProtoPathNotFoundException(fmt::format(
"could not find config field '{}'", field->name()));
message = reflection->MutableMessage(message, field);
}
descriptor = message->GetDescriptor();
@@ -215,7 +205,7 @@ static void updateRepeatedField(
void ProtoField::set(const std::string& value)
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
if (_field->is_repeated())
{
if (_index == -1)
error("field '{}' is repeated but no index is provided");
@@ -359,7 +349,7 @@ void ProtoField::set(const std::string& value)
std::string ProtoField::get() const
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
if (_field->is_repeated())
{
if (_index == -1)
error("field '{}' is repeated but no index is provided",
@@ -456,7 +446,7 @@ std::string ProtoField::get() const
google::protobuf::Message* ProtoField::getMessage() const
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
if (_field->is_repeated())
{
if (_index == -1)
error("field '{}' is repeated but no index is provided",
@@ -477,7 +467,7 @@ google::protobuf::Message* ProtoField::getMessage() const
std::string ProtoField::getBytes() const
{
const auto* reflection = _message->GetReflection();
if (_field->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
if (_field->is_repeated())
{
if (_index == -1)
error("field '{}' is repeated but no index is provided",
@@ -536,7 +526,7 @@ findAllPossibleProtoFields(const google::protobuf::Descriptor* descriptor)
const google::protobuf::FieldDescriptor* f = d->field(i);
std::string n = s + (std::string)f->name();
if (f->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
if (f->is_repeated())
n += "[]";
if (shouldRecurse(f))
@@ -550,12 +540,13 @@ findAllPossibleProtoFields(const google::protobuf::Descriptor* descriptor)
return fields;
}
std::vector<ProtoField> findAllProtoFields(google::protobuf::Message* message)
std::vector<ProtoField> findAllProtoFields(
const google::protobuf::Message* message)
{
std::vector<ProtoField> allFields;
std::function<void(google::protobuf::Message*, const std::string&)>
recurse = [&](auto* message, const auto& name)
std::function<void(const google::protobuf::Message*, const std::string&)>
recurse = [&](const auto* message, const auto& name)
{
const auto* reflection = message->GetReflection();
std::vector<const google::protobuf::FieldDescriptor*> fields;
@@ -568,25 +559,26 @@ std::vector<ProtoField> findAllProtoFields(google::protobuf::Message* message)
basename += '.';
basename += f->name();
if (f->label() == google::protobuf::FieldDescriptor::LABEL_REPEATED)
if (f->is_repeated())
{
for (int i = 0; i < reflection->FieldSize(*message, f); i++)
{
const auto n = fmt::format("{}[{}]", basename, i);
if (shouldRecurse(f))
recurse(
reflection->MutableRepeatedMessage(message, f, i),
n);
&reflection->GetRepeatedMessage(*message, f, i), n);
else
allFields.push_back(ProtoField(n, message, f, i));
allFields.push_back(ProtoField(
n, (google::protobuf::Message*)message, f, i));
}
}
else
{
if (shouldRecurse(f))
recurse(reflection->MutableMessage(message, f), basename);
recurse(&reflection->GetMessage(*message, f), basename);
else
allFields.push_back(ProtoField(basename, message, f));
allFields.push_back(ProtoField(
basename, (google::protobuf::Message*)message, f));
}
}
};
@@ -594,3 +586,12 @@ std::vector<ProtoField> findAllProtoFields(google::protobuf::Message* message)
recurse(message, "");
return allFields;
}
std::string renderProtoAsConfig(const google::protobuf::Message* message)
{
auto allFields = findAllProtoFields(message);
std::stringstream ss;
for (const auto& field : allFields)
ss << field.path() << "=" << field.get() << "\n";
return ss.str();
}

View File

@@ -72,7 +72,9 @@ extern std::map<std::string, const google::protobuf::FieldDescriptor*>
findAllPossibleProtoFields(const google::protobuf::Descriptor* descriptor);
extern std::vector<ProtoField> findAllProtoFields(
google::protobuf::Message* message);
const google::protobuf::Message* message);
extern std::string renderProtoAsConfig(
const google::protobuf::Message* message);
template <class T>
static inline const T parseProtoBytes(const std::string_view& bytes)

View File

@@ -23,5 +23,6 @@ cxxlibrary(
"dep/agg",
"dep/stb",
"+fmt_lib",
"+z_lib",
],
)

View File

@@ -27,28 +27,27 @@
#define STRINGIFY(a) XSTRINGIFY(a)
#define XSTRINGIFY(a) #a
template <class T>
static inline std::vector<T> vector_of(T item)
{
return std::vector<T>{item};
}
typedef double nanoseconds_t;
class Bytes;
extern double getCurrentTime();
extern void hexdump(std::ostream& stream, const Bytes& bytes);
extern void hexdumpForSrp16(std::ostream& stream, const Bytes& bytes);
struct ErrorException
struct ErrorException : public std::exception
{
ErrorException(const std::string& message): message(message) {}
const std::string message;
const char* what() const throw() override;
void print() const;
};
struct OutOfRangeException : public ErrorException
{
OutOfRangeException(const std::string& message): ErrorException(message) {}
};
template <typename... Args>
[[noreturn]] inline void error(fmt::string_view fstr, const Args&... args)
{
@@ -71,4 +70,24 @@ struct overloaded : Ts...
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
template <typename K, typename V>
inline const V& findOrDefault(
const std::map<K, V>& map, const K& key, const V& defaultValue = V())
{
auto it = map.find(key);
if (it == map.end())
return defaultValue;
return it->second;
}
template <typename K, typename V>
inline const std::optional<V> findOptionally(
const std::map<K, V>& map, const K& key, const V& defaultValue = V())
{
auto it = map.find(key);
if (it == map.end())
return std::nullopt;
return std::make_optional(it->second);
}
#endif

View File

@@ -32,10 +32,3 @@ void hexdump(std::ostream& stream, const Bytes& buffer)
pos += 16;
}
}
void hexdumpForSrp16(std::ostream& stream, const Bytes& buffer)
{
for (uint8_t byte : buffer)
stream << fmt::format("{:02x}", byte);
stream << std::endl;
}

View File

@@ -4,9 +4,8 @@
#include "fmt/format.h"
#include <type_traits>
class DiskFlux;
class TrackDataFlux;
class TrackFlux;
class Disk;
class Track;
class Sector;
class LogRenderer;

View File

@@ -17,6 +17,11 @@ void ErrorException::print() const
std::cerr << message << '\n';
}
const char* ErrorException::what() const throw()
{
return message.c_str();
}
std::string join(
const std::vector<std::string>& values, const std::string& separator)
{

View File

@@ -37,6 +37,15 @@ std::map<V, K> reverseMap(const std::map<K, V>& map)
return reverse;
}
template <typename K, typename V>
std::map<K, std::vector<V>> multimapToMapOfVectors(const std::multimap<K, V>& multimap)
{
std::map<K, std::vector<V>> results;
for (auto& [k, v] : multimap)
results[k].push_back(v);
return results;
}
/* If set, any running job will terminate as soon as possible (with an error).
*/

View File

@@ -3,6 +3,7 @@ from build.c import cxxlibrary
cxxlibrary(
name="data",
srcs=[
"./disk.cc",
"./fluxmap.cc",
"./fluxmapreader.cc",
"./fluxpattern.cc",
@@ -12,7 +13,7 @@ cxxlibrary(
"./sector.cc",
],
hdrs={
"lib/data/flux.h": "./flux.h",
"lib/data/disk.h": "./disk.h",
"lib/data/fluxmap.h": "./fluxmap.h",
"lib/data/sector.h": "./sector.h",
"lib/data/layout.h": "./layout.h",

56
lib/data/disk.cc Normal file
View File

@@ -0,0 +1,56 @@
#include "lib/core/globals.h"
#include "lib/data/disk.h"
#include "lib/data/image.h"
#include "lib/data/layout.h"
#include "lib/data/sector.h"
#include "protocol.h"
namespace
{
struct pair_to_range_t
{
template <typename I>
friend constexpr auto operator|(
std::pair<I, I> const& pr, pair_to_range_t)
{
return std::ranges::subrange(pr.first, pr.second);
}
};
inline constexpr pair_to_range_t pair_to_range{};
}
Disk::Disk() {}
Disk::Disk(
const std::shared_ptr<const Image>& image, const DiskLayout& diskLayout):
image(image)
{
std::multimap<CylinderHead, std::shared_ptr<const Sector>>
sectorsGroupedByTrack;
for (const auto& sector : *image)
sectorsGroupedByTrack.insert(
std::make_pair(sector->physicalLocation.value(), sector));
for (const auto& physicalLocation :
std::views::keys(sectorsGroupedByTrack) | std::ranges::to<std::set>())
{
const auto& ptl =
diskLayout.layoutByPhysicalLocation.at(physicalLocation);
const auto& ltl = ptl->logicalTrackLayout;
auto decodedTrack = std::make_shared<Track>();
decodedTrack->ltl = ltl;
decodedTrack->ptl = ptl;
tracksByPhysicalLocation.insert(
std::make_pair(physicalLocation, decodedTrack));
for (auto& [ch, sector] :
sectorsGroupedByTrack.equal_range(physicalLocation) | pair_to_range)
{
decodedTrack->allSectors.push_back(sector);
decodedTrack->normalisedSectors.push_back(sector);
sectorsByPhysicalLocation.insert(
std::make_pair(physicalLocation, sector));
}
}
}

62
lib/data/disk.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef FLUX_H
#define FLUX_H
#include "lib/core/bytes.h"
#include "lib/data/locations.h"
class DiskLayout;
class Fluxmap;
class Image;
class LogicalTrackLayout;
class PhysicalTrackLayout;
class Sector;
struct Record
{
nanoseconds_t clock = 0;
nanoseconds_t startTime = 0;
nanoseconds_t endTime = 0;
uint32_t position = 0;
Bytes rawData;
};
struct Track
{
std::shared_ptr<const LogicalTrackLayout> ltl;
std::shared_ptr<const PhysicalTrackLayout> ptl;
std::shared_ptr<const Fluxmap> fluxmap;
std::vector<std::shared_ptr<const Record>> records;
/* All sectors, valid or not, including duplicates. */
std::vector<std::shared_ptr<const Sector>> allSectors;
/* Zero or one sector for each ID, preferring good ones. */
std::vector<std::shared_ptr<const Sector>> normalisedSectors;
};
struct Disk
{
Disk();
/* Creates a Disk from an Image, populating the tracks and sectors maps
* based on the supplied disk layout. */
Disk(const std::shared_ptr<const Image>& image,
const DiskLayout& diskLayout);
Disk& operator=(const Disk& other) = default;
std::multimap<CylinderHead, std::shared_ptr<const Track>>
tracksByPhysicalLocation;
std::multimap<CylinderHead, std::shared_ptr<const Sector>>
sectorsByPhysicalLocation;
std::shared_ptr<const Image> image;
/* 0 if the period is unknown (e.g. if this Disk was made from an image). */
nanoseconds_t rotationalPeriod = 0;
};
#endif

View File

@@ -1,41 +0,0 @@
#ifndef FLUX_H
#define FLUX_H
#include "lib/core/bytes.h"
class Fluxmap;
class Sector;
class Image;
class TrackInfo;
struct Record
{
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;
};
struct TrackFlux
{
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;
};
#endif

View File

@@ -1,6 +1,8 @@
#include "lib/core/globals.h"
#include "lib/data/fluxmap.h"
#include "lib/data/fluxmapreader.h"
#include "protocol.h"
#include <mutex>
Fluxmap& Fluxmap::appendBytes(const Bytes& bytes)
{
@@ -12,6 +14,8 @@ Fluxmap& Fluxmap::appendBytes(const Bytes& bytes)
Fluxmap& Fluxmap::appendBytes(const uint8_t* ptr, size_t len)
{
flushIndexMarks();
ByteWriter bw(_bytes);
bw.seekToEnd();
@@ -52,6 +56,7 @@ Fluxmap& Fluxmap::appendPulse()
Fluxmap& Fluxmap::appendIndex()
{
flushIndexMarks();
findLastByte() |= 0x40;
return *this;
}
@@ -75,3 +80,33 @@ std::vector<std::unique_ptr<const Fluxmap>> Fluxmap::split() const
return maps;
}
const std::vector<nanoseconds_t>& Fluxmap::getIndexMarks() const
{
std::scoped_lock lock(_mutationMutex);
if (!_indexMarks.has_value())
{
_indexMarks = std::make_optional<std::vector<nanoseconds_t>>();
FluxmapReader fmr(*this);
nanoseconds_t oldt = -1;
for (;;)
{
unsigned ticks;
if (!fmr.findEvent(F_BIT_INDEX, ticks))
break;
/* Debounce. */
nanoseconds_t t = fmr.tell().ns();
if (t != oldt)
_indexMarks->push_back(t);
oldt = t;
}
}
return *_indexMarks;
}
void Fluxmap::flushIndexMarks()
{
std::scoped_lock lock(_mutationMutex);
_indexMarks = {};
}

View File

@@ -4,6 +4,7 @@
#include "lib/core/bytes.h"
#include "protocol.h"
#include "fmt/format.h"
#include <mutex>
class RawBits;
@@ -82,14 +83,18 @@ public:
std::unique_ptr<const Fluxmap> precompensate(
int threshold_ticks, int amount_ticks);
std::vector<std::unique_ptr<const Fluxmap>> split() const;
const std::vector<nanoseconds_t>& getIndexMarks() const;
private:
uint8_t& findLastByte();
void flushIndexMarks();
private:
nanoseconds_t _duration = 0;
int _ticks = 0;
Bytes _bytes;
mutable std::mutex _mutationMutex;
mutable std::optional<std::vector<nanoseconds_t>> _indexMarks;
};
#endif

View File

@@ -44,6 +44,13 @@ void FluxmapReader::skipToEvent(int event)
findEvent(event, ticks);
}
int FluxmapReader::getCurrentEvent()
{
if (eof())
return F_EOF;
return _bytes[_pos.bytes] & 0xc0;
}
bool FluxmapReader::findEvent(int event, unsigned& ticks)
{
ticks = 0;

View File

@@ -42,6 +42,7 @@ public:
return (_fluxmap.duration());
}
int getCurrentEvent();
void getNextEvent(int& event, unsigned& ticks);
void skipToEvent(int event);
bool findEvent(int event, unsigned& ticks);

View File

@@ -6,14 +6,13 @@
Image::Image() {}
Image::Image(std::set<std::shared_ptr<const Sector>>& sectors)
Image::Image(const std::vector<std::shared_ptr<const Sector>>& sectors)
{
for (auto& sector : sectors)
{
key_t key = std::make_tuple(
sector->logicalTrack, sector->logicalSide, sector->logicalSector);
_sectors[key] = sector;
}
_sectors[{sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector}] = sector;
calculateSize();
}
@@ -23,72 +22,53 @@ void Image::clear()
_geometry = {0, 0, 0};
}
void Image::createBlankImage()
{
clear();
for (const auto& trackAndHead : Layout::getTrackOrdering(
globalConfig()->layout().filesystem_track_order()))
{
unsigned track = trackAndHead.first;
unsigned side = trackAndHead.second;
auto trackLayout = Layout::getLayoutOfTrack(track, side);
Bytes blank(trackLayout->sectorSize);
for (unsigned sectorId : trackLayout->naturalSectorOrder)
put(track, side, sectorId)->data = blank;
}
}
bool Image::empty() const
{
return _sectors.empty();
}
bool Image::contains(unsigned track, unsigned side, unsigned sectorid) const
bool Image::contains(const LogicalLocation& location) const
{
key_t key = std::make_tuple(track, side, sectorid);
return _sectors.find(key) != _sectors.end();
return _sectors.find(location) != _sectors.end();
}
std::shared_ptr<const Sector> Image::get(
unsigned track, unsigned side, unsigned sectorid) const
std::shared_ptr<const Sector> Image::get(const LogicalLocation& location) const
{
static std::shared_ptr<const Sector> NONE;
key_t key = std::make_tuple(track, side, sectorid);
auto i = _sectors.find(key);
auto i = _sectors.find(location);
if (i == _sectors.end())
return NONE;
return nullptr;
return i->second;
}
std::shared_ptr<Sector> Image::put(
unsigned track, unsigned side, unsigned sectorid)
std::shared_ptr<Sector> Image::put(const LogicalLocation& location)
{
auto trackLayout = Layout::getLayoutOfTrack(track, side);
key_t key = std::make_tuple(track, side, sectorid);
std::shared_ptr<Sector> sector = std::make_shared<Sector>();
sector->logicalTrack = track;
sector->logicalSide = side;
sector->logicalSector = sectorid;
sector->physicalTrack = Layout::remapTrackLogicalToPhysical(track);
sector->physicalSide = side;
_sectors[key] = sector;
auto sector = std::make_shared<Sector>(location);
_sectors[location] = sector;
return sector;
}
void Image::erase(unsigned track, unsigned side, unsigned sectorid)
void Image::erase(const LogicalLocation& location)
{
key_t key = std::make_tuple(track, side, sectorid);
_sectors.erase(key);
_sectors.erase(location);
}
std::set<std::pair<unsigned, unsigned>> Image::tracks() const
void Image::addMissingSectors(const DiskLayout& diskLayout, bool populated)
{
std::set<std::pair<unsigned, unsigned>> result;
for (const auto& e : _sectors)
result.insert(
std::make_pair(std::get<0>(e.first), std::get<1>(e.first)));
return result;
for (auto& location : diskLayout.logicalSectorLocationsInFilesystemOrder)
if (!_sectors.contains(location))
{
auto& ltl = diskLayout.layoutByLogicalLocation.at(
{location.logicalCylinder, location.logicalHead});
auto sector = std::make_shared<Sector>(location);
if (populated)
sector->data = Bytes(ltl->sectorSize);
else
sector->status = Sector::MISSING;
_sectors[location] = sector;
}
calculateSize();
}
void Image::calculateSize()
@@ -100,16 +80,39 @@ void Image::calculateSize()
const auto& sector = i.second;
if (sector)
{
_geometry.numTracks = std::max(
_geometry.numTracks, (unsigned)sector->logicalTrack + 1);
_geometry.numSides =
std::max(_geometry.numSides, (unsigned)sector->logicalSide + 1);
_geometry.numCylinders = std::max(
_geometry.numCylinders, (unsigned)sector->logicalCylinder + 1);
_geometry.numHeads =
std::max(_geometry.numHeads, (unsigned)sector->logicalHead + 1);
_geometry.firstSector = std::min(
_geometry.firstSector, (unsigned)sector->logicalSector);
maxSector = std::max(maxSector, (unsigned)sector->logicalSector);
_geometry.sectorSize =
std::max(_geometry.sectorSize, (unsigned)sector->data.size());
std::max(_geometry.sectorSize, sector->data.size());
_geometry.totalBytes += _geometry.sectorSize;
}
}
_geometry.numSectors = maxSector - _geometry.firstSector + 1;
}
void Image::populateSectorPhysicalLocationsFromLogicalLocations(
const DiskLayout& diskLayout)
{
Image tempImage;
for (const auto& sector : *this)
{
const auto& ltl = diskLayout.layoutByLogicalLocation.at(
{sector->logicalCylinder, sector->logicalHead});
auto newSector = tempImage.put(sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector);
*newSector = *sector;
newSector->physicalLocation = std::make_optional<CylinderHead>(
ltl->physicalCylinder, ltl->physicalHead);
}
for (const auto& sector : tempImage)
_sectors[{sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector}] = sector;
}

View File

@@ -1,32 +1,33 @@
#ifndef IMAGE_H
#define IMAGE_H
#include "lib/data/locations.h"
class Sector;
class DiskLayout;
struct Geometry
{
unsigned numTracks = 0;
unsigned numSides = 0;
unsigned numCylinders = 0;
unsigned numHeads = 0;
unsigned firstSector = UINT_MAX;
unsigned numSectors = 0;
unsigned sectorSize = 0;
bool irregular = false;
unsigned totalBytes = 0;
};
class Image
{
private:
typedef std::tuple<unsigned, unsigned, unsigned> key_t;
public:
Image();
Image(std::set<std::shared_ptr<const Sector>>& sectors);
Image(const std::vector<std::shared_ptr<const Sector>>& sectors);
public:
class const_iterator
{
typedef std::map<key_t, std::shared_ptr<const Sector>>::const_iterator
wrapped_iterator_t;
typedef std::map<LogicalLocation,
std::shared_ptr<const Sector>>::const_iterator wrapped_iterator_t;
public:
const_iterator(const wrapped_iterator_t& it): _it(it) {}
@@ -60,14 +61,53 @@ public:
void clear();
void createBlankImage();
bool empty() const;
bool contains(unsigned track, unsigned side, unsigned sectorId) const;
std::shared_ptr<const Sector> get(
unsigned track, unsigned side, unsigned sectorId) const;
std::shared_ptr<Sector> put(
unsigned track, unsigned side, unsigned sectorId);
void erase(unsigned track, unsigned side, unsigned sectorId);
std::set<std::pair<unsigned, unsigned>> tracks() const;
bool contains(const LogicalLocation& location) const;
std::shared_ptr<const Sector> get(const LogicalLocation& location) const;
std::shared_ptr<Sector> put(const LogicalLocation& location);
void erase(const LogicalLocation& location);
bool contains(const CylinderHead& ch, unsigned sector) const
{
return contains({ch.cylinder, ch.head, sector});
}
bool contains(unsigned cylinder, unsigned head, unsigned sector) const
{
return contains({cylinder, head, sector});
}
std::shared_ptr<const Sector> get(
const CylinderHead& ch, unsigned sector) const
{
return get({ch.cylinder, ch.head, sector});
}
std::shared_ptr<const Sector> get(
unsigned cylinder, unsigned head, unsigned sector) const
{
return get({cylinder, head, sector});
}
std::shared_ptr<Sector> put(const CylinderHead& ch, unsigned sector)
{
return put({ch.cylinder, ch.head, sector});
}
std::shared_ptr<Sector> put(
unsigned cylinder, unsigned head, unsigned sector)
{
return put({cylinder, head, sector});
}
void erase(unsigned cylinder, unsigned head, unsigned sector)
{
erase({cylinder, head, sector});
}
void addMissingSectors(const DiskLayout& layout, bool populated = false);
void populateSectorPhysicalLocationsFromLogicalLocations(
const DiskLayout& diskLayout);
const_iterator begin() const
{
@@ -89,7 +129,7 @@ public:
private:
Geometry _geometry = {0, 0, 0};
std::map<key_t, std::shared_ptr<const Sector>> _sectors;
std::map<LogicalLocation, std::shared_ptr<const Sector>> _sectors;
};
#endif

View File

@@ -4,10 +4,10 @@
#include "lib/config/proto.h"
#include "lib/core/logger.h"
static unsigned getTrackStep()
static unsigned getTrackStep(const ConfigProto& config = globalConfig())
{
auto format_type = globalConfig()->layout().format_type();
auto drive_type = globalConfig()->drive().drive_type();
auto format_type = config.layout().format_type();
auto drive_type = config.drive().drive_type();
switch (format_type)
{
@@ -22,6 +22,9 @@ static unsigned getTrackStep()
case DRIVETYPE_APPLE2:
return 4;
default:
break;
}
case FORMATTYPE_80TRACK:
@@ -30,8 +33,7 @@ static unsigned getTrackStep()
case DRIVETYPE_40TRACK:
error(
"you can't read/write an 80 track image from/to a 40 "
"track "
"drive");
"track drive");
case DRIVETYPE_80TRACK:
return 1;
@@ -39,111 +41,55 @@ static unsigned getTrackStep()
case DRIVETYPE_APPLE2:
error(
"you can't read/write an 80 track image from/to an "
"Apple II "
"drive");
"Apple II drive");
default:
break;
}
default:
break;
}
return 1;
}
unsigned Layout::remapTrackPhysicalToLogical(unsigned ptrack)
static std::vector<CylinderHead> getTrackOrdering(
LayoutProto::Order ordering, unsigned tracks, unsigned sides)
{
return (ptrack - globalConfig()->drive().head_bias()) / getTrackStep();
}
unsigned Layout::remapTrackLogicalToPhysical(unsigned ltrack)
{
return globalConfig()->drive().head_bias() + ltrack * getTrackStep();
}
unsigned Layout::remapSidePhysicalToLogical(unsigned pside)
{
return pside ^ globalConfig()->layout().swap_sides();
}
unsigned Layout::remapSideLogicalToPhysical(unsigned lside)
{
return lside ^ globalConfig()->layout().swap_sides();
}
std::vector<CylinderHead> Layout::computePhysicalLocations()
{
if (globalConfig()->has_tracks())
return parseCylinderHeadsString(globalConfig()->tracks());
std::set<unsigned> tracks = iterate(0, globalConfig()->layout().tracks());
std::set<unsigned> heads = iterate(0, globalConfig()->layout().sides());
std::vector<CylinderHead> locations;
for (unsigned logicalTrack : tracks)
for (unsigned logicalHead : heads)
locations.push_back(
CylinderHead{remapTrackLogicalToPhysical(logicalTrack),
remapSideLogicalToPhysical(logicalHead)});
return locations;
}
Layout::LayoutBounds Layout::getBounds(
const std::vector<CylinderHead>& locations)
{
LayoutBounds r{.minTrack = INT_MAX,
.maxTrack = INT_MIN,
.minSide = INT_MAX,
.maxSide = INT_MIN};
for (const auto& ti : locations)
{
r.minTrack = std::min<int>(r.minTrack, ti.cylinder);
r.maxTrack = std::max<int>(r.maxTrack, ti.cylinder);
r.minSide = std::min<int>(r.minSide, ti.head);
r.maxSide = std::max<int>(r.maxSide, ti.head);
}
return r;
}
std::vector<std::pair<int, int>> Layout::getTrackOrdering(
LayoutProto::Order ordering, unsigned guessedTracks, unsigned guessedSides)
{
auto layout = globalConfig()->layout();
int tracks = layout.has_tracks() ? layout.tracks() : guessedTracks;
int sides = layout.has_sides() ? layout.sides() : guessedSides;
std::vector<std::pair<int, int>> trackList;
std::vector<CylinderHead> trackList;
switch (ordering)
{
case LayoutProto::CHS:
{
for (int track = 0; track < tracks; track++)
for (unsigned track = 0; track < tracks; track++)
{
for (int side = 0; side < sides; side++)
trackList.push_back(std::make_pair(track, side));
for (unsigned side = 0; side < sides; side++)
trackList.push_back({track, side});
}
break;
}
case LayoutProto::HCS:
{
for (int side = 0; side < sides; side++)
for (unsigned side = 0; side < sides; side++)
{
for (int track = 0; track < tracks; track++)
trackList.push_back(std::make_pair(track, side));
for (unsigned track = 0; track < tracks; track++)
trackList.push_back({track, side});
}
break;
}
case LayoutProto::HCS_RH1:
{
for (int side = 0; side < sides; side++)
for (unsigned side = 0; side < sides; side++)
{
if (side == 0)
for (int track = 0; track < tracks; track++)
trackList.push_back(std::make_pair(track, side));
for (unsigned track = 0; track < tracks; track++)
trackList.push_back({track, side});
if (side == 1)
for (int track = tracks; track >= 0; track--)
trackList.push_back(std::make_pair(track - 1, side));
for (unsigned track = tracks; track >= 0; track--)
trackList.push_back({track - 1, side});
}
break;
}
@@ -155,7 +101,7 @@ std::vector<std::pair<int, int>> Layout::getTrackOrdering(
return trackList;
}
std::vector<unsigned> Layout::expandSectorList(
static std::vector<unsigned> expandSectorList(
const SectorListProto& sectorsProto)
{
std::vector<unsigned> sectors;
@@ -197,95 +143,200 @@ std::vector<unsigned> Layout::expandSectorList(
return sectors;
}
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrack(
unsigned logicalTrack, unsigned logicalSide)
static const LayoutProto::LayoutdataProto getLayoutData(
unsigned logicalCylinder, unsigned logicalHead, const ConfigProto& config)
{
auto trackInfo = std::make_shared<TrackInfo>();
LayoutProto::LayoutdataProto layoutdata;
for (const auto& f : globalConfig()->layout().layoutdata())
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())))
((logicalCylinder < f.track()) ||
(logicalCylinder > f.up_to_track())))
continue;
if (f.has_track() && !f.has_up_to_track() &&
(logicalTrack != f.track()))
(logicalCylinder != f.track()))
continue;
if (f.has_side() && (f.side() != logicalSide))
if (f.has_side() && (f.side() != logicalHead))
continue;
layoutdata.MergeFrom(f);
layoutData.MergeFrom(f);
}
trackInfo->numTracks = globalConfig()->layout().tracks();
trackInfo->numSides = globalConfig()->layout().sides();
trackInfo->sectorSize = layoutdata.sector_size();
trackInfo->logicalTrack = logicalTrack;
trackInfo->logicalSide = logicalSide;
trackInfo->physicalTrack = remapTrackLogicalToPhysical(logicalTrack);
trackInfo->physicalSide =
logicalSide ^ globalConfig()->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;
return layoutData;
}
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical(
unsigned physicalTrack, unsigned physicalSide)
DiskLayout::DiskLayout(const ConfigProto& config)
{
return getLayoutOfTrack(remapTrackPhysicalToLogical(physicalTrack),
remapSidePhysicalToLogical(physicalSide));
}
minPhysicalCylinder = minPhysicalHead = UINT_MAX;
maxPhysicalCylinder = maxPhysicalHead = 0;
std::shared_ptr<const TrackInfo> Layout::getLayoutOfTrackPhysical(
const CylinderHead& physicalLocation)
{
return getLayoutOfTrackPhysical(
physicalLocation.cylinder, physicalLocation.head);
}
numLogicalCylinders = config.layout().tracks();
numLogicalHeads = config.layout().sides();
std::vector<std::shared_ptr<const TrackInfo>> Layout::getLayoutOfTracksPhysical(
const std::vector<CylinderHead>& physicalLocations)
{
std::vector<std::shared_ptr<const TrackInfo>> results;
for (const auto& physicalLocation : physicalLocations)
results.push_back(getLayoutOfTrackPhysical(physicalLocation));
return results;
}
groupSize = getTrackStep(config);
headBias = config.drive().head_bias();
swapSides = config.layout().swap_sides();
int Layout::getHeadWidth()
{
switch (globalConfig()->drive().drive_type())
switch (config.drive().drive_type())
{
case DRIVETYPE_APPLE2:
return 4;
headWidth = 4;
break;
default:
return 1;
headWidth = 1;
break;
}
for (unsigned logicalCylinder = 0; logicalCylinder < numLogicalCylinders;
logicalCylinder++)
for (unsigned logicalHead = 0; logicalHead < numLogicalHeads;
logicalHead++)
{
auto ltl = std::make_shared<LogicalTrackLayout>();
CylinderHead ch(logicalCylinder, logicalHead);
layoutByLogicalLocation[ch] = ltl;
logicalLocations.push_back(ch);
ltl->logicalCylinder = logicalCylinder;
ltl->logicalHead = logicalHead;
ltl->groupSize = groupSize;
ltl->physicalCylinder =
remapCylinderLogicalToPhysical(logicalCylinder);
ltl->physicalHead = remapHeadLogicalToPhysical(logicalHead);
minPhysicalCylinder =
std::min(minPhysicalCylinder, ltl->physicalCylinder);
maxPhysicalCylinder = std::max(maxPhysicalCylinder,
ltl->physicalCylinder + ltl->groupSize - 1);
minPhysicalHead = std::min(minPhysicalHead, ltl->physicalHead);
maxPhysicalHead = std::max(maxPhysicalHead, ltl->physicalHead);
auto layoutdata =
getLayoutData(logicalCylinder, logicalHead, config);
ltl->sectorSize = layoutdata.sector_size();
ltl->diskSectorOrder = expandSectorList(layoutdata.physical());
ltl->naturalSectorOrder = ltl->diskSectorOrder;
std::sort(
ltl->naturalSectorOrder.begin(), ltl->naturalSectorOrder.end());
ltl->numSectors = ltl->naturalSectorOrder.size();
if (layoutdata.has_filesystem())
{
ltl->filesystemSectorOrder =
expandSectorList(layoutdata.filesystem());
if (ltl->filesystemSectorOrder.size() != ltl->numSectors)
error(
"filesystem sector order list doesn't contain the "
"right number of sectors");
}
else
ltl->filesystemSectorOrder = ltl->naturalSectorOrder;
for (int i = 0; i < ltl->numSectors; i++)
{
unsigned fid = ltl->naturalSectorOrder[i];
unsigned lid = ltl->filesystemSectorOrder[i];
ltl->sectorIdToNaturalOrdering[i] = fid;
ltl->sectorIdToFilesystemOrdering[i] = fid;
}
};
for (unsigned physicalCylinder = minPhysicalCylinder;
physicalCylinder <= maxPhysicalCylinder;
physicalCylinder++)
for (unsigned physicalHead = minPhysicalHead;
physicalHead <= maxPhysicalHead;
physicalHead++)
{
auto ptl = std::make_shared<PhysicalTrackLayout>();
CylinderHead ch(physicalCylinder, physicalHead);
layoutByPhysicalLocation[ch] = ptl;
physicalLocations.push_back(ch);
ptl->physicalCylinder = physicalCylinder;
ptl->physicalHead = physicalHead;
ptl->groupOffset = (physicalCylinder - headBias) % groupSize;
unsigned logicalCylinder =
remapCylinderPhysicalToLogical(physicalCylinder);
unsigned logicalHead = remapHeadPhysicalToLogical(physicalHead);
ptl->logicalTrackLayout = findOrDefault(
layoutByLogicalLocation, {logicalCylinder, logicalHead});
}
unsigned sectorOffset = 0;
unsigned blockId = 0;
for (auto& ch : getTrackOrdering(config.layout().filesystem_track_order(),
numLogicalCylinders,
numLogicalHeads))
{
const auto& ltl = layoutByLogicalLocation[ch];
logicalLocationsInFilesystemOrder.push_back(ch);
for (unsigned lid : ltl->filesystemSectorOrder)
{
LogicalLocation logicalLocation = {ch.cylinder, ch.head, lid};
logicalSectorLocationBySectorOffset[sectorOffset] = logicalLocation;
sectorOffsetByLogicalSectorLocation[logicalLocation] = sectorOffset;
logicalSectorLocationsInFilesystemOrder.push_back(logicalLocation);
sectorOffset += ltl->sectorSize;
blockIdByLogicalSectorLocation[logicalLocation] = blockId;
blockId++;
}
}
totalBytes = sectorOffset;
}
static ConfigProto createTestConfig(unsigned numCylinders,
unsigned numHeads,
unsigned numSectors,
unsigned sectorSize)
{
ConfigProto config;
auto* layout = config.mutable_layout();
layout->set_tracks(numCylinders);
layout->set_sides(numHeads);
auto* layoutData = layout->add_layoutdata();
layoutData->set_sector_size(sectorSize);
layoutData->mutable_physical()->set_count(numSectors);
return config;
}
DiskLayout::DiskLayout(unsigned numCylinders,
unsigned numHeads,
unsigned numSectors,
unsigned sectorSize):
DiskLayout(createTestConfig(numCylinders, numHeads, numSectors, sectorSize))
{
}
static DiskLayout::LayoutBounds getBounds(std::ranges::view auto keys)
{
DiskLayout::LayoutBounds r{.minCylinder = INT_MAX,
.maxCylinder = INT_MIN,
.minHead = INT_MAX,
.maxHead = INT_MIN};
for (const auto& ch : keys)
{
r.minCylinder = std::min<int>(r.minCylinder, ch.cylinder);
r.maxCylinder = std::max<int>(r.maxCylinder, ch.cylinder);
r.minHead = std::min<int>(r.minHead, ch.head);
r.maxHead = std::max<int>(r.maxHead, ch.head);
}
return r;
}
DiskLayout::LayoutBounds DiskLayout::getPhysicalBounds() const
{
return getBounds(std::views::keys(layoutByPhysicalLocation));
}
DiskLayout::LayoutBounds DiskLayout::getLogicalBounds() const
{
return getBounds(std::views::keys(layoutByLogicalLocation));
}

View File

@@ -1,73 +1,163 @@
#ifndef LAYOUT_H
#define LAYOUT_H
#include "lib/data/flux.h"
#include "lib/data/disk.h"
#include "lib/config/layout.pb.h"
#include "lib/config/config.h"
#include "lib/data/locations.h"
class SectorListProto;
class TrackInfo;
class ConfigProto;
class Layout
struct LogicalTrackLayout
{
/* Physical cylinder of the first element of the group. */
unsigned physicalCylinder;
/* Physical head of the first element of the group. */
unsigned physicalHead;
/* Size of this group. */
unsigned groupSize;
/* Logical cylinder of this track. */
unsigned logicalCylinder = 0;
/* Logical side of this track. */
unsigned logicalHead = 0;
/* The number of sectors in this track. */
unsigned numSectors = 0;
/* Number of bytes in a sector. */
unsigned sectorSize = 0;
/* 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 filesystem order. This is the order in which the filesystem
* uses them. */
std::vector<unsigned> filesystemSectorOrder;
/* Mapping of sector ID to filesystem ordering. */
std::map<unsigned, unsigned> sectorIdToFilesystemOrdering;
/* Mapping of sector ID to natural ordering. */
std::map<unsigned, unsigned> sectorIdToNaturalOrdering;
};
struct PhysicalTrackLayout
{
/* Physical location of this track. */
unsigned physicalCylinder;
/* Physical side of this track. */
unsigned physicalHead;
/* Which member of the group this is. */
unsigned groupOffset;
/* The logical track that this track is part of. */
std::shared_ptr<const LogicalTrackLayout> logicalTrackLayout;
};
class DiskLayout
{
public:
/* Translates logical track numbering (the numbers actually written in the
* sector headers) to the track numbering on the actual drive, taking into
* account tpi settings.
*/
static unsigned remapTrackPhysicalToLogical(unsigned physicalTrack);
static unsigned remapTrackLogicalToPhysical(unsigned logicalTrack);
DiskLayout(const ConfigProto& config = globalConfig());
/* Translates logical side numbering (the numbers actually written in the
* sector headers) to the sides used on the actual drive.
*/
static unsigned remapSidePhysicalToLogical(unsigned physicalSide);
static unsigned remapSideLogicalToPhysical(unsigned logicalSide);
/* Makes a simplified layout for testing. */
/* Uses the layout and current track and heads settings to determine
* which physical tracks are going to be read from or written to.
*/
static std::vector<CylinderHead> computePhysicalLocations();
DiskLayout(unsigned numCylinders,
unsigned numHeads,
unsigned numSectors,
unsigned sectorSize);
public:
/* Logical size. */
unsigned numLogicalCylinders;
unsigned numLogicalHeads;
/* Physical size and properties. */
unsigned minPhysicalCylinder, maxPhysicalCylinder;
unsigned minPhysicalHead, maxPhysicalHead;
unsigned groupSize; /* Number of physical cylinders per logical cylinder */
unsigned headBias; /* Physical cylinder offset */
unsigned headWidth; /* Width of the physical head */
bool swapSides; /* Whether sides need to be swapped */
unsigned totalBytes; /* Total number of bytes on the disk. */
/* Physical and logical layouts by location. */
std::map<CylinderHead, std::shared_ptr<const PhysicalTrackLayout>>
layoutByPhysicalLocation;
std::map<CylinderHead, std::shared_ptr<const LogicalTrackLayout>>
layoutByLogicalLocation;
/* Ordered lists of physical and logical locations. */
std::vector<CylinderHead> logicalLocations;
std::vector<CylinderHead> logicalLocationsInFilesystemOrder;
std::vector<CylinderHead> physicalLocations;
/* Ordered lists of sector locations, plus the reverse mapping. */
std::vector<LogicalLocation> logicalSectorLocationsInFilesystemOrder;
std::map<LogicalLocation, unsigned> blockIdByLogicalSectorLocation;
std::vector<CylinderHeadSector> physicalSectorLocationsInFilesystemOrder;
/* Mapping from logical location to sector offset and back again. */
std::map<unsigned, LogicalLocation> logicalSectorLocationBySectorOffset;
std::map<LogicalLocation, unsigned> sectorOffsetByLogicalSectorLocation;
public:
unsigned remapCylinderPhysicalToLogical(unsigned physicalCylinder) const
{
return (physicalCylinder - headBias) / groupSize;
}
unsigned remapCylinderLogicalToPhysical(unsigned logicalCylinder) const
{
return headBias + logicalCylinder * groupSize;
}
unsigned remapHeadPhysicalToLogical(unsigned physicalHead) const
{
return physicalHead ^ swapSides;
}
unsigned remapHeadLogicalToPhysical(unsigned logicalHead) const
{
return logicalHead ^ swapSides;
}
/* Given a list of CylinderHead locations, determines the minimum and
* maximum track and side settings. */
struct LayoutBounds
{
int minTrack, maxTrack, minSide, maxSide;
std::strong_ordering operator<=>(
const LayoutBounds& other) const = default;
int minCylinder, maxCylinder, minHead, maxHead;
};
static LayoutBounds getBounds(const std::vector<CylinderHead>& locations);
/* 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(
LayoutProto::Order ordering,
unsigned guessedTracks = 0,
unsigned guessedSides = 0);
/* Returns the layout of a given track. */
static std::shared_ptr<const TrackInfo> getLayoutOfTrack(
unsigned logicalTrack, unsigned logicalHead);
/* Returns the layout of a given track via physical location. */
static std::shared_ptr<const TrackInfo> getLayoutOfTrackPhysical(
unsigned physicalTrack, unsigned physicalSide);
/* Returns the layout of a given track via physical location. */
static std::shared_ptr<const TrackInfo> getLayoutOfTrackPhysical(
const CylinderHead& physicalLocation);
/* Returns the layouts of a multiple tracks via physical location. */
static std::vector<std::shared_ptr<const TrackInfo>>
getLayoutOfTracksPhysical(const std::vector<CylinderHead>& locations);
/* Expand a SectorList into the actual sector IDs. */
static std::vector<unsigned> expandSectorList(
const SectorListProto& sectorsProto);
/* Return the head width of the current drive. */
static int getHeadWidth();
LayoutBounds getPhysicalBounds() const;
LayoutBounds getLogicalBounds() const;
};
static std::shared_ptr<DiskLayout> createDiskLayout(
const ConfigProto& config = globalConfig())
{
return std::make_shared<DiskLayout>(config);
}
class TrackInfo
{
public:
@@ -79,23 +169,23 @@ private:
TrackInfo& operator=(const TrackInfo&);
public:
unsigned numTracks = 0;
unsigned numSides = 0;
unsigned numCylinders = 0;
unsigned numHeads = 0;
/* The number of sectors in this track. */
unsigned numSectors = 0;
/* Physical location of this track. */
unsigned physicalTrack = 0;
unsigned physicalCylinder = 0;
/* Physical side of this track. */
unsigned physicalSide = 0;
unsigned physicalHead = 0;
/* Logical location of this track. */
unsigned logicalTrack = 0;
unsigned logicalCylinder = 0;
/* Logical side of this track. */
unsigned logicalSide = 0;
unsigned logicalHead = 0;
/* The number of physical tracks which need to be written for one logical
* track. */

View File

@@ -10,6 +10,7 @@
#include <lexy_ext/report_error.hpp>
#include "fmt/ranges.h"
#include <ranges>
#include <algorithm>
namespace
{

View File

@@ -8,6 +8,41 @@ struct CylinderHead
unsigned cylinder, head;
};
struct CylinderHeadSector
{
bool operator==(const CylinderHeadSector&) const = default;
std::strong_ordering operator<=>(const CylinderHeadSector&) const = default;
unsigned cylinder, head, sector;
};
struct LogicalLocation
{
bool operator==(const LogicalLocation&) const = default;
std::strong_ordering operator<=>(const LogicalLocation&) const = default;
unsigned logicalCylinder;
unsigned logicalHead;
unsigned logicalSector;
operator std::string() const
{
return fmt::format(
"c{}h{}s{}", logicalCylinder, logicalHead, logicalSector);
}
CylinderHead trackLocation() const
{
return {logicalCylinder, logicalHead};
}
};
inline std::ostream& operator<<(std::ostream& stream, LogicalLocation location)
{
stream << (std::string)location;
return stream;
}
extern std::vector<CylinderHead> parseCylinderHeadsString(const std::string& s);
extern std::string convertCylinderHeadsToString(
const std::vector<CylinderHead>& chs);

View File

@@ -1,21 +1,9 @@
#include "lib/core/globals.h"
#include "lib/data/flux.h"
#include "lib/data/disk.h"
#include "lib/data/sector.h"
#include "lib/data/layout.h"
Sector::Sector(const LogicalLocation& location):
LogicalLocation(location),
physicalTrack(Layout::remapTrackLogicalToPhysical(location.logicalTrack)),
physicalSide(Layout::remapSideLogicalToPhysical(location.logicalSide))
{
}
Sector::Sector(std::shared_ptr<const TrackInfo>& layout, unsigned sectorId):
LogicalLocation({layout->logicalTrack, layout->logicalSide, sectorId}),
physicalTrack(layout->physicalTrack),
physicalSide(layout->physicalSide)
{
}
Sector::Sector(const LogicalLocation& location): LogicalLocation(location) {}
std::string Sector::statusToString(Status status)
{
@@ -32,7 +20,7 @@ std::string Sector::statusToString(Status status)
case Status::CONFLICT:
return "conflicting data";
default:
return fmt::format("unknown error {}", status);
return fmt::format("unknown error {}", (int)status);
}
}

View File

@@ -3,36 +3,10 @@
#include "lib/core/bytes.h"
#include "lib/data/fluxmap.h"
#include "lib/data/locations.h"
class Record;
class TrackInfo;
struct LogicalLocation
{
unsigned logicalTrack;
unsigned logicalSide;
unsigned logicalSector;
std::tuple<int, int, int> key() const
{
return std::make_tuple(logicalTrack, logicalSide, logicalSector);
}
bool operator==(const LogicalLocation& rhs) const
{
return key() == rhs.key();
}
bool operator!=(const LogicalLocation& rhs) const
{
return key() != rhs.key();
}
bool operator<(const LogicalLocation& rhs) const
{
return key() < rhs.key();
}
};
class LogicalTrackLayout;
struct Sector : public LogicalLocation
{
@@ -57,36 +31,24 @@ struct Sector : public LogicalLocation
nanoseconds_t headerEndTime = 0;
nanoseconds_t dataStartTime = 0;
nanoseconds_t dataEndTime = 0;
unsigned physicalTrack = 0;
unsigned physicalSide = 0;
std::optional<CylinderHead> physicalLocation = {};
Bytes data;
std::vector<std::shared_ptr<Record>> records;
Sector() {}
Sector(std::shared_ptr<const TrackInfo>& layout, unsigned sectorId = 0);
Sector(const Sector& other) = default;
Sector& operator=(const Sector& other) = default;
Sector(const LogicalLocation& location);
std::tuple<int, int, int, Status> key() const
{
return std::make_tuple(
logicalTrack, logicalSide, logicalSector, status);
logicalCylinder, logicalHead, logicalSector, status);
}
bool operator==(const Sector& rhs) const
std::strong_ordering operator<=>(const Sector& rhs) const
{
return key() == rhs.key();
}
bool operator!=(const Sector& rhs) const
{
return key() != rhs.key();
}
bool operator<(const Sector& rhs) const
{
return key() < rhs.key();
return key() <=> rhs.key();
}
};
@@ -95,7 +57,7 @@ struct fmt::formatter<Sector::Status> : formatter<string_view>
{
auto format(Sector::Status status, format_context& ctx) const
{
return format_to(ctx.out(), "{}", Sector::statusToString(status));
return fmt::format_to(ctx.out(), "{}", Sector::statusToString(status));
}
};

View File

@@ -4,7 +4,7 @@
#include "lib/config/config.h"
#include "lib/decoders/decoders.h"
#include "lib/data/fluxmapreader.h"
#include "lib/data/flux.h"
#include "lib/data/disk.h"
#include "protocol.h"
#include "lib/decoders/rawbits.h"
#include "lib/data/sector.h"
@@ -13,20 +13,25 @@
#include "lib/data/layout.h"
#include <numeric>
std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
std::shared_ptr<Track> Decoder::decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo)
const std::shared_ptr<const PhysicalTrackLayout>& ptl)
{
_trackdata = std::make_shared<TrackDataFlux>();
_ltl = ptl->logicalTrackLayout;
_trackdata = std::make_shared<Track>();
_trackdata->fluxmap = fluxmap;
_trackdata->trackInfo = trackInfo;
_trackdata->ptl = ptl;
_trackdata->ltl = ptl->logicalTrackLayout;
FluxmapReader fmr(*fluxmap);
_fmr = &fmr;
auto newSector = [&]
{
_sector = std::make_shared<Sector>(trackInfo, 0);
_sector = std::make_shared<Sector>(LogicalLocation{0, 0, 0});
_sector->physicalLocation = std::make_optional<CylinderHead>(
ptl->physicalCylinder, ptl->physicalHead);
_sector->status = Sector::MISSING;
};
@@ -67,6 +72,7 @@ std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
before = fmr.tell();
decodeDataRecord();
_sector->data = _sector->data.slice(0, _ltl->sectorSize);
after = fmr.tell();
if (_sector->status != Sector::DATA_MISSING)
@@ -84,11 +90,7 @@ std::shared_ptr<TrackDataFlux> Decoder::decodeToSectors(
}
if (_sector->status != Sector::MISSING)
{
auto trackLayout = Layout::getLayoutOfTrack(
_sector->logicalTrack, _sector->logicalSide);
_trackdata->sectors.push_back(_sector);
}
_trackdata->allSectors.push_back(_sector);
}
return _trackdata;

View File

@@ -6,15 +6,16 @@
#include "lib/data/fluxmapreader.h"
#include "lib/decoders/fluxdecoder.h"
class Sector;
class Fluxmap;
class FluxMatcher;
class FluxmapReader;
class RawBits;
class DecoderProto;
class Config;
class DecoderProto;
class FluxMatcher;
class Fluxmap;
class FluxmapReader;
class PhysicalTrackLayout;
class RawBits;
class Sector;
#include "lib/data/flux.h"
#include "lib/data/disk.h"
extern void setDecoderManualClockRate(double clockrate_us);
@@ -52,9 +53,9 @@ public:
};
public:
std::shared_ptr<TrackDataFlux> decodeToSectors(
std::shared_ptr<Track> decodeToSectors(
std::shared_ptr<const Fluxmap> fluxmap,
std::shared_ptr<const TrackInfo>& trackInfo);
const std::shared_ptr<const PhysicalTrackLayout>& ptl);
void pushRecord(
const Fluxmap::Position& start, const Fluxmap::Position& end);
@@ -104,7 +105,8 @@ protected:
virtual void decodeDataRecord() {};
const DecoderProto& _config;
std::shared_ptr<TrackDataFlux> _trackdata;
std::shared_ptr<const LogicalTrackLayout> _ltl;
std::shared_ptr<Track> _trackdata;
std::shared_ptr<Sector> _sector;
std::unique_ptr<FluxDecoder> _decoder;
std::vector<bool> _recordBits;

View File

@@ -5,6 +5,7 @@
#include "lib/encoders/encoders.pb.h"
#include "lib/config/proto.h"
#include "lib/data/layout.h"
#include "lib/data/locations.h"
#include "lib/data/image.h"
#include "protocol.h"
@@ -23,25 +24,24 @@ nanoseconds_t Encoder::calculatePhysicalClockPeriod(
}
std::shared_ptr<const Sector> Encoder::getSector(
std::shared_ptr<const TrackInfo>& trackInfo,
const Image& image,
unsigned sectorId)
const CylinderHead& ch, const Image& image, unsigned sectorId)
{
return image.get(trackInfo->logicalTrack, trackInfo->logicalSide, sectorId);
return image.get(ch.cylinder, ch.head, sectorId);
}
std::vector<std::shared_ptr<const Sector>> Encoder::collectSectors(
std::shared_ptr<const TrackInfo>& trackLayout, const Image& image)
const LogicalTrackLayout& ltl, const Image& image)
{
std::vector<std::shared_ptr<const Sector>> sectors;
for (unsigned sectorId : trackLayout->diskSectorOrder)
for (unsigned sectorId : ltl.diskSectorOrder)
{
const auto& sector = getSector(trackLayout, image, sectorId);
const auto& sector =
getSector({ltl.logicalCylinder, ltl.logicalHead}, image, sectorId);
if (!sector)
error("sector {}.{}.{} is missing from the image",
trackLayout->logicalTrack,
trackLayout->logicalSide,
ltl.logicalCylinder,
ltl.logicalHead,
sectorId);
sectors.push_back(sector);
}

View File

@@ -1,13 +1,14 @@
#ifndef ENCODERS_H
#define ENCODERS_H
class Config;
class CylinderHead;
class EncoderProto;
class Fluxmap;
class Image;
class Layout;
class LogicalTrackLayout;
class Sector;
class TrackInfo;
class Config;
class Encoder
{
@@ -19,15 +20,12 @@ public:
public:
virtual std::shared_ptr<const Sector> getSector(
std::shared_ptr<const TrackInfo>&,
const Image& image,
unsigned sectorId);
const CylinderHead& ch, const Image& image, unsigned sectorId);
virtual std::vector<std::shared_ptr<const Sector>> collectSectors(
std::shared_ptr<const TrackInfo>&, const Image& image);
const LogicalTrackLayout& ltl, const Image& image);
virtual std::unique_ptr<Fluxmap> encode(
std::shared_ptr<const TrackInfo>& trackInfo,
virtual std::unique_ptr<Fluxmap> encode(const LogicalTrackLayout& ltl,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) = 0;

View File

@@ -28,14 +28,6 @@ std::unique_ptr<Fluxmap> readStream(
{
std::string suffix = fmt::format("{:02}.{}.raw", track, side);
FILE* fp = fopen(dir.c_str(), "r");
if (fp)
{
fclose(fp);
int i = dir.find_last_of("/\\");
dir = dir.substr(0, i);
}
DIR* dirp = opendir(dir.c_str());
if (!dirp)
error("cannot access path '{}'", dir);

View File

@@ -17,228 +17,246 @@
#include <sys/types.h>
#include "fmt/chrono.h"
namespace
static uint32_t ticks_to_a2r(uint32_t ticks)
{
uint32_t ticks_to_a2r(uint32_t ticks)
return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
}
class A2RSink : public FluxSink
{
public:
A2RSink(const std::string& filename):
_filename(filename),
_bytes{},
_writer(_bytes.writer())
{
return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
time_t now{std::time(nullptr)};
auto t = gmtime(&now);
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
}
class A2RFluxSink : public FluxSink
~A2RSink()
{
public:
A2RFluxSink(const A2RFluxSinkProto& lconfig):
_config(lconfig),
_bytes{},
_writer{_bytes.writer()}
// FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout();
auto [minCylinder, maxCylinder, minHead, maxHead] =
diskLayout->getPhysicalBounds();
_minCylinder = minCylinder;
_maxCylinder = maxCylinder;
_minHead = minHead;
_maxHead = maxHead;
log("A2R: writing A2R {} file containing {} tracks...",
(_minHead == _maxHead) ? "single sided" : "double sided",
_maxCylinder - _minCylinder + 1);
writeHeader();
writeInfo();
writeStream();
writeMeta();
std::ofstream of(_filename, std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
_bytes.writeTo(of);
of.close();
}
private:
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
{
_writer.write_le32(chunk_id);
_writer.write_le32(data.size());
_writer += data;
}
void writeHeader()
{
static const uint8_t a2r2_fileheader[] = {
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
}
void writeInfo()
{
Bytes info;
auto writer = info.writer();
writer.write_8(A2R_INFO_CHUNK_VERSION);
auto version_str_padded = fmt::format("{: <32}", "FluxEngine");
assert(version_str_padded.size() == 32);
writer.append(version_str_padded);
writer.write_8(
(globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
? A2R_DISK_525
: A2R_DISK_35);
writer.write_8(1); // write protected
writer.write_8(1); // synchronized
writeChunkAndData(A2R_CHUNK_INFO, info);
}
void writeMeta()
{
Bytes meta;
auto writer = meta.writer();
for (auto& i : _metadata)
{
time_t now{std::time(nullptr)};
auto t = gmtime(&now);
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
writer.append(i.first);
writer.write_8('\t');
writer.append(i.second);
writer.write_8('\n');
}
writeChunkAndData(A2R_CHUNK_META, meta);
}
void writeStream()
{
// A STRM always ends with a 255, even though this could ALSO
// indicate the first byte of a multi-byte sequence
_strmWriter.write_8(255);
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
}
public:
void addFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
if (!fluxmap.bytes())
{
return;
}
~A2RFluxSink()
// Writing from an image (as opposed to from a floppy) will
// contain exactly one revolution and no index events.
auto is_image = [](auto& fluxmap)
{
auto physicalLocations = Layout::computePhysicalLocations();
auto [minTrack, maxTrack, minSide, maxSide] =
Layout::getBounds(physicalLocations);
_minTrack = minTrack;
_maxTrack = maxTrack;
_minSide = minSide;
_maxSide = maxSide;
log("A2R: writing A2R {} file containing {} tracks...",
(_minSide == _maxSide) ? "single sided" : "double sided",
_maxTrack - _minTrack + 1);
writeHeader();
writeInfo();
writeStream();
writeMeta();
std::ofstream of(
_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
_bytes.writeTo(of);
of.close();
}
private:
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
{
_writer.write_le32(chunk_id);
_writer.write_le32(data.size());
_writer += data;
}
void writeHeader()
{
static const uint8_t a2r2_fileheader[] = {
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
}
void writeInfo()
{
Bytes info;
auto writer = info.writer();
writer.write_8(A2R_INFO_CHUNK_VERSION);
auto version_str_padded = fmt::format("{: <32}", "FluxEngine");
assert(version_str_padded.size() == 32);
writer.append(version_str_padded);
writer.write_8(
(globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
? A2R_DISK_525
: A2R_DISK_35);
writer.write_8(1); // write protected
writer.write_8(1); // synchronized
writeChunkAndData(A2R_CHUNK_INFO, info);
}
void writeMeta()
{
Bytes meta;
auto writer = meta.writer();
for (auto& i : _metadata)
{
writer.append(i.first);
writer.write_8('\t');
writer.append(i.second);
writer.write_8('\n');
}
writeChunkAndData(A2R_CHUNK_META, meta);
}
void writeStream()
{
// A STRM always ends with a 255, even though this could ALSO
// indicate the first byte of a multi-byte sequence
_strmWriter.write_8(255);
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
}
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
if (!fluxmap.bytes())
{
return;
}
// Writing from an image (as opposed to from a floppy) will contain
// exactly one revolution and no index events.
auto is_image = [](auto& fluxmap)
{
FluxmapReader fmr(fluxmap);
fmr.skipToEvent(F_BIT_INDEX);
// but maybe there is no index, if we're writing from an image
// to an a2r
return fmr.eof();
};
// Write the flux data into its own Bytes
Bytes trackBytes;
auto trackWriter = trackBytes.writer();
auto write_one_flux = [&](unsigned ticks)
{
auto value = ticks_to_a2r(ticks);
while (value > 254)
{
trackWriter.write_8(255);
value -= 255;
}
trackWriter.write_8(value);
};
int revolution = 0;
uint32_t loopPoint = 0;
uint32_t totalTicks = 0;
FluxmapReader fmr(fluxmap);
fmr.skipToEvent(F_BIT_INDEX);
// but maybe there is no index, if we're writing from an
// image to an a2r
return fmr.eof();
};
auto write_flux = [&](unsigned maxTicks = ~0u)
{
unsigned ticksSinceLastPulse = 0;
// Write the flux data into its own Bytes
Bytes trackBytes;
auto trackWriter = trackBytes.writer();
while (!fmr.eof() && totalTicks < maxTicks)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
if (event & F_BIT_PULSE)
{
write_one_flux(ticksSinceLastPulse);
ticksSinceLastPulse = 0;
}
if (event & F_BIT_INDEX && revolution == 0)
{
loopPoint = totalTicks;
revolution += 1;
}
}
};
if (is_image(fluxmap))
{
// A timing stream with no index represents exactly one
// revolution with no index. However, a2r nominally contains 450
// degress of rotation, 250ms at 300rpm.
write_flux();
loopPoint = totalTicks;
fmr.rewind();
revolution += 1;
write_flux(totalTicks * 5 / 4);
}
else
{
// We have an index, so this is a real read from a floppy and
// should be "one revolution plus a bit"
fmr.skipToEvent(F_BIT_INDEX);
write_flux();
}
uint32_t chunk_size = 10 + trackBytes.size();
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
_strmWriter.write_8(cylinder);
else
_strmWriter.write_8((cylinder << 1) | head);
_strmWriter.write_8(A2R_TIMING);
_strmWriter.write_le32(trackBytes.size());
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
_strmWriter += trackBytes;
}
operator std::string() const override
auto write_one_flux = [&](unsigned ticks)
{
return fmt::format("a2r({})", _config.filename());
auto value = ticks_to_a2r(ticks);
while (value > 254)
{
trackWriter.write_8(255);
value -= 255;
}
trackWriter.write_8(value);
};
int revolution = 0;
uint32_t loopPoint = 0;
uint32_t totalTicks = 0;
FluxmapReader fmr(fluxmap);
auto write_flux = [&](unsigned maxTicks = ~0u)
{
unsigned ticksSinceLastPulse = 0;
while (!fmr.eof() && totalTicks < maxTicks)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
if (event & F_BIT_PULSE)
{
write_one_flux(ticksSinceLastPulse);
ticksSinceLastPulse = 0;
}
if (event & F_BIT_INDEX && revolution == 0)
{
loopPoint = totalTicks;
revolution += 1;
}
}
};
if (is_image(fluxmap))
{
// A timing stream with no index represents exactly one
// revolution with no index. However, a2r nominally contains
// 450 degress of rotation, 250ms at 300rpm.
write_flux();
loopPoint = totalTicks;
fmr.rewind();
revolution += 1;
write_flux(totalTicks * 5 / 4);
}
else
{
// We have an index, so this is a real read from a floppy
// and should be "one revolution plus a bit"
fmr.skipToEvent(F_BIT_INDEX);
write_flux();
}
private:
const A2RFluxSinkProto& _config;
Bytes _bytes;
ByteWriter _writer;
Bytes _strmBytes;
ByteWriter _strmWriter{_strmBytes.writer()};
std::map<std::string, std::string> _metadata;
int _minSide;
int _maxSide;
int _minTrack;
int _maxTrack;
};
} // namespace
uint32_t chunk_size = 10 + trackBytes.size();
std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink(
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
_strmWriter.write_8(cylinder);
else
_strmWriter.write_8((cylinder << 1) | head);
_strmWriter.write_8(A2R_TIMING);
_strmWriter.write_le32(trackBytes.size());
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
_strmWriter += trackBytes;
}
private:
std::string _filename;
Bytes _bytes;
ByteWriter _writer;
Bytes _strmBytes;
ByteWriter _strmWriter{_strmBytes.writer()};
std::map<std::string, std::string> _metadata;
int _minHead;
int _maxHead;
int _minCylinder;
int _maxCylinder;
};
class A2RFluxSinkFactory : public FluxSinkFactory
{
public:
A2RFluxSinkFactory(const A2RFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<FluxSink> create() override
{
return std::make_unique<A2RSink>(_config.filename());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.filename());
}
operator std::string() const override
{
return fmt::format("a2r({})", _config.filename());
}
private:
const A2RFluxSinkProto& _config;
};
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createA2RFluxSinkFactory(
const A2RFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new A2RFluxSink(config));
return std::unique_ptr<FluxSinkFactory>(new A2RFluxSinkFactory(config));
}

View File

@@ -1,4 +1,5 @@
#include "lib/core/globals.h"
#include "lib/core/logger.h"
#include "lib/config/flags.h"
#include "lib/data/fluxmap.h"
#include "lib/core/bytes.h"
@@ -11,27 +12,29 @@
#include <sys/stat.h>
#include <sys/types.h>
class AuFluxSink : public FluxSink
class AuSink : public FluxSink
{
public:
AuFluxSink(const AuFluxSinkProto& config): _config(config) {}
~AuFluxSink()
AuSink(const std::string& directory, bool indexMarkers):
_directory(directory),
_indexMarkers(indexMarkers)
{
std::cerr << "Warning: do not play these files, or you will break your "
"speakers and/or ears!\n";
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
~AuSink()
{
log("Warning: do not play these files, or you will break your "
"speakers and/or ears!");
}
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
unsigned totalTicks = fluxmap.ticks() + 2;
unsigned channels = _config.index_markers() ? 2 : 1;
unsigned channels = _indexMarkers ? 2 : 1;
mkdir(_config.directory().c_str(), 0744);
mkdir(_directory.c_str(), 0744);
std::ofstream of(
fmt::format(
"{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
fmt::format("{}/c{:02d}.h{:01d}.au", _directory, track, head),
std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
@@ -73,7 +76,7 @@ public:
if (event & F_BIT_PULSE)
data[timestamp * channels + 0] = 0x7f;
if (_config.index_markers() && (event & F_BIT_INDEX))
if (_indexMarkers && (event & F_BIT_INDEX))
data[timestamp * channels + 1] = 0x7f;
}
@@ -81,6 +84,27 @@ public:
}
}
private:
std::string _directory;
bool _indexMarkers;
};
class AuFluxSinkFactory : public FluxSinkFactory
{
public:
AuFluxSinkFactory(const AuFluxSinkProto& config): _config(config) {}
std::unique_ptr<FluxSink> create() override
{
return std::make_unique<AuSink>(
_config.directory(), _config.index_markers());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.directory());
}
operator std::string() const override
{
return fmt::format("au({})", _config.directory());
@@ -90,8 +114,8 @@ private:
const AuFluxSinkProto& _config;
};
std::unique_ptr<FluxSink> FluxSink::createAuFluxSink(
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createAuFluxSinkFactory(
const AuFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new AuFluxSink(config));
return std::unique_ptr<FluxSinkFactory>(new AuFluxSinkFactory(config));
}

View File

@@ -16,15 +16,10 @@
#include <sys/types.h>
#include <filesystem>
class Fl2FluxSink : public FluxSink
class Fl2Sink : public FluxSink
{
public:
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const std::string& filename): _filename(filename)
Fl2Sink(const std::string& filename): _filename(filename)
{
std::ofstream of(filename);
if (!of.is_open())
@@ -33,7 +28,7 @@ public:
std::filesystem::remove(filename);
}
~Fl2FluxSink()
~Fl2Sink()
{
log("FL2: writing {}", _filename);
@@ -54,31 +49,51 @@ public:
saveFl2File(_filename, proto);
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
void addFlux(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);
}
private:
std::string _filename;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
};
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
class Fl2FluxSinkFactory : public FluxSinkFactory
{
public:
Fl2FluxSinkFactory(const std::string& filename): _filename(filename) {}
std::unique_ptr<FluxSink> create() override
{
return std::make_unique<Fl2Sink>(_filename);
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_filename);
}
public:
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
private:
const std::string _filename;
};
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createFl2FluxSinkFactory(
const Fl2FluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config));
return std::unique_ptr<FluxSinkFactory>(
new Fl2FluxSinkFactory(config.filename()));
}
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createFl2FluxSinkFactory(
const std::string& filename)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(filename));
return std::unique_ptr<FluxSinkFactory>(new Fl2FluxSinkFactory(filename));
}

View File

@@ -7,36 +7,37 @@
#include "lib/core/utils.h"
#include <regex>
std::unique_ptr<FluxSink> FluxSink::create(Config& config)
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::create(Config& config)
{
if (!config.hasFluxSink())
error("no flux sink configured");
return create(config->flux_sink());
}
std::unique_ptr<FluxSink> FluxSink::create(const FluxSinkProto& config)
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::create(
const FluxSinkProto& config)
{
switch (config.type())
{
case FLUXTYPE_DRIVE:
return createHardwareFluxSink(config.drive());
return createHardwareFluxSinkFactory(config.drive());
case FLUXTYPE_A2R:
return createA2RFluxSink(config.a2r());
return createA2RFluxSinkFactory(config.a2r());
case FLUXTYPE_AU:
return createAuFluxSink(config.au());
return createAuFluxSinkFactory(config.au());
case FLUXTYPE_VCD:
return createVcdFluxSink(config.vcd());
return createVcdFluxSinkFactory(config.vcd());
case FLUXTYPE_SCP:
return createScpFluxSink(config.scp());
return createScpFluxSinkFactory(config.scp());
case FLUXTYPE_FLUX:
return createFl2FluxSink(config.fl2());
return createFl2FluxSinkFactory(config.fl2());
default:
return std::unique_ptr<FluxSink>();
return std::unique_ptr<FluxSinkFactory>();
}
}

View File

@@ -4,6 +4,7 @@
#include "lib/config/flags.h"
#include "lib/data/locations.h"
#include <ostream>
#include <filesystem>
class Fluxmap;
class FluxSinkProto;
@@ -14,39 +15,52 @@ class VcdFluxSinkProto;
class ScpFluxSinkProto;
class Fl2FluxSinkProto;
class Config;
class Disk;
class FluxSink
{
public:
FluxSink() {}
virtual ~FluxSink() {}
static std::unique_ptr<FluxSink> createHardwareFluxSink(
const HardwareFluxSinkProto& config);
static std::unique_ptr<FluxSink> createAuFluxSink(
const AuFluxSinkProto& config);
static std::unique_ptr<FluxSink> createA2RFluxSink(
const A2RFluxSinkProto& config);
static std::unique_ptr<FluxSink> createVcdFluxSink(
const VcdFluxSinkProto& config);
static std::unique_ptr<FluxSink> createScpFluxSink(
const ScpFluxSinkProto& config);
static std::unique_ptr<FluxSink> createFl2FluxSink(
const Fl2FluxSinkProto& config);
static std::unique_ptr<FluxSink> createFl2FluxSink(
const std::string& filename);
static std::unique_ptr<FluxSink> create(Config& config);
static std::unique_ptr<FluxSink> create(const FluxSinkProto& config);
public:
/* Writes a fluxmap to a track and side. */
virtual void writeFlux(int track, int side, const Fluxmap& fluxmap) = 0;
void writeFlux(const CylinderHead& location, const Fluxmap& fluxmap)
virtual void addFlux(int track, int side, const Fluxmap& fluxmap) = 0;
void addFlux(const CylinderHead& location, const Fluxmap& fluxmap)
{
writeFlux(location.cylinder, location.head, fluxmap);
addFlux(location.cylinder, location.head, fluxmap);
}
};
class FluxSinkFactory
{
public:
virtual ~FluxSinkFactory() {}
static std::unique_ptr<FluxSinkFactory> createHardwareFluxSinkFactory(
const HardwareFluxSinkProto& config);
static std::unique_ptr<FluxSinkFactory> createAuFluxSinkFactory(
const AuFluxSinkProto& config);
static std::unique_ptr<FluxSinkFactory> createA2RFluxSinkFactory(
const A2RFluxSinkProto& config);
static std::unique_ptr<FluxSinkFactory> createVcdFluxSinkFactory(
const VcdFluxSinkProto& config);
static std::unique_ptr<FluxSinkFactory> createScpFluxSinkFactory(
const ScpFluxSinkProto& config);
static std::unique_ptr<FluxSinkFactory> createFl2FluxSinkFactory(
const Fl2FluxSinkProto& config);
static std::unique_ptr<FluxSinkFactory> createFl2FluxSinkFactory(
const std::string& filename);
static std::unique_ptr<FluxSinkFactory> create(Config& config);
static std::unique_ptr<FluxSinkFactory> create(const FluxSinkProto& config);
public:
/* Creates a writer object. */
virtual std::unique_ptr<FluxSink> create() = 0;
/* Returns whether this is writing to real hardware or not. */
@@ -55,10 +69,19 @@ public:
return false;
}
/* Returns the path (filename or directory) being written to, if there is
* one. */
virtual std::optional<std::filesystem::path> getPath() const
{
return {};
}
virtual operator std::string() const = 0;
};
inline std::ostream& operator<<(std::ostream& stream, FluxSink& flushSink)
inline std::ostream& operator<<(
std::ostream& stream, FluxSinkFactory& flushSink)
{
stream << (std::string)flushSink;
return stream;

View File

@@ -8,15 +8,9 @@
#include "lib/fluxsink/fluxsink.h"
#include "lib/fluxsink/fluxsink.pb.h"
class HardwareFluxSink : public FluxSink
class HardwareSink : public FluxSink
{
public:
HardwareFluxSink(const HardwareFluxSinkProto& conf): _config(conf) {}
~HardwareFluxSink() {}
public:
void writeFlux(int track, int side, const Fluxmap& fluxmap) override
void addFlux(int track, int side, const Fluxmap& fluxmap) override
{
auto& drive = globalConfig()->drive();
usbSetDrive(drive.drive(), drive.high_density(), drive.index_mode());
@@ -25,6 +19,15 @@ public:
return usbWrite(
side, fluxmap.rawBytes(), drive.hard_sector_threshold_ns());
}
};
class HardwareFluxSinkFactory : public FluxSinkFactory
{
public:
std::unique_ptr<FluxSink> create() override
{
return std::make_unique<HardwareSink>();
}
bool isHardware() const override
{
@@ -33,15 +36,12 @@ public:
operator std::string() const override
{
return fmt::format("drive {}", globalConfig()->drive().drive());
return "hardware {}";
}
private:
const HardwareFluxSinkProto& _config;
};
std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createHardwareFluxSinkFactory(
const HardwareFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new HardwareFluxSink(config));
return std::make_unique<HardwareFluxSinkFactory>();
}

View File

@@ -36,41 +36,46 @@ static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
checksum += br.read_8();
}
class ScpFluxSink : public FluxSink
class ScpSink : public FluxSink
{
public:
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig)
ScpSink(const std::string& filename, uint8_t typeByte, bool alignWithIndex):
_filename(filename),
_typeByte(typeByte),
_alignWithIndex(alignWithIndex)
{
auto [minTrack, maxTrack, minSide, maxSide] =
Layout::getBounds(Layout::computePhysicalLocations());
// FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout();
auto [minCylinder, maxCylinder, minHead, maxHead] =
diskLayout->getPhysicalBounds();
_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.type = _typeByte;
_fileheader.start_track = strackno(minCylinder, minHead);
_fileheader.end_track = strackno(maxCylinder, maxHead);
_fileheader.flags = SCP_FLAG_INDEXED;
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
error("you can't write Apple II flux images to SCP files yet");
if (globalConfig()->drive().drive_type() != DRIVETYPE_40TRACK)
_fileheader.flags |= SCP_FLAG_96TPI;
_fileheader.cell_width = 0;
if ((minSide == 0) && (maxSide == 0))
if ((minHead == 0) && (maxHead == 0))
_fileheader.heads = 1;
else if ((minSide == 1) && (maxSide == 1))
else if ((minHead == 1) && (maxHead == 1))
_fileheader.heads = 2;
else
_fileheader.heads = 0;
log("SCP: writing {} tpi {} file containing {} tracks",
(_fileheader.flags & SCP_FLAG_96TPI) ? 96 : 48,
(minSide == maxSide) ? "single sided" : "double sided",
(minHead == maxHead) ? "single sided" : "double sided",
_fileheader.end_track - _fileheader.start_track + 1);
}
~ScpFluxSink()
~ScpSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
@@ -80,7 +85,7 @@ public:
write_le32(_fileheader.checksum, checksum);
log("SCP: writing output file");
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
std::ofstream of(_filename, std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
of.write((const char*)&_fileheader, sizeof(_fileheader));
@@ -88,8 +93,7 @@ public:
of.close();
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
@@ -97,7 +101,8 @@ public:
if (strack >= std::size(_fileheader.track))
{
log("SCP: cannot write track {} head {}, there are not not enough "
log("SCP: cannot write track {} head {}, there are not not "
"enough "
"Track Data Headers.",
track,
head);
@@ -115,7 +120,7 @@ public:
int revolution =
-1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index())
if (_alignWithIndex)
{
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
@@ -134,9 +139,9 @@ public:
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 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))
{
@@ -179,6 +184,32 @@ public:
trackdataWriter += fluxdata;
}
private:
std::string _filename;
uint8_t _typeByte;
bool _alignWithIndex;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
class ScpFluxSinkFactory : public FluxSinkFactory
{
public:
ScpFluxSinkFactory(const ScpFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<FluxSink> create() override
{
return std::make_unique<ScpSink>(_config.filename(),
_config.type_byte(),
_config.align_with_index());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.filename());
}
public:
operator std::string() const override
{
return fmt::format("scp({})", _config.filename());
@@ -186,12 +217,10 @@ public:
private:
const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createScpFluxSinkFactory(
const ScpFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new ScpFluxSink(config));
return std::unique_ptr<FluxSinkFactory>(new ScpFluxSinkFactory(config));
}

View File

@@ -11,18 +11,16 @@
#include <sys/stat.h>
#include <sys/types.h>
class VcdFluxSink : public FluxSink
class VcdSink : public FluxSink
{
public:
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {}
VcdSink(const std::string& directory): _directory(directory) {}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
mkdir(_config.directory().c_str(), 0744);
mkdir(_directory.c_str(), 0744);
std::ofstream of(
fmt::format(
"{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
fmt::format("{}/c{:02d}.h{:01d}.vcd", _directory, track, head),
std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
@@ -64,6 +62,26 @@ public:
of << "\n";
}
private:
const std::string _directory;
};
class VcdFluxSinkFactory : public FluxSinkFactory
{
public:
VcdFluxSinkFactory(const VcdFluxSinkProto& config): _config(config) {}
std::unique_ptr<FluxSink> create() override
{
return std::make_unique<VcdSink>(_config.directory());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.directory());
}
public:
operator std::string() const override
{
return fmt::format("vcd({})", _config.directory());
@@ -73,8 +91,8 @@ private:
const VcdFluxSinkProto& _config;
};
std::unique_ptr<FluxSink> FluxSink::createVcdFluxSink(
std::unique_ptr<FluxSinkFactory> FluxSinkFactory::createVcdFluxSinkFactory(
const VcdFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new VcdFluxSink(config));
return std::unique_ptr<FluxSinkFactory>(new VcdFluxSinkFactory(config));
}

View File

@@ -119,8 +119,14 @@ public:
auto keys = std::views::keys(_v2data);
std::vector<CylinderHead> chs{keys.begin(), keys.end()};
auto [minCylinder, maxCylinder, minHead, maxHead] =
Layout::getBounds(chs);
unsigned minCylinder = std::ranges::min(
chs | std::views::transform(&CylinderHead::cylinder));
unsigned maxCylinder = std::ranges::min(
chs | std::views::transform(&CylinderHead::cylinder));
unsigned minHead = std::ranges::min(
chs | std::views::transform(&CylinderHead::head));
unsigned maxHead = std::ranges::min(
chs | std::views::transform(&CylinderHead::head));
log("A2R: reading A2R {} file with {} cylinders and {} head{}",
(disktype == 1) ? "Apple II"
: (disktype == 2) ? "normal"

View File

@@ -11,9 +11,7 @@ std::unique_ptr<FluxSource> FluxSource::create(Config& config)
{
if (!config.hasFluxSource())
error("no flux source configured");
auto fluxSource = create(config->flux_source());
globalConfig().base()->MergeFrom(fluxSource->getExtraConfig());
return fluxSource;
return create(config->flux_source());
}
std::unique_ptr<FluxSource> FluxSource::create(const FluxSourceProto& config)

View File

@@ -7,7 +7,7 @@
class A2rFluxSourceProto;
class CwfFluxSourceProto;
class DiskFlux;
class Disk;
class EraseFluxSourceProto;
class Fl2FluxSourceProto;
class FluxSourceProto;
@@ -58,7 +58,7 @@ private:
public:
static std::unique_ptr<FluxSource> createMemoryFluxSource(
const DiskFlux& flux);
const Disk& flux);
static std::unique_ptr<FluxSource> create(Config& config);
static std::unique_ptr<FluxSource> create(const FluxSourceProto& spec);

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