Compare commits

...

346 commits

Author SHA1 Message Date
6cf86a4797
fix some whitespace 2022-04-22 11:03:51 -05:00
feb5eac02e
Never write via structures, always write via a ByteWriter 2022-04-22 11:03:46 -05:00
Jeff Epler
00d30fe26b fix several missing override declarations 2022-04-12 11:20:46 -05:00
Jeff Epler
f2083ed5e9 fix macos-only build error (narrowing diagnostic) 2022-04-12 11:20:40 -05:00
Jeff Epler
6ac98d02a7 Merge remote-tracking branch 'origin/master' into applea2r 2022-04-12 10:53:17 -05:00
David Given
8baf4ffd2f
Merge pull request #459 from davidgiven/bk0010formats
Add formats for the BK.
2022-04-06 18:26:40 +02:00
David Given
632357ff9d Add BK documentation. 2022-04-06 18:12:23 +02:00
David Given
074714180b Merge from master. 2022-04-05 01:07:46 +02:00
David Given
bd62b7a9bf
Merge pull request #508 from davidgiven/diskspeed
Automatically compensate for the drive speed
2022-03-29 22:21:19 +01:00
David Given
49723d05cf Update documentation with lots more stuff about disk densities. 2022-03-29 23:08:07 +02:00
David Given
f75422a412 We don't need the _525 variants of the profiles any more. 2022-03-29 19:44:03 +02:00
5e8f35c94e
Fix review items, try again on applesauce compatibility
After these changes, applesauce accepts the image but produces a
non-working result.  passport.py still likes the files just fine, if
they're of the limited sorts that it can handle.

(applesauce wants just one STRM chunk with all the flux from all the
tracks; the whole STRM ends with an extra FF, which is not counted in the
size of any of the TIMING blocks. passport.py / a2archery is considerably
more forgiving)
2022-03-29 10:54:15 -05:00
David Given
15eb88e922 Convert, hopefully, the remaining encoders to do automatic disk sizing. Ther e
may be bugs.
2022-03-29 01:23:35 +02:00
David Given
9a299b758a Correctly set the format byte. 2022-03-29 00:03:59 +02:00
David Given
21afc26b68 Fix sigsegv on non-standard sector sizes. 2022-03-28 22:41:27 +02:00
David Given
5c68b47a29 Add missing file. 2022-03-28 00:19:02 +02:00
David Given
adff739a5d Partial conversion to do automatic scaling of flux based on the disk rotation
speed. Although, something seems to have broken for 5.25" disks.
2022-03-27 23:50:32 +02:00
David Given
0da3d8b231
Merge pull request #497 from davidgiven/inspect
Increase the generated pulse width in the firmware.
2022-03-26 22:30:10 +00:00
David Given
3c17e74f6d Bump the protocol version to ensure people upgrade. 2022-03-26 21:54:29 +00:00
David Given
bf35983ebb Merge from master. 2022-03-26 21:39:32 +00:00
David Given
cc31b325ea
Merge pull request #506 from davidgiven/logical
Rework to use logical track numbers throughout.
2022-03-26 21:24:43 +00:00
David Given
7c0eb464c1 Remove the obsolete news paragraph. 2022-03-26 20:31:41 +01:00
David Given
8884ca09fa Add documentation on drive configuration. 2022-03-26 20:30:53 +01:00
David Given
287f0d8909 Adjust the TPI for an Apple 2 drive. 2022-03-26 13:08:06 +01:00
David Given
26c20e2262 Rename the drive extensions to be more distinct. 2022-03-26 13:00:37 +01:00
David Given
b062582d15 Multiple reads from flux files now work again. 2022-03-26 00:35:35 +01:00
David Given
fb66e49a1f Correct number of cylinders (now that empty tracks aren't ignored any more). 2022-03-26 00:35:20 +01:00
David Given
79e37f2c18 Format. 2022-03-26 00:19:22 +01:00
David Given
9ab1dae553 Correctly support retrying on hardware. 2022-03-26 00:19:07 +01:00
David Given
c5ad0b4bec Fix track display in the visualiser when reading and writing. 2022-03-25 23:01:34 +01:00
David Given
606d1012d3 Don't use <=>. Sigh. 2022-03-25 22:10:27 +01:00
David Given
45f2d98f3c Typo fix? 2022-03-25 21:57:02 +01:00
David Given
5cf15a9b11 Try to work around the issue with <compare>. 2022-03-25 21:37:39 +01:00
David Given
178aa9d32f Remove the obsolete reader.* and rename writer.* to readerwriter.*. 2022-03-25 20:57:42 +01:00
David Given
29f181f9bf Change the readers to correctly set the physical track for each sector using
the mapper (where appropriate).
2022-03-25 20:50:57 +01:00
David Given
86c5cccb08 Rename cylinder to track throughout (mostly). 2022-03-25 20:16:12 +01:00
David Given
ea8af83d61 Add missing header. 2022-03-25 00:45:25 +01:00
David Given
d303067deb Massive refactor to work in logical cylinders everywhere. The logical/physical
mapping is now done at the last stage and should, ideally, be automatic. I'm
sure there are bugs everywhere.
2022-03-25 00:22:28 +01:00
6e817e2d7c
Add a2r as a flux sink
a2r is preferred for apple2 disk archiving and should have a pathway
to emulation via passport.py (woz and dsk conversion)
2022-03-23 14:06:01 -05:00
16277f803c
Allow directly building fluxengine, skipping other targets 2022-03-23 14:05:47 -05:00
ef55e10ff2
fe-write: Only enable the flux source if verifying
Otherwise, a command like
```
./fluxengine-debug write apple2 prodos -i prodos-log.img -d prodos.a2r --no-verify
```
fails saying "Error: no devices found (is one plugged in? Do you have the appropriate permissions?"

This only occurs when the output flux format can't be used as a source.
It probably affects types like .au as well.
2022-03-23 14:05:47 -05:00
David Given
aaccd648b3
Merge pull request #502 from davidgiven/windows
Don't build the Windows CLI as a GUI program.
2022-03-21 19:48:30 +00:00
David Given
9596cbd85a Don't use the wxwidgets settings for the console fluxengine binary, because it
ends up building a windows-mode binary and not a console-mode binary.
2022-03-21 19:25:38 +00:00
David Given
5b6320b61a
Merge pull request #457 from davidgiven/agat
Add support for the Agat 340kB disk format.
2022-03-19 19:04:15 +00:00
David Given
4de4fdc0d1 Add documentation. 2022-03-19 19:13:04 +01:00
David Given
77a0c9f341 Typo fix. 2022-03-19 18:43:20 +01:00
David Given
fc7859dc27 Update comment about the generation of the desync sequence. 2022-03-19 18:41:03 +01:00
David Given
39d6b0525f Fix incredibly stupid bug. 2022-03-19 18:07:10 +01:00
David Given
51e091ded6 Better documentation of the missing clock bit. 2022-03-19 14:00:35 +01:00
David Given
52407848c1 Do more sanity checks for bad sectors, improving the reads by a lot. 2022-03-19 13:55:22 +01:00
David Given
a6e2511e6b Don't test SCP files --- it's taking too long. 2022-03-19 13:55:00 +01:00
David Given
422f3ba8c8 Sort the records and sectors dumps. 2022-03-19 13:54:48 +01:00
David Given
276282e847 Merge from master. 2022-03-19 00:51:59 +01:00
David Given
7782e27cc5
Merge pull request #500 from davidgiven/retry
Retries got broken in a recent change. Fix them.
2022-03-18 19:02:07 +00:00
David Given
4f0a178984 Fix retries to work. 2022-03-18 19:19:08 +01:00
David Given
28ddda4635
Merge pull request #483 from davidgiven/mapper
Add support for soft sector skew
2022-03-16 15:31:26 +00:00
David Given
36e20ec396 Merge from master. 2022-03-16 16:01:24 +01:00
David Given
22e65227fb Increase the generated pulse width to try and generate a stronger signal. This
does seem to make Amiga disks read more reliably.
2022-03-15 23:56:49 +00:00
David Given
9ea68b66f7 Allow viewing the raw bitstream as bytes. 2022-03-16 00:17:29 +01:00
David Given
7d93692468
Merge pull request #496 from davidgiven/fixes1
Fix regression with verification on writes.
2022-03-15 20:13:24 +00:00
David Given
93121275ae Verification on writes now works by default. 2022-03-15 20:49:37 +01:00
David Given
07b48b2e0d
Merge pull request #493 from davidgiven/drive
Move drive configuration settings out of flux_source/flux_sink into a common config area.
2022-03-14 19:39:40 +00:00
David Given
294672d946 Update documentation. 2022-03-13 21:15:18 +01:00
David Given
ba3f806616 Move a lot of drive parameters into a toplevel drive{} config rather than
leaving them in the source/sink configs (which means they have to be set
twice).
2022-03-13 21:13:56 +01:00
David Given
9e4d99faca
Merge pull request #489 from jepler/further-apple-fixes
Further apple fixes - have now booted a DOS 3.3 written with FE + GW
2022-03-08 16:01:15 +00:00
David Given
e89648200b
Merge pull request #490 from jepler/mapper
Upgrade Apple II writing to 'unicorn', fix & explain remapping
2022-03-08 15:13:06 +00:00
3c305e8a37
Fix & Document Apple II sector mapping
* Update documentation (note apple flux writing is a unicorn now even though this isn't quite true until #489)
 * Fix DOS 3.3 mapping
 * Add ProDOS (all versions) mapping
2022-03-08 08:44:14 -06:00
6cfe69634c
Merge remote-tracking branch 'origin/master' into further-apple-fixes 2022-03-08 07:45:20 -06:00
61be3714a5
apple2: encoder: Fix writing of "FF40"
I was writing "FF40" as "FF48": 1111 1111 0000 instead of 1111 1111 00.
This sequence of four zeros will not allow the real hardware to synchronize
to the bitstream.

With this change, I was able to boot a DOS 3.3 disk I rearranged into
"physical" order; some 'there's a disk error' raspberries occur (probably
indicating a data error within a sector) during the boot process, so
something is still obviously marginal, but this is a huge step forward.
2022-03-08 07:44:03 -06:00
0aed615ee5
Fix crash when reading disks written on real apple2
`readRaw8` throws an exception if you run out of data. This did not
turn up during my testing, because I was reading back a disk written by
fluxengine, so everything was aligned to the index pulse and no sector
ever straddled a revolution. On a "real" apple2-written disk, this was
not the case.

Incidentally, the "extra 0" problem exists on a real apple2-written
floppy as well.
2022-03-07 22:40:33 -06:00
David Given
6797037bdb
Merge pull request #488 from davidgiven/multiread
Refactor to deprecate F_DESYNC.
2022-03-07 23:14:07 +00:00
David Given
39f8b25fd8 Tidy. 2022-03-07 00:28:19 +01:00
David Given
96214bf3fd Refactor for better multi-read support. Each read is now held separately, with
F_DESYNC being deprecated, and FluxSource returns an iterator which can be used
to retry reads.
2022-03-07 00:07:42 +01:00
David Given
400cd87802 Update documentation with new dependencies.
Fixes: #487
2022-03-06 12:07:55 +01:00
David Given
00c458db1e
Merge pull request #486 from davidgiven/brother
Add support for creating bootable Brother 120kB disks.
2022-03-06 01:00:45 +00:00
David Given
1454e200db Create bootable Brother disks. 2022-03-06 01:32:19 +01:00
David Given
752875061c Format. 2022-03-06 00:47:11 +01:00
David Given
78186d8a45 Archive. 2022-03-06 00:46:53 +01:00
David Given
a4ef434f11
Merge pull request #485 from davidgiven/brother
Add support for writing 120kB Brother disk images.
2022-03-04 23:49:09 +00:00
David Given
9842c9945d Typo fix. 2022-03-05 00:33:10 +01:00
David Given
6dcd97dedf Create executable files (although possibly not correctly). 2022-03-05 00:15:11 +01:00
David Given
549a984eab Add support for writing 120kB Brother disk images. Also add some utils tests,
because they needed it.
2022-03-04 23:42:29 +01:00
David Given
aa805f81e0 Simplify. 2022-03-04 20:28:09 +01:00
David Given
93a67dadf6 Merge from master. 2022-03-04 20:19:42 +01:00
David Given
e9286f6ae9 Merge. 2022-03-04 20:19:14 +01:00
David Given
a31fcdb753
Merge pull request #484 from davidgiven/brother
Fix the Brother 120kB format.
2022-03-02 23:36:02 +00:00
David Given
0edca836f0 Let the image reader do the physical/logical track mapping. 2022-03-03 00:18:49 +01:00
David Given
8537f291b7 Update README. 2022-03-02 23:57:22 +01:00
David Given
46611ec720 Fix writing to 120kB Brother disks. 2022-03-02 23:56:02 +01:00
David Given
cbc3db8100 Display erased tracks a bit more gracefully. 2022-03-02 23:55:53 +01:00
David Given
7d5b07bf37 Merge from master. 2022-03-02 23:26:18 +01:00
David Given
5bd633d5bb
Merge pull request #481 from jepler/apple2decoder
apple2: Comment decoder, add fix/workaround for stray 0 at start of sector data
2022-03-02 22:26:00 +00:00
David Given
17fdad1d6e Add a hopefully correct AppleDOS sector mapping table. 2022-03-02 23:21:41 +01:00
David Given
1ad26671b0 Merge from master. 2022-03-02 23:15:56 +01:00
David Given
2dc5064409 Add support for remapping sectors. 2022-03-02 23:00:20 +01:00
79eec41bcd
Revamp how extra zeros are handled
It's not OK to call seek() here. Instead, add a function which can read
an apple 8-bit flux value in terms of readRaw8 and readRawBits. Apply
this function to all the data bytes, rather than just the first one.
2022-03-02 07:45:24 -06:00
386d22a45e
Makefile: give a clear error at build time if wx is not available 2022-03-02 07:44:09 -06:00
d90fcbf7ad
Handle extra 0-bits at the start of a sector 2022-03-02 07:43:45 -06:00
c4e4520058
apple2 decoder: explain the progression of sector status values 2022-03-02 07:43:43 -06:00
David Given
dd49f01499
Merge pull request #480 from jepler/apple2encoder
Add apple2 encoder
2022-03-02 10:24:05 +00:00
2c698cee71
Mark apple2 image writing as dinosaur, add note about writing, not ro anymore 2022-03-01 19:05:53 -06:00
87cb4b6d18 Add apple2 encoder
This is tested with encodedecodetest.sh but is not tested on HW yet.
It's likely that the sector order (interleave) doesn't match real systems.
2022-02-28 16:22:53 -06:00
David Given
2b7c747209
Merge pull request #471 from jepler/rx50-pdp11-format
Add support for PDP-11 RX-50 disks
2022-02-27 18:59:10 +00:00
707308b490
Fix alphetization of format list 2022-02-27 10:47:33 -06:00
ed1012bf07
Fix rx50 format description & test it
gap3 follows f10f52ded8/src/image/img.c (L135)
2022-02-27 10:47:22 -06:00
David Given
cc5b2bc27b
Merge pull request #479 from davidgiven/wxw
Add a GUI.
2022-02-27 12:24:38 +00:00
David Given
a1a7cfa735 We don't need the rich text wxwidgets addon. 2022-02-27 13:12:29 +01:00
David Given
4624ff92df Remember to set the high density bit. 2022-02-27 13:11:11 +01:00
David Given
47dde98728 Include the GUI exe in release artifacts. 2022-02-27 13:05:39 +01:00
David Given
6f1031e95b Writing flux now works. 2022-02-27 12:54:57 +01:00
David Given
d5245e3784 Don't capture parameters in static blocks. That never ends well... 2022-02-27 12:51:51 +01:00
David Given
d97e72edb6 The image writers now log to the logger. 2022-02-27 00:27:44 +01:00
David Given
23b9e9ef5f The image readers now log to the logger. 2022-02-27 00:20:20 +01:00
David Given
02c7b86f85 You can write images now. 2022-02-27 00:07:59 +01:00
Paul Devine
f796b6b40d works in physical victor 9000 machine 2022-02-26 14:59:09 -08:00
David Given
528454c361 You can now load images into memory. 2022-02-26 23:32:24 +01:00
9ae3f7e61d
Correct description of media type 2022-02-26 15:41:14 -06:00
David Given
dd33922810 Errors are now caught. 2022-02-26 19:52:07 +01:00
David Given
b52fdb3155 Additional settings pane now works. 2022-02-26 19:33:20 +01:00
edf9f9e714
Implement review suggestions 2022-02-26 12:06:08 -06:00
David Given
38eda6ed3c Add log and settings panels. The log panel is populated during a read (or
write).
2022-02-26 18:36:21 +01:00
David Given
32f0c5dc09 Update buttons as the application state changes. 2022-02-26 18:10:00 +01:00
David Given
b45b45b1b3 Enable double buffering for the visualiser in Windows. 2022-02-26 13:36:28 +00:00
David Given
f1c60b7177 Adjust header order so that things build on Windows. 2022-02-26 13:33:00 +00:00
David Given
2d8ff826f3
Merge pull request #475 from hharte/improve-hard-sector-decode
Improve hard sector decode
2022-02-26 12:37:48 +00:00
Howard M. Harte
d028db1ba3 northstar: Improve decode a little more.
seekToPattern() can skip the index hole if it doesn't find the
SYNC pattern.  If this happens too close to the end of the flux
stream, it can result in a conflicted sector.  In this case,
discard the sector.
2022-02-25 22:08:57 -08:00
Howard M. Harte
2d4e2b87bc micropolis: Improve decode a little more.
seekToPattern() can skip the index hole if it doesn't find the
SYNC pattern.  If this happens too close to the end of the flux
stream, it can result in a conflicted sector.  In this case,
discard the sector.
2022-02-25 21:59:18 -08:00
David Given
1d7a75c7b3 Okay, the visualiser looks pretty much done. 2022-02-26 00:23:17 +01:00
David Given
65584a953d
Merge pull request #473 from hharte/fix-fluxmap-split
fluxmap: Don't push_back() if Fluxmap begins with F_DESYNC.
2022-02-25 11:15:31 +00:00
Howard M. Harte
4cbd19d2e5 fluxmap: Don't push_back() if Fluxmap begins with F_DESYNC.
Fluxmap::split() creates a 0-length Fluxmap if the Fluxmap
begins with F_DESYNC.  Fix this by not doing push_back()
if a F_DESYNC is encountered at the start of the Fluxmap.
2022-02-24 19:07:09 -08:00
David Given
a59b59fea4 First sector visualisation! Doesn't look bad. 2022-02-25 01:31:10 +01:00
David Given
5c063a9de3 Replumb the visualiser data flow... again. I think I might have it right this
time.
2022-02-25 00:36:12 +01:00
David Given
3666a7d7bd Merge. 2022-02-24 23:52:18 +01:00
David Given
8250112c7f
Merge pull request #468 from JohnVeness/JohnVeness-fix-docs
Fix various doc typos
2022-02-24 22:16:48 +00:00
David Given
30957f4105
Merge pull request #472 from davidgiven/proto
Make the internal disk data structures all const.
2022-02-24 22:15:58 +00:00
David Given
eade2e279e Make the internal disk data structures all const, to allow us to pass them to
the GUI UI thread safely.
2022-02-24 22:46:10 +01:00
b4489b9402
Add support for PDP-11 RX-50 disks
According to my source [https://www.cbmstuff.com/forum/showthread.php?tid=634] the format of RX-50 is

 * single sided
 * 80 tracks
 * 10 sectors per track
 * 96 tpi (tracks per inch)
 * 300 rpm (revolutions per minute)
 * 250 KHz data rate

I have a disk labeled
```
BL-T540E-MC
CZUFDE0 MICRO PDP-11
USER TESTS
© 1983, 1894
DIGITAL EQUIPMENT CORP.
```
and stamped 14131.

The image read all sectors and the content looks plausible.  `strings` on it says things like
```
$ strings rx50.img  | grep -i pdp
; This file implements the USER FRIENDLY Diagnostic for the MICRO PDP11 & 11/73
```
however, I don't have an emulator or other system to use the disk image with so
I can't 100% vouch for the image being complete and correct.
2022-02-23 17:24:05 -06:00
David Given
a67b7c80c1 Rework the visualiser to look better, maybe? Also fiddle with the logger
somewhat. There's a pending problem where it's not safe to send mutable objects
through the logger to the visualiser, which will need work.
2022-02-23 23:50:29 +01:00
David Given
dfc0cdd0fa Merge from master. 2022-02-23 20:43:19 +01:00
John Veness
d6faf5b074
Change to consistent "-sided" 2022-02-23 11:05:57 +00:00
John Veness
44ffa6a3b0
Change to consistent "-sided" 2022-02-23 10:58:13 +00:00
John Veness
168b78d9d7
Change to consistent "-sided" 2022-02-23 10:57:17 +00:00
John Veness
bd8f313ccb
Change to consistent "-sided" 2022-02-23 10:56:36 +00:00
David Given
4a0fc3d566
Merge pull request #469 from hharte/fix-warnings
Fix override warnings in decoders.
2022-02-23 10:53:15 +00:00
Howard M. Harte
8d04d17e39 Fix override warnings in decoders. 2022-02-22 22:45:39 -08:00
John Veness
9f44b1e783
Add space between sentences 2022-02-23 00:08:15 +00:00
John Veness
1b15271fe2
Fix "seperated" typo 2022-02-22 23:45:51 +00:00
John Veness
f451d3203c
Made case consistent on "Read only" 2022-02-22 23:44:19 +00:00
John Veness
c713d38c19
Fix representing typo 2022-02-22 23:39:34 +00:00
John Veness
4d51f9d097
Fix GreaseWeazel internal link and bracket 2022-02-22 23:36:25 +00:00
John Veness
8750341862
Fix grammar in building.md 2022-02-22 23:31:56 +00:00
John Veness
3a4fe086ea
Fix resale/rescale typo 2022-02-22 23:06:55 +00:00
John Veness
212e457c4c
Fixed grammar in README 2022-02-22 23:06:06 +00:00
John Veness
790e2b534f
Fixed end bracket in README 2022-02-22 22:54:16 +00:00
David Given
48414f0ce9
Merge pull request #467 from davidgiven/logging
Miscellaneous minor fixes
2022-02-22 22:53:19 +00:00
David Given
b5c3e75f10 Fix the new logger output. 2022-02-22 23:39:08 +01:00
David Given
548e07ce17 Fix off-by-one error in the MX decoder. 2022-02-22 22:51:14 +01:00
David Given
0aa0c6866c Do a very basic read/write visualisation. It looks like suck. 2022-02-22 22:35:13 +01:00
David Given
3d4cf7df26 Merge from master. 2022-02-22 18:12:33 +01:00
David Given
c4ba180a0c
Merge pull request #462 from davidgiven/logging
Overhaul the logging system
2022-02-22 13:11:28 +00:00
David Given
bd392b91b7 Make the C++ old enough for Ubuntu. 2022-02-22 13:56:50 +01:00
David Given
042f7b0502 Log messages are now forwarded to the UI thread. 2022-02-22 00:50:58 +01:00
David Given
0fc5f0ee7d
Merge pull request #465 from hharte/fix-northstar-decoder
northstar: Fix after decoder change.
2022-02-21 21:42:13 +00:00
Howard M. Harte
8cbef669b1 northstar: Fix after decoder change.
Recent changes to the decoders broke North Star.
2022-02-21 13:34:34 -08:00
David Given
f9004fb14c Merge. 2022-02-21 22:22:10 +01:00
David Given
40a42c65c1 The rotational speed message is now done via the logger. 2022-02-21 22:21:46 +01:00
David Given
e14030e369 Actually start using bits of the decoder framework. 2022-02-21 22:02:28 +01:00
David Given
c6cef191a7 Start work on setting the controls in the main window. 2022-02-21 00:57:39 +01:00
David Given
6ca9f83bfe Add the main threading stuff. 2022-02-21 00:14:27 +01:00
David Given
b3fcdc5f40 Come up with a UI design I'm happy with. Start work on the disk visualiser. 2022-02-20 22:41:47 +01:00
David Given
c6591cc11a Start using wxformbuilder for GUI design. 2022-02-20 00:07:47 +01:00
David Given
e31ba479b2 Merge from master. 2022-02-19 23:38:51 +01:00
David Given
659b668012
Merge pull request #463 from hharte/improve_micropolis_decoder
Improve micropolis decoder
2022-02-19 22:34:14 +00:00
Howard M. Harte
d69944dd8c Micropolis: Add support for MZOS checksum.
Vector MZOS uses a different sector checksum than the standard
Micropolis checksum used by MDOS, CP/M and OASIS.  Automatically
detect the checksum on the disk by default, but allow the user
to force the use of a specific checksum using the
--decoder.micropolis.checksum_type parameter.
2022-02-19 14:10:56 -08:00
Howard M. Harte
a25111e411 Micropolis: Improve decode.
* Discard a partial sector at the end of the track.
    * Do not seek to the index mark for the first sector.
    * Use a 64-bit pattern to match the SYNC.
    * If SYNC is found too early, search for a subsequent SYNC.
    * While decoding the sector record, enforce the SYNC pattern
      and track ID.
2022-02-19 14:10:44 -08:00
Howard M. Harte
6866d5d3fa Require 16 sectors for Micropolis disks. 2022-02-19 14:10:35 -08:00
David Given
21b3d1c521 Remove stuff which is too modern for Ubuntu. 2022-02-19 22:56:13 +01:00
David Given
f2bdd1cc49 More work on the logger overhaul: the reader should be done now. 2022-02-19 22:48:44 +01:00
David Given
9f09794ae6
Merge pull request #461 from hharte/fix_upgrade_windows
upgrade-flux-file: Fix for Windows
2022-02-19 21:29:54 +00:00
Howard M. Harte
6a1fb3d829 upgrade-flux-file: Fix for Windows
Close the database and remove the input file prior to renaming.
2022-02-19 13:14:25 -08:00
David Given
631b9768af Attempt to fix the BK formats. 2022-02-17 23:57:23 +01:00
Eugene Bolshakoff
a2d9db85cb adjust some numbers 2022-02-17 19:20:50 +01:00
David Given
4cb9d0b50a
Merge pull request #458 from davidgiven/atarist
Fix the Atari ST profiles
2022-02-16 22:24:48 +01:00
David Given
73f37ef289 Update Atari ST documentation. 2022-02-16 22:06:26 +01:00
David Given
dbda19a209 Make sure that Atari ST disks ignore any spurious sector 66, because of
FastCopy.
2022-02-16 22:03:15 +01:00
David Given
0d7de7bbc0 Add support for ignoring certain sectors (needed for FastCopy disks on the
Atari ST).
2022-02-16 22:02:50 +01:00
David Given
649b78611c Work-in-progress logging overhaul. 2022-02-16 21:24:17 +01:00
Eugene Bolshakoff
22a21d76a4 trying to set clock_mhz the same as for ibm 2022-02-15 15:31:39 +01:00
David Given
5668a74aef Forgot to check in the build script. 2022-02-13 21:21:10 +01:00
David Given
482638601a Add missing files. 2022-02-13 21:07:01 +01:00
David Given
1bfe518f74 First draft (very bad) agat340 decoder. 2022-02-13 20:53:23 +01:00
David Given
eeb5ba40fb
Merge pull request #455 from jepler/serial-behavior-improvements
Some quality of life improvements for Adafruit generic GW-compatibles
2022-02-13 12:43:18 +01:00
33bd912476
Some quality of life improvements for Adafruit generic GW-compatibles
The addition of the new tcsetattr call fixes a problem where interrupting
fluxengine during a flux read will leave data in the device and/or Linux's
serial buffers, so that the next invocation of fluxengine will fail similar
to
```
Error: command returned garbage (0x27 != 0x0 with status 0x31)
```
(the specific value differs because it's actually flux data)

Merely changing the existing tcsetattr call to specify TCSAFLUSH was not
enough; moving it after the 200ms pause seems to be enough.

Note that it doesn't seem feasible in our USB stack to make DTR reset
the device, since that would take down the USB stack and require a fresh
USB connection.

The addition of the special case for `rlen == 0` in read is for when the
GW-compatible board is reset or crashes and usb-disconnects during a
reading operation. Without this change, fluxengine spins forever at 100%
of a CPU, repeating a read().  After the change, this will cause
the host computer to print messages like:
```
  2.1: 200 ms in 68928 bytes
  3.0: Error: serial read returned no data (device removed?)
```
and exit.

I only tested these changes on Linux (Debian with kernel 5.10) and in
particular I don't know if/how it will work on a Mac.
2022-02-12 22:04:18 -06:00
David Given
33f1084f0a Merge from master. 2022-02-13 00:20:12 +01:00
David Given
e8a9b7cae3
Merge pull request #454 from davidgiven/mx
Fix a regression in the MX decoder.
2022-02-13 00:05:24 +01:00
David Given
f6b1d9c493 Fix a regression in the MX decoder. 2022-02-12 23:37:20 +01:00
David Given
624c34b378
Merge pull request #453 from davidgiven/mx
Fix regressions caused by the decoder change.
2022-02-12 22:56:57 +01:00
David Given
bc6753e5bf Correctly record sector positions. 2022-02-12 22:42:40 +01:00
David Given
c539debc84 Fix the Amiga decoder, which got broken with the decoder change. Also fix the
encoder which was always broken (but Amigas apparently didn't care).
2022-02-12 22:42:15 +01:00
David Given
830f4cec0f
Merge pull request #452 from davidgiven/mx
Prevent bad reads on gapless formats.
2022-02-12 15:32:36 +01:00
David Given
03dd9e6e83 Add back support for capturing raw records. 2022-02-12 15:16:25 +01:00
David Given
e8d1c90182 Fix, hopefully, the rest of the decoders. 2022-02-12 15:02:42 +01:00
David Given
0933dc1afa Partially complete rework of all the decoders to avoid seeking inside the
fluxmap. This requires resetting the FluxDecoder, which loses any pending
state, resulting in bad reads for (some) formats which don't have gaps between
sectors --- the DVK MX is the main victim.
2022-02-12 00:55:09 +01:00
David Given
610b7fe95c
Merge pull request #451 from jepler/scp-84track
Support 84-track SCP files
2022-02-11 16:25:35 +01:00
a6bf6f901f
Error gracefully when requested to write a too-large scp image
This prevents a crash when more tracks were recorded than fit in an
scp image. When I first encountered a problem doing an 84-track operation,
this led to a segfault later on since memory was written past the end
of _fileheader, and (I think) stomped on a pointer.
2022-02-11 09:10:20 -06:00
ca2e37e852
Include 168 track data headers
According to version 2.2 of the SCP image specification
<https://www.cbmstuff.com/downloads/scp/scp_image_specs.txt>
the number of Track Data Entries for a floppy can be up to 168,
so that up to 84 tracks can be recorded (e.g., fluxengine -c 0-83).

> BYTES 0x10-0x2AF (for floppy image files) are a table of longwords with each entry being
> an offset to a Track Data Header (TDH) for each track that is stored in the image.  The table
> is always sequential.  There is an entry for every track, with up to 168 entries supported for
> floppy disks.  This means that floppy disk images of up to 84 tracks with sides 0/1 are possible.
2022-02-11 09:08:59 -06:00
David Given
d1ffaaa327
Merge pull request #449 from davidgiven/macdsk
Change the mac profiles to produce simple sector images rather the DiskCopy files.
2022-02-08 22:04:48 +01:00
David Given
8b7f551505 Change the mac profiles to produce simple sector images rather the DiskCopy
files (as that seems to be what most people typically use).
2022-02-08 21:21:22 +01:00
David Given
759b3f8fcd
Merge pull request #447 from adafruit/master
fix 2 byte pack for revolution count
2022-02-08 20:04:57 +01:00
lady ada
9ec56f1dfa fix 2 byte pack for revolution count 2022-02-08 11:43:20 -05:00
David Given
84e997949f Merge. 2022-02-06 22:14:11 +01:00
David Given
f82bd45fec Pull out extension profiles into their own section of the documentation list. 2022-02-06 22:13:26 +01:00
David Given
3863724007
Merge pull request #443 from davidgiven/upgrade
Rework flux file upgrading and remove the Sqlite dependency.
2022-02-04 22:16:31 +01:00
David Given
7e0ed9fe93 Try to fix the build on Windows and OSX. 2022-02-04 22:00:44 +01:00
David Given
945e3f3c5f Typo fix. 2022-02-04 21:29:56 +01:00
David Given
6f100219f7 Include the upgrade-flux-file binaries in the releases. 2022-02-04 21:29:04 +01:00
David Given
89688394f8 Replace the upgradefluxfile builtin with a seperate upgrade-flux-file tool.
This allows us to remove all the SQL stuff from the main program, and restores
the ability to upgrade from version 2 SQL files.
2022-02-04 21:27:24 +01:00
David Given
091ef6d972
Merge pull request #439 from tdaede/d88_doublestep
D88: Add 2D (40 track double density) support.
2022-02-03 00:27:55 +01:00
Eric Anderson
e501c44985 Merge branch 'master' into micropolis-275 2022-01-29 10:09:35 -07:00
Eric Anderson
bc9b761903 Prefer IMG for Micropolis, and use a vgi profile to swap format 2022-01-29 10:05:54 -07:00
Thomas Daede
dfb461b05c D88: Add 2D (40 track double density) support.
This assumes a 360rpm drive, and double stepping.

If fluxengine supports the concept of 40 vs 80 track drives in the
future, it might make sense for this to assume a 300rpm drive
instead.
2022-01-28 10:50:36 -08:00
David Given
86f6a2f722
Merge pull request #437 from davidgiven/verify
Fix --no-verify.
2022-01-27 22:05:23 +01:00
David Given
77d6d0d5be Fix --no-verify. 2022-01-27 20:49:38 +01:00
David Given
4946909c6d
Merge pull request #436 from davidgiven/dim
Fix some autoconfigure issues.
2022-01-27 00:07:23 +01:00
David Given
2439736cb4 When autoconfiguring, set up the decoder to allow disk verification. 2022-01-26 23:53:44 +01:00
David Given
8fb2ad1986 Automatically set the number of heads and cylinders where appropriate. 2022-01-26 23:53:16 +01:00
David Given
1d7bbcec66
Merge pull request #435 from davidgiven/brother
Don't use signed values for track/sector/side values
2022-01-26 23:29:51 +01:00
David Given
6ac9d34aac Don't rely on the geometry autodetector for Brother formats. 2022-01-26 23:13:40 +01:00
David Given
3369029e9a Don't use signed ints for the track/side/sector values. 2022-01-26 23:13:22 +01:00
David Given
11f411b745
Merge pull request #433 from davidgiven/rawread
Don't crash when trying to read flux from a fl2 file where there isn't any.
2022-01-26 23:09:48 +01:00
David Given
9bb6e15900 Don't crash when trying to read flux from a fl2 file where there isn't any
flux.
2022-01-26 22:09:50 +01:00
David Given
d2a6b37f5f
Merge pull request #430 from tdaede/greaseweazle_time_read
Support time-based reads for Greaseweazle V24+.
2022-01-26 21:17:44 +01:00
David Given
0c1f6163f7
Merge pull request #432 from tdaede/no_verify
Add flag to skip verification during write.
2022-01-26 21:16:21 +01:00
Thomas Daede
6860317a58 Add flag to skip verification during write.
Fixes #425
2022-01-26 11:45:28 -08:00
Thomas Daede
77257b0989 Support time-based reads for Greaseweazle V24+.
This substantially speeds up read and verify operations, allowing
only 1.2 revs rather than 2 revs to be read.

Fixes #426
2022-01-26 08:02:30 -08:00
David Given
8f35da5163
Merge pull request #428 from tdaede/doc_pc98
Add documentation for PC-98, X68000, etc.
2022-01-26 17:00:40 +01:00
Thomas Daede
82d8c40b5f Add documentation for PC-98, X68000, etc. 2022-01-25 09:53:56 -08:00
David Given
dbfc0f98c3
Merge pull request #422 from tdaede/nfdr0
Add NFD r0 image reader.
2022-01-22 23:28:55 +01:00
David Given
ca15bf4a92
Merge pull request #417 from tdaede/fluxsink_ratio
Add option to rescale flux source pulses.
2022-01-22 23:27:53 +01:00
David Given
3e4d4cc002 Hotfix for Windows build. 2022-01-22 22:29:34 +01:00
David Given
4257544402 Merge. 2022-01-22 21:20:48 +01:00
David Given
83d44bd03e Update documentation. 2022-01-22 20:46:40 +01:00
David Given
745e0685a4 Allow specifying the GreaseWeazle serial port directly (for devices which don't
have the GreaseWeazle VID/PID).
2022-01-22 20:41:27 +01:00
David Given
d6db2128df Toggle DTR on Unixes as well as on Windows. 2022-01-22 20:39:20 +01:00
David Given
6e83ed5654 Update clang-format file. 2022-01-22 20:37:58 +01:00
Thomas Daede
a4f44933ec Add NFD r0 image reader.
This format is very similar to D88, but used mostly for PC-98
rather than PC-88.
2022-01-22 08:33:43 -08:00
David Given
fe080e1d90 Merge from master. 2022-01-21 19:36:18 +01:00
David Given
dd462873d2
Merge pull request #410 from pauldevine/victor9k
Victor9k Double Sided Support
2022-01-20 23:51:24 +01:00
Paul Devine
628fec2c92 Merge branch 'victor9k' of github.com:pauldevine/fluxengine into victor9k 2022-01-20 13:57:25 -08:00
Paul Devine
c4700f3cb8 updated documentation for double-sided, made note about documentaiton bug for track format 2022-01-20 13:56:35 -08:00
Paul Devine
fac606c475 adding Victor9k double sided src format 2022-01-20 13:56:35 -08:00
Paul Devine
5a0aadabc1 adding support for double-sided Victor9k disks 2022-01-20 13:56:35 -08:00
Paul Devine
283b9871cb correct media size in README 2022-01-20 13:56:35 -08:00
David Given
f9d88cc2a0
Merge pull request #420 from davidgiven/contains
Don't use string::contains as it's not sufficiently widespread yet.
2022-01-20 20:40:19 +01:00
David Given
c751810b84 Don't use string::contains. 2022-01-20 20:24:10 +01:00
David Given
2dbc9c39b1
Merge pull request #419 from davidgiven/clang
Add initial clang-format file.
2022-01-20 19:54:07 +01:00
Paul Devine
a9933c7764 updated documentation for double-sided, made note about documentaiton bug for track format 2022-01-19 16:21:40 -08:00
David Given
ffcac3a12d Add initial clang-format file. 2022-01-20 00:08:26 +01:00
David Given
a4130e5f78
Merge pull request #418 from tdaede/auto_fdi
FDI: Add support for automatically setting format.
2022-01-19 23:12:41 +01:00
Thomas Daede
de8f8b7d91 FDI: Add support for automatically setting format. 2022-01-17 17:02:26 -08:00
Thomas Daede
b8c58b12fd Add option to rescale flux source pulses.
This actually scales by the reciprotal of the flux sink, to
allow the same value to be set for both options.
2022-01-17 16:53:50 -08:00
David Given
4af355e1f9
Merge pull request #416 from jepler/patch-1
Fix a little markup error
2022-01-17 15:36:43 +01:00
0e7e9d5643
Line columns up again 2022-01-16 20:08:01 -06:00
fafadc3333
Fix a markup typo 2022-01-16 20:07:08 -06:00
Limor "Ladyada" Fried
752c92bb21
Merge pull request #1 from adafruit/floppzy
PR check!
2022-01-16 13:38:23 -05:00
lady ada
45d7b284e3 1) update CI to add zip on commit for quick testing
2) fix gw readflux command (should be 8 bytes long but was packed as 10)
3) add support for non-gw VID/PID (generic serial port that is gw compatible)
4) manually set/clear DTR on serial port devices - this seems to be essential for tinyusb/arduino CDC

not tested with a gw but ought not to have broken anything!
2022-01-16 12:43:47 -05:00
Paul Devine
085fca7e31 adding Victor9k double sided src format 2022-01-10 15:34:37 -08:00
Paul Devine
8744114790 adding support for double-sided Victor9k disks 2022-01-10 15:33:29 -08:00
Paul Devine
c658a764d0 correct media size in README 2022-01-10 15:06:29 -08:00
David Given
6a07ba850c
Merge pull request #409 from davidgiven/hcs
Support HCS as well as CHS format in the image reader/writer.
2022-01-11 00:03:04 +01:00
David Given
c4e29e74b2 Support HCS as well as CHS format in the image reader/writer. 2022-01-10 23:49:25 +01:00
David Given
40bca8b38c Merge pull request #370 from tdaede/rescale 2022-01-10 23:02:18 +01:00
David Given
18af881fe5 Attach the underlying raw records to Sector structures; add a .raw exporter for
getting the MFM/FM/GCR stream.
2022-01-04 23:49:39 +01:00
David Given
dfd97d9fc5
Merge pull request #359 from tdaede/compdb
Add Make target to build compdb.
2022-01-03 00:15:18 +01:00
David Given
ebd2c329ff
Merge pull request #397 from hharte/master
reader: Fix conflicting sector error message.
2022-01-02 17:38:11 +01:00
David Given
265b169d43 Merge from master. 2022-01-02 15:07:38 +01:00
David Given
d154b1464f
Merge pull request #404 from davidgiven/shugart
Add support for selecting the Shugart or IBMPC GreaseWeazle bus types.
2022-01-01 23:05:26 +01:00
David Given
a32ea6e5f8 Add support for selecting the Shugart or IBMPC GreaseWeazle bus types. 2022-01-01 22:45:27 +01:00
David Given
d7b21bf07e
Merge pull request #400 from davidgiven/update
Update firmware to version 16.
2021-12-31 21:41:05 +01:00
David Given
d120790da7 Update firmware to version 16. 2021-12-31 20:24:51 +00:00
Eric Anderson
bd854d29a4 Merge branch 'master' into micropolis-275 2021-12-29 12:39:07 -06:00
Eric Anderson
d06e3db90b Support .vgi for image writing
vgi was already added to imagereader, but was missing from imagewriter.
2021-12-29 12:23:04 -06:00
Howard M. Harte
5ada533b06 reader: Fix conflicting sector error message.
The conflicting sector error message incorrectly displays the track instead
of the sector in conflict.
2021-12-25 09:10:53 -08:00
David Given
6f5e648751
Merge pull request #394 from hharte/fixserial
Windows: Support serial ports > COM9
2021-12-17 23:06:41 +00:00
Howard M. Harte
9ee5b3eaf3 Windows: Support serial ports > COM9
Windows requires the use of the DOS UNC path name for COM ports
greater than COM9.  Prefix the port name with \\.\
2021-12-17 14:21:19 -08:00
David Given
0560da246b Add wxwidgets to the build dependencies. 2021-12-17 20:24:50 +00:00
David Given
8b6b3ee3b8 Fix wxwidgets building on Windows. We now have it working on all three
platforms.
2021-12-17 20:10:11 +00:00
David Given
45fe83951a
Merge pull request #392 from davidgiven/warnings
Fix some minor build glitches and warnings.
2021-12-16 13:54:26 +00:00
David Given
57e5d8911d Fix missing build dependency causing sporadic build failures. 2021-12-16 14:27:45 +01:00
David Given
5768a766b8 Silence stray warnings. 2021-12-16 14:27:18 +01:00
David Given
051e9e38f3 Add a boilerplate wxwidgets example. 2021-12-15 22:55:57 +01:00
David Given
63a5954dfa
Merge pull request #390 from davidgiven/victor9k
Victor 9000 fixes
2021-12-15 15:08:41 +00:00
David Given
897931f273
Merge pull request #386 from davidgiven/pulsefix
Various firmware fixes
2021-12-14 21:07:06 +00:00
David Given
03e80b2f1c Fix the format so it no longer discards sector 0. 2021-12-14 21:10:38 +01:00
David Given
53c8ec864c Clean up the GreaseWeazle bandwidth tester. Sometimes it doesn't read the right
amount of data from the device?
2021-12-14 18:01:53 +00:00
David Given
5d3002f118 Announce the device serial number on connection again. 2021-12-13 23:24:02 +01:00
David Given
5eeb52660c Update documentation for the USB autodetection. 2021-12-13 23:19:16 +01:00
David Given
3dfafaa278 GreaseWeazle autodetection now works (at least on Linux). 2021-12-13 23:09:33 +01:00
David Given
462bd9ae5e Rewrite the sampler pulse detection... again. 2021-12-12 23:13:23 +00:00
David Given
dab0fcc7c0 Hopefully fix Linux build. 2021-12-12 21:15:30 +01:00
David Given
b8a3e8085e Fix after merge. 2021-12-12 19:58:19 +00:00
David Given
c9d1d72ba3 Merge in the libusbp changes. 2021-12-12 19:50:33 +00:00
David Given
ccf5d513d2 Merge from master. 2021-12-12 19:49:32 +00:00
David Given
d157b7b05d Make libusbp work on Windows; rework the Microsoft-specific bits of the
firmware to match. This does at least allow us to get rid of the patcher.
2021-12-12 17:25:24 +00:00
dg
05981ac46c Make work on OSX. 2021-12-12 15:40:12 +00:00
David Given
08615a5954 Make libusbp build, at least on Linux; port fluxengineusb to use it. 2021-12-12 16:29:51 +01:00
David Given
b6dbe26c7e Raw import of libusbp. 2021-12-11 18:59:44 +01:00
David Given
4f0d61b379
Merge pull request #388 from davidgiven/supercardpro
Tell the various IBM decoders which sectors to expect, for more reliable retries.
2021-12-11 11:26:51 +00:00
David Given
9e5e494f88 Merge from master. 2021-12-11 12:18:06 +01:00
David Given
b15fd05e8d Add support in the IBM decoder for trackdata protos, and then configure the IBM
profiles to know about which sectors it should see in the result image to allow
retries if sectors aren't found.
2021-12-11 12:17:32 +01:00
David Given
faabc28d1b
Merge pull request #387 from davidgiven/supercardpro
Refactor the serial port code.
2021-12-10 23:45:49 +00:00
David Given
4b815846ee ...and fix for OSX. 2021-12-10 23:35:58 +00:00
David Given
fe8be18c4c Fix the serial port code on Windows. 2021-12-10 23:29:20 +00:00
David Given
519c30321d Split the serial port code off into its own file so we can use it for the SCP
driver.
2021-12-11 00:06:14 +01:00
David Given
77be88a5bb Add a package that the tests need. 2021-12-10 21:25:00 +01:00
David Given
8d04931f9f Turns out the high density pin is asserted when _double_ density. Set it
correctly.
2021-12-10 20:07:02 +00:00
David Given
3d1ee7a43e Don't spin in an infinite loop if the decoder tries to find a data record and
the matcher fails to find anything, leaving the seek point unchanged.
2021-12-10 19:56:00 +00:00
David Given
2584a25527 Update components and binary. 2021-12-10 19:55:06 +00:00
David Given
0647a82d38 The tests will run on Windows now. 2021-12-10 19:54:31 +00:00
David Given
c56459c98c Add back the pulse converter, as a 12MHz sample clock only allows pulses which
are 83ns long, which I think is too big. We now sample the index signal at
64MHz which allows pulses down to 16ns.
2021-12-10 17:40:58 +00:00
David Given
7b0a4a057d
Merge pull request #376 from tdaede/scpfix2
Fix SCP writer to always write complete revolutions.
2021-12-09 22:26:36 +00:00
David Given
3c3e520594
Merge pull request #378 from tdaede/ibm_n88basic
Add ibm_n88basic format.
2021-12-08 19:56:35 +00:00
Thomas Daede
ce1daf6a2b Add option to rescale flux sink pulses.
Note that this does not affect the decoder used for verify,
but at least for tame rescaling the decoders are still able to
lock.

This is a precursor to automatic scaling via the disk speed
measurement.
2021-12-07 14:45:32 -08:00
Thomas Daede
68314cd44d Add n88basic format.
This isn't really intended to be used directly (bare images are
normally .d88 etc) but is useful for testing the FM encoder.
2021-12-07 14:39:31 -08:00
David Given
17787b97d4
Merge pull request #371 from tdaede/d88hd
Set media density automatically for D88 format.
2021-12-07 21:54:58 +00:00
David Given
da84297b2c
Merge pull request #380 from tdaede/last_fluxmap
SCP: Only write the last continuous fluxmap.
2021-12-07 21:47:54 +00:00
David Given
e3e3bb770d
Merge pull request #379 from tdaede/no_disk_sector_check
Remove per-track sector check from D88.
2021-12-07 21:45:47 +00:00
David Given
9d4f180741
Merge pull request #381 from hharte/fixnorthstar
Fix North Star after decoder update
2021-12-07 21:44:26 +00:00
David Given
316836d9f6
Merge pull request #382 from darkstar/protofix
Fix a small typo in proto.cc
2021-12-07 17:24:24 +00:00
Michael D
aa3b0a117a Fix a small typo in proto.cc
Also add long-forms "yes" and "no" as valid options
2021-12-07 18:10:53 +01:00
Howard M. Harte
0f7df3281f NorthStar: Fix nsiimagewriter image size in informational message. 2021-12-06 17:57:08 -10:00
Howard M. Harte
4a91a35799 Fix the North Star decoder after the PLL upgrade. 2021-12-06 17:55:54 -10:00
dg
36c2263675 Fix the Micropolis decoder after the PLL upgrade. 2021-12-06 22:20:18 +00:00
David Given
28068d8d31
Merge pull request #375 from davidgiven/fl2
Make the new FL2 format the default.
2021-12-06 22:14:33 +00:00
Thomas Daede
0c3c08db36 SCP: Only write the last continuous fluxmap. 2021-12-06 11:47:42 -08:00
Thomas Daede
bb5d62fe69 Remove per-track sector check from D88.
It's no longer needed to be this strict.
2021-12-06 10:13:30 -08:00
David Given
376270dd53
Merge pull request #377 from tdaede/d88trackfill
Set FM gap fill byte in D88.
2021-12-06 16:21:13 +00:00
Thomas Daede
9056b23eaa Set FM gap fill byte in D88. 2021-12-06 08:10:02 -08:00
Thomas Daede
634be4ae51 Fix SCP writer to always write complete revolutions.
There were a couple of problems that needed to be fixed:

- Fluxmaps can be produced with zero index pulses by encoders.
This is somewhat ambiguous but it's assumed that this means there
is exactly one revolution.

- The SCP unindexed mode wasn't properly supported, as it should
create equal-length revolutions not aligned to indexes. However,
fluxmaps don't contain any information on rotation speed. Therefore,
drop the ability to create unindexed SCPs and slightly change the
meaning of the option to not include pre-index data.

- Real flux reads can potentially produce duplicate index pulses.
Avoid creating zero-length revs.

- Fix off-by-one error for rev count.
2021-12-06 07:41:16 -08:00
David Given
422620f4fe
Merge pull request #374 from davidgiven/ibm
Make the gap fill byte configurable.
2021-12-05 22:27:45 +00:00
dg
ebb5c17be4 Make the IBM format gap fill byte configurable. 2021-12-05 16:43:45 +00:00
dg
1e99fe5b04 Merge from master. 2021-12-05 16:37:04 +00:00
Thomas Daede
66f82f764d Set media density automatically for D88 format. 2021-12-04 10:31:17 -08:00
Thomas Daede
08c1671f21 Add Make target to build compdb. 2021-12-03 02:13:01 -08:00
Eric Anderson
3b95e56418 Support Micropolis variations and prefer VGI extension 2021-11-07 21:54:00 -08:00
Eric Anderson
ce5fcaf172 Micropolis raw 275 byte sectors
These are used by formats like VGI since the ECC varied per machine and
the 10 extra bytes of user data may contain useful information.
2021-10-02 22:59:14 -07:00
364 changed files with 27866 additions and 5892 deletions

42
.clang-format Normal file
View file

@ -0,0 +1,42 @@
---
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignArrayOfStructures: Left
AlignEscapedNewlines: Left
AllowAllArgumentsOnNextLine: 'true'
AllowAllConstructorInitializersOnNextLine: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'true'
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'false'
BinPackParameters: 'false'
BreakConstructorInitializers: 'AfterColon'
BreakBeforeBraces: Allman
BreakInheritanceList: AfterColon
BreakStringLiterals: 'true'
IndentCaseLabels: 'true'
IndentWidth: '4'
ColumnLimit: '80'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
IncludeBlocks: Preserve
IndentWrappedFunctionNames: 'false'
KeepEmptyLinesAtTheStartOfBlocks: 'true'
PointerAlignment: Left
ReflowComments: 'true'
SortIncludes: 'false'
SortUsingDeclarations: 'true'
SpaceAfterTemplateKeyword: 'true'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCtorInitializerColon: 'false'
SpaceBeforeInheritanceColon: 'true'
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: 'false'
...

View file

@ -10,7 +10,7 @@ jobs:
with:
fetch-depth: 1
- name: apt
run: sudo apt update && sudo apt install libusb-1.0-0-dev libsqlite3-dev ninja-build protobuf-compiler
run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev ninja-build protobuf-compiler libwxgtk3.0-gtk3-dev
- name: make
run: make
@ -21,7 +21,7 @@ jobs:
with:
fetch-depth: 1
- name: brew
run: brew install sqlite pkg-config libusb ninja protobuf truncate
run: brew install sqlite pkg-config libusb ninja protobuf truncate wxwidgets
- name: make
run: make
@ -45,6 +45,8 @@ jobs:
zip
mingw-w64-i686-protobuf
vim
diffutils
mingw-w64-i686-wxWidgets
- uses: actions/checkout@v1
with:
fetch-depth: 1
@ -52,3 +54,12 @@ jobs:
run: |
make
- name: zip
run: |
zip -9 fluxengine.zip fluxengine.exe fluxengine-debug.exe fluxengine-gui.exe fluxengine-gui-debug.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.repository.name }}.${{ github.sha }}
path: fluxengine.zip

View file

@ -26,6 +26,8 @@ jobs:
zip
mingw-w64-i686-protobuf
vim
diffutils
mingw-w64-i686-wxWidgets
- uses: actions/checkout@v2
with:
fetch-depth: 1
@ -34,7 +36,7 @@ jobs:
make
- name: zip
run: |
zip -9 fluxengine.zip fluxengine.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex
- name: date
run: |
echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV}

7
.gitignore vendored
View file

@ -1,2 +1,9 @@
.obj
.project
/.ninja*
/brother120tool
/brother240tool
/fluxengine
/brother120tool-*
/brother240tool-*
/fluxengine-*

View file

@ -1,249 +1,249 @@
:4000000000800020110000004110000041100000064A08B5136843F020031360044B1A6803F53F5302331A6001F028F8E8460040FA46004010B5054C237833B9044B13B163
:400040000448AFF300800123237010BD6881FF1F00000000C8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000041
:400080006C81FF1FC8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8881FF1FBC
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8881FF1F3F000080114BDA68196919B9FD
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F046B9704700BF70
:400140008881FF1F10B5C4B2204601F059F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002B13
:400180000CBF02230023002814BF184643F0010002F082FE62B608BD8881FF1F38B50446C5B2284602F0B2F8062002F0CFFA44F00200C0B202F0AAF8062002F0C7FA28460D
:4001C00002F0A4F8BDE83840062002F0A9BA10B5642402F095F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F005F9012805D0204601F01EFA34
:400200002846FFF79FFF204601F002F9314605460246204601F0BEF9204601F0F1F80028FAD1284670BD000038B5044D0024285D013402F03BFA402CF9D138BDAC81FF1FCB
:4002400008B502F055FC002002F05EFC02F070FC02F07AFC80B208BD10B50446012002F06DF8642002F05CFAFFF7EAFF2080002002F064F8642002F053FAFFF7E1FF60809C
:4002800010BD08B502F060FD002002F069FD02F07BFD02F085FD80B208BD10B50446FFF796FF322002F03CFAFFF7EBFF20800120FFF774FF322002F033FAFFF7E2FF6080D2
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0E0FE02A802F07CF802F086F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF627816
:400300002146BDE81040042001F0D2B8DC38000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0DAFFEE
:40034000002002F06DFD002384F8643010BD00BF8881FF1F38B5104D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F865302279094B1A71A378002B14BF45
:400380000220012002F04CFDE07802F043FD2079BDE8384002F07ABD8881FF1FED81FF1F38B50D4C94F8645065B904F16500FFF7D1FF012001F09EFF4FF47A7002F0B0F968
:4003C00084F86A50E368E366012384F86430BDE8384002F0E3B900BF8881FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF767FFFFF7EBFE0120002384F86A007C
:40040000236702F0A3F92A46216F1848FFF759FF144E0027236F9D4216D001F071FF00B13767236F9D4205DD0120FFF7B7FE336F013305E005DA0020FFF7B0FE336F013B0B
:40044000336702F0ABF9E5E7322002F069F92A2DCCBF0020012002F025FDBDE8F8400448FFF72FBF8881FF1FE9380000F03800000D3900002DE9F04F99B062B602F0F8F947
:400480009E49042002F01CFA9D4801F045FF9D4802F0E8FC9C4801F079FF02F0C9FB02F09BFA002002F0BCFC01F094FF0221002000F05CFF954C012001F0D4F8002384F890
:4004C0006730FFF76DFFFFF782FE84F87400FFF72FFF012384F86730FFF762FFFFF777FE84F87500FFF724FF894B94F87400894994F875202546002A14BF0A461A4600286F
:4005000008BF19468448FFF7DCFE0321084602F025F9264602F042F994F8643043B1EA6EEB689B1A41F28832934201D9FFF700FF00F054FF18B97948FFF7C3FE04E000F079
:4005400053FF0028F7D10BE000F048FF10B902F025F9F9E77248FFF7B4FE032001F06EF8032000F04DFF0128D4D16E48FFF7F2FE6D490320FFF738FE94F876106B48FFF7F9
:40058000A0FE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D4033303D4034D0303238DF851
:4005C00020308DF821300F238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD0B4611E083B100227E
:40060000174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF786FD4FF000080DF1200A54
:4006400002F0ACF84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF772FD3A465146022000F023FFB9F10109EBD108F10108B8F1400FE2D12E4B38E04FF076
:40068000010A4FF000080DF1200B02F087F84FF0000959460120FFF7A7FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B464A461F48FFF701FEE0
:4006C0004FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461648FFF7EEFDBAF1000F00F01B81144B1B8807A8ADF81C3095E200BF5501000004
:40070000F900000091000000C50000008881FF1F1F3900001B390000223900003A3900004D390000ED81FF1FFE81FF1F57390000CC380000CE380000663900008239000026
:40074000D0380000206FFFF749FE94F8780001F0F5FD94F8780001F0D9FD02F009FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A7802F0FE021A701A7880
:4007800002F0FE021A7002F0F7FB0220FFF7DAFC012141F6FF734FF48042084602F046FB84F8B60001F068FF08F807000137402FF8D1DFF8B0A200270AF195091FFA89F816
:4007C0000137402F14BF3A4600221AF8010F2244062392F82420402101F082FF424646F240419AF8000001F08DFF08F14008402F1FFA88F8E4D196F8793053B196F87C30C8
:40080000336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241001F0DDFE94F8B60001F09BFE012194F8B60001F06EFEAA
:400840002368002BFCD0002398467360D6F80CA0012701F0A3FFE368B4F87A20CAEB030393420DD367B1042195F8B60001F0C8FE94F8B60001F0D4FE0028F9D107463072CF
:40088000237AFBB96A682B689A4202D1002FE0D118E00220FFF756FC6968402209EB8111022000F005FE6A68674B01321340002BBEBF03F1FF3363F03F03013308F1010820
:4008C0006360C6E70220277AFFF73CFC00221146022000F0EDFD0220FFF734FCFFB2FFF7A3FC002001F012FD37B15848FFF7E9FC0220FFF70DFD06E0554B08A81B88ADF871
:400900002030FFF7F3FC227D4146237A5148FFF7D8FC15E25048FFF7D4FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F003FD95F8780001F0E7FC012054
:4009400001F002FD02F014FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F003FB01214FF4804341F6FF72084601F0CB
:40098000E9FC85F8B60001F077FE08F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F82420402101F090FEB2
:4009C000414646F2475299F8000001F09BFE08F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF0010A4FF0000A2168C1
:400A0000114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059302EB8010BAF1000F16D040223F2102F079
:400A4000F7FA1CE0906400403F0000808C390000D2380000A6390000B939000097650040AC81FF1FAB81FF1F014601370120FFF7BBFBC7EB0903D3F1000A4AEB030A216882
:400A8000B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F080FC85F808806B6895F8B6002B4493F82410D9
:400AC00001F092FD95F8B60001F050FD012195F8B60001F023FD95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F06AFC404601F027FC00E023B1237A54
:400B00005BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F04FFC012001F012FC002001F04FFC042194F8B60001F066FD94F8B60001F072FD8046002873
:400B4000F8D196F8B60001F0FFFC337D327A0293012303920193CDF800A05B463A4649467C48FFF7AEFBC6F81080BAF1000F0BD0FFF75AFB002001F0C9FB237A63B1764854
:400B8000FFF79FFB0220D9E0B945F1D073490120FFF72AFB0137F7E77148FFF792FB714B3DE094F8780001F0C9FB206FFFF716FC6D48FFF786FB94F87930236100232375F8
:400BC000237D002BFCD0012001F0FEFB00233375237D002BFCD0002001F0F6FB002363483361FFF76EFB624B19E0002084F86A00FFF7F4FB5F4B12E094F8743023B195F870
:400C000075200AB985F8782094F875201AB113B9012385F878305848FFF79CFB574B1B88ADF8203008A8FFF761FB89E0FFF780FB02F07CF8002002F01FF82A2701F04AFFE9
:400C4000002001F0EDFE3A46002108A802F0F0F917238DF820308DF8217001F09FFD002001F048FB002002F0DBF8C82001F058FD0DF12200FFF7F0FA0DF13600FFF70DFB01
:400C800001F08CFD012002F0CBF8322001F048FD0DF12600FFF7E0FA0DF13A00FFF7FDFA012001F027FB4FF4967001F039FD01F075FD0DF12E00FFF7CFFA0DF14200FFF71B
:400CC000ECFA002001F016FB4FF4967001F028FD01F064FD022002F0A3F8322001F020FD0DEB0700FFF7B8FA0DF13E00FFF7D5FA012001F0FFFA4FF4967001F011FD01F040
:400D00004DFD0DF13200FFF7A7FA0DF14600FFF7C4FA002001F0EEFA4FF4967001F000FD01F03CFD002002F07BF8002384F86A3001F07EFF01F050FE74E70120FFF7E8FA91
:400D4000032000F07BFC0E48FFF7BBFAFFF7E2BB3F000080C3390000F33900004092FF1FFD390000D4380000053A0000133A0000D6380000D8380000FE81FF1FDA380000E3
:400D8000203A00002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F8002283E80300522203F580731A707F4B7F4A1B787F4EDBB2FE
:400DC000137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0BDF8013C05F003052ED0032DF0D1744B4FF480721A8007221A70724A20
:400E0000002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013C062743F0030302F8013C2378012243F0800323705B4B1A70F9
:400E4000654A137843F02003137000E0FEE707FB056300219A881868013502F0E9F8072DF5D15E485E4E002550F8041F05F1105303F1480221F0FF074933C9B20B44520042
:400E80005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A002313609360136193614F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A7B
:400EC000137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A464B07CA03C31A80454A2833106843F8250C127903F8212CA9
:400F0000424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F8100EC2F8141E1378042043F008031370394B02F5AA521B78A7
:400F40003D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B48221A702E4A402313702E49937013729372082382F81F32BF
:400F800020220A7048710A72294A0A20137001F0DDFB284B88F8006044223D70264D1A7094E80F0007C52B80BDE8F081004800404C0A00480F010049A1460040254200408F
:400FC000224200400440004006400040A2430040A0430040253A0000E8460040FCFFFF478C00004800760040540A0048F846004020760040580A004828760040035001404D
:401000000C0A0048C0510040180A0048200A00482C0A0048380A004832510040440A0048CF0100491D51004001590040235B0040585B004076580040B0430040F9460040F2
:4010400008B501F0C9FF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F0B8FF0C2303604FF0FF33184608BDCC80FF1F9093FF1F80B5114817
:40108000114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1AA64202D041F8045BF9E701381033E5E701F094FFFFF7DAF9D9
:4010C000FEE700BF01000000F43B0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF54F823204FEA830188BF0E4A0133302B4250F3D10C4B1A7814
:401100000C4B1A700C4B084A1A60FFF73BFEBDE8104001F0EDB900BF0004FA050CED00E014ED00E0000000000080FF1F41100000BC760040C080FF1F08ED00E0F8B501F042
:4011400017FF4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C1378054603F07F031370002001F0EAFA2378404A03F0F903F2
:4011800023701378384603F0DF03137023783B43237001F0DBFA282001F0D8FA384B30461A7802F07F021A701A7802F0BF021A7023783343237001F0C9FA2378314A43F0C6
:4011C000040323700023137053702F4AFF2199540133092BFBD1284601F0CEFE0721172001F0FCFA2949172001F0EAFA0721182001F0F4FA2649182001F0E2FA0721152033
:4012000001F0ECFA2349152001F0DAFA0721052001F0E4FA2049052001F0D2FA0721062001F0DCFA1D49062001F0CAFA0721084601F0D4FA1A49072001F0C2FA07210820F8
:4012400001F0CCFA1749082001F0BAFA0021162001F0C4FA1449162001F0B2FA07210C2001F0BCFABDE8F84010490C2001F0A8BAA5430040944300409D6000401260004076
:40128000F851004084600040B592FF1F0B1B000045190000091B00003D1A0000691A0000991A0000D11A0000111B0000851B0000214B224A10B5187000231370204A40209B
:4012C0001370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF400121A6020221A601860802018604FF480701860174804702E
:401300004FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8202C012001F018FE0D4B04221A7010BDD092FF1FD692FF1F39
:40134000D492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F00E100E09E6000409C600040286000401260004070B5074C054623780E461BB9FFF7E0FE0123237031
:4013800031462846BDE87040FFF792BF8092FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4A13700A4A13700A4B03221A70802203F8202C7047D692FF1F4E
:4013C000D492FF1FD592FF1FD192FF1FC092FF1FD392FF1F4893FF1F28600040014B1878704700BFD592FF1F044B1A7802F0FF001AB118780022C0B21A707047D492FF1F52
:40140000024A0C2303FB002040787047DC92FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA0020023880B2704700207047FC5F00401A4A38B50C2303FBA9
:4014400000231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F002070C110021084601F01BF80DE00021084600F0FAFF08E0002180
:40148000084600F0D9FF03E00021084600F0B8FF054B1855EDB2072D03D801F0EDF8034B185538BDDC92FF1FAC92FF1FB592FF1F431E072B2DE9F0470446894615465CD857
:4014C0002F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F50075ADB2254A43EA15230601B354B244EBB28AF80130224BD4
:401500001A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F011F811232946FE2218F8040001F0D6F806F5C04278321FFA89F118F8D2
:40154000040001F0DFF8124D18F80410385D01F04BF80121385D00F0E1FF735D43F002037355735D03F0FD037355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFE7
:40158000DC92FF1FFC5F0040B592FF1FAC92FF1F706000402DE9F047044615468846002940D0431E072B3FD8FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D1DE
:4015C00041462046FFF738FFDFF868A027011AF8040000F0B9FF1223FE222946305D01F07FF807F5C0411FFA88F27831305D01F089F8DFF84490315D1AF8040000F0F4FFE9
:4016000001211AF8040000F089FF17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E000252846BDE8F087B592FF1FAC92FF1F70600040431E072BA7
:401640000AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFDC92FF1FFE5F0040431E072B9FBF024B000108221A547047FE5F004030B51A4A1A491B4D0A
:401680000878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E85450680133013050601088013880B21080ECE718460B780E4C082B0E4A00D003
:4016C00040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4C93FF1F4893FF1F00600040C492FF1FC192FF1FD692FF1FD292FF1FE1
:401700004993FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047D692FF1FD292FF1FC192FF1F4893FF1F4993FF1F30B5164B16491B78E1
:401740000A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B229705168DBB20131516011880130013989B21180ECE7094A1370B3
:40178000094A137883F080031370084B0B221A7030BD00BF296000404C93FF1F00600040C492FF1F4993FF1FD292FF1FC192FF1F064A06231370064A01201370054B802273
:4017C0001A70054B00221A70704700BFD692FF1FC192FF1FD292FF1F4993FF1F054B9A683AB19A68044910709A680988518000229A607047C492FF1F4C93FF1F08B5124BAC
:401800001A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B06D043B900F012FC04E001F0A5FB01E000F046FD10B9034B1C
:4018400003221A7008BD00BF28600040C192FF1F0060004008B5084A084B0120197813880B449BB21380064B00221A70FFF7B6FF044B03221A7008BD4C93FF1F4893FF1F1D
:40188000D692FF1FC192FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE80840FFF746BF0320FFF795FF034B03221A7008BD00BFD692FF1FCC
:4018C000C192FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFD692FF1FC192FF1F08B50A4B1A7832B11A78094942F080020A7000221A70074B00220120D1
:401900001A70FFF76BFF054B03221A7008BD00BFC092FF1F08600040D692FF1FC192FF1F074B1B78DBB2042B05D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF70479E
:40194000D692FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D538BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B066B
:401980001BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378DBB20B700B7803F00F0328788342F1D138BD38BD2860004067
:4019C000C192FF1FD292FF1F4993FF1F29600040054A00231380054A916819B191680B7092685380704700BF4C93FF1FC492FF1F0E4808B503889BB213B9FFF783FE13E0D1
:401A00000B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FE012008BDC492FF1FD692FF1FD292FF1F00600040084B012220
:401A40001A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040DC92FF1F094B02221A700F3B93F82230074B1A7E02F00302012A1EBF0F
:401A8000DA7E82F08002DA7601225A76704700BF0B600040DC92FF1F0B4B04221A700F3B93F83230094B93F8242002F00302012A1EBF93F8272082F0800283F827200122E0
:401AC00083F82520704700BF0B600040DC92FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012A1EBF93F8332082F0800283F83320012283F83120704700BFF5
:401B00000B600040DC92FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5144A4FEA031E7244947850782040C5070DD507FB03652C799F
:401B4000240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C090700133DBB24908D7E7F0BD9F600040DC92FF1F70600040D5
:401B8000FE5F004000F0ACBC70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B782B70FCB12223237001AD03232846637000F08AFE00222046DB
:401BC0001146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F10400EAD108B070BD4F3A00002DE9F0431C4D01222E460C2093
:401C00001F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F8044024064CBF887081F802C001E081F802E000FB0261CC880F
:401C40000132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE8F08300BFDC92FF1F70600040FC5F00400A600040064B074A70
:401C80001B7802EBC30253681A7C824286BF03EBC003586900207047D092FF1FB03A00002DE9F84F424B1A78002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC368147810
:401CC000007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED19A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B01E
:401D000009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF4FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF20
:401D40004FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F800A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFAAB
:401D8000494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083394E7BDE8F88F00BFD592FF1FDC92FF1FD192FF1FFC5F004052
:401DC00070600040C292FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDD592FF1F00212DE9F84F0B464E4E0C2707FB01F4013132190929335553
:401E00004FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187801250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0B5
:401E4000400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BF50
:401E8000A1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F0030607DA012E0CBF07260D264E7181F8018006E0012E0CBF052673
:401EC00009264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F80A5C01F07F0100FB01418D72F2E7FFF767FF114B01211860E6
:401F000000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE8F88F00BFDC92FF1FC292FF1F4A93FF1FD592FF1FD392FF1FCE
:401F4000D892FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D170517841F002015170127912F0800F074A1A4414BF8D238923CF
:401F80009370FFF715BC0020704700BF00600040DC92FF1FFC5F004030B4194B1A7902F07F02531E072B27D8164B0C2404FB02339978154D01F0FE0199700021D970294600
:401FC0001201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E0012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF9C
:4020000000600040DC92FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D2012909D8074A0848535CDBB24354A3780120DBB2535410BD56
:40204000002010BDD592FF1F00600040C292FF1F4A93FF1F38B58A4A8A4C13780021DBB221801806517840F18D800A2900F20581DFE811F05D000301030103010301030167
:402080000B0003017E0003018200D3787C49012B09D17D4B1A787D4B03EBC2035B685B686360122310E0CB78022B12D18878FFF7E5FD002800F0E180436863606368DA7885
:4020C00063689B7843EA02232380BDE83840FFF78FBCCB78032B26D16D4B00228878D5B2854209D3664A91786A4AEE2908BF1346634A917881B106E0187801320028F1D025
:4021000018780344EAE764499278097C914203D16248FFF739FD614B1A78002A00F0AD801A78228018E0BDE8384000F029BF13F0030313D0022B40F0A0802380504B0C21C4
:402140001B7903F07F02564B01FB02339A78554BD2B21A7000225A706360B6E702222280514A11784F4AC9B2117053706260ACE7012323804D4BEFE70123238013794C4AC4
:402180001344E9E701390A2977D8DFE801F037764F76067676760A7620009378454ADBB25AE0937803F0FF0153B9404B1A7891425FD01970404B01201870FFF715FE58E082
:4021C000481EC0B2FFF75AFD0028EED155E0FFF71DFF002851D02A4A384913791279DBB2D2B20A70364A3249D25CCB5C9A4240D0314B01221A70FFF753FD3AE003F00303EE
:40220000012B2BD009D3022B37D11D4B9B78002B33D1BDE83840FFF7BFBE194B9B78012B2BD1214A137803F0FD0315E003F00303012B13D008D3022B1FD1114B9B78E3B9A4
:40224000BDE83840FFF77EBE0D4B9B78012B14D1154A137843F0020313700AE0084B1A795AB998781B791749DBB2CA5C22EA0002CA54BDE83840FFF79BBA002038BD00BFEC
:4022800000600040C492FF1FD092FF1FB03A0000143B00009C3A0000873B00006893FF1FDC92FF1F8192FF1FD392FF1FD592FF1FC292FF1FC092FF1FD492FF1FD192FF1FF4
:4022C0004A93FF1FD792FF1F074B1A78120609D55B78012B06D1054B054A5A6012781A80FFF786BB0020704700600040C492FF1F743A0000014B1870704700BF76650040FA
:40230000014B1878704700BF67640040014B1870704700BF77640040064A0123136002F688321268E0211064034A1170A2F540721360704780E100E000E400E0014B187039
:40234000704700BF74640040014B1870704700BF7565004073B515461E460B4C05230022019200920A4601461846237000F064F932462946207800F01FF90221207800F0B7
:4023800009F9207802B070BDD080FF1F064A0423136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04228E
:4023C0001A60704780E100E0014B1870704700BF74650040704738B505460078012428B100F066FD285D0134E4B2F8E738BD08B50D2000F05DFDBDE808400A2000F058BDDC
:40240000F7B516461F460B4C00230325019300930A4601462846257000F00EF93A463146207800F0C9F80221207800F0B3F8207803B0F0BDE080FF1FF7B516461F460B4C05
:4024400000230225019300930A4601462846257000F0F2F83A463146207800F0ADF82946207800F097F8207803B0F0BDE180FF1FF7B516461F460B4C002301250193009322
:402480000A4601462846257000F0D6F83A463146207800F091F80221207800F07BF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F026
:4024C000BBF832462946207800F076F80221207800F060F8207802B070BD00BFE380FF1F024B1878C0F38010704700BF8F450040074A7F23802113705170064A013BDBB2F7
:4025000002F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C254C25C42F00102C2540020704780
:40254000012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD8074900010B4603441A7942F004021A7150
:40258000435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E011943815400207047012070471070004041F6FF73994208BFD2
:4025C0004FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002088BF0120704700BF197000402C
:4026000017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF20704714700040172810B51AD8C000B7
:4026400001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD012010BD10B500F079FC0A4A00
:402680005378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F06BFC204610BD00BFE480FF1F030610B5044611D400F05CFC084AE300117803F1FE
:4026C000804303F5F04319705378147001335370BDE8104000F050BC10BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F5F0442180A270E370284630BDB0
:40270000012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F017FCA4F50044F6E7034B58686043BDE875
:40274000384000F00DBC00BFEC80FF1F024B1B7A584300F005BC00BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600142F080021A701A7802F07F024E
:402780001A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F8220043F82210704700BF08ED00E008
:4027C000054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B50F4900240F205C609C60DC60F4
:402800001C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD8492FF1F9D28000010E000E0EC80FF1FC6
:4028400014E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CA3691BB9FFF7BAFF0123A36179
:40288000BDE81040FFF7E8BF8492FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B198470134052CF8D138BD00BF8892FF1FAE
:4028C000024B03EB80035868596070478492FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA026303600E4B0E4A1B78DBB2436031
:40290000127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC46004002010049010100490001004905010049060100490D
:4029400010B500F015FB204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F003031370134B18223F
:402980001A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F008031370FFF7CAFE064B10222046BDE8DE
:4029C00010401A6000F0D8BAAB4300400E5900402F5B004080E200E008B500F0C9FA0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137C03F0FD03137412F8E1
:402A00000A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F0AFBA00BF08590040044A137803F03F0343EA8010C0B21070704700BF08590040082804D00A280CBF9D
:402A40008223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B33
:402A80001A8070470A590040603A00005293FF1F5493FF1F5893FF1F08B5102000F0A6F907210420FFF79AFE07490420FFF788FE064A0C20137843F006031370FFF7BCFFBA
:402AC000034B00221A8008BD912B0000095900405093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABFA092FF1F044B1A7802F0FB021A701A7842F0010256
:402B00001A7070470859004010B5084B1C7814F0010403D10028F9D0002404E02046FFF715FE024B1B78204610BD00BF09590040034A044B1B881088181A00B2704700BF1D
:402B40005893FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F0C1
:402B800000B270475293FF1F5493FF1F5093FF1F7047000010B500F0EBF9214A044613780A2043F001031370137C43F00103137412F80A3C43F0020302F80A3C937943F0DB
:402BC0000203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222123B1A7067
:402C0000094A137843F008031370FFF79FFD074B08222046BDE810401A6000F0ADB900BFAB43004006590040275B004080E200E008B500F09DF90F4A137803F0FE0313708E
:402C4000A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F083B900BF00590040044A137803F03F0361
:402C800043EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A80AE
:402CC00042F210734B4341F2883103F6C41393FBF1F305490B60054B1A807047025900406A3A00005E93FF1F6493FF1F5C93FF1F08B5102000F084F807210320FFF76EFD92
:402D000007490320FFF75CFD064A0C20137843F006031370FFF7BCFF034B00221A8008BDE92D0000015900406093FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040C8
:402D4000FFF728BFA192FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E02046FFF7E9FC024B1B78204621
:402D800010BD00BF01590040034A044B1B881088181A00B2704700BF5C93FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107381
:402DC00000B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475E93FF1F6493FF1F6093FF1F70470000034A00F0F800137803431370704700BF02410040A1
:402E0000034A00F0F800137803431370704700BF06410040014B1870704700BF72640040014B1870704700BF7864004073B515461E460B4C04230022019200920A46014603
:402E400018462370FFF7F8FB324629462078FFF7B3FB02212078FFF79DFB207802B070BDFC80FF1F074A0223136002F688321268E0215064044A11706FF440710A4413608D
:402E8000704700BF80E100E001E400E0014B1870704700BF75640040014B1870704700BF76640040014B1870704700BF79640040FEB5494652465B460EB407462449096800
:402EC0008A46244A12682448022100F071F8030020480068C018204900F06AF8143883460121C9430C460125002600F041F8814651460B7823400B705846013000F030F81C
:402F00003800F04028400B78234003430B70584600F026F80136072EF2D9002001300138013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF12
:402F40000EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C20100000000000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF77
:402F8000094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A00E013790020704700600040DC92FF1F002902D0B0FBF1F0704708B14FF0FF3097
:402FC00000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BF6081FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E75C
:4030000000F0DEFD084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BDCC3B0000CC3B0000CC3B0000D43B000003460244934202D003F8011BFAE770475A
:4030400030B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBF234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F0D4
:4030800073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A46014603480068FFF7CBFF03B05DF804FB6081FF1F2DE9F0478E6882469E420C46D9
:4030C000914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F57B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FA24
:40310000A38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FA0C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561E0
:40314000ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E442660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB958
:40318000402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF82930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BD0
:4031C000F9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B78002B00F0A08000234FF0FF3204930793059206938DF853301A93012605222178C6
:403200004E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BF20228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039AA2
:40324000111D12680391002A10DA524243F00200079204900BE027463B780134303B092B03D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1DAA
:403280001B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B1402309
:4032C000CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F988B1194B33B9039B073323F007030833039314E003AB00932A46144B04A940468F
:40330000AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B5344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BF9B3B000071
:40334000A13B0000A53B000000000000B53000002DE9F04791461F460A698B6806469342B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BF15
:40338000D9F800300233C9F80030256815F0060510D104F1190A07E00123524639463046C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF01230D
:4033C00092060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF30BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042AD7
:4034000008BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D4509D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F0438A
:4034400017460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D8002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A65
:4034800000F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A3AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D098
:4034C000111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D225B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F104010E
:40350000196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F845202268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F032
:40354000200222601BB9226822F0200222601022002084F8430001E049490A226568002DA56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F020
:4035800002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0FC
:4035C000101D1860136808E010F0400F02F104001860136801D0198000E0196000232361754616E01A68111D1960156800216268284600F049F808B1401B6060636804E0BC
:4036000004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF797FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D563
:40364000002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F083AB
:403680004F3A0000AC3B000010B5C9B202449042034605D01C7801308C42F8D1184610BD002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EBCE
:4036C000020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BF2
:40370000B8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A018834201BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918DB
:40374000A1420BD12168014458188242196013D110685268014419605A600DE002D90C232B6009E021686018824201BF106852680918216062605C602846BDE8384000F0C4
:4037800098B838BDA892FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA94202D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D94E
:4037C0000B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E7174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304D4
:40380000A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA892FF1F09
:40384000A492FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF2846F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF3146E1
:403880003846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7D8FB431C02D1236803B12B6038BD8C93FF1F7047704751F8040C0028BEBF091851F8F0
:4038C000043CC0180438704700000000050209020B020D020F021102130215027265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066CB
:40390000726F6D20256420746F2025640066696E6973686564207365656B00796573006E6F00647269766520303A20257320647269766520313A2025730057616974696E5F
:403940006720666F72205553422E2E2E0055534220726561647900636F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F7420254E
:4039800064007061737365643D256400756E64657272756E206166746572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F77726997
:4039C000746500703D25642063723D25642063773D256420663D256420773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E21007375635E
:403A0000636573730073746172742065726173696E670073746F702065726173696E670069646C6500005100401000405100403000000001400010001401400008004001A3
:403A400040000A004C0140000200500140200030313233343536373839414243444546000001000000040000001000010000000400000010280000000001040001000000C2
:403A800000000000000157494E5553420000303030303100000000000000000012034D005300460054003100300030000100000001000000B83A000001000000873B0000A5
:403AC000000000000000000001000000D03A000001000000593B000004000000F23A0000000000000000000000000000F03A0000FF00000001024000FF00000082024000C7
:403B0000FF00000003034000FF00000084034000FF00020304030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200034
:403B400054006500630068006E006F006C006F0067006900650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000AE8
:403B80000705840340000A12010002FF0001080912006E0100020180014300232D302B2000686C4C0065666745464700303132333435363738396162636465660000000069
:403BC000F8B500BFF8BC08BC9E46704759000000CD100000F8B500BFF8BC08BC9E46704735000000F83B0000C880FF1FA00000002812000000000000000000009093FF1FA8
:403C0000FF000000675000400C00000007000000FFFFFFFF7F8000003F0000000000007D00FA0000400000000090D003FF0000000000000000000000000000000000000028
:403C400000000000000000000000000000000000993B0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070
:403C8000000000000000000000000000000000000081FF1F000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065
:403CC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C4
:403D00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083
:403D40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043
:4000000000800020110000009910000099100000064A08B5136843F020031360044B1A6803F53F5302331A6001F054F8E8460040FA46004010B5054C237833B9044B13B187
:400040000448AFF300800123237010BD6081FF1F00000000E8380000084B10B51BB108490848AFF300800848036803B910BD074B002BFBD0BDE81040184700BF0000000029
:400080006481FF1FE8380000C880FF1F000000000A4A0B4B116801310B40002BBEBF03F1FF3363F03F030133136011685368994202BF024B01221A72704700BF8081FF1FAC
:4000C0003F0000800A4A0B4B516801310B40002BBEBF03F1FF3363F03F030133536051681368994202BF024B01221A72704700BF8081FF1F3F000080114BDA68196919B905
:4001000001221A75597514E09969521A19698A4294BF587D00201875187D094908B1002204E0086982428CBF002201224A75DA689A611B7D13B1002002F07EB9704700BF38
:400140008081FF1F10B5C4B2204601F085F90128FAD110BD08B572B60F4B0F49DA680132DA60DA690132C82A08BF0022DA611A6AD8690132A72A08BF00220A621B6A002BEF
:400180000CBF02230023002814BF184643F0010002F0D6FB62B608BD8081FF1F38B50446C5B2284602F0C0F8062002F095FD44F00200C0B202F0B8F8062002F08DFD284616
:4001C00002F0B2F8BDE83840062002F06FBD10B5642402F0A3F828B9FFF7E0FF013CF8D1204610BD012010BD70B5C4B2054620460E4601F031F9012805D0204601F04AFAF7
:400200002846FFF79FFF204601F02EF9314605460246204601F0EAF9204601F01DF90028FAD1284670BD000038B5044D0024285D013402F001FD402CF9D138BDA481FF1F85
:4002400008B502F0F9F9002002F002FA02F014FA02F01EFA80B208BD10B50446012002F07BF8642002F022FDFFF7EAFF2080002002F072F8642002F019FDFFF7E1FF608067
:4002800010BD08B502F004FB002002F00DFB02F01FFB02F029FB80B208BD10B50446FFF796FF322002F002FDFFF7EBFF20800120FFF774FF322002F0F9FCFFF7E2FF6080B9
:4002C00010BD0FB400B593B014AB53F8042B402102A8019302F0F0FE02A802F0BAF802F0C4F813B05DF804EB04B0704710B5044601780648FFF7E5FF0420FFF723FF62788A
:400300002146BDE81040042001F0FEB8043A000007B50023ADF804308DF80600032301A88DF80530FFF7E2FF03B05DF804FB000010B5074C94F8643043B1002001F0E8FF8A
:40034000002002F07FF8002384F8643010BD00BF8081FF1F38B5124D837895F8672004469A4204D0FFF7E4FF002385F86A302368C5F8653022790B4B1A71A378002B14BF3C
:400380000220012002F05EF8E078B0FA80F0400902F0D0FA2079BDE8384002F0D7BA00BF8081FF1FE581FF1F38B50D4C94F8645065B904F16500FFF7CDFF012001F0A8FF27
:4003C0004FF47A7002F072FC84F86A50E368E366012384F86430BDE8384002F0A5BC00BF8081FF1FF8B5214C0546FFF7DDFF94F86A3003B15DB91E48FFF763FFFFF7E7FE64
:400400000120002384F86A00236702F065FC2A46216F1848FFF755FF144E0027236F9D4216D001F07BFF00B13767236F9D4205DD0120FFF7B3FE336F013305E005DA00209C
:40044000FFF7ACFE336F013B336702F06DFCE5E7322002F02BFC2A2DCCBF0020012002F07BFABDE8F8400448FFF72BBF8081FF1F113A0000183A0000353A00002DE9F04FBF
:4004800099B062B602F0BAFC9E49042002F0DEFC9D4801F04FFF9D4801F080FF9C4801F0ADFF02F069F902F03BF8002001F0CAFF01F0CEFF0221002000F084FF954C012099
:4004C00001F0FCF8002384F86730FFF76DFFFFF77EFE84F87400FFF72BFF012384F86730FFF762FFFFF773FE84F87500FFF720FF894B94F87400894994F875202546002AA6
:4005000014BF0A461A46002808BF19468448FFF7D8FE0321084602F0E7FB264602F004FC94F8643043B1EA6EEB689B1A41F28832934201D9FFF7FCFE00F07CFF18B97948B1
:40054000FFF7BFFE04E000F07BFF0028F7D10BE000F070FF10B902F0E7FBF9E77248FFF7B0FE032001F096F8032000F075FF0128D4D16E48FFF7EEFE6D490320FFF734FED5
:4005800094F876106B48FFF79CFE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D40333036C
:4005C000D4034D0303238DF820308DF8213011238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD42
:400600000B4611E083B10022174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF782FD2F
:400640004FF000080DF1200A02F06EFB4FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76EFD3A465146022000F04BFFB9F10109EBD108F10108B8F1400FA1
:40068000E2D12E4B38E04FF0010A4FF000080DF1200B02F049FB4FF0000959460120FFF7A3FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B4688
:4006C0004A461F48FFF7FDFD4FF0000A0137402FEBD109F10109B9F5807FDED108F10108B8F1400FD5D151461648FFF7EAFDBAF1000F00F01B81144B1B8807A8ADF81C30AD
:4007000095E200BF55010000F900000091000000C50000008081FF1F473A0000433A00004A3A0000623A0000753A0000E581FF1FF681FF1F7F3A0000EC380000EE380000D6
:400740008E3A0000AA3A0000F0380000206FFFF749FE94F8780001F0FFFD94F8780001F0E3FD02F015FCB94BDFF8FC821A78002702F0FB021A701A7842F001021A701A78A2
:4007800002F0FE021A701A7802F0FE021A7002F003FC0220FFF7D6FC012141F6FF734FF48042084601F0DEFD84F8B60002F02AFA08F807000137402FF8D1DFF8B0A20027DB
:4007C0000AF195091FFA89F80137402F14BF3A4600221AF8010F2244062392F82420402102F044FA424646F24E419AF8000002F04FFA08F14008402F1FFA88F8E4D196F8F2
:40080000793053B196F87C30336100233375237D002BFCD000233375336100234FF0FF32236062602372236894F8B600234493F8241002F09FF994F8B60002F05DF90121E6
:4008400094F8B60002F030F92368002BFCD0002398467360D6F80CA0012702F065FAE368B4F87A20CAEB030393420DD367B1042195F8B60002F08AF994F8B60002F096F919
:400880000028F9D107463072237AFBB96A682B689A4202D1002FE0D118E00220FFF752FC6968402209EB8111022000F02DFE6A68674B01321340002BBEBF03F1FF3363F093
:4008C0003F03013308F101086360C6E70220277AFFF738FC00221146022000F015FE0220FFF730FCFFB2FFF79FFC002001F01CFD37B15848FFF7E5FC0220FFF709FD06E072
:40090000554B08A81B88ADF82030FFF7EFFC227D4146237A5148FFF7D4FC15E25048FFF7D0FCD4F87A7017F03F0701D0032009E2286FFFF757FD95F8780001F00DFD95F82B
:40094000780001F0F1FC012001F098FD02F020FB444BDFF814811A7842F004021A701A7842F001021A701A7802F0FE021A701A7802F0FE021A7002F00FFB01214FF480438D
:4009800041F6FF72084601F01DFD85F8B60002F039F908F807000137402FF8D1DFF8CC90002709F195031FFA83F804930137402F14BF3A46002219F8010F2244052392F8FC
:4009C0002420402102F052F9414646F2484299F8000002F05DF908F14008402F1FFA88F8E4D100274FF0FF33376098467360BB463B46D6F87A9037725FEA99190CBF4FF00D
:400A0000010A4FF0000A2168114A01310A40002ABCBF02F1FF3262F03F026068B8BF013282426FD02BB1227A002A7AD12A7D002A77D12068049A059302EB8010BAF1000F36
:400A400016D040223F2102F003FB1CE09E6400403F000080B43A0000F2380000CE3A0000E13A000098640040A481FF1FA381FF1F014601370120FFF7B7FBC7EB0903D3F11B
:400A8000000A4AEB030A2168B34A01310A40002ABEBF02F1FF3262F03F02013222606268059B01322ED12A683F2A2BD14FF00008C5F8048001F0B4FC85F808806B6895F8B4
:400AC000B6002B4493F8241002F054F895F8B60002F012F8012195F8B60001F0E5FF95F87E302B6185F81480237D002BFCD04FF00008012086F8148001F09EFC404601F070
:400B0000BDFC00E023B1237A5BB92B7D4BB90123626842453FF477AF0BF1010BD5F8048071E701F083FC012001F0A8FC002001F083FC042194F8B60002F028F894F8B600FB
:400B400002F034F880460028F8D196F8B60001F0C1FF337D327A0293012303920193CDF800A05B463A4649467C48FFF7AAFBC6F81080BAF1000F0BD0FFF756FB002001F0BE
:400B8000D3FB237A63B17648FFF79BFB0220D9E0B945F1D073490120FFF726FB0137F7E77148FFF78EFB714B3DE094F8780001F0D3FB206FFFF716FC6D48FFF782FB94F8A9
:400BC0007930236100232375237D002BFCD0012001F032FC00233375237D002BFCD0002001F02AFC002363483361FFF76AFB624B19E0002084F86A00FFF7F4FB5F4B12E0B3
:400C000094F8743023B195F875200AB985F8782094F875201AB113B9012385F878305848FFF798FB574B1B88ADF8203008A8FFF75DFB89E0FFF77CFB01F01CFE002001F062
:400C4000BFFD2A2701F0EAFC002001F08DFC3A46002108A802F0FCF917238DF820308DF8217002F061F8002001F052FB002001F0E9FBC82002F01AF80DF12200FFF7ECFA13
:400C80000DF13600FFF709FB02F04EF8012001F0D9FB322002F00AF80DF12600FFF7DCFA0DF13A00FFF7F9FA012001F031FB4FF4967001F0FBFF02F037F80DF12E00FFF7DC
:400CC000CBFA0DF14200FFF7E8FA002001F020FB4FF4967001F0EAFF02F026F8022001F0B1FB322001F0E2FF0DEB0700FFF7B4FA0DF13E00FFF7D1FA012001F009FB4FF4DC
:400D0000967001F0D3FF02F00FF80DF13200FFF7A3FA0DF14600FFF7C0FA002001F0F8FA4FF4967001F0C2FF01F0FEFF002001F089FB002384F86A3001F01EFD01F0F0FB98
:400D400074E70120FFF7E4FA032000F0A3FC0E48FFF7B7FAFFF7E2BB3F000080EB3A00001B3B00003892FF1F253B0000F43800002D3B00003B3B0000F6380000F8380000F7
:400D8000F681FF1FFA380000483B00000F4B1A78120616D55878C0B2012814D11B79DBB2042B02D0052B05D07047094A094B5A60282203E0084A074B5A60E0221A8000F054
:400DC00043BE002070470120704700BF00600040FC380000BC92FF1F243900002DE9F04172B6884B61221A70A3F5F06301221A801924854A9C7092E803008033062283F805
:400E0000002283E80300522203F580731A707F4B7F4A1B787F4EDBB2137040F618027E4B00251A8041F2512223F8022C33784FF4F07003F0010343EA450502F0A1F8013CEB
:400E400005F003052ED0032DF0D1744B4FF480721A8007221A70724A002548211570917002221D705D7103F8032C0422DA716D4A6D4C13786D4E43F00103137012F8013CA4
:400E8000062743F0030302F8013C2378012243F0800323705B4B1A70654A137843F02003137000E0FEE707FB056300219A881868013502F0CDF8072DF5D15E485E4E0025FB
:400EC00050F8041F05F1105303F14A0221F0FF074B33C9B20B4452005B0002329A4206D012F802EC12F801CC0EF807C0F5E7B0420D44E5D1514A00231360936013619361FF
:400F00004F4B504F1A68504BDFF888811A604F4B1A684F4B1A604F4A137843F002031370137C43F0020313742378A2F5863243F040032370413A137843F010031370464A52
:400F4000464B07CA03C31A80454A2833106843F8250C127903F8212C424A07CA03C31A80414AE83B07CA03C31A80404A083307CA03C31A803E4A3F4BA2F5616203CBC2F888
:400F8000100EC2F8141E1378042043F008031370394B02F5AA521B783D78DBB298F80060EDB203F007010C321B091170F6B2537045F003033B7046F0030388F800302F4B47
:400FC00048221A702E4A402313702E49937013729372082382F81F3220220A7048710A72294A0A20137001F077FE284B88F8006044223D70264D1A7094E80F0007C52B80C9
:40100000BDE8F08100480040680A00480F010049A146004025420040224200400440004006400040A2430040A04300404D3B0000E8460040FCFFFF47900000480076004076
:40104000700A0048F846004020760040740A00482876004003500140280A0048C0510040340A00483C0A0048480A0048540A004832510040600A0048CF0100491D510040C7
:4010800001590040235B0040585B004076580040B0430040F946004008B501F0ADFF03680C2B00D1FEE7FEE7084908B50B68084A1844821A802A01DC086005E001F09CFF63
:4010C0000C2303604FF0FF33184608BDCC80FF1F8893FF1F80B51148114B0025C0B1A3F1100192C922460439161BB74204D051F8046F42F8046BF7E7114653F8046C8C1A09
:40110000A64202D041F8045BF9E701381033E5E701F078FFFFF7B2F9FEE700BF01000000E03C0000124A134B10B51A60124A134C1368134843F4007313600023032B98BF1C
:4011400054F823204FEA830188BF0E4A0133302B4250F3D10C4B1A780C4B1A700C4B084A1A60FFF73BFEBDE8104001F087BC00BF0004FA050CED00E014ED00E000000000DE
:401180000080FF1F99100000BC760040C080FF1F08ED00E0F8B501F0FBFE4B4A01271378022643F001031370137C484C43F001031374474B02F5E3521F700B3203F8946C1C
:4011C0001378054603F07F031370002001F084FD2378404A03F0F90323701378384603F0DF03137023783B43237001F075FD282001F072FD384B30461A7802F07F021A7048
:401200001A7802F0BF021A7023783343237001F063FD2378314A43F0040323700023137053702F4AFF2199540133092BFBD1284601F0B2FE0721172001F096FD2949172049
:4012400001F084FD0721182001F08EFD2649182001F07CFD0721152001F086FD2349152001F074FD0721052001F07EFD2049052001F06CFD0721062001F076FD1D4906205C
:4012800001F064FD0721084601F06EFD1A49072001F05CFD0721082001F066FD1749082001F054FD0021162001F05EFD1449162001F04CFD07210C2001F056FDBDE8F840E3
:4012C00010490C2001F042BDA5430040944300409D60004012600040F851004084600040AD92FF1F631B00009D190000611B0000951A0000C11A0000F11A0000291B0000B8
:40130000691B0000DD1B0000214B224A10B5187000231370204A40201370204A0F2413701F4A13701F4A13701F4A13701F4A13701F4B4FF400021A604FF080721A604FF432
:4013400000121A6020221A601860802018604FF480701860174804704FF480001860164B1A70933B19B91A7802F0FE0202E01A7842F001021A70114B03221A70802203F8F2
:40138000202C012001F0FCFD0D4B04221A7010BDC892FF1FCE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F00E100E09E6000409C60004028600040C2
:4013C0001260004070B5074C054623780E461BB9FFF7E0FE0123237031462846BDE87040FFF792BF7892FF1F0A4A002313700A4A13700A4A13700A4A13700A4A13700A4AD7
:4014000013700A4A13700A4B03221A70802203F8202C7047CE92FF1FCC92FF1FCD92FF1FC992FF1FB892FF1FCB92FF1F4093FF1F28600040014B1878704700BFCD92FF1F53
:40144000044B1A7802F0FF001AB118780022C0B21A707047CC92FF1F024A0C2303FB002040787047D492FF1F431E072B0CD8074A064B00010344805C5B7800F00F0043EA26
:401480000020023880B2704700207047FC5F00401A4A38B50C2303FB00231B79090C13F0800F00F1FF35044619BF8AB24FF480438BB24FF48042032D18D8DFE805F00207EB
:4014C0000C110021084601F0A1FA0DE00021084601F080FA08E00021084601F05FFA03E00021084601F03EFA054B1855EDB2072D03D801F087FB034B185538BDD492FF1FDF
:40150000A492FF1FAD92FF1F431E072B2DE9F0470446894615465CD82F4F0C2202FB0072D388DFF8B8A09BB2C3F500739D424FF00C0303FB007388BFD588DB7884BFC5F5C3
:401540000075ADB2254A43EA15230601B354B244EBB28AF80130224B1A5C9846FF2A01D1FFF796FF0C2303FB047200215170B9F1000F28D03DB31B4F385D01F0ABFA112339
:401580002946FE2218F8040001F070FB06F5C04278321FFA89F118F8040001F079FB124D18F80410385D01F0E5FA0121385D01F07BFA735D43F002037355735D03F0FD03E1
:4015C0007355BDE8F08703FB04746379DBB28AF80230BDE8F08700BFD492FF1FFC5F0040AD92FF1FA492FF1F706000402DE9F047044615468846002940D0431E072B3FD816
:40160000FFF732FFA84203D22046FFF72DFF05461D4E335DFF2B03D141462046FFF738FFDFF868A027011AF8040001F053FA1223FE222946305D01F019FB07F5C0411FFA17
:4016400088F27831305D01F023FBDFF84490315D1AF8040001F08EFA01211AF8040001F023FA17F8093043F0020307F8093017F8093003F0FD0307F8093002E00D4600E05D
:4016800000252846BDE8F087AD92FF1FA492FF1F70600040431E072B0AD8064A0C2303FB002300225A705A79034BD2B200011A54704700BFD492FF1FFE5F0040431E072B7B
:4016C0009FBF024B000108221A547047FE5F004030B51A4A1A491B4D0878138803449BB21380194A00231488D8B2A4B27CB1082B0CD050680078C0B2E854506801330130C3
:4017000050601088013880B21080ECE718460B780E4C082B0E4A00D040B10E4D2B7883F080032B700F232370022301E0022323701370094B1870087030BD00BF4493FF1F82
:401740004093FF1F00600040BC92FF1FB992FF1FCE92FF1FCA92FF1F4193FF1F074B02221A70074B80221A70064B0F221A70064A00231370054A012013707047CE92FF1F71
:40178000CA92FF1FB992FF1F4093FF1F4193FF1F30B5164B16491B780A8803F00F03023BDBB21A4492B20A80124C134A0020118889B279B173B15568215C013BC9B2297017
:4017C0005168DBB20131516011880130013989B21180ECE7094A1370094A137883F080031370084B0B221A7030BD00BF296000404493FF1F00600040BC92FF1F4193FF1F7E
:40180000CA92FF1FB992FF1F064A06231370064A01201370054B80221A70054B00221A70704700BFCE92FF1FB992FF1FCA92FF1F4193FF1F054B9A683AB19A680449107088
:401840009A680988518000229A607047BC92FF1F4493FF1F08B5124B1A78D2B21A701B78DBB21A0602D50F4A137008BD0220FFF7E1FF0D4B1B7803F06003202B05D0402B9A
:4018800006D043B900F012FC04E001F089FB01E0FFF77CFA10B9034B03221A7008BD00BF28600040B992FF1F0060004008B5084A084B0120197813880B449BB21380064B68
:4018C00000221A70FFF7B6FF044B03221A7008BD4493FF1F4093FF1FCE92FF1FB992FF1F08B50C4B1B78DBB2042B07D0062B09D0022B0DD1BDE80840FFF7D8BFBDE808404B
:40190000FFF746BF0320FFF795FF034B03221A7008BD00BFCE92FF1FB992FF1F08B5054B002201201A70FFF785FF034B03221A7008BD00BFCE92FF1FB992FF1F08B50A4BC9
:401940001A7832B11A78094942F080020A7000221A70074B002201201A70FFF76BFF054B03221A7008BD00BFB892FF1F08600040CE92FF1FB992FF1F074B1B78DBB2042B9A
:4019800005D0062B05D0022B05D1FFF7A1BEFFF7C5BFFFF7D3BF7047CE92FF1F38B51D4C2378DBB2DD0634D518060AD503F00F03012B2ED1FFF74EFF174B1B78190609D5F1
:4019C00038BD5A0602D5FFF7D7FF03E09D0620D5FFF786FF23781B061BD4104B1A78104B1B7813430F4A13701278934211D10A4A0849154613782078DBB2000605D41378E6
:401A0000DBB20B700B7803F00F0328788342F1D138BD38BD28600040B992FF1FCA92FF1F4193FF1F29600040054A00231380054A916819B191680B7092685380704700BFD1
:401A40004493FF1FBC92FF1F0E4808B503889BB213B9FFF783FE13E00B4B02221A700B4B00221A70FFF7E0FF094AD1799379028843EA012392B2934238BF0380FFF728FED6
:401A8000012008BDBC92FF1FCE92FF1FCA92FF1F00600040084B01221A700F3B9B7C074B1A7B02F00302012A1EBFDA7B82F08002DA7301225A7370470B600040D492FF1F89
:401AC000094B02221A700F3B93F82230074B1A7E02F00302012A1EBFDA7E82F08002DA7601225A76704700BF0B600040D492FF1F0B4B04221A700F3B93F83230094B93F884
:401B0000242002F00302012A1EBF93F8272082F0800283F82720012283F82520704700BF0B600040D492FF1F0B4B08221A700F3B93F84230094B93F8302002F00302012AB0
:401B40001EBF93F8332082F0800283F83320012283F83120704700BF0B600040D492FF1F7047FFF741BC0000F0B5184B184E19780C27C9B201234FF0000C31B3CA0720D5E4
:401B8000144A4FEA031E7244947850782040C5070DD507FB03652C79240608D5147804F0FE0414706D790C4CEDB204F80E50840706D507FB036425792D0658BF84F801C08E
:401BC00090700133DBB24908D7E7F0BD9F600040D492FF1F70600040FE5F004000F032BF70B50446184B88B003AA03F11006154618685968083303C5B3422A46F7D11B78F7
:401C00002B70FCB12223237001AD03232846637001F024F9002220461146AB5C08AC04EB131414F8144C03F00F03847008AC234413F8143C0132082AC1700371417100F129
:401C40000400EAD108B070BD773B00002DE9F0431C4D01222E460C201F274FF0800E4FF0080C194B00FB02581401234418705F70164998F805902144B9F1000F07D098F89E
:401C8000044024064CBF887081F802C001E081F802E000FB0261CC880132E4B29C71CC88092AC4F30724DC71CC88E4B21C71C988C1F307215971D4D1054BFF221A70BDE84B
:401CC000F08300BFD492FF1F70600040FC5F00400A600040064B074A1B7802EBC30253681A7C824286BF03EBC003586900207047C892FF1F9C3B00002DE9F84F424B1A7884
:401D0000002A7ED01878414D0138C0B2FFF7E2FFA8463F4AC3681478007ADFF800C1E4B203EBC0000C2600274FF0010E834268D01A78A24263D11CF80420597891425ED1AE
:401D40009A7893F8039002F07F0206FB02FA05EB0A01CF7093F802B009F0030981F804B093F803B005F80AB0B3F804A0A1F808A093F902A0BAF1000F0BDAB9F1010F0CBF43
:401D80004FF007094FF00D0981F8059081F801E009E0B9F1010F0CBF4FF005094FF0090981F805904F704FEA02191A4906FB0282494481F802E0B2F808A0CAF3072A81F861
:401DC00000A0B2F808A05FFA8AFA81F801A0B2F806A011495FFA8AFA494481F806A0B2F80690C9F3072981F80790B2F806905FFA89F981F80490D288C2F307224A71083335
:401E000094E7BDE8F88F00BFCD92FF1FD492FF1FC992FF1FFC5F004070600040BA92FF1F08B5064B18780138C0B2FFF753FF20B143681B7900EBC300406908BDCD92FF1F73
:401E400000212DE9F84F0B464E4E0C2707FB01F401313219092933554FF000059370494CD3701381937253705371EFD118B1464B1D70464B1D70464B1A78002A7FD0187866
:401E800001250138C0B2FFF725FFA8464368DFF8F8E0DB790C2713F0400F3E4B4FF0000C1A7814BF42F0010202F0FE021A70027AD20007FB0541C36803EB02094B4531D0E1
:401EC00093F802A00AF07F06AE4229D10E89B3F804B0B6B25E4538BFA1F808B01E7893F801B01EF80660B3451AD181F804A0DE780E7093F902A0DE78BAF1000F06F00306A4
:401F000007DA012E0CBF07260D264E7181F8018006E0012E0CBF052609264E7181F801C00833CBE70135092DC3D1C1680A328B1C0A440C200833934209D013F8081C13F8E4
:401F40000A5C01F07F0100FB01418D72F2E7FFF767FF114B0121186000230C2000FB0142D3801289013113449BB203F00102134409299BB2F2D1BDE8F84FFFF767BEBDE897
:401F8000F88F00BFD492FF1FBA92FF1F4293FF1FCD92FF1FCB92FF1FD092FF1F114B1B7903F07F035A1E072A19D80F490C2202FB031291781B0141F0010191700021D17030
:401FC000517841F002015170127912F0800F074A1A4414BF8D2389239370FFF715BC0020704700BF00600040D492FF1FFC5F004030B4194B1A7902F07F02531E072B27D81A
:40200000164B0C2404FB02339978154D01F0FE0199700021D97029461201505D114400F07F0050555A7802F0FD025A701A795B78120605D5012B01D18C7006E00D2303E095
:40204000012B0CBF082309238B7030BCFFF7DCBB002030BC704700BF00600040D492FF1FFC5F004010B50D4B0D4C21791878C9B20138C0B2FFF72EFE43681B798B4201D296
:40208000012909D8074A0848535CDBB24354A3780120DBB2535410BD002010BDCD92FF1F00600040BA92FF1F4293FF1F38B5874A874C13780021DBB221801806517840F17A
:4020C00088800A2900F20081DFE811F05800FE00FE00FE00FE00FE000B00FE007900FE007D00D3787949012B09D17A4B1A787A4B03EBC2035B685B686360122310E0CB788C
:40210000022B12D18878FFF7E5FD002800F0DC80436863606368DA7863689B7843EA02232380BDE83840FFF78FBCCB78032B21D16A4B00228878D5B2854203D3634A927872
:402140003AB910E0187801320028F7D018780344F0E75E4A62499278097C914203D16148FFF73EFD5F4B1A78002A00F0AD801A78228018E0BDE8384000F012BF13F0030323
:4021800013D0022B40F0A0802380504B0C211B7903F07F02544B01FB02339A78534BD2B21A7000225A706360BBE702222280504A11784E4AC9B2117053706260B1E70123AF
:4021C00023804C4BEFE70123238013794A4A1344E9E701390A2977D8DFE801F037764F76067676760A7620009378444ADBB25AE0937803F0FF0153B93E4B1A7891425FD04C
:4022000019703F4B01201870FFF71AFE58E0481EC0B2FFF75FFD0028EED155E0FFF722FF002851D0294A374913791279DBB2D2B20A70354A3049D25CCB5C9A4240D0304BAD
:4022400001221A70FFF758FD3AE003F00303012B2BD009D3022B37D11C4B9B78002B33D1BDE83840FFF7C4BE184B9B78012B2BD11F4A137803F0FD0315E003F00303012B3E
:4022800013D008D3022B1FD1104B9B78E3B9BDE83840FFF783BE0D4B9B78012B14D1144A137843F0020313700AE0084B1A795AB998781B791549DBB2CA5C22EA0002CA5401
:4022C000BDE83840FFF7A0BA002038BD00600040BC92FF1FC892FF1F9C3B0000003C0000733C00006093FF1FD492FF1F7992FF1FCB92FF1FCD92FF1FBA92FF1FB892FF1F8E
:40230000CC92FF1FC992FF1F4293FF1FCF92FF1F014B1870704700BF72640040014B1878704700BF68650040014B1870704700BF7A650040064A0123136002F688321268FB
:40234000E0211064034A1170A2F540721360704780E100E000E400E0014B1870704700BF7A64004073B515461E460B4C04230022019200920A4601461846237000F022FCF8
:4023800032462946207800F0DDFB0221207800F0C7FB207802B070BDD080FF1F074A0223136002F688321268E0215064044A11706FF440710A441360704700BF80E100E05F
:4023C00001E400E073B515461E460B4C05230022019200920A4601461846237000F0F2FB32462946207800F0ADFB0221207800F097FB207802B070BDD180FF1F064A042355
:40240000136002F688321268E0219064034A1170A2F202321360704780E100E002E400E0014B04221A60704700E100E0014B04221A60704780E100E0014B1870704700BFAF
:402440007B650040014B1870704700BF73640040704738B505460078012428B100F038FD285D0134E4B2F8E738BD08B50D2000F02FFDBDE808400A2000F02ABD014B187065
:40248000704700BF7865004010B500F081FD204A044613780A2043F002031370137C43F00203137412F80A3C43F0010302F80A3C937943F00103937102F5AB52137843F024
:4024C00003031370134B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F597530222183B1A70094A137843F00803137000F0ECFBF7
:40250000064B10222046BDE810401A6000F044BDAB4300400E5900402F5B004080E200E008B500F035FD0F4A137803F0FE031370A2F5AA521D3A137803F0FD031370137CBD
:4025400003F0FD03137412F80A3C03F0FE0302F80A3C937903F0FE039371BDE8084000F01BBD00BF08590040044A137803F03F0343EA8010C0B21070704700BF0859004070
:40258000082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F910100A4B88BF11461A8042F210734B4341F2883103F6C41393FBE4
:4025C000F1F305490B60054B1A8070470A590040883B00004A93FF1F4C93FF1F5093FF1F08B5102000F036FA0721042000F0BCFB0749042000F0AAFB064A0C20137843F0FB
:4026000006031370FFF7BCFF034B00221A8008BDD9260000095900404893FF1F10B5054C23781BB9FFF7DCFF01232370BDE81040FFF72ABF7B92FF1F044B1A7802F0FB0218
:402640001A701A7842F001021A7070470859004010B5084B1C7814F0010403D10028F9D0002404E0204600F037FB024B1B78204610BD00BF09590040034A044B1B881088D2
:40268000181A00B2704700BF5093FF1FA25B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B1B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF97
:4026C0005B42134493FBF1F000B270474A93FF1F4C93FF1F4893FF1F7047000010B500F057FC214A044613780A2043F001031370137C43F00103137412F80A3C43F0020365
:4027000002F80A3C937943F00203937102F5AA521832137843F003031370144B18221A7013F8012C42F0400203F8012C13F8012C02F0FC0203F8012CCE2203F8062CA3F591
:4027400097530222123B1A70094A137843F00803137000F0C1FA074B08222046BDE810401A6000F019BC00BFAB43004006590040275B004080E200E008B500F009FC0F4A79
:40278000137803F0FE031370A2F5AA52153A137803F0FE031370137C03F0FE03137412F80A3C03F0FD0302F80A3C937903F0FD039371BDE8084000F0EFBB00BF00590040C4
:4027C000044A137803F03F0343EA8010C0B21070704700BF00590040082804D00A280CBF8223C22300E0422308380E4AC0B20428137098BF0C4B4FF0000298BF33F91010F2
:402800000A4B88BF11461A8042F210734B4341F2883103F6C41393FBF1F305490B60054B1A80704702590040923B00005693FF1F5C93FF1F5493FF1F08B5102000F014F9D3
:402840000721032000F090FA0749032000F07EFA064A0C20137843F006031370FFF7BCFF034B00221A8008BD31290000015900405893FF1F10B5054C23781BB9FFF7DCFF1F
:4028800001232370BDE81040FFF728BF7C92FF1F044B1A7802F0FB021A701A7842F001021A7070470059004010B5084B1C7814F0010403D10028F9D0002404E0204600F090
:4028C0000BFA024B1B78204610BD00BF01590040034A044B1B881088181A00B2704700BF5493FF1FA05B00400E4A13881BB223B111880A2309B2594301E00B4B19680B4B37
:402900001B88C01A42F2107300B203FB00F2022391FBF3F30028D8BF5B42134493FBF1F000B270475693FF1F5C93FF1F5893FF1F70470000014B1870704700BF7B64004062
:40294000014B1870704700BF7F640040014B1870704700BF7C640040014B1870704700BF7E640040F7B516461F460B4C00230325019300930A4601462846257000F022F939
:402980003A463146207800F0DDF80221207800F0C7F8207803B0F0BDE080FF1FF7B516461F460B4C00230225019300930A4601462846257000F006F93A463146207800F0A6
:4029C000C1F82946207800F0ABF8207803B0F0BDE180FF1FF7B516461F460B4C00230125019300930A4601462846257000F0EAF83A463146207800F0A5F80221207800F0A6
:402A00008FF8207803B0F0BDE280FF1F73B515461E460B4C0023019300930A4601461846237000F0CFF832462946207800F08AF80221207800F074F8207802B070BD00BF92
:402A4000E380FF1F024B1878C0F38010704700BF8F450040034A00F0F800137803431370704700BF02410040034A00F0F800137803431370704700BF06410040074A7F2330
:402A8000802113705170064A013BDBB202F80839002BF9D1034A1370704700BFE480FF1FF87B00400078004017280FD8084B0001C25C11B142F0200201E002F0DF02C2543D
:402AC000C25C42F00102C25400207047012070471070004017280BD8064B0001C25C02F0FE02C254C25C02F0DF02C25400207047012070471070004017280DD80749000179
:402B00000B4603441A7942F004021A71435C43F00103435400207047012070471070004017280BD8064A0001835C490003F0F10301F00E0119438154002070470120704769
:402B40001070004041F6FF73994208BF4FF400519A4208BF4FF4005217289FBFC00000F1804000F5EC4081809ABFC280002001207047000017289FBF034B00011954002042
:402B800088BF0120704700BF1970004017289FBF054B00011A5C01F007019DBF1143195400200120704700BF1470004017289FBF034B0001185C00F0070088BFFF20704705
:402BC00014700040172810B51AD8C00001F07F0100F1804441EAC21204F5EC44D2B222709DF8082003F00F0343EA0213DBB263709DF80C30002003F00F03A370E07010BD6D
:402C0000012010BD10B500F0C3F90A4A5378182B0AD91478013B5370E30003F1804303F5F0431B78137000E0FF2400F0B5F9204610BD00BFE480FF1F030610B5044611D478
:402C400000F0A6F9084AE300117803F1804303F5F04319705378147001335370BDE8104000F09AB910BD00BFE480FF1F30B504060CD411F4704509D1C40004F1804404F537
:402C8000F0442180A270E370284630BD012030BD03065FBFC00000F1804000F5F04081805ABFC280002001207047000038B50446084DB4F5004F05D9286800F061F9A4F58B
:402CC0000044F6E7034B58686043BDE8384000F057B900BFEC80FF1F024B1B7A584300F04FB900BFEC80FF1F0E4B00F003001A78490102F0FC02104318701A7801F0600107
:402D000042F080021A701A7802F07F021A701A7802F09F020A431A701A7842F010021A70704700BF83430040014B01221A70704784430040044B00F00F021B6853F82200F7
:402D400043F82210704700BF08ED00E0054A00F01F00126800F1100352F8230042F82310704700BF08ED00E000F01F0000F16040490100F56440C9B2017070470F4B10B555
:402D80000F4900240F205C609C60DC601C615C61FFF7D0FF0B4A136843F0040313600A4B4FF47A72DB68B3FBF2F3084A1360084B4FF400421C60C3F8E82010BD8092FF1FC5
:402DC000292E000010E000E0EC80FF1F14E000E018E000E0024A136843F002031360704710E000E008B5FFF7F5FF034A136843F00103136008BD00BF10E000E010B5054CFA
:402E0000A3691BB9FFF7BAFF0123A361BDE81040FFF7E8BF8092FF1F024B1868C0F30040704700BF10E000E038B5FFF7F5FF012808D1054D002455F8243003B1984701345B
:402E4000052CF8D138BD00BF8492FF1F024B03EB80035868596070478092FF1F134B144A1B78DBB20360127843EA0223114A0360127843EA0243104A0360127843EA0263A4
:402E800003600E4B0E4A1B78DBB24360127843EA02230C4A4360127843EA02430A4A4360127843EA02634360704700BF0301004904010049EC46004002010049010100499A
:402EC00000010049050100490601004900000000FEB5494652465B460EB40746244909688A46244A12682448022100F071F8030020480068C018204900F06AF81438834666
:402F00000121C9430C460125002600F041F8814651460B7823400B705846013000F030F83800F04028400B78234003430B70584600F026F80136072EF2D900200130013812
:402F4000013001200B78234003430B705846043000F016F8484600F01FF800BF00BF00BF0EBC894692469B46FEBD00BFAFF30080D480FF1FF880FF1F00C2010000000000FD
:402F80000230800803D000BF01380046FCD17047EFF3108072B6704780F31088704700BF094A137803F00303012B0AD0022B09D113790C2103F07F02044B01FB02339B7A4A
:402FC00000E013790020704700600040D492FF1F002902D0B0FBF1F0704708B14FF0FF3000F008B80029F8D00246B0FBF1F000FB11217047704700BF014B1868704700BFF4
:403000005C81FF1F0E4B70B51E460E4C0025E41AA410A54204D056F8253098470135F8E700F044FE084B094C1E46E41AA4100025A54204D056F8253098470135F8E770BD9C
:40304000B83C0000B83C0000B83C0000C03C000003460244934202D003F8011BFAE7704730B5141E05469BB0184604DA8B232B604FF0FF301DE04FF40273ADF80C300CBFA2
:40308000234604F1FF33029305934FF6FF7300910491ADF80E3002461E9B6946284600F073F8431CBCBF8B232B6014B1009B00221A701BB030BD000007B5009313460A464B
:4030C000014603480068FFF7CBFF03B05DF804FB5C81FF1F2DE9F0478E6882469E420C46914698463ED88A8912F4906F3AD02568096902236F1A656905EB450595FBF3F5BD
:403100007B1C43449D4238BF1D4653050FD5294600F04AFB064698B13A46216900F0D2FAA38923F4906343F08003A38113E02A4600F098FB064670B92169504600F0E8FAA0
:403140000C23CAF80030A3894FF0FF3043F04003A381BDE8F08726613E44266046466561ED1BA560464528BF464649463246206800F0B3FAA36800209B1BA36023681E44F5
:403180002660BDE8F08700002DE9F04F9DB003938B8980461C060D4616460DD50B695BB9402100F001FB2860286118B90C23C8F80030CDE040236B610023099320238DF86F
:4031C0002930DFF89CB130238DF82A3037463C4614F8013B1BB9B7EB060910D003E0252BF9D02746F3E74B46324629464046FFF771FF013000F0A780099B4B4409933B7803
:40320000002B00F0A08000234FF0FF3204930793059206938DF853301A930126052221784E4800F041FA671C049B38B14B4A3C46801A06FA00F018430490EFE7D90644BFAF
:4032400020228DF853201A0744BF2B228DF8532022782A2A03D0079A00210A200BE0039A111D12680391002A10DA524243F00200079204900BE027463B780134303B092B51
:4032800003D800FB02320121F5E701B107923B782E2B1ED17B782A2B0AD1039B02371A1D1B680392002BB8BF4FF0FF33059310E0002319460593781C0A2407463A780130D0
:4032C000303A092A03D804FB01210123F5E703B1059103223978224800F0E6F940B14023CBEB000003FA00F0049B013718430490397806221B487E1C8DF8281000F0D4F9CF
:4033000088B1194B33B9039B073323F007030833039314E003AB00932A46144B04A94046AFF3008007E003AB00932A460F4B04A9404600F093F8B0F1FF3F824603D0099B27
:403340005344099342E7AB895B0601D4099801E04FF0FF301DB0BDE8F08F00BF873C00008D3C0000913C000000000000D53000002DE9F04791461F460A698B680646934279
:40338000B8BF1346C9F8003091F843200C46DDF8208012B10133C9F800302368990642BFD9F800300233C9F80030256815F0060510D104F1190A07E0012352463946304631
:4033C000C04701301AD00135E368D9F800209B1A9D42F1DB94F843302268003318BF012392060FD5E118302081F843005A1C94F845102244023382F8431003E04FF0FF3091
:40340000BDE8F08704F1430239463046C0470130F4D02268D9F80050E36802F00602042A08BF5D1B2269A3680CBF25EAE57500259342C4BF9B1AED184FF000091A344D45BF
:4034400009D00123224639463046C0470130D5D009F10109F3E70020BDE8F0872DE9F04317460A7E85B06E2A984606460C460C9B01F1430E00F0AE8011D8632A22D009D833
:40348000002A00F0BB80582A40F0CA8081F84520834955E0642A1ED0692A1CD0C0E0732A00F0B08009D86F2A2ED0702A40F0B8800A6842F020020A603EE0752A24D0782A87
:4034C0003AD0ADE01A6801F14205111D1960136884F84230A8E021681A6811F0800F02D0111D196008E011F0400F02F10401196002D0B2F9003000E01368002B3CDA2D228D
:403500005B4284F8432037E021681A6811F0800F02D0111D196007E011F0400F02F10401196001D0138800E01368227E5C496F2A14BF0A2208221BE078225A4984F8452055
:403540002268186812F0800F00F104051D6003D1550601D5038800E00368D00744BF42F0200222601BB9226822F0200222601022002084F8430001E049490A226568002DF0
:40358000A56008DB206820F0040020602BB9002D7DD175460CE0002B79D07546B3FBF2F002FB1033CB5C05F8013D03460028F5D1082A0BD12368DA0708D5236962689A42E0
:4035C000DEBF302305F8013C05F1FF35C5EB0E0323612EE008681A6810F0800F496903D0101D1860136808E010F0400F02F104001860136801D0198000E019600023236173
:40360000754616E01A68111D1960156800216268284600F049F808B1401B6060636804E004F1420584F8422001232361002384F84330CDF800803B4603AA21463046FFF70C
:4036400097FE013002D14FF0FF3026E023692A4639463046C0470130F5D023689B0710D5002504F1190907E001234A4639463046C0470130E7D00135E368039A9B1A9D42D0
:40368000F2DBE068039B9842B8BF184605E00B7804F1420584F842308AE705B0BDE8F083773B0000983C000010B5C9B202449042034605D01C7801308C42F8D1184610BD55
:4036C000002010BD10B5431E0A44914204D011F8014B03F8014FF8E710BD884210B501EB020301D8421E0BE09842FBD28118D21AD34204D013F8014D01F8014DF8E710BD71
:40370000994204D011F8014B02F8014FF8E710BD38B50546002944D051F8043C0C1F002BB8BFE41800F0D4F81E4A1368114613B96360146030E0A3420DD92268A0188342ED
:4037400001BF18685B681218226063600C6023E0A24203D813465A68002AF9D118681918A1420BD12168014458188242196013D110685268014419605A600DE002D90C232A
:403780002B6009E021686018824201BF106852680918216062605C602846BDE8384000F098B838BDA092FF1F70B5CD1C25F0030508350C2D38BF0C25002D064601DBA942A5
:4037C00002D90C23336046E000F082F8234B1C681A462146A1B10B685B1B0ED40B2B03D90B60CC18CD501EE08C420BBF63684B681360636018BF0C4615E00C464968E9E70D
:40380000174C23681BB9304600F052F820602946304600F04DF8431C18D0C41C24F00304A0420DD12560304600F053F804F10B00231D20F00700C31A0ED05A42E25070BD37
:40384000211A304600F034F80130EBD10C233360304600F03EF8002070BD00BFA092FF1F9C92FF1FF8B5074615460E4621B91146BDE8F840FFF798BF1AB9FFF749FF284605
:40388000F8BD00F027F885420ED929463846FFF78BFF044650B131462A46FFF713FF31463846FFF735FF01E03046F8BD2046F8BD38B5064C0023054608462360FDF7F4FB48
:4038C000431C02D1236803B12B6038BD8493FF1F7047704751F8040C0028BEBF091851F8043CC0180438704700000000050209020B020D020F02110213021502280000001B
:40390000000104000100000000000000000157494E55534200003030303031000000000000000000E0000000000105000100D6000000070000002A00440065007600690041
:403940006300650049006E0074006500720066006100630065004700550049004400730000009E0000007B00330064003200370035006300660065002D003500340033000D
:4039800035002D0034006400640035002D0061006300630061002D003900660062003900390035006500320066003600330038007D0000007B00330064003200370035001F
:4039C0006300660065002D0035003400330035002D0034006400640035002D0061006300630061002D003900660062003900390035006500320066003600330038007D0098
:403A0000000000007265706C792030782530327800686F6D696E6700626567696E6E696E67207365656B2066726F6D20256420746F2025640066696E69736865642073652D
:403A4000656B00796573006E6F00647269766520303A20257320647269766520313A2025730057616974696E6720666F72205553422E2E2E0055534220726561647900631F
:403A80006F6D6D616E6420307825303278006661696C2025642B25642B2564203D3D2025642C206E6F74202564007061737365643D256400756E64657272756E2061667479
:403AC0006572202564207061636B65747300636F756E743D256420693D256420643D256400636D645F777269746500703D25642063723D25642063773D256420663D2564F8
:403B000020773D256420696E6465783D256420756E64657272756E3D256400756E64657272756E2100737563636573730073746172742065726173696E670073746F702027
:403B400065726173696E670069646C650000510040100040510040300000000140001000140140000800400140000A004C01400002005001402000303132333435363738CF
:403B80003941424344454600000100000004000000100001000000040000001001000000A43B000001000000733C0000000000000000000001000000BC3B00000100000084
:403BC000453C000004000000DE3B0000000000000000000000000000DC3B0000FF00000001024000FF00000082024000FF00000003034000FF00000084034000FF000203FC
:403C000004030904160346006C007500780045006E00670069006E0065002A0343006F0077006C00610072006B00200054006500630068006E006F006C006F006700690036
:403C4000650073000009022E0001010080320904000004FF00000107050102400000070582024000000705030340000A0705840340000A12010002FF0001080912006E016F
:403C800000020180014300232D302B2000686C4C00656667454647003031323334353637383961626364656600000000F8B500BFF8BC08BC9E4670475900000025110000DE
:403CC000F8B500BFF8BC08BC9E46704735000000E03C0000C880FF1F9800000028120000000000008893FF1FFFFF0000675000400C00000007000000FFFFFFFF7F80000080
:403D00003F0000000000007D00FA0000400000000090D0030000000000000000000000000000000000000000000000000000000000000000853C0000000000000000000069
:403D400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FC80FF1F0000000000000000A9
:403D80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003
:403DC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C3
:403E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082
@ -4098,48 +4098,48 @@
:40FF80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041
:40FFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
:0200000480007A
:400000000145004009520040015B00400165004047000140380101404E0201404703014046040140440501404C060140490701402C08014031090140040B0140030D014075
:400040006C14014057150140591601404B17014011190140071B01400B400140104101400C4201400F43014002440140054501400A4601400B4701400C480140104901404C
:40008000174C01400C4D014006500140045101407E02080409021082110218021904601561137C402721240A02E40303040E0520064107C00A840B900E840F901001120693
:4000C00013901684179019011A841B481C081DFC1E221F0221C122182324268427902B9C2CE72E10300733E0351F36F83A023B084204460E480449FF4AFF4BFF4F83580B46
:40010000590B5A045B045C995D095F01868088028A8190E7921094019686980F9A409C0F9E20A2E0A618A804AA81AE80B207B4F8D80BDC09DF010022022003010524084089
:4001400009080A080B400C420D200E2010021108130215801610172119011A011C241E201F0C210422082308284029042B88312032643301382039883A013B804240528017
:400180005A215B4A600461016218634278027C028280C06FC2FFC4FDCA07CC0FCE0FD001D60FD80FDE8103140404050806110710081F0A800C010E1E0FE4101F124015102D
:4001C00016E0178218021A111B141C1F1E20211023212408261127142B142D1F2F4032F0330F360F37F03A083B80580B590B5B045C995F018203830884618608873088204C
:4002000089108A508B828DC78E108F2892109304961C97C49A109B04A061A110A204A341A440A630A704AB04AC7CAE02AF04B01FB30FB460B5F4BB08D80BD90BDB04DC99FF
:40024000DF01012A02200301040805400708081009080B8C0C420E200F04104211081504180819221A0C1B011C221D4C1E211F4820042218242025442731285029402B8839
:400280002C022E042F883204331134103508380439813A103D8C3E016230680269806A806D4078027C029308942096E897809C029D249F61A403A50CA620A781AA01AB0889
:4002C000C0EFC2FEC42DCAFFCC67CEDFDE81E208021004400620080C0C010E0210081101140818051A081C04200828202AC02CE02F0130603280341E3501360138203E40ED
:400300005608580459045B045C995D905F0181608209842487108A128B108F10903F93039407950197089B1C9D40A036A209A310A710A82DA901AA12AB04AC40AD7CAF0220
:40034000B11FB238B440B540B607B720BE10D608D80BD90BDB04DC99DD90DF01000201280302040A070409980B800C020E20104212081550170119281A401B401E201FA0A5
:4003800022102662274828022C022E052F043302364237283C203D803F0558805D80600267016B026D106F017C0287208B848F4091409205930496409B209C029F48A0124A
:4003C000A140A208A620A780AD01AE08C07FC23FC4DBCAF8CCF1CEF0D618D818DE80E208E408EA040104020F0440068008700A800C800E200F0110011106130114081602E9
:40040000170218011C0F1EF0200124072608288029052A102B022C082E04310736FF3E403F01580459045B045F018001811C820283E18402852087C0880489828B218C0248
:400440008D088F10900491FF940495089710980599029C049D04A002A1FCA402A520A740A802AC03AE04AF02B007B3FCB503BB20BE01D804D904DB04DC90DF010081012440
:4004800004450520084009080A080B810C080D010E020F041008120113861544172219011C0220042114222C23282740280229012B202D1030083180321033023705396892
:4004C0003A013D82588068066C016D806F048004810184048F0191109220940A9780980299209B019C409F04A401A598A702A880A980AF02C0FFC2FFC4FFCA2BCCCFCE9F8E
:40050000D608E24CE402E605EA04EC02EE0C06010B0410041304160119021C022101240829042C013001310232043304340236083701380239083E553F45560858045904A9
:400540005B045D905F0183018A029501A401AC02B401B501B602B880B920BE50BF10D804D904DB04DF01030804080D800F50100212101410154019101A021E2A1F022210BD
:40058000250226882C802F083308372138023F10584059105DA45E01645266106A406C076D406F09800282C085808608890293409980AF40C022C2B0C4A5CAA0CCA2CE2157
:4005C000D6FCD8F0EA04EE0C9980E208E448EA049980B180E208018002200608070108280B100C020D210E2C1028110F1434152117081828192F1C101E021F2F202821210D
:40060000230224022740282829212B042C202D212E012F0E321F336035803620371F39083E443F1040524720482049FF4AFF4BFF4D204EF05110580459045A045B045C9038
:400640005D095F01610862406340648066406740800F810183028503861087FC88208A018B048C208DFF8E0290039302942395809608970499049A409B209D049E2F9FF9AF
:40068000A023A104A20CA308A423A504A604A740A82FA904AB10AF02B060B3FFB460B61FB822BF04D804D904DB04DC09DF01012202200301040205040608070209880A04D5
:4006C0000B800C080D800E48118012081305144015051610171019881C401D401E041F602118228023112410284129082A102D202E402F6030083240331234013680372A63
:40070000381039423A083C063D803F1041104980510159025A0269806C016D806F0285018D409004914592C2930494429604979199249A089B3B9D40A009A180A22CA548B8
:40074000A722AC02B704C0FFC2FFC47FCAFFCCFFCEFFD004D208E402E808EE1200FF01880403054606FC07B8080C09880A100C0C0EF10F0112081510160417A0180C198849
:400780001A401C011D421E021F04218822022480259A260C2720280C29882A202E022F0431C132FF333F35C137C139A23E043F04580459045B045C905F01800282058521EF
:4007C000870288028C0291279403950896049B389D049F01A118A340A706AC02AF01B006B107B310B401B560B708BE10BF40C006C5ECC803C9FFCAFFCBFFD004D601D8046C
:40080000D904DA04DB04DC99DD09DF01E2C00020010203020406056007100A200C010D800E040F1013801542172819131D042003218022D5240425102605278128102901DB
:400840002A802D202E402F603180321433023680372838043C043D803F11481049104B0264016701684869476A406C02728873068410860194419546978799209B2C9F827A
:40088000A228A448A598A601A722AB40B002B481C0FBC2F4C4F1CAFBCC7FCEF2D204E220E604EA4082108A018E048F029222960197109D209F20A214A302B080B104B6017C
:4008C000E220EA10EC808F209F20AF10B211B520E412EE021B011F083240330836843B408140C630CCF0CE10E62030803204358037083A023D408480914096809740A6046A
:40090000AE80AF01CCF0CE60E210508057208580968897409D809E02A604AB08D460E210EA20832284808E02968097409E02A480A620A720AA24AE04E230E620EC80EE30D9
:400940001501C4045D828740B101D605EE011B04850287108D8097809D82A480A880C608E4020B880C800F108A109798A480A740AB44AF04C20FE404262080018A01974061
:40098000A280A302A620AA40B680C820E620EE8052805302551070017E0190029202A110A280A302AF40B510D4E0DC80DE20EE40052008040E020F801F1053805610588014
:4009C00063028E208F809A109D20A740B120C001C20DC601D407D602E002E402EA04762084809A209C80AF10B004B301B601DE04E801EA08EE01010109010B010D011101C4
:400A00001B0100FF01AB020211050000BF0000A09F001F000000000000000000100000004000000000000000C0000000FF0000B847004700000100008000000282008200DC
:400A400000000000000707000700000027001801270018010004000000050000000000000000000000000000000000000000000000000000000000000000000000000000D8
:400000000145004008520040015B004001650040020101402C0201402D0301400F0501400B0701406308014056090140570A01404A0B0140500C01404A0D0140480E014042
:40004000390F01400415014004170140671801404B190140361A0140471B01400D40014010410140134201400D43014004440140064501400A460140084701400B48014097
:4000800010490140144C01400C4D014006500140045101407E02080109441180180219016009610A7C402721290AE204E6080F011001150126012E012F013001310138026A
:4000C00039023E013F01580459045B045F01810883048C018D0E910194029701A404AD04AE04AF0AB004B102B301B402B50CB601B802B908BE51BF04D608D804D909DB0401
:40010000DC90DD90DF01020208080C02100213081540170818081A801B801C80200821012210231026012A802C02300233043708388039023D405840600268026B0C6D4004
:40014000782088028B109840B040C001C214C4A5CA18CC43CE19D608D808DE04E208E68080088201928094809C289E80A201A602B044B380B404B610E604EA01EE44804068
:40018000842094809C209E80A602AA40E211E608EA01EE0C00800121020403020403052106FC0704080109210A020B080C040E100F4010FF11011404152F162019201A0495
:4001C0001B011C041D211EF91F0E220224042608271028042A402B2F2D0F2E02311F336034FF39083A0C3E104003450C470E481149FF4AFF4BFF50045609581859045A044E
:400200005B045C995D995F0162C081028407850186388702880189048C028D048E04902091049208940895029610983F99029C089D049E30A105A503A704A802A902AA0417
:40024000AC3FAD02B43FB507B63FBF10D608D804D904DB04DC09DD90DF010010010803010490050909240A420C030E020F24100A15081602172119401A021B501E101F107B
:400280002120242025052603272029982B402C022F18300831803202332035983701388039013B183C803D113E0447084F0856085A805F4062806302670269806B026C0881
:4002C0006D116E846F21759076467F01814087018E08908094209581971499049C019DC99E449F03A382A408A682A729C0F7C2FFC4F3CA7FCCFFCEFFD040D618D818DE1080
:40030000E210EA01EE0C0101041205010624084209800A100B200C040D700F80107011081201130414061508170219071A081B081D801E601F102140221023802404250F16
:4003400027F028042B0F2C802D01300F320F348035FF3670380A3B0C3E103F105804590A5B045C995F01824185F186418704888089208A018B118CF18E0291F1924E930218
:40038000954096419711980199109A149BE19F0EA1F1A308A580A641A711A801AA28B00FB10FB3F0B4F0BA02BB02D80BD90BDB04DC99DF010108020203080510076208017B
:4003C00009200A220D020E1410021240131414081508188819041A221B821C801D121F1021282501277029892B082D022F103008314033213608376238A039013A043E1454
:4004000068026D407A107E80800481408D108F019108921495809702980899029D109F72A218A440A509A701A809B340B408B502B640C0F7C27FC46FCACFCCFFCE6FDE8298
:40044000E401E8F0EC20EE40020103C4070808050A020B040C380D200F82101011C7120813181602170418081A301B041F302520274128042B042C062E012F043020320715
:40048000330F341837F43B083E04580459085B045C995F0182108303840285628604870489208A108B508DFC8E108F0192039420964097809A109B1C9C409E209F10A07C22
:4004C000A162A201A308A61CA710A940AA10AB30AC02AE08AF10B01FB11FB360B460B780D808D908DC99DF0100A001800204036004020706081009080A800B010C080E8878
:400500001218134215401608180419201A841B201C081EA02105220923112504268429922A242D802E102F20301031083350340136883722380439823B103D0A3E203F4070
:40054000611062106D406F047B037F028A08900292509508968297129C019D329F62A018A444A501A690A721C0DFC27FC4CFCA7FCCFECEFFDE18E020E208EA8000020120A4
:40058000024803C004C20502062407440A030B900E9011FC129013011690170318201AC01B901E901F9020FC21C222012328269027902A9C2B9C2F90321F331F34E037E015
:4005C0003A203B80420647E0482049FF4AFF4BFF4F83580859085A045B045C995D095F0180E28208861088E28A049210942096D09A03A0FCA201A440A630A880AA30AE1C91
:40060000B2E0B41FD808DB04DC09DF01009001C6032005260610072009010A140B420D200E100F40121013121548171218101A241B101C201D201E101F0820022204230148
:4006400029862B08300131103204335039043B1148054A0252015AAA6120628263206A406C097B037F0282018610C07FC27FC4FECA0FCC0FCE07D204D60FD80FDE188A02E6
:40068000A602E604EE02A602B680E401EE020010022003020406063807040802090C0A040BF10C080D0C0F20100813081408150C17101808190C1B401C081F0221802204B3
:4006C000230C250327FC281A29012A202B022DFF2E01303F31FF3E013F01580459045B045F0181238280830C842286CC870188308A028B2F8CC88D0F9123924894C8952367
:40070000970898D499239B049CC89F10A0C8A120A302A4C8A52FA802AB40AC80AE01B080B261B31FB41FB560B760B808B9A0BE11C046C620C808C9FFCAFFCBFFCD20CEF004
:40074000D110D804D904DA04DB04DC99DD09DF01E108E240E340E480E640E7400010010803820490050909080A420B040E020F941002110A120415401721184019181C80B4
:40078000218C2314274029182A022D4A2F0830083361350436413720381039413A043D013F9440404A085010584059205F806C026D4082409080910192029410950896C023
:4007C000971C99049A069B219D099F08A008A105A2C1A3E6A442A580A708B340B610C0FFC2FFC4DFCAF7CCFFCEFFD001D204D61CEE120008060209020D01100411041202D8
:4008000018041F04220428012C063006310132013302340835043A023E143F055608580459045B045C995D905F0181028405881090019236930198019C319E0AAC05AD012B
:40084000B00CB102B210B420B501B603BE04BF11D608D804D904DB04DC09DD90DF0101080240051809200A400E820F08100811401280154019231A501C101D101E821F0E2C
:4008800020402120220827882F02300232803601382039803F0158205A805F40600862406701680869806F0180408210844887408B018E4090209140941096409708982064
:4008C00099089C409D239F88A002A280A304A608A708B020B410C06CC2DAC48BCA10CC89CE8CD61CD81CE001E622EE021B011F083140330836843B4083408540C630CCF07B
:40090000CE10E220E61030803204364037043A083F80848088029740A340A604AE80AF01CCF0CE60E2105004570880049002960897409A049E089F04A002A120A644A80182
:40094000AA04B520D460E680EA80EE208E08900297409A049E08A002A120A640AA04AB0CAE04EA80EEB014407008C404DC0160808740A408B040D802EA011B0482209780E6
:400980009940B008B140B480C608E809EC040B880F4197899940A220AB05AF04C20F24088004900297409A04A002A120AE40C820E480EE4050015120560470027F018301EC
:4009C00090029A04A002A120AF40D4E0DC80DE20E620EE40070208080D010E021F10520854085940622095029940A220AF40B101C001C20DC601D407D802EA04800887021E
:400A00008E019B02A201A408AF10B008B608E208EA01EC02010109010D010F0111011D0100FF01AB02021105BF0000A09F001F0000000000000000001000000040000000B7
:400A400000000000C0000000FF0000B84700470000010000800000008000800000000000000707000700000027001801270018010004000000050000000000000000000052
:400A80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036
:400AC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F6
:400B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B5
@ -4615,12 +4615,12 @@
:0200000490105A
:04000000BC90ACAF55
:0200000490303A
:02000000FEBF41
:0200000018EFF7
:0200000490402A
:4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0
:400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080
:400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040
:4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
:0200000490501A
:0C00000000012E16106900002E300F28A1
:0C00000000012E16106900002E30295857
:00000001FF

View file

@ -816,7 +816,7 @@
<Group key="v1">
<Data key="cy_boot" value="cy_boot_v6_10" />
<Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" />
<Data key="LIN_Dynamic" value="LIN_Dynamic_v5_0" />
<Data key="LIN_Dynamic" value="LIN_Dynamic_v6_0" />
</Group>
</Group>
<Data key="DataVersionKey" value="2" />

File diff suppressed because it is too large Load diff

View file

@ -23,36 +23,49 @@ module Sampler (
reg [5:0] counter;
reg index_q;
reg rdata_q;
reg index_edge;
reg rdata_edge;
reg req_toggle;
reg rdata_toggle;
reg old_rdata_toggle;
reg index_toggle;
reg old_index_toggle;
always @(posedge rdata)
begin
rdata_toggle <= ~rdata_toggle;
end
always @(posedge index)
begin
index_toggle <= ~index_toggle;
end
always @(posedge sampleclock)
begin
if (reset)
begin
old_rdata_toggle <= 0;
old_index_toggle <= 0;
index_edge <= 0;
rdata_edge <= 0;
index_q <= 0;
rdata_q <= 0;
counter <= 0;
req_toggle <= 0;
end
else
begin
/* Both index and rdata are active high -- positive-going edges
* indicate the start of an index pulse and read pulse, respectively.
*/
/* If data_toggle or index_toggle have changed state, this means that they've
* gone high since the last sampleclock. */
index_edge <= index && !index_q;
index_q <= index;
index_edge <= index_toggle != old_index_toggle;
old_index_toggle <= index_toggle;
rdata_edge <= rdata && !rdata_q;
rdata_q <= rdata;
rdata_edge <= rdata_toggle != old_rdata_toggle;
old_rdata_toggle <= rdata_toggle;
if (rdata_edge || index_edge || (counter == 6'h3f)) begin
opcode <= { rdata_edge, index_edge, counter };

View file

@ -147,7 +147,7 @@ static void set_drive_flags(struct set_drive_frame* flags)
current_drive_flags = *flags;
DRIVESELECT_REG_Write(flags->drive ? 2 : 1); /* select drive 1 or 0 */
DENSITY_REG_Write(flags->high_density); /* density bit */
DENSITY_REG_Write(!flags->high_density); /* double density bit */
INDEX_REG_Write(flags->index_mode);
}
@ -221,7 +221,7 @@ static int usb_read(int ep, uint8_t buffer[FRAME_SIZE])
static void cmd_get_version(struct any_frame* f)
{
DECLARE_REPLY_FRAME(struct version_frame, F_FRAME_GET_VERSION_REPLY);
r.version = FLUXENGINE_VERSION;
r.version = FLUXENGINE_PROTOCOL_VERSION;
send_reply((struct any_frame*) &r);
}
@ -937,3 +937,75 @@ int main(void)
}
}
}
const uint8_t USBFS_MSOS_CONFIGURATION_DESCR[USBFS_MSOS_CONF_DESCR_LENGTH] = {
/* Length of the descriptor 4 bytes */ 0x28u, 0x00u, 0x00u, 0x00u,
/* Version of the descriptor 2 bytes */ 0x00u, 0x01u,
/* wIndex - Fixed:INDEX_CONFIG_DESCRIPTOR */ 0x04u, 0x00u,
/* bCount - Count of device functions. */ 0x01u,
/* Reserved : 7 bytes */ 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
/* bFirstInterfaceNumber */ 0x00u,
/* Reserved */ 0x01u,
/* compatibleId - "WINUSB\0\0" */ 'W', 'I', 'N', 'U', 'S', 'B', 0, 0,
/* subcompatibleID - "00001\0\0" */ '0', '0', '0', '0', '1',
0x00u, 0x00u, 0x00u,
/* Reserved : 6 bytes */ 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u
};
const uint8_t USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[224] = {
/* Length; 4 bytes */ 224, 0, 0, 0,
/* Version; 2 bytes */ 0x00, 0x01, /* 1.0 */
/* wIndex */ 0x05, 0x00,
/* Number of sections */ 0x01, 0x00,
/* Property section size */ 214, 0, 0, 0,
/* Property data type */ 0x07, 0x00, 0x00, 0x00, /* 7 = REG_MULTI_SZ Unicode */
/* Property name length */ 42, 0,
/* Property name */ 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e', 0,
'I', 0, 'n', 0, 't', 0, 'e', 0, 'r', 0, 'f', 0,
'a', 0, 'c', 0, 'e', 0, 'G', 0, 'U', 0, 'I', 0,
'D', 0, 's', 0, 0, 0,
/* Property data length */ 158, 0, 0, 0,
/* GUID #1 data */ '{', 0, '3', 0, 'd', 0, '2', 0, '7', 0, '5', 0,
'c', 0, 'f', 0, 'e', 0, '-', 0, '5', 0, '4', 0,
'3', 0, '5', 0, '-', 0, '4', 0, 'd', 0, 'd', 0,
'5', 0, '-', 0, 'a', 0, 'c', 0, 'c', 0, 'a', 0,
'-', 0, '9', 0, 'f', 0, 'b', 0, '9', 0, '9', 0,
'5', 0, 'e', 0, '2', 0, 'f', 0, '6', 0, '3', 0,
'8', 0, '}', 0, '\0', 0,
/* GUID #2 data */ '{', 0, '3', 0, 'd', 0, '2', 0, '7', 0, '5', 0,
'c', 0, 'f', 0, 'e', 0, '-', 0, '5', 0, '4', 0,
'3', 0, '5', 0, '-', 0, '4', 0, 'd', 0, 'd', 0,
'5', 0, '-', 0, 'a', 0, 'c', 0, 'c', 0, 'a', 0,
'-', 0, '9', 0, 'f', 0, 'b', 0, '9', 0, '9', 0,
'5', 0, 'e', 0, '2', 0, 'f', 0, '6', 0, '3', 0,
'8', 0, '}', 0, '\0', 0, '\0', 0
};
uint8 USBFS_HandleVendorRqst(void)
{
if (!(USBFS_bmRequestTypeReg & USBFS_RQST_DIR_D2H))
return false;
switch (USBFS_bRequestReg)
{
case USBFS_GET_EXTENDED_CONFIG_DESCRIPTOR:
switch (USBFS_wIndexLoReg)
{
case 4:
USBFS_currentTD.pData = (volatile uint8 *) &USBFS_MSOS_CONFIGURATION_DESCR[0u];
USBFS_currentTD.count = USBFS_MSOS_CONFIGURATION_DESCR[0u];
return USBFS_InitControlRead();
case 5:
USBFS_currentTD.pData = (volatile uint8 *) &USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[0u];
USBFS_currentTD.count = USBFS_MSOS_EXTENDED_PROPERTIES_DESCR[0u];
return USBFS_InitControlRead();
}
default:
break;
}
return true;
}

View file

@ -1,18 +0,0 @@
const READ = 1
const WRITE = 2
filename = "Generated_Source\PSoC5\USBFS_descr.c"
set fso = CreateObject("Scripting.FileSystemObject")
set file = fso.OpenTextFile(filename, READ)
text = file.ReadAll
file.Close
set r = New RegExp
r.MultiLine = True
r.Pattern = "/\* +compatibleID.*\n.*"
text = r.replace(text, "'W', 'I', 'N', 'U', 'S', 'B', 0, 0,")
set file = fso.CreateTextFile(filename, True)
file.Write text
file.Close

View file

@ -1,6 +1,9 @@
PACKAGES = zlib sqlite3 libusb-1.0 protobuf
PACKAGES = zlib sqlite3 protobuf
export CFLAGS = -x c++ --std=gnu++2a -ffunction-sections -fdata-sections \
export CFLAGS = \
-ffunction-sections -fdata-sections
export CXXFLAGS = $(CFLAGS) \
-x c++ --std=gnu++2a \
-Wno-deprecated-enum-enum-conversion \
-Wno-deprecated-enum-float-conversion
export LDFLAGS = -pthread
@ -11,15 +14,31 @@ export LDOPTFLAGS = -Os
export CDBGFLAGS = -O0 -g
export LDDBGFLAGS = -O0 -g
ifeq ($(OS), Windows_NT)
else
ifeq ($(shell uname),Darwin)
else
PACKAGES += libudev
endif
endif
ifeq ($(OS), Windows_NT)
export PROTOC = /mingw32/bin/protoc
export CC = /mingw32/bin/gcc
export CXX = /mingw32/bin/g++
export AR = /mingw32/bin/ar rc
export RANLIB = /mingw32/bin/ranlib
export STRIP = /mingw32/bin/strip
export CFLAGS += -I/mingw32/include/libusb-1.0 -I/mingw32/include
export LDFLAGS +=
export LIBS += -L/mingw32/lib -static -lz -lsqlite3 -lusb-1.0 -lprotobuf
export CFLAGS += -I/mingw32/include
export CXXFLAGS += $(shell wx-config --cxxflags --static=yes)
export GUILDFLAGS += -lmingw32
export LIBS += -L/mingw32/lib -static\
-lsqlite3 -lz \
-lsetupapi -lwinusb -lole32 -lprotobuf -luuid
export GUILIBS += -L/mingw32/lib -static -lsqlite3 \
$(shell wx-config --libs --static=yes core base) -lz \
-lcomctl32 -loleaut32 -lspoolss -loleacc -lwinspool \
-lsetupapi -lwinusb -lole32 -lprotobuf -luuid
export EXTENSION = .exe
else
@ -28,20 +47,31 @@ ifneq ($(packages-exist),yes)
$(warning These pkg-config packages are installed: $(shell pkg-config --list-all | sort | awk '{print $$1}'))
$(error You must have these pkg-config packages installed: $(PACKAGES))
endif
wx-exist = $(shell wx-config --cflags > /dev/null && echo yes)
ifneq ($(wx-exist),yes)
$(error You must have these wx-config installed)
endif
export PROTOC = protoc
export CC = gcc
export CXX = g++
export AR = ar rc
export RANLIB = ranlib
export STRIP = strip
export CFLAGS += $(shell pkg-config --cflags $(PACKAGES))
export CFLAGS += $(shell pkg-config --cflags $(PACKAGES)) $(shell wx-config --cxxflags)
export LDFLAGS +=
export LIBS += $(shell pkg-config --libs $(PACKAGES))
export GUILIBS += $(shell wx-config --libs core base)
export EXTENSION =
ifeq ($(shell uname),Darwin)
AR = ar rcS
RANLIB += -c -no_warning_for_no_symbols
export CC = clang
export CXX = clang++
export COBJC = clang
export LDFLAGS += -framework IOKit -framework CoreFoundation
export CFLAGS += -Wno-deprecated-declarations
endif
endif
@ -51,10 +81,12 @@ CFLAGS += -Ilib -Idep/fmt -Iarch
export OBJDIR = .obj
.PHONY: all
all: .obj/build.ninja
@ninja -f .obj/build.ninja -k 0
@ninja -f .obj/build.ninja
@if command -v cscope > /dev/null; then cscope -bRq; fi
.PHONY: clean
clean:
@echo CLEAN
@rm -rf $(OBJDIR)
@ -64,3 +96,10 @@ clean:
@mkdir -p $(OBJDIR)
@sh $< > $@
.PHONY: compdb
compdb:
@ninja -f .obj/build.ninja -t compdb > compile_commands.json
.PHONY: fluxengine fluxengine-debug
fluxengine fluxengine-debug: .obj/build.ninja
@ninja -f .obj/build.ninja "$@"

View file

@ -2,7 +2,7 @@ FluxEngine
==========
(If you're reading this on GitHub, the formatting's a bit messed up. [Try the
version on cowlark.com instead.](http://cowlark.com/fluxengine/)
version on cowlark.com instead.](http://cowlark.com/fluxengine/))
**Breaking news!** As of 2021-05-21, the command line environment has changed
_substantially_ (to make it more consistent and flexible, and allow some new
@ -37,11 +37,6 @@ FluxEngine features are available with the GreaseWeazle and it works out-of-the
box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md)
for more information.
**Important note.** On 2020-04-02 I changed the bytecode format (and firmware).
Flux files will need to be upgraded with `fluxengine upgradefluxfile`. The new
format should be more reliable and use way, way less bandwidth. Sorry for the
inconvenience.
Where?
------
@ -66,14 +61,18 @@ following friendly articles:
flux files and image files ∾ knowing what you're doing
- [Using GreaseWeazle hardware with the FluxEngine client
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
go for help
software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to
go for help
- [Configuring for your drive](doc/drives.md) ∾ but I don't have a 80 track
drive! ∾ reading and writing 40 track disks ∾ Shugart and Apple II
- [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact
science ∾ the sector map ∾ clock detection and the histogram
science ∾ the sector map ∾ clock detection and the histogram
- [Checking your drive](doc/driveresponse.md) ∾ you can't do that with that ∾
measuring your drive's ability to work with exotic formats
- [Disk densities](doc/driveresponse.md) ∾ what's the difference between an HD
and DD disk? ∾ you can't do that with that ∾ measuring your drive's ability to
work with exotic formats ∾ I think my drive is broken
Which?
------
@ -93,16 +92,21 @@ people who've had it work).
| Format | Read? | Write? | Notes |
|:------------------------------------------|:-----:|:------:|-------|
| [IBM PC compatible](doc/disk-ibm.md) | 🦄 | 🦄 | and compatibles (like the Atari ST) |
| [Atari ST](doc/disk-atarist.md) | 🦄 | 🦄 | technically the same as IBM, almost |
| [Acorn ADFS](doc/disk-acornadfs.md) | 🦄 | 🦖* | single- and double- sided |
| [Acorn DFS](doc/disk-acorndfs.md) | 🦄 | 🦖* | |
| [Ampro Little Board](doc/disk-ampro.md) | 🦖 | 🦖* | |
| [Apple II DOS 3.3](doc/disk-apple2.md) | 🦄 | | doesn't do logical sector remapping |
| [Agat](doc/disk-agat.md) | 🦖 | | Soviet Union Apple-II-like computer |
| [Apple II](doc/disk-apple2.md) | 🦄 | 🦄 | |
| [Amiga](doc/disk-amiga.md) | 🦄 | 🦄 | |
| [Commodore 64 1541/1581](doc/disk-c64.md) | 🦄 | 🦄 | and probably the other formats |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦖 | |
| [Brother 120kB](doc/disk-brother.md) | 🦄 | 🦄 | |
| [Brother 240kB](doc/disk-brother.md) | 🦄 | 🦄 | |
| [Brother FB-100](doc/disk-fb100.md) | 🦖 | | Tandy Model 100, Husky Hunter, knitting machines |
| [Macintosh 800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | and probably the 400kB too |
| [Elektronika BK](doc/disk-bd.md) | 🦄 | 🦄 | Soviet Union PDP-11 clone |
| [Macintosh 400kB/800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | |
| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required |
| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | |
| [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme |
{: .datatable }
@ -125,9 +129,9 @@ at least, check the CRC so what data's there is probably good.
| [DVK MX](doc/disk-mx.md) | 🦖 | | Soviet PDP-11 clone |
| [VDS Eco1](doc/disk-eco1.md) | 🦖 | | 8" mixed format |
| [Micropolis](doc/disk-micropolis.md) | 🦄 | | Micropolis 100tpi drives |
| [Northstar(doc/disk-northstar.md) | 🦖 | 🦖 | 5.25" hard sectors |
| [Northstar](doc/disk-northstar.md) | 🦖 | 🦖 | 5.25" hard sectors |
| [TI DS990 FD1000](doc/disk-tids990.md) | 🦄 | 🦄 | 8" |
| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 8" |
| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 5.25" GCR encoded |
| [Zilog MCZ](doc/disk-zilogmcz.md) | 🦖 | | 8" _and_ hard sectors |
{: .datatable }
@ -146,7 +150,7 @@ at least, check the CRC so what data's there is probably good.
There hasn't been a lot of demand for this yet; if you have a pressing
need to write weird disks, [please
ask](https://github.com/davidgiven/fluxengine/issues/new). I haven't
implement write support for PC disks because they're boring and I'm lazy,
implemented write support for PC disks because they're boring and I'm lazy,
and also because they vary so much that figuring out how to specify them
is hard.
@ -230,3 +234,6 @@ As an exception, `dep/snowhouse` contains the snowhouse assertion library,
taken from https://github.com/banditcpp/snowhouse. It is Boost Standard License
1.0 licensed. Please see the contents of the directory for the full text.
As an exception, `dep/libusbp` contains the libusbp library, taken from
https://github.com/pololu/libusbp. It is MIT licensed. Please see the contents
of the directory for the full text.

View file

@ -30,17 +30,14 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
if (_fmr->eof() || !_sector->clock)
return UNKNOWN_RECORD;
return SECTOR_RECORD;
return seekToPattern(SECTOR_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
/* Skip ID mark. */
/* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */
readRawBits(16);

22
arch/agat/agat.cc Normal file
View file

@ -0,0 +1,22 @@
#include "globals.h"
#include "decoders/decoders.h"
#include "agat.h"
#include "bytes.h"
#include "fmt/format.h"
uint8_t agatChecksum(const Bytes& bytes)
{
uint16_t checksum = 0;
for (uint8_t b : bytes)
{
if (checksum > 0xff)
checksum = (checksum + 1) & 0xff;
checksum += b;
}
return checksum & 0xff;
}

11
arch/agat/agat.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef AGAT_H
#define AGAT_H
#define AGAT_SECTOR_SIZE 256
extern std::unique_ptr<AbstractDecoder> createAgatDecoder(const DecoderProto& config);
extern uint8_t agatChecksum(const Bytes& bytes);
#endif

5
arch/agat/agat.proto Normal file
View file

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

97
arch/agat/decoder.cc Normal file
View file

@ -0,0 +1,97 @@
#include "globals.h"
#include "decoders/decoders.h"
#include "agat.h"
#include "crc.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "sector.h"
#include "bytes.h"
#include "fmt/format.h"
#include <string.h>
/*
* data: X X X X X X X X X - - X - X - X - X X - X - X - = 0xff956a
* flux: 01 01 01 01 01 01 01 01 01 00 10 01 00 01 00 01 00 01 01 00 01 00 01 00 = 0x555549111444
*
* data: X X X X X X X X - X X - X - X - X - - X - X - X = 0xff6a95
* flux: 01 01 01 01 01 01 01 01 00 01 01 00 01 00 01 00 01 00 10 01 00 01 00 01 = 0x555514444911
*
* Each pattern is prefixed with this one:
*
* data: - - - X - - X - = 0x12
* flux: (10) 10 10 10 01 00 10 01 00 = 0xa924
* magic: (10) 10 00 10 01 00 10 01 00 = 0x8924
* ^
*
* This seems to be generated by emitting A4 in MFM and then a single 0 bit to
* shift it out of phase, so the data bits become clock bits and vice versa.
*
* X - X - - X - - = 0xA4
* 0100010010010010 = MFM encoded
* 1000100100100100 = with trailing zero
* - - - X - - X - = effective bitstream = 0x12
*
*/
static const uint64_t SECTOR_ID = 0x8924555549111444;
static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID);
static const uint64_t DATA_ID = 0x8924555514444911;
static const FluxPattern DATA_PATTERN(64, DATA_ID);
static const FluxMatchers ALL_PATTERNS = {
&SECTOR_PATTERN,
&DATA_PATTERN
};
class AgatDecoder : public AbstractDecoder
{
public:
AgatDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ALL_PATTERNS);
}
void decodeSectorRecord() override
{
if (readRaw64() != SECTOR_ID)
return;
auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4);
if (bytes[3] != 0x5a)
return;
_sector->logicalTrack = bytes[1] >> 1;
_sector->logicalSector = bytes[2];
_sector->logicalSide = bytes[1] & 1;
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord() override
{
if (readRaw64() != DATA_ID)
return;
Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE+2)*16)).slice(0, AGAT_SECTOR_SIZE+2);
if (bytes[AGAT_SECTOR_SIZE+1] != 0x5a)
return;
_sector->data = bytes.slice(0, AGAT_SECTOR_SIZE);
uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE];
uint8_t gotChecksum = agatChecksum(_sector->data);
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
};
std::unique_ptr<AbstractDecoder> createAgatDecoder(const DecoderProto& config)
{
return std::unique_ptr<AbstractDecoder>(new AgatDecoder(config));
}

View file

@ -7,7 +7,7 @@
#define AMIGA_TRACKS_PER_DISK 80
#define AMIGA_SECTORS_PER_TRACK 11
#define AMIGA_RECORD_SIZE 0x21f
#define AMIGA_RECORD_SIZE 0x21c
extern std::unique_ptr<AbstractDecoder> createAmigaDecoder(const DecoderProto& config);
extern std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config);

View file

@ -29,23 +29,23 @@ public:
_config(config.amiga())
{}
RecordType advanceToNextRecord() override
nanoseconds_t advanceToNextRecord() override
{
_sector->clock = _fmr->seekToPattern(SECTOR_PATTERN);
if (_fmr->eof() || !_sector->clock)
return UNKNOWN_RECORD;
return SECTOR_RECORD;
return seekToPattern(SECTOR_PATTERN);
}
void decodeSectorRecord() override
{
if (readRaw48() != AMIGA_SECTOR_RECORD)
return;
const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16);
if (rawbits.size() < (AMIGA_RECORD_SIZE*16))
return;
const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2);
const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE);
const uint8_t* ptr = bytes.begin() + 3;
const uint8_t* ptr = bytes.begin();
Bytes header = amigaDeinterleave(ptr, 4);
Bytes recoveryinfo = amigaDeinterleave(ptr, 16);
@ -55,12 +55,12 @@ public:
_sector->logicalSector = header[2];
uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(6, 40));
uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40));
if (gotheaderchecksum != wantedheaderchecksum)
return;
uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32();
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(62, 1024));
uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024));
Bytes data;
data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo);
@ -68,7 +68,7 @@ public:
_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location&) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
return sectors;
@ -76,6 +76,7 @@ public:
private:
const AmigaDecoderProto& _config;
nanoseconds_t _clock;
};
std::unique_ptr<AbstractDecoder> createAmigaDecoder(const DecoderProto& config)

View file

@ -3,8 +3,9 @@
#include "encoders/encoders.h"
#include "amiga.h"
#include "crc.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "arch/amiga/amiga.pb.h"
#include "lib/encoders/encoders.pb.h"
@ -12,151 +13,159 @@ static bool lastBit;
static int charToInt(char c)
{
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{
for (bool bit : src)
{
if (cursor < bits.size())
lastBit = bits[cursor++] = bit;
}
for (bool bit : src)
{
if (cursor < bits.size())
lastBit = bits[cursor++] = bit;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{
cursor += width;
lastBit = data & 1;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
cursor += width;
lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{
ByteReader br(bytes);
BitReader bitr(br);
ByteReader br(bytes);
BitReader bitr(br);
while (!bitr.eof())
{
if (cursor < bits.size())
bits[cursor++] = bitr.get();
}
while (!bitr.eof())
{
if (cursor < bits.size())
bits[cursor++] = bitr.get();
}
}
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{
if ((sector->data.size() != 512) && (sector->data.size() != 528))
Error() << "unsupported sector size --- you must pick 512 or 528";
if ((sector->data.size() != 512) && (sector->data.size() != 528))
Error() << "unsupported sector size --- you must pick 512 or 528";
uint32_t checksum = 0;
uint32_t checksum = 0;
auto write_interleaved_bytes = [&](const Bytes& bytes)
{
Bytes interleaved = amigaInterleave(bytes);
Bytes mfm = encodeMfm(interleaved, lastBit);
checksum ^= amigaChecksum(mfm);
checksum &= 0x55555555;
write_bits(bits, cursor, mfm);
};
auto write_interleaved_bytes = [&](const Bytes& bytes)
{
Bytes interleaved = amigaInterleave(bytes);
Bytes mfm = encodeMfm(interleaved, lastBit);
checksum ^= amigaChecksum(mfm);
checksum &= 0x55555555;
write_bits(bits, cursor, mfm);
};
auto write_interleaved_word = [&](uint32_t word)
{
Bytes b(4);
b.writer().write_be32(word);
write_interleaved_bytes(b);
};
auto write_interleaved_word = [&](uint32_t word)
{
Bytes b(4);
b.writer().write_be32(word);
write_interleaved_bytes(b);
};
write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6*8);
write_bits(bits, cursor, 0xaaaa, 2 * 8);
write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6 * 8);
checksum = 0;
Bytes header =
{
0xff, /* Amiga 1.0 format byte */
(uint8_t) ((sector->logicalTrack<<1) | sector->logicalSide),
(uint8_t) sector->logicalSector,
(uint8_t) (AMIGA_SECTORS_PER_TRACK - sector->logicalSector)
};
write_interleaved_bytes(header);
Bytes recoveryInfo(16);
if (sector->data.size() == 528)
recoveryInfo = sector->data.slice(512, 16);
write_interleaved_bytes(recoveryInfo);
write_interleaved_word(checksum);
checksum = 0;
Bytes header = {0xff, /* Amiga 1.0 format byte */
(uint8_t)((sector->logicalTrack << 1) | sector->logicalSide),
(uint8_t)sector->logicalSector,
(uint8_t)(AMIGA_SECTORS_PER_TRACK - sector->logicalSector)};
write_interleaved_bytes(header);
Bytes recoveryInfo(16);
if (sector->data.size() == 528)
recoveryInfo = sector->data.slice(512, 16);
write_interleaved_bytes(recoveryInfo);
write_interleaved_word(checksum);
Bytes data = sector->data.slice(0, 512);
write_interleaved_word(amigaChecksum(encodeMfm(amigaInterleave(data), lastBit)));
write_interleaved_bytes(data);
Bytes data = sector->data.slice(0, 512);
write_interleaved_word(
amigaChecksum(encodeMfm(amigaInterleave(data), lastBit)));
write_interleaved_bytes(data);
}
class AmigaEncoder : public AbstractEncoder
{
public:
AmigaEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.amiga()) {}
AmigaEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.amiga())
{
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
if ((physicalTrack >= 0) && (physicalTrack < AMIGA_TRACKS_PER_DISK))
{
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
{
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
if ((location.logicalTrack >= 0) &&
(location.logicalTrack < AMIGA_TRACKS_PER_DISK))
{
for (int sectorId = 0; sectorId < AMIGA_SECTORS_PER_TRACK;
sectorId++)
{
const auto& sector =
image.get(location.logicalTrack, location.head, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
if ((physicalTrack < 0) || (physicalTrack >= AMIGA_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
/* Number of bits for one nominal revolution of a real 200ms Amiga disk. */
int bitsPerRevolution = 200e3 / _config.clock_rate_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits,
cursor,
_config.post_index_gap_ms() * 1000 / _config.clock_rate_us(),
{true, false});
lastBit = false;
fillBitmapTo(bits, cursor, _config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), { true, false });
lastBit = false;
for (const auto& sector : sectors)
write_sector(bits, cursor, sector);
for (int sectorId=0; sectorId<AMIGA_SECTORS_PER_TRACK; sectorId++)
{
const auto& sectorData = image.get(physicalTrack, physicalSide, sectorId);
if (sectorData)
write_sector(bits, cursor, sectorData);
}
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), {true, false});
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, _config.clock_rate_us()*1e3);
return fluxmap;
}
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBits(bits,
Mapper::calculatePhysicalClockPeriod(
_config.clock_rate_us() * 1e3, 200e6));
return fluxmap;
}
private:
const AmigaEncoderProto& _config;
const AmigaEncoderProto& _config;
};
std::unique_ptr<AbstractEncoder> createAmigaEncoder(const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config));
return std::unique_ptr<AbstractEncoder>(new AmigaEncoder(config));
}

View file

@ -1,13 +1,20 @@
#ifndef APPLE2_H
#define APPLE2_H
#include <memory.h>
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#define APPLE2_SECTOR_RECORD 0xd5aa96
#define APPLE2_DATA_RECORD 0xd5aaad
#define APPLE2_SECTOR_LENGTH 256
#define APPLE2_ENCODED_SECTOR_LENGTH 342
#define APPLE2_SECTORS 16
extern std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config);
extern std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config);
#endif

View file

@ -1,4 +1,16 @@
syntax = "proto2";
import "lib/common.proto";
message Apple2DecoderProto {}
message Apple2EncoderProto
{
/* 245kHz. */
optional double clock_period_us = 1
[ default = 4, (help) = "clock rate on the real device" ];
/* Apple II disk drives spin at 300rpm. */
optional double rotational_period_ms = 2
[ default = 200.0, (help) = "rotational period on the real device" ];
}

View file

@ -71,21 +71,15 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
/* Skip ID (as we know it's a APPLE2_SECTOR_RECORD). */
readRawBits(24);
if (readRaw24() != APPLE2_SECTOR_RECORD)
return;
/* Read header. */
@ -96,26 +90,68 @@ public:
_sector->logicalTrack = combine(br.read_be16());
_sector->logicalSector = combine(br.read_be16());
uint8_t checksum = combine(br.read_be16());
// If the checksum is correct, upgrade the sector from MISSING
// to DATA_MISSING in anticipation of its data record
if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector))
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord()
void decodeDataRecord() override
{
/* Check ID. */
Bytes bytes = toBytes(readRawBits(3*8)).slice(0, 3);
if (bytes.reader().read_be24() != APPLE2_DATA_RECORD)
if (readRaw24() != APPLE2_DATA_RECORD)
return;
// Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and
// the data itself. This has been seen on real world disks
// such as the Apple II Operating System Kit from Apple2Online.
// However, I haven't seen it described in any of the various
// references.
//
// This extra '0' bit would not affect the real disk interface,
// as it was a '1' reaching the top bit of a shift register
// that triggered a byte to be available, but it affects the
// way the data is read here.
//
// While the floppies tested only seemed to need this applied
// to the first byte of the data record, applying it
// consistently to all of them doesn't seem to hurt, and
// simplifies the code.
/* Read and decode data. */
unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2;
bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength);
auto readApple8 = [&]() {
auto result = 0;
while((result & 0x80) == 0) {
auto b = readRawBits(1);
if(b.empty()) break;
result = (result << 1) | b[0];
}
return result;
};
constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH+2;
uint8_t bytes[recordLength];
for(auto &byte : bytes) {
byte = readApple8();
}
// Upgrade the sector from MISSING to BAD_CHECKSUM.
// If decode_crazy_data succeeds, it upgrades the sector to
// OK.
_sector->status = Sector::BAD_CHECKSUM;
_sector->data = decode_crazy_data(&bytes[0], _sector->status);
}
std::set<unsigned> requiredSectors(const Location& location) const override
{
std::set<unsigned> sectors;
for (int sectorId = 0; sectorId < APPLE2_SECTORS; sectorId++)
sectors.insert(sectorId);
return sectors;
}
};
std::unique_ptr<AbstractDecoder> createApple2Decoder(const DecoderProto& config)

210
arch/apple2/encoder.cc Normal file
View file

@ -0,0 +1,210 @@
#include "globals.h"
#include "arch/apple2/apple2.h"
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "sector.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "fmt/format.h"
#include "lib/encoders/encoders.pb.h"
#include <ctype.h>
#include "bytes.h"
static int encode_data_gcr(uint8_t data)
{
switch (data)
{
#define GCR_ENTRY(gcr, data) \
case data: \
return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
class Apple2Encoder : public AbstractEncoder
{
public:
Apple2Encoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.apple2())
{
}
private:
const Apple2EncoderProto& _config;
public:
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
if (location.head == 0)
{
for (int sectorId = 0; sectorId < APPLE2_SECTORS; sectorId++)
{
const auto& sector =
image.get(location.logicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
int bitsPerRevolution =
(_config.rotational_period_ms() * 1e3) / _config.clock_period_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sector : sectors)
writeSector(bits, cursor, *sector);
if (cursor >= bits.size())
Error() << fmt::format(
"track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), {true, false});
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
Mapper::calculatePhysicalClockPeriod(
_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6));
return fluxmap;
}
private:
uint8_t volume_id = 254;
/* This is extremely inspired by the MESS implementation, written by Nathan
* Woods and R. Belmont:
* https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp
* as well as Understanding the Apple II (1983) Chapter 9
* https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater
*/
void writeSector(
std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const
{
if ((sector.status == Sector::OK) or
(sector.status == Sector::BAD_CHECKSUM))
{
auto write_bit = [&](bool val)
{
if (cursor <= bits.size())
{
bits[cursor] = val;
}
cursor++;
};
auto write_bits = [&](uint32_t bits, int width)
{
for (int i = width; i--;)
{
write_bit(bits & (1u << i));
}
};
auto write_gcr44 = [&](uint8_t value)
{
write_bits((value << 7) | value | 0xaaaa, 16);
};
auto write_gcr6 = [&](uint8_t value)
{
write_bits(encode_data_gcr(value), 8);
};
// The special "FF40" sequence is used to synchronize the receiving
// shift register. It's written as "1111 1111 00"; FF indicates the
// 8 consecutive 1-bits, while "40" indicates the total number of
// microseconds.
auto write_ff40 = [&](int n = 1)
{
for (; n--;)
{
write_bits(0xff << 2, 10);
}
};
// There is data to encode to disk.
if ((sector.data.size() != APPLE2_SECTOR_LENGTH))
Error() << fmt::format(
"unsupported sector size {} --- you must pick 256",
sector.data.size());
// Write address syncing leader : A sequence of "FF40"s; 5 of them
// are said to suffice to synchronize the decoder.
// "FF40" indicates that the actual data written is "1111
// 1111 00" i.e., 8 1s and a total of 40 microseconds
//
// In standard formatting, the first logical sector apparently gets
// extra padding.
write_ff40(sector.logicalSector == 0 ? 32 : 8);
// Write address field: APPLE2_SECTOR_RECORD + sector identifier +
// DE AA EB
write_bits(APPLE2_SECTOR_RECORD, 24);
write_gcr44(volume_id);
write_gcr44(sector.logicalTrack);
write_gcr44(sector.logicalSector);
write_gcr44(volume_id ^ sector.logicalTrack ^ sector.logicalSector);
write_bits(0xDEAAEB, 24);
// Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector
// data + sum + DE AA EB (+ mystery bits cut off of the scan?)
write_ff40(8);
write_bits(APPLE2_DATA_RECORD, 24);
// Convert the sector data to GCR, append the checksum, and write it
// out
constexpr auto TWOBIT_COUNT =
0x56; // Size of the 'twobit' area at the start of the GCR data
uint8_t checksum = 0;
for (int i = 0; i < APPLE2_ENCODED_SECTOR_LENGTH; i++)
{
int value;
if (i >= TWOBIT_COUNT)
{
value = sector.data[i - TWOBIT_COUNT] >> 2;
}
else
{
uint8_t tmp = sector.data[i];
value = ((tmp & 1) << 1) | ((tmp & 2) >> 1);
tmp = sector.data[i + TWOBIT_COUNT];
value |= ((tmp & 1) << 3) | ((tmp & 2) << 1);
if (i + 2 * TWOBIT_COUNT < APPLE2_SECTOR_LENGTH)
{
tmp = sector.data[i + 2 * TWOBIT_COUNT];
value |= ((tmp & 1) << 5) | ((tmp & 2) << 3);
}
}
checksum ^= value;
// assert(checksum & ~0x3f == 0);
write_gcr6(checksum);
checksum = value;
}
if (sector.status == Sector::BAD_CHECKSUM)
checksum ^= 0x3f;
write_gcr6(checksum);
write_bits(0xDEAAEB, 24);
}
}
};
std::unique_ptr<AbstractEncoder> createApple2Encoder(const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new Apple2Encoder(config));
}

View file

@ -15,6 +15,5 @@ message BrotherEncoderProto {
optional string sector_skew = 5 [default = "05a3816b4927"];
optional BrotherFormat format = 6 [default = BROTHER240];
optional int32 bias = 7 [default = 0];
}

View file

@ -1,5 +1,4 @@
#include "globals.h"
#include "sql.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "decoders/decoders.h"
@ -60,20 +59,16 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
readRawBits(32);
if (readRaw32() != BROTHER_SECTOR_RECORD)
return;
const auto& rawbits = readRawBits(32);
const auto& bytes = toBytes(rawbits).slice(0, 4);
@ -91,9 +86,10 @@ public:
_sector->status = Sector::DATA_MISSING;
}
void decodeDataRecord()
void decodeDataRecord() override
{
readRawBits(32);
if (readRaw32() != BROTHER_DATA_RECORD)
return;
const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE*8);
const auto& rawbytes = toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE);

View file

@ -3,91 +3,95 @@
#include "encoders/encoders.h"
#include "brother.h"
#include "crc.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "arch/brother/brother.pb.h"
#include "lib/encoders/encoders.pb.h"
static int encode_header_gcr(uint16_t word)
{
switch (word)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "header_gcr.h"
#undef GCR_ENTRY
}
return -1;
switch (word)
{
#define GCR_ENTRY(gcr, data) \
case data: \
return gcr;
#include "header_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
static int encode_data_gcr(uint8_t data)
{
switch (data)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
switch (data)
{
#define GCR_ENTRY(gcr, data) \
case data: \
return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width)
{
cursor += width;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
cursor += width;
for (int i = 0; i < width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}
static void write_sector_header(std::vector<bool>& bits, unsigned& cursor,
int track, int sector)
static void write_sector_header(
std::vector<bool>& bits, unsigned& cursor, int track, int sector)
{
write_bits(bits, cursor, 0xffffffff, 31);
write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32);
write_bits(bits, cursor, encode_header_gcr(track), 16);
write_bits(bits, cursor, encode_header_gcr(sector), 16);
write_bits(bits, cursor, encode_header_gcr(0x2f), 16);
write_bits(bits, cursor, 0xffffffff, 31);
write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32);
write_bits(bits, cursor, encode_header_gcr(track), 16);
write_bits(bits, cursor, encode_header_gcr(sector), 16);
write_bits(bits, cursor, encode_header_gcr(0x2f), 16);
}
static void write_sector_data(std::vector<bool>& bits, unsigned& cursor, const Bytes& data)
static void write_sector_data(
std::vector<bool>& bits, unsigned& cursor, const Bytes& data)
{
write_bits(bits, cursor, 0xffffffff, 32);
write_bits(bits, cursor, BROTHER_DATA_RECORD, 32);
write_bits(bits, cursor, 0xffffffff, 32);
write_bits(bits, cursor, BROTHER_DATA_RECORD, 32);
uint16_t fifo = 0;
int width = 0;
uint16_t fifo = 0;
int width = 0;
if (data.size() != BROTHER_DATA_RECORD_PAYLOAD)
Error() << "unsupported sector size";
if (data.size() != BROTHER_DATA_RECORD_PAYLOAD)
Error() << "unsupported sector size";
auto write_byte = [&](uint8_t byte)
{
fifo |= (byte << (8 - width));
width += 8;
auto write_byte = [&](uint8_t byte)
{
fifo |= (byte << (8 - width));
width += 8;
while (width >= 5)
{
uint8_t quintet = fifo >> 11;
fifo <<= 5;
width -= 5;
while (width >= 5)
{
uint8_t quintet = fifo >> 11;
fifo <<= 5;
width -= 5;
write_bits(bits, cursor, encode_data_gcr(quintet), 8);
}
};
write_bits(bits, cursor, encode_data_gcr(quintet), 8);
}
};
for (uint8_t byte : data)
write_byte(byte);
for (uint8_t byte : data)
write_byte(byte);
uint32_t realCrc = crcbrother(data);
write_byte(realCrc>>16);
write_byte(realCrc>>8);
write_byte(realCrc);
write_byte(0x58); /* magic */
uint32_t realCrc = crcbrother(data);
write_byte(realCrc >> 16);
write_byte(realCrc >> 8);
write_byte(realCrc);
write_byte(0x58); /* magic */
write_byte(0xd4);
while (width != 0)
write_byte(0);
@ -95,118 +99,98 @@ static void write_sector_data(std::vector<bool>& bits, unsigned& cursor, const B
static int charToInt(char c)
{
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
}
class BrotherEncoder : public AbstractEncoder
{
public:
BrotherEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.brother())
{}
BrotherEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.brother())
{
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location,
const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
int logicalTrack;
if (physicalSide != 0)
return sectors;
physicalTrack -= _config.bias();
switch (_config.format())
{
case BROTHER120:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return sectors;
logicalTrack = physicalTrack/2;
break;
if (location.head != 0)
return sectors;
case BROTHER240:
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
return sectors;
logicalTrack = physicalTrack;
break;
}
for (int sectorId=0; sectorId<BROTHER_SECTORS_PER_TRACK; sectorId++)
switch (_config.format())
{
const auto& sector = image.get(logicalTrack, 0, sectorId);
case BROTHER120:
if (location.logicalTrack >= BROTHER_TRACKS_PER_120KB_DISK)
return sectors;
break;
case BROTHER240:
if (location.logicalTrack >= BROTHER_TRACKS_PER_240KB_DISK)
return sectors;
break;
}
const std::string& skew = _config.sector_skew();
for (int sectorCount = 0; sectorCount < BROTHER_SECTORS_PER_TRACK;
sectorCount++)
{
int sectorId = charToInt(skew.at(sectorCount));
const auto& sector = image.get(location.logicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
int logicalTrack;
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
physicalTrack -= _config.bias();
switch (_config.format())
{
case BROTHER120:
if ((physicalTrack < 0) || (physicalTrack >= (BROTHER_TRACKS_PER_120KB_DISK*2))
|| (physicalTrack & 1))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack/2;
break;
std::unique_ptr<Fluxmap> encode(
const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
case BROTHER240:
if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_240KB_DISK))
return std::unique_ptr<Fluxmap>();
logicalTrack = physicalTrack;
break;
}
int sectorCount = 0;
for (const auto& sectorData : sectors)
{
double headerMs = _config.post_index_gap_ms() +
sectorCount * _config.sector_spacing_ms();
unsigned headerCursor = headerMs * 1e3 / _config.clock_rate_us();
double dataMs = headerMs + _config.post_header_spacing_ms();
unsigned dataCursor = dataMs * 1e3 / _config.clock_rate_us();
int bitsPerRevolution = 200000.0 / _config.clock_rate_us();
const std::string& skew = _config.sector_skew();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits, cursor, headerCursor, {true, false});
write_sector_header(
bits, cursor, sectorData->logicalTrack, sectorData->logicalSector);
fillBitmapTo(bits, cursor, dataCursor, {true, false});
write_sector_data(bits, cursor, sectorData->data);
for (int sectorCount=0; sectorCount<BROTHER_SECTORS_PER_TRACK; sectorCount++)
{
int sectorId = charToInt(skew.at(sectorCount));
double headerMs = _config.post_index_gap_ms() + sectorCount*_config.sector_spacing_ms();
unsigned headerCursor = headerMs*1e3 / _config.clock_rate_us();
double dataMs = headerMs + _config.post_header_spacing_ms();
unsigned dataCursor = dataMs*1e3 / _config.clock_rate_us();
sectorCount++;
}
const auto& sectorData = image.get(logicalTrack, 0, sectorId);
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), {true, false});
fillBitmapTo(bits, cursor, headerCursor, { true, false });
write_sector_header(bits, cursor, logicalTrack, sectorId);
fillBitmapTo(bits, cursor, dataCursor, { true, false });
write_sector_data(bits, cursor, sectorData->data);
}
if (cursor >= bits.size())
Error() << "track data overrun";
fillBitmapTo(bits, cursor, bits.size(), { true, false });
// The pre-index gap is not normally reported.
// std::cerr << "pre-index gap " << 200.0 - (double)cursor*clockRateUs/1e3 << std::endl;
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, _config.clock_rate_us()*1e3);
return fluxmap;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, _config.clock_rate_us() * 1e3);
return fluxmap;
}
private:
const BrotherEncoderProto& _config;
const BrotherEncoderProto& _config;
};
std::unique_ptr<AbstractEncoder> createBrotherEncoder(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> createBrotherEncoder(
const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config));
return std::unique_ptr<AbstractEncoder>(new BrotherEncoder(config));
}

38
arch/c64/c64.cc Normal file
View file

@ -0,0 +1,38 @@
#include "globals.h"
#include "c64.h"
/*
* Track Sectors/track # Sectors Storage in Bytes Clock rate
* ----- ------------- --------- ---------------- ----------
* 1-17 21 357 7820 3.25
* 18-24 19 133 7170 3.5
* 25-30 18 108 6300 3.75
* 31-40(*) 17 85 6020 4
* ---
* 683 (for a 35 track image)
*
* The clock rate is normalised for a 200ms drive.
*/
unsigned sectorsForC64Track(unsigned track)
{
if (track < 17)
return 21;
if (track < 24)
return 19;
if (track < 30)
return 18;
return 17;
}
nanoseconds_t clockPeriodForC64Track(unsigned track)
{
constexpr double BYTE_SIZE = 8.0;
if (track < 17)
return 26.0 / BYTE_SIZE;
if (track < 24)
return 28.0 / BYTE_SIZE;
if (track < 30)
return 30.0 / BYTE_SIZE;
return 32.0 / BYTE_SIZE;
}

View file

@ -30,4 +30,7 @@
extern std::unique_ptr<AbstractDecoder> createCommodore64Decoder(const DecoderProto& config);
extern std::unique_ptr<AbstractEncoder> createCommodore64Encoder(const EncoderProto& config);
extern unsigned sectorsForC64Track(unsigned track);
extern nanoseconds_t clockPeriodForC64Track(unsigned track);
#endif

View file

@ -7,7 +7,5 @@ message Commodore64DecoderProto {}
message Commodore64EncoderProto {
optional double post_index_gap_us = 1 [default=0.0,
(help) = "post-index gap before first sector header."];
optional double clock_compensation_factor = 2 [default=1.0,
(help) = "scale the output clock by this much."];
}

View file

@ -13,16 +13,18 @@
const FluxPattern SECTOR_RECORD_PATTERN(20, C64_SECTOR_RECORD);
const FluxPattern DATA_RECORD_PATTERN(20, C64_DATA_RECORD);
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
const FluxMatchers ANY_RECORD_PATTERN(
{&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN});
static int decode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
#define GCR_ENTRY(gcr, data) \
case gcr: return data;
#include "data_gcr.h"
#undef GCR_ENTRY
#define GCR_ENTRY(gcr, data) \
case gcr: \
return data;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
@ -37,11 +39,11 @@ static Bytes decode(const std::vector<bool>& bits)
while (ii != bits.end())
{
uint8_t inputfifo = 0;
for (size_t i=0; i<5; i++)
for (size_t i = 0; i < 5; i++)
{
if (ii == bits.end())
break;
inputfifo = (inputfifo<<1) | *ii++;
inputfifo = (inputfifo << 1) | *ii++;
}
bitw.push(decode_data_gcr(inputfifo), 4);
@ -54,53 +56,57 @@ static Bytes decode(const std::vector<bool>& bits)
class Commodore64Decoder : public AbstractDecoder
{
public:
Commodore64Decoder(const DecoderProto& config):
AbstractDecoder(config)
{}
Commodore64Decoder(const DecoderProto& config): AbstractDecoder(config) {}
RecordType advanceToNextRecord()
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
}
nanoseconds_t advanceToNextRecord() override
{
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
{
readRawBits(20);
void decodeSectorRecord() override
{
if (readRaw20() != C64_SECTOR_RECORD)
return;
const auto& bits = readRawBits(5*10);
const auto& bytes = decode(bits).slice(0, 5);
const auto& bits = readRawBits(5 * 10);
const auto& bytes = decode(bits).slice(0, 5);
uint8_t checksum = bytes[0];
_sector->logicalSector = bytes[1];
_sector->logicalSide = 0;
_sector->logicalTrack = bytes[2] - 1;
if (checksum == xorBytes(bytes.slice(1, 4)))
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
uint8_t checksum = bytes[0];
_sector->logicalSector = bytes[1];
_sector->logicalSide = 0;
_sector->logicalTrack = bytes[2] - 1;
if (checksum == xorBytes(bytes.slice(1, 4)))
_sector->status =
Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord()
{
readRawBits(20);
void decodeDataRecord() override
{
if (readRaw20() != C64_DATA_RECORD)
return;
const auto& bits = readRawBits(259*10);
const auto& bytes = decode(bits).slice(0, 259);
const auto& bits = readRawBits(259 * 10);
const auto& bytes = decode(bits).slice(0, 259);
_sector->data = bytes.slice(0, C64_SECTOR_LENGTH);
uint8_t gotChecksum = xorBytes(_sector->data);
uint8_t wantChecksum = bytes[256];
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
_sector->data = bytes.slice(0, C64_SECTOR_LENGTH);
uint8_t gotChecksum = xorBytes(_sector->data);
uint8_t wantChecksum = bytes[256];
_sector->status =
(wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(const Location& location) const override
{
unsigned count = sectorsForC64Track(location.logicalTrack);
std::set<unsigned> sectors;
for (int sectorId = 0; sectorId < count; sectorId++)
sectors.insert(sectorId);
return sectors;
}
};
std::unique_ptr<AbstractDecoder> createCommodore64Decoder(const DecoderProto& config)
std::unique_ptr<AbstractDecoder> createCommodore64Decoder(
const DecoderProto& config)
{
return std::unique_ptr<AbstractDecoder>(new Commodore64Decoder(config));
return std::unique_ptr<AbstractDecoder>(new Commodore64Decoder(config));
}

View file

@ -4,8 +4,9 @@
#include "c64.h"
#include "crc.h"
#include "sector.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "fmt/format.h"
#include "arch/c64/c64.pb.h"
#include "lib/encoders/encoders.pb.h"
@ -14,46 +15,6 @@
static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{
/*
* Track # Sectors/Track Speed Zone bits/rotation
* 1 17 21 3 61,538.4
* 18 24 19 2 57,142.8
* 25 30 18 1 53,333.4
* 31 35 17 0 50,000.0
*/
if (track < 17)
return 200000.0/61538.4;
if (track < 24)
return 200000.0/57142.8;
if (track < 30)
return 200000.0/53333.4;
return 200000.0/50000.0;
}
static unsigned sectorsForTrack(unsigned track)
{
/*
* Track Sectors/track # Sectors Storage in Bytes
* ----- ------------- --------- ----------------
* 1-17 21 357 7820
* 18-24 19 133 7170
* 25-30 18 108 6300
* 31-40(*) 17 85 6020
* ---
* 683 (for a 35 track image)
*/
if (track < 17)
return 21;
if (track < 24)
return 19;
if (track < 30)
return 18;
return 17;
}
static int encode_data_gcr(uint8_t data)
{
switch (data)
@ -211,17 +172,16 @@ public:
{}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
std::vector<std::shared_ptr<const Sector>> collectSectors(const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> sectors;
if (physicalSide == 0)
if (location.head == 0)
{
int logicalTrack = physicalTrack / 2;
unsigned numSectors = sectorsForTrack(logicalTrack);
unsigned numSectors = sectorsForC64Track(location.logicalTrack);
for (int sectorId=0; sectorId<numSectors; sectorId++)
{
const auto& sector = image.get(logicalTrack, 0, sectorId);
const auto& sector = image.get(location.logicalTrack, 0, sectorId);
if (sector)
sectors.push_back(sector);
}
@ -230,8 +190,8 @@ public:
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors, const Image& image) override
{
/* The format ID Character # 1 and # 2 are in the .d64 image only present
* in track 18 sector zero which contains the BAM info in byte 162 and 163.
@ -240,10 +200,7 @@ public:
* contains the BAM.
*/
if (physicalSide != 0)
return std::unique_ptr<Fluxmap>();
const auto& sectorData = image.get(C64_BAM_TRACK*2, 0, 0); //Read de BAM to get the DISK ID bytes
const auto& sectorData = image.get(C64_BAM_TRACK, 0, 0); //Read de BAM to get the DISK ID bytes
if (sectorData)
{
ByteReader br(sectorData->data);
@ -254,9 +211,7 @@ public:
else
_formatByte1 = _formatByte2 = 0;
int logicalTrack = physicalTrack / 2;
double clockRateUs = clockRateUsForTrack(logicalTrack) * _config.clock_compensation_factor();
double clockRateUs = clockPeriodForC64Track(location.logicalTrack);
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
@ -273,12 +228,13 @@ public:
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
fluxmap->appendBits(bits,
Mapper::calculatePhysicalClockPeriod(clockRateUs*1e3, 200e6));
return fluxmap;
}
private:
void writeSector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector) const
void writeSector(std::vector<bool>& bits, unsigned& cursor, std::shared_ptr<const Sector> sector) const
{
/* Source: http://www.unusedino.de/ec64/technical/formats/g64.html
* 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR)

View file

@ -58,22 +58,17 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
/* Skip sync bits and ID byte. */
readRawBits(24);
if (readRaw24() != F85_SECTOR_RECORD)
return;
/* Read header. */
@ -89,11 +84,12 @@ public:
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord()
void decodeDataRecord() override
{
/* Skip sync bits ID byte. */
readRawBits(24);
if (readRaw24() != F85_DATA_RECORD)
return;
const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH+3)*10))
.slice(0, F85_SECTOR_LENGTH+3);

View file

@ -104,16 +104,12 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(SECTOR_ID_PATTERN, matcher);
if (matcher == &SECTOR_ID_PATTERN)
return RecordType::SECTOR_RECORD;
return RecordType::UNKNOWN_RECORD;
return seekToPattern(SECTOR_ID_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
auto rawbits = readRawBits(FB100_RECORD_SIZE*16);

View file

@ -98,88 +98,149 @@ public:
_config(config.ibm())
{}
RecordType advanceToNextRecord() override
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
/* If this is the MFM prefix byte, the the decoder is going to expect three
* extra bytes on the front of the header. */
_currentHeaderLength = (matcher == &MFM_PATTERN) ? 3 : 0;
Fluxmap::Position here = tell();
resetFluxDecoder();
if (_currentHeaderLength > 0)
readRawBits(_currentHeaderLength*16);
auto idbits = readRawBits(16);
const Bytes idbytes = decodeFmMfm(idbits);
uint8_t id = idbytes.slice(0, 1)[0];
seek(here);
switch (id)
{
case IBM_IDAM:
return RecordType::SECTOR_RECORD;
case IBM_DAM1:
case IBM_DAM2:
case IBM_TRS80DAM1:
case IBM_TRS80DAM2:
return RecordType::DATA_RECORD;
}
return RecordType::UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord() override
{
unsigned recordSize = _currentHeaderLength + IBM_IDAM_LEN;
auto bits = readRawBits(recordSize*16);
auto bytes = decodeFmMfm(bits).slice(0, recordSize);
/* This is really annoying because the IBM record scheme has a
* variable-sized header _and_ the checksum covers this header too. So
* we have to read and decode a byte at a time until we know where the
* record itself starts, saving the bytes for the checksumming later.
*/
Bytes bytes;
ByteWriter bw(bytes);
auto readByte = [&]() {
auto bits = readRawBits(16);
auto bytes = decodeFmMfm(bits).slice(0, 1);
uint8_t byte = bytes[0];
bw.write_8(byte);
return byte;
};
uint8_t id = readByte();
if (id == 0xa1)
{
readByte();
readByte();
id = readByte();
}
if (id != IBM_IDAM)
return;
ByteReader br(bytes);
br.seek(_currentHeaderLength);
br.read_8(); /* skip ID byte */
br.seek(bw.pos);
auto bits = readRawBits(IBM_IDAM_LEN*16);
bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN);
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, _sector->physicalTrack, _sector->physicalHead);
_sector->logicalTrack = br.read_8();
_sector->logicalSide = br.read_8();
_sector->logicalSector = br.read_8();
_currentSectorSize = 1 << (br.read_8() + 7);
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
uint16_t wantCrc = br.read_be16();
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, _currentHeaderLength + 5));
if (wantCrc == gotCrc)
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
if (_config.swap_sides())
if (trackdata.swap_sides())
_sector->logicalSide ^= 1;
if (_config.ignore_side_byte())
if (trackdata.ignore_side_byte())
_sector->logicalSide = _sector->physicalHead;
if (_config.ignore_track_byte())
_sector->logicalTrack = _sector->physicalCylinder;
if (trackdata.ignore_track_byte())
_sector->logicalTrack = _sector->physicalTrack;
for (int sector : trackdata.ignore_sector())
if (_sector->logicalSector == sector)
{
_sector->status = Sector::MISSING;
break;
}
}
void decodeDataRecord() override
{
unsigned recordLength = _currentHeaderLength + _currentSectorSize + 3;
auto bits = readRawBits(recordLength*16);
auto bytes = decodeFmMfm(bits).slice(0, recordLength);
/* This is the same deal as the sector record. */
Bytes bytes;
ByteWriter bw(bytes);
auto readByte = [&]() {
auto bits = readRawBits(16);
auto bytes = decodeFmMfm(bits).slice(0, 1);
uint8_t byte = bytes[0];
bw.write_8(byte);
return byte;
};
uint8_t id = readByte();
if (id == 0xa1)
{
readByte();
readByte();
id = readByte();
}
if ((id != IBM_DAM1) && (id != IBM_DAM2)
&& (id != IBM_TRS80DAM1) && (id != IBM_TRS80DAM2))
return;
ByteReader br(bytes);
br.seek(_currentHeaderLength);
br.read_8(); /* skip ID byte */
br.seek(bw.pos);
auto bits = readRawBits((_currentSectorSize + 2) * 16);
bw += decodeFmMfm(bits).slice(0, _currentSectorSize+2);
_sector->data = br.read(_currentSectorSize);
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
uint16_t wantCrc = br.read_be16();
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, recordLength-2));
_sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location& location) const override
{
IbmDecoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, location.logicalTrack, location.head);
std::set<unsigned> s;
for (int sectorId : _config.sectors().sector())
s.insert(sectorId);
if (trackdata.has_sectors())
{
for (int sectorId : trackdata.sectors().sector())
s.insert(sectorId);
}
else if (trackdata.has_sector_range())
{
int sectorId = trackdata.sector_range().min_sector();
while (sectorId <= trackdata.sector_range().max_sector())
{
s.insert(sectorId);
sectorId++;
}
}
return s;
}
private:
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, unsigned track, unsigned head) const
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_track() && (f.track() != track))
continue;
if (f.has_head() && (f.head() != head))
continue;
trackdata.MergeFrom(f);
}
}
private:
const IbmDecoderProto& _config;
unsigned _currentSectorSize;

View file

@ -3,8 +3,9 @@
#include "encoders/encoders.h"
#include "ibm.h"
#include "crc.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "arch/ibm/ibm.pb.h"
#include "lib/encoders/encoders.pb.h"
#include "fmt/format.h"
@ -39,9 +40,9 @@
* ^^^^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data.
*
*
* shifted: 10 00 10 01 00 01 00 1
*
*
* It's repeated three times.
*/
#define MFM_RECORD_SEPARATOR 0x4489
@ -59,228 +60,258 @@
static uint8_t decodeUint16(uint16_t raw)
{
Bytes b;
ByteWriter bw(b);
bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0];
Bytes b;
ByteWriter bw(b);
bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0];
}
class IbmEncoder : public AbstractEncoder
{
public:
IbmEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.ibm())
{}
IbmEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.ibm())
{
}
private:
void writeRawBits(uint32_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i=0; i<width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeRawBits(uint32_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_cylinder() && (f.cylinder() != cylinder))
continue;
if (f.has_head() && (f.head() != head))
continue;
void getTrackFormat(IbmEncoderProto::TrackdataProto& trackdata,
unsigned track,
unsigned head)
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_track() && (f.track() != track))
continue;
if (f.has_head() && (f.head() != head))
continue;
trackdata.MergeFrom(f);
}
}
trackdata.MergeFrom(f);
}
}
private:
static std::set<unsigned> getSectorIds(
const IbmEncoderProto::TrackdataProto& trackdata)
{
std::set<unsigned> s;
if (trackdata.has_sectors())
{
for (int sectorId : trackdata.sectors().sector())
s.insert(sectorId);
}
else if (trackdata.has_sector_range())
{
int sectorId = trackdata.sector_range().min_sector();
while (sectorId <= trackdata.sector_range().max_sector())
{
s.insert(sectorId);
sectorId++;
}
}
return s;
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
IbmEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, physicalTrack, physicalSide);
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
IbmEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, location.logicalTrack, location.head);
int logicalSide = physicalSide ^ trackdata.swap_sides();
for (int sectorId : trackdata.sectors().sector())
int logicalSide = location.head ^ trackdata.swap_sides();
for (int sectorId : getSectorIds(trackdata))
{
const auto& sector = image.get(physicalTrack, logicalSide, sectorId);
if (sector)
sectors.push_back(sector);
const auto& sector =
image.get(location.logicalTrack, logicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
IbmEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, physicalTrack, physicalSide);
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
IbmEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, location.logicalTrack, location.head);
auto writeBytes = [&](const Bytes& bytes)
{
if (trackdata.use_fm())
encodeFm(_bits, _cursor, bytes);
else
encodeMfm(_bits, _cursor, bytes, _lastBit);
};
auto writeBytes = [&](const Bytes& bytes)
{
if (trackdata.use_fm())
encodeFm(_bits, _cursor, bytes);
else
encodeMfm(_bits, _cursor, bytes, _lastBit);
};
auto writeFillerBytes = [&](int count, uint8_t byte)
{
Bytes bytes = { byte };
for (int i=0; i<count; i++)
writeBytes(bytes);
};
auto writeFillerRawBytes = [&](int count, uint16_t byte)
{
for (int i = 0; i < count; i++)
writeRawBits(byte, 16);
};
double clockRateUs = 1e3 / trackdata.clock_rate_khz();
if (!trackdata.use_fm())
clockRateUs /= 2.0;
int bitsPerRevolution = (trackdata.track_length_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
auto writeFillerBytes = [&](int count, uint8_t byte)
{
Bytes b{byte};
for (int i = 0; i < count; i++)
writeBytes(b);
};
uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
double clockRateUs = trackdata.target_clock_period_us();
if (!trackdata.use_fm())
clockRateUs /= 2.0;
int bitsPerRevolution =
(trackdata.target_rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
uint8_t sectorSize = 0;
{
int s = trackdata.sector_size() >> 7;
while (s > 1)
{
s >>= 1;
sectorSize += 1;
}
}
uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
uint8_t gapFill = trackdata.use_fm() ? 0x00 : 0x4e;
uint8_t sectorSize = 0;
{
int s = trackdata.sector_size() >> 7;
while (s > 1)
{
s >>= 1;
sectorSize += 1;
}
}
writeFillerBytes(trackdata.gap0(), gapFill);
if (trackdata.emit_iam())
{
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
writeRawBits(MFM_IAM_SEPARATOR, 16);
}
writeRawBits(trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
writeFillerBytes(trackdata.gap1(), gapFill);
}
uint16_t gapFill = trackdata.gap_fill_byte();
int logicalSide = physicalSide ^ trackdata.swap_sides();
bool first = true;
for (int sectorId : trackdata.sectors().sector())
{
if (!first)
writeFillerBytes(trackdata.gap3(), gapFill);
first = false;
writeFillerRawBytes(trackdata.gap0(), gapFill);
if (trackdata.emit_iam())
{
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i = 0; i < 3; i++)
writeRawBits(MFM_IAM_SEPARATOR, 16);
}
writeRawBits(
trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
writeFillerRawBytes(trackdata.gap1(), gapFill);
}
const auto& sectorData = image.get(physicalTrack, logicalSide, sectorId);
if (!sectorData)
continue;
bool first = true;
for (const auto& sectorData : sectors)
{
if (!first)
writeFillerRawBytes(trackdata.gap3(), gapFill);
first = false;
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
{
Bytes header;
ByteWriter bw(header);
{
Bytes header;
ByteWriter bw(header);
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(idamUnencoded);
bw.write_8(sectorData->logicalTrack);
bw.write_8(sectorData->logicalSide);
bw.write_8(sectorData->logicalSector);
bw.write_8(sectorSize);
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i = 0; i < 3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(idamUnencoded);
bw.write_8(sectorData->logicalTrack);
bw.write_8(sectorData->logicalSide);
bw.write_8(sectorData->logicalSector);
bw.write_8(sectorSize);
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
int conventionalHeaderStart = 0;
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
int conventionalHeaderStart = 0;
if (!trackdata.use_fm())
{
for (int i = 0; i < 3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
}
writeRawBits(trackdata.idam_byte(), 16);
conventionalHeaderStart += 1;
}
writeRawBits(trackdata.idam_byte(), 16);
conventionalHeaderStart += 1;
writeBytes(header.slice(conventionalHeaderStart));
}
writeBytes(header.slice(conventionalHeaderStart));
}
writeFillerRawBytes(trackdata.gap2(), gapFill);
writeFillerBytes(trackdata.gap2(), gapFill);
{
Bytes data;
ByteWriter bw(data);
{
Bytes data;
ByteWriter bw(data);
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i = 0; i < 3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(damUnencoded);
writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
}
bw.write_8(damUnencoded);
Bytes truncatedData =
sectorData->data.slice(0, trackdata.sector_size());
bw += truncatedData;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
Bytes truncatedData = sectorData->data.slice(0, trackdata.sector_size());
bw += truncatedData;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
int conventionalHeaderStart = 0;
if (!trackdata.use_fm())
{
for (int i = 0; i < 3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
}
writeRawBits(trackdata.dam_byte(), 16);
conventionalHeaderStart += 1;
int conventionalHeaderStart = 0;
if (!trackdata.use_fm())
{
for (int i=0; i<3; i++)
writeRawBits(MFM_RECORD_SEPARATOR, 16);
conventionalHeaderStart += 3;
writeBytes(data.slice(conventionalHeaderStart));
}
}
}
writeRawBits(trackdata.dam_byte(), 16);
conventionalHeaderStart += 1;
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeFillerRawBytes(1, gapFill);
writeBytes(data.slice(conventionalHeaderStart));
}
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeFillerBytes(1, gapFill);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits, clockRateUs*1e3);
return fluxmap;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits,
Mapper::calculatePhysicalClockPeriod(clockRateUs * 1e3,
trackdata.target_rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const IbmEncoderProto& _config;
std::vector<bool> _bits;
unsigned _cursor;
bool _lastBit;
const IbmEncoderProto& _config;
std::vector<bool> _bits;
unsigned _cursor;
bool _lastBit;
};
std::unique_ptr<AbstractEncoder> createIbmEncoder(const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config));
return std::unique_ptr<AbstractEncoder>(new IbmEncoder(config));
}

View file

@ -19,7 +19,7 @@
struct IbmIdam
{
uint8_t id;
uint8_t cylinder;
uint8_t track;
uint8_t side;
uint8_t sector;
uint8_t sectorSize;

View file

@ -2,32 +2,52 @@ syntax = "proto2";
import "lib/common.proto";
// Next: 7
message IbmDecoderProto {
message SectorsProto {
repeated int32 sector = 1 [(help) = "require these sectors to exist for a good read"];
// Next: 11
message TrackdataProto {
message SectorsProto {
repeated int32 sector = 1 [(help) = "require these sectors to exist for a good read"];
}
message SectorRangeProto {
optional int32 min_sector = 1 [(help) = "require these sectors to exist for a good read"];
optional int32 max_sector = 2 [(help) = "require these sectors to exist for a good read"];
}
optional int32 track = 7 [(help) = "if set, the format applies only to this track"];
optional int32 head = 8 [(help) = "if set, the format applies only to this head"];
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"];
optional bool swap_sides = 4 [default = false, (help) = "put logical side 1 on physical side 0"];
repeated int32 ignore_sector = 10 [(help) = "sectors with these IDs will not be read"];
oneof required_sectors {
SectorsProto sectors = 5 [(help) = "require these sectors to exist for a good read"];
SectorRangeProto sector_range = 9 [(help) = "require these sectors to exist for a good read"];
}
}
optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"];
optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"];
optional bool swap_sides = 4 [default = false, (help) = "put logical side 1 on physical side 0"];
optional SectorsProto sectors = 5 [(help) = "require these sectors to exist for a good read"];
repeated TrackdataProto trackdata = 1;
}
message IbmEncoderProto {
// Next: 18
// Next: 20
message TrackdataProto {
message SectorsProto {
repeated int32 sector = 1 [(help) = "write these sectors (in order) on each track"];
}
message SectorRangeProto {
optional int32 min_sector = 1 [(help) = "write these sectors (in order) on each track"];
optional int32 max_sector = 2 [(help) = "write these sectors (in order) on each track"];
}
optional int32 cylinder = 15 [(help) = "if set, the format applies only to this track"];
optional int32 track = 15 [(help) = "if set, the format applies only to this track"];
optional int32 head = 16 [(help) = "if set, the format applies only to this head"];
optional double track_length_ms = 1 [(help) = "length of track"];
optional int32 sector_size = 2 [default=512, (help) = "number of bytes per sector"];
optional bool emit_iam = 3 [default=true, (help) = "whether to emit an IAM record"];
optional double clock_rate_khz = 5 [(help) = "data clock rate"];
optional double target_clock_period_us = 5 [default=4, (help) = "data clock rate on target disk"];
optional bool use_fm = 6 [default=false, (help) = "whether to use FM encoding rather than MFM"];
optional int32 idam_byte = 7 [default=0x5554, (help) = "16-bit raw bit pattern of IDAM byte"];
optional int32 dam_byte = 8 [default=0x5545, (help) = "16-bit raw bit pattern of DAM byte"];
@ -36,7 +56,13 @@ message IbmEncoderProto {
optional int32 gap2 = 11 [default=22, (help) = "size of gap 3 (the pre-data gap)"];
optional int32 gap3 = 12 [default=80, (help) = "size of gap 4 (the post-data or format gap)"];
optional bool swap_sides = 14 [default=false, (help) = "swap side bytes when writing"];
optional SectorsProto sectors = 17 [(help) = "write these sectors (in order) on each track"];
optional int32 gap_fill_byte = 18 [default=0x9254, (help) = "16-bit raw bit pattern of gap fill byte"];
optional double target_rotational_period_ms = 1 [default=200, (help) = "rotational period of target disk"];
oneof required_sectors {
SectorsProto sectors = 17 [(help) = "require these sectors to exist for a good read"];
SectorRangeProto sector_range = 19 [(help) = "require these sectors to exist for a good read"];
}
}
repeated TrackdataProto trackdata = 1;

View file

@ -129,28 +129,22 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return DATA_RECORD;
return UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
/* Skip ID (as we know it's a MAC_SECTOR_RECORD). */
readRawBits(24);
if (readRaw24() != MAC_SECTOR_RECORD)
return;
/* Read header. */
auto header = toBytes(readRawBits(7*8)).slice(0, 7);
uint8_t encodedTrack = decode_data_gcr(header[0]);
if (encodedTrack != (_sector->physicalCylinder & 0x3f))
if (encodedTrack != (_sector->physicalTrack & 0x3f))
return;
uint8_t encodedSector = decode_data_gcr(header[1]);
@ -161,7 +155,7 @@ public:
if (encodedSector > 11)
return;
_sector->logicalTrack = _sector->physicalCylinder;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = decode_side(encodedSide);
_sector->logicalSector = encodedSector;
uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
@ -169,10 +163,9 @@ public:
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord()
void decodeDataRecord() override
{
auto id = toBytes(readRawBits(24)).reader().read_be24();
if (id != MAC_DATA_RECORD)
if (readRaw24() != MAC_DATA_RECORD)
return;
/* Read data. */
@ -190,16 +183,18 @@ public:
_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12));
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const
std::set<unsigned> requiredSectors(const Location& location) const override
{
unsigned track = location.logicalTrack;
int count;
if (cylinder < 16)
if (track < 16)
count = 12;
else if (cylinder < 32)
else if (track < 32)
count = 11;
else if (cylinder < 48)
else if (track < 48)
count = 10;
else if (cylinder < 64)
else if (track < 64)
count = 9;
else
count = 8;

View file

@ -3,8 +3,9 @@
#include "encoders/encoders.h"
#include "macintosh.h"
#include "crc.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "fmt/format.h"
#include "lib/encoders/encoders.pb.h"
#include "arch/macintosh/macintosh.pb.h"
@ -14,44 +15,46 @@ static bool lastBit;
static double clockRateUsForTrack(unsigned track)
{
if (track < 16)
return 2.623;
if (track < 32)
return 2.861;
if (track < 48)
return 3.148;
if (track < 64)
return 3.497;
return 3.934;
if (track < 16)
return 2.623;
if (track < 32)
return 2.861;
if (track < 48)
return 3.148;
if (track < 64)
return 3.497;
return 3.934;
}
static unsigned sectorsForTrack(unsigned track)
{
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
if (track < 16)
return 12;
if (track < 32)
return 11;
if (track < 48)
return 10;
if (track < 64)
return 9;
return 8;
}
static int encode_data_gcr(uint8_t gcr)
{
switch (gcr)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
#define GCR_ENTRY(gcr, data) \
case data: \
return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
/* This is extremely inspired by the MESS implementation, written by Nathan Woods
* and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
/* This is extremely inspired by the MESS implementation, written by Nathan
* Woods and R. Belmont:
* https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp
*/
static Bytes encode_crazy_data(const Bytes& input)
{
@ -59,7 +62,7 @@ static Bytes encode_crazy_data(const Bytes& input)
ByteWriter bw(output);
ByteReader br(input);
uint8_t w1, w2, w3, w4;
uint8_t w1, w2, w3, w4;
static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3;
@ -67,92 +70,94 @@ static Bytes encode_crazy_data(const Bytes& input)
uint8_t b2[LOOKUP_LEN + 1];
uint8_t b3[LOOKUP_LEN + 1];
uint32_t c1 = 0;
uint32_t c2 = 0;
uint32_t c3 = 0;
for (int j=0;; j++)
{
c1 = (c1 & 0xff) << 1;
if (c1 & 0x0100)
c1++;
uint32_t c1 = 0;
uint32_t c2 = 0;
uint32_t c3 = 0;
for (int j = 0;; j++)
{
c1 = (c1 & 0xff) << 1;
if (c1 & 0x0100)
c1++;
uint8_t val = br.read_8();
c3 += val;
if (c1 & 0x0100)
{
c3++;
c1 &= 0xff;
}
b1[j] = (val ^ c1) & 0xff;
uint8_t val = br.read_8();
c3 += val;
if (c1 & 0x0100)
{
c3++;
c1 &= 0xff;
}
b1[j] = (val ^ c1) & 0xff;
val = br.read_8();
c2 += val;
if (c3 > 0xff)
{
c2++;
c3 &= 0xff;
}
b2[j] = (val ^ c3) & 0xff;
val = br.read_8();
c2 += val;
if (c3 > 0xff)
{
c2++;
c3 &= 0xff;
}
b2[j] = (val ^ c3) & 0xff;
if (br.pos == 524)
break;
if (br.pos == 524)
break;
val = br.read_8();
c1 += val;
if (c2 > 0xff)
{
c1++;
c2 &= 0xff;
}
b3[j] = (val ^ c2) & 0xff;
}
uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2);
b3[LOOKUP_LEN] = 0;
val = br.read_8();
c1 += val;
if (c2 > 0xff)
{
c1++;
c2 &= 0xff;
}
b3[j] = (val ^ c2) & 0xff;
}
uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2);
b3[LOOKUP_LEN] = 0;
for (int i = 0; i <= LOOKUP_LEN; i++)
{
w1 = b1[i] & 0x3f;
w2 = b2[i] & 0x3f;
w3 = b3[i] & 0x3f;
w4 = ((b1[i] & 0xc0) >> 2);
w4 |= ((b2[i] & 0xc0) >> 4);
w4 |= ((b3[i] & 0xc0) >> 6);
for (int i = 0; i <= LOOKUP_LEN; i++)
{
w1 = b1[i] & 0x3f;
w2 = b2[i] & 0x3f;
w3 = b3[i] & 0x3f;
w4 = ((b1[i] & 0xc0) >> 2);
w4 |= ((b2[i] & 0xc0) >> 4);
w4 |= ((b3[i] & 0xc0) >> 6);
bw.write_8(w4);
bw.write_8(w1);
bw.write_8(w2);
bw.write_8(w4);
bw.write_8(w1);
bw.write_8(w2);
if (i != LOOKUP_LEN)
bw.write_8(w3);
}
if (i != LOOKUP_LEN)
bw.write_8(w3);
}
bw.write_8(c4 & 0x3f);
bw.write_8(c3 & 0x3f);
bw.write_8(c2 & 0x3f);
bw.write_8(c1 & 0x3f);
bw.write_8(c4 & 0x3f);
bw.write_8(c3 & 0x3f);
bw.write_8(c2 & 0x3f);
bw.write_8(c1 & 0x3f);
return output;
return output;
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{
for (bool bit : src)
{
if (cursor < bits.size())
bits[cursor++] = bit;
}
for (bool bit : src)
{
if (cursor < bits.size())
bits[cursor++] = bit;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{
cursor += width;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
cursor += width;
for (int i = 0; i < width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}
static uint8_t encode_side(uint8_t track, uint8_t side)
@ -161,103 +166,116 @@ static uint8_t encode_side(uint8_t track, uint8_t side)
* bit 5) and also whether we're above track 0x3f (in bit 0).
*/
return (side ? 0x20 : 0x00) | ((track>0x3f) ? 0x01 : 0x00);
return (side ? 0x20 : 0x00) | ((track > 0x3f) ? 0x01 : 0x00);
}
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{
if ((sector->data.size() != 512) && (sector->data.size() != 524))
Error() << "unsupported sector size --- you must pick 512 or 524";
if ((sector->data.size() != 512) && (sector->data.size() != 524))
Error() << "unsupported sector size --- you must pick 512 or 524";
write_bits(bits, cursor, 0xff, 1*8); /* pad byte */
for (int i=0; i<7; i++)
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
write_bits(bits, cursor, MAC_SECTOR_RECORD, 3*8);
write_bits(bits, cursor, 0xff, 1 * 8); /* pad byte */
for (int i = 0; i < 7; i++)
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 encodedSector = sector->logicalSector;
uint8_t encodedSide = encode_side(sector->logicalTrack, sector->logicalSide);
uint8_t formatByte = MAC_FORMAT_BYTE;
uint8_t headerChecksum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
uint8_t encodedSector = sector->logicalSector;
uint8_t encodedSide =
encode_side(sector->logicalTrack, sector->logicalSide);
uint8_t formatByte = MAC_FORMAT_BYTE;
uint8_t headerChecksum =
(encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f;
write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedSector), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedSide), 1*8);
write_bits(bits, cursor, encode_data_gcr(formatByte), 1*8);
write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1*8);
write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1 * 8);
write_bits(bits, cursor, encode_data_gcr(encodedSector), 1 * 8);
write_bits(bits, cursor, encode_data_gcr(encodedSide), 1 * 8);
write_bits(bits, cursor, encode_data_gcr(formatByte), 1 * 8);
write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1 * 8);
write_bits(bits, cursor, 0xdeaaff, 3*8);
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6*8); /* sync */
write_bits(bits, cursor, MAC_DATA_RECORD, 3*8);
write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1*8);
write_bits(bits, cursor, 0xdeaaff, 3 * 8);
write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */
write_bits(bits, cursor, MAC_DATA_RECORD, 3 * 8);
write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1 * 8);
Bytes wireData;
wireData.writer().append(sector->data.slice(512, 12)).append(sector->data.slice(0, 512));
for (uint8_t b : encode_crazy_data(wireData))
write_bits(bits, cursor, encode_data_gcr(b), 1*8);
Bytes wireData;
wireData.writer()
.append(sector->data.slice(512, 12))
.append(sector->data.slice(0, 512));
for (uint8_t b : encode_crazy_data(wireData))
write_bits(bits, cursor, encode_data_gcr(b), 1 * 8);
write_bits(bits, cursor, 0xdeaaff, 3*8);
write_bits(bits, cursor, 0xdeaaff, 3 * 8);
}
class MacintoshEncoder : public AbstractEncoder
{
public:
MacintoshEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.macintosh())
{}
MacintoshEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.macintosh())
{
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
if ((physicalTrack >= 0) && (physicalTrack < MAC_TRACKS_PER_DISK))
if ((location.logicalTrack >= 0) &&
(location.logicalTrack < MAC_TRACKS_PER_DISK))
{
unsigned numSectors = sectorsForTrack(physicalTrack);
for (int sectorId=0; sectorId<numSectors; sectorId++)
unsigned numSectors = sectorsForTrack(location.logicalTrack);
for (int sectorId = 0; sectorId < numSectors; sectorId++)
{
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
const auto& sector =
image.get(location.logicalTrack, location.head, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
if ((physicalTrack < 0) || (physicalTrack >= MAC_TRACKS_PER_DISK))
return std::unique_ptr<Fluxmap>();
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
double clockRateUs = clockRateUsForTrack(location.logicalTrack);
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
double clockRateUs = clockRateUsForTrack(physicalTrack) * _config.clock_compensation_factor();
int bitsPerRevolution = 200000.0 / clockRateUs;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
fillBitmapTo(bits,
cursor,
_config.post_index_gap_us() / clockRateUs,
{true, false});
lastBit = false;
fillBitmapTo(bits, cursor, _config.post_index_gap_us() / clockRateUs, { true, false });
lastBit = false;
for (const auto& sector : sectors)
write_sector(bits, cursor, sector);
for (const auto& sector : sectors)
write_sector(bits, cursor, sector);
if (cursor >= bits.size())
Error() << fmt::format(
"track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), {true, false});
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
return fluxmap;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
Mapper::calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6));
return fluxmap;
}
private:
const MacintoshEncoderProto& _config;
const MacintoshEncoderProto& _config;
};
std::unique_ptr<AbstractEncoder> createMacintoshEncoder(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> createMacintoshEncoder(
const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config));
return std::unique_ptr<AbstractEncoder>(new MacintoshEncoder(config));
}

View file

@ -7,8 +7,5 @@ message MacintoshDecoderProto {}
message MacintoshEncoderProto {
optional double post_index_gap_us = 1 [default = 0.0,
(help) = "post-index gap before first sector header (microseconds)."];
optional double clock_compensation_factor = 2 [default = 1.0,
(help) = "scale the output clock by this much."];
}

View file

@ -6,11 +6,20 @@
#include "micropolis.h"
#include "bytes.h"
#include "fmt/format.h"
#include "lib/decoders/decoders.pb.h"
/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */
static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555);
/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern.
*
* 00 00 00 F F
* 0000 0000 0000 0000 0000 0000 0101 0101 0101 0101
* A A A A A A 5 5 5 5
*/
static const FluxPattern SECTOR_SYNC_PATTERN(64, 0xAAAAAAAAAAAA5555LL);
/* Adds all bytes, with carry. */
/* Pattern to skip past current SYNC. */
static const FluxPattern SECTOR_ADVANCE_PATTERN(64, 0xAAAAAAAAAAAAAAAALL);
/* Standard Micropolis checksum. Adds all bytes, with carry. */
uint8_t micropolisChecksum(const Bytes& bytes) {
ByteReader br(bytes);
uint16_t sum = 0;
@ -24,48 +33,155 @@ uint8_t micropolisChecksum(const Bytes& bytes) {
return sum & 0xFF;
}
/* Vector MZOS does not use the standard Micropolis checksum.
* The checksum is initially 0.
* For each data byte in the 256-byte payload, rotate left,
* carrying bit 7 to bit 0. XOR with the current checksum.
*
* Unlike the Micropolis checksum, this does not cover the 12-byte
* header (track, sector, 10 OS-specific bytes.)
*/
uint8_t mzosChecksum(const Bytes& bytes) {
ByteReader br(bytes);
uint8_t checksum = 0;
uint8_t databyte;
while (!br.eof()) {
databyte = br.read_8();
checksum ^= ((databyte << 1) | (databyte >> 7));
}
return checksum;
}
class MicropolisDecoder : public AbstractDecoder
{
public:
MicropolisDecoder(const DecoderProto& config):
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
AbstractDecoder(config),
_config(config.micropolis())
{
_fmr->seekToIndexMark();
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher);
if (matcher == &SECTOR_SYNC_PATTERN) {
readRawBits(16);
return SECTOR_RECORD;
}
return UNKNOWN_RECORD;
_checksumType = _config.checksum_type();
}
void decodeSectorRecord()
nanoseconds_t advanceToNextRecord() override
{
nanoseconds_t now = tell().ns();
/* For all but the first sector, seek to the next sector pulse.
* The first sector does not contain the sector pulse in the fluxmap.
*/
if (now != 0) {
seekToIndexMark();
now = tell().ns();
}
/* Discard a possible partial sector at the end of the track.
* This partial sector could be mistaken for a conflicted sector, if
* whatever data read happens to match the checksum of 0, which is
* rare, but has been observed on some disks.
*/
if (now > (getFluxmapDuration() - 12.5e6)) {
seekToIndexMark();
return 0;
}
nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN);
auto syncDelta = tell().ns() - now;
/* Due to the weak nature of the Micropolis SYNC patern,
* it's possible to detect a false SYNC during the gap
* between the sector pulse and the write gate. If the SYNC
* is detected less than 100uS after the sector pulse, search
* for another valid SYNC.
*
* Reference: Vector Micropolis Disk Controller Board Technical
* Information Manual, pp. 1-16.
*/
if ((syncDelta > 0) && (syncDelta < 100e3)) {
seekToPattern(SECTOR_ADVANCE_PATTERN);
clock = seekToPattern(SECTOR_SYNC_PATTERN);
}
_sector->headerStartTime = tell().ns();
/* seekToPattern() can skip past the index hole, if this happens
* too close to the end of the Fluxmap, discard the sector.
*/
if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6)) {
return 0;
}
return clock;
}
void decodeSectorRecord() override
{
readRawBits(48);
auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16);
auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE);
ByteReader br(bytes);
br.read_8(); /* sync */
int syncByte = br.read_8(); /* sync */
if (syncByte != 0xFF)
return;
_sector->logicalTrack = br.read_8();
_sector->logicalSide = _sector->physicalHead;
_sector->logicalSector = br.read_8();
if (_sector->logicalSector > 15)
return;
if (_sector->logicalTrack > 77)
if (_sector->logicalTrack > 76)
return;
if (_sector->logicalTrack != _sector->physicalTrack)
return;
br.read(10); /* OS data or padding */
_sector->data = br.read(256);
auto data = br.read(MICROPOLIS_PAYLOAD_SIZE);
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = micropolisChecksum(bytes.slice(1, 2+266));
/* If not specified, automatically determine the checksum type.
* Once the checksum type is determined, it will be used for the
* entire disk.
*/
if (_checksumType == MicropolisDecoderProto::AUTO) {
/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS checksums */
if (wantChecksum == micropolisChecksum(bytes.slice(1, 2+266))) {
_checksumType = MicropolisDecoderProto::MICROPOLIS;
} else if (wantChecksum == mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE))) {
_checksumType = MicropolisDecoderProto::MZOS;
std::cout << "Note: MZOS checksum detected." << std::endl;
}
}
uint8_t gotChecksum;
if (_checksumType == MicropolisDecoderProto::MZOS) {
gotChecksum = mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE));
} else {
gotChecksum = micropolisChecksum(bytes.slice(1, 2+266));
}
br.read(5); /* 4 byte ECC and ECC-present flag */
if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE)
_sector->data = data;
else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
_sector->data = bytes;
else
Error() << "Sector output size may only be 256 or 275";
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(const Location& location) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
return sectors;
}
private:
const MicropolisDecoderProto& _config;
MicropolisDecoderProto_ChecksumType _checksumType; /* -1 = auto, 1 = Micropolis, 2=MZOS */
};
std::unique_ptr<AbstractDecoder> createMicropolisDecoder(const DecoderProto& config)

View file

@ -4,110 +4,129 @@
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "image.h"
#include "mapper.h"
#include "lib/encoders/encoders.pb.h"
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{
if ((sector->data.size() != 256) && (sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
Error() << "unsupported sector size --- you must pick 256 or 275";
if ((sector->data.size() != 256) &&
(sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE))
Error() << "unsupported sector size --- you must pick 256 or 275";
int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35;
auto fullSector = std::make_shared<std::vector<uint8_t>>();
fullSector->reserve(fullSectorSize);
/* sector preamble */
for (int i=0; i<40; i++)
fullSector->push_back(0);
Bytes sectorData;
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
sectorData = sector->data;
else
{
ByteWriter writer(sectorData);
writer.write_8(0xff); /* Sync */
writer.write_8(sector->logicalTrack);
writer.write_8(sector->logicalSector);
for (int i=0; i<10; i++)
writer.write_8(0); /* Padding */
writer += sector->data;
writer.write_8(micropolisChecksum(sectorData.slice(1)));
for (int i=0; i<5; i++)
writer.write_8(0); /* 4 byte ECC and ECC not present flag */
}
for (uint8_t b : sectorData)
fullSector->push_back(b);
/* sector postamble */
for (int i=0; i<40; i++)
fullSector->push_back(0);
/* filler */
for (int i=0; i<35; i++)
fullSector->push_back(0);
int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35;
auto fullSector = std::make_shared<std::vector<uint8_t>>();
fullSector->reserve(fullSectorSize);
/* sector preamble */
for (int i = 0; i < 40; i++)
fullSector->push_back(0);
Bytes sectorData;
if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE)
{
if (sector->data[0] != 0xFF)
Error() << "275 byte sector doesn't start with sync byte 0xFF. "
"Corrupted sector";
uint8_t wantChecksum = sector->data[1 + 2 + 266];
uint8_t gotChecksum =
micropolisChecksum(sector->data.slice(1, 2 + 266));
if (wantChecksum != gotChecksum)
std::cerr << "Warning: checksum incorrect. Sector: "
<< sector->logicalSector << std::endl;
sectorData = sector->data;
}
else
{
ByteWriter writer(sectorData);
writer.write_8(0xff); /* Sync */
writer.write_8(sector->logicalTrack);
writer.write_8(sector->logicalSector);
for (int i = 0; i < 10; i++)
writer.write_8(0); /* Padding */
writer += sector->data;
writer.write_8(micropolisChecksum(sectorData.slice(1)));
for (int i = 0; i < 5; i++)
writer.write_8(0); /* 4 byte ECC and ECC not present flag */
}
for (uint8_t b : sectorData)
fullSector->push_back(b);
/* sector postamble */
for (int i = 0; i < 40; i++)
fullSector->push_back(0);
/* filler */
for (int i = 0; i < 35; i++)
fullSector->push_back(0);
if (fullSector->size() != fullSectorSize)
Error() << "sector mismatched length";
bool lastBit = false;
encodeMfm(bits, cursor, fullSector, lastBit);
/* filler */
for (int i=0; i<5; i++)
{
bits[cursor++] = 1;
bits[cursor++] = 0;
}
if (fullSector->size() != fullSectorSize)
Error() << "sector mismatched length";
bool lastBit = false;
encodeMfm(bits, cursor, fullSector, lastBit);
/* filler */
for (int i = 0; i < 5; i++)
{
bits[cursor++] = 1;
bits[cursor++] = 0;
}
}
class MicropolisEncoder : public AbstractEncoder
{
public:
MicropolisEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.micropolis())
{}
MicropolisEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.micropolis())
{
}
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
if ((physicalTrack >= 0) && (physicalTrack < 77))
{
for (int sectorId = 0; sectorId < 16; sectorId++)
{
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
if ((location.logicalTrack >= 0) && (location.logicalTrack < 77))
{
for (int sectorId = 0; sectorId < 16; sectorId++)
{
const auto& sector =
image.get(location.logicalTrack, location.head, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
int bitsPerRevolution = 100000;
double clockRateUs = 2.00;
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
int bitsPerRevolution =
(_config.rotational_period_ms() * 1e3) / _config.clock_period_us();
if ((physicalTrack < 0) || (physicalTrack >= 77) || sectors.empty())
return std::unique_ptr<Fluxmap>();
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sectorData : sectors)
write_sector(bits, cursor, sectorData);
for (const auto& sectorData : sectors)
write_sector(bits, cursor, sectorData);
if (cursor != bits.size())
Error() << "track data mismatched length";
if (cursor != bits.size())
Error() << "track data mismatched length";
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs * 1e3);
return fluxmap;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
Mapper::calculatePhysicalClockPeriod(
_config.clock_period_us() * 1e3,
_config.rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const MicropolisEncoderProto& _config;
const MicropolisEncoderProto& _config;
};
std::unique_ptr<AbstractEncoder> createMicropolisEncoder(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> createMicropolisEncoder(
const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new MicropolisEncoder(config));
return std::unique_ptr<AbstractEncoder>(new MicropolisEncoder(config));
}

View file

@ -1,7 +1,9 @@
#ifndef MICROPOLIS_H
#define MICROPOLIS_H
#define MICROPOLIS_ENCODED_SECTOR_SIZE (1+2+266+6)
#define MICROPOLIS_PAYLOAD_SIZE (256)
#define MICROPOLIS_HEADER_SIZE (1+2+10)
#define MICROPOLIS_ENCODED_SECTOR_SIZE (MICROPOLIS_HEADER_SIZE + MICROPOLIS_PAYLOAD_SIZE + 6)
class AbstractDecoder;
class AbstractEncoder;

View file

@ -1,5 +1,24 @@
syntax = "proto2";
message MicropolisDecoderProto {}
message MicropolisEncoderProto {}
import "lib/common.proto";
message MicropolisDecoderProto {
enum ChecksumType {
AUTO = 0;
MICROPOLIS = 1;
MZOS = 2;
}
optional int32 sector_output_size = 1 [default = 256,
(help) = "How much of the raw sector should be saved. Must be 256 or 275"];
optional ChecksumType checksum_type = 2 [default = AUTO,
(help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"];
}
message MicropolisEncoderProto {
optional double clock_period_us = 1
[ default = 2.0, (help) = "clock rate on the real device" ];
optional double rotational_period_ms = 2
[ default = 166.0, (help) = "rotational period on the real device" ];
}

View file

@ -16,8 +16,10 @@ const int SECTOR_SIZE = 256;
*/
/* FM beginning of track marker:
* 0 0 f 3 decoded nibbles
* 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1
* 1010 1010 1010 1010 1111 1111 1010 1111
* a a a a f f a f
* a a a a f f a f encoded nibbles
*/
const FluxPattern ID_PATTERN(32, 0xaaaaffaf);
@ -28,61 +30,52 @@ public:
AbstractDecoder(config)
{}
void beginTrack()
void beginTrack() override
{
_currentSector = -1;
_clock = 0;
_clock = _sector->clock = seekToPattern(ID_PATTERN);
_currentSector = 0;
}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
if (_currentSector == -1)
{
/* First sector in the track: look for the sync marker. */
const FluxMatcher* matcher = nullptr;
_sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher);
readRawBits(32); /* skip the ID mark */
_logicalTrack = decodeFmMfm(readRawBits(32)).slice(0, 32).reader().read_be16();
}
else if (_currentSector == 10)
if (_currentSector == 11)
{
/* That was the last sector on the disk. */
return UNKNOWN_RECORD;
return 0;
}
else
{
/* Otherwise we assume the clock from the first sector is still valid.
* The decoder framwork will automatically stop when we hit the end of
* the track. */
_sector->clock = _clock;
}
_currentSector++;
return SECTOR_RECORD;
return _clock;
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
/* Skip the ID pattern and track word, which is only present on the
* first sector. We don't trust the track word because some driver
* don't write it correctly. */
if (_currentSector == 0)
readRawBits(64);
auto bits = readRawBits((SECTOR_SIZE+2)*16);
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2).swab();
auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2);
uint16_t gotChecksum = 0;
ByteReader br(bytes);
for (int i=0; i<(SECTOR_SIZE/2); i++)
gotChecksum += br.read_le16();
uint16_t wantChecksum = br.read_le16();
gotChecksum += br.read_be16();
uint16_t wantChecksum = br.read_be16();
_sector->logicalTrack = _logicalTrack;
_sector->logicalTrack = _sector->physicalTrack;
_sector->logicalSide = _sector->physicalHead;
_sector->logicalSector = _currentSector;
_sector->data = bytes.slice(0, SECTOR_SIZE);
_sector->data = bytes.slice(0, SECTOR_SIZE).swab();
_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
_currentSector++;
}
private:
nanoseconds_t _clock;
int _currentSector;
int _logicalTrack;
};
std::unique_ptr<AbstractDecoder> createMxDecoder(const DecoderProto& config)

View file

@ -21,6 +21,8 @@
#include "lib/decoders/decoders.pb.h"
#include "fmt/format.h"
#define MFM_ID 0xaaaaaaaaaaaa5545LL
#define FM_ID 0xaaaaaaaaaaaaffefLL
/*
* MFM sectors have 32 bytes of 00's followed by two sync characters,
* specified in the North Star MDS manual as 0xFBFB.
@ -33,14 +35,14 @@
* 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101
* A A A A A A 5 5 4 5
*/
static const FluxPattern MFM_PATTERN(64, 0xAAAAAAAAAAAA5545LL);
static const FluxPattern MFM_PATTERN(64, MFM_ID);
/* FM sectors have 16 bytes of 00's followed by 0xFB.
* 00 FB
* 0000 0000 1111 1111 1110 1111
* A A F F E F
*/
static const FluxPattern FM_PATTERN(64, 0xAAAAAAAAAAAAFFEFLL);
static const FluxPattern FM_PATTERN(64, FM_ID);
const FluxMatchers ANY_SECTOR_PATTERN(
{
@ -74,16 +76,16 @@ public:
{}
/* Search for FM or MFM sector record */
RecordType advanceToNextRecord() override
nanoseconds_t advanceToNextRecord() override
{
nanoseconds_t now = _fmr->tell().ns();
nanoseconds_t now = tell().ns();
/* For all but the first sector, seek to the next sector pulse.
* The first sector does not contain the sector pulse in the fluxmap.
*/
if (now != 0) {
_fmr->seekToIndexMark();
now = _fmr->tell().ns();
seekToIndexMark();
now = tell().ns();
}
/* Discard a possible partial sector at the end of the track.
@ -91,23 +93,27 @@ public:
* whatever data read happens to match the checksum of 0, which is
* rare, but has been observed on some disks.
*/
if (now > (_fmr->getDuration() - 21e6)) {
_fmr->seekToIndexMark();
return(UNKNOWN_RECORD);
if (now > (getFluxmapDuration() - 21e6)) {
seekToIndexMark();
return 0;
}
int msSinceIndex = std::round(now / 1e6);
const FluxMatcher* matcher = nullptr;
/* Note that the seekToPattern ignores the sector pulses, so if
* a sector is not found for some reason, the seek will advance
* past one or more sector pulses. For this reason, calculate
* _hardSectorId after the sector header is found.
*/
_sector->clock = _fmr->seekToPattern(ANY_SECTOR_PATTERN, matcher);
nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN);
_sector->headerStartTime = tell().ns();
int sectorFoundTimeRaw = std::round((_fmr->tell().ns()) / 1e6);
/* Discard a possible partial sector. */
if (_sector->headerStartTime > (getFluxmapDuration() - 21e6)) {
return 0;
}
int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6);
int sectorFoundTime;
/* Round time to the nearest 20ms */
@ -121,30 +127,15 @@ public:
/* Calculate the sector ID based on time since the index */
_hardSectorId = (sectorFoundTime / 20) % 10;
// std::cout << fmt::format(
// "Sector ID {}: hole at {}ms, sector start at {}ms",
// _hardSectorId, msSinceIndex, sectorFoundTimeRaw) << std::endl;
if (matcher == &MFM_PATTERN) {
_sectorType = SECTOR_TYPE_MFM;
readRawBits(48);
return SECTOR_RECORD;
}
if (matcher == &FM_PATTERN) {
_sectorType = SECTOR_TYPE_FM;
readRawBits(48);
return SECTOR_RECORD;
}
return UNKNOWN_RECORD;
return clock;
}
void decodeSectorRecord() override
{
uint64_t id = toBytes(readRawBits(64)).reader().read_be64();
unsigned recordSize, payloadSize, headerSize;
if (_sectorType == SECTOR_TYPE_MFM) {
if (id == MFM_ID) {
recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD;
payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD;
headerSize = NORTHSTAR_HEADER_SIZE_DD;
@ -158,26 +149,22 @@ public:
auto rawbits = readRawBits(recordSize * 16);
auto bytes = decodeFmMfm(rawbits).slice(0, recordSize);
ByteReader br(bytes);
uint8_t sync_char;
_sector->logicalSide = _sector->physicalHead;
_sector->logicalSector = _hardSectorId;
_sector->logicalTrack = _sector->physicalCylinder;
_sector->logicalTrack = _sector->physicalTrack;
sync_char = br.read_8(); /* Sync char: 0xFB */
if (_sectorType == SECTOR_TYPE_MFM) {
sync_char = br.read_8();/* MFM second Sync char, usually 0xFB */
if (headerSize == NORTHSTAR_HEADER_SIZE_DD) {
br.read_8(); /* MFM second Sync char, usually 0xFB */
}
_sector->data = br.read(payloadSize);
uint8_t wantChecksum = br.read_8();
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize, payloadSize));
uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize - 1, payloadSize));
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;
}
std::set<unsigned> requiredSectors(unsigned cylinder, unsigned head) const override
std::set<unsigned> requiredSectors(const Location&) const override
{
static std::set<unsigned> sectors = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return sectors;
@ -185,7 +172,6 @@ public:
private:
const NorthstarDecoderProto& _config;
uint8_t _sectorType = SECTOR_TYPE_MFM;
uint8_t _hardSectorId;
};

View file

@ -5,6 +5,7 @@
#include "decoders/decoders.h"
#include "encoders/encoders.h"
#include "image.h"
#include "mapper.h"
#include "lib/encoders/encoders.pb.h"
#define GAP_FILL_SIZE_SD 30
@ -12,155 +13,176 @@
#define GAP_FILL_SIZE_DD 62
#define PRE_HEADER_GAP_FILL_SIZE_DD 16
#define GAP1_FILL_BYTE (0x4F)
#define GAP2_FILL_BYTE (0x4F)
#define GAP1_FILL_BYTE (0x4F)
#define GAP2_FILL_BYTE (0x4F)
#define TOTAL_SECTOR_BYTES ()
static void write_sector(std::vector<bool>& bits, unsigned& cursor, const std::shared_ptr<Sector>& sector)
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const std::shared_ptr<const Sector>& sector)
{
int preambleSize = 0;
int encodedSectorSize = 0;
int gapFillSize = 0;
int preHeaderGapFillSize = 0;
int preambleSize = 0;
int encodedSectorSize = 0;
int gapFillSize = 0;
int preHeaderGapFillSize = 0;
bool doubleDensity;
bool doubleDensity;
switch (sector->data.size()) {
case NORTHSTAR_PAYLOAD_SIZE_SD:
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD;
gapFillSize = GAP_FILL_SIZE_SD;
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
doubleDensity = false;
break;
case NORTHSTAR_PAYLOAD_SIZE_DD:
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD;
gapFillSize = GAP_FILL_SIZE_DD;
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
doubleDensity = true;
break;
default:
Error() << "unsupported sector size --- you must pick 256 or 512";
break;
}
switch (sector->data.size())
{
case NORTHSTAR_PAYLOAD_SIZE_SD:
preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD;
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD +
NORTHSTAR_ENCODED_SECTOR_SIZE_SD +
GAP_FILL_SIZE_SD;
gapFillSize = GAP_FILL_SIZE_SD;
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD;
doubleDensity = false;
break;
case NORTHSTAR_PAYLOAD_SIZE_DD:
preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD;
encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD +
NORTHSTAR_ENCODED_SECTOR_SIZE_DD +
GAP_FILL_SIZE_DD;
gapFillSize = GAP_FILL_SIZE_DD;
preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD;
doubleDensity = true;
break;
default:
Error() << "unsupported sector size --- you must pick 256 or 512";
break;
}
int fullSectorSize = preambleSize + encodedSectorSize;
auto fullSector = std::make_shared<std::vector<uint8_t>>();
fullSector->reserve(fullSectorSize);
int fullSectorSize = preambleSize + encodedSectorSize;
auto fullSector = std::make_shared<std::vector<uint8_t>>();
fullSector->reserve(fullSectorSize);
/* sector gap after index pulse */
for (int i = 0; i < preHeaderGapFillSize; i++)
fullSector->push_back(GAP1_FILL_BYTE);
/* sector gap after index pulse */
for (int i = 0; i < preHeaderGapFillSize; i++)
fullSector->push_back(GAP1_FILL_BYTE);
/* sector preamble */
for (int i = 0; i < preambleSize; i++)
fullSector->push_back(0);
/* sector preamble */
for (int i = 0; i < preambleSize; i++)
fullSector->push_back(0);
Bytes sectorData;
if (sector->data.size() == encodedSectorSize)
sectorData = sector->data;
else {
ByteWriter writer(sectorData);
writer.write_8(0xFB); /* sync character */
if (doubleDensity == true) {
writer.write_8(0xFB); /* Double-density has two sync characters */
}
writer += sector->data;
if (doubleDensity == true) {
writer.write_8(northstarChecksum(sectorData.slice(2)));
} else {
writer.write_8(northstarChecksum(sectorData.slice(1)));
}
}
for (uint8_t b : sectorData)
fullSector->push_back(b);
Bytes sectorData;
if (sector->data.size() == encodedSectorSize)
sectorData = sector->data;
else
{
ByteWriter writer(sectorData);
writer.write_8(0xFB); /* sync character */
if (doubleDensity == true)
{
writer.write_8(0xFB); /* Double-density has two sync characters */
}
writer += sector->data;
if (doubleDensity == true)
{
writer.write_8(northstarChecksum(sectorData.slice(2)));
}
else
{
writer.write_8(northstarChecksum(sectorData.slice(1)));
}
}
for (uint8_t b : sectorData)
fullSector->push_back(b);
if (sector->logicalSector != 9) {
/* sector postamble */
for (int i = 0; i < gapFillSize; i++)
fullSector->push_back(GAP2_FILL_BYTE);
if (sector->logicalSector != 9)
{
/* sector postamble */
for (int i = 0; i < gapFillSize; i++)
fullSector->push_back(GAP2_FILL_BYTE);
if (fullSector->size() != fullSectorSize)
Error() << "sector mismatched length (" << sector->data.size() << ") expected: " << fullSector->size() << " got " << fullSectorSize;
} else {
/* sector postamble */
for (int i = 0; i < gapFillSize; i++)
fullSector->push_back(GAP2_FILL_BYTE);
}
if (fullSector->size() != fullSectorSize)
Error() << "sector mismatched length (" << sector->data.size()
<< ") expected: " << fullSector->size() << " got "
<< fullSectorSize;
}
else
{
/* sector postamble */
for (int i = 0; i < gapFillSize; i++)
fullSector->push_back(GAP2_FILL_BYTE);
}
bool lastBit = false;
bool lastBit = false;
if (doubleDensity == true) {
encodeMfm(bits, cursor, fullSector, lastBit);
}
else {
encodeFm(bits, cursor, fullSector);
}
if (doubleDensity == true)
{
encodeMfm(bits, cursor, fullSector, lastBit);
}
else
{
encodeFm(bits, cursor, fullSector);
}
}
class NorthstarEncoder : public AbstractEncoder
{
public:
NorthstarEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.northstar())
{}
NorthstarEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.northstar())
{
}
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
if ((physicalTrack >= 0) && (physicalTrack < 35))
{
for (int sectorId = 0; sectorId < 10; sectorId++)
{
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
}
}
if ((location.logicalTrack >= 0) && (location.logicalTrack < 35))
{
for (int sectorId = 0; sectorId < 10; sectorId++)
{
const auto& sector =
image.get(location.logicalTrack, location.head, sectorId);
if (sector)
sectors.push_back(sector);
}
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
int bitsPerRevolution = 100000;
double clockRateUs = 4.00;
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
int bitsPerRevolution = 100000;
double clockRateUs = _config.clock_period_us();
if ((physicalTrack < 0) || (physicalTrack >= 35) || sectors.empty())
return std::unique_ptr<Fluxmap>();
const auto& sector = *sectors.begin();
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD)
bitsPerRevolution /= 2; // FM
else
clockRateUs /= 2.00;
const auto& sector = *sectors.begin();
if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) {
bitsPerRevolution /= 2; // FM
} else {
clockRateUs /= 2.00;
}
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
std::vector<bool> bits(bitsPerRevolution);
unsigned cursor = 0;
for (const auto& sectorData : sectors)
write_sector(bits, cursor, sectorData);
for (const auto& sectorData : sectors)
write_sector(bits, cursor, sectorData);
if (cursor > bits.size())
Error() << "track data overrun";
if (cursor > bits.size())
Error() << "track data overrun";
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs * 1e3);
return fluxmap;
}
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits,
Mapper::calculatePhysicalClockPeriod(
clockRateUs * 1e3, _config.rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const NorthstarEncoderProto& _config;
const NorthstarEncoderProto& _config;
};
std::unique_ptr<AbstractEncoder> createNorthstarEncoder(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> createNorthstarEncoder(
const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config));
return std::unique_ptr<AbstractEncoder>(new NorthstarEncoder(config));
}

View file

@ -22,9 +22,6 @@
#define NORTHSTAR_ENCODED_SECTOR_SIZE_SD (NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE)
#define NORTHSTAR_ENCODED_SECTOR_SIZE_DD (NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE)
#define SECTOR_TYPE_MFM (0)
#define SECTOR_TYPE_FM (1)
class AbstractDecoder;
class AbstractEncoder;
class EncoderProto;

View file

@ -1,5 +1,13 @@
syntax = "proto2";
message NorthstarDecoderProto {}
message NorthstarEncoderProto {}
import "lib/common.proto";
message NorthstarDecoderProto {}
message NorthstarEncoderProto {
optional double clock_period_us = 1
[ default = 4.0, (help) = "clock rate on the real device (for FM)" ];
optional double rotational_period_ms = 2
[ default = 166.0, (help) = "rotational period on the real device" ];
}

View file

@ -23,17 +23,19 @@
* When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data.
*/
const uint16_t SECTOR_ID = 0x550a;
const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244);
/*
* Data record:
* data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 1 = 0x550c
* data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 1 = 0x550b
* mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45
* special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245
* ^^
* When shifted out of phase, the special 0xa1 byte becomes an illegal
* encoding (you can't do 10 00). So this can't be spoofed by user data.
*/
const uint16_t DATA_ID = 0x550b;
const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245);
const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN });
@ -45,26 +47,22 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return RecordType::SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return RecordType::DATA_RECORD;
return RecordType::UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16);
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE);
ByteReader br(bytes);
if (br.read_be16() != SECTOR_ID)
return;
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3));
br.seek(2);
_sector->logicalSide = br.read_8() >> 3;
_sector->logicalTrack = br.read_8();
br.read_8(); /* number of sectors per track */
@ -76,15 +74,17 @@ public:
_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */
}
void decodeDataRecord()
void decodeDataRecord() override
{
auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16);
auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE);
ByteReader br(bytes);
if (br.read_be16() != DATA_ID)
return;
uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3));
br.seek(2);
_sector->data = br.read(TIDS990_PAYLOAD_SIZE);
uint16_t wantChecksum = br.read_be16();
_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM;

View file

@ -3,166 +3,168 @@
#include "encoders/encoders.h"
#include "tids990.h"
#include "crc.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "arch/tids990/tids990.pb.h"
#include "lib/encoders/encoders.pb.h"
#include <fmt/format.h>
static int charToInt(char c)
{
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
if (isdigit(c))
return c - '0';
return 10 + tolower(c) - 'a';
}
static uint8_t decodeUint16(uint16_t raw)
{
Bytes b;
ByteWriter bw(b);
bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0];
Bytes b;
ByteWriter bw(b);
bw.write_be16(raw);
return decodeFmMfm(b.toBits())[0];
}
class Tids990Encoder : public AbstractEncoder
{
public:
Tids990Encoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.tids990())
{}
Tids990Encoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.tids990())
{
}
private:
void writeRawBits(uint32_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i=0; i<width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeRawBits(uint32_t data, int width)
{
_cursor += width;
_lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = _cursor - i - 1;
if (pos < _bits.size())
_bits[pos] = data & 1;
data >>= 1;
}
}
void writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void writeBytes(const Bytes& bytes)
{
encodeMfm(_bits, _cursor, bytes, _lastBit);
}
void writeBytes(int count, uint8_t byte)
{
Bytes bytes = { byte };
for (int i=0; i<count; i++)
writeBytes(bytes);
}
void writeBytes(int count, uint8_t byte)
{
Bytes bytes = {byte};
for (int i = 0; i < count; i++)
writeBytes(bytes);
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
for (char sectorChar : _config.sector_skew())
for (char sectorChar : _config.sector_skew())
{
int sectorId = charToInt(sectorChar);
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
int sectorId = charToInt(sectorChar);
const auto& sector =
image.get(location.logicalTrack, location.head, sectorId);
if (sector)
sectors.push_back(sector);
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
{
double clockRateUs = 1e3 / _config.clock_rate_khz() / 2.0;
int bitsPerRevolution = (_config.track_length_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
double clockRateUs = _config.clock_period_us() / 2.0;
int bitsPerRevolution =
(_config.rotational_period_ms() * 1000.0) / clockRateUs;
_bits.resize(bitsPerRevolution);
_cursor = 0;
uint8_t am1Unencoded = decodeUint16(_config.am1_byte());
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
uint8_t am1Unencoded = decodeUint16(_config.am1_byte());
uint8_t am2Unencoded = decodeUint16(_config.am2_byte());
writeBytes(_config.gap1_bytes(), 0x55);
writeBytes(_config.gap1_bytes(), 0x55);
bool first = true;
for (char sectorChar : _config.sector_skew())
{
int sectorId = charToInt(sectorChar);
if (!first)
writeBytes(_config.gap3_bytes(), 0x55);
first = false;
bool first = true;
for (const auto& sectorData : sectors)
{
if (!first)
writeBytes(_config.gap3_bytes(), 0x55);
first = false;
const auto& sectorData = image.get(physicalTrack, physicalSide, sectorId);
if (!sectorData)
Error() << fmt::format("format tried to find sector {} which wasn't in the input file", sectorId);
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
/* Writing the sector and data records are fantastically annoying.
* The CRC is calculated from the *very start* of the record, and
* include the malformed marker bytes. Our encoder doesn't know
* about this, of course, with the result that we have to construct
* the unencoded header, calculate the checksum, and then use the
* same logic to emit the bytes which require special encoding
* before encoding the rest of the header normally. */
{
Bytes header;
ByteWriter bw(header);
{
Bytes header;
ByteWriter bw(header);
writeBytes(12, 0x55);
bw.write_8(am1Unencoded);
bw.write_8(sectorData->logicalSide << 3);
bw.write_8(sectorData->logicalTrack);
bw.write_8(_config.sector_count());
bw.write_8(sectorData->logicalSector);
bw.write_be16(sectorData->data.size());
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
writeBytes(12, 0x55);
bw.write_8(am1Unencoded);
bw.write_8(sectorData->logicalSide << 3);
bw.write_8(sectorData->logicalTrack);
bw.write_8(_config.sector_count());
bw.write_8(sectorData->logicalSector);
bw.write_be16(sectorData->data.size());
uint16_t crc = crc16(CCITT_POLY, header);
bw.write_be16(crc);
writeRawBits(_config.am1_byte(), 16);
writeBytes(header.slice(1));
}
writeRawBits(_config.am1_byte(), 16);
writeBytes(header.slice(1));
}
writeBytes(_config.gap2_bytes(), 0x55);
writeBytes(_config.gap2_bytes(), 0x55);
{
Bytes data;
ByteWriter bw(data);
{
Bytes data;
ByteWriter bw(data);
writeBytes(12, 0x55);
bw.write_8(am2Unencoded);
writeBytes(12, 0x55);
bw.write_8(am2Unencoded);
bw += sectorData->data;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
bw += sectorData->data;
uint16_t crc = crc16(CCITT_POLY, data);
bw.write_be16(crc);
writeRawBits(_config.am2_byte(), 16);
writeBytes(data.slice(1));
}
}
writeRawBits(_config.am2_byte(), 16);
writeBytes(data.slice(1));
}
}
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeBytes(1, 0x55);
if (_cursor >= _bits.size())
Error() << "track data overrun";
while (_cursor < _bits.size())
writeBytes(1, 0x55);
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(_bits, clockRateUs*1e3);
return fluxmap;
}
auto fluxmap = std::make_unique<Fluxmap>();
fluxmap->appendBits(_bits,
Mapper::calculatePhysicalClockPeriod(clockRateUs * 1e3,
_config.rotational_period_ms() * 1e6));
return fluxmap;
}
private:
const Tids990EncoderProto& _config;
std::vector<bool> _bits;
unsigned _cursor;
bool _lastBit;
const Tids990EncoderProto& _config;
std::vector<bool> _bits;
unsigned _cursor;
bool _lastBit;
};
std::unique_ptr<AbstractEncoder> createTids990Encoder(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> createTids990Encoder(
const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new Tids990Encoder(config));
return std::unique_ptr<AbstractEncoder>(new Tids990Encoder(config));
}

View file

@ -3,12 +3,13 @@ syntax = "proto2";
import "lib/common.proto";
message Tids990DecoderProto {}
message Tids990EncoderProto {
optional double track_length_ms = 1 [ default = 166,
optional double rotational_period_ms = 1 [ default = 166,
(help) = "length of a track" ];
optional int32 sector_count = 2 [ default = 26,
(help) = "number of sectors per track" ];
optional double clock_rate_khz = 3 [ default = 500,
optional double clock_period_us = 3 [ default = 2,
(help) = "clock rate of data to write" ];
optional int32 am1_byte = 4 [ default = 0x2244,
(help) = "16-bit RAW bit pattern to use for the AM1 ID byte" ];

View file

@ -59,33 +59,29 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher);
if (matcher == &SECTOR_RECORD_PATTERN)
return SECTOR_RECORD;
if (matcher == &DATA_RECORD_PATTERN)
return DATA_RECORD;
return UNKNOWN_RECORD;
return seekToPattern(ANY_RECORD_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
/* Skip the sync marker bit. */
readRawBits(22);
/* Check the ID. */
if (readRaw32() != VICTOR9K_SECTOR_RECORD)
return;
/* Read header. */
auto bytes = decode(readRawBits(4*10)).slice(0, 4);
auto bytes = decode(readRawBits(3*10)).slice(0, 3);
uint8_t rawTrack = bytes[1];
_sector->logicalSector = bytes[2];
uint8_t gotChecksum = bytes[3];
uint8_t rawTrack = bytes[0];
_sector->logicalSector = bytes[1];
uint8_t gotChecksum = bytes[2];
_sector->logicalTrack = rawTrack & 0x7f;
_sector->logicalSide = rawTrack >> 7;
uint8_t wantChecksum = bytes[1] + bytes[2];
uint8_t wantChecksum = bytes[0] + bytes[1];
if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1))
return;
@ -93,22 +89,19 @@ public:
_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */
}
void decodeDataRecord()
void decodeDataRecord() override
{
/* Skip the sync marker bit. */
readRawBits(22);
/* Check the ID. */
if (readRaw32() != VICTOR9K_DATA_RECORD)
return;
/* Read data. */
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+5)*10))
.slice(0, VICTOR9K_SECTOR_LENGTH+5);
auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+4)*10))
.slice(0, VICTOR9K_SECTOR_LENGTH+4);
ByteReader br(bytes);
/* Check that this is actually a data record. */
if (br.read_8() != 8)
return;
_sector->data = br.read(VICTOR9K_SECTOR_LENGTH);
uint16_t gotChecksum = sumBytes(_sector->data);
uint16_t wantChecksum = br.read_le16();

View file

@ -4,8 +4,9 @@
#include "victor9k.h"
#include "crc.h"
#include "sector.h"
#include "writer.h"
#include "readerwriter.h"
#include "image.h"
#include "mapper.h"
#include "fmt/format.h"
#include "arch/victor9k/victor9k.pb.h"
#include "lib/encoders/encoders.pb.h"
@ -14,96 +15,115 @@
static bool lastBit;
static void write_zero_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
static void write_zero_bits(
std::vector<bool>& bits, unsigned& cursor, unsigned count)
{
while (count--)
{
if (cursor < bits.size())
lastBit = bits[cursor++] = 0;
}
{
if (cursor < bits.size())
lastBit = bits[cursor++] = 0;
}
}
static void write_one_bits(std::vector<bool>& bits, unsigned& cursor, unsigned count)
static void write_one_bits(
std::vector<bool>& bits, unsigned& cursor, unsigned count)
{
while (count--)
{
if (cursor < bits.size())
lastBit = bits[cursor++] = 1;
}
{
if (cursor < bits.size())
lastBit = bits[cursor++] = 1;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src)
{
for (bool bit : src)
{
if (cursor < bits.size())
lastBit = bits[cursor++] = bit;
}
for (bool bit : src)
{
if (cursor < bits.size())
lastBit = bits[cursor++] = bit;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width)
{
cursor += width;
lastBit = data & 1;
for (int i=0; i<width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
cursor += width;
lastBit = data & 1;
for (int i = 0; i < width; i++)
{
unsigned pos = cursor - i - 1;
if (pos < bits.size())
bits[pos] = data & 1;
data >>= 1;
}
}
static void write_bits(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
static void write_bits(
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{
ByteReader br(bytes);
BitReader bitr(br);
ByteReader br(bytes);
BitReader bitr(br);
while (!bitr.eof())
{
if (cursor < bits.size())
bits[cursor++] = bitr.get();
}
while (!bitr.eof())
{
if (cursor < bits.size())
bits[cursor++] = bitr.get();
}
}
static int encode_data_gcr(uint8_t data)
{
switch (data & 0x0f)
{
#define GCR_ENTRY(gcr, data) \
case data: return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
#define GCR_ENTRY(gcr, data) \
case data: \
return gcr;
#include "data_gcr.h"
#undef GCR_ENTRY
}
return -1;
}
static void write_bytes(std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
static void write_byte(std::vector<bool>& bits, unsigned& cursor, uint8_t b)
{
for (uint8_t b : bytes)
{
write_bits(bits, cursor, encode_data_gcr(b>>4), 5);
write_bits(bits, cursor, encode_data_gcr(b), 5);
}
write_bits(bits, cursor, encode_data_gcr(b >> 4), 5);
write_bits(bits, cursor, encode_data_gcr(b), 5);
}
static void write_sector(std::vector<bool>& bits, unsigned& cursor,
const Victor9kEncoderProto::TrackdataProto& trackdata,
const Sector& sector)
static void write_bytes(
std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes)
{
for (uint8_t b : bytes)
write_byte(bits, cursor, b);
}
static void write_gap(std::vector<bool>& bits, unsigned& cursor, int length)
{
for (int i = 0; i < length / 10; i++)
write_byte(bits, cursor, '0');
}
static void write_sector(std::vector<bool>& bits,
unsigned& cursor,
const Victor9kEncoderProto::TrackdataProto& trackdata,
const Sector& sector)
{
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.logicalTrack | (sector.logicalSide << 7);
uint8_t encodedSector = sector.logicalSector;
write_bytes(bits, cursor, Bytes {
encodedTrack,
encodedSector,
(uint8_t)(encodedTrack + encodedSector),
});
write_bytes(bits,
cursor,
Bytes{
encodedTrack,
encodedSector,
(uint8_t)(encodedTrack + encodedSector),
});
write_gap(bits, cursor, trackdata.post_header_gap_bits());
write_zero_bits(bits, cursor, trackdata.post_header_gap_bits());
write_one_bits(bits, cursor, trackdata.pre_data_sync_bits());
write_bits(bits, cursor, VICTOR9K_DATA_RECORD, 10);
@ -112,89 +132,100 @@ static void write_sector(std::vector<bool>& bits, unsigned& cursor,
Bytes checksum(2);
checksum.writer().write_le16(sumBytes(sector.data));
write_bytes(bits, cursor, checksum);
write_zero_bits(bits, cursor, trackdata.post_data_gap_bits());
write_gap(bits, cursor, trackdata.post_data_gap_bits());
}
class Victor9kEncoder : public AbstractEncoder
{
public:
Victor9kEncoder(const EncoderProto& config):
Victor9kEncoder(const EncoderProto& config):
AbstractEncoder(config),
_config(config.victor9k())
{}
_config(config.victor9k())
{
}
private:
void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata,
unsigned track,
unsigned head)
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_min_track() && (track < f.min_track()))
continue;
if (f.has_max_track() && (track > f.max_track()))
continue;
if (f.has_head() && (head != f.head()))
continue;
void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata, unsigned cylinder, unsigned head)
{
trackdata.Clear();
for (const auto& f : _config.trackdata())
{
if (f.has_min_cylinder() && (cylinder < f.min_cylinder()))
continue;
if (f.has_max_cylinder() && (cylinder > f.max_cylinder()))
continue;
if (f.has_head() && (head != f.head()))
continue;
trackdata.MergeFrom(f);
}
}
trackdata.MergeFrom(f);
}
}
public:
std::vector<std::shared_ptr<Sector>> collectSectors(int physicalTrack, int physicalSide, const Image& image) override
{
std::vector<std::shared_ptr<Sector>> sectors;
std::vector<std::shared_ptr<const Sector>> collectSectors(
const Location& location, const Image& image) override
{
std::vector<std::shared_ptr<const Sector>> sectors;
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, physicalTrack, physicalSide);
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, location.logicalTrack, location.head);
for (int i = 0; i < trackdata.sector_range().sector_count(); i++)
{
int sectorId = trackdata.sector_range().start_sector() + i;
const auto& sector = image.get(physicalTrack, physicalSide, sectorId);
if (sector)
sectors.push_back(sector);
const auto& sector =
image.get(location.logicalTrack, location.head, sectorId);
if (sector)
sectors.push_back(sector);
}
return sectors;
}
return sectors;
}
std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide,
const std::vector<std::shared_ptr<Sector>>& sectors, const Image& image) override
std::unique_ptr<Fluxmap> encode(const Location& location,
const std::vector<std::shared_ptr<const Sector>>& sectors,
const Image& image) override
{
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, physicalTrack, physicalSide);
Victor9kEncoderProto::TrackdataProto trackdata;
getTrackFormat(trackdata, location.logicalTrack, location.head);
unsigned bitsPerRevolution = trackdata.original_data_rate_khz() * trackdata.original_period_ms();
unsigned bitsPerRevolution = (trackdata.rotational_period_ms() * 1e3) /
trackdata.clock_period_us();
std::vector<bool> bits(bitsPerRevolution);
double clockRateUs = 166666.0 / bitsPerRevolution;
nanoseconds_t clockPeriod = Mapper::calculatePhysicalClockPeriod(
trackdata.clock_period_us() * 1e3,
trackdata.rotational_period_ms() * 1e6);
unsigned cursor = 0;
fillBitmapTo(bits, cursor, trackdata.post_index_gap_us() / clockRateUs, { true, false });
fillBitmapTo(bits,
cursor,
trackdata.post_index_gap_us() * 1e3 / clockPeriod,
{true, false});
lastBit = false;
for (const auto& sector : sectors)
write_sector(bits, cursor, trackdata, *sector);
if (cursor >= bits.size())
Error() << fmt::format("track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), { true, false });
Error() << fmt::format(
"track data overrun by {} bits", cursor - bits.size());
fillBitmapTo(bits, cursor, bits.size(), {true, false});
std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
fluxmap->appendBits(bits, clockRateUs*1e3);
fluxmap->appendBits(bits, clockPeriod);
return fluxmap;
}
private:
const Victor9kEncoderProto& _config;
const Victor9kEncoderProto& _config;
};
std::unique_ptr<AbstractEncoder> createVictor9kEncoder(const EncoderProto& config)
std::unique_ptr<AbstractEncoder> createVictor9kEncoder(
const EncoderProto& config)
{
return std::unique_ptr<AbstractEncoder>(new Victor9kEncoder(config));
return std::unique_ptr<AbstractEncoder>(new Victor9kEncoder(config));
}
// vim: sw=4 ts=4 et

View file

@ -8,11 +8,13 @@ class DecoderProto;
/* ... 1101 0101 0111
* ^^ ^^^^ ^^^^ ten bit IO byte */
#define VICTOR9K_SECTOR_RECORD 0xfffffd57
#define VICTOR9K_SECTOR_RECORD 0xfffffd57
#define VICTOR9K_HEADER_ID 0x7
/* ... 1101 0100 1001
* ^^ ^^^^ ^^^^ ten bit IO byte */
#define VICTOR9K_DATA_RECORD 0xfffffd49
#define VICTOR9K_DATA_ID 0x8
#define VICTOR9K_SECTOR_LENGTH 512

View file

@ -5,28 +5,43 @@ import "lib/common.proto";
message Victor9kDecoderProto {}
// NEXT: 12
message Victor9kEncoderProto {
message TrackdataProto {
message SectorRangeProto {
optional int32 start_sector = 1 [(help) = "first sector ID on track"];
optional int32 sector_count = 2 [(help) = "number of sectors on track"];
}
message Victor9kEncoderProto
{
message TrackdataProto
{
message SectorRangeProto
{
optional int32 start_sector = 1
[ (help) = "first sector ID on track" ];
optional int32 sector_count = 2
[ (help) = "number of sectors on track" ];
}
optional int32 min_cylinder = 1 [(help) = "minimum cylinder this format applies to"];
optional int32 max_cylinder = 2 [(help) = "maximum cylinder this format applies to"];
optional int32 head = 3 [(help) = "which head this format applies to"];
optional int32 min_track = 1
[ (help) = "minimum track this format applies to" ];
optional int32 max_track = 2
[ (help) = "maximum track this format applies to" ];
optional int32 head = 3
[ (help) = "which head this format applies to" ];
optional double original_period_ms = 4 [(help) = "original rotational period of this cylinder"];
optional double original_data_rate_khz = 5 [(help) = "original data rate of this cylinder"];
optional double post_index_gap_us = 6 [(help) = "size of post-index gap"];
optional int32 pre_header_sync_bits = 10 [(help) = "number of sync bits before the sector header"];
optional int32 pre_data_sync_bits = 8 [(help) = "number of sync bits before the sector data"];
optional int32 post_data_gap_bits = 9 [(help) = "size of gap between data and the next header"];
optional int32 post_header_gap_bits = 11 [(help) = "size of gap between header and the data"];
optional double rotational_period_ms = 4
[ (help) = "original rotational period of this track" ];
optional double clock_period_us = 5
[ (help) = "original data rate of this track" ];
optional double post_index_gap_us = 6
[ (help) = "size of post-index gap" ];
optional int32 pre_header_sync_bits = 10
[ (help) = "number of sync bits before the sector header" ];
optional int32 pre_data_sync_bits = 8
[ (help) = "number of sync bits before the sector data" ];
optional int32 post_data_gap_bits = 9
[ (help) = "size of gap between data and the next header" ];
optional int32 post_header_gap_bits = 11
[ (help) = "size of gap between header and the data" ];
optional SectorRangeProto sector_range = 7 [(help) = "write these sectors on each track"];
}
optional SectorRangeProto sector_range = 7
[ (help) = "write these sectors on each track" ];
}
repeated TrackdataProto trackdata = 1;
repeated TrackdataProto trackdata = 1;
}

View file

@ -20,17 +20,13 @@ public:
AbstractDecoder(config)
{}
RecordType advanceToNextRecord()
nanoseconds_t advanceToNextRecord() override
{
const FluxMatcher* matcher = nullptr;
_fmr->seekToIndexMark();
_sector->clock = _fmr->seekToPattern(SECTOR_START_PATTERN, matcher);
if (matcher == &SECTOR_START_PATTERN)
return SECTOR_RECORD;
return UNKNOWN_RECORD;
seekToIndexMark();
return seekToPattern(SECTOR_START_PATTERN);
}
void decodeSectorRecord()
void decodeSectorRecord() override
{
readRawBits(14);

View file

@ -0,0 +1,5 @@
(
(c-default-style . "BSD")
(c-mode . ((c-basic-offset . 4) (tab-width . 4) (indent-tabs-mode . nil)))
(nil . ((fill-column . 80)))
)

130
dep/libusbp/CMakeLists.txt Normal file
View file

@ -0,0 +1,130 @@
cmake_minimum_required (VERSION 2.8.11)
# Fix behavior of CMAKE_CXX_STANDARD when targeting macOS.
if (POLICY CMP0025)
cmake_policy(SET CMP0025 NEW)
endif ()
# Fix a warning on macOS.
if (POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif ()
# Don't use -rdynamic since it breaks causes musl static linking.
if (POLICY CMP0065)
cmake_policy(SET CMP0065 NEW)
endif ()
project (libusbp)
set (LIBUSBP_VERSION_MAJOR 1)
set (LIBUSBP_VERSION_MINOR 2)
set (LIBUSBP_VERSION_PATCH 0)
# Make 'Release' be the default build type, since the debug builds
# include exported symbols that might cause name conflicts.
if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE "Release" CACHE STRING
"Options are Debug Release RelWithDebInfo MinSizeRel" FORCE)
endif ()
option (BUILD_SHARED_LIBS "Build as shared library" TRUE)
if (NOT BUILD_SHARED_LIBS)
add_definitions (-DLIBUSBP_STATIC)
set (PC_MORE_CFLAGS "-DLIBUSBP_STATIC")
endif ()
set(ENABLE_EXAMPLES FALSE CACHE BOOL
"True if you want to build the examples.")
set(ENABLE_TESTS FALSE CACHE BOOL
"True if you want to build the tests.")
set(LIBUSBP_LOG FALSE CACHE BOOL
"Output log messages to stderr for debugging.")
set(VBOX_LINUX_ON_WINDOWS FALSE CACHE BOOL
"Skip tests known to cause problems on a Linux VirtualBox guest on Windows.")
set(ENABLE_GCOV FALSE CACHE BOOL
"Compile with special options needed for gcov.")
# Our C code uses features from the C99 standard.
macro(use_c99)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
set (CMAKE_C_FLAGS "--std=gnu99 ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_C_STANDARD 99)
endif ()
endmacro(use_c99)
# Our C++ code uses features from the C++11 standard.
macro(use_cxx11)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
# Use --std=gnu++0x instead of --std=gnu++11 in order to support GCC 4.6.
set (CMAKE_CXX_FLAGS "--std=gnu++0x ${CMAKE_C_FLAGS}")
endif ()
else ()
set (CMAKE_CXX_STANDARD 11)
endif ()
endmacro(use_cxx11)
set (LIBUSBP_VERSION ${LIBUSBP_VERSION_MAJOR}.${LIBUSBP_VERSION_MINOR}.${LIBUSBP_VERSION_PATCH})
if (CMAKE_VERSION VERSION_GREATER "2.8.10")
string(TIMESTAMP YEAR "%Y")
endif ()
find_package(PkgConfig)
# Put libraries and executables in the top level of the build directory
# so that the executables can find the libraries and it is easy to run
# everything.
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
# Warn about everything.
set (CMAKE_C_FLAGS "-Wall -Wextra -pedantic ${CMAKE_C_FLAGS}")
set (CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic ${CMAKE_CXX_FLAGS}")
if (ENABLE_GCOV)
set (CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage ${CMAKE_C_FLAGS}")
endif ()
if (WIN32)
# Enable correct behavior for the return value of vsnprintf.
add_definitions (-D__USE_MINGW_ANSI_STDIO=1)
# Enable functions only available in Windows Vista and later,
# such as StringCompareEx.
add_definitions (-D_WIN32_WINNT=0x0600 -DNTDDI_VERSION=0x06000000)
endif ()
# Detect Linux.
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set (LINUX 1)
endif ()
# Install the header files into include/
install(FILES include/libusbp.h include/libusbp.hpp
DESTINATION "include/libusbp-${LIBUSBP_VERSION_MAJOR}")
add_subdirectory (src)
if (ENABLE_TESTS)
add_subdirectory (test)
add_subdirectory (manual_tests)
endif ()
if (ENABLE_EXAMPLES)
add_subdirectory (examples)
endif ()
if (WIN32)
add_subdirectory (install_helper)
endif ()

View file

@ -0,0 +1,74 @@
# Contributing to libusbp
## Development Tools
The following tools are used to help build, test, debug, and document this library:
- USB Test Device A: This is a custom USB device that is used for testing this library. You must have a device like this plugged in if you want to run the full test suite. The `test/firmware/wixel` folder contains firmware that can turn a [Pololu Wixel](https://www.pololu.com/product/1337) into a USB Test Device A, and it should be possible to implement it on other USB-capable boards as well.
- [cmake](http://www.cmake.org)
- [catch](https://github.com/philsquared/Catch)
- [Doxygen](http://www.stack.nl/~dimitri/doxygen/)
- Development environments:
- Windows: [MSYS2](http://msys2.github.io/)
- macOS: [Homebrew](http://brew.sh/)
- Memory leak checkers:
- Windows: Install Dr. Memory and run `drmemory -leaks_only ./run_test.exe`
- Linux: Install Valgrind and run `valgrind ./run_test`
- macOS: Run `MallocStackLogging=1 ./run_test -p`, wait for the tests to finish, press Ctrl+Z, run `leaks run_test`, then finally run `fg` to go back to the stopped test process and end it.
## Underlying API documentation
This section attempts to organize the documentation and information about the underlying APIs used by libusbp, which should be useful to anyone reviewing or editing the code.
### Windows APIs
- [WinUSB](https://msdn.microsoft.com/en-us/library/windows/hardware/ff540196)
- [SetupAPI](https://msdn.microsoft.com/en-us/library/cc185682)
- [PnP Configuration Manager Reference](https://msdn.microsoft.com/en-us/library/windows/hardware/ff549717)
- [Standard USB Identifiers](https://msdn.microsoft.com/en-us/library/windows/hardware/ff553356)
- [What characters or bytes are valid in a USB serial number?](https://msdn.microsoft.com/en-us/library/windows/hardware/dn423379#usbsn)
- [INFO: Windows Rundll and Rundll32 Interface](https://support.microsoft.com/en-us/kb/164787)
- [MSI Custom Action Type 17](https://msdn.microsoft.com/en-us/library/aa368076?f=255&MSPPError=-2147217396)
### Linux APIs
- udev
- [libudev Reference Manual](https://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ch01.html)
- [Kernel documentation of sysfs-bus-usb](https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-bus-usb)
- [sysfs.c](https://github.com/torvalds/linux/blob/master/drivers/usb/core/sysfs.c)
- [libudev and Sysfs Tutorial](http://www.signal11.us/oss/udev/)
- USB device node
- [USB documentation folder in the kernel tree](https://github.com/torvalds/linux/tree/master/Documentation/usb)
- [USB error code documentation](https://github.com/torvalds/linux/blob/master/Documentation/usb/error-codes.txt)
- [usbdevice_fs.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/usbdevice_fs.h)
- [devio.c](https://github.com/torvalds/linux/blob/master/drivers/usb/core/devio.c)
- [Linux USB Drivers presentation](http://free-electrons.com/doc/linux-usb.pdf)
- [USB Drivers chapter from a book](http://lwn.net/images/pdf/LDD3/ch13.pdf)
- [linux_usbfs.c from libusb](https://github.com/libusb/libusb/blob/master/libusb/os/linux_usbfs.c)
- Error numbers
- [errno.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h)
- [errno-base.h](https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h)
### macOS APIs
- [I/O Kit Framework Reference](https://developer.apple.com/library/mac/documentation/Darwin/Reference/IOKit/index.html#//apple_ref/doc/uid/TP30000815)
- [IOKitLib.h Reference](https://developer.apple.com/library/mac/documentation/IOKit/Reference/IOKitLib_header_reference/)
- [Accessing Hardware From Applications](https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/AccessingHardware)
- [USB Device Interface Guide](https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/USBBook/)
- [IOUSBInterfaceClass.cpp](https://github.com/opensource-apple/IOUSBFamily/blob/master/IOUSBLib/Classes/IOUSBInterfaceClass.cpp)
- [IOUSBInterfaceUserClient.cpp](https://github.com/opensource-apple/IOUSBFamily/blob/master/IOUSBUserClient/Classes/IOUSBInterfaceUserClient.cpp)
- [darwin_usb.c from libusb](https://github.com/libusb/libusb/blob/master/libusb/os/darwin_usb.c)
- [Mach messaging interface (mach ports)](http://www.gnu.org/software/hurd/gnumach-doc/Messaging-Interface.html)
## Future development
Here are some things we might want to work on in future versions of the library:
- Serial port support. (Even just listing the serial ports of a USB device would be useful.)
- Human interface device (HID) support.
- Hotplug support: detect when new devices are added and detect when a specific device is removed.
- Stronger guarantees about thread safety. (This might not require code changes.)
- More options for asynchronous transfers.
- Perhaps use asynchronous operations to implement the synchronous ones, like libusb does. This would fix some quirks on various platforms: it would allow us to have timeouts for interrupt endpoints on Mac OS X and it would probably allow long synchronous operations on Linux to be interrupted with Ctrl+C. The main drawback is that it would add complexity.
- Sending data on OUT pipes.
- A device library template to help people who want to make a cross-platform C or C++ library for a USB device based on libusbp.

26
dep/libusbp/Doxyfile Normal file
View file

@ -0,0 +1,26 @@
# Doxygen configuration file for generating documentation.
PROJECT_NAME = "libusbp"
OUTPUT_DIRECTORY = docs
INLINE_INHERITED_MEMB = YES
INPUT = README.md PLATFORM_NOTES.md CONTRIBUTING.md THREADS.md include
USE_MDFILE_AS_MAINPAGE = README.md
RECURSIVE = YES
SOURCE_BROWSER = YES
USE_MATHJAX = YES
GENERATE_LATEX = NO
# TYPEDEF_HIDES_STRUCT = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
PREDEFINED = \
LIBUSBP_API= \
LIBUSBP_WARN_UNUSED= \
_WIN32=1 \
__linux__=1 \
__APPLE__=1
EXCLUDE_SYMBOLS = \
LIBUSBP_API \
LIBUSBP_DLL_EXPORT \
LIBUSBP_DLL_IMPORT \
LIBUSBP_WARN_UNUSED

25
dep/libusbp/LICENSE.txt Normal file
View file

@ -0,0 +1,25 @@
Copyright (c) 2015-2017 Pololu Corporation. For more information, see
http://www.pololu.com/
http://forum.pololu.com/
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,36 @@
# Platform notes
This page documents some notable differences between the platforms that libusbp supports that can affect the library's behavior.
## Permissions
On Linux, a udev rule is typically needed in order to grant permissions for non-root users to directly access USB devices. The simplest way to do this would be to add a file named `usb.rules` in the directory `/etc/udev/rules.d` with the following contents, which grants all users permission to access all USB devices:
SUBSYSTEM=="usb", MODE="0666"
## Multiple handles to the same generic interface
On Linux, you can have multiple simultaneous handles open to the same generic interface of a device. On Windows and macOS, this is not possible, and you will get an error with the code `LIBUSBP_ERROR_ACCESS_DENIED` when trying to open the second handle with `libusbp_generic_handle_open`.
## Timeouts for interrupt IN endpoints
On macOS, you cannot specify a timeout for an interrupt IN endpoint. Doing so will result in the following error when you try to read from the endpoint:
Failed to read from pipe. (iokit/common) invalid argument. Error code 0xe00002c2.
## Serial numbers
On Windows, calling `libusbp_device_get_serial_number` on a device that does not actually have a serial number will retrieve some other type of identifier that contains andpersands (&) and numbers. On other platforms, an error will be returned with the code `LIBUSBP_ERROR_NO_SERIAL_NUMBER`.
## Synchronous operations and Ctrl+C
On Linux, a synchronous (blocking) USB transfer cannot be interrupted by pressing Ctrl+C. Other signals will probably not work either.
## Interface-specific control transfers
Performing control transfers that are directed to a specific interface might not work correctly on all systems, especially if the device is a composite device and the interface you are connected to is not the one addressed in your control transfer. For example, see [this message](https://sourceforge.net/p/libusb/mailman/message/34414447/) from the libusb mailing list.

186
dep/libusbp/README.md Normal file
View file

@ -0,0 +1,186 @@
# libusbp: Pololu USB Library
Version: 1.2.0<br/>
Release date: 2020-11-16<br/>
[www.pololu.com](https://www.pololu.com/)
The **Pololu USB Library** (also known as **libusbp**) is a cross-platform C library for accessing USB devices.
## Features
- Can retrieve the vendor ID, product ID, revision, and serial number for each connected USB device.
- Can perform I/O on generic (vendor-defined) USB interfaces:
- Synchronous control transfers.
- Synchronous and asynchronous bulk/interrupt transfers on IN endpoints.
- Synchronous bulk/interrupt transfers on OUT endpoints.
- Can retrieve the names of virtual serial ports provided by a specified USB device (e.g. "COM5").
- Provides detailed error information to the caller.
- Each error includes one or more English sentences describing the error, including error codes from underlying APIs.
- Some errors have libusbp-defined error codes that can be used to programmatically decide how to handle the error.
- Provides an object-oriented C++ wrapper (using features of C++11).
- Provides access to underlying identifiers, handles, and file descriptors.
## Supported platforms
The library runs on:
* Microsoft Windows (Windows Vista and later)
* Linux
* macOS (10.11 and later)
## Supported USB devices and interfaces
The library only supports certain types of USB devices.
On Windows, any generic interface that you want to access with this library must use WinUSB as its driver, so you will need to provide a driver package or use some other mechanism to make sure WinUSB is installed. The generic interface must have a registry key named "DeviceInterfaceGUIDs" which is a REG_MULTI_SZ, and the first string in the key must be a valid GUID. The "DeviceInterfaceGUID" key is not supported.
Both composite and non-composite devices are supported.
There is no support for switching device configurations or switching between alternate settings of an interface.
There is no support for accessing a generic interface that is included in an interface association and is not the first interface in the association.
## Platform differences
This library is a relatively simple wrapper around low-level USB APIs provided by each supported platform. Because these APIs have different capabilities and behavior, you should not assume your program will work on a platform on which it has not been tested. Some notable differences are documented in `PLATFORM_NOTES.md`.
## Comparison to libusb
This library has a lot in common with [libusb 1.0](http://libusb.info/), which has been around for longer and has many more features. However, libusbp does have some useful features that libusb lacks, such as listing the serial number of a USB device without performing I/O or getting information about a USB device's virtual serial ports.
## Building from source on Windows with MSYS2
The recommended way to build this library on Windows is to use [MSYS2](http://msys2.github.io/).
After installing MSYS2, launch it by selecting "MSYS2 MinGW 32-bit" or "MSYS2 MinGW 64-bit" from the Start Menu. Then run this command to install the required packages:
pacman -S $MINGW_PACKAGE_PREFIX-{toolchain,cmake}
If pacman prompts you to enter a selection of packages to install, just press enter to install all of the packages.
Download the source code of this library and navigate to the top-level directory using `cd`. Then run these commands to build the library and install it:
mkdir build
cd build
MSYS2_ARG_CONV_EXCL=- cmake .. -G"MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX
make install DESTDIR=/
We currently do not provide any build files for Visual Studio. You can use CMake to generate Visual Studio build files, and the library and its examples will probably compile, but we have not tested the resulting library.
## Building from source on Linux
First, you will need to make sure that you have a suitable compiler installed, such as gcc. You can run `gcc -v` in a shell to make sure it is available.
You will also need to install CMake and libudev. On Ubuntu, Raspbian, and other Debian-based distributions, the command to do this is:
sudo apt-get install cmake libudev-dev
On Arch Linux, libudev should already be installed, and you can install CMake by running:
sudo pacman -S cmake
Download the source code of this library and navigate to the top-level directory using `cd`. Then run these commands to build the library and install it:
mkdir build
cd build
cmake ..
make
sudo make install
## Building from source on macOS
First, install [Homebrew](http://brew.sh/), a package manager for macOS. Then use Homebrew to install CMake by running the following command in a Terminal:
brew install cmake
Download the source code of this library and navigate to the top-level directory using `cd`. Then run these commands in a Terminal to build the library and install it:
mkdir build
cd build
cmake ..
make
sudo make install
## Incorporating libusbp into a C/C++ project
The first step to incorporating libusbp into another project is to add the libusbp header folder to your project's include search path.
The header folder is the folder that contains `libusbp.h` and `libusbp.hpp`, and it will typically be named `libusbp-1`.
On systems with `pkg-config`, assuming libusbp has been installed properly, you can run the following command to get the include directory:
pkg-config --cflags libusbp-1
The output of that command is formatted so that it can be directly added to the command-line arguments for most compilers.
Next, you should include the appropriate libusbp header in your source code by adding an include directive at the top of one of your source files.
To use the C API from a C or C++ project, you should write:
#include <libusbp.h>
To use the C++ API from a C++ project, you should write:
#include <libusbp.hpp>
After making the changes above, you should be able compile your project successfully.
If the compiler says that the libusbp header file cannot be found, make sure you have specified the include path correctly as described above.
If you add a call to any of the libusbp functions and rebuild, you will probably get an undefined reference error from the linker. To fix this, you need to add libusbp's linker settings to your project. To get the linker settings, run:
pkg-config --libs libusbp-1
If you are using GCC and a shell that supports Bash-like syntax, here is an example command that compiles a single-file C program using the correct compiler and linker settings:
gcc program.c `pkg-config --cflags --libs libusbp-1`
Here is an equivalent command for C++. Note that we use the `--std=gnu++11` option because the libusbp C++ API requires features from C++11:
g++ --std=gnu++11 program.cpp `pkg-config --cflags --libs libusbp-1`
The order of the arguments above matters: the user program must come before libusbp because it relies on symbols that are defined by libusbp.
The `examples` folder of this repository contains some example code that uses libusbp. These examples can serve as a starting point for your own project.
## Versioning
The version numbers used by this library follow the rules of [Semantic Versioning 2.0.0](http://semver.org/).
A backwards-incompatible version of this library might be released in the future.
If that happens, the new version will have a different major version number: its version number will be 2.0.0 and it will be known as libusbp-2 to `pkg-config`.
This library was designed to support having different major versions installed side-by-side on the same computer, so you could have both libusbp-1 and libusbp-2 installed at the same time.
However, you would not be able to use both versions from a single program.
If you write software that depends on libusbp, we recommend specifying which major version of libusbp your software uses in both the documentation of your software and in the scripts used to build it. Scripts or instructions for downloading the source code of libusbp should use a branch name to ensure that they downlod the latest version of the code for a given major version number. For example, the branch of this repository named "v1-latest" will always point to the latest release of libusbp-1.
## Documentation
For detailed documentation of this library, see the header files `libusb.h` and `libusbp.hpp` in the `include` directory, and see the `.md` files in this directory. You can also use [Doxygen](http://doxygen.org/) to generate documentation from those files.
## Version history
* 1.2.0 (2020-11-16):
* Linux: Made the library work with devices attached to the cp210x driver.
* Windows: Made the library work with devices that have lowercase letters in their hardware IDs.
* 1.1.0 (2018-11-23):
* Added `libusbp_write_pipe`.
* 1.0.4 (2017-08-29):
* Fixed a compilation error for macOS.
* Added the `lsport` example for listing multiple USB serial ports.
* 1.0.3 (2017-08-29):
* Compiler flags from libudev.pc are now used.
* For static builds, libusbp-1.pc now requires libudev.pc instead of copying its flags.
* Always build the install helper as a shared library (DLL).
* 1.0.2 (2017-06-29):
* Fixed some issues with using libusbp as a static library.
* Added the `drop_in` example, which shows how to use libusbp as a three-file library.
* 1.0.1 (2016-03-20): Fixed the `libusbp_broadcast_setting_change*` functions (a bonus feature for the Windows version).
* 1.0.0 (2016-03-02): Original release.

18
dep/libusbp/THREADS.md Normal file
View file

@ -0,0 +1,18 @@
# Thread safety
If you are using multiple threads in your application, it is important to make sure that your calls to libusbp will be thread safe. This page covers everything you need to know about thread safety and libusbp.
We will only discuss the C API functions defined in libusbp.h. The C++ API defined in libusbp.hpp is just a simple wrapper around the C API and does not introduce or solve any thread safety issues.
This library does not create threads, use mutable global variables, use volatile variables, use mutexes, use memory barriers, or use reference counting.
On this page, two function calls are said to *conflict* with each other if there is no guarantee that executing the function calls concurrently on different threads will work as expected. To characterize the thread-safety of libusbp, we will specify which pairs of function calls conflict with each other. A function call consists of the name of a library function being called along with the values of its arguments.
Two function calls will not conflict if the memory areas pointed to by their arguments have no overlap. This is because the library does not use any mutable global variables. To determine whether the memory areas overlap, you will need to know which library objects hold pointers to other library objects. The rules for that are defined in the "Pointer rules" section below.
If there is an overlap in the memory areas pointed to by the arguments of two functions calls, the overlap will not cause a conflict as long as all of the parameters that are responsible for the overlap are marked with the `const` qualifier in the header. We use `const` as an indicator that the function will not modify the memory pointed to by that argument, and it will not call any API functions that might change the state of the underlying handles held by the object.
## Pointer rules
* Each ::libusbp_async_in_pipe object may hold a pointer to the ::libusbp_generic_handle that it was created from. Similarly, the ::libusbp_generic_handle may hold pointers to its ::libusbp_async_in_pipe objects.
* All other objects contain no pointers to each other.

2
dep/libusbp/UPSTREAM.md Normal file
View file

@ -0,0 +1,2 @@
This was taken from https://github.com/pololu/libusbp on 2021-12-11.

View file

@ -0,0 +1,4 @@
add_subdirectory(async_in)
add_subdirectory(lsport)
add_subdirectory(lsusb)
add_subdirectory(port_name)

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(async_in async_in.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(async_in usbp)

View file

@ -0,0 +1,91 @@
/* This example shows how to read a constant stream of data from an IN endpoint.
* By queueing up a large number of asynchronous transfers, we ensure that the
* USB host controller is busy, and that it will retrieve data from the endpoint
* as fast as possible for an indefinite period of time.
*
* This example is designed to connect to Test Device A and read ADC data from
* endpoint 0x82. */
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
#ifdef _MSC_VER
#define usleep(x) Sleep(((x) + 999) / 1000)
#else
#include <unistd.h>
#endif
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x82;
const size_t transfer_size = 5;
const size_t transfer_count = 250;
// Prints the data in the given buffer to the standard output in HEX.
void print_data(uint8_t * buffer, size_t size)
{
for (uint8_t i = 0; i < size; i++)
{
printf("%02x", buffer[i]);
if (i < size - 1) { putchar(' '); }
}
printf("\n");
fflush(stdout);
}
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(endpoint_address);
pipe.allocate_transfers(transfer_count, transfer_size);
pipe.start_endless_transfers();
while(true)
{
uint8_t buffer[transfer_size];
size_t transferred;
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
if (transfer_error) { throw transfer_error; }
print_data(buffer, transferred);
}
pipe.handle_events();
usleep(500);
}
// Note that closing an async_in_pipe cleanly without causing memory leaks
// can be difficult. For more information, see the documentation of
// libusbp_async_in_pipe_close in libusbp.h. In this example, we don't
// worry about it because we are only ever creating one pipe, so the amount
// of the memory leak is bounded.
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,2 @@
/libusbp*
/lsusb*

View file

@ -0,0 +1,38 @@
# This script converts libusbp into a format that can easily be dropped into
# other projects: one source file, one C header file, and one C++ header file.
#
# This is handy for people who want to use libusbp in their own C/C++ software
# projects but don't want to deal with the issues that come with having one more
# dependency.
#
# It also compiles the lsusb example program using the drop-in library.
set -eou pipefail
cd `dirname $0`
SRC=../../src
{
echo "#if 0"
cat $SRC/../LICENSE.txt
echo "#endif"
cat $SRC/libusbp_internal.h
cat $SRC/*.c
echo "#ifdef _WIN32"
cat $SRC/windows/*.c
echo "#endif"
echo "#ifdef __linux__"
cat $SRC/linux/*.c
echo "#endif"
echo "#ifdef __APPLE__"
cat $SRC/mac/*.c
echo "#endif"
} | sed 's/#include <libusbp_internal.h>//' > libusbp.c
cp ../lsusb/lsusb.cpp $SRC/../include/libusbp.h* .
# TODO: fix the library so we don't need -fpermissive here
g++ -std=gnu++11 -Wall -fpermissive -g -O2 -I. \
-DLIBUSBP_DROP_IN -DLIBUSBP_STATIC \
lsusb.cpp libusbp.c -ludev -o lsusb

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(lsport lsport.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(lsport usbp)

View file

@ -0,0 +1,108 @@
// This example prints the names of all USB serial ports along with information
// about the USB devices they belong to.
//
// For each USB device, it prints the USB vendor ID, product ID, and serial
// number on a line. Then, on the following lines, it prints any serial port
// names it found, sorted by interface number, ascending.
//
// Note: This example is slow and ugly because libusbp does not yet have
// built-in support for listing serial ports; it only has support for finding
// a serial port if you already know what USB device it is connected to and what
// interface you expect the port to be on. This might be improved in the future.
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
std::string serial_number_or_default(const libusbp::device & device,
const std::string & def)
{
try
{
return device.get_serial_number();
}
catch (const libusbp::error & error)
{
if (error.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER))
{
return def;
}
throw;
}
}
bool try_print_port_name(const libusbp::device & device,
uint8_t interface_number, bool composite)
{
std::string port_name;
try
{
libusbp::serial_port port(device, interface_number, composite);
port_name = port.get_name();
}
catch (const libusbp::error & error)
{
return false;
}
std::cout << " " << port_name << std::endl;
return true;
}
int main_with_exceptions()
{
auto devices = libusbp::list_connected_devices();
for (const libusbp::device & device : devices)
{
bool success = false;
// Print the USB device info.
uint16_t vendor_id = device.get_vendor_id();
uint16_t product_id = device.get_product_id();
std::string serial_number = serial_number_or_default(device, "-");
std::ios::fmtflags flags(std::cout.flags());
std::cout
<< std::hex << std::setfill('0') << std::right
<< std::setw(4) << vendor_id
<< ':'
<< std::setw(4) << product_id
<< ' '
<< std::setfill(' ') << std::left << serial_number
<< std::endl;
std::cout.flags(flags);
// First, assume the device is composite and try the first 16 interfaces.
// Most devices don't have more interfaces than that. Trying all 255 possible
// interfaces slows the program down noticeably. This issue could be fixed if
// we added better serial port enumeration support to libusbp.
for (uint32_t i = 0; i < 16; i++)
{
success = try_print_port_name(device, i, true) || success;
}
// Try to find a port assuming the device is non-composite. Only do so if
// no ports were found earlier, to help avoid printing the same port twice.
if (!success)
{
try_print_port_name(device, 0, false);
}
}
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(lsusb lsusb.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(lsusb usbp)

View file

@ -0,0 +1,80 @@
/* This example shows how to gather information about the USB devices connected
* to the system and print it. */
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
std::string serial_number_or_default(const libusbp::device & device,
const std::string & def)
{
try
{
return device.get_serial_number();
}
catch (const libusbp::error & error)
{
if (error.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER))
{
return def;
}
throw;
}
}
void print_device(libusbp::device & device)
{
uint16_t vendor_id = device.get_vendor_id();
uint16_t product_id = device.get_product_id();
uint16_t revision = device.get_revision();
std::string serial_number = serial_number_or_default(device, "-");
std::string os_id = device.get_os_id();
// Note: The serial number might have spaces in it, so it should be the last
// field to avoid confusing programs that are looking for a field after the
// serial number.
std::ios::fmtflags flags(std::cout.flags());
std::cout
<< std::hex << std::setfill('0') << std::right
<< std::setw(4) << vendor_id
<< ':'
<< std::setw(4) << product_id
<< ' '
<< std::setfill(' ') << std::setw(2) << (revision >> 8)
<< '.'
<< std::setfill('0') << std::setw(2) << (revision & 0xFF)
<< ' '
<< os_id
<< ' '
<< std::setfill(' ') << std::left << serial_number
<< std::endl;
std::cout.flags(flags);
}
int main_with_exceptions()
{
std::vector<libusbp::device> list = libusbp::list_connected_devices();
for (auto it = list.begin(); it != list.end(); ++it)
{
print_device(*it);
}
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(port_name port_name.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(port_name usbp)

View file

@ -0,0 +1,44 @@
// This example shows how to get the name of a USB serial port (e.g. "COM6")
// for a single USB device.
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 2;
const bool composite = true;
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::serial_port port(device, interface_number, composite);
std::string port_name = port.get_name();
std::cout << port_name << std::endl;
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,737 @@
// Copyright (C) Pololu Corporation. See www.pololu.com for details.
/*! \file libusbp.h
*
* This header file provides the C API for libusbp.
*/
#pragma once
/*! The major component of libusbp's version number. In accordance with
* semantic versioning, this gets incremented every time there is a breaking
* change. */
#define LIBUSBP_VERSION_MAJOR 1
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef _WIN32
#include <wtypesbase.h>
#endif
#ifdef _WIN32
# define LIBUSBP_DLL_EXPORT __declspec(dllexport)
# define LIBUSBP_DLL_IMPORT __declspec(dllimport)
#else
# define LIBUSBP_DLL_IMPORT __attribute__((visibility ("default")))
# define LIBUSBP_DLL_EXPORT __attribute__((visibility ("default")))
#endif
#ifdef _MSC_VER
#define LIBUSBP_WARN_UNUSED _Check_return_
#else
#define LIBUSBP_WARN_UNUSED __attribute__((warn_unused_result))
#endif
#ifdef LIBUSBP_STATIC
# define LIBUSBP_API
#else
#error not static
# ifdef LIBUSBP_EXPORTS
# define LIBUSBP_API LIBUSBP_DLL_EXPORT
# else
# define LIBUSBP_API LIBUSBP_DLL_IMPORT
# endif
#endif
/*! Some functions in this library return strings to the caller via a char **
* argument. If the function call was successful, it is the caller's
* responsibility to free those strings by passing them to this function.
* Passing the NULL pointer to this function is OK. Do not pass any strings to
* this function unless they were previously returned by a call to this
* library. Do not free the same non-NULL string twice. */
LIBUSBP_API
void libusbp_string_free(char *);
/** libusbp_error **************************************************************/
/*! A libusbp_error object represents an error that occurred in the library.
* Many functions return a libusbp_error pointer as a return value. The
* convention is that a NULL pointer indicates success. If the pointer is not
* NULL, the caller needs to free it at some point by calling
* libusbp_error_free().
*
* NULL is a valid value for a libusbp_error pointer, and can be passed to any
* function in this library that takes a libusbp_error pointer. */
typedef struct libusbp_error
libusbp_error;
/*! Each ::libusbp_error can have 0 or more error codes that give additional
* information about the error that might help the caller take the right action
* when the error occurs. This enum defines which error codes are possible. */
enum libusbp_error_code
{
/*! There were problems allocating memory. A memory shortage might be the
* root cause of the error, or there might be another error that is masked
* by the memory problems. */
LIBUSBP_ERROR_MEMORY = 1,
/*! It is possible that the error was caused by a temporary condition, such
* as the operating system taking some time to initialize drivers. Any
* function that could return this error will say so explicitly in its
* documentation so you do not have to worry about handling it in too many
* places. */
LIBUSBP_ERROR_NOT_READY = 2,
/*! Access was denied. A common cause of this error on Windows is that
* another application has a handle open to the same device. */
LIBUSBP_ERROR_ACCESS_DENIED = 3,
/*! The device does not have a serial number. */
LIBUSBP_ERROR_NO_SERIAL_NUMBER = 4,
/*! The device took too long to respond to a request or transfer data. */
LIBUSBP_ERROR_TIMEOUT = 5,
/*! The error might have been caused by the device being disconnected, but
* it is possible it was caused by something else. */
LIBUSBP_ERROR_DEVICE_DISCONNECTED = 6,
/*! The error might have been caused by the host receiving a STALL packet
* from the device, but it is possible it was caused by something else. */
LIBUSBP_ERROR_STALL = 7,
/*! The error might have been caused by the transfer getting cancelled from
* the host side. Some data might have been transferred anyway. */
LIBUSBP_ERROR_CANCELLED = 8,
};
/*! Attempts to copy an error. If you copy a NULL ::libusbp_error
* pointer, the result will also be NULL. If you copy a non-NULL ::libusbp_error
* pointer, the result will be non-NULL, but if there are issues allocating
* memory, then the copied error might have different properties than the
* original error, and it will have the ::LIBUSBP_ERROR_MEMORY code.
*
* It is the caller's responsibility to free the copied error. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_error_copy(const libusbp_error *);
/*! Frees a returned error object. Passing the NULL pointer to this function is
* OK. Do not free the same non-NULL error twice. */
LIBUSBP_API
void libusbp_error_free(libusbp_error *);
/*! Returns true if the error has specified error code. The error codes are
* listed in the ::libusbp_error_code enum. */
LIBUSBP_API bool libusbp_error_has_code(const libusbp_error *, uint32_t code);
/*! Returns an English-language ASCII-encoded string describing the error. The
* message consists of one or more sentences. If there are multiple sentences,
* the earlier ones will typically explain the context that the error happened
* in.
*
* The returned pointer will be valid until the error is freed, at which point
* it might become invalid. Do not pass the returned pointer to
* libusbp_string_free(). */
LIBUSBP_API const char * libusbp_error_get_message(const libusbp_error *);
/** libusbp_async_in_pipe ******************************************************/
/*! A libusbp_async_in_pipe is an object that holds the memory and other data
* structures for a set of asynchronous USB requests to read data from a
* non-zero endpoint. It can be used to read data from a bulk or IN endpoint
* with high throughput. */
typedef struct libusbp_async_in_pipe
libusbp_async_in_pipe;
/*! Closes the pipe immediately. Note that if the pipe has any
* pending transfers, then it is possible that they cannot be freed
* by this function. Freeing a pipe with pending transfers could
* cause a memory leak, but is otherwise safe. */
LIBUSBP_API
void libusbp_async_in_pipe_close(libusbp_async_in_pipe *);
/*! Allocates buffers and other data structures for performing multiple
* concurrent transfers on the pipe.
*
* The @a transfer_count parameter specifies how many transfers to allocate. You
* can also think of this as the maximum number of concurrent transfers that can
* be active at the same time.
*
* The @a transfer_size parameter specifies how large each transfer's buffer should
* be, and is also the number of bytes that will be requested from the operating
* system when the transfer is submitted.
*
* It is best to set the transfer size to a multiple of the maximum packet size
* of the endpoint. Otherwise, you might get an error when the device sends
* more data than can fit in the transfer's buffer. This type of error is
* called an overflow.
*
* If you want to be reading the pipe every millisecond without gaps, you should
* set the transfer count high enough so that it would take about 100 ms to 250
* ms to finish all the transfers. As long as the operating system runs your
* process that often, you should be able to keep the USB host controller busy
* permanently. (Though we have observed gaps in the transfers when trying to
* do this inside a VirtualBox machine.)
*
* You should not set the transfer count too high, or else it might end up
* taking a long time to cancel the transfers when you are closing the pipe. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_allocate_transfers(
libusbp_async_in_pipe *,
size_t transfer_count,
size_t transfer_size);
/*! Starts reading data from the pipe. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_start_endless_transfers(
libusbp_async_in_pipe *);
/*! Checks for new events, such as a transfer completing. This
* function and libusbp_async_in_pipe_handle_finished_transfer() should
* be called regularly in order to get data from the pipe. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe *);
/*! Retrieves a boolean saying whether there are any pending
* transfers. A pending transfer is a transfer that was submitted to
* the operating system, and it may have been completed, but it has
* not been passed to the caller yet via
* libusbp_async_in_pipe_handle_finished_transfer(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error *
libusbp_async_in_pipe_has_pending_transfers(
libusbp_async_in_pipe *,
bool * result);
/*! Checks to see if there is a finished transfer that can be handled.
* If there is one, then this function retrieves the data from the
* transfer, the number of bytes transferred, and any error that might
* have occurred related to the transfer.
*
* @param finished An optional output pointer used to return a pointer that
* indicates whether a transfer was finished. If the returned value is false,
* then no transfer was finished, and there is no transfer_error or data to
* handle.
*
* @param buffer An optional output pointer used to return the data
* from the transfer. The buffer must be at least as large as the
* transfer size specifed when
* libusbp_async_in_pipe_allocate_transfers was called.
*
* @param transferred An optional output pointer used to return the
* number of bytes transferred.
*
* @param transfer_error An optional pointer used to return an error
* related to the transfer, such as a timeout or a cancellation. If
* this pointer is provided, and a non-NULL error is returned via it,
* then the error must later be freed with libusbp_error_free(). There
* will never be a non-NULL transfer error if there is a regular error
* returned as the return value of this function. */
LIBUSBP_API LIBUSBP_WARN_UNUSED libusbp_error *
libusbp_async_in_pipe_handle_finished_transfer(
libusbp_async_in_pipe *,
bool * finished,
void * buffer,
size_t * transferred,
libusbp_error ** transfer_error);
/*! Cancels all the transfers for this pipe. The cancellation is
* asynchronous, so it won't have an immediate effect. If you want
* to actually make sure that all the transfers get cancelled, you
* will need to call libusbp_async_in_pipe_handle_events() and
* libusbp_async_in_pipe_handle_finished_transfer() repeatedly until
* libusbp_async_in_pipe_has_pending_transfers() indicates there are
* no pending transfers left. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe *);
/** libusbp_device *************************************************************/
/*! Represents a single USB device. A composite device with multiple functions
* is represented by a single libusbp_device object.
*
* A NULL libusbp_device pointer is valid and can be passed to any function in
* this library that takes such pointers. */
typedef struct libusbp_device
libusbp_device;
/*! Finds all the USB devices connected to the computer and returns a list of them.
*
* The optional @a device_count parameter is used to return the number of
* devices in the list. The list is actually one element larger because it ends
* with a NULL pointer.
*
* If this function is successful (the returned error pointer is NULL), then you
* must later free each device by calling libusbp_device_free() and free the
* list by calling libusbp_list_free(). The order in which the retrieved
* objects are freed does not matter. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_list_connected_devices(
libusbp_device *** device_list,
size_t * device_count);
/*! Frees a device list returned by libusbp_list_connected_device(). */
LIBUSBP_API
void libusbp_list_free(libusbp_device ** list);
/*! Finds a device with the specified vendor ID and product ID and returns a
* pointer to it. If no device can be found, returns a NULL pointer. If the
* retrieved device pointer is not NULL, you must free it later by calling
* libusbp_device_free(). The retrieved device pointer will always be NULL if
* an error is returned. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_find_device_with_vid_pid(
uint16_t vendor_id,
uint16_t product_id,
libusbp_device ** device);
/*! Makes a copy of a device object. If this function is successful, you will
* need to free the copy by calling libusbp_device_free() at some point. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_copy(
const libusbp_device * source,
libusbp_device ** dest);
/*! Frees a device object. Passing a NULL pointer to this function is OK. Do
* not free the same non-NULL device twice. */
LIBUSBP_API void libusbp_device_free(libusbp_device *);
/*! Gets the USB vendor ID of the device (idVendor). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_vendor_id(
const libusbp_device *,
uint16_t * vendor_id);
/*! Gets the USB product ID of the device (idProduct). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_product_id(
const libusbp_device *,
uint16_t * product_id);
/*! Gets the USB revision code of the device (bcdDevice). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_revision(
const libusbp_device *,
uint16_t * revision);
/*! Gets the serial number of the device as an ASCII-encoded string.
*
* On Windows, this just returns the segment of the Device Instance ID after the
* last slash. If the device does not have a serial number, it will return some
* other type of identifier that contains andpersands (&). Windows ignores
* serial numbers with invalid characters in them. For more information, see:
*
* https://msdn.microsoft.com/en-us/library/windows/hardware/dn423379#usbsn
*
* On other systems, if the device does not have a serial number, then this
* function returns an error with the code ::LIBUSBP_ERROR_NO_SERIAL_NUMBER.
*
* (Most applications should only call this function on specific USB devices
* that are already known to have serial numbers, in which case the lack of a
* serial number really does indicate a failure.)
*
* You should free the returned string by calling libusbp_string_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_serial_number(
const libusbp_device *,
char ** serial_number);
/*! Gets an operating system-specific string that identifies the device.
*
* Note that the level of specificity provided by the ID depends on the system
* you are on, and whether your device has a USB serial number. As long as the
* device remains connected to the bus, this ID is not expected to change and
* there should be no other devices that have the same ID. However, if the
* device gets disconnected from the bus, it may be possible for the ID to be
* reused by another device.
*
* @b Windows: This will be a device instance ID, and it will look something
* like this:
*
* <pre>
* USB\\VID_1FFB&PID_DA01\6&11A23516&18&0000
* </pre>
*
* If your device has a serial number, the part after the slash will be the
* serial number. Otherwise, it will be a string with andpersands in it.
*
* @b Linux: This will be a sysfs path, and it will look like something like
* this:
*
* <pre>
* /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2
* </pre>
*
* <b>macOS:</b> This will be an integer from
* IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
* no leading zeros. It will look something like this:
*
* <pre>
* 10000021a
* </pre>
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_device_get_os_id(
const libusbp_device *,
char ** id);
/** libusbp_generic_interface **************************************************/
/*! Represents a generic or vendor-defined interface of a USB device. A null
* libusbp_generic_interface pointer is valid and can be passed to any function
* in this library that takes such pointers. */
typedef struct libusbp_generic_interface
libusbp_generic_interface;
/*! Creates a generic interface object for a specified interface of the
* specified USB device. This function does as many checks as possible to make
* sure that a handle to the interface could be opened, without actually opening
* it yet.
*
* On all platforms, if a record of the interface cannot be found, then an error
* is returned with the code LIBUSBP_ERROR_NOT_READY, because this could just be
* a temporary condition that happens right after the device is plugged in.
*
* On Windows, the generic interface must use the WinUSB driver, or this
* function will fail. If it is using no driver, that could be a temporary
* condition, and the error returned will use the LIBUSBP_ERROR_NOT_READY error
* code.
*
* On Linux, if the corresponding devnode file does not exist, an error with
* code LIBUSBP_ERROR_NOT_READY is returned. If the interface is assigned to a
* driver that is not "usbfs", an error is returned.
*
* On macOS, we do not have any additional checks beyond just making sure
* that an entry for the interface is found. For non-composite devices, that
* check is deferred until a handle is opened.
*
* @param interface_number The lowest @a bInterfaceNumber for the interfaces in
* the USB function you want to use.
*
* @param composite Should be true if the device is composite, and false
* otherwise.
*
* The returned object must be freed with libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_create(
const libusbp_device *,
uint8_t interface_number,
bool composite,
libusbp_generic_interface **);
/*! Frees the specified generic interface object. Passing the NULL pointer to
* this function is OK. Do not free the same non-NULL pointer twice. */
LIBUSBP_API void libusbp_generic_interface_free(libusbp_generic_interface *);
/*! Makes a copy of the generic interface object. The copy must be freed with
* libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_copy(
const libusbp_generic_interface * source,
libusbp_generic_interface ** dest);
/*! Returns an operating system-specific string that can be used to uniquely
* identify this generic interface.
*
* <b>Windows:</b> This will be a device instance ID specific to this interface, and
* it will look something like this:
*
* <pre>
* USB\\VID_1FFB&PID_DA01&MI_00\6&11A23516&18&0000
* </pre>
*
* <b>Linux:</b> This will be a sysfs path specific to this interface, and it will
* look like something like this:
*
* <pre>
* /sys/devices/pci0000:00/0000:00:06.0/usb1/1-2/1-2:1.0
* </pre>
*
* <b>macOS:</b> This will be an integer from
* IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
* no leading zeros. It will look something like this:
*
* <pre>
* 10000021a
* </pre>
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_get_os_id(
const libusbp_generic_interface *,
char ** id);
/*! Returns an operating system-specific filename corresponding to this
* interface.
*
* <b>Windows:</b> This will be the name of a file you can use with CreateFile to
* access the device, and it will look something like this:
*
* <pre>
* \\\\?\\usb#vid_1ffb&pid_da01&mi_00#6&11a23516&18&0000#{99c4bbb0-e925-4397-afee-981cd0702163}
* </pre>
*
* <b>Linux:</b> this will return a device node file name that represents the
* overall USB device. It will look something like:
*
* <pre>
* /dev/bus/usb/001/007
* </pre>
*
* <b>macOS:</b> This will be an integer from
* IORegistryEntryGetRegistryEntryID, formatted as a lower-case hex number with
* no leading zeros. It will look something like this:
*
* <pre>
* 10000021a
* </pre>
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_interface_get_os_filename(
const libusbp_generic_interface *,
char ** filename);
/** libusbp_generic_handle *****************************************************/
/*! Represents a generic handle to a USB device. This handle can be used to
* perform operations such as control transfers and reading and writing data
* from non-zero endpoints.
*
* NULL is a valid value for a libusbp_generic_handle pointer, and can be passed
* in any functions of this library that take a libusbp_generic_handle
* pointer. */
typedef struct libusbp_generic_handle
libusbp_generic_handle;
/*! Opens a generic handle to the specified interface of a USB device which can
* be used to perform USB I/O operations.
*
* The handle must later be closed with libusbp_generic_handle_close().
*
* On Windows, for devices using WinUSB, if another application has a handle
* open already when this function is called, then this function will fail and
* the returned error will have code ::LIBUSBP_ERROR_ACCESS_DENIED.
*
* On macOS, this function will set the device's configuration to 1 as a
* side effect in case it is not already configured. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_handle_open(
const libusbp_generic_interface *,
libusbp_generic_handle **);
/*! Closes and frees the specified generic handle. It is OK to pass NULL to
* this function. Do not close the same non-NULL handle twice. All
* ::libusbp_async_in_pipe objects created by the handle must be closed before
* closing the handle. */
LIBUSBP_API
void libusbp_generic_handle_close(
libusbp_generic_handle *);
/*! Creates a new asynchronous pipe object for reading data in from the device
* on one of its bulk or interrupt IN endpoints.
*
* The behavior of this library is unspecified if you use both an asynchronous
* IN pipe and synchronous reads with libusbp_read_pipe() on the same pipe of
* the same generic handle. One reason for that is because for WinUSB devices,
* this function enables RAW_IO for the pipe, and it does not turn off RAW_IO
* again after the pipe is closed. So the behavior of libusbp_read_pipe() could
* change depending on whether an asynchronous IN pipe has been used. */
LIBUSBP_API
libusbp_error * libusbp_generic_handle_open_async_in_pipe(
libusbp_generic_handle *,
uint8_t pipe_id,
libusbp_async_in_pipe ** async_in_pipe);
/*! Sets a timeout for a particular pipe on the USB device.
*
* The @a pipe_id should either be 0 to specify control transfers on endpoint 0, or
* should be a bEndpointAddress value from one of the device's endpoint
* descriptors. Specifying an invalid pipe might result in an error.
*
* The timeout value is specified in milliseconds, and a value of 0 means no
* timeout (wait forever). If this function is not called, the default behavior
* of the handle is to have no timeout.
*
* The behavior of this function is unspecified if there is any data being
* transferred on the pipe while this function is running.
*
* It is unspecified whether this timeout has an effect on asynchronous
* transfers. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_generic_handle_set_timeout(
libusbp_generic_handle *,
uint8_t pipe_id,
uint32_t timeout);
/*! Performs a synchronous (blocking) control transfer on endpoint 0.
*
* Under Linux, this blocking transfer unfortunately cannot be interrupted with
* Ctrl+C.
*
* The @a buffer parameter should point to a buffer that is at least @a wLength
* bytes long.
*
* The @a transferred pointer is optional, and is used to return the number of
* bytes that were actually transferred.
*
* The direction of the transfer is determined by the @a bmRequestType parameter.
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_control_transfer(
libusbp_generic_handle *,
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * buffer,
uint16_t wLength,
size_t * transferred);
/*! Performs a synchronous (blocking) write of data to a bulk or interrupt
* endpoint.
*
* Under Linux, this blocking transfer unfortunately cannot be interrupted with
* Ctrl+C.
*
* The @a pipe_id parameter specifies which endpoint to use. This argument
* should be bEndpointAddress value from one of the device's IN endpoint
* descriptors. (Its most significant bit must be 0.)
*
* The @a transferred parameter is an optional pointer to a variable that will
* receive the number of bytes transferred. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_write_pipe(
libusbp_generic_handle *,
uint8_t pipe_id,
const void * buffer,
size_t size,
size_t * transferred);
/*! Performs a synchronous (blocking) read of data from a bulk or interrupt
* endpoint.
*
* It is best to set the buffer size to a multiple of the maximum
* packet size of the endpoint. Otherwise, this function might return
* an error when the device sends more data than can fit in the
* buffer. This type of error is called an overflow.
*
* Under Linux, this blocking transfer unfortunately cannot be interrupted with
* Ctrl+C.
*
* The @a pipe_id parameter specifies which endpoint to use. This argument
* should be bEndpointAddress value from one of the device's IN endpoint
* descriptors. (Its most significant bit must be 1.)
*
* The @a transferred parameter is an optional pointer to a variable that will
* receive the number of bytes transferred. */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_read_pipe(
libusbp_generic_handle *,
uint8_t pipe_id,
void * buffer,
size_t size,
size_t * transferred);
#ifdef __linux__
/*! Gets the underlying file descriptor of the generic handle. This function is
* only available on Linux, and is intended for advanced users. The returned
* file descriptor will remain open and valid as long as the handle is open and
* has not been closed. */
LIBUSBP_API
int libusbp_generic_handle_get_fd(libusbp_generic_handle *);
#endif
#ifdef _WIN32
/*! Gets the underlying WinUSB handle for the generic handle. This function is
* only available on Windows, and is intended for advanced users. The returned
* WinUSB handle will remain open and valid as long as the generic handle is
* open and has not been closed. */
LIBUSBP_API
HANDLE libusbp_generic_handle_get_winusb_handle(libusbp_generic_handle *);
#endif
#ifdef __APPLE__
/*! Gets the underlying IOCFPlugInInterface object representing the interface.
* You can cast the returned pointer to a `IOCFPlugInInterface **` and then use
* `QueryInterface` to get the corresponding `IOUSBInterfaceInterface **`.
* There is an example of this in generic_handle_test.cpp. */
LIBUSBP_API
void ** libusbp_generic_handle_get_cf_plug_in(libusbp_generic_handle *);
#endif
/** libusbp_serial_port ********************************************************/
/*! Represents a serial port. A null libusbp_serial_port pointer is valid and
* can be passed to any function in this library that takes such pointers. */
typedef struct libusbp_serial_port
libusbp_serial_port;
/*! Creates a serial port object for a specified interface of the
* specified USB device.
*
* On all platforms, if a record of the interface cannot be found, then an error
* is returned with the code LIBUSBP_ERROR_NOT_READY, because this could just be
* a temporary condition that happens right after the device is plugged in.
*
* On macOS, it is assumed that the interface with a @a bInterfaceNumber one
* greater than @a interface_number is the interface that the IOSerialBSDClient
* will attach to. This should be true if the device implements the USB CDC ACM
* class and has ordered its interfaces so that the control interface is right
* before the data interface.
*
* @param interface_number The lowest @a bInterfaceNumber for the USB interfaces
* that comprise the serial port.
*
* @param composite Should be true if the device is composite, and false
* otherwise.
*
* The returned object must be freed with libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_serial_port_create(
const libusbp_device *,
uint8_t interface_number,
bool composite,
libusbp_serial_port **);
/*! Frees the specified serial port object. Passing the NULL pointer to
* this function is OK. Do not free the same non-NULL pointer twice. */
LIBUSBP_API void libusbp_serial_port_free(libusbp_serial_port *);
/*! Makes a copy of the generic interface object. The copy must be freed with
* libusbp_generic_interface_free(). */
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_serial_port_copy(
const libusbp_serial_port * source,
libusbp_serial_port ** dest);
/*! Gets the user-friendly name of the COM port
* that could be used to open a handle.
*
* On Windows, this will be something like "COM12".
*
* On Linux, it will be something like "/dev/ttyACM0".
*
* On macOS, it will be something like "/dev/cu.usbmodem012345".
* Specifically, it will be a call-out device, not a dial-in device.
*
* You should free the returned string by calling libusbp_string_free().
*/
LIBUSBP_API LIBUSBP_WARN_UNUSED
libusbp_error * libusbp_serial_port_get_name(
const libusbp_serial_port *,
char ** name);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,569 @@
// Copyright (C) Pololu Corporation. See www.pololu.com for details.
/*! \file libusbp.hpp
*
* This header files provides the C++ API for libusbp. The classes and
* functions here are just thin wrappers around the C API, so you should see
* libusbp.h for full documentation. */
#pragma once
#include "libusbp.h"
#include <cstddef>
#include <utility>
#include <memory>
#include <string>
#include <vector>
/** Display a nice error if C++11 is not enabled (e.g. --std=c++11 or --std=gnu++11).
* The __GXX_EXPERIMENTAL_CXX0X__ check is needed for GCC 4.6, which defines __cplusplus as 1.
* The _MSC_VER check is needed for Visual Studio 2015. */
#if (!defined(__cplusplus) || (__cplusplus < 201103L)) && !defined(__GXX_EXPERIMENTAL_CXX0X__) && !defined(_MSC_VER)
#error This header requires features from C++11.
#endif
namespace libusbp
{
/*! \cond */
inline void throw_if_needed(libusbp_error * err);
/*! \endcond */
/*! Wrapper for libusbp_error_free(). */
inline void pointer_free(libusbp_error * p) noexcept
{
libusbp_error_free(p);
}
/*! Wrapper for libusbp_error_copy(). */
inline libusbp_error * pointer_copy(libusbp_error * p) noexcept
{
return libusbp_error_copy(p);
}
/*! Wrapper for libusbp_async_in_pipe_close(). */
inline void pointer_free(libusbp_async_in_pipe * p) noexcept
{
libusbp_async_in_pipe_close(p);
}
/*! Wrapper for libusbp_device_free(). */
inline void pointer_free(libusbp_device * p) noexcept
{
libusbp_device_free(p);
}
/*! Wrapper for libusbp_device_copy(). */
inline libusbp_device * pointer_copy(libusbp_device * pointer)
{
libusbp_device * copy;
throw_if_needed(libusbp_device_copy(pointer, &copy));
return copy;
}
/*! Wrapper for libusbp_generic_interface_free(). */
inline void pointer_free(libusbp_generic_interface * p) noexcept
{
libusbp_generic_interface_free(p);
}
/*! Wrapper for libusbp_generic_interface_copy(). */
inline libusbp_generic_interface * pointer_copy(libusbp_generic_interface * pointer)
{
libusbp_generic_interface * copy;
throw_if_needed(libusbp_generic_interface_copy(pointer, &copy));
return copy;
}
/*! Wrapper for libusbp_generic_handle_free(). */
inline void pointer_free(libusbp_generic_handle * p) noexcept
{
libusbp_generic_handle_close(p);
}
/*! Wrapper for libusbp_serial_port_copy(). */
inline libusbp_serial_port * pointer_copy(libusbp_serial_port * pointer)
{
libusbp_serial_port * copy;
throw_if_needed(libusbp_serial_port_copy(pointer, &copy));
return copy;
}
/*! Wrapper for libusbp_serial_port_free(). */
inline void pointer_free(libusbp_serial_port * p) noexcept
{
libusbp_serial_port_free(p);
}
/*! This class is not part of the public API of the library and you should
* not use it directly, but you can use the public methods it provides to
* the classes that inherit from it.
*
* For any type T, if you define pointer_free(T *), then
* unique_pointer_wrapper<T> will be a well-behaved C++ class that provides
* a constructor, implicit conversion to a bool, C++ move operations,
* pointer operations, and forbids C++ copy operations. */
template<class T>
class unique_pointer_wrapper
{
public:
/*! Constructor that takes a pointer. */
explicit unique_pointer_wrapper(T * p = nullptr) noexcept
: pointer(p)
{
}
/*! Move constructor. */
unique_pointer_wrapper(unique_pointer_wrapper && other) noexcept
{
pointer = other.pointer_release();
}
/*! Move assignment operator. */
unique_pointer_wrapper & operator=(unique_pointer_wrapper && other) noexcept
{
pointer_reset(other.pointer_release());
return *this;
}
/*! Destructor. */
~unique_pointer_wrapper() noexcept
{
pointer_reset();
}
/*! Implicit conversion to bool. Returns true if the underlying pointer
* is not NULL. */
explicit operator bool() const noexcept
{
return pointer != nullptr;
}
/*! Returns the underlying pointer. */
T * pointer_get() const noexcept
{
return pointer;
}
/*! Sets the underlying pointer to the specified value, freeing the
* previous pointer and taking ownership of the specified one. */
void pointer_reset(T * p = nullptr) noexcept
{
pointer_free(pointer);
pointer = p;
}
/*! Releases the pointer, transferring ownership of it to the caller and
* resetting the underlying pointer of this object to nullptr. The caller
* is responsible for freeing the returned pointer if it is not NULL. */
T * pointer_release() noexcept
{
T * p = pointer;
pointer = nullptr;
return p;
}
/*! Returns a pointer to the underlying pointer. */
T ** pointer_to_pointer_get() noexcept
{
return &pointer;
}
/*! Copy constructor: forbid. */
unique_pointer_wrapper(const unique_pointer_wrapper & other) = delete;
/*! Copy assignment operator: forbid. */
unique_pointer_wrapper & operator=(const unique_pointer_wrapper & other) = delete;
protected:
/*! The underlying pointer that is being wrapped. This pointer will be
* freed when the object is destroyed. */
T * pointer;
};
/*! This class is not part of the public API of the library and you should
* not use it directly, but you can use the public methods it provides to
* the classes that inherit from it.
*
* For any type T, if you define pointer_free(T *) and pointer_copy(T *), then
* unique_pointer_wrapper_with_copy<T> will be a well-behaved C++ class that provides
* a constructor, implicit conversion to a bool, C++ move operations, C++ copy operations,
* and pointer operations. */
template <class T>
class unique_pointer_wrapper_with_copy : public unique_pointer_wrapper<T>
{
public:
/*! Constructor that takes a pointer. */
explicit unique_pointer_wrapper_with_copy(T * p = nullptr) noexcept
: unique_pointer_wrapper<T>(p)
{
}
/*! Move constructor. */
unique_pointer_wrapper_with_copy(
unique_pointer_wrapper_with_copy && other) noexcept = default;
/*! Copy constructor */
unique_pointer_wrapper_with_copy(
const unique_pointer_wrapper_with_copy & other)
: unique_pointer_wrapper<T>()
{
this->pointer = pointer_copy(other.pointer);
}
/*! Copy assignment operator. */
unique_pointer_wrapper_with_copy & operator=(
const unique_pointer_wrapper_with_copy & other)
{
this->pointer_reset(pointer_copy(other.pointer));
return *this;
}
/*! Move assignment operator. */
unique_pointer_wrapper_with_copy & operator=(
unique_pointer_wrapper_with_copy && other) = default;
};
/*! Wrapper for a ::libusbp_error pointer. */
class error : public unique_pointer_wrapper_with_copy<libusbp_error>, public std::exception
{
public:
/*! Constructor that takes a pointer. */
explicit error(libusbp_error * p = nullptr) noexcept
: unique_pointer_wrapper_with_copy(p)
{
}
/*! Wrapper for libusbp_error_get_message(). */
const char * what() const noexcept override {
return libusbp_error_get_message(pointer);
}
/*! Wrapper for libusbp_error_get_message() that returns a
* std::string. */
std::string message() const
{
return what();
}
/*! Wrapper for libusbp_error_has_code(). */
bool has_code(uint32_t error_code) const noexcept
{
return libusbp_error_has_code(pointer, error_code);
}
};
/*! \cond */
inline void throw_if_needed(libusbp_error * err)
{
if (err != nullptr)
{
throw error(err);
}
}
/*! \endcond */
/*! Wrapper for a ::libusbp_async_in_pipe pointer. */
class async_in_pipe : public unique_pointer_wrapper<libusbp_async_in_pipe>
{
public:
/*! Constructor that takes a pointer. */
explicit async_in_pipe(libusbp_async_in_pipe * pointer = nullptr)
: unique_pointer_wrapper(pointer)
{
}
/*! Wrapper for libusbp_async_in_pipe_allocate_transfers(). */
void allocate_transfers(size_t transfer_count, size_t transfer_size)
{
throw_if_needed(libusbp_async_in_pipe_allocate_transfers(
pointer, transfer_count, transfer_size));
}
/*! Wrapper for libusbp_async_in_pipe_start_endless_transfers(). */
void start_endless_transfers()
{
throw_if_needed(libusbp_async_in_pipe_start_endless_transfers(pointer));
}
/*! Wrapper for libusbp_async_in_pipe_handle_events(). */
void handle_events()
{
throw_if_needed(libusbp_async_in_pipe_handle_events(pointer));
}
/*! Wrapper for libusbp_async_in_pipe_has_pending_transfers(). */
bool has_pending_transfers()
{
bool result;
throw_if_needed(libusbp_async_in_pipe_has_pending_transfers(pointer, &result));
return result;
}
/*! Wrapper for libusbp_async_in_pipe_handle_finished_transfer(). */
bool handle_finished_transfer(void * buffer, size_t * transferred,
error * transfer_error)
{
libusbp_error ** error_out = nullptr;
if (transfer_error != nullptr)
{
transfer_error->pointer_reset();
error_out = transfer_error->pointer_to_pointer_get();
}
bool finished;
throw_if_needed(libusbp_async_in_pipe_handle_finished_transfer(
pointer, &finished, buffer, transferred, error_out));
return finished;
}
/*! Wrapper for libusbp_async_in_pipe_cancel_transfers(). */
void cancel_transfers()
{
throw_if_needed(libusbp_async_in_pipe_cancel_transfers(pointer));
}
};
/*! Wrapper for a ::libusbp_device pointer. */
class device : public unique_pointer_wrapper_with_copy<libusbp_device>
{
public:
/*! Constructor that takes a pointer. */
explicit device(libusbp_device * pointer = nullptr) :
unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_device_get_vendor_id(). */
uint16_t get_vendor_id() const
{
uint16_t id;
throw_if_needed(libusbp_device_get_vendor_id(pointer, &id));
return id;
}
/*! Wrapper for libusbp_device_get_product_id(). */
uint16_t get_product_id() const
{
uint16_t id;
throw_if_needed(libusbp_device_get_product_id(pointer, &id));
return id;
}
/*! Wrapper for libusbp_device_get_revision(). */
uint16_t get_revision() const
{
uint16_t r;
throw_if_needed(libusbp_device_get_revision(pointer, &r));
return r;
}
/*! Wrapper for libusbp_device_get_serial_number(). */
std::string get_serial_number() const
{
char * str;
throw_if_needed(libusbp_device_get_serial_number(pointer, &str));
std::string serial_number = str;
libusbp_string_free(str);
return serial_number;
}
/*! Wrapper for libusbp_device_get_os_id(). */
std::string get_os_id() const
{
char * str;
throw_if_needed(libusbp_device_get_os_id(pointer, &str));
std::string serial_number = str;
libusbp_string_free(str);
return serial_number;
}
};
/*! Wrapper for libusbp_list_connected_devices(). */
inline std::vector<libusbp::device> list_connected_devices()
{
libusbp_device ** device_list;
size_t size;
throw_if_needed(libusbp_list_connected_devices(&device_list, &size));
std::vector<device> vector;
for(size_t i = 0; i < size; i++)
{
vector.emplace_back(device_list[i]);
}
libusbp_list_free(device_list);
return vector;
}
/*! Wrapper for libusbp_find_device_with_vid_pid(). */
inline libusbp::device find_device_with_vid_pid(uint16_t vendor_id, uint16_t product_id)
{
libusbp_device * device_pointer;
throw_if_needed(libusbp_find_device_with_vid_pid(
vendor_id, product_id, &device_pointer));
return device(device_pointer);
}
/*! Wrapper for a ::libusbp_generic_interface pointer. */
class generic_interface : public unique_pointer_wrapper_with_copy<libusbp_generic_interface>
{
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit generic_interface(libusbp_generic_interface * pointer = nullptr)
: unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_generic_interface_create. */
explicit generic_interface(const device & device,
uint8_t interface_number = 0, bool composite = false)
{
throw_if_needed(libusbp_generic_interface_create(
device.pointer_get(), interface_number, composite, &pointer));
}
/*! Wrapper for libusbp_generic_interface_get_os_id(). */
std::string get_os_id() const
{
char * str;
throw_if_needed(libusbp_generic_interface_get_os_id(pointer, &str));
std::string id = str;
libusbp_string_free(str);
return id;
}
/*! Wrapper for libusbp_generic_interface_get_os_filename(). */
std::string get_os_filename() const
{
char * str;
throw_if_needed(libusbp_generic_interface_get_os_filename(pointer, &str));
std::string filename = str;
libusbp_string_free(str);
return filename;
}
};
/*! Wrapper for a ::libusbp_generic_handle pointer. */
class generic_handle : public unique_pointer_wrapper<libusbp_generic_handle>
{
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit generic_handle(libusbp_generic_handle * pointer = nullptr) noexcept
: unique_pointer_wrapper(pointer)
{
}
/*! Wrapper for libusbp_generic_handle_open(). */
explicit generic_handle(const generic_interface & gi)
{
throw_if_needed(libusbp_generic_handle_open(gi.pointer_get(), &pointer));
}
/*! Wrapper for libusbp_generic_handle_close(). */
void close() noexcept
{
pointer_reset();
}
/*! Wrapper for libusbp_generic_handle_open_async_in_pipe(). */
async_in_pipe open_async_in_pipe(uint8_t pipe_id)
{
libusbp_async_in_pipe * pipe;
throw_if_needed(libusbp_generic_handle_open_async_in_pipe(
pointer, pipe_id, &pipe));
return async_in_pipe(pipe);
}
/*! Wrapper for libusbp_generic_handle_set_timeout(). */
void set_timeout(uint8_t pipe_id, uint32_t timeout)
{
throw_if_needed(libusbp_generic_handle_set_timeout(pointer, pipe_id, timeout));
}
/*! Wrapper for libusbp_control_transfer(). */
void control_transfer(
uint8_t bmRequestType,
uint8_t bRequest,
uint16_t wValue,
uint16_t wIndex,
void * buffer = nullptr,
uint16_t wLength = 0,
size_t * transferred = nullptr)
{
throw_if_needed(libusbp_control_transfer(pointer,
bmRequestType, bRequest, wValue, wIndex,
buffer, wLength, transferred));
}
/*! Wrapper for libusbp_write_pipe(). */
void write_pipe(uint8_t pipe_id, const void * buffer,
size_t size, size_t * transferred)
{
throw_if_needed(libusbp_write_pipe(pointer,
pipe_id, buffer, size, transferred));
}
/*! Wrapper for libusbp_read_pipe(). */
void read_pipe(uint8_t pipe_id, void * buffer,
size_t size, size_t * transferred)
{
throw_if_needed(libusbp_read_pipe(pointer,
pipe_id, buffer, size, transferred));
}
#ifdef _WIN32
/*! Wrapper for libusbp_generic_handle_get_winusb_handle(). */
HANDLE get_winusb_handle()
{
return libusbp_generic_handle_get_winusb_handle(pointer);
}
#endif
#ifdef __linux__
/*! Wrapper for libusbp_generic_handle_get_fd(). */
int get_fd()
{
return libusbp_generic_handle_get_fd(pointer);
}
#endif
#ifdef __APPLE__
/*! Wrapper for libusbp_generic_handle_get_cf_plug_in(). */
void ** get_cf_plug_in()
{
return libusbp_generic_handle_get_cf_plug_in(pointer);
}
#endif
};
/*! Wrapper for a ::libusbp_serial_port pointer. */
class serial_port : public unique_pointer_wrapper_with_copy<libusbp_serial_port>
{
public:
/*! Constructor that takes a pointer. This object will free the pointer
* when it is destroyed. */
explicit serial_port(libusbp_serial_port * pointer = nullptr)
: unique_pointer_wrapper_with_copy(pointer)
{
}
/*! Wrapper for libusbp_serial_port_create(). */
explicit serial_port(const device & device,
uint8_t interface_number = 0, bool composite = false)
{
throw_if_needed(libusbp_serial_port_create(
device.pointer_get(), interface_number, composite, &pointer));
}
/*! Wrapper for libusbp_serial_port_get_name(). */
std::string get_name() const
{
char * str;
throw_if_needed(libusbp_serial_port_get_name(pointer, &str));
std::string id = str;
libusbp_string_free(str);
return id;
}
};
}

View file

@ -0,0 +1,8 @@
#pragma once
#define BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR 1
#define LIBUSBP_STATIC
#undef LIBUSBP_LOG
#undef VBOX_LINUX_ON_WINDOWS
#undef USE_TEST_DEVICE_A
#undef USE_TEST_DEVICE_B

View file

@ -0,0 +1,15 @@
use_c99()
add_library (install_helper SHARED install_helper_windows.c dll.def)
target_link_libraries (install_helper setupapi msi)
set_target_properties (install_helper PROPERTIES
OUTPUT_NAME usbp-install-helper-${LIBUSBP_VERSION_MAJOR}
LINK_FLAGS "-Wl,--enable-stdcall-fixup -static"
)
install (TARGETS install_helper
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

View file

@ -0,0 +1,3 @@
The files in this directory compile a separate, statically linked DLL named
`libusbp-install-helper-<x>.dll` that can be useful in installers of USB
software.

View file

@ -0,0 +1,5 @@
EXPORTS
libusbp_install_inf
libusbp_install_infW
libusbp_broadcast_setting_change
libusbp_broadcast_setting_changeW

View file

@ -0,0 +1,167 @@
/* This file contains special functions that can help install an application
* that uses USB. The functions can be used from an MSI custom action or from
* rundll32. */
#include <windows.h>
#include <devpropdef.h>
#include <msiquery.h>
#include <setupapi.h>
#include <strsafe.h>
#define LIBUSBP_UNUSED(param_name) (void)param_name;
typedef struct install_context
{
HWND owner;
MSIHANDLE install;
} install_context;
static void log_message(install_context * context, LPCWSTR message)
{
if (context->install == 0)
{
// The MSI handle is not available so just ignore log messages.
}
else
{
// Report the log message through MSI, which will put it in the log
// file.
MSIHANDLE record = MsiCreateRecord(1);
MsiRecordSetStringW(record, 0, message);
MsiProcessMessage(context->install, INSTALLMESSAGE_INFO, record);
MsiCloseHandle(record);
}
}
// Adds an error message to the Windows Installer log file and displays it
// to the user in a dialog box with an OK button.
static void error_message(install_context * context, LPCWSTR message)
{
if (context->install == 0)
{
// The MSIHANDLE is not available, so just display a dialog box.
MessageBoxW(context->owner, message, L"Installation Error", MB_ICONERROR);
}
else
{
// Report the error through MSI, which will in turn display the dialog
// box.
MSIHANDLE record = MsiCreateRecord(1);
MsiRecordSetStringW(record, 0, message);
MsiProcessMessage(context->install, INSTALLMESSAGE_ERROR, record);
MsiCloseHandle(record);
}
}
/* You might need to call this function after modifying the PATH in order to
* notify other programs about the change. This allows a newly launched Command
* Prompt to see that the PATH has changed and start using it. */
static void broadcast_setting_change_core(install_context * context)
{
DWORD_PTR result2 = 0;
LRESULT result = SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
(LPARAM)L"Environment", SMTO_ABORTIFHUNG, 5000, &result2);
if (result == 0)
{
WCHAR message[1024];
StringCbPrintfW(message, sizeof(message),
L"SendMessageTimeout failed: Error code 0x%lx. Result %d",
GetLastError(), result2);
error_message(context, message);
}
}
// Usage: rundll32 libusbp*.dll libusbp_broadcast_setting_change
void __stdcall libusbp_broadcast_setting_changeW(
HWND owner, HINSTANCE hinst, LPWSTR args, int n)
{
LIBUSBP_UNUSED(hinst);
LIBUSBP_UNUSED(args);
LIBUSBP_UNUSED(n);
install_context context = {0};
context.owner = owner;
broadcast_setting_change_core(&context);
}
// Usage: make a Custom Action in an MSI with this function as the entry point.
UINT __stdcall libusbp_broadcast_setting_change(MSIHANDLE install)
{
install_context context = {0};
context.install = install;
log_message(&context, L"libusbp_broadcast_setting_change: Begin.");
broadcast_setting_change_core(&context);
log_message(&context, L"libusbp_broadcast_setting_change: End.");
return 0; // Always return success.
}
/* Calls SetupCopyOEMInf to install the specified INF file. The user may be
* prompted to accept the driver software, and if everything works then the file
* will be copied to the C:\Windows\inf directory. */
static void install_inf_core(install_context * context, LPWSTR filename)
{
BOOL success = SetupCopyOEMInfW(filename, NULL, SPOST_PATH, 0, NULL, 0, NULL, NULL);
if (!success)
{
WCHAR message[1024];
// NOTE: newlines do not show up in the MSI log, but they do show up in
// the MSI error dialog box.
StringCbPrintfW(message, sizeof(message),
L"There was an error installing the driver file %s. \n"
L"You might have to manually install this file by right-clicking it "
L"and selecting \"Install\". \n"
L"Error code 0x%lx.", filename, GetLastError());
error_message(context, message);
}
}
// Usage: rundll32 libusbp*.dll libusbp_install_inf path
void __stdcall libusbp_install_infW(HWND owner, HINSTANCE hinst, LPWSTR args, int n)
{
LIBUSBP_UNUSED(hinst);
LIBUSBP_UNUSED(args);
LIBUSBP_UNUSED(n);
install_context context = {0};
context.owner = owner;
install_inf_core(&context, args);
}
// Usage: make a Custom Action in your installer with a "CustomActionData"
// property set equal to the full path of the inf file.
UINT __stdcall libusbp_install_inf(MSIHANDLE install)
{
install_context context = {0};
context.install = install;
broadcast_setting_change_core(&context);
log_message(&context, L"libusbp_install_inf: Begin.");
WCHAR message[1024];
// Get the name of inf file.
WCHAR filename[1024];
DWORD length = 1024;
UINT result = MsiGetPropertyW(install, L"CustomActionData", filename, &length);
if (result != ERROR_SUCCESS)
{
StringCbPrintfW(message, sizeof(message),
L"libusbp_install_inf: Unable to get filename parameter. Error code %d.", result);
error_message(&context, message);
return 0; // Return success anyway.
}
StringCbPrintfW(message, sizeof(message), L"libusbp_install_inf: filename=%s", filename);
log_message(&context, message);
install_inf_core(&context, filename);
StringCbPrintfW(message, sizeof(message), L"libusbp_install_inf: End. result2=%d", result);
log_message(&context, message);
// Always return 0 even if there was an error, because we don't want to roll
// back the rest of the installation just because this part fails. The user
// can either manually install the INF files or try again.
return 0;
}

View file

@ -0,0 +1,4 @@
add_subdirectory(test_async_in)
add_subdirectory(test_long_read)
add_subdirectory(test_long_write)
add_subdirectory(test_transitions)

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_async_in test_async_in.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_async_in usbp)

View file

@ -0,0 +1,107 @@
/* Tests that libusbp is capable of reliably reading data from an IN endpoint on
* every frame using asynchronous transfers. It prints to the standard output
* as it successfully receives data, and prints to the standard error if there
* was a gap in the data (a USB frame where the device did not generate a new
* packet for the host).
*
* You can also check the CPU usage while running this function to make
* sure libusbp is not doing anything too inefficient.
*/
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
#ifdef _MSC_VER
#define usleep(x) Sleep(((x) + 999) / 1000)
#else
#include <unistd.h>
#endif
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x82;
const size_t packet_size = 5;
const size_t transfer_size = packet_size;
const size_t transfer_count = 250;
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
libusbp::async_in_pipe pipe = handle.open_async_in_pipe(endpoint_address);
pipe.allocate_transfers(transfer_count, transfer_size);
pipe.start_endless_transfers();
uint8_t last_f = 0;
uint32_t finish_count = 0;
while(true)
{
uint8_t buffer[transfer_size];
size_t transferred;
libusbp::error transfer_error;
while(pipe.handle_finished_transfer(buffer, &transferred, &transfer_error))
{
if (transfer_error)
{
fprintf(stderr, "Transfer error.\n");
throw transfer_error;
}
if (transferred != transfer_size)
{
fprintf(stderr, "Got %d bytes instead of %d.\n",
(int)transferred, (int)transfer_size);
}
uint8_t f = buffer[0];
if (f != (uint8_t)(last_f + transfer_size/packet_size))
{
// If this happens, it indicates there was a USB frame where the
// device did not generate a new packet for the host, which is
// bad. However, you should expect to see a few of these at the
// very beginning of the test because there will be some old
// packets queued up in the device from earlier, and because
// last_f always starts at 0.
fprintf(stderr, "Frame number gap: %d to %d\n", last_f, f);
}
last_f = f;
if ((++finish_count % 4096) == 0)
{
printf("Another 4096 transfers done.\n");
fflush(stdout);
}
}
pipe.handle_events();
usleep(20000);
}
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_long_read test_long_read.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_long_read usbp)

View file

@ -0,0 +1,90 @@
/* Tests what happens if we do a synchronous read from an IN endpoint that
* takes a long time to complete. This can be used to check that pressing
* Ctrl+C is able to interrupt the read. */
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x82;
const size_t packet_size = 5;
const size_t transfer_size = packet_size * 10000;
void long_read(libusbp::generic_handle & handle)
{
uint8_t buffer[transfer_size];
size_t transferred;
printf("Reading %d bytes...\n", (unsigned int)transfer_size);
fflush(stdout);
handle.read_pipe(endpoint_address, buffer, sizeof(buffer), &transferred);
if (transferred == transfer_size)
{
printf("Transfer successful.\n");
}
else
{
printf("Transferred only %d bytes out of %d.\n",
(unsigned int)transferred, (unsigned int)transfer_size);
}
fflush(stdout);
}
void long_control_read(libusbp::generic_handle & handle)
{
uint8_t buffer[5];
size_t transferred;
printf("Performing a slow control read...\n");
fflush(stdout);
handle.control_transfer(0xC0, 0x91, 10000, 5, buffer, sizeof(buffer), &transferred);
if (transferred == 5)
{
printf("Control read successful.\n");
}
else
{
printf("Transferred only %d bytes out of %d.\n",
(unsigned int)transferred, (unsigned int)sizeof(buffer));
}
fflush(stdout);
}
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
long_read(handle);
long_control_read(handle);
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_long_write test_long_write.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_long_write usbp)

View file

@ -0,0 +1,70 @@
/* Tests what happens if we do a synchronous write that takes a long time to
* complete. This can be used to check that pressing Ctrl+C is able to
* interrupt the write. */
#include <libusbp.hpp>
#include <stdio.h>
#include <iostream>
const uint16_t vendor_id = 0x1FFB;
const uint16_t product_id = 0xDA01;
const uint8_t interface_number = 0;
const bool composite = true;
const uint8_t endpoint_address = 0x03;
const size_t packet_size = 32;
const size_t transfer_size = packet_size * 4;
void long_write(libusbp::generic_handle & handle)
{
// First packet causes a delay of 4 seconds (0x0FA0 ms)
uint8_t buffer[transfer_size] = { 0xDE, 0xA0, 0x0F };
size_t transferred;
printf("Writing...\n");
fflush(stdout);
handle.write_pipe(endpoint_address, buffer, sizeof(buffer), &transferred);
if (transferred == transfer_size)
{
printf("Transfer successful.\n");
}
else
{
printf("Transferred only %d bytes out of %d.\n",
(unsigned int)transferred, (unsigned int)transfer_size);
}
}
int main_with_exceptions()
{
libusbp::device device = libusbp::find_device_with_vid_pid(
vendor_id, product_id);
if (!device)
{
std::cerr << "Device not found." << std::endl;
return 1;
}
libusbp::generic_interface gi(device, interface_number, composite);
libusbp::generic_handle handle(gi);
long_write(handle);
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,9 @@
use_cxx11()
add_executable(test_transitions test_transitions.cpp)
include_directories (
"${CMAKE_SOURCE_DIR}/include"
)
target_link_libraries(test_transitions usbp)

View file

@ -0,0 +1,98 @@
/* This program helps us test the transitions that a USB device goes through as
* it gets connected or disconnected from a computer. It helps us identify
* errors that might occur so we can assign code to them such as
* LIBUSBP_ERROR_NOT_READY. */
#include <libusbp.hpp>
#include <iostream>
#include <iomanip>
#include <chrono>
#ifdef _MSC_VER
#define usleep(x) Sleep(((x) + 999) / 1000)
#else
#include <unistd.h>
#endif
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 6
typedef std::chrono::monotonic_clock clock_type;
#else
typedef std::chrono::steady_clock clock_type;
#endif
std::ostream & log()
{
return std::cout
<< clock_type::now().time_since_epoch().count()
<< ": ";
}
void check_test_device_a(libusbp::device device)
{
static std::string current_status;
std::string status;
if (device)
{
status = "Found " + device.get_serial_number() + ".";
// Try to connect to the generic interface.
try
{
libusbp::generic_interface gi(device, 0, true);
libusbp::generic_handle handle(gi);
status += " Interface 0 works.";
}
catch(const libusbp::error & error)
{
status += " Interface 0 error: " + error.message();
if (!error.has_code(LIBUSBP_ERROR_NOT_READY))
{
status += " Lacks code LIBUSBP_ERROR_NOT_READY!";
}
}
}
else
{
status = "Not found.";
}
if (current_status != status)
{
log() << "Test device A: " << status << std::endl;
current_status = status;
}
}
int main_with_exceptions()
{
std::cout
<< "Clock tick period: "
<< clock_type::period::num
<< "/"
<< clock_type::period::den
<< " seconds" << std::endl;
while(1)
{
check_test_device_a(libusbp::find_device_with_vid_pid(0x1FFB, 0xDA01));
usleep(10);
}
return 0;
}
int main(int argc, char ** argv)
{
// Suppress unused parameter warnings.
(void)argc;
(void)argv;
try
{
return main_with_exceptions();
}
catch(const std::exception & error)
{
std::cerr << "Error: " << error.what() << std::endl;
}
return 1;
}

View file

@ -0,0 +1,116 @@
use_c99()
# Settings for GCC
if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
# By default, symbols are not visible outside of the library.
set (CMAKE_C_FLAGS "-fvisibility=hidden ${CMAKE_C_FLAGS}")
# Avoid tons of errors from strsafe.h.
set (CMAKE_C_FLAGS "-fgnu89-inline ${CMAKE_C_FLAGS}")
endif ()
# Define cross-platform source files.
set (sources
async_in_pipe.c
error.c
error_hresult.c
find_device.c
list.c
pipe_id.c
string.c)
# Define operating system-specific source files.
if (WIN32)
set (sources ${sources}
windows/error_windows.c
windows/device_windows.c
windows/interface_windows.c
windows/device_instance_id_windows.c
windows/generic_interface_windows.c
windows/list_windows.c
windows/generic_handle_windows.c
windows/async_in_transfer_windows.c
windows/serial_port_windows.c
${CMAKE_CURRENT_BINARY_DIR}/info.rc)
elseif (LINUX)
set (sources ${sources}
linux/list_linux.c
linux/device_linux.c
linux/generic_interface_linux.c
linux/generic_handle_linux.c
linux/error_linux.c
linux/udev_linux.c
linux/usbfd_linux.c
linux/async_in_transfer_linux.c
linux/serial_port_linux.c)
elseif (APPLE)
set (sources ${sources}
mac/list_mac.c
mac/device_mac.c
mac/error_mac.c
mac/generic_interface_mac.c
mac/generic_handle_mac.c
mac/async_in_transfer_mac.c
mac/serial_port_mac.c
mac/iokit_mac.c)
endif ()
add_library (usbp ${sources})
include_directories (
"${CMAKE_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}"
)
if (WIN32)
target_link_libraries (usbp setupapi winusb uuid ole32)
if (NOT BUILD_SHARED_LIBS)
set (PC_MORE_LIBS "-lsetupapi -lwinusb -luuid -lole32")
endif ()
elseif (LINUX)
pkg_check_modules(LIBUDEV REQUIRED libudev)
string (REPLACE ";" " " LIBUDEV_CFLAGS "${LIBUDEV_CFLAGS}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LIBUDEV_CFLAGS}")
target_link_libraries (usbp udev)
if (NOT BUILD_SHARED_LIBS)
set (PC_REQUIRES "libudev")
endif ()
elseif (APPLE)
set (link_flags "-framework IOKit -framework CoreFoundation ${link_flags}")
if (NOT BUILD_SHARED_LIBS)
set (PC_MORE_LIBS "${link_flags}")
endif ()
endif ()
set_target_properties(usbp PROPERTIES
OUTPUT_NAME usbp-${LIBUSBP_VERSION_MAJOR}
SOVERSION ${LIBUSBP_VERSION}
VERSION ${LIBUSBP_VERSION}
DEFINE_SYMBOL LIBUSBP_EXPORTS
LINK_FLAGS "${link_flags}"
)
configure_file (
"libusbp_config.h.in"
"libusbp_config.h"
)
configure_file (
"info.rc.in"
"info.rc"
)
configure_file (
"libusbp.pc.in"
"libusbp-${LIBUSBP_VERSION_MAJOR}.pc"
@ONLY
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libusbp-${LIBUSBP_VERSION_MAJOR}.pc"
DESTINATION lib/pkgconfig)
install(TARGETS usbp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

View file

@ -0,0 +1,343 @@
#include <libusbp_internal.h>
struct libusbp_async_in_pipe
{
libusbp_generic_handle * handle;
uint8_t pipe_id;
async_in_transfer ** transfer_array;
size_t transfer_size;
size_t transfer_count;
bool endless_transfers_enabled;
// The number of transfers that are pending, meaning that they were
// submitted (and possibly completed by the kernel) but not finished (handed
// to the user of the pipe) yet. This variable allows us to distinguish
// between the state where no transfers are pending and the state where all
// of them are pending. Note that this definition of pending is different
// than the definition of pending in the async_in_transfer struct.
size_t pending_count;
// The index of the transfer that should finish next. That transfer will be
// pending (and thus able to be checked) if pending_count > 0.
size_t next_finish;
// The index of the transfer that will be submitted next when we need to
// submit more transfers. That transfer can be submitted if pending_count <
// transfer_count.
size_t next_submit;
};
static inline size_t increment_and_wrap_size(size_t n, size_t bound)
{
n++;
return n >= bound ? 0 : n;
}
static void async_in_transfer_array_free(async_in_transfer ** array, size_t transfer_count)
{
if (array == NULL) { return; }
for (size_t i = 0; i < transfer_count; i++)
{
async_in_transfer_free(array[i]);
}
free(array);
}
void libusbp_async_in_pipe_close(libusbp_async_in_pipe * pipe)
{
if (pipe != NULL)
{
async_in_transfer_array_free(pipe->transfer_array, pipe->transfer_count);
free(pipe);
}
}
libusbp_error * async_in_pipe_create(libusbp_generic_handle * handle,
uint8_t pipe_id, libusbp_async_in_pipe ** pipe)
{
// Check the pipe output pointer.
if (pipe == NULL)
{
return error_create("Pipe output pointer is null.");
}
*pipe = NULL;
if (handle == NULL)
{
return error_create("Generic handle is null.");
}
libusbp_error * error = NULL;
// Check the pipe_id parameter.
if (error == NULL)
{
error = check_pipe_id(pipe_id);
}
if (error == NULL && !(pipe_id & 0x80))
{
error = error_create("Asynchronous pipes for OUT endpoints are not supported.");
}
// Perform OS-specific setup.
if (error == NULL)
{
error = async_in_pipe_setup(handle, pipe_id);
}
libusbp_async_in_pipe * new_pipe = NULL;
if (error == NULL)
{
new_pipe = calloc(1, sizeof(libusbp_async_in_pipe));
if (new_pipe == NULL)
{
error = &error_no_memory;
}
}
if (error == NULL)
{
new_pipe->handle = handle;
new_pipe->pipe_id = pipe_id;
*pipe = new_pipe;
new_pipe = NULL;
}
free(new_pipe);
return error;
}
libusbp_error * libusbp_async_in_pipe_allocate_transfers(
libusbp_async_in_pipe * pipe,
size_t transfer_count,
size_t transfer_size)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
if (pipe->transfer_array != NULL)
{
return error_create("Transfers were already allocated for this pipe.");
}
if (transfer_count == 0)
{
return error_create("Transfer count cannot be zero.");
}
if (transfer_size == 0)
{
return error_create("Transfer size cannot be zero.");
}
libusbp_error * error = NULL;
async_in_transfer ** new_transfer_array = NULL;
if (error == NULL)
{
new_transfer_array = calloc(transfer_count, sizeof(async_in_transfer *));
if (new_transfer_array == NULL)
{
error = &error_no_memory;
}
}
for(size_t i = 0; error == NULL && i < transfer_count; i++)
{
error = async_in_transfer_create(pipe->handle, pipe->pipe_id,
transfer_size, &new_transfer_array[i]);
}
// Put the new array and the information about it into the pipe.
if (error == NULL)
{
pipe->transfer_array = new_transfer_array;
pipe->transfer_count = transfer_count;
pipe->transfer_size = transfer_size;
new_transfer_array = NULL;
}
async_in_transfer_array_free(new_transfer_array, transfer_count);
if (error != NULL)
{
error = error_add(error, "Failed to allocate transfers for asynchronous IN pipe.");
}
return error;
}
static void async_in_pipe_submit_next_transfer(libusbp_async_in_pipe * pipe)
{
assert(pipe != NULL);
assert(pipe->pending_count < pipe->transfer_count);
// Submit the next transfer.
async_in_transfer_submit(pipe->transfer_array[pipe->next_submit]);
// Update the counts and indices.
pipe->pending_count++;
pipe->next_submit = increment_and_wrap_size(pipe->next_submit, pipe->transfer_count);
}
libusbp_error * libusbp_async_in_pipe_start_endless_transfers(
libusbp_async_in_pipe * pipe)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
if (pipe->transfer_array == NULL)
{
return error_create("Pipe transfers have not been allocated yet.");
}
pipe->endless_transfers_enabled = true;
while(pipe->pending_count < pipe->transfer_count)
{
async_in_pipe_submit_next_transfer(pipe);
}
return NULL;
}
libusbp_error * libusbp_async_in_pipe_handle_events(libusbp_async_in_pipe * pipe)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
return generic_handle_events(pipe->handle);
}
libusbp_error * libusbp_async_in_pipe_has_pending_transfers(
libusbp_async_in_pipe * pipe,
bool * result)
{
libusbp_error * error = NULL;
if (error == NULL && result == NULL)
{
error = error_create("Boolean output pointer is null.");
}
if (error == NULL)
{
*result = false;
}
if (error == NULL && pipe == NULL)
{
error = error_create("Pipe argument is null.");
}
if (error == NULL)
{
*result = pipe->pending_count ? 1 : 0;
}
return error;
}
libusbp_error * libusbp_async_in_pipe_handle_finished_transfer(
libusbp_async_in_pipe * pipe,
bool * finished,
void * buffer,
size_t * transferred,
libusbp_error ** transfer_error)
{
if (finished != NULL)
{
*finished = false;
}
if (transferred != NULL)
{
*transferred = 0;
}
if (transfer_error != NULL)
{
*transfer_error = NULL;
}
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
if (pipe->pending_count == 0)
{
// There are no pending transfers that we could check for completion.
return NULL;
}
async_in_transfer * transfer = pipe->transfer_array[pipe->next_finish];
if (async_in_transfer_pending(transfer))
{
// The next transfer we expect to finish is still pending;
// the kernel has not told us that it is done.
return NULL;
}
libusbp_error * error = async_in_transfer_get_results(transfer, buffer,
transferred, transfer_error);
if (error == NULL)
{
if (finished != NULL)
{
*finished = true;
}
pipe->pending_count--;
pipe->next_finish = increment_and_wrap_size(pipe->next_finish, pipe->transfer_count);
}
if (error == NULL && pipe->endless_transfers_enabled)
{
async_in_pipe_submit_next_transfer(pipe);
}
return error;
}
libusbp_error * libusbp_async_in_pipe_cancel_transfers(libusbp_async_in_pipe * pipe)
{
if (pipe == NULL)
{
return error_create("Pipe argument is null.");
}
pipe->endless_transfers_enabled = false;
libusbp_error * error = NULL;
#ifdef __linux__
// In Linux, transfers need to be cancelled individually.
for (size_t i = 0; error == NULL && i < pipe->transfer_count; i++)
{
error = async_in_transfer_cancel(pipe->transfer_array[i]);
// This doesn't help the performance issue in this function:
//if (error == NULL) { error = generic_handle_events(pipe->handle); }
}
#else
// On other platforms, any of the transfers has all the information needed
// to cancel the others.
error = async_in_transfer_cancel(pipe->transfer_array[0]);
#endif
return error;
}

223
dep/libusbp/src/error.c Normal file
View file

@ -0,0 +1,223 @@
#include <libusbp_internal.h>
#ifdef _WIN32
#if (defined(__GNUC__) && !defined(__USE_MINGW_ANSI_STDIO)) || (defined(_MSC_VER) && _MSC_VER < 1900)
#error This code depends on vsnprintf returning a number of characters.
#endif
#endif
struct libusbp_error
{
bool do_not_free;
char * message;
size_t code_count;
uint32_t * code_array;
};
static uint32_t mem_error_code_array[1] = { LIBUSBP_ERROR_MEMORY };
static char error_no_memory_msg[] = "Failed to allocate memory.";
libusbp_error error_no_memory =
{
.do_not_free = true,
.message = error_no_memory_msg,
.code_count = 1,
.code_array = mem_error_code_array,
};
static char error_masked_by_no_memory_msg[] = "Failed to allocate memory for reporting an error.";
static libusbp_error error_masked_by_no_memory =
{
.do_not_free = true,
.message = error_masked_by_no_memory_msg,
.code_count = 1,
.code_array = mem_error_code_array,
};
static libusbp_error error_blank =
{
.do_not_free = true,
.message = NULL,
.code_count = 0,
.code_array = NULL,
};
void libusbp_error_free(libusbp_error * error)
{
if (error != NULL && !error->do_not_free)
{
free(error->message);
free(error->code_array);
free(error);
}
}
// Copies the error. If the input is not NULL, the output will always
// be not NULL, but it might be a immutable error (do_not_free=1).
libusbp_error * libusbp_error_copy(const libusbp_error * src_error)
{
if (src_error == NULL) { return NULL; }
const char * src_message = src_error->message;
if (src_message == NULL) { src_message = ""; }
size_t message_length = strlen(src_message);
size_t code_count = src_error->code_count;
if (src_error->code_array == NULL) { code_count = 0; }
// Allocate memory.
libusbp_error * new_error = malloc(sizeof(libusbp_error));
char * new_message = malloc(message_length + 1);
uint32_t * new_code_array = malloc(code_count * sizeof(uint32_t));
if (new_error == NULL || new_message == NULL ||
(code_count != 0 && new_code_array == NULL))
{
free(new_error);
free(new_message);
free(new_code_array);
return &error_masked_by_no_memory;
}
if (code_count != 0)
{
memcpy(new_code_array, src_error->code_array, code_count * sizeof(uint32_t));
}
strncpy(new_message, src_message, message_length + 1);
new_error->do_not_free = false;
new_error->message = new_message;
new_error->code_count = code_count;
new_error->code_array = new_code_array;
return new_error;
}
// Tries to make the error mutable. Always returns a non-NULL error,
// but it might return an immutable error (do_not_free == 1) if memory
// cannot be allocated.
static libusbp_error * error_make_mutable(libusbp_error * error)
{
if (error == NULL) { error = &error_blank; }
if (error->do_not_free)
{
error = libusbp_error_copy(error);
}
return error;
}
// Tries to add a message to the error. After calling this, you
// should not use the error you passed as an input, because it might
// have been freed.
libusbp_error * error_add_v(libusbp_error * error, const char * format, va_list ap)
{
if (format == NULL) { return error; }
error = error_make_mutable(error);
if (error == NULL || error->do_not_free) { return error; }
if (error->message == NULL) { error->message = ""; }
// Determine all the string lengths.
size_t outer_message_length = 0;
{
char x[1];
va_list ap2;
va_copy(ap2, ap);
int result = vsnprintf(x, 0, format, ap2);
if (result > 0)
{
outer_message_length = (size_t) result;
}
va_end(ap2);
}
size_t inner_message_length = strlen(error->message);
size_t separator_length = (inner_message_length && outer_message_length) ? 2 : 0;
size_t message_length = outer_message_length + separator_length + inner_message_length;
char * message = malloc(message_length + 1);
if (message == NULL)
{
libusbp_error_free(error);
return &error_masked_by_no_memory;
}
// Assemble the message.
vsnprintf(message, outer_message_length + 1, format, ap);
if (separator_length)
{
strncpy(message + outer_message_length, " ", separator_length + 1);
}
strncpy(message + outer_message_length + separator_length,
error->message, inner_message_length + 1);
message[message_length] = 0;
free(error->message);
error->message = message;
return error;
}
// Tries to add the specified code to the error.
// This is just like error_add_v in terms of pointer ownership.
libusbp_error * error_add_code(libusbp_error * error, uint32_t code)
{
error = error_make_mutable(error);
if (error == NULL || error->do_not_free) { return error; }
if (error->code_count >= SIZE_MAX / sizeof(uint32_t)) { return error; }
size_t size = (error->code_count + 1) * sizeof(uint32_t);
uint32_t * new_array = realloc(error->code_array, size);
if (new_array == NULL)
{
libusbp_error_free(error);
return &error_masked_by_no_memory;
}
error->code_array = new_array;
error->code_array[error->code_count++] = code;
return error;
}
// Variadic version of error_add_v.
libusbp_error * error_add(libusbp_error * error, const char * format, ...)
{
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}
// Creates a new error.
libusbp_error * error_create(const char * format, ...)
{
va_list ap;
va_start(ap, format);
libusbp_error * error = error_add_v(NULL, format, ap);
va_end(ap);
return error;
}
bool libusbp_error_has_code(const libusbp_error * error, uint32_t code)
{
if (error == NULL) { return false; }
for (size_t i = 0; i < error->code_count; i++)
{
if (error->code_array[i] == code)
{
return true;
}
}
return false;
}
const char * libusbp_error_get_message(const libusbp_error * error)
{
if (error == NULL)
{
return "No error.";
}
if (error->message == NULL)
{
return "";
}
return error->message;
}

View file

@ -0,0 +1,21 @@
#include <libusbp_internal.h>
#if defined(_WIN32) || defined(__APPLE__)
libusbp_error * error_create_hr(HRESULT hr, const char * format, ...)
{
// HRESULT should be an int32_t on the systems we care about (Mac OS X,
// Win32, Win64), but let's assert it here in case that ever changes.
assert(sizeof(HRESULT) == 4);
assert((HRESULT)-1 < (HRESULT)0);
libusbp_error * error = error_create("HRESULT error code 0x%x.", (int32_t)hr);
va_list ap;
va_start(ap, format);
error = error_add_v(error, format, ap);
va_end(ap);
return error;
}
#endif

View file

@ -0,0 +1,70 @@
#include <libusbp_internal.h>
static libusbp_error * check_device_vid_pid(const libusbp_device * device,
uint16_t vendor_id, uint16_t product_id, bool * matches)
{
assert(matches != NULL);
*matches = false;
libusbp_error * error;
uint16_t device_vid;
error = libusbp_device_get_vendor_id(device, &device_vid);
if (error != NULL) { return error; }
if (device_vid != vendor_id) { return NULL; }
uint16_t device_pid;
error = libusbp_device_get_product_id(device, &device_pid);
if (error != NULL) { return error; }
if (device_pid != product_id) { return NULL; }
*matches = true;
return NULL;
}
libusbp_error * libusbp_find_device_with_vid_pid(
uint16_t vendor_id, uint16_t product_id, libusbp_device ** device)
{
if (device == NULL)
{
return error_create("Device output pointer is null.");
}
*device = NULL;
libusbp_error * error = NULL;
libusbp_device ** new_list = NULL;
size_t size = 0;
error = libusbp_list_connected_devices(&new_list, &size);
assert(error != NULL || new_list != NULL);
for(size_t i = 0; error == NULL && i < size; i++)
{
// Each iteration of this loop checks one candidate device
// and either passes it to the caller or frees it.
libusbp_device * candidate = new_list[i];
if (*device == NULL)
{
bool matches;
error = check_device_vid_pid(candidate, vendor_id, product_id, &matches);
if (error != NULL) { break; }
if (matches)
{
// Return the device to the caller.
*device = candidate;
candidate = NULL;
}
}
libusbp_device_free(candidate);
}
libusbp_list_free(new_list);
return error;
}

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